@kentwynn/kgraph 0.2.0 → 0.2.1
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 +15 -2
- package/dist/cli/commands/context.js +56 -16
- package/dist/cli/commands/doctor.js +43 -1
- package/dist/cli/commands/integrate.js +2 -1
- package/dist/cli/commands/session.js +20 -0
- package/dist/cli/commands/uninstall.d.ts +2 -0
- package/dist/cli/commands/uninstall.js +69 -0
- package/dist/cli/help.js +2 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +29 -2
- package/dist/context/context-query.js +77 -0
- package/dist/storage/kgraph-paths.js +5 -1
- package/dist/types/cognition.d.ts +8 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -62,6 +62,7 @@ kgraph "blog admin token usage"
|
|
|
62
62
|
```
|
|
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
|
+
Each context item explains why it was returned, such as a path/name match, a matched cognition reference, a domain match, or a nearby import relationship.
|
|
65
66
|
|
|
66
67
|
When you need change impact instead of broad context:
|
|
67
68
|
|
|
@@ -103,7 +104,7 @@ kgraph integrate add codex copilot cursor claude-code gemini windsurf cline
|
|
|
103
104
|
# 3. Run the normal workflow for a topic
|
|
104
105
|
kgraph "auth token refresh"
|
|
105
106
|
|
|
106
|
-
# 4.
|
|
107
|
+
# 4. Verify the setup and use doctor as the quality gate
|
|
107
108
|
kgraph doctor
|
|
108
109
|
```
|
|
109
110
|
|
|
@@ -120,7 +121,7 @@ kgraph "topic"
|
|
|
120
121
|
kgraph
|
|
121
122
|
```
|
|
122
123
|
|
|
123
|
-
Use `kgraph doctor --quality` and `kgraph repair --dry-run`
|
|
124
|
+
Use `kgraph doctor` after setup and before trusting a repo's saved intelligence. It checks initialization, maps, pending inbox notes, integration targets, and actionable quality problems. Use `kgraph doctor --quality` and `kgraph repair --dry-run` when stale or noisy cognition references start making context harder to trust.
|
|
124
125
|
|
|
125
126
|
Agents can also report session activity so KGraph can estimate token waste:
|
|
126
127
|
|
|
@@ -167,6 +168,8 @@ kgraph doctor --quality
|
|
|
167
168
|
|
|
168
169
|
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.
|
|
169
170
|
|
|
171
|
+
The default doctor result is the main quality gate. It fails on actionable hygiene issues such as stale/noisy cognition, duplicate cognition titles, generated integration files leaking into scans, missing maps, or broken integration targets. Scanner coverage counts such as unresolved local imports or unresolved call edges remain visible in `--quality`, but they do not fail the gate by themselves because they often reflect current parser limits.
|
|
172
|
+
|
|
170
173
|
```bash
|
|
171
174
|
kgraph repair --dry-run
|
|
172
175
|
kgraph repair
|
|
@@ -174,6 +177,14 @@ kgraph repair
|
|
|
174
177
|
|
|
175
178
|
`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.
|
|
176
179
|
|
|
180
|
+
```bash
|
|
181
|
+
kgraph uninstall
|
|
182
|
+
kgraph uninstall --yes
|
|
183
|
+
kgraph uninstall --keep-integrations --yes
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`uninstall` previews repo-local removal and does not delete anything unless `--yes` is passed. `uninstall --yes` removes `.kgraph/` and KGraph-managed integration blocks/files while preserving source files and user-authored text outside managed blocks. Use `--keep-integrations --yes` to remove only `.kgraph/` while leaving AI tool instruction files in place. After uninstalling, `kgraph init` can be run again for a fresh setup.
|
|
187
|
+
|
|
177
188
|
```bash
|
|
178
189
|
kgraph impact "Button"
|
|
179
190
|
kgraph impact "createSession" --json
|
|
@@ -192,6 +203,7 @@ kgraph session end --agent codex
|
|
|
192
203
|
```
|
|
193
204
|
|
|
194
205
|
Track agent-reported read/write activity, repeated reads, and estimated token cost. Supported agents are `codex`, `claude-code`, `copilot`, `cursor`, `gemini`, `windsurf`, and `cline`.
|
|
206
|
+
The text report now includes next actions, such as using `kgraph context "<topic>"` before repeated broad file inspection.
|
|
195
207
|
|
|
196
208
|
## Optional Step Commands
|
|
197
209
|
|
|
@@ -209,6 +221,7 @@ kgraph context "auth token refresh" --json
|
|
|
209
221
|
```
|
|
210
222
|
|
|
211
223
|
Return context from existing maps and cognition without scanning or updating first.
|
|
224
|
+
Markdown output includes the reason each file, symbol, cognition note, nearby symbol, or relationship was selected. Use `--json` when an agent or script needs the same explanation data programmatically.
|
|
212
225
|
|
|
213
226
|
```bash
|
|
214
227
|
kgraph update
|
|
@@ -27,7 +27,7 @@ export function registerContextCommand(program) {
|
|
|
27
27
|
export function renderContextMarkdown(response) {
|
|
28
28
|
const lines = [`# KGraph Context`, ``, `Query: ${response.query}`, ``];
|
|
29
29
|
lines.push('## Matched Domains', '');
|
|
30
|
-
lines.push(...formatList(response.matchedDomains.map((item) => `- ${item.item.name}
|
|
30
|
+
lines.push(...formatList(response.matchedDomains.map((item) => `- ${item.item.name} because ${formatReasons(item.reasons)}`)));
|
|
31
31
|
lines.push('', '## Relevant Files', '');
|
|
32
32
|
lines.push(...formatList(response.relevantFiles.map((item) => {
|
|
33
33
|
const f = item.item;
|
|
@@ -37,7 +37,7 @@ export function renderContextMarkdown(response) {
|
|
|
37
37
|
]
|
|
38
38
|
.filter(Boolean)
|
|
39
39
|
.join(', ');
|
|
40
|
-
return `- ${f.path}${meta ? ` [${meta}]` : ''}`;
|
|
40
|
+
return `- ${f.path}${meta ? ` [${meta}]` : ''} because ${formatReasons(item.reasons)}`;
|
|
41
41
|
})));
|
|
42
42
|
lines.push('', '## Relevant Symbols', '');
|
|
43
43
|
lines.push(...formatList(response.relevantSymbols.map((item) => {
|
|
@@ -46,25 +46,29 @@ export function renderContextMarkdown(response) {
|
|
|
46
46
|
const lineRange = s.startLine != null && s.endLine != null
|
|
47
47
|
? `:${s.startLine}-${s.endLine}`
|
|
48
48
|
: '';
|
|
49
|
-
return `- ${s.name} (${kindInfo}) in ${s.filePath}${lineRange}`;
|
|
49
|
+
return `- ${s.name} (${kindInfo}) in ${s.filePath}${lineRange} because ${formatReasons(item.reasons)}`;
|
|
50
50
|
})));
|
|
51
51
|
lines.push('', '## Relevant Cognition', '');
|
|
52
|
-
lines.push(...formatList(response.relevantCognition.map((item) => `- ${item.item.title} [${item.item.referencesStatus}]`)));
|
|
52
|
+
lines.push(...formatList(response.relevantCognition.map((item) => `- ${item.item.title} [${item.item.referencesStatus}] because ${formatReasons(item.reasons)}`)));
|
|
53
53
|
lines.push('', '## Relationships', '');
|
|
54
|
-
lines.push(...formatGroupedRelationships(response.relationships));
|
|
54
|
+
lines.push(...formatGroupedRelationships(response.relationships, response.relationshipExplanations));
|
|
55
55
|
lines.push('', '## Nearby Symbols (1-hop imports)', '');
|
|
56
|
-
lines.push(...formatList((response
|
|
56
|
+
lines.push(...formatList(nearbySymbolItems(response).map(({ symbol: s, reasons }) => {
|
|
57
57
|
const kindInfo = [s.kind, s.parentName].filter(Boolean).join(', ');
|
|
58
58
|
const lineRange = s.startLine != null && s.endLine != null
|
|
59
59
|
? `:${s.startLine}-${s.endLine}`
|
|
60
60
|
: '';
|
|
61
|
-
return `- ${s.name} (${kindInfo}) in ${s.filePath}${lineRange}`;
|
|
61
|
+
return `- ${s.name} (${kindInfo}) in ${s.filePath}${lineRange} because ${formatReasons(reasons)}`;
|
|
62
62
|
})));
|
|
63
63
|
lines.push('', '## Stale References', '');
|
|
64
64
|
lines.push(...formatList(response.staleReferences.map((ref) => `- ${ref}`)));
|
|
65
65
|
return lines.join('\n');
|
|
66
66
|
}
|
|
67
|
-
function formatGroupedRelationships(relationships) {
|
|
67
|
+
function formatGroupedRelationships(relationships, explanations) {
|
|
68
|
+
const reasonsByRelationship = new Map((explanations ?? []).map((item) => [
|
|
69
|
+
relationshipKey(item.relationship),
|
|
70
|
+
item.reasons,
|
|
71
|
+
]));
|
|
68
72
|
const imports = relationships.filter((r) => r.relationshipType === 'import');
|
|
69
73
|
const calls = relationships.filter((r) => r.relationshipType === 'calls');
|
|
70
74
|
const contains = relationships.filter((r) => r.relationshipType === 'symbol-contains');
|
|
@@ -77,26 +81,62 @@ function formatGroupedRelationships(relationships) {
|
|
|
77
81
|
const lines = [];
|
|
78
82
|
if (imports.length > 0) {
|
|
79
83
|
lines.push('Imports:');
|
|
80
|
-
for (const r of imports)
|
|
81
|
-
lines.push(` ${r.sourceId} → ${r.targetId}`);
|
|
84
|
+
for (const r of imports) {
|
|
85
|
+
lines.push(` ${r.sourceId} → ${r.targetId}${formatRelationshipReason(r, reasonsByRelationship)}`);
|
|
86
|
+
}
|
|
82
87
|
}
|
|
83
88
|
if (calls.length > 0) {
|
|
84
89
|
lines.push('Calls:');
|
|
85
|
-
for (const r of calls)
|
|
86
|
-
lines.push(` ${r.sourceId} → ${r.targetId}`);
|
|
90
|
+
for (const r of calls) {
|
|
91
|
+
lines.push(` ${r.sourceId} → ${r.targetId}${formatRelationshipReason(r, reasonsByRelationship)}`);
|
|
92
|
+
}
|
|
87
93
|
}
|
|
88
94
|
if (contains.length > 0) {
|
|
89
95
|
lines.push('Contains:');
|
|
90
|
-
for (const r of contains)
|
|
91
|
-
lines.push(` ${r.sourceId} contains ${r.targetId}`);
|
|
96
|
+
for (const r of contains) {
|
|
97
|
+
lines.push(` ${r.sourceId} contains ${r.targetId}${formatRelationshipReason(r, reasonsByRelationship)}`);
|
|
98
|
+
}
|
|
92
99
|
}
|
|
93
100
|
if (other.length > 0) {
|
|
94
101
|
lines.push('Other:');
|
|
95
|
-
for (const r of other)
|
|
96
|
-
lines.push(` ${r.sourceId} ${r.relationshipType} ${r.targetId}`);
|
|
102
|
+
for (const r of other) {
|
|
103
|
+
lines.push(` ${r.sourceId} ${r.relationshipType} ${r.targetId}${formatRelationshipReason(r, reasonsByRelationship)}`);
|
|
104
|
+
}
|
|
97
105
|
}
|
|
98
106
|
return lines.length > 0 ? lines : ['- None'];
|
|
99
107
|
}
|
|
100
108
|
function formatList(items) {
|
|
101
109
|
return items.length > 0 ? items : ['- None'];
|
|
102
110
|
}
|
|
111
|
+
function formatReasons(reasons) {
|
|
112
|
+
if (reasons.length === 0) {
|
|
113
|
+
return 'it is near the query';
|
|
114
|
+
}
|
|
115
|
+
const visible = reasons.slice(0, 3);
|
|
116
|
+
const remaining = reasons.length - visible.length;
|
|
117
|
+
return remaining > 0
|
|
118
|
+
? `${visible.join('; ')}; and ${remaining} more`
|
|
119
|
+
: visible.join('; ');
|
|
120
|
+
}
|
|
121
|
+
function nearbySymbolItems(response) {
|
|
122
|
+
if (response.nearbySymbolExplanations) {
|
|
123
|
+
return response.nearbySymbolExplanations;
|
|
124
|
+
}
|
|
125
|
+
return (response.nearbySymbols ?? []).map((symbol) => ({
|
|
126
|
+
symbol,
|
|
127
|
+
reasons: ['exported symbol from 1-hop import'],
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
function formatRelationshipReason(relationship, reasonsByRelationship) {
|
|
131
|
+
const reasons = reasonsByRelationship.get(relationshipKey(relationship));
|
|
132
|
+
return reasons && reasons.length > 0
|
|
133
|
+
? ` because ${formatReasons(reasons)}`
|
|
134
|
+
: '';
|
|
135
|
+
}
|
|
136
|
+
function relationshipKey(relationship) {
|
|
137
|
+
return [
|
|
138
|
+
relationship.sourceId,
|
|
139
|
+
relationship.targetId,
|
|
140
|
+
relationship.relationshipType,
|
|
141
|
+
].join('\0');
|
|
142
|
+
}
|
|
@@ -80,12 +80,28 @@ export function registerDoctorCommand(program) {
|
|
|
80
80
|
: `${integration.name}: missing ${integration.targetPath}`)
|
|
81
81
|
.join('; '),
|
|
82
82
|
});
|
|
83
|
+
let qualityReport;
|
|
84
|
+
if (maps) {
|
|
85
|
+
qualityReport = await analyzeCognitionQuality(workspace, maps);
|
|
86
|
+
const qualityFindings = summarizeQualityFindings(qualityReport);
|
|
87
|
+
const coverageNotes = summarizeCoverageNotes(qualityReport);
|
|
88
|
+
checks.push({
|
|
89
|
+
label: 'quality gate',
|
|
90
|
+
ok: qualityFindings.length === 0,
|
|
91
|
+
detail: qualityFindings.length === 0
|
|
92
|
+
? [
|
|
93
|
+
'no stale/noisy cognition, generated scan noise, or duplicate titles',
|
|
94
|
+
...coverageNotes,
|
|
95
|
+
].join('; ')
|
|
96
|
+
: qualityFindings.join('; '),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
83
99
|
printChecks(checks);
|
|
84
100
|
if (options.quality && maps) {
|
|
85
101
|
console.log('');
|
|
86
102
|
console.log('KGraph Cognition Quality');
|
|
87
103
|
console.log('');
|
|
88
|
-
printQualityReport(await analyzeCognitionQuality(workspace, maps));
|
|
104
|
+
printQualityReport(qualityReport ?? (await analyzeCognitionQuality(workspace, maps)));
|
|
89
105
|
}
|
|
90
106
|
if (checks.some((check) => !check.ok)) {
|
|
91
107
|
process.exitCode = 1;
|
|
@@ -134,3 +150,29 @@ export function printQualityReport(report) {
|
|
|
134
150
|
console.log(` next status: ${change.nextStatus}`);
|
|
135
151
|
}
|
|
136
152
|
}
|
|
153
|
+
function summarizeQualityFindings(report) {
|
|
154
|
+
const findings = [];
|
|
155
|
+
if (report.mixedOrStaleCount > 0) {
|
|
156
|
+
findings.push(`${report.mixedOrStaleCount} stale/mixed/unresolved note(s)`);
|
|
157
|
+
}
|
|
158
|
+
if (report.noisyFileRefCount > 0 || report.noisySymbolRefCount > 0) {
|
|
159
|
+
findings.push(`${report.noisyFileRefCount + report.noisySymbolRefCount} noisy cognition ref(s); run \`kgraph repair --dry-run\``);
|
|
160
|
+
}
|
|
161
|
+
if (report.duplicateTitleCount > 0) {
|
|
162
|
+
findings.push(`${report.duplicateTitleCount} duplicate cognition title(s)`);
|
|
163
|
+
}
|
|
164
|
+
if (report.generatedFileScanCount > 0) {
|
|
165
|
+
findings.push(`${report.generatedFileScanCount} generated/integration file(s) scanned; update excludes`);
|
|
166
|
+
}
|
|
167
|
+
return findings;
|
|
168
|
+
}
|
|
169
|
+
function summarizeCoverageNotes(report) {
|
|
170
|
+
const notes = [];
|
|
171
|
+
if (report.unresolvedLocalImportCount > 0) {
|
|
172
|
+
notes.push(`${report.unresolvedLocalImportCount} unresolved local import(s) visible in --quality`);
|
|
173
|
+
}
|
|
174
|
+
if (report.unresolvedCallCount > 0) {
|
|
175
|
+
notes.push(`${report.unresolvedCallCount} unresolved call edge(s) visible in --quality`);
|
|
176
|
+
}
|
|
177
|
+
return notes;
|
|
178
|
+
}
|
|
@@ -5,7 +5,8 @@ import { KGraphError, runCommand } from '../errors.js';
|
|
|
5
5
|
export function registerIntegrateCommand(program) {
|
|
6
6
|
const integrate = program
|
|
7
7
|
.command('integrate')
|
|
8
|
-
.description('Manage AI tool integrations')
|
|
8
|
+
.description('Manage AI tool integrations')
|
|
9
|
+
.helpOption('-h, --help', 'Show help');
|
|
9
10
|
integrate
|
|
10
11
|
.command('list')
|
|
11
12
|
.description('List configured integrations')
|
|
@@ -6,6 +6,7 @@ export function registerSessionCommand(program) {
|
|
|
6
6
|
const session = program
|
|
7
7
|
.command('session')
|
|
8
8
|
.description('Track agent read/write session activity and token estimates')
|
|
9
|
+
.helpOption('-h, --help', 'Show help')
|
|
9
10
|
.option('--json', 'Print JSON output')
|
|
10
11
|
.action((options) => runCommand(async () => {
|
|
11
12
|
const workspace = await assertWorkspace(process.cwd());
|
|
@@ -93,6 +94,8 @@ export function renderSessionReport(report) {
|
|
|
93
94
|
lines.push(...formatList(report.recentEvents.map((event) => `- ${event.agent} ${event.type}${event.path ? ` ${event.path}` : ''} [${event.captureSource}]`)));
|
|
94
95
|
lines.push('', 'Recent Ledger');
|
|
95
96
|
lines.push(...formatList(report.ledger.map((entry) => `- ${entry.agent} ${entry.readCount} reads, ${entry.writeCount} writes, ${entry.repeatedReadCount} repeated`)));
|
|
97
|
+
lines.push('', 'Next');
|
|
98
|
+
lines.push(...sessionNextActions(report));
|
|
96
99
|
return lines.join('\n');
|
|
97
100
|
}
|
|
98
101
|
function requireAgent(value) {
|
|
@@ -110,3 +113,20 @@ function normalizeSource(value) {
|
|
|
110
113
|
function formatList(items) {
|
|
111
114
|
return items.length > 0 ? items : ['- None'];
|
|
112
115
|
}
|
|
116
|
+
function sessionNextActions(report) {
|
|
117
|
+
if (report.readCount === 0 && report.writeCount === 0) {
|
|
118
|
+
return [
|
|
119
|
+
'- Start tracking with `kgraph session start --agent <name>`.',
|
|
120
|
+
'- Record meaningful reads/writes with `kgraph session read <path> --agent <name>` and `kgraph session write <path> --agent <name>`.',
|
|
121
|
+
];
|
|
122
|
+
}
|
|
123
|
+
if (report.repeatedReadCount > 0) {
|
|
124
|
+
return [
|
|
125
|
+
'- Repeated reads are present; run `kgraph context "<topic>"` before broad file inspection.',
|
|
126
|
+
'- End the tracked work with `kgraph session end --agent <name>` when the coding session is done.',
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
return [
|
|
130
|
+
'- End the tracked work with `kgraph session end --agent <name>` when the coding session is done.',
|
|
131
|
+
];
|
|
132
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { rm } from 'node:fs/promises';
|
|
2
|
+
import { loadConfig } from '../../config/config.js';
|
|
3
|
+
import { removeIntegrations } from '../../integrations/integration-store.js';
|
|
4
|
+
import { pathExists, resolveWorkspace } from '../../storage/kgraph-paths.js';
|
|
5
|
+
import { runCommand } from '../errors.js';
|
|
6
|
+
export function registerUninstallCommand(program) {
|
|
7
|
+
program
|
|
8
|
+
.command('uninstall')
|
|
9
|
+
.description('Remove KGraph from this repository')
|
|
10
|
+
.option('--yes', 'Apply the uninstall after previewing what will be removed')
|
|
11
|
+
.option('--keep-integrations', 'Remove only .kgraph/ and preserve generated AI tool instruction files')
|
|
12
|
+
.action((options) => runCommand(async () => {
|
|
13
|
+
const workspace = resolveWorkspace(process.cwd());
|
|
14
|
+
const initialized = await pathExists(workspace.kgraphPath);
|
|
15
|
+
const configuredIntegrations = initialized
|
|
16
|
+
? (await loadConfig(workspace)).integrations.map((integration) => integration.name)
|
|
17
|
+
: [];
|
|
18
|
+
printUninstallPreview({
|
|
19
|
+
initialized,
|
|
20
|
+
integrations: configuredIntegrations,
|
|
21
|
+
keepIntegrations: options.keepIntegrations === true,
|
|
22
|
+
applying: options.yes === true,
|
|
23
|
+
});
|
|
24
|
+
if (!options.yes) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (initialized &&
|
|
28
|
+
!options.keepIntegrations &&
|
|
29
|
+
configuredIntegrations.length > 0) {
|
|
30
|
+
await removeIntegrations(workspace, configuredIntegrations);
|
|
31
|
+
}
|
|
32
|
+
if (initialized) {
|
|
33
|
+
await rm(workspace.kgraphPath, { recursive: true, force: true });
|
|
34
|
+
}
|
|
35
|
+
console.log('');
|
|
36
|
+
console.log('KGraph uninstall complete.');
|
|
37
|
+
console.log('Run `kgraph init` to set up this repository again.');
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
function printUninstallPreview(input) {
|
|
41
|
+
console.log('KGraph Uninstall Preview');
|
|
42
|
+
console.log('');
|
|
43
|
+
console.log('Will remove:');
|
|
44
|
+
if (input.initialized) {
|
|
45
|
+
console.log('- .kgraph/ runtime workspace');
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
console.log('- Nothing: .kgraph/ does not exist in this repository');
|
|
49
|
+
}
|
|
50
|
+
if (!input.keepIntegrations) {
|
|
51
|
+
if (input.integrations.length > 0) {
|
|
52
|
+
console.log(`- KGraph-managed integration blocks/files for: ${input.integrations.join(', ')}`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.log('- No configured integration blocks/files found');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
console.log('');
|
|
59
|
+
console.log('Will preserve:');
|
|
60
|
+
console.log('- Repository source files');
|
|
61
|
+
console.log('- User-authored content outside KGraph managed blocks');
|
|
62
|
+
if (input.keepIntegrations) {
|
|
63
|
+
console.log('- KGraph-managed integration instruction files');
|
|
64
|
+
}
|
|
65
|
+
if (!input.applying) {
|
|
66
|
+
console.log('');
|
|
67
|
+
console.log('No files were removed. Run `kgraph uninstall --yes` to apply.');
|
|
68
|
+
}
|
|
69
|
+
}
|
package/dist/cli/help.js
CHANGED
|
@@ -36,6 +36,8 @@ export function renderRootHelp(useColor = supportsColor()) {
|
|
|
36
36
|
command('doctor --quality', 'Report stale/noisy cognition references'),
|
|
37
37
|
command('repair --dry-run', 'Preview cognition reference cleanup'),
|
|
38
38
|
command('repair', 'Clean noisy stale cognition references'),
|
|
39
|
+
command('uninstall', 'Preview repo-local KGraph removal'),
|
|
40
|
+
command('uninstall --yes', 'Remove .kgraph/ and managed integrations'),
|
|
39
41
|
command('visualize', 'Interactive dependency graph at http://localhost:4242'),
|
|
40
42
|
command('history "blog button"', 'Search processed cognition sessions'),
|
|
41
43
|
'',
|
package/dist/cli/index.d.ts
CHANGED
package/dist/cli/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { registerIntegrateCommand } from './commands/integrate.js';
|
|
|
12
12
|
import { registerRepairCommand } from './commands/repair.js';
|
|
13
13
|
import { registerScanCommand } from './commands/scan.js';
|
|
14
14
|
import { registerSessionCommand } from './commands/session.js';
|
|
15
|
+
import { registerUninstallCommand } from './commands/uninstall.js';
|
|
15
16
|
import { registerUpdateCommand } from './commands/update.js';
|
|
16
17
|
import { registerVisualizeCommand } from './commands/visualize.js';
|
|
17
18
|
import { runDefaultWorkflow } from './commands/workflow.js';
|
|
@@ -25,7 +26,6 @@ export function createProgram() {
|
|
|
25
26
|
.description('Persistent repo intelligence for AI coding assistants')
|
|
26
27
|
.argument('[topic...]', 'Run the default refresh workflow and optionally return context for a topic')
|
|
27
28
|
.version(version)
|
|
28
|
-
.addHelpText('beforeAll', renderRootHelp())
|
|
29
29
|
.helpOption(false)
|
|
30
30
|
.action(async (topicParts = []) => {
|
|
31
31
|
await runDefaultWorkflow(topicParts.join(' '));
|
|
@@ -48,17 +48,44 @@ export function createProgram() {
|
|
|
48
48
|
registerHistoryCommand(program);
|
|
49
49
|
registerDoctorCommand(program);
|
|
50
50
|
registerRepairCommand(program);
|
|
51
|
+
registerUninstallCommand(program);
|
|
51
52
|
return program;
|
|
52
53
|
}
|
|
53
54
|
if (isCliEntrypoint()) {
|
|
54
55
|
const program = createProgram();
|
|
55
|
-
|
|
56
|
+
const helpTarget = findExplicitHelpTarget(program, process.argv.slice(2));
|
|
57
|
+
if (helpTarget === program || shouldRenderRootHelpBeforeParse(process.argv)) {
|
|
56
58
|
console.log(renderRootHelp());
|
|
57
59
|
}
|
|
60
|
+
else if (helpTarget) {
|
|
61
|
+
helpTarget.outputHelp();
|
|
62
|
+
}
|
|
58
63
|
else {
|
|
59
64
|
await program.parseAsync(process.argv);
|
|
60
65
|
}
|
|
61
66
|
}
|
|
67
|
+
export function shouldRenderRootHelpBeforeParse(argv) {
|
|
68
|
+
const args = argv.slice(2);
|
|
69
|
+
return args.length === 1 && (args[0] === '-h' || args[0] === '--help');
|
|
70
|
+
}
|
|
71
|
+
function findExplicitHelpTarget(program, args) {
|
|
72
|
+
let command = program;
|
|
73
|
+
let matchedSubcommand = false;
|
|
74
|
+
for (const arg of args) {
|
|
75
|
+
if (arg === '-h' || arg === '--help') {
|
|
76
|
+
return matchedSubcommand ? command : program;
|
|
77
|
+
}
|
|
78
|
+
if (arg.startsWith('-')) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const next = command.commands.find((candidate) => candidate.name() === arg || candidate.aliases().includes(arg));
|
|
82
|
+
if (next) {
|
|
83
|
+
command = next;
|
|
84
|
+
matchedSubcommand = true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
62
89
|
function isCliEntrypoint() {
|
|
63
90
|
if (!process.argv[1]) {
|
|
64
91
|
return false;
|
|
@@ -59,6 +59,13 @@ export async function queryContext(workspace, config, maps, query) {
|
|
|
59
59
|
].filter((relationship, index, all) => all.findIndex((candidate) => candidate.sourceId === relationship.sourceId &&
|
|
60
60
|
candidate.targetId === relationship.targetId &&
|
|
61
61
|
candidate.relationshipType === relationship.relationshipType) === index);
|
|
62
|
+
const relationshipExplanations = explainRelationships(relationships, {
|
|
63
|
+
rankedRelationships,
|
|
64
|
+
relevantFiles,
|
|
65
|
+
relevantSymbols,
|
|
66
|
+
relevantCognition,
|
|
67
|
+
matchedDomains,
|
|
68
|
+
});
|
|
62
69
|
const filePaths = new Set(maps.fileMap.files.map((f) => f.path));
|
|
63
70
|
const symbolNames = new Set(maps.symbolMap.symbols.map((s) => s.name));
|
|
64
71
|
const staleReferences = cognition
|
|
@@ -95,6 +102,13 @@ export async function queryContext(workspace, config, maps, query) {
|
|
|
95
102
|
importedFilePaths.has(s.filePath) &&
|
|
96
103
|
!matchedSymbolIds.has(s.id))
|
|
97
104
|
.slice(0, max);
|
|
105
|
+
const nearbySymbolExplanations = nearbySymbols.map((symbol) => ({
|
|
106
|
+
symbol,
|
|
107
|
+
reasons: [
|
|
108
|
+
`exported symbol from 1-hop import ${symbol.filePath}`,
|
|
109
|
+
...dependenciesForImportedSymbol(symbol, maps.dependencyMap.dependencies),
|
|
110
|
+
],
|
|
111
|
+
}));
|
|
98
112
|
return {
|
|
99
113
|
query,
|
|
100
114
|
matchedDomains,
|
|
@@ -102,8 +116,71 @@ export async function queryContext(workspace, config, maps, query) {
|
|
|
102
116
|
relevantSymbols,
|
|
103
117
|
relevantCognition,
|
|
104
118
|
relationships: relationships.slice(0, max),
|
|
119
|
+
relationshipExplanations: relationshipExplanations.slice(0, max),
|
|
105
120
|
nearbySymbols,
|
|
121
|
+
nearbySymbolExplanations,
|
|
106
122
|
staleReferences,
|
|
107
123
|
warnings: [],
|
|
108
124
|
};
|
|
109
125
|
}
|
|
126
|
+
function explainRelationships(relationships, context) {
|
|
127
|
+
const rankedReasons = new Map(context.rankedRelationships.map((ranked) => [
|
|
128
|
+
relationshipKey(ranked.item),
|
|
129
|
+
ranked.reasons,
|
|
130
|
+
]));
|
|
131
|
+
return relationships.map((relationship) => {
|
|
132
|
+
const reasons = new Set();
|
|
133
|
+
for (const reason of rankedReasons.get(relationshipKey(relationship)) ?? []) {
|
|
134
|
+
reasons.add(reason);
|
|
135
|
+
}
|
|
136
|
+
for (const file of context.relevantFiles) {
|
|
137
|
+
if (relationship.sourceId === file.item.path ||
|
|
138
|
+
relationship.targetId === file.item.path) {
|
|
139
|
+
reasons.add(`connected to matched file ${file.item.path}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
for (const symbol of context.relevantSymbols) {
|
|
143
|
+
if (relationship.sourceId === symbol.item.id ||
|
|
144
|
+
relationship.targetId === symbol.item.id ||
|
|
145
|
+
relationship.sourceId === symbol.item.name ||
|
|
146
|
+
relationship.targetId === symbol.item.name ||
|
|
147
|
+
relationship.sourceId === symbol.item.filePath ||
|
|
148
|
+
relationship.targetId === symbol.item.filePath) {
|
|
149
|
+
reasons.add(`connected to matched symbol ${symbol.item.name}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (const note of context.relevantCognition) {
|
|
153
|
+
if (note.item.relatedFiles.includes(relationship.sourceId) ||
|
|
154
|
+
note.item.relatedFiles.includes(relationship.targetId) ||
|
|
155
|
+
note.item.relatedSymbols.includes(relationship.sourceId) ||
|
|
156
|
+
note.item.relatedSymbols.includes(relationship.targetId)) {
|
|
157
|
+
reasons.add(`referenced by cognition "${note.item.title}"`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
for (const domain of context.matchedDomains) {
|
|
161
|
+
if (domain.item.files.includes(relationship.sourceId) ||
|
|
162
|
+
domain.item.files.includes(relationship.targetId) ||
|
|
163
|
+
domain.item.symbols.includes(relationship.sourceId) ||
|
|
164
|
+
domain.item.symbols.includes(relationship.targetId)) {
|
|
165
|
+
reasons.add(`inside matched domain ${domain.item.name}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (reasons.size === 0) {
|
|
169
|
+
reasons.add(`nearby ${relationship.relationshipType} relationship`);
|
|
170
|
+
}
|
|
171
|
+
return { relationship, reasons: [...reasons] };
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function dependenciesForImportedSymbol(symbol, dependencies) {
|
|
175
|
+
return dependencies
|
|
176
|
+
.filter((dependency) => dependency.kind === 'local' &&
|
|
177
|
+
dependency.resolvedFile === symbol.filePath)
|
|
178
|
+
.map((dependency) => `imported by ${dependency.fromFile} via ${dependency.specifier}`);
|
|
179
|
+
}
|
|
180
|
+
function relationshipKey(relationship) {
|
|
181
|
+
return [
|
|
182
|
+
relationship.sourceId,
|
|
183
|
+
relationship.targetId,
|
|
184
|
+
relationship.relationshipType,
|
|
185
|
+
].join('\0');
|
|
186
|
+
}
|
|
@@ -37,7 +37,11 @@ export async function pathExists(targetPath) {
|
|
|
37
37
|
export async function assertWorkspace(rootPath = process.cwd()) {
|
|
38
38
|
const workspace = resolveWorkspace(rootPath);
|
|
39
39
|
if (!(await pathExists(workspace.kgraphPath))) {
|
|
40
|
-
throw new KGraphError(
|
|
40
|
+
throw new KGraphError([
|
|
41
|
+
"KGraph is not initialized for this repository.",
|
|
42
|
+
"Run `kgraph init` first, or use `kgraph init --integrations codex,copilot,cursor,claude-code` to initialize and connect common AI tools.",
|
|
43
|
+
"After init, run `kgraph doctor` to verify maps, integrations, and cognition quality.",
|
|
44
|
+
].join("\n"));
|
|
41
45
|
}
|
|
42
46
|
const info = await stat(workspace.kgraphPath);
|
|
43
47
|
if (!info.isDirectory()) {
|
|
@@ -38,7 +38,15 @@ export interface ContextResponse {
|
|
|
38
38
|
relevantSymbols: RankedItem<CodeSymbol>[];
|
|
39
39
|
relevantCognition: RankedItem<CognitionNote>[];
|
|
40
40
|
relationships: Relationship[];
|
|
41
|
+
relationshipExplanations?: Array<{
|
|
42
|
+
relationship: Relationship;
|
|
43
|
+
reasons: string[];
|
|
44
|
+
}>;
|
|
41
45
|
nearbySymbols?: CodeSymbol[];
|
|
46
|
+
nearbySymbolExplanations?: Array<{
|
|
47
|
+
symbol: CodeSymbol;
|
|
48
|
+
reasons: string[];
|
|
49
|
+
}>;
|
|
42
50
|
staleReferences: string[];
|
|
43
51
|
warnings: string[];
|
|
44
52
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kentwynn/kgraph",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Persistent repo intelligence for AI coding assistants.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/node": "^20.17.10",
|
|
61
61
|
"tsx": "^4.19.2",
|
|
62
|
-
"vitest": "^2.
|
|
62
|
+
"vitest": "^3.2.4"
|
|
63
63
|
},
|
|
64
64
|
"engines": {
|
|
65
65
|
"node": ">=20"
|