@polymorphism-tech/morph-spec 9.0.0 → 9.1.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 (304) hide show
  1. package/CLAUDE.md +8 -9
  2. package/README.md +38 -36
  3. package/bin/morph-spec.js +4 -62
  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 +1 -1
  9. package/framework/MORPH.md +1 -1
  10. package/framework/agents.json +12 -4
  11. package/framework/commands/morph-apply.md +1 -1
  12. package/framework/commands/morph-status.md +1 -1
  13. package/framework/docs/CLI.md +6 -17
  14. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
  15. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
  16. package/framework/hooks/claude-code/core/chain.js +4 -65
  17. package/framework/hooks/claude-code/core/comment-guard.js +2 -16
  18. package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
  19. package/framework/hooks/claude-code/core/gate-guard.js +7 -67
  20. package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
  21. package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
  22. package/framework/hooks/claude-code/core/heavy-release.js +2 -13
  23. package/framework/hooks/claude-code/core/loop-detect.js +2 -31
  24. package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
  25. package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
  26. package/framework/hooks/claude-code/core/state-sync.js +11 -67
  27. package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
  28. package/framework/hooks/claude-code/core/validate-completion.js +11 -57
  29. package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
  30. package/framework/hooks/claude-code/core/worktree-context.js +10 -110
  31. package/framework/hooks/claude-code/core/worktree-create.js +6 -70
  32. package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
  33. package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
  34. package/framework/hooks/shared/activity-logger.js +9 -89
  35. package/framework/hooks/shared/agent-reap.js +2 -17
  36. package/framework/hooks/shared/build-check.js +6 -75
  37. package/framework/hooks/shared/claim-registry.js +9 -119
  38. package/framework/hooks/shared/command-tokens.js +6 -51
  39. package/framework/hooks/shared/comment-rules.js +5 -20
  40. package/framework/hooks/shared/comment-scanner.js +6 -25
  41. package/framework/hooks/shared/comment-stripper.js +11 -85
  42. package/framework/hooks/shared/e2e-check.js +0 -0
  43. package/framework/hooks/shared/errata.js +2 -15
  44. package/framework/hooks/shared/feature-locator.js +11 -95
  45. package/framework/hooks/shared/frontend-locations.js +1 -3
  46. package/framework/hooks/shared/gate-question.js +1 -7
  47. package/framework/hooks/shared/git-exclude.js +3 -20
  48. package/framework/hooks/shared/git-paths.js +8 -104
  49. package/framework/hooks/shared/graph-freshness.js +22 -166
  50. package/framework/hooks/shared/graph-refresh.js +21 -149
  51. package/framework/hooks/shared/heavy-command.js +4 -12
  52. package/framework/hooks/shared/hook-response.js +8 -83
  53. package/framework/hooks/shared/morph-cli.js +8 -52
  54. package/framework/hooks/shared/payload-utils.js +10 -132
  55. package/framework/hooks/shared/phase-utils.js +1 -42
  56. package/framework/hooks/shared/redact-secrets.js +4 -15
  57. package/framework/hooks/shared/reparse-scan.js +6 -66
  58. package/framework/hooks/shared/slice-progress.js +4 -23
  59. package/framework/hooks/shared/source-files.js +4 -17
  60. package/framework/hooks/shared/state-reader.js +13 -183
  61. package/framework/hooks/shared/state-writer.js +8 -52
  62. package/framework/hooks/shared/stdin-reader.js +1 -11
  63. package/framework/hooks/shared/telemetry-logger.js +7 -67
  64. package/framework/hooks/shared/transcript-usage.js +7 -29
  65. package/framework/hooks/shared/worktree-root.js +7 -29
  66. package/framework/rules/csharp-standards.md +1 -1
  67. package/framework/rules/frontend-standards.md +1 -0
  68. package/framework/rules/hangfire-standards.md +1 -1
  69. package/framework/rules/neon-standards.md +1 -1
  70. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  71. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  72. package/framework/skills/core/morph-apply/SKILL.md +2 -2
  73. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  74. package/framework/skills/core/morph-machine/SKILL.md +1 -1
  75. package/framework/skills/core/morph-media/SKILL.md +1 -1
  76. package/framework/skills/core/morph-plan/SKILL.md +2 -2
  77. package/framework/skills/core/morph-review/SKILL.md +1 -1
  78. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  79. package/framework/skills/core/morph-uiux/SKILL.md +29 -12
  80. package/framework/standards/STANDARDS.json +34 -12
  81. package/framework/standards/STANDARDS.meta.json +16 -2
  82. package/framework/standards/ai-agents/agent-archetypes.md +2 -2
  83. package/framework/standards/ai-agents/agent-session.md +1 -1
  84. package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
  85. package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
  86. package/framework/standards/ai-agents/guardrails.md +1 -1
  87. package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
  88. package/framework/standards/ai-agents/observability-patterns.md +1 -1
  89. package/framework/standards/ai-agents/production.md +1 -1
  90. package/framework/standards/ai-agents/prompt-sources.md +2 -2
  91. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  92. package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
  93. package/framework/standards/ai-agents/setup.md +2 -2
  94. package/framework/standards/ai-agents/sweet-spot.md +2 -2
  95. package/framework/standards/ai-agents/tool-adapters.md +0 -2
  96. package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
  97. package/framework/standards/ai-agents/workflows.md +1 -1
  98. package/framework/standards/architecture/code-comments.md +1 -1
  99. package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
  100. package/framework/standards/backend/api/validation.md +1 -1
  101. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
  102. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
  103. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
  104. package/framework/standards/data/nosql/cache/redis.md +1 -1
  105. package/framework/standards/data/nosql/cosmos-db.md +3 -3
  106. package/framework/standards/frontend/admin-app/forms.md +1 -1
  107. package/framework/standards/frontend/design-system/art-direction.md +88 -0
  108. package/framework/standards/frontend/design-system/premium-finish.md +77 -2
  109. package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
  110. package/framework/templates/CLAUDE.md.core.template +3 -3
  111. package/framework/templates/REGISTRY.json +9 -9
  112. package/framework/templates/docs/ui-mockups.md +4 -0
  113. package/package.json +1 -1
  114. package/src/commands/context/context-refresh.js +0 -16
  115. package/src/commands/dashboard/dashboard.js +2 -8
  116. package/src/commands/e2e/e2e.js +17 -104
  117. package/src/commands/graph/affected.js +0 -14
  118. package/src/commands/graph/build.js +11 -50
  119. package/src/commands/graph/explain.js +0 -16
  120. package/src/commands/graph/status.js +1 -12
  121. package/src/commands/mcp/mcp-setup.js +4 -62
  122. package/src/commands/observability/cost.js +5 -57
  123. package/src/commands/observability/telemetry.js +1 -14
  124. package/src/commands/project/branch.js +4 -45
  125. package/src/commands/project/claim.js +4 -48
  126. package/src/commands/project/doctor.js +48 -302
  127. package/src/commands/project/env.js +1 -27
  128. package/src/commands/project/fleet.js +3 -32
  129. package/src/commands/project/init.js +4 -68
  130. package/src/commands/project/install-plugin-cmd.js +0 -1
  131. package/src/commands/project/standards.js +1 -62
  132. package/src/commands/project/status.js +0 -53
  133. package/src/commands/project/update.js +31 -239
  134. package/src/commands/project/worktree.js +108 -882
  135. package/src/commands/scaffold/scaffold.js +0 -25
  136. package/src/commands/state/advance.js +14 -85
  137. package/src/commands/state/approve.js +14 -96
  138. package/src/commands/state/archive.js +12 -64
  139. package/src/commands/state/auto-approval.js +2 -5
  140. package/src/commands/state/create.js +22 -109
  141. package/src/commands/state/delete.js +11 -63
  142. package/src/commands/state/finish.js +98 -475
  143. package/src/commands/state/gate-check.js +22 -132
  144. package/src/commands/state/gate-decision.js +3 -39
  145. package/src/commands/state/handoff.js +3 -30
  146. package/src/commands/state/list.js +14 -84
  147. package/src/commands/state/report-check.js +8 -58
  148. package/src/commands/state/retype.js +4 -26
  149. package/src/commands/validation/index.js +0 -3
  150. package/src/commands/validation/mutate.js +2 -5
  151. package/src/commands/validation/validate-feature.js +5 -94
  152. package/src/commands/validation/verify.js +15 -98
  153. package/src/core/paths/output-schema.js +2 -101
  154. package/src/core/state/feature-identity.js +1 -25
  155. package/src/core/state/gate-order.js +0 -27
  156. package/src/core/state/lifecycle-audit.js +8 -100
  157. package/src/core/state/state-manager.js +22 -304
  158. package/src/core/templates/template-registry.js +2 -97
  159. package/src/core/templates/template-renderer.js +13 -93
  160. package/src/core/templates/template-seed.js +0 -1
  161. package/src/core/work-types.js +1 -36
  162. package/src/dashboard/app/App.jsx +1 -8
  163. package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
  164. package/src/dashboard/app/components/CommandPalette.jsx +0 -7
  165. package/src/dashboard/app/components/DetailPanel.jsx +1 -5
  166. package/src/dashboard/app/components/EntityLink.jsx +0 -12
  167. package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
  168. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
  169. package/src/dashboard/app/components/Markdown.jsx +0 -9
  170. package/src/dashboard/app/components/PhaseRail.jsx +0 -11
  171. package/src/dashboard/app/components/SearchFilter.jsx +0 -4
  172. package/src/dashboard/app/components/Sidebar.jsx +0 -5
  173. package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
  174. package/src/dashboard/app/hooks/usePref.js +1 -5
  175. package/src/dashboard/app/lib/format.js +0 -2
  176. package/src/dashboard/app/lib/phases.js +1 -5
  177. package/src/dashboard/app/lib/project.js +0 -12
  178. package/src/dashboard/app/pages/AgentMap.jsx +1 -8
  179. package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
  180. package/src/dashboard/app/pages/Features.jsx +1 -8
  181. package/src/dashboard/app/pages/Guide.jsx +0 -1
  182. package/src/dashboard/app/pages/HookInspector.jsx +2 -23
  183. package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
  184. package/src/dashboard/app/pages/Portfolio.jsx +0 -1
  185. package/src/dashboard/app/pages/RulesMap.jsx +1 -16
  186. package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
  187. package/src/dashboard/app/pages/Standards.jsx +2 -17
  188. package/src/dashboard/scanner/activity-scanner.js +10 -69
  189. package/src/dashboard/scanner/agents-scanner.js +1 -23
  190. package/src/dashboard/scanner/docs-scanner.js +0 -6
  191. package/src/dashboard/scanner/feature-status.js +2 -5
  192. package/src/dashboard/scanner/features-scanner.js +10 -66
  193. package/src/dashboard/scanner/hooks-scanner.js +5 -28
  194. package/src/dashboard/scanner/index.js +0 -4
  195. package/src/dashboard/scanner/portfolio.js +5 -29
  196. package/src/dashboard/scanner/project-api.js +8 -54
  197. package/src/dashboard/scanner/rules-scanner.js +1 -2
  198. package/src/dashboard/scanner/skills-scanner.js +2 -12
  199. package/src/dashboard/scanner/standards-scanner.js +2 -6
  200. package/src/dashboard/vite-plugin-morph-data.js +5 -19
  201. package/src/lib/ai/ai-checks.js +7 -52
  202. package/src/lib/ai/ai-project-scan.js +14 -108
  203. package/src/lib/ai/code-audit.js +7 -63
  204. package/src/lib/ai/pin-drift.js +8 -51
  205. package/src/lib/ai/registry-audit.js +11 -90
  206. package/src/lib/ci/ci-verify.js +4 -19
  207. package/src/lib/ci/github-ci.js +9 -37
  208. package/src/lib/ci/workflow-scaffold.js +2 -10
  209. package/src/lib/detectors/claude-config-detector.js +13 -74
  210. package/src/lib/detectors/index.js +4 -50
  211. package/src/lib/e2e/compose-env.js +5 -66
  212. package/src/lib/e2e/stack-teardown.js +6 -40
  213. package/src/lib/generators/architecture-scanner.js +18 -156
  214. package/src/lib/generators/handoff-generator.js +9 -53
  215. package/src/lib/generators/scaffold-generator.js +5 -101
  216. package/src/lib/generators/settings-generator.js +4 -37
  217. package/src/lib/git/branch-classifier.js +11 -84
  218. package/src/lib/git/default-branch.js +14 -169
  219. package/src/lib/git/evidence-staleness.js +1 -0
  220. package/src/lib/git/gh.js +2 -17
  221. package/src/lib/git/pending-prs.js +6 -37
  222. package/src/lib/git/pr-files.js +3 -28
  223. package/src/lib/git/pr-index.js +3 -34
  224. package/src/lib/git/tree-fingerprint.js +3 -15
  225. package/src/lib/git/tree-state.js +6 -33
  226. package/src/lib/graph/god-nodes.js +2 -22
  227. package/src/lib/graph/graph-health.js +3 -24
  228. package/src/lib/graph/graph-loader.js +5 -56
  229. package/src/lib/graph/graphify-runtime.js +7 -37
  230. package/src/lib/graph/graphifyignore.js +8 -74
  231. package/src/lib/graph/sanitize.js +16 -136
  232. package/src/lib/heavy/cleanup.js +1 -1
  233. package/src/lib/installers/effects.js +8 -93
  234. package/src/lib/installers/mcp-installer.js +9 -169
  235. package/src/lib/installers/memory-installer.js +4 -27
  236. package/src/lib/installers/templates-reconcile.js +5 -60
  237. package/src/lib/mutation/dotnet-introspection.js +20 -106
  238. package/src/lib/mutation/subset.js +1 -4
  239. package/src/lib/plan/artifact-vocabulary.js +5 -25
  240. package/src/lib/plan/checks.js +5 -31
  241. package/src/lib/plan/plan-parser.js +1 -6
  242. package/src/lib/projects/registry.js +7 -44
  243. package/src/lib/review-reports.js +5 -23
  244. package/src/lib/risk-detector.js +39 -173
  245. package/src/lib/specs/adr-index.js +6 -34
  246. package/src/lib/specs/frontmatter.js +2 -15
  247. package/src/lib/specs/gfm-table.js +1 -7
  248. package/src/lib/specs/revocations.js +1 -14
  249. package/src/lib/specs/screen-checklist.js +47 -124
  250. package/src/lib/stack/dotnet-build-ignores.js +2 -6
  251. package/src/lib/stack/stack-profile.js +1 -28
  252. package/src/lib/stack/stack-scan.js +1 -8
  253. package/src/lib/stack-filter.js +4 -27
  254. package/src/lib/standards/registry.js +1 -7
  255. package/src/lib/standards/sections.js +2 -13
  256. package/src/lib/standards/verified-against.js +19 -191
  257. package/src/lib/tasks/eval-node.js +21 -169
  258. package/src/lib/tasks/linux-runner.js +18 -142
  259. package/src/lib/tasks/test-nodes.js +43 -240
  260. package/src/lib/tasks/test-runner.js +43 -341
  261. package/src/lib/telemetry/metrics.js +7 -36
  262. package/src/lib/telemetry/telemetry-reader.js +13 -125
  263. package/src/lib/telemetry/verify-event.js +2 -30
  264. package/src/lib/validators/code-budget/code-budget-validator.js +3 -43
  265. package/src/lib/validators/code-budget/index.js +0 -7
  266. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
  267. package/src/lib/validators/contracts/index.js +0 -6
  268. package/src/lib/validators/nextjs/index.js +0 -5
  269. package/src/lib/validators/nextjs/next-component-validator.js +4 -81
  270. package/src/lib/validators/packages/index.js +0 -6
  271. package/src/lib/validators/packages/package-validator.js +6 -56
  272. package/src/lib/validators/reuse/index.js +0 -8
  273. package/src/lib/validators/reuse/reuse-validator.js +11 -101
  274. package/src/lib/validators/reuse/symbol-scanner.js +6 -62
  275. package/src/lib/validators/traceability-check.js +5 -63
  276. package/src/lib/validators/validation-runner.js +20 -125
  277. package/src/lib/validators/verify-runner.js +52 -358
  278. package/src/lib/work-type-detector.js +5 -67
  279. package/src/lib/worktree/docker-audit.js +17 -136
  280. package/src/lib/worktree/env-allocator.js +10 -98
  281. package/src/lib/worktree/feature-truth.js +12 -115
  282. package/src/lib/worktree/finish-backup.js +32 -242
  283. package/src/lib/worktree/fleet-scanner.js +13 -95
  284. package/src/lib/worktree/ignored-triage.js +15 -91
  285. package/src/lib/worktree/vscode-settings.js +2 -3
  286. package/src/lib/worktree/worktree-audit.js +21 -213
  287. package/src/lib/worktree/worktree-include.js +3 -5
  288. package/src/scripts/global-install.js +3 -9
  289. package/src/scripts/install-plugin.js +1 -13
  290. package/src/scripts/setup-infra.js +17 -147
  291. package/src/utils/agents-installer.js +28 -275
  292. package/src/utils/banner.js +4 -25
  293. package/src/utils/claude-md-injector.js +1 -37
  294. package/src/utils/claude-settings-manager.js +1 -52
  295. package/src/utils/file-copier.js +11 -95
  296. package/src/utils/hooks-installer.js +21 -298
  297. package/src/utils/skills-installer.js +4 -101
  298. package/src/utils/version-checker.js +4 -83
  299. package/framework/commands/morph-proposal.md +0 -9
  300. package/src/commands/utils/index.js +0 -3
  301. package/src/lib/detectors/design-system-detector.js +0 -188
  302. package/src/lib/generators/design-system-generator.js +0 -508
  303. package/src/lib/specs/old-format.js +0 -68
  304. 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';
