@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polymorphism-tech/morph-spec",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.45.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
|
|
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",
|
package/src/commands/e2e/e2e.js
CHANGED
|
@@ -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}.
|
|
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}.
|
|
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', '
|
|
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', '
|
|
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', '
|
|
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,
|
|
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(
|
|
362
|
-
console.error(chalk.
|
|
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
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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 {
|
|
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'
|
|
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
|
|
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
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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:
|
|
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. \'{"
|
|
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
|
-
*
|
|
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
|
|
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
|
-
// (
|
|
52
|
-
{ id: '
|
|
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', '
|
|
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', '
|
|
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 =
|
|
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}.
|
|
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
|
-
['
|
|
131
|
-
['Recomendação', 'Recomendacao', 'Recommendation'],
|
|
106
|
+
['Veredito', 'Verdict'],
|
|
132
107
|
],
|
|
133
108
|
recommended: [
|
|
134
|
-
['
|
|
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', '
|
|
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
|
|
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.
|
|
@@ -203,8 +203,7 @@ const PARTIAL_RUN_FLAGS = [['filter', 'filtered'], ['skipTests', 'skip-tests'],
|
|
|
203
203
|
* paths to it — never a verdict, because a verdict about nothing is worse
|
|
204
204
|
* than a refusal:
|
|
205
205
|
* - unknown feature, or unknown task id (the task's `group` decides the
|
|
206
|
-
* rubric, so it is a hard usage error here
|
|
207
|
-
* `morph-spec score` gives for the same situation);
|
|
206
|
+
* rubric, so it is a hard usage error here);
|
|
208
207
|
* - `--filter` that matches no declared node (a filtered-to-empty run is
|
|
209
208
|
* green and proves nothing);
|
|
210
209
|
* - `--record-baseline` with nowhere to write (no `config.json`), or with a
|
|
@@ -263,7 +262,7 @@ export async function verifyCommand(featureName, taskId, options = {}) {
|
|
|
263
262
|
process.exit(2);
|
|
264
263
|
}
|
|
265
264
|
// Ocupado não é veredito: nada foi verificado, então não há stamp nem
|
|
266
|
-
// evento, e o exit 3
|
|
265
|
+
// evento, e o exit 3 nunca é "vermelho".
|
|
267
266
|
if (err instanceof HeavyBusyError) {
|
|
268
267
|
if (options.json) {
|
|
269
268
|
const holders = err.holders.map(({ pid, label, feature, node, since }) => ({ pid, label, feature, node, since }));
|
|
@@ -271,7 +270,7 @@ export async function verifyCommand(featureName, taskId, options = {}) {
|
|
|
271
270
|
status: 'busy', persisted: false, busy: { reason: err.reason, holders, freeGb: err.freeGb, waitedMs: err.waitedMs },
|
|
272
271
|
}, null, 2));
|
|
273
272
|
}
|
|
274
|
-
console.error(chalk.yellow(`\n⏳ verify não rodou: ${err.message}. Não é vermelho —
|
|
273
|
+
console.error(chalk.yellow(`\n⏳ verify 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`));
|
|
275
274
|
process.exit(3);
|
|
276
275
|
}
|
|
277
276
|
throw err;
|
|
@@ -46,7 +46,6 @@ export const OUTPUT_SCHEMA = {
|
|
|
46
46
|
|
|
47
47
|
// 2-plan/
|
|
48
48
|
spec: { filename: 'spec.md', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
|
|
49
|
-
mandate: { filename: 'mandate.md', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
|
|
50
49
|
tasks: { filename: 'tasks.json', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan', mutableFields: ['status', 'outputs', 'notes'] },
|
|
51
50
|
decisions: { filename: 'decisions.md', phaseDir: '2-plan', phase: 'plan', protected: false },
|
|
52
51
|
// Veredito REUSAR/ESTENDER/CRIAR por conceito que a feature introduz, com a
|
|
@@ -55,7 +54,6 @@ export const OUTPUT_SCHEMA = {
|
|
|
55
54
|
// como decisions.md: é registro vivo — o implementador que descobre um alvo
|
|
56
55
|
// melhor no meio da task precisa poder anotar, e congelá-lo transformaria a
|
|
57
56
|
// correção num bloqueio de gate.
|
|
58
|
-
reuseMap: { filename: 'reuse-map.md', phaseDir: '2-plan', phase: 'plan', protected: false },
|
|
59
57
|
// N files, one per system use case — the behaviour layer the plan distils
|
|
60
58
|
// BEFORE spec.md/tasks.json. Deliberately NOT protected: editing a use case
|
|
61
59
|
// after Gate 2 is the supported way to change behaviour and re-run implement;
|
|
@@ -581,7 +581,6 @@ export async function createFeature(featureName, options = {}) {
|
|
|
581
581
|
review: { approved: false, timestamp: null, approvedBy: null }
|
|
582
582
|
},
|
|
583
583
|
gateDecisions: {},
|
|
584
|
-
taskScores: {},
|
|
585
584
|
tasks: {
|
|
586
585
|
total: 0,
|
|
587
586
|
completed: 0,
|
|
@@ -782,7 +781,7 @@ function findClosestMatch(inputType, validTypes) {
|
|
|
782
781
|
/**
|
|
783
782
|
* Mark output as created
|
|
784
783
|
* @param {string} featureName - Feature name
|
|
785
|
-
* @param {string} outputType - Output type (proposal, spec,
|
|
784
|
+
* @param {string} outputType - Output type (proposal, spec, tasks, etc.)
|
|
786
785
|
*/
|
|
787
786
|
export async function markOutput(featureName, outputType) {
|
|
788
787
|
await ensureFeature(featureName);
|