vibecheck-ai 2.0.1 → 5.0.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 (456) hide show
  1. package/bin/.generated +25 -0
  2. package/bin/_deprecations.js +463 -0
  3. package/bin/_router.js +46 -0
  4. package/bin/cli-hygiene.js +241 -0
  5. package/bin/dev/run-v2-torture.js +30 -0
  6. package/bin/registry.js +656 -0
  7. package/bin/runners/CLI_REFACTOR_SUMMARY.md +229 -0
  8. package/bin/runners/ENHANCEMENT_GUIDE.md +121 -0
  9. package/bin/runners/REPORT_AUDIT.md +64 -0
  10. package/bin/runners/cli-utils.js +1070 -0
  11. package/bin/runners/context/ai-task-decomposer.js +337 -0
  12. package/bin/runners/context/analyzer.js +513 -0
  13. package/bin/runners/context/api-contracts.js +427 -0
  14. package/bin/runners/context/context-diff.js +342 -0
  15. package/bin/runners/context/context-pruner.js +291 -0
  16. package/bin/runners/context/dependency-graph.js +414 -0
  17. package/bin/runners/context/generators/claude.js +107 -0
  18. package/bin/runners/context/generators/codex.js +108 -0
  19. package/bin/runners/context/generators/copilot.js +119 -0
  20. package/bin/runners/context/generators/cursor-enhanced.js +2525 -0
  21. package/bin/runners/context/generators/cursor.js +514 -0
  22. package/bin/runners/context/generators/mcp.js +169 -0
  23. package/bin/runners/context/generators/windsurf.js +180 -0
  24. package/bin/runners/context/git-context.js +304 -0
  25. package/bin/runners/context/index.js +1110 -0
  26. package/bin/runners/context/insights.js +173 -0
  27. package/bin/runners/context/mcp-server/generate-rules.js +337 -0
  28. package/bin/runners/context/mcp-server/index.js +1176 -0
  29. package/bin/runners/context/mcp-server/package.json +24 -0
  30. package/bin/runners/context/memory.js +200 -0
  31. package/bin/runners/context/monorepo.js +215 -0
  32. package/bin/runners/context/multi-repo-federation.js +404 -0
  33. package/bin/runners/context/patterns.js +253 -0
  34. package/bin/runners/context/proof-context.js +1264 -0
  35. package/bin/runners/context/security-scanner.js +541 -0
  36. package/bin/runners/context/semantic-search.js +350 -0
  37. package/bin/runners/context/shared.js +264 -0
  38. package/bin/runners/context/team-conventions.js +336 -0
  39. package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -0
  40. package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
  41. package/bin/runners/lib/agent-firewall/change-packet/builder.js +488 -0
  42. package/bin/runners/lib/agent-firewall/change-packet/schema.json +228 -0
  43. package/bin/runners/lib/agent-firewall/change-packet/store.js +200 -0
  44. package/bin/runners/lib/agent-firewall/claims/claim-types.js +21 -0
  45. package/bin/runners/lib/agent-firewall/claims/extractor.js +303 -0
  46. package/bin/runners/lib/agent-firewall/claims/patterns.js +24 -0
  47. package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
  48. package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
  49. package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
  50. package/bin/runners/lib/agent-firewall/enforcement/gateway.js +1059 -0
  51. package/bin/runners/lib/agent-firewall/enforcement/index.js +98 -0
  52. package/bin/runners/lib/agent-firewall/enforcement/mode.js +318 -0
  53. package/bin/runners/lib/agent-firewall/enforcement/orchestrator.js +484 -0
  54. package/bin/runners/lib/agent-firewall/enforcement/proof-artifact.js +418 -0
  55. package/bin/runners/lib/agent-firewall/enforcement/schemas/change-event.schema.json +173 -0
  56. package/bin/runners/lib/agent-firewall/enforcement/schemas/intent.schema.json +181 -0
  57. package/bin/runners/lib/agent-firewall/enforcement/schemas/verdict.schema.json +222 -0
  58. package/bin/runners/lib/agent-firewall/enforcement/verdict-v2.js +333 -0
  59. package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +88 -0
  60. package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +75 -0
  61. package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +127 -0
  62. package/bin/runners/lib/agent-firewall/evidence/resolver.js +102 -0
  63. package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +213 -0
  64. package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +145 -0
  65. package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +19 -0
  66. package/bin/runners/lib/agent-firewall/fs-hook/installer.js +87 -0
  67. package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +184 -0
  68. package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +163 -0
  69. package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +107 -0
  70. package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +68 -0
  71. package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +66 -0
  72. package/bin/runners/lib/agent-firewall/index.js +200 -0
  73. package/bin/runners/lib/agent-firewall/integration/index.js +20 -0
  74. package/bin/runners/lib/agent-firewall/integration/ship-gate.js +437 -0
  75. package/bin/runners/lib/agent-firewall/intent/alignment-engine.js +634 -0
  76. package/bin/runners/lib/agent-firewall/intent/auto-detect.js +426 -0
  77. package/bin/runners/lib/agent-firewall/intent/index.js +102 -0
  78. package/bin/runners/lib/agent-firewall/intent/schema.js +352 -0
  79. package/bin/runners/lib/agent-firewall/intent/store.js +283 -0
  80. package/bin/runners/lib/agent-firewall/interception/fs-interceptor.js +502 -0
  81. package/bin/runners/lib/agent-firewall/interception/index.js +23 -0
  82. package/bin/runners/lib/agent-firewall/interceptor/base.js +308 -0
  83. package/bin/runners/lib/agent-firewall/interceptor/cursor.js +35 -0
  84. package/bin/runners/lib/agent-firewall/interceptor/vscode.js +35 -0
  85. package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +34 -0
  86. package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
  87. package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
  88. package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
  89. package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
  90. package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
  91. package/bin/runners/lib/agent-firewall/logger.js +141 -0
  92. package/bin/runners/lib/agent-firewall/policy/default-policy.json +90 -0
  93. package/bin/runners/lib/agent-firewall/policy/engine.js +103 -0
  94. package/bin/runners/lib/agent-firewall/policy/loader.js +451 -0
  95. package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +50 -0
  96. package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +50 -0
  97. package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +79 -0
  98. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +227 -0
  99. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +191 -0
  100. package/bin/runners/lib/agent-firewall/policy/rules/scope.js +93 -0
  101. package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +57 -0
  102. package/bin/runners/lib/agent-firewall/policy/schema.json +183 -0
  103. package/bin/runners/lib/agent-firewall/policy/verdict.js +54 -0
  104. package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
  105. package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
  106. package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
  107. package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
  108. package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
  109. package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
  110. package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
  111. package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
  112. package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
  113. package/bin/runners/lib/agent-firewall/risk/thresholds.js +322 -0
  114. package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
  115. package/bin/runners/lib/agent-firewall/session/collector.js +451 -0
  116. package/bin/runners/lib/agent-firewall/session/index.js +26 -0
  117. package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
  118. package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
  119. package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
  120. package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
  121. package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
  122. package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
  123. package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
  124. package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
  125. package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
  126. package/bin/runners/lib/agent-firewall/truthpack/index.js +67 -0
  127. package/bin/runners/lib/agent-firewall/truthpack/loader.js +137 -0
  128. package/bin/runners/lib/agent-firewall/unblock/planner.js +337 -0
  129. package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
  130. package/bin/runners/lib/ai-bridge.js +416 -0
  131. package/bin/runners/lib/analysis-core.js +309 -0
  132. package/bin/runners/lib/analyzers.js +2500 -0
  133. package/bin/runners/lib/api-client.js +269 -0
  134. package/bin/runners/lib/approve-output.js +235 -0
  135. package/bin/runners/lib/artifact-envelope.js +540 -0
  136. package/bin/runners/lib/assets/vibecheck-logo.png +0 -0
  137. package/bin/runners/lib/audit-bridge.js +391 -0
  138. package/bin/runners/lib/auth-shared.js +977 -0
  139. package/bin/runners/lib/auth-truth.js +193 -0
  140. package/bin/runners/lib/auth.js +215 -0
  141. package/bin/runners/lib/authority-badge.js +425 -0
  142. package/bin/runners/lib/backup.js +62 -0
  143. package/bin/runners/lib/billing.js +107 -0
  144. package/bin/runners/lib/checkpoint.js +941 -0
  145. package/bin/runners/lib/claims.js +118 -0
  146. package/bin/runners/lib/classify-output.js +204 -0
  147. package/bin/runners/lib/cleanup/engine.js +571 -0
  148. package/bin/runners/lib/cleanup/index.js +53 -0
  149. package/bin/runners/lib/cleanup/output.js +375 -0
  150. package/bin/runners/lib/cleanup/rules.js +1060 -0
  151. package/bin/runners/lib/cli-output.js +400 -0
  152. package/bin/runners/lib/cli-ui.js +540 -0
  153. package/bin/runners/lib/compliance-bridge-new.js +0 -0
  154. package/bin/runners/lib/compliance-bridge.js +165 -0
  155. package/bin/runners/lib/contracts/auth-contract.js +202 -0
  156. package/bin/runners/lib/contracts/env-contract.js +181 -0
  157. package/bin/runners/lib/contracts/external-contract.js +206 -0
  158. package/bin/runners/lib/contracts/guard.js +168 -0
  159. package/bin/runners/lib/contracts/index.js +89 -0
  160. package/bin/runners/lib/contracts/plan-validator.js +311 -0
  161. package/bin/runners/lib/contracts/route-contract.js +199 -0
  162. package/bin/runners/lib/contracts.js +804 -0
  163. package/bin/runners/lib/default-config.js +127 -0
  164. package/bin/runners/lib/detect.js +89 -0
  165. package/bin/runners/lib/detectors-v2.js +622 -0
  166. package/bin/runners/lib/doctor/autofix.js +254 -0
  167. package/bin/runners/lib/doctor/diagnosis-receipt.js +454 -0
  168. package/bin/runners/lib/doctor/failure-signatures.js +526 -0
  169. package/bin/runners/lib/doctor/fix-script.js +336 -0
  170. package/bin/runners/lib/doctor/index.js +37 -0
  171. package/bin/runners/lib/doctor/modules/build-tools.js +453 -0
  172. package/bin/runners/lib/doctor/modules/dependencies.js +325 -0
  173. package/bin/runners/lib/doctor/modules/index.js +105 -0
  174. package/bin/runners/lib/doctor/modules/network.js +250 -0
  175. package/bin/runners/lib/doctor/modules/os-quirks.js +706 -0
  176. package/bin/runners/lib/doctor/modules/project.js +312 -0
  177. package/bin/runners/lib/doctor/modules/repo-integrity.js +485 -0
  178. package/bin/runners/lib/doctor/modules/runtime.js +224 -0
  179. package/bin/runners/lib/doctor/modules/security.js +350 -0
  180. package/bin/runners/lib/doctor/modules/system.js +213 -0
  181. package/bin/runners/lib/doctor/modules/vibecheck.js +394 -0
  182. package/bin/runners/lib/doctor/reporter.js +262 -0
  183. package/bin/runners/lib/doctor/safe-repair.js +384 -0
  184. package/bin/runners/lib/doctor/service.js +262 -0
  185. package/bin/runners/lib/doctor/types.js +113 -0
  186. package/bin/runners/lib/doctor/ui.js +263 -0
  187. package/bin/runners/lib/doctor-enhanced.js +233 -0
  188. package/bin/runners/lib/doctor-output.js +226 -0
  189. package/bin/runners/lib/doctor-v2.js +608 -0
  190. package/bin/runners/lib/drift.js +425 -0
  191. package/bin/runners/lib/enforcement.js +72 -0
  192. package/bin/runners/lib/engine/ast-cache.js +210 -0
  193. package/bin/runners/lib/engine/auth-extractor.js +211 -0
  194. package/bin/runners/lib/engine/billing-extractor.js +112 -0
  195. package/bin/runners/lib/engine/enforcement-extractor.js +100 -0
  196. package/bin/runners/lib/engine/env-extractor.js +207 -0
  197. package/bin/runners/lib/engine/express-extractor.js +208 -0
  198. package/bin/runners/lib/engine/extractors.js +849 -0
  199. package/bin/runners/lib/engine/index.js +207 -0
  200. package/bin/runners/lib/engine/repo-index.js +514 -0
  201. package/bin/runners/lib/engine/types.js +124 -0
  202. package/bin/runners/lib/engines/accessibility-engine.js +190 -0
  203. package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
  204. package/bin/runners/lib/engines/ast-cache.js +99 -0
  205. package/bin/runners/lib/engines/attack-detector.js +1192 -0
  206. package/bin/runners/lib/engines/code-quality-engine.js +255 -0
  207. package/bin/runners/lib/engines/console-logs-engine.js +115 -0
  208. package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
  209. package/bin/runners/lib/engines/dead-code-engine.js +198 -0
  210. package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
  211. package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
  212. package/bin/runners/lib/engines/file-filter.js +131 -0
  213. package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
  214. package/bin/runners/lib/engines/mock-data-engine.js +272 -0
  215. package/bin/runners/lib/engines/parallel-processor.js +71 -0
  216. package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
  217. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
  218. package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
  219. package/bin/runners/lib/engines/type-aware-engine.js +152 -0
  220. package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
  221. package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
  222. package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
  223. package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
  224. package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
  225. package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
  226. package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
  227. package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
  228. package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
  229. package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
  230. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
  231. package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
  232. package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
  233. package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
  234. package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
  235. package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
  236. package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
  237. package/bin/runners/lib/enterprise-detect.js +603 -0
  238. package/bin/runners/lib/enterprise-init.js +942 -0
  239. package/bin/runners/lib/entitlements-v2.js +265 -0
  240. package/bin/runners/lib/entitlements.generated.js +0 -0
  241. package/bin/runners/lib/entitlements.js +340 -0
  242. package/bin/runners/lib/env-resolver.js +417 -0
  243. package/bin/runners/lib/env-template.js +66 -0
  244. package/bin/runners/lib/env.js +189 -0
  245. package/bin/runners/lib/error-handler.js +368 -0
  246. package/bin/runners/lib/error-messages.js +289 -0
  247. package/bin/runners/lib/evidence-pack.js +684 -0
  248. package/bin/runners/lib/exit-codes.js +275 -0
  249. package/bin/runners/lib/extractors/client-calls.js +990 -0
  250. package/bin/runners/lib/extractors/fastify-route-dump.js +573 -0
  251. package/bin/runners/lib/extractors/fastify-routes.js +426 -0
  252. package/bin/runners/lib/extractors/index.js +363 -0
  253. package/bin/runners/lib/extractors/next-routes.js +524 -0
  254. package/bin/runners/lib/extractors/proof-graph.js +431 -0
  255. package/bin/runners/lib/extractors/route-matcher.js +451 -0
  256. package/bin/runners/lib/extractors/truthpack-v2.js +377 -0
  257. package/bin/runners/lib/extractors/ui-bindings.js +547 -0
  258. package/bin/runners/lib/finding-id.js +69 -0
  259. package/bin/runners/lib/finding-sorter.js +89 -0
  260. package/bin/runners/lib/findings-schema.js +281 -0
  261. package/bin/runners/lib/fingerprint.js +377 -0
  262. package/bin/runners/lib/firewall-prompt.js +50 -0
  263. package/bin/runners/lib/fix-output.js +228 -0
  264. package/bin/runners/lib/global-flags.js +250 -0
  265. package/bin/runners/lib/graph/graph-builder.js +265 -0
  266. package/bin/runners/lib/graph/html-renderer.js +413 -0
  267. package/bin/runners/lib/graph/index.js +32 -0
  268. package/bin/runners/lib/graph/runtime-collector.js +215 -0
  269. package/bin/runners/lib/graph/static-extractor.js +518 -0
  270. package/bin/runners/lib/help-formatter.js +413 -0
  271. package/bin/runners/lib/html-proof-report.js +913 -0
  272. package/bin/runners/lib/html-report.js +650 -0
  273. package/bin/runners/lib/init-wizard.js +601 -0
  274. package/bin/runners/lib/interactive-menu.js +1496 -0
  275. package/bin/runners/lib/json-output.js +76 -0
  276. package/bin/runners/lib/llm.js +75 -0
  277. package/bin/runners/lib/logger.js +38 -0
  278. package/bin/runners/lib/meter.js +61 -0
  279. package/bin/runners/lib/missions/briefing.js +427 -0
  280. package/bin/runners/lib/missions/checkpoint.js +753 -0
  281. package/bin/runners/lib/missions/evidence.js +126 -0
  282. package/bin/runners/lib/missions/hardening.js +851 -0
  283. package/bin/runners/lib/missions/plan.js +648 -0
  284. package/bin/runners/lib/missions/safety-gates.js +645 -0
  285. package/bin/runners/lib/missions/schema.js +478 -0
  286. package/bin/runners/lib/missions/templates.js +317 -0
  287. package/bin/runners/lib/next-action.js +560 -0
  288. package/bin/runners/lib/packs/bundle.js +675 -0
  289. package/bin/runners/lib/packs/evidence-pack.js +671 -0
  290. package/bin/runners/lib/packs/pack-factory.js +837 -0
  291. package/bin/runners/lib/packs/permissions-pack.js +686 -0
  292. package/bin/runners/lib/packs/proof-graph-pack.js +779 -0
  293. package/bin/runners/lib/patch.js +40 -0
  294. package/bin/runners/lib/permissions/auth-model.js +213 -0
  295. package/bin/runners/lib/permissions/idor-prover.js +205 -0
  296. package/bin/runners/lib/permissions/index.js +45 -0
  297. package/bin/runners/lib/permissions/matrix-builder.js +198 -0
  298. package/bin/runners/lib/pkgjson.js +28 -0
  299. package/bin/runners/lib/policy.js +295 -0
  300. package/bin/runners/lib/polish/accessibility.js +62 -0
  301. package/bin/runners/lib/polish/analyzer.js +93 -0
  302. package/bin/runners/lib/polish/backend.js +87 -0
  303. package/bin/runners/lib/polish/configuration.js +83 -0
  304. package/bin/runners/lib/polish/documentation.js +83 -0
  305. package/bin/runners/lib/polish/frontend.js +817 -0
  306. package/bin/runners/lib/polish/index.js +27 -0
  307. package/bin/runners/lib/polish/infrastructure.js +80 -0
  308. package/bin/runners/lib/polish/internationalization.js +85 -0
  309. package/bin/runners/lib/polish/libraries.js +180 -0
  310. package/bin/runners/lib/polish/observability.js +75 -0
  311. package/bin/runners/lib/polish/performance.js +64 -0
  312. package/bin/runners/lib/polish/privacy.js +110 -0
  313. package/bin/runners/lib/polish/resilience.js +92 -0
  314. package/bin/runners/lib/polish/security.js +78 -0
  315. package/bin/runners/lib/polish/seo.js +71 -0
  316. package/bin/runners/lib/polish/styles.js +62 -0
  317. package/bin/runners/lib/polish/utils.js +104 -0
  318. package/bin/runners/lib/preflight.js +142 -0
  319. package/bin/runners/lib/prerequisites.js +149 -0
  320. package/bin/runners/lib/prove-output.js +220 -0
  321. package/bin/runners/lib/reality/correlation-detectors.js +359 -0
  322. package/bin/runners/lib/reality/index.js +318 -0
  323. package/bin/runners/lib/reality/request-hashing.js +416 -0
  324. package/bin/runners/lib/reality/request-mapper.js +453 -0
  325. package/bin/runners/lib/reality/safety-rails.js +463 -0
  326. package/bin/runners/lib/reality/semantic-snapshot.js +408 -0
  327. package/bin/runners/lib/reality/toast-detector.js +393 -0
  328. package/bin/runners/lib/reality-findings.js +84 -0
  329. package/bin/runners/lib/reality-output.js +231 -0
  330. package/bin/runners/lib/receipts.js +179 -0
  331. package/bin/runners/lib/redact.js +29 -0
  332. package/bin/runners/lib/replay/capsule-manager.js +154 -0
  333. package/bin/runners/lib/replay/index.js +263 -0
  334. package/bin/runners/lib/replay/player.js +348 -0
  335. package/bin/runners/lib/replay/recorder.js +331 -0
  336. package/bin/runners/lib/report-engine.js +626 -0
  337. package/bin/runners/lib/report-html.js +1233 -0
  338. package/bin/runners/lib/report-output.js +366 -0
  339. package/bin/runners/lib/report-templates.js +967 -0
  340. package/bin/runners/lib/report.js +135 -0
  341. package/bin/runners/lib/route-detection.js +1209 -0
  342. package/bin/runners/lib/route-truth.js +1322 -0
  343. package/bin/runners/lib/safelist/index.js +96 -0
  344. package/bin/runners/lib/safelist/integration.js +334 -0
  345. package/bin/runners/lib/safelist/matcher.js +696 -0
  346. package/bin/runners/lib/safelist/schema.js +948 -0
  347. package/bin/runners/lib/safelist/store.js +438 -0
  348. package/bin/runners/lib/sandbox/index.js +59 -0
  349. package/bin/runners/lib/sandbox/proof-chain.js +399 -0
  350. package/bin/runners/lib/sandbox/sandbox-runner.js +205 -0
  351. package/bin/runners/lib/sandbox/worktree.js +174 -0
  352. package/bin/runners/lib/scan-cache.js +330 -0
  353. package/bin/runners/lib/scan-output-schema.js +344 -0
  354. package/bin/runners/lib/scan-output.js +631 -0
  355. package/bin/runners/lib/scan-runner.js +135 -0
  356. package/bin/runners/lib/schema-validator.js +350 -0
  357. package/bin/runners/lib/schemas/ajv-validator.js +464 -0
  358. package/bin/runners/lib/schemas/contracts.schema.json +160 -0
  359. package/bin/runners/lib/schemas/error-envelope.schema.json +105 -0
  360. package/bin/runners/lib/schemas/finding-v3.schema.json +151 -0
  361. package/bin/runners/lib/schemas/finding.schema.json +100 -0
  362. package/bin/runners/lib/schemas/mission-pack.schema.json +206 -0
  363. package/bin/runners/lib/schemas/proof-graph.schema.json +176 -0
  364. package/bin/runners/lib/schemas/reality-report.schema.json +162 -0
  365. package/bin/runners/lib/schemas/report-artifact.schema.json +120 -0
  366. package/bin/runners/lib/schemas/run-request.schema.json +108 -0
  367. package/bin/runners/lib/schemas/share-pack.schema.json +180 -0
  368. package/bin/runners/lib/schemas/ship-manifest.schema.json +251 -0
  369. package/bin/runners/lib/schemas/ship-report.schema.json +117 -0
  370. package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -0
  371. package/bin/runners/lib/schemas/validator.js +465 -0
  372. package/bin/runners/lib/schemas/verdict.schema.json +140 -0
  373. package/bin/runners/lib/score-history.js +282 -0
  374. package/bin/runners/lib/security-bridge.js +249 -0
  375. package/bin/runners/lib/server-usage.js +513 -0
  376. package/bin/runners/lib/share-pack.js +239 -0
  377. package/bin/runners/lib/ship-gate.js +832 -0
  378. package/bin/runners/lib/ship-manifest.js +1153 -0
  379. package/bin/runners/lib/ship-output-enterprise.js +239 -0
  380. package/bin/runners/lib/ship-output.js +1128 -0
  381. package/bin/runners/lib/snippets.js +67 -0
  382. package/bin/runners/lib/status-output.js +340 -0
  383. package/bin/runners/lib/terminal-ui.js +356 -0
  384. package/bin/runners/lib/truth.js +1691 -0
  385. package/bin/runners/lib/ui.js +562 -0
  386. package/bin/runners/lib/unified-cli-output.js +947 -0
  387. package/bin/runners/lib/unified-output.js +197 -0
  388. package/bin/runners/lib/upsell.js +410 -0
  389. package/bin/runners/lib/usage.js +153 -0
  390. package/bin/runners/lib/validate-patch.js +156 -0
  391. package/bin/runners/lib/verdict-engine.js +628 -0
  392. package/bin/runners/lib/verification.js +345 -0
  393. package/bin/runners/lib/why-tree.js +650 -0
  394. package/bin/runners/reality/engine.js +917 -0
  395. package/bin/runners/reality/flows.js +122 -0
  396. package/bin/runners/reality/report.js +378 -0
  397. package/bin/runners/reality/session.js +193 -0
  398. package/bin/runners/runAIAgent.js +229 -0
  399. package/bin/runners/runAgent.d.ts +5 -0
  400. package/bin/runners/runAgent.js +161 -0
  401. package/bin/runners/runAllowlist.js +418 -0
  402. package/bin/runners/runApprove.js +320 -0
  403. package/bin/runners/runAudit.js +692 -0
  404. package/bin/runners/runAuth.js +731 -0
  405. package/bin/runners/runCI.js +353 -0
  406. package/bin/runners/runCheckpoint.js +530 -0
  407. package/bin/runners/runClassify.js +928 -0
  408. package/bin/runners/runCleanup.js +343 -0
  409. package/bin/runners/runContext.d.ts +4 -0
  410. package/bin/runners/runContext.js +175 -0
  411. package/bin/runners/runDoctor.js +877 -0
  412. package/bin/runners/runEvidencePack.js +362 -0
  413. package/bin/runners/runFirewall.d.ts +5 -0
  414. package/bin/runners/runFirewall.js +134 -0
  415. package/bin/runners/runFirewallHook.d.ts +5 -0
  416. package/bin/runners/runFirewallHook.js +56 -0
  417. package/bin/runners/runFix.js +1355 -0
  418. package/bin/runners/runForge.js +451 -0
  419. package/bin/runners/runGuard.js +262 -0
  420. package/bin/runners/runInit.js +1927 -0
  421. package/bin/runners/runIntent.js +906 -0
  422. package/bin/runners/runKickoff.js +878 -0
  423. package/bin/runners/runLabs.js +424 -0
  424. package/bin/runners/runLaunch.js +2000 -0
  425. package/bin/runners/runLink.js +785 -0
  426. package/bin/runners/runMcp.js +1875 -0
  427. package/bin/runners/runPacks.js +2089 -0
  428. package/bin/runners/runPolish.d.ts +4 -0
  429. package/bin/runners/runPolish.js +390 -0
  430. package/bin/runners/runPromptFirewall.js +211 -0
  431. package/bin/runners/runProve.js +1411 -0
  432. package/bin/runners/runQuickstart.js +531 -0
  433. package/bin/runners/runReality.js +2260 -0
  434. package/bin/runners/runReport.js +726 -0
  435. package/bin/runners/runRuntime.js +110 -0
  436. package/bin/runners/runSafelist.js +1190 -0
  437. package/bin/runners/runScan.js +688 -0
  438. package/bin/runners/runShield.js +1282 -0
  439. package/bin/runners/runShip.js +1660 -0
  440. package/bin/runners/runTruth.d.ts +5 -0
  441. package/bin/runners/runTruth.js +101 -0
  442. package/bin/runners/runValidate.js +179 -0
  443. package/bin/runners/runWatch.js +478 -0
  444. package/bin/runners/utils.js +360 -0
  445. package/bin/scan.js +617 -0
  446. package/bin/vibecheck.js +1617 -0
  447. package/dist/guardrail/index.d.ts +2405 -0
  448. package/dist/guardrail/index.js +9747 -0
  449. package/dist/guardrail/index.js.map +1 -0
  450. package/dist/scanner/index.d.ts +282 -0
  451. package/dist/scanner/index.js +3395 -0
  452. package/dist/scanner/index.js.map +1 -0
  453. package/package.json +123 -104
  454. package/README.md +0 -491
  455. package/dist/index.js +0 -99711
  456. package/dist/index.js.map +0 -1
