@polymorphism-tech/morph-spec 8.47.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 (222) hide show
  1. package/CLAUDE.md +48 -39
  2. package/README.md +3 -4
  3. package/bin/morph-spec.js +37 -51
  4. package/bin/validate.js +3 -111
  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 +41 -101
  9. package/framework/MORPH.md +95 -252
  10. package/framework/agents.json +75 -18
  11. package/framework/commands/morph-apply.md +5 -28
  12. package/framework/commands/morph-archive.md +1 -1
  13. package/framework/commands/morph-hotfix.md +8 -8
  14. package/framework/commands/morph-infra.md +1 -1
  15. package/framework/commands/morph-preflight.md +2 -3
  16. package/framework/commands/morph-proposal.md +2 -50
  17. package/framework/commands/morph-status.md +3 -3
  18. package/framework/docs/CLI.md +585 -286
  19. package/framework/evals/review-checklist.md +82 -41
  20. package/framework/hooks/README.md +10 -11
  21. package/framework/hooks/claude-code/core/comment-guard.js +1 -1
  22. package/framework/hooks/claude-code/core/denylist.json +33 -3
  23. package/framework/hooks/claude-code/core/enforce-phase-writes.js +16 -20
  24. package/framework/hooks/claude-code/core/gate-guard.js +2 -11
  25. package/framework/hooks/claude-code/core/pre-bash-denylist.js +38 -0
  26. package/framework/hooks/claude-code/core/protect-spec-files.js +30 -242
  27. package/framework/hooks/claude-code/core/state-sync.js +7 -21
  28. package/framework/hooks/claude-code/core/telemetry-log.js +3 -3
  29. package/framework/hooks/claude-code/core/validate-completion.js +33 -97
  30. package/framework/hooks/claude-code/core/warn-bash-source-write.js +31 -0
  31. package/framework/hooks/claude-code/core/worktree-context.js +5 -4
  32. package/framework/hooks/claude-code/statusline.py +55 -41
  33. package/framework/hooks/package.json +3 -0
  34. package/framework/hooks/shared/activity-logger.js +1 -1
  35. package/framework/hooks/shared/build-check.js +10 -6
  36. package/framework/hooks/shared/command-tokens.js +169 -0
  37. package/framework/hooks/shared/comment-rules.js +1 -1
  38. package/framework/hooks/shared/comment-scanner.js +10 -23
  39. package/framework/hooks/shared/comment-stripper.js +1 -1
  40. package/framework/hooks/shared/e2e-check.js +64 -27
  41. package/framework/hooks/shared/errata.js +72 -0
  42. package/framework/hooks/shared/feature-locator.js +1 -1
  43. package/framework/hooks/shared/frontend-locations.js +6 -0
  44. package/framework/hooks/shared/gate-question.js +18 -0
  45. package/framework/hooks/shared/phase-utils.js +15 -32
  46. package/framework/hooks/shared/slice-progress.js +118 -0
  47. package/framework/hooks/shared/spawn-command.js +23 -0
  48. package/framework/hooks/shared/state-reader.js +61 -87
  49. package/framework/hooks/shared/state-writer.js +2 -3
  50. package/framework/mcp/registry.json +3 -4
  51. package/framework/phases.json +23 -39
  52. package/framework/schemas/config.schema.json +14 -9
  53. package/framework/schemas/feature.schema.json +15 -50
  54. package/framework/skills/README.md +3 -4
  55. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  56. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  57. package/framework/skills/core/morph-apply/SKILL.md +149 -222
  58. package/framework/skills/core/morph-hotfix/SKILL.md +33 -42
  59. package/framework/skills/core/morph-machine/SKILL.md +2 -2
  60. package/framework/skills/core/morph-media/SKILL.md +1 -1
  61. package/framework/skills/core/morph-plan/SKILL.md +137 -299
  62. package/framework/skills/core/morph-review/SKILL.md +111 -136
  63. package/framework/skills/core/morph-standards/SKILL.md +27 -39
  64. package/framework/skills/core/morph-uiux/SKILL.md +77 -152
  65. package/framework/standards/STANDARDS.json +86 -1218
  66. package/framework/standards/STANDARDS.meta.json +1305 -0
  67. package/framework/standards/ai-agents/evals-with-cache.md +1 -2
  68. package/framework/standards/ai-agents/setup.md +1 -1
  69. package/framework/standards/architecture/code-comments.md +6 -6
  70. package/framework/standards/integration/mcp/mcp-tools.md +7 -7
  71. package/framework/templates/README.md +2 -3
  72. package/framework/templates/REGISTRY.json +6 -170
  73. package/framework/templates/docs/plan.md +72 -0
  74. package/framework/templates/dotnet/ai-kit/README.md +1 -9
  75. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
  76. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
  77. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
  78. package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
  79. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
  80. package/package.json +1 -1
  81. package/src/commands/e2e/e2e.js +2 -2
  82. package/src/commands/observability/cost.js +18 -60
  83. package/src/commands/project/doctor-procs.js +1 -1
  84. package/src/commands/project/doctor.js +9 -9
  85. package/src/commands/project/fleet.js +4 -4
  86. package/src/commands/project/heavy.js +3 -3
  87. package/src/commands/project/init.js +4 -0
  88. package/src/commands/project/setup-infra-cmd.js +2 -1
  89. package/src/commands/project/stack-scan.js +58 -0
  90. package/src/commands/project/standards.js +79 -25
  91. package/src/commands/project/status.js +29 -69
  92. package/src/commands/project/update.js +17 -1
  93. package/src/commands/project/worktree.js +1 -1
  94. package/src/commands/state/advance.js +62 -46
  95. package/src/commands/state/approve.js +81 -141
  96. package/src/commands/state/archive.js +20 -84
  97. package/src/commands/state/auto-approval.js +68 -0
  98. package/src/commands/state/create.js +28 -1
  99. package/src/commands/state/delete.js +1 -1
  100. package/src/commands/state/finish.js +11 -23
  101. package/src/commands/state/gate-check.js +85 -84
  102. package/src/commands/state/gate-decision.js +6 -4
  103. package/src/commands/state/handoff.js +1 -1
  104. package/src/commands/state/list.js +9 -9
  105. package/src/commands/state/report-check.js +157 -311
  106. package/src/commands/state/retype.js +9 -3
  107. package/src/commands/validation/mutate.js +77 -14
  108. package/src/commands/validation/plan-check.js +81 -0
  109. package/src/commands/validation/validate-feature.js +60 -135
  110. package/src/commands/validation/verify.js +40 -72
  111. package/src/core/paths/output-schema.js +19 -66
  112. package/src/core/state/gate-order.js +5 -6
  113. package/src/core/state/lifecycle-audit.js +2 -2
  114. package/src/core/state/state-manager.js +17 -98
  115. package/src/core/templates/template-registry.js +2 -2
  116. package/src/core/templates/template-seed.js +22 -0
  117. package/src/core/work-types.js +10 -10
  118. package/src/dashboard/app/components/ActivityFeed.jsx +2 -3
  119. package/src/dashboard/app/components/CommandPalette.jsx +5 -5
  120. package/src/dashboard/app/components/EntityLink.jsx +1 -1
  121. package/src/dashboard/app/hooks/useMorphQuery.js +1 -1
  122. package/src/dashboard/app/lib/phases.js +2 -4
  123. package/src/dashboard/app/pages/Activity.jsx +1 -1
  124. package/src/dashboard/app/pages/FeatureDetail.jsx +139 -129
  125. package/src/dashboard/app/pages/Features.jsx +9 -9
  126. package/src/dashboard/app/pages/Guide.jsx +2 -2
  127. package/src/dashboard/app/pages/PhasePipeline.jsx +3 -4
  128. package/src/dashboard/app/pages/Standards.jsx +1 -1
  129. package/src/dashboard/scanner/activity-scanner.js +5 -15
  130. package/src/dashboard/scanner/agents-scanner.js +1 -1
  131. package/src/dashboard/scanner/docs-scanner.js +2 -2
  132. package/src/dashboard/scanner/features-scanner.js +95 -60
  133. package/src/dashboard/scanner/project-api.js +1 -1
  134. package/src/dashboard/scanner/standards-scanner.js +2 -1
  135. package/src/lib/ci/ci-verify.js +17 -1
  136. package/src/lib/ci/github-ci.js +1 -1
  137. package/src/lib/detectors/index.js +3 -8
  138. package/src/lib/generators/handoff-generator.js +34 -65
  139. package/src/lib/generators/settings-generator.js +11 -89
  140. package/src/lib/git/evidence-staleness.js +1 -1
  141. package/src/lib/git/tree-fingerprint.js +19 -7
  142. package/src/lib/git/tree-state.js +2 -2
  143. package/src/lib/heavy/cleanup.js +1 -5
  144. package/src/lib/installers/memory-installer.js +6 -8
  145. package/src/lib/mutation/commands.js +3 -2
  146. package/src/lib/{tasks/dotnet-test-introspection.js → mutation/dotnet-introspection.js} +17 -282
  147. package/src/lib/mutation/plan.js +14 -6
  148. package/src/lib/mutation/runner.js +1 -1
  149. package/src/lib/mutation/subset.js +46 -9
  150. package/src/lib/plan/artifact-vocabulary.js +60 -0
  151. package/src/lib/plan/checks.js +234 -0
  152. package/src/lib/plan/plan-parser.js +209 -0
  153. package/src/lib/plan/plan-template.js +18 -0
  154. package/src/lib/review-reports.js +222 -0
  155. package/src/lib/risk-detector.js +131 -140
  156. package/src/lib/specs/adr-index.js +50 -69
  157. package/src/lib/specs/design-seed.js +18 -0
  158. package/src/lib/specs/frontmatter.js +44 -0
  159. package/src/lib/specs/gfm-table.js +17 -0
  160. package/src/lib/specs/old-format.js +68 -0
  161. package/src/lib/specs/revocations.js +37 -95
  162. package/src/lib/specs/screen-checklist.js +18 -17
  163. package/src/lib/stack/dotnet-build-ignores.js +47 -0
  164. package/src/lib/stack/stack-scan.js +267 -0
  165. package/src/lib/standards/registry.js +43 -0
  166. package/src/lib/standards/sections.js +58 -0
  167. package/src/lib/tasks/eval-node.js +4 -12
  168. package/src/lib/tasks/test-nodes.js +1 -74
  169. package/src/lib/tasks/test-runner.js +75 -117
  170. package/src/lib/telemetry/metrics.js +8 -69
  171. package/src/lib/telemetry/telemetry-reader.js +5 -118
  172. package/src/lib/telemetry/verify-event.js +5 -7
  173. package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
  174. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -2
  175. package/src/lib/validators/reuse/reuse-validator.js +22 -45
  176. package/src/lib/validators/traceability-check.js +85 -129
  177. package/src/lib/validators/verify-runner.js +118 -235
  178. package/src/lib/worktree/docker-audit.js +1 -2
  179. package/src/lib/worktree/fleet-scanner.js +25 -38
  180. package/src/scripts/setup-infra.js +9 -0
  181. package/src/utils/agents-installer.js +76 -9
  182. package/src/utils/claude-settings-manager.js +0 -35
  183. package/src/utils/file-copier.js +27 -1
  184. package/src/utils/hooks-installer.js +16 -9
  185. package/src/utils/skills-installer.js +1 -2
  186. package/framework/evals/trace-schema.md +0 -67
  187. package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
  188. package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
  189. package/framework/hooks/git/pre-commit/specs.sh +0 -50
  190. package/framework/hooks/shared/tasks-validator.js +0 -111
  191. package/framework/rules/morph-task-tracking.md +0 -54
  192. package/framework/rules/morph-workflow.md +0 -244
  193. package/framework/schemas/tasks.schema.json +0 -79
  194. package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
  195. package/framework/skills/core/morph-implement/SKILL.md +0 -194
  196. package/framework/skills/core/morph-proposal/SKILL.md +0 -152
  197. package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
  198. package/framework/templates/docs/proposal.md +0 -192
  199. package/framework/templates/docs/spec.md +0 -57
  200. package/framework/templates/docs/ui-components.md +0 -52
  201. package/framework/templates/docs/ui-design-system.md +0 -76
  202. package/framework/templates/docs/ui-flows.md +0 -167
  203. package/framework/templates/examples/spec-examples.md +0 -91
  204. package/framework/templates/feature/decisions.md +0 -124
  205. package/framework/templates/feature/recap.md +0 -145
  206. package/framework/templates/tasks.template.json +0 -17
  207. package/src/commands/state/usecase.js +0 -143
  208. package/src/commands/tasks/dag.js +0 -181
  209. package/src/lib/agents/output-collector.js +0 -76
  210. package/src/lib/evaluator-report.js +0 -104
  211. package/src/lib/specs/acervo-format.js +0 -70
  212. package/src/lib/specs/acervo-lifecycle.js +0 -264
  213. package/src/lib/specs/done-criteria-check.js +0 -200
  214. package/src/lib/specs/usecase-check.js +0 -301
  215. package/src/lib/specs/usecase-promoter.js +0 -301
  216. package/src/lib/specs/usecase-registry.js +0 -237
  217. package/src/lib/tasks/dispatch-planner.js +0 -319
  218. package/src/lib/tasks/mermaid-renderer.js +0 -132
  219. package/src/lib/tasks/task-classifier.js +0 -30
  220. package/src/lib/tasks/task-parser.js +0 -209
  221. package/src/lib/tasks/tasks-json-parser.js +0 -296
  222. package/src/lib/tasks/test-scope.js +0 -597
