@polymorphism-tech/morph-spec 8.46.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/CLAUDE.md +53 -44
  2. package/README.md +3 -4
  3. package/bin/morph-spec.js +38 -55
  4. package/bin/validate.js +5 -134
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +2 -2
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/CLAUDE.md +41 -101
  9. package/framework/MORPH.md +95 -252
  10. package/framework/agents.json +75 -18
  11. package/framework/commands/create-umbraco-lp.md +2 -0
  12. package/framework/commands/migrate-lp-to-umbraco.md +2 -0
  13. package/framework/commands/morph-apply.md +5 -28
  14. package/framework/commands/morph-archive.md +2 -2
  15. package/framework/commands/morph-hotfix.md +8 -8
  16. package/framework/commands/morph-infra.md +17 -203
  17. package/framework/commands/morph-media.md +3 -0
  18. package/framework/commands/morph-preflight.md +7 -31
  19. package/framework/commands/morph-proposal.md +2 -50
  20. package/framework/commands/morph-status.md +3 -3
  21. package/framework/commands/morph-troubleshoot.md +2 -14
  22. package/framework/docs/CLI.md +585 -286
  23. package/framework/evals/review-checklist.md +82 -41
  24. package/framework/hooks/README.md +10 -11
  25. package/framework/hooks/claude-code/core/comment-guard.js +1 -1
  26. package/framework/hooks/claude-code/core/denylist.json +33 -3
  27. package/framework/hooks/claude-code/core/enforce-phase-writes.js +16 -20
  28. package/framework/hooks/claude-code/core/gate-guard.js +2 -11
  29. package/framework/hooks/claude-code/core/pre-bash-denylist.js +38 -0
  30. package/framework/hooks/claude-code/core/protect-spec-files.js +30 -242
  31. package/framework/hooks/claude-code/core/state-sync.js +17 -19
  32. package/framework/hooks/claude-code/core/telemetry-log.js +3 -3
  33. package/framework/hooks/claude-code/core/validate-completion.js +33 -97
  34. package/framework/hooks/claude-code/core/warn-bash-source-write.js +31 -0
  35. package/framework/hooks/claude-code/core/worktree-context.js +5 -4
  36. package/framework/hooks/claude-code/statusline.py +55 -41
  37. package/framework/hooks/package.json +3 -0
  38. package/framework/hooks/shared/activity-logger.js +1 -1
  39. package/framework/hooks/shared/build-check.js +10 -6
  40. package/framework/hooks/shared/command-tokens.js +169 -0
  41. package/framework/hooks/shared/comment-rules.js +1 -1
  42. package/framework/hooks/shared/comment-scanner.js +10 -23
  43. package/framework/hooks/shared/comment-stripper.js +1 -1
  44. package/framework/hooks/shared/e2e-check.js +67 -31
  45. package/framework/hooks/shared/errata.js +72 -0
  46. package/framework/hooks/shared/feature-locator.js +1 -1
  47. package/framework/hooks/shared/frontend-locations.js +6 -0
  48. package/framework/hooks/shared/gate-question.js +18 -0
  49. package/framework/hooks/shared/phase-utils.js +15 -32
  50. package/framework/hooks/shared/slice-progress.js +118 -0
  51. package/framework/hooks/shared/spawn-command.js +23 -0
  52. package/framework/hooks/shared/state-reader.js +61 -87
  53. package/framework/hooks/shared/state-writer.js +3 -3
  54. package/framework/mcp/registry.json +3 -4
  55. package/framework/phases.json +23 -39
  56. package/framework/rules/ai-agents-standards.md +18 -16
  57. package/framework/rules/asaas-standards.md +3 -1
  58. package/framework/rules/csharp-standards.md +10 -8
  59. package/framework/rules/data-standards.md +5 -5
  60. package/framework/rules/dotnet-templates.md +1 -1
  61. package/framework/rules/frontend-standards.md +4 -2
  62. package/framework/rules/gsap-standards.md +10 -5
  63. package/framework/rules/hangfire-standards.md +3 -1
  64. package/framework/rules/infrastructure-standards.md +4 -2
  65. package/framework/rules/neon-auth-standards.md +3 -1
  66. package/framework/rules/neon-standards.md +6 -4
  67. package/framework/rules/nextjs-standards.md +13 -11
  68. package/framework/rules/nextjs-templates.md +1 -1
  69. package/framework/rules/resend-standards.md +3 -1
  70. package/framework/schemas/config.schema.json +14 -9
  71. package/framework/schemas/feature.schema.json +15 -52
  72. package/framework/skills/README.md +4 -5
  73. package/framework/skills/core/create-umbraco-lp/SKILL.md +3 -3
  74. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +3 -3
  75. package/framework/skills/core/morph-apply/SKILL.md +150 -223
  76. package/framework/skills/core/morph-hotfix/SKILL.md +34 -43
  77. package/framework/skills/core/morph-machine/SKILL.md +4 -4
  78. package/framework/skills/core/morph-media/SKILL.md +3 -3
  79. package/framework/skills/core/morph-plan/SKILL.md +138 -300
  80. package/framework/skills/core/morph-review/SKILL.md +112 -137
  81. package/framework/skills/core/morph-standards/SKILL.md +29 -41
  82. package/framework/skills/core/morph-uiux/SKILL.md +78 -153
  83. package/framework/standards/STANDARDS.json +90 -1219
  84. package/framework/standards/STANDARDS.meta.json +1305 -0
  85. package/framework/standards/ai-agents/evals-with-cache.md +2 -3
  86. package/framework/standards/ai-agents/llm-runtime-defaults.md +1 -1
  87. package/framework/standards/ai-agents/modalities-speech-to-text.md +36 -26
  88. package/framework/standards/ai-agents/prompt-sources.md +6 -4
  89. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  90. package/framework/standards/ai-agents/rag-custom-pgvector.md +67 -39
  91. package/framework/standards/ai-agents/setup.md +2 -2
  92. package/framework/standards/ai-agents/structured-output.md +1 -1
  93. package/framework/standards/ai-agents/sweet-spot.md +1 -1
  94. package/framework/standards/architecture/code-comments.md +8 -8
  95. package/framework/standards/architecture/vertical-slice/vertical-slice.md +18 -9
  96. package/framework/standards/backend/api/validation.md +135 -47
  97. package/framework/standards/backend/database/postgresql/database.md +11 -7
  98. package/framework/standards/backend/database/vector-search-rag.md +207 -324
  99. package/framework/standards/backend/dotnet/fluent-validation-vsa.md +14 -8
  100. package/framework/standards/backend/dotnet/result-pattern.md +40 -6
  101. package/framework/standards/backend/dotnet/scalar-ui.md +13 -7
  102. package/framework/standards/backend/dotnet/scrutor.md +1 -1
  103. package/framework/standards/backend/dotnet/testing.md +1 -1
  104. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +7 -7
  105. package/framework/standards/backend/integrations/asaas/asaas-api.md +309 -165
  106. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +134 -154
  107. package/framework/standards/data/nosql/cache/redis.md +120 -44
  108. package/framework/standards/data/nosql/cosmos-db.md +114 -71
  109. package/framework/standards/frontend/design-system/animations.md +1 -1
  110. package/framework/standards/frontend/design-system/naming.md +1 -1
  111. package/framework/standards/frontend/nextjs/motion-patterns.md +45 -29
  112. package/framework/standards/frontend/nextjs/naming-conventions.md +1 -1
  113. package/framework/standards/infrastructure/docker/coolify-deploy.md +8 -6
  114. package/framework/standards/infrastructure/neon/neon-pgvector.md +120 -75
  115. package/framework/standards/infrastructure/neon/neon-setup.md +53 -31
  116. package/framework/standards/integration/mcp/mcp-tools.md +7 -7
  117. package/framework/templates/README.md +2 -3
  118. package/framework/templates/REGISTRY.json +6 -170
  119. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +2 -2
  120. package/framework/templates/code/dotnet/extensions/database-extensions.cs +1 -1
  121. package/framework/templates/code/dotnet/extensions/result-extensions.cs +27 -0
  122. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +2 -2
  123. package/framework/templates/docs/plan.md +72 -0
  124. package/framework/templates/dotnet/ai-kit/README.md +1 -9
  125. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +1 -1
  126. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
  127. package/framework/templates/infrastructure/docker/coolify.template.json +3 -3
  128. package/framework/templates/infrastructure/github/README.md +2 -1
  129. package/framework/templates/infrastructure/github/workflows/deploy-coolify.yml.hbs +2 -1
  130. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
  131. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
  132. package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
  133. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
  134. package/package.json +1 -1
  135. package/src/commands/e2e/e2e.js +2 -2
  136. package/src/commands/observability/cost.js +18 -60
  137. package/src/commands/observability/telemetry.js +3 -5
  138. package/src/commands/project/doctor-procs.js +1 -1
  139. package/src/commands/project/doctor.js +9 -9
  140. package/src/commands/project/fleet.js +4 -4
  141. package/src/commands/project/heavy.js +3 -3
  142. package/src/commands/project/init.js +6 -1
  143. package/src/commands/project/setup-infra-cmd.js +2 -1
  144. package/src/commands/project/stack-scan.js +58 -0
  145. package/src/commands/project/standards.js +79 -25
  146. package/src/commands/project/status.js +29 -69
  147. package/src/commands/project/update.js +33 -19
  148. package/src/commands/project/worktree.js +1 -1
  149. package/src/commands/state/advance.js +62 -46
  150. package/src/commands/state/approve.js +95 -138
  151. package/src/commands/state/archive.js +20 -84
  152. package/src/commands/state/auto-approval.js +68 -0
  153. package/src/commands/state/create.js +28 -1
  154. package/src/commands/state/delete.js +1 -1
  155. package/src/commands/state/finish.js +11 -23
  156. package/src/commands/state/gate-check.js +86 -95
  157. package/src/commands/state/gate-decision.js +6 -4
  158. package/src/commands/state/handoff.js +1 -1
  159. package/src/commands/state/list.js +9 -9
  160. package/src/commands/state/report-check.js +157 -311
  161. package/src/commands/state/retype.js +9 -3
  162. package/src/commands/validation/mutate.js +77 -14
  163. package/src/commands/validation/plan-check.js +81 -0
  164. package/src/commands/validation/validate-feature.js +60 -135
  165. package/src/commands/validation/verify.js +41 -82
  166. package/src/core/paths/output-schema.js +20 -67
  167. package/src/core/state/gate-order.js +5 -6
  168. package/src/core/state/lifecycle-audit.js +2 -2
  169. package/src/core/state/state-manager.js +17 -98
  170. package/src/core/templates/template-registry.js +2 -2
  171. package/src/core/templates/template-seed.js +22 -0
  172. package/src/core/work-types.js +10 -10
  173. package/src/dashboard/app/App.jsx +0 -2
  174. package/src/dashboard/app/components/ActivityFeed.jsx +2 -3
  175. package/src/dashboard/app/components/CommandPalette.jsx +7 -8
  176. package/src/dashboard/app/components/EntityLink.jsx +1 -2
  177. package/src/dashboard/app/components/Markdown.jsx +1 -1
  178. package/src/dashboard/app/components/Sidebar.jsx +1 -2
  179. package/src/dashboard/app/hooks/useMorphQuery.js +1 -1
  180. package/src/dashboard/app/lib/phases.js +2 -4
  181. package/src/dashboard/app/pages/Activity.jsx +1 -1
  182. package/src/dashboard/app/pages/FeatureDetail.jsx +139 -129
  183. package/src/dashboard/app/pages/Features.jsx +9 -9
  184. package/src/dashboard/app/pages/Guide.jsx +2 -2
  185. package/src/dashboard/app/pages/PhasePipeline.jsx +3 -4
  186. package/src/dashboard/app/pages/RulesMap.jsx +3 -3
  187. package/src/dashboard/app/pages/SkillsLibrary.jsx +3 -3
  188. package/src/dashboard/app/pages/Standards.jsx +1 -1
  189. package/src/dashboard/scanner/activity-scanner.js +6 -16
  190. package/src/dashboard/scanner/agents-scanner.js +1 -1
  191. package/src/dashboard/scanner/docs-scanner.js +5 -23
  192. package/src/dashboard/scanner/features-scanner.js +95 -60
  193. package/src/dashboard/scanner/index.js +0 -2
  194. package/src/dashboard/scanner/project-api.js +1 -1
  195. package/src/dashboard/scanner/rules-scanner.js +15 -10
  196. package/src/dashboard/scanner/skills-scanner.js +2 -3
  197. package/src/dashboard/scanner/standards-scanner.js +2 -1
  198. package/src/lib/ci/ci-verify.js +17 -1
  199. package/src/lib/ci/github-ci.js +1 -1
  200. package/src/lib/detectors/index.js +3 -8
  201. package/src/lib/generators/handoff-generator.js +34 -65
  202. package/src/lib/generators/settings-generator.js +11 -89
  203. package/src/lib/git/evidence-staleness.js +1 -1
  204. package/src/lib/git/tree-fingerprint.js +19 -7
  205. package/src/lib/git/tree-state.js +2 -2
  206. package/src/lib/heavy/cleanup.js +1 -5
  207. package/src/lib/installers/memory-installer.js +6 -8
  208. package/src/lib/mutation/commands.js +3 -2
  209. package/src/lib/{tasks/dotnet-test-introspection.js → mutation/dotnet-introspection.js} +17 -282
  210. package/src/lib/mutation/plan.js +14 -6
  211. package/src/lib/mutation/runner.js +1 -1
  212. package/src/lib/mutation/subset.js +46 -9
  213. package/src/lib/plan/artifact-vocabulary.js +60 -0
  214. package/src/lib/plan/checks.js +234 -0
  215. package/src/lib/plan/plan-parser.js +209 -0
  216. package/src/lib/plan/plan-template.js +18 -0
  217. package/src/lib/review-reports.js +222 -0
  218. package/src/lib/risk-detector.js +134 -135
  219. package/src/lib/specs/adr-index.js +50 -69
  220. package/src/lib/specs/design-seed.js +18 -0
  221. package/src/lib/specs/frontmatter.js +44 -0
  222. package/src/lib/specs/gfm-table.js +17 -0
  223. package/src/lib/specs/old-format.js +68 -0
  224. package/src/lib/specs/revocations.js +37 -95
  225. package/src/lib/specs/screen-checklist.js +18 -17
  226. package/src/lib/stack/dotnet-build-ignores.js +47 -0
  227. package/src/lib/stack/stack-profile.js +1 -1
  228. package/src/lib/stack/stack-scan.js +267 -0
  229. package/src/lib/standards/registry.js +43 -0
  230. package/src/lib/standards/sections.js +58 -0
  231. package/src/lib/tasks/eval-node.js +4 -12
  232. package/src/lib/tasks/test-nodes.js +1 -74
  233. package/src/lib/tasks/test-runner.js +75 -117
  234. package/src/lib/telemetry/metrics.js +8 -69
  235. package/src/lib/telemetry/telemetry-reader.js +5 -118
  236. package/src/lib/telemetry/verify-event.js +5 -7
  237. package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
  238. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -2
  239. package/src/lib/validators/reuse/reuse-validator.js +24 -48
  240. package/src/lib/validators/reuse/symbol-scanner.js +2 -4
  241. package/src/lib/validators/traceability-check.js +85 -129
  242. package/src/lib/validators/validation-runner.js +8 -88
  243. package/src/lib/validators/verify-runner.js +134 -301
  244. package/src/lib/worktree/docker-audit.js +1 -2
  245. package/src/lib/worktree/fleet-scanner.js +25 -38
  246. package/src/scripts/setup-infra.js +42 -22
  247. package/src/utils/agents-installer.js +92 -13
  248. package/src/utils/claude-settings-manager.js +0 -35
  249. package/src/utils/file-copier.js +34 -6
  250. package/src/utils/hooks-installer.js +16 -9
  251. package/src/utils/skills-installer.js +1 -2
  252. package/framework/commands/commit.md +0 -166
  253. package/framework/evals/trace-schema.md +0 -67
  254. package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
  255. package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
  256. package/framework/hooks/git/pre-commit/specs.sh +0 -50
  257. package/framework/hooks/shared/tasks-validator.js +0 -111
  258. package/framework/rubrics/backend.json +0 -41
  259. package/framework/rubrics/default.json +0 -36
  260. package/framework/rubrics/frontend.json +0 -36
  261. package/framework/rules/morph-task-tracking.md +0 -54
  262. package/framework/rules/morph-workflow.md +0 -244
  263. package/framework/schemas/tasks.schema.json +0 -79
  264. package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
  265. package/framework/skills/core/morph-implement/SKILL.md +0 -194
  266. package/framework/skills/core/morph-proposal/SKILL.md +0 -152
  267. package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
  268. package/framework/templates/docs/proposal.md +0 -192
  269. package/framework/templates/docs/spec.md +0 -57
  270. package/framework/templates/docs/ui-components.md +0 -52
  271. package/framework/templates/docs/ui-design-system.md +0 -76
  272. package/framework/templates/docs/ui-flows.md +0 -167
  273. package/framework/templates/examples/spec-examples.md +0 -91
  274. package/framework/templates/feature/decisions.md +0 -124
  275. package/framework/templates/feature/recap.md +0 -145
  276. package/framework/templates/project/validate.js +0 -97
  277. package/framework/templates/tasks.template.json +0 -17
  278. package/src/commands/state/usecase.js +0 -143
  279. package/src/commands/tasks/dag.js +0 -181
  280. package/src/dashboard/app/pages/Rubrics.jsx +0 -97
  281. package/src/dashboard/scanner/rubrics-scanner.js +0 -12
  282. package/src/lib/agents/output-collector.js +0 -76
  283. package/src/lib/evaluator-report.js +0 -91
  284. package/src/lib/specs/acervo-format.js +0 -70
  285. package/src/lib/specs/acervo-lifecycle.js +0 -264
  286. package/src/lib/specs/done-criteria-check.js +0 -200
  287. package/src/lib/specs/usecase-check.js +0 -301
  288. package/src/lib/specs/usecase-promoter.js +0 -301
  289. package/src/lib/specs/usecase-registry.js +0 -237
  290. package/src/lib/tasks/dispatch-planner.js +0 -319
  291. package/src/lib/tasks/mermaid-renderer.js +0 -132
  292. package/src/lib/tasks/task-classifier.js +0 -30
  293. package/src/lib/tasks/task-parser.js +0 -209
  294. package/src/lib/tasks/tasks-json-parser.js +0 -296
  295. package/src/lib/tasks/test-scope.js +0 -597
  296. package/src/lib/validators/architecture/architecture-validator.js +0 -270
  297. package/src/lib/validators/architecture/index.js +0 -7
  298. package/src/lib/validators/css/css-validator.js +0 -340
  299. package/src/lib/validators/css/index.js +0 -7
  300. package/src/lib/validators/design-system/design-system-validator.js +0 -237
  301. package/src/lib/validators/design-system/index.js +0 -7
  302. package/src/lib/validators/score-aggregator.js +0 -244
  303. package/src/lib/validators/ui/index.js +0 -7
  304. package/src/lib/validators/ui/ui-contrast-validator.js +0 -422
