@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.
package/README.md CHANGED
@@ -222,12 +222,39 @@ kgraph conclude "auth refresh requires rotating the session cookie" \
222
222
 
223
223
  Store durable engineering memory directly. Cognition is typed as `finding`, `decision`, `gotcha`, `summary`, or `relationship`, and confidence is `high`, `medium`, or `low`. Keep conclusions concise: preserve expensive-to-rediscover knowledge, not raw chain-of-thought, speculative exploration, or temporary reasoning.
224
224
 
225
+ KGraph stores these conclusions as canonical knowledge atoms under `.kgraph/knowledge/` while keeping existing Markdown cognition files readable for compatibility.
226
+
227
+ ```bash
228
+ kgraph knowledge list
229
+ kgraph knowledge list --type finding --topic auth --json
230
+ kgraph knowledge get <atom-id>
231
+ kgraph knowledge archive <atom-id>
232
+ kgraph knowledge supersede <old-id> <new-id>
233
+ ```
234
+
235
+ Inspect and manage canonical knowledge atoms. Archive and supersede update lifecycle metadata; they do not delete history.
236
+
237
+ ```bash
238
+ kgraph stale
239
+ kgraph stale --json
240
+ kgraph blame <atom-id>
241
+ ```
242
+
243
+ Refresh atom lifecycle status against the current scan and inspect atom provenance. Changed file hashes move atoms to `needs-review`; deleted files or missing symbols move atoms to `stale`; `blame` shows the source command, agent/session/commit, evidence refs, and lifecycle links.
244
+
245
+ ```bash
246
+ kgraph pack "auth token refresh" --budget 8000
247
+ kgraph pack "auth token refresh" --budget 8000 --json
248
+ ```
249
+
250
+ Build a budget-aware context pack from files, symbols, relationships, git changes, session history, and knowledge atoms. JSON output is the stable machine-readable contract for agents.
251
+
225
252
  ```bash
226
253
  kgraph compact --dry-run
227
254
  kgraph compact
228
255
  ```
229
256
 
230
- Merge duplicate cognition records and archive low-confidence stale entries. Compaction keeps memory inspectable under `.kgraph/cognition/` while reducing low-value noise in future context responses.
257
+ Merge duplicate knowledge atoms and archive low-confidence stale entries. Compaction operates on `.kgraph/knowledge/atoms.jsonl` first, then regenerates indexes and compatibility domain records so future context responses use the atom lifecycle as the source of truth.
231
258
 
232
259
  ## Optional Step Commands
233
260
 
@@ -327,10 +354,14 @@ All runtime data lives under `.kgraph/`:
327
354
  ├── domains/
328
355
  ├── interactions/processed/
329
356
  ├── sessions/
357
+ ├── knowledge/
358
+ │ ├── atoms.jsonl
359
+ │ ├── schema.json
360
+ │ └── indexes/
330
361
  └── context/
