@mir-code/specs-platform 1.0.1

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 (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +460 -0
  3. package/assets/specs-usage-statusline.sh +77 -0
  4. package/assets/template/.agents/README.md +123 -0
  5. package/assets/template/.agents/RUNTIME.md +64 -0
  6. package/assets/template/.agents/agents/discovery-agent.md +15 -0
  7. package/assets/template/.agents/agents/drawing-agent.md +15 -0
  8. package/assets/template/.agents/agents/feature-runner.md +15 -0
  9. package/assets/template/.agents/agents/refinement-runner.md +15 -0
  10. package/assets/template/.agents/agents/refinement.md +15 -0
  11. package/assets/template/.agents/project.md +158 -0
  12. package/assets/template/.agents/scripts/check-commit.mjs +70 -0
  13. package/assets/template/.agents/scripts/validate-spec.mjs +345 -0
  14. package/assets/template/.agents/skills/discovery-agent/SKILL.md +80 -0
  15. package/assets/template/.agents/skills/discovery-writing/SKILL.md +76 -0
  16. package/assets/template/.agents/skills/drawing-agent/SKILL.md +85 -0
  17. package/assets/template/.agents/skills/drawing-writing/SKILL.md +100 -0
  18. package/assets/template/.agents/skills/execution-protocol/SKILL.md +110 -0
  19. package/assets/template/.agents/skills/execution-protocol/references/git.md +104 -0
  20. package/assets/template/.agents/skills/execution-protocol/references/relatorio-halt.md +103 -0
  21. package/assets/template/.agents/skills/feature-runner/SKILL.md +81 -0
  22. package/assets/template/.agents/skills/feature-runner/references/divergencias.md +38 -0
  23. package/assets/template/.agents/skills/feature-runner/references/implementar.md +80 -0
  24. package/assets/template/.agents/skills/feature-runner/references/localizar.md +46 -0
  25. package/assets/template/.agents/skills/mermaid-diagramming/SKILL.md +198 -0
  26. package/assets/template/.agents/skills/prototype-check/SKILL.md +90 -0
  27. package/assets/template/.agents/skills/refinement/SKILL.md +136 -0
  28. package/assets/template/.agents/skills/refinement/references/estrutura.md +27 -0
  29. package/assets/template/.agents/skills/refinement/references/mapear-terreno.md +46 -0
  30. package/assets/template/.agents/skills/refinement/references/validar-renderizacao.md +29 -0
  31. package/assets/template/.agents/skills/refinement-runner/SKILL.md +145 -0
  32. package/assets/template/.agents/skills/requirements-closure/SKILL.md +236 -0
  33. package/assets/template/.agents/skills/spec-writing/SKILL.md +258 -0
  34. package/assets/template/.agents/skills/test-strategy/SKILL.md +176 -0
  35. package/assets/template/.agents/skills/verification/SKILL.md +143 -0
  36. package/assets/template/.specs/config.json +39 -0
  37. package/assets/template/.specs/discoveries/meta.json +4 -0
  38. package/assets/template/.specs/drawings/meta.json +4 -0
  39. package/assets/template/.specs/specs/_templates/task-backend.md +75 -0
  40. package/assets/template/.specs/specs/_templates/task-frontend.md +89 -0
  41. package/assets/template/.specs/specs/_templates/task-integracao.md +72 -0
  42. package/assets/template/.specs/specs/exemplo/feat-exemplo-primeira-task.md +82 -0
  43. package/assets/template/.specs/specs/exemplo/meta.json +8 -0
  44. package/assets/template/.specs/specs/exemplo/overview.md +45 -0
  45. package/assets/template/.specs/specs/meta.json +4 -0
  46. package/assets/template/SPECS.md +213 -0
  47. package/dist/chunk-SOOETS3N.js +3490 -0
  48. package/dist/chunk-SOOETS3N.js.map +1 -0
  49. package/dist/cli.js +15 -0
  50. package/dist/cli.js.map +1 -0
  51. package/dist/index.d.ts +69 -0
  52. package/dist/index.js +16 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/ui/assets/abnfDiagram-VCTEODGH-DYXtkHNq.js +2 -0
  55. package/dist/ui/assets/abnfDiagram-VCTEODGH-DYXtkHNq.js.map +1 -0
  56. package/dist/ui/assets/arc-CJs0gz4E.js +2 -0
  57. package/dist/ui/assets/arc-CJs0gz4E.js.map +1 -0
  58. package/dist/ui/assets/architectureDiagram-5GKGNRK7-Br7ZNRfe.js +37 -0
  59. package/dist/ui/assets/architectureDiagram-5GKGNRK7-Br7ZNRfe.js.map +1 -0
  60. package/dist/ui/assets/blockDiagram-I7D4REHJ-172V7R5o.js +130 -0
  61. package/dist/ui/assets/blockDiagram-I7D4REHJ-172V7R5o.js.map +1 -0
  62. package/dist/ui/assets/c4Diagram-7LVT6UL2-B4AYFhRs.js +39 -0
  63. package/dist/ui/assets/c4Diagram-7LVT6UL2-B4AYFhRs.js.map +1 -0
  64. package/dist/ui/assets/channel-BfWO3B41.js +2 -0
  65. package/dist/ui/assets/channel-BfWO3B41.js.map +1 -0
  66. package/dist/ui/assets/chunk-2Q5K7J3B-B-u7gtZN.js +2 -0
  67. package/dist/ui/assets/chunk-2Q5K7J3B-B-u7gtZN.js.map +1 -0
  68. package/dist/ui/assets/chunk-5VM5RSS4-CjFDq_cq.js +16 -0
  69. package/dist/ui/assets/chunk-5VM5RSS4-CjFDq_cq.js.map +1 -0
  70. package/dist/ui/assets/chunk-F27PBJKO-BDYeB7B8.js +2 -0
  71. package/dist/ui/assets/chunk-F27PBJKO-BDYeB7B8.js.map +1 -0
  72. package/dist/ui/assets/chunk-IMKFNOWR-y2kXPnac.js +232 -0
  73. package/dist/ui/assets/chunk-IMKFNOWR-y2kXPnac.js.map +1 -0
  74. package/dist/ui/assets/chunk-JWPE2WC7-DyCryvDN.js +2 -0
  75. package/dist/ui/assets/chunk-JWPE2WC7-DyCryvDN.js.map +1 -0
  76. package/dist/ui/assets/chunk-POPQ4Y6H-DeaxKHvR.js +2 -0
  77. package/dist/ui/assets/chunk-POPQ4Y6H-DeaxKHvR.js.map +1 -0
  78. package/dist/ui/assets/chunk-SVP7TREG-CmcZEncL.js +89 -0
  79. package/dist/ui/assets/chunk-SVP7TREG-CmcZEncL.js.map +1 -0
  80. package/dist/ui/assets/chunk-TICWLB2K-Df1RRXWR.js +207 -0
  81. package/dist/ui/assets/chunk-TICWLB2K-Df1RRXWR.js.map +1 -0
  82. package/dist/ui/assets/chunk-XXDRQBXY-ClTat8Q0.js +2 -0
  83. package/dist/ui/assets/chunk-XXDRQBXY-ClTat8Q0.js.map +1 -0
  84. package/dist/ui/assets/classDiagram-ZZMXUADV-YTUiu8rF.js +2 -0
  85. package/dist/ui/assets/classDiagram-ZZMXUADV-YTUiu8rF.js.map +1 -0
  86. package/dist/ui/assets/classDiagram-v2-VYDZK3BY-YTUiu8rF.js +2 -0
  87. package/dist/ui/assets/classDiagram-v2-VYDZK3BY-YTUiu8rF.js.map +1 -0
  88. package/dist/ui/assets/cose-bilkent-JH36ORCC-QvqQLoy5.js +2 -0
  89. package/dist/ui/assets/cose-bilkent-JH36ORCC-QvqQLoy5.js.map +1 -0
  90. package/dist/ui/assets/cynefin-OW5HDTMX-BY6w3pAQ.js +179 -0
  91. package/dist/ui/assets/cynefin-OW5HDTMX-BY6w3pAQ.js.map +1 -0
  92. package/dist/ui/assets/cynefinDiagram-5FMLGOSQ-luPP4ha1.js +63 -0
  93. package/dist/ui/assets/cynefinDiagram-5FMLGOSQ-luPP4ha1.js.map +1 -0
  94. package/dist/ui/assets/cytoscape.esm-Ix0LnXOy.js +332 -0
  95. package/dist/ui/assets/cytoscape.esm-Ix0LnXOy.js.map +1 -0
  96. package/dist/ui/assets/dagre-GXQ25YYZ-DhpPwKqq.js +5 -0
  97. package/dist/ui/assets/dagre-GXQ25YYZ-DhpPwKqq.js.map +1 -0
  98. package/dist/ui/assets/defaultLocale-DX6XiGOO.js +2 -0
  99. package/dist/ui/assets/defaultLocale-DX6XiGOO.js.map +1 -0
  100. package/dist/ui/assets/diagram-S7CK7UJ4-B9wvQM0w.js +31 -0
  101. package/dist/ui/assets/diagram-S7CK7UJ4-B9wvQM0w.js.map +1 -0
  102. package/dist/ui/assets/diagram-UQ7AKVKN-gm1M_iZS.js +42 -0
  103. package/dist/ui/assets/diagram-UQ7AKVKN-gm1M_iZS.js.map +1 -0
  104. package/dist/ui/assets/diagram-VSXAHHWV-DkEB6nEx.js +4 -0
  105. package/dist/ui/assets/diagram-VSXAHHWV-DkEB6nEx.js.map +1 -0
  106. package/dist/ui/assets/diagram-VX7I27RA-Ce2F1bHi.js +25 -0
  107. package/dist/ui/assets/diagram-VX7I27RA-Ce2F1bHi.js.map +1 -0
  108. package/dist/ui/assets/diagram-Z3DM3KII-B5ArCh-E.js +25 -0
  109. package/dist/ui/assets/diagram-Z3DM3KII-B5ArCh-E.js.map +1 -0
  110. package/dist/ui/assets/ebnfDiagram-PWID7BFC-DgBM80MD.js +2 -0
  111. package/dist/ui/assets/ebnfDiagram-PWID7BFC-DgBM80MD.js.map +1 -0
  112. package/dist/ui/assets/erDiagram-RLTQ6QDP-C161af8G.js +100 -0
  113. package/dist/ui/assets/erDiagram-RLTQ6QDP-C161af8G.js.map +1 -0
  114. package/dist/ui/assets/flowDiagram-HODETNUW-B8N_WyW5.js +169 -0
  115. package/dist/ui/assets/flowDiagram-HODETNUW-B8N_WyW5.js.map +1 -0
  116. package/dist/ui/assets/ganttDiagram-EL5Y4UJY-CD2RJpTb.js +293 -0
  117. package/dist/ui/assets/ganttDiagram-EL5Y4UJY-CD2RJpTb.js.map +1 -0
  118. package/dist/ui/assets/gitGraphDiagram-WWUBYQGX-BrDAzeFT.js +107 -0
  119. package/dist/ui/assets/gitGraphDiagram-WWUBYQGX-BrDAzeFT.js.map +1 -0
  120. package/dist/ui/assets/index-CVWRdirI.css +32 -0
  121. package/dist/ui/assets/index-DoR97Zqf.js +408 -0
  122. package/dist/ui/assets/index-DoR97Zqf.js.map +1 -0
  123. package/dist/ui/assets/infoDiagram-27XIBGKW-HYGaMzrA.js +3 -0
  124. package/dist/ui/assets/infoDiagram-27XIBGKW-HYGaMzrA.js.map +1 -0
  125. package/dist/ui/assets/init-Gi6I4Gst.js +2 -0
  126. package/dist/ui/assets/init-Gi6I4Gst.js.map +1 -0
  127. package/dist/ui/assets/ishikawaDiagram-5VMMS53U-DXTLlBiw.js +71 -0
  128. package/dist/ui/assets/ishikawaDiagram-5VMMS53U-DXTLlBiw.js.map +1 -0
  129. package/dist/ui/assets/journeyDiagram-3NMN7TZE-DSJ8QN1S.js +140 -0
  130. package/dist/ui/assets/journeyDiagram-3NMN7TZE-DSJ8QN1S.js.map +1 -0
  131. package/dist/ui/assets/kanban-definition-UXKFOSKX-Bm5iz0k3.js +90 -0
  132. package/dist/ui/assets/kanban-definition-UXKFOSKX-Bm5iz0k3.js.map +1 -0
  133. package/dist/ui/assets/katex-C5jXJg4s.js +258 -0
  134. package/dist/ui/assets/katex-C5jXJg4s.js.map +1 -0
  135. package/dist/ui/assets/layout-DfJgW6eG.js +2 -0
  136. package/dist/ui/assets/layout-DfJgW6eG.js.map +1 -0
  137. package/dist/ui/assets/linear-B_kyVotV.js +2 -0
  138. package/dist/ui/assets/linear-B_kyVotV.js.map +1 -0
  139. package/dist/ui/assets/mermaid-block-CUJSBQFx.js +2 -0
  140. package/dist/ui/assets/mermaid-block-CUJSBQFx.js.map +1 -0
  141. package/dist/ui/assets/mermaid.core-ExHG1WnM.js +313 -0
  142. package/dist/ui/assets/mermaid.core-ExHG1WnM.js.map +1 -0
  143. package/dist/ui/assets/mindmap-definition-YA3MSWOX-B1Hh9dQz.js +97 -0
  144. package/dist/ui/assets/mindmap-definition-YA3MSWOX-B1Hh9dQz.js.map +1 -0
  145. package/dist/ui/assets/ordinal-Cboi1Yqb.js +2 -0
  146. package/dist/ui/assets/ordinal-Cboi1Yqb.js.map +1 -0
  147. package/dist/ui/assets/pegDiagram-XKGWAZYB-x242yOd7.js +2 -0
  148. package/dist/ui/assets/pegDiagram-XKGWAZYB-x242yOd7.js.map +1 -0
  149. package/dist/ui/assets/pieDiagram-E7YTZNPT-Dl-YP7Xs.js +40 -0
  150. package/dist/ui/assets/pieDiagram-E7YTZNPT-Dl-YP7Xs.js.map +1 -0
  151. package/dist/ui/assets/quadrantDiagram-AXDQQJYC-CMJ920V9.js +8 -0
  152. package/dist/ui/assets/quadrantDiagram-AXDQQJYC-CMJ920V9.js.map +1 -0
  153. package/dist/ui/assets/railroadDiagram-O6MQD6OU-1kV83y5t.js +2 -0
  154. package/dist/ui/assets/railroadDiagram-O6MQD6OU-1kV83y5t.js.map +1 -0
  155. package/dist/ui/assets/requirementDiagram-BXWQKSXE-BFd8ZZD3.js +85 -0
  156. package/dist/ui/assets/requirementDiagram-BXWQKSXE-BFd8ZZD3.js.map +1 -0
  157. package/dist/ui/assets/sankeyDiagram-P5KCCOFB-C0LZevnf.js +41 -0
  158. package/dist/ui/assets/sankeyDiagram-P5KCCOFB-C0LZevnf.js.map +1 -0
  159. package/dist/ui/assets/sequenceDiagram-WJ2MYXX4-BasIXLJ0.js +163 -0
  160. package/dist/ui/assets/sequenceDiagram-WJ2MYXX4-BasIXLJ0.js.map +1 -0
  161. package/dist/ui/assets/sizeCapture-INFHLROL-qAtnqB7N.js +2 -0
  162. package/dist/ui/assets/sizeCapture-INFHLROL-qAtnqB7N.js.map +1 -0
  163. package/dist/ui/assets/stateDiagram-D77RDMKH-CllWDWZP.js +2 -0
  164. package/dist/ui/assets/stateDiagram-D77RDMKH-CllWDWZP.js.map +1 -0
  165. package/dist/ui/assets/stateDiagram-v2-MP3YSRHH-Dwntf8Tk.js +2 -0
  166. package/dist/ui/assets/stateDiagram-v2-MP3YSRHH-Dwntf8Tk.js.map +1 -0
  167. package/dist/ui/assets/swimlanes-42K2YHIH-Bgt0KslH.js +2 -0
  168. package/dist/ui/assets/swimlanes-42K2YHIH-Bgt0KslH.js.map +1 -0
  169. package/dist/ui/assets/swimlanesDiagram-VR7AAH4N-C9abicdw.js +9 -0
  170. package/dist/ui/assets/swimlanesDiagram-VR7AAH4N-C9abicdw.js.map +1 -0
  171. package/dist/ui/assets/timeline-definition-24CTP7MA-BYO2h9Qr.js +121 -0
  172. package/dist/ui/assets/timeline-definition-24CTP7MA-BYO2h9Qr.js.map +1 -0
  173. package/dist/ui/assets/vennDiagram-4TSXK5OY-ZzyiwIUi.js +35 -0
  174. package/dist/ui/assets/vennDiagram-4TSXK5OY-ZzyiwIUi.js.map +1 -0
  175. package/dist/ui/assets/wardleyDiagram-VM6X3IG4-DfjKCK0N.js +79 -0
  176. package/dist/ui/assets/wardleyDiagram-VM6X3IG4-DfjKCK0N.js.map +1 -0
  177. package/dist/ui/assets/xychartDiagram-S5SC5T6Z-Z7daOfmM.js +8 -0
  178. package/dist/ui/assets/xychartDiagram-S5SC5T6Z-Z7daOfmM.js.map +1 -0
  179. package/dist/ui/favicon.svg +6 -0
  180. package/dist/ui/index.html +21 -0
  181. package/package.json +70 -0
  182. package/scripts/copy-ui.mjs +21 -0
  183. package/scripts/fix-node-pty-perms.cjs +73 -0
@@ -0,0 +1,345 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Validador estrutural das specs de `.specs/specs/`.
4
+ *
5
+ * Checa por código o que hoje só é lembrado: frontmatter, ordem das seções, diagrama Mermaid,
6
+ * blocos <details>, critérios de aceite em EARS, IDs de requisito, tabela de premissas e
7
+ * consistência com o `meta.json` da feature.
8
+ *
9
+ * Uso:
10
+ * node .agents/scripts/validate-spec.mjs <arquivo.md|pasta-da-feature> [--strict]
11
+ * node .agents/scripts/validate-spec.mjs --all [--strict]
12
+ *
13
+ * `--strict` promove os avisos a erros. Saída != 0 significa: pare e corrija.
14
+ */
15
+
16
+ import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs'
17
+ import { join, basename, dirname, resolve } from 'node:path'
18
+
19
+ const STATUSES = ['pending', 'in-progress', 'completed', 'blocked']
20
+ const TASK_SECTIONS = [
21
+ '## Contexto da alteração',
22
+ '## Visão Técnica',
23
+ '## Escopo',
24
+ '## Code Review Checklist',
25
+ ]
26
+ const CLASS_DEFS = ['classDef changed', 'classDef added', 'classDef removed', 'classDef untouched']
27
+ const EARS_KEYWORDS = /(^|\s)(QUANDO|ENQUANTO|ONDE|SE)\s/
28
+ const REQ_ID = /^\*\*[A-Z][A-Z0-9]*(-[A-Z0-9]+)*-\d{2}\*\*/
29
+
30
+ // Heurística: pistas de que a linha descreve estrutura interna em vez de comportamento
31
+ // observável. Só gera aviso — "a estrutura é a entrega" é exceção legítima (ver a skill
32
+ // `requirements-closure`, seção "O que é critério de aceite, e o que não é").
33
+ // Extensões que contam como "path de código" neste repositório.
34
+ // Ajuste aqui ao portar — o valor espelha a seção "Documentação e processo" de .agents/project.md.
35
+ const CODE_EXTENSIONS = ['ts', 'tsx', 'prisma', 'json', 'http']
36
+ const CODE_PATH_RE = new RegExp('`[^`]*\\/[^`]*\\.(' + CODE_EXTENSIONS.join('|') + ')`')
37
+
38
+ const STRUCTURAL_HINTS = [
39
+ { re: CODE_PATH_RE, why: 'cita path de arquivo' },
40
+ { re: /\b(DTO|Schema|Interface|Repository|UseCase|Adapter|Controller)\b/, why: 'nomeia artefato interno' },
41
+ { re: /`\.(strict|transform|refine|parse)\(\)`/, why: 'cita chamada de implementação' },
42
+ { re: /\b(private readonly|setter|getter|índice novo|index novo|backfill)\b/i, why: 'descreve decisão de estrutura' },
43
+ ]
44
+ /** Verbos que indicam comportamento observável — se houver um, a linha provavelmente é critério. */
45
+ const BEHAVIOR_VERBS =
46
+ /\b(responde[r]?|retorna[r]?|exibe|exibir|persiste|persistir|grava[r]?|rejeita[r]?|bloqueia[r]?|redireciona[r]?|mantém|manter|cobra[r]?|envia[r]?|registra[r]?)\b/i
47
+
48
+ const args = process.argv.slice(2)
49
+ const strict = args.includes('--strict')
50
+ const targets = args.filter((a) => !a.startsWith('--'))
51
+ const all = args.includes('--all')
52
+
53
+ /** Um achado de validação, ligado à linha que o originou. */
54
+ class Report {
55
+ constructor(file) {
56
+ this.file = file
57
+ this.errors = []
58
+ this.warnings = []
59
+ }
60
+ err(line, msg) {
61
+ this.errors.push({ line, msg })
62
+ }
63
+ warn(line, msg) {
64
+ ;(strict ? this.errors : this.warnings).push({ line, msg })
65
+ }
66
+ }
67
+
68
+ function parseFrontmatter(lines, r) {
69
+ if (lines[0] !== '---') {
70
+ r.err(1, 'arquivo não começa com frontmatter `---`')
71
+ return { data: {}, bodyStart: 0 }
72
+ }
73
+ const end = lines.indexOf('---', 1)
74
+ if (end === -1) {
75
+ r.err(1, 'frontmatter sem fechamento `---`')
76
+ return { data: {}, bodyStart: 0 }
77
+ }
78
+ const data = {}
79
+ for (let i = 1; i < end; i++) {
80
+ const m = lines[i].match(/^(\w+):\s*(.*)$/)
81
+ if (m) data[m[1]] = m[2].replace(/^["']|["']$/g, '').trim()
82
+ }
83
+ return { data, bodyStart: end + 1 }
84
+ }
85
+
86
+ function checkFrontmatter(data, r, isOverview) {
87
+ if (!data.title) r.err(2, 'frontmatter sem `title`')
88
+ if (!data.description) r.err(2, 'frontmatter sem `description`')
89
+ if (isOverview) {
90
+ if (data.status) r.err(2, '`overview.md` não pode ter `status` — não é uma task')
91
+ return
92
+ }
93
+ if (!data.status) r.err(2, 'frontmatter sem `status`')
94
+ else if (!STATUSES.includes(data.status))
95
+ r.err(2, `status \`${data.status}\` inválido — use ${STATUSES.join(' | ')}`)
96
+ }
97
+
98
+ function checkNoH1(lines, bodyStart, r) {
99
+ let inFence = false
100
+ for (let i = bodyStart; i < lines.length; i++) {
101
+ if (/^\s*```/.test(lines[i])) inFence = !inFence
102
+ if (!inFence && /^# \S/.test(lines[i]))
103
+ r.err(i + 1, 'H1 no corpo — o `title` do frontmatter já vira o <h1> da página')
104
+ }
105
+ }
106
+
107
+ function checkSections(lines, r, isOverview) {
108
+ const headings = []
109
+ let inFence = false
110
+ lines.forEach((l, i) => {
111
+ if (/^\s*```/.test(l)) inFence = !inFence
112
+ if (!inFence && /^## /.test(l)) headings.push({ text: l.trim(), line: i + 1 })
113
+ })
114
+ const texts = headings.map((h) => h.text)
115
+
116
+ if (isOverview) {
117
+ for (const s of ['## O que será feito', '## Visão Técnica'])
118
+ if (!texts.includes(s)) r.err(0, `overview sem a seção \`${s}\``)
119
+ return
120
+ }
121
+
122
+ for (const s of TASK_SECTIONS) if (!texts.includes(s)) r.err(0, `falta a seção \`${s}\``)
123
+
124
+ // Ordem relativa das seções obrigatórias (ignora `## Execuções`, que vem no fim).
125
+ const idx = TASK_SECTIONS.map((s) => texts.indexOf(s)).filter((i) => i !== -1)
126
+ for (let i = 1; i < idx.length; i++)
127
+ if (idx[i] < idx[i - 1]) {
128
+ r.err(0, `seções fora de ordem — a ordem fixa é: ${TASK_SECTIONS.join(' → ')}`)
129
+ break
130
+ }
131
+
132
+ for (const banned of ['## Critérios de Aceite', '## Detalhes Técnicos', '## Testes', '## Dependências'])
133
+ if (texts.includes(banned))
134
+ r.err(
135
+ headings.find((h) => h.text === banned).line,
136
+ `\`${banned}\` não existe como seção de primeiro nível — vive dentro de um bloco <details>`,
137
+ )
138
+
139
+ const body = lines.join('\n')
140
+ if (!body.includes('**Depende de:**'))
141
+ r.err(0, 'Contexto da alteração não termina com a lista `**Depende de:**`')
142
+ if (!texts.includes('## Escopo') || !body.includes('### Fora de escopo'))
143
+ r.err(0, 'falta `### Fora de escopo` dentro de `## Escopo`')
144
+ if (!body.includes('### O que muda, em resumo'))
145
+ r.err(0, 'falta `### O que muda, em resumo` na Visão Técnica')
146
+ if (!body.includes('### Detalhamento técnico'))
147
+ r.err(0, 'falta `### Detalhamento técnico` na Visão Técnica')
148
+ }
149
+
150
+ function checkMermaid(lines, r) {
151
+ const start = lines.findIndex((l) => /^\s*```mermaid/.test(l))
152
+ if (start === -1) {
153
+ r.err(0, 'sem diagrama Mermaid na Visão Técnica (obrigatório)')
154
+ return
155
+ }
156
+ const end = lines.findIndex((l, i) => i > start && /^\s*```\s*$/.test(l))
157
+ const block = lines.slice(start, end === -1 ? lines.length : end).join('\n')
158
+ for (const cd of CLASS_DEFS)
159
+ if (!block.includes(cd)) r.err(start + 1, `diagrama sem \`${cd}\``)
160
+ const after = lines.slice(end + 1, end + 4).join('\n')
161
+ if (!after.includes('🟡') || !after.includes('⚪'))
162
+ r.warn(end + 2, 'sem a linha de legenda `> 🟡 alterado · 🟢 adicionado · 🔴 removido · ⚪ inalterado (contexto)`')
163
+ }
164
+
165
+ /** Percorre os blocos <details>, validando forma e conteúdo de cada um. */
166
+ function checkDetails(lines, r) {
167
+ const blocks = []
168
+ let open = null
169
+ lines.forEach((l, i) => {
170
+ const t = l.trim()
171
+ if (t === '<details>') {
172
+ if (open !== null) r.err(i + 1, '<details> aninhado ou anterior não fechado')
173
+ open = { start: i, summary: null, summaryLine: null }
174
+ } else if (t.startsWith('<summary>') && open) {
175
+ open.summary = t.replace(/<\/?summary>/g, '').trim()
176
+ open.summaryLine = i
177
+ if (lines[i + 1]?.trim() !== '')
178
+ r.err(i + 2, 'falta a linha em branco depois de `</summary>` — o markdown interno não é interpretado')
179
+ } else if (t === '</details>' && open) {
180
+ if (lines[i - 1]?.trim() !== '')
181
+ r.err(i, 'falta a linha em branco antes de `</details>`')
182
+ open.end = i
183
+ blocks.push(open)
184
+ open = null
185
+ } else if (open && /^\s*#{1,6} /.test(l)) {
186
+ r.err(i + 1, 'heading markdown dentro de <details> — use **negrito** para subtítulo interno')
187
+ }
188
+ })
189
+ if (open !== null) r.err(open.start + 1, '<details> sem `</details>`')
190
+ return blocks
191
+ }
192
+
193
+ function checkCriteria(lines, blocks, r) {
194
+ let total = 0
195
+ for (const b of blocks) {
196
+ const summary = (b.summary || '').toLowerCase()
197
+ if (summary.startsWith('dd/') || /^\d{2}\/\d{2}\/\d{4}/.test(b.summary || '')) continue // Execuções
198
+ const isAssumptions = summary.includes('premissa')
199
+ const isTests = summary.includes('teste')
200
+
201
+ for (let i = b.start; i < b.end; i++) {
202
+ const line = lines[i]
203
+ const m = line.match(/^\s*- \[[ x]\]\s+(.*)$/)
204
+ if (!m) continue
205
+ total++
206
+ const text = m[1].trim()
207
+ if (text === '...' || text === '') continue // template não preenchido: ignorado aqui
208
+ const withoutId = text.replace(REQ_ID, '').trim()
209
+ // Só acusa quando a linha claramente tenta ser um ID (`**ALGO-12**`), não em negrito comum.
210
+ if (/^\*\*[A-Z][A-Z0-9-]*\d+\*\*/.test(text) && !REQ_ID.test(text))
211
+ r.warn(i + 1, 'ID de requisito fora do formato `**CATEGORIA-NN**`')
212
+ if (!isTests && !/\bDEVE\b/.test(withoutId))
213
+ r.warn(i + 1, 'critério sem `DEVE` — não está em EARS (QUANDO/ENQUANTO/ONDE/SE ... DEVE)')
214
+ else if (!isTests && !EARS_KEYWORDS.test(withoutId) && !/^O sistema DEVE/i.test(withoutId))
215
+ r.warn(i + 1, 'critério com `DEVE` mas sem padrão EARS reconhecível (QUANDO/ENQUANTO/ONDE/SE ou ubíquo)')
216
+
217
+ // Estrutura interna sem verbo de comportamento: provavelmente é nota de implementação,
218
+ // que mora na Code Review Checklist. Um único aviso por linha, com a pista encontrada.
219
+ if (!isTests && !BEHAVIOR_VERBS.test(withoutId)) {
220
+ const hint = STRUCTURAL_HINTS.find((h) => h.re.test(withoutId))
221
+ if (hint)
222
+ r.warn(
223
+ i + 1,
224
+ `parece nota de implementação (${hint.why}) — se não quebra ao refatorar sem mudar comportamento, mova para a Code Review Checklist`,
225
+ )
226
+ }
227
+ }
228
+
229
+ if (isAssumptions) {
230
+ const body = lines.slice(b.start, b.end)
231
+ if (!body.some((l) => l.includes('**Questões em aberto:**')))
232
+ r.err(b.summaryLine + 1, 'bloco de premissas sem a linha `**Questões em aberto:**`')
233
+ for (let i = 0; i < body.length; i++) {
234
+ const l = body[i].trim()
235
+ if (!l.startsWith('|') || /^\|[\s|:-]+\|$/.test(l)) continue
236
+ const cells = l.split('|').slice(1, -1).map((c) => c.trim())
237
+ if (cells.length >= 4 && !/premissa|decis/i.test(cells[0]) && cells.some((c) => c === ''))
238
+ r.err(b.start + i + 1, 'linha de premissa com célula vazia — default e racional são obrigatórios')
239
+ }
240
+ }
241
+
242
+ if (isTests) {
243
+ const body = lines.slice(b.start, b.end).join('\n')
244
+ if (!/\*\*Tipo:\*\*/.test(body) || !/\*\*Gate:\*\*/.test(body))
245
+ r.warn(b.summaryLine + 1, 'bloco de Testes sem `**Tipo:**` e `**Gate:**` (ver skill `test-strategy`)')
246
+ }
247
+ }
248
+ if (total === 0) r.err(0, 'nenhum critério de aceite (`- [ ]`) encontrado nos blocos <details>')
249
+ return blocks.map((b) => (b.summary || '').toLowerCase())
250
+ }
251
+
252
+ function checkMetaJson(file, r) {
253
+ const dir = dirname(resolve(file))
254
+ const metaPath = join(dir, 'meta.json')
255
+ const name = basename(file, '.md')
256
+ if (name === 'overview') return
257
+ if (!existsSync(metaPath)) {
258
+ r.err(0, `feature sem \`meta.json\` em ${dir}`)
259
+ return
260
+ }
261
+ let meta
262
+ try {
263
+ meta = JSON.parse(readFileSync(metaPath, 'utf8'))
264
+ } catch (e) {
265
+ r.err(0, `meta.json inválido: ${e.message}`)
266
+ return
267
+ }
268
+ if (!Array.isArray(meta.pages)) {
269
+ r.err(0, 'meta.json sem array `pages`')
270
+ return
271
+ }
272
+ if (!meta.pages.includes(name))
273
+ r.err(0, `\`${name}\` não está em \`pages\` do meta.json — a página não aparece na sidebar`)
274
+ const slug = basename(dir)
275
+ if (!name.startsWith(`feat-${slug}-`))
276
+ r.warn(0, `nome do arquivo fora do padrão \`feat-${slug}-<task>.md\``)
277
+ }
278
+
279
+ function validateFile(file) {
280
+ const r = new Report(file)
281
+ const raw = readFileSync(file, 'utf8')
282
+ const lines = raw.split('\n')
283
+ const isOverview = basename(file) === 'overview.md'
284
+
285
+ const { data, bodyStart } = parseFrontmatter(lines, r)
286
+ checkFrontmatter(data, r, isOverview)
287
+ checkNoH1(lines, bodyStart, r)
288
+ checkSections(lines, r, isOverview)
289
+ checkMermaid(lines, r)
290
+ const blocks = checkDetails(lines, r)
291
+ if (!isOverview) {
292
+ const summaries = checkCriteria(lines, blocks, r)
293
+ if (!summaries.some((s) => s.includes('teste')))
294
+ r.warn(0, 'sem bloco <details> de Testes')
295
+ if (!summaries.some((s) => s.includes('premissa')))
296
+ r.warn(0, 'sem bloco <details> de Premissas e questões em aberto (ver skill `requirements-closure`)')
297
+ checkMetaJson(file, r)
298
+ }
299
+ return r
300
+ }
301
+
302
+ function collect(target) {
303
+ const p = resolve(target)
304
+ if (!existsSync(p)) {
305
+ console.error(`caminho não encontrado: ${target}`)
306
+ process.exit(2)
307
+ }
308
+ if (statSync(p).isFile()) return [p]
309
+ return readdirSync(p)
310
+ .filter((f) => f.endsWith('.md') && f !== 'CLAUDE.md')
311
+ .map((f) => join(p, f))
312
+ }
313
+
314
+ const specsRoot = resolve('.specs/specs')
315
+ let files = []
316
+ if (all) {
317
+ for (const d of readdirSync(specsRoot)) {
318
+ const full = join(specsRoot, d)
319
+ if (d.startsWith('_') || !statSync(full).isDirectory()) continue
320
+ files.push(...collect(full))
321
+ }
322
+ } else if (targets.length === 0) {
323
+ console.error('uso: node .agents/scripts/validate-spec.mjs <arquivo|pasta> [--strict] | --all')
324
+ process.exit(2)
325
+ } else {
326
+ for (const t of targets) files.push(...collect(t))
327
+ }
328
+
329
+ let errors = 0
330
+ let warnings = 0
331
+ for (const f of files) {
332
+ const r = validateFile(f)
333
+ errors += r.errors.length
334
+ warnings += r.warnings.length
335
+ if (r.errors.length || r.warnings.length) {
336
+ console.log(`\n${f.replace(`${process.cwd()}/`, '')}`)
337
+ for (const e of r.errors) console.log(` ERRO ${e.line ? `L${e.line}: ` : ''}${e.msg}`)
338
+ for (const w of r.warnings) console.log(` aviso ${w.line ? `L${w.line}: ` : ''}${w.msg}`)
339
+ }
340
+ }
341
+
342
+ console.log(
343
+ `\n${files.length} arquivo(s) · ${errors} erro(s) · ${warnings} aviso(s)${strict ? ' (--strict)' : ''}`,
344
+ )
345
+ process.exit(errors > 0 ? 1 : 0)
@@ -0,0 +1,80 @@
1
+ ---
2
+ name: discovery-agent
3
+ description: Transforma um problema em aberto, dúvida ou hipótese num documento fundamentado em `.specs/discoveries/` — RFC, spike, ADR ou note — sem alterar código de produção. Carregue ao analisar, pesquisar, explorar, avaliar alternativas, levantar opções, fazer um spike, escrever uma RFC ou registrar uma ADR.
4
+ ---
5
+
6
+ # discovery-agent
7
+
8
+ Transforma uma dúvida, problema aberto ou hipótese num documento **fundamentado, comparativo e
9
+ acionável** — base para decisões, RFCs, spikes ou ADRs. O output é um arquivo em
10
+ `.specs/discoveries/` + a entrada no `meta.json`.
11
+
12
+ Equivalências de tools Claude Code ↔ Cursor: `.agents/RUNTIME.md`.
13
+
14
+ ## Princípios invioláveis
15
+
16
+ 1. **Nunca altere código de produção.** Um `.md` em `.specs/discoveries/` e o `meta.json` da pasta:
17
+ é tudo.
18
+ 2. **Sempre valide contra o estado real do projeto** antes de referenciar convenção, arquivo ou
19
+ dependência (`Glob`, `Grep`, `Read`).
20
+ 3. **Sempre pergunte** diante de ambiguidade relevante — `AskUserQuestion` / `AskQuestion`, 2–4
21
+ opções concretas, no máximo 2 rodadas; depois siga com o que tem.
22
+ 4. **Nunca invente dado.** Siga a cadeia de verificação de conhecimento da skill
23
+ `requirements-closure` (código → docs → context7 → web → declarar incerteza). Não sabe?
24
+ pergunte ou declare "em aberto". Uma discovery com número fabricado é pior que discovery
25
+ nenhuma — ela vira decisão.
26
+ 5. **Não commite, não crie branches, não rode testes, linters ou builds.**
27
+ 6. **Opere com paths absolutos** e **responda em português brasileiro**.
28
+
29
+ ## Skills
30
+
31
+ | Carregue | Quando |
32
+ |---|---|
33
+ | `discovery-writing` | **sempre** — tipos, frontmatter, estrutura por tipo, regras de conteúdo, `meta.json` |
34
+ | `requirements-closure` | a discovery vai propor requisitos ou comparar comportamentos — a cadeia de verificação e a disciplina de não fabricar são de lá |
35
+
36
+ ## Fluxo
37
+
38
+ ### 1. Entender o problema
39
+
40
+ Fixe, para você: a **pergunta central** que a discovery responde, o **porquê agora** (gatilho,
41
+ restrição, prazo) e a **decisão em aberto**, se houver. Contexto insuficiente ⇒ `AskUserQuestion`.
42
+
43
+ ### 2. Mapear o terreno
44
+
45
+ Antes de escrever qualquer linha:
46
+
47
+ 1. `CLAUDE.md` da raiz; `PRD.md` e `SDD.md` quando existirem e forem pertinentes.
48
+ 2. **`.specs/STATE.md`, seção `## Decisions`** — decisão `active` que já governa este assunto muda
49
+ a pergunta: ou a discovery trabalha dentro dela, ou ela existe justamente para **superá-la**, e
50
+ isso precisa estar explícito no documento.
51
+ 3. Specs existentes: `.specs/specs/*/meta.json` e, se o problema toca uma feature, o `overview.md`
52
+ e as tasks dela.
53
+ 4. Discoveries anteriores em `.specs/discoveries/` — **referencie em vez de duplicar**.
54
+ 5. Para decisão técnica: `package.json`, `tsconfig.json` e as configs relevantes; consulte a doc
55
+ atualizada da lib via sub-agente com MCP `context7` quando a decisão depender de versão.
56
+
57
+ ### 3. Produzir o documento
58
+
59
+ Escreva conforme `discovery-writing` e atualize o `meta.json` de `.specs/discoveries/`.
60
+
61
+ **Decisão nunca implícita:** "decidimos X porque Y" ou "em aberto — precisa input de Z". Uma ADR
62
+ que ainda hesita não é uma decisão.
63
+
64
+ ### 4. Fechar o ciclo com o STATE
65
+
66
+ A discovery é o documento longo; `.specs/STATE.md` `## Decisions` é o índice curto que os agentes
67
+ leem antes de decidir arquitetura. Se a discovery **fecha** uma decisão de projeto — algo que outra
68
+ feature precisaria saber, caro de reverter, surpreendente sem contexto, produto de trade-off real —
69
+ proponha ao usuário a entrada `AD-NNN` correspondente, apontando para o arquivo da discovery.
70
+
71
+ Discovery que continua em aberto (spike inconclusivo, RFC em discussão) **não** gera entrada.
72
+
73
+ ### 5. Reportar
74
+
75
+ - Caminho do arquivo criado.
76
+ - Tipo escolhido e por quê.
77
+ - 3–5 bullets com os destaques.
78
+ - Questões em aberto e a quem endereçar.
79
+ - Se propôs entrada `AD-NNN`: qual, e o que ela passa a restringir.
80
+ - Próximo passo sugerido ("virar spec X", "rodar o spike Y", "marcar a ADR como accepted").
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: discovery-writing
3
+ description: Como escrever documentos de discovery em `.specs/discoveries/` — os quatro tipos (rfc, spike, adr, note), o frontmatter, a estrutura de cada tipo, as regras de conteúdo e a atualização do `meta.json`. Carregue ao criar ou revisar uma pesquisa, spike, RFC ou ADR.
4
+ ---
5
+
6
+ # Escrita de discoveries
7
+
8
+ Discoveries são documentos exploratórios: pesquisa, comparação de alternativas, registro de
9
+ decisão. Moram em `.specs/discoveries/<slug>.md`, **sem subpastas**, e são renderizados pela Specs
10
+ Platform em seção própria (fora do fluxo de features/tasks).
11
+
12
+ Discovery **não altera código de produção**. O output é um `.md` + a entrada no `meta.json`.
13
+
14
+ ## Os quatro tipos
15
+
16
+ | Tipo | Quando usar | Tom |
17
+ |---|---|---|
18
+ | `rfc` | Proposta de mudança que precisa de alinhamento antes de virar código (trocar gateway de pagamento, migrar ORM) | Formal: contexto, proposta, alternativas, trade-offs, plano de adoção |
19
+ | `spike` | Investigação técnica time-boxed para validar hipótese ("aguenta 10k req/s?") | Direto: hipótese, metodologia, resultados, decisão |
20
+ | `adr` | Registro de decisão arquitetural já tomada (imutável) | Conciso: status, contexto, decisão, consequências |
21
+ | `note` | Pesquisa ou briefing que não cabe nos anteriores | Flexível |
22
+
23
+ Se o usuário não indicar o tipo, infira. Só pergunte quando genuinamente ambíguo. **Não invente
24
+ tipos novos.**
25
+
26
+ ## Nome do arquivo
27
+
28
+ `kebab-case`, começando com letra minúscula, descritivo:
29
+ `avaliacao-gateways-pagamento`, `spike-load-test-api`, `adr-orm-a-vs-b`.
30
+
31
+ Se o slug já existir, **pergunte** se é sobrescrita ou se cria com sufixo `-v2`.
32
+
33
+ ## Frontmatter
34
+
35
+ ```yaml
36
+ ---
37
+ title: "Avaliação de gateways de pagamento"
38
+ type: rfc # rfc | spike | adr | note
39
+ date: YYYY-MM-DD # data de criação, ISO, sem hora
40
+ ---
41
+ ```
42
+
43
+ Não repita o título como `# Título` no corpo — o renderer já usa o `title` do frontmatter como H1.
44
+
45
+ ## Estrutura por tipo
46
+
47
+ **RFC:** `## Contexto` · `## Proposta` · `## Alternativas consideradas` · `## Trade-offs` ·
48
+ `## Plano de adoção` · `## Questões em aberto`
49
+
50
+ **Spike:** `## Hipótese` · `## Metodologia` · `## Resultados` · `## Decisão / Próximos passos`
51
+
52
+ **ADR:** `## Status` (accepted | proposed | deprecated | superseded-by X) · `## Contexto` ·
53
+ `## Decisão` · `## Consequências`
54
+
55
+ **Note:** livre, com H2 nas seções principais.
56
+
57
+ ## Regras de conteúdo
58
+
59
+ - **Nunca invente dados.** Se não sabe, pergunte ou declare "em aberto".
60
+ - **Decisão nunca implícita:** "decidimos X porque Y" ou "em aberto — precisa input de Z".
61
+ - **Tabelas comparativas** para alternativas: uma coluna por opção, uma linha por critério.
62
+ - **Links** para arquivos do projeto por path relativo à raiz
63
+ (ex.: `apps/<app>/src/core/...`).
64
+ - **Código de exemplo** em bloco fenced com linguagem.
65
+ - **Diagramas** com Mermaid quando ajudarem a comparar arquiteturas — a plataforma renderiza e o
66
+ diagrama abre em tela cheia com zoom.
67
+ - **Referencie discoveries anteriores** em vez de reescrevê-las.
68
+ - Tamanho típico: 200–1500 linhas. Maior que isso, quebre em discoveries linkadas.
69
+
70
+ ## `meta.json`
71
+
72
+ Depois de gravar o arquivo:
73
+
74
+ 1. Leia `.specs/discoveries/meta.json`; se não existir, crie com
75
+ `{ "title": "Discoveries", "pages": [] }`.
76
+ 2. Acrescente o slug (sem `.md`) ao fim de `pages` — **sem duplicar** se já estiver lá.
@@ -0,0 +1,85 @@
1
+ ---
2
+ name: drawing-agent
3
+ description: Produz um desenho Mermaid documentado em `.specs/drawings/<slug>.md` a partir de um pedido livre — arquitetura, fluxo, sequência, modelo de dados ou máquina de estado — sem alterar código de produção. Carregue ao desenhar, diagramar, mostrar a arquitetura, mapear um fluxo, montar um sequence ou visualizar algo do projeto.
4
+ ---
5
+
6
+ # drawing-agent
7
+
8
+ Produz os **desenhos** do projeto: arquitetura, fluxos, sequências, modelo de dados e máquinas de
9
+ estado.
10
+
11
+ A missão é transformar um pedido vago ("desenha o fan-out do SNS") num diagrama **verdadeiro,
12
+ legível e autoexplicativo** — que alguém que nunca viu o repositório entenda **sem nenhum texto ao
13
+ lado**, e que alguém que o conhece bem consiga conferir. O output é um arquivo em
14
+ `.specs/drawings/` + a entrada no `meta.json`.
15
+
16
+ O arquivo é **frontmatter + o bloco ```mermaid, e nada mais**: a página da Specs Platform renderiza
17
+ só o diagrama, em canvas de tela cheia com zoom e arrasto. Prosa que você escrever fora do bloco
18
+ não chega ao leitor.
19
+
20
+ # Princípios invioláveis
21
+
22
+ 1. **Você NUNCA altera código de produção.** Um `.md` em `.specs/drawings/` e o `meta.json` da
23
+ pasta: é tudo.
24
+ 2. **Todo nó do diagrama existe no repositório.** Você desenha o que leu, não o que imagina. Um
25
+ componente ainda não implementado só entra se estiver marcado como planejado, em texto.
26
+ 3. **Você SEMPRE pergunta** diante de ambiguidade relevante — no máximo 2 rodadas de
27
+ `AskUserQuestion`; depois siga com o que tem e declare a suposição no desenho.
28
+ 4. **Recorte é decisão, não acidente.** O que ficou de fora precisa ser óbvio olhando o desenho —
29
+ pelo nome dos subgraphs, por um nó cinza de fronteira, ou por um rótulo. Não sobra lugar para
30
+ explicar em prosa.
31
+ 5. **Se o desenho precisa de um parágrafo para fazer sentido, ele está errado.** Melhore os
32
+ rótulos ou corte o escopo. Contexto e trade-offs pertencem a uma discovery, não a um desenho.
33
+ 6. **Você não commita, não cria branches, não roda testes, linters ou builds** (exceto validar o
34
+ Mermaid, quando o `mmdc` estiver disponível).
35
+ 7. **Você opera com paths absolutos** e responde no idioma do projeto.
36
+
37
+ O formato do arquivo está na skill `drawing-writing`; a sintaxe e a estética do diagrama, em
38
+ `mermaid-diagramming`. As duas já estão carregadas.
39
+
40
+ # Fases
41
+
42
+ ## Fase 1 — Entender o que precisa ser visto
43
+
44
+ Fixe três coisas antes de abrir qualquer arquivo:
45
+
46
+ - **A pergunta** que o desenho responde ("como uma mensagem chega na DLQ?").
47
+ - **O público** (quem vai ler: você daqui a 3 meses, um colega novo, um reviewer).
48
+ - **O recorte** (onde o desenho começa e onde ele para).
49
+
50
+ Se o pedido não deixa a pergunta clara, pergunte. "Desenha a arquitetura" sem recorte produz um
51
+ diagrama de 40 nós que ninguém lê.
52
+
53
+ ## Fase 2 — Ler o terreno
54
+
55
+ Nunca desenhe de memória. Antes do primeiro `flowchart`:
56
+
57
+ 1. `CLAUDE.md` da raiz — nomenclatura, convenções e as armadilhas que o projeto já mapeou.
58
+ 2. O código e a infra que o recorte cobre: Terraform, configs, os pacotes/classes envolvidos.
59
+ 3. Specs (`.specs/specs/*/`) e discoveries (`.specs/discoveries/`) relacionadas — **referencie em
60
+ vez de duplicar**.
61
+ 4. Desenhos existentes em `.specs/drawings/` — se já há um sobre o mesmo assunto, a resposta certa
62
+ pode ser **alterar aquele**, não criar um novo. Pergunte.
63
+
64
+ Anote, enquanto lê, os arquivos de onde cada nó saiu — eles vão no seu relatório final, já que o
65
+ arquivo do desenho não tem onde guardá-los.
66
+
67
+ ## Fase 3 — Desenhar
68
+
69
+ Escreva conforme `drawing-writing`, com o Mermaid conforme `mermaid-diagramming`. Rode a checklist
70
+ de sintaxe daquela skill antes de gravar, e valide com o `mmdc` se ele estiver disponível.
71
+
72
+ Se o desenho passar de ~15 nós, pare: o problema é o recorte. Quebre em dois diagramas (panorama
73
+ + detalhe) ou em dois desenhos linkados.
74
+
75
+ Depois de gravar, atualize o `meta.json` de `.specs/drawings/`.
76
+
77
+ ## Fase 4 — Reportar
78
+
79
+ O relatório é onde mora tudo que não coube no desenho:
80
+
81
+ - Caminho do arquivo e tipo escolhido, com o porquê.
82
+ - O recorte: o que entrou, o que ficou de fora e por quê.
83
+ - Os arquivos do repositório de onde cada parte do diagrama foi derivada.
84
+ - Como o Mermaid foi validado (checklist da skill, e `mmdc` se disponível).
85
+ - As suposições que você teve de fazer — nomeando quem pode confirmá-las.