@kentwynn/kgraph 0.1.21 → 0.1.23

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.
Files changed (46) hide show
  1. package/README.md +56 -3
  2. package/dist/cli/commands/doctor.js +8 -0
  3. package/dist/cli/commands/history.d.ts +3 -1
  4. package/dist/cli/commands/history.js +22 -7
  5. package/dist/cli/commands/impact.d.ts +4 -0
  6. package/dist/cli/commands/impact.js +51 -0
  7. package/dist/cli/commands/init.js +11 -3
  8. package/dist/cli/commands/integrate.js +32 -5
  9. package/dist/cli/commands/session.d.ts +4 -0
  10. package/dist/cli/commands/session.js +112 -0
  11. package/dist/cli/commands/workflow.js +5 -0
  12. package/dist/cli/help.d.ts +6 -0
  13. package/dist/cli/help.js +17 -2
  14. package/dist/cli/index.js +4 -0
  15. package/dist/cognition/cognition-quality.d.ts +13 -1
  16. package/dist/cognition/cognition-quality.js +60 -0
  17. package/dist/config/config.js +6 -0
  18. package/dist/context/context-query.js +1 -0
  19. package/dist/context/impact.d.ts +20 -0
  20. package/dist/context/impact.js +72 -0
  21. package/dist/context/ranking.js +21 -6
  22. package/dist/integrations/adapters/claude-code.js +68 -10
  23. package/dist/integrations/adapters/cline.js +8 -6
  24. package/dist/integrations/adapters/codex.js +12 -10
  25. package/dist/integrations/adapters/copilot.js +35 -10
  26. package/dist/integrations/adapters/cursor.js +8 -6
  27. package/dist/integrations/adapters/gemini.js +8 -6
  28. package/dist/integrations/adapters/windsurf.js +8 -6
  29. package/dist/integrations/instruction-blocks.d.ts +4 -0
  30. package/dist/integrations/instruction-blocks.js +17 -0
  31. package/dist/integrations/integration-store.d.ts +4 -2
  32. package/dist/integrations/integration-store.js +19 -5
  33. package/dist/scanner/repo-scanner.js +2 -0
  34. package/dist/session/session-store.d.ts +15 -0
  35. package/dist/session/session-store.js +170 -0
  36. package/dist/session/token-estimator.d.ts +1 -0
  37. package/dist/session/token-estimator.js +17 -0
  38. package/dist/storage/kgraph-paths.js +4 -2
  39. package/dist/types/config.d.ts +3 -0
  40. package/dist/types/maps.d.ts +1 -0
  41. package/dist/types/session.d.ts +51 -0
  42. package/dist/types/session.js +1 -0
  43. package/dist/visualization/graph-builder.d.ts +1 -0
  44. package/dist/visualization/graph-builder.js +14 -1
  45. package/dist/visualization/html-template.js +19 -2
  46. package/package.json +1 -1
package/README.md CHANGED
@@ -63,6 +63,14 @@ kgraph "blog admin token usage"
63
63
 
64
64
  Instead of reading the whole repo, it gets a compact starting point: relevant files, symbols, relationships, domains, prior notes, and stale references to watch.
65
65
 
66
+ When you need change impact instead of broad context:
67
+
68
+ ```bash
69
+ kgraph impact Button
70
+ ```
71
+
72
+ That shows matched files/symbols, files importing the target, known callers/callees, related cognition, and simple risk signals.
73
+
66
74
  ## Install
67
75
 
68
76
  Use the published CLI:
@@ -112,6 +120,18 @@ kgraph
112
120
 
113
121
  Use `kgraph doctor --quality` and `kgraph repair --dry-run` only when stale or noisy cognition references start making context harder to trust.
114
122
 
123
+ Agents can also report session activity so KGraph can estimate token waste:
124
+
125
+ ```bash
126
+ kgraph session start --agent codex
127
+ kgraph session read src/auth.ts --agent codex
128
+ kgraph session write src/auth.ts --agent codex
129
+ kgraph session end --agent codex
130
+ kgraph session
131
+ ```
132
+
133
+ This is optional. Claude Code can use generated hook scripts for automatic capture; other agents use the same commands through their managed instructions, rules, or prompts.
134
+
115
135
  ## Main Commands
116
136
 
117
137
  ```bash
@@ -143,14 +163,33 @@ kgraph doctor
143
163
  kgraph doctor --quality
144
164
  ```
145
165
 
146
- Checks whether the workspace is initialized, maps exist, inbox notes are pending, and configured integrations point to real files. Use `--quality` when context shows stale/noisy cognition references.
166
+ Checks whether the workspace is initialized, maps exist, inbox notes are pending, and configured integrations point to real files. Use `--quality` when context shows stale/noisy cognition references, unresolved local imports, unresolved call edges, duplicate cognition titles, or generated files in the scan.
147
167
 
148
168
  ```bash
149
169
  kgraph repair --dry-run
150
170
  kgraph repair
151
171
  ```
