@polymorphism-tech/morph-spec 8.44.0 → 8.46.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 (199) hide show
  1. package/CLAUDE.md +11 -9
  2. package/README.md +10 -10
  3. package/bin/morph-spec.js +11 -13
  4. package/bin/validate.js +379 -379
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +2 -4
  7. package/docs/QUICKSTART.md +6 -6
  8. package/framework/CLAUDE.md +6 -8
  9. package/framework/MORPH.md +39 -45
  10. package/framework/agents.json +8 -9
  11. package/framework/commands/morph-apply.md +1 -3
  12. package/framework/commands/morph-hotfix.md +1 -1
  13. package/framework/commands/morph-proposal.md +2 -2
  14. package/framework/docs/CLI.md +15 -14
  15. package/framework/evals/review-checklist.md +90 -0
  16. package/framework/evals/trace-schema.md +2 -12
  17. package/framework/hooks/README.md +2 -3
  18. package/framework/hooks/claude-code/core/heavy-gate.js +1 -1
  19. package/framework/hooks/claude-code/core/protect-spec-files.js +2 -2
  20. package/framework/hooks/claude-code/core/telemetry-log.js +2 -2
  21. package/framework/hooks/claude-code/core/trace-autogen.js +1 -22
  22. package/framework/hooks/claude-code/core/validate-completion.js +3 -56
  23. package/framework/hooks/claude-code/statusline.sh +11 -11
  24. package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
  25. package/framework/hooks/git/pre-commit/agents.sh +25 -25
  26. package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
  27. package/framework/hooks/git/pre-commit/specs.sh +50 -50
  28. package/framework/hooks/git/pre-push/run-tests.sh +44 -44
  29. package/framework/hooks/shared/build-check.js +1 -1
  30. package/framework/hooks/shared/comment-scanner.js +1 -1
  31. package/framework/hooks/shared/phase-utils.js +0 -5
  32. package/framework/hooks/shared/stdin-reader.js +26 -26
  33. package/framework/phases.json +4 -6
  34. package/framework/rubrics/backend.json +1 -1
  35. package/framework/rubrics/default.json +1 -1
  36. package/framework/rubrics/frontend.json +1 -1
  37. package/framework/rules/morph-workflow.md +6 -6
  38. package/framework/schemas/config.schema.json +4 -0
  39. package/framework/schemas/feature.schema.json +5 -29
  40. package/framework/schemas/tasks.schema.json +6 -2
  41. package/framework/skills/README.md +1 -2
  42. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  43. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  44. package/framework/skills/core/morph-apply/SKILL.md +145 -246
  45. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  46. package/framework/skills/core/morph-hotfix/SKILL.md +5 -3
  47. package/framework/skills/core/morph-implement/SKILL.md +101 -201
  48. package/framework/skills/core/morph-machine/SKILL.md +3 -3
  49. package/framework/skills/core/morph-media/SKILL.md +1 -1
  50. package/framework/skills/core/morph-plan/SKILL.md +171 -287
  51. package/framework/skills/core/morph-proposal/SKILL.md +4 -5
  52. package/framework/skills/core/morph-review/SKILL.md +122 -430
  53. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  54. package/framework/skills/core/morph-standards/SKILL.md +2 -2
  55. package/framework/skills/core/morph-uiux/SKILL.md +2 -3
  56. package/framework/standards/STANDARDS.json +10 -10
  57. package/framework/standards/ai-agents/agent-archetypes.md +1 -1
  58. package/framework/standards/ai-agents/agent-session.md +1 -1
  59. package/framework/standards/ai-agents/agent-spec.md +1 -1
  60. package/framework/standards/ai-agents/batching.md +1 -1
  61. package/framework/standards/ai-agents/context-providers.md +1 -1
  62. package/framework/standards/ai-agents/conversational-agent-with-phases.md +1 -1
  63. package/framework/standards/ai-agents/cost-and-budget.md +1 -1
  64. package/framework/standards/ai-agents/durable-workflows-hitl.md +1 -1
  65. package/framework/standards/ai-agents/guardrails.md +1 -1
  66. package/framework/standards/ai-agents/llm-runtime-defaults.md +1 -1
  67. package/framework/standards/ai-agents/mcp-server.md +1 -1
  68. package/framework/standards/ai-agents/mcp-tools.md +1 -1
  69. package/framework/standards/ai-agents/media-pipeline.md +1 -1
  70. package/framework/standards/ai-agents/media-video.md +1 -1
  71. package/framework/standards/ai-agents/middleware-patterns.md +1 -1
  72. package/framework/standards/ai-agents/modalities-image-gen.md +1 -1
  73. package/framework/standards/ai-agents/modalities-speech-to-text.md +1 -1
  74. package/framework/standards/ai-agents/modalities-text-to-speech.md +1 -1
  75. package/framework/standards/ai-agents/modalities-vision.md +1 -1
  76. package/framework/standards/ai-agents/multi-agent-patterns.md +1 -1
  77. package/framework/standards/ai-agents/observability-patterns.md +3 -3
  78. package/framework/standards/ai-agents/production.md +2 -2
  79. package/framework/standards/ai-agents/prompt-sources.md +1 -1
  80. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  81. package/framework/standards/ai-agents/rag-custom-pgvector.md +1 -1
  82. package/framework/standards/ai-agents/service-tier-flex.md +1 -1
  83. package/framework/standards/ai-agents/setup.md +1 -1
  84. package/framework/standards/ai-agents/structured-output.md +1 -1
  85. package/framework/standards/ai-agents/sweet-spot.md +1 -1
  86. package/framework/standards/ai-agents/testing-ai.md +1 -1
  87. package/framework/standards/ai-agents/tool-adapters.md +1 -1
  88. package/framework/standards/ai-agents/vector-data-extensions.md +1 -1
  89. package/framework/standards/ai-agents/workflows.md +1 -1
  90. package/framework/templates/CLAUDE.md.core.template +1 -1
  91. package/framework/templates/IDE-SNIPPETS.md +262 -262
  92. package/framework/templates/README.md +1 -1
  93. package/framework/templates/REGISTRY.json +0 -81
  94. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  95. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
  96. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  97. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  98. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  99. package/framework/templates/docs/spec.md +19 -205
  100. package/framework/templates/docs/ui-components.md +52 -52
  101. package/framework/templates/docs/ui-design-system.md +76 -76
  102. package/framework/templates/docs/ui-flows.md +167 -167
  103. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  104. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  105. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  106. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  107. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  108. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  109. package/framework/templates/infrastructure/github/README.md +56 -56
  110. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  111. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  112. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  113. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  114. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  115. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  116. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
  117. package/framework/templates/state.template.json +17 -17
  118. package/framework/templates/ui/design-system.css +226 -226
  119. package/package.json +2 -2
  120. package/src/commands/e2e/e2e.js +1 -1
  121. package/src/commands/project/heavy.js +1 -1
  122. package/src/commands/project/status.js +3 -3
  123. package/src/commands/state/approve.js +7 -49
  124. package/src/commands/state/gate-check.js +16 -106
  125. package/src/commands/state/gate-decision.js +1 -1
  126. package/src/commands/state/report-check.js +42 -17
  127. package/src/commands/validation/mutate.js +1 -1
  128. package/src/commands/validation/scout-check.js +28 -0
  129. package/src/commands/validation/validate-feature.js +5 -32
  130. package/src/commands/validation/verify.js +98 -48
  131. package/src/core/paths/output-schema.js +0 -2
  132. package/src/core/state/state-manager.js +1 -2
  133. package/src/core/templates/template-renderer.js +477 -477
  134. package/src/dashboard/app/lib/format.js +5 -0
  135. package/src/dashboard/app/main.jsx +13 -13
  136. package/src/dashboard/app/pages/FeatureDetail.jsx +16 -22
  137. package/src/dashboard/app/pages/Features.jsx +7 -7
  138. package/src/dashboard/app/pages/PhasePipeline.jsx +1 -1
  139. package/src/dashboard/app/pages/Rubrics.jsx +7 -11
  140. package/src/dashboard/scanner/docs-scanner.js +1 -2
  141. package/src/dashboard/scanner/features-scanner.js +3 -4
  142. package/src/dashboard/scanner/rubrics-scanner.js +12 -12
  143. package/src/dashboard/vite.config.js +24 -24
  144. package/src/lib/agents/output-collector.js +0 -1
  145. package/src/lib/ci/ci-verify.js +118 -0
  146. package/src/lib/ci/github-ci.js +192 -0
  147. package/src/lib/ci/workflow-scaffold.js +122 -0
  148. package/src/lib/detectors/design-system-detector.js +188 -188
  149. package/src/lib/evaluator-report.js +52 -334
  150. package/src/lib/generators/design-system-generator.js +508 -508
  151. package/src/lib/generators/handoff-generator.js +4 -5
  152. package/src/lib/installers/memory-installer.js +2 -2
  153. package/src/lib/mutation/manifest.js +8 -2
  154. package/src/lib/mutation/report.js +5 -3
  155. package/src/lib/mutation/runner.js +9 -7
  156. package/src/lib/risk-detector.js +60 -122
  157. package/src/lib/scout/scout-check.js +293 -0
  158. package/src/lib/tasks/task-classifier.js +30 -30
  159. package/src/lib/tasks/task-parser.js +209 -209
  160. package/src/lib/tasks/tasks-json-parser.js +7 -5
  161. package/src/lib/tasks/test-nodes.js +1 -1
  162. package/src/lib/tasks/test-runner.js +1 -1
  163. package/src/lib/telemetry/verify-event.js +3 -2
  164. package/src/lib/validators/css/css-validator.js +340 -340
  165. package/src/lib/validators/reuse/reuse-validator.js +6 -8
  166. package/src/lib/validators/verify-runner.js +1 -1
  167. package/src/scripts/setup-infra.js +1 -1
  168. package/src/utils/agent-id-mapper.js +11 -11
  169. package/src/utils/agents-installer.js +5 -9
  170. package/src/utils/claude-settings-manager.js +300 -300
  171. package/src/utils/hooks-installer.js +3 -10
  172. package/src/utils/skills-installer.js +1 -1
  173. package/framework/evals/proof-rubrics/README.md +0 -76
  174. package/framework/evals/proof-rubrics/eval-de-modelo.md +0 -80
  175. package/framework/evals/proof-rubrics/fiacao.md +0 -54
  176. package/framework/evals/proof-rubrics/guard-cego.md +0 -52
  177. package/framework/evals/proof-rubrics/media-claims.md +0 -109
  178. package/framework/evals/proof-rubrics/mutation-proof.md +0 -107
  179. package/framework/evals/proof-rubrics/observabilidade-genai.md +0 -119
  180. package/framework/evals/proof-rubrics/verde-nao-e-coberto.md +0 -48
  181. package/framework/evals/rubrics/README.md +0 -45
  182. package/framework/evals/rubrics/architecture.md +0 -103
  183. package/framework/evals/rubrics/code-quality.md +0 -98
  184. package/framework/evals/rubrics/condensed.md +0 -57
  185. package/framework/evals/rubrics/contracts.md +0 -67
  186. package/framework/evals/rubrics/test-coverage.md +0 -99
  187. package/framework/evals/scenarios/dotnet-crud.yaml +0 -42
  188. package/framework/evals/scenarios/nextjs-auth.yaml +0 -38
  189. package/framework/evals/suites/standard.yaml +0 -5
  190. package/framework/hooks/claude-code/__pycache__/statusline.cpython-313.pyc +0 -0
  191. package/framework/hooks/claude-code/core/post-edit-typecheck.js +0 -127
  192. package/framework/skills/core/morph-eval/SKILL.md +0 -64
  193. package/framework/templates/.idea/morph-templates.xml +0 -92
  194. package/framework/templates/.vscode/morph-templates.code-snippets +0 -186
  195. package/framework/templates/docs/clarifications.md +0 -253
  196. package/framework/templates/docs/onboarding.md +0 -122
  197. package/framework/templates/docs/schema-analysis.md +0 -119
  198. package/framework/templates/docs/user-stories.md +0 -34
  199. package/src/commands/state/score.js +0 -147
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polymorphism-tech/morph-spec",
3
- "version": "8.44.0",
3
+ "version": "8.46.0",
4
4
  "description": "MORPH-SPEC: NLH (Natural Language Harness) for spec-driven development with Claude Code",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -44,7 +44,7 @@