@@ -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 })];
@@ -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
5
  import { knownPersonas, planHash, validatePlan } from '../../lib/plan/checks.js';
20
6
 
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
- */
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,10 +112,7 @@ 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
118
  const { errors, warnings } = validatePlan(plan, { personas: knownPersonas(path.resolve(planDir, '..', '..', '..', '..')) });
@@ -171,7 +121,6 @@ function validatePlanOutput(content, planDir, result) {
171
121
  result.sections.found.push(...plan.sections.filter((x) => x.key).map((x) => x.name));
172
122
  }
173
123
 
174
- /** checks.json must parse and carry the hash of the plan.md beside it. */
175
124
  function validateChecksOutput(content, planDir, result) {
176
125
  let doc;
177
126
  try {
@@ -189,16 +138,6 @@ function validateChecksOutput(content, planDir, result) {
189
138
  result.sections.found.push(`${doc.checks?.length ?? 0} check(s), ${doc.slices?.length ?? 0} slice(s)`);
190
139
  }
191
140
 
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
141
  function validateMarkdownSections(content, sections, result) {
203
142
  const markers = extractSectionMarkers(content);
204
143
  const match = (entry) => {
@@ -234,13 +173,6 @@ function validateMarkdownSections(content, sections, result) {
234
173
  }
235
174
  }
236
175
 
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
176
  function resolveOutputFiles(featurePath, phase) {
245
177
  if (phase) {
246
178
  const files = PHASE_OUTPUTS[phase];
@@ -250,7 +182,6 @@ function resolveOutputFiles(featurePath, phase) {
250
182
  return { outputFiles: files.map(file => ({ file, dir: searchPath })) };
251
183
  }
252
184
 
253
- // Validate every existing output across the feature root + phase folders.
254
185
  const outputFiles = [];
255
186
  const isOutput = name => name.endsWith('.md') || name.endsWith('.cs') || name === 'checks.json';
256
187
  const entries = fs.readdirSync(featurePath, { withFileTypes: true });
@@ -267,17 +198,7 @@ function resolveOutputFiles(featurePath, phase) {
267
198
  return { outputFiles };
268
199
  }
269
200
 
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
- */
201
+ /** Pure core: no console output, no process.exit. */
281
202
  export function runFeatureValidation(featurePath, options = {}) {
282
203
  const phase = options.phase || null;
283
204
  const feature = path.basename(featurePath);
@@ -312,9 +233,6 @@ export function runFeatureValidation(featurePath, options = {}) {
312
233
  };
313
234
  }
314
235
 
315
- /**
316
- * Main validate command (CLI wrapper around `runFeatureValidation`)
317
- */
318
236
  export async function validateFeatureCommand(featureName, options = {}) {
319
237
  if (!featureName) {
320
238
  console.error(chalk.red('Error: Feature name required'));
@@ -340,7 +258,6 @@ export async function validateFeatureCommand(featureName, options = {}) {
340
258
  const { results, totalErrors, totalWarnings, totalFound } = validation;
341
259
  const schemaWarnings = [];
342
260
 
343
- // JSON output mode
344
261
  if (options.json) {
345
262
  const output = {
346
263
  feature: featureName,
@@ -367,7 +284,6 @@ export async function validateFeatureCommand(featureName, options = {}) {
367
284
  return;
368
285
  }
369
286
 
370
- // Visual output
371
287
  console.log(chalk.cyan('\n┌────────────────────────────────────────────────────────────┐'));
372
288
  console.log(chalk.cyan('│ MORPH-SPEC FEATURE VALIDATOR │'));
373
289
  console.log(chalk.cyan('└────────────────────────────────────────────────────────────┘\n'));
@@ -376,7 +292,6 @@ export async function validateFeatureCommand(featureName, options = {}) {
376
292
  if (phase) console.log(chalk.gray(`Phase: ${phase}`));
377
293
  console.log('');
378
294
 
379
- // File-by-file results
380
295
  for (const result of results) {
381
296
  const symbol = result.exists
382
297
  ? (result.errors.length === 0 ? '✅' : '❌')
@@ -387,25 +302,21 @@ export async function validateFeatureCommand(featureName, options = {}) {
387
302
 
388
303
  console.log(`${symbol} ${name}${result.exists ? chalk.gray(` (${result.lineCount} lines)`) : ''}`);
389
304
 
390
- // Sections found
391
305
  if (result.sections.found.length > 0 && (options.verbose || result.errors.length > 0)) {
392
306
  result.sections.found.forEach(s => {
393
307
  console.log(chalk.green(` ✓ ${s}`));
394
308
  });
395
309
  }
396
310
 
397
- // Errors
398
311
  result.errors.forEach(err => {
399
312
  console.log(chalk.red(` ✗ ${err}`));
400
313
  });
401
314
 
402
- // Warnings
403
315
  result.warnings.forEach(warn => {
404
316
  console.log(chalk.yellow(` ⚠ ${warn}`));
405
317
  });
406
318
  }
407
319
 
408
- // Summary
409
320
  console.log('');
410
321
  console.log(chalk.gray('─'.repeat(60)));
411
322
 
@@ -14,7 +14,6 @@ import { afterHeavyRun } from '../../lib/heavy/cleanup.js';
14
14
  import { readHeavyConfig } from '../../../framework/hooks/shared/heavy-config.js';
15
15
  import { runCiVerify, CiUsageError, reusableLocalStamp } from '../../lib/ci/ci-verify.js';
16
16
 
17
- /** ✓ pass · ✗ fail · – skip */
18
17
  function statusIcon(status) {
19
18
  if (status === 'pass') return chalk.green('✓');
20
19
  if (status === 'fail') return chalk.red('✗');
@@ -69,27 +68,9 @@ function formatVerifyResult(result) {
69
68
 
70
69
  const samePath = (a, b) => canonicalPath(a).replace(/\\/g, '/') === canonicalPath(b).replace(/\\/g, '/');
71
70
 
72
- /**
73
- * Persist only in the tree that OWNS the feature — the same predicate
74
- * `list`/`handoff`/the mutator guard read (`resolveFeatureTree`), asked from
75
- * the PRIMARY ROOT so every tree gets the same answer. Any other tree (a
76
- * sub-agent's `morph-task/*` tree branched from `morph/{f}`, a scratch
77
- * checkout) must never write feature.json: its copy is not the one the gates
78
- * read, and the write would merge-conflict with the owner's.
79
- *
80
- * The old rule read only the root's `worktree.path` pointer, which a feature
81
- * created INSIDE its worktree never has: a `worktrees/homolog-v2` on
82
- * `morph/chat-demo-simulacao` measured `unregistered-worktree` on 11/11 runs,
83
- * and `repeatOf` (computed only when persisting) never fired. Git's worktree
84
- * registry names that tree as the owner.
85
- *
86
- * The root is not special: with a live worktree owning the feature, the
87
- * root's copy is the birth-instant skeleton and a stamp there is drift —
88
- * `owner-tree-elsewhere`, the verify side of `assertOwnerTree`.
89
- *
90
- * @param {{ cwd: string, featureName: string, noPersist: boolean }} params
91
- * @returns {{ persist: boolean, reason: string|null }}
92
- */
71
+ // Persists only in the tree that OWNS the feature (`resolveFeatureTree`, asked from the PRIMARY
72
+ // ROOT so every tree agrees): another tree's copy is not what the gates read, and writing it
73
+ // would merge-conflict with the owner's.
93
74
  function decidePersistence({ cwd, featureName, noPersist }) {
94
75
  if (noPersist) return { persist: false, reason: 'no-persist-flag' };
95
76
 
@@ -106,17 +87,8 @@ function baselineStampBlock(baseline) {
106
87
  return `record-baseline: a contagem caiu em ${fell.map((u) => `${u.id} (${u.from} → ${u.to})`).join(', ')}`;
107
88
  }
108
89
 
109
- /**
110
- * The per-node status map, in the shape `feature.schema.json` fixes for BOTH
111
- * writers of it — the history entry (`verifications.feature`) and the
112
- * singular gate stamp (`verification.nodes`). One function because they were
113
- * two literal copies twenty-five lines apart: a fifth node meant remembering
114
- * both, and a stamp missing a node the history has is precisely the kind of
115
- * half-truth the gate reads as fact.
116
- *
117
- * @param {{ nodes: Record<string, { status: string }> }} result - runVerify() output
118
- * @returns {{ build: string, tests: string, validators: string, e2e: string }}
119
- */
90
+ // The single writer of both node maps the schema fixes (`verification.nodes` and the history
91
+ // entry): a stamp missing a node the history has is a half-truth the gate reads as fact.
120
92
  function nodeStatuses(result) {
121
93
  return {
122
94
  build: result.nodes.build.status,
@@ -127,25 +99,8 @@ function nodeStatuses(result) {
127
99
  };
128
100
  }
129
101
 
130
- /**
131
- * The status of the eval suites, pulled OUT of the tests rollup.
132
- *
133
- * The rollup cannot answer the question Gate 3 asks. A red eval and a red unit
134
- * test both arrive as `tests: 'fail'`, and — worse — an eval node that was
135
- * SKIPPED (`--filter`) leaves the rollup green, so a gate reading `tests` alone
136
- * cannot tell "the eval ran and passed" from "the eval never ran".
137
- *
138
- * Four values, and the distinction between the last two is the whole point:
139
- * - `'pass'` / `'fail'`: every declared eval node agreed, or at least one did
140
- * not. Same pessimistic rollup rule the tests node already uses;
141
- * - `'skip'`: eval nodes ARE declared, but this run did not execute them;
142
- * - `null`: this project declares NO eval node. Not a skip — there is nothing
143
- * to skip. `risk-detector.js` pauses on the two for different reasons,
144
- * because they call for different actions.
145
- *
146
- * @param {{nodes: {tests: {runs?: Array}}}} result - runVerify() output
147
- * @returns {'pass'|'fail'|'skip'|null}
148
- */
102
+ // `'skip'` = eval nodes declared but not run; `null` = no eval node declared. A skipped eval
103
+ // leaves the tests rollup green, so the gate needs this split to tell them apart.
149
104
  function evalsStatusOf(result) {
150
105
  const runs = (result.nodes.tests?.runs || []).filter((run) => run.runner === EVAL_RUNNER);
151
106
  if (runs.length === 0) return null;
@@ -154,10 +109,7 @@ function evalsStatusOf(result) {
154
109
  return 'skip';
155
110
  }
156
111
 
157
- /**
158
- * The options that make a run partial, each with the `reason` it
159
- * reports when it keeps the stamp from being written — first match wins.
160
- */
112
+ // First match wins; each keeps the stamp from being written, with its `reason`.
161
113
  const PARTIAL_RUN_FLAGS = [['filter', 'filtered'], ['skipTests', 'skip-tests'], ['skipBuild', 'skip-build']];
162
114
 
163
115
  const CI_EXCLUSIVE_FLAGS = [
@@ -181,9 +133,7 @@ async function runLocalVerify(featureName, options) {
181
133
  linuxImage: options.linuxImage,
182
134
  });
183
135
  } catch (err) {
184
- // A wrong invocation is exit 2, like an unknown feature — never a
185
- // verdict. `--filter` that selects nothing must not print green (or red):
186
- // there is no verification to report, only a command to fix.
136
+ // Exit 2, never a verdict: a `--filter` that selects nothing must not print green or red.
187
137
  if (err instanceof VerifyUsageError) {
188
138
  console.error(chalk.red(`\n❌ ${err.message}\n`));
189
139
  process.exit(2);
@@ -204,25 +154,8 @@ async function runLocalVerify(featureName, options) {
204
154
  }
205
155
  }
206
156
 
207
- /**
208
- * `morph-spec verify <feature>` — the deterministic code-before-judge
209
- * gate: build + tests + validators, BEFORE the LLM eval. See verify-runner.js
210
- * for the node cadence; this command wraps it with CLI ergonomics
211
- * (human/JSON output, exit codes) and context-aware persistence.
212
- *
213
- * Exit codes: 0 = pass (including all-skip) · 1 = fail (>=1 red node, or a
214
- * `--record-baseline` the run refused to write) · 2 = usage error.
215
- *
216
- * Exit 2 is "the command cannot mean what you typed", and there are four
217
- * paths to it — never a verdict, because a verdict about nothing is worse
218
- * than a refusal:
219
- * - unknown feature;
220
- * - `--filter` that matches no declared node (a filtered-to-empty run is
221
- * green and proves nothing);
222
- * - `--record-baseline` with nowhere to write (no `config.json`);
223
- * - `--linux` without Docker.
224
- * The last three arrive as `VerifyUsageError` from the runner.
225
- */
157
+ // Exit 2 is "the command cannot mean what you typed" (unknown feature, `--filter` matching
158
+ // nothing, `--record-baseline` without config.json, `--linux` without Docker): never a verdict.
226
159
  export async function verifyCommand(featureName, options = {}) {
227
160
  let feature;
228
161
  try {
@@ -311,22 +244,9 @@ export async function verifyCommand(featureName, options = {}) {
311
244
  };
312
245
  await updateFeature(featureName, historyKey, compact);
313
246
 
314
- // Singular "latest stamp" — distinct from the `verifications.feature`
315
- // history above. `sha` is what lets a
316
- // consumer (morph-spec gate-check) correlate this pass/fail with the
317
- // EXACT commit it covers, the same way a review report's
318
- // `**Reviewed-SHA:**` token lets approve.js detect a stale review. null
319
- // in a non-git checkout (or before the first commit) — fail-open, never
320
- // blocks persistence.
321
- //
322
- // `nodes` + `e2eReason` são acréscimo de campo, não mudança de forma: o
323
- // rollup `status` continua onde estava. Existem porque um nó e2e que PULA
324
- // não reprova o rollup — sem o status do nó e a razão LITERAL do skip, o
325
- // gate-check lê 'pass' tanto quando o e2e rodou e passou quanto quando ele
326
- // nunca rodou, e não tem como separar dispensa legítima ("o projeto não
327
- // containeriza") de ausência de prova ("docker indisponível"). `e2eReason`
328
- // é null quando o nó não pulou — e um null NUM SKIP é lido como dispensa
329
- // sem motivo, que risk-detector.js recusa.
247
+ // `sha` ties the stamp to the exact commit (null without git: fail-open). `nodes` and `e2eReason`
248
+ // exist because a skipped e2e does not fail the rollup; a null reason ON A SKIP reads as a
249
+ // waiver without motive, which risk-detector.js refuses.
330
250
  const stamp = {
331
251
  status: result.status,
332
252
  nodes: nodeStatuses(result),
@@ -391,10 +311,7 @@ export async function verifyCommand(featureName, options = {}) {
391
311
  }
392
312
  }
393
313
 
394
- // A recording the user asked for and did not get is a failure of the
395
- // invocation even when the run itself was green (a filtered run is green and
396
- // unrecordable): exiting 0 there would report success for something that did
397
- // not happen.
314
+ // A recording asked for and not made fails the invocation, even when the run was green.
398
315
  const baselineRefused = Boolean(result.baseline && !result.baseline.recorded);
399
316
  process.exit(result.status === 'pass' && !baselineRefused ? 0 : 1);
400
317
  }