@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
@@ -2,7 +2,7 @@
2
2
  * MORPH-SPEC Feature Status Command
3
3
  *
4
4
  * Displays a comprehensive dashboard showing feature progress, phase status,
5
- * outputs, tasks, approvals, and next steps.
5
+ * outputs, slices, approvals, and next steps.
6
6
  *
7
7
  * Usage:
8
8
  * morph-spec status <feature-name>
@@ -14,6 +14,7 @@
14
14
 
15
15
  import chalk from 'chalk';
16
16
  import { loadState, getFeature, derivePhase, deriveOutputs } from '../../core/state/state-manager.js';
17
+ import { sliceProgress } from '../../../framework/hooks/shared/slice-progress.js';
17
18
 
18
19
  /**
19
20
  * Phase symbols for visual representation
@@ -29,13 +30,19 @@ const PHASE_SYMBOLS = {
29
30
  * Phase definitions with order
30
31
  */
31
32
  const PHASE_ORDER = [
32
- { key: 'proposal', name: 'FASE 0: PROPOSAL', order: 0 },
33
- { key: 'uiux', name: 'FASE 1: UI/UX', order: 1, optional: true },
34
- { key: 'plan', name: 'FASE 2: PLAN', order: 2 },
35
- { key: 'implement', name: 'FASE 3: IMPLEMENT', order: 3 },
36
- { key: 'review', name: 'FASE 4: REVIEW', order: 4 }
33
+ { key: 'plan', name: 'FASE 1: PLAN', order: 0 },
34
+ { key: 'uiux', name: 'FASE 2: UI/UX', order: 1, optional: true },
35
+ { key: 'implement', name: 'FASE 3: IMPLEMENT', order: 2 },
36
+ { key: 'review', name: 'FASE 4: REVIEW', order: 3 }
37
37
  ];
38
38
 
39
+ const PHASE_OUTPUTS = {
40
+ plan: ['plan', 'checks'],
41
+ uiux: ['uiDesignSystem', 'uiMockups', 'uiComponents', 'uiFlows'],
42
+ implement: [],
43
+ review: ['verifierReport', 'reviewerReport']
44
+ };
45
+
39
46
  /**
40
47
  * Calculate phase status (completed, current, pending, skipped)
41
48
  */
