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.
- package/bin/.generated +25 -0
- package/bin/_deprecations.js +463 -0
- package/bin/_router.js +46 -0
- package/bin/cli-hygiene.js +241 -0
- package/bin/dev/run-v2-torture.js +30 -0
- package/bin/registry.js +656 -0
- package/bin/runners/CLI_REFACTOR_SUMMARY.md +229 -0
- package/bin/runners/ENHANCEMENT_GUIDE.md +121 -0
- package/bin/runners/REPORT_AUDIT.md +64 -0
- package/bin/runners/cli-utils.js +1070 -0
- package/bin/runners/context/ai-task-decomposer.js +337 -0
- package/bin/runners/context/analyzer.js +513 -0
- package/bin/runners/context/api-contracts.js +427 -0
- package/bin/runners/context/context-diff.js +342 -0
- package/bin/runners/context/context-pruner.js +291 -0
- package/bin/runners/context/dependency-graph.js +414 -0
- package/bin/runners/context/generators/claude.js +107 -0
- package/bin/runners/context/generators/codex.js +108 -0
- package/bin/runners/context/generators/copilot.js +119 -0
- package/bin/runners/context/generators/cursor-enhanced.js +2525 -0
- package/bin/runners/context/generators/cursor.js +514 -0
- package/bin/runners/context/generators/mcp.js +169 -0
- package/bin/runners/context/generators/windsurf.js +180 -0
- package/bin/runners/context/git-context.js +304 -0
- package/bin/runners/context/index.js +1110 -0
- package/bin/runners/context/insights.js +173 -0
- package/bin/runners/context/mcp-server/generate-rules.js +337 -0
- package/bin/runners/context/mcp-server/index.js +1176 -0
- package/bin/runners/context/mcp-server/package.json +24 -0
- package/bin/runners/context/memory.js +200 -0
- package/bin/runners/context/monorepo.js +215 -0
- package/bin/runners/context/multi-repo-federation.js +404 -0
- package/bin/runners/context/patterns.js +253 -0
- package/bin/runners/context/proof-context.js +1264 -0
- package/bin/runners/context/security-scanner.js +541 -0
- package/bin/runners/context/semantic-search.js +350 -0
- package/bin/runners/context/shared.js +264 -0
- package/bin/runners/context/team-conventions.js +336 -0
- package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -0
- package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
- package/bin/runners/lib/agent-firewall/change-packet/builder.js +488 -0
- package/bin/runners/lib/agent-firewall/change-packet/schema.json +228 -0
- package/bin/runners/lib/agent-firewall/change-packet/store.js +200 -0
- package/bin/runners/lib/agent-firewall/claims/claim-types.js +21 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +303 -0
- package/bin/runners/lib/agent-firewall/claims/patterns.js +24 -0
- package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
- package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
- package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
- package/bin/runners/lib/agent-firewall/enforcement/gateway.js +1059 -0
- package/bin/runners/lib/agent-firewall/enforcement/index.js +98 -0
- package/bin/runners/lib/agent-firewall/enforcement/mode.js +318 -0
- package/bin/runners/lib/agent-firewall/enforcement/orchestrator.js +484 -0
- package/bin/runners/lib/agent-firewall/enforcement/proof-artifact.js +418 -0
- package/bin/runners/lib/agent-firewall/enforcement/schemas/change-event.schema.json +173 -0
- package/bin/runners/lib/agent-firewall/enforcement/schemas/intent.schema.json +181 -0
- package/bin/runners/lib/agent-firewall/enforcement/schemas/verdict.schema.json +222 -0
- package/bin/runners/lib/agent-firewall/enforcement/verdict-v2.js +333 -0
- package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +88 -0
- package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +75 -0
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +127 -0
- package/bin/runners/lib/agent-firewall/evidence/resolver.js +102 -0
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +213 -0
- package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +145 -0
- package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +19 -0
- package/bin/runners/lib/agent-firewall/fs-hook/installer.js +87 -0
- package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +184 -0
- package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +163 -0
- package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +107 -0
- package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +68 -0
- package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +66 -0
- package/bin/runners/lib/agent-firewall/index.js +200 -0
- package/bin/runners/lib/agent-firewall/integration/index.js +20 -0
- package/bin/runners/lib/agent-firewall/integration/ship-gate.js +437 -0
- package/bin/runners/lib/agent-firewall/intent/alignment-engine.js +634 -0
- package/bin/runners/lib/agent-firewall/intent/auto-detect.js +426 -0
- package/bin/runners/lib/agent-firewall/intent/index.js +102 -0
- package/bin/runners/lib/agent-firewall/intent/schema.js +352 -0
- package/bin/runners/lib/agent-firewall/intent/store.js +283 -0
- package/bin/runners/lib/agent-firewall/interception/fs-interceptor.js +502 -0
- package/bin/runners/lib/agent-firewall/interception/index.js +23 -0
- package/bin/runners/lib/agent-firewall/interceptor/base.js +308 -0
- package/bin/runners/lib/agent-firewall/interceptor/cursor.js +35 -0
- package/bin/runners/lib/agent-firewall/interceptor/vscode.js +35 -0
- package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +34 -0
- package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
- package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
- package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
- package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
- package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
- package/bin/runners/lib/agent-firewall/logger.js +141 -0
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +90 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +103 -0
- package/bin/runners/lib/agent-firewall/policy/loader.js +451 -0
- package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +79 -0
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +227 -0
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +191 -0
- package/bin/runners/lib/agent-firewall/policy/rules/scope.js +93 -0
- package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +57 -0
- package/bin/runners/lib/agent-firewall/policy/schema.json +183 -0
- package/bin/runners/lib/agent-firewall/policy/verdict.js +54 -0
- package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
- package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
- package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
- package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
- package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
- package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
- package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
- package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
- package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
- package/bin/runners/lib/agent-firewall/risk/thresholds.js +322 -0
- package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
- package/bin/runners/lib/agent-firewall/session/collector.js +451 -0
- package/bin/runners/lib/agent-firewall/session/index.js +26 -0
- package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
- package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
- package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
- package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
- package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
- package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
- package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
- package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
- package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
- package/bin/runners/lib/agent-firewall/truthpack/index.js +67 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +137 -0
- package/bin/runners/lib/agent-firewall/unblock/planner.js +337 -0
- package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
- package/bin/runners/lib/ai-bridge.js +416 -0
- package/bin/runners/lib/analysis-core.js +309 -0
- package/bin/runners/lib/analyzers.js +2500 -0
- package/bin/runners/lib/api-client.js +269 -0
- package/bin/runners/lib/approve-output.js +235 -0
- package/bin/runners/lib/artifact-envelope.js +540 -0
- package/bin/runners/lib/assets/vibecheck-logo.png +0 -0
- package/bin/runners/lib/audit-bridge.js +391 -0
- package/bin/runners/lib/auth-shared.js +977 -0
- package/bin/runners/lib/auth-truth.js +193 -0
- package/bin/runners/lib/auth.js +215 -0
- package/bin/runners/lib/authority-badge.js +425 -0
- package/bin/runners/lib/backup.js +62 -0
- package/bin/runners/lib/billing.js +107 -0
- package/bin/runners/lib/checkpoint.js +941 -0
- package/bin/runners/lib/claims.js +118 -0
- package/bin/runners/lib/classify-output.js +204 -0
- package/bin/runners/lib/cleanup/engine.js +571 -0
- package/bin/runners/lib/cleanup/index.js +53 -0
- package/bin/runners/lib/cleanup/output.js +375 -0
- package/bin/runners/lib/cleanup/rules.js +1060 -0
- package/bin/runners/lib/cli-output.js +400 -0
- package/bin/runners/lib/cli-ui.js +540 -0
- package/bin/runners/lib/compliance-bridge-new.js +0 -0
- package/bin/runners/lib/compliance-bridge.js +165 -0
- package/bin/runners/lib/contracts/auth-contract.js +202 -0
- package/bin/runners/lib/contracts/env-contract.js +181 -0
- package/bin/runners/lib/contracts/external-contract.js +206 -0
- package/bin/runners/lib/contracts/guard.js +168 -0
- package/bin/runners/lib/contracts/index.js +89 -0
- package/bin/runners/lib/contracts/plan-validator.js +311 -0
- package/bin/runners/lib/contracts/route-contract.js +199 -0
- package/bin/runners/lib/contracts.js +804 -0
- package/bin/runners/lib/default-config.js +127 -0
- package/bin/runners/lib/detect.js +89 -0
- package/bin/runners/lib/detectors-v2.js +622 -0
- package/bin/runners/lib/doctor/autofix.js +254 -0
- package/bin/runners/lib/doctor/diagnosis-receipt.js +454 -0
- package/bin/runners/lib/doctor/failure-signatures.js +526 -0
- package/bin/runners/lib/doctor/fix-script.js +336 -0
- package/bin/runners/lib/doctor/index.js +37 -0
- package/bin/runners/lib/doctor/modules/build-tools.js +453 -0
- package/bin/runners/lib/doctor/modules/dependencies.js +325 -0
- package/bin/runners/lib/doctor/modules/index.js +105 -0
- package/bin/runners/lib/doctor/modules/network.js +250 -0
- package/bin/runners/lib/doctor/modules/os-quirks.js +706 -0
- package/bin/runners/lib/doctor/modules/project.js +312 -0
- package/bin/runners/lib/doctor/modules/repo-integrity.js +485 -0
- package/bin/runners/lib/doctor/modules/runtime.js +224 -0
- package/bin/runners/lib/doctor/modules/security.js +350 -0
- package/bin/runners/lib/doctor/modules/system.js +213 -0
- package/bin/runners/lib/doctor/modules/vibecheck.js +394 -0
- package/bin/runners/lib/doctor/reporter.js +262 -0
- package/bin/runners/lib/doctor/safe-repair.js +384 -0
- package/bin/runners/lib/doctor/service.js +262 -0
- package/bin/runners/lib/doctor/types.js +113 -0
- package/bin/runners/lib/doctor/ui.js +263 -0
- package/bin/runners/lib/doctor-enhanced.js +233 -0
- package/bin/runners/lib/doctor-output.js +226 -0
- package/bin/runners/lib/doctor-v2.js +608 -0
- package/bin/runners/lib/drift.js +425 -0
- package/bin/runners/lib/enforcement.js +72 -0
- package/bin/runners/lib/engine/ast-cache.js +210 -0
- package/bin/runners/lib/engine/auth-extractor.js +211 -0
- package/bin/runners/lib/engine/billing-extractor.js +112 -0
- package/bin/runners/lib/engine/enforcement-extractor.js +100 -0
- package/bin/runners/lib/engine/env-extractor.js +207 -0
- package/bin/runners/lib/engine/express-extractor.js +208 -0
- package/bin/runners/lib/engine/extractors.js +849 -0
- package/bin/runners/lib/engine/index.js +207 -0
- package/bin/runners/lib/engine/repo-index.js +514 -0
- package/bin/runners/lib/engine/types.js +124 -0
- package/bin/runners/lib/engines/accessibility-engine.js +190 -0
- package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
- package/bin/runners/lib/engines/ast-cache.js +99 -0
- package/bin/runners/lib/engines/attack-detector.js +1192 -0
- package/bin/runners/lib/engines/code-quality-engine.js +255 -0
- package/bin/runners/lib/engines/console-logs-engine.js +115 -0
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
- package/bin/runners/lib/engines/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
- package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
- package/bin/runners/lib/engines/file-filter.js +131 -0
- package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
- package/bin/runners/lib/engines/mock-data-engine.js +272 -0
- package/bin/runners/lib/engines/parallel-processor.js +71 -0
- package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
- package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
- package/bin/runners/lib/engines/type-aware-engine.js +152 -0
- package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
- package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
- package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
- package/bin/runners/lib/enterprise-detect.js +603 -0
- package/bin/runners/lib/enterprise-init.js +942 -0
- package/bin/runners/lib/entitlements-v2.js +265 -0
- package/bin/runners/lib/entitlements.generated.js +0 -0
- package/bin/runners/lib/entitlements.js +340 -0
- package/bin/runners/lib/env-resolver.js +417 -0
- package/bin/runners/lib/env-template.js +66 -0
- package/bin/runners/lib/env.js +189 -0
- package/bin/runners/lib/error-handler.js +368 -0
- package/bin/runners/lib/error-messages.js +289 -0
- package/bin/runners/lib/evidence-pack.js +684 -0
- package/bin/runners/lib/exit-codes.js +275 -0
- package/bin/runners/lib/extractors/client-calls.js +990 -0
- package/bin/runners/lib/extractors/fastify-route-dump.js +573 -0
- package/bin/runners/lib/extractors/fastify-routes.js +426 -0
- package/bin/runners/lib/extractors/index.js +363 -0
- package/bin/runners/lib/extractors/next-routes.js +524 -0
- package/bin/runners/lib/extractors/proof-graph.js +431 -0
- package/bin/runners/lib/extractors/route-matcher.js +451 -0
- package/bin/runners/lib/extractors/truthpack-v2.js +377 -0
- package/bin/runners/lib/extractors/ui-bindings.js +547 -0
- package/bin/runners/lib/finding-id.js +69 -0
- package/bin/runners/lib/finding-sorter.js +89 -0
- package/bin/runners/lib/findings-schema.js +281 -0
- package/bin/runners/lib/fingerprint.js +377 -0
- package/bin/runners/lib/firewall-prompt.js +50 -0
- package/bin/runners/lib/fix-output.js +228 -0
- package/bin/runners/lib/global-flags.js +250 -0
- package/bin/runners/lib/graph/graph-builder.js +265 -0
- package/bin/runners/lib/graph/html-renderer.js +413 -0
- package/bin/runners/lib/graph/index.js +32 -0
- package/bin/runners/lib/graph/runtime-collector.js +215 -0
- package/bin/runners/lib/graph/static-extractor.js +518 -0
- package/bin/runners/lib/help-formatter.js +413 -0
- package/bin/runners/lib/html-proof-report.js +913 -0
- package/bin/runners/lib/html-report.js +650 -0
- package/bin/runners/lib/init-wizard.js +601 -0
- package/bin/runners/lib/interactive-menu.js +1496 -0
- package/bin/runners/lib/json-output.js +76 -0
- package/bin/runners/lib/llm.js +75 -0
- package/bin/runners/lib/logger.js +38 -0
- package/bin/runners/lib/meter.js +61 -0
- package/bin/runners/lib/missions/briefing.js +427 -0
- package/bin/runners/lib/missions/checkpoint.js +753 -0
- package/bin/runners/lib/missions/evidence.js +126 -0
- package/bin/runners/lib/missions/hardening.js +851 -0
- package/bin/runners/lib/missions/plan.js +648 -0
- package/bin/runners/lib/missions/safety-gates.js +645 -0
- package/bin/runners/lib/missions/schema.js +478 -0
- package/bin/runners/lib/missions/templates.js +317 -0
- package/bin/runners/lib/next-action.js +560 -0
- package/bin/runners/lib/packs/bundle.js +675 -0
- package/bin/runners/lib/packs/evidence-pack.js +671 -0
- package/bin/runners/lib/packs/pack-factory.js +837 -0
- package/bin/runners/lib/packs/permissions-pack.js +686 -0
- package/bin/runners/lib/packs/proof-graph-pack.js +779 -0
- package/bin/runners/lib/patch.js +40 -0
- package/bin/runners/lib/permissions/auth-model.js +213 -0
- package/bin/runners/lib/permissions/idor-prover.js +205 -0
- package/bin/runners/lib/permissions/index.js +45 -0
- package/bin/runners/lib/permissions/matrix-builder.js +198 -0
- package/bin/runners/lib/pkgjson.js +28 -0
- package/bin/runners/lib/policy.js +295 -0
- package/bin/runners/lib/polish/accessibility.js +62 -0
- package/bin/runners/lib/polish/analyzer.js +93 -0
- package/bin/runners/lib/polish/backend.js +87 -0
- package/bin/runners/lib/polish/configuration.js +83 -0
- package/bin/runners/lib/polish/documentation.js +83 -0
- package/bin/runners/lib/polish/frontend.js +817 -0
- package/bin/runners/lib/polish/index.js +27 -0
- package/bin/runners/lib/polish/infrastructure.js +80 -0
- package/bin/runners/lib/polish/internationalization.js +85 -0
- package/bin/runners/lib/polish/libraries.js +180 -0
- package/bin/runners/lib/polish/observability.js +75 -0
- package/bin/runners/lib/polish/performance.js +64 -0
- package/bin/runners/lib/polish/privacy.js +110 -0
- package/bin/runners/lib/polish/resilience.js +92 -0
- package/bin/runners/lib/polish/security.js +78 -0
- package/bin/runners/lib/polish/seo.js +71 -0
- package/bin/runners/lib/polish/styles.js +62 -0
- package/bin/runners/lib/polish/utils.js +104 -0
- package/bin/runners/lib/preflight.js +142 -0
- package/bin/runners/lib/prerequisites.js +149 -0
- package/bin/runners/lib/prove-output.js +220 -0
- package/bin/runners/lib/reality/correlation-detectors.js +359 -0
- package/bin/runners/lib/reality/index.js +318 -0
- package/bin/runners/lib/reality/request-hashing.js +416 -0
- package/bin/runners/lib/reality/request-mapper.js +453 -0
- package/bin/runners/lib/reality/safety-rails.js +463 -0
- package/bin/runners/lib/reality/semantic-snapshot.js +408 -0
- package/bin/runners/lib/reality/toast-detector.js +393 -0
- package/bin/runners/lib/reality-findings.js +84 -0
- package/bin/runners/lib/reality-output.js +231 -0
- package/bin/runners/lib/receipts.js +179 -0
- package/bin/runners/lib/redact.js +29 -0
- package/bin/runners/lib/replay/capsule-manager.js +154 -0
- package/bin/runners/lib/replay/index.js +263 -0
- package/bin/runners/lib/replay/player.js +348 -0
- package/bin/runners/lib/replay/recorder.js +331 -0
- package/bin/runners/lib/report-engine.js +626 -0
- package/bin/runners/lib/report-html.js +1233 -0
- package/bin/runners/lib/report-output.js +366 -0
- package/bin/runners/lib/report-templates.js +967 -0
- package/bin/runners/lib/report.js +135 -0
- package/bin/runners/lib/route-detection.js +1209 -0
- package/bin/runners/lib/route-truth.js +1322 -0
- package/bin/runners/lib/safelist/index.js +96 -0
- package/bin/runners/lib/safelist/integration.js +334 -0
- package/bin/runners/lib/safelist/matcher.js +696 -0
- package/bin/runners/lib/safelist/schema.js +948 -0
- package/bin/runners/lib/safelist/store.js +438 -0
- package/bin/runners/lib/sandbox/index.js +59 -0
- package/bin/runners/lib/sandbox/proof-chain.js +399 -0
- package/bin/runners/lib/sandbox/sandbox-runner.js +205 -0
- package/bin/runners/lib/sandbox/worktree.js +174 -0
- package/bin/runners/lib/scan-cache.js +330 -0
- package/bin/runners/lib/scan-output-schema.js +344 -0
- package/bin/runners/lib/scan-output.js +631 -0
- package/bin/runners/lib/scan-runner.js +135 -0
- package/bin/runners/lib/schema-validator.js +350 -0
- package/bin/runners/lib/schemas/ajv-validator.js +464 -0
- package/bin/runners/lib/schemas/contracts.schema.json +160 -0
- package/bin/runners/lib/schemas/error-envelope.schema.json +105 -0
- package/bin/runners/lib/schemas/finding-v3.schema.json +151 -0
- package/bin/runners/lib/schemas/finding.schema.json +100 -0
- package/bin/runners/lib/schemas/mission-pack.schema.json +206 -0
- package/bin/runners/lib/schemas/proof-graph.schema.json +176 -0
- package/bin/runners/lib/schemas/reality-report.schema.json +162 -0
- package/bin/runners/lib/schemas/report-artifact.schema.json +120 -0
- package/bin/runners/lib/schemas/run-request.schema.json +108 -0
- package/bin/runners/lib/schemas/share-pack.schema.json +180 -0
- package/bin/runners/lib/schemas/ship-manifest.schema.json +251 -0
- package/bin/runners/lib/schemas/ship-report.schema.json +117 -0
- package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -0
- package/bin/runners/lib/schemas/validator.js +465 -0
- package/bin/runners/lib/schemas/verdict.schema.json +140 -0
- package/bin/runners/lib/score-history.js +282 -0
- package/bin/runners/lib/security-bridge.js +249 -0
- package/bin/runners/lib/server-usage.js +513 -0
- package/bin/runners/lib/share-pack.js +239 -0
- package/bin/runners/lib/ship-gate.js +832 -0
- package/bin/runners/lib/ship-manifest.js +1153 -0
- package/bin/runners/lib/ship-output-enterprise.js +239 -0
- package/bin/runners/lib/ship-output.js +1128 -0
- package/bin/runners/lib/snippets.js +67 -0
- package/bin/runners/lib/status-output.js +340 -0
- package/bin/runners/lib/terminal-ui.js +356 -0
- package/bin/runners/lib/truth.js +1691 -0
- package/bin/runners/lib/ui.js +562 -0
- package/bin/runners/lib/unified-cli-output.js +947 -0
- package/bin/runners/lib/unified-output.js +197 -0
- package/bin/runners/lib/upsell.js +410 -0
- package/bin/runners/lib/usage.js +153 -0
- package/bin/runners/lib/validate-patch.js +156 -0
- package/bin/runners/lib/verdict-engine.js +628 -0
- package/bin/runners/lib/verification.js +345 -0
- package/bin/runners/lib/why-tree.js +650 -0
- package/bin/runners/reality/engine.js +917 -0
- package/bin/runners/reality/flows.js +122 -0
- package/bin/runners/reality/report.js +378 -0
- package/bin/runners/reality/session.js +193 -0
- package/bin/runners/runAIAgent.js +229 -0
- package/bin/runners/runAgent.d.ts +5 -0
- package/bin/runners/runAgent.js +161 -0
- package/bin/runners/runAllowlist.js +418 -0
- package/bin/runners/runApprove.js +320 -0
- package/bin/runners/runAudit.js +692 -0
- package/bin/runners/runAuth.js +731 -0
- package/bin/runners/runCI.js +353 -0
- package/bin/runners/runCheckpoint.js +530 -0
- package/bin/runners/runClassify.js +928 -0
- package/bin/runners/runCleanup.js +343 -0
- package/bin/runners/runContext.d.ts +4 -0
- package/bin/runners/runContext.js +175 -0
- package/bin/runners/runDoctor.js +877 -0
- package/bin/runners/runEvidencePack.js +362 -0
- package/bin/runners/runFirewall.d.ts +5 -0
- package/bin/runners/runFirewall.js +134 -0
- package/bin/runners/runFirewallHook.d.ts +5 -0
- package/bin/runners/runFirewallHook.js +56 -0
- package/bin/runners/runFix.js +1355 -0
- package/bin/runners/runForge.js +451 -0
- package/bin/runners/runGuard.js +262 -0
- package/bin/runners/runInit.js +1927 -0
- package/bin/runners/runIntent.js +906 -0
- package/bin/runners/runKickoff.js +878 -0
- package/bin/runners/runLabs.js +424 -0
- package/bin/runners/runLaunch.js +2000 -0
- package/bin/runners/runLink.js +785 -0
- package/bin/runners/runMcp.js +1875 -0
- package/bin/runners/runPacks.js +2089 -0
- package/bin/runners/runPolish.d.ts +4 -0
- package/bin/runners/runPolish.js +390 -0
- package/bin/runners/runPromptFirewall.js +211 -0
- package/bin/runners/runProve.js +1411 -0
- package/bin/runners/runQuickstart.js +531 -0
- package/bin/runners/runReality.js +2260 -0
- package/bin/runners/runReport.js +726 -0
- package/bin/runners/runRuntime.js +110 -0
- package/bin/runners/runSafelist.js +1190 -0
- package/bin/runners/runScan.js +688 -0
- package/bin/runners/runShield.js +1282 -0
- package/bin/runners/runShip.js +1660 -0
- package/bin/runners/runTruth.d.ts +5 -0
- package/bin/runners/runTruth.js +101 -0
- package/bin/runners/runValidate.js +179 -0
- package/bin/runners/runWatch.js +478 -0
- package/bin/runners/utils.js +360 -0
- package/bin/scan.js +617 -0
- package/bin/vibecheck.js +1617 -0
- package/dist/guardrail/index.d.ts +2405 -0
- package/dist/guardrail/index.js +9747 -0
- package/dist/guardrail/index.js.map +1 -0
- package/dist/scanner/index.d.ts +282 -0
- package/dist/scanner/index.js +3395 -0
- package/dist/scanner/index.js.map +1 -0
- package/package.json +123 -104
- package/README.md +0 -491
- package/dist/index.js +0 -99711
- 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
|
+
};
|