@@ -0,0 +1,1691 @@
1
+ // bin/runners/lib/truth.js
2
+ const fg = require("fast-glob");
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const crypto = require("crypto");
6
+ const parser = require("@babel/parser");
7
+ const traverse = require("@babel/traverse").default;
8
+ const t = require("@babel/types");
9
+
10
+ // Env Truth v1
11
+ const { buildEnvTruth } = require("./env");
12
+ // Auth Truth v1
13
+ const { buildAuthTruth } = require("./auth-truth");
14
+ // Billing Truth v1
15
+ const { buildBillingTruth } = require("./billing");
16
+ // Enforcement Truth v1
17
+ const { buildEnforcementTruth } = require("./enforcement");
18
+ // Multi-framework route detection v2
19
+ const { resolveAllRoutes, detectFrameworks } = require("./route-detection");
20
+
21
+ // ---------- constants ----------
22
+ const IGNORE_GLOBS = [
23
+ "**/node_modules/**",
24
+ "**/.next/**",
25
+ "**/dist/**",
26
+ "**/build/**",
27
+ "**/.turbo/**",
28
+ "**/.git/**",
29
+ "**/.vibecheck/**",
30
+ ];
31
+
32
+ const CODE_FILE_GLOBS = ["**/*.{ts,tsx,js,jsx}"];
33
+
34
+ // ---------- helpers ----------
35
+ function sha256(text) {
36
+ return "sha256:" + crypto.createHash("sha256").update(text).digest("hex");
37
+ }
38
+
39
+ function canonicalizeMethod(m) {
40
+ const u = String(m || "").toUpperCase();
41
+ if (u === "ALL" || u === "ANY" || u === "*") return "*";
42
+ return u;
43
+ }
44
+
45
+ function stripQueryHash(s) {
46
+ const v = String(s || "");
47
+ const q = v.indexOf("?");
48
+ const h = v.indexOf("#");
49
+ const cut = (q === -1 ? h : (h === -1 ? q : Math.min(q, h)));
50
+ return cut === -1 ? v : v.slice(0, cut);
51
+ }
52
+
53
+ /**
54
+ * Canonical path rules:
55
+ * - ensure leading slash
56
+ * - collapse multiple slashes
57
+ * - strip query/hash
58
+ * - normalize Next dynamic segments:
59
+ * [[...slug]] -> *slug?
60
+ * [...slug] -> *slug
61
+ * [id] -> :id
62
+ */
63
+ function canonicalizePath(p) {
64
+ let s = stripQueryHash(String(p || "").trim());
65
+
66
+ // If someone passed a full URL, only keep pathname-like portion if possible.
67
+ // (We still require local routes to start with "/" for client refs.)
68
+ const protoIdx = s.indexOf("://");
69
+ if (protoIdx !== -1) {
70
+ // attempt to strip scheme+host
71
+ const slashAfterHost = s.indexOf("/", protoIdx + 3);
72
+ s = slashAfterHost === -1 ? "/" : s.slice(slashAfterHost);
73
+ }
74
+
75
+ if (!s.startsWith("/")) s = "/" + s;
76
+ s = s.replace(/\/+/g, "/");
77
+
78
+ // Next dynamic segments (filesystem style)
79
+ s = s.replace(/\[\[\.{3}([^\]]+)\]\]/g, "*$1?"); // [[...slug]] -> *slug?
80
+ s = s.replace(/\[\.{3}([^\]]+)\]/g, "*$1"); // [...slug] -> *slug
81
+ s = s.replace(/\[([^\]]+)\]/g, ":$1"); // [id] -> :id
82
+
83
+ if (s.length > 1) s = s.replace(/\/$/, "");
84
+ return s;
85
+ }
86
+
87
+ function joinPaths(prefix, p) {
88
+ const a = canonicalizePath(prefix || "/");
89
+ const b = canonicalizePath(p || "/");
90
+ if (a === "/") return b;
91
+ if (b === "/") return a;
92
+ return canonicalizePath(a + "/" + b);
93
+ }
94
+
95
+ function parseFile(code, fileAbsForErrors) {
96
+ // Be permissive: production repos contain decorators, top-level await, etc.
97
+ return parser.parse(code, {
98
+ sourceType: "unambiguous",
99
+ errorRecovery: true,
100
+ allowImportExportEverywhere: true,
101
+ plugins: [
102
+ "typescript",
103
+ "jsx",
104
+ "dynamicImport",
105
+ "importMeta",
106
+ "topLevelAwait",
107
+ "classProperties",
108
+ "classPrivateProperties",
109
+ "classPrivateMethods",
110
+ "optionalChaining",
111
+ "nullishCoalescingOperator",
112
+ "decorators-legacy",
113
+ ],
114
+ sourceFilename: fileAbsForErrors || undefined,
115
+ });
116
+ }
117
+
118
+ // File cache for performance (avoids reading the same file multiple times)
119
+ const _FILE_CACHE = new Map();
120
+
121
+ function safeRead(fileAbs) {
122
+ if (_FILE_CACHE.has(fileAbs)) return _FILE_CACHE.get(fileAbs);
123
+ try {
124
+ const content = fs.readFileSync(fileAbs, "utf8");
125
+ _FILE_CACHE.set(fileAbs, content);
126
+ return content;
127
+ } catch {
128
+ return null;
129
+ }
130
+ }
131
+
132
+ // Clear cache to free memory after a scan (important for long-running processes)
133
+ function clearCache() {
134
+ _FILE_CACHE.clear();
135
+ }
136
+
137
+ function ensureDir(p) {
138
+ fs.mkdirSync(p, { recursive: true });
139
+ }
140
+
141
+ function evidenceFromLoc({ fileAbs, fileRel, loc, reason }) {
142
+ if (!loc) return null;
143
+ const code = safeRead(fileAbs);
144
+ if (!code) return null;
145
+
146
+ const lines = code.split(/\r?\n/);
147
+ const start = Math.max(1, loc.start?.line || 1);
148
+ const end = Math.max(start, loc.end?.line || start);
149
+ const snippet = lines.slice(start - 1, end).join("\n");
150
+
151
+ return {
152
+ id: `ev_${crypto.randomBytes(4).toString("hex")}`,
153
+ file: fileRel,
154
+ lines: `${start}-${end}`,
155
+ snippetHash: sha256(snippet),
156
+ reason,
157
+ };
158
+ }
159
+
160
+ function normalizeRel(repoRoot, fileAbs) {
161
+ return path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
162
+ }
163
+
164
+ function scoreConfidence(c) {
165
+ if (c === "high") return 3;
166
+ if (c === "med") return 2;
167
+ if (c === "low") return 1;
168
+ return 0;
169
+ }
170
+
171
+ function isRouteGroupSegment(seg) {
172
+ // Next route group: (group)
173
+ return seg.startsWith("(") && seg.endsWith(")");
174
+ }
175
+
176
+ function isParallelSegment(seg) {
177
+ // Next parallel routes: @slot
178
+ return seg.startsWith("@");
179
+ }
180
+
181
+ // ---------- Next: app router API ----------
182
+ const HTTP_EXPORTS = new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]);
183
+
184
+ function nextAppApiPathFromRel(fileRel) {
185
+ const idx = fileRel.indexOf("app/api/");
186
+ if (idx === -1) return null;
187
+
188
+ let sub = fileRel.slice(idx + "app/api/".length);
189
+
190
+ // route.ts / route.js / route.tsx / route.jsx
191
+ sub = sub.replace(/\/route\.(ts|tsx|js|jsx)$/, "");
192
+
193
+ // remove route groups + parallel segments from the filesystem path
194
+ const parts = sub.split("/").filter(Boolean).filter((seg) => !isRouteGroupSegment(seg) && !isParallelSegment(seg));
195
+ sub = parts.join("/");
196
+
197
+ return canonicalizePath("/api/" + sub);
198
+ }
199
+
200
+ async function resolveNextAppApiRoutes(repoRoot, stats) {
201
+ const files = await fg(["**/app/api/**/route.@(ts|tsx|js|jsx)"], {
202
+ cwd: repoRoot,
203
+ absolute: true,
204
+ ignore: IGNORE_GLOBS,
205
+ });
206
+
207
+ const out = [];
208
+
209
+ for (const fileAbs of files) {
210
+ const fileRel = normalizeRel(repoRoot, fileAbs);
211
+ const routePath = nextAppApiPathFromRel(fileRel);
212
+ if (!routePath) continue;
213
+
214
+ const code = safeRead(fileAbs);
215
+ if (!code) continue;
216
+
217
+ let ast;
218
+ try {
219
+ ast = parseFile(code, fileAbs);
220
+ } catch {
221
+ stats.parseErrors++;
222
+ continue;
223
+ }
224
+
225
+ const methods = [];
226
+
227
+ try {
228
+ traverse(ast, {
229
+ // export async function GET() {}
230
+ ExportNamedDeclaration(p) {
231
+ const decl = p.node.declaration;
232
+
233
+ if (t.isFunctionDeclaration(decl) && decl.id?.name) {
234
+ const n = decl.id.name.toUpperCase();
235
+ if (HTTP_EXPORTS.has(n)) methods.push({ method: n, loc: decl.loc, why: "export function" });
236
+ }
237
+
238
+ // export const GET = async () => {}
239
+ if (t.isVariableDeclaration(decl)) {
240
+ for (const d of decl.declarations) {
241
+ if (!t.isVariableDeclarator(d)) continue;
242
+ if (!t.isIdentifier(d.id)) continue;
243
+ const n = d.id.name.toUpperCase();
244
+ if (HTTP_EXPORTS.has(n)) methods.push({ method: n, loc: d.loc || decl.loc, why: "export const" });
245
+ }
246
+ }
247
+
248
+ // export { GET } from "./handler"
249
+ for (const s of p.node.specifiers || []) {
250
+ if (!t.isExportSpecifier(s)) continue;
251
+ if (!t.isIdentifier(s.exported)) continue;
252
+ const n = s.exported.name.toUpperCase();
253
+ if (HTTP_EXPORTS.has(n)) methods.push({ method: n, loc: s.loc || p.node.loc, why: "export re-export" });
254
+ }
255
+ },
256
+ });
257
+ } catch {
258
+ // Babel traverse can fail on some edge-case files; skip them
259
+ stats.parseErrors++;
260
+ continue;
261
+ }
262
+
263
+ if (methods.length === 0) {
264
+ // Still include route.ts, but with "*" and low confidence to avoid missing-route spam.
265
+ out.push({
266
+ method: "*",
267
+ path: routePath,
268
+ handler: fileRel,
269
+ confidence: "low",
270
+ framework: "next",
271
+ evidence: [],
272
+ });
273
+ continue;
274
+ }
275
+
276
+ for (const m of methods) {
277
+ const ev = evidenceFromLoc({
278
+ fileAbs,
279
+ fileRel,
280
+ loc: m.loc,
281
+ reason: `Next app router ${m.method} (${m.why})`,
282
+ });
283
+
284
+ out.push({
285
+ method: m.method,
286
+ path: routePath,
287
+ handler: fileRel,
288
+ confidence: m.why === "export re-export" ? "med" : "high",
289
+ framework: "next",
290
+ evidence: ev ? [ev] : [],
291
+ });
292
+ }
293
+ }
294
+
295
+ return out;
296
+ }
297
+
298
+ // ---------- Next: pages router API ----------
299
+ function nextPagesApiPathFromRel(fileRel) {
300
+ const idx = fileRel.indexOf("pages/api/");
301
+ if (idx === -1) return null;
302
+
303
+ let sub = fileRel.slice(idx + "pages/api/".length);
304
+ sub = sub.replace(/\.(ts|tsx|js|jsx)$/, "");
305
+
306
+ // pages/api/foo/index.ts -> /api/foo
307
+ if (sub === "index") sub = "";
308
+ sub = sub.replace(/\/index$/, "");
309
+
310
+ return canonicalizePath("/api/" + sub);
311
+ }
312
+
313
+ async function resolveNextPagesApiRoutes(repoRoot, stats) {
314
+ const files = await fg(["**/pages/api/**/*.@(ts|tsx|js|jsx)"], {
315
+ cwd: repoRoot,
316
+ absolute: true,
317
+ ignore: IGNORE_GLOBS,
318
+ });
319
+
320
+ const out = [];
321
+
322
+ for (const fileAbs of files) {
323
+ const fileRel = normalizeRel(repoRoot, fileAbs);
324
+
325
+ // Skip Next.js special files that aren't API routes (_app, _document, _utils, etc.)
326
+ if (fileRel.includes("/_") && !fileRel.includes("/_next")) continue;
327
+
328
+ const routePath = nextPagesApiPathFromRel(fileRel);
329
+ if (!routePath) continue;
330
+
331
+ const code = safeRead(fileAbs);
332
+ if (!code) continue;
333
+
334
+ // Parse to verify it's actually a route (has export default)
335
+ let ast;
336
+ try {
337
+ ast = parseFile(code, fileAbs);
338
+ } catch {
339
+ stats.parseErrors++;
340
+ continue;
341
+ }
342
+
343
+ // Check for 'export default' (Required for Pages Router API routes)
344
+ // Files without default export are helper files (db.ts, types.ts, utils.ts)
345
+ let hasDefaultExport = false;
346
+ try {
347
+ traverse(ast, {
348
+ ExportDefaultDeclaration(p) {
349
+ hasDefaultExport = true;
350
+ p.stop(); // Found it, stop traversing
351
+ },
352
+ });
353
+ } catch {
354
+ // Traverse failed, skip this file
355
+ stats.parseErrors++;
356
+ continue;
357
+ }
358
+
359
+ if (!hasDefaultExport) continue; // It's a helper file, not an API route
360
+
361
+ out.push({
362
+ method: "*", // Pages router handles all methods in one function
363
+ path: routePath,
364
+ handler: fileRel,
365
+ confidence: "med",
366
+ framework: "next",
367
+ evidence: [],
368
+ });
369
+ }
370
+
371
+ return out;
372
+ }
373
+
374
+ // ---------- minimal relative module resolver ----------
375
+ function exists(p) {
376
+ try {
377
+ return fs.statSync(p).isFile();
378
+ } catch {
379
+ return false;
380
+ }
381
+ }
382
+
383
+ function resolveRelativeModule(fromFileAbs, spec) {
384
+ if (!spec || (!spec.startsWith("./") && !spec.startsWith("../"))) return null;
385
+
386
+ const base = path.resolve(path.dirname(fromFileAbs), spec);
387
+ const candidates = [
388
+ base,
389
+ base + ".ts",
390
+ base + ".tsx",
391
+ base + ".js",
392
+ base + ".jsx",
393
+ path.join(base, "index.ts"),
394
+ path.join(base, "index.tsx"),
395
+ path.join(base, "index.js"),
396
+ path.join(base, "index.jsx"),
397
+ ];
398
+
399
+ for (const c of candidates) if (exists(c)) return c;
400
+ return null;
401
+ }
402
+
403
+ function extractRequireOrImportSpec(node) {
404
+ // require("./x")
405
+ if (t.isCallExpression(node) && t.isIdentifier(node.callee, { name: "require" })) {
406
+ const a0 = node.arguments && node.arguments[0];
407
+ if (t.isStringLiteral(a0)) return a0.value;
408
+ }
409
+
410
+ // import("./x")
411
+ if (t.isCallExpression(node) && node.callee && node.callee.type === "Import") {
412
+ const a0 = node.arguments && node.arguments[0];
413
+ if (t.isStringLiteral(a0)) return a0.value;
414
+ }
415
+
416
+ // await import("./x")
417
+ if (t.isAwaitExpression(node)) {
418
+ return extractRequireOrImportSpec(node.argument);
419
+ }
420
+
421
+ return null;
422
+ }
423
+
424
+ // ---------- Fastify route extraction ----------
425
+ const FASTIFY_METHODS = new Set(["get", "post", "put", "patch", "delete", "options", "head", "all"]);
426
+
427
+ function isFastifyMethod(name) {
428
+ return FASTIFY_METHODS.has(name);
429
+ }
430
+
431
+ function extractStringLiteral(node) {
432
+ return t.isStringLiteral(node) ? node.value : null;
433
+ }
434
+
435
+ function extractPrefixFromOpts(node) {
436
+ if (!t.isObjectExpression(node)) return null;
437
+ for (const p of node.properties) {
438
+ if (!t.isObjectProperty(p)) continue;
439
+ const key =
440
+ t.isIdentifier(p.key) ? p.key.name :
441
+ t.isStringLiteral(p.key) ? p.key.value :
442
+ null;
443
+ if (key === "prefix" && t.isStringLiteral(p.value)) return p.value.value;
444
+ }
445
+ return null;
446
+ }
447
+
448
+ function extractRouteObject(objExpr) {
449
+ let url = null;
450
+ let methods = [];
451
+ let hasHandler = false;
452
+ const hooks = [];
453
+
454
+ for (const p of objExpr.properties) {
455
+ if (!t.isObjectProperty(p)) continue;
456
+
457
+ const key =
458
+ t.isIdentifier(p.key) ? p.key.name :
459
+ t.isStringLiteral(p.key) ? p.key.value :
460
+ null;
461
+ if (!key) continue;
462
+
463
+ if (key === "url" && t.isStringLiteral(p.value)) url = p.value.value;
464
+
465
+ if (key === "method") {
466
+ if (t.isStringLiteral(p.value)) methods = [p.value.value];
467
+ if (t.isArrayExpression(p.value)) {
468
+ methods = p.value.elements.filter((e) => t.isStringLiteral(e)).map((e) => e.value);
469
+ }
470
+ }
471
+
472
+ if (key === "handler") hasHandler = true;
473
+ if (["preHandler", "onRequest", "preValidation", "preSerialization"].includes(key)) hooks.push(key);
474
+ }
475
+
476
+ return { url, methods, hasHandler, hooks };
477
+ }
478
+
479
+ function resolveFastifyRoutes(repoRoot, entryAbs, stats) {
480
+ const seen = new Set();
481
+ const routes = [];
482
+ const gaps = [];
483
+
484
+ function scanFile(fileAbs, prefix) {
485
+ if (!fileAbs || seen.has(fileAbs)) return;
486
+ seen.add(fileAbs);
487
+
488
+ const fileRel = normalizeRel(repoRoot, fileAbs);
489
+ const code = safeRead(fileAbs);
490
+ if (!code) return;
491
+
492
+ let ast;
493
+ try {
494
+ ast = parseFile(code, fileAbs);
495
+ } catch {
496
+ stats.parseErrors++;
497
+ return;
498
+ }
499
+
500
+ // best-effort: fastify instance identifiers
501
+ const fastifyNames = new Set(["fastify"]);
502
+
503
+ // Static string + local-plugin harvesting (cheap, big impact on false positives)
504
+ const _rawConstInits = new Map(); // name -> init node (evaluated lazily)
505
+ const _constStrings = new Map(); // name -> resolved string
506
+ const _localPlugins = new Map(); // name -> Function node
507
+
508
+ function evalStaticString(node, depth = 0) {
509
+ if (!node || depth > 6) return null;
510
+
511
+ if (t.isStringLiteral(node)) return node.value;
512
+
513
+ if (t.isTemplateLiteral(node) && node.expressions.length === 0) {
514
+ return node.quasis.map((q) => q.value.cooked || "").join("");
515
+ }
516
+
517
+ if (t.isIdentifier(node)) {
518
+ const name = node.name;
519
+ if (_constStrings.has(name)) return _constStrings.get(name);
520
+ const init = _rawConstInits.get(name);
521
+ if (!init) return null;
522
+ const v = evalStaticString(init, depth + 1);
523
+ if (typeof v === "string") {
524
+ // cap to avoid pathological blowups
525
+ if (v.length <= 4096) _constStrings.set(name, v);
526
+ return v;
527
+ }
528
+ return null;
529
+ }
530
+
531
+ if (t.isBinaryExpression(node, { operator: "+" })) {
532
+ const l = evalStaticString(node.left, depth + 1);
533
+ const r = evalStaticString(node.right, depth + 1);
534
+ if (typeof l !== "string" || typeof r !== "string") return null;
535
+ const out = l + r;
536
+ return out.length <= 4096 ? out : null;
537
+ }
538
+
539
+ return null;
540
+ }
541
+
542
+ function extractPrefixFromOptsV3(node) {
543
+ if (!t.isObjectExpression(node)) return null;
544
+ for (const p of node.properties) {
545
+ if (!t.isObjectProperty(p)) continue;
546
+ const key =
547
+ t.isIdentifier(p.key) ? p.key.name :
548
+ t.isStringLiteral(p.key) ? p.key.value :
549
+ null;
550
+ if (key !== "prefix") continue;
551
+ return evalStaticString(p.value);
552
+ }
553
+ return null;
554
+ }
555
+
556
+ function extractRouteObjectV3(objExpr) {
557
+ let url = null;
558
+ let methods = [];
559
+ let hasHandler = false;
560
+ const hooks = [];
561
+
562
+ for (const p of objExpr.properties) {
563
+ if (!t.isObjectProperty(p)) continue;
564
+
565
+ const key =
566
+ t.isIdentifier(p.key) ? p.key.name :
567
+ t.isStringLiteral(p.key) ? p.key.value :
568
+ null;
569
+ if (!key) continue;
570
+
571
+ if (key === "url") {
572
+ const u = evalStaticString(p.value);
573
+ if (typeof u === "string") url = u;
574
+ }
575
+
576
+ if (key === "method") {
577
+ if (t.isStringLiteral(p.value)) methods = [p.value.value];
578
+ if (t.isArrayExpression(p.value)) {
579
+ methods = p.value.elements.filter((e) => t.isStringLiteral(e)).map((e) => e.value);
580
+ }
581
+ }
582
+
583
+ if (key === "handler") hasHandler = true;
584
+ if (["preHandler", "onRequest", "preValidation", "preSerialization"].includes(key)) hooks.push(key);
585
+ }
586
+
587
+ return { url, methods, hasHandler, hooks };
588
+ }
589
+
590
+ function unwrapPluginArg(node) {
591
+ // fastify-plugin wrappers are common: fastify.register(fp(plugin))
592
+ // We unwrap 1 layer, best-effort.
593
+ if (!node) return node;
594
+ if (!t.isCallExpression(node)) return node;
595
+
596
+ const calleeName =
597
+ t.isIdentifier(node.callee) ? node.callee.name :
598
+ t.isMemberExpression(node.callee) && t.isIdentifier(node.callee.property) ? node.callee.property.name :
599
+ null;
600
+
601
+ if (!calleeName) return node;
602
+ if (!/^(fp|fastifyPlugin|plugin|fastifyPluginify)$/i.test(calleeName)) return node;
603
+
604
+ const a0 = node.arguments && node.arguments[0];
605
+ return a0 || node;
606
+ }
607
+
608
+ try {
609
+ traverse(ast, {
610
+ FunctionDeclaration(p) {
611
+ if (t.isIdentifier(p.node.id)) {
612
+ _localPlugins.set(p.node.id.name, p.node);
613
+ }
614
+ },
615
+ VariableDeclarator(p) {
616
+ if (!t.isIdentifier(p.node.id)) return;
617
+ const id = p.node.id.name;
618
+ const init = p.node.init;
619
+ if (!init) return;
620
+
621
+ _rawConstInits.set(id, init);
622
+
623
+ // local plugin: const routes = async (fastify) => { ... }
624
+ if (t.isFunctionExpression(init) || t.isArrowFunctionExpression(init)) {
625
+ _localPlugins.set(id, init);
626
+ }
627
+
628
+ // const app = Fastify()
629
+ if (t.isCallExpression(init) && t.isIdentifier(init.callee)) {
630
+ const cal = init.callee.name;
631
+ if (cal === "Fastify" || cal === "fastify") fastifyNames.add(id);
632
+ }
633
+
634
+ // const app = require("fastify")()
635
+ if (t.isCallExpression(init) && t.isCallExpression(init.callee)) {
636
+ const inner = init.callee;
637
+ if (t.isIdentifier(inner.callee, { name: "require" }) && t.isStringLiteral(inner.arguments?.[0], { value: "fastify" })) {
638
+ fastifyNames.add(id);
639
+ }
640
+ }
641
+ },
642
+ });
643
+ } catch {
644
+ // Babel traverse can fail on some edge-case files; skip this step
645
+ }
646
+
647
+ // helper: resolve imports for register(pluginIdent,...)
648
+ function resolveImportSpecForLocal(localName) {
649
+ let spec = null;
650
+
651
+ try {
652
+ traverse(ast, {
653
+ ImportDeclaration(ip) {
654
+ for (const s of ip.node.specifiers) {
655
+ if ((t.isImportDefaultSpecifier(s) || t.isImportSpecifier(s)) && s.local.name === localName) {
656
+ spec = ip.node.source.value;
657
+ }
658
+ }
659
+ },
660
+ VariableDeclarator(vp) {
661
+ if (!t.isIdentifier(vp.node.id) || vp.node.id.name !== localName) return;
662
+ const init = vp.node.init;
663
+ if (!t.isCallExpression(init)) return;
664
+ if (!t.isIdentifier(init.callee) || init.callee.name !== "require") return;
665
+ const a0 = init.arguments[0];
666
+ if (t.isStringLiteral(a0)) spec = a0.value;
667
+ },
668
+ });
669
+ } catch {
670
+ // Babel traverse can fail; ignore
671
+ }
672
+
673
+ return spec;
674
+ }
675
+
676
+ try {
677
+ traverse(ast, {
678
+ CallExpression(p) {
679
+ const callee = p.node.callee;
680
+ if (!t.isMemberExpression(callee)) return;
681
+ if (!t.isIdentifier(callee.object) || !t.isIdentifier(callee.property)) return;
682
+
683
+ const obj = callee.object.name;
684
+ const prop = callee.property.name;
685
+
686
+ if (!fastifyNames.has(obj)) return;
687
+
688
+ // fastify.get('/x', ...)
689
+ if (isFastifyMethod(prop)) {
690
+ const routeStr = evalStaticString(p.node.arguments[0]);
691
+ if (!routeStr) return;
692
+
693
+ const fullPath = joinPaths(prefix, routeStr);
694
+ const method = canonicalizeMethod(prop);
695
+
696
+ const ev = evidenceFromLoc({
697
+ fileAbs,
698
+ fileRel,
699
+ loc: p.node.loc,
700
+ reason: `Fastify ${prop.toUpperCase()}("${routeStr}")`,
701
+ });
702
+
703
+ routes.push({
704
+ method,
705
+ path: fullPath,
706
+ handler: fileRel,
707
+ confidence: "med",
708
+ framework: "fastify",
709
+ evidence: ev ? [ev] : [],
710
+ });
711
+ return;
712
+ }
713
+
714
+ // fastify.route({ method, url, handler })
715
+ if (prop === "route") {
716
+ const arg0 = p.node.arguments[0];
717
+ if (!t.isObjectExpression(arg0)) return;
718
+
719
+ const r = extractRouteObjectV3(arg0);
720
+ if (!r.url) return;
721
+
722
+ const fullPath = joinPaths(prefix, r.url);
723
+ const ms = (r.methods.length ? r.methods : ["*"]).map(canonicalizeMethod);
724
+
725
+ const ev = evidenceFromLoc({
726
+ fileAbs,
727
+ fileRel,
728
+ loc: p.node.loc,
729
+ reason: `Fastify.route({ url: "${r.url}" })`,
730
+ });
731
+
732
+ for (const m of ms) {
733
+ routes.push({
734
+ method: m,
735
+ path: fullPath,
736
+ handler: fileRel,
737
+ hooks: r.hooks,
738
+ confidence: r.hasHandler ? "med" : "low",
739
+ framework: "fastify",
740
+ evidence: ev ? [ev] : [],
741
+ });
742
+ }
743
+ return;
744
+ }
745
+
746
+ // fastify.register(plugin, { prefix })
747
+ if (prop === "register") {
748
+ const pluginArgRaw = unwrapPluginArg(p.node.arguments[0]);
749
+ const optsArg = p.node.arguments[1];
750
+
751
+ const childPrefixRaw = extractPrefixFromOptsV3(optsArg);
752
+ const childPrefix = childPrefixRaw ? joinPaths(prefix, childPrefixRaw) : prefix;
753
+
754
+ // inline plugin OR local plugin identifier (common in real Fastify codebases)
755
+ let pluginFn = null;
756
+ const localIdentName = t.isIdentifier(pluginArgRaw) ? pluginArgRaw.name : null;
757
+ if (t.isFunctionExpression(pluginArgRaw) || t.isArrowFunctionExpression(pluginArgRaw)) pluginFn = pluginArgRaw;
758
+ if (!pluginFn && localIdentName) pluginFn = _localPlugins.get(localIdentName) || null;
759
+
760
+ if (pluginFn) {
761
+ const param0 = pluginFn.params && pluginFn.params[0];
762
+ const innerName = t.isIdentifier(param0) ? param0.name : "fastify";
763
+ const bodyNode = pluginFn.body;
764
+ if (!t.isBlockStatement(bodyNode)) {
765
+ // We only handle block bodies. Expression-bodied arrows are rare for route plugins.
766
+ if (localIdentName) return;
767
+ }
768
+
769
+ // traverse just the plugin body (best effort)
770
+ try {
771
+ traverse(
772
+ bodyNode,
773
+ {
774
+ CallExpression(pp) {
775
+ const c = pp.node.callee;
776
+ if (!t.isMemberExpression(c)) return;
777
+ if (!t.isIdentifier(c.object) || !t.isIdentifier(c.property)) return;
778
+ if (c.object.name !== innerName) return;
779
+
780
+ const pr = c.property.name;
781
+
782
+ if (isFastifyMethod(pr)) {
783
+ const rs = evalStaticString(pp.node.arguments[0]);
784
+ if (!rs) return;
785
+
786
+ const fullPath = joinPaths(childPrefix, rs);
787
+ const method = canonicalizeMethod(pr);
788
+
789
+ const ev = evidenceFromLoc({
790
+ fileAbs,
791
+ fileRel,
792
+ loc: pp.node.loc,
793
+ reason: `Fastify plugin ${pr.toUpperCase()}("${rs}") prefix="${childPrefixRaw || ""}"`,
794
+ });
795
+
796
+ routes.push({
797
+ method,
798
+ path: fullPath,
799
+ handler: fileRel,
800
+ confidence: "med",
801
+ framework: "fastify",
802
+ evidence: ev ? [ev] : [],
803
+ });
804
+ }
805
+
806
+ if (pr === "route") {
807
+ const a0 = pp.node.arguments[0];
808
+ if (!t.isObjectExpression(a0)) return;
809
+
810
+ const r = extractRouteObjectV3(a0);
811
+ if (!r.url) return;
812
+
813
+ const fullPath = joinPaths(childPrefix, r.url);
814
+ const ms = (r.methods.length ? r.methods : ["*"]).map(canonicalizeMethod);
815
+
816
+ const ev = evidenceFromLoc({
817
+ fileAbs,
818
+ fileRel,
819
+ loc: pp.node.loc,
820
+ reason: `Fastify plugin route("${r.url}") prefix="${childPrefixRaw || ""}"`,
821
+ });
822
+
823
+ for (const m of ms) {
824
+ routes.push({
825
+ method: m,
826
+ path: fullPath,
827
+ handler: fileRel,
828
+ confidence: "med",
829
+ framework: "fastify",
830
+ evidence: ev ? [ev] : [],
831
+ });
832
+ }
833
+ }
834
+ },
835
+ },
836
+ p.scope,
837
+ p
838
+ );
839
+ } catch {
840
+ // Inner traverse can fail; skip this plugin body
841
+ }
842
+
843
+ // If this was a local plugin identifier, we've already extracted its routes.
844
+ if (localIdentName) return;
845
+ return;
846
+ }
847
+
848
+ // Resolve dynamic require/import spec directly (fastify.register(require("./x")) / import("./x"))
849
+ const dynSpec = extractRequireOrImportSpec(pluginArgRaw);
850
+ if (dynSpec) {
851
+ const resolved = resolveRelativeModule(fileAbs, dynSpec);
852
+ if (!resolved) {
853
+ gaps.push({ kind: "fastify_plugin_unresolved", file: fileRel, spec: dynSpec });
854
+ return;
855
+ }
856
+ scanFile(resolved, childPrefix);
857
+ return;
858
+ }
859
+
860
+ // imported plugin identifier
861
+ if (t.isIdentifier(pluginArgRaw)) {
862
+ const localName = pluginArgRaw.name;
863
+ const spec = resolveImportSpecForLocal(localName);
864
+
865
+ if (!spec) {
866
+ gaps.push({ kind: "fastify_plugin_unresolved", file: fileRel, name: localName });
867
+ return;
868
+ }
869
+
870
+ const resolved = resolveRelativeModule(fileAbs, spec);
871
+ if (!resolved) {
872
+ gaps.push({ kind: "fastify_plugin_unresolved", file: fileRel, spec });
873
+ return;
874
+ }
875
+
876
+ scanFile(resolved, childPrefix);
877
+ return;
878
+ }
879
+
880
+ // Anything else: unknown plugin shape. Mark a gap so analyzers can be lenient.
881
+ gaps.push({
882
+ kind: "fastify_plugin_unresolved",
883
+ file: fileRel,
884
+ note: "register() plugin not statically resolvable",
885
+ });
886
+ }
887
+ },
888
+ });
889
+ } catch {
890
+ // Babel traverse can fail on some edge-case files; skip
891
+ stats.parseErrors++;
892
+ }
893
+ }
894
+
895
+ scanFile(entryAbs, "/");
896
+ return { routes, gaps };
897
+ }
898
+
899
+ // ---------- client refs (fetch + axios + template literals best-effort) ----------
900
+ function isAxiosMember(node) {
901
+ return (
902
+ t.isMemberExpression(node) &&
903
+ t.isIdentifier(node.object) &&
904
+ t.isIdentifier(node.property) &&
905
+ ["get", "post", "put", "patch", "delete"].includes(node.property.name)
906
+ );
907
+ }
908
+
909
+ function isAxiosCallee(node) {
910
+ return t.isIdentifier(node, { name: "axios" }) || isAxiosMember(node);
911
+ }
912
+
913
+ function extractUrlLike(node) {
914
+ // "literal"
915
+ if (t.isStringLiteral(node)) return { url: node.value, confidence: "high", note: "string" };
916
+
917
+ // `literal`
918
+ if (t.isTemplateLiteral(node) && node.expressions.length === 0) {
919
+ return { url: node.quasis.map((q) => q.value.cooked || "").join(""), confidence: "high", note: "template_static" };
920
+ }
921
+
922
+ // `/api/x/${id}` -> "/api/x/:id" (med confidence)
923
+ if (t.isTemplateLiteral(node) && node.quasis.length >= 1) {
924
+ const start = node.quasis[0]?.value?.cooked || "";
925
+ if (!start.startsWith("/")) return null;
926
+
927
+ let built = "";
928
+ for (let i = 0; i < node.quasis.length; i++) {
929
+ built += node.quasis[i].value.cooked || "";
930
+ if (i < node.expressions.length) {
931
+ const expr = node.expressions[i];
932
+ if (t.isIdentifier(expr)) built += `:${expr.name}`;
933
+ else built += "*";
934
+ }
935
+ }
936
+ return { url: built, confidence: "med", note: "template_dynamic" };
937
+ }
938
+
939
+ // "/api/x" + "/y" or "/api/x" + id (low confidence)
940
+ if (t.isBinaryExpression(node, { operator: "+" })) {
941
+ if (t.isStringLiteral(node.left) && node.left.value.startsWith("/")) {
942
+ const left = node.left.value;
943
+ let right = "";
944
+ if (t.isStringLiteral(node.right)) right = node.right.value;
945
+ else if (t.isIdentifier(node.right)) right = `:${node.right.name}`;
946
+ else right = "*";
947
+ return { url: left + right, confidence: "low", note: "concat" };
948
+ }
949
+ }
950
+
951
+ return null;
952
+ }
953
+
954
+ function extractFetchMethodFromOptions(node) {
955
+ if (!t.isObjectExpression(node)) return "*";
956
+ for (const prop of node.properties) {
957
+ if (!t.isObjectProperty(prop)) continue;
958
+ const key =
959
+ t.isIdentifier(prop.key) ? prop.key.name :
960
+ t.isStringLiteral(prop.key) ? prop.key.value :
961
+ null;
962
+ if (key === "method" && t.isStringLiteral(prop.value)) return canonicalizeMethod(prop.value.value);
963
+ }
964
+ return "*";
965
+ }
966
+
967
+ function extractAxiosConfig(node) {
968
+ // axios({ url: "/api/x", method: "post" })
969
+ if (!t.isObjectExpression(node)) return null;
970
+
971
+ let urlNode = null;
972
+ let methodNode = null;
973
+
974
+ for (const prop of node.properties) {
975
+ if (!t.isObjectProperty(prop)) continue;
976
+ const key =
977
+ t.isIdentifier(prop.key) ? prop.key.name :
978
+ t.isStringLiteral(prop.key) ? prop.key.value :
979
+ null;
980
+ if (!key) continue;
981
+
982
+ if (key === "url") urlNode = prop.value;
983
+ if (key === "method") methodNode = prop.value;
984
+ }
985
+
986
+ const urlInfo = urlNode ? extractUrlLike(urlNode) : null;
987
+ if (!urlInfo) return null;
988
+
989
+ const method =
990
+ methodNode && t.isStringLiteral(methodNode)
991
+ ? canonicalizeMethod(methodNode.value)
992
+ : "*";
993
+
994
+ return { method, urlInfo };
995
+ }
996
+
997
+ async function resolveClientRouteRefs(repoRoot, stats) {
998
+ const files = await fg(CODE_FILE_GLOBS, {
999
+ cwd: repoRoot,
1000
+ absolute: true,
1001
+ ignore: [
1002
+ ...IGNORE_GLOBS,
1003
+ "**/test/**",
1004
+ "**/tests/**",
1005
+ "**/__tests__/**",
1006
+ "**/*.test.*",
1007
+ "**/*.spec.*",
1008
+ "**/jest.setup.*",
1009
+ "**/jest.config.*",
1010
+ "**/*.mock.*",
1011
+ "**/mocks/**",
1012
+ "**/fixtures/**",
1013
+ ],
1014
+ });
1015
+
1016
+ const out = [];
1017
+
1018
+ for (const fileAbs of files) {
1019
+ const fileRel = normalizeRel(repoRoot, fileAbs);
1020
+ const code = safeRead(fileAbs);
1021
+ if (!code) continue;
1022
+
1023
+ let ast;
1024
+ try {
1025
+ ast = parseFile(code, fileAbs);
1026
+ } catch {
1027
+ stats.parseErrors++;
1028
+ continue;
1029
+ }
1030
+
1031
+ try {
1032
+ traverse(ast, {
1033
+ CallExpression(p) {
1034
+ const callee = p.node.callee;
1035
+
1036
+ // fetch(url, opts)
1037
+ if (t.isIdentifier(callee, { name: "fetch" })) {
1038
+ const a0 = p.node.arguments[0];
1039
+ const a1 = p.node.arguments[1];
1040
+
1041
+ const urlInfo = extractUrlLike(a0);
1042
+ if (!urlInfo) return;
1043
+
1044
+ const url = urlInfo.url;
1045
+ if (!url.startsWith("/")) return;
1046
+
1047
+ const method = extractFetchMethodFromOptions(a1);
1048
+
1049
+ const ev = evidenceFromLoc({
1050
+ fileAbs,
1051
+ fileRel,
1052
+ loc: p.node.loc,
1053
+ reason: `Client fetch(${urlInfo.note}) "${stripQueryHash(url)}"`,
1054
+ });
1055
+
1056
+ out.push({
1057
+ method,
1058
+ path: canonicalizePath(url),
1059
+ source: fileRel,
1060
+ confidence: urlInfo.confidence,
1061
+ kind: "fetch",
1062
+ evidence: ev ? [ev] : [],
1063
+ });
1064
+ return;
1065
+ }
1066
+
1067
+ // axios.get("/api/x") etc
1068
+ if (isAxiosMember(callee)) {
1069
+ const verb = callee.property.name.toUpperCase();
1070
+ const a0 = p.node.arguments[0];
1071
+
1072
+ const urlInfo = extractUrlLike(a0);
1073
+ if (!urlInfo) return;
1074
+
1075
+ const url = urlInfo.url;
1076
+ if (!url.startsWith("/")) return;
1077
+
1078
+ const ev = evidenceFromLoc({
1079
+ fileAbs,
1080
+ fileRel,
1081
+ loc: p.node.loc,
1082
+ reason: `Client axios.${verb.toLowerCase()}(${urlInfo.note}) "${stripQueryHash(url)}"`,
1083
+ });
1084
+
1085
+ out.push({
1086
+ method: canonicalizeMethod(verb),
1087
+ path: canonicalizePath(url),
1088
+ source: fileRel,
1089
+ confidence: urlInfo.confidence,
1090
+ kind: "axios_member",
1091
+ evidence: ev ? [ev] : [],
1092
+ });
1093
+ return;
1094
+ }
1095
+
1096
+ // axios({ url, method })
1097
+ if (t.isIdentifier(callee, { name: "axios" })) {
1098
+ const a0 = p.node.arguments[0];
1099
+ const cfg = extractAxiosConfig(a0);
1100
+ if (!cfg) return;
1101
+
1102
+ const url = cfg.urlInfo.url;
1103
+ if (!url.startsWith("/")) return;
1104
+
1105
+ const ev = evidenceFromLoc({
1106
+ fileAbs,
1107
+ fileRel,
1108
+ loc: p.node.loc,
1109
+ reason: `Client axios(config:${cfg.urlInfo.note}) "${stripQueryHash(url)}"`,
1110
+ });
1111
+
1112
+ out.push({
1113
+ method: cfg.method,
1114
+ path: canonicalizePath(url),
1115
+ source: fileRel,
1116
+ confidence: cfg.urlInfo.confidence === "high" ? "high" : "med",
1117
+ kind: "axios_config",
1118
+ evidence: ev ? [ev] : [],
1119
+ });
1120
+ return;
1121
+ }
1122
+
1123
+ // useSWR("/api/user", fetcher) - Modern React data fetching
1124
+ if (t.isIdentifier(callee, { name: "useSWR" })) {
1125
+ const a0 = p.node.arguments[0];
1126
+
1127
+ const urlInfo = extractUrlLike(a0);
1128
+ if (!urlInfo) return;
1129
+
1130
+ const url = urlInfo.url;
1131
+ if (!url.startsWith("/")) return;
1132
+
1133
+ const ev = evidenceFromLoc({
1134
+ fileAbs,
1135
+ fileRel,
1136
+ loc: p.node.loc,
1137
+ reason: `Client useSWR(${urlInfo.note}) "${stripQueryHash(url)}"`,
1138
+ });
1139
+
1140
+ out.push({
1141
+ method: "GET", // SWR is almost always GET
1142
+ path: canonicalizePath(url),
1143
+ source: fileRel,
1144
+ confidence: urlInfo.confidence,
1145
+ kind: "useSWR",
1146
+ evidence: ev ? [ev] : [],
1147
+ });
1148
+ return;
1149
+ }
1150
+
1151
+ // useQuery (React Query / TanStack Query) - Another popular data fetching library
1152
+ if (t.isIdentifier(callee, { name: "useQuery" })) {
1153
+ // useQuery({ queryKey: [...], queryFn: () => fetch("/api/x") })
1154
+ // or useQuery(["key"], () => fetch("/api/x"))
1155
+ const a0 = p.node.arguments[0];
1156
+
1157
+ // Try to extract URL from the arguments (often in queryFn)
1158
+ if (t.isObjectExpression(a0)) {
1159
+ for (const prop of a0.properties) {
1160
+ if (!t.isObjectProperty(prop)) continue;
1161
+ if (!t.isIdentifier(prop.key, { name: "queryFn" })) continue;
1162
+
1163
+ // queryFn is often an arrow function with fetch inside
1164
+ const fn = prop.value;
1165
+ if (t.isArrowFunctionExpression(fn) || t.isFunctionExpression(fn)) {
1166
+ // Best effort: look for string literals that look like API paths
1167
+ const fnCode = code.slice(fn.start, fn.end);
1168
+ const urlMatch = fnCode.match(/["'`](\/api\/[^"'`]+)["'`]/);
1169
+ if (urlMatch) {
1170
+ const url = urlMatch[1].split("?")[0].split("#")[0];
1171
+ const ev = evidenceFromLoc({
1172
+ fileAbs,
1173
+ fileRel,
1174
+ loc: p.node.loc,
1175
+ reason: `Client useQuery(queryFn) "${url}"`,
1176
+ });
1177
+
1178
+ out.push({
1179
+ method: "GET",
1180
+ path: canonicalizePath(url),
1181
+ source: fileRel,
1182
+ confidence: "low", // Less certain extraction
1183
+ kind: "useQuery",
1184
+ evidence: ev ? [ev] : [],
1185
+ });
1186
+ }
1187
+ }
1188
+ }
1189
+ }
1190
+ }
1191
+ },
1192
+ });
1193
+ } catch {
1194
+ // Babel traverse can fail on some edge-case files; skip
1195
+ stats.parseErrors++;
1196
+ }
1197
+ }
1198
+
1199
+ return out;
1200
+ }
1201
+
1202
+ // ---------- workspace detection (best-effort, no new deps) ----------
1203
+ function readJsonIfExists(abs) {
1204
+ try {
1205
+ return JSON.parse(fs.readFileSync(abs, "utf8"));
1206
+ } catch {
1207
+ return null;
1208
+ }
1209
+ }
1210
+
1211
+ function detectWorkspaces(repoRoot) {
1212
+ const roots = [];
1213
+
1214
+ const pkg = readJsonIfExists(path.join(repoRoot, "package.json"));
1215
+ if (pkg && pkg.workspaces) {
1216
+ const ws = pkg.workspaces;
1217
+ const patterns = Array.isArray(ws) ? ws : Array.isArray(ws.packages) ? ws.packages : [];
1218
+ for (const pat of patterns) {
1219
+ if (typeof pat === "string") roots.push(pat);
1220
+ }
1221
+ }
1222
+
1223
+ // pnpm-workspace.yaml minimal parser (just handles `packages:` list)
1224
+ const pnpmWs = path.join(repoRoot, "pnpm-workspace.yaml");
1225
+ if (fs.existsSync(pnpmWs)) {
1226
+ const raw = safeRead(pnpmWs) || "";
1227
+ const lines = raw.split(/\r?\n/);
1228
+ let inPackages = false;
1229
+ for (const line of lines) {
1230
+ const l = line.trim();
1231
+ if (!l) continue;
1232
+ if (l.startsWith("packages:")) {
1233
+ inPackages = true;
1234
+ continue;
1235
+ }
1236
+ if (inPackages) {
1237
+ const m = l.match(/^-+\s*['"]?([^'"]+)['"]?\s*$/);
1238
+ if (m && m[1]) roots.push(m[1]);
1239
+ else if (!l.startsWith("-")) inPackages = false;
1240
+ }
1241
+ }
1242
+ }
1243
+
1244
+ // Expand to actual package.json roots
1245
+ const uniq = Array.from(new Set(roots)).filter(Boolean);
1246
+ const pkgJsonGlobs = uniq.map((p) => (p.endsWith("/") ? p : p + "/") + "package.json");
1247
+
1248
+ const found = pkgJsonGlobs.length
1249
+ ? fg.sync(pkgJsonGlobs, { cwd: repoRoot, absolute: true, ignore: IGNORE_GLOBS })
1250
+ : [];
1251
+
1252
+ const workspaces = found
1253
+ .map((abs) => path.dirname(abs))
1254
+ .map((abs) => normalizeRel(repoRoot, abs))
1255
+ .sort();
1256
+
1257
+ return workspaces;
1258
+ }
1259
+
1260
+ // ---------- fastify entry detection (monorepo-friendly) ----------
1261
+ async function detectFastifyEntries(repoRoot) {
1262
+ // Keep it targeted (fast), but broad enough for monorepos.
1263
+ const candidates = await fg(
1264
+ [
1265
+ "**/{server,app,main,index}.{ts,tsx,js,jsx}",
1266
+ "**/src/{server,app,main,index}.{ts,tsx,js,jsx}",
1267
+ "**/*fastify*.{ts,tsx,js,jsx}",
1268
+ "**/*api*.{ts,tsx,js,jsx}",
1269
+ ],
1270
+ {
1271
+ cwd: repoRoot,
1272
+ absolute: true,
1273
+ ignore: IGNORE_GLOBS,
1274
+ }
1275
+ );
1276
+
1277
+ const entries = [];
1278
+ const fastifySignal = /\b(Fastify\s*\(|fastify\s*\(|require\(['"]fastify['"]\)|from\s+['"]fastify['"])\b/;
1279
+ const listenSignal = /\.\s*(listen|ready)\s*\(/;
1280
+
1281
+ for (const fileAbs of candidates) {
1282
+ const code = safeRead(fileAbs);
1283
+ if (!code) continue;
1284
+ // Must look like fastify + server start-ish signal (reduces noise)
1285
+ if (fastifySignal.test(code) && listenSignal.test(code)) {
1286
+ entries.push(fileAbs);
1287
+ }
1288
+ }
1289
+
1290
+ return Array.from(new Set(entries));
1291
+ }
1292
+
1293
+ // ---------- truthpack build/write ----------
1294
+ async function buildTruthpack({ repoRoot, fastifyEntry }) {
1295
+ const stats = {
1296
+ parseErrors: 0,
1297
+ fastifyEntries: 0,
1298
+ fastifyRoutes: 0,
1299
+ nextAppRoutes: 0,
1300
+ nextPagesRoutes: 0,
1301
+ clientRefs: 0,
1302
+ serverRoutes: 0,
1303
+ gaps: 0,
1304
+ };
1305
+
1306
+ // Workspaces (for metadata + future use)
1307
+ const workspaces = detectWorkspaces(repoRoot);
1308
+
1309
+ // Next.js routes (App Router + Pages Router)
1310
+ const nextApp = await resolveNextAppApiRoutes(repoRoot, stats);
1311
+ const nextPages = await resolveNextPagesApiRoutes(repoRoot, stats);
1312
+
1313
+ stats.nextAppRoutes = nextApp.length;
1314
+ stats.nextPagesRoutes = nextPages.length;
1315
+
1316
+ // Fastify routes (monorepo-friendly)
1317
+ let fastify = { routes: [], gaps: [] };
1318
+
1319
+ if (fastifyEntry) {
1320
+ const entryAbs = path.isAbsolute(fastifyEntry) ? fastifyEntry : path.join(repoRoot, fastifyEntry);
1321
+ if (exists(entryAbs)) {
1322
+ const resolved = resolveFastifyRoutes(repoRoot, entryAbs, stats);
1323
+ fastify.routes.push(...resolved.routes);
1324
+ fastify.gaps.push(...resolved.gaps);
1325
+ stats.fastifyEntries = 1;
1326
+ }
1327
+ } else {
1328
+ const entries = await detectFastifyEntries(repoRoot);
1329
+ stats.fastifyEntries = entries.length;
1330
+
1331
+ for (const entryAbs of entries) {
1332
+ const resolved = resolveFastifyRoutes(repoRoot, entryAbs, stats);
1333
+ fastify.routes.push(...resolved.routes);
1334
+ fastify.gaps.push(...resolved.gaps);
1335
+ }
1336
+ }
1337
+
1338
+ stats.fastifyRoutes = fastify.routes.length;
1339
+
1340
+ // Multi-framework route detection v2 (Express, Flask, FastAPI, Django, Hono, Koa, etc.)
1341
+ const multiFramework = await resolveAllRoutes(repoRoot);
1342
+ const detectedFrameworks = await detectFrameworks(repoRoot);
1343
+
1344
+ // Client refs (JS/TS fetch/axios + Python requests/httpx)
1345
+ const clientRefs = await resolveClientRouteRefs(repoRoot, stats);
1346
+ const allClientRefs = [...clientRefs, ...(multiFramework.clientRefs || [])];
1347
+
1348
+ stats.clientRefs = allClientRefs.length;
1349
+
1350
+ // Merge all server routes (dedupe with priority)
1351
+ const serverRoutesRaw = [...nextApp, ...nextPages, ...(fastify.routes || []), ...(multiFramework.routes || [])];
1352
+
1353
+ const bestByKey = new Map(); // key = method:path
1354
+ for (const r of serverRoutesRaw) {
1355
+ const key = `${canonicalizeMethod(r.method)}:${canonicalizePath(r.path)}`;
1356
+
1357
+ const prev = bestByKey.get(key);
1358
+ if (!prev) {
1359
+ bestByKey.set(key, { ...r, method: canonicalizeMethod(r.method), path: canonicalizePath(r.path) });
1360
+ continue;
1361
+ }
1362
+
1363
+ // Prefer higher confidence, and prefer specific method over "*"
1364
+ const prevScore = scoreConfidence(prev.confidence) + (prev.method === "*" ? 0 : 1);
1365
+ const curScore = scoreConfidence(r.confidence) + (r.method === "*" ? 0 : 1);
1366
+
1367
+ if (curScore > prevScore) {
1368
+ bestByKey.set(key, { ...r, method: canonicalizeMethod(r.method), path: canonicalizePath(r.path) });
1369
+ }
1370
+ }
1371
+
1372
+ const server = Array.from(bestByKey.values());
1373
+ stats.serverRoutes = server.length;
1374
+
1375
+ // Merge gaps
1376
+ const allGaps = [...(fastify.gaps || []), ...(multiFramework.gaps || [])];
1377
+ stats.gaps = allGaps.length;
1378
+
1379
+ // Env Truth v1
1380
+ const env = await buildEnvTruth(repoRoot);
1381
+
1382
+ // Auth Truth v1
1383
+ const auth = await buildAuthTruth(repoRoot, server);
1384
+
1385
+ // Billing Truth v1
1386
+ const billing = await buildBillingTruth(repoRoot);
1387
+
1388
+ // Enforcement Truth v1
1389
+ const enforcement = buildEnforcementTruth(repoRoot, server);
1390
+
1391
+ // Determine frameworks
1392
+ const frameworks = new Set();
1393
+ detectedFrameworks.forEach((f) => frameworks.add(f));
1394
+ server.forEach((r) => r.framework && frameworks.add(r.framework));
1395
+ if (nextApp.length || nextPages.length) frameworks.add("next");
1396
+ if (fastify.routes.length) frameworks.add("fastify");
1397
+
1398
+ const truthpack = {
1399
+ meta: {
1400
+ version: "2.1.0",
1401
+ generatedAt: new Date().toISOString(),
1402
+ repoRoot,
1403
+ commit: { sha: process.env.VIBECHECK_COMMIT_SHA || "unknown" },
1404
+ stats,
1405
+ },
1406
+ project: {
1407
+ frameworks: Array.from(frameworks),
1408
+ workspaces,
1409
+ entrypoints: {
1410
+ fastify: fastifyEntry ? [fastifyEntry] : [], // entries auto-detected are not stored as rel here by default
1411
+ },
1412
+ },
1413
+ routes: { server, clientRefs: allClientRefs, gaps: allGaps },
1414
+ env,
1415
+ auth,
1416
+ billing,
1417
+ enforcement,
1418
+ };
1419
+
1420
+ const hash = sha256(JSON.stringify(truthpack));
1421
+ truthpack.index = { hashes: { truthpackHash: hash }, evidenceRefs: [] };
1422
+
1423
+ return truthpack;
1424
+ }
1425
+
1426
+ function writeTruthpack(repoRoot, truthpack) {
1427
+ const dir = path.join(repoRoot, ".vibecheck");
1428
+ ensureDir(dir);
1429
+
1430
+ const target = path.join(dir, "truthpack.json");
1431
+ const tmp = path.join(dir, `truthpack.${process.pid}.${Date.now()}.tmp.json`);
1432
+
1433
+ // atomic-ish write: write tmp then rename
1434
+ fs.writeFileSync(tmp, JSON.stringify(truthpack, null, 2));
1435
+ fs.renameSync(tmp, target);
1436
+ }
1437
+
1438
+ function loadTruthpack(repoRoot) {
1439
+ const specPath = path.join(repoRoot, ".vibecheck", "truthpack.json");
1440
+ const legacyPath = path.join(repoRoot, ".vibecheck", "truth", "truthpack.json");
1441
+
1442
+ try {
1443
+ return JSON.parse(fs.readFileSync(specPath, "utf8"));
1444
+ } catch {
1445
+ try {
1446
+ return JSON.parse(fs.readFileSync(legacyPath, "utf8"));
1447
+ } catch {
1448
+ return null;
1449
+ }
1450
+ }
1451
+ }
1452
+
1453
+ // ---------- RepoIndex-powered build (vNext) ----------
1454
+
1455
+ /**
1456
+ * Build truthpack using RepoIndex for single-pass file indexing
1457
+ * This is the optimized path that shares file reads across all extractors.
1458
+ *
1459
+ * Enable with: VIBECHECK_ENGINE_V2=1
1460
+ *
1461
+ * @param {Object} options
1462
+ * @param {string} options.repoRoot
1463
+ * @param {string} [options.fastifyEntry]
1464
+ * @param {boolean} [options.verbose]
1465
+ * @returns {Promise<Object>}
1466
+ */
1467
+ async function buildTruthpackV2({ repoRoot, fastifyEntry, verbose }) {
1468
+ const {
1469
+ createIndex,
1470
+ globalASTCache,
1471
+ logIndexSummary,
1472
+ extractNextAppRoutes,
1473
+ extractNextPagesRoutes,
1474
+ extractClientRefs,
1475
+ extractFastifyRoutes,
1476
+ detectFastifyEntries: detectFastifyEntriesV2,
1477
+ buildEnvTruthV2,
1478
+ buildBillingTruthV2,
1479
+ buildAuthTruthV2,
1480
+ buildEnforcementTruthV2,
1481
+ extractExpressRoutes,
1482
+ } = require("./engine");
1483
+
1484
+ const startTime = Date.now();
1485
+
1486
+ // Phase 0: Build RepoIndex (single glob pass)
1487
+ const index = await createIndex(repoRoot);
1488
+
1489
+ if (verbose || process.env.VIBECHECK_VERBOSE) {
1490
+ logIndexSummary(index);
1491
+ console.log(` AST Cache: ${globalASTCache.getSummary().hitRate} hit rate`);
1492
+ }
1493
+
1494
+ const stats = {
1495
+ parseErrors: 0,
1496
+ fastifyEntries: 0,
1497
+ fastifyRoutes: 0,
1498
+ nextAppRoutes: 0,
1499
+ nextPagesRoutes: 0,
1500
+ clientRefs: 0,
1501
+ serverRoutes: 0,
1502
+ gaps: 0,
1503
+ indexTimeMs: index.stats.indexTimeMs,
1504
+ };
1505
+
1506
+ // Use signals from index instead of re-detecting
1507
+ const detectedFrameworks = Array.from(index.signals.detectedFrameworks);
1508
+
1509
+ // Workspaces
1510
+ const workspaces = detectWorkspaces(repoRoot);
1511
+
1512
+ // Phase 1: Extract routes using optimized extractors (use RepoIndex + AST cache)
1513
+
1514
+ // Next.js routes - use optimized extractors
1515
+ const nextApp = extractNextAppRoutes(index, stats);
1516
+ const nextPages = extractNextPagesRoutes(index, stats);
1517
+
1518
+ stats.nextAppRoutes = nextApp.length;
1519
+ stats.nextPagesRoutes = nextPages.length;
1520
+
1521
+ // Fastify routes - use optimized extractor
1522
+ let fastify = { routes: [], gaps: [] };
1523
+
1524
+ if (index.hasFramework("fastify") || fastifyEntry) {
1525
+ if (fastifyEntry) {
1526
+ const entryAbs = path.isAbsolute(fastifyEntry) ? fastifyEntry : path.join(repoRoot, fastifyEntry);
1527
+ if (exists(entryAbs)) {
1528
+ const resolved = extractFastifyRoutes(index, entryAbs, stats);
1529
+ fastify.routes.push(...resolved.routes);
1530
+ fastify.gaps.push(...resolved.gaps);
1531
+ stats.fastifyEntries = 1;
1532
+ }
1533
+ } else {
1534
+ const entries = detectFastifyEntriesV2(index);
1535
+ stats.fastifyEntries = entries.length;
1536
+
1537
+ for (const entryAbs of entries) {
1538
+ const resolved = extractFastifyRoutes(index, entryAbs, stats);
1539
+ fastify.routes.push(...resolved.routes);
1540
+ fastify.gaps.push(...resolved.gaps);
1541
+ }
1542
+ }
1543
+ }
1544
+
1545
+ stats.fastifyRoutes = fastify.routes.length;
1546
+
1547
+ // Express routes - use optimized extractor
1548
+ const expressRoutes = extractExpressRoutes(index, stats);
1549
+
1550
+ // Multi-framework routes (Flask, Django, etc. - still uses old resolvers for non-JS frameworks)
1551
+ // Express is handled above via optimized extractor
1552
+ const multiFramework = await resolveAllRoutes(repoRoot, {
1553
+ mode: process.env.VIBECHECK_ROUTE_SCAN_MODE || "smart",
1554
+ verbose: verbose || !!process.env.VIBECHECK_VERBOSE_ROUTES,
1555
+ skipExpress: true, // Skip Express since we handle it above with optimized extractor
1556
+ });
1557
+
1558
+ // Client refs - use optimized extractor
1559
+ const clientRefsOptimized = extractClientRefs(index, stats);
1560
+ const allClientRefs = [...clientRefsOptimized, ...(multiFramework.clientRefs || [])];
1561
+
1562
+ stats.clientRefs = allClientRefs.length;
1563
+
1564
+ // Merge server routes (including optimized Express routes)
1565
+ const serverRoutesRaw = [...nextApp, ...nextPages, ...(fastify.routes || []), ...expressRoutes, ...(multiFramework.routes || [])];
1566
+
1567
+ const bestByKey = new Map();
1568
+ for (const r of serverRoutesRaw) {
1569
+ const key = `${canonicalizeMethod(r.method)}:${canonicalizePath(r.path)}`;
1570
+ const prev = bestByKey.get(key);
1571
+ if (!prev) {
1572
+ bestByKey.set(key, { ...r, method: canonicalizeMethod(r.method), path: canonicalizePath(r.path) });
1573
+ continue;
1574
+ }
1575
+ const prevScore = scoreConfidence(prev.confidence) + (prev.method === "*" ? 0 : 1);
1576
+ const curScore = scoreConfidence(r.confidence) + (r.method === "*" ? 0 : 1);
1577
+ if (curScore > prevScore) {
1578
+ bestByKey.set(key, { ...r, method: canonicalizeMethod(r.method), path: canonicalizePath(r.path) });
1579
+ }
1580
+ }
1581
+
1582
+ const server = Array.from(bestByKey.values());
1583
+ stats.serverRoutes = server.length;
1584
+
1585
+ // Merge gaps
1586
+ const allGaps = [...(fastify.gaps || []), ...(multiFramework.gaps || [])];
1587
+ stats.gaps = allGaps.length;
1588
+
1589
+ // Phase 2: Build other truths (env, auth, billing, enforcement)
1590
+ // All use optimized extractors with RepoIndex
1591
+ const env = buildEnvTruthV2(index, stats);
1592
+ const auth = buildAuthTruthV2(index, server);
1593
+ const billing = buildBillingTruthV2(index, stats);
1594
+ const enforcement = buildEnforcementTruthV2(index, server);
1595
+
1596
+ // Determine frameworks
1597
+ const frameworks = new Set(detectedFrameworks);
1598
+ server.forEach((r) => r.framework && frameworks.add(r.framework));
1599
+ if (nextApp.length || nextPages.length) frameworks.add("next");
1600
+ if (fastify.routes.length) frameworks.add("fastify");
1601
+
1602
+ stats.totalTimeMs = Date.now() - startTime;
1603
+
1604
+ const truthpack = {
1605
+ meta: {
1606
+ version: "2.2.0", // Bump version for v2 engine
1607
+ generatedAt: new Date().toISOString(),
1608
+ repoRoot,
1609
+ commit: { sha: process.env.VIBECHECK_COMMIT_SHA || "unknown" },
1610
+ stats,
1611
+ engine: "v2", // Mark as v2 engine
1612
+ },
1613
+ project: {
1614
+ frameworks: Array.from(frameworks),
1615
+ workspaces,
1616
+ entrypoints: {
1617
+ fastify: fastifyEntry ? [fastifyEntry] : [],
1618
+ },
1619
+ },
1620
+ routes: { server, clientRefs: allClientRefs, gaps: allGaps },
1621
+ env,
1622
+ auth,
1623
+ billing,
1624
+ enforcement,
1625
+ };
1626
+
1627
+ const hash = sha256(JSON.stringify(truthpack));
1628
+ truthpack.index = {
1629
+ hashes: { truthpackHash: hash },
1630
+ evidenceRefs: [],
1631
+ repoIndex: {
1632
+ totalFiles: index.stats.totalFiles,
1633
+ totalSize: index.stats.totalSize,
1634
+ indexTimeMs: index.stats.indexTimeMs,
1635
+ },
1636
+ };
1637
+
1638
+ // Clear caches to free memory
1639
+ index.clearContentCache();
1640
+ clearCache();
1641
+
1642
+ return truthpack;
1643
+ }
1644
+
1645
+ /**
1646
+ * Smart buildTruthpack - uses v2 engine by default for better performance.
1647
+ * Set VIBECHECK_ENGINE_V1=1 to fall back to v1 for backward compatibility.
1648
+ *
1649
+ * V2 Engine Benefits:
1650
+ * - Single-pass file indexing (RepoIndex)
1651
+ * - Shared AST cache (globalASTCache)
1652
+ * - Token prefiltering for faster file selection
1653
+ * - ~30% faster on typical projects
1654
+ */
1655
+ async function buildTruthpackSmart(options) {
1656
+ if (process.env.VIBECHECK_ENGINE_V1 === "1") {
1657
+ return buildTruthpack(options);
1658
+ }
1659
+ // V2 is now the default - uses RepoIndex + AST cache
1660
+ return buildTruthpackV2(options);
1661
+ }
1662
+
1663
+ module.exports = {
1664
+ canonicalizeMethod,
1665
+ canonicalizePath,
1666
+ buildTruthpack,
1667
+ buildTruthpackV2,
1668
+ buildTruthpackSmart,
1669
+ writeTruthpack,
1670
+ loadTruthpack,
1671
+ clearCache, // Clear file cache to free memory (important for long-running processes)
1672
+ // kept for backward compatibility if other code imports it,
1673
+ // but fastifyEntry is now optional and auto-detected.
1674
+ detectFastifyEntry: function detectFastifyEntry(repoRoot) {
1675
+ const candidates = [
1676
+ "src/server.ts",
1677
+ "src/server.js",
1678
+ "server.ts",
1679
+ "server.js",
1680
+ "src/index.ts",
1681
+ "src/index.js",
1682
+ "index.ts",
1683
+ "index.js",
1684
+ ];
1685
+ for (const rel of candidates) {
1686
+ const abs = path.join(repoRoot, rel);
1687
+ if (exists(abs)) return rel;
1688
+ }
1689
+ return null;
1690
+ },
1691
+ };