@opensip-cli/mcp 0.1.15

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 (188) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +8 -0
  3. package/README.md +33 -0
  4. package/dist/__tests__/command-transport.test.d.ts +13 -0
  5. package/dist/__tests__/command-transport.test.d.ts.map +1 -0
  6. package/dist/__tests__/command-transport.test.js +63 -0
  7. package/dist/__tests__/command-transport.test.js.map +1 -0
  8. package/dist/__tests__/e2e-stdio.test.d.ts +16 -0
  9. package/dist/__tests__/e2e-stdio.test.d.ts.map +1 -0
  10. package/dist/__tests__/e2e-stdio.test.js +271 -0
  11. package/dist/__tests__/e2e-stdio.test.js.map +1 -0
  12. package/dist/__tests__/freshness.test.d.ts +9 -0
  13. package/dist/__tests__/freshness.test.d.ts.map +1 -0
  14. package/dist/__tests__/freshness.test.js +78 -0
  15. package/dist/__tests__/freshness.test.js.map +1 -0
  16. package/dist/__tests__/integration.test.d.ts +20 -0
  17. package/dist/__tests__/integration.test.d.ts.map +1 -0
  18. package/dist/__tests__/integration.test.js +178 -0
  19. package/dist/__tests__/integration.test.js.map +1 -0
  20. package/dist/__tests__/register-mcp-graph-adapters.test.d.ts +12 -0
  21. package/dist/__tests__/register-mcp-graph-adapters.test.d.ts.map +1 -0
  22. package/dist/__tests__/register-mcp-graph-adapters.test.js +47 -0
  23. package/dist/__tests__/register-mcp-graph-adapters.test.js.map +1 -0
  24. package/dist/__tests__/session-results-read-port.test.d.ts +13 -0
  25. package/dist/__tests__/session-results-read-port.test.d.ts.map +1 -0
  26. package/dist/__tests__/session-results-read-port.test.js +151 -0
  27. package/dist/__tests__/session-results-read-port.test.js.map +1 -0
  28. package/dist/__tests__/sqlite-graph-read-port.test.d.ts +12 -0
  29. package/dist/__tests__/sqlite-graph-read-port.test.d.ts.map +1 -0
  30. package/dist/__tests__/sqlite-graph-read-port.test.js +322 -0
  31. package/dist/__tests__/sqlite-graph-read-port.test.js.map +1 -0
  32. package/dist/__tests__/tool-descriptor.test.d.ts +9 -0
  33. package/dist/__tests__/tool-descriptor.test.d.ts.map +1 -0
  34. package/dist/__tests__/tool-descriptor.test.js +55 -0
  35. package/dist/__tests__/tool-descriptor.test.js.map +1 -0
  36. package/dist/catalog-generation.d.ts +22 -0
  37. package/dist/catalog-generation.d.ts.map +1 -0
  38. package/dist/catalog-generation.js +21 -0
  39. package/dist/catalog-generation.js.map +1 -0
  40. package/dist/command.d.ts +3 -0
  41. package/dist/command.d.ts.map +1 -0
  42. package/dist/command.js +111 -0
  43. package/dist/command.js.map +1 -0
  44. package/dist/freshness.d.ts +50 -0
  45. package/dist/freshness.d.ts.map +1 -0
  46. package/dist/freshness.js +96 -0
  47. package/dist/freshness.js.map +1 -0
  48. package/dist/graph-read-port.d.ts +111 -0
  49. package/dist/graph-read-port.d.ts.map +1 -0
  50. package/dist/graph-read-port.js +16 -0
  51. package/dist/graph-read-port.js.map +1 -0
  52. package/dist/index.d.ts +9 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +9 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/mcp-error.d.ts +15 -0
  57. package/dist/mcp-error.d.ts.map +1 -0
  58. package/dist/mcp-error.js +5 -0
  59. package/dist/mcp-error.js.map +1 -0
  60. package/dist/register-mcp-graph-adapters.d.ts +3 -0
  61. package/dist/register-mcp-graph-adapters.d.ts.map +1 -0
  62. package/dist/register-mcp-graph-adapters.js +21 -0
  63. package/dist/register-mcp-graph-adapters.js.map +1 -0
  64. package/dist/result-dto.d.ts +63 -0
  65. package/dist/result-dto.d.ts.map +1 -0
  66. package/dist/result-dto.js +11 -0
  67. package/dist/result-dto.js.map +1 -0
  68. package/dist/results-read-port.d.ts +43 -0
  69. package/dist/results-read-port.d.ts.map +1 -0
  70. package/dist/results-read-port.js +13 -0
  71. package/dist/results-read-port.js.map +1 -0
  72. package/dist/server.d.ts +84 -0
  73. package/dist/server.d.ts.map +1 -0
  74. package/dist/server.js +153 -0
  75. package/dist/server.js.map +1 -0
  76. package/dist/session-results-read-port.d.ts +42 -0
  77. package/dist/session-results-read-port.d.ts.map +1 -0
  78. package/dist/session-results-read-port.js +147 -0
  79. package/dist/session-results-read-port.js.map +1 -0
  80. package/dist/sqlite-graph-read-port.d.ts +88 -0
  81. package/dist/sqlite-graph-read-port.d.ts.map +1 -0
  82. package/dist/sqlite-graph-read-port.js +304 -0
  83. package/dist/sqlite-graph-read-port.js.map +1 -0
  84. package/dist/symbol-dto.d.ts +58 -0
  85. package/dist/symbol-dto.d.ts.map +1 -0
  86. package/dist/symbol-dto.js +12 -0
  87. package/dist/symbol-dto.js.map +1 -0
  88. package/dist/tool.d.ts +6 -0
  89. package/dist/tool.d.ts.map +1 -0
  90. package/dist/tool.js +33 -0
  91. package/dist/tool.js.map +1 -0
  92. package/dist/tools/__tests__/graph-handlers.test.d.ts +11 -0
  93. package/dist/tools/__tests__/graph-handlers.test.d.ts.map +1 -0
  94. package/dist/tools/__tests__/graph-handlers.test.js +415 -0
  95. package/dist/tools/__tests__/graph-handlers.test.js.map +1 -0
  96. package/dist/tools/__tests__/graph-walk.test.d.ts +9 -0
  97. package/dist/tools/__tests__/graph-walk.test.d.ts.map +1 -0
  98. package/dist/tools/__tests__/graph-walk.test.js +72 -0
  99. package/dist/tools/__tests__/graph-walk.test.js.map +1 -0
  100. package/dist/tools/__tests__/refresh-graph.test.d.ts +11 -0
  101. package/dist/tools/__tests__/refresh-graph.test.d.ts.map +1 -0
  102. package/dist/tools/__tests__/refresh-graph.test.js +100 -0
  103. package/dist/tools/__tests__/refresh-graph.test.js.map +1 -0
  104. package/dist/tools/__tests__/result-handlers.test.d.ts +9 -0
  105. package/dist/tools/__tests__/result-handlers.test.d.ts.map +1 -0
  106. package/dist/tools/__tests__/result-handlers.test.js +194 -0
  107. package/dist/tools/__tests__/result-handlers.test.js.map +1 -0
  108. package/dist/tools/__tests__/schemas.test.d.ts +10 -0
  109. package/dist/tools/__tests__/schemas.test.d.ts.map +1 -0
  110. package/dist/tools/__tests__/schemas.test.js +73 -0
  111. package/dist/tools/__tests__/schemas.test.js.map +1 -0
  112. package/dist/tools/blast-radius.d.ts +12 -0
  113. package/dist/tools/blast-radius.d.ts.map +1 -0
  114. package/dist/tools/blast-radius.js +33 -0
  115. package/dist/tools/blast-radius.js.map +1 -0
  116. package/dist/tools/call-walk-tool.d.ts +17 -0
  117. package/dist/tools/call-walk-tool.d.ts.map +1 -0
  118. package/dist/tools/call-walk-tool.js +46 -0
  119. package/dist/tools/call-walk-tool.js.map +1 -0
  120. package/dist/tools/callees-of.d.ts +12 -0
  121. package/dist/tools/callees-of.d.ts.map +1 -0
  122. package/dist/tools/callees-of.js +20 -0
  123. package/dist/tools/callees-of.js.map +1 -0
  124. package/dist/tools/find-dead-code.d.ts +11 -0
  125. package/dist/tools/find-dead-code.d.ts.map +1 -0
  126. package/dist/tools/find-dead-code.js +26 -0
  127. package/dist/tools/find-dead-code.js.map +1 -0
  128. package/dist/tools/get-agent-catalog.d.ts +12 -0
  129. package/dist/tools/get-agent-catalog.d.ts.map +1 -0
  130. package/dist/tools/get-agent-catalog.js +23 -0
  131. package/dist/tools/get-agent-catalog.js.map +1 -0
  132. package/dist/tools/get-architecture.d.ts +11 -0
  133. package/dist/tools/get-architecture.d.ts.map +1 -0
  134. package/dist/tools/get-architecture.js +26 -0
  135. package/dist/tools/get-architecture.js.map +1 -0
  136. package/dist/tools/get-latest-findings.d.ts +13 -0
  137. package/dist/tools/get-latest-findings.d.ts.map +1 -0
  138. package/dist/tools/get-latest-findings.js +38 -0
  139. package/dist/tools/get-latest-findings.js.map +1 -0
  140. package/dist/tools/get-symbol.d.ts +18 -0
  141. package/dist/tools/get-symbol.d.ts.map +1 -0
  142. package/dist/tools/get-symbol.js +44 -0
  143. package/dist/tools/get-symbol.js.map +1 -0
  144. package/dist/tools/graph-walk.d.ts +50 -0
  145. package/dist/tools/graph-walk.d.ts.map +1 -0
  146. package/dist/tools/graph-walk.js +89 -0
  147. package/dist/tools/graph-walk.js.map +1 -0
  148. package/dist/tools/list-runs.d.ts +11 -0
  149. package/dist/tools/list-runs.d.ts.map +1 -0
  150. package/dist/tools/list-runs.js +37 -0
  151. package/dist/tools/list-runs.js.map +1 -0
  152. package/dist/tools/refresh-graph.d.ts +22 -0
  153. package/dist/tools/refresh-graph.d.ts.map +1 -0
  154. package/dist/tools/refresh-graph.js +75 -0
  155. package/dist/tools/refresh-graph.js.map +1 -0
  156. package/dist/tools/register.d.ts +13 -0
  157. package/dist/tools/register.d.ts.map +1 -0
  158. package/dist/tools/register.js +40 -0
  159. package/dist/tools/register.js.map +1 -0
  160. package/dist/tools/schemas.d.ts +54 -0
  161. package/dist/tools/schemas.d.ts.map +1 -0
  162. package/dist/tools/schemas.js +59 -0
  163. package/dist/tools/schemas.js.map +1 -0
  164. package/dist/tools/search-symbols.d.ts +12 -0
  165. package/dist/tools/search-symbols.d.ts.map +1 -0
  166. package/dist/tools/search-symbols.js +37 -0
  167. package/dist/tools/search-symbols.js.map +1 -0
  168. package/dist/tools/show-run.d.ts +12 -0
  169. package/dist/tools/show-run.d.ts.map +1 -0
  170. package/dist/tools/show-run.js +40 -0
  171. package/dist/tools/show-run.js.map +1 -0
  172. package/dist/tools/tool-result.d.ts +29 -0
  173. package/dist/tools/tool-result.d.ts.map +1 -0
  174. package/dist/tools/tool-result.js +39 -0
  175. package/dist/tools/tool-result.js.map +1 -0
  176. package/dist/tools/trace-path.d.ts +12 -0
  177. package/dist/tools/trace-path.d.ts.map +1 -0
  178. package/dist/tools/trace-path.js +57 -0
  179. package/dist/tools/trace-path.js.map +1 -0
  180. package/dist/tools/types.d.ts +20 -0
  181. package/dist/tools/types.d.ts.map +1 -0
  182. package/dist/tools/types.js +11 -0
  183. package/dist/tools/types.js.map +1 -0
  184. package/dist/tools/who-calls.d.ts +12 -0
  185. package/dist/tools/who-calls.d.ts.map +1 -0
  186. package/dist/tools/who-calls.js +21 -0
  187. package/dist/tools/who-calls.js.map +1 -0
  188. package/package.json +104 -0
