@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/tracker.ts
ADDED
|
@@ -0,0 +1,1591 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GRD Tracker Integration -- Issue tracker sync (GitHub/Jira) and mapping
|
|
5
|
+
*
|
|
6
|
+
* Extracted from bin/grd-tools.js during Phase 3 modularization.
|
|
7
|
+
* Handles: tracker config, mapping, schedule, GitHub sync, cmdTracker dispatch.
|
|
8
|
+
*
|
|
9
|
+
* Depends on: lib/utils.ts (fs, path, execFileSync, safeReadFile, safeReadMarkdown, stripShippedSections, loadConfig, output, error)
|
|
10
|
+
* lib/roadmap.ts (computeSchedule, getScheduleForPhase, getScheduleForMilestone)
|
|
11
|
+
* lib/paths.ts (phasesDir)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
import type { GrdConfig } from './types';
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
fs,
|
|
19
|
+
path,
|
|
20
|
+
execFileSync,
|
|
21
|
+
safeReadFile,
|
|
22
|
+
safeReadMarkdown,
|
|
23
|
+
stripShippedSections,
|
|
24
|
+
loadConfig,
|
|
25
|
+
output,
|
|
26
|
+
error,
|
|
27
|
+
}: {
|
|
28
|
+
fs: typeof import('fs');
|
|
29
|
+
path: typeof import('path');
|
|
30
|
+
execFileSync: typeof import('child_process').execFileSync;
|
|
31
|
+
safeReadFile: (filePath: string) => string | null;
|
|
32
|
+
safeReadMarkdown: (filePath: string) => string | null;
|
|
33
|
+
stripShippedSections: (content: string) => string;
|
|
34
|
+
loadConfig: (cwd: string) => GrdConfig;
|
|
35
|
+
output: (result: unknown, raw: boolean, rawValue?: unknown) => never;
|
|
36
|
+
error: (message: string) => never;
|
|
37
|
+
} = require('./utils');
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
computeSchedule,
|
|
41
|
+
getScheduleForPhase,
|
|
42
|
+
getScheduleForMilestone,
|
|
43
|
+
}: {
|
|
44
|
+
computeSchedule: (cwd: string) => ScheduleResult;
|
|
45
|
+
getScheduleForPhase: (
|
|
46
|
+
schedule: ScheduleResult,
|
|
47
|
+
phaseNum: string | number
|
|
48
|
+
) => PhaseScheduleEntry | null;
|
|
49
|
+
getScheduleForMilestone: (schedule: ScheduleResult, version: string) => ParsedMilestone | null;
|
|
50
|
+
} = require('./roadmap');
|
|
51
|
+
|
|
52
|
+
const {
|
|
53
|
+
phasesDir: getPhasesDirPath,
|
|
54
|
+
}: {
|
|
55
|
+
phasesDir: (cwd: string, milestone?: string | null) => string;
|
|
56
|
+
} = require('./paths');
|
|
57
|
+
|
|
58
|
+
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Tracker provider identifier.
|
|
62
|
+
*/
|
|
63
|
+
type TrackerProvider = 'github' | 'mcp-atlassian' | 'none';
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* GitHub-specific tracker configuration.
|
|
67
|
+
*/
|
|
68
|
+
interface GitHubConfig {
|
|
69
|
+
project_board: string;
|
|
70
|
+
default_assignee: string;
|
|
71
|
+
default_labels: string[];
|
|
72
|
+
auto_issues: boolean;
|
|
73
|
+
pr_per_phase: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* MCP Atlassian (Jira) specific tracker configuration.
|
|
78
|
+
*/
|
|
79
|
+
interface McpAtlassianConfig {
|
|
80
|
+
project_key: string;
|
|
81
|
+
milestone_issue_type: string;
|
|
82
|
+
phase_issue_type: string;
|
|
83
|
+
plan_issue_type: string;
|
|
84
|
+
start_date_field?: string;
|
|
85
|
+
epic_issue_type?: string;
|
|
86
|
+
task_issue_type?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Full tracker configuration from config.json.
|
|
91
|
+
*/
|
|
92
|
+
interface TrackerConfig {
|
|
93
|
+
provider: TrackerProvider;
|
|
94
|
+
auto_sync?: boolean;
|
|
95
|
+
github?: GitHubConfig;
|
|
96
|
+
mcp_atlassian?: McpAtlassianConfig;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* A milestone entry in the tracker mapping.
|
|
101
|
+
*/
|
|
102
|
+
interface MilestoneMapping {
|
|
103
|
+
issueRef: string;
|
|
104
|
+
url: string;
|
|
105
|
+
status: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* A phase entry in the tracker mapping.
|
|
110
|
+
*/
|
|
111
|
+
interface PhaseMapping {
|
|
112
|
+
issueRef: string;
|
|
113
|
+
url: string;
|
|
114
|
+
parentRef: string;
|
|
115
|
+
status: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* A plan entry in the tracker mapping.
|
|
120
|
+
*/
|
|
121
|
+
interface PlanMapping {
|
|
122
|
+
issueRef: string;
|
|
123
|
+
url: string;
|
|
124
|
+
parentRef: string;
|
|
125
|
+
status: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Full tracker mapping loaded from TRACKER.md.
|
|
130
|
+
*/
|
|
131
|
+
interface TrackerMapping {
|
|
132
|
+
provider: string | null;
|
|
133
|
+
last_synced: string | null;
|
|
134
|
+
milestones: Record<string, MilestoneMapping>;
|
|
135
|
+
phases: Record<string, PhaseMapping>;
|
|
136
|
+
plans: Record<string, PlanMapping>;
|
|
137
|
+
_trackerIndex: Map<string, MilestoneMapping | PhaseMapping | PlanMapping>;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Result of creating a GitHub issue.
|
|
142
|
+
*/
|
|
143
|
+
interface IssueCreateResult {
|
|
144
|
+
issueRef: string | null;
|
|
145
|
+
url: string | null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Result of updating an issue's status.
|
|
150
|
+
*/
|
|
151
|
+
interface StatusUpdateResult {
|
|
152
|
+
success: boolean;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Statistics from a sync operation.
|
|
157
|
+
*/
|
|
158
|
+
interface SyncStats {
|
|
159
|
+
created: number;
|
|
160
|
+
updated: number;
|
|
161
|
+
skipped?: number;
|
|
162
|
+
errors: number;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* GitHub tracker interface -- returned by createGitHubTracker.
|
|
167
|
+
*/
|
|
168
|
+
interface GitHubTracker {
|
|
169
|
+
provider: string;
|
|
170
|
+
createPhaseIssue: (
|
|
171
|
+
phaseNum: string | number,
|
|
172
|
+
title: string,
|
|
173
|
+
body: string,
|
|
174
|
+
labels?: string[]
|
|
175
|
+
) => IssueCreateResult;
|
|
176
|
+
createTaskIssue: (
|
|
177
|
+
phaseNum: string | number,
|
|
178
|
+
planNum: string | number,
|
|
179
|
+
title: string,
|
|
180
|
+
parentRef: string | null
|
|
181
|
+
) => IssueCreateResult;
|
|
182
|
+
updateIssueStatus: (issueRef: string, status: string) => StatusUpdateResult;
|
|
183
|
+
addComment: (issueRef: string, markdownBody: string) => StatusUpdateResult;
|
|
184
|
+
syncRoadmap: (roadmapData: { phases: RoadmapPhaseInput[] }) => SyncStats;
|
|
185
|
+
syncPhase: (phaseNum: string | number, phaseData: { plans: PlanInput[] }) => SyncStats;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Parsed milestone from roadmap.ts schedule computation.
|
|
190
|
+
*/
|
|
191
|
+
interface ParsedMilestone {
|
|
192
|
+
version: string;
|
|
193
|
+
heading: string;
|
|
194
|
+
start: string | null;
|
|
195
|
+
target: string | null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Phase schedule entry from roadmap.ts schedule computation.
|
|
200
|
+
*/
|
|
201
|
+
interface PhaseScheduleEntry {
|
|
202
|
+
number: string;
|
|
203
|
+
name: string;
|
|
204
|
+
duration_days: number;
|
|
205
|
+
milestone: string | null;
|
|
206
|
+
start_date: string | null;
|
|
207
|
+
due_date: string | null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Schedule result from computeSchedule.
|
|
212
|
+
*/
|
|
213
|
+
interface ScheduleResult {
|
|
214
|
+
milestones: ParsedMilestone[];
|
|
215
|
+
phases: PhaseScheduleEntry[];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Input phase shape for roadmap sync.
|
|
220
|
+
*/
|
|
221
|
+
interface RoadmapPhaseInput {
|
|
222
|
+
number: string;
|
|
223
|
+
name: string;
|
|
224
|
+
goal?: string;
|
|
225
|
+
labels?: string[];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Input plan shape for phase sync.
|
|
230
|
+
*/
|
|
231
|
+
interface PlanInput {
|
|
232
|
+
number: string;
|
|
233
|
+
objective?: string;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Milestone position for prepare-roadmap-sync parsing.
|
|
238
|
+
*/
|
|
239
|
+
interface MilestonePosition {
|
|
240
|
+
heading: string;
|
|
241
|
+
version: string;
|
|
242
|
+
index: number;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Phase position for prepare-roadmap-sync parsing.
|
|
247
|
+
*/
|
|
248
|
+
interface PhasePosition {
|
|
249
|
+
number: string;
|
|
250
|
+
name: string;
|
|
251
|
+
goal: string;
|
|
252
|
+
milestone: string | null;
|
|
253
|
+
index: number;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* A sync operation entry (create or skip).
|
|
258
|
+
*/
|
|
259
|
+
interface SyncOperation {
|
|
260
|
+
action: 'create' | 'skip' | 'update';
|
|
261
|
+
type: 'milestone' | 'phase' | 'plan';
|
|
262
|
+
milestone?: string;
|
|
263
|
+
phase?: string;
|
|
264
|
+
plan?: string;
|
|
265
|
+
issue_key?: string;
|
|
266
|
+
reason?: string;
|
|
267
|
+
summary?: string;
|
|
268
|
+
description?: string;
|
|
269
|
+
parent_key?: string | null;
|
|
270
|
+
start_date?: string;
|
|
271
|
+
due_date?: string;
|
|
272
|
+
duration_days?: number;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Tracker mapping cache entry.
|
|
277
|
+
*/
|
|
278
|
+
interface TrackerMappingCacheEntry {
|
|
279
|
+
mtime: number | null;
|
|
280
|
+
mapping: TrackerMapping;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Legacy github_integration config shape for backward-compat migration.
|
|
285
|
+
*/
|
|
286
|
+
interface LegacyGitHubIntegration {
|
|
287
|
+
enabled?: boolean;
|
|
288
|
+
auto_issues?: boolean;
|
|
289
|
+
project_board?: string;
|
|
290
|
+
project_name?: string;
|
|
291
|
+
default_assignee?: string;
|
|
292
|
+
default_labels?: string[];
|
|
293
|
+
labels?: Record<string, string>;
|
|
294
|
+
pr_per_phase?: boolean;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Raw config.json structure for tracker parsing.
|
|
299
|
+
*/
|
|
300
|
+
interface RawConfig {
|
|
301
|
+
tracker?: Record<string, unknown>;
|
|
302
|
+
github_integration?: LegacyGitHubIntegration;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ─── Tracker Config & Mapping ─────────────────────────────────────────────────
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Load tracker configuration from config.json, with auto-migration of legacy formats.
|
|
309
|
+
* @param cwd - Project working directory
|
|
310
|
+
* @returns Tracker config object with provider field ('github', 'mcp-atlassian', or 'none')
|
|
311
|
+
*/
|
|
312
|
+
function loadTrackerConfig(cwd: string): TrackerConfig {
|
|
313
|
+
const configPath: string = path.join(cwd, '.planning', 'config.json');
|
|
314
|
+
const raw: string | null = safeReadFile(configPath);
|
|
315
|
+
if (!raw) return { provider: 'none' };
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
const config: RawConfig = JSON.parse(raw) as RawConfig;
|
|
319
|
+
// New tracker config format
|
|
320
|
+
if (config.tracker) {
|
|
321
|
+
const tracker = config.tracker as Record<string, unknown>;
|
|
322
|
+
// Auto-migrate old "jira" provider to "mcp-atlassian"
|
|
323
|
+
if (tracker.provider === 'jira') {
|
|
324
|
+
tracker.provider = 'mcp-atlassian';
|
|
325
|
+
if (tracker.jira && !tracker.mcp_atlassian) {
|
|
326
|
+
const jira = tracker.jira as Record<string, unknown>;
|
|
327
|
+
tracker.mcp_atlassian = {
|
|
328
|
+
project_key: (jira.project_key as string) || '',
|
|
329
|
+
milestone_issue_type: (jira.epic_issue_type as string) || 'Epic',
|
|
330
|
+
phase_issue_type: (jira.task_issue_type as string) || 'Task',
|
|
331
|
+
plan_issue_type: 'Sub-task',
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// Auto-migrate old epic/task config to milestone/phase/plan config
|
|
336
|
+
if (tracker.mcp_atlassian) {
|
|
337
|
+
const mcp = tracker.mcp_atlassian as Record<string, unknown>;
|
|
338
|
+
if (mcp.epic_issue_type && !mcp.milestone_issue_type) {
|
|
339
|
+
mcp.milestone_issue_type = mcp.epic_issue_type;
|
|
340
|
+
delete mcp.epic_issue_type;
|
|
341
|
+
}
|
|
342
|
+
if (mcp.task_issue_type && !mcp.phase_issue_type) {
|
|
343
|
+
mcp.phase_issue_type = mcp.task_issue_type;
|
|
344
|
+
delete mcp.task_issue_type;
|
|
345
|
+
}
|
|
346
|
+
if (!mcp.plan_issue_type) {
|
|
347
|
+
mcp.plan_issue_type = 'Sub-task';
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// Validate required fields for github provider
|
|
351
|
+
if (tracker.provider === 'github' && tracker.github) {
|
|
352
|
+
const gh = tracker.github as Record<string, unknown>;
|
|
353
|
+
if (!gh.project_board) {
|
|
354
|
+
process.stderr.write('Warning: github tracker missing required field: project_board\n');
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return tracker as unknown as TrackerConfig;
|
|
358
|
+
}
|
|
359
|
+
// Backward compat: migrate old github_integration format
|
|
360
|
+
if (config.github_integration && config.github_integration.enabled) {
|
|
361
|
+
const gi = config.github_integration;
|
|
362
|
+
return {
|
|
363
|
+
provider: 'github',
|
|
364
|
+
auto_sync: gi.auto_issues || false,
|
|
365
|
+
github: {
|
|
366
|
+
project_board: gi.project_board || gi.project_name || '',
|
|
367
|
+
default_assignee: gi.default_assignee || '',
|
|
368
|
+
default_labels:
|
|
369
|
+
gi.default_labels || gi.labels
|
|
370
|
+
? Object.values(gi.labels || {})
|
|
371
|
+
: ['research', 'implementation', 'evaluation', 'integration'],
|
|
372
|
+
auto_issues: gi.auto_issues || false,
|
|
373
|
+
pr_per_phase: gi.pr_per_phase || false,
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
return { provider: 'none' };
|
|
378
|
+
} catch (e) {
|
|
379
|
+
process.stderr.write('Warning: failed to parse config.json: ' + (e as Error).message + '\n');
|
|
380
|
+
return { provider: 'none' };
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Cache for loadTrackerMapping: keyed by cwd, stores { mtime, mapping, index }
|
|
385
|
+
const _trackerMappingCache = new Map<string, TrackerMappingCacheEntry>();
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Split a markdown table row into column strings (preserves empty cells).
|
|
389
|
+
* @param row - A single markdown table row string
|
|
390
|
+
* @returns Array of trimmed cell values
|
|
391
|
+
*/
|
|
392
|
+
function _splitTableRow(row: string): string[] {
|
|
393
|
+
const parts: string[] = row.split('|').map((c: string) => c.trim());
|
|
394
|
+
if (parts.length > 0 && parts[0] === '') parts.shift();
|
|
395
|
+
if (parts.length > 0 && parts[parts.length - 1] === '') parts.pop();
|
|
396
|
+
return parts;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Build a Map index over a loaded mapping for O(1) lookups.
|
|
401
|
+
* Keys: milestone version strings, phase number strings, and "phase-plan" composite strings.
|
|
402
|
+
* @param mapping - Parsed mapping object with milestones, phases, and plans
|
|
403
|
+
* @returns Index map keyed by identifier
|
|
404
|
+
*/
|
|
405
|
+
function _buildTrackerIndex(
|
|
406
|
+
mapping: TrackerMapping
|
|
407
|
+
): Map<string, MilestoneMapping | PhaseMapping | PlanMapping> {
|
|
408
|
+
const index = new Map<string, MilestoneMapping | PhaseMapping | PlanMapping>();
|
|
409
|
+
for (const [k, v] of Object.entries(mapping.milestones || {})) index.set(k, v);
|
|
410
|
+
for (const [k, v] of Object.entries(mapping.phases || {})) index.set(k, v);
|
|
411
|
+
for (const [k, v] of Object.entries(mapping.plans || {})) index.set(k, v);
|
|
412
|
+
return index;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Load the tracker ID mapping from TRACKER.md, parsing milestone/phase/plan tables.
|
|
417
|
+
* Results are cached in memory; the cache is invalidated when the file's mtime changes.
|
|
418
|
+
* A Map index (_trackerIndex) is attached to the returned object for O(1) lookups.
|
|
419
|
+
* @param cwd - Project working directory
|
|
420
|
+
* @returns Mapping object with provider, last_synced, milestones, phases, plans, and _trackerIndex
|
|
421
|
+
*/
|
|
422
|
+
function loadTrackerMapping(cwd: string): TrackerMapping {
|
|
423
|
+
const mappingPath: string = path.join(cwd, '.planning', 'TRACKER.md');
|
|
424
|
+
|
|
425
|
+
// Check mtime for cache invalidation
|
|
426
|
+
let mtime: number | null = null;
|
|
427
|
+
try {
|
|
428
|
+
mtime = fs.statSync(mappingPath).mtimeMs;
|
|
429
|
+
} catch {
|
|
430
|
+
/* file does not exist */
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const cached: TrackerMappingCacheEntry | undefined = _trackerMappingCache.get(cwd);
|
|
434
|
+
if (cached && cached.mtime === mtime && mtime !== null) {
|
|
435
|
+
return cached.mapping;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const content: string | null = safeReadFile(mappingPath);
|
|
439
|
+
if (!content) {
|
|
440
|
+
const empty: TrackerMapping = {
|
|
441
|
+
provider: null,
|
|
442
|
+
last_synced: null,
|
|
443
|
+
milestones: {},
|
|
444
|
+
phases: {},
|
|
445
|
+
plans: {},
|
|
446
|
+
_trackerIndex: new Map<string, MilestoneMapping | PhaseMapping | PlanMapping>(),
|
|
447
|
+
};
|
|
448
|
+
// Cache the empty result keyed on null mtime so repeated misses are cheap
|
|
449
|
+
_trackerMappingCache.set(cwd, { mtime: null, mapping: empty });
|
|
450
|
+
return empty;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const result: TrackerMapping = {
|
|
454
|
+
provider: null,
|
|
455
|
+
last_synced: null,
|
|
456
|
+
milestones: {},
|
|
457
|
+
phases: {},
|
|
458
|
+
plans: {},
|
|
459
|
+
_trackerIndex: new Map<string, MilestoneMapping | PhaseMapping | PlanMapping>(),
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
const providerMatch: RegExpMatchArray | null = content.match(/^Provider:\s*(.+)$/m);
|
|
463
|
+
if (providerMatch) result.provider = providerMatch[1].trim();
|
|
464
|
+
|
|
465
|
+
const syncMatch: RegExpMatchArray | null = content.match(/^Last Synced:\s*(.+)$/m);
|
|
466
|
+
if (syncMatch) result.last_synced = syncMatch[1].trim();
|
|
467
|
+
|
|
468
|
+
// Parse milestone table (Epics) -- handles optional blank line between heading and table
|
|
469
|
+
const milestoneTableMatch: RegExpMatchArray | null = content.match(
|
|
470
|
+
/## Milestone Issues\n\n?\|[^\n]+\n\|[^\n]+\n([\s\S]*?)(?=\n##|\n$|$)/
|
|
471
|
+
);
|
|
472
|
+
if (milestoneTableMatch) {
|
|
473
|
+
const rows: string[] = milestoneTableMatch[1]
|
|
474
|
+
.trim()
|
|
475
|
+
.split('\n')
|
|
476
|
+
.filter((r: string) => r.startsWith('|'));
|
|
477
|
+
for (const row of rows) {
|
|
478
|
+
const cols: string[] = _splitTableRow(row);
|
|
479
|
+
if (cols.length >= 4) {
|
|
480
|
+
result.milestones[cols[0]] = {
|
|
481
|
+
issueRef: cols[1],
|
|
482
|
+
url: cols[2],
|
|
483
|
+
status: cols[3],
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Parse phase table (Tasks) -- handles optional blank line between heading and table
|
|
490
|
+
const phaseTableMatch: RegExpMatchArray | null = content.match(
|
|
491
|
+
/## Phase Issues\n\n?\|[^\n]+\n\|[^\n]+\n([\s\S]*?)(?=\n##|\n$|$)/
|
|
492
|
+
);
|
|
493
|
+
if (phaseTableMatch) {
|
|
494
|
+
const rows: string[] = phaseTableMatch[1]
|
|
495
|
+
.trim()
|
|
496
|
+
.split('\n')
|
|
497
|
+
.filter((r: string) => r.startsWith('|'));
|
|
498
|
+
for (const row of rows) {
|
|
499
|
+
const cols: string[] = _splitTableRow(row);
|
|
500
|
+
if (cols.length >= 5) {
|
|
501
|
+
result.phases[cols[0]] = {
|
|
502
|
+
issueRef: cols[1],
|
|
503
|
+
url: cols[2],
|
|
504
|
+
parentRef: cols[3],
|
|
505
|
+
status: cols[4],
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Parse plan table (Sub-tasks) -- handles optional blank line between heading and table
|
|
512
|
+
const planTableMatch: RegExpMatchArray | null = content.match(
|
|
513
|
+
/## Plan Issues\n\n?\|[^\n]+\n\|[^\n]+\n([\s\S]*?)(?=\n##|\n$|$)/
|
|
514
|
+
);
|
|
515
|
+
if (planTableMatch) {
|
|
516
|
+
const rows: string[] = planTableMatch[1]
|
|
517
|
+
.trim()
|
|
518
|
+
.split('\n')
|
|
519
|
+
.filter((r: string) => r.startsWith('|'));
|
|
520
|
+
for (const row of rows) {
|
|
521
|
+
const cols: string[] = _splitTableRow(row);
|
|
522
|
+
if (cols.length >= 6) {
|
|
523
|
+
const key: string = `${cols[0]}-${cols[1]}`;
|
|
524
|
+
result.plans[key] = {
|
|
525
|
+
issueRef: cols[2],
|
|
526
|
+
url: cols[3],
|
|
527
|
+
parentRef: cols[4],
|
|
528
|
+
status: cols[5],
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Attach O(1) lookup index
|
|
535
|
+
result._trackerIndex = _buildTrackerIndex(result);
|
|
536
|
+
|
|
537
|
+
_trackerMappingCache.set(cwd, { mtime, mapping: result });
|
|
538
|
+
return result;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Save the tracker ID mapping to TRACKER.md with formatted markdown tables.
|
|
543
|
+
* @param cwd - Project working directory
|
|
544
|
+
* @param mapping - Mapping object with provider, milestones, phases, and plans entries
|
|
545
|
+
*/
|
|
546
|
+
function saveTrackerMapping(cwd: string, mapping: TrackerMapping): void {
|
|
547
|
+
const mappingPath: string = path.join(cwd, '.planning', 'TRACKER.md');
|
|
548
|
+
const timestamp: string = new Date()
|
|
549
|
+
.toISOString()
|
|
550
|
+
.replace('T', ' ')
|
|
551
|
+
.replace(/\.\d+Z$/, ' UTC');
|
|
552
|
+
|
|
553
|
+
let content: string = `# Tracker Mapping\n\nProvider: ${mapping.provider || 'none'}\nLast Synced: ${timestamp}\n\n`;
|
|
554
|
+
|
|
555
|
+
content += `## Milestone Issues\n\n| Milestone | Issue Ref | URL | Status |\n|-----------|-----------|-----|--------|\n`;
|
|
556
|
+
for (const [milestone, info] of Object.entries(mapping.milestones || {}) as [
|
|
557
|
+
string,
|
|
558
|
+
MilestoneMapping,
|
|
559
|
+
][]) {
|
|
560
|
+
content += `| ${milestone} | ${info.issueRef} | ${info.url} | ${info.status} |\n`;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
content += `\n## Phase Issues\n\n| Phase | Issue Ref | URL | Parent Ref | Status |\n|-------|-----------|-----|------------|--------|\n`;
|
|
564
|
+
for (const [phase, info] of Object.entries(mapping.phases || {}) as [string, PhaseMapping][]) {
|
|
565
|
+
content += `| ${phase} | ${info.issueRef} | ${info.url} | ${info.parentRef || ''} | ${info.status} |\n`;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
content += `\n## Plan Issues\n\n| Phase | Plan | Issue Ref | URL | Parent Ref | Status |\n|-------|------|-----------|-----|------------|--------|\n`;
|
|
569
|
+
for (const [key, info] of Object.entries(mapping.plans || {}) as [string, PlanMapping][]) {
|
|
570
|
+
const [phase, plan] = key.split('-');
|
|
571
|
+
content += `| ${phase} | ${plan} | ${info.issueRef} | ${info.url} | ${info.parentRef} | ${info.status} |\n`;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const planningDir: string = path.join(cwd, '.planning');
|
|
575
|
+
if (!fs.existsSync(planningDir)) fs.mkdirSync(planningDir, { recursive: true });
|
|
576
|
+
fs.writeFileSync(mappingPath, content, 'utf-8');
|
|
577
|
+
// Invalidate cache so the next loadTrackerMapping call re-reads the updated file
|
|
578
|
+
_trackerMappingCache.delete(cwd);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// ─── GitHub Tracker ───────────────────────────────────────────────────────────
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Create a GitHub Issues tracker operations object with methods for issue CRUD.
|
|
585
|
+
* @param cwd - Project working directory
|
|
586
|
+
* @param config - Tracker config with github sub-object for labels, assignees, etc.
|
|
587
|
+
* @returns Tracker object with createPhaseIssue, createTaskIssue, updateIssueStatus, addComment, syncRoadmap, syncPhase methods
|
|
588
|
+
*/
|
|
589
|
+
function createGitHubTracker(cwd: string, config: TrackerConfig): GitHubTracker {
|
|
590
|
+
const gh: GitHubConfig = config.github || ({} as GitHubConfig);
|
|
591
|
+
const mainConfig: GrdConfig = loadConfig(cwd);
|
|
592
|
+
|
|
593
|
+
// NOTE: execFileSync is already the safe alternative (no shell injection).
|
|
594
|
+
// This is used for gh CLI calls which require direct process execution.
|
|
595
|
+
function ghExec(args: string[]): string | null {
|
|
596
|
+
try {
|
|
597
|
+
return (
|
|
598
|
+
execFileSync('gh', args, {
|
|
599
|
+
cwd,
|
|
600
|
+
encoding: 'utf-8',
|
|
601
|
+
timeout: mainConfig.timeouts.tracker_gh_ms,
|
|
602
|
+
stdio: 'pipe',
|
|
603
|
+
}) as string
|
|
604
|
+
).trim();
|
|
605
|
+
} catch {
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function ghAvailable(): boolean {
|
|
611
|
+
return ghExec(['--version']) !== null;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return {
|
|
615
|
+
provider: 'github',
|
|
616
|
+
|
|
617
|
+
createPhaseIssue(
|
|
618
|
+
_phaseNum: string | number,
|
|
619
|
+
title: string,
|
|
620
|
+
body: string,
|
|
621
|
+
labels?: string[]
|
|
622
|
+
): IssueCreateResult {
|
|
623
|
+
if (!ghAvailable()) return { issueRef: null, url: null };
|
|
624
|
+
const args: string[] = [
|
|
625
|
+
'issue',
|
|
626
|
+
'create',
|
|
627
|
+
'--title',
|
|
628
|
+
title,
|
|
629
|
+
'--body',
|
|
630
|
+
body || '',
|
|
631
|
+
'--label',
|
|
632
|
+
'epic',
|
|
633
|
+
];
|
|
634
|
+
for (const l of labels || gh.default_labels || []) {
|
|
635
|
+
args.push('--label', l);
|
|
636
|
+
}
|
|
637
|
+
if (gh.default_assignee) {
|
|
638
|
+
args.push('--assignee', gh.default_assignee);
|
|
639
|
+
}
|
|
640
|
+
const url: string | null = ghExec(args);
|
|
641
|
+
if (!url) return { issueRef: null, url: null };
|
|
642
|
+
const issueRef: string = url.match(/\/(\d+)$/)?.[1] || url;
|
|
643
|
+
return { issueRef: `#${issueRef}`, url };
|
|
644
|
+
},
|
|
645
|
+
|
|
646
|
+
createTaskIssue(
|
|
647
|
+
phaseNum: string | number,
|
|
648
|
+
planNum: string | number,
|
|
649
|
+
title: string,
|
|
650
|
+
parentRef: string | null
|
|
651
|
+
): IssueCreateResult {
|
|
652
|
+
if (!ghAvailable()) return { issueRef: null, url: null };
|
|
653
|
+
const bodyText: string = `Parent: ${parentRef}\nPhase: ${phaseNum}\nPlan: ${planNum}`;
|
|
654
|
+
const url: string | null = ghExec([
|
|
655
|
+
'issue',
|
|
656
|
+
'create',
|
|
657
|
+
'--title',
|
|
658
|
+
title,
|
|
659
|
+
'--body',
|
|
660
|
+
bodyText,
|
|
661
|
+
'--label',
|
|
662
|
+
'task',
|
|
663
|
+
]);
|
|
664
|
+
if (!url) return { issueRef: null, url: null };
|
|
665
|
+
const issueRef: string = url.match(/\/(\d+)$/)?.[1] || url;
|
|
666
|
+
// Try to link as sub-issue
|
|
667
|
+
if (parentRef) {
|
|
668
|
+
const parentNum: string = parentRef.replace('#', '');
|
|
669
|
+
ghExec(['sub-issue', 'add', parentNum, '--child', issueRef]);
|
|
670
|
+
}
|
|
671
|
+
return { issueRef: `#${issueRef}`, url };
|
|
672
|
+
},
|
|
673
|
+
|
|
674
|
+
updateIssueStatus(issueRef: string, status: string): StatusUpdateResult {
|
|
675
|
+
if (!ghAvailable()) return { success: false };
|
|
676
|
+
const num: string = String(issueRef).replace('#', '');
|
|
677
|
+
const statusLabels: Record<string, string> = {
|
|
678
|
+
pending: 'status:todo',
|
|
679
|
+
in_progress: 'status:in-progress',
|
|
680
|
+
complete: 'status:done',
|
|
681
|
+
};
|
|
682
|
+
const label: string | undefined = statusLabels[status];
|
|
683
|
+
if (label) {
|
|
684
|
+
// Remove other status labels, add new one
|
|
685
|
+
for (const sl of Object.values(statusLabels)) {
|
|
686
|
+
ghExec(['issue', 'edit', num, '--remove-label', sl]);
|
|
687
|
+
}
|
|
688
|
+
ghExec(['issue', 'edit', num, '--add-label', label]);
|
|
689
|
+
}
|
|
690
|
+
if (status === 'complete') {
|
|
691
|
+
ghExec(['issue', 'close', num]);
|
|
692
|
+
}
|
|
693
|
+
return { success: true };
|
|
694
|
+
},
|
|
695
|
+
|
|
696
|
+
addComment(issueRef: string, markdownBody: string): StatusUpdateResult {
|
|
697
|
+
if (!ghAvailable()) return { success: false };
|
|
698
|
+
const num: string = String(issueRef).replace('#', '');
|
|
699
|
+
ghExec(['issue', 'comment', num, '--body', markdownBody]);
|
|
700
|
+
return { success: true };
|
|
701
|
+
},
|
|
702
|
+
|
|
703
|
+
syncRoadmap(roadmapData: { phases: RoadmapPhaseInput[] }): SyncStats {
|
|
704
|
+
const mapping: TrackerMapping = loadTrackerMapping(cwd);
|
|
705
|
+
mapping.provider = 'github';
|
|
706
|
+
const stats: SyncStats = {
|
|
707
|
+
created: 0,
|
|
708
|
+
updated: 0,
|
|
709
|
+
skipped: 0,
|
|
710
|
+
errors: 0,
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
for (const phase of roadmapData.phases || []) {
|
|
714
|
+
const key: string = String(phase.number);
|
|
715
|
+
if (mapping.phases[key]) {
|
|
716
|
+
stats.skipped = (stats.skipped || 0) + 1;
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
const result: IssueCreateResult = this.createPhaseIssue(
|
|
720
|
+
phase.number,
|
|
721
|
+
`Phase ${phase.number}: ${phase.name}`,
|
|
722
|
+
phase.goal || '',
|
|
723
|
+
phase.labels || []
|
|
724
|
+
);
|
|
725
|
+
if (result.issueRef) {
|
|
726
|
+
mapping.phases[key] = {
|
|
727
|
+
issueRef: result.issueRef,
|
|
728
|
+
url: result.url || '',
|
|
729
|
+
parentRef: '',
|
|
730
|
+
status: 'pending',
|
|
731
|
+
};
|
|
732
|
+
stats.created++;
|
|
733
|
+
} else {
|
|
734
|
+
stats.errors++;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
saveTrackerMapping(cwd, mapping);
|
|
739
|
+
return stats;
|
|
740
|
+
},
|
|
741
|
+
|
|
742
|
+
syncPhase(phaseNum: string | number, phaseData: { plans: PlanInput[] }): SyncStats {
|
|
743
|
+
const mapping: TrackerMapping = loadTrackerMapping(cwd);
|
|
744
|
+
mapping.provider = 'github';
|
|
745
|
+
const stats: SyncStats = { created: 0, updated: 0, errors: 0 };
|
|
746
|
+
const parentRef: string | null = mapping.phases[String(phaseNum)]?.issueRef || null;
|
|
747
|
+
|
|
748
|
+
for (const plan of phaseData.plans || []) {
|
|
749
|
+
const key: string = `${phaseNum}-${plan.number}`;
|
|
750
|
+
if (mapping.plans[key]) {
|
|
751
|
+
stats.updated++;
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
const result: IssueCreateResult = this.createTaskIssue(
|
|
755
|
+
phaseNum,
|
|
756
|
+
plan.number,
|
|
757
|
+
`Plan ${phaseNum}-${plan.number}: ${plan.objective || ''}`,
|
|
758
|
+
parentRef
|
|
759
|
+
);
|
|
760
|
+
if (result.issueRef) {
|
|
761
|
+
mapping.plans[key] = {
|
|
762
|
+
issueRef: result.issueRef,
|
|
763
|
+
url: result.url || '',
|
|
764
|
+
parentRef: parentRef || '',
|
|
765
|
+
status: 'pending',
|
|
766
|
+
};
|
|
767
|
+
stats.created++;
|
|
768
|
+
} else {
|
|
769
|
+
stats.errors++;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
saveTrackerMapping(cwd, mapping);
|
|
774
|
+
return stats;
|
|
775
|
+
},
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// Note: Jira integration is now handled via mcp-atlassian MCP server.
|
|
780
|
+
// grd-tools.js provides prepare/record commands; Claude agents call MCP tools directly.
|
|
781
|
+
// See references/mcp-tracker-protocol.md for the full protocol.
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Factory function: create a tracker instance based on the configured provider.
|
|
785
|
+
* @param cwd - Project working directory
|
|
786
|
+
* @returns Tracker instance for GitHub, or null for mcp-atlassian/none
|
|
787
|
+
*/
|
|
788
|
+
function createTracker(cwd: string): GitHubTracker | null {
|
|
789
|
+
const config: TrackerConfig = loadTrackerConfig(cwd);
|
|
790
|
+
if (config.provider === 'github') return createGitHubTracker(cwd, config);
|
|
791
|
+
// mcp-atlassian provider is handled by Claude agents via MCP tools, not by grd-tools.js
|
|
792
|
+
return null;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Provider factory map -- maps provider names to factory functions.
|
|
797
|
+
* Each factory takes (cwd, config) and returns a tracker instance.
|
|
798
|
+
*/
|
|
799
|
+
const PROVIDERS: Record<string, (cwd: string, config: TrackerConfig) => GitHubTracker> = {
|
|
800
|
+
github: (cwd: string, config: TrackerConfig) => createGitHubTracker(cwd, config),
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
// ─── Tracker Subcommand Handlers ──────────────────────────────────────────────
|
|
804
|
+
|
|
805
|
+
function handleGetConfig(cwd: string, _args: string[], raw: boolean): void {
|
|
806
|
+
const config: TrackerConfig = loadTrackerConfig(cwd);
|
|
807
|
+
const mainConfig: GrdConfig = loadConfig(cwd);
|
|
808
|
+
let authStatus: string = 'not_configured';
|
|
809
|
+
if (config.provider === 'github') {
|
|
810
|
+
try {
|
|
811
|
+
execFileSync('gh', ['auth', 'status'], {
|
|
812
|
+
cwd,
|
|
813
|
+
encoding: 'utf-8',
|
|
814
|
+
timeout: mainConfig.timeouts.tracker_auth_ms,
|
|
815
|
+
stdio: 'pipe',
|
|
816
|
+
});
|
|
817
|
+
authStatus = 'authenticated';
|
|
818
|
+
} catch {
|
|
819
|
+
authStatus = 'not_authenticated';
|
|
820
|
+
}
|
|
821
|
+
} else if (config.provider === 'mcp-atlassian') {
|
|
822
|
+
authStatus = 'mcp_server';
|
|
823
|
+
}
|
|
824
|
+
output({ ...config, auth_status: authStatus }, raw);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
function handleSyncRoadmap(cwd: string, args: string[], raw: boolean): void {
|
|
828
|
+
const isDryRun: boolean = args.includes('--dry-run');
|
|
829
|
+
const config: TrackerConfig = loadTrackerConfig(cwd);
|
|
830
|
+
if (config.provider === 'mcp-atlassian') {
|
|
831
|
+
output(
|
|
832
|
+
{
|
|
833
|
+
error:
|
|
834
|
+
'Use "tracker prepare-roadmap-sync" for mcp-atlassian provider. Agent executes MCP calls.',
|
|
835
|
+
created: 0,
|
|
836
|
+
updated: 0,
|
|
837
|
+
skipped: 0,
|
|
838
|
+
errors: 0,
|
|
839
|
+
},
|
|
840
|
+
raw
|
|
841
|
+
);
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
const tracker: GitHubTracker | null = createTracker(cwd);
|
|
845
|
+
if (!tracker) {
|
|
846
|
+
output(
|
|
847
|
+
{
|
|
848
|
+
error: 'No tracker configured',
|
|
849
|
+
created: 0,
|
|
850
|
+
updated: 0,
|
|
851
|
+
skipped: 0,
|
|
852
|
+
errors: 0,
|
|
853
|
+
},
|
|
854
|
+
raw
|
|
855
|
+
);
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
const roadmapContent: string | null = safeReadMarkdown(path.join(cwd, '.planning', 'ROADMAP.md'));
|
|
859
|
+
if (!roadmapContent) {
|
|
860
|
+
output(
|
|
861
|
+
{
|
|
862
|
+
error: 'No ROADMAP.md found',
|
|
863
|
+
created: 0,
|
|
864
|
+
updated: 0,
|
|
865
|
+
skipped: 0,
|
|
866
|
+
errors: 0,
|
|
867
|
+
},
|
|
868
|
+
raw
|
|
869
|
+
);
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
const activeContent: string = stripShippedSections(roadmapContent);
|
|
873
|
+
const phases: RoadmapPhaseInput[] = [];
|
|
874
|
+
const phaseRegex: RegExp = /^##\s+Phase\s+(\d+(?:\.\d+)?)\s*[:\-\u2014]\s*(.+)$/gm;
|
|
875
|
+
let match: RegExpExecArray | null;
|
|
876
|
+
while ((match = phaseRegex.exec(activeContent)) !== null) {
|
|
877
|
+
const number: string = match[1];
|
|
878
|
+
const name: string = match[2].trim();
|
|
879
|
+
const afterPhase: string = activeContent.slice(
|
|
880
|
+
match.index + match[0].length,
|
|
881
|
+
match.index + match[0].length + 500
|
|
882
|
+
);
|
|
883
|
+
const goalMatch: RegExpMatchArray | null = afterPhase.match(/(?:\*\*Goal:\*\*|Goal:)\s*(.+)/);
|
|
884
|
+
phases.push({
|
|
885
|
+
number,
|
|
886
|
+
name,
|
|
887
|
+
goal: goalMatch ? goalMatch[1].trim() : '',
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
if (isDryRun) {
|
|
892
|
+
// Dry-run mode: report what would be created/skipped without executing
|
|
893
|
+
const mapping: TrackerMapping = loadTrackerMapping(cwd);
|
|
894
|
+
const wouldCreate: Array<{ number: string; name: string }> = [];
|
|
895
|
+
const wouldSkip: Array<{
|
|
896
|
+
number: string;
|
|
897
|
+
name: string;
|
|
898
|
+
reason: string;
|
|
899
|
+
}> = [];
|
|
900
|
+
for (const p of phases) {
|
|
901
|
+
if (mapping.phases && mapping.phases[p.number]) {
|
|
902
|
+
wouldSkip.push({
|
|
903
|
+
number: p.number,
|
|
904
|
+
name: p.name,
|
|
905
|
+
reason: 'already_mapped',
|
|
906
|
+
});
|
|
907
|
+
} else {
|
|
908
|
+
wouldCreate.push({ number: p.number, name: p.name });
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
output(
|
|
912
|
+
{
|
|
913
|
+
dry_run: true,
|
|
914
|
+
would_create: wouldCreate,
|
|
915
|
+
would_skip: wouldSkip,
|
|
916
|
+
created: 0,
|
|
917
|
+
},
|
|
918
|
+
raw
|
|
919
|
+
);
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
const stats: SyncStats = tracker.syncRoadmap({ phases });
|
|
924
|
+
output(
|
|
925
|
+
stats,
|
|
926
|
+
raw,
|
|
927
|
+
`Roadmap synced: created ${stats.created}, updated ${stats.updated}, errors ${stats.errors}`
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
function handleSyncPhase(cwd: string, args: string[], raw: boolean): void {
|
|
932
|
+
const phaseNum: string | undefined = args[0];
|
|
933
|
+
if (!phaseNum) {
|
|
934
|
+
error(
|
|
935
|
+
'Usage: tracker sync-phase <phase-number>. Example: tracker sync-phase 3. Make sure you are in a GRD project directory and have a tracker configured in .planning/config.json. Run: tracker sync-phase <N> where N is the phase number.'
|
|
936
|
+
);
|
|
937
|
+
return; // unreachable after error() but helps TS narrowing
|
|
938
|
+
}
|
|
939
|
+
const config: TrackerConfig = loadTrackerConfig(cwd);
|
|
940
|
+
if (config.provider === 'mcp-atlassian') {
|
|
941
|
+
output(
|
|
942
|
+
{
|
|
943
|
+
error:
|
|
944
|
+
'Use "tracker prepare-phase-sync" for mcp-atlassian provider. Agent executes MCP calls.',
|
|
945
|
+
created: 0,
|
|
946
|
+
updated: 0,
|
|
947
|
+
errors: 0,
|
|
948
|
+
},
|
|
949
|
+
raw
|
|
950
|
+
);
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
const tracker: GitHubTracker | null = createTracker(cwd);
|
|
954
|
+
if (!tracker) {
|
|
955
|
+
output({ error: 'No tracker configured', created: 0, updated: 0, errors: 0 }, raw);
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
const planningDir: string = getPhasesDirPath(cwd);
|
|
959
|
+
let phaseDir: string | null = null;
|
|
960
|
+
try {
|
|
961
|
+
const dirs: string[] = fs.readdirSync(planningDir);
|
|
962
|
+
phaseDir =
|
|
963
|
+
dirs.find((d: string) => d.startsWith(`${phaseNum}-`) || d === String(phaseNum)) || null;
|
|
964
|
+
} catch {
|
|
965
|
+
/* no phases dir */
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
const plans: PlanInput[] = [];
|
|
969
|
+
if (phaseDir) {
|
|
970
|
+
const fullPhaseDir: string = path.join(planningDir, phaseDir);
|
|
971
|
+
try {
|
|
972
|
+
const files: string[] = fs
|
|
973
|
+
.readdirSync(fullPhaseDir)
|
|
974
|
+
.filter((f: string) => f.match(/-PLAN\.md$/));
|
|
975
|
+
for (const f of files) {
|
|
976
|
+
const planMatch: RegExpMatchArray | null = f.match(/(\d+)-(\d+)-PLAN\.md$/);
|
|
977
|
+
if (planMatch) {
|
|
978
|
+
const planContent: string | null = safeReadFile(path.join(fullPhaseDir, f));
|
|
979
|
+
const objMatch: RegExpMatchArray | null | undefined = planContent?.match(
|
|
980
|
+
/(?:objective|title):\s*["']?(.+?)["']?\s*$/m
|
|
981
|
+
);
|
|
982
|
+
plans.push({
|
|
983
|
+
number: planMatch[2],
|
|
984
|
+
objective: objMatch ? objMatch[1] : f,
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
} catch {
|
|
989
|
+
/* no plan files */
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
const stats: SyncStats = tracker.syncPhase(phaseNum, { plans });
|
|
993
|
+
output(
|
|
994
|
+
stats,
|
|
995
|
+
raw,
|
|
996
|
+
`Phase ${phaseNum} synced: created ${stats.created}, updated ${stats.updated}, errors ${stats.errors}`
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
function handleUpdateStatus(cwd: string, args: string[], raw: boolean): void {
|
|
1001
|
+
const phaseNum: string | undefined = args[0];
|
|
1002
|
+
const status: string | undefined = args[1];
|
|
1003
|
+
if (!phaseNum || !status) {
|
|
1004
|
+
error(
|
|
1005
|
+
'Usage: tracker update-status <phase-number> <status>. Provide phase number and one of the valid status values (in-progress, completed, blocked). Example: tracker update-status 3 completed. To see current phase numbers: grd-tools.js roadmap get-phase <N>'
|
|
1006
|
+
);
|
|
1007
|
+
return; // unreachable after error() but helps TS narrowing
|
|
1008
|
+
}
|
|
1009
|
+
const config: TrackerConfig = loadTrackerConfig(cwd);
|
|
1010
|
+
if (config.provider === 'mcp-atlassian') {
|
|
1011
|
+
const mapping: TrackerMapping = loadTrackerMapping(cwd);
|
|
1012
|
+
const phaseInfo: PhaseMapping | undefined = mapping.phases[String(phaseNum)];
|
|
1013
|
+
if (!phaseInfo) {
|
|
1014
|
+
output({ success: false, error: 'Phase not synced to tracker' }, raw);
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
phaseInfo.status = status;
|
|
1018
|
+
saveTrackerMapping(cwd, mapping);
|
|
1019
|
+
output({ success: true, issue_key: phaseInfo.issueRef, status }, raw);
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
const tracker: GitHubTracker | null = createTracker(cwd);
|
|
1023
|
+
if (!tracker) {
|
|
1024
|
+
output({ success: false, error: 'No tracker configured' }, raw);
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
const mapping: TrackerMapping = loadTrackerMapping(cwd);
|
|
1028
|
+
const phaseInfo: PhaseMapping | undefined = mapping.phases[String(phaseNum)];
|
|
1029
|
+
if (!phaseInfo) {
|
|
1030
|
+
output({ success: false, error: 'Phase not synced to tracker' }, raw);
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
const result: StatusUpdateResult = tracker.updateIssueStatus(phaseInfo.issueRef, status);
|
|
1034
|
+
if (result.success) {
|
|
1035
|
+
phaseInfo.status = status;
|
|
1036
|
+
saveTrackerMapping(cwd, mapping);
|
|
1037
|
+
}
|
|
1038
|
+
output(result, raw, `Status ${result.success ? `updated to '${status}'` : 'update failed'}`);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
function handleAddComment(cwd: string, args: string[], raw: boolean): void {
|
|
1042
|
+
const phaseNum: string | undefined = args[0];
|
|
1043
|
+
const filePath: string | undefined = args[1];
|
|
1044
|
+
if (!phaseNum || !filePath) {
|
|
1045
|
+
error('Usage: tracker add-comment <phase-number> <file-path>');
|
|
1046
|
+
return; // unreachable after error() but helps TS narrowing
|
|
1047
|
+
}
|
|
1048
|
+
const config: TrackerConfig = loadTrackerConfig(cwd);
|
|
1049
|
+
if (config.provider === 'mcp-atlassian') {
|
|
1050
|
+
const mapping: TrackerMapping = loadTrackerMapping(cwd);
|
|
1051
|
+
const phaseInfo: PhaseMapping | undefined = mapping.phases[String(phaseNum)];
|
|
1052
|
+
if (!phaseInfo) {
|
|
1053
|
+
output({ success: false, error: 'Phase not synced to tracker' }, raw);
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
const content: string | null = safeReadFile(path.join(cwd, filePath));
|
|
1057
|
+
if (!content) {
|
|
1058
|
+
output({ success: false, error: 'File not found: ' + filePath }, raw);
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
output(
|
|
1062
|
+
{
|
|
1063
|
+
provider: 'mcp-atlassian',
|
|
1064
|
+
issue_key: phaseInfo.issueRef,
|
|
1065
|
+
file_path: filePath,
|
|
1066
|
+
content_length: content.length,
|
|
1067
|
+
content,
|
|
1068
|
+
},
|
|
1069
|
+
raw
|
|
1070
|
+
);
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
const tracker: GitHubTracker | null = createTracker(cwd);
|
|
1074
|
+
if (!tracker) {
|
|
1075
|
+
output({ success: false, error: 'No tracker configured' }, raw);
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
const mapping: TrackerMapping = loadTrackerMapping(cwd);
|
|
1079
|
+
const phaseInfo: PhaseMapping | undefined = mapping.phases[String(phaseNum)];
|
|
1080
|
+
if (!phaseInfo) {
|
|
1081
|
+
output({ success: false, error: 'Phase not synced to tracker' }, raw);
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
const content: string | null = safeReadFile(path.join(cwd, filePath));
|
|
1085
|
+
if (!content) {
|
|
1086
|
+
output({ success: false, error: 'File not found: ' + filePath }, raw);
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
const result: StatusUpdateResult = tracker.addComment(phaseInfo.issueRef, content);
|
|
1090
|
+
output(result, raw, `Comment ${result.success ? 'added successfully' : 'failed'}`);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* Parse all milestones from active roadmap content.
|
|
1095
|
+
* @param content - Roadmap content with shipped sections already stripped
|
|
1096
|
+
* @returns Ordered milestone list
|
|
1097
|
+
*/
|
|
1098
|
+
function _parseAllMilestones(content: string): MilestonePosition[] {
|
|
1099
|
+
const milestoneRegex: RegExp = /^##\s*(.*v(\d+\.\d+)[^(\n]*)/gim;
|
|
1100
|
+
let mMatch: RegExpExecArray | null;
|
|
1101
|
+
const milestonePositions: MilestonePosition[] = [];
|
|
1102
|
+
while ((mMatch = milestoneRegex.exec(content)) !== null) {
|
|
1103
|
+
milestonePositions.push({
|
|
1104
|
+
heading: mMatch[1].trim(),
|
|
1105
|
+
version: 'v' + mMatch[2],
|
|
1106
|
+
index: mMatch.index,
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
return milestonePositions;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* Parse all phases from active roadmap content, associating each with its milestone.
|
|
1114
|
+
* @param content - Roadmap content with shipped sections already stripped
|
|
1115
|
+
* @param milestonePositions - Parsed milestone list
|
|
1116
|
+
* @returns Parsed phase positions
|
|
1117
|
+
*/
|
|
1118
|
+
function _parseAllPhases(
|
|
1119
|
+
content: string,
|
|
1120
|
+
milestonePositions: MilestonePosition[]
|
|
1121
|
+
): PhasePosition[] {
|
|
1122
|
+
const phaseRegex: RegExp = /^##\s+Phase\s+(\d+(?:\.\d+)?)\s*[:\-\u2014]\s*(.+)$/gm;
|
|
1123
|
+
let match: RegExpExecArray | null;
|
|
1124
|
+
const allPhases: PhasePosition[] = [];
|
|
1125
|
+
while ((match = phaseRegex.exec(content)) !== null) {
|
|
1126
|
+
const number: string = match[1];
|
|
1127
|
+
const name: string = match[2].trim();
|
|
1128
|
+
const afterPhase: string = content.slice(
|
|
1129
|
+
match.index + match[0].length,
|
|
1130
|
+
match.index + match[0].length + 500
|
|
1131
|
+
);
|
|
1132
|
+
const goalMatch: RegExpMatchArray | null = afterPhase.match(/(?:\*\*Goal:\*\*|Goal:)\s*(.+)/);
|
|
1133
|
+
const goal: string = goalMatch ? goalMatch[1].trim() : '';
|
|
1134
|
+
let milestone: string | null =
|
|
1135
|
+
milestonePositions.length > 0 ? milestonePositions[0].version : null;
|
|
1136
|
+
for (const ms of milestonePositions) {
|
|
1137
|
+
if (match.index > ms.index) milestone = ms.version;
|
|
1138
|
+
}
|
|
1139
|
+
allPhases.push({ number, name, goal, milestone, index: match.index });
|
|
1140
|
+
}
|
|
1141
|
+
return allPhases;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
/**
|
|
1145
|
+
* Build the operations array for a roadmap sync, covering milestones and phases.
|
|
1146
|
+
* @param milestones - Parsed milestones
|
|
1147
|
+
* @param phases - Parsed phases
|
|
1148
|
+
* @param ctx - Loaded mapping and computed schedule
|
|
1149
|
+
* @returns Operations array (create/skip entries)
|
|
1150
|
+
*/
|
|
1151
|
+
function _buildMilestoneOperations(
|
|
1152
|
+
milestones: MilestonePosition[],
|
|
1153
|
+
phases: PhasePosition[],
|
|
1154
|
+
ctx: { mapping: TrackerMapping; schedule: ScheduleResult }
|
|
1155
|
+
): SyncOperation[] {
|
|
1156
|
+
const { mapping, schedule } = ctx;
|
|
1157
|
+
const operations: SyncOperation[] = [];
|
|
1158
|
+
|
|
1159
|
+
for (const ms of milestones) {
|
|
1160
|
+
if (mapping.milestones[ms.version]) {
|
|
1161
|
+
operations.push({
|
|
1162
|
+
action: 'skip',
|
|
1163
|
+
type: 'milestone',
|
|
1164
|
+
milestone: ms.version,
|
|
1165
|
+
issue_key: mapping.milestones[ms.version].issueRef,
|
|
1166
|
+
reason: 'already_synced',
|
|
1167
|
+
});
|
|
1168
|
+
} else {
|
|
1169
|
+
const msSchedule: ParsedMilestone | null = getScheduleForMilestone(schedule, ms.version);
|
|
1170
|
+
const op: SyncOperation = {
|
|
1171
|
+
action: 'create',
|
|
1172
|
+
type: 'milestone',
|
|
1173
|
+
milestone: ms.version,
|
|
1174
|
+
summary: ms.heading,
|
|
1175
|
+
description: `Milestone ${ms.version}`,
|
|
1176
|
+
};
|
|
1177
|
+
if (msSchedule && msSchedule.start) op.start_date = msSchedule.start;
|
|
1178
|
+
if (msSchedule && msSchedule.target) op.due_date = msSchedule.target;
|
|
1179
|
+
operations.push(op);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
for (const phase of phases) {
|
|
1184
|
+
const milestoneKey: string | null =
|
|
1185
|
+
phase.milestone && mapping.milestones[phase.milestone]
|
|
1186
|
+
? mapping.milestones[phase.milestone].issueRef
|
|
1187
|
+
: null;
|
|
1188
|
+
if (mapping.phases[phase.number]) {
|
|
1189
|
+
operations.push({
|
|
1190
|
+
action: 'skip',
|
|
1191
|
+
type: 'phase',
|
|
1192
|
+
phase: phase.number,
|
|
1193
|
+
issue_key: mapping.phases[phase.number].issueRef,
|
|
1194
|
+
reason: 'already_synced',
|
|
1195
|
+
});
|
|
1196
|
+
} else {
|
|
1197
|
+
const phaseSchedule: PhaseScheduleEntry | null = getScheduleForPhase(schedule, phase.number);
|
|
1198
|
+
const op: SyncOperation = {
|
|
1199
|
+
action: 'create',
|
|
1200
|
+
type: 'phase',
|
|
1201
|
+
phase: phase.number,
|
|
1202
|
+
milestone: phase.milestone || undefined,
|
|
1203
|
+
parent_key: milestoneKey,
|
|
1204
|
+
summary: `Phase ${phase.number}: ${phase.name}`,
|
|
1205
|
+
description: phase.goal,
|
|
1206
|
+
};
|
|
1207
|
+
if (phaseSchedule && phaseSchedule.start_date) {
|
|
1208
|
+
op.start_date = phaseSchedule.start_date;
|
|
1209
|
+
op.due_date = phaseSchedule.due_date || undefined;
|
|
1210
|
+
op.duration_days = phaseSchedule.duration_days;
|
|
1211
|
+
}
|
|
1212
|
+
operations.push(op);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
return operations;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
function handlePrepareRoadmapSync(cwd: string, _args: string[], raw: boolean): void {
|
|
1220
|
+
const config: TrackerConfig = loadTrackerConfig(cwd);
|
|
1221
|
+
if (config.provider !== 'mcp-atlassian') {
|
|
1222
|
+
output(
|
|
1223
|
+
{
|
|
1224
|
+
error:
|
|
1225
|
+
'prepare-roadmap-sync is only for mcp-atlassian provider. Use "tracker sync-roadmap" for GitHub.',
|
|
1226
|
+
},
|
|
1227
|
+
raw
|
|
1228
|
+
);
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
const mcpConfig: McpAtlassianConfig = config.mcp_atlassian || ({} as McpAtlassianConfig);
|
|
1232
|
+
const roadmapContent: string | null = safeReadMarkdown(path.join(cwd, '.planning', 'ROADMAP.md'));
|
|
1233
|
+
if (!roadmapContent) {
|
|
1234
|
+
output({ error: 'No ROADMAP.md found', operations: [] }, raw);
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
const activeContent: string = stripShippedSections(roadmapContent);
|
|
1238
|
+
const mapping: TrackerMapping = loadTrackerMapping(cwd);
|
|
1239
|
+
const schedule: ScheduleResult = computeSchedule(cwd);
|
|
1240
|
+
|
|
1241
|
+
const milestonePositions: MilestonePosition[] = _parseAllMilestones(activeContent);
|
|
1242
|
+
const allPhases: PhasePosition[] = _parseAllPhases(activeContent, milestonePositions);
|
|
1243
|
+
const operations: SyncOperation[] = _buildMilestoneOperations(milestonePositions, allPhases, {
|
|
1244
|
+
mapping,
|
|
1245
|
+
schedule,
|
|
1246
|
+
});
|
|
1247
|
+
|
|
1248
|
+
output(
|
|
1249
|
+
{
|
|
1250
|
+
provider: 'mcp-atlassian',
|
|
1251
|
+
project_key: mcpConfig.project_key || '',
|
|
1252
|
+
start_date_field: mcpConfig.start_date_field || 'customfield_10015',
|
|
1253
|
+
milestone_issue_type: mcpConfig.milestone_issue_type || 'Epic',
|
|
1254
|
+
phase_issue_type: mcpConfig.phase_issue_type || 'Task',
|
|
1255
|
+
operations,
|
|
1256
|
+
},
|
|
1257
|
+
raw
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
function handlePreparePhaseSync(cwd: string, args: string[], raw: boolean): void {
|
|
1262
|
+
const phaseNum: string | undefined = args[0];
|
|
1263
|
+
if (!phaseNum) {
|
|
1264
|
+
error(
|
|
1265
|
+
'Usage: tracker prepare-phase-sync <phase-number>. Provide the phase number to sync, e.g.: tracker prepare-phase-sync 3. To see available phase numbers: grd-tools.js roadmap analyze'
|
|
1266
|
+
);
|
|
1267
|
+
return; // unreachable after error() but helps TS narrowing
|
|
1268
|
+
}
|
|
1269
|
+
const config: TrackerConfig = loadTrackerConfig(cwd);
|
|
1270
|
+
if (config.provider !== 'mcp-atlassian') {
|
|
1271
|
+
output(
|
|
1272
|
+
{
|
|
1273
|
+
error:
|
|
1274
|
+
'prepare-phase-sync is only for mcp-atlassian provider. Use "tracker sync-phase" for GitHub.',
|
|
1275
|
+
},
|
|
1276
|
+
raw
|
|
1277
|
+
);
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
const mcpConfig: McpAtlassianConfig = config.mcp_atlassian || ({} as McpAtlassianConfig);
|
|
1281
|
+
const mapping: TrackerMapping = loadTrackerMapping(cwd);
|
|
1282
|
+
const parentInfo: PhaseMapping | undefined = mapping.phases[String(phaseNum)];
|
|
1283
|
+
const parentKey: string | null = parentInfo ? parentInfo.issueRef : null;
|
|
1284
|
+
|
|
1285
|
+
const planningDir: string = getPhasesDirPath(cwd);
|
|
1286
|
+
let phaseDir: string | null = null;
|
|
1287
|
+
try {
|
|
1288
|
+
const dirs: string[] = fs.readdirSync(planningDir);
|
|
1289
|
+
phaseDir =
|
|
1290
|
+
dirs.find((d: string) => d.startsWith(`${phaseNum}-`) || d === String(phaseNum)) || null;
|
|
1291
|
+
} catch {
|
|
1292
|
+
/* no phases dir */
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
const operations: SyncOperation[] = [];
|
|
1296
|
+
if (phaseDir) {
|
|
1297
|
+
const fullPhaseDir: string = path.join(planningDir, phaseDir);
|
|
1298
|
+
try {
|
|
1299
|
+
const files: string[] = fs
|
|
1300
|
+
.readdirSync(fullPhaseDir)
|
|
1301
|
+
.filter((f: string) => f.match(/-PLAN\.md$/));
|
|
1302
|
+
for (const f of files) {
|
|
1303
|
+
const planMatch: RegExpMatchArray | null = f.match(/(\d+)-(\d+)-PLAN\.md$/);
|
|
1304
|
+
if (planMatch) {
|
|
1305
|
+
const planNum: string = planMatch[2];
|
|
1306
|
+
const key: string = `${phaseNum}-${planNum}`;
|
|
1307
|
+
if (mapping.plans[key]) {
|
|
1308
|
+
operations.push({
|
|
1309
|
+
action: 'skip',
|
|
1310
|
+
type: 'plan',
|
|
1311
|
+
phase: phaseNum,
|
|
1312
|
+
plan: planNum,
|
|
1313
|
+
issue_key: mapping.plans[key].issueRef,
|
|
1314
|
+
reason: 'already_synced',
|
|
1315
|
+
});
|
|
1316
|
+
} else {
|
|
1317
|
+
const planContent: string | null = safeReadFile(path.join(fullPhaseDir, f));
|
|
1318
|
+
const objMatch: RegExpMatchArray | null | undefined = planContent?.match(
|
|
1319
|
+
/(?:objective|title):\s*["']?(.+?)["']?\s*$/m
|
|
1320
|
+
);
|
|
1321
|
+
operations.push({
|
|
1322
|
+
action: 'create',
|
|
1323
|
+
type: 'plan',
|
|
1324
|
+
phase: phaseNum,
|
|
1325
|
+
plan: planNum,
|
|
1326
|
+
summary: `Plan ${phaseNum}-${planNum}: ${objMatch ? objMatch[1] : f}`,
|
|
1327
|
+
description: '',
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
} catch {
|
|
1333
|
+
/* no plan files */
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
output(
|
|
1337
|
+
{
|
|
1338
|
+
provider: 'mcp-atlassian',
|
|
1339
|
+
project_key: mcpConfig.project_key || '',
|
|
1340
|
+
plan_issue_type: mcpConfig.plan_issue_type || 'Sub-task',
|
|
1341
|
+
parent_key: parentKey,
|
|
1342
|
+
operations,
|
|
1343
|
+
},
|
|
1344
|
+
raw
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
function handleRecordMapping(cwd: string, args: string[], raw: boolean): void {
|
|
1349
|
+
const typeIdx: number = args.indexOf('--type');
|
|
1350
|
+
const milestoneIdx: number = args.indexOf('--milestone');
|
|
1351
|
+
const phaseIdx: number = args.indexOf('--phase');
|
|
1352
|
+
const planIdx: number = args.indexOf('--plan');
|
|
1353
|
+
const keyIdx: number = args.indexOf('--key');
|
|
1354
|
+
const urlIdx: number = args.indexOf('--url');
|
|
1355
|
+
const parentIdx: number = args.indexOf('--parent');
|
|
1356
|
+
|
|
1357
|
+
const type: string | null = typeIdx !== -1 ? args[typeIdx + 1] : null;
|
|
1358
|
+
const milestoneVer: string | null = milestoneIdx !== -1 ? args[milestoneIdx + 1] : null;
|
|
1359
|
+
const phaseNum: string | null = phaseIdx !== -1 ? args[phaseIdx + 1] : null;
|
|
1360
|
+
const planNum: string | null = planIdx !== -1 ? args[planIdx + 1] : null;
|
|
1361
|
+
const issueKey: string | null = keyIdx !== -1 ? args[keyIdx + 1] : null;
|
|
1362
|
+
const issueUrl: string | null = urlIdx !== -1 ? args[urlIdx + 1] : null;
|
|
1363
|
+
const parentKey: string = parentIdx !== -1 ? args[parentIdx + 1] : '';
|
|
1364
|
+
|
|
1365
|
+
if (!type || !issueKey) {
|
|
1366
|
+
error(
|
|
1367
|
+
'Usage: tracker record-mapping --type milestone|phase|plan [--milestone V] [--phase N] [--plan M] --key PROJ-1 --url URL [--parent PROJ-0]. Example: tracker record-mapping --type phase --phase 2 --key PROJ-5 --url https://.... Ensure --type and --key flags are provided at minimum.'
|
|
1368
|
+
);
|
|
1369
|
+
return; // unreachable after error() but helps TS narrowing
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
const mapping: TrackerMapping = loadTrackerMapping(cwd);
|
|
1373
|
+
mapping.provider = 'mcp-atlassian';
|
|
1374
|
+
|
|
1375
|
+
if (type === 'milestone') {
|
|
1376
|
+
if (!milestoneVer) {
|
|
1377
|
+
error('--milestone is required for type "milestone"');
|
|
1378
|
+
return; // unreachable after error() but helps TS narrowing
|
|
1379
|
+
}
|
|
1380
|
+
mapping.milestones[milestoneVer] = {
|
|
1381
|
+
issueRef: issueKey,
|
|
1382
|
+
url: issueUrl || '',
|
|
1383
|
+
status: 'pending',
|
|
1384
|
+
};
|
|
1385
|
+
} else if (type === 'phase') {
|
|
1386
|
+
if (!phaseNum) {
|
|
1387
|
+
error('--phase is required for type "phase"');
|
|
1388
|
+
return; // unreachable after error() but helps TS narrowing
|
|
1389
|
+
}
|
|
1390
|
+
mapping.phases[phaseNum] = {
|
|
1391
|
+
issueRef: issueKey,
|
|
1392
|
+
url: issueUrl || '',
|
|
1393
|
+
parentRef: parentKey,
|
|
1394
|
+
status: 'pending',
|
|
1395
|
+
};
|
|
1396
|
+
} else if (type === 'plan') {
|
|
1397
|
+
if (!phaseNum) {
|
|
1398
|
+
error('--phase is required for type "plan"');
|
|
1399
|
+
return; // unreachable after error() but helps TS narrowing
|
|
1400
|
+
}
|
|
1401
|
+
if (!planNum) {
|
|
1402
|
+
error('--plan is required for type "plan"');
|
|
1403
|
+
return; // unreachable after error() but helps TS narrowing
|
|
1404
|
+
}
|
|
1405
|
+
const key: string = `${phaseNum}-${planNum}`;
|
|
1406
|
+
mapping.plans[key] = {
|
|
1407
|
+
issueRef: issueKey,
|
|
1408
|
+
url: issueUrl || '',
|
|
1409
|
+
parentRef: parentKey,
|
|
1410
|
+
status: 'pending',
|
|
1411
|
+
};
|
|
1412
|
+
} else {
|
|
1413
|
+
error(`Unknown mapping type: ${type}. Use "milestone", "phase", or "plan".`);
|
|
1414
|
+
return; // unreachable after error() but helps TS narrowing
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
saveTrackerMapping(cwd, mapping);
|
|
1418
|
+
output(
|
|
1419
|
+
{
|
|
1420
|
+
success: true,
|
|
1421
|
+
type,
|
|
1422
|
+
milestone: milestoneVer || null,
|
|
1423
|
+
phase: phaseNum || null,
|
|
1424
|
+
plan: planNum || null,
|
|
1425
|
+
key: issueKey,
|
|
1426
|
+
},
|
|
1427
|
+
raw
|
|
1428
|
+
);
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
function handleRecordStatus(cwd: string, args: string[], raw: boolean): void {
|
|
1432
|
+
const phaseIdx: number = args.indexOf('--phase');
|
|
1433
|
+
const statusIdx: number = args.indexOf('--status');
|
|
1434
|
+
const phaseNum: string | null = phaseIdx !== -1 ? args[phaseIdx + 1] : null;
|
|
1435
|
+
const status: string | null = statusIdx !== -1 ? args[statusIdx + 1] : null;
|
|
1436
|
+
|
|
1437
|
+
if (!phaseNum || !status) {
|
|
1438
|
+
error(
|
|
1439
|
+
'Usage: tracker record-status --phase N --status pending|in_progress|complete. Example: tracker record-status --phase 2 --status in_progress. Ensure both --phase and --status flags are provided.'
|
|
1440
|
+
);
|
|
1441
|
+
return; // unreachable after error() but helps TS narrowing
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
const mapping: TrackerMapping = loadTrackerMapping(cwd);
|
|
1445
|
+
const phaseInfo: PhaseMapping | undefined = mapping.phases[String(phaseNum)];
|
|
1446
|
+
if (!phaseInfo) {
|
|
1447
|
+
output({ success: false, error: 'Phase not synced to tracker' }, raw);
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
phaseInfo.status = status;
|
|
1451
|
+
saveTrackerMapping(cwd, mapping);
|
|
1452
|
+
output({ success: true, phase: phaseNum, status, issue_key: phaseInfo.issueRef }, raw);
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
function handleSyncStatus(cwd: string, _args: string[], raw: boolean): void {
|
|
1456
|
+
const config: TrackerConfig = loadTrackerConfig(cwd);
|
|
1457
|
+
const mapping: TrackerMapping = loadTrackerMapping(cwd);
|
|
1458
|
+
|
|
1459
|
+
const roadmapContent: string | null = safeReadMarkdown(path.join(cwd, '.planning', 'ROADMAP.md'));
|
|
1460
|
+
const activeContent: string = stripShippedSections(roadmapContent || '');
|
|
1461
|
+
const phaseRegex: RegExp = /^##\s+Phase\s+(\d+(?:\.\d+)?)/gm;
|
|
1462
|
+
const roadmapPhases: string[] = [];
|
|
1463
|
+
let m: RegExpExecArray | null;
|
|
1464
|
+
while ((m = phaseRegex.exec(activeContent)) !== null) {
|
|
1465
|
+
roadmapPhases.push(m[1]);
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
const synced: string[] = roadmapPhases.filter((p: string) => mapping.phases[p]);
|
|
1469
|
+
const unsynced: string[] = roadmapPhases.filter((p: string) => !mapping.phases[p]);
|
|
1470
|
+
|
|
1471
|
+
output(
|
|
1472
|
+
{
|
|
1473
|
+
provider: config.provider,
|
|
1474
|
+
last_synced: mapping.last_synced,
|
|
1475
|
+
total_milestones: Object.keys(mapping.milestones).length,
|
|
1476
|
+
total_phases: roadmapPhases.length,
|
|
1477
|
+
synced_phases: synced.length,
|
|
1478
|
+
unsynced_phases: unsynced.length,
|
|
1479
|
+
synced: synced,
|
|
1480
|
+
unsynced: unsynced,
|
|
1481
|
+
plan_count: Object.keys(mapping.plans).length,
|
|
1482
|
+
},
|
|
1483
|
+
raw
|
|
1484
|
+
);
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
function handleSchedule(cwd: string, _args: string[], raw: boolean): void {
|
|
1488
|
+
const schedule: ScheduleResult = computeSchedule(cwd);
|
|
1489
|
+
output(
|
|
1490
|
+
schedule,
|
|
1491
|
+
raw,
|
|
1492
|
+
`${schedule.phases.length} phases scheduled across ${schedule.milestones.length} milestone(s)`
|
|
1493
|
+
);
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
function handlePrepareReschedule(cwd: string, _args: string[], raw: boolean): void {
|
|
1497
|
+
const config: TrackerConfig = loadTrackerConfig(cwd);
|
|
1498
|
+
if (config.provider !== 'mcp-atlassian') {
|
|
1499
|
+
output({ error: 'prepare-reschedule is only for mcp-atlassian provider.' }, raw);
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
const mcpConfig: McpAtlassianConfig = config.mcp_atlassian || ({} as McpAtlassianConfig);
|
|
1503
|
+
const mapping: TrackerMapping = loadTrackerMapping(cwd);
|
|
1504
|
+
const schedule: ScheduleResult = computeSchedule(cwd);
|
|
1505
|
+
const operations: SyncOperation[] = [];
|
|
1506
|
+
|
|
1507
|
+
for (const ms of schedule.milestones) {
|
|
1508
|
+
const mapped: MilestoneMapping | undefined = mapping.milestones[ms.version];
|
|
1509
|
+
if (mapped && (ms.start || ms.target)) {
|
|
1510
|
+
const op: SyncOperation = {
|
|
1511
|
+
action: 'update',
|
|
1512
|
+
type: 'milestone',
|
|
1513
|
+
milestone: ms.version,
|
|
1514
|
+
issue_key: mapped.issueRef,
|
|
1515
|
+
};
|
|
1516
|
+
if (ms.start) op.start_date = ms.start;
|
|
1517
|
+
if (ms.target) op.due_date = ms.target;
|
|
1518
|
+
operations.push(op);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
for (const phase of schedule.phases) {
|
|
1523
|
+
const mapped: PhaseMapping | undefined = mapping.phases[phase.number];
|
|
1524
|
+
if (mapped && phase.start_date) {
|
|
1525
|
+
operations.push({
|
|
1526
|
+
action: 'update',
|
|
1527
|
+
type: 'phase',
|
|
1528
|
+
phase: phase.number,
|
|
1529
|
+
issue_key: mapped.issueRef,
|
|
1530
|
+
start_date: phase.start_date,
|
|
1531
|
+
due_date: phase.due_date || undefined,
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
output(
|
|
1537
|
+
{
|
|
1538
|
+
provider: 'mcp-atlassian',
|
|
1539
|
+
start_date_field: mcpConfig.start_date_field || 'customfield_10015',
|
|
1540
|
+
operations,
|
|
1541
|
+
},
|
|
1542
|
+
raw
|
|
1543
|
+
);
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
// ─── Tracker Command Dispatcher ───────────────────────────────────────────────
|
|
1547
|
+
|
|
1548
|
+
type TrackerHandler = (cwd: string, args: string[], raw: boolean) => void;
|
|
1549
|
+
|
|
1550
|
+
const trackerHandlers: Record<string, TrackerHandler> = {
|
|
1551
|
+
'get-config': handleGetConfig,
|
|
1552
|
+
'sync-roadmap': handleSyncRoadmap,
|
|
1553
|
+
'sync-phase': handleSyncPhase,
|
|
1554
|
+
'update-status': handleUpdateStatus,
|
|
1555
|
+
'add-comment': handleAddComment,
|
|
1556
|
+
'sync-status': handleSyncStatus,
|
|
1557
|
+
'prepare-roadmap-sync': handlePrepareRoadmapSync,
|
|
1558
|
+
'prepare-phase-sync': handlePreparePhaseSync,
|
|
1559
|
+
'record-mapping': handleRecordMapping,
|
|
1560
|
+
'record-status': handleRecordStatus,
|
|
1561
|
+
schedule: handleSchedule,
|
|
1562
|
+
'prepare-reschedule': handlePrepareReschedule,
|
|
1563
|
+
};
|
|
1564
|
+
|
|
1565
|
+
/**
|
|
1566
|
+
* CLI command: Dispatch tracker subcommand (get-config, sync-roadmap, sync-phase, update-status, etc.).
|
|
1567
|
+
* @param cwd - Project working directory
|
|
1568
|
+
* @param subcommand - Tracker subcommand name
|
|
1569
|
+
* @param args - Additional arguments for the subcommand
|
|
1570
|
+
* @param raw - Output raw text instead of JSON
|
|
1571
|
+
*/
|
|
1572
|
+
function cmdTracker(cwd: string, subcommand: string, args: string[], raw: boolean): void {
|
|
1573
|
+
const handler: TrackerHandler | undefined = trackerHandlers[subcommand];
|
|
1574
|
+
if (!handler) {
|
|
1575
|
+
error(
|
|
1576
|
+
`Unknown tracker subcommand: '${subcommand}'. Available: ${Object.keys(trackerHandlers).join(', ')}`
|
|
1577
|
+
);
|
|
1578
|
+
return; // unreachable after error() but helps TS narrowing
|
|
1579
|
+
}
|
|
1580
|
+
handler(cwd, args, raw);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
module.exports = {
|
|
1584
|
+
loadTrackerConfig,
|
|
1585
|
+
loadTrackerMapping,
|
|
1586
|
+
saveTrackerMapping,
|
|
1587
|
+
createGitHubTracker,
|
|
1588
|
+
PROVIDERS,
|
|
1589
|
+
createTracker,
|
|
1590
|
+
cmdTracker,
|
|
1591
|
+
};
|