152
172
 
153
- `repair --dry-run` previews cleanup for noisy cognition references, such as framework names recorded as files or local variables recorded as symbols. `repair` applies that cleanup. Run repair intentionally when stale references make context noisy; it is not part of every normal workflow.
173
+ `repair --dry-run` previews cleanup for noisy cognition references, such as framework names recorded as files or local variables recorded as symbols. `repair` applies only the safe noisy-reference cleanup; broader quality findings stay report-only. Run repair intentionally when stale references make context noisy; it is not part of every normal workflow.
174
+
175
+ ```bash
176
+ kgraph impact "Button"
177
+ kgraph impact "createSession" --json
178
+ ```
179
+
180
+ Show practical impact for a file, symbol, or topic: matched files/symbols, import users, callers, callees, ownership edges, related cognition, and risk hints.
181
+
182
+ ```bash
183
+ kgraph session
184
+ kgraph session --json
185
+ kgraph session reset
186
+ kgraph session start --agent codex
187
+ kgraph session read src/auth.ts --agent codex
188
+ kgraph session write src/auth.ts --agent codex
189
+ kgraph session end --agent codex
190
+ ```
191
+
192
+ Track agent-reported read/write activity, repeated reads, and estimated token cost. Supported agents are `codex`, `claude-code`, `copilot`, `cursor`, `gemini`, `windsurf`, and `cline`.
154
193
 
155
194
  ## Optional Step Commands
156
195
 
@@ -187,10 +226,11 @@ Open the local interactive dependency graph at `http://localhost:4242`.
187
226
  ```bash
188
227
  kgraph history
189
228
  kgraph history --last 10
229
+ kgraph history "blog button"
190
230
  kgraph history --json
191
231
  ```
192
232
 
193
- Show processed cognition sessions.
233
+ Show processed cognition sessions. Add a query to find historical work by title, summary, file, symbol, or note body.
194
234
 
195
235
  ## AI Tool Integrations
196
236
 
@@ -198,10 +238,21 @@ KGraph integrations are local files. They do not start background agents, call A
198
238
 
199
239
  ```bash
200
240
  kgraph integrate add codex copilot cursor claude-code gemini windsurf cline
241
+ kgraph integrate add copilot --mode always
242
+ kgraph integrate set copilot --mode manual
201
243
  kgraph integrate list
202
244
  kgraph integrate remove cursor
203
245
  ```
204
246
 
247
+ New integrations default to `always` mode because coding agents often under-classify small UI, route, button, and link changes as not needing repo context.
248
+
249
+ | Mode | Behavior |
250
+ | --- | --- |
251
+ | `always` | Every chat in the repository starts with `kgraph "<topic>"`, even simple or conversational requests. |
252
+ | `smart` | Runs KGraph automatically for repo-specific coding, debugging, architecture, refactor, review, or file-exploration requests. Skips simple conversational requests that do not depend on repo knowledge. |
253
+ | `manual` | Exposes KGraph commands and instructions, but the agent runs KGraph only when the user explicitly asks. |
254
+ | `off` | Disables that integration and removes generated KGraph instruction blocks/command files. |
255
+
205
256
  | Tool | Files KGraph manages |
206
257
  | --- | --- |
207
258
  | Codex | `AGENTS.md`, `.agents/skills/kgraph/SKILL.md` |
@@ -232,6 +283,7 @@ All runtime data lives under `.kgraph/`:
232
283
  ├── cognition/
233
284
  ├── domains/
234
285
  ├── interactions/processed/
286
+ ├── sessions/
235
287
  └── context/
