@polymorphism-tech/morph-spec 9.0.0 → 9.2.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 (345) hide show
  1. package/CLAUDE.md +8 -9
  2. package/README.md +38 -36
  3. package/bin/morph-spec.js +11 -63
  4. package/bin/validate.js +1 -39
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +1 -1
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/CLAUDE.md +2 -1
  9. package/framework/MORPH.md +3 -3
  10. package/framework/agents.json +19 -11
  11. package/framework/commands/morph-apply.md +1 -1
  12. package/framework/commands/morph-status.md +1 -1
  13. package/framework/docs/CLI.md +22 -31
  14. package/framework/evals/review-checklist.md +12 -2
  15. package/framework/hooks/README.md +8 -44
  16. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
  17. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
  18. package/framework/hooks/claude-code/core/chain.js +4 -65
  19. package/framework/hooks/claude-code/core/comment-guard.js +2 -16
  20. package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
  21. package/framework/hooks/claude-code/core/gate-guard.js +7 -67
  22. package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
  23. package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
  24. package/framework/hooks/claude-code/core/heavy-release.js +2 -13
  25. package/framework/hooks/claude-code/core/loop-detect.js +2 -31
  26. package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
  27. package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
  28. package/framework/hooks/claude-code/core/state-sync.js +12 -68
  29. package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
  30. package/framework/hooks/claude-code/core/validate-completion.js +11 -57
  31. package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
  32. package/framework/hooks/claude-code/core/worktree-context.js +10 -110
  33. package/framework/hooks/claude-code/core/worktree-create.js +6 -70
  34. package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
  35. package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
  36. package/framework/hooks/claude-code/statusline.sh +11 -11
  37. package/framework/hooks/shared/activity-logger.js +9 -89
  38. package/framework/hooks/shared/agent-reap.js +2 -17
  39. package/framework/hooks/shared/build-check.js +6 -75
  40. package/framework/hooks/shared/claim-registry.js +9 -119
  41. package/framework/hooks/shared/command-tokens.js +6 -51
  42. package/framework/hooks/shared/comment-rules.js +5 -20
  43. package/framework/hooks/shared/comment-scanner.js +6 -25
  44. package/framework/hooks/shared/comment-stripper.js +11 -85
  45. package/framework/hooks/shared/e2e-check.js +0 -0
  46. package/framework/hooks/shared/errata.js +2 -15
  47. package/framework/hooks/shared/feature-locator.js +11 -95
  48. package/framework/hooks/shared/frontend-locations.js +1 -3
  49. package/framework/hooks/shared/gate-question.js +1 -7
  50. package/framework/hooks/shared/git-exclude.js +3 -20
  51. package/framework/hooks/shared/git-paths.js +8 -104
  52. package/framework/hooks/shared/graph-freshness.js +22 -166
  53. package/framework/hooks/shared/graph-refresh.js +21 -149
  54. package/framework/hooks/shared/heavy-command.js +4 -12
  55. package/framework/hooks/shared/hook-response.js +8 -83
  56. package/framework/hooks/shared/morph-cli.js +8 -52
  57. package/framework/hooks/shared/payload-utils.js +10 -132
  58. package/framework/hooks/shared/phase-utils.js +1 -42
  59. package/framework/hooks/shared/redact-secrets.js +4 -15
  60. package/framework/hooks/shared/reparse-scan.js +6 -66
  61. package/framework/hooks/shared/slice-progress.js +4 -23
  62. package/framework/hooks/shared/source-files.js +4 -17
  63. package/framework/hooks/shared/state-reader.js +13 -183
  64. package/framework/hooks/shared/state-writer.js +8 -52
  65. package/framework/hooks/shared/stdin-reader.js +1 -11
  66. package/framework/hooks/shared/telemetry-logger.js +7 -67
  67. package/framework/hooks/shared/transcript-usage.js +7 -29
  68. package/framework/hooks/shared/worktree-root.js +7 -29
  69. package/framework/rules/csharp-standards.md +1 -1
  70. package/framework/rules/frontend-standards.md +1 -0
  71. package/framework/rules/hangfire-standards.md +1 -1
  72. package/framework/rules/neon-standards.md +1 -1
  73. package/framework/rules/nextjs-standards.md +1 -1
  74. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  75. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  76. package/framework/skills/core/morph-apply/SKILL.md +4 -4
  77. package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
  78. package/framework/skills/core/morph-machine/SKILL.md +1 -1
  79. package/framework/skills/core/morph-media/SKILL.md +1 -1
  80. package/framework/skills/core/morph-plan/SKILL.md +9 -7
  81. package/framework/skills/core/morph-review/SKILL.md +9 -6
  82. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  83. package/framework/skills/core/morph-uiux/SKILL.md +29 -12
  84. package/framework/standards/STANDARDS.json +36 -14
  85. package/framework/standards/STANDARDS.meta.json +16 -2
  86. package/framework/standards/ai-agents/agent-archetypes.md +2 -2
  87. package/framework/standards/ai-agents/agent-session.md +1 -1
  88. package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
  89. package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
  90. package/framework/standards/ai-agents/guardrails.md +1 -1
  91. package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
  92. package/framework/standards/ai-agents/observability-patterns.md +1 -1
  93. package/framework/standards/ai-agents/production.md +1 -1
  94. package/framework/standards/ai-agents/prompt-sources.md +2 -2
  95. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  96. package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
  97. package/framework/standards/ai-agents/setup.md +2 -2
  98. package/framework/standards/ai-agents/sweet-spot.md +2 -2
  99. package/framework/standards/ai-agents/tool-adapters.md +0 -2
  100. package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
  101. package/framework/standards/ai-agents/workflows.md +1 -1
  102. package/framework/standards/architecture/code-comments.md +1 -1
  103. package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
  104. package/framework/standards/backend/api/validation.md +1 -1
  105. package/framework/standards/backend/dotnet/testing.md +10 -4
  106. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
  107. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
  108. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
  109. package/framework/standards/data/nosql/cache/redis.md +1 -1
  110. package/framework/standards/data/nosql/cosmos-db.md +3 -3
  111. package/framework/standards/frontend/admin-app/forms.md +1 -1
  112. package/framework/standards/frontend/design-system/art-direction.md +88 -0
  113. package/framework/standards/frontend/design-system/premium-finish.md +77 -2
  114. package/framework/standards/frontend/nextjs/testing.md +10 -6
  115. package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
  116. package/framework/templates/.idea/morph-templates.xml +92 -0
  117. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  118. package/framework/templates/CLAUDE.md.core.template +3 -3
  119. package/framework/templates/IDE-SNIPPETS.md +262 -262
  120. package/framework/templates/REGISTRY.json +9 -9
  121. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  122. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  123. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  124. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  125. package/framework/templates/docs/plan.md +3 -1
  126. package/framework/templates/docs/ui-mockups.md +4 -0
  127. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  128. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  129. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  130. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  131. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  132. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  133. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  134. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  135. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  136. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  137. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  138. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  139. package/framework/templates/state.template.json +17 -17
  140. package/framework/templates/ui/design-system.css +226 -226
  141. package/package.json +1 -1
  142. package/src/commands/context/context-refresh.js +0 -16
  143. package/src/commands/dashboard/dashboard.js +2 -8
  144. package/src/commands/e2e/e2e.js +17 -104
  145. package/src/commands/graph/affected.js +0 -14
  146. package/src/commands/graph/build.js +11 -50
  147. package/src/commands/graph/explain.js +0 -16
  148. package/src/commands/graph/status.js +1 -12
  149. package/src/commands/mcp/mcp-setup.js +4 -62
  150. package/src/commands/observability/cost.js +5 -57
  151. package/src/commands/observability/telemetry.js +1 -14
  152. package/src/commands/project/branch.js +4 -45
  153. package/src/commands/project/claim.js +4 -48
  154. package/src/commands/project/doctor.js +48 -302
  155. package/src/commands/project/env.js +1 -27
  156. package/src/commands/project/fleet.js +3 -32
  157. package/src/commands/project/init.js +4 -68
  158. package/src/commands/project/install-plugin-cmd.js +0 -1
  159. package/src/commands/project/standards.js +1 -62
  160. package/src/commands/project/status.js +0 -53
  161. package/src/commands/project/update.js +31 -239
  162. package/src/commands/project/worktree.js +108 -882
  163. package/src/commands/scaffold/scaffold.js +0 -25
  164. package/src/commands/state/advance.js +14 -85
  165. package/src/commands/state/approve.js +15 -97
  166. package/src/commands/state/archive.js +12 -64
  167. package/src/commands/state/auto-approval.js +2 -5
  168. package/src/commands/state/create.js +22 -109
  169. package/src/commands/state/delete.js +11 -63
  170. package/src/commands/state/finish.js +98 -475
  171. package/src/commands/state/gate-check.js +22 -132
  172. package/src/commands/state/gate-decision.js +3 -39
  173. package/src/commands/state/handoff.js +3 -30
  174. package/src/commands/state/list.js +14 -84
  175. package/src/commands/state/report-check.js +8 -58
  176. package/src/commands/state/retype.js +4 -26
  177. package/src/commands/validation/index.js +0 -3
  178. package/src/commands/validation/mutate.js +11 -11
  179. package/src/commands/validation/plan-check.js +4 -2
  180. package/src/commands/validation/tests-audit.js +7 -0
  181. package/src/commands/validation/validate-feature.js +10 -97
  182. package/src/commands/validation/verify.js +30 -113
  183. package/src/core/paths/output-schema.js +2 -101
  184. package/src/core/state/feature-identity.js +1 -25
  185. package/src/core/state/gate-order.js +0 -27
  186. package/src/core/state/lifecycle-audit.js +8 -100
  187. package/src/core/state/state-manager.js +22 -304
  188. package/src/core/templates/template-registry.js +2 -97
  189. package/src/core/templates/template-renderer.js +13 -93
  190. package/src/core/templates/template-seed.js +0 -1
  191. package/src/core/work-types.js +1 -36
  192. package/src/dashboard/app/App.jsx +1 -8
  193. package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
  194. package/src/dashboard/app/components/CommandPalette.jsx +0 -7
  195. package/src/dashboard/app/components/DetailPanel.jsx +1 -5
  196. package/src/dashboard/app/components/EntityLink.jsx +0 -12
  197. package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
  198. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
  199. package/src/dashboard/app/components/Markdown.jsx +0 -9
  200. package/src/dashboard/app/components/PhaseRail.jsx +0 -11
  201. package/src/dashboard/app/components/SearchFilter.jsx +0 -4
  202. package/src/dashboard/app/components/Sidebar.jsx +0 -5
  203. package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
  204. package/src/dashboard/app/hooks/usePref.js +1 -5
  205. package/src/dashboard/app/lib/format.js +0 -2
  206. package/src/dashboard/app/lib/phases.js +1 -5
  207. package/src/dashboard/app/lib/project.js +0 -12
  208. package/src/dashboard/app/main.jsx +13 -13
  209. package/src/dashboard/app/pages/AgentMap.jsx +1 -8
  210. package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
  211. package/src/dashboard/app/pages/Features.jsx +1 -8
  212. package/src/dashboard/app/pages/Guide.jsx +0 -1
  213. package/src/dashboard/app/pages/HookInspector.jsx +2 -23
  214. package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
  215. package/src/dashboard/app/pages/Portfolio.jsx +0 -1
  216. package/src/dashboard/app/pages/RulesMap.jsx +1 -16
  217. package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
  218. package/src/dashboard/app/pages/Standards.jsx +2 -17
  219. package/src/dashboard/scanner/activity-scanner.js +10 -69
  220. package/src/dashboard/scanner/agents-scanner.js +1 -23
  221. package/src/dashboard/scanner/docs-scanner.js +0 -6
  222. package/src/dashboard/scanner/feature-status.js +2 -5
  223. package/src/dashboard/scanner/features-scanner.js +10 -66
  224. package/src/dashboard/scanner/hooks-scanner.js +5 -28
  225. package/src/dashboard/scanner/index.js +0 -4
  226. package/src/dashboard/scanner/portfolio.js +5 -29
  227. package/src/dashboard/scanner/project-api.js +8 -54
  228. package/src/dashboard/scanner/rules-scanner.js +1 -2
  229. package/src/dashboard/scanner/skills-scanner.js +2 -12
  230. package/src/dashboard/scanner/standards-scanner.js +2 -6
  231. package/src/dashboard/vite-plugin-morph-data.js +5 -19
  232. package/src/dashboard/vite.config.js +24 -24
  233. package/src/lib/ai/ai-checks.js +7 -52
  234. package/src/lib/ai/ai-project-scan.js +14 -108
  235. package/src/lib/ai/code-audit.js +7 -63
  236. package/src/lib/ai/pin-drift.js +8 -51
  237. package/src/lib/ai/registry-audit.js +11 -90
  238. package/src/lib/ci/ci-verify.js +4 -19
  239. package/src/lib/ci/github-ci.js +9 -37
  240. package/src/lib/ci/workflow-scaffold.js +14 -10
  241. package/src/lib/detectors/claude-config-detector.js +13 -74
  242. package/src/lib/detectors/index.js +4 -50
  243. package/src/lib/e2e/applicability.js +37 -0
  244. package/src/lib/e2e/compose-env.js +5 -66
  245. package/src/lib/e2e/stack-teardown.js +6 -40
  246. package/src/lib/generators/architecture-scanner.js +18 -156
  247. package/src/lib/generators/handoff-generator.js +9 -53
  248. package/src/lib/generators/scaffold-generator.js +5 -101
  249. package/src/lib/generators/settings-generator.js +4 -37
  250. package/src/lib/git/branch-classifier.js +11 -84
  251. package/src/lib/git/default-branch.js +14 -169
  252. package/src/lib/git/evidence-staleness.js +1 -0
  253. package/src/lib/git/gh.js +2 -17
  254. package/src/lib/git/pending-prs.js +6 -37
  255. package/src/lib/git/pr-files.js +3 -28
  256. package/src/lib/git/pr-index.js +3 -34
  257. package/src/lib/git/tree-fingerprint.js +3 -15
  258. package/src/lib/git/tree-state.js +6 -33
  259. package/src/lib/graph/god-nodes.js +2 -22
  260. package/src/lib/graph/graph-health.js +3 -24
  261. package/src/lib/graph/graph-loader.js +5 -56
  262. package/src/lib/graph/graphify-runtime.js +7 -37
  263. package/src/lib/graph/graphifyignore.js +8 -74
  264. package/src/lib/graph/sanitize.js +16 -136
  265. package/src/lib/heavy/cleanup.js +1 -1
  266. package/src/lib/installers/effects.js +8 -93
  267. package/src/lib/installers/mcp-installer.js +9 -169
  268. package/src/lib/installers/memory-installer.js +4 -27
  269. package/src/lib/installers/templates-reconcile.js +5 -60
  270. package/src/lib/mutation/adapters.js +6 -17
  271. package/src/lib/mutation/dotnet-introspection.js +20 -106
  272. package/src/lib/mutation/runner.js +23 -75
  273. package/src/lib/mutation/subset.js +1 -4
  274. package/src/lib/mutation/trx.js +0 -1
  275. package/src/lib/plan/artifact-vocabulary.js +5 -25
  276. package/src/lib/plan/checks.js +34 -33
  277. package/src/lib/plan/plan-parser.js +7 -8
  278. package/src/lib/projects/registry.js +7 -44
  279. package/src/lib/review-reports.js +11 -26
  280. package/src/lib/risk-detector.js +41 -173
  281. package/src/lib/specs/adr-index.js +6 -34
  282. package/src/lib/specs/frontmatter.js +2 -15
  283. package/src/lib/specs/gfm-table.js +1 -7
  284. package/src/lib/specs/revocations.js +1 -14
  285. package/src/lib/specs/screen-checklist.js +47 -124
  286. package/src/lib/stack/dotnet-build-ignores.js +2 -6
  287. package/src/lib/stack/stack-profile.js +1 -28
  288. package/src/lib/stack/stack-scan.js +1 -8
  289. package/src/lib/stack-filter.js +4 -27
  290. package/src/lib/standards/registry.js +1 -7
  291. package/src/lib/standards/sections.js +2 -13
  292. package/src/lib/standards/verified-against.js +19 -191
  293. package/src/lib/tasks/eval-node.js +21 -169
  294. package/src/lib/tasks/linux-runner.js +18 -142
  295. package/src/lib/tasks/test-nodes.js +43 -240
  296. package/src/lib/tasks/test-runner.js +43 -341
  297. package/src/lib/telemetry/metrics.js +7 -36
  298. package/src/lib/telemetry/telemetry-reader.js +13 -125
  299. package/src/lib/telemetry/verify-event.js +2 -30
  300. package/src/lib/tests-audit/audit.js +96 -0
  301. package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
  302. package/src/lib/validators/code-budget/index.js +0 -7
  303. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
  304. package/src/lib/validators/contracts/index.js +0 -6
  305. package/src/lib/validators/nextjs/index.js +0 -5
  306. package/src/lib/validators/nextjs/next-component-validator.js +4 -81
  307. package/src/lib/validators/packages/index.js +0 -6
  308. package/src/lib/validators/packages/package-validator.js +6 -56
  309. package/src/lib/validators/reuse/index.js +0 -8
  310. package/src/lib/validators/reuse/reuse-validator.js +11 -101
  311. package/src/lib/validators/reuse/symbol-scanner.js +6 -62
  312. package/src/lib/validators/traceability-check.js +5 -63
  313. package/src/lib/validators/validation-runner.js +21 -158
  314. package/src/lib/validators/verify-runner.js +85 -376
  315. package/src/lib/work-type-detector.js +5 -67
  316. package/src/lib/worktree/docker-audit.js +17 -136
  317. package/src/lib/worktree/env-allocator.js +10 -98
  318. package/src/lib/worktree/feature-truth.js +12 -115
  319. package/src/lib/worktree/finish-backup.js +32 -242
  320. package/src/lib/worktree/fleet-scanner.js +13 -95
  321. package/src/lib/worktree/ignored-triage.js +15 -91
  322. package/src/lib/worktree/vscode-settings.js +2 -3
  323. package/src/lib/worktree/worktree-audit.js +21 -213
  324. package/src/lib/worktree/worktree-include.js +3 -5
  325. package/src/scripts/global-install.js +3 -9
  326. package/src/scripts/install-plugin.js +1 -13
  327. package/src/scripts/setup-infra.js +17 -147
  328. package/src/utils/agents-installer.js +29 -276
  329. package/src/utils/banner.js +4 -25
  330. package/src/utils/claude-md-injector.js +1 -37
  331. package/src/utils/claude-settings-manager.js +1 -52
  332. package/src/utils/file-copier.js +11 -95
  333. package/src/utils/hooks-installer.js +21 -298
  334. package/src/utils/skills-installer.js +4 -101
  335. package/src/utils/version-checker.js +4 -83
  336. package/framework/commands/morph-proposal.md +0 -9
  337. package/framework/hooks/git/commit-msg/conventional-commits.sh +0 -33
  338. package/framework/hooks/git/pre-commit/agents.sh +0 -25
  339. package/framework/hooks/git/pre-commit/orchestrator.sh +0 -64
  340. package/framework/hooks/git/pre-push/run-tests.sh +0 -44
  341. package/src/commands/utils/index.js +0 -3
  342. package/src/lib/detectors/design-system-detector.js +0 -188
  343. package/src/lib/generators/design-system-generator.js +0 -508
  344. package/src/lib/specs/old-format.js +0 -68
  345. package/src/utils/agent-id-mapper.js +0 -11
