@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.
- package/README.md +56 -3
- package/dist/cli/commands/doctor.js +8 -0
- package/dist/cli/commands/history.d.ts +3 -1
- package/dist/cli/commands/history.js +22 -7
- package/dist/cli/commands/impact.d.ts +4 -0
- package/dist/cli/commands/impact.js +51 -0
- package/dist/cli/commands/init.js +11 -3
- package/dist/cli/commands/integrate.js +32 -5
- package/dist/cli/commands/session.d.ts +4 -0
- package/dist/cli/commands/session.js +112 -0
- package/dist/cli/commands/workflow.js +5 -0
- package/dist/cli/help.d.ts +6 -0
- package/dist/cli/help.js +17 -2
- package/dist/cli/index.js +4 -0
- package/dist/cognition/cognition-quality.d.ts +13 -1
- package/dist/cognition/cognition-quality.js +60 -0
- package/dist/config/config.js +6 -0
- package/dist/context/context-query.js +1 -0
- package/dist/context/impact.d.ts +20 -0
- package/dist/context/impact.js +72 -0
- package/dist/context/ranking.js +21 -6
- package/dist/integrations/adapters/claude-code.js +68 -10
- package/dist/integrations/adapters/cline.js +8 -6
- package/dist/integrations/adapters/codex.js +12 -10
- package/dist/integrations/adapters/copilot.js +35 -10
- package/dist/integrations/adapters/cursor.js +8 -6
- package/dist/integrations/adapters/gemini.js +8 -6
- package/dist/integrations/adapters/windsurf.js +8 -6
- package/dist/integrations/instruction-blocks.d.ts +4 -0
- package/dist/integrations/instruction-blocks.js +17 -0
- package/dist/integrations/integration-store.d.ts +4 -2
- package/dist/integrations/integration-store.js +19 -5
- package/dist/scanner/repo-scanner.js +2 -0
- package/dist/session/session-store.d.ts +15 -0
- package/dist/session/session-store.js +170 -0
- package/dist/session/token-estimator.d.ts +1 -0
- package/dist/session/token-estimator.js +17 -0
- package/dist/storage/kgraph-paths.js +4 -2
- package/dist/types/config.d.ts +3 -0
- package/dist/types/maps.d.ts +1 -0
- package/dist/types/session.d.ts +51 -0
- package/dist/types/session.js +1 -0
- package/dist/visualization/graph-builder.d.ts +1 -0
- package/dist/visualization/graph-builder.js +14 -1
- package/dist/visualization/html-template.js +19 -2
- 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
|
|
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
|
|
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,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
|
|
22
|
-
|
|
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
|
|
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
|
|
25
|
-
|
|
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]) {
|
package/dist/cli/help.d.ts
CHANGED
|
@@ -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', '
|
|
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
|
|
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>;
|