@howlil/ez-agents 3.4.2 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. package/README.md +735 -462
  2. package/agents/ez-architect-agent.md +267 -0
  3. package/agents/ez-backend-agent.md +303 -0
  4. package/agents/ez-chief-strategist.md +271 -0
  5. package/agents/ez-codebase-mapper.md +770 -770
  6. package/agents/ez-context-manager.md +319 -0
  7. package/agents/ez-debugger.md +1255 -1255
  8. package/agents/ez-design-expert.md +347 -0
  9. package/agents/ez-devops-agent.md +331 -0
  10. package/agents/ez-executor.md +487 -487
  11. package/agents/ez-frontend-agent.md +322 -0
  12. package/agents/ez-phase-researcher.md +553 -553
  13. package/agents/ez-planner.md +1307 -1307
  14. package/agents/ez-product-engineer.md +435 -0
  15. package/agents/ez-project-researcher.md +629 -629
  16. package/agents/ez-qa-agent.md +320 -0
  17. package/agents/ez-release-agent.md +333 -0
  18. package/agents/ez-requirements-agent.md +377 -0
  19. package/agents/ez-roadmapper.md +650 -650
  20. package/agents/ez-technical-writer.md +551 -0
  21. package/agents/ez-ux-expert.md +393 -0
  22. package/agents/ez-verifier.md +579 -579
  23. package/bin/guards/autonomy-guard.cjs +346 -0
  24. package/bin/guards/context-budget-guard.cjs +278 -0
  25. package/bin/guards/hallucination-guard.cjs +380 -0
  26. package/bin/guards/hidden-state-guard.cjs +182 -0
  27. package/bin/guards/team-overhead-guard.cjs +266 -0
  28. package/bin/guards/tool-sprawl-guard.cjs +271 -0
  29. package/bin/install.js +3221 -3272
  30. package/bin/lib/analytics/analytics-collector.cjs +86 -0
  31. package/bin/lib/analytics/analytics-reporter.cjs +130 -0
  32. package/bin/lib/analytics/cohort-analyzer.cjs +138 -0
  33. package/bin/lib/analytics/funnel-analyzer.cjs +147 -0
  34. package/bin/lib/analytics/nps-tracker.cjs +147 -0
  35. package/bin/lib/archetype-detector.cjs +289 -0
  36. package/bin/lib/assistant-adapter.cjs +361 -0
  37. package/bin/lib/audit-exec.cjs +175 -0
  38. package/bin/lib/auth.cjs +176 -0
  39. package/bin/lib/backup-service.cjs +422 -0
  40. package/bin/lib/bdd-validator.cjs +622 -0
  41. package/bin/lib/business-flow-mapper.cjs +429 -0
  42. package/bin/lib/circuit-breaker.cjs +276 -0
  43. package/bin/lib/code-complexity-analyzer.cjs +360 -0
  44. package/bin/lib/codebase-analyzer.cjs +241 -0
  45. package/bin/lib/commands.cjs +691 -0
  46. package/bin/lib/config.cjs +236 -0
  47. package/bin/lib/constraint-extractor.cjs +526 -0
  48. package/bin/lib/content-scanner.cjs +238 -0
  49. package/bin/lib/context-cache.cjs +154 -0
  50. package/bin/lib/context-compressor.cjs +102 -0
  51. package/bin/lib/context-deduplicator.cjs +105 -0
  52. package/bin/lib/context-errors.cjs +78 -0
  53. package/bin/lib/context-manager.cjs +338 -0
  54. package/bin/lib/context-metadata-tracker.cjs +140 -0
  55. package/bin/lib/context-relevance-scorer.cjs +99 -0
  56. package/bin/lib/core.cjs +507 -0
  57. package/bin/lib/cost-alerts.cjs +174 -0
  58. package/bin/lib/cost-tracker.cjs +275 -0
  59. package/bin/lib/crash-recovery.cjs +220 -0
  60. package/bin/lib/dependency-graph.cjs +319 -0
  61. package/bin/lib/deploy/deploy-audit-log.cjs +76 -0
  62. package/bin/lib/deploy/deploy-detector.cjs +69 -0
  63. package/bin/lib/deploy/deploy-env-manager.cjs +109 -0
  64. package/bin/lib/deploy/deploy-health-check.cjs +88 -0
  65. package/bin/lib/deploy/deploy-pre-flight.cjs +57 -0
  66. package/bin/lib/deploy/deploy-rollback.cjs +72 -0
  67. package/bin/lib/deploy/deploy-runner.cjs +97 -0
  68. package/bin/lib/deploy/deploy-status.cjs +74 -0
  69. package/bin/lib/discussion-synthesizer.cjs +439 -0
  70. package/bin/lib/error-cache.cjs +114 -0
  71. package/bin/lib/error-registry.cjs +177 -0
  72. package/bin/lib/file-access.cjs +207 -0
  73. package/bin/lib/file-lock.cjs +236 -0
  74. package/bin/lib/finops/budget-enforcer.cjs +126 -0
  75. package/bin/lib/finops/cost-reporter.cjs +132 -0
  76. package/bin/lib/finops/finops-analyzer.cjs +112 -0
  77. package/bin/lib/finops/spot-manager.cjs +118 -0
  78. package/bin/lib/framework-detector.cjs +396 -0
  79. package/bin/lib/frontmatter.cjs +313 -0
  80. package/bin/lib/fs-utils.cjs +153 -0
  81. package/bin/lib/gate-executor.cjs +272 -0
  82. package/bin/lib/gates/README.md +374 -0
  83. package/bin/lib/gates/gate-01-requirement.cjs +303 -0
  84. package/bin/lib/gates/gate-02-architecture.cjs +555 -0
  85. package/bin/lib/gates/gate-03-code.cjs +635 -0
  86. package/bin/lib/gates/gate-04-security.cjs +829 -0
  87. package/bin/lib/git-errors.cjs +83 -0
  88. package/bin/lib/git-utils.cjs +321 -0
  89. package/bin/lib/git-workflow-engine.cjs +1157 -0
  90. package/bin/lib/health-check.cjs +227 -0
  91. package/bin/lib/index.cjs +279 -0
  92. package/bin/lib/init.cjs +725 -0
  93. package/bin/lib/lock-logger.cjs +194 -0
  94. package/bin/lib/lock-state.cjs +263 -0
  95. package/bin/lib/lockfile-validator.cjs +227 -0
  96. package/bin/lib/log-rotation.cjs +71 -0
  97. package/bin/lib/logger.cjs +125 -0
  98. package/bin/lib/memory-compression.cjs +256 -0
  99. package/bin/lib/milestone.cjs +247 -0
  100. package/bin/lib/model-provider.cjs +241 -0
  101. package/bin/lib/package-manager-detector.cjs +203 -0
  102. package/bin/lib/package-manager-executor.cjs +385 -0
  103. package/bin/lib/package-manager-service.cjs +216 -0
  104. package/bin/lib/perf/api-monitor.cjs +88 -0
  105. package/bin/lib/perf/db-optimizer.cjs +78 -0
  106. package/bin/lib/perf/frontend-performance.cjs +56 -0
  107. package/bin/lib/perf/perf-analyzer.cjs +77 -0
  108. package/bin/lib/perf/perf-baseline.cjs +102 -0
  109. package/bin/lib/perf/perf-reporter.cjs +117 -0
  110. package/bin/lib/perf/regression-detector.cjs +92 -0
  111. package/bin/lib/phase.cjs +963 -0
  112. package/bin/lib/planning-write.cjs +123 -0
  113. package/bin/lib/project-reporter.cjs +565 -0
  114. package/bin/lib/quality-gate.cjs +332 -0
  115. package/bin/lib/quality-metrics.cjs +324 -0
  116. package/bin/lib/recovery-manager.cjs +98 -0
  117. package/bin/lib/release-validator.cjs +617 -0
  118. package/bin/lib/retry.cjs +119 -0
  119. package/bin/lib/roadmap.cjs +309 -0
  120. package/bin/lib/safe-exec.cjs +173 -0
  121. package/bin/lib/safe-path.cjs +130 -0
  122. package/bin/lib/security-errors.cjs +62 -0
  123. package/bin/lib/session-chain.cjs +304 -0
  124. package/bin/lib/session-errors.cjs +81 -0
  125. package/bin/lib/session-export.cjs +251 -0
  126. package/bin/lib/session-import.cjs +262 -0
  127. package/bin/lib/session-manager.cjs +280 -0
  128. package/bin/lib/skill-context.cjs +148 -0
  129. package/bin/lib/skill-matcher.cjs +236 -0
  130. package/bin/lib/skill-registry.cjs +360 -0
  131. package/bin/lib/skill-resolver.cjs +449 -0
  132. package/bin/lib/skill-triggers.cjs +90 -0
  133. package/bin/lib/skill-validator.cjs +270 -0
  134. package/bin/lib/skill-versioning.cjs +355 -0
  135. package/bin/lib/stack-detector.cjs +399 -0
  136. package/bin/lib/state.cjs +736 -0
  137. package/bin/lib/tech-debt-analyzer.cjs +309 -0
  138. package/bin/lib/temp-file.cjs +239 -0
  139. package/bin/lib/template.cjs +223 -0
  140. package/bin/lib/test-file-lock.cjs +112 -0
  141. package/bin/lib/test-graceful.cjs +93 -0
  142. package/bin/lib/test-logger.cjs +60 -0
  143. package/bin/lib/test-safe-exec.cjs +38 -0
  144. package/bin/lib/test-safe-path.cjs +33 -0
  145. package/bin/lib/test-temp-file.cjs +125 -0
  146. package/bin/lib/tier-manager.cjs +428 -0
  147. package/bin/lib/timeout-exec.cjs +63 -0
  148. package/bin/lib/tradeoff-analyzer.cjs +284 -0
  149. package/bin/lib/url-fetch.cjs +170 -0
  150. package/bin/lib/verify.cjs +863 -0
  151. package/bin/update.js +217 -214
  152. package/commands/deploy.cjs +53 -0
  153. package/commands/ez/add-tests.md +41 -41
  154. package/commands/ez/audit-milestone.md +36 -36
  155. package/commands/ez/complete-milestone.md +136 -136
  156. package/commands/ez/discuss-phase.md +90 -90
  157. package/commands/ez/execute-phase.md +52 -41
  158. package/commands/ez/help.md +22 -22
  159. package/commands/ez/map-codebase.md +71 -71
  160. package/commands/ez/new-milestone.md +44 -44
  161. package/commands/ez/new-project.md +51 -42
  162. package/commands/ez/plan-phase.md +53 -45
  163. package/commands/ez/progress.md +36 -24
  164. package/commands/ez/quick.md +45 -45
  165. package/commands/ez/resume-work.md +40 -40
  166. package/commands/ez/run-phase.md +580 -0
  167. package/commands/ez/settings.md +36 -36
  168. package/commands/ez/update.md +37 -37
  169. package/commands/ez/verify-work.md +402 -38
  170. package/commands/health-check.cjs +44 -0
  171. package/commands/rollback.cjs +47 -0
  172. package/ez-agents/bin/ez-tools.cjs +1692 -716
  173. package/ez-agents/bin/guards/autonomy-guard.cjs +346 -0
  174. package/ez-agents/bin/guards/context-budget-guard.cjs +247 -0
  175. package/ez-agents/bin/guards/hallucination-guard.cjs +271 -0
  176. package/ez-agents/bin/guards/hidden-state-guard.cjs +182 -0
  177. package/ez-agents/bin/guards/team-overhead-guard.cjs +266 -0
  178. package/ez-agents/bin/guards/tool-sprawl-guard.cjs +271 -0
  179. package/ez-agents/bin/lib/analytics/analytics-collector.cjs +86 -0
  180. package/ez-agents/bin/lib/analytics/analytics-reporter.cjs +130 -0
  181. package/ez-agents/bin/lib/analytics/cohort-analyzer.cjs +138 -0
  182. package/ez-agents/bin/lib/analytics/funnel-analyzer.cjs +147 -0
  183. package/ez-agents/bin/lib/analytics/nps-tracker.cjs +147 -0
  184. package/ez-agents/bin/lib/archetype-detector.cjs +289 -0
  185. package/ez-agents/bin/lib/audit-exec.cjs +166 -167
  186. package/ez-agents/bin/lib/auth.cjs +176 -176
  187. package/ez-agents/bin/lib/backup-service.cjs +422 -0
  188. package/ez-agents/bin/lib/bdd-validator.cjs +622 -0
  189. package/ez-agents/bin/lib/business-flow-mapper.cjs +429 -0
  190. package/ez-agents/bin/lib/code-complexity-analyzer.cjs +360 -0
  191. package/ez-agents/bin/lib/codebase-analyzer.cjs +241 -0
  192. package/ez-agents/bin/lib/commands.cjs +685 -685
  193. package/ez-agents/bin/lib/config.cjs +41 -1
  194. package/ez-agents/bin/lib/constraint-extractor.cjs +526 -0
  195. package/ez-agents/bin/lib/content-scanner.cjs +238 -0
  196. package/ez-agents/bin/lib/context-cache.cjs +154 -0
  197. package/ez-agents/bin/lib/context-errors.cjs +71 -0
  198. package/ez-agents/bin/lib/context-manager.cjs +220 -0
  199. package/ez-agents/bin/lib/core.cjs +507 -512
  200. package/ez-agents/bin/lib/cost-tracker.cjs +243 -0
  201. package/ez-agents/bin/lib/crash-recovery.cjs +172 -0
  202. package/ez-agents/bin/lib/dependency-graph.cjs +319 -0
  203. package/ez-agents/bin/lib/deploy/deploy-audit-log.cjs +76 -0
  204. package/ez-agents/bin/lib/deploy/deploy-detector.cjs +69 -0
  205. package/ez-agents/bin/lib/deploy/deploy-env-manager.cjs +109 -0
  206. package/ez-agents/bin/lib/deploy/deploy-health-check.cjs +88 -0
  207. package/ez-agents/bin/lib/deploy/deploy-pre-flight.cjs +57 -0
  208. package/ez-agents/bin/lib/deploy/deploy-rollback.cjs +72 -0
  209. package/ez-agents/bin/lib/deploy/deploy-runner.cjs +97 -0
  210. package/ez-agents/bin/lib/deploy/deploy-status.cjs +74 -0
  211. package/ez-agents/bin/lib/discussion-synthesizer.cjs +458 -0
  212. package/ez-agents/bin/lib/file-access.cjs +207 -0
  213. package/ez-agents/bin/lib/finops/budget-enforcer.cjs +126 -0
  214. package/ez-agents/bin/lib/finops/cost-reporter.cjs +132 -0
  215. package/ez-agents/bin/lib/finops/finops-analyzer.cjs +112 -0
  216. package/ez-agents/bin/lib/finops/spot-manager.cjs +118 -0
  217. package/ez-agents/bin/lib/framework-detector.cjs +396 -0
  218. package/ez-agents/bin/lib/frontmatter.cjs +3 -1
  219. package/ez-agents/bin/lib/gates/README.md +374 -0
  220. package/ez-agents/bin/lib/gates/gate-01-requirement.cjs +303 -0
  221. package/ez-agents/bin/lib/gates/gate-02-architecture.cjs +555 -0
  222. package/ez-agents/bin/lib/gates/gate-03-code.cjs +635 -0
  223. package/ez-agents/bin/lib/gates/gate-04-security.cjs +829 -0
  224. package/ez-agents/bin/lib/git-errors.cjs +83 -0
  225. package/ez-agents/bin/lib/git-utils.cjs +118 -0
  226. package/ez-agents/bin/lib/git-workflow-engine.cjs +1157 -0
  227. package/ez-agents/bin/lib/health-check.cjs +162 -162
  228. package/ez-agents/bin/lib/index.cjs +40 -2
  229. package/ez-agents/bin/lib/init.cjs +0 -2
  230. package/ez-agents/bin/lib/lockfile-validator.cjs +227 -0
  231. package/ez-agents/bin/lib/log-rotation.cjs +71 -0
  232. package/ez-agents/bin/lib/logger.cjs +99 -154
  233. package/ez-agents/bin/lib/memory-compression.cjs +256 -0
  234. package/ez-agents/bin/lib/package-manager-detector.cjs +203 -0
  235. package/ez-agents/bin/lib/package-manager-executor.cjs +385 -0
  236. package/ez-agents/bin/lib/package-manager-service.cjs +216 -0
  237. package/ez-agents/bin/lib/perf/api-monitor.cjs +88 -0
  238. package/ez-agents/bin/lib/perf/db-optimizer.cjs +78 -0
  239. package/ez-agents/bin/lib/perf/frontend-performance.cjs +56 -0
  240. package/ez-agents/bin/lib/perf/perf-analyzer.cjs +77 -0
  241. package/ez-agents/bin/lib/perf/perf-baseline.cjs +102 -0
  242. package/ez-agents/bin/lib/perf/perf-reporter.cjs +117 -0
  243. package/ez-agents/bin/lib/perf/regression-detector.cjs +92 -0
  244. package/ez-agents/bin/lib/project-reporter.cjs +502 -0
  245. package/ez-agents/bin/lib/quality-gate.cjs +332 -0
  246. package/ez-agents/bin/lib/recovery-manager.cjs +98 -0
  247. package/ez-agents/bin/lib/release-validator.cjs +617 -0
  248. package/ez-agents/bin/lib/safe-exec.cjs +128 -214
  249. package/ez-agents/bin/lib/security-errors.cjs +62 -0
  250. package/ez-agents/bin/lib/session-chain.cjs +304 -0
  251. package/ez-agents/bin/lib/session-errors.cjs +81 -0
  252. package/ez-agents/bin/lib/session-export.cjs +251 -0
  253. package/ez-agents/bin/lib/session-import.cjs +262 -0
  254. package/ez-agents/bin/lib/session-manager.cjs +280 -0
  255. package/ez-agents/bin/lib/skill-context.cjs +148 -0
  256. package/ez-agents/bin/lib/skill-matcher.cjs +236 -0
  257. package/ez-agents/bin/lib/skill-registry.cjs +341 -0
  258. package/ez-agents/bin/lib/skill-resolver.cjs +449 -0
  259. package/ez-agents/bin/lib/skill-triggers.cjs +90 -0
  260. package/ez-agents/bin/lib/skill-validator.cjs +270 -0
  261. package/ez-agents/bin/lib/skill-versioning.cjs +355 -0
  262. package/ez-agents/bin/lib/stack-detector.cjs +399 -0
  263. package/ez-agents/bin/lib/tech-debt-analyzer.cjs +309 -0
  264. package/ez-agents/bin/lib/tier-manager.cjs +428 -0
  265. package/ez-agents/bin/lib/tradeoff-analyzer.cjs +284 -0
  266. package/ez-agents/bin/lib/url-fetch.cjs +170 -0
  267. package/ez-agents/bin/lib/verify.cjs +863 -863
  268. package/ez-agents/references/decimal-phase-calculation.md +65 -65
  269. package/ez-agents/references/git-integration.md +248 -248
  270. package/ez-agents/references/git-planning-commit.md +38 -38
  271. package/ez-agents/references/metrics-schema.md +118 -0
  272. package/ez-agents/references/model-profile-resolution.md +34 -34
  273. package/ez-agents/references/model-profiles.md +93 -93
  274. package/ez-agents/references/phase-argument-parsing.md +61 -61
  275. package/ez-agents/references/planning-config.md +340 -200
  276. package/ez-agents/references/tier-strategy.md +103 -0
  277. package/ez-agents/references/ui-brand.md +160 -160
  278. package/ez-agents/references/verification-patterns.md +612 -612
  279. package/ez-agents/templates/DEBUG.md +164 -164
  280. package/ez-agents/templates/UAT.md +247 -247
  281. package/ez-agents/templates/agent-output-format.md +404 -0
  282. package/ez-agents/templates/bdd-feature.md +173 -0
  283. package/ez-agents/templates/codebase/architecture.md +255 -255
  284. package/ez-agents/templates/codebase/structure.md +285 -285
  285. package/ez-agents/templates/copilot-instructions.md +7 -7
  286. package/ez-agents/templates/debug-subagent-prompt.md +91 -91
  287. package/ez-agents/templates/discovery.md +146 -146
  288. package/ez-agents/templates/discussion.md +68 -0
  289. package/ez-agents/templates/handoff-protocol.md +294 -0
  290. package/ez-agents/templates/incident-runbook.md +205 -0
  291. package/ez-agents/templates/mode-workflow-templates.md +301 -0
  292. package/ez-agents/templates/phase-prompt.md +610 -610
  293. package/ez-agents/templates/planner-subagent-prompt.md +117 -117
  294. package/ez-agents/templates/project.md +184 -184
  295. package/ez-agents/templates/release-checklist.md +136 -0
  296. package/ez-agents/templates/research.md +552 -552
  297. package/ez-agents/templates/rollback-plan.md +201 -0
  298. package/ez-agents/templates/security-user-setup.md +244 -0
  299. package/ez-agents/templates/skill-validation-rules.md +476 -0
  300. package/ez-agents/templates/state.md +180 -176
  301. package/ez-agents/templates/summary-complex.md +59 -59
  302. package/ez-agents/tests/gates/gate-01-02.test.cjs +812 -0
  303. package/ez-agents/tests/gates/gate-03-04.test.cjs +762 -0
  304. package/ez-agents/tests/gates/gate-05-validator.test.cjs +145 -0
  305. package/ez-agents/tests/gates/gate-06-docs-validator.test.cjs +244 -0
  306. package/ez-agents/tests/gates/gate-07-release-validator.test.cjs +219 -0
  307. package/ez-agents/tests/guards/context-budget-guard.test.cjs +145 -0
  308. package/ez-agents/tests/guards/edge-case-guards.test.cjs +238 -0
  309. package/ez-agents/tests/guards/hallucination-guard.test.cjs +124 -0
  310. package/ez-agents/workflows/audit-milestone.md +1 -1
  311. package/ez-agents/workflows/autonomous.md +131 -30
  312. package/ez-agents/workflows/complete-milestone.md +1 -1
  313. package/ez-agents/workflows/discuss-phase.md +1 -1
  314. package/ez-agents/workflows/execute-phase.md +169 -3
  315. package/ez-agents/workflows/help.md +86 -133
  316. package/ez-agents/workflows/hotfix.md +291 -0
  317. package/ez-agents/workflows/new-milestone.md +340 -11
  318. package/ez-agents/workflows/new-project.md +294 -318
  319. package/ez-agents/workflows/plan-phase.md +22 -40
  320. package/ez-agents/workflows/progress.md +15 -25
  321. package/ez-agents/workflows/release.md +253 -0
  322. package/ez-agents/workflows/resume-session.md +215 -0
  323. package/ez-agents/workflows/run-phase.md +531 -0
  324. package/ez-agents/workflows/settings.md +2 -35
  325. package/hooks/dist/ez-check-update.js +81 -81
  326. package/hooks/dist/ez-context-monitor.js +148 -141
  327. package/hooks/dist/ez-statusline.js +115 -115
  328. package/package.json +78 -64
  329. package/scripts/fix-qwen-installation.js +144 -144
  330. package/agents/ez-integration-checker.md +0 -443
  331. package/agents/ez-nyquist-auditor.md +0 -176
  332. package/agents/ez-plan-checker.md +0 -706
  333. package/agents/ez-research-synthesizer.md +0 -247
  334. package/agents/ez-ui-auditor.md +0 -439
  335. package/agents/ez-ui-checker.md +0 -300
  336. package/agents/ez-ui-researcher.md +0 -353
  337. package/commands/ez/add-phase.md +0 -43
  338. package/commands/ez/add-todo.md +0 -47
  339. package/commands/ez/auth.md +0 -87
  340. package/commands/ez/autonomous.md +0 -41
  341. package/commands/ez/check-todos.md +0 -45
  342. package/commands/ez/cleanup.md +0 -18
  343. package/commands/ez/debug.md +0 -168
  344. package/commands/ez/health.md +0 -22
  345. package/commands/ez/insert-phase.md +0 -32
  346. package/commands/ez/join-discord.md +0 -18
  347. package/commands/ez/list-phase-assumptions.md +0 -46
  348. package/commands/ez/pause-work.md +0 -38
  349. package/commands/ez/plan-milestone-gaps.md +0 -34
  350. package/commands/ez/reapply-patches.md +0 -124
  351. package/commands/ez/remove-phase.md +0 -31
  352. package/commands/ez/research-phase.md +0 -190
  353. package/commands/ez/set-profile.md +0 -34
  354. package/commands/ez/stats.md +0 -18
  355. package/commands/ez/ui-phase.md +0 -34
  356. package/commands/ez/ui-review.md +0 -32
  357. package/commands/ez/validate-phase.md +0 -35
  358. package/ez-agents/templates/UI-SPEC.md +0 -100
  359. package/ez-agents/templates/VALIDATION.md +0 -76
  360. package/ez-agents/templates/context.md +0 -352
  361. package/ez-agents/templates/verification-report.md +0 -322
  362. package/ez-agents/workflows/research-phase.md +0 -74
  363. package/ez-agents/workflows/ui-phase.md +0 -290
  364. package/ez-agents/workflows/ui-review.md +0 -157
  365. package/ez-agents/workflows/validate-phase.md +0 -167
