@graphpilot-oss/graphpilot 0.0.1 → 0.1.0

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 (116) hide show
  1. package/CHANGELOG.md +73 -126
  2. package/README.md +359 -101
  3. package/dist/cli.js +20 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/indexer.js +3 -3
  6. package/dist/indexer.js.map +1 -1
  7. package/dist/init.d.ts +28 -0
  8. package/dist/init.js +112 -0
  9. package/dist/init.js.map +1 -0
  10. package/dist/interactions.d.ts +5 -4
  11. package/dist/interactions.js +0 -0
  12. package/dist/interactions.js.map +1 -1
  13. package/dist/mcp.js +126 -46
  14. package/dist/mcp.js.map +1 -1
  15. package/dist/repo-resolve.d.ts +47 -0
  16. package/dist/repo-resolve.js +195 -0
  17. package/dist/repo-resolve.js.map +1 -0
  18. package/dist/storage.js +10 -1
  19. package/dist/storage.js.map +1 -1
  20. package/dist/validation.js +30 -4
  21. package/dist/validation.js.map +1 -1
  22. package/dist/watcher.d.ts +10 -0
  23. package/dist/watcher.js +70 -7
  24. package/dist/watcher.js.map +1 -1
  25. package/examples/README.md +105 -0
  26. package/examples/claude-code/README.md +125 -0
  27. package/examples/claude-code/claude-routing.md +102 -0
  28. package/examples/claude-code/claude_config.json +8 -0
  29. package/examples/cline/.clinerules +39 -0
  30. package/examples/cline/README.md +104 -0
  31. package/examples/cline/cline_mcp_settings.json +10 -0
  32. package/examples/continue/.continuerules +39 -0
  33. package/examples/continue/README.md +98 -0
  34. package/examples/continue/config.json +13 -0
  35. package/examples/cursor/.cursorrules +39 -0
  36. package/examples/cursor/README.md +98 -0
  37. package/examples/cursor/mcp.json +11 -0
  38. package/examples/windsurf/.windsurfrules +39 -0
  39. package/examples/windsurf/README.md +85 -0
  40. package/examples/windsurf/mcp_config.json +8 -0
  41. package/package.json +12 -3
  42. package/.editorconfig +0 -15
  43. package/.github/CODEOWNERS +0 -22
  44. package/.github/FUNDING.yml +0 -1
  45. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -33
  46. package/.github/ISSUE_TEMPLATE/config.yml +0 -5
  47. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -23
  48. package/.github/PULL_REQUEST_TEMPLATE.md +0 -19
  49. package/.github/dependabot.yml +0 -15
  50. package/.github/workflows/ci.yml +0 -62
  51. package/.github/workflows/release.yml +0 -50
  52. package/.prettierignore +0 -19
  53. package/.prettierrc.json +0 -20
  54. package/CODE_OF_CONDUCT.md +0 -83
  55. package/CONTRIBUTING.md +0 -111
  56. package/bench/README.md +0 -544
  57. package/bench/results/agent-tier-2026-05-22.md +0 -28
  58. package/bench/results/agent-tier-summary.md +0 -44
  59. package/bench/results/baseline-tier-2026-05-22.md +0 -23
  60. package/bench/results/baseline.json +0 -810
  61. package/bench/results/baseline.md +0 -28
  62. package/bench/run-agent-tier-automated.ts +0 -234
  63. package/bench/run-agent-tier.md +0 -125
  64. package/bench/run-baseline-tier.ts +0 -200
  65. package/bench/run.ts +0 -210
  66. package/bench/runner-baseline.ts +0 -177
  67. package/bench/runner-graphpilot.ts +0 -131
  68. package/bench/score-agent-tier.ts +0 -191
  69. package/bench/score.ts +0 -59
  70. package/bench/tasks.ts +0 -236
  71. package/dist/provenance.d.ts +0 -74
  72. package/dist/provenance.js +0 -95
  73. package/dist/provenance.js.map +0 -1
  74. package/docs/architecture.md +0 -311
  75. package/docs/limitations.md +0 -156
  76. package/docs/mcp-setup.md +0 -231
  77. package/docs/quickstart.md +0 -202
  78. package/eslint.config.js +0 -148
  79. package/lefthook.yml +0 -81
  80. package/pnpm-workspace.yaml +0 -6
  81. package/scripts/smoke-stdio.mjs +0 -97
  82. package/src/cli.ts +0 -171
  83. package/src/edges.ts +0 -202
  84. package/src/git.ts +0 -255
  85. package/src/graph-schema.ts +0 -229
  86. package/src/impact.ts +0 -218
  87. package/src/indexer.ts +0 -152
  88. package/src/interactions.ts +0 -0
  89. package/src/mcp.ts +0 -652
  90. package/src/parser.ts +0 -138
  91. package/src/provenance.ts +0 -115
  92. package/src/query.ts +0 -148
  93. package/src/redact.ts +0 -122
  94. package/src/storage.ts +0 -115
  95. package/src/symbols.ts +0 -173
  96. package/src/validation.ts +0 -69
  97. package/src/validators.ts +0 -253
  98. package/src/watcher.ts +0 -383
  99. package/tests/edges.test.ts +0 -175
  100. package/tests/fixtures/sample.ts +0 -32
  101. package/tests/git.test.ts +0 -303
  102. package/tests/graph-schema.test.ts +0 -321
  103. package/tests/impact.test.ts +0 -454
  104. package/tests/interactions.test.ts +0 -180
  105. package/tests/lint-policy.test.ts +0 -106
  106. package/tests/mcp-stdio.test.ts +0 -171
  107. package/tests/mcp.test.ts +0 -335
  108. package/tests/parser.test.ts +0 -31
  109. package/tests/provenance.test.ts +0 -132
  110. package/tests/query.test.ts +0 -160
  111. package/tests/redact.test.ts +0 -167
  112. package/tests/security.test.ts +0 -144
  113. package/tests/symbols.test.ts +0 -78
  114. package/tests/validators.test.ts +0 -193
  115. package/tests/watcher.test.ts +0 -250
  116. package/tsconfig.json +0 -18