@@ -1,7 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import { resolve } from 'path';
3
3
  import { getFeature, updateFeature } from '../../core/state/state-manager.js';
4
- import { runVerify, readTask, VerifyUsageError } from '../../lib/validators/verify-runner.js';
4
+ import { runVerify, VerifyUsageError } from '../../lib/validators/verify-runner.js';
5
5
  import { resolvePrimaryRoot } from '../project/worktree.js';
6
6
  import { resolveFeatureTree } from '../../lib/worktree/feature-truth.js';
7
7
  import { canonicalPath, readMorphConfig } from '../../lib/tasks/test-runner.js';
@@ -12,7 +12,7 @@ import { treeFingerprint } from '../../lib/git/tree-fingerprint.js';
12
12
  import { HeavyBusyError } from '../../lib/heavy/gate.js';
13
13
  import { afterHeavyRun } from '../../lib/heavy/cleanup.js';
14
14
  import { readHeavyConfig } from '../../../framework/hooks/shared/heavy-config.js';
15
- import { runCiVerify, CiUsageError } from '../../lib/ci/ci-verify.js';
15
+ import { runCiVerify, CiUsageError, reusableLocalStamp } from '../../lib/ci/ci-verify.js';
16
16
 
17
17
  /** ✓ pass · ✗ fail · – skip */
