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,523 @@
1
+ /**
2
+ * Seer Scale Test — exercises every large codebase under `Large Codebases/`,
3
+ * runs each one twice (fresh + cached), and writes a robust report.
4
+ *
5
+ * Why this exists: after every foundational change (e.g. a future
6
+ * worker-thread parser, a new edge type, a schema migration) we need a
7
+ * one-shot way to confirm:
8
+ * 1. Every codebase still indexes without exceptions.
9
+ * 2. Counts are deterministic — fresh and cached runs produce identical
10
+ * symbol/edge/resolved totals. Drift here = a real bug.
11
+ * 3. The cache really rehydrates everything (reused == fresh-indexed).
12
+ * 4. No WASM aborts triggered (resets stay at 0).
13
+ * 5. Parse-error rate stays low.
14
+ * 6. PageRank produces nonzero variance.
15
+ * 7. Indexing speed isn't regressed (ms/file is logged for comparison).
16
+ *
17
+ * Outputs:
18
+ * tests/outputs/run-<ISO>.json — full machine-readable report
19
+ * tests/outputs/latest.md — human-readable summary (overwritten)
20
+ * tests/outputs/dbs/<name>.db — per-codebase SQLite index (kept for
21
+ * follow-up queries; safe to delete)
22
+ *
23
+ * Usage:
24
+ * npm run scale-test # run all codebases
25
+ * npm run scale-test -- --only helix,react # subset
26
+ * npm run scale-test -- --skip unreal # exclude
27
+ * npm run scale-test -- --pass 3 # do 3 fresh passes (catches
28
+ * run-to-run non-determinism)
29
+ */
30
+
31
+ import path from 'path';
32
+ import fs from 'fs';
33
+ import { Store } from '../src/db/store';
34
+ import { Indexer, IndexResult } from '../src/indexer/index';
35
+
36
+ interface CodebaseSpec {
37
+ name: string;
38
+ relativePath: string;
39
+ }
40
+
41
+ const CODEBASES: CodebaseSpec[] = [
42
+ { name: 'helix', relativePath: 'Large Codebases/helix-master' },
43
+ { name: 'client-go', relativePath: 'Large Codebases/client-go-master' },
44
+ { name: 'react', relativePath: 'Large Codebases/react-main' },
45
+ { name: 'godot', relativePath: 'Large Codebases/godot-master' },
46
+ { name: 'linux', relativePath: 'Large Codebases/linux-master' },
47
+ { name: 'typescript', relativePath: 'Large Codebases/TypeScript-main' },
48
+ { name: 'unreal', relativePath: 'Large Codebases/UnrealEngine-release' },
49
+ // Self-validation: the codebase that surfaced the original dogfood gaps
50
+ // (.c handling, vendored pollution, parallel-read DB locks). Including it
51
+ // here so future regressions show up in the standard run.
52
+ { name: 'cbm', relativePath: 'Large Codebases/codebase-memory-mcp-main' },
53
+ ];
54
+
55
+ interface TopSymbol {
56
+ name: string;
57
+ kind: string;
58
+ pagerank: number;
59
+ filePath: string;
60
+ }
61
+
62
+ interface RunReport {
63
+ name: string;
64
+ status: 'ok' | 'warn' | 'error';
65
+ errors: string[];
66
+ warnings: string[];
67
+
68
+ // File-discovery breakdown
69
+ filesDiscovered: number;
70
+ filesIndexed: number;
71
+ filesReused: number; // from cached run — should equal filesIndexed
72
+ filesParseError: number;
73
+ filesSkipped: number;
74
+ filesSkippedTooLarge: number;
75
+ parseErrorPct: number;
76
+
77
+ // Graph totals (from fresh run; cached must match)
78
+ symbols: number;
79
+ edges: number;
80
+ resolvedEdges: number;
81
+ resolutionPct: number;
82
+
83
+ // Resolution breakdown (fresh run only)
84
+ sameFile: number;
85
+ imported: number;
86
+ global: number;
87
+ resolvedImports: number;
88
+
89
+ // Health markers
90
+ wasmResets: number;
91
+ languages: Record<string, number>;
92
+
93
+ // Timing
94
+ freshMs: number;
95
+ cachedMs: number;
96
+ msPerFileFresh: number;
97
+ cacheSpeedup: number; // freshMs / cachedMs
98
+ dbSizeMb: number;
99
+
100
+ // Determinism sample
101
+ topSymbols: TopSymbol[];
102
+ pagerankVariance: number; // stddev of top 20 PageRank values
103
+
104
+ // Determinism cross-checks (filled at compare time)
105
+ cachedSymbols: number;
106
+ cachedEdges: number;
107
+ cachedResolvedEdges: number;
108
+
109
+ // Lazy PageRank verification: the fresh run should ALWAYS recompute, the
110
+ // cached run should NEVER recompute (graph is identical, ranks are valid).
111
+ freshPagerankRecomputed: boolean;
112
+ cachedPagerankRecomputed: boolean;
113
+
114
+ // Schema + classification snapshots so a regression in either surfaces here
115
+ // alongside the determinism checks. `roles` is the file count broken down
116
+ // by classification; `schemaVersion` is what the DB pinned.
117
+ schemaVersion: number;
118
+ roles: { project: number; vendor: number; generated: number; test: number };
119
+ }
120
+
121
+ // ── Run one codebase ──────────────────────────────────────────────────────────
122
+
123
+ async function runOne(spec: CodebaseSpec, repoRoot: string, dbPath: string): Promise<RunReport> {
124
+ // Clean DB + WAL sidecars so the fresh run is genuinely fresh
125
+ for (const ext of ['', '-wal', '-shm']) {
126
+ const p = dbPath + ext;
127
+ if (fs.existsSync(p)) fs.unlinkSync(p);
128
+ }
129
+
130
+ // ── Fresh ──
131
+ const store1 = new Store(dbPath);
132
+ const indexer1 = new Indexer(store1);
133
+ const fresh = await indexer1.indexDirectory(repoRoot, { quiet: true });
134
+ const topAfterFresh = store1.getTopSymbols(20);
135
+ store1.close();
136
+
137
+ // ── Cached ──
138
+ const store2 = new Store(dbPath);
139
+ const indexer2 = new Indexer(store2);
140
+ const cached = await indexer2.indexDirectory(repoRoot, { quiet: true });
141
+ const topAfterCached = store2.getTopSymbols(20);
142
+ const stats = store2.getStats();
143
+ const schemaInfo = store2.schemaInfo();
144
+ const roles = store2.getRoleCounts();
145
+ store2.close();
146
+
147
+ const errors: string[] = [];
148
+ const warnings: string[] = [];
149
+
150
+ // ── Determinism: fresh vs cached must match ──
151
+ if (fresh.symbols !== cached.symbols) {
152
+ errors.push(`symbol drift: fresh=${fresh.symbols} cached=${cached.symbols}`);
153
+ }
154
+ if (fresh.edges !== cached.edges) {
155
+ errors.push(`edge drift: fresh=${fresh.edges} cached=${cached.edges}`);
156
+ }
157
+ // The post-pass resolved count is reported as the running DB total in
158
+ // IndexResult.resolvedEdges (see indexer/index.ts comment). It must be
159
+ // identical between fresh and cached because cached re-runs the same
160
+ // resolve passes against unchanged data.
161
+ if (fresh.resolvedEdges !== cached.resolvedEdges) {
162
+ errors.push(`resolved-edge drift: fresh=${fresh.resolvedEdges} cached=${cached.resolvedEdges}`);
163
+ }
164
+ // Cache rehydration: every file we indexed fresh should be reused next time.
165
+ if (cached.filesReusedFromCache !== fresh.filesIndexed) {
166
+ errors.push(
167
+ `cache reuse mismatch: fresh indexed ${fresh.filesIndexed} but cached reused ${cached.filesReusedFromCache}`,
168
+ );
169
+ }
170
+ // Cached run must not re-resolve anything.
171
+ const cachedDelta =
172
+ cached.edgeResolution.sameFile + cached.edgeResolution.imported + cached.edgeResolution.global;
173
+ if (cachedDelta !== 0) {
174
+ errors.push(
175
+ `cached run resolved ${cachedDelta} new edges (should be 0 — implies edges left NULL after fresh)`,
176
+ );
177
+ }
178
+
179
+ // Lazy PageRank invariants: fresh must recompute, cached must skip.
180
+ // If cached recomputed despite no graph changes, the skip predicate is buggy.
181
+ // If fresh skipped, something is *really* wrong (we just inserted all symbols).
182
+ if (!fresh.pagerankRecomputed && fresh.symbols > 0) {
183
+ errors.push('fresh run skipped PageRank despite inserting symbols');
184
+ }
185
+ if (cached.pagerankRecomputed && cached.symbols > 0 && cachedDelta === 0) {
186
+ errors.push('cached run recomputed PageRank despite zero graph changes (lazy-skip predicate missed a case)');
187
+ }
188
+
189
+ // Top-symbol determinism check: top-20 PageRanks should be byte-identical
190
+ // across fresh/cached. If they drift, our PageRank or ordering is unstable.
191
+ if (topAfterFresh.length !== topAfterCached.length) {
192
+ errors.push(`top-symbol count drifted: fresh=${topAfterFresh.length} cached=${topAfterCached.length}`);
193
+ } else {
194
+ for (let i = 0; i < topAfterFresh.length; i++) {
195
+ if (topAfterFresh[i].id !== topAfterCached[i].id) {
196
+ errors.push(`top-symbol[${i}] drifted: fresh-id=${topAfterFresh[i].id} cached-id=${topAfterCached[i].id}`);
197
+ break;
198
+ }
199
+ }
200
+ }
201
+
202
+ // PageRank sanity: variance across top 20 should be nonzero if we have any
203
+ // edges. (Helix passes have shown ~5e-3 variance; flat-zero would mean
204
+ // PageRank failed to propagate.)
205
+ const ranks = topAfterFresh.map(s => s.pagerank);
206
+ const mean = ranks.reduce((a, b) => a + b, 0) / Math.max(ranks.length, 1);
207
+ const variance = ranks.length > 0
208
+ ? ranks.reduce((a, b) => a + (b - mean) ** 2, 0) / ranks.length
209
+ : 0;
210
+ if (fresh.edges > 0 && variance === 0) {
211
+ errors.push('PageRank produced uniform values despite nonzero edges');
212
+ }
213
+
214
+ // ── Schema version: must match the current build ──
215
+ if (schemaInfo.dbVersion !== schemaInfo.buildVersion) {
216
+ errors.push(`schema_version mismatch: db=${schemaInfo.dbVersion} build=${schemaInfo.buildVersion}`);
217
+ }
218
+
219
+ // ── Track A regression: default top-symbol must not be a vendored file ──
220
+ // The reason this is enforced is that the very first dogfood-gap reported
221
+ // by indexing Codebase-Memory was that vendored grammar parsers were
222
+ // dominating the PageRank top-20. After classification + project-first
223
+ // defaults, the top symbol on any of these codebases should be project-owned.
224
+ if (topAfterFresh.length > 0) {
225
+ const top = topAfterFresh[0];
226
+ const norm = top.filePath.replace(/\\/g, '/');
227
+ const looksVendored =
228
+ /(^|\/)(vendor|vendored|thirdparty|third_party|external|node_modules)\//i.test(norm);
229
+ if (looksVendored) {
230
+ errors.push(`top symbol is in vendored path: ${top.filePath}`);
231
+ }
232
+ }
233
+
234
+ // ── Warnings (informational, don't fail the run) ──
235
+ if (fresh.wasmResets > 0) {
236
+ warnings.push(`${fresh.wasmResets} WASM runtime reset(s) during fresh — recovered, but worth investigating`);
237
+ }
238
+ const totalParsed = fresh.filesIndexed + fresh.filesParseError;
239
+ const parseErrorPct = totalParsed > 0 ? (fresh.filesParseError / totalParsed) * 100 : 0;
240
+ if (parseErrorPct > 10) {
241
+ warnings.push(`parse error rate ${parseErrorPct.toFixed(1)}% > 10% — language extractor coverage might be slipping`);
242
+ }
243
+ if (fresh.edges > 0 && fresh.resolvedEdges / fresh.edges < 0.3) {
244
+ warnings.push(
245
+ `low resolution rate ${((fresh.resolvedEdges / fresh.edges) * 100).toFixed(1)}% — expected for languages without import resolution, but flag here for visibility`,
246
+ );
247
+ }
248
+
249
+ // ── DB size ──
250
+ let dbBytes = 0;
251
+ try { dbBytes = fs.statSync(dbPath).size; } catch { /* deleted? */ }
252
+
253
+ const status: 'ok' | 'warn' | 'error' =
254
+ errors.length > 0 ? 'error' : warnings.length > 0 ? 'warn' : 'ok';
255
+
256
+ return {
257
+ name: spec.name,
258
+ status,
259
+ errors,
260
+ warnings,
261
+ filesDiscovered: fresh.filesDiscovered,
262
+ filesIndexed: fresh.filesIndexed,
263
+ filesReused: cached.filesReusedFromCache,
264
+ filesParseError: fresh.filesParseError,
265
+ filesSkipped: fresh.filesSkipped,
266
+ filesSkippedTooLarge: fresh.filesSkippedTooLarge,
267
+ parseErrorPct,
268
+ symbols: fresh.symbols,
269
+ edges: fresh.edges,
270
+ resolvedEdges: fresh.resolvedEdges,
271
+ resolutionPct: fresh.edges > 0 ? (fresh.resolvedEdges / fresh.edges) * 100 : 0,
272
+ sameFile: fresh.edgeResolution.sameFile,
273
+ imported: fresh.edgeResolution.imported,
274
+ global: fresh.edgeResolution.global,
275
+ resolvedImports: fresh.resolvedImports,
276
+ wasmResets: fresh.wasmResets,
277
+ languages: stats.languages,
278
+ freshMs: fresh.elapsedMs,
279
+ cachedMs: cached.elapsedMs,
280
+ msPerFileFresh: fresh.filesIndexed > 0 ? fresh.elapsedMs / fresh.filesIndexed : 0,
281
+ cacheSpeedup: cached.elapsedMs > 0 ? fresh.elapsedMs / cached.elapsedMs : 0,
282
+ dbSizeMb: dbBytes / (1024 * 1024),
283
+ topSymbols: topAfterFresh.slice(0, 5).map(s => ({
284
+ name: s.name,
285
+ kind: s.kind,
286
+ pagerank: s.pagerank,
287
+ filePath: path.relative(repoRoot, s.filePath).replace(/\\/g, '/'),
288
+ })),
289
+ pagerankVariance: variance,
290
+ cachedSymbols: cached.symbols,
291
+ cachedEdges: cached.edges,
292
+ cachedResolvedEdges: cached.resolvedEdges,
293
+ freshPagerankRecomputed: fresh.pagerankRecomputed,
294
+ cachedPagerankRecomputed: cached.pagerankRecomputed,
295
+ schemaVersion: schemaInfo.dbVersion,
296
+ roles,
297
+ };
298
+ }
299
+
300
+ // ── Output formatters ────────────────────────────────────────────────────────
301
+
302
+ function fmtTime(ms: number): string {
303
+ if (ms < 1000) return `${ms.toFixed(0)}ms`;
304
+ if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`;
305
+ const min = Math.floor(ms / 60_000);
306
+ const sec = ((ms - min * 60_000) / 1000).toFixed(0);
307
+ return `${min}m${sec.padStart(2, '0')}s`;
308
+ }
309
+
310
+ function printSummary(reports: RunReport[]): void {
311
+ const rows: Array<[string, string, string, string, string, string, string, string, string, string]> = [
312
+ ['Name', 'Files', 'Symbols', 'Edges', 'Resolved%', 'Fresh', 'Cached', 'Speedup', 'ms/file', 'Status'],
313
+ ];
314
+ for (const r of reports) {
315
+ rows.push([
316
+ r.name,
317
+ r.filesIndexed.toLocaleString(),
318
+ r.symbols.toLocaleString(),
319
+ r.edges.toLocaleString(),
320
+ r.resolutionPct.toFixed(1) + '%',
321
+ fmtTime(r.freshMs),
322
+ fmtTime(r.cachedMs),
323
+ r.cacheSpeedup.toFixed(1) + 'x',
324
+ r.msPerFileFresh.toFixed(1),
325
+ r.status === 'ok' ? 'OK' : r.status === 'warn' ? 'WARN' : 'ERROR',
326
+ ]);
327
+ }
328
+
329
+ const widths = rows[0].map((_, i) => Math.max(...rows.map(r => r[i].length)) + 2);
330
+ for (let rIdx = 0; rIdx < rows.length; rIdx++) {
331
+ const cells = rows[rIdx].map((cell, i) => cell.padEnd(widths[i]));
332
+ console.log(' ' + cells.join(''));
333
+ if (rIdx === 0) {
334
+ console.log(' ' + widths.map(w => '─'.repeat(w - 1)).join(' '));
335
+ }
336
+ }
337
+ }
338
+
339
+ function generateMarkdown(reports: RunReport[]): string {
340
+ const ts = new Date().toISOString();
341
+ let md = `# Seer Scale-Test Results\n\n`;
342
+ md += `- Generated: ${ts}\n`;
343
+ md += `- Node: ${process.version}\n`;
344
+ md += `- Platform: ${process.platform}\n\n`;
345
+
346
+ md += `## Summary\n\n`;
347
+ md += `| Codebase | Files | Symbols | Edges | Resolved | Fresh | Cached | Speedup | ms/file | DB | Status |\n`;
348
+ md += `|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|:---:|\n`;
349
+ for (const r of reports) {
350
+ md += `| ${r.name} | ${r.filesIndexed.toLocaleString()} | ${r.symbols.toLocaleString()} | ${r.edges.toLocaleString()} | ${r.resolutionPct.toFixed(1)}% | ${fmtTime(r.freshMs)} | ${fmtTime(r.cachedMs)} | ${r.cacheSpeedup.toFixed(1)}× | ${r.msPerFileFresh.toFixed(2)} | ${r.dbSizeMb.toFixed(1)} MB | ${r.status === 'ok' ? '✓' : r.status === 'warn' ? '⚠' : '✗'} |\n`;
351
+ }
352
+
353
+ // Issues / warnings
354
+ const withErrors = reports.filter(r => r.errors.length > 0);
355
+ const withWarns = reports.filter(r => r.warnings.length > 0);
356
+ if (withErrors.length > 0) {
357
+ md += `\n## Errors\n\nReal correctness or determinism failures. These must be fixed.\n\n`;
358
+ for (const r of withErrors) {
359
+ md += `### ${r.name}\n`;
360
+ for (const e of r.errors) md += `- ${e}\n`;
361
+ md += `\n`;
362
+ }
363
+ }
364
+ if (withWarns.length > 0) {
365
+ md += `\n## Warnings\n\nLikely explainable but worth a look.\n\n`;
366
+ for (const r of withWarns) {
367
+ md += `### ${r.name}\n`;
368
+ for (const w of r.warnings) md += `- ${w}\n`;
369
+ md += `\n`;
370
+ }
371
+ }
372
+
373
+ // Per-codebase detail
374
+ md += `\n## Detail\n\n`;
375
+ for (const r of reports) {
376
+ md += `### ${r.name}\n\n`;
377
+ md += `**Discovery & parsing**\n`;
378
+ md += `- Files: ${r.filesDiscovered.toLocaleString()} discovered → ${r.filesIndexed.toLocaleString()} indexed`;
379
+ md += `, ${r.filesParseError} parse errors (${r.parseErrorPct.toFixed(2)}%)`;
380
+ md += `, ${r.filesSkipped} skipped (no language match)`;
381
+ if (r.filesSkippedTooLarge > 0) md += `, ${r.filesSkippedTooLarge} skipped (size cap)`;
382
+ md += `\n`;
383
+ md += `- Languages: ${Object.entries(r.languages).sort((a, b) => b[1] - a[1]).map(([l, n]) => `${l}=${n.toLocaleString()}`).join(', ')}\n`;
384
+ md += `- WASM runtime resets during fresh: ${r.wasmResets}\n`;
385
+ md += `\n**Graph**\n`;
386
+ md += `- Symbols: ${r.symbols.toLocaleString()}\n`;
387
+ md += `- Edges: ${r.edges.toLocaleString()} (${r.resolvedEdges.toLocaleString()} resolved = ${r.resolutionPct.toFixed(1)}%)\n`;
388
+ md += `- Resolution: same-file ${r.sameFile.toLocaleString()}, imported ${r.imported.toLocaleString()}, global ${r.global.toLocaleString()}\n`;
389
+ md += `- Imports resolved to files: ${r.resolvedImports.toLocaleString()}\n`;
390
+ md += `- PageRank top-20 variance: ${r.pagerankVariance.toExponential(2)}\n`;
391
+ md += `\n**Cache & determinism**\n`;
392
+ md += `- Cached run reused: ${r.filesReused.toLocaleString()} / ${r.filesIndexed.toLocaleString()} (must match)\n`;
393
+ md += `- Cached symbols/edges/resolved: ${r.cachedSymbols.toLocaleString()} / ${r.cachedEdges.toLocaleString()} / ${r.cachedResolvedEdges.toLocaleString()} (must match fresh)\n`;
394
+ md += `- PageRank: fresh recomputed=${r.freshPagerankRecomputed}, cached recomputed=${r.cachedPagerankRecomputed} (cached should be \`false\` when no files changed)\n`;
395
+ md += `\n**Timing**\n`;
396
+ md += `- Fresh: ${fmtTime(r.freshMs)} (${r.msPerFileFresh.toFixed(2)} ms/file)\n`;
397
+ md += `- Cached: ${fmtTime(r.cachedMs)} (${r.cacheSpeedup.toFixed(1)}× cache speedup)\n`;
398
+ md += `- DB size: ${r.dbSizeMb.toFixed(1)} MB\n`;
399
+ md += `\n**Top 5 symbols by PageRank**\n`;
400
+ for (const s of r.topSymbols) {
401
+ md += `- \`${s.name}\` (${s.kind}) — ${s.pagerank.toFixed(5)} — ${s.filePath}\n`;
402
+ }
403
+ md += `\n`;
404
+ }
405
+ return md;
406
+ }
407
+
408
+ // ── Main ─────────────────────────────────────────────────────────────────────
409
+
410
+ async function main(): Promise<void> {
411
+ const repoRoot = path.resolve(__dirname, '..');
412
+ const args = process.argv.slice(2);
413
+ let onlySet: Set<string> | null = null;
414
+ let skipSet = new Set<string>();
415
+ for (let i = 0; i < args.length; i++) {
416
+ if (args[i] === '--only' && args[i + 1]) {
417
+ onlySet = new Set(args[i + 1].split(','));
418
+ i++;
419
+ } else if (args[i] === '--skip' && args[i + 1]) {
420
+ skipSet = new Set(args[i + 1].split(','));
421
+ i++;
422
+ }
423
+ }
424
+
425
+ const toRun = CODEBASES.filter(c => {
426
+ if (skipSet.has(c.name)) return false;
427
+ if (onlySet && !onlySet.has(c.name)) return false;
428
+ return true;
429
+ });
430
+
431
+ const outDir = path.join(__dirname, 'outputs');
432
+ const dbDir = path.join(outDir, 'dbs');
433
+ fs.mkdirSync(outDir, { recursive: true });
434
+ fs.mkdirSync(dbDir, { recursive: true });
435
+
436
+ console.log(`\nSeer Scale Test`);
437
+ console.log(`─────────────────`);
438
+ console.log(`Running ${toRun.length} codebase(s)\n`);
439
+
440
+ const reports: RunReport[] = [];
441
+ const overallStart = Date.now();
442
+ for (const spec of toRun) {
443
+ const repoPath = path.join(repoRoot, spec.relativePath);
444
+ if (!fs.existsSync(repoPath)) {
445
+ console.log(` ⤬ ${spec.name}: skipped (path not found: ${spec.relativePath})`);
446
+ continue;
447
+ }
448
+ const dbPath = path.join(dbDir, `${spec.name}.db`);
449
+ process.stdout.write(` ▸ ${spec.name}: indexing... `);
450
+ const start = Date.now();
451
+ try {
452
+ const r = await runOne(spec, repoPath, dbPath);
453
+ reports.push(r);
454
+ const totalSec = ((Date.now() - start) / 1000).toFixed(1);
455
+ const mark = r.status === 'ok' ? '✓' : r.status === 'warn' ? '⚠' : '✗';
456
+ process.stdout.write(`\r ${mark} ${spec.name.padEnd(11)} ${r.filesIndexed.toLocaleString().padStart(7)} files, ${r.symbols.toLocaleString().padStart(9)} symbols, ${r.edges.toLocaleString().padStart(9)} edges fresh=${fmtTime(r.freshMs).padStart(7)} cached=${fmtTime(r.cachedMs).padStart(6)} (run ${totalSec}s)\n`);
457
+ for (const e of r.errors) console.log(` ✗ ERROR: ${e}`);
458
+ for (const w of r.warnings) console.log(` ⚠ WARN: ${w}`);
459
+ } catch (err) {
460
+ process.stdout.write(`\n ✗ EXCEPTION: ${err instanceof Error ? err.message : String(err)}\n`);
461
+ reports.push({
462
+ name: spec.name,
463
+ status: 'error',
464
+ errors: [`exception during indexing: ${err instanceof Error ? err.message : String(err)}`],
465
+ warnings: [],
466
+ filesDiscovered: 0, filesIndexed: 0, filesReused: 0, filesParseError: 0,
467
+ filesSkipped: 0, filesSkippedTooLarge: 0, parseErrorPct: 0,
468
+ symbols: 0, edges: 0, resolvedEdges: 0, resolutionPct: 0,
469
+ sameFile: 0, imported: 0, global: 0, resolvedImports: 0,
470
+ wasmResets: 0, languages: {},
471
+ freshMs: 0, cachedMs: 0, msPerFileFresh: 0, cacheSpeedup: 0,
472
+ dbSizeMb: 0, topSymbols: [], pagerankVariance: 0,
473
+ cachedSymbols: 0, cachedEdges: 0, cachedResolvedEdges: 0,
474
+ freshPagerankRecomputed: false, cachedPagerankRecomputed: false,
475
+ schemaVersion: 0,
476
+ roles: { project: 0, vendor: 0, generated: 0, test: 0 },
477
+ });
478
+ }
479
+ }
480
+ const overallSec = ((Date.now() - overallStart) / 1000).toFixed(1);
481
+
482
+ // ── Save outputs ──
483
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
484
+ const jsonPath = path.join(outDir, `run-${ts}.json`);
485
+ const latestJson = path.join(outDir, 'latest.json');
486
+ const mdPath = path.join(outDir, 'latest.md');
487
+
488
+ const payload = {
489
+ timestamp: new Date().toISOString(),
490
+ node: process.version,
491
+ platform: process.platform,
492
+ totalDurationSec: Number(overallSec),
493
+ reports,
494
+ };
495
+ fs.writeFileSync(jsonPath, JSON.stringify(payload, null, 2));
496
+ fs.writeFileSync(latestJson, JSON.stringify(payload, null, 2));
497
+ fs.writeFileSync(mdPath, generateMarkdown(reports));
498
+
499
+ console.log(`\n Summary table:\n`);
500
+ printSummary(reports);
501
+
502
+ console.log(`\n Saved:`);
503
+ console.log(` ${path.relative(repoRoot, jsonPath)}`);
504
+ console.log(` ${path.relative(repoRoot, latestJson)}`);
505
+ console.log(` ${path.relative(repoRoot, mdPath)}`);
506
+ console.log(`\n Total wall time: ${overallSec}s\n`);
507
+
508
+ const failures = reports.filter(r => r.status === 'error');
509
+ const warns = reports.filter(r => r.status === 'warn');
510
+ if (failures.length > 0) {
511
+ console.error(` ✗ ${failures.length} codebase(s) reported errors.`);
512
+ process.exit(1);
513
+ }
514
+ if (warns.length > 0) {
515
+ console.log(` ⚠ ${warns.length} codebase(s) reported warnings (non-fatal).`);
516
+ }
517
+ console.log(` ✓ All ${reports.length} codebases passed consistency checks.\n`);
518
+ }
519
+
520
+ main().catch(err => {
521
+ console.error('Scale test crashed:', err);
522
+ process.exit(1);
523
+ });