@polymorphism-tech/morph-spec 8.46.0 → 9.0.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 +53 -44
- package/README.md +3 -4
- package/bin/morph-spec.js +38 -55
- package/bin/validate.js +5 -134
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +2 -2
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +41 -101
- package/framework/MORPH.md +95 -252
- package/framework/agents.json +75 -18
- package/framework/commands/create-umbraco-lp.md +2 -0
- package/framework/commands/migrate-lp-to-umbraco.md +2 -0
- package/framework/commands/morph-apply.md +5 -28
- package/framework/commands/morph-archive.md +2 -2
- package/framework/commands/morph-hotfix.md +8 -8
- package/framework/commands/morph-infra.md +17 -203
- package/framework/commands/morph-media.md +3 -0
- package/framework/commands/morph-preflight.md +7 -31
- package/framework/commands/morph-proposal.md +2 -50
- package/framework/commands/morph-status.md +3 -3
- package/framework/commands/morph-troubleshoot.md +2 -14
- package/framework/docs/CLI.md +585 -286
- package/framework/evals/review-checklist.md +82 -41
- package/framework/hooks/README.md +10 -11
- package/framework/hooks/claude-code/core/comment-guard.js +1 -1
- package/framework/hooks/claude-code/core/denylist.json +33 -3
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +16 -20
- package/framework/hooks/claude-code/core/gate-guard.js +2 -11
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +38 -0
- package/framework/hooks/claude-code/core/protect-spec-files.js +30 -242
- package/framework/hooks/claude-code/core/state-sync.js +17 -19
- package/framework/hooks/claude-code/core/telemetry-log.js +3 -3
- package/framework/hooks/claude-code/core/validate-completion.js +33 -97
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +31 -0
- package/framework/hooks/claude-code/core/worktree-context.js +5 -4
- package/framework/hooks/claude-code/statusline.py +55 -41
- package/framework/hooks/package.json +3 -0
- package/framework/hooks/shared/activity-logger.js +1 -1
- package/framework/hooks/shared/build-check.js +10 -6
- package/framework/hooks/shared/command-tokens.js +169 -0
- package/framework/hooks/shared/comment-rules.js +1 -1
- package/framework/hooks/shared/comment-scanner.js +10 -23
- package/framework/hooks/shared/comment-stripper.js +1 -1
- package/framework/hooks/shared/e2e-check.js +67 -31
- package/framework/hooks/shared/errata.js +72 -0
- package/framework/hooks/shared/feature-locator.js +1 -1
- package/framework/hooks/shared/frontend-locations.js +6 -0
- package/framework/hooks/shared/gate-question.js +18 -0
- package/framework/hooks/shared/phase-utils.js +15 -32
- package/framework/hooks/shared/slice-progress.js +118 -0
- package/framework/hooks/shared/spawn-command.js +23 -0
- package/framework/hooks/shared/state-reader.js +61 -87
- package/framework/hooks/shared/state-writer.js +3 -3
- package/framework/mcp/registry.json +3 -4
- package/framework/phases.json +23 -39
- package/framework/rules/ai-agents-standards.md +18 -16
- package/framework/rules/asaas-standards.md +3 -1
- package/framework/rules/csharp-standards.md +10 -8
- package/framework/rules/data-standards.md +5 -5
- package/framework/rules/dotnet-templates.md +1 -1
- package/framework/rules/frontend-standards.md +4 -2
- package/framework/rules/gsap-standards.md +10 -5
- package/framework/rules/hangfire-standards.md +3 -1
- package/framework/rules/infrastructure-standards.md +4 -2
- package/framework/rules/neon-auth-standards.md +3 -1
- package/framework/rules/neon-standards.md +6 -4
- package/framework/rules/nextjs-standards.md +13 -11
- package/framework/rules/nextjs-templates.md +1 -1
- package/framework/rules/resend-standards.md +3 -1
- package/framework/schemas/config.schema.json +14 -9
- package/framework/schemas/feature.schema.json +15 -52
- package/framework/skills/README.md +4 -5
- package/framework/skills/core/create-umbraco-lp/SKILL.md +3 -3
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +3 -3
- package/framework/skills/core/morph-apply/SKILL.md +150 -223
- package/framework/skills/core/morph-hotfix/SKILL.md +34 -43
- package/framework/skills/core/morph-machine/SKILL.md +4 -4
- package/framework/skills/core/morph-media/SKILL.md +3 -3
- package/framework/skills/core/morph-plan/SKILL.md +138 -300
- package/framework/skills/core/morph-review/SKILL.md +112 -137
- package/framework/skills/core/morph-standards/SKILL.md +29 -41
- package/framework/skills/core/morph-uiux/SKILL.md +78 -153
- package/framework/standards/STANDARDS.json +90 -1219
- package/framework/standards/STANDARDS.meta.json +1305 -0
- package/framework/standards/ai-agents/evals-with-cache.md +2 -3
- package/framework/standards/ai-agents/llm-runtime-defaults.md +1 -1
- package/framework/standards/ai-agents/modalities-speech-to-text.md +36 -26
- package/framework/standards/ai-agents/prompt-sources.md +6 -4
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +67 -39
- package/framework/standards/ai-agents/setup.md +2 -2
- package/framework/standards/ai-agents/structured-output.md +1 -1
- package/framework/standards/ai-agents/sweet-spot.md +1 -1
- package/framework/standards/architecture/code-comments.md +8 -8
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +18 -9
- package/framework/standards/backend/api/validation.md +135 -47
- package/framework/standards/backend/database/postgresql/database.md +11 -7
- package/framework/standards/backend/database/vector-search-rag.md +207 -324
- package/framework/standards/backend/dotnet/fluent-validation-vsa.md +14 -8
- package/framework/standards/backend/dotnet/result-pattern.md +40 -6
- package/framework/standards/backend/dotnet/scalar-ui.md +13 -7
- package/framework/standards/backend/dotnet/scrutor.md +1 -1
- package/framework/standards/backend/dotnet/testing.md +1 -1
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +7 -7
- package/framework/standards/backend/integrations/asaas/asaas-api.md +309 -165
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +134 -154
- package/framework/standards/data/nosql/cache/redis.md +120 -44
- package/framework/standards/data/nosql/cosmos-db.md +114 -71
- package/framework/standards/frontend/design-system/animations.md +1 -1
- package/framework/standards/frontend/design-system/naming.md +1 -1
- package/framework/standards/frontend/nextjs/motion-patterns.md +45 -29
- package/framework/standards/frontend/nextjs/naming-conventions.md +1 -1
- package/framework/standards/infrastructure/docker/coolify-deploy.md +8 -6
- package/framework/standards/infrastructure/neon/neon-pgvector.md +120 -75
- package/framework/standards/infrastructure/neon/neon-setup.md +53 -31
- package/framework/standards/integration/mcp/mcp-tools.md +7 -7
- package/framework/templates/README.md +2 -3
- package/framework/templates/REGISTRY.json +6 -170
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +2 -2
- package/framework/templates/code/dotnet/extensions/database-extensions.cs +1 -1
- package/framework/templates/code/dotnet/extensions/result-extensions.cs +27 -0
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +2 -2
- package/framework/templates/docs/plan.md +72 -0
- package/framework/templates/dotnet/ai-kit/README.md +1 -9
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +1 -1
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
- package/framework/templates/infrastructure/docker/coolify.template.json +3 -3
- package/framework/templates/infrastructure/github/README.md +2 -1
- package/framework/templates/infrastructure/github/workflows/deploy-coolify.yml.hbs +2 -1
- package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
- package/package.json +1 -1
- package/src/commands/e2e/e2e.js +2 -2
- package/src/commands/observability/cost.js +18 -60
- package/src/commands/observability/telemetry.js +3 -5
- package/src/commands/project/doctor-procs.js +1 -1
- package/src/commands/project/doctor.js +9 -9
- package/src/commands/project/fleet.js +4 -4
- package/src/commands/project/heavy.js +3 -3
- package/src/commands/project/init.js +6 -1
- package/src/commands/project/setup-infra-cmd.js +2 -1
- package/src/commands/project/stack-scan.js +58 -0
- package/src/commands/project/standards.js +79 -25
- package/src/commands/project/status.js +29 -69
- package/src/commands/project/update.js +33 -19
- package/src/commands/project/worktree.js +1 -1
- package/src/commands/state/advance.js +62 -46
- package/src/commands/state/approve.js +95 -138
- package/src/commands/state/archive.js +20 -84
- package/src/commands/state/auto-approval.js +68 -0
- package/src/commands/state/create.js +28 -1
- package/src/commands/state/delete.js +1 -1
- package/src/commands/state/finish.js +11 -23
- package/src/commands/state/gate-check.js +86 -95
- package/src/commands/state/gate-decision.js +6 -4
- package/src/commands/state/handoff.js +1 -1
- package/src/commands/state/list.js +9 -9
- package/src/commands/state/report-check.js +157 -311
- package/src/commands/state/retype.js +9 -3
- package/src/commands/validation/mutate.js +77 -14
- package/src/commands/validation/plan-check.js +81 -0
- package/src/commands/validation/validate-feature.js +60 -135
- package/src/commands/validation/verify.js +41 -82
- package/src/core/paths/output-schema.js +20 -67
- package/src/core/state/gate-order.js +5 -6
- package/src/core/state/lifecycle-audit.js +2 -2
- package/src/core/state/state-manager.js +17 -98
- package/src/core/templates/template-registry.js +2 -2
- package/src/core/templates/template-seed.js +22 -0
- package/src/core/work-types.js +10 -10
- package/src/dashboard/app/App.jsx +0 -2
- package/src/dashboard/app/components/ActivityFeed.jsx +2 -3
- package/src/dashboard/app/components/CommandPalette.jsx +7 -8
- package/src/dashboard/app/components/EntityLink.jsx +1 -2
- package/src/dashboard/app/components/Markdown.jsx +1 -1
- package/src/dashboard/app/components/Sidebar.jsx +1 -2
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -1
- package/src/dashboard/app/lib/phases.js +2 -4
- package/src/dashboard/app/pages/Activity.jsx +1 -1
- package/src/dashboard/app/pages/FeatureDetail.jsx +139 -129
- package/src/dashboard/app/pages/Features.jsx +9 -9
- package/src/dashboard/app/pages/Guide.jsx +2 -2
- package/src/dashboard/app/pages/PhasePipeline.jsx +3 -4
- package/src/dashboard/app/pages/RulesMap.jsx +3 -3
- package/src/dashboard/app/pages/SkillsLibrary.jsx +3 -3
- package/src/dashboard/app/pages/Standards.jsx +1 -1
- package/src/dashboard/scanner/activity-scanner.js +6 -16
- package/src/dashboard/scanner/agents-scanner.js +1 -1
- package/src/dashboard/scanner/docs-scanner.js +5 -23
- package/src/dashboard/scanner/features-scanner.js +95 -60
- package/src/dashboard/scanner/index.js +0 -2
- package/src/dashboard/scanner/project-api.js +1 -1
- package/src/dashboard/scanner/rules-scanner.js +15 -10
- package/src/dashboard/scanner/skills-scanner.js +2 -3
- package/src/dashboard/scanner/standards-scanner.js +2 -1
- package/src/lib/ci/ci-verify.js +17 -1
- package/src/lib/ci/github-ci.js +1 -1
- package/src/lib/detectors/index.js +3 -8
- package/src/lib/generators/handoff-generator.js +34 -65
- package/src/lib/generators/settings-generator.js +11 -89
- package/src/lib/git/evidence-staleness.js +1 -1
- package/src/lib/git/tree-fingerprint.js +19 -7
- package/src/lib/git/tree-state.js +2 -2
- package/src/lib/heavy/cleanup.js +1 -5
- package/src/lib/installers/memory-installer.js +6 -8
- package/src/lib/mutation/commands.js +3 -2
- package/src/lib/{tasks/dotnet-test-introspection.js → mutation/dotnet-introspection.js} +17 -282
- package/src/lib/mutation/plan.js +14 -6
- package/src/lib/mutation/runner.js +1 -1
- package/src/lib/mutation/subset.js +46 -9
- package/src/lib/plan/artifact-vocabulary.js +60 -0
- package/src/lib/plan/checks.js +234 -0
- package/src/lib/plan/plan-parser.js +209 -0
- package/src/lib/plan/plan-template.js +18 -0
- package/src/lib/review-reports.js +222 -0
- package/src/lib/risk-detector.js +134 -135
- package/src/lib/specs/adr-index.js +50 -69
- package/src/lib/specs/design-seed.js +18 -0
- package/src/lib/specs/frontmatter.js +44 -0
- package/src/lib/specs/gfm-table.js +17 -0
- package/src/lib/specs/old-format.js +68 -0
- package/src/lib/specs/revocations.js +37 -95
- package/src/lib/specs/screen-checklist.js +18 -17
- package/src/lib/stack/dotnet-build-ignores.js +47 -0
- package/src/lib/stack/stack-profile.js +1 -1
- package/src/lib/stack/stack-scan.js +267 -0
- package/src/lib/standards/registry.js +43 -0
- package/src/lib/standards/sections.js +58 -0
- package/src/lib/tasks/eval-node.js +4 -12
- package/src/lib/tasks/test-nodes.js +1 -74
- package/src/lib/tasks/test-runner.js +75 -117
- package/src/lib/telemetry/metrics.js +8 -69
- package/src/lib/telemetry/telemetry-reader.js +5 -118
- package/src/lib/telemetry/verify-event.js +5 -7
- package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
- package/src/lib/validators/contracts/contract-compliance-validator.js +2 -2
- package/src/lib/validators/reuse/reuse-validator.js +24 -48
- package/src/lib/validators/reuse/symbol-scanner.js +2 -4
- package/src/lib/validators/traceability-check.js +85 -129
- package/src/lib/validators/validation-runner.js +8 -88
- package/src/lib/validators/verify-runner.js +134 -301
- package/src/lib/worktree/docker-audit.js +1 -2
- package/src/lib/worktree/fleet-scanner.js +25 -38
- package/src/scripts/setup-infra.js +42 -22
- package/src/utils/agents-installer.js +92 -13
- package/src/utils/claude-settings-manager.js +0 -35
- package/src/utils/file-copier.js +34 -6
- package/src/utils/hooks-installer.js +16 -9
- package/src/utils/skills-installer.js +1 -2
- package/framework/commands/commit.md +0 -166
- package/framework/evals/trace-schema.md +0 -67
- package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
- package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
- package/framework/hooks/git/pre-commit/specs.sh +0 -50
- package/framework/hooks/shared/tasks-validator.js +0 -111
- package/framework/rubrics/backend.json +0 -41
- package/framework/rubrics/default.json +0 -36
- package/framework/rubrics/frontend.json +0 -36
- package/framework/rules/morph-task-tracking.md +0 -54
- package/framework/rules/morph-workflow.md +0 -244
- package/framework/schemas/tasks.schema.json +0 -79
- package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
- package/framework/skills/core/morph-implement/SKILL.md +0 -194
- package/framework/skills/core/morph-proposal/SKILL.md +0 -152
- package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
- package/framework/templates/docs/proposal.md +0 -192
- package/framework/templates/docs/spec.md +0 -57
- package/framework/templates/docs/ui-components.md +0 -52
- package/framework/templates/docs/ui-design-system.md +0 -76
- package/framework/templates/docs/ui-flows.md +0 -167
- package/framework/templates/examples/spec-examples.md +0 -91
- package/framework/templates/feature/decisions.md +0 -124
- package/framework/templates/feature/recap.md +0 -145
- package/framework/templates/project/validate.js +0 -97
- package/framework/templates/tasks.template.json +0 -17
- package/src/commands/state/usecase.js +0 -143
- package/src/commands/tasks/dag.js +0 -181
- package/src/dashboard/app/pages/Rubrics.jsx +0 -97
- package/src/dashboard/scanner/rubrics-scanner.js +0 -12
- package/src/lib/agents/output-collector.js +0 -76
- package/src/lib/evaluator-report.js +0 -91
- package/src/lib/specs/acervo-format.js +0 -70
- package/src/lib/specs/acervo-lifecycle.js +0 -264
- package/src/lib/specs/done-criteria-check.js +0 -200
- package/src/lib/specs/usecase-check.js +0 -301
- package/src/lib/specs/usecase-promoter.js +0 -301
- package/src/lib/specs/usecase-registry.js +0 -237
- package/src/lib/tasks/dispatch-planner.js +0 -319
- package/src/lib/tasks/mermaid-renderer.js +0 -132
- package/src/lib/tasks/task-classifier.js +0 -30
- package/src/lib/tasks/task-parser.js +0 -209
- package/src/lib/tasks/tasks-json-parser.js +0 -296
- package/src/lib/tasks/test-scope.js +0 -597
- package/src/lib/validators/architecture/architecture-validator.js +0 -270
- package/src/lib/validators/architecture/index.js +0 -7
- package/src/lib/validators/css/css-validator.js +0 -340
- package/src/lib/validators/css/index.js +0 -7
- package/src/lib/validators/design-system/design-system-validator.js +0 -237
- package/src/lib/validators/design-system/index.js +0 -7
- package/src/lib/validators/score-aggregator.js +0 -244
- package/src/lib/validators/ui/index.js +0 -7
- package/src/lib/validators/ui/ui-contrast-validator.js +0 -422
|
@@ -9,13 +9,13 @@ export async function validateContracts(projectPath, featureName, options = {})
|
|
|
9
9
|
|
|
10
10
|
const skipped = (message) => ({ status: 'ok', errors: 0, warnings: 0, issues: [], coverage: null, message });
|
|
11
11
|
|
|
12
|
-
if (!existsSync(contractsPath)) return skipped('No
|
|
12
|
+
if (!existsSync(contractsPath)) return skipped('No plan.md found — skipping');
|
|
13
13
|
|
|
14
14
|
const contractsContent = readFileSync(contractsPath, 'utf-8');
|
|
15
15
|
const contracts = parseContracts(contractsContent);
|
|
16
16
|
|
|
17
17
|
if (contracts.interfaces.length === 0 && contracts.records.length === 0 && contracts.enums.length === 0) {
|
|
18
|
-
return skipped('
|
|
18
|
+
return skipped('plan.md declares no interfaces/records/enums — skipping');
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
// Find all .cs files in the project (excluding .morph outputs, bin, obj)
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Reuse Validator — símbolo novo cujo nome já existe no codebase.
|
|
3
3
|
*
|
|
4
|
-
* A terceira camada do ciclo de reuso
|
|
4
|
+
* A terceira camada do ciclo de reuso:
|
|
5
5
|
*
|
|
6
|
-
* PROSA (morph-plan, personas) ──► ARTEFATO (
|
|
6
|
+
* PROSA (morph-plan, personas) ──► ARTEFATO (seção de reuso do plano)
|
|
7
7
|
* ──► COBRANÇA (este arquivo)
|
|
8
8
|
*
|
|
9
|
-
* Prosa sozinha não segura —
|
|
10
|
-
*
|
|
11
|
-
* MESMA pasta assim mesmo
|
|
12
|
-
* tokens) tem verificação em código; este é o equivalente para código.
|
|
9
|
+
* Prosa sozinha não segura — a skill de planejamento já mandava olhar o que
|
|
10
|
+
* existe, e o ProspectPRO redefiniu `SimpleBuilding` em cinco arquivos da
|
|
11
|
+
* MESMA pasta assim mesmo: só a verificação em código segura o ciclo.
|
|
13
12
|
*
|
|
14
13
|
* Regras, na ordem em que reduzem ruído:
|
|
15
14
|
*
|
|
@@ -20,9 +19,9 @@
|
|
|
20
19
|
* não se resolve por aviso, e fica FORA daqui de propósito.
|
|
21
20
|
* 3. Mesma feature = achado (`error`); feature diferente = aviso
|
|
22
21
|
* (`warning`), porque VSA prescreve DTO homônimo entre slices.
|
|
23
|
-
* 4. Veredito `CRIAR` declarado
|
|
24
|
-
* achado a aviso: a decisão foi tomada no
|
|
25
|
-
* `why`. O validador cobra a decisão AUSENTE, não a decisão tomada.
|
|
22
|
+
* 4. Veredito `CRIAR` declarado na tabela de reuso do plano rebaixa o
|
|
23
|
+
* achado a aviso: a decisão foi tomada no gate do plano, com justificativa
|
|
24
|
+
* em `why`. O validador cobra a decisão AUSENTE, não a decisão tomada.
|
|
26
25
|
*
|
|
27
26
|
* Sem grafo o validador não roda (ADR-002: ausência de grafo nunca bloqueia).
|
|
28
27
|
*
|
|
@@ -35,13 +34,13 @@ import { join, isAbsolute } from 'path';
|
|
|
35
34
|
|
|
36
35
|
import { loadGraph } from '../../graph/graph-loader.js';
|
|
37
36
|
import { languageOf } from '../../graph/sanitize.js';
|
|
38
|
-
import { getAbsoluteOutputPath } from '../../../core/paths/output-schema.js';
|
|
39
37
|
import {
|
|
40
38
|
featureOf,
|
|
41
39
|
isIdiomaticLocalName,
|
|
42
40
|
isScannableFile,
|
|
43
41
|
scanDeclarations,
|
|
44
42
|
} from './symbol-scanner.js';
|
|
43
|
+
import { readPlanChecks } from '../../plan/checks.js';
|
|
45
44
|
|
|
46
45
|
/** Quantos locais citar por achado antes de resumir com "(+N)". */
|
|
47
46
|
const MAX_CITED = 4;
|
|
@@ -155,7 +154,7 @@ export function findDuplicateSymbols(declarations, existing, opts = {}) {
|
|
|
155
154
|
? `Reuse ou estenda o tipo existente em vez de redeclarar. Confirme com ` +
|
|
156
155
|
`\`morph-spec graph explain ${decls[0].name}\` (sem grafo: ` +
|
|
157
156
|
`\`grep -rn "${decls[0].name}"\`) e, se a criação for mesmo necessária, ` +
|
|
158
|
-
'registre o veredito CRIAR com a justificativa em "why"
|
|
157
|
+
'registre o veredito CRIAR com a justificativa em "why" na tabela ## Reuso do plan.md.'
|
|
159
158
|
: `VSA tolera DTO homônimo entre slices — confirme que são conceitos ` +
|
|
160
159
|
`diferentes e não a mesma coisa escrita duas vezes.`,
|
|
161
160
|
});
|
|
@@ -168,9 +167,7 @@ export function findDuplicateSymbols(declarations, existing, opts = {}) {
|
|
|
168
167
|
}
|
|
169
168
|
|
|
170
169
|
/**
|
|
171
|
-
* Vereditos de reuso
|
|
172
|
-
*
|
|
173
|
-
* O veredito mora só no `tasks.json`: dado estruturado, lido aqui e no Gate 2.
|
|
170
|
+
* Vereditos de reuso do plano: a tabela `## Reuso` que o plan-check extraiu.
|
|
174
171
|
*
|
|
175
172
|
* @param {string} projectPath
|
|
176
173
|
* @param {string} featureName
|
|
@@ -179,24 +176,13 @@ export function findDuplicateSymbols(declarations, existing, opts = {}) {
|
|
|
179
176
|
export function readReuseVerdicts(projectPath, featureName) {
|
|
180
177
|
const verdicts = new Map();
|
|
181
178
|
if (!featureName) return verdicts;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (!entry?.concept) continue;
|
|
190
|
-
verdicts.set(String(entry.concept).toLowerCase(), {
|
|
191
|
-
verdict: String(entry.verdict || '').toUpperCase(),
|
|
192
|
-
target: entry.target,
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
} catch {
|
|
197
|
-
// Plano ilegível não é problema deste validador — o tasks-json-guard já
|
|
198
|
-
// cobra o schema, e aqui a ausência de veredito só torna a cobrança mais
|
|
199
|
-
// rígida, nunca mais frouxa.
|
|
179
|
+
// Sem extração, nenhum veredito: a cobrança só fica mais rígida, nunca mais frouxa.
|
|
180
|
+
for (const entry of readPlanChecks(projectPath, featureName).checks?.reuse ?? []) {
|
|
181
|
+
if (!entry?.concept) continue;
|
|
182
|
+
verdicts.set(String(entry.concept).toLowerCase(), {
|
|
183
|
+
verdict: String(entry.verdict || '').toUpperCase(),
|
|
184
|
+
target: entry.target,
|
|
185
|
+
});
|
|
200
186
|
}
|
|
201
187
|
return verdicts;
|
|
202
188
|
}
|
|
@@ -205,9 +191,8 @@ export function readReuseVerdicts(projectPath, featureName) {
|
|
|
205
191
|
* Arquivos que esta feature criou ou alterou.
|
|
206
192
|
*
|
|
207
193
|
* Duas fontes, unidas, porque cada uma é cega no momento em que a outra
|
|
208
|
-
* enxerga: `
|
|
209
|
-
*
|
|
210
|
-
* pega o que ainda não foi registrado numa task. Sem git, sem drama.
|
|
194
|
+
* enxerga: os `arquivos:` das fatias são o contrato do plano e sobrevivem ao
|
|
195
|
+
* commit; `git status` pega o que o plano não previu. Sem git, sem drama.
|
|
211
196
|
*
|
|
212
197
|
* @param {string} projectPath
|
|
213
198
|
* @param {string} featureName
|
|
@@ -215,19 +200,10 @@ export function readReuseVerdicts(projectPath, featureName) {
|
|
|
215
200
|
*/
|
|
216
201
|
export function collectChangedFiles(projectPath, featureName) {
|
|
217
202
|
const files = new Set();
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (existsSync(tasksPath)) {
|
|
222
|
-
const doc = JSON.parse(readFileSync(tasksPath, 'utf8').replace(/^/, ''));
|
|
223
|
-
for (const task of doc.tasks ?? []) {
|
|
224
|
-
for (const out of task.outputs ?? []) {
|
|
225
|
-
if (typeof out === 'string') files.add(out.replace(/\\/g, '/'));
|
|
226
|
-
}
|
|
227
|
-
}
|
|
203
|
+
for (const slice of readPlanChecks(projectPath, featureName).checks?.slices ?? []) {
|
|
204
|
+
for (const file of slice.files ?? []) {
|
|
205
|
+
if (typeof file === 'string') files.add(file.replace(/\\/g, '/'));
|
|
228
206
|
}
|
|
229
|
-
} catch {
|
|
230
|
-
// tasks.json ausente/ilegível: sobra o git.
|
|
231
207
|
}
|
|
232
208
|
|
|
233
209
|
try {
|
|
@@ -248,7 +224,7 @@ export function collectChangedFiles(projectPath, featureName) {
|
|
|
248
224
|
if (path) files.add(path.replace(/\\/g, '/'));
|
|
249
225
|
}
|
|
250
226
|
} catch {
|
|
251
|
-
// Fora de um repo git, ou git indisponível: o
|
|
227
|
+
// Fora de um repo git, ou git indisponível: o plano já respondeu.
|
|
252
228
|
}
|
|
253
229
|
|
|
254
230
|
return [...files];
|
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
* papéis produziria um validador cego justamente na janela em que ele precisa
|
|
10
10
|
* morder — ver graph-loader.js `getFreshness`.
|
|
11
11
|
*
|
|
12
|
-
* A varredura é por linha, com regex ancorada no início da declaração
|
|
13
|
-
* limitação (e mesmo grau de tolerância) do design-system-validator: uma
|
|
12
|
+
* A varredura é por linha, com regex ancorada no início da declaração: uma
|
|
14
13
|
* declaração citada dentro de uma string ou de um comentário de bloco pode
|
|
15
14
|
* passar. O custo de errar é um aviso a mais, nunca um bloqueio silencioso.
|
|
16
15
|
*
|
|
@@ -28,8 +27,7 @@ import { isProjectSource } from '../../../../framework/hooks/shared/source-files
|
|
|
28
27
|
*
|
|
29
28
|
* A lista é curta de propósito e só admite nome genérico de UMA palavra: assim
|
|
30
29
|
* que há qualificador de domínio (`BuildingFiltersProps`, `FilterOption`) o
|
|
31
|
-
* nome volta a ser um conceito e a regra vale.
|
|
32
|
-
* cores de sistema no design-system-validator.
|
|
30
|
+
* nome volta a ser um conceito e a regra vale.
|
|
33
31
|
*/
|
|
34
32
|
const IDIOMATIC_LOCAL_NAMES = new Set([
|
|
35
33
|
'props', 'state', 'row', 'item', 'column', 'context', 'params', 'options',
|
|
@@ -1,89 +1,69 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* traceability-check.js — advisory, feature-scope-only check: do the files
|
|
3
|
-
* cited
|
|
4
|
-
*
|
|
3
|
+
* cited under `**Arquivos:**` in the slice reports (`3-implement/reports/F{n}.md`)
|
|
4
|
+
* belong to some slice of the plan (its `arquivos:`, as extracted by plan-check)?
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* recap.md — the real-world case that motivated this module, see the finding
|
|
11
|
-
* below). This module cannot catch THAT class of drift — a file absent from
|
|
12
|
-
* both documents is invisible to a check that only compares the two documents
|
|
13
|
-
* against each other. What it DOES catch is a narrower, complementary class:
|
|
14
|
-
* recap.md and tasks.json DISAGREEING — an author cited a file in the recap's
|
|
15
|
-
* prose but forgot (or a later task re-touched it without) declaring it in any
|
|
16
|
-
* task's `outputs`. That disagreement is real signal even though it is not the
|
|
17
|
-
* same defect the SKILL.md rule targets.
|
|
6
|
+
* It catches the reports and the plan DISAGREEING: a file the implementation
|
|
7
|
+
* says it touched that no slice declared. A file absent from both documents
|
|
8
|
+
* is invisible to it by construction — that drift is the reuse/contract
|
|
9
|
+
* reviewer's job.
|
|
18
10
|
*
|
|
19
|
-
*
|
|
20
|
-
* `
|
|
21
|
-
*
|
|
22
|
-
* construction). Separately, `src/VetClinic.Api/appsettings.json` and
|
|
23
|
-
* `Properties/launchSettings.json` WERE cited in T1's recap `**Files:**` list
|
|
24
|
-
* but never appear in any task's `outputs` — exactly the disagreement this
|
|
25
|
-
* module flags.
|
|
11
|
+
* Advisory, never blocking: `status` is only ever 'pass' | 'warn' | 'skip'.
|
|
12
|
+
* `runVerify()` keeps this result OUTSIDE `nodes` so it can never flip the
|
|
13
|
+
* overall build/tests/validators/e2e status.
|
|
26
14
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* purpose so it can never flip the overall build/tests/validators/e2e status
|
|
30
|
-
* computed from `nodes` alone (see verify-runner.js).
|
|
31
|
-
*
|
|
32
|
-
* Fail-open throughout: no recap.md, no tasks.json, or a parse error all
|
|
33
|
-
* degrade to `{ status: 'skip' }` rather than throwing or warning about the
|
|
34
|
-
* absence itself — this is rastreabilidade tooling, not a completeness gate.
|
|
15
|
+
* Fail-open throughout: no slice report, no extracted plan or a parse error
|
|
16
|
+
* all degrade to `{ status: 'skip' }`.
|
|
35
17
|
*
|
|
36
18
|
* @module traceability-check
|
|
37
19
|
*/
|
|
38
20
|
|
|
39
21
|
import { existsSync, readFileSync } from 'fs';
|
|
40
|
-
import {
|
|
22
|
+
import { join } from 'path';
|
|
23
|
+
import { readPlanChecks } from '../plan/checks.js';
|
|
24
|
+
import { sliceProgress, sliceReportPath } from '../../../framework/hooks/shared/slice-progress.js';
|
|
41
25
|
|
|
42
|
-
/**
|
|
43
|
-
|
|
26
|
+
/**
|
|
27
|
+
* An `**Arquivos:**` marker and what follows it: the rest of its own line
|
|
28
|
+
* (inline list) plus any `- ` bullet lines right below.
|
|
29
|
+
*/
|
|
30
|
+
const FILES_BLOCK_RE = /\*\*Arquivos:\*\*([^\r\n]*)\r?\n?((?:^[ \t]*-[ \t].*\r?\n?)*)/gm;
|
|
44
31
|
|
|
45
|
-
/** Backtick-quoted spans within a
|
|
32
|
+
/** Backtick-quoted spans within a line. */
|
|
46
33
|
const BACKTICK_RE = /`([^`]+)`/g;
|
|
47
34
|
|
|
48
35
|
/**
|
|
49
|
-
* Extract the set of file paths cited under every
|
|
50
|
-
*
|
|
51
|
-
*
|
|
36
|
+
* Extract the set of file paths cited under every `**Arquivos:**` marker of a
|
|
37
|
+
* slice report, applying a conservative decomposition of the shorthand real
|
|
38
|
+
* reports use:
|
|
52
39
|
*
|
|
53
40
|
* - A line with exactly one backtick span: that span is a candidate,
|
|
54
|
-
* whether or not it contains a
|
|
55
|
-
*
|
|
56
|
-
* - A line with multiple comma-separated backtick spans (e.g.
|
|
57
|
-
* `` `a/b.json`, `c.json`, `d/e.json` ``): only the FIRST span and any
|
|
41
|
+
* whether or not it contains a `/`.
|
|
42
|
+
* - A line with several comma-separated spans: only the FIRST span and any
|
|
58
43
|
* LATER span that itself contains `/` are candidates. A later span with
|
|
59
|
-
* no `/` is ambiguous shorthand
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* - Any span containing `{`/`}` (brace-expansion shorthand, e.g.
|
|
64
|
-
* `` `Foo/{A,B,C}.cs` ``) is skipped entirely — decomposing it correctly
|
|
65
|
-
* would require guessing at delimiters this format doesn't standardize.
|
|
44
|
+
* no `/` is ambiguous shorthand and is skipped rather than guessed —
|
|
45
|
+
* false negatives are acceptable for advisory tooling, false positives
|
|
46
|
+
* are not.
|
|
47
|
+
* - Any span containing `{`/`}` (brace expansion) is skipped.
|
|
66
48
|
* - Any span ending in `/` is a directory reference, not a file — skipped.
|
|
67
49
|
*
|
|
68
|
-
* @param {string}
|
|
50
|
+
* @param {string} reportText
|
|
69
51
|
* @returns {string[]} de-duplicated, normalized (forward-slash) candidate paths
|
|
70
52
|
*/
|
|
71
|
-
export function extractCitedFiles(
|
|
53
|
+
export function extractCitedFiles(reportText) {
|
|
72
54
|
const cited = new Set();
|
|
73
|
-
if (!
|
|
55
|
+
if (!reportText) return [];
|
|
74
56
|
|
|
75
57
|
FILES_BLOCK_RE.lastIndex = 0;
|
|
76
58
|
let blockMatch;
|
|
77
|
-
while ((blockMatch = FILES_BLOCK_RE.exec(
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
for (const line of lines) {
|
|
59
|
+
while ((blockMatch = FILES_BLOCK_RE.exec(reportText)) !== null) {
|
|
60
|
+
const bullets = blockMatch[2].split(/\r?\n/).filter((l) => /^[ \t]*-[ \t]/.test(l));
|
|
61
|
+
for (const line of [blockMatch[1], ...bullets]) {
|
|
82
62
|
const spans = [...line.matchAll(BACKTICK_RE)].map((m) => m[1].trim()).filter(Boolean);
|
|
83
63
|
spans.forEach((span, idx) => {
|
|
84
|
-
if (idx > 0 && !span.includes('/')) return;
|
|
85
|
-
if (span.includes('{') || span.includes('}')) return;
|
|
86
|
-
if (span.endsWith('/')) return;
|
|
64
|
+
if (idx > 0 && !span.includes('/')) return;
|
|
65
|
+
if (span.includes('{') || span.includes('}')) return;
|
|
66
|
+
if (span.endsWith('/')) return;
|
|
87
67
|
cited.add(normalizePath(span));
|
|
88
68
|
});
|
|
89
69
|
}
|
|
@@ -92,114 +72,90 @@ export function extractCitedFiles(recapText) {
|
|
|
92
72
|
return [...cited];
|
|
93
73
|
}
|
|
94
74
|
|
|
95
|
-
/**
|
|
96
|
-
* Backslashes to forward slashes, strip a leading `./`.
|
|
97
|
-
*
|
|
98
|
-
* Exported because `src/lib/tasks/test-scope.js` normalises the SAME thing —
|
|
99
|
-
* a task's `outputs` entry — before deciding which tests a task-scoped verify
|
|
100
|
-
* runs. One normaliser for `outputs`, so the traceability check and the test
|
|
101
|
-
* recorte never disagree about which file a task delivered.
|
|
102
|
-
*/
|
|
75
|
+
/** Backslashes to forward slashes, strip a leading `./`. */
|
|
103
76
|
export function normalizePath(p) {
|
|
104
77
|
return String(p).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
105
78
|
}
|
|
106
79
|
|
|
107
80
|
/**
|
|
108
|
-
* Union of `
|
|
81
|
+
* Union of the `arquivos:` of every slice of the plan, normalized.
|
|
109
82
|
*
|
|
110
|
-
* @
|
|
111
|
-
* @param {string} featureName
|
|
112
|
-
* @returns {string[]|null} null when tasks.json is missing/unreadable/invalid
|
|
113
|
-
* (the caller treats null as "cannot judge coverage" and skips, not warns)
|
|
83
|
+
* @returns {string[]|null} null when the plan has no readable extraction
|
|
114
84
|
*/
|
|
115
|
-
export function
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
if (!existsSync(tasksPath)) return null;
|
|
123
|
-
|
|
124
|
-
let data;
|
|
125
|
-
try {
|
|
126
|
-
data = JSON.parse(readFileSync(tasksPath, 'utf8'));
|
|
127
|
-
} catch {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const tasks = Array.isArray(data?.tasks) ? data.tasks : [];
|
|
132
|
-
const outputs = [];
|
|
133
|
-
for (const task of tasks) {
|
|
134
|
-
if (task?.status !== 'done') continue;
|
|
135
|
-
for (const output of task.outputs || []) {
|
|
136
|
-
if (typeof output === 'string' && output.trim()) outputs.push(normalizePath(output));
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return outputs;
|
|
85
|
+
export function collectSliceFiles(projectPath, featureName) {
|
|
86
|
+
const checks = readPlanChecks(projectPath, featureName).checks;
|
|
87
|
+
if (!checks) return null;
|
|
88
|
+
return checks.slices.flatMap((slice) => (slice.files ?? [])
|
|
89
|
+
.filter((f) => typeof f === 'string' && f.trim())
|
|
90
|
+
.map(normalizePath));
|
|
140
91
|
}
|
|
141
92
|
|
|
142
93
|
/**
|
|
143
|
-
* A cited file is covered if some
|
|
144
|
-
*
|
|
94
|
+
* A cited file is covered if some slice names it exactly, or names a
|
|
95
|
+
* directory (an entry ending in `/`) that the cited file falls under.
|
|
145
96
|
*
|
|
146
97
|
* @param {string} file
|
|
147
|
-
* @param {string[]}
|
|
98
|
+
* @param {string[]} declared
|
|
148
99
|
* @returns {boolean}
|
|
149
100
|
*/
|
|
150
|
-
function isCovered(file,
|
|
151
|
-
for (const
|
|
152
|
-
if (
|
|
153
|
-
if (
|
|
101
|
+
function isCovered(file, declared) {
|
|
102
|
+
for (const entry of declared) {
|
|
103
|
+
if (entry === file) return true;
|
|
104
|
+
if (entry.endsWith('/') && file.startsWith(entry)) return true;
|
|
154
105
|
}
|
|
155
106
|
return false;
|
|
156
107
|
}
|
|
157
108
|
|
|
109
|
+
function readOrNull(path) {
|
|
110
|
+
try {
|
|
111
|
+
return existsSync(path) ? readFileSync(path, 'utf8') : null;
|
|
112
|
+
} catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
158
117
|
/**
|
|
159
|
-
* Run the
|
|
118
|
+
* Run the reports-vs-slices traceability check for a feature.
|
|
160
119
|
*
|
|
161
120
|
* @param {string} projectPath
|
|
162
121
|
* @param {string} featureName
|
|
163
122
|
* @returns {{ status: 'pass'|'warn'|'skip', warnings: string[], reason: string|null, citedCount: number }}
|
|
164
123
|
*/
|
|
165
124
|
export function checkOutputsTraceability(projectPath, featureName) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (!existsSync(recapPath)) {
|
|
174
|
-
return { status: 'skip', warnings: [], reason: 'no recap.md', citedCount: 0 };
|
|
125
|
+
const featureDir = join(projectPath, '.morph', 'features', featureName);
|
|
126
|
+
const progress = sliceProgress(featureDir);
|
|
127
|
+
const reports = (progress?.slices ?? [])
|
|
128
|
+
.map((slice) => ({ id: slice.id, text: readOrNull(sliceReportPath(featureDir, slice.id)) }))
|
|
129
|
+
.filter((r) => r.text !== null);
|
|
130
|
+
if (reports.length === 0) {
|
|
131
|
+
return { status: 'skip', warnings: [], reason: 'no slice reports', citedCount: 0 };
|
|
175
132
|
}
|
|
176
133
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
134
|
+
const citedBy = new Map();
|
|
135
|
+
for (const { id, text } of reports) {
|
|
136
|
+
for (const file of extractCitedFiles(text)) {
|
|
137
|
+
if (!citedBy.has(file)) citedBy.set(file, []);
|
|
138
|
+
citedBy.get(file).push(id);
|
|
139
|
+
}
|
|
182
140
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (cited.length === 0) {
|
|
186
|
-
return { status: 'skip', warnings: [], reason: 'no Files: entries found in recap.md', citedCount: 0 };
|
|
141
|
+
if (citedBy.size === 0) {
|
|
142
|
+
return { status: 'skip', warnings: [], reason: 'no Arquivos: entries found in the slice reports', citedCount: 0 };
|
|
187
143
|
}
|
|
188
144
|
|
|
189
|
-
const
|
|
190
|
-
if (
|
|
191
|
-
return { status: 'skip', warnings: [], reason: 'no
|
|
145
|
+
const declared = collectSliceFiles(projectPath, featureName);
|
|
146
|
+
if (declared === null) {
|
|
147
|
+
return { status: 'skip', warnings: [], reason: 'no checks.json', citedCount: citedBy.size };
|
|
192
148
|
}
|
|
193
149
|
|
|
194
|
-
const warnings =
|
|
195
|
-
.filter((file) => !isCovered(file,
|
|
150
|
+
const warnings = [...citedBy.keys()]
|
|
151
|
+
.filter((file) => !isCovered(file, declared))
|
|
196
152
|
.sort()
|
|
197
|
-
.map((file) => `${file} — cited in
|
|
153
|
+
.map((file) => `${file} — cited in reports/${citedBy.get(file).join(', ')} but declared in no slice's arquivos:`);
|
|
198
154
|
|
|
199
155
|
return {
|
|
200
156
|
status: warnings.length > 0 ? 'warn' : 'pass',
|
|
201
157
|
warnings,
|
|
202
158
|
reason: null,
|
|
203
|
-
citedCount:
|
|
159
|
+
citedCount: citedBy.size,
|
|
204
160
|
};
|
|
205
161
|
}
|
|
@@ -7,24 +7,14 @@
|
|
|
7
7
|
* MORPH-SPEC 3.0 - Phase A: Active Validation Pipeline
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { existsSync
|
|
11
|
-
import { join } from 'path';
|
|
10
|
+
import { existsSync } from 'fs';
|
|
12
11
|
import chalk from 'chalk';
|
|
13
12
|
import { getAbsoluteOutputPath, resolveContractsOutputType } from '../../core/paths/output-schema.js';
|
|
14
13
|
import { getStackProfile } from '../stack/stack-profile.js';
|
|
15
|
-
import { aggregateScores, loadRubric } from './score-aggregator.js';
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
* Heavy validators skipped in 'light' mode (checkpoint-only).
|
|
19
|
-
*
|
|
20
|
-
* What light mode actually leaves behind is whatever the stack contributes that
|
|
21
|
-
* is NOT in this set — in practice `contrast` and `nextjs-component`. There are
|
|
22
|
-
* no `spec` or `content` validators; the switch in runSingleValidator has no
|
|
23
|
-
* case for either, and the old comment here promised both.
|
|
24
|
-
*/
|
|
15
|
+
// Skipped in 'light' mode (checkpoint-only); light keeps `nextjs-component`.
|
|
25
16
|
const HEAVY_VALIDATORS = new Set([
|
|
26
|
-
'
|
|
27
|
-
'css', 'design-system', 'reuse', 'code-budget'
|
|
17
|
+
'packages', 'contract-compliance', 'reuse', 'code-budget'
|
|
28
18
|
]);
|
|
29
19
|
|
|
30
20
|
/**
|
|
@@ -167,17 +157,12 @@ export async function runValidation(projectPath, featureName, options = {}) {
|
|
|
167
157
|
result.issues.push(...issues);
|
|
168
158
|
|
|
169
159
|
// A validator that skipped itself checked nothing: in `results` it would
|
|
170
|
-
//
|
|
160
|
+
// read as a validator that ran clean.
|
|
171
161
|
if (!validatorResult._skipReason) result.results[validatorId] = { passed: errors === 0, issues };
|
|
172
162
|
}
|
|
173
163
|
} catch (err) {
|
|
174
|
-
// A validator that throws is a FAILED validation, not a silent skip
|
|
175
|
-
//
|
|
176
|
-
// `result.errors` never moved, so `result.passed = errors === 0` stayed
|
|
177
|
-
// true and the run reported "✅ All validations passed". The only trace
|
|
178
|
-
// was a console line, and `verify` silences stdout. That is how a broken
|
|
179
|
-
// import (`css`) and a path bug (`packages` under a different cwd) went
|
|
180
|
-
// unnoticed: both threw, both read as clean.
|
|
164
|
+
// A validator that throws is a FAILED validation, not a silent skip:
|
|
165
|
+
// `verify` silences stdout, so a console line alone would read as clean.
|
|
181
166
|
if (options.verbose) {
|
|
182
167
|
console.log(chalk.yellow(` ⚠ Validator '${validatorId}' failed: ${err.message}`));
|
|
183
168
|
}
|
|
@@ -208,17 +193,6 @@ export async function runValidation(projectPath, featureName, options = {}) {
|
|
|
208
193
|
? `✅ All validations passed (${result.warnings} warnings)`
|
|
209
194
|
: `❌ ${result.errors} error(s), ${result.warnings} warning(s)`;
|
|
210
195
|
|
|
211
|
-
// Score aggregation — layered on top of pass/fail
|
|
212
|
-
try {
|
|
213
|
-
const rubricId = readRubricConfig(projectPath);
|
|
214
|
-
const rubric = loadRubric(projectPath, rubricId);
|
|
215
|
-
const thresholdOverrides = readThresholdConfig(projectPath);
|
|
216
|
-
result.scores = aggregateScores(result, rubric, thresholdOverrides);
|
|
217
|
-
} catch {
|
|
218
|
-
// Score aggregation is non-blocking — if it fails, pass/fail still works
|
|
219
|
-
result.scores = null;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
196
|
return result;
|
|
223
197
|
}
|
|
224
198
|
|
|
@@ -243,7 +217,7 @@ function detectValidators(projectPath = '.') {
|
|
|
243
217
|
* seven validators only two returned all three keys. `packages` — one of the
|
|
244
218
|
* two defaults for the `dotnet` stack — returned
|
|
245
219
|
* `{status, totalFiles, filesWithIssues, results}` and had every NuGet
|
|
246
|
-
* incompatibility it found thrown away, while `verify` reported
|
|
220
|
+
* incompatibility it found thrown away, while `verify` reported green.
|
|
247
221
|
*
|
|
248
222
|
* Reading the nested `results` shape here keeps that contract in ONE place
|
|
249
223
|
* instead of asking every validator to remember it.
|
|
@@ -257,7 +231,7 @@ export function normalizeValidatorResult(validatorId, raw) {
|
|
|
257
231
|
|
|
258
232
|
// Issues can be top-level, or nested one level under `results` — either as
|
|
259
233
|
// an array of `{issues}` (packages: one entry per .csproj) or an object of
|
|
260
|
-
// them
|
|
234
|
+
// them keyed by check name. The nested read is a FALLBACK,
|
|
261
235
|
// never additive: validators that expose both would otherwise report every
|
|
262
236
|
// finding twice.
|
|
263
237
|
const topLevel = Array.isArray(raw.issues) ? raw.issues : [];
|
|
@@ -299,38 +273,16 @@ export function normalizeValidatorResult(validatorId, raw) {
|
|
|
299
273
|
*/
|
|
300
274
|
async function runSingleValidator(validatorId, projectPath, featureName, options) {
|
|
301
275
|
switch (validatorId) {
|
|
302
|
-
case 'architecture': {
|
|
303
|
-
const { validateArchitecture } = await import('./architecture/architecture-validator.js');
|
|
304
|
-
return await validateArchitecture(projectPath, options);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
276
|
case 'packages': {
|
|
308
277
|
const { validatePackages } = await import('./packages/package-validator.js');
|
|
309
278
|
return await validatePackages(projectPath, options);
|
|
310
279
|
}
|
|
311
280
|
|
|
312
|
-
case 'contrast': {
|
|
313
|
-
const { validateContrast } = await import('./ui/ui-contrast-validator.js');
|
|
314
|
-
return await validateContrast(projectPath, options);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
case 'css': {
|
|
318
|
-
// The module exports `validateCssClasses`; this asked for `validateCss`,
|
|
319
|
-
// which is undefined — the call threw on every invocation.
|
|
320
|
-
const { validateCssClasses } = await import('./css/css-validator.js');
|
|
321
|
-
return await validateCssClasses(projectPath, options);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
281
|
case 'contract-compliance': {
|
|
325
282
|
const { validateContracts } = await import('./contracts/contract-compliance-validator.js');
|
|
326
283
|
return await validateContracts(projectPath, featureName, options);
|
|
327
284
|
}
|
|
328
285
|
|
|
329
|
-
case 'design-system': {
|
|
330
|
-
const { validateDesignSystem } = await import('./design-system/design-system-validator.js');
|
|
331
|
-
return await validateDesignSystem(projectPath, featureName, options);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
286
|
case 'reuse': {
|
|
335
287
|
const { validateReuse } = await import('./reuse/reuse-validator.js');
|
|
336
288
|
return await validateReuse(projectPath, featureName, options);
|
|
@@ -414,35 +366,3 @@ export function formatValidationResults(result) {
|
|
|
414
366
|
|
|
415
367
|
console.log('');
|
|
416
368
|
}
|
|
417
|
-
|
|
418
|
-
/**
|
|
419
|
-
* Read rubric ID from project config.
|
|
420
|
-
* @param {string} projectPath
|
|
421
|
-
* @returns {string}
|
|
422
|
-
*/
|
|
423
|
-
function readRubricConfig(projectPath) {
|
|
424
|
-
try {
|
|
425
|
-
const configPath = join(projectPath, '.morph', 'config', 'config.json');
|
|
426
|
-
if (!existsSync(configPath)) return 'default';
|
|
427
|
-
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
428
|
-
return config.validation?.rubric ?? 'default';
|
|
429
|
-
} catch {
|
|
430
|
-
return 'default';
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
/**
|
|
435
|
-
* Read threshold overrides from project config.
|
|
436
|
-
* @param {string} projectPath
|
|
437
|
-
* @returns {Object} { approve?: number, retry?: number }
|
|
438
|
-
*/
|
|
439
|
-
function readThresholdConfig(projectPath) {
|
|
440
|
-
try {
|
|
441
|
-
const configPath = join(projectPath, '.morph', 'config', 'config.json');
|
|
442
|
-
if (!existsSync(configPath)) return {};
|
|
443
|
-
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
444
|
-
return config.validation?.thresholds ?? {};
|
|
445
|
-
} catch {
|
|
446
|
-
return {};
|
|
447
|
-
}
|
|
448
|
-
}
|