@@ -61,15 +68,7 @@ function getPhaseStatus(phaseKey, currentPhase, feature) {
61
68
  * Check if phase has any outputs created
62
69
  */
63
70
  function checkPhaseOutputs(phaseKey, feature) {
64
- const phaseOutputMap = {
65
- proposal: ['proposal', 'stackScan'],
66
- uiux: ['uiDesignSystem', 'uiMockups', 'uiComponents', 'uiFlows'],
67
- plan: ['spec', 'tasks', 'decisions'],
68
- implement: ['recap'],
69
- review: ['reviewReport']
70
- };
71
-
72
- const outputs = phaseOutputMap[phaseKey] || [];
71
+ const outputs = PHASE_OUTPUTS[phaseKey] || [];
73
72
  return outputs.some(output => feature.outputs[output]?.created);
74
73
  }
75
74
 
@@ -99,15 +98,7 @@ function formatPhase(phaseKey, currentPhase, feature, options = {}) {
99
98
 
100
99
  // Add output files if verbose
101
100
  if (options.verbose && status !== 'pending' && status !== 'skipped') {
102
- const phaseOutputMap = {
103
- proposal: ['proposal', 'stackScan'],
104
- uiux: ['uiDesignSystem', 'uiMockups', 'uiComponents', 'uiFlows'],
105
- plan: ['spec', 'tasks', 'decisions'],
106
- implement: ['recap'],
107
- review: ['reviewReport']
108
- };
109
-
110
- const outputs = phaseOutputMap[phaseKey] || [];
101
+ const outputs = PHASE_OUTPUTS[phaseKey] || [];
111
102
  const created = outputs.filter(o => feature.outputs[o]?.created);
112
103
  if (created.length > 0) {
113
104
  display += chalk.gray(`\n Files: ${created.length}/${outputs.length}`);
@@ -121,7 +112,7 @@ function formatPhase(phaseKey, currentPhase, feature, options = {}) {
121
112
  * Calculate completion percentage
122
113
  */
123
114
  function calculateCompletionPercentage(feature) {
124
- const totalPhases = PHASE_ORDER.filter(p => !p.optional).length; // 6 required phases
115
+ const totalPhases = PHASE_ORDER.filter(p => !p.optional).length;
125
116
  const completedPhases = PHASE_ORDER.filter(p => {
126
117
  if (p.optional) return false;
127
118
  const status = getPhaseStatus(p.key, feature.phase, feature);
@@ -139,9 +130,8 @@ function getNextSteps(feature) {
139
130
 
140
131
  // Check for pending approvals relevant to current phase
141
132
  const phaseApprovalMap = {
142
- proposal: 'proposal',
143
- uiux: 'uiux',
144
133
  plan: 'plan',
134
+ uiux: 'uiux',
145
135
  implement: 'plan',
146
136
  review: 'review'
147
137
  };
@@ -158,14 +148,7 @@ function getNextSteps(feature) {
158
148
  }
159
149
 
160
150
  // Check for missing outputs in current phase
161
- const phaseOutputMap = {
162
- proposal: ['proposal'],
163
- plan: ['spec', 'tasks'],
164
- implement: ['recap'],
165
- review: ['reviewReport']
166
- };
167
-
168
- const requiredOutputs = phaseOutputMap[feature.phase] || [];
151
+ const requiredOutputs = feature.phase === 'uiux' ? [] : PHASE_OUTPUTS[feature.phase] || [];
169
152
  const missingOutputs = requiredOutputs.filter(o => !feature.outputs[o]?.created);
170
153
 
171
154
  if (missingOutputs.length > 0) {
@@ -199,25 +182,6 @@ function getNextSteps(feature) {
199
182
  return steps;
200
183
  }
201
184
 
202
- /**
203
- * Estimate remaining time based on tasks
204
- */
205
- function estimateRemainingTime(feature) {
206
- if (!feature.tasks || feature.tasks.total === 0) {
207
- return 'Not estimated';
208
- }
209
-
210
- const remaining = feature.tasks.total - feature.tasks.completed;
211
- const avgHoursPerTask = 2; // Configurable estimate
212
- const hours = remaining * avgHoursPerTask;
213
-
214
- if (hours < 1) return '< 1 hour';
215
- if (hours === 1) return '1 hour';
216
- if (hours < 8) return `${hours} hours`;
217
- const days = Math.ceil(hours / 8);
218
- return `~${days} day${days > 1 ? 's' : ''} (${hours}h)`;
219
- }
220
-
221
185
  /**
222
186
  * Main status command
223
187
  */
@@ -251,6 +215,7 @@ export async function statusCommand(featureName, options = {}) {
251
215
  if (!feature.outputs) {
252
216
  feature.outputs = deriveOutputs(featureName, process.cwd());
253
217
  }
218
+ const slices = sliceProgress(featurePath);
254
219
 
255
220
  // JSON output mode
256
221
  if (options.json) {
@@ -270,10 +235,9 @@ export async function statusCommand(featureName, options = {}) {
270
235
  created: output.created,
271
236
  path: output.path
272
237
  })),
273
- tasks: feature.tasks || { total: 0, completed: 0, inProgress: 0, pending: 0 },
238
+ slices,
274
239
  approvals: feature.approvalGates || {},
275
- nextSteps: getNextSteps(feature),
276
- estimatedTimeRemaining: estimateRemainingTime(feature)
240
+ nextSteps: getNextSteps(feature)
277
241
  };
278
242
 
279
243
  console.log(JSON.stringify(status, null, 2));
@@ -315,17 +279,13 @@ export async function statusCommand(featureName, options = {}) {
315
279
  }
316
280
  console.log('');
317
281
 
318
- // Tasks progress
319
- if (feature.tasks && feature.tasks.total > 0) {
320
- console.log(chalk.white.bold('Tasks Progress:'));
321
- const { total, completed, inProgress, pending } = feature.tasks;
322
- const percentage = total > 0 ? Math.round((completed / total) * 100) : 0;
323
-
324
- console.log(` Total: ${total}`);
325
- console.log(chalk.green(` ✓ Completed: ${completed} (${percentage}%)`));
326
- if (inProgress > 0) console.log(chalk.cyan(` ▶ In Progress: ${inProgress}`));
327
- if (pending > 0) console.log(chalk.gray(` ⬜ Pending: ${pending}`));
328
- console.log(` Estimated time remaining: ${estimateRemainingTime(feature)}`);
282
+ if (slices && slices.total > 0) {
283
+ console.log(chalk.white.bold('Fatias:'));
284
+ const percentage = Math.round((slices.done / slices.total) * 100);
285
+ console.log(` Total: ${slices.total}`);
286
+ console.log(chalk.green(` ✓ Feitas: ${slices.done} (${percentage}%)`));
287
+ if (slices.partial > 0) console.log(chalk.cyan(` ▶ Parciais: ${slices.partial}`));
288
+ if (slices.pending > 0) console.log(chalk.gray(` ⬜ Pendentes: ${slices.pending}`));
329
289
  console.log('');
330
290
  }
331
291
 
@@ -361,7 +321,7 @@ export async function statusCommand(featureName, options = {}) {
361
321
  });
362
322
  console.log('');
363
323
  } else {
364
- console.log(chalk.green('✓ All tasks complete!\n'));
324
+ console.log(chalk.green('✓ Nada pendente.\n'));
365
325
  }
366
326
 
367
327
  // Footer
@@ -35,6 +35,7 @@ import { reconcileLegacyTemplatesCopy } from '../../lib/installers/templates-rec
35
35
  import { createEffects } from '../../lib/installers/effects.js';
36
36
  import { applyEditorSettings } from '../../lib/worktree/vscode-settings.js';
37
37
  import { registerProject } from '../../lib/projects/registry.js';
38
+ import { findOldFormatFeatures, oldFormatGuardLines } from '../../lib/specs/old-format.js';
38
39
  import {
39
40
  writeGraphifyignore, describeOutdatedIgnore, graphifyignoreTracked,
40
41
  } from '../../lib/graph/graphifyignore.js';
@@ -531,6 +532,21 @@ export async function updateCommand(options = {}) {
531
532
  + `Quem decide é o primeiro.`);
532
533
  }
533
534
 
535
+ // `--force` não dispensa: esta CLI não sabe conduzir essas features.
536
+ const oldFormat = needsInitialization ? [] : findOldFormatFeatures(targetPath);
537
+ if (oldFormat.length) {
538
+ const lines = oldFormatGuardLines(oldFormat, targetPath);
539
+ if (dryRun) {
540
+ notices.push(...lines);
541
+ } else {
542
+ logger.error(lines[0]);
543
+ for (const l of lines.slice(1)) logger.dim(l);
544
+ logger.blank();
545
+ process.exitCode = 1;
546
+ return { refused: true, reason: 'old-format-features', features: oldFormat, effects: fx.list() };
547
+ }
548
+ }
549
+
534
550
  // ── A partir daqui, tudo passa pelo coletor ──────────────────────────────
535
551
  if (needsInitialization) {
536
552
  logger.warn('MORPH not fully initialized. Creating basic structure...');
@@ -647,7 +663,7 @@ export async function updateCommand(options = {}) {
647
663
  // MUST come from the pre-clean backup: cleanFrameworkDirs() deleted .morph/config/
648
664
  // above and restoreUserConfig() only runs at the end — re-reading the file here
649
665
  // yields [] and silently downgrades the whole install to universal-only assets
650
- // (the ".claude/agents/ only has morph-evaluator.md after update" bug).
666
+ // (the ".claude/agents/ only has morph-verifier.md after update" bug).
651
667
  let projectTags = [];
652
668
  try {
653
669
  if (configBackup) projectTags = getProjectTags(configBackup);
@@ -1867,7 +1867,7 @@ export async function worktreeListCommand(options = {}) {
1867
1867
  if (!m) continue; // skip the primary root entry and any non-morph worktree
1868
1868
  const feature = m[1];
1869
1869
 
1870
- let phase = 'proposal';
1870
+ let phase = 'plan';
1871
1871
  let status = 'unknown';
1872
1872
  let pendingGates = [];
1873
1873
  try {
@@ -1,12 +1,16 @@
1
1
  import chalk from 'chalk';
2
2
  import { existsSync, mkdirSync, readFileSync } from 'fs';
3
- import { join } from 'path';
3
+ import { join, relative } from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { getFeature, setApprovalGate, derivePhase } from '../../core/state/state-manager.js';
6
6
  import { unmetPrereqs } from '../../core/state/gate-order.js';
7
7
  import { OUTPUT_SCHEMA } from '../../core/paths/output-schema.js';
8
8
  import { assertOwnerTree, ownerTreeRefusalLines } from '../../lib/worktree/feature-truth.js';
9
9
  import { uiuxAutoApprovalBlock } from '../../lib/specs/screen-checklist.js';
10
+ import { seedDesignArtifacts } from '../../lib/specs/design-seed.js';
11
+ import { sliceProgress } from '../../../framework/hooks/shared/slice-progress.js';
12
+ import { autoApprovalRefusal, printRefusal } from './auto-approval.js';
13
+ import { seedDotnetBuildIgnores } from '../../lib/stack/dotnet-build-ignores.js';
10
14
 
11
15
  const FRAMEWORK_DIR = fileURLToPath(new URL('../../../framework/', import.meta.url));
12
16
 
@@ -20,17 +24,16 @@ const FRAMEWORK_DIR = fileURLToPath(new URL('../../../framework/', import.meta.u
20
24
  */
21
25
 
22
26
  // The gate approved when *completing* a given phase. `implement` has no gate
23
- // (it ends when all tasks are done), so advancing from implement just scaffolds
24
- // the review folder.
25
- const PHASE_GATE = { proposal: 'proposal', uiux: 'uiux', plan: 'plan', review: 'review' };
27
+ // (it ends when all slices are done), so advancing from implement just
28
+ // scaffolds the review folder.
29
+ const PHASE_GATE = { plan: 'plan', uiux: 'uiux', review: 'review' };
26
30
 
27
31
  // The next phase folder to scaffold when advancing FROM a given phase. The
28
- // default forward path skips the optional design phase (proposal → plan); pass
29
- // --design to take the uiux branch (proposal → 1-design).
32
+ // default forward path skips the optional design phase (plan → implement); pass
33
+ // --design to take the uiux branch (plan → 1-design).
30
34
  const NEXT_PHASE_DIR = {
31
- proposal: '2-plan',
32
- uiux: '2-plan',
33
35
  plan: '3-implement',
36
+ uiux: '3-implement',
34
37
  implement: '4-review',
35
38
  };
36
39
 
@@ -63,12 +66,19 @@ function missingPhaseArtifacts(phase, featurePath) {
63
66
  const rel = `${entry.phaseDir}/${entry.filename}`;
64
67
  if (!existsSync(join(featurePath, entry.phaseDir, entry.filename))) missing.push(rel);
65
68
  }
69
+ if (phase === 'implement') missing.push(...unfinishedSliceReports(featurePath));
66
70
  return missing;
67
71
  } catch {
68
72
  return [];
69
73
  }
70
74
  }
71
75
 
76
+ function unfinishedSliceReports(featurePath) {
77
+ const progress = sliceProgress(featurePath);
78
+ if (!progress) return ['2-plan/checks.json'];
79
+ return progress.slices.filter((s) => s.status !== 'feito').map((s) => `3-implement/reports/${s.id}.md`);
80
+ }
81
+
72
82
  /**
73
83
  * When a required artifact is absent HERE, check whether it exists in the
74
84
  * worktree that owns this feature. `feature.json` travels committed on
@@ -105,6 +115,16 @@ function resolveApprovedBy(options) {
105
115
  return 'auto';
106
116
  }
107
117
 
118
+ export function reportDotnetIgnores(added) {
119
+ if (added.length) console.log(chalk.gray(` .gitignore: ${added.join(' ')} adicionados (projeto .NET) — commite antes do primeiro build.`));
120
+ }
121
+
122
+ function reportDesignSeed({ mockups, designSystem }) {
123
+ const rel = (p) => relative(process.cwd(), p).replace(/\\/g, '/');
124
+ if (mockups.written) console.log(chalk.gray(` Escrito ${rel(mockups.path)} a partir do template (checklist de tela por \`## Tela:\`).`));
125
+ if (designSystem.written) console.log(chalk.gray(` Escrito ${rel(designSystem.path)} a partir do template (o projeto não tinha design system).`));
126
+ }
127
+
108
128
  export async function advanceCommand(featureName, options = {}) {
109
129
  try {
110
130
  const feature = getFeature(featureName);
@@ -127,6 +147,21 @@ export async function advanceCommand(featureName, options = {}) {
127
147
  const featurePath = join(process.cwd(), '.morph', 'features', featureName);
128
148
  const phase = derivePhase(featurePath);
129
149
 
150
+ // The plan gate is often closed by the human's answer to the gate question
151
+ // (state-sync), which already moves the derived phase to implement: taking
152
+ // the design branch then is opening 1-design/, not closing a phase.
153
+ if (options.design && phase === 'implement' && feature.approvalGates?.plan?.approved === true) {
154
+ mkdirSync(join(featurePath, '1-design'), { recursive: true });
155
+ reportDesignSeed(seedDesignArtifacts(featureName, process.cwd()));
156
+ reportDotnetIgnores(seedDotnetBuildIgnores(process.cwd(), { featureName }));
157
+ try {
158
+ const { writeHandoff } = await import('../../lib/generators/handoff-generator.js');
159
+ writeHandoff(featureName, process.cwd());
160
+ } catch { /* fail-open */ }
161
+ console.log(chalk.green(`\n✅ "${featureName}": gate plan já aprovado — ramo de design aberto (1-design/).\n`));
162
+ return;
163
+ }
164
+
130
165
  if (phase === 'review') {
131
166
  console.log(chalk.yellow(`\n⚠️ Feature "${featureName}" is already at the final phase (review).`));
132
167
  console.log(chalk.gray(`Approve Gate 3 with: morph-spec approve ${featureName} review`));
@@ -134,11 +169,9 @@ export async function advanceCommand(featureName, options = {}) {
134
169
  return;
135
170
  }
136
171
 
137
- // Phase-exit artifact check (D3): a phase transition must not step over the
138
- // artifact the phase it is CLOSING owes. Observed: a feature reached review
139
- // with NO 3-implement/recap.md — the file was written later, by hand, so
140
- // the Gate 3 review had nothing to review. `implement` has no gate, which
141
- // is precisely why nothing stopped it.
172
+ // Phase-exit artifact check: a phase transition must not step over what the
173
+ // phase it is CLOSING owes. `implement` has no gate, so this is the only
174
+ // thing that keeps a feature with unfinished slice reports out of review.
142
175
  //
143
176
  // `--force` bypasses (same deliberate-recovery role it already plays for
144
177
  // gate order) but still names what was skipped — a silent bypass would be
@@ -160,7 +193,7 @@ export async function advanceCommand(featureName, options = {}) {
160
193
  process.exit(1);
161
194
  }
162
195
 
163
- console.error(chalk.red(`\n❌ A fase "${phase}" não pode ser encerrada — artefato(s) obrigatório(s) ausente(s):`));
196
+ console.error(chalk.red(`\n❌ A fase "${phase}" não pode ser encerrada — artefato(s) obrigatório(s) ausente(s) ou PARCIAL:`));
164
197
  for (const rel of missingArtifacts) console.error(chalk.gray(` • ${rel}`));
165
198
  console.error(chalk.gray('\n Produza o artefato e rode de novo, ou passe --force para avançar assim mesmo.\n'));
166
199
  process.exit(1);
@@ -170,25 +203,11 @@ export async function advanceCommand(featureName, options = {}) {
170
203
  const gate = PHASE_GATE[phase];
171
204
  const approvedBy = resolveApprovedBy(options);
172
205
 
173
- // The screen checklist is part of what the uiux phase owes. The predicate
174
- // is approve.js's too: an "auto" signature never closes a gate the
175
- // gate-check would pause, and a recorded decision or a human signature
176
- // (--approver) is the answer to that pause; --force is not, as in approve.
177
- const checklistBlock = phase === 'uiux' ? uiuxAutoApprovalBlock(process.cwd(), featureName, feature) : null;
178
- if (checklistBlock && approvedBy === 'auto') {
179
- console.error(chalk.red(`\n❌ A fase "uiux" não pode ser encerrada com assinatura automática — ${checklistBlock}.`));
180
- console.error(chalk.gray('\n Complete o checklist (ver a skill morph-uiux) e rode de novo. Quem decidiu aprovar assim'));
181
- console.error(chalk.gray(` mesmo grava a decisão (morph-spec gate-decision ${featureName} uiux --pause false --reason "...")`));
182
- console.error(chalk.gray(' ou assina com --approver <nome>. O --force não dispensa o checklist.\n'));
183
- process.exit(1);
184
- }
185
-
186
206
  // Gate monotonicity (mirrors approve.js): advance is a gate write-path too,
187
207
  // so it must not stamp gate N while a predecessor is pending. In the
188
- // sanctioned flow advance is always in-order (it approves G1 before the plan
189
- // artifact that flips the phase to plan exists), but an anomalous filesystem
190
- // state — e.g. 2-plan/spec.md present without Gate 1, the lead-termometro
191
- // shape — would otherwise let advance deepen the violation by stamping G2.
208
+ // sanctioned flow advance is always in-order, but an anomalous filesystem
209
+ // state — e.g. 1-design/design-system.md present without the plan gate —
210
+ // would otherwise let advance deepen the violation by stamping uiux.
192
211
  // Escape via --force for deliberate recovery.
193
212
  if (gate) {
194
213
  const unmet = unmetPrereqs(gate, feature.approvalGates);
@@ -199,24 +218,11 @@ export async function advanceCommand(featureName, options = {}) {
199
218
  }
200
219
  }
201
220
 
202
- // Hotfix backstop (defensive, mirrors approve.js): the phase==='review'
203
- // early-return above already stops advance from ever reaching a
204
- // gate==='review' approval, but guard the approval itself too so this
205
- // stays true even if the early-return's shape changes later. Gate on the
206
- // RESOLVED approvedBy — headless contexts have no TTY and resolve to
207
- // "auto" regardless of --mode, so checking the raw --mode string alone
208
- // would let them slip through (same bug the approve.js backstop had).
209
- if (gate === 'review' && feature.workType === 'hotfix' && approvedBy === 'auto') {
210
- console.error(chalk.red('\n❌ Hotfix exige aprovação humana no Gate 3 — trust:auto não se aplica.'));
211
- console.error(chalk.gray(' Aprove com --approver <nome>, num terminal interativo, ou pela resposta humana "aprovado" (state-sync) ao review-report via AskUserQuestion.\n'));
212
- process.exit(1);
213
- }
214
-
215
221
  // Gate idempotency (F15): `setApprovalGate` REPLACES the gate object whole
216
222
  // (state-manager.js), so re-writing an already-approved gate does not
217
223
  // refresh it — it destroys `approvedBy`, `timestamp`, `notes` and
218
224
  // `selfAssessed` in one assignment. And `resolveApprovedBy` above answers
219
- // "auto" in every headless context, so `approve f proposal --approver
225
+ // "auto" in every headless context, so `approve f plan --approver
220
226
  // lucas` followed by `advance f` used to rewrite a human's signature as
221
227
  // "auto" — the one stamp the charter forbids on a human-approved gate.
222
228
  // The state-sync hook already carries exactly this guard; `advance`, the
@@ -228,6 +234,14 @@ export async function advanceCommand(featureName, options = {}) {
228
234
  const priorGate = gate ? feature.approvalGates?.[gate] : null;
229
235
  const alreadyApproved = priorGate?.approved === true;
230
236
 
237
+ const refusal = alreadyApproved ? null
238
+ : autoApprovalRefusal({ cwd: process.cwd(), featureName, feature, gate, approvedBy });
239
+ if (refusal) {
240
+ printRefusal(chalk, refusal);
241
+ process.exit(1);
242
+ }
243
+ const checklistBlock = phase === 'uiux' && !alreadyApproved ? uiuxAutoApprovalBlock(process.cwd(), featureName, feature) : null;
244
+
231
245
  if (gate && !alreadyApproved) {
232
246
  await setApprovalGate(featureName, gate, true, {
233
247
  approvedBy,
@@ -238,11 +252,13 @@ export async function advanceCommand(featureName, options = {}) {
238
252
 
239
253
  // 2) Scaffold the next phase folder so the agent can write its first artifact.
240
254
  let nextDir = NEXT_PHASE_DIR[phase];
241
- if (phase === 'proposal' && options.design) nextDir = '1-design';
255
+ if (phase === 'plan' && options.design) nextDir = '1-design';
242
256
  if (nextDir) {
243
257
  const nextPath = join(featurePath, nextDir);
244
258
  if (!existsSync(nextPath)) mkdirSync(nextPath, { recursive: true });
245
259
  }
260
+ if (nextDir === '1-design') reportDesignSeed(seedDesignArtifacts(featureName, process.cwd()));
261
+ if (phase === 'plan') reportDotnetIgnores(seedDotnetBuildIgnores(process.cwd(), { featureName }));
246
262
 
247
263
  // Fleet handoff: a phase transition is exactly when a session hands the
248
264
  // feature over (to a new instance, or to itself after /compact) — refresh