236
288
  ```
237
289
 
@@ -319,6 +371,7 @@ The release workflow builds, tests, packs, publishes the npm package on version
319
371
  - Explicit: no daemon and no hidden background process.
320
372
  - Inspectable: generated knowledge is JSON, YAML, and Markdown.
321
373
  - Deterministic first: useful ranking without requiring embeddings or a model.
374
+ - Practical impact: context, history, quality, and impact commands should answer coding questions directly from local maps.
322
375
  - Assistant-friendly: one normal command, with lower-level commands available when needed.
323
376
 
324
377
  ## Roadmap
@@ -111,6 +111,14 @@ export function printQualityReport(report) {
111
111
  console.log(`Mixed/stale/unresolved notes: ${report.mixedOrStaleCount}`);
112
112
  console.log(`Noisy file refs: ${report.noisyFileRefCount}`);
113
113
  console.log(`Noisy symbol refs: ${report.noisySymbolRefCount}`);
114
+ console.log(`Unresolved local imports: ${report.unresolvedLocalImportCount}`);
115
+ console.log(`Unresolved call edges: ${report.unresolvedCallCount}`);
116
+ console.log(`Duplicate cognition titles: ${report.duplicateTitleCount}`);
117
+ console.log(`Generated files scanned: ${report.generatedFileScanCount}`);
118
+ console.log(`Expensive files: ${report.expensiveFileCount}`);
119
+ console.log(`Session repeated reads: ${report.sessionRepeatedReadCount}`);
120
+ console.log(`Session estimated read tokens: ${report.sessionEstimatedReadTokens}`);
121
+ console.log(`Session repeated-read tokens: ${report.sessionEstimatedRepeatedReadTokens}`);
114
122
  if (report.changes.length === 0) {
115
123
  return;
116
124
  }
@@ -3,6 +3,8 @@ export interface HistoryEntry {
3
3
  timestamp: Date;
4
4
  filename: string;
5
5
  title: string;
6
+ summary?: string;
7
+ text?: string;
6
8
  author?: string;
7
9
  }
8
10
  export declare function registerHistoryCommand(program: Command): void;
@@ -13,4 +15,4 @@ export declare function readHistoryEntries(processedPath: string, rootPath: stri
13
15
  * (colons and dot replaced by dashes when written to disk)
14
16
  */
15
17
  export declare function parseTimestampFromFilename(filename: string): Date | undefined;
16
- export declare function renderHistory(entries: HistoryEntry[], useColor?: boolean): string;
18
+ export declare function renderHistory(entries: HistoryEntry[], useColor?: boolean, query?: string): string;
@@ -4,32 +4,43 @@ import { readFile, readdir } from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
  import { promisify } from 'node:util';
6
6
  import { assertWorkspace, pathExists } from '../../storage/kgraph-paths.js';
7
+ import { rankByFields } from '../../context/ranking.js';
7
8
  import { KGraphError, runCommand } from '../errors.js';
8
9
  const execFileAsync = promisify(execFile);
9
10
  export function registerHistoryCommand(program) {
10
11
  program
11
- .command('history')
12
+ .command('history [query...]')
12
13
  .description('Show a timeline of processed cognition sessions')
13
14
  .option('--last <n>', 'Show only the last N entries')
14
15
  .option('--json', 'Print JSON output')
15
- .action((options) => runCommand(async () => {
16
+ .action((queryParts = [], options) => runCommand(async () => {
16
17
  const workspace = await assertWorkspace(process.cwd());
17
18
  const entries = await readHistoryEntries(workspace.processedInteractionsPath, workspace.rootPath);
19
+ const query = queryParts.join(' ').trim();
18
20
  const limit = options.last !== undefined ? parseInt(options.last, 10) : 0;
19
21
  if (options.last !== undefined && (isNaN(limit) || limit < 1)) {
20
22
  throw new KGraphError('--last must be a positive integer.');
21
23
  }
22
- const shown = limit > 0 ? entries.slice(-limit) : entries;
24
+ const matched = query
25
+ ? rankByFields(query, entries, [
26
+ { name: 'title', value: (entry) => entry.title },
27
+ { name: 'summary', value: (entry) => entry.summary },
28
+ { name: 'content', value: (entry) => entry.text },
29
+ { name: 'filename', value: (entry) => entry.filename },
30
+ ]).map((entry) => entry.item)
31
+ : entries;
32
+ const shown = limit > 0 ? matched.slice(-limit) : matched;
23
33
  if (options.json) {
24
34
  console.log(JSON.stringify(shown.map((e) => ({
25
35
  timestamp: e.timestamp.toISOString(),
26
36
  filename: e.filename,
27
37
  title: e.title,
38
+ ...(e.summary !== undefined ? { summary: e.summary } : {}),
28
39
  ...(e.author !== undefined ? { author: e.author } : {}),
29
40
  })), null, 2));
30
41
  }
31
42
  else {
32
- console.log(renderHistory(shown));
43
+ console.log(renderHistory(shown, undefined, query));
33
44
  }
34
45
  }));
35
46
  }
@@ -50,9 +61,10 @@ export async function readHistoryEntries(processedPath, rootPath) {
50
61
  const filePath = path.join(processedPath, filename);
51
62
  const content = await readFile(filePath, 'utf8');
52
63
  const title = content.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? filename;
64
+ const summary = content.match(/^## Summary\s+([\s\S]*?)(?:\n## |\n# |$)/m)?.[1]?.trim();
53
65
  const relPath = path.relative(rootPath, filePath);
54
66
  const author = await getGitAuthor(rootPath, relPath);
55
- entries.push({ timestamp, filename, title, author });
67
+ entries.push({ timestamp, filename, title, summary, text: content, author });
56
68
  }
57
69
  return entries;
58
70
  }
@@ -69,14 +81,14 @@ export function parseTimestampFromFilename(filename) {
69
81
  const d = new Date(iso);
70
82
  return isNaN(d.getTime()) ? undefined : d;
71
83
  }
72
- export function renderHistory(entries, useColor = supportsColor()) {
84
+ export function renderHistory(entries, useColor = supportsColor(), query = '') {
73
85
  const chalk = new Chalk({ level: useColor ? 3 : 0 });
74
86
  if (entries.length === 0) {
75
87
  return ('\n' +
76
88
  chalk.dim(' No processed cognition notes found. Write Markdown notes to .kgraph/inbox/ and run `kgraph update`.') +
77
89
  '\n');
78
90
  }
79
- const header = ` ${chalk.bold('KGraph History')} ${chalk.dim(`· ${entries.length} ${entries.length === 1 ? 'entry' : 'entries'}`)}`;
91
+ const header = ` ${chalk.bold('KGraph History')} ${chalk.dim(`· ${entries.length} ${entries.length === 1 ? 'entry' : 'entries'}${query ? ` matching "${query}"` : ''}`)}`;
80
92
  const lines = ['', header, ''];
81
93
  const titleWidth = Math.max(...entries.map((e) => e.title.length));
82
94
  for (const entry of entries) {
@@ -86,6 +98,9 @@ export function renderHistory(entries, useColor = supportsColor()) {
86
98
  ? chalk.cyan(`by ${entry.author}`)
87
99
  : chalk.dim('(uncommitted)');
88
100
  lines.push(` ${when} ${title} ${who}`);
101
+ if (entry.summary) {
102
+ lines.push(` ${chalk.dim(entry.summary.split('\n')[0])}`);
103
+ }
89
104
  }
90
105
  lines.push('');
91
106
  return lines.join('\n');
@@ -0,0 +1,4 @@
1
+ import type { Command } from 'commander';
2
+ import { type ImpactResponse } from '../../context/impact.js';
3
+ export declare function registerImpactCommand(program: Command): void;
4
+ export declare function renderImpactMarkdown(response: ImpactResponse): string;
@@ -0,0 +1,51 @@
1
+ import { loadConfig } from '../../config/config.js';
2
+ import { analyzeImpact } from '../../context/impact.js';
3
+ import { readCognitionNotes } from '../../storage/cognition-store.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 registerImpactCommand(program) {
8
+ program
9
+ .command('impact <query>')
10
+ .description('Show practical impact for a file, symbol, or topic')
11
+ .option('--json', 'Print JSON output')
12
+ .action((query, options) => runCommand(async () => {
13
+ if (!query.trim()) {
14
+ throw new KGraphError('Query cannot be empty.');
15
+ }
16
+ const workspace = await assertWorkspace(process.cwd());
17
+ if (!(await mapsExist(workspace))) {
18
+ throw new KGraphError('KGraph maps are missing. Run `kgraph scan` first.');
19
+ }
20
+ const [config, maps, cognition] = await Promise.all([
21
+ loadConfig(workspace),
22
+ readMaps(workspace),
23
+ readCognitionNotes(workspace),
24
+ ]);
25
+ const response = analyzeImpact(query, maps, cognition, config.maxContextItems);
26
+ console.log(options.json ? JSON.stringify(response, null, 2) : renderImpactMarkdown(response));
27
+ }));
28
+ }
29
+ export function renderImpactMarkdown(response) {
30
+ const lines = [`# KGraph Impact`, ``, `Query: ${response.query}`, ``];
31
+ lines.push('## Matched Files', '');
32
+ lines.push(...formatList(response.files.map((file) => `- ${file.item.path} (${file.reasons.join(', ')})`)));
33
+ lines.push('', '## Matched Symbols', '');
34
+ lines.push(...formatList(response.symbols.map((symbol) => `- ${symbol.item.name} in ${symbol.item.filePath}`)));
35
+ lines.push('', '## Imported By', '');
36
+ lines.push(...formatList(response.importedBy.map((file) => `- ${file}`)));
37
+ lines.push('', '## Called By', '');
38
+ lines.push(...formatList(response.callers.map((rel) => `- ${rel.sourceId} calls ${rel.targetId} (${rel.confidence})`)));
39
+ lines.push('', '## Calls', '');
40
+ lines.push(...formatList(response.calls.map((rel) => `- ${rel.sourceId} calls ${rel.targetId} (${rel.confidence})`)));
41
+ lines.push('', '## Ownership', '');
42
+ lines.push(...formatList(response.ownership.map((rel) => `- ${rel.sourceId} owns ${rel.targetId} (${rel.confidence})`)));
43
+ lines.push('', '## Related Cognition', '');
44
+ lines.push(...formatList(response.relatedCognition.map((note) => `- ${note.title} [${note.referencesStatus}]`)));
45
+ lines.push('', '## Risk', '');
46
+ lines.push(...formatList(response.risk.map((item) => `- ${item}`)));
47
+ return lines.join('\n');
48
+ }
49
+ function formatList(items) {
50
+ return items.length > 0 ? items : ['- None'];
51
+ }
@@ -2,13 +2,14 @@ import { writeDefaultConfig } from "../../config/config.js";
2
2
  import { normalizeIntegrationNames } from "../../integrations/integration-registry.js";
