@kentwynn/kgraph 0.1.21 → 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 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:
@@ -143,14 +151,21 @@ kgraph doctor
143
151
  kgraph doctor --quality
144
152
  ```
145
153
 
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.
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.
147
155
 
148
156
  ```bash
149
157
  kgraph repair --dry-run
150
158
  kgraph repair
151
159
  ```
152
160
 
153
- `repair --dry-run` previews cleanup for noisy cognition references, such as framework names recorded as files or local variables recorded as symbols. `repair` applies that cleanup. Run repair intentionally when stale references make context noisy; it is not part of every normal workflow.
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.
154
169
 
155
170
  ## Optional Step Commands
156
171
 
@@ -187,10 +202,11 @@ Open the local interactive dependency graph at `http://localhost:4242`.
187
202
  ```bash
188
203
  kgraph history
189
204
  kgraph history --last 10
205
+ kgraph history "blog button"
190
206
  kgraph history --json
191
207
  ```
192
208
 
193
- Show processed cognition sessions.
209
+ Show processed cognition sessions. Add a query to find historical work by title, summary, file, symbol, or note body.
194
210
 
195
211
  ## AI Tool Integrations
196
212
 
@@ -319,6 +335,7 @@ The release workflow builds, tests, packs, publishes the npm package on version
319
335
  - Explicit: no daemon and no hidden background process.
320
336
  - Inspectable: generated knowledge is JSON, YAML, and Markdown.
321
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.
322
339
  - Assistant-friendly: one normal command, with lower-level commands available when needed.
323
340
 
324
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 shown = limit > 0 ? entries.slice(-limit) : entries;
24
+ const matched = query
25
+ ? rankByFields(query, entries, [
26
+ { name: 'title', value: (entry) => entry.title },
27
+ { name: 'summary', value: (entry) => entry.summary },
28
+ { name: 'content', value: (entry) => entry.text },
29
+ { name: 'filename', value: (entry) => entry.filename },
30
+ ]).map((entry) => entry.item)
31
+ : entries;
32
+ const shown = limit > 0 ? matched.slice(-limit) : matched;
23
33
  if (options.json) {
24
34
  console.log(JSON.stringify(shown.map((e) => ({
25
35
  timestamp: e.timestamp.toISOString(),
26
36
  filename: e.filename,
27
37
  title: e.title,
38
+ ...(e.summary !== undefined ? { summary: e.summary } : {}),
28
39
  ...(e.author !== undefined ? { author: e.author } : {}),
29
40
  })), null, 2));
30
41
  }
31
42
  else {
32
- console.log(renderHistory(shown));
43
+ console.log(renderHistory(shown, undefined, query));
33
44
  }
34
45
  }));
35
46
  }
@@ -50,9 +61,10 @@ export async function readHistoryEntries(processedPath, rootPath) {
50
61
  const filePath = path.join(processedPath, filename);
51
62
  const content = await readFile(filePath, 'utf8');
52
63
  const title = content.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? filename;
64
+ const summary = content.match(/^## Summary\s+([\s\S]*?)(?:\n## |\n# |$)/m)?.[1]?.trim();
53
65
  const relPath = path.relative(rootPath, filePath);
54
66
  const author = await getGitAuthor(rootPath, relPath);
55
- entries.push({ timestamp, filename, title, author });
67
+ entries.push({ timestamp, filename, title, summary, text: content, author });
56
68
  }
57
69
  return entries;
58
70
  }
@@ -69,14 +81,14 @@ export function parseTimestampFromFilename(filename) {
69
81
  const d = new Date(iso);
70
82
  return isNaN(d.getTime()) ? undefined : d;
71
83
  }
