@howlil/ez-agents 3.4.2 → 4.0.0

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 (365) hide show
  1. package/README.md +735 -462
  2. package/agents/ez-architect-agent.md +267 -0
  3. package/agents/ez-backend-agent.md +303 -0
  4. package/agents/ez-chief-strategist.md +271 -0
  5. package/agents/ez-codebase-mapper.md +770 -770
  6. package/agents/ez-context-manager.md +319 -0
  7. package/agents/ez-debugger.md +1255 -1255
  8. package/agents/ez-design-expert.md +347 -0
  9. package/agents/ez-devops-agent.md +331 -0
  10. package/agents/ez-executor.md +487 -487
  11. package/agents/ez-frontend-agent.md +322 -0
  12. package/agents/ez-phase-researcher.md +553 -553
  13. package/agents/ez-planner.md +1307 -1307
  14. package/agents/ez-product-engineer.md +435 -0
  15. package/agents/ez-project-researcher.md +629 -629
  16. package/agents/ez-qa-agent.md +320 -0
  17. package/agents/ez-release-agent.md +333 -0
  18. package/agents/ez-requirements-agent.md +377 -0
  19. package/agents/ez-roadmapper.md +650 -650
  20. package/agents/ez-technical-writer.md +551 -0
  21. package/agents/ez-ux-expert.md +393 -0
  22. package/agents/ez-verifier.md +579 -579
  23. package/bin/guards/autonomy-guard.cjs +346 -0
  24. package/bin/guards/context-budget-guard.cjs +278 -0
  25. package/bin/guards/hallucination-guard.cjs +380 -0
  26. package/bin/guards/hidden-state-guard.cjs +182 -0
  27. package/bin/guards/team-overhead-guard.cjs +266 -0
  28. package/bin/guards/tool-sprawl-guard.cjs +271 -0
  29. package/bin/install.js +3221 -3272
  30. package/bin/lib/analytics/analytics-collector.cjs +86 -0
  31. package/bin/lib/analytics/analytics-reporter.cjs +130 -0
  32. package/bin/lib/analytics/cohort-analyzer.cjs +138 -0
  33. package/bin/lib/analytics/funnel-analyzer.cjs +147 -0
  34. package/bin/lib/analytics/nps-tracker.cjs +147 -0
  35. package/bin/lib/archetype-detector.cjs +289 -0
  36. package/bin/lib/assistant-adapter.cjs +361 -0
  37. package/bin/lib/audit-exec.cjs +175 -0
  38. package/bin/lib/auth.cjs +176 -0
  39. package/bin/lib/backup-service.cjs +422 -0
  40. package/bin/lib/bdd-validator.cjs +622 -0
  41. package/bin/lib/business-flow-mapper.cjs +429 -0
  42. package/bin/lib/circuit-breaker.cjs +276 -0
  43. package/bin/lib/code-complexity-analyzer.cjs +360 -0
  44. package/bin/lib/codebase-analyzer.cjs +241 -0
  45. package/bin/lib/commands.cjs +691 -0
  46. package/bin/lib/config.cjs +236 -0
  47. package/bin/lib/constraint-extractor.cjs +526 -0
  48. package/bin/lib/content-scanner.cjs +238 -0
  49. package/bin/lib/context-cache.cjs +154 -0
  50. package/bin/lib/context-compressor.cjs +102 -0
  51. package/bin/lib/context-deduplicator.cjs +105 -0
  52. package/bin/lib/context-errors.cjs +78 -0
  53. package/bin/lib/context-manager.cjs +338 -0
  54. package/bin/lib/context-metadata-tracker.cjs +140 -0
  55. package/bin/lib/context-relevance-scorer.cjs +99 -0
  56. package/bin/lib/core.cjs +507 -0
  57. package/bin/lib/cost-alerts.cjs +174 -0
  58. package/bin/lib/cost-tracker.cjs +275 -0
  59. package/bin/lib/crash-recovery.cjs +220 -0
  60. package/bin/lib/dependency-graph.cjs +319 -0
  61. package/bin/lib/deploy/deploy-audit-log.cjs +76 -0
  62. package/bin/lib/deploy/deploy-detector.cjs +69 -0
  63. package/bin/lib/deploy/deploy-env-manager.cjs +109 -0
  64. package/bin/lib/deploy/deploy-health-check.cjs +88 -0
  65. package/bin/lib/deploy/deploy-pre-flight.cjs +57 -0
  66. package/bin/lib/deploy/deploy-rollback.cjs +72 -0
  67. package/bin/lib/deploy/deploy-runner.cjs +97 -0
  68. package/bin/lib/deploy/deploy-status.cjs +74 -0
  69. package/bin/lib/discussion-synthesizer.cjs +439 -0
  70. package/bin/lib/error-cache.cjs +114 -0
  71. package/bin/lib/error-registry.cjs +177 -0
  72. package/bin/lib/file-access.cjs +207 -0
  73. package/bin/lib/file-lock.cjs +236 -0
  74. package/bin/lib/finops/budget-enforcer.cjs +126 -0
  75. package/bin/lib/finops/cost-reporter.cjs +132 -0
  76. package/bin/lib/finops/finops-analyzer.cjs +112 -0
  77. package/bin/lib/finops/spot-manager.cjs +118 -0
  78. package/bin/lib/framework-detector.cjs +396 -0
  79. package/bin/lib/frontmatter.cjs +313 -0
  80. package/bin/lib/fs-utils.cjs +153 -0
  81. package/bin/lib/gate-executor.cjs +272 -0
  82. package/bin/lib/gates/README.md +374 -0
  83. package/bin/lib/gates/gate-01-requirement.cjs +303 -0
  84. package/bin/lib/gates/gate-02-architecture.cjs +555 -0
  85. package/bin/lib/gates/gate-03-code.cjs +635 -0
  86. package/bin/lib/gates/gate-04-security.cjs +829 -0
  87. package/bin/lib/git-errors.cjs +83 -0
  88. package/bin/lib/git-utils.cjs +321 -0
  89. package/bin/lib/git-workflow-engine.cjs +1157 -0
  90. package/bin/lib/health-check.cjs +227 -0
  91. package/bin/lib/index.cjs +279 -0
  92. package/bin/lib/init.cjs +725 -0
  93. package/bin/lib/lock-logger.cjs +194 -0
  94. package/bin/lib/lock-state.cjs +263 -0
  95. package/bin/lib/lockfile-validator.cjs +227 -0
  96. package/bin/lib/log-rotation.cjs +71 -0
  97. package/bin/lib/logger.cjs +125 -0
  98. package/bin/lib/memory-compression.cjs +256 -0
  99. package/bin/lib/milestone.cjs +247 -0
  100. package/bin/lib/model-provider.cjs +241 -0
  101. package/bin/lib/package-manager-detector.cjs +203 -0
  102. package/bin/lib/package-manager-executor.cjs +385 -0
  103. package/bin/lib/package-manager-service.cjs +216 -0
  104. package/bin/lib/perf/api-monitor.cjs +88 -0
  105. package/bin/lib/perf/db-optimizer.cjs +78 -0
  106. package/bin/lib/perf/frontend-performance.cjs +56 -0
  107. package/bin/lib/perf/perf-analyzer.cjs +77 -0
  108. package/bin/lib/perf/perf-baseline.cjs +102 -0
  109. package/bin/lib/perf/perf-reporter.cjs +117 -0
  110. package/bin/lib/perf/regression-detector.cjs +92 -0
  111. package/bin/lib/phase.cjs +963 -0
  112. package/bin/lib/planning-write.cjs +123 -0
  113. package/bin/lib/project-reporter.cjs +565 -0
  114. package/bin/lib/quality-gate.cjs +332 -0
  115. package/bin/lib/quality-metrics.cjs +324 -0
  116. package/bin/lib/recovery-manager.cjs +98 -0
  117. package/bin/lib/release-validator.cjs +617 -0
  118. package/bin/lib/retry.cjs +119 -0
  119. package/bin/lib/roadmap.cjs +309 -0
  120. package/bin/lib/safe-exec.cjs +173 -0
  121. package/bin/lib/safe-path.cjs +130 -0
  122. package/bin/lib/security-errors.cjs +62 -0
  123. package/bin/lib/session-chain.cjs +304 -0
  124. package/bin/lib/session-errors.cjs +81 -0
  125. package/bin/lib/session-export.cjs +251 -0
  126. package/bin/lib/session-import.cjs +262 -0
  127. package/bin/lib/session-manager.cjs +280 -0
  128. package/bin/lib/skill-context.cjs +148 -0
  129. package/bin/lib/skill-matcher.cjs +236 -0
  130. package/bin/lib/skill-registry.cjs +360 -0
  131. package/bin/lib/skill-resolver.cjs +449 -0
  132. package/bin/lib/skill-triggers.cjs +90 -0
  133. package/bin/lib/skill-validator.cjs +270 -0
  134. package/bin/lib/skill-versioning.cjs +355 -0
  135. package/bin/lib/stack-detector.cjs +399 -0
  136. package/bin/lib/state.cjs +736 -0
  137. package/bin/lib/tech-debt-analyzer.cjs +309 -0
  138. package/bin/lib/temp-file.cjs +239 -0
  139. package/bin/lib/template.cjs +223 -0
  140. package/bin/lib/test-file-lock.cjs +112 -0
  141. package/bin/lib/test-graceful.cjs +93 -0
  142. package/bin/lib/test-logger.cjs +60 -0
  143. package/bin/lib/test-safe-exec.cjs +38 -0
  144. package/bin/lib/test-safe-path.cjs +33 -0
  145. package/bin/lib/test-temp-file.cjs +125 -0
  146. package/bin/lib/tier-manager.cjs +428 -0
  147. package/bin/lib/timeout-exec.cjs +63 -0
  148. package/bin/lib/tradeoff-analyzer.cjs +284 -0
  149. package/bin/lib/url-fetch.cjs +170 -0
  150. package/bin/lib/verify.cjs +863 -0
  151. package/bin/update.js +217 -214
  152. package/commands/deploy.cjs +53 -0
  153. package/commands/ez/add-tests.md +41 -41
  154. package/commands/ez/audit-milestone.md +36 -36
  155. package/commands/ez/complete-milestone.md +136 -136
  156. package/commands/ez/discuss-phase.md +90 -90
  157. package/commands/ez/execute-phase.md +52 -41
  158. package/commands/ez/help.md +22 -22
  159. package/commands/ez/map-codebase.md +71 -71
  160. package/commands/ez/new-milestone.md +44 -44
  161. package/commands/ez/new-project.md +51 -42
  162. package/commands/ez/plan-phase.md +53 -45
  163. package/commands/ez/progress.md +36 -24
  164. package/commands/ez/quick.md +45 -45
  165. package/commands/ez/resume-work.md +40 -40
  166. package/commands/ez/run-phase.md +580 -0
  167. package/commands/ez/settings.md +36 -36
  168. package/commands/ez/update.md +37 -37
  169. package/commands/ez/verify-work.md +402 -38
  170. package/commands/health-check.cjs +44 -0
  171. package/commands/rollback.cjs +47 -0
  172. package/ez-agents/bin/ez-tools.cjs +1692 -716
  173. package/ez-agents/bin/guards/autonomy-guard.cjs +346 -0
  174. package/ez-agents/bin/guards/context-budget-guard.cjs +247 -0
  175. package/ez-agents/bin/guards/hallucination-guard.cjs +271 -0
  176. package/ez-agents/bin/guards/hidden-state-guard.cjs +182 -0
  177. package/ez-agents/bin/guards/team-overhead-guard.cjs +266 -0
  178. package/ez-agents/bin/guards/tool-sprawl-guard.cjs +271 -0
  179. package/ez-agents/bin/lib/analytics/analytics-collector.cjs +86 -0
  180. package/ez-agents/bin/lib/analytics/analytics-reporter.cjs +130 -0
  181. package/ez-agents/bin/lib/analytics/cohort-analyzer.cjs +138 -0
  182. package/ez-agents/bin/lib/analytics/funnel-analyzer.cjs +147 -0
  183. package/ez-agents/bin/lib/analytics/nps-tracker.cjs +147 -0
  184. package/ez-agents/bin/lib/archetype-detector.cjs +289 -0
  185. package/ez-agents/bin/lib/audit-exec.cjs +166 -167
  186. package/ez-agents/bin/lib/auth.cjs +176 -176
  187. package/ez-agents/bin/lib/backup-service.cjs +422 -0
  188. package/ez-agents/bin/lib/bdd-validator.cjs +622 -0
  189. package/ez-agents/bin/lib/business-flow-mapper.cjs +429 -0
  190. package/ez-agents/bin/lib/code-complexity-analyzer.cjs +360 -0
  191. package/ez-agents/bin/lib/codebase-analyzer.cjs +241 -0
  192. package/ez-agents/bin/lib/commands.cjs +685 -685
  193. package/ez-agents/bin/lib/config.cjs +41 -1
  194. package/ez-agents/bin/lib/constraint-extractor.cjs +526 -0
  195. package/ez-agents/bin/lib/content-scanner.cjs +238 -0
  196. package/ez-agents/bin/lib/context-cache.cjs +154 -0
  197. package/ez-agents/bin/lib/context-errors.cjs +71 -0
  198. package/ez-agents/bin/lib/context-manager.cjs +220 -0
  199. package/ez-agents/bin/lib/core.cjs +507 -512
  200. package/ez-agents/bin/lib/cost-tracker.cjs +243 -0
  201. package/ez-agents/bin/lib/crash-recovery.cjs +172 -0
  202. package/ez-agents/bin/lib/dependency-graph.cjs +319 -0
  203. package/ez-agents/bin/lib/deploy/deploy-audit-log.cjs +76 -0
  204. package/ez-agents/bin/lib/deploy/deploy-detector.cjs +69 -0
  205. package/ez-agents/bin/lib/deploy/deploy-env-manager.cjs +109 -0
  206. package/ez-agents/bin/lib/deploy/deploy-health-check.cjs +88 -0
  207. package/ez-agents/bin/lib/deploy/deploy-pre-flight.cjs +57 -0
  208. package/ez-agents/bin/lib/deploy/deploy-rollback.cjs +72 -0
  209. package/ez-agents/bin/lib/deploy/deploy-runner.cjs +97 -0
  210. package/ez-agents/bin/lib/deploy/deploy-status.cjs +74 -0
  211. package/ez-agents/bin/lib/discussion-synthesizer.cjs +458 -0
  212. package/ez-agents/bin/lib/file-access.cjs +207 -0
  213. package/ez-agents/bin/lib/finops/budget-enforcer.cjs +126 -0
  214. package/ez-agents/bin/lib/finops/cost-reporter.cjs +132 -0
  215. package/ez-agents/bin/lib/finops/finops-analyzer.cjs +112 -0
  216. package/ez-agents/bin/lib/finops/spot-manager.cjs +118 -0
  217. package/ez-agents/bin/lib/framework-detector.cjs +396 -0
  218. package/ez-agents/bin/lib/frontmatter.cjs +3 -1
  219. package/ez-agents/bin/lib/gates/README.md +374 -0
  220. package/ez-agents/bin/lib/gates/gate-01-requirement.cjs +303 -0
  221. package/ez-agents/bin/lib/gates/gate-02-architecture.cjs +555 -0
  222. package/ez-agents/bin/lib/gates/gate-03-code.cjs +635 -0
  223. package/ez-agents/bin/lib/gates/gate-04-security.cjs +829 -0
  224. package/ez-agents/bin/lib/git-errors.cjs +83 -0
  225. package/ez-agents/bin/lib/git-utils.cjs +118 -0
  226. package/ez-agents/bin/lib/git-workflow-engine.cjs +1157 -0
  227. package/ez-agents/bin/lib/health-check.cjs +162 -162
  228. package/ez-agents/bin/lib/index.cjs +40 -2
  229. package/ez-agents/bin/lib/init.cjs +0 -2
  230. package/ez-agents/bin/lib/lockfile-validator.cjs +227 -0
  231. package/ez-agents/bin/lib/log-rotation.cjs +71 -0
  232. package/ez-agents/bin/lib/logger.cjs +99 -154
  233. package/ez-agents/bin/lib/memory-compression.cjs +256 -0
  234. package/ez-agents/bin/lib/package-manager-detector.cjs +203 -0
  235. package/ez-agents/bin/lib/package-manager-executor.cjs +385 -0
  236. package/ez-agents/bin/lib/package-manager-service.cjs +216 -0
  237. package/ez-agents/bin/lib/perf/api-monitor.cjs +88 -0
  238. package/ez-agents/bin/lib/perf/db-optimizer.cjs +78 -0
  239. package/ez-agents/bin/lib/perf/frontend-performance.cjs +56 -0
  240. package/ez-agents/bin/lib/perf/perf-analyzer.cjs +77 -0
  241. package/ez-agents/bin/lib/perf/perf-baseline.cjs +102 -0
  242. package/ez-agents/bin/lib/perf/perf-reporter.cjs +117 -0
  243. package/ez-agents/bin/lib/perf/regression-detector.cjs +92 -0
  244. package/ez-agents/bin/lib/project-reporter.cjs +502 -0
  245. package/ez-agents/bin/lib/quality-gate.cjs +332 -0
  246. package/ez-agents/bin/lib/recovery-manager.cjs +98 -0
  247. package/ez-agents/bin/lib/release-validator.cjs +617 -0
  248. package/ez-agents/bin/lib/safe-exec.cjs +128 -214
  249. package/ez-agents/bin/lib/security-errors.cjs +62 -0
  250. package/ez-agents/bin/lib/session-chain.cjs +304 -0
  251. package/ez-agents/bin/lib/session-errors.cjs +81 -0
  252. package/ez-agents/bin/lib/session-export.cjs +251 -0
  253. package/ez-agents/bin/lib/session-import.cjs +262 -0
  254. package/ez-agents/bin/lib/session-manager.cjs +280 -0
  255. package/ez-agents/bin/lib/skill-context.cjs +148 -0
  256. package/ez-agents/bin/lib/skill-matcher.cjs +236 -0
  257. package/ez-agents/bin/lib/skill-registry.cjs +341 -0
  258. package/ez-agents/bin/lib/skill-resolver.cjs +449 -0
  259. package/ez-agents/bin/lib/skill-triggers.cjs +90 -0
  260. package/ez-agents/bin/lib/skill-validator.cjs +270 -0
  261. package/ez-agents/bin/lib/skill-versioning.cjs +355 -0
  262. package/ez-agents/bin/lib/stack-detector.cjs +399 -0
  263. package/ez-agents/bin/lib/tech-debt-analyzer.cjs +309 -0
  264. package/ez-agents/bin/lib/tier-manager.cjs +428 -0
  265. package/ez-agents/bin/lib/tradeoff-analyzer.cjs +284 -0
  266. package/ez-agents/bin/lib/url-fetch.cjs +170 -0
  267. package/ez-agents/bin/lib/verify.cjs +863 -863
  268. package/ez-agents/references/decimal-phase-calculation.md +65 -65
  269. package/ez-agents/references/git-integration.md +248 -248
  270. package/ez-agents/references/git-planning-commit.md +38 -38
  271. package/ez-agents/references/metrics-schema.md +118 -0
  272. package/ez-agents/references/model-profile-resolution.md +34 -34
  273. package/ez-agents/references/model-profiles.md +93 -93
  274. package/ez-agents/references/phase-argument-parsing.md +61 -61
  275. package/ez-agents/references/planning-config.md +340 -200
  276. package/ez-agents/references/tier-strategy.md +103 -0
  277. package/ez-agents/references/ui-brand.md +160 -160
  278. package/ez-agents/references/verification-patterns.md +612 -612
  279. package/ez-agents/templates/DEBUG.md +164 -164
  280. package/ez-agents/templates/UAT.md +247 -247
  281. package/ez-agents/templates/agent-output-format.md +404 -0
  282. package/ez-agents/templates/bdd-feature.md +173 -0
  283. package/ez-agents/templates/codebase/architecture.md +255 -255
  284. package/ez-agents/templates/codebase/structure.md +285 -285
  285. package/ez-agents/templates/copilot-instructions.md +7 -7
  286. package/ez-agents/templates/debug-subagent-prompt.md +91 -91
  287. package/ez-agents/templates/discovery.md +146 -146
  288. package/ez-agents/templates/discussion.md +68 -0
  289. package/ez-agents/templates/handoff-protocol.md +294 -0
  290. package/ez-agents/templates/incident-runbook.md +205 -0
  291. package/ez-agents/templates/mode-workflow-templates.md +301 -0
  292. package/ez-agents/templates/phase-prompt.md +610 -610
  293. package/ez-agents/templates/planner-subagent-prompt.md +117 -117
  294. package/ez-agents/templates/project.md +184 -184
  295. package/ez-agents/templates/release-checklist.md +136 -0
  296. package/ez-agents/templates/research.md +552 -552
  297. package/ez-agents/templates/rollback-plan.md +201 -0
  298. package/ez-agents/templates/security-user-setup.md +244 -0
  299. package/ez-agents/templates/skill-validation-rules.md +476 -0
  300. package/ez-agents/templates/state.md +180 -176
  301. package/ez-agents/templates/summary-complex.md +59 -59
  302. package/ez-agents/tests/gates/gate-01-02.test.cjs +812 -0
  303. package/ez-agents/tests/gates/gate-03-04.test.cjs +762 -0
  304. package/ez-agents/tests/gates/gate-05-validator.test.cjs +145 -0
  305. package/ez-agents/tests/gates/gate-06-docs-validator.test.cjs +244 -0
  306. package/ez-agents/tests/gates/gate-07-release-validator.test.cjs +219 -0
  307. package/ez-agents/tests/guards/context-budget-guard.test.cjs +145 -0
  308. package/ez-agents/tests/guards/edge-case-guards.test.cjs +238 -0
  309. package/ez-agents/tests/guards/hallucination-guard.test.cjs +124 -0
  310. package/ez-agents/workflows/audit-milestone.md +1 -1
  311. package/ez-agents/workflows/autonomous.md +131 -30
  312. package/ez-agents/workflows/complete-milestone.md +1 -1
  313. package/ez-agents/workflows/discuss-phase.md +1 -1
  314. package/ez-agents/workflows/execute-phase.md +169 -3
  315. package/ez-agents/workflows/help.md +86 -133
  316. package/ez-agents/workflows/hotfix.md +291 -0
  317. package/ez-agents/workflows/new-milestone.md +340 -11
  318. package/ez-agents/workflows/new-project.md +294 -318
  319. package/ez-agents/workflows/plan-phase.md +22 -40
  320. package/ez-agents/workflows/progress.md +15 -25
  321. package/ez-agents/workflows/release.md +253 -0
  322. package/ez-agents/workflows/resume-session.md +215 -0
  323. package/ez-agents/workflows/run-phase.md +531 -0
  324. package/ez-agents/workflows/settings.md +2 -35
  325. package/hooks/dist/ez-check-update.js +81 -81
  326. package/hooks/dist/ez-context-monitor.js +148 -141
  327. package/hooks/dist/ez-statusline.js +115 -115
  328. package/package.json +78 -64
  329. package/scripts/fix-qwen-installation.js +144 -144
  330. package/agents/ez-integration-checker.md +0 -443
  331. package/agents/ez-nyquist-auditor.md +0 -176
  332. package/agents/ez-plan-checker.md +0 -706
  333. package/agents/ez-research-synthesizer.md +0 -247
  334. package/agents/ez-ui-auditor.md +0 -439
  335. package/agents/ez-ui-checker.md +0 -300
  336. package/agents/ez-ui-researcher.md +0 -353
  337. package/commands/ez/add-phase.md +0 -43
  338. package/commands/ez/add-todo.md +0 -47
  339. package/commands/ez/auth.md +0 -87
  340. package/commands/ez/autonomous.md +0 -41
  341. package/commands/ez/check-todos.md +0 -45
  342. package/commands/ez/cleanup.md +0 -18
  343. package/commands/ez/debug.md +0 -168
  344. package/commands/ez/health.md +0 -22
  345. package/commands/ez/insert-phase.md +0 -32
  346. package/commands/ez/join-discord.md +0 -18
  347. package/commands/ez/list-phase-assumptions.md +0 -46
  348. package/commands/ez/pause-work.md +0 -38
  349. package/commands/ez/plan-milestone-gaps.md +0 -34
  350. package/commands/ez/reapply-patches.md +0 -124
  351. package/commands/ez/remove-phase.md +0 -31
  352. package/commands/ez/research-phase.md +0 -190
  353. package/commands/ez/set-profile.md +0 -34
  354. package/commands/ez/stats.md +0 -18
  355. package/commands/ez/ui-phase.md +0 -34
  356. package/commands/ez/ui-review.md +0 -32
  357. package/commands/ez/validate-phase.md +0 -35
  358. package/ez-agents/templates/UI-SPEC.md +0 -100
  359. package/ez-agents/templates/VALIDATION.md +0 -76
  360. package/ez-agents/templates/context.md +0 -352
  361. package/ez-agents/templates/verification-report.md +0 -322
  362. package/ez-agents/workflows/research-phase.md +0 -74
  363. package/ez-agents/workflows/ui-phase.md +0 -290
  364. package/ez-agents/workflows/ui-review.md +0 -157
  365. package/ez-agents/workflows/validate-phase.md +0 -167
