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,1322 @@
1
+ /**
2
+ * Route Truth v1 - JavaScript Runtime (hardened + more accurate)
3
+ *
4
+ * Upgrades vs your version:
5
+ * - Next.js App/Pages routes: AST-based export detection + safer path derivation
6
+ * - Fastify routes: AST-based extraction (get/post/route/register + inline plugins + relative import resolution)
7
+ * - Better canonicalization + prefix joining + safer matching (no weird edge crashes)
8
+ * - Gaps are real (unresolved plugins/modules) instead of silent “false”
9
+ */
10
+
11
+ const fs = require("fs");
12
+ const path = require("path");
13
+ const crypto = require("crypto");
14
+
15
+ let fg = null;
16
+ try {
17
+ fg = require("fast-glob");
18
+ } catch { /* optional */ }
19
+
20
+ const parser = require("@babel/parser");
21
+ const traverse = require("@babel/traverse").default;
22
+ const t = require("@babel/types");
23
+
24
+ // ============================================================================
25
+ // CANONICALIZATION + MATCHING
26
+ // ============================================================================
27
+
28
+ function canonicalizePath(p) {
29
+ let s = String(p || "").trim();
30
+ if (!s) return "/";
31
+ if (!s.startsWith("/")) s = "/" + s;
32
+ s = s.replace(/\/+/g, "/");
33
+
34
+ // Next.js dynamic segments
35
+ s = s.replace(/\[\[\.{3}([^\]]+)\]\]/g, "*$1?"); // [[...slug]] -> *slug?
36
+ s = s.replace(/\[\.{3}([^\]]+)\]/g, "*$1"); // [...slug] -> *slug
37
+ s = s.replace(/\[([^\]]+)\]/g, ":$1"); // [id] -> :id
38
+
39
+ if (s.length > 1) s = s.replace(/\/$/, "");
40
+ return s;
41
+ }
42
+
43
+ function canonicalizeMethod(m) {
44
+ const u = String(m || "").toUpperCase();
45
+ if (u === "ALL" || u === "ANY" || u === "*" ) return "*";
46
+ return u || "*";
47
+ }
48
+
49
+ function joinPrefix(prefix, p) {
50
+ const a = canonicalizePath(prefix || "/");
51
+ const b = canonicalizePath(p || "/");
52
+ if (a === "/") return b;
53
+ if (b === "/") return a;
54
+ return canonicalizePath(a + "/" + b);
55
+ }
56
+
57
+ function isParameterizedPath(p) {
58
+ const s = canonicalizePath(p);
59
+ return s.includes(":") || s.includes("*");
60
+ }
61
+
62
+ /**
63
+ * Match a pattern against a concrete path.
64
+ * Supported:
65
+ * - :id matches one segment
66
+ * - *slug or *slug? matches the rest of the path (0+ segments)
67
+ */
68
+ function matchPath(pattern, concrete) {
69
+ const pat = canonicalizePath(pattern);
70
+ const con = canonicalizePath(concrete);
71
+
72
+ if (pat === con) return true;
73
+
74
+ const pParts = pat.split("/").filter(Boolean);
75
+ const cParts = con.split("/").filter(Boolean);
76
+
77
+ let pIdx = 0, cIdx = 0;
78
+
79
+ while (pIdx < pParts.length && cIdx < cParts.length) {
80
+ const pSeg = pParts[pIdx];
81
+ const cSeg = cParts[cIdx];
82
+
83
+ if (pSeg.startsWith("*")) {
84
+ // splat: match remainder (including empty remainder)
85
+ return true;
86
+ }
87
+ if (pSeg.startsWith(":")) {
88
+ pIdx++; cIdx++; continue;
89
+ }
90
+ if (pSeg !== cSeg) return false;
91
+
92
+ pIdx++; cIdx++;
93
+ }
94
+
95
+ // If pattern still has a trailing splat, it matches empty remainder
96
+ if (pIdx === pParts.length - 1 && pParts[pIdx]?.startsWith("*")) return true;
97
+
98
+ return pIdx === pParts.length && cIdx === cParts.length;
99
+ }
100
+
101
+ function matchMethod(pattern, concrete) {
102
+ const p = canonicalizeMethod(pattern);
103
+ const c = canonicalizeMethod(concrete);
104
+ if (p === "*") return true;
105
+ return p === c;
106
+ }
107
+
108
+ // ============================================================================
109
+ // COMMON HELPERS
110
+ // ============================================================================
111
+
112
+ const NEXT_HTTP_METHODS = new Set(["GET","POST","PUT","PATCH","DELETE","OPTIONS","HEAD"]);
113
+ let evidenceCounter = 0;
114
+
115
+ function sha256Short(txt) {
116
+ return crypto.createHash("sha256").update(String(txt || "")).digest("hex").slice(0, 16);
117
+ }
118
+
119
+ function createEvidence(file, lines, reason, snippet) {
120
+ evidenceCounter++;
121
+ return {
122
+ id: `ev_${String(evidenceCounter).padStart(4, "0")}`,
123
+ file,
124
+ lines,
125
+ snippetHash: `sha256:${sha256Short(snippet || "")}`,
126
+ reason,
127
+ };
128
+ }
129
+
130
+ function safeRead(fileAbs) {
131
+ try { return fs.readFileSync(fileAbs, "utf8"); } catch { return null; }
132
+ }
133
+
134
+ function parseFile(code) {
135
+ return parser.parse(code, {
136
+ sourceType: "unambiguous",
137
+ plugins: ["typescript", "jsx"],
138
+ errorRecovery: true,
139
+ ranges: false,
140
+ });
141
+ }
142
+
143
+ function evidenceFromLoc({ fileAbs, fileRel, loc, reason }) {
144
+ if (!loc || !loc.start) return [];
145
+ const code = safeRead(fileAbs);
146
+ if (!code) return [];
147
+ const lines = code.split(/\r?\n/);
148
+ const start = Math.max(1, loc.start.line || 1);
149
+ const end = Math.max(start, loc.end?.line || start);
150
+ const snippet = lines.slice(start - 1, end).join("\n");
151
+ return [createEvidence(fileRel, `${start}-${end}`, reason, snippet)];
152
+ }
153
+
154
+ function findFilesFallback(dirAbs, includeRe, excludeRe) {
155
+ const out = [];
156
+ function walk(d) {
157
+ let entries;
158
+ try { entries = fs.readdirSync(d, { withFileTypes: true }); } catch { return; }
159
+ for (const ent of entries) {
160
+ const full = path.join(d, ent.name);
161
+ if (ent.isDirectory()) {
162
+ if (
163
+ ent.name === "node_modules" ||
164
+ ent.name === ".next" ||
165
+ ent.name === "dist" ||
166
+ ent.name === "build" ||
167
+ ent.name === "coverage" ||
168
+ ent.name.startsWith(".")
169
+ ) continue;
170
+ walk(full);
171
+ } else if (ent.isFile()) {
172
+ if (excludeRe && excludeRe.test(ent.name)) continue;
173
+ if (includeRe.test(ent.name)) out.push(full);
174
+ }
175
+ }
176
+ }
177
+ walk(dirAbs);
178
+ return out;
179
+ }
180
+
181
+ async function globFiles(repoRoot, patterns, ignore) {
182
+ if (fg) {
183
+ return fg(patterns, {
184
+ cwd: repoRoot,
185
+ absolute: true,
186
+ dot: false,
187
+ ignore: ignore || [
188
+ "**/node_modules/**",
189
+ "**/.next/**",
190
+ "**/dist/**",
191
+ "**/build/**",
192
+ "**/coverage/**",
193
+ ],
194
+ });
195
+ }
196
+
197
+ // fallback: only supports the specific Next patterns we use
198
+ const out = [];
199
+ for (const ptn of patterns) {
200
+ // minimal handling: find root dirs from patterns
201
+ if (ptn.includes("app/api") || ptn.includes("src/app/api")) {
202
+ const dir1 = path.join(repoRoot, "app", "api");
203
+ const dir2 = path.join(repoRoot, "src", "app", "api");
204
+ if (fs.existsSync(dir1)) out.push(...findFilesFallback(dir1, /route\.(ts|js)$/));
205
+ if (fs.existsSync(dir2)) out.push(...findFilesFallback(dir2, /route\.(ts|js)$/));
206
+ }
207
+ if (ptn.includes("pages/api") || ptn.includes("src/pages/api")) {
208
+ const dir1 = path.join(repoRoot, "pages", "api");
209
+ const dir2 = path.join(repoRoot, "src", "pages", "api");
210
+ if (fs.existsSync(dir1)) out.push(...findFilesFallback(dir1, /\.(ts|js)$/, /\.d\.ts$/));
211
+ if (fs.existsSync(dir2)) out.push(...findFilesFallback(dir2, /\.(ts|js)$/, /\.d\.ts$/));
212
+ }
213
+ }
214
+ return Array.from(new Set(out));
215
+ }
216
+
217
+ // ============================================================================
218
+ // NEXT.JS RESOLVER (AST)
219
+ // ============================================================================
220
+
221
+ function extractNextAppRouterMethodsAST(ast) {
222
+ const methods = [];
223
+ traverse(ast, {
224
+ ExportNamedDeclaration(p) {
225
+ const decl = p.node.declaration;
226
+
227
+ // export function GET() {}
228
+ if (t.isFunctionDeclaration(decl) && decl.id?.name) {
229
+ const n = decl.id.name.toUpperCase();
230
+ if (NEXT_HTTP_METHODS.has(n)) {
231
+ methods.push({ name: n, loc: decl.loc });
232
+ }
233
+ }
234
+
235
+ // export const GET = () => {}
236
+ if (t.isVariableDeclaration(decl)) {
237
+ for (const d of decl.declarations) {
238
+ if (!t.isIdentifier(d.id)) continue;
239
+ const n = d.id.name.toUpperCase();
240
+ if (NEXT_HTTP_METHODS.has(n)) {
241
+ methods.push({ name: n, loc: d.loc || decl.loc });
242
+ }
243
+ }
244
+ }
245
+ },
246
+ });
247
+ return methods;
248
+ }
249
+
250
+ function deriveNextAppRoutePath(fileRel) {
251
+ // matches: app/api/**/route.ts|js OR src/app/api/**/route.ts|js
252
+ const m = fileRel.match(/(?:^|\/)(?:src\/)?app\/api\/(.+)\/route\.(ts|js)$/);
253
+ if (!m) return null;
254
+ const sub = m[1];
255
+ return canonicalizePath("/api/" + sub);
256
+ }
257
+
258
+ function deriveNextPagesRoutePath(fileRel) {
259
+ // matches: pages/api/**.ts|js OR src/pages/api/**.ts|js
260
+ const m = fileRel.match(/(?:^|\/)(?:src\/)?pages\/api\/(.+)\.(ts|js)$/);
261
+ if (!m) return null;
262
+ let sub = m[1];
263
+ sub = sub.replace(/\/index$/, ""); // /foo/index -> /foo
264
+ return canonicalizePath("/api/" + sub);
265
+ }
266
+
267
+ async function resolveNextRoutes(repoRoot) {
268
+ const routes = [];
269
+
270
+ // App Router
271
+ const appFiles = await globFiles(repoRoot, [
272
+ "**/app/api/**/route.@(ts|js)",
273
+ "**/src/app/api/**/route.@(ts|js)",
274
+ ]);
275
+
276
+ for (const fileAbs of appFiles) {
277
+ const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
278
+ const routePath = deriveNextAppRoutePath(fileRel);
279
+ if (!routePath) continue;
280
+
281
+ const code = safeRead(fileAbs);
282
+ if (!code) continue;
283
+
284
+ let ast;
285
+ try { ast = parseFile(code); } catch { continue; }
286
+
287
+ const methods = extractNextAppRouterMethodsAST(ast);
288
+
289
+ if (methods.length === 0) {
290
+ routes.push({
291
+ method: "*",
292
+ path: routePath,
293
+ handler: fileRel,
294
+ framework: "next",
295
+ routerType: "app",
296
+ confidence: "low",
297
+ evidence: [createEvidence(fileRel, "1", "route file with no method exports", code.slice(0, 140))],
298
+ });
299
+ continue;
300
+ }
301
+
302
+ for (const m of methods) {
303
+ routes.push({
304
+ method: m.name,
305
+ path: routePath,
306
+ handler: fileRel,
307
+ framework: "next",
308
+ routerType: "app",
309
+ confidence: "high",
310
+ evidence: evidenceFromLoc({ fileAbs, fileRel, loc: m.loc, reason: `Next app router export ${m.name}` }),
311
+ });
312
+ }
313
+ }
314
+
315
+ // Pages Router
316
+ const pagesFiles = await globFiles(repoRoot, [
317
+ "**/pages/api/**/*.@(ts|js)",
318
+ "**/src/pages/api/**/*.@(ts|js)",
319
+ ]);
320
+
321
+ for (const fileAbs of pagesFiles) {
322
+ const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
323
+ if (fileRel.endsWith(".d.ts")) continue;
324
+
325
+ const routePath = deriveNextPagesRoutePath(fileRel);
326
+ if (!routePath) continue;
327
+
328
+ const code = safeRead(fileAbs);
329
+ if (!code) continue;
330
+
331
+ const hasDefaultExport = /\bexport\s+default\b/.test(code);
332
+
333
+ routes.push({
334
+ method: "*",
335
+ path: routePath,
336
+ handler: fileRel,
337
+ framework: "next",
338
+ routerType: "pages",
339
+ confidence: hasDefaultExport ? "med" : "low",
340
+ evidence: [createEvidence(fileRel, "1", "Next pages API route", code.slice(0, 140))],
341
+ });
342
+ }
343
+
344
+ return routes;
345
+ }
346
+
347
+ // ============================================================================
348
+ // FASTIFY RESOLVER (AST, follows register prefixes + relative plugin modules)
349
+ // ============================================================================
350
+
351
+ const FASTIFY_METHODS = new Set(["get","post","put","patch","delete","options","head","all"]);
352
+
353
+ function existsFile(p) {
354
+ try { return fs.statSync(p).isFile(); } catch { return false; }
355
+ }
356
+
357
+ function existsDir(p) {
358
+ try { return fs.statSync(p).isDirectory(); } catch { return false; }
359
+ }
360
+
361
+ /**
362
+ * Find project root by walking up from a file until we find package.json
363
+ */
364
+ function findProjectRoot(fromFileAbs) {
365
+ let dir = path.dirname(fromFileAbs);
366
+ const root = path.parse(dir).root;
367
+ while (dir !== root) {
368
+ if (existsFile(path.join(dir, "package.json"))) return dir;
369
+ const parent = path.dirname(dir);
370
+ if (parent === dir) break;
371
+ dir = parent;
372
+ }
373
+ return null;
374
+ }
375
+
376
+ /**
377
+ * Load and cache tsconfig.json paths for a project
378
+ */
379
+ const tsconfigCache = new Map();
380
+
381
+ function loadTsConfigPaths(projectRoot) {
382
+ if (!projectRoot) return null;
383
+ if (tsconfigCache.has(projectRoot)) return tsconfigCache.get(projectRoot);
384
+
385
+ const tsconfigPath = path.join(projectRoot, "tsconfig.json");
386
+ if (!existsFile(tsconfigPath)) {
387
+ tsconfigCache.set(projectRoot, null);
388
+ return null;
389
+ }
390
+
391
+ try {
392
+ const raw = fs.readFileSync(tsconfigPath, "utf8");
393
+ // Remove comments (// and /* */) for JSON parsing
394
+ const cleaned = raw
395
+ .replace(/\/\/.*$/gm, "")
396
+ .replace(/\/\*[\s\S]*?\*\//g, "");
397
+ const tsconfig = JSON.parse(cleaned);
398
+
399
+ const paths = tsconfig?.compilerOptions?.paths || {};
400
+ const baseUrl = tsconfig?.compilerOptions?.baseUrl || ".";
401
+ const baseDir = path.resolve(projectRoot, baseUrl);
402
+
403
+ const result = { paths, baseDir, projectRoot };
404
+ tsconfigCache.set(projectRoot, result);
405
+ return result;
406
+ } catch {
407
+ tsconfigCache.set(projectRoot, null);
408
+ return null;
409
+ }
410
+ }
411
+
412
+ /**
413
+ * Resolve a module specifier using TypeScript path mappings
414
+ */
415
+ function resolveWithTsConfigPaths(spec, tsConfig) {
416
+ if (!tsConfig || !tsConfig.paths) return null;
417
+
418
+ const { paths, baseDir } = tsConfig;
419
+
420
+ for (const [pattern, targets] of Object.entries(paths)) {
421
+ // Handle exact match: "@vibecheck/core" -> ["../../packages/core/dist"]
422
+ if (pattern === spec) {
423
+ for (const target of targets) {
424
+ const resolved = path.resolve(baseDir, target.replace(/\/\*$/, ""));
425
+ const candidates = [
426
+ resolved,
427
+ resolved + ".ts",
428
+ resolved + ".js",
429
+ path.join(resolved, "index.ts"),
430
+ path.join(resolved, "index.js"),
431
+ ];
432
+ for (const c of candidates) if (existsFile(c)) return c;
433
+ }
434
+ }
435
+
436
+ // Handle wildcard pattern: "@/*" -> ["./src/*"]
437
+ if (pattern.endsWith("/*")) {
438
+ const prefix = pattern.slice(0, -2);
439
+ if (spec.startsWith(prefix + "/")) {
440
+ const rest = spec.slice(prefix.length + 1);
441
+ for (const target of targets) {
442
+ const targetBase = target.replace(/\/\*$/, "");
443
+ const resolved = path.resolve(baseDir, targetBase, rest);
444
+ const candidates = [
445
+ resolved,
446
+ resolved + ".ts",
447
+ resolved + ".js",
448
+ path.join(resolved, "index.ts"),
449
+ path.join(resolved, "index.js"),
450
+ ];
451
+ for (const c of candidates) if (existsFile(c)) return c;
452
+ }
453
+ }
454
+ }
455
+ }
456
+
457
+ return null;
458
+ }
459
+
460
+ /**
461
+ * Parse package.json and find the main entrypoint
462
+ */
463
+ function getPackageEntrypoint(pkgJsonPath) {
464
+ try {
465
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
466
+ const pkgDir = path.dirname(pkgJsonPath);
467
+
468
+ // Priority: exports["."] > main > index.js
469
+ // Handle exports field (modern packages)
470
+ if (pkgJson.exports) {
471
+ const exp = pkgJson.exports;
472
+
473
+ // exports: "./lib/index.js" (string shorthand)
474
+ if (typeof exp === "string") {
475
+ const resolved = path.resolve(pkgDir, exp);
476
+ if (existsFile(resolved)) return resolved;
477
+ }
478
+
479
+ // exports: { ".": "./lib/index.js" } or { ".": { "require": "...", "import": "..." } }
480
+ if (typeof exp === "object" && exp["."]) {
481
+ const dotExport = exp["."];
482
+
483
+ if (typeof dotExport === "string") {
484
+ const resolved = path.resolve(pkgDir, dotExport);
485
+ if (existsFile(resolved)) return resolved;
486
+ }
487
+
488
+ // Conditional exports - prefer require for CommonJS, then import, then default
489
+ if (typeof dotExport === "object") {
490
+ const conditions = ["require", "node", "import", "default"];
491
+ for (const cond of conditions) {
492
+ if (dotExport[cond]) {
493
+ const val = dotExport[cond];
494
+ const target = typeof val === "string" ? val : val?.default;
495
+ if (target) {
496
+ const resolved = path.resolve(pkgDir, target);
497
+ if (existsFile(resolved)) return resolved;
498
+ }
499
+ }
500
+ }
501
+ }
502
+ }
503
+ }
504
+
505
+ // Fallback to main field
506
+ if (pkgJson.main) {
507
+ const resolved = path.resolve(pkgDir, pkgJson.main);
508
+ if (existsFile(resolved)) return resolved;
509
+ }
510
+
511
+ // Final fallback: index.js
512
+ const indexJs = path.join(pkgDir, "index.js");
513
+ if (existsFile(indexJs)) return indexJs;
514
+
515
+ return null;
516
+ } catch {
517
+ return null;
518
+ }
519
+ }
520
+
521
+ /**
522
+ * Resolve a package specifier from node_modules
523
+ * Handles: "fastify", "@fastify/cors", "@vibecheck/core"
524
+ */
525
+ function resolveFromNodeModules(spec, projectRoot) {
526
+ if (!projectRoot || !spec) return null;
527
+
528
+ // Don't resolve built-in Node.js modules
529
+ const builtins = new Set([
530
+ "fs", "path", "http", "https", "url", "crypto", "os", "util", "stream",
531
+ "events", "buffer", "querystring", "child_process", "cluster", "dgram",
532
+ "dns", "net", "readline", "tls", "tty", "zlib", "assert", "async_hooks",
533
+ "perf_hooks", "v8", "vm", "worker_threads", "module", "process"
534
+ ]);
535
+
536
+ const pkgName = spec.startsWith("@")
537
+ ? spec.split("/").slice(0, 2).join("/") // @scope/name
538
+ : spec.split("/")[0]; // name
539
+
540
+ if (builtins.has(pkgName)) return null;
541
+
542
+ // Walk up directory tree looking for node_modules
543
+ let searchDir = projectRoot;
544
+ const root = path.parse(searchDir).root;
545
+
546
+ while (searchDir !== root) {
547
+ const nodeModulesDir = path.join(searchDir, "node_modules");
548
+
549
+ if (existsDir(nodeModulesDir)) {
550
+ const pkgDir = path.join(nodeModulesDir, pkgName);
551
+
552
+ if (existsDir(pkgDir)) {
553
+ const pkgJsonPath = path.join(pkgDir, "package.json");
554
+
555
+ if (existsFile(pkgJsonPath)) {
556
+ // Check if spec has a subpath: "@fastify/cors/lib/foo"
557
+ const subpath = spec.slice(pkgName.length);
558
+
559
+ if (subpath && subpath !== "/") {
560
+ // Resolve subpath within the package
561
+ const subpathResolved = path.join(pkgDir, subpath);
562
+ const candidates = [
563
+ subpathResolved,
564
+ subpathResolved + ".js",
565
+ subpathResolved + ".ts",
566
+ path.join(subpathResolved, "index.js"),
567
+ path.join(subpathResolved, "index.ts"),
568
+ ];
569
+ for (const c of candidates) if (existsFile(c)) return c;
570
+ }
571
+
572
+ // Resolve main entrypoint
573
+ const entry = getPackageEntrypoint(pkgJsonPath);
574
+ if (entry) return entry;
575
+ }
576
+ }
577
+ }
578
+
579
+ const parent = path.dirname(searchDir);
580
+ if (parent === searchDir) break;
581
+ searchDir = parent;
582
+ }
583
+
584
+ return null;
585
+ }
586
+
587
+ /**
588
+ * Check if a package is a "non-route" Fastify plugin
589
+ * These plugins add functionality but don't define routes themselves
590
+ */
591
+ function isNonRoutePlugin(spec) {
592
+ const nonRoutePlugins = new Set([
593
+ // @fastify/* scoped plugins
594
+ "@fastify/cors",
595
+ "@fastify/helmet",
596
+ "@fastify/compress",
597
+ "@fastify/cookie",
598
+ "@fastify/secure-session",
599
+ "@fastify/session",
600
+ "@fastify/rate-limit",
601
+ "@fastify/jwt",
602
+ "@fastify/auth",
603
+ "@fastify/bearer-auth",
604
+ "@fastify/basic-auth",
605
+ "@fastify/multipart",
606
+ "@fastify/formbody",
607
+ "@fastify/static",
608
+ "@fastify/view",
609
+ "@fastify/sensible",
610
+ "@fastify/env",
611
+ "@fastify/accepts",
612
+ "@fastify/caching",
613
+ "@fastify/etag",
614
+ "@fastify/circuit-breaker",
615
+ "@fastify/response-validation",
616
+ "@fastify/request-context",
617
+ "@fastify/under-pressure",
618
+ "@fastify/middie",
619
+ "@fastify/express",
620
+ "@fastify/http-proxy",
621
+ "@fastify/reply-from",
622
+ "@fastify/websocket",
623
+ "@fastify/type-provider-json-schema-to-ts",
624
+ "@fastify/type-provider-typebox",
625
+ "@fastify/type-provider-zod",
626
+ "@fastify/mongodb",
627
+ "@fastify/postgres",
628
+ "@fastify/mysql",
629
+ "@fastify/redis",
630
+ "@fastify/leveldb",
631
+ "@fastify/elasticsearch",
632
+ "@fastify/metrics",
633
+ "@fastify/request-id",
634
+ // Legacy fastify-* plugins
635
+ "fastify-plugin",
636
+ "fastify-cors",
637
+ "fastify-helmet",
638
+ "fastify-compress",
639
+ "fastify-cookie",
640
+ "fastify-session",
641
+ "fastify-rate-limit",
642
+ "fastify-jwt",
643
+ "fastify-auth",
644
+ "fastify-sensible",
645
+ "fastify-multipart",
646
+ "fastify-formbody",
647
+ "fastify-static",
648
+ "fastify-websocket",
649
+ ]);
650
+
651
+ const pkgName = spec.startsWith("@")
652
+ ? spec.split("/").slice(0, 2).join("/")
653
+ : spec.split("/")[0];
654
+
655
+ return nonRoutePlugins.has(pkgName);
656
+ }
657
+
658
+ /**
659
+ * Check if this is @fastify/autoload which needs special directory handling
660
+ */
661
+ function isAutoloadPlugin(spec) {
662
+ return spec === "@fastify/autoload" || spec === "fastify-autoload";
663
+ }
664
+
665
+ function resolveRelativeModule(fromFileAbs, spec) {
666
+ if (!spec || (!spec.startsWith("./") && !spec.startsWith("../"))) return null;
667
+ const base = path.resolve(path.dirname(fromFileAbs), spec);
668
+ const candidates = [
669
+ base,
670
+ base + ".ts",
671
+ base + ".js",
672
+ path.join(base, "index.ts"),
673
+ path.join(base, "index.js"),
674
+ ];
675
+ for (const c of candidates) if (existsFile(c)) return c;
676
+ return null;
677
+ }
678
+
679
+ /**
680
+ * Resolve any module specifier (relative, absolute, package, or TS paths)
681
+ * Returns: { resolved: string | null, isNonRoute: boolean, reason: string }
682
+ */
683
+ function resolveModuleSpec(fromFileAbs, spec) {
684
+ if (!spec) return { resolved: null, isNonRoute: false, reason: "empty spec" };
685
+
686
+ // 1. Relative imports
687
+ if (spec.startsWith("./") || spec.startsWith("../")) {
688
+ const resolved = resolveRelativeModule(fromFileAbs, spec);
689
+ return {
690
+ resolved,
691
+ isNonRoute: false,
692
+ reason: resolved ? "relative import" : "relative module not found"
693
+ };
694
+ }
695
+
696
+ // 2. Check if it's a non-route plugin (skip scanning)
697
+ if (isNonRoutePlugin(spec)) {
698
+ return {
699
+ resolved: null,
700
+ isNonRoute: true,
701
+ reason: `non-route plugin: ${spec}`
702
+ };
703
+ }
704
+
705
+ const projectRoot = findProjectRoot(fromFileAbs);
706
+
707
+ // 3. TypeScript path mappings
708
+ if (projectRoot) {
709
+ const tsConfig = loadTsConfigPaths(projectRoot);
710
+ if (tsConfig) {
711
+ const resolved = resolveWithTsConfigPaths(spec, tsConfig);
712
+ if (resolved) {
713
+ return { resolved, isNonRoute: false, reason: "tsconfig paths" };
714
+ }
715
+ }
716
+ }
717
+
718
+ // 4. Node modules resolution
719
+ if (projectRoot) {
720
+ const resolved = resolveFromNodeModules(spec, projectRoot);
721
+ if (resolved) {
722
+ return { resolved, isNonRoute: false, reason: "node_modules" };
723
+ }
724
+ }
725
+
726
+ return {
727
+ resolved: null,
728
+ isNonRoute: false,
729
+ reason: `unresolved package: ${spec}`
730
+ };
731
+ }
732
+
733
+ function extractStringLiteral(node) {
734
+ return t.isStringLiteral(node) ? node.value : null;
735
+ }
736
+
737
+ function extractPrefixFromOpts(node) {
738
+ if (!t.isObjectExpression(node)) return null;
739
+ for (const p of node.properties) {
740
+ if (!t.isObjectProperty(p)) continue;
741
+ const key =
742
+ t.isIdentifier(p.key) ? p.key.name :
743
+ t.isStringLiteral(p.key) ? p.key.value :
744
+ null;
745
+ if (key === "prefix" && t.isStringLiteral(p.value)) return p.value.value;
746
+ }
747
+ return null;
748
+ }
749
+
750
+ function extractRouteObject(objExpr) {
751
+ let url = null;
752
+ let methods = [];
753
+ let hasHandler = false;
754
+
755
+ for (const p of objExpr.properties) {
756
+ if (!t.isObjectProperty(p)) continue;
757
+
758
+ const key =
759
+ t.isIdentifier(p.key) ? p.key.name :
760
+ t.isStringLiteral(p.key) ? p.key.value :
761
+ null;
762
+ if (!key) continue;
763
+
764
+ if (key === "url" && t.isStringLiteral(p.value)) url = p.value.value;
765
+
766
+ if (key === "method") {
767
+ if (t.isStringLiteral(p.value)) methods = [p.value.value];
768
+ if (t.isArrayExpression(p.value)) {
769
+ methods = p.value.elements.filter(e => t.isStringLiteral(e)).map(e => e.value);
770
+ }
771
+ }
772
+
773
+ if (key === "handler") hasHandler = true;
774
+ }
775
+
776
+ return { url, methods, hasHandler };
777
+ }
778
+
779
+ function detectFastifyEntry(repoRoot) {
780
+ const candidates = [
781
+ "src/server.ts","src/server.js",
782
+ "server.ts","server.js",
783
+ "src/index.ts","src/index.js",
784
+ "index.ts","index.js",
785
+ "apps/api/src/server.ts",
786
+ "apps/api/src/index.ts",
787
+ ];
788
+ for (const rel of candidates) {
789
+ const abs = path.join(repoRoot, rel);
790
+ if (existsFile(abs)) return rel;
791
+ }
792
+ return null;
793
+ }
794
+
795
+ function resolveFastifyRoutesFromEntry(repoRoot, entryAbs) {
796
+ const seen = new Set();
797
+ const routes = [];
798
+ const gaps = [];
799
+
800
+ function scanFile(fileAbs, prefix) {
801
+ if (!fileAbs || seen.has(fileAbs)) return;
802
+ seen.add(fileAbs);
803
+
804
+ const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
805
+ const code = safeRead(fileAbs);
806
+ if (!code) return;
807
+
808
+ let ast;
809
+ try { ast = parseFile(code); } catch { return; }
810
+
811
+ // fastify instance identifiers
812
+ const fastifyNames = new Set(["fastify", "app", "server"]);
813
+
814
+ traverse(ast, {
815
+ VariableDeclarator(p) {
816
+ if (!t.isIdentifier(p.node.id)) return;
817
+ const id = p.node.id.name;
818
+ const init = p.node.init;
819
+ if (!init) return;
820
+ if (t.isCallExpression(init) && t.isIdentifier(init.callee)) {
821
+ const cal = init.callee.name;
822
+ if (cal === "Fastify" || cal === "fastify") fastifyNames.add(id);
823
+ }
824
+ },
825
+ });
826
+
827
+ function resolveImportSpecForLocal(localName) {
828
+ let spec = null;
829
+
830
+ traverse(ast, {
831
+ ImportDeclaration(ip) {
832
+ for (const s of ip.node.specifiers) {
833
+ if (
834
+ (t.isImportDefaultSpecifier(s) || t.isImportSpecifier(s)) &&
835
+ s.local.name === localName
836
+ ) {
837
+ spec = ip.node.source.value;
838
+ }
839
+ }
840
+ },
841
+ VariableDeclarator(vp) {
842
+ if (!t.isIdentifier(vp.node.id) || vp.node.id.name !== localName) return;
843
+ const init = vp.node.init;
844
+ if (!t.isCallExpression(init)) return;
845
+ if (!t.isIdentifier(init.callee) || init.callee.name !== "require") return;
846
+ const a0 = init.arguments[0];
847
+ if (t.isStringLiteral(a0)) spec = a0.value;
848
+ },
849
+ });
850
+
851
+ return spec;
852
+ }
853
+
854
+ traverse(ast, {
855
+ CallExpression(p) {
856
+ const callee = p.node.callee;
857
+ if (!t.isMemberExpression(callee)) return;
858
+ if (!t.isIdentifier(callee.object) || !t.isIdentifier(callee.property)) return;
859
+
860
+ const obj = callee.object.name;
861
+ const prop = callee.property.name;
862
+
863
+ if (!fastifyNames.has(obj)) return;
864
+
865
+ // fastify.get('/x', ...)
866
+ if (FASTIFY_METHODS.has(prop)) {
867
+ const routeStr = extractStringLiteral(p.node.arguments[0]);
868
+ if (!routeStr) return;
869
+
870
+ routes.push({
871
+ method: canonicalizeMethod(prop),
872
+ path: joinPrefix(prefix, routeStr),
873
+ handler: fileRel,
874
+ framework: "fastify",
875
+ confidence: "med",
876
+ evidence: evidenceFromLoc({
877
+ fileAbs,
878
+ fileRel,
879
+ loc: p.node.loc,
880
+ reason: `Fastify ${prop.toUpperCase()}("${routeStr}")`,
881
+ }),
882
+ });
883
+ return;
884
+ }
885
+
886
+ // fastify.route({ method, url, handler })
887
+ if (prop === "route") {
888
+ const arg0 = p.node.arguments[0];
889
+ if (!t.isObjectExpression(arg0)) return;
890
+
891
+ const r = extractRouteObject(arg0);
892
+ if (!r.url) return;
893
+
894
+ const fullPath = joinPrefix(prefix, r.url);
895
+ const ms = (r.methods.length ? r.methods : ["*"]).map(canonicalizeMethod);
896
+
897
+ for (const m of ms) {
898
+ routes.push({
899
+ method: m,
900
+ path: fullPath,
901
+ handler: fileRel,
902
+ framework: "fastify",
903
+ confidence: r.hasHandler ? "med" : "low",
904
+ evidence: evidenceFromLoc({
905
+ fileAbs,
906
+ fileRel,
907
+ loc: p.node.loc,
908
+ reason: `Fastify.route({ url: "${r.url}" })`,
909
+ }),
910
+ });
911
+ }
912
+ return;
913
+ }
914
+
915
+ // fastify.register(plugin, { prefix })
916
+ if (prop === "register") {
917
+ const pluginArg = p.node.arguments[0];
918
+ const optsArg = p.node.arguments[1];
919
+ const childPrefixRaw = extractPrefixFromOpts(optsArg);
920
+ const childPrefix = childPrefixRaw ? joinPrefix(prefix, childPrefixRaw) : prefix;
921
+
922
+ // inline plugin: fastify.register((f, opts) => { f.get(...) }, { prefix })
923
+ if (t.isFunctionExpression(pluginArg) || t.isArrowFunctionExpression(pluginArg)) {
924
+ const param0 = pluginArg.params[0];
925
+ const innerName = t.isIdentifier(param0) ? param0.name : "fastify";
926
+
927
+ traverse(
928
+ pluginArg.body,
929
+ {
930
+ CallExpression(pp) {
931
+ const c = pp.node.callee;
932
+ if (!t.isMemberExpression(c)) return;
933
+ if (!t.isIdentifier(c.object) || !t.isIdentifier(c.property)) return;
934
+ if (c.object.name !== innerName) return;
935
+
936
+ const pr = c.property.name;
937
+
938
+ if (FASTIFY_METHODS.has(pr)) {
939
+ const rs = extractStringLiteral(pp.node.arguments[0]);
940
+ if (!rs) return;
941
+
942
+ routes.push({
943
+ method: canonicalizeMethod(pr),
944
+ path: joinPrefix(childPrefix, rs),
945
+ handler: fileRel,
946
+ framework: "fastify",
947
+ confidence: "med",
948
+ evidence: evidenceFromLoc({
949
+ fileAbs,
950
+ fileRel,
951
+ loc: pp.node.loc,
952
+ reason: `Fastify plugin ${pr.toUpperCase()}("${rs}") prefix="${childPrefixRaw || ""}"`,
953
+ }),
954
+ });
955
+ }
956
+
957
+ if (pr === "route") {
958
+ const a0 = pp.node.arguments[0];
959
+ if (!t.isObjectExpression(a0)) return;
960
+ const r = extractRouteObject(a0);
961
+ if (!r.url) return;
962
+
963
+ const fullPath = joinPrefix(childPrefix, r.url);
964
+ const ms = (r.methods.length ? r.methods : ["*"]).map(canonicalizeMethod);
965
+
966
+ for (const m of ms) {
967
+ routes.push({
968
+ method: m,
969
+ path: fullPath,
970
+ handler: fileRel,
971
+ framework: "fastify",
972
+ confidence: r.hasHandler ? "med" : "low",
973
+ evidence: evidenceFromLoc({
974
+ fileAbs,
975
+ fileRel,
976
+ loc: pp.node.loc,
977
+ reason: `Fastify plugin route("${r.url}") prefix="${childPrefixRaw || ""}"`,
978
+ }),
979
+ });
980
+ }
981
+ }
982
+ },
983
+ },
984
+ p.scope,
985
+ p
986
+ );
987
+
988
+ return;
989
+ }
990
+
991
+ // imported plugin identifier: resolve module (relative, package, or TS paths)
992
+ if (t.isIdentifier(pluginArg)) {
993
+ const localName = pluginArg.name;
994
+ const spec = resolveImportSpecForLocal(localName);
995
+
996
+ if (!spec) {
997
+ gaps.push({ kind: "fastify_plugin_unresolved", file: fileRel, name: localName });
998
+ return;
999
+ }
1000
+
1001
+ // Handle @fastify/autoload: scan the specified directory
1002
+ if (isAutoloadPlugin(spec)) {
1003
+ const autoloadDir = extractAutoloadDir(optsArg, fileAbs);
1004
+ if (autoloadDir && existsDir(autoloadDir)) {
1005
+ scanAutoloadDir(autoloadDir, childPrefix);
1006
+ }
1007
+ return;
1008
+ }
1009
+
1010
+ const { resolved, isNonRoute, reason } = resolveModuleSpec(fileAbs, spec);
1011
+
1012
+ // Skip non-route plugins silently (they don't add routes)
1013
+ if (isNonRoute) {
1014
+ return;
1015
+ }
1016
+
1017
+ if (!resolved) {
1018
+ gaps.push({ kind: "fastify_plugin_unresolved", file: fileRel, spec, reason });
1019
+ return;
1020
+ }
1021
+
1022
+ scanFile(resolved, childPrefix);
1023
+ }
1024
+
1025
+ // Direct require/import: fastify.register(require('./routes'))
1026
+ if (t.isCallExpression(pluginArg)) {
1027
+ const callee = pluginArg.callee;
1028
+ if (t.isIdentifier(callee) && callee.name === "require") {
1029
+ const reqArg = pluginArg.arguments[0];
1030
+ if (t.isStringLiteral(reqArg)) {
1031
+ const spec = reqArg.value;
1032
+
1033
+ // Handle @fastify/autoload via require
1034
+ if (isAutoloadPlugin(spec)) {
1035
+ const autoloadDir = extractAutoloadDir(optsArg, fileAbs);
1036
+ if (autoloadDir && existsDir(autoloadDir)) {
1037
+ scanAutoloadDir(autoloadDir, childPrefix);
1038
+ }
1039
+ return;
1040
+ }
1041
+
1042
+ const { resolved, isNonRoute, reason } = resolveModuleSpec(fileAbs, spec);
1043
+
1044
+ if (isNonRoute) return;
1045
+
1046
+ if (!resolved) {
1047
+ gaps.push({ kind: "fastify_plugin_unresolved", file: fileRel, spec, reason });
1048
+ return;
1049
+ }
1050
+
1051
+ scanFile(resolved, childPrefix);
1052
+ }
1053
+ }
1054
+ }
1055
+ }
1056
+ },
1057
+ });
1058
+ }
1059
+
1060
+ /**
1061
+ * Extract the 'dir' option from autoload config
1062
+ * Handles: { dir: path.join(__dirname, 'routes') } or { dir: './routes' }
1063
+ */
1064
+ function extractAutoloadDir(optsNode, fromFileAbs) {
1065
+ if (!t.isObjectExpression(optsNode)) return null;
1066
+
1067
+ for (const prop of optsNode.properties) {
1068
+ if (!t.isObjectProperty(prop)) continue;
1069
+
1070
+ const key = t.isIdentifier(prop.key) ? prop.key.name :
1071
+ t.isStringLiteral(prop.key) ? prop.key.value : null;
1072
+
1073
+ if (key !== "dir") continue;
1074
+
1075
+ // Simple string: { dir: './routes' }
1076
+ if (t.isStringLiteral(prop.value)) {
1077
+ return path.resolve(path.dirname(fromFileAbs), prop.value.value);
1078
+ }
1079
+
1080
+ // path.join(__dirname, 'routes')
1081
+ if (t.isCallExpression(prop.value)) {
1082
+ const callee = prop.value.callee;
1083
+ if (t.isMemberExpression(callee) &&
1084
+ t.isIdentifier(callee.object) && callee.object.name === "path" &&
1085
+ t.isIdentifier(callee.property) && callee.property.name === "join") {
1086
+ const args = prop.value.arguments;
1087
+ // path.join(__dirname, 'subdir')
1088
+ if (args.length >= 2 && t.isIdentifier(args[0]) && args[0].name === "__dirname") {
1089
+ const parts = args.slice(1)
1090
+ .filter(a => t.isStringLiteral(a))
1091
+ .map(a => a.value);
1092
+ if (parts.length > 0) {
1093
+ return path.resolve(path.dirname(fromFileAbs), ...parts);
1094
+ }
1095
+ }
1096
+ }
1097
+ }
1098
+
1099
+ // Template literal or identifier - can't resolve statically
1100
+ return null;
1101
+ }
1102
+
1103
+ return null;
1104
+ }
1105
+
1106
+ /**
1107
+ * Scan a directory for route files (used by @fastify/autoload)
1108
+ */
1109
+ function scanAutoloadDir(dirAbs, prefix) {
1110
+ if (!existsDir(dirAbs)) return;
1111
+
1112
+ let entries;
1113
+ try {
1114
+ entries = fs.readdirSync(dirAbs, { withFileTypes: true });
1115
+ } catch {
1116
+ return;
1117
+ }
1118
+
1119
+ for (const ent of entries) {
1120
+ const fullPath = path.join(dirAbs, ent.name);
1121
+
1122
+ if (ent.isDirectory()) {
1123
+ // Subdirectory: recurse with directory name as prefix segment
1124
+ // @fastify/autoload uses directory names as route prefixes
1125
+ const subPrefix = joinPrefix(prefix, "/" + ent.name);
1126
+ scanAutoloadDir(fullPath, subPrefix);
1127
+ } else if (ent.isFile() && /\.(ts|js)$/.test(ent.name) && !ent.name.endsWith(".d.ts")) {
1128
+ // Skip index files for prefix (they define routes at current prefix level)
1129
+ const isIndex = /^index\.(ts|js)$/.test(ent.name);
1130
+ const filePrefix = isIndex ? prefix : joinPrefix(prefix, "/" + ent.name.replace(/\.(ts|js)$/, ""));
1131
+
1132
+ scanFile(fullPath, filePrefix);
1133
+ }
1134
+ }
1135
+ }
1136
+
1137
+ scanFile(entryAbs, "/");
1138
+ return { routes, gaps };
1139
+ }
1140
+
1141
+ async function resolveFastifyRoutes(repoRoot, entryRel = null) {
1142
+ const entry = entryRel || detectFastifyEntry(repoRoot);
1143
+ if (!entry) return { routes: [], gaps: [{ kind: "fastify_entry_missing", file: null }] };
1144
+
1145
+ const entryAbs = path.isAbsolute(entry) ? entry : path.join(repoRoot, entry);
1146
+ if (!existsFile(entryAbs)) {
1147
+ return { routes: [], gaps: [{ kind: "fastify_entry_missing", file: entry }] };
1148
+ }
1149
+
1150
+ return resolveFastifyRoutesFromEntry(repoRoot, entryAbs);
1151
+ }
1152
+
1153
+ // ============================================================================
1154
+ // ROUTE INDEX
1155
+ // ============================================================================
1156
+
1157
+ class RouteIndex {
1158
+ constructor() {
1159
+ this.routes = [];
1160
+ this.byMethod = new Map();
1161
+ this.byPath = new Map();
1162
+ this.parameterized = [];
1163
+ this.gaps = [];
1164
+ }
1165
+
1166
+ async build(repoRoot, options = {}) {
1167
+ const nextRoutes = await resolveNextRoutes(repoRoot);
1168
+ this.routes.push(...nextRoutes);
1169
+
1170
+ const { routes: fastifyRoutes, gaps } = await resolveFastifyRoutes(repoRoot, options.fastifyEntry || null);
1171
+ this.routes.push(...fastifyRoutes);
1172
+ this.gaps.push(...(gaps || []));
1173
+
1174
+ for (const r of this.routes) {
1175
+ const m = canonicalizeMethod(r.method);
1176
+ const p = canonicalizePath(r.path);
1177
+
1178
+ r.method = m;
1179
+ r.path = p;
1180
+
1181
+ if (!this.byMethod.has(m)) this.byMethod.set(m, []);
1182
+ this.byMethod.get(m).push(r);
1183
+
1184
+ if (!this.byPath.has(p)) this.byPath.set(p, []);
1185
+ this.byPath.get(p).push(r);
1186
+
1187
+ if (isParameterizedPath(p)) this.parameterized.push(r);
1188
+ }
1189
+
1190
+ return this;
1191
+ }
1192
+
1193
+ findRoutes(method, p) {
1194
+ const m = canonicalizeMethod(method);
1195
+ const cp = canonicalizePath(p);
1196
+
1197
+ const matches = [];
1198
+
1199
+ // Exact path match
1200
+ for (const r of this.byPath.get(cp) || []) {
1201
+ if (matchMethod(r.method, m)) matches.push(r);
1202
+ }
1203
+
1204
+ // Wildcard method match on exact path
1205
+ for (const r of this.byMethod.get("*") || []) {
1206
+ if (r.path === cp && !matches.includes(r)) matches.push(r);
1207
+ }
1208
+
1209
+ // Parameterized route match
1210
+ for (const r of this.parameterized) {
1211
+ if (r.path === cp) continue;
1212
+ if (matchPath(r.path, cp) && matchMethod(r.method, m)) {
1213
+ if (!matches.includes(r)) matches.push(r);
1214
+ }
1215
+ }
1216
+
1217
+ return matches;
1218
+ }
1219
+
1220
+ findClosestRoutes(p, limit = 3) {
1221
+ const cp = canonicalizePath(p);
1222
+ const pathParts = cp.split("/").filter(Boolean);
1223
+
1224
+ const scored = this.routes.map((r) => {
1225
+ const routeParts = r.path.split("/").filter(Boolean);
1226
+ let score = 0;
1227
+
1228
+ for (let i = 0; i < Math.min(pathParts.length, routeParts.length); i++) {
1229
+ if (pathParts[i] === routeParts[i]) score += 1;
1230
+ else if (routeParts[i].startsWith(":")) score += 0.8;
1231
+ else if (routeParts[i].startsWith("*")) { score += 0.6; break; }
1232
+ else break;
1233
+ }
1234
+
1235
+ if (pathParts.length === routeParts.length) score += 0.25;
1236
+ if (isParameterizedPath(r.path)) score += 0.05;
1237
+
1238
+ return { route: r, score };
1239
+ });
1240
+
1241
+ return scored
1242
+ .sort((a, b) => b.score - a.score)
1243
+ .slice(0, Math.max(0, limit))
1244
+ .map((s) => s.route);
1245
+ }
1246
+
1247
+ getRouteMap() {
1248
+ return {
1249
+ server: this.routes,
1250
+ clientRefs: [],
1251
+ gaps: this.gaps,
1252
+ generatedAt: new Date().toISOString(),
1253
+ };
1254
+ }
1255
+ }
1256
+
1257
+ // ============================================================================
1258
+ // VALIDATE CLAIM
1259
+ // ============================================================================
1260
+
1261
+ async function validateRouteExists(claim, repoRoot, routeIndex) {
1262
+ const index = routeIndex || new RouteIndex();
1263
+ if (!routeIndex) await index.build(repoRoot);
1264
+
1265
+ const method = claim?.method || "*";
1266
+ const routePath = claim?.path;
1267
+
1268
+ if (!routePath) {
1269
+ return {
1270
+ result: "unknown",
1271
+ confidence: "low",
1272
+ evidence: [],
1273
+ nextSteps: ["Claim missing path"],
1274
+ };
1275
+ }
1276
+
1277
+ const matches = index.findRoutes(method, routePath);
1278
+
1279
+ if (matches.length > 0) {
1280
+ const best = matches[0];
1281
+ return {
1282
+ result: "true",
1283
+ confidence: best.confidence || "med",
1284
+ evidence: best.evidence || [],
1285
+ matchedRoute: best,
1286
+ };
1287
+ }
1288
+
1289
+ const closest = index.findClosestRoutes(routePath, 3);
1290
+ const hasGaps = (index.gaps || []).length > 0;
1291
+
1292
+ if (hasGaps) {
1293
+ return {
1294
+ result: "unknown",
1295
+ confidence: "low",
1296
+ evidence: [],
1297
+ closestRoutes: closest,
1298
+ gaps: index.gaps,
1299
+ nextSteps: ["Some routes may not be detected due to unresolved plugins/imports."],
1300
+ };
1301
+ }
1302
+
1303
+ return {
1304
+ result: "false",
1305
+ confidence: "high",
1306
+ evidence: [],
1307
+ closestRoutes: closest,
1308
+ nextSteps: closest.length
1309
+ ? [`Did you mean: ${closest.map(r => `${r.method} ${r.path}`).join(", ")}?`]
1310
+ : ["No similar routes found"],
1311
+ };
1312
+ }
1313
+
1314
+ module.exports = {
1315
+ canonicalizePath,
1316
+ canonicalizeMethod,
1317
+ resolveNextRoutes,
1318
+ resolveFastifyRoutes,
1319
+ detectFastifyEntry,
1320
+ RouteIndex,
1321
+ validateRouteExists,
1322
+ };