@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.
- package/LICENSE +21 -0
- package/README.md +460 -0
- package/assets/specs-usage-statusline.sh +77 -0
- package/assets/template/.agents/README.md +123 -0
- package/assets/template/.agents/RUNTIME.md +64 -0
- package/assets/template/.agents/agents/discovery-agent.md +15 -0
- package/assets/template/.agents/agents/drawing-agent.md +15 -0
- package/assets/template/.agents/agents/feature-runner.md +15 -0
- package/assets/template/.agents/agents/refinement-runner.md +15 -0
- package/assets/template/.agents/agents/refinement.md +15 -0
- package/assets/template/.agents/project.md +158 -0
- package/assets/template/.agents/scripts/check-commit.mjs +70 -0
- package/assets/template/.agents/scripts/validate-spec.mjs +345 -0
- package/assets/template/.agents/skills/discovery-agent/SKILL.md +80 -0
- package/assets/template/.agents/skills/discovery-writing/SKILL.md +76 -0
- package/assets/template/.agents/skills/drawing-agent/SKILL.md +85 -0
- package/assets/template/.agents/skills/drawing-writing/SKILL.md +100 -0
- package/assets/template/.agents/skills/execution-protocol/SKILL.md +110 -0
- package/assets/template/.agents/skills/execution-protocol/references/git.md +104 -0
- package/assets/template/.agents/skills/execution-protocol/references/relatorio-halt.md +103 -0
- package/assets/template/.agents/skills/feature-runner/SKILL.md +81 -0
- package/assets/template/.agents/skills/feature-runner/references/divergencias.md +38 -0
- package/assets/template/.agents/skills/feature-runner/references/implementar.md +80 -0
- package/assets/template/.agents/skills/feature-runner/references/localizar.md +46 -0
- package/assets/template/.agents/skills/mermaid-diagramming/SKILL.md +198 -0
- package/assets/template/.agents/skills/prototype-check/SKILL.md +90 -0
- package/assets/template/.agents/skills/refinement/SKILL.md +136 -0
- package/assets/template/.agents/skills/refinement/references/estrutura.md +27 -0
- package/assets/template/.agents/skills/refinement/references/mapear-terreno.md +46 -0
- package/assets/template/.agents/skills/refinement/references/validar-renderizacao.md +29 -0
- package/assets/template/.agents/skills/refinement-runner/SKILL.md +145 -0
- package/assets/template/.agents/skills/requirements-closure/SKILL.md +236 -0
- package/assets/template/.agents/skills/spec-writing/SKILL.md +258 -0
- package/assets/template/.agents/skills/test-strategy/SKILL.md +176 -0
- package/assets/template/.agents/skills/verification/SKILL.md +143 -0
- package/assets/template/.specs/config.json +39 -0
- package/assets/template/.specs/discoveries/meta.json +4 -0
- package/assets/template/.specs/drawings/meta.json +4 -0
- package/assets/template/.specs/specs/_templates/task-backend.md +75 -0
- package/assets/template/.specs/specs/_templates/task-frontend.md +89 -0
- package/assets/template/.specs/specs/_templates/task-integracao.md +72 -0
- package/assets/template/.specs/specs/exemplo/feat-exemplo-primeira-task.md +82 -0
- package/assets/template/.specs/specs/exemplo/meta.json +8 -0
- package/assets/template/.specs/specs/exemplo/overview.md +45 -0
- package/assets/template/.specs/specs/meta.json +4 -0
- package/assets/template/SPECS.md +213 -0
- package/dist/chunk-SOOETS3N.js +3490 -0
- package/dist/chunk-SOOETS3N.js.map +1 -0
- package/dist/cli.js +15 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/ui/assets/abnfDiagram-VCTEODGH-DYXtkHNq.js +2 -0
- package/dist/ui/assets/abnfDiagram-VCTEODGH-DYXtkHNq.js.map +1 -0
- package/dist/ui/assets/arc-CJs0gz4E.js +2 -0
- package/dist/ui/assets/arc-CJs0gz4E.js.map +1 -0
- package/dist/ui/assets/architectureDiagram-5GKGNRK7-Br7ZNRfe.js +37 -0
- package/dist/ui/assets/architectureDiagram-5GKGNRK7-Br7ZNRfe.js.map +1 -0
- package/dist/ui/assets/blockDiagram-I7D4REHJ-172V7R5o.js +130 -0
- package/dist/ui/assets/blockDiagram-I7D4REHJ-172V7R5o.js.map +1 -0
- package/dist/ui/assets/c4Diagram-7LVT6UL2-B4AYFhRs.js +39 -0
- package/dist/ui/assets/c4Diagram-7LVT6UL2-B4AYFhRs.js.map +1 -0
- package/dist/ui/assets/channel-BfWO3B41.js +2 -0
- package/dist/ui/assets/channel-BfWO3B41.js.map +1 -0
- package/dist/ui/assets/chunk-2Q5K7J3B-B-u7gtZN.js +2 -0
- package/dist/ui/assets/chunk-2Q5K7J3B-B-u7gtZN.js.map +1 -0
- package/dist/ui/assets/chunk-5VM5RSS4-CjFDq_cq.js +16 -0
- package/dist/ui/assets/chunk-5VM5RSS4-CjFDq_cq.js.map +1 -0
- package/dist/ui/assets/chunk-F27PBJKO-BDYeB7B8.js +2 -0
- package/dist/ui/assets/chunk-F27PBJKO-BDYeB7B8.js.map +1 -0
- package/dist/ui/assets/chunk-IMKFNOWR-y2kXPnac.js +232 -0
- package/dist/ui/assets/chunk-IMKFNOWR-y2kXPnac.js.map +1 -0
- package/dist/ui/assets/chunk-JWPE2WC7-DyCryvDN.js +2 -0
- package/dist/ui/assets/chunk-JWPE2WC7-DyCryvDN.js.map +1 -0
- package/dist/ui/assets/chunk-POPQ4Y6H-DeaxKHvR.js +2 -0
- package/dist/ui/assets/chunk-POPQ4Y6H-DeaxKHvR.js.map +1 -0
- package/dist/ui/assets/chunk-SVP7TREG-CmcZEncL.js +89 -0
- package/dist/ui/assets/chunk-SVP7TREG-CmcZEncL.js.map +1 -0
- package/dist/ui/assets/chunk-TICWLB2K-Df1RRXWR.js +207 -0
- package/dist/ui/assets/chunk-TICWLB2K-Df1RRXWR.js.map +1 -0
- package/dist/ui/assets/chunk-XXDRQBXY-ClTat8Q0.js +2 -0
- package/dist/ui/assets/chunk-XXDRQBXY-ClTat8Q0.js.map +1 -0
- package/dist/ui/assets/classDiagram-ZZMXUADV-YTUiu8rF.js +2 -0
- package/dist/ui/assets/classDiagram-ZZMXUADV-YTUiu8rF.js.map +1 -0
- package/dist/ui/assets/classDiagram-v2-VYDZK3BY-YTUiu8rF.js +2 -0
- package/dist/ui/assets/classDiagram-v2-VYDZK3BY-YTUiu8rF.js.map +1 -0
- package/dist/ui/assets/cose-bilkent-JH36ORCC-QvqQLoy5.js +2 -0
- package/dist/ui/assets/cose-bilkent-JH36ORCC-QvqQLoy5.js.map +1 -0
- package/dist/ui/assets/cynefin-OW5HDTMX-BY6w3pAQ.js +179 -0
- package/dist/ui/assets/cynefin-OW5HDTMX-BY6w3pAQ.js.map +1 -0
- package/dist/ui/assets/cynefinDiagram-5FMLGOSQ-luPP4ha1.js +63 -0
- package/dist/ui/assets/cynefinDiagram-5FMLGOSQ-luPP4ha1.js.map +1 -0
- package/dist/ui/assets/cytoscape.esm-Ix0LnXOy.js +332 -0
- package/dist/ui/assets/cytoscape.esm-Ix0LnXOy.js.map +1 -0
- package/dist/ui/assets/dagre-GXQ25YYZ-DhpPwKqq.js +5 -0
- package/dist/ui/assets/dagre-GXQ25YYZ-DhpPwKqq.js.map +1 -0
- package/dist/ui/assets/defaultLocale-DX6XiGOO.js +2 -0
- package/dist/ui/assets/defaultLocale-DX6XiGOO.js.map +1 -0
- package/dist/ui/assets/diagram-S7CK7UJ4-B9wvQM0w.js +31 -0
- package/dist/ui/assets/diagram-S7CK7UJ4-B9wvQM0w.js.map +1 -0
- package/dist/ui/assets/diagram-UQ7AKVKN-gm1M_iZS.js +42 -0
- package/dist/ui/assets/diagram-UQ7AKVKN-gm1M_iZS.js.map +1 -0
- package/dist/ui/assets/diagram-VSXAHHWV-DkEB6nEx.js +4 -0
- package/dist/ui/assets/diagram-VSXAHHWV-DkEB6nEx.js.map +1 -0
- package/dist/ui/assets/diagram-VX7I27RA-Ce2F1bHi.js +25 -0
- package/dist/ui/assets/diagram-VX7I27RA-Ce2F1bHi.js.map +1 -0
- package/dist/ui/assets/diagram-Z3DM3KII-B5ArCh-E.js +25 -0
- package/dist/ui/assets/diagram-Z3DM3KII-B5ArCh-E.js.map +1 -0
- package/dist/ui/assets/ebnfDiagram-PWID7BFC-DgBM80MD.js +2 -0
- package/dist/ui/assets/ebnfDiagram-PWID7BFC-DgBM80MD.js.map +1 -0
- package/dist/ui/assets/erDiagram-RLTQ6QDP-C161af8G.js +100 -0
- package/dist/ui/assets/erDiagram-RLTQ6QDP-C161af8G.js.map +1 -0
- package/dist/ui/assets/flowDiagram-HODETNUW-B8N_WyW5.js +169 -0
- package/dist/ui/assets/flowDiagram-HODETNUW-B8N_WyW5.js.map +1 -0
- package/dist/ui/assets/ganttDiagram-EL5Y4UJY-CD2RJpTb.js +293 -0
- package/dist/ui/assets/ganttDiagram-EL5Y4UJY-CD2RJpTb.js.map +1 -0
- package/dist/ui/assets/gitGraphDiagram-WWUBYQGX-BrDAzeFT.js +107 -0
- package/dist/ui/assets/gitGraphDiagram-WWUBYQGX-BrDAzeFT.js.map +1 -0
- package/dist/ui/assets/index-CVWRdirI.css +32 -0
- package/dist/ui/assets/index-DoR97Zqf.js +408 -0
- package/dist/ui/assets/index-DoR97Zqf.js.map +1 -0
- package/dist/ui/assets/infoDiagram-27XIBGKW-HYGaMzrA.js +3 -0
- package/dist/ui/assets/infoDiagram-27XIBGKW-HYGaMzrA.js.map +1 -0
- package/dist/ui/assets/init-Gi6I4Gst.js +2 -0
- package/dist/ui/assets/init-Gi6I4Gst.js.map +1 -0
- package/dist/ui/assets/ishikawaDiagram-5VMMS53U-DXTLlBiw.js +71 -0
- package/dist/ui/assets/ishikawaDiagram-5VMMS53U-DXTLlBiw.js.map +1 -0
- package/dist/ui/assets/journeyDiagram-3NMN7TZE-DSJ8QN1S.js +140 -0
- package/dist/ui/assets/journeyDiagram-3NMN7TZE-DSJ8QN1S.js.map +1 -0
- package/dist/ui/assets/kanban-definition-UXKFOSKX-Bm5iz0k3.js +90 -0
- package/dist/ui/assets/kanban-definition-UXKFOSKX-Bm5iz0k3.js.map +1 -0
- package/dist/ui/assets/katex-C5jXJg4s.js +258 -0
- package/dist/ui/assets/katex-C5jXJg4s.js.map +1 -0
- package/dist/ui/assets/layout-DfJgW6eG.js +2 -0
- package/dist/ui/assets/layout-DfJgW6eG.js.map +1 -0
- package/dist/ui/assets/linear-B_kyVotV.js +2 -0
- package/dist/ui/assets/linear-B_kyVotV.js.map +1 -0
- package/dist/ui/assets/mermaid-block-CUJSBQFx.js +2 -0
- package/dist/ui/assets/mermaid-block-CUJSBQFx.js.map +1 -0
- package/dist/ui/assets/mermaid.core-ExHG1WnM.js +313 -0
- package/dist/ui/assets/mermaid.core-ExHG1WnM.js.map +1 -0
- package/dist/ui/assets/mindmap-definition-YA3MSWOX-B1Hh9dQz.js +97 -0
- package/dist/ui/assets/mindmap-definition-YA3MSWOX-B1Hh9dQz.js.map +1 -0
- package/dist/ui/assets/ordinal-Cboi1Yqb.js +2 -0
- package/dist/ui/assets/ordinal-Cboi1Yqb.js.map +1 -0
- package/dist/ui/assets/pegDiagram-XKGWAZYB-x242yOd7.js +2 -0
- package/dist/ui/assets/pegDiagram-XKGWAZYB-x242yOd7.js.map +1 -0
- package/dist/ui/assets/pieDiagram-E7YTZNPT-Dl-YP7Xs.js +40 -0
- package/dist/ui/assets/pieDiagram-E7YTZNPT-Dl-YP7Xs.js.map +1 -0
- package/dist/ui/assets/quadrantDiagram-AXDQQJYC-CMJ920V9.js +8 -0
- package/dist/ui/assets/quadrantDiagram-AXDQQJYC-CMJ920V9.js.map +1 -0
- package/dist/ui/assets/railroadDiagram-O6MQD6OU-1kV83y5t.js +2 -0
- package/dist/ui/assets/railroadDiagram-O6MQD6OU-1kV83y5t.js.map +1 -0
- package/dist/ui/assets/requirementDiagram-BXWQKSXE-BFd8ZZD3.js +85 -0
- package/dist/ui/assets/requirementDiagram-BXWQKSXE-BFd8ZZD3.js.map +1 -0
- package/dist/ui/assets/sankeyDiagram-P5KCCOFB-C0LZevnf.js +41 -0
- package/dist/ui/assets/sankeyDiagram-P5KCCOFB-C0LZevnf.js.map +1 -0
- package/dist/ui/assets/sequenceDiagram-WJ2MYXX4-BasIXLJ0.js +163 -0
- package/dist/ui/assets/sequenceDiagram-WJ2MYXX4-BasIXLJ0.js.map +1 -0
- package/dist/ui/assets/sizeCapture-INFHLROL-qAtnqB7N.js +2 -0
- package/dist/ui/assets/sizeCapture-INFHLROL-qAtnqB7N.js.map +1 -0
- package/dist/ui/assets/stateDiagram-D77RDMKH-CllWDWZP.js +2 -0
- package/dist/ui/assets/stateDiagram-D77RDMKH-CllWDWZP.js.map +1 -0
- package/dist/ui/assets/stateDiagram-v2-MP3YSRHH-Dwntf8Tk.js +2 -0
- package/dist/ui/assets/stateDiagram-v2-MP3YSRHH-Dwntf8Tk.js.map +1 -0
- package/dist/ui/assets/swimlanes-42K2YHIH-Bgt0KslH.js +2 -0
- package/dist/ui/assets/swimlanes-42K2YHIH-Bgt0KslH.js.map +1 -0
- package/dist/ui/assets/swimlanesDiagram-VR7AAH4N-C9abicdw.js +9 -0
- package/dist/ui/assets/swimlanesDiagram-VR7AAH4N-C9abicdw.js.map +1 -0
- package/dist/ui/assets/timeline-definition-24CTP7MA-BYO2h9Qr.js +121 -0
- package/dist/ui/assets/timeline-definition-24CTP7MA-BYO2h9Qr.js.map +1 -0
- package/dist/ui/assets/vennDiagram-4TSXK5OY-ZzyiwIUi.js +35 -0
- package/dist/ui/assets/vennDiagram-4TSXK5OY-ZzyiwIUi.js.map +1 -0
- package/dist/ui/assets/wardleyDiagram-VM6X3IG4-DfjKCK0N.js +79 -0
- package/dist/ui/assets/wardleyDiagram-VM6X3IG4-DfjKCK0N.js.map +1 -0
- package/dist/ui/assets/xychartDiagram-S5SC5T6Z-Z7daOfmM.js +8 -0
- package/dist/ui/assets/xychartDiagram-S5SC5T6Z-Z7daOfmM.js.map +1 -0
- package/dist/ui/favicon.svg +6 -0
- package/dist/ui/index.html +21 -0
- package/package.json +70 -0
- package/scripts/copy-ui.mjs +21 -0
- 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.
|