@@ -0,0 +1,863 @@
1
+ /**
2
+ * Verify — Verification suite, consistency, and health validation
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const { safeReadFile, normalizePhaseName, execGit, findPhaseInternal, getMilestoneInfo, output, error } = require('./core.cjs');
8
+ const { extractFrontmatter, parseMustHavesBlock } = require('./frontmatter.cjs');
9
+ const { writeStateMd } = require('./state.cjs');
10
+ const { defaultLogger: logger } = require('./logger.cjs');
11
+
12
+ async function cmdVerifySummary(cwd, summaryPath, checkFileCount, raw) {
13
+ if (!summaryPath) {
14
+ error('summary-path required');
15
+ }
16
+
17
+ const fullPath = path.join(cwd, summaryPath);
18
+ const checkCount = checkFileCount || 2;
19
+
20
+ // Check 1: Summary exists
21
+ if (!fs.existsSync(fullPath)) {
22
+ const result = {
23
+ passed: false,
24
+ checks: {
25
+ summary_exists: false,
26
+ files_created: { checked: 0, found: 0, missing: [] },
27
+ commits_exist: false,
28
+ self_check: 'not_found',
29
+ },
30
+ errors: ['SUMMARY.md not found'],
31
+ };
32
+ output(result, raw, 'failed');
33
+ return;
34
+ }
35
+
36
+ const content = fs.readFileSync(fullPath, 'utf-8');
37
+ const errors = [];
38
+
39
+ // Check 2: Spot-check files mentioned in summary
40
+ const mentionedFiles = new Set();
41
+ const patterns = [
42
+ /`([^`]+\.[a-zA-Z]+)`/g,
43
+ /(?:Created|Modified|Added|Updated|Edited):\s*`?([^\s`]+\.[a-zA-Z]+)`?/gi,
44
+ ];
45
+
46
+ for (const pattern of patterns) {
47
+ let m;
48
+ while ((m = pattern.exec(content)) !== null) {
49
+ const filePath = m[1];
50
+ if (filePath && !filePath.startsWith('http') && filePath.includes('/')) {
51
+ mentionedFiles.add(filePath);
52
+ }
53
+ }
54
+ }
55
+
56
+ const filesToCheck = Array.from(mentionedFiles).slice(0, checkCount);
57
+ const missing = [];
58
+ for (const file of filesToCheck) {
59
+ if (!fs.existsSync(path.join(cwd, file))) {
60
+ missing.push(file);
61
+ }
62
+ }
63
+
64
+ // Check 3: Commits exist
65
+ const commitHashPattern = /\b[0-9a-f]{7,40}\b/g;
66
+ const hashes = content.match(commitHashPattern) || [];
67
+ let commitsExist = false;
68
+ if (hashes.length > 0) {
69
+ for (const hash of hashes.slice(0, 3)) {
70
+ const result = await execGit(cwd, ['cat-file', '-t', hash]);
71
+ if (result.exitCode === 0 && result.stdout === 'commit') {
72
+ commitsExist = true;
73
+ break;
74
+ }
75
+ }
76
+ }
77
+
78
+ // Check 4: Self-check section
79
+ let selfCheck = 'not_found';
80
+ const selfCheckPattern = /##\s*(?:Self[- ]?Check|Verification|Quality Check)/i;
81
+ if (selfCheckPattern.test(content)) {
82
+ const passPattern = /(?:all\s+)?(?:pass|✓|✅|complete|succeeded)/i;
83
+ const failPattern = /(?:fail|✗|❌|incomplete|blocked)/i;
84
+ const checkSection = content.slice(content.search(selfCheckPattern));
85
+ if (failPattern.test(checkSection)) {
86
+ selfCheck = 'failed';
87
+ } else if (passPattern.test(checkSection)) {
88
+ selfCheck = 'passed';
89
+ }
90
+ }
91
+
92
+ if (missing.length > 0) errors.push('Missing files: ' + missing.join(', '));
93
+ if (!commitsExist && hashes.length > 0) errors.push('Referenced commit hashes not found in git history');
94
+ if (selfCheck === 'failed') errors.push('Self-check section indicates failure');
95
+
96
+ const checks = {
97
+ summary_exists: true,
98
+ files_created: { checked: filesToCheck.length, found: filesToCheck.length - missing.length, missing },
99
+ commits_exist: commitsExist,
100
+ self_check: selfCheck,
101
+ };
102
+
103
+ const passed = missing.length === 0 && selfCheck !== 'failed';
104
+ const result = { passed, checks, errors };
105
+ output(result, raw, passed ? 'passed' : 'failed');
106
+ }
107
+
108
+ function cmdVerifyPlanStructure(cwd, filePath, raw) {
109
+ if (!filePath) { error('file path required'); }
110
+ const fullPath = path.isAbsolute(filePath) ? filePath : path.join(cwd, filePath);
111
+ const content = safeReadFile(fullPath);
112
+ if (!content) { output({ error: 'File not found', path: filePath }, raw); return; }
113
+
114
+ const fm = extractFrontmatter(content);
115
+ const errors = [];
116
+ const warnings = [];
117
+
118
+ // Check required frontmatter fields
119
+ const required = ['phase', 'plan', 'type', 'wave', 'depends_on', 'files_modified', 'autonomous', 'must_haves'];
120
+ for (const field of required) {
121
+ if (fm[field] === undefined) errors.push(`Missing required frontmatter field: ${field}`);
122
+ }
123
+
124
+ // Parse and check task elements
125
+ const taskPattern = /<task[^>]*>([\s\S]*?)<\/task>/g;
126
+ const tasks = [];
127
+ let taskMatch;
128
+ while ((taskMatch = taskPattern.exec(content)) !== null) {
129
+ const taskContent = taskMatch[1];
130
+ const nameMatch = taskContent.match(/<name>([\s\S]*?)<\/name>/);
131
+ const taskName = nameMatch ? nameMatch[1].trim() : 'unnamed';
132
+ const hasFiles = /<files>/.test(taskContent);
133
+ const hasAction = /<action>/.test(taskContent);
134
+ const hasVerify = /<verify>/.test(taskContent);
135
+ const hasDone = /<done>/.test(taskContent);
136
+
137
+ if (!nameMatch) errors.push('Task missing <name> element');
138
+ if (!hasAction) errors.push(`Task '${taskName}' missing <action>`);
139
+ if (!hasVerify) warnings.push(`Task '${taskName}' missing <verify>`);
140
+ if (!hasDone) warnings.push(`Task '${taskName}' missing <done>`);
141
+ if (!hasFiles) warnings.push(`Task '${taskName}' missing <files>`);
142
+
143
+ tasks.push({ name: taskName, hasFiles, hasAction, hasVerify, hasDone });
144
+ }
145
+
146
+ if (tasks.length === 0) warnings.push('No <task> elements found');
147
+
148
+ // Wave/depends_on consistency
149
+ if (fm.wave && parseInt(fm.wave) > 1 && (!fm.depends_on || (Array.isArray(fm.depends_on) && fm.depends_on.length === 0))) {
150
+ warnings.push('Wave > 1 but depends_on is empty');
151
+ }
152
+
153
+ // Autonomous/checkpoint consistency
154
+ const hasCheckpoints = /<task\s+type=["']?checkpoint/.test(content);
155
+ if (hasCheckpoints && fm.autonomous !== 'false' && fm.autonomous !== false) {
156
+ errors.push('Has checkpoint tasks but autonomous is not false');
157
+ }
158
+
159
+ output({
160
+ valid: errors.length === 0,
161
+ errors,
162
+ warnings,
163
+ task_count: tasks.length,
164
+ tasks,
165
+ frontmatter_fields: Object.keys(fm),
166
+ }, raw, errors.length === 0 ? 'valid' : 'invalid');
167
+ }
168
+
169
+ function cmdVerifyPhaseCompleteness(cwd, phase, raw) {
170
+ if (!phase) { error('phase required'); }
171
+ const phaseInfo = findPhaseInternal(cwd, phase);
172
+ if (!phaseInfo || !phaseInfo.found) {
173
+ output({ error: 'Phase not found', phase }, raw);
174
+ return;
175
+ }
176
+
177
+ const errors = [];
178
+ const warnings = [];
179
+ const phaseDir = path.join(cwd, phaseInfo.directory);
180
+
181
+ // List plans and summaries
182
+ let files;
183
+ try {
184
+ files = fs.readdirSync(phaseDir);
185
+ } catch (err) {
186
+ logger.warn('Failed to read phase directory in cmdVerifyPhaseCompleteness', { phaseDir, error: err.message });
187
+ output({ error: 'Cannot read phase directory' }, raw);
188
+ return;
189
+ }
190
+
191
+ const plans = files.filter(f => f.match(/-PLAN\.md$/i));
192
+ const summaries = files.filter(f => f.match(/-SUMMARY\.md$/i));
193
+
194
+ // Extract plan IDs (everything before -PLAN.md)
195
+ const planIds = new Set(plans.map(p => p.replace(/-PLAN\.md$/i, '')));
196
+ const summaryIds = new Set(summaries.map(s => s.replace(/-SUMMARY\.md$/i, '')));
197
+
198
+ // Plans without summaries
199
+ const incompletePlans = [...planIds].filter(id => !summaryIds.has(id));
200
+ if (incompletePlans.length > 0) {
201
+ errors.push(`Plans without summaries: ${incompletePlans.join(', ')}`);
202
+ }
203
+
204
+ // Summaries without plans (orphans)
205
+ const orphanSummaries = [...summaryIds].filter(id => !planIds.has(id));
206
+ if (orphanSummaries.length > 0) {
207
+ warnings.push(`Summaries without plans: ${orphanSummaries.join(', ')}`);
208
+ }
209
+
210
+ output({
211
+ complete: errors.length === 0,
212
+ phase: phaseInfo.phase_number,
213
+ plan_count: plans.length,
214
+ summary_count: summaries.length,
215
+ incomplete_plans: incompletePlans,
216
+ orphan_summaries: orphanSummaries,
217
+ errors,
218
+ warnings,
219
+ }, raw, errors.length === 0 ? 'complete' : 'incomplete');
220
+ }
221
+
222
+ function cmdVerifyReferences(cwd, filePath, raw) {
223
+ if (!filePath) { error('file path required'); }
224
+ const fullPath = path.isAbsolute(filePath) ? filePath : path.join(cwd, filePath);
225
+ const content = safeReadFile(fullPath);
226
+ if (!content) { output({ error: 'File not found', path: filePath }, raw); return; }
227
+
228
+ const found = [];
229
+ const missing = [];
230
+
231
+ // Find @-references: @path/to/file (must contain / to be a file path)
232
+ const atRefs = content.match(/@([^\s\n,)]+\/[^\s\n,)]+)/g) || [];
233
+ for (const ref of atRefs) {
234
+ const cleanRef = ref.slice(1); // remove @
235
+ const resolved = cleanRef.startsWith('~/')
236
+ ? path.join(process.env.HOME || '', cleanRef.slice(2))
237
+ : path.join(cwd, cleanRef);
238
+ if (fs.existsSync(resolved)) {
239
+ found.push(cleanRef);
240
+ } else {
241
+ missing.push(cleanRef);
242
+ }
243
+ }
244
+
245
+ // Find backtick file paths that look like real paths (contain / and have extension)
246
+ const backtickRefs = content.match(/`([^`]+\/[^`]+\.[a-zA-Z]{1,10})`/g) || [];
247
+ for (const ref of backtickRefs) {
248
+ const cleanRef = ref.slice(1, -1); // remove backticks
249
+ if (cleanRef.startsWith('http') || cleanRef.includes('${') || cleanRef.includes('{{')) continue;
250
+ if (found.includes(cleanRef) || missing.includes(cleanRef)) continue; // dedup
251
+ const resolved = path.join(cwd, cleanRef);
252
+ if (fs.existsSync(resolved)) {
253
+ found.push(cleanRef);
254
+ } else {
255
+ missing.push(cleanRef);
256
+ }
257
+ }
258
+
259
+ output({
260
+ valid: missing.length === 0,
261
+ found: found.length,
262
+ missing,
263
+ total: found.length + missing.length,
264
+ }, raw, missing.length === 0 ? 'valid' : 'invalid');
265
+ }
266
+
267
+ async function cmdVerifyCommits(cwd, hashes, raw) {
268
+ if (!hashes || hashes.length === 0) { error('At least one commit hash required'); }
269
+
270
+ const valid = [];
271
+ const invalid = [];
272
+ for (const hash of hashes) {
273
+ // Use git cat-file -t which supports both short and full hashes
274
+ // First try with the hash as-is (works for both short and full)
275
+ const result = await execGit(cwd, ['cat-file', '-t', hash]);
276
+ if (result.exitCode === 0 && result.stdout.trim() === 'commit') {
277
+ valid.push(hash);
278
+ } else {
279
+ // If that fails, try to resolve to full hash first
280
+ const resolveResult = await execGit(cwd, ['rev-parse', hash]);
281
+ if (resolveResult.exitCode === 0) {
282
+ const fullHash = resolveResult.stdout.trim();
283
+ const result2 = await execGit(cwd, ['cat-file', '-t', fullHash]);
284
+ if (result2.exitCode === 0 && result2.stdout.trim() === 'commit') {
285
+ valid.push(hash);
286
+ } else {
287
+ invalid.push(hash);
288
+ }
289
+ } else {
290
+ invalid.push(hash);
291
+ }
292
+ }
293
+ }
294
+
295
+ output({
296
+ all_valid: invalid.length === 0,
297
+ valid,
298
+ invalid,
299
+ total: hashes.length,
300
+ }, raw, invalid.length === 0 ? 'valid' : 'invalid');
301
+ }
302
+
303
+ function cmdVerifyArtifacts(cwd, planFilePath, raw) {
304
+ if (!planFilePath) { error('plan file path required'); }
305
+ const fullPath = path.isAbsolute(planFilePath) ? planFilePath : path.join(cwd, planFilePath);
306
+ const content = safeReadFile(fullPath);
307
+ if (!content) { output({ error: 'File not found', path: planFilePath }, raw); return; }
308
+
309
+ const artifacts = parseMustHavesBlock(content, 'artifacts');
310
+ if (artifacts.length === 0) {
311
+ output({ error: 'No must_haves.artifacts found in frontmatter', path: planFilePath }, raw);
312
+ return;
313
+ }
314
+
315
+ const results = [];
316
+ for (const artifact of artifacts) {
317
+ if (typeof artifact === 'string') continue; // skip simple string items
318
+ const artPath = artifact.path;
319
+ if (!artPath) continue;
320
+
321
+ const artFullPath = path.join(cwd, artPath);
322
+ const exists = fs.existsSync(artFullPath);
323
+ const check = { path: artPath, exists, issues: [], passed: false };
324
+
325
+ if (exists) {
326
+ const fileContent = safeReadFile(artFullPath) || '';
327
+ const lineCount = fileContent.split('\n').length;
328
+
329
+ if (artifact.min_lines && lineCount < artifact.min_lines) {
330
+ check.issues.push(`Only ${lineCount} lines, need ${artifact.min_lines}`);
331
+ }
332
+ if (artifact.contains && !fileContent.includes(artifact.contains)) {
333
+ check.issues.push(`Missing pattern: ${artifact.contains}`);
334
+ }
335
+ if (artifact.exports) {
336
+ const exports = Array.isArray(artifact.exports) ? artifact.exports : [artifact.exports];
337
+ for (const exp of exports) {
338
+ if (!fileContent.includes(exp)) check.issues.push(`Missing export: ${exp}`);
339
+ }
340
+ }
341
+ check.passed = check.issues.length === 0;
342
+ } else {
343
+ check.issues.push('File not found');
344
+ }
345
+
346
+ results.push(check);
347
+ }
348
+
349
+ const passed = results.filter(r => r.passed).length;
350
+ output({
351
+ all_passed: passed === results.length,
352
+ passed,
353
+ total: results.length,
354
+ artifacts: results,
355
+ }, raw, passed === results.length ? 'valid' : 'invalid');
356
+ }
357
+
358
+ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
359
+ if (!planFilePath) { error('plan file path required'); }
360
+ const fullPath = path.isAbsolute(planFilePath) ? planFilePath : path.join(cwd, planFilePath);
361
+ const content = safeReadFile(fullPath);
362
+ if (!content) { output({ error: 'File not found', path: planFilePath }, raw); return; }
363
+
364
+ const keyLinks = parseMustHavesBlock(content, 'key_links');
365
+ if (keyLinks.length === 0) {
366
+ output({ error: 'No must_haves.key_links found in frontmatter', path: planFilePath }, raw);
367
+ return;
368
+ }
369
+
370
+ const results = [];
371
+ for (const link of keyLinks) {
372
+ if (typeof link === 'string') continue;
373
+ const check = { from: link.from, to: link.to, via: link.via || '', verified: false, detail: '' };
374
+
375
+ const sourceContent = safeReadFile(path.join(cwd, link.from || ''));
376
+ if (!sourceContent) {
377
+ check.detail = 'Source file not found';
378
+ } else if (link.pattern) {
379
+ try {
380
+ const regex = new RegExp(link.pattern);
381
+ if (regex.test(sourceContent)) {
382
+ check.verified = true;
383
+ check.detail = 'Pattern found in source';
384
+ } else {
385
+ const targetContent = safeReadFile(path.join(cwd, link.to || ''));
386
+ if (targetContent && regex.test(targetContent)) {
387
+ check.verified = true;
388
+ check.detail = 'Pattern found in target';
389
+ } else {
390
+ check.detail = `Pattern "${link.pattern}" not found in source or target`;
391
+ }
392
+ }
393
+ } catch (err) {
394
+ logger.warn('Invalid regex while verifying key links', { pattern: link.pattern, error: err.message });
395
+ check.detail = `Invalid regex pattern: ${link.pattern}`;
396
+ }
397
+ } else {
398
+ // No pattern: just check source references target
399
+ if (sourceContent.includes(link.to || '')) {
400
+ check.verified = true;
401
+ check.detail = 'Target referenced in source';
402
+ } else {
403
+ check.detail = 'Target not referenced in source';
404
+ }
405
+ }
406
+
407
+ results.push(check);
408
+ }
409
+
410
+ const verified = results.filter(r => r.verified).length;
411
+ output({
412
+ all_verified: verified === results.length,
413
+ verified,
414
+ total: results.length,
415
+ links: results,
416
+ }, raw, verified === results.length ? 'valid' : 'invalid');
417
+ }
418
+
419
+ function cmdValidateConsistency(cwd, raw) {
420
+ const roadmapPath = path.join(cwd, '.planning', 'ROADMAP.md');
421
+ const phasesDir = path.join(cwd, '.planning', 'phases');
422
+ const errors = [];
423
+ const warnings = [];
424
+
425
+ // Check for ROADMAP
426
+ if (!fs.existsSync(roadmapPath)) {
427
+ errors.push('ROADMAP.md not found');
428
+ output({ passed: false, errors, warnings }, raw, 'failed');
429
+ return;
430
+ }
431
+
432
+ const roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
433
+
434
+ // Extract phases from ROADMAP
435
+ const roadmapPhases = new Set();
436
+ const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:/gi;
437
+ let m;
438
+ while ((m = phasePattern.exec(roadmapContent)) !== null) {
439
+ roadmapPhases.add(m[1]);
440
+ }
441
+
442
+ // Get phases on disk
443
+ const diskPhases = new Set();
444
+ try {
445
+ const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
446
+ const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
447
+ for (const dir of dirs) {
448
+ const dm = dir.match(/^(\d+[A-Z]?(?:\.\d+)*)/i);
449
+ if (dm) diskPhases.add(dm[1]);
450
+ }
451
+ } catch (err) {
452
+ logger.warn('Failed to enumerate phase directories while validating consistency', { phasesDir, error: err.message });
453
+ }
454
+
455
+ // Check: phases in ROADMAP but not on disk
456
+ for (const p of roadmapPhases) {
457
+ if (!diskPhases.has(p) && !diskPhases.has(normalizePhaseName(p))) {
458
+ warnings.push(`Phase ${p} in ROADMAP.md but no directory on disk`);
459
+ }
460
+ }
461
+
462
+ // Check: phases on disk but not in ROADMAP
463
+ for (const p of diskPhases) {
464
+ const unpadded = String(parseInt(p, 10));
465
+ if (!roadmapPhases.has(p) && !roadmapPhases.has(unpadded)) {
466
+ warnings.push(`Phase ${p} exists on disk but not in ROADMAP.md`);
467
+ }
468
+ }
469
+
470
+ // Check: sequential phase numbers (integers only)
471
+ const integerPhases = [...diskPhases]
472
+ .filter(p => !p.includes('.'))
473
+ .map(p => parseInt(p, 10))
474
+ .sort((a, b) => a - b);
475
+
476
+ for (let i = 1; i < integerPhases.length; i++) {
477
+ if (integerPhases[i] !== integerPhases[i - 1] + 1) {
478
+ warnings.push(`Gap in phase numbering: ${integerPhases[i - 1]} → ${integerPhases[i]}`);
479
+ }
480
+ }
481
+
482
+ // Check: plan numbering within phases
483
+ try {
484
+ const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
485
+ const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort();
486
+
487
+ for (const dir of dirs) {
488
+ const phaseFiles = fs.readdirSync(path.join(phasesDir, dir));
489
+ const plans = phaseFiles.filter(f => f.endsWith('-PLAN.md')).sort();
490
+
491
+ // Extract plan numbers
492
+ const planNums = plans.map(p => {
493
+ const pm = p.match(/-(\d{2})-PLAN\.md$/);
494
+ return pm ? parseInt(pm[1], 10) : null;
495
+ }).filter(n => n !== null);
496
+
497
+ for (let i = 1; i < planNums.length; i++) {
498
+ if (planNums[i] !== planNums[i - 1] + 1) {
499
+ warnings.push(`Gap in plan numbering in ${dir}: plan ${planNums[i - 1]} → ${planNums[i]}`);
500
+ }
501
+ }
502
+
503
+ // Check: plans without summaries (completed plans)
504
+ const summaries = phaseFiles.filter(f => f.endsWith('-SUMMARY.md'));
505
+ const planIds = new Set(plans.map(p => p.replace('-PLAN.md', '')));
506
+ const summaryIds = new Set(summaries.map(s => s.replace('-SUMMARY.md', '')));
507
+
508
+ // Summary without matching plan is suspicious
509
+ for (const sid of summaryIds) {
510
+ if (!planIds.has(sid)) {
511
+ warnings.push(`Summary ${sid}-SUMMARY.md in ${dir} has no matching PLAN.md`);
512
+ }
513
+ }
514
+ }
515
+ } catch (err) {
516
+ logger.warn('Failed to validate plan numbering while validating consistency', { phasesDir, error: err.message });
517
+ }
518
+
519
+ // Check: frontmatter in plans has required fields
520
+ try {
521
+ const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
522
+ const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
523
+
524
+ for (const dir of dirs) {
525
+ const phaseFiles = fs.readdirSync(path.join(phasesDir, dir));
526
+ const plans = phaseFiles.filter(f => f.endsWith('-PLAN.md'));
527
+
528
+ for (const plan of plans) {
529
+ const content = fs.readFileSync(path.join(phasesDir, dir, plan), 'utf-8');
530
+ const fm = extractFrontmatter(content);
531
+
532
+ if (!fm.wave) {
533
+ warnings.push(`${dir}/${plan}: missing 'wave' in frontmatter`);
534
+ }
535
+ }
536
+ }
537
+ } catch (err) {
538
+ logger.warn('Failed to validate plan frontmatter while validating consistency', { phasesDir, error: err.message });
539
+ }
540
+
541
+ const passed = errors.length === 0;
542
+ output({ passed, errors, warnings, warning_count: warnings.length }, raw, passed ? 'passed' : 'failed');
543
+ }
544
+
545
+ function cmdValidateHealth(cwd, options, raw) {
546
+ const planningDir = path.join(cwd, '.planning');
547
+ const projectPath = path.join(planningDir, 'PROJECT.md');
548
+ const roadmapPath = path.join(planningDir, 'ROADMAP.md');
549
+ const statePath = path.join(planningDir, 'STATE.md');
550
+ const configPath = path.join(planningDir, 'config.json');
551
+ const phasesDir = path.join(planningDir, 'phases');
552
+
553
+ const errors = [];
554
+ const warnings = [];
555
+ const info = [];
556
+ const repairs = [];
557
+
558
+ // Helper to add issue
559
+ const addIssue = (severity, code, message, fix, repairable = false) => {
560
+ const issue = { code, message, fix, repairable };
561
+ if (severity === 'error') errors.push(issue);
562
+ else if (severity === 'warning') warnings.push(issue);
563
+ else info.push(issue);
564
+ };
565
+
566
+ // ─── Check 1: .planning/ exists ───────────────────────────────────────────
567
+ if (!fs.existsSync(planningDir)) {
568
+ addIssue('error', 'E001', '.planning/ directory not found', 'Run /ez:new-project to initialize');
569
+ output({
570
+ status: 'broken',
571
+ errors,
572
+ warnings,
573
+ info,
574
+ repairable_count: 0,
575
+ }, raw);
576
+ return;
577
+ }
578
+
579
+ // ─── Check 2: PROJECT.md exists and has required sections ─────────────────
580
+ if (!fs.existsSync(projectPath)) {
581
+ addIssue('error', 'E002', 'PROJECT.md not found', 'Run /ez:new-project to create');
582
+ } else {
583
+ const content = fs.readFileSync(projectPath, 'utf-8');
584
+ const requiredSections = ['## What This Is', '## Core Value', '## Requirements'];
585
+ for (const section of requiredSections) {
586
+ if (!content.includes(section)) {
587
+ addIssue('warning', 'W001', `PROJECT.md missing section: ${section}`, 'Add section manually');
588
+ }
589
+ }
590
+ }
591
+
592
+ // ─── Check 3: ROADMAP.md exists ───────────────────────────────────────────
593
+ if (!fs.existsSync(roadmapPath)) {
594
+ addIssue('error', 'E003', 'ROADMAP.md not found', 'Run /ez:new-milestone to create roadmap');
595
+ }
596
+
597
+ // ─── Check 4: STATE.md exists and references valid phases ─────────────────
598
+ if (!fs.existsSync(statePath)) {
599
+ addIssue('error', 'E004', 'STATE.md not found', 'Run /ez:health --repair to regenerate', true);
600
+ repairs.push('regenerateState');
601
+ } else {
602
+ const stateContent = fs.readFileSync(statePath, 'utf-8');
603
+ // Extract phase references from STATE.md
604
+ const phaseRefs = [...stateContent.matchAll(/[Pp]hase\s+(\d+(?:\.\d+)*)/g)].map(m => m[1]);
605
+ // Get disk phases
606
+ const diskPhases = new Set();
607
+ try {
608
+ const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
609
+ for (const e of entries) {
610
+ if (e.isDirectory()) {
611
+ const m = e.name.match(/^(\d+(?:\.\d+)*)/);
612
+ if (m) diskPhases.add(m[1]);
613
+ }
614
+ }
615
+ } catch (err) {
616
+ logger.warn('Failed to read phase directories while validating STATE references', { phasesDir, error: err.message });
617
+ }
618
+ // Check for invalid references
619
+ for (const ref of phaseRefs) {
620
+ const normalizedRef = String(parseInt(ref, 10)).padStart(2, '0');
621
+ if (!diskPhases.has(ref) && !diskPhases.has(normalizedRef) && !diskPhases.has(String(parseInt(ref, 10)))) {
622
+ // Only warn if phases dir has any content (not just an empty project)
623
+ if (diskPhases.size > 0) {
624
+ addIssue('warning', 'W002', `STATE.md references phase ${ref}, but only phases ${[...diskPhases].sort().join(', ')} exist`, 'Run /ez:health --repair to regenerate STATE.md', true);
625
+ if (!repairs.includes('regenerateState')) repairs.push('regenerateState');
626
+ }
627
+ }
628
+ }
629
+ }
630
+
631
+ // ─── Check 5: config.json valid JSON + valid schema ───────────────────────
632
+ if (!fs.existsSync(configPath)) {
633
+ addIssue('warning', 'W003', 'config.json not found', 'Run /ez:health --repair to create with defaults', true);
634
+ repairs.push('createConfig');
635
+ } else {
636
+ try {
637
+ const raw = fs.readFileSync(configPath, 'utf-8');
638
+ const parsed = JSON.parse(raw);
639
+ // Validate known fields
640
+ const validProfiles = ['quality', 'balanced', 'budget'];
641
+ if (parsed.model_profile && !validProfiles.includes(parsed.model_profile)) {
642
+ addIssue('warning', 'W004', `config.json: invalid model_profile "${parsed.model_profile}"`, `Valid values: ${validProfiles.join(', ')}`);
643
+ }
644
+ } catch (err) {
645
+ logger.warn('Failed to parse config.json in cmdValidateHealth', { configPath, error: err.message });
646
+ addIssue('error', 'E005', `config.json: JSON parse error - ${err.message}`, 'Run /ez:health --repair to reset to defaults', true);
647
+ repairs.push('resetConfig');
648
+ }
649
+ }
650
+
651
+ // ─── Check 5b: Nyquist validation key presence ──────────────────────────
652
+ if (fs.existsSync(configPath)) {
653
+ try {
654
+ const configRaw = fs.readFileSync(configPath, 'utf-8');
655
+ const configParsed = JSON.parse(configRaw);
656
+ if (configParsed.workflow && configParsed.workflow.nyquist_validation === undefined) {
657
+ addIssue('warning', 'W008', 'config.json: workflow.nyquist_validation absent (defaults to enabled but agents may skip)', 'Run /ez:health --repair to add key', true);
658
+ if (!repairs.includes('addNyquistKey')) repairs.push('addNyquistKey');
659
+ }
660
+ } catch (err) {
661
+ logger.warn('Failed to parse config for nyquist key check', { configPath, error: err.message });
662
+ }
663
+ }
664
+
665
+ // ─── Check 6: Phase directory naming (NN-name format) ─────────────────────
666
+ try {
667
+ const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
668
+ for (const e of entries) {
669
+ if (e.isDirectory() && !e.name.match(/^\d{2}(?:\.\d+)*-[\w-]+$/)) {
670
+ addIssue('warning', 'W005', `Phase directory "${e.name}" doesn't follow NN-name format`, 'Rename to match pattern (e.g., 01-setup)');
671
+ }
672
+ }
673
+ } catch (err) {
674
+ logger.warn('Failed to inspect phase directory naming in health validation', { phasesDir, error: err.message });
675
+ }
676
+
677
+ // ─── Check 7: Orphaned plans (PLAN without SUMMARY) ───────────────────────
678
+ try {
679
+ const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
680
+ for (const e of entries) {
681
+ if (!e.isDirectory()) continue;
682
+ const phaseFiles = fs.readdirSync(path.join(phasesDir, e.name));
683
+ const plans = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
684
+ const summaries = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
685
+ const summaryBases = new Set(summaries.map(s => s.replace('-SUMMARY.md', '').replace('SUMMARY.md', '')));
686
+
687
+ for (const plan of plans) {
688
+ const planBase = plan.replace('-PLAN.md', '').replace('PLAN.md', '');
689
+ if (!summaryBases.has(planBase)) {
690
+ addIssue('info', 'I001', `${e.name}/${plan} has no SUMMARY.md`, 'May be in progress');
691
+ }
692
+ }
693
+ }
694
+ } catch (err) {
695
+ logger.warn('Failed to inspect orphaned plans in health validation', { phasesDir, error: err.message });
696
+ }
697
+
698
+ // ─── Check 7b: Nyquist VALIDATION.md consistency ────────────────────────
699
+ try {
700
+ const phaseEntries = fs.readdirSync(phasesDir, { withFileTypes: true });
701
+ for (const e of phaseEntries) {
702
+ if (!e.isDirectory()) continue;
703
+ const phaseFiles = fs.readdirSync(path.join(phasesDir, e.name));
704
+ const hasResearch = phaseFiles.some(f => f.endsWith('-RESEARCH.md'));
705
+ const hasValidation = phaseFiles.some(f => f.endsWith('-VALIDATION.md'));
706
+ if (hasResearch && !hasValidation) {
707
+ const researchFile = phaseFiles.find(f => f.endsWith('-RESEARCH.md'));
708
+ const researchContent = fs.readFileSync(path.join(phasesDir, e.name, researchFile), 'utf-8');
709
+ if (researchContent.includes('## Validation Architecture')) {
710
+ addIssue('warning', 'W009', `Phase ${e.name}: has Validation Architecture in RESEARCH.md but no VALIDATION.md`, 'Re-run /ez-plan-phase with --research to regenerate');
711
+ }
712
+ }
713
+ }
714
+ } catch (err) {
715
+ logger.warn('Failed to inspect validation architecture consistency in health validation', { phasesDir, error: err.message });
716
+ }
717
+
718
+ // ─── Check 8: Run existing consistency checks ─────────────────────────────
719
+ // Inline subset of cmdValidateConsistency
720
+ if (fs.existsSync(roadmapPath)) {
721
+ const roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
722
+ const roadmapPhases = new Set();
723
+ const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:/gi;
724
+ let m;
725
+ while ((m = phasePattern.exec(roadmapContent)) !== null) {
726
+ roadmapPhases.add(m[1]);
727
+ }
728
+
729
+ const diskPhases = new Set();
730
+ try {
731
+ const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
732
+ for (const e of entries) {
733
+ if (e.isDirectory()) {
734
+ const dm = e.name.match(/^(\d+[A-Z]?(?:\.\d+)*)/i);
735
+ if (dm) diskPhases.add(dm[1]);
736
+ }
737
+ }
738
+ } catch (err) {
739
+ logger.warn('Failed to run roadmap/disk consistency checks in health validation', { roadmapPath, phasesDir, error: err.message });
740
+ }
741
+
742
+ // Phases in ROADMAP but not on disk
743
+ for (const p of roadmapPhases) {
744
+ const padded = String(parseInt(p, 10)).padStart(2, '0');
745
+ if (!diskPhases.has(p) && !diskPhases.has(padded)) {
746
+ addIssue('warning', 'W006', `Phase ${p} in ROADMAP.md but no directory on disk`, 'Create phase directory or remove from roadmap');
747
+ }
748
+ }
749
+
750
+ // Phases on disk but not in ROADMAP
751
+ for (const p of diskPhases) {
752
+ const unpadded = String(parseInt(p, 10));
753
+ if (!roadmapPhases.has(p) && !roadmapPhases.has(unpadded)) {
754
+ addIssue('warning', 'W007', `Phase ${p} exists on disk but not in ROADMAP.md`, 'Add to roadmap or remove directory');
755
+ }
756
+ }
757
+ }
758
+
759
+ // ─── Perform repairs if requested ─────────────────────────────────────────
760
+ const repairActions = [];
761
+ if (options.repair && repairs.length > 0) {
762
+ for (const repair of repairs) {
763
+ try {
764
+ switch (repair) {
765
+ case 'createConfig':
766
+ case 'resetConfig': {
767
+ const defaults = {
768
+ model_profile: 'balanced',
769
+ commit_docs: true,
770
+ search_gitignored: false,
771
+ branching_strategy: 'none',
772
+ research: true,
773
+ plan_checker: true,
774
+ verifier: true,
775
+ parallelization: true,
776
+ };
777
+ fs.writeFileSync(configPath, JSON.stringify(defaults, null, 2), 'utf-8');
778
+ repairActions.push({ action: repair, success: true, path: 'config.json' });
779
+ break;
780
+ }
781
+ case 'regenerateState': {
782
+ // Create timestamped backup before overwriting
783
+ if (fs.existsSync(statePath)) {
784
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
785
+ const backupPath = `${statePath}.bak-${timestamp}`;
786
+ fs.copyFileSync(statePath, backupPath);
787
+ repairActions.push({ action: 'backupState', success: true, path: backupPath });
788
+ }
789
+ // Generate minimal STATE.md from ROADMAP.md structure
790
+ const milestone = getMilestoneInfo(cwd);
791
+ let stateContent = `# Session State\n\n`;
792
+ stateContent += `## Project Reference\n\n`;
793
+ stateContent += `See: .planning/PROJECT.md\n\n`;
794
+ stateContent += `## Position\n\n`;
795
+ stateContent += `**Milestone:** ${milestone.version} ${milestone.name}\n`;
796
+ stateContent += `**Current phase:** (determining...)\n`;
797
+ stateContent += `**Status:** Resuming\n\n`;
798
+ stateContent += `## Session Log\n\n`;
799
+ stateContent += `- ${new Date().toISOString().split('T')[0]}: STATE.md regenerated by /ez:health --repair\n`;
800
+ writeStateMd(statePath, stateContent, cwd);
801
+ repairActions.push({ action: repair, success: true, path: 'STATE.md' });
802
+ break;
803
+ }
804
+ case 'addNyquistKey': {
805
+ if (fs.existsSync(configPath)) {
806
+ try {
807
+ const configRaw = fs.readFileSync(configPath, 'utf-8');
808
+ const configParsed = JSON.parse(configRaw);
809
+ if (!configParsed.workflow) configParsed.workflow = {};
810
+ if (configParsed.workflow.nyquist_validation === undefined) {
811
+ configParsed.workflow.nyquist_validation = true;
812
+ fs.writeFileSync(configPath, JSON.stringify(configParsed, null, 2), 'utf-8');
813
+ }
814
+ repairActions.push({ action: repair, success: true, path: 'config.json' });
815
+ } catch (err) {
816
+ logger.error('Failed to repair nyquist key', { error: err.message });
817
+ repairActions.push({ action: repair, success: false, error: err.message });
818
+ }
819
+ }
820
+ break;
821
+ }
822
+ }
823
+ } catch (err) {
824
+ logger.error('Failed to perform repair action', { action: repair, error: err.message });
825
+ repairActions.push({ action: repair, success: false, error: err.message });
826
+ }
827
+ }
828
+ }
829
+
830
+ // ─── Determine overall status ─────────────────────────────────────────────
831
+ let status;
832
+ if (errors.length > 0) {
833
+ status = 'broken';
834
+ } else if (warnings.length > 0) {
835
+ status = 'degraded';
836
+ } else {
837
+ status = 'healthy';
838
+ }
839
+
840
+ const repairableCount = errors.filter(e => e.repairable).length +
841
+ warnings.filter(w => w.repairable).length;
842
+
843
+ output({
844
+ status,
845
+ errors,
846
+ warnings,
847
+ info,
848
+ repairable_count: repairableCount,
849
+ repairs_performed: repairActions.length > 0 ? repairActions : undefined,
850
+ }, raw);
851
+ }
852
+
853
+ module.exports = {
854
+ cmdVerifySummary,
855
+ cmdVerifyPlanStructure,
856
+ cmdVerifyPhaseCompleteness,
857
+ cmdVerifyReferences,
858
+ cmdVerifyCommits,
859
+ cmdVerifyArtifacts,
860
+ cmdVerifyKeyLinks,
861
+ cmdValidateConsistency,
862
+ cmdValidateHealth,
863
+ };