@@ -0,0 +1,322 @@
1
+ /**
2
+ * `SqliteGraphReadPort` against a REAL in-memory `DataStore` (Task 6.1 steps 2,
3
+ * 4, 7 — ports return Result; persistence round-trip; generation snapshotting;
4
+ * forward-compat; no raw body in DTOs).
5
+ *
6
+ * Seeds a hand-built catalog through the graph engine's `CatalogRepo` (the same
7
+ * persistence path `runGraph` writes), then drives the port — no `runGraph`
8
+ * needed at this level. A separate suite seeds NOTHING to assert the
9
+ * missing-catalog ok-`fresh:false` contract.
10
+ */
11
+ import { readdirSync, readFileSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+ import { DataStoreFactory } from '@opensip-cli/datastore';
15
+ import { CatalogRepo } from '@opensip-cli/graph/internal';
16
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
17
+ import { workingTreeContextFromCatalog } from '../freshness.js';
18
+ import { SqliteGraphReadPort } from '../sqlite-graph-read-port.js';
19
+ const BUILT_AT = '2026-05-22T00:00:00.000Z';
20
+ /** A `FunctionOccurrence` with only the required fields set (pre-feature shape). */
21
+ function fnOcc(over) {
22
+ return {
23
+ qualifiedName: over.simpleName,
24
+ line: 1,
25
+ column: 0,
26
+ endLine: 5,
27
+ kind: 'function-declaration',
28
+ params: [],
29
+ returnType: null,
30
+ enclosingClass: null,
31
+ decorators: [],
32
+ visibility: 'module-local',
33
+ inTestFile: false,
34
+ definedInGenerated: false,
35
+ calls: [],
36
+ ...over,
37
+ };
38
+ }
39
+ function makeCatalog(over = {}) {
40
+ return {
41
+ version: '3.0',
42
+ tool: 'graph',
43
+ language: 'typescript',
44
+ builtAt: BUILT_AT,
45
+ cacheKey: 'ts-5.7.3-test',
46
+ filesFingerprint: '0\n',
47
+ functions: {},
48
+ ...over,
49
+ };
50
+ }
51
+ /** caller→target plus two unreachable functions, for blast + dead-code + arch. */
52
+ function seededCatalog(builtAt = BUILT_AT) {
53
+ return makeCatalog({
54
+ builtAt,
55
+ functions: {
56
+ caller: [
57
+ fnOcc({
58
+ bodyHash: 'h-caller',
59
+ simpleName: 'caller',
60
+ filePath: 'src/caller.ts',
61
+ line: 10,
62
+ column: 2,
63
+ endLine: 20,
64
+ // `calls[].to` holds RESOLVED body hashes (post stage-2), not names.
65
+ calls: [
66
+ {
67
+ to: ['h-target'],
68
+ line: 12,
69
+ column: 4,
70
+ resolution: 'static',
71
+ confidence: 'high',
72
+ text: 'target()',
73
+ },
74
+ ],
75
+ }),
76
+ ],
77
+ target: [fnOcc({ bodyHash: 'h-target', simpleName: 'target', filePath: 'src/target.ts' })],
78
+ lonely1: [fnOcc({ bodyHash: 'h-l1', simpleName: 'lonely1', filePath: 'src/l1.ts' })],
79
+ lonely2: [fnOcc({ bodyHash: 'h-l2', simpleName: 'lonely2', filePath: 'src/l2.ts' })],
80
+ },
81
+ });
82
+ }
83
+ let store;
84
+ beforeEach(() => {
85
+ store = DataStoreFactory.open({ backend: 'memory' });
86
+ });
87
+ afterEach(() => {
88
+ store.close();
89
+ });
90
+ function seed(catalog) {
91
+ new CatalogRepo(store).replaceAll(catalog);
92
+ }
93
+ describe('SqliteGraphReadPort — missing catalog', () => {
94
+ it('reports getGeneration ok with fresh:false (missing) and no auto-build', () => {
95
+ const port = new SqliteGraphReadPort({ store });
96
+ const out = port.getGeneration();
97
+ expect(out.ok).toBe(true);
98
+ if (out.ok) {
99
+ expect(out.value.data).toBeUndefined();
100
+ expect(out.value.freshness).toEqual({ fresh: false, reason: 'missing' });
101
+ }
102
+ });
103
+ it('resolveSymbolId on a missing catalog is ok with undefined data (not an error)', () => {
104
+ const port = new SqliteGraphReadPort({ store });
105
+ const out = port.resolveSymbolId('src/a.ts:1:0');
106
+ expect(out.ok).toBe(true);
107
+ expect(out.ok && out.value.data).toBeUndefined();
108
+ });
109
+ it('refresh without a rebuild provider returns a structured err', async () => {
110
+ const port = new SqliteGraphReadPort({ store });
111
+ const out = await port.refresh();
112
+ expect(out.ok).toBe(false);
113
+ expect(!out.ok && out.error.code).toBe('refresh-unavailable');
114
+ });
115
+ });
116
+ describe('SqliteGraphReadPort — seeded catalog reads', () => {
117
+ beforeEach(() => seed(seededCatalog()));
118
+ it('round-trips the catalog: getGeneration ok with the builtAt', () => {
119
+ const port = new SqliteGraphReadPort({ store });
120
+ const out = port.getGeneration();
121
+ expect(out.ok && out.value.data?.builtAt).toBe(BUILT_AT);
122
+ });
123
+ it('resolves a known symbolId to a metadata-only SymbolRef (bodyHash, never a raw body)', () => {
124
+ const port = new SqliteGraphReadPort({ store });
125
+ const out = port.resolveSymbolId('src/caller.ts:10:2');
126
+ expect(out.ok).toBe(true);
127
+ const ref = out.ok ? out.value.data : undefined;
128
+ expect(ref?.qualifiedName).toBe('caller');
129
+ expect(ref?.bodyHash).toBe('h-caller');
130
+ expect(ref).not.toHaveProperty('body');
131
+ expect(ref).not.toHaveProperty('source');
132
+ expect(ref).not.toHaveProperty('calls');
133
+ });
134
+ it('returns ok-undefined for an unknown symbolId', () => {
135
+ const port = new SqliteGraphReadPort({ store });
136
+ const out = port.resolveSymbolId('src/nope.ts:99:9');
137
+ expect(out.ok && out.value.data).toBeUndefined();
138
+ });
139
+ it('searches symbols by case-insensitive substring and applies the limit with truncated', () => {
140
+ const port = new SqliteGraphReadPort({ store });
141
+ const all = port.searchSymbols('lonely');
142
+ expect(all.ok && all.value.data.map((r) => r.qualifiedName).sort()).toEqual([
143
+ 'lonely1',
144
+ 'lonely2',
145
+ ]);
146
+ const capped = port.searchSymbols('lonely', { limit: 1 });
147
+ expect(capped.ok && capped.value.data).toHaveLength(1);
148
+ expect(capped.ok && capped.value.truncated).toBe(true);
149
+ });
150
+ it('findBySpan returns the occurrence whose [line, endLine] span encloses the line', () => {
151
+ const port = new SqliteGraphReadPort({ store });
152
+ const out = port.findBySpan('src/caller.ts', 15);
153
+ expect(out.ok && out.value.data.map((r) => r.qualifiedName)).toEqual(['caller']);
154
+ const none = port.findBySpan('src/caller.ts', 99);
155
+ expect(none.ok && none.value.data).toHaveLength(0);
156
+ });
157
+ it('exposes caller/callee adjacency snapshots that resolve body hashes to SymbolRefs', () => {
158
+ const port = new SqliteGraphReadPort({ store });
159
+ const callee = port.calleeGraph();
160
+ expect(callee.ok).toBe(true);
161
+ if (callee.ok) {
162
+ const snap = callee.value.data;
163
+ expect(snap.edges.get('h-caller')).toContain('h-target');
164
+ expect(snap.resolve('h-target')?.qualifiedName).toBe('target');
165
+ }
166
+ const caller = port.callerGraph();
167
+ expect(caller.ok && caller.value.data.edges.get('h-target')).toContain('h-caller');
168
+ });
169
+ it('computes blast via graph’s canonical scoring (target has one direct caller)', () => {
170
+ const port = new SqliteGraphReadPort({ store });
171
+ const out = port.blast('src/target.ts:1:0');
172
+ expect(out.ok).toBe(true);
173
+ const dto = out.ok ? out.value.data : undefined;
174
+ expect(dto?.direct).toBe(1);
175
+ expect(dto?.symbol.qualifiedName).toBe('target');
176
+ });
177
+ it('blast on an unknown symbolId is ok with undefined data', () => {
178
+ const port = new SqliteGraphReadPort({ store });
179
+ const out = port.blast('src/nope.ts:1:0');
180
+ expect(out.ok && out.value.data).toBeUndefined();
181
+ });
182
+ it('reports dead code (orphans) and honors the limit with truncated', () => {
183
+ const port = new SqliteGraphReadPort({ store });
184
+ const all = port.deadCode();
185
+ expect(all.ok).toBe(true);
186
+ const total = all.ok ? all.value.data.length : 0;
187
+ expect(total).toBeGreaterThanOrEqual(2);
188
+ const capped = port.deadCode(1);
189
+ expect(capped.ok && capped.value.data).toHaveLength(1);
190
+ expect(capped.ok && capped.value.truncated).toBe(true);
191
+ });
192
+ it('summarizes architecture (function/edge counts, languages, hotspots)', () => {
193
+ const port = new SqliteGraphReadPort({ store });
194
+ const out = port.architectureSummary();
195
+ expect(out.ok).toBe(true);
196
+ if (out.ok) {
197
+ expect(out.value.data.functionCount).toBe(4);
198
+ expect(out.value.data.languages).toEqual(['typescript']);
199
+ expect(out.value.data.edgeCount).toBeGreaterThanOrEqual(1);
200
+ }
201
+ });
202
+ });
203
+ describe('SqliteGraphReadPort — freshness', () => {
204
+ it('reports fresh:true (unverified) when no freshness context is wired', () => {
205
+ seed(seededCatalog());
206
+ const port = new SqliteGraphReadPort({ store });
207
+ expect(port.freshness()).toEqual({ fresh: true, builtAt: BUILT_AT });
208
+ });
209
+ it('a pre-fingerprint catalog still loads and classifies without throwing (forward-compat)', () => {
210
+ const { filesFingerprint, ...withoutFp } = seededCatalog();
211
+ void filesFingerprint;
212
+ seed(withoutFp);
213
+ // Freshness context provider returns undefined for a catalog with no fingerprint.
214
+ const port = new SqliteGraphReadPort({
215
+ store,
216
+ freshnessContext: workingTreeContextFromCatalog,
217
+ });
218
+ expect(() => port.freshness()).not.toThrow();
219
+ expect(port.freshness().fresh).toBe(true);
220
+ // Reads still work over the older-shaped occurrences.
221
+ expect(port.searchSymbols('caller').ok).toBe(true);
222
+ });
223
+ });
224
+ describe('SqliteGraphReadPort — generation snapshotting (TOCTOU-safe refresh)', () => {
225
+ it('an interleaved read sees the stable OLD generation until a slow refresh swaps', async () => {
226
+ seed(seededCatalog(BUILT_AT));
227
+ const NEW_BUILT_AT = '2026-06-01T00:00:00.000Z';
228
+ let releaseRebuild;
229
+ const rebuildGate = new Promise((resolve) => {
230
+ releaseRebuild = resolve;
231
+ });
232
+ const port = new SqliteGraphReadPort({ store, rebuild: () => rebuildGate });
233
+ const builtAt = () => {
234
+ const out = port.getGeneration();
235
+ return out.ok ? out.value.data?.builtAt : undefined;
236
+ };
237
+ // Pin the current generation.
238
+ expect(builtAt()).toBe(BUILT_AT);
239
+ // Start a refresh but do not let the rebuild resolve yet.
240
+ const refreshing = port.refresh();
241
+ // Mid-rebuild, reads still see the OLD generation.
242
+ expect(builtAt()).toBe(BUILT_AT);
243
+ // Let the rebuild complete; the generation swaps atomically on resolve.
244
+ releaseRebuild(seededCatalog(NEW_BUILT_AT));
245
+ const result = await refreshing;
246
+ expect(result.ok).toBe(true);
247
+ expect(builtAt()).toBe(NEW_BUILT_AT);
248
+ });
249
+ });
250
+ describe('SqliteGraphReadPort — every read degrades gracefully on a missing catalog', () => {
251
+ it('returns empty/undefined ok results (never throws, never auto-builds)', () => {
252
+ const port = new SqliteGraphReadPort({ store });
253
+ const search = port.searchSymbols('x');
254
+ expect(search.ok && search.value.data).toEqual([]);
255
+ const span = port.findBySpan('a.ts', 1);
256
+ expect(span.ok && span.value.data).toEqual([]);
257
+ const blast = port.blast('a.ts:1:0');
258
+ expect(blast.ok && blast.value.data).toBeUndefined();
259
+ const dead = port.deadCode();
260
+ expect(dead.ok && dead.value.data).toEqual([]);
261
+ const caller = port.callerGraph();
262
+ if (caller.ok) {
263
+ expect(caller.value.data.edges.size).toBe(0);
264
+ expect(caller.value.data.resolve('h')).toBeUndefined();
265
+ }
266
+ const callee = port.calleeGraph();
267
+ if (callee.ok)
268
+ expect(callee.value.data.edges.size).toBe(0);
269
+ const arch = port.architectureSummary();
270
+ expect(arch.ok && arch.value.data.functionCount).toBe(0);
271
+ expect(arch.ok && arch.value.freshness).toEqual({ fresh: false, reason: 'missing' });
272
+ });
273
+ });
274
+ describe('SqliteGraphReadPort — search excludes module-init occurrences', () => {
275
+ it('a <module-init> occurrence whose name matches the query is not returned', () => {
276
+ seed(makeCatalog({
277
+ functions: {
278
+ '<module-init:mod.ts>': [
279
+ fnOcc({
280
+ bodyHash: 'h-mod',
281
+ simpleName: 'modinit-target',
282
+ filePath: 'mod.ts',
283
+ kind: 'module-init',
284
+ }),
285
+ ],
286
+ real: [fnOcc({ bodyHash: 'h-real', simpleName: 'modinit-real', filePath: 'real.ts' })],
287
+ },
288
+ }));
289
+ const port = new SqliteGraphReadPort({ store });
290
+ const out = port.searchSymbols('modinit');
291
+ expect(out.ok && out.value.data.map((r) => r.qualifiedName)).toEqual(['modinit-real']);
292
+ });
293
+ });
294
+ describe('SqliteGraphReadPort — concurrent refresh serializes to one rebuild', () => {
295
+ it('two overlapping refresh() calls share a single in-flight build', async () => {
296
+ seed(seededCatalog());
297
+ let rebuilds = 0;
298
+ const port = new SqliteGraphReadPort({
299
+ store,
300
+ rebuild: async () => {
301
+ rebuilds += 1;
302
+ await Promise.resolve();
303
+ return seededCatalog('2026-07-01T00:00:00.000Z');
304
+ },
305
+ });
306
+ const [a, b] = await Promise.all([port.refresh(), port.refresh()]);
307
+ expect(a.ok && b.ok).toBe(true);
308
+ expect(rebuilds).toBe(1);
309
+ });
310
+ });
311
+ describe('persistence invariant — MCP adds no migration', () => {
312
+ it('no datastore migration references "mcp" (read-only server owns no schema)', () => {
313
+ const migrationsDir = fileURLToPath(new URL('../../../datastore/migrations', import.meta.url));
314
+ const sqlFiles = readdirSync(migrationsDir).filter((f) => f.endsWith('.sql'));
315
+ expect(sqlFiles.length).toBeGreaterThan(0);
316
+ for (const file of sqlFiles) {
317
+ const contents = readFileSync(join(migrationsDir, file), 'utf8').toLowerCase();
318
+ expect(contents, `${file} must not introduce an mcp-owned table`).not.toContain('mcp');
319
+ }
320
+ });
321
+ });
322
+ //# sourceMappingURL=sqlite-graph-read-port.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-graph-read-port.test.js","sourceRoot":"","sources":["../../src/__tests__/sqlite-graph-read-port.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,gBAAgB,EAAkB,MAAM,wBAAwB,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAErE,OAAO,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAInE,MAAM,QAAQ,GAAG,0BAA0B,CAAC;AAE5C,oFAAoF;AACpF,SAAS,KAAK,CACZ,IAIC;IAED,OAAO;QACL,aAAa,EAAE,IAAI,CAAC,UAAU;QAC9B,IAAI,EAAE,CAAC;QACP,MAAM,EAAE,CAAC;QACT,OAAO,EAAE,CAAC;QACV,IAAI,EAAE,sBAAsB;QAC5B,MAAM,EAAE,EAAE;QACV,UAAU,EAAE,IAAI;QAChB,cAAc,EAAE,IAAI;QACpB,UAAU,EAAE,EAAE;QACd,UAAU,EAAE,cAAc;QAC1B,UAAU,EAAE,KAAK;QACjB,kBAAkB,EAAE,KAAK;QACzB,KAAK,EAAE,EAAE;QACT,GAAG,IAAI;KACR,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,OAAyB,EAAE;IAC9C,OAAO;QACL,OAAO,EAAE,KAAK;QACd,IAAI,EAAE,OAAO;QACb,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,QAAQ;QACjB,QAAQ,EAAE,eAAe;QACzB,gBAAgB,EAAE,KAAK;QACvB,SAAS,EAAE,EAAE;QACb,GAAG,IAAI;KACR,CAAC;AACJ,CAAC;AAED,kFAAkF;AAClF,SAAS,aAAa,CAAC,OAAO,GAAG,QAAQ;IACvC,OAAO,WAAW,CAAC;QACjB,OAAO;QACP,SAAS,EAAE;YACT,MAAM,EAAE;gBACN,KAAK,CAAC;oBACJ,QAAQ,EAAE,UAAU;oBACpB,UAAU,EAAE,QAAQ;oBACpB,QAAQ,EAAE,eAAe;oBACzB,IAAI,EAAE,EAAE;oBACR,MAAM,EAAE,CAAC;oBACT,OAAO,EAAE,EAAE;oBACX,qEAAqE;oBACrE,KAAK,EAAE;wBACL;4BACE,EAAE,EAAE,CAAC,UAAU,CAAC;4BAChB,IAAI,EAAE,EAAE;4BACR,MAAM,EAAE,CAAC;4BACT,UAAU,EAAE,QAAQ;4BACpB,UAAU,EAAE,MAAM;4BAClB,IAAI,EAAE,UAAU;yBACjB;qBACF;iBACF,CAAC;aACH;YACD,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC,CAAC;YAC1F,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC;YACpF,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC;SACrF;KACF,CAAC,CAAC;AACL,CAAC;AAED,IAAI,KAAgB,CAAC;AAErB,UAAU,CAAC,GAAG,EAAE;IACd,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;AACvD,CAAC,CAAC,CAAC;AAEH,SAAS,CAAC,GAAG,EAAE;IACb,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC,CAAC,CAAC;AAEH,SAAS,IAAI,CAAC,OAAgB;IAC5B,IAAI,WAAW,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;AAC7C,CAAC;AAED,QAAQ,CAAC,uCAAuC,EAAE,GAAG,EAAE;IACrD,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;QAC/E,MAAM,IAAI,GAAG,IAAI,mBAAmB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACjC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;YACX,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,CAAC;YACvC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+EAA+E,EAAE,GAAG,EAAE;QACvF,MAAM,IAAI,GAAG,IAAI,mBAAmB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC;QACjD,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1B,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;QAC3E,MAAM,IAAI,GAAG,IAAI,mBAAmB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAChD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACjC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3B,MAAM,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,4CAA4C,EAAE,GAAG,EAAE;IAC1D,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;IAExC,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACpE,MAAM,IAAI,GAAG,IAAI,mBAAmB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACjC,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qFAAqF,EAAE,GAAG,EAAE;QAC7F,MAAM,IAAI,GAAG,IAAI,mBAAmB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,eAAe,CAAC,oBAAoB,CAAC,CAAC;QACvD,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1B,MAAM,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;QAChD,MAAM,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC1C,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACvC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QACvC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QACzC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,IAAI,GAAG,IAAI,mBAAmB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,eAAe,CAAC,kBAAkB,CAAC,CAAC;QACrD,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qFAAqF,EAAE,GAAG,EAAE;QAC7F,MAAM,IAAI,GAAG,IAAI,mBAAmB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QACzC,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC;YAC1E,SAAS;YACT,SAAS;SACV,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QAC1D,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACvD,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gFAAgF,EAAE,GAAG,EAAE;QACxF,MAAM,IAAI,GAAG,IAAI,mBAAmB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC;QACjD,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QACjF,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC;QAClD,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kFAAkF,EAAE,GAAG,EAAE;QAC1F,MAAM,IAAI,GAAG,IAAI,mBAAmB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAClC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;YACd,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;YACzD,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,aAAa,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjE,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAClC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6EAA6E,EAAE,GAAG,EAAE;QACrF,MAAM,IAAI,GAAG,IAAI,mBAAmB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;QAC5C,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1B,MAAM,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;QAChD,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5B,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,IAAI,GAAG,IAAI,mBAAmB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;QAC1C,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QACzE,MAAM,IAAI,GAAG,IAAI,mBAAmB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC5B,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1B,MAAM,KAAK,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,KAAK,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACvD,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,MAAM,IAAI,GAAG,IAAI,mBAAmB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAC;QACvC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;YACX,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC7C,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;YACzD,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC/C,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;QAC5E,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;QACtB,MAAM,IAAI,GAAG,IAAI,mBAAmB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAChD,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wFAAwF,EAAE,GAAG,EAAE;QAChG,MAAM,EAAE,gBAAgB,EAAE,GAAG,SAAS,EAAE,GAAG,aAAa,EAAE,CAAC;QAC3D,KAAK,gBAAgB,CAAC;QACtB,IAAI,CAAC,SAAS,CAAC,CAAC;QAChB,kFAAkF;QAClF,MAAM,IAAI,GAAG,IAAI,mBAAmB,CAAC;YACnC,KAAK;YACL,gBAAgB,EAAE,6BAA6B;SAChD,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QAC7C,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1C,sDAAsD;QACtD,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,qEAAqE,EAAE,GAAG,EAAE;IACnF,EAAE,CAAC,+EAA+E,EAAE,KAAK,IAAI,EAAE;QAC7F,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC9B,MAAM,YAAY,GAAG,0BAA0B,CAAC;QAChD,IAAI,cAA2C,CAAC;QAChD,MAAM,WAAW,GAAG,IAAI,OAAO,CAAU,CAAC,OAAO,EAAE,EAAE;YACnD,cAAc,GAAG,OAAO,CAAC;QAC3B,CAAC,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,mBAAmB,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;QAE5E,MAAM,OAAO,GAAG,GAAuB,EAAE;YACvC,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;YACjC,OAAO,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;QACtD,CAAC,CAAC;QAEF,8BAA8B;QAC9B,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAEjC,0DAA0D;QAC1D,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,mDAAmD;QACnD,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAEjC,wEAAwE;QACxE,cAAc,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC;QAChC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,2EAA2E,EAAE,GAAG,EAAE;IACzF,EAAE,CAAC,sEAAsE,EAAE,GAAG,EAAE;QAC9E,MAAM,IAAI,GAAG,IAAI,mBAAmB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;QACvC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACnD,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACrC,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,CAAC;QACrD,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC7B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAClC,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;YACd,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC7C,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QACzD,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAClC,IAAI,MAAM,CAAC,EAAE;YAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5D,MAAM,IAAI,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACzD,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IACvF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,+DAA+D,EAAE,GAAG,EAAE;IAC7E,EAAE,CAAC,yEAAyE,EAAE,GAAG,EAAE;QACjF,IAAI,CACF,WAAW,CAAC;YACV,SAAS,EAAE;gBACT,sBAAsB,EAAE;oBACtB,KAAK,CAAC;wBACJ,QAAQ,EAAE,OAAO;wBACjB,UAAU,EAAE,gBAAgB;wBAC5B,QAAQ,EAAE,QAAQ;wBAClB,IAAI,EAAE,aAAa;qBACpB,CAAC;iBACH;gBACD,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;aACvF;SACF,CAAC,CACH,CAAC;QACF,MAAM,IAAI,GAAG,IAAI,mBAAmB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;QAC1C,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC;IACzF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,oEAAoE,EAAE,GAAG,EAAE;IAClF,EAAE,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;QAC9E,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;QACtB,IAAI,QAAQ,GAAG,CAAC,CAAC;QACjB,MAAM,IAAI,GAAG,IAAI,mBAAmB,CAAC;YACnC,KAAK;YACL,OAAO,EAAE,KAAK,IAAI,EAAE;gBAClB,QAAQ,IAAI,CAAC,CAAC;gBACd,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;gBACxB,OAAO,aAAa,CAAC,0BAA0B,CAAC,CAAC;YACnD,CAAC;SACF,CAAC,CAAC;QACH,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACnE,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,+CAA+C,EAAE,GAAG,EAAE;IAC7D,EAAE,CAAC,2EAA2E,EAAE,GAAG,EAAE;QACnF,MAAM,aAAa,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,+BAA+B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC/F,MAAM,QAAQ,GAAG,WAAW,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QAC9E,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAC3C,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;YAC/E,MAAM,CAAC,QAAQ,EAAE,GAAG,IAAI,wCAAwC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACzF,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The `@opensip-cli/mcp` Tool descriptor + tool registration wiring (Task 6.1).
3
+ *
4
+ * Asserts the bundled tool descriptor (`mcp` identity, the single `mcp` command,
5
+ * the `mcp-graph-adapter` capability registrar) and that `registerMcpTools`
6
+ * mounts all 13 tools (9 graph + 4 result) through the server's register seam.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=tool-descriptor.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-descriptor.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/tool-descriptor.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The `@opensip-cli/mcp` Tool descriptor + tool registration wiring (Task 6.1).
3
+ *
4
+ * Asserts the bundled tool descriptor (`mcp` identity, the single `mcp` command,
5
+ * the `mcp-graph-adapter` capability registrar) and that `registerMcpTools`
6
+ * mounts all 13 tools (9 graph + 4 result) through the server's register seam.
7
+ */
8
+ import { describe, expect, it } from 'vitest';
9
+ import { mcpTool, MCP_IDENTITY, MCP_STABLE_ID } from '../index.js';
10
+ import { registerMcpTools } from '../tools/register.js';
11
+ describe('mcpTool descriptor', () => {
12
+ it('declares the mcp identity, one command, and the graph-adapter registrar', () => {
13
+ expect(MCP_IDENTITY.name).toBe('mcp');
14
+ expect(mcpTool.identity.name).toBe('mcp');
15
+ expect(mcpTool.metadata.id).toBe(MCP_STABLE_ID);
16
+ const commands = mcpTool.commands ?? [];
17
+ expect(commands).toHaveLength(1);
18
+ expect(commands[0]?.name).toBe('mcp');
19
+ expect(mcpTool.extensionPoints?.capabilityRegistrars).toHaveProperty('mcp-graph-adapter');
20
+ });
21
+ });
22
+ describe('registerMcpTools', () => {
23
+ it('mounts all 13 MCP tools (9 graph + 4 result) on the server', () => {
24
+ const names = [];
25
+ const server = {
26
+ register: (name) => {
27
+ names.push(name);
28
+ return undefined;
29
+ },
30
+ };
31
+ const deps = {
32
+ graph: {},
33
+ results: {},
34
+ validToolIds: new Set(),
35
+ };
36
+ registerMcpTools(server, deps);
37
+ expect(names).toHaveLength(13);
38
+ expect(new Set(names)).toEqual(new Set([
39
+ 'search_symbols',
40
+ 'get_symbol',
41
+ 'who_calls',
42
+ 'callees_of',
43
+ 'trace_path',
44
+ 'blast_radius',
45
+ 'find_dead_code',
46
+ 'get_architecture',
47
+ 'refresh_graph',
48
+ 'get_agent_catalog',
49
+ 'list_runs',
50
+ 'show_run',
51
+ 'get_latest_findings',
52
+ ]));
53
+ });
54
+ });
55
+ //# sourceMappingURL=tool-descriptor.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-descriptor.test.js","sourceRoot":"","sources":["../../src/__tests__/tool-descriptor.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAOxD,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,yEAAyE,EAAE,GAAG,EAAE;QACjF,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1C,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAChD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;QACxC,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACjC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtC,MAAM,CAAC,OAAO,CAAC,eAAe,EAAE,oBAAoB,CAAC,CAAC,cAAc,CAAC,mBAAmB,CAAC,CAAC;IAC5F,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACpE,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG;YACb,QAAQ,EAAE,CAAC,IAAY,EAAE,EAAE;gBACzB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACjB,OAAO,SAAS,CAAC;YACnB,CAAC;SAC2B,CAAC;QAC/B,MAAM,IAAI,GAAgB;YACxB,KAAK,EAAE,EAAmB;YAC1B,OAAO,EAAE,EAAqB;YAC9B,YAAY,EAAE,IAAI,GAAG,EAAE;SACxB,CAAC;QAEF,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAE/B,MAAM,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QAC/B,MAAM,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAC5B,IAAI,GAAG,CAAC;YACN,gBAAgB;YAChB,YAAY;YACZ,WAAW;YACX,YAAY;YACZ,YAAY;YACZ,cAAc;YACd,gBAAgB;YAChB,kBAAkB;YAClB,eAAe;YACf,mBAAmB;YACnB,WAAW;YACX,UAAU;YACV,qBAAqB;SACtB,CAAC,CACH,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Immutable catalog generation (ADR-0084).
3
+ *
4
+ * The server holds EXACTLY ONE generation in memory at a time: an immutable
5
+ * `{ catalog, indexes, builtAt }` snapshot. Reads pin the current generation;
6
+ * `refresh()` builds the next and swaps the reference atomically on completion
7
+ * (a single synchronous assignment after the async rebuild resolves), so
8
+ * in-flight reads keep the old generation until the swap — TOCTOU-safe. The
9
+ * `Indexes` are derived per generation via the graph engine's canonical
10
+ * `buildIndexes`, never persisted.
11
+ */
12
+ import type { Catalog, Indexes } from '@opensip-cli/graph';
13
+ /** One immutable in-memory snapshot of the served catalog + its derived indexes. */
14
+ export interface CatalogGeneration {
15
+ readonly catalog: Catalog;
16
+ readonly indexes: Indexes;
17
+ /** ISO timestamp the catalog was built at (its `builtAt`). */
18
+ readonly builtAt: string;
19
+ }
20
+ /** Derive a generation snapshot from a loaded catalog (builds adjacency once). */
21
+ export declare function createGeneration(catalog: Catalog): CatalogGeneration;
22
+ //# sourceMappingURL=catalog-generation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-generation.d.ts","sourceRoot":"","sources":["../src/catalog-generation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE3D,oFAAoF;AACpF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,iBAAiB,CAMpE"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Immutable catalog generation (ADR-0084).
3
+ *
4
+ * The server holds EXACTLY ONE generation in memory at a time: an immutable
5
+ * `{ catalog, indexes, builtAt }` snapshot. Reads pin the current generation;
6
+ * `refresh()` builds the next and swaps the reference atomically on completion
7
+ * (a single synchronous assignment after the async rebuild resolves), so
8
+ * in-flight reads keep the old generation until the swap — TOCTOU-safe. The
9
+ * `Indexes` are derived per generation via the graph engine's canonical
10
+ * `buildIndexes`, never persisted.
11
+ */
12
+ import { buildIndexes } from '@opensip-cli/graph/internal';
13
+ /** Derive a generation snapshot from a loaded catalog (builds adjacency once). */
14
+ export function createGeneration(catalog) {
15
+ return {
16
+ catalog,
17
+ indexes: buildIndexes(catalog),
18
+ builtAt: catalog.builtAt,
19
+ };
20
+ }
21
+ //# sourceMappingURL=catalog-generation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-generation.js","sourceRoot":"","sources":["../src/catalog-generation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAY3D,kFAAkF;AAClF,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAC/C,OAAO;QACL,OAAO;QACP,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC;QAC9B,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ToolCliContext } from '@opensip-cli/core';
2
+ export declare const mcpCommandSpec: import("@opensip-cli/core").PrimaryCommandSpecDraft<unknown, ToolCliContext>;
3
+ //# sourceMappingURL=command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAY,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAIlE,eAAO,MAAM,cAAc,8EA2FzB,CAAC"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * The `opensip mcp` command (ADR-0084).
3
+ *
4
+ * A long-lived, BLOCKING stdio JSON-RPC server. `output: 'raw-stream'` +
5
+ * `rawStreamReason: 'mcp-stdio'` because the protocol genuinely owns stdout: an
6
+ * MCP client speaks JSON-RPC frames over this command's stdin/stdout for the
7
+ * whole serve lifetime, so the host must render NOTHING and the handler owns its
8
+ * entire output surface. This is the documented raw-stream escape hatch — NOT a
9
+ * bypass of the `SignalEnvelope`/`CommandResult` currency: there is no run
10
+ * verdict to render, only a transport. Every diagnostic goes to stderr (the
11
+ * server routes the structured logger sink there for the serve lifetime); stdout
12
+ * carries only JSON-RPC. See ADR-0084 and `server.ts`.
13
+ *
14
+ * The handler captures the entered `RunScope` (never `currentScope()`), opens the
15
+ * per-run datastore through the documented `cli.scope.datastore()` seam, builds
16
+ * the two read ports from it, and hands the captured scope + ports to the server
17
+ * — which re-enters the scope around every tool dispatch (the EventEmitter ALS
18
+ * fix). It resolves to a clean exit (0) when the transport closes on stdin EOF.
19
+ */
20
+ import { definePrimaryCommand, readPackageVersion } from '@opensip-cli/core';
21
+ import { runGraph } from '@opensip-cli/graph/internal';
22
+ import { workingTreeContextFromCatalog } from './freshness.js';
23
+ import { McpStdioServer } from './server.js';
24
+ import { SessionResultsReadPort } from './session-results-read-port.js';
25
+ import { SqliteGraphReadPort } from './sqlite-graph-read-port.js';
26
+ import { registerMcpTools } from './tools/register.js';
27
+ export const mcpCommandSpec = definePrimaryCommand({
28
+ description: 'Serve the OpenSIP call graph + stored results to MCP agents over stdio',
29
+ commonFlags: ['cwd'],
30
+ scope: 'project',
31
+ output: 'raw-stream',
32
+ rawStreamReason: 'mcp-stdio',
33
+ handler: async (_opts, cli) => {
34
+ // The host enters a concrete `RunScope` for the project-scoped command and
35
+ // hands it to tools as the narrowed `ToolScope` view; the MCP server needs
36
+ // the full `RunScope` — for `runWithScope` re-entry AND the `tools` registry
37
+ // the results port replays through — so we narrow back to the runtime type.
38
+ // We capture it here (NOT `currentScope()`) because the SDK's EventEmitter
39
+ // dispatch would lose the ambient scope inside tool handlers.
40
+ const scope = cli.scope;
41
+ // Documented per-run datastore seam (no raw `DataStore.db`, no `SessionRepo`).
42
+ const store = cli.scope.datastore();
43
+ if (store === undefined) {
44
+ await cli.reportFailure({
45
+ message: 'opensip mcp requires a project datastore, but none is available.',
46
+ suggestion: 'Run `opensip mcp` from inside an opensip-cli project (where `opensip init` has been run).',
47
+ code: 'MCP.DATASTORE_UNAVAILABLE',
48
+ exitCode: 2,
49
+ log: { evt: 'mcp.server.datastore_unavailable', level: 'error' },
50
+ });
51
+ return;
52
+ }
53
+ // Pre-build both read ports from the captured datastore (Phase 2 impls).
54
+ //
55
+ // Freshness (Task 4.4): the provider derives the working-tree
56
+ // `ValidationContext` from the served catalog's OWN recorded inputs
57
+ // (`workingTreeContextFromCatalog`) — the file set (recovered from the
58
+ // persisted `filesFingerprint`, in order) plus the catalog's language +
59
+ // cacheKey. `classifyCatalog` then re-stats those files, so a mutated/deleted
60
+ // tracked file flips `fresh` to false. (Newly-added files / a tsconfig change
61
+ // are catalog-additive and resolved by the explicit `refresh_graph` op — see
62
+ // the helper's doc for the precise approximation.)
63
+ //
64
+ // Rebuild (Task 4.4): `refresh_graph` runs the graph engine's programmatic
65
+ // build (`runGraph`) over the project root, threading the same datastore so
66
+ // the rebuilt catalog persists where the port reads it. v1 is the exact
67
+ // single-program build (no cloud egress, no live render).
68
+ const projectRoot = scope.projectContext?.projectRoot ?? process.cwd();
69
+ /**
70
+ * The `refresh_graph` rebuild thunk: runs the graph engine's programmatic
71
+ * build over the project root and returns the fresh catalog.
72
+ *
73
+ * @throws {Error} when the build discovers no source files (no catalog
74
+ * produced) — surfaced to the refresh tool as an infra-boundary failure.
75
+ */
76
+ async function rebuild() {
77
+ const outcome = await runGraph({ cwd: projectRoot, datastore: store });
78
+ if (outcome.catalog === null) {
79
+ throw new Error('graph rebuild produced no catalog (no source files discovered).');
80
+ }
81
+ return outcome.catalog;
82
+ }
83
+ const graph = new SqliteGraphReadPort({
84
+ store,
85
+ freshnessContext: workingTreeContextFromCatalog,
86
+ rebuild,
87
+ });
88
+ const results = new SessionResultsReadPort({ store, tools: scope.tools });
89
+ const server = new McpStdioServer({
90
+ scope,
91
+ graph,
92
+ results,
93
+ version: readPackageVersion(import.meta.url),
94
+ });
95
+ // Mount the tool catalog through the server's scope-wrapping register seam.
96
+ // `validToolIds` lets the result tools reject an unknown `tool` argument. It
97
+ // must be the per-tool LAYOUT KEY (`fit`/`sim`/`graph`/`yagni`) — the key
98
+ // sessions are stored under and the value `sessions show --tool <k>` accepts —
99
+ // NOT `identity.name` (`fitness`/`simulation`), or `get_latest_findings({ tool:
100
+ // 'fit' })`, the headline result-first path, would be rejected as unknown.
101
+ const validToolIds = new Set(scope.tools.list().map((t) => t.identity.layoutKey ?? t.identity.name));
102
+ // `void`: registerMcpTools is synchronous (returns void); the leading `void`
103
+ // marks the discard explicitly so the detached-promises heuristic (which can't
104
+ // see cross-file sync callables) doesn't read this floating call as a promise.
105
+ void registerMcpTools(server, { graph, results, validToolIds });
106
+ // Block for the serve lifetime; resolves on stdin EOF (or graceful SIGINT).
107
+ await server.serve();
108
+ cli.setExitCode(0);
109
+ },
110
+ });
111
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAEvD,OAAO,EAAE,6BAA6B,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAMvD,MAAM,CAAC,MAAM,cAAc,GAAG,oBAAoB,CAA0B;IAC1E,WAAW,EAAE,wEAAwE;IACrF,WAAW,EAAE,CAAC,KAAK,CAAC;IACpB,KAAK,EAAE,SAAS;IAChB,MAAM,EAAE,YAAY;IACpB,eAAe,EAAE,WAAW;IAC5B,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QAC5B,2EAA2E;QAC3E,2EAA2E;QAC3E,6EAA6E;QAC7E,4EAA4E;QAC5E,2EAA2E;QAC3E,8DAA8D;QAC9D,MAAM,KAAK,GAAG,GAAG,CAAC,KAAiB,CAAC;QAEpC,+EAA+E;QAC/E,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,SAAS,EAA2B,CAAC;QAC7D,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,GAAG,CAAC,aAAa,CAAC;gBACtB,OAAO,EAAE,kEAAkE;gBAC3E,UAAU,EACR,2FAA2F;gBAC7F,IAAI,EAAE,2BAA2B;gBACjC,QAAQ,EAAE,CAAC;gBACX,GAAG,EAAE,EAAE,GAAG,EAAE,kCAAkC,EAAE,KAAK,EAAE,OAAO,EAAE;aACjE,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,yEAAyE;QACzE,EAAE;QACF,8DAA8D;QAC9D,oEAAoE;QACpE,uEAAuE;QACvE,wEAAwE;QACxE,8EAA8E;QAC9E,8EAA8E;QAC9E,6EAA6E;QAC7E,mDAAmD;QACnD,EAAE;QACF,2EAA2E;QAC3E,4EAA4E;QAC5E,wEAAwE;QACxE,0DAA0D;QAC1D,MAAM,WAAW,GAAG,KAAK,CAAC,cAAc,EAAE,WAAW,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QACvE;;;;;;WAMG;QACH,KAAK,UAAU,OAAO;YACpB,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;YACvE,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;gBAC7B,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;YACrF,CAAC;YACD,OAAO,OAAO,CAAC,OAAO,CAAC;QACzB,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,mBAAmB,CAAC;YACpC,KAAK;YACL,gBAAgB,EAAE,6BAA6B;YAC/C,OAAO;SACR,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,sBAAsB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;QAE1E,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC;YAChC,KAAK;YACL,KAAK;YACL,OAAO;YACP,OAAO,EAAE,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;SAC7C,CAAC,CAAC;QAEH,4EAA4E;QAC5E,6EAA6E;QAC7E,0EAA0E;QAC1E,+EAA+E;QAC/E,gFAAgF;QAChF,2EAA2E;QAC3E,MAAM,YAAY,GAAG,IAAI,GAAG,CAC1B,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CACvE,CAAC;QACF,6EAA6E;QAC7E,+EAA+E;QAC/E,+EAA+E;QAC/E,KAAK,gBAAgB,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC;QAEhE,4EAA4E;QAC5E,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IACrB,CAAC;CACF,CAAC,CAAC"}