@orkastery/cli 0.4.3 → 0.5.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 (146) hide show
  1. package/adapters/README.md +11 -0
  2. package/adapters/claude-code/.claude-plugin/plugin.json +3 -1
  3. package/adapters/claude-code/README.md +11 -2
  4. package/adapters/claude-code/agents/ork-check.md +1 -1
  5. package/adapters/claude-code/agents/ork-go.md +2 -2
  6. package/adapters/claude-code/agents/ork-goal.md +1 -1
  7. package/adapters/claude-code/agents/ork-master.md +1 -1
  8. package/adapters/claude-code/agents/ork-plan.md +1 -1
  9. package/adapters/claude-code/agents/ork-ship.md +1 -1
  10. package/adapters/claude-code/commands/master.md +11 -8
  11. package/adapters/claude-code/commands/onboarding.md +3 -2
  12. package/adapters/claude-code/commands/ork.md +8 -0
  13. package/adapters/claude-code/commands/ship.md +2 -2
  14. package/adapters/codex/skills/ork/SKILL.md +12 -1
  15. package/adapters/hermes/README.md +3 -0
  16. package/adapters/hermes/bin/ork-abrir-thread.sh +17 -2
  17. package/adapters/hermes/bin/ork-brain.sh +3 -0
  18. package/adapters/hermes/bin/ork-maestro.sh +4 -1
  19. package/adapters/hermes/bin/ork-network-roadmap.sh +11 -0
  20. package/adapters/hermes/bin/ork-roadmap-status.sh +9 -0
  21. package/adapters/hermes/hermes.plugin.json +2 -0
  22. package/adapters/hermes/hitl-ingress/__init__.py +36 -3
  23. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +19 -20
  24. package/adapters/openclaw/README.md +45 -2
  25. package/adapters/openclaw/dist/hitl-ingress.js +37 -3
  26. package/adapters/openclaw/dist/index.js +93 -21
  27. package/adapters/openclaw/openclaw.plugin.json +10 -1
  28. package/adapters/openclaw/package.json +2 -2
  29. package/adapters/openclaw/src/hitl-ingress.ts +32 -3
  30. package/adapters/openclaw/src/index.ts +109 -21
  31. package/assets/docs/padroes/documentacao-de-produto.md +2 -2
  32. package/assets/docs/padroes/roadmap-de-produto.md +1 -1
  33. package/assets/docs/produto/_modelo-feature.md +2 -0
  34. package/assets/orkmind_bridge.py +214 -1
  35. package/dist/adapters/claude-bg.js +24 -7
  36. package/dist/auditrun.js +1 -1
  37. package/dist/board.js +74 -0
  38. package/dist/branch-de-estado.js +5 -4
  39. package/dist/busca-semantica.js +161 -0
  40. package/dist/ci.js +141 -10
  41. package/dist/claim-lint.js +3 -1
  42. package/dist/company-brain-cli.js +27 -2
  43. package/dist/company-brain-context.js +132 -0
  44. package/dist/company-brain-dossie.js +376 -0
  45. package/dist/company-brain-mcp.js +4 -1
  46. package/dist/conducao.js +71 -18
  47. package/dist/decisao-autonoma.js +42 -0
  48. package/dist/docs.js +135 -25
  49. package/dist/doctor.js +242 -32
  50. package/dist/entrega-pr.js +116 -5
  51. package/dist/experiencia-instalacao.js +256 -0
  52. package/dist/experiencia.js +72 -0
  53. package/dist/fabrica-estado.js +63 -12
  54. package/dist/fechamento.js +105 -0
  55. package/dist/fix.js +3 -3
  56. package/dist/forja.js +435 -0
  57. package/dist/gates.js +9 -1
  58. package/dist/hitl-classificacao.js +11 -1
  59. package/dist/hitl-contract.js +50 -7
  60. package/dist/hitl-curto.js +158 -0
  61. package/dist/hitl-gates.js +67 -1
  62. package/dist/hitl-lote.js +30 -15
  63. package/dist/hitl-presentation.js +10 -7
  64. package/dist/hitl-resumo.js +57 -13
  65. package/dist/hitl-texto-livre.js +150 -0
  66. package/dist/hosts.js +92 -3
  67. package/dist/index.js +637 -45
  68. package/dist/indice-vetorial.js +317 -0
  69. package/dist/init.js +24 -5
  70. package/dist/integracoes-locais.js +3 -1
  71. package/dist/intelligence-benchmark-contract.js +570 -0
  72. package/dist/intelligence-graph-cli.js +584 -0
  73. package/dist/intelligence-graph-contract.js +636 -0
  74. package/dist/intelligence-graph-extract-md.js +487 -0
  75. package/dist/intelligence-graph-extract-ts.js +980 -0
  76. package/dist/intelligence-graph-extract.js +546 -0
  77. package/dist/intelligence-graph-index.js +693 -0
  78. package/dist/intelligence-graph-parsers.js +327 -0
  79. package/dist/intelligence-graph-query.js +344 -0
  80. package/dist/intelligence-graph-repo.js +197 -0
  81. package/dist/leases.js +100 -0
  82. package/dist/ledger.js +23 -0
  83. package/dist/licoes.js +5 -4
  84. package/dist/liveness.js +2 -1
  85. package/dist/maestro-cli.js +10 -1
  86. package/dist/maestro-contract.js +5 -1
  87. package/dist/maestro-discovery.js +16 -2
  88. package/dist/maestro-runtime.js +3 -0
  89. package/dist/maestro-snapshot.js +6 -2
  90. package/dist/manifest.js +166 -5
  91. package/dist/master-batch.js +3 -2
  92. package/dist/master-digest.js +2 -2
  93. package/dist/master-nota.js +203 -0
  94. package/dist/master.js +4 -0
  95. package/dist/mcp-artifacts.js +2 -0
  96. package/dist/mcp-experiencia.js +164 -0
  97. package/dist/mcp-git.js +64 -12
  98. package/dist/mcp-grafo-worker.js +91 -0
  99. package/dist/mcp-grafo.js +233 -0
  100. package/dist/mcp-install.js +5 -2
  101. package/dist/mcp-maestro.js +3 -2
  102. package/dist/mcp-server.js +93 -5
  103. package/dist/mcp-ship.js +2 -0
  104. package/dist/mcp-verify.js +3 -1
  105. package/dist/memoria.js +147 -1
  106. package/dist/network-roadmap.js +907 -0
  107. package/dist/ocupacao.js +43 -3
  108. package/dist/onboarding.js +122 -11
  109. package/dist/orkmind.js +165 -9
  110. package/dist/orquestracao.js +2 -1
  111. package/dist/parado-no-condutor.js +933 -0
  112. package/dist/phase.js +166 -13
  113. package/dist/policies.js +29 -2
  114. package/dist/projeto-alvo.js +551 -0
  115. package/dist/prompts.js +1 -1
  116. package/dist/prova-ativacao.js +345 -0
  117. package/dist/pulse-consentimento.js +4 -2
  118. package/dist/pulse-delivery.js +32 -6
  119. package/dist/pulse-resposta.js +674 -49
  120. package/dist/pulse.js +74 -3
  121. package/dist/retry.js +51 -8
  122. package/dist/roadmap-reservas.js +262 -20
  123. package/dist/roadmap-status.js +348 -0
  124. package/dist/runtime-context.js +5 -1
  125. package/dist/sessoes-inventario.js +7 -1
  126. package/dist/sessoes.js +4 -1
  127. package/dist/ship.js +25 -1
  128. package/dist/thread-close.js +3 -0
  129. package/dist/thread.js +21 -2
  130. package/dist/util.js +71 -4
  131. package/dist/verify.js +6 -5
  132. package/dist/worktree.js +29 -6
  133. package/eval/casos/onboarding.json +1 -1
  134. package/eval/casos/orchestration-experience-pt-br.json +48 -0
  135. package/eval/casos/orchestration-experience.json +48 -0
  136. package/package.json +1 -1
  137. package/schemas/code-artifact-graph.v1.schema.json +581 -0
  138. package/schemas/graph-benchmark.v1.schema.json +2240 -0
  139. package/schemas/maestro-snapshot.schema.json +23 -0
  140. package/skills/README.md +7 -5
  141. package/skills/core/onboarding/SKILL.md +10 -2
  142. package/skills/core/orchestration-experience/SKILL.md +113 -0
  143. package/skills/core/orchestration-experience-pt-br/SKILL.md +112 -0
  144. package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
  145. package/skills/phases/go-implementation/SKILL.md +2 -0
  146. package/skills/phases/ship-release/SKILL.md +6 -3
