@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/cleanup.ts ADDED
@@ -0,0 +1,1588 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * GRD Phase Cleanup — Config schema handling and quality analysis functions
5
+ *
6
+ * Provides config reading for the phase_cleanup section, and quality analysis
7
+ * functions for ESLint complexity, dead export detection, file size checks,
8
+ * doc drift detection (changelog staleness, broken README links, JSDoc mismatches),
9
+ * test coverage gap detection, export consistency checking, CLAUDE.md doc staleness
10
+ * detection, and config schema drift analysis.
11
+ * These are the data-layer operations wired into phase completion by Plan 13-02.
12
+ */
13
+
14
+
15
+ import type { RunCache, CleanupConfig, QualityAnalysisSummary } from './types';
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const { execFileSync } = require('child_process');
20
+ const { planningDir: getPlanningDir, phasesDir: getPhasesDirPath } = require('./paths');
21
+ const { safeReadFile, safeReadJSON, createRunCache, walkJsFiles } = require('./utils');
22
+
23
+ // ─── Domain Types ─────────────────────────────────────────────────────────────
24
+
25
+ /** A complexity violation found by ESLint analysis. */
26
+ interface ComplexityViolation {
27
+ file: string;
28
+ line: number;
29
+ functionName: string;
30
+ complexity: number;
31
+ }
32
+
33
+ /** A dead export found during export analysis. */
34
+ interface DeadExportViolation {
35
+ file: string;
36
+ exportName: string;
37
+ line: number;
38
+ }
39
+
40
+ /** A file size violation. */
41
+ interface FileSizeViolation {
42
+ file: string;
43
+ lines: number;
44
+ threshold: number;
45
+ }
46
+
47
+ /** A changelog drift violation. */
48
+ interface ChangelogDriftViolation {
49
+ file: string;
50
+ reason: string;
51
+ last_modified: string;
52
+ latest_summary: string;
53
+ }
54
+
55
+ /** A broken README link violation. */
56
+ interface BrokenLinkViolation {
57
+ file: string;
58
+ link: string;
59
+ line: number;
60
+ }
61
+
62
+ /** A JSDoc drift issue. */
63
+ interface JsdocDriftIssue {
64
+ file: string;
65
+ line: number;
66
+ functionName: string;
67
+ issue: string;
68
+ }
69
+
70
+ /** A test coverage gap. */
71
+ interface TestCoverageGap {
72
+ file: string;
73
+ exportName: string;
74
+ testFile: string;
75
+ line: number;
76
+ }
77
+
78
+ /** A stale import (export consistency) issue. */
79
+ interface ExportConsistencyIssue {
80
+ file: string;
81
+ importedName: string;
82
+ sourceModule: string;
83
+ line: number;
84
+ }
85
+
86
+ /** A doc staleness issue (CLAUDE.md vs mcp-server.js). */
87
+ interface DocStalenessIssue {
88
+ file: string;
89
+ issue: string;
90
+ detail: string;
91
+ line: number;
92
+ }
93
+
94
+ /** A config schema drift issue. */
95
+ interface ConfigSchemaDriftIssue {
96
+ file: string;
97
+ issue: string;
98
+ detail: string;
99
+ line: number;
100
+ }
101
+
102
+ /** Doc drift detail sub-structure within quality analysis details. */
103
+ interface DocDriftDetails {
104
+ changelog: ChangelogDriftViolation[];
105
+ readme_links: BrokenLinkViolation[];
106
+ jsdoc: JsdocDriftIssue[];
107
+ }
108
+
109
+ /** Full quality analysis details containing all analysis results. */
110
+ interface QualityAnalysisDetails {
111
+ complexity: ComplexityViolation[];
112
+ dead_exports: DeadExportViolation[];
113
+ file_size: FileSizeViolation[];
114
+ doc_drift?: DocDriftDetails;
115
+ test_coverage?: TestCoverageGap[];
116
+ export_consistency?: ExportConsistencyIssue[];
117
+ doc_staleness?: DocStalenessIssue[];
118
+ config_schema?: ConfigSchemaDriftIssue[];
119
+ }
120
+
121
+ /** Trend information for a single metric. */
122
+ interface TrendEntry {
123
+ delta: number;
124
+ label: string;
125
+ }
126
+
127
+ /** Full quality analysis result from runQualityAnalysis. */
128
+ interface QualityAnalysisResult {
129
+ skipped?: boolean;
130
+ reason?: string;
131
+ phase?: string;
132
+ timestamp?: string;
133
+ summary?: QualityAnalysisSummary;
134
+ details?: QualityAnalysisDetails;
135
+ trends?: Record<string, TrendEntry> | null;
136
+ }
137
+
138
+ /** Quality history: map of phase number to quality summary. */
139
+ type QualityHistory = Record<string, QualityAnalysisSummary>;
140
+
141
+ /** Generated cleanup plan info. */
142
+ interface CleanupPlanResult {
143
+ path: string;
144
+ plan_number: string;
145
+ issues_addressed: number;
146
+ }
147
+
148
+ /** Complexity analysis options. */
149
+ interface ComplexityOptions {
150
+ threshold?: number;
151
+ onProgress?: (event: { event: string; file_count?: number; violation_count?: number }) => void;
152
+ }
153
+
154
+ /** Dead export analysis options. */
155
+ interface DeadExportOptions {
156
+ excludePatterns?: string[];
157
+ }
158
+
159
+ /** File size analysis thresholds. */
160
+ interface FileSizeThresholds {
161
+ maxLines?: number;
162
+ }
163
+
164
+ /** A documented CLI command extracted from CLAUDE.md. */
165
+ interface DocumentedCommand {
166
+ raw: string;
167
+ possibleNames: string[];
168
+ line: number;
169
+ }
170
+
171
+ /** A documented config key extracted from CLAUDE.md. */
172
+ interface DocumentedConfigKey {
173
+ key: string;
174
+ line: number;
175
+ }
176
+
177
+ /** Parsed function info following a JSDoc block. */
178
+ interface ParsedFunctionInfo {
179
+ funcName: string | null;
180
+ paramsStr: string | null;
181
+ }
182
+
183
+ /** Cleanup plan task for generating PLAN.md. */
184
+ interface CleanupTask {
185
+ name: string;
186
+ items: string[];
187
+ }
188
+
189
+ /** ESLint JSON output file result structure. */
190
+ interface EslintFileResult {
191
+ filePath: string;
192
+ messages: EslintMessage[];
193
+ }
194
+
195
+ /** ESLint JSON output message structure. */
196
+ interface EslintMessage {
197
+ ruleId: string | null;
198
+ message: string;
199
+ line: number;
200
+ column: number;
201
+ }
202
+
203
+ // ─── File Content Cache ───────────────────────────────────────────────────────
204
+ const _cleanupCache: RunCache = createRunCache();
205
+
206
+ function _cachedRead(absPath: string): string | null {
207
+ return _cleanupCache.get(absPath, safeReadFile) as string | null;
208
+ }
209
+
210
+ /**
211
+ * Reset the cleanup file content cache.
212
+ * Useful for tests that need to ensure a clean state between runs.
213
+ */
214
+ function resetCleanupCache(): void {
215
+ _cleanupCache.reset();
216
+ }
217
+
218
+ // ─── Config ──────────────────────────────────────────────────────────────────
219
+
220
+ const CLEANUP_DEFAULTS: CleanupConfig = {
221
+ enabled: false,
222
+ refactoring: false,
223
+ doc_sync: false,
224
+ test_coverage: false,
225
+ export_consistency: false,
226
+ doc_staleness: false,
227
+ config_schema: false,
228
+ cleanup_threshold: 5,
229
+ };
230
+
231
+ /**
232
+ * Read the `phase_cleanup` section from `.planning/config.json` and return
233
+ * a merged config object with defaults for any missing fields.
234
+ */
235
+ function getCleanupConfig(cwd: string): CleanupConfig {
236
+ const configPath: string = path.join(cwd, '.planning', 'config.json');
237
+ const parsed: Record<string, unknown> = safeReadJSON(configPath, {});
238
+ const phaseCleanup = (parsed.phase_cleanup || {}) as Partial<CleanupConfig>;
239
+ return { ...CLEANUP_DEFAULTS, ...phaseCleanup };
240
+ }
241
+
242
+ // ─── Complexity Analysis ─────────────────────────────────────────────────────
243
+
244
+ /** Module-level cache for ESLint complexity results: key = `${threshold}:${files.join(',')}` */
245
+ const _complexityCache: Map<string, ComplexityViolation[]> = new Map();
246
+
247
+ /**
248
+ * Run ESLint complexity analysis on the specified files and return violations.
249
+ * Results are memoized per unique (files, threshold) combination within a process lifetime.
250
+ */
251
+ function analyzeComplexity(
252
+ cwd: string,
253
+ files: string[],
254
+ options: ComplexityOptions = {}
255
+ ): ComplexityViolation[] {
256
+ if (!files || files.length === 0) return [];
257
+
258
+ const threshold: number = options.threshold || 10;
259
+ const onProgress = options.onProgress;
260
+
261
+ // Filter to existing files only
262
+ const existingFiles: string[] = files.filter((f: string) => {
263
+ const absPath: string = path.resolve(cwd, f);
264
+ try {
265
+ fs.statSync(absPath);
266
+ return true;
267
+ } catch {
268
+ return false;
269
+ }
270
+ });
271
+
272
+ if (existingFiles.length === 0) return [];
273
+
274
+ // Check memoization cache (skip for calls with progress callbacks since they need events)
275
+ const cacheKey: string = `${threshold}:${existingFiles.join(',')}`;
276
+ if (!onProgress && _complexityCache.has(cacheKey)) {
277
+ return _complexityCache.get(cacheKey) as ComplexityViolation[];
278
+ }
279
+
280
+ if (onProgress) {
281
+ onProgress({ event: 'started', file_count: existingFiles.length });
282
+ }
283
+
284
+ try {
285
+ // Run ESLint with complexity rule only, JSON output
286
+ // Use execFileSync with args array to avoid shell quoting issues
287
+ // Use relative paths with -- separator to avoid "outside base path" errors
288
+ const args: string[] = [
289
+ 'eslint',
290
+ '--no-config-lookup',
291
+ '--rule',
292
+ `complexity: ["warn", ${threshold}]`,
293
+ '--format',
294
+ 'json',
295
+ '--',
296
+ ...existingFiles,
297
+ ];
298
+
299
+ const result: string = execFileSync('npx', args, {
300
+ cwd,
301
+ encoding: 'utf-8',
302
+ stdio: ['pipe', 'pipe', 'pipe'],
303
+ });
304
+
305
+ const violations: ComplexityViolation[] = parseEslintComplexityResults(cwd, result);
306
+ if (!onProgress) _complexityCache.set(cacheKey, violations);
307
+ if (onProgress) {
308
+ onProgress({ event: 'completed', violation_count: violations.length });
309
+ }
310
+ return violations;
311
+ } catch (err: unknown) {
312
+ // ESLint exits with code 1 when there are warnings/errors; stdout still has JSON
313
+ const execErr = err as { stdout?: string; message?: string };
314
+ if (execErr.stdout) {
315
+ try {
316
+ const violations: ComplexityViolation[] = parseEslintComplexityResults(cwd, execErr.stdout);
317
+ if (!onProgress) _complexityCache.set(cacheKey, violations);
318
+ if (onProgress) {
319
+ onProgress({ event: 'completed', violation_count: violations.length });
320
+ }
321
+ return violations;
322
+ } catch {
323
+ process.stderr.write('[cleanup] ESLint complexity analysis failed to parse output\n');
324
+ return [];
325
+ }
326
+ }
327
+ process.stderr.write(
328
+ '[cleanup] ESLint complexity analysis failed: ' + (execErr.message || 'unknown error') + '\n'
329
+ );
330
+ return [];
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Parse ESLint JSON output for complexity violations.
336
+ */
337
+ function parseEslintComplexityResults(cwd: string, jsonOutput: string): ComplexityViolation[] {
338
+ const results: EslintFileResult[] = JSON.parse(jsonOutput);
339
+ const violations: ComplexityViolation[] = [];
340
+ // Resolve cwd to real path for consistent path.relative calculation
341
+ // (handles macOS /var -> /private/var symlink)
342
+ const realCwd: string = fs.realpathSync(cwd);
343
+
344
+ for (const fileResult of results) {
345
+ for (const msg of fileResult.messages || []) {
346
+ if (msg.ruleId !== 'complexity') continue;
347
+
348
+ // Extract function name and complexity from message text
349
+ // Format: "Function 'name' has a complexity of N. Maximum allowed is M."
350
+ // or: "Arrow function has a complexity of N. Maximum allowed is M."
351
+ const funcMatch: RegExpMatchArray | null = msg.message.match(
352
+ /(?:Function '(\w+)'|(\w+ function))/
353
+ );
354
+ const complexityMatch: RegExpMatchArray | null = msg.message.match(/complexity of (\d+)/);
355
+
356
+ if (complexityMatch) {
357
+ violations.push({
358
+ file: path.relative(realCwd, fileResult.filePath),
359
+ line: msg.line,
360
+ functionName: funcMatch ? funcMatch[1] || funcMatch[2] : 'anonymous',
361
+ complexity: parseInt(complexityMatch[1], 10),
362
+ });
363
+ }
364
+ }
365
+ }
366
+
367
+ return violations;
368
+ }
369
+
370
+ // ─── Dead Export Detection ────────────────────────────────────────────────────
371
+
372
+ /**
373
+ * Detect unused exports by scanning `module.exports` declarations and searching
374
+ * for corresponding `require()` imports across the codebase.
375
+ */
376
+ function analyzeDeadExports(
377
+ cwd: string,
378
+ files: string[],
379
+ options: DeadExportOptions = {}
380
+ ): DeadExportViolation[] {
381
+ if (!files || files.length === 0) return [];
382
+
383
+ const excludePatterns: string[] = options.excludePatterns || [];
384
+ const deadExports: DeadExportViolation[] = [];
385
+
386
+ for (const file of files) {
387
+ const absPath: string = path.resolve(cwd, file);
388
+ const content: string | null = _cachedRead(absPath);
389
+ if (!content) continue;
390
+
391
+ const exportNames: string[] = extractExportNames(content);
392
+ if (exportNames.length === 0) continue;
393
+
394
+ const allJsFiles: string[] = findJsFiles(cwd, excludePatterns);
395
+
396
+ for (const exportName of exportNames) {
397
+ const hasConsumer: boolean = allJsFiles.some((jsFile: string) => {
398
+ if (path.resolve(cwd, jsFile) === absPath) return false;
399
+ const jsContent: string | null = safeReadFile(path.resolve(cwd, jsFile));
400
+ if (!jsContent) return false;
401
+
402
+ // Check for destructured import: { exportName } = require(...)
403
+ // or property access: someVar.exportName
404
+ const patterns: RegExp[] = [
405
+ new RegExp(`\\b${exportName}\\b.*require`, 'g'),
406
+ new RegExp(`require.*\\b${exportName}\\b`, 'g'),
407
+ new RegExp(`\\.${exportName}\\b`, 'g'),
408
+ ];
409
+
410
+ return patterns.some((p: RegExp) => p.test(jsContent));
411
+ });
412
+
413
+ if (!hasConsumer) {
414
+ // Find the line number where this name is exported
415
+ const exportLine: number = findExportLine(content, exportName);
416
+ deadExports.push({
417
+ file,
418
+ exportName,
419
+ line: exportLine,
420
+ });
421
+ }
422
+ }
423
+ }
424
+
425
+ return deadExports;
426
+ }
427
+
428
+ /**
429
+ * Extract exported names from a JS file content.
430
+ * Handles `module.exports = { a, b, c }` and `exports.name = ...` patterns.
431
+ */
432
+ function extractExportNames(content: string): string[] {
433
+ const names: string[] = [];
434
+
435
+ // Pattern 1: module.exports = { name1, name2, ... }
436
+ // Handle multiline exports blocks
437
+ const moduleExportsMatch: RegExpMatchArray | null = content.match(
438
+ /module\.exports\s*=\s*\{([^}]+)\}/s
439
+ );
440
+ if (moduleExportsMatch) {
441
+ const block: string = moduleExportsMatch[1];
442
+ // Split on commas, extract name (handle `name: value` and plain `name`)
443
+ const entries: string[] = block.split(',');
444
+ for (const entry of entries) {
445
+ const trimmed: string = entry.trim();
446
+ if (!trimmed) continue;
447
+ // Could be "name" or "name: value" or "name: someFunction"
448
+ const nameMatch: RegExpMatchArray | null = trimmed.match(/^(\w+)/);
449
+ if (nameMatch) {
450
+ names.push(nameMatch[1]);
451
+ }
452
+ }
453
+ }
454
+
455
+ // Pattern 2: exports.funcName = ...
456
+ const exportsPattern: RegExp = /exports\.(\w+)\s*=/g;
457
+ let match: RegExpExecArray | null;
458
+ while ((match = exportsPattern.exec(content)) !== null) {
459
+ if (!names.includes(match[1])) {
460
+ names.push(match[1]);
461
+ }
462
+ }
463
+
464
+ return names;
465
+ }
466
+
467
+ /**
468
+ * Find the line number where an export name is defined or exported.
469
+ */
470
+ function findExportLine(content: string, exportName: string): number {
471
+ const lines: string[] = content.split('\n');
472
+ for (let i = 0; i < lines.length; i++) {
473
+ if (lines[i].includes(exportName)) {
474
+ return i + 1;
475
+ }
476
+ }
477
+ return 0;
478
+ }
479
+
480
+ /**
481
+ * Recursively find all .js files under cwd, excluding node_modules and
482
+ * files matching excludePatterns. Delegates to the shared walkJsFiles utility.
483
+ */
484
+ function findJsFiles(cwd: string, excludePatterns: string[] = []): string[] {
485
+ return walkJsFiles(cwd, excludePatterns) as string[];
486
+ }
487
+
488
+ // ─── File Size Analysis ──────────────────────────────────────────────────────
489
+
490
+ /**
491
+ * Check files against configurable line-count thresholds.
492
+ */
493
+ function analyzeFileSize(
494
+ cwd: string,
495
+ files: string[],
496
+ thresholds: FileSizeThresholds = {}
497
+ ): FileSizeViolation[] {
498
+ if (!files || files.length === 0) return [];
499
+
500
+ const maxLines: number = thresholds.maxLines || 500;
501
+ const violations: FileSizeViolation[] = [];
502
+
503
+ for (const file of files) {
504
+ const absPath: string = path.resolve(cwd, file);
505
+ const content: string | null = _cachedRead(absPath);
506
+ if (!content) continue;
507
+
508
+ const lines: number = content.split('\n').length - (content.endsWith('\n') ? 1 : 0);
509
+
510
+ if (lines > maxLines) {
511
+ violations.push({
512
+ file,
513
+ lines,
514
+ threshold: maxLines,
515
+ });
516
+ }
517
+ }
518
+
519
+ return violations;
520
+ }
521
+
522
+ // ─── Doc Drift Detection ─────────────────────────────────────────────────────
523
+
524
+ /**
525
+ * Detect stale CHANGELOG.md by comparing its mtime to the newest SUMMARY.md.
526
+ */
527
+ function analyzeChangelogDrift(cwd: string): ChangelogDriftViolation[] {
528
+ const changelogPath: string = path.join(cwd, 'CHANGELOG.md');
529
+ try {
530
+ fs.statSync(changelogPath);
531
+ } catch {
532
+ return []; // No CHANGELOG.md — graceful skip
533
+ }
534
+
535
+ // Find all *-SUMMARY.md files under .planning/phases/
536
+ const phasesDir: string = getPhasesDirPath(cwd);
537
+ const summaryFiles: string[] = _findSummaryFiles(phasesDir);
538
+ if (summaryFiles.length === 0) return []; // Nothing to compare
539
+
540
+ // Get mtimes
541
+ const changelogMtime: Date = fs.statSync(changelogPath).mtime;
542
+ let newestSummaryMtime: Date = new Date(0);
543
+ for (const sf of summaryFiles) {
544
+ const mtime: Date = fs.statSync(sf).mtime;
545
+ if (mtime > newestSummaryMtime) {
546
+ newestSummaryMtime = mtime;
547
+ }
548
+ }
549
+
550
+ if (changelogMtime < newestSummaryMtime) {
551
+ return [
552
+ {
553
+ file: 'CHANGELOG.md',
554
+ reason: 'Not updated since last plan completion',
555
+ last_modified: changelogMtime.toISOString(),
556
+ latest_summary: newestSummaryMtime.toISOString(),
557
+ },
558
+ ];
559
+ }
560
+
561
+ return [];
562
+ }
563
+
564
+ /**
565
+ * Recursively find all *-SUMMARY.md files under a directory.
566
+ */
567
+ function _findSummaryFiles(dir: string): string[] {
568
+ const results: string[] = [];
569
+ try {
570
+ const entries: import('fs').Dirent[] = fs.readdirSync(dir, {
571
+ withFileTypes: true,
572
+ });
573
+ for (const entry of entries) {
574
+ const fullPath: string = path.join(dir, entry.name);
575
+ if (entry.isDirectory()) {
576
+ results.push(..._findSummaryFiles(fullPath));
577
+ } else if (entry.isFile() && entry.name.endsWith('-SUMMARY.md')) {
578
+ results.push(fullPath);
579
+ }
580
+ }
581
+ } catch {
582
+ // Directory doesn't exist — return empty
583
+ }
584
+ return results;
585
+ }
586
+
587
+ /**
588
+ * Detect broken internal file references in README.md markdown links.
589
+ */
590
+ function analyzeReadmeLinks(cwd: string): BrokenLinkViolation[] {
591
+ const readmePath: string = path.join(cwd, 'README.md');
592
+ const content: string | null = safeReadFile(readmePath);
593
+ if (!content) return [];
594
+
595
+ const broken: BrokenLinkViolation[] = [];
596
+ const lines: string[] = content.split('\n');
597
+ const linkRegex: RegExp = /\[([^\]]*)\]\(([^)"\s]+)(?:\s+"[^"]*")?\)/g;
598
+
599
+ for (let i = 0; i < lines.length; i++) {
600
+ let match: RegExpExecArray | null;
601
+ // Reset lastIndex for each line since we reuse the regex
602
+ linkRegex.lastIndex = 0;
603
+ while ((match = linkRegex.exec(lines[i])) !== null) {
604
+ const linkPath: string = match[2];
605
+
606
+ // Skip external links and anchor-only links
607
+ if (
608
+ linkPath.startsWith('http://') ||
609
+ linkPath.startsWith('https://') ||
610
+ linkPath.startsWith('#')
611
+ ) {
612
+ continue;
613
+ }
614
+
615
+ // Resolve relative path against cwd
616
+ const resolved: string = path.resolve(cwd, linkPath);
617
+ if (!fs.existsSync(resolved)) {
618
+ broken.push({
619
+ file: 'README.md',
620
+ link: linkPath,
621
+ line: i + 1,
622
+ });
623
+ }
624
+ }
625
+ }
626
+
627
+ return broken;
628
+ }
629
+
630
+ /**
631
+ * Detect JSDoc @param annotations that do not match actual function parameter names.
632
+ */
633
+ // Compiled once at module level to avoid repeated RegExp construction in loops
634
+ const _JSDOC_BLOCK_RE_SRC: string = '\\/\\*\\*[\\s\\S]*?\\*\\/';
635
+ const _JSDOC_PARAM_RE_SRC: string = '@param\\s+(?:\\{[^}]*\\}\\s+)?(\\w+)';
636
+ const _FUNC_PATTERNS: RegExp[] = [
637
+ /function\s+(\w+)\s*\(([^)]*)\)/,
638
+ /(?:const|let|var)\s+(\w+)\s*=\s*\(([^)]*)\)\s*=>/,
639
+ /(?:const|let|var)\s+(\w+)\s*=\s*(\w+)\s*=>/,
640
+ /(\w+)\s*\(([^)]*)\)\s*\{/,
641
+ ];
642
+
643
+ /**
644
+ * Extract @param names from a JSDoc comment block string.
645
+ */
646
+ function _extractJsdocParams(block: string): string[] {
647
+ const params: string[] = [];
648
+ const re: RegExp = new RegExp(_JSDOC_PARAM_RE_SRC, 'g');
649
+ let m: RegExpExecArray | null;
650
+ while ((m = re.exec(block)) !== null) {
651
+ params.push(m[1]);
652
+ }
653
+ return params;
654
+ }
655
+
656
+ /**
657
+ * Find the function name and parameter string immediately following a JSDoc block.
658
+ */
659
+ function _parseFunctionAfterJsdoc(contentAfterBlock: string): ParsedFunctionInfo {
660
+ const nextLines: string = contentAfterBlock.split('\n').slice(0, 5).join('\n');
661
+ for (const pattern of _FUNC_PATTERNS) {
662
+ const m: RegExpMatchArray | null = nextLines.match(pattern);
663
+ if (m) return { funcName: m[1], paramsStr: m[2] };
664
+ }
665
+ return { funcName: null, paramsStr: null };
666
+ }
667
+
668
+ function analyzeJsdocDrift(cwd: string, files: string[]): JsdocDriftIssue[] {
669
+ if (!files || files.length === 0) return [];
670
+
671
+ const issues: JsdocDriftIssue[] = [];
672
+
673
+ for (const file of files) {
674
+ const absPath: string = path.resolve(cwd, file);
675
+ const content: string | null = _cachedRead(absPath);
676
+ if (!content) continue;
677
+
678
+ const blockRe: RegExp = new RegExp(_JSDOC_BLOCK_RE_SRC, 'g');
679
+ let blockMatch: RegExpExecArray | null;
680
+
681
+ while ((blockMatch = blockRe.exec(content)) !== null) {
682
+ const blockEnd: number = blockMatch.index + blockMatch[0].length;
683
+ const blockStartLine: number = content.substring(0, blockMatch.index).split('\n').length;
684
+
685
+ const docParams: string[] = _extractJsdocParams(blockMatch[0]);
686
+ if (docParams.length === 0) continue; // No @param annotations, skip
687
+
688
+ const { funcName, paramsStr } = _parseFunctionAfterJsdoc(content.substring(blockEnd));
689
+ if (!funcName || paramsStr === null || paramsStr === undefined) continue;
690
+
691
+ const actualParams: string[] = _extractParamNames(paramsStr);
692
+
693
+ for (const dp of docParams) {
694
+ if (!actualParams.includes(dp)) {
695
+ issues.push({
696
+ file,
697
+ line: blockStartLine,
698
+ functionName: funcName,
699
+ issue: `extra @param: ${dp}`,
700
+ });
701
+ }
702
+ }
703
+ for (const ap of actualParams) {
704
+ if (!docParams.includes(ap)) {
705
+ issues.push({
706
+ file,
707
+ line: blockStartLine,
708
+ functionName: funcName,
709
+ issue: `missing @param: ${ap}`,
710
+ });
711
+ }
712
+ }
713
+ }
714
+ }
715
+
716
+ return issues;
717
+ }
718
+
719
+ /**
720
+ * Extract parameter names from a function signature string.
721
+ * Handles default values (x = 1), destructured params ({a, b}), and rest params (...args).
722
+ */
723
+ function _extractParamNames(paramsStr: string): string[] {
724
+ if (!paramsStr || !paramsStr.trim()) return [];
725
+
726
+ const params: string[] = [];
727
+ // Split by commas, but be careful of nested braces/brackets
728
+ let depth: number = 0;
729
+ let current: string = '';
730
+
731
+ for (const ch of paramsStr) {
732
+ if (ch === '{' || ch === '[' || ch === '(') depth++;
733
+ else if (ch === '}' || ch === ']' || ch === ')') depth--;
734
+ else if (ch === ',' && depth === 0) {
735
+ params.push(current.trim());
736
+ current = '';
737
+ continue;
738
+ }
739
+ current += ch;
740
+ }
741
+ if (current.trim()) params.push(current.trim());
742
+
743
+ return params
744
+ .map((p: string) => {
745
+ // Remove rest operator
746
+ let cleaned: string = p.replace(/^\.\.\./, '');
747
+ // Remove default value
748
+ cleaned = cleaned.split('=')[0].trim();
749
+ // If it's a destructured param ({...}), skip — can't match by name
750
+ if (cleaned.startsWith('{') || cleaned.startsWith('[')) return null;
751
+ // Extract just the identifier
752
+ const nameMatch: RegExpMatchArray | null = cleaned.match(/^(\w+)/);
753
+ return nameMatch ? nameMatch[1] : null;
754
+ })
755
+ .filter((n): n is string => n !== null);
756
+ }
757
+
758
+ // ─── Test Coverage Gap Detection ──────────────────────────────────────────────
759
+
760
+ /**
761
+ * Detect exported functions/values with no corresponding mention in test files.
762
+ * For each lib/foo.js, checks tests/unit/foo.test.js for references to each export.
763
+ */
764
+ function analyzeTestCoverageGaps(cwd: string, files: string[]): TestCoverageGap[] {
765
+ if (!files || files.length === 0) return [];
766
+
767
+ const gaps: TestCoverageGap[] = [];
768
+
769
+ for (const file of files) {
770
+ const absPath: string = path.resolve(cwd, file);
771
+ const content: string | null = _cachedRead(absPath);
772
+ if (!content) continue;
773
+
774
+ const exportNames: string[] = extractExportNames(content);
775
+ if (exportNames.length === 0) continue;
776
+
777
+ const basename: string = path.basename(file, '.js');
778
+ const testFile: string = path.join('tests', 'unit', `${basename}.test.js`);
779
+ const testContent: string | null = safeReadFile(path.resolve(cwd, testFile));
780
+
781
+ if (!testContent) {
782
+ for (const exportName of exportNames) {
783
+ gaps.push({
784
+ file,
785
+ exportName,
786
+ testFile,
787
+ line: findExportLine(content, exportName),
788
+ });
789
+ }
790
+ continue;
791
+ }
792
+
793
+ for (const exportName of exportNames) {
794
+ if (!testContent.includes(exportName)) {
795
+ gaps.push({
796
+ file,
797
+ exportName,
798
+ testFile,
799
+ line: findExportLine(content, exportName),
800
+ });
801
+ }
802
+ }
803
+ }
804
+
805
+ return gaps;
806
+ }
807
+
808
+ // ─── Export Consistency Detection ──────────────────────────────────────────────
809
+
810
+ /**
811
+ * Detect stale imports where a destructured require references a name no longer
812
+ * exported by the target module.
813
+ */
814
+ function analyzeExportConsistency(cwd: string, files: string[]): ExportConsistencyIssue[] {
815
+ if (!files || files.length === 0) return [];
816
+
817
+ const issues: ExportConsistencyIssue[] = [];
818
+
819
+ for (const file of files) {
820
+ const absPath: string = path.resolve(cwd, file);
821
+ const content: string | null = _cachedRead(absPath);
822
+ if (!content) continue;
823
+
824
+ const lines: string[] = content.split('\n');
825
+ const requirePattern: RegExp = /const\s+\{([^}]+)\}\s*=\s*require\(['"]([^'"]+)['"]\)/;
826
+
827
+ for (let i = 0; i < lines.length; i++) {
828
+ const match: RegExpMatchArray | null = lines[i].match(requirePattern);
829
+ if (!match) continue;
830
+
831
+ const importedNames: string[] = match[1]
832
+ .split(',')
833
+ .map((n: string) => n.trim())
834
+ .filter(Boolean);
835
+ const requirePath: string = match[2];
836
+ if (!requirePath.startsWith('.')) continue;
837
+
838
+ let sourcePath: string = path.resolve(path.dirname(absPath), requirePath);
839
+ if (!sourcePath.endsWith('.js')) sourcePath += '.js';
840
+ const sourceContent: string | null = safeReadFile(sourcePath);
841
+ if (!sourceContent) continue;
842
+
843
+ const sourceExports: string[] = extractExportNames(sourceContent);
844
+ const sourceModule: string = path.relative(cwd, sourcePath);
845
+
846
+ for (const name of importedNames) {
847
+ const cleanName: string = name.split(':')[0].trim();
848
+ if (!cleanName) continue;
849
+
850
+ if (!sourceExports.includes(cleanName)) {
851
+ issues.push({
852
+ file,
853
+ importedName: cleanName,
854
+ sourceModule,
855
+ line: i + 1,
856
+ });
857
+ }
858
+ }
859
+ }
860
+ }
861
+
862
+ return issues;
863
+ }
864
+
865
+ // ─── Doc Staleness Detection ──────────────────────────────────────────────────
866
+
867
+ /**
868
+ * Cross-reference CLAUDE.md CLI Tooling documentation with mcp-server.js
869
+ * COMMAND_DESCRIPTORS to detect documentation gaps.
870
+ */
871
+ function analyzeDocStaleness(cwd: string): DocStalenessIssue[] {
872
+ const claudeMdPath: string = path.join(cwd, 'CLAUDE.md');
873
+ const claudeContent: string | null = safeReadFile(claudeMdPath);
874
+ if (!claudeContent) return [];
875
+
876
+ const mcpPath: string = path.join(cwd, 'lib', 'mcp-server.js');
877
+ const mcpContent: string | null = safeReadFile(mcpPath);
878
+ if (!mcpContent) return [];
879
+
880
+ const issues: DocStalenessIssue[] = [];
881
+ const documentedCommands: DocumentedCommand[] = _extractDocumentedCommands(claudeContent);
882
+ const actualTools: Set<string> = _extractToolNames(mcpContent);
883
+
884
+ // Check documented but not implemented
885
+ for (const cmd of documentedCommands) {
886
+ const hasMatch: boolean = cmd.possibleNames.some((n: string) => actualTools.has(n));
887
+ if (!hasMatch) {
888
+ issues.push({
889
+ file: 'CLAUDE.md',
890
+ issue: 'documented-but-not-implemented',
891
+ detail: `Command "${cmd.raw}" not found in COMMAND_DESCRIPTORS`,
892
+ line: cmd.line,
893
+ });
894
+ }
895
+ }
896
+
897
+ // Check implemented but not documented
898
+ const allDocumentedNames: Set<string> = new Set();
899
+ for (const cmd of documentedCommands) {
900
+ for (const n of cmd.possibleNames) {
901
+ allDocumentedNames.add(n);
902
+ }
903
+ }
904
+ for (const tool of actualTools) {
905
+ if (!allDocumentedNames.has(tool)) {
906
+ issues.push({
907
+ file: 'lib/mcp-server.js',
908
+ issue: 'implemented-but-not-documented',
909
+ detail: `Tool "${tool}" not documented in CLAUDE.md CLI Tooling section`,
910
+ line: 0,
911
+ });
912
+ }
913
+ }
914
+
915
+ return issues;
916
+ }
917
+
918
+ /**
919
+ * Extract documented CLI commands from the "CLI Tooling" section of CLAUDE.md.
920
+ */
921
+ function _extractDocumentedCommands(content: string): DocumentedCommand[] {
922
+ const commands: DocumentedCommand[] = [];
923
+ const lines: string[] = content.split('\n');
924
+
925
+ let inSection: boolean = false;
926
+ for (let i = 0; i < lines.length; i++) {
927
+ const line: string = lines[i];
928
+
929
+ if (/^## CLI Tooling/.test(line)) {
930
+ inSection = true;
931
+ continue;
932
+ }
933
+ if (inSection && /^## /.test(line)) break;
934
+ if (!inSection) continue;
935
+
936
+ const cmdMatch: RegExpMatchArray | null = line.match(/^- `([^`]+)`/);
937
+ if (!cmdMatch) continue;
938
+
939
+ const rawCmd: string = cmdMatch[1];
940
+ const cleaned: string = rawCmd
941
+ .replace(/\[[^\]]*\]/g, '')
942
+ .replace(/<[^>]*>/g, '')
943
+ .replace(/--\S+(\s+"[^"]*"|\s+\S+)?/g, '')
944
+ .replace(/\.\.\./g, '')
945
+ .trim()
946
+ .replace(/\s+/g, ' ');
947
+
948
+ if (!cleaned) continue;
949
+
950
+ const possibleNames: string[] = _generateToolNames(cleaned);
951
+ if (possibleNames.length > 0) {
952
+ commands.push({ raw: rawCmd, possibleNames, line: i + 1 });
953
+ }
954
+ }
955
+
956
+ return commands;
957
+ }
958
+
959
+ /**
960
+ * Generate possible MCP tool names from a cleaned CLI command string.
961
+ * Handles slash-separated subcommands and " / " alternatives.
962
+ */
963
+ function _generateToolNames(cleaned: string): string[] {
964
+ const names: string[] = [];
965
+
966
+ // Handle " / " pattern: "state add-blocker / resolve-blocker"
967
+ if (cleaned.includes(' / ')) {
968
+ const parts: string[] = cleaned.split(' / ').map((p: string) => p.trim());
969
+ const firstParts: string[] = parts[0].split(/\s+/);
970
+ if (firstParts.length >= 2) {
971
+ const base: string = firstParts.slice(0, -1).join('_').replace(/-/g, '_');
972
+ names.push('grd_' + base + '_' + firstParts[firstParts.length - 1].replace(/-/g, '_'));
973
+ for (let j = 1; j < parts.length; j++) {
974
+ names.push('grd_' + base + '_' + parts[j].replace(/-/g, '_'));
975
+ }
976
+ }
977
+ return names;
978
+ }
979
+
980
+ const words: string[] = cleaned.split(/\s+/).filter(Boolean);
981
+ const lastWord: string | undefined = words[words.length - 1];
982
+
983
+ // Handle slash-separated in last word
984
+ if (lastWord && lastWord.includes('/')) {
985
+ const base: string = words.slice(0, -1).join('_').replace(/-/g, '_');
986
+ const subs: string[] = lastWord.split('/');
987
+ // Add base-only version (for parameterized tools like scaffold)
988
+ if (base) names.push('grd_' + base);
989
+ // Add expanded versions
990
+ for (const sub of subs) {
991
+ const subNorm: string = sub.replace(/-/g, '_');
992
+ names.push(base ? 'grd_' + base + '_' + subNorm : 'grd_' + subNorm);
993
+ }
994
+ return names;
995
+ }
996
+
997
+ // Simple command
998
+ names.push('grd_' + cleaned.replace(/[\s-]+/g, '_'));
999
+ return names;
1000
+ }
1001
+
1002
+ /**
1003
+ * Extract tool names from mcp-server.js COMMAND_DESCRIPTORS.
1004
+ */
1005
+ function _extractToolNames(content: string): Set<string> {
1006
+ const names: Set<string> = new Set();
1007
+ const namePattern: RegExp = /name:\s*'(grd_[^']+)'/g;
1008
+ let match: RegExpExecArray | null;
1009
+ while ((match = namePattern.exec(content)) !== null) {
1010
+ names.add(match[1]);
1011
+ }
1012
+ return names;
1013
+ }
1014
+
1015
+ // ─── Config Schema Drift Detection ───────────────────────────────────────────
1016
+
1017
+ /**
1018
+ * Detect drift between documented config keys in CLAUDE.md and actual
1019
+ * config.json keys, and verify COMMAND_DESCRIPTORS execute references.
1020
+ */
1021
+ function analyzeConfigSchemaDrift(cwd: string): ConfigSchemaDriftIssue[] {
1022
+ const issues: ConfigSchemaDriftIssue[] = [];
1023
+
1024
+ // 1. Parse CLAUDE.md Configuration section for documented config keys
1025
+ const claudeMdPath: string = path.join(cwd, 'CLAUDE.md');
1026
+ const claudeContent: string | null = safeReadFile(claudeMdPath);
1027
+ if (!claudeContent) return [];
1028
+
1029
+ const documentedKeys: DocumentedConfigKey[] = _extractDocumentedConfigKeys(claudeContent);
1030
+
1031
+ // 2. Read actual config.json
1032
+ const configPath: string = path.join(cwd, '.planning', 'config.json');
1033
+ const parsedConfig: Record<string, unknown> | null = safeReadJSON(configPath);
1034
+ const actualKeys: string[] | null = parsedConfig
1035
+ ? Object.keys(parsedConfig).filter((k: string) => !k.startsWith('_'))
1036
+ : null;
1037
+
1038
+ if (actualKeys) {
1039
+ for (const dk of documentedKeys) {
1040
+ if (!actualKeys.includes(dk.key)) {
1041
+ issues.push({
1042
+ file: 'CLAUDE.md',
1043
+ issue: 'documented-key-not-in-config',
1044
+ detail: `Config key "${dk.key}" documented but not found in config.json`,
1045
+ line: dk.line,
1046
+ });
1047
+ }
1048
+ }
1049
+
1050
+ const documentedKeySet: Set<string> = new Set(
1051
+ documentedKeys.map((dk: DocumentedConfigKey) => dk.key)
1052
+ );
1053
+ for (const key of actualKeys) {
1054
+ if (!documentedKeySet.has(key)) {
1055
+ issues.push({
1056
+ file: path.relative(cwd, path.join(getPlanningDir(cwd), 'config.json')),
1057
+ issue: 'config-key-not-documented',
1058
+ detail: `Config key "${key}" present but not documented in CLAUDE.md`,
1059
+ line: 0,
1060
+ });
1061
+ }
1062
+ }
1063
+ }
1064
+
1065
+ // 3. Check COMMAND_DESCRIPTORS execute references
1066
+ const mcpPath: string = path.join(cwd, 'lib', 'mcp-server.js');
1067
+ const mcpContent: string | null = safeReadFile(mcpPath);
1068
+ if (!mcpContent) return issues;
1069
+
1070
+ const executePattern: RegExp = /execute:\s*\([^)]*\)\s*=>\s*(\w+)\(/g;
1071
+ const executeFunctions: Set<string> = new Set();
1072
+ let execMatch: RegExpExecArray | null;
1073
+ while ((execMatch = executePattern.exec(mcpContent)) !== null) {
1074
+ executeFunctions.add(execMatch[1]);
1075
+ }
1076
+
1077
+ const preamble: string = mcpContent.split('const COMMAND_DESCRIPTORS')[0] || '';
1078
+ for (const funcName of executeFunctions) {
1079
+ if (!preamble.includes(funcName)) {
1080
+ issues.push({
1081
+ file: 'lib/mcp-server.js',
1082
+ issue: 'execute-function-not-imported',
1083
+ detail: `Function "${funcName}" referenced in execute but not imported`,
1084
+ line: 0,
1085
+ });
1086
+ }
1087
+ }
1088
+
1089
+ return issues;
1090
+ }
1091
+
1092
+ /**
1093
+ * Extract documented configuration keys from the "Configuration" section of CLAUDE.md.
1094
+ */
1095
+ function _extractDocumentedConfigKeys(content: string): DocumentedConfigKey[] {
1096
+ const keys: DocumentedConfigKey[] = [];
1097
+ const lines: string[] = content.split('\n');
1098
+
1099
+ let inSection: boolean = false;
1100
+ for (let i = 0; i < lines.length; i++) {
1101
+ const line: string = lines[i];
1102
+
1103
+ if (/^## Configuration/.test(line)) {
1104
+ inSection = true;
1105
+ continue;
1106
+ }
1107
+ if (inSection && /^## /.test(line)) break;
1108
+ if (!inSection) continue;
1109
+
1110
+ const keyMatch: RegExpMatchArray | null = line.match(/^- `(\w+)`/);
1111
+ if (keyMatch) {
1112
+ keys.push({ key: keyMatch[1], line: i + 1 });
1113
+ }
1114
+ }
1115
+
1116
+ return keys;
1117
+ }
1118
+
1119
+ // ─── Quality Analysis Orchestrator ───────────────────────────────────────────
1120
+
1121
+ /**
1122
+ * Orchestrate all quality checks (complexity, dead exports, file size, doc drift)
1123
+ * and return a structured quality report.
1124
+ */
1125
+ function runQualityAnalysis(cwd: string, phaseNum: string): QualityAnalysisResult {
1126
+ const config: CleanupConfig = getCleanupConfig(cwd);
1127
+
1128
+ if (!config.enabled) {
1129
+ return { skipped: true, reason: 'phase_cleanup not enabled' };
1130
+ }
1131
+
1132
+ _cleanupCache.init();
1133
+ try {
1134
+ // Find files to analyze: scan lib/*.js as default
1135
+ const jsFiles: string[] = findAnalysisFiles(cwd, phaseNum);
1136
+
1137
+ // Run core analyses
1138
+ const complexityResults: ComplexityViolation[] = analyzeComplexity(cwd, jsFiles);
1139
+ const deadExportResults: DeadExportViolation[] = analyzeDeadExports(cwd, jsFiles);
1140
+ const fileSizeResults: FileSizeViolation[] = analyzeFileSize(cwd, jsFiles);
1141
+
1142
+ const baseIssues: number =
1143
+ complexityResults.length + deadExportResults.length + fileSizeResults.length;
1144
+
1145
+ const summary: QualityAnalysisSummary = {
1146
+ total_issues: baseIssues,
1147
+ complexity_violations: complexityResults.length,
1148
+ dead_exports: deadExportResults.length,
1149
+ oversized_files: fileSizeResults.length,
1150
+ };
1151
+
1152
+ const details: QualityAnalysisDetails = {
1153
+ complexity: complexityResults,
1154
+ dead_exports: deadExportResults,
1155
+ file_size: fileSizeResults,
1156
+ };
1157
+
1158
+ // Run doc drift checks when doc_sync is enabled
1159
+ if (config.doc_sync) {
1160
+ const changelogResults: ChangelogDriftViolation[] = analyzeChangelogDrift(cwd);
1161
+ const readmeResults: BrokenLinkViolation[] = analyzeReadmeLinks(cwd);
1162
+ const jsdocResults: JsdocDriftIssue[] = analyzeJsdocDrift(cwd, jsFiles);
1163
+
1164
+ const docDriftCount: number =
1165
+ changelogResults.length + readmeResults.length + jsdocResults.length;
1166
+ summary.doc_drift_issues = docDriftCount;
1167
+ summary.total_issues += docDriftCount;
1168
+
1169
+ details.doc_drift = {
1170
+ changelog: changelogResults,
1171
+ readme_links: readmeResults,
1172
+ jsdoc: jsdocResults,
1173
+ };
1174
+ }
1175
+
1176
+ // Run test coverage gap analysis when test_coverage is enabled
1177
+ if (config.test_coverage) {
1178
+ const testCoverageResults: TestCoverageGap[] = analyzeTestCoverageGaps(cwd, jsFiles);
1179
+ summary.test_coverage_gaps = testCoverageResults.length;
1180
+ summary.total_issues += testCoverageResults.length;
1181
+ details.test_coverage = testCoverageResults;
1182
+ }
1183
+
1184
+ // Run export consistency analysis when export_consistency is enabled
1185
+ if (config.export_consistency) {
1186
+ const exportConsistencyResults: ExportConsistencyIssue[] = analyzeExportConsistency(
1187
+ cwd,
1188
+ jsFiles
1189
+ );
1190
+ summary.stale_imports = exportConsistencyResults.length;
1191
+ summary.total_issues += exportConsistencyResults.length;
1192
+ details.export_consistency = exportConsistencyResults;
1193
+ }
1194
+
1195
+ // Run doc staleness analysis when doc_staleness is enabled
1196
+ if (config.doc_staleness) {
1197
+ const docStalenessResults: DocStalenessIssue[] = analyzeDocStaleness(cwd);
1198
+ summary.doc_staleness_issues = docStalenessResults.length;
1199
+ summary.total_issues += docStalenessResults.length;
1200
+ details.doc_staleness = docStalenessResults;
1201
+ }
1202
+
1203
+ // Run config schema drift analysis when config_schema is enabled
1204
+ if (config.config_schema) {
1205
+ const configSchemaResults: ConfigSchemaDriftIssue[] = analyzeConfigSchemaDrift(cwd);
1206
+ summary.config_schema_issues = configSchemaResults.length;
1207
+ summary.total_issues += configSchemaResults.length;
1208
+ details.config_schema = configSchemaResults;
1209
+ }
1210
+
1211
+ // Compute trends against previous phase if history exists
1212
+ const history: QualityHistory = loadQualityHistory(cwd);
1213
+ let trends: Record<string, TrendEntry> | null = null;
1214
+ const prevPhase: string = String(parseInt(phaseNum, 10) - 1);
1215
+ if (history[prevPhase]) {
1216
+ trends = computeTrends(summary, history[prevPhase], prevPhase);
1217
+ }
1218
+
1219
+ return {
1220
+ phase: phaseNum,
1221
+ timestamp: new Date().toISOString().split('T')[0],
1222
+ summary,
1223
+ details,
1224
+ trends,
1225
+ };
1226
+ } finally {
1227
+ _cleanupCache.reset();
1228
+ }
1229
+ }
1230
+
1231
+ /**
1232
+ * Find files to analyze for a given phase. Scans lib/*.js as fallback.
1233
+ */
1234
+ function findAnalysisFiles(cwd: string, _phaseNum: string): string[] {
1235
+ const libDir: string = path.join(cwd, 'lib');
1236
+ try {
1237
+ const entries: import('fs').Dirent[] = fs.readdirSync(libDir, {
1238
+ withFileTypes: true,
1239
+ });
1240
+ return entries
1241
+ .filter((e: import('fs').Dirent) => e.isFile() && e.name.endsWith('.js'))
1242
+ .map((e: import('fs').Dirent) => path.join('lib', e.name));
1243
+ } catch {
1244
+ return [];
1245
+ }
1246
+ }
1247
+
1248
+ // ─── Cleanup Plan Generation ──────────────────────────────────────────────────
1249
+
1250
+ /**
1251
+ * Auto-generate a cleanup PLAN.md when quality issues exceed a configurable threshold.
1252
+ * Scans existing plan files in the phase directory to determine the next plan number,
1253
+ * then writes a standard-format PLAN.md with tasks derived from the quality report.
1254
+ */
1255
+ function generateCleanupPlan(
1256
+ cwd: string,
1257
+ phaseNum: string,
1258
+ qualityReport: QualityAnalysisResult
1259
+ ): CleanupPlanResult | null {
1260
+ const config: CleanupConfig = getCleanupConfig(cwd);
1261
+ const threshold: number = config.cleanup_threshold;
1262
+
1263
+ // No plan needed if issues are at or below threshold
1264
+ if (!qualityReport || !qualityReport.summary || qualityReport.summary.total_issues <= threshold) {
1265
+ return null;
1266
+ }
1267
+
1268
+ // Find phase directory
1269
+ const phasesDir: string = getPhasesDirPath(cwd);
1270
+ let phaseDir: string | null = null;
1271
+ let phaseDirName: string | null = null;
1272
+
1273
+ try {
1274
+ const entries: import('fs').Dirent[] = fs.readdirSync(phasesDir, {
1275
+ withFileTypes: true,
1276
+ });
1277
+ const dirs: string[] = entries
1278
+ .filter((e: import('fs').Dirent) => e.isDirectory())
1279
+ .map((e: import('fs').Dirent) => e.name);
1280
+ const normalized: string = String(phaseNum).padStart(2, '0');
1281
+ const match: string | undefined = dirs.find(
1282
+ (d: string) => d.startsWith(normalized + '-') || d === normalized
1283
+ );
1284
+ if (match) {
1285
+ phaseDir = path.join(phasesDir, match);
1286
+ phaseDirName = match;
1287
+ }
1288
+ } catch {
1289
+ // phases directory doesn't exist
1290
+ }
1291
+
1292
+ if (!phaseDir || !phaseDirName) return null;
1293
+
1294
+ // Find next plan number
1295
+ let nextPlanNum: number = 1;
1296
+ try {
1297
+ const files: string[] = fs.readdirSync(phaseDir);
1298
+ const normalized: string = String(phaseNum).padStart(2, '0');
1299
+ const planFiles: string[] = files.filter((f: string) => {
1300
+ const pattern: RegExp = new RegExp(`^${normalized}-(\\d{2})-PLAN\\.md$`);
1301
+ return pattern.test(f);
1302
+ });
1303
+
1304
+ if (planFiles.length > 0) {
1305
+ const planNums: number[] = planFiles.map((f: string) => {
1306
+ const m: RegExpMatchArray | null = f.match(/(\d{2})-PLAN\.md$/);
1307
+ return m ? parseInt(m[1], 10) : 0;
1308
+ });
1309
+ nextPlanNum = Math.max(...planNums) + 1;
1310
+ }
1311
+ } catch {
1312
+ // directory read error
1313
+ }
1314
+
1315
+ const paddedPlan: string = String(nextPlanNum).padStart(2, '0');
1316
+ const phaseSlug: string = phaseDirName.replace(/^\d+(?:\.\d+)?-?/, '');
1317
+
1318
+ // Collect unique files from quality issues
1319
+ const filesSet: Set<string> = new Set();
1320
+ const { details } = qualityReport;
1321
+ if (details) {
1322
+ if (details.complexity) {
1323
+ details.complexity.forEach((v: ComplexityViolation) => filesSet.add(v.file));
1324
+ }
1325
+ if (details.dead_exports) {
1326
+ details.dead_exports.forEach((v: DeadExportViolation) => filesSet.add(v.file));
1327
+ }
1328
+ if (details.file_size) {
1329
+ details.file_size.forEach((v: FileSizeViolation) => filesSet.add(v.file));
1330
+ }
1331
+ if (details.doc_drift) {
1332
+ if (details.doc_drift.changelog) {
1333
+ details.doc_drift.changelog.forEach((v: ChangelogDriftViolation) => filesSet.add(v.file));
1334
+ }
1335
+ if (details.doc_drift.readme_links) {
1336
+ details.doc_drift.readme_links.forEach((v: BrokenLinkViolation) => filesSet.add(v.file));
1337
+ }
1338
+ if (details.doc_drift.jsdoc) {
1339
+ details.doc_drift.jsdoc.forEach((v: JsdocDriftIssue) => filesSet.add(v.file));
1340
+ }
1341
+ }
1342
+ if (details.test_coverage) {
1343
+ details.test_coverage.forEach((v: TestCoverageGap) => filesSet.add(v.file));
1344
+ }
1345
+ if (details.export_consistency) {
1346
+ details.export_consistency.forEach((v: ExportConsistencyIssue) => filesSet.add(v.file));
1347
+ }
1348
+ if (details.doc_staleness) {
1349
+ details.doc_staleness.forEach((v: DocStalenessIssue) => filesSet.add(v.file));
1350
+ }
1351
+ if (details.config_schema) {
1352
+ details.config_schema.forEach((v: ConfigSchemaDriftIssue) => filesSet.add(v.file));
1353
+ }
1354
+ }
1355
+
1356
+ const filesModified: string[] = [...filesSet];
1357
+
1358
+ // Build tasks from quality report
1359
+ const tasks: CleanupTask[] = [];
1360
+
1361
+ // Task 1: Code quality issues (complexity, dead exports, file size)
1362
+ const codeIssues: string[] = [];
1363
+ if (details && details.complexity && details.complexity.length > 0) {
1364
+ codeIssues.push(
1365
+ `- Refactor ${details.complexity.length} high-complexity function(s): ${details.complexity.map((v: ComplexityViolation) => `${v.functionName} in ${v.file}`).join(', ')}`
1366
+ );
1367
+ }
1368
+ if (details && details.dead_exports && details.dead_exports.length > 0) {
1369
+ codeIssues.push(
1370
+ `- Remove or document ${details.dead_exports.length} dead export(s): ${details.dead_exports.map((v: DeadExportViolation) => `${v.exportName} in ${v.file}`).join(', ')}`
1371
+ );
1372
+ }
1373
+ if (details && details.file_size && details.file_size.length > 0) {
1374
+ codeIssues.push(
1375
+ `- Split ${details.file_size.length} oversized file(s): ${details.file_size.map((v: FileSizeViolation) => `${v.file} (${v.lines} lines)`).join(', ')}`
1376
+ );
1377
+ }
1378
+
1379
+ if (codeIssues.length > 0) {
1380
+ tasks.push({
1381
+ name: 'Resolve code quality issues',
1382
+ items: codeIssues,
1383
+ });
1384
+ }
1385
+
1386
+ // Task 2: Doc drift issues
1387
+ const docIssues: string[] = [];
1388
+ if (details && details.doc_drift) {
1389
+ if (details.doc_drift.changelog && details.doc_drift.changelog.length > 0) {
1390
+ docIssues.push(`- Update stale CHANGELOG.md`);
1391
+ }
1392
+ if (details.doc_drift.readme_links && details.doc_drift.readme_links.length > 0) {
1393
+ docIssues.push(
1394
+ `- Fix ${details.doc_drift.readme_links.length} broken README link(s): ${details.doc_drift.readme_links.map((v: BrokenLinkViolation) => v.link).join(', ')}`
1395
+ );
1396
+ }
1397
+ if (details.doc_drift.jsdoc && details.doc_drift.jsdoc.length > 0) {
1398
+ docIssues.push(
1399
+ `- Fix ${details.doc_drift.jsdoc.length} JSDoc mismatch(es): ${details.doc_drift.jsdoc.map((v: JsdocDriftIssue) => `${v.functionName} in ${v.file}`).join(', ')}`
1400
+ );
1401
+ }
1402
+ }
1403
+
1404
+ if (docIssues.length > 0) {
1405
+ tasks.push({
1406
+ name: 'Update stale documentation',
1407
+ items: docIssues,
1408
+ });
1409
+ }
1410
+
1411
+ // Task: Test coverage gaps
1412
+ if (details && details.test_coverage && details.test_coverage.length > 0) {
1413
+ tasks.push({
1414
+ name: 'Close test coverage gaps',
1415
+ items: details.test_coverage.map(
1416
+ (v: TestCoverageGap) => `- Add tests for ${v.exportName} from ${v.file} in ${v.testFile}`
1417
+ ),
1418
+ });
1419
+ }
1420
+
1421
+ // Task: Consistency and schema issues
1422
+ const consistencyIssues: string[] = [];
1423
+ if (details && details.export_consistency && details.export_consistency.length > 0) {
1424
+ consistencyIssues.push(
1425
+ `- Fix ${details.export_consistency.length} stale import(s): ${details.export_consistency.map((v: ExportConsistencyIssue) => `${v.importedName} in ${v.file}`).join(', ')}`
1426
+ );
1427
+ }
1428
+ if (details && details.doc_staleness && details.doc_staleness.length > 0) {
1429
+ consistencyIssues.push(`- Resolve ${details.doc_staleness.length} doc staleness issue(s)`);
1430
+ }
1431
+ if (details && details.config_schema && details.config_schema.length > 0) {
1432
+ consistencyIssues.push(`- Fix ${details.config_schema.length} config schema drift(s)`);
1433
+ }
1434
+ if (consistencyIssues.length > 0) {
1435
+ tasks.push({
1436
+ name: 'Fix consistency and schema issues',
1437
+ items: consistencyIssues,
1438
+ });
1439
+ }
1440
+
1441
+ // If no tasks were generated (shouldn't happen if total_issues > threshold), create a generic one
1442
+ if (tasks.length === 0) {
1443
+ tasks.push({
1444
+ name: 'Resolve quality issues',
1445
+ items: [
1446
+ `- Address ${qualityReport.summary!.total_issues} quality issue(s) found during analysis`,
1447
+ ],
1448
+ });
1449
+ }
1450
+
1451
+ // Build PLAN.md content
1452
+ const filesModifiedYaml: string = filesModified.map((f: string) => ` - "${f}"`).join('\n');
1453
+ const taskBlocks: string = tasks
1454
+ .map((t: CleanupTask, i: number) => {
1455
+ return `<task type="auto">
1456
+ <name>Task ${i + 1}: ${t.name}</name>
1457
+ <files>${filesModified.join(', ')}</files>
1458
+ <action>
1459
+ ${t.items.join('\n')}
1460
+ </action>
1461
+ <verify>Run tests and verify issues are resolved.</verify>
1462
+ <done>${t.name} complete.</done>
1463
+ </task>`;
1464
+ })
1465
+ .join('\n\n');
1466
+
1467
+ const planContent: string = `---
1468
+ phase: ${phaseSlug}
1469
+ plan: ${paddedPlan}
1470
+ type: execute
1471
+ wave: 1
1472
+ depends_on: []
1473
+ files_modified:
1474
+ ${filesModifiedYaml}
1475
+ autonomous: true
1476
+ verification_level: sanity
1477
+ cleanup_generated: true
1478
+ must_haves:
1479
+ truths:
1480
+ - "All auto-detected quality issues from phase ${phaseNum} completion are resolved"
1481
+ artifacts: []
1482
+ key_links: []
1483
+ ---
1484
+
1485
+ <objective>
1486
+ Auto-generated cleanup plan for ${qualityReport.summary!.total_issues} quality issue(s) detected during phase ${phaseNum} completion.
1487
+ </objective>
1488
+
1489
+ <tasks>
1490
+
1491
+ ${taskBlocks}
1492
+
1493
+ </tasks>
1494
+
1495
+ <success_criteria>
1496
+ All quality issues from phase ${phaseNum} completion resolved.
1497
+ </success_criteria>
1498
+ `;
1499
+
1500
+ // Write the plan file
1501
+ const normalized: string = String(phaseNum).padStart(2, '0');
1502
+ const planFileName: string = `${normalized}-${paddedPlan}-PLAN.md`;
1503
+ const planPath: string = path.join(phaseDir, planFileName);
1504
+ fs.writeFileSync(planPath, planContent, 'utf-8');
1505
+
1506
+ return {
1507
+ path: path.relative(cwd, planPath),
1508
+ plan_number: paddedPlan,
1509
+ issues_addressed: qualityReport.summary!.total_issues,
1510
+ };
1511
+ }
1512
+
1513
+ // ─── Quality History & Trends ─────────────────────────────────────────────────
1514
+
1515
+ const QUALITY_HISTORY_FILE: string = '.quality-history.json';
1516
+
1517
+ /**
1518
+ * Load quality history from .planning/.quality-history.json.
1519
+ */
1520
+ function loadQualityHistory(cwd: string): QualityHistory {
1521
+ const historyPath: string = path.join(cwd, '.planning', QUALITY_HISTORY_FILE);
1522
+ try {
1523
+ const content: string = fs.readFileSync(historyPath, 'utf-8');
1524
+ return JSON.parse(content) as QualityHistory;
1525
+ } catch {
1526
+ return {};
1527
+ }
1528
+ }
1529
+
1530
+ /**
1531
+ * Save quality metrics for a phase into .planning/.quality-history.json.
1532
+ * Accumulates entries across phases.
1533
+ */
1534
+ function saveQualityMetrics(cwd: string, phase: string, summary: QualityAnalysisSummary): void {
1535
+ const history: QualityHistory = loadQualityHistory(cwd);
1536
+ history[phase] = summary;
1537
+ const historyPath: string = path.join(cwd, '.planning', QUALITY_HISTORY_FILE);
1538
+ fs.writeFileSync(historyPath, JSON.stringify(history, null, 2), 'utf-8');
1539
+ }
1540
+
1541
+ /**
1542
+ * Compute trends (deltas) between current and previous quality summaries.
1543
+ * Only compares numeric fields. Returns an object with delta and label for each field.
1544
+ */
1545
+ function computeTrends(
1546
+ current: QualityAnalysisSummary,
1547
+ previous: QualityAnalysisSummary,
1548
+ fromPhase: string
1549
+ ): Record<string, TrendEntry> {
1550
+ const trends: Record<string, TrendEntry> = {};
1551
+ for (const key of Object.keys(current)) {
1552
+ const currentVal = current[key];
1553
+ const previousVal = previous[key];
1554
+ if (typeof currentVal !== 'number' || typeof previousVal !== 'number') continue;
1555
+ const delta: number = currentVal - previousVal;
1556
+ let arrow: string = '';
1557
+ if (delta > 0)
1558
+ arrow = '\u2191'; // up arrow (regression for issues, improvement for scores)
1559
+ else if (delta < 0) arrow = '\u2193'; // down arrow
1560
+ trends[key] = {
1561
+ delta,
1562
+ label: `${arrow} ${delta >= 0 ? '+' : ''}${delta} from Phase ${fromPhase}`,
1563
+ };
1564
+ }
1565
+ return trends;
1566
+ }
1567
+
1568
+ // ─── Exports ─────────────────────────────────────────────────────────────────
1569
+
1570
+ module.exports = {
1571
+ getCleanupConfig,
1572
+ analyzeComplexity,
1573
+ analyzeDeadExports,
1574
+ analyzeFileSize,
1575
+ analyzeChangelogDrift,
1576
+ analyzeReadmeLinks,
1577
+ analyzeJsdocDrift,
1578
+ analyzeTestCoverageGaps,
1579
+ analyzeExportConsistency,
1580
+ analyzeDocStaleness,
1581
+ analyzeConfigSchemaDrift,
1582
+ runQualityAnalysis,
1583
+ generateCleanupPlan,
1584
+ resetCleanupCache,
1585
+ loadQualityHistory,
1586
+ saveQualityMetrics,
1587
+ computeTrends,
1588
+ };