@@ -1,193 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import {
3
- validateGpIndex,
4
- validateGpRecall,
5
- validateGpCallers,
6
- validateGpImpact,
7
- validateGpStats,
8
- } from '../src/validators.js';
9
-
10
- describe('validateGpStats', () => {
11
- it('accepts empty object', () => {
12
- expect(validateGpStats({})).toEqual({ ok: true, value: { path: undefined } });
13
- });
14
-
15
- it('accepts a path', () => {
16
- expect(validateGpStats({ path: '/x' })).toEqual({
17
- ok: true,
18
- value: { path: '/x' },
19
- });
20
- });
21
-
22
- it('rejects non-object', () => {
23
- expect(validateGpStats('nope').ok).toBe(false);
24
- expect(validateGpStats(42).ok).toBe(false);
25
- expect(validateGpStats([]).ok).toBe(false);
26
- });
27
-
28
- it('rejects extra keys', () => {
29
- const r = validateGpStats({ path: '/x', sneaky: true });
30
- expect(r.ok).toBe(false);
31
- if (!r.ok) expect(r.error).toMatch(/sneaky/);
32
- });
33
-
34
- it('rejects non-string path', () => {
35
- const r = validateGpStats({ path: 42 });
36
- expect(r.ok).toBe(false);
37
- });
38
- });
39
-
40
- describe('validateGpIndex', () => {
41
- it('accepts empty (path defaults to cwd later)', () => {
42
- expect(validateGpIndex({}).ok).toBe(true);
43
- });
44
- it('accepts path', () => {
45
- const r = validateGpIndex({ path: '/repo' });
46
- expect(r.ok).toBe(true);
47
- if (r.ok) expect(r.value.path).toBe('/repo');
48
- });
49
- it('rejects extra keys', () => {
50
- expect(validateGpIndex({ path: '/x', force: true }).ok).toBe(false);
51
- });
52
- });
53
-
54
- describe('validateGpRecall', () => {
55
- it('requires a non-empty query', () => {
56
- expect(validateGpRecall({}).ok).toBe(false);
57
- expect(validateGpRecall({ query: '' }).ok).toBe(false);
58
- expect(validateGpRecall({ query: ' ' }).ok).toBe(false);
59
- });
60
-
61
- it('accepts minimal valid input', () => {
62
- const r = validateGpRecall({ query: 'parseToken' });
63
- expect(r.ok).toBe(true);
64
- if (r.ok) {
65
- expect(r.value.query).toBe('parseToken');
66
- expect(r.value.limit).toBeUndefined();
67
- expect(r.value.substring).toBeUndefined();
68
- }
69
- });
70
-
71
- it('accepts full input', () => {
72
- const r = validateGpRecall({
73
- query: 'parse',
74
- limit: 25,
75
- substring: true,
76
- path: '/x',
77
- });
78
- expect(r.ok).toBe(true);
79
- if (r.ok) {
80
- expect(r.value.limit).toBe(25);
81
- expect(r.value.substring).toBe(true);
82
- }
83
- });
84
-
85
- it('caps limit range', () => {
86
- expect(validateGpRecall({ query: 'x', limit: 0 }).ok).toBe(false);
87
- expect(validateGpRecall({ query: 'x', limit: 51 }).ok).toBe(false);
88
- expect(validateGpRecall({ query: 'x', limit: 1.5 }).ok).toBe(false);
89
- expect(validateGpRecall({ query: 'x', limit: -1 }).ok).toBe(false);
90
- });
91
-
92
- it('rejects extra keys', () => {
93
- expect(validateGpRecall({ query: 'x', shellInjection: 'oops' }).ok).toBe(false);
94
- });
95
-
96
- it('rejects wrong types', () => {
97
- expect(validateGpRecall({ query: 42 }).ok).toBe(false);
98
- expect(validateGpRecall({ query: 'x', substring: 'yes' }).ok).toBe(false);
99
- });
100
-
101
- it('caps query length', () => {
102
- const r = validateGpRecall({ query: 'a'.repeat(500) });
103
- expect(r.ok).toBe(false);
104
- });
105
- });
106
-
107
- describe('validateGpCallers', () => {
108
- it('requires a non-empty symbol', () => {
109
- expect(validateGpCallers({}).ok).toBe(false);
110
- expect(validateGpCallers({ symbol: '' }).ok).toBe(false);
111
- });
112
-
113
- it('accepts minimal valid input', () => {
114
- const r = validateGpCallers({ symbol: 'parseToken' });
115
- expect(r.ok).toBe(true);
116
- });
117
-
118
- it('enforces direction enum', () => {
119
- expect(validateGpCallers({ symbol: 'x', direction: 'callers' }).ok).toBe(true);
120
- expect(validateGpCallers({ symbol: 'x', direction: 'callees' }).ok).toBe(true);
121
- expect(validateGpCallers({ symbol: 'x', direction: 'both' }).ok).toBe(false);
122
- expect(validateGpCallers({ symbol: 'x', direction: 42 }).ok).toBe(false);
123
- });
124
-
125
- it('caps limit at 100', () => {
126
- expect(validateGpCallers({ symbol: 'x', limit: 100 }).ok).toBe(true);
127
- expect(validateGpCallers({ symbol: 'x', limit: 101 }).ok).toBe(false);
128
- });
129
-
130
- it('rejects extra keys', () => {
131
- expect(validateGpCallers({ symbol: 'x', sql: 'drop table' }).ok).toBe(false);
132
- });
133
- });
134
-
135
- describe('validateGpImpact', () => {
136
- it('requires a non-empty symbol', () => {
137
- expect(validateGpImpact({}).ok).toBe(false);
138
- expect(validateGpImpact({ symbol: '' }).ok).toBe(false);
139
- expect(validateGpImpact({ symbol: ' ' }).ok).toBe(false);
140
- });
141
-
142
- it('accepts a minimal valid input', () => {
143
- const r = validateGpImpact({ symbol: 'parseToken' });
144
- expect(r.ok).toBe(true);
145
- if (r.ok) {
146
- expect(r.value.symbol).toBe('parseToken');
147
- expect(r.value.depth).toBeUndefined();
148
- }
149
- });
150
-
151
- it('accepts depth in range 1..5', () => {
152
- expect(validateGpImpact({ symbol: 'x', depth: 1 }).ok).toBe(true);
153
- expect(validateGpImpact({ symbol: 'x', depth: 3 }).ok).toBe(true);
154
- expect(validateGpImpact({ symbol: 'x', depth: 5 }).ok).toBe(true);
155
- });
156
-
157
- it('rejects depth out of range', () => {
158
- expect(validateGpImpact({ symbol: 'x', depth: 0 }).ok).toBe(false);
159
- expect(validateGpImpact({ symbol: 'x', depth: 6 }).ok).toBe(false);
160
- expect(validateGpImpact({ symbol: 'x', depth: -1 }).ok).toBe(false);
161
- });
162
-
163
- it('rejects non-integer depth', () => {
164
- expect(validateGpImpact({ symbol: 'x', depth: 2.5 }).ok).toBe(false);
165
- });
166
-
167
- it('accepts path', () => {
168
- const r = validateGpImpact({ symbol: 'x', path: '/tmp/repo' });
169
- expect(r.ok).toBe(true);
170
- });
171
-
172
- it('rejects extra keys', () => {
173
- expect(validateGpImpact({ symbol: 'x', surprise: 'hello' }).ok).toBe(false);
174
- });
175
-
176
- it('rejects wrong types', () => {
177
- expect(validateGpImpact({ symbol: 42 }).ok).toBe(false);
178
- expect(validateGpImpact({ symbol: 'x', depth: '3' }).ok).toBe(false);
179
- });
180
-
181
- it('accepts a non-empty `since` (commit/branch/tag)', () => {
182
- const r = validateGpImpact({ symbol: 'x', since: 'main' });
183
- expect(r.ok).toBe(true);
184
- if (r.ok) expect(r.value.since).toBe('main');
185
- expect(validateGpImpact({ symbol: 'x', since: 'abc1234' }).ok).toBe(true);
186
- });
187
-
188
- it('rejects empty / whitespace / wrong-typed `since`', () => {
189
- expect(validateGpImpact({ symbol: 'x', since: '' }).ok).toBe(false);
190
- expect(validateGpImpact({ symbol: 'x', since: ' ' }).ok).toBe(false);
191
- expect(validateGpImpact({ symbol: 'x', since: 42 }).ok).toBe(false);
192
- });
193
- });
@@ -1,250 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { mkdtempSync, rmSync, writeFileSync, unlinkSync, existsSync, readFileSync } from 'node:fs';
3
- import { tmpdir, homedir } from 'node:os';
4
- import { join } from 'node:path';
5
- import { GraphWatcher } from '../src/watcher.js';
6
- import { repoDir, loadGraph } from '../src/storage.js';
7
-
8
- /**
9
- * Tests drive applyUpdate / applyDeletion directly. We do NOT spin chokidar
10
- * up in tests — it's timing-dependent and racy on CI. The chokidar wiring is
11
- * exercised manually with `graphpilot watch <path>` and observed in dev.
12
- */
13
-
14
- let workDir: string;
15
-
16
- function silentLog(): (s: string) => void {
17
- return () => undefined;
18
- }
19
-
20
- beforeEach(() => {
21
- workDir = mkdtempSync(join(tmpdir(), 'graphpilot-watch-'));
22
- });
23
-
24
- afterEach(() => {
25
- if (workDir && existsSync(workDir)) rmSync(workDir, { recursive: true, force: true });
26
- // Clean the index dir this workDir would have produced
27
- const dir = repoDir(workDir);
28
- if (existsSync(dir)) rmSync(dir, { recursive: true, force: true });
29
- });
30
-
31
- describe('GraphWatcher — initial state', () => {
32
- it('starts with empty graph when no index exists yet', () => {
33
- const w = new GraphWatcher(workDir, { log: silentLog() });
34
- expect(w.currentGraph.symbols.length).toBe(0);
35
- expect(w.currentGraph.edges.length).toBe(0);
36
- expect(w.currentGraph.rootPath).toBe(workDir);
37
- });
38
-
39
- it('loads existing graph from disk if present', async () => {
40
- writeFileSync(join(workDir, 'hello.ts'), 'export function hello() { return 1; }\n');
41
- const w = new GraphWatcher(workDir, { log: silentLog() });
42
- await w.fullReindex();
43
- expect(w.currentGraph.symbols.length).toBeGreaterThan(0);
44
-
45
- // New watcher should pick the existing graph from disk.
46
- const w2 = new GraphWatcher(workDir, { log: silentLog() });
47
- expect(w2.currentGraph.symbols.length).toBe(w.currentGraph.symbols.length);
48
- });
49
-
50
- it('refuses to construct on a dangerous root path', () => {
51
- expect(() => new GraphWatcher(homedir(), { log: silentLog() })).toThrow(
52
- /home directory|system path/i,
53
- );
54
- expect(() => new GraphWatcher('/', { log: silentLog() })).toThrow(/system path/i);
55
- });
56
- });
57
-
58
- describe('GraphWatcher — applyUpdate (single-file change)', () => {
59
- it('adds symbols when a previously-unseen file appears', async () => {
60
- const w = new GraphWatcher(workDir, { log: silentLog() });
61
- await w.fullReindex(); // empty graph
62
-
63
- const newFile = join(workDir, 'new.ts');
64
- writeFileSync(newFile, 'export function appeared() { return 42; }\n');
65
- const r = await w.applyUpdate(newFile, 'add');
66
-
67
- expect(r).not.toBeNull();
68
- expect(r!.symbolsAfter).toBeGreaterThan(r!.symbolsBefore);
69
- const names = w.currentGraph.symbols.map((s) => s.name);
70
- expect(names).toContain('appeared');
71
- });
72
-
73
- it("replaces a file's symbols when it changes (rename function)", async () => {
74
- const file = join(workDir, 'a.ts');
75
- writeFileSync(file, 'export function oldName() { return 1; }\n');
76
- const w = new GraphWatcher(workDir, { log: silentLog() });
77
- await w.fullReindex();
78
- expect(w.currentGraph.symbols.some((s) => s.name === 'oldName')).toBe(true);
79
-
80
- // Rewrite the file with a different function name
81
- writeFileSync(file, 'export function newName() { return 2; }\n');
82
- await w.applyUpdate(file, 'change');
83
-
84
- const names = w.currentGraph.symbols.map((s) => s.name);
85
- expect(names).toContain('newName');
86
- expect(names).not.toContain('oldName');
87
- });
88
-
89
- it('updates edges when a caller appears that resolves a previously-unresolved name', async () => {
90
- // Start with a self-contained caller that calls a not-yet-existing function
91
- writeFileSync(join(workDir, 'consumer.ts'), `function consume() { helper(); return 1; }\n`);
92
- const w = new GraphWatcher(workDir, { log: silentLog() });
93
- await w.fullReindex();
94
- const unresolvedBefore = w.currentGraph.edges.filter(
95
- (e) => e.toName === 'helper' && e.toId === null,
96
- );
97
- expect(unresolvedBefore.length).toBe(1);
98
-
99
- // Add the helper — the edge should newly resolve
100
- const helperFile = join(workDir, 'helper.ts');
101
- writeFileSync(helperFile, 'export function helper() { return 7; }\n');
102
- await w.applyUpdate(helperFile, 'add');
103
-
104
- const resolved = w.currentGraph.edges.find((e) => e.toName === 'helper' && e.toId !== null);
105
- expect(resolved).toBeDefined();
106
- expect(resolved!.toId).toMatch(/helper/);
107
- });
108
-
109
- it('drops edges for a removed caller', async () => {
110
- writeFileSync(join(workDir, 'target.ts'), 'export function target() { return 1; }\n');
111
- writeFileSync(
112
- join(workDir, 'caller.ts'),
113
- 'import { target } from "./target";\nfunction call() { return target(); }\n',
114
- );
115
- const w = new GraphWatcher(workDir, { log: silentLog() });
116
- await w.fullReindex();
117
- const edgesBefore = w.currentGraph.edges.length;
118
- expect(edgesBefore).toBeGreaterThan(0);
119
-
120
- // Remove the caller's body — should drop the edge
121
- const callerFile = join(workDir, 'caller.ts');
122
- writeFileSync(callerFile, 'import { target } from "./target";\n');
123
- await w.applyUpdate(callerFile, 'change');
124
-
125
- const callTargetEdges = w.currentGraph.edges.filter((e) => e.toName === 'target');
126
- expect(callTargetEdges.length).toBe(0);
127
- });
128
-
129
- it('ignores non-watchable files', async () => {
130
- const w = new GraphWatcher(workDir, { log: silentLog() });
131
- await w.fullReindex();
132
- const txtFile = join(workDir, 'note.txt');
133
- writeFileSync(txtFile, 'just text');
134
- const r = await w.applyUpdate(txtFile, 'add');
135
- expect(r).toBeNull();
136
- });
137
-
138
- it('ignores files outside the watched root', async () => {
139
- const w = new GraphWatcher(workDir, { log: silentLog() });
140
- await w.fullReindex();
141
- // A path outside the root
142
- const outside = '/tmp/some-other-file.ts';
143
- const r = await w.applyUpdate(outside, 'change');
144
- expect(r).toBeNull();
145
- });
146
- });
147
-
148
- describe('GraphWatcher — applyDeletion', () => {
149
- it('removes all symbols + edges that came from the deleted file', async () => {
150
- const a = join(workDir, 'a.ts');
151
- const b = join(workDir, 'b.ts');
152
- writeFileSync(a, 'export function fromA() { return 1; }\n');
153
- writeFileSync(b, 'import { fromA } from "./a";\nexport function fromB() { return fromA(); }\n');
154
-
155
- const w = new GraphWatcher(workDir, { log: silentLog() });
156
- await w.fullReindex();
157
- expect(w.currentGraph.symbols.some((s) => s.name === 'fromA')).toBe(true);
158
-
159
- unlinkSync(a);
160
- const r = await w.applyDeletion(a);
161
- expect(r).not.toBeNull();
162
- expect(w.currentGraph.symbols.some((s) => s.name === 'fromA')).toBe(false);
163
- // The fromA-call edge in b.ts is now unresolved
164
- const stillThere = w.currentGraph.edges.find((e) => e.toName === 'fromA');
165
- expect(stillThere?.toId).toBeNull();
166
- });
167
-
168
- it('is a no-op if the file was not in the index', async () => {
169
- const w = new GraphWatcher(workDir, { log: silentLog() });
170
- await w.fullReindex();
171
- const r = await w.applyDeletion(join(workDir, 'never-existed.ts'));
172
- expect(r).toBeNull();
173
- });
174
- });
175
-
176
- describe('GraphWatcher — on-disk side effects', () => {
177
- it('persists each applyUpdate atomically (graph.json never partial)', async () => {
178
- writeFileSync(join(workDir, 'x.ts'), 'export function x() {}\n');
179
- const w = new GraphWatcher(workDir, { log: silentLog() });
180
- await w.fullReindex();
181
-
182
- // After a change, on-disk graph.json must be parseable JSON matching
183
- // what's in memory. (Atomic .tmp + rename in storage.saveGraph.)
184
- writeFileSync(join(workDir, 'x.ts'), 'export function x() { return 9; }\n');
185
- await w.applyUpdate(join(workDir, 'x.ts'), 'change');
186
-
187
- const onDisk = loadGraph(workDir);
188
- expect(onDisk).not.toBeNull();
189
- expect(onDisk!.symbolCount).toBe(w.currentGraph.symbols.length);
190
- });
191
-
192
- it('recomputes filesIndexed from surviving symbols', async () => {
193
- writeFileSync(join(workDir, 'one.ts'), 'export function one() {}\n');
194
- writeFileSync(join(workDir, 'two.ts'), 'export function two() {}\n');
195
- const w = new GraphWatcher(workDir, { log: silentLog() });
196
- await w.fullReindex();
197
- expect(w.currentGraph.filesIndexed).toBe(2);
198
-
199
- unlinkSync(join(workDir, 'one.ts'));
200
- await w.applyDeletion(join(workDir, 'one.ts'));
201
- expect(w.currentGraph.filesIndexed).toBe(1);
202
- });
203
-
204
- it('serializes concurrent updates via the chain (no torn graph)', async () => {
205
- // Drive multiple applyUpdates in parallel — the watcher should still end
206
- // up consistent. (The internal chain only protects chokidar-triggered
207
- // events, but applyUpdate is awaitable from the outside; here we test
208
- // that two awaits in sequence produce a deterministic outcome.)
209
- const a = join(workDir, 'a.ts');
210
- const b = join(workDir, 'b.ts');
211
- writeFileSync(a, 'export function a1() {}\n');
212
- writeFileSync(b, 'export function b1() {}\n');
213
- const w = new GraphWatcher(workDir, { log: silentLog() });
214
- await w.fullReindex();
215
-
216
- writeFileSync(a, 'export function a2() {}\n');
217
- writeFileSync(b, 'export function b2() {}\n');
218
- await Promise.all([w.applyUpdate(a, 'change'), w.applyUpdate(b, 'change')]);
219
-
220
- const names = w.currentGraph.symbols.map((s) => s.name).sort();
221
- expect(names).toEqual(['a2', 'b2']);
222
- });
223
- });
224
-
225
- describe('GraphWatcher — diagnostic output', () => {
226
- it('writes one diagnostic line per applyUpdate', async () => {
227
- writeFileSync(join(workDir, 'x.ts'), 'export function x() {}\n');
228
- const lines: string[] = [];
229
- const w = new GraphWatcher(workDir, { log: (s) => lines.push(s) });
230
- // fullReindex is silent by design; only event-driven updates log.
231
- await w.fullReindex();
232
- const before = lines.length;
233
- writeFileSync(join(workDir, 'x.ts'), 'export function x() { return 1; }\n');
234
- await w.applyUpdate(join(workDir, 'x.ts'), 'change');
235
- expect(lines.length).toBe(before + 1);
236
- expect(lines[lines.length - 1]).toMatch(/x\.ts/);
237
- });
238
-
239
- it('on-disk graph contains the new symbols after change', async () => {
240
- writeFileSync(join(workDir, 'x.ts'), 'export function oldOne() {}\n');
241
- const w = new GraphWatcher(workDir, { log: silentLog() });
242
- await w.fullReindex();
243
- writeFileSync(join(workDir, 'x.ts'), 'export function newOne() {}\n');
244
- await w.applyUpdate(join(workDir, 'x.ts'), 'change');
245
-
246
- const raw = readFileSync(join(repoDir(workDir), 'graph.json'), 'utf8');
247
- expect(raw).toContain('newOne');
248
- expect(raw).not.toContain('oldOne');
249
- });
250
- });
package/tsconfig.json DELETED
@@ -1,18 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "outDir": "./dist",
7
- "rootDir": "./src",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "declaration": true,
13
- "sourceMap": true,
14
- "resolveJsonModule": true
15
- },
16
- "include": ["src/**/*"],
17
- "exclude": ["node_modules", "dist", "tests"]
18
- }