44
44
  "generate:check": "node scripts/generate-refs.js --check && node scripts/generate-ai-pin-props.js --check && node scripts/api-probe/probe.mjs --check",
45
45
  "generate-standards": "node scripts/generate-standards-registry.js",
46
46
  "generate-standards:check": "node scripts/generate-standards-registry.js --check",
47
- "validate": "node scripts/validate-framework.js && node scripts/check-rubric-parity.js",
47
+ "validate": "node scripts/validate-framework.js",
48
48
  "freshness": "node scripts/check-freshness.js",
49
49
  "verify": "npm run generate:check && npm run generate-standards:check && npm run validate && npm run freshness && npm test",
50
50
  "test": "node scripts/run-tests.mjs",
@@ -178,7 +178,7 @@ export async function e2eUpCommand(featureName, options = {}) {
178
178
  if (options.json) {
179
179
  console.log(JSON.stringify({ status: 'busy', feature: featureName, busy: { reason: err.reason, waitedMs: err.waitedMs } }, null, 2));
180
180
  }
181
- console.error(chalk.yellow(`\n⏳ e2e up não rodou: ${err.message}. Tente de novo quando liberar (\`morph-spec heavy status\`).\n`));
181
+ console.error(chalk.yellow(`\n⏳ e2e up não rodou: ${err.message}. Não é vermelho — a fila já esperou o prazo dela: não repita em laço — siga sem esta rodada e avise o usuário (\`morph-spec heavy status\`).\n`));
182
182
  process.exit(EXIT_BUSY);
183
183
  } finally {
184
184
  gate.release();
@@ -45,7 +45,7 @@ export function heavyRunCommand(command, options = {}) {
45
45
  gate.enter(file);
46
46
  } catch (err) {
47
47
  if (!(err instanceof HeavyBusyError)) throw err;
48
- console.error(chalk.yellow(`\n⏳ ${file} não rodou: ${err.message}. Tente de novo quando liberar (\`morph-spec heavy status\`).\n`));
48
+ console.error(chalk.yellow(`\n⏳ ${file} não rodou: ${err.message}. Não é vermelho — a fila já esperou o prazo dela: não repita em laço — siga sem esta rodada e avise o usuário (\`morph-spec heavy status\` mostra quem segura os slots).\n`));
49
49
  process.exit(EXIT_BUSY);
50
50
  }
51
51
 
@@ -64,7 +64,7 @@ function checkPhaseOutputs(phaseKey, feature) {
64
64
  const phaseOutputMap = {
65
65
  proposal: ['proposal', 'stackScan'],
66
66
  uiux: ['uiDesignSystem', 'uiMockups', 'uiComponents', 'uiFlows'],
67
- plan: ['spec', 'mandate', 'tasks', 'decisions'],
67
+ plan: ['spec', 'tasks', 'decisions'],
68
68
  implement: ['recap'],
69
69
  review: ['reviewReport']
70
70
  };
@@ -102,7 +102,7 @@ function formatPhase(phaseKey, currentPhase, feature, options = {}) {
102
102
  const phaseOutputMap = {
103
103
  proposal: ['proposal', 'stackScan'],
104
104
  uiux: ['uiDesignSystem', 'uiMockups', 'uiComponents', 'uiFlows'],
105
- plan: ['spec', 'mandate', 'tasks', 'decisions'],
105
+ plan: ['spec', 'tasks', 'decisions'],
106
106
  implement: ['recap'],
107
107
  review: ['reviewReport']
108
108
  };
@@ -160,7 +160,7 @@ function getNextSteps(feature) {
160
160
  // Check for missing outputs in current phase
161
161
  const phaseOutputMap = {
162
162
  proposal: ['proposal'],
163
- plan: ['spec', 'mandate', 'tasks'],
163
+ plan: ['spec', 'tasks'],
164
164
  implement: ['recap'],
165
165
  review: ['reviewReport']
166
166
  };
@@ -5,7 +5,7 @@ import { existsSync, readFileSync } from 'fs';
5
5
  import { join } from 'path';
6
6
  import { getFeature, setApprovalGate, getApprovalGate, getApprovalHistory, derivePhase } from '../../core/state/state-manager.js';
7
7
  import { unmetPrereqs } from '../../core/state/gate-order.js';
8
- import { evaluatorReportState, COMPOSITE_SCORE_TOKEN } from '../../lib/evaluator-report.js';
8
+ import { evaluatorReportState, VERDICT_TOKEN } from '../../lib/evaluator-report.js';
9
9
  import { assertOwnerTree, ownerTreeRefusalLines } from '../../lib/worktree/feature-truth.js';
10
10
  import { uiuxAutoApprovalBlock } from '../../lib/specs/screen-checklist.js';
11
11
  import { treeMovedSince } from '../../lib/git/evidence-staleness.js';
@@ -247,18 +247,6 @@ export async function approveCommand(featureName, gateName, options = {}) {
247
247
  process.exit(1);
248
248
  }
249
249
 
250
- // Gate 3 evaluator backstop: the composite score of a feature comes from
251
- // the INDEPENDENT evaluator (persona `evaluator`, fresh context) dispatched
252
- // by morph-review — never from self-assessment by whoever implemented it.
253
- // Nothing enforced that. When the dispatch was impossible (observed: a
254
- // session instruction forbade sub-agents), the flow still produced a
255
- // review-report.md carrying a composite score and a verdict, with a caveat
256
- // in prose nobody is obliged to read. A gate that yields a grade without
257
- // its evaluator is not a gate.
258
- //
259
- // The escape hatch is EXPLICIT and RECORDED (`--self-assessed`), never
260
- // silent — the silence is the defect. `--force` deliberately does NOT
261
- // bypass this: it is the out-of-order recovery flag, a different concern.
262
250
  // Gate 3 staleness backstop (D1): a review reviews a TREE, and the tree
263
251
  // moves. Observed: review-report.md written at b30ac6e, then T3 landed and
264
252
  // inverted the default of a business rule; the report stayed in the repo
@@ -346,25 +334,16 @@ export async function approveCommand(featureName, gateName, options = {}) {
346
334
  process.exit(1);
347
335
  }
348
336
  } else {
349
- // `existsSync` puro não bastava: um evaluator-report SEM o token
350
- // `**Composite-Score:**` passava aqui E era descartado pelo gate-check
351
- // (que então caía no proxy de taskScores). O resultado era um Gate 3
352
- // carimbado `approvedBy: "auto"` sobre a média das auto-avaliações de
353
- // quem implementou — precisamente o que MORPH.md §5 proíbe. Arquivo
354
- // presente sem veredito legível é relatório OCO, não relatório.
355
337
  const report = evaluatorReportState(process.cwd(), featureName);
356
338
  if (report.status !== 'ok') {
357
339
  if (!options.selfAssessed) {
358
340
  if (report.status === 'missing') {
359
341
  console.error(chalk.red('\n❌ Gate 3 exige o avaliador independente — `4-review/evaluator-report.md` não existe.'));
360
342
  } else {
361
- console.error(chalk.red('\n❌ Gate 3: o `4-review/evaluator-report.md` existe mas está OCO —'));
362
- console.error(chalk.red(` falta a linha \`${COMPOSITE_SCORE_TOKEN} {n.n}\`, o veredito legível por máquina.`));
363
- console.error(chalk.gray(' Sem ela o gate-check não lê o score real e cai na média das auto-avaliações.'));
364
- console.error(chalk.gray(' Peça ao avaliador o relatório completo (morph-review §4) ou complete a linha.'));
343
+ console.error(chalk.red(`\n❌ Gate 3: o \`4-review/evaluator-report.md\` não tem \`${VERDICT_TOKEN} SHIP|FIX\`.`));
344
+ console.error(chalk.gray(' Peça ao avaliador o relatório completo (morph-review §4) ou retome-o por SendMessage.'));
365
345
  }
366
- console.error(chalk.gray(' O score composto da feature vem do avaliador (persona `evaluator`, contexto fresco)'));
367
- console.error(chalk.gray(' despachado pela morph-review, nunca de auto-avaliação de quem implementou.'));
346
+ console.error(chalk.gray(' O veredito vem do avaliador (persona `evaluator`, contexto fresco), nunca de quem implementou.'));
368
347
  console.error(chalk.gray(' Se o dispatch for impossível nesta sessão, aprove com --self-assessed —'));
369
348
  console.error(chalk.gray(' a degradação fica registrada no feature.json e aparece no `list`/`fleet`.\n'));
370
349
  process.exit(1);
@@ -372,30 +351,9 @@ export async function approveCommand(featureName, gateName, options = {}) {
372
351
  selfAssessed = true;
373
352
  }
374
353
 
375
- // REAVALIAÇÃO VISÍVEL. O `evaluator-report.md` é append-only, então uma
376
- // segunda rodada acrescenta um `**Composite-Score:**` ao fim e o arquivo
377
- // passa a carregar mais de um veredito. O gate decide pelo ÚLTIMO (o
378
- // vigente) — mas aprovar em silêncio um relatório com notas superadas
379
- // esconde do humano exatamente o fato que ele precisa ver: a nota MUDOU,
380
- // e numa das rodadas medidas em campo ela CAIU (8.9 → 8.3, quando uma
381
- // correção abriu um bloqueante).
382
- //
383
- // Aqui é o lugar: `approve` é a precondição humana do Gate 3 e a única
384
- // superfície onde uma pessoa lê o veredito antes de carimbá-lo. Sem este
385
- // bloco, `scoreTokens`/`superseded` seriam mitigação inerte — campo que
386
- // ninguém lê não mitiga nada.
387
- if (report.status === 'ok' && report.scoreTokens > 1) {
388
- console.log(chalk.yellow(
389
- `
390
- ⚠️ O relatório do avaliador tem ${report.scoreTokens} vereditos (o arquivo é append-only).`
391
- ));
392
- console.log(chalk.gray(` Vigente (o que o gate usa): ${report.score}`));
393
- console.log(chalk.gray(` Superado(s), na ordem do arquivo: ${report.superseded.join(', ')}`));
394
- if (report.superseded.some((n) => n > report.score)) {
395
- console.log(chalk.yellow(' A nota CAIU em relação a uma rodada anterior — confira o que a correção'));
396
- console.log(chalk.yellow(' abriu antes de aprovar; o relatório mantém a análise das duas rodadas.'));
397
- }
398
- console.log('');
354
+ if (report.status === 'ok' && report.superseded.length > 0) {
355
+ console.log(chalk.yellow(`\n⚠️ O relatório do avaliador tem ${report.superseded.length + 1} vereditos (append-only).`));
356
+ console.log(chalk.gray(` Vigente: ${report.verdict}, ${report.blockers.length} bloqueador(es). Superados: ${report.superseded.join(', ')}\n`));
399
357
  }
400
358
  }
401
359
  }
@@ -3,7 +3,7 @@ import { existsSync, readFileSync } from 'fs';
3
3
  import { getFeature } from '../../core/state/state-manager.js';
4
4
  import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
5
5
  import { shouldPauseGate, getRiskSignals, classifyE2eSkip } from '../../lib/risk-detector.js';
6
- import { evaluatorCompositeScore, evaluatorProofState, minProofScore, mediaProofScore } from '../../lib/evaluator-report.js';
6
+ import { evaluatorReportState, evaluatorMediaSection } from '../../lib/evaluator-report.js';
7
7
  import { collectScreenChecklist } from '../../lib/specs/screen-checklist.js';
8
8
  import { persistGateDecision, VALID_GATES, readTrustFromConfig } from './gate-decision.js';
9
9
  import { minimatch } from 'minimatch';
@@ -24,13 +24,8 @@ import { assertOwnerTree, ownerTreeRefusalLines } from '../../lib/worktree/featu
24
24
  * - standardsViolations / designSystemDivergence: still an LLM's
25
25
  * natural-language read (see the R4 report — no deterministic detector
26
26
  * exists in JS yet).
27
- * - evalScore: escape hatch for the TRUE Gate-3 composite score. Since R4
28
- * the evaluator-report carries a machine-readable `**Composite-Score:**`
29
- * token that this command parses by itself (see evaluatorCompositeScore),
30
- * so the override is only needed for legacy reports without the token —
31
- * there the taskScores-average proxy is the fallback.
32
27
  */
33
- const OVERRIDABLE_SIGNAL_KEYS = ['standardsViolations', 'designSystemDivergence', 'evalScore'];
28
+ const OVERRIDABLE_SIGNAL_KEYS = ['standardsViolations', 'designSystemDivergence'];
34
29
 
35
30
  /**
36
31
  * Os paths de standard que fazem uma feature ser "de midia".
@@ -96,31 +91,6 @@ function readTasks(featureName) {
96
91
  }
97
92
  }
98
93
 
99
- /**
100
- * Composite eval score PROXY: the average of `lastScore` across every task
101
- * recorded in `feature.taskScores`, rounded to 1 decimal. `lastScore` (not
102
- * `bestScore`) on purpose — a gate check reports current-state risk, and
103
- * `bestScore` can be a stale high-water mark from an attempt the code no
104
- * longer matches. "average" mirrors morph-apply's own Gate-3 risk-signal
105
- * prose ("Score médio < 9 no review-report" — SKILL.md §5).
106
- *
107
- * This is explicitly NOT the true Gate-3 composite score (see
108
- * OVERRIDABLE_SIGNAL_KEYS doc above) — undefined (key omitted) when no task
109
- * has been scored yet, so shouldPauseGate's `typeof === 'number'` check
110
- * naturally skips it instead of pausing on a false 0.
111
- *
112
- * @param {object} feature
113
- * @returns {number|undefined}
114
- */
115
- function compositeEvalScore(feature) {
116
- const scores = Object.values(feature.taskScores || {})
117
- .map((t) => t.lastScore)
118
- .filter((n) => typeof n === 'number');
119
- if (scores.length === 0) return undefined;
120
- const avg = scores.reduce((a, b) => a + b, 0) / scores.length;
121
- return Math.round(avg * 10) / 10;
122
- }
123
-
124
94
  /**
125
95
  * "Esta feature mexeu em prompt ou em modelo?" — em duas metades, porque uma só
126
96
  * não distingue as duas situações que pedem ações diferentes.
@@ -203,78 +173,25 @@ function collectSignals(featureName, feature, cwd, gateName) {
203
173
  if (gateName === 'plan') signals.oversizedTasks = oversizedTasks({ tasks });
204
174
 
205
175
  // Só onde o checklist decide algo: no fecho do design e no ship. Fato de
206
- // disco, fora de OVERRIDABLE_SIGNAL_KEYS como `mutationProof`.
176
+ // disco, fora de OVERRIDABLE_SIGNAL_KEYS.
207
177
  if (gateName === 'uiux' || gateName === 'review') {
208
178
  signals.screenChecklist = collectScreenChecklist(cwd, featureName, gateName);
209
179
  }
210
180
 
211
- const evalScore = compositeEvalScore(feature);
212
- if (evalScore !== undefined) {
213
- signals.evalScore = evalScore;
214
- signals.evalScoreSource = 'taskScores-proxy';
215
- }
216
-
217
- // No Gate 3, o score verdadeiro do avaliador independente (token
218
- // Composite-Score no evaluator-report.md) tem precedência sobre o proxy.
219
181
  if (gateName === 'review') {
220
- const trueComposite = evaluatorCompositeScore(cwd, featureName);
221
- if (trueComposite !== undefined) {
222
- signals.evalScore = trueComposite;
223
- signals.evalScoreSource = 'evaluator-report';
224
- }
225
-
226
- // PROVA DE MUTAÇÃO, derivada do MESMO módulo leitor do relatório. Um score
227
- // alto diz o que o avaliador ACHOU; a seção `## Mutações` diz se ele
228
- // PROVOU. Sem ela, um Gate 3 auto-passa sobre uma suíte que ninguém mostrou
229
- // que morde — o defeito que as rubricas de `evals/proof-rubrics/` existem
230
- // para nomear.
231
- //
232
- // `productionCode` viaja DENTRO do sinal para que risk-detector.js continue
233
- // puro sobre fatos já reunidos: a divisão é "gate-check COLETA do disco,
234
- // risk-detector DECIDE". Uma feature 100% `docs` não tem o que mutar, e
235
- // exigir prova dela seria pedir uma cerimônia vazia — que é como uma regra
236
- // vira ruído e depois é desligada.
237
- //
238
- // Task SEM `group` conta como código de produção: `group` é opcional no
239
- // schema e o default documentado é `backend`. O conservador aqui é assumir
240
- // que HÁ código — errar para o lado de pedir prova custa uma pausa; errar
241
- // para o outro lado devolve o gate cego.
242
- //
243
- // `tasks` vazio (plano ausente ou ilegível) ⇒ `productionCode: false` ⇒ NÃO
244
- // pausa. Fail-open deliberado: sem plano, não há como AFIRMAR que existe
245
- // código de produção, e uma regra nova não pode travar feature legada.
246
- //
247
- // Nada disto entra em OVERRIDABLE_SIGNAL_KEYS: são fatos de disco, como
248
- // `gate`/`workType`/`e2e*`. Um `--signals '{"mutationProof":{"section":true}}'`
249
- // compraria o Gate 3 exatamente como um `e2eStatus: "pass"` forjado.
250
- const proof = evaluatorProofState(cwd, featureName);
251
- signals.mutationProof = {
252
- section: proof.mutationSection,
253
- min: minProofScore(proof.proofScores),
254
- productionCode: tasks.some((t) => (t?.group ?? 'backend') !== 'docs'),
255
- };
256
-
257
- // PROVA DE MÍDIA, mesmo módulo leitor e mesma disciplina anti-spoof. As
258
- // quatro afirmações de uma feature de mídia (custo, revisão humana,
259
- // marcação, hot path não bloqueado) são propriedades ESTRUTURAIS, e por isso
260
- // `score` limita `architecture` e não `test-coverage` — daí `media` ficar
261
- // FORA do `min` acima.
262
- //
263
- // `featureGeneratesMedia` é determinístico e vem do DISCO, nunca de
264
- // julgamento: existe ao menos uma task cujo array `standards` cita um dos
265
- // três paths de standard de mídia. É o mesmo formato do `productionCode`
266
- // acima e usa um campo que o Gate 2 congelou. Mora numa função exportada
267
- // (acima) porque um predicado que decide se um guardrail arma precisa de
268
- // canário próprio — e porque o prefixo `framework/` precisa ser normalizado
269
- // ali, num lugar só.
270
- //
271
- // A isenção é automática e é o comportamento certo: a maioria das features
272
- // não gera mídia, e exigir a seção delas seria cerimônia vazia — que é como
273
- // uma regra vira ruído e depois é desligada. `tasks` vazio ⇒ false ⇒ não
274
- // pausa, o mesmo fail-open por ausência do sinal irmão.
182
+ const report = evaluatorReportState(cwd, featureName);
183
+ signals.evaluatorVerdict = report.status === 'ok'
184
+ ? {
185
+ status: 'ok',
186
+ verdict: report.verdict,
187
+ blockers: report.blockers.length,
188
+ blockerIds: report.blockers.map((b) => b.id),
189
+ untyped: report.untyped.length,
190
+ superseded: report.superseded,
191
+ }
192
+ : { status: report.status };
275
193
  signals.mediaClaims = {
276
- section: proof.mediaSection,
277
- score: mediaProofScore(proof.proofScores),
194
+ section: evaluatorMediaSection(cwd, featureName),
278
195
  featureGeneratesMedia: featureGeneratesMedia(tasks),
279
196
  };
280
197
  }
@@ -374,7 +291,7 @@ export async function gateCheckCommand(featureName, gateName, options = {}) {
374
291
  process.exit(1);
375
292
  }
376
293
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
377
- console.error(chalk.red('\n❌ Invalid --signals: expected a JSON object, e.g. \'{"evalScore":9.2}\'\n'));
294
+ console.error(chalk.red('\n❌ Invalid --signals: expected a JSON object, e.g. \'{"standardsViolations":[]}\'\n'));
378
295
  process.exit(1);
379
296
  }
380
297
 
@@ -390,13 +307,6 @@ export async function gateCheckCommand(featureName, gateName, options = {}) {
390
307
  if (key in parsed) overrides[key] = parsed[key];
391
308
  }
392
309
 
393
- // Um `evalScore` vindo de --signals substitui o VALOR mas não a
394
- // PROCEDÊNCIA: sem isto o merge cego abaixo deixaria `evalScoreSource`
395
- // com o que collectSignals escreveu ('taskScores-proxy'), e o sinal de
396
- // auditoria passaria a mentir sobre de onde veio o número. `signals-
397
- // override` não está em OVERRIDABLE_SIGNAL_KEYS — o caller não pode
398
- // forjá-lo, mesmo anti-spoof de `gate`/`workType`.
399
- if ('evalScore' in overrides) overrides.evalScoreSource = 'signals-override';
400
310
  }
401
311
 
402
312
  const cwd = process.cwd();
@@ -68,7 +68,7 @@ export async function persistGateDecision(featureName, gateName, { pause, reason
68
68
  * [--signals <json>] [--trust <mode>]` — record the trust decision for a gate
69
69
  * under `gateDecisions` in the authoritative feature.json.
70
70
  *
71
- * Same philosophy as `morph-spec score`: the timestamp (`decidedAt`) is
71
+ * The timestamp (`decidedAt`) is
72
72
  * stamped server-side and the write is atomic (state-manager). This replaces
73
73
  * the manual Edit of feature.json from the skills — which collided with the
74
74
  * state-sync hook rewriting the file between the agent's Read and Edit
@@ -35,9 +35,15 @@ import { join } from 'path';
35
35
  import { readTelemetryEvents, isFailedToolCall } from '../../../framework/hooks/shared/telemetry-logger.js';
36
36
  import { treeState, snapshotTree } from '../../lib/git/tree-state.js';
37
37
  import { atomicWriteJson } from '../../core/state/state-manager.js';
38
+ import { minimatch } from 'minimatch';
39
+ import { evaluatorVerdictFromText, VERDICTS } from '../../lib/evaluator-report.js';
40
+ import { evaluatorModelFor } from '../../lib/risk-detector.js';
41
+ import { changedPathsAgainst, detectDefaultBranch } from '../../lib/git/default-branch.js';
42
+ import { readMorphConfig } from '../../lib/tasks/test-runner.js';
43
+ import { featureGeneratesMedia } from './gate-check.js';
38
44
 
39
45
  /**
40
- * The four mandatory items of the report contract, as detectable markers.
46
+ * The mandatory items of the report contract, as detectable markers.
41
47
  *
42
48
  * HEURISTIC BY CONSTRUCTION, and deliberately loose: the contract asks for
43
49
  * content, not for a fixed template, and reports written before this command
@@ -48,10 +54,8 @@ import { atomicWriteJson } from '../../core/state/state-manager.js';
48
54
  const REPORT_MARKERS = [
49
55
  // (2) files touched — a list line carrying a path or a backticked identifier
50
56
  { id: 'files', re: /^\s*[-*]\s+.*[`/\\].*$/m },
51
- // (4) self-score 0-10
52
- { id: 'score', re: /score[^\n]*?\b(10|\d)(?:[.,]\d+)?\b/i },
53
- // (3) problems found (or an explicit "none")
54
- { id: 'problems', re: /problema|issue|blocker|nenhum|impedimento/i },
57
+ // (3) each doneCriteria, met or not, with its evidence
58
+ { id: 'criteria', re: /crit[ée]rio|doneCriteria|p[óo]s-condi[çc][ãa]o|atendid/i },
55
59
  ];
56
60
 
57
61
  /** (1) "what was done" — prose beyond the structural markers. */
@@ -60,9 +64,6 @@ const MIN_PROSE_CHARS = 80;
60
64
  /**
61
65
  * The partial-report marker of the dispatch contract (clause 5, ADR-004): a
62
66
  * sub-agent nearing its `maxTurns` ceiling writes `PARCIAL` on the FIRST line.
63
- * Read on purpose, never inferred from a missing score: the score marker above
64
- * is loose by design, and a partial saying "auto-score: pendente (0-10 no
65
- * final)" matches it — with the other items present it would audit as `ok`.
66
67
  * Tolerates heading/bold/quote typography (`# PARCIAL`, `**PARCIAL**`).
67
68
  */
68
69
  const PARTIAL_MARKER = /^[\s#>*_]*PARCIAL\b/;
@@ -96,12 +97,12 @@ function evaluatorPath(cwd, feature) {
96
97
  * items can be found in it.
97
98
  */
98
99
  export function inspectReport(path) {
99
- if (!existsSync(path)) return { exists: false, complete: false, missing: ['what', 'files', 'problems', 'score'] };
100
+ if (!existsSync(path)) return { exists: false, complete: false, missing: ['what', 'files', 'criteria'] };
100
101
  let body = '';
101
102
  try {
102
103
  body = readFileSync(path, 'utf8');
103
104
  } catch {
104
- return { exists: true, complete: false, missing: ['what', 'files', 'problems', 'score'], unreadable: true };
105
+ return { exists: true, complete: false, missing: ['what', 'files', 'criteria'], unreadable: true };
105
106
  }
106
107
 
107
108
  const missing = REPORT_MARKERS.filter((m) => !m.re.test(body)).map((m) => m.id);
@@ -122,6 +123,29 @@ export function inspectReport(path) {
122
123
  return { exists: true, complete: missing.length === 0, missing, partial };
123
124
  }
124
125
 
126
+ /** The evaluator report is complete when it carries a readable verdict and is not partial. */
127
+ export function inspectEvaluatorReport(path) {
128
+ if (!existsSync(path)) return { exists: false, complete: false };
129
+ let body = '';
130
+ try { body = readFileSync(path, 'utf8'); } catch { return { exists: true, complete: false, unreadable: true }; }
131
+ const firstLine = body.split('\n').find((l) => l.trim() !== '') ?? '';
132
+ const partial = PARTIAL_MARKER.test(firstLine);
133
+ const { verdict } = evaluatorVerdictFromText(body);
134
+ return { exists: true, complete: !partial && VERDICTS.includes(verdict), partial, verdict: verdict ?? null };
135
+ }
136
+
137
+ function evaluatorDispatch(cwd, tasks) {
138
+ let changedFiles = [];
139
+ try { changedFiles = changedPathsAgainst(detectDefaultBranch(cwd), cwd); } catch { changedFiles = []; }
140
+ const watch = readMorphConfig(cwd)?.project?.evals?.watch;
141
+ const globs = Array.isArray(watch) ? watch.filter((g) => typeof g === 'string' && g.trim()) : [];
142
+ return evaluatorModelFor({
143
+ changedFiles,
144
+ promptModelTouched: changedFiles.some((f) => globs.some((g) => minimatch(f, g))),
145
+ generatesMedia: featureGeneratesMedia(tasks),
146
+ });
147
+ }
148
+
125
149
  /** How many of a task's declared outputs actually exist on disk. */
126
150
  function outputEvidence(cwd, taskOutputs) {
127
151
  const expected = Array.isArray(taskOutputs) ? taskOutputs : [];
@@ -312,16 +336,17 @@ function snapshotPath(cwd, feature) {
312
336
  * dispatched when there is one. `beforeEval` takes a new snapshot after the
313
337
  * comparison, so the output still shows what changed since the previous one.
314
338
  */
315
- function treeFor(cwd, feature, beforeEval) {
339
+ function treeFor(cwd, feature, beforeEval, tasks = []) {
316
340
  const path = snapshotPath(cwd, feature);
317
341
  let snapshot = null;
318
342
  try { if (existsSync(path)) snapshot = JSON.parse(readFileSync(path, 'utf8')); } catch { snapshot = null; }
319
343
  const tree = treeState(cwd, { snapshot });
320
344
  if (!beforeEval) return { tree };
345
+ const evaluatorModel = evaluatorDispatch(cwd, tasks);
321
346
  const next = snapshotTree(cwd);
322
- if (!next) return { tree, beforeEval: null };
347
+ if (!next) return { tree, beforeEval: null, evaluatorModel };
323
348
  atomicWriteJson(path, next);
324
- return { tree, beforeEval: { at: next.at, files: Object.keys(next.files).length } };
349
+ return { tree, beforeEval: { at: next.at, files: Object.keys(next.files).length }, evaluatorModel };
325
350
  }
326
351
 
327
352
  /**
@@ -356,21 +381,21 @@ export function reportCheckCommand(feature, taskId, options = {}) {
356
381
  if (unknown.length) emit({ error: `task(s) ${unknown.join(', ')} não existe(m) em tasks.json`, feature }, 1);
357
382
  const results = taskId.map((id) => checkTask(cwd, feature, tasks.find((t) => t.id === id)));
358
383
  const clean = results.every((r) => r.verdict === 'ok');
359
- emit({ feature, results, clean, ...treeFor(cwd, feature, options.beforeEval) }, clean ? 0 : 1);
384
+ emit({ feature, results, clean, ...treeFor(cwd, feature, options.beforeEval, tasks) }, clean ? 0 : 1);
360
385
  }
361
386
 
362
387
  if (taskId) {
363
388
  const task = tasks.find((t) => t.id === taskId);
364
389
  if (!task) emit({ error: `task "${taskId}" não existe em tasks.json`, feature }, 1);
365
390
  const result = checkTask(cwd, feature, task);
366
- emit({ feature, ...result, hint: ACTION_HINT[result.action], ...treeFor(cwd, feature, options.beforeEval) }, result.verdict === 'ok' ? 0 : 1);
391
+ emit({ feature, ...result, hint: ACTION_HINT[result.action], ...treeFor(cwd, feature, options.beforeEval, tasks) }, result.verdict === 'ok' ? 0 : 1);
367
392
  }
368
393
 
369
394
  // Whole-feature audit: every DONE task owes a report; a pending one does not.
370
395
  const results = tasks.filter((t) => t.status === 'done').map((t) => checkTask(cwd, feature, t));
371
- const evaluator = inspectReport(evaluatorPath(cwd, feature));
396
+ const evaluator = inspectEvaluatorReport(evaluatorPath(cwd, feature));
372
397
  const clean = results.every((r) => r.verdict === 'ok');
373
- emit({ feature, results, evaluator, clean, ...treeFor(cwd, feature, options.beforeEval) }, clean ? 0 : 1);
398
+ emit({ feature, results, evaluator, clean, ...treeFor(cwd, feature, options.beforeEval, tasks) }, clean ? 0 : 1);
374
399
  }
375
400
 
376
401
  function printHuman(obj) {
@@ -176,7 +176,7 @@ export async function mutateCommand(feature, options = {}) {
176
176
  merged.method.jobs = Math.max(merged.method.jobs ?? 1, result.method.jobs);
177
177
  }
178
178
  } catch (err) {
179
- if (err instanceof HeavyBusyError) return fail(`suíte não rodou: ${err.message}. Tente de novo quando liberar (morph-spec heavy status).`, EXIT.busy);
179
+ if (err instanceof HeavyBusyError) return fail(`suíte não rodou: ${err.message}. A fila já esperou o prazo dela: não repita em laço — registre a mutação como não executada (morph-spec heavy status).`, EXIT.busy);
180
180
  if (err.code === 'baseline-build-failed') return fail(`${err.message}\n${String(err.output ?? '').split('\n').slice(-12).join('\n')}`, EXIT.failed);
181
181
  throw err;
182
182
  }
@@ -0,0 +1,28 @@
1
+ import chalk from 'chalk';
2
+ import { existsSync, readFileSync } from 'fs';
3
+ import { resolve } from 'path';
4
+ import { checkScoutMap, resolveScoutRoot } from '../../lib/scout/scout-check.js';
5
+
6
+ export function scoutCheckCommand(mapPath, options = {}) {
7
+ const file = resolve(mapPath);
8
+ if (!existsSync(file)) {
9
+ if (options.json) console.log(JSON.stringify({ ok: false, error: 'map-not-found', map: file }));
10
+ else console.error(chalk.red(`scout-check: mapa não encontrado: ${file}`));
11
+ process.exit(2);
12
+ }
13
+ const result = { map: file, ...checkScoutMap(readFileSync(file, 'utf8'), { root: resolveScoutRoot(options.root) }) };
14
+
15
+ if (options.json) {
16
+ console.log(JSON.stringify(result, null, 2));
17
+ } else if (result.ok) {
18
+ console.log(chalk.green(`✓ scout-check: ${result.citations.length} citações conferidas contra ${result.root}`));
19
+ } else {
20
+ console.log(chalk.red(`✗ scout-check: ${result.failures.length} falha(s) em ${file} (raiz ${result.root})`));
21
+ for (const f of result.failures) {
22
+ const where = f.line ? `L${f.line}` : '—';
23
+ console.log(` ${where} ${chalk.yellow(f.kind)} ${f.citation ?? ''} ${f.detail}`);
24
+ }
25
+ console.log(chalk.gray(' Mapa reprovado: descarte e re-despache o scout UMA vez com estas falhas no prompt (MORPH.md §6).'));
26
+ }
27
+ process.exit(result.ok ? 0 : 1);
28
+ }
@@ -84,28 +84,6 @@ const OUTPUT_SECTIONS = {
84
84
  ['Performance', 'Desempenho'],
85
85
  ],
86
86
  },
87
- // morph-plan (mandate.md) emits: Stack, Standards obrigatórios,
88
- // Patterns proibidos, Verificação de runtime, Checklist de done.
89
- 'mandate.md': {
90
- required: [
91
- 'Stack',
92
- ['Standards', 'Padrões', 'Padroes'],
93
- ['Done', 'Pronto', 'Checklist', 'Critérios de done', 'Definition of Done'],
94
- ],
95
- recommended: [
96
- ['Patterns proibidos', 'Padrões proibidos', 'Padroes proibidos', 'Forbidden Patterns', 'Forbidden', 'Proibido'],
97
- // Recommended rather than required on purpose: mandate.md freezes after
98
- // Gate 2 (protect-spec-files blocks Write AND Edit), so promoting this to
99
- // `required` would fail every in-flight feature with no way to fix it.
100
- // New mandates get the section from morph-plan §4; the enforcement that
101
- // actually matters is verify's e2e node, not this heading check.
102
- ['Verificação de runtime', 'Verificacao de runtime', 'Runtime', 'E2E'],
103
- ],
104
- optional: [
105
- ['Riscos', 'Risks'],
106
- ['Acesso a dados', 'Data Access', 'Repository', 'Repositório'],
107
- ],
108
- },
109
87
  // decisions.md follows the ADR convention: an `### ADR-NNN` heading with
110
88
  // `**Status:** / **Context:** / **Decision:**` line-start bold labels.
111
89
  'decisions.md': {
@@ -123,19 +101,14 @@ const OUTPUT_SECTIONS = {
123
101
  ['Custos', 'Costs'],
124
102
  ],
125
103
  },
126
- // morph-review emits: Scores por dimensão, Bloqueantes, Melhorias,
127
- // Recomendação final.
128
104
  'review-report.md': {
129
105
  required: [
130
- ['Scores', 'Score', 'Pontuação', 'Pontuacao', 'Scoring'],
131
- ['Recomendação', 'Recomendacao', 'Recommendation'],
106
+ ['Veredito', 'Verdict'],
132
107
  ],
133
108
  recommended: [
134
- ['Bloqueantes', 'Bloqueante', 'Blockers'],
135
- ],
136
- optional: [
137
- ['Melhorias', 'Improvements', 'Recomendadas'],
109
+ ['Bloqueadores', 'Bloqueantes', 'Blockers'],
138
110
  ],
111
+ optional: [],
139
112
  },
140
113
  };
141
114
 
@@ -145,13 +118,13 @@ const OUTPUT_SECTIONS = {
145
118
  const PHASE_OUTPUTS = {
146
119
  proposal: ['proposal.md'],
147
120
  uiux: ['design-system.md', 'mockups.md', 'components.md', 'flows.md'],
148
- plan: ['spec.md', 'mandate.md', 'tasks.json'],
121
+ plan: ['spec.md', 'tasks.json'],
149
122
  implement: ['recap.md'],
150
123
  review: ['review-report.md'],
151
124
  };
152
125
 
153
126
  // Work types whose plan phase (see src/core/work-types.js) skips proposal.md/
154
- // spec.md/mandate.md — chore and hotfix stamp proposal+plan gates at birth
127
+ // spec.md — chore and hotfix stamp proposal+plan gates at birth
155
128
  // (policy-approved, no human deliberation over those docs) and go straight to
156
129
  // tasks.json. Validating the full plan doc set against a feature that was
157
130
  // never asked to write those docs would be a false failure, not a real gap.