@polymorphism-tech/morph-spec 8.43.0 → 8.45.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.
- package/CLAUDE.md +16 -13
- package/README.md +10 -10
- package/bin/morph-spec.js +10 -13
- package/bin/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +2 -4
- package/docs/QUICKSTART.md +6 -6
- package/framework/CLAUDE.md +6 -8
- package/framework/MORPH.md +39 -45
- package/framework/agents.json +8 -9
- package/framework/commands/morph-apply.md +1 -3
- package/framework/commands/morph-hotfix.md +1 -1
- package/framework/commands/morph-proposal.md +2 -2
- package/framework/docs/CLI.md +22 -17
- package/framework/evals/review-checklist.md +90 -0
- package/framework/evals/trace-schema.md +2 -12
- package/framework/hooks/README.md +2 -3
- package/framework/hooks/claude-code/core/heavy-gate.js +1 -1
- package/framework/hooks/claude-code/core/protect-spec-files.js +2 -2
- package/framework/hooks/claude-code/core/telemetry-log.js +2 -2
- package/framework/hooks/claude-code/core/trace-autogen.js +1 -22
- package/framework/hooks/claude-code/core/validate-completion.js +3 -56
- package/framework/hooks/claude-code/core/worktree-env-inject.js +1 -4
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
- package/framework/hooks/git/pre-commit/agents.sh +25 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
- package/framework/hooks/git/pre-commit/specs.sh +50 -50
- package/framework/hooks/git/pre-push/run-tests.sh +44 -44
- package/framework/hooks/shared/build-check.js +1 -1
- package/framework/hooks/shared/comment-scanner.js +1 -1
- package/framework/hooks/shared/phase-utils.js +0 -5
- package/framework/hooks/shared/stdin-reader.js +26 -26
- package/framework/phases.json +4 -6
- package/framework/rubrics/backend.json +1 -1
- package/framework/rubrics/default.json +1 -1
- package/framework/rubrics/frontend.json +1 -1
- package/framework/rules/ai-agents-standards.md +1 -0
- package/framework/rules/morph-workflow.md +6 -6
- package/framework/schemas/feature.schema.json +2 -28
- package/framework/schemas/tasks.schema.json +6 -2
- package/framework/skills/README.md +1 -2
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +145 -246
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +5 -3
- package/framework/skills/core/morph-implement/SKILL.md +101 -201
- package/framework/skills/core/morph-machine/SKILL.md +3 -3
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +171 -287
- package/framework/skills/core/morph-proposal/SKILL.md +4 -5
- package/framework/skills/core/morph-review/SKILL.md +109 -430
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +2 -2
- package/framework/skills/core/morph-uiux/SKILL.md +2 -3
- package/framework/standards/STANDARDS.json +54 -10
- package/framework/standards/ai-agents/agent-archetypes.md +1 -1
- package/framework/standards/ai-agents/agent-session.md +1 -1
- package/framework/standards/ai-agents/agent-spec.md +1 -1
- package/framework/standards/ai-agents/batching.md +1 -1
- package/framework/standards/ai-agents/context-providers.md +1 -1
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +1 -1
- package/framework/standards/ai-agents/cost-and-budget.md +1 -1
- package/framework/standards/ai-agents/durable-workflows-hitl.md +1 -1
- package/framework/standards/ai-agents/guardrails.md +1 -1
- package/framework/standards/ai-agents/llm-runtime-defaults.md +1 -1
- package/framework/standards/ai-agents/mcp-server.md +1 -1
- package/framework/standards/ai-agents/mcp-tools.md +1 -1
- package/framework/standards/ai-agents/media-pipeline.md +1 -1
- package/framework/standards/ai-agents/media-video.md +1 -1
- package/framework/standards/ai-agents/middleware-patterns.md +1 -1
- package/framework/standards/ai-agents/modalities-image-gen.md +1 -1
- package/framework/standards/ai-agents/modalities-speech-to-text.md +1 -1
- package/framework/standards/ai-agents/modalities-text-to-speech.md +1 -1
- package/framework/standards/ai-agents/modalities-vision.md +1 -1
- package/framework/standards/ai-agents/multi-agent-patterns.md +1 -1
- package/framework/standards/ai-agents/observability-patterns.md +3 -3
- package/framework/standards/ai-agents/production.md +2 -2
- package/framework/standards/ai-agents/prompt-sources.md +1 -1
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +1 -1
- package/framework/standards/ai-agents/service-tier-flex.md +1 -1
- package/framework/standards/ai-agents/setup.md +1 -1
- package/framework/standards/ai-agents/structured-output.md +1 -1
- package/framework/standards/ai-agents/sweet-spot.md +1 -1
- package/framework/standards/ai-agents/testing-ai.md +1 -1
- package/framework/standards/ai-agents/tool-adapters.md +226 -0
- package/framework/standards/ai-agents/vector-data-extensions.md +1 -1
- package/framework/standards/ai-agents/workflows.md +1 -1
- package/framework/standards/infrastructure/docker/local-compose-isolation.md +11 -1
- package/framework/templates/CLAUDE.md.core.template +1 -1
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/README.md +1 -1
- package/framework/templates/REGISTRY.json +0 -81
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/docs/spec.md +19 -205
- package/framework/templates/docs/ui-components.md +52 -52
- package/framework/templates/docs/ui-design-system.md +76 -76
- package/framework/templates/docs/ui-flows.md +167 -167
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/README.md +56 -56
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +2 -2
- package/src/commands/e2e/e2e.js +1 -1
- package/src/commands/project/heavy.js +1 -1
- package/src/commands/project/status.js +3 -3
- package/src/commands/project/worktree.js +34 -16
- package/src/commands/state/approve.js +9 -50
- package/src/commands/state/finish.js +4 -0
- package/src/commands/state/gate-check.js +18 -107
- package/src/commands/state/gate-decision.js +1 -1
- package/src/commands/state/report-check.js +42 -17
- package/src/commands/validation/mutate.js +1 -1
- package/src/commands/validation/scout-check.js +28 -0
- package/src/commands/validation/validate-feature.js +5 -32
- package/src/commands/validation/verify.js +16 -25
- package/src/core/paths/output-schema.js +0 -2
- package/src/core/state/state-manager.js +1 -2
- package/src/core/templates/template-renderer.js +477 -477
- package/src/dashboard/app/lib/format.js +5 -0
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/FeatureDetail.jsx +16 -22
- package/src/dashboard/app/pages/Features.jsx +7 -7
- package/src/dashboard/app/pages/PhasePipeline.jsx +1 -1
- package/src/dashboard/app/pages/Rubrics.jsx +7 -11
- package/src/dashboard/scanner/docs-scanner.js +1 -2
- package/src/dashboard/scanner/features-scanner.js +3 -4
- package/src/dashboard/scanner/rubrics-scanner.js +12 -12
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/agents/output-collector.js +0 -1
- package/src/lib/detectors/design-system-detector.js +188 -188
- package/src/lib/e2e/compose-env.js +5 -2
- package/src/lib/evaluator-report.js +52 -334
- package/src/lib/generators/design-system-generator.js +508 -508
- package/src/lib/generators/handoff-generator.js +4 -5
- package/src/lib/git/evidence-staleness.js +39 -0
- package/src/lib/installers/memory-installer.js +2 -2
- package/src/lib/mutation/manifest.js +8 -2
- package/src/lib/mutation/report.js +5 -3
- package/src/lib/mutation/runner.js +9 -7
- package/src/lib/risk-detector.js +63 -125
- package/src/lib/scout/scout-check.js +293 -0
- package/src/lib/tasks/eval-node.js +4 -34
- package/src/lib/tasks/task-classifier.js +30 -30
- package/src/lib/tasks/task-parser.js +209 -209
- package/src/lib/tasks/tasks-json-parser.js +7 -5
- package/src/lib/tasks/test-nodes.js +1 -1
- package/src/lib/tasks/test-runner.js +1 -1
- package/src/lib/validators/contracts/contract-compliance-validator.js +4 -42
- package/src/lib/validators/css/css-validator.js +340 -340
- package/src/lib/validators/reuse/reuse-validator.js +6 -8
- package/src/lib/validators/verify-runner.js +1 -1
- package/src/lib/worktree/env-allocator.js +35 -5
- package/src/scripts/setup-infra.js +1 -1
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/agents-installer.js +5 -9
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/hooks-installer.js +3 -10
- package/src/utils/skills-installer.js +1 -1
- package/framework/evals/proof-rubrics/README.md +0 -76
- package/framework/evals/proof-rubrics/eval-de-modelo.md +0 -80
- package/framework/evals/proof-rubrics/fiacao.md +0 -54
- package/framework/evals/proof-rubrics/guard-cego.md +0 -52
- package/framework/evals/proof-rubrics/media-claims.md +0 -109
- package/framework/evals/proof-rubrics/mutation-proof.md +0 -107
- package/framework/evals/proof-rubrics/observabilidade-genai.md +0 -119
- package/framework/evals/proof-rubrics/verde-nao-e-coberto.md +0 -48
- package/framework/evals/rubrics/README.md +0 -45
- package/framework/evals/rubrics/architecture.md +0 -103
- package/framework/evals/rubrics/code-quality.md +0 -98
- package/framework/evals/rubrics/condensed.md +0 -57
- package/framework/evals/rubrics/contracts.md +0 -67
- package/framework/evals/rubrics/test-coverage.md +0 -99
- package/framework/evals/scenarios/dotnet-crud.yaml +0 -42
- package/framework/evals/scenarios/nextjs-auth.yaml +0 -38
- package/framework/evals/suites/standard.yaml +0 -5
- package/framework/hooks/claude-code/core/post-edit-typecheck.js +0 -127
- package/framework/skills/core/morph-eval/SKILL.md +0 -64
- package/framework/templates/.idea/morph-templates.xml +0 -92
- package/framework/templates/.vscode/morph-templates.code-snippets +0 -186
- package/framework/templates/docs/clarifications.md +0 -253
- package/framework/templates/docs/onboarding.md +0 -122
- package/framework/templates/docs/schema-analysis.md +0 -119
- package/framework/templates/docs/user-stories.md +0 -34
- package/src/commands/state/score.js +0 -147
|
@@ -1,373 +1,91 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Leitura do `4-review/evaluator-report.md` — o relatório BRUTO do avaliador
|
|
3
|
-
* independente despachado pela `morph-review` no Gate 3.
|
|
4
|
-
*
|
|
5
|
-
* Existe como módulo próprio porque DOIS comandos dependem do mesmo arquivo por
|
|
6
|
-
* razões diferentes, e antes o liam de maneiras diferentes:
|
|
7
|
-
*
|
|
8
|
-
* - `gate-check` lê o token `**Composite-Score:**` para saber o score REAL da
|
|
9
|
-
* feature (em vez do proxy de médias de auto-avaliação);
|
|
10
|
-
* - `approve` exige o arquivo como precondição do Gate 3.
|
|
11
|
-
*
|
|
12
|
-
* A divergência era concreta: `approve.js` montava o path à mão com `join(...)`
|
|
13
|
-
* enquanto `gate-check.js` resolvia por `getAbsoluteOutputPath`. Dois caminhos
|
|
14
|
-
* para o mesmo arquivo é a receita de drift silencioso (a lição A5), e a
|
|
15
|
-
* checagem por `existsSync` puro do `approve` aceitava um relatório SEM o token
|
|
16
|
-
* que o `gate-check` então descartava — o Gate 3 passava carimbado `auto` sobre
|
|
17
|
-
* uma média de auto-avaliação, exatamente o que MORPH.md §5 proíbe.
|
|
18
|
-
*
|
|
19
|
-
* ── O TOKEN VIGENTE É O ÚLTIMO ──────────────────────────────────────────────
|
|
20
|
-
*
|
|
21
|
-
* O `evaluator-report.md` é APPEND-ONLY por contrato: uma reavaliação acrescenta
|
|
22
|
-
* ao fim, preservando a análise anterior. Este módulo lia o COMEÇO (`String.match`
|
|
23
|
-
* com regex sem flag `g`), então num relatório reavaliado o gate era decidido
|
|
24
|
-
* pelo score SUPERADO. Medido em campo três vezes na mesma epic — #90 (8.7 →
|
|
25
|
-
* 9.4), #92 (8.2 → 9.0) e #93 (8.9 → 8.3 → 9.1) — e nas três o avaliador teve de
|
|
26
|
-
* desarmar o token antigo à mão para o gate ler a nota vigente.
|
|
27
|
-
*
|
|
28
|
-
* O caso perigoso é o INVERSO e é silencioso: numa reavaliação que BAIXA a nota
|
|
29
|
-
* (o #93 caiu de 8.9 para 8.3 quando uma correção abriu um bloqueante), o gate
|
|
30
|
-
* leria a nota antiga, mais alta, e AUTO-PASSARIA o Gate 3 sobre um produto com
|
|
31
|
-
* bloqueante aberto — a mesma classe de defeito que a v8.26.0 existiu para
|
|
32
|
-
* fechar, voltando por outra porta: lendo um veredito real, porém vencido.
|
|
33
|
-
*
|
|
34
|
-
* DECISÃO EXPLÍCITA — "o último vence", e não "múltiplos tokens viram
|
|
35
|
-
* `report-ambiguous` e recusam":
|
|
36
|
-
*
|
|
37
|
-
* 1. É a semântica do próprio artefato. Append-only significa que o arquivo é
|
|
38
|
-
* um LOG, e o valor corrente de um log é a sua última entrada. Ler o
|
|
39
|
-
* primeiro não é uma política conservadora — é ler a entrada errada.
|
|
40
|
-
* 2. Recusar transformaria um veredito REAL e CORRENTE numa pausa que só sai
|
|
41
|
-
* editando à mão um arquivo declarado append-only. Isto é exatamente o
|
|
42
|
-
* contorno manual que este conserto existe para eliminar, promovido a
|
|
43
|
-
* requisito.
|
|
44
|
-
* 3. Recusar não é mais seguro em nenhum dos dois casos observados: com a nota
|
|
45
|
-
* SUBINDO, o humano é obrigado a mexer no relatório para liberar um gate
|
|
46
|
-
* que já foi aprovado; com a nota CAINDO, "o último vence" já pausa sozinho
|
|
47
|
-
* pela regra de score, que é a razão certa a reportar.
|
|
48
|
-
*
|
|
49
|
-
* Ambiguidade não fica silenciosa, ainda assim: `evaluatorReportState` devolve
|
|
50
|
-
* `scoreTokens` (quantos tokens vivos o arquivo tem) e `superseded` (os valores
|
|
51
|
-
* anteriores, na ordem do arquivo). Quem consome pode dizer "9.1, terceira
|
|
52
|
-
* rodada, superando 8.9 e 8.3" em vez de fingir que só havia um veredito.
|
|
53
|
-
*/
|
|
54
|
-
|
|
55
1
|
import { existsSync, readFileSync } from 'fs';
|
|
56
2
|
import { getAbsoluteOutputPath } from '../core/paths/output-schema.js';
|
|
57
3
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
* de LLM só é auditável por máquina se carregar um campo que a máquina lê.
|
|
62
|
-
*/
|
|
63
|
-
export const COMPOSITE_SCORE_TOKEN = '**Composite-Score:**';
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* O token das rubricas de PROVA (`.morph/framework/evals/proof-rubrics/`):
|
|
67
|
-
* `**Proof-Scores:** mutation=9 verde=8 fiacao=9 guard=7 eval=8`. Mesma convenção
|
|
68
|
-
* literal do Composite-Score de propósito — mesmo arquivo, mesmo módulo leitor,
|
|
69
|
-
* mesmo estilo de token. Ele pontua a EVIDÊNCIA que o avaliador produziu, nunca
|
|
70
|
-
* o produto: não entra no composto, impõe teto em `test-coverage`.
|
|
71
|
-
*/
|
|
72
|
-
export const PROOF_SCORES_TOKEN = '**Proof-Scores:**';
|
|
73
|
-
|
|
74
|
-
/** `g` é obrigatório: o arquivo é append-only e o token vigente é o ÚLTIMO. */
|
|
75
|
-
const COMPOSITE_SCORE_RE = /\*\*Composite-Score:\*\*\s*([0-9]+(?:\.[0-9]+)?)/g;
|
|
76
|
-
|
|
77
|
-
/** Idem — uma reavaliação reescreve os Proof-Scores acrescentando ao fim. */
|
|
78
|
-
const PROOF_SCORES_RE = /\*\*Proof-Scores:\*\*[ \t]*([^\r\n]*)/g;
|
|
4
|
+
export const VERDICT_TOKEN = '**Verdict:**';
|
|
5
|
+
export const VERDICTS = ['SHIP', 'FIX'];
|
|
6
|
+
export const BLOCKER_TYPES = ['uc', 'teste', 'regressao', 'seguranca'];
|
|
79
7
|
|
|
80
|
-
|
|
81
|
-
*
|
|
82
|
-
|
|
83
|
-
* entrega ao avaliador gera exatamente `## Mutações`. Aceita a grafia sem
|
|
84
|
-
* acento porque um relatório escrito em terminal ASCII continua sendo o mesmo
|
|
85
|
-
* fato — o que não se aceita é o nível errado de cabeçalho.
|
|
86
|
-
*/
|
|
87
|
-
const MUTATION_SECTION_RE = /^##\s+Muta[çc][õo]es\s*$/m;
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* A seção de prova de MÍDIA. Mesma disciplina do `## Mutações`: exige `##`, e
|
|
91
|
-
* aceita a grafia sem acento porque um relatório escrito em terminal ASCII
|
|
92
|
-
* continua sendo o mesmo fato. `Mídia gerada` / `Midia gerada`.
|
|
93
|
-
*/
|
|
8
|
+
// O relatório é append-only: uma reavaliação acrescenta ao fim, e o token vigente é o ÚLTIMO.
|
|
9
|
+
const VERDICT_RE = /\*\*Verdict:\*\*\s*([A-Za-z]+)/g;
|
|
10
|
+
const BLOCKERS_SECTION_RE = /^##\s+Bloqueadores\s*$/gim;
|
|
94
11
|
const MEDIA_SECTION_RE = /^##\s+M[íi]dia\s+gerada\s*$/m;
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* A seção em que o avaliador confere o checklist de tela do mockup contra a
|
|
98
|
-
* tela entregue (`morph-review` §4). Mesma disciplina: exige `##`.
|
|
99
|
-
*/
|
|
100
12
|
const SCREEN_CHECKLIST_SECTION_RE = /^##\s+Checklist\s+de\s+tela\s*$/gim;
|
|
101
13
|
|
|
102
|
-
/**
|
|
103
|
-
* As chaves reconhecidas de `**Proof-Scores:**`, uma por rubrica de prova.
|
|
104
|
-
* Chave fora desta lista é IGNORADA (não vira score, não vira erro): o relatório
|
|
105
|
-
* é texto de LLM e uma chave inventada não deve nem contaminar nenhum mínimo nem
|
|
106
|
-
* derrubar a leitura das outras.
|
|
107
|
-
*
|
|
108
|
-
* `media` PRECISA estar aqui. Sem isso a rubrica `media-claims.md` seria
|
|
109
|
-
* decorativa: o avaliador escreveria `media=7` e o número seria descartado em
|
|
110
|
-
* silêncio, que é o caso "campo publicado sem nenhum leitor".
|
|
111
|
-
*/
|
|
112
|
-
export const PROOF_SCORE_KEYS = ['mutation', 'verde', 'fiacao', 'guard', 'eval', 'media', 'obs'];
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* As chaves que NÃO entram no `min` de `test-coverage` porque impõem teto em
|
|
116
|
-
* OUTRA dimensão: `media` (teto em `architecture`) e `obs` (teto em
|
|
117
|
-
* `code-quality`).
|
|
118
|
-
*
|
|
119
|
-
* **`obs` chegou exatamente pelo caminho que o parágrafo abaixo antecipou**, e
|
|
120
|
-
* vale registrar porque o aviso não bastou. A feature irmã (#94) criou a rubrica
|
|
121
|
-
* `observabilidade-genai.md`, escreveu `obs=` onze vezes no seu próprio
|
|
122
|
-
* `evaluator-report.md` — e a chave não estava em `PROOF_SCORE_KEYS`, nem lá nem
|
|
123
|
-
* na base dela. O parser a descartava em silêncio (ver o `continue` de
|
|
124
|
-
* `parseProofScores`), então a rubrica nasceu DECORATIVA e atravessou **onze
|
|
125
|
-
* rodadas de Gate 3** sem que ninguém percebesse: o número era escrito, lido por
|
|
126
|
-
* um humano no relatório, e nunca chegava ao gate. Só o rebase, que põe as duas
|
|
127
|
-
* features na mesma árvore, tornou o buraco visível.
|
|
128
|
-
*
|
|
129
|
-
* **Isto é uma exclusão, e a forma importa mais que a lista.** A primeira versão
|
|
130
|
-
* era o inverso — uma lista fechada das quatro chaves que entram. Ela responde
|
|
131
|
-
* certo hoje e erra sozinha amanhã: a feature irmã acrescenta uma quinta chave
|
|
132
|
-
* (`eval`) com rubrica própria, e depois do rebase esse `eval=N` cairia do teto
|
|
133
|
-
* **em silêncio** — o "campo publicado sem nenhum leitor" que este mesmo arquivo
|
|
134
|
-
* existe para impedir, produzido pela correção.
|
|
135
|
-
*
|
|
136
|
-
* Por exclusão, uma chave nova nasce DENTRO do mínimo por padrão, e tirá-la de lá
|
|
137
|
-
* passa a ser um ato deliberado e visível: uma linha aqui, com a dimensão que ela
|
|
138
|
-
* limita escrita ao lado. Errar para o lado de "conta no mínimo" custa um teto a
|
|
139
|
-
* mais; errar para o outro lado devolve o gate cego.
|
|
140
|
-
*
|
|
141
|
-
* O motivo de `media` estar fora continua o mesmo: as quatro do mínimo perguntam
|
|
142
|
-
* "a evidência é real?"; `media` pergunta "a chamada mora no lugar certo? que
|
|
143
|
-
* estados existem? o que é publicado?". Um `media=4` derrubando o teto de TESTE
|
|
144
|
-
* seriam dois fatos diferentes lidos como um só.
|
|
145
|
-
*/
|
|
146
|
-
export const NON_MUTATION_PROOF_KEYS = ['media', 'obs'];
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* As chaves que entram no `min` que impõe teto em `test-coverage`: DERIVADA de
|
|
150
|
-
* {@link PROOF_SCORE_KEYS} menos {@link NON_MUTATION_PROOF_KEYS} — nunca digitada.
|
|
151
|
-
* Ver o racional da exclusão acima.
|
|
152
|
-
*/
|
|
153
|
-
export const MUTATION_PROOF_KEYS =
|
|
154
|
-
PROOF_SCORE_KEYS.filter((k) => !NON_MUTATION_PROOF_KEYS.includes(k));
|
|
155
|
-
|
|
156
|
-
/** Path canônico do evaluator-report da feature. Fonte única para os dois comandos. */
|
|
157
14
|
export function evaluatorReportPath(cwd, featureName) {
|
|
158
15
|
return getAbsoluteOutputPath(cwd, featureName, 'evaluatorReport');
|
|
159
16
|
}
|
|
160
17
|
|
|
161
|
-
/** Conteúdo do relatório, ou null quando ausente/ilegível. Nunca lança. */
|
|
162
18
|
function readReport(cwd, featureName) {
|
|
163
19
|
try {
|
|
164
20
|
const p = evaluatorReportPath(cwd, featureName);
|
|
165
|
-
|
|
166
|
-
return readFileSync(p, 'utf8');
|
|
21
|
+
return existsSync(p) ? readFileSync(p, 'utf8') : null;
|
|
167
22
|
} catch {
|
|
168
23
|
return null;
|
|
169
24
|
}
|
|
170
25
|
}
|
|
171
26
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
function compositeScoresIn(text) {
|
|
180
|
-
return [...text.matchAll(COMPOSITE_SCORE_RE)].map((m) => Number(m[1]));
|
|
27
|
+
function lastSectionBody(text, re) {
|
|
28
|
+
const matches = [...text.matchAll(re)];
|
|
29
|
+
if (matches.length === 0) return null;
|
|
30
|
+
const last = matches[matches.length - 1];
|
|
31
|
+
const rest = text.slice(last.index + last[0].length);
|
|
32
|
+
const next = rest.search(/^#{1,2}\s/m);
|
|
33
|
+
return next === -1 ? rest : rest.slice(0, next);
|
|
181
34
|
}
|
|
182
35
|
|
|
183
|
-
|
|
184
|
-
* Score composto REAL da feature, do token gravado pelo avaliador independente.
|
|
185
|
-
*
|
|
186
|
-
* Num relatório reavaliado (append-only), é o token do FIM — o veredito
|
|
187
|
-
* vigente. Ver o bloco "O TOKEN VIGENTE É O ÚLTIMO" no topo do arquivo.
|
|
188
|
-
*
|
|
189
|
-
* @returns {number|undefined} undefined quando o relatório não existe, não é
|
|
190
|
-
* legível, ou existe sem o token (relatório legado ou oco). `undefined` é
|
|
191
|
-
* deliberado e não 0: quem chama distingue "sem veredito" de "veredito ruim".
|
|
192
|
-
*/
|
|
193
|
-
export function evaluatorCompositeScore(cwd, featureName) {
|
|
194
|
-
const text = readReport(cwd, featureName);
|
|
195
|
-
if (text === null) return undefined;
|
|
196
|
-
const scores = compositeScoresIn(text);
|
|
197
|
-
return scores.length === 0 ? undefined : scores[scores.length - 1];
|
|
198
|
-
}
|
|
36
|
+
const normalizeType = (s) => s.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').trim();
|
|
199
37
|
|
|
200
38
|
/**
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* Três valores em vez de booleano PORQUE AS TRÊS SITUAÇÕES PEDEM MENSAGENS
|
|
204
|
-
* DIFERENTES: arquivo ausente é "despache o avaliador"; arquivo sem token é
|
|
205
|
-
* "o avaliador rodou mas o relatório está oco — ele não seguiu o contrato";
|
|
206
|
-
* e só o terceiro caso é aprovável.
|
|
207
|
-
*
|
|
208
|
-
* `scoreTokens`/`superseded` só aparecem no caso `ok` e existem para que uma
|
|
209
|
-
* reavaliação nunca passe silenciosa: o score é o último, mas quem consome sabe
|
|
210
|
-
* quantas rodadas o arquivo carrega e quais notas foram superadas.
|
|
211
|
-
*
|
|
212
|
-
* @returns {{ status: 'ok'|'missing'|'untokenized', score?: number,
|
|
213
|
-
* scoreTokens?: number, superseded?: number[], path: string }}
|
|
39
|
+
* Linhas de tabela da última `## Bloqueadores`: `| id | tipo | onde | evidência |`.
|
|
40
|
+
* Cabeçalho e separador não contam; `untyped` são as linhas cujo tipo não é de {@link BLOCKER_TYPES}.
|
|
214
41
|
*/
|
|
215
|
-
|
|
216
|
-
const
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
superseded: scores.slice(0, -1),
|
|
226
|
-
path,
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Parseia o corpo de UMA linha `**Proof-Scores:**` — pares `chave=N` separados
|
|
232
|
-
* por espaço.
|
|
233
|
-
*
|
|
234
|
-
* @param {string} body
|
|
235
|
-
* @returns {Record<string, number>|undefined} undefined quando nenhum par
|
|
236
|
-
* VÁLIDO foi lido (linha vazia, só chaves desconhecidas, ou só valores
|
|
237
|
-
* não-numéricos). `undefined` e não `{}` pela mesma disciplina do
|
|
238
|
-
* `evaluatorCompositeScore`: "sem veredito" tem de ser distinguível de
|
|
239
|
-
* "veredito vazio".
|
|
240
|
-
*/
|
|
241
|
-
function parseProofScores(body) {
|
|
242
|
-
const out = {};
|
|
243
|
-
for (const m of body.matchAll(/([A-Za-zÀ-ÿ][\w-]*)\s*=\s*([^\s]+)/g)) {
|
|
244
|
-
const key = m[1].toLowerCase();
|
|
245
|
-
if (!PROOF_SCORE_KEYS.includes(key)) continue;
|
|
246
|
-
if (!/^[0-9]+(?:\.[0-9]+)?$/.test(m[2])) continue;
|
|
247
|
-
out[key] = Number(m[2]);
|
|
42
|
+
function parseBlockers(body) {
|
|
43
|
+
const blockers = [];
|
|
44
|
+
const untyped = [];
|
|
45
|
+
for (const line of body.split(/\r?\n/)) {
|
|
46
|
+
if (!/^\s*\|/.test(line)) continue;
|
|
47
|
+
const cells = line.trim().replace(/^\||\|$/g, '').split('|').map((c) => c.trim());
|
|
48
|
+
if (cells.every((c) => /^:?-{2,}:?$/.test(c) || c === '')) continue;
|
|
49
|
+
if (/^#$|^id$/i.test(cells[0]) && /^tipo$/i.test(cells[1] ?? '')) continue;
|
|
50
|
+
const row = { id: cells[0], type: normalizeType(cells[1] ?? ''), where: cells[2] ?? '', evidence: cells[3] ?? '' };
|
|
51
|
+
(BLOCKER_TYPES.includes(row.type) ? blockers : untyped).push(row);
|
|
248
52
|
}
|
|
249
|
-
return
|
|
53
|
+
return { blockers, untyped };
|
|
250
54
|
}
|
|
251
55
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
* `risk-detector.js` (a divisão "gate-check coleta, risk-detector decide"), e
|
|
258
|
-
* quem decide o teto é a rubrica `test-coverage.md`. O `min` NÃO é gravado
|
|
259
|
-
* aqui — é derivado por quem consome, para que exista um só lugar onde
|
|
260
|
-
* "mínimo de quantas chaves" é definido.
|
|
261
|
-
*
|
|
262
|
-
* Arquivo ausente ou ilegível: `{ mutationSection: false, mediaSection: false,
|
|
263
|
-
* proofScores: undefined }`. Nunca lança, e nunca devolve `0` no lugar de
|
|
264
|
-
* `undefined`.
|
|
265
|
-
*
|
|
266
|
-
* `mediaSection` é lido do MESMO arquivo pelo MESMO módulo, de propósito: uma
|
|
267
|
-
* segunda leitura do relatório noutro lugar é como duas superfícies passam a
|
|
268
|
-
* discordar sobre o que ele diz.
|
|
269
|
-
*
|
|
270
|
-
* @param {string} cwd
|
|
271
|
-
* @param {string} featureName
|
|
272
|
-
* @returns {{ mutationSection: boolean, mediaSection: boolean, proofScores: Record<string, number>|undefined, path: string }}
|
|
273
|
-
*/
|
|
274
|
-
export function evaluatorProofState(cwd, featureName) {
|
|
275
|
-
const path = evaluatorReportPath(cwd, featureName);
|
|
276
|
-
const text = readReport(cwd, featureName);
|
|
277
|
-
if (text === null) {
|
|
278
|
-
return { mutationSection: false, mediaSection: false, proofScores: undefined, path };
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Append-only outra vez: numa reavaliação a linha nova vai para o fim, e é
|
|
282
|
-
// ela que vale. A primeira linha descreveria a prova de uma rodada superada.
|
|
283
|
-
const lines = [...text.matchAll(PROOF_SCORES_RE)];
|
|
284
|
-
const proofScores = lines.length === 0
|
|
285
|
-
? undefined
|
|
286
|
-
: parseProofScores(lines[lines.length - 1][1]);
|
|
287
|
-
|
|
56
|
+
export function evaluatorVerdictFromText(text) {
|
|
57
|
+
if (typeof text !== 'string') return { verdict: undefined, blockers: [], untyped: [], superseded: [] };
|
|
58
|
+
const verdicts = [...text.matchAll(VERDICT_RE)].map((m) => m[1].toUpperCase());
|
|
59
|
+
const body = lastSectionBody(text, BLOCKERS_SECTION_RE);
|
|
60
|
+
const { blockers, untyped } = body === null ? { blockers: [], untyped: [] } : parseBlockers(body);
|
|
288
61
|
return {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
62
|
+
verdict: verdicts.length ? verdicts[verdicts.length - 1] : undefined,
|
|
63
|
+
blockers,
|
|
64
|
+
untyped,
|
|
65
|
+
superseded: verdicts.slice(0, -1),
|
|
293
66
|
};
|
|
294
67
|
}
|
|
295
68
|
|
|
296
69
|
/**
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
* O corpo vai cru para `checklistCoverage` (`src/lib/specs/screen-checklist.js`),
|
|
300
|
-
* dono único do parser de linha do checklist.
|
|
301
|
-
*
|
|
302
|
-
* @returns {{ section: boolean, body: string }} arquivo ausente ou sem a seção:
|
|
303
|
-
* `{ section: false, body: '' }`. Nunca lança.
|
|
70
|
+
* `missing`: sem relatório. `unverdicted`: relatório sem `**Verdict:** SHIP|FIX` válido.
|
|
71
|
+
* `ok`: veredito legível — quem decide se ele libera o ship é o risk-detector.
|
|
304
72
|
*/
|
|
305
|
-
export function
|
|
73
|
+
export function evaluatorReportState(cwd, featureName) {
|
|
74
|
+
const path = evaluatorReportPath(cwd, featureName);
|
|
306
75
|
const text = readReport(cwd, featureName);
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
const next = rest.search(/^#{1,2}\s/m);
|
|
312
|
-
return { section: true, body: next === -1 ? rest : rest.slice(0, next) };
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* O menor Proof-Score das provas de EVIDÊNCIA — o que impõe o teto em
|
|
317
|
-
* `test-coverage`.
|
|
318
|
-
*
|
|
319
|
-
* Calculado sobre {@link MUTATION_PROOF_KEYS} — que é
|
|
320
|
-
* {@link PROOF_SCORE_KEYS} MENOS {@link NON_MUTATION_PROOF_KEYS}, e não uma lista
|
|
321
|
-
* digitada. `media` responde a outra pergunta e limita outra dimensão
|
|
322
|
-
* (`architecture`); ler as duas famílias como um mínimo só faria um `media=4`
|
|
323
|
-
* derrubar o teto de teste sem que nada de teste tivesse acontecido. Uma chave
|
|
324
|
-
* NOVA de prova entra aqui sozinha: excluí-la exige uma linha deliberada em
|
|
325
|
-
* `NON_MUTATION_PROOF_KEYS`, e nunca acontece por esquecimento.
|
|
326
|
-
*
|
|
327
|
-
* @param {Record<string, number>|undefined} proofScores
|
|
328
|
-
* @returns {number|null} null quando não há nenhum score do mínimo: SEM TETO A
|
|
329
|
-
* APLICAR, jamais "teto 0". A mesma razão pela qual `count: null` do verify
|
|
330
|
-
* não é 0.
|
|
331
|
-
*/
|
|
332
|
-
export function minProofScore(proofScores) {
|
|
333
|
-
if (!proofScores) return null;
|
|
334
|
-
const values = MUTATION_PROOF_KEYS
|
|
335
|
-
.map((key) => proofScores[key])
|
|
336
|
-
.filter((n) => typeof n === 'number' && Number.isFinite(n));
|
|
337
|
-
return values.length === 0 ? null : Math.min(...values);
|
|
76
|
+
if (text === null) return { status: 'missing', path };
|
|
77
|
+
const parsed = evaluatorVerdictFromText(text);
|
|
78
|
+
if (!VERDICTS.includes(parsed.verdict)) return { status: 'unverdicted', path };
|
|
79
|
+
return { status: 'ok', path, ...parsed };
|
|
338
80
|
}
|
|
339
81
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
*
|
|
344
|
-
* @param {Record<string, number>|undefined} proofScores
|
|
345
|
-
* @returns {number|null} null quando a chave não foi registrada — o caso normal
|
|
346
|
-
* de uma feature que não gera mídia, e nunca "teto 0".
|
|
347
|
-
*/
|
|
348
|
-
export function mediaProofScore(proofScores) {
|
|
349
|
-
const value = proofScores?.media;
|
|
350
|
-
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
82
|
+
export function evaluatorMediaSection(cwd, featureName) {
|
|
83
|
+
const text = readReport(cwd, featureName);
|
|
84
|
+
return text !== null && MEDIA_SECTION_RE.test(text);
|
|
351
85
|
}
|
|
352
86
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
*
|
|
358
|
-
* Existe para quem tem o relatório fora do caminho canônico — o dashboard lê
|
|
359
|
-
* features arquivadas em `.morph/archive/{f}/4-review/` —, sem que ele
|
|
360
|
-
* precise de uma segunda cópia dos regexes.
|
|
361
|
-
*
|
|
362
|
-
* @param {string} text
|
|
363
|
-
* @returns {{ score: number|undefined, proofScores: Record<string, number>|undefined }}
|
|
364
|
-
*/
|
|
365
|
-
export function evaluatorVerdictFromText(text) {
|
|
366
|
-
if (typeof text !== 'string') return { score: undefined, proofScores: undefined };
|
|
367
|
-
const scores = compositeScoresIn(text);
|
|
368
|
-
const lines = [...text.matchAll(PROOF_SCORES_RE)];
|
|
369
|
-
return {
|
|
370
|
-
score: scores.length === 0 ? undefined : scores[scores.length - 1],
|
|
371
|
-
proofScores: lines.length === 0 ? undefined : parseProofScores(lines[lines.length - 1][1]),
|
|
372
|
-
};
|
|
87
|
+
export function evaluatorScreenChecklist(cwd, featureName) {
|
|
88
|
+
const text = readReport(cwd, featureName);
|
|
89
|
+
const body = text === null ? null : lastSectionBody(text, SCREEN_CHECKLIST_SECTION_RE);
|
|
90
|
+
return body === null ? { section: false, body: '' } : { section: true, body };
|
|
373
91
|
}
|