@orkastery/cli 0.5.3 → 0.5.6

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 (212) hide show
  1. package/adapters/claude-code/README.md +158 -0
  2. package/adapters/claude-code/mods/autoridade.json +56 -0
  3. package/adapters/claude-code/mods/ork-clima/.claude-plugin/plugin.json +14 -0
  4. package/adapters/claude-code/mods/ork-clima/hooks/contas.ts +283 -0
  5. package/adapters/claude-code/mods/ork-clima/hooks/hooks.json +3 -0
  6. package/adapters/claude-code/mods/ork-clima/hooks/previsao.ts +393 -0
  7. package/adapters/claude-code/mods/ork-clima/hooks/register.ts +205 -0
  8. package/adapters/claude-code/mods/ork-clima/orkastery-mod.json +5 -0
  9. package/adapters/claude-code/mods/ork-clima/types/index.d.ts +80 -0
  10. package/adapters/claude-code/mods/ork-freio/.claude-plugin/plugin.json +14 -0
  11. package/adapters/claude-code/mods/ork-freio/hooks/carga.ts +160 -0
  12. package/adapters/claude-code/mods/ork-freio/hooks/conferencia.ts +309 -0
  13. package/adapters/claude-code/mods/ork-freio/hooks/hooks.json +3 -0
  14. package/adapters/claude-code/mods/ork-freio/hooks/lote.ts +270 -0
  15. package/adapters/claude-code/mods/ork-freio/hooks/register.ts +285 -0
  16. package/adapters/claude-code/mods/ork-freio/orkastery-mod.json +5 -0
  17. package/adapters/claude-code/mods/ork-freio/types/index.d.ts +127 -0
  18. package/adapters/claude-code/mods/ork-mesa/.claude-plugin/plugin.json +14 -0
  19. package/adapters/claude-code/mods/ork-mesa/hooks/cartoes.tsx +246 -0
  20. package/adapters/claude-code/mods/ork-mesa/hooks/decidir.ts +110 -0
  21. package/adapters/claude-code/mods/ork-mesa/hooks/fila.ts +253 -0
  22. package/adapters/claude-code/mods/ork-mesa/hooks/hooks.json +3 -0
  23. package/adapters/claude-code/mods/ork-mesa/hooks/obvia.ts +98 -0
  24. package/adapters/claude-code/mods/ork-mesa/hooks/register.tsx +493 -0
  25. package/adapters/claude-code/mods/ork-mesa/orkastery-mod.json +5 -0
  26. package/adapters/claude-code/mods/ork-mesa/types/index.d.ts +131 -0
  27. package/adapters/claude-code/mods/ork-pista/.claude-plugin/plugin.json +14 -0
  28. package/adapters/claude-code/mods/ork-pista/hooks/acoes.ts +71 -0
  29. package/adapters/claude-code/mods/ork-pista/hooks/ci.ts +117 -0
  30. package/adapters/claude-code/mods/ork-pista/hooks/hooks.json +3 -0
  31. package/adapters/claude-code/mods/ork-pista/hooks/pista.tsx +356 -0
  32. package/adapters/claude-code/mods/ork-pista/hooks/register.tsx +439 -0
  33. package/adapters/claude-code/mods/ork-pista/orkastery-mod.json +5 -0
  34. package/adapters/claude-code/mods/ork-pista/types/index.d.ts +85 -0
  35. package/adapters/claude-code/mods/ork-ponte/.claude-plugin/plugin.json +13 -0
  36. package/adapters/claude-code/mods/ork-ponte/hooks/faixa.tsx +57 -0
  37. package/adapters/claude-code/mods/ork-ponte/hooks/guarda.ts +410 -0
  38. package/adapters/claude-code/mods/ork-ponte/hooks/hooks.json +3 -0
  39. package/adapters/claude-code/mods/ork-ponte/hooks/mostrado.ts +55 -0
  40. package/adapters/claude-code/mods/ork-ponte/hooks/nucleo.ts +157 -0
  41. package/adapters/claude-code/mods/ork-ponte/hooks/papel.ts +42 -0
  42. package/adapters/claude-code/mods/ork-ponte/hooks/register.ts +331 -0
  43. package/adapters/claude-code/mods/ork-ponte/hooks/vigia.ts +76 -0
  44. package/adapters/claude-code/mods/ork-ponte/orkastery-mod.json +5 -0
  45. package/adapters/claude-code/mods/ork-ponte/types/index.d.ts +178 -0
  46. package/adapters/claude-code/mods/ork-portao/.claude-plugin/plugin.json +14 -0
  47. package/adapters/claude-code/mods/ork-portao/hooks/hooks.json +3 -0
  48. package/adapters/claude-code/mods/ork-portao/hooks/lease.ts +201 -0
  49. package/adapters/claude-code/mods/ork-portao/hooks/plano.ts +304 -0
  50. package/adapters/claude-code/mods/ork-portao/hooks/register.ts +339 -0
  51. package/adapters/claude-code/mods/ork-portao/hooks/verify.ts +152 -0
  52. package/adapters/claude-code/mods/ork-portao/orkastery-mod.json +5 -0
  53. package/adapters/claude-code/mods/ork-portao/types/index.d.ts +60 -0
  54. package/adapters/claude-code/mods/ork-radar/.claude-plugin/plugin.json +14 -0
  55. package/adapters/claude-code/mods/ork-radar/hooks/colisao.ts +420 -0
  56. package/adapters/claude-code/mods/ork-radar/hooks/hooks.json +3 -0
  57. package/adapters/claude-code/mods/ork-radar/hooks/mapa.tsx +322 -0
  58. package/adapters/claude-code/mods/ork-radar/hooks/register.tsx +470 -0
  59. package/adapters/claude-code/mods/ork-radar/orkastery-mod.json +5 -0
  60. package/adapters/claude-code/mods/ork-radar/types/index.d.ts +72 -0
  61. package/adapters/claude-code/mods/ork-sentinela/.claude-plugin/plugin.json +14 -0
  62. package/adapters/claude-code/mods/ork-sentinela/hooks/alertas.ts +85 -0
  63. package/adapters/claude-code/mods/ork-sentinela/hooks/despacho.ts +156 -0
  64. package/adapters/claude-code/mods/ork-sentinela/hooks/hooks.json +3 -0
  65. package/adapters/claude-code/mods/ork-sentinela/hooks/register.ts +511 -0
  66. package/adapters/claude-code/mods/ork-sentinela/hooks/vigia.ts +186 -0
  67. package/adapters/claude-code/mods/ork-sentinela/orkastery-mod.json +5 -0
  68. package/adapters/claude-code/mods/ork-sentinela/types/index.d.ts +65 -0
  69. package/adapters/claude-code/mods/ork-torre/.claude-plugin/plugin.json +14 -0
  70. package/adapters/claude-code/mods/ork-torre/hooks/agenda.ts +60 -0
  71. package/adapters/claude-code/mods/ork-torre/hooks/detalhe.tsx +236 -0
  72. package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +42 -0
  73. package/adapters/claude-code/mods/ork-torre/hooks/hooks.json +3 -0
  74. package/adapters/claude-code/mods/ork-torre/hooks/painel.tsx +344 -0
  75. package/adapters/claude-code/mods/ork-torre/hooks/rede.ts +97 -0
  76. package/adapters/claude-code/mods/ork-torre/hooks/register.tsx +437 -0
  77. package/adapters/claude-code/mods/ork-torre/orkastery-mod.json +5 -0
  78. package/adapters/claude-code/mods/ork-torre/types/index.d.ts +108 -0
  79. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +1 -1
  80. package/adapters/openclaw/README.md +2 -1
  81. package/adapters/openclaw/openclaw.plugin.json +1 -1
  82. package/adapters/openclaw/package.json +1 -1
  83. package/assets/docs-en/markdownlint-cli2.jsonc +22 -0
  84. package/assets/docs-en/padroes/documentacao-de-produto.md +135 -0
  85. package/assets/docs-en/padroes/roadmap-de-produto.md +100 -0
  86. package/assets/docs-en/produto/README.md +14 -0
  87. package/assets/docs-en/produto/_modelo-feature.md +62 -0
  88. package/assets/docs-en/roadmap/README.md +14 -0
  89. package/assets/docs-en/roadmap/_modelo-item.md +75 -0
  90. package/dist/adapters/claude-bg.js +77 -13
  91. package/dist/adapters/codex.js +10 -1
  92. package/dist/agents-md.js +24 -15
  93. package/dist/auditrun.js +17 -2
  94. package/dist/board.js +4 -3
  95. package/dist/branch-de-estado.js +23 -1
  96. package/dist/catalogo.js +33 -0
  97. package/dist/ci.js +13 -7
  98. package/dist/claims-conferir.js +342 -0
  99. package/dist/claims.js +3 -1
  100. package/dist/company-brain-client.js +36 -1
  101. package/dist/company-brain-journal.js +2 -1
  102. package/dist/company-brain-migration.js +36 -1
  103. package/dist/conducao-sentinela.js +388 -0
  104. package/dist/conducao-texto.js +12 -18
  105. package/dist/conducao.js +122 -30
  106. package/dist/conhecimento.js +490 -0
  107. package/dist/docs.js +22 -8
  108. package/dist/doctor-pulse-cron.js +182 -27
  109. package/dist/doctor-rede.js +18 -17
  110. package/dist/doctor.js +115 -111
  111. package/dist/entrega-pr.js +22 -21
  112. package/dist/escopo-escrita.js +10 -1
  113. package/dist/estado-thread.js +23 -1
  114. package/dist/experiencia-instalacao.js +45 -25
  115. package/dist/fabrica-batida.js +310 -0
  116. package/dist/fabrica-estado.js +38 -8
  117. package/dist/fabrica-publicar.js +3 -1
  118. package/dist/fila-pesada.js +444 -0
  119. package/dist/fix.js +3 -2
  120. package/dist/forja.js +1 -1
  121. package/dist/gates.js +3 -28
  122. package/dist/handoff.js +10 -6
  123. package/dist/hitl-local-receipt.js +2 -0
  124. package/dist/hitl-resumo.js +29 -3
  125. package/dist/hitl-tempo-parado.js +4 -4
  126. package/dist/hitl.js +29 -33
  127. package/dist/horario.js +14 -7
  128. package/dist/hosts.js +52 -1
  129. package/dist/impedimento.js +108 -14
  130. package/dist/index.js +623 -521
  131. package/dist/indice.js +7 -1
  132. package/dist/init.js +43 -47
  133. package/dist/instabilidade.js +144 -0
  134. package/dist/integracoes-locais.js +2 -0
  135. package/dist/intelligence-graph-cli.js +92 -21
  136. package/dist/intelligence-graph-contexto.js +56 -17
  137. package/dist/intelligence-graph-contract.js +2 -1
  138. package/dist/intelligence-graph-extract-md.js +56 -4
  139. package/dist/intelligence-graph-extract-ts.js +71 -3
  140. package/dist/intelligence-graph-query.js +66 -19
  141. package/dist/ledger.js +15 -1
  142. package/dist/liveness.js +6 -3
  143. package/dist/locale-ajuda.js +761 -0
  144. package/dist/locale-gerados.js +160 -0
  145. package/dist/locale-master.js +99 -0
  146. package/dist/locale-onboarding.js +92 -0
  147. package/dist/locale-phase.js +151 -0
  148. package/dist/locale-pulse.js +211 -0
  149. package/dist/locale-retry.js +385 -0
  150. package/dist/locale-roadmap.js +102 -0
  151. package/dist/locale-ship.js +494 -0
  152. package/dist/locale-thread.js +277 -0
  153. package/dist/locale-verify.js +125 -0
  154. package/dist/locale.js +714 -0
  155. package/dist/manifest.js +129 -3
  156. package/dist/maquina.js +23 -4
  157. package/dist/master-digest.js +6 -1
  158. package/dist/master-nota.js +4 -6
  159. package/dist/master.js +21 -24
  160. package/dist/mcp-artifacts.js +11 -2
  161. package/dist/mcp-git.js +23 -6
  162. package/dist/mcp-grafo.js +7 -4
  163. package/dist/mcp-server.js +8 -2
  164. package/dist/mcp-ship.js +3 -1
  165. package/dist/mcp-verify.js +6 -1
  166. package/dist/modos.js +3 -6
  167. package/dist/network-roadmap.js +5 -2
  168. package/dist/noctum-arco.js +911 -0
  169. package/dist/noctum-capacidade.js +141 -0
  170. package/dist/noctum-envelope.js +810 -0
  171. package/dist/noctum-execucao.js +304 -0
  172. package/dist/noctum-fila.js +280 -0
  173. package/dist/ocupacao.js +29 -2
  174. package/dist/onboarding.js +40 -21
  175. package/dist/orkmind.js +9 -2
  176. package/dist/orquestracao.js +13 -12
  177. package/dist/parado-no-condutor.js +34 -9
  178. package/dist/phase.js +94 -39
  179. package/dist/policies.js +4 -4
  180. package/dist/portfolio.js +4 -2
  181. package/dist/postura-local.js +234 -0
  182. package/dist/preflight.js +11 -10
  183. package/dist/procedencia.js +225 -0
  184. package/dist/projeto-alvo.js +127 -4
  185. package/dist/prompts.js +6 -2
  186. package/dist/pulse-delivery.js +13 -2
  187. package/dist/pulse.js +52 -19
  188. package/dist/ratelimit.js +4 -2
  189. package/dist/redacao-saida.js +63 -0
  190. package/dist/rede-status.js +3 -2
  191. package/dist/retry.js +232 -148
  192. package/dist/roadmap-reservas.js +20 -3
  193. package/dist/roadmap-status.js +129 -33
  194. package/dist/runtime-context.js +181 -3
  195. package/dist/runtimes.js +2 -1
  196. package/dist/session-events.js +7 -3
  197. package/dist/session-watcher-claude.js +7 -1
  198. package/dist/session-watcher.js +185 -33
  199. package/dist/sessoes-contas.js +1 -1
  200. package/dist/ship-publicar.js +800 -0
  201. package/dist/ship.js +84 -81
  202. package/dist/steal.js +77 -0
  203. package/dist/thread-close.js +3 -2
  204. package/dist/thread.js +153 -82
  205. package/dist/util.js +8 -1
  206. package/dist/verify.js +141 -52
  207. package/dist/vida-da-thread.js +254 -0
  208. package/dist/worktree-local.js +165 -0
  209. package/dist/worktree.js +20 -0
  210. package/dist/yaml.js +27 -8
  211. package/package.json +1 -1
  212. package/schemas/code-artifact-graph.v1.schema.json +2 -1