@@ -1,29 +1,5 @@
1
- /**
2
- * `morph-spec report-check <feature> [slice...] [--json]` — the deterministic
3
- * half of the missing-report protocol.
4
- *
5
- * The slice report file is the sub-agent's authoritative return value; the
6
- * final message is a courtesy. This command answers what the orchestrator
7
- * needs when it resumes control, from evidence rather than from the agent's
8
- * own word: **did the WORK land, independently of whether the REPORT did?**
9
- *
10
- * verdict action
11
- * ───────────────────────────── ──────────────────
12
- * ok proceed
13
- * report-stale refresh-from-evidence
14
- * report-incomplete resume | complete-degraded
15
- * work-landed-report-missing accept-degraded
16
- * no-work-no-report redispatch
17
- * report-missing-evidence-unknown redispatch (conservative default)
18
- *
19
- * The observed evidence is keyed by the dispatch: the agent whose `agentId`
20
- * wrote the report owns the window, and its writes after the report make the
21
- * report stale. A report written inline (no sub-agent) is judged by its
22
- * markers only.
23
- *
24
- * Exit 0 only for `ok`. The output also carries `tree` (see tree-state.js),
25
- * information only — it never changes the verdict.
26
- */
1
+ // Judged from observed evidence, never the agent's word: did the WORK land, independently of
2
+ // the REPORT? The `tree` block is information only and never changes the verdict.
27
3
 
