@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/verify.ts ADDED
@@ -0,0 +1,1434 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * GRD Verification Suite -- Plan structure, phase completeness, references, commits, artifacts, key-links
5
+ *
6
+ * Extracted from bin/grd-tools.js during Phase 03 modularization.
7
+ * Depends on: lib/utils.ts (safeReadFile, execGit, findPhaseInternal, validateGitRef, output, error)
8
+ * lib/frontmatter.ts (extractFrontmatter, parseMustHavesBlock)
9
+ */
10
+
11
+
12
+ import type { FrontmatterObject, PhaseInfo, ExecGitResult } from './types';
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const os = require('os');
17
+ const {
18
+ safeReadFile,
19
+ execGit,
20
+ findPhaseInternal,
21
+ validateGitRef,
22
+ output,
23
+ error,
24
+ }: {
25
+ safeReadFile: (filePath: string) => string | null;
26
+ execGit: (cwd: string, args: string[], opts?: { allowBlocked?: boolean }) => ExecGitResult;
27
+ findPhaseInternal: (cwd: string, phase: string) => PhaseInfo | null;
28
+ validateGitRef: (ref: string) => string;
29
+ output: (result: unknown, raw: boolean, rawValue?: unknown) => never;
30
+ error: (message: string) => never;
31
+ } = require('./utils');
32
+ const {
33
+ extractFrontmatter,
34
+ parseMustHavesBlock,
35
+ }: {
36
+ extractFrontmatter: (content: string) => FrontmatterObject;
37
+ parseMustHavesBlock: (content: string, field: string) => MustHavesEntry[];
38
+ } = require('./frontmatter');
39
+
40
+ // ─── Domain Types ────────────────────────────────────────────────────────────
41
+
42
+ /**
43
+ * A must_haves artifact entry from plan frontmatter.
44
+ */
45
+ interface MustHavesArtifact {
46
+ path: string;
47
+ provides?: string;
48
+ exports?: string | string[];
49
+ min_lines?: number;
50
+ contains?: string;
51
+ }
52
+
53
+ /**
54
+ * A must_haves key_links entry from plan frontmatter.
55
+ */
56
+ interface MustHavesKeyLink {
57
+ from: string;
58
+ to: string;
59
+ via?: string;
60
+ pattern?: string;
61
+ }
62
+
63
+ /**
64
+ * Union type for entries returned by parseMustHavesBlock.
65
+ */
66
+ type MustHavesEntry = string | MustHavesArtifact | MustHavesKeyLink;
67
+
68
+ /**
69
+ * Result of file creation check in summary verification.
70
+ */
71
+ interface FilesCreatedCheck {
72
+ checked: number;
73
+ found: number;
74
+ missing: string[];
75
+ }
76
+
77
+ /**
78
+ * Checks performed during summary verification.
79
+ */
80
+ interface SummaryVerifyChecks {
81
+ summary_exists: boolean;
82
+ files_created: FilesCreatedCheck;
83
+ commits_exist: boolean;
84
+ self_check: string;
85
+ }
86
+
87
+ /**
88
+ * Result of summary verification.
89
+ */
90
+ interface SummaryVerifyResult {
91
+ passed: boolean;
92
+ checks: SummaryVerifyChecks;
93
+ errors: string[];
94
+ }
95
+
96
+ /**
97
+ * Task info extracted from plan structure verification.
98
+ */
99
+ interface PlanTask {
100
+ name: string;
101
+ hasFiles: boolean;
102
+ hasAction: boolean;
103
+ hasVerify: boolean;
104
+ hasDone: boolean;
105
+ }
106
+
107
+ /**
108
+ * Result of plan structure verification.
109
+ */
110
+ interface PlanVerifyResult {
111
+ valid: boolean;
112
+ errors: string[];
113
+ warnings: string[];
114
+ task_count: number;
115
+ tasks: PlanTask[];
116
+ frontmatter_fields: string[];
117
+ found_sections: string[];
118
+ }
119
+
120
+ /**
121
+ * Result of phase completeness verification.
122
+ */
123
+ interface PhaseCompletenessResult {
124
+ complete: boolean;
125
+ phase: string;
126
+ plan_count: number;
127
+ summary_count: number;
128
+ incomplete_plans: string[];
129
+ orphan_summaries: string[];
130
+ errors: string[];
131
+ warnings: string[];
132
+ }
133
+
134
+ /**
135
+ * Result of reference verification.
136
+ */
137
+ interface ReferenceVerifyResult {
138
+ valid: boolean;
139
+ found: number;
140
+ missing: string[];
141
+ total: number;
142
+ }
143
+
144
+ /**
145
+ * Result of commit verification.
146
+ */
147
+ interface CommitVerifyResult {
148
+ all_valid: boolean;
149
+ valid: string[];
150
+ invalid: string[];
151
+ total: number;
152
+ }
153
+
154
+ /**
155
+ * Result of a single artifact check.
156
+ */
157
+ interface ArtifactCheck {
158
+ path: string;
159
+ exists: boolean;
160
+ issues: string[];
161
+ passed: boolean;
162
+ plan_file: string;
163
+ must_haves_field: string;
164
+ remediation?: string;
165
+ }
166
+
167
+ /**
168
+ * Result of artifact verification.
169
+ */
170
+ interface ArtifactVerifyResult {
171
+ all_passed: boolean;
172
+ passed: number;
173
+ total: number;
174
+ artifacts: ArtifactCheck[];
175
+ }
176
+
177
+ /**
178
+ * Result of a single key-link check.
179
+ */
180
+ interface KeyLinkCheck {
181
+ from: string;
182
+ to: string;
183
+ via: string;
184
+ verified: boolean;
185
+ detail: string;
186
+ }
187
+
188
+ /**
189
+ * Result of key-link verification.
190
+ */
191
+ interface KeyLinkVerifyResult {
192
+ all_verified: boolean;
193
+ verified: number;
194
+ total: number;
195
+ links: KeyLinkCheck[];
196
+ }
197
+
198
+ // Module-level cache for file reads within a single process invocation.
199
+ // Safe for verify functions since they never write files.
200
+ const _fileReadCache = new Map<string, string | null>();
201
+ function readFileCached(fullPath: string): string | null {
202
+ if (!_fileReadCache.has(fullPath)) {
203
+ _fileReadCache.set(fullPath, safeReadFile(fullPath));
204
+ }
205
+ return _fileReadCache.get(fullPath) as string | null;
206
+ }
207
+
208
+ /** Clear the module-level file read cache. Call in test beforeEach to prevent stale reads across tests. */
209
+ function clearVerifyCache(): void {
210
+ _fileReadCache.clear();
211
+ }
212
+
213
+ // ─── Verification Command Functions ──────────────────────────────────────────
214
+
215
+ /**
216
+ * CLI command: Verify SUMMARY.md structure including file existence, commit hashes, and self-check.
217
+ * @param cwd - Project working directory
218
+ * @param summaryPath - Relative path to the SUMMARY.md file
219
+ * @param checkFileCount - Number of mentioned files to spot-check for existence
220
+ * @param raw - Output raw 'passed'/'failed' instead of JSON
221
+ */
222
+ function cmdVerifySummary(
223
+ cwd: string,
224
+ summaryPath: string,
225
+ checkFileCount: number,
226
+ raw: boolean
227
+ ): void {
228
+ if (!summaryPath) {
229
+ error('summary-path required');
230
+ }
231
+
232
+ const fullPath: string = path.join(cwd, summaryPath);
233
+ const checkCount: number = checkFileCount || 2;
234
+
235
+ // Check 1: Summary exists
236
+ let content: string;
237
+ try {
238
+ content = fs.readFileSync(fullPath, 'utf-8');
239
+ } catch {
240
+ const result: SummaryVerifyResult = {
241
+ passed: false,
242
+ checks: {
243
+ summary_exists: false,
244
+ files_created: { checked: 0, found: 0, missing: [] },
245
+ commits_exist: false,
246
+ self_check: 'not_found',
247
+ },
248
+ errors: ['SUMMARY.md not found'],
249
+ };
250
+ output(result, raw, 'failed');
251
+ return;
252
+ }
253
+ const errors: string[] = [];
254
+
255
+ // Check 2: Spot-check files mentioned in summary
256
+ const mentionedFiles = new Set<string>();
257
+ const patterns: RegExp[] = [
258
+ /`([^`]+\.[a-zA-Z]+)`/g,
259
+ /(?:Created|Modified|Added|Updated|Edited):\s*`?([^\s`]+\.[a-zA-Z]+)`?/gi,
260
+ ];
261
+
262
+ for (const pattern of patterns) {
263
+ let m: RegExpExecArray | null;
264
+ while ((m = pattern.exec(content)) !== null) {
265
+ const filePath: string = m[1];
266
+ if (filePath && !filePath.startsWith('http') && filePath.includes('/')) {
267
+ mentionedFiles.add(filePath);
268
+ }
269
+ }
270
+ }
271
+
272
+ const filesToCheck: string[] = Array.from(mentionedFiles).slice(0, checkCount);
273
+ const missing: string[] = [];
274
+ for (const file of filesToCheck) {
275
+ if (!fs.existsSync(path.join(cwd, file))) {
276
+ missing.push(file);
277
+ }
278
+ }
279
+
280
+ // Check 3: Commits exist. Codex r15 P2: every referenced commit must
281
+ // exist (was sampling 3 and passing if any one resolved). Check the
282
+ // first ~10 unique hashes to keep the cost bounded but catch
283
+ // partial-failure cases.
284
+ // Codex r27 P2: a context-free hex-token regex also matches
285
+ // checksums, cache keys, content-hash IDs, and other non-commit
286
+ // hex strings. Anchor on explicit commit labels first (commit:,
287
+ // SHA:, ref:, hash:, parent:, `**Commit**`, hyperlinks to
288
+ // /commit/<sha>, conventional `(abcdef1)` parens), and only fall
289
+ // back to the bare hex scan if the labelled set is empty AND the
290
+ // SUMMARY explicitly mentions "commit" somewhere (so we keep the
291
+ // backward-compatible behaviour without false-failing on summaries
292
+ // that legitimately reference no commits).
293
+ // Codex r28 P2: a labelled `Commits: <a>, <b>, <c>` list contains
294
+ // multiple hashes. Capture the full label line (up to newline or
295
+ // next section) and pull every hex token from it, instead of only
296
+ // matching one hash per label.
297
+ // Codex r30 P2: only collect hex tokens that appear in commit
298
+ // contexts. Three kinds of context count:
299
+ // 1. Labelled lines: `Commit: <sha>`, `SHA: <sha>`, etc., and
300
+ // multi-hash lists like `Commits: <a>, <b>`. The line itself
301
+ // is the context; harvest every hex token on it.
302
+ // 2. /commit/<sha> hyperlinks.
303
+ // 3. A commits-block heading (`## (Task )?Commits`, `### Commits`,
304
+ // `# Commits`) — harvest every hex token in lines until the
305
+ // next heading at the same or shallower level, or the next
306
+ // blank line followed by a non-list line.
307
+ // Bare `(deadbeef)` parens and the prior whole-document scan are
308
+ // dropped — they re-introduced the false positives r27 was trying
309
+ // to fix.
310
+ const labelledHashes: string[] = [];
311
+ // Codex r32 P2: accept bold/bulleted label forms documented in
312
+ // agents/grd-executor.md, e.g.:
313
+ // - **Commits:** abc, def
314
+ // **Commits:**
315
+ // - abc
316
+ // Match the *label line* generically, then if it's a bare label
317
+ // (no hash on the same line) sweep subsequent bullet rows.
318
+ const linesAll = content.split('\n');
319
+ for (let i = 0; i < linesAll.length; i++) {
320
+ const line = linesAll[i];
321
+ // Codex r34 P2: also accept colonless forms used in real
322
+ // SUMMARYs:
323
+ // - `Commit 29c6883 exists` (label + space + hash)
324
+ // - `### Task 1 ... (29c6883)` (paren-suffix on a
325
+ // heading/task line)
326
+ // - `- [x] Task X completed (abc1234)` (paren-suffix on a
327
+ // checklist line)
328
+ const isColonLabel = /(?:[-*]\s+|^|\*\*)\s*(?:commit|sha|ref|hash|parent)s?\s*[:*]/i.test(line);
329
+ // Codex r38 P2: colonless `Commits a, b, c` should capture every
330
+ // hash on the line, not just the first. Detect the colonless
331
+ // label and then harvest *all* hex tokens from the line.
332
+ // Codex r39 P2: also accept backticked hashes after a colonless
333
+ // label, e.g. `- [x] Commit \`8880489\` exists`. Allow optional
334
+ // backticks between the label and the hash.
335
+ const hasColonlessLabel = /\b(?:commit|sha|ref|hash|parent)s?\s+`?[0-9a-f]{7,40}`?\b/i.test(line);
336
+ const colonlessHashes = hasColonlessLabel
337
+ ? line.match(/\b[0-9a-f]{7,40}\b/gi) ?? []
338
+ : [];
339
+ // Codex r35 P2: paren-suffix `(<sha>)` is ambiguous — it's also
340
+ // used for checksums/artifact IDs. Require the line itself to
341
+ // mention something commit-flavored OR be a task-completion
342
+ // checklist marker (`- [x]`). Plain headings like
343
+ // `### Artifact checksum (deadbeef)` are no longer matched.
344
+ const isCheckedTask = /^\s*[-*]\s+\[x\]\s+/i.test(line);
345
+ const lineHasCommitWord = /\b(?:commit|merge|landed|shipped)\b/i.test(line);
346
+ // Codex r41 P2: also accept task-heading paren suffixes such as
347
+ // `### Task 1: add parser (deadbee)`. The heading-with-"Task"
348
+ // signal disambiguates from `### Artifact checksum (deadbeef)`.
349
+ const isTaskHeading = /^#{1,6}\s+(?:Task|Step|Plan|Phase|Subtask)\b/i.test(line);
350
+ const parenSuffixMatch =
351
+ (isCheckedTask || lineHasCommitWord || isTaskHeading)
352
+ ? line.match(/\(([0-9a-f]{7,40})\)\s*$/i)
353
+ : null;
354
+ for (const h of colonlessHashes) {
355
+ labelledHashes.push(h.toLowerCase());
356
+ }
357
+ if (parenSuffixMatch) {
358
+ labelledHashes.push(parenSuffixMatch[1].toLowerCase());
359
+ }
360
+ if (!isColonLabel) {
361
+ continue;
362
+ }
363
+ // Codex r36 P2: use word boundaries so a SHA-256 artifact hash
364
+ // (64 hex chars) isn't truncated to its first 40 and treated as
365
+ // a commit. Boundary-aware match is consistent with the table
366
+ // and block scanners below.
367
+ const hashesOnLine = line.match(/\b[0-9a-f]{7,40}\b/gi) ?? [];
368
+ if (hashesOnLine.length > 0) {
369
+ for (const h of hashesOnLine) labelledHashes.push(h.toLowerCase());
370
+ continue;
371
+ }
372
+ // Bare label — sweep following bullet lines until indent drops
373
+ // back or a non-bullet line appears.
374
+ for (let j = i + 1; j < linesAll.length; j++) {
375
+ const row = linesAll[j];
376
+ if (/^\s*$/.test(row)) continue;
377
+ if (!/^\s*[-*]\s+/.test(row)) break;
378
+ for (const h of row.match(/\b[0-9a-f]{7,40}\b/gi) ?? []) {
379
+ labelledHashes.push(h.toLowerCase());
380
+ }
381
+ }
382
+ }
383
+ for (const m of content.matchAll(/\/commit\/([0-9a-f]{7,40})\b/gi)) {
384
+ labelledHashes.push(m[1].toLowerCase());
385
+ }
386
+ // Codex r35 P2: a `## Commits` section can contain nested `### Task N`
387
+ // subheadings with the actual hashes. The prior pattern stopped at
388
+ // any heading of any depth, missing those. Walk lines explicitly:
389
+ // when we hit a `## Commits` heading at depth D, harvest hashes
390
+ // from following lines until we see a heading at depth ≤ D
391
+ // (siblings or shallower).
392
+ const headingRe = /^(#{1,6})\s+(?:Task\s+)?Commits?\s*$/i;
393
+ for (let i = 0; i < linesAll.length; i++) {
394
+ const headingMatch = linesAll[i].match(headingRe);
395
+ if (!headingMatch) continue;
396
+ const depth = headingMatch[1].length;
397
+ for (let j = i + 1; j < linesAll.length; j++) {
398
+ const nextHeading = linesAll[j].match(/^(#{1,6})\s+/);
399
+ if (nextHeading && nextHeading[1].length <= depth) break;
400
+ for (const h of linesAll[j].match(/\b[0-9a-f]{7,40}\b/gi) ?? []) {
401
+ labelledHashes.push(h.toLowerCase());
402
+ }
403
+ }
404
+ }
405
+ // Codex r31 P2 / r33 P2: markdown tables with a `Commit` column
406
+ // header. Find the commit-column INDEX from the header row, then
407
+ // only harvest hex tokens from that specific cell in each row —
408
+ // otherwise a `| task | commit | checksum |` table would also try
409
+ // to validate the unrelated `checksum` column against git.
410
+ const lines = content.split('\n');
411
+ // Codex r37 P2: only drop the trailing-pipe artifact when one
412
+ // actually exists. Otherwise tables without a trailing `|`
413
+ // (`| Task | Commit` / `| 1 | deadbeef`) lose their last real
414
+ // cell — the Commit column — and the scanner misses every hash.
415
+ const splitCells = (s: string): string[] => {
416
+ const trimmed = s.trimEnd();
417
+ const hasTrailingPipe = trimmed.endsWith('|');
418
+ const cells = trimmed.split('|').map((c) => c.trim());
419
+ cells.shift(); // drop leading empty before first `|`
420
+ if (hasTrailingPipe) cells.pop();
421
+ return cells;
422
+ };
423
+ for (let i = 0; i < lines.length; i++) {
424
+ // Codex r40 P2: trim leading whitespace before checking for table
425
+ // start — indented tables (inside a list or quoted block) also
426
+ // need their commit column scanned.
427
+ const line = lines[i].trimStart();
428
+ if (
429
+ !line.startsWith('|') ||
430
+ !/\bcommit\b/i.test(line) ||
431
+ !lines[i + 1]?.match(/^\s*\|?\s*[-:]+/)
432
+ ) {
433
+ continue;
434
+ }
435
+ const headerCells = splitCells(line);
436
+ const commitColIdx = headerCells.findIndex((c) => /\bcommit\b/i.test(c));
437
+ if (commitColIdx === -1) continue;
438
+ for (let j = i + 2; j < lines.length; j++) {
439
+ const row = lines[j].trimStart();
440
+ if (!row.startsWith('|')) break;
441
+ const rowCells = splitCells(row);
442
+ const cell = rowCells[commitColIdx] ?? '';
443
+ for (const h of cell.match(/\b[0-9a-f]{7,40}\b/gi) ?? []) {
444
+ labelledHashes.push(h.toLowerCase());
445
+ }
446
+ }
447
+ }
448
+ const hashes: string[] = Array.from(new Set(labelledHashes));
449
+ let commitsExist = false;
450
+ const invalidHashes: string[] = [];
451
+ if (hashes.length > 0) {
452
+ const uniqueHashes = Array.from(new Set(hashes)).slice(0, 10);
453
+ let allValid = true;
454
+ for (const hash of uniqueHashes) {
455
+ try {
456
+ validateGitRef(hash);
457
+ } catch {
458
+ allValid = false;
459
+ invalidHashes.push(hash);
460
+ continue;
461
+ }
462
+ const result: ExecGitResult = execGit(cwd, ['cat-file', '-t', hash]);
463
+ if (!(result.exitCode === 0 && result.stdout === 'commit')) {
464
+ allValid = false;
465
+ invalidHashes.push(hash);
466
+ }
467
+ }
468
+ commitsExist = allValid;
469
+ }
470
+
471
+ // Check 4: Self-check section
472
+ let selfCheck = 'not_found';
473
+ const selfCheckPattern = /##\s*(?:Self[- ]?Check|Verification|Quality Check)/i;
474
+ if (selfCheckPattern.test(content)) {
475
+ const passPattern = /(?:all\s+)?(?:pass|✓|✅|complete|succeeded)/i;
476
+ const failPattern = /(?:fail|✗|❌|incomplete|blocked)/i;
477
+ const checkSection: string = content.slice(content.search(selfCheckPattern));
478
+ if (failPattern.test(checkSection)) {
479
+ selfCheck = 'failed';
480
+ } else if (passPattern.test(checkSection)) {
481
+ selfCheck = 'passed';
482
+ }
483
+ }
484
+
485
+ if (missing.length > 0) errors.push('Missing files: ' + missing.join(', '));
486
+ if (!commitsExist && hashes.length > 0)
487
+ errors.push(
488
+ invalidHashes.length > 0
489
+ ? `Referenced commit hashes not found in git history: ${invalidHashes.join(', ')}`
490
+ : 'Referenced commit hashes not found in git history'
491
+ );
492
+ if (selfCheck === 'failed') errors.push('Self-check section indicates failure');
493
+
494
+ const checks: SummaryVerifyChecks = {
495
+ summary_exists: true,
496
+ files_created: {
497
+ checked: filesToCheck.length,
498
+ found: filesToCheck.length - missing.length,
499
+ missing,
500
+ },
501
+ commits_exist: commitsExist,
502
+ self_check: selfCheck,
503
+ };
504
+
505
+ const passed: boolean = missing.length === 0 && selfCheck !== 'failed' && (commitsExist || hashes.length === 0);
506
+ const result: SummaryVerifyResult = { passed, checks, errors };
507
+ output(result, raw, passed ? 'passed' : 'failed');
508
+ }
509
+
510
+ /**
511
+ * CLI command: Verify PLAN.md structure, frontmatter fields, and task element completeness.
512
+ * @param cwd - Project working directory
513
+ * @param filePath - Path to the PLAN.md file to validate
514
+ * @param raw - Output raw 'valid'/'invalid' instead of JSON
515
+ */
516
+ function cmdVerifyPlanStructure(cwd: string, filePath: string, raw: boolean): void {
517
+ if (!filePath) {
518
+ error('file path required');
519
+ }
520
+ const fullPath: string = path.isAbsolute(filePath) ? filePath : path.join(cwd, filePath);
521
+ const content: string | null = readFileCached(fullPath);
522
+ if (!content) {
523
+ output({ error: 'File not found', path: filePath }, raw);
524
+ return;
525
+ }
526
+
527
+ const fm: FrontmatterObject = extractFrontmatter(content);
528
+ const errors: string[] = [];
529
+ const warnings: string[] = [];
530
+
531
+ // Check required frontmatter fields
532
+ const required: string[] = [
533
+ 'phase',
534
+ 'plan',
535
+ 'type',
536
+ 'wave',
537
+ 'depends_on',
538
+ 'files_modified',
539
+ 'autonomous',
540
+ 'must_haves',
541
+ ];
542
+ const missingFields: string[] = [];
543
+ for (const field of required) {
544
+ if (fm[field] === undefined) {
545
+ errors.push(`Missing required frontmatter field: ${field}`);
546
+ missingFields.push(field);
547
+ }
548
+ }
549
+ // Include found frontmatter fields context when fields are missing
550
+ if (missingFields.length > 0) {
551
+ const foundFields: string[] = Object.keys(fm);
552
+ if (foundFields.length > 0) {
553
+ errors.push(`Found frontmatter fields: ${foundFields.join(', ')}`);
554
+ }
555
+ }
556
+
557
+ // Parse and check task elements
558
+ const taskPattern = /<task[^>]*>([\s\S]*?)<\/task>/g;
559
+ const tasks: PlanTask[] = [];
560
+ let taskMatch: RegExpExecArray | null;
561
+ while ((taskMatch = taskPattern.exec(content)) !== null) {
562
+ const taskContent: string = taskMatch[1];
563
+ const nameMatch: RegExpMatchArray | null = taskContent.match(/<name>([\s\S]*?)<\/name>/);
564
+ const taskName: string = nameMatch ? nameMatch[1].trim() : 'unnamed';
565
+ const hasFiles: boolean = /<files>/.test(taskContent);
566
+ const hasAction: boolean = /<action>/.test(taskContent);
567
+ const hasVerify: boolean = /<verify>/.test(taskContent);
568
+ const hasDone: boolean = /<done>/.test(taskContent);
569
+
570
+ if (!nameMatch) errors.push('Task missing <name> element');
571
+ if (!hasAction) errors.push(`Task '${taskName}' missing <action>`);
572
+ if (!hasVerify) warnings.push(`Task '${taskName}' missing <verify>`);
573
+ if (!hasDone) warnings.push(`Task '${taskName}' missing <done>`);
574
+ if (!hasFiles) warnings.push(`Task '${taskName}' missing <files>`);
575
+
576
+ tasks.push({ name: taskName, hasFiles, hasAction, hasVerify, hasDone });
577
+ }
578
+
579
+ if (tasks.length === 0) warnings.push('No <task> elements found');
580
+
581
+ // Wave/depends_on consistency
582
+ if (
583
+ fm.wave &&
584
+ parseInt(String(fm.wave)) > 1 &&
585
+ (!fm.depends_on || (Array.isArray(fm.depends_on) && fm.depends_on.length === 0))
586
+ ) {
587
+ warnings.push('Wave > 1 but depends_on is empty');
588
+ }
589
+
590
+ // Autonomous/checkpoint consistency
591
+ const hasCheckpoints: boolean = /<task\s+type=["']?checkpoint/.test(content);
592
+ // fm.autonomous may arrive as boolean or string from YAML parsing -- check both
593
+ const autonomousVal: unknown = fm.autonomous;
594
+ if (hasCheckpoints && autonomousVal !== 'false' && autonomousVal !== false) {
595
+ errors.push('Has checkpoint tasks but autonomous is not false');
596
+ }
597
+
598
+ // Extract markdown headings for found_sections
599
+ const headingPattern = /^#{1,6}\s+.+$/gm;
600
+ const found_sections: string[] = (content.match(headingPattern) || []).map((h: string) =>
601
+ h.trim()
602
+ );
603
+
604
+ const result: PlanVerifyResult = {
605
+ valid: errors.length === 0,
606
+ errors,
607
+ warnings,
608
+ task_count: tasks.length,
609
+ tasks,
610
+ frontmatter_fields: Object.keys(fm),
611
+ found_sections,
612
+ };
613
+ output(result, raw, errors.length === 0 ? 'valid' : 'invalid');
614
+ }
615
+
616
+ /**
617
+ * CLI command: Check that all plans in a phase have corresponding summaries.
618
+ * @param cwd - Project working directory
619
+ * @param phase - Phase number to check
620
+ * @param raw - Output raw 'complete'/'incomplete' instead of JSON
621
+ */
622
+ function cmdVerifyPhaseCompleteness(cwd: string, phase: string, raw: boolean): void {
623
+ if (!phase) {
624
+ error('phase required');
625
+ }
626
+ const phaseInfo: PhaseInfo | null = findPhaseInternal(cwd, phase);
627
+ if (!phaseInfo || !phaseInfo.found) {
628
+ output({ error: 'Phase not found', phase }, raw);
629
+ return;
630
+ }
631
+
632
+ const errors: string[] = [];
633
+ const warnings: string[] = [];
634
+ const phaseDir: string = path.join(cwd, phaseInfo.directory);
635
+
636
+ // List plans and summaries
637
+ let files: string[];
638
+ try {
639
+ files = fs.readdirSync(phaseDir);
640
+ } catch {
641
+ output({ error: 'Cannot read phase directory' }, raw);
642
+ return;
643
+ }
644
+
645
+ const plans: string[] = files.filter((f: string) => f.match(/-PLAN\.md$/i));
646
+ const summaries: string[] = files.filter((f: string) => f.match(/-SUMMARY\.md$/i));
647
+
648
+ // Extract plan IDs (everything before -PLAN.md)
649
+ const planIds = new Set<string>(plans.map((p: string) => p.replace(/-PLAN\.md$/i, '')));
650
+ const summaryIds = new Set<string>(summaries.map((s: string) => s.replace(/-SUMMARY\.md$/i, '')));
651
+
652
+ // Plans without summaries
653
+ const incompletePlans: string[] = [...planIds].filter((id) => !summaryIds.has(id));
654
+ if (incompletePlans.length > 0) {
655
+ errors.push(`Plans without summaries: ${incompletePlans.join(', ')}`);
656
+ }
657
+
658
+ // Summaries without plans (orphans)
659
+ const orphanSummaries: string[] = [...summaryIds].filter((id) => !planIds.has(id));
660
+ if (orphanSummaries.length > 0) {
661
+ warnings.push(`Summaries without plans: ${orphanSummaries.join(', ')}`);
662
+ }
663
+
664
+ const result: PhaseCompletenessResult = {
665
+ complete: errors.length === 0,
666
+ phase: phaseInfo.phase_number,
667
+ plan_count: plans.length,
668
+ summary_count: summaries.length,
669
+ incomplete_plans: incompletePlans,
670
+ orphan_summaries: orphanSummaries,
671
+ errors,
672
+ warnings,
673
+ };
674
+ output(result, raw, errors.length === 0 ? 'complete' : 'incomplete');
675
+ }
676
+
677
+ /**
678
+ * CLI command: Validate @-references and backtick file paths in a markdown file.
679
+ * @param cwd - Project working directory
680
+ * @param filePath - Path to the markdown file to check
681
+ * @param raw - Output raw 'valid'/'invalid' instead of JSON
682
+ */
683
+ function cmdVerifyReferences(cwd: string, filePath: string, raw: boolean): void {
684
+ if (!filePath) {
685
+ error('file path required');
686
+ }
687
+ const fullPath: string = path.isAbsolute(filePath) ? filePath : path.join(cwd, filePath);
688
+ const content: string | null = readFileCached(fullPath);
689
+ if (!content) {
690
+ output({ error: 'File not found', path: filePath }, raw);
691
+ return;
692
+ }
693
+
694
+ const found: string[] = [];
695
+ const missing: string[] = [];
696
+
697
+ // Find @-references: @path/to/file (must contain / to be a file path)
698
+ const atRefs: string[] = content.match(/@([^\s\n,)]+\/[^\s\n,)]+)/g) || [];
699
+ for (const ref of atRefs) {
700
+ const cleanRef: string = ref.slice(1); // remove @
701
+ // Skip templated/dynamic refs (e.g. @${CLAUDE_PLUGIN_ROOT}/...) — same
702
+ // guard used by the backtick-ref branch below.
703
+ if (cleanRef.includes('${') || cleanRef.includes('{{')) continue;
704
+ const resolved: string = cleanRef.startsWith('~/')
705
+ ? path.join(process.env.HOME || os.homedir() || '', cleanRef.slice(2))
706
+ : path.join(cwd, cleanRef);
707
+ if (fs.existsSync(resolved)) {
708
+ found.push(cleanRef);
709
+ } else {
710
+ missing.push(cleanRef);
711
+ }
712
+ }
713
+
714
+ // Find backtick file paths that look like real paths (contain / and have extension)
715
+ const backtickRefs: string[] = content.match(/`([^`]+\/[^`]+\.[a-zA-Z]{1,10})`/g) || [];
716
+ for (const ref of backtickRefs) {
717
+ const cleanRef: string = ref.slice(1, -1); // remove backticks
718
+ if (cleanRef.startsWith('http') || cleanRef.includes('${') || cleanRef.includes('{{')) continue;
719
+ if (found.includes(cleanRef) || missing.includes(cleanRef)) continue; // dedup
720
+ const resolved: string = path.join(cwd, cleanRef);
721
+ if (fs.existsSync(resolved)) {
722
+ found.push(cleanRef);
723
+ } else {
724
+ missing.push(cleanRef);
725
+ }
726
+ }
727
+
728
+ const result: ReferenceVerifyResult = {
729
+ valid: missing.length === 0,
730
+ found: found.length,
731
+ missing,
732
+ total: found.length + missing.length,
733
+ };
734
+ output(result, raw, missing.length === 0 ? 'valid' : 'invalid');
735
+ }
736
+
737
+ /**
738
+ * CLI command: Batch verify that git commit hashes exist in the repository.
739
+ * @param cwd - Project working directory
740
+ * @param hashes - Array of commit hashes to verify
741
+ * @param raw - Output raw 'valid'/'invalid' instead of JSON
742
+ */
743
+ function cmdVerifyCommits(cwd: string, hashes: string[], raw: boolean): void {
744
+ if (!hashes || hashes.length === 0) {
745
+ error(
746
+ 'At least one commit hash required. Usage: verify commits <hash1> [hash2 ...]. Run "git log --oneline" to find commit hashes'
747
+ );
748
+ }
749
+
750
+ const valid: string[] = [];
751
+ const invalid: string[] = [];
752
+ for (const hash of hashes) {
753
+ try {
754
+ validateGitRef(hash);
755
+ } catch {
756
+ invalid.push(hash);
757
+ continue;
758
+ }
759
+ const result: ExecGitResult = execGit(cwd, ['cat-file', '-t', hash]);
760
+ if (result.exitCode === 0 && result.stdout.trim() === 'commit') {
761
+ valid.push(hash);
762
+ } else {
763
+ invalid.push(hash);
764
+ }
765
+ }
766
+
767
+ const verifyResult: CommitVerifyResult = {
768
+ all_valid: invalid.length === 0,
769
+ valid,
770
+ invalid,
771
+ total: hashes.length,
772
+ };
773
+ output(verifyResult, raw, invalid.length === 0 ? 'valid' : 'invalid');
774
+ }
775
+
776
+ /**
777
+ * CLI command: Check that must_haves.artifacts from a plan exist on disk with required content.
778
+ * @param cwd - Project working directory
779
+ * @param planFilePath - Path to the PLAN.md file containing must_haves.artifacts
780
+ * @param raw - Output raw 'valid'/'invalid' instead of JSON
781
+ */
782
+ function cmdVerifyArtifacts(cwd: string, planFilePath: string, raw: boolean): void {
783
+ if (!planFilePath) {
784
+ error('plan file path required');
785
+ }
786
+ const fullPath: string = path.isAbsolute(planFilePath)
787
+ ? planFilePath
788
+ : path.join(cwd, planFilePath);
789
+ const content: string | null = readFileCached(fullPath);
790
+ if (!content) {
791
+ output({ error: 'File not found', path: planFilePath }, raw);
792
+ return;
793
+ }
794
+
795
+ const artifacts: MustHavesEntry[] = parseMustHavesBlock(content, 'artifacts');
796
+ if (artifacts.length === 0) {
797
+ output({ error: 'No must_haves.artifacts found in frontmatter', path: planFilePath }, raw);
798
+ return;
799
+ }
800
+
801
+ const results: ArtifactCheck[] = [];
802
+ for (const artifact of artifacts) {
803
+ if (typeof artifact === 'string') continue; // skip simple string items
804
+ const artPath: string | undefined = (artifact as MustHavesArtifact).path;
805
+ if (!artPath) continue;
806
+
807
+ const artFullPath: string = path.join(cwd, artPath);
808
+ const exists: boolean = fs.existsSync(artFullPath);
809
+ const check: ArtifactCheck = {
810
+ path: artPath,
811
+ exists,
812
+ issues: [],
813
+ passed: false,
814
+ plan_file: planFilePath,
815
+ must_haves_field: 'must_haves.artifacts',
816
+ };
817
+
818
+ if (exists) {
819
+ const fileContent: string = safeReadFile(artFullPath) || '';
820
+ const lineCount: number = fileContent.split('\n').length;
821
+ const artTyped = artifact as MustHavesArtifact;
822
+
823
+ if (artTyped.min_lines && lineCount < artTyped.min_lines) {
824
+ check.issues.push(`Only ${lineCount} lines, need ${artTyped.min_lines}`);
825
+ }
826
+ if (artTyped.contains && !fileContent.includes(artTyped.contains)) {
827
+ check.issues.push(`Missing pattern: ${artTyped.contains}`);
828
+ }
829
+ if (artTyped.exports) {
830
+ const exports: string[] = Array.isArray(artTyped.exports)
831
+ ? artTyped.exports
832
+ : [artTyped.exports];
833
+ for (const exp of exports) {
834
+ if (!fileContent.includes(exp)) check.issues.push(`Missing export: ${exp}`);
835
+ }
836
+ }
837
+ check.passed = check.issues.length === 0;
838
+ } else {
839
+ check.issues.push('File not found');
840
+ check.remediation = `Create the missing file at: ${artPath}`;
841
+ }
842
+
843
+ results.push(check);
844
+ }
845
+
846
+ const passed: number = results.filter((r) => r.passed).length;
847
+ const verifyResult: ArtifactVerifyResult = {
848
+ all_passed: passed === results.length,
849
+ passed,
850
+ total: results.length,
851
+ artifacts: results,
852
+ };
853
+ output(verifyResult, raw, passed === results.length ? 'valid' : 'invalid');
854
+ }
855
+
856
+ /**
857
+ * CLI command: Validate must_haves.key_links patterns between source and target files.
858
+ * @param cwd - Project working directory
859
+ * @param planFilePath - Path to the PLAN.md file containing must_haves.key_links
860
+ * @param raw - Output raw 'valid'/'invalid' instead of JSON
861
+ */
862
+ function cmdVerifyKeyLinks(cwd: string, planFilePath: string, raw: boolean): void {
863
+ if (!planFilePath) {
864
+ error('plan file path required');
865
+ }
866
+ const fullPath: string = path.isAbsolute(planFilePath)
867
+ ? planFilePath
868
+ : path.join(cwd, planFilePath);
869
+ const content: string | null = readFileCached(fullPath);
870
+ if (!content) {
871
+ output({ error: 'File not found', path: planFilePath }, raw);
872
+ return;
873
+ }
874
+
875
+ const keyLinks: MustHavesEntry[] = parseMustHavesBlock(content, 'key_links');
876
+ if (keyLinks.length === 0) {
877
+ output({ error: 'No must_haves.key_links found in frontmatter', path: planFilePath }, raw);
878
+ return;
879
+ }
880
+
881
+ const results: KeyLinkCheck[] = [];
882
+ for (const link of keyLinks) {
883
+ if (typeof link === 'string') continue;
884
+ const linkTyped = link as MustHavesKeyLink;
885
+ const check: KeyLinkCheck = {
886
+ from: linkTyped.from,
887
+ to: linkTyped.to,
888
+ via: linkTyped.via || '',
889
+ verified: false,
890
+ detail: '',
891
+ };
892
+
893
+ const sourceContent: string | null = safeReadFile(path.join(cwd, linkTyped.from || ''));
894
+ if (!sourceContent) {
895
+ check.detail = 'Source file not found';
896
+ } else if (linkTyped.pattern) {
897
+ try {
898
+ const regex = new RegExp(linkTyped.pattern);
899
+ if (regex.test(sourceContent)) {
900
+ check.verified = true;
901
+ check.detail = 'Pattern found in source';
902
+ } else {
903
+ const targetContent: string | null = safeReadFile(path.join(cwd, linkTyped.to || ''));
904
+ if (targetContent && regex.test(targetContent)) {
905
+ check.verified = true;
906
+ check.detail = 'Pattern found in target';
907
+ } else {
908
+ check.detail = `Pattern "${linkTyped.pattern}" not found in source or target`;
909
+ }
910
+ }
911
+ } catch {
912
+ check.detail = `Invalid regex pattern: ${linkTyped.pattern}`;
913
+ }
914
+ } else {
915
+ // No pattern: just check source references target
916
+ if (sourceContent.includes(linkTyped.to || '')) {
917
+ check.verified = true;
918
+ check.detail = 'Target referenced in source';
919
+ } else {
920
+ check.detail = 'Target not referenced in source';
921
+ }
922
+ }
923
+
924
+ results.push(check);
925
+ }
926
+
927
+ const verified: number = results.filter((r) => r.verified).length;
928
+ const verifyResult: KeyLinkVerifyResult = {
929
+ all_verified: verified === results.length,
930
+ verified,
931
+ total: results.length,
932
+ links: results,
933
+ };
934
+ output(verifyResult, raw, verified === results.length ? 'valid' : 'invalid');
935
+ }
936
+
937
+ /**
938
+ * Result of a single mechanical check inside the bundle.
939
+ */
940
+ interface MechanicalCheckResult {
941
+ check: 'frontmatter' | 'artifacts' | 'key_links' | 'references' | 'plan_summary_completeness';
942
+ scope: string;
943
+ passed: boolean;
944
+ detail: string;
945
+ data?: Record<string, unknown>;
946
+ }
947
+
948
+ /**
949
+ * Aggregated result of cmdVerifyMechanical — the "Mechanical tier" of
950
+ * the verifier agent's three-stage gate.
951
+ */
952
+ interface MechanicalVerifyResult {
953
+ passed: boolean;
954
+ phase: string;
955
+ plan_count: number;
956
+ total_checks: number;
957
+ passed_count: number;
958
+ failed_count: number;
959
+ checks: MechanicalCheckResult[];
960
+ }
961
+
962
+ /**
963
+ * Bundle the four PLAN.md mechanical checks (frontmatter, artifacts,
964
+ * key_links, references) plus a phase-level plan/summary completeness
965
+ * check into a single aggregated JSON result. Reuses the same helpers
966
+ * as the discrete verify commands so behavior stays consistent.
967
+ *
968
+ * Required PLAN.md frontmatter fields are kept in sync with
969
+ * cmdVerifyPlanStructure. Frontmatter check passes if all required
970
+ * fields are present; artifact and key-link checks pass when every
971
+ * declared item resolves; reference check passes when every @-reference
972
+ * and backtick path resolves; completeness check passes when every
973
+ * PLAN has a matching SUMMARY.
974
+ *
975
+ * @param cwd - Project working directory
976
+ * @param phase - Phase number or name passed to findPhaseInternal
977
+ * @param raw - Output 'pass'/'fail' instead of JSON
978
+ */
979
+ function cmdVerifyMechanical(cwd: string, phase: string, raw: boolean): void {
980
+ if (!phase) {
981
+ error('phase required');
982
+ }
983
+ const phaseInfo: PhaseInfo | null = findPhaseInternal(cwd, phase);
984
+ if (!phaseInfo || !phaseInfo.found) {
985
+ output({ error: 'Phase not found', phase }, raw);
986
+ return;
987
+ }
988
+
989
+ const phaseDir: string = path.join(cwd, phaseInfo.directory);
990
+ let files: string[];
991
+ try {
992
+ files = fs.readdirSync(phaseDir);
993
+ } catch {
994
+ output({ error: 'Cannot read phase directory', phase }, raw);
995
+ return;
996
+ }
997
+
998
+ // Accept both prefixed (`01-01-PLAN.md`) and bare (`PLAN.md`) filenames —
999
+ // matches the convention used across the codebase (phase.ts:336, utils.ts:1046,
1000
+ // gates.ts:199, knowledge.ts:230, roadmap.ts:614).
1001
+ const plans: string[] = files.filter(
1002
+ (f) => /-PLAN\.md$/i.test(f) || f === 'PLAN.md'
1003
+ );
1004
+ const summaries: string[] = files.filter(
1005
+ (f) => /-SUMMARY\.md$/i.test(f) || f === 'SUMMARY.md'
1006
+ );
1007
+ const checks: MechanicalCheckResult[] = [];
1008
+
1009
+ // A phase with zero PLAN.md files cannot pass the mechanical gate by
1010
+ // vacuously satisfying every check. Emit an explicit failing check so the
1011
+ // aggregate result correctly reports passed=false.
1012
+ checks.push({
1013
+ check: 'plan_summary_completeness',
1014
+ scope: `phase:${phaseInfo.phase_number}`,
1015
+ passed: plans.length > 0,
1016
+ detail:
1017
+ plans.length > 0
1018
+ ? `Phase has ${plans.length} PLAN.md file(s)`
1019
+ : 'Phase has no PLAN.md files — nothing to verify',
1020
+ data: { plan_count: plans.length },
1021
+ });
1022
+ if (plans.length === 0) {
1023
+ const result: MechanicalVerifyResult = {
1024
+ passed: false,
1025
+ phase: phaseInfo.phase_number,
1026
+ plan_count: 0,
1027
+ total_checks: checks.length,
1028
+ passed_count: 0,
1029
+ failed_count: checks.length,
1030
+ checks,
1031
+ };
1032
+ output(result, raw, 'fail');
1033
+ return;
1034
+ }
1035
+ // The placeholder above will be replaced by the real plan_summary_completeness
1036
+ // check below; pop it so we don't double-report.
1037
+ checks.pop();
1038
+
1039
+ const requiredFrontmatterFields: readonly string[] = [
1040
+ 'phase',
1041
+ 'plan',
1042
+ 'type',
1043
+ 'wave',
1044
+ 'depends_on',
1045
+ 'files_modified',
1046
+ 'autonomous',
1047
+ 'must_haves',
1048
+ ];
1049
+
1050
+ for (const planFile of plans) {
1051
+ const planPath: string = path.join(phaseDir, planFile);
1052
+ const content: string | null = readFileCached(planPath);
1053
+ if (!content) {
1054
+ checks.push({
1055
+ check: 'frontmatter',
1056
+ scope: `plan:${planFile}`,
1057
+ passed: false,
1058
+ detail: 'PLAN.md unreadable',
1059
+ });
1060
+ continue;
1061
+ }
1062
+ const fm: FrontmatterObject = extractFrontmatter(content);
1063
+
1064
+ // frontmatter check
1065
+ const missingFields: string[] = requiredFrontmatterFields.filter(
1066
+ (f) => fm[f] === undefined
1067
+ );
1068
+ checks.push({
1069
+ check: 'frontmatter',
1070
+ scope: `plan:${planFile}`,
1071
+ passed: missingFields.length === 0,
1072
+ detail:
1073
+ missingFields.length === 0
1074
+ ? `All ${requiredFrontmatterFields.length} required fields present`
1075
+ : `Missing: ${missingFields.join(', ')}`,
1076
+ data: { missing: missingFields, required: [...requiredFrontmatterFields] },
1077
+ });
1078
+
1079
+ // artifacts check — mirror cmdVerifyArtifacts: existence AND content
1080
+ // constraints (min_lines / contains / exports).
1081
+ const artifacts: MustHavesEntry[] = parseMustHavesBlock(content, 'artifacts');
1082
+ if (artifacts.length > 0) {
1083
+ const failed: { path: string; issues: string[] }[] = [];
1084
+ for (const art of artifacts) {
1085
+ if (typeof art === 'string') continue;
1086
+ const artTyped = art as MustHavesArtifact;
1087
+ const artPath: string | undefined = artTyped.path;
1088
+ if (!artPath) continue;
1089
+ const artFullPath: string = path.join(cwd, artPath);
1090
+ const issues: string[] = [];
1091
+ if (!fs.existsSync(artFullPath)) {
1092
+ issues.push('File not found');
1093
+ } else {
1094
+ const fileContent: string = safeReadFile(artFullPath) || '';
1095
+ const lineCount: number = fileContent.split('\n').length;
1096
+ if (artTyped.min_lines && lineCount < artTyped.min_lines) {
1097
+ issues.push(`Only ${lineCount} lines, need ${artTyped.min_lines}`);
1098
+ }
1099
+ if (artTyped.contains && !fileContent.includes(artTyped.contains)) {
1100
+ issues.push(`Missing pattern: ${artTyped.contains}`);
1101
+ }
1102
+ if (artTyped.exports) {
1103
+ const exps: string[] = Array.isArray(artTyped.exports)
1104
+ ? artTyped.exports
1105
+ : [artTyped.exports];
1106
+ for (const exp of exps) {
1107
+ if (!fileContent.includes(exp)) issues.push(`Missing export: ${exp}`);
1108
+ }
1109
+ }
1110
+ }
1111
+ if (issues.length > 0) failed.push({ path: artPath, issues });
1112
+ }
1113
+ checks.push({
1114
+ check: 'artifacts',
1115
+ scope: `plan:${planFile}`,
1116
+ passed: failed.length === 0,
1117
+ detail:
1118
+ failed.length === 0
1119
+ ? `All ${artifacts.length} artifacts present and satisfy content constraints`
1120
+ : `Failed: ${failed.map((f) => `${f.path} (${f.issues.join('; ')})`).join('; ')}`,
1121
+ data: { failed },
1122
+ });
1123
+ }
1124
+
1125
+ // key_links check
1126
+ const keyLinks: MustHavesEntry[] = parseMustHavesBlock(content, 'key_links');
1127
+ if (keyLinks.length > 0) {
1128
+ const failed: string[] = [];
1129
+ for (const link of keyLinks) {
1130
+ if (typeof link === 'string') continue;
1131
+ const linkTyped = link as MustHavesKeyLink;
1132
+ const fromContent: string | null = safeReadFile(path.join(cwd, linkTyped.from || ''));
1133
+ const toPath: string = path.join(cwd, linkTyped.to || '');
1134
+ let verified = false;
1135
+ if (fromContent) {
1136
+ if (linkTyped.pattern) {
1137
+ try {
1138
+ const regex = new RegExp(linkTyped.pattern);
1139
+ if (regex.test(fromContent)) verified = true;
1140
+ else {
1141
+ const toContent: string | null = safeReadFile(toPath);
1142
+ if (toContent && regex.test(toContent)) verified = true;
1143
+ }
1144
+ } catch {
1145
+ verified = false;
1146
+ }
1147
+ } else {
1148
+ verified = fromContent.includes(linkTyped.to || '');
1149
+ }
1150
+ }
1151
+ if (!verified) failed.push(`${linkTyped.from} → ${linkTyped.to}`);
1152
+ }
1153
+ checks.push({
1154
+ check: 'key_links',
1155
+ scope: `plan:${planFile}`,
1156
+ passed: failed.length === 0,
1157
+ detail:
1158
+ failed.length === 0
1159
+ ? `All ${keyLinks.length} key links verified`
1160
+ : `Failed: ${failed.join('; ')}`,
1161
+ data: { failed },
1162
+ });
1163
+ }
1164
+
1165
+ // references check
1166
+ const missingRefs: string[] = [];
1167
+ const atRefs: string[] = content.match(/@([^\s\n,)]+\/[^\s\n,)]+)/g) || [];
1168
+ for (const ref of atRefs) {
1169
+ const cleanRef: string = ref.slice(1);
1170
+ // Skip templated/dynamic refs (e.g. @${CLAUDE_PLUGIN_ROOT}/...) — they
1171
+ // are not literal paths. Same guard the backtick branch uses below.
1172
+ if (cleanRef.includes('${') || cleanRef.includes('{{')) continue;
1173
+ const resolved: string = cleanRef.startsWith('~/')
1174
+ ? path.join(process.env.HOME || os.homedir() || '', cleanRef.slice(2))
1175
+ : path.join(cwd, cleanRef);
1176
+ if (!fs.existsSync(resolved)) missingRefs.push(cleanRef);
1177
+ }
1178
+ const backtickRefs: string[] = content.match(/`([^`]+\/[^`]+\.[a-zA-Z]{1,10})`/g) || [];
1179
+ for (const ref of backtickRefs) {
1180
+ const cleanRef: string = ref.slice(1, -1);
1181
+ if (cleanRef.startsWith('http') || cleanRef.includes('${') || cleanRef.includes('{{')) continue;
1182
+ if (missingRefs.includes(cleanRef)) continue;
1183
+ if (!fs.existsSync(path.join(cwd, cleanRef))) missingRefs.push(cleanRef);
1184
+ }
1185
+ const totalRefs: number = atRefs.length + backtickRefs.length;
1186
+ if (totalRefs > 0) {
1187
+ checks.push({
1188
+ check: 'references',
1189
+ scope: `plan:${planFile}`,
1190
+ passed: missingRefs.length === 0,
1191
+ detail:
1192
+ missingRefs.length === 0
1193
+ ? `All ${totalRefs} references resolve`
1194
+ : `Missing: ${missingRefs.join(', ')}`,
1195
+ data: { missing: missingRefs, total: totalRefs },
1196
+ });
1197
+ }
1198
+ }
1199
+
1200
+ // phase-level: plan/summary completeness — bare PLAN.md / SUMMARY.md
1201
+ // normalise to '' so they pair off.
1202
+ const stripPlanId = (n: string): string =>
1203
+ n === 'PLAN.md' ? '' : n.replace(/-PLAN\.md$/i, '');
1204
+ const stripSummaryId = (n: string): string =>
1205
+ n === 'SUMMARY.md' ? '' : n.replace(/-SUMMARY\.md$/i, '');
1206
+ const planIds = new Set<string>(plans.map(stripPlanId));
1207
+ const summaryIds = new Set<string>(summaries.map(stripSummaryId));
1208
+ const incomplete: string[] = [...planIds].filter((id) => !summaryIds.has(id));
1209
+ checks.push({
1210
+ check: 'plan_summary_completeness',
1211
+ scope: `phase:${phaseInfo.phase_number}`,
1212
+ passed: incomplete.length === 0,
1213
+ detail:
1214
+ incomplete.length === 0
1215
+ ? `All ${plans.length} plans have summaries`
1216
+ : `Plans without summaries: ${incomplete.join(', ')}`,
1217
+ data: { incomplete, plan_count: plans.length, summary_count: summaries.length },
1218
+ });
1219
+
1220
+ const passedCount: number = checks.filter((c) => c.passed).length;
1221
+ const failedCount: number = checks.length - passedCount;
1222
+ const result: MechanicalVerifyResult = {
1223
+ passed: failedCount === 0,
1224
+ phase: phaseInfo.phase_number,
1225
+ plan_count: plans.length,
1226
+ total_checks: checks.length,
1227
+ passed_count: passedCount,
1228
+ failed_count: failedCount,
1229
+ checks,
1230
+ };
1231
+ output(result, raw, failedCount === 0 ? 'pass' : 'fail');
1232
+ }
1233
+
1234
+ // ─── Diagnose Phase ──────────────────────────────────────────────────────────
1235
+
1236
+ /** A single ranked root-cause finding from phase diagnosis. */
1237
+ interface DiagnosisEntry {
1238
+ rank: number;
1239
+ cause: string;
1240
+ evidence: string;
1241
+ suggestion: string;
1242
+ }
1243
+
1244
+ /** Result of phase diagnosis. */
1245
+ interface DiagnosisResult {
1246
+ phase: string;
1247
+ verdict: string;
1248
+ root_causes: DiagnosisEntry[];
1249
+ failed_checks: string[];
1250
+ git_diff_lines: number;
1251
+ plans_missing_summaries: number;
1252
+ }
1253
+
1254
+ /**
1255
+ * CLI command: Diagnose a failed phase by reading VERIFICATION.md, plan files,
1256
+ * and running git diff to produce a ranked root-cause list.
1257
+ *
1258
+ * Intended as a quick forensics tool after `gd verify-phase N` fails.
1259
+ * Reads:
1260
+ * - .planning/milestones/{m}/phases/{N}/VERIFICATION.md (verdict + failed checks)
1261
+ * - *-PLAN.md files in phase dir (detect plans without summaries)
1262
+ * - `git diff HEAD` (scope estimate of uncommitted changes)
1263
+ * Ranks causes by heuristic signal strength.
1264
+ *
1265
+ * @param cwd - Project working directory
1266
+ * @param phase - Phase number or name
1267
+ * @param raw - Output raw text instead of JSON
1268
+ */
1269
+ function cmdDiagnosePhase(cwd: string, phase: string, raw: boolean): void {
1270
+ if (!phase) {
1271
+ error('phase required. Usage: gd diagnose <phase>');
1272
+ }
1273
+
1274
+ const phaseInfo: PhaseInfo | null = findPhaseInternal(cwd, phase);
1275
+ if (!phaseInfo || !phaseInfo.found) {
1276
+ output({ error: 'Phase not found', phase }, raw);
1277
+ return;
1278
+ }
1279
+
1280
+ const phaseDir: string = path.join(cwd, phaseInfo.directory);
1281
+ // Codex r20 P2: scaffold/execution flow produces prefixed names like
1282
+ // `75-VERIFICATION.md`. Match both forms.
1283
+ let verificationPath: string = path.join(phaseDir, 'VERIFICATION.md');
1284
+ try {
1285
+ const files = require('fs').readdirSync(phaseDir) as string[];
1286
+ const prefixed = files.find((f: string) => /-VERIFICATION\.md$/.test(f));
1287
+ if (prefixed && !require('fs').existsSync(verificationPath)) {
1288
+ verificationPath = path.join(phaseDir, prefixed);
1289
+ }
1290
+ } catch { /* fall through */ }
1291
+ const verificationContent: string | null = safeReadFile(verificationPath);
1292
+
1293
+ // Parse failed checks from VERIFICATION.md
1294
+ const failedChecks: string[] = [];
1295
+ let verdict = 'unknown';
1296
+ if (verificationContent) {
1297
+ const verdictMatch = verificationContent.match(/\*\*verdict\*\*[:\s]*([^\n]+)/i);
1298
+ if (verdictMatch) verdict = verdictMatch[1].trim();
1299
+
1300
+ // Extract lines starting with ❌ or [FAIL] or "- FAIL"
1301
+ const failLines = verificationContent.match(/^(?:❌|[-*]\s*\[?FAIL[^]]*]?).*$/gim) || [];
1302
+ for (const line of failLines) {
1303
+ const clean = line.replace(/^[-*\s❌✗x]+/i, '').trim();
1304
+ if (clean) failedChecks.push(clean);
1305
+ }
1306
+ // Also parse "check: X passed: false" patterns in JSON-like blocks
1307
+ const checkFailMatches = verificationContent.match(/"check"\s*:\s*"([^"]+)"[^}]*"passed"\s*:\s*false/g) || [];
1308
+ for (const m of checkFailMatches) {
1309
+ const nameMatch = m.match(/"check"\s*:\s*"([^"]+)"/);
1310
+ if (nameMatch && !failedChecks.includes(nameMatch[1])) {
1311
+ failedChecks.push(nameMatch[1]);
1312
+ }
1313
+ }
1314
+ }
1315
+
1316
+ // Check for plans without summaries
1317
+ let files: string[] = [];
1318
+ try {
1319
+ files = fs.readdirSync(phaseDir) as string[];
1320
+ } catch {
1321
+ // phase dir unreadable
1322
+ }
1323
+ const plans = files.filter((f) => /-PLAN\.md$/i.test(f) || f === 'PLAN.md');
1324
+ const summaries = files.filter((f) => /-SUMMARY\.md$/i.test(f) || f === 'SUMMARY.md');
1325
+ const planIds = plans.map((f) => (f === 'PLAN.md' ? '' : f.replace(/-PLAN\.md$/i, '')));
1326
+ const summaryIds = new Set(summaries.map((f) => (f === 'SUMMARY.md' ? '' : f.replace(/-SUMMARY\.md$/i, ''))));
1327
+ const plansMissingSummaries = planIds.filter((id) => !summaryIds.has(id)).length;
1328
+
1329
+ // Measure uncommitted git diff size
1330
+ let gitDiffLines = 0;
1331
+ try {
1332
+ const diffResult = execGit(cwd, ['diff', 'HEAD', '--stat'], { allowBlocked: true });
1333
+ if (diffResult.stdout) {
1334
+ gitDiffLines = (diffResult.stdout.match(/\n/g) || []).length;
1335
+ }
1336
+ } catch {
1337
+ // git not available or not a repo
1338
+ }
1339
+
1340
+ // Build ranked root causes
1341
+ const rootCauses: DiagnosisEntry[] = [];
1342
+ let rank = 1;
1343
+
1344
+ if (plansMissingSummaries > 0) {
1345
+ rootCauses.push({
1346
+ rank: rank++,
1347
+ cause: `${plansMissingSummaries} plan(s) missing SUMMARY.md`,
1348
+ evidence: `Found ${plans.length} PLAN.md files, ${summaries.length} SUMMARY.md files`,
1349
+ suggestion: 'Run gd execute-phase N or manually write SUMMARY.md for each incomplete plan',
1350
+ });
1351
+ }
1352
+
1353
+ if (!verificationContent) {
1354
+ rootCauses.push({
1355
+ rank: rank++,
1356
+ cause: 'VERIFICATION.md not found',
1357
+ evidence: `Expected at ${path.relative(cwd, verificationPath)}`,
1358
+ suggestion: 'Run gd verify-phase N to generate VERIFICATION.md before diagnosing',
1359
+ });
1360
+ } else if (verdict.toLowerCase().includes('fail') || verdict === 'unknown') {
1361
+ rootCauses.push({
1362
+ rank: rank++,
1363
+ cause: `Verification verdict: ${verdict}`,
1364
+ evidence: `${failedChecks.length} failed check(s) recorded`,
1365
+ suggestion: `Address failed checks: ${failedChecks.slice(0, 3).join(', ')}${failedChecks.length > 3 ? '...' : ''}`,
1366
+ });
1367
+ }
1368
+
1369
+ for (const check of failedChecks.slice(0, 5)) {
1370
+ const cause = `Failed check: ${check}`;
1371
+ if (rootCauses.some((c) => c.cause === cause)) continue;
1372
+ rootCauses.push({
1373
+ rank: rank++,
1374
+ cause,
1375
+ evidence: `Recorded in VERIFICATION.md`,
1376
+ suggestion: _checkSuggestion(check),
1377
+ });
1378
+ }
1379
+
1380
+ if (gitDiffLines > 10) {
1381
+ rootCauses.push({
1382
+ rank: rank,
1383
+ cause: `${gitDiffLines} uncommitted lines in working tree`,
1384
+ evidence: 'git diff HEAD --stat shows pending changes',
1385
+ suggestion: 'Commit or stash changes before re-running verification',
1386
+ });
1387
+ }
1388
+
1389
+ if (rootCauses.length === 0) {
1390
+ rootCauses.push({
1391
+ rank: 1,
1392
+ cause: 'No obvious failure signals found',
1393
+ evidence: 'VERIFICATION.md exists, no missing summaries, no large diffs',
1394
+ suggestion: 'Review VERIFICATION.md manually or re-run gd verify-phase N for details',
1395
+ });
1396
+ }
1397
+
1398
+ const result: DiagnosisResult = {
1399
+ phase: phaseInfo.phase_number,
1400
+ verdict,
1401
+ root_causes: rootCauses,
1402
+ failed_checks: failedChecks,
1403
+ git_diff_lines: gitDiffLines,
1404
+ plans_missing_summaries: plansMissingSummaries,
1405
+ };
1406
+
1407
+ const summary = `Phase ${phaseInfo.phase_number}: ${rootCauses.length} root cause(s) — top: ${rootCauses[0].cause}`;
1408
+ output(result, raw, summary);
1409
+ }
1410
+
1411
+ /** Map a failed check name to a human-readable next-step suggestion. */
1412
+ function _checkSuggestion(check: string): string {
1413
+ if (/frontmatter/i.test(check)) return 'Ensure PLAN.md has all required frontmatter fields (phase, plan, type, wave, depends_on, files_modified, autonomous, must_haves)';
1414
+ if (/artifact/i.test(check)) return 'Verify all must_haves artifacts exist at the declared paths with required content';
1415
+ if (/reference/i.test(check)) return 'Fix broken @file or `path` references in PLAN.md';
1416
+ if (/key.?link/i.test(check)) return 'Confirm all key_links pairs are wired together in source/target files';
1417
+ if (/summary|completeness/i.test(check)) return 'Write a SUMMARY.md for each PLAN.md in the phase directory';
1418
+ return `Review the "${check}" section in VERIFICATION.md for details`;
1419
+ }
1420
+
1421
+ // ─── Exports ─────────────────────────────────────────────────────────────────
1422
+
1423
+ module.exports = {
1424
+ cmdVerifySummary,
1425
+ cmdVerifyPlanStructure,
1426
+ cmdVerifyPhaseCompleteness,
1427
+ cmdVerifyReferences,
1428
+ cmdVerifyCommits,
1429
+ cmdVerifyArtifacts,
1430
+ cmdVerifyKeyLinks,
1431
+ cmdVerifyMechanical,
1432
+ cmdDiagnosePhase,
1433
+ clearVerifyCache,
1434
+ };