3
3
  import { addIntegrations } from "../../integrations/integration-store.js";
4
4
  import { ensureWorkspace } from "../../storage/kgraph-paths.js";
5
- import { runCommand } from "../errors.js";
5
+ import { KGraphError, runCommand } from "../errors.js";
6
6
  export function registerInitCommand(program) {
7
7
  program
8
8
  .command("init")
9
9
  .description("Initialize a .kgraph workspace")
10
10
  .option("--integration <name>", "Configure an AI tool integration", collectOption, [])
11
11
  .option("--integrations <names>", "Configure comma-separated AI tool integrations")
12
+ .option("--mode <mode>", "Integration mode: smart, always, manual, or off", "always")
12
13
  .action((options) => runCommand(async () => {
13
14
  const workspace = await ensureWorkspace(process.cwd());
14
15
  const wroteConfig = await writeDefaultConfig(workspace);
@@ -18,8 +19,9 @@ export function registerInitCommand(program) {
18
19
  ...(options.integrations ? [options.integrations] : [])
19
20
  ]);
20
21
  if (names.length > 0) {
21
- const changed = await addIntegrations(workspace, names);
22
- console.log(`Configured integrations: ${changed.map((item) => item.name).join(", ")}`);
22
+ const mode = normalizeIntegrationMode(options.mode);
23
+ const changed = await addIntegrations(workspace, names, mode);
24
+ console.log(`Configured integrations: ${changed.map((item) => `${item.name}:${item.mode}`).join(", ")}`);
23
25
  }
24
26
  }));
