@jokerized/getresearchdone 0.4.1
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/.claude-plugin/plugin.json +103 -0
- package/README.md +211 -0
- package/agents/grd-baseline-assessor.md +684 -0
- package/agents/grd-code-reviewer.md +300 -0
- package/agents/grd-codebase-mapper.md +355 -0
- package/agents/grd-critique-agent.md +119 -0
- package/agents/grd-debugger.md +519 -0
- package/agents/grd-deep-diver.md +737 -0
- package/agents/grd-eval-planner.md +913 -0
- package/agents/grd-eval-reporter.md +717 -0
- package/agents/grd-executor.md +683 -0
- package/agents/grd-feasibility-analyst.md +624 -0
- package/agents/grd-integration-checker.md +367 -0
- package/agents/grd-knowledge-miner.md +81 -0
- package/agents/grd-migrator.md +88 -0
- package/agents/grd-phase-researcher.md +697 -0
- package/agents/grd-plan-checker.md +443 -0
- package/agents/grd-planner.md +1532 -0
- package/agents/grd-product-owner.md +562 -0
- package/agents/grd-project-researcher.md +513 -0
- package/agents/grd-research-synthesizer.md +273 -0
- package/agents/grd-roadmapper.md +798 -0
- package/agents/grd-surveyor.md +566 -0
- package/agents/grd-verifier.md +893 -0
- package/bin/gd.js +4 -0
- package/bin/gd.ts +227 -0
- package/bin/grd-manifest.js +4 -0
- package/bin/grd-manifest.ts +286 -0
- package/bin/grd-mcp-server.js +4 -0
- package/bin/grd-mcp-server.ts +124 -0
- package/bin/grd-tools.js +4 -0
- package/bin/grd-tools.ts +2471 -0
- package/bin/postinstall.js +4 -0
- package/bin/postinstall.ts +80 -0
- package/commands/add-phase.md +123 -0
- package/commands/add-todo.md +87 -0
- package/commands/assess-baseline.md +289 -0
- package/commands/autopilot.md +100 -0
- package/commands/autoplan.md +55 -0
- package/commands/check-todos.md +87 -0
- package/commands/compare-methods.md +262 -0
- package/commands/complete-milestone.md +225 -0
- package/commands/debug.md +372 -0
- package/commands/deep-dive.md +288 -0
- package/commands/discover.md +281 -0
- package/commands/discuss-phase.md +188 -0
- package/commands/discuss.md +55 -0
- package/commands/eval-report.md +310 -0
- package/commands/evolve.md +79 -0
- package/commands/execute-phase.md +1017 -0
- package/commands/feasibility.md +292 -0
- package/commands/help.md +407 -0
- package/commands/init.md +1508 -0
- package/commands/insert-phase.md +113 -0
- package/commands/iterate.md +327 -0
- package/commands/list-phase-assumptions.md +217 -0
- package/commands/long-term-roadmap.md +202 -0
- package/commands/map-codebase.md +111 -0
- package/commands/migrate.md +159 -0
- package/commands/new-milestone.md +169 -0
- package/commands/pause-work.md +83 -0
- package/commands/plan-milestone-gaps.md +373 -0
- package/commands/plan-phase.md +655 -0
- package/commands/principles.md +328 -0
- package/commands/product-plan.md +319 -0
- package/commands/progress.md +481 -0
- package/commands/quick.md +167 -0
- package/commands/reapply-patches.md +154 -0
- package/commands/remove-phase.md +97 -0
- package/commands/requirement.md +96 -0
- package/commands/resume-project.md +113 -0
- package/commands/settings.md +1144 -0
- package/commands/survey.md +242 -0
- package/commands/sync.md +246 -0
- package/commands/tracker-setup.md +322 -0
- package/commands/update.md +202 -0
- package/commands/verify-phase.md +335 -0
- package/commands/verify-work.md +701 -0
- package/commands/wireup.md +29 -0
- package/dist/bin/gd.d.ts +3 -0
- package/dist/bin/gd.d.ts.map +1 -0
- package/dist/bin/gd.js +178 -0
- package/dist/bin/gd.js.map +1 -0
- package/dist/bin/grd-manifest.d.ts +3 -0
- package/dist/bin/grd-manifest.d.ts.map +1 -0
- package/dist/bin/grd-manifest.js +202 -0
- package/dist/bin/grd-manifest.js.map +1 -0
- package/dist/bin/grd-mcp-server.d.ts +3 -0
- package/dist/bin/grd-mcp-server.d.ts.map +1 -0
- package/dist/bin/grd-mcp-server.js +71 -0
- package/dist/bin/grd-mcp-server.js.map +1 -0
- package/dist/bin/grd-tools.d.ts +3 -0
- package/dist/bin/grd-tools.d.ts.map +1 -0
- package/dist/bin/grd-tools.js +1680 -0
- package/dist/bin/grd-tools.js.map +1 -0
- package/dist/bin/postinstall.d.ts +3 -0
- package/dist/bin/postinstall.d.ts.map +1 -0
- package/dist/bin/postinstall.js +61 -0
- package/dist/bin/postinstall.js.map +1 -0
- package/dist/lib/autopilot-milestone.d.ts +2 -0
- package/dist/lib/autopilot-milestone.d.ts.map +1 -0
- package/dist/lib/autopilot-milestone.js +94 -0
- package/dist/lib/autopilot-milestone.js.map +1 -0
- package/dist/lib/autopilot-pipeline.d.ts +2 -0
- package/dist/lib/autopilot-pipeline.d.ts.map +1 -0
- package/dist/lib/autopilot-pipeline.js +830 -0
- package/dist/lib/autopilot-pipeline.js.map +1 -0
- package/dist/lib/autopilot-waves.d.ts +2 -0
- package/dist/lib/autopilot-waves.d.ts.map +1 -0
- package/dist/lib/autopilot-waves.js +266 -0
- package/dist/lib/autopilot-waves.js.map +1 -0
- package/dist/lib/autopilot.d.ts +2 -0
- package/dist/lib/autopilot.d.ts.map +1 -0
- package/dist/lib/autopilot.js +1314 -0
- package/dist/lib/autopilot.js.map +1 -0
- package/dist/lib/autoplan.d.ts +2 -0
- package/dist/lib/autoplan.d.ts.map +1 -0
- package/dist/lib/autoplan.js +198 -0
- package/dist/lib/autoplan.js.map +1 -0
- package/dist/lib/autoresearch.d.ts +2 -0
- package/dist/lib/autoresearch.d.ts.map +1 -0
- package/dist/lib/autoresearch.js +626 -0
- package/dist/lib/autoresearch.js.map +1 -0
- package/dist/lib/backend.d.ts +2 -0
- package/dist/lib/backend.d.ts.map +1 -0
- package/dist/lib/backend.js +1036 -0
- package/dist/lib/backend.js.map +1 -0
- package/dist/lib/benchmark.d.ts +99 -0
- package/dist/lib/benchmark.d.ts.map +1 -0
- package/dist/lib/benchmark.js +278 -0
- package/dist/lib/benchmark.js.map +1 -0
- package/dist/lib/citations.d.ts +2 -0
- package/dist/lib/citations.d.ts.map +1 -0
- package/dist/lib/citations.js +642 -0
- package/dist/lib/citations.js.map +1 -0
- package/dist/lib/cleanup.d.ts +2 -0
- package/dist/lib/cleanup.d.ts.map +1 -0
- package/dist/lib/cleanup.js +1222 -0
- package/dist/lib/cleanup.js.map +1 -0
- package/dist/lib/cli/adapters.d.ts +10 -0
- package/dist/lib/cli/adapters.d.ts.map +1 -0
- package/dist/lib/cli/adapters.js +27 -0
- package/dist/lib/cli/adapters.js.map +1 -0
- package/dist/lib/cli/agent.d.ts +17 -0
- package/dist/lib/cli/agent.d.ts.map +1 -0
- package/dist/lib/cli/agent.js +53 -0
- package/dist/lib/cli/agent.js.map +1 -0
- package/dist/lib/cli/index.d.ts +21 -0
- package/dist/lib/cli/index.d.ts.map +1 -0
- package/dist/lib/cli/index.js +264 -0
- package/dist/lib/cli/index.js.map +1 -0
- package/dist/lib/cli/output.d.ts +20 -0
- package/dist/lib/cli/output.d.ts.map +1 -0
- package/dist/lib/cli/output.js +22 -0
- package/dist/lib/cli/output.js.map +1 -0
- package/dist/lib/cli/scan-dispatch.d.ts +9 -0
- package/dist/lib/cli/scan-dispatch.d.ts.map +1 -0
- package/dist/lib/cli/scan-dispatch.js +107 -0
- package/dist/lib/cli/scan-dispatch.js.map +1 -0
- package/dist/lib/cli/tools.d.ts +16 -0
- package/dist/lib/cli/tools.d.ts.map +1 -0
- package/dist/lib/cli/tools.js +168 -0
- package/dist/lib/cli/tools.js.map +1 -0
- package/dist/lib/commands/_dashboard-parsers.d.ts +2 -0
- package/dist/lib/commands/_dashboard-parsers.d.ts.map +1 -0
- package/dist/lib/commands/_dashboard-parsers.js +192 -0
- package/dist/lib/commands/_dashboard-parsers.js.map +1 -0
- package/dist/lib/commands/analysis.d.ts +2 -0
- package/dist/lib/commands/analysis.d.ts.map +1 -0
- package/dist/lib/commands/analysis.js +1418 -0
- package/dist/lib/commands/analysis.js.map +1 -0
- package/dist/lib/commands/assumptions.d.ts +2 -0
- package/dist/lib/commands/assumptions.d.ts.map +1 -0
- package/dist/lib/commands/assumptions.js +166 -0
- package/dist/lib/commands/assumptions.js.map +1 -0
- package/dist/lib/commands/blame.d.ts +2 -0
- package/dist/lib/commands/blame.d.ts.map +1 -0
- package/dist/lib/commands/blame.js +133 -0
- package/dist/lib/commands/blame.js.map +1 -0
- package/dist/lib/commands/budget.d.ts +2 -0
- package/dist/lib/commands/budget.d.ts.map +1 -0
- package/dist/lib/commands/budget.js +100 -0
- package/dist/lib/commands/budget.js.map +1 -0
- package/dist/lib/commands/check-plans.d.ts +2 -0
- package/dist/lib/commands/check-plans.d.ts.map +1 -0
- package/dist/lib/commands/check-plans.js +190 -0
- package/dist/lib/commands/check-plans.js.map +1 -0
- package/dist/lib/commands/config.d.ts +2 -0
- package/dist/lib/commands/config.d.ts.map +1 -0
- package/dist/lib/commands/config.js +188 -0
- package/dist/lib/commands/config.js.map +1 -0
- package/dist/lib/commands/dashboard.d.ts +2 -0
- package/dist/lib/commands/dashboard.d.ts.map +1 -0
- package/dist/lib/commands/dashboard.js +466 -0
- package/dist/lib/commands/dashboard.js.map +1 -0
- package/dist/lib/commands/estimate.d.ts +2 -0
- package/dist/lib/commands/estimate.d.ts.map +1 -0
- package/dist/lib/commands/estimate.js +148 -0
- package/dist/lib/commands/estimate.js.map +1 -0
- package/dist/lib/commands/eval-diff.d.ts +2 -0
- package/dist/lib/commands/eval-diff.d.ts.map +1 -0
- package/dist/lib/commands/eval-diff.js +213 -0
- package/dist/lib/commands/eval-diff.js.map +1 -0
- package/dist/lib/commands/freshness.d.ts +2 -0
- package/dist/lib/commands/freshness.d.ts.map +1 -0
- package/dist/lib/commands/freshness.js +163 -0
- package/dist/lib/commands/freshness.js.map +1 -0
- package/dist/lib/commands/health.d.ts +2 -0
- package/dist/lib/commands/health.d.ts.map +1 -0
- package/dist/lib/commands/health.js +435 -0
- package/dist/lib/commands/health.js.map +1 -0
- package/dist/lib/commands/index.d.ts +2 -0
- package/dist/lib/commands/index.d.ts.map +1 -0
- package/dist/lib/commands/index.js +128 -0
- package/dist/lib/commands/index.js.map +1 -0
- package/dist/lib/commands/install.d.ts +56 -0
- package/dist/lib/commands/install.d.ts.map +1 -0
- package/dist/lib/commands/install.js +214 -0
- package/dist/lib/commands/install.js.map +1 -0
- package/dist/lib/commands/knowhow-aggregator.d.ts +2 -0
- package/dist/lib/commands/knowhow-aggregator.d.ts.map +1 -0
- package/dist/lib/commands/knowhow-aggregator.js +279 -0
- package/dist/lib/commands/knowhow-aggregator.js.map +1 -0
- package/dist/lib/commands/knowledge-search.d.ts +2 -0
- package/dist/lib/commands/knowledge-search.d.ts.map +1 -0
- package/dist/lib/commands/knowledge-search.js +113 -0
- package/dist/lib/commands/knowledge-search.js.map +1 -0
- package/dist/lib/commands/long-term-roadmap.d.ts +2 -0
- package/dist/lib/commands/long-term-roadmap.d.ts.map +1 -0
- package/dist/lib/commands/long-term-roadmap.js +272 -0
- package/dist/lib/commands/long-term-roadmap.js.map +1 -0
- package/dist/lib/commands/patterns.d.ts +91 -0
- package/dist/lib/commands/patterns.d.ts.map +1 -0
- package/dist/lib/commands/patterns.js +391 -0
- package/dist/lib/commands/patterns.js.map +1 -0
- package/dist/lib/commands/phase-info.d.ts +2 -0
- package/dist/lib/commands/phase-info.d.ts.map +1 -0
- package/dist/lib/commands/phase-info.js +509 -0
- package/dist/lib/commands/phase-info.js.map +1 -0
- package/dist/lib/commands/plan-lint.d.ts +56 -0
- package/dist/lib/commands/plan-lint.d.ts.map +1 -0
- package/dist/lib/commands/plan-lint.js +481 -0
- package/dist/lib/commands/plan-lint.js.map +1 -0
- package/dist/lib/commands/plan-phase.d.ts +53 -0
- package/dist/lib/commands/plan-phase.d.ts.map +1 -0
- package/dist/lib/commands/plan-phase.js +288 -0
- package/dist/lib/commands/plan-phase.js.map +1 -0
- package/dist/lib/commands/progress.d.ts +2 -0
- package/dist/lib/commands/progress.d.ts.map +1 -0
- package/dist/lib/commands/progress.js +266 -0
- package/dist/lib/commands/progress.js.map +1 -0
- package/dist/lib/commands/quality.d.ts +2 -0
- package/dist/lib/commands/quality.d.ts.map +1 -0
- package/dist/lib/commands/quality.js +80 -0
- package/dist/lib/commands/quality.js.map +1 -0
- package/dist/lib/commands/rollback.d.ts +2 -0
- package/dist/lib/commands/rollback.d.ts.map +1 -0
- package/dist/lib/commands/rollback.js +145 -0
- package/dist/lib/commands/rollback.js.map +1 -0
- package/dist/lib/commands/scan.d.ts +25 -0
- package/dist/lib/commands/scan.d.ts.map +1 -0
- package/dist/lib/commands/scan.js +28 -0
- package/dist/lib/commands/scan.js.map +1 -0
- package/dist/lib/commands/search.d.ts +2 -0
- package/dist/lib/commands/search.d.ts.map +1 -0
- package/dist/lib/commands/search.js +212 -0
- package/dist/lib/commands/search.js.map +1 -0
- package/dist/lib/commands/select-candidate.d.ts +128 -0
- package/dist/lib/commands/select-candidate.d.ts.map +1 -0
- package/dist/lib/commands/select-candidate.js +518 -0
- package/dist/lib/commands/select-candidate.js.map +1 -0
- package/dist/lib/commands/singularity.d.ts +2 -0
- package/dist/lib/commands/singularity.d.ts.map +1 -0
- package/dist/lib/commands/singularity.js +185 -0
- package/dist/lib/commands/singularity.js.map +1 -0
- package/dist/lib/commands/slug-timestamp.d.ts +2 -0
- package/dist/lib/commands/slug-timestamp.d.ts.map +1 -0
- package/dist/lib/commands/slug-timestamp.js +54 -0
- package/dist/lib/commands/slug-timestamp.js.map +1 -0
- package/dist/lib/commands/tail.d.ts +2 -0
- package/dist/lib/commands/tail.d.ts.map +1 -0
- package/dist/lib/commands/tail.js +100 -0
- package/dist/lib/commands/tail.js.map +1 -0
- package/dist/lib/commands/todo.d.ts +2 -0
- package/dist/lib/commands/todo.d.ts.map +1 -0
- package/dist/lib/commands/todo.js +200 -0
- package/dist/lib/commands/todo.js.map +1 -0
- package/dist/lib/commands/watch.d.ts +2 -0
- package/dist/lib/commands/watch.d.ts.map +1 -0
- package/dist/lib/commands/watch.js +72 -0
- package/dist/lib/commands/watch.js.map +1 -0
- package/dist/lib/complexity.d.ts +55 -0
- package/dist/lib/complexity.d.ts.map +1 -0
- package/dist/lib/complexity.js +80 -0
- package/dist/lib/complexity.js.map +1 -0
- package/dist/lib/context/agents.d.ts +2 -0
- package/dist/lib/context/agents.d.ts.map +1 -0
- package/dist/lib/context/agents.js +344 -0
- package/dist/lib/context/agents.js.map +1 -0
- package/dist/lib/context/base.d.ts +2 -0
- package/dist/lib/context/base.d.ts.map +1 -0
- package/dist/lib/context/base.js +81 -0
- package/dist/lib/context/base.js.map +1 -0
- package/dist/lib/context/execute.d.ts +2 -0
- package/dist/lib/context/execute.d.ts.map +1 -0
- package/dist/lib/context/execute.js +753 -0
- package/dist/lib/context/execute.js.map +1 -0
- package/dist/lib/context/index.d.ts +2 -0
- package/dist/lib/context/index.d.ts.map +1 -0
- package/dist/lib/context/index.js +88 -0
- package/dist/lib/context/index.js.map +1 -0
- package/dist/lib/context/progress.d.ts +2 -0
- package/dist/lib/context/progress.d.ts.map +1 -0
- package/dist/lib/context/progress.js +178 -0
- package/dist/lib/context/progress.js.map +1 -0
- package/dist/lib/context/project.d.ts +2 -0
- package/dist/lib/context/project.d.ts.map +1 -0
- package/dist/lib/context/project.js +413 -0
- package/dist/lib/context/project.js.map +1 -0
- package/dist/lib/context/research.d.ts +2 -0
- package/dist/lib/context/research.d.ts.map +1 -0
- package/dist/lib/context/research.js +466 -0
- package/dist/lib/context/research.js.map +1 -0
- package/dist/lib/dead-ends.d.ts +28 -0
- package/dist/lib/dead-ends.d.ts.map +1 -0
- package/dist/lib/dead-ends.js +451 -0
- package/dist/lib/dead-ends.js.map +1 -0
- package/dist/lib/deps.d.ts +2 -0
- package/dist/lib/deps.d.ts.map +1 -0
- package/dist/lib/deps.js +630 -0
- package/dist/lib/deps.js.map +1 -0
- package/dist/lib/discussion.d.ts +2 -0
- package/dist/lib/discussion.d.ts.map +1 -0
- package/dist/lib/discussion.js +1041 -0
- package/dist/lib/discussion.js.map +1 -0
- package/dist/lib/drift.d.ts +36 -0
- package/dist/lib/drift.d.ts.map +1 -0
- package/dist/lib/drift.js +481 -0
- package/dist/lib/drift.js.map +1 -0
- package/dist/lib/evolve/_dimensions-features.d.ts +2 -0
- package/dist/lib/evolve/_dimensions-features.d.ts.map +1 -0
- package/dist/lib/evolve/_dimensions-features.js +369 -0
- package/dist/lib/evolve/_dimensions-features.js.map +1 -0
- package/dist/lib/evolve/_dimensions.d.ts +2 -0
- package/dist/lib/evolve/_dimensions.d.ts.map +1 -0
- package/dist/lib/evolve/_dimensions.js +358 -0
- package/dist/lib/evolve/_dimensions.js.map +1 -0
- package/dist/lib/evolve/_product-ideation.d.ts +2 -0
- package/dist/lib/evolve/_product-ideation.d.ts.map +1 -0
- package/dist/lib/evolve/_product-ideation.js +281 -0
- package/dist/lib/evolve/_product-ideation.js.map +1 -0
- package/dist/lib/evolve/_prompts.d.ts +2 -0
- package/dist/lib/evolve/_prompts.d.ts.map +1 -0
- package/dist/lib/evolve/_prompts.js +153 -0
- package/dist/lib/evolve/_prompts.js.map +1 -0
- package/dist/lib/evolve/cli.d.ts +2 -0
- package/dist/lib/evolve/cli.d.ts.map +1 -0
- package/dist/lib/evolve/cli.js +224 -0
- package/dist/lib/evolve/cli.js.map +1 -0
- package/dist/lib/evolve/discovery.d.ts +2 -0
- package/dist/lib/evolve/discovery.d.ts.map +1 -0
- package/dist/lib/evolve/discovery.js +391 -0
- package/dist/lib/evolve/discovery.js.map +1 -0
- package/dist/lib/evolve/index.d.ts +2 -0
- package/dist/lib/evolve/index.d.ts.map +1 -0
- package/dist/lib/evolve/index.js +88 -0
- package/dist/lib/evolve/index.js.map +1 -0
- package/dist/lib/evolve/orchestrator.d.ts +2 -0
- package/dist/lib/evolve/orchestrator.d.ts.map +1 -0
- package/dist/lib/evolve/orchestrator.js +851 -0
- package/dist/lib/evolve/orchestrator.js.map +1 -0
- package/dist/lib/evolve/scoring.d.ts +2 -0
- package/dist/lib/evolve/scoring.d.ts.map +1 -0
- package/dist/lib/evolve/scoring.js +118 -0
- package/dist/lib/evolve/scoring.js.map +1 -0
- package/dist/lib/evolve/state.d.ts +2 -0
- package/dist/lib/evolve/state.d.ts.map +1 -0
- package/dist/lib/evolve/state.js +264 -0
- package/dist/lib/evolve/state.js.map +1 -0
- package/dist/lib/evolve/types.d.ts +249 -0
- package/dist/lib/evolve/types.d.ts.map +1 -0
- package/dist/lib/evolve/types.js +3 -0
- package/dist/lib/evolve/types.js.map +1 -0
- package/dist/lib/frontmatter.d.ts +2 -0
- package/dist/lib/frontmatter.d.ts.map +1 -0
- package/dist/lib/frontmatter.js +513 -0
- package/dist/lib/frontmatter.js.map +1 -0
- package/dist/lib/gates.d.ts +2 -0
- package/dist/lib/gates.d.ts.map +1 -0
- package/dist/lib/gates.js +578 -0
- package/dist/lib/gates.js.map +1 -0
- package/dist/lib/genome.d.ts +10 -0
- package/dist/lib/genome.d.ts.map +1 -0
- package/dist/lib/genome.js +368 -0
- package/dist/lib/genome.js.map +1 -0
- package/dist/lib/got.d.ts +2 -0
- package/dist/lib/got.d.ts.map +1 -0
- package/dist/lib/got.js +280 -0
- package/dist/lib/got.js.map +1 -0
- package/dist/lib/invariants.d.ts +2 -0
- package/dist/lib/invariants.d.ts.map +1 -0
- package/dist/lib/invariants.js +298 -0
- package/dist/lib/invariants.js.map +1 -0
- package/dist/lib/knowledge.d.ts +2 -0
- package/dist/lib/knowledge.d.ts.map +1 -0
- package/dist/lib/knowledge.js +658 -0
- package/dist/lib/knowledge.js.map +1 -0
- package/dist/lib/long-term-roadmap.d.ts +2 -0
- package/dist/lib/long-term-roadmap.d.ts.map +1 -0
- package/dist/lib/long-term-roadmap.js +602 -0
- package/dist/lib/long-term-roadmap.js.map +1 -0
- package/dist/lib/markdown-split.d.ts +2 -0
- package/dist/lib/markdown-split.d.ts.map +1 -0
- package/dist/lib/markdown-split.js +199 -0
- package/dist/lib/markdown-split.js.map +1 -0
- package/dist/lib/mcp-server.d.ts +2 -0
- package/dist/lib/mcp-server.d.ts.map +1 -0
- package/dist/lib/mcp-server.js +2424 -0
- package/dist/lib/mcp-server.js.map +1 -0
- package/dist/lib/metrics.d.ts +16 -0
- package/dist/lib/metrics.d.ts.map +1 -0
- package/dist/lib/metrics.js +48 -0
- package/dist/lib/metrics.js.map +1 -0
- package/dist/lib/overstory.d.ts +2 -0
- package/dist/lib/overstory.d.ts.map +1 -0
- package/dist/lib/overstory.js +211 -0
- package/dist/lib/overstory.js.map +1 -0
- package/dist/lib/parallel.d.ts +2 -0
- package/dist/lib/parallel.d.ts.map +1 -0
- package/dist/lib/parallel.js +349 -0
- package/dist/lib/parallel.js.map +1 -0
- package/dist/lib/paths.d.ts +2 -0
- package/dist/lib/paths.d.ts.map +1 -0
- package/dist/lib/paths.js +254 -0
- package/dist/lib/paths.js.map +1 -0
- package/dist/lib/phase-complete-llm.d.ts +22 -0
- package/dist/lib/phase-complete-llm.d.ts.map +1 -0
- package/dist/lib/phase-complete-llm.js +331 -0
- package/dist/lib/phase-complete-llm.js.map +1 -0
- package/dist/lib/phase-complete.d.ts +46 -0
- package/dist/lib/phase-complete.d.ts.map +1 -0
- package/dist/lib/phase-complete.js +278 -0
- package/dist/lib/phase-complete.js.map +1 -0
- package/dist/lib/phase-io.d.ts +2 -0
- package/dist/lib/phase-io.d.ts.map +1 -0
- package/dist/lib/phase-io.js +126 -0
- package/dist/lib/phase-io.js.map +1 -0
- package/dist/lib/phase.d.ts +2 -0
- package/dist/lib/phase.d.ts.map +1 -0
- package/dist/lib/phase.js +1344 -0
- package/dist/lib/phase.js.map +1 -0
- package/dist/lib/plan-tournament.d.ts +63 -0
- package/dist/lib/plan-tournament.d.ts.map +1 -0
- package/dist/lib/plan-tournament.js +353 -0
- package/dist/lib/plan-tournament.js.map +1 -0
- package/dist/lib/refinement.d.ts +74 -0
- package/dist/lib/refinement.d.ts.map +1 -0
- package/dist/lib/refinement.js +283 -0
- package/dist/lib/refinement.js.map +1 -0
- package/dist/lib/requirements.d.ts +2 -0
- package/dist/lib/requirements.d.ts.map +1 -0
- package/dist/lib/requirements.js +355 -0
- package/dist/lib/requirements.js.map +1 -0
- package/dist/lib/research-bundle.d.ts +2 -0
- package/dist/lib/research-bundle.d.ts.map +1 -0
- package/dist/lib/research-bundle.js +246 -0
- package/dist/lib/research-bundle.js.map +1 -0
- package/dist/lib/roadmap.d.ts +2 -0
- package/dist/lib/roadmap.d.ts.map +1 -0
- package/dist/lib/roadmap.js +541 -0
- package/dist/lib/roadmap.js.map +1 -0
- package/dist/lib/sample.d.ts +16 -0
- package/dist/lib/sample.d.ts.map +1 -0
- package/dist/lib/sample.js +20 -0
- package/dist/lib/sample.js.map +1 -0
- package/dist/lib/scaffold.d.ts +2 -0
- package/dist/lib/scaffold.d.ts.map +1 -0
- package/dist/lib/scaffold.js +355 -0
- package/dist/lib/scaffold.js.map +1 -0
- package/dist/lib/scan/_utils.d.ts +11 -0
- package/dist/lib/scan/_utils.d.ts.map +1 -0
- package/dist/lib/scan/_utils.js +36 -0
- package/dist/lib/scan/_utils.js.map +1 -0
- package/dist/lib/scan/base64.d.ts +15 -0
- package/dist/lib/scan/base64.d.ts.map +1 -0
- package/dist/lib/scan/base64.js +66 -0
- package/dist/lib/scan/base64.js.map +1 -0
- package/dist/lib/scan/ignorefile.d.ts +30 -0
- package/dist/lib/scan/ignorefile.d.ts.map +1 -0
- package/dist/lib/scan/ignorefile.js +101 -0
- package/dist/lib/scan/ignorefile.js.map +1 -0
- package/dist/lib/scan/injection.d.ts +14 -0
- package/dist/lib/scan/injection.d.ts.map +1 -0
- package/dist/lib/scan/injection.js +39 -0
- package/dist/lib/scan/injection.js.map +1 -0
- package/dist/lib/scan/patterns.d.ts +17 -0
- package/dist/lib/scan/patterns.d.ts.map +1 -0
- package/dist/lib/scan/patterns.js +123 -0
- package/dist/lib/scan/patterns.js.map +1 -0
- package/dist/lib/scan/strip-markdown.d.ts +7 -0
- package/dist/lib/scan/strip-markdown.d.ts.map +1 -0
- package/dist/lib/scan/strip-markdown.js +38 -0
- package/dist/lib/scan/strip-markdown.js.map +1 -0
- package/dist/lib/scan/types.d.ts +23 -0
- package/dist/lib/scan/types.d.ts.map +1 -0
- package/dist/lib/scan/types.js +3 -0
- package/dist/lib/scan/types.js.map +1 -0
- package/dist/lib/scheduler-wait.d.ts +2 -0
- package/dist/lib/scheduler-wait.d.ts.map +1 -0
- package/dist/lib/scheduler-wait.js +59 -0
- package/dist/lib/scheduler-wait.js.map +1 -0
- package/dist/lib/scheduler.d.ts +254 -0
- package/dist/lib/scheduler.d.ts.map +1 -0
- package/dist/lib/scheduler.js +1147 -0
- package/dist/lib/scheduler.js.map +1 -0
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.d.ts.map +1 -0
- package/dist/lib/state.js +744 -0
- package/dist/lib/state.js.map +1 -0
- package/dist/lib/think.d.ts +18 -0
- package/dist/lib/think.d.ts.map +1 -0
- package/dist/lib/think.js +317 -0
- package/dist/lib/think.js.map +1 -0
- package/dist/lib/tracker.d.ts +2 -0
- package/dist/lib/tracker.d.ts.map +1 -0
- package/dist/lib/tracker.js +1121 -0
- package/dist/lib/tracker.js.map +1 -0
- package/dist/lib/types.d.ts +1514 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +4 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +1363 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/lib/verify.d.ts +2 -0
- package/dist/lib/verify.d.ts.map +1 -0
- package/dist/lib/verify.js +1153 -0
- package/dist/lib/verify.js.map +1 -0
- package/dist/lib/wireup/autofix.d.ts +2 -0
- package/dist/lib/wireup/autofix.d.ts.map +1 -0
- package/dist/lib/wireup/autofix.js +188 -0
- package/dist/lib/wireup/autofix.js.map +1 -0
- package/dist/lib/wireup/cli.d.ts +2 -0
- package/dist/lib/wireup/cli.d.ts.map +1 -0
- package/dist/lib/wireup/cli.js +194 -0
- package/dist/lib/wireup/cli.js.map +1 -0
- package/dist/lib/wireup/detection.d.ts +47 -0
- package/dist/lib/wireup/detection.d.ts.map +1 -0
- package/dist/lib/wireup/detection.js +410 -0
- package/dist/lib/wireup/detection.js.map +1 -0
- package/dist/lib/wireup/discovery.d.ts +2 -0
- package/dist/lib/wireup/discovery.d.ts.map +1 -0
- package/dist/lib/wireup/discovery.js +934 -0
- package/dist/lib/wireup/discovery.js.map +1 -0
- package/dist/lib/wireup/execution.d.ts +2 -0
- package/dist/lib/wireup/execution.d.ts.map +1 -0
- package/dist/lib/wireup/execution.js +573 -0
- package/dist/lib/wireup/execution.js.map +1 -0
- package/dist/lib/wireup/index.d.ts +2 -0
- package/dist/lib/wireup/index.d.ts.map +1 -0
- package/dist/lib/wireup/index.js +85 -0
- package/dist/lib/wireup/index.js.map +1 -0
- package/dist/lib/wireup/orchestrator.d.ts +2 -0
- package/dist/lib/wireup/orchestrator.d.ts.map +1 -0
- package/dist/lib/wireup/orchestrator.js +366 -0
- package/dist/lib/wireup/orchestrator.js.map +1 -0
- package/dist/lib/wireup/report.d.ts +47 -0
- package/dist/lib/wireup/report.d.ts.map +1 -0
- package/dist/lib/wireup/report.js +201 -0
- package/dist/lib/wireup/report.js.map +1 -0
- package/dist/lib/wireup/scenarios.d.ts +2 -0
- package/dist/lib/wireup/scenarios.d.ts.map +1 -0
- package/dist/lib/wireup/scenarios.js +516 -0
- package/dist/lib/wireup/scenarios.js.map +1 -0
- package/dist/lib/wireup/state.d.ts +2 -0
- package/dist/lib/wireup/state.d.ts.map +1 -0
- package/dist/lib/wireup/state.js +102 -0
- package/dist/lib/wireup/state.js.map +1 -0
- package/dist/lib/wireup/types.d.ts +376 -0
- package/dist/lib/wireup/types.d.ts.map +1 -0
- package/dist/lib/wireup/types.js +3 -0
- package/dist/lib/wireup/types.js.map +1 -0
- package/dist/lib/worktree.d.ts +2 -0
- package/dist/lib/worktree.d.ts.map +1 -0
- package/dist/lib/worktree.js +999 -0
- package/dist/lib/worktree.js.map +1 -0
- package/lib/autopilot-milestone.ts +136 -0
- package/lib/autopilot-pipeline.ts +1179 -0
- package/lib/autopilot-waves.ts +361 -0
- package/lib/autopilot.ts +1874 -0
- package/lib/autoplan.ts +280 -0
- package/lib/autoresearch.js +4 -0
- package/lib/autoresearch.ts +886 -0
- package/lib/backend.ts +1252 -0
- package/lib/benchmark.ts +341 -0
- package/lib/citations.ts +760 -0
- package/lib/cleanup.ts +1588 -0
- package/lib/cli/adapters.ts +41 -0
- package/lib/cli/agent.ts +83 -0
- package/lib/cli/index.ts +273 -0
- package/lib/cli/output.ts +33 -0
- package/lib/cli/scan-dispatch.ts +130 -0
- package/lib/cli/tools.ts +198 -0
- package/lib/commands/_dashboard-parsers.ts +275 -0
- package/lib/commands/analysis.ts +1851 -0
- package/lib/commands/assumptions.ts +232 -0
- package/lib/commands/blame.ts +174 -0
- package/lib/commands/budget.ts +148 -0
- package/lib/commands/check-plans.ts +233 -0
- package/lib/commands/config.ts +287 -0
- package/lib/commands/dashboard.ts +680 -0
- package/lib/commands/estimate.ts +204 -0
- package/lib/commands/eval-diff.ts +252 -0
- package/lib/commands/freshness.ts +213 -0
- package/lib/commands/health.ts +607 -0
- package/lib/commands/index.ts +266 -0
- package/lib/commands/install.ts +307 -0
- package/lib/commands/knowhow-aggregator.ts +345 -0
- package/lib/commands/knowledge-search.ts +153 -0
- package/lib/commands/long-term-roadmap.ts +390 -0
- package/lib/commands/patterns.ts +465 -0
- package/lib/commands/phase-info.ts +698 -0
- package/lib/commands/plan-lint.ts +546 -0
- package/lib/commands/plan-phase.ts +375 -0
- package/lib/commands/progress.ts +319 -0
- package/lib/commands/quality.ts +138 -0
- package/lib/commands/rollback.ts +195 -0
- package/lib/commands/scan.ts +72 -0
- package/lib/commands/search.ts +300 -0
- package/lib/commands/select-candidate.ts +687 -0
- package/lib/commands/singularity.ts +222 -0
- package/lib/commands/slug-timestamp.ts +74 -0
- package/lib/commands/tail.ts +129 -0
- package/lib/commands/todo.ts +273 -0
- package/lib/commands/watch.ts +80 -0
- package/lib/complexity.ts +117 -0
- package/lib/context/agents.ts +505 -0
- package/lib/context/base.ts +123 -0
- package/lib/context/execute.ts +977 -0
- package/lib/context/index.ts +110 -0
- package/lib/context/progress.ts +278 -0
- package/lib/context/project.ts +531 -0
- package/lib/context/research.ts +646 -0
- package/lib/dead-ends.ts +506 -0
- package/lib/deps.ts +773 -0
- package/lib/discussion.ts +1275 -0
- package/lib/drift.ts +519 -0
- package/lib/evolve/_dimensions-features.ts +525 -0
- package/lib/evolve/_dimensions.ts +511 -0
- package/lib/evolve/_product-ideation.ts +405 -0
- package/lib/evolve/_prompts.ts +178 -0
- package/lib/evolve/cli.ts +330 -0
- package/lib/evolve/discovery.ts +571 -0
- package/lib/evolve/index.ts +105 -0
- package/lib/evolve/orchestrator.ts +1139 -0
- package/lib/evolve/scoring.ts +167 -0
- package/lib/evolve/state.ts +330 -0
- package/lib/evolve/types.ts +290 -0
- package/lib/frontmatter.ts +615 -0
- package/lib/gates.ts +695 -0
- package/lib/genome.ts +402 -0
- package/lib/got.js +4 -0
- package/lib/got.ts +361 -0
- package/lib/invariants.ts +378 -0
- package/lib/knowledge.ts +768 -0
- package/lib/long-term-roadmap.ts +806 -0
- package/lib/markdown-split.ts +273 -0
- package/lib/mcp-server.ts +3292 -0
- package/lib/metrics.ts +49 -0
- package/lib/overstory.ts +270 -0
- package/lib/parallel.ts +570 -0
- package/lib/paths.ts +293 -0
- package/lib/phase-complete-llm.ts +376 -0
- package/lib/phase-complete.ts +366 -0
- package/lib/phase-io.ts +101 -0
- package/lib/phase.ts +1981 -0
- package/lib/plan-tournament.ts +426 -0
- package/lib/refinement.ts +349 -0
- package/lib/requirements.ts +469 -0
- package/lib/research-bundle.ts +300 -0
- package/lib/roadmap.ts +775 -0
- package/lib/scaffold.ts +480 -0
- package/lib/scan/_utils.ts +37 -0
- package/lib/scan/base64.ts +90 -0
- package/lib/scan/ignorefile.ts +109 -0
- package/lib/scan/injection.ts +67 -0
- package/lib/scan/patterns.ts +139 -0
- package/lib/scan/strip-markdown.ts +39 -0
- package/lib/scan/types.ts +28 -0
- package/lib/scheduler-wait.ts +58 -0
- package/lib/scheduler.ts +1370 -0
- package/lib/state.ts +1000 -0
- package/lib/think.ts +365 -0
- package/lib/tracker.ts +1591 -0
- package/lib/types.ts +1663 -0
- package/lib/utils.ts +1479 -0
- package/lib/verify.ts +1434 -0
- package/lib/wireup/autofix.ts +241 -0
- package/lib/wireup/cli.ts +278 -0
- package/lib/wireup/detection.ts +542 -0
- package/lib/wireup/discovery.ts +1063 -0
- package/lib/wireup/execution.ts +686 -0
- package/lib/wireup/index.ts +117 -0
- package/lib/wireup/orchestrator.ts +519 -0
- package/lib/wireup/report.ts +286 -0
- package/lib/wireup/scenarios.ts +616 -0
- package/lib/wireup/state.ts +139 -0
- package/lib/wireup/types.ts +436 -0
- package/lib/worktree.ts +1309 -0
- package/package.json +67 -0
|
@@ -0,0 +1,1063 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GRD Wireup -- Discovery engine
|
|
5
|
+
*
|
|
6
|
+
* Pure filesystem-based analysis to identify features that exist in the codebase
|
|
7
|
+
* but lack full integration. Includes both GRD-internal scanners and application-aware
|
|
8
|
+
* scanners that discover routes, exports, models, and components in target projects.
|
|
9
|
+
*
|
|
10
|
+
* IMPORTANT: No child process spawn or exec calls. Uses only fs.readFileSync /
|
|
11
|
+
* fs.readdirSync for all analysis.
|
|
12
|
+
*
|
|
13
|
+
* @dependencies ./types, ../utils, ../paths
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { UnwiredFeature, UnwiredFeatureCategory } from './types';
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const {
|
|
21
|
+
safeReadFile,
|
|
22
|
+
}: {
|
|
23
|
+
safeReadFile: (filePath: string) => string | null;
|
|
24
|
+
} = require('../utils');
|
|
25
|
+
|
|
26
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Recursively collect all files under a directory matching given extensions.
|
|
30
|
+
* Returns absolute paths.
|
|
31
|
+
*/
|
|
32
|
+
function _collectFiles(dir: string, extensions: string[]): string[] {
|
|
33
|
+
const results: string[] = [];
|
|
34
|
+
let entries: ReturnType<typeof fs.readdirSync>;
|
|
35
|
+
try {
|
|
36
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
37
|
+
} catch {
|
|
38
|
+
return results;
|
|
39
|
+
}
|
|
40
|
+
for (const entry of entries as Array<{
|
|
41
|
+
name: string;
|
|
42
|
+
isFile: () => boolean;
|
|
43
|
+
isDirectory: () => boolean;
|
|
44
|
+
}>) {
|
|
45
|
+
const fullPath: string = path.join(dir, entry.name);
|
|
46
|
+
if (entry.isDirectory()) {
|
|
47
|
+
if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;
|
|
48
|
+
results.push(..._collectFiles(fullPath, extensions));
|
|
49
|
+
} else if (entry.isFile()) {
|
|
50
|
+
const ext: string = path.extname(entry.name);
|
|
51
|
+
if (extensions.includes(ext)) {
|
|
52
|
+
results.push(fullPath);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return results;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Extract exported function/value names from a TypeScript/JavaScript source file.
|
|
61
|
+
*
|
|
62
|
+
* Handles CJS and ES module patterns:
|
|
63
|
+
* - module.exports = { name1, name2, ... }
|
|
64
|
+
* - exports.name = ...
|
|
65
|
+
* - export function name / export const name / export class name
|
|
66
|
+
* - export default function name
|
|
67
|
+
*/
|
|
68
|
+
function _extractExports(content: string): string[] {
|
|
69
|
+
const names: string[] = [];
|
|
70
|
+
|
|
71
|
+
// Pattern 1: module.exports = { name1, name2, ... }
|
|
72
|
+
const moduleExportMatch: RegExpMatchArray | null = content.match(
|
|
73
|
+
/module\.exports\s*=\s*\{([^}]+)\}/
|
|
74
|
+
);
|
|
75
|
+
if (moduleExportMatch) {
|
|
76
|
+
const inner: string = moduleExportMatch[1];
|
|
77
|
+
const parts: string[] = inner.split(',');
|
|
78
|
+
for (const part of parts) {
|
|
79
|
+
const trimmed: string = part.trim();
|
|
80
|
+
if (!trimmed) continue;
|
|
81
|
+
const colonIdx: number = trimmed.indexOf(':');
|
|
82
|
+
const name: string = colonIdx >= 0 ? trimmed.slice(0, colonIdx).trim() : trimmed;
|
|
83
|
+
if (/^\w+$/.test(name)) {
|
|
84
|
+
names.push(name);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Pattern 2: exports.name = ...
|
|
90
|
+
const exportsPattern: RegExp = /\bexports\.(\w+)\s*=/g;
|
|
91
|
+
let match: RegExpExecArray | null;
|
|
92
|
+
while ((match = exportsPattern.exec(content)) !== null) {
|
|
93
|
+
if (!names.includes(match[1])) {
|
|
94
|
+
names.push(match[1]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Pattern 3: ES module exports — export [async] function/const/class/let/var name
|
|
99
|
+
const esExportPattern: RegExp = /export\s+(?:async\s+)?(?:function|const|class|let|var)\s+(\w+)/g;
|
|
100
|
+
while ((match = esExportPattern.exec(content)) !== null) {
|
|
101
|
+
if (!names.includes(match[1])) {
|
|
102
|
+
names.push(match[1]);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Pattern 4: export default function name (skip anonymous defaults)
|
|
107
|
+
const defaultExportPattern: RegExp = /export\s+default\s+(?:async\s+)?(?:function|class)\s+(\w+)/g;
|
|
108
|
+
while ((match = defaultExportPattern.exec(content)) !== null) {
|
|
109
|
+
if (match[1] !== 'default' && !names.includes(match[1])) {
|
|
110
|
+
names.push(match[1]);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return names;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ─── Scanner: exported-but-uncalled ─────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Scan lib/*.ts files for exported functions that are never called/imported
|
|
121
|
+
* in lib/, bin/, or commands/.
|
|
122
|
+
*/
|
|
123
|
+
function scanExportedButUncalled(cwd: string): UnwiredFeature[] {
|
|
124
|
+
const libDir: string = path.join(cwd, 'lib');
|
|
125
|
+
const binDir: string = path.join(cwd, 'bin');
|
|
126
|
+
const commandsDir: string = path.join(cwd, 'commands');
|
|
127
|
+
|
|
128
|
+
let libEntries: ReturnType<typeof fs.readdirSync>;
|
|
129
|
+
try {
|
|
130
|
+
libEntries = fs.readdirSync(libDir, { withFileTypes: true });
|
|
131
|
+
} catch {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const libFiles: string[] = (
|
|
136
|
+
libEntries as Array<{ name: string; isFile: () => boolean; isDirectory: () => boolean }>
|
|
137
|
+
)
|
|
138
|
+
.filter(
|
|
139
|
+
(e: { name: string; isFile: () => boolean }) => e.isFile() && e.name.endsWith('.ts')
|
|
140
|
+
)
|
|
141
|
+
.map((e: { name: string }) => path.join(libDir, e.name));
|
|
142
|
+
|
|
143
|
+
const exportMap: Map<string, string> = new Map();
|
|
144
|
+
const fileContents: Map<string, string> = new Map();
|
|
145
|
+
|
|
146
|
+
for (const filePath of libFiles) {
|
|
147
|
+
const content: string | null = safeReadFile(filePath);
|
|
148
|
+
if (!content) continue;
|
|
149
|
+
fileContents.set(filePath, content);
|
|
150
|
+
const relPath: string = path.relative(cwd, filePath);
|
|
151
|
+
const exportedNames: string[] = _extractExports(content);
|
|
152
|
+
for (const name of exportedNames) {
|
|
153
|
+
if (!exportMap.has(name)) {
|
|
154
|
+
exportMap.set(name, relPath);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const searchFiles: string[] = [
|
|
160
|
+
..._collectFiles(libDir, ['.ts', '.js', '.md']),
|
|
161
|
+
..._collectFiles(binDir, ['.ts', '.js', '.md']),
|
|
162
|
+
..._collectFiles(commandsDir, ['.ts', '.js', '.md']),
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
const features: UnwiredFeature[] = [];
|
|
166
|
+
|
|
167
|
+
for (const [funcName, relFilePath] of exportMap) {
|
|
168
|
+
const absFilePath: string = path.join(cwd, relFilePath);
|
|
169
|
+
let referenced: boolean = false;
|
|
170
|
+
|
|
171
|
+
for (const searchFile of searchFiles) {
|
|
172
|
+
if (path.resolve(searchFile) === path.resolve(absFilePath)) continue;
|
|
173
|
+
const content: string | null = safeReadFile(searchFile);
|
|
174
|
+
if (!content) continue;
|
|
175
|
+
if (new RegExp(`\\b${funcName}\\b`).test(content)) {
|
|
176
|
+
referenced = true;
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!referenced) {
|
|
182
|
+
features.push({
|
|
183
|
+
category: 'exported-but-uncalled' as UnwiredFeatureCategory,
|
|
184
|
+
filePath: relFilePath,
|
|
185
|
+
functionName: funcName,
|
|
186
|
+
suggestedAction: 'Add call site in a command, route, or test',
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return features;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ─── Scanner: config-without-surface ────────────────────────────────────────
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Scan .planning/config.json for top-level config keys that are not referenced
|
|
198
|
+
* in commands/*.md or bin/*.ts.
|
|
199
|
+
*/
|
|
200
|
+
function scanConfigWithoutSurface(cwd: string): UnwiredFeature[] {
|
|
201
|
+
const configPath: string = path.join(cwd, '.planning', 'config.json');
|
|
202
|
+
const content: string | null = safeReadFile(configPath);
|
|
203
|
+
if (!content) return [];
|
|
204
|
+
|
|
205
|
+
let configObj: Record<string, unknown>;
|
|
206
|
+
try {
|
|
207
|
+
configObj = JSON.parse(content) as Record<string, unknown>;
|
|
208
|
+
} catch {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const configKeys: string[] = Object.keys(configObj);
|
|
213
|
+
if (configKeys.length === 0) return [];
|
|
214
|
+
|
|
215
|
+
const searchDirs: Array<{ dir: string; exts: string[] }> = [
|
|
216
|
+
{ dir: path.join(cwd, 'commands'), exts: ['.md'] },
|
|
217
|
+
{ dir: path.join(cwd, 'bin'), exts: ['.ts', '.js'] },
|
|
218
|
+
{ dir: path.join(cwd, 'lib'), exts: ['.ts', '.js'] },
|
|
219
|
+
{ dir: path.join(cwd, 'src'), exts: ['.ts', '.js'] },
|
|
220
|
+
];
|
|
221
|
+
const searchFiles: string[] = [];
|
|
222
|
+
|
|
223
|
+
for (const { dir, exts } of searchDirs) {
|
|
224
|
+
try {
|
|
225
|
+
fs.readdirSync(dir);
|
|
226
|
+
searchFiles.push(..._collectFiles(dir, exts));
|
|
227
|
+
} catch {
|
|
228
|
+
// dir doesn't exist
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const combinedContent: string = searchFiles
|
|
233
|
+
.map((f) => safeReadFile(f) || '')
|
|
234
|
+
.join('\n');
|
|
235
|
+
|
|
236
|
+
const features: UnwiredFeature[] = [];
|
|
237
|
+
|
|
238
|
+
for (const key of configKeys) {
|
|
239
|
+
if (key.startsWith('_')) continue;
|
|
240
|
+
if (!new RegExp(`\\b${key}\\b`).test(combinedContent)) {
|
|
241
|
+
features.push({
|
|
242
|
+
category: 'config-without-surface' as UnwiredFeatureCategory,
|
|
243
|
+
filePath: '.planning/config.json',
|
|
244
|
+
functionName: key,
|
|
245
|
+
suggestedAction: 'Expose via CLI flag, command option, or settings UI',
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return features;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ─── Scanner: endpoint-without-integration-test ──────────────────────────────
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Scan lib/mcp-server.ts for registered MCP tool names and check if they
|
|
257
|
+
* are referenced in tests/integration/.
|
|
258
|
+
*/
|
|
259
|
+
function scanEndpointsWithoutTests(cwd: string): UnwiredFeature[] {
|
|
260
|
+
const serverPath: string = path.join(cwd, 'lib', 'mcp-server.ts');
|
|
261
|
+
const content: string | null = safeReadFile(serverPath);
|
|
262
|
+
if (!content) return [];
|
|
263
|
+
|
|
264
|
+
const toolNamePattern: RegExp = /name:\s*['"](\w+)['"]/g;
|
|
265
|
+
const toolNames: string[] = [];
|
|
266
|
+
let match: RegExpExecArray | null;
|
|
267
|
+
while ((match = toolNamePattern.exec(content)) !== null) {
|
|
268
|
+
const name: string = match[1];
|
|
269
|
+
if (name.startsWith('grd_') && !toolNames.includes(name)) {
|
|
270
|
+
toolNames.push(name);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (toolNames.length === 0) return [];
|
|
275
|
+
|
|
276
|
+
const integrationDir: string = path.join(cwd, 'tests', 'integration');
|
|
277
|
+
const integrationFiles: string[] = _collectFiles(integrationDir, ['.ts', '.js']);
|
|
278
|
+
|
|
279
|
+
const combinedIntegration: string = integrationFiles
|
|
280
|
+
.map((f) => safeReadFile(f) || '')
|
|
281
|
+
.join('\n');
|
|
282
|
+
|
|
283
|
+
const features: UnwiredFeature[] = [];
|
|
284
|
+
|
|
285
|
+
for (const toolName of toolNames) {
|
|
286
|
+
if (!combinedIntegration.includes(toolName)) {
|
|
287
|
+
features.push({
|
|
288
|
+
category: 'endpoint-without-integration-test' as UnwiredFeatureCategory,
|
|
289
|
+
filePath: 'lib/mcp-server.ts',
|
|
290
|
+
functionName: toolName,
|
|
291
|
+
suggestedAction: 'Add integration test covering this endpoint',
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return features;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ─── Application-Aware Scanners ──────────────────────────────────────────────
|
|
300
|
+
|
|
301
|
+
/** Directories to skip when scanning for application source code. */
|
|
302
|
+
const SKIP_DIRS: Set<string> = new Set([
|
|
303
|
+
'node_modules', '.git', '.planning', '.claude-plugin', 'dist', 'build', 'out',
|
|
304
|
+
'coverage', '.next', '.nuxt', '.svelte-kit', '__pycache__', '.venv', 'venv',
|
|
305
|
+
'vendor', 'target', '.cache', '.turbo', '.vercel', '.output',
|
|
306
|
+
// GRD-internal directories — skip so we don't double-count with GRD scanners
|
|
307
|
+
'agents', 'commands', 'templates', 'examples', 'docs', 'references',
|
|
308
|
+
]);
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Detect source directories in the project by scanning all top-level directories
|
|
312
|
+
* and filtering out infrastructure/config dirs. This is NOT a whitelist — any
|
|
313
|
+
* directory that contains .ts/.js/.tsx/.jsx files is a candidate.
|
|
314
|
+
*/
|
|
315
|
+
function _detectAppSourceDirs(cwd: string): string[] {
|
|
316
|
+
const found: string[] = [];
|
|
317
|
+
let entries: ReturnType<typeof fs.readdirSync>;
|
|
318
|
+
try {
|
|
319
|
+
entries = fs.readdirSync(cwd, { withFileTypes: true });
|
|
320
|
+
} catch {
|
|
321
|
+
return found;
|
|
322
|
+
}
|
|
323
|
+
for (const entry of entries as Array<{
|
|
324
|
+
name: string;
|
|
325
|
+
isFile: () => boolean;
|
|
326
|
+
isDirectory: () => boolean;
|
|
327
|
+
}>) {
|
|
328
|
+
if (!entry.isDirectory()) continue;
|
|
329
|
+
if (entry.name.startsWith('.')) continue;
|
|
330
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
331
|
+
// Check if this dir contains at least one source file (quick heuristic)
|
|
332
|
+
const dirPath: string = path.join(cwd, entry.name);
|
|
333
|
+
const files: string[] = _collectFiles(dirPath, ['.ts', '.js', '.tsx', '.jsx']);
|
|
334
|
+
if (files.length > 0) {
|
|
335
|
+
found.push(entry.name);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return found;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ─── Route pattern definitions ───────────────────────────────────────────────
|
|
342
|
+
|
|
343
|
+
interface RouteMatch {
|
|
344
|
+
method: string;
|
|
345
|
+
route: string;
|
|
346
|
+
filePath: string;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Extract route registrations from Express/Fastify/Hono-style source files.
|
|
351
|
+
*
|
|
352
|
+
* Detects patterns like:
|
|
353
|
+
* app.get('/path', handler)
|
|
354
|
+
* router.post('/path', handler)
|
|
355
|
+
* export async function GET(req) — Next.js App Router
|
|
356
|
+
* @Get('/path') — NestJS
|
|
357
|
+
*/
|
|
358
|
+
function _extractRoutes(content: string, filePath: string): RouteMatch[] {
|
|
359
|
+
const routes: RouteMatch[] = [];
|
|
360
|
+
|
|
361
|
+
// Express/Fastify/Hono: app.method('/path', ...) or router.method('/path', ...)
|
|
362
|
+
const expressPattern: RegExp =
|
|
363
|
+
/(?:app|router|server|fastify|hono)\s*\.\s*(get|post|put|patch|delete|all|use)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
|
|
364
|
+
let match: RegExpExecArray | null;
|
|
365
|
+
while ((match = expressPattern.exec(content)) !== null) {
|
|
366
|
+
routes.push({ method: match[1].toUpperCase(), route: match[2], filePath });
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Next.js App Router: export async function GET/POST/PUT/DELETE/PATCH
|
|
370
|
+
const nextAppPattern: RegExp =
|
|
371
|
+
/export\s+(?:async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s*\(/gi;
|
|
372
|
+
while ((match = nextAppPattern.exec(content)) !== null) {
|
|
373
|
+
const routePath: string = _filePathToRoute(filePath);
|
|
374
|
+
routes.push({ method: match[1].toUpperCase(), route: routePath, filePath });
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Decorator patterns: @Get('/path'), @Post('/path') etc (NestJS)
|
|
378
|
+
const decoratorPattern: RegExp =
|
|
379
|
+
/@(Get|Post|Put|Patch|Delete)\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/gi;
|
|
380
|
+
while ((match = decoratorPattern.exec(content)) !== null) {
|
|
381
|
+
routes.push({ method: match[1].toUpperCase(), route: match[2], filePath });
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return routes;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Convert a file path like `app/api/users/route.ts` or `pages/api/users.ts`
|
|
389
|
+
* to a route path like `/api/users`.
|
|
390
|
+
*/
|
|
391
|
+
function _filePathToRoute(filePath: string): string {
|
|
392
|
+
let route: string = filePath
|
|
393
|
+
.replace(/\\/g, '/')
|
|
394
|
+
.replace(/^.*?\b(app|pages)\//, '/')
|
|
395
|
+
.replace(/\/route\.(ts|js|tsx|jsx)$/, '')
|
|
396
|
+
.replace(/\.(ts|js|tsx|jsx)$/, '')
|
|
397
|
+
.replace(/\/index$/, '');
|
|
398
|
+
if (!route.startsWith('/')) route = '/' + route;
|
|
399
|
+
return route;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Scan for application routes/endpoints that have no corresponding test file.
|
|
404
|
+
*/
|
|
405
|
+
function scanAppRoutes(cwd: string): UnwiredFeature[] {
|
|
406
|
+
const features: UnwiredFeature[] = [];
|
|
407
|
+
const appDirs: string[] = _detectAppSourceDirs(cwd);
|
|
408
|
+
if (appDirs.length === 0) return features;
|
|
409
|
+
|
|
410
|
+
// Collect all source files from app directories
|
|
411
|
+
const sourceFiles: string[] = [];
|
|
412
|
+
for (const dir of appDirs) {
|
|
413
|
+
sourceFiles.push(..._collectFiles(path.join(cwd, dir), ['.ts', '.js', '.tsx', '.jsx']));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Also check root-level route files
|
|
417
|
+
const rootFiles: string[] = ['routes.ts', 'routes.js', 'server.ts', 'server.js'];
|
|
418
|
+
for (const file of rootFiles) {
|
|
419
|
+
const fullPath: string = path.join(cwd, file);
|
|
420
|
+
try {
|
|
421
|
+
if (fs.statSync(fullPath).isFile()) sourceFiles.push(fullPath);
|
|
422
|
+
} catch {
|
|
423
|
+
// doesn't exist
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Extract routes from all source files
|
|
428
|
+
const allRoutes: RouteMatch[] = [];
|
|
429
|
+
for (const file of sourceFiles) {
|
|
430
|
+
const content: string | null = safeReadFile(file);
|
|
431
|
+
if (!content) continue;
|
|
432
|
+
const relPath: string = path.relative(cwd, file);
|
|
433
|
+
allRoutes.push(..._extractRoutes(content, relPath));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (allRoutes.length === 0) return features;
|
|
437
|
+
|
|
438
|
+
// Collect test files
|
|
439
|
+
const testDirs: string[] = ['tests', 'test', '__tests__', 'spec'];
|
|
440
|
+
const testFiles: string[] = [];
|
|
441
|
+
for (const dir of testDirs) {
|
|
442
|
+
testFiles.push(..._collectFiles(path.join(cwd, dir), ['.ts', '.js', '.tsx', '.jsx']));
|
|
443
|
+
}
|
|
444
|
+
// Also collect *.test.* and *.spec.* from source dirs
|
|
445
|
+
for (const dir of appDirs) {
|
|
446
|
+
const allFiles: string[] = _collectFiles(path.join(cwd, dir), ['.ts', '.js', '.tsx', '.jsx']);
|
|
447
|
+
for (const f of allFiles) {
|
|
448
|
+
const basename: string = path.basename(f);
|
|
449
|
+
if (basename.includes('.test.') || basename.includes('.spec.')) {
|
|
450
|
+
testFiles.push(f);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const combinedTests: string = testFiles
|
|
456
|
+
.map((f) => safeReadFile(f) || '')
|
|
457
|
+
.join('\n');
|
|
458
|
+
|
|
459
|
+
// Check each route for test coverage
|
|
460
|
+
const seen: Set<string> = new Set();
|
|
461
|
+
for (const route of allRoutes) {
|
|
462
|
+
const key: string = `${route.method} ${route.route}`;
|
|
463
|
+
if (seen.has(key)) continue;
|
|
464
|
+
seen.add(key);
|
|
465
|
+
|
|
466
|
+
const routeEscaped: string = route.route.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
467
|
+
const routeReferenced: boolean =
|
|
468
|
+
new RegExp(routeEscaped).test(combinedTests) ||
|
|
469
|
+
combinedTests.includes(route.route);
|
|
470
|
+
|
|
471
|
+
if (!routeReferenced) {
|
|
472
|
+
features.push({
|
|
473
|
+
category: 'app-route-without-test' as UnwiredFeatureCategory,
|
|
474
|
+
filePath: route.filePath,
|
|
475
|
+
functionName: `${route.method} ${route.route}`,
|
|
476
|
+
suggestedAction: `Add integration test for ${route.method} ${route.route}`,
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return features;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Scan src/ (or similar) for exported functions/classes that are never imported
|
|
486
|
+
* anywhere in the project.
|
|
487
|
+
*/
|
|
488
|
+
function scanAppExportedButUncalled(cwd: string): UnwiredFeature[] {
|
|
489
|
+
const appDirs: string[] = _detectAppSourceDirs(cwd);
|
|
490
|
+
if (appDirs.length === 0) return [];
|
|
491
|
+
|
|
492
|
+
const exportMap: Map<string, string> = new Map();
|
|
493
|
+
const allAppFiles: string[] = [];
|
|
494
|
+
|
|
495
|
+
for (const dir of appDirs) {
|
|
496
|
+
allAppFiles.push(..._collectFiles(path.join(cwd, dir), ['.ts', '.js', '.tsx', '.jsx']));
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
for (const filePath of allAppFiles) {
|
|
500
|
+
const content: string | null = safeReadFile(filePath);
|
|
501
|
+
if (!content) continue;
|
|
502
|
+
const relPath: string = path.relative(cwd, filePath);
|
|
503
|
+
|
|
504
|
+
// Skip test files
|
|
505
|
+
const basename: string = path.basename(filePath);
|
|
506
|
+
if (basename.includes('.test.') || basename.includes('.spec.')) continue;
|
|
507
|
+
|
|
508
|
+
const exportedNames: string[] = _extractExports(content);
|
|
509
|
+
|
|
510
|
+
// Also detect ES module exports: export function name / export const name
|
|
511
|
+
const esExportPattern: RegExp = /export\s+(?:async\s+)?(?:function|const|class|let|var)\s+(\w+)/g;
|
|
512
|
+
let match: RegExpExecArray | null;
|
|
513
|
+
while ((match = esExportPattern.exec(content)) !== null) {
|
|
514
|
+
if (!exportedNames.includes(match[1])) {
|
|
515
|
+
exportedNames.push(match[1]);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
for (const name of exportedNames) {
|
|
520
|
+
if (name === 'default') continue;
|
|
521
|
+
if (!exportMap.has(name)) {
|
|
522
|
+
exportMap.set(name, relPath);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Search all project files for references
|
|
528
|
+
const searchFiles: string[] = [...allAppFiles];
|
|
529
|
+
const testDirs: string[] = ['tests', 'test', '__tests__'];
|
|
530
|
+
for (const dir of testDirs) {
|
|
531
|
+
searchFiles.push(..._collectFiles(path.join(cwd, dir), ['.ts', '.js', '.tsx', '.jsx']));
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const features: UnwiredFeature[] = [];
|
|
535
|
+
|
|
536
|
+
for (const [funcName, relFilePath] of exportMap) {
|
|
537
|
+
const absFilePath: string = path.join(cwd, relFilePath);
|
|
538
|
+
let referenced: boolean = false;
|
|
539
|
+
|
|
540
|
+
for (const searchFile of searchFiles) {
|
|
541
|
+
if (path.resolve(searchFile) === path.resolve(absFilePath)) continue;
|
|
542
|
+
const content: string | null = safeReadFile(searchFile);
|
|
543
|
+
if (!content) continue;
|
|
544
|
+
if (new RegExp(`\\b${funcName}\\b`).test(content)) {
|
|
545
|
+
referenced = true;
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (!referenced) {
|
|
551
|
+
features.push({
|
|
552
|
+
category: 'app-exported-but-uncalled' as UnwiredFeatureCategory,
|
|
553
|
+
filePath: relFilePath,
|
|
554
|
+
functionName: funcName,
|
|
555
|
+
suggestedAction: 'Exported but never imported — wire into a route, component, or remove',
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return features;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Scan for ORM model/entity definitions without corresponding CRUD route handlers.
|
|
565
|
+
*
|
|
566
|
+
* Detects Prisma models, TypeORM @Entity classes, and Drizzle table definitions.
|
|
567
|
+
*/
|
|
568
|
+
function scanAppModelsWithoutHandlers(cwd: string): UnwiredFeature[] {
|
|
569
|
+
const features: UnwiredFeature[] = [];
|
|
570
|
+
const modelNames: Array<{ name: string; filePath: string }> = [];
|
|
571
|
+
|
|
572
|
+
// Prisma schema
|
|
573
|
+
const prismaPath: string = path.join(cwd, 'prisma', 'schema.prisma');
|
|
574
|
+
const prismaContent: string | null = safeReadFile(prismaPath);
|
|
575
|
+
if (prismaContent) {
|
|
576
|
+
const modelPattern: RegExp = /model\s+(\w+)\s*\{/g;
|
|
577
|
+
let match: RegExpExecArray | null;
|
|
578
|
+
while ((match = modelPattern.exec(prismaContent)) !== null) {
|
|
579
|
+
modelNames.push({ name: match[1], filePath: 'prisma/schema.prisma' });
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// TypeORM/MikroORM entity decorators and Drizzle table definitions
|
|
584
|
+
const appDirs: string[] = _detectAppSourceDirs(cwd);
|
|
585
|
+
const sourceFiles: string[] = [];
|
|
586
|
+
for (const dir of appDirs) {
|
|
587
|
+
sourceFiles.push(..._collectFiles(path.join(cwd, dir), ['.ts', '.js']));
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
for (const file of sourceFiles) {
|
|
591
|
+
const content: string | null = safeReadFile(file);
|
|
592
|
+
if (!content) continue;
|
|
593
|
+
const relPath: string = path.relative(cwd, file);
|
|
594
|
+
|
|
595
|
+
// @Entity() class ClassName
|
|
596
|
+
const entityPattern: RegExp = /@Entity\s*\([^)]*\)\s*(?:export\s+)?class\s+(\w+)/g;
|
|
597
|
+
let match: RegExpExecArray | null;
|
|
598
|
+
while ((match = entityPattern.exec(content)) !== null) {
|
|
599
|
+
modelNames.push({ name: match[1], filePath: relPath });
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Drizzle: const <tableName> = pgTable/mysqlTable/sqliteTable(...)
|
|
603
|
+
const drizzlePattern: RegExp =
|
|
604
|
+
/(?:export\s+)?const\s+(\w+)\s*=\s*(?:pgTable|mysqlTable|sqliteTable)\s*\(/g;
|
|
605
|
+
while ((match = drizzlePattern.exec(content)) !== null) {
|
|
606
|
+
modelNames.push({ name: match[1], filePath: relPath });
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
if (modelNames.length === 0) return features;
|
|
611
|
+
|
|
612
|
+
// Check if each model is referenced in route/handler files
|
|
613
|
+
const allSourceFiles: string[] = [];
|
|
614
|
+
for (const dir of appDirs) {
|
|
615
|
+
allSourceFiles.push(..._collectFiles(path.join(cwd, dir), ['.ts', '.js', '.tsx', '.jsx']));
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const combinedSource: string = allSourceFiles
|
|
619
|
+
.map((f) => safeReadFile(f) || '')
|
|
620
|
+
.join('\n');
|
|
621
|
+
|
|
622
|
+
for (const model of modelNames) {
|
|
623
|
+
const modelLower: string = model.name.toLowerCase();
|
|
624
|
+
const hasRoute: boolean =
|
|
625
|
+
new RegExp(`['"\`/]${modelLower}`, 'i').test(combinedSource) ||
|
|
626
|
+
new RegExp(`${model.name}\\.(find|create|update|delete|save|remove|insert|select)`, 'i').test(
|
|
627
|
+
combinedSource
|
|
628
|
+
);
|
|
629
|
+
|
|
630
|
+
if (!hasRoute) {
|
|
631
|
+
features.push({
|
|
632
|
+
category: 'app-model-without-handler' as UnwiredFeatureCategory,
|
|
633
|
+
filePath: model.filePath,
|
|
634
|
+
functionName: model.name,
|
|
635
|
+
suggestedAction: `Model "${model.name}" has no CRUD handlers — add routes or remove unused model`,
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return features;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Scan for React/Vue components that are defined but never imported anywhere.
|
|
645
|
+
*/
|
|
646
|
+
function scanAppComponentsWithoutImport(cwd: string): UnwiredFeature[] {
|
|
647
|
+
const features: UnwiredFeature[] = [];
|
|
648
|
+
const appDirs: string[] = _detectAppSourceDirs(cwd);
|
|
649
|
+
if (appDirs.length === 0) return [];
|
|
650
|
+
|
|
651
|
+
const componentFiles: string[] = [];
|
|
652
|
+
for (const dir of appDirs) {
|
|
653
|
+
componentFiles.push(..._collectFiles(path.join(cwd, dir), ['.tsx', '.jsx']));
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const componentMap: Map<string, string> = new Map();
|
|
657
|
+
for (const file of componentFiles) {
|
|
658
|
+
const basename: string = path.basename(file);
|
|
659
|
+
if (basename.includes('.test.') || basename.includes('.spec.')) continue;
|
|
660
|
+
if (basename.startsWith('index.')) continue;
|
|
661
|
+
|
|
662
|
+
const content: string | null = safeReadFile(file);
|
|
663
|
+
if (!content) continue;
|
|
664
|
+
|
|
665
|
+
const relPath: string = path.relative(cwd, file);
|
|
666
|
+
|
|
667
|
+
// Detect: export default function ComponentName / export function ComponentName
|
|
668
|
+
// Only PascalCase names (React components)
|
|
669
|
+
const componentPattern: RegExp =
|
|
670
|
+
/export\s+(?:default\s+)?(?:function|const)\s+([A-Z]\w+)/g;
|
|
671
|
+
let match: RegExpExecArray | null;
|
|
672
|
+
while ((match = componentPattern.exec(content)) !== null) {
|
|
673
|
+
componentMap.set(match[1], relPath);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Search for imports of each component
|
|
678
|
+
const allFiles: string[] = [];
|
|
679
|
+
for (const dir of appDirs) {
|
|
680
|
+
allFiles.push(..._collectFiles(path.join(cwd, dir), ['.ts', '.js', '.tsx', '.jsx']));
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
for (const [componentName, relFilePath] of componentMap) {
|
|
684
|
+
const absFilePath: string = path.join(cwd, relFilePath);
|
|
685
|
+
let referenced: boolean = false;
|
|
686
|
+
|
|
687
|
+
for (const searchFile of allFiles) {
|
|
688
|
+
if (path.resolve(searchFile) === path.resolve(absFilePath)) continue;
|
|
689
|
+
const content: string | null = safeReadFile(searchFile);
|
|
690
|
+
if (!content) continue;
|
|
691
|
+
if (
|
|
692
|
+
new RegExp(`import\\s+.*\\b${componentName}\\b`).test(content) ||
|
|
693
|
+
new RegExp(`<${componentName}[\\s/>]`).test(content)
|
|
694
|
+
) {
|
|
695
|
+
referenced = true;
|
|
696
|
+
break;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
if (!referenced) {
|
|
701
|
+
features.push({
|
|
702
|
+
category: 'app-component-without-import' as UnwiredFeatureCategory,
|
|
703
|
+
filePath: relFilePath,
|
|
704
|
+
functionName: componentName,
|
|
705
|
+
suggestedAction: `Component "${componentName}" is never imported — wire into a page or remove`,
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
return features;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// ─── Generic CLI/Library Scanners ────────────────────────────────────────────
|
|
714
|
+
|
|
715
|
+
/** Check if a directory exists. */
|
|
716
|
+
function _dirExists(dirPath: string): boolean {
|
|
717
|
+
try { return fs.statSync(dirPath).isDirectory(); } catch { return false; }
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Scan lib/*.ts for exported functions that have no corresponding test.
|
|
722
|
+
* A function is considered tested if its name appears in any tests/ file.
|
|
723
|
+
*/
|
|
724
|
+
function scanLibExportedWithoutTest(cwd: string): UnwiredFeature[] {
|
|
725
|
+
const libDir: string = path.join(cwd, 'lib');
|
|
726
|
+
const testDirs: string[] = ['tests', 'test', '__tests__'];
|
|
727
|
+
const testFiles: string[] = [];
|
|
728
|
+
for (const dir of testDirs) {
|
|
729
|
+
testFiles.push(..._collectFiles(path.join(cwd, dir), ['.ts', '.js']));
|
|
730
|
+
}
|
|
731
|
+
if (testFiles.length === 0) return [];
|
|
732
|
+
|
|
733
|
+
const combinedTests: string = testFiles
|
|
734
|
+
.map((f: string) => safeReadFile(f) || '')
|
|
735
|
+
.join('\n');
|
|
736
|
+
|
|
737
|
+
let libEntries: ReturnType<typeof fs.readdirSync>;
|
|
738
|
+
try {
|
|
739
|
+
libEntries = fs.readdirSync(libDir, { withFileTypes: true });
|
|
740
|
+
} catch {
|
|
741
|
+
return [];
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const features: UnwiredFeature[] = [];
|
|
745
|
+
for (const entry of libEntries as Array<{ name: string; isFile: () => boolean }>) {
|
|
746
|
+
if (!entry.isFile() || !entry.name.endsWith('.ts')) continue;
|
|
747
|
+
const filePath: string = path.join(libDir, entry.name);
|
|
748
|
+
const content: string | null = safeReadFile(filePath);
|
|
749
|
+
if (!content) continue;
|
|
750
|
+
|
|
751
|
+
const exports: string[] = _extractExports(content);
|
|
752
|
+
for (const funcName of exports) {
|
|
753
|
+
if (!new RegExp(`\\b${funcName}\\b`).test(combinedTests)) {
|
|
754
|
+
features.push({
|
|
755
|
+
category: 'lib-exported-without-test' as UnwiredFeatureCategory,
|
|
756
|
+
filePath: path.relative(cwd, filePath),
|
|
757
|
+
functionName: funcName,
|
|
758
|
+
suggestedAction: `Add test coverage for ${funcName}`,
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return features;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Scan bin/*.ts entry points for scripts that have no corresponding test.
|
|
768
|
+
* Checks if the bin file's basename (without extension) appears in any test filename.
|
|
769
|
+
*/
|
|
770
|
+
function scanBinEntriesWithoutTest(cwd: string): UnwiredFeature[] {
|
|
771
|
+
const binDir: string = path.join(cwd, 'bin');
|
|
772
|
+
const testDirs: string[] = ['tests', 'test', '__tests__'];
|
|
773
|
+
const testFileNames: Set<string> = new Set();
|
|
774
|
+
for (const dir of testDirs) {
|
|
775
|
+
const files: string[] = _collectFiles(path.join(cwd, dir), ['.ts', '.js']);
|
|
776
|
+
for (const f of files) {
|
|
777
|
+
testFileNames.add(path.basename(f).toLowerCase());
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
// Also collect all test file content for reference checks
|
|
781
|
+
const testFiles: string[] = [];
|
|
782
|
+
for (const dir of testDirs) {
|
|
783
|
+
testFiles.push(..._collectFiles(path.join(cwd, dir), ['.ts', '.js']));
|
|
784
|
+
}
|
|
785
|
+
const combinedTests: string = testFiles
|
|
786
|
+
.map((f: string) => safeReadFile(f) || '')
|
|
787
|
+
.join('\n');
|
|
788
|
+
|
|
789
|
+
let binEntries: ReturnType<typeof fs.readdirSync>;
|
|
790
|
+
try {
|
|
791
|
+
binEntries = fs.readdirSync(binDir, { withFileTypes: true });
|
|
792
|
+
} catch {
|
|
793
|
+
return [];
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const features: UnwiredFeature[] = [];
|
|
797
|
+
for (const entry of binEntries as Array<{ name: string; isFile: () => boolean }>) {
|
|
798
|
+
if (!entry.isFile()) continue;
|
|
799
|
+
if (!entry.name.endsWith('.ts') && !entry.name.endsWith('.js')) continue;
|
|
800
|
+
const baseName: string = entry.name.replace(/\.(ts|js)$/, '');
|
|
801
|
+
// Check if any test file references this bin entry
|
|
802
|
+
const hasTestFile: boolean = testFileNames.has(`${baseName}.test.ts`) ||
|
|
803
|
+
testFileNames.has(`${baseName}.test.js`) ||
|
|
804
|
+
testFileNames.has(`${baseName}.spec.ts`);
|
|
805
|
+
const hasReference: boolean = new RegExp(`\\b${baseName}\\b`).test(combinedTests);
|
|
806
|
+
|
|
807
|
+
if (!hasTestFile && !hasReference) {
|
|
808
|
+
features.push({
|
|
809
|
+
category: 'bin-entry-without-test' as UnwiredFeatureCategory,
|
|
810
|
+
filePath: path.relative(cwd, path.join(binDir, entry.name)),
|
|
811
|
+
functionName: baseName,
|
|
812
|
+
suggestedAction: `Add integration test for bin/${entry.name}`,
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return features;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// ─── Claude Code Plugin Scanners ─────────────────────────────────────────────
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Detect if this is a Claude Code plugin project by checking for
|
|
823
|
+
* commands/ and/or agents/ directories with .md files.
|
|
824
|
+
*/
|
|
825
|
+
function _isPluginProject(cwd: string): boolean {
|
|
826
|
+
const commandsDir: string = path.join(cwd, 'commands');
|
|
827
|
+
const agentsDir: string = path.join(cwd, 'agents');
|
|
828
|
+
return _dirExists(commandsDir) || _dirExists(agentsDir);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Read .md filenames from a directory, returning basenames without extension.
|
|
833
|
+
*/
|
|
834
|
+
function _readMdNames(dir: string): string[] {
|
|
835
|
+
try {
|
|
836
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true }) as Array<{
|
|
837
|
+
name: string;
|
|
838
|
+
isFile: () => boolean;
|
|
839
|
+
}>;
|
|
840
|
+
return entries
|
|
841
|
+
.filter((e: { name: string; isFile: () => boolean }) => e.isFile() && e.name.endsWith('.md'))
|
|
842
|
+
.map((e: { name: string }) => e.name.replace(/\.md$/, ''));
|
|
843
|
+
} catch {
|
|
844
|
+
return [];
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Extract the set of registered command names from the CLI registry file.
|
|
850
|
+
* Reads lib/cli/index.ts (or similar) and parses TOOL_COMMANDS and AGENT_COMMANDS sets.
|
|
851
|
+
*/
|
|
852
|
+
function _extractRegisteredCommands(cwd: string): Set<string> {
|
|
853
|
+
const registered: Set<string> = new Set();
|
|
854
|
+
const candidates: string[] = [
|
|
855
|
+
path.join(cwd, 'lib', 'cli', 'index.ts'),
|
|
856
|
+
path.join(cwd, 'lib', 'cli', 'index.js'),
|
|
857
|
+
path.join(cwd, 'lib', 'cli.ts'),
|
|
858
|
+
path.join(cwd, 'lib', 'cli.js'),
|
|
859
|
+
];
|
|
860
|
+
for (const candidate of candidates) {
|
|
861
|
+
const content: string | null = safeReadFile(candidate);
|
|
862
|
+
if (!content) continue;
|
|
863
|
+
// Match strings inside Set([...]) or array literals
|
|
864
|
+
const stringPattern: RegExp = /['"]([a-z][\w-]*)['"],?/g;
|
|
865
|
+
let match: RegExpExecArray | null;
|
|
866
|
+
while ((match = stringPattern.exec(content)) !== null) {
|
|
867
|
+
registered.add(match[1]);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
return registered;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* Extract agent names referenced by commands via subagent_type="grd:grd-{name}" patterns.
|
|
875
|
+
*/
|
|
876
|
+
function _extractReferencedAgents(cwd: string): Set<string> {
|
|
877
|
+
const referenced: Set<string> = new Set();
|
|
878
|
+
const commandsDir: string = path.join(cwd, 'commands');
|
|
879
|
+
const commandFiles: string[] = _collectFiles(commandsDir, ['.md']);
|
|
880
|
+
for (const file of commandFiles) {
|
|
881
|
+
const content: string | null = safeReadFile(file);
|
|
882
|
+
if (!content) continue;
|
|
883
|
+
// Match subagent_type="grd:grd-{name}" or subagent_type="grd:{name}"
|
|
884
|
+
const agentRefPattern: RegExp = /subagent_type\s*[=:]\s*["']grd:(?:grd-)?([^"']+)["']/g;
|
|
885
|
+
let match: RegExpExecArray | null;
|
|
886
|
+
while ((match = agentRefPattern.exec(content)) !== null) {
|
|
887
|
+
referenced.add(match[1]);
|
|
888
|
+
}
|
|
889
|
+
// Also match Agent tool calls with description referencing agents
|
|
890
|
+
const agentFilePattern: RegExp = /agents\/grd-([^."'\s]+)\.md/g;
|
|
891
|
+
while ((match = agentFilePattern.exec(content)) !== null) {
|
|
892
|
+
referenced.add(match[1]);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
// Also check lib/ files for agent references
|
|
896
|
+
const libFiles: string[] = _collectFiles(path.join(cwd, 'lib'), ['.ts', '.js']);
|
|
897
|
+
for (const file of libFiles) {
|
|
898
|
+
const content: string | null = safeReadFile(file);
|
|
899
|
+
if (!content) continue;
|
|
900
|
+
const agentRefPattern: RegExp = /subagent_type\s*[=:]\s*["']grd:(?:grd-)?([^"']+)["']/g;
|
|
901
|
+
let match: RegExpExecArray | null;
|
|
902
|
+
while ((match = agentRefPattern.exec(content)) !== null) {
|
|
903
|
+
referenced.add(match[1]);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
return referenced;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* Scan for commands/ .md files that are not registered in the CLI command registry.
|
|
911
|
+
*/
|
|
912
|
+
function scanCommandsWithoutRegistration(cwd: string): UnwiredFeature[] {
|
|
913
|
+
const commandNames: string[] = _readMdNames(path.join(cwd, 'commands'));
|
|
914
|
+
if (commandNames.length === 0) return [];
|
|
915
|
+
|
|
916
|
+
const registered: Set<string> = _extractRegisteredCommands(cwd);
|
|
917
|
+
if (registered.size === 0) return []; // No registry found — skip
|
|
918
|
+
|
|
919
|
+
const features: UnwiredFeature[] = [];
|
|
920
|
+
for (const name of commandNames) {
|
|
921
|
+
if (!registered.has(name)) {
|
|
922
|
+
features.push({
|
|
923
|
+
category: 'command-without-registration' as UnwiredFeatureCategory,
|
|
924
|
+
filePath: `commands/${name}.md`,
|
|
925
|
+
functionName: name,
|
|
926
|
+
suggestedAction: `Register "${name}" in TOOL_COMMANDS or AGENT_COMMANDS in the CLI registry`,
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return features;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* Scan for agents/ .md files that are not referenced by any command.
|
|
935
|
+
*/
|
|
936
|
+
function scanAgentsWithoutCommand(cwd: string): UnwiredFeature[] {
|
|
937
|
+
const agentNames: string[] = _readMdNames(path.join(cwd, 'agents'));
|
|
938
|
+
if (agentNames.length === 0) return [];
|
|
939
|
+
|
|
940
|
+
const referenced: Set<string> = _extractReferencedAgents(cwd);
|
|
941
|
+
|
|
942
|
+
const features: UnwiredFeature[] = [];
|
|
943
|
+
for (const name of agentNames) {
|
|
944
|
+
// Normalize: agent files are grd-{name}.md, references may be just {name} or grd-{name}
|
|
945
|
+
const shortName: string = name.replace(/^grd-/, '');
|
|
946
|
+
if (!referenced.has(shortName) && !referenced.has(name)) {
|
|
947
|
+
features.push({
|
|
948
|
+
category: 'agent-without-command' as UnwiredFeatureCategory,
|
|
949
|
+
filePath: `agents/${name}.md`,
|
|
950
|
+
functionName: name,
|
|
951
|
+
suggestedAction: `Agent "${name}" is not spawned by any command — wire it via subagent_type or remove`,
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
return features;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* Scan commands for agent references that point to non-existent agent files.
|
|
960
|
+
*/
|
|
961
|
+
function scanCommandsWithMissingAgents(cwd: string): UnwiredFeature[] {
|
|
962
|
+
const commandsDir: string = path.join(cwd, 'commands');
|
|
963
|
+
const agentsDir: string = path.join(cwd, 'agents');
|
|
964
|
+
const commandFiles: string[] = _collectFiles(commandsDir, ['.md']);
|
|
965
|
+
if (commandFiles.length === 0) return [];
|
|
966
|
+
|
|
967
|
+
const existingAgents: Set<string> = new Set(_readMdNames(agentsDir));
|
|
968
|
+
|
|
969
|
+
const features: UnwiredFeature[] = [];
|
|
970
|
+
const seen: Set<string> = new Set();
|
|
971
|
+
|
|
972
|
+
for (const file of commandFiles) {
|
|
973
|
+
const content: string | null = safeReadFile(file);
|
|
974
|
+
if (!content) continue;
|
|
975
|
+
const relPath: string = path.relative(cwd, file);
|
|
976
|
+
|
|
977
|
+
// Match subagent_type references
|
|
978
|
+
const agentRefPattern: RegExp = /subagent_type\s*[=:]\s*["']grd:(?:grd-)?([^"']+)["']/g;
|
|
979
|
+
let match: RegExpExecArray | null;
|
|
980
|
+
while ((match = agentRefPattern.exec(content)) !== null) {
|
|
981
|
+
const agentName: string = match[1];
|
|
982
|
+
const fullName: string = agentName.startsWith('grd-') ? agentName : `grd-${agentName}`;
|
|
983
|
+
if (!existingAgents.has(fullName) && !seen.has(fullName)) {
|
|
984
|
+
seen.add(fullName);
|
|
985
|
+
features.push({
|
|
986
|
+
category: 'command-without-agent-file' as UnwiredFeatureCategory,
|
|
987
|
+
filePath: relPath,
|
|
988
|
+
functionName: fullName,
|
|
989
|
+
suggestedAction: `Command references agent "${fullName}" but agents/${fullName}.md does not exist`,
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
return features;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// ─── Public Orchestrator ─────────────────────────────────────────────────────
|
|
998
|
+
|
|
999
|
+
/**
|
|
1000
|
+
* Discover all unwired features in the codebase using pure filesystem analysis.
|
|
1001
|
+
*
|
|
1002
|
+
* Runs structural scanners when matching directories exist (lib/, bin/, tests/)
|
|
1003
|
+
* AND application-aware scanners that detect routes, exports, models, and
|
|
1004
|
+
* components in the target project. Works for web apps, CLI tools, and libraries.
|
|
1005
|
+
*
|
|
1006
|
+
* NEVER spawns child processes — pure fs.readFileSync/readdirSync only.
|
|
1007
|
+
*
|
|
1008
|
+
* @param cwd - Absolute path to the project root
|
|
1009
|
+
* @returns Array of UnwiredFeature objects describing integration gaps
|
|
1010
|
+
*/
|
|
1011
|
+
function discoverUnwiredFeatures(cwd: string): UnwiredFeature[] {
|
|
1012
|
+
const allFeatures: UnwiredFeature[] = [];
|
|
1013
|
+
|
|
1014
|
+
// Structural scanners — run when matching directories exist (not gated to GRD)
|
|
1015
|
+
const hasLib: boolean = _dirExists(path.join(cwd, 'lib'));
|
|
1016
|
+
const hasBin: boolean = _dirExists(path.join(cwd, 'bin'));
|
|
1017
|
+
const hasMcpServer: boolean = (() => {
|
|
1018
|
+
try { return fs.statSync(path.join(cwd, 'lib', 'mcp-server.ts')).isFile(); } catch { return false; }
|
|
1019
|
+
})();
|
|
1020
|
+
|
|
1021
|
+
if (hasLib) {
|
|
1022
|
+
allFeatures.push(...scanExportedButUncalled(cwd));
|
|
1023
|
+
allFeatures.push(...scanLibExportedWithoutTest(cwd));
|
|
1024
|
+
}
|
|
1025
|
+
if (hasLib || hasBin) {
|
|
1026
|
+
allFeatures.push(...scanConfigWithoutSurface(cwd));
|
|
1027
|
+
}
|
|
1028
|
+
if (hasMcpServer) {
|
|
1029
|
+
allFeatures.push(...scanEndpointsWithoutTests(cwd));
|
|
1030
|
+
}
|
|
1031
|
+
if (hasBin) {
|
|
1032
|
+
allFeatures.push(...scanBinEntriesWithoutTest(cwd));
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// Plugin-aware scanners — run when commands/ or agents/ exist
|
|
1036
|
+
if (_isPluginProject(cwd)) {
|
|
1037
|
+
allFeatures.push(
|
|
1038
|
+
...scanCommandsWithoutRegistration(cwd),
|
|
1039
|
+
...scanAgentsWithoutCommand(cwd),
|
|
1040
|
+
...scanCommandsWithMissingAgents(cwd)
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// Application-aware scanners — always run
|
|
1045
|
+
allFeatures.push(
|
|
1046
|
+
...scanAppRoutes(cwd),
|
|
1047
|
+
...scanAppExportedButUncalled(cwd),
|
|
1048
|
+
...scanAppModelsWithoutHandlers(cwd),
|
|
1049
|
+
...scanAppComponentsWithoutImport(cwd)
|
|
1050
|
+
);
|
|
1051
|
+
|
|
1052
|
+
allFeatures.sort((a, b) => {
|
|
1053
|
+
const catCmp: number = a.category.localeCompare(b.category);
|
|
1054
|
+
if (catCmp !== 0) return catCmp;
|
|
1055
|
+
return a.filePath.localeCompare(b.filePath);
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
return allFeatures;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// ─── Exports ─────────────────────────────────────────────────────────────────
|
|
1062
|
+
|
|
1063
|
+
module.exports = { discoverUnwiredFeatures };
|