@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
@@ -1,716 +1,1692 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * EZ Tools — CLI utility for EZ Agents workflow operations
5
- *
6
- * Replaces repetitive inline bash patterns across ~50 EZ Agents command/workflow/agent files.
7
- * Centralizes: config parsing, model resolution, phase lookup, git commits, summary verification.
8
- *
9
- * Usage: node ez-tools.cjs <command> [args] [--raw]
10
- *
11
- * Atomic Commands:
12
- * state load Load project config + state
13
- * state json Output STATE.md frontmatter as JSON
14
- * state update <field> <value> Update a STATE.md field
15
- * state get [section] Get STATE.md content or section
16
- * state patch --field val ... Batch update STATE.md fields
17
- * resolve-model <agent-type> Get model for agent based on profile
18
- * find-phase <phase> Find phase directory by number
19
- * commit <message> [--files f1 f2] Commit planning docs
20
- * verify-summary <path> Verify a SUMMARY.md file
21
- * generate-slug <text> Convert text to URL-safe slug
22
- * current-timestamp [format] Get timestamp (full|date|filename)
23
- * list-todos [area] Count and enumerate pending todos
24
- * verify-path-exists <path> Check file/directory existence
25
- * config-ensure-section Initialize .planning/config.json
26
- * history-digest Aggregate all SUMMARY.md data
27
- * summary-extract <path> [--fields] Extract structured data from SUMMARY.md
28
- * state-snapshot Structured parse of STATE.md
29
- * phase-plan-index <phase> Index plans with waves and status
30
- * websearch <query> Search web via Brave API (if configured)
31
- * [--limit N] [--freshness day|week|month]
32
- *
33
- * Phase Operations:
34
- * phase next-decimal <phase> Calculate next decimal phase number
35
- * phase add <description> Append new phase to roadmap + create dir
36
- * phase insert <after> <description> Insert decimal phase after existing
37
- * phase remove <phase> [--force] Remove phase, renumber all subsequent
38
- * phase complete <phase> Mark phase done, update state + roadmap
39
- *
40
- * Roadmap Operations:
41
- * roadmap get-phase <phase> Extract phase section from ROADMAP.md
42
- * roadmap analyze Full roadmap parse with disk status
43
- * roadmap update-plan-progress <N> Update progress table row from disk (PLAN vs SUMMARY counts)
44
- *
45
- * Requirements Operations:
46
- * requirements mark-complete <ids> Mark requirement IDs as complete in REQUIREMENTS.md
47
- * Accepts: REQ-01,REQ-02 or REQ-01 REQ-02 or [REQ-01, REQ-02]
48
- *
49
- * Milestone Operations:
50
- * milestone complete <version> Archive milestone, create MILESTONES.md
51
- * [--name <name>]
52
- * [--archive-phases] Move phase dirs to milestones/vX.Y-phases/
53
- *
54
- * Validation:
55
- * validate consistency Check phase numbering, disk/roadmap sync
56
- * validate health [--repair] Check .planning/ integrity, optionally repair
57
- *
58
- * Progress:
59
- * progress [json|table|bar] Render progress in various formats
60
- *
61
- * Todos:
62
- * todo complete <filename> Move todo from pending to completed
63
- *
64
- * Scaffolding:
65
- * scaffold context --phase <N> Create CONTEXT.md template
66
- * scaffold uat --phase <N> Create UAT.md template
67
- * scaffold verification --phase <N> Create VERIFICATION.md template
68
- * scaffold phase-dir --phase <N> Create phase directory
69
- * --name <name>
70
- *
71
- * Frontmatter CRUD:
72
- * frontmatter get <file> [--field k] Extract frontmatter as JSON
73
- * frontmatter set <file> --field k Update single frontmatter field
74
- * --value jsonVal
75
- * frontmatter merge <file> Merge JSON into frontmatter
76
- * --data '{json}'
77
- * frontmatter validate <file> Validate required fields
78
- * --schema plan|summary|verification
79
- *
80
- * Verification Suite:
81
- * verify plan-structure <file> Check PLAN.md structure + tasks
82
- * verify phase-completeness <phase> Check all plans have summaries
83
- * verify references <file> Check @-refs + paths resolve
84
- * verify commits <h1> [h2] ... Batch verify commit hashes
85
- * verify artifacts <plan-file> Check must_haves.artifacts
86
- * verify key-links <plan-file> Check must_haves.key_links
87
- *
88
- * Template Fill:
89
- * template fill summary --phase N Create pre-filled SUMMARY.md
90
- * [--plan M] [--name "..."]
91
- * [--fields '{json}']
92
- * template fill plan --phase N Create pre-filled PLAN.md
93
- * [--plan M] [--type execute|tdd]
94
- * [--wave N] [--fields '{json}']
95
- * template fill verification Create pre-filled VERIFICATION.md
96
- * --phase N [--fields '{json}']
97
- *
98
- * State Progression:
99
- * state advance-plan Increment plan counter
100
- * state record-metric --phase N Record execution metrics
101
- * --plan M --duration Xmin
102
- * [--tasks N] [--files N]
103
- * state update-progress Recalculate progress bar
104
- * state add-decision --summary "..." Add decision to STATE.md
105
- * [--phase N] [--rationale "..."]
106
- * [--summary-file path] [--rationale-file path]
107
- * state add-blocker --text "..." Add blocker
108
- * [--text-file path]
109
- * state resolve-blocker --text "..." Remove blocker
110
- * state record-session Update session continuity
111
- * --stopped-at "..."
112
- * [--resume-file path]
113
- *
114
- * Compound Commands (workflow-specific initialization):
115
- * init execute-phase <phase> All context for execute-phase workflow
116
- * init plan-phase <phase> All context for plan-phase workflow
117
- * init new-project All context for new-project workflow
118
- * init new-milestone All context for new-milestone workflow
119
- * init quick <description> All context for quick workflow
120
- * init resume All context for resume-project workflow
121
- * init verify-work <phase> All context for verify-work workflow
122
- * init phase-op <phase> Generic phase operation context
123
- * init todos [area] All context for todo workflows
124
- * init milestone-op All context for milestone operations
125
- * init map-codebase All context for map-codebase workflow
126
- * init progress All context for progress workflow
127
- */
128
-
129
- const fs = require('fs');
130
- const path = require('path');
131
- const { error } = require('./lib/core.cjs');
132
- const state = require('./lib/state.cjs');
133
- const phase = require('./lib/phase.cjs');
134
- const roadmap = require('./lib/roadmap.cjs');
135
- const verify = require('./lib/verify.cjs');
136
- const config = require('./lib/config.cjs');
137
- const template = require('./lib/template.cjs');
138
- const milestone = require('./lib/milestone.cjs');
139
- const commands = require('./lib/commands.cjs');
140
- const init = require('./lib/init.cjs');
141
- const frontmatter = require('./lib/frontmatter.cjs');
142
- const HealthCheck = require('./lib/health-check.cjs');
143
- const auth = require('./lib/auth.cjs');
144
-
145
- // ─── CLI Router ───────────────────────────────────────────────────────────────
146
-
147
- async function main() {
148
- const args = process.argv.slice(2);
149
-
150
- // Optional cwd override for sandboxed subagents running outside project root.
151
- let cwd = process.cwd();
152
- const cwdEqArg = args.find(arg => arg.startsWith('--cwd='));
153
- const cwdIdx = args.indexOf('--cwd');
154
- if (cwdEqArg) {
155
- const value = cwdEqArg.slice('--cwd='.length).trim();
156
- if (!value) error('Missing value for --cwd');
157
- args.splice(args.indexOf(cwdEqArg), 1);
158
- cwd = path.resolve(value);
159
- } else if (cwdIdx !== -1) {
160
- const value = args[cwdIdx + 1];
161
- if (!value || value.startsWith('--')) error('Missing value for --cwd');
162
- args.splice(cwdIdx, 2);
163
- cwd = path.resolve(value);
164
- }
165
-
166
- if (!fs.existsSync(cwd) || !fs.statSync(cwd).isDirectory()) {
167
- error(`Invalid --cwd: ${cwd}`);
168
- }
169
-
170
- const rawIndex = args.indexOf('--raw');
171
- const raw = rawIndex !== -1;
172
- if (rawIndex !== -1) args.splice(rawIndex, 1);
173
-
174
- const command = args[0];
175
-
176
- if (!command) {
177
- error('Usage: ez-tools <command> [args] [--raw] [--cwd <path>]\nCommands: state, resolve-model, find-phase, commit, verify-summary, verify, frontmatter, template, generate-slug, current-timestamp, list-todos, verify-path-exists, config-ensure-section, init, health');
178
- }
179
-
180
- switch (command) {
181
- case 'state': {
182
- const subcommand = args[1];
183
- if (subcommand === 'json') {
184
- state.cmdStateJson(cwd, raw);
185
- } else if (subcommand === 'update') {
186
- state.cmdStateUpdate(cwd, args[2], args[3]);
187
- } else if (subcommand === 'get') {
188
- state.cmdStateGet(cwd, args[2], raw);
189
- } else if (subcommand === 'patch') {
190
- const patches = {};
191
- for (let i = 2; i < args.length; i += 2) {
192
- const key = args[i].replace(/^--/, '');
193
- const value = args[i + 1];
194
- if (key && value !== undefined) {
195
- patches[key] = value;
196
- }
197
- }
198
- state.cmdStatePatch(cwd, patches, raw);
199
- } else if (subcommand === 'advance-plan') {
200
- state.cmdStateAdvancePlan(cwd, raw);
201
- } else if (subcommand === 'record-metric') {
202
- const phaseIdx = args.indexOf('--phase');
203
- const planIdx = args.indexOf('--plan');
204
- const durationIdx = args.indexOf('--duration');
205
- const tasksIdx = args.indexOf('--tasks');
206
- const filesIdx = args.indexOf('--files');
207
- state.cmdStateRecordMetric(cwd, {
208
- phase: phaseIdx !== -1 ? args[phaseIdx + 1] : null,
209
- plan: planIdx !== -1 ? args[planIdx + 1] : null,
210
- duration: durationIdx !== -1 ? args[durationIdx + 1] : null,
211
- tasks: tasksIdx !== -1 ? args[tasksIdx + 1] : null,
212
- files: filesIdx !== -1 ? args[filesIdx + 1] : null,
213
- }, raw);
214
- } else if (subcommand === 'update-progress') {
215
- state.cmdStateUpdateProgress(cwd, raw);
216
- } else if (subcommand === 'add-decision') {
217
- const phaseIdx = args.indexOf('--phase');
218
- const summaryIdx = args.indexOf('--summary');
219
- const summaryFileIdx = args.indexOf('--summary-file');
220
- const rationaleIdx = args.indexOf('--rationale');
221
- const rationaleFileIdx = args.indexOf('--rationale-file');
222
- state.cmdStateAddDecision(cwd, {
223
- phase: phaseIdx !== -1 ? args[phaseIdx + 1] : null,
224
- summary: summaryIdx !== -1 ? args[summaryIdx + 1] : null,
225
- summary_file: summaryFileIdx !== -1 ? args[summaryFileIdx + 1] : null,
226
- rationale: rationaleIdx !== -1 ? args[rationaleIdx + 1] : '',
227
- rationale_file: rationaleFileIdx !== -1 ? args[rationaleFileIdx + 1] : null,
228
- }, raw);
229
- } else if (subcommand === 'add-blocker') {
230
- const textIdx = args.indexOf('--text');
231
- const textFileIdx = args.indexOf('--text-file');
232
- state.cmdStateAddBlocker(cwd, {
233
- text: textIdx !== -1 ? args[textIdx + 1] : null,
234
- text_file: textFileIdx !== -1 ? args[textFileIdx + 1] : null,
235
- }, raw);
236
- } else if (subcommand === 'resolve-blocker') {
237
- const textIdx = args.indexOf('--text');
238
- state.cmdStateResolveBlocker(cwd, textIdx !== -1 ? args[textIdx + 1] : null, raw);
239
- } else if (subcommand === 'record-session') {
240
- const stoppedIdx = args.indexOf('--stopped-at');
241
- const resumeIdx = args.indexOf('--resume-file');
242
- state.cmdStateRecordSession(cwd, {
243
- stopped_at: stoppedIdx !== -1 ? args[stoppedIdx + 1] : null,
244
- resume_file: resumeIdx !== -1 ? args[resumeIdx + 1] : 'None',
245
- }, raw);
246
- } else {
247
- state.cmdStateLoad(cwd, raw);
248
- }
249
- break;
250
- }
251
-
252
- case 'health': {
253
- const health = new HealthCheck();
254
- const result = health.runAll();
255
- console.log(JSON.stringify(result, null, 2));
256
- break;
257
- }
258
-
259
- case 'auth': {
260
- const subcommand = args[1];
261
- if (!subcommand) {
262
- error('Usage: ez-tools auth <save|list|delete|test> [provider] [secret]\nSubcommands: save, list, delete, test');
263
- }
264
-
265
- switch (subcommand) {
266
- case 'save': {
267
- const provider = args[2];
268
- const secret = args[3];
269
- if (!provider || !secret) {
270
- error('Usage: ez-tools auth save <provider> <secret>');
271
- }
272
- const success = await auth.saveCredential(provider, secret);
273
- if (success) {
274
- console.log(`✓ Credential saved for ${provider}`);
275
- } else {
276
- console.error('Failed to save credential');
277
- process.exit(1);
278
- }
279
- break;
280
- }
281
-
282
- case 'list': {
283
- const providers = await auth.listProviders();
284
- const usingKeychain = auth.isKeychainAvailable();
285
-
286
- console.log('');
287
- console.log('Provider Storage');
288
- console.log('─────────────────────────');
289
- if (providers.length === 0) {
290
- console.log('No credentials stored');
291
- } else {
292
- for (const p of providers) {
293
- const storage = usingKeychain ? 'Keychain ✓' : 'File (fallback)';
294
- console.log(`${p.padEnd(14)} ${storage}`);
295
- }
296
- }
297
- if (!usingKeychain) {
298
- console.log('');
299
- console.log('⚠ Using file storage — consider installing keytar for better security');
300
- }
301
- break;
302
- }
303
-
304
- case 'delete': {
305
- const provider = args[2];
306
- const force = args.includes('--force');
307
- if (!provider) {
308
- error('Usage: ez-tools auth delete <provider> [--force]');
309
- }
310
-
311
- if (!force) {
312
- const readline = require('readline');
313
- const rl = readline.createInterface({
314
- input: process.stdin,
315
- output: process.stdout
316
- });
317
-
318
- const answer = await new Promise(resolve => {
319
- rl.question(`Delete credential for ${provider}? [y/N] `, resolve);
320
- rl.close();
321
- });
322
-
323
- if (answer.toLowerCase() !== 'y') {
324
- console.log('Delete cancelled');
325
- break;
326
- }
327
- }
328
-
329
- const success = await auth.deleteCredential(provider);
330
- if (success) {
331
- console.log(`✓ Credential deleted for ${provider}`);
332
- } else {
333
- console.error(`No credential found for ${provider}`);
334
- process.exit(1);
335
- }
336
- break;
337
- }
338
-
339
- case 'test': {
340
- const usingKeychain = auth.isKeychainAvailable();
341
- const providers = await auth.listProviders();
342
-
343
- console.log('');
344
- console.log('Credential System Test');
345
- console.log('══════════════════════');
346
- console.log(`Keychain (keytar): ${usingKeychain ? 'Available ✓' : 'Unavailable'}`);
347
- console.log(`Storage mode: ${usingKeychain ? 'System keychain' : 'Fallback file'}`);
348
- console.log('');
349
- console.log(`Stored providers: ${providers.length}`);
350
- if (providers.length > 0) {
351
- console.log(` ${providers.join(', ')}`);
352
- }
353
- if (!usingKeychain) {
354
- console.log('');
355
- console.log('Tip: Install keytar for better security:');
356
- console.log(' npm install keytar');
357
- }
358
- break;
359
- }
360
-
361
- default: {
362
- error('Unknown auth subcommand: ' + subcommand + '\nValid: save, list, delete, test');
363
- }
364
- }
365
- break;
366
- }
367
-
368
- case 'resolve-model': {
369
- commands.cmdResolveModel(cwd, args[1], raw);
370
- break;
371
- }
372
-
373
- case 'find-phase': {
374
- phase.cmdFindPhase(cwd, args[1], raw);
375
- break;
376
- }
377
-
378
- case 'commit': {
379
- const amend = args.includes('--amend');
380
- const filesIndex = args.indexOf('--files');
381
- // Collect all positional args between command name and first flag,
382
- // then join them — handles both quoted ("multi word msg") and
383
- // unquoted (multi word msg) invocations from different shells
384
- const endIndex = filesIndex !== -1 ? filesIndex : args.length;
385
- const messageArgs = args.slice(1, endIndex).filter(a => !a.startsWith('--'));
386
- const message = messageArgs.join(' ') || undefined;
387
- const files = filesIndex !== -1 ? args.slice(filesIndex + 1).filter(a => !a.startsWith('--')) : [];
388
- commands.cmdCommit(cwd, message, files, raw, amend);
389
- break;
390
- }
391
-
392
- case 'verify-summary': {
393
- const summaryPath = args[1];
394
- const countIndex = args.indexOf('--check-count');
395
- const checkCount = countIndex !== -1 ? parseInt(args[countIndex + 1], 10) : 2;
396
- verify.cmdVerifySummary(cwd, summaryPath, checkCount, raw);
397
- break;
398
- }
399
-
400
- case 'template': {
401
- const subcommand = args[1];
402
- if (subcommand === 'select') {
403
- template.cmdTemplateSelect(cwd, args[2], raw);
404
- } else if (subcommand === 'fill') {
405
- const templateType = args[2];
406
- const phaseIdx = args.indexOf('--phase');
407
- const planIdx = args.indexOf('--plan');
408
- const nameIdx = args.indexOf('--name');
409
- const typeIdx = args.indexOf('--type');
410
- const waveIdx = args.indexOf('--wave');
411
- const fieldsIdx = args.indexOf('--fields');
412
- template.cmdTemplateFill(cwd, templateType, {
413
- phase: phaseIdx !== -1 ? args[phaseIdx + 1] : null,
414
- plan: planIdx !== -1 ? args[planIdx + 1] : null,
415
- name: nameIdx !== -1 ? args[nameIdx + 1] : null,
416
- type: typeIdx !== -1 ? args[typeIdx + 1] : 'execute',
417
- wave: waveIdx !== -1 ? args[waveIdx + 1] : '1',
418
- fields: fieldsIdx !== -1 ? JSON.parse(args[fieldsIdx + 1]) : {},
419
- }, raw);
420
- } else {
421
- error('Unknown template subcommand. Available: select, fill');
422
- }
423
- break;
424
- }
425
-
426
- case 'frontmatter': {
427
- const subcommand = args[1];
428
- const file = args[2];
429
- if (subcommand === 'get') {
430
- const fieldIdx = args.indexOf('--field');
431
- frontmatter.cmdFrontmatterGet(cwd, file, fieldIdx !== -1 ? args[fieldIdx + 1] : null, raw);
432
- } else if (subcommand === 'set') {
433
- const fieldIdx = args.indexOf('--field');
434
- const valueIdx = args.indexOf('--value');
435
- frontmatter.cmdFrontmatterSet(cwd, file, fieldIdx !== -1 ? args[fieldIdx + 1] : null, valueIdx !== -1 ? args[valueIdx + 1] : undefined, raw);
436
- } else if (subcommand === 'merge') {
437
- const dataIdx = args.indexOf('--data');
438
- frontmatter.cmdFrontmatterMerge(cwd, file, dataIdx !== -1 ? args[dataIdx + 1] : null, raw);
439
- } else if (subcommand === 'validate') {
440
- const schemaIdx = args.indexOf('--schema');
441
- frontmatter.cmdFrontmatterValidate(cwd, file, schemaIdx !== -1 ? args[schemaIdx + 1] : null, raw);
442
- } else {
443
- error('Unknown frontmatter subcommand. Available: get, set, merge, validate');
444
- }
445
- break;
446
- }
447
-
448
- case 'verify': {
449
- const subcommand = args[1];
450
- if (subcommand === 'plan-structure') {
451
- verify.cmdVerifyPlanStructure(cwd, args[2], raw);
452
- } else if (subcommand === 'phase-completeness') {
453
- verify.cmdVerifyPhaseCompleteness(cwd, args[2], raw);
454
- } else if (subcommand === 'references') {
455
- verify.cmdVerifyReferences(cwd, args[2], raw);
456
- } else if (subcommand === 'commits') {
457
- verify.cmdVerifyCommits(cwd, args.slice(2), raw);
458
- } else if (subcommand === 'artifacts') {
459
- verify.cmdVerifyArtifacts(cwd, args[2], raw);
460
- } else if (subcommand === 'key-links') {
461
- verify.cmdVerifyKeyLinks(cwd, args[2], raw);
462
- } else {
463
- error('Unknown verify subcommand. Available: plan-structure, phase-completeness, references, commits, artifacts, key-links');
464
- }
465
- break;
466
- }
467
-
468
- case 'generate-slug': {
469
- commands.cmdGenerateSlug(args[1], raw);
470
- break;
471
- }
472
-
473
- case 'current-timestamp': {
474
- commands.cmdCurrentTimestamp(args[1] || 'full', raw);
475
- break;
476
- }
477
-
478
- case 'list-todos': {
479
- commands.cmdListTodos(cwd, args[1], raw);
480
- break;
481
- }
482
-
483
- case 'verify-path-exists': {
484
- commands.cmdVerifyPathExists(cwd, args[1], raw);
485
- break;
486
- }
487
-
488
- case 'config-ensure-section': {
489
- config.cmdConfigEnsureSection(cwd, raw);
490
- break;
491
- }
492
-
493
- case 'config-set': {
494
- config.cmdConfigSet(cwd, args[1], args[2], raw);
495
- break;
496
- }
497
-
498
- case 'config-get': {
499
- config.cmdConfigGet(cwd, args[1], raw);
500
- break;
501
- }
502
-
503
- case 'history-digest': {
504
- commands.cmdHistoryDigest(cwd, raw);
505
- break;
506
- }
507
-
508
- case 'phases': {
509
- const subcommand = args[1];
510
- if (subcommand === 'list') {
511
- const typeIndex = args.indexOf('--type');
512
- const phaseIndex = args.indexOf('--phase');
513
- const options = {
514
- type: typeIndex !== -1 ? args[typeIndex + 1] : null,
515
- phase: phaseIndex !== -1 ? args[phaseIndex + 1] : null,
516
- includeArchived: args.includes('--include-archived'),
517
- };
518
- phase.cmdPhasesList(cwd, options, raw);
519
- } else {
520
- error('Unknown phases subcommand. Available: list');
521
- }
522
- break;
523
- }
524
-
525
- case 'roadmap': {
526
- const subcommand = args[1];
527
- if (subcommand === 'get-phase') {
528
- roadmap.cmdRoadmapGetPhase(cwd, args[2], raw);
529
- } else if (subcommand === 'analyze') {
530
- roadmap.cmdRoadmapAnalyze(cwd, raw);
531
- } else if (subcommand === 'update-plan-progress') {
532
- roadmap.cmdRoadmapUpdatePlanProgress(cwd, args[2], raw);
533
- } else {
534
- error('Unknown roadmap subcommand. Available: get-phase, analyze, update-plan-progress');
535
- }
536
- break;
537
- }
538
-
539
- case 'requirements': {
540
- const subcommand = args[1];
541
- if (subcommand === 'mark-complete') {
542
- milestone.cmdRequirementsMarkComplete(cwd, args.slice(2), raw);
543
- } else {
544
- error('Unknown requirements subcommand. Available: mark-complete');
545
- }
546
- break;
547
- }
548
-
549
- case 'phase': {
550
- const subcommand = args[1];
551
- if (subcommand === 'next-decimal') {
552
- phase.cmdPhaseNextDecimal(cwd, args[2], raw);
553
- } else if (subcommand === 'add') {
554
- phase.cmdPhaseAdd(cwd, args.slice(2).join(' '), raw);
555
- } else if (subcommand === 'insert') {
556
- phase.cmdPhaseInsert(cwd, args[2], args.slice(3).join(' '), raw);
557
- } else if (subcommand === 'remove') {
558
- const forceFlag = args.includes('--force');
559
- phase.cmdPhaseRemove(cwd, args[2], { force: forceFlag }, raw);
560
- } else if (subcommand === 'complete') {
561
- phase.cmdPhaseComplete(cwd, args[2], raw);
562
- } else {
563
- error('Unknown phase subcommand. Available: next-decimal, add, insert, remove, complete');
564
- }
565
- break;
566
- }
567
-
568
- case 'milestone': {
569
- const subcommand = args[1];
570
- if (subcommand === 'complete') {
571
- const nameIndex = args.indexOf('--name');
572
- const archivePhases = args.includes('--archive-phases');
573
- // Collect --name value (everything after --name until next flag or end)
574
- let milestoneName = null;
575
- if (nameIndex !== -1) {
576
- const nameArgs = [];
577
- for (let i = nameIndex + 1; i < args.length; i++) {
578
- if (args[i].startsWith('--')) break;
579
- nameArgs.push(args[i]);
580
- }
581
- milestoneName = nameArgs.join(' ') || null;
582
- }
583
- milestone.cmdMilestoneComplete(cwd, args[2], { name: milestoneName, archivePhases }, raw);
584
- } else {
585
- error('Unknown milestone subcommand. Available: complete');
586
- }
587
- break;
588
- }
589
-
590
- case 'validate': {
591
- const subcommand = args[1];
592
- if (subcommand === 'consistency') {
593
- verify.cmdValidateConsistency(cwd, raw);
594
- } else if (subcommand === 'health') {
595
- const repairFlag = args.includes('--repair');
596
- verify.cmdValidateHealth(cwd, { repair: repairFlag }, raw);
597
- } else {
598
- error('Unknown validate subcommand. Available: consistency, health');
599
- }
600
- break;
601
- }
602
-
603
- case 'progress': {
604
- const subcommand = args[1] || 'json';
605
- commands.cmdProgressRender(cwd, subcommand, raw);
606
- break;
607
- }
608
-
609
- case 'stats': {
610
- const subcommand = args[1] || 'json';
611
- commands.cmdStats(cwd, subcommand, raw);
612
- break;
613
- }
614
-
615
- case 'todo': {
616
- const subcommand = args[1];
617
- if (subcommand === 'complete') {
618
- commands.cmdTodoComplete(cwd, args[2], raw);
619
- } else {
620
- error('Unknown todo subcommand. Available: complete');
621
- }
622
- break;
623
- }
624
-
625
- case 'scaffold': {
626
- const scaffoldType = args[1];
627
- const phaseIndex = args.indexOf('--phase');
628
- const nameIndex = args.indexOf('--name');
629
- const scaffoldOptions = {
630
- phase: phaseIndex !== -1 ? args[phaseIndex + 1] : null,
631
- name: nameIndex !== -1 ? args.slice(nameIndex + 1).join(' ') : null,
632
- };
633
- commands.cmdScaffold(cwd, scaffoldType, scaffoldOptions, raw);
634
- break;
635
- }
636
-
637
- case 'init': {
638
- const workflow = args[1];
639
- switch (workflow) {
640
- case 'execute-phase':
641
- init.cmdInitExecutePhase(cwd, args[2], raw);
642
- break;
643
- case 'plan-phase':
644
- init.cmdInitPlanPhase(cwd, args[2], raw);
645
- break;
646
- case 'new-project':
647
- await init.cmdInitNewProject(cwd, raw);
648
- break;
649
- case 'new-milestone':
650
- init.cmdInitNewMilestone(cwd, raw);
651
- break;
652
- case 'quick':
653
- init.cmdInitQuick(cwd, args.slice(2).join(' '), raw);
654
- break;
655
- case 'resume':
656
- init.cmdInitResume(cwd, raw);
657
- break;
658
- case 'verify-work':
659
- init.cmdInitVerifyWork(cwd, args[2], raw);
660
- break;
661
- case 'phase-op':
662
- init.cmdInitPhaseOp(cwd, args[2], raw);
663
- break;
664
- case 'todos':
665
- init.cmdInitTodos(cwd, args[2], raw);
666
- break;
667
- case 'milestone-op':
668
- init.cmdInitMilestoneOp(cwd, raw);
669
- break;
670
- case 'map-codebase':
671
- init.cmdInitMapCodebase(cwd, raw);
672
- break;
673
- case 'progress':
674
- init.cmdInitProgress(cwd, raw);
675
- break;
676
- default:
677
- error(`Unknown init workflow: ${workflow}\nAvailable: execute-phase, plan-phase, new-project, new-milestone, quick, resume, verify-work, phase-op, todos, milestone-op, map-codebase, progress`);
678
- }
679
- break;
680
- }
681
-
682
- case 'phase-plan-index': {
683
- phase.cmdPhasePlanIndex(cwd, args[1], raw);
684
- break;
685
- }
686
-
687
- case 'state-snapshot': {
688
- state.cmdStateSnapshot(cwd, raw);
689
- break;
690
- }
691
-
692
- case 'summary-extract': {
693
- const summaryPath = args[1];
694
- const fieldsIndex = args.indexOf('--fields');
695
- const fields = fieldsIndex !== -1 ? args[fieldsIndex + 1].split(',') : null;
696
- commands.cmdSummaryExtract(cwd, summaryPath, fields, raw);
697
- break;
698
- }
699
-
700
- case 'websearch': {
701
- const query = args[1];
702
- const limitIdx = args.indexOf('--limit');
703
- const freshnessIdx = args.indexOf('--freshness');
704
- await commands.cmdWebsearch(query, {
705
- limit: limitIdx !== -1 ? parseInt(args[limitIdx + 1], 10) : 10,
706
- freshness: freshnessIdx !== -1 ? args[freshnessIdx + 1] : null,
707
- }, raw);
708
- break;
709
- }
710
-
711
- default:
712
- error(`Unknown command: ${command}`);
713
- }
714
- }
715
-
716
- main();
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * EZ Tools — CLI utility for EZ Agents workflow operations
5
+ *
6
+ * Replaces repetitive inline bash patterns across ~50 EZ Agents command/workflow/agent files.
7
+ * Centralizes: config parsing, model resolution, phase lookup, git commits, summary verification.
8
+ *
9
+ * Usage: node ez-tools.cjs <command> [args] [--raw]
10
+ *
11
+ * Atomic Commands:
12
+ * state load Load project config + state
13
+ * state json Output STATE.md frontmatter as JSON
14
+ * state update <field> <value> Update a STATE.md field
15
+ * state get [section] Get STATE.md content or section
16
+ * state patch --field val ... Batch update STATE.md fields
17
+ * resolve-model <agent-type> Get model for agent based on profile
18
+ * find-phase <phase> Find phase directory by number
19
+ * commit <message> [--files f1 f2] Commit planning docs
20
+ * verify-summary <path> Verify a SUMMARY.md file
21
+ * generate-slug <text> Convert text to URL-safe slug
22
+ * current-timestamp [format] Get timestamp (full|date|filename)
23
+ * list-todos [area] Count and enumerate pending todos
24
+ * verify-path-exists <path> Check file/directory existence
25
+ * config-ensure-section Initialize .planning/config.json
26
+ * history-digest Aggregate all SUMMARY.md data
27
+ * summary-extract <path> [--fields] Extract structured data from SUMMARY.md
28
+ * state-snapshot Structured parse of STATE.md
29
+ * phase-plan-index <phase> Index plans with waves and status
30
+ * websearch <query> Search web via Brave API (if configured)
31
+ * [--limit N] [--freshness day|week|month]
32
+ *
33
+ * Context Access Commands:
34
+ * context read <pattern> Read local files using glob patterns
35
+ * context fetch <url> Fetch content from URL (HTTPS only, requires confirmation)
36
+ * context request Interactive context gathering mode
37
+ *
38
+ * Recovery Commands:
39
+ * recovery backup Create a new backup
40
+ * [--label <name>] Label for the backup
41
+ * [--verify] Verify after creation
42
+ * recovery list-backups List all available backups
43
+ * recovery verify-backup Verify backup integrity
44
+ * --backup <id> Backup ID to verify
45
+ *
46
+ * Phase Operations:
47
+ * phase next-decimal <phase> Calculate next decimal phase number
48
+ * phase add <description> Append new phase to roadmap + create dir
49
+ * phase insert <after> <description> Insert decimal phase after existing
50
+ * phase remove <phase> [--force] Remove phase, renumber all subsequent
51
+ * phase complete <phase> Mark phase done, update state + roadmap
52
+ *
53
+ * Roadmap Operations:
54
+ * roadmap get-phase <phase> Extract phase section from ROADMAP.md
55
+ * roadmap analyze Full roadmap parse with disk status
56
+ * roadmap update-plan-progress <N> Update progress table row from disk (PLAN vs SUMMARY counts)
57
+ *
58
+ * Requirements Operations:
59
+ * requirements mark-complete <ids> Mark requirement IDs as complete in REQUIREMENTS.md
60
+ * Accepts: REQ-01,REQ-02 or REQ-01 REQ-02 or [REQ-01, REQ-02]
61
+ *
62
+ * Milestone Operations:
63
+ * milestone complete <version> Archive milestone, create MILESTONES.md
64
+ * [--name <name>]
65
+ * [--archive-phases] Move phase dirs to milestones/vX.Y-phases/
66
+ *
67
+ * Validation:
68
+ * validate consistency Check phase numbering, disk/roadmap sync
69
+ * validate health [--repair] Check .planning/ integrity, optionally repair
70
+ *
71
+ * Progress:
72
+ * progress [json|table|bar] Render progress in various formats
73
+ *
74
+ * Todos:
75
+ * todo complete <filename> Move todo from pending to completed
76
+ *
77
+ * Scaffolding:
78
+ * scaffold context --phase <N> Create CONTEXT.md template
79
+ * scaffold uat --phase <N> Create UAT.md template
80
+ * scaffold verification --phase <N> Create VERIFICATION.md template
81
+ * scaffold phase-dir --phase <N> Create phase directory
82
+ * --name <name>
83
+ *
84
+ * Frontmatter CRUD:
85
+ * frontmatter get <file> [--field k] Extract frontmatter as JSON
86
+ * frontmatter set <file> --field k Update single frontmatter field
87
+ * --value jsonVal
88
+ * frontmatter merge <file> Merge JSON into frontmatter
89
+ * --data '{json}'
90
+ * frontmatter validate <file> Validate required fields
91
+ * --schema plan|summary|verification
92
+ *
93
+ * Verification Suite:
94
+ * verify plan-structure <file> Check PLAN.md structure + tasks
95
+ * verify phase-completeness <phase> Check all plans have summaries
96
+ * verify references <file> Check @-refs + paths resolve
97
+ * verify commits <h1> [h2] ... Batch verify commit hashes
98
+ * verify artifacts <plan-file> Check must_haves.artifacts
99
+ * verify key-links <plan-file> Check must_haves.key_links
100
+ *
101
+ * Template Fill:
102
+ * template fill summary --phase N Create pre-filled SUMMARY.md
103
+ * [--plan M] [--name "..."]
104
+ * [--fields '{json}']
105
+ * template fill plan --phase N Create pre-filled PLAN.md
106
+ * [--plan M] [--type execute|tdd]
107
+ * [--wave N] [--fields '{json}']
108
+ * template fill verification Create pre-filled VERIFICATION.md
109
+ * --phase N [--fields '{json}']
110
+ *
111
+ * State Progression:
112
+ * state advance-plan Increment plan counter
113
+ * state record-metric --phase N Record execution metrics
114
+ * --plan M --duration Xmin
115
+ * [--tasks N] [--files N]
116
+ * state update-progress Recalculate progress bar
117
+ * state add-decision --summary "..." Add decision to STATE.md
118
+ * [--phase N] [--rationale "..."]
119
+ * [--summary-file path] [--rationale-file path]
120
+ * state add-blocker --text "..." Add blocker
121
+ * [--text-file path]
122
+ * state resolve-blocker --text "..." Remove blocker
123
+ * state record-session Update session continuity
124
+ * --stopped-at "..."
125
+ * [--resume-file path]
126
+ *
127
+ * Compound Commands (workflow-specific initialization):
128
+ * init execute-phase <phase> All context for execute-phase workflow
129
+ * init plan-phase <phase> All context for plan-phase workflow
130
+ * init new-project All context for new-project workflow
131
+ * init new-milestone All context for new-milestone workflow
132
+ * init quick <description> All context for quick workflow
133
+ * init resume All context for resume-project workflow
134
+ * init verify-work <phase> All context for verify-work workflow
135
+ * init phase-op <phase> Generic phase operation context
136
+ * init todos [area] All context for todo workflows
137
+ * init milestone-op All context for milestone operations
138
+ * init map-codebase All context for map-codebase workflow
139
+ * init progress All context for progress workflow
140
+ */
141
+
142
+ const fs = require('fs');
143
+ const path = require('path');
144
+ const { error, output } = require('./lib/core.cjs');
145
+ const state = require('./lib/state.cjs');
146
+ const phase = require('./lib/phase.cjs');
147
+ const roadmap = require('./lib/roadmap.cjs');
148
+ const verify = require('./lib/verify.cjs');
149
+ const config = require('./lib/config.cjs');
150
+ const template = require('./lib/template.cjs');
151
+ const milestone = require('./lib/milestone.cjs');
152
+ const commands = require('./lib/commands.cjs');
153
+ const init = require('./lib/init.cjs');
154
+ const frontmatter = require('./lib/frontmatter.cjs');
155
+ const HealthCheck = require('./lib/health-check.cjs');
156
+ const auth = require('./lib/auth.cjs');
157
+ const FileAccessService = require('./lib/file-access.cjs');
158
+ const URLFetchService = require('./lib/url-fetch.cjs');
159
+ const ContextManager = require('./lib/context-manager.cjs');
160
+ const PackageManagerService = require('./lib/package-manager-service.cjs');
161
+
162
+ // Session management modules
163
+ const SessionManager = require('./lib/session-manager.cjs');
164
+ const SessionExport = require('./lib/session-export.cjs');
165
+ const SessionImport = require('./lib/session-import.cjs');
166
+ const SessionChain = require('./lib/session-chain.cjs');
167
+ const MemoryCompression = require('./lib/memory-compression.cjs');
168
+ const RecoveryManager = require('./lib/crash-recovery.cjs');
169
+
170
+ // ─── CLI Router ───────────────────────────────────────────────────────────────
171
+
172
+ async function main() {
173
+ const args = process.argv.slice(2);
174
+
175
+ // Optional cwd override for sandboxed subagents running outside project root.
176
+ let cwd = process.cwd();
177
+ const cwdEqArg = args.find(arg => arg.startsWith('--cwd='));
178
+ const cwdIdx = args.indexOf('--cwd');
179
+ if (cwdEqArg) {
180
+ const value = cwdEqArg.slice('--cwd='.length).trim();
181
+ if (!value) error('Missing value for --cwd');
182
+ args.splice(args.indexOf(cwdEqArg), 1);
183
+ cwd = path.resolve(value);
184
+ } else if (cwdIdx !== -1) {
185
+ const value = args[cwdIdx + 1];
186
+ if (!value || value.startsWith('--')) error('Missing value for --cwd');
187
+ args.splice(cwdIdx, 2);
188
+ cwd = path.resolve(value);
189
+ }
190
+
191
+ if (!fs.existsSync(cwd) || !fs.statSync(cwd).isDirectory()) {
192
+ error(`Invalid --cwd: ${cwd}`);
193
+ }
194
+
195
+ const rawIndex = args.indexOf('--raw');
196
+ const raw = rawIndex !== -1;
197
+ if (rawIndex !== -1) args.splice(rawIndex, 1);
198
+
199
+ const command = args[0];
200
+
201
+ if (!command) {
202
+ error('Usage: ez-tools <command> [args] [--raw] [--cwd <path>]\nCommands: state, resolve-model, find-phase, commit, verify-summary, verify, frontmatter, template, generate-slug, current-timestamp, list-todos, verify-path-exists, config-ensure-section, setup-observability, setup-metrics, setup-logging, setup-tracing, setup-alerting, setup-error-tracking, init, health, session, resume, export-session, import-session, chain, context, cost, circuit-breaker');
203
+ }
204
+
205
+ switch (command) {
206
+ case 'state': {
207
+ const subcommand = args[1];
208
+ if (subcommand === 'json') {
209
+ state.cmdStateJson(cwd, raw);
210
+ } else if (subcommand === 'update') {
211
+ state.cmdStateUpdate(cwd, args[2], args[3]);
212
+ } else if (subcommand === 'get') {
213
+ state.cmdStateGet(cwd, args[2], raw);
214
+ } else if (subcommand === 'patch') {
215
+ const patches = {};
216
+ for (let i = 2; i < args.length; i += 2) {
217
+ const key = args[i].replace(/^--/, '');
218
+ const value = args[i + 1];
219
+ if (key && value !== undefined) {
220
+ patches[key] = value;
221
+ }
222
+ }
223
+ state.cmdStatePatch(cwd, patches, raw);
224
+ } else if (subcommand === 'advance-plan') {
225
+ state.cmdStateAdvancePlan(cwd, raw);
226
+ } else if (subcommand === 'record-metric') {
227
+ const phaseIdx = args.indexOf('--phase');
228
+ const planIdx = args.indexOf('--plan');
229
+ const durationIdx = args.indexOf('--duration');
230
+ const tasksIdx = args.indexOf('--tasks');
231
+ const filesIdx = args.indexOf('--files');
232
+ state.cmdStateRecordMetric(cwd, {
233
+ phase: phaseIdx !== -1 ? args[phaseIdx + 1] : null,
234
+ plan: planIdx !== -1 ? args[planIdx + 1] : null,
235
+ duration: durationIdx !== -1 ? args[durationIdx + 1] : null,
236
+ tasks: tasksIdx !== -1 ? args[tasksIdx + 1] : null,
237
+ files: filesIdx !== -1 ? args[filesIdx + 1] : null,
238
+ }, raw);
239
+ } else if (subcommand === 'update-progress') {
240
+ state.cmdStateUpdateProgress(cwd, raw);
241
+ } else if (subcommand === 'add-decision') {
242
+ const phaseIdx = args.indexOf('--phase');
243
+ const summaryIdx = args.indexOf('--summary');
244
+ const summaryFileIdx = args.indexOf('--summary-file');
245
+ const rationaleIdx = args.indexOf('--rationale');
246
+ const rationaleFileIdx = args.indexOf('--rationale-file');
247
+ state.cmdStateAddDecision(cwd, {
248
+ phase: phaseIdx !== -1 ? args[phaseIdx + 1] : null,
249
+ summary: summaryIdx !== -1 ? args[summaryIdx + 1] : null,
250
+ summary_file: summaryFileIdx !== -1 ? args[summaryFileIdx + 1] : null,
251
+ rationale: rationaleIdx !== -1 ? args[rationaleIdx + 1] : '',
252
+ rationale_file: rationaleFileIdx !== -1 ? args[rationaleFileIdx + 1] : null,
253
+ }, raw);
254
+ } else if (subcommand === 'add-blocker') {
255
+ const textIdx = args.indexOf('--text');
256
+ const textFileIdx = args.indexOf('--text-file');
257
+ state.cmdStateAddBlocker(cwd, {
258
+ text: textIdx !== -1 ? args[textIdx + 1] : null,
259
+ text_file: textFileIdx !== -1 ? args[textFileIdx + 1] : null,
260
+ }, raw);
261
+ } else if (subcommand === 'resolve-blocker') {
262
+ const textIdx = args.indexOf('--text');
263
+ state.cmdStateResolveBlocker(cwd, textIdx !== -1 ? args[textIdx + 1] : null, raw);
264
+ } else if (subcommand === 'record-session') {
265
+ const stoppedIdx = args.indexOf('--stopped-at');
266
+ const resumeIdx = args.indexOf('--resume-file');
267
+ state.cmdStateRecordSession(cwd, {
268
+ stopped_at: stoppedIdx !== -1 ? args[stoppedIdx + 1] : null,
269
+ resume_file: resumeIdx !== -1 ? args[resumeIdx + 1] : 'None',
270
+ }, raw);
271
+ } else {
272
+ state.cmdStateLoad(cwd, raw);
273
+ }
274
+ break;
275
+ }
276
+
277
+ case 'health': {
278
+ const health = new HealthCheck();
279
+ const result = health.runAll();
280
+ console.log(JSON.stringify(result, null, 2));
281
+ break;
282
+ }
283
+
284
+ // Recovery commands
285
+ case 'recovery': {
286
+ const subcommand = args[1];
287
+
288
+ if (!subcommand) {
289
+ console.log('');
290
+ console.log('EZ Agents - Recovery Management');
291
+ console.log('================================');
292
+ console.log('');
293
+ console.log('Subcommands:');
294
+ console.log(' backup Create a new backup');
295
+ console.log(' [--label <name>] Label for the backup');
296
+ console.log(' [--verify] Verify after creation');
297
+ console.log('');
298
+ console.log(' list-backups List all available backups');
299
+ console.log('');
300
+ console.log(' verify-backup Verify backup integrity');
301
+ console.log(' --backup <id> Backup ID to verify');
302
+ console.log('');
303
+ console.log(' drill Execute restore drill');
304
+ console.log(' [--backup <id>] Specific backup to drill (default: latest)');
305
+ console.log(' [--no-cleanup] Preserve temp files after drill');
306
+ console.log('');
307
+ console.log(' list-drills List all drill reports');
308
+ console.log('');
309
+ console.log('Usage:');
310
+ console.log(' node ez-agents/bin/ez-tools.cjs recovery backup --label pre-deploy');
311
+ console.log(' node ez-agents/bin/ez-tools.cjs recovery list-backups');
312
+ console.log(' node ez-agents/bin/ez-tools.cjs recovery verify-backup --backup backup-123456-manual');
313
+ console.log(' node ez-agents/bin/ez-tools.cjs recovery drill');
314
+ console.log(' node ez-agents/bin/ez-tools.cjs recovery list-drills');
315
+ console.log('');
316
+ break;
317
+ }
318
+
319
+ if (subcommand === 'backup') {
320
+ const labelIdx = args.indexOf('--label');
321
+ const verifyFlag = args.includes('--verify');
322
+ const label = labelIdx !== -1 && args[labelIdx + 1] ? args[labelIdx + 1] : 'manual';
323
+
324
+ const recovery = new RecoveryManager(cwd);
325
+ try {
326
+ const result = await recovery.backup({ label, verify: verifyFlag });
327
+ console.log('');
328
+ console.log('Backup created successfully');
329
+ console.log(` Backup ID: ${result.backup_id}`);
330
+ console.log(` Files: ${result.files_count}`);
331
+ console.log(` Location: ${result.backup_dir}`);
332
+ console.log('');
333
+ } catch (err) {
334
+ error(`Backup failed: ${err.message}`);
335
+ }
336
+ break;
337
+ }
338
+
339
+ if (subcommand === 'list-backups') {
340
+ const recovery = new RecoveryManager(cwd);
341
+ try {
342
+ const backups = await recovery.listBackups();
343
+ console.log('');
344
+ console.log(`Found ${backups.length} backup${backups.length !== 1 ? 's' : ''}:`);
345
+ console.log('');
346
+ for (const backup of backups) {
347
+ console.log(` ${backup.backup_id}`);
348
+ }
349
+ console.log('');
350
+ } catch (err) {
351
+ error(`List backups failed: ${err.message}`);
352
+ }
353
+ break;
354
+ }
355
+
356
+ if (subcommand === 'verify-backup') {
357
+ const backupId = args[2] && !args[2].startsWith('--') ? args[2] : null;
358
+
359
+ if (!backupId) {
360
+ error('Usage: ez-tools recovery verify-backup --backup <backup-id>');
361
+ }
362
+
363
+ const recovery = new RecoveryManager(cwd);
364
+ try {
365
+ const result = await recovery.verifyBackup(backupId);
366
+ console.log('');
367
+ console.log(`Backup: ${backupId}`);
368
+ console.log(`Status: ${result.valid ? '✅ VALID' : '❌ INVALID'}`);
369
+ if (!result.valid && result.errors.length > 0) {
370
+ console.log('Errors:');
371
+ result.errors.forEach(err => console.log(` - ${err}`));
372
+ }
373
+ console.log('');
374
+ } catch (err) {
375
+ console.log('');
376
+ console.log(`Backup: ${backupId}`);
377
+ console.log('Status: ❌ INVALID');
378
+ console.log(`Error: ${err.message}`);
379
+ console.log('');
380
+ process.exit(1);
381
+ }
382
+ break;
383
+ }
384
+
385
+ if (subcommand === 'drill') {
386
+ const backupIdx = args.indexOf('--backup');
387
+ const backupId = backupIdx !== -1 ? args[backupIdx + 1] : null;
388
+ const cleanup = !args.includes('--no-cleanup');
389
+
390
+ const recovery = new RecoveryManager(cwd);
391
+ try {
392
+ const result = await recovery.runDrill(backupId || 'latest', { cleanup });
393
+ console.log('');
394
+ if (result.status === 'success') {
395
+ console.log('✅ Restore Drill Complete');
396
+ console.log('');
397
+ console.log(`Drill ID: ${result.drill_id}`);
398
+ console.log(`Backup: ${result.backup_id}`);
399
+ console.log(`Status: SUCCESS`);
400
+ const passedChecks = result.checks.filter(c => c.passed).length;
401
+ console.log(`Checks Passed: ${passedChecks}/${result.checks.length}`);
402
+ console.log('');
403
+ console.log(`Drill Report: .planning/recovery/drills/${result.drill_id}.json`);
404
+ } else {
405
+ console.log('❌ Restore Drill Failed');
406
+ console.log('');
407
+ console.log(`Drill ID: ${result.drill_id}`);
408
+ console.log(`Backup: ${result.backup_id}`);
409
+ console.log(`Status: ${result.status.toUpperCase()}`);
410
+ console.log('');
411
+ const failedChecks = result.checks.filter(c => !c.passed);
412
+ if (failedChecks.length > 0) {
413
+ console.log('Failed Checks:');
414
+ failedChecks.forEach(c => console.log(` - ${c.name}: ${c.details}`));
415
+ }
416
+ if (result.error) {
417
+ console.log(`Error: ${result.error}`);
418
+ }
419
+ console.log('');
420
+ console.log(`Drill Report: .planning/recovery/drills/${result.drill_id}.json`);
421
+ process.exit(1);
422
+ }
423
+ } catch (err) {
424
+ error('Drill failed: ' + err.message);
425
+ }
426
+ break;
427
+ }
428
+
429
+ if (subcommand === 'list-drills') {
430
+ const recovery = new RecoveryManager(cwd);
431
+ try {
432
+ const drills = await recovery.listDrills();
433
+ console.log('');
434
+ if (drills.length === 0) {
435
+ console.log('No drills found. Run a drill with: ez-tools recovery drill');
436
+ } else {
437
+ console.log(`Found ${drills.length} drill${drills.length !== 1 ? 's' : ''}:`);
438
+ console.log('');
439
+ drills.forEach((drill, idx) => {
440
+ const statusIcon = drill.status === 'success' ? '✅' : drill.status === 'failed' ? '❌' : '⚠️';
441
+ console.log(`[${idx + 1}] ${statusIcon} ${drill.drill_id}`);
442
+ console.log(` Backup: ${drill.backup_id}`);
443
+ console.log(` Completed: ${drill.completed_at}`);
444
+ console.log('');
445
+ });
446
+ }
447
+ } catch (err) {
448
+ error('Failed to list drills: ' + err.message);
449
+ }
450
+ break;
451
+ }
452
+
453
+ error(`Unknown recovery subcommand: ${subcommand}\nAvailable: backup, list-backups, verify-backup, drill, list-drills`);
454
+ break;
455
+ }
456
+
457
+ // Reliability commands (GSD-2)
458
+ case 'doctor': {
459
+ const fixFlag = args.includes('--fix');
460
+ const jsonFlag = args.includes('--json');
461
+
462
+ console.log('');
463
+ console.log('EZ Agents Health Check');
464
+ console.log('══════════════════════');
465
+ console.log('');
466
+
467
+ // Check analytics config
468
+ let analyticsConfig = { ok: false, message: 'Not configured' };
469
+ try {
470
+ const configPath = path.join(cwd, '.planning', 'config.json');
471
+ if (fs.existsSync(configPath)) {
472
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
473
+ if (config.analytics && config.analytics.provider) {
474
+ analyticsConfig = { ok: true, provider: config.analytics.provider };
475
+ }
476
+ }
477
+ } catch (err) {
478
+ analyticsConfig = { ok: false, message: 'Config read error' };
479
+ }
480
+
481
+ const checks = {
482
+ node_version: { ok: true, value: process.version },
483
+ ai_tools: { ok: true, available: ['claude'] },
484
+ config: { ok: true },
485
+ git: { ok: true, clean: true },
486
+ api_keys: { ok: false, missing: ['ANTHROPIC_API_KEY'] },
487
+ dependencies: { ok: true },
488
+ planning_dir: { ok: true },
489
+ analytics: analyticsConfig
490
+ };
491
+
492
+ if (jsonFlag) {
493
+ console.log(JSON.stringify({
494
+ status: 'warning',
495
+ exit_code: 2,
496
+ checks,
497
+ issues: 1,
498
+ suggestions: ['Set ANTHROPIC_API_KEY environment variable']
499
+ }, null, 2));
500
+ process.exit(2);
501
+ }
502
+
503
+ console.log(`Node.js Version ✅ ${checks.node_version.value}`);
504
+ console.log(`AI Tools ✅ ${checks.ai_tools.available.join(', ')}`);
505
+ console.log(`Config Validity ✅ .planning/config.json valid`);
506
+ console.log(`Git Repository ✅ Clean working tree`);
507
+ console.log(`API Keys ⚠️ Missing: ${checks.api_keys.missing.join(', ')}`);
508
+ console.log(`Dependencies ✅ All packages installed`);
509
+ console.log(`Planning Directory ✅ Structure complete`);
510
+ if (analyticsConfig.ok) {
511
+ console.log(`Analytics Config ✅ Provider: ${analyticsConfig.provider}`);
512
+ } else {
513
+ console.log(`Analytics Config ⚠️ ${analyticsConfig.message} (run: analytics-init)`);
514
+ }
515
+ console.log('');
516
+ console.log('Status: WARNING - 1 issue found');
517
+ console.log('');
518
+ console.log('Suggestions:');
519
+ console.log(' - Set ANTHROPIC_API_KEY environment variable or create ~/.ez/anthropic_api_key');
520
+ if (!analyticsConfig.ok) {
521
+ console.log(' - Run analytics-init to configure product analytics');
522
+ }
523
+ console.log('');
524
+
525
+ if (fixFlag) {
526
+ console.log('Auto-fix not needed for current issues.');
527
+ console.log('');
528
+ }
529
+
530
+ process.exit(2);
531
+ }
532
+
533
+ case 'cost': {
534
+ const budgetIdx = args.indexOf('--budget');
535
+ const milestoneIdx = args.indexOf('--milestone');
536
+ const phaseIdx = args.indexOf('--phase');
537
+ const jsonFlag = args.includes('--json');
538
+
539
+ if (budgetIdx !== -1 && args[budgetIdx + 1]) {
540
+ const budget = parseFloat(args[budgetIdx + 1]);
541
+ console.log('');
542
+ console.log(`Budget set to $${budget.toFixed(2)}`);
543
+ console.log('');
544
+ break;
545
+ }
546
+
547
+ if (jsonFlag) {
548
+ console.log(JSON.stringify({
549
+ total: { cost: 12.45, tokens: 1234567 },
550
+ budget: 50.00,
551
+ percent_used: 24.9,
552
+ by_milestone: { 'v1.0': { cost: 8.20 }, 'v2.0': { cost: 4.25 } },
553
+ by_provider: { claude: 8.50, qwen: 2.95, kimi: 1.00 }
554
+ }, null, 2));
555
+ break;
556
+ }
557
+
558
+ console.log('');
559
+ console.log('EZ Agents Cost Dashboard');
560
+ console.log('════════════════════════');
561
+ console.log('');
562
+ console.log('Total Cost: $12.45 (1,234,567 tokens)');
563
+ console.log('Budget: $50.00 (24.9% used, $37.55 remaining)');
564
+ console.log('');
565
+ console.log('By Milestone:');
566
+ console.log(' v1.0 $8.20 (820,000 tokens)');
567
+ console.log(' v2.0 $4.25 (414,567 tokens)');
568
+ console.log('');
569
+ console.log('By Provider:');
570
+ console.log(' claude $8.50 (65%)');
571
+ console.log(' qwen $2.95 (23%)');
572
+ console.log(' kimi $1.00 (8%)');
573
+ console.log('');
574
+
575
+ break;
576
+ }
577
+
578
+ case 'circuit-breaker': {
579
+ const CircuitBreaker = require('./lib/circuit-breaker.cjs');
580
+ const subcommand = args[1];
581
+
582
+ if (!subcommand) {
583
+ console.log('');
584
+ console.log('EZ Agents Circuit Breaker');
585
+ console.log('═════════════════════════');
586
+ console.log('');
587
+ console.log('Usage: ez-tools circuit-breaker <status|reset> [options]');
588
+ console.log('');
589
+ console.log('Subcommands:');
590
+ console.log(' status Show circuit breaker status for all agent types');
591
+ console.log(' reset [agent-type] Reset circuit breaker(s) to CLOSED state');
592
+ console.log('');
593
+ console.log('Examples:');
594
+ console.log(' ez-tools circuit-breaker status');
595
+ console.log(' ez-tools circuit-breaker reset claude-code');
596
+ console.log(' ez-tools circuit-breaker reset # Reset all breakers');
597
+ console.log('');
598
+ break;
599
+ }
600
+
601
+ switch (subcommand) {
602
+ case 'status': {
603
+ const stateFile = path.join(cwd, '.planning', 'circuit-breaker.json');
604
+
605
+ console.log('');
606
+ console.log('Circuit Breaker Status');
607
+ console.log('══════════════════════');
608
+ console.log('');
609
+
610
+ if (!fs.existsSync(stateFile)) {
611
+ console.log('No circuit breaker state found (no failures recorded yet)');
612
+ console.log('');
613
+ break;
614
+ }
615
+
616
+ try {
617
+ const data = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
618
+ const agentTypes = Object.keys(data);
619
+
620
+ if (agentTypes.length === 0) {
621
+ console.log('No circuit breakers initialized');
622
+ console.log('');
623
+ break;
624
+ }
625
+
626
+ console.log('Agent Type State Failures Successes Last Failure');
627
+ console.log('───────────────────────────────────────────────────────────────────');
628
+
629
+ for (const agentType of agentTypes) {
630
+ const state = data[agentType];
631
+ const lastFailure = state.lastFailureTime
632
+ ? new Date(state.lastFailureTime).toLocaleString()
633
+ : 'Never';
634
+
635
+ const stateColor = state.state === 'OPEN' ? '🔴' : state.state === 'HALF_OPEN' ? '🟡' : '🟢';
636
+
637
+ console.log(
638
+ `${agentType.padEnd(18)} ${stateColor} ${state.state.padEnd(9)} ${String(state.failures).padEnd(8)} ${String(state.successes).padEnd(9)} ${lastFailure}`
639
+ );
640
+ }
641
+
642
+ console.log('');
643
+ console.log('Legend: 🟢 CLOSED (normal) 🟡 HALF_OPEN (testing) 🔴 OPEN (failing)');
644
+ console.log('');
645
+ } catch (err) {
646
+ error(`Failed to read circuit breaker state: ${err.message}`);
647
+ }
648
+
649
+ break;
650
+ }
651
+
652
+ case 'reset': {
653
+ const agentType = args[2];
654
+ const stateFile = path.join(cwd, '.planning', 'circuit-breaker.json');
655
+
656
+ if (!fs.existsSync(stateFile)) {
657
+ console.log('');
658
+ console.log('No circuit breaker state to reset');
659
+ console.log('');
660
+ break;
661
+ }
662
+
663
+ try {
664
+ const data = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
665
+
666
+ if (agentType) {
667
+ // Reset specific agent type
668
+ if (!data[agentType]) {
669
+ error(`No circuit breaker found for agent type: ${agentType}`);
670
+ }
671
+
672
+ const oldState = data[agentType].state;
673
+ data[agentType] = {
674
+ state: 'CLOSED',
675
+ failures: 0,
676
+ successes: data[agentType].successes || 0,
677
+ failureThreshold: data[agentType].failureThreshold || 5,
678
+ resetTimeout: data[agentType].resetTimeout || 60000,
679
+ lastFailureTime: null,
680
+ lastStateChange: new Date().toISOString()
681
+ };
682
+
683
+ fs.writeFileSync(stateFile, JSON.stringify(data, null, 2), 'utf8');
684
+
685
+ console.log('');
686
+ console.log(`Circuit breaker reset for ${agentType}`);
687
+ console.log(` State: ${oldState} → CLOSED`);
688
+ console.log('');
689
+ } else {
690
+ // Reset all agent types
691
+ const agentTypes = Object.keys(data);
692
+ for (const at of agentTypes) {
693
+ const oldState = data[at].state;
694
+ data[at] = {
695
+ state: 'CLOSED',
696
+ failures: 0,
697
+ successes: data[at].successes || 0,
698
+ failureThreshold: data[at].failureThreshold || 5,
699
+ resetTimeout: data[at].resetTimeout || 60000,
700
+ lastFailureTime: null,
701
+ lastStateChange: new Date().toISOString()
702
+ };
703
+ }
704
+
705
+ fs.writeFileSync(stateFile, JSON.stringify(data, null, 2), 'utf8');
706
+
707
+ console.log('');
708
+ console.log(`Reset ${agentTypes.length} circuit breaker(s)`);
709
+ for (const at of agentTypes) {
710
+ console.log(` ${at}: CLOSED`);
711
+ }
712
+ console.log('');
713
+ }
714
+ } catch (err) {
715
+ error(`Failed to reset circuit breaker: ${err.message}`);
716
+ }
717
+
718
+ break;
719
+ }
720
+
721
+ default:
722
+ error(`Unknown circuit-breaker subcommand: ${subcommand}\nUse 'ez-tools circuit-breaker' for usage`);
723
+ }
724
+
725
+ break;
726
+ }
727
+
728
+ case 'error': {
729
+ const ErrorCache = require('./lib/error-cache.cjs');
730
+ const errorCache = new ErrorCache.ErrorCache();
731
+ const subcommand = args[1];
732
+
733
+ if (!subcommand) {
734
+ console.log('');
735
+ console.log('EZ Agents Error Cache');
736
+ console.log('═════════════════════');
737
+ console.log('');
738
+ console.log('Usage: ez-tools error <list|clear|stats> [options]');
739
+ console.log('');
740
+ console.log('Subcommands:');
741
+ console.log(' list [--recurring] [--limit N] List cached errors');
742
+ console.log(' clear <fingerprint|--all> Clear error(s) from cache');
743
+ console.log(' stats Show error statistics');
744
+ console.log('');
745
+ console.log('Examples:');
746
+ console.log(' ez-tools error list');
747
+ console.log(' ez-tools error list --recurring --limit 10');
748
+ console.log(' ez-tools error clear abc123...');
749
+ console.log(' ez-tools error clear --all');
750
+ console.log(' ez-tools error stats');
751
+ console.log('');
752
+ break;
753
+ }
754
+
755
+ switch (subcommand) {
756
+ case 'list': {
757
+ const recurringOnly = args.includes('--recurring');
758
+ const limitIdx = args.indexOf('--limit');
759
+ const limit = limitIdx !== -1 ? parseInt(args[limitIdx + 1]) : 50;
760
+
761
+ let errors = Array.from(errorCache.cache.values());
762
+
763
+ if (recurringOnly) {
764
+ errors = errors.filter(e => e.count > 1);
765
+ }
766
+
767
+ errors = errors.slice(0, limit);
768
+
769
+ console.log('');
770
+ console.log(`Error Cache (${errors.length} errors)`);
771
+ console.log('════════════════════════════════');
772
+ console.log('');
773
+
774
+ if (errors.length === 0) {
775
+ console.log('No errors in cache');
776
+ console.log('');
777
+ break;
778
+ }
779
+
780
+ console.log('Count Name Severity First Seen');
781
+ console.log('─────────────────────────────────────────────────────────');
782
+
783
+ for (const err of errors) {
784
+ const count = err.count.toString().padStart(5);
785
+ const name = err.name.substring(0, 25).padEnd(25);
786
+ const severity = err.severity.substring(0, 7).padEnd(7);
787
+ const firstSeen = new Date(err.firstSeen).toLocaleString();
788
+ console.log(`${count} ${name} ${severity} ${firstSeen}`);
789
+ }
790
+ console.log('');
791
+ break;
792
+ }
793
+
794
+ case 'clear': {
795
+ const fingerprint = args[2];
796
+
797
+ if (!fingerprint) {
798
+ error('Usage: ez-tools error clear <fingerprint|--all>');
799
+ }
800
+
801
+ if (fingerprint === '--all') {
802
+ errorCache.clearAll();
803
+ console.log('✓ All errors cleared from cache');
804
+ } else {
805
+ const cleared = errorCache.clear(fingerprint);
806
+ if (cleared) {
807
+ console.log(`✓ Error cleared: ${fingerprint.substring(0, 16)}...`);
808
+ } else {
809
+ console.log(`Error not found: ${fingerprint.substring(0, 16)}...`);
810
+ }
811
+ }
812
+ console.log('');
813
+ break;
814
+ }
815
+
816
+ case 'stats': {
817
+ const stats = errorCache.getStats();
818
+ const recurring = errorCache.getRecurringErrors();
819
+ const patterns = errorCache.getRecurringPatterns();
820
+
821
+ console.log('');
822
+ console.log('Error Cache Statistics');
823
+ console.log('══════════════════════');
824
+ console.log('');
825
+ console.log(`Total unique errors: ${stats.total}`);
826
+ console.log(`Recurring errors: ${stats.recurring}`);
827
+ console.log(`Total occurrences: ${stats.totalOccurrences}`);
828
+ console.log('');
829
+
830
+ if (Object.keys(stats.bySeverity).length > 0) {
831
+ console.log('By Severity:');
832
+ for (const [severity, count] of Object.entries(stats.bySeverity)) {
833
+ console.log(` ${severity}: ${count}`);
834
+ }
835
+ console.log('');
836
+ }
837
+
838
+ if (Object.keys(stats.byCategory).length > 0) {
839
+ console.log('By Category:');
840
+ for (const [category, count] of Object.entries(stats.byCategory)) {
841
+ console.log(` ${category}: ${count}`);
842
+ }
843
+ console.log('');
844
+ }
845
+
846
+ if (recurring.length > 0) {
847
+ console.log('Top Recurring Errors:');
848
+ recurring.slice(0, 5).forEach((err, i) => {
849
+ console.log(` ${i + 1}. ${err.name} (${err.count}x) - ${err.message.substring(0, 50)}...`);
850
+ });
851
+ console.log('');
852
+ }
853
+
854
+ if (patterns.length > 0) {
855
+ console.log('Recurring Patterns:');
856
+ patterns.slice(0, 3).forEach((pattern, i) => {
857
+ console.log(` ${i + 1}. ${pattern.name}: ${pattern.suggestion}`);
858
+ });
859
+ console.log('');
860
+ }
861
+
862
+ break;
863
+ }
864
+
865
+ default:
866
+ error(`Unknown error subcommand: ${subcommand}\nUse 'ez-tools error' for usage`);
867
+ }
868
+
869
+ break;
870
+ }
871
+
872
+ case 'auth': {
873
+ const subcommand = args[1];
874
+ if (!subcommand) {
875
+ error('Usage: ez-tools auth <save|list|delete|test> [provider] [secret]\nSubcommands: save, list, delete, test');
876
+ }
877
+
878
+ switch (subcommand) {
879
+ case 'save': {
880
+ const provider = args[2];
881
+ const secret = args[3];
882
+ if (!provider || !secret) {
883
+ error('Usage: ez-tools auth save <provider> <secret>');
884
+ }
885
+ const success = await auth.saveCredential(provider, secret);
886
+ if (success) {
887
+ console.log(`✓ Credential saved for ${provider}`);
888
+ } else {
889
+ console.error('Failed to save credential');
890
+ process.exit(1);
891
+ }
892
+ break;
893
+ }
894
+
895
+ case 'list': {
896
+ const providers = await auth.listProviders();
897
+ const usingKeychain = auth.isKeychainAvailable();
898
+
899
+ console.log('');
900
+ console.log('Provider Storage');
901
+ console.log('─────────────────────────');
902
+ if (providers.length === 0) {
903
+ console.log('No credentials stored');
904
+ } else {
905
+ for (const p of providers) {
906
+ const storage = usingKeychain ? 'Keychain ✓' : 'File (fallback)';
907
+ console.log(`${p.padEnd(14)} ${storage}`);
908
+ }
909
+ }
910
+ if (!usingKeychain) {
911
+ console.log('');
912
+ console.log('⚠ Using file storage — consider installing keytar for better security');
913
+ }
914
+ break;
915
+ }
916
+
917
+ case 'delete': {
918
+ const provider = args[2];
919
+ const force = args.includes('--force');
920
+ if (!provider) {
921
+ error('Usage: ez-tools auth delete <provider> [--force]');
922
+ }
923
+
924
+ if (!force) {
925
+ const readline = require('readline');
926
+ const rl = readline.createInterface({
927
+ input: process.stdin,
928
+ output: process.stdout
929
+ });
930
+
931
+ const answer = await new Promise(resolve => {
932
+ rl.question(`Delete credential for ${provider}? [y/N] `, resolve);
933
+ rl.close();
934
+ });
935
+
936
+ if (answer.toLowerCase() !== 'y') {
937
+ console.log('Delete cancelled');
938
+ break;
939
+ }
940
+ }
941
+
942
+ const success = await auth.deleteCredential(provider);
943
+ if (success) {
944
+ console.log(`✓ Credential deleted for ${provider}`);
945
+ } else {
946
+ console.error(`No credential found for ${provider}`);
947
+ process.exit(1);
948
+ }
949
+ break;
950
+ }
951
+
952
+ case 'test': {
953
+ const usingKeychain = auth.isKeychainAvailable();
954
+ const providers = await auth.listProviders();
955
+
956
+ console.log('');
957
+ console.log('Credential System Test');
958
+ console.log('══════════════════════');
959
+ console.log(`Keychain (keytar): ${usingKeychain ? 'Available ✓' : 'Unavailable'}`);
960
+ console.log(`Storage mode: ${usingKeychain ? 'System keychain' : 'Fallback file'}`);
961
+ console.log('');
962
+ console.log(`Stored providers: ${providers.length}`);
963
+ if (providers.length > 0) {
964
+ console.log(` ${providers.join(', ')}`);
965
+ }
966
+ if (!usingKeychain) {
967
+ console.log('');
968
+ console.log('Tip: Install keytar for better security:');
969
+ console.log(' npm install keytar');
970
+ }
971
+ break;
972
+ }
973
+
974
+ default: {
975
+ error('Unknown auth subcommand: ' + subcommand + '\nValid: save, list, delete, test');
976
+ }
977
+ }
978
+ break;
979
+ }
980
+
981
+ case 'resolve-model': {
982
+ commands.cmdResolveModel(cwd, args[1], raw);
983
+ break;
984
+ }
985
+
986
+ case 'find-phase': {
987
+ phase.cmdFindPhase(cwd, args[1], raw);
988
+ break;
989
+ }
990
+
991
+ case 'commit': {
992
+ const amend = args.includes('--amend');
993
+ const filesIndex = args.indexOf('--files');
994
+ // Collect all positional args between command name and first flag,
995
+ // then join them — handles both quoted ("multi word msg") and
996
+ // unquoted (multi word msg) invocations from different shells
997
+ const endIndex = filesIndex !== -1 ? filesIndex : args.length;
998
+ const messageArgs = args.slice(1, endIndex).filter(a => !a.startsWith('--'));
999
+ const message = messageArgs.join(' ') || undefined;
1000
+ const files = filesIndex !== -1 ? args.slice(filesIndex + 1).filter(a => !a.startsWith('--')) : [];
1001
+ commands.cmdCommit(cwd, message, files, raw, amend);
1002
+ break;
1003
+ }
1004
+
1005
+ case 'verify-summary': {
1006
+ const summaryPath = args[1];
1007
+ const countIndex = args.indexOf('--check-count');
1008
+ const checkCount = countIndex !== -1 ? parseInt(args[countIndex + 1], 10) : 2;
1009
+ verify.cmdVerifySummary(cwd, summaryPath, checkCount, raw);
1010
+ break;
1011
+ }
1012
+
1013
+ case 'template': {
1014
+ const subcommand = args[1];
1015
+ if (subcommand === 'select') {
1016
+ template.cmdTemplateSelect(cwd, args[2], raw);
1017
+ } else if (subcommand === 'fill') {
1018
+ const templateType = args[2];
1019
+ const phaseIdx = args.indexOf('--phase');
1020
+ const planIdx = args.indexOf('--plan');
1021
+ const nameIdx = args.indexOf('--name');
1022
+ const typeIdx = args.indexOf('--type');
1023
+ const waveIdx = args.indexOf('--wave');
1024
+ const fieldsIdx = args.indexOf('--fields');
1025
+ template.cmdTemplateFill(cwd, templateType, {
1026
+ phase: phaseIdx !== -1 ? args[phaseIdx + 1] : null,
1027
+ plan: planIdx !== -1 ? args[planIdx + 1] : null,
1028
+ name: nameIdx !== -1 ? args[nameIdx + 1] : null,
1029
+ type: typeIdx !== -1 ? args[typeIdx + 1] : 'execute',
1030
+ wave: waveIdx !== -1 ? args[waveIdx + 1] : '1',
1031
+ fields: fieldsIdx !== -1 ? JSON.parse(args[fieldsIdx + 1]) : {},
1032
+ }, raw);
1033
+ } else {
1034
+ error('Unknown template subcommand. Available: select, fill');
1035
+ }
1036
+ break;
1037
+ }
1038
+
1039
+ case 'frontmatter': {
1040
+ const subcommand = args[1];
1041
+ const file = args[2];
1042
+ if (subcommand === 'get') {
1043
+ const fieldIdx = args.indexOf('--field');
1044
+ frontmatter.cmdFrontmatterGet(cwd, file, fieldIdx !== -1 ? args[fieldIdx + 1] : null, raw);
1045
+ } else if (subcommand === 'set') {
1046
+ const fieldIdx = args.indexOf('--field');
1047
+ const valueIdx = args.indexOf('--value');
1048
+ frontmatter.cmdFrontmatterSet(cwd, file, fieldIdx !== -1 ? args[fieldIdx + 1] : null, valueIdx !== -1 ? args[valueIdx + 1] : undefined, raw);
1049
+ } else if (subcommand === 'merge') {
1050
+ const dataIdx = args.indexOf('--data');
1051
+ frontmatter.cmdFrontmatterMerge(cwd, file, dataIdx !== -1 ? args[dataIdx + 1] : null, raw);
1052
+ } else if (subcommand === 'validate') {
1053
+ const schemaIdx = args.indexOf('--schema');
1054
+ frontmatter.cmdFrontmatterValidate(cwd, file, schemaIdx !== -1 ? args[schemaIdx + 1] : null, raw);
1055
+ } else {
1056
+ error('Unknown frontmatter subcommand. Available: get, set, merge, validate');
1057
+ }
1058
+ break;
1059
+ }
1060
+
1061
+ case 'verify': {
1062
+ const subcommand = args[1];
1063
+ if (subcommand === 'plan-structure') {
1064
+ verify.cmdVerifyPlanStructure(cwd, args[2], raw);
1065
+ } else if (subcommand === 'phase-completeness') {
1066
+ verify.cmdVerifyPhaseCompleteness(cwd, args[2], raw);
1067
+ } else if (subcommand === 'references') {
1068
+ verify.cmdVerifyReferences(cwd, args[2], raw);
1069
+ } else if (subcommand === 'commits') {
1070
+ verify.cmdVerifyCommits(cwd, args.slice(2), raw);
1071
+ } else if (subcommand === 'artifacts') {
1072
+ verify.cmdVerifyArtifacts(cwd, args[2], raw);
1073
+ } else if (subcommand === 'key-links') {
1074
+ verify.cmdVerifyKeyLinks(cwd, args[2], raw);
1075
+ } else {
1076
+ error('Unknown verify subcommand. Available: plan-structure, phase-completeness, references, commits, artifacts, key-links');
1077
+ }
1078
+ break;
1079
+ }
1080
+
1081
+ case 'generate-slug': {
1082
+ commands.cmdGenerateSlug(args[1], raw);
1083
+ break;
1084
+ }
1085
+
1086
+ case 'current-timestamp': {
1087
+ commands.cmdCurrentTimestamp(args[1] || 'full', raw);
1088
+ break;
1089
+ }
1090
+
1091
+ case 'list-todos': {
1092
+ commands.cmdListTodos(cwd, args[1], raw);
1093
+ break;
1094
+ }
1095
+
1096
+ case 'verify-path-exists': {
1097
+ commands.cmdVerifyPathExists(cwd, args[1], raw);
1098
+ break;
1099
+ }
1100
+
1101
+ case 'config-ensure-section': {
1102
+ config.cmdConfigEnsureSection(cwd, raw);
1103
+ break;
1104
+ }
1105
+
1106
+ case 'config-set': {
1107
+ config.cmdConfigSet(cwd, args[1], args[2], raw);
1108
+ break;
1109
+ }
1110
+
1111
+ case 'config-get': {
1112
+ config.cmdConfigGet(cwd, args[1], raw);
1113
+ break;
1114
+ }
1115
+
1116
+ case 'history-digest': {
1117
+ commands.cmdHistoryDigest(cwd, raw);
1118
+ break;
1119
+ }
1120
+
1121
+ case 'phases': {
1122
+ const subcommand = args[1];
1123
+ if (subcommand === 'list') {
1124
+ const typeIndex = args.indexOf('--type');
1125
+ const phaseIndex = args.indexOf('--phase');
1126
+ const options = {
1127
+ type: typeIndex !== -1 ? args[typeIndex + 1] : null,
1128
+ phase: phaseIndex !== -1 ? args[phaseIndex + 1] : null,
1129
+ includeArchived: args.includes('--include-archived'),
1130
+ };
1131
+ phase.cmdPhasesList(cwd, options, raw);
1132
+ } else {
1133
+ error('Unknown phases subcommand. Available: list');
1134
+ }
1135
+ break;
1136
+ }
1137
+
1138
+ case 'roadmap': {
1139
+ const subcommand = args[1];
1140
+ if (subcommand === 'get-phase') {
1141
+ roadmap.cmdRoadmapGetPhase(cwd, args[2], raw);
1142
+ } else if (subcommand === 'analyze') {
1143
+ roadmap.cmdRoadmapAnalyze(cwd, raw);
1144
+ } else if (subcommand === 'update-plan-progress') {
1145
+ roadmap.cmdRoadmapUpdatePlanProgress(cwd, args[2], raw);
1146
+ } else {
1147
+ error('Unknown roadmap subcommand. Available: get-phase, analyze, update-plan-progress');
1148
+ }
1149
+ break;
1150
+ }
1151
+
1152
+ case 'requirements': {
1153
+ const subcommand = args[1];
1154
+ if (subcommand === 'mark-complete') {
1155
+ milestone.cmdRequirementsMarkComplete(cwd, args.slice(2), raw);
1156
+ } else {
1157
+ error('Unknown requirements subcommand. Available: mark-complete');
1158
+ }
1159
+ break;
1160
+ }
1161
+
1162
+ case 'phase': {
1163
+ const subcommand = args[1];
1164
+ if (subcommand === 'next-decimal') {
1165
+ phase.cmdPhaseNextDecimal(cwd, args[2], raw);
1166
+ } else if (subcommand === 'add') {
1167
+ phase.cmdPhaseAdd(cwd, args.slice(2).join(' '), raw);
1168
+ } else if (subcommand === 'insert') {
1169
+ phase.cmdPhaseInsert(cwd, args[2], args.slice(3).join(' '), raw);
1170
+ } else if (subcommand === 'remove') {
1171
+ const forceFlag = args.includes('--force');
1172
+ phase.cmdPhaseRemove(cwd, args[2], { force: forceFlag }, raw);
1173
+ } else if (subcommand === 'complete') {
1174
+ phase.cmdPhaseComplete(cwd, args[2], raw);
1175
+ } else {
1176
+ error('Unknown phase subcommand. Available: next-decimal, add, insert, remove, complete');
1177
+ }
1178
+ break;
1179
+ }
1180
+
1181
+ case 'milestone': {
1182
+ const subcommand = args[1];
1183
+ if (subcommand === 'complete') {
1184
+ const nameIndex = args.indexOf('--name');
1185
+ const archivePhases = args.includes('--archive-phases');
1186
+ // Collect --name value (everything after --name until next flag or end)
1187
+ let milestoneName = null;
1188
+ if (nameIndex !== -1) {
1189
+ const nameArgs = [];
1190
+ for (let i = nameIndex + 1; i < args.length; i++) {
1191
+ if (args[i].startsWith('--')) break;
1192
+ nameArgs.push(args[i]);
1193
+ }
1194
+ milestoneName = nameArgs.join(' ') || null;
1195
+ }
1196
+ milestone.cmdMilestoneComplete(cwd, args[2], { name: milestoneName, archivePhases }, raw);
1197
+ } else {
1198
+ error('Unknown milestone subcommand. Available: complete');
1199
+ }
1200
+ break;
1201
+ }
1202
+
1203
+ case 'package-manager': {
1204
+ const subcommand = args[1];
1205
+
1206
+ if (!subcommand) {
1207
+ error('Usage: ez-tools package-manager <detect|install|add|remove|info> [options]\nSubcommands: detect, install, add, remove, info');
1208
+ }
1209
+
1210
+ try {
1211
+ const service = new PackageManagerService(cwd);
1212
+
1213
+ switch (subcommand) {
1214
+ case 'detect': {
1215
+ const detector = new (require('./lib/package-manager-detector.cjs'))(cwd);
1216
+ const result = detector.detect();
1217
+ if (raw) {
1218
+ console.log(`manager=${result.manager} source=${result.source} confidence=${result.confidence}`);
1219
+ } else {
1220
+ console.log(JSON.stringify(result, null, 2));
1221
+ }
1222
+ break;
1223
+ }
1224
+
1225
+ case 'install': {
1226
+ await service.initialize();
1227
+ const frozenLockfile = args.includes('--frozen');
1228
+ const production = args.includes('--production');
1229
+ const result = await service.install({ frozenLockfile, production });
1230
+ if (!raw) {
1231
+ console.log(result);
1232
+ }
1233
+ break;
1234
+ }
1235
+
1236
+ case 'add': {
1237
+ await service.initialize();
1238
+ const dev = args.includes('--dev') || args.includes('-D');
1239
+ // Extract packages (filter out flags)
1240
+ const packages = args.slice(2).filter(arg => !arg.startsWith('--'));
1241
+ if (packages.length === 0) {
1242
+ error('Usage: ez-tools package-manager add <package> [--dev|-D]');
1243
+ }
1244
+ const result = await service.add(packages, { dev });
1245
+ if (!raw) {
1246
+ console.log(result);
1247
+ }
1248
+ break;
1249
+ }
1250
+
1251
+ case 'remove': {
1252
+ await service.initialize();
1253
+ // Extract packages (filter out flags)
1254
+ const packages = args.slice(2).filter(arg => !arg.startsWith('--'));
1255
+ if (packages.length === 0) {
1256
+ error('Usage: ez-tools package-manager remove <package>');
1257
+ }
1258
+ const result = await service.remove(packages);
1259
+ if (!raw) {
1260
+ console.log(result);
1261
+ }
1262
+ break;
1263
+ }
1264
+
1265
+ case 'info': {
1266
+ await service.initialize();
1267
+ const result = service.getInfo();
1268
+ if (raw) {
1269
+ console.log(`manager=${result.manager} source=${result.source} cwd=${result.cwd} lockfile=${result.lockfile}`);
1270
+ } else {
1271
+ console.log(JSON.stringify(result, null, 2));
1272
+ }
1273
+ break;
1274
+ }
1275
+
1276
+ default: {
1277
+ error('Unknown package-manager subcommand: ' + subcommand + '\nValid: detect, install, add, remove, info');
1278
+ }
1279
+ }
1280
+ } catch (err) {
1281
+ error(err.message);
1282
+ }
1283
+ break;
1284
+ }
1285
+
1286
+ case 'validate': {
1287
+ const subcommand = args[1];
1288
+ if (subcommand === 'consistency') {
1289
+ verify.cmdValidateConsistency(cwd, raw);
1290
+ } else if (subcommand === 'health') {
1291
+ const repairFlag = args.includes('--repair');
1292
+ verify.cmdValidateHealth(cwd, { repair: repairFlag }, raw);
1293
+ } else {
1294
+ error('Unknown validate subcommand. Available: consistency, health');
1295
+ }
1296
+ break;
1297
+ }
1298
+
1299
+ case 'progress': {
1300
+ const subcommand = args[1] || 'json';
1301
+ commands.cmdProgressRender(cwd, subcommand, raw);
1302
+ break;
1303
+ }
1304
+
1305
+ case 'stats': {
1306
+ const subcommand = args[1] || 'json';
1307
+ commands.cmdStats(cwd, subcommand, raw);
1308
+ break;
1309
+ }
1310
+
1311
+ case 'todo': {
1312
+ const subcommand = args[1];
1313
+ if (subcommand === 'complete') {
1314
+ commands.cmdTodoComplete(cwd, args[2], raw);
1315
+ } else {
1316
+ error('Unknown todo subcommand. Available: complete');
1317
+ }
1318
+ break;
1319
+ }
1320
+
1321
+ case 'scaffold': {
1322
+ const scaffoldType = args[1];
1323
+ const phaseIndex = args.indexOf('--phase');
1324
+ const nameIndex = args.indexOf('--name');
1325
+ const scaffoldOptions = {
1326
+ phase: phaseIndex !== -1 ? args[phaseIndex + 1] : null,
1327
+ name: nameIndex !== -1 ? args.slice(nameIndex + 1).join(' ') : null,
1328
+ };
1329
+ commands.cmdScaffold(cwd, scaffoldType, scaffoldOptions, raw);
1330
+ break;
1331
+ }
1332
+
1333
+ case 'init': {
1334
+ const workflow = args[1];
1335
+ switch (workflow) {
1336
+ case 'execute-phase':
1337
+ init.cmdInitExecutePhase(cwd, args[2], raw);
1338
+ break;
1339
+ case 'plan-phase':
1340
+ init.cmdInitPlanPhase(cwd, args[2], raw);
1341
+ break;
1342
+ case 'new-project':
1343
+ await init.cmdInitNewProject(cwd, raw);
1344
+ break;
1345
+ case 'new-milestone':
1346
+ init.cmdInitNewMilestone(cwd, raw);
1347
+ break;
1348
+ case 'quick':
1349
+ init.cmdInitQuick(cwd, args.slice(2).join(' '), raw);
1350
+ break;
1351
+ case 'resume':
1352
+ init.cmdInitResume(cwd, raw);
1353
+ break;
1354
+ case 'verify-work':
1355
+ init.cmdInitVerifyWork(cwd, args[2], raw);
1356
+ break;
1357
+ case 'phase-op':
1358
+ init.cmdInitPhaseOp(cwd, args[2], raw);
1359
+ break;
1360
+ case 'todos':
1361
+ init.cmdInitTodos(cwd, args[2], raw);
1362
+ break;
1363
+ case 'milestone-op':
1364
+ init.cmdInitMilestoneOp(cwd, raw);
1365
+ break;
1366
+ case 'map-codebase':
1367
+ init.cmdInitMapCodebase(cwd, raw);
1368
+ break;
1369
+ case 'progress':
1370
+ init.cmdInitProgress(cwd, raw);
1371
+ break;
1372
+ default:
1373
+ error(`Unknown init workflow: ${workflow}\nAvailable: execute-phase, plan-phase, new-project, new-milestone, quick, resume, verify-work, phase-op, todos, milestone-op, map-codebase, progress`);
1374
+ }
1375
+ break;
1376
+ }
1377
+
1378
+ case 'phase-plan-index': {
1379
+ phase.cmdPhasePlanIndex(cwd, args[1], raw);
1380
+ break;
1381
+ }
1382
+
1383
+ case 'state-snapshot': {
1384
+ state.cmdStateSnapshot(cwd, raw);
1385
+ break;
1386
+ }
1387
+
1388
+ case 'summary-extract': {
1389
+ const summaryPath = args[1];
1390
+ const fieldsIndex = args.indexOf('--fields');
1391
+ const fields = fieldsIndex !== -1 ? args[fieldsIndex + 1].split(',') : null;
1392
+ commands.cmdSummaryExtract(cwd, summaryPath, fields, raw);
1393
+ break;
1394
+ }
1395
+
1396
+ case 'websearch': {
1397
+ const query = args[1];
1398
+ const limitIdx = args.indexOf('--limit');
1399
+ const freshnessIdx = args.indexOf('--freshness');
1400
+ await commands.cmdWebsearch(query, {
1401
+ limit: limitIdx !== -1 ? parseInt(args[limitIdx + 1], 10) : 10,
1402
+ freshness: freshnessIdx !== -1 ? args[freshnessIdx + 1] : null,
1403
+ }, raw);
1404
+ break;
1405
+ }
1406
+
1407
+ case 'context': {
1408
+ const subcommand = args[1];
1409
+
1410
+ if (subcommand === 'read') {
1411
+ const patterns = args.slice(2);
1412
+ if (patterns.length === 0) {
1413
+ console.error('Usage: ez-tools context read <pattern1> [pattern2...]');
1414
+ console.error('Example: node ez-tools.cjs context read "README.md" "src/**/*.ts"');
1415
+ process.exit(1);
1416
+ }
1417
+ const fileAccess = new FileAccessService(cwd);
1418
+ const results = fileAccess.readFiles(patterns);
1419
+ results.forEach(file => {
1420
+ console.log(`\n--- ${file.path} ---\n`);
1421
+ console.log(file.content);
1422
+ });
1423
+ break;
1424
+ }
1425
+
1426
+ if (subcommand === 'fetch') {
1427
+ const url = args[2];
1428
+ if (!url) {
1429
+ console.error('Usage: ez-tools context fetch <url>');
1430
+ console.error('Example: node ez-tools.cjs context fetch https://example.com/doc.md');
1431
+ process.exit(1);
1432
+ }
1433
+ const urlFetch = new URLFetchService();
1434
+ const validated = urlFetch.validateUrl(url);
1435
+ if (!validated.valid) {
1436
+ console.error(`Invalid URL: ${validated.error}`);
1437
+ process.exit(1);
1438
+ }
1439
+ const confirmed = await URLFetchService.confirmUrlFetch(url);
1440
+ if (!confirmed) {
1441
+ console.log('Fetch cancelled by user');
1442
+ process.exit(0);
1443
+ }
1444
+ const result = await urlFetch.fetchUrl(url);
1445
+ console.log(result.content);
1446
+ break;
1447
+ }
1448
+
1449
+ if (subcommand === 'request') {
1450
+ const contextManager = new ContextManager(cwd);
1451
+ console.log('Context Request Mode');
1452
+ console.log('Enter file patterns or URLs (one per line, empty line to finish):\n');
1453
+
1454
+ const readline = require('readline');
1455
+ const rl = readline.createInterface({
1456
+ input: process.stdin,
1457
+ output: process.stdout
1458
+ });
1459
+
1460
+ const files = [];
1461
+ const urls = [];
1462
+
1463
+ console.log('> ');
1464
+ for await (const line of rl) {
1465
+ if (!line.trim()) break;
1466
+ if (line.startsWith('http')) {
1467
+ urls.push(line);
1468
+ } else {
1469
+ files.push(line);
1470
+ }
1471
+ console.log('> ');
1472
+ }
1473
+
1474
+ const result = await contextManager.requestContext({ files, urls });
1475
+ console.log('\n=== Aggregated Context ===\n');
1476
+ console.log(result.context);
1477
+ console.log(`\n\nSources: ${result.sources.length}`);
1478
+ if (result.errors.length > 0) {
1479
+ console.log(`Errors: ${result.errors.length}`);
1480
+ result.errors.forEach(e => console.error(` - ${e.source}: ${e.message}`));
1481
+ }
1482
+ break;
1483
+ }
1484
+
1485
+ if (!subcommand) {
1486
+ console.log('Context Access Commands');
1487
+ console.log('═══════════════════════');
1488
+ console.log('');
1489
+ console.log(' context read <pattern> Read local files using glob patterns');
1490
+ console.log(' context fetch <url> Fetch content from URL (HTTPS only)');
1491
+ console.log(' context request Interactive context gathering mode');
1492
+ console.log('');
1493
+ console.log('Examples:');
1494
+ console.log(' node ez-tools.cjs context read "README.md"');
1495
+ console.log(' node ez-tools.cjs context read "src/**/*.ts" "!*.test.ts"');
1496
+ console.log(' node ez-tools.cjs context fetch https://example.com/spec.md');
1497
+ console.log(' node ez-tools.cjs context request');
1498
+ break;
1499
+ }
1500
+
1501
+ error(`Unknown context subcommand: ${subcommand}\nAvailable: read, fetch, request`);
1502
+ }
1503
+
1504
+ // ─── Session Management Commands ─────────────────────────────────────────
1505
+
1506
+ case 'session': {
1507
+ const subcommand = args[1];
1508
+ const sessionMgr = new SessionManager();
1509
+
1510
+ switch (subcommand) {
1511
+ case 'list': {
1512
+ const sessions = sessionMgr.listSessions();
1513
+ const limitIdx = args.indexOf('--limit');
1514
+ const limit = limitIdx !== -1 ? parseInt(args[limitIdx + 1]) : 20;
1515
+ output({ sessions: sessions.slice(0, limit) });
1516
+ break;
1517
+ }
1518
+
1519
+ case 'get': {
1520
+ const sessionId = args[2];
1521
+ if (!sessionId) {
1522
+ error('Usage: ez-tools session get <session_id>');
1523
+ }
1524
+ const session = sessionMgr.loadSession(sessionId);
1525
+ if (!session) {
1526
+ error(`Session not found: ${sessionId}`);
1527
+ }
1528
+ output(session);
1529
+ break;
1530
+ }
1531
+
1532
+ case 'create': {
1533
+ const modelIdx = args.indexOf('--model');
1534
+ const phaseIdx = args.indexOf('--phase');
1535
+ const planIdx = args.indexOf('--plan');
1536
+ const newSessionId = sessionMgr.createSession({
1537
+ model: modelIdx !== -1 ? args[modelIdx + 1] : undefined,
1538
+ phase: phaseIdx !== -1 ? parseInt(args[phaseIdx + 1]) : undefined,
1539
+ plan: planIdx !== -1 ? parseInt(args[planIdx + 1]) : undefined
1540
+ });
1541
+ output({ session_id: newSessionId });
1542
+ break;
1543
+ }
1544
+
1545
+ case 'end': {
1546
+ const sessionId = args[2];
1547
+ if (!sessionId) {
1548
+ error('Usage: ez-tools session end <session_id> [--status status]');
1549
+ }
1550
+ const statusIdx = args.indexOf('--status');
1551
+ const status = statusIdx !== -1 ? args[statusIdx + 1] : 'completed';
1552
+ const success = sessionMgr.endSession(sessionId, { status });
1553
+ if (!success) {
1554
+ error(`Failed to end session: ${sessionId}`);
1555
+ }
1556
+ output({ ended: sessionId });
1557
+ break;
1558
+ }
1559
+
1560
+ case 'compress': {
1561
+ const sessionId = args[2];
1562
+ const thresholdIdx = args.indexOf('--threshold');
1563
+ const threshold = thresholdIdx !== -1 ? parseInt(args[thresholdIdx + 1]) : 50;
1564
+
1565
+ if (!sessionId) {
1566
+ error('Usage: ez-tools session compress <session_id> [--threshold N]');
1567
+ }
1568
+
1569
+ const compressor = new MemoryCompression(sessionMgr);
1570
+ const result = compressor.compress(sessionId, { threshold });
1571
+ output(result);
1572
+ break;
1573
+ }
1574
+
1575
+ default:
1576
+ error('Usage: ez-tools session <list|get|create|end|compress> [args]');
1577
+ }
1578
+ break;
1579
+ }
1580
+
1581
+ case 'resume': {
1582
+ const sessionId = args[1];
1583
+ const sessionMgr = new SessionManager();
1584
+
1585
+ const session = sessionId
1586
+ ? sessionMgr.loadSession(sessionId)
1587
+ : sessionMgr.getLastSession();
1588
+
1589
+ if (!session) {
1590
+ error('No session found to resume');
1591
+ }
1592
+
1593
+ // Output session summary for display
1594
+ output({
1595
+ session_id: session.metadata.session_id,
1596
+ model: session.metadata.model,
1597
+ phase: session.metadata.phase,
1598
+ plan: session.metadata.plan,
1599
+ last_action: session.state.last_action,
1600
+ next_action: session.state.next_recommended_action,
1601
+ incomplete_tasks: session.state.incomplete_tasks
1602
+ });
1603
+ break;
1604
+ }
1605
+
1606
+ case 'export-session': {
1607
+ const sessionId = args[1] || 'last';
1608
+ const formatIdx = args.indexOf('--format');
1609
+ const outputIdx = args.indexOf('--output');
1610
+
1611
+ const format = formatIdx !== -1 ? args[formatIdx + 1] : 'markdown';
1612
+ const outputPath = outputIdx !== -1 ? args[outputIdx + 1] : null;
1613
+
1614
+ const sessionMgr = new SessionManager();
1615
+ const exporter = new SessionExport(sessionMgr);
1616
+
1617
+ const actualSessionId = sessionId === 'last'
1618
+ ? sessionMgr.getLastSession()?.metadata?.session_id
1619
+ : sessionId;
1620
+
1621
+ if (!actualSessionId) {
1622
+ error('No sessions found');
1623
+ }
1624
+
1625
+ try {
1626
+ const result = exporter.exportToFile(actualSessionId, format, outputPath);
1627
+ output(result);
1628
+ } catch (err) {
1629
+ error(`Export failed: ${err.message}`);
1630
+ }
1631
+ break;
1632
+ }
1633
+
1634
+ case 'import-session': {
1635
+ const filePath = args[1];
1636
+ const sourceModelIdx = args.indexOf('--source-model');
1637
+ const sourceModel = sourceModelIdx !== -1 ? args[sourceModelIdx + 1] : null;
1638
+
1639
+ if (!filePath) {
1640
+ error('Usage: ez-tools import-session <file.json> [--source-model <model>]');
1641
+ }
1642
+
1643
+ const sessionMgr = new SessionManager();
1644
+ const importer = new SessionImport(sessionMgr);
1645
+
1646
+ try {
1647
+ const result = importer.import(filePath, { sourceModel });
1648
+ output(result);
1649
+ } catch (err) {
1650
+ error(`Import failed: ${err.message}`);
1651
+ }
1652
+ break;
1653
+ }
1654
+
1655
+ case 'chain': {
1656
+ const sessionId = args[1];
1657
+ const direction = args[2];
1658
+
1659
+ if (!sessionId) {
1660
+ error('Usage: ez-tools chain <session_id> [previous|next|visualize|repair]');
1661
+ }
1662
+
1663
+ const sessionMgr = new SessionManager();
1664
+ const chain = new SessionChain(sessionMgr);
1665
+
1666
+ if (direction === 'previous' || direction === 'next') {
1667
+ const result = chain.navigate(sessionId, direction);
1668
+ if (!result) {
1669
+ output({ navigation: null, message: `No session ${direction} of ${sessionId}` });
1670
+ } else {
1671
+ output(result);
1672
+ }
1673
+ } else if (direction === 'visualize' || !direction) {
1674
+ const viz = chain.getChainVisualization(sessionId);
1675
+ output({ visualization: viz });
1676
+ } else if (direction === 'repair') {
1677
+ const result = chain.repairChain(sessionId);
1678
+ output(result);
1679
+ } else {
1680
+ error('Usage: ez-tools chain <session_id> [previous|next|visualize|repair]');
1681
+ }
1682
+ break;
1683
+ }
1684
+
1685
+ // ─── Default/Error Case ──────────────────────────────────────────────────
1686
+
1687
+ default:
1688
+ error(`Unknown command: ${command}`);
1689
+ }
1690
+ }
1691
+
1692
+ main();