@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
package/lib/worktree.ts
ADDED
|
@@ -0,0 +1,1309 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GRD Worktree -- Git worktree lifecycle management for phase isolation
|
|
5
|
+
*
|
|
6
|
+
* Provides create, remove, list, and stale cleanup for git worktrees
|
|
7
|
+
* used during phase execution. Each phase executes in its own worktree
|
|
8
|
+
* at {project}/.worktrees/grd-worktree-{milestone}-{phase}.
|
|
9
|
+
*
|
|
10
|
+
* Dependencies: utils.ts (one-directional, no circular deps)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
import type { ExecGitResult, GrdConfig, PhaseInfo, MilestoneInfo } from './types';
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const { execFileSync } = require('child_process');
|
|
19
|
+
const {
|
|
20
|
+
execGit,
|
|
21
|
+
output,
|
|
22
|
+
error,
|
|
23
|
+
getMilestoneInfo,
|
|
24
|
+
loadConfig,
|
|
25
|
+
findPhaseInternal,
|
|
26
|
+
generateSlugInternal,
|
|
27
|
+
}: {
|
|
28
|
+
execGit: (cwd: string, args: string[], opts?: { allowBlocked?: boolean }) => ExecGitResult;
|
|
29
|
+
output: (result: unknown, raw: boolean, rawValue?: unknown) => never;
|
|
30
|
+
error: (message: string) => never;
|
|
31
|
+
getMilestoneInfo: (cwd: string) => MilestoneInfo;
|
|
32
|
+
loadConfig: (cwd: string) => GrdConfig;
|
|
33
|
+
findPhaseInternal: (cwd: string, phase: string) => PhaseInfo | null;
|
|
34
|
+
generateSlugInternal: (text: string) => string | null;
|
|
35
|
+
} = require('./utils');
|
|
36
|
+
|
|
37
|
+
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Parsed from git worktree list --porcelain output.
|
|
41
|
+
*/
|
|
42
|
+
interface WorktreeEntry {
|
|
43
|
+
path: string;
|
|
44
|
+
head: string;
|
|
45
|
+
branch: string;
|
|
46
|
+
locked: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Enriched worktree info with GRD metadata.
|
|
51
|
+
*/
|
|
52
|
+
interface GrdWorktreeEntry {
|
|
53
|
+
path: string;
|
|
54
|
+
branch: string;
|
|
55
|
+
phase: string;
|
|
56
|
+
milestone: string;
|
|
57
|
+
locked: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Options for cmdWorktreeCreate.
|
|
62
|
+
*/
|
|
63
|
+
interface WorktreeCreateOptions {
|
|
64
|
+
phase: string;
|
|
65
|
+
milestone?: string;
|
|
66
|
+
slug?: string;
|
|
67
|
+
startPoint?: string;
|
|
68
|
+
force?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Parsed phase and milestone from a GRD worktree directory name.
|
|
73
|
+
*/
|
|
74
|
+
interface WorktreeParsedName {
|
|
75
|
+
phase: string;
|
|
76
|
+
milestone: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Options for cmdWorktreeRemove.
|
|
81
|
+
*/
|
|
82
|
+
interface WorktreeRemoveOptions {
|
|
83
|
+
phase?: string;
|
|
84
|
+
milestone?: string;
|
|
85
|
+
path?: string;
|
|
86
|
+
force?: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Options for cmdWorktreeMerge.
|
|
91
|
+
*/
|
|
92
|
+
interface MergeOptions {
|
|
93
|
+
phase: string;
|
|
94
|
+
milestone?: string;
|
|
95
|
+
slug?: string;
|
|
96
|
+
branch?: string;
|
|
97
|
+
base?: string;
|
|
98
|
+
deleteBranch?: boolean;
|
|
99
|
+
strategy?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Result from createEvolveWorktree.
|
|
104
|
+
*/
|
|
105
|
+
interface EvolveWorktreeResult {
|
|
106
|
+
path: string;
|
|
107
|
+
branch: string;
|
|
108
|
+
baseBranch: string;
|
|
109
|
+
created: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface EvolveWorktreeError {
|
|
113
|
+
error: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Result from removeEvolveWorktree.
|
|
118
|
+
*/
|
|
119
|
+
interface RemoveWorktreeResult {
|
|
120
|
+
removed: boolean;
|
|
121
|
+
already_gone?: boolean;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface RemoveWorktreeError {
|
|
125
|
+
error: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Options for pushAndCreatePR programmatic helper.
|
|
130
|
+
*/
|
|
131
|
+
interface PushPROptions {
|
|
132
|
+
title?: string;
|
|
133
|
+
body?: string;
|
|
134
|
+
base?: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Result from programmatic pushAndCreatePR.
|
|
139
|
+
*/
|
|
140
|
+
interface PushPRSuccessResult {
|
|
141
|
+
pr_url: string;
|
|
142
|
+
branch: string;
|
|
143
|
+
base: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
interface PushPRErrorResult {
|
|
147
|
+
error: string;
|
|
148
|
+
push_succeeded?: boolean;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ─── Internal Helpers ─────────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Compute the worktree directory path for a given milestone and phase.
|
|
155
|
+
* Uses project-local .worktrees/ directory to avoid OS temp cleanup issues.
|
|
156
|
+
* Resolves cwd via realpathSync to handle macOS symlinks.
|
|
157
|
+
*/
|
|
158
|
+
function worktreePath(cwd: string, milestone: string, phase: string): string {
|
|
159
|
+
const resolvedCwd: string = fs.realpathSync(cwd);
|
|
160
|
+
return path.join(resolvedCwd, '.worktrees', `grd-worktree-${milestone}-${phase}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Ensure .worktrees/ directory exists and is listed in .gitignore.
|
|
165
|
+
*/
|
|
166
|
+
function ensureWorktreesDir(cwd: string): boolean {
|
|
167
|
+
const worktreesDir: string = path.join(cwd, '.worktrees');
|
|
168
|
+
if (!fs.existsSync(worktreesDir)) {
|
|
169
|
+
fs.mkdirSync(worktreesDir, { recursive: true });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Add .worktrees/ to .gitignore if not already present
|
|
173
|
+
const gitignorePath: string = path.join(cwd, '.gitignore');
|
|
174
|
+
let gitignoreContent = '';
|
|
175
|
+
try {
|
|
176
|
+
gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8') as string;
|
|
177
|
+
} catch (e) {
|
|
178
|
+
if ((e as NodeJS.ErrnoException).code !== 'ENOENT') {
|
|
179
|
+
// Non-ENOENT errors (EISDIR, EPERM, etc.) are unexpected — re-throw
|
|
180
|
+
throw e;
|
|
181
|
+
}
|
|
182
|
+
// No .gitignore yet — will create
|
|
183
|
+
}
|
|
184
|
+
if (!gitignoreContent.includes('.worktrees')) {
|
|
185
|
+
const newline = gitignoreContent.length > 0 && !gitignoreContent.endsWith('\n') ? '\n' : '';
|
|
186
|
+
try {
|
|
187
|
+
fs.writeFileSync(gitignorePath, gitignoreContent + newline + '.worktrees/\n', 'utf-8');
|
|
188
|
+
} catch (e) {
|
|
189
|
+
process.stderr.write('Warning: could not update .gitignore: ' + (e as Error).message + '\n');
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Compute the branch name for a worktree using the config template.
|
|
198
|
+
*/
|
|
199
|
+
function worktreeBranch(cwd: string, milestone: string, phase: string, slug: string): string {
|
|
200
|
+
const config: GrdConfig = loadConfig(cwd);
|
|
201
|
+
const template: string = config.phase_branch_template || 'grd/{milestone}/{phase}-{slug}';
|
|
202
|
+
return template
|
|
203
|
+
.replace('{milestone}', milestone)
|
|
204
|
+
.replace('{phase}', phase)
|
|
205
|
+
.replace('{slug}', slug || phase);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Compute the milestone branch name using the config template.
|
|
210
|
+
*/
|
|
211
|
+
function milestoneBranch(cwd: string, milestoneVersion?: string): string {
|
|
212
|
+
const config: GrdConfig = loadConfig(cwd);
|
|
213
|
+
const template: string = config.milestone_branch_template || 'grd/{milestone}-{slug}';
|
|
214
|
+
const milestone: MilestoneInfo = getMilestoneInfo(cwd);
|
|
215
|
+
const version: string = milestoneVersion || milestone.version;
|
|
216
|
+
const slug: string = generateSlugInternal(milestone.name) || 'milestone';
|
|
217
|
+
return template.replace('{milestone}', version).replace('{slug}', slug);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Parse git worktree list --porcelain output into structured entries.
|
|
222
|
+
* Each entry is separated by a blank line and has fields:
|
|
223
|
+
* worktree /path/to/worktree
|
|
224
|
+
* HEAD abc123
|
|
225
|
+
* branch refs/heads/branch-name
|
|
226
|
+
* (optional) locked
|
|
227
|
+
*/
|
|
228
|
+
function parsePorcelainWorktrees(porcelainOutput: string): WorktreeEntry[] {
|
|
229
|
+
if (!porcelainOutput || !porcelainOutput.trim()) return [];
|
|
230
|
+
|
|
231
|
+
const blocks: string[] = porcelainOutput.trim().split(/\n\n+/);
|
|
232
|
+
const entries: WorktreeEntry[] = [];
|
|
233
|
+
|
|
234
|
+
for (const block of blocks) {
|
|
235
|
+
const lines: string[] = block.trim().split('\n');
|
|
236
|
+
const entry: WorktreeEntry = { path: '', head: '', branch: '', locked: false };
|
|
237
|
+
|
|
238
|
+
for (const line of lines) {
|
|
239
|
+
if (line.startsWith('worktree ')) {
|
|
240
|
+
entry.path = line.slice('worktree '.length);
|
|
241
|
+
} else if (line.startsWith('HEAD ')) {
|
|
242
|
+
entry.head = line.slice('HEAD '.length);
|
|
243
|
+
} else if (line.startsWith('branch ')) {
|
|
244
|
+
entry.branch = line.slice('branch '.length).replace('refs/heads/', '');
|
|
245
|
+
} else if (line === 'locked') {
|
|
246
|
+
entry.locked = true;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (entry.path) {
|
|
251
|
+
entries.push(entry);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return entries;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Extract phase and milestone from a GRD worktree directory name.
|
|
260
|
+
* Expected format: grd-worktree-{milestone}-{phase}
|
|
261
|
+
*/
|
|
262
|
+
function parseWorktreeName(wtPath: string): WorktreeParsedName | null {
|
|
263
|
+
const dirName: string = path.basename(wtPath);
|
|
264
|
+
const match: RegExpMatchArray | null = dirName.match(/^grd-worktree-(.+)-(\d+)$/);
|
|
265
|
+
if (!match) return null;
|
|
266
|
+
return { milestone: match[1], phase: match[2] };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Get all GRD-managed worktrees (filtering out main and non-GRD entries).
|
|
271
|
+
* Looks for worktrees in the project-local .worktrees/ directory.
|
|
272
|
+
*/
|
|
273
|
+
function getGrdWorktrees(cwd: string): GrdWorktreeEntry[] {
|
|
274
|
+
const listResult: ExecGitResult = execGit(cwd, ['worktree', 'list', '--porcelain']);
|
|
275
|
+
if (listResult.exitCode !== 0) return [];
|
|
276
|
+
|
|
277
|
+
const all: WorktreeEntry[] = parsePorcelainWorktrees(listResult.stdout);
|
|
278
|
+
let resolvedCwd: string;
|
|
279
|
+
try {
|
|
280
|
+
resolvedCwd = fs.realpathSync(cwd);
|
|
281
|
+
} catch {
|
|
282
|
+
return [];
|
|
283
|
+
}
|
|
284
|
+
const worktreesDir: string = path.join(resolvedCwd, '.worktrees');
|
|
285
|
+
|
|
286
|
+
return all
|
|
287
|
+
.filter(
|
|
288
|
+
(wt: WorktreeEntry) =>
|
|
289
|
+
wt.path.startsWith(worktreesDir) && path.basename(wt.path).startsWith('grd-worktree-')
|
|
290
|
+
)
|
|
291
|
+
.map((wt: WorktreeEntry): GrdWorktreeEntry => {
|
|
292
|
+
const meta: WorktreeParsedName | null = parseWorktreeName(wt.path);
|
|
293
|
+
return {
|
|
294
|
+
path: wt.path,
|
|
295
|
+
branch: wt.branch,
|
|
296
|
+
phase: meta ? meta.phase : '',
|
|
297
|
+
milestone: meta ? meta.milestone : '',
|
|
298
|
+
locked: wt.locked,
|
|
299
|
+
};
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ─── CLI Commands ─────────────────────────────────────────────────────────────
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Create a new git worktree for a phase.
|
|
307
|
+
*
|
|
308
|
+
* Creates a worktree at {cwd}/.worktrees/grd-worktree-{milestone}-{phase}
|
|
309
|
+
* with a branch following the configured phase_branch_template pattern.
|
|
310
|
+
* Ensures .worktrees/ directory exists and is listed in .gitignore.
|
|
311
|
+
*
|
|
312
|
+
* @param cwd - Project working directory
|
|
313
|
+
* @param options - Worktree creation options (phase, milestone, slug, startPoint, force)
|
|
314
|
+
* @param raw - If true, output raw text instead of JSON
|
|
315
|
+
* @returns void (outputs JSON or raw text to stdout and exits)
|
|
316
|
+
*/
|
|
317
|
+
function cmdWorktreeCreate(cwd: string, options: WorktreeCreateOptions, raw: boolean): void {
|
|
318
|
+
const { phase, slug, startPoint } = options;
|
|
319
|
+
if (!phase) {
|
|
320
|
+
error('phase is required for worktree create');
|
|
321
|
+
return; // unreachable — error() calls process.exit()
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Resolve milestone from options or ROADMAP.md
|
|
325
|
+
const milestone: string = options.milestone || getMilestoneInfo(cwd).version;
|
|
326
|
+
const branchSlug: string = slug || phase;
|
|
327
|
+
|
|
328
|
+
// Ensure .worktrees/ directory exists and is in .gitignore
|
|
329
|
+
ensureWorktreesDir(cwd);
|
|
330
|
+
|
|
331
|
+
const wtPath: string = worktreePath(cwd, milestone, phase);
|
|
332
|
+
const branch: string = worktreeBranch(cwd, milestone, phase, branchSlug);
|
|
333
|
+
|
|
334
|
+
// Check if worktree already exists
|
|
335
|
+
if (fs.existsSync(wtPath)) {
|
|
336
|
+
output({ error: 'Worktree already exists', path: wtPath }, raw);
|
|
337
|
+
return; // unreachable — output() calls process.exit()
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Verify we are in a git repo
|
|
341
|
+
const revParse: ExecGitResult = execGit(cwd, ['rev-parse', '--git-dir']);
|
|
342
|
+
if (revParse.exitCode !== 0) {
|
|
343
|
+
output({ error: 'Not a git repository', details: revParse.stderr }, raw);
|
|
344
|
+
return; // unreachable
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Validate start-point exists if provided
|
|
348
|
+
if (startPoint) {
|
|
349
|
+
const check: ExecGitResult = execGit(cwd, ['rev-parse', '--verify', startPoint]);
|
|
350
|
+
if (check.exitCode !== 0) {
|
|
351
|
+
output({ error: `Start point '${startPoint}' not found`, details: check.stderr }, raw);
|
|
352
|
+
return; // unreachable
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Create the worktree with a new branch, optionally from a start point
|
|
357
|
+
const gitArgs: string[] = ['worktree', 'add', '-b', branch, wtPath];
|
|
358
|
+
if (startPoint) {
|
|
359
|
+
gitArgs.push(startPoint);
|
|
360
|
+
}
|
|
361
|
+
// Check if branch already exists (before attempting worktree add)
|
|
362
|
+
if (!options.force) {
|
|
363
|
+
const branchCheck: ExecGitResult = execGit(cwd, [
|
|
364
|
+
'rev-parse',
|
|
365
|
+
'--verify',
|
|
366
|
+
'refs/heads/' + branch,
|
|
367
|
+
]);
|
|
368
|
+
if (branchCheck.exitCode === 0) {
|
|
369
|
+
output({ error: `Branch '${branch}' already exists`, branch }, raw);
|
|
370
|
+
return; // unreachable
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const result: ExecGitResult = execGit(cwd, gitArgs);
|
|
375
|
+
if (result.exitCode !== 0) {
|
|
376
|
+
output({ error: 'Failed to create worktree', details: result.stderr, branch }, raw);
|
|
377
|
+
return; // unreachable
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const out: Record<string, string> = {
|
|
381
|
+
path: wtPath,
|
|
382
|
+
branch,
|
|
383
|
+
phase,
|
|
384
|
+
milestone,
|
|
385
|
+
created: new Date().toISOString(),
|
|
386
|
+
};
|
|
387
|
+
if (startPoint) {
|
|
388
|
+
out.start_point = startPoint;
|
|
389
|
+
}
|
|
390
|
+
output(out, raw, `Worktree created: ${out.path} (branch: ${out.branch})`);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Remove a git worktree by phase identifier or direct path.
|
|
395
|
+
*
|
|
396
|
+
* Gracefully handles non-existent worktrees (returns success with already_gone flag).
|
|
397
|
+
* Calls git worktree prune after removal to clean git state.
|
|
398
|
+
*
|
|
399
|
+
* @param cwd - Project working directory
|
|
400
|
+
* @param options - Removal options containing phase, milestone, path, or force flag
|
|
401
|
+
* @param raw - If true, output raw text instead of JSON
|
|
402
|
+
* @returns void (outputs JSON or raw text to stdout and exits)
|
|
403
|
+
*/
|
|
404
|
+
function cmdWorktreeRemove(cwd: string, options: WorktreeRemoveOptions, raw: boolean): void {
|
|
405
|
+
let wtPath: string;
|
|
406
|
+
|
|
407
|
+
if (options.path) {
|
|
408
|
+
wtPath = options.path;
|
|
409
|
+
} else if (options.phase) {
|
|
410
|
+
const milestone: string = options.milestone || getMilestoneInfo(cwd).version;
|
|
411
|
+
wtPath = worktreePath(cwd, milestone, options.phase);
|
|
412
|
+
} else {
|
|
413
|
+
error(
|
|
414
|
+
'Either --phase or --path is required for worktree remove. Specify which worktree to remove using --phase <N> or --path <worktree-path>. Example: worktree remove --phase 3. To list active worktrees: grd-tools.js worktree list'
|
|
415
|
+
);
|
|
416
|
+
return; // unreachable — error() calls process.exit()
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// If path does not exist on disk, return graceful response
|
|
420
|
+
if (!fs.existsSync(wtPath)) {
|
|
421
|
+
// Also try to prune from git's perspective
|
|
422
|
+
execGit(cwd, ['worktree', 'prune']);
|
|
423
|
+
output({ removed: true, path: wtPath, already_gone: true }, raw);
|
|
424
|
+
return; // unreachable
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Try to remove via git worktree remove (--force for GRD-managed temp worktrees)
|
|
428
|
+
const removeResult: ExecGitResult = execGit(cwd, ['worktree', 'remove', wtPath, '--force'], {
|
|
429
|
+
allowBlocked: true,
|
|
430
|
+
});
|
|
431
|
+
if (removeResult.exitCode !== 0) {
|
|
432
|
+
process.stderr.write(
|
|
433
|
+
'Warning: git worktree remove failed: ' +
|
|
434
|
+
(removeResult.stderr || 'unknown error').trim() +
|
|
435
|
+
'\n'
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Prune to clean git state
|
|
440
|
+
execGit(cwd, ['worktree', 'prune']);
|
|
441
|
+
|
|
442
|
+
// Clean up the temp directory if it still exists
|
|
443
|
+
if (fs.existsSync(wtPath)) {
|
|
444
|
+
fs.rmSync(wtPath, { recursive: true, force: true });
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
output({ removed: true, path: wtPath }, raw);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* List all active GRD-managed worktrees.
|
|
452
|
+
*
|
|
453
|
+
* Parses git worktree list --porcelain output and filters to only
|
|
454
|
+
* GRD-created worktrees (those in .worktrees/ with grd-worktree- prefix).
|
|
455
|
+
* Returns empty array if no worktrees exist.
|
|
456
|
+
*
|
|
457
|
+
* @param cwd - Project working directory
|
|
458
|
+
* @param raw - If true, output raw text instead of JSON
|
|
459
|
+
* @returns void (outputs JSON or raw text to stdout and exits)
|
|
460
|
+
*/
|
|
461
|
+
function cmdWorktreeList(cwd: string, raw: boolean): void {
|
|
462
|
+
const worktrees: GrdWorktreeEntry[] = getGrdWorktrees(cwd);
|
|
463
|
+
output({ worktrees, count: worktrees.length }, raw);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Remove stale worktrees whose disk directories no longer exist.
|
|
468
|
+
*
|
|
469
|
+
* A worktree is stale if its path does not exist on disk or is empty.
|
|
470
|
+
* Locked worktrees are never removed regardless of disk state.
|
|
471
|
+
*
|
|
472
|
+
* @param cwd - Project working directory
|
|
473
|
+
* @param raw - If true, output raw text instead of JSON
|
|
474
|
+
* @returns void (outputs JSON or raw text to stdout and exits)
|
|
475
|
+
*/
|
|
476
|
+
function cmdWorktreeRemoveStale(cwd: string, raw: boolean): void {
|
|
477
|
+
const worktrees: GrdWorktreeEntry[] = getGrdWorktrees(cwd);
|
|
478
|
+
const removed: string[] = [];
|
|
479
|
+
|
|
480
|
+
for (const wt of worktrees) {
|
|
481
|
+
// Never remove locked worktrees
|
|
482
|
+
if (wt.locked) continue;
|
|
483
|
+
|
|
484
|
+
// Check if path exists and is non-empty
|
|
485
|
+
let isStale = false;
|
|
486
|
+
try {
|
|
487
|
+
if (!fs.existsSync(wt.path)) {
|
|
488
|
+
isStale = true;
|
|
489
|
+
} else {
|
|
490
|
+
const entries: string[] = fs.readdirSync(wt.path);
|
|
491
|
+
if (entries.length === 0) {
|
|
492
|
+
isStale = true;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
} catch {
|
|
496
|
+
isStale = true;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (isStale) {
|
|
500
|
+
// Remove from git's worktree tracking
|
|
501
|
+
execGit(cwd, ['worktree', 'remove', wt.path, '--force'], { allowBlocked: true });
|
|
502
|
+
removed.push(wt.path);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Final prune to clean any remaining stale references
|
|
507
|
+
execGit(cwd, ['worktree', 'prune']);
|
|
508
|
+
|
|
509
|
+
output({ removed, count: removed.length }, raw);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Push worktree branch to remote and create a PR via gh CLI.
|
|
514
|
+
*
|
|
515
|
+
* Both error paths return structured JSON (exit 0) so callers (orchestrators,
|
|
516
|
+
* MCP) receive parseable output rather than a crash.
|
|
517
|
+
*
|
|
518
|
+
* @param cwd - Project working directory
|
|
519
|
+
* @param options - Options object containing phase, milestone, title, body, and base branch
|
|
520
|
+
* @param raw - If true, output raw text instead of JSON
|
|
521
|
+
* @returns void (outputs JSON or raw text to stdout and exits)
|
|
522
|
+
*/
|
|
523
|
+
function cmdWorktreePushAndPR(cwd: string, options: Record<string, string>, raw: boolean): void {
|
|
524
|
+
const { phase } = options;
|
|
525
|
+
if (!phase) {
|
|
526
|
+
output({ error: 'phase is required for worktree push-pr' }, raw);
|
|
527
|
+
return; // unreachable
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Resolve milestone
|
|
531
|
+
const milestone: string = options.milestone || getMilestoneInfo(cwd).version;
|
|
532
|
+
|
|
533
|
+
// Compute worktree path
|
|
534
|
+
const wtPath: string = worktreePath(cwd, milestone, phase);
|
|
535
|
+
|
|
536
|
+
// Verify worktree directory exists on disk
|
|
537
|
+
if (!fs.existsSync(wtPath)) {
|
|
538
|
+
output({ error: `Worktree not found at ${wtPath}`, phase, milestone }, raw);
|
|
539
|
+
return; // unreachable
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Read the actual branch name from the worktree HEAD (more robust than recomputing)
|
|
543
|
+
const headResult: ExecGitResult = execGit(wtPath, ['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
544
|
+
const branch: string =
|
|
545
|
+
headResult.exitCode === 0 && headResult.stdout
|
|
546
|
+
? headResult.stdout.trim()
|
|
547
|
+
: worktreeBranch(cwd, milestone, phase, phase); // fallback
|
|
548
|
+
|
|
549
|
+
// Derive slug from branch name for title generation
|
|
550
|
+
const phaseInfo: PhaseInfo | null = findPhaseInternal(cwd, phase);
|
|
551
|
+
const slug: string =
|
|
552
|
+
phaseInfo && phaseInfo.phase_slug
|
|
553
|
+
? phaseInfo.phase_slug
|
|
554
|
+
: branch.split('/').pop()?.replace(/^\d+-/, '') || phase;
|
|
555
|
+
|
|
556
|
+
// Push branch to origin from worktree directory
|
|
557
|
+
const pushResult: ExecGitResult = execGit(wtPath, ['push', '-u', 'origin', branch], {
|
|
558
|
+
allowBlocked: true,
|
|
559
|
+
});
|
|
560
|
+
if (pushResult.exitCode !== 0) {
|
|
561
|
+
output(
|
|
562
|
+
{
|
|
563
|
+
error: 'Failed to push branch',
|
|
564
|
+
details: pushResult.stderr,
|
|
565
|
+
phase,
|
|
566
|
+
milestone,
|
|
567
|
+
branch,
|
|
568
|
+
},
|
|
569
|
+
raw
|
|
570
|
+
);
|
|
571
|
+
return; // unreachable
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Resolve base branch
|
|
575
|
+
const config: GrdConfig = loadConfig(cwd);
|
|
576
|
+
const baseBranch: string = options.base || config.base_branch || 'main';
|
|
577
|
+
|
|
578
|
+
// Build PR title
|
|
579
|
+
const title: string = options.title || `Phase ${phase}: ${slug} (${milestone})`;
|
|
580
|
+
|
|
581
|
+
// Build PR body
|
|
582
|
+
const body: string =
|
|
583
|
+
options.body || `## Phase ${phase}\n\nMilestone: ${milestone}\nBranch: ${branch}\n`;
|
|
584
|
+
|
|
585
|
+
// Create PR via gh CLI
|
|
586
|
+
let ghOutput: string;
|
|
587
|
+
try {
|
|
588
|
+
ghOutput = execFileSync(
|
|
589
|
+
'gh',
|
|
590
|
+
['pr', 'create', '--title', title, '--body', body, '--base', baseBranch, '--head', branch],
|
|
591
|
+
{ cwd: wtPath, stdio: 'pipe', encoding: 'utf-8' }
|
|
592
|
+
) as string;
|
|
593
|
+
} catch (ghErr) {
|
|
594
|
+
// gh failed — return structured error with push_succeeded flag
|
|
595
|
+
const stderr: string = (ghErr as { stderr?: Buffer | string }).stderr
|
|
596
|
+
? String((ghErr as { stderr?: Buffer | string }).stderr).trim()
|
|
597
|
+
: (ghErr as Error).message || 'Unknown gh error';
|
|
598
|
+
output(
|
|
599
|
+
{
|
|
600
|
+
error: 'Failed to create PR via gh CLI',
|
|
601
|
+
details: stderr,
|
|
602
|
+
push_succeeded: true,
|
|
603
|
+
phase,
|
|
604
|
+
milestone,
|
|
605
|
+
branch,
|
|
606
|
+
base: baseBranch,
|
|
607
|
+
title,
|
|
608
|
+
body,
|
|
609
|
+
},
|
|
610
|
+
raw
|
|
611
|
+
);
|
|
612
|
+
return; // unreachable
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// gh pr create outputs the PR URL on stdout
|
|
616
|
+
const prUrl: string = ghOutput.trim();
|
|
617
|
+
const urlParts: string[] = prUrl.split('/');
|
|
618
|
+
const prNumber: string | null = urlParts.length > 0 ? urlParts[urlParts.length - 1] : null;
|
|
619
|
+
|
|
620
|
+
output(
|
|
621
|
+
{
|
|
622
|
+
pr_url: prUrl,
|
|
623
|
+
pr_number: prNumber,
|
|
624
|
+
branch,
|
|
625
|
+
base: baseBranch,
|
|
626
|
+
title,
|
|
627
|
+
body,
|
|
628
|
+
phase,
|
|
629
|
+
milestone,
|
|
630
|
+
},
|
|
631
|
+
raw
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// ─── Programmatic Helpers (evolve, autopilot) ────────────────────────────────
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Create a worktree for the evolve loop.
|
|
639
|
+
* Returns a result object instead of calling output() — suitable for
|
|
640
|
+
* programmatic callers that need the path/branch info.
|
|
641
|
+
*
|
|
642
|
+
* Worktree directory: {cwd}/.worktrees/grd-evolve-{YYYYMMDD-HHmmss}
|
|
643
|
+
* Branch name: grd/evolve/{YYYYMMDD-HHmmss}
|
|
644
|
+
*
|
|
645
|
+
* @param cwd - Project working directory
|
|
646
|
+
* @returns EvolveWorktreeResult with path, branch, baseBranch, and created timestamp, or EvolveWorktreeError with error message
|
|
647
|
+
*/
|
|
648
|
+
function createEvolveWorktree(cwd: string): EvolveWorktreeResult | EvolveWorktreeError {
|
|
649
|
+
// Verify git repo
|
|
650
|
+
const revParse: ExecGitResult = execGit(cwd, ['rev-parse', '--git-dir']);
|
|
651
|
+
if (revParse.exitCode !== 0) {
|
|
652
|
+
return { error: 'Not a git repository' };
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const now: Date = new Date();
|
|
656
|
+
const stamp: string = now
|
|
657
|
+
.toISOString()
|
|
658
|
+
.replace(/[-:T]/g, '')
|
|
659
|
+
.replace(/\.\d+Z$/, '')
|
|
660
|
+
.slice(0, 15);
|
|
661
|
+
// stamp format: YYYYMMDD + HHmmss (15 chars)
|
|
662
|
+
const tag = `${stamp.slice(0, 8)}-${stamp.slice(8)}`;
|
|
663
|
+
|
|
664
|
+
const config: GrdConfig = loadConfig(cwd);
|
|
665
|
+
const baseBranch: string = config.base_branch || 'main';
|
|
666
|
+
|
|
667
|
+
ensureWorktreesDir(cwd);
|
|
668
|
+
|
|
669
|
+
const resolvedCwd: string = fs.realpathSync(cwd);
|
|
670
|
+
const wtPath: string = path.join(resolvedCwd, '.worktrees', `grd-evolve-${tag}`);
|
|
671
|
+
const branch = `grd/evolve/${tag}`;
|
|
672
|
+
|
|
673
|
+
if (fs.existsSync(wtPath)) {
|
|
674
|
+
return { error: `Worktree already exists at ${wtPath}` };
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const result: ExecGitResult = execGit(cwd, ['worktree', 'add', '-b', branch, wtPath, baseBranch]);
|
|
678
|
+
if (result.exitCode !== 0) {
|
|
679
|
+
return { error: `Failed to create worktree: ${result.stderr}` };
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
return {
|
|
683
|
+
path: wtPath,
|
|
684
|
+
branch,
|
|
685
|
+
baseBranch,
|
|
686
|
+
created: now.toISOString(),
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Remove an evolve worktree and prune git state.
|
|
692
|
+
* Gracefully handles already-removed worktrees.
|
|
693
|
+
*/
|
|
694
|
+
function removeEvolveWorktree(
|
|
695
|
+
cwd: string,
|
|
696
|
+
wtPath: string
|
|
697
|
+
): RemoveWorktreeResult | RemoveWorktreeError {
|
|
698
|
+
if (!fs.existsSync(wtPath)) {
|
|
699
|
+
execGit(cwd, ['worktree', 'prune']);
|
|
700
|
+
return { removed: true, already_gone: true };
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
execGit(cwd, ['worktree', 'remove', wtPath, '--force'], { allowBlocked: true });
|
|
704
|
+
execGit(cwd, ['worktree', 'prune']);
|
|
705
|
+
|
|
706
|
+
if (fs.existsSync(wtPath)) {
|
|
707
|
+
fs.rmSync(wtPath, { recursive: true, force: true });
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
return { removed: true };
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Push a worktree branch to remote and create a PR via gh CLI.
|
|
715
|
+
* Returns structured result instead of calling output().
|
|
716
|
+
*/
|
|
717
|
+
function pushAndCreatePR(
|
|
718
|
+
cwd: string,
|
|
719
|
+
wtPath: string,
|
|
720
|
+
options: PushPROptions = {}
|
|
721
|
+
): PushPRSuccessResult | PushPRErrorResult {
|
|
722
|
+
// Read branch from worktree HEAD
|
|
723
|
+
const headResult: ExecGitResult = execGit(wtPath, ['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
724
|
+
if (headResult.exitCode !== 0) {
|
|
725
|
+
return { error: 'Failed to determine worktree branch' };
|
|
726
|
+
}
|
|
727
|
+
const branch: string = headResult.stdout.trim();
|
|
728
|
+
|
|
729
|
+
const config: GrdConfig = loadConfig(cwd);
|
|
730
|
+
const baseBranch: string = options.base || config.base_branch || 'main';
|
|
731
|
+
|
|
732
|
+
// Push branch
|
|
733
|
+
const pushResult: ExecGitResult = execGit(wtPath, ['push', '-u', 'origin', branch], {
|
|
734
|
+
allowBlocked: true,
|
|
735
|
+
});
|
|
736
|
+
if (pushResult.exitCode !== 0) {
|
|
737
|
+
return {
|
|
738
|
+
error: `Failed to push branch: ${pushResult.stderr}`,
|
|
739
|
+
push_succeeded: false,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const title: string = options.title || `Evolve: improvements on ${branch}`;
|
|
744
|
+
const body: string =
|
|
745
|
+
options.body || `Automated improvements from the evolve loop.\n\nBranch: ${branch}\n`;
|
|
746
|
+
|
|
747
|
+
// Create PR via gh CLI
|
|
748
|
+
let ghOutput: string;
|
|
749
|
+
try {
|
|
750
|
+
ghOutput = execFileSync(
|
|
751
|
+
'gh',
|
|
752
|
+
['pr', 'create', '--title', title, '--body', body, '--base', baseBranch, '--head', branch],
|
|
753
|
+
{ cwd: wtPath, stdio: 'pipe', encoding: 'utf-8' }
|
|
754
|
+
) as string;
|
|
755
|
+
} catch (ghErr) {
|
|
756
|
+
const stderr: string = (ghErr as { stderr?: Buffer | string }).stderr
|
|
757
|
+
? String((ghErr as { stderr?: Buffer | string }).stderr).trim()
|
|
758
|
+
: (ghErr as Error).message || 'Unknown gh error';
|
|
759
|
+
return { error: `Failed to create PR: ${stderr}`, push_succeeded: true };
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
return {
|
|
763
|
+
pr_url: ghOutput.trim(),
|
|
764
|
+
branch,
|
|
765
|
+
base: baseBranch,
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// ─── Milestone Branch Operations ──────────────────────────────────────────────
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Ensure the milestone branch exists, creating it from baseBranch if needed.
|
|
773
|
+
*/
|
|
774
|
+
function cmdWorktreeEnsureMilestoneBranch(
|
|
775
|
+
cwd: string,
|
|
776
|
+
options: Record<string, string>,
|
|
777
|
+
raw: boolean
|
|
778
|
+
): void {
|
|
779
|
+
const milestoneVersion: string = options.milestone || getMilestoneInfo(cwd).version;
|
|
780
|
+
const config: GrdConfig = loadConfig(cwd);
|
|
781
|
+
const baseBranch: string = options.baseBranch || config.base_branch || 'main';
|
|
782
|
+
const branch: string = milestoneBranch(cwd, milestoneVersion);
|
|
783
|
+
|
|
784
|
+
// Check if branch already exists
|
|
785
|
+
const check: ExecGitResult = execGit(cwd, ['rev-parse', '--verify', branch]);
|
|
786
|
+
if (check.exitCode === 0) {
|
|
787
|
+
output({ branch, already_existed: true }, raw);
|
|
788
|
+
return; // unreachable
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// Verify base branch exists
|
|
792
|
+
const baseCheck: ExecGitResult = execGit(cwd, ['rev-parse', '--verify', baseBranch]);
|
|
793
|
+
if (baseCheck.exitCode !== 0) {
|
|
794
|
+
output({ error: `Base branch '${baseBranch}' not found`, details: baseCheck.stderr }, raw);
|
|
795
|
+
return; // unreachable
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// Create branch without checkout
|
|
799
|
+
const result: ExecGitResult = execGit(cwd, ['branch', branch, baseBranch]);
|
|
800
|
+
if (result.exitCode !== 0) {
|
|
801
|
+
output({ error: 'Failed to create milestone branch', details: result.stderr }, raw);
|
|
802
|
+
return; // unreachable
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
output({ branch, already_existed: false, base_branch: baseBranch }, raw);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* Merge a phase branch into the target branch.
|
|
810
|
+
*
|
|
811
|
+
* Target branch is determined by: options.base > config.base_branch > milestone branch.
|
|
812
|
+
* When --base is provided, merges directly into that branch (no milestone branch required).
|
|
813
|
+
* Otherwise falls back to the milestone branch for backward compatibility.
|
|
814
|
+
*
|
|
815
|
+
* Records the current branch, checks out the target branch, merges the
|
|
816
|
+
* phase branch with --no-ff, and restores the original branch. On conflict,
|
|
817
|
+
* aborts the merge and restores the original branch.
|
|
818
|
+
*
|
|
819
|
+
* @param cwd - Project working directory
|
|
820
|
+
* @param options - Merge options including phase, milestone, slug, branch, base, deleteBranch, and strategy
|
|
821
|
+
* @param raw - If true, output raw text instead of JSON
|
|
822
|
+
* @returns void (outputs JSON or raw text to stdout and exits)
|
|
823
|
+
*/
|
|
824
|
+
function cmdWorktreeMerge(cwd: string, options: MergeOptions, raw: boolean): void {
|
|
825
|
+
const { phase, slug, deleteBranch } = options;
|
|
826
|
+
if (!phase) {
|
|
827
|
+
error(
|
|
828
|
+
'phase is required for worktree merge. Usage: worktree merge --phase <N>. Add the --phase flag, e.g.: worktree merge --phase 2'
|
|
829
|
+
);
|
|
830
|
+
return; // unreachable — error() calls process.exit()
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const milestoneVersion: string = options.milestone || getMilestoneInfo(cwd).version;
|
|
834
|
+
const phaseBranch: string =
|
|
835
|
+
options.branch || worktreeBranch(cwd, milestoneVersion, phase, slug || phase);
|
|
836
|
+
|
|
837
|
+
// Determine target branch: explicit --base, or milestone branch (with fallback to config base_branch)
|
|
838
|
+
let targetBranch: string;
|
|
839
|
+
if (options.base) {
|
|
840
|
+
// Explicit base branch — merge directly into it (no milestone branch needed)
|
|
841
|
+
targetBranch = options.base;
|
|
842
|
+
} else {
|
|
843
|
+
// Try milestone branch first (backward compat); fall back to config.base_branch
|
|
844
|
+
const msBranch: string = milestoneBranch(cwd, milestoneVersion);
|
|
845
|
+
const msCheck: ExecGitResult = execGit(cwd, ['rev-parse', '--verify', msBranch]);
|
|
846
|
+
if (msCheck.exitCode === 0) {
|
|
847
|
+
targetBranch = msBranch;
|
|
848
|
+
} else {
|
|
849
|
+
const config: GrdConfig = loadConfig(cwd);
|
|
850
|
+
targetBranch = config.base_branch || 'main';
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Verify target branch exists
|
|
855
|
+
const targetCheck: ExecGitResult = execGit(cwd, ['rev-parse', '--verify', targetBranch]);
|
|
856
|
+
if (targetCheck.exitCode !== 0) {
|
|
857
|
+
output({ error: `Target branch '${targetBranch}' not found` }, raw);
|
|
858
|
+
return; // unreachable
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// Verify phase branch exists
|
|
862
|
+
const phaseCheck: ExecGitResult = execGit(cwd, ['rev-parse', '--verify', phaseBranch]);
|
|
863
|
+
if (phaseCheck.exitCode !== 0) {
|
|
864
|
+
output({ error: `Phase branch '${phaseBranch}' not found` }, raw);
|
|
865
|
+
return; // unreachable
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// Record current branch
|
|
869
|
+
const headResult: ExecGitResult = execGit(cwd, ['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
870
|
+
const originalBranch: string = headResult.exitCode === 0 ? headResult.stdout.trim() : 'main';
|
|
871
|
+
|
|
872
|
+
// Checkout target branch
|
|
873
|
+
const coResult: ExecGitResult = execGit(cwd, ['checkout', targetBranch]);
|
|
874
|
+
if (coResult.exitCode !== 0) {
|
|
875
|
+
output(
|
|
876
|
+
{
|
|
877
|
+
error: `Failed to checkout target branch '${targetBranch}'`,
|
|
878
|
+
details: coResult.stderr,
|
|
879
|
+
},
|
|
880
|
+
raw
|
|
881
|
+
);
|
|
882
|
+
return; // unreachable
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// Get phase info for merge commit message
|
|
886
|
+
const phaseInfo: PhaseInfo | null = findPhaseInternal(cwd, phase);
|
|
887
|
+
const phaseName: string = phaseInfo ? phaseInfo.phase_name || slug || phase : slug || phase;
|
|
888
|
+
|
|
889
|
+
// Merge phase branch with --no-ff
|
|
890
|
+
const mergeResult: ExecGitResult = execGit(cwd, [
|
|
891
|
+
'merge',
|
|
892
|
+
'--no-ff',
|
|
893
|
+
phaseBranch,
|
|
894
|
+
'-m',
|
|
895
|
+
`Merge phase ${phase}: ${phaseName}`,
|
|
896
|
+
]);
|
|
897
|
+
|
|
898
|
+
if (mergeResult.exitCode !== 0) {
|
|
899
|
+
// Merge conflict — abort and restore original branch
|
|
900
|
+
execGit(cwd, ['merge', '--abort']);
|
|
901
|
+
const abortRestore = execGit(cwd, ['checkout', originalBranch]);
|
|
902
|
+
if (abortRestore.exitCode !== 0) {
|
|
903
|
+
process.stderr.write(
|
|
904
|
+
`[grd] WARNING: failed to restore branch ${originalBranch} after merge abort\n`
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
output(
|
|
908
|
+
{
|
|
909
|
+
error: 'Merge conflict',
|
|
910
|
+
details: mergeResult.stderr,
|
|
911
|
+
target_branch: targetBranch,
|
|
912
|
+
phase_branch: phaseBranch,
|
|
913
|
+
},
|
|
914
|
+
raw
|
|
915
|
+
);
|
|
916
|
+
return; // unreachable
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// Optionally delete phase branch
|
|
920
|
+
if (deleteBranch) {
|
|
921
|
+
execGit(cwd, ['branch', '-d', phaseBranch]);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// Restore original branch
|
|
925
|
+
const restoreResult = execGit(cwd, ['checkout', originalBranch]);
|
|
926
|
+
const restoredBranch = restoreResult.exitCode === 0;
|
|
927
|
+
if (!restoredBranch) {
|
|
928
|
+
process.stderr.write(`[grd] WARNING: failed to restore branch ${originalBranch} after merge\n`);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
output(
|
|
932
|
+
{
|
|
933
|
+
merged: true,
|
|
934
|
+
target_branch: targetBranch,
|
|
935
|
+
phase_branch: phaseBranch,
|
|
936
|
+
phase,
|
|
937
|
+
branch_deleted: !!deleteBranch,
|
|
938
|
+
original_branch_restored: restoredBranch,
|
|
939
|
+
},
|
|
940
|
+
raw
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// ─── Hook Handlers ────────────────────────────────────────────────────────────
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* Hook handler for WorktreeCreate events from Claude Code.
|
|
948
|
+
*
|
|
949
|
+
* When Claude Code creates a worktree natively (via `isolation: worktree`),
|
|
950
|
+
* this hook fires to optionally rename the branch to GRD's naming convention
|
|
951
|
+
* and log the lifecycle event. No-op when GRD is inactive or branching disabled.
|
|
952
|
+
*
|
|
953
|
+
* @param cwd - Project working directory
|
|
954
|
+
* @param wtPath - Path to the created worktree ($WORKTREE_PATH)
|
|
955
|
+
* @param wtBranch - Branch name of the created worktree ($WORKTREE_BRANCH)
|
|
956
|
+
* @param raw - If true, output raw text instead of JSON
|
|
957
|
+
* @returns void (outputs JSON or raw text to stdout and exits)
|
|
958
|
+
*/
|
|
959
|
+
function cmdWorktreeHookCreate(cwd: string, wtPath: string, wtBranch: string, raw: boolean): void {
|
|
960
|
+
// No-op when GRD is inactive
|
|
961
|
+
if (!fs.existsSync(path.join(cwd, '.planning'))) {
|
|
962
|
+
output({ skipped: true, reason: 'no .planning directory' }, raw);
|
|
963
|
+
return; // unreachable
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// No-op when branching is disabled
|
|
967
|
+
const config: GrdConfig = loadConfig(cwd);
|
|
968
|
+
if (config.branching_strategy === 'none') {
|
|
969
|
+
output({ skipped: true, reason: 'branching disabled' }, raw);
|
|
970
|
+
return; // unreachable
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Check if branch already follows GRD convention (starts with 'grd/')
|
|
974
|
+
if (wtBranch && wtBranch.startsWith('grd/')) {
|
|
975
|
+
output(
|
|
976
|
+
{
|
|
977
|
+
hooked: true,
|
|
978
|
+
worktree_path: wtPath,
|
|
979
|
+
branch: wtBranch,
|
|
980
|
+
renamed: false,
|
|
981
|
+
reason: 'branch already follows GRD convention',
|
|
982
|
+
},
|
|
983
|
+
raw
|
|
984
|
+
);
|
|
985
|
+
return; // unreachable
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Try to extract phase info from the worktree path for branch rename
|
|
989
|
+
const dirName: string = path.basename(wtPath || '');
|
|
990
|
+
const phaseMatch: RegExpMatchArray | null = dirName.match(/(\d+)$/);
|
|
991
|
+
|
|
992
|
+
if (phaseMatch) {
|
|
993
|
+
const phase: string = phaseMatch[1];
|
|
994
|
+
try {
|
|
995
|
+
const milestone: MilestoneInfo = getMilestoneInfo(cwd);
|
|
996
|
+
const phaseInfo: PhaseInfo | null = findPhaseInternal(cwd, phase);
|
|
997
|
+
const slug: string = phaseInfo
|
|
998
|
+
? generateSlugInternal(phaseInfo.phase_slug || phaseInfo.phase_name || '') || phase
|
|
999
|
+
: phase;
|
|
1000
|
+
const grdBranch: string = worktreeBranch(cwd, milestone.version, phase, slug);
|
|
1001
|
+
|
|
1002
|
+
if (grdBranch !== wtBranch) {
|
|
1003
|
+
const renameResult: ExecGitResult = execGit(wtPath, ['branch', '-m', wtBranch, grdBranch]);
|
|
1004
|
+
if (renameResult.exitCode === 0) {
|
|
1005
|
+
output(
|
|
1006
|
+
{
|
|
1007
|
+
hooked: true,
|
|
1008
|
+
worktree_path: wtPath,
|
|
1009
|
+
branch: grdBranch,
|
|
1010
|
+
renamed: true,
|
|
1011
|
+
original_branch: wtBranch,
|
|
1012
|
+
},
|
|
1013
|
+
raw
|
|
1014
|
+
);
|
|
1015
|
+
return; // unreachable
|
|
1016
|
+
}
|
|
1017
|
+
// Rename failed — log warning to stderr and continue
|
|
1018
|
+
process.stderr.write(
|
|
1019
|
+
'Warning: branch rename failed: ' +
|
|
1020
|
+
(renameResult.stderr || 'git branch rename failed').trim() +
|
|
1021
|
+
'\n'
|
|
1022
|
+
);
|
|
1023
|
+
output(
|
|
1024
|
+
{
|
|
1025
|
+
hooked: true,
|
|
1026
|
+
worktree_path: wtPath,
|
|
1027
|
+
branch: wtBranch,
|
|
1028
|
+
renamed: false,
|
|
1029
|
+
rename_failed: true,
|
|
1030
|
+
reason: renameResult.stderr || 'git branch rename failed',
|
|
1031
|
+
},
|
|
1032
|
+
raw
|
|
1033
|
+
);
|
|
1034
|
+
return; // unreachable
|
|
1035
|
+
}
|
|
1036
|
+
} catch (_e) {
|
|
1037
|
+
// Could not determine milestone/phase info — log creation without rename
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// Default: log creation without rename
|
|
1042
|
+
output(
|
|
1043
|
+
{
|
|
1044
|
+
hooked: true,
|
|
1045
|
+
worktree_path: wtPath,
|
|
1046
|
+
branch: wtBranch,
|
|
1047
|
+
renamed: false,
|
|
1048
|
+
},
|
|
1049
|
+
raw
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
* Hook handler for WorktreeRemove events from Claude Code.
|
|
1055
|
+
*
|
|
1056
|
+
* Logs the worktree removal for tracking. Intentionally minimal —
|
|
1057
|
+
* Phase 46 will add state cleanup here if needed.
|
|
1058
|
+
* No-op when GRD is inactive or branching disabled.
|
|
1059
|
+
*
|
|
1060
|
+
* @param cwd - Project working directory
|
|
1061
|
+
* @param wtPath - Path to the removed worktree ($WORKTREE_PATH)
|
|
1062
|
+
* @param wtBranch - Branch name of the removed worktree ($WORKTREE_BRANCH)
|
|
1063
|
+
* @param raw - If true, output raw text instead of JSON
|
|
1064
|
+
* @returns void (outputs JSON or raw text to stdout and exits)
|
|
1065
|
+
*/
|
|
1066
|
+
function cmdWorktreeHookRemove(cwd: string, wtPath: string, wtBranch: string, raw: boolean): void {
|
|
1067
|
+
// No-op when GRD is inactive
|
|
1068
|
+
if (!fs.existsSync(path.join(cwd, '.planning'))) {
|
|
1069
|
+
output({ skipped: true, reason: 'no .planning directory' }, raw);
|
|
1070
|
+
return; // unreachable
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// No-op when branching is disabled
|
|
1074
|
+
const config: GrdConfig = loadConfig(cwd);
|
|
1075
|
+
if (config.branching_strategy === 'none') {
|
|
1076
|
+
output({ skipped: true, reason: 'branching disabled' }, raw);
|
|
1077
|
+
return; // unreachable
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
const result: Record<string, unknown> = {
|
|
1081
|
+
hooked: true,
|
|
1082
|
+
worktree_path: wtPath,
|
|
1083
|
+
branch: wtBranch,
|
|
1084
|
+
action: 'remove_logged',
|
|
1085
|
+
};
|
|
1086
|
+
|
|
1087
|
+
// Try to extract phase/milestone metadata from the worktree path
|
|
1088
|
+
// Expected GRD format: grd-worktree-{milestone}-{phase}
|
|
1089
|
+
try {
|
|
1090
|
+
const meta: WorktreeParsedName | null = parseWorktreeName(wtPath || '');
|
|
1091
|
+
if (meta) {
|
|
1092
|
+
result.phase_detected = meta.phase;
|
|
1093
|
+
result.milestone_detected = meta.milestone;
|
|
1094
|
+
}
|
|
1095
|
+
} catch (_e) {
|
|
1096
|
+
// Best-effort: metadata extraction failure is not an error
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
output(result, raw, `Worktree hook: ${result.worktree_path}`);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// ─── Hook Handlers ────────────────────────────────────────────────────────────
|
|
1103
|
+
|
|
1104
|
+
/**
|
|
1105
|
+
* Hook handler for TeammateIdle events.
|
|
1106
|
+
* Called when a teammate spawned via Agent tool becomes idle.
|
|
1107
|
+
* Supports {continue: false, stopReason: "..."} response to stop the teammate.
|
|
1108
|
+
*
|
|
1109
|
+
* Environment variables from hook payload:
|
|
1110
|
+
* - AGENT_ID: unique identifier of the idle agent
|
|
1111
|
+
* - AGENT_TYPE: type of the agent (e.g., "task", "teammate")
|
|
1112
|
+
*/
|
|
1113
|
+
function cmdTeammateIdleHook(_cwd: string, raw: boolean): void {
|
|
1114
|
+
const agentId = process.env.AGENT_ID || 'unknown';
|
|
1115
|
+
const agentType = process.env.AGENT_TYPE || 'unknown';
|
|
1116
|
+
|
|
1117
|
+
// For now, allow all teammates to continue.
|
|
1118
|
+
// Future: filter by agent_type to stop non-GRD agents or agents that have completed their work.
|
|
1119
|
+
const result = {
|
|
1120
|
+
ok: true,
|
|
1121
|
+
hook: 'TeammateIdle',
|
|
1122
|
+
agent_id: agentId,
|
|
1123
|
+
agent_type: agentType,
|
|
1124
|
+
action: 'continue',
|
|
1125
|
+
};
|
|
1126
|
+
|
|
1127
|
+
if (raw) {
|
|
1128
|
+
process.stdout.write(`TeammateIdle: agent=${agentId} type=${agentType} action=continue\n`);
|
|
1129
|
+
} else {
|
|
1130
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
/**
|
|
1135
|
+
* Hook handler for TaskCompleted events.
|
|
1136
|
+
* Called when a background task completes.
|
|
1137
|
+
* Supports {continue: false, stopReason: "..."} response to stop the task.
|
|
1138
|
+
*
|
|
1139
|
+
* Environment variables from hook payload:
|
|
1140
|
+
* - AGENT_ID: unique identifier of the completed task agent
|
|
1141
|
+
* - AGENT_TYPE: type of the agent
|
|
1142
|
+
*/
|
|
1143
|
+
function cmdTaskCompletedHook(_cwd: string, raw: boolean): void {
|
|
1144
|
+
const agentId = process.env.AGENT_ID || 'unknown';
|
|
1145
|
+
const agentType = process.env.AGENT_TYPE || 'unknown';
|
|
1146
|
+
|
|
1147
|
+
const result = {
|
|
1148
|
+
ok: true,
|
|
1149
|
+
hook: 'TaskCompleted',
|
|
1150
|
+
agent_id: agentId,
|
|
1151
|
+
agent_type: agentType,
|
|
1152
|
+
action: 'acknowledged',
|
|
1153
|
+
};
|
|
1154
|
+
|
|
1155
|
+
if (raw) {
|
|
1156
|
+
process.stdout.write(`TaskCompleted: agent=${agentId} type=${agentType}\n`);
|
|
1157
|
+
} else {
|
|
1158
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
/**
|
|
1163
|
+
* Hook handler for InstructionsLoaded events.
|
|
1164
|
+
* Called when CLAUDE.md or rules files are loaded.
|
|
1165
|
+
* Used for plugin setup verification (e.g., confirm .planning/ exists).
|
|
1166
|
+
*
|
|
1167
|
+
* Environment variables from hook payload:
|
|
1168
|
+
* - AGENT_ID: unique identifier of the agent loading instructions
|
|
1169
|
+
* - AGENT_TYPE: type of the agent
|
|
1170
|
+
*/
|
|
1171
|
+
function cmdInstructionsLoadedHook(cwd: string, raw: boolean): void {
|
|
1172
|
+
const agentId = process.env.AGENT_ID || 'unknown';
|
|
1173
|
+
const agentType = process.env.AGENT_TYPE || 'unknown';
|
|
1174
|
+
const planningDir = path.join(cwd, '.planning');
|
|
1175
|
+
const planningExists = fs.existsSync(planningDir);
|
|
1176
|
+
|
|
1177
|
+
const result = {
|
|
1178
|
+
ok: true,
|
|
1179
|
+
hook: 'InstructionsLoaded',
|
|
1180
|
+
agent_id: agentId,
|
|
1181
|
+
agent_type: agentType,
|
|
1182
|
+
planning_exists: planningExists,
|
|
1183
|
+
};
|
|
1184
|
+
|
|
1185
|
+
if (raw) {
|
|
1186
|
+
process.stdout.write(`InstructionsLoaded: agent=${agentId} planning=${planningExists}\n`);
|
|
1187
|
+
} else {
|
|
1188
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/**
|
|
1193
|
+
* Hook handler for StopFailure events (Claude Code v2.1.78+).
|
|
1194
|
+
*
|
|
1195
|
+
* Fires when a turn ends due to an API-level failure (rate limit, auth failure,
|
|
1196
|
+
* network error). GRD uses this to log failures to autopilot.log so the
|
|
1197
|
+
* autopilot/evolve retry logic can detect and act on them.
|
|
1198
|
+
*
|
|
1199
|
+
* Environment variables from hook payload:
|
|
1200
|
+
* - STOP_REASON: reason the turn stopped (e.g., "rate_limit", "auth_failure", "api_error")
|
|
1201
|
+
* - ERROR_MESSAGE: error message text (optional)
|
|
1202
|
+
* - AGENT_ID: unique identifier of the affected agent (optional)
|
|
1203
|
+
*
|
|
1204
|
+
* Note: allowRead sandbox setting (v2.1.77) can re-allow read access within
|
|
1205
|
+
* denyRead regions — relevant for plugin data paths that need to check autopilot.log.
|
|
1206
|
+
*
|
|
1207
|
+
* @param cwd - Project working directory
|
|
1208
|
+
* @param raw - If true, output raw text instead of JSON
|
|
1209
|
+
* @returns void (outputs JSON or raw text to stdout)
|
|
1210
|
+
*/
|
|
1211
|
+
function cmdStopFailureHook(cwd: string, raw: boolean): void {
|
|
1212
|
+
const stopReason = process.env.STOP_REASON || 'unknown';
|
|
1213
|
+
const errorMessage = process.env.ERROR_MESSAGE || '';
|
|
1214
|
+
const agentId = process.env.AGENT_ID || 'unknown';
|
|
1215
|
+
|
|
1216
|
+
// Check if autopilot.log exists — indicates an active autopilot/evolve session
|
|
1217
|
+
const autopilotLogPath = path.join(cwd, '.planning', 'autopilot', 'autopilot.log');
|
|
1218
|
+
const autopilotActive = fs.existsSync(autopilotLogPath);
|
|
1219
|
+
|
|
1220
|
+
let logged = false;
|
|
1221
|
+
if (autopilotActive) {
|
|
1222
|
+
// Append timestamped failure entry to autopilot.log
|
|
1223
|
+
const timestamp = new Date().toISOString();
|
|
1224
|
+
const logEntry = `[${timestamp}] STOP_FAILURE: reason=${stopReason} error=${errorMessage || '(none)'} agent=${agentId}\n`;
|
|
1225
|
+
try {
|
|
1226
|
+
fs.appendFileSync(autopilotLogPath, logEntry, 'utf-8');
|
|
1227
|
+
logged = true;
|
|
1228
|
+
} catch (writeErr) {
|
|
1229
|
+
process.stderr.write(
|
|
1230
|
+
`[grd] WARNING: failed to write StopFailure to autopilot.log: ${(writeErr as Error).message}\n`
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
const result = {
|
|
1236
|
+
ok: true,
|
|
1237
|
+
hook: 'StopFailure',
|
|
1238
|
+
stop_reason: stopReason,
|
|
1239
|
+
error_message: errorMessage,
|
|
1240
|
+
agent_id: agentId,
|
|
1241
|
+
logged,
|
|
1242
|
+
};
|
|
1243
|
+
|
|
1244
|
+
if (raw) {
|
|
1245
|
+
process.stdout.write(`StopFailure: reason=${stopReason} agent=${agentId} logged=${logged}\n`);
|
|
1246
|
+
} else {
|
|
1247
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
/**
|
|
1252
|
+
* Hook handler for PostCompact events (Claude Code v2.1.76+).
|
|
1253
|
+
*
|
|
1254
|
+
* Fires after context compaction completes. GRD acknowledges the event and
|
|
1255
|
+
* continues — this hook is informational only. Future use: could trigger
|
|
1256
|
+
* context reload or state refresh to account for compacted history.
|
|
1257
|
+
*
|
|
1258
|
+
* Environment variables from hook payload:
|
|
1259
|
+
* - AGENT_ID: unique identifier of the agent (optional)
|
|
1260
|
+
* - AGENT_TYPE: type of the agent (optional)
|
|
1261
|
+
*
|
|
1262
|
+
* @param _cwd - Project working directory (unused — compaction is informational)
|
|
1263
|
+
* @param raw - If true, output raw text instead of JSON
|
|
1264
|
+
* @returns void (outputs JSON or raw text to stdout)
|
|
1265
|
+
*/
|
|
1266
|
+
function cmdPostCompactHook(_cwd: string, raw: boolean): void {
|
|
1267
|
+
const agentId = process.env.AGENT_ID || 'unknown';
|
|
1268
|
+
const agentType = process.env.AGENT_TYPE || 'unknown';
|
|
1269
|
+
|
|
1270
|
+
const result = {
|
|
1271
|
+
ok: true,
|
|
1272
|
+
hook: 'PostCompact',
|
|
1273
|
+
agent_id: agentId,
|
|
1274
|
+
agent_type: agentType,
|
|
1275
|
+
acknowledged: true,
|
|
1276
|
+
};
|
|
1277
|
+
|
|
1278
|
+
if (raw) {
|
|
1279
|
+
process.stdout.write(`PostCompact: agent=${agentId} type=${agentType} acknowledged=true\n`);
|
|
1280
|
+
} else {
|
|
1281
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// ─── Exports ──────────────────────────────────────────────────────────────────
|
|
1286
|
+
|
|
1287
|
+
module.exports = {
|
|
1288
|
+
worktreePath,
|
|
1289
|
+
worktreeBranch,
|
|
1290
|
+
ensureWorktreesDir,
|
|
1291
|
+
createEvolveWorktree,
|
|
1292
|
+
removeEvolveWorktree,
|
|
1293
|
+
pushAndCreatePR,
|
|
1294
|
+
cmdWorktreeCreate,
|
|
1295
|
+
cmdWorktreeRemove,
|
|
1296
|
+
cmdWorktreeList,
|
|
1297
|
+
cmdWorktreeRemoveStale,
|
|
1298
|
+
cmdWorktreePushAndPR,
|
|
1299
|
+
milestoneBranch,
|
|
1300
|
+
cmdWorktreeEnsureMilestoneBranch,
|
|
1301
|
+
cmdWorktreeMerge,
|
|
1302
|
+
cmdWorktreeHookCreate,
|
|
1303
|
+
cmdWorktreeHookRemove,
|
|
1304
|
+
cmdTeammateIdleHook,
|
|
1305
|
+
cmdTaskCompletedHook,
|
|
1306
|
+
cmdInstructionsLoadedHook,
|
|
1307
|
+
cmdStopFailureHook,
|
|
1308
|
+
cmdPostCompactHook,
|
|
1309
|
+
};
|