@kentwynn/kgraph 0.1.20 → 0.1.22
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 +24 -6
- package/dist/cli/commands/doctor.js +4 -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/help.js +2 -1
- package/dist/cli/index.js +2 -0
- package/dist/cognition/cognition-quality.d.ts +9 -1
- package/dist/cognition/cognition-quality.js +46 -0
- package/dist/context/context-query.js +8 -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 +14 -8
- package/dist/integrations/adapters/cline.js +2 -1
- package/dist/integrations/adapters/codex.js +8 -7
- package/dist/integrations/adapters/copilot.js +19 -6
- package/dist/integrations/adapters/cursor.js +2 -1
- package/dist/integrations/adapters/gemini.js +2 -1
- package/dist/integrations/adapters/windsurf.js +2 -1
- package/dist/scanner/ts-symbol-extractor.js +94 -7
- package/dist/types/maps.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,6 +51,7 @@ KGraph stores the reusable parts locally:
|
|
|
51
51
|
- What files exist and what language they use.
|
|
52
52
|
- What symbols each source file defines.
|
|
53
53
|
- Which files import each other.
|
|
54
|
+
- Which TypeScript/JavaScript functions and methods directly call each other when KGraph can infer it cheaply.
|
|
54
55
|
- Which notes, decisions, debugging findings, and gotchas were captured from prior sessions.
|
|
55
56
|
- Which cognition references are current, mixed, stale, or unresolved after code moves.
|
|
56
57
|
|
|
@@ -62,6 +63,14 @@ kgraph "blog admin token usage"
|
|
|
62
63
|
|
|
63
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.
|
|
64
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
|
+
|
|
65
74
|
## Install
|
|
66
75
|
|
|
67
76
|
Use the published CLI:
|
|
@@ -142,14 +151,21 @@ kgraph doctor
|
|
|
142
151
|
kgraph doctor --quality
|
|
143
152
|
```
|
|
144
153
|
|
|
145
|
-
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.
|
|
154
|
+
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.
|
|
146
155
|
|
|
147
156
|
```bash
|
|
148
157
|
kgraph repair --dry-run
|
|
149
158
|
kgraph repair
|
|
150
159
|
```
|
|
151
160
|
|
|
152
|
-
`repair --dry-run` previews cleanup for noisy cognition references, such as framework names recorded as files or local variables recorded as symbols. `repair` applies
|
|
161
|
+
`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.
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
kgraph impact "Button"
|
|
165
|
+
kgraph impact "createSession" --json
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Show practical impact for a file, symbol, or topic: matched files/symbols, import users, callers, callees, ownership edges, related cognition, and risk hints.
|
|
153
169
|
|
|
154
170
|
## Optional Step Commands
|
|
155
171
|
|
|
@@ -186,10 +202,11 @@ Open the local interactive dependency graph at `http://localhost:4242`.
|
|
|
186
202
|
```bash
|
|
187
203
|
kgraph history
|
|
188
204
|
kgraph history --last 10
|
|
205
|
+
kgraph history "blog button"
|
|
189
206
|
kgraph history --json
|
|
190
207
|
```
|
|
191
208
|
|
|
192
|
-
Show processed cognition sessions.
|
|
209
|
+
Show processed cognition sessions. Add a query to find historical work by title, summary, file, symbol, or note body.
|
|
193
210
|
|
|
194
211
|
## AI Tool Integrations
|
|
195
212
|
|
|
@@ -240,7 +257,7 @@ The files are local, inspectable, and human-readable. There is no database, tele
|
|
|
240
257
|
|
|
241
258
|
KGraph deeply scans:
|
|
242
259
|
|
|
243
|
-
- TypeScript and JavaScript
|
|
260
|
+
- TypeScript and JavaScript, including lightweight function/method call relationships
|
|
244
261
|
- Python
|
|
245
262
|
- Go
|
|
246
263
|
- Rust
|
|
@@ -248,7 +265,7 @@ KGraph deeply scans:
|
|
|
248
265
|
- C and C++
|
|
249
266
|
- C#
|
|
250
267
|
|
|
251
|
-
Other
|
|
268
|
+
Other languages keep practical file, import, and symbol depth without full call graph analysis. Common file types still appear in the file map with generic metadata, so context queries can still point to docs, config, SQL, CSS, HTML, YAML, and similar files.
|
|
252
269
|
|
|
253
270
|
## Visualization
|
|
254
271
|
|
|
@@ -256,7 +273,7 @@ Other common file types still appear in the file map with generic metadata, so c
|
|
|
256
273
|
kgraph visualize
|
|
257
274
|
```
|
|
258
275
|
|
|
259
|
-
The graph shows files, symbols, imports, cognition notes, and relationship edges. Cognition notes are colored by reference health:
|
|
276
|
+
The graph shows files, symbols, imports, TypeScript/JavaScript call edges, ownership edges, cognition notes, and relationship edges. Cognition notes are colored by reference health:
|
|
260
277
|
|
|
261
278
|
- current
|
|
262
279
|
- mixed
|
|
@@ -318,6 +335,7 @@ The release workflow builds, tests, packs, publishes the npm package on version
|
|
|
318
335
|
- Explicit: no daemon and no hidden background process.
|
|
319
336
|
- Inspectable: generated knowledge is JSON, YAML, and Markdown.
|
|
320
337
|
- Deterministic first: useful ranking without requiring embeddings or a model.
|
|
338
|
+
- Practical impact: context, history, quality, and impact commands should answer coding questions directly from local maps.
|
|
321
339
|
- Assistant-friendly: one normal command, with lower-level commands available when needed.
|
|
322
340
|
|
|
323
341
|
## Roadmap
|
|
@@ -111,6 +111,10 @@ 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}`);
|
|
114
118
|
if (report.changes.length === 0) {
|
|
115
119
|
return;
|
|
116
120
|
}
|
|
@@ -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
|
+
}
|
package/dist/cli/help.js
CHANGED
|
@@ -28,13 +28,14 @@ export function renderRootHelp(useColor = supportsColor()) {
|
|
|
28
28
|
theme.bold('Workflows'),
|
|
29
29
|
command('scan', 'Optional: refresh only file, symbol, import, and relationship maps'),
|
|
30
30
|
command('context "auth token refresh"', 'Optional: return context without scanning or updating'),
|
|
31
|
+
command('impact "Button"', 'Show imports, callers, calls, cognition, and risk'),
|
|
31
32
|
command('update', 'Optional: process only .kgraph/inbox Markdown cognition notes'),
|
|
32
33
|
command('doctor', 'Check workspace health and next actions'),
|
|
33
34
|
command('doctor --quality', 'Report stale/noisy cognition references'),
|
|
34
35
|
command('repair --dry-run', 'Preview cognition reference cleanup'),
|
|
35
36
|
command('repair', 'Clean noisy stale cognition references'),
|
|
36
37
|
command('visualize', 'Interactive dependency graph at http://localhost:4242'),
|
|
37
|
-
command('history', '
|
|
38
|
+
command('history "blog button"', 'Search processed cognition sessions'),
|
|
38
39
|
'',
|
|
39
40
|
theme.bold('Integrations'),
|
|
40
41
|
command('integrate list', 'Show configured AI tool integrations'),
|
package/dist/cli/index.js
CHANGED
|
@@ -6,6 +6,7 @@ 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';
|
|
@@ -39,6 +40,7 @@ export function createProgram() {
|
|
|
39
40
|
registerScanCommand(program);
|
|
40
41
|
registerUpdateCommand(program);
|
|
41
42
|
registerContextCommand(program);
|
|
43
|
+
registerImpactCommand(program);
|
|
42
44
|
registerIntegrateCommand(program);
|
|
43
45
|
registerVisualizeCommand(program);
|
|
44
46
|
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,21 @@ 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;
|
|
16
20
|
changes: CognitionRepairChange[];
|
|
17
21
|
}
|
|
18
22
|
export declare function analyzeCognitionQuality(workspace: KGraphWorkspace, maps: {
|
|
19
23
|
fileMap: FileMap;
|
|
20
24
|
symbolMap: SymbolMap;
|
|
25
|
+
dependencyMap?: DependencyMap;
|
|
26
|
+
relationshipMap?: RelationshipMap;
|
|
21
27
|
}): Promise<CognitionQualityReport>;
|
|
22
28
|
export declare function repairCognition(workspace: KGraphWorkspace, maps: {
|
|
23
29
|
fileMap: FileMap;
|
|
24
30
|
symbolMap: SymbolMap;
|
|
31
|
+
dependencyMap?: DependencyMap;
|
|
32
|
+
relationshipMap?: RelationshipMap;
|
|
25
33
|
}, dryRun?: boolean): Promise<CognitionQualityReport>;
|
|
@@ -10,6 +10,10 @@ export async function analyzeCognitionQuality(workspace, maps) {
|
|
|
10
10
|
mixedOrStaleCount: notes.filter((note) => ['mixed', 'stale', 'unresolved'].includes(note.referencesStatus)).length,
|
|
11
11
|
noisyFileRefCount: changes.reduce((total, change) => total + change.removedFileRefs.length, 0),
|
|
12
12
|
noisySymbolRefCount: changes.reduce((total, change) => total + change.removedSymbolRefs.length, 0),
|
|
13
|
+
unresolvedLocalImportCount: countUnresolvedLocalImports(maps.dependencyMap),
|
|
14
|
+
unresolvedCallCount: countUnresolvedCalls(maps.symbolMap, maps.relationshipMap),
|
|
15
|
+
duplicateTitleCount: countDuplicateTitles(notes),
|
|
16
|
+
generatedFileScanCount: countGeneratedScannedFiles(maps.fileMap),
|
|
13
17
|
changes,
|
|
14
18
|
};
|
|
15
19
|
}
|
|
@@ -37,9 +41,51 @@ export async function repairCognition(workspace, maps, dryRun = false) {
|
|
|
37
41
|
mixedOrStaleCount: nextNotes.filter((note) => ['mixed', 'stale', 'unresolved'].includes(note.referencesStatus)).length,
|
|
38
42
|
noisyFileRefCount: changes.reduce((total, change) => total + change.removedFileRefs.length, 0),
|
|
39
43
|
noisySymbolRefCount: changes.reduce((total, change) => total + change.removedSymbolRefs.length, 0),
|
|
44
|
+
unresolvedLocalImportCount: countUnresolvedLocalImports(maps.dependencyMap),
|
|
45
|
+
unresolvedCallCount: countUnresolvedCalls(maps.symbolMap, maps.relationshipMap),
|
|
46
|
+
duplicateTitleCount: countDuplicateTitles(nextNotes),
|
|
47
|
+
generatedFileScanCount: countGeneratedScannedFiles(maps.fileMap),
|
|
40
48
|
changes,
|
|
41
49
|
};
|
|
42
50
|
}
|
|
51
|
+
function countUnresolvedLocalImports(dependencyMap) {
|
|
52
|
+
return (dependencyMap?.dependencies.filter((dependency) => dependency.kind === 'local' && !dependency.resolvedFile).length ?? 0);
|
|
53
|
+
}
|
|
54
|
+
function countUnresolvedCalls(symbolMap, relationshipMap) {
|
|
55
|
+
const symbolIds = new Set(symbolMap.symbols.map((symbol) => symbol.id));
|
|
56
|
+
const symbolNames = new Set(symbolMap.symbols.map((symbol) => symbol.name));
|
|
57
|
+
return (relationshipMap?.relationships.filter((relationship) => relationship.relationshipType === 'calls' &&
|
|
58
|
+
relationship.targetType === 'symbol' &&
|
|
59
|
+
!symbolIds.has(relationship.targetId) &&
|
|
60
|
+
!symbolNames.has(relationship.targetId) &&
|
|
61
|
+
![...symbolNames].some((name) => relationship.targetId.endsWith(`#${name}`))).length ?? 0);
|
|
62
|
+
}
|
|
63
|
+
function countDuplicateTitles(notes) {
|
|
64
|
+
const seen = new Set();
|
|
65
|
+
const duplicates = new Set();
|
|
66
|
+
for (const note of notes) {
|
|
67
|
+
const key = note.title.trim().toLowerCase();
|
|
68
|
+
if (!key)
|
|
69
|
+
continue;
|
|
70
|
+
if (seen.has(key))
|
|
71
|
+
duplicates.add(key);
|
|
72
|
+
seen.add(key);
|
|
73
|
+
}
|
|
74
|
+
return duplicates.size;
|
|
75
|
+
}
|
|
76
|
+
function countGeneratedScannedFiles(fileMap) {
|
|
77
|
+
return fileMap.files.filter((file) => [
|
|
78
|
+
'.agents/',
|
|
79
|
+
'.claude/',
|
|
80
|
+
'.cursor/',
|
|
81
|
+
'.windsurf/',
|
|
82
|
+
'.clinerules/',
|
|
83
|
+
'.github/prompts/',
|
|
84
|
+
'AGENTS.md',
|
|
85
|
+
'CLAUDE.md',
|
|
86
|
+
'GEMINI.md',
|
|
87
|
+
].some((prefix) => file.path === prefix || file.path.startsWith(prefix))).length;
|
|
88
|
+
}
|
|
43
89
|
function analyzeNote(note, maps) {
|
|
44
90
|
const filePaths = new Set(maps.fileMap.files.map((file) => file.path));
|
|
45
91
|
const symbolNames = new Set(maps.symbolMap.symbols.map((symbol) => symbol.name));
|
|
@@ -12,6 +12,7 @@ export async function queryContext(workspace, config, maps, query) {
|
|
|
12
12
|
{ name: 'name', value: (symbol) => symbol.name },
|
|
13
13
|
{ name: 'path', value: (symbol) => symbol.filePath },
|
|
14
14
|
{ name: 'kind', value: (symbol) => symbol.kind },
|
|
15
|
+
{ name: 'parent', value: (symbol) => symbol.parentName },
|
|
15
16
|
]).slice(0, max);
|
|
16
17
|
const relevantCognition = rankByFields(query, cognition, [
|
|
17
18
|
{ name: 'title', value: (note) => note.title },
|
|
@@ -39,6 +40,13 @@ export async function queryContext(workspace, config, maps, query) {
|
|
|
39
40
|
...domain.item.symbols,
|
|
40
41
|
]),
|
|
41
42
|
]);
|
|
43
|
+
for (const relationship of maps.relationshipMap.relationships) {
|
|
44
|
+
if (relatedIds.has(relationship.sourceId) ||
|
|
45
|
+
relatedIds.has(relationship.targetId)) {
|
|
46
|
+
relatedIds.add(relationship.sourceId);
|
|
47
|
+
relatedIds.add(relationship.targetId);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
42
50
|
const rankedRelationships = rankByFields(query, maps.relationshipMap.relationships, [
|
|
43
51
|
{ name: 'source', value: (relationship) => relationship.sourceId },
|
|
44
52
|
{ name: 'target', value: (relationship) => relationship.targetId },
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CognitionNote } from '../types/cognition.js';
|
|
2
|
+
import type { DependencyMap, FileMap, Relationship, RelationshipMap, SymbolMap } from '../types/maps.js';
|
|
3
|
+
import { type Ranked } from './ranking.js';
|
|
4
|
+
export interface ImpactResponse {
|
|
5
|
+
query: string;
|
|
6
|
+
files: Ranked<FileMap['files'][number]>[];
|
|
7
|
+
symbols: Ranked<SymbolMap['symbols'][number]>[];
|
|
8
|
+
importedBy: string[];
|
|
9
|
+
callers: Relationship[];
|
|
10
|
+
calls: Relationship[];
|
|
11
|
+
ownership: Relationship[];
|
|
12
|
+
relatedCognition: CognitionNote[];
|
|
13
|
+
risk: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare function analyzeImpact(query: string, maps: {
|
|
16
|
+
fileMap: FileMap;
|
|
17
|
+
symbolMap: SymbolMap;
|
|
18
|
+
dependencyMap: DependencyMap;
|
|
19
|
+
relationshipMap: RelationshipMap;
|
|
20
|
+
}, cognition: CognitionNote[], max?: number): ImpactResponse;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { rankByFields } from './ranking.js';
|
|
2
|
+
export function analyzeImpact(query, maps, cognition, max = 8) {
|
|
3
|
+
const files = rankByFields(query, maps.fileMap.files, [
|
|
4
|
+
{ name: 'path', value: (file) => file.path },
|
|
5
|
+
{ name: 'language', value: (file) => file.language },
|
|
6
|
+
]).slice(0, max);
|
|
7
|
+
const symbols = rankByFields(query, maps.symbolMap.symbols, [
|
|
8
|
+
{ name: 'name', value: (symbol) => symbol.name },
|
|
9
|
+
{ name: 'path', value: (symbol) => symbol.filePath },
|
|
10
|
+
{ name: 'kind', value: (symbol) => symbol.kind },
|
|
11
|
+
{ name: 'parent', value: (symbol) => symbol.parentName },
|
|
12
|
+
]).slice(0, max);
|
|
13
|
+
const filePaths = new Set([
|
|
14
|
+
...files.map((file) => file.item.path),
|
|
15
|
+
...symbols.map((symbol) => symbol.item.filePath),
|
|
16
|
+
]);
|
|
17
|
+
const symbolIds = new Set(symbols.map((symbol) => symbol.item.id));
|
|
18
|
+
const symbolNames = new Set(symbols.map((symbol) => symbol.item.name));
|
|
19
|
+
const importHints = new Set([
|
|
20
|
+
...[...filePaths].map((file) => basenameWithoutExtension(file).toLowerCase()),
|
|
21
|
+
...[...symbolNames].map((name) => name.toLowerCase()),
|
|
22
|
+
]);
|
|
23
|
+
const importedBy = unique(maps.dependencyMap.dependencies
|
|
24
|
+
.filter((dep) => (dep.resolvedFile && filePaths.has(dep.resolvedFile)) ||
|
|
25
|
+
[...importHints].some((hint) => hint && dep.specifier.toLowerCase().includes(hint)))
|
|
26
|
+
.map((dep) => dep.fromFile)).slice(0, max);
|
|
27
|
+
const calls = maps.relationshipMap.relationships
|
|
28
|
+
.filter((rel) => rel.relationshipType === 'calls' &&
|
|
29
|
+
(symbolIds.has(rel.sourceId) || symbolNames.has(rel.sourceId)))
|
|
30
|
+
.slice(0, max);
|
|
31
|
+
const callers = maps.relationshipMap.relationships
|
|
32
|
+
.filter((rel) => rel.relationshipType === 'calls' &&
|
|
33
|
+
(symbolIds.has(rel.targetId) || symbolNames.has(rel.targetId) || [...symbolNames].some((name) => rel.targetId.endsWith(`#${name}`))))
|
|
34
|
+
.slice(0, max);
|
|
35
|
+
const ownership = maps.relationshipMap.relationships
|
|
36
|
+
.filter((rel) => rel.relationshipType === 'symbol-contains' &&
|
|
37
|
+
(symbolIds.has(rel.sourceId) || symbolIds.has(rel.targetId)))
|
|
38
|
+
.slice(0, max);
|
|
39
|
+
const relatedCognition = cognition
|
|
40
|
+
.filter((note) => note.relatedFiles.some((file) => filePaths.has(file)) ||
|
|
41
|
+
note.relatedSymbols.some((symbol) => symbolNames.has(symbol) || symbolIds.has(symbol)))
|
|
42
|
+
.slice(0, max);
|
|
43
|
+
const risk = [];
|
|
44
|
+
if (importedBy.length > 2)
|
|
45
|
+
risk.push(`Shared file imported by ${importedBy.length} files`);
|
|
46
|
+
if (callers.length > 2)
|
|
47
|
+
risk.push(`Symbol called by ${callers.length} known callers`);
|
|
48
|
+
if (relatedCognition.some((note) => note.referencesStatus !== 'current')) {
|
|
49
|
+
risk.push('Related cognition has stale or mixed references');
|
|
50
|
+
}
|
|
51
|
+
if (calls.some((rel) => rel.confidence === 'low') || callers.some((rel) => rel.confidence === 'low')) {
|
|
52
|
+
risk.push('Some call relationships are low confidence');
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
query,
|
|
56
|
+
files,
|
|
57
|
+
symbols,
|
|
58
|
+
importedBy,
|
|
59
|
+
callers,
|
|
60
|
+
calls,
|
|
61
|
+
ownership,
|
|
62
|
+
relatedCognition,
|
|
63
|
+
risk,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function unique(items) {
|
|
67
|
+
return [...new Set(items)];
|
|
68
|
+
}
|
|
69
|
+
function basenameWithoutExtension(filePath) {
|
|
70
|
+
const basename = filePath.split('/').pop() ?? filePath;
|
|
71
|
+
return basename.replace(/\.[^.]+$/, '');
|
|
72
|
+
}
|
package/dist/context/ranking.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export function tokenize(query) {
|
|
2
|
-
return query
|
|
2
|
+
return expandTokens(query
|
|
3
3
|
.toLowerCase()
|
|
4
4
|
.split(/[^a-z0-9_$./-]+/)
|
|
5
5
|
.map((token) => token.trim())
|
|
6
|
-
.filter(Boolean);
|
|
6
|
+
.filter(Boolean));
|
|
7
7
|
}
|
|
8
8
|
export function rankByFields(query, items, fields) {
|
|
9
9
|
const tokens = tokenize(query);
|
|
@@ -14,14 +14,19 @@ export function rankByFields(query, items, fields) {
|
|
|
14
14
|
for (const field of fields) {
|
|
15
15
|
const value = field.value(item);
|
|
16
16
|
const values = Array.isArray(value) ? value : value ? [value] : [];
|
|
17
|
-
const haystack = values.join(' ').toLowerCase();
|
|
17
|
+
const haystack = values.flatMap((value) => [value, splitIdentifier(value).join(' ')]).join(' ').toLowerCase();
|
|
18
18
|
for (const token of tokens) {
|
|
19
19
|
if (haystack.includes(token)) {
|
|
20
|
-
const baseScore = field.name === 'path' || field.name === 'name'
|
|
20
|
+
const baseScore = field.name === 'path' || field.name === 'name'
|
|
21
|
+
? 4
|
|
22
|
+
: field.name === 'summary' || field.name === 'title'
|
|
23
|
+
? 2
|
|
24
|
+
: 1;
|
|
21
25
|
const escaped = token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
22
26
|
const wordBoundary = new RegExp(`\\b${escaped}\\b`).test(haystack);
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
const exactValue = values.some((value) => value.toLowerCase() === token);
|
|
28
|
+
score += baseScore + (wordBoundary ? 2 : 0) + (exactValue ? 4 : 0);
|
|
29
|
+
reasons.push(`${field.name} matched "${token}"${wordBoundary || exactValue ? ' (exact)' : ''}`);
|
|
25
30
|
}
|
|
26
31
|
}
|
|
27
32
|
}
|
|
@@ -30,3 +35,13 @@ export function rankByFields(query, items, fields) {
|
|
|
30
35
|
.filter((ranked) => ranked.score > 0)
|
|
31
36
|
.sort((a, b) => b.score - a.score);
|
|
32
37
|
}
|
|
38
|
+
function expandTokens(tokens) {
|
|
39
|
+
return [...new Set(tokens.flatMap((token) => [token, ...splitIdentifier(token)]))];
|
|
40
|
+
}
|
|
41
|
+
function splitIdentifier(value) {
|
|
42
|
+
return value
|
|
43
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
44
|
+
.split(/[^A-Za-z0-9_$]+/)
|
|
45
|
+
.map((part) => part.toLowerCase())
|
|
46
|
+
.filter((part) => part.length > 1);
|
|
47
|
+
}
|
|
@@ -15,13 +15,14 @@ Before exploring the repository, run \`kgraph "<topic>"\` to refresh repo intell
|
|
|
15
15
|
2. Run \`kgraph "<topic>"\`. This refreshes maps, processes pending inbox notes, and returns focused context in one command.
|
|
16
16
|
3. Use the returned files, symbols, relationships, and cognition before broad exploration.
|
|
17
17
|
4. Run \`kgraph doctor\` when setup, maps, inbox processing, or integrations look wrong. Run \`kgraph doctor --quality\` when context shows stale/noisy cognition references.
|
|
18
|
-
5.
|
|
19
|
-
6.
|
|
20
|
-
7.
|
|
21
|
-
8.
|
|
22
|
-
9.
|
|
23
|
-
10. Run \`kgraph
|
|
24
|
-
11. Run \`kgraph
|
|
18
|
+
5. 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.
|
|
19
|
+
6. At the end of any session that changed repository files, check the KGraph capture workflow before finishing.
|
|
20
|
+
7. If the file change has future value, write one concise Markdown note to \`.kgraph/inbox/<slug>.md\`; otherwise explicitly skip capture as trivial.
|
|
21
|
+
8. Skip capture only for read-only work, trivial formatting, typo-only docs, dependency-only churn, mechanical cleanup with no future value, or sessions where no repo files changed.
|
|
22
|
+
9. Do not run KGraph repeatedly. Run it once at the start with \`kgraph "<topic>"\`. If repo files changed, write any needed inbox note first, then run \`kgraph\` once at the end.
|
|
23
|
+
10. 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.
|
|
24
|
+
11. Run \`kgraph visualize\` when the user wants to inspect the dependency graph — opens an interactive graph at http://localhost:4242 with PNG export.
|
|
25
|
+
12. Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.
|
|
25
26
|
|
|
26
27
|
The inbox note must use this structure:
|
|
27
28
|
\`\`\`markdown
|
|
@@ -64,11 +65,16 @@ Any implementation or product decision future sessions should know.
|
|
|
64
65
|
{
|
|
65
66
|
path: '.claude/commands/kgraph-visualize.md',
|
|
66
67
|
content: `Run \`kgraph visualize\` to start an interactive dependency graph at http://localhost:4242. Opens in browser automatically. Use \`--no-open\` to print URL only, \`--port <n>\` for a custom port.
|
|
68
|
+
`,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
path: '.claude/commands/kgraph-impact.md',
|
|
72
|
+
content: `Run \`kgraph impact "$ARGUMENTS"\` to show matched files/symbols, import users, callers, callees, related cognition, and risk hints.
|
|
67
73
|
`,
|
|
68
74
|
},
|
|
69
75
|
{
|
|
70
76
|
path: '.claude/commands/kgraph-history.md',
|
|
71
|
-
content: `Run \`kgraph history\`
|
|
77
|
+
content: `Run \`kgraph history\` or \`kgraph history "$ARGUMENTS"\` to show processed cognition sessions. Includes git author attribution when available. Use \`--last <n>\` to limit entries, \`--json\` for machine-readable output.
|
|
72
78
|
`,
|
|
73
79
|
},
|
|
74
80
|
],
|
|
@@ -6,12 +6,13 @@ export const clineAdapter = {
|
|
|
6
6
|
|
|
7
7
|
- **Before exploring the repository**, run \`kgraph "<topic>"\` to refresh maps, process pending inbox notes, and load focused repo intelligence. Use the returned files, symbols, relationships, and cognition before any broad exploration.
|
|
8
8
|
- Run \`kgraph doctor\` when setup, maps, inbox processing, or integrations look wrong. Run \`kgraph doctor --quality\` when context shows stale/noisy cognition references.
|
|
9
|
+
- 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.
|
|
9
10
|
- At the end of any session that changed repository files, check the KGraph capture workflow before finishing.
|
|
10
11
|
- If the file change has future value, write one concise Markdown note to \`.kgraph/inbox/<slug>.md\`; otherwise explicitly skip capture as trivial.
|
|
11
12
|
- Skip capture only for read-only work, trivial formatting, typo-only docs, dependency-only churn, mechanical cleanup with no future value, or sessions where no repo files changed.
|
|
12
13
|
- Do not run KGraph repeatedly. Run it once at the start with \`kgraph "<topic>"\`. If repo files changed, write any needed inbox note first, then run \`kgraph\` once at the end.
|
|
13
14
|
- 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.
|
|
14
15
|
- Run \`kgraph visualize\` to open the interactive dependency graph at http://localhost:4242 with PNG export.
|
|
15
|
-
- Run \`kgraph history\`
|
|
16
|
+
- Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.
|
|
16
17
|
`,
|
|
17
18
|
};
|
|
@@ -22,13 +22,14 @@ Workflow:
|
|
|
22
22
|
2. Run \`kgraph "<topic>"\` before broad repo exploration. This refreshes maps, processes pending inbox notes, and returns focused context in one command.
|
|
23
23
|
3. Use KGraph's returned files, symbols, relationships, and cognition as navigation hints.
|
|
24
24
|
4. Run \`kgraph doctor\` when setup, maps, inbox processing, or integrations look wrong. Run \`kgraph doctor --quality\` when context shows stale/noisy cognition references.
|
|
25
|
-
5.
|
|
26
|
-
6.
|
|
27
|
-
7.
|
|
28
|
-
8.
|
|
29
|
-
9.
|
|
30
|
-
10. Run \`kgraph
|
|
31
|
-
11. Run \`kgraph
|
|
25
|
+
5. 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.
|
|
26
|
+
6. At the end of any session that changed repository files, check the KGraph capture workflow before finishing.
|
|
27
|
+
7. If the file change has future value, write one concise Markdown note to \`.kgraph/inbox/<slug>.md\`; otherwise explicitly skip capture as trivial.
|
|
28
|
+
8. Skip capture only for read-only work, trivial formatting, typo-only docs, dependency-only churn, mechanical cleanup with no future value, or sessions where no repo files changed.
|
|
29
|
+
9. Do not run KGraph repeatedly. Run it once at the start with \`kgraph "<topic>"\`. If repo files changed, write any needed inbox note first, then run \`kgraph\` once at the end.
|
|
30
|
+
10. 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.
|
|
31
|
+
11. Run \`kgraph visualize\` when the user wants to inspect the dependency graph — opens an interactive graph at http://localhost:4242 with PNG export.
|
|
32
|
+
12. Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.
|
|
32
33
|
|
|
33
34
|
The inbox note must use this structure:
|
|
34
35
|
\`\`\`markdown
|
|
@@ -6,11 +6,12 @@ export const copilotAdapter = {
|
|
|
6
6
|
|
|
7
7
|
1. **Before exploring the repository**, run \`kgraph "<topic>"\` to refresh maps, process pending inbox notes, and load focused repo intelligence. Use the returned files, symbols, relationships, and cognition before any broad exploration.
|
|
8
8
|
2. Run \`kgraph doctor\` when setup, maps, inbox processing, or integrations look wrong. Run \`kgraph doctor --quality\` when context shows stale/noisy cognition references.
|
|
9
|
-
3.
|
|
10
|
-
4.
|
|
11
|
-
5.
|
|
12
|
-
6.
|
|
13
|
-
7.
|
|
9
|
+
3. 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.
|
|
10
|
+
4. At the end of any session that changed repository files, check the KGraph capture workflow before finishing.
|
|
11
|
+
5. If the file change has future value, write one concise Markdown note to \`.kgraph/inbox/<slug>.md\`; otherwise explicitly skip capture as trivial.
|
|
12
|
+
6. Skip capture only for read-only work, trivial formatting, typo-only docs, dependency-only churn, mechanical cleanup with no future value, or sessions where no repo files changed.
|
|
13
|
+
7. Do not run KGraph repeatedly. Run it once at the start with \`kgraph "<topic>"\`. If repo files changed, write any needed inbox note first, then run \`kgraph\` once at the end.
|
|
14
|
+
8. 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.
|
|
14
15
|
|
|
15
16
|
The inbox note must use this structure:
|
|
16
17
|
\`\`\`markdown
|
|
@@ -112,6 +113,17 @@ One or two sentences describing what was done.
|
|
|
112
113
|
## Decisions
|
|
113
114
|
Any architectural or implementation decisions made.
|
|
114
115
|
\`\`\`
|
|
116
|
+
`,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
path: '.github/prompts/kgraph-impact.prompt.md',
|
|
120
|
+
content: `---
|
|
121
|
+
description: Show KGraph change impact for a file, symbol, or topic
|
|
122
|
+
agent: agent
|
|
123
|
+
argument-hint: "File, symbol, or topic"
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
Run \`kgraph impact "$ARGUMENTS"\` to show matched files/symbols, import users, callers, callees, related cognition, and risk hints.
|
|
115
127
|
`,
|
|
116
128
|
},
|
|
117
129
|
{
|
|
@@ -119,9 +131,10 @@ Any architectural or implementation decisions made.
|
|
|
119
131
|
content: `---
|
|
120
132
|
description: Show timeline of KGraph cognition sessions with git attribution
|
|
121
133
|
agent: agent
|
|
134
|
+
argument-hint: "Optional topic"
|
|
122
135
|
---
|
|
123
136
|
|
|
124
|
-
Run \`kgraph history\`
|
|
137
|
+
Run \`kgraph history\` or \`kgraph history "$ARGUMENTS"\` to display processed cognition sessions. Summarize who contributed what and when. Use \`--last <n>\` to limit entries.
|
|
125
138
|
`,
|
|
126
139
|
},
|
|
127
140
|
],
|
|
@@ -11,13 +11,14 @@ alwaysApply: true
|
|
|
11
11
|
|
|
12
12
|
- **Before exploring the repository**, run \`kgraph "<topic>"\` to refresh maps, process pending inbox notes, and load focused repo intelligence. Use the returned files, symbols, relationships, and cognition before any broad exploration.
|
|
13
13
|
- Run \`kgraph doctor\` when setup, maps, inbox processing, or integrations look wrong. Run \`kgraph doctor --quality\` when context shows stale/noisy cognition references.
|
|
14
|
+
- 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.
|
|
14
15
|
- At the end of any session that changed repository files, check the KGraph capture workflow before finishing.
|
|
15
16
|
- If the file change has future value, write one concise Markdown note to \`.kgraph/inbox/<slug>.md\`; otherwise explicitly skip capture as trivial.
|
|
16
17
|
- Skip capture only for read-only work, trivial formatting, typo-only docs, dependency-only churn, mechanical cleanup with no future value, or sessions where no repo files changed.
|
|
17
18
|
- Do not run KGraph repeatedly. Run it once at the start with \`kgraph "<topic>"\`. If repo files changed, write any needed inbox note first, then run \`kgraph\` once at the end.
|
|
18
19
|
- 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.
|
|
19
20
|
- Run \`kgraph visualize\` to open the interactive dependency graph at http://localhost:4242 with PNG export.
|
|
20
|
-
- Run \`kgraph history\`
|
|
21
|
+
- Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.
|
|
21
22
|
`,
|
|
22
23
|
obsoleteCommandFiles: ['.cursor/rules/kgraph-commands.mdc'],
|
|
23
24
|
};
|
|
@@ -6,12 +6,13 @@ export const geminiAdapter = {
|
|
|
6
6
|
|
|
7
7
|
- **Before exploring the repository**, run \`kgraph "<topic>"\` to refresh maps, process pending inbox notes, and load focused repo intelligence. Use the returned files, symbols, relationships, and cognition before any broad exploration.
|
|
8
8
|
- Run \`kgraph doctor\` when setup, maps, inbox processing, or integrations look wrong. Run \`kgraph doctor --quality\` when context shows stale/noisy cognition references.
|
|
9
|
+
- 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.
|
|
9
10
|
- At the end of any session that changed repository files, check the KGraph capture workflow before finishing.
|
|
10
11
|
- If the file change has future value, write one concise Markdown note to \`.kgraph/inbox/<slug>.md\`; otherwise explicitly skip capture as trivial.
|
|
11
12
|
- Skip capture only for read-only work, trivial formatting, typo-only docs, dependency-only churn, mechanical cleanup with no future value, or sessions where no repo files changed.
|
|
12
13
|
- Do not run KGraph repeatedly. Run it once at the start with \`kgraph "<topic>"\`. If repo files changed, write any needed inbox note first, then run \`kgraph\` once at the end.
|
|
13
14
|
- 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.
|
|
14
15
|
- Run \`kgraph visualize\` to open the interactive dependency graph at http://localhost:4242 with PNG export.
|
|
15
|
-
- Run \`kgraph history\`
|
|
16
|
+
- Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.
|
|
16
17
|
`,
|
|
17
18
|
};
|
|
@@ -6,12 +6,13 @@ export const windsurfAdapter = {
|
|
|
6
6
|
|
|
7
7
|
- **Before exploring the repository**, run \`kgraph "<topic>"\` to refresh maps, process pending inbox notes, and load focused repo intelligence. Use the returned files, symbols, relationships, and cognition before any broad exploration.
|
|
8
8
|
- Run \`kgraph doctor\` when setup, maps, inbox processing, or integrations look wrong. Run \`kgraph doctor --quality\` when context shows stale/noisy cognition references.
|
|
9
|
+
- 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.
|
|
9
10
|
- At the end of any session that changed repository files, check the KGraph capture workflow before finishing.
|
|
10
11
|
- If the file change has future value, write one concise Markdown note to \`.kgraph/inbox/<slug>.md\`; otherwise explicitly skip capture as trivial.
|
|
11
12
|
- Skip capture only for read-only work, trivial formatting, typo-only docs, dependency-only churn, mechanical cleanup with no future value, or sessions where no repo files changed.
|
|
12
13
|
- Do not run KGraph repeatedly. Run it once at the start with \`kgraph "<topic>"\`. If repo files changed, write any needed inbox note first, then run \`kgraph\` once at the end.
|
|
13
14
|
- 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.
|
|
14
15
|
- Run \`kgraph visualize\` to open the interactive dependency graph at http://localhost:4242 with PNG export.
|
|
15
|
-
- Run \`kgraph history\`
|
|
16
|
+
- Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.
|
|
16
17
|
`,
|
|
17
18
|
};
|
|
@@ -6,11 +6,14 @@ export function extractTsSymbols(sourceText, filePath) {
|
|
|
6
6
|
const dependencies = [];
|
|
7
7
|
const relationships = [];
|
|
8
8
|
const warnings = [];
|
|
9
|
+
const symbolIdsByNode = new Map();
|
|
10
|
+
const symbolsByName = new Map();
|
|
11
|
+
const importedBindings = new Map();
|
|
9
12
|
const addSymbol = (name, kind, node, exported = false, parentName) => {
|
|
10
13
|
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
11
14
|
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
12
15
|
const id = [filePath, kind, parentName, name, start.line + 1, end.line + 1].filter(Boolean).join("#");
|
|
13
|
-
|
|
16
|
+
const symbol = {
|
|
14
17
|
id,
|
|
15
18
|
name,
|
|
16
19
|
kind,
|
|
@@ -19,7 +22,12 @@ export function extractTsSymbols(sourceText, filePath) {
|
|
|
19
22
|
endLine: end.line + 1,
|
|
20
23
|
exported,
|
|
21
24
|
parentName
|
|
22
|
-
}
|
|
25
|
+
};
|
|
26
|
+
symbols.push(symbol);
|
|
27
|
+
symbolIdsByNode.set(node, id);
|
|
28
|
+
const byName = symbolsByName.get(name) ?? [];
|
|
29
|
+
byName.push(symbol);
|
|
30
|
+
symbolsByName.set(name, byName);
|
|
23
31
|
relationships.push({
|
|
24
32
|
sourceType: "file",
|
|
25
33
|
sourceId: filePath,
|
|
@@ -28,8 +36,9 @@ export function extractTsSymbols(sourceText, filePath) {
|
|
|
28
36
|
relationshipType: "contains",
|
|
29
37
|
confidence: "high"
|
|
30
38
|
});
|
|
39
|
+
return symbol;
|
|
31
40
|
};
|
|
32
|
-
const
|
|
41
|
+
const collectSymbols = (node, parentName) => {
|
|
33
42
|
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
34
43
|
const specifier = node.moduleSpecifier.text;
|
|
35
44
|
const dependency = {
|
|
@@ -48,6 +57,7 @@ export function extractTsSymbols(sourceText, filePath) {
|
|
|
48
57
|
relationshipType: "import",
|
|
49
58
|
confidence: dependency.resolvedFile ? "high" : "medium"
|
|
50
59
|
});
|
|
60
|
+
collectImportedBindings(node, specifier, dependency.resolvedFile, importedBindings);
|
|
51
61
|
}
|
|
52
62
|
if (ts.isExportDeclaration(node)) {
|
|
53
63
|
const name = node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier) ? node.moduleSpecifier.text : "export";
|
|
@@ -66,10 +76,18 @@ export function extractTsSymbols(sourceText, filePath) {
|
|
|
66
76
|
}
|
|
67
77
|
}
|
|
68
78
|
if (ts.isClassDeclaration(node) && node.name) {
|
|
69
|
-
addSymbol(node.name.text, "class", node, isExported(node), parentName);
|
|
79
|
+
const classSymbol = addSymbol(node.name.text, "class", node, isExported(node), parentName);
|
|
70
80
|
node.members.forEach((member) => {
|
|
71
81
|
if (ts.isMethodDeclaration(member) && member.name && ts.isIdentifier(member.name)) {
|
|
72
|
-
addSymbol(member.name.text, "method", member, false, node.name?.text);
|
|
82
|
+
const methodSymbol = addSymbol(member.name.text, "method", member, false, node.name?.text);
|
|
83
|
+
relationships.push({
|
|
84
|
+
sourceType: "symbol",
|
|
85
|
+
sourceId: classSymbol.id,
|
|
86
|
+
targetType: "symbol",
|
|
87
|
+
targetId: methodSymbol.id,
|
|
88
|
+
relationshipType: "symbol-contains",
|
|
89
|
+
confidence: "high"
|
|
90
|
+
});
|
|
73
91
|
}
|
|
74
92
|
});
|
|
75
93
|
}
|
|
@@ -79,16 +97,85 @@ export function extractTsSymbols(sourceText, filePath) {
|
|
|
79
97
|
if (ts.isTypeAliasDeclaration(node)) {
|
|
80
98
|
addSymbol(node.name.text, "type", node, isExported(node), parentName);
|
|
81
99
|
}
|
|
82
|
-
ts.forEachChild(node, (child) =>
|
|
100
|
+
ts.forEachChild(node, (child) => collectSymbols(child, parentName));
|
|
101
|
+
};
|
|
102
|
+
const collectCalls = (node, currentSymbolId) => {
|
|
103
|
+
const nextSymbolId = symbolIdsByNode.get(node) ?? currentSymbolId;
|
|
104
|
+
if (ts.isCallExpression(node) && nextSymbolId) {
|
|
105
|
+
const target = resolveCallTarget(node, symbolsByName, importedBindings);
|
|
106
|
+
if (target) {
|
|
107
|
+
relationships.push({
|
|
108
|
+
sourceType: "symbol",
|
|
109
|
+
sourceId: nextSymbolId,
|
|
110
|
+
targetType: target.targetType,
|
|
111
|
+
targetId: target.targetId,
|
|
112
|
+
relationshipType: "calls",
|
|
113
|
+
confidence: target.confidence
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
ts.forEachChild(node, (child) => collectCalls(child, nextSymbolId));
|
|
83
118
|
};
|
|
84
119
|
try {
|
|
85
|
-
|
|
120
|
+
collectSymbols(sourceFile);
|
|
121
|
+
collectCalls(sourceFile);
|
|
86
122
|
}
|
|
87
123
|
catch (error) {
|
|
88
124
|
warnings.push(error instanceof Error ? error.message : String(error));
|
|
89
125
|
}
|
|
90
126
|
return { symbols, dependencies, relationships, warnings };
|
|
91
127
|
}
|
|
128
|
+
function collectImportedBindings(node, specifier, resolvedFile, importedBindings) {
|
|
129
|
+
const clause = node.importClause;
|
|
130
|
+
if (!clause) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (clause.name) {
|
|
134
|
+
importedBindings.set(clause.name.text, { specifier, resolvedFile });
|
|
135
|
+
}
|
|
136
|
+
const bindings = clause.namedBindings;
|
|
137
|
+
if (bindings && ts.isNamedImports(bindings)) {
|
|
138
|
+
for (const element of bindings.elements) {
|
|
139
|
+
importedBindings.set(element.name.text, { specifier, resolvedFile });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function resolveCallTarget(node, symbolsByName, importedBindings) {
|
|
144
|
+
const expression = node.expression;
|
|
145
|
+
if (ts.isIdentifier(expression)) {
|
|
146
|
+
const localSymbols = symbolsByName
|
|
147
|
+
.get(expression.text)
|
|
148
|
+
?.filter((symbol) => symbol.kind === "function" || symbol.kind === "method");
|
|
149
|
+
if (localSymbols?.[0]) {
|
|
150
|
+
return {
|
|
151
|
+
targetType: "symbol",
|
|
152
|
+
targetId: localSymbols[0].id,
|
|
153
|
+
confidence: "high"
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const imported = importedBindings.get(expression.text);
|
|
157
|
+
if (imported) {
|
|
158
|
+
return {
|
|
159
|
+
targetType: "symbol",
|
|
160
|
+
targetId: imported.resolvedFile ? `${imported.resolvedFile}#${expression.text}` : expression.text,
|
|
161
|
+
confidence: imported.resolvedFile ? "medium" : "low"
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
targetType: "symbol",
|
|
166
|
+
targetId: expression.text,
|
|
167
|
+
confidence: "low"
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (ts.isPropertyAccessExpression(expression)) {
|
|
171
|
+
return {
|
|
172
|
+
targetType: "symbol",
|
|
173
|
+
targetId: expression.getText(),
|
|
174
|
+
confidence: "low"
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
92
179
|
function isExported(node) {
|
|
93
180
|
return ts.canHaveModifiers(node) && Boolean(ts.getModifiers(node)?.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword));
|
|
94
181
|
}
|
package/dist/types/maps.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type ScanStatus = "mapped" | "generic" | "failed";
|
|
2
2
|
export type DependencyKind = "local" | "package" | "unknown";
|
|
3
3
|
export type SymbolKind = "function" | "class" | "method" | "type" | "interface" | "export" | "import";
|
|
4
|
-
export type RelationshipType = "import" | "contains" | "mentions" | "belongs-to-domain" | "stale-reference" | "moved-from";
|
|
4
|
+
export type RelationshipType = "import" | "contains" | "symbol-contains" | "calls" | "mentions" | "belongs-to-domain" | "stale-reference" | "moved-from";
|
|
5
5
|
export interface RepositoryFile {
|
|
6
6
|
id: string;
|
|
7
7
|
path: string;
|