@polymorphism-tech/morph-spec 8.29.0 → 8.31.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 +6 -1
- package/README.md +13 -2
- package/bin/morph-spec.js +48 -0
- package/bin/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +2 -0
- package/framework/MORPH.md +2 -1
- package/framework/agents.json +6 -6
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +18 -0
- package/framework/evals/rubrics/code-quality.md +17 -0
- package/framework/evals/rubrics/condensed.md +3 -1
- 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/phase-utils.js +2 -0
- package/framework/hooks/shared/stdin-reader.js +26 -26
- package/framework/mcp/registry.json +39 -4
- package/framework/rubrics/backend.json +2 -2
- package/framework/rubrics/default.json +2 -2
- package/framework/rubrics/frontend.json +2 -2
- package/framework/schemas/tasks.schema.json +25 -0
- 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 +12 -3
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-implement/SKILL.md +12 -2
- package/framework/skills/core/morph-plan/SKILL.md +45 -3
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +11 -3
- package/framework/skills/core/morph-stack-scan/SKILL.md +13 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +1 -1
- package/framework/standards/ai-agents/agent-session.md +160 -160
- package/framework/standards/ai-agents/batching.md +193 -193
- package/framework/standards/ai-agents/context-providers.md +188 -188
- package/framework/standards/ai-agents/mcp-server.md +250 -250
- package/framework/standards/ai-agents/mcp-tools.md +197 -197
- package/framework/standards/ai-agents/middleware-patterns.md +207 -207
- package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
- package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
- package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
- package/framework/standards/ai-agents/modalities-vision.md +201 -201
- package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
- package/framework/standards/ai-agents/production.md +231 -231
- package/framework/standards/ai-agents/prompts-in-db.md +214 -214
- package/framework/standards/ai-agents/providers/model-registry.md +158 -158
- package/framework/standards/ai-agents/service-tier-flex.md +135 -135
- package/framework/standards/ai-agents/structured-output.md +249 -249
- package/framework/standards/ai-agents/sweet-spot.md +109 -109
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
- package/framework/templates/code/dotnet/database/migration.cs +83 -83
- 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/dotnet/feature/validator.cs.hbs +15 -15
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/code/typescript/contracts.ts +167 -167
- package/framework/templates/context/ARCHITECTURE.md +20 -0
- package/framework/templates/docs/clarifications.md +253 -253
- package/framework/templates/docs/schema-analysis.md +119 -119
- 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/docs/ui-mockups.md +98 -98
- package/framework/templates/feature/decisions.md +122 -122
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
- 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/integrations/asaas-client.cs +387 -387
- package/framework/templates/integrations/asaas-webhook.cs +351 -351
- 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/project/validate.js +120 -120
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/dashboard/dashboard.js +53 -53
- package/src/commands/graph/affected.js +89 -0
- package/src/commands/graph/build.js +94 -0
- package/src/commands/graph/explain.js +96 -0
- package/src/commands/graph/status.js +93 -0
- package/src/commands/index.js +13 -13
- package/src/commands/project/checkpoint.js +209 -209
- package/src/commands/project/doctor.js +11 -0
- package/src/core/paths/output-schema.js +7 -0
- package/src/core/state/phase-state-machine.js +197 -197
- package/src/core/templates/template-renderer.js +477 -477
- package/src/dashboard/app/components/DetailPanel.jsx +11 -11
- package/src/dashboard/app/components/FlowGraph.jsx +17 -17
- package/src/dashboard/app/components/SearchFilter.jsx +22 -22
- package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +370 -370
- package/src/dashboard/app/pages/HookInspector.jsx +418 -418
- package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
- package/src/dashboard/app/pages/RulesMap.jsx +273 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
- package/src/dashboard/app/pages/Standards.jsx +332 -332
- package/src/dashboard/app/styles/index.css +47 -47
- package/src/dashboard/index.html +12 -12
- package/src/dashboard/package.json +24 -24
- package/src/dashboard/scanner/phases-scanner.js +15 -15
- package/src/dashboard/scanner/rubrics-scanner.js +12 -12
- package/src/dashboard/scanner/rules-scanner.js +37 -37
- package/src/dashboard/scanner/skills-scanner.js +43 -43
- package/src/dashboard/scanner/squads-scanner.js +13 -13
- package/src/dashboard/vite-plugin-morph-data.js +17 -17
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/detectors/design-system-detector.js +188 -188
- package/src/lib/detectors/index.js +217 -217
- package/src/lib/generators/architecture-scanner.js +45 -4
- package/src/lib/generators/design-system-generator.js +508 -508
- package/src/lib/generators/settings-generator.js +209 -209
- package/src/lib/graph/god-nodes.js +75 -0
- package/src/lib/graph/graph-health.js +61 -0
- package/src/lib/graph/graph-loader.js +86 -0
- package/src/lib/graph/graphify-runtime.js +100 -0
- package/src/lib/graph/graphifyignore.js +86 -0
- package/src/lib/graph/sanitize.js +280 -0
- package/src/lib/hooks/hook-executor.js +167 -167
- package/src/lib/installers/mcp-installer.js +719 -719
- 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 +26 -0
- package/src/lib/validators/checkpoint-persistence.js +39 -39
- package/src/lib/validators/css/css-validator.js +340 -340
- package/src/lib/validators/reuse/index.js +10 -0
- package/src/lib/validators/reuse/reuse-validator.js +324 -0
- package/src/lib/validators/reuse/symbol-scanner.js +144 -0
- package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
- package/src/lib/validators/shared/issue-counter.js +14 -14
- package/src/lib/validators/validation-runner.js +40 -1
- package/src/scripts/setup-infra.js +60 -1
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/file-copier.js +40 -0
- package/src/utils/skills-installer.js +187 -187
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reuse Validators
|
|
3
|
+
*
|
|
4
|
+
* Cobrança determinística de reuso de código: símbolo novo cujo nome já existe
|
|
5
|
+
* no grafo do codebase, na mesma linguagem. É a terceira camada do ciclo
|
|
6
|
+
* PROSA → ARTEFATO → VERIFICAÇÃO (ver reuse-validator.js).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export * from './reuse-validator.js';
|
|
10
|
+
export * from './symbol-scanner.js';
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reuse Validator — símbolo novo cujo nome já existe no codebase.
|
|
3
|
+
*
|
|
4
|
+
* A terceira camada do ciclo de reuso, no molde do `design-system-validator`:
|
|
5
|
+
*
|
|
6
|
+
* PROSA (morph-plan §1c, personas) ──► ARTEFATO (2-plan/reuse-map.md,
|
|
7
|
+
* `reuse` em tasks.json) ──► COBRANÇA (este arquivo)
|
|
8
|
+
*
|
|
9
|
+
* Prosa sozinha não segura — `morph-brainstorming/SKILL.md:39` já manda olhar
|
|
10
|
+
* o que existe, e o ProspectPRO redefiniu `SimpleBuilding` em cinco arquivos da
|
|
11
|
+
* MESMA pasta assim mesmo. O único ciclo que funciona no framework (design
|
|
12
|
+
* tokens) tem verificação em código; este é o equivalente para código.
|
|
13
|
+
*
|
|
14
|
+
* Regras, na ordem em que reduzem ruído:
|
|
15
|
+
*
|
|
16
|
+
* 1. Só tipo (class/record/interface/enum/struct/type) — não função nem
|
|
17
|
+
* variável. É a categoria que o grafo casa com confiança.
|
|
18
|
+
* 2. Só MESMA LINGUAGEM. Contrato espelhado entre C# e TS (110 casos no
|
|
19
|
+
* ProspectPRO) é o escopo F da proposta: exige gerar tipos do contrato,
|
|
20
|
+
* não se resolve por aviso, e fica FORA daqui de propósito.
|
|
21
|
+
* 3. Mesma feature = achado (`error`); feature diferente = aviso
|
|
22
|
+
* (`warning`), porque VSA prescreve DTO homônimo entre slices.
|
|
23
|
+
* 4. Veredito `CRIAR` declarado no plano (tasks.json `reuse`) rebaixa o
|
|
24
|
+
* achado a aviso: a decisão foi tomada no Gate 2, com justificativa no
|
|
25
|
+
* reuse-map. O validador cobra a decisão AUSENTE, não a decisão tomada.
|
|
26
|
+
*
|
|
27
|
+
* Sem grafo o validador não roda (ADR-002: ausência de grafo nunca bloqueia).
|
|
28
|
+
*
|
|
29
|
+
* @module reuse-validator
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { existsSync, readFileSync } from 'fs';
|
|
33
|
+
import { execFileSync } from 'child_process';
|
|
34
|
+
import { join, isAbsolute } from 'path';
|
|
35
|
+
|
|
36
|
+
import { loadGraph } from '../../graph/graph-loader.js';
|
|
37
|
+
import { languageOf } from '../../graph/sanitize.js';
|
|
38
|
+
import { getAbsoluteOutputPath } from '../../../core/paths/output-schema.js';
|
|
39
|
+
import {
|
|
40
|
+
featureOf,
|
|
41
|
+
isIdiomaticLocalName,
|
|
42
|
+
isScannableFile,
|
|
43
|
+
scanDeclarations,
|
|
44
|
+
} from './symbol-scanner.js';
|
|
45
|
+
|
|
46
|
+
/** Quantos locais citar por achado antes de resumir com "(+N)". */
|
|
47
|
+
const MAX_CITED = 4;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Índice "o que já existe": nome em minúsculas -> ocorrências reais do grafo.
|
|
51
|
+
*
|
|
52
|
+
* Só nós com `source_file` preenchido entram — nó com `source_file: ""` é stub
|
|
53
|
+
* não resolvido (BCL, NuGet, pacote externo), e apontar `Task` do .NET como
|
|
54
|
+
* duplicação seria ruído garantido. É o mesmo critério do `buildIndex`.
|
|
55
|
+
*
|
|
56
|
+
* @param {{nodes: object[]}} graph
|
|
57
|
+
* @returns {Map<string, Array<{name: string, file: string, language: string, feature: string}>>}
|
|
58
|
+
*/
|
|
59
|
+
export function buildExistingIndex(graph) {
|
|
60
|
+
const index = new Map();
|
|
61
|
+
|
|
62
|
+
for (const node of graph?.nodes ?? []) {
|
|
63
|
+
if (!node.source_file) continue;
|
|
64
|
+
// `_callable_class` é a marca de tipo do graphify; nós de ARQUIVO também a
|
|
65
|
+
// carregam, e se distinguem pelo label com extensão.
|
|
66
|
+
if (node._callable_class !== true) continue;
|
|
67
|
+
if (/\.[a-z0-9]+$/i.test(node.label)) continue;
|
|
68
|
+
if (!isScannableFile(node.source_file)) continue;
|
|
69
|
+
|
|
70
|
+
const key = String(node.label).toLowerCase();
|
|
71
|
+
const entry = {
|
|
72
|
+
name: node.label,
|
|
73
|
+
file: node.source_file.replace(/\\/g, '/'),
|
|
74
|
+
language: languageOf(node.source_file),
|
|
75
|
+
feature: featureOf(node.source_file),
|
|
76
|
+
};
|
|
77
|
+
const bucket = index.get(key);
|
|
78
|
+
if (bucket) bucket.push(entry);
|
|
79
|
+
else index.set(key, [entry]);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return index;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Casa as declarações desta feature contra o que já existe.
|
|
87
|
+
*
|
|
88
|
+
* Puro e sem I/O — é aqui que a regra mora, e é isto que o teste roda contra o
|
|
89
|
+
* grafo real do ProspectPRO.
|
|
90
|
+
*
|
|
91
|
+
* @param {Array<{name: string, kind: string, line: number, file: string}>} declarations
|
|
92
|
+
* @param {Map<string, Array<{name: string, file: string, language: string, feature: string}>>} existing
|
|
93
|
+
* @param {{verdicts?: Map<string, {verdict: string, target?: string}>}} [opts]
|
|
94
|
+
* @returns {Array<object>} issues, uma por símbolo duplicado
|
|
95
|
+
*/
|
|
96
|
+
export function findDuplicateSymbols(declarations, existing, opts = {}) {
|
|
97
|
+
const verdicts = opts.verdicts ?? new Map();
|
|
98
|
+
const issues = [];
|
|
99
|
+
|
|
100
|
+
// Uma declaração por (nome, arquivo); o mesmo nome em N arquivos da feature
|
|
101
|
+
// vira UM achado com N locais, não N achados.
|
|
102
|
+
const byName = new Map();
|
|
103
|
+
for (const decl of declarations) {
|
|
104
|
+
const key = decl.name.toLowerCase();
|
|
105
|
+
const bucket = byName.get(key);
|
|
106
|
+
if (bucket) bucket.push(decl);
|
|
107
|
+
else byName.set(key, [decl]);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const [key, decls] of byName) {
|
|
111
|
+
if (isIdiomaticLocalName(decls[0].name)) continue;
|
|
112
|
+
|
|
113
|
+
const declaredFiles = new Set(decls.map((d) => d.file));
|
|
114
|
+
const language = languageOf(decls[0].file);
|
|
115
|
+
|
|
116
|
+
// Onde mais esse nome vive: no grafo (código que já existia) e nos OUTROS
|
|
117
|
+
// arquivos desta mesma leva (código novo que o grafo ainda não viu — o
|
|
118
|
+
// rebuild é assíncrono, e é exatamente o caso SimpleBuilding).
|
|
119
|
+
const fromGraph = (existing.get(key) ?? []).filter(
|
|
120
|
+
(e) => e.language === language && !declaredFiles.has(e.file),
|
|
121
|
+
);
|
|
122
|
+
// O filtro de linguagem vale AQUI TAMBÉM, não só contra o grafo: uma leva
|
|
123
|
+
// que toca `CatalogContracts.cs` e `catalog.types.ts` declara `CatalogItem`
|
|
124
|
+
// nos dois, na mesma feature `Catalog/` — e sem este filtro isso vira erro,
|
|
125
|
+
// que é precisamente o contrato espelhado C#↔TS que a proposta separa no
|
|
126
|
+
// escopo F por não se resolver com aviso. Medido no MORPH_GHLBrain.
|
|
127
|
+
const fromBatch = decls
|
|
128
|
+
.slice(1)
|
|
129
|
+
.filter((d) => languageOf(d.file) === language)
|
|
130
|
+
.map((d) => ({ name: d.name, file: d.file, language, feature: featureOf(d.file) }));
|
|
131
|
+
|
|
132
|
+
const elsewhere = [...fromGraph, ...fromBatch];
|
|
133
|
+
if (elsewhere.length === 0) continue;
|
|
134
|
+
|
|
135
|
+
const homeFeature = featureOf(decls[0].file);
|
|
136
|
+
const intra = elsewhere.some((e) => e.feature === homeFeature);
|
|
137
|
+
const verdict = verdicts.get(key);
|
|
138
|
+
const planned = verdict?.verdict === 'CRIAR';
|
|
139
|
+
|
|
140
|
+
const locations = elsewhere.slice(0, MAX_CITED).map((e) => e.file);
|
|
141
|
+
const more = elsewhere.length - locations.length;
|
|
142
|
+
|
|
143
|
+
issues.push({
|
|
144
|
+
level: intra && !planned ? 'error' : 'warning',
|
|
145
|
+
scope: intra ? 'intra-feature' : 'cross-feature',
|
|
146
|
+
symbol: decls[0].name,
|
|
147
|
+
file: decls[0].file,
|
|
148
|
+
line: decls[0].line,
|
|
149
|
+
planned,
|
|
150
|
+
message:
|
|
151
|
+
`${decls[0].kind} "${decls[0].name}" já existe ${intra ? 'na MESMA feature' : 'no projeto'} ` +
|
|
152
|
+
`(${elsewhere.length}× em ${locations.join(', ')}${more > 0 ? ` (+${more})` : ''})` +
|
|
153
|
+
(planned ? ' — o plano declarou CRIAR, mas confira se a justificativa ainda vale' : ''),
|
|
154
|
+
solution: intra
|
|
155
|
+
? `Reuse ou estenda o tipo existente em vez de redeclarar. Confirme com ` +
|
|
156
|
+
`\`morph-spec graph explain ${decls[0].name}\` (sem grafo: ` +
|
|
157
|
+
`\`grep -rn "${decls[0].name}"\`) e, se a criação for mesmo necessária, ` +
|
|
158
|
+
`registre o veredito CRIAR com justificativa no reuse-map da feature.`
|
|
159
|
+
: `VSA tolera DTO homônimo entre slices — confirme que são conceitos ` +
|
|
160
|
+
`diferentes e não a mesma coisa escrita duas vezes.`,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return issues.sort((a, b) => {
|
|
165
|
+
if (a.level !== b.level) return a.level === 'error' ? -1 : 1;
|
|
166
|
+
return a.symbol.localeCompare(b.symbol);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Vereditos de reuso carimbados no plano: `tasks[].reuse[]`.
|
|
172
|
+
*
|
|
173
|
+
* Ler do `tasks.json` — e não do `reuse-map.md` — é deliberado: o veredito é
|
|
174
|
+
* um dado estruturado, e o markdown existe para o humano ler no Gate 2. Duas
|
|
175
|
+
* leituras do mesmo fato, cada uma na forma certa para o seu consumidor.
|
|
176
|
+
*
|
|
177
|
+
* @param {string} projectPath
|
|
178
|
+
* @param {string} featureName
|
|
179
|
+
* @returns {Map<string, {verdict: string, target?: string}>}
|
|
180
|
+
*/
|
|
181
|
+
export function readReuseVerdicts(projectPath, featureName) {
|
|
182
|
+
const verdicts = new Map();
|
|
183
|
+
if (!featureName) return verdicts;
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const tasksPath = getAbsoluteOutputPath(projectPath, featureName, 'tasks');
|
|
187
|
+
if (!existsSync(tasksPath)) return verdicts;
|
|
188
|
+
const doc = JSON.parse(readFileSync(tasksPath, 'utf8').replace(/^/, ''));
|
|
189
|
+
for (const task of doc.tasks ?? []) {
|
|
190
|
+
for (const entry of task.reuse ?? []) {
|
|
191
|
+
if (!entry?.concept) continue;
|
|
192
|
+
verdicts.set(String(entry.concept).toLowerCase(), {
|
|
193
|
+
verdict: String(entry.verdict || '').toUpperCase(),
|
|
194
|
+
target: entry.target,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
} catch {
|
|
199
|
+
// Plano ilegível não é problema deste validador — o tasks-json-guard já
|
|
200
|
+
// cobra o schema, e aqui a ausência de veredito só torna a cobrança mais
|
|
201
|
+
// rígida, nunca mais frouxa.
|
|
202
|
+
}
|
|
203
|
+
return verdicts;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Arquivos que esta feature criou ou alterou.
|
|
208
|
+
*
|
|
209
|
+
* Duas fontes, unidas, porque cada uma é cega no momento em que a outra
|
|
210
|
+
* enxerga: `outputs` do `tasks.json` é o contrato do framework (morph-implement
|
|
211
|
+
* §8 exige listar TODO arquivo produzido) e sobrevive ao commit; `git status`
|
|
212
|
+
* pega o que ainda não foi registrado numa task. Sem git, sem drama.
|
|
213
|
+
*
|
|
214
|
+
* @param {string} projectPath
|
|
215
|
+
* @param {string} featureName
|
|
216
|
+
* @returns {string[]} paths relativos ao projeto, com barra normal
|
|
217
|
+
*/
|
|
218
|
+
export function collectChangedFiles(projectPath, featureName) {
|
|
219
|
+
const files = new Set();
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
const tasksPath = getAbsoluteOutputPath(projectPath, featureName, 'tasks');
|
|
223
|
+
if (existsSync(tasksPath)) {
|
|
224
|
+
const doc = JSON.parse(readFileSync(tasksPath, 'utf8').replace(/^/, ''));
|
|
225
|
+
for (const task of doc.tasks ?? []) {
|
|
226
|
+
for (const out of task.outputs ?? []) {
|
|
227
|
+
if (typeof out === 'string') files.add(out.replace(/\\/g, '/'));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
} catch {
|
|
232
|
+
// tasks.json ausente/ilegível: sobra o git.
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
const out = execFileSync('git', ['status', '--porcelain', '--untracked-files=all'], {
|
|
237
|
+
cwd: projectPath,
|
|
238
|
+
encoding: 'utf-8',
|
|
239
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
240
|
+
windowsHide: true,
|
|
241
|
+
});
|
|
242
|
+
for (const line of out.split(/\r?\n/)) {
|
|
243
|
+
if (!line.trim()) continue;
|
|
244
|
+
// "XY path" e, em rename, "XY antigo -> novo": só o destino interessa.
|
|
245
|
+
// Os paths vêm relativos à RAIZ do repositório — que no morph-spec é a
|
|
246
|
+
// própria árvore da feature (cada worktree é a sua raiz). Se algum dia
|
|
247
|
+
// não coincidir com `projectPath`, o `existsSync` adiante descarta o
|
|
248
|
+
// path: perde-se cobertura, nunca se acusa um arquivo que não existe.
|
|
249
|
+
const path = line.slice(3).split(' -> ').pop().replace(/^"|"$/g, '');
|
|
250
|
+
if (path) files.add(path.replace(/\\/g, '/'));
|
|
251
|
+
}
|
|
252
|
+
} catch {
|
|
253
|
+
// Fora de um repo git, ou git indisponível: o tasks.json já respondeu.
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return [...files];
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Validador plugado no `validation-runner` (e por ele no nó `validators` do
|
|
261
|
+
* `morph-spec verify`).
|
|
262
|
+
*
|
|
263
|
+
* @param {string} projectPath
|
|
264
|
+
* @param {string} featureName
|
|
265
|
+
* @param {object} [options]
|
|
266
|
+
* @param {string[]} [options.changedFiles] - override da coleta (testes)
|
|
267
|
+
* @param {object} [options._graph] - grafo já carregado (testes)
|
|
268
|
+
* @returns {Promise<{errors: number, warnings: number, issues: object[]}>}
|
|
269
|
+
*/
|
|
270
|
+
export async function validateReuse(projectPath, featureName = null, options = {}) {
|
|
271
|
+
const result = { errors: 0, warnings: 0, issues: [] };
|
|
272
|
+
|
|
273
|
+
const graph = options._graph ?? (() => {
|
|
274
|
+
const loaded = loadGraph(projectPath);
|
|
275
|
+
return loaded.ok ? loaded.graph : null;
|
|
276
|
+
})();
|
|
277
|
+
|
|
278
|
+
if (!graph) {
|
|
279
|
+
// ADR-002: projeto sem grafo (sem Python, ou grafo ainda não construído)
|
|
280
|
+
// segue em frente. Um validador que reprovasse aqui transformaria uma
|
|
281
|
+
// dependência opcional em pré-requisito.
|
|
282
|
+
result._skipReason = 'sem grafo de codebase — `morph-spec graph build` habilita esta verificação';
|
|
283
|
+
return result;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const changed = options.changedFiles ?? collectChangedFiles(projectPath, featureName);
|
|
287
|
+
const declarations = [];
|
|
288
|
+
|
|
289
|
+
for (const rel of changed) {
|
|
290
|
+
if (!isScannableFile(rel)) continue;
|
|
291
|
+
const abs = isAbsolute(rel) ? rel : join(projectPath, rel);
|
|
292
|
+
if (!existsSync(abs)) continue;
|
|
293
|
+
let content;
|
|
294
|
+
try {
|
|
295
|
+
content = readFileSync(abs, 'utf8');
|
|
296
|
+
} catch {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
for (const decl of scanDeclarations(content, rel)) {
|
|
300
|
+
declarations.push({ ...decl, file: rel.replace(/\\/g, '/') });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (declarations.length === 0) {
|
|
305
|
+
result._skipReason = 'nenhum tipo novo declarado nos arquivos desta feature';
|
|
306
|
+
return result;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const existing = buildExistingIndex(graph);
|
|
310
|
+
const verdicts = readReuseVerdicts(projectPath, featureName);
|
|
311
|
+
const issues = findDuplicateSymbols(declarations, existing, { verdicts });
|
|
312
|
+
|
|
313
|
+
result.issues = issues.map((i) => ({
|
|
314
|
+
level: i.level,
|
|
315
|
+
message: i.message,
|
|
316
|
+
file: i.file,
|
|
317
|
+
line: i.line,
|
|
318
|
+
solution: i.solution,
|
|
319
|
+
}));
|
|
320
|
+
result.errors = issues.filter((i) => i.level === 'error').length;
|
|
321
|
+
result.warnings = issues.filter((i) => i.level === 'warning').length;
|
|
322
|
+
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extração determinística de declarações de tipo a partir do TEXTO do arquivo.
|
|
3
|
+
*
|
|
4
|
+
* Por que não ler as declarações do grafo: o grafo é reconstruído em background
|
|
5
|
+
* pelo hook post-commit do graphify, então o código que acabou de ser escrito
|
|
6
|
+
* (o caso que este validador existe para pegar) ainda NÃO está nele. O grafo
|
|
7
|
+
* responde "o que já existia"; o disco responde "o que esta feature acabou de
|
|
8
|
+
* declarar". Inverter os dois papéis produziria um validador cego justamente na
|
|
9
|
+
* janela em que ele precisa morder — ver graph-loader.js `getFreshness`.
|
|
10
|
+
*
|
|
11
|
+
* A varredura é por linha, com regex ancorada no início da declaração. Mesma
|
|
12
|
+
* limitação (e mesmo grau de tolerância) do design-system-validator: uma
|
|
13
|
+
* declaração citada dentro de uma string ou de um comentário de bloco pode
|
|
14
|
+
* passar. O custo de errar é um aviso a mais, nunca um bloqueio silencioso.
|
|
15
|
+
*
|
|
16
|
+
* @module reuse-symbol-scanner
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { languageOf } from '../../graph/sanitize.js';
|
|
20
|
+
|
|
21
|
+
/** Extensões que este scanner sabe ler. Fora daqui, o arquivo é ignorado. */
|
|
22
|
+
const SCANNABLE = /\.(cs|ts|tsx|js|jsx|mjs|cjs)$/i;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Arquivo de teste, em qualquer das convenções que os dois stacks usam.
|
|
26
|
+
* Duplicar um stub entre três arquivos de teste (`ResizeObserverStub` no
|
|
27
|
+
* ProspectPRO) é ruído conhecido: teste é código descartável por natureza e a
|
|
28
|
+
* regra de reuso não se aplica a ele. Cobre `__tests__/`, `.test.`, `.spec.`,
|
|
29
|
+
* e os projetos `*.Tests/` do .NET.
|
|
30
|
+
*/
|
|
31
|
+
const TEST_FILE = /(^|\/)__tests__\/|\.(test|spec)\.[jt]sx?$|(^|\/)(?:[Tt]ests?|[^/]*\.[Tt]ests?|[^/]*[a-z0-9_]Tests?)\//;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Diretórios que contêm arquivos com extensão de código mas que não são o
|
|
35
|
+
* código-fonte do projeto. `.morph/` é o caso que apareceu de verdade: o
|
|
36
|
+
* `archive/{feature}/1-design/contracts.cs` de uma feature arquivada entra no
|
|
37
|
+
* grafo do ProspectPRO como um tipo `RoulettePriority` a mais, e acusaria a
|
|
38
|
+
* entidade real de duplicar o próprio artefato de planejamento.
|
|
39
|
+
*/
|
|
40
|
+
const NOT_SOURCE = /(^|\/)(?:\.morph|node_modules|dist|build|out|\.next|bin|obj|coverage)\//;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Declaration file do TypeScript. `auth-debug.d.ts` + `auth-debug.ts`
|
|
44
|
+
* declarando o mesmo tipo é o mecanismo da linguagem, não duplicação.
|
|
45
|
+
*/
|
|
46
|
+
const DECLARATION_FILE = /\.d\.ts$/i;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Nomes que a convenção usa como tipo LOCAL de arquivo, não como conceito do
|
|
50
|
+
* domínio. Todo componente React declara o seu `Props`; dois arquivos com
|
|
51
|
+
* `interface Props` não são o mesmo conceito escrito duas vezes — são dois
|
|
52
|
+
* conceitos diferentes com o nome que a convenção manda usar.
|
|
53
|
+
*
|
|
54
|
+
* A lista é curta de propósito e só admite nome genérico de UMA palavra: assim
|
|
55
|
+
* que há qualificador de domínio (`BuildingFiltersProps`, `FilterOption`) o
|
|
56
|
+
* nome volta a ser um conceito e a regra vale. Mesmo papel da allowlist de
|
|
57
|
+
* cores de sistema no design-system-validator.
|
|
58
|
+
*/
|
|
59
|
+
const IDIOMATIC_LOCAL_NAMES = new Set([
|
|
60
|
+
'props', 'state', 'row', 'item', 'column', 'context', 'params', 'options',
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
/** Declaração de tipo em C#. `partial` é capturado para ser excluído depois. */
|
|
64
|
+
const CSHARP_DECL =
|
|
65
|
+
/^\s*(?:\[[^\]]*\]\s*)*(?:(?:public|internal|private|protected|sealed|abstract|static|readonly|new|file|partial)\s+)*(class|record|interface|enum|struct)\s+([A-Za-z_]\w*)/;
|
|
66
|
+
|
|
67
|
+
/** Declaração de tipo em TS/JS. */
|
|
68
|
+
const WEB_DECL =
|
|
69
|
+
/^\s*(?:export\s+(?:default\s+)?)?(?:declare\s+)?(?:abstract\s+)?(interface|type|class|enum)\s+([A-Za-z_$][\w$]*)/;
|
|
70
|
+
|
|
71
|
+
/** Linha que é claramente comentário — não vale a pena parsear. */
|
|
72
|
+
const COMMENT_LINE = /^\s*(?:\/\/|\/\*|\*|<!--)/;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param {string} sourceFile
|
|
76
|
+
* @returns {boolean} o arquivo entra na varredura de reuso
|
|
77
|
+
*/
|
|
78
|
+
export function isScannableFile(sourceFile) {
|
|
79
|
+
const f = String(sourceFile || '').replace(/\\/g, '/');
|
|
80
|
+
if (!SCANNABLE.test(f)) return false;
|
|
81
|
+
if (DECLARATION_FILE.test(f)) return false;
|
|
82
|
+
if (TEST_FILE.test(f)) return false;
|
|
83
|
+
if (NOT_SOURCE.test(f)) return false;
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* "Feature" de um path, com a mesma heurística do baseline
|
|
89
|
+
* (docs/specs/code-reuse-gap/measure-duplication.mjs): o segmento após
|
|
90
|
+
* `features/`/`Features/` quando existe, senão a pasta do arquivo. É o que
|
|
91
|
+
* separa o achado indefensável (mesma feature) do aviso tolerado por VSA.
|
|
92
|
+
*
|
|
93
|
+
* @param {string} sourceFile
|
|
94
|
+
* @returns {string}
|
|
95
|
+
*/
|
|
96
|
+
export function featureOf(sourceFile) {
|
|
97
|
+
const f = String(sourceFile || '').replace(/\\/g, '/');
|
|
98
|
+
const m = /(?:^|\/)(?:features|Features)\/([^/]+)/.exec(f);
|
|
99
|
+
if (m) return m[1].toLowerCase();
|
|
100
|
+
return f.split('/').slice(0, -1).join('/').toLowerCase();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {string} name
|
|
105
|
+
* @returns {boolean} nome genérico que a convenção usa como tipo local
|
|
106
|
+
*/
|
|
107
|
+
export function isIdiomaticLocalName(name) {
|
|
108
|
+
return IDIOMATIC_LOCAL_NAMES.has(String(name).toLowerCase());
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Declarações de tipo de um arquivo.
|
|
113
|
+
*
|
|
114
|
+
* `partial` (C#) é reconhecido e DESCARTADO: declarar a mesma classe em dois
|
|
115
|
+
* arquivos é o propósito da palavra-chave, e sinalizá-la seria um falso
|
|
116
|
+
* positivo garantido em qualquer projeto .NET com código gerado.
|
|
117
|
+
*
|
|
118
|
+
* @param {string} content - conteúdo do arquivo
|
|
119
|
+
* @param {string} sourceFile - path, usado só para decidir a linguagem
|
|
120
|
+
* @returns {Array<{name: string, kind: string, line: number}>}
|
|
121
|
+
*/
|
|
122
|
+
export function scanDeclarations(content, sourceFile) {
|
|
123
|
+
const language = languageOf(sourceFile);
|
|
124
|
+
const re = language === 'dotnet' ? CSHARP_DECL : WEB_DECL;
|
|
125
|
+
const out = [];
|
|
126
|
+
const seen = new Set();
|
|
127
|
+
|
|
128
|
+
const lines = String(content || '').split(/\r?\n/);
|
|
129
|
+
for (let i = 0; i < lines.length; i++) {
|
|
130
|
+
const line = lines[i];
|
|
131
|
+
if (COMMENT_LINE.test(line)) continue;
|
|
132
|
+
if (language === 'dotnet' && /\bpartial\s+(class|record|interface|struct)\b/.test(line)) continue;
|
|
133
|
+
|
|
134
|
+
const m = re.exec(line);
|
|
135
|
+
if (!m) continue;
|
|
136
|
+
|
|
137
|
+
const [, kind, name] = m;
|
|
138
|
+
if (seen.has(name)) continue;
|
|
139
|
+
seen.add(name);
|
|
140
|
+
out.push({ name, kind, line: i + 1 });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Stub: validation dispatch was driven by tier-4 agents in v4.
|
|
3
|
-
* In v5, validation is handled by morph-eval skill invoked directly
|
|
4
|
-
* by the Evaluator role. This function is kept for call-site compatibility.
|
|
5
|
-
*
|
|
6
|
-
* @module emit-validator-dispatch
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* No-op in v5. Validation dispatch is now handled by morph-eval skill.
|
|
11
|
-
*/
|
|
12
|
-
export async function emitValidatorDispatch(_featureName, _phase, _cwd) {
|
|
13
|
-
// v5: evaluation driven by morph-eval skill, not CLI dispatch
|
|
14
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Stub: validation dispatch was driven by tier-4 agents in v4.
|
|
3
|
+
* In v5, validation is handled by morph-eval skill invoked directly
|
|
4
|
+
* by the Evaluator role. This function is kept for call-site compatibility.
|
|
5
|
+
*
|
|
6
|
+
* @module emit-validator-dispatch
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* No-op in v5. Validation dispatch is now handled by morph-eval skill.
|
|
11
|
+
*/
|
|
12
|
+
export async function emitValidatorDispatch(_featureName, _phase, _cwd) {
|
|
13
|
+
// v5: evaluation driven by morph-eval skill, not CLI dispatch
|
|
14
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared issue counting utility for validators
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* @param {Array} issues - Array of validation issues with type property
|
|
6
|
-
* @returns {{errors: number, warnings: number, infos: number}} Count by type
|
|
7
|
-
*/
|
|
8
|
-
export function countIssues(issues) {
|
|
9
|
-
return {
|
|
10
|
-
errors: issues.filter((i) => i.type === 'error').length,
|
|
11
|
-
warnings: issues.filter((i) => i.type === 'warning').length,
|
|
12
|
-
infos: issues.filter((i) => i.type === 'info').length,
|
|
13
|
-
};
|
|
14
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Shared issue counting utility for validators
|
|
3
|
+
*
|
|
4
|
+
*
|
|
5
|
+
* @param {Array} issues - Array of validation issues with type property
|
|
6
|
+
* @returns {{errors: number, warnings: number, infos: number}} Count by type
|
|
7
|
+
*/
|
|
8
|
+
export function countIssues(issues) {
|
|
9
|
+
return {
|
|
10
|
+
errors: issues.filter((i) => i.type === 'error').length,
|
|
11
|
+
warnings: issues.filter((i) => i.type === 'warning').length,
|
|
12
|
+
infos: issues.filter((i) => i.type === 'info').length,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -24,7 +24,7 @@ import { aggregateScores, loadRubric } from './score-aggregator.js';
|
|
|
24
24
|
*/
|
|
25
25
|
const HEAVY_VALIDATORS = new Set([
|
|
26
26
|
'architecture', 'packages', 'contract-compliance',
|
|
27
|
-
'css', 'design-system'
|
|
27
|
+
'css', 'design-system', 'reuse'
|
|
28
28
|
]);
|
|
29
29
|
|
|
30
30
|
/**
|
|
@@ -74,6 +74,22 @@ export async function runValidation(projectPath, featureName, options = {}) {
|
|
|
74
74
|
// Merge constraint-enabled validators
|
|
75
75
|
validatorIds.push(...constraints.enabledValidators);
|
|
76
76
|
|
|
77
|
+
// Reuse is stack-agnostic: "isto já existe?" é a mesma pergunta em C# e em
|
|
78
|
+
// TypeScript, e o validador se cala sozinho quando não há grafo (ADR-002).
|
|
79
|
+
// Por isso ele não entra no `defaultValidators` de stack profile nenhum —
|
|
80
|
+
// entra aqui, ANTES do early-return de "nenhum validador": num projeto de
|
|
81
|
+
// stack `empty`/`nodejs-cli` não há outro validador para carregá-lo, e é
|
|
82
|
+
// exatamente lá que ele seria a única verificação a rodar.
|
|
83
|
+
// Em `light` (checkpoint) ele fica de fora, como todo validador pesado — é
|
|
84
|
+
// uma leitura de graph.json (11-24 MB nos repos medidos) — e por isso está
|
|
85
|
+
// em HEAVY_VALIDATORS, que é onde essa política se declara. Não entra em
|
|
86
|
+
// `droppedByMode`: essa lista responde "o que o modo tirou da SUA lista", e
|
|
87
|
+
// é ela que distingue "light pulou os pesados" de "este stack não tem
|
|
88
|
+
// validador nenhum" na mensagem final.
|
|
89
|
+
if (!options.skipReuse && mode !== 'light') {
|
|
90
|
+
validatorIds.push('reuse');
|
|
91
|
+
}
|
|
92
|
+
|
|
77
93
|
if (validatorIds.length === 0) {
|
|
78
94
|
// Say which of the two it is. On a pure `dotnet` project every default
|
|
79
95
|
// validator is heavy, so a light run legitimately runs none — but the old
|
|
@@ -115,6 +131,11 @@ export async function runValidation(projectPath, featureName, options = {}) {
|
|
|
115
131
|
// Deduplicate
|
|
116
132
|
const uniqueValidators = [...new Set(validatorIds)];
|
|
117
133
|
|
|
134
|
+
// Validadores que rodaram mas se declararam inaplicáveis (ex.: `reuse` num
|
|
135
|
+
// projeto sem grafo). Um validador assim não acha nada — e "não achou nada"
|
|
136
|
+
// é indistinguível de "verificou e está limpo" se ninguém contar.
|
|
137
|
+
const selfSkipped = [];
|
|
138
|
+
|
|
118
139
|
// Run each validator
|
|
119
140
|
for (const validatorId of uniqueValidators) {
|
|
120
141
|
try {
|
|
@@ -127,6 +148,9 @@ export async function runValidation(projectPath, featureName, options = {}) {
|
|
|
127
148
|
const validatorResult = await runSingleValidator(validatorId, projectPath, featureName, validatorOptions);
|
|
128
149
|
|
|
129
150
|
if (validatorResult) {
|
|
151
|
+
if (validatorResult._skipReason) {
|
|
152
|
+
selfSkipped.push(`${validatorId}: ${validatorResult._skipReason}`);
|
|
153
|
+
}
|
|
130
154
|
const { errors, warnings, issues } = normalizeValidatorResult(validatorId, validatorResult);
|
|
131
155
|
result.errors += errors;
|
|
132
156
|
result.warnings += warnings;
|
|
@@ -158,6 +182,16 @@ export async function runValidation(projectPath, featureName, options = {}) {
|
|
|
158
182
|
}
|
|
159
183
|
}
|
|
160
184
|
|
|
185
|
+
// Todos os validadores se declararam inaplicáveis: o resultado é SKIP, não um
|
|
186
|
+
// verde. Sem isto, um projeto de stack sem validadores e sem grafo receberia
|
|
187
|
+
// `validators: pass` do `morph-spec verify` tendo verificado exatamente nada
|
|
188
|
+
// — o modo de falha que este repositório já pagou caro (guardrail inerte).
|
|
189
|
+
if (selfSkipped.length > 0 && selfSkipped.length === uniqueValidators.length) {
|
|
190
|
+
result._skipReason = `No validators ran — ${selfSkipped.join('; ')}`;
|
|
191
|
+
result.summary = result._skipReason;
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
|
|
161
195
|
result.passed = result.errors === 0;
|
|
162
196
|
result.summary = result.passed
|
|
163
197
|
? `✅ All validations passed (${result.warnings} warnings)`
|
|
@@ -286,6 +320,11 @@ async function runSingleValidator(validatorId, projectPath, featureName, options
|
|
|
286
320
|
return await validateDesignSystem(projectPath, featureName, options);
|
|
287
321
|
}
|
|
288
322
|
|
|
323
|
+
case 'reuse': {
|
|
324
|
+
const { validateReuse } = await import('./reuse/reuse-validator.js');
|
|
325
|
+
return await validateReuse(projectPath, featureName, options);
|
|
326
|
+
}
|
|
327
|
+
|
|
289
328
|
case 'nextjs-component': {
|
|
290
329
|
const { validateNextComponentFiles } = await import('./nextjs/next-component-validator.js');
|
|
291
330
|
return await validateNextComponentFiles(projectPath, options);
|