seer-mcp 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 (371) hide show
  1. package/.vscode/settings.json +3 -0
  2. package/LICENSE +176 -0
  3. package/README.md +272 -0
  4. package/README_dev.md +199 -0
  5. package/dist/bundle/ci.d.ts +47 -0
  6. package/dist/bundle/ci.d.ts.map +1 -0
  7. package/dist/bundle/ci.js +113 -0
  8. package/dist/bundle/ci.js.map +1 -0
  9. package/dist/bundle/contract.d.ts +111 -0
  10. package/dist/bundle/contract.d.ts.map +1 -0
  11. package/dist/bundle/contract.js +352 -0
  12. package/dist/bundle/contract.js.map +1 -0
  13. package/dist/bundle/export.d.ts +36 -0
  14. package/dist/bundle/export.d.ts.map +1 -0
  15. package/dist/bundle/export.js +152 -0
  16. package/dist/bundle/export.js.map +1 -0
  17. package/dist/bundle/external.d.ts +66 -0
  18. package/dist/bundle/external.d.ts.map +1 -0
  19. package/dist/bundle/external.js +238 -0
  20. package/dist/bundle/external.js.map +1 -0
  21. package/dist/bundle/format.d.ts +94 -0
  22. package/dist/bundle/format.d.ts.map +1 -0
  23. package/dist/bundle/format.js +42 -0
  24. package/dist/bundle/format.js.map +1 -0
  25. package/dist/bundle/import.d.ts +49 -0
  26. package/dist/bundle/import.d.ts.map +1 -0
  27. package/dist/bundle/import.js +116 -0
  28. package/dist/bundle/import.js.map +1 -0
  29. package/dist/cli/index.d.ts +3 -0
  30. package/dist/cli/index.d.ts.map +1 -0
  31. package/dist/cli/index.js +1402 -0
  32. package/dist/cli/index.js.map +1 -0
  33. package/dist/cli/init.d.ts +48 -0
  34. package/dist/cli/init.d.ts.map +1 -0
  35. package/dist/cli/init.js +284 -0
  36. package/dist/cli/init.js.map +1 -0
  37. package/dist/db/schema.d.ts +3 -0
  38. package/dist/db/schema.d.ts.map +1 -0
  39. package/dist/db/schema.js +616 -0
  40. package/dist/db/schema.js.map +1 -0
  41. package/dist/db/store.d.ts +1011 -0
  42. package/dist/db/store.d.ts.map +1 -0
  43. package/dist/db/store.js +3888 -0
  44. package/dist/db/store.js.map +1 -0
  45. package/dist/graph/pagerank.d.ts +9 -0
  46. package/dist/graph/pagerank.d.ts.map +1 -0
  47. package/dist/graph/pagerank.js +47 -0
  48. package/dist/graph/pagerank.js.map +1 -0
  49. package/dist/indexer/architecture.d.ts +72 -0
  50. package/dist/indexer/architecture.d.ts.map +1 -0
  51. package/dist/indexer/architecture.js +112 -0
  52. package/dist/indexer/architecture.js.map +1 -0
  53. package/dist/indexer/behavior.d.ts +75 -0
  54. package/dist/indexer/behavior.d.ts.map +1 -0
  55. package/dist/indexer/behavior.js +395 -0
  56. package/dist/indexer/behavior.js.map +1 -0
  57. package/dist/indexer/boundaries.d.ts +60 -0
  58. package/dist/indexer/boundaries.d.ts.map +1 -0
  59. package/dist/indexer/boundaries.js +366 -0
  60. package/dist/indexer/boundaries.js.map +1 -0
  61. package/dist/indexer/churn.d.ts +15 -0
  62. package/dist/indexer/churn.d.ts.map +1 -0
  63. package/dist/indexer/churn.js +49 -0
  64. package/dist/indexer/churn.js.map +1 -0
  65. package/dist/indexer/classify.d.ts +9 -0
  66. package/dist/indexer/classify.d.ts.map +1 -0
  67. package/dist/indexer/classify.js +90 -0
  68. package/dist/indexer/classify.js.map +1 -0
  69. package/dist/indexer/context.d.ts +176 -0
  70. package/dist/indexer/context.d.ts.map +1 -0
  71. package/dist/indexer/context.js +193 -0
  72. package/dist/indexer/context.js.map +1 -0
  73. package/dist/indexer/continuity.d.ts +67 -0
  74. package/dist/indexer/continuity.d.ts.map +1 -0
  75. package/dist/indexer/continuity.js +288 -0
  76. package/dist/indexer/continuity.js.map +1 -0
  77. package/dist/indexer/detectchanges.d.ts +32 -0
  78. package/dist/indexer/detectchanges.d.ts.map +1 -0
  79. package/dist/indexer/detectchanges.js +74 -0
  80. package/dist/indexer/detectchanges.js.map +1 -0
  81. package/dist/indexer/discovery.d.ts +37 -0
  82. package/dist/indexer/discovery.d.ts.map +1 -0
  83. package/dist/indexer/discovery.js +136 -0
  84. package/dist/indexer/discovery.js.map +1 -0
  85. package/dist/indexer/externaldeps.d.ts +18 -0
  86. package/dist/indexer/externaldeps.d.ts.map +1 -0
  87. package/dist/indexer/externaldeps.js +288 -0
  88. package/dist/indexer/externaldeps.js.map +1 -0
  89. package/dist/indexer/freshness.d.ts +48 -0
  90. package/dist/indexer/freshness.d.ts.map +1 -0
  91. package/dist/indexer/freshness.js +128 -0
  92. package/dist/indexer/freshness.js.map +1 -0
  93. package/dist/indexer/git.d.ts +144 -0
  94. package/dist/indexer/git.d.ts.map +1 -0
  95. package/dist/indexer/git.js +444 -0
  96. package/dist/indexer/git.js.map +1 -0
  97. package/dist/indexer/index.d.ts +145 -0
  98. package/dist/indexer/index.d.ts.map +1 -0
  99. package/dist/indexer/index.js +930 -0
  100. package/dist/indexer/index.js.map +1 -0
  101. package/dist/indexer/modules.d.ts +62 -0
  102. package/dist/indexer/modules.d.ts.map +1 -0
  103. package/dist/indexer/modules.js +293 -0
  104. package/dist/indexer/modules.js.map +1 -0
  105. package/dist/indexer/preflight.d.ts +154 -0
  106. package/dist/indexer/preflight.d.ts.map +1 -0
  107. package/dist/indexer/preflight.js +399 -0
  108. package/dist/indexer/preflight.js.map +1 -0
  109. package/dist/indexer/protoScanner.d.ts +34 -0
  110. package/dist/indexer/protoScanner.d.ts.map +1 -0
  111. package/dist/indexer/protoScanner.js +133 -0
  112. package/dist/indexer/protoScanner.js.map +1 -0
  113. package/dist/indexer/risk.d.ts +115 -0
  114. package/dist/indexer/risk.d.ts.map +1 -0
  115. package/dist/indexer/risk.js +194 -0
  116. package/dist/indexer/risk.js.map +1 -0
  117. package/dist/indexer/serviceHostScanner.d.ts +25 -0
  118. package/dist/indexer/serviceHostScanner.d.ts.map +1 -0
  119. package/dist/indexer/serviceHostScanner.js +95 -0
  120. package/dist/indexer/serviceHostScanner.js.map +1 -0
  121. package/dist/indexer/serviceLinks.d.ts +105 -0
  122. package/dist/indexer/serviceLinks.d.ts.map +1 -0
  123. package/dist/indexer/serviceLinks.js +509 -0
  124. package/dist/indexer/serviceLinks.js.map +1 -0
  125. package/dist/indexer/shapehash.d.ts +98 -0
  126. package/dist/indexer/shapehash.d.ts.map +1 -0
  127. package/dist/indexer/shapehash.js +354 -0
  128. package/dist/indexer/shapehash.js.map +1 -0
  129. package/dist/indexer/skeleton.d.ts +15 -0
  130. package/dist/indexer/skeleton.d.ts.map +1 -0
  131. package/dist/indexer/skeleton.js +136 -0
  132. package/dist/indexer/skeleton.js.map +1 -0
  133. package/dist/indexer/symbolhistory.d.ts +41 -0
  134. package/dist/indexer/symbolhistory.d.ts.map +1 -0
  135. package/dist/indexer/symbolhistory.js +124 -0
  136. package/dist/indexer/symbolhistory.js.map +1 -0
  137. package/dist/indexer/watcher.d.ts +68 -0
  138. package/dist/indexer/watcher.d.ts.map +1 -0
  139. package/dist/indexer/watcher.js +179 -0
  140. package/dist/indexer/watcher.js.map +1 -0
  141. package/dist/mcp/server.d.ts +80 -0
  142. package/dist/mcp/server.d.ts.map +1 -0
  143. package/dist/mcp/server.js +1610 -0
  144. package/dist/mcp/server.js.map +1 -0
  145. package/dist/parser/index.d.ts +8 -0
  146. package/dist/parser/index.d.ts.map +1 -0
  147. package/dist/parser/index.js +33 -0
  148. package/dist/parser/index.js.map +1 -0
  149. package/dist/parser/languages/cpp.d.ts +3 -0
  150. package/dist/parser/languages/cpp.d.ts.map +1 -0
  151. package/dist/parser/languages/cpp.js +350 -0
  152. package/dist/parser/languages/cpp.js.map +1 -0
  153. package/dist/parser/languages/csharp.d.ts +3 -0
  154. package/dist/parser/languages/csharp.d.ts.map +1 -0
  155. package/dist/parser/languages/csharp.js +239 -0
  156. package/dist/parser/languages/csharp.js.map +1 -0
  157. package/dist/parser/languages/go.d.ts +3 -0
  158. package/dist/parser/languages/go.d.ts.map +1 -0
  159. package/dist/parser/languages/go.js +259 -0
  160. package/dist/parser/languages/go.js.map +1 -0
  161. package/dist/parser/languages/java.d.ts +3 -0
  162. package/dist/parser/languages/java.d.ts.map +1 -0
  163. package/dist/parser/languages/java.js +391 -0
  164. package/dist/parser/languages/java.js.map +1 -0
  165. package/dist/parser/languages/python.d.ts +3 -0
  166. package/dist/parser/languages/python.d.ts.map +1 -0
  167. package/dist/parser/languages/python.js +396 -0
  168. package/dist/parser/languages/python.js.map +1 -0
  169. package/dist/parser/languages/rust.d.ts +3 -0
  170. package/dist/parser/languages/rust.d.ts.map +1 -0
  171. package/dist/parser/languages/rust.js +159 -0
  172. package/dist/parser/languages/rust.js.map +1 -0
  173. package/dist/parser/languages/typescript.d.ts +3 -0
  174. package/dist/parser/languages/typescript.d.ts.map +1 -0
  175. package/dist/parser/languages/typescript.js +1442 -0
  176. package/dist/parser/languages/typescript.js.map +1 -0
  177. package/dist/parser/parserContext.d.ts +77 -0
  178. package/dist/parser/parserContext.d.ts.map +1 -0
  179. package/dist/parser/parserContext.js +354 -0
  180. package/dist/parser/parserContext.js.map +1 -0
  181. package/dist/parser/walker.d.ts +81 -0
  182. package/dist/parser/walker.d.ts.map +1 -0
  183. package/dist/parser/walker.js +217 -0
  184. package/dist/parser/walker.js.map +1 -0
  185. package/dist/parser/worker.d.ts +66 -0
  186. package/dist/parser/worker.d.ts.map +1 -0
  187. package/dist/parser/worker.js +129 -0
  188. package/dist/parser/worker.js.map +1 -0
  189. package/dist/parser/workerpool.d.ts +107 -0
  190. package/dist/parser/workerpool.d.ts.map +1 -0
  191. package/dist/parser/workerpool.js +383 -0
  192. package/dist/parser/workerpool.js.map +1 -0
  193. package/dist/scip/format.d.ts +87 -0
  194. package/dist/scip/format.d.ts.map +1 -0
  195. package/dist/scip/format.js +31 -0
  196. package/dist/scip/format.js.map +1 -0
  197. package/dist/scip/import.d.ts +37 -0
  198. package/dist/scip/import.d.ts.map +1 -0
  199. package/dist/scip/import.js +180 -0
  200. package/dist/scip/import.js.map +1 -0
  201. package/dist/types.d.ts +392 -0
  202. package/dist/types.d.ts.map +1 -0
  203. package/dist/types.js +4 -0
  204. package/dist/types.js.map +1 -0
  205. package/docs/architecture.md +105 -0
  206. package/docs/benchmarks/methodology.md +134 -0
  207. package/docs/benchmarks/raw-results.md +71 -0
  208. package/docs/benchmarks.md +74 -0
  209. package/docs/cli.md +148 -0
  210. package/docs/examples/behavior-tests.md +70 -0
  211. package/docs/examples/change-history.md +85 -0
  212. package/docs/examples/pre-edit-context.md +81 -0
  213. package/docs/examples/service-links.md +88 -0
  214. package/docs/examples.md +80 -0
  215. package/docs/faq.md +70 -0
  216. package/docs/internals.md +104 -0
  217. package/docs/languages.md +70 -0
  218. package/docs/limits.md +52 -0
  219. package/docs/mcp.md +199 -0
  220. package/docs/quickstart.md +119 -0
  221. package/docs/testing.md +123 -0
  222. package/docs/tools.md +115 -0
  223. package/package.json +52 -0
  224. package/research-codebase.md +578 -0
  225. package/seer-cli-docs.md +326 -0
  226. package/seer-master-guide.md +246 -0
  227. package/src/bundle/ci.ts +141 -0
  228. package/src/bundle/contract.ts +387 -0
  229. package/src/bundle/export.ts +175 -0
  230. package/src/bundle/external.ts +285 -0
  231. package/src/bundle/format.ts +92 -0
  232. package/src/bundle/import.ts +157 -0
  233. package/src/cli/index.ts +1249 -0
  234. package/src/cli/init.ts +389 -0
  235. package/src/db/schema.ts +614 -0
  236. package/src/db/store.ts +4306 -0
  237. package/src/graph/pagerank.ts +53 -0
  238. package/src/indexer/architecture.ts +148 -0
  239. package/src/indexer/behavior.ts +466 -0
  240. package/src/indexer/boundaries.ts +374 -0
  241. package/src/indexer/churn.ts +58 -0
  242. package/src/indexer/classify.ts +96 -0
  243. package/src/indexer/context.ts +340 -0
  244. package/src/indexer/continuity.ts +322 -0
  245. package/src/indexer/detectchanges.ts +94 -0
  246. package/src/indexer/discovery.ts +176 -0
  247. package/src/indexer/externaldeps.ts +243 -0
  248. package/src/indexer/freshness.ts +166 -0
  249. package/src/indexer/git.ts +453 -0
  250. package/src/indexer/index.ts +1092 -0
  251. package/src/indexer/modules.ts +358 -0
  252. package/src/indexer/preflight.ts +548 -0
  253. package/src/indexer/protoScanner.ts +147 -0
  254. package/src/indexer/risk.ts +304 -0
  255. package/src/indexer/serviceHostScanner.ts +92 -0
  256. package/src/indexer/serviceLinks.ts +543 -0
  257. package/src/indexer/shapehash.ts +370 -0
  258. package/src/indexer/skeleton.ts +169 -0
  259. package/src/indexer/symbolhistory.ts +172 -0
  260. package/src/indexer/watcher.ts +206 -0
  261. package/src/mcp/server.ts +1659 -0
  262. package/src/parser/index.ts +37 -0
  263. package/src/parser/languages/cpp.ts +361 -0
  264. package/src/parser/languages/csharp.ts +235 -0
  265. package/src/parser/languages/go.ts +259 -0
  266. package/src/parser/languages/java.ts +382 -0
  267. package/src/parser/languages/python.ts +370 -0
  268. package/src/parser/languages/rust.ts +164 -0
  269. package/src/parser/languages/typescript.ts +1435 -0
  270. package/src/parser/parserContext.ts +392 -0
  271. package/src/parser/walker.ts +306 -0
  272. package/src/parser/worker.ts +181 -0
  273. package/src/parser/workerpool.ts +448 -0
  274. package/src/scip/format.ts +83 -0
  275. package/src/scip/import.ts +216 -0
  276. package/src/types.ts +457 -0
  277. package/tests/benchmark-service-links.ts +244 -0
  278. package/tests/bug-regressions.ts +626 -0
  279. package/tests/filters.ts +264 -0
  280. package/tests/fixtures/Counter.tsx +38 -0
  281. package/tests/fixtures/caller.ts +7 -0
  282. package/tests/fixtures/collisions.ts +23 -0
  283. package/tests/fixtures/local_helper.ts +5 -0
  284. package/tests/fixtures/overloads.java +17 -0
  285. package/tests/fixtures/remote_helper.ts +4 -0
  286. package/tests/fixtures/sample.c +15 -0
  287. package/tests/fixtures/sample.cpp +47 -0
  288. package/tests/fixtures/sample.cs +62 -0
  289. package/tests/fixtures/sample.go +68 -0
  290. package/tests/fixtures/sample.h +30 -0
  291. package/tests/fixtures/sample.java +85 -0
  292. package/tests/fixtures/sample.py +46 -0
  293. package/tests/fixtures/sample.rs +78 -0
  294. package/tests/fixtures/sample.ts +76 -0
  295. package/tests/fixtures-service/HttpClients.cs +30 -0
  296. package/tests/fixtures-service/HttpClients.java +24 -0
  297. package/tests/fixtures-service/billing.ts +15 -0
  298. package/tests/fixtures-service/docker-compose.yml +15 -0
  299. package/tests/fixtures-service/gateway.ts +10 -0
  300. package/tests/fixtures-service/get_user.ts +11 -0
  301. package/tests/fixtures-service/graphql_client.ts +63 -0
  302. package/tests/fixtures-service/graphql_server.ts +30 -0
  303. package/tests/fixtures-service/grpc_client.go +30 -0
  304. package/tests/fixtures-service/http_clients.go +23 -0
  305. package/tests/fixtures-service/http_clients.py +38 -0
  306. package/tests/fixtures-service/http_clients.ts +49 -0
  307. package/tests/fixtures-service/k8s/payment-service.yaml +22 -0
  308. package/tests/fixtures-service/k8s_calls.ts +20 -0
  309. package/tests/fixtures-service/messaging.ts +87 -0
  310. package/tests/fixtures-service/trpc_client.ts +39 -0
  311. package/tests/fixtures-service/trpc_server.ts +39 -0
  312. package/tests/fixtures-service/user_service.proto +33 -0
  313. package/tests/fixtures-trackcd/Cargo.toml +11 -0
  314. package/tests/fixtures-trackcd/SpringController.java +36 -0
  315. package/tests/fixtures-trackcd/auth_service.ts +19 -0
  316. package/tests/fixtures-trackcd/complex_module.py +50 -0
  317. package/tests/fixtures-trackcd/express_app.js +30 -0
  318. package/tests/fixtures-trackcd/fastapi_app.py +49 -0
  319. package/tests/fixtures-trackcd/fastify_object_routes.js +32 -0
  320. package/tests/fixtures-trackcd/go.mod +8 -0
  321. package/tests/fixtures-trackcd/package.json +15 -0
  322. package/tests/fixtures-trackcd/requirements.txt +4 -0
  323. package/tests/fixtures-trackcd/tests/auth_service.test.ts +13 -0
  324. package/tests/fixtures-tracke/auth/AuthService.ts +23 -0
  325. package/tests/fixtures-tracke/auth/crypto.ts +7 -0
  326. package/tests/fixtures-tracke/billing/Billing.ts +20 -0
  327. package/tests/fixtures-tracke/billing/Invoice.ts +10 -0
  328. package/tests/fixtures-tracke/billing/server.ts +17 -0
  329. package/tests/fixtures-tracke/package.json +7 -0
  330. package/tests/fixtures-tracke/tests/auth.test.ts +23 -0
  331. package/tests/fixtures-tracke/tests/billing.test.ts +14 -0
  332. package/tests/fixtures-trackf/package.json +5 -0
  333. package/tests/fixtures-trackf/src/auth.ts +26 -0
  334. package/tests/fixtures-trackf/src/handlers.ts +35 -0
  335. package/tests/fixtures-tracki/billing/routes.ts +12 -0
  336. package/tests/fixtures-tracki/gateway/client.ts +13 -0
  337. package/tests/git-features.ts +267 -0
  338. package/tests/init.ts +141 -0
  339. package/tests/mcp-jit.ts +130 -0
  340. package/tests/mcp-smoke.ts +191 -0
  341. package/tests/mcp-trackcd.ts +169 -0
  342. package/tests/mcp-tracke.ts +229 -0
  343. package/tests/mcp-trackf.ts +330 -0
  344. package/tests/mcp-trackg.ts +219 -0
  345. package/tests/mcp-tracki.ts +174 -0
  346. package/tests/mcp-watcher.ts +126 -0
  347. package/tests/optspec.ts +194 -0
  348. package/tests/parallel-index.ts +333 -0
  349. package/tests/parallel-read.ts +125 -0
  350. package/tests/parallel-recovery.ts +241 -0
  351. package/tests/perf-callers.ts +145 -0
  352. package/tests/query-parity.ts +184 -0
  353. package/tests/query-perf.ts +55 -0
  354. package/tests/scale-parallel-parity.ts +225 -0
  355. package/tests/scale-test.ts +523 -0
  356. package/tests/smoke.ts +396 -0
  357. package/tests/trackcd.ts +325 -0
  358. package/tests/tracke-collisions.ts +255 -0
  359. package/tests/tracke.ts +314 -0
  360. package/tests/trackf-bugs.ts +406 -0
  361. package/tests/trackf.ts +390 -0
  362. package/tests/trackg.ts +1372 -0
  363. package/tests/tracki-boundaries.ts +202 -0
  364. package/tests/tracki-continuity.ts +253 -0
  365. package/tests/tracki-contract-diff.ts +249 -0
  366. package/tests/tracki-external-bundles.ts +341 -0
  367. package/tests/tracki-preflight.ts +251 -0
  368. package/tests/verify-roles.ts +51 -0
  369. package/tests/worker-parity.ts +286 -0
  370. package/tests/worker-pool.ts +262 -0
  371. package/tsconfig.json +20 -0
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Git-dependent features: file churn, symbol history, detect_changes, PR/URL
3
+ * mining. We build a tiny git repo on the fly in a temp dir and assert that
4
+ * each pass correctly captures the history we just committed.
5
+ *
6
+ * Run with: npx tsx tests/git-features.ts
7
+ */
8
+
9
+ import path from 'path';
10
+ import fs from 'fs';
11
+ import os from 'os';
12
+ import { spawnSync } from 'child_process';
13
+ import { Indexer } from '../src/indexer/index';
14
+ import { Store } from '../src/db/store';
15
+ import { collectChurn } from '../src/indexer/churn';
16
+ import { buildSymbolHistory } from '../src/indexer/symbolhistory';
17
+ import { detectChanges } from '../src/indexer/detectchanges';
18
+ import { extractPrNumber, githubPrUrl } from '../src/indexer/git';
19
+
20
+ const TMP = path.join(os.tmpdir(), `seer-git-${Date.now()}`);
21
+ const REPO = path.join(TMP, 'repo');
22
+ const DB = path.join(TMP, 'graph.db');
23
+
24
+ let passed = 0;
25
+ let failed = 0;
26
+
27
+ function assert(cond: boolean, msg: string): void {
28
+ if (cond) { console.log(` ✓ ${msg}`); passed++; }
29
+ else { console.error(` ✗ ${msg}`); failed++; }
30
+ }
31
+
32
+ function git(...args: string[]): { stdout: string; status: number } {
33
+ const r = spawnSync('git', ['-C', REPO, ...args], { encoding: 'utf8' });
34
+ return { stdout: r.stdout ?? '', status: r.status ?? 1 };
35
+ }
36
+
37
+ function commit(message: string, author = 'Alice <alice@example.com>'): string {
38
+ git('add', '.');
39
+ const r = spawnSync(
40
+ 'git',
41
+ ['-C', REPO, '-c', `user.email=${author.replace(/^.* <(.+)>$/, '$1')}`,
42
+ '-c', `user.name=${author.replace(/ <.+>$/, '')}`,
43
+ 'commit', '-m', message, '--no-gpg-sign'],
44
+ { encoding: 'utf8' },
45
+ );
46
+ if (r.status !== 0) throw new Error(`git commit failed: ${r.stderr}`);
47
+ const sha = git('rev-parse', 'HEAD').stdout.trim();
48
+ return sha;
49
+ }
50
+
51
+ function write(rel: string, content: string): void {
52
+ const full = path.join(REPO, rel);
53
+ fs.mkdirSync(path.dirname(full), { recursive: true });
54
+ fs.writeFileSync(full, content, 'utf8');
55
+ }
56
+
57
+ async function run(): Promise<void> {
58
+ console.log('\nSeer Git-Features Tests');
59
+ console.log('=========================\n');
60
+
61
+ // Setup
62
+ fs.mkdirSync(REPO, { recursive: true });
63
+ const initRes = spawnSync('git', ['-C', REPO, 'init', '-q', '-b', 'main'], { encoding: 'utf8' });
64
+ if (initRes.status !== 0) {
65
+ // try without -b on older git
66
+ spawnSync('git', ['-C', REPO, 'init', '-q'], { encoding: 'utf8' });
67
+ }
68
+ git('config', 'commit.gpgsign', 'false');
69
+
70
+ // Configure a fake GitHub remote so PR URL mining can run.
71
+ git('remote', 'add', 'origin', 'git@github.com:example/myrepo.git');
72
+
73
+ // Commit 1: initial files
74
+ write('app.ts', `
75
+ export function login(user: string, password: string): boolean {
76
+ return user === password;
77
+ }
78
+
79
+ export function logout(token: string): void {
80
+ // noop
81
+ }
82
+
83
+ export function helper(x: number): number {
84
+ return x * 2;
85
+ }
86
+ `.trimStart());
87
+ write('README.md', '# Demo\n');
88
+ const sha1 = commit('Initial commit', 'Alice <alice@example.com>');
89
+
90
+ // Commit 2: extend login
91
+ write('app.ts', `
92
+ export function login(user: string, password: string): boolean {
93
+ if (!user || !password) return false;
94
+ return user === password;
95
+ }
96
+
97
+ export function logout(token: string): void {
98
+ // noop
99
+ }
100
+
101
+ export function helper(x: number): number {
102
+ return x * 2;
103
+ }
104
+ `.trimStart());
105
+ const sha2 = commit('Add empty-credentials guard to login (#42)', 'Bob <bob@example.com>');
106
+
107
+ // Commit 3: add new file
108
+ write('utils.ts', `
109
+ export function formatUser(u: string): string { return u.toUpperCase(); }
110
+ `.trimStart());
111
+ const sha3 = commit('Merge pull request #99 from feature/utils\n\nAdd utils module', 'Alice <alice@example.com>');
112
+
113
+ // Commit 4: tweak helper
114
+ write('app.ts', `
115
+ export function login(user: string, password: string): boolean {
116
+ if (!user || !password) return false;
117
+ return user === password;
118
+ }
119
+
120
+ export function logout(token: string): void {
121
+ // noop
122
+ }
123
+
124
+ export function helper(x: number): number {
125
+ return x * 4;
126
+ }
127
+ `.trimStart());
128
+ const sha4 = commit('Tweak helper multiplier', 'Bob <bob@example.com>');
129
+
130
+ console.log(` 4 commits made: ${sha1.slice(0,8)} … ${sha4.slice(0,8)}\n`);
131
+
132
+ // Index it
133
+ const store = new Store(DB);
134
+ const indexer = new Indexer(store);
135
+ const r = await indexer.indexDirectory(REPO, { quiet: true });
136
+ console.log(` Indexed: files=${r.filesIndexed} symbols=${r.symbols} edges=${r.edges}\n`);
137
+
138
+ // ── File churn ────────────────────────────────────────────────────────────
139
+ console.log('── File churn ──');
140
+ const ch = await collectChurn(REPO, store);
141
+ console.log(` ${ch.filesWithChurn}/${ch.filesAnalyzed} files have history, HEAD ${ch.headSha?.slice(0,8)}`);
142
+ assert(ch.headSha === sha4, `churn.headSha matches HEAD (${ch.headSha?.slice(0,8)})`);
143
+ assert(ch.filesWithChurn >= 2, `churn covers ≥2 files (app.ts and utils.ts)`);
144
+
145
+ const appChurn = store.getFileChurn(path.join(REPO, 'app.ts'));
146
+ console.log(` app.ts: commits=${appChurn?.commitCount}, top=${appChurn?.topAuthor}, second=${appChurn?.secondAuthor}`);
147
+ assert(appChurn !== null, 'getFileChurn(app.ts) returns a row');
148
+ assert(appChurn!.commitCount === 3, `app.ts touched in 3 commits (got ${appChurn!.commitCount})`);
149
+ // Bob did 2 commits to app.ts (sha2 + sha4), Alice did 1 (sha1)
150
+ assert(appChurn!.topAuthor === 'Bob', `app.ts top author = Bob (got ${appChurn!.topAuthor})`);
151
+ assert(appChurn!.secondAuthor === 'Alice', `app.ts second author = Alice (got ${appChurn!.secondAuthor})`);
152
+ assert(appChurn!.lastCommitSha === sha4, `app.ts last commit = sha4 (got ${appChurn!.lastCommitSha?.slice(0,8)})`);
153
+ assert(appChurn!.lastCommitAt !== null && appChurn!.lastCommitAt > 0, 'last commit timestamp populated');
154
+
155
+ const utilsChurn = store.getFileChurn(path.join(REPO, 'utils.ts'));
156
+ assert(utilsChurn !== null && utilsChurn.commitCount === 1, `utils.ts: 1 commit`);
157
+
158
+ // top churned files
159
+ const topChurn = store.topChurnedFiles(10);
160
+ assert(topChurn[0].filePath.endsWith('app.ts'), `topChurnedFiles: app.ts first (most edited)`);
161
+
162
+ // ── Git index state ──────────────────────────────────────────────────────
163
+ console.log('\n── git_index_state ──');
164
+ const state = store.getGitIndexState();
165
+ assert(state !== null, 'git_index_state row populated');
166
+ assert(state!.lastHeadSha === sha4, `git_index_state.last_head_sha = HEAD`);
167
+ assert(state!.remoteUrl !== null, `git_index_state.remote_url captured`);
168
+
169
+ // ── PR mining ────────────────────────────────────────────────────────────
170
+ console.log('\n── PR / URL mining ──');
171
+ assert(extractPrNumber('Add foo (#42)') === 42, 'extractPrNumber inline (#42)');
172
+ assert(extractPrNumber('Merge pull request #99 from foo') === 99, 'extractPrNumber merge form');
173
+ assert(extractPrNumber(' #123 fix') === 123, 'extractPrNumber leading');
174
+ assert(extractPrNumber('no number here') === null, 'extractPrNumber empty');
175
+ assert(githubPrUrl('git@github.com:foo/bar.git', 7) === 'https://github.com/foo/bar/pull/7',
176
+ 'githubPrUrl SSH form');
177
+ assert(githubPrUrl('https://github.com/foo/bar.git', 7) === 'https://github.com/foo/bar/pull/7',
178
+ 'githubPrUrl HTTPS form');
179
+ assert(githubPrUrl('https://gitlab.com/foo/bar.git', 7) === null, 'githubPrUrl rejects non-github');
180
+ assert(githubPrUrl(null, 7) === null, 'githubPrUrl null remote');
181
+
182
+ // ── Symbol history (Track D) ─────────────────────────────────────────────
183
+ console.log('\n── Symbol history ──');
184
+ const sh = await buildSymbolHistory(REPO, store, { skipIfHeadUnchanged: false, log: () => {} });
185
+ console.log(` inserts=${sh.historyRowsInserted}, files=${sh.filesProcessed}, symbols=${sh.symbolsProcessed}`);
186
+ assert(sh.historyRowsInserted >= 4, `symbol history: ≥4 rows`);
187
+ assert(sh.filesProcessed >= 1, `symbol history: ≥1 file processed`);
188
+
189
+ // login was modified in sha2 — verify
190
+ const loginDef = store.getDefinition('login')[0];
191
+ assert(loginDef !== undefined, 'login defined');
192
+ const loginHistory = store.getSymbolHistory(loginDef.id, { limit: 10 });
193
+ console.log(` login history: ${loginHistory.length} commits`);
194
+ for (const h of loginHistory) {
195
+ console.log(` ${h.commitSha.slice(0,8)} ${h.authorName ?? '?'} pr=${h.prNumber} ${(h.message ?? '').split('\n')[0].slice(0,40)}`);
196
+ }
197
+ assert(loginHistory.length >= 2, `login history: ≥2 commits (got ${loginHistory.length})`);
198
+ assert(loginHistory.some(h => h.commitSha === sha1), `login history includes sha1`);
199
+ assert(loginHistory.some(h => h.commitSha === sha2 && h.prNumber === 42),
200
+ `login history sha2 includes PR #42`);
201
+ assert(loginHistory.some(h => h.prUrl === 'https://github.com/example/myrepo/pull/42'),
202
+ `login history includes GitHub PR URL`);
203
+
204
+ // helper was modified in sha1 (added) and sha4 (multiplier change)
205
+ const helperDef = store.getDefinition('helper')[0];
206
+ const helperHistory = store.getSymbolHistory(helperDef.id);
207
+ console.log(` helper history: ${helperHistory.length} commits`);
208
+ assert(helperHistory.some(h => h.commitSha === sha4), `helper history includes sha4`);
209
+
210
+ // logout was NOT modified after sha1 (only sha1 in its history)
211
+ const logoutDef = store.getDefinition('logout')[0];
212
+ const logoutHistory = store.getSymbolHistory(logoutDef.id);
213
+ console.log(` logout history: ${logoutHistory.length} commits`);
214
+ assert(logoutHistory.length === 1 && logoutHistory[0].commitSha === sha1,
215
+ `logout history is exactly sha1 (got ${logoutHistory.length})`);
216
+
217
+ // count
218
+ const loginCount = store.countSymbolHistory(loginDef.id);
219
+ assert(loginCount === loginHistory.length, 'countSymbolHistory matches getSymbolHistory');
220
+
221
+ // skipIfHeadUnchanged: second run with no commits should be a no-op
222
+ const sh2 = await buildSymbolHistory(REPO, store, { skipIfHeadUnchanged: true, log: () => {} });
223
+ assert(sh2.skipped === true, 'second buildSymbolHistory skips because HEAD unchanged');
224
+
225
+ // ── detect_changes ────────────────────────────────────────────────────────
226
+ console.log('\n── detect_changes ──');
227
+ // Make an uncommitted edit to app.ts (modify logout body)
228
+ write('app.ts', `
229
+ export function login(user: string, password: string): boolean {
230
+ if (!user || !password) return false;
231
+ return user === password;
232
+ }
233
+
234
+ export function logout(token: string): void {
235
+ console.log("logging out " + token);
236
+ }
237
+
238
+ export function helper(x: number): number {
239
+ return x * 4;
240
+ }
241
+ `.trimStart());
242
+
243
+ const dc = detectChanges(REPO, store, { callerDepth: 2 });
244
+ console.log(` changedFiles=${dc.changedFiles.length} directly=${dc.directlyChanged.length} transitively=${dc.transitivelyAffected.length}`);
245
+ for (const f of dc.changedFiles) {
246
+ console.log(` ${path.basename(f.path)}: hunks=${f.hunks}, symbols=${f.symbols.map(s => s.symbol.name).join(',')}`);
247
+ }
248
+ assert(dc.changedFiles.length === 1, 'detect_changes finds 1 modified file');
249
+ assert(dc.directlyChanged.some(s => s.name === 'logout'), 'detect_changes flags logout as directly-changed');
250
+ assert(!dc.directlyChanged.some(s => s.name === 'helper'), 'detect_changes does NOT flag helper');
251
+
252
+ // detect_changes between two refs
253
+ const dcRange = detectChanges(REPO, store, { fromRef: sha1, toRef: sha2, callerDepth: 2 });
254
+ // sha1 → sha2 touched login only
255
+ assert(dcRange.directlyChanged.some(s => s.name === 'login'), 'between-refs detect_changes flags login');
256
+
257
+ // ── Cleanup ───────────────────────────────────────────────────────────────
258
+ store.close();
259
+ try { fs.rmSync(TMP, { recursive: true, force: true }); } catch { /* */ }
260
+
261
+ console.log(`\n══════════════════════════════════════════════════════════════`);
262
+ console.log(` Results: ${passed} passed, ${failed} failed`);
263
+ if (failed > 0) { console.error('\n GIT-FEATURES TEST FAILED\n'); process.exit(1); }
264
+ else { console.log('\n All git-features tests passed! ✓\n'); }
265
+ }
266
+
267
+ run().catch(err => { console.error('git-features crashed:', err); process.exit(1); });
package/tests/init.ts ADDED
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Tests for `seer init` — the cross-agent MCP installer.
3
+ *
4
+ * These exercise only the project-local clients (claude, cursor, vscode,
5
+ * codex, gemini), which write inside a throwaway workspace. We never touch the
6
+ * user-level clients (antigravity) here, so the test cannot scribble on a real
7
+ * home config.
8
+ *
9
+ * Run: npx tsx tests/init.ts
10
+ */
11
+ import path from 'path';
12
+ import fs from 'fs';
13
+ import os from 'os';
14
+ import { runInit, ClientId } from '../src/cli/init';
15
+
16
+ let passed = 0;
17
+ let failed = 0;
18
+ const ok = (m: string): void => { passed++; console.log(` ✓ ${m}`); };
19
+ const bad = (m: string, x?: unknown): void => {
20
+ failed++;
21
+ console.error(` ✗ ${m}` + (x !== undefined ? ` :: ${JSON.stringify(x).slice(0, 240)}` : ''));
22
+ };
23
+ const check = (c: boolean, m: string, x?: unknown): void => { c ? ok(m) : bad(m, x); };
24
+
25
+ const PROJECT_CLIENTS: ClientId[] = ['claude', 'cursor', 'vscode', 'codex', 'gemini'];
26
+
27
+ function freshWs(tag: string): string {
28
+ const ws = path.join(os.tmpdir(), `seer-init-${tag}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
29
+ fs.mkdirSync(ws, { recursive: true });
30
+ return ws;
31
+ }
32
+
33
+ function main(): void {
34
+ console.log('\nSeer Init Tests\n===============\n');
35
+
36
+ // ── 1. Fresh write across all project-local clients ───────────────────────
37
+ {
38
+ const ws = freshWs('fresh');
39
+ const r = runInit({ workspace: ws, clients: PROJECT_CLIENTS });
40
+
41
+ check(r.launch.command === 'node', '1.launcher uses node by default', r.launch);
42
+ check(r.launch.args.includes('mcp') && r.launch.args.includes('--workspace'),
43
+ '1.launcher carries "mcp --workspace"', r.launch.args);
44
+
45
+ const mcp = JSON.parse(fs.readFileSync(path.join(ws, '.mcp.json'), 'utf8'));
46
+ check(!!mcp.mcpServers?.seer, '1.claude .mcp.json has mcpServers.seer');
47
+ check(mcp.mcpServers.seer.command === 'node', '1.claude entry command is node');
48
+
49
+ const cursor = JSON.parse(fs.readFileSync(path.join(ws, '.cursor', 'mcp.json'), 'utf8'));
50
+ check(!!cursor.mcpServers?.seer, '1.cursor .cursor/mcp.json has mcpServers.seer');
51
+
52
+ const vscode = JSON.parse(fs.readFileSync(path.join(ws, '.vscode', 'mcp.json'), 'utf8'));
53
+ check(!!vscode.servers?.seer, '1.vscode uses the "servers" root key');
54
+ check(vscode.servers.seer.type === 'stdio', '1.vscode entry carries type:stdio');
55
+
56
+ const gemini = JSON.parse(fs.readFileSync(path.join(ws, '.gemini', 'settings.json'), 'utf8'));
57
+ check(!!gemini.mcpServers?.seer, '1.gemini .gemini/settings.json has mcpServers.seer');
58
+
59
+ const toml = fs.readFileSync(path.join(ws, '.codex', 'config.toml'), 'utf8');
60
+ check(/\[mcp_servers\.seer\]/.test(toml), '1.codex config.toml has [mcp_servers.seer]');
61
+ check(/command = "node"/.test(toml), '1.codex block has command = "node"');
62
+
63
+ const agents = fs.readFileSync(path.join(ws, 'AGENTS.md'), 'utf8');
64
+ check(agents.includes('<!-- seer:begin -->') && agents.includes('<!-- seer:end -->'),
65
+ '1.AGENTS.md written with seer markers');
66
+ check(agents.includes('seer_preflight'), '1.AGENTS.md mentions seer_preflight workflow');
67
+
68
+ fs.rmSync(ws, { recursive: true, force: true });
69
+ }
70
+
71
+ // ── 2. Idempotency: a second run skips, --force updates ────────────────────
72
+ {
73
+ const ws = freshWs('idem');
74
+ runInit({ workspace: ws, clients: PROJECT_CLIENTS });
75
+ const r2 = runInit({ workspace: ws, clients: PROJECT_CLIENTS });
76
+ check(r2.entries.every((e) => e.action === 'skipped'), '2.re-run skips every client', r2.entries.map((e) => e.action));
77
+ check(r2.agents?.action === 'skipped', '2.re-run skips AGENTS.md');
78
+
79
+ const r3 = runInit({ workspace: ws, clients: PROJECT_CLIENTS, force: true });
80
+ check(r3.entries.every((e) => e.action === 'updated'), '3.--force updates every client', r3.entries.map((e) => e.action));
81
+
82
+ fs.rmSync(ws, { recursive: true, force: true });
83
+ }
84
+
85
+ // ── 3. Merge: existing config keeps its other servers ─────────────────────
86
+ {
87
+ const ws = freshWs('merge');
88
+ fs.writeFileSync(path.join(ws, '.mcp.json'),
89
+ JSON.stringify({ mcpServers: { other: { command: 'foo', args: ['bar'] } } }, null, 2));
90
+ fs.mkdirSync(path.join(ws, '.codex'), { recursive: true });
91
+ fs.writeFileSync(path.join(ws, '.codex', 'config.toml'),
92
+ '[model]\nname = "gpt-5.5"\n\n[mcp_servers.other]\ncommand = "foo"\nargs = []\n');
93
+
94
+ runInit({ workspace: ws, clients: ['claude', 'codex'] });
95
+
96
+ const mcp = JSON.parse(fs.readFileSync(path.join(ws, '.mcp.json'), 'utf8'));
97
+ check(!!mcp.mcpServers.other && !!mcp.mcpServers.seer, '4.json merge keeps existing "other" server + adds seer', mcp.mcpServers);
98
+
99
+ const toml = fs.readFileSync(path.join(ws, '.codex', 'config.toml'), 'utf8');
100
+ check(/\[model\]/.test(toml) && /\[mcp_servers\.other\]/.test(toml) && /\[mcp_servers\.seer\]/.test(toml),
101
+ '4.toml append keeps [model] + [mcp_servers.other] + adds seer');
102
+
103
+ fs.rmSync(ws, { recursive: true, force: true });
104
+ }
105
+
106
+ // ── 4. --npx launcher is portable (no machine paths, no --workspace) ───────
107
+ {
108
+ const ws = freshWs('npx');
109
+ const r = runInit({ workspace: ws, clients: ['claude'], npx: true, pkg: 'seer-core' });
110
+ check(r.launch.command === 'npx', '5.--npx launcher command is npx', r.launch);
111
+ check(r.launch.args.join(' ') === '-y seer-core mcp', '5.--npx args are "-y seer-core mcp"', r.launch.args);
112
+ const mcp = JSON.parse(fs.readFileSync(path.join(ws, '.mcp.json'), 'utf8'));
113
+ check(JSON.stringify(mcp).indexOf(ws) === -1, '5.--npx config carries no absolute workspace path');
114
+ fs.rmSync(ws, { recursive: true, force: true });
115
+ }
116
+
117
+ // ── 5. --print writes nothing ──────────────────────────────────────────────
118
+ {
119
+ const ws = freshWs('print');
120
+ const r = runInit({ workspace: ws, clients: PROJECT_CLIENTS, print: true });
121
+ check(!fs.existsSync(path.join(ws, '.mcp.json')), '6.--print does not write .mcp.json');
122
+ check(!fs.existsSync(path.join(ws, 'AGENTS.md')), '6.--print does not write AGENTS.md');
123
+ check(r.entries.length === PROJECT_CLIENTS.length, '6.--print still returns a full plan');
124
+ check(r.entries.every((e) => !!e.snippet), '6.--print plan carries snippets to preview');
125
+ fs.rmSync(ws, { recursive: true, force: true });
126
+ }
127
+
128
+ // ── 6. --no-agents skips the guide ─────────────────────────────────────────
129
+ {
130
+ const ws = freshWs('noagents');
131
+ const r = runInit({ workspace: ws, clients: ['claude'], agents: false });
132
+ check(r.agents === undefined, '7.--no-agents returns no agents plan');
133
+ check(!fs.existsSync(path.join(ws, 'AGENTS.md')), '7.--no-agents writes no AGENTS.md');
134
+ fs.rmSync(ws, { recursive: true, force: true });
135
+ }
136
+
137
+ console.log(`\n${failed === 0 ? 'PASS' : 'FAIL'} ${passed} passed, ${failed} failed\n`);
138
+ if (failed > 0) process.exit(1);
139
+ }
140
+
141
+ main();
@@ -0,0 +1,130 @@
1
+ /**
2
+ * JIT-freshness MCP integration test.
3
+ *
4
+ * Procedure:
5
+ * 1. Start `seer mcp --workspace <tmp>` with watcher off and JIT on.
6
+ * 2. Verify a symbol from sample.ts is queryable (post initial index).
7
+ * 3. Append a new function to sample.ts on disk (no calls in/out).
8
+ * 4. Issue another query and verify JIT picked up the new symbol.
9
+ * 5. Delete a fixture file, issue another query, verify it's gone.
10
+ *
11
+ * This catches regressions where the JIT pass either fails to reindex
12
+ * (returning stale results) or kicks off a full reindex on every call
13
+ * (which would still be correct but should be measurable as slow).
14
+ */
15
+
16
+ import { spawn } from 'child_process';
17
+ import path from 'path';
18
+ import fs from 'fs';
19
+ import os from 'os';
20
+
21
+ const ROOT = path.resolve(__dirname, '..');
22
+ const FIXTURES = path.join(ROOT, 'tests/fixtures');
23
+ const TMP_WS = path.join(os.tmpdir(), `seer-mcp-jit-${Date.now()}`);
24
+ const CLI = path.join(ROOT, 'dist/cli/index.js');
25
+
26
+ let passed = 0;
27
+ let failed = 0;
28
+ function ok(label: string): void { passed++; console.log(` ✓ ${label}`); }
29
+ function bad(label: string, extra?: unknown): void {
30
+ failed++;
31
+ console.error(` ✗ ${label}` + (extra !== undefined ? ` :: ${JSON.stringify(extra)}` : ''));
32
+ }
33
+
34
+ async function main(): Promise<void> {
35
+ console.log('\nSeer MCP JIT Freshness Test\n=============================\n');
36
+
37
+ fs.mkdirSync(TMP_WS, { recursive: true });
38
+ for (const f of fs.readdirSync(FIXTURES)) {
39
+ fs.copyFileSync(path.join(FIXTURES, f), path.join(TMP_WS, f));
40
+ }
41
+ console.log(` Workspace: ${TMP_WS}\n`);
42
+
43
+ const proc = spawn(process.execPath, [CLI, 'mcp', '--workspace', TMP_WS, '--no-watch'], {
44
+ stdio: ['pipe', 'pipe', 'pipe'],
45
+ });
46
+ proc.stderr.on('data', (d) => process.stderr.write(`[mcp-stderr] ${d}`));
47
+
48
+ let buf = '';
49
+ const pending = new Map<number, (msg: any) => void>();
50
+ proc.stdout.on('data', (chunk: Buffer) => {
51
+ buf += chunk.toString('utf8');
52
+ let nl: number;
53
+ while ((nl = buf.indexOf('\n')) >= 0) {
54
+ const line = buf.slice(0, nl).trim();
55
+ buf = buf.slice(nl + 1);
56
+ if (!line) continue;
57
+ try {
58
+ const msg = JSON.parse(line);
59
+ if (msg.id != null && pending.has(msg.id)) {
60
+ pending.get(msg.id)!(msg);
61
+ pending.delete(msg.id);
62
+ }
63
+ } catch { /* skip */ }
64
+ }
65
+ });
66
+
67
+ let nextId = 1;
68
+ function call(method: string, params: any): Promise<any> {
69
+ const id = nextId++;
70
+ return new Promise((resolve, reject) => {
71
+ pending.set(id, resolve);
72
+ proc.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n');
73
+ setTimeout(() => {
74
+ if (pending.has(id)) { pending.delete(id); reject(new Error(`timeout: ${method}`)); }
75
+ }, 30_000);
76
+ });
77
+ }
78
+
79
+ // Wait for ready
80
+ for (let i = 0; i < 30; i++) {
81
+ try {
82
+ const r = await call('initialize', { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'jit-smoke', version: '0.1.0' } });
83
+ if (r.result) break;
84
+ } catch { /* */ }
85
+ await new Promise(r => setTimeout(r, 500));
86
+ }
87
+ ok('server initialized');
88
+
89
+ // Baseline: AuthService should exist
90
+ const before = JSON.parse((await call('tools/call', { name: 'seer_symbols', arguments: { query: 'AuthService' } })).result.content[0].text);
91
+ if (before.items.some((i: any) => i.name === 'AuthService')) ok('baseline: AuthService visible');
92
+ else bad('baseline: AuthService missing');
93
+
94
+ // ── Edit sample.ts: add a new function ──
95
+ const samplePath = path.join(TMP_WS, 'sample.ts');
96
+ const original = fs.readFileSync(samplePath, 'utf8');
97
+ const newFn = `\n\nexport function jitInjectedFunction(): string {\n return "added by JIT test";\n}\n`;
98
+ fs.writeFileSync(samplePath, original + newFn);
99
+ // Give the OS a moment to flush; chokidar isn't running here so it doesn't matter, but the
100
+ // hash check needs the on-disk content to differ.
101
+ await new Promise(r => setTimeout(r, 100));
102
+
103
+ const after = JSON.parse((await call('tools/call', { name: 'seer_symbols', arguments: { query: 'jitInjectedFunction' } })).result.content[0].text);
104
+ if (after.items.some((i: any) => i.name === 'jitInjectedFunction')) ok('JIT picked up new function after file edit');
105
+ else bad('JIT did not pick up new function', after);
106
+
107
+ // ── Remove a fixture file and verify it disappears ──
108
+ const goPath = path.join(TMP_WS, 'sample.go');
109
+ if (fs.existsSync(goPath)) fs.unlinkSync(goPath);
110
+ await new Promise(r => setTimeout(r, 100));
111
+
112
+ const stats = JSON.parse((await call('tools/call', { name: 'seer_stats', arguments: {} })).result.content[0].text);
113
+ if (!('go' in stats.languages)) ok('JIT pruned removed sample.go (go language gone from stats)');
114
+ else bad('JIT did not prune sample.go', stats.languages);
115
+
116
+ proc.stdin.end();
117
+ proc.kill();
118
+ await new Promise(r => setTimeout(r, 200));
119
+
120
+ try { fs.rmSync(TMP_WS, { recursive: true, force: true }); } catch { /* */ }
121
+
122
+ console.log(`\n══════════════════════════════════════════════════════════════`);
123
+ console.log(` JIT results: ${passed} passed, ${failed} failed\n`);
124
+ if (failed > 0) process.exit(1);
125
+ }
126
+
127
+ main().catch(err => {
128
+ console.error('JIT smoke crashed:', err);
129
+ process.exit(1);
130
+ });