28
4
  import chalk from 'chalk';
29
5
  import { existsSync, readFileSync, statSync } from 'fs';
@@ -54,7 +30,7 @@ export function inspectReviewReports(cwd, feature) {
54
30
  return out;
55
31
  }
56
32
 
57
- /** Who reviews and on which model: the verifier routes to opus on prompt/model or media; security is dispatched only when required. */
33
+ // The verifier routes to opus on prompt/model or media; security is dispatched only when required.
58
34
  function reviewDispatch(cwd, feature, slices) {
59
35
  let changedFiles = [];
60
36
  try { changedFiles = changedPathsAgainst(detectDefaultBranch(cwd), cwd); } catch { changedFiles = []; }
@@ -74,20 +50,12 @@ function reviewDispatch(cwd, feature, slices) {
74
50
  };
75
51
  }
76
52
 
77
- /**
78
- * Two clocks: the report's mtime comes from the filesystem, the event's `ts`
79
- * from the telemetry hook. Two seconds absorb their skew and are far less
80
- * than any real work done after reporting.
81
- */
53
+ // Two clocks: the report mtime comes from the filesystem, the event `ts` from the hook. Two
54
+ // seconds absorb their skew and are far less than any real work done after reporting.
82
55
  export const STALE_TOLERANCE_MS = 2000;