331
362
  ```
332
363
 
333
- The files are local, inspectable, and human-readable. Core KGraph functionality is free. There is no database, telemetry, cloud service, account, API key, embedding service, model provider, or source-code upload.
364
+ The files are local, inspectable, and human-readable. `knowledge/atoms.jsonl` is the canonical durable-memory store; Markdown cognition remains a compatibility and input layer. Core KGraph functionality is free. There is no database, telemetry, cloud service, account, API key, embedding service, model provider, or source-code upload.
334
365
 
335
366
  ## Language Support
336
367
 
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerBlameCommand(program: Command): void;
@@ -0,0 +1,52 @@
1
+ import { readKnowledgeAtoms } from '../../knowledge/atom-store.js';
2
+ import { assertWorkspace } from '../../storage/kgraph-paths.js';
3
+ import { KGraphError, runCommand } from '../errors.js';
4
+ export function registerBlameCommand(program) {
5
+ program
6
+ .command('blame <atomId>')
7
+ .description('Show who or what created a knowledge atom and why it exists')
8
+ .option('--json', 'Print JSON output')
9
+ .action((atomId, options) => runCommand(async () => {
10
+ const workspace = await assertWorkspace(process.cwd());
11
+ const atom = (await readKnowledgeAtoms(workspace)).find((candidate) => candidate.id === atomId);
12
+ if (!atom)
13
+ throw new KGraphError(`Knowledge atom not found: ${atomId}`);
14
+ const result = {
15
+ id: atom.id,
16
+ topic: atom.topic,
17
+ claim: atom.claim,
18
+ provenance: atom.provenance,
19
+ evidenceRefs: atom.evidenceRefs,
20
+ lifecycle: atom.lifecycle,
21
+ };
22
+ if (options.json) {
23
+ console.log(JSON.stringify(result, null, 2));
24
+ return;
25
+ }
26
+ console.log(`# ${atom.topic}`);
27
+ console.log('');
28
+ console.log(`ID: ${atom.id}`);
29
+ console.log(`Claim: ${atom.claim}`);
30
+ console.log(`Source: ${atom.provenance.sourceCommand}`);
31
+ if (atom.provenance.agent)
32
+ console.log(`Agent: ${atom.provenance.agent}`);
33
+ if (atom.provenance.sessionId) {
34
+ console.log(`Session: ${atom.provenance.sessionId}`);
35
+ }
36
+ if (atom.provenance.commit)
37
+ console.log(`Commit: ${atom.provenance.commit}`);
38
+ console.log(`Created: ${atom.provenance.createdAt}`);
39
+ if (atom.provenance.updatedAt)
40
+ console.log(`Updated: ${atom.provenance.updatedAt}`);
41
+ console.log('');
42
+ console.log('Evidence:');
43
+ for (const ref of atom.evidenceRefs) {
44
+ console.log(`- ${JSON.stringify(ref)}`);
45
+ }
46
+ if (atom.lifecycle.supersededBy || atom.lifecycle.supersedes.length > 0) {
47
+ console.log('');
48
+ console.log('Lifecycle:');
49
+ console.log(JSON.stringify(atom.lifecycle, null, 2));
50
+ }
51
+ }));
52
+ }
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import { analyzeCognitionQuality, } from '../../cognition/cognition-quality.js';
4
4
  import { loadConfig } from '../../config/config.js';
5
5
  import { listIntegrations } from '../../integrations/integration-store.js';
6
+ import { validateKnowledgeStore } from '../../knowledge/atom-store.js';
6
7
  import { getCurrentCommit, isGitRepo } from '../../scanner/git-utils.js';
7
8
  import { assertWorkspace, pathExists, resolveWorkspace, } from '../../storage/kgraph-paths.js';
8
9
  import { mapPaths, mapsExist, readMaps } from '../../storage/map-store.js';
@@ -76,6 +77,22 @@ export function registerDoctorCommand(program) {
76
77
  detail: missing.join(', '),
77
78
  });
78
79
  }
80
+ const knowledgeIssues = await validateKnowledgeStore(workspace, maps
81
+ ? { fileMap: maps.fileMap, symbolMap: maps.symbolMap }
82
+ : undefined);
83
+ checks.push({
84
+ label: 'knowledge',
85
+ ok: knowledgeIssues.length === 0,
86
+ detail: knowledgeIssues.length === 0
87
+ ? 'knowledge atoms, schema, and refs are valid'
88
+ : knowledgeIssues
89
+ .slice(0, 3)
90
+ .map((issue) => issue.message)
91
+ .join('; ') +
92
+ (knowledgeIssues.length > 3
93
+ ? `; and ${knowledgeIssues.length - 3} more`
94
+ : ''),
95
+ });
79
96
  const inboxCount = await countMarkdownFiles(workspace.inboxPath);
