@polymorphism-tech/morph-spec 8.24.3 → 8.26.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/README.md +2 -2
- package/bin/morph-spec.js +2 -1
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +2 -2
- package/framework/MORPH.md +22 -21
- package/framework/agents.json +1 -1
- package/framework/commands/create-umbraco-lp.md +1 -1
- package/framework/commands/migrate-lp-to-umbraco.md +1 -1
- package/framework/commands/morph-apply.md +4 -4
- package/framework/commands/morph-hotfix.md +3 -3
- package/framework/commands/morph-proposal.md +5 -5
- package/framework/docs/CLI.md +4 -4
- package/framework/evals/rubrics/condensed.md +2 -2
- package/framework/evals/rubrics/test-coverage.md +7 -0
- package/framework/hooks/claude-code/core/protect-spec-files.js +115 -4
- package/framework/hooks/shared/payload-utils.js +19 -4
- package/framework/rules/dotnet-templates.md +2 -1
- package/framework/rules/morph-workflow.md +42 -29
- 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 +6 -6
- package/framework/skills/core/morph-brainstorming/SKILL.md +2 -2
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +7 -7
- package/framework/skills/core/morph-implement/SKILL.md +21 -6
- package/framework/skills/core/morph-plan/SKILL.md +15 -3
- package/framework/skills/core/morph-proposal/SKILL.md +7 -7
- package/framework/skills/core/morph-review/SKILL.md +10 -3
- package/framework/skills/core/morph-stack-scan/SKILL.md +3 -3
- package/framework/skills/core/morph-standards/SKILL.md +4 -4
- package/framework/skills/core/morph-uiux/SKILL.md +3 -3
- package/framework/templates/README.md +72 -643
- package/framework/templates/examples/spec-examples.md +2 -1
- package/package.json +1 -1
- package/src/commands/project/claim.js +19 -3
- package/src/commands/state/approve.js +17 -3
- package/src/commands/state/finish.js +202 -15
- package/src/commands/state/gate-check.js +9 -20
- package/src/commands/state/report-check.js +157 -5
- package/src/lib/evaluator-report.js +72 -0
- package/src/lib/risk-detector.js +35 -0
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
|
|
20
|
+
import { existsSync, readFileSync } from 'fs';
|
|
21
|
+
import { getAbsoluteOutputPath } from '../core/paths/output-schema.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* O token machine-readable que o avaliador grava por contrato (`morph-review`
|
|
25
|
+
* §4). Espelho de `REVIEWED_SHA_TOKEN` do approve.js — mesma ideia: um relatório
|
|
26
|
+
* de LLM só é auditável por máquina se carregar um campo que a máquina lê.
|
|
27
|
+
*/
|
|
28
|
+
export const COMPOSITE_SCORE_TOKEN = '**Composite-Score:**';
|
|
29
|
+
|
|
30
|
+
const COMPOSITE_SCORE_RE = /\*\*Composite-Score:\*\*\s*([0-9]+(?:\.[0-9]+)?)/;
|
|
31
|
+
|
|
32
|
+
/** Path canônico do evaluator-report da feature. Fonte única para os dois comandos. */
|
|
33
|
+
export function evaluatorReportPath(cwd, featureName) {
|
|
34
|
+
return getAbsoluteOutputPath(cwd, featureName, 'evaluatorReport');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Score composto REAL da feature, do token gravado pelo avaliador independente.
|
|
39
|
+
*
|
|
40
|
+
* @returns {number|undefined} undefined quando o relatório não existe, não é
|
|
41
|
+
* legível, ou existe sem o token (relatório legado ou oco). `undefined` é
|
|
42
|
+
* deliberado e não 0: quem chama distingue "sem veredito" de "veredito ruim".
|
|
43
|
+
*/
|
|
44
|
+
export function evaluatorCompositeScore(cwd, featureName) {
|
|
45
|
+
try {
|
|
46
|
+
const p = evaluatorReportPath(cwd, featureName);
|
|
47
|
+
if (!existsSync(p)) return undefined;
|
|
48
|
+
const m = readFileSync(p, 'utf8').match(COMPOSITE_SCORE_RE);
|
|
49
|
+
return m ? Number(m[1]) : undefined;
|
|
50
|
+
} catch {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Estado do relatório para a precondição do Gate 3.
|
|
57
|
+
*
|
|
58
|
+
* Três valores em vez de booleano PORQUE AS TRÊS SITUAÇÕES PEDEM MENSAGENS
|
|
59
|
+
* DIFERENTES: arquivo ausente é "despache o avaliador"; arquivo sem token é
|
|
60
|
+
* "o avaliador rodou mas o relatório está oco — ele não seguiu o contrato";
|
|
61
|
+
* e só o terceiro caso é aprovável.
|
|
62
|
+
*
|
|
63
|
+
* @returns {{ status: 'ok'|'missing'|'untokenized', score?: number, path: string }}
|
|
64
|
+
*/
|
|
65
|
+
export function evaluatorReportState(cwd, featureName) {
|
|
66
|
+
const path = evaluatorReportPath(cwd, featureName);
|
|
67
|
+
if (!existsSync(path)) return { status: 'missing', path };
|
|
68
|
+
const score = evaluatorCompositeScore(cwd, featureName);
|
|
69
|
+
return score === undefined
|
|
70
|
+
? { status: 'untokenized', path }
|
|
71
|
+
: { status: 'ok', score, path };
|
|
72
|
+
}
|
package/src/lib/risk-detector.js
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* - verificationStatus: string — último `morph-spec verify` ('fail' pausa, qualquer gate)
|
|
12
12
|
* - verificationStale + gate: bool/str — stamp com sha != HEAD pausa SÓ no
|
|
13
13
|
* ship (gate 'review') — código mudou depois do último verify
|
|
14
|
+
* - evalScoreSource + gate: str — no ship, score que NÃO veio do avaliador
|
|
15
|
+
* independente pausa (auto-avaliação não decide o Gate 3)
|
|
14
16
|
* - evalScore: number — morph-eval score (pause if < 9)
|
|
15
17
|
* - taskCount: number — total tasks (pause if > 20)
|
|
16
18
|
*/
|
|
@@ -18,6 +20,13 @@
|
|
|
18
20
|
const SCOPE_TASK_THRESHOLD = 20;
|
|
19
21
|
const MIN_EVAL_SCORE = 9;
|
|
20
22
|
|
|
23
|
+
// A ÚNICA procedência de `evalScore` que pode decidir o gate de ship. Escrita
|
|
24
|
+
// por gate-check.js quando o token `**Composite-Score:**` foi lido do
|
|
25
|
+
// evaluator-report.md — o veredito do avaliador independente em contexto
|
|
26
|
+
// fresco. Toda outra procedência (proxy de taskScores, override de --signals,
|
|
27
|
+
// ou nenhuma) é auto-avaliação ou palavra de quem já estava no contexto.
|
|
28
|
+
const TRUSTED_EVAL_SOURCE = 'evaluator-report';
|
|
29
|
+
|
|
21
30
|
// Work types whose ship gate (review) always pauses for a human, regardless
|
|
22
31
|
// of trust mode — a type policy, not a per-feature risk signal. Mirrors
|
|
23
32
|
// `forcedPauseGates` in src/core/work-types.js (hotfix.review); kept here too
|
|
@@ -80,6 +89,28 @@ export function shouldPauseGate(config, signals) {
|
|
|
80
89
|
};
|
|
81
90
|
}
|
|
82
91
|
|
|
92
|
+
// No gate de SHIP, um `evalScore` que não veio do avaliador independente NÃO
|
|
93
|
+
// é sinal de qualidade da feature — é sinal de qualidade das tasks, dado por
|
|
94
|
+
// quem as implementou. Observado em campo: 7 tasks com auto-score 9.0-9.3
|
|
95
|
+
// levaram o gate-check a imprimir "auto-pass, nenhum sinal de risco" sobre um
|
|
96
|
+
// produto que o avaliador independente pontuou 7.2 e reprovou com 3 P1.
|
|
97
|
+
//
|
|
98
|
+
// O predicado é por AUSÊNCIA da fonte confiável, não por presença do proxy,
|
|
99
|
+
// porque há três maneiras de chegar aqui sem avaliador e todas devem pausar:
|
|
100
|
+
// proxy de taskScores; override manual via --signals; e nenhuma fonte (sem
|
|
101
|
+
// task pontuada e sem relatório, `evalScore` é OMITIDO e a regra de score
|
|
102
|
+
// abaixo nem chega a rodar).
|
|
103
|
+
//
|
|
104
|
+
// Fora do review não vale: em plan/uiux/proposal o proxy é exatamente o sinal
|
|
105
|
+
// certo — não existe avaliador independente antes de haver implementação.
|
|
106
|
+
if (signals.gate === 'review' && signals.evalScoreSource !== TRUSTED_EVAL_SOURCE) {
|
|
107
|
+
return {
|
|
108
|
+
pause: true,
|
|
109
|
+
reason: `Gate 3 sem score do avaliador independente (fonte: ${signals.evalScoreSource ?? 'nenhuma'})`
|
|
110
|
+
+ ' — auto-avaliação de quem implementou não decide o ship',
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
83
114
|
if (typeof signals.evalScore === 'number' && signals.evalScore < MIN_EVAL_SCORE) {
|
|
84
115
|
return {
|
|
85
116
|
pause: true,
|
|
@@ -112,6 +143,9 @@ export function getRiskSignals(signals) {
|
|
|
112
143
|
if (signals.gate === 'review' && signals.verificationStale === true) {
|
|
113
144
|
out.push('verification stamp desatualizado (stale) no ship');
|
|
114
145
|
}
|
|
146
|
+
if (signals.gate === 'review' && signals.evalScoreSource !== TRUSTED_EVAL_SOURCE) {
|
|
147
|
+
out.push(`score do Gate 3 sem avaliador independente (fonte: ${signals.evalScoreSource ?? 'nenhuma'})`);
|
|
148
|
+
}
|
|
115
149
|
if (typeof signals.evalScore === 'number' && signals.evalScore < MIN_EVAL_SCORE) {
|
|
116
150
|
out.push(`eval score ${signals.evalScore}/10`);
|
|
117
151
|
}
|
|
@@ -125,4 +159,5 @@ export const RISK_CONSTANTS = {
|
|
|
125
159
|
SCOPE_TASK_THRESHOLD,
|
|
126
160
|
MIN_EVAL_SCORE,
|
|
127
161
|
FORCED_PAUSE_WORK_TYPES,
|
|
162
|
+
TRUSTED_EVAL_SOURCE,
|
|
128
163
|
};
|