@@ -0,0 +1,622 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * BDD Validator — INVEST criteria checker and MoSCoW tagging utilities
5
+ *
6
+ * Validates Gherkin .feature files against:
7
+ * - INVEST criteria (Independent, Negotiable, Valuable, Estimable, Small, Testable)
8
+ * - MoSCoW priority tagging (@must/@should/@could/@wont)
9
+ * - Tier tagging (@mvp/@medium/@enterprise)
10
+ * - Structural correctness (Given/When/Then format)
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ // ─────────────────────────────────────────────
19
+ // Traceability
20
+ // ─────────────────────────────────────────────
21
+
22
+ /**
23
+ * Generate a deterministic scenario ID using FNV-1a hash
24
+ * @param {string} featureName
25
+ * @param {string} scenarioName
26
+ * @returns {string} ID like "SC-A1B2C3"
27
+ */
28
+ function generateScenarioId(featureName, scenarioName) {
29
+ const input = `${featureName}::${scenarioName}`.toLowerCase().replace(/\s+/g, '-');
30
+ // Simple non-crypto hash (FNV-1a)
31
+ let hash = 0x811c9dc5;
32
+ for (const char of input) {
33
+ hash ^= char.charCodeAt(0);
34
+ hash = (hash * 0x01000193) >>> 0;
35
+ }
36
+ return `SC-${hash.toString(16).toUpperCase().slice(0, 6)}`;
37
+ }
38
+
39
+ // ─────────────────────────────────────────────
40
+ // Parser
41
+ // ─────────────────────────────────────────────
42
+
43
+ /**
44
+ * Parse a .feature file into structured object
45
+ * @param {string} content - Raw file content
46
+ * @returns {{ feature: object, scenarios: object[], errors: string[] }}
47
+ */
48
+ function parseFeatureFile(content) {
49
+ const lines = content.split('\n');
50
+ const result = { feature: null, scenarios: [], errors: [] };
51
+
52
+ let currentScenario = null;
53
+ let currentStep = null;
54
+ let pendingTags = [];
55
+
56
+ for (let i = 0; i < lines.length; i++) {
57
+ const line = lines[i].trim();
58
+
59
+ // Skip blank lines and comments
60
+ if (!line || line.startsWith('#')) continue;
61
+
62
+ // Tags
63
+ if (line.startsWith('@')) {
64
+ const tags = line.split(/\s+/).filter(t => t.startsWith('@'));
65
+ pendingTags.push(...tags);
66
+ continue;
67
+ }
68
+
69
+ // Feature declaration
70
+ if (line.startsWith('Feature:')) {
71
+ result.feature = {
72
+ name: line.replace('Feature:', '').trim(),
73
+ tags: pendingTags.slice(),
74
+ lineNumber: i + 1
75
+ };
76
+ pendingTags = [];
77
+ continue;
78
+ }
79
+
80
+ // Background
81
+ if (line.startsWith('Background:')) {
82
+ currentScenario = { type: 'background', steps: [] };
83
+ pendingTags = [];
84
+ continue;
85
+ }
86
+
87
+ // Scenario
88
+ if (line.startsWith('Scenario:') || line.startsWith('Scenario Outline:')) {
89
+ if (currentScenario && currentScenario.type !== 'background') {
90
+ result.scenarios.push(currentScenario);
91
+ }
92
+ const scenarioName = line.replace(/^Scenario(?: Outline)?:/, '').trim();
93
+ const featureName = result.feature ? result.feature.name : 'unknown';
94
+ currentScenario = {
95
+ type: line.startsWith('Scenario Outline:') ? 'outline' : 'scenario',
96
+ name: scenarioName,
97
+ id: generateScenarioId(featureName, scenarioName),
98
+ tags: pendingTags.slice(),
99
+ steps: [],
100
+ lineNumber: i + 1
101
+ };
102
+ pendingTags = [];
103
+ currentStep = null;
104
+ continue;
105
+ }
106
+
107
+ // Steps
108
+ const stepMatch = line.match(/^(Given|When|Then|And|But)\s+(.+)$/);
109
+ if (stepMatch && currentScenario) {
110
+ const stepType = stepMatch[1];
111
+ // Resolve And/But to actual type based on previous step
112
+ let resolvedType = stepType;
113
+ if ((stepType === 'And' || stepType === 'But') && currentStep) {
114
+ resolvedType = currentStep.resolvedType;
115
+ } else if (stepType === 'And' || stepType === 'But') {
116
+ resolvedType = 'Given'; // fallback
117
+ }
118
+ currentStep = {
119
+ keyword: stepType,
120
+ resolvedType,
121
+ text: stepMatch[2],
122
+ lineNumber: i + 1
123
+ };
124
+ currentScenario.steps.push(currentStep);
125
+ continue;
126
+ }
127
+ }
128
+
129
+ // Push last scenario
130
+ if (currentScenario && currentScenario.type !== 'background') {
131
+ result.scenarios.push(currentScenario);
132
+ }
133
+
134
+ if (!result.feature) {
135
+ result.errors.push('No Feature: declaration found');
136
+ }
137
+
138
+ return result;
139
+ }
140
+
141
+ // ─────────────────────────────────────────────
142
+ // MoSCoW Validation
143
+ // ─────────────────────────────────────────────
144
+
145
+ const MOSCOW_TAGS = ['@must', '@should', '@could', '@wont'];
146
+ const TIER_TAGS = ['@mvp', '@medium', '@enterprise'];
147
+
148
+ /**
149
+ * Validate MoSCoW tags on a scenario
150
+ * @param {object} scenario
151
+ * @returns {{ valid: boolean, moscow: string|null, tier: string|null, issues: string[] }}
152
+ */
153
+ function validateMosCowTags(scenario) {
154
+ const issues = [];
155
+ const tags = scenario.tags || [];
156
+
157
+ const moscowTag = tags.find(t => MOSCOW_TAGS.includes(t));
158
+ const tierTag = tags.find(t => TIER_TAGS.includes(t));
159
+
160
+ if (!moscowTag) {
161
+ issues.push(`Scenario "${scenario.name}" missing MoSCoW tag (@must/@should/@could/@wont)`);
162
+ }
163
+
164
+ if (!tierTag && moscowTag !== '@wont') {
165
+ issues.push(`Scenario "${scenario.name}" missing tier tag (@mvp/@medium/@enterprise)`);
166
+ }
167
+
168
+ // Check consistency
169
+ if (moscowTag === '@must' && tierTag && tierTag !== '@mvp') {
170
+ issues.push(`Scenario "${scenario.name}": @must scenarios should be tagged @mvp (found ${tierTag})`);
171
+ }
172
+ if (moscowTag === '@could' && tierTag === '@mvp') {
173
+ issues.push(`Scenario "${scenario.name}": @could scenarios should not be tagged @mvp`);
174
+ }
175
+
176
+ return {
177
+ valid: issues.length === 0,
178
+ moscow: moscowTag || null,
179
+ tier: tierTag || null,
180
+ issues
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Count scenarios by MoSCoW priority
186
+ * @param {object[]} scenarios
187
+ * @returns {{ must: number, should: number, could: number, wont: number, untagged: number }}
188
+ */
189
+ function countByMosCow(scenarios) {
190
+ const counts = { must: 0, should: 0, could: 0, wont: 0, untagged: 0 };
191
+ for (const s of scenarios) {
192
+ const tag = (s.tags || []).find(t => MOSCOW_TAGS.includes(t));
193
+ if (!tag) counts.untagged++;
194
+ else counts[tag.replace('@', '')]++;
195
+ }
196
+ return counts;
197
+ }
198
+
199
+ // ─────────────────────────────────────────────
200
+ // INVEST Validation
201
+ // ─────────────────────────────────────────────
202
+
203
+ /**
204
+ * Validate INVEST criteria for a Feature + its scenarios
205
+ * @param {object} parsed - Result from parseFeatureFile
206
+ * @returns {{ score: number, max: number, dimensions: object[], passed: boolean }}
207
+ */
208
+ function validateINVEST(parsed) {
209
+ const dimensions = [];
210
+
211
+ // Independent — check for explicit dependency language
212
+ const dependencyWords = ['requires', 'depends on', 'after', 'before completing'];
213
+ const featureName = parsed.feature ? parsed.feature.name.toLowerCase() : '';
214
+ const hasDependencyLanguage = dependencyWords.some(w => featureName.includes(w));
215
+ dimensions.push({
216
+ dimension: 'Independent',
217
+ letter: 'I',
218
+ passed: !hasDependencyLanguage,
219
+ note: hasDependencyLanguage
220
+ ? 'Feature name suggests hard dependency — split or remove dependency language'
221
+ : 'No hard dependency language detected in Feature name'
222
+ });
223
+
224
+ // Negotiable — check that Then clauses don't over-specify implementation
225
+ const implementationWords = ['using react', 'via postgres', 'with redis', 'using jwt', 'via sendgrid'];
226
+ let thenClauses = [];
227
+ for (const s of parsed.scenarios) {
228
+ thenClauses.push(...s.steps.filter(st => st.resolvedType === 'Then').map(st => st.text.toLowerCase()));
229
+ }
230
+ const overSpecified = thenClauses.some(t => implementationWords.some(w => t.includes(w)));
231
+ dimensions.push({
232
+ dimension: 'Negotiable',
233
+ letter: 'N',
234
+ passed: !overSpecified,
235
+ note: overSpecified
236
+ ? 'Then clauses reference specific implementation technology — keep outcomes technology-agnostic'
237
+ : 'Then clauses describe outcomes, not implementation'
238
+ });
239
+
240
+ // Valuable — check Feature has "As a... I want... So that..." structure
241
+ const hasAsA = parsed.feature && /as a/i.test(parsed.feature.name);
242
+ dimensions.push({
243
+ dimension: 'Valuable',
244
+ letter: 'V',
245
+ passed: !!parsed.feature, // Feature declaration exists
246
+ note: parsed.feature
247
+ ? (hasAsA ? 'Feature has user-value statement' : 'Feature exists but consider adding "As a... I want... So that..."')
248
+ : 'No Feature declaration found'
249
+ });
250
+
251
+ // Estimable — check sufficient detail in steps
252
+ const avgStepsPerScenario = parsed.scenarios.length > 0
253
+ ? parsed.scenarios.reduce((sum, s) => sum + s.steps.length, 0) / parsed.scenarios.length
254
+ : 0;
255
+ const estimable = avgStepsPerScenario >= 2 && avgStepsPerScenario <= 10;
256
+ dimensions.push({
257
+ dimension: 'Estimable',
258
+ letter: 'E',
259
+ passed: estimable,
260
+ note: avgStepsPerScenario < 2
261
+ ? 'Scenarios have too few steps — add more detail for estimability'
262
+ : avgStepsPerScenario > 10
263
+ ? 'Scenarios are overly complex — split into smaller scenarios'
264
+ : `Average ${avgStepsPerScenario.toFixed(1)} steps per scenario — good estimability`
265
+ });
266
+
267
+ // Small — count @must scenarios (should be <= 8 for one phase)
268
+ const mustCount = parsed.scenarios.filter(s => (s.tags || []).includes('@must')).length;
269
+ const small = mustCount <= 8;
270
+ dimensions.push({
271
+ dimension: 'Small',
272
+ letter: 'S',
273
+ passed: small,
274
+ note: small
275
+ ? `${mustCount} @must scenarios — fits in one phase`
276
+ : `${mustCount} @must scenarios — consider splitting Feature across phases (max 8 recommended)`
277
+ });
278
+
279
+ // Testable — check all Then clauses have specific assertions
280
+ const vagueWords = ['should work', 'is correct', 'looks good', 'is happy', 'functions properly'];
281
+ const vagueThens = thenClauses.filter(t => vagueWords.some(w => t.includes(w)));
282
+ dimensions.push({
283
+ dimension: 'Testable',
284
+ letter: 'T',
285
+ passed: vagueThens.length === 0,
286
+ note: vagueThens.length === 0
287
+ ? 'All Then clauses have specific, testable assertions'
288
+ : `${vagueThens.length} vague Then clause(s) found — replace with specific assertions`
289
+ });
290
+
291
+ const score = dimensions.filter(d => d.passed).length;
292
+ return {
293
+ score,
294
+ max: dimensions.length,
295
+ dimensions,
296
+ passed: score === dimensions.length
297
+ };
298
+ }
299
+
300
+ // ─────────────────────────────────────────────
301
+ // Structural Validation
302
+ // ─────────────────────────────────────────────
303
+
304
+ /**
305
+ * Validate Given/When/Then structure of scenarios
306
+ * @param {object[]} scenarios
307
+ * @returns {{ valid: boolean, issues: string[] }}
308
+ */
309
+ function validateStructure(scenarios) {
310
+ const issues = [];
311
+
312
+ for (const scenario of scenarios) {
313
+ if (scenario.type === 'background') continue;
314
+
315
+ const steps = scenario.steps;
316
+ if (steps.length === 0) {
317
+ issues.push(`Scenario "${scenario.name}" has no steps`);
318
+ continue;
319
+ }
320
+
321
+ const hasGiven = steps.some(s => s.resolvedType === 'Given');
322
+ const hasWhen = steps.some(s => s.resolvedType === 'When');
323
+ const hasThen = steps.some(s => s.resolvedType === 'Then');
324
+
325
+ if (!hasWhen) {
326
+ issues.push(`Scenario "${scenario.name}": missing When step (the action being tested)`);
327
+ }
328
+ if (!hasThen) {
329
+ issues.push(`Scenario "${scenario.name}": missing Then step (the expected outcome)`);
330
+ }
331
+
332
+ // Check order: Given before When, When before Then
333
+ let givenIndex = steps.findLastIndex(s => s.resolvedType === 'Given');
334
+ let whenIndex = steps.findIndex(s => s.resolvedType === 'When');
335
+ let thenIndex = steps.findIndex(s => s.resolvedType === 'Then');
336
+
337
+ if (hasWhen && hasThen && whenIndex > thenIndex) {
338
+ issues.push(`Scenario "${scenario.name}": When step appears after Then step`);
339
+ }
340
+ if (hasGiven && hasWhen && givenIndex > whenIndex) {
341
+ // Only warn if Given appears much later
342
+ }
343
+ }
344
+
345
+ return { valid: issues.length === 0, issues };
346
+ }
347
+
348
+ // ─────────────────────────────────────────────
349
+ // Main Validation Entry Point
350
+ // ─────────────────────────────────────────────
351
+
352
+ /**
353
+ * Validate a single .feature file
354
+ * @param {string} filePath - Path to .feature file
355
+ * @returns {{ file: string, valid: boolean, invest: object, moscow: object, structure: object, summary: string }}
356
+ */
357
+ function validateFeatureFile(filePath) {
358
+ if (!fs.existsSync(filePath)) {
359
+ return {
360
+ file: filePath,
361
+ valid: false,
362
+ error: `File not found: ${filePath}`,
363
+ invest: null,
364
+ moscow: null,
365
+ structure: null,
366
+ summary: 'FILE_NOT_FOUND'
367
+ };
368
+ }
369
+
370
+ const content = fs.readFileSync(filePath, 'utf8');
371
+ const parsed = parseFeatureFile(content);
372
+
373
+ // Structural validation
374
+ const structure = validateStructure(parsed.scenarios);
375
+
376
+ // INVEST validation
377
+ const invest = validateINVEST(parsed);
378
+
379
+ // MoSCoW validation (per scenario)
380
+ const moscowResults = parsed.scenarios.map(s => validateMosCowTags(s));
381
+ const moscowIssues = moscowResults.flatMap(r => r.issues);
382
+ const moscow = {
383
+ valid: moscowIssues.length === 0,
384
+ issues: moscowIssues,
385
+ counts: countByMosCow(parsed.scenarios),
386
+ scenarios: moscowResults
387
+ };
388
+
389
+ const allIssues = [
390
+ ...parsed.errors,
391
+ ...structure.issues,
392
+ ...moscowIssues
393
+ ];
394
+
395
+ const valid = allIssues.length === 0 && invest.score >= 5; // Must pass at least 5/6 INVEST
396
+
397
+ return {
398
+ file: filePath,
399
+ valid,
400
+ invest,
401
+ moscow,
402
+ structure,
403
+ parseErrors: parsed.errors,
404
+ scenarioCount: parsed.scenarios.length,
405
+ summary: valid ? 'PASS' : `FAIL (${allIssues.length} issues, INVEST ${invest.score}/${invest.max})`
406
+ };
407
+ }
408
+
409
+ /**
410
+ * Validate all .feature files in a directory
411
+ * @param {string} dirPath - Directory to scan
412
+ * @param {object} options - { outputTraceability: boolean, cwd: string }
413
+ * @returns {{ valid: boolean, files: object[], totalScenarios: number, moscowCounts: object }}
414
+ */
415
+ function validateFeatureDirectory(dirPath, options = {}) {
416
+ const featureFiles = findFeatureFiles(dirPath);
417
+
418
+ if (featureFiles.length === 0) {
419
+ return {
420
+ valid: false,
421
+ files: [],
422
+ totalScenarios: 0,
423
+ moscowCounts: { must: 0, should: 0, could: 0, wont: 0, untagged: 0 },
424
+ error: `No .feature files found in ${dirPath}`
425
+ };
426
+ }
427
+
428
+ const results = featureFiles.map(f => validateFeatureFile(f));
429
+ const totalMoscow = { must: 0, should: 0, could: 0, wont: 0, untagged: 0 };
430
+
431
+ let totalScenarios = 0;
432
+ const allScenarios = []; // for traceability matrix
433
+
434
+ for (const r of results) {
435
+ totalScenarios += r.scenarioCount || 0;
436
+ if (r.moscow && r.moscow.counts) {
437
+ for (const [key, val] of Object.entries(r.moscow.counts)) {
438
+ totalMoscow[key] = (totalMoscow[key] || 0) + val;
439
+ }
440
+ }
441
+ // Collect scenario data for traceability
442
+ if (r.moscow && r.moscow.scenarios) {
443
+ const featureBase = path.basename(r.file);
444
+ r.moscow.scenarios.forEach((s, idx) => {
445
+ allScenarios.push({
446
+ id: s.id || `SC-${idx}`,
447
+ name: s.scenario ? s.scenario.name : `Scenario ${idx + 1}`,
448
+ moscow: s.moscow || 'untagged',
449
+ file: featureBase,
450
+ status: 'not-run'
451
+ });
452
+ });
453
+ }
454
+ }
455
+
456
+ // Write traceability matrix if requested or by default when .planning/ exists
457
+ const cwd = options.cwd || process.cwd();
458
+ const planningDir = path.join(cwd, '.planning');
459
+ if (allScenarios.length > 0 && fs.existsSync(planningDir)) {
460
+ try {
461
+ const matrixLines = [
462
+ '# BDD Traceability Matrix',
463
+ '',
464
+ '| ID | Scenario | MoSCoW | Feature File | Status |',
465
+ '|-----|----------|--------|-------------|--------|'
466
+ ];
467
+ for (const s of allScenarios) {
468
+ matrixLines.push(`| ${s.id} | ${s.name} | ${s.moscow} | ${s.file} | ⬜ ${s.status} |`);
469
+ }
470
+ matrixLines.push('');
471
+ matrixLines.push(`*Generated: ${new Date().toISOString()}*`);
472
+ fs.writeFileSync(
473
+ path.join(planningDir, 'bdd-traceability.md'),
474
+ matrixLines.join('\n'),
475
+ 'utf8'
476
+ );
477
+ } catch {
478
+ // Non-fatal — traceability matrix is best-effort
479
+ }
480
+ }
481
+
482
+ return {
483
+ valid: results.every(r => r.valid),
484
+ files: results,
485
+ totalScenarios,
486
+ moscowCounts: totalMoscow
487
+ };
488
+ }
489
+
490
+ /**
491
+ * Recursively find all .feature files
492
+ * @param {string} dirPath
493
+ * @returns {string[]}
494
+ */
495
+ function findFeatureFiles(dirPath) {
496
+ if (!fs.existsSync(dirPath)) return [];
497
+
498
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
499
+ const files = [];
500
+
501
+ for (const entry of entries) {
502
+ const fullPath = path.join(dirPath, entry.name);
503
+ if (entry.isDirectory()) {
504
+ files.push(...findFeatureFiles(fullPath));
505
+ } else if (entry.name.endsWith('.feature')) {
506
+ files.push(fullPath);
507
+ }
508
+ }
509
+
510
+ return files;
511
+ }
512
+
513
+ /**
514
+ * Format validation report as human-readable markdown
515
+ * @param {object} result - From validateFeatureDirectory or validateFeatureFile
516
+ * @returns {string}
517
+ */
518
+ function formatReport(result) {
519
+ const lines = [];
520
+
521
+ if (result.files) {
522
+ // Directory report
523
+ lines.push(`## BDD Validation Report`);
524
+ lines.push(`**Status:** ${result.valid ? '✓ PASS' : '✗ FAIL'}`);
525
+ lines.push(`**Files:** ${result.files.length} | **Scenarios:** ${result.totalScenarios}`);
526
+ lines.push('');
527
+ lines.push('### MoSCoW Distribution');
528
+ lines.push(`| Priority | Count |`);
529
+ lines.push(`|----------|-------|`);
530
+ for (const [key, val] of Object.entries(result.moscowCounts)) {
531
+ lines.push(`| @${key} | ${val} |`);
532
+ }
533
+ lines.push('');
534
+ lines.push('### File Results');
535
+ for (const f of result.files) {
536
+ const icon = f.valid ? '✓' : '✗';
537
+ lines.push(`- ${icon} \`${path.basename(f.file)}\` — ${f.summary}`);
538
+ if (!f.valid && f.parseErrors) {
539
+ for (const e of f.parseErrors) lines.push(` - **Parse Error:** ${e}`);
540
+ }
541
+ if (f.structure && f.structure.issues.length > 0) {
542
+ for (const e of f.structure.issues) lines.push(` - **Structure:** ${e}`);
543
+ }
544
+ if (f.moscow && f.moscow.issues.length > 0) {
545
+ for (const e of f.moscow.issues.slice(0, 3)) lines.push(` - **MoSCoW:** ${e}`);
546
+ }
547
+ }
548
+ } else {
549
+ // Single file report
550
+ lines.push(`## BDD Validation: ${path.basename(result.file)}`);
551
+ lines.push(`**Status:** ${result.valid ? '✓ PASS' : '✗ FAIL'}`);
552
+ lines.push(`**Scenarios:** ${result.scenarioCount}`);
553
+
554
+ if (result.invest) {
555
+ lines.push('');
556
+ lines.push('### INVEST Score');
557
+ lines.push(`**${result.invest.score}/${result.invest.max}** dimensions pass`);
558
+ for (const d of result.invest.dimensions) {
559
+ lines.push(`- ${d.passed ? '✓' : '✗'} **${d.letter}** — ${d.dimension}: ${d.note}`);
560
+ }
561
+ }
562
+ }
563
+
564
+ return lines.join('\n');
565
+ }
566
+
567
+ // ─────────────────────────────────────────────
568
+ // CLI Interface
569
+ // ─────────────────────────────────────────────
570
+
571
+ if (require.main === module) {
572
+ const args = process.argv.slice(2);
573
+ const cmd = args[0];
574
+ const target = args[1];
575
+
576
+ if (!cmd || !target) {
577
+ console.error('Usage: bdd-validator.cjs <validate-file|validate-dir|count-moscow> <path>');
578
+ process.exit(1);
579
+ }
580
+
581
+ try {
582
+ if (cmd === 'validate-file') {
583
+ const result = validateFeatureFile(target);
584
+ if (args.includes('--json')) {
585
+ console.log(JSON.stringify(result, null, 2));
586
+ } else {
587
+ console.log(formatReport(result));
588
+ process.exit(result.valid ? 0 : 1);
589
+ }
590
+ } else if (cmd === 'validate-dir') {
591
+ const result = validateFeatureDirectory(target);
592
+ if (args.includes('--json')) {
593
+ console.log(JSON.stringify(result, null, 2));
594
+ } else {
595
+ console.log(formatReport(result));
596
+ process.exit(result.valid ? 0 : 1);
597
+ }
598
+ } else if (cmd === 'count-moscow') {
599
+ const result = validateFeatureDirectory(target);
600
+ console.log(JSON.stringify(result.moscowCounts, null, 2));
601
+ } else {
602
+ console.error(`Unknown command: ${cmd}`);
603
+ process.exit(1);
604
+ }
605
+ } catch (err) {
606
+ console.error(`Error: ${err.message}`);
607
+ process.exit(1);
608
+ }
609
+ }
610
+
611
+ module.exports = {
612
+ parseFeatureFile,
613
+ validateFeatureFile,
614
+ validateFeatureDirectory,
615
+ validateMosCowTags,
616
+ validateINVEST,
617
+ validateStructure,
618
+ countByMosCow,
619
+ findFeatureFiles,
620
+ formatReport,
621
+ generateScenarioId
622
+ };