@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
package/dist/indice.js CHANGED
@@ -59,13 +59,19 @@ exports.INSUMOS_DO_INDICE = [
59
59
  * reversao e `ship.ts`, que e quem sabe de merge e de base; aqui so se conta o evento
60
60
  * que ele gravou.
61
61
  */
62
+ function pausaPrevistaDoFast(e) {
63
+ return e.tipo === ledger_1.TIPOS_DE_EVENTO.shipBloqueado && e.modo === 'fast' && e.motivo === 'human.pending';
64
+ }
62
65
  function calcularIndice(eventos) {
63
66
  const parcelas = [];
64
67
  let valor = exports.INDICE_BASE;
65
68
  for (const insumo of exports.INSUMOS_DO_INDICE) {
66
69
  // Ensaio de 03/10/2026 (RM-049): o `ork ship --dry-run` barrado grava `ship_blocked` com `dryRun: true`;
67
70
  // ensaio nao e tropeco da entrega, como ja nao e pausa no board (fatia 2 do ensaio da 0.5.0, P2).
68
- const ocorrencias = eventos.filter((e) => e.tipo === insumo.evento && e.dryRun !== true).length;
71
+ // EN8 (a), decisao do dono de 11/10/2026: no #Fast, o ship sem autorizacao de push e a pausa prevista
72
+ // do modo, e o `ship_blocked` com `human.pending` tambem nao conta.
73
+ const ocorrencias = eventos.filter((e) => e.tipo === insumo.evento && e.dryRun !== true &&
74
+ !pausaPrevistaDoFast(e)).length;
69
75
  if (ocorrencias === 0)
70
76
  continue;
71
77
  const bruto = insumo.peso * ocorrencias;
package/dist/init.js CHANGED
@@ -48,6 +48,7 @@ exports.detectar = detectar;
48
48
  exports.manifestoYaml = manifestoYaml;
49
49
  exports.exigirRepositorioParaInit = exigirRepositorioParaInit;
50
50
  exports.init = init;
51
+ const locale_1 = require("./locale");
51
52
  const fs = __importStar(require("node:fs"));
52
53
  const path = __importStar(require("node:path"));
53
54
  const manifest_1 = require("./manifest");
@@ -55,7 +56,8 @@ const slug_1 = require("./slug");
55
56
  const util_1 = require("./util");
56
57
  const horario_1 = require("./horario");
57
58
  const modos_1 = require("./modos");
58
- exports.PROXIMO_PASSO_INIT = 'Proximo passo: ork doctor; depois ork onboarding para conduzir a entrevista do projeto.';
59
+ /** O proximo passo do `ork init` em pt-BR; o texto do CLI sai pelo catalogo (`msg().init.proximoPasso`). */
60
+ exports.PROXIMO_PASSO_INIT = (0, locale_1.msg)('pt-BR').init.proximoPasso;
59
61
  /** Raiz do repositorio git, resolvendo tambem o caso de worktree. */
60
62
  function raizDoRepo(dirInicial) {
61
63
  const r = (0, util_1.exec)('git', ['rev-parse', '--show-toplevel'], dirInicial);
@@ -144,57 +146,57 @@ function detectar(dirInicial, nomeForcado, abbrevForcada) {
144
146
  verify: verifyDetectado(raiz),
145
147
  };
146
148
  }
147
- /** Serializa o manifesto v1 a partir da deteccao. */
148
- function manifestoYaml(d) {
149
- const linhaVerify = (chave, valor) => valor ? ` ${chave}: "${valor}"` : ` # ${chave}: nao detectado`;
150
- return `# Manifesto do Orkastery (v1, bloco B0)
151
- # Gerado por \`ork init\`. Fonte unica da configuracao do projeto.
152
- # Limite duro de 16 KB: prosa longa vai para a memoria com tag, nao para este arquivo.
149
+ /** Cada linha do texto vira comentario do YAML, com o recuo da chave em que ele fica. */
150
+ function comentario(texto, recuo = ' ') {
151
+ return texto.split('\n').map((linha) => `${recuo}# ${linha}`).join('\n');
152
+ }
153
+ /**
154
+ * Serializa o manifesto v1 a partir da deteccao. RM-061 (F3a, D1 a D3): os comentarios saem na lingua pedida, pela
155
+ * familia `gerados` do catalogo; chaves, valores e exemplos comentados sao este esqueleto, o mesmo nas duas linguas.
156
+ * Sem locale, pt-BR: so o `ork init` passa a lingua do dono (D2).
157
+ */
158
+ function manifestoYaml(d, locale = locale_1.LOCALE_PADRAO) {
159
+ const c = (0, locale_1.msg)(locale).gerados.manifesto;
160
+ const linhaVerify = (chave, valor) => valor ? ` ${chave}: "${valor}"` : ` # ${chave}: ${c.naoDetectado}`;
161
+ return `${comentario(c.cabecalho, '')}
153
162
 
154
163
  project:
155
164
  name: "${d.nome}"
156
- # abbrev: ate 3 caracteres [a-z0-9], parte 1 do slug de sessao, unica no workspace
165
+ ${comentario(c.abbrev)}
157
166
  abbrev: "${d.abbrev}"
158
167
  stage: nascente
159
168
 
160
169
  owner:
161
- # fuso do dono (nome IANA): todo horario mostrado a pessoas usa este fuso.
162
- # Ausente: fuso do sistema. Exemplo:
170
+ ${comentario(c.fuso)}
163
171
  # timezone: "${horario_1.FUSO_DE_BRASILIA}"
164
- # Preferências: ork onboarding set maestro --conteudo '{"owner":{"experience":true}}'
165
- # language: pt-BR # ausente: locale do sistema
172
+ ${comentario(c.preferencias(`ork onboarding set maestro --conteudo '{"owner":{"experience":true}}'`))}
173
+ # language: ${c.exemploDeIdioma} # ${c.idiomaAusente}
166
174
  # depth: curta # curta | detalhada
167
- # experience: true # false desativa o pacote na próxima instalação do adaptador
175
+ # experience: true # ${c.experienciaFalse}
168
176
 
169
177
  board:
170
178
  adapter: hermes-kanban
171
179
  default: default
172
180
 
173
181
  runtime:
174
- # Runtimes homologados:
175
- # claude-bg (padrao, mais maduro): despacha \`claude --bg\`, sessao visivel no app
176
- # e em \`claude agents --json\`
177
- # codex: despacha \`codex exec\` headless na sessao autenticada do Codex CLI,
178
- # verificado no rollout de $CODEX_HOME/sessions (requer \`codex login\`)
182
+ ${comentario(c.runtimes)}
179
183
  adapter: claude-bg
180
184
  model: opus
181
185
  effort: high
182
- # subscription-only: proibido despachar por provider pago (cobra por token, fora da assinatura)
186
+ ${comentario(c.providerPolicy)}
183
187
  provider_policy: subscription-only
184
- # sandbox do runtime codex: read-only | workspace-write | danger-full-access
185
- # (danger-full-access SO onde o bubblewrap nao roda; \`ork doctor\` tem a sonda)
188
+ ${comentario(c.sandbox)}
186
189
  sandbox: workspace-write
187
190
 
188
191
  conduction:
189
- # modo usado quando o pedido do builder nao traz #TAG
192
+ ${comentario(c.modoPadrao)}
190
193
  default_mode: classic
191
194
  allowed_modes: [${modos_1.ORDEM_DOS_MODOS.join(', ')}]
192
195
 
193
196
  worktree:
194
197
  base_branch: "${d.baseBranch}"
195
198
  dir: ".claude/worktrees"
196
- # true: o ork thread new cria a worktree e a branch da thread sem flag (--sem-worktree cria sem);
197
- # false ou ausente: só com --worktree auto
199
+ ${comentario(c.porThread)}
198
200
  por_thread: true
199
201
 
200
202
  verify:
@@ -203,54 +205,46 @@ ${linhaVerify('test', d.verify.test)}
203
205
  ${linhaVerify('typecheck', d.verify.typecheck)}
204
206
 
205
207
  ci:
206
- # Ative depois de publicar o workflow e tornar ork-verify um check obrigatório.
208
+ ${comentario(c.ci)}
207
209
  required_for_ship: false
208
210
  context: ork-verify
209
- # command: "npm test" # perfil hermético opcional do runner; sem ele, usa verify acima
211
+ # command: "npm test" # ${c.ciComando}
210
212
 
211
213
  concurrency:
212
214
  max_parallel_threads: 3
213
- # thread despachada sem atividade (ledger parado, sessao nao-working) por mais que
214
- # isto NAO ocupa vaga do escalonador: a vaga volta para quem esta pronto para rodar
215
+ ${comentario(c.staleAfter)}
215
216
  stale_after_min: 240
216
217
 
217
218
  handoff:
218
- # gate de tokens (bloco B1): rotaciona a sessao acima destas ocupacoes de janela
219
+ ${comentario(c.handoff)}
219
220
  rotate_above: 0.70
220
221
  force_rotate_above: 0.85
221
222
 
222
223
  retry:
223
- # autonomia do bloco B3: retry tipado por motivo de gate e fila duravel de rate limit
224
- # max_tentativas e o LIMITE DE ESCALACAO: estourado, pausa qualquer modo, ate #Auto
224
+ ${comentario(c.retry)}
225
225
  max_tentativas: 3
226
- # janela usada so quando o runtime diz "rate limit" e NAO diz a hora do reset
227
- # (fica marcada como estimativa na fila; o ork nunca inventa horario medido)
226
+ ${comentario(c.janelaPadrao)}
228
227
  janela_padrao_min: 60
229
228
  escalar_esforco: true
230
229
 
231
230
  memory:
232
- # files = fallback honesto sem OrkMind (handoff por arquivos, ponteiro path#ancora)
233
- # orkmind = camada semantica do bloco B6 por cima, com degradacao honesta para files
231
+ ${comentario(c.memoriaModo)}
234
232
  mode: files
235
- # NOME da variavel de ambiente com a DSN da base PROPRIA deste tenant, nunca a DSN.
236
- # Vazio = o regime orkmind nao liga (o ork nao adivinha base: base alheia e pior que
237
- # base nenhuma, precedente do incidente memory-orkmind).
233
+ ${comentario(c.databaseUrl)}
238
234
  database_url_env: ""
239
235
  cli: orkmind
240
236
  timeout_ms: 15000
241
- # busca por significado (ork memory search --texto): sem o bloco, provider none (desligada).
242
- # api_key_env recebe o NOME da variavel com a chave dedicada, nunca o valor.
237
+ ${comentario(c.embedding)}
243
238
  # embedding:
244
239
  # provider: "none" # none | openrouter
245
240
  # model: "qwen/qwen3-embedding-8b"
246
241
  # dim: 1024
247
242
  # api_key_env: "ORKASTERY_EMBEDDING_API_KEY"
248
- # fallback_model: "" # modelo local offline, ex.: intfloat/multilingual-e5-small
243
+ # fallback_model: "" # ${c.modeloOffline}
249
244
  # max_tokens_por_execucao: 1000000
250
245
 
251
246
  audit:
252
- # governanca de custo dos auditores periodicos (bloco B5)
253
- # janela ociosa da assinatura: fora dela a rodada so anda com --agora <quem>
247
+ ${comentario(c.audit)}
254
248
  janela_ociosa: "22:00-06:00"
255
249
  effort: eco
256
250
  since_padrao: 7d
@@ -273,10 +267,12 @@ function exigirRepositorioParaInit(dir) {
273
267
  const r = (0, util_1.exec)('git', ['rev-parse', '--is-inside-work-tree'], dir);
274
268
  if (r.ok && r.stdout.trim() === 'true')
275
269
  return;
276
- throw new Error(`init.fora-do-repositorio: ${path.resolve(dir)} não está num repositório git; nada foi criado. ` +
277
- 'Entre no repositório do projeto (ou crie um com git init e o primeiro commit) e rode ork init de novo.');
270
+ throw new Error((0, locale_1.msg)().init.foraDoRepositorio(path.resolve(dir)));
278
271
  }
279
- /** Gera o manifesto e o esqueleto de `.orkastery/`. Nao sobrescreve sem `force`. */
272
+ /**
273
+ * Gera o manifesto e o esqueleto de `.orkastery/`. Nao sobrescreve sem `force`. Os comentarios do manifesto saem
274
+ * no `locale` pedido (RM-061, F3a), pt-BR por omissao.
275
+ */
280
276
  function init(dirInicial, opcoes = {}) {
281
277
  const deteccao = detectar(dirInicial, opcoes.nome, opcoes.abbrev);
282
278
  const caminho = path.join(deteccao.raiz, manifest_1.NOME_MANIFESTO);
@@ -284,7 +280,7 @@ function init(dirInicial, opcoes = {}) {
284
280
  if (existe && !opcoes.force) {
285
281
  return { caminho, criado: false, deteccao };
286
282
  }
287
- (0, util_1.gravar)(caminho, manifestoYaml(deteccao));
283
+ (0, util_1.gravar)(caminho, manifestoYaml(deteccao, opcoes.locale));
288
284
  const estado = path.join(deteccao.raiz, manifest_1.DIR_ESTADO);
289
285
  fs.mkdirSync(path.join(estado, 'threads'), { recursive: true });
290
286
  // Fatia 2 do ensaio da 0.5.0 (P3): o estado e da maquina; fica fora do git sem mexer no `.gitignore` do usuario.
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ /**
3
+ * RM-037 (fatia 6, P6/T15): o registro de instabilidade, contrato `ork.instabilidade/v1`.
4
+ *
5
+ * Arquivo `core/instabilidade.json` na arvore verificada. Nasce vazio. Uma entrada diz que um teste
6
+ * depende do relogio e por isso reprova sob steal alto; ela so entra com a taxa MEDIDA (falhas em
7
+ * rodadas, com o comando que mediu e a data) e com revalidacao em ate 30 dias da medida. Vencida a
8
+ * revalidacao, a entrada deixa de valer ate alguem medir de novo.
9
+ *
10
+ * O registro nunca atenua uma falha sozinho: ele so diz ao verify que aquele teste e de relogio, e
11
+ * a reprovacao so vira `verify.timeout` com o steal medido acima de 40% na janela do comando. Sem
12
+ * steal, o teste registrado reprova como qualquer outro.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.CAUSAS_DE_INSTABILIDADE = exports.REVALIDACAO_MAXIMA_DIAS = exports.ARQUIVO_INSTABILIDADE = exports.CONTRATO_INSTABILIDADE = void 0;
16
+ exports.validarRegistro = validarRegistro;
17
+ exports.vigente = vigente;
18
+ exports.lerRegistroDeInstabilidade = lerRegistroDeInstabilidade;
19
+ exports.testesDeRelogioVigentes = testesDeRelogioVigentes;
20
+ const node_fs_1 = require("node:fs");
21
+ const node_path_1 = require("node:path");
22
+ exports.CONTRATO_INSTABILIDADE = 'ork.instabilidade/v1';
23
+ exports.ARQUIVO_INSTABILIDADE = 'core/instabilidade.json';
24
+ /** Prazo maximo entre a medida e a revalidacao. */
25
+ exports.REVALIDACAO_MAXIMA_DIAS = 30;
26
+ exports.CAUSAS_DE_INSTABILIDADE = ['relogio'];
27
+ const DIA_MS = 24 * 60 * 60 * 1000;
28
+ const CHAVES_DA_ENTRADA = new Set(['teste', 'arquivo', 'causa', 'taxa', 'revalidarAte', 'nota']);
29
+ const CHAVES_DA_TAXA = new Set(['falhas', 'rodadas', 'comando', 'medidaEm']);
30
+ function data(valor) {
31
+ if (typeof valor !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(valor))
32
+ return null;
33
+ const ms = Date.parse(`${valor}T00:00:00Z`);
34
+ return Number.isFinite(ms) && new Date(ms).toISOString().slice(0, 10) === valor ? ms : null;
35
+ }
36
+ const texto = (v) => typeof v === 'string' && v.trim() !== '';
37
+ const inteiro = (v) => typeof v === 'number' && Number.isInteger(v);
38
+ /** Os erros de contrato de um registro ja desserializado. Lista vazia: o registro vale. */
39
+ function validarRegistro(bruto) {
40
+ const erros = [];
41
+ if (!bruto || typeof bruto !== 'object' || Array.isArray(bruto))
42
+ return ['o registro precisa ser um objeto JSON'];
43
+ const r = bruto;
44
+ if (r.contrato !== exports.CONTRATO_INSTABILIDADE)
45
+ erros.push(`contrato precisa ser "${exports.CONTRATO_INSTABILIDADE}"`);
46
+ for (const chave of Object.keys(r)) {
47
+ if (chave !== 'contrato' && chave !== 'entradas')
48
+ erros.push(`chave desconhecida no registro: ${chave}`);
49
+ }
50
+ if (!Array.isArray(r.entradas))
51
+ return [...erros, 'entradas precisa ser uma lista (vazia no nascimento)'];
52
+ const vistos = new Set();
53
+ r.entradas.forEach((e, i) => {
54
+ const onde = `entradas[${i}]`;
55
+ if (!e || typeof e !== 'object' || Array.isArray(e)) {
56
+ erros.push(`${onde}: precisa ser um objeto`);
57
+ return;
58
+ }
59
+ const entrada = e;
60
+ for (const chave of Object.keys(entrada)) {
61
+ if (!CHAVES_DA_ENTRADA.has(chave))
62
+ erros.push(`${onde}: chave desconhecida ${chave}`);
63
+ }
64
+ if (!texto(entrada.teste))
65
+ erros.push(`${onde}.teste: nome do teste como o runner reporta`);
66
+ else if (vistos.has(entrada.teste))
67
+ erros.push(`${onde}.teste: "${entrada.teste}" repetido`);
68
+ else
69
+ vistos.add(entrada.teste);
70
+ if (!texto(entrada.arquivo))
71
+ erros.push(`${onde}.arquivo: caminho do teste`);
72
+ if (!exports.CAUSAS_DE_INSTABILIDADE.includes(entrada.causa)) {
73
+ erros.push(`${onde}.causa: ${exports.CAUSAS_DE_INSTABILIDADE.join(' ou ')}`);
74
+ }
75
+ if (entrada.nota !== undefined && typeof entrada.nota !== 'string')
76
+ erros.push(`${onde}.nota: texto`);
77
+ const taxa = entrada.taxa;
78
+ let medida = null;
79
+ if (!taxa || typeof taxa !== 'object' || Array.isArray(taxa)) {
80
+ erros.push(`${onde}.taxa: sem taxa medida a entrada nao entra (falhas, rodadas, comando, medidaEm)`);
81
+ }
82
+ else {
83
+ for (const chave of Object.keys(taxa)) {
84
+ if (!CHAVES_DA_TAXA.has(chave))
85
+ erros.push(`${onde}.taxa: chave desconhecida ${chave}`);
86
+ }
87
+ if (!inteiro(taxa.rodadas) || taxa.rodadas < 1)
88
+ erros.push(`${onde}.taxa.rodadas: inteiro de rodadas medidas, 1 ou mais`);
89
+ if (!inteiro(taxa.falhas) || taxa.falhas < 1)
90
+ erros.push(`${onde}.taxa.falhas: inteiro de reprovacoes medidas, 1 ou mais`);
91
+ else if (inteiro(taxa.rodadas) && taxa.falhas > taxa.rodadas)
92
+ erros.push(`${onde}.taxa.falhas: maior que rodadas`);
93
+ if (!texto(taxa.comando))
94
+ erros.push(`${onde}.taxa.comando: o comando que mediu, reproduzivel`);
95
+ medida = data(taxa.medidaEm);
96
+ if (medida === null)
97
+ erros.push(`${onde}.taxa.medidaEm: data AAAA-MM-DD valida`);
98
+ }
99
+ const revalidar = data(entrada.revalidarAte);
100
+ if (revalidar === null)
101
+ erros.push(`${onde}.revalidarAte: data AAAA-MM-DD valida`);
102
+ else if (medida !== null) {
103
+ if (revalidar < medida)
104
+ erros.push(`${onde}.revalidarAte: antes da medida`);
105
+ else if (revalidar - medida > exports.REVALIDACAO_MAXIMA_DIAS * DIA_MS) {
106
+ erros.push(`${onde}.revalidarAte: no maximo ${exports.REVALIDACAO_MAXIMA_DIAS} dias depois de taxa.medidaEm`);
107
+ }
108
+ }
109
+ });
110
+ return erros;
111
+ }
112
+ /** A entrada vale hoje? Vale ate o fim do dia de `revalidarAte`, em UTC. */
113
+ function vigente(entrada, agoraMs = Date.now()) {
114
+ const ate = data(entrada.revalidarAte);
115
+ return ate !== null && agoraMs < ate + DIA_MS;
116
+ }
117
+ /** Le o registro da arvore `cwd`. Ausente: vazio. Ilegivel ou fora do contrato: erros e nada vale. */
118
+ function lerRegistroDeInstabilidade(cwd, agoraMs = Date.now()) {
119
+ const arquivo = (0, node_path_1.join)(cwd, exports.ARQUIVO_INSTABILIDADE);
120
+ if (!(0, node_fs_1.existsSync)(arquivo))
121
+ return { arquivo: exports.ARQUIVO_INSTABILIDADE, presente: false, erros: [], vigentes: [], vencidas: [] };
122
+ let bruto;
123
+ try {
124
+ bruto = JSON.parse((0, node_fs_1.readFileSync)(arquivo, 'utf8'));
125
+ }
126
+ catch (e) {
127
+ return { arquivo: exports.ARQUIVO_INSTABILIDADE, presente: true, erros: [`JSON ilegivel: ${e.message}`], vigentes: [], vencidas: [] };
128
+ }
129
+ const erros = validarRegistro(bruto);
130
+ if (erros.length > 0)
131
+ return { arquivo: exports.ARQUIVO_INSTABILIDADE, presente: true, erros, vigentes: [], vencidas: [] };
132
+ const entradas = bruto.entradas;
133
+ return {
134
+ arquivo: exports.ARQUIVO_INSTABILIDADE,
135
+ presente: true,
136
+ erros: [],
137
+ vigentes: entradas.filter((e) => vigente(e, agoraMs)),
138
+ vencidas: entradas.filter((e) => !vigente(e, agoraMs)),
139
+ };
140
+ }
141
+ /** Os nomes dos testes de relogio que o verify pode considerar nesta rodada. */
142
+ function testesDeRelogioVigentes(registro) {
143
+ return new Set(registro.vigentes.filter((e) => e.causa === 'relogio').map((e) => e.teste));
144
+ }
@@ -18,4 +18,6 @@ exports.TESTES_DE_INTEGRACAO_LOCAL = new Set([
18
18
  'rm037-fatia3-hard-link-mcp-git.test.js', 'rm037-fatia3-lease-mcp-git.test.js',
19
19
  // RM-038 (fatia de correcao): a ponte com o GovernedStore real da biblioteca instalada.
20
20
  'rm038-universo-ponte.test.js',
21
+ // RM-059 (T0.10): a conferencia pontual com o sandbox real do codex, como o mcp-verify.test.js.
22
+ 'claims-conferir-sandbox.test.js',
21
23
  ]);
@@ -33,11 +33,12 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.CORRECAO_DO_INDICE = exports.USO_DO_GRAFO = exports.AMOSTRA_SCHEMA = exports.STATUS_SCHEMA = void 0;
36
+ exports.CORRECAO_DO_INDICE = exports.USO_DO_GRAFO = exports.AMOSTRA_SCHEMA = exports.REDACAO_DOS_ANALISADORES = exports.STATUS_SCHEMA = void 0;
37
37
  exports.descreverVersoes = descreverVersoes;
38
38
  exports.correcaoDosAnalisadores = correcaoDosAnalisadores;
39
39
  exports.checarAnalisadoresDoGrafo = checarAnalisadoresDoGrafo;
40
40
  exports.lerPedido = lerPedido;
41
+ exports.situacaoNaArvore = situacaoNaArvore;
41
42
  exports.executarGrafo = executarGrafo;
42
43
  /**
43
44
  * RM-031 KG3 (D7, D8): `ork grafo`, o CLI do indice e da consulta do grafo de codigo.
@@ -78,17 +79,21 @@ const RECUSA_DOS_ANALISADORES = 'grafo.parser.indisponivel';
78
79
  * de `MODULOS_DO_EXTRATOR`, para a redacao nao entrar na chave do indice; a versao vem de quem chama
79
80
  * (o `index.ts`), porque o CLI do grafo so importa a propria familia (fronteira do KG3).
80
81
  */
81
- function correcaoDosAnalisadores(motivo, versao) {
82
- if (/ERR_REQUIRE_ESM/.test(motivo)) {
83
- return `o micromark e so ESM, e este Node (${process.version}) nao o carrega por require: use Node 20.19, 22.12 ou mais novo, ` +
84
- 'sem a opcao --no-experimental-require-module (na linha de comando ou no NODE_OPTIONS)';
85
- }
82
+ function correcaoDosAnalisadores(motivo, versao, redacao = exports.REDACAO_DOS_ANALISADORES) {
83
+ if (/ERR_REQUIRE_ESM/.test(motivo))
84
+ return redacao.soEsm(process.version);
86
85
  // Sem a versao do pacote (`0.0.0-desconhecida` quando o package.json dele nao e achado), o comando
87
86
  // sai sem `@<versao>` e o npm instala a ultima publicada.
88
87
  const fixada = versao && /^\d+\.\d+\.\d+/.test(versao) && !versao.startsWith('0.0.0') ? `@${versao}` : '';
89
- return `reinstale o ork global, que traz os analisadores como dependencias: npm install -g @orkastery/cli${fixada}` +
90
- ' (instalado dentro de um projeto ou pelo npx, o npm deixa os analisadores fora do pacote do ork, e o grafo os recusa)';
88
+ return redacao.reinstale(fixada);
91
89
  }
90
+ exports.REDACAO_DOS_ANALISADORES = {
91
+ recusa: (motivo) => `${motivo}: o ork grafo indexar recusa nesta instalacao, e sem o indice as consultas e as tools ork_grafo_* tambem`,
92
+ soEsm: (versaoDoNode) => `o micromark e so ESM, e este Node (${versaoDoNode}) nao o carrega por require: use Node 20.19, 22.12 ou mais novo, ` +
93
+ 'sem a opcao --no-experimental-require-module (na linha de comando ou no NODE_OPTIONS)',
94
+ reinstale: (fixada) => `reinstale o ork global, que traz os analisadores como dependencias: npm install -g @orkastery/cli${fixada}` +
95
+ ' (instalado dentro de um projeto ou pelo npx, o npm deixa os analisadores fora do pacote do ork, e o grafo os recusa)',
96
+ };
92
97
  /**
93
98
  * RM-031 (pacote do npm): o check "analisadores do grafo" do `ork doctor`. Carrega o que o
94
99
  * `ork grafo indexar` carrega (`carregarAnalisadores`), entao diz o que o indexar faria, inclusive no
@@ -96,7 +101,7 @@ function correcaoDosAnalisadores(motivo, versao) {
96
101
  * fase. Fora da familia do grafo so o `index.ts` e o worker do MCP a abrem (fronteira do KG1), entao o
97
102
  * `index.ts` passa este check ao doctor, que nao importa o grafo.
98
103
  */
99
- function checarAnalisadoresDoGrafo(versao, carregar = intelligence_graph_parsers_1.carregarAnalisadores, versoes = intelligence_graph_parsers_1.versoesDosAnalisadores) {
104
+ function checarAnalisadoresDoGrafo(versao, carregar = intelligence_graph_parsers_1.carregarAnalisadores, versoes = intelligence_graph_parsers_1.versoesDosAnalisadores, redacao = exports.REDACAO_DOS_ANALISADORES) {
100
105
  const nome = 'analisadores do grafo';
101
106
  try {
102
107
  carregar();
@@ -104,9 +109,7 @@ function checarAnalisadoresDoGrafo(versao, carregar = intelligence_graph_parsers
104
109
  }
105
110
  catch (e) {
106
111
  const motivo = e.message;
107
- return { nome, nivel: 'warn',
108
- detalhe: `${motivo}: o ork grafo indexar recusa nesta instalacao, e sem o indice as consultas e as tools ork_grafo_* tambem`,
109
- correcao: correcaoDosAnalisadores(motivo, versao) };
112
+ return { nome, nivel: 'warn', detalhe: redacao.recusa(motivo), correcao: correcaoDosAnalisadores(motivo, versao, redacao) };
110
113
  }
111
114
  }
112
115
  /** O mesmo schema da amostra do KG2: as amostras auditadas continuam validas. */
@@ -366,7 +369,10 @@ function semIndiceDoHead(ctx, original) {
366
369
  }
367
370
  throw original;
368
371
  }
369
- /** O indice do HEAD pronto para consulta, com a concessao local e o cabecalho; `grafo` ja vem filtrado por ela. */
372
+ /**
373
+ * O indice do HEAD pronto para consulta, com a concessao local e o cabecalho; `grafo` ja vem filtrado por ela.
374
+ * KG5 fatia 5 (D3): `situacao` so le a arvore quando o cabecalho diz `modificada`; limpa, tudo vale `igual`.
375
+ */
370
376
  function consultavel(ctx) {
371
377
  let doHead;
372
378
  try {
@@ -385,7 +391,8 @@ function consultavel(ctx) {
385
391
  arvore: arvore.motivo === null ? 'limpa' : 'modificada',
386
392
  extratores: grafo.snapshot.extractors.map((e) => ({ extractor_id: e.extractor_id, extractor_version: e.extractor_version })),
387
393
  };
388
- return { g: (0, intelligence_graph_query_1.prepararConsulta)(grafo, concessao), cabecalho, grafo, raiz: arvore.raiz };
394
+ const situacao = cabecalho.arvore === 'limpa' ? intelligence_graph_query_1.TUDO_IGUAL : situacaoNaArvore(arvore.raiz, grafo.snapshot.source_manifest);
395
+ return { g: (0, intelligence_graph_query_1.prepararConsulta)(grafo, concessao), cabecalho, grafo, raiz: arvore.raiz, situacao };
389
396
  }
390
397
  function responder(ctx, p, r) {
391
398
  ctx.escrever(p.bandeiras.has('json') ? (0, intelligence_graph_query_1.jsonDaResposta)(r) : (0, intelligence_graph_query_1.textoDaResposta)(r));
@@ -430,15 +437,16 @@ function consultar(ctx, p) {
430
437
  // As opcoes sao conferidas antes de carregar o indice: erro de uso nao custa a leitura do grafo.
431
438
  const [a, b] = p.posicionais;
432
439
  const profundidade = inteiro(p, 'profundidade'), limite = inteiro(p, 'limite'), s = sentido(p), t = tipos(p), teto = tetoDeBytes(p);
433
- const { g, cabecalho } = consultavel(ctx);
440
+ const { g, cabecalho, situacao } = consultavel(ctx);
441
+ // A busca binaria do teto pergunta de novo com outro limite; a situacao guarda o que ja leu da arvore.
434
442
  const perguntar = (lim) => {
435
443
  if (p.sub === 'vizinhos')
436
- return (0, intelligence_graph_query_1.vizinhos)(g, cabecalho, a, { profundidade, limite: lim, sentido: s, tipos: t });
444
+ return (0, intelligence_graph_query_1.vizinhos)(g, cabecalho, a, { profundidade, limite: lim, sentido: s, tipos: t }, situacao);
437
445
  if (p.sub === 'chamadores')
438
- return (0, intelligence_graph_query_1.chamadores)(g, cabecalho, a, { profundidade, limite: lim });
446
+ return (0, intelligence_graph_query_1.chamadores)(g, cabecalho, a, { profundidade, limite: lim }, situacao);
439
447
  if (p.sub === 'importadores')
440
- return (0, intelligence_graph_query_1.importadores)(g, cabecalho, a, { profundidade, limite: lim });
441
- return (0, intelligence_graph_query_1.caminho)(g, cabecalho, a, b, { sentido: s, tipos: t });
448
+ return (0, intelligence_graph_query_1.importadores)(g, cabecalho, a, { profundidade, limite: lim }, situacao);
449
+ return (0, intelligence_graph_query_1.caminho)(g, cabecalho, a, b, { sentido: s, tipos: t }, situacao);
442
450
  };
443
451
  if (teto === undefined)
444
452
  return responder(ctx, p, perguntar(limite));
@@ -452,18 +460,21 @@ function contexto(ctx, p) {
452
460
  if (!ctx.contextoDaThread)
453
461
  throw new intelligence_graph_query_1.ErroDeConsulta('grafo.contexto.indisponivel', 'borda sem leitor da thread');
454
462
  const lido = ctx.contextoDaThread(p.posicionais[0]);
455
- const { grafo, cabecalho } = consultavel({ ...ctx, raiz: lido.raiz });
463
+ const { grafo, cabecalho, situacao } = consultavel({ ...ctx, raiz: lido.raiz });
456
464
  if (lido.head !== cabecalho.revision)
457
465
  throw new intelligence_graph_query_1.ErroDeConsulta('grafo.contexto.revisao-mudou', 'HEAD mudou durante a leitura; repita a consulta');
458
- const json = (0, intelligence_graph_contexto_1.pacoteDeContexto)(grafo, cabecalho, lido.entrada, teto);
466
+ const json = (0, intelligence_graph_contexto_1.pacoteDeContexto)(grafo, cabecalho, lido.entrada, teto, situacao);
459
467
  if (p.bandeiras.has('json'))
460
468
  ctx.escrever(json);
461
469
  else {
462
470
  const r = JSON.parse(json);
471
+ // KG5 fatia 5 (D9): o resumo conta as ligacoes cuja fonte mudou na arvore, como o JSON marca.
472
+ const mudadas = r.arestas.filter((a) => a.arvore === 'modificada').length;
463
473
  ctx.escrever([`Contexto ${r.thread} @ ${r.indice.revision}`,
464
474
  ` ${r.sementes.length}/${r.total_sementes} sementes, ${r.arestas.length}/${r.total_ligacoes} ligacoes agregadas; ${r.medida.pacote_bytes} bytes JSON`,
465
475
  ` tamanho dos arquivos no pacote (nao descoberta): ${r.medida.leitura_crua_bytes} bytes (revisao indexada); tokens: unavailable`,
466
476
  ` diff: ${r.fontes.diff}`,
477
+ ...(mudadas ? [` ligacoes com fonte modificada na arvore: ${mudadas}`] : []),
467
478
  ` truncado: ${r.truncado}; arvore: ${r.indice.arvore}; use --json para nos e evidencias`, json].join('\n'));
468
479
  }
469
480
  return 0;
@@ -507,6 +518,66 @@ function leitorDaArvore(raiz, grafo) {
507
518
  return bytes;
508
519
  };
509
520
  }
521
+ /**
522
+ * KG5 fatia 5 (D3, D6): a situacao de cada fonte do indice na arvore, pelos bytes. So caminho do manifesto;
523
+ * sem seguir link, nem no ultimo nome nem numa pasta do caminho: o caminho real tem de ser o proprio caminho
524
+ * indexado dentro da raiz (CHECK rodada 1, A1). So arquivo regular; tamanho diferente do indexado e
525
+ * `modificada` sem ler, e o sha256 dos bytes decide o resto, lidos ate o tamanho indexado mais um. O arquivo
526
+ * aberto tem de ser o mesmo do `lstat` (dispositivo e inode, CHECK rodada 1, S3): troca entre a conferencia e
527
+ * a abertura vira `ausente`; duas trocas no intervalo ainda passam, e revelariam so se os bytes sao os
528
+ * indexados. Qualquer falha e `ausente`: nao ha arquivo regular legivel no caminho. Cada caminho e lido uma
529
+ * vez, sob demanda, e nada e escrito.
530
+ */
531
+ function situacaoNaArvore(raiz, manifesto) {
532
+ const porCaminho = new Map(manifesto.map((m) => [m.path, m]));
533
+ const vistas = new Map();
534
+ let raizReal = null;
535
+ const ler = (p) => {
536
+ const m = porCaminho.get(p);
537
+ if (!m)
538
+ return 'ausente';
539
+ const absoluto = path.join(raiz, p);
540
+ try {
541
+ raizReal ??= fs.realpathSync(raiz);
542
+ const st = fs.lstatSync(absoluto, { throwIfNoEntry: false });
543
+ if (!st || !st.isFile())
544
+ return 'ausente';
545
+ // CHECK rodada 2 (R2-S1): caminho de manifesto adulterado com `..` coincidiria com o real fora da raiz.
546
+ const real = fs.realpathSync(absoluto);
547
+ if (real !== path.join(raizReal, p) || !real.startsWith(raizReal + path.sep))
548
+ return 'ausente';
549
+ if (st.size !== m.size_bytes)
550
+ return 'modificada';
551
+ const fd = fs.openSync(absoluto, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
552
+ try {
553
+ const aberto = fs.fstatSync(fd);
554
+ if (!aberto.isFile() || aberto.dev !== st.dev || aberto.ino !== st.ino)
555
+ return 'ausente';
556
+ if (aberto.size !== m.size_bytes)
557
+ return 'modificada';
558
+ const bytes = Buffer.alloc(m.size_bytes + 1);
559
+ let lidos = 0;
560
+ for (let n = 1; n > 0 && lidos < bytes.length; lidos += n)
561
+ n = fs.readSync(fd, bytes, lidos, bytes.length - lidos, null);
562
+ return lidos === m.size_bytes && sha256(bytes.subarray(0, lidos)) === m.source_hash ? 'igual' : 'modificada';
563
+ }
564
+ finally {
565
+ fs.closeSync(fd);
566
+ }
567
+ }
568
+ catch {
569
+ return 'ausente';
570
+ }
571
+ };
572
+ return (p) => {
573
+ const vista = vistas.get(p);
574
+ if (vista !== undefined)
575
+ return vista;
576
+ const s = ler(p);
577
+ vistas.set(p, s);
578
+ return s;
579
+ };
580
+ }
510
581
  const localizadorDe = (g) => new Map(g.nodes.map((n) => [n.node_id, { kind: n.kind, path: n.locator.path, fragment: n.locator.fragment }]));
511
582
  const rotulo = (l) => `${l.kind}:${l.path}${l.fragment === null ? '' : `#${l.fragment}`}`;
512
583
  const chaveDaAresta = (kind, de, para) => `${kind} ${rotulo(de)} -> ${rotulo(para)}`;