@graphpilot-oss/graphpilot 0.0.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/.editorconfig +15 -0
- package/.github/CODEOWNERS +22 -0
- package/.github/FUNDING.yml +1 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +19 -0
- package/.github/dependabot.yml +15 -0
- package/.github/workflows/ci.yml +62 -0
- package/.github/workflows/release.yml +50 -0
- package/.prettierignore +19 -0
- package/.prettierrc.json +20 -0
- package/CHANGELOG.md +138 -0
- package/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +111 -0
- package/LICENSE +201 -0
- package/README.md +132 -0
- package/SECURITY.md +44 -0
- package/assets/logo.png +0 -0
- package/assets/logo.svg +1 -0
- package/bench/README.md +544 -0
- package/bench/results/agent-tier-2026-05-22.md +28 -0
- package/bench/results/agent-tier-summary.md +44 -0
- package/bench/results/baseline-tier-2026-05-22.md +23 -0
- package/bench/results/baseline.json +810 -0
- package/bench/results/baseline.md +28 -0
- package/bench/run-agent-tier-automated.ts +234 -0
- package/bench/run-agent-tier.md +125 -0
- package/bench/run-baseline-tier.ts +200 -0
- package/bench/run.ts +210 -0
- package/bench/runner-baseline.ts +177 -0
- package/bench/runner-graphpilot.ts +131 -0
- package/bench/score-agent-tier.ts +191 -0
- package/bench/score.ts +59 -0
- package/bench/tasks.ts +236 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +162 -0
- package/dist/cli.js.map +1 -0
- package/dist/edges.d.ts +57 -0
- package/dist/edges.js +170 -0
- package/dist/edges.js.map +1 -0
- package/dist/git.d.ts +95 -0
- package/dist/git.js +247 -0
- package/dist/git.js.map +1 -0
- package/dist/graph-schema.d.ts +36 -0
- package/dist/graph-schema.js +208 -0
- package/dist/graph-schema.js.map +1 -0
- package/dist/impact.d.ts +99 -0
- package/dist/impact.js +123 -0
- package/dist/impact.js.map +1 -0
- package/dist/indexer.d.ts +28 -0
- package/dist/indexer.js +111 -0
- package/dist/indexer.js.map +1 -0
- package/dist/interactions.d.ts +46 -0
- package/dist/interactions.js +0 -0
- package/dist/interactions.js.map +1 -0
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.js +567 -0
- package/dist/mcp.js.map +1 -0
- package/dist/parser.d.ts +24 -0
- package/dist/parser.js +128 -0
- package/dist/parser.js.map +1 -0
- package/dist/provenance.d.ts +74 -0
- package/dist/provenance.js +95 -0
- package/dist/provenance.js.map +1 -0
- package/dist/query.d.ts +68 -0
- package/dist/query.js +127 -0
- package/dist/query.js.map +1 -0
- package/dist/redact.d.ts +30 -0
- package/dist/redact.js +117 -0
- package/dist/redact.js.map +1 -0
- package/dist/storage.d.ts +42 -0
- package/dist/storage.js +85 -0
- package/dist/storage.js.map +1 -0
- package/dist/symbols.d.ts +20 -0
- package/dist/symbols.js +140 -0
- package/dist/symbols.js.map +1 -0
- package/dist/validation.d.ts +9 -0
- package/dist/validation.js +65 -0
- package/dist/validation.js.map +1 -0
- package/dist/validators.d.ts +55 -0
- package/dist/validators.js +205 -0
- package/dist/validators.js.map +1 -0
- package/dist/watcher.d.ts +86 -0
- package/dist/watcher.js +310 -0
- package/dist/watcher.js.map +1 -0
- package/docs/architecture.md +311 -0
- package/docs/limitations.md +156 -0
- package/docs/mcp-setup.md +231 -0
- package/docs/quickstart.md +202 -0
- package/eslint.config.js +148 -0
- package/lefthook.yml +81 -0
- package/package.json +56 -0
- package/pnpm-workspace.yaml +6 -0
- package/scripts/smoke-stdio.mjs +97 -0
- package/src/cli.ts +171 -0
- package/src/edges.ts +202 -0
- package/src/git.ts +255 -0
- package/src/graph-schema.ts +229 -0
- package/src/impact.ts +218 -0
- package/src/indexer.ts +152 -0
- package/src/interactions.ts +0 -0
- package/src/mcp.ts +652 -0
- package/src/parser.ts +138 -0
- package/src/provenance.ts +115 -0
- package/src/query.ts +148 -0
- package/src/redact.ts +122 -0
- package/src/storage.ts +115 -0
- package/src/symbols.ts +173 -0
- package/src/validation.ts +69 -0
- package/src/validators.ts +253 -0
- package/src/watcher.ts +383 -0
- package/tests/edges.test.ts +175 -0
- package/tests/fixtures/sample.ts +32 -0
- package/tests/git.test.ts +303 -0
- package/tests/graph-schema.test.ts +321 -0
- package/tests/impact.test.ts +454 -0
- package/tests/interactions.test.ts +180 -0
- package/tests/lint-policy.test.ts +106 -0
- package/tests/mcp-stdio.test.ts +171 -0
- package/tests/mcp.test.ts +335 -0
- package/tests/parser.test.ts +31 -0
- package/tests/provenance.test.ts +132 -0
- package/tests/query.test.ts +160 -0
- package/tests/redact.test.ts +167 -0
- package/tests/security.test.ts +144 -0
- package/tests/symbols.test.ts +78 -0
- package/tests/validators.test.ts +193 -0
- package/tests/watcher.test.ts +250 -0
- package/tsconfig.json +18 -0
package/bench/score.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scoring: precision/recall/F1 of a returned set against ground truth.
|
|
3
|
+
*
|
|
4
|
+
* We score whether the set of names returned MATCHES the expected set,
|
|
5
|
+
* not whether it's identical — partial credit is honest. F1 is the
|
|
6
|
+
* primary metric. Per-task results show all three.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface Scored {
|
|
10
|
+
precision: number;
|
|
11
|
+
recall: number;
|
|
12
|
+
f1: number;
|
|
13
|
+
intersectionSize: number;
|
|
14
|
+
truePositives: string[];
|
|
15
|
+
falsePositives: string[];
|
|
16
|
+
falseNegatives: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function score(returned: string[], groundTruth: string[]): Scored {
|
|
20
|
+
const ret = new Set(returned);
|
|
21
|
+
const gt = new Set(groundTruth);
|
|
22
|
+
|
|
23
|
+
const tp: string[] = [];
|
|
24
|
+
for (const r of ret) if (gt.has(r)) tp.push(r);
|
|
25
|
+
|
|
26
|
+
const fp: string[] = [];
|
|
27
|
+
for (const r of ret) if (!gt.has(r)) fp.push(r);
|
|
28
|
+
|
|
29
|
+
const fn: string[] = [];
|
|
30
|
+
for (const g of gt) if (!ret.has(g)) fn.push(g);
|
|
31
|
+
|
|
32
|
+
// Edge case: empty ground truth + empty return = perfect.
|
|
33
|
+
// (The recall-miss task hits this.)
|
|
34
|
+
if (gt.size === 0 && ret.size === 0) {
|
|
35
|
+
return {
|
|
36
|
+
precision: 1,
|
|
37
|
+
recall: 1,
|
|
38
|
+
f1: 1,
|
|
39
|
+
intersectionSize: 0,
|
|
40
|
+
truePositives: [],
|
|
41
|
+
falsePositives: [],
|
|
42
|
+
falseNegatives: [],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const precision = ret.size === 0 ? 0 : tp.length / ret.size;
|
|
47
|
+
const recall = gt.size === 0 ? 0 : tp.length / gt.size;
|
|
48
|
+
const f1 = precision + recall === 0 ? 0 : (2 * precision * recall) / (precision + recall);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
precision,
|
|
52
|
+
recall,
|
|
53
|
+
f1,
|
|
54
|
+
intersectionSize: tp.length,
|
|
55
|
+
truePositives: tp.sort(),
|
|
56
|
+
falsePositives: fp.sort(),
|
|
57
|
+
falseNegatives: fn.sort(),
|
|
58
|
+
};
|
|
59
|
+
}
|
package/bench/tasks.ts
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 10-task benchmark corpus. Hand-curated against graphpilot indexing
|
|
3
|
+
* itself, so anyone can `git clone` + `pnpm install` + `pnpm bench` and
|
|
4
|
+
* see the same numbers.
|
|
5
|
+
*
|
|
6
|
+
* Each task carries its OWN ground truth so this file is the single
|
|
7
|
+
* source of truth for what "correct" means. Ground truth was computed
|
|
8
|
+
* by probing the live index at the time this file was authored; if the
|
|
9
|
+
* corpus repo (graphpilot itself) is materially edited, ground truth
|
|
10
|
+
* needs to be refreshed (see bench/README.md §Refreshing).
|
|
11
|
+
*
|
|
12
|
+
* Mix of task types is deliberate:
|
|
13
|
+
* - 7 tasks where GraphPilot's structural index should win
|
|
14
|
+
* - 1 task that's roughly a tie (negative result)
|
|
15
|
+
* - 1 task where grep should outperform GraphPilot (string-literal
|
|
16
|
+
* search). Keeping this in the corpus is what makes the benchmark
|
|
17
|
+
* honest.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export type TaskKind =
|
|
21
|
+
| 'callers' // who calls X?
|
|
22
|
+
| 'recall' // find symbol by exact name
|
|
23
|
+
| 'recall-substring' // find symbols whose name contains a fragment
|
|
24
|
+
| 'kind-filter' // find all symbols of kind=...
|
|
25
|
+
| 'impact' // blast-radius analysis
|
|
26
|
+
| 'impact-since' // differential impact: callers in changed files since a ref
|
|
27
|
+
| 'tests-affected' // which tests depend on this symbol
|
|
28
|
+
| 'recall-miss' // symbol that doesn't exist
|
|
29
|
+
| 'string-literal'; // text-only search — grep should win
|
|
30
|
+
|
|
31
|
+
export interface Task {
|
|
32
|
+
id: string;
|
|
33
|
+
description: string;
|
|
34
|
+
/** What a developer / agent would naturally ask. */
|
|
35
|
+
prompt: string;
|
|
36
|
+
kind: TaskKind;
|
|
37
|
+
/** Argument to the task's natural tool (graphpilot side). */
|
|
38
|
+
query: string;
|
|
39
|
+
/**
|
|
40
|
+
* Set of expected symbol names (sorted) that the correct answer must
|
|
41
|
+
* contain. For tests-affected tasks, this is the set of test file paths.
|
|
42
|
+
*/
|
|
43
|
+
groundTruth: string[];
|
|
44
|
+
/** Which side we expect to win on F1 score. */
|
|
45
|
+
expectedWinner: 'graphpilot' | 'grep' | 'tie';
|
|
46
|
+
/** Helpful for the README/results: structural vs text-only. */
|
|
47
|
+
difficulty: 'low' | 'medium' | 'high';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const TASKS: Task[] = [
|
|
51
|
+
{
|
|
52
|
+
id: 't01-callers-analyzeImpact',
|
|
53
|
+
description: 'Find every function that calls analyzeImpact',
|
|
54
|
+
prompt: 'In this repo, what functions call analyzeImpact?',
|
|
55
|
+
kind: 'callers',
|
|
56
|
+
query: 'analyzeImpact',
|
|
57
|
+
groundTruth: ['handleGpImpact'],
|
|
58
|
+
expectedWinner: 'graphpilot',
|
|
59
|
+
difficulty: 'low',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 't02-callers-extractSymbols',
|
|
63
|
+
description: 'Find every direct caller of extractSymbols',
|
|
64
|
+
prompt: 'Who calls extractSymbols in this codebase?',
|
|
65
|
+
kind: 'callers',
|
|
66
|
+
query: 'extractSymbols',
|
|
67
|
+
groundTruth: ['indexDirectory', 'applyUpdate', 'symbolsOf'],
|
|
68
|
+
expectedWinner: 'graphpilot',
|
|
69
|
+
difficulty: 'low',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 't03-callers-validateRootPath',
|
|
73
|
+
description: 'Find every direct caller of validateRootPath',
|
|
74
|
+
prompt: 'Where is validateRootPath used in the codebase? List every callsite.',
|
|
75
|
+
kind: 'callers',
|
|
76
|
+
query: 'validateRootPath',
|
|
77
|
+
// Note: GraphWatcher constructor calls validateRootPath; the SymbolRecord
|
|
78
|
+
// for that constructor has name="constructor" (not "GraphWatcher").
|
|
79
|
+
groundTruth: ['cmdIndex', 'main', 'handleGpIndex', 'constructor'],
|
|
80
|
+
expectedWinner: 'graphpilot',
|
|
81
|
+
difficulty: 'medium',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 't04-recall-substring-parse',
|
|
85
|
+
description: 'Find every symbol whose name contains "parse"',
|
|
86
|
+
prompt: 'List every function, class, or interface whose name contains "parse".',
|
|
87
|
+
kind: 'recall-substring',
|
|
88
|
+
query: 'parse',
|
|
89
|
+
groundTruth: ['ParsedFile', 'getParser', 'parseFile', 'parseSource', 'parseToken'],
|
|
90
|
+
expectedWinner: 'graphpilot',
|
|
91
|
+
difficulty: 'low',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 't05-kind-filter-interfaces',
|
|
95
|
+
description: 'Enumerate all TypeScript interfaces under src/',
|
|
96
|
+
prompt: 'List every TypeScript interface defined under src/.',
|
|
97
|
+
kind: 'kind-filter',
|
|
98
|
+
query: 'interface', // means: kind === "interface"
|
|
99
|
+
groundTruth: [
|
|
100
|
+
'CallEdge',
|
|
101
|
+
'EdgeQueryOptions',
|
|
102
|
+
'Graph',
|
|
103
|
+
'GpCallersArgs',
|
|
104
|
+
'GpImpactArgs',
|
|
105
|
+
'GpIndexArgs',
|
|
106
|
+
'GpRecallArgs',
|
|
107
|
+
'GpStatsArgs',
|
|
108
|
+
'ImpactCaller',
|
|
109
|
+
'ImpactOptions',
|
|
110
|
+
'ImpactResult',
|
|
111
|
+
'IndexOptions',
|
|
112
|
+
'IndexResult',
|
|
113
|
+
'InteractionEntry',
|
|
114
|
+
'ParsedFile',
|
|
115
|
+
'RawCall',
|
|
116
|
+
'RecallOptions',
|
|
117
|
+
'SecretPattern',
|
|
118
|
+
'SymbolRecord',
|
|
119
|
+
'ToolResult',
|
|
120
|
+
'UpdateResult',
|
|
121
|
+
'ValidationContext',
|
|
122
|
+
'WatcherOptions',
|
|
123
|
+
],
|
|
124
|
+
expectedWinner: 'graphpilot',
|
|
125
|
+
difficulty: 'medium',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: 't06-impact-extractSymbols-depth2',
|
|
129
|
+
description: 'Compute blast radius of changing extractSymbols (depth 2)',
|
|
130
|
+
prompt:
|
|
131
|
+
"If I change extractSymbols's signature, what functions will I need to update? Include indirect callers up to two hops.",
|
|
132
|
+
kind: 'impact',
|
|
133
|
+
query: 'extractSymbols',
|
|
134
|
+
// Direct callers + their direct callers
|
|
135
|
+
groundTruth: [
|
|
136
|
+
// d=1
|
|
137
|
+
'indexDirectory',
|
|
138
|
+
'applyUpdate',
|
|
139
|
+
'symbolsOf',
|
|
140
|
+
// d=2 — callers of the above
|
|
141
|
+
'cmdIndex',
|
|
142
|
+
'handleGpIndex',
|
|
143
|
+
'handleEvent',
|
|
144
|
+
// symbolsOf has no callers in production code; it's only in tests
|
|
145
|
+
],
|
|
146
|
+
expectedWinner: 'graphpilot',
|
|
147
|
+
difficulty: 'high',
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: 't07-tests-affected-parseFile',
|
|
151
|
+
description: 'Identify test files that exercise parseFile (directly)',
|
|
152
|
+
prompt: 'If I change the behavior of parseFile, which test files are most likely to break?',
|
|
153
|
+
kind: 'tests-affected',
|
|
154
|
+
query: 'parseFile',
|
|
155
|
+
// The test file containing symbolsOf which calls parseFile
|
|
156
|
+
groundTruth: ['tests/symbols.test.ts'],
|
|
157
|
+
expectedWinner: 'graphpilot',
|
|
158
|
+
difficulty: 'medium',
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: 't08-recall-substring-args',
|
|
162
|
+
description: 'Find every MCP-tool input-args interface',
|
|
163
|
+
prompt: 'List every TypeScript type whose name ends with "Args".',
|
|
164
|
+
kind: 'recall-substring',
|
|
165
|
+
query: 'Args',
|
|
166
|
+
groundTruth: ['GpCallersArgs', 'GpImpactArgs', 'GpIndexArgs', 'GpRecallArgs', 'GpStatsArgs'],
|
|
167
|
+
expectedWinner: 'graphpilot',
|
|
168
|
+
difficulty: 'low',
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: 't09-recall-miss',
|
|
172
|
+
description: 'Look up a symbol that does not exist (negative test)',
|
|
173
|
+
prompt: 'Find the function definitelyNotARealSymbol in this codebase.',
|
|
174
|
+
kind: 'recall-miss',
|
|
175
|
+
query: 'definitelyNotARealSymbol',
|
|
176
|
+
groundTruth: [], // empty set
|
|
177
|
+
expectedWinner: 'tie',
|
|
178
|
+
difficulty: 'low',
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: 't10-string-literal-MAX_FILE_BYTES',
|
|
182
|
+
description: 'Find every literal occurrence of the constant name "MAX_FILE_BYTES"',
|
|
183
|
+
prompt: 'Find every place the string "MAX_FILE_BYTES" appears in the source.',
|
|
184
|
+
kind: 'string-literal',
|
|
185
|
+
query: 'MAX_FILE_BYTES',
|
|
186
|
+
// We don't index string literals or identifier usages outside structural
|
|
187
|
+
// contexts — but for THIS specific constant the structural index has the
|
|
188
|
+
// declaration. Both should find the declaration; only grep finds every
|
|
189
|
+
// usage. We expect grep to win on recall here.
|
|
190
|
+
groundTruth: [
|
|
191
|
+
'src/validation.ts', // declared here
|
|
192
|
+
'src/parser.ts', // imported and used
|
|
193
|
+
'tests/security.test.ts', // referenced in a test
|
|
194
|
+
],
|
|
195
|
+
expectedWinner: 'grep',
|
|
196
|
+
difficulty: 'medium',
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
id: 't11-impact-since-indexDirectory',
|
|
200
|
+
description: 'Differential impact: callers of indexDirectory changed since HEAD~1',
|
|
201
|
+
prompt:
|
|
202
|
+
'Show me callers of indexDirectory, but only those in files that have changed since HEAD~1. This is for PR review — I want to know which of my changes will be affected.',
|
|
203
|
+
kind: 'impact-since',
|
|
204
|
+
query: 'indexDirectory',
|
|
205
|
+
// Ground truth: callers of indexDirectory at all depths are
|
|
206
|
+
// [cmdIndex, handleGpIndex, applyUpdate]. On a clean repo HEAD~1
|
|
207
|
+
// should be empty or shallow-history, so we expect ~0 to all three
|
|
208
|
+
// depending on the branch state. Scorer will filter by changed files.
|
|
209
|
+
groundTruth: [], // Filled in during scoring based on actual git state
|
|
210
|
+
expectedWinner: 'graphpilot', // GraphPilot filters noise; baseline can't
|
|
211
|
+
difficulty: 'high',
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: 't12-evidence-anchor-resolution',
|
|
215
|
+
description: 'Evidence anchors: every tool response carries file:line @ sha citations',
|
|
216
|
+
prompt:
|
|
217
|
+
'Find every function that calls analyzeImpact. For each result, I need the exact file and line number I can jump to. Include the git SHA from when the index was built.',
|
|
218
|
+
kind: 'callers', // same tool as t01, different validation
|
|
219
|
+
query: 'analyzeImpact',
|
|
220
|
+
groundTruth: ['handleGpImpact'],
|
|
221
|
+
expectedWinner: 'graphpilot', // Only GP returns structured evidence anchors
|
|
222
|
+
difficulty: 'medium',
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
id: 't13-recall-nonexistent-with-anchor',
|
|
226
|
+
description:
|
|
227
|
+
'Anti-hallucination: looking up a symbol that does not exist returns citation proof',
|
|
228
|
+
prompt:
|
|
229
|
+
'Find the function fakeSymbolXYZ123. If it does not exist, show me the evidence — what query returned no results and why?',
|
|
230
|
+
kind: 'recall-miss',
|
|
231
|
+
query: 'fakeSymbolXYZ123',
|
|
232
|
+
groundTruth: [], // Does not exist
|
|
233
|
+
expectedWinner: 'graphpilot', // GP can cite "not in index" with proof; baseline may hallucinate
|
|
234
|
+
difficulty: 'high',
|
|
235
|
+
},
|
|
236
|
+
];
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { indexDirectory } from './indexer.js';
|
|
4
|
+
import { saveGraph, loadGraph, graphPath, repoIdFor } from './storage.js';
|
|
5
|
+
import { validateRootPath } from './validation.js';
|
|
6
|
+
import { startMcpServer } from './mcp.js';
|
|
7
|
+
import { GraphWatcher } from './watcher.js';
|
|
8
|
+
import { resolveIndexRoot } from './git.js';
|
|
9
|
+
const HELP = `graphpilot — structural memory for coding agents
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
graphpilot index <path> Index a TypeScript/JavaScript repo
|
|
13
|
+
graphpilot status <path> Show info about an indexed repo
|
|
14
|
+
graphpilot watch <path> Watch the repo and update the index on save
|
|
15
|
+
graphpilot mcp Start the MCP server (stdio)
|
|
16
|
+
graphpilot help Show this help
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
graphpilot index .
|
|
20
|
+
graphpilot status .
|
|
21
|
+
graphpilot watch . # keeps the index fresh as you edit
|
|
22
|
+
graphpilot mcp # used by MCP clients (Claude Code, Cursor, ...)
|
|
23
|
+
`;
|
|
24
|
+
async function cmdIndex(pathArg, opts = {}) {
|
|
25
|
+
const requested = resolve(pathArg);
|
|
26
|
+
// Worktree-scope: by default, if the user pointed inside a git worktree
|
|
27
|
+
// we re-root to the worktree top so the index covers the full branch.
|
|
28
|
+
// Pass --no-worktree to disable.
|
|
29
|
+
const { root: absRoot, redirected } = resolveIndexRoot(requested, { disable: opts.noWorktree });
|
|
30
|
+
if (redirected) {
|
|
31
|
+
process.stdout.write(`[graphpilot] Re-rooting index to git worktree top: ${absRoot}\n` +
|
|
32
|
+
` (Pass --no-worktree to index ${requested} directly.)\n`);
|
|
33
|
+
}
|
|
34
|
+
// T10 defence: refuse `/`, `/etc`, `~`, and friends before walking.
|
|
35
|
+
const refusal = validateRootPath(absRoot);
|
|
36
|
+
if (refusal) {
|
|
37
|
+
process.stderr.write(`Error: ${refusal}\n`);
|
|
38
|
+
return 2;
|
|
39
|
+
}
|
|
40
|
+
process.stdout.write(`Indexing ${absRoot} ...\n`);
|
|
41
|
+
const result = await indexDirectory(absRoot);
|
|
42
|
+
const graph = {
|
|
43
|
+
version: 1,
|
|
44
|
+
repoId: repoIdFor(absRoot),
|
|
45
|
+
rootPath: absRoot,
|
|
46
|
+
indexedAt: new Date().toISOString(),
|
|
47
|
+
filesIndexed: result.filesIndexed,
|
|
48
|
+
symbolCount: result.symbols.length,
|
|
49
|
+
edgeCount: result.edges.length,
|
|
50
|
+
symbols: result.symbols,
|
|
51
|
+
edges: result.edges,
|
|
52
|
+
indexedSha: result.git.sha,
|
|
53
|
+
indexedBranch: result.git.branch,
|
|
54
|
+
};
|
|
55
|
+
const saved = saveGraph(graph);
|
|
56
|
+
const resolved = result.edges.filter((e) => e.toId !== null).length;
|
|
57
|
+
// Build the git stamp line lazily — only printed when we're in a git repo.
|
|
58
|
+
let gitLine = '';
|
|
59
|
+
if (result.git.shortSha || result.git.branch) {
|
|
60
|
+
const parts = [];
|
|
61
|
+
if (result.git.branch)
|
|
62
|
+
parts.push(`branch ${result.git.branch}`);
|
|
63
|
+
if (result.git.shortSha)
|
|
64
|
+
parts.push(`sha ${result.git.shortSha}`);
|
|
65
|
+
gitLine = ` Git: ${parts.join(' @ ')}\n`;
|
|
66
|
+
}
|
|
67
|
+
process.stdout.write(`\n✓ Remembered ${result.symbols.length} symbols, ${result.edges.length} calls ` +
|
|
68
|
+
`(${resolved} resolved) across ${result.filesIndexed} files in ${result.durationMs}ms.\n` +
|
|
69
|
+
` Repo id: ${graph.repoId}\n` +
|
|
70
|
+
gitLine +
|
|
71
|
+
` Graph file: ${saved}\n` +
|
|
72
|
+
(result.filesFailed ? ` Failed: ${result.filesFailed} file(s)\n` : ''));
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
function cmdStatus(pathArg) {
|
|
76
|
+
const absRoot = resolve(pathArg);
|
|
77
|
+
const graph = loadGraph(absRoot);
|
|
78
|
+
if (!graph) {
|
|
79
|
+
process.stderr.write(`No index found for ${absRoot}\n` + `Run: graphpilot index ${pathArg}\n`);
|
|
80
|
+
return 1;
|
|
81
|
+
}
|
|
82
|
+
// Compose a git line if the indexed repo had provenance at the time.
|
|
83
|
+
let gitLine = '';
|
|
84
|
+
if (graph.indexedSha || graph.indexedBranch) {
|
|
85
|
+
const parts = [];
|
|
86
|
+
if (graph.indexedBranch)
|
|
87
|
+
parts.push(`branch ${graph.indexedBranch}`);
|
|
88
|
+
if (graph.indexedSha)
|
|
89
|
+
parts.push(`sha ${graph.indexedSha.slice(0, 7)}`);
|
|
90
|
+
gitLine = `Git: ${parts.join(' @ ')}\n`;
|
|
91
|
+
}
|
|
92
|
+
process.stdout.write(`Repo id: ${graph.repoId}\n` +
|
|
93
|
+
`Root: ${graph.rootPath}\n` +
|
|
94
|
+
`Indexed at: ${graph.indexedAt}\n` +
|
|
95
|
+
gitLine +
|
|
96
|
+
`Files: ${graph.filesIndexed}\n` +
|
|
97
|
+
`Symbols: ${graph.symbolCount}\n` +
|
|
98
|
+
`Calls: ${graph.edgeCount ?? 0}\n` +
|
|
99
|
+
`Graph file: ${graphPath(absRoot)}\n`);
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
async function main() {
|
|
103
|
+
const [, , cmd, ...rest] = process.argv;
|
|
104
|
+
switch (cmd) {
|
|
105
|
+
case 'index': {
|
|
106
|
+
const noWorktree = rest.includes('--no-worktree');
|
|
107
|
+
const path = rest.find((a) => !a.startsWith('--')) ?? '.';
|
|
108
|
+
return cmdIndex(path, { noWorktree });
|
|
109
|
+
}
|
|
110
|
+
case 'status': {
|
|
111
|
+
const path = rest[0] ?? '.';
|
|
112
|
+
return cmdStatus(path);
|
|
113
|
+
}
|
|
114
|
+
case 'mcp': {
|
|
115
|
+
// Server runs until stdin closes (MCP client disconnect). Never
|
|
116
|
+
// returns under normal operation.
|
|
117
|
+
await startMcpServer();
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
case 'watch': {
|
|
121
|
+
const noWorktree = rest.includes('--no-worktree');
|
|
122
|
+
const path = rest.find((a) => !a.startsWith('--')) ?? '.';
|
|
123
|
+
const requested = resolve(path);
|
|
124
|
+
const { root: absRoot, redirected } = resolveIndexRoot(requested, { disable: noWorktree });
|
|
125
|
+
if (redirected) {
|
|
126
|
+
process.stderr.write(`[graphpilot:watch] Re-rooting to worktree top: ${absRoot}\n`);
|
|
127
|
+
}
|
|
128
|
+
const refusal = validateRootPath(absRoot);
|
|
129
|
+
if (refusal) {
|
|
130
|
+
process.stderr.write(`Error: ${refusal}\n`);
|
|
131
|
+
return 2;
|
|
132
|
+
}
|
|
133
|
+
const watcher = new GraphWatcher(absRoot);
|
|
134
|
+
await watcher.start();
|
|
135
|
+
process.stderr.write(`[graphpilot:watch] Ctrl+C to stop.\n`);
|
|
136
|
+
// Hold the process open until SIGINT or stdin EOF.
|
|
137
|
+
await new Promise((res) => {
|
|
138
|
+
const finish = () => res();
|
|
139
|
+
process.once('SIGINT', finish);
|
|
140
|
+
process.once('SIGTERM', finish);
|
|
141
|
+
process.stdin.once('end', finish);
|
|
142
|
+
process.stdin.once('close', finish);
|
|
143
|
+
});
|
|
144
|
+
await watcher.stop();
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
|
147
|
+
case 'help':
|
|
148
|
+
case '--help':
|
|
149
|
+
case '-h':
|
|
150
|
+
case undefined:
|
|
151
|
+
process.stdout.write(HELP);
|
|
152
|
+
return 0;
|
|
153
|
+
default:
|
|
154
|
+
process.stderr.write(`Unknown command: ${cmd}\n\n${HELP}`);
|
|
155
|
+
return 2;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
main().then((code) => process.exit(code), (err) => {
|
|
159
|
+
process.stderr.write(`Error: ${err?.stack ?? err}\n`);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
});
|
|
162
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/edges.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type ParsedFile } from './parser.js';
|
|
2
|
+
import type { SymbolRecord } from './symbols.js';
|
|
3
|
+
/**
|
|
4
|
+
* A resolved call edge.
|
|
5
|
+
*
|
|
6
|
+
* `toId` is null when the call's target couldn't be resolved to a known symbol
|
|
7
|
+
* (e.g. it's a stdlib call like `Array.from`, a third-party import, or a
|
|
8
|
+
* dynamic dispatch we don't track in v1). `toName` is always set, so the agent
|
|
9
|
+
* still knows what was called.
|
|
10
|
+
*/
|
|
11
|
+
export interface CallEdge {
|
|
12
|
+
fromId: string;
|
|
13
|
+
toId: string | null;
|
|
14
|
+
toName: string;
|
|
15
|
+
file: string;
|
|
16
|
+
line: number;
|
|
17
|
+
column: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A pre-resolution call site. Same shape as CallEdge minus `toId`. Used during
|
|
21
|
+
* indexing before we have the full symbol table.
|
|
22
|
+
*/
|
|
23
|
+
export interface RawCall {
|
|
24
|
+
fromId: string;
|
|
25
|
+
toName: string;
|
|
26
|
+
file: string;
|
|
27
|
+
line: number;
|
|
28
|
+
column: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* For every function-like symbol in `fileSymbols`, walk its body and emit a
|
|
32
|
+
* RawCall for every call/new expression directly inside it.
|
|
33
|
+
*
|
|
34
|
+
* Returns calls keyed by *line+name lookup* so resolution can happen later.
|
|
35
|
+
*/
|
|
36
|
+
export declare function extractRawCalls(parsed: ParsedFile, fileSymbols: SymbolRecord[]): RawCall[];
|
|
37
|
+
/**
|
|
38
|
+
* Second-pass resolver. Given the full symbol table and a list of raw calls,
|
|
39
|
+
* fill in `toId` where the callee name matches a known symbol.
|
|
40
|
+
*
|
|
41
|
+
* Resolution strategy (v1 — deliberately dumb):
|
|
42
|
+
* 1. Prefer a symbol with the same name in the same file (likely the right one)
|
|
43
|
+
* 2. Otherwise pick any symbol with that name (first match — non-deterministic
|
|
44
|
+
* across reruns of ambiguous names, but stable within a single index)
|
|
45
|
+
* 3. Otherwise leave toId null
|
|
46
|
+
*
|
|
47
|
+
* Known limitations (documented as v1 caveats):
|
|
48
|
+
* - No import resolution: if `parseToken` is imported from another file we'll
|
|
49
|
+
* still find it globally, but if two files both export `parseToken` we may
|
|
50
|
+
* pick the wrong one.
|
|
51
|
+
* - No method-of-class disambiguation: `obj.method()` resolves to the first
|
|
52
|
+
* symbol named `method`, regardless of receiver type.
|
|
53
|
+
* - No re-export chains.
|
|
54
|
+
*
|
|
55
|
+
* These are fine for v1; the goal is "better than grep" not "compiler-grade".
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolveCallEdges(rawCalls: RawCall[], allSymbols: SymbolRecord[]): CallEdge[];
|