25
27
  }
@@ -27,3 +29,9 @@ function collectOption(value, previous) {
27
29
  previous.push(value);
28
30
  return previous;
29
31
  }
32
+ function normalizeIntegrationMode(value) {
33
+ if (value === "smart" || value === "always" || value === "manual" || value === "off") {
34
+ return value;
35
+ }
36
+ throw new KGraphError("--mode must be smart, always, manual, or off.");
37
+ }
@@ -1,4 +1,4 @@
1
- import { addIntegrations, listIntegrations, removeIntegrations } from "../../integrations/integration-store.js";
1
+ import { addIntegrations, listIntegrations, removeIntegrations, setIntegrationMode } from "../../integrations/integration-store.js";
2
2
  import { normalizeIntegrationNames } from "../../integrations/integration-registry.js";
3
3
  import { assertWorkspace } from "../../storage/kgraph-paths.js";
4
4
  import { KGraphError, runCommand } from "../errors.js";
@@ -12,17 +12,38 @@ export function registerIntegrateCommand(program) {
12
12
  return;
13
13
  }
14
14
  for (const integration of integrations) {
15
- console.log(`${integration.name} ${integration.enabled ? "enabled" : "disabled"} ${integration.targetPath} ${integration.targetExists ? "present" : "missing"}`);
15
+ console.log(`${integration.name} ${integration.enabled ? "enabled" : "disabled"} ${integration.mode} ${integration.targetPath} ${integration.targetExists ? "present" : "missing"}`);
16
16
  }
17
17
  }));