72
- export function renderHistory(entries, useColor = supportsColor()) {
84
+ export function renderHistory(entries, useColor = supportsColor(), query = '') {
73
85
  const chalk = new Chalk({ level: useColor ? 3 : 0 });
74
86
  if (entries.length === 0) {
75
87
  return ('\n' +
76
88
  chalk.dim(' No processed cognition notes found. Write Markdown notes to .kgraph/inbox/ and run `kgraph update`.') +
77
89
  '\n');
78
90
  }
79
- const header = ` ${chalk.bold('KGraph History')} ${chalk.dim(`· ${entries.length} ${entries.length === 1 ? 'entry' : 'entries'}`)}`;
91
+ const header = ` ${chalk.bold('KGraph History')} ${chalk.dim(`· ${entries.length} ${entries.length === 1 ? 'entry' : 'entries'}${query ? ` matching "${query}"` : ''}`)}`;
80
92
  const lines = ['', header, ''];
81
93
  const titleWidth = Math.max(...entries.map((e) => e.title.length));
82
94
  for (const entry of entries) {
@@ -86,6 +98,9 @@ export function renderHistory(entries, useColor = supportsColor()) {
86
98
  ? chalk.cyan(`by ${entry.author}`)
87
99
  : chalk.dim('(uncommitted)');
88
100
  lines.push(` ${when} ${title} ${who}`);
101
+ if (entry.summary) {
102
+ lines.push(` ${chalk.dim(entry.summary.split('\n')[0])}`);
103
+ }
89
104
  }
90
105
  lines.push('');
91
106
  return lines.join('\n');
@@ -0,0 +1,4 @@
1
+ import type { Command } from 'commander';
2
+ import { type ImpactResponse } from '../../context/impact.js';
3
+ export declare function registerImpactCommand(program: Command): void;
4
+ export declare function renderImpactMarkdown(response: ImpactResponse): string;
@@ -0,0 +1,51 @@
1
+ import { loadConfig } from '../../config/config.js';
2
+ import { analyzeImpact } from '../../context/impact.js';
3
+ import { readCognitionNotes } from '../../storage/cognition-store.js';
4
+ import { assertWorkspace } from '../../storage/kgraph-paths.js';
5
+ import { mapsExist, readMaps } from '../../storage/map-store.js';
6
+ import { KGraphError, runCommand } from '../errors.js';
7
+ export function registerImpactCommand(program) {
8
+ program
9
+ .command('impact <query>')
10
+ .description('Show practical impact for a file, symbol, or topic')
11
+ .option('--json', 'Print JSON output')
12
+ .action((query, options) => runCommand(async () => {
13
+ if (!query.trim()) {
14
+ throw new KGraphError('Query cannot be empty.');
15
+ }
16
+ const workspace = await assertWorkspace(process.cwd());
17
+ if (!(await mapsExist(workspace))) {
18
+ throw new KGraphError('KGraph maps are missing. Run `kgraph scan` first.');
19
+ }
20
+ const [config, maps, cognition] = await Promise.all([
21
+ loadConfig(workspace),
22
+ readMaps(workspace),
23
+ readCognitionNotes(workspace),
24
+ ]);
25
+ const response = analyzeImpact(query, maps, cognition, config.maxContextItems);
26
+ console.log(options.json ? JSON.stringify(response, null, 2) : renderImpactMarkdown(response));
27
+ }));
28
+ }
29
+ export function renderImpactMarkdown(response) {
30
+ const lines = [`# KGraph Impact`, ``, `Query: ${response.query}`, ``];
31
+ lines.push('## Matched Files', '');
32
+ lines.push(...formatList(response.files.map((file) => `- ${file.item.path} (${file.reasons.join(', ')})`)));
33
+ lines.push('', '## Matched Symbols', '');
34
+ lines.push(...formatList(response.symbols.map((symbol) => `- ${symbol.item.name} in ${symbol.item.filePath}`)));
35
+ lines.push('', '## Imported By', '');
36
+ lines.push(...formatList(response.importedBy.map((file) => `- ${file}`)));
37
+ lines.push('', '## Called By', '');
38
+ lines.push(...formatList(response.callers.map((rel) => `- ${rel.sourceId} calls ${rel.targetId} (${rel.confidence})`)));
39
+ lines.push('', '## Calls', '');
40
+ lines.push(...formatList(response.calls.map((rel) => `- ${rel.sourceId} calls ${rel.targetId} (${rel.confidence})`)));
41
+ lines.push('', '## Ownership', '');
42
+ lines.push(...formatList(response.ownership.map((rel) => `- ${rel.sourceId} owns ${rel.targetId} (${rel.confidence})`)));
43
+ lines.push('', '## Related Cognition', '');
44
+ lines.push(...formatList(response.relatedCognition.map((note) => `- ${note.title} [${note.referencesStatus}]`)));
45
+ lines.push('', '## Risk', '');
46
+ lines.push(...formatList(response.risk.map((item) => `- ${item}`)));
47
+ return lines.join('\n');
48
+ }
49
+ function formatList(items) {
50
+ return items.length > 0 ? items : ['- None'];
51
+ }
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', 'Timeline of processed cognition sessions'),
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 },
@@ -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
+ }
@@ -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' ? 3 : 1;
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
- score += baseScore + (wordBoundary ? 2 : 0);
24
- reasons.push(`${field.name} matched "${token}"${wordBoundary ? ' (exact)' : ''}`);
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. At the end of any session that changed repository files, check the KGraph capture workflow before finishing.
19
- 6. If the file change has future value, write one concise Markdown note to \`.kgraph/inbox/<slug>.md\`; otherwise explicitly skip capture as trivial.
20
- 7. 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.
21
- 8. 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.
22
- 9. 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.
23
- 10. Run \`kgraph visualize\` when the user wants to inspect the dependency graph opens an interactive graph at http://localhost:4242 with PNG export.
24
- 11. Run \`kgraph history\` to review the timeline of past cognition sessions with git author attribution.
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\` to show a timeline of all processed cognition sessions. Includes git author attribution when available. Use \`--last <n>\` to limit entries, \`--json\` for machine-readable output.
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\` to review the timeline of past cognition sessions with git author attribution.
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. At the end of any session that changed repository files, check the KGraph capture workflow before finishing.
26
- 6. If the file change has future value, write one concise Markdown note to \`.kgraph/inbox/<slug>.md\`; otherwise explicitly skip capture as trivial.
27
- 7. 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.
28
- 8. 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.
29
- 9. 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.
30
- 10. Run \`kgraph visualize\` when the user wants to inspect the dependency graph opens an interactive graph at http://localhost:4242 with PNG export.
31
- 11. Run \`kgraph history\` to review the timeline of past cognition sessions with git author attribution.
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. At the end of any session that changed repository files, check the KGraph capture workflow before finishing.
10
- 4. If the file change has future value, write one concise Markdown note to \`.kgraph/inbox/<slug>.md\`; otherwise explicitly skip capture as trivial.
11
- 5. 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
- 6. 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
- 7. 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.
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\` to display the timeline of all processed cognition sessions. Summarize who contributed what and when. Use \`--last <n>\` to limit entries.
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\` to review the timeline of past cognition sessions with git author attribution.
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\` to review the timeline of past cognition sessions with git author attribution.
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\` to review the timeline of past cognition sessions with git author attribution.
16
+ - Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.
16
17
  `,
17
18
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kentwynn/kgraph",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "Persistent repo intelligence for AI coding assistants.",
5
5
  "type": "module",
6
6
  "bin": {