@kentwynn/kgraph 0.2.5 → 0.2.6
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 +22 -2
- package/dist/cli/commands/compact.d.ts +2 -0
- package/dist/cli/commands/compact.js +27 -0
- package/dist/cli/commands/conclude.d.ts +5 -0
- package/dist/cli/commands/conclude.js +57 -0
- package/dist/cli/commands/context.js +1 -1
- package/dist/cli/commands/session.js +20 -0
- package/dist/cli/help.js +3 -0
- package/dist/cli/index.js +4 -0
- package/dist/cognition/cognition-updater.js +2 -0
- package/dist/cognition/compact.d.ts +14 -0
- package/dist/cognition/compact.js +167 -0
- package/dist/cognition/conclusion.d.ts +16 -0
- package/dist/cognition/conclusion.js +96 -0
- package/dist/cognition/markdown-note-parser.js +24 -0
- package/dist/context/context-query.js +47 -2
- package/dist/integrations/adapters/claude-code.js +12 -2
- package/dist/integrations/adapters/codex.js +1 -1
- package/dist/integrations/adapters/copilot.js +24 -2
- package/dist/integrations/instruction-blocks.js +11 -5
- package/dist/integrations/workflow-steps.js +6 -3
- package/dist/session/session-store.js +3 -0
- package/dist/storage/cognition-store.js +26 -2
- package/dist/types/cognition.d.ts +9 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -133,7 +133,7 @@ Agents can also report session activity so KGraph can estimate token waste:
|
|
|
133
133
|
kgraph session start --agent codex
|
|
134
134
|
kgraph session read src/auth.ts --agent codex
|
|
135
135
|
kgraph session write src/auth.ts --agent codex
|
|
136
|
-
kgraph session end --agent codex
|
|
136
|
+
kgraph session end --agent codex --conclude --topic "auth session work"
|
|
137
137
|
kgraph session
|
|
138
138
|
```
|
|
139
139
|
|
|
@@ -204,10 +204,30 @@ kgraph session start --agent codex
|
|
|
204
204
|
kgraph session read src/auth.ts --agent codex
|
|
205
205
|
kgraph session write src/auth.ts --agent codex
|
|
206
206
|
kgraph session end --agent codex
|
|
207
|
+
kgraph session end --agent codex --conclude --topic "auth token refresh"
|
|
207
208
|
```
|
|
208
209
|
|
|
209
210
|
Track agent-reported read/write activity, repeated reads, and estimated token cost. Supported agents are `codex`, `claude-code`, `copilot`, `cursor`, `gemini`, `windsurf`, and `cline`.
|
|
210
|
-
The text report now includes next actions, such as using `kgraph context "<topic>"` before repeated broad file inspection.
|
|
211
|
+
The text report now includes next actions, such as using `kgraph context "<topic>"` before repeated broad file inspection. Add `--conclude` to store a durable session summary with touched files attached as related cognition.
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
kgraph conclude "auth refresh requires rotating the session cookie" \
|
|
215
|
+
--type gotcha \
|
|
216
|
+
--confidence high \
|
|
217
|
+
--domain auth \
|
|
218
|
+
--file src/auth.ts \
|
|
219
|
+
--symbol refreshSession \
|
|
220
|
+
--note "The refresh path must update both the access token and cookie expiry."
|
|
221
|
+
```
|
|
222
|
+
|
|
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
|
+
|
|
225
|
+
```bash
|
|
226
|
+
kgraph compact --dry-run
|
|
227
|
+
kgraph compact
|
|
228
|
+
```
|
|
229
|
+
|
|
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.
|
|
211
231
|
|
|
212
232
|
## Optional Step Commands
|
|
213
233
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { compactCognition } from '../../cognition/compact.js';
|
|
2
|
+
import { assertWorkspace } from '../../storage/kgraph-paths.js';
|
|
3
|
+
import { runCommand } from '../errors.js';
|
|
4
|
+
export function registerCompactCommand(program) {
|
|
5
|
+
program
|
|
6
|
+
.command('compact')
|
|
7
|
+
.description('Merge duplicate cognition and archive low-value stale entries')
|
|
8
|
+
.option('--dry-run', 'Preview compaction without changing files')
|
|
9
|
+
.option('--json', 'Print JSON output')
|
|
10
|
+
.action((options) => runCommand(async () => {
|
|
11
|
+
const workspace = await assertWorkspace(process.cwd());
|
|
12
|
+
const result = await compactCognition(workspace, Boolean(options.dryRun));
|
|
13
|
+
if (options.json) {
|
|
14
|
+
console.log(JSON.stringify(result, null, 2));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
console.log(options.dryRun ? 'KGraph Compact Preview' : 'KGraph Compact Complete');
|
|
18
|
+
console.log(`Merged duplicate groups: ${result.merged.length}`);
|
|
19
|
+
console.log(`Archived stale low-confidence notes: ${result.archived.length}`);
|
|
20
|
+
for (const item of result.merged) {
|
|
21
|
+
console.log(`- merged ${item.sourceIds.length} notes into ${item.title}`);
|
|
22
|
+
}
|
|
23
|
+
for (const item of result.archived) {
|
|
24
|
+
console.log(`- archived ${item.title}: ${item.reason}`);
|
|
25
|
+
}
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import type { CognitionConfidence, CognitionKind } from '../../types/cognition.js';
|
|
3
|
+
export declare function registerConcludeCommand(program: Command): void;
|
|
4
|
+
export declare function normalizeKind(value: string | undefined): CognitionKind;
|
|
5
|
+
export declare function normalizeConfidence(value: string | undefined): CognitionConfidence;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { concludeTopic } from '../../cognition/conclusion.js';
|
|
2
|
+
import { assertWorkspace } from '../../storage/kgraph-paths.js';
|
|
3
|
+
import { KGraphError, runCommand } from '../errors.js';
|
|
4
|
+
export function registerConcludeCommand(program) {
|
|
5
|
+
program
|
|
6
|
+
.command('conclude <topic>')
|
|
7
|
+
.description('Store durable typed engineering cognition for this repo')
|
|
8
|
+
.option('--type <type>', 'finding, decision, gotcha, summary, or relationship', 'summary')
|
|
9
|
+
.option('--confidence <level>', 'high, medium, or low', 'medium')
|
|
10
|
+
.option('--domain <name>', 'Domain name for this cognition')
|
|
11
|
+
.option('--tag <tag>', 'Tag to attach; repeatable', collect, [])
|
|
12
|
+
.option('--file <path>', 'Related repo file; repeatable', collect, [])
|
|
13
|
+
.option('--symbol <name>', 'Related symbol; repeatable', collect, [])
|
|
14
|
+
.option('--note <text>', 'Concise durable conclusion text')
|
|
15
|
+
.option('--json', 'Print JSON output')
|
|
16
|
+
.action((topic, options) => runCommand(async () => {
|
|
17
|
+
const workspace = await assertWorkspace(process.cwd());
|
|
18
|
+
const note = await concludeTopic(workspace, {
|
|
19
|
+
topic,
|
|
20
|
+
body: options.note,
|
|
21
|
+
kind: normalizeKind(options.type),
|
|
22
|
+
confidence: normalizeConfidence(options.confidence),
|
|
23
|
+
domain: options.domain,
|
|
24
|
+
tags: options.tag ?? [],
|
|
25
|
+
relatedFiles: options.file ?? [],
|
|
26
|
+
relatedSymbols: options.symbol ?? [],
|
|
27
|
+
source: 'conclude',
|
|
28
|
+
});
|
|
29
|
+
if (options.json) {
|
|
30
|
+
console.log(JSON.stringify(note, null, 2));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
console.log(`Stored ${note.kind} cognition: ${note.title}`);
|
|
34
|
+
console.log(`Confidence: ${note.confidence}`);
|
|
35
|
+
console.log(`Status: ${note.referencesStatus}`);
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
export function normalizeKind(value) {
|
|
39
|
+
if (value === 'finding' ||
|
|
40
|
+
value === 'decision' ||
|
|
41
|
+
value === 'gotcha' ||
|
|
42
|
+
value === 'summary' ||
|
|
43
|
+
value === 'relationship') {
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
throw new KGraphError('--type must be finding, decision, gotcha, summary, or relationship.');
|
|
47
|
+
}
|
|
48
|
+
export function normalizeConfidence(value) {
|
|
49
|
+
if (value === 'high' || value === 'medium' || value === 'low') {
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
throw new KGraphError('--confidence must be high, medium, or low.');
|
|
53
|
+
}
|
|
54
|
+
function collect(value, previous) {
|
|
55
|
+
previous.push(value);
|
|
56
|
+
return previous;
|
|
57
|
+
}
|
|
@@ -49,7 +49,7 @@ export function renderContextMarkdown(response) {
|
|
|
49
49
|
return `- ${s.name} (${kindInfo}) in ${s.filePath}${lineRange} because ${formatReasons(item.reasons)}`;
|
|
50
50
|
})));
|
|
51
51
|
lines.push('', '## Relevant Cognition', '');
|
|
52
|
-
lines.push(...formatList(response.relevantCognition.map((item) => `- ${item.item.title} [${item.item.referencesStatus}] because ${formatReasons(item.reasons)}`)));
|
|
52
|
+
lines.push(...formatList(response.relevantCognition.map((item) => `- ${item.item.title} [${item.item.kind ?? 'summary'}, ${item.item.confidence ?? 'medium'}, ${item.item.referencesStatus}] because ${formatReasons(item.reasons)}`)));
|
|
53
53
|
lines.push('', '## Relationships', '');
|
|
54
54
|
lines.push(...formatGroupedRelationships(response.relationships, response.relationshipExplanations));
|
|
55
55
|
lines.push('', '## Nearby Symbols (1-hop imports)', '');
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { buildActiveSessionConclusion, concludeTopic, } from '../../cognition/conclusion.js';
|
|
1
2
|
import { assertSessionAgent, buildSessionReport, recordSessionEvent, resetSession, } from '../../session/session-store.js';
|
|
2
3
|
import { assertWorkspace } from '../../storage/kgraph-paths.js';
|
|
3
4
|
import { readMaps } from '../../storage/map-store.js';
|
|
4
5
|
import { KGraphError, runCommand } from '../errors.js';
|
|
6
|
+
import { normalizeConfidence, normalizeKind } from './conclude.js';
|
|
5
7
|
export function registerSessionCommand(program) {
|
|
6
8
|
const session = program
|
|
7
9
|
.command('session')
|
|
@@ -62,14 +64,32 @@ export function registerSessionCommand(program) {
|
|
|
62
64
|
.command('end')
|
|
63
65
|
.requiredOption('--agent <name>', 'KGraph integration agent name')
|
|
64
66
|
.option('--source <source>', 'automatic, agent-reported, or manual', 'manual')
|
|
67
|
+
.option('--conclude', 'Store a durable typed summary for this session')
|
|
68
|
+
.option('--topic <topic>', 'Conclusion topic when using --conclude')
|
|
69
|
+
.option('--type <type>', 'finding, decision, gotcha, summary, or relationship', 'summary')
|
|
70
|
+
.option('--confidence <level>', 'high, medium, or low', 'medium')
|
|
71
|
+
.option('--note <text>', 'Concise durable conclusion text')
|
|
65
72
|
.action((options) => runCommand(async () => {
|
|
66
73
|
const workspace = await assertWorkspace(process.cwd());
|
|
74
|
+
let pendingConclusion;
|
|
75
|
+
if (options.conclude) {
|
|
76
|
+
pendingConclusion = await buildActiveSessionConclusion(workspace, requireAgent(options.agent), {
|
|
77
|
+
topic: options.topic ?? `${options.agent} session summary`,
|
|
78
|
+
body: options.note,
|
|
79
|
+
kind: normalizeKind(options.type),
|
|
80
|
+
confidence: normalizeConfidence(options.confidence),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
67
83
|
const event = await recordSessionEvent(workspace, {
|
|
68
84
|
agent: requireAgent(options.agent),
|
|
69
85
|
type: 'end',
|
|
70
86
|
captureSource: normalizeSource(options.source),
|
|
71
87
|
});
|
|
72
88
|
console.log(`KGraph session ended for ${event.agent}.`);
|
|
89
|
+
if (pendingConclusion) {
|
|
90
|
+
const note = await concludeTopic(workspace, pendingConclusion);
|
|
91
|
+
console.log(`Stored session cognition: ${note.title}`);
|
|
92
|
+
}
|
|
73
93
|
}));
|
|
74
94
|
session
|
|
75
95
|
.command('reset')
|
package/dist/cli/help.js
CHANGED
|
@@ -29,6 +29,9 @@ export function renderRootHelp(useColor = supportsColor()) {
|
|
|
29
29
|
command('scan', 'Optional: refresh only file, symbol, import, and relationship maps'),
|
|
30
30
|
command('session', 'Show agent read/write activity and token estimates'),
|
|
31
31
|
command('session read src/auth.ts --agent codex', 'Record an agent file read'),
|
|
32
|
+
command('session end --agent codex --conclude', 'End tracking and store a durable session summary'),
|
|
33
|
+
command('conclude "auth refresh gotcha"', 'Store typed engineering cognition'),
|
|
34
|
+
command('compact', 'Merge duplicate cognition and archive stale noise'),
|
|
32
35
|
command('context "auth token refresh"', 'Optional: return context without scanning or updating'),
|
|
33
36
|
command('impact "Button"', 'Show imports, callers, calls, cognition, and risk'),
|
|
34
37
|
command('update', 'Optional: process only .kgraph/inbox Markdown cognition notes'),
|
package/dist/cli/index.js
CHANGED
|
@@ -3,6 +3,8 @@ 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 { registerCompactCommand } from './commands/compact.js';
|
|
7
|
+
import { registerConcludeCommand } from './commands/conclude.js';
|
|
6
8
|
import { registerContextCommand } from './commands/context.js';
|
|
7
9
|
import { registerDoctorCommand } from './commands/doctor.js';
|
|
8
10
|
import { registerHistoryCommand } from './commands/history.js';
|
|
@@ -40,6 +42,8 @@ export function createProgram() {
|
|
|
40
42
|
registerInitCommand(program);
|
|
41
43
|
registerScanCommand(program);
|
|
42
44
|
registerSessionCommand(program);
|
|
45
|
+
registerConcludeCommand(program);
|
|
46
|
+
registerCompactCommand(program);
|
|
43
47
|
registerUpdateCommand(program);
|
|
44
48
|
registerContextCommand(program);
|
|
45
49
|
registerImpactCommand(program);
|
|
@@ -29,6 +29,7 @@ export async function updateCognition(workspace, currentMaps, dryRun = false) {
|
|
|
29
29
|
.split(path.sep)
|
|
30
30
|
.join('/'),
|
|
31
31
|
createdAt: new Date().toISOString(),
|
|
32
|
+
source: 'inbox',
|
|
32
33
|
referencesStatus: evaluateReferenceStatus(parsed.relatedFiles, parsed.relatedSymbols, currentMaps),
|
|
33
34
|
};
|
|
34
35
|
processed.push(note);
|
|
@@ -73,6 +74,7 @@ export async function refreshCognitionReferenceStatuses(workspace, currentMaps)
|
|
|
73
74
|
await writeCognitionNote(workspace, {
|
|
74
75
|
...note,
|
|
75
76
|
relatedSymbols,
|
|
77
|
+
updatedAt: new Date().toISOString(),
|
|
76
78
|
referencesStatus: nextStatus,
|
|
77
79
|
});
|
|
78
80
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { KGraphWorkspace } from '../types/config.js';
|
|
2
|
+
export interface CompactResult {
|
|
3
|
+
merged: Array<{
|
|
4
|
+
targetId: string;
|
|
5
|
+
sourceIds: string[];
|
|
6
|
+
title: string;
|
|
7
|
+
}>;
|
|
8
|
+
archived: Array<{
|
|
9
|
+
id: string;
|
|
10
|
+
title: string;
|
|
11
|
+
reason: string;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
export declare function compactCognition(workspace: KGraphWorkspace, dryRun?: boolean): Promise<CompactResult>;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { mkdir, rename } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { overwriteDomainRecord, readCognitionNotes, readDomainRecords, slugify, writeCognitionNote, } from '../storage/cognition-store.js';
|
|
4
|
+
import { pathExists } from '../storage/kgraph-paths.js';
|
|
5
|
+
import { readMaps } from '../storage/map-store.js';
|
|
6
|
+
import { evaluateReferenceStatus } from './cognition-updater.js';
|
|
7
|
+
export async function compactCognition(workspace, dryRun = false) {
|
|
8
|
+
const notes = await readCognitionNotes(workspace);
|
|
9
|
+
const maps = await readMaps(workspace);
|
|
10
|
+
const groups = groupDuplicates(notes);
|
|
11
|
+
const result = { merged: [], archived: [] };
|
|
12
|
+
const consumed = new Set();
|
|
13
|
+
const archived = new Set();
|
|
14
|
+
const mergedNotes = [];
|
|
15
|
+
for (const group of groups.filter((items) => items.length > 1)) {
|
|
16
|
+
const merged = mergeNotes(group, {
|
|
17
|
+
files: maps.fileMap.files,
|
|
18
|
+
symbols: maps.symbolMap.symbols,
|
|
19
|
+
});
|
|
20
|
+
const sourceIds = group.map((note) => note.id);
|
|
21
|
+
result.merged.push({
|
|
22
|
+
targetId: merged.id,
|
|
23
|
+
sourceIds,
|
|
24
|
+
title: merged.title,
|
|
25
|
+
});
|
|
26
|
+
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
|
+
}
|
|
34
|
+
}
|
|
35
|
+
for (const note of notes) {
|
|
36
|
+
if (consumed.has(note.id))
|
|
37
|
+
continue;
|
|
38
|
+
if (note.confidence === 'low' &&
|
|
39
|
+
(note.referencesStatus === 'stale' || note.referencesStatus === 'unresolved')) {
|
|
40
|
+
result.archived.push({
|
|
41
|
+
id: note.id,
|
|
42
|
+
title: note.title,
|
|
43
|
+
reason: 'low-confidence stale cognition',
|
|
44
|
+
});
|
|
45
|
+
archived.add(note.id);
|
|
46
|
+
if (!dryRun) {
|
|
47
|
+
await archiveNote(workspace, note, 'low-confidence-stale');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
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,
|
|
55
|
+
];
|
|
56
|
+
await rebuildDomainRecords(workspace, activeNotes, {
|
|
57
|
+
files: maps.fileMap.files,
|
|
58
|
+
symbols: maps.symbolMap.symbols,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
function groupDuplicates(notes) {
|
|
64
|
+
const byKey = new Map();
|
|
65
|
+
for (const note of notes) {
|
|
66
|
+
if (note.supersededBy)
|
|
67
|
+
continue;
|
|
68
|
+
const key = [
|
|
69
|
+
note.kind ?? 'summary',
|
|
70
|
+
note.domain ?? 'general',
|
|
71
|
+
normalizeTitle(note.title),
|
|
72
|
+
normalizeText(note.summary ?? ''),
|
|
73
|
+
stableList(note.relatedFiles),
|
|
74
|
+
stableList(note.relatedSymbols),
|
|
75
|
+
].join('\0');
|
|
76
|
+
const group = byKey.get(key) ?? [];
|
|
77
|
+
group.push(note);
|
|
78
|
+
byKey.set(key, group);
|
|
79
|
+
}
|
|
80
|
+
return [...byKey.values()];
|
|
81
|
+
}
|
|
82
|
+
function mergeNotes(notes, currentMaps) {
|
|
83
|
+
const sorted = [...notes].sort((left, right) => left.createdAt.localeCompare(right.createdAt));
|
|
84
|
+
const base = sorted[sorted.length - 1];
|
|
85
|
+
const now = new Date().toISOString();
|
|
86
|
+
const id = `${now.replace(/[:.]/g, '-')}-${slugify(base.title) || 'compacted'}`;
|
|
87
|
+
const summaries = unique(sorted
|
|
88
|
+
.map((note) => note.summary)
|
|
89
|
+
.filter((summary) => Boolean(summary?.trim())));
|
|
90
|
+
const relatedFiles = unique(sorted.flatMap((note) => note.relatedFiles));
|
|
91
|
+
const relatedSymbols = unique(sorted.flatMap((note) => note.relatedSymbols));
|
|
92
|
+
return {
|
|
93
|
+
...base,
|
|
94
|
+
id,
|
|
95
|
+
source: 'compact',
|
|
96
|
+
createdAt: now,
|
|
97
|
+
updatedAt: now,
|
|
98
|
+
supersedes: sorted.map((note) => note.id),
|
|
99
|
+
supersededBy: undefined,
|
|
100
|
+
confidence: highestConfidence(sorted.map((note) => note.confidence)),
|
|
101
|
+
relatedFiles,
|
|
102
|
+
relatedSymbols,
|
|
103
|
+
tags: unique(sorted.flatMap((note) => note.tags)),
|
|
104
|
+
summary: summaries[0] ?? base.summary,
|
|
105
|
+
sections: {
|
|
106
|
+
Summary: summaries.map((summary) => `- ${summary}`).join('\n'),
|
|
107
|
+
'Compacted From': sorted.map((note) => `- ${note.id}`).join('\n'),
|
|
108
|
+
},
|
|
109
|
+
warnings: unique(sorted.flatMap((note) => note.warnings)),
|
|
110
|
+
referencesStatus: evaluateReferenceStatus(relatedFiles, relatedSymbols, currentMaps),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
async function rebuildDomainRecords(workspace, notes, currentMaps) {
|
|
114
|
+
const existingDomains = await readDomainRecords(workspace);
|
|
115
|
+
const existingByName = new Map(existingDomains.map((domain) => [domain.name, domain]));
|
|
116
|
+
const domainNames = new Set([
|
|
117
|
+
...existingDomains.map((domain) => domain.name),
|
|
118
|
+
...notes.map((note) => note.domain ?? 'general'),
|
|
119
|
+
]);
|
|
120
|
+
const fileSet = new Set(currentMaps.files.map((file) => file.path));
|
|
121
|
+
const symbolSet = new Set(currentMaps.symbols.map((symbol) => symbol.name));
|
|
122
|
+
for (const name of domainNames) {
|
|
123
|
+
const relatedNotes = notes.filter((note) => (note.domain ?? 'general') === name);
|
|
124
|
+
const existing = existingByName.get(name);
|
|
125
|
+
const next = {
|
|
126
|
+
name,
|
|
127
|
+
description: existing?.description,
|
|
128
|
+
pathHints: unique(relatedNotes.flatMap((note) => note.relatedFiles)),
|
|
129
|
+
tags: unique(relatedNotes.flatMap((note) => note.tags)),
|
|
130
|
+
files: unique(relatedNotes
|
|
131
|
+
.flatMap((note) => note.relatedFiles)
|
|
132
|
+
.filter((file) => fileSet.has(file))),
|
|
133
|
+
symbols: unique(relatedNotes
|
|
134
|
+
.flatMap((note) => note.relatedSymbols)
|
|
135
|
+
.filter((symbol) => symbolSet.has(symbol))),
|
|
136
|
+
cognitionNotes: relatedNotes.map((note) => note.id),
|
|
137
|
+
};
|
|
138
|
+
await overwriteDomainRecord(workspace, next);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function archiveNote(workspace, note, reason) {
|
|
142
|
+
const source = path.join(workspace.cognitionPath, `${note.id}.md`);
|
|
143
|
+
if (!(await pathExists(source)))
|
|
144
|
+
return;
|
|
145
|
+
const archivedDir = path.join(workspace.cognitionPath, 'archived');
|
|
146
|
+
await mkdir(archivedDir, { recursive: true });
|
|
147
|
+
await rename(source, path.join(archivedDir, `${reason}-${note.id}.md`));
|
|
148
|
+
}
|
|
149
|
+
function highestConfidence(values) {
|
|
150
|
+
if (values.includes('high'))
|
|
151
|
+
return 'high';
|
|
152
|
+
if (values.includes('medium'))
|
|
153
|
+
return 'medium';
|
|
154
|
+
return 'low';
|
|
155
|
+
}
|
|
156
|
+
function normalizeTitle(value) {
|
|
157
|
+
return normalizeText(value);
|
|
158
|
+
}
|
|
159
|
+
function normalizeText(value) {
|
|
160
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
|
|
161
|
+
}
|
|
162
|
+
function stableList(values) {
|
|
163
|
+
return [...values].sort().join('\0');
|
|
164
|
+
}
|
|
165
|
+
function unique(items) {
|
|
166
|
+
return [...new Set(items)];
|
|
167
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CognitionConfidence, CognitionKind, CognitionNote, CognitionSource } from '../types/cognition.js';
|
|
2
|
+
import type { KGraphWorkspace } from '../types/config.js';
|
|
3
|
+
export interface ConclusionInput {
|
|
4
|
+
topic: string;
|
|
5
|
+
body?: string;
|
|
6
|
+
kind?: CognitionKind;
|
|
7
|
+
confidence?: CognitionConfidence;
|
|
8
|
+
domain?: string;
|
|
9
|
+
tags?: string[];
|
|
10
|
+
relatedFiles?: string[];
|
|
11
|
+
relatedSymbols?: string[];
|
|
12
|
+
source: CognitionSource;
|
|
13
|
+
}
|
|
14
|
+
export declare function concludeTopic(workspace: KGraphWorkspace, input: ConclusionInput): Promise<CognitionNote>;
|
|
15
|
+
export declare function concludeActiveSession(workspace: KGraphWorkspace, agent: string, input: Omit<ConclusionInput, 'source' | 'relatedFiles' | 'relatedSymbols'>): Promise<CognitionNote>;
|
|
16
|
+
export declare function buildActiveSessionConclusion(workspace: KGraphWorkspace, agent: string, input: Omit<ConclusionInput, 'source' | 'relatedFiles' | 'relatedSymbols'>): Promise<ConclusionInput>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { readMaps } from '../storage/map-store.js';
|
|
2
|
+
import { slugify, writeCognitionNote, writeDomainRecord } from '../storage/cognition-store.js';
|
|
3
|
+
import { KGraphError } from '../cli/errors.js';
|
|
4
|
+
import { evaluateReferenceStatus } from './cognition-updater.js';
|
|
5
|
+
import { readSessionState } from '../session/session-store.js';
|
|
6
|
+
export async function concludeTopic(workspace, input) {
|
|
7
|
+
const maps = await readMaps(workspace);
|
|
8
|
+
const now = new Date().toISOString();
|
|
9
|
+
const timestamp = now.replace(/[:.]/g, '-');
|
|
10
|
+
const title = input.topic.trim();
|
|
11
|
+
const summary = normalizeBody(input.body) ?? title;
|
|
12
|
+
const note = {
|
|
13
|
+
title,
|
|
14
|
+
kind: input.kind ?? 'summary',
|
|
15
|
+
confidence: input.confidence ?? 'medium',
|
|
16
|
+
domain: input.domain,
|
|
17
|
+
tags: input.tags ?? [],
|
|
18
|
+
summary,
|
|
19
|
+
sections: {
|
|
20
|
+
Summary: summary,
|
|
21
|
+
...(input.relatedFiles?.length ? { 'Related Files': input.relatedFiles.map((file) => `- ${file}`).join('\n') } : {}),
|
|
22
|
+
...(input.relatedSymbols?.length ? { 'Key Symbols': input.relatedSymbols.map((symbol) => `- \`${symbol}\``).join('\n') } : {}),
|
|
23
|
+
},
|
|
24
|
+
relatedFiles: input.relatedFiles ?? [],
|
|
25
|
+
relatedSymbols: input.relatedSymbols ?? [],
|
|
26
|
+
warnings: [],
|
|
27
|
+
id: `${timestamp}-${slugify(title) || 'conclusion'}`,
|
|
28
|
+
sourceInboxPath: '',
|
|
29
|
+
processedPath: `.kgraph/cognition/${timestamp}-${slugify(title) || 'conclusion'}.md`,
|
|
30
|
+
createdAt: now,
|
|
31
|
+
source: input.source,
|
|
32
|
+
referencesStatus: evaluateReferenceStatus(input.relatedFiles ?? [], input.relatedSymbols ?? [], { files: maps.fileMap.files, symbols: maps.symbolMap.symbols }),
|
|
33
|
+
};
|
|
34
|
+
await writeCognitionNote(workspace, note);
|
|
35
|
+
await writeDomainRecord(workspace, toDomainRecord(note, {
|
|
36
|
+
files: maps.fileMap.files,
|
|
37
|
+
symbols: maps.symbolMap.symbols,
|
|
38
|
+
}));
|
|
39
|
+
return note;
|
|
40
|
+
}
|
|
41
|
+
export async function concludeActiveSession(workspace, agent, input) {
|
|
42
|
+
return concludeTopic(workspace, await buildActiveSessionConclusion(workspace, agent, input));
|
|
43
|
+
}
|
|
44
|
+
export async function buildActiveSessionConclusion(workspace, agent, input) {
|
|
45
|
+
const state = await readSessionState(workspace);
|
|
46
|
+
const active = state.active[agent];
|
|
47
|
+
if (!active) {
|
|
48
|
+
throw new KGraphError(`No active session for agent "${agent}".`);
|
|
49
|
+
}
|
|
50
|
+
const events = state.events.filter((event) => event.agent === agent && event.timestamp >= active.startedAt);
|
|
51
|
+
const touchedFiles = [
|
|
52
|
+
...new Set(events
|
|
53
|
+
.filter((event) => event.type === 'read' || event.type === 'write')
|
|
54
|
+
.map((event) => event.path)
|
|
55
|
+
.filter((file) => Boolean(file))),
|
|
56
|
+
];
|
|
57
|
+
const writtenFiles = [
|
|
58
|
+
...new Set(events
|
|
59
|
+
.filter((event) => event.type === 'write')
|
|
60
|
+
.map((event) => event.path)
|
|
61
|
+
.filter((file) => Boolean(file))),
|
|
62
|
+
];
|
|
63
|
+
const body = [
|
|
64
|
+
normalizeBody(input.body) ?? `Session concluded for ${input.topic}.`,
|
|
65
|
+
touchedFiles.length
|
|
66
|
+
? `Touched files: ${touchedFiles.join(', ')}.`
|
|
67
|
+
: undefined,
|
|
68
|
+
writtenFiles.length
|
|
69
|
+
? `Changed files: ${writtenFiles.join(', ')}.`
|
|
70
|
+
: undefined,
|
|
71
|
+
]
|
|
72
|
+
.filter(Boolean)
|
|
73
|
+
.join('\n\n');
|
|
74
|
+
return {
|
|
75
|
+
...input,
|
|
76
|
+
body,
|
|
77
|
+
source: 'session-conclude',
|
|
78
|
+
relatedFiles: touchedFiles,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function normalizeBody(value) {
|
|
82
|
+
const trimmed = value?.trim();
|
|
83
|
+
return trimmed ? trimmed : undefined;
|
|
84
|
+
}
|
|
85
|
+
function toDomainRecord(note, currentMaps) {
|
|
86
|
+
const fileSet = new Set(currentMaps.files.map((file) => file.path));
|
|
87
|
+
const symbolSet = new Set(currentMaps.symbols.map((symbol) => symbol.name));
|
|
88
|
+
return {
|
|
89
|
+
name: note.domain ?? 'general',
|
|
90
|
+
pathHints: note.relatedFiles,
|
|
91
|
+
tags: note.tags,
|
|
92
|
+
files: note.relatedFiles.filter((file) => fileSet.has(file)),
|
|
93
|
+
symbols: note.relatedSymbols.filter((symbol) => symbolSet.has(symbol)),
|
|
94
|
+
cognitionNotes: [note.id],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -10,6 +10,8 @@ export function parseMarkdownNote(markdown) {
|
|
|
10
10
|
const combined = Object.values(sections).join('\n');
|
|
11
11
|
return {
|
|
12
12
|
title,
|
|
13
|
+
kind: normalizeKind(frontmatter.type ?? frontmatter.kind, warnings),
|
|
14
|
+
confidence: normalizeConfidence(frontmatter.confidence, warnings),
|
|
13
15
|
domain: typeof frontmatter.domain === 'string' ? frontmatter.domain : undefined,
|
|
14
16
|
tags: Array.isArray(frontmatter.tags) ? frontmatter.tags.map(String) : [],
|
|
15
17
|
summary: sections.Summary,
|
|
@@ -19,6 +21,28 @@ export function parseMarkdownNote(markdown) {
|
|
|
19
21
|
warnings,
|
|
20
22
|
};
|
|
21
23
|
}
|
|
24
|
+
function normalizeKind(value, warnings) {
|
|
25
|
+
if (value === 'finding' ||
|
|
26
|
+
value === 'decision' ||
|
|
27
|
+
value === 'gotcha' ||
|
|
28
|
+
value === 'summary' ||
|
|
29
|
+
value === 'relationship') {
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
if (value !== undefined) {
|
|
33
|
+
warnings.push(`Unsupported cognition type "${String(value)}"; defaulted to summary.`);
|
|
34
|
+
}
|
|
35
|
+
return 'summary';
|
|
36
|
+
}
|
|
37
|
+
function normalizeConfidence(value, warnings) {
|
|
38
|
+
if (value === 'high' || value === 'medium' || value === 'low') {
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
if (value !== undefined) {
|
|
42
|
+
warnings.push(`Unsupported confidence "${String(value)}"; defaulted to medium.`);
|
|
43
|
+
}
|
|
44
|
+
return 'medium';
|
|
45
|
+
}
|
|
22
46
|
function splitFrontmatter(markdown, warnings) {
|
|
23
47
|
if (!markdown.startsWith('---\n')) {
|
|
24
48
|
return { frontmatter: {}, body: markdown };
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { getRecentlyCommittedFiles, getWorkingTreeChangesDetailed, isGitRepo, } from '../scanner/git-utils.js';
|
|
2
2
|
import { readCognitionNotes, readDomainRecords, } from '../storage/cognition-store.js';
|
|
3
|
+
import { readSessionState } from '../session/session-store.js';
|
|
3
4
|
import { rankByFields } from './ranking.js';
|
|
4
5
|
export async function queryContext(workspace, config, maps, query) {
|
|
5
6
|
const cognition = await readCognitionNotes(workspace);
|
|
6
7
|
const domains = await readDomainRecords(workspace);
|
|
8
|
+
const session = await readSessionState(workspace);
|
|
9
|
+
const sessionTouchedPaths = new Set(session.events
|
|
10
|
+
.map((event) => event.path)
|
|
11
|
+
.filter((path) => Boolean(path)));
|
|
7
12
|
const max = config.maxContextItems;
|
|
8
13
|
let relevantFiles = rankByFields(query, maps.fileMap.files, [
|
|
9
14
|
{ name: 'path', value: (file) => file.path },
|
|
@@ -11,7 +16,12 @@ export async function queryContext(workspace, config, maps, query) {
|
|
|
11
16
|
])
|
|
12
17
|
.map((ranked) => ({
|
|
13
18
|
...ranked,
|
|
14
|
-
score: ranked.score -
|
|
19
|
+
score: ranked.score -
|
|
20
|
+
Math.floor((ranked.item.tokenEstimate ?? 0) / 2000) +
|
|
21
|
+
(sessionTouchedPaths.has(ranked.item.path) ? 3 : 0),
|
|
22
|
+
reasons: sessionTouchedPaths.has(ranked.item.path)
|
|
23
|
+
? [...ranked.reasons, 'touched in current session']
|
|
24
|
+
: ranked.reasons,
|
|
15
25
|
}))
|
|
16
26
|
.sort((a, b) => b.score - a.score)
|
|
17
27
|
.slice(0, max);
|
|
@@ -23,12 +33,17 @@ export async function queryContext(workspace, config, maps, query) {
|
|
|
23
33
|
]).slice(0, max);
|
|
24
34
|
const relevantCognition = rankByFields(query, cognition, [
|
|
25
35
|
{ name: 'title', value: (note) => note.title },
|
|
36
|
+
{ name: 'type', value: (note) => note.kind },
|
|
37
|
+
{ name: 'confidence', value: (note) => note.confidence },
|
|
26
38
|
{ name: 'domain', value: (note) => note.domain },
|
|
27
39
|
{ name: 'tags', value: (note) => note.tags },
|
|
28
40
|
{ name: 'files', value: (note) => note.relatedFiles },
|
|
29
41
|
{ name: 'symbols', value: (note) => note.relatedSymbols },
|
|
30
42
|
{ name: 'summary', value: (note) => note.summary },
|
|
31
|
-
])
|
|
43
|
+
])
|
|
44
|
+
.map((ranked) => applyCognitionRankAdjustments(ranked))
|
|
45
|
+
.sort((a, b) => b.score - a.score)
|
|
46
|
+
.slice(0, max);
|
|
32
47
|
const matchedDomains = rankByFields(query, domains, [
|
|
33
48
|
{ name: 'name', value: (domain) => domain.name },
|
|
34
49
|
{ name: 'tags', value: (domain) => domain.tags },
|
|
@@ -277,6 +292,36 @@ function explainRelationships(relationships, context) {
|
|
|
277
292
|
return { relationship, reasons: [...reasons] };
|
|
278
293
|
});
|
|
279
294
|
}
|
|
295
|
+
function applyCognitionRankAdjustments(ranked) {
|
|
296
|
+
const reasons = [...ranked.reasons];
|
|
297
|
+
let score = ranked.score;
|
|
298
|
+
if (ranked.item.confidence === 'high') {
|
|
299
|
+
score += 3;
|
|
300
|
+
reasons.push('high confidence cognition');
|
|
301
|
+
}
|
|
302
|
+
else if (ranked.item.confidence === 'low') {
|
|
303
|
+
score -= 2;
|
|
304
|
+
reasons.push('low confidence penalty');
|
|
305
|
+
}
|
|
306
|
+
if (ranked.item.referencesStatus === 'current') {
|
|
307
|
+
score += 2;
|
|
308
|
+
reasons.push('current references');
|
|
309
|
+
}
|
|
310
|
+
else if (ranked.item.referencesStatus === 'mixed') {
|
|
311
|
+
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');
|
|
318
|
+
}
|
|
319
|
+
if (ranked.item.kind === 'decision' || ranked.item.kind === 'gotcha') {
|
|
320
|
+
score += 1;
|
|
321
|
+
reasons.push(`${ranked.item.kind} cognition`);
|
|
322
|
+
}
|
|
323
|
+
return { ...ranked, score, reasons };
|
|
324
|
+
}
|
|
280
325
|
function dependenciesForImportedSymbol(symbol, dependencies) {
|
|
281
326
|
return dependencies
|
|
282
327
|
.filter((dependency) => dependency.kind === 'local' &&
|
|
@@ -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 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 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
|
{
|
|
@@ -23,6 +23,11 @@ ${numberedWorkflow('claude-code', { sessionQualifier: 'when native hooks are una
|
|
|
23
23
|
{
|
|
24
24
|
path: '.claude/commands/kgraph-repair.md',
|
|
25
25
|
content: `Run \`kgraph repair --dry-run\` first and summarize the proposed cognition cleanup. Run \`kgraph repair\` only when the user asks to apply the cleanup.
|
|
26
|
+
`,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
path: '.claude/commands/kgraph-compact.md',
|
|
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.
|
|
26
31
|
`,
|
|
27
32
|
},
|
|
28
33
|
{
|
|
@@ -47,7 +52,12 @@ ${numberedWorkflow('claude-code', { sessionQualifier: 'when native hooks are una
|
|
|
47
52
|
},
|
|
48
53
|
{
|
|
49
54
|
path: '.claude/commands/kgraph-session.md',
|
|
50
|
-
content: `Use \`kgraph session\` to inspect session read/write/token estimates. Record meaningful events with \`kgraph session start --agent claude-code\`, \`kgraph session read <path> --agent claude-code\`, \`kgraph session write <path> --agent claude-code\`, and \`kgraph session end --agent claude-code
|
|
55
|
+
content: `Use \`kgraph session\` to inspect session read/write/token estimates. Record meaningful events with \`kgraph session start --agent claude-code\`, \`kgraph session read <path> --agent claude-code\`, \`kgraph session write <path> --agent claude-code\`, and \`kgraph session end --agent claude-code --conclude --topic "<topic>"\` when durable session memory is useful.
|
|
56
|
+
`,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
path: '.claude/commands/kgraph-conclude.md',
|
|
60
|
+
content: `Use \`kgraph conclude "$ARGUMENTS"\` when the session produced reusable engineering knowledge. Choose one type from finding, decision, gotcha, summary, relationship, and one confidence from high, medium, low. Store only durable conclusions, not raw chain-of-thought, temporary reasoning, speculative exploration, or low-value observations.
|
|
51
61
|
`,
|
|
52
62
|
},
|
|
53
63
|
{
|
|
@@ -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 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 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, 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, 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
|
|
@@ -47,6 +47,17 @@ argument-hint: "--dry-run or apply"
|
|
|
47
47
|
---
|
|
48
48
|
|
|
49
49
|
Run \`kgraph repair --dry-run\` first and summarize the proposed cognition cleanup. Run \`kgraph repair\` only when the user asks to apply the cleanup.
|
|
50
|
+
`,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
path: '.github/prompts/kgraph-compact.prompt.md',
|
|
54
|
+
content: `---
|
|
55
|
+
description: Merge duplicate KGraph cognition and archive stale low-value entries
|
|
56
|
+
agent: agent
|
|
57
|
+
argument-hint: "--dry-run or apply"
|
|
58
|
+
---
|
|
59
|
+
|
|
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.
|
|
50
61
|
`,
|
|
51
62
|
},
|
|
52
63
|
{
|
|
@@ -90,6 +101,17 @@ argument-hint: "Brief description of what was done"
|
|
|
90
101
|
Capture this session into KGraph cognition.
|
|
91
102
|
|
|
92
103
|
{{KGRAPH_CAPTURE_POLICY}}
|
|
104
|
+
`,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
path: '.github/prompts/kgraph-conclude.prompt.md',
|
|
108
|
+
content: `---
|
|
109
|
+
description: Store a typed durable KGraph engineering conclusion
|
|
110
|
+
agent: agent
|
|
111
|
+
argument-hint: "Topic plus optional type, confidence, files, and symbols"
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
Use \`kgraph conclude "$ARGUMENTS"\` when the session produced reusable engineering knowledge. Choose one type from finding, decision, gotcha, summary, relationship, and one confidence from high, medium, low. Store only durable conclusions, not raw chain-of-thought, temporary reasoning, speculative exploration, or low-value observations.
|
|
93
115
|
`,
|
|
94
116
|
},
|
|
95
117
|
{
|
|
@@ -111,7 +133,7 @@ agent: agent
|
|
|
111
133
|
argument-hint: "start, read <path>, write <path>, end, or status"
|
|
112
134
|
---
|
|
113
135
|
|
|
114
|
-
Use \`kgraph session\` to inspect current session activity. Record meaningful events with \`kgraph session start --agent copilot\`, \`kgraph session read <path> --agent copilot\`, \`kgraph session write <path> --agent copilot\`, and \`kgraph session end --agent copilot
|
|
136
|
+
Use \`kgraph session\` to inspect current session activity. Record meaningful events with \`kgraph session start --agent copilot\`, \`kgraph session read <path> --agent copilot\`, \`kgraph session write <path> --agent copilot\`, and \`kgraph session end --agent copilot --conclude --topic "<topic>"\` when durable session memory is useful.
|
|
115
137
|
`,
|
|
116
138
|
},
|
|
117
139
|
{
|
|
@@ -50,14 +50,20 @@ export function renderContextPolicy(mode) {
|
|
|
50
50
|
}
|
|
51
51
|
export function renderCapturePolicy() {
|
|
52
52
|
return `Capture policy:
|
|
53
|
-
- At the end of any session that changed repository files,
|
|
54
|
-
-
|
|
55
|
-
-
|
|
53
|
+
- At the end of any session that changed repository files, store durable engineering memory with \`kgraph conclude "<topic>" --type <finding|decision|gotcha|summary|relationship> --confidence <high|medium|low>\` or \`kgraph session end --agent <agent> --conclude --topic "<topic>"\`.
|
|
54
|
+
- Preserve only expensive-to-rediscover findings, decisions, gotchas, summaries, and relationships. Do not store raw chain-of-thought, temporary reasoning, speculative exploration, or low-value observations.
|
|
55
|
+
- Use \`.kgraph/inbox/<slug>.md\` only when a longer structured note is clearer than a single \`kgraph conclude\` command.
|
|
56
|
+
- A \`.kgraph/inbox/*.md\` note is KGraph runtime capture, not project documentation. It is allowed by this workflow unless the user explicitly says not to capture to KGraph.
|
|
57
|
+
- Do not skip capture for meaningful UI text, button, link, route, styling, or small file edits. Skip capture only when no reusable repository knowledge was created.
|
|
56
58
|
- Do not run KGraph repeatedly. Run it once at the start with \`kgraph "<topic>"\`. If repo files changed, write the inbox note first, then run \`kgraph\` once at the end.
|
|
57
|
-
- After the final \`kgraph\` run, mention whether
|
|
59
|
+
- After the final \`kgraph\` run, mention whether durable cognition was stored or processed.
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
When using an inbox note, use this structure:
|
|
60
62
|
\`\`\`markdown
|
|
63
|
+
---
|
|
64
|
+
type: finding
|
|
65
|
+
confidence: medium
|
|
66
|
+
---
|
|
61
67
|
# <Short Title>
|
|
62
68
|
|
|
63
69
|
## Summary
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
const DOCTOR_STEP = `Run \`kgraph doctor\` when setup, maps, inbox processing, or integrations look wrong. Run \`kgraph doctor --quality\` when context shows stale/noisy cognition references.`;
|
|
6
6
|
const IMPACT_STEP = `Run \`kgraph impact "<file-or-symbol>"\` when the user asks what a change may affect. Run \`kgraph history "<topic>"\` when prior work or decisions matter.`;
|
|
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
|
+
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.`;
|
|
8
9
|
const HISTORY_STEP = `Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.`;
|
|
9
10
|
function sessionStep(agentName, qualifier) {
|
|
10
|
-
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}
|
|
11
|
+
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`;
|
|
11
12
|
return qualifier ? `${base} ${qualifier}.` : `${base}.`;
|
|
12
13
|
}
|
|
13
14
|
/**
|
|
@@ -25,8 +26,9 @@ export function numberedWorkflow(agentName, options = {}) {
|
|
|
25
26
|
{{KGRAPH_CAPTURE_POLICY}}
|
|
26
27
|
|
|
27
28
|
7. ${REPAIR_STEP}
|
|
28
|
-
8.
|
|
29
|
-
9.
|
|
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}`;
|
|
30
32
|
}
|
|
31
33
|
/**
|
|
32
34
|
* Returns the bullet-list workflow for rules files.
|
|
@@ -39,6 +41,7 @@ export function bulletWorkflow(agentName, options = {}) {
|
|
|
39
41
|
- ${IMPACT_STEP}
|
|
40
42
|
{{KGRAPH_CAPTURE_POLICY}}
|
|
41
43
|
- ${REPAIR_STEP}
|
|
44
|
+
- ${COMPACT_STEP}
|
|
42
45
|
- Run \`kgraph visualize\` to open the interactive dependency graph at http://localhost:4242 with PNG export.
|
|
43
46
|
- ${HISTORY_STEP}`;
|
|
44
47
|
}
|
|
@@ -32,6 +32,9 @@ export async function readSessionLedger(workspace) {
|
|
|
32
32
|
export async function recordSessionEvent(workspace, input) {
|
|
33
33
|
const now = new Date().toISOString();
|
|
34
34
|
const state = await readSessionState(workspace);
|
|
35
|
+
if (input.type === 'end' && !state.active[input.agent]) {
|
|
36
|
+
throw new KGraphError(`No active session for agent "${input.agent}".`);
|
|
37
|
+
}
|
|
35
38
|
// Auto-close any open session for this agent before starting a new one so
|
|
36
39
|
// the ledger entry is never silently lost on repeated start calls.
|
|
37
40
|
if (input.type === 'start' && state.active[input.agent]) {
|
|
@@ -53,7 +53,7 @@ export async function readCognitionNotes(workspace) {
|
|
|
53
53
|
const raw = await readFile(filePath, "utf8");
|
|
54
54
|
const encoded = parseEmbeddedJson(raw);
|
|
55
55
|
if (encoded) {
|
|
56
|
-
notes.push(encoded);
|
|
56
|
+
notes.push(normalizeCognitionNote(encoded));
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
return notes;
|
|
@@ -80,6 +80,30 @@ function parseEmbeddedJson(raw) {
|
|
|
80
80
|
const encoded = raw.match(/```json\n([\s\S]*?)\n```/);
|
|
81
81
|
return encoded ? JSON.parse(encoded[1]) : undefined;
|
|
82
82
|
}
|
|
83
|
+
function normalizeCognitionNote(note) {
|
|
84
|
+
const title = note.title ?? 'Untitled Cognition Note';
|
|
85
|
+
return {
|
|
86
|
+
title,
|
|
87
|
+
kind: note.kind ?? 'summary',
|
|
88
|
+
confidence: note.confidence ?? 'medium',
|
|
89
|
+
domain: note.domain,
|
|
90
|
+
tags: note.tags ?? [],
|
|
91
|
+
summary: note.summary,
|
|
92
|
+
sections: note.sections ?? {},
|
|
93
|
+
relatedFiles: note.relatedFiles ?? [],
|
|
94
|
+
relatedSymbols: note.relatedSymbols ?? [],
|
|
95
|
+
warnings: note.warnings ?? [],
|
|
96
|
+
id: (note.id ?? slugify(title)) || 'cognition-note',
|
|
97
|
+
sourceInboxPath: note.sourceInboxPath ?? '',
|
|
98
|
+
processedPath: note.processedPath ?? '',
|
|
99
|
+
createdAt: note.createdAt ?? '',
|
|
100
|
+
updatedAt: note.updatedAt,
|
|
101
|
+
source: note.source ?? 'inbox',
|
|
102
|
+
supersedes: note.supersedes,
|
|
103
|
+
supersededBy: note.supersededBy,
|
|
104
|
+
referencesStatus: note.referencesStatus ?? 'unresolved',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
83
107
|
function mergeDomainRecords(existing, next) {
|
|
84
108
|
return {
|
|
85
109
|
...existing,
|
|
@@ -109,7 +133,7 @@ function renderCognitionNote(note) {
|
|
|
109
133
|
const sectionText = Object.entries(note.sections)
|
|
110
134
|
.map(([heading, content]) => `## ${heading}\n\n${content.trim()}`)
|
|
111
135
|
.join("\n\n");
|
|
112
|
-
return `# ${note.title}\n\nStatus: ${note.referencesStatus}\n\n${sectionText}\n\n## KGraph Metadata\n\n\`\`\`json\n${JSON.stringify(note, null, 2)}\n\`\`\`\n`;
|
|
136
|
+
return `# ${note.title}\n\nType: ${note.kind ?? 'summary'}\nConfidence: ${note.confidence ?? 'medium'}\nStatus: ${note.referencesStatus}\nSource: ${note.source ?? 'inbox'}\n\n${sectionText}\n\n## KGraph Metadata\n\n\`\`\`json\n${JSON.stringify(note, null, 2)}\n\`\`\`\n`;
|
|
113
137
|
}
|
|
114
138
|
function renderDomainRecord(domain) {
|
|
115
139
|
return `# ${domain.name}\n\n${domain.description ?? ""}\n\n## Files\n\n${domain.files
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { CodeSymbol, Relationship, RepositoryFile } from './maps.js';
|
|
2
2
|
export type ReferenceStatus = 'current' | 'stale' | 'unresolved' | 'mixed';
|
|
3
|
+
export type CognitionKind = 'finding' | 'decision' | 'gotcha' | 'summary' | 'relationship';
|
|
4
|
+
export type CognitionConfidence = 'high' | 'medium' | 'low';
|
|
5
|
+
export type CognitionSource = 'inbox' | 'conclude' | 'session-conclude' | 'compact';
|
|
3
6
|
export interface ParsedCognitionNote {
|
|
4
7
|
title: string;
|
|
8
|
+
kind: CognitionKind;
|
|
9
|
+
confidence: CognitionConfidence;
|
|
5
10
|
domain?: string;
|
|
6
11
|
tags: string[];
|
|
7
12
|
summary?: string;
|
|
@@ -15,6 +20,10 @@ export interface CognitionNote extends ParsedCognitionNote {
|
|
|
15
20
|
sourceInboxPath: string;
|
|
16
21
|
processedPath: string;
|
|
17
22
|
createdAt: string;
|
|
23
|
+
updatedAt?: string;
|
|
24
|
+
source: CognitionSource;
|
|
25
|
+
supersedes?: string[];
|
|
26
|
+
supersededBy?: string;
|
|
18
27
|
referencesStatus: ReferenceStatus;
|
|
19
28
|
}
|
|
20
29
|
export interface DomainRecord {
|