@polymorphism-tech/morph-spec 8.44.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 +10 -9
- 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 +10 -10
- 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/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/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 +10 -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 +1 -1
- package/framework/standards/ai-agents/vector-data-extensions.md +1 -1
- package/framework/standards/ai-agents/workflows.md +1 -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/state/approve.js +7 -49
- package/src/commands/state/gate-check.js +16 -106
- 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 +3 -4
- 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/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/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 +60 -122
- package/src/lib/scout/scout-check.js +293 -0
- 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/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/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
|
@@ -35,7 +35,6 @@ const POINTER_CANDIDATES = [
|
|
|
35
35
|
// Behaviour before solution — the same order morph-plan writes them in.
|
|
36
36
|
['2-plan/usecases', 'use cases: comportamento e pós-condições testáveis (pasta)'],
|
|
37
37
|
['2-plan/spec.md', 'especificação técnica'],
|
|
38
|
-
['2-plan/mandate.md', 'contrato de implementação (leia antes de cada task)'],
|
|
39
38
|
['2-plan/tasks.json', 'DAG de tasks (status vivo)'],
|
|
40
39
|
['decisions.md', 'decisões registradas'],
|
|
41
40
|
['3-implement/recap.md', 'recap da implementação'],
|
|
@@ -97,18 +96,18 @@ function nextAction(feature, phase, tasks, featureName) {
|
|
|
97
96
|
// the derived phase only flips when 2-plan/spec.md lands (derivePhase is
|
|
98
97
|
// artifact-first). Pointing back at the proposal here would be wrong.
|
|
99
98
|
return approved('proposal')
|
|
100
|
-
? `Gate 1 aprovado — produza \`2-plan/spec.md\` + \`
|
|
99
|
+
? `Gate 1 aprovado — produza \`2-plan/spec.md\` + \`tasks.json\` (a fase avança com o primeiro artefato; Gate 2 em seguida).`
|
|
101
100
|
: `Complete \`0-proposal/proposal.md\` e aprove o Gate 1 (\`morph-spec advance ${featureName}\`). Skill: /morph-proposal ${featureName}.`;
|
|
102
101
|
case 'uiux':
|
|
103
102
|
return approved('uiux')
|
|
104
|
-
? `Gate de uiux aprovado — produza \`2-plan/spec.md\` + \`
|
|
103
|
+
? `Gate de uiux aprovado — produza \`2-plan/spec.md\` + \`tasks.json\` (Gate 2 em seguida).`
|
|
105
104
|
: `Complete \`1-design/design-system.md\` e aprove o gate de uiux (\`morph-spec advance ${featureName}\`).`;
|
|
106
105
|
case 'plan':
|
|
107
|
-
return `Produza \`2-plan/spec.md\` + \`
|
|
106
|
+
return `Produza \`2-plan/spec.md\` + \`tasks.json\` e aprove o Gate 2 (\`morph-spec advance ${featureName}\`).`;
|
|
108
107
|
case 'implement': {
|
|
109
108
|
const ready = tasks?.ready?.map((t) => `${t.id} "${t.title}"`).join(', ');
|
|
110
109
|
return ready
|
|
111
|
-
? `Execute as tasks ready — ${ready} — via /morph-apply ${featureName} (verify
|
|
110
|
+
? `Execute as tasks ready — ${ready} — via /morph-apply ${featureName} (task fecha com verify verde e critérios provados).`
|
|
112
111
|
: `Todas as tasks concluídas ou bloqueadas — rode \`morph-spec advance ${featureName}\` para escancarar a review, ou destrave dependências. Skill: /morph-apply ${featureName}.`;
|
|
113
112
|
}
|
|
114
113
|
case 'review':
|
|
@@ -59,7 +59,7 @@ metadata:
|
|
|
59
59
|
|
|
60
60
|
## Core skills
|
|
61
61
|
|
|
62
|
-
\`morph-proposal\`, \`morph-apply\`, \`morph-brainstorming\`, \`morph-plan\`, \`morph-implement\`, \`morph-
|
|
62
|
+
\`morph-proposal\`, \`morph-apply\`, \`morph-brainstorming\`, \`morph-plan\`, \`morph-implement\`, \`morph-review\`, \`morph-stack-scan\`, \`morph-standards\`
|
|
63
63
|
|
|
64
64
|
**Why:** This project is managed by morph-spec — spec-driven autonomous development with trust-aware gates.
|
|
65
65
|
|
|
@@ -86,7 +86,7 @@ metadata:
|
|
|
86
86
|
- \`.morph/framework/schemas/tasks.schema.json\` — tasks.json schema reference
|
|
87
87
|
- \`.morph/framework/phases.json\` — phase folder mapping and skill associations
|
|
88
88
|
- \`.morph/framework/agents.json\` — domain persona definitions (dotnet-senior, ef-modeler, etc.)
|
|
89
|
-
- \`.morph/framework/evals/
|
|
89
|
+
- \`.morph/framework/evals/review-checklist.md\` — what the Gate 3 evaluator checks (verdict SHIP/FIX + evidence-typed blockers)
|
|
90
90
|
`;
|
|
91
91
|
|
|
92
92
|
/**
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { posix, win32 } from 'node:path';
|
|
2
2
|
|
|
3
3
|
export const MUTANT_ID = /^[A-Za-z][A-Za-z0-9_-]{0,31}$/;
|
|
4
4
|
export const MODES = ['schemata', 'rebuild'];
|
|
5
5
|
export const BACKSLASH_TOKEN = '{{BS}}';
|
|
6
|
+
export const MAX_MUTANTS = 3;
|
|
6
7
|
|
|
7
8
|
const expandBackslash = (s) => (typeof s === 'string' ? s.split(BACKSLASH_TOKEN).join(String.fromCharCode(92)) : s);
|
|
8
9
|
|
|
@@ -58,6 +59,9 @@ export function validateManifest(manifest, { fileExists } = {}) {
|
|
|
58
59
|
if (!Array.isArray(list) || list.length === 0) {
|
|
59
60
|
throw new ManifestError(['`mutants` precisa ser um array com ao menos um mutante']);
|
|
60
61
|
}
|
|
62
|
+
if (list.length > MAX_MUTANTS) {
|
|
63
|
+
throw new ManifestError([`no máximo ${MAX_MUTANTS} mutantes por rodada: escolha as linhas de guarda ou regra que um UC exige`]);
|
|
64
|
+
}
|
|
61
65
|
const seen = new Set();
|
|
62
66
|
const mutants = list.map((raw, index) => {
|
|
63
67
|
const at = `mutants[${index}]${typeof raw?.id === 'string' ? ` (${raw.id})` : ''}`;
|
|
@@ -66,17 +70,19 @@ export function validateManifest(manifest, { fileExists } = {}) {
|
|
|
66
70
|
else if (seen.has(m.id)) problems.push(`${at}: \`id\` repetido`);
|
|
67
71
|
else seen.add(m.id);
|
|
68
72
|
if (typeof m.file !== 'string' || m.file === '') problems.push(`${at}: \`file\` ausente`);
|
|
69
|
-
else if (isAbsolute(m.file) ||
|
|
73
|
+
else if (posix.isAbsolute(m.file) || win32.isAbsolute(m.file) || m.file.split(/[\\/]/).includes('..')) problems.push(`${at}: \`file\` tem de ser relativo à raiz do projeto, sem \`..\``);
|
|
70
74
|
else if (fileExists && !fileExists(m.file)) problems.push(`${at}: \`file\` não existe: ${m.file}`);
|
|
71
75
|
if (!Number.isInteger(m.line) || m.line < 1) problems.push(`${at}: \`line\` tem de ser inteiro >= 1`);
|
|
72
76
|
if (typeof m.find !== 'string' || m.find === '') problems.push(`${at}: \`find\` ausente`);
|
|
73
77
|
if (typeof m.replace !== 'string') problems.push(`${at}: \`replace\` ausente (use string vazia para apagar)`);
|
|
74
78
|
else if (m.replace === m.find) problems.push(`${at}: \`replace\` igual a \`find\` não é mutação`);
|
|
75
79
|
if (m.mode !== undefined && !MODES.includes(m.mode)) problems.push(`${at}: \`mode\` tem de ser ${MODES.join(' | ')}`);
|
|
80
|
+
if (typeof m.expect !== 'string' || m.expect.trim() === '') problems.push(`${at}: \`expect\` ausente — diga qual teste deve falhar e por quê; sem isso o mutante pode ser equivalente`);
|
|
76
81
|
return {
|
|
77
82
|
id: m.id, file: typeof m.file === 'string' ? m.file.replace(/\\/g, '/') : m.file,
|
|
78
83
|
line: m.line, find: expandBackslash(m.find), replace: expandBackslash(m.replace), mode: m.mode ?? null,
|
|
79
84
|
note: typeof m.note === 'string' ? m.note : null,
|
|
85
|
+
expect: typeof m.expect === 'string' ? m.expect : null,
|
|
80
86
|
};
|
|
81
87
|
});
|
|
82
88
|
if (problems.length > 0) throw new ManifestError(problems);
|
|
@@ -13,7 +13,7 @@ function countCell(m) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
function reasonCell(m) {
|
|
16
|
-
if (m.verdict === 'survived') return m.reason ||
|
|
16
|
+
if (m.verdict === 'survived') return m.reason || `nenhum teste falhou ${m.scope === 'subset' ? 'no subconjunto' : 'na suíte inteira'}${m.expect ? ` — esperado: ${m.expect}` : ''}`;
|
|
17
17
|
const how = m.demoted ? ` [rebuild: ${m.demoted}]` : '';
|
|
18
18
|
return `${m.reason || '—'}${how}`;
|
|
19
19
|
}
|
|
@@ -30,12 +30,14 @@ export function renderSection(result, { head = null, date = null, subset = null,
|
|
|
30
30
|
const lines = ['## Mutações', ''];
|
|
31
31
|
const { method } = result;
|
|
32
32
|
const where = subset?.usable
|
|
33
|
-
?
|
|
33
|
+
? (subset.classes
|
|
34
|
+
? `${subset.classes.length} classe(s) de teste (${subset.files?.changed?.length ?? 0} alterada(s) pela feature, ${subset.files?.mentions?.length ?? 0} citando os tipos mutados)`
|
|
35
|
+
: `${subset.files?.related?.length ?? 0} arquivo(s) de teste relacionados`)
|
|
34
36
|
: (subset?.reason ?? 'nenhum');
|
|
35
37
|
lines.push(`**Método:** \`morph-spec mutate\`${head ? ` sobre ${head.slice(0, 8)}` : ''}${date ? ` em ${date}` : ''}.`);
|
|
36
38
|
lines.push(`- ${method.schemata} mutante(s) por chave em UM build (schemata), ${method.rebuild} por rebuild real${(method.jobs ?? 1) > 1 ? `; rodadas de escopo rápido em ${method.jobs} workers` : ''}.`);
|
|
37
39
|
lines.push(`- Escopo rápido: ${method.subset.live ? where : `desligado (${method.subset.note ?? subset?.reason ?? 'sem subconjunto'})`}.`);
|
|
38
|
-
lines.push('- Morto = falha nova (ausente na rodada-base do mesmo escopo)
|
|
40
|
+
lines.push('- Morto = falha nova (ausente na rodada-base do mesmo escopo). Com subconjunto, a decisão fica nele: sobrevivente não é refeito na suíte inteira.');
|
|
39
41
|
if (result.baseline.subset) lines.push(`- Rodada-base do subconjunto: ${result.baseline.subset.executed} executados, ${result.baseline.subset.failed} falhando.`);
|
|
40
42
|
if (result.baseline.full) lines.push(`- Rodada-base da suíte inteira: ${result.baseline.full.executed} executados, ${result.baseline.full.failed} falhando.`);
|
|
41
43
|
if (note) lines.push(`- ${note}`);
|
|
@@ -98,7 +98,7 @@ export async function runMutation(input) {
|
|
|
98
98
|
};
|
|
99
99
|
|
|
100
100
|
const results = new Map(regions.map((r) => [r.id, {
|
|
101
|
-
id: r.id, file: r.file, line: r.line, diff: diffOf(r), note: r.note,
|
|
101
|
+
id: r.id, file: r.file, line: r.line, diff: diffOf(r), note: r.note, expect: r.expect,
|
|
102
102
|
md5Before: md5Of(readFileSync(join(root, r.file))),
|
|
103
103
|
md5After: md5Of(applyRegions(originals.get(r.file), [r], (x) => x.replaceText).text),
|
|
104
104
|
mode: r.mode === 'rebuild' || adapter.kind !== 'dotnet' || !/\.cs$/i.test(r.file) ? 'rebuild' : 'schemata',
|
|
@@ -262,12 +262,14 @@ export async function runMutation(input) {
|
|
|
262
262
|
const base = { scope: scopeOf, executed: result.executed, durationMs: result.durationMs };
|
|
263
263
|
if (verdict.state === 'killed') {
|
|
264
264
|
record(id, { ...base, verdict: 'killed', killers: verdict.failures.length, reason: verdict.reason });
|
|
265
|
+
} else if (scopeOf === 'subset' && verdict.state === 'survived') {
|
|
266
|
+
record(id, { ...base, verdict: 'survived', reason: '' });
|
|
265
267
|
} else {
|
|
266
268
|
record(id, { ...base, verdict: null, reason: verdict.reason ?? '' });
|
|
267
269
|
routed.push({ id, needs: 'full' });
|
|
268
270
|
}
|
|
269
271
|
}
|
|
270
|
-
for (const r of results.values()) if (r.mode === 'rebuild') routed.push({ id: r.id, needs: subsetLive ? 'subset
|
|
272
|
+
for (const r of results.values()) if (r.mode === 'rebuild') routed.push({ id: r.id, needs: subsetLive ? 'subset' : 'full' });
|
|
271
273
|
|
|
272
274
|
for (const { id, needs } of routed) {
|
|
273
275
|
applyOnDisk(id);
|
|
@@ -280,13 +282,13 @@ export async function runMutation(input) {
|
|
|
280
282
|
continue;
|
|
281
283
|
}
|
|
282
284
|
}
|
|
283
|
-
if (needs === 'subset
|
|
285
|
+
if (needs === 'subset') {
|
|
284
286
|
const sub = await run('subset', `${id}-subset`, envWithout(process.env));
|
|
285
287
|
const verdict = judge(sub, baselineSubset);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
288
|
+
const base = { scope: 'subset', executed: sub.executed, durationMs: sub.durationMs };
|
|
289
|
+
if (verdict.state === 'killed') record(id, { ...base, verdict: 'killed', killers: verdict.failures.length, reason: verdict.reason });
|
|
290
|
+
else record(id, { ...base, verdict: verdict.state, reason: verdict.reason ?? '' });
|
|
291
|
+
continue;
|
|
290
292
|
}
|
|
291
293
|
const full = await run('full', `${id}-full`, envWithout(process.env));
|
|
292
294
|
const first = judge(full, baselineFull);
|
package/src/lib/risk-detector.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Signals checked (in order):
|
|
9
9
|
* - designSystemDivergence: boolean — conflicting design sources detected
|
|
10
|
-
* - standardsViolations: string[] — violations of
|
|
10
|
+
* - standardsViolations: string[] — violations of the plan's forbidden patterns
|
|
11
11
|
* - verificationStatus: string — último `morph-spec verify` ('fail' pausa, qualquer gate)
|
|
12
12
|
* - verificationScope + gate: string — presente e != 'feature' pausa SÓ no ship: o
|
|
13
13
|
* último `verify` foi um recorte de task, não da feature inteira (`null`/ausente
|
|
@@ -17,31 +17,22 @@
|
|
|
17
17
|
* Sem escopo (stamp sem o campo) cai do mesmo lado seguro que `unknown`
|
|
18
18
|
* - verificationStale + gate: bool/str — stamp de um commit cujo código já mudou
|
|
19
19
|
* (fora de .morph/) pausa SÓ no ship (gate 'review')
|
|
20
|
-
* -
|
|
21
|
-
*
|
|
22
|
-
* -
|
|
23
|
-
* evaluator-report.md. No ship, relatório SEM a seção `## Mutações` numa
|
|
24
|
-
* feature com código de produção pausa: score é opinião, mutação é prova
|
|
25
|
-
* - mediaClaims + gate: object — { section, score, featureGeneratesMedia }
|
|
20
|
+
* - evaluatorVerdict + gate: object — { status, verdict, blockers, untyped } do
|
|
21
|
+
* evaluator-report.md. No ship, só `SHIP` sem bloqueador libera
|
|
22
|
+
* - mediaClaims + gate: object — { section, featureGeneratesMedia }
|
|
26
23
|
* - screenChecklist + gate: object — { mockups, legacy, screens, problems, exempt?,
|
|
27
24
|
* section?, uncovered? } derivado do 1-design/mockups.md. No uiux, checklist
|
|
28
25
|
* de tela incompleto pausa; no ship, item decidido sem a conferência do
|
|
29
26
|
* avaliador pausa. Mockup legado é isento nos dois, com a razão listada
|
|
30
27
|
* - oversizedTasks + gate: array — tasks acima de MAX_TASK_OUTPUTS outputs;
|
|
31
28
|
* pausa SÓ no plano (gate 'plan'): a task tem de caber num dispatch
|
|
32
|
-
* - evalScore: number — morph-eval score (pause if < 9)
|
|
33
29
|
* - taskCount: number — total tasks (pause if > 20)
|
|
34
30
|
*/
|
|
35
31
|
|
|
36
32
|
const SCOPE_TASK_THRESHOLD = 20;
|
|
37
|
-
const MIN_EVAL_SCORE = 9;
|
|
38
33
|
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
// evaluator-report.md — o veredito do avaliador independente em contexto
|
|
42
|
-
// fresco. Toda outra procedência (proxy de taskScores, override de --signals,
|
|
43
|
-
// ou nenhuma) é auto-avaliação ou palavra de quem já estava no contexto.
|
|
44
|
-
const TRUSTED_EVAL_SOURCE = 'evaluator-report';
|
|
34
|
+
// Caminho alterado que pede o avaliador em opus: o resto da review roda em sonnet.
|
|
35
|
+
const OPUS_PATH_RE = /(^|[/._-])(auth|login|senha|password|token|secret|crypto|permiss|rbac|acl|lgpd|pii|privacy|payment|pagamento|billing|cobranca|invoice|asaas|stripe|migrations?)([/._-]|$)/i;
|
|
45
36
|
|
|
46
37
|
// Work types whose ship gate (review) always pauses for a human, regardless
|
|
47
38
|
// of trust mode — a type policy, not a per-feature risk signal. Mirrors
|
|
@@ -168,6 +159,50 @@ export function uiuxChecklistPause(screenChecklist) {
|
|
|
168
159
|
+ ' ("não se aplica" vale, se escrito)';
|
|
169
160
|
}
|
|
170
161
|
|
|
162
|
+
function blockerList(v) {
|
|
163
|
+
return firstOf(v.blockerIds ?? []);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Por que o veredito do avaliador segura o ship, ou null. */
|
|
167
|
+
export function verdictPause(v) {
|
|
168
|
+
if (!v || v.status === 'missing') {
|
|
169
|
+
return 'Gate 3 sem `4-review/evaluator-report.md` — despache o avaliador independente;'
|
|
170
|
+
+ ' auto-avaliação de quem implementou não decide o ship';
|
|
171
|
+
}
|
|
172
|
+
if (v.status !== 'ok') {
|
|
173
|
+
return 'evaluator-report sem `**Verdict:** SHIP|FIX` — o avaliador não seguiu o contrato do relatório';
|
|
174
|
+
}
|
|
175
|
+
if ((v.untyped ?? 0) > 0) {
|
|
176
|
+
return `${v.untyped} bloqueador(es) sem tipo de evidência (uc | teste | regressao | seguranca)`
|
|
177
|
+
+ ' — sem evidência tipada não é bloqueador: reclassifique como nota ou complete a evidência';
|
|
178
|
+
}
|
|
179
|
+
if ((v.blockers ?? 0) > 0) return `avaliador abriu ${v.blockers} bloqueador(es): ${blockerList(v)}`;
|
|
180
|
+
if (v.verdict !== 'SHIP') return 'veredito do avaliador é FIX';
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function verdictSummary(v) {
|
|
185
|
+
if (!v || v.status !== 'ok') return 'Gate 3 sem veredito do avaliador independente';
|
|
186
|
+
return `veredito ${v.verdict}, ${v.blockers ?? 0} bloqueador(es)`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Modelo do avaliador do Gate 3: `opus` quando a feature toca caminho sensível
|
|
191
|
+
* (auth, segredo, permissão, dado pessoal, dinheiro, migration), prompt/modelo
|
|
192
|
+
* ou geração de mídia; `sonnet` no resto.
|
|
193
|
+
*
|
|
194
|
+
* @param {{ changedFiles?: string[], promptModelTouched?: boolean, generatesMedia?: boolean }} input
|
|
195
|
+
* @returns {{ model: 'opus'|'sonnet', reasons: string[] }}
|
|
196
|
+
*/
|
|
197
|
+
export function evaluatorModelFor({ changedFiles = [], promptModelTouched = false, generatesMedia = false } = {}) {
|
|
198
|
+
const reasons = [];
|
|
199
|
+
const risky = changedFiles.filter((f) => OPUS_PATH_RE.test(f.replaceAll('\\', '/')));
|
|
200
|
+
if (risky.length) reasons.push(`caminho sensível: ${firstOf(risky)}`);
|
|
201
|
+
if (promptModelTouched) reasons.push('prompt/modelo alterado');
|
|
202
|
+
if (generatesMedia) reasons.push('gera mídia');
|
|
203
|
+
return { model: reasons.length ? 'opus' : 'sonnet', reasons };
|
|
204
|
+
}
|
|
205
|
+
|
|
171
206
|
/**
|
|
172
207
|
* @param {object} config - .morph/config/config.json contents (uses `trust`)
|
|
173
208
|
* @param {object} signals
|
|
@@ -278,55 +313,9 @@ export function shouldPauseGate(config, signals) {
|
|
|
278
313
|
};
|
|
279
314
|
}
|
|
280
315
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
// levaram o gate-check a imprimir "auto-pass, nenhum sinal de risco" sobre um
|
|
285
|
-
// produto que o avaliador independente pontuou 7.2 e reprovou com 3 P1.
|
|
286
|
-
//
|
|
287
|
-
// O predicado é por AUSÊNCIA da fonte confiável, não por presença do proxy,
|
|
288
|
-
// porque há três maneiras de chegar aqui sem avaliador e todas devem pausar:
|
|
289
|
-
// proxy de taskScores; override manual via --signals; e nenhuma fonte (sem
|
|
290
|
-
// task pontuada e sem relatório, `evalScore` é OMITIDO e a regra de score
|
|
291
|
-
// abaixo nem chega a rodar).
|
|
292
|
-
//
|
|
293
|
-
// Fora do review não vale: em plan/uiux/proposal o proxy é exatamente o sinal
|
|
294
|
-
// certo — não existe avaliador independente antes de haver implementação.
|
|
295
|
-
if (signals.gate === 'review' && signals.evalScoreSource !== TRUSTED_EVAL_SOURCE) {
|
|
296
|
-
return {
|
|
297
|
-
pause: true,
|
|
298
|
-
reason: `Gate 3 sem score do avaliador independente (fonte: ${signals.evalScoreSource ?? 'nenhuma'})`
|
|
299
|
-
+ ' — auto-avaliação de quem implementou não decide o ship',
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// No gate de SHIP: o relatório do avaliador chegou SEM a seção `## Mutações`.
|
|
304
|
-
// O score diz o que o avaliador ACHOU; a seção diz o que ele PROVOU — e um
|
|
305
|
-
// teste verde só prova que o código roda, nunca que o teste morde. Medido:
|
|
306
|
-
// 11 testes verdes sobre um artefato que o build recusava, porque nenhum
|
|
307
|
-
// deles CARREGAVA o arquivo; e um caminho de escrita reduzido a `return 0;`
|
|
308
|
-
// que passou 23 de 23, todos de recusa.
|
|
309
|
-
//
|
|
310
|
-
// DEPOIS da cláusula de procedência e ANTES da de score, e a ordem é
|
|
311
|
-
// POLÍTICA, não acaso: quando faltam avaliador E seção, a razão reportada tem
|
|
312
|
-
// de ser a mais fundamental ("sem avaliador independente"), porque exigir a
|
|
313
|
-
// prova de um relatório que nem devia decidir o gate manda o humano consertar
|
|
314
|
-
// a coisa errada.
|
|
315
|
-
//
|
|
316
|
-
// FAIL-OPEN POR AUSÊNCIA, em duas direções: `productionCode === true` é
|
|
317
|
-
// exigido EXPLICITAMENTE, então uma feature legada (sinal ausente, `undefined`)
|
|
318
|
-
// não pausa, e uma feature 100% `docs` — que não tem o que mutar — também
|
|
319
|
-
// não. Quem decide as duas coisas é gate-check.js, lendo o disco; aqui só se
|
|
320
|
-
// aplica a política sobre fatos já reunidos.
|
|
321
|
-
if (signals.gate === 'review'
|
|
322
|
-
&& signals.mutationProof?.productionCode === true
|
|
323
|
-
&& signals.mutationProof?.section !== true) {
|
|
324
|
-
return {
|
|
325
|
-
pause: true,
|
|
326
|
-
reason: 'relatório do avaliador sem a seção `## Mutações` — prova de mutação não registrada'
|
|
327
|
-
+ ' (rubrica evals/proof-rubrics/mutation-proof.md): um teste verde prova que o código'
|
|
328
|
-
+ ' roda, não que o teste morde',
|
|
329
|
-
};
|
|
316
|
+
if (signals.gate === 'review') {
|
|
317
|
+
const reason = verdictPause(signals.evaluatorVerdict);
|
|
318
|
+
if (reason) return { pause: true, reason };
|
|
330
319
|
}
|
|
331
320
|
|
|
332
321
|
// No gate de SHIP apenas: a feature mexeu num caminho que o PROJETO declarou
|
|
@@ -368,24 +357,7 @@ export function shouldPauseGate(config, signals) {
|
|
|
368
357
|
// também já pausou, por `verificationStale`.
|
|
369
358
|
}
|
|
370
359
|
|
|
371
|
-
//
|
|
372
|
-
// seção `## Mídia gerada`. As quatro afirmações que essa seção carrega —
|
|
373
|
-
// custo, revisão humana, marcação e hot path não bloqueado — são fáceis de
|
|
374
|
-
// escrever e difíceis de provar, e a mais cara delas é a que ninguém percebe
|
|
375
|
-
// falhando: mídia gerada dentro de um turno de conversa, com latência
|
|
376
|
-
// documentada de até 2 min, custo por chamada e ZERO revisão possível.
|
|
377
|
-
//
|
|
378
|
-
// DEPOIS da cláusula de procedência e da de mutação, e a ordem é POLÍTICA,
|
|
379
|
-
// como lá: quando faltam avaliador, seção de mutação E seção de mídia, a razão
|
|
380
|
-
// reportada tem de ser a mais fundamental, porque mandar o humano consertar a
|
|
381
|
-
// terceira enquanto a primeira está aberta manda consertar a coisa errada.
|
|
382
|
-
//
|
|
383
|
-
// FAIL-OPEN POR AUSÊNCIA, igual: `featureGeneratesMedia === true` é exigido
|
|
384
|
-
// EXPLICITAMENTE, então uma feature legada (sinal ausente, `undefined`) não
|
|
385
|
-
// pausa, e a maioria das features — que não gera mídia — fica isenta
|
|
386
|
-
// automaticamente, sem ninguém precisar lembrar de aplicar a exceção. Quem
|
|
387
|
-
// decide isso é gate-check.js, lendo `standards[]` das tasks; aqui só se
|
|
388
|
-
// aplica a política sobre fatos já reunidos.
|
|
360
|
+
// Feature que gera mídia: custo, revisão humana, marcação e hot path só valem provados na seção.
|
|
389
361
|
if (signals.gate === 'review'
|
|
390
362
|
&& signals.mediaClaims?.featureGeneratesMedia === true
|
|
391
363
|
&& signals.mediaClaims.section !== true) {
|
|
@@ -393,7 +365,7 @@ export function shouldPauseGate(config, signals) {
|
|
|
393
365
|
pause: true,
|
|
394
366
|
reason: 'relatório do avaliador sem a seção `## Mídia gerada` — as quatro afirmações de mídia'
|
|
395
367
|
+ ' (custo, revisão humana, marcação, hot path) não foram provadas'
|
|
396
|
-
+ ' (
|
|
368
|
+
+ ' (evals/review-checklist.md, "Feature que gera mídia")',
|
|
397
369
|
};
|
|
398
370
|
}
|
|
399
371
|
|
|
@@ -417,7 +389,7 @@ export function shouldPauseGate(config, signals) {
|
|
|
417
389
|
pause: true,
|
|
418
390
|
reason: `relatório do avaliador sem a seção \`## Checklist de tela\` — o design decidiu ${firstOf(uncovered)}`
|
|
419
391
|
+ ' e a avaliação não registrou a conferência contra a tela entregue'
|
|
420
|
-
+ ' (
|
|
392
|
+
+ ' (evals/review-checklist.md, "Contrato de tela")',
|
|
421
393
|
};
|
|
422
394
|
}
|
|
423
395
|
return {
|
|
@@ -435,13 +407,6 @@ export function shouldPauseGate(config, signals) {
|
|
|
435
407
|
};
|
|
436
408
|
}
|
|
437
409
|
|
|
438
|
-
if (typeof signals.evalScore === 'number' && signals.evalScore < MIN_EVAL_SCORE) {
|
|
439
|
-
return {
|
|
440
|
-
pause: true,
|
|
441
|
-
reason: `eval score ${signals.evalScore} below threshold ${MIN_EVAL_SCORE}`,
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
|
|
445
410
|
if (typeof signals.taskCount === 'number' && signals.taskCount > SCOPE_TASK_THRESHOLD) {
|
|
446
411
|
return {
|
|
447
412
|
pause: true,
|
|
@@ -479,24 +444,7 @@ export function getRiskSignals(signals) {
|
|
|
479
444
|
if (signals.gate === 'review' && signals.verificationStale === true) {
|
|
480
445
|
out.push('verification stamp desatualizado (stale) no ship');
|
|
481
446
|
}
|
|
482
|
-
if (signals.gate === 'review'
|
|
483
|
-
out.push(`score do Gate 3 sem avaliador independente (fonte: ${signals.evalScoreSource ?? 'nenhuma'})`);
|
|
484
|
-
}
|
|
485
|
-
// Listado mesmo quando NÃO pausa (feature só de docs, ou prova presente com
|
|
486
|
-
// score baixo): a prova é o insumo do teto de `test-coverage.md`, e um teto
|
|
487
|
-
// que ninguém enxerga não é aplicado por ninguém.
|
|
488
|
-
if (signals.gate === 'review' && signals.mutationProof) {
|
|
489
|
-
const { section, min, productionCode } = signals.mutationProof;
|
|
490
|
-
if (section !== true) {
|
|
491
|
-
out.push(productionCode === true
|
|
492
|
-
? 'relatório sem a seção `## Mutações` (prova de mutação ausente)'
|
|
493
|
-
: 'relatório sem a seção `## Mutações` — feature sem código de produção, não pausa');
|
|
494
|
-
} else if (typeof min === 'number') {
|
|
495
|
-
out.push(`prova de mutação registrada (menor Proof-Score: ${min})`);
|
|
496
|
-
} else {
|
|
497
|
-
out.push('prova de mutação registrada (sem `**Proof-Scores:**` — nenhum teto a aplicar)');
|
|
498
|
-
}
|
|
499
|
-
}
|
|
447
|
+
if (signals.gate === 'review') out.push(verdictSummary(signals.evaluatorVerdict));
|
|
500
448
|
// Listado mesmo quando NÃO pausa, pela mesma razão do e2e: um projeto que não
|
|
501
449
|
// declara caminho de prompt/modelo é indistinguível, no relatório, de um que
|
|
502
450
|
// declara e não tocou em nada — e as duas coisas significam coisas diferentes.
|
|
@@ -511,9 +459,6 @@ export function getRiskSignals(signals) {
|
|
|
511
459
|
if (signals.gate === 'plan' && Array.isArray(signals.oversizedTasks) && signals.oversizedTasks.length > 0) {
|
|
512
460
|
out.push(`tasks grandes demais: ${signals.oversizedTasks.map((t) => t.id).join(', ')}`);
|
|
513
461
|
}
|
|
514
|
-
if (typeof signals.evalScore === 'number' && signals.evalScore < MIN_EVAL_SCORE) {
|
|
515
|
-
out.push(`eval score ${signals.evalScore}/10`);
|
|
516
|
-
}
|
|
517
462
|
if (typeof signals.taskCount === 'number' && signals.taskCount > SCOPE_TASK_THRESHOLD) {
|
|
518
463
|
out.push(`${signals.taskCount} tasks (over threshold)`);
|
|
519
464
|
}
|
|
@@ -521,14 +466,9 @@ export function getRiskSignals(signals) {
|
|
|
521
466
|
// de uma feature que não gera mídia tem de ser VISÍVEL, senão é
|
|
522
467
|
// indistinguível de uma omissão.
|
|
523
468
|
if (signals.gate === 'review' && signals.mediaClaims?.featureGeneratesMedia === true) {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
} else if (typeof score === 'number') {
|
|
528
|
-
out.push(`prova de mídia registrada (media=${score}; teto em arquitetura)`);
|
|
529
|
-
} else {
|
|
530
|
-
out.push('prova de mídia registrada (sem `media=` nos Proof-Scores — nenhum teto a aplicar)');
|
|
531
|
-
}
|
|
469
|
+
out.push(signals.mediaClaims.section === true
|
|
470
|
+
? 'prova de mídia registrada'
|
|
471
|
+
: 'relatório sem a seção `## Mídia gerada` (prova de mídia ausente)');
|
|
532
472
|
}
|
|
533
473
|
|
|
534
474
|
// Listado mesmo quando NÃO pausa: a isenção (sem mockup, legado, nada
|
|
@@ -563,8 +503,6 @@ export function getRiskSignals(signals) {
|
|
|
563
503
|
|
|
564
504
|
export const RISK_CONSTANTS = {
|
|
565
505
|
SCOPE_TASK_THRESHOLD,
|
|
566
|
-
MIN_EVAL_SCORE,
|
|
567
506
|
FORCED_PAUSE_WORK_TYPES,
|
|
568
|
-
TRUSTED_EVAL_SOURCE,
|
|
569
507
|
E2E_SKIP_CLASSES_THAT_PAUSE,
|
|
570
508
|
};
|