@@ -0,0 +1,693 @@
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.versoesDoParser = exports.EXCLUSOES_DA_ARVORE = exports.MODULOS_DO_EXTRATOR = exports.PRAZO_DE_SOBRA_MS = exports.LIMITE_DE_ANCESTRAIS = exports.NOME_DE_INDICE = exports.INDICE_SCHEMA_ANTERIOR = exports.INDICE_SCHEMA = void 0;
37
+ exports.impressaoDoExtrator = impressaoDoExtrator;
38
+ exports.perfilDoIndice = perfilDoIndice;
39
+ exports.chaveDoIndice = chaveDoIndice;
40
+ exports.concessaoLocal = concessaoLocal;
41
+ exports.pastaDoGrafo = pastaDoGrafo;
42
+ exports.lerIndice = lerIndice;
43
+ exports.escolherBase = escolherBase;
44
+ exports.construirIndice = construirIndice;
45
+ exports.indiceDoHead = indiceDoHead;
46
+ exports.estadoDosIndices = estadoDosIndices;
47
+ exports.limparIndices = limparIndices;
48
+ /**
49
+ * RM-031 KG3 (D2 a D4, D6): indice persistente e local do grafo de codigo.
50
+ *
51
+ * O indice e derivado do grafo do KG2 e chaveado pela revisao da arvore limpa: mora no estado
52
+ * canonico do projeto, fora do git, uma pasta por chave:
53
+ *
54
+ * <raiz canonica>/.orkastery/grafo/idx-<sha256>/{indice.json, grafo.json, relatorio.json}
55
+ *
56
+ * Pastas 0700 e arquivos 0600, do dono do processo, sem link simbolico nem segundo link fisico. A
57
+ * chave deriva da revisao, do repositorio, do tenant, da ACL, das versoes dos analisadores, do fecho
58
+ * de pacotes deles e da impressao do codigo compilado do extrator e do indice: mesma chave, mesmo
59
+ * conteudo, byte a byte, sem horario. Arquivo rastreado que a leitura nao alcanca (sparse checkout,
60
+ * `skip-worktree`) recusa a construcao: o grafo precisa ser o da revisao inteira.
61
+ * A construcao valida o grafo, confere cada fonte e cada evidencia contra os bytes lidos e so entao
62
+ * publica, por pasta temporaria e `rename`; a leitura confere tamanho e digest dos bytes.
63
+ *
64
+ * O indice nao e autoridade de fato: e projecao descartavel do repositorio. Nada nele concede acesso.
65
+ *
66
+ * RM-031 KG4 (D1, D2, D5, D7): o indice guarda tambem as unidades por arquivo (`unidades.json`), e a
67
+ * construcao sem indice do HEAD parte do indice da revisao ancestral mais proxima com o mesmo perfil:
68
+ * reextrai so o que a mudanca alcanca e grava os mesmos bytes da extracao completa. Sem base que
69
+ * prove isso, extrai completo e diz por que.
70
+ */
71
+ const node_crypto_1 = require("node:crypto");
72
+ const fs = __importStar(require("node:fs"));
73
+ const path = __importStar(require("node:path"));
74
+ const intelligence_graph_contract_1 = require("./intelligence-graph-contract");
75
+ const intelligence_graph_extract_1 = require("./intelligence-graph-extract");
76
+ const intelligence_graph_parsers_1 = require("./intelligence-graph-parsers");
77
+ const intelligence_graph_repo_1 = require("./intelligence-graph-repo");
78
+ /** KG4 (D2): a v1 tem as unidades por arquivo; a v0 (KG3) nao serve de base e o `limpar` a remove. */
79
+ exports.INDICE_SCHEMA = 'ork.code-graph-index/v1';
80
+ exports.INDICE_SCHEMA_ANTERIOR = 'ork.code-graph-index/v0';
81
+ exports.NOME_DE_INDICE = /^idx-[a-f0-9]{64}$/;
82
+ const ARQUIVOS = { manifesto: 'indice.json', grafo: 'grafo.json', relatorio: 'relatorio.json', unidades: 'unidades.json' };
83
+ /** KG4 (D1): quantas revisoes, do HEAD para tras, a escolha da base percorre. */
84
+ exports.LIMITE_DE_ANCESTRAIS = 512;
85
+ /** Temporario e lixo de construcao interrompida so saem no `limpar` depois deste prazo. */
86
+ exports.PRAZO_DE_SOBRA_MS = 60 * 60 * 1000;
87
+ /** D3: os modulos compilados da extracao e do indice. Codigo corrigido sem troca de versao muda a chave. */
88
+ exports.MODULOS_DO_EXTRATOR = [
89
+ 'intelligence-graph-contract', 'intelligence-graph-extract', 'intelligence-graph-extract-ts', 'intelligence-graph-extract-md',
90
+ 'intelligence-graph-repo', 'intelligence-graph-parsers', 'intelligence-graph-index', 'yaml',
91
+ ];
92
+ /**
93
+ * CHECK rodada 1 (B1): exclusoes do KG2 que dependem da arvore, nao da revisao. Com o status limpo,
94
+ * elas vem de sparse checkout ou `skip-worktree`, e o grafo seria outro na mesma revisao.
95
+ */
96
+ exports.EXCLUSOES_DA_ARVORE = new Set(['ausente-na-arvore', 'nao-e-arquivo', 'fora-do-repositorio']);
97
+ function falha(codigo, detalhe) {
98
+ throw new Error(detalhe ? `${codigo}: ${detalhe}` : codigo);
99
+ }
100
+ const sha256 = (b) => (0, node_crypto_1.createHash)('sha256').update(b).digest('hex');
101
+ const uid = () => (typeof process.getuid === 'function' ? process.getuid() : null);
102
+ /** As versoes do `Parser` carregado, no formato que `versoesDosAnalisadores` le sem carregar. */
103
+ const versoesDoParser = (p) => ({ typescript: p.ts.version, javascript: p.javascript.versao, markdown: p.markdown.versao, unicode: p.unicode });
104
+ exports.versoesDoParser = versoesDoParser;
105
+ /** D3: SHA-256 dos bytes compilados dos modulos da extracao, na ordem fixa; `dir` so muda em teste. */
106
+ function impressaoDoExtrator(dir = __dirname) {
107
+ const h = (0, node_crypto_1.createHash)('sha256');
108
+ for (const m of exports.MODULOS_DO_EXTRATOR) {
109
+ const bytes = fs.readFileSync(path.join(dir, `${m}.js`));
110
+ h.update(`${m}\u0000${bytes.length}\u0000`).update(bytes);
111
+ }
112
+ return h.digest('hex');
113
+ }
114
+ function perfilDoIndice(raiz, analisadores = (0, intelligence_graph_parsers_1.versoesDosAnalisadores)(), leitura = {}) {
115
+ const id = (0, intelligence_graph_repo_1.identidadeDaLeitura)(raiz, leitura);
116
+ return {
117
+ ...id, acl_refs: [...new Set(id.acl_refs)].sort(intelligence_graph_contract_1.compararUtf8), analisadores, pacotes: (0, intelligence_graph_parsers_1.pacotesDosAnalisadores)(), codigo: impressaoDoExtrator(),
118
+ };
119
+ }
120
+ /** A leitura do contexto: o repositorio do manifesto do CLI, se veio, sob as opcoes pedidas. */
121
+ const leituraDo = (ctx, leitura = {}) => (ctx.repositorio && leitura.repository_id === undefined ? { ...leitura, repository_id: ctx.repositorio } : leitura);
122
+ /** D3: a chave particiona por revisao, repositorio, tenant, ACL, analisadores e codigo do extrator. */
123
+ function chaveDoIndice(revision, perfil) {
124
+ return `idx-${(0, intelligence_graph_contract_1.sha256DoCanonico)({ schema: exports.INDICE_SCHEMA, revision, ...perfil })}`;
125
+ }
126
+ /**
127
+ * D6: a avaliacao local. Quem roda o CLI e le a raiz do repositorio recebe a leitura dele, no
128
+ * tenant local; nada mais. Sem leitura, a recusa e explicita, sem cair para outro escopo.
129
+ */
130
+ function concessaoLocal(raiz, perfil) {
131
+ try {
132
+ fs.accessSync(raiz, fs.constants.R_OK | fs.constants.X_OK);
133
+ }
134
+ catch {
135
+ falha('grafo.acesso.negado', 'quem roda o CLI nao le a raiz do repositorio');
136
+ }
137
+ return { tenant_id: intelligence_graph_repo_1.TENANT_PADRAO, acl_refs: [`repo:${perfil.repository_id}:leitura`] };
138
+ }
139
+ /** Pasta do estado: real, do dono e 0700; `criar` ajusta a que o proprio processo cria. */
140
+ function pastaPrivada(p, criar) {
141
+ if (criar) {
142
+ try {
143
+ fs.mkdirSync(p, { mode: 0o700 });
144
+ }
145
+ catch (e) {
146
+ if (e.code !== 'EEXIST')
147
+ throw e;
148
+ }
149
+ }
150
+ const st = fs.lstatSync(p, { throwIfNoEntry: false });
151
+ if (!st)
152
+ falha('grafo.indice.ausente');
153
+ if (st.isSymbolicLink() || !st.isDirectory())
154
+ falha('grafo.indice.permissao-invalida', `${path.basename(p)} nao e pasta real`);
155
+ const dono = uid();
156
+ if (dono !== null && st.uid !== dono)
157
+ falha('grafo.indice.permissao-invalida', `${path.basename(p)} de outro dono`);
158
+ if ((st.mode & 0o777) !== 0o700) {
159
+ if (!criar)
160
+ falha('grafo.indice.permissao-invalida', `${path.basename(p)} com modo ${(st.mode & 0o777).toString(8)}`);
161
+ fs.chmodSync(p, 0o700);
162
+ }
163
+ }
164
+ /** `<estado>/.orkastery/grafo`, real e privada. So a construcao cria. */
165
+ function pastaDoGrafo(ctx, criar) {
166
+ const estado = path.join(ctx.estado, '.orkastery');
167
+ if (criar)
168
+ fs.mkdirSync(estado, { recursive: true });
169
+ const st = fs.lstatSync(estado, { throwIfNoEntry: false });
170
+ if (!st)
171
+ falha('grafo.indice.ausente');
172
+ if (st.isSymbolicLink() || !st.isDirectory())
173
+ falha('grafo.indice.permissao-invalida', '.orkastery nao e pasta real');
174
+ const dir = path.join(estado, 'grafo');
175
+ pastaPrivada(dir, criar);
176
+ return dir;
177
+ }
178
+ /** Le arquivo do indice pelo descritor aberto sem seguir link: regular, do dono, 0600 e com um link so. */
179
+ function lerPrivado(arquivo) {
180
+ let fd;
181
+ try {
182
+ fd = fs.openSync(arquivo, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
183
+ }
184
+ catch (e) {
185
+ const codigo = e.code;
186
+ if (codigo === 'ENOENT')
187
+ falha('grafo.indice.corrompido', `${path.basename(arquivo)} ausente`);
188
+ falha('grafo.indice.permissao-invalida', `${path.basename(arquivo)} (${codigo ?? 'erro'})`);
189
+ }
190
+ try {
191
+ const st = fs.fstatSync(fd);
192
+ const dono = uid();
193
+ if (!st.isFile() || st.nlink !== 1 || (dono !== null && st.uid !== dono) || (st.mode & 0o777) !== 0o600) {
194
+ falha('grafo.indice.permissao-invalida', path.basename(arquivo));
195
+ }
196
+ return fs.readFileSync(fd);
197
+ }
198
+ finally {
199
+ fs.closeSync(fd);
200
+ }
201
+ }
202
+ /** KG4: as mesmas regras de `lerPrivado` e o tamanho do manifesto, sem ler o conteudo (a consulta nao precisa das unidades). */
203
+ function conferirPrivado(arquivo, bytes) {
204
+ let fd;
205
+ try {
206
+ fd = fs.openSync(arquivo, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
207
+ }
208
+ catch (e) {
209
+ const codigo = e.code;
210
+ if (codigo === 'ENOENT')
211
+ falha('grafo.indice.corrompido', `${path.basename(arquivo)} ausente`);
212
+ falha('grafo.indice.permissao-invalida', `${path.basename(arquivo)} (${codigo ?? 'erro'})`);
213
+ }
214
+ try {
215
+ const st = fs.fstatSync(fd);
216
+ const dono = uid();
217
+ if (!st.isFile() || st.nlink !== 1 || (dono !== null && st.uid !== dono) || (st.mode & 0o777) !== 0o600) {
218
+ falha('grafo.indice.permissao-invalida', path.basename(arquivo));
219
+ }
220
+ if (st.size !== bytes)
221
+ falha('grafo.indice.corrompido', `${path.basename(arquivo)} nao bate com o tamanho`);
222
+ }
223
+ finally {
224
+ fs.closeSync(fd);
225
+ }
226
+ }
227
+ function gravarPrivado(arquivo, texto) {
228
+ const fd = fs.openSync(arquivo, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_NOFOLLOW, 0o600);
229
+ try {
230
+ fs.fchmodSync(fd, 0o600);
231
+ fs.writeFileSync(fd, texto, 'utf8');
232
+ fs.fsyncSync(fd);
233
+ }
234
+ finally {
235
+ fs.closeSync(fd);
236
+ }
237
+ }
238
+ function sincronizarPasta(dir) {
239
+ const fd = fs.openSync(dir, 'r');
240
+ try {
241
+ fs.fsyncSync(fd);
242
+ }
243
+ finally {
244
+ fs.closeSync(fd);
245
+ }
246
+ }
247
+ const HEX64 = /^[a-f0-9]{64}$/;
248
+ function manifestoValido(m, chave) {
249
+ const o = m;
250
+ const inteiro = (n) => Number.isSafeInteger(n) && n >= 0;
251
+ if (o && typeof o === 'object' && o.schema === exports.INDICE_SCHEMA_ANTERIOR) {
252
+ falha('grafo.indice.formato-anterior', `${exports.INDICE_SCHEMA_ANTERIOR} (KG3), sem unidades; o ork grafo limpar o remove`);
253
+ }
254
+ if (!o || typeof o !== 'object' || o.schema !== exports.INDICE_SCHEMA || o.chave !== chave || o.graph_schema !== intelligence_graph_contract_1.GRAFO_SCHEMA
255
+ || o.units_schema !== intelligence_graph_extract_1.UNIDADES_SCHEMA || !HEX64.test(o.units_digest) || !inteiro(o.units_bytes)
256
+ || typeof o.revision !== 'string' || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(o.revision)
257
+ || !HEX64.test(o.graph_digest) || !HEX64.test(o.report_digest) || !inteiro(o.graph_bytes) || !inteiro(o.report_bytes)
258
+ || typeof o.snapshot_id !== 'string' || typeof o.repository_id !== 'string' || typeof o.tenant_id !== 'string'
259
+ || !Array.isArray(o.acl_refs) || !o.analisadores || !Array.isArray(o.pacotes) || typeof o.codigo !== 'string') {
260
+ falha('grafo.indice.corrompido', 'indice.json');
261
+ }
262
+ return o;
263
+ }
264
+ /**
265
+ * Le o indice da chave e confere a integridade: permissao de pasta e arquivo, manifesto, tamanho e
266
+ * digest dos bytes do grafo e do relatorio, e o envelope do grafo contra o manifesto. Nao roda
267
+ * `validarGrafo` (D4): a construcao ja validou, e o digest prova que os bytes sao aqueles.
268
+ */
269
+ function lerIndice(ctx, chave, opcoes = {}) {
270
+ if (!exports.NOME_DE_INDICE.test(chave))
271
+ falha('grafo.indice.chave-invalida');
272
+ const dir = path.join(pastaDoGrafo(ctx, false), chave);
273
+ pastaPrivada(dir, false);
274
+ let manifesto;
275
+ try {
276
+ manifesto = manifestoValido(JSON.parse(lerPrivado(path.join(dir, ARQUIVOS.manifesto)).toString('utf8')), chave);
277
+ }
278
+ catch (e) {
279
+ if (e instanceof SyntaxError)
280
+ falha('grafo.indice.corrompido', 'indice.json');
281
+ throw e;
282
+ }
283
+ const conferido = (nome, bytes, digest) => {
284
+ const b = lerPrivado(path.join(dir, nome));
285
+ if (b.length !== bytes || sha256(b) !== digest)
286
+ falha('grafo.indice.corrompido', `${nome} nao bate com o digest`);
287
+ return b;
288
+ };
289
+ const bytesDoGrafo = conferido(ARQUIVOS.grafo, manifesto.graph_bytes, manifesto.graph_digest);
290
+ const bytesDoRelatorio = conferido(ARQUIVOS.relatorio, manifesto.report_bytes, manifesto.report_digest);
291
+ let grafo = null;
292
+ if (opcoes.grafo !== false) {
293
+ grafo = JSON.parse(bytesDoGrafo.toString('utf8'));
294
+ const s = grafo.snapshot;
295
+ if (grafo.schema !== intelligence_graph_contract_1.GRAFO_SCHEMA || grafo.tenant_id !== manifesto.tenant_id || grafo.repository_id !== manifesto.repository_id
296
+ || !s || s.snapshot_id !== manifesto.snapshot_id || s.revision !== manifesto.revision) {
297
+ falha('grafo.indice.corrompido', 'grafo.json fora do manifesto');
298
+ }
299
+ }
300
+ const relatorio = opcoes.relatorio ? JSON.parse(bytesDoRelatorio.toString('utf8')) : null;
301
+ // KG4: as unidades so sao lidas (e o digest conferido) por quem as usa; a consulta confere so o tamanho.
302
+ let unidades = null;
303
+ if (opcoes.unidades) {
304
+ let u;
305
+ try {
306
+ u = JSON.parse(conferido(ARQUIVOS.unidades, manifesto.units_bytes, manifesto.units_digest).toString('utf8'));
307
+ }
308
+ catch (e) {
309
+ if (e instanceof SyntaxError)
310
+ falha('grafo.indice.corrompido', 'unidades.json');
311
+ throw e;
312
+ }
313
+ if (!u || u.schema !== intelligence_graph_extract_1.UNIDADES_SCHEMA || u.snapshot_id !== manifesto.snapshot_id || u.graph_digest !== manifesto.graph_digest
314
+ || !Array.isArray(u.arquivos) || !Array.isArray(u.globais_ts)) {
315
+ falha('grafo.indice.corrompido', 'unidades.json fora do manifesto');
316
+ }
317
+ unidades = u;
318
+ }
319
+ else
320
+ conferirPrivado(path.join(dir, ARQUIVOS.unidades), manifesto.units_bytes);
321
+ return { dir, manifesto, grafo, relatorio, unidades };
322
+ }
323
+ /** Embaralhamento reproduzivel (LCG): a permutacao da verificacao nao depende de acaso. */
324
+ function embaralhar(itens, semente) {
325
+ const r = [...itens];
326
+ let s = semente >>> 0;
327
+ for (let i = r.length - 1; i > 0; i--) {
328
+ s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
329
+ const j = s % (i + 1);
330
+ [r[i], r[j]] = [r[j], r[i]];
331
+ }
332
+ return r;
333
+ }
334
+ function mesmoConteudo(dir, conteudos) {
335
+ try {
336
+ return Object.entries(conteudos).every(([nome, texto]) => lerPrivado(path.join(dir, nome)).equals(Buffer.from(texto, 'utf8')))
337
+ && fs.readdirSync(dir).length === Object.keys(conteudos).length;
338
+ }
339
+ catch {
340
+ return false;
341
+ }
342
+ }
343
+ function removerPasta(grafoDir, nome) {
344
+ const alvo = path.join(grafoDir, nome);
345
+ const st = fs.lstatSync(alvo, { throwIfNoEntry: false });
346
+ if (!st)
347
+ return;
348
+ if (st.isSymbolicLink() || !st.isDirectory()) {
349
+ fs.unlinkSync(alvo);
350
+ return;
351
+ }
352
+ // Renomeia antes de apagar: quem le nunca ve a pasta da chave pela metade.
353
+ const lixo = nome.startsWith('.lixo-') ? alvo : path.join(grafoDir, `.lixo-${(0, node_crypto_1.randomUUID)()}`);
354
+ if (lixo !== alvo)
355
+ fs.renameSync(alvo, lixo);
356
+ fs.rmSync(lixo, { recursive: true, force: true });
357
+ }
358
+ /** O perfil que um manifesto guarda: tudo o que entra na chave, menos a revisao. */
359
+ const perfilDoManifesto = (m) => ({
360
+ repository_id: m.repository_id, tenant_id: m.tenant_id, acl_refs: m.acl_refs, analisadores: m.analisadores, pacotes: m.pacotes, codigo: m.codigo,
361
+ });
362
+ const curta = (r) => r.slice(0, 12);
363
+ /**
364
+ * KG4 (D1, D5): a base e o indice da revisao ancestral mais proxima do HEAD (nas `LIMITE_DE_ANCESTRAIS`
365
+ * do `git rev-list`) com o mesmo perfil. Sem ela, o motivo diz o que havia: indice ancestral de outro
366
+ * extrator, indice do mesmo extrator fora da linha do HEAD (historico reescrito ou outro ramo), indice
367
+ * de formato anterior, ou nenhum. Base que nao passa na leitura e trocada pela proxima, com o erro.
368
+ */
369
+ function escolherBase(ctx, perfil, head, raiz) {
370
+ let dir;
371
+ try {
372
+ dir = pastaDoGrafo(ctx, false);
373
+ }
374
+ catch (e) {
375
+ if (e.message === 'grafo.indice.ausente')
376
+ return { base: null, motivo: 'nenhum indice guardado' };
377
+ throw e;
378
+ }
379
+ const posicao = new Map((0, intelligence_graph_repo_1.revisoesAncestrais)(raiz, exports.LIMITE_DE_ANCESTRAIS).map((r, i) => [r, i]));
380
+ const desejado = (0, intelligence_graph_contract_1.canonico)(perfil), identidade = (0, intelligence_graph_contract_1.canonico)([perfil.repository_id, perfil.tenant_id, perfil.acl_refs]);
381
+ const candidatos = [];
382
+ let outroExtrator = null, foraDaLinha = null, anterior = false;
383
+ for (const nome of fs.readdirSync(dir).sort(intelligence_graph_contract_1.compararUtf8)) {
384
+ if (!exports.NOME_DE_INDICE.test(nome))
385
+ continue;
386
+ let m;
387
+ try {
388
+ pastaPrivada(path.join(dir, nome), false);
389
+ m = manifestoValido(JSON.parse(lerPrivado(path.join(dir, nome, ARQUIVOS.manifesto)).toString('utf8')), nome);
390
+ }
391
+ catch (e) {
392
+ if (e.message.startsWith('grafo.indice.formato-anterior'))
393
+ anterior = true;
394
+ continue;
395
+ }
396
+ if (m.revision === head)
397
+ continue;
398
+ const p = perfilDoManifesto(m), i = posicao.get(m.revision);
399
+ if ((0, intelligence_graph_contract_1.canonico)(p) === desejado) {
400
+ if (i === undefined)
401
+ foraDaLinha ??= m.revision;
402
+ else
403
+ candidatos.push({ chave: nome, revision: m.revision, posicao: i });
404
+ }
405
+ else if (i !== undefined && (0, intelligence_graph_contract_1.canonico)([p.repository_id, p.tenant_id, p.acl_refs]) === identidade && (!outroExtrator || i < outroExtrator.posicao)) {
406
+ const campos = ['analisadores', 'pacotes', 'codigo'].filter((k) => (0, intelligence_graph_contract_1.canonico)(p[k]) !== (0, intelligence_graph_contract_1.canonico)(perfil[k]));
407
+ outroExtrator = { revision: m.revision, campos, posicao: i };
408
+ }
409
+ }
410
+ const erros = [];
411
+ for (const c of candidatos.sort((a, b) => a.posicao - b.posicao)) {
412
+ try {
413
+ const lido = lerIndice(ctx, c.chave, { grafo: false, unidades: true });
414
+ return { base: { revision: c.revision, chave: c.chave, unidades: lido.unidades }, motivo: null };
415
+ }
416
+ catch (e) {
417
+ erros.push(`${curta(c.revision)}: ${e.message}`);
418
+ }
419
+ }
420
+ if (erros.length)
421
+ return { base: null, motivo: `base ilegivel (${erros.join('; ')})` };
422
+ const motivos = [];
423
+ if (foraDaLinha) {
424
+ motivos.push(`nenhum indice de revisao ancestral nas ultimas ${exports.LIMITE_DE_ANCESTRAIS}; o de ${curta(foraDaLinha)} nao esta entre elas (historico reescrito, outro ramo ou revisao mais antiga)`);
425
+ }
426
+ if (outroExtrator)
427
+ motivos.push(`o extrator mudou desde o indice de ${curta(outroExtrator.revision)} (${outroExtrator.campos.join(', ')})`);
428
+ if (!motivos.length && anterior)
429
+ motivos.push(`so ha indice de formato anterior (${exports.INDICE_SCHEMA_ANTERIOR})`);
430
+ return { base: null, motivo: motivos.length ? motivos.join('; ') : 'nenhum indice de revisao ancestral com este extrator' };
431
+ }
432
+ const textoDasUnidades = (u) => JSON.stringify(u);
433
+ /**
434
+ * Constroi o indice do HEAD limpo, ou confirma o que ja existe. Sem o indice do HEAD, parte do indice
435
+ * ancestral com o mesmo perfil (KG4) e reextrai so o que a mudanca alcanca; sem base, ou se o
436
+ * incremental falha, extrai completo e diz por que. `--forcar` extrai completo e so troca os arquivos se
437
+ * o conteudo mudou; `--verificar` extrai completo, prova o determinismo com a ordem de leitura trocada,
438
+ * compara com o incremental quando ha base e reprova se o guardado integro difere; indice que nao passa
439
+ * na leitura e substituido. O incremental passa pelas mesmas validacoes da completa antes de publicar.
440
+ */
441
+ function construirIndice(ctx, opcoes = {}) {
442
+ const inicio = process.hrtime.bigint();
443
+ const ms = () => Number((process.hrtime.bigint() - inicio) / 1000000n);
444
+ const arvore = (0, intelligence_graph_repo_1.revisaoDaArvore)(ctx.raiz);
445
+ if (arvore.motivo !== null || arvore.head === null)
446
+ falha('grafo.indice.arvore-nao-limpa', arvore.motivo ?? 'sem-commit');
447
+ const head = arvore.head;
448
+ const parser = opcoes.parser ?? (0, intelligence_graph_parsers_1.carregarAnalisadores)();
449
+ const leitura = leituraDo(ctx, opcoes.leitura);
450
+ const perfil = perfilDoIndice(arvore.raiz, (0, exports.versoesDoParser)(parser), leitura);
451
+ const chave = chaveDoIndice(head, perfil);
452
+ const grafoDir = pastaDoGrafo(ctx, true);
453
+ const final = path.join(grafoDir, chave);
454
+ let motivo = null;
455
+ const existia = !!fs.lstatSync(final, { throwIfNoEntry: false });
456
+ if (existia) {
457
+ try {
458
+ const atual = lerIndice(ctx, chave, { grafo: false });
459
+ // `--verificar` nunca confia no guardado: extrai de novo e compara, como o `--forcar`.
460
+ if (!opcoes.forcar && !opcoes.verificar) {
461
+ return {
462
+ estado: 'existente', motivo: null, chave, dir: final, manifesto: atual.manifesto, determinismo: null, ms: ms(),
463
+ modo: null, base: null, motivo_completo: null, reaproveitamento: null, incremental: null,
464
+ };
465
+ }
466
+ }
467
+ catch (e) {
468
+ motivo = e.message;
469
+ }
470
+ }
471
+ const entrada = (0, intelligence_graph_repo_1.lerRepositorio)(arvore.raiz, leitura);
472
+ if (entrada.revision !== head)
473
+ falha('grafo.indice.arvore-nao-limpa', entrada.revision_unavailable_reason ?? 'revisao-mudou');
474
+ const fora = (entrada.excluidas ?? []).filter((e) => exports.EXCLUSOES_DA_ARVORE.has(e.motivo)).sort((a, b) => (0, intelligence_graph_contract_1.compararUtf8)(a.path, b.path));
475
+ if (fora.length) {
476
+ falha('grafo.indice.arvore-nao-limpa', `rastreado fora da leitura (${fora[0].motivo}: ${fora[0].path}${fora.length > 1 ? ` e mais ${fora.length - 1}` : ''})`);
477
+ }
478
+ // KG4 (D1, D5, D7): a base, salvo com `--forcar`.
479
+ let base = null, motivoCompleto = 'pedido com --forcar';
480
+ if (!opcoes.forcar)
481
+ ({ base, motivo: motivoCompleto } = escolherBase(ctx, perfil, head, arvore.raiz));
482
+ let r = null, modo = 'completo';
483
+ if (base && !opcoes.verificar) {
484
+ try {
485
+ r = (0, intelligence_graph_extract_1.extrairGrafo)(entrada, parser, base.unidades);
486
+ modo = 'incremental';
487
+ }
488
+ catch (e) {
489
+ // O incremental nunca impede o indice: a completa decide, e o motivo fica dito.
490
+ motivoCompleto = `o incremental a partir de ${curta(base.revision)} falhou e a extracao foi completa (${e.message})`;
491
+ r = null;
492
+ }
493
+ }
494
+ if (!r)
495
+ r = (0, intelligence_graph_extract_1.extrairGrafo)(entrada, parser);
496
+ const bytesDe = new Map(entrada.fontes.map((f) => [f.path, f.bytes]));
497
+ const fornecidas = new Map(r.grafo.snapshot.source_manifest.map((m) => {
498
+ const b = bytesDe.get(m.path);
499
+ return [m.path, { tipo: (0, intelligence_graph_extract_1.decodificarUtf8)(b) === null ? 'binario' : 'texto', bytes: b }];
500
+ }));
501
+ // KG4 (D6): `r.grafo` e o retorno de `validarGrafo` dentro da extracao; validar de novo so repetiria.
502
+ const conferencia = (0, intelligence_graph_contract_1.conferirFontesDoGrafoValidado)(r.grafo, fornecidas);
503
+ if (conferencia.estado !== 'verificada' || conferencia.evidenciasIndisponiveis > 0)
504
+ falha('grafo.indice.fontes-nao-conferidas');
505
+ const textoDoGrafo = (0, intelligence_graph_contract_1.canonico)(r.grafo), textoDoRelatorio = (0, intelligence_graph_contract_1.canonico)(r.relatorio), unidades = textoDasUnidades(r.unidades);
506
+ if (sha256(textoDoGrafo) !== r.digest)
507
+ falha('grafo.indice.digest-divergente');
508
+ let determinismo = null, incremental = null;
509
+ if (opcoes.verificar) {
510
+ const relatorio = sha256(textoDoRelatorio), dasUnidades = sha256(unidades);
511
+ determinismo = [['ordem invertida', [...entrada.fontes].reverse()], ['ordem embaralhada', embaralhar(entrada.fontes, 2026)]]
512
+ .map(([ordem, fontes]) => {
513
+ const outra = (0, intelligence_graph_extract_1.extrairGrafo)({ ...entrada, fontes }, parser);
514
+ const doOutro = (0, intelligence_graph_contract_1.sha256DoCanonico)(outra.relatorio), unidadesDoOutro = sha256(textoDasUnidades(outra.unidades));
515
+ return {
516
+ ordem, digest: outra.digest, relatorio: doOutro, unidades: unidadesDoOutro,
517
+ igual: outra.digest === r.digest && doOutro === relatorio && unidadesDoOutro === dasUnidades,
518
+ };
519
+ });
520
+ const diferente = determinismo.find((d) => !d.igual);
521
+ if (diferente)
522
+ falha('grafo.indice.nao-deterministico', diferente.ordem);
523
+ if (base) {
524
+ // KG4 (D7): a prova no repositorio de quem usa: o incremental da base da os mesmos bytes da completa.
525
+ let inc;
526
+ try {
527
+ inc = (0, intelligence_graph_extract_1.extrairGrafo)(entrada, parser, base.unidades);
528
+ }
529
+ catch (e) {
530
+ falha('grafo.indice.incremental-divergente', `o incremental da base ${curta(base.revision)} falhou: ${e.message}`);
531
+ }
532
+ const igual = (0, intelligence_graph_contract_1.canonico)(inc.grafo) === textoDoGrafo && (0, intelligence_graph_contract_1.canonico)(inc.relatorio) === textoDoRelatorio && textoDasUnidades(inc.unidades) === unidades;
533
+ if (!igual)
534
+ falha('grafo.indice.incremental-divergente', `base ${curta(base.revision)}`);
535
+ incremental = { base: base.revision, igual, reaproveitamento: inc.reaproveitamento };
536
+ }
537
+ }
538
+ const manifesto = {
539
+ schema: exports.INDICE_SCHEMA, chave, revision: head, ...perfil, graph_schema: intelligence_graph_contract_1.GRAFO_SCHEMA, snapshot_id: r.grafo.snapshot.snapshot_id,
540
+ graph_digest: r.digest, graph_bytes: Buffer.byteLength(textoDoGrafo, 'utf8'),
541
+ report_digest: sha256(textoDoRelatorio), report_bytes: Buffer.byteLength(textoDoRelatorio, 'utf8'),
542
+ contagens: r.relatorio.contagens,
543
+ conferencia: { fontes: conferencia.fontesVerificadas.length, evidencias: conferencia.evidenciasVerificadas },
544
+ units_schema: intelligence_graph_extract_1.UNIDADES_SCHEMA, units_digest: sha256(unidades), units_bytes: Buffer.byteLength(unidades, 'utf8'),
545
+ };
546
+ const conteudos = {
547
+ [ARQUIVOS.manifesto]: (0, intelligence_graph_contract_1.canonico)(manifesto), [ARQUIVOS.grafo]: textoDoGrafo, [ARQUIVOS.relatorio]: textoDoRelatorio, [ARQUIVOS.unidades]: unidades,
548
+ };
549
+ const daBase = modo === 'incremental' && base ? { revision: base.revision, chave: base.chave } : opcoes.verificar && base ? { revision: base.revision, chave: base.chave } : null;
550
+ const tmp = path.join(grafoDir, `.tmp-${(0, node_crypto_1.randomUUID)()}`);
551
+ pastaPrivada(tmp, true);
552
+ try {
553
+ for (const [nome, texto] of Object.entries(conteudos))
554
+ gravarPrivado(path.join(tmp, nome), texto);
555
+ sincronizarPasta(tmp);
556
+ opcoes.antesDePublicar?.();
557
+ const resultado = (estado) => ({
558
+ estado, motivo, chave, dir: final, manifesto, determinismo, ms: ms(), modo, base: daBase,
559
+ motivo_completo: modo === 'incremental' ? null : motivoCompleto, reaproveitamento: r?.reaproveitamento ?? null, incremental,
560
+ });
561
+ if (!existia && fs.lstatSync(final, { throwIfNoEntry: false })) {
562
+ // Corrida: outra construcao publicou a mesma chave durante esta. Vale a dela, se estiver integra.
563
+ lerIndice(ctx, chave, { grafo: false });
564
+ return resultado('existente');
565
+ }
566
+ if (existia && fs.lstatSync(final, { throwIfNoEntry: false })) {
567
+ if (motivo === null && mesmoConteudo(final, conteudos))
568
+ return resultado('reconstruido-identico');
569
+ // Mesma chave e conteudo integro diferente: a extracao nao se repetiu. A verificacao reprova; o forcar troca.
570
+ if (motivo === null && opcoes.verificar)
571
+ falha('grafo.indice.nao-deterministico', 'o indice guardado difere da extracao nova');
572
+ if (motivo === null)
573
+ motivo = 'conteudo diferente na mesma chave';
574
+ removerPasta(grafoDir, chave);
575
+ fs.renameSync(tmp, final);
576
+ sincronizarPasta(grafoDir);
577
+ return resultado('substituido');
578
+ }
579
+ try {
580
+ fs.renameSync(tmp, final);
581
+ }
582
+ catch (e) {
583
+ const codigo = e.code;
584
+ if (codigo !== 'ENOTEMPTY' && codigo !== 'EEXIST')
585
+ throw e;
586
+ lerIndice(ctx, chave, { grafo: false });
587
+ return resultado('existente');
588
+ }
589
+ sincronizarPasta(grafoDir);
590
+ return resultado('criado');
591
+ }
592
+ finally {
593
+ if (fs.lstatSync(tmp, { throwIfNoEntry: false }))
594
+ fs.rmSync(tmp, { recursive: true, force: true });
595
+ }
596
+ }
597
+ /** O indice do HEAD com o extrator desta instalacao, e a arvore como esta agora. */
598
+ function indiceDoHead(ctx, opcoes = {}) {
599
+ const arvore = (0, intelligence_graph_repo_1.revisaoDaArvore)(ctx.raiz);
600
+ if (arvore.head === null)
601
+ falha('grafo.indice.arvore-nao-limpa', 'sem-commit');
602
+ const perfil = perfilDoIndice(arvore.raiz, undefined, leituraDo(ctx));
603
+ const chave = chaveDoIndice(arvore.head, perfil);
604
+ try {
605
+ return { arvore, perfil, chave, indice: lerIndice(ctx, chave, opcoes) };
606
+ }
607
+ catch (e) {
608
+ if (e.message === 'grafo.indice.ausente')
609
+ falha('grafo.indice.ausente', `sem indice do HEAD ${arvore.head.slice(0, 12)}; rode ork grafo indexar`);
610
+ throw e;
611
+ }
612
+ }
613
+ function bytesDaPasta(dir) {
614
+ let total = 0;
615
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
616
+ const p = path.join(dir, e.name);
617
+ const st = fs.lstatSync(p);
618
+ total += st.isDirectory() ? bytesDaPasta(p) : st.size;
619
+ }
620
+ return total;
621
+ }
622
+ /** Os indices guardados, com a integridade de cada um, e as sobras de construcao interrompida. */
623
+ function estadoDosIndices(ctx) {
624
+ let dir;
625
+ try {
626
+ dir = pastaDoGrafo(ctx, false);
627
+ }
628
+ catch (e) {
629
+ if (e.message === 'grafo.indice.ausente')
630
+ return { dir: null, indices: [], sobras: [], bytes: 0 };
631
+ throw e;
632
+ }
633
+ const indices = [], sobras = [];
634
+ let bytes = 0;
635
+ for (const nome of fs.readdirSync(dir).sort(intelligence_graph_contract_1.compararUtf8)) {
636
+ // Uma construcao concorrente pode renomear o `.tmp-` entre a listagem e o `lstat`: some da lista.
637
+ const p = path.join(dir, nome), st = fs.lstatSync(p, { throwIfNoEntry: false });
638
+ if (!st)
639
+ continue;
640
+ let tamanho;
641
+ try {
642
+ tamanho = st.isDirectory() && !st.isSymbolicLink() ? bytesDaPasta(p) : st.size;
643
+ }
644
+ catch (e) {
645
+ if (e.code === 'ENOENT')
646
+ continue;
647
+ throw e;
648
+ }
649
+ bytes += tamanho;
650
+ if (!exports.NOME_DE_INDICE.test(nome)) {
651
+ sobras.push(nome);
652
+ continue;
653
+ }
654
+ try {
655
+ const { manifesto } = lerIndice(ctx, nome, { grafo: false });
656
+ indices.push({ chave: nome, bytes: tamanho, revision: manifesto.revision, repository_id: manifesto.repository_id, snapshot_id: manifesto.snapshot_id, problema: null });
657
+ }
658
+ catch (e) {
659
+ indices.push({ chave: nome, bytes: tamanho, revision: null, repository_id: null, snapshot_id: null, problema: e.message });
660
+ }
661
+ }
662
+ return { dir, indices, sobras, bytes };
663
+ }
664
+ /**
665
+ * Apaga os indices fora de `manter` e cuja revisao nao esta em `manterRevisoes` (ou todos, com
666
+ * `tudo`), e as sobras `.tmp-` e `.lixo-` com mais de uma hora. Nome que nao e do indice nunca e
667
+ * apagado.
668
+ */
669
+ function limparIndices(ctx, opcoes = {}) {
670
+ const estado = estadoDosIndices(ctx);
671
+ if (!estado.dir)
672
+ return { removidos: [], bytes: 0 };
673
+ const dir = estado.dir, agora = opcoes.agora ?? Date.now(), manter = new Set(opcoes.manter ?? []);
674
+ const revisoes = new Set(opcoes.manterRevisoes ?? []);
675
+ const removidos = [];
676
+ for (const i of estado.indices) {
677
+ if (!opcoes.tudo && (manter.has(i.chave) || (i.revision !== null && revisoes.has(i.revision))))
678
+ continue;
679
+ removerPasta(dir, i.chave);
680
+ removidos.push({ nome: i.chave, bytes: i.bytes });
681
+ }
682
+ for (const nome of estado.sobras) {
683
+ if (!/^\.(tmp|lixo)-[0-9a-f-]{36}$/.test(nome))
684
+ continue;
685
+ const p = path.join(dir, nome), st = fs.lstatSync(p, { throwIfNoEntry: false });
686
+ if (!st || agora - st.mtimeMs < exports.PRAZO_DE_SOBRA_MS)
687
+ continue;
688
+ const tamanho = st.isDirectory() && !st.isSymbolicLink() ? bytesDaPasta(p) : st.size;
689
+ removerPasta(dir, nome);
690
+ removidos.push({ nome, bytes: tamanho });
691
+ }
692
+ return { removidos, bytes: removidos.reduce((n, r) => n + r.bytes, 0) };
693
+ }