@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.
Files changed (711) hide show
  1. package/.claude-plugin/plugin.json +103 -0
  2. package/README.md +211 -0
  3. package/agents/grd-baseline-assessor.md +684 -0
  4. package/agents/grd-code-reviewer.md +300 -0
  5. package/agents/grd-codebase-mapper.md +355 -0
  6. package/agents/grd-critique-agent.md +119 -0
  7. package/agents/grd-debugger.md +519 -0
  8. package/agents/grd-deep-diver.md +737 -0
  9. package/agents/grd-eval-planner.md +913 -0
  10. package/agents/grd-eval-reporter.md +717 -0
  11. package/agents/grd-executor.md +683 -0
  12. package/agents/grd-feasibility-analyst.md +624 -0
  13. package/agents/grd-integration-checker.md +367 -0
  14. package/agents/grd-knowledge-miner.md +81 -0
  15. package/agents/grd-migrator.md +88 -0
  16. package/agents/grd-phase-researcher.md +697 -0
  17. package/agents/grd-plan-checker.md +443 -0
  18. package/agents/grd-planner.md +1532 -0
  19. package/agents/grd-product-owner.md +562 -0
  20. package/agents/grd-project-researcher.md +513 -0
  21. package/agents/grd-research-synthesizer.md +273 -0
  22. package/agents/grd-roadmapper.md +798 -0
  23. package/agents/grd-surveyor.md +566 -0
  24. package/agents/grd-verifier.md +893 -0
  25. package/bin/gd.js +4 -0
  26. package/bin/gd.ts +227 -0
  27. package/bin/grd-manifest.js +4 -0
  28. package/bin/grd-manifest.ts +286 -0
  29. package/bin/grd-mcp-server.js +4 -0
  30. package/bin/grd-mcp-server.ts +124 -0
  31. package/bin/grd-tools.js +4 -0
  32. package/bin/grd-tools.ts +2471 -0
  33. package/bin/postinstall.js +4 -0
  34. package/bin/postinstall.ts +80 -0
  35. package/commands/add-phase.md +123 -0
  36. package/commands/add-todo.md +87 -0
  37. package/commands/assess-baseline.md +289 -0
  38. package/commands/autopilot.md +100 -0
  39. package/commands/autoplan.md +55 -0
  40. package/commands/check-todos.md +87 -0
  41. package/commands/compare-methods.md +262 -0
  42. package/commands/complete-milestone.md +225 -0
  43. package/commands/debug.md +372 -0
  44. package/commands/deep-dive.md +288 -0
  45. package/commands/discover.md +281 -0
  46. package/commands/discuss-phase.md +188 -0
  47. package/commands/discuss.md +55 -0
  48. package/commands/eval-report.md +310 -0
  49. package/commands/evolve.md +79 -0
  50. package/commands/execute-phase.md +1017 -0
  51. package/commands/feasibility.md +292 -0
  52. package/commands/help.md +407 -0
  53. package/commands/init.md +1508 -0
  54. package/commands/insert-phase.md +113 -0
  55. package/commands/iterate.md +327 -0
  56. package/commands/list-phase-assumptions.md +217 -0
  57. package/commands/long-term-roadmap.md +202 -0
  58. package/commands/map-codebase.md +111 -0
  59. package/commands/migrate.md +159 -0
  60. package/commands/new-milestone.md +169 -0
  61. package/commands/pause-work.md +83 -0
  62. package/commands/plan-milestone-gaps.md +373 -0
  63. package/commands/plan-phase.md +655 -0
  64. package/commands/principles.md +328 -0
  65. package/commands/product-plan.md +319 -0
  66. package/commands/progress.md +481 -0
  67. package/commands/quick.md +167 -0
  68. package/commands/reapply-patches.md +154 -0
  69. package/commands/remove-phase.md +97 -0
  70. package/commands/requirement.md +96 -0
  71. package/commands/resume-project.md +113 -0
  72. package/commands/settings.md +1144 -0
  73. package/commands/survey.md +242 -0
  74. package/commands/sync.md +246 -0
  75. package/commands/tracker-setup.md +322 -0
  76. package/commands/update.md +202 -0
  77. package/commands/verify-phase.md +335 -0
  78. package/commands/verify-work.md +701 -0
  79. package/commands/wireup.md +29 -0
  80. package/dist/bin/gd.d.ts +3 -0
  81. package/dist/bin/gd.d.ts.map +1 -0
  82. package/dist/bin/gd.js +178 -0
  83. package/dist/bin/gd.js.map +1 -0
  84. package/dist/bin/grd-manifest.d.ts +3 -0
  85. package/dist/bin/grd-manifest.d.ts.map +1 -0
  86. package/dist/bin/grd-manifest.js +202 -0
  87. package/dist/bin/grd-manifest.js.map +1 -0
  88. package/dist/bin/grd-mcp-server.d.ts +3 -0
  89. package/dist/bin/grd-mcp-server.d.ts.map +1 -0
  90. package/dist/bin/grd-mcp-server.js +71 -0
  91. package/dist/bin/grd-mcp-server.js.map +1 -0
  92. package/dist/bin/grd-tools.d.ts +3 -0
  93. package/dist/bin/grd-tools.d.ts.map +1 -0
  94. package/dist/bin/grd-tools.js +1680 -0
  95. package/dist/bin/grd-tools.js.map +1 -0
  96. package/dist/bin/postinstall.d.ts +3 -0
  97. package/dist/bin/postinstall.d.ts.map +1 -0
  98. package/dist/bin/postinstall.js +61 -0
  99. package/dist/bin/postinstall.js.map +1 -0
  100. package/dist/lib/autopilot-milestone.d.ts +2 -0
  101. package/dist/lib/autopilot-milestone.d.ts.map +1 -0
  102. package/dist/lib/autopilot-milestone.js +94 -0
  103. package/dist/lib/autopilot-milestone.js.map +1 -0
  104. package/dist/lib/autopilot-pipeline.d.ts +2 -0
  105. package/dist/lib/autopilot-pipeline.d.ts.map +1 -0
  106. package/dist/lib/autopilot-pipeline.js +830 -0
  107. package/dist/lib/autopilot-pipeline.js.map +1 -0
  108. package/dist/lib/autopilot-waves.d.ts +2 -0
  109. package/dist/lib/autopilot-waves.d.ts.map +1 -0
  110. package/dist/lib/autopilot-waves.js +266 -0
  111. package/dist/lib/autopilot-waves.js.map +1 -0
  112. package/dist/lib/autopilot.d.ts +2 -0
  113. package/dist/lib/autopilot.d.ts.map +1 -0
  114. package/dist/lib/autopilot.js +1314 -0
  115. package/dist/lib/autopilot.js.map +1 -0
  116. package/dist/lib/autoplan.d.ts +2 -0
  117. package/dist/lib/autoplan.d.ts.map +1 -0
  118. package/dist/lib/autoplan.js +198 -0
  119. package/dist/lib/autoplan.js.map +1 -0
  120. package/dist/lib/autoresearch.d.ts +2 -0
  121. package/dist/lib/autoresearch.d.ts.map +1 -0
  122. package/dist/lib/autoresearch.js +626 -0
  123. package/dist/lib/autoresearch.js.map +1 -0
  124. package/dist/lib/backend.d.ts +2 -0
  125. package/dist/lib/backend.d.ts.map +1 -0
  126. package/dist/lib/backend.js +1036 -0
  127. package/dist/lib/backend.js.map +1 -0
  128. package/dist/lib/benchmark.d.ts +99 -0
  129. package/dist/lib/benchmark.d.ts.map +1 -0
  130. package/dist/lib/benchmark.js +278 -0
  131. package/dist/lib/benchmark.js.map +1 -0
  132. package/dist/lib/citations.d.ts +2 -0
  133. package/dist/lib/citations.d.ts.map +1 -0
  134. package/dist/lib/citations.js +642 -0
  135. package/dist/lib/citations.js.map +1 -0
  136. package/dist/lib/cleanup.d.ts +2 -0
  137. package/dist/lib/cleanup.d.ts.map +1 -0
  138. package/dist/lib/cleanup.js +1222 -0
  139. package/dist/lib/cleanup.js.map +1 -0
  140. package/dist/lib/cli/adapters.d.ts +10 -0
  141. package/dist/lib/cli/adapters.d.ts.map +1 -0
  142. package/dist/lib/cli/adapters.js +27 -0
  143. package/dist/lib/cli/adapters.js.map +1 -0
  144. package/dist/lib/cli/agent.d.ts +17 -0
  145. package/dist/lib/cli/agent.d.ts.map +1 -0
  146. package/dist/lib/cli/agent.js +53 -0
  147. package/dist/lib/cli/agent.js.map +1 -0
  148. package/dist/lib/cli/index.d.ts +21 -0
  149. package/dist/lib/cli/index.d.ts.map +1 -0
  150. package/dist/lib/cli/index.js +264 -0
  151. package/dist/lib/cli/index.js.map +1 -0
  152. package/dist/lib/cli/output.d.ts +20 -0
  153. package/dist/lib/cli/output.d.ts.map +1 -0
  154. package/dist/lib/cli/output.js +22 -0
  155. package/dist/lib/cli/output.js.map +1 -0
  156. package/dist/lib/cli/scan-dispatch.d.ts +9 -0
  157. package/dist/lib/cli/scan-dispatch.d.ts.map +1 -0
  158. package/dist/lib/cli/scan-dispatch.js +107 -0
  159. package/dist/lib/cli/scan-dispatch.js.map +1 -0
  160. package/dist/lib/cli/tools.d.ts +16 -0
  161. package/dist/lib/cli/tools.d.ts.map +1 -0
  162. package/dist/lib/cli/tools.js +168 -0
  163. package/dist/lib/cli/tools.js.map +1 -0
  164. package/dist/lib/commands/_dashboard-parsers.d.ts +2 -0
  165. package/dist/lib/commands/_dashboard-parsers.d.ts.map +1 -0
  166. package/dist/lib/commands/_dashboard-parsers.js +192 -0
  167. package/dist/lib/commands/_dashboard-parsers.js.map +1 -0
  168. package/dist/lib/commands/analysis.d.ts +2 -0
  169. package/dist/lib/commands/analysis.d.ts.map +1 -0
  170. package/dist/lib/commands/analysis.js +1418 -0
  171. package/dist/lib/commands/analysis.js.map +1 -0
  172. package/dist/lib/commands/assumptions.d.ts +2 -0
  173. package/dist/lib/commands/assumptions.d.ts.map +1 -0
  174. package/dist/lib/commands/assumptions.js +166 -0
  175. package/dist/lib/commands/assumptions.js.map +1 -0
  176. package/dist/lib/commands/blame.d.ts +2 -0
  177. package/dist/lib/commands/blame.d.ts.map +1 -0
  178. package/dist/lib/commands/blame.js +133 -0
  179. package/dist/lib/commands/blame.js.map +1 -0
  180. package/dist/lib/commands/budget.d.ts +2 -0
  181. package/dist/lib/commands/budget.d.ts.map +1 -0
  182. package/dist/lib/commands/budget.js +100 -0
  183. package/dist/lib/commands/budget.js.map +1 -0
  184. package/dist/lib/commands/check-plans.d.ts +2 -0
  185. package/dist/lib/commands/check-plans.d.ts.map +1 -0
  186. package/dist/lib/commands/check-plans.js +190 -0
  187. package/dist/lib/commands/check-plans.js.map +1 -0
  188. package/dist/lib/commands/config.d.ts +2 -0
  189. package/dist/lib/commands/config.d.ts.map +1 -0
  190. package/dist/lib/commands/config.js +188 -0
  191. package/dist/lib/commands/config.js.map +1 -0
  192. package/dist/lib/commands/dashboard.d.ts +2 -0
  193. package/dist/lib/commands/dashboard.d.ts.map +1 -0
  194. package/dist/lib/commands/dashboard.js +466 -0
  195. package/dist/lib/commands/dashboard.js.map +1 -0
  196. package/dist/lib/commands/estimate.d.ts +2 -0
  197. package/dist/lib/commands/estimate.d.ts.map +1 -0
  198. package/dist/lib/commands/estimate.js +148 -0
  199. package/dist/lib/commands/estimate.js.map +1 -0
  200. package/dist/lib/commands/eval-diff.d.ts +2 -0
  201. package/dist/lib/commands/eval-diff.d.ts.map +1 -0
  202. package/dist/lib/commands/eval-diff.js +213 -0
  203. package/dist/lib/commands/eval-diff.js.map +1 -0
  204. package/dist/lib/commands/freshness.d.ts +2 -0
  205. package/dist/lib/commands/freshness.d.ts.map +1 -0
  206. package/dist/lib/commands/freshness.js +163 -0
  207. package/dist/lib/commands/freshness.js.map +1 -0
  208. package/dist/lib/commands/health.d.ts +2 -0
  209. package/dist/lib/commands/health.d.ts.map +1 -0
  210. package/dist/lib/commands/health.js +435 -0
  211. package/dist/lib/commands/health.js.map +1 -0
  212. package/dist/lib/commands/index.d.ts +2 -0
  213. package/dist/lib/commands/index.d.ts.map +1 -0
  214. package/dist/lib/commands/index.js +128 -0
  215. package/dist/lib/commands/index.js.map +1 -0
  216. package/dist/lib/commands/install.d.ts +56 -0
  217. package/dist/lib/commands/install.d.ts.map +1 -0
  218. package/dist/lib/commands/install.js +214 -0
  219. package/dist/lib/commands/install.js.map +1 -0
  220. package/dist/lib/commands/knowhow-aggregator.d.ts +2 -0
  221. package/dist/lib/commands/knowhow-aggregator.d.ts.map +1 -0
  222. package/dist/lib/commands/knowhow-aggregator.js +279 -0
  223. package/dist/lib/commands/knowhow-aggregator.js.map +1 -0
  224. package/dist/lib/commands/knowledge-search.d.ts +2 -0
  225. package/dist/lib/commands/knowledge-search.d.ts.map +1 -0
  226. package/dist/lib/commands/knowledge-search.js +113 -0
  227. package/dist/lib/commands/knowledge-search.js.map +1 -0
  228. package/dist/lib/commands/long-term-roadmap.d.ts +2 -0
  229. package/dist/lib/commands/long-term-roadmap.d.ts.map +1 -0
  230. package/dist/lib/commands/long-term-roadmap.js +272 -0
  231. package/dist/lib/commands/long-term-roadmap.js.map +1 -0
  232. package/dist/lib/commands/patterns.d.ts +91 -0
  233. package/dist/lib/commands/patterns.d.ts.map +1 -0
  234. package/dist/lib/commands/patterns.js +391 -0
  235. package/dist/lib/commands/patterns.js.map +1 -0
  236. package/dist/lib/commands/phase-info.d.ts +2 -0
  237. package/dist/lib/commands/phase-info.d.ts.map +1 -0
  238. package/dist/lib/commands/phase-info.js +509 -0
  239. package/dist/lib/commands/phase-info.js.map +1 -0
  240. package/dist/lib/commands/plan-lint.d.ts +56 -0
  241. package/dist/lib/commands/plan-lint.d.ts.map +1 -0
  242. package/dist/lib/commands/plan-lint.js +481 -0
  243. package/dist/lib/commands/plan-lint.js.map +1 -0
  244. package/dist/lib/commands/plan-phase.d.ts +53 -0
  245. package/dist/lib/commands/plan-phase.d.ts.map +1 -0
  246. package/dist/lib/commands/plan-phase.js +288 -0
  247. package/dist/lib/commands/plan-phase.js.map +1 -0
  248. package/dist/lib/commands/progress.d.ts +2 -0
  249. package/dist/lib/commands/progress.d.ts.map +1 -0
  250. package/dist/lib/commands/progress.js +266 -0
  251. package/dist/lib/commands/progress.js.map +1 -0
  252. package/dist/lib/commands/quality.d.ts +2 -0
  253. package/dist/lib/commands/quality.d.ts.map +1 -0
  254. package/dist/lib/commands/quality.js +80 -0
  255. package/dist/lib/commands/quality.js.map +1 -0
  256. package/dist/lib/commands/rollback.d.ts +2 -0
  257. package/dist/lib/commands/rollback.d.ts.map +1 -0
  258. package/dist/lib/commands/rollback.js +145 -0
  259. package/dist/lib/commands/rollback.js.map +1 -0
  260. package/dist/lib/commands/scan.d.ts +25 -0
  261. package/dist/lib/commands/scan.d.ts.map +1 -0
  262. package/dist/lib/commands/scan.js +28 -0
  263. package/dist/lib/commands/scan.js.map +1 -0
  264. package/dist/lib/commands/search.d.ts +2 -0
  265. package/dist/lib/commands/search.d.ts.map +1 -0
  266. package/dist/lib/commands/search.js +212 -0
  267. package/dist/lib/commands/search.js.map +1 -0
  268. package/dist/lib/commands/select-candidate.d.ts +128 -0
  269. package/dist/lib/commands/select-candidate.d.ts.map +1 -0
  270. package/dist/lib/commands/select-candidate.js +518 -0
  271. package/dist/lib/commands/select-candidate.js.map +1 -0
  272. package/dist/lib/commands/singularity.d.ts +2 -0
  273. package/dist/lib/commands/singularity.d.ts.map +1 -0
  274. package/dist/lib/commands/singularity.js +185 -0
  275. package/dist/lib/commands/singularity.js.map +1 -0
  276. package/dist/lib/commands/slug-timestamp.d.ts +2 -0
  277. package/dist/lib/commands/slug-timestamp.d.ts.map +1 -0
  278. package/dist/lib/commands/slug-timestamp.js +54 -0
  279. package/dist/lib/commands/slug-timestamp.js.map +1 -0
  280. package/dist/lib/commands/tail.d.ts +2 -0
  281. package/dist/lib/commands/tail.d.ts.map +1 -0
  282. package/dist/lib/commands/tail.js +100 -0
  283. package/dist/lib/commands/tail.js.map +1 -0
  284. package/dist/lib/commands/todo.d.ts +2 -0
  285. package/dist/lib/commands/todo.d.ts.map +1 -0
  286. package/dist/lib/commands/todo.js +200 -0
  287. package/dist/lib/commands/todo.js.map +1 -0
  288. package/dist/lib/commands/watch.d.ts +2 -0
  289. package/dist/lib/commands/watch.d.ts.map +1 -0
  290. package/dist/lib/commands/watch.js +72 -0
  291. package/dist/lib/commands/watch.js.map +1 -0
  292. package/dist/lib/complexity.d.ts +55 -0
  293. package/dist/lib/complexity.d.ts.map +1 -0
  294. package/dist/lib/complexity.js +80 -0
  295. package/dist/lib/complexity.js.map +1 -0
  296. package/dist/lib/context/agents.d.ts +2 -0
  297. package/dist/lib/context/agents.d.ts.map +1 -0
  298. package/dist/lib/context/agents.js +344 -0
  299. package/dist/lib/context/agents.js.map +1 -0
  300. package/dist/lib/context/base.d.ts +2 -0
  301. package/dist/lib/context/base.d.ts.map +1 -0
  302. package/dist/lib/context/base.js +81 -0
  303. package/dist/lib/context/base.js.map +1 -0
  304. package/dist/lib/context/execute.d.ts +2 -0
  305. package/dist/lib/context/execute.d.ts.map +1 -0
  306. package/dist/lib/context/execute.js +753 -0
  307. package/dist/lib/context/execute.js.map +1 -0
  308. package/dist/lib/context/index.d.ts +2 -0
  309. package/dist/lib/context/index.d.ts.map +1 -0
  310. package/dist/lib/context/index.js +88 -0
  311. package/dist/lib/context/index.js.map +1 -0
  312. package/dist/lib/context/progress.d.ts +2 -0
  313. package/dist/lib/context/progress.d.ts.map +1 -0
  314. package/dist/lib/context/progress.js +178 -0
  315. package/dist/lib/context/progress.js.map +1 -0
  316. package/dist/lib/context/project.d.ts +2 -0
  317. package/dist/lib/context/project.d.ts.map +1 -0
  318. package/dist/lib/context/project.js +413 -0
  319. package/dist/lib/context/project.js.map +1 -0
  320. package/dist/lib/context/research.d.ts +2 -0
  321. package/dist/lib/context/research.d.ts.map +1 -0
  322. package/dist/lib/context/research.js +466 -0
  323. package/dist/lib/context/research.js.map +1 -0
  324. package/dist/lib/dead-ends.d.ts +28 -0
  325. package/dist/lib/dead-ends.d.ts.map +1 -0
  326. package/dist/lib/dead-ends.js +451 -0
  327. package/dist/lib/dead-ends.js.map +1 -0
  328. package/dist/lib/deps.d.ts +2 -0
  329. package/dist/lib/deps.d.ts.map +1 -0
  330. package/dist/lib/deps.js +630 -0
  331. package/dist/lib/deps.js.map +1 -0
  332. package/dist/lib/discussion.d.ts +2 -0
  333. package/dist/lib/discussion.d.ts.map +1 -0
  334. package/dist/lib/discussion.js +1041 -0
  335. package/dist/lib/discussion.js.map +1 -0
  336. package/dist/lib/drift.d.ts +36 -0
  337. package/dist/lib/drift.d.ts.map +1 -0
  338. package/dist/lib/drift.js +481 -0
  339. package/dist/lib/drift.js.map +1 -0
  340. package/dist/lib/evolve/_dimensions-features.d.ts +2 -0
  341. package/dist/lib/evolve/_dimensions-features.d.ts.map +1 -0
  342. package/dist/lib/evolve/_dimensions-features.js +369 -0
  343. package/dist/lib/evolve/_dimensions-features.js.map +1 -0
  344. package/dist/lib/evolve/_dimensions.d.ts +2 -0
  345. package/dist/lib/evolve/_dimensions.d.ts.map +1 -0
  346. package/dist/lib/evolve/_dimensions.js +358 -0
  347. package/dist/lib/evolve/_dimensions.js.map +1 -0
  348. package/dist/lib/evolve/_product-ideation.d.ts +2 -0
  349. package/dist/lib/evolve/_product-ideation.d.ts.map +1 -0
  350. package/dist/lib/evolve/_product-ideation.js +281 -0
  351. package/dist/lib/evolve/_product-ideation.js.map +1 -0
  352. package/dist/lib/evolve/_prompts.d.ts +2 -0
  353. package/dist/lib/evolve/_prompts.d.ts.map +1 -0
  354. package/dist/lib/evolve/_prompts.js +153 -0
  355. package/dist/lib/evolve/_prompts.js.map +1 -0
  356. package/dist/lib/evolve/cli.d.ts +2 -0
  357. package/dist/lib/evolve/cli.d.ts.map +1 -0
  358. package/dist/lib/evolve/cli.js +224 -0
  359. package/dist/lib/evolve/cli.js.map +1 -0
  360. package/dist/lib/evolve/discovery.d.ts +2 -0
  361. package/dist/lib/evolve/discovery.d.ts.map +1 -0
  362. package/dist/lib/evolve/discovery.js +391 -0
  363. package/dist/lib/evolve/discovery.js.map +1 -0
  364. package/dist/lib/evolve/index.d.ts +2 -0
  365. package/dist/lib/evolve/index.d.ts.map +1 -0
  366. package/dist/lib/evolve/index.js +88 -0
  367. package/dist/lib/evolve/index.js.map +1 -0
  368. package/dist/lib/evolve/orchestrator.d.ts +2 -0
  369. package/dist/lib/evolve/orchestrator.d.ts.map +1 -0
  370. package/dist/lib/evolve/orchestrator.js +851 -0
  371. package/dist/lib/evolve/orchestrator.js.map +1 -0
  372. package/dist/lib/evolve/scoring.d.ts +2 -0
  373. package/dist/lib/evolve/scoring.d.ts.map +1 -0
  374. package/dist/lib/evolve/scoring.js +118 -0
  375. package/dist/lib/evolve/scoring.js.map +1 -0
  376. package/dist/lib/evolve/state.d.ts +2 -0
  377. package/dist/lib/evolve/state.d.ts.map +1 -0
  378. package/dist/lib/evolve/state.js +264 -0
  379. package/dist/lib/evolve/state.js.map +1 -0
  380. package/dist/lib/evolve/types.d.ts +249 -0
  381. package/dist/lib/evolve/types.d.ts.map +1 -0
  382. package/dist/lib/evolve/types.js +3 -0
  383. package/dist/lib/evolve/types.js.map +1 -0
  384. package/dist/lib/frontmatter.d.ts +2 -0
  385. package/dist/lib/frontmatter.d.ts.map +1 -0
  386. package/dist/lib/frontmatter.js +513 -0
  387. package/dist/lib/frontmatter.js.map +1 -0
  388. package/dist/lib/gates.d.ts +2 -0
  389. package/dist/lib/gates.d.ts.map +1 -0
  390. package/dist/lib/gates.js +578 -0
  391. package/dist/lib/gates.js.map +1 -0
  392. package/dist/lib/genome.d.ts +10 -0
  393. package/dist/lib/genome.d.ts.map +1 -0
  394. package/dist/lib/genome.js +368 -0
  395. package/dist/lib/genome.js.map +1 -0
  396. package/dist/lib/got.d.ts +2 -0
  397. package/dist/lib/got.d.ts.map +1 -0
  398. package/dist/lib/got.js +280 -0
  399. package/dist/lib/got.js.map +1 -0
  400. package/dist/lib/invariants.d.ts +2 -0
  401. package/dist/lib/invariants.d.ts.map +1 -0
  402. package/dist/lib/invariants.js +298 -0
  403. package/dist/lib/invariants.js.map +1 -0
  404. package/dist/lib/knowledge.d.ts +2 -0
  405. package/dist/lib/knowledge.d.ts.map +1 -0
  406. package/dist/lib/knowledge.js +658 -0
  407. package/dist/lib/knowledge.js.map +1 -0
  408. package/dist/lib/long-term-roadmap.d.ts +2 -0
  409. package/dist/lib/long-term-roadmap.d.ts.map +1 -0
  410. package/dist/lib/long-term-roadmap.js +602 -0
  411. package/dist/lib/long-term-roadmap.js.map +1 -0
  412. package/dist/lib/markdown-split.d.ts +2 -0
  413. package/dist/lib/markdown-split.d.ts.map +1 -0
  414. package/dist/lib/markdown-split.js +199 -0
  415. package/dist/lib/markdown-split.js.map +1 -0
  416. package/dist/lib/mcp-server.d.ts +2 -0
  417. package/dist/lib/mcp-server.d.ts.map +1 -0
  418. package/dist/lib/mcp-server.js +2424 -0
  419. package/dist/lib/mcp-server.js.map +1 -0
  420. package/dist/lib/metrics.d.ts +16 -0
  421. package/dist/lib/metrics.d.ts.map +1 -0
  422. package/dist/lib/metrics.js +48 -0
  423. package/dist/lib/metrics.js.map +1 -0
  424. package/dist/lib/overstory.d.ts +2 -0
  425. package/dist/lib/overstory.d.ts.map +1 -0
  426. package/dist/lib/overstory.js +211 -0
  427. package/dist/lib/overstory.js.map +1 -0
  428. package/dist/lib/parallel.d.ts +2 -0
  429. package/dist/lib/parallel.d.ts.map +1 -0
  430. package/dist/lib/parallel.js +349 -0
  431. package/dist/lib/parallel.js.map +1 -0
  432. package/dist/lib/paths.d.ts +2 -0
  433. package/dist/lib/paths.d.ts.map +1 -0
  434. package/dist/lib/paths.js +254 -0
  435. package/dist/lib/paths.js.map +1 -0
  436. package/dist/lib/phase-complete-llm.d.ts +22 -0
  437. package/dist/lib/phase-complete-llm.d.ts.map +1 -0
  438. package/dist/lib/phase-complete-llm.js +331 -0
  439. package/dist/lib/phase-complete-llm.js.map +1 -0
  440. package/dist/lib/phase-complete.d.ts +46 -0
  441. package/dist/lib/phase-complete.d.ts.map +1 -0
  442. package/dist/lib/phase-complete.js +278 -0
  443. package/dist/lib/phase-complete.js.map +1 -0
  444. package/dist/lib/phase-io.d.ts +2 -0
  445. package/dist/lib/phase-io.d.ts.map +1 -0
  446. package/dist/lib/phase-io.js +126 -0
  447. package/dist/lib/phase-io.js.map +1 -0
  448. package/dist/lib/phase.d.ts +2 -0
  449. package/dist/lib/phase.d.ts.map +1 -0
  450. package/dist/lib/phase.js +1344 -0
  451. package/dist/lib/phase.js.map +1 -0
  452. package/dist/lib/plan-tournament.d.ts +63 -0
  453. package/dist/lib/plan-tournament.d.ts.map +1 -0
  454. package/dist/lib/plan-tournament.js +353 -0
  455. package/dist/lib/plan-tournament.js.map +1 -0
  456. package/dist/lib/refinement.d.ts +74 -0
  457. package/dist/lib/refinement.d.ts.map +1 -0
  458. package/dist/lib/refinement.js +283 -0
  459. package/dist/lib/refinement.js.map +1 -0
  460. package/dist/lib/requirements.d.ts +2 -0
  461. package/dist/lib/requirements.d.ts.map +1 -0
  462. package/dist/lib/requirements.js +355 -0
  463. package/dist/lib/requirements.js.map +1 -0
  464. package/dist/lib/research-bundle.d.ts +2 -0
  465. package/dist/lib/research-bundle.d.ts.map +1 -0
  466. package/dist/lib/research-bundle.js +246 -0
  467. package/dist/lib/research-bundle.js.map +1 -0
  468. package/dist/lib/roadmap.d.ts +2 -0
  469. package/dist/lib/roadmap.d.ts.map +1 -0
  470. package/dist/lib/roadmap.js +541 -0
  471. package/dist/lib/roadmap.js.map +1 -0
  472. package/dist/lib/sample.d.ts +16 -0
  473. package/dist/lib/sample.d.ts.map +1 -0
  474. package/dist/lib/sample.js +20 -0
  475. package/dist/lib/sample.js.map +1 -0
  476. package/dist/lib/scaffold.d.ts +2 -0
  477. package/dist/lib/scaffold.d.ts.map +1 -0
  478. package/dist/lib/scaffold.js +355 -0
  479. package/dist/lib/scaffold.js.map +1 -0
  480. package/dist/lib/scan/_utils.d.ts +11 -0
  481. package/dist/lib/scan/_utils.d.ts.map +1 -0
  482. package/dist/lib/scan/_utils.js +36 -0
  483. package/dist/lib/scan/_utils.js.map +1 -0
  484. package/dist/lib/scan/base64.d.ts +15 -0
  485. package/dist/lib/scan/base64.d.ts.map +1 -0
  486. package/dist/lib/scan/base64.js +66 -0
  487. package/dist/lib/scan/base64.js.map +1 -0
  488. package/dist/lib/scan/ignorefile.d.ts +30 -0
  489. package/dist/lib/scan/ignorefile.d.ts.map +1 -0
  490. package/dist/lib/scan/ignorefile.js +101 -0
  491. package/dist/lib/scan/ignorefile.js.map +1 -0
  492. package/dist/lib/scan/injection.d.ts +14 -0
  493. package/dist/lib/scan/injection.d.ts.map +1 -0
  494. package/dist/lib/scan/injection.js +39 -0
  495. package/dist/lib/scan/injection.js.map +1 -0
  496. package/dist/lib/scan/patterns.d.ts +17 -0
  497. package/dist/lib/scan/patterns.d.ts.map +1 -0
  498. package/dist/lib/scan/patterns.js +123 -0
  499. package/dist/lib/scan/patterns.js.map +1 -0
  500. package/dist/lib/scan/strip-markdown.d.ts +7 -0
  501. package/dist/lib/scan/strip-markdown.d.ts.map +1 -0
  502. package/dist/lib/scan/strip-markdown.js +38 -0
  503. package/dist/lib/scan/strip-markdown.js.map +1 -0
  504. package/dist/lib/scan/types.d.ts +23 -0
  505. package/dist/lib/scan/types.d.ts.map +1 -0
  506. package/dist/lib/scan/types.js +3 -0
  507. package/dist/lib/scan/types.js.map +1 -0
  508. package/dist/lib/scheduler-wait.d.ts +2 -0
  509. package/dist/lib/scheduler-wait.d.ts.map +1 -0
  510. package/dist/lib/scheduler-wait.js +59 -0
  511. package/dist/lib/scheduler-wait.js.map +1 -0
  512. package/dist/lib/scheduler.d.ts +254 -0
  513. package/dist/lib/scheduler.d.ts.map +1 -0
  514. package/dist/lib/scheduler.js +1147 -0
  515. package/dist/lib/scheduler.js.map +1 -0
  516. package/dist/lib/state.d.ts +2 -0
  517. package/dist/lib/state.d.ts.map +1 -0
  518. package/dist/lib/state.js +744 -0
  519. package/dist/lib/state.js.map +1 -0
  520. package/dist/lib/think.d.ts +18 -0
  521. package/dist/lib/think.d.ts.map +1 -0
  522. package/dist/lib/think.js +317 -0
  523. package/dist/lib/think.js.map +1 -0
  524. package/dist/lib/tracker.d.ts +2 -0
  525. package/dist/lib/tracker.d.ts.map +1 -0
  526. package/dist/lib/tracker.js +1121 -0
  527. package/dist/lib/tracker.js.map +1 -0
  528. package/dist/lib/types.d.ts +1514 -0
  529. package/dist/lib/types.d.ts.map +1 -0
  530. package/dist/lib/types.js +4 -0
  531. package/dist/lib/types.js.map +1 -0
  532. package/dist/lib/utils.d.ts +2 -0
  533. package/dist/lib/utils.d.ts.map +1 -0
  534. package/dist/lib/utils.js +1363 -0
  535. package/dist/lib/utils.js.map +1 -0
  536. package/dist/lib/verify.d.ts +2 -0
  537. package/dist/lib/verify.d.ts.map +1 -0
  538. package/dist/lib/verify.js +1153 -0
  539. package/dist/lib/verify.js.map +1 -0
  540. package/dist/lib/wireup/autofix.d.ts +2 -0
  541. package/dist/lib/wireup/autofix.d.ts.map +1 -0
  542. package/dist/lib/wireup/autofix.js +188 -0
  543. package/dist/lib/wireup/autofix.js.map +1 -0
  544. package/dist/lib/wireup/cli.d.ts +2 -0
  545. package/dist/lib/wireup/cli.d.ts.map +1 -0
  546. package/dist/lib/wireup/cli.js +194 -0
  547. package/dist/lib/wireup/cli.js.map +1 -0
  548. package/dist/lib/wireup/detection.d.ts +47 -0
  549. package/dist/lib/wireup/detection.d.ts.map +1 -0
  550. package/dist/lib/wireup/detection.js +410 -0
  551. package/dist/lib/wireup/detection.js.map +1 -0
  552. package/dist/lib/wireup/discovery.d.ts +2 -0
  553. package/dist/lib/wireup/discovery.d.ts.map +1 -0
  554. package/dist/lib/wireup/discovery.js +934 -0
  555. package/dist/lib/wireup/discovery.js.map +1 -0
  556. package/dist/lib/wireup/execution.d.ts +2 -0
  557. package/dist/lib/wireup/execution.d.ts.map +1 -0
  558. package/dist/lib/wireup/execution.js +573 -0
  559. package/dist/lib/wireup/execution.js.map +1 -0
  560. package/dist/lib/wireup/index.d.ts +2 -0
  561. package/dist/lib/wireup/index.d.ts.map +1 -0
  562. package/dist/lib/wireup/index.js +85 -0
  563. package/dist/lib/wireup/index.js.map +1 -0
  564. package/dist/lib/wireup/orchestrator.d.ts +2 -0
  565. package/dist/lib/wireup/orchestrator.d.ts.map +1 -0
  566. package/dist/lib/wireup/orchestrator.js +366 -0
  567. package/dist/lib/wireup/orchestrator.js.map +1 -0
  568. package/dist/lib/wireup/report.d.ts +47 -0
  569. package/dist/lib/wireup/report.d.ts.map +1 -0
  570. package/dist/lib/wireup/report.js +201 -0
  571. package/dist/lib/wireup/report.js.map +1 -0
  572. package/dist/lib/wireup/scenarios.d.ts +2 -0
  573. package/dist/lib/wireup/scenarios.d.ts.map +1 -0
  574. package/dist/lib/wireup/scenarios.js +516 -0
  575. package/dist/lib/wireup/scenarios.js.map +1 -0
  576. package/dist/lib/wireup/state.d.ts +2 -0
  577. package/dist/lib/wireup/state.d.ts.map +1 -0
  578. package/dist/lib/wireup/state.js +102 -0
  579. package/dist/lib/wireup/state.js.map +1 -0
  580. package/dist/lib/wireup/types.d.ts +376 -0
  581. package/dist/lib/wireup/types.d.ts.map +1 -0
  582. package/dist/lib/wireup/types.js +3 -0
  583. package/dist/lib/wireup/types.js.map +1 -0
  584. package/dist/lib/worktree.d.ts +2 -0
  585. package/dist/lib/worktree.d.ts.map +1 -0
  586. package/dist/lib/worktree.js +999 -0
  587. package/dist/lib/worktree.js.map +1 -0
  588. package/lib/autopilot-milestone.ts +136 -0
  589. package/lib/autopilot-pipeline.ts +1179 -0
  590. package/lib/autopilot-waves.ts +361 -0
  591. package/lib/autopilot.ts +1874 -0
  592. package/lib/autoplan.ts +280 -0
  593. package/lib/autoresearch.js +4 -0
  594. package/lib/autoresearch.ts +886 -0
  595. package/lib/backend.ts +1252 -0
  596. package/lib/benchmark.ts +341 -0
  597. package/lib/citations.ts +760 -0
  598. package/lib/cleanup.ts +1588 -0
  599. package/lib/cli/adapters.ts +41 -0
  600. package/lib/cli/agent.ts +83 -0
  601. package/lib/cli/index.ts +273 -0
  602. package/lib/cli/output.ts +33 -0
  603. package/lib/cli/scan-dispatch.ts +130 -0
  604. package/lib/cli/tools.ts +198 -0
  605. package/lib/commands/_dashboard-parsers.ts +275 -0
  606. package/lib/commands/analysis.ts +1851 -0
  607. package/lib/commands/assumptions.ts +232 -0
  608. package/lib/commands/blame.ts +174 -0
  609. package/lib/commands/budget.ts +148 -0
  610. package/lib/commands/check-plans.ts +233 -0
  611. package/lib/commands/config.ts +287 -0
  612. package/lib/commands/dashboard.ts +680 -0
  613. package/lib/commands/estimate.ts +204 -0
  614. package/lib/commands/eval-diff.ts +252 -0
  615. package/lib/commands/freshness.ts +213 -0
  616. package/lib/commands/health.ts +607 -0
  617. package/lib/commands/index.ts +266 -0
  618. package/lib/commands/install.ts +307 -0
  619. package/lib/commands/knowhow-aggregator.ts +345 -0
  620. package/lib/commands/knowledge-search.ts +153 -0
  621. package/lib/commands/long-term-roadmap.ts +390 -0
  622. package/lib/commands/patterns.ts +465 -0
  623. package/lib/commands/phase-info.ts +698 -0
  624. package/lib/commands/plan-lint.ts +546 -0
  625. package/lib/commands/plan-phase.ts +375 -0
  626. package/lib/commands/progress.ts +319 -0
  627. package/lib/commands/quality.ts +138 -0
  628. package/lib/commands/rollback.ts +195 -0
  629. package/lib/commands/scan.ts +72 -0
  630. package/lib/commands/search.ts +300 -0
  631. package/lib/commands/select-candidate.ts +687 -0
  632. package/lib/commands/singularity.ts +222 -0
  633. package/lib/commands/slug-timestamp.ts +74 -0
  634. package/lib/commands/tail.ts +129 -0
  635. package/lib/commands/todo.ts +273 -0
  636. package/lib/commands/watch.ts +80 -0
  637. package/lib/complexity.ts +117 -0
  638. package/lib/context/agents.ts +505 -0
  639. package/lib/context/base.ts +123 -0
  640. package/lib/context/execute.ts +977 -0
  641. package/lib/context/index.ts +110 -0
  642. package/lib/context/progress.ts +278 -0
  643. package/lib/context/project.ts +531 -0
  644. package/lib/context/research.ts +646 -0
  645. package/lib/dead-ends.ts +506 -0
  646. package/lib/deps.ts +773 -0
  647. package/lib/discussion.ts +1275 -0
  648. package/lib/drift.ts +519 -0
  649. package/lib/evolve/_dimensions-features.ts +525 -0
  650. package/lib/evolve/_dimensions.ts +511 -0
  651. package/lib/evolve/_product-ideation.ts +405 -0
  652. package/lib/evolve/_prompts.ts +178 -0
  653. package/lib/evolve/cli.ts +330 -0
  654. package/lib/evolve/discovery.ts +571 -0
  655. package/lib/evolve/index.ts +105 -0
  656. package/lib/evolve/orchestrator.ts +1139 -0
  657. package/lib/evolve/scoring.ts +167 -0
  658. package/lib/evolve/state.ts +330 -0
  659. package/lib/evolve/types.ts +290 -0
  660. package/lib/frontmatter.ts +615 -0
  661. package/lib/gates.ts +695 -0
  662. package/lib/genome.ts +402 -0
  663. package/lib/got.js +4 -0
  664. package/lib/got.ts +361 -0
  665. package/lib/invariants.ts +378 -0
  666. package/lib/knowledge.ts +768 -0
  667. package/lib/long-term-roadmap.ts +806 -0
  668. package/lib/markdown-split.ts +273 -0
  669. package/lib/mcp-server.ts +3292 -0
  670. package/lib/metrics.ts +49 -0
  671. package/lib/overstory.ts +270 -0
  672. package/lib/parallel.ts +570 -0
  673. package/lib/paths.ts +293 -0
  674. package/lib/phase-complete-llm.ts +376 -0
  675. package/lib/phase-complete.ts +366 -0
  676. package/lib/phase-io.ts +101 -0
  677. package/lib/phase.ts +1981 -0
  678. package/lib/plan-tournament.ts +426 -0
  679. package/lib/refinement.ts +349 -0
  680. package/lib/requirements.ts +469 -0
  681. package/lib/research-bundle.ts +300 -0
  682. package/lib/roadmap.ts +775 -0
  683. package/lib/scaffold.ts +480 -0
  684. package/lib/scan/_utils.ts +37 -0
  685. package/lib/scan/base64.ts +90 -0
  686. package/lib/scan/ignorefile.ts +109 -0
  687. package/lib/scan/injection.ts +67 -0
  688. package/lib/scan/patterns.ts +139 -0
  689. package/lib/scan/strip-markdown.ts +39 -0
  690. package/lib/scan/types.ts +28 -0
  691. package/lib/scheduler-wait.ts +58 -0
  692. package/lib/scheduler.ts +1370 -0
  693. package/lib/state.ts +1000 -0
  694. package/lib/think.ts +365 -0
  695. package/lib/tracker.ts +1591 -0
  696. package/lib/types.ts +1663 -0
  697. package/lib/utils.ts +1479 -0
  698. package/lib/verify.ts +1434 -0
  699. package/lib/wireup/autofix.ts +241 -0
  700. package/lib/wireup/cli.ts +278 -0
  701. package/lib/wireup/detection.ts +542 -0
  702. package/lib/wireup/discovery.ts +1063 -0
  703. package/lib/wireup/execution.ts +686 -0
  704. package/lib/wireup/index.ts +117 -0
  705. package/lib/wireup/orchestrator.ts +519 -0
  706. package/lib/wireup/report.ts +286 -0
  707. package/lib/wireup/scenarios.ts +616 -0
  708. package/lib/wireup/state.ts +139 -0
  709. package/lib/wireup/types.ts +436 -0
  710. package/lib/worktree.ts +1309 -0
  711. 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
+ };