@@ -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,34 +21,11 @@ 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
24
  function formatVerifyResult(result) {
47
25
  const lines = [];
48
- const scopeLabel = result.scope === 'task' ? `${result.feature}/${result.task}` : result.feature;
49
26
 
50
27
  lines.push('');
51
- lines.push(chalk.bold(`Verify — ${scopeLabel}`));
28
+ lines.push(chalk.bold(`Verify — ${result.feature}`));
52
29
  if (result.stack) lines.push(chalk.gray(`stack: ${result.stack}`));
53
30
  lines.push('');
54
31
  lines.push(result.status === 'pass' ? chalk.green.bold('✓ PASS') : chalk.red.bold('✗ FAIL'));
@@ -72,7 +49,6 @@ function formatVerifyResult(result) {
72
49
  detail = `${node.durationMs}ms`;
73
50
  }
74
51
  lines.push(` ${icon} ${name.padEnd(11)} ${node.status.padEnd(5)} ${chalk.gray(detail)}`);
75
- if (name === 'tests') lines.push(...selectionLines(node.runs));
76
52
  }
77
53
 
78
54
  if (result.feedback.length > 0) {
@@ -132,7 +108,7 @@ function baselineStampBlock(baseline) {
132
108
 
133
109
  /**
134
110
  * The per-node status map, in the shape `feature.schema.json` fixes for BOTH
135
- * writers of it — the per-scope history entry (`verifications.*`) and the
111
+ * writers of it — the history entry (`verifications.feature`) and the
136
112
  * singular gate stamp (`verification.nodes`). One function because they were
137
113
  * two literal copies twenty-five lines apart: a fifth node meant remembering
138
114
  * both, and a stamp missing a node the history has is precisely the kind of
@@ -156,7 +132,7 @@ function nodeStatuses(result) {
156
132
  *
157
133
  * The rollup cannot answer the question Gate 3 asks. A red eval and a red unit
158
134
  * test both arrive as `tests: 'fail'`, and — worse — an eval node that was
159
- * 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
160
136
  * cannot tell "the eval ran and passed" from "the eval never ran".
161
137
  *
162
138
  * Four values, and the distinction between the last two is the whole point:
@@ -179,7 +155,7 @@ function evalsStatusOf(result) {
179
155
  }
180
156
 
181
157
  /**
182
- * 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
183
159
  * reports when it keeps the stamp from being written — first match wins.
184
160
  */
185
161
  const PARTIAL_RUN_FLAGS = [['filter', 'filtered'], ['skipTests', 'skip-tests'], ['skipBuild', 'skip-build']];
@@ -190,9 +166,9 @@ const CI_EXCLUSIVE_FLAGS = [
190
166
  ['noPersist', '--no-persist'],
191
167
  ];
192
168
 
193
- async function runLocalVerify(featureName, taskId, options) {
169
+ async function runLocalVerify(featureName, options) {
194
170
  try {
195
- return await runVerify(process.cwd(), featureName, taskId || null, {
171
+ return await runVerify(process.cwd(), featureName, {
196
172
  timeout: options.timeout,
197
173
  skipTests: options.skipTests,
198
174
  skipBuild: options.skipBuild,
@@ -203,12 +179,9 @@ async function runLocalVerify(featureName, taskId, options) {
203
179
  refreshCache: options.refreshCache,
204
180
  linux: options.linux,
205
181
  linuxImage: options.linuxImage,
206
- // Task scope: switch the recorte off (#87). At feature scope nothing is
207
- // ever narrowed, so the flag is a documented no-op there, not an error.
208
- full: options.full,
209
182
  });
210
183
  } catch (err) {
211
- // 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
212
185
  // verdict. `--filter` that selects nothing must not print green (or red):
213
186
  // there is no verification to report, only a command to fix.
214
187
  if (err instanceof VerifyUsageError) {
@@ -232,7 +205,7 @@ async function runLocalVerify(featureName, taskId, options) {
232
205
  }
233
206
 
234
207
  /**
235
- * `morph-spec verify <feature> [task]` — the deterministic code-before-judge
208
+ * `morph-spec verify <feature>` — the deterministic code-before-judge
236
209
  * gate: build + tests + validators, BEFORE the LLM eval. See verify-runner.js
237
210
  * for the node cadence; this command wraps it with CLI ergonomics
238
211
  * (human/JSON output, exit codes) and context-aware persistence.
@@ -243,16 +216,14 @@ async function runLocalVerify(featureName, taskId, options) {
243
216
  * Exit 2 is "the command cannot mean what you typed", and there are four
244
217
  * paths to it — never a verdict, because a verdict about nothing is worse
245
218
  * than a refusal:
246
- * - unknown feature, or unknown task id (the task's `group` and `outputs`
247
- * decide the scope, so it is a hard usage error here);
219
+ * - unknown feature;
248
220
  * - `--filter` that matches no declared node (a filtered-to-empty run is
249
221
  * green and proves nothing);
250
- * - `--record-baseline` with nowhere to write (no `config.json`), or with a
251
- * task id (a recorte de task is never a fleet baseline, #87);
222
+ * - `--record-baseline` with nowhere to write (no `config.json`);
252
223
  * - `--linux` without Docker.
253
224
  * The last three arrive as `VerifyUsageError` from the runner.
254
225
  */
255
- export async function verifyCommand(featureName, taskId, options = {}) {
226
+ export async function verifyCommand(featureName, options = {}) {
256
227
  let feature;
257
228
  try {
258
229
  feature = getFeature(featureName);
@@ -265,22 +236,13 @@ export async function verifyCommand(featureName, taskId, options = {}) {
265
236
  }
266
237
 
267
238
  if (options.ci) {
268
- 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];
269
240
  if (conflict) {
270
241
  console.error(chalk.red(`\n❌ --ci roda a suíte inteira da feature no CI; não combina com ${conflict}\n`));
271
242
  process.exit(2);
272
243
  }
273
244
  }
274
245
 
275
- // The runner's own reader (one reader of tasks.json for verify, #87): null
276
- // means tasks.json or the task is missing, and both are this usage error.
277
- if (taskId) {
278
- if (!readTask(process.cwd(), featureName, taskId)) {
279
- console.error(chalk.red(`\n❌ Task "${taskId}" not found in tasks.json for feature "${featureName}"\n`));
280
- process.exit(2);
281
- }
282
- }
283
-
284
246
  let ci = null;
285
247
  if (options.ci) {
286
248
  try {
@@ -294,6 +256,17 @@ export async function verifyCommand(featureName, taskId, options = {}) {
294
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`));
295
257
  process.exit(3);
296
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
+ }
297
270
  console.error(chalk.yellow(`\n↪ CI indisponível (${outcome.reason}) — verify local.\n`));
298
271
  }
299
272
  } catch (err) {
@@ -308,31 +281,27 @@ export async function verifyCommand(featureName, taskId, options = {}) {
308
281
  // Taken before the run: it is the tree the run is about to read.
309
282
  const fingerprint = treeFingerprint(process.cwd());
310
283
 
311
- const result = ci ? ci.result : await runLocalVerify(featureName, taskId, options);
284
+ const result = ci ? ci.result : await runLocalVerify(featureName, options);
312
285
 
313
286
  const cleanup = ci ? { status: 'skipped', reason: 'ci' } : afterHeavyRun({
314
287
  projectPath: process.cwd(),
315
288
  config: readHeavyConfig(process.cwd()),
316
- scope: taskId ? 'task' : 'feature',
317
289
  });
318
290
 
319
- const scopeKey = taskId ? `taskVerifications.${taskId}` : 'verifications.feature';
291
+ const historyKey = 'verifications.feature';
320
292
  // A run that measured only part of the suite must not stand in for the proof a gate asks for:
321
- // --filter and --skip-* at feature scope, or a --record-baseline that recorded nothing. Task scope
322
- // is unaffected (its stamp already says scope 'task:{id}'); --no-persist keeps its own reason.
323
- const partialFeatureRun = (!options.noPersist && !taskId)
324
- ? PARTIAL_RUN_FLAGS.find(([flag]) => options[flag])?.[1] ?? null
325
- : 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;
326
295
  const baselineBlock = options.recordBaseline ? baselineStampBlock(result.baseline) : null;
327
296
  const { persist, reason } = baselineBlock
328
297
  ? { persist: false, reason: baselineBlock }
329
- : partialFeatureRun
330
- ? { persist: false, reason: partialFeatureRun }
298
+ : partialRun
299
+ ? { persist: false, reason: partialRun }
331
300
  : decidePersistence({ cwd: process.cwd(), featureName, noPersist: options.noPersist });
332
301
 
333
302
  let compact = null;
334
303
  if (persist) {
335
- const prevEntry = taskId ? feature.taskVerifications?.[taskId] : feature.verifications?.feature;
304
+ const prevEntry = feature.verifications?.feature;
336
305
  const now = new Date().toISOString();
337
306
  compact = {
338
307
  status: result.status,
@@ -340,12 +309,12 @@ export async function verifyCommand(featureName, taskId, options = {}) {
340
309
  attempts: (prevEntry?.attempts ?? 0) + 1,
341
310
  updatedAt: now,
342
311
  };
343
- await updateFeature(featureName, scopeKey, compact);
312
+ await updateFeature(featureName, historyKey, compact);
344
313
 
345
- // Singular, scope-agnostic "latest stamp" — distinct from the
346
- // 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
347
316
  // consumer (morph-spec gate-check) correlate this pass/fail with the
348
- // EXACT commit it covers, the same way review-report.md's
317
+ // EXACT commit it covers, the same way a review report's
349
318
  // `**Reviewed-SHA:**` token lets approve.js detect a stale review. null
350
319
  // in a non-git checkout (or before the first commit) — fail-open, never
351
320
  // blocks persistence.
@@ -364,18 +333,17 @@ export async function verifyCommand(featureName, taskId, options = {}) {
364
333
  e2eReason: result.nodes.e2e.status === 'skip' ? (result.nodes.e2e.reason ?? null) : null,
365
334
  sha: ci ? ci.run.headSha : getHeadSha(process.cwd()),
366
335
  ts: now,
367
- scope: taskId ? `task:${taskId}` : 'feature',
368
336
  ...(ci ? { source: 'ci', ciRun: ci.run.url } : {}),
369
337
  };
370
338
  await updateFeature(featureName, 'verification', stamp);
371
339
  }
372
340
 
373
- // 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
374
342
  // new. Advisory only: the stamp and the exit code are the same either way.
375
343
  let repeatOf = null;
376
344
  try {
377
345
  const cwd = process.cwd();
378
- 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 });
379
347
  const tree = resolve(cwd).replace(/\\/g, '/') === resolvePrimaryRoot(cwd) ? 'root' : 'worktree';
380
348
  recordVerifyEvent(cwd, buildVerifyEvent(result, { sha: ci ? ci.run.headSha : getHeadSha(cwd), tree, persisted: persist, reason, fingerprint, source: ci ? 'ci' : 'local' }));
381
349
  } catch { /* telemetry never changes the verdict */ }
@@ -406,7 +374,7 @@ export async function verifyCommand(featureName, taskId, options = {}) {
406
374
  console.log(chalk.gray(`${mark} dotnet build-server shutdown: ${cleanup.status}${cleanup.reason ? ` (${cleanup.reason})` : ''}\n`));
407
375
  }
408
376
  if (repeatOf) {
409
- 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. `
410
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'));
411
379
  }
412
380
  if (result.baseline?.recorded) {
@@ -417,7 +385,7 @@ export async function verifyCommand(featureName, taskId, options = {}) {
417
385
  `));
418
386
  }
419
387
  if (persist) {
420
- console.log(chalk.gray(`Persisted: ${scopeKey} (attempts: ${compact.attempts})\n`));
388
+ console.log(chalk.gray(`Persisted: ${historyKey} (attempts: ${compact.attempts})\n`));
421
389
  } else {
422
390
  console.log(chalk.yellow(`⚠️ Not persisted (${reason})\n`));
423
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
48
  // dispatch prompt.
78
- evidencias: { dirname: 'evidencias', filenamePattern: '^T\\d+\\.md$', displayFilename: '{taskId}.md',
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`.