18
18
  function statusIcon(status) {
@@ -21,40 +21,12 @@ function statusIcon(status) {
21
21
  return chalk.gray('–');
22
22
  }
23
23
 
24
- /**
25
- * One line per test run naming what the task-scope recorte did to it (#87):
26
- * narrowed (how many selectors, from how many test files), whole, out of scope
27
- * or full, each with its reason. A green `tests` line alone cannot say that the
28
- * task ran 12 tests instead of 3.082. Feature-scope runs carry no selection and
29
- * print nothing extra; `feedback[]` is untouched either way.
30
- *
31
- * @param {Array<{id: string, selection: object|null}>} [runs]
32
- * @returns {string[]}
33
- */
34
- function selectionLines(runs = []) {
35
- return runs
36
- .filter((run) => run.selection)
37
- .map((run) => {
38
- const { mode, reason, selectors = [], files = [] } = run.selection;
39
- const detail = mode === 'narrowed'
40
- ? `${selectors.length} selector(s) from ${files.length} test file(s)`
41
- : (reason ?? '');
42
- return chalk.gray(` ↳ ${run.id.padEnd(10)} ${mode.padEnd(12)} ${detail}`);
43
- });
44
- }
45
-
46
- /**
47
- * Human-readable rendering of a runVerify() result — mirrors the
48
- * ✓/✗-per-node + feedback-list style of formatScoreBreakdown() in
49
- * score-aggregator.js, adapted to verify's build/tests/validators nodes.
50
- */
51
24
  function formatVerifyResult(result) {
52
25
  const lines = [];
53
- const scopeLabel = result.scope === 'task' ? `${result.feature}/${result.task}` : result.feature;
54
26
 
55
27
  lines.push('');
56
- lines.push(chalk.bold(`Verify — ${scopeLabel}`));
57
- lines.push(chalk.gray(`rubric: ${result.rubric}${result.stack ? ` | stack: ${result.stack}` : ''}`));
28
+ lines.push(chalk.bold(`Verify — ${result.feature}`));
29
+ if (result.stack) lines.push(chalk.gray(`stack: ${result.stack}`));
58
30
  lines.push('');
59
31
  lines.push(result.status === 'pass' ? chalk.green.bold('✓ PASS') : chalk.red.bold('✗ FAIL'));
60
32
  lines.push('');
@@ -69,8 +41,6 @@ function formatVerifyResult(result) {
69
41
  let detail;
70
42
  if (node.status === 'skip') {
71
43
  detail = node.reason || '';
72
- } else if (name === 'validators' && node.score != null) {
73
- detail = `score ${node.score}/10`;
74
44
  } else if (node.reason) {
75
45
  // A green e2e still carries meaning worth showing: smoke-only vs specs,
76
46
  // warm reuse vs rebuild. Without it, "pass" hides how much was proven.
@@ -79,7 +49,6 @@ function formatVerifyResult(result) {
79
49
  detail = `${node.durationMs}ms`;
80
50
  }
81
51
  lines.push(` ${icon} ${name.padEnd(11)} ${node.status.padEnd(5)} ${chalk.gray(detail)}`);
82
- if (name === 'tests') lines.push(...selectionLines(node.runs));
83
52
  }
84
53
 
85
54
  if (result.feedback.length > 0) {
@@ -139,7 +108,7 @@ function baselineStampBlock(baseline) {
139
108
 
140
109
  /**
141
110
  * The per-node status map, in the shape `feature.schema.json` fixes for BOTH
142
- * writers of it — the per-scope history entry (`verifications.*`) and the
111
+ * writers of it — the history entry (`verifications.feature`) and the
143
112
  * singular gate stamp (`verification.nodes`). One function because they were
144
113
  * two literal copies twenty-five lines apart: a fifth node meant remembering
145
114
  * both, and a stamp missing a node the history has is precisely the kind of
@@ -163,7 +132,7 @@ function nodeStatuses(result) {
163
132
  *
164
133
  * The rollup cannot answer the question Gate 3 asks. A red eval and a red unit
165
134
  * test both arrive as `tests: 'fail'`, and — worse — an eval node that was
166
- * SKIPPED (task scope) leaves the rollup green, so a gate reading `tests` alone
135
+ * SKIPPED (`--filter`) leaves the rollup green, so a gate reading `tests` alone
167
136
  * cannot tell "the eval ran and passed" from "the eval never ran".
168
137
  *
169
138
  * Four values, and the distinction between the last two is the whole point:
@@ -186,7 +155,7 @@ function evalsStatusOf(result) {
186
155
  }
187
156
 
188
157
  /**
189
- * The options that make a FEATURE-scope run partial, each with the `reason` it
158
+ * The options that make a run partial, each with the `reason` it
190
159
  * reports when it keeps the stamp from being written — first match wins.
191
160
  */
192
161
  const PARTIAL_RUN_FLAGS = [['filter', 'filtered'], ['skipTests', 'skip-tests'], ['skipBuild', 'skip-build']];
@@ -197,10 +166,9 @@ const CI_EXCLUSIVE_FLAGS = [
197
166
  ['noPersist', '--no-persist'],
198
167
  ];
199
168
 
200
- async function runLocalVerify(featureName, taskId, options) {
169
+ async function runLocalVerify(featureName, options) {
201
170
  try {
202
- return await runVerify(process.cwd(), featureName, taskId || null, {
203
- rubric: options.rubric,
171
+ return await runVerify(process.cwd(), featureName, {
204
172
  timeout: options.timeout,
205
173
  skipTests: options.skipTests,
206
174
  skipBuild: options.skipBuild,
@@ -211,12 +179,9 @@ async function runLocalVerify(featureName, taskId, options) {
211
179
  refreshCache: options.refreshCache,
212
180
  linux: options.linux,
213
181
  linuxImage: options.linuxImage,
214
- // Task scope: switch the recorte off (#87). At feature scope nothing is
215
- // ever narrowed, so the flag is a documented no-op there, not an error.
216
- full: options.full,
217
182
  });
218
183
  } catch (err) {
219
- // A wrong invocation is exit 2, like an unknown feature or task — never a
184
+ // A wrong invocation is exit 2, like an unknown feature — never a
220
185
  // verdict. `--filter` that selects nothing must not print green (or red):
221
186
  // there is no verification to report, only a command to fix.
222
187
  if (err instanceof VerifyUsageError) {
@@ -240,7 +205,7 @@ async function runLocalVerify(featureName, taskId, options) {
240
205
  }
241
206
 
242
207
  /**
243
- * `morph-spec verify <feature> [task]` — the deterministic code-before-judge
208
+ * `morph-spec verify <feature>` — the deterministic code-before-judge
244
209
  * gate: build + tests + validators, BEFORE the LLM eval. See verify-runner.js
245
210
  * for the node cadence; this command wraps it with CLI ergonomics
246
211
  * (human/JSON output, exit codes) and context-aware persistence.
@@ -251,16 +216,14 @@ async function runLocalVerify(featureName, taskId, options) {
251
216
  * Exit 2 is "the command cannot mean what you typed", and there are four
252
217
  * paths to it — never a verdict, because a verdict about nothing is worse
253
218
  * than a refusal:
254
- * - unknown feature, or unknown task id (the task's `group` decides the
255
- * rubric, so it is a hard usage error here);
219
+ * - unknown feature;
256
220
  * - `--filter` that matches no declared node (a filtered-to-empty run is
257
221
  * green and proves nothing);
258
- * - `--record-baseline` with nowhere to write (no `config.json`), or with a
259
- * task id (a recorte de task is never a fleet baseline, #87);
222
+ * - `--record-baseline` with nowhere to write (no `config.json`);
260
223
  * - `--linux` without Docker.
261
224
  * The last three arrive as `VerifyUsageError` from the runner.
262
225
  */
263
- export async function verifyCommand(featureName, taskId, options = {}) {
226
+ export async function verifyCommand(featureName, options = {}) {
264
227
  let feature;
265
228
  try {
266
229
  feature = getFeature(featureName);
@@ -273,22 +236,13 @@ export async function verifyCommand(featureName, taskId, options = {}) {
273
236
  }
274
237
 
275
238
  if (options.ci) {
276
- const conflict = taskId ? `a task ${taskId} (o loop por task é local)` : CI_EXCLUSIVE_FLAGS.find(([flag]) => options[flag])?.[1];
239
+ const conflict = CI_EXCLUSIVE_FLAGS.find(([flag]) => options[flag])?.[1];
277
240
  if (conflict) {
278
241
  console.error(chalk.red(`\n❌ --ci roda a suíte inteira da feature no CI; não combina com ${conflict}\n`));
279
242
  process.exit(2);
280
243
  }
281
244
  }
282
245
 
283
- // The runner's own reader (one reader of tasks.json for verify, #87): null
284
- // means tasks.json or the task is missing, and both are this usage error.
285
- if (taskId) {
286
- if (!readTask(process.cwd(), featureName, taskId)) {
287
- console.error(chalk.red(`\n❌ Task "${taskId}" not found in tasks.json for feature "${featureName}"\n`));
288
- process.exit(2);
289
- }
290
- }
291
-
292
246
  let ci = null;
293
247
  if (options.ci) {
294
248
  try {
@@ -302,6 +256,17 @@ export async function verifyCommand(featureName, taskId, options = {}) {
302
256
  console.error(chalk.yellow(`\n⏳ ${outcome.reason}. Não é vermelho e não há stamp: não repita em laço — siga e deixe o gate-check decidir.\n`));
303
257
  process.exit(3);
304
258
  } else {
259
+ const reused = reusableLocalStamp(process.cwd(), feature.verification);
260
+ if (reused) {
261
+ console.error(chalk.green(`\n↪ CI indisponível (${outcome.reason}) — stamp fresco reaproveitado (sha ${reused.sha.slice(0, 8)}, ${reused.ts}): nada mudou fora de .morph/ desde aquela rodada verde.\n`));
262
+ if (options.json) {
263
+ console.log(JSON.stringify({
264
+ feature: featureName, status: reused.status, nodes: reused.nodes, e2eReason: reused.e2eReason ?? null,
265
+ reused: { sha: reused.sha, ts: reused.ts, source: reused.source ?? 'local' }, persisted: false, reason: 'stamp-reused',
266
+ }, null, 2));
267
+ }
268
+ process.exit(0);
269
+ }
305
270
  console.error(chalk.yellow(`\n↪ CI indisponível (${outcome.reason}) — verify local.\n`));
306
271
  }
307
272
  } catch (err) {
@@ -316,45 +281,40 @@ export async function verifyCommand(featureName, taskId, options = {}) {
316
281
  // Taken before the run: it is the tree the run is about to read.
317
282
  const fingerprint = treeFingerprint(process.cwd());
318
283
 
319
- const result = ci ? ci.result : await runLocalVerify(featureName, taskId, options);
284
+ const result = ci ? ci.result : await runLocalVerify(featureName, options);
320
285
 
321
286
  const cleanup = ci ? { status: 'skipped', reason: 'ci' } : afterHeavyRun({
322
287
  projectPath: process.cwd(),
323
288
  config: readHeavyConfig(process.cwd()),
324
- scope: taskId ? 'task' : 'feature',
325
289
  });
326
290
 
327
- const scopeKey = taskId ? `taskVerifications.${taskId}` : 'verifications.feature';
291
+ const historyKey = 'verifications.feature';
328
292
  // A run that measured only part of the suite must not stand in for the proof a gate asks for:
329
- // --filter and --skip-* at feature scope, or a --record-baseline that recorded nothing. Task scope
330
- // is unaffected (its stamp already says scope 'task:{id}'); --no-persist keeps its own reason.
331
- const partialFeatureRun = (!options.noPersist && !taskId)
332
- ? PARTIAL_RUN_FLAGS.find(([flag]) => options[flag])?.[1] ?? null
333
- : null;
293
+ // --filter and --skip-*, or a --record-baseline that recorded nothing; --no-persist keeps its own reason.
294
+ const partialRun = options.noPersist ? null : PARTIAL_RUN_FLAGS.find(([flag]) => options[flag])?.[1] ?? null;
334
295
  const baselineBlock = options.recordBaseline ? baselineStampBlock(result.baseline) : null;
335
296
  const { persist, reason } = baselineBlock
336
297
  ? { persist: false, reason: baselineBlock }
337
- : partialFeatureRun
338
- ? { persist: false, reason: partialFeatureRun }
298
+ : partialRun
299
+ ? { persist: false, reason: partialRun }
339
300
  : decidePersistence({ cwd: process.cwd(), featureName, noPersist: options.noPersist });
340
301
 
341
302
  let compact = null;
342
303
  if (persist) {
343
- const prevEntry = taskId ? feature.taskVerifications?.[taskId] : feature.verifications?.feature;
304
+ const prevEntry = feature.verifications?.feature;
344
305
  const now = new Date().toISOString();
345
306
  compact = {
346
307
  status: result.status,
347
308
  nodes: nodeStatuses(result),
348
- score: result.nodes.validators.score ?? null,
349
309
  attempts: (prevEntry?.attempts ?? 0) + 1,
350
310
  updatedAt: now,
351
311
  };
352
- await updateFeature(featureName, scopeKey, compact);
312
+ await updateFeature(featureName, historyKey, compact);
353
313
 
354
- // Singular, scope-agnostic "latest stamp" — distinct from the
355
- // verifications/taskVerifications history above. `sha` is what lets a
314
+ // Singular "latest stamp" — distinct from the `verifications.feature`
315
+ // history above. `sha` is what lets a
356
316
  // consumer (morph-spec gate-check) correlate this pass/fail with the
357
- // EXACT commit it covers, the same way review-report.md's
317
+ // EXACT commit it covers, the same way a review report's
358
318
  // `**Reviewed-SHA:**` token lets approve.js detect a stale review. null
359
319
  // in a non-git checkout (or before the first commit) — fail-open, never
360
320
  // blocks persistence.
@@ -373,18 +333,17 @@ export async function verifyCommand(featureName, taskId, options = {}) {
373
333
  e2eReason: result.nodes.e2e.status === 'skip' ? (result.nodes.e2e.reason ?? null) : null,
374
334
  sha: ci ? ci.run.headSha : getHeadSha(process.cwd()),
375
335
  ts: now,
376
- scope: taskId ? `task:${taskId}` : 'feature',
377
336
  ...(ci ? { source: 'ci', ciRun: ci.run.url } : {}),
378
337
  };
379
338
  await updateFeature(featureName, 'verification', stamp);
380
339
  }
381
340
 
382
- // A feature-scope run over the exact tree of the previous one proves nothing
341
+ // A run over the exact tree of the previous one proves nothing
383
342
  // new. Advisory only: the stamp and the exit code are the same either way.
384
343
  let repeatOf = null;
385
344
  try {
386
345
  const cwd = process.cwd();
387
- if (!taskId && persist && !ci) repeatOf = findRepeatedVerify(cwd, { feature: featureName, task: null, scope: result.scope, fingerprint });
346
+ if (persist && !ci) repeatOf = findRepeatedVerify(cwd, { feature: featureName, fingerprint });
388
347
  const tree = resolve(cwd).replace(/\\/g, '/') === resolvePrimaryRoot(cwd) ? 'root' : 'worktree';
389
348
  recordVerifyEvent(cwd, buildVerifyEvent(result, { sha: ci ? ci.run.headSha : getHeadSha(cwd), tree, persisted: persist, reason, fingerprint, source: ci ? 'ci' : 'local' }));
390
349
  } catch { /* telemetry never changes the verdict */ }
@@ -415,7 +374,7 @@ export async function verifyCommand(featureName, taskId, options = {}) {
415
374
  console.log(chalk.gray(`${mark} dotnet build-server shutdown: ${cleanup.status}${cleanup.reason ? ` (${cleanup.reason})` : ''}\n`));
416
375
  }
417
376
  if (repeatOf) {
418
- console.log(chalk.yellow(`⚠️ Mesma árvore do verify de feature de ${repeatOf.ts} (${repeatOf.status}): nada mudou fora de .morph/ desde então. `
377
+ console.log(chalk.yellow(`⚠️ Mesma árvore do verify de ${repeatOf.ts} (${repeatOf.status}): nada mudou fora de .morph/ desde então. `
419
378
  + 'Esta rodada não prova nada novo — reuse o resultado anterior, a menos que o ambiente (Docker, banco, serviço externo) tenha mudado.\n'));
420
379
  }
421
380
  if (result.baseline?.recorded) {
@@ -426,7 +385,7 @@ export async function verifyCommand(featureName, taskId, options = {}) {
426
385
  `));
427
386
  }
428
387
  if (persist) {
429
- console.log(chalk.gray(`Persisted: ${scopeKey} (attempts: ${compact.attempts})\n`));
388
+ console.log(chalk.gray(`Persisted: ${historyKey} (attempts: ${compact.attempts})\n`));
430
389
  } else {
431
390
  console.log(chalk.yellow(`⚠️ Not persisted (${reason})\n`));
432
391
  }
@@ -15,28 +15,24 @@ import { join } from 'path';
15
15
  * Schema for output types produced by morph-spec NLH phases.
16
16
  *
17
17
  * Fields:
18
- * filename - The file's basename (e.g. 'spec.md'). Single-file types only.
18
+ * filename - The file's basename (e.g. 'plan.md'). Single-file types only.
19
19
  * phaseDir - Subdirectory within .morph/features/{feature}/
20
20
  * phase - The morph-spec phase that produces this output
21
21
  * protected - Whether the file requires an approval gate before editing
22
22
  * approvalGate - (optional) Which gate protects this file
23
- * mutableFields- (optional) For protected JSON files: top-level array-item
24
- * field names that MAY still change after the gate is approved.
25
- * Lets the implement phase update task progress without
26
- * unfreezing the plan contract. When present, the protect
27
- * hook does a semantic diff instead of a blanket block.
28
23
  *
29
24
  * Multi-file types (`multi: true`) hold N files in a directory instead of one
30
25
  * fixed basename. They carry `dirname` + `filenamePattern` + `displayFilename`
31
26
  * INSTEAD of `filename`, and every derivation keyed by basename
32
- * (FILENAME_TO_OUTPUT, PROTECTED_SPEC_FILES, MUTABLE_SPEC_FIELDS) skips them —
27
+ * (FILENAME_TO_OUTPUT, PROTECTED_SPEC_FILES) skips them —
33
28
  * there is no single basename to key on. `getOutputPath` returns the DIRECTORY
34
29
  * for these types.
35
30
  */
36
31
  export const OUTPUT_SCHEMA = {
37
- // 0-proposal/
38
- proposal: { filename: 'proposal.md', phaseDir: '0-proposal', phase: 'proposal', protected: false },
39
- stackScan: { filename: 'stack-scan.md', phaseDir: '0-proposal', phase: 'proposal', protected: false },
32
+ // 2-plan/ — the plan gate freezes both: plan.md accepts only an append in
33
+ // `## Errata`, checks.json only a rewrite by `morph-spec plan-check`.
34
+ plan: { filename: 'plan.md', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
35
+ checks: { filename: 'checks.json', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
40
36
 
41
37
  // 1-design/ (optional)
42
38
  uiDesignSystem: { filename: 'design-system.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
@@ -44,47 +40,20 @@ export const OUTPUT_SCHEMA = {
44
40
  uiComponents: { filename: 'components.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
45
41
  uiFlows: { filename: 'flows.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
46
42
 
47
- // 2-plan/
48
- spec: { filename: 'spec.md', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
49
- tasks: { filename: 'tasks.json', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan', mutableFields: ['status', 'outputs', 'notes'] },
50
- decisions: { filename: 'decisions.md', phaseDir: '2-plan', phase: 'plan', protected: false },
51
- // Veredito REUSAR/ESTENDER/CRIAR por conceito que a feature introduz, com a
52
- // justificativa de cada CRIAR. É o ARTEFATO do ciclo de reuso (a prosa está
53
- // em morph-plan §2b, a cobrança em src/lib/validators/reuse/). NÃO protegido,
54
- // como decisions.md: é registro vivo — o implementador que descobre um alvo
55
- // melhor no meio da task precisa poder anotar, e congelá-lo transformaria a
56
- // correção num bloqueio de gate.
57
- // N files, one per system use case — the behaviour layer the plan distils
58
- // BEFORE spec.md/tasks.json. Deliberately NOT protected: editing a use case
59
- // after Gate 2 is the supported way to change behaviour and re-run implement;
60
- // the audit trail lives in git.
61
- usecases: {
62
- dirname: 'usecases',
63
- filenamePattern: '^UC-\\d+-[a-z0-9-]+\\.md$',
64
- displayFilename: 'UC-{nn}-{slug}.md',
65
- phaseDir: '2-plan',
66
- phase: 'plan',
67
- protected: false,
68
- multi: true,
69
- },
70
-
71
- // 3-implement/
72
- recap: { filename: 'recap.md', phaseDir: '3-implement', phase: 'implement', protected: false },
73
- // N files, one per task whose doneCriteria judges what a MODEL answers
74
- // (morph-plan §5). Runtime evidence: input, model, output, verdict — pointing
43
+ // 3-implement/ — the slice reports (`reports/F{n}.md`, see getTaskReportPath) are the record.
44
+ // N files, one per scenario whose proof judges what a MODEL answers
45
+ // (`prova: eval` in the plan). Runtime evidence: input, model, output, verdict — pointing
75
46
  // at the `runner: "evals"` node when the stack declares one. CONDITIONAL by
76
47
  // construction: never in phases.json requiredOutputs. Readers: morph-review
77
- // dispatch prompt, proof rubric eval-de-modelo, test-coverage Q7.
78
- evidencias: { dirname: 'evidencias', filenamePattern: '^T\\d+\\.md$', displayFilename: '{taskId}.md',
48
+ // dispatch prompt.
49
+ evidencias: { dirname: 'evidencias', filenamePattern: '^C\\d+\\.md$', displayFilename: '{Cn}.md',
79
50
  phaseDir: '3-implement', phase: 'implement', protected: false, multi: true },
80
51
 
81
52
  // 4-review/
82
- reviewReport: { filename: 'review-report.md', phaseDir: '4-review', phase: 'review', protected: false },
83
- // Durable report the dispatched morph-evaluator sub-agent writes itself
84
- // (MORPH.md §6) — the orchestrator reads it and synthesizes reviewReport
85
- // from it; the two never collide because one is raw sub-agent output and
86
- // the other is the orchestrator's curated Gate 3 presentation.
87
- evaluatorReport:{ filename: 'evaluator-report.md', phaseDir: '4-review', phase: 'review', protected: false },
53
+ // Um relatório por papel; o canônico da fase é o do verificador.
54
+ verifierReport: { filename: 'verifier.md', phaseDir: '4-review', phase: 'review', protected: false },
55
+ reviewerReport: { filename: 'reviewer.md', phaseDir: '4-review', phase: 'review', protected: false },
56
+ securityReport: { filename: 'security.md', phaseDir: '4-review', phase: 'review', protected: false },
88
57
  };
89
58
 
90
59
  // ============================================================================
@@ -104,11 +73,11 @@ function leafSegment(entry) {
104
73
  * Get the morph-relative path for a specific output type of a feature.
105
74
  *
106
75
  * For `multi` types this is the DIRECTORY holding the N files
107
- * (".morph/features/my-feature/2-plan/usecases"), not a single file.
76
+ * (".morph/features/my-feature/3-implement/evidencias"), not a single file.
108
77
  *
109
78
  * @param {string} featureName
110
79
  * @param {string} outputType - camelCase output type key from OUTPUT_SCHEMA
111
- * @returns {string} e.g. ".morph/features/my-feature/2-plan/spec.md"
80
+ * @returns {string} e.g. ".morph/features/my-feature/2-plan/plan.md"
112
81
  */
113
82
  export function getOutputPath(featureName, outputType) {
114
83
  const entry = OUTPUT_SCHEMA[outputType];
@@ -205,27 +174,11 @@ export const PROTECTED_SPEC_FILES = Object.fromEntries(
205
174
  .map(([, entry]) => [entry.filename, entry.approvalGate])
206
175
  );
207
176
 
208
- /**
209
- * For protected files that carry mutable progress fields: filename → the
210
- * top-level array-item fields that may change after the gate is approved.
211
- * The protect-spec-files hook uses this to allow progress updates (e.g.
212
- * task status) while still freezing the plan contract.
213
- */
214
- export const MUTABLE_SPEC_FIELDS = Object.fromEntries(
215
- Object.entries(OUTPUT_SCHEMA)
216
- .filter(([, entry]) => entry.protected && entry.approvalGate && entry.mutableFields && !entry.multi)
217
- .map(([, entry]) => [entry.filename, entry.mutableFields])
218
- );
219
-
220
177
  // ============================================================================
221
- // Backwards-compat shim (will be removed in v5.1)
178
+ // Contracts
222
179
  // ============================================================================
223
180
 
224
- /**
225
- * v4 had separate contracts.cs / contracts.ts / contracts-vsa.cs output types.
226
- * In v5 contracts live inside spec.md (or in source files for VSA). This
227
- * function keeps callers happy until they're updated.
228
- */
181
+ /** The output type whose code blocks declare the contracts the code must honour. */
229
182
  export function resolveContractsOutputType() {
230
- return 'spec';
183
+ return 'plan';
231
184
  }
@@ -2,12 +2,12 @@
2
2
  * Gate monotonicity — single source of truth.
3
3
  *
4
4
  * A gate may only be approved once its predecessors are approved. The strict
5
- * chain is proposal → plan → review. uiux is optional: it requires proposal but
6
- * gates nothing downstream, so a feature that skips UI/UX is never blocked.
5
+ * chain is plan → review. uiux is optional: it requires plan but gates nothing
6
+ * downstream, so a feature that skips UI/UX is never blocked.
7
7
  *
8
8
  * Policy-stamped gates (approvedBy: "policy:{type}", written at birth by
9
9
  * `morph-spec create` for a work type's skippedGates) count as approved, so
10
- * chore/hotfix (proposal+plan pre-stamped) satisfy prereqs naturally.
10
+ * chore/hotfix (plan pre-stamped) satisfy prereqs naturally.
11
11
  *
12
12
  * Pure module — no filesystem/CLI side effects. Consumed by
13
13
  * `approve`/`advance` (refuse), the state-sync hook (warn), and `list`/`doctor`
@@ -15,9 +15,8 @@
15
15
  */
16
16
 
17
17
  export const GATE_PREREQ = {
18
- proposal: [],
19
- uiux: ['proposal'],
20
- plan: ['proposal'],
18
+ plan: [],
19
+ uiux: ['plan'],
21
20
  review: ['plan'],
22
21
  };
23
22
 
@@ -103,7 +103,7 @@ function lastTouched(fdir, fj) {
103
103
  * Does the feature dir hold any FILE other than its own `feature.json`?
104
104
  *
105
105
  * Files, not entries: `create` scaffolds the phase folders empty, so an empty
106
- * `0-proposal/` is the absence of work, not evidence of it.
106
+ * `2-plan/` is the absence of work, not evidence of it.
107
107
  */
108
108
  function hasArtifacts(fdir) {
109
109
  let entries;
@@ -159,7 +159,7 @@ function ownCommitCount(baseDir, branch) {
159
159
  * mean nobody ever came back to it.
160
160
  *
161
161
  * The artifact test is deliberately broader than the "0 tasks" the report
162
- * named: a proposal drafted and waiting on Gate 1 also has zero tasks and is
162
+ * named: a plan drafted and waiting on its gate also has zero tasks and is
163
163
  * very much alive. What a dead scaffold has is NOTHING — checked in the root
164
164
  * and, if the feature registered one, in its worktree, where uncommitted work
165
165
  * would otherwise be invisible to both the disk scan and `rev-list`.