@orkastery/cli 0.2.0 → 0.4.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 (257) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +86 -65
  3. package/adapters/README.md +30 -15
  4. package/adapters/claude-code/.claude-plugin/plugin.json +11 -5
  5. package/adapters/claude-code/README.md +110 -8
  6. package/adapters/claude-code/commands/check.md +4 -3
  7. package/adapters/claude-code/commands/goal.md +3 -2
  8. package/adapters/claude-code/commands/master.md +3 -2
  9. package/adapters/claude-code/commands/onboarding.md +21 -0
  10. package/adapters/claude-code/commands/ork.md +99 -27
  11. package/adapters/claude-code/hooks/hooks.json +6 -1
  12. package/adapters/claude-code/hooks/ork-guard.js +1 -1
  13. package/adapters/claude-code/hooks/ork-sensor.js +66 -0
  14. package/adapters/codex/skills/ork/SKILL.md +57 -0
  15. package/adapters/hermes/README.md +161 -0
  16. package/adapters/hermes/bin/ork-abrir-thread.sh +2 -0
  17. package/adapters/hermes/bin/ork-brain.sh +4 -0
  18. package/adapters/hermes/bin/ork-hitl-answer.py +82 -0
  19. package/adapters/hermes/bin/ork-maestro.sh +6 -0
  20. package/adapters/hermes/bin/ork-master-enviar.py +84 -0
  21. package/adapters/hermes/bin/ork-pulse-enviar.py +47 -0
  22. package/adapters/hermes/hermes.plugin.json +24 -4
  23. package/adapters/hermes/hitl-ingress/__init__.py +356 -0
  24. package/adapters/hermes/hitl-ingress/plugin.yaml +4 -0
  25. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +75 -22
  26. package/adapters/openclaw/README.md +136 -27
  27. package/adapters/openclaw/bin/ork-brain.sh +4 -0
  28. package/adapters/openclaw/construir.sh +19 -0
  29. package/adapters/openclaw/dist/hitl-ingress.js +199 -0
  30. package/adapters/openclaw/dist/index.js +437 -0
  31. package/adapters/openclaw/openclaw.plugin.json +39 -134
  32. package/adapters/openclaw/package.json +26 -0
  33. package/adapters/openclaw/src/hitl-ingress.ts +180 -0
  34. package/adapters/openclaw/src/index.ts +460 -0
  35. package/adapters/openclaw/src/tipos-openclaw.d.ts +58 -0
  36. package/adapters/openclaw/tsconfig.json +15 -0
  37. package/assets/docs/markdownlint-cli2.jsonc +22 -0
  38. package/assets/docs/padroes/documentacao-de-produto.md +133 -0
  39. package/assets/docs/padroes/roadmap-de-produto.md +98 -0
  40. package/assets/docs/produto/README.md +14 -0
  41. package/assets/docs/produto/_modelo-feature.md +59 -0
  42. package/assets/docs/roadmap/README.md +14 -0
  43. package/assets/docs/roadmap/_modelo-item.md +73 -0
  44. package/assets/orkmind-native-schema.json +32 -0
  45. package/assets/orkmind_bridge.py +357 -0
  46. package/assets/orkmind_fixture.py +67 -0
  47. package/assets/orkmind_prospective.py +138 -0
  48. package/assets/reference-tariffs-i07.json +23 -0
  49. package/dist/adapters/claude-bg.js +488 -39
  50. package/dist/adapters/codex-controller-sensor.js +449 -0
  51. package/dist/adapters/codex-controller-worker.js +361 -0
  52. package/dist/adapters/codex-controller.js +252 -0
  53. package/dist/adapters/codex-events.js +205 -0
  54. package/dist/adapters/codex-question.js +26 -0
  55. package/dist/adapters/codex-runner.js +133 -0
  56. package/dist/adapters/codex.js +394 -0
  57. package/dist/agents-md.js +84 -0
  58. package/dist/auditoria.js +12 -9
  59. package/dist/auditrun.js +5 -4
  60. package/dist/board.js +174 -23
  61. package/dist/branch-de-estado.js +136 -0
  62. package/dist/canarios-hitl.js +177 -0
  63. package/dist/canarios-i43.js +543 -0
  64. package/dist/canarios-pulse.js +147 -0
  65. package/dist/canarios-sensores.js +129 -0
  66. package/dist/canarios.js +111 -2
  67. package/dist/catalogo.js +9 -0
  68. package/dist/ci.js +223 -0
  69. package/dist/ciclos.js +2 -1
  70. package/dist/claim-lint.js +64 -0
  71. package/dist/claims.js +60 -0
  72. package/dist/company-brain-capture.js +195 -0
  73. package/dist/company-brain-cli.js +123 -0
  74. package/dist/company-brain-client.js +61 -0
  75. package/dist/company-brain-contract.js +166 -0
  76. package/dist/company-brain-journal.js +192 -0
  77. package/dist/company-brain-mcp.js +33 -0
  78. package/dist/company-brain-migration.js +60 -0
  79. package/dist/company-brain-source.js +177 -0
  80. package/dist/company-brain-worker.js +18 -0
  81. package/dist/conducao-texto.js +61 -0
  82. package/dist/conducao.js +876 -0
  83. package/dist/contrato-publico.js +39 -0
  84. package/dist/creation-operation-store.js +251 -0
  85. package/dist/creation-operation.js +148 -0
  86. package/dist/decisao-autonoma.js +183 -0
  87. package/dist/delegation.js +79 -0
  88. package/dist/demo.js +120 -0
  89. package/dist/docs.js +828 -0
  90. package/dist/doctor.js +184 -25
  91. package/dist/entrega-pr.js +117 -0
  92. package/dist/escopo-escrita.js +71 -0
  93. package/dist/estado-thread.js +222 -0
  94. package/dist/evalrunner.js +12 -0
  95. package/dist/fabrica-estado.js +329 -0
  96. package/dist/fabrica-publicar.js +76 -0
  97. package/dist/fix.js +22 -1
  98. package/dist/gates.js +59 -11
  99. package/dist/handoff.js +39 -23
  100. package/dist/hitl-canais.js +333 -0
  101. package/dist/hitl-classificacao.js +131 -0
  102. package/dist/hitl-contract.js +465 -0
  103. package/dist/hitl-estado.js +136 -0
  104. package/dist/hitl-gates.js +554 -0
  105. package/dist/hitl-ingress-receipt.js +382 -0
  106. package/dist/hitl-local-atestado.js +57 -0
  107. package/dist/hitl-local-receipt.js +328 -0
  108. package/dist/hitl-local.js +143 -0
  109. package/dist/hitl-lock.js +139 -0
  110. package/dist/hitl-lote.js +223 -0
  111. package/dist/hitl-native-offer.js +97 -0
  112. package/dist/hitl-native.js +65 -0
  113. package/dist/hitl-presentation.js +209 -0
  114. package/dist/hitl-public-receipt.js +176 -0
  115. package/dist/hitl-resumo.js +209 -0
  116. package/dist/hitl-sessions.js +475 -0
  117. package/dist/hitl.js +711 -0
  118. package/dist/horario.js +269 -0
  119. package/dist/hosts.js +198 -22
  120. package/dist/index.js +1741 -94
  121. package/dist/indice.js +99 -0
  122. package/dist/init.js +28 -3
  123. package/dist/integracoes-locais.js +17 -0
  124. package/dist/leases.js +65 -21
  125. package/dist/ledger-stats.js +272 -0
  126. package/dist/ledger.js +109 -2
  127. package/dist/licoes.js +221 -0
  128. package/dist/liveness.js +218 -0
  129. package/dist/maestro-actions.js +51 -0
  130. package/dist/maestro-authority.js +152 -0
  131. package/dist/maestro-cli.js +97 -0
  132. package/dist/maestro-contract.js +55 -0
  133. package/dist/maestro-discovery.js +132 -0
  134. package/dist/maestro-runtime.js +268 -0
  135. package/dist/maestro-snapshot.js +86 -0
  136. package/dist/maestro-sources.js +224 -0
  137. package/dist/manifest.js +165 -6
  138. package/dist/maquina.js +102 -0
  139. package/dist/master-audit.js +101 -0
  140. package/dist/master-batch.js +43 -0
  141. package/dist/master-digest.js +149 -0
  142. package/dist/master-migracao.js +155 -0
  143. package/dist/master.js +361 -77
  144. package/dist/mcp-artifacts.js +234 -0
  145. package/dist/mcp-git.js +433 -0
  146. package/dist/mcp-install.js +310 -0
  147. package/dist/mcp-maestro.js +25 -0
  148. package/dist/mcp-server.js +469 -0
  149. package/dist/mcp-ship.js +436 -0
  150. package/dist/mcp-verify.js +173 -0
  151. package/dist/memoria-humana.js +207 -0
  152. package/dist/memoria.js +314 -78
  153. package/dist/memory-migration.js +279 -0
  154. package/dist/memory-prospective.js +148 -0
  155. package/dist/modos-migracao.js +191 -0
  156. package/dist/modos.js +151 -15
  157. package/dist/monitor-lock.js +110 -0
  158. package/dist/objective.js +449 -0
  159. package/dist/ocupacao.js +267 -0
  160. package/dist/onboarding.js +303 -0
  161. package/dist/orkmind.js +389 -90
  162. package/dist/orquestracao.js +96 -69
  163. package/dist/phase.js +612 -151
  164. package/dist/playbook-capabilities.js +150 -0
  165. package/dist/playbook-contracts.js +188 -0
  166. package/dist/policies.js +72 -0
  167. package/dist/portfolio-context.js +61 -0
  168. package/dist/portfolio.js +180 -0
  169. package/dist/preflight.js +207 -0
  170. package/dist/process-audit.js +112 -0
  171. package/dist/project-state.js +93 -0
  172. package/dist/prompts.js +25 -5
  173. package/dist/prova-minima.js +112 -0
  174. package/dist/pulse-cadencia.js +163 -0
  175. package/dist/pulse-consentimento.js +260 -0
  176. package/dist/pulse-delivery.js +323 -0
  177. package/dist/pulse-resposta.js +542 -0
  178. package/dist/pulse.js +262 -0
  179. package/dist/ratelimit.js +35 -2
  180. package/dist/recall.js +80 -3
  181. package/dist/redacao-saida.js +45 -0
  182. package/dist/redacao-url.js +66 -0
  183. package/dist/retry.js +640 -118
  184. package/dist/roadmap-reservas.js +243 -0
  185. package/dist/runtime-ambiente.js +38 -0
  186. package/dist/runtime-context.js +215 -0
  187. package/dist/runtime-profiles.js +869 -0
  188. package/dist/runtimes.js +103 -0
  189. package/dist/sandbox.js +9 -0
  190. package/dist/session-events.js +203 -0
  191. package/dist/session-watcher-claude.js +637 -0
  192. package/dist/session-watcher.js +700 -0
  193. package/dist/sessoes-adopt.js +154 -0
  194. package/dist/sessoes-inventario.js +159 -0
  195. package/dist/sessoes.js +16 -35
  196. package/dist/setup.js +691 -0
  197. package/dist/ship.js +168 -19
  198. package/dist/slug.js +1 -1
  199. package/dist/thread-close.js +115 -0
  200. package/dist/thread.js +142 -28
  201. package/dist/tokens.js +1 -1
  202. package/dist/util.js +4 -2
  203. package/dist/verify-sandbox.js +265 -0
  204. package/dist/verify.js +270 -25
  205. package/dist/versao.js +60 -0
  206. package/dist/worktree.js +31 -2
  207. package/dist/write-activation.js +291 -0
  208. package/dist/yaml.js +5 -1
  209. package/eval/casos/master-metrics.json +1 -1
  210. package/eval/casos/onboarding.json +102 -0
  211. package/eval/casos/orkastery-bootstrap.json +37 -3
  212. package/eval/casos/scope-check-capability-map.json +45 -7
  213. package/eval/casos/ship-release.json +3 -3
  214. package/eval/fixtures/b0-slug-e-modos/caso.json +35 -22
  215. package/eval/fixtures/b2-master-log/caso.json +1 -1
  216. package/eval/fixtures/fx-adapter-editado-detectado/caso.json +19 -0
  217. package/eval/fixtures/fx-auto-quiet/caso.json +11 -0
  218. package/eval/fixtures/fx-blanket-approve/caso.json +10 -0
  219. package/eval/fixtures/fx-check-runtime-cruzado/caso.json +20 -0
  220. package/eval/fixtures/fx-codex-dry/caso.json +18 -0
  221. package/eval/fixtures/fx-donewhen-executavel/caso.json +15 -0
  222. package/eval/fixtures/fx-estado-dividido/caso.json +15 -0
  223. package/eval/fixtures/fx-fase-orfa/caso.json +116 -0
  224. package/eval/fixtures/fx-happy/caso.json +6 -4
  225. package/eval/fixtures/fx-hitl-latency/caso.json +20 -0
  226. package/eval/fixtures/fx-indice-reversao/caso.json +25 -0
  227. package/eval/fixtures/fx-listagem-abertas/caso.json +16 -0
  228. package/eval/fixtures/fx-maestro-bootstrap/caso.json +21 -0
  229. package/eval/fixtures/fx-modo-aposentado-escritor/caso.json +21 -0
  230. package/eval/fixtures/fx-modo-aposentado-leitor/caso.json +16 -0
  231. package/eval/fixtures/fx-objective-oscillation/caso.json +14 -0
  232. package/eval/fixtures/fx-omnicanal/caso.json +17 -0
  233. package/eval/fixtures/fx-sensores-runtime/caso.json +17 -0
  234. package/monitor/company-brain.cjs +17 -0
  235. package/monitor/pulse-scope.cjs +45 -0
  236. package/monitor/pulse.cron +18 -0
  237. package/monitor/varredura-pulse.sh +27 -0
  238. package/package.json +28 -7
  239. package/references/definition-of-done.md +2 -2
  240. package/schemas/claims.schema.json +25 -0
  241. package/schemas/company-brain.schema.json +1158 -0
  242. package/schemas/creation-operation.schema.json +365 -0
  243. package/schemas/maestro-snapshot.schema.json +2716 -0
  244. package/skills/README.md +3 -3
  245. package/skills/core/onboarding/SKILL.md +50 -0
  246. package/skills/core/orkastery-bootstrap/SKILL.md +95 -65
  247. package/skills/core/thread-state/SKILL.md +3 -1
  248. package/skills/governance/decision-triage/SKILL.md +4 -4
  249. package/skills/governance/narrative-guardian/SKILL.md +1 -1
  250. package/skills/governance/roadmap-keeper/SKILL.md +1 -1
  251. package/skills/governance/scope-check-capability-map/SKILL.md +17 -7
  252. package/skills/observability/thread-tracing/SKILL.md +1 -1
  253. package/skills/phases/check-quality/SKILL.md +2 -2
  254. package/skills/phases/goal-definition/SKILL.md +1 -1
  255. package/skills/phases/master-metrics/SKILL.md +9 -7
  256. package/skills/phases/plan-specification/SKILL.md +1 -1
  257. package/skills/phases/ship-release/SKILL.md +1 -1
