@kentwynn/kgraph 0.2.2 → 0.2.4
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 +19 -16
- package/dist/cli/commands/context.js +24 -0
- package/dist/cli/commands/init.js +3 -2
- package/dist/cli/commands/integrate.js +1 -1
- package/dist/cli/commands/scan.d.ts +1 -1
- package/dist/cli/commands/scan.js +15 -11
- package/dist/cli/commands/workflow.js +1 -0
- package/dist/context/context-query.js +39 -1
- package/dist/integrations/adapters/claude-code.js +2 -12
- package/dist/integrations/adapters/cline.js +2 -8
- package/dist/integrations/adapters/codex.js +4 -14
- package/dist/integrations/adapters/copilot.js +25 -8
- package/dist/integrations/adapters/cursor.js +2 -8
- package/dist/integrations/adapters/gemini.js +2 -8
- package/dist/integrations/adapters/windsurf.js +2 -8
- package/dist/integrations/integration-store.js +1 -1
- package/dist/integrations/workflow-steps.d.ts +18 -0
- package/dist/integrations/workflow-steps.js +44 -0
- package/dist/scanner/git-utils.d.ts +34 -0
- package/dist/scanner/git-utils.js +130 -0
- package/dist/scanner/repo-scanner.d.ts +3 -1
- package/dist/scanner/repo-scanner.js +38 -2
- package/dist/storage/map-store.d.ts +2 -2
- package/dist/storage/map-store.js +38 -20
- package/dist/types/cognition.d.ts +7 -0
- package/dist/types/maps.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -219,6 +219,8 @@ kgraph scan
|
|
|
219
219
|
|
|
220
220
|
Refresh only the structural maps in `.kgraph/map/`.
|
|
221
221
|
|
|
222
|
+
If the repository is a git repo, KGraph stores the HEAD commit hash with the scan result. On the next scan it computes which files changed since that commit using `git diff --name-only` and skips unchanged files without any filesystem `stat()` calls. In large repos this is measurably faster than the mtime+size fallback, which still runs automatically in non-git directories.
|
|
223
|
+
|
|
222
224
|
```bash
|
|
223
225
|
kgraph context "auth token refresh"
|
|
224
226
|
kgraph context "auth token refresh" --json
|
|
@@ -227,6 +229,8 @@ kgraph context "auth token refresh" --json
|
|
|
227
229
|
Return context from existing maps and cognition without scanning or updating first.
|
|
228
230
|
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.
|
|
229
231
|
|
|
232
|
+
Context output includes a **Recent Git Changes** section that surfaces files with staged edits, unstaged edits, or changes in recent commits. This lets AI agents know which files are actively in flux without running a separate `git status` or `git log`.
|
|
233
|
+
|
|
230
234
|
```bash
|
|
231
235
|
kgraph update
|
|
232
236
|
kgraph update --dry-run
|
|
@@ -263,24 +267,24 @@ kgraph integrate list
|
|
|
263
267
|
kgraph integrate remove cursor
|
|
264
268
|
```
|
|
265
269
|
|
|
266
|
-
New integrations default to `
|
|
270
|
+
New integrations default to `smart` mode. Use `--mode always` to force KGraph on every chat, or `--mode manual` to run only when explicitly asked.
|
|
267
271
|
|
|
268
|
-
| Mode
|
|
269
|
-
|
|
|
270
|
-
| `always` | Every chat in the repository starts with `kgraph "<topic>"`, even simple or conversational requests.
|
|
271
|
-
| `smart`
|
|
272
|
-
| `manual` | Exposes KGraph commands and instructions, but the agent runs KGraph only when the user explicitly asks.
|
|
273
|
-
| `off`
|
|
272
|
+
| Mode | Behavior |
|
|
273
|
+
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
274
|
+
| `always` | Every chat in the repository starts with `kgraph "<topic>"`, even simple or conversational requests. |
|
|
275
|
+
| `smart` | Runs KGraph automatically for repo-specific coding, debugging, architecture, refactor, review, or file-exploration requests. Skips simple conversational requests that do not depend on repo knowledge. |
|
|
276
|
+
| `manual` | Exposes KGraph commands and instructions, but the agent runs KGraph only when the user explicitly asks. |
|
|
277
|
+
| `off` | Disables that integration and removes generated KGraph instruction blocks/command files. |
|
|
274
278
|
|
|
275
|
-
| Tool
|
|
276
|
-
|
|
|
277
|
-
| Codex
|
|
279
|
+
| Tool | Files KGraph manages |
|
|
280
|
+
| -------------- | ------------------------------------------------------ |
|
|
281
|
+
| Codex | `AGENTS.md`, `.agents/skills/kgraph/SKILL.md` |
|
|
278
282
|
| GitHub Copilot | `.github/copilot-instructions.md`, `.github/prompts/*` |
|
|
279
|
-
| Cursor
|
|
280
|
-
| Claude Code
|
|
281
|
-
| Gemini CLI
|
|
282
|
-
| Windsurf
|
|
283
|
-
| Cline
|
|
283
|
+
| Cursor | `.cursor/rules/kgraph.mdc` |
|
|
284
|
+
| Claude Code | `CLAUDE.md`, `.claude/commands/*` |
|
|
285
|
+
| Gemini CLI | `GEMINI.md` |
|
|
286
|
+
| Windsurf | `.windsurf/rules/kgraph.md` |
|
|
287
|
+
| Cline | `.clinerules/kgraph.md` |
|
|
284
288
|
|
|
285
289
|
Antigravity is supported through the existing agent instruction surfaces it can read, especially `AGENTS.md` and `GEMINI.md`; it does not need a separate KGraph adapter yet.
|
|
286
290
|
|
|
@@ -406,7 +410,6 @@ The release workflow builds, tests, packs, publishes the npm package on version
|
|
|
406
410
|
|
|
407
411
|
## Roadmap
|
|
408
412
|
|
|
409
|
-
- Better Git-aware token saving and diff context.
|
|
410
413
|
- Smarter cross-file symbol and call relationship inference.
|
|
411
414
|
- Stronger TypeScript path alias and package export resolution.
|
|
412
415
|
- Richer graph filtering for large repositories.
|
|
@@ -62,6 +62,30 @@ export function renderContextMarkdown(response) {
|
|
|
62
62
|
})));
|
|
63
63
|
lines.push('', '## Stale References', '');
|
|
64
64
|
lines.push(...formatList(response.staleReferences.map((ref) => `- ${ref}`)));
|
|
65
|
+
lines.push('', '## Recent Git Changes', '');
|
|
66
|
+
if (response.gitChanges && response.gitChanges.length > 0) {
|
|
67
|
+
const staged = response.gitChanges.filter((c) => c.status === 'staged');
|
|
68
|
+
const unstaged = response.gitChanges.filter((c) => c.status === 'unstaged');
|
|
69
|
+
const recent = response.gitChanges.filter((c) => c.status === 'recent-commit');
|
|
70
|
+
if (staged.length > 0) {
|
|
71
|
+
lines.push('Staged:');
|
|
72
|
+
for (const c of staged)
|
|
73
|
+
lines.push(` ${c.path} (${c.reason})`);
|
|
74
|
+
}
|
|
75
|
+
if (unstaged.length > 0) {
|
|
76
|
+
lines.push('Unstaged:');
|
|
77
|
+
for (const c of unstaged)
|
|
78
|
+
lines.push(` ${c.path} (${c.reason})`);
|
|
79
|
+
}
|
|
80
|
+
if (recent.length > 0) {
|
|
81
|
+
lines.push('Recent commits:');
|
|
82
|
+
for (const c of recent)
|
|
83
|
+
lines.push(` ${c.path} (${c.reason})`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
lines.push('- None');
|
|
88
|
+
}
|
|
65
89
|
return lines.join('\n');
|
|
66
90
|
}
|
|
67
91
|
function formatGroupedRelationships(relationships, explanations) {
|
|
@@ -14,7 +14,7 @@ export function registerInitCommand(program) {
|
|
|
14
14
|
.description('Initialize a .kgraph workspace')
|
|
15
15
|
.option('--integration <name>', 'Configure an AI tool integration', collectOption, [])
|
|
16
16
|
.option('--integrations <names>', 'Configure comma-separated AI tool integrations')
|
|
17
|
-
.option('--mode <mode>', 'Integration mode: always, smart, manual, or off', '
|
|
17
|
+
.option('--mode <mode>', 'Integration mode: always, smart, manual, or off', 'smart')
|
|
18
18
|
.action((options) => runCommand(async () => {
|
|
19
19
|
const workspace = await ensureWorkspace(process.cwd());
|
|
20
20
|
const wroteConfig = await writeDefaultConfig(workspace);
|
|
@@ -38,6 +38,7 @@ export function registerInitCommand(program) {
|
|
|
38
38
|
dependencies: previousMaps.dependencyMap.dependencies,
|
|
39
39
|
relationships: previousMaps.relationshipMap.relationships,
|
|
40
40
|
warnings: [],
|
|
41
|
+
scannedAtCommit: previousMaps.fileMap.scannedAtCommit,
|
|
41
42
|
});
|
|
42
43
|
await writeMaps(workspace, result);
|
|
43
44
|
console.log(`Scanned ${result.files.length} files and ${result.symbols.length} symbols.`);
|
|
@@ -52,7 +53,7 @@ export function registerInitCommand(program) {
|
|
|
52
53
|
})) {
|
|
53
54
|
const selected = await promptForInitIntegrations(recommendedIntegrations);
|
|
54
55
|
if (selected.length > 0) {
|
|
55
|
-
const changed = await addIntegrations(workspace, selected, '
|
|
56
|
+
const changed = await addIntegrations(workspace, selected, 'smart');
|
|
56
57
|
console.log(`Configured integrations: ${changed.map((item) => `${item.name}:${item.mode}`).join(', ')}`);
|
|
57
58
|
config = await loadConfig(workspace);
|
|
58
59
|
recommendedIntegrations = recommendedIntegrationsForInit({
|
|
@@ -25,7 +25,7 @@ export function registerIntegrateCommand(program) {
|
|
|
25
25
|
.command('add')
|
|
26
26
|
.description('Add AI tool integrations')
|
|
27
27
|
.argument('<names...>')
|
|
28
|
-
.option('--mode <mode>', 'always, smart, manual, or off', '
|
|
28
|
+
.option('--mode <mode>', 'always, smart, manual, or off', 'smart')
|
|
29
29
|
.action((names, options) => runCommand(async () => {
|
|
30
30
|
const workspace = await assertWorkspace(process.cwd());
|
|
31
31
|
const normalized = normalizeIntegrationNames(names);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { Command } from
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
2
|
export declare function registerScanCommand(program: Command): void;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { refreshCognitionReferenceStatuses } from
|
|
2
|
-
import { loadConfig } from
|
|
3
|
-
import { scanRepository } from
|
|
4
|
-
import { assertWorkspace } from
|
|
5
|
-
import { readMaps, writeMaps } from
|
|
6
|
-
import { runCommand } from
|
|
1
|
+
import { refreshCognitionReferenceStatuses } from '../../cognition/cognition-updater.js';
|
|
2
|
+
import { loadConfig } from '../../config/config.js';
|
|
3
|
+
import { scanRepository } from '../../scanner/repo-scanner.js';
|
|
4
|
+
import { assertWorkspace } from '../../storage/kgraph-paths.js';
|
|
5
|
+
import { readMaps, writeMaps } from '../../storage/map-store.js';
|
|
6
|
+
import { runCommand } from '../errors.js';
|
|
7
7
|
export function registerScanCommand(program) {
|
|
8
8
|
program
|
|
9
|
-
.command(
|
|
10
|
-
.description(
|
|
11
|
-
.option(
|
|
9
|
+
.command('scan')
|
|
10
|
+
.description('Scan the repository into deterministic KGraph maps')
|
|
11
|
+
.option('--verbose', 'Print scan warnings')
|
|
12
12
|
.action((options) => runCommand(async () => {
|
|
13
13
|
const workspace = await assertWorkspace(process.cwd());
|
|
14
14
|
const config = await loadConfig(workspace);
|
|
@@ -18,10 +18,14 @@ export function registerScanCommand(program) {
|
|
|
18
18
|
symbols: previousMaps.symbolMap.symbols,
|
|
19
19
|
dependencies: previousMaps.dependencyMap.dependencies,
|
|
20
20
|
relationships: previousMaps.relationshipMap.relationships,
|
|
21
|
-
warnings: []
|
|
21
|
+
warnings: [],
|
|
22
|
+
scannedAtCommit: previousMaps.fileMap.scannedAtCommit,
|
|
22
23
|
});
|
|
23
24
|
await writeMaps(workspace, result);
|
|
24
|
-
await refreshCognitionReferenceStatuses(workspace, {
|
|
25
|
+
await refreshCognitionReferenceStatuses(workspace, {
|
|
26
|
+
files: result.files,
|
|
27
|
+
symbols: result.symbols,
|
|
28
|
+
});
|
|
25
29
|
console.log(`Scanned ${result.files.length} files and ${result.symbols.length} symbols.`);
|
|
26
30
|
if (options.verbose && result.warnings.length > 0) {
|
|
27
31
|
for (const warning of result.warnings) {
|
|
@@ -24,6 +24,7 @@ export async function runDefaultWorkflow(query) {
|
|
|
24
24
|
dependencies: previousMaps.dependencyMap.dependencies,
|
|
25
25
|
relationships: previousMaps.relationshipMap.relationships,
|
|
26
26
|
warnings: [],
|
|
27
|
+
scannedAtCommit: previousMaps.fileMap.scannedAtCommit,
|
|
27
28
|
});
|
|
28
29
|
await writeMaps(workspace, scan);
|
|
29
30
|
await refreshCognitionReferenceStatuses(workspace, {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getRecentlyCommittedFiles, getWorkingTreeChangesDetailed, isGitRepo, } from '../scanner/git-utils.js';
|
|
1
2
|
import { readCognitionNotes, readDomainRecords, } from '../storage/cognition-store.js';
|
|
2
3
|
import { rankByFields } from './ranking.js';
|
|
3
4
|
export async function queryContext(workspace, config, maps, query) {
|
|
@@ -109,6 +110,41 @@ export async function queryContext(workspace, config, maps, query) {
|
|
|
109
110
|
...dependenciesForImportedSymbol(symbol, maps.dependencyMap.dependencies),
|
|
110
111
|
],
|
|
111
112
|
}));
|
|
113
|
+
// Collect git changes: working-tree and recently committed files known to KGraph
|
|
114
|
+
const knownFilePaths = new Set(maps.fileMap.files.map((f) => f.path));
|
|
115
|
+
const gitChanges = [];
|
|
116
|
+
if (await isGitRepo(workspace.rootPath)) {
|
|
117
|
+
const workingTreeChanges = await getWorkingTreeChangesDetailed(workspace.rootPath);
|
|
118
|
+
for (const change of workingTreeChanges) {
|
|
119
|
+
if (!knownFilePaths.has(change.path))
|
|
120
|
+
continue;
|
|
121
|
+
const status = change.staged && !change.unstaged
|
|
122
|
+
? 'staged'
|
|
123
|
+
: change.unstaged && !change.staged
|
|
124
|
+
? 'unstaged'
|
|
125
|
+
: 'staged'; // both staged and unstaged → report as staged
|
|
126
|
+
gitChanges.push({
|
|
127
|
+
path: change.path,
|
|
128
|
+
status,
|
|
129
|
+
reason: change.staged && change.unstaged
|
|
130
|
+
? 'partially staged'
|
|
131
|
+
: status === 'staged'
|
|
132
|
+
? 'staged change'
|
|
133
|
+
: 'unstaged change',
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
const committedPaths = new Set(gitChanges.map((c) => c.path));
|
|
137
|
+
const recentCommitted = await getRecentlyCommittedFiles(workspace.rootPath);
|
|
138
|
+
for (const filePath of recentCommitted) {
|
|
139
|
+
if (!knownFilePaths.has(filePath) || committedPaths.has(filePath))
|
|
140
|
+
continue;
|
|
141
|
+
gitChanges.push({
|
|
142
|
+
path: filePath,
|
|
143
|
+
status: 'recent-commit',
|
|
144
|
+
reason: 'changed in recent commits',
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
112
148
|
return {
|
|
113
149
|
query,
|
|
114
150
|
matchedDomains,
|
|
@@ -119,6 +155,7 @@ export async function queryContext(workspace, config, maps, query) {
|
|
|
119
155
|
relationshipExplanations: relationshipExplanations.slice(0, max),
|
|
120
156
|
nearbySymbols,
|
|
121
157
|
nearbySymbolExplanations,
|
|
158
|
+
gitChanges,
|
|
122
159
|
staleReferences,
|
|
123
160
|
warnings: [],
|
|
124
161
|
};
|
|
@@ -130,7 +167,8 @@ function explainRelationships(relationships, context) {
|
|
|
130
167
|
]));
|
|
131
168
|
return relationships.map((relationship) => {
|
|
132
169
|
const reasons = new Set();
|
|
133
|
-
for (const reason of rankedReasons.get(relationshipKey(relationship)) ??
|
|
170
|
+
for (const reason of rankedReasons.get(relationshipKey(relationship)) ??
|
|
171
|
+
[]) {
|
|
134
172
|
reasons.add(reason);
|
|
135
173
|
}
|
|
136
174
|
for (const file of context.relevantFiles) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { numberedWorkflow } from '../workflow-steps.js';
|
|
1
2
|
export const claudeCodeAdapter = {
|
|
2
3
|
name: 'claude-code',
|
|
3
4
|
label: 'Claude Code',
|
|
@@ -11,18 +12,7 @@ export const claudeCodeAdapter = {
|
|
|
11
12
|
path: '.claude/commands/kgraph.md',
|
|
12
13
|
content: `Use KGraph persistent repo intelligence for the current request.
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
2. {{KGRAPH_CONTEXT_POLICY}}
|
|
16
|
-
3. Use the returned files, symbols, relationships, and cognition before broad exploration.
|
|
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. Track meaningful session activity with \`kgraph session start --agent claude-code\`, \`kgraph session read <path> --agent claude-code\`, \`kgraph session write <path> --agent claude-code\`, and \`kgraph session end --agent claude-code\` when native hooks are unavailable.
|
|
19
|
-
6. 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.
|
|
20
|
-
|
|
21
|
-
{{KGRAPH_CAPTURE_POLICY}}
|
|
22
|
-
|
|
23
|
-
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.
|
|
24
|
-
8. Run \`kgraph visualize\` when the user wants to inspect the dependency graph — opens an interactive graph at http://localhost:4242 with PNG export.
|
|
25
|
-
9. Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.
|
|
15
|
+
${numberedWorkflow('claude-code', { sessionQualifier: 'when native hooks are unavailable' })}
|
|
26
16
|
`,
|
|
27
17
|
},
|
|
28
18
|
{
|
|
@@ -1,16 +1,10 @@
|
|
|
1
|
+
import { bulletWorkflow } from '../workflow-steps.js';
|
|
1
2
|
export const clineAdapter = {
|
|
2
3
|
name: 'cline',
|
|
3
4
|
label: 'Cline',
|
|
4
5
|
targetPath: '.clinerules/kgraph.md',
|
|
5
6
|
instructions: `# KGraph Workflow
|
|
6
7
|
|
|
7
|
-
|
|
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
|
-
- Track meaningful session activity with \`kgraph session start --agent cline\`, \`kgraph session read <path> --agent cline\`, \`kgraph session write <path> --agent cline\`, and \`kgraph session end --agent cline\`.
|
|
10
|
-
- 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.
|
|
11
|
-
{{KGRAPH_CAPTURE_POLICY}}
|
|
12
|
-
- 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.
|
|
13
|
-
- Run \`kgraph visualize\` to open the interactive dependency graph at http://localhost:4242 with PNG export.
|
|
14
|
-
- Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.
|
|
8
|
+
${bulletWorkflow('cline')}
|
|
15
9
|
`,
|
|
16
10
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { numberedWorkflow } from '../workflow-steps.js';
|
|
1
2
|
export const codexAdapter = {
|
|
2
3
|
name: 'codex',
|
|
3
4
|
label: 'Codex',
|
|
@@ -18,23 +19,12 @@ description: Use KGraph persistent repo intelligence according to the configured
|
|
|
18
19
|
|
|
19
20
|
Workflow:
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
2. {{KGRAPH_CONTEXT_POLICY}}
|
|
23
|
-
3. Use KGraph's returned files, symbols, relationships, and cognition as navigation hints.
|
|
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. Track meaningful session activity with \`kgraph session start --agent codex\`, \`kgraph session read <path> --agent codex\`, \`kgraph session write <path> --agent codex\`, and \`kgraph session end --agent codex\`.
|
|
26
|
-
6. 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.
|
|
27
|
-
|
|
28
|
-
{{KGRAPH_CAPTURE_POLICY}}
|
|
29
|
-
|
|
30
|
-
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.
|
|
31
|
-
8. Run \`kgraph visualize\` when the user wants to inspect the dependency graph — opens an interactive graph at http://localhost:4242 with PNG export.
|
|
32
|
-
9. Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.
|
|
22
|
+
${numberedWorkflow('codex')}
|
|
33
23
|
`,
|
|
34
24
|
},
|
|
35
25
|
],
|
|
36
26
|
obsoleteCommandFiles: [
|
|
37
|
-
'.agents/skills/kgraph-
|
|
38
|
-
'.agents/skills/kgraph-
|
|
27
|
+
'.agents/skills/kgraph-scan/SKILL.md',
|
|
28
|
+
'.agents/skills/kgraph-update/SKILL.md',
|
|
39
29
|
],
|
|
40
30
|
};
|
|
@@ -1,19 +1,33 @@
|
|
|
1
|
+
import { numberedWorkflow } from '../workflow-steps.js';
|
|
1
2
|
export const copilotAdapter = {
|
|
2
3
|
name: 'copilot',
|
|
3
4
|
label: 'GitHub Copilot',
|
|
4
5
|
targetPath: '.github/copilot-instructions.md',
|
|
5
6
|
instructions: `## KGraph Workflow
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
${numberedWorkflow('copilot')}
|
|
9
|
+
`,
|
|
10
|
+
commandFiles: [
|
|
11
|
+
{
|
|
12
|
+
path: '.github/agents/kgraph.agent.md',
|
|
13
|
+
content: `---
|
|
14
|
+
name: kgraph
|
|
15
|
+
description: Use KGraph persistent repo intelligence to answer questions about this codebase. Runs kgraph context, scan, update, impact, history, and session commands to ground responses in durable local knowledge.
|
|
16
|
+
tools:
|
|
17
|
+
- run_in_terminal
|
|
18
|
+
- read_file
|
|
19
|
+
- file_search
|
|
20
|
+
- grep_search
|
|
21
|
+
- semantic_search
|
|
22
|
+
---
|
|
11
23
|
|
|
12
|
-
|
|
24
|
+
## KGraph Agent
|
|
25
|
+
|
|
26
|
+
You are a KGraph-powered agent. Before exploring the repository freely, always:
|
|
13
27
|
|
|
14
|
-
|
|
28
|
+
${numberedWorkflow('copilot')}
|
|
15
29
|
`,
|
|
16
|
-
|
|
30
|
+
},
|
|
17
31
|
{
|
|
18
32
|
path: '.github/prompts/kgraph-doctor.prompt.md',
|
|
19
33
|
content: `---
|
|
@@ -112,5 +126,8 @@ Run \`kgraph history\` or \`kgraph history "$ARGUMENTS"\` to display processed c
|
|
|
112
126
|
`,
|
|
113
127
|
},
|
|
114
128
|
],
|
|
115
|
-
obsoleteCommandFiles: [
|
|
129
|
+
obsoleteCommandFiles: [
|
|
130
|
+
'.github/prompts/kgraph.prompt.md',
|
|
131
|
+
'.github/kgraph.agent.md',
|
|
132
|
+
],
|
|
116
133
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bulletWorkflow } from '../workflow-steps.js';
|
|
1
2
|
export const cursorAdapter = {
|
|
2
3
|
name: 'cursor',
|
|
3
4
|
label: 'Cursor',
|
|
@@ -9,14 +10,7 @@ alwaysApply: true
|
|
|
9
10
|
|
|
10
11
|
## KGraph Workflow
|
|
11
12
|
|
|
12
|
-
|
|
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
|
-
- Track meaningful session activity with \`kgraph session start --agent cursor\`, \`kgraph session read <path> --agent cursor\`, \`kgraph session write <path> --agent cursor\`, and \`kgraph session end --agent cursor\`.
|
|
15
|
-
- 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.
|
|
16
|
-
{{KGRAPH_CAPTURE_POLICY}}
|
|
17
|
-
- 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.
|
|
18
|
-
- Run \`kgraph visualize\` to open the interactive dependency graph at http://localhost:4242 with PNG export.
|
|
19
|
-
- Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.
|
|
13
|
+
${bulletWorkflow('cursor')}
|
|
20
14
|
`,
|
|
21
15
|
obsoleteCommandFiles: ['.cursor/rules/kgraph-commands.mdc'],
|
|
22
16
|
};
|
|
@@ -1,16 +1,10 @@
|
|
|
1
|
+
import { bulletWorkflow } from '../workflow-steps.js';
|
|
1
2
|
export const geminiAdapter = {
|
|
2
3
|
name: 'gemini',
|
|
3
4
|
label: 'Gemini CLI',
|
|
4
5
|
targetPath: 'GEMINI.md',
|
|
5
6
|
instructions: `## KGraph Workflow
|
|
6
7
|
|
|
7
|
-
|
|
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
|
-
- Track meaningful session activity with \`kgraph session start --agent gemini\`, \`kgraph session read <path> --agent gemini\`, \`kgraph session write <path> --agent gemini\`, and \`kgraph session end --agent gemini\`.
|
|
10
|
-
- 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.
|
|
11
|
-
{{KGRAPH_CAPTURE_POLICY}}
|
|
12
|
-
- 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.
|
|
13
|
-
- Run \`kgraph visualize\` to open the interactive dependency graph at http://localhost:4242 with PNG export.
|
|
14
|
-
- Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.
|
|
8
|
+
${bulletWorkflow('gemini')}
|
|
15
9
|
`,
|
|
16
10
|
};
|
|
@@ -1,16 +1,10 @@
|
|
|
1
|
+
import { bulletWorkflow } from '../workflow-steps.js';
|
|
1
2
|
export const windsurfAdapter = {
|
|
2
3
|
name: 'windsurf',
|
|
3
4
|
label: 'Windsurf',
|
|
4
5
|
targetPath: '.windsurf/rules/kgraph.md',
|
|
5
6
|
instructions: `# KGraph Workflow
|
|
6
7
|
|
|
7
|
-
|
|
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
|
-
- Track meaningful session activity with \`kgraph session start --agent windsurf\`, \`kgraph session read <path> --agent windsurf\`, \`kgraph session write <path> --agent windsurf\`, and \`kgraph session end --agent windsurf\`.
|
|
10
|
-
- 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.
|
|
11
|
-
{{KGRAPH_CAPTURE_POLICY}}
|
|
12
|
-
- 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.
|
|
13
|
-
- Run \`kgraph visualize\` to open the interactive dependency graph at http://localhost:4242 with PNG export.
|
|
14
|
-
- Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.
|
|
8
|
+
${bulletWorkflow('windsurf')}
|
|
15
9
|
`,
|
|
16
10
|
};
|
|
@@ -15,7 +15,7 @@ export async function listIntegrations(workspace) {
|
|
|
15
15
|
})));
|
|
16
16
|
return statuses.sort((left, right) => left.name.localeCompare(right.name));
|
|
17
17
|
}
|
|
18
|
-
export async function addIntegrations(workspace, names, mode = '
|
|
18
|
+
export async function addIntegrations(workspace, names, mode = 'smart') {
|
|
19
19
|
const config = await loadConfig(workspace);
|
|
20
20
|
const byName = new Map(config.integrations.map((integration) => [integration.name, integration]));
|
|
21
21
|
const changed = [];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared KGraph workflow step strings used across all AI tool adapters.
|
|
3
|
+
* Update here once instead of in each adapter file.
|
|
4
|
+
*/
|
|
5
|
+
export interface WorkflowOptions {
|
|
6
|
+
/** Qualifier appended to the session step, e.g. "when native hooks are unavailable" */
|
|
7
|
+
sessionQualifier?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Returns the 9-step numbered workflow for skill/agent/command files.
|
|
11
|
+
* Used by: copilot (agent file), codex (skill file), claude-code (command file).
|
|
12
|
+
*/
|
|
13
|
+
export declare function numberedWorkflow(agentName: string, options?: WorkflowOptions): string;
|
|
14
|
+
/**
|
|
15
|
+
* Returns the bullet-list workflow for rules files.
|
|
16
|
+
* Used by: cursor, cline, windsurf, gemini.
|
|
17
|
+
*/
|
|
18
|
+
export declare function bulletWorkflow(agentName: string, options?: WorkflowOptions): string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared KGraph workflow step strings used across all AI tool adapters.
|
|
3
|
+
* Update here once instead of in each adapter file.
|
|
4
|
+
*/
|
|
5
|
+
const DOCTOR_STEP = `Run \`kgraph doctor\` when setup, maps, inbox processing, or integrations look wrong. Run \`kgraph doctor --quality\` when context shows stale/noisy cognition references.`;
|
|
6
|
+
const IMPACT_STEP = `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.`;
|
|
7
|
+
const REPAIR_STEP = `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.`;
|
|
8
|
+
const HISTORY_STEP = `Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.`;
|
|
9
|
+
function sessionStep(agentName, qualifier) {
|
|
10
|
+
const base = `Track meaningful session activity with \`kgraph session start --agent ${agentName}\`, \`kgraph session read <path> --agent ${agentName}\`, \`kgraph session write <path> --agent ${agentName}\`, and \`kgraph session end --agent ${agentName}\``;
|
|
11
|
+
return qualifier ? `${base} ${qualifier}.` : `${base}.`;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Returns the 9-step numbered workflow for skill/agent/command files.
|
|
15
|
+
* Used by: copilot (agent file), codex (skill file), claude-code (command file).
|
|
16
|
+
*/
|
|
17
|
+
export function numberedWorkflow(agentName, options = {}) {
|
|
18
|
+
return `1. Infer the topic from the user's request.
|
|
19
|
+
2. {{KGRAPH_CONTEXT_POLICY}}
|
|
20
|
+
3. Use the returned files, symbols, relationships, and cognition before broad exploration.
|
|
21
|
+
4. ${DOCTOR_STEP}
|
|
22
|
+
5. ${sessionStep(agentName, options.sessionQualifier)}
|
|
23
|
+
6. ${IMPACT_STEP}
|
|
24
|
+
|
|
25
|
+
{{KGRAPH_CAPTURE_POLICY}}
|
|
26
|
+
|
|
27
|
+
7. ${REPAIR_STEP}
|
|
28
|
+
8. Run \`kgraph visualize\` when the user wants to inspect the dependency graph — opens an interactive graph at http://localhost:4242 with PNG export.
|
|
29
|
+
9. ${HISTORY_STEP}`;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Returns the bullet-list workflow for rules files.
|
|
33
|
+
* Used by: cursor, cline, windsurf, gemini.
|
|
34
|
+
*/
|
|
35
|
+
export function bulletWorkflow(agentName, options = {}) {
|
|
36
|
+
return `- {{KGRAPH_CONTEXT_POLICY}}
|
|
37
|
+
- ${DOCTOR_STEP}
|
|
38
|
+
- ${sessionStep(agentName, options.sessionQualifier)}
|
|
39
|
+
- ${IMPACT_STEP}
|
|
40
|
+
{{KGRAPH_CAPTURE_POLICY}}
|
|
41
|
+
- ${REPAIR_STEP}
|
|
42
|
+
- Run \`kgraph visualize\` to open the interactive dependency graph at http://localhost:4242 with PNG export.
|
|
43
|
+
- ${HISTORY_STEP}`;
|
|
44
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns true if the given directory is inside a git repository.
|
|
3
|
+
* Uses a fast filesystem check rather than spawning a process.
|
|
4
|
+
*/
|
|
5
|
+
export declare function isGitRepo(rootPath: string): Promise<boolean>;
|
|
6
|
+
/**
|
|
7
|
+
* Returns the current HEAD commit hash, or null if unavailable.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getCurrentCommit(rootPath: string): Promise<string | null>;
|
|
10
|
+
/**
|
|
11
|
+
* Returns paths of files changed between the given ref and HEAD.
|
|
12
|
+
* Returns an empty array if git is unavailable or the ref is unknown.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getChangedFilesSince(rootPath: string, ref: string): Promise<string[]>;
|
|
15
|
+
/**
|
|
16
|
+
* Returns paths of files with uncommitted changes (staged or unstaged)
|
|
17
|
+
* relative to HEAD. Returns an empty array if git is unavailable.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getWorkingTreeChanges(rootPath: string): Promise<string[]>;
|
|
20
|
+
export type WorkingTreeChange = {
|
|
21
|
+
path: string;
|
|
22
|
+
staged: boolean;
|
|
23
|
+
unstaged: boolean;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Returns working-tree changes with staged/unstaged flags.
|
|
27
|
+
* Returns an empty array if git is unavailable.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getWorkingTreeChangesDetailed(rootPath: string): Promise<WorkingTreeChange[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Returns up to `limit` files changed in the most recent commits
|
|
32
|
+
* (excluding uncommitted changes). Returns an empty array if git is unavailable.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getRecentlyCommittedFiles(rootPath: string, limit?: number): Promise<string[]>;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { access } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
|
+
/**
|
|
7
|
+
* Returns true if the given directory is inside a git repository.
|
|
8
|
+
* Uses a fast filesystem check rather than spawning a process.
|
|
9
|
+
*/
|
|
10
|
+
export async function isGitRepo(rootPath) {
|
|
11
|
+
try {
|
|
12
|
+
await access(path.join(rootPath, '.git'));
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Returns the current HEAD commit hash, or null if unavailable.
|
|
21
|
+
*/
|
|
22
|
+
export async function getCurrentCommit(rootPath) {
|
|
23
|
+
try {
|
|
24
|
+
const { stdout } = await execFileAsync('git', ['rev-parse', 'HEAD'], {
|
|
25
|
+
cwd: rootPath,
|
|
26
|
+
});
|
|
27
|
+
return stdout.trim() || null;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Returns paths of files changed between the given ref and HEAD.
|
|
35
|
+
* Returns an empty array if git is unavailable or the ref is unknown.
|
|
36
|
+
*/
|
|
37
|
+
export async function getChangedFilesSince(rootPath, ref) {
|
|
38
|
+
try {
|
|
39
|
+
const { stdout } = await execFileAsync('git', ['diff', '--name-only', ref, 'HEAD'], { cwd: rootPath });
|
|
40
|
+
return stdout
|
|
41
|
+
.trim()
|
|
42
|
+
.split('\n')
|
|
43
|
+
.map((line) => line.trim())
|
|
44
|
+
.filter(Boolean);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Returns paths of files with uncommitted changes (staged or unstaged)
|
|
52
|
+
* relative to HEAD. Returns an empty array if git is unavailable.
|
|
53
|
+
*/
|
|
54
|
+
export async function getWorkingTreeChanges(rootPath) {
|
|
55
|
+
try {
|
|
56
|
+
const [stagedResult, unstagedResult] = await Promise.all([
|
|
57
|
+
execFileAsync('git', ['diff', '--name-only', '--cached'], {
|
|
58
|
+
cwd: rootPath,
|
|
59
|
+
}),
|
|
60
|
+
execFileAsync('git', ['diff', '--name-only'], { cwd: rootPath }),
|
|
61
|
+
]);
|
|
62
|
+
const staged = stagedResult.stdout
|
|
63
|
+
.trim()
|
|
64
|
+
.split('\n')
|
|
65
|
+
.map((line) => line.trim())
|
|
66
|
+
.filter(Boolean);
|
|
67
|
+
const unstaged = unstagedResult.stdout
|
|
68
|
+
.trim()
|
|
69
|
+
.split('\n')
|
|
70
|
+
.map((line) => line.trim())
|
|
71
|
+
.filter(Boolean);
|
|
72
|
+
// Deduplicate: a file can appear in both if partially staged
|
|
73
|
+
return [...new Set([...staged, ...unstaged])];
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Returns working-tree changes with staged/unstaged flags.
|
|
81
|
+
* Returns an empty array if git is unavailable.
|
|
82
|
+
*/
|
|
83
|
+
export async function getWorkingTreeChangesDetailed(rootPath) {
|
|
84
|
+
try {
|
|
85
|
+
const [stagedResult, unstagedResult] = await Promise.all([
|
|
86
|
+
execFileAsync('git', ['diff', '--name-only', '--cached'], {
|
|
87
|
+
cwd: rootPath,
|
|
88
|
+
}),
|
|
89
|
+
execFileAsync('git', ['diff', '--name-only'], { cwd: rootPath }),
|
|
90
|
+
]);
|
|
91
|
+
const staged = new Set(stagedResult.stdout
|
|
92
|
+
.trim()
|
|
93
|
+
.split('\n')
|
|
94
|
+
.map((line) => line.trim())
|
|
95
|
+
.filter(Boolean));
|
|
96
|
+
const unstaged = new Set(unstagedResult.stdout
|
|
97
|
+
.trim()
|
|
98
|
+
.split('\n')
|
|
99
|
+
.map((line) => line.trim())
|
|
100
|
+
.filter(Boolean));
|
|
101
|
+
const all = new Set([...staged, ...unstaged]);
|
|
102
|
+
return [...all].map((filePath) => ({
|
|
103
|
+
path: filePath,
|
|
104
|
+
staged: staged.has(filePath),
|
|
105
|
+
unstaged: unstaged.has(filePath),
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Returns up to `limit` files changed in the most recent commits
|
|
114
|
+
* (excluding uncommitted changes). Returns an empty array if git is unavailable.
|
|
115
|
+
*/
|
|
116
|
+
export async function getRecentlyCommittedFiles(rootPath, limit = 5) {
|
|
117
|
+
try {
|
|
118
|
+
// git log --name-only gives files touched in each of the last N commits
|
|
119
|
+
const { stdout } = await execFileAsync('git', ['log', '--name-only', '--pretty=format:', `-n`, String(limit), 'HEAD'], { cwd: rootPath });
|
|
120
|
+
return [
|
|
121
|
+
...new Set(stdout
|
|
122
|
+
.split('\n')
|
|
123
|
+
.map((line) => line.trim())
|
|
124
|
+
.filter(Boolean)),
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { KGraphConfig } from '../types/config.js';
|
|
2
2
|
import type { ScanResult } from '../types/maps.js';
|
|
3
|
-
export declare function scanRepository(rootPath: string, config: KGraphConfig, previous?: ScanResult
|
|
3
|
+
export declare function scanRepository(rootPath: string, config: KGraphConfig, previous?: ScanResult & {
|
|
4
|
+
scannedAtCommit?: string;
|
|
5
|
+
}): Promise<ScanResult>;
|
|
@@ -6,6 +6,7 @@ import { estimateTokens } from '../session/token-estimator.js';
|
|
|
6
6
|
import { extractCSymbols } from './c-symbol-extractor.js';
|
|
7
7
|
import { extractCSharpSymbols } from './csharp-symbol-extractor.js';
|
|
8
8
|
import { buildFastGlobIgnore, detectLanguage, isPreciseLanguage, readGitignorePatterns, shouldExclude, } from './file-classifier.js';
|
|
9
|
+
import { getChangedFilesSince, isGitRepo } from './git-utils.js';
|
|
9
10
|
import { extractGoSymbols } from './go-symbol-extractor.js';
|
|
10
11
|
import { extractJvmSymbols } from './jvm-symbol-extractor.js';
|
|
11
12
|
import { extractPythonSymbols } from './python-symbol-extractor.js';
|
|
@@ -71,6 +72,16 @@ export async function scanRepository(rootPath, config, previous) {
|
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
}
|
|
75
|
+
// Build git-changed-file set for fast incremental skip bypassing stat() calls.
|
|
76
|
+
// Only used when the previous scan stored a commit hash and we are in a git repo.
|
|
77
|
+
let gitChangedFiles = null;
|
|
78
|
+
if (previous?.scannedAtCommit && (await isGitRepo(rootPath))) {
|
|
79
|
+
const changed = await getChangedFilesSince(rootPath, previous.scannedAtCommit);
|
|
80
|
+
// Also include working-tree dirty files so uncommitted edits are always re-scanned.
|
|
81
|
+
// getChangedFilesSince returns committed changes; we complement with a stat check
|
|
82
|
+
// fallback below for uncommitted edits to keep correctness.
|
|
83
|
+
gitChangedFiles = new Set(changed);
|
|
84
|
+
}
|
|
74
85
|
const files = [];
|
|
75
86
|
const symbols = [];
|
|
76
87
|
const dependencies = [];
|
|
@@ -83,9 +94,34 @@ export async function scanRepository(rootPath, config, previous) {
|
|
|
83
94
|
}
|
|
84
95
|
const absolutePath = path.join(rootPath, repoPath);
|
|
85
96
|
try {
|
|
86
|
-
const info = await stat(absolutePath);
|
|
87
|
-
// Incremental skip: if mtime and size match previous, carry forward
|
|
88
97
|
const prevFile = prevFileByPath.get(repoPath);
|
|
98
|
+
// Fast git-based skip: if we have a git-changed set and this file is NOT in it,
|
|
99
|
+
// carry forward directly without touching the filesystem.
|
|
100
|
+
if (prevFile &&
|
|
101
|
+
gitChangedFiles !== null &&
|
|
102
|
+
!gitChangedFiles.has(repoPath)) {
|
|
103
|
+
files.push(prevFile);
|
|
104
|
+
const prevSyms = prevSymbolsByFile.get(repoPath);
|
|
105
|
+
if (prevSyms)
|
|
106
|
+
symbols.push(...prevSyms);
|
|
107
|
+
const prevDeps = prevDepsByFile.get(repoPath);
|
|
108
|
+
if (prevDeps)
|
|
109
|
+
dependencies.push(...prevDeps);
|
|
110
|
+
const prevRels = prevRelsBySource.get(repoPath);
|
|
111
|
+
if (prevRels)
|
|
112
|
+
relationships.push(...prevRels);
|
|
113
|
+
if (prevSyms) {
|
|
114
|
+
for (const sym of prevSyms) {
|
|
115
|
+
const symRels = prevRelsBySource.get(sym.id);
|
|
116
|
+
if (symRels)
|
|
117
|
+
relationships.push(...symRels);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
skippedFiles++;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const info = await stat(absolutePath);
|
|
124
|
+
// Fallback incremental skip: if mtime and size match previous, carry forward
|
|
89
125
|
if (prevFile &&
|
|
90
126
|
prevFile.sizeBytes === info.size &&
|
|
91
127
|
prevFile.modifiedAt === info.mtime.toISOString()) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { KGraphWorkspace } from
|
|
2
|
-
import type { DependencyMap, FileMap, RelationshipMap, ScanResult, SymbolMap } from
|
|
1
|
+
import type { KGraphWorkspace } from '../types/config.js';
|
|
2
|
+
import type { DependencyMap, FileMap, RelationshipMap, ScanResult, SymbolMap } from '../types/maps.js';
|
|
3
3
|
export declare function mapPaths(workspace: KGraphWorkspace): Record<string, string>;
|
|
4
4
|
export declare function readMaps(workspace: KGraphWorkspace): Promise<{
|
|
5
5
|
fileMap: FileMap;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { readFile, writeFile } from
|
|
2
|
-
import path from
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { KGraphError } from '../cli/errors.js';
|
|
4
|
+
import { getCurrentCommit } from '../scanner/git-utils.js';
|
|
5
|
+
import { pathExists } from './kgraph-paths.js';
|
|
5
6
|
async function readJson(filePath, fallback) {
|
|
6
7
|
if (!(await pathExists(filePath))) {
|
|
7
8
|
return fallback;
|
|
8
9
|
}
|
|
9
10
|
try {
|
|
10
|
-
return JSON.parse(await readFile(filePath,
|
|
11
|
+
return JSON.parse(await readFile(filePath, 'utf8'));
|
|
11
12
|
}
|
|
12
13
|
catch (error) {
|
|
13
14
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -15,40 +16,57 @@ async function readJson(filePath, fallback) {
|
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
18
|
async function writeJson(filePath, value) {
|
|
18
|
-
await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`,
|
|
19
|
+
await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
19
20
|
}
|
|
20
21
|
export function mapPaths(workspace) {
|
|
21
22
|
return {
|
|
22
|
-
files: path.join(workspace.mapPath,
|
|
23
|
-
symbols: path.join(workspace.mapPath,
|
|
24
|
-
dependencies: path.join(workspace.mapPath,
|
|
25
|
-
relationships: path.join(workspace.mapPath,
|
|
23
|
+
files: path.join(workspace.mapPath, 'files.json'),
|
|
24
|
+
symbols: path.join(workspace.mapPath, 'symbols.json'),
|
|
25
|
+
dependencies: path.join(workspace.mapPath, 'dependencies.json'),
|
|
26
|
+
relationships: path.join(workspace.mapPath, 'relationships.json'),
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
29
|
export async function readMaps(workspace) {
|
|
29
30
|
const paths = mapPaths(workspace);
|
|
30
31
|
return {
|
|
31
|
-
fileMap: await readJson(paths.files, {
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
fileMap: await readJson(paths.files, {
|
|
33
|
+
generatedAt: '',
|
|
34
|
+
files: [],
|
|
35
|
+
}),
|
|
36
|
+
symbolMap: await readJson(paths.symbols, {
|
|
37
|
+
generatedAt: '',
|
|
38
|
+
symbols: [],
|
|
39
|
+
}),
|
|
40
|
+
dependencyMap: await readJson(paths.dependencies, {
|
|
41
|
+
generatedAt: '',
|
|
42
|
+
dependencies: [],
|
|
43
|
+
}),
|
|
34
44
|
relationshipMap: await readJson(paths.relationships, {
|
|
35
|
-
generatedAt:
|
|
36
|
-
relationships: []
|
|
37
|
-
})
|
|
45
|
+
generatedAt: '',
|
|
46
|
+
relationships: [],
|
|
47
|
+
}),
|
|
38
48
|
};
|
|
39
49
|
}
|
|
40
50
|
export async function writeMaps(workspace, result) {
|
|
41
51
|
const generatedAt = new Date().toISOString();
|
|
52
|
+
const scannedAtCommit = (await getCurrentCommit(workspace.rootPath)) ?? undefined;
|
|
42
53
|
const paths = mapPaths(workspace);
|
|
43
|
-
await writeJson(paths.files, {
|
|
44
|
-
|
|
54
|
+
await writeJson(paths.files, {
|
|
55
|
+
generatedAt,
|
|
56
|
+
scannedAtCommit,
|
|
57
|
+
files: result.files,
|
|
58
|
+
});
|
|
59
|
+
await writeJson(paths.symbols, {
|
|
60
|
+
generatedAt,
|
|
61
|
+
symbols: result.symbols,
|
|
62
|
+
});
|
|
45
63
|
await writeJson(paths.dependencies, {
|
|
46
64
|
generatedAt,
|
|
47
|
-
dependencies: result.dependencies
|
|
65
|
+
dependencies: result.dependencies,
|
|
48
66
|
});
|
|
49
67
|
await writeJson(paths.relationships, {
|
|
50
68
|
generatedAt,
|
|
51
|
-
relationships: result.relationships
|
|
69
|
+
relationships: result.relationships,
|
|
52
70
|
});
|
|
53
71
|
}
|
|
54
72
|
export async function mapsExist(workspace) {
|
|
@@ -31,6 +31,12 @@ export interface RankedItem<T> {
|
|
|
31
31
|
score: number;
|
|
32
32
|
reasons: string[];
|
|
33
33
|
}
|
|
34
|
+
export type GitChangeStatus = 'staged' | 'unstaged' | 'recent-commit';
|
|
35
|
+
export interface GitContextChange {
|
|
36
|
+
path: string;
|
|
37
|
+
status: GitChangeStatus;
|
|
38
|
+
reason: string;
|
|
39
|
+
}
|
|
34
40
|
export interface ContextResponse {
|
|
35
41
|
query: string;
|
|
36
42
|
matchedDomains: RankedItem<DomainRecord>[];
|
|
@@ -47,6 +53,7 @@ export interface ContextResponse {
|
|
|
47
53
|
symbol: CodeSymbol;
|
|
48
54
|
reasons: string[];
|
|
49
55
|
}>;
|
|
56
|
+
gitChanges?: GitContextChange[];
|
|
50
57
|
staleReferences: string[];
|
|
51
58
|
warnings: string[];
|
|
52
59
|
}
|
package/dist/types/maps.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export interface Relationship {
|
|
|
40
40
|
}
|
|
41
41
|
export interface FileMap {
|
|
42
42
|
generatedAt: string;
|
|
43
|
+
scannedAtCommit?: string;
|
|
43
44
|
files: RepositoryFile[];
|
|
44
45
|
}
|
|
45
46
|
export interface SymbolMap {
|
|
@@ -61,4 +62,5 @@ export interface ScanResult {
|
|
|
61
62
|
relationships: Relationship[];
|
|
62
63
|
warnings: string[];
|
|
63
64
|
skippedFiles?: number;
|
|
65
|
+
scannedAtCommit?: string;
|
|
64
66
|
}
|