80
97
  checks.push({
81
98
  label: 'inbox',
@@ -1,6 +1,7 @@
1
1
  import { loadConfig, writeDefaultConfig } from '../../config/config.js';
2
2
  import { normalizeIntegrationNames } from '../../integrations/integration-registry.js';
3
3
  import { addIntegrations } from '../../integrations/integration-store.js';
4
+ import { ensureKnowledgeStore } from '../../knowledge/atom-store.js';
4
5
  import { scanRepository } from '../../scanner/repo-scanner.js';
5
6
  import { ensureWorkspace } from '../../storage/kgraph-paths.js';
6
7
  import { readMaps, writeMaps } from '../../storage/map-store.js';
@@ -17,6 +18,7 @@ export function registerInitCommand(program) {
17
18
  .option('--mode <mode>', 'Integration mode: always, smart, manual, or off', 'smart')
18
19
  .action((options) => runCommand(async () => {
19
20
  const workspace = await ensureWorkspace(process.cwd());
21
+ await ensureKnowledgeStore(workspace);
20
22
  const wroteConfig = await writeDefaultConfig(workspace);
21
23
  console.log(wroteConfig
22
24
  ? 'Initialized .kgraph workspace.'
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerKnowledgeCommand(program: Command): void;
@@ -0,0 +1,137 @@
1
+ import { readKnowledgeAtoms, updateKnowledgeAtom, } from '../../knowledge/atom-store.js';
2
+ import { assertWorkspace } from '../../storage/kgraph-paths.js';
3
+ import { KGraphError, runCommand } from '../errors.js';
4
+ export function registerKnowledgeCommand(program) {
5
+ const knowledge = program
6
+ .command('knowledge')
7
+ .description('Manage canonical KGraph knowledge atoms');
8
+ knowledge
9
+ .command('list')
10
+ .option('--type <type>', 'Filter by atom type')
11
+ .option('--topic <topic>', 'Filter by topic substring')
12
+ .option('--status <status>', 'Filter by active, stale, needs-review, or archived')
13
+ .option('--json', 'Print JSON output')
14
+ .action((options) => runCommand(async () => {
15
+ const workspace = await assertWorkspace(process.cwd());
16
+ const atoms = filterAtoms(await readKnowledgeAtoms(workspace), options);
17
+ if (options.json) {
18
+ console.log(JSON.stringify(atoms, null, 2));
19
+ return;
20
+ }
21
+ console.log('KGraph Knowledge');
22
+ console.log('');
23
+ for (const atom of atoms) {
24
+ console.log(`- ${atom.id} [${atom.type}, ${atom.confidence}, ${atom.status}] ${atom.topic}`);
25
+ console.log(` ${atom.claim}`);
26
+ }
27
+ if (atoms.length === 0)
28
+ console.log('- None');
29
+ }));
30
+ knowledge
31
+ .command('get <atomId>')
32
+ .option('--json', 'Print JSON output')
33
+ .action((atomId, options) => runCommand(async () => {
34
+ const workspace = await assertWorkspace(process.cwd());
35
+ const atom = await requireAtom(workspace, atomId);
36
+ if (options.json) {
37
+ console.log(JSON.stringify(atom, null, 2));
38
+ return;
39
+ }
40
+ console.log(`# ${atom.topic}`);
41
+ console.log('');
42
+ console.log(`ID: ${atom.id}`);
43
+ console.log(`Type: ${atom.type}`);
44
+ console.log(`Confidence: ${atom.confidence}`);
45
+ console.log(`Status: ${atom.status}`);
46
+ console.log(`Claim: ${atom.claim}`);
47
+ if (atom.summary)
48
+ console.log(`Summary: ${atom.summary}`);
49
+ console.log('');
50
+ console.log('Evidence:');
51
+ for (const ref of atom.evidenceRefs) {
52
+ console.log(`- ${JSON.stringify(ref)}`);
53
+ }
54
+ console.log('');
55
+ console.log('Provenance:');
56
+ console.log(JSON.stringify(atom.provenance, null, 2));
57
+ console.log('');
58
+ console.log('Lifecycle:');
59
+ console.log(JSON.stringify(atom.lifecycle, null, 2));
60
+ }));
61
+ knowledge
62
+ .command('archive <atomId>')
63
+ .option('--json', 'Print JSON output')
64
+ .action((atomId, options) => runCommand(async () => {
65
+ const workspace = await assertWorkspace(process.cwd());
66
+ const now = new Date().toISOString();
67
+ const atom = await updateKnowledgeAtom(workspace, atomId, (current) => ({
68
+ ...current,
69
+ status: 'archived',
70
+ provenance: { ...current.provenance, updatedAt: now },
71
+ lifecycle: { ...current.lifecycle, archivedAt: now },
72
+ }));
73
+ console.log(options.json
74
+ ? JSON.stringify(atom, null, 2)
75
+ : `Archived knowledge atom: ${atom.id}`);
76
+ }));
77
+ knowledge
78
+ .command('supersede <oldId> <newId>')
79
+ .option('--json', 'Print JSON output')
80
+ .action((oldId, newId, options) => runCommand(async () => {
81
+ const workspace = await assertWorkspace(process.cwd());
82
+ await requireAtom(workspace, newId);
83
+ const now = new Date().toISOString();
84
+ const oldAtom = await updateKnowledgeAtom(workspace, oldId, (current) => ({
85
+ ...current,
86
+ status: 'archived',
87
+ provenance: { ...current.provenance, updatedAt: now },
88
+ lifecycle: {
89
+ ...current.lifecycle,
90
+ supersededBy: newId,
91
+ archivedAt: now,
92
+ },
93
+ }));
94
+ const newAtom = await updateKnowledgeAtom(workspace, newId, (current) => ({
95
+ ...current,
96
+ provenance: { ...current.provenance, updatedAt: now },
97
+ lifecycle: {
98
+ ...current.lifecycle,
99
+ supersedes: [...new Set([...current.lifecycle.supersedes, oldId])],
100
+ },
101
+ }));
102
+ const result = { old: oldAtom, new: newAtom };
103
+ console.log(options.json
104
+ ? JSON.stringify(result, null, 2)
105
+ : `Superseded ${oldId} with ${newId}`);
106
+ }));
107
+ }
108
+ async function requireAtom(workspace, atomId) {
109
+ const atom = (await readKnowledgeAtoms(workspace)).find((candidate) => candidate.id === atomId);
110
+ if (!atom)
111
+ throw new KGraphError(`Knowledge atom not found: ${atomId}`);
112
+ return atom;
113
+ }
114
+ function filterAtoms(atoms, options) {
115
+ return atoms.filter((atom) => {
116
+ if (options.type && atom.type !== options.type)
117
+ return false;
118
+ if (options.status &&
119
+ atom.status !== normalizeStatus(options.status)) {
120
+ return false;
121
+ }
122
+ if (options.topic &&
123
+ !atom.topic.toLowerCase().includes(options.topic.toLowerCase())) {
124
+ return false;
125
+ }
126
+ return true;
127
+ });
128
+ }
129
+ function normalizeStatus(value) {
130
+ if (value === 'active' ||
131
+ value === 'stale' ||
132
+ value === 'needs-review' ||
133
+ value === 'archived') {
134
+ return value;
135
+ }
136
+ throw new KGraphError('--status must be active, stale, needs-review, or archived.');
137
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerPackCommand(program: Command): void;
@@ -0,0 +1,49 @@
1
+ import { buildContextPack } from '../../context/context-pack.js';
2
+ import { queryContext } from '../../context/context-query.js';
3
+ import { loadConfig } from '../../config/config.js';
4
+ import { assertWorkspace } from '../../storage/kgraph-paths.js';
5
+ import { mapsExist, readMaps } from '../../storage/map-store.js';
6
+ import { KGraphError, runCommand } from '../errors.js';
7
+ export function registerPackCommand(program) {
8
+ program
9
+ .command('pack <task>')
10
+ .description('Build a budget-aware KGraph context pack for a task')
11
+ .option('--budget <tokens>', 'Maximum estimated tokens to include', '8000')
12
+ .option('--json', 'Print JSON output')
13
+ .action((task, options) => runCommand(async () => {
14
+ if (!task.trim())
15
+ throw new KGraphError('Task cannot be empty.');
16
+ const budget = Number.parseInt(options.budget ?? '8000', 10);
17
+ if (!Number.isFinite(budget) || budget < 1) {
18
+ throw new KGraphError('--budget must be a positive integer.');
19
+ }
20
+ const workspace = await assertWorkspace(process.cwd());
21
+ if (!(await mapsExist(workspace))) {
22
+ throw new KGraphError('KGraph maps are missing. Run `kgraph scan` first.');
23
+ }
24
+ const [config, maps] = await Promise.all([
25
+ loadConfig(workspace),
26
+ readMaps(workspace),
27
+ ]);
28
+ const response = await queryContext(workspace, config, maps, task);
29
+ const pack = buildContextPack(response, budget);
30
+ if (options.json) {
31
+ console.log(JSON.stringify(pack, null, 2));
32
+ return;
33
+ }
34
+ console.log(`# KGraph Context Pack`);
35
+ console.log('');
36
+ console.log(`Task: ${pack.task}`);
37
+ console.log(`Budget: ${pack.budget}`);
38
+ console.log(`Used: ${pack.usedTokens}`);
39
+ console.log('');
40
+ for (const item of pack.items) {
41
+ console.log(`- [${item.kind}] ${item.title} (~${item.tokenEstimate} tokens)`);
42
+ console.log(` because ${item.reasons.slice(0, 3).join('; ')}`);
43
+ }
44
+ if (pack.omitted.length > 0) {
45
+ console.log('');
46
+ console.log(`Omitted: ${pack.omitted.length} item(s) over budget`);
47
+ }
48
+ }));
49
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerStaleCommand(program: Command): void;
@@ -0,0 +1,33 @@
1
+ import { refreshKnowledgeAtomStatuses } from '../../knowledge/atom-store.js';
2
+ import { readMaps } from '../../storage/map-store.js';
3
+ import { assertWorkspace } from '../../storage/kgraph-paths.js';
4
+ import { runCommand } from '../errors.js';
5
+ export function registerStaleCommand(program) {
6
+ program
7
+ .command('stale')
8
+ .description('Show knowledge atoms invalidated by changed or missing refs')
9
+ .option('--json', 'Print JSON output')
10
+ .action((options) => runCommand(async () => {
11
+ const workspace = await assertWorkspace(process.cwd());
12
+ const maps = await readMaps(workspace);
13
+ const result = await refreshKnowledgeAtomStatuses(workspace, {
14
+ fileMap: maps.fileMap,
15
+ symbolMap: maps.symbolMap,
16
+ });
17
+ const atoms = result.atoms.filter((atom) => atom.status === 'stale' || atom.status === 'needs-review');
18
+ if (options.json) {
19
+ console.log(JSON.stringify({ updated: result.updated, atoms }, null, 2));
20
+ return;
21
+ }
22
+ console.log('KGraph Stale Knowledge');
23
+ console.log('');
24
+ for (const atom of atoms) {
25
+ console.log(`- ${atom.id} [${atom.type}, ${atom.confidence}, ${atom.status}] ${atom.topic}`);
26
+ for (const reason of atom.lifecycle.invalidatedBy ?? []) {
27
+ console.log(` - ${reason}`);
28
+ }
29
+ }
30
+ if (atoms.length === 0)
31
+ console.log('- None');
32
+ }));
33
+ }
package/dist/cli/help.js CHANGED
@@ -32,6 +32,10 @@ export function renderRootHelp(useColor = supportsColor()) {
32
32
  command('session end --agent codex --conclude', 'End tracking and store a durable session summary'),
33
33
  command('conclude "auth refresh gotcha"', 'Store typed engineering cognition'),
34
34
  command('compact', 'Merge duplicate cognition and archive stale noise'),
35
+ command('knowledge list', 'Inspect canonical knowledge atoms'),
36
+ command('pack "auth task" --budget 8000', 'Build a budget-aware context pack'),
37
+ command('stale', 'Show atoms invalidated by changed or missing refs'),
38
+ command('blame <atom-id>', 'Show atom provenance and evidence'),
35
39
  command('context "auth token refresh"', 'Optional: return context without scanning or updating'),
36
40
  command('impact "Button"', 'Show imports, callers, calls, cognition, and risk'),
37
41
  command('update', 'Optional: process only .kgraph/inbox Markdown cognition notes'),
@@ -94,6 +98,8 @@ export function renderWorkflowBanner(stats, useColor = supportsColor()) {
94
98
  command('kgraph "auth token refresh"', 'Return compact context for a topic'),
95
99
  command('kgraph doctor', 'Check workspace health'),
96
100
  command('kgraph doctor --quality', 'Check cognition quality'),
101
+ command('kgraph knowledge list', 'Inspect knowledge atoms'),
102
+ command('kgraph pack "auth task"', 'Build budget-aware context'),
97
103
  command('kgraph session', 'Check session token waste'),
98
104
  command('kgraph --help', 'Show all commands'),
99
105
  ].join('\n');
package/dist/cli/index.js CHANGED
@@ -3,6 +3,7 @@ import { Command } from 'commander';
3
3
  import { realpathSync } from 'node:fs';
4
4
  import { createRequire } from 'node:module';
5
5
  import { fileURLToPath } from 'node:url';
6
+ import { registerBlameCommand } from './commands/blame.js';
6
7
  import { registerCompactCommand } from './commands/compact.js';
7
8
  import { registerConcludeCommand } from './commands/conclude.js';
8
9
  import { registerContextCommand } from './commands/context.js';
@@ -11,9 +12,12 @@ import { registerHistoryCommand } from './commands/history.js';
11
12
  import { registerImpactCommand } from './commands/impact.js';
12
13
  import { registerInitCommand } from './commands/init.js';
13
14
  import { registerIntegrateCommand } from './commands/integrate.js';
15
+ import { registerKnowledgeCommand } from './commands/knowledge.js';
16
+ import { registerPackCommand } from './commands/pack.js';
14
17
  import { registerRepairCommand } from './commands/repair.js';
15
18
  import { registerScanCommand } from './commands/scan.js';
16
19
  import { registerSessionCommand } from './commands/session.js';
20
+ import { registerStaleCommand } from './commands/stale.js';
17
21
  import { registerUninstallCommand } from './commands/uninstall.js';
18
22
  import { registerUpdateCommand } from './commands/update.js';
19
23
  import { registerVisualizeCommand } from './commands/visualize.js';
@@ -46,6 +50,10 @@ export function createProgram() {
46
50
  registerCompactCommand(program);
47
51
  registerUpdateCommand(program);
48
52
  registerContextCommand(program);
53
+ registerPackCommand(program);
54
+ registerKnowledgeCommand(program);
55
+ registerStaleCommand(program);
56
+ registerBlameCommand(program);
49
57
  registerImpactCommand(program);
50
58
  registerIntegrateCommand(program);
51
59
  registerVisualizeCommand(program);
@@ -2,6 +2,7 @@ import { readFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { archiveInboxNote, listInboxNotes, readCognitionNotes, slugify, writeCognitionNote, writeDomainRecord, } from '../storage/cognition-store.js';
4
4
  import { parseMarkdownNote } from './markdown-note-parser.js';
5
+ import { createKnowledgeAtom } from '../knowledge/atom-store.js';
5
6
  export async function updateCognition(workspace, currentMaps, dryRun = false) {
6
7
  const inboxNotes = await listInboxNotes(workspace);
7
8
  const processed = [];
@@ -38,6 +39,19 @@ export async function updateCognition(workspace, currentMaps, dryRun = false) {
38
39
  await archiveInboxNote(workspace, inboxPath, timestamp);
39
40
  await writeCognitionNote(workspace, note);
40
41
  await writeDomainRecord(workspace, toDomainRecord(note, currentMaps));
42
+ await createKnowledgeAtom(workspace, {
43
+ type: note.kind,
44
+ topic: note.title,
45
+ claim: note.summary ?? note.title,
46
+ summary: note.summary,
47
+ confidence: note.confidence,
48
+ files: note.relatedFiles,
49
+ symbols: note.relatedSymbols,
50
+ domains: note.domain ? [note.domain] : [],
51
+ sourceCommand: 'update',
52
+ createdAt: note.createdAt,
53
+ idSeed: note.id,
54
+ });
41
55
  }
42
56
  }
43
57
  catch (error) {