package/dist/docs.js ADDED
@@ -0,0 +1,828 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.LIMITE_DO_PARAGRAFO = exports.LIMITE_DA_FRASE = exports.CATEGORIAS = exports.DIMENSOES = exports.ESTADOS_DA_ESPECIFICACAO = exports.DIR_PADROES = exports.DIR_ROADMAP = exports.DIR_PRODUTO = void 0;
37
+ exports.separarFrontmatter = separarFrontmatter;
38
+ exports.carregarDocs = carregarDocs;
39
+ exports.resolverBase = resolverBase;
40
+ exports.comandosDaAjuda = comandosDaAjuda;
41
+ exports.verificarDocs = verificarDocs;
42
+ exports.renderizarRelance = renderizarRelance;
43
+ exports.renderizarEstado = renderizarEstado;
44
+ exports.textoDaVerificacao = textoDaVerificacao;
45
+ exports.escreverYaml = escreverYaml;
46
+ exports.mergeDaThread = mergeDaThread;
47
+ exports.sincronizarDocs = sincronizarDocs;
48
+ exports.tabelaDoRoadmap = tabelaDoRoadmap;
49
+ exports.tabelaDoProduto = tabelaDoProduto;
50
+ exports.textoDaSincronizacao = textoDaSincronizacao;
51
+ exports.dirDosModelos = dirDosModelos;
52
+ exports.iniciarDocs = iniciarDocs;
53
+ /**
54
+ * I-44: documentacao como codigo, com paridade entre doc, roadmap e repositorio.
55
+ *
56
+ * Dois padroes do dono vivem no repositorio de cada produto (`docs/padroes/`): documentacao
57
+ * de produto (plataforma > sistema > modulo > feature) e roadmap (itens RM). Cada pagina tem
58
+ * TRES leitores ao mesmo tempo, e este modulo cobra os tres:
59
+ *
60
+ * - humano com TDAH: resposta primeiro ("Em uma frase"), secoes previsiveis, paragrafo curto;
61
+ * - verificador de paridade: o que a pagina afirma sobre codigo, testes e merge e conferido
62
+ * contra o repositorio e o git; divergencia vira erro de lint, nao opiniao;
63
+ * - modelos e agentes: frontmatter YAML com chaves e valores fixos e IDs estaveis.
64
+ *
65
+ * A pagina carrega fatos de SDLC do metodo Orkastery ligados ao PRODUTO (thread, fase, CHECK,
66
+ * merge). Micro-decisoes de conducao ficam no ledger, nunca na doc.
67
+ *
68
+ * `verificarDocs` so le docs e git, entao roda no CI. `sincronizarDocs` le tambem o estado das
69
+ * threads (que nao e versionado) e por isso roda na maquina do projeto.
70
+ */
71
+ const fs = __importStar(require("node:fs"));
72
+ const path = __importStar(require("node:path"));
73
+ const node_child_process_1 = require("node:child_process");
74
+ const yaml_1 = require("./yaml");
75
+ const thread_1 = require("./thread");
76
+ const ledger_1 = require("./ledger");
77
+ exports.DIR_PRODUTO = 'docs/produto';
78
+ exports.DIR_ROADMAP = 'docs/roadmap';
79
+ exports.DIR_PADROES = 'docs/padroes';
80
+ const TIPOS = ['plataforma', 'sistema', 'modulo', 'feature', 'roadmap'];
81
+ const PADRAO_ID = {
82
+ plataforma: /^PLAT-\d{2}$/,
83
+ sistema: /^SYS-\d{2}$/,
84
+ modulo: /^MOD-\d{2}$/,
85
+ feature: /^FEAT-\d{3}$/,
86
+ roadmap: /^RM-\d{3}$/,
87
+ };
88
+ /** Hierarquia do padrao de produto; o roadmap pode ter item pai do proprio roadmap. */
89
+ const PAI = {
90
+ sistema: PADRAO_ID.plataforma,
91
+ modulo: PADRAO_ID.sistema,
92
+ feature: PADRAO_ID.modulo,
93
+ roadmap: PADRAO_ID.roadmap,
94
+ };
95
+ exports.ESTADOS_DA_ESPECIFICACAO = ['vigente', 'em desenvolvimento', 'proposto', 'descontinuado'];
96
+ /** Estados independentes do padrao de roadmap (secao 3.6), com os valores exatos do padrao. */
97
+ exports.DIMENSOES = {
98
+ ciclo: ['Discovery', 'Backlog', 'Refinamento', 'Pronto para desenvolvimento', 'Em desenvolvimento',
99
+ 'Em validação', 'Piloto', 'Disponível', 'Concluído', 'Bloqueado', 'Cancelado', 'Descontinuado'],
100
+ documentacao: ['Rascunho', 'Em revisão', 'Aprovada', 'Desatualizada'],
101
+ codigo: ['Não iniciado', 'Branch criada', 'PR aberto', 'Mesclado'],
102
+ testes: ['Não iniciados', 'Em execução', 'Aprovados', 'Falhando'],
103
+ deploy: ['Não implantado', 'Dev', 'Staging', 'Produção'],
104
+ exposicao: ['Flag desligada', 'Piloto/Canary', 'Parcial', 'Geral'],
105
+ habilitacao: ['Pendente', 'Em andamento', 'Concluída'],
106
+ };
107
+ exports.CATEGORIAS = ['iniciativa', 'épico', 'feature', 'melhoria'];
108
+ const SECOES = {
109
+ plataforma: ['Contexto e limites'],
110
+ sistema: ['Contexto e limites'],
111
+ modulo: ['Contexto e limites'],
112
+ feature: ['Comportamento', 'Dados e contratos', 'Operação e controle', 'Histórico'],
113
+ roadmap: ['Problema e resultado', 'Escopo e validação', 'Plano e decisões', 'Estado com evidências',
114
+ 'Responsabilidades e histórico'],
115
+ };
116
+ const CHAVES = {
117
+ plataforma: ['id', 'tipo', 'titulo', 'owner', 'estado', 'verificado_em'],
118
+ sistema: ['id', 'tipo', 'titulo', 'owner', 'estado', 'pai', 'verificado_em'],
119
+ modulo: ['id', 'tipo', 'titulo', 'owner', 'estado', 'pai', 'verificado_em'],
120
+ feature: ['id', 'tipo', 'titulo', 'owner', 'aprovador', 'estado', 'pai', 'roadmap', 'verificado_em', 'versao', 'fontes'],
121
+ roadmap: ['id', 'tipo', 'titulo', 'categoria', 'owner', 'atualizado_em', 'features', 'estado', 'evidencias'],
122
+ };
123
+ /** Limite da frase de abertura: cabe numa linha de leitura rapida. */
124
+ exports.LIMITE_DA_FRASE = 240;
125
+ /** Acima disso o paragrafo pede topicos (leitura com TDAH). */
126
+ exports.LIMITE_DO_PARAGRAFO = 600;
127
+ const ehMapa = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
128
+ const lista = (v) => v === undefined || v === null ? [] : Array.isArray(v) ? v.map(String) : [String(v)];
129
+ /** Separa o frontmatter (`---` ... `---`) do corpo. `bruto` null quando a pagina nao tem. */
130
+ function separarFrontmatter(texto) {
131
+ const t = texto.replace(/\r\n/g, '\n');
132
+ if (!t.startsWith('---\n'))
133
+ return { bruto: null, corpo: t };
134
+ const fim = t.indexOf('\n---\n', 3);
135
+ if (fim < 0)
136
+ return { bruto: null, corpo: t };
137
+ return { bruto: t.slice(4, fim + 1), corpo: t.slice(fim + 5) };
138
+ }
139
+ function paginas(raiz, dir) {
140
+ const abs = path.join(raiz, dir);
141
+ if (!fs.existsSync(abs))
142
+ return [];
143
+ // `_modelo-*.md` e `README.md` sao modelo e indice, nao paginas de entidade.
144
+ return fs.readdirSync(abs)
145
+ .filter((n) => n.endsWith('.md') && !n.startsWith('_') && n !== 'README.md')
146
+ .sort()
147
+ .map((n) => `${dir}/${n}`);
148
+ }
149
+ function carregarDocs(raiz) {
150
+ const docs = [];
151
+ const achados = [];
152
+ for (const arquivo of [...paginas(raiz, exports.DIR_PRODUTO), ...paginas(raiz, exports.DIR_ROADMAP)]) {
153
+ const { bruto, corpo } = separarFrontmatter(fs.readFileSync(path.join(raiz, arquivo), 'utf8'));
154
+ if (bruto === null) {
155
+ achados.push(erro(arquivo, undefined, 'docs.frontmatter.ausente', 'página sem frontmatter YAML', 'comece o arquivo com as chaves do modelo entre duas linhas ---'));
156
+ continue;
157
+ }
158
+ let dados;
159
+ try {
160
+ dados = (0, yaml_1.lerYaml)(bruto);
161
+ }
162
+ catch (e) {
163
+ achados.push(erro(arquivo, undefined, 'docs.frontmatter.invalido', `frontmatter inválido: ${e.message}`));
164
+ continue;
165
+ }
166
+ if (!ehMapa(dados)) {
167
+ achados.push(erro(arquivo, undefined, 'docs.frontmatter.invalido', 'o frontmatter precisa ser um mapa de chaves'));
168
+ continue;
169
+ }
170
+ docs.push({ arquivo, tipo: dados.tipo, id: String(dados.id ?? ''), dados, corpo });
171
+ }
172
+ return { docs, achados };
173
+ }
174
+ function erro(arquivo, id, regra, mensagem, correcao) {
175
+ return { arquivo, ...(id ? { id } : {}), regra, gravidade: 'erro', mensagem, ...(correcao ? { correcao } : {}) };
176
+ }
177
+ function aviso(arquivo, id, regra, mensagem, correcao) {
178
+ return { arquivo, ...(id ? { id } : {}), regra, gravidade: 'aviso', mensagem, ...(correcao ? { correcao } : {}) };
179
+ }
180
+ function git(raiz, args) {
181
+ const r = (0, node_child_process_1.spawnSync)('git', args, { cwd: raiz, encoding: 'utf8' });
182
+ return { ok: r.status === 0, saida: (r.stdout ?? '').trim() };
183
+ }
184
+ /** Resolve a branch base: local, depois `origin/`, porque o CI faz checkout destacado. */
185
+ function resolverBase(raiz, preferida = 'main') {
186
+ for (const ref of [preferida, `origin/${preferida}`]) {
187
+ if (git(raiz, ['rev-parse', '--verify', '--quiet', `${ref}^{commit}`]).ok)
188
+ return ref;
189
+ }
190
+ return null;
191
+ }
192
+ const ISO_COM_FUSO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})$/;
193
+ /**
194
+ * Os comandos de primeiro e segundo nivel que a ajuda do CLI declara (`accounts list`, `verify`).
195
+ * Alternativas com barra (`brain status|inventory`) viram um subcomando cada.
196
+ */
197
+ function comandosDaAjuda(ajuda) {
198
+ const conhecidos = new Set();
199
+ for (const linha of ajuda.split('\n')) {
200
+ const m = /^ {2}([a-z][a-z-]*)(?: ([a-z][a-z|-]*))?/.exec(linha);
201
+ if (!m)
202
+ continue;
203
+ conhecidos.add(m[1]);
204
+ for (const sub of (m[2] ?? '').split('|'))
205
+ if (sub)
206
+ conhecidos.add(`${m[1]} ${sub}`);
207
+ }
208
+ return conhecidos;
209
+ }
210
+ function verificarDocs(raiz, opcoes = {}) {
211
+ const { docs, achados } = carregarDocs(raiz);
212
+ const porId = new Map();
213
+ for (const d of docs) {
214
+ if (!TIPOS.includes(d.tipo)) {
215
+ achados.push(erro(d.arquivo, d.id || undefined, 'docs.tipo', `tipo "${String(d.dados.tipo)}" desconhecido`, `use um de: ${TIPOS.join(', ')}`));
216
+ continue;
217
+ }
218
+ if (!PADRAO_ID[d.tipo].test(d.id)) {
219
+ achados.push(erro(d.arquivo, d.id || undefined, 'docs.id.formato', `ID "${d.id}" fora do padrão de ${d.tipo}`, `formato esperado: ${PADRAO_ID[d.tipo].source.replace(/[\^$\\]/g, '')}`));
220
+ }
221
+ const nome = path.posix.basename(d.arquivo);
222
+ if (!(nome === `${d.id}.md` || nome.startsWith(`${d.id}-`))) {
223
+ achados.push(erro(d.arquivo, d.id, 'docs.id.arquivo', 'o nome do arquivo não começa pelo ID', `renomeie para ${d.id}-<assunto>.md`));
224
+ }
225
+ const pastaCerta = d.tipo === 'roadmap' ? exports.DIR_ROADMAP : exports.DIR_PRODUTO;
226
+ if (path.posix.dirname(d.arquivo) !== pastaCerta) {
227
+ achados.push(erro(d.arquivo, d.id, 'docs.pasta', `${d.tipo} mora em ${pastaCerta}`));
228
+ }
229
+ const anterior = porId.get(d.id);
230
+ if (anterior) {
231
+ achados.push(erro(d.arquivo, d.id, 'docs.id.duplicado', `ID repetido: também em ${anterior.arquivo}`, 'IDs são estáveis e únicos; renumere o novo, nunca o antigo'));
232
+ }
233
+ else {
234
+ porId.set(d.id, d);
235
+ }
236
+ }
237
+ const base = opcoes.semGit ? null : resolverBase(raiz, opcoes.baseBranch);
238
+ if (!opcoes.semGit && !base) {
239
+ achados.push(aviso('.', undefined, 'docs.git.base', `branch base "${opcoes.baseBranch ?? 'main'}" não encontrada: a paridade com o git ficou de fora`, 'rode com o histórico do git disponível (fetch-depth: 0 no CI)'));
240
+ }
241
+ const comandos = opcoes.ajudaDoCli ? comandosDaAjuda(opcoes.ajudaDoCli) : null;
242
+ for (const d of docs) {
243
+ if (!TIPOS.includes(d.tipo))
244
+ continue;
245
+ verificarChaves(d, achados);
246
+ verificarReferencias(raiz, d, porId, achados);
247
+ verificarLeitura(d, achados);
248
+ verificarFontes(raiz, d, comandos, achados);
249
+ if (d.tipo === 'roadmap') {
250
+ verificarEstadoDoRoadmap(raiz, d, base, achados);
251
+ verificarTabelasDeEstado(d, achados);
252
+ }
253
+ }
254
+ return { docs, achados };
255
+ }
256
+ function verificarChaves(d, achados) {
257
+ for (const k of CHAVES[d.tipo]) {
258
+ if (!(k in d.dados)) {
259
+ achados.push(erro(d.arquivo, d.id, 'docs.frontmatter.chave', `falta a chave "${k}"`, 'copie a chave do modelo; lacuna é "A definir — responsável, prazo"'));
260
+ }
261
+ }
262
+ if (d.tipo !== 'roadmap' && 'estado' in d.dados) {
263
+ const e = String(d.dados.estado);
264
+ if (!exports.ESTADOS_DA_ESPECIFICACAO.includes(e)) {
265
+ achados.push(erro(d.arquivo, d.id, 'docs.frontmatter.valor', `estado "${e}" fora do padrão`, `use um de: ${exports.ESTADOS_DA_ESPECIFICACAO.join(', ')}`));
266
+ }
267
+ }
268
+ if (d.tipo === 'roadmap') {
269
+ const cat = String(d.dados.categoria ?? '');
270
+ if ('categoria' in d.dados && !exports.CATEGORIAS.includes(cat)) {
271
+ achados.push(erro(d.arquivo, d.id, 'docs.frontmatter.valor', `categoria "${cat}" fora do padrão`, `use uma de: ${exports.CATEGORIAS.join(', ')}`));
272
+ }
273
+ const estado = d.dados.estado;
274
+ if ('estado' in d.dados && !ehMapa(estado)) {
275
+ achados.push(erro(d.arquivo, d.id, 'docs.frontmatter.valor', 'estado do roadmap é um mapa de dimensões', `dimensões: ${Object.keys(exports.DIMENSOES).join(', ')}`));
276
+ }
277
+ else if (ehMapa(estado)) {
278
+ for (const [dim, valores] of Object.entries(exports.DIMENSOES)) {
279
+ if (!(dim in estado)) {
280
+ achados.push(erro(d.arquivo, d.id, 'docs.frontmatter.chave', `falta a dimensão estado.${dim}`, 'o padrão atualiza cada dimensão separadamente'));
281
+ }
282
+ else if (!valores.includes(String(estado[dim]))) {
283
+ achados.push(erro(d.arquivo, d.id, 'docs.frontmatter.valor', `estado.${dim} "${String(estado[dim])}" fora do padrão`, `use um de: ${valores.join(', ')}`));
284
+ }
285
+ }
286
+ for (const dim of Object.keys(estado)) {
287
+ if (!(dim in exports.DIMENSOES)) {
288
+ achados.push(erro(d.arquivo, d.id, 'docs.frontmatter.chave', `dimensão desconhecida estado.${dim}`));
289
+ }
290
+ }
291
+ }
292
+ }
293
+ for (const k of ['verificado_em', 'atualizado_em']) {
294
+ if (k in d.dados && !ISO_COM_FUSO.test(String(d.dados[k]))) {
295
+ achados.push(erro(d.arquivo, d.id, 'docs.data', `${k} "${String(d.dados[k])}" não é ISO 8601 com fuso`, 'exemplo: 2026-09-24T20:00:00-03:00'));
296
+ }
297
+ }
298
+ // Valor de modelo esquecido no frontmatter: `<...>` ou ID zerado (FEAT-000, MOD-00, RM-000).
299
+ const visitar = (v, onde) => {
300
+ if (typeof v === 'string') {
301
+ if (/^<[^>]*>$/.test(v.trim()) || /^(PLAT|SYS|MOD|FEAT|RM)-0+$/.test(v.trim()) || /@0{7}$/.test(v.trim())) {
302
+ achados.push(erro(d.arquivo, d.id, 'docs.modelo', `valor do modelo não preenchido em ${onde}: ${v}`));
303
+ }
304
+ }
305
+ else if (Array.isArray(v))
306
+ v.forEach((x, i) => visitar(x, `${onde}[${i}]`));
307
+ else if (ehMapa(v))
308
+ for (const [k, x] of Object.entries(v))
309
+ visitar(x, onde ? `${onde}.${k}` : k);
310
+ };
311
+ visitar(d.dados, '');
312
+ }
313
+ // Tabelas de estado do corpo do roadmap: geradas do frontmatter, nunca escritas a mao.
314
+ const RELANCE_INI = '<!-- ork-docs:relance:inicio -->';
315
+ const RELANCE_FIM = '<!-- ork-docs:relance:fim -->';
316
+ const ESTADO_INI = '<!-- ork-docs:estado:inicio -->';
317
+ const ESTADO_FIM = '<!-- ork-docs:estado:fim -->';
318
+ const ROTULO_DA_DIMENSAO = {
319
+ ciclo: 'Ciclo do item', documentacao: 'Documentação', codigo: 'Código', testes: 'Testes',
320
+ deploy: 'Deploy', exposicao: 'Exposição', habilitacao: 'Habilitação',
321
+ };
322
+ function evidenciaDe(ev) {
323
+ if (ev === undefined || ev === null)
324
+ return '—';
325
+ if (!ehMapa(ev))
326
+ return celula(ev);
327
+ const partes = [];
328
+ if (ev.commit !== undefined && ev.commit !== null)
329
+ partes.push(`commit \`${String(ev.commit)}\``);
330
+ if (ev.pr !== undefined && ev.pr !== null)
331
+ partes.push(`PR #${String(ev.pr)}`);
332
+ for (const [k, v] of Object.entries(ev)) {
333
+ if (k !== 'commit' && k !== 'pr' && v !== null && v !== undefined)
334
+ partes.push(`${k}: ${celula(v)}`);
335
+ }
336
+ return partes.length ? partes.join(' · ') : '—';
337
+ }
338
+ /** Linha de relance: o estado inteiro numa tabela de uma linha, logo abaixo do "Em uma frase". */
339
+ function renderizarRelance(d) {
340
+ const e = ehMapa(d.dados.estado) ? d.dados.estado : {};
341
+ const dims = ['ciclo', 'codigo', 'testes', 'deploy', 'exposicao'];
342
+ return [`| ${dims.map((k) => ROTULO_DA_DIMENSAO[k]).join(' | ')} |`, `| ${dims.map(() => '---').join(' | ')} |`,
343
+ `| ${dims.map((k) => celula(e[k])).join(' | ')} |`].join('\n');
344
+ }
345
+ /** Tabela "Estado com evidências" do padrão (3.6), com a evidência que o frontmatter carrega. */
346
+ function renderizarEstado(d) {
347
+ const e = ehMapa(d.dados.estado) ? d.dados.estado : {};
348
+ const ev = ehMapa(d.dados.evidencias) ? d.dados.evidencias : {};
349
+ const data = String(d.dados.atualizado_em ?? '').slice(0, 10) || '—';
350
+ const L = ['| Dimensão | Estado | Evidência | Data | Responsável |', '| --- | --- | --- | --- | --- |'];
351
+ for (const dim of Object.keys(exports.DIMENSOES)) {
352
+ L.push(`| ${ROTULO_DA_DIMENSAO[dim]} | ${celula(e[dim])} | ${evidenciaDe(ev[dim])} | ${data} | ${celula(d.dados.owner)} |`);
353
+ }
354
+ return L.join('\n');
355
+ }
356
+ function blocoEntre(corpo, ini, fim) {
357
+ const i = corpo.indexOf(ini);
358
+ const f = corpo.indexOf(fim);
359
+ if (i < 0 || f < i)
360
+ return null;
361
+ return corpo.slice(i + ini.length, f).trim();
362
+ }
363
+ /** Conteudo gerado entre marcadores, com linha em branco em volta (tabela colada em comentario reprova no MD058). */
364
+ function entreMarcadores(conteudo) {
365
+ return conteudo ? `\n\n${conteudo}\n\n` : '\n';
366
+ }
367
+ function substituirBloco(corpo, ini, fim, conteudo) {
368
+ const i = corpo.indexOf(ini);
369
+ const f = corpo.indexOf(fim);
370
+ if (i < 0 || f < i)
371
+ return corpo;
372
+ return `${corpo.slice(0, i + ini.length)}${entreMarcadores(conteudo)}${corpo.slice(f)}`;
373
+ }
374
+ function verificarTabelasDeEstado(d, achados) {
375
+ for (const [ini, fim, render, nome] of [
376
+ [RELANCE_INI, RELANCE_FIM, renderizarRelance, 'a linha de relance'],
377
+ [ESTADO_INI, ESTADO_FIM, renderizarEstado, 'a tabela "Estado com evidências"'],
378
+ ]) {
379
+ const atual = blocoEntre(d.corpo, ini, fim);
380
+ if (atual === null) {
381
+ achados.push(erro(d.arquivo, d.id, 'docs.paridade.estado', `falta ${nome} gerada do frontmatter`, 'copie os marcadores do modelo e rode ork docs sincronizar --escrever'));
382
+ }
383
+ else if (atual !== render(d)) {
384
+ achados.push(erro(d.arquivo, d.id, 'docs.paridade.estado', `${nome} diverge do frontmatter`, 'o estado se edita no frontmatter; rode ork docs sincronizar --escrever para regerar a tabela'));
385
+ }
386
+ }
387
+ }
388
+ function verificarReferencias(raiz, d, porId, achados) {
389
+ const esperadoDoPai = PAI[d.tipo];
390
+ const pai = d.dados.pai;
391
+ if (esperadoDoPai && pai !== undefined && pai !== null) {
392
+ const idPai = String(pai);
393
+ if (!esperadoDoPai.test(idPai)) {
394
+ achados.push(erro(d.arquivo, d.id, 'docs.referencia', `pai "${idPai}" não é do nível acima de ${d.tipo}`));
395
+ }
396
+ else if (!porId.has(idPai)) {
397
+ achados.push(erro(d.arquivo, d.id, 'docs.referencia', `pai ${idPai} não existe`, 'crie a página do pai ou corrija o ID'));
398
+ }
399
+ }
400
+ else if (esperadoDoPai && d.tipo !== 'roadmap' && (pai === undefined || pai === null)) {
401
+ achados.push(erro(d.arquivo, d.id, 'docs.referencia', `${d.tipo} precisa de pai`));
402
+ }
403
+ const cruzadas = d.tipo === 'feature' ? [['roadmap', 'roadmap', 'features']]
404
+ : d.tipo === 'roadmap' ? [['features', 'feature', 'roadmap']] : [];
405
+ for (const [chave, tipoAlvo, chaveDeVolta] of cruzadas) {
406
+ for (const alvo of lista(d.dados[chave])) {
407
+ const outro = porId.get(alvo);
408
+ if (!outro || outro.tipo !== tipoAlvo) {
409
+ achados.push(erro(d.arquivo, d.id, 'docs.referencia', `${chave} cita ${alvo}, que não existe como ${tipoAlvo}`));
410
+ }
411
+ else if (!lista(outro.dados[chaveDeVolta]).includes(d.id)) {
412
+ achados.push(aviso(d.arquivo, d.id, 'docs.referencia.reciproca', `${alvo} não cita ${d.id} de volta`, `acrescente ${d.id} em ${chaveDeVolta} de ${alvo}`));
413
+ }
414
+ }
415
+ }
416
+ // Links relativos no corpo: link quebrado e paridade perdida entre paginas.
417
+ for (const m of semCodigo(d.corpo).matchAll(/\]\(([^)\s#]+)(?:#[^)\s]*)?\)/g)) {
418
+ const alvo = m[1];
419
+ if (/^[a-z][a-z0-9+.-]*:/i.test(alvo))
420
+ continue;
421
+ const abs = path.resolve(path.dirname(path.join(raiz, d.arquivo)), decodeURIComponent(alvo));
422
+ if (!fs.existsSync(abs)) {
423
+ achados.push(erro(d.arquivo, d.id, 'docs.link', `link quebrado: ${alvo}`));
424
+ }
425
+ }
426
+ }
427
+ /** Tira blocos e trechos de codigo, que nao sao texto de leitura nem link. */
428
+ function semCodigo(texto) {
429
+ return texto.replace(/```[\s\S]*?```/g, '').replace(/`[^`\n]*`/g, '');
430
+ }
431
+ function verificarLeitura(d, achados) {
432
+ const linhas = d.corpo.split('\n');
433
+ const h1 = linhas.find((l) => l.startsWith('# '));
434
+ if (!h1 || !h1.includes(d.id)) {
435
+ achados.push(erro(d.arquivo, d.id, 'docs.leitura.titulo', 'o título (#) não começa pelo ID', `# ${d.id} — <nome>: humano e agente acham a página pelo mesmo nome`));
436
+ }
437
+ const frase = linhas.find((l) => /^>\s*\*\*Em uma frase:\*\*/.test(l));
438
+ if (!frase) {
439
+ achados.push(erro(d.arquivo, d.id, 'docs.leitura.resumo', 'falta a linha "> **Em uma frase:**" logo abaixo do título', 'escreva o que a página responde, antes de qualquer detalhe'));
440
+ }
441
+ else {
442
+ const n = frase.replace(/^>\s*\*\*Em uma frase:\*\*\s*/, '').trim().length;
443
+ if (n === 0)
444
+ achados.push(erro(d.arquivo, d.id, 'docs.leitura.resumo', 'a frase de abertura está vazia'));
445
+ else if (n > exports.LIMITE_DA_FRASE) {
446
+ achados.push(erro(d.arquivo, d.id, 'docs.leitura.resumo', `a frase de abertura tem ${n} caracteres (limite ${exports.LIMITE_DA_FRASE})`, 'corte para uma ideia só; o resto vai em tópicos'));
447
+ }
448
+ }
449
+ const h2 = new Set(linhas.filter((l) => l.startsWith('## ')).map((l) => l.slice(3).trim()));
450
+ for (const s of SECOES[d.tipo]) {
451
+ if (!h2.has(s)) {
452
+ achados.push(erro(d.arquivo, d.id, 'docs.secao', `falta a seção "## ${s}"`, 'se não se aplica, escreva dentro dela: Não aplicável — motivo'));
453
+ }
454
+ }
455
+ const texto = semCodigo(d.corpo);
456
+ for (const m of texto.matchAll(/\[(\.\.\.|[^\]\n]{1,60})\](?!\()/g)) {
457
+ achados.push(erro(d.arquivo, d.id, 'docs.modelo', `campo do modelo não preenchido: [${m[1]}]`, 'preencha, ou escreva "A definir — responsável, prazo" / "Não aplicável — motivo"'));
458
+ }
459
+ for (const p of texto.split(/\n\s*\n/)) {
460
+ const bloco = p.trim();
461
+ if (!bloco || /^(#|>|\||[-*+] |\d+\. |<!--)/.test(bloco))
462
+ continue;
463
+ if (bloco.length > exports.LIMITE_DO_PARAGRAFO) {
464
+ achados.push(aviso(d.arquivo, d.id, 'docs.leitura.paragrafo', `parágrafo com ${bloco.length} caracteres`, 'quebre em tópicos: uma ideia por linha'));
465
+ }
466
+ }
467
+ }
468
+ function verificarFontes(raiz, d, comandos, achados) {
469
+ const f = d.dados.fontes;
470
+ if (f === undefined || f === null)
471
+ return;
472
+ if (!ehMapa(f)) {
473
+ achados.push(erro(d.arquivo, d.id, 'docs.frontmatter.valor', 'fontes é um mapa (codigo, testes, simbolos, contratos, comandos)'));
474
+ return;
475
+ }
476
+ const existe = (p) => fs.existsSync(path.join(raiz, p));
477
+ for (const chave of ['codigo', 'testes', 'docs']) {
478
+ for (const p of lista(f[chave])) {
479
+ if (path.isAbsolute(p) || p.split('/').includes('..')) {
480
+ achados.push(erro(d.arquivo, d.id, 'docs.paridade.fonte', `${p}: caminho fora do repositório`, 'use caminho relativo à raiz'));
481
+ }
482
+ else if (!existe(p)) {
483
+ achados.push(erro(d.arquivo, d.id, 'docs.paridade.fonte', `${p} não existe no repositório`, 'a doc descreve o código que existe: corrija o caminho ou a página'));
484
+ }
485
+ }
486
+ }
487
+ for (const s of lista(f.simbolos)) {
488
+ const [arq, simbolo] = s.split('#');
489
+ if (!arq || !simbolo) {
490
+ achados.push(erro(d.arquivo, d.id, 'docs.paridade.simbolo', `símbolo "${s}" fora do formato arquivo#nome`));
491
+ }
492
+ else if (!existe(arq)) {
493
+ achados.push(erro(d.arquivo, d.id, 'docs.paridade.simbolo', `${arq} não existe (símbolo ${simbolo})`));
494
+ }
495
+ else if (!new RegExp(`\\b${escapar(simbolo)}\\b`).test(fs.readFileSync(path.join(raiz, arq), 'utf8'))) {
496
+ achados.push(erro(d.arquivo, d.id, 'docs.paridade.simbolo', `${simbolo} não aparece em ${arq}`, 'o código mudou de nome ou a página ficou para trás'));
497
+ }
498
+ }
499
+ const codigo = lista(f.codigo).filter(existe).map((p) => fs.readFileSync(path.join(raiz, p), 'utf8'));
500
+ for (const c of lista(f.contratos)) {
501
+ if (!codigo.some((t) => t.includes(c))) {
502
+ achados.push(erro(d.arquivo, d.id, 'docs.paridade.contrato', `o contrato ${c} não aparece nos arquivos de fontes.codigo`));
503
+ }
504
+ }
505
+ if (comandos) {
506
+ for (const cmd of lista(f.comandos)) {
507
+ const [programa, topo = '', sub = ''] = cmd.trim().split(/\s+/);
508
+ if (programa !== 'ork')
509
+ continue;
510
+ // Comando com subcomandos declarados (`accounts list`) exige o par; argumento (`<id>`) nao conta.
511
+ const temSubcomandos = [...comandos].some((c) => c.startsWith(`${topo} `));
512
+ const exigePar = temSubcomandos && /^[a-z][a-z-]*$/.test(sub);
513
+ if (!comandos.has(topo) || (exigePar && !comandos.has(`${topo} ${sub}`))) {
514
+ achados.push(erro(d.arquivo, d.id, 'docs.paridade.comando', `o CLI não declara "${[programa, topo, sub].join(' ').trim()}"`, 'confira `ork --help`: a doc descreve o CLI que existe'));
515
+ }
516
+ }
517
+ }
518
+ }
519
+ function escapar(s) {
520
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
521
+ }
522
+ function verificarEstadoDoRoadmap(raiz, d, base, achados) {
523
+ const e = ehMapa(d.dados.estado) ? d.dados.estado : {};
524
+ const ev = ehMapa(d.dados.evidencias) ? d.dados.evidencias : {};
525
+ const codigoEv = ehMapa(ev.codigo) ? ev.codigo : {};
526
+ const commit = codigoEv.commit === undefined || codigoEv.commit === null ? '' : String(codigoEv.commit);
527
+ const [ciclo, codigo, testes, deploy] = [e.ciclo, e.codigo, e.testes, e.deploy].map((v) => String(v ?? ''));
528
+ // Coerencia entre dimensoes (padrao 3.6): o status nao pode esconder o que falta.
529
+ if (ciclo === 'Concluído' && codigo !== 'Mesclado') {
530
+ achados.push(erro(d.arquivo, d.id, 'docs.estado.coerencia', 'ciclo Concluído com código que não está Mesclado'));
531
+ }
532
+ if (ciclo === 'Concluído' && testes !== 'Aprovados') {
533
+ achados.push(erro(d.arquivo, d.id, 'docs.estado.coerencia', 'ciclo Concluído com testes que não estão Aprovados'));
534
+ }
535
+ if (deploy === 'Produção' && codigo !== 'Mesclado') {
536
+ achados.push(erro(d.arquivo, d.id, 'docs.estado.coerencia', 'deploy em Produção com código que não está Mesclado'));
537
+ }
538
+ if (ciclo === 'Bloqueado' && !ehMapa(d.dados.bloqueio)) {
539
+ achados.push(erro(d.arquivo, d.id, 'docs.estado.bloqueio', 'Bloqueado exige o mapa "bloqueio"', 'bloqueio: motivo, dependencia, responsavel, proxima_revisao'));
540
+ }
541
+ if (codigo === 'Mesclado' && !commit) {
542
+ achados.push(erro(d.arquivo, d.id, 'docs.paridade.git', 'código Mesclado sem o commit do merge', 'preencha evidencias.codigo.commit (ork docs sincronizar faz isso a partir do ledger e do git)'));
543
+ }
544
+ if (commit && base) {
545
+ if (!git(raiz, ['cat-file', '-e', `${commit}^{commit}`]).ok) {
546
+ achados.push(erro(d.arquivo, d.id, 'docs.paridade.git', `o commit ${commit} não existe neste repositório`));
547
+ }
548
+ else if (codigo === 'Mesclado' && !git(raiz, ['merge-base', '--is-ancestor', commit, base]).ok) {
549
+ achados.push(erro(d.arquivo, d.id, 'docs.paridade.git', `o commit ${commit} não está na ${base}: o merge não aconteceu`, 'volte o estado.codigo para "PR aberto" ou "Branch criada" até o merge'));
550
+ }
551
+ }
552
+ }
553
+ // --------------------------------------------------------------------------------------------
554
+ // Saida para humano (TDAH) e para agente
555
+ function textoDaVerificacao(docs, achados) {
556
+ const conta = (t) => docs.filter((d) => d.tipo === t).length;
557
+ const erros = achados.filter((a) => a.gravidade === 'erro');
558
+ const avisos = achados.filter((a) => a.gravidade === 'aviso');
559
+ const L = [];
560
+ L.push(`Documentação: ${docs.length} páginas — ${conta('feature')} features, ${conta('roadmap')} itens de roadmap, `
561
+ + `${conta('plataforma') + conta('sistema') + conta('modulo')} de contexto`);
562
+ L.push(erros.length === 0
563
+ ? `OK: nenhum erro de paridade${avisos.length ? ` · ${avisos.length} aviso(s) de leitura` : ''}`
564
+ : `REPROVADO: ${erros.length} erro(s)${avisos.length ? ` · ${avisos.length} aviso(s)` : ''}`);
565
+ for (const [titulo, grupo] of [['Erros', erros], ['Avisos', avisos]]) {
566
+ if (grupo.length === 0)
567
+ continue;
568
+ L.push('', titulo);
569
+ for (const a of grupo) {
570
+ L.push(` ${(a.id ?? a.arquivo).padEnd(9)} ${a.mensagem}`);
571
+ if (a.correcao)
572
+ L.push(` ${''.padEnd(9)} → ${a.correcao}`);
573
+ }
574
+ }
575
+ return L.join('\n');
576
+ }
577
+ // --------------------------------------------------------------------------------------------
578
+ // Escrita de YAML (subconjunto que o `lerYaml` le de volta)
579
+ function escalarYaml(v) {
580
+ if (v === null)
581
+ return 'null';
582
+ if (typeof v !== 'string')
583
+ return String(v);
584
+ const precisaAspas = v === '' || /^[\s]|[\s]$/.test(v) || /^[-?:,[\]{}#&*!|>'"%@`]/.test(v)
585
+ || /: |\s#|:$/.test(v) || /^(true|false|yes|no|null|~)$/i.test(v) || /^-?\d*\.?\d+$/.test(v);
586
+ if (!precisaAspas)
587
+ return v;
588
+ // O leitor do nucleo tira as aspas sem interpretar escape: a aspa escolhida nao pode aparecer dentro.
589
+ if (!v.includes('"'))
590
+ return `"${v}"`;
591
+ if (!v.includes("'"))
592
+ return `'${v}'`;
593
+ throw new Error(`escreverYaml: valor com aspas simples e duplas fora do subconjunto: ${v}`);
594
+ }
595
+ function escreverYaml(v, indent = 0) {
596
+ const pad = ' '.repeat(indent);
597
+ if (!ehMapa(v))
598
+ throw new Error('escreverYaml: a raiz é um mapa');
599
+ const L = [];
600
+ for (const [k, valor] of Object.entries(v)) {
601
+ if (ehMapa(valor)) {
602
+ if (Object.keys(valor).length === 0) {
603
+ L.push(`${pad}${k}: {}`);
604
+ continue;
605
+ }
606
+ L.push(`${pad}${k}:`);
607
+ L.push(escreverYaml(valor, indent + 2));
608
+ }
609
+ else if (Array.isArray(valor)) {
610
+ const escalares = valor.every((x) => !ehMapa(x) && !Array.isArray(x));
611
+ // A lista inline do leitor separa por virgula: item com virgula ou colchete vai em bloco.
612
+ const inlineSeguro = escalares && valor.every((x) => typeof x !== 'string' || !/[,[\]]/.test(x));
613
+ const inline = `[${valor.map((x) => escalarYaml(x)).join(', ')}]`;
614
+ if (inlineSeguro && (valor.length === 0 || inline.length <= 72))
615
+ L.push(`${pad}${k}: ${inline}`);
616
+ else {
617
+ L.push(`${pad}${k}:`);
618
+ for (const x of valor) {
619
+ if (ehMapa(x) || Array.isArray(x))
620
+ throw new Error('escreverYaml: lista de mapas fora do subconjunto');
621
+ L.push(`${pad} - ${escalarYaml(x)}`);
622
+ }
623
+ }
624
+ }
625
+ else {
626
+ L.push(`${pad}${k}: ${escalarYaml(valor)}`);
627
+ }
628
+ }
629
+ return L.join('\n');
630
+ }
631
+ function isoComFusoLocal(d) {
632
+ const off = -d.getTimezoneOffset();
633
+ const s = off >= 0 ? '+' : '-';
634
+ const p = (n) => String(Math.trunc(Math.abs(n))).padStart(2, '0');
635
+ const local = new Date(d.getTime() + off * 60000).toISOString().slice(0, 19);
636
+ return `${local}${s}${p(off / 60)}:${p(off % 60)}`;
637
+ }
638
+ /**
639
+ * Commit de merge da thread na base: evento `ship_done` do ledger, ou mensagem `ship(<thread>)`.
640
+ *
641
+ * Vale o PRIMEIRO merge que esta na base, nao o ultimo. E ele que prova quando o codigo do item
642
+ * passou a estar mesclado, e ele nao muda: com o ultimo, o PR que so atualiza a doc do item
643
+ * (mesclado de novo como `ship(<thread>)`) viraria o novo "merge do item" a cada rodada, num
644
+ * laco em que sincronizar a doc sempre gera outra sincronizacao para fazer.
645
+ */
646
+ function mergeDaThread(raiz, thread, base) {
647
+ try {
648
+ const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, thread));
649
+ const ship = eventos.filter((e) => e.tipo === 'ship_done' && typeof e.mergeSha === 'string')
650
+ .find((e) => git(raiz, ['merge-base', '--is-ancestor', String(e.mergeSha), base]).ok);
651
+ if (ship)
652
+ return { sha: String(ship.mergeSha).slice(0, 7), fonte: 'ledger ship_done' };
653
+ }
654
+ catch { /* thread sem estado nesta maquina: cai para o git */ }
655
+ // Sem `-n 1`: o git corta antes de inverter, e `--reverse -n 1` devolveria o mais recente.
656
+ const log = git(raiz, ['log', base, '--reverse', '--format=%H', '--fixed-strings', `--grep=ship(${thread})`]);
657
+ const primeiro = log.ok ? log.saida.split('\n')[0] : '';
658
+ if (primeiro)
659
+ return { sha: primeiro.slice(0, 7), fonte: `git log ${base}` };
660
+ return null;
661
+ }
662
+ function temBranch(raiz, thread) {
663
+ const r = git(raiz, ['for-each-ref', '--format=%(refname:short)', `refs/heads/ork/${thread}-*`, `refs/remotes/origin/ork/${thread}-*`]);
664
+ return r.ok && r.saida.length > 0;
665
+ }
666
+ const ORDEM_DO_CODIGO = exports.DIMENSOES.codigo;
667
+ function sincronizarDocs(raiz, opcoes = {}) {
668
+ const base = resolverBase(raiz, opcoes.baseBranch) ?? (opcoes.baseBranch ?? 'main');
669
+ const agora = opcoes.agora ?? (() => isoComFusoLocal(new Date()));
670
+ const { docs } = carregarDocs(raiz);
671
+ const mudancas = [];
672
+ for (const d of docs.filter((x) => x.tipo === 'roadmap')) {
673
+ const sdlc = ehMapa(d.dados.sdlc) ? d.dados.sdlc : null;
674
+ const thread = sdlc && typeof sdlc.thread === 'string' ? sdlc.thread : null;
675
+ if (!thread)
676
+ continue;
677
+ const antes = mudancas.length;
678
+ // Grava um fato no caminho dado (`estado.codigo`), criando mapas so quando ha o que gravar.
679
+ const definir = (caminho, para, fonte) => {
680
+ const chaves = caminho.split('.');
681
+ let alvo = d.dados;
682
+ for (const k of chaves.slice(0, -1))
683
+ alvo = ehMapa(alvo[k]) ? alvo[k] : (alvo[k] = {});
684
+ const ultima = chaves[chaves.length - 1];
685
+ if (String(alvo[ultima] ?? '') === para)
686
+ return;
687
+ mudancas.push({ id: d.id, arquivo: d.arquivo, campo: caminho, de: String(alvo[ultima] ?? ''), para, fonte });
688
+ alvo[ultima] = para;
689
+ };
690
+ const merge = mergeDaThread(raiz, thread, base);
691
+ if (merge) {
692
+ definir('estado.codigo', 'Mesclado', merge.fonte);
693
+ definir('evidencias.codigo.commit', merge.sha, merge.fonte);
694
+ }
695
+ else if (temBranch(raiz, thread)) {
696
+ // So avanca: um PR aberto registrado por humano nao volta para "Branch criada".
697
+ const atual = String((ehMapa(d.dados.estado) ? d.dados.estado.codigo : null) ?? 'Não iniciado');
698
+ if (ORDEM_DO_CODIGO.indexOf(atual) < ORDEM_DO_CODIGO.indexOf('Branch criada')) {
699
+ definir('estado.codigo', 'Branch criada', 'git for-each-ref');
700
+ }
701
+ }
702
+ try {
703
+ const t = JSON.parse(fs.readFileSync(path.join((0, thread_1.dirThread)(raiz, thread), 'thread.json'), 'utf8'));
704
+ for (const [campo, valor] of [['fase', t.faseAtual], ['status', t.status]]) {
705
+ if (typeof valor === 'string')
706
+ definir(`sdlc.${campo}`, valor, 'thread.json');
707
+ }
708
+ }
709
+ catch { /* estado da thread ausente nesta maquina: so o git conta */ }
710
+ if (mudancas.length !== antes) {
711
+ d.dados.atualizado_em = agora();
712
+ if (opcoes.escrever)
713
+ regravarFrontmatter(raiz, d);
714
+ }
715
+ }
716
+ // As tabelas de estado do corpo sao vistas do frontmatter: regeradas em todo item de roadmap,
717
+ // inclusive quando a pessoa mudou o frontmatter a mao (ciclo, deploy, exposicao...).
718
+ for (const d of docs.filter((x) => x.tipo === 'roadmap')) {
719
+ const novo = substituirBloco(substituirBloco(d.corpo, RELANCE_INI, RELANCE_FIM, renderizarRelance(d)), ESTADO_INI, ESTADO_FIM, renderizarEstado(d));
720
+ if (novo !== d.corpo) {
721
+ if (!mudancas.some((m) => m.id === d.id)) {
722
+ mudancas.push({ id: d.id, arquivo: d.arquivo, campo: 'tabelas de estado', de: 'desatualizadas', para: 'regeradas', fonte: 'frontmatter' });
723
+ }
724
+ d.corpo = novo;
725
+ if (opcoes.escrever)
726
+ regravarFrontmatter(raiz, d);
727
+ }
728
+ }
729
+ const indices = [
730
+ atualizarIndice(raiz, `${exports.DIR_ROADMAP}/README.md`, tabelaDoRoadmap(docs), opcoes.escrever === true),
731
+ atualizarIndice(raiz, `${exports.DIR_PRODUTO}/README.md`, tabelaDoProduto(docs), opcoes.escrever === true),
732
+ ].filter((x) => x !== null);
733
+ return { mudancas, indices };
734
+ }
735
+ /** Regrava frontmatter e corpo do documento (o corpo em memoria ja traz as tabelas regeradas). */
736
+ function regravarFrontmatter(raiz, d) {
737
+ fs.writeFileSync(path.join(raiz, d.arquivo), `---\n${escreverYaml(d.dados)}\n---\n${d.corpo}`);
738
+ }
739
+ const INICIO = '<!-- ork-docs:indice:inicio -->';
740
+ const FIM = '<!-- ork-docs:indice:fim -->';
741
+ /** Substitui o bloco entre os marcadores; devolve o arquivo quando mudou (ou mudaria). */
742
+ function atualizarIndice(raiz, arquivo, tabela, escrever) {
743
+ const abs = path.join(raiz, arquivo);
744
+ if (!fs.existsSync(abs))
745
+ return null;
746
+ const atual = fs.readFileSync(abs, 'utf8');
747
+ const i = atual.indexOf(INICIO);
748
+ const f = atual.indexOf(FIM);
749
+ if (i < 0 || f < i)
750
+ return null;
751
+ const novo = `${atual.slice(0, i + INICIO.length)}${entreMarcadores(tabela)}${atual.slice(f)}`;
752
+ if (novo === atual)
753
+ return null;
754
+ if (escrever)
755
+ fs.writeFileSync(abs, novo);
756
+ return arquivo;
757
+ }
758
+ function celula(v) {
759
+ return String(v ?? '—').replace(/\|/g, '\\|');
760
+ }
761
+ function tabelaDoRoadmap(docs) {
762
+ const itens = docs.filter((d) => d.tipo === 'roadmap').sort((a, b) => a.id.localeCompare(b.id));
763
+ const L = ['| ID | Resultado | Ciclo | Código | Testes | Deploy | Atualizado |', '| --- | --- | --- | --- | --- | --- | --- |'];
764
+ for (const d of itens) {
765
+ const e = ehMapa(d.dados.estado) ? d.dados.estado : {};
766
+ const data = String(d.dados.atualizado_em ?? '').slice(0, 10);
767
+ L.push(`| [${d.id}](${path.posix.basename(d.arquivo)}) | ${celula(d.dados.titulo)} | ${celula(e.ciclo)} | `
768
+ + `${celula(e.codigo)} | ${celula(e.testes)} | ${celula(e.deploy)} | ${data || '—'} |`);
769
+ }
770
+ return L.join('\n');
771
+ }
772
+ function tabelaDoProduto(docs) {
773
+ const ordem = ['plataforma', 'sistema', 'modulo', 'feature'];
774
+ const itens = docs.filter((d) => d.tipo !== 'roadmap')
775
+ .sort((a, b) => ordem.indexOf(a.tipo) - ordem.indexOf(b.tipo) || a.id.localeCompare(b.id));
776
+ const L = ['| ID | Nome | Tipo | Estado | Pai | Verificado |', '| --- | --- | --- | --- | --- | --- |'];
777
+ for (const d of itens) {
778
+ L.push(`| [${d.id}](${path.posix.basename(d.arquivo)}) | ${celula(d.dados.titulo)} | ${d.tipo} | ${celula(d.dados.estado)} | `
779
+ + `${celula(d.dados.pai)} | ${String(d.dados.verificado_em ?? '').slice(0, 10) || '—'} |`);
780
+ }
781
+ return L.join('\n');
782
+ }
783
+ function textoDaSincronizacao(r, escreveu) {
784
+ const L = [];
785
+ const ids = new Set(r.mudancas.map((m) => m.id));
786
+ L.push(r.mudancas.length === 0 && r.indices.length === 0
787
+ ? 'Sincronizado: a documentação já bate com o ledger e o git.'
788
+ : `${escreveu ? 'Atualizado' : 'A atualizar'}: ${ids.size} item(ns) de roadmap, ${r.indices.length} índice(s).`);
789
+ for (const m of r.mudancas)
790
+ L.push(` ${m.id.padEnd(7)} ${m.campo}: ${m.de || '—'} → ${m.para} (${m.fonte})`);
791
+ for (const i of r.indices)
792
+ L.push(` índice ${i}`);
793
+ if (!escreveu && (r.mudancas.length || r.indices.length))
794
+ L.push('', 'Nada foi gravado. Para gravar: ork docs sincronizar --escrever');
795
+ return L.join('\n');
796
+ }
797
+ // --------------------------------------------------------------------------------------------
798
+ // Scaffolding: o mesmo padrao em todo produto conduzido pelo Orkastery
799
+ const ARQUIVOS_DO_PADRAO = [
800
+ ['padroes/documentacao-de-produto.md', `${exports.DIR_PADROES}/documentacao-de-produto.md`],
801
+ ['padroes/roadmap-de-produto.md', `${exports.DIR_PADROES}/roadmap-de-produto.md`],
802
+ ['produto/_modelo-feature.md', `${exports.DIR_PRODUTO}/_modelo-feature.md`],
803
+ ['produto/README.md', `${exports.DIR_PRODUTO}/README.md`],
804
+ ['roadmap/_modelo-item.md', `${exports.DIR_ROADMAP}/_modelo-item.md`],
805
+ ['roadmap/README.md', `${exports.DIR_ROADMAP}/README.md`],
806
+ ['markdownlint-cli2.jsonc', '.markdownlint-cli2.jsonc'],
807
+ ];
808
+ function dirDosModelos() {
809
+ for (const c of [path.join(__dirname, '..', 'assets', 'docs'), path.join(__dirname, '..', '..', 'assets', 'docs')]) {
810
+ if (fs.existsSync(c))
811
+ return c;
812
+ }
813
+ throw new Error('docs.modelos: assets/docs do nucleo nao encontrado');
814
+ }
815
+ /** Cria o que falta; nunca sobrescreve pagina existente. Devolve o que criou. */
816
+ function iniciarDocs(raiz) {
817
+ const origem = dirDosModelos();
818
+ const criados = [];
819
+ for (const [de, para] of ARQUIVOS_DO_PADRAO) {
820
+ const destino = path.join(raiz, para);
821
+ if (fs.existsSync(destino))
822
+ continue;
823
+ fs.mkdirSync(path.dirname(destino), { recursive: true, mode: 0o755 });
824
+ fs.copyFileSync(path.join(origem, de), destino);
825
+ criados.push(para);
826
+ }
827
+ return criados;
828
+ }