@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.
Files changed (193) hide show
  1. package/CLAUDE.md +10 -9
  2. package/README.md +10 -10
  3. package/bin/morph-spec.js +10 -13
  4. package/bin/validate.js +379 -379
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +2 -4
  7. package/docs/QUICKSTART.md +6 -6
  8. package/framework/CLAUDE.md +6 -8
  9. package/framework/MORPH.md +39 -45
  10. package/framework/agents.json +8 -9
  11. package/framework/commands/morph-apply.md +1 -3
  12. package/framework/commands/morph-hotfix.md +1 -1
  13. package/framework/commands/morph-proposal.md +2 -2
  14. package/framework/docs/CLI.md +10 -10
  15. package/framework/evals/review-checklist.md +90 -0
  16. package/framework/evals/trace-schema.md +2 -12
  17. package/framework/hooks/README.md +2 -3
  18. package/framework/hooks/claude-code/core/heavy-gate.js +1 -1
  19. package/framework/hooks/claude-code/core/protect-spec-files.js +2 -2
  20. package/framework/hooks/claude-code/core/telemetry-log.js +2 -2
  21. package/framework/hooks/claude-code/core/trace-autogen.js +1 -22
  22. package/framework/hooks/claude-code/core/validate-completion.js +3 -56
  23. package/framework/hooks/claude-code/statusline.sh +11 -11
  24. package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
  25. package/framework/hooks/git/pre-commit/agents.sh +25 -25
  26. package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
  27. package/framework/hooks/git/pre-commit/specs.sh +50 -50
  28. package/framework/hooks/git/pre-push/run-tests.sh +44 -44
  29. package/framework/hooks/shared/build-check.js +1 -1
  30. package/framework/hooks/shared/comment-scanner.js +1 -1
  31. package/framework/hooks/shared/phase-utils.js +0 -5
  32. package/framework/hooks/shared/stdin-reader.js +26 -26
  33. package/framework/phases.json +4 -6
  34. package/framework/rubrics/backend.json +1 -1
  35. package/framework/rubrics/default.json +1 -1
  36. package/framework/rubrics/frontend.json +1 -1
  37. package/framework/rules/morph-workflow.md +6 -6
  38. package/framework/schemas/feature.schema.json +2 -28
  39. package/framework/schemas/tasks.schema.json +6 -2
  40. package/framework/skills/README.md +1 -2
  41. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  42. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  43. package/framework/skills/core/morph-apply/SKILL.md +145 -246
  44. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  45. package/framework/skills/core/morph-hotfix/SKILL.md +5 -3
  46. package/framework/skills/core/morph-implement/SKILL.md +101 -201
  47. package/framework/skills/core/morph-machine/SKILL.md +3 -3
  48. package/framework/skills/core/morph-media/SKILL.md +1 -1
  49. package/framework/skills/core/morph-plan/SKILL.md +171 -287
  50. package/framework/skills/core/morph-proposal/SKILL.md +4 -5
  51. package/framework/skills/core/morph-review/SKILL.md +109 -430
  52. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  53. package/framework/skills/core/morph-standards/SKILL.md +2 -2
  54. package/framework/skills/core/morph-uiux/SKILL.md +2 -3
  55. package/framework/standards/STANDARDS.json +10 -10
  56. package/framework/standards/ai-agents/agent-archetypes.md +1 -1
  57. package/framework/standards/ai-agents/agent-session.md +1 -1
  58. package/framework/standards/ai-agents/agent-spec.md +1 -1
  59. package/framework/standards/ai-agents/batching.md +1 -1
  60. package/framework/standards/ai-agents/context-providers.md +1 -1
  61. package/framework/standards/ai-agents/conversational-agent-with-phases.md +1 -1
  62. package/framework/standards/ai-agents/cost-and-budget.md +1 -1
  63. package/framework/standards/ai-agents/durable-workflows-hitl.md +1 -1
  64. package/framework/standards/ai-agents/guardrails.md +1 -1
  65. package/framework/standards/ai-agents/llm-runtime-defaults.md +1 -1
  66. package/framework/standards/ai-agents/mcp-server.md +1 -1
  67. package/framework/standards/ai-agents/mcp-tools.md +1 -1
  68. package/framework/standards/ai-agents/media-pipeline.md +1 -1
  69. package/framework/standards/ai-agents/media-video.md +1 -1
  70. package/framework/standards/ai-agents/middleware-patterns.md +1 -1
  71. package/framework/standards/ai-agents/modalities-image-gen.md +1 -1
  72. package/framework/standards/ai-agents/modalities-speech-to-text.md +1 -1
  73. package/framework/standards/ai-agents/modalities-text-to-speech.md +1 -1
  74. package/framework/standards/ai-agents/modalities-vision.md +1 -1
  75. package/framework/standards/ai-agents/multi-agent-patterns.md +1 -1
  76. package/framework/standards/ai-agents/observability-patterns.md +3 -3
  77. package/framework/standards/ai-agents/production.md +2 -2
  78. package/framework/standards/ai-agents/prompt-sources.md +1 -1
  79. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  80. package/framework/standards/ai-agents/rag-custom-pgvector.md +1 -1
  81. package/framework/standards/ai-agents/service-tier-flex.md +1 -1
  82. package/framework/standards/ai-agents/setup.md +1 -1
  83. package/framework/standards/ai-agents/structured-output.md +1 -1
  84. package/framework/standards/ai-agents/sweet-spot.md +1 -1
  85. package/framework/standards/ai-agents/testing-ai.md +1 -1
  86. package/framework/standards/ai-agents/tool-adapters.md +1 -1
  87. package/framework/standards/ai-agents/vector-data-extensions.md +1 -1
  88. package/framework/standards/ai-agents/workflows.md +1 -1
  89. package/framework/templates/CLAUDE.md.core.template +1 -1
  90. package/framework/templates/IDE-SNIPPETS.md +262 -262
  91. package/framework/templates/README.md +1 -1
  92. package/framework/templates/REGISTRY.json +0 -81
  93. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  94. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
  95. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  96. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  97. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  98. package/framework/templates/docs/spec.md +19 -205
  99. package/framework/templates/docs/ui-components.md +52 -52
  100. package/framework/templates/docs/ui-design-system.md +76 -76
  101. package/framework/templates/docs/ui-flows.md +167 -167
  102. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  103. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  104. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  105. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  106. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  107. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  108. package/framework/templates/infrastructure/github/README.md +56 -56
  109. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  110. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  111. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  112. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  113. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  114. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  115. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
  116. package/framework/templates/state.template.json +17 -17
  117. package/framework/templates/ui/design-system.css +226 -226
  118. package/package.json +2 -2
  119. package/src/commands/e2e/e2e.js +1 -1
  120. package/src/commands/project/heavy.js +1 -1
  121. package/src/commands/project/status.js +3 -3
  122. package/src/commands/state/approve.js +7 -49
  123. package/src/commands/state/gate-check.js +16 -106
  124. package/src/commands/state/gate-decision.js +1 -1
  125. package/src/commands/state/report-check.js +42 -17
  126. package/src/commands/validation/mutate.js +1 -1
  127. package/src/commands/validation/scout-check.js +28 -0
  128. package/src/commands/validation/validate-feature.js +5 -32
  129. package/src/commands/validation/verify.js +3 -4
  130. package/src/core/paths/output-schema.js +0 -2
  131. package/src/core/state/state-manager.js +1 -2
  132. package/src/core/templates/template-renderer.js +477 -477
  133. package/src/dashboard/app/lib/format.js +5 -0
  134. package/src/dashboard/app/main.jsx +13 -13
  135. package/src/dashboard/app/pages/FeatureDetail.jsx +16 -22
  136. package/src/dashboard/app/pages/Features.jsx +7 -7
  137. package/src/dashboard/app/pages/PhasePipeline.jsx +1 -1
  138. package/src/dashboard/app/pages/Rubrics.jsx +7 -11
  139. package/src/dashboard/scanner/docs-scanner.js +1 -2
  140. package/src/dashboard/scanner/features-scanner.js +3 -4
  141. package/src/dashboard/scanner/rubrics-scanner.js +12 -12
  142. package/src/dashboard/vite.config.js +24 -24
  143. package/src/lib/agents/output-collector.js +0 -1
  144. package/src/lib/detectors/design-system-detector.js +188 -188
  145. package/src/lib/evaluator-report.js +52 -334
  146. package/src/lib/generators/design-system-generator.js +508 -508
  147. package/src/lib/generators/handoff-generator.js +4 -5
  148. package/src/lib/installers/memory-installer.js +2 -2
  149. package/src/lib/mutation/manifest.js +8 -2
  150. package/src/lib/mutation/report.js +5 -3
  151. package/src/lib/mutation/runner.js +9 -7
  152. package/src/lib/risk-detector.js +60 -122
  153. package/src/lib/scout/scout-check.js +293 -0
  154. package/src/lib/tasks/task-classifier.js +30 -30
  155. package/src/lib/tasks/task-parser.js +209 -209
  156. package/src/lib/tasks/tasks-json-parser.js +7 -5
  157. package/src/lib/tasks/test-nodes.js +1 -1
  158. package/src/lib/tasks/test-runner.js +1 -1
  159. package/src/lib/validators/css/css-validator.js +340 -340
  160. package/src/lib/validators/reuse/reuse-validator.js +6 -8
  161. package/src/lib/validators/verify-runner.js +1 -1
  162. package/src/scripts/setup-infra.js +1 -1
  163. package/src/utils/agent-id-mapper.js +11 -11
  164. package/src/utils/agents-installer.js +5 -9
  165. package/src/utils/claude-settings-manager.js +300 -300
  166. package/src/utils/hooks-installer.js +3 -10
  167. package/src/utils/skills-installer.js +1 -1
  168. package/framework/evals/proof-rubrics/README.md +0 -76
  169. package/framework/evals/proof-rubrics/eval-de-modelo.md +0 -80
  170. package/framework/evals/proof-rubrics/fiacao.md +0 -54
  171. package/framework/evals/proof-rubrics/guard-cego.md +0 -52
  172. package/framework/evals/proof-rubrics/media-claims.md +0 -109
  173. package/framework/evals/proof-rubrics/mutation-proof.md +0 -107
  174. package/framework/evals/proof-rubrics/observabilidade-genai.md +0 -119
  175. package/framework/evals/proof-rubrics/verde-nao-e-coberto.md +0 -48
  176. package/framework/evals/rubrics/README.md +0 -45
  177. package/framework/evals/rubrics/architecture.md +0 -103
  178. package/framework/evals/rubrics/code-quality.md +0 -98
  179. package/framework/evals/rubrics/condensed.md +0 -57
  180. package/framework/evals/rubrics/contracts.md +0 -67
  181. package/framework/evals/rubrics/test-coverage.md +0 -99
  182. package/framework/evals/scenarios/dotnet-crud.yaml +0 -42
  183. package/framework/evals/scenarios/nextjs-auth.yaml +0 -38
  184. package/framework/evals/suites/standard.yaml +0 -5
  185. package/framework/hooks/claude-code/core/post-edit-typecheck.js +0 -127
  186. package/framework/skills/core/morph-eval/SKILL.md +0 -64
  187. package/framework/templates/.idea/morph-templates.xml +0 -92
  188. package/framework/templates/.vscode/morph-templates.code-snippets +0 -186
  189. package/framework/templates/docs/clarifications.md +0 -253
  190. package/framework/templates/docs/onboarding.md +0 -122
  191. package/framework/templates/docs/schema-analysis.md +0 -119
  192. package/framework/templates/docs/user-stories.md +0 -34
  193. 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
+ }