83
56
 
84
57
  const slash = (p) => String(p || '').replace(/\\/g, '/');
85
58
 
86
- /**
87
- * The event target relative to the tree that logged it, the same shape as the
88
- * slice's `arquivos:`. `Write`/`Edit` targets are absolute; a target outside
89
- * that tree comes back as it was.
90
- */
91
59
  function toTreeRelative(target, treePath) {
92
60
  const norm = slash(target);
93
61
  const root = slash(treePath).replace(/\/+$/, '');
@@ -98,7 +66,6 @@ function toTreeRelative(target, treePath) {
98
66
 
99
67
  const reportSuffix = (feature, sliceId) => `.morph/features/${feature}/3-implement/reports/${sliceId}.md`.toLowerCase();
100
68
 
101
- /** Every successful Write/Edit of every tree of this repo, oldest first. */
102
69
  function loadWrites(cwd) {
103
70
  let trees = [];
104
71
  try { trees = loadTelemetryAllTrees(cwd); } catch { trees = []; }
@@ -115,14 +82,8 @@ function loadWrites(cwd) {
115
82
  return { available: trees.some((t) => t.events.length > 0), writes: dedupeEvents(events) };
116
83
  }
117
84
 
118
- /**
119
- * Evidence OBSERVED in the telemetry, never the agent's own word.
120
- *
121
- * The dispatch that owns the slice is the sub-agent that wrote its report —
122
- * the latest such write carrying an `agentId`; an orchestrator edit made
123
- * afterwards does not hide it. Without a report, any write to one of the
124
- * slice's files says the work landed.
125
- */
85
+ // The owning dispatch is the latest sub-agent write of the report; a later orchestrator edit
86
+ // does not hide it. Without a report, any write to one of the slice files says the work landed.
126
87
  function observedEvidence(telemetry, feature, slice) {
127
88
  if (!telemetry.available) return { available: false };
128
89
  const suffix = reportSuffix(feature, slice.id);
@@ -150,7 +111,6 @@ function observedEvidence(telemetry, feature, slice) {
150
111
  };
151
112
  }
152
113
 
153
- /** Is the report older than the last work its own dispatch observed? */
154
114
  function isReportStale(reportFsPath, observed) {
155
115
  if (!observed?.agentId || !observed.lastWriteAt) return false;
156
116
  try {
@@ -160,7 +120,6 @@ function isReportStale(reportFsPath, observed) {
160
120
  }
161
121
  }
162
122
 
163
- /** Combine report state + evidence into a verdict and the action it implies. */
164
123
  function decide(report, observed, stale) {
165
124
  if (report.exists && report.complete) {
166
125
  // Structural integrity answers "is the report complete?", never "is it
@@ -200,11 +159,7 @@ function snapshotPath(cwd, feature) {
200
159
  return join(cwd, '.morph', 'features', feature, '4-review', 'tree-before-eval.json');
201
160
  }
202
161
 
203
- /**
204
- * The tree block, compared with the snapshot taken before the review agents were
205
- * dispatched when there is one. `beforeEval` takes a new snapshot after the
206
- * comparison, so the output still shows what changed since the previous one.
207
- */
162
+ // `beforeEval` snapshots after comparing, so the output still shows what changed since the last one.
208
163
  function treeFor(cwd, feature, beforeEval, slices = []) {
209
164
  const path = snapshotPath(cwd, feature);
210
165
  let snapshot = null;
@@ -218,11 +173,6 @@ function treeFor(cwd, feature, beforeEval, slices = []) {
218
173
  return { tree, beforeEval: { at: next.at, files: Object.keys(next.files).length }, reviewModels };
219
174
  }
220
175
 
221
- /**
222
- * @param {string} feature
223
- * @param {string[]|undefined} sliceIds - audits only these slices; none audits every slice
224
- * @param {{ json?: boolean, cwd?: string, beforeEval?: boolean }} [options]
225
- */
226
176
  export function reportCheckCommand(feature, sliceIds, options = {}) {
227
177
  const cwd = options.cwd || process.cwd();
228
178
  const emit = (obj, code) => {
@@ -5,28 +5,8 @@ import { getFeature, setApprovalGate, updateFeature } from '../../core/state/sta
5
5
  import { WORK_TYPES, isValidWorkType, POLICY_APPROVER_PREFIX } from '../../core/work-types.js';
6
6
  import { renderPlanSkeleton } from '../../lib/plan/plan-template.js';
7
7
 
8
- /**
9
- * `morph-spec retype <feature> <newType>` — mid-flight work-type change with
10
- * asymmetric gate rules that preserve GENUINE human/auto approvals and only
11
- * ever undo policy stamps:
12
- *
13
- * - Blocked outright once Gate 3 (review) is approved — the feature already
14
- * shipped its pipeline, retyping it retroactively would be revisionist.
15
- * - Downgrade (fewer skipped gates → more skipped gates, e.g. feature/bug
16
- * → chore/hotfix) is blocked once the plan gate carries a genuine
17
- * (non-policy) approval — the plan was already accepted under the old
18
- * type's scrutiny; retyping around it would erase that judgment call.
19
- * - Upgrade (more skipped gates → fewer, e.g. chore/hotfix → feature/bug)
20
- * is always allowed before review: it only ever ADDS scrutiny.
21
- *
22
- * Applying a retype resets exactly the gates that were stamped
23
- * `policy:{oldType}` and the NEW type no longer skips (so the human is asked
24
- * again), stamps `policy:{newType}` on any of the new type's skippedGates
25
- * that aren't already approved (a prior genuine approval is left standing —
26
- * that's the "preserves human approvals" half of the contract), scaffolds
27
- * any phase folders the new type needs that don't exist yet, and appends an
28
- * audit entry to `workTypeHistory`.
29
- */
8
+ // Retype only ever undoes `policy:` stamps: blocked once review is approved; a downgrade
9
+ // is blocked once the plan carries a genuine approval; an upgrade only adds scrutiny.
30
10
  export async function retypeCommand(featureName, newType, options = {}) {
31
11
  try {
32
12
  if (!isValidWorkType(newType)) {
@@ -69,8 +49,7 @@ export async function retypeCommand(featureName, newType, options = {}) {
69
49
 
70
50
  const now = new Date().toISOString();
71
51
 
72
- // Reset gates that were stamped policy:{oldType} and the new type no
73
- // longer skips — a genuine (non-policy) approval is never touched.
52
+ // A genuine (non-policy) approval is never reset.
74
53
  const resetGates = [];
75
54
  for (const [gate, data] of Object.entries(feature.approvalGates || {})) {
76
55
  const isPolicyStamped = String(data?.approvedBy || '').startsWith(POLICY_APPROVER_PREFIX);
@@ -80,8 +59,7 @@ export async function retypeCommand(featureName, newType, options = {}) {
80
59
  }
81
60
  }
82
61
 
83
- // Stamp the new type's skipped gates — but only the ones not already
84
- // approved (preserves a prior genuine approval untouched).
62
+ // A gate already approved keeps its genuine approval.
85
63
  const stampedGates = [];
86
64
  for (const gate of newWorkType.skippedGates) {
87
65
  if (!feature.approvalGates?.[gate]?.approved) {
@@ -1,5 +1,2 @@
1
- /**
2
- * Validation Commands
3
- */
4
1
  export { validateFeatureCommand } from './validate-feature.js';
5
2
  export { verifyCommand } from './verify.js';
@@ -15,7 +15,7 @@ import { groupByNode, loadPlan } from '../../lib/mutation/plan.js';
15
15
  import { dotnetSubset, jsSubset, mutatedTypesOf, testFilesCiting, testProjectDirsOf } from '../../lib/mutation/subset.js';
16
16
  import { isFinished } from '../../dashboard/scanner/feature-status.js';
17
17
  import { dotnetAdapter, genericAdapter, spawnExec } from '../../lib/mutation/adapters.js';
18
- import { runMutation } from '../../lib/mutation/runner.js';
18
+ import { noSubsetReason, runMutation } from '../../lib/mutation/runner.js';
19
19
  import { renderSection, summarize } from '../../lib/mutation/report.js';
20
20
  import { readJournal, restoreSnapshot } from '../../lib/mutation/snapshot.js';
21
21
 
@@ -23,11 +23,8 @@ const EXIT = { ok: 0, failed: 1, usage: 2, busy: 3, unsafe: 4 };
23
23
  const KEEP_RUNS = 2;
24
24
  const DEFAULT_JOBS = 2;
25
25
 
26
- /**
27
- * One heavy slot per worker: the first waits like any heavy node, the others
28
- * are taken only if free right now. The run uses as many workers as it got,
29
- * so a busy machine degrades to sequential instead of queueing.
30
- */
26
+ // One heavy slot per worker: the first waits, the others are taken only if free now,
27
+ // so a busy machine degrades to sequential instead of queueing.
31
28
  export function workerGate({ root, feature, jobs }) {
32
29
  const config = readHeavyConfig(root);
33
30
  const gates = [createHeavyGate({ projectPath: root, label: 'mutate', feature, config })];
@@ -104,8 +101,7 @@ function testDirsOf(node, root, { projectsOnly = false } = {}) {
104
101
  return testProjectDirsOf(root, projectsOnly ? targets.filter((t) => !/\.slnx?$/i.test(t)) : targets, cwd);
105
102
  }
106
103
 
107
- function subsetFor({ node, group, root, options }) {
108
- if (options.subset === false) return { usable: false, reason: '--no-subset' };
104
+ function subsetFor({ node, group, root }) {
109
105
  const mutatedFiles = group.regions.map((r) => r.file);
110
106
  if (node.runner === 'dotnet') {
111
107
  const base = detectDefaultBranch(root);
@@ -196,6 +192,12 @@ export async function mutateCommand(featureArg, options = {}) {
196
192
  return fail(`${named}\nNó e2e: true declarado: ${e2eIds}. ${e2eManualProtocol(feature, uncited[0].e2e[0])}`, EXIT.usage);
197
193
  }
198
194
 
195
+ const subsets = groups.map((group) => subsetFor({ node: group.node, group, root }));
196
+ const noSubset = subsets.findIndex((s) => !s.usable);
197
+ if (noSubset >= 0) {
198
+ return fail(`suíte "${groups[noSubset].node.id}": ${noSubsetReason(subsets[noSubset].reason)}.`, EXIT.usage);
199
+ }
200
+
199
201
  if (!options.allowDirty) {
200
202
  const dirty = gitOut(root, ['status', '--porcelain', '--', ...new Set(regions.map((r) => r.file))]).trim();
201
203
  if (dirty) return fail(`há alteração não commitada nos arquivos a mutar:\n${dirty}\nCommite o fix ANTES de mutar (ou use --allow-dirty sabendo que o snapshot é o único caminho de volta).`, EXIT.usage);
@@ -210,13 +212,11 @@ export async function mutateCommand(featureArg, options = {}) {
210
212
  const exec = options.exec ?? spawnExec;
211
213
 
212
214
  const merged = { wallMs: 0, mutants: [], timings: [], restore: { files: [], ok: true, treeClean: true }, binary: { proof: 'unavailable', projects: [] }, baseline: {}, method: { schemata: 0, rebuild: 0, subset: {} } };
213
- const subsets = [];
214
215
  try {
215
216
  for (const [index, group] of groups.entries()) {
216
217
  const workDir = join(base, runId, `g${index}`);
217
218
  mkdirSync(workDir, { recursive: true });
218
- const subset = subsetFor({ node: group.node, group, root, options });
219
- subsets.push(subset);
219
+ const subset = subsets[index];
220
220
  const timeoutMs = options.timeoutMs ?? group.node.timeoutMs ?? 900_000;
221
221
  const make = group.node.runner === 'dotnet' ? dotnetAdapter : genericAdapter;
222
222
  const adapter = make({ node: group.node, root, workDir, exec, subset, timeoutMs });
@@ -5,7 +5,7 @@ import { atomicWriteJson } from '../../core/state/state-manager.js';
5
5
  import { isGateApproved } from '../../../framework/hooks/shared/state-reader.js';
6
6
  import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
7
7
  import { parsePlan } from '../../lib/plan/plan-parser.js';
8
- import { buildChecks, knownPersonas, parallelGroups, planHash, validatePlan } from '../../lib/plan/checks.js';
8
+ import { buildChecks, knownPersonas, parallelGroups, planHash, planRuleOptions, validatePlan } from '../../lib/plan/checks.js';
9
9
  import { knownSurfaces } from '../../lib/specs/screen-checklist.js';
10
10
  import { revocationWarnings } from '../../lib/specs/adr-index.js';
11
11
  import { assertOwnerTree, ownerTreeRefusalLines } from '../../lib/worktree/feature-truth.js';
@@ -25,7 +25,9 @@ export function runPlanCheck(featureName, cwd = process.cwd()) {
25
25
 
26
26
  const text = readFileSync(planPath, 'utf8');
27
27
  const plan = parsePlan(text);
28
- const { errors, warnings } = validatePlan(plan, { personas: knownPersonas(cwd), surfaces: knownSurfaces(cwd) });
28
+ const { errors, warnings } = validatePlan(plan, {
29
+ personas: knownPersonas(cwd), surfaces: knownSurfaces(cwd), ...planRuleOptions(cwd, featureName),
30
+ });
29
31
  warnings.push(...revocationWarnings(cwd, plan));
30
32
  const groups = errors.length === 0 ? parallelGroups(plan.slices) : [];
31
33
  const hash = planHash(text);
@@ -0,0 +1,7 @@
1
+ import { auditTests, renderTestsAudit } from '../../lib/tests-audit/audit.js';
2
+
3
+ export function testsAuditCommand(options = {}) {
4
+ const result = auditTests(process.cwd());
5
+ console.log(options.json ? JSON.stringify(result, null, 2) : renderTestsAudit(result));
6
+ if (!result.ok) process.exit(2);
7
+ }
@@ -1,34 +1,11 @@
1
- /**
2
- * MORPH-SPEC Feature Validation Command
3
- *
4
- * Content-aware validation of feature outputs at each phase.
5
- * Checks section presence, schema consistency, and contract correctness.
6
- *
7
- * Usage:
8
- * morph-spec validate <feature-name> [--phase <phase>] [--json] [--verbose]
9
- *
10
- * Example:
11
- * morph-spec validate user-portal --phase plan
12
- * morph-spec validate user-portal --json
13
- */
14
-
15
1
  import fs from 'fs';
16
2
  import path from 'path';
17
3
  import chalk from 'chalk';
18
4
  import { parsePlan } from '../../lib/plan/plan-parser.js';
19
- import { knownPersonas, planHash, validatePlan } from '../../lib/plan/checks.js';
20
-
21
- /**
22
- * Expected sections per markdown output — matched against *section markers*
23
- * (headings and line-start bold labels), not whole-file substrings; see
24
- * `extractSectionMarkers`. plan.md is judged by the plan parser instead.
25
- *
26
- * required → ERROR if absent.
27
- * recommended → WARNING if absent.
28
- * optional → silent. Tracked when present, never penalized.
29
- *
30
- * Each entry is a string or an array of acceptable aliases (any-of).
31
- */
5
+ import { knownPersonas, planHash, planRuleOptions, validatePlan } from '../../lib/plan/checks.js';
6
+
7
+ // Matched against section markers (headings and line-start bold labels), never whole-file
8
+ // substrings. required → ERROR, recommended → WARNING, optional → never penalized; an array is any-of.
32
9
  const OUTPUT_SECTIONS = {
33
10
  'verifier.md': {
34
11
  required: [
@@ -60,9 +37,6 @@ const OUTPUT_SECTIONS = {
60
37
  },
61
38
  };
62
39
 
63
- /**
64
- * Phase-to-output mapping (v5 NLH)
65
- */
66
40
  const PHASE_OUTPUTS = {
67
41
  plan: ['plan.md', 'checks.json'],
68
42
  uiux: ['design-system.md', 'mockups.md', 'components.md', 'flows.md'],
@@ -70,9 +44,6 @@ const PHASE_OUTPUTS = {
70
44
  review: ['verifier.md', 'reviewer.md'],
71
45
  };
72
46
 
73
- /**
74
- * Phase-to-folder mapping (v5 filesystem convention)
75
- */
76
47
  const PHASE_FOLDERS = {
77
48
  plan: '2-plan',
78
49
  uiux: '1-design',
@@ -80,20 +51,7 @@ const PHASE_FOLDERS = {
80
51
  review: '4-review',
81
52
  };
82
53
 
83
- /**
84
- * Extract section markers from markdown content.
85
- *
86
- * A marker is a line that structurally introduces a section:
87
- * - a markdown heading → `## Arquitetura da solução`
88
- * - a line-start bold label → `**Status:** Aceito`
89
- *
90
- * Bold labels are included on purpose (deviation from heading-only): reports
91
- * write `**Verdict:**` as a line-start bold label, not a heading. They are
92
- * structural, not prose, so this avoids the accidental-prose-match problem that
93
- * whole-file substring matching had.
94
- *
95
- * Returns lowercased marker texts.
96
- */
54
+ // Line-start bold labels count: reports write `**Verdict:**` as a label, not a heading.
97
55
  function extractSectionMarkers(content) {
98
56
  const markers = [];
99
57
  for (const rawLine of content.split('\n')) {
@@ -111,9 +69,6 @@ function extractSectionMarkers(content) {
111
69
  return markers;
112
70
  }
113
71
 
114
- /**
115
- * Validate a single output file
116
- */
117
72
  function validateOutput(featurePath, outputFile) {
118
73
  const filePath = path.join(featurePath, outputFile);
119
74
  const result = {
@@ -143,7 +98,6 @@ function validateOutput(featurePath, outputFile) {
143
98
  return result;
144
99
  }
145
100
 
146
- // Skip section checks for files without defined sections
147
101
  const sections = OUTPUT_SECTIONS[outputFile];
148
102
  if (!sections) {
149
103
  return result;
@@ -151,7 +105,6 @@ function validateOutput(featurePath, outputFile) {
151
105
 
152
106
  validateMarkdownSections(content, sections, result);
153
107
 
154
- // Size warnings
155
108
  if (result.lineCount < 10) {
156
109
  result.warnings.push(`File is very short (${result.lineCount} lines) — may be incomplete`);
157
110
  }
@@ -159,19 +112,17 @@ function validateOutput(featurePath, outputFile) {
159
112
  return result;
160
113
  }
161
114
 
162
- /**
163
- * plan.md is judged by the same parser and rules as `morph-spec plan-check`.
164
- * `planDir` is `.morph/features/{f}/2-plan`, so the project is four levels up.
165
- */
115
+ // `planDir` is `.morph/features/{f}/2-plan`, so the project is four levels up.
166
116
  function validatePlanOutput(content, planDir, result) {
167
117
  const plan = parsePlan(content);
168
- const { errors, warnings } = validatePlan(plan, { personas: knownPersonas(path.resolve(planDir, '..', '..', '..', '..')) });
118
+ const root = path.resolve(planDir, '..', '..', '..', '..');
119
+ const feature = path.basename(path.resolve(planDir, '..'));
120
+ const { errors, warnings } = validatePlan(plan, { personas: knownPersonas(root), ...planRuleOptions(root, feature) });
169
121
  result.errors.push(...errors);
170
122
  result.warnings.push(...warnings);
171
123
  result.sections.found.push(...plan.sections.filter((x) => x.key).map((x) => x.name));
172
124
  }
173
125
 
174
- /** checks.json must parse and carry the hash of the plan.md beside it. */
175
126
  function validateChecksOutput(content, planDir, result) {
176
127
  let doc;
177
128
  try {
@@ -189,16 +140,6 @@ function validateChecksOutput(content, planDir, result) {
189
140
  result.sections.found.push(`${doc.checks?.length ?? 0} check(s), ${doc.slices?.length ?? 0} slice(s)`);
190
141
  }
191
142
 
192
- /**
193
- * Validate markdown sections against section markers (headings + bold labels).
194
- *
195
- * Each `entry` is a string or array of acceptable aliases (any-of). A section
196
- * is present when at least one alias is a substring of at least one marker.
197
- *
198
- * required → missing produces an ERROR
199
- * recommended → missing produces a WARNING
200
- * optional → tracked when present, never penalized
201
- */
202
143
  function validateMarkdownSections(content, sections, result) {
203
144
  const markers = extractSectionMarkers(content);
204
145
  const match = (entry) => {
@@ -234,13 +175,6 @@ function validateMarkdownSections(content, sections, result) {
234
175
  }
235
176
  }
236
177
 
237
- /**
238
- * Resolve the list of `{ file, dir }` outputs to validate.
239
- *
240
- * @param {string} featurePath Absolute path to the feature directory.
241
- * @param {string|null} phase Phase key, or null to validate everything.
242
- * @returns {{ outputFiles?: Array<{file:string, dir:string}>, error?: string }}
243
- */
244
178
  function resolveOutputFiles(featurePath, phase) {
245
179
  if (phase) {
246
180
  const files = PHASE_OUTPUTS[phase];
@@ -250,7 +184,6 @@ function resolveOutputFiles(featurePath, phase) {
250
184
  return { outputFiles: files.map(file => ({ file, dir: searchPath })) };
251
185
  }
252
186
 
253
- // Validate every existing output across the feature root + phase folders.
254
187
  const outputFiles = [];
255
188
  const isOutput = name => name.endsWith('.md') || name.endsWith('.cs') || name === 'checks.json';
256
189
  const entries = fs.readdirSync(featurePath, { withFileTypes: true });
@@ -267,17 +200,7 @@ function resolveOutputFiles(featurePath, phase) {
267
200
  return { outputFiles };
268
201
  }
269
202
 
270
- /**
271
- * Pure validation core — no console output, no process.exit.
272
- *
273
- * @param {string} featurePath Absolute path to the feature directory.
274
- * @param {{ phase?: string }} [options]
275
- * @returns {{
276
- * feature: string, phase: string, valid: boolean, error?: string,
277
- * totalErrors: number, totalWarnings: number, totalFound: number,
278
- * results: object[]
279
- * }}
280
- */
203
+ /** Pure core: no console output, no process.exit. */
281
204
  export function runFeatureValidation(featurePath, options = {}) {
282
205
  const phase = options.phase || null;
283
206
  const feature = path.basename(featurePath);
@@ -312,9 +235,6 @@ export function runFeatureValidation(featurePath, options = {}) {
312
235
  };
313
236
  }
314
237
 
315
- /**
316
- * Main validate command (CLI wrapper around `runFeatureValidation`)
317
- */
318
238
  export async function validateFeatureCommand(featureName, options = {}) {
319
239
  if (!featureName) {
320
240
  console.error(chalk.red('Error: Feature name required'));
@@ -340,7 +260,6 @@ export async function validateFeatureCommand(featureName, options = {}) {
340
260
  const { results, totalErrors, totalWarnings, totalFound } = validation;
341
261
  const schemaWarnings = [];
342
262
 
343
- // JSON output mode
344
263
  if (options.json) {
345
264
  const output = {
346
265
  feature: featureName,
@@ -367,7 +286,6 @@ export async function validateFeatureCommand(featureName, options = {}) {
367
286
  return;
368
287
  }
369
288
 
370
- // Visual output
371
289
  console.log(chalk.cyan('\n┌────────────────────────────────────────────────────────────┐'));
372
290
  console.log(chalk.cyan('│ MORPH-SPEC FEATURE VALIDATOR │'));
373
291
  console.log(chalk.cyan('└────────────────────────────────────────────────────────────┘\n'));
@@ -376,7 +294,6 @@ export async function validateFeatureCommand(featureName, options = {}) {
376
294
  if (phase) console.log(chalk.gray(`Phase: ${phase}`));
377
295
  console.log('');
378
296
 
379
- // File-by-file results
380
297
  for (const result of results) {
381
298
  const symbol = result.exists
382
299
  ? (result.errors.length === 0 ? '✅' : '❌')
@@ -387,25 +304,21 @@ export async function validateFeatureCommand(featureName, options = {}) {
387
304
 
388
305
  console.log(`${symbol} ${name}${result.exists ? chalk.gray(` (${result.lineCount} lines)`) : ''}`);
389
306
 
390
- // Sections found
391
307
  if (result.sections.found.length > 0 && (options.verbose || result.errors.length > 0)) {
392
308
  result.sections.found.forEach(s => {
393
309
  console.log(chalk.green(` ✓ ${s}`));
394
310
  });
395
311
  }
396
312
 
397
- // Errors
398
313
  result.errors.forEach(err => {
399
314
  console.log(chalk.red(` ✗ ${err}`));
400
315
  });
401
316
 
402
- // Warnings
403
317
  result.warnings.forEach(warn => {
404
318
  console.log(chalk.yellow(` ⚠ ${warn}`));
405
319
  });
406
320
  }
407
321
 
408
- // Summary
409
322
  console.log('');
410
323
  console.log(chalk.gray('─'.repeat(60)));
411
324