@kentwynn/kgraph 0.2.9 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,58 +1,89 @@
1
1
  import { mkdir, rename } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { overwriteDomainRecord, readCognitionNotes, readDomainRecords, slugify, writeCognitionNote, } from '../storage/cognition-store.js';
3
+ import { atomToCognitionNote, refreshKnowledgeAtomStatuses, writeKnowledgeAtoms, } from '../knowledge/atom-store.js';
4
+ import { overwriteDomainRecord, readDomainRecords, slugify, writeCognitionNote, } from '../storage/cognition-store.js';
4
5
  import { pathExists } from '../storage/kgraph-paths.js';
5
6
  import { readMaps } from '../storage/map-store.js';
6
7
  import { evaluateReferenceStatus } from './cognition-updater.js';
7
8
  export async function compactCognition(workspace, dryRun = false) {
8
- const notes = await readCognitionNotes(workspace);
9
9
  const maps = await readMaps(workspace);
10
- const groups = groupDuplicates(notes);
10
+ const refreshed = await refreshKnowledgeAtomStatuses(workspace, { fileMap: maps.fileMap, symbolMap: maps.symbolMap }, dryRun);
11
+ const atoms = refreshed.atoms;
12
+ const groups = groupDuplicateAtoms(atoms);
11
13
  const result = { merged: [], archived: [] };
12
14
  const consumed = new Set();
13
15
  const archived = new Set();
14
- const mergedNotes = [];
16
+ const mergedAtoms = [];
15
17
  for (const group of groups.filter((items) => items.length > 1)) {
16
- const merged = mergeNotes(group, {
18
+ const merged = mergeAtoms(group, {
17
19
  files: maps.fileMap.files,
18
20
  symbols: maps.symbolMap.symbols,
19
21
  });
20
- const sourceIds = group.map((note) => note.id);
22
+ const sourceIds = group.map((atom) => atom.id);
21
23
  result.merged.push({
22
24
  targetId: merged.id,
23
25
  sourceIds,
24
- title: merged.title,
26
+ title: merged.topic,
25
27
  });
26
28
  sourceIds.forEach((id) => consumed.add(id));
27
- mergedNotes.push(merged);
28
- if (!dryRun) {
29
- await writeCognitionNote(workspace, merged);
30
- for (const note of group) {
31
- await archiveNote(workspace, note, `superseded-by-${merged.id}`);
32
- }
33
- }
29
+ mergedAtoms.push(merged);
34
30
  }
35
- for (const note of notes) {
36
- if (consumed.has(note.id))
31
+ for (const atom of atoms) {
32
+ if (consumed.has(atom.id) || atom.status === 'archived')
37
33
  continue;
38
- if (note.confidence === 'low' &&
39
- (note.referencesStatus === 'stale' || note.referencesStatus === 'unresolved')) {
34
+ if (atom.confidence === 'low' &&
35
+ (atom.status === 'stale' || atom.status === 'needs-review')) {
40
36
  result.archived.push({
41
- id: note.id,
42
- title: note.title,
43
- reason: 'low-confidence stale cognition',
37
+ id: atom.id,
38
+ title: atom.topic,
39
+ reason: `low-confidence ${atom.status} atom`,
44
40
  });
45
- archived.add(note.id);
46
- if (!dryRun) {
47
- await archiveNote(workspace, note, 'low-confidence-stale');
48
- }
41
+ archived.add(atom.id);
49
42
  }
50
43
  }
51
44
  if (!dryRun && (consumed.size > 0 || archived.size > 0)) {
52
- const activeNotes = [
53
- ...notes.filter((note) => !consumed.has(note.id) && !archived.has(note.id)),
54
- ...mergedNotes,
45
+ const now = new Date().toISOString();
46
+ const nextAtoms = [
47
+ ...atoms.map((atom) => {
48
+ if (consumed.has(atom.id)) {
49
+ const merged = mergedAtoms.find((candidate) => candidate.lifecycle.supersedes.includes(atom.id));
50
+ return {
51
+ ...atom,
52
+ status: 'archived',
53
+ confidence: 'low',
54
+ lifecycle: {
55
+ ...atom.lifecycle,
56
+ supersededBy: merged?.id,
57
+ archivedAt: now,
58
+ },
59
+ provenance: { ...atom.provenance, updatedAt: now },
60
+ };
61
+ }
62
+ if (archived.has(atom.id)) {
63
+ return {
64
+ ...atom,
65
+ status: 'archived',
66
+ lifecycle: { ...atom.lifecycle, archivedAt: now },
67
+ provenance: { ...atom.provenance, updatedAt: now },
68
+ };
69
+ }
70
+ return atom;
71
+ }),
72
+ ...mergedAtoms,
55
73
  ];
74
+ await writeKnowledgeAtoms(workspace, nextAtoms);
75
+ for (const atom of [...atoms.filter((item) => consumed.has(item.id) || archived.has(item.id)), ...mergedAtoms]) {
76
+ const note = atomToCognitionNote(atom);
77
+ if (mergedAtoms.some((merged) => merged.id === atom.id)) {
78
+ await writeCognitionNote(workspace, note);
79
+ }
80
+ else {
81
+ await archiveNote(workspace, note, consumed.has(atom.id) ? `superseded-by-${note.supersededBy ?? 'compact'}` : 'low-confidence-stale');
82
+ }
83
+ }
84
+ const activeNotes = nextAtoms
85
+ .filter((atom) => atom.status !== 'archived')
86
+ .map(atomToCognitionNote);
56
87
  await rebuildDomainRecords(workspace, activeNotes, {
57
88
  files: maps.fileMap.files,
58
89
  symbols: maps.symbolMap.symbols,
@@ -60,6 +91,59 @@ export async function compactCognition(workspace, dryRun = false) {
60
91
  }
61
92
  return result;
62
93
  }
94
+ function groupDuplicateAtoms(atoms) {
95
+ const byKey = new Map();
96
+ for (const atom of atoms) {
97
+ if (atom.status === 'archived' || atom.lifecycle.supersededBy)
98
+ continue;
99
+ const key = [
100
+ atom.type,
101
+ atom.scopeRefs.domains[0] ?? 'general',
102
+ normalizeText(atom.topic),
103
+ normalizeText(atom.claim),
104
+ stableList(atom.scopeRefs.files),
105
+ stableList(atom.scopeRefs.symbols),
106
+ ].join('\0');
107
+ const group = byKey.get(key) ?? [];
108
+ group.push(atom);
109
+ byKey.set(key, group);
110
+ }
111
+ return [...byKey.values()];
112
+ }
113
+ function mergeAtoms(atoms, currentMaps) {
114
+ const sorted = [...atoms].sort((left, right) => left.provenance.createdAt.localeCompare(right.provenance.createdAt));
115
+ const base = sorted[sorted.length - 1];
116
+ const now = new Date().toISOString();
117
+ const id = `${now.replace(/[:.]/g, '-')}-${slugify(base.topic) || 'compacted'}`;
118
+ const summaries = unique(sorted
119
+ .map((atom) => atom.summary ?? atom.claim)
120
+ .filter((summary) => Boolean(summary?.trim())));
121
+ const files = unique(sorted.flatMap((atom) => atom.scopeRefs.files));
122
+ const symbols = unique(sorted.flatMap((atom) => atom.scopeRefs.symbols));
123
+ const domains = unique(sorted.flatMap((atom) => atom.scopeRefs.domains));
124
+ const packages = unique(sorted.flatMap((atom) => atom.scopeRefs.packages));
125
+ const status = atomStatusFromReferenceStatus(evaluateReferenceStatus(files, symbols, currentMaps));
126
+ return {
127
+ ...base,
128
+ id,
129
+ claim: summaries[0] ?? base.claim,
130
+ summary: summaries.join('\n'),
131
+ confidence: highestConfidence(sorted.map((atom) => atom.confidence)),
132
+ status,
133
+ evidenceRefs: uniqueEvidence(sorted.flatMap((atom) => atom.evidenceRefs)),
134
+ scopeRefs: { files, symbols, domains, packages },
135
+ provenance: {
136
+ sourceCommand: 'compact',
137
+ agent: base.provenance.agent,
138
+ sessionId: base.provenance.sessionId,
139
+ commit: base.provenance.commit,
140
+ createdAt: now,
141
+ },
142
+ lifecycle: {
143
+ supersedes: sorted.map((atom) => atom.id),
144
+ },
145
+ };
146
+ }
63
147
  function groupDuplicates(notes) {
64
148
  const byKey = new Map();
65
149
  for (const note of notes) {
@@ -153,6 +237,23 @@ function highestConfidence(values) {
153
237
  return 'medium';
154
238
  return 'low';
155
239
  }
240
+ function atomStatusFromReferenceStatus(status) {
241
+ if (status === 'current')
242
+ return 'active';
243
+ if (status === 'mixed')
244
+ return 'needs-review';
245
+ return 'stale';
246
+ }
247
+ function uniqueEvidence(items) {
248
+ const seen = new Set();
249
+ return items.filter((item) => {
250
+ const key = JSON.stringify(item);
251
+ if (seen.has(key))
252
+ return false;
253
+ seen.add(key);
254
+ return true;
255
+ });
256
+ }
156
257
  function normalizeTitle(value) {
157
258
  return normalizeText(value);
158
259
  }
@@ -10,6 +10,8 @@ export interface ConclusionInput {
10
10
  relatedFiles?: string[];
11
11
  relatedSymbols?: string[];
12
12
  source: CognitionSource;
13
+ agent?: string;
14
+ sessionId?: string;
13
15
  }
14
16
  export declare function concludeTopic(workspace: KGraphWorkspace, input: ConclusionInput): Promise<CognitionNote>;
15
17
  export declare function concludeActiveSession(workspace: KGraphWorkspace, agent: string, input: Omit<ConclusionInput, 'source' | 'relatedFiles' | 'relatedSymbols'>): Promise<CognitionNote>;
@@ -1,6 +1,7 @@
1
1
  import { readMaps } from '../storage/map-store.js';
2
2
  import { slugify, writeCognitionNote, writeDomainRecord } from '../storage/cognition-store.js';
3
3
  import { KGraphError } from '../cli/errors.js';
4
+ import { createKnowledgeAtom } from '../knowledge/atom-store.js';
4
5
  import { evaluateReferenceStatus } from './cognition-updater.js';
5
6
  import { readSessionState } from '../session/session-store.js';
6
7
  export async function concludeTopic(workspace, input) {
@@ -36,6 +37,25 @@ export async function concludeTopic(workspace, input) {
36
37
  files: maps.fileMap.files,
37
38
  symbols: maps.symbolMap.symbols,
38
39
  }));
40
+ await createKnowledgeAtom(workspace, {
41
+ type: note.kind,
42
+ topic: note.title,
43
+ claim: note.summary ?? note.title,
44
+ summary: note.summary,
45
+ confidence: note.confidence,
46
+ files: note.relatedFiles,
47
+ symbols: note.relatedSymbols,
48
+ domains: note.domain ? [note.domain] : [],
49
+ sourceCommand: input.source === 'session-conclude'
50
+ ? 'session-conclude'
51
+ : input.source === 'compact'
52
+ ? 'compact'
53
+ : 'conclude',
54
+ agent: input.agent,
55
+ sessionId: input.sessionId,
56
+ createdAt: note.createdAt,
57
+ idSeed: note.id,
58
+ }, maps);
39
59
  return note;
40
60
  }
41
61
  export async function concludeActiveSession(workspace, agent, input) {
@@ -76,6 +96,8 @@ export async function buildActiveSessionConclusion(workspace, agent, input) {
76
96
  body,
77
97
  source: 'session-conclude',
78
98
  relatedFiles: touchedFiles,
99
+ agent,
100
+ sessionId: active.sessionId,
79
101
  };
80
102
  }
81
103
  function normalizeBody(value) {
@@ -0,0 +1,3 @@
1
+ import type { ContextResponse } from '../types/cognition.js';
2
+ import type { ContextPack } from '../types/knowledge.js';
3
+ export declare function buildContextPack(response: ContextResponse, budget: number): ContextPack;
@@ -0,0 +1,71 @@
1
+ import { estimateTokens } from '../session/token-estimator.js';
2
+ export function buildContextPack(response, budget) {
3
+ const candidates = [
4
+ ...response.relevantFiles.map((ranked) => ({
5
+ kind: 'file',
6
+ id: ranked.item.path,
7
+ title: ranked.item.path,
8
+ tokenEstimate: ranked.item.tokenEstimate ?? 0,
9
+ reasons: ranked.reasons,
10
+ data: ranked.item,
11
+ })),
12
+ ...response.relevantSymbols.map((ranked) => ({
13
+ kind: 'symbol',
14
+ id: ranked.item.id,
15
+ title: ranked.item.name,
16
+ tokenEstimate: 20,
17
+ reasons: ranked.reasons,
18
+ data: ranked.item,
19
+ })),
20
+ ...response.relevantCognition.map((ranked) => ({
21
+ kind: 'atom',
22
+ id: ranked.item.id,
23
+ title: ranked.item.title,
24
+ tokenEstimate: estimateTokens([ranked.item.title, ranked.item.summary ?? ''].join('\n'), `${ranked.item.id}.md`),
25
+ reasons: ranked.reasons,
26
+ data: ranked.item,
27
+ })),
28
+ ...response.relationships.map((relationship) => ({
29
+ kind: 'relationship',
30
+ id: [
31
+ relationship.sourceId,
32
+ relationship.relationshipType,
33
+ relationship.targetId,
34
+ ].join(' -> '),
35
+ title: `${relationship.sourceId} ${relationship.relationshipType} ${relationship.targetId}`,
36
+ tokenEstimate: 16,
37
+ reasons: response.relationshipExplanations?.find((item) => item.relationship.sourceId === relationship.sourceId &&
38
+ item.relationship.targetId === relationship.targetId &&
39
+ item.relationship.relationshipType === relationship.relationshipType)?.reasons ?? ['related graph edge'],
40
+ data: relationship,
41
+ })),
42
+ ...(response.gitChanges ?? []).map((change) => ({
43
+ kind: 'git-change',
44
+ id: change.path,
45
+ title: `${change.status}: ${change.path}`,
46
+ tokenEstimate: 12,
47
+ reasons: [change.reason],
48
+ data: change,
49
+ })),
50
+ ];
51
+ const items = [];
52
+ const omitted = [];
53
+ let usedTokens = 0;
54
+ for (const candidate of candidates) {
55
+ if (usedTokens + candidate.tokenEstimate <= budget) {
56
+ items.push(candidate);
57
+ usedTokens += candidate.tokenEstimate;
58
+ }
59
+ else {
60
+ omitted.push(candidate);
61
+ }
62
+ }
63
+ return {
64
+ task: response.query,
65
+ budget,
66
+ usedTokens,
67
+ items,
68
+ omitted,
69
+ warnings: response.warnings,
70
+ };
71
+ }
@@ -1,9 +1,17 @@
1
1
  import { getRecentlyCommittedFiles, getWorkingTreeChangesDetailed, isGitRepo, } from '../scanner/git-utils.js';
2
- import { readCognitionNotes, readDomainRecords, } from '../storage/cognition-store.js';
2
+ import { readDomainRecords } from '../storage/cognition-store.js';
3
3
  import { readSessionState } from '../session/session-store.js';
4
+ import { atomToCognitionNote, refreshKnowledgeAtomStatuses, } from '../knowledge/atom-store.js';
4
5
  import { rankByFields } from './ranking.js';
5
6
  export async function queryContext(workspace, config, maps, query) {
6
- const cognition = await readCognitionNotes(workspace);
7
+ const refreshedAtoms = await refreshKnowledgeAtomStatuses(workspace, {
8
+ fileMap: maps.fileMap,
9
+ symbolMap: maps.symbolMap,
10
+ });
11
+ const atoms = refreshedAtoms.atoms;
12
+ const cognition = atoms
13
+ .filter((atom) => atom.status !== 'archived')
14
+ .map(atomToCognitionNote);
7
15
  const domains = await readDomainRecords(workspace);
8
16
  const session = await readSessionState(workspace);
9
17
  const sessionTouchedPaths = new Set(session.events
@@ -31,17 +39,23 @@ export async function queryContext(workspace, config, maps, query) {
31
39
  { name: 'kind', value: (symbol) => symbol.kind },
32
40
  { name: 'parent', value: (symbol) => symbol.parentName },
33
41
  ]).slice(0, max);
34
- const relevantCognition = rankByFields(query, cognition, [
35
- { name: 'title', value: (note) => note.title },
36
- { name: 'type', value: (note) => note.kind },
37
- { name: 'confidence', value: (note) => note.confidence },
38
- { name: 'domain', value: (note) => note.domain },
39
- { name: 'tags', value: (note) => note.tags },
40
- { name: 'files', value: (note) => note.relatedFiles },
41
- { name: 'symbols', value: (note) => note.relatedSymbols },
42
- { name: 'summary', value: (note) => note.summary },
42
+ const relevantCognition = rankByFields(query, atoms.filter((atom) => atom.status !== 'archived'), [
43
+ { name: 'topic', value: (atom) => atom.topic },
44
+ { name: 'claim', value: (atom) => atom.claim },
45
+ { name: 'type', value: (atom) => atom.type },
46
+ { name: 'confidence', value: (atom) => atom.confidence },
47
+ { name: 'status', value: (atom) => atom.status },
48
+ { name: 'source', value: (atom) => atom.provenance.sourceCommand },
49
+ { name: 'domains', value: (atom) => atom.scopeRefs.domains },
50
+ { name: 'files', value: (atom) => atom.scopeRefs.files },
51
+ { name: 'symbols', value: (atom) => atom.scopeRefs.symbols },
52
+ { name: 'summary', value: (atom) => atom.summary },
43
53
  ])
44
- .map((ranked) => applyCognitionRankAdjustments(ranked))
54
+ .map((ranked) => applyAtomRankAdjustments(ranked))
55
+ .map((ranked) => ({
56
+ ...ranked,
57
+ item: atomToCognitionNote(ranked.item),
58
+ }))
45
59
  .sort((a, b) => b.score - a.score)
46
60
  .slice(0, max);
47
61
  const matchedDomains = rankByFields(query, domains, [
@@ -292,33 +306,44 @@ function explainRelationships(relationships, context) {
292
306
  return { relationship, reasons: [...reasons] };
293
307
  });
294
308
  }
295
- function applyCognitionRankAdjustments(ranked) {
309
+ function applyAtomRankAdjustments(ranked) {
296
310
  const reasons = [...ranked.reasons];
297
311
  let score = ranked.score;
298
312
  if (ranked.item.confidence === 'high') {
313
+ score += 4;
314
+ reasons.push('high confidence atom');
315
+ }
316
+ else if (ranked.item.confidence === 'medium') {
317
+ score += 1;
318
+ reasons.push('medium confidence atom');
319
+ }
320
+ else {
321
+ score -= 3;
322
+ reasons.push('low confidence penalty');
323
+ }
324
+ if (ranked.item.status === 'active') {
299
325
  score += 3;
300
- reasons.push('high confidence cognition');
326
+ reasons.push('active atom evidence');
301
327
  }
302
- else if (ranked.item.confidence === 'low') {
328
+ else if (ranked.item.status === 'needs-review') {
303
329
  score -= 2;
304
- reasons.push('low confidence penalty');
330
+ reasons.push('needs-review stale penalty');
305
331
  }
306
- if (ranked.item.referencesStatus === 'current') {
332
+ else if (ranked.item.status === 'stale') {
333
+ score -= 6;
334
+ reasons.push('stale atom penalty');
335
+ }
336
+ if (ranked.item.type === 'decision' || ranked.item.type === 'gotcha') {
307
337
  score += 2;
308
- reasons.push('current references');
338
+ reasons.push(`${ranked.item.type} atom`);
309
339
  }
310
- else if (ranked.item.referencesStatus === 'mixed') {
340
+ if (ranked.item.provenance.sourceCommand === 'legacy-migration') {
311
341
  score -= 1;
312
- reasons.push('mixed reference penalty');
313
- }
314
- else if (ranked.item.referencesStatus === 'stale' ||
315
- ranked.item.referencesStatus === 'unresolved') {
316
- score -= 4;
317
- reasons.push('stale reference penalty');
342
+ reasons.push('legacy compatibility atom');
318
343
  }
319
- if (ranked.item.kind === 'decision' || ranked.item.kind === 'gotcha') {
320
- score += 1;
321
- reasons.push(`${ranked.item.kind} cognition`);
344
+ if (ranked.item.lifecycle.supersededBy) {
345
+ score -= 8;
346
+ reasons.push('superseded atom penalty');
322
347
  }
323
348
  return { ...ranked, score, reasons };
324
349
  }
@@ -5,7 +5,7 @@ export const claudeCodeAdapter = {
5
5
  targetPath: 'CLAUDE.md',
6
6
  instructions: `## KGraph Workflow
7
7
 
8
- {{KGRAPH_CONTEXT_POLICY}} Use /kgraph for the full automated workflow. Run \`kgraph conclude\` for durable typed engineering memory and \`kgraph compact --dry-run\` when cognition looks duplicated or stale. Run \`kgraph doctor\` when setup or generated maps look wrong. Run \`kgraph scan\`, \`kgraph update\`, and \`kgraph context\` manually only when you need one specific step.
8
+ {{KGRAPH_CONTEXT_POLICY}} Use /kgraph for the full automated workflow. Run \`kgraph pack "<task>" --budget 8000 --json\` for a machine-readable token-budgeted context pack, \`kgraph knowledge list\` or \`kgraph knowledge get <atom-id>\` to inspect durable atoms, \`kgraph stale\` and \`kgraph blame <atom-id>\` when lifecycle/provenance matters, \`kgraph conclude\` for durable typed engineering memory, and \`kgraph compact --dry-run\` when cognition looks duplicated or stale. Run \`kgraph doctor\` when setup or generated maps look wrong. Run \`kgraph scan\`, \`kgraph update\`, and \`kgraph context\` manually only when you need one specific step.
9
9
  `,
10
10
  commandFiles: [
11
11
  {
@@ -28,6 +28,26 @@ ${numberedWorkflow('claude-code', { sessionQualifier: 'when native hooks are una
28
28
  {
29
29
  path: '.claude/commands/kgraph-compact.md',
30
30
  content: `Run \`kgraph compact --dry-run\` first and summarize duplicate cognition groups and stale low-confidence notes. Run \`kgraph compact\` only when the user asks to apply compaction.
31
+ `,
32
+ },
33
+ {
34
+ path: '.claude/commands/kgraph-pack.md',
35
+ content: `Run \`kgraph pack "$ARGUMENTS" --budget 8000 --json\` to build a machine-readable context pack. Summarize token use, included files, symbols, relationships, git changes, session history, atoms, and omitted items with the inclusion reasons.
36
+ `,
37
+ },
38
+ {
39
+ path: '.claude/commands/kgraph-knowledge.md',
40
+ content: `Use \`kgraph knowledge list\` and \`kgraph knowledge get <atom-id>\` to inspect durable atoms, evidence, provenance, and lifecycle. Run \`kgraph knowledge archive <atom-id>\` or \`kgraph knowledge supersede <old-id> <new-id>\` only when the user explicitly asks to mutate atom lifecycle.
41
+ `,
42
+ },
43
+ {
44
+ path: '.claude/commands/kgraph-stale.md',
45
+ content: `Run \`kgraph stale\` to refresh atom status against the current scan and summarize stale or needs-review atoms with invalidation reasons.
46
+ `,
47
+ },
48
+ {
49
+ path: '.claude/commands/kgraph-blame.md',
50
+ content: `Run \`kgraph blame "$ARGUMENTS"\` to show who or what created a knowledge atom, the source command/session/commit, evidence refs, and lifecycle links.
31
51
  `,
32
52
  },
33
53
  {
@@ -5,7 +5,7 @@ export const codexAdapter = {
5
5
  targetPath: 'AGENTS.md',
6
6
  instructions: `## KGraph Workflow
7
7
 
8
- {{KGRAPH_CONTEXT_POLICY}} The /kgraph skill handles the full automated workflow. Run \`kgraph conclude\` for durable typed engineering memory and \`kgraph compact --dry-run\` when cognition looks duplicated or stale. Run \`kgraph doctor\` when setup or generated maps look wrong. Run \`kgraph scan\`, \`kgraph update\`, and \`kgraph context\` manually only when you need one specific step.
8
+ {{KGRAPH_CONTEXT_POLICY}} The /kgraph skill handles the full automated workflow. Run \`kgraph pack "<task>" --budget 8000 --json\` for a machine-readable token-budgeted context pack, \`kgraph knowledge list\` or \`kgraph knowledge get <atom-id>\` to inspect durable atoms, \`kgraph stale\` and \`kgraph blame <atom-id>\` when lifecycle/provenance matters, \`kgraph conclude\` for durable typed engineering memory, and \`kgraph compact --dry-run\` when cognition looks duplicated or stale. Run \`kgraph doctor\` when setup or generated maps look wrong. Run \`kgraph scan\`, \`kgraph update\`, and \`kgraph context\` manually only when you need one specific step.
9
9
  `,
10
10
  commandFiles: [
11
11
  {
@@ -12,7 +12,7 @@ ${numberedWorkflow('copilot')}
12
12
  path: '.github/agents/kgraph.agent.md',
13
13
  content: `---
14
14
  name: kgraph
15
- description: Use KGraph persistent repo intelligence to answer questions about this codebase. Runs kgraph context, scan, update, conclude, compact, impact, history, and session commands to ground responses in durable local knowledge.
15
+ description: Use KGraph persistent repo intelligence to answer questions about this codebase. Runs kgraph context, pack, knowledge, stale, blame, scan, update, conclude, compact, impact, history, and session commands to ground responses in durable local knowledge.
16
16
  tools:
17
17
  - run_in_terminal
18
18
  - read_file
@@ -58,6 +58,49 @@ argument-hint: "--dry-run or apply"
58
58
  ---
59
59
 
60
60
  Run \`kgraph compact --dry-run\` first and summarize duplicate cognition groups and stale low-confidence notes. Run \`kgraph compact\` only when the user asks to apply compaction.
61
+ `,
62
+ },
63
+ {
64
+ path: '.github/prompts/kgraph-pack.prompt.md',
65
+ content: `---
66
+ description: Build a budget-aware KGraph context pack
67
+ agent: agent
68
+ argument-hint: "Task description"
69
+ ---
70
+
71
+ Run \`kgraph pack "$ARGUMENTS" --budget 8000 --json\` to build a machine-readable context pack. Summarize token use, included files, symbols, relationships, git changes, session history, atoms, and omitted items with the inclusion reasons.
72
+ `,
73
+ },
74
+ {
75
+ path: '.github/prompts/kgraph-knowledge.prompt.md',
76
+ content: `---
77
+ description: Inspect or manage KGraph canonical knowledge atoms
78
+ agent: agent
79
+ argument-hint: "list, get <atom-id>, archive <atom-id>, or supersede <old-id> <new-id>"
80
+ ---
81
+
82
+ Use \`kgraph knowledge list\` and \`kgraph knowledge get <atom-id>\` to inspect durable atoms, evidence, provenance, and lifecycle. Run \`kgraph knowledge archive <atom-id>\` or \`kgraph knowledge supersede <old-id> <new-id>\` only when the user explicitly asks to mutate atom lifecycle.
83
+ `,
84
+ },
85
+ {
86
+ path: '.github/prompts/kgraph-stale.prompt.md',
87
+ content: `---
88
+ description: Show KGraph knowledge invalidated by changed or missing refs
89
+ agent: agent
90
+ ---
91
+
92
+ Run \`kgraph stale\` to refresh atom status against the current scan and summarize stale or needs-review atoms with invalidation reasons.
93
+ `,
94
+ },
95
+ {
96
+ path: '.github/prompts/kgraph-blame.prompt.md',
97
+ content: `---
98
+ description: Show KGraph atom provenance and evidence
99
+ agent: agent
100
+ argument-hint: "Atom id"
101
+ ---
102
+
103
+ Run \`kgraph blame "$ARGUMENTS"\` to show who or what created a knowledge atom, the source command/session/commit, evidence refs, and lifecycle links.
61
104
  `,
62
105
  },
63
106
  {
@@ -7,6 +7,9 @@ const IMPACT_STEP = `Run \`kgraph impact "<file-or-symbol>"\` when the user asks
7
7
  const REPAIR_STEP = `Run \`kgraph repair --dry-run\` before cleanup when stale/noisy cognition needs fixing. Run \`kgraph repair\` only when the user asks to apply that cleanup.`;
8
8
  const COMPACT_STEP = `Run \`kgraph compact --dry-run\` when cognition looks duplicated, noisy, or stale. Run \`kgraph compact\` only when the user asks to merge/archive cognition.`;
9
9
  const HISTORY_STEP = `Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.`;
10
+ const KNOWLEDGE_STEP = `Run \`kgraph knowledge list --topic "<topic>"\` or \`kgraph knowledge get <atom-id>\` when the user asks what KGraph remembers or atom provenance/lifecycle matters.`;
11
+ const PACK_STEP = `Run \`kgraph pack "<task>" --budget 8000 --json\` when an agent needs a machine-readable, token-budgeted context pack instead of human Markdown context.`;
12
+ const STALE_STEP = `Run \`kgraph stale\` when changed or deleted code may have invalidated durable knowledge. Run \`kgraph blame <atom-id>\` when provenance or evidence for a memory matters.`;
10
13
  function sessionStep(agentName, qualifier) {
11
14
  const base = `Track meaningful session activity with \`kgraph session start --agent ${agentName}\`, \`kgraph session read <path> --agent ${agentName}\`, \`kgraph session write <path> --agent ${agentName}\`, and \`kgraph session end --agent ${agentName} --conclude --topic "<topic>"\` when durable session memory is useful`;
12
15
  return qualifier ? `${base} ${qualifier}.` : `${base}.`;
@@ -19,16 +22,19 @@ export function numberedWorkflow(agentName, options = {}) {
19
22
  return `1. Infer the topic from the user's request.
20
23
  2. {{KGRAPH_CONTEXT_POLICY}}
21
24
  3. Use the returned files, symbols, relationships, and cognition before broad exploration.
22
- 4. ${DOCTOR_STEP}
23
- 5. ${sessionStep(agentName, options.sessionQualifier)}
24
- 6. ${IMPACT_STEP}
25
+ 4. ${PACK_STEP}
26
+ 5. ${KNOWLEDGE_STEP}
27
+ 6. ${DOCTOR_STEP}
28
+ 7. ${STALE_STEP}
29
+ 8. ${sessionStep(agentName, options.sessionQualifier)}
30
+ 9. ${IMPACT_STEP}
25
31
 
26
32
  {{KGRAPH_CAPTURE_POLICY}}
27
33
 
28
- 7. ${REPAIR_STEP}
29
- 8. ${COMPACT_STEP}
30
- 9. Run \`kgraph visualize\` when the user wants to inspect the dependency graph — opens an interactive graph at http://localhost:4242 with PNG export.
31
- 10. ${HISTORY_STEP}`;
34
+ 10. ${REPAIR_STEP}
35
+ 11. ${COMPACT_STEP}
36
+ 12. Run \`kgraph visualize\` when the user wants to inspect the dependency graph — opens an interactive graph at http://localhost:4242 with PNG export.
37
+ 13. ${HISTORY_STEP}`;
32
38
  }
33
39
  /**
34
40
  * Returns the bullet-list workflow for rules files.
@@ -36,7 +42,10 @@ export function numberedWorkflow(agentName, options = {}) {
36
42
  */
37
43
  export function bulletWorkflow(agentName, options = {}) {
38
44
  return `- {{KGRAPH_CONTEXT_POLICY}}
45
+ - ${PACK_STEP}
46
+ - ${KNOWLEDGE_STEP}
39
47
  - ${DOCTOR_STEP}
48
+ - ${STALE_STEP}
40
49
  - ${sessionStep(agentName, options.sessionQualifier)}
41
50
  - ${IMPACT_STEP}
42
51
  {{KGRAPH_CAPTURE_POLICY}}
@@ -0,0 +1,60 @@
1
+ import type { CognitionConfidence, CognitionNote } from '../types/cognition.js';
2
+ import type { KGraphWorkspace } from '../types/config.js';
3
+ import type { KnowledgeAtom, KnowledgeValidationIssue } from '../types/knowledge.js';
4
+ import type { FileMap, SymbolMap } from '../types/maps.js';
5
+ export declare const KNOWLEDGE_SCHEMA_VERSION = 1;
6
+ export interface AtomInput {
7
+ type: KnowledgeAtom['type'];
8
+ topic: string;
9
+ claim: string;
10
+ summary?: string;
11
+ confidence?: CognitionConfidence;
12
+ files?: string[];
13
+ symbols?: string[];
14
+ domains?: string[];
15
+ packages?: string[];
16
+ sourceCommand: KnowledgeAtom['provenance']['sourceCommand'];
17
+ agent?: string;
18
+ sessionId?: string;
19
+ commit?: string;
20
+ createdAt?: string;
21
+ idSeed?: string;
22
+ }
23
+ export interface AtomStatusRefreshResult {
24
+ atoms: KnowledgeAtom[];
25
+ updated: Array<{
26
+ atomId: string;
27
+ previousStatus: KnowledgeAtom['status'];
28
+ nextStatus: KnowledgeAtom['status'];
29
+ reasons: string[];
30
+ }>;
31
+ }
32
+ export declare function ensureKnowledgeStore(workspace: KGraphWorkspace): Promise<void>;
33
+ export declare function readKnowledgeAtoms(workspace: KGraphWorkspace): Promise<KnowledgeAtom[]>;
34
+ export declare function readAtomsFile(workspace: KGraphWorkspace): Promise<KnowledgeAtom[]>;
35
+ export declare function parseAtomsJsonl(raw: string): KnowledgeAtom[];
36
+ export declare function writeKnowledgeAtoms(workspace: KGraphWorkspace, atoms: KnowledgeAtom[]): Promise<void>;
37
+ export declare function appendKnowledgeAtom(workspace: KGraphWorkspace, atom: KnowledgeAtom): Promise<KnowledgeAtom>;
38
+ export declare function createKnowledgeAtom(workspace: KGraphWorkspace, input: AtomInput, maps?: {
39
+ fileMap: FileMap;
40
+ symbolMap: SymbolMap;
41
+ }): Promise<KnowledgeAtom>;
42
+ export declare function migrateLegacyCognitionToAtoms(workspace: KGraphWorkspace): Promise<void>;
43
+ export declare function updateKnowledgeAtom(workspace: KGraphWorkspace, atomId: string, updater: (atom: KnowledgeAtom) => KnowledgeAtom): Promise<KnowledgeAtom>;
44
+ export declare function refreshKnowledgeAtomStatuses(workspace: KGraphWorkspace, maps: {
45
+ fileMap: FileMap;
46
+ symbolMap: SymbolMap;
47
+ }, dryRun?: boolean): Promise<AtomStatusRefreshResult>;
48
+ export declare function validateKnowledgeStore(workspace: KGraphWorkspace, maps?: {
49
+ fileMap: FileMap;
50
+ symbolMap: SymbolMap;
51
+ }): Promise<KnowledgeValidationIssue[]>;
52
+ export declare function atomToCognitionNote(atom: KnowledgeAtom): CognitionNote;
53
+ export declare function knowledgePaths(workspace: KGraphWorkspace): {
54
+ atoms: string;
55
+ schema: string;
56
+ indexes: string;
57
+ terms: string;
58
+ refs: string;
59
+ topics: string;
60
+ };