@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
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { execFileSync } from 'child_process';
|
|
2
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
|
|
3
|
+
import { join, relative, resolve } from 'path';
|
|
4
|
+
import { workTreeTop } from '../../../framework/hooks/shared/git-paths.js';
|
|
5
|
+
|
|
6
|
+
export const LINE_TOLERANCE = 2;
|
|
7
|
+
|
|
8
|
+
const SPAN = /`([^`\n]+)`/g;
|
|
9
|
+
const SPAN_CITATION = /^(.+\.[A-Za-z0-9]+):(\d+)(?:-(\d+))?$/;
|
|
10
|
+
const BARE_CITATION = /(?<![\w.:\\/-])(?:[A-Za-z]:)?[\w.\\/-]*\w\.[A-Za-z0-9]+:(\d+)(?:-(\d+))?\b/g;
|
|
11
|
+
const GREP_LINE = /^((?:[A-Za-z]:)?[^\s:]+\.[A-Za-z0-9]+)(?::(\d+):|-(\d+)-)(.*)$/;
|
|
12
|
+
const FENCE = /^\s*(```|~~~)/;
|
|
13
|
+
const HEADING = /^\s{0,3}#{1,6}\s+(.*)$/;
|
|
14
|
+
const INFERENCE_HEADING = /inferid|hip[óo]tese|inferred|hypothes/i;
|
|
15
|
+
const INFERENCE_MARKER = /\b(INFERIDO|hip[óo]tese)\b/i;
|
|
16
|
+
const COMMAND_SPAN = /^(rg|grep|git\s+grep|morph-spec\s+graph|findstr|Select-String|sls)\b/;
|
|
17
|
+
const COUNT = /(?:→|->|=>)\s*\d+|\b\d+\s+(?:ocorr[êe]ncias?|arquivos?|linhas?|hits?|resultados?|matches?|files?|lines?)\b/i;
|
|
18
|
+
const PATH_SPAN = /^(?:[\w.-]*[\\/])+[\w.-]+$|^[\w.-]+\.(?:cs|csproj|fs|vb|razor|js|mjs|cjs|ts|tsx|jsx|json|md|py|go|java|sql|ya?ml|xml|props|targets|sln)$/i;
|
|
19
|
+
|
|
20
|
+
const VERDICTS = [
|
|
21
|
+
/\b(?:VIVO|VIVA|MORTO|MORTA|ALIVE|DEAD)\b/,
|
|
22
|
+
/\b(?:c[óo]digo morto|dead code|unused)\b/i,
|
|
23
|
+
/\bsem (?:nenhum )?(?:consumidor|chamador|leitor|escritor|setter|uso|refer[êe]ncia|teste)s?\b/i,
|
|
24
|
+
/(?<!\b(?:execut|rod)\w*\s)\bnenhum[a]? (?:consumidor|chamador|leitor|escritor|setter|uso|refer[êe]ncia|teste|caller|call site)s?\b/i,
|
|
25
|
+
/\bningu[ée]m (?:chama|usa|l[êe]|escreve|grava|consome|referencia)\b/i,
|
|
26
|
+
/\bn[ãa]o (?:é|est[áa]) (?:usad|chamad|lid|referenciad|escrit)/i,
|
|
27
|
+
/\bnunca (?:é )?(?:usad|chamad|lid|escrit)/i,
|
|
28
|
+
/\bn[ãa]o h[áa] (?:consumidor|chamador|leitor|escritor|uso|teste|refer[êe]ncia)/i,
|
|
29
|
+
/\bno (?:callers?|consumers?|readers?|writers?|tests?|usages?)\b/i,
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const WALK_SKIP = new Set(['node_modules', '.git', 'bin', 'obj', 'worktrees', '.worktrees']);
|
|
33
|
+
|
|
34
|
+
function splitLines(text) {
|
|
35
|
+
const lines = text.replace(/^/, '').split(/\r?\n/);
|
|
36
|
+
if (lines.length > 1 && lines[lines.length - 1] === '') lines.pop();
|
|
37
|
+
return lines;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const squash = (s) => s.replace(/\s+/g, ' ').trim();
|
|
41
|
+
|
|
42
|
+
function lineTokens(line) {
|
|
43
|
+
const tokens = [];
|
|
44
|
+
const masked = line.replace(SPAN, (whole, inner, index) => {
|
|
45
|
+
const cite = inner.trim().match(SPAN_CITATION);
|
|
46
|
+
if (cite) tokens.push({ index, cite: { raw: inner.trim(), path: cite[1], start: +cite[2], end: +(cite[3] ?? cite[2]) } });
|
|
47
|
+
else tokens.push({ index, quote: inner });
|
|
48
|
+
return ' '.repeat(whole.length);
|
|
49
|
+
});
|
|
50
|
+
for (const m of masked.matchAll(BARE_CITATION)) {
|
|
51
|
+
const cite = m[0].match(SPAN_CITATION);
|
|
52
|
+
if (cite) tokens.push({ index: m.index, cite: { raw: m[0], path: cite[1], start: +cite[2], end: +(cite[3] ?? cite[2]) } });
|
|
53
|
+
}
|
|
54
|
+
return tokens.sort((a, b) => a.index - b.index);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function citationsOf(line) {
|
|
58
|
+
const tokens = lineTokens(line);
|
|
59
|
+
const out = [];
|
|
60
|
+
tokens.forEach((t, i) => {
|
|
61
|
+
if (!t.cite) return;
|
|
62
|
+
let quote = null;
|
|
63
|
+
for (const next of tokens.slice(i + 1)) {
|
|
64
|
+
if (next.cite) break;
|
|
65
|
+
const q = next.quote.trim();
|
|
66
|
+
if (COMMAND_SPAN.test(q) || PATH_SPAN.test(q)) continue;
|
|
67
|
+
quote = next.quote;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
out.push({ ...t.cite, quote });
|
|
71
|
+
});
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function makeFileLister(root) {
|
|
76
|
+
let files = null;
|
|
77
|
+
return () => {
|
|
78
|
+
if (files) return files;
|
|
79
|
+
try {
|
|
80
|
+
files = execFileSync('git', ['ls-files', '-z', '--cached', '--others', '--exclude-standard'], {
|
|
81
|
+
cwd: root, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true, maxBuffer: 64 * 1024 * 1024,
|
|
82
|
+
}).toString().split('\0').filter(Boolean);
|
|
83
|
+
} catch {
|
|
84
|
+
files = [];
|
|
85
|
+
const walk = (dir) => {
|
|
86
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
87
|
+
if (WALK_SKIP.has(entry.name)) continue;
|
|
88
|
+
const full = join(dir, entry.name);
|
|
89
|
+
if (entry.isDirectory()) walk(full);
|
|
90
|
+
else files.push(relative(root, full).replace(/\\/g, '/'));
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
walk(root);
|
|
94
|
+
}
|
|
95
|
+
return files;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const isFile = (p) => existsSync(p) && statSync(p).isFile();
|
|
100
|
+
|
|
101
|
+
function resolveCitedPath(rawPath, root, listFiles) {
|
|
102
|
+
const p = rawPath.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
103
|
+
if (/^[A-Za-z]:\//.test(p) || p.startsWith('/')) {
|
|
104
|
+
return isFile(p) ? { path: p } : { problem: 'missing-file' };
|
|
105
|
+
}
|
|
106
|
+
const direct = join(root, p);
|
|
107
|
+
if (isFile(direct)) return { path: direct };
|
|
108
|
+
const fold = process.platform === 'win32' ? (s) => s.toLowerCase() : (s) => s;
|
|
109
|
+
const suffix = fold(`/${p}`);
|
|
110
|
+
const candidates = listFiles().filter((f) => fold(`/${f}`).endsWith(suffix));
|
|
111
|
+
if (candidates.length === 1) return { path: join(root, candidates[0]) };
|
|
112
|
+
if (candidates.length > 1) return { problem: 'ambiguous-path', candidates };
|
|
113
|
+
return { problem: 'missing-file' };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function quoteAppears(quote, lines, start, end) {
|
|
117
|
+
const from = Math.max(0, start - 1 - LINE_TOLERANCE);
|
|
118
|
+
const to = Math.min(lines.length, end + LINE_TOLERANCE);
|
|
119
|
+
const window = squash(lines.slice(from, to).join(' '));
|
|
120
|
+
let at = 0;
|
|
121
|
+
for (const fragment of quote.split(/\.\.\.|…/).map(squash).filter(Boolean)) {
|
|
122
|
+
const found = window.indexOf(fragment, at);
|
|
123
|
+
if (found === -1) return false;
|
|
124
|
+
at = found + fragment.length;
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function auditCitation(cite, root, listFiles, readCache) {
|
|
130
|
+
const where = resolveCitedPath(cite.path, root, listFiles);
|
|
131
|
+
if (where.problem === 'ambiguous-path') {
|
|
132
|
+
return { kind: 'ambiguous-path', detail: `${where.candidates.length} arquivos casam: ${where.candidates.slice(0, 5).join(', ')}` };
|
|
133
|
+
}
|
|
134
|
+
if (where.problem) return { kind: 'missing-file', detail: `${cite.path} não existe sob ${root}` };
|
|
135
|
+
if (!readCache.has(where.path)) readCache.set(where.path, splitLines(readFileSync(where.path, 'utf8')));
|
|
136
|
+
const lines = readCache.get(where.path);
|
|
137
|
+
cite.resolved = where.path;
|
|
138
|
+
if (cite.start < 1 || cite.end < cite.start || cite.end > lines.length) {
|
|
139
|
+
return { kind: 'line-out-of-range', detail: `o arquivo tem ${lines.length} linhas` };
|
|
140
|
+
}
|
|
141
|
+
const quotes = cite.quote != null ? [cite.quote] : cite.fence;
|
|
142
|
+
if (!quotes) {
|
|
143
|
+
return cite.listing ? null : { kind: 'missing-quote', detail: 'a citação não traz o trecho literal em crase depois dela' };
|
|
144
|
+
}
|
|
145
|
+
const missing = quotes.find((q) => !quoteAppears(q, lines, cite.start, cite.end));
|
|
146
|
+
if (missing != null) {
|
|
147
|
+
return {
|
|
148
|
+
kind: 'quote-mismatch',
|
|
149
|
+
detail: `trecho ausente nas linhas ${Math.max(1, cite.start - LINE_TOLERANCE)}-${Math.min(lines.length, cite.end + LINE_TOLERANCE)}; linha ${cite.start}: ${squash(lines[cite.start - 1])}`,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
cite.verified = true;
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const asMention = (line) => line.replace(SPAN, ' ').replace(/"[^"\n]*"|“[^”\n]*”/g, ' ');
|
|
157
|
+
|
|
158
|
+
const isVerdict = (line) =>
|
|
159
|
+
VERDICTS[0].test(line.replace(SPAN, ' ')) || VERDICTS.slice(1).some((re) => re.test(asMention(line)));
|
|
160
|
+
|
|
161
|
+
const hasSearchCommand = (line) => [...line.matchAll(SPAN)].some((m) => COMMAND_SPAN.test(m[1].trim()));
|
|
162
|
+
|
|
163
|
+
function underSearchCommand(lines, i) {
|
|
164
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
165
|
+
if (hasSearchCommand(lines[j])) return true;
|
|
166
|
+
if (HEADING.test(lines[j])) return false;
|
|
167
|
+
}
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function auditVerdict(line, evidence, inInference) {
|
|
172
|
+
if (!inInference && !INFERENCE_MARKER.test(line)) {
|
|
173
|
+
return { kind: 'verdict-outside-inference', detail: 'veredito de ausência/vida fora da seção de hipóteses (Inferido)' };
|
|
174
|
+
}
|
|
175
|
+
const spans = [...evidence.matchAll(SPAN)].map((m) => m[1].trim());
|
|
176
|
+
if (!spans.some((s) => COMMAND_SPAN.test(s))) {
|
|
177
|
+
return { kind: 'verdict-without-command', detail: 'hipótese de ausência/vida sem o comando de busca usado, em crase' };
|
|
178
|
+
}
|
|
179
|
+
if (!COUNT.test(evidence)) return { kind: 'verdict-without-count', detail: 'hipótese de ausência/vida sem a contagem bruta do comando' };
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function fenceAfter(lines, i) {
|
|
184
|
+
let j = i + 1;
|
|
185
|
+
while (j < lines.length && lines[j].trim() === '') j++;
|
|
186
|
+
if (j >= lines.length || !FENCE.test(lines[j])) return null;
|
|
187
|
+
const body = [];
|
|
188
|
+
for (let k = j + 1; k < lines.length && !FENCE.test(lines[k]); k++) {
|
|
189
|
+
const text = lines[k].replace(/^\s*:?\d+[:\s]\s*/, '').trim();
|
|
190
|
+
if (text) body.push(text);
|
|
191
|
+
}
|
|
192
|
+
return body.length ? body : null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const indentOf = (line) => line.match(/^\s*/)[0].length;
|
|
196
|
+
const BULLET = /^\s*([-*+]|\d+[.)])\s/;
|
|
197
|
+
|
|
198
|
+
function itemStart(lines, i) {
|
|
199
|
+
let start = i;
|
|
200
|
+
for (let j = i - 1; j >= 0 && lines[j].trim() !== '' && !HEADING.test(lines[j]); j--) {
|
|
201
|
+
if (BULLET.test(lines[j]) && indentOf(lines[j]) < indentOf(lines[start])) start = j;
|
|
202
|
+
}
|
|
203
|
+
return start;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function continuationEnd(lines, i) {
|
|
207
|
+
const indent = indentOf(lines[i]);
|
|
208
|
+
let j = i + 1;
|
|
209
|
+
while (j < lines.length && lines[j].trim() !== '' && !HEADING.test(lines[j]) && !FENCE.test(lines[j])) {
|
|
210
|
+
if (indentOf(lines[j]) <= indent && BULLET.test(lines[j])) break;
|
|
211
|
+
j++;
|
|
212
|
+
}
|
|
213
|
+
return j;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function resolveScoutRoot(root, cwd = process.cwd()) {
|
|
217
|
+
return root ? resolve(cwd, root) : (workTreeTop(cwd) ?? cwd);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function checkScoutMap(text, { root }) {
|
|
221
|
+
const lines = splitLines(text);
|
|
222
|
+
const listFiles = makeFileLister(root);
|
|
223
|
+
const readCache = new Map();
|
|
224
|
+
const failures = [];
|
|
225
|
+
const citations = [];
|
|
226
|
+
let verdicts = 0;
|
|
227
|
+
let inFence = false;
|
|
228
|
+
let inferenceLevel = null;
|
|
229
|
+
let subsectionStart = null;
|
|
230
|
+
|
|
231
|
+
const audit = (cite, lineNo) => {
|
|
232
|
+
citations.push(cite);
|
|
233
|
+
const problem = auditCitation(cite, root, listFiles, readCache);
|
|
234
|
+
if (problem) failures.push({ line: lineNo, citation: cite.raw, cite, ...problem });
|
|
235
|
+
};
|
|
236
|
+
const subsectionText = (from) => {
|
|
237
|
+
let to = from + 1;
|
|
238
|
+
while (to < lines.length && !HEADING.test(lines[to])) to++;
|
|
239
|
+
return lines.slice(from, to).join('\n');
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
lines.forEach((line, i) => {
|
|
243
|
+
const lineNo = i + 1;
|
|
244
|
+
if (FENCE.test(line)) { inFence = !inFence; return; }
|
|
245
|
+
if (inFence) {
|
|
246
|
+
const grep = line.trim().match(GREP_LINE);
|
|
247
|
+
if (grep) {
|
|
248
|
+
const n = +(grep[2] ?? grep[3]);
|
|
249
|
+
audit({ raw: `${grep[1]}:${n}`, path: grep[1], start: n, end: n, quote: grep[4].trim() || null }, lineNo);
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const heading = line.match(HEADING);
|
|
254
|
+
if (heading) {
|
|
255
|
+
const level = line.trim().match(/^#+/)[0].length;
|
|
256
|
+
if (INFERENCE_HEADING.test(heading[1])) { inferenceLevel = level; subsectionStart = null; }
|
|
257
|
+
else if (inferenceLevel != null && level > inferenceLevel) subsectionStart = i;
|
|
258
|
+
else { inferenceLevel = null; subsectionStart = null; }
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const cites = citationsOf(line);
|
|
262
|
+
const residue = cites.reduce((rest, c) => rest.split(c.raw).join(' '), line)
|
|
263
|
+
.replace(/[`*_|>()[\]\-+:;,.—–\s]|\d+[.)]/g, '');
|
|
264
|
+
for (const cite of cites) {
|
|
265
|
+
if (cite.quote == null) {
|
|
266
|
+
cite.fence = fenceAfter(lines, i);
|
|
267
|
+
cite.listing = residue === '' && underSearchCommand(lines, i);
|
|
268
|
+
}
|
|
269
|
+
audit(cite, lineNo);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!isVerdict(line)) return;
|
|
273
|
+
verdicts++;
|
|
274
|
+
const evidence = subsectionStart != null
|
|
275
|
+
? subsectionText(subsectionStart)
|
|
276
|
+
: lines.slice(itemStart(lines, i), continuationEnd(lines, itemStart(lines, i))).join('\n');
|
|
277
|
+
const verdict = auditVerdict(line, evidence, inferenceLevel != null);
|
|
278
|
+
if (verdict) failures.push({ line: lineNo, text: squash(line), ...verdict });
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const covered = (c) => citations.some((v) => v.verified && v.resolved === c.resolved && v.start <= c.start && c.end <= v.end);
|
|
282
|
+
const kept = failures.filter((f) => !(f.kind === 'missing-quote' && covered(f.cite)));
|
|
283
|
+
if (citations.length === 0) {
|
|
284
|
+
kept.push({ line: null, kind: 'no-citations', detail: 'o mapa não cita nenhum arquivo:linha — nada nele é verificável' });
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
ok: kept.length === 0,
|
|
288
|
+
root,
|
|
289
|
+
citations: citations.map(({ raw, resolved, quote }) => ({ raw, resolved: resolved ?? null, quote })),
|
|
290
|
+
verdicts,
|
|
291
|
+
failures: kept.map(({ cite, ...f }) => f),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Task classifier — identifies test/validation and infrastructure tasks by title.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const TEST_TASK_PATTERN = /\b(tests?|testing|specs?|coverage|e2e|unit|integration|integra[cç][aã]o|valida[cç][aã]o|cobertura|testes?)\b/i;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Returns true if the task title indicates a testing or validation task.
|
|
9
|
-
*
|
|
10
|
-
* @param {string} taskTitle
|
|
11
|
-
* @returns {boolean}
|
|
12
|
-
*/
|
|
13
|
-
export function isTestTask(taskTitle) {
|
|
14
|
-
if (!taskTitle) return false;
|
|
15
|
-
return TEST_TASK_PATTERN.test(taskTitle);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const INFRA_TASK_PATTERN = /\b(infrastructure|infra|cleanup|migration|migra[cç][aã]o|config|setup|ci\/cd|devops|deploy|docker|packages?|dependencies|tags)\b/i;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Returns true if the task title indicates an infrastructure/cleanup/migration task.
|
|
22
|
-
* Used to skip contract validation and suppress breaking change warnings.
|
|
23
|
-
*
|
|
24
|
-
* @param {string} taskTitle
|
|
25
|
-
* @returns {boolean}
|
|
26
|
-
*/
|
|
27
|
-
export function isInfrastructureTask(taskTitle) {
|
|
28
|
-
if (!taskTitle) return false;
|
|
29
|
-
return INFRA_TASK_PATTERN.test(taskTitle);
|
|
30
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Task classifier — identifies test/validation and infrastructure tasks by title.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const TEST_TASK_PATTERN = /\b(tests?|testing|specs?|coverage|e2e|unit|integration|integra[cç][aã]o|valida[cç][aã]o|cobertura|testes?)\b/i;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Returns true if the task title indicates a testing or validation task.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} taskTitle
|
|
11
|
+
* @returns {boolean}
|
|
12
|
+
*/
|
|
13
|
+
export function isTestTask(taskTitle) {
|
|
14
|
+
if (!taskTitle) return false;
|
|
15
|
+
return TEST_TASK_PATTERN.test(taskTitle);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const INFRA_TASK_PATTERN = /\b(infrastructure|infra|cleanup|migration|migra[cç][aã]o|config|setup|ci\/cd|devops|deploy|docker|packages?|dependencies|tags)\b/i;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns true if the task title indicates an infrastructure/cleanup/migration task.
|
|
22
|
+
* Used to skip contract validation and suppress breaking change warnings.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} taskTitle
|
|
25
|
+
* @returns {boolean}
|
|
26
|
+
*/
|
|
27
|
+
export function isInfrastructureTask(taskTitle) {
|
|
28
|
+
if (!taskTitle) return false;
|
|
29
|
+
return INFRA_TASK_PATTERN.test(taskTitle);
|
|
30
|
+
}
|