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