Skip to main content

lean_ctx/core/knowledge/
snapshot.rs

1//! `KnowledgeSnapshot` — the single in-memory view of a project's durable
2//! knowledge that every *outbound* rendering shares.
3//!
4//! Before this existed, the Context Package builder read `knowledge.json` +
5//! `relations.json` inline and shaped its own `KnowledgeLayer`. Adding a second
6//! portable format (Open Knowledge Format, see [`super::okf`]) would have meant a
7//! *second* extractor reading the same stores with subtly different rules — the
8//! exact fragmentation the "one model, many renderings" positioning warns
9//! against. Instead both the ctxpkg `KnowledgeLayer` and the OKF Markdown bundle
10//! are rendered from this one snapshot, so they can never drift on *what counts
11//! as the project's knowledge*.
12//!
13//! The snapshot carries the full fact history (ctxpkg preserves superseded facts
14//! for fidelity); [`KnowledgeSnapshot::current_facts`] is the human-facing view
15//! portable exports use.
16
17use crate::core::knowledge_relations::{KnowledgeEdge, KnowledgeRelationGraph};
18
19use super::types::{ConsolidatedInsight, KnowledgeFact, ProjectKnowledge, ProjectPattern};
20
21/// A consistent read of a project's knowledge (facts + patterns + insights) and
22/// its relation graph, taken at one point in time. The single source of truth
23/// for outbound renderings (ctxpkg, OKF).
24#[derive(Debug, Clone)]
25pub struct KnowledgeSnapshot {
26    pub project_root: String,
27    pub project_hash: String,
28    /// All facts, including superseded ones (ctxpkg keeps the history).
29    pub facts: Vec<KnowledgeFact>,
30    pub patterns: Vec<ProjectPattern>,
31    /// Consolidated insights (the project's `history`).
32    pub insights: Vec<ConsolidatedInsight>,
33    /// Typed relations between facts (`relations.json`).
34    pub relations: Vec<KnowledgeEdge>,
35}
36
37impl KnowledgeSnapshot {
38    /// Loads a project's knowledge and relation graph from disk into one
39    /// snapshot. Missing stores yield empty collections rather than an error, so
40    /// callers can treat "no knowledge yet" via [`KnowledgeSnapshot::is_empty`].
41    pub fn collect(project_root: &str) -> Self {
42        let knowledge = ProjectKnowledge::load_or_create(project_root);
43        let relations = KnowledgeRelationGraph::load(&knowledge.project_hash)
44            .map(|g| g.edges)
45            .unwrap_or_default();
46        Self::from_project(&knowledge, relations)
47    }
48
49    /// Builds a snapshot from an already-loaded `ProjectKnowledge` plus its
50    /// relation edges. Keeps the collection logic in one place for callers that
51    /// already hold the knowledge (e.g. inside a lock).
52    pub fn from_project(knowledge: &ProjectKnowledge, relations: Vec<KnowledgeEdge>) -> Self {
53        Self {
54            project_root: knowledge.project_root.clone(),
55            project_hash: knowledge.project_hash.clone(),
56            facts: knowledge.facts.clone(),
57            patterns: knowledge.patterns.clone(),
58            insights: knowledge.history.clone(),
59            relations,
60        }
61    }
62
63    /// True when there is nothing worth exporting (no facts, patterns, or
64    /// insights). Relations alone never make a bundle — they are edges between
65    /// facts that, without endpoints, carry no standalone meaning.
66    pub fn is_empty(&self) -> bool {
67        self.facts.is_empty() && self.patterns.is_empty() && self.insights.is_empty()
68    }
69
70    /// The current (non-superseded, temporally valid) facts — the human-facing
71    /// view portable exports render. Superseded history stays in [`Self::facts`]
72    /// for ctxpkg fidelity but would only confuse a hand-edited OKF bundle.
73    pub fn current_facts(&self) -> Vec<&KnowledgeFact> {
74        self.facts.iter().filter(|f| f.is_current()).collect()
75    }
76}