@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.
Files changed (130) hide show
  1. package/.editorconfig +15 -0
  2. package/.github/CODEOWNERS +22 -0
  3. package/.github/FUNDING.yml +1 -0
  4. package/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
  5. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  6. package/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
  7. package/.github/PULL_REQUEST_TEMPLATE.md +19 -0
  8. package/.github/dependabot.yml +15 -0
  9. package/.github/workflows/ci.yml +62 -0
  10. package/.github/workflows/release.yml +50 -0
  11. package/.prettierignore +19 -0
  12. package/.prettierrc.json +20 -0
  13. package/CHANGELOG.md +138 -0
  14. package/CODE_OF_CONDUCT.md +83 -0
  15. package/CONTRIBUTING.md +111 -0
  16. package/LICENSE +201 -0
  17. package/README.md +132 -0
  18. package/SECURITY.md +44 -0
  19. package/assets/logo.png +0 -0
  20. package/assets/logo.svg +1 -0
  21. package/bench/README.md +544 -0
  22. package/bench/results/agent-tier-2026-05-22.md +28 -0
  23. package/bench/results/agent-tier-summary.md +44 -0
  24. package/bench/results/baseline-tier-2026-05-22.md +23 -0
  25. package/bench/results/baseline.json +810 -0
  26. package/bench/results/baseline.md +28 -0
  27. package/bench/run-agent-tier-automated.ts +234 -0
  28. package/bench/run-agent-tier.md +125 -0
  29. package/bench/run-baseline-tier.ts +200 -0
  30. package/bench/run.ts +210 -0
  31. package/bench/runner-baseline.ts +177 -0
  32. package/bench/runner-graphpilot.ts +131 -0
  33. package/bench/score-agent-tier.ts +191 -0
  34. package/bench/score.ts +59 -0
  35. package/bench/tasks.ts +236 -0
  36. package/dist/cli.d.ts +2 -0
  37. package/dist/cli.js +162 -0
  38. package/dist/cli.js.map +1 -0
  39. package/dist/edges.d.ts +57 -0
  40. package/dist/edges.js +170 -0
  41. package/dist/edges.js.map +1 -0
  42. package/dist/git.d.ts +95 -0
  43. package/dist/git.js +247 -0
  44. package/dist/git.js.map +1 -0
  45. package/dist/graph-schema.d.ts +36 -0
  46. package/dist/graph-schema.js +208 -0
  47. package/dist/graph-schema.js.map +1 -0
  48. package/dist/impact.d.ts +99 -0
  49. package/dist/impact.js +123 -0
  50. package/dist/impact.js.map +1 -0
  51. package/dist/indexer.d.ts +28 -0
  52. package/dist/indexer.js +111 -0
  53. package/dist/indexer.js.map +1 -0
  54. package/dist/interactions.d.ts +46 -0
  55. package/dist/interactions.js +0 -0
  56. package/dist/interactions.js.map +1 -0
  57. package/dist/mcp.d.ts +3 -0
  58. package/dist/mcp.js +567 -0
  59. package/dist/mcp.js.map +1 -0
  60. package/dist/parser.d.ts +24 -0
  61. package/dist/parser.js +128 -0
  62. package/dist/parser.js.map +1 -0
  63. package/dist/provenance.d.ts +74 -0
  64. package/dist/provenance.js +95 -0
  65. package/dist/provenance.js.map +1 -0
  66. package/dist/query.d.ts +68 -0
  67. package/dist/query.js +127 -0
  68. package/dist/query.js.map +1 -0
  69. package/dist/redact.d.ts +30 -0
  70. package/dist/redact.js +117 -0
  71. package/dist/redact.js.map +1 -0
  72. package/dist/storage.d.ts +42 -0
  73. package/dist/storage.js +85 -0
  74. package/dist/storage.js.map +1 -0
  75. package/dist/symbols.d.ts +20 -0
  76. package/dist/symbols.js +140 -0
  77. package/dist/symbols.js.map +1 -0
  78. package/dist/validation.d.ts +9 -0
  79. package/dist/validation.js +65 -0
  80. package/dist/validation.js.map +1 -0
  81. package/dist/validators.d.ts +55 -0
  82. package/dist/validators.js +205 -0
  83. package/dist/validators.js.map +1 -0
  84. package/dist/watcher.d.ts +86 -0
  85. package/dist/watcher.js +310 -0
  86. package/dist/watcher.js.map +1 -0
  87. package/docs/architecture.md +311 -0
  88. package/docs/limitations.md +156 -0
  89. package/docs/mcp-setup.md +231 -0
  90. package/docs/quickstart.md +202 -0
  91. package/eslint.config.js +148 -0
  92. package/lefthook.yml +81 -0
  93. package/package.json +56 -0
  94. package/pnpm-workspace.yaml +6 -0
  95. package/scripts/smoke-stdio.mjs +97 -0
  96. package/src/cli.ts +171 -0
  97. package/src/edges.ts +202 -0
  98. package/src/git.ts +255 -0
  99. package/src/graph-schema.ts +229 -0
  100. package/src/impact.ts +218 -0
  101. package/src/indexer.ts +152 -0
  102. package/src/interactions.ts +0 -0
  103. package/src/mcp.ts +652 -0
  104. package/src/parser.ts +138 -0
  105. package/src/provenance.ts +115 -0
  106. package/src/query.ts +148 -0
  107. package/src/redact.ts +122 -0
  108. package/src/storage.ts +115 -0
  109. package/src/symbols.ts +173 -0
  110. package/src/validation.ts +69 -0
  111. package/src/validators.ts +253 -0
  112. package/src/watcher.ts +383 -0
  113. package/tests/edges.test.ts +175 -0
  114. package/tests/fixtures/sample.ts +32 -0
  115. package/tests/git.test.ts +303 -0
  116. package/tests/graph-schema.test.ts +321 -0
  117. package/tests/impact.test.ts +454 -0
  118. package/tests/interactions.test.ts +180 -0
  119. package/tests/lint-policy.test.ts +106 -0
  120. package/tests/mcp-stdio.test.ts +171 -0
  121. package/tests/mcp.test.ts +335 -0
  122. package/tests/parser.test.ts +31 -0
  123. package/tests/provenance.test.ts +132 -0
  124. package/tests/query.test.ts +160 -0
  125. package/tests/redact.test.ts +167 -0
  126. package/tests/security.test.ts +144 -0
  127. package/tests/symbols.test.ts +78 -0
  128. package/tests/validators.test.ts +193 -0
  129. package/tests/watcher.test.ts +250 -0
  130. 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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -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[];