18
- integrate.command("add").description("Add AI tool integrations").argument("<names...>").action((names) => runCommand(async () => {
18
+ integrate
19
+ .command("add")
20
+ .description("Add AI tool integrations")
21
+ .argument("<names...>")
22
+ .option("--mode <mode>", "smart, always, manual, or off", "always")
23
+ .action((names, options) => runCommand(async () => {
19
24
  const workspace = await assertWorkspace(process.cwd());
20
25
  const normalized = normalizeIntegrationNames(names);
21
26
  if (normalized.length === 0) {
22
27
  throw new KGraphError("Provide at least one integration name.");
23
28
  }
24
- const changed = await addIntegrations(workspace, normalized);
25
- console.log(`Configured integrations: ${changed.map((item) => item.name).join(", ")}`);
29
+ const mode = normalizeIntegrationMode(options.mode);
30
+ const changed = await addIntegrations(workspace, normalized, mode);
31
+ console.log(`Configured integrations: ${changed.map((item) => `${item.name}:${item.mode}`).join(", ")}`);
32
+ }));
33
+ integrate
34
+ .command("set")
35
+ .description("Set AI tool integration mode")
36
+ .argument("<names...>")
37
+ .requiredOption("--mode <mode>", "smart, always, manual, or off")
38
+ .action((names, options) => runCommand(async () => {
39
+ const workspace = await assertWorkspace(process.cwd());
40
+ const normalized = normalizeIntegrationNames(names);
41
+ if (normalized.length === 0) {
42
+ throw new KGraphError("Provide at least one integration name.");
43
+ }
44
+ const mode = normalizeIntegrationMode(options.mode);
45
+ const changed = await setIntegrationMode(workspace, normalized, mode);
46
+ console.log(`Updated integrations: ${changed.map((item) => `${item.name}:${item.mode}`).join(", ")}`);
26
47
  }));
27
48
  integrate.command("remove").description("Remove AI tool integrations").argument("<names...>").action((names) => runCommand(async () => {
28
49
  const workspace = await assertWorkspace(process.cwd());
@@ -34,3 +55,9 @@ export function registerIntegrateCommand(program) {
34
55
  console.log(`Removed integrations: ${removed.join(", ")}`);
35
56
  }));
36
57
  }
58
+ function normalizeIntegrationMode(value) {
59
+ if (value === "smart" || value === "always" || value === "manual" || value === "off") {
60
+ return value;
61
+ }
62
+ throw new KGraphError("--mode must be smart, always, manual, or off.");
63
+ }
@@ -0,0 +1,4 @@
1
+ import type { Command } from 'commander';
2
+ import { buildSessionReport } from '../../session/session-store.js';
3
+ export declare function registerSessionCommand(program: Command): void;
4
+ export declare function renderSessionReport(report: Awaited<ReturnType<typeof buildSessionReport>>): string;
@@ -0,0 +1,112 @@
1
+ import { assertSessionAgent, buildSessionReport, recordSessionEvent, resetSession, } from '../../session/session-store.js';
2
+ import { assertWorkspace } from '../../storage/kgraph-paths.js';
3
+ import { readMaps } from '../../storage/map-store.js';
4
+ import { KGraphError, runCommand } from '../errors.js';
5
+ export function registerSessionCommand(program) {
6
+ const session = program
7
+ .command('session')
8
+ .description('Track agent read/write session activity and token estimates')
9
+ .option('--json', 'Print JSON output')
10
+ .action((options) => runCommand(async () => {
11
+ const workspace = await assertWorkspace(process.cwd());
12
+ const report = await buildSessionReport(workspace);
13
+ console.log(options.json ? JSON.stringify(report, null, 2) : renderSessionReport(report));
14
+ }));
15
+ session
16
+ .command('start')
17
+ .requiredOption('--agent <name>', 'KGraph integration agent name')
18
+ .option('--source <source>', 'automatic, agent-reported, or manual', 'manual')
19
+ .action((options) => runCommand(async () => {
20
+ const workspace = await assertWorkspace(process.cwd());
21
+ const event = await recordSessionEvent(workspace, {
22
+ agent: requireAgent(options.agent),
23
+ type: 'start',
24
+ captureSource: normalizeSource(options.source),
25
+ });
26
+ console.log(`KGraph session started for ${event.agent}.`);
27
+ }));
28
+ session
29
+ .command('read <path>')
30
+ .requiredOption('--agent <name>', 'KGraph integration agent name')
31
+ .option('--source <source>', 'automatic, agent-reported, or manual', 'manual')
32
+ .action((filePath, options) => runCommand(async () => {
33
+ const workspace = await assertWorkspace(process.cwd());
34
+ const maps = await readMaps(workspace);
35
+ const event = await recordSessionEvent(workspace, {
36
+ agent: requireAgent(options.agent),
37
+ type: 'read',
38
+ path: filePath,
39
+ captureSource: normalizeSource(options.source),
40
+ fileMap: maps.fileMap,
41
+ });
42
+ console.log(`KGraph recorded read: ${event.path}${event.repeated ? ' (repeated)' : ''}${event.tokenEstimate !== undefined ? ` ~${event.tokenEstimate} tokens` : ''}.`);
43
+ }));
44
+ session
45
+ .command('write <path>')
46
+ .requiredOption('--agent <name>', 'KGraph integration agent name')
47
+ .option('--source <source>', 'automatic, agent-reported, or manual', 'manual')
48
+ .action((filePath, options) => runCommand(async () => {
49
+ const workspace = await assertWorkspace(process.cwd());
50
+ const maps = await readMaps(workspace);
51
+ const event = await recordSessionEvent(workspace, {
52
+ agent: requireAgent(options.agent),
53
+ type: 'write',
54
+ path: filePath,
55
+ captureSource: normalizeSource(options.source),
56
+ fileMap: maps.fileMap,
57
+ });
58
+ console.log(`KGraph recorded write: ${event.path}${event.tokenEstimate !== undefined ? ` ~${event.tokenEstimate} tokens` : ''}.`);
59
+ }));
60
+ session
61
+ .command('end')
62
+ .requiredOption('--agent <name>', 'KGraph integration agent name')
63
+ .option('--source <source>', 'automatic, agent-reported, or manual', 'manual')
64
+ .action((options) => runCommand(async () => {
65
+ const workspace = await assertWorkspace(process.cwd());
66
+ const event = await recordSessionEvent(workspace, {
67
+ agent: requireAgent(options.agent),
68
+ type: 'end',
69
+ captureSource: normalizeSource(options.source),
70
+ });
71
+ console.log(`KGraph session ended for ${event.agent}.`);
72
+ }));
73
+ session
74
+ .command('reset')
75
+ .description('Clear the current session tracker')
76
+ .action(() => runCommand(async () => {
77
+ const workspace = await assertWorkspace(process.cwd());
78
+ await resetSession(workspace);
79
+ console.log('KGraph current session reset.');
80
+ }));
81
+ }
82
+ export function renderSessionReport(report) {
83
+ const lines = ['', 'KGraph Session', ''];
84
+ lines.push(`Active agents: ${report.activeAgents.length === 0 ? 'none' : report.activeAgents.map((agent) => agent.agent).join(', ')}`);
85
+ lines.push(`Reads: ${report.readCount}`);
86
+ lines.push(`Writes: ${report.writeCount}`);
87
+ lines.push(`Repeated reads: ${report.repeatedReadCount}`);
88
+ lines.push(`Estimated read tokens: ${report.estimatedReadTokens}`);
89
+ lines.push(`Estimated repeated-read tokens: ${report.estimatedRepeatedReadTokens}`);
90
+ lines.push('', 'Top Repeated Reads');
91
+ lines.push(...formatList(report.topRepeatedReads.map((item) => `- ${item.path} read ${item.count} times (~${item.estimatedTokens} tokens)`)));
92
+ lines.push('', 'Recent Events');
93
+ lines.push(...formatList(report.recentEvents.map((event) => `- ${event.agent} ${event.type}${event.path ? ` ${event.path}` : ''} [${event.captureSource}]`)));
94
+ lines.push('', 'Recent Ledger');
95
+ lines.push(...formatList(report.ledger.map((entry) => `- ${entry.agent} ${entry.readCount} reads, ${entry.writeCount} writes, ${entry.repeatedReadCount} repeated`)));
96
+ return lines.join('\n');
97
+ }
98
+ function requireAgent(value) {
99
+ if (!value) {
100
+ throw new KGraphError('--agent is required.');
101
+ }
102
+ return assertSessionAgent(value);
103
+ }
104
+ function normalizeSource(value) {
105
+ if (value === 'automatic' || value === 'agent-reported' || value === 'manual') {
106
+ return value;
107
+ }
108
+ throw new KGraphError('--source must be automatic, agent-reported, or manual.');
109
+ }
110
+ function formatList(items) {
111
+ return items.length > 0 ? items : ['- None'];
112
+ }
@@ -36,6 +36,11 @@ export async function runDefaultWorkflow(query) {
36
36
  files: scan.files.length,
37
37
  symbols: scan.symbols.length,
38
38
  cognitionNotes: update.processed.length,
39
+ integrations: config.integrations.map((integration) => ({
40
+ name: integration.name,
41
+ mode: integration.mode,
42
+ enabled: integration.enabled,
43
+ })),
39
44
  }));
40
45
  console.log('');
41
46
  for (const warning of [...scan.warnings, ...update.warnings]) {
@@ -3,6 +3,12 @@ interface WorkflowBannerStats {
3
3
  files: number;
4
4
  symbols: number;
5
5
  cognitionNotes: number;
6
+ integrations?: WorkflowBannerIntegration[];
7
+ }
8
+ interface WorkflowBannerIntegration {
9
+ name: string;
10
+ mode: string;
11
+ enabled: boolean;
6
12
  }
7
13
  export declare function renderWorkflowBanner(stats: WorkflowBannerStats, useColor?: boolean): string;
8
14
  export {};
package/dist/cli/help.js CHANGED
@@ -27,19 +27,25 @@ export function renderRootHelp(useColor = supportsColor()) {
27
27
  '',
28
28
  theme.bold('Workflows'),
29
29
  command('scan', 'Optional: refresh only file, symbol, import, and relationship maps'),
30
+ command('session', 'Show agent read/write activity and token estimates'),
31
+ command('session read src/auth.ts --agent codex', 'Record an agent file read'),
30
32
  command('context "auth token refresh"', 'Optional: return context without scanning or updating'),
33
+ command('impact "Button"', 'Show imports, callers, calls, cognition, and risk'),
31
34
  command('update', 'Optional: process only .kgraph/inbox Markdown cognition notes'),
32
35
  command('doctor', 'Check workspace health and next actions'),
33
36
  command('doctor --quality', 'Report stale/noisy cognition references'),
34
37
  command('repair --dry-run', 'Preview cognition reference cleanup'),
35
38
  command('repair', 'Clean noisy stale cognition references'),
36
39
  command('visualize', 'Interactive dependency graph at http://localhost:4242'),
37
- command('history', 'Timeline of processed cognition sessions'),
40
+ command('history "blog button"', 'Search processed cognition sessions'),
38
41
  '',
39
42
  theme.bold('Integrations'),
40
43
  command('integrate list', 'Show configured AI tool integrations'),
41
- command('integrate add gemini windsurf cline', 'Write KGraph instructions for AI tools'),
44
+ command('integrate add gemini windsurf cline', 'Write KGraph instructions using always mode by default'),
45
+ command('integrate add copilot --mode always', 'Every Copilot chat starts with kgraph "<topic>"'),
46
+ command('integrate set copilot --mode manual', 'Only run KGraph when explicitly requested'),
42
47
  command('integrate remove cursor', 'Remove KGraph-managed instruction blocks'),
48
+ command('--mode smart|always|manual|off', 'Control automatic KGraph involvement per integration'),
43
49
  '',
44
50
  theme.bold('Options'),
45
51
  command('-V, --version', 'Show version'),
@@ -57,6 +63,13 @@ export function renderRootHelp(useColor = supportsColor()) {
57
63
  export function renderWorkflowBanner(stats, useColor = supportsColor()) {
58
64
  const theme = new Chalk({ level: useColor ? 3 : 0 });
59
65
  const command = (name, description) => ` ${theme.green(name.padEnd(42))} ${description}`;
66
+ const integrationLine = stats.integrations && stats.integrations.length > 0
67
+ ? stats.integrations
68
+ .map((integration) => integration.enabled
69
+ ? `${integration.name}:${integration.mode}`
70
+ : `${integration.name}:off`)
71
+ .join(', ')
72
+ : 'none configured';
60
73
  return [
61
74
  '',
62
75
  theme.hex('#7dd3fc').bold(renderLogo()),
@@ -67,11 +80,13 @@ export function renderWorkflowBanner(stats, useColor = supportsColor()) {
67
80
  command('files', String(stats.files)),
68
81
  command('symbols', String(stats.symbols)),
69
82
  command('cognition notes processed', String(stats.cognitionNotes)),
83
+ command('integration modes', integrationLine),
70
84
  '',
71
85
  theme.bold('Next'),
72
86
  command('kgraph "auth token refresh"', 'Return compact context for a topic'),
73
87
  command('kgraph doctor', 'Check workspace health'),
74
88
  command('kgraph doctor --quality', 'Check cognition quality'),
89
+ command('kgraph session', 'Check session token waste'),
75
90
  command('kgraph --help', 'Show all commands'),
76
91
  ].join('\n');
77
92
  }
package/dist/cli/index.js CHANGED
@@ -6,10 +6,12 @@ import { fileURLToPath } from 'node:url';
6
6
  import { registerContextCommand } from './commands/context.js';
7
7
  import { registerDoctorCommand } from './commands/doctor.js';
8
8
  import { registerHistoryCommand } from './commands/history.js';
9
+ import { registerImpactCommand } from './commands/impact.js';
9
10
  import { registerInitCommand } from './commands/init.js';
10
11
  import { registerIntegrateCommand } from './commands/integrate.js';
11
12
  import { registerRepairCommand } from './commands/repair.js';
12
13
  import { registerScanCommand } from './commands/scan.js';
14
+ import { registerSessionCommand } from './commands/session.js';
13
15
  import { registerUpdateCommand } from './commands/update.js';
14
16
  import { registerVisualizeCommand } from './commands/visualize.js';
15
17
  import { runDefaultWorkflow } from './commands/workflow.js';
@@ -37,8 +39,10 @@ export function createProgram() {
37
39
  });
38
40
  registerInitCommand(program);
39
41
  registerScanCommand(program);
42
+ registerSessionCommand(program);
40
43
  registerUpdateCommand(program);
41
44
  registerContextCommand(program);
45
+ registerImpactCommand(program);
42
46
  registerIntegrateCommand(program);
43
47
  registerVisualizeCommand(program);
44
48
  registerHistoryCommand(program);
@@ -1,6 +1,6 @@
1
1
  import type { KGraphWorkspace } from '../types/config.js';
2
2
  import type { ReferenceStatus } from '../types/cognition.js';
3
- import type { FileMap, SymbolMap } from '../types/maps.js';
3
+ import type { DependencyMap, FileMap, RelationshipMap, SymbolMap } from '../types/maps.js';
4
4
  export interface CognitionRepairChange {
5
5
  noteId: string;
6
6
  title: string;
@@ -13,13 +13,25 @@ export interface CognitionQualityReport {
13
13
  mixedOrStaleCount: number;
14
14
  noisyFileRefCount: number;
15
15
  noisySymbolRefCount: number;
16
+ unresolvedLocalImportCount: number;
17
+ unresolvedCallCount: number;
18
+ duplicateTitleCount: number;
19
+ generatedFileScanCount: number;
20
+ expensiveFileCount: number;
21
+ sessionRepeatedReadCount: number;
22
+ sessionEstimatedReadTokens: number;
23
+ sessionEstimatedRepeatedReadTokens: number;
16
24
  changes: CognitionRepairChange[];
17
25
  }
18
26
  export declare function analyzeCognitionQuality(workspace: KGraphWorkspace, maps: {
19
27
  fileMap: FileMap;
20
28
  symbolMap: SymbolMap;
29
+ dependencyMap?: DependencyMap;
30
+ relationshipMap?: RelationshipMap;
21
31
  }): Promise<CognitionQualityReport>;
22
32
  export declare function repairCognition(workspace: KGraphWorkspace, maps: {
23
33
  fileMap: FileMap;
24
34
  symbolMap: SymbolMap;
35
+ dependencyMap?: DependencyMap;
36
+ relationshipMap?: RelationshipMap;
25
37
  }, dryRun?: boolean): Promise<CognitionQualityReport>;