@@ -0,0 +1,490 @@
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.LIMITES_DO_CONHECIMENTO = exports.REFERENCIA_DE_DECISAO = exports.NOME_DE_COLECAO = exports.ID_DE_DOCUMENTO = exports.CONTRATO_CONHECIMENTO = void 0;
37
+ exports.lerConhecimento = lerConhecimento;
38
+ /**
39
+ * RM-026 K7 (fatia 1): conhecimento escrito, no contrato `ork.conhecimento/v1`.
40
+ *
41
+ * Cada documento é um Markdown versionado na pasta que o `orkastery.yaml` declara (`conhecimento.dir`), com
42
+ * frontmatter fechado: `id` (`doc-...`), `titulo`, `colecoes` e, opcional, `referencias` a ids do Brain e a
43
+ * decisões `thread#linha` do ledger. A coleção é o nome no frontmatter, sem arquivo próprio.
44
+ *
45
+ * Só leitura. Documento inválido ou referência que não confere vira lacuna tipada, nunca resposta (D4). A pasta
46
+ * vem com o repositório e segue a fronteira da RM-047 (D6): relativa, dentro da raiz pelo caminho real, sem
47
+ * seguir link e sem executar nada. Nesta fatia a decisão confere no ledger canônico e o id do Brain só pela forma
48
+ * (D5); a CLI, as tools MCP e a conferência pelo Brain chegam nas fatias 2 e 3. Contrato:
49
+ * docs/referencia/contratos/conhecimento-rm026.md.
50
+ */
51
+ const fs = __importStar(require("node:fs"));
52
+ const path = __importStar(require("node:path"));
53
+ const company_brain_journal_1 = require("./company-brain-journal");
54
+ const docs_1 = require("./docs");
55
+ const portfolio_1 = require("./portfolio");
56
+ const saida_segura_1 = require("./saida-segura");
57
+ const thread_1 = require("./thread");
58
+ const yaml_1 = require("./yaml");
59
+ exports.CONTRATO_CONHECIMENTO = 'ork.conhecimento/v1';
60
+ /** D3: a forma do id do documento (a regra dos ids do portfólio), da coleção e da citação de decisão. */
61
+ exports.ID_DE_DOCUMENTO = /^doc-[a-z0-9][a-z0-9-]{2,47}$/;
62
+ exports.NOME_DE_COLECAO = /^[a-z0-9][a-z0-9-]{0,47}$/;
63
+ exports.REFERENCIA_DE_DECISAO = /^([a-zA-Z0-9][a-zA-Z0-9._-]{0,79})#([1-9][0-9]{0,8})$/;
64
+ const LIMITE_DO_TITULO = 160;
65
+ const COLECOES_POR_DOCUMENTO = 16;
66
+ const REFERENCIAS_POR_LISTA = 64;
67
+ /** D2: as chaves do frontmatter; qualquer outra invalida o documento. */
68
+ const CHAVES_DO_DOCUMENTO = ['id', 'titulo', 'colecoes', 'referencias'];
69
+ const CHAVES_DAS_REFERENCIAS = ['brain', 'decisoes'];
70
+ /** D5: as linhas do ledger que são decisão, as do dossiê; `human_decision` é relato sem recibo e não conta. */
71
+ const TIPOS_DE_DECISAO = ['autonomous_decision', 'hitl_requested', 'human_gate'];
72
+ /** D6: pastas do repositório que nunca guardam conhecimento, sem distinguir caixa (o disco do macOS não distingue). */
73
+ const PASTAS_RESERVADAS = ['.git', '.orkastery'];
74
+ const reservada = (segmento) => PASTAS_RESERVADAS.includes(segmento.toLowerCase());
75
+ /** D10: o invisível é o do `saida-segura.ts`, a regra única do núcleo para texto alheio (ZWJ e seletores de emoji valem). */
76
+ const INVISIVEIS = new RegExp(saida_segura_1.INVISIVEL.source, 'gu');
77
+ exports.LIMITES_DO_CONHECIMENTO = Object.freeze({
78
+ entradas: 5000, profundidade: 8, bytesPorDocumento: 1024 * 1024, bytesDoFrontmatter: 16 * 1024,
79
+ bytesNoTotal: 32 * 1024 * 1024, bytesDoLedger: 16 * 1024 * 1024,
80
+ });
81
+ const comparar = (a, b) => a < b ? -1 : a > b ? 1 : 0;
82
+ const ehMapa = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
83
+ /** Texto que veio do repositório, pronto para a resposta: numa linha, sem invisível e, com teto, cortado sem partir caractere. */
84
+ const limpo = (v, teto = Infinity) => Array.from((0, saida_segura_1.emUmaLinha)(v)).slice(0, teto).join('');
85
+ const relativo = (raizReal, abs) => path.relative(raizReal, abs).split(path.sep).join('/');
86
+ const LINK = 'link simbólico não é seguido; o documento é o arquivo versionado';
87
+ /** `stat` que nunca lança: a pasta que some ou não abre vale como ausente. */
88
+ const statOuNulo = (p) => { try {
89
+ return fs.statSync(p, { throwIfNoEntry: false }) ?? null;
90
+ }
91
+ catch {
92
+ return null;
93
+ } };
94
+ function lacuna(codigo, detalhe, onde = {}) {
95
+ return { codigo, arquivo: onde.arquivo ?? null, documento: onde.documento ?? null,
96
+ referencia: typeof onde.referencia === 'string' ? limpo(onde.referencia, 200) : null, detalhe };
97
+ }
98
+ /**
99
+ * D6: a pasta declarada, pelo caminho real. A forma recusa antes de tocar o disco (vazia, absoluta, `..`, `\`,
100
+ * invisível, a própria raiz, `.git` e `.orkastery`); o caminho real, com os links resolvidos, precisa ficar dentro
101
+ * da raiz.
102
+ */
103
+ function pastaDoConhecimento(raiz, dir) {
104
+ const segmentos = dir.split('/').filter((s) => s !== '' && s !== '.');
105
+ if (path.isAbsolute(dir) || dir.includes('\\') || saida_segura_1.INVISIVEL.test(dir) || !segmentos.length || segmentos.includes('..') ||
106
+ reservada(segmentos[0])) {
107
+ return lacuna('conhecimento.dir-invalido', 'conhecimento.dir precisa ser uma pasta relativa à raiz, sem "..", ' +
108
+ 'fora de .git e .orkastery, como docs/conhecimento; nada foi lido');
109
+ }
110
+ let raizReal, real;
111
+ try {
112
+ raizReal = fs.realpathSync(raiz);
113
+ real = fs.realpathSync(path.join(raizReal, ...segmentos));
114
+ }
115
+ catch {
116
+ return lacuna('conhecimento.dir-ausente', 'a pasta de conhecimento.dir não existe ou não abre');
117
+ }
118
+ const rel = path.relative(raizReal, real);
119
+ if (rel === '..' || rel.startsWith('..' + path.sep) || path.isAbsolute(rel)) {
120
+ return lacuna('conhecimento.dir-fora-da-raiz', 'pelo caminho real (links resolvidos), conhecimento.dir sai da raiz ' +
121
+ 'do projeto; o manifesto vem com o repositório e não escolhe caminho fora dela; nada foi lido');
122
+ }
123
+ if (rel === '' || reservada(rel.split(path.sep)[0]) || saida_segura_1.INVISIVEL.test(rel)) {
124
+ return lacuna('conhecimento.dir-invalido', 'pelo caminho real, conhecimento.dir é a própria raiz, cai em .git ou ' +
125
+ '.orkastery, ou tem caractere invisível; nada foi lido');
126
+ }
127
+ if (!statOuNulo(real)?.isDirectory())
128
+ return lacuna('conhecimento.dir-ausente', 'conhecimento.dir não é uma pasta');
129
+ return { real, raizReal };
130
+ }
131
+ /**
132
+ * Varre a pasta sem seguir link (D6) e sem ler entrada oculta (D8). Devolve os candidatos a documento, ou o
133
+ * detalhe do teto estourado (D7), que falha fechado. Entrada que não abre vira lacuna, nunca exceção.
134
+ */
135
+ function varrer(real, raizReal, limites, lacunas) {
136
+ const candidatos = [];
137
+ const pilha = [{ abs: real, profundidade: 0 }];
138
+ let entradas = 0;
139
+ while (pilha.length) {
140
+ const atual = pilha.pop();
141
+ // Uma pasta trocada por link depois de entrar na pilha não leva a varredura para fora: o caminho real é
142
+ // conferido logo antes da listagem. Num clone parado não há troca; a conferência fecha a janela larga.
143
+ let realDaPasta;
144
+ try {
145
+ realDaPasta = fs.realpathSync(atual.abs);
146
+ }
147
+ catch {
148
+ realDaPasta = null;
149
+ }
150
+ if (realDaPasta !== atual.abs) {
151
+ lacunas.push(lacuna('documento.link', LINK, { arquivo: relativo(raizReal, atual.abs) }));
152
+ continue;
153
+ }
154
+ let nomes;
155
+ try {
156
+ nomes = fs.readdirSync(atual.abs, { encoding: 'buffer' }).sort(Buffer.compare);
157
+ }
158
+ catch {
159
+ lacunas.push(lacuna('documento.nao-regular', 'a pasta não abriu para leitura; o que está nela não foi lido', { arquivo: relativo(raizReal, atual.abs) }));
160
+ continue;
161
+ }
162
+ for (const bruto of nomes) {
163
+ if (bruto[0] === 0x2e)
164
+ continue;
165
+ if (++entradas > limites.entradas)
166
+ return { teto: `a pasta passa de ${limites.entradas} entradas` };
167
+ const nome = bruto.toString('utf8');
168
+ if (!Buffer.from(nome, 'utf8').equals(bruto) || saida_segura_1.INVISIVEL.test(nome)) {
169
+ lacunas.push(lacuna('documento.nome-invalido', 'nome fora de UTF-8 ou com caractere invisível; não foi lido', { arquivo: relativo(raizReal, path.join(atual.abs, nome.replace(INVISIVEIS, '?'))) }));
170
+ continue;
171
+ }
172
+ const abs = path.join(atual.abs, nome), arquivo = relativo(raizReal, abs);
173
+ let st;
174
+ try {
175
+ st = fs.lstatSync(abs, { throwIfNoEntry: false });
176
+ }
177
+ catch {
178
+ lacunas.push(lacuna('documento.nao-regular', 'a entrada não abriu para leitura; não foi lida', { arquivo }));
179
+ continue;
180
+ }
181
+ if (!st)
182
+ continue;
183
+ if (st.isSymbolicLink()) {
184
+ lacunas.push(lacuna('documento.link', LINK, { arquivo }));
185
+ continue;
186
+ }
187
+ if (st.isDirectory()) {
188
+ if (atual.profundidade + 1 > limites.profundidade)
189
+ return { teto: `a pasta passa de ${limites.profundidade} níveis` };
190
+ pilha.push({ abs, profundidade: atual.profundidade + 1 });
191
+ continue;
192
+ }
193
+ if (!(0, docs_1.ehPaginaDeDocs)(nome))
194
+ continue;
195
+ if (!st.isFile()) {
196
+ lacunas.push(lacuna('documento.nao-regular', 'não é arquivo comum; não foi lido', { arquivo }));
197
+ continue;
198
+ }
199
+ candidatos.push({ abs, arquivo, st });
200
+ }
201
+ }
202
+ return candidatos;
203
+ }
204
+ /**
205
+ * Lê um arquivo comum sem seguir link (`O_NOFOLLOW`) e sem travar em FIFO (`O_NONBLOCK`), conferindo que é o
206
+ * mesmo do `lstat` e que cabe no teto. Devolve os bytes ou o motivo. Com `crescer` (o ledger, que só recebe linha
207
+ * no fim), vale o que existia no `fstat`, mesmo que o arquivo cresça durante a leitura; o documento que muda
208
+ * durante a leitura não vale.
209
+ */
210
+ function lerArquivo(abs, st, teto, crescer = false) {
211
+ let fd;
212
+ try {
213
+ fd = fs.openSync(abs, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
214
+ }
215
+ catch (e) {
216
+ return e.code === 'ELOOP' ? 'link' : 'nao-regular';
217
+ }
218
+ try {
219
+ const f = fs.fstatSync(fd);
220
+ if (!f.isFile() || f.dev !== st.dev || f.ino !== st.ino)
221
+ return 'nao-regular';
222
+ if (f.size > teto)
223
+ return 'grande';
224
+ const bytes = Buffer.alloc(crescer ? f.size : f.size + 1);
225
+ let lidos = 0, n;
226
+ while (lidos < bytes.length && (n = fs.readSync(fd, bytes, lidos, bytes.length - lidos, null)) > 0)
227
+ lidos += n;
228
+ if (crescer)
229
+ return bytes.subarray(0, lidos);
230
+ return lidos === f.size ? bytes.subarray(0, lidos) : 'nao-regular';
231
+ }
232
+ finally {
233
+ fs.closeSync(fd);
234
+ }
235
+ }
236
+ /**
237
+ * Valor de lista do frontmatter: lista de textos ou um texto só. Inteiro vira texto, sem perda; nulo, booleano
238
+ * e decimal (o `yes` que o YAML lê como `true`, a vírgula sobrando que vira nulo) recusam em vez de virar texto.
239
+ */
240
+ function lista(v) {
241
+ if (v === undefined || v === null)
242
+ return [];
243
+ const itens = [];
244
+ for (const b of Array.isArray(v) ? v : [v]) {
245
+ if (typeof b === 'string')
246
+ itens.push(b);
247
+ else if (typeof b === 'number' && Number.isSafeInteger(b))
248
+ itens.push(String(b));
249
+ else
250
+ return null;
251
+ }
252
+ return itens;
253
+ }
254
+ /** D2 e D3: o frontmatter vale inteiro ou devolve a lacuna do primeiro problema, com o id quando ele já vale. */
255
+ function validarFrontmatter(dados, arquivo) {
256
+ if (!ehMapa(dados))
257
+ return lacuna('documento.frontmatter-invalido', 'o frontmatter precisa ser um mapa de chaves', { arquivo });
258
+ const id = typeof dados.id === 'string' && exports.ID_DE_DOCUMENTO.test(dados.id) ? dados.id : null;
259
+ if (!id) {
260
+ return lacuna('documento.id-invalido', 'id precisa ser doc- seguido de 3 a 48 letras minúsculas, números ou hífens, ' +
261
+ 'começando por letra ou número', { arquivo });
262
+ }
263
+ const onde = { arquivo, documento: id };
264
+ const estranhas = Object.keys(dados).filter((k) => !CHAVES_DO_DOCUMENTO.includes(k));
265
+ if (estranhas.length) {
266
+ return lacuna('documento.chave-desconhecida', `chave fora do contrato: ${estranhas.map((k) => JSON.stringify(limpo(k, 40))).join(', ')} ` +
267
+ `(o frontmatter tem só ${CHAVES_DO_DOCUMENTO.join(', ')})`, onde);
268
+ }
269
+ const titulo = typeof dados.titulo === 'string' ? dados.titulo.trim() : '';
270
+ if (!titulo || titulo.length > LIMITE_DO_TITULO || saida_segura_1.INVISIVEL.test(titulo)) {
271
+ return lacuna('documento.titulo-invalido', `titulo precisa ser texto de 1 a ${LIMITE_DO_TITULO} caracteres, ` +
272
+ 'sem caractere de controle', onde);
273
+ }
274
+ const colecoes = lista(dados.colecoes);
275
+ if (!colecoes || !colecoes.length || colecoes.length > COLECOES_POR_DOCUMENTO ||
276
+ colecoes.some((c) => !exports.NOME_DE_COLECAO.test(c)) || new Set(colecoes).size !== colecoes.length) {
277
+ return lacuna('documento.colecoes-invalidas', `colecoes precisa listar de 1 a ${COLECOES_POR_DOCUMENTO} nomes ` +
278
+ 'de 1 a 48 letras minúsculas, números ou hífens, sem repetir', onde);
279
+ }
280
+ const refs = dados.referencias ?? null;
281
+ const brain = ehMapa(refs) ? lista(refs.brain) : [], decisoes = ehMapa(refs) ? lista(refs.decisoes) : [];
282
+ if ((refs !== null && !ehMapa(refs)) || (ehMapa(refs) && Object.keys(refs).some((k) => !CHAVES_DAS_REFERENCIAS.includes(k))) ||
283
+ !brain || brain.length > REFERENCIAS_POR_LISTA || !decisoes || decisoes.length > REFERENCIAS_POR_LISTA) {
284
+ return lacuna('documento.referencias-invalidas', 'referencias é um mapa só com brain e decisoes, cada uma uma lista ' +
285
+ `de até ${REFERENCIAS_POR_LISTA} textos`, onde);
286
+ }
287
+ return { id, titulo, colecoes: [...colecoes].sort(comparar), brain: [...new Set(brain)], decisoes: [...new Set(decisoes)] };
288
+ }
289
+ /**
290
+ * As linhas citadas do ledger canônico de uma thread (D5). O ledger é lido como a captura do Brain o lê
291
+ * (`readCycle`): só linhas completas, em UTF-8 exato, para o sha256 da linha ser o `source_hash` dela no Brain.
292
+ * Cada ledger é lido uma vez, e só as linhas citadas ficam na memória.
293
+ */
294
+ function linhasCitadas(raiz, thread, citadas, teto) {
295
+ let arquivo;
296
+ try {
297
+ arquivo = path.join((0, thread_1.dirThread)(raiz, thread), 'ledger.jsonl');
298
+ }
299
+ catch {
300
+ return { motivo: 'a pasta da thread no estado do projeto é link ou fica fora da regra de id' };
301
+ }
302
+ let st;
303
+ try {
304
+ st = fs.lstatSync(arquivo, { throwIfNoEntry: false });
305
+ }
306
+ catch (e) {
307
+ // A pasta da thread que é arquivo comum dá ENOTDIR: a thread não tem ledger, e nada lança.
308
+ if (e.code !== 'ENOTDIR')
309
+ return { motivo: `o ledger da thread ${thread} não abriu para leitura` };
310
+ }
311
+ if (!st)
312
+ return { motivo: `a thread ${thread} não tem ledger neste projeto` };
313
+ const bytes = lerArquivo(arquivo, st, teto, true);
314
+ if (!Buffer.isBuffer(bytes))
315
+ return { motivo: `o ledger da thread ${thread} não é arquivo comum de até ${teto / 1024 / 1024} MiB` };
316
+ const fim = bytes.lastIndexOf(10) + 1;
317
+ const completo = bytes.subarray(0, fim).toString('utf8');
318
+ if (!Buffer.from(completo, 'utf8').equals(bytes.subarray(0, fim)))
319
+ return { motivo: `o ledger da thread ${thread} não é UTF-8` };
320
+ const todas = completo.split('\n').slice(0, -1);
321
+ const linhas = new Map();
322
+ for (const n of citadas)
323
+ if (n <= todas.length)
324
+ linhas.set(n, todas[n - 1]);
325
+ return { linhas, completas: todas.length };
326
+ }
327
+ /** D5: a referência `thread#linha` confere quando a linha completa existe, é da thread e é uma decisão. */
328
+ function conferirDecisao(ref, ledgers) {
329
+ const m = exports.REFERENCIA_DE_DECISAO.exec(ref);
330
+ if (!m)
331
+ return { codigo: 'referencia.invalida', detalhe: 'decisão é <thread>#<linha>, com o id da thread e a linha do ledger a partir de 1' };
332
+ const thread = m[1], linha = Number(m[2]);
333
+ const ledger = ledgers.get(thread);
334
+ if ('motivo' in ledger)
335
+ return { codigo: 'referencia.decisao-ausente', detalhe: ledger.motivo };
336
+ const bruta = ledger.linhas.get(linha);
337
+ if (bruta === undefined) {
338
+ return { codigo: 'referencia.decisao-ausente', detalhe: `o ledger da thread ${thread} tem ${ledger.completas} linha(s) completa(s)` };
339
+ }
340
+ let evento = null;
341
+ try {
342
+ const lido = JSON.parse(bruta);
343
+ if (lido && typeof lido === 'object' && !Array.isArray(lido))
344
+ evento = lido;
345
+ }
346
+ catch { /* linha que não é JSON não é decisão */ }
347
+ const onde = `a linha ${linha} do ledger da thread ${thread}`;
348
+ if (!evento)
349
+ return { codigo: 'referencia.nao-e-decisao', detalhe: `${onde} não é um evento JSON` };
350
+ if (evento.thread !== thread)
351
+ return { codigo: 'referencia.nao-e-decisao', detalhe: `${onde} é de outra thread` };
352
+ const tipo = TIPOS_DE_DECISAO.find((t) => t === evento?.tipo);
353
+ if (!tipo) {
354
+ const visto = typeof evento.tipo === 'string' && /^[a-z_]{1,40}$/.test(evento.tipo) ? evento.tipo : 'sem tipo conhecido';
355
+ return { codigo: 'referencia.nao-e-decisao', detalhe: `${onde} é ${visto}, não decisão (${TIPOS_DE_DECISAO.join(', ')})` };
356
+ }
357
+ return { ref, thread, linha, tipo, fonte: `threads/${thread}/ledger.jsonl#L${linha}`, sha256: (0, company_brain_journal_1.bytesHash)(bruta) };
358
+ }
359
+ /**
360
+ * Lê o conhecimento declarado no manifesto: documentos válidos, coleções e lacunas, tudo em ordem estável.
361
+ * Nunca lança por causa do conteúdo do repositório; o que não vale vira lacuna tipada.
362
+ */
363
+ function lerConhecimento(c, limites = exports.LIMITES_DO_CONHECIMENTO) {
364
+ const declarado = c.manifesto.conhecimento?.dir ?? null;
365
+ const dir = declarado === null ? null : limpo(declarado);
366
+ const so = (l) => ({ contrato: exports.CONTRATO_CONHECIMENTO, dir, documentos: [], colecoes: [], lacunas: [l] });
367
+ if (declarado === null)
368
+ return so(lacuna('conhecimento.nao-declarado', 'o orkastery.yaml não declara conhecimento.dir'));
369
+ const pasta = pastaDoConhecimento(c.raiz, declarado);
370
+ if ('codigo' in pasta)
371
+ return so(pasta);
372
+ const teto = (detalhe) => so(lacuna('conhecimento.limite', `${detalhe}; nenhum documento foi devolvido`));
373
+ const lacunas = [];
374
+ const candidatos = varrer(pasta.real, pasta.raizReal, limites, lacunas);
375
+ if (!Array.isArray(candidatos))
376
+ return teto(candidatos.teto);
377
+ const decodificador = new TextDecoder('utf-8', { fatal: true });
378
+ const validos = [];
379
+ const declarados = new Map();
380
+ let total = 0;
381
+ for (const { abs, arquivo, st } of candidatos) {
382
+ const bytes = lerArquivo(abs, st, limites.bytesPorDocumento);
383
+ if (bytes === 'link') {
384
+ lacunas.push(lacuna('documento.link', LINK, { arquivo }));
385
+ continue;
386
+ }
387
+ if (bytes === 'nao-regular') {
388
+ lacunas.push(lacuna('documento.nao-regular', 'não abriu como arquivo comum, ou mudou durante a leitura', { arquivo }));
389
+ continue;
390
+ }
391
+ if (bytes === 'grande') {
392
+ lacunas.push(lacuna('documento.grande-demais', `o documento passa de ${limites.bytesPorDocumento / 1024} KiB`, { arquivo }));
393
+ continue;
394
+ }
395
+ total += bytes.length;
396
+ if (total > limites.bytesNoTotal)
397
+ return teto(`os documentos passam de ${limites.bytesNoTotal / 1024 / 1024} MiB no total`);
398
+ let texto;
399
+ try {
400
+ texto = decodificador.decode(bytes);
401
+ }
402
+ catch {
403
+ lacunas.push(lacuna('documento.codificacao', 'o arquivo não é UTF-8', { arquivo }));
404
+ continue;
405
+ }
406
+ const { bruto } = (0, docs_1.separarFrontmatter)(texto);
407
+ if (bruto === null) {
408
+ lacunas.push(lacuna('documento.sem-frontmatter', 'o arquivo não começa com o frontmatter entre duas linhas ---', { arquivo }));
409
+ continue;
410
+ }
411
+ if (Buffer.byteLength(bruto, 'utf8') > limites.bytesDoFrontmatter) {
412
+ lacunas.push(lacuna('documento.frontmatter-invalido', `o frontmatter passa de ${limites.bytesDoFrontmatter / 1024} KiB`, { arquivo }));
413
+ continue;
414
+ }
415
+ let dados;
416
+ // A linha do `---` de abertura entra na leitura (e é pulada nela) para o erro dizer a linha do arquivo.
417
+ try {
418
+ dados = (0, yaml_1.lerYaml)('---\n' + bruto, { estrito: true });
419
+ }
420
+ catch (e) {
421
+ lacunas.push(lacuna('documento.frontmatter-invalido', limpo(e.message, 200), { arquivo }));
422
+ continue;
423
+ }
424
+ // D4: o id na forma certa conta para a regra do repetido mesmo quando o resto do documento não vale.
425
+ const idDeclarado = ehMapa(dados) && typeof dados.id === 'string' && exports.ID_DE_DOCUMENTO.test(dados.id) ? dados.id : null;
426
+ if (idDeclarado)
427
+ declarados.set(idDeclarado, (declarados.get(idDeclarado) ?? 0) + 1);
428
+ const v = validarFrontmatter(dados, arquivo);
429
+ if ('codigo' in v) {
430
+ lacunas.push(v);
431
+ continue;
432
+ }
433
+ validos.push({ ...v, arquivo, bytes: bytes.length, sha256: (0, company_brain_journal_1.bytesHash)(bytes) });
434
+ }
435
+ // D4: id repetido não escolhe arquivo; todos os documentos com ele saem da resposta, também quando a outra
436
+ // cópia não vale por outro motivo (ela já tem a lacuna dela).
437
+ const unicos = validos.filter((v) => {
438
+ const vezes = declarados.get(v.id);
439
+ if (vezes > 1) {
440
+ lacunas.push(lacuna('documento.id-repetido', `o id aparece em ${vezes} arquivos; nenhum deles vale até sobrar um`, { arquivo: v.arquivo, documento: v.id }));
441
+ }
442
+ return vezes === 1;
443
+ });
444
+ // D5: cada ledger citado é lido uma vez, com as linhas que os documentos válidos pedem.
445
+ const citadas = new Map();
446
+ for (const v of unicos)
447
+ for (const ref of v.decisoes) {
448
+ const m = exports.REFERENCIA_DE_DECISAO.exec(ref);
449
+ if (m)
450
+ citadas.set(m[1], (citadas.get(m[1]) ?? new Set()).add(Number(m[2])));
451
+ }
452
+ const ledgers = new Map();
453
+ for (const [thread, linhas] of citadas)
454
+ ledgers.set(thread, linhasCitadas(c.raiz, thread, linhas, limites.bytesDoLedger));
455
+ const documentos = [];
456
+ for (const v of unicos) {
457
+ const onde = { arquivo: v.arquivo, documento: v.id };
458
+ const brain = [], decisoes = [];
459
+ for (const referencia of v.brain) {
460
+ if (portfolio_1.ID_DE_ENTIDADE.test(referencia))
461
+ brain.push(referencia);
462
+ else
463
+ lacunas.push(lacuna('referencia.invalida', 'id do Brain é prod-, proj- ou init-, na regra dos ids do portfólio', { ...onde, referencia }));
464
+ }
465
+ for (const referencia of v.decisoes) {
466
+ const r = conferirDecisao(referencia, ledgers);
467
+ if ('codigo' in r)
468
+ lacunas.push(lacuna(r.codigo, r.detalhe, { ...onde, referencia }));
469
+ else
470
+ decisoes.push(r);
471
+ }
472
+ documentos.push({ id: v.id, titulo: v.titulo, colecoes: v.colecoes,
473
+ referencias: { brain: brain.sort(comparar), decisoes: decisoes.sort((a, b) => comparar(a.thread, b.thread) || a.linha - b.linha) },
474
+ arquivo: v.arquivo, bytes: v.bytes, sha256: v.sha256 });
475
+ }
476
+ const colecoes = new Map();
477
+ for (const d of documentos)
478
+ for (const nome of d.colecoes) {
479
+ const ids = colecoes.get(nome) ?? [];
480
+ ids.push(d.id);
481
+ colecoes.set(nome, ids);
482
+ }
483
+ return {
484
+ contrato: exports.CONTRATO_CONHECIMENTO, dir,
485
+ documentos: documentos.sort((a, b) => comparar(a.id, b.id)),
486
+ colecoes: [...colecoes].map(([nome, ids]) => ({ nome, documentos: ids.sort(comparar) })).sort((a, b) => comparar(a.nome, b.nome)),
487
+ lacunas: lacunas.sort((a, b) => comparar(a.arquivo ?? '', b.arquivo ?? '') || comparar(a.codigo, b.codigo) ||
488
+ comparar(a.referencia ?? '', b.referencia ?? '')),
489
+ };
490
+ }
package/dist/docs.js CHANGED
@@ -79,6 +79,8 @@ const node_child_process_1 = require("node:child_process");
79
79
  const yaml_1 = require("./yaml");
80
80
  const thread_1 = require("./thread");
81
81
  const ledger_1 = require("./ledger");
82
+ const branch_de_estado_1 = require("./branch-de-estado");
83
+ const locale_1 = require("./locale");
82
84
  exports.DIR_PRODUTO = 'docs/produto';
83
85
  exports.DIR_ROADMAP = 'docs/roadmap';
84
86
  exports.DIR_PADROES = 'docs/padroes';
@@ -199,6 +201,9 @@ function git(raiz, args) {
199
201
  }
200
202
  /** Resolve a branch base: local, depois `origin/`, porque o CI faz checkout destacado. */
201
203
  function resolverBase(raiz, preferida = 'main') {
204
+ // RM-047 (fronteira de confiança): `preferida` vem do `worktree.base_branch` do clone.
205
+ if (!(0, branch_de_estado_1.branchValida)(preferida))
206
+ return null;
202
207
  for (const ref of [preferida, `origin/${preferida}`]) {
203
208
  if (git(raiz, ['rev-parse', '--verify', '--quiet', `${ref}^{commit}`]).ok)
204
209
  return ref;
@@ -561,7 +566,8 @@ function verificarEstadoDoRoadmap(raiz, d, base, achados) {
561
566
  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)'));
562
567
  }
563
568
  if (commit && base) {
564
- if (!git(raiz, ['cat-file', '-e', `${commit}^{commit}`]).ok) {
569
+ // RM-047: o commit vem do frontmatter do clone; só sha hexadecimal vai ao git.
570
+ if (!(0, branch_de_estado_1.shaValido)(commit) || !git(raiz, ['cat-file', '-e', `${commit}^{commit}`]).ok) {
565
571
  achados.push(erro(d.arquivo, d.id, 'docs.paridade.git', `o commit ${commit} não existe neste repositório`));
566
572
  }
567
573
  else if (codigo === 'Mesclado' && !git(raiz, ['merge-base', '--is-ancestor', commit, base]).ok) {
@@ -755,7 +761,7 @@ function mergeDaThread(raiz, thread, base) {
755
761
  try {
756
762
  const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, thread));
757
763
  const ship = eventos.filter((e) => e.tipo === 'ship_done' && typeof e.mergeSha === 'string')
758
- .find((e) => git(raiz, ['merge-base', '--is-ancestor', String(e.mergeSha), base]).ok);
764
+ .find((e) => (0, branch_de_estado_1.shaValido)(e.mergeSha) && (0, branch_de_estado_1.branchValida)(base) && git(raiz, ['merge-base', '--is-ancestor', e.mergeSha, base]).ok);
759
765
  if (ship)
760
766
  return { sha: String(ship.mergeSha).slice(0, 7), fonte: 'ledger ship_done' };
761
767
  }
@@ -773,6 +779,9 @@ function mergesDaThreadNoGit(raiz, thread, base) {
773
779
  // Sem `-n 1`: o git corta antes de inverter, e `--reverse -n 1` devolveria o mais recente.
774
780
  // GO-FIX 2 (achado 4 do CHECK): so a linha de primeiro pai da base e o assunto que COMECA com
775
781
  // `ship(<thread>)`, como no `mergeDaEntrega`. Commit que cita o merge no corpo, ou o Revert dele, nao e merge.
782
+ // RM-047 (fronteira de confiança): a base pode ser o `worktree.base_branch` cru do clone.
783
+ if (!(0, branch_de_estado_1.branchValida)(base))
784
+ return [];
776
785
  const log = git(raiz, ['log', base, '--first-parent', '--reverse', '--format=%H%x09%s', '--fixed-strings', `--grep=ship(${thread})`]);
777
786
  if (!log.ok)
778
787
  return [];
@@ -954,16 +963,21 @@ const ARQUIVOS_DO_PADRAO = [
954
963
  ['roadmap/README.md', `${exports.DIR_ROADMAP}/README.md`],
955
964
  ['markdownlint-cli2.jsonc', '.markdownlint-cli2.jsonc'],
956
965
  ];
957
- function dirDosModelos() {
958
- for (const c of [path.join(__dirname, '..', 'assets', 'docs'), path.join(__dirname, '..', '..', 'assets', 'docs')]) {
966
+ /**
967
+ * A pasta dos modelos do nucleo na lingua pedida. RM-061 (F3a, D3 e D6): o ingles mora em `assets/docs-en/`, com os
968
+ * mesmos nomes; o contrato que o `ork docs verificar` le (frontmatter, secoes, `Em uma frase`) e o mesmo nas duas.
969
+ */
970
+ function dirDosModelos(locale = locale_1.LOCALE_PADRAO) {
971
+ const pasta = locale === 'en' ? 'docs-en' : 'docs';
972
+ for (const c of [path.join(__dirname, '..', 'assets', pasta), path.join(__dirname, '..', '..', 'assets', pasta)]) {
959
973
  if (fs.existsSync(c))
960
974
  return c;
961
975
  }
962
- throw new Error('docs.modelos: assets/docs do nucleo nao encontrado');
976
+ throw new Error(`docs.modelos: assets/${pasta} do nucleo nao encontrado`);
963
977
  }
964
- /** Cria o que falta; nunca sobrescreve pagina existente. Devolve o que criou. */
965
- function iniciarDocs(raiz) {
966
- const origem = dirDosModelos();
978
+ /** Cria o que falta, no `locale` pedido (pt-BR por omissao); nunca sobrescreve pagina existente. Devolve o que criou. */
979
+ function iniciarDocs(raiz, locale = locale_1.LOCALE_PADRAO) {
980
+ const origem = dirDosModelos(locale);
967
981
  const criados = [];
968
982
  for (const [de, para] of ARQUIVOS_DO_PADRAO) {
969
983
  const destino = path.join(raiz, para);