@orkastery/cli 0.5.6 → 0.6.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 (396) hide show
  1. package/adapters/README.md +18 -0
  2. package/adapters/claude-code/.claude-plugin/plugin.json +1 -0
  3. package/adapters/claude-code/README.md +37 -5
  4. package/adapters/claude-code/agents/ork-check.md +1 -0
  5. package/adapters/claude-code/agents/ork-go.md +1 -0
  6. package/adapters/claude-code/agents/ork-goal.md +1 -0
  7. package/adapters/claude-code/agents/ork-master.md +1 -0
  8. package/adapters/claude-code/agents/ork-plan.md +1 -0
  9. package/adapters/claude-code/agents/ork-ship.md +1 -0
  10. package/adapters/claude-code/hooks/hooks.json +2 -1
  11. package/adapters/claude-code/hooks/ork-guard.js +15 -1
  12. package/adapters/claude-code/hooks/ork-resposta.js +91 -0
  13. package/adapters/claude-code/hooks/ork-sensor.js +18 -2
  14. package/adapters/claude-code/hooks/quota-signal.js +14 -0
  15. package/adapters/claude-code/mods/ork-mensageiro/.claude-plugin/plugin.json +14 -0
  16. package/adapters/claude-code/mods/ork-mensageiro/hooks/caixa.ts +215 -0
  17. package/adapters/claude-code/mods/ork-mensageiro/hooks/hooks.json +3 -0
  18. package/adapters/claude-code/mods/ork-mensageiro/hooks/painel.tsx +165 -0
  19. package/adapters/claude-code/mods/ork-mensageiro/hooks/recepcao.ts +256 -0
  20. package/adapters/claude-code/mods/ork-mensageiro/hooks/register.tsx +481 -0
  21. package/adapters/claude-code/mods/ork-mensageiro/orkastery-mod.json +5 -0
  22. package/adapters/claude-code/mods/ork-mensageiro/types/index.d.ts +85 -0
  23. package/adapters/claude-code/mods/ork-pista/hooks/ci.ts +9 -5
  24. package/adapters/claude-code/mods/ork-ritual/.claude-plugin/plugin.json +14 -0
  25. package/adapters/claude-code/mods/ork-ritual/hooks/hooks.json +3 -0
  26. package/adapters/claude-code/mods/ork-ritual/hooks/nota.ts +88 -0
  27. package/adapters/claude-code/mods/ork-ritual/hooks/register.tsx +494 -0
  28. package/adapters/claude-code/mods/ork-ritual/hooks/replay.tsx +271 -0
  29. package/adapters/claude-code/mods/ork-ritual/hooks/ritual.ts +218 -0
  30. package/adapters/claude-code/mods/ork-ritual/orkastery-mod.json +5 -0
  31. package/adapters/claude-code/mods/ork-ritual/types/index.d.ts +106 -0
  32. package/adapters/claude-code/mods/ork-sentinela/hooks/vigia.ts +1 -1
  33. package/adapters/codex/skills/ork/SKILL.md +2 -0
  34. package/adapters/hermes/README.md +64 -1
  35. package/adapters/hermes/bin/ork-conhecimento-colecoes.sh +14 -0
  36. package/adapters/hermes/bin/ork-conhecimento-listar.sh +15 -0
  37. package/adapters/hermes/bin/ork-conhecimento-mostrar.sh +16 -0
  38. package/adapters/hermes/bin/ork-conhecimento-verificar.sh +15 -0
  39. package/adapters/hermes/bin/ork-vigia-batida.py +127 -0
  40. package/adapters/hermes/hermes.plugin.json +4 -0
  41. package/adapters/hermes/hitl-ingress/__init__.py +101 -8
  42. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +2 -2
  43. package/adapters/openclaw/README.md +81 -9
  44. package/adapters/openclaw/bin/ork-vigia-batida.js +86 -0
  45. package/adapters/openclaw/dist/hitl-ingress.js +106 -9
  46. package/adapters/openclaw/dist/index.js +86 -0
  47. package/adapters/openclaw/openclaw.plugin.json +34 -4
  48. package/adapters/openclaw/package.json +2 -2
  49. package/adapters/openclaw/src/hitl-ingress.ts +103 -9
  50. package/adapters/openclaw/src/index.ts +94 -0
  51. package/assets/adapters-en/claude-code/README.md +403 -0
  52. package/assets/adapters-en/claude-code/agents/ork-check.md +33 -0
  53. package/assets/adapters-en/claude-code/agents/ork-go.md +33 -0
  54. package/assets/adapters-en/claude-code/agents/ork-goal.md +34 -0
  55. package/assets/adapters-en/claude-code/agents/ork-master.md +35 -0
  56. package/assets/adapters-en/claude-code/agents/ork-plan.md +33 -0
  57. package/assets/adapters-en/claude-code/agents/ork-ship.md +33 -0
  58. package/assets/adapters-en/claude-code/commands/check.md +46 -0
  59. package/assets/adapters-en/claude-code/commands/go.md +46 -0
  60. package/assets/adapters-en/claude-code/commands/goal.md +55 -0
  61. package/assets/adapters-en/claude-code/commands/master.md +54 -0
  62. package/assets/adapters-en/claude-code/commands/onboarding.md +22 -0
  63. package/assets/adapters-en/claude-code/commands/ork.md +126 -0
  64. package/assets/adapters-en/claude-code/commands/plan.md +47 -0
  65. package/assets/adapters-en/claude-code/commands/ship.md +45 -0
  66. package/assets/adapters-en/codex/skills/ork/SKILL.md +85 -0
  67. package/assets/adapters-en/hermes/README.md +273 -0
  68. package/assets/adapters-en/hermes/skills/orkastery-devmaster/SKILL.md +169 -0
  69. package/assets/references-en/README.md +25 -0
  70. package/assets/references-en/code-review-axes.md +82 -0
  71. package/assets/references-en/definition-of-done.md +74 -0
  72. package/assets/references-en/performance-checklist.md +53 -0
  73. package/assets/references-en/security-checklist.md +101 -0
  74. package/assets/references-en/testing-patterns.md +51 -0
  75. package/assets/security-scan/defaults/.golangci.yml +2 -0
  76. package/assets/security-scan/defaults/eslint.config.mjs +11 -0
  77. package/assets/security-scan/defaults/gitleaks.toml +3 -0
  78. package/assets/security-scan/defaults/ruff.toml +1 -0
  79. package/assets/security-scan/tools-agil.json +87 -0
  80. package/assets/security-scan/tools.json +625 -0
  81. package/assets/skills-en/core/onboarding/SKILL.md +58 -0
  82. package/assets/skills-en/core/orkastery-bootstrap/SKILL.md +119 -0
  83. package/assets/skills-en/core/thread-state/SKILL.md +88 -0
  84. package/assets/skills-en/governance/decision-triage/SKILL.md +65 -0
  85. package/assets/skills-en/governance/narrative-guardian/SKILL.md +66 -0
  86. package/assets/skills-en/governance/roadmap-keeper/SKILL.md +60 -0
  87. package/assets/skills-en/governance/scope-check-capability-map/SKILL.md +71 -0
  88. package/assets/skills-en/observability/thread-tracing/SKILL.md +63 -0
  89. package/assets/skills-en/phases/check-quality/SKILL.md +103 -0
  90. package/assets/skills-en/phases/go-implementation/SKILL.md +78 -0
  91. package/assets/skills-en/phases/goal-definition/SKILL.md +69 -0
  92. package/assets/skills-en/phases/master-metrics/SKILL.md +72 -0
  93. package/assets/skills-en/phases/plan-specification/SKILL.md +67 -0
  94. package/assets/skills-en/phases/security-remediation/SKILL.md +70 -0
  95. package/assets/skills-en/phases/ship-release/SKILL.md +71 -0
  96. package/assets/skills-en/reviewers/code-reviewer/SKILL.md +61 -0
  97. package/assets/skills-en/reviewers/security-auditor/SKILL.md +93 -0
  98. package/assets/skills-en/reviewers/test-engineer/SKILL.md +66 -0
  99. package/assets/skills-en/reviewers/web-performance-auditor/SKILL.md +63 -0
  100. package/dist/adapters/claude-bg.js +123 -9
  101. package/dist/adapters/codex-controller-worker.js +12 -5
  102. package/dist/adapters/codex-events.js +16 -4
  103. package/dist/adapters/codex.js +3 -1
  104. package/dist/adapters/grok-events.js +227 -0
  105. package/dist/adapters/grok-mcp-servidor.js +612 -0
  106. package/dist/adapters/grok-mcp.js +178 -0
  107. package/dist/adapters/grok.js +686 -0
  108. package/dist/agendador-cpu.js +92 -0
  109. package/dist/atualizar-branch.js +245 -0
  110. package/dist/auditoria.js +13 -11
  111. package/dist/auditrun.js +51 -46
  112. package/dist/autorizacao-duravel.js +327 -0
  113. package/dist/board.js +94 -40
  114. package/dist/branch-de-estado.js +23 -10
  115. package/dist/busca-semantica.js +7 -5
  116. package/dist/catalogo.js +2 -1
  117. package/dist/changelog-reconciliacao.js +290 -0
  118. package/dist/check-agil.js +528 -0
  119. package/dist/check-modo.js +41 -0
  120. package/dist/check-paralelo.js +238 -0
  121. package/dist/ci.js +248 -45
  122. package/dist/ciclos.js +8 -6
  123. package/dist/claim-lint.js +12 -8
  124. package/dist/claims-conferir.js +47 -39
  125. package/dist/claims.js +10 -9
  126. package/dist/company-brain-client.js +10 -3
  127. package/dist/company-brain-migration.js +11 -5
  128. package/dist/conducao-batida.js +759 -0
  129. package/dist/conducao-sentinela.js +25 -8
  130. package/dist/conducao.js +89 -29
  131. package/dist/conhecimento-consulta.js +270 -0
  132. package/dist/conhecimento.js +111 -87
  133. package/dist/conta-esgotada.js +127 -0
  134. package/dist/dado-privado.js +816 -0
  135. package/dist/decisao-autonoma.js +5 -4
  136. package/dist/demo.js +14 -12
  137. package/dist/dependencias-da-worktree.js +25 -0
  138. package/dist/divida.js +38 -36
  139. package/dist/docs.js +89 -89
  140. package/dist/doctor-forja.js +343 -0
  141. package/dist/doctor-pulse-cron.js +113 -1
  142. package/dist/doctor.js +164 -6
  143. package/dist/dsn-da-memoria.js +417 -0
  144. package/dist/entrega-por-mod.js +503 -0
  145. package/dist/entrega-pr.js +154 -54
  146. package/dist/esgotamentos.js +13 -11
  147. package/dist/estado-thread.js +21 -18
  148. package/dist/evalrunner.js +35 -32
  149. package/dist/fabrica-batida.js +34 -21
  150. package/dist/fabrica-estado.js +33 -21
  151. package/dist/fix.js +21 -19
  152. package/dist/forja/adaptador.js +6 -0
  153. package/dist/forja/ambiente-de-ci.js +27 -0
  154. package/dist/forja/api.js +256 -0
  155. package/dist/forja/cache.js +89 -0
  156. package/dist/forja/cli.js +60 -0
  157. package/dist/forja/contrato.js +9 -0
  158. package/dist/forja/endereco.js +43 -0
  159. package/dist/forja/estado.js +22 -0
  160. package/dist/forja/executor.js +73 -0
  161. package/dist/forja/github/ci.js +95 -0
  162. package/dist/forja/github/dono-da-rede.js +18 -0
  163. package/dist/forja/github/metadados.js +15 -0
  164. package/dist/forja/github/pedidos.js +200 -0
  165. package/dist/forja/gitlab/ci.js +141 -0
  166. package/dist/forja/gitlab/consultas.js +59 -0
  167. package/dist/forja/gitlab/dono-da-rede.js +43 -0
  168. package/dist/forja/gitlab/metadados.js +15 -0
  169. package/dist/forja/gitlab/pedidos-abertos.js +158 -0
  170. package/dist/forja/gitlab/pedidos.js +141 -0
  171. package/dist/forja/produtor.js +32 -0
  172. package/dist/forja/registro.js +31 -0
  173. package/dist/forja/resolver.js +121 -0
  174. package/dist/forja/texto.js +13 -0
  175. package/dist/forja/versao.js +71 -0
  176. package/dist/forja.js +123 -80
  177. package/dist/handoff.js +23 -13
  178. package/dist/hitl-contract.js +46 -43
  179. package/dist/hitl-curto.js +37 -25
  180. package/dist/hitl-gates.js +13 -11
  181. package/dist/hitl-local-receipt.js +51 -0
  182. package/dist/hitl-local.js +11 -2
  183. package/dist/hitl-lock.js +5 -11
  184. package/dist/hitl-public-receipt.js +11 -0
  185. package/dist/hitl-resumo.js +44 -1
  186. package/dist/hitl-sessions.js +62 -9
  187. package/dist/hitl-texto-livre.js +7 -4
  188. package/dist/hitl.js +34 -31
  189. package/dist/horario.js +20 -1
  190. package/dist/hosts.js +104 -134
  191. package/dist/index.js +864 -554
  192. package/dist/indice-vetorial.js +14 -12
  193. package/dist/init.js +11 -2
  194. package/dist/integracoes-locais.js +4 -0
  195. package/dist/intelligence-graph-cli.js +123 -69
  196. package/dist/intelligence-graph-query.js +37 -22
  197. package/dist/leases.js +19 -18
  198. package/dist/ledger-stats.js +17 -16
  199. package/dist/ledger.js +31 -1
  200. package/dist/licoes.js +12 -11
  201. package/dist/locale-accounts.js +228 -0
  202. package/dist/locale-adapter.js +291 -0
  203. package/dist/locale-ajuda.js +86 -10
  204. package/dist/locale-audit.js +199 -0
  205. package/dist/locale-claims.js +213 -0
  206. package/dist/locale-conducao.js +138 -0
  207. package/dist/locale-conhecimento.js +181 -0
  208. package/dist/locale-decisao.js +205 -0
  209. package/dist/locale-docs.js +230 -0
  210. package/dist/locale-estado.js +101 -0
  211. package/dist/locale-forja.js +288 -0
  212. package/dist/locale-gerados.js +5 -0
  213. package/dist/locale-grafo.js +129 -0
  214. package/dist/locale-master.js +83 -0
  215. package/dist/locale-network.js +524 -0
  216. package/dist/locale-operacao-restante.js +931 -0
  217. package/dist/locale-projetos.js +100 -0
  218. package/dist/locale-prompts.js +194 -0
  219. package/dist/locale-pulse.js +140 -2
  220. package/dist/locale-reservas.js +87 -0
  221. package/dist/locale-retry.js +183 -6
  222. package/dist/locale-security-scan.js +69 -0
  223. package/dist/locale-setup.js +192 -0
  224. package/dist/locale-ship.js +177 -66
  225. package/dist/locale-thread.js +149 -5
  226. package/dist/locale-verify.js +80 -4
  227. package/dist/locale-worktree.js +246 -0
  228. package/dist/locale.js +553 -9
  229. package/dist/maestro-runtime.js +22 -3
  230. package/dist/manifest.js +293 -12
  231. package/dist/maquina-ociosa.js +211 -0
  232. package/dist/maquina.js +10 -5
  233. package/dist/master-nota-local.js +200 -0
  234. package/dist/master-nota.js +10 -2
  235. package/dist/master.js +118 -41
  236. package/dist/mcp-conhecimento.js +52 -0
  237. package/dist/mcp-experiencia.js +26 -5
  238. package/dist/mcp-install.js +90 -15
  239. package/dist/mcp-noctum.js +101 -0
  240. package/dist/mcp-server.js +116 -22
  241. package/dist/mcp-ship-transporte.js +977 -0
  242. package/dist/mcp-ship.js +68 -24
  243. package/dist/mcp-transportes.js +17 -0
  244. package/dist/memoria.js +63 -58
  245. package/dist/merge-da-entrega.js +85 -0
  246. package/dist/modos-migracao.js +8 -6
  247. package/dist/modos.js +17 -18
  248. package/dist/network-roadmap.js +94 -54
  249. package/dist/noctum-aceite.js +929 -0
  250. package/dist/noctum-arco.js +540 -33
  251. package/dist/noctum-capacidade.js +3 -3
  252. package/dist/noctum-changelog.js +399 -0
  253. package/dist/noctum-cli.js +539 -0
  254. package/dist/noctum-coordenacao.js +1225 -0
  255. package/dist/noctum-entrega.js +1217 -0
  256. package/dist/noctum-envelope.js +253 -17
  257. package/dist/noctum-execucao.js +100 -24
  258. package/dist/noctum-fechamento.js +416 -0
  259. package/dist/noctum-fila.js +49 -10
  260. package/dist/noctum-guarda.js +333 -0
  261. package/dist/noctum-integracao.js +507 -0
  262. package/dist/noctum-maquinas.js +387 -0
  263. package/dist/noctum-medicao.js +450 -0
  264. package/dist/noctum-modo.js +390 -0
  265. package/dist/noctum-pergunta-canal.js +96 -0
  266. package/dist/noctum-pergunta.js +115 -0
  267. package/dist/noctum-provas.js +838 -0
  268. package/dist/noctum-reconciliacao.js +594 -0
  269. package/dist/noctum-rede.js +102 -0
  270. package/dist/noctum-registro.js +215 -0
  271. package/dist/noctum-relatorio.js +861 -0
  272. package/dist/noctum-resposta.js +207 -0
  273. package/dist/noctum-retomada-rede.js +71 -0
  274. package/dist/noctum-sessoes-em-voo.js +93 -0
  275. package/dist/noctum-supervisao.js +649 -0
  276. package/dist/noctum-terminal.js +459 -0
  277. package/dist/noctum-transporte.js +655 -0
  278. package/dist/noctum-vigia.js +679 -0
  279. package/dist/ocupacao.js +3 -1
  280. package/dist/onboarding.js +16 -5
  281. package/dist/orkmind.js +39 -23
  282. package/dist/orquestracao.js +31 -35
  283. package/dist/parado-no-condutor.js +160 -139
  284. package/dist/phase.js +96 -42
  285. package/dist/policies.js +33 -0
  286. package/dist/postura-local.js +11 -6
  287. package/dist/preflight.js +2 -1
  288. package/dist/procedencia.js +9 -14
  289. package/dist/projeto-alvo.js +59 -20
  290. package/dist/prompts.js +136 -42
  291. package/dist/prova-minima.js +5 -6
  292. package/dist/pulse-cadencia.js +5 -4
  293. package/dist/pulse-consentimento.js +53 -1
  294. package/dist/pulse-delivery.js +143 -27
  295. package/dist/pulse-resposta.js +368 -4
  296. package/dist/pulse.js +136 -3
  297. package/dist/ratelimit.js +13 -4
  298. package/dist/recall.js +14 -13
  299. package/dist/rede-adesao.js +13 -8
  300. package/dist/rede-forja.js +108 -24
  301. package/dist/rede-status.js +63 -45
  302. package/dist/rede.js +86 -46
  303. package/dist/retomada-checkpoint.js +101 -0
  304. package/dist/retry.js +326 -78
  305. package/dist/roadmap-reservas.js +32 -32
  306. package/dist/runtime-ambiente.js +5 -0
  307. package/dist/runtime-context.js +279 -85
  308. package/dist/runtime-escada.js +429 -0
  309. package/dist/runtime-profiles.js +130 -63
  310. package/dist/runtimes.js +42 -5
  311. package/dist/saida-segura.js +3 -2
  312. package/dist/security-scan/agil.js +366 -0
  313. package/dist/security-scan/cli.js +76 -0
  314. package/dist/security-scan/compare.js +149 -0
  315. package/dist/security-scan/config.js +93 -0
  316. package/dist/security-scan/detect.js +97 -0
  317. package/dist/security-scan/executor.js +196 -0
  318. package/dist/security-scan/guard.js +180 -0
  319. package/dist/security-scan/imagem-de-lint.js +96 -0
  320. package/dist/security-scan/normalize/actionlint.js +12 -0
  321. package/dist/security-scan/normalize/eslint.js +12 -0
  322. package/dist/security-scan/normalize/gitleaks.js +13 -0
  323. package/dist/security-scan/normalize/golangci-lint.js +13 -0
  324. package/dist/security-scan/normalize/govulncheck.js +36 -0
  325. package/dist/security-scan/normalize/index.js +25 -0
  326. package/dist/security-scan/normalize/npm-audit.js +30 -0
  327. package/dist/security-scan/normalize/osv-scanner.js +32 -0
  328. package/dist/security-scan/normalize/oxlint.js +51 -0
  329. package/dist/security-scan/normalize/pip-audit.js +16 -0
  330. package/dist/security-scan/normalize/ruff.js +12 -0
  331. package/dist/security-scan/normalize/semgrep.js +20 -0
  332. package/dist/security-scan/normalize/trivy.js +33 -0
  333. package/dist/security-scan/normalize/util.js +204 -0
  334. package/dist/security-scan/normalize/zizmor.js +19 -0
  335. package/dist/security-scan/report.js +156 -0
  336. package/dist/security-scan/run.js +448 -0
  337. package/dist/security-scan/tools.js +62 -0
  338. package/dist/security-scan/trabalhador.js +74 -0
  339. package/dist/security-scan/types.js +15 -0
  340. package/dist/session-events.js +19 -2
  341. package/dist/session-watcher-claude.js +74 -19
  342. package/dist/session-watcher-grok.js +425 -0
  343. package/dist/session-watcher.js +174 -13
  344. package/dist/sessoes-contas.js +42 -5
  345. package/dist/sessoes-inventario.js +1 -1
  346. package/dist/sessoes.js +25 -23
  347. package/dist/setup.js +67 -66
  348. package/dist/ship-publicar.js +263 -92
  349. package/dist/ship.js +160 -17
  350. package/dist/superficie.js +14 -11
  351. package/dist/thread-close.js +9 -9
  352. package/dist/thread.js +121 -18
  353. package/dist/tokens.js +15 -11
  354. package/dist/trabalho-thread.js +297 -0
  355. package/dist/transporte-do-canal.js +189 -0
  356. package/dist/types.js +12 -1
  357. package/dist/util.js +5 -2
  358. package/dist/varredura-acoes.js +500 -0
  359. package/dist/verify.js +491 -32
  360. package/dist/worktree-local.js +12 -10
  361. package/dist/worktree.js +81 -85
  362. package/eval/casos/check-quality.json +77 -0
  363. package/eval/casos/security-auditor.json +148 -0
  364. package/eval/casos/security-remediation.json +161 -0
  365. package/eval/fixtures/security-scan/README.md +8 -0
  366. package/eval/fixtures/security-scan/go/.github/workflows/ci.yml +12 -0
  367. package/eval/fixtures/security-scan/go/Dockerfile +4 -0
  368. package/eval/fixtures/security-scan/go/README.md +4 -0
  369. package/eval/fixtures/security-scan/go/config.go +4 -0
  370. package/eval/fixtures/security-scan/go/go.mod +5 -0
  371. package/eval/fixtures/security-scan/go/go.sum +2 -0
  372. package/eval/fixtures/security-scan/go/main.go +31 -0
  373. package/eval/fixtures/security-scan/js/.github/workflows/ci.yml +12 -0
  374. package/eval/fixtures/security-scan/js/Dockerfile +3 -0
  375. package/eval/fixtures/security-scan/js/README.md +4 -0
  376. package/eval/fixtures/security-scan/js/package-lock.json +844 -0
  377. package/eval/fixtures/security-scan/js/package.json +10 -0
  378. package/eval/fixtures/security-scan/js/src/config.js +4 -0
  379. package/eval/fixtures/security-scan/js/src/server.js +17 -0
  380. package/eval/fixtures/security-scan/js/src/util.ts +4 -0
  381. package/eval/fixtures/security-scan/js/tsconfig.json +4 -0
  382. package/eval/fixtures/security-scan/python/.github/workflows/ci.yml +12 -0
  383. package/eval/fixtures/security-scan/python/Dockerfile +3 -0
  384. package/eval/fixtures/security-scan/python/README.md +4 -0
  385. package/eval/fixtures/security-scan/python/app/__init__.py +0 -0
  386. package/eval/fixtures/security-scan/python/app/config.py +2 -0
  387. package/eval/fixtures/security-scan/python/app/main.py +19 -0
  388. package/eval/fixtures/security-scan/python/requirements.txt +1 -0
  389. package/package.json +10 -5
  390. package/references/security-checklist.md +3 -3
  391. package/skills/README.md +1 -0
  392. package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
  393. package/skills/phases/check-quality/SKILL.md +16 -0
  394. package/skills/phases/go-implementation/SKILL.md +6 -0
  395. package/skills/phases/security-remediation/SKILL.md +70 -0
  396. package/skills/reviewers/security-auditor/SKILL.md +25 -0
@@ -0,0 +1,838 @@
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.ALEGACAO_DE_MUTANTE = exports.RODADAS_POR_SHA = exports.REPROVACOES_QUE_BLOQUEIAM = exports.RecusaDaEntrega = exports.MOTIVOS_DA_ENTREGA = exports.CONTRATO_DAS_PROVAS = void 0;
37
+ exports.equivalente = equivalente;
38
+ exports.contextosDoCi = contextosDoCi;
39
+ exports.avaliarProvas = avaliarProvas;
40
+ exports.gitDaEntrega = gitDaEntrega;
41
+ exports.blobLido = blobLido;
42
+ exports.blobEm = blobEm;
43
+ exports.textoDe = textoDe;
44
+ exports.pontaDaBaseNoRemoto = pontaDaBaseNoRemoto;
45
+ exports.garantirObjetos = garantirObjetos;
46
+ exports.classificarCommit = classificarCommit;
47
+ exports.cadeiaDaFonte = cadeiaDaFonte;
48
+ exports.segredosDaHistoria = segredosDaHistoria;
49
+ exports.lerFatosDaEntrega = lerFatosDaEntrega;
50
+ exports.provasDaEntrega = provasDaEntrega;
51
+ /**
52
+ * RM-058, fatia N5 (D2, D3, D6, D7, D8 e D11 da ork-rm058n5): as provas da entrega no SHA exato.
53
+ *
54
+ * A página da RM-058 diz que uma fatia só chega à `main` com revisão independente, claims executadas, verify contra a
55
+ * baseline, CI obrigatório verde no SHA fonte exato e prova de integração com a base vigente; que segredo e mutante relevante
56
+ * sobrevivente impedem; e que um verde antigo não sobrevive a ajuste de conflito, código, teste ou documentação. Aqui cada uma
57
+ * dessas exigências vira uma prova tipada sobre fatos que o núcleo lê do disco, do git e da forja:
58
+ *
59
+ * - `lerFatosDaEntrega` lê a thread, o ledger, as claims, a fonte (a ref da branch), a base no remoto (`ls-remote`), a cadeia
60
+ * de primeiro pai da fonte com cada commit classificado, o diff de três pontos da base até a fonte e o check do CI;
61
+ * - `avaliarProvas` é pura sobre esses fatos: devolve as provas, o `ok` e o sha256 canônico delas (o `provas` do candidato
62
+ * da N3), com as reprovações anteriores do verify no mesmo SHA preservadas (`falhasRegistradas`).
63
+ *
64
+ * A neutralidade (D3) é por recomputação: um commit que só mexe em `.ork-ci/` é bundle; um merge cujo segundo pai está na base
65
+ * é mecânico quando o `git merge-tree` diff3 dos pais, com a regra de `noctum-changelog.ts`, reproduz a árvore dele byte a
66
+ * byte. O verify sobrevive só a commits de bundle; a revisão, a bundle e a merge mecânico limpo ou só com o CHANGELOG em
67
+ * conflito; o CI, a nada. A revisão independente (D2) é a sessão de CHECK que o núcleo despachou, verificou e viu concluir,
68
+ * com o veredito do parecer e o verify do intervalo, cujo id nativo não é de nenhum autor.
69
+ *
70
+ * Nada aqui grava, despacha nem empurra: quem age é `noctum-entrega.ts`.
71
+ */
72
+ const fs = __importStar(require("node:fs"));
73
+ const path = __importStar(require("node:path"));
74
+ const node_crypto_1 = require("node:crypto");
75
+ const node_child_process_1 = require("node:child_process");
76
+ const ci_1 = require("./ci");
77
+ const claims_1 = require("./claims");
78
+ const ledger_1 = require("./ledger");
79
+ const noctum_changelog_1 = require("./noctum-changelog");
80
+ const noctum_envelope_1 = require("./noctum-envelope");
81
+ const policies_1 = require("./policies");
82
+ const session_watcher_claude_1 = require("./session-watcher-claude");
83
+ const ship_publicar_1 = require("./ship-publicar");
84
+ const thread_1 = require("./thread");
85
+ const types_1 = require("./types");
86
+ const verify_1 = require("./verify");
87
+ const worktree_1 = require("./worktree");
88
+ exports.CONTRATO_DAS_PROVAS = 'ork.noctum-provas/v1';
89
+ /** D2, D3, D4, D6 a D11: as recusas da entrega, num tipo fechado próprio (fora de `MotivoGate` e das recusas do envelope). */
90
+ exports.MOTIVOS_DA_ENTREGA = [
91
+ 'noctum.entrega.fonte-ausente', 'noctum.entrega.base-sem-leitura', 'noctum.entrega.base-mudou',
92
+ 'noctum.entrega.revisao-ausente', 'noctum.entrega.revisao-em-curso', 'noctum.entrega.revisao-sem-vinculo',
93
+ 'noctum.entrega.revisor-autor', 'noctum.entrega.revisao-antiga', 'noctum.entrega.revisao-reprovada',
94
+ 'noctum.entrega.revisao-sem-sha', 'noctum.entrega.revisao-esgotada',
95
+ 'noctum.entrega.verify-ausente', 'noctum.entrega.verify-reprovado', 'noctum.entrega.verify-falha-consistente',
96
+ 'noctum.entrega.verify-esgotado', 'noctum.entrega.verify-sem-comandos', 'noctum.entrega.baseline-ausente',
97
+ 'noctum.entrega.ci-vermelho', 'noctum.entrega.ci-pendente', 'noctum.entrega.ci-sem-leitura', 'noctum.entrega.ci-ausente',
98
+ 'noctum.entrega.segredo', 'noctum.entrega.mutante-sobrevivente', 'noctum.entrega.mutantes-ausentes',
99
+ 'noctum.entrega.mutante-nao-executado', 'noctum.entrega.changelog', 'noctum.entrega.bundle-velho',
100
+ 'noctum.entrega.conflito-nao-aditivo', 'noctum.entrega.worktree-suja', 'noctum.entrega.fora-do-destino',
101
+ 'noctum.entrega.exige-vez-da-n3', 'noctum.entrega.integracao-ocupada', 'noctum.entrega.fonte-mudou',
102
+ 'noctum.entrega.push-incerto', 'noctum.entrega.docs-vermelhos', 'noctum.entrega.nao-provada', 'noctum.entrega.ocupada',
103
+ // GO-FIX da rodada 1 do CHECK: a leitura do git que falhou (lacuna, nunca "nada mudou"), a thread sem produto, o push
104
+ // recusado com a base parada, a thread que não é trabalho do envelope e as esperas do despacho do CHECK, que antes
105
+ // voltavam como texto solto (A8).
106
+ 'noctum.entrega.git-sem-leitura', 'noctum.entrega.sem-produto', 'noctum.entrega.push-recusado', 'noctum.entrega.fora-do-envelope',
107
+ 'noctum.entrega.agendado', 'noctum.entrega.sem-trava-do-kernel', 'noctum.entrega.despacho-em-curso', 'noctum.entrega.pedido-ausente',
108
+ 'noctum.entrega.despacho-recusado', 'noctum.entrega.despacho-incerto',
109
+ // GO-FIX da rodada 2 do CHECK: a supervisão da N4 (o trabalho isolado, o arco parado, o log que não se leu), e o git que
110
+ // falha sem conflito nem recusa do remoto (a trava do índice, a rede do push), que espera a cadência (R2-B1, R2-A2, R2-A3).
111
+ 'noctum.entrega.isolado', 'noctum.entrega.arco-parado', 'noctum.entrega.supervisao-sem-leitura', 'noctum.entrega.git-falhou',
112
+ ];
113
+ class RecusaDaEntrega extends Error {
114
+ motivo;
115
+ constructor(motivo, detalhe) {
116
+ super(`${motivo}: ${detalhe}`);
117
+ this.motivo = motivo;
118
+ this.name = 'RecusaDaEntrega';
119
+ }
120
+ }
121
+ exports.RecusaDaEntrega = RecusaDaEntrega;
122
+ /** D8: duas reprovações do verify no mesmo SHA são falha consistente; três rodadas no SHA sem verde esgotam o passo. */
123
+ exports.REPROVACOES_QUE_BLOQUEIAM = 2;
124
+ exports.RODADAS_POR_SHA = 3;
125
+ /** Os motivos do verify que são reprovação de fato; o prazo estourado e a rodada sem veredito não contam como falha. */
126
+ const MOTIVOS_DE_REPROVACAO = ['claims.failed', 'verify.regression', 'verify.failed'];
127
+ /** D6: a claim de mutante. A alegação começa por "Mutante" (ou "Mutantes"), em qualquer caixa, e ela tem comando. */
128
+ exports.ALEGACAO_DE_MUTANTE = /^\s*mutantes?\b/i;
129
+ /** O máximo de commits da cadeia de primeiro pai que a prova lê: a thread nunca chega perto disso. */
130
+ const MAXIMO_DA_CADEIA = 300;
131
+ /** D3: o que cada prova aceita entre o SHA dela e a fonte. */
132
+ const NEUTROS_DO_VERIFY = ['bundle'];
133
+ const NEUTROS_DA_REVISAO = ['bundle', 'merge-mecanico'];
134
+ const SHA = /^[0-9a-f]{40}$/;
135
+ const curto = (s) => (s ? s.slice(0, 12) : '-');
136
+ const texto = (v) => (typeof v === 'string' && v ? v : null);
137
+ const primeira = (s) => s.trim().split('\n')[0].slice(0, 200);
138
+ /** A leitura do git que falhou: a prova que dependia dela é lacuna, e a entrega espera (nunca "nada mudou"). */
139
+ const LACUNA = 'noctum.entrega.git-sem-leitura';
140
+ // ---------------------------------------------------------------------------
141
+ // A avaliação, pura sobre os fatos.
142
+ // ---------------------------------------------------------------------------
143
+ /** O SHA `x` vale para a fonte se a cadeia de primeiro pai chega nele passando só por commits dos tipos aceitos. */
144
+ function equivalente(cadeia, x, aceitos) {
145
+ if (!x)
146
+ return { ok: false, entre: [] };
147
+ const i = cadeia.findIndex(c => c.sha === x);
148
+ if (i < 0)
149
+ return { ok: false, entre: [] };
150
+ const entre = cadeia.slice(0, i);
151
+ return { ok: entre.every(c => aceitos.includes(c.tipo)), entre };
152
+ }
153
+ const prova = (id, motivo, detalhe, evidencia = {}) => ({ id, ok: motivo === null, motivo, detalhe, evidencia });
154
+ function provaDaIntegracao(f) {
155
+ if (!f.fonte)
156
+ return prova('integracao', 'noctum.entrega.fonte-ausente', `a branch ${f.branch} não existe nesta cópia`);
157
+ if (!f.base.sha) {
158
+ return prova('integracao', 'noctum.entrega.base-sem-leitura', `a base ${f.base.nome} não se leu no remoto: ${f.base.erro ?? 'sem resposta'}`);
159
+ }
160
+ if (f.integrada === null)
161
+ return prova('integracao', 'noctum.entrega.base-sem-leitura', 'o git não respondeu se a base está na fonte');
162
+ if (!f.integrada) {
163
+ return prova('integracao', 'noctum.entrega.base-mudou', `a base ${f.base.nome} está em ${curto(f.base.sha)}, que a fonte ${curto(f.fonte)} não contém: o candidato precisa ser atualizado`, { base: f.base.sha, fonte: f.fonte });
164
+ }
165
+ return prova('integracao', null, `a fonte ${curto(f.fonte)} contém a base ${f.base.nome} em ${curto(f.base.sha)}`, { base: f.base.sha, fonte: f.fonte });
166
+ }
167
+ function provaDoSegredo(f) {
168
+ if (!f.diffLido)
169
+ return prova('segredo', LACUNA, 'a história da base até a fonte não saiu inteira: nada se prova sobre ela');
170
+ // R3-B1 da rodada 3: o merge polvo não tem o remerge; sem segredo achado, a prova não o dá por lido.
171
+ if (!f.segredos.length && f.polvos.length) {
172
+ return prova('segredo', 'noctum.entrega.nao-provada', `merge com mais de dois pais na história da thread (${f.polvos.join(', ')}): ` +
173
+ 'a varredura não lê o que ele acrescenta, e o arco não o entrega', { polvos: f.polvos });
174
+ }
175
+ if (!f.segredos.length) {
176
+ return prova('segredo', null, `nenhum padrão de segredo nas linhas que os ${f.commitsVarridos} commit(s) da thread acrescentam nem ` +
177
+ `nas mensagens deles (${f.arquivos.length} arquivo(s) no diff final)`);
178
+ }
179
+ const lista = f.segredos.slice(0, 10).map(s => `${s.nome} em ${s.arquivo}:${s.linha}${s.commit ? ` (commit ${s.commit})` : ''}`);
180
+ // Só o nome do padrão, o arquivo, a linha e o commit: a prova que imprime o segredo vaza de novo.
181
+ return prova('segredo', 'noctum.entrega.segredo', `segredo na história que a thread empurraria: ${lista.join('; ')}`, { achados: f.segredos.slice(0, 50) });
182
+ }
183
+ function provaDoChangelog(f) {
184
+ if (!f.diffLido)
185
+ return prova('changelog', LACUNA, 'sem o diff, não se sabe se a fonte pede linha no CHANGELOG');
186
+ if (!f.changelog)
187
+ return prova('changelog', LACUNA, 'o CHANGELOG da fonte ou do ancestral comum não se leu');
188
+ if (!f.changelog.ok)
189
+ return prova('changelog', 'noctum.entrega.changelog', `${f.changelog.motivo}: ${f.changelog.detalhe}`);
190
+ return prova('changelog', null, f.changelog.detalhe, { linhas: f.changelog.linhas.slice(0, 10) });
191
+ }
192
+ function provaDoBundle(f) {
193
+ if (f.bundle.lido === false)
194
+ return prova('bundle', LACUNA, f.bundle.texto);
195
+ return f.bundle.ok ? prova('bundle', null, f.bundle.texto) : prova('bundle', 'noctum.entrega.bundle-velho', f.bundle.texto);
196
+ }
197
+ /** A lacuna da cadeia de primeiro pai: sem ela inteira, nada se diz sobre o que veio depois do SHA de uma prova. */
198
+ function lacunaDaCadeia(f) {
199
+ if (!f.cadeiaLida)
200
+ return `a cadeia de primeiro pai da fonte ${curto(f.fonte)} até a base não se leu`;
201
+ const c = f.cadeia.find(x => x.tipo === 'sem-leitura');
202
+ return c ? `o commit ${curto(c.sha)} da cadeia não se classificou: ${c.detalhe}` : null;
203
+ }
204
+ /** Os `verify_run` que valem para a fonte (D3: no SHA, ou num ancestral seguido só de commits de bundle), na ordem do ledger. */
205
+ function verifiesDaFonte(f) {
206
+ return f.verifies.filter(v => equivalente(f.cadeia, v.commit, NEUTROS_DO_VERIFY).ok);
207
+ }
208
+ const reprovado = (v) => !v.ok && v.motivos.some(m => MOTIVOS_DE_REPROVACAO.includes(m));
209
+ function provaDoVerify(f) {
210
+ const lacuna = lacunaDaCadeia(f);
211
+ if (lacuna)
212
+ return { prova: prova('verify', LACUNA, lacuna), falhas: [], ultimo: null, rodadas: 0 };
213
+ const daFonte = verifiesDaFonte(f);
214
+ const ultimo = daFonte.at(-1) ?? null;
215
+ const falhas = daFonte.filter(v => !v.ok).map(v => ({ eventId: v.eventId, commit: v.commit, motivos: v.motivos, ts: v.ts }));
216
+ const evidencia = { rodadas: daFonte.length, ultimo: ultimo?.eventId ?? null, commit: ultimo?.commit ?? null, falhas: falhas.map(x => x.eventId) };
217
+ const fim = (p) => ({ prova: p, falhas, ultimo, rodadas: daFonte.length });
218
+ // A2 da rodada 1: o teto de rodadas por SHA vale para toda prova que pede outro verify, e não só para a reprovada.
219
+ const repetir = (motivo, detalhe, extra = {}) => fim(daFonte.length >= exports.RODADAS_POR_SHA
220
+ ? prova('verify', 'noctum.entrega.verify-esgotado', `${daFonte.length} rodadas do verify no SHA, e a prova ainda pede outra: ${detalhe}`, { ...evidencia, ...extra })
221
+ : prova('verify', motivo, detalhe, { ...evidencia, ...extra }));
222
+ if (!ultimo)
223
+ return fim(prova('verify', 'noctum.entrega.verify-ausente', `nenhum verify na fonte ${curto(f.fonte)}`, evidencia));
224
+ if (!ultimo.comBaseline) {
225
+ return fim(prova('verify', 'noctum.entrega.baseline-ausente', `o verify ${ultimo.eventId} rodou sem baseline: grave a baseline antes do GO (ork verify <thread> --baseline)`, evidencia));
226
+ }
227
+ if (ultimo.ok) {
228
+ if (f.comandosEsperados > 0 && !ultimo.comComandos) {
229
+ return repetir('noctum.entrega.verify-sem-comandos', `o verify ${ultimo.eventId} rodou só as claims; o manifesto pede ${f.comandosEsperados} comando(s)`);
230
+ }
231
+ // Claims executadas: a claim cadastrada depois do verify não rodou nele, e o verify se repete.
232
+ const rodaram = new Set(ultimo.claims.map(c => c.id));
233
+ const semExecucao = f.claims.map(c => c.id).filter(id => !rodaram.has(id));
234
+ if (semExecucao.length) {
235
+ return repetir('noctum.entrega.verify-ausente', `claim(s) ativa(s) que o verify ${ultimo.eventId} não executou: ${semExecucao.slice(0, 10).join(', ')}`, { semExecucao });
236
+ }
237
+ return fim(prova('verify', null, `verify verde em ${curto(ultimo.commit)} (${ultimo.eventId})` +
238
+ (falhas.length ? `, com ${falhas.length} reprovação(ões) anterior(es) no mesmo SHA registrada(s)` : ''), evidencia));
239
+ }
240
+ const reprovacoes = daFonte.filter(reprovado).length;
241
+ if (reprovacoes >= exports.REPROVACOES_QUE_BLOQUEIAM) {
242
+ return fim(prova('verify', 'noctum.entrega.verify-falha-consistente', `${reprovacoes} reprovações do verify no mesmo SHA: falha consistente, que não se repete sozinha`, evidencia));
243
+ }
244
+ return repetir('noctum.entrega.verify-reprovado', `o último verify (${ultimo.eventId}) reprovou: ${ultimo.motivos.join(', ') || 'sem motivo'}; repete isolado na fila pesada`);
245
+ }
246
+ function provaDosMutantes(f, ultimo) {
247
+ if (!f.diffLido)
248
+ return prova('mutantes', LACUNA, 'sem o diff, não se sabe se a fonte muda código');
249
+ const lacuna = lacunaDaCadeia(f);
250
+ if (lacuna)
251
+ return prova('mutantes', LACUNA, `sem a cadeia, não se sabe qual verify vale para a fonte: ${lacuna}`);
252
+ const mutantes = f.claims.filter(c => c.mutante).map(c => c.id);
253
+ const codigo = f.arquivos.filter(noctum_changelog_1.pedeLinha);
254
+ if (!mutantes.length) {
255
+ if (codigo.length) {
256
+ return prova('mutantes', 'noctum.entrega.mutantes-ausentes', `a fonte muda ${codigo.length} arquivo(s) de código (${codigo.slice(0, 3).join(', ')}) e nenhuma claim de mutante prova as guardas`, { codigo: codigo.slice(0, 20) });
257
+ }
258
+ return prova('mutantes', null, 'sem código do produto no diff: o mutante não se aplica', { arquivos: f.arquivos.slice(0, 20) });
259
+ }
260
+ const noVerify = new Map((ultimo?.claims ?? []).map(c => [c.id, c]));
261
+ // Sobrevivente é a claim reprovada; a que estourou o prazo ou ficou sem veredito não rodou até o fim, e roda de novo.
262
+ const sobreviventes = mutantes.filter(id => noVerify.get(id)?.verificado === false && noVerify.get(id)?.motivo === 'claims.failed');
263
+ if (sobreviventes.length) {
264
+ // O verify só guarda a reprovação da claim: o mutante que sobreviveu (saída 1) e a especificação que não se aplica
265
+ // (saída 2) caem juntos aqui, e os dois deixam a guarda sem prova.
266
+ return prova('mutantes', 'noctum.entrega.mutante-sobrevivente', `mutante(s) que não morreram no verify ${ultimo?.eventId ?? '-'} (sobreviveram, ou a especificação não se aplica): ${sobreviventes.join(', ')}`, { sobreviventes });
267
+ }
268
+ const faltam = mutantes.filter(id => noVerify.get(id)?.verificado !== true);
269
+ if (faltam.length) {
270
+ return prova('mutantes', 'noctum.entrega.mutante-nao-executado', `mutante(s) que o verify da fonte não executou: ${faltam.join(', ')}`, { faltam });
271
+ }
272
+ return prova('mutantes', null, `${mutantes.length} mutante(s) mortos no verify ${ultimo?.eventId ?? '-'}`, { mutantes });
273
+ }
274
+ function provaDaRevisao(f) {
275
+ const r = f.revisoes.at(-1);
276
+ if (!r)
277
+ return prova('revisao', 'noctum.entrega.revisao-ausente', 'nenhuma sessão de CHECK despachada pelo núcleo nesta thread');
278
+ const ev = { sessionId: r.sessionId, runtime: r.runtime, despachadaEm: r.despachadaEm, revisado: r.revisado, veredito: r.prova.veredito };
279
+ if (r.semVinculo)
280
+ return prova('revisao', 'noctum.entrega.revisao-sem-vinculo', `a sessão ${r.sessionId.slice(0, 8)} não tem vínculo provado: ${r.semVinculo}`, ev);
281
+ if (f.autores.includes(r.sessionId)) {
282
+ return prova('revisao', 'noctum.entrega.revisor-autor', `a sessão ${r.sessionId.slice(0, 8)} também é autora do candidato: não é revisão independente`, ev);
283
+ }
284
+ if (f.ultimoDespachoDeAutor && !(Date.parse(r.despachadaEm) > Date.parse(f.ultimoDespachoDeAutor))) {
285
+ return prova('revisao', 'noctum.entrega.revisao-antiga', 'o CHECK foi despachado antes do último despacho de quem escreveu o candidato', ev);
286
+ }
287
+ if (r.resultado === 'em-curso')
288
+ return prova('revisao', 'noctum.entrega.revisao-em-curso', `a sessão de CHECK ${r.sessionId.slice(0, 8)} ainda não tem resultado`, ev);
289
+ if (r.prova.veredito && r.prova.veredito !== 'PASSOU') {
290
+ return prova('revisao', 'noctum.entrega.revisao-reprovada', `o CHECK independente concluiu ${r.prova.veredito}`, ev);
291
+ }
292
+ if (r.resultado !== 'concluida') {
293
+ return prova('revisao', 'noctum.entrega.revisao-ausente', `a sessão de CHECK ${r.sessionId.slice(0, 8)} terminou sem concluir (${r.resultado})`, ev);
294
+ }
295
+ if (!r.prova.ok)
296
+ return prova('revisao', 'noctum.entrega.revisao-sem-vinculo', `o CHECK concluiu sem a prova do núcleo: ${r.prova.fonte}`, ev);
297
+ if (r.parecerMudou)
298
+ return prova('revisao', 'noctum.entrega.revisao-antiga', 'o parecer do CHECK mudou depois do resultado que o observador gravou', ev);
299
+ if (!r.revisado)
300
+ return prova('revisao', 'noctum.entrega.revisao-sem-sha', 'o CHECK não rodou verify no intervalo: o SHA revisado é desconhecido', ev);
301
+ const lacuna = lacunaDaCadeia(f);
302
+ if (lacuna)
303
+ return prova('revisao', LACUNA, lacuna, ev);
304
+ const eq = equivalente(f.cadeia, r.revisado, NEUTROS_DA_REVISAO);
305
+ if (!eq.ok) {
306
+ const depois = eq.entre.filter(c => !NEUTROS_DA_REVISAO.includes(c.tipo)).map(c => `${curto(c.sha)} (${c.tipo})`);
307
+ return prova('revisao', 'noctum.entrega.revisao-antiga', depois.length
308
+ ? `depois do SHA revisado ${curto(r.revisado)} vieram commits que não são neutros: ${depois.slice(0, 5).join(', ')}`
309
+ : `o SHA revisado ${curto(r.revisado)} não está na cadeia da fonte ${curto(f.fonte)}`, ev);
310
+ }
311
+ return prova('revisao', null, `CHECK independente ${r.sessionId.slice(0, 8)} (${r.runtime}) com PASSOU em ${curto(r.revisado)}`, ev);
312
+ }
313
+ /** RM-058, integração (D10): os contextos que a prova do CI confere, na ordem: `ci.contexts`, ou `[ci.context]`. */
314
+ function contextosDoCi(manifesto) {
315
+ return manifesto.ci.contexts?.length ? [...manifesto.ci.contexts] : [manifesto.ci.context || 'ork-verify'];
316
+ }
317
+ /** A prova de um contexto do CI no SHA fonte, como sempre foi com um contexto só. */
318
+ function provaDoContexto(f, ci) {
319
+ const ev = { sha: ci.sha, context: ci.context, state: ci.state, url: ci.url };
320
+ if (ci.sha !== f.fonte)
321
+ return prova('ci', 'noctum.entrega.ci-pendente', `o check lido é de outro SHA (${curto(ci.sha)})`, ev);
322
+ switch (ci.state) {
323
+ case 'success': return prova('ci', null, `${ci.context} verde em ${curto(ci.sha)}`, ev);
324
+ case 'failure': return prova('ci', 'noctum.entrega.ci-vermelho', `${ci.context} vermelho em ${curto(ci.sha)}`, ev);
325
+ case 'pending':
326
+ case 'missing': return prova('ci', 'noctum.entrega.ci-pendente', `${ci.context} ainda sem verde em ${curto(ci.sha)} (${ci.state})`, ev);
327
+ case 'disabled': return prova('ci', 'noctum.entrega.ci-ausente', 'o CI não é exigido neste projeto', ev);
328
+ default: return prova('ci', 'noctum.entrega.ci-sem-leitura', `a consulta do CI falhou: ${ci.detail.slice(0, 200)}`, ev);
329
+ }
330
+ }
331
+ /**
332
+ * A ordem em que um contexto decide a prova de vários (D10): o vermelho bloqueia, o CI desligado também, a leitura que
333
+ * falhou e o pendente (ou ausente) esperam; só com todos verdes no SHA fonte a prova vale.
334
+ */
335
+ const ORDEM_DOS_CONTEXTOS = ['noctum.entrega.ci-vermelho', 'noctum.entrega.ci-ausente',
336
+ 'noctum.entrega.ci-sem-leitura', 'noctum.entrega.ci-pendente'];
337
+ function provaDoCi(f) {
338
+ if (!f.ciExigido)
339
+ return prova('ci', 'noctum.entrega.ci-ausente', 'o manifesto não exige CI (ci.required_for_ship): o arco não integra sem CI obrigatório');
340
+ const lidos = f.cis?.length ? f.cis : f.ci ? [f.ci] : [];
341
+ if (!lidos.length)
342
+ return prova('ci', 'noctum.entrega.ci-sem-leitura', 'o check do CI não foi lido');
343
+ if (lidos.length === 1)
344
+ return provaDoContexto(f, lidos[0]);
345
+ const cada = lidos.map(ci => provaDoContexto(f, ci));
346
+ const ev = { sha: f.fonte, contextos: lidos.map(ci => ({ context: ci.context, sha: ci.sha, state: ci.state, url: ci.url })) };
347
+ for (const motivo of ORDEM_DOS_CONTEXTOS) {
348
+ const decide = cada.find(x => x.motivo === motivo);
349
+ if (decide)
350
+ return prova('ci', motivo, decide.detalhe, ev);
351
+ }
352
+ return prova('ci', null, `${lidos.length} contextos verdes em ${curto(f.fonte)}: ${lidos.map(ci => ci.context).join(', ')}`, ev);
353
+ }
354
+ /** As provas da entrega sobre os fatos, sem disco nem rede. */
355
+ function avaliarProvas(f) {
356
+ const verify = provaDoVerify(f);
357
+ const mutantes = provaDosMutantes(f, verify.ultimo);
358
+ // A2 da rodada 1: o mutante sem veredito também pede outro verify, e o teto de rodadas por SHA vale para ele.
359
+ const doVerify = verify.prova.ok && mutantes.motivo === 'noctum.entrega.mutante-nao-executado' && verify.rodadas >= exports.RODADAS_POR_SHA
360
+ ? prova('verify', 'noctum.entrega.verify-esgotado', `${verify.rodadas} rodadas do verify no SHA sem o veredito dos mutantes: ${mutantes.detalhe}`, verify.prova.evidencia)
361
+ : verify.prova;
362
+ const provas = [provaDaIntegracao(f), provaDoSegredo(f), mutantes, provaDoChangelog(f), provaDoBundle(f), provaDaRevisao(f), doVerify, provaDoCi(f)];
363
+ const base = { nome: f.base.nome, sha: f.base.sha };
364
+ const sha256 = (0, node_crypto_1.createHash)('sha256').update((0, noctum_envelope_1.canonico)({ contrato: exports.CONTRATO_DAS_PROVAS, thread: f.thread, branch: f.branch, fonte: f.fonte,
365
+ base: base.sha, provas: provas.map(p => ({ id: p.id, ok: p.ok, motivo: p.motivo, evidencia: p.evidencia })) })).digest('hex');
366
+ return { contrato: exports.CONTRATO_DAS_PROVAS, thread: f.thread, branch: f.branch, fonte: f.fonte, base, ok: provas.every(p => p.ok), provas,
367
+ falhasRegistradas: verify.falhas, sha256 };
368
+ }
369
+ // ---------------------------------------------------------------------------
370
+ // A leitura dos fatos.
371
+ // ---------------------------------------------------------------------------
372
+ /**
373
+ * Git local sem rede escondida nem pergunta de credencial, com buffer de 256 MiB (B1 da rodada 1 do CHECK): o diff de uma
374
+ * fatia grande passa de 1 MiB, o teto do `spawnSync`, e a saída cortada esconderia o resto. `ok` só com saída 0 e sem erro.
375
+ */
376
+ function gitDaEntrega(raiz, args, prazoMs = 120000) {
377
+ const r = (0, node_child_process_1.spawnSync)('git', args, { cwd: raiz, encoding: 'utf8', timeout: prazoMs, maxBuffer: 256 * 1024 * 1024,
378
+ env: { ...process.env, GIT_NO_LAZY_FETCH: '1', GIT_TERMINAL_PROMPT: '0' } });
379
+ return { ok: r.status === 0 && !r.error, code: r.status ?? -1, stdout: r.stdout ?? '', stderr: r.stderr || (r.error ? r.error.message : '') };
380
+ }
381
+ const git = gitDaEntrega;
382
+ /** O mesmo git, com a saída em bytes: o blob como está no repositório, sem decodificar. */
383
+ function gitEmBytes(raiz, args, prazoMs = 120000) {
384
+ const r = (0, node_child_process_1.spawnSync)('git', args, { cwd: raiz, timeout: prazoMs, maxBuffer: 256 * 1024 * 1024,
385
+ env: { ...process.env, GIT_NO_LAZY_FETCH: '1', GIT_TERMINAL_PROMPT: '0' } });
386
+ return { ok: r.status === 0 && !r.error, stdout: r.stdout ?? Buffer.alloc(0), stderr: r.stderr?.toString('utf8') || (r.error ? r.error.message : '') };
387
+ }
388
+ /**
389
+ * Um arquivo numa árvore ou commit, em bytes: `null` quando ele não existe nela (ou não é arquivo), e a lacuna quando o git
390
+ * não respondeu. O `ls-tree` separa "não existe" de "não se leu", que o `cat-file` sozinho confunde.
391
+ */
392
+ function blobLido(raiz, arvore, arquivo) {
393
+ const ls = git(raiz, ['ls-tree', '-z', '--full-tree', arvore, '--', arquivo]);
394
+ if (!ls.ok)
395
+ return { ok: false, erro: `o git ls-tree de ${arquivo} em ${curto(arvore)} falhou: ${primeira(ls.stderr) || `código ${ls.code}`}` };
396
+ const m = /^\d+ blob ([0-9a-f]{40,64})\t/.exec(ls.stdout);
397
+ if (!m)
398
+ return { ok: true, bytes: null };
399
+ const c = gitEmBytes(raiz, ['cat-file', 'blob', m[1]]);
400
+ return c.ok ? { ok: true, bytes: c.stdout } : { ok: false, erro: `o git cat-file de ${arquivo} em ${curto(arvore)} falhou: ${primeira(c.stderr)}` };
401
+ }
402
+ /** O texto de um arquivo numa árvore ou commit, ou `null` quando ele não existe nela; a leitura que falha é lacuna tipada. */
403
+ function blobEm(raiz, arvore, arquivo) {
404
+ const b = blobLido(raiz, arvore, arquivo);
405
+ if (!b.ok)
406
+ throw new RecusaDaEntrega(LACUNA, b.erro);
407
+ return b.bytes === null ? null : b.bytes.toString('utf8');
408
+ }
409
+ /**
410
+ * O texto que a regra do núcleo pode reescrever: UTF-8 que volta aos mesmos bytes e sem byte nulo (B3 da rodada 1). Arquivo
411
+ * binário ou noutra codificação devolve `null`, e o conflito nele não é só aditivo.
412
+ */
413
+ function textoDe(bytes) {
414
+ if (bytes.includes(0))
415
+ return null;
416
+ const t = bytes.toString('utf8');
417
+ return Buffer.from(t, 'utf8').equals(bytes) ? t : null;
418
+ }
419
+ /** A ponta da base no remoto pelo `ls-remote`, só a linha da ref exata. */
420
+ function pontaDaBaseNoRemoto(raiz, remoto, base) {
421
+ const r = git(raiz, ['ls-remote', '--', remoto, `refs/heads/${base}`], 60000);
422
+ if (!r.ok)
423
+ return { sha: null, erro: (r.stderr || r.stdout || `código ${r.code}`).trim().split('\n')[0].slice(0, 200) };
424
+ const linha = r.stdout.split('\n').map(l => l.trim().split(/\s+/)).find(p => p[1] === `refs/heads/${base}`);
425
+ return linha && SHA.test(linha[0]) ? { sha: linha[0], erro: null } : { sha: null, erro: `a branch ${base} não existe no remoto ${remoto}` };
426
+ }
427
+ /** Garante os objetos da base nesta cópia (busca a branch do remoto quando falta o commit). */
428
+ function garantirObjetos(raiz, remoto, base, sha) {
429
+ if (git(raiz, ['cat-file', '-e', `${sha}^{commit}`]).ok)
430
+ return true;
431
+ git(raiz, ['fetch', '--quiet', '--', remoto, `+refs/heads/${base}:refs/remotes/${remoto}/${base}`], 180000);
432
+ return git(raiz, ['cat-file', '-e', `${sha}^{commit}`]).ok;
433
+ }
434
+ /**
435
+ * D3: o que um commit da cadeia de primeiro pai é. Com um pai: bundle (só `.ork-ci/`) ou produto. Com dois: o segundo pai fora
436
+ * da base é merge de fora; dentro, o `git merge-tree` diff3 dos pais com a regra do núcleo (união só aditiva e a entrada da
437
+ * thread em "Não publicado") precisa reproduzir a árvore do merge byte a byte: mecânico quando só o CHANGELOG conflitou,
438
+ * de código quando outro arquivo conflitou, e com ajuste quando a árvore não é a recomputada.
439
+ */
440
+ function classificarCommit(raiz, sha, base) {
441
+ // A leitura que falha é lacuna (`sem-leitura`), e não um tipo: nem "nada diferente", nem uma revisão nova pedida à toa.
442
+ const semLeitura = (detalhe, conflitos = []) => ({ sha, tipo: 'sem-leitura', conflitos, detalhe });
443
+ const lidos = git(raiz, ['rev-list', '--parents', '-n', '1', sha]);
444
+ if (!lidos.ok)
445
+ return semLeitura('os pais do commit não se leram');
446
+ const pais = lidos.stdout.trim().split(/\s+/).slice(1);
447
+ if (pais.length <= 1) {
448
+ const mudou = git(raiz, ['diff-tree', '--no-commit-id', '--name-only', '-z', '-r', '--root', sha]);
449
+ if (!mudou.ok)
450
+ return semLeitura('os arquivos do commit não se leram');
451
+ const arquivos = mudou.stdout.split('\0').filter(Boolean);
452
+ return arquivos.every(a => a.startsWith('.ork-ci/'))
453
+ ? { sha, tipo: 'bundle', conflitos: [], detalhe: 'só o bundle de CI' }
454
+ : { sha, tipo: 'produto', conflitos: [], detalhe: `${arquivos.length} arquivo(s)` };
455
+ }
456
+ if (pais.length > 2)
457
+ return { sha, tipo: 'merge-com-ajuste', conflitos: [], detalhe: `merge com ${pais.length} pais` };
458
+ const [p1, p2] = pais;
459
+ const naBase = git(raiz, ['merge-base', '--is-ancestor', p2, base]);
460
+ if (naBase.code === 1)
461
+ return { sha, tipo: 'merge-de-fora', conflitos: [], detalhe: `o segundo pai ${curto(p2)} não está na base` };
462
+ if (naBase.code !== 0)
463
+ return semLeitura('não se leu se o segundo pai está na base');
464
+ const mt = git(raiz, ['-c', 'merge.conflictStyle=diff3', 'merge-tree', '--write-tree', p1, p2], 120000);
465
+ if (mt.code !== 0 && mt.code !== 1)
466
+ return semLeitura('o git merge-tree --write-tree não respondeu (pede git 2.38 ou mais novo)');
467
+ const [arvore, ...resto] = mt.stdout.split('\n');
468
+ const estagios = new Map();
469
+ for (const l of resto) {
470
+ if (!l.trim())
471
+ break;
472
+ const m = /^\d+ [0-9a-f]+ ([123])\t(.+)$/.exec(l);
473
+ if (!m || m[2].startsWith('"'))
474
+ return { sha, tipo: 'merge-com-ajuste', conflitos: [], detalhe: 'conflito fora do formato do merge-tree' };
475
+ estagios.set(m[2], (estagios.get(m[2]) ?? new Set()).add(m[1]));
476
+ }
477
+ const conflitos = [...estagios.keys()];
478
+ // Só o conflito de conteúdo (ancestral, thread e base, os três estágios) tem a regra do núcleo; o resto é escolha de alguém.
479
+ const semConteudo = conflitos.find(a => estagios.get(a).size !== 3);
480
+ if (semConteudo)
481
+ return { sha, tipo: 'merge-com-ajuste', conflitos, detalhe: `${semConteudo}: conflito que não é de conteúdo` };
482
+ const ancestral = git(raiz, ['merge-base', p1, p2]);
483
+ if (!ancestral.ok)
484
+ return semLeitura('o ancestral comum dos pais não se leu', conflitos);
485
+ const changelogs = [ancestral.stdout.trim(), p1, p2].map(rev => blobLido(raiz, rev, noctum_changelog_1.ARQUIVO_DO_CHANGELOG));
486
+ const falhou = changelogs.find(b => !b.ok);
487
+ if (falhou && !falhou.ok)
488
+ return semLeitura(falhou.erro, conflitos);
489
+ const [noAncestral, naThread, naBaseDoMerge] = changelogs.map(b => (b.ok && b.bytes ? b.bytes.toString('utf8') : ''));
490
+ const entradas = (0, noctum_changelog_1.entradasDaThread)(noAncestral, naThread);
491
+ // S1 da rodada 1: a comparação que falha nunca vira "nada diferente".
492
+ const diff = git(raiz, ['diff-tree', '-r', '-z', '--name-only', arvore.trim(), `${sha}^{tree}`]);
493
+ if (!diff.ok)
494
+ return semLeitura('a árvore do merge não se comparou com a recomputada', conflitos);
495
+ const diferentes = diff.stdout.split('\0').filter(Boolean);
496
+ for (const arquivo of new Set([...conflitos, ...diferentes])) {
497
+ if (!conflitos.includes(arquivo) && arquivo !== noctum_changelog_1.ARQUIVO_DO_CHANGELOG) {
498
+ return { sha, tipo: 'merge-com-ajuste', conflitos, detalhe: `${arquivo} difere do merge recomputado sem ter conflitado` };
499
+ }
500
+ const comMarcas = blobLido(raiz, arvore.trim(), arquivo);
501
+ if (!comMarcas.ok)
502
+ return semLeitura(comMarcas.erro, conflitos);
503
+ if (comMarcas.bytes === null)
504
+ return { sha, tipo: 'merge-com-ajuste', conflitos, detalhe: `${arquivo} some no merge recomputado` };
505
+ // B3 da rodada 1: binário ou fora de UTF-8 não tem a regra do núcleo; o texto decodificado esconderia a troca de bytes.
506
+ const textoComMarcas = textoDe(comMarcas.bytes);
507
+ if (textoComMarcas === null)
508
+ return { sha, tipo: 'merge-com-ajuste', conflitos, detalhe: `${arquivo}: não é texto UTF-8 (binário)` };
509
+ const resolvido = (0, noctum_changelog_1.resolverConflitosAditivos)(textoComMarcas);
510
+ if (!resolvido.ok)
511
+ return { sha, tipo: 'merge-com-ajuste', conflitos, detalhe: `${arquivo}: ${resolvido.recusa}` };
512
+ // O conflito sem marcador é de arquivo que o git não mesclou como texto (deixou um dos lados inteiro).
513
+ if (conflitos.includes(arquivo) && resolvido.conflitos === 0) {
514
+ return { sha, tipo: 'merge-com-ajuste', conflitos, detalhe: `${arquivo}: conflito sem marcador de texto` };
515
+ }
516
+ const esperado = arquivo === noctum_changelog_1.ARQUIVO_DO_CHANGELOG ? (0, noctum_changelog_1.posicionarEntradas)(resolvido.texto, entradas, naBaseDoMerge).texto : resolvido.texto;
517
+ const noMerge = blobLido(raiz, sha, arquivo);
518
+ if (!noMerge.ok)
519
+ return semLeitura(noMerge.erro, conflitos);
520
+ // Byte a byte: a árvore do merge precisa ser a que a regra produz.
521
+ if (noMerge.bytes === null || !noMerge.bytes.equals(Buffer.from(esperado, 'utf8'))) {
522
+ return { sha, tipo: 'merge-com-ajuste', conflitos, detalhe: `${arquivo} não é o que a regra do núcleo produz` };
523
+ }
524
+ }
525
+ const deCodigo = conflitos.filter(a => a !== noctum_changelog_1.ARQUIVO_DO_CHANGELOG);
526
+ return deCodigo.length
527
+ ? { sha, tipo: 'merge-de-codigo', conflitos, detalhe: `conflito de código resolvido pela regra: ${deCodigo.slice(0, 3).join(', ')}` }
528
+ : { sha, tipo: 'merge-mecanico', conflitos, detalhe: conflitos.length ? 'só o CHANGELOG conflitou' : 'merge limpo da base' };
529
+ }
530
+ /**
531
+ * A9 da rodada 1: a classificação de um commit sobre uma base não muda (o commit, os pais, os blobs e o merge recomputado
532
+ * são imutáveis), então cada passo não recomputa a cadeia inteira. A lacuna (`sem-leitura`) nunca fica guardada.
533
+ */
534
+ const classificados = new Map();
535
+ const LIMITE_DA_MEMORIA = 2000;
536
+ function classificarComMemoria(raiz, sha, base) {
537
+ const chave = `${raiz}\0${sha}\0${base}`;
538
+ const guardado = classificados.get(chave);
539
+ if (guardado)
540
+ return { ...guardado, conflitos: [...guardado.conflitos] };
541
+ const c = classificarCommit(raiz, sha, base);
542
+ if (c.tipo !== 'sem-leitura') {
543
+ if (classificados.size >= LIMITE_DA_MEMORIA)
544
+ classificados.clear();
545
+ classificados.set(chave, { ...c, conflitos: [...c.conflitos] });
546
+ }
547
+ return c;
548
+ }
549
+ /**
550
+ * A cadeia de primeiro pai da fonte até a base (exclusive), cada commit classificado; a mais nova primeiro. `null` quando o
551
+ * git não a listou: a lacuna não vira cadeia vazia.
552
+ */
553
+ function cadeiaDaFonte(raiz, fonte, base) {
554
+ const r = git(raiz, ['rev-list', '--first-parent', `--max-count=${MAXIMO_DA_CADEIA}`, fonte, '--not', base]);
555
+ if (!r.ok)
556
+ return null;
557
+ return r.stdout.split('\n').map(l => l.trim()).filter(l => SHA.test(l)).map(sha => classificarComMemoria(raiz, sha, base));
558
+ }
559
+ /**
560
+ * As linhas acrescentadas de uma saída de `git diff` ou `git log -p` com `--unified=0`, com o arquivo, o número da linha
561
+ * nova e os padrões de segredo de cada uma. No `git log`, a linha do formato (`\0<sha> <pais>`) abre cada commit; devolve
562
+ * os merges com mais de dois pais, que o git não refaz pelo `--diff-merges=remerge`.
563
+ */
564
+ function segredosDaSaida(saida, achar) {
565
+ const polvos = [];
566
+ let commit = '', pais = [], arquivo = '', nova = 0, cabecalho = false;
567
+ for (const l of saida.split('\n')) {
568
+ // A linha do formato abre cada commit; dentro do diff, toda linha começa por espaço, "+", "-" ou "\".
569
+ if (l.startsWith('\0')) {
570
+ const [sha, ...resto] = l.slice(1).split(' ');
571
+ commit = sha.slice(0, 12);
572
+ pais = resto.filter(Boolean);
573
+ if (pais.length > 2)
574
+ polvos.push(commit);
575
+ cabecalho = false;
576
+ arquivo = '';
577
+ continue;
578
+ }
579
+ if (l.startsWith('diff --git ')) {
580
+ cabecalho = true;
581
+ arquivo = '';
582
+ continue;
583
+ }
584
+ // O `+++` só é o nome do arquivo no cabeçalho; dentro do hunk, é uma linha acrescentada que começa por "++".
585
+ if (cabecalho && l.startsWith('+++ ')) {
586
+ arquivo = l.slice(4).replace(/^b\//, '');
587
+ continue;
588
+ }
589
+ const h = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(l);
590
+ if (h) {
591
+ nova = Number(h[1]);
592
+ cabecalho = false;
593
+ continue;
594
+ }
595
+ if (!cabecalho && l.startsWith('+') && arquivo) {
596
+ for (const a of (0, policies_1.procurarSegredos)(l.slice(1)))
597
+ achar({ nome: a.nome, arquivo, linha: nova, texto: l.slice(1), commit, pais });
598
+ nova += 1;
599
+ }
600
+ }
601
+ return polvos;
602
+ }
603
+ /**
604
+ * R2-B2 da rodada 2: os padrões de segredo na história que a thread empurraria. O push da branch e o merge levam todos os
605
+ * commits de `base..fonte`, e a chave posta num commit e tirada no seguinte some do diff final, mas não do remoto. Por isso
606
+ * a varredura lê as linhas que cada commit acrescenta (no merge, só as que a resolução trouxe: `--diff-merges=remerge`) e
607
+ * as mensagens dos commits. O arquivo que o git acha binário entra como texto (`--text`, A6 da rodada 1): um byte nulo não
608
+ * esconde a chave. `lido` é falso quando a história não saiu inteira, e então a prova não diz "nenhum segredo".
609
+ *
610
+ * R3-B1 da rodada 3: o diff de três pontos entra também, em união com a história, porque o git não refaz o merge polvo
611
+ * (mais de dois pais), e o que a árvore final da thread acrescenta está sempre nele; o merge polvo volta em `polvos`, e a
612
+ * prova não o dá por lido. R3-A2: no merge, a linha que já está no arquivo de um dos pais não é da thread (o remerge mostra
613
+ * assim a linha da base que a regra do núcleo pôs antes da thread).
614
+ */
615
+ function segredosDaHistoria(raiz, base, fonte) {
616
+ const r = git(raiz, ['-c', 'core.quotePath=false', 'log', '--format=%x00%H %P', '-p', '--text', '--no-color', '--no-ext-diff', '--unified=0',
617
+ '--no-renames', '--diff-merges=remerge', `${base}..${fonte}`], 300000);
618
+ const final = git(raiz, ['-c', 'core.quotePath=false', 'diff', '--text', '--no-color', '--no-ext-diff', '--unified=0', '--no-renames',
619
+ `${base}...${fonte}`], 300000);
620
+ const mensagens = git(raiz, ['log', '--format=%x00%H%n%B', `${base}..${fonte}`]);
621
+ const achados = [];
622
+ if (!r.ok || !final.ok || !mensagens.ok)
623
+ return { lido: false, commits: 0, polvos: [], achados };
624
+ const doPai = new Map();
625
+ const linhasDoPai = (pai, arquivo) => {
626
+ const chave = `${pai}:${arquivo}`;
627
+ if (!doPai.has(chave))
628
+ doPai.set(chave, blobEm(raiz, pai, arquivo)?.split('\n') ?? null);
629
+ return doPai.get(chave);
630
+ };
631
+ const vistos = new Set();
632
+ const guardar = (s) => {
633
+ if (s.pais.length === 2 && s.pais.some(pai => linhasDoPai(pai, s.arquivo)?.includes(s.texto)))
634
+ return;
635
+ const chave = `${s.nome}\0${s.arquivo}\0${s.texto}`;
636
+ if (vistos.has(chave))
637
+ return;
638
+ vistos.add(chave);
639
+ achados.push({ nome: s.nome, arquivo: s.arquivo, linha: s.linha, ...(s.commit ? { commit: s.commit } : {}) });
640
+ };
641
+ const polvos = segredosDaSaida(r.stdout, guardar);
642
+ segredosDaSaida(final.stdout, guardar);
643
+ const blocos = mensagens.stdout.split('\0').filter(Boolean);
644
+ for (const bloco of blocos) {
645
+ const [sha, ...linhas] = bloco.split('\n');
646
+ linhas.forEach((linha, i) => {
647
+ for (const a of (0, policies_1.procurarSegredos)(linha))
648
+ achados.push({ nome: a.nome, arquivo: 'mensagem do commit', linha: i + 1, commit: sha.slice(0, 12) });
649
+ });
650
+ }
651
+ return { lido: true, commits: blocos.length, polvos, achados };
652
+ }
653
+ /** Os autores do candidato: as sessões de GO, as que começaram antes do GO num bloco com GO, e as que commitaram. */
654
+ function autoresDaThread(t, eventos) {
655
+ const autores = new Set();
656
+ let ultimo = null;
657
+ const marcar = (id, em) => {
658
+ autores.add(id);
659
+ if (em && (!ultimo || Date.parse(em) > Date.parse(ultimo)))
660
+ ultimo = em;
661
+ };
662
+ for (const s of t.sessoes ?? []) {
663
+ const fases = (s.bloco ?? '').split('-');
664
+ const antesDoGo = fases.includes('GO') && types_1.FASES.indexOf(s.fase) <= types_1.FASES.indexOf('GO');
665
+ if (s.fase === 'GO' || antesDoGo)
666
+ marcar(s.sessionId, s.despachadaEm);
667
+ }
668
+ for (const e of eventos) {
669
+ if ((e.tipo === 'mcp_git_committed' || e.tipo === 'commit') && typeof e.sessionId === 'string' && e.sessionId) {
670
+ const s = (t.sessoes ?? []).find(x => x.sessionId === e.sessionId);
671
+ marcar(e.sessionId, s?.despachadaEm);
672
+ }
673
+ }
674
+ return { autores: [...autores], ultimoDespacho: ultimo };
675
+ }
676
+ /** O vínculo da sessão de CHECK provado pelo núcleo, como o Maestro confere (`maestro-authority.ts`), ou o que faltou. */
677
+ function vinculoDaSessao(raiz, t, s, eventos) {
678
+ if (s.origem === 'adocao' || !s.despachadaEm || !s.promptPath || !s.promptSha256)
679
+ return 'sessão adotada ou sem despacho registrado';
680
+ if (!s.verificada)
681
+ return 'o runtime não confirmou a sessão depois do despacho';
682
+ const despacho = eventos.filter(e => e.tipo === ledger_1.TIPOS_DE_EVENTO.faseDespachada && e.sessionId === s.sessionId).at(-1);
683
+ if (!despacho || typeof despacho.eventId !== 'string' || despacho.fase !== 'CHECK' || despacho.runtime !== s.runtime ||
684
+ despacho.promptSha256 !== s.promptSha256)
685
+ return 'sem o phase_dispatch do CHECK desta sessão';
686
+ const verificada = eventos.find(e => e.tipo === ledger_1.TIPOS_DE_EVENTO.despachoVerificado && e.sessionId === s.sessionId && e.encontrada === true &&
687
+ typeof e.eventId === 'string');
688
+ if (!verificada)
689
+ return 'sem o phase_dispatch_verified desta sessão';
690
+ try {
691
+ const escopo = fs.realpathSync((0, thread_1.dirThread)(raiz, t.id)) + path.sep;
692
+ const prompt = fs.realpathSync(path.resolve(raiz, s.promptPath));
693
+ if (!prompt.startsWith(escopo))
694
+ return 'o prompt do despacho fica fora da pasta da thread';
695
+ if ((0, node_crypto_1.createHash)('sha256').update(fs.readFileSync(prompt)).digest('hex') !== s.promptSha256)
696
+ return 'o prompt mudou depois do despacho';
697
+ }
698
+ catch {
699
+ return 'o prompt do despacho não se lê';
700
+ }
701
+ return null;
702
+ }
703
+ /** As sessões de CHECK da thread, da mais velha para a mais nova, com o que o núcleo prova de cada uma. */
704
+ function revisoesDaThread(raiz, t, eventos) {
705
+ const posicao = new Map(eventos.map((e, i) => [e, i]));
706
+ const checks = (t.sessoes ?? []).filter(s => s.fase === 'CHECK' && s.origem !== 'adocao' && !!s.despachadaEm)
707
+ .sort((a, b) => Date.parse(a.despachadaEm) - Date.parse(b.despachadaEm));
708
+ return checks.map(s => {
709
+ const sessao = { sessionId: s.sessionId, fase: 'CHECK', despachadaEm: s.despachadaEm };
710
+ const resultado = eventos.filter(e => e.tipo === 'phase_result' && e.sessionId === s.sessionId && e.fase === 'CHECK' &&
711
+ e.despachoEm === s.despachadaEm && e.origem === 'sessions.watch' && typeof e.sensorResultId === 'string' && e.sensorResultId).at(-1);
712
+ const morta = eventos.some(e => e.tipo === 'sessao_morta' && e.sessionId === s.sessionId);
713
+ const estado = resultado
714
+ ? (resultado.classificacao === 'fase_concluida' && resultado.ok === true ? 'concluida' : 'bloqueada') : morta ? 'morta' : 'em-curso';
715
+ const intervalo = (0, session_watcher_claude_1.eventosDoIntervalo)(eventos, sessao);
716
+ // O SHA revisado e o veredito são os do fim do turno do revisor: a prova que o observador gravou no resultado (o claude-bg
717
+ // grava `provaOrk` e o sha do parecer) ou, sem ela, o parecer de agora e o último verify até o resultado. Um verify que
718
+ // outro roda depois (o do próprio arco, por exemplo) nunca vira o SHA da revisão.
719
+ const gravada = resultado?.provaOrk;
720
+ let prova;
721
+ let revisado;
722
+ if (gravada && typeof gravada.ok === 'boolean') {
723
+ prova = { ok: gravada.ok, fonte: texto(gravada.fonte) ?? 'a prova do núcleo gravada no resultado do CHECK', veredito: texto(gravada.veredito) };
724
+ revisado = texto(gravada.verify?.commit);
725
+ }
726
+ else {
727
+ const p = (0, session_watcher_claude_1.provaDoOrk)(raiz, t.id, sessao, eventos, (0, session_watcher_claude_1.fonteRegistrada)(eventos, sessao));
728
+ prova = { ok: p.ok, fonte: p.fonte, veredito: p.veredito ?? null };
729
+ // Pela posição no ledger, e não pela hora: dois eventos no mesmo milissegundo não trocam de lado.
730
+ const ate = resultado ? posicao.get(resultado) ?? eventos.length : eventos.length;
731
+ revisado = texto(intervalo.filter(e => e.tipo === ledger_1.TIPOS_DE_EVENTO.verificacao && (posicao.get(e) ?? eventos.length) < ate).at(-1)?.commit);
732
+ }
733
+ // O parecer do resultado: o sha que o observador viu; sem ele (o observador do codex não grava), a data do arquivo, que
734
+ // não pode ser depois do resultado (A5 da rodada 1). O `ts` do ledger é de milissegundo; a data do arquivo, mais fina.
735
+ const viu = texto(resultado?.artefato?.sha256Atual);
736
+ const parecer = (0, session_watcher_claude_1.estadoDoArtefato)(raiz, t.id, 'docs/check.md');
737
+ const parecerMudou = viu ? parecer.sha256 !== viu
738
+ : !!resultado && parecer.mtimeMs !== null && Math.floor(parecer.mtimeMs) > Date.parse(resultado.ts);
739
+ return { sessionId: s.sessionId, runtime: s.runtime, despachadaEm: s.despachadaEm, semVinculo: vinculoDaSessao(raiz, t, s, eventos),
740
+ resultado: estado, prova, revisado: revisado && SHA.test(revisado) ? revisado : null, parecerMudou };
741
+ });
742
+ }
743
+ function verifiesDoLedger(eventos) {
744
+ return eventos.filter(e => e.tipo === ledger_1.TIPOS_DE_EVENTO.verificacao && typeof e.commit === 'string' && SHA.test(e.commit)).map(e => ({
745
+ eventId: texto(e.eventId) ?? '-', ts: e.ts, commit: e.commit, ok: e.veredito === 'verdade sustentada',
746
+ motivos: Array.isArray(e.motivos) ? e.motivos.map(String) : [],
747
+ comBaseline: !!e.baseline && typeof e.baseline === 'object',
748
+ comComandos: Array.isArray(e.comandos) && e.comandos.length > 0,
749
+ claims: Array.isArray(e.claims) ? e.claims.filter(c => typeof c?.id === 'string')
750
+ .map(c => ({ id: c.id, verificado: c.verificado === true, motivo: texto(c.motivo) })) : [],
751
+ }));
752
+ }
753
+ /** Os fatos da entrega de uma thread, lidos agora do disco, do git e da forja. Não grava nada. */
754
+ function lerFatosDaEntrega(carregado, threadId, opcoes = {}) {
755
+ const { raiz, manifesto } = carregado;
756
+ const t = (0, thread_1.lerThread)(raiz, threadId);
757
+ const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, threadId));
758
+ const branch = (0, worktree_1.branchDaWorktree)(t);
759
+ const remoto = opcoes.remoto ?? 'origin';
760
+ const nomeDaBase = opcoes.base ?? manifesto.worktree.base_branch;
761
+ const ref = git(raiz, ['rev-parse', '--verify', '--quiet', `refs/heads/${branch}^{commit}`]);
762
+ const fonte = ref.ok && SHA.test(ref.stdout.trim()) ? ref.stdout.trim() : null;
763
+ const lida = opcoes.pontaDaBase ?? pontaDaBaseNoRemoto(raiz, remoto, nomeDaBase);
764
+ const base = { nome: nomeDaBase, sha: lida.sha, erro: lida.erro };
765
+ if (base.sha && !garantirObjetos(raiz, remoto, nomeDaBase, base.sha)) {
766
+ base.erro = `o commit ${curto(base.sha)} da base não chegou a esta cópia`;
767
+ base.sha = null;
768
+ }
769
+ const { autores, ultimoDespacho } = autoresDaThread(t, eventos);
770
+ const claimsAtivas = (() => { try {
771
+ return (0, claims_1.lerClaims)(raiz, threadId).filter(c => c.estado !== 'retirada');
772
+ }
773
+ catch {
774
+ return [];
775
+ } })();
776
+ const daBranch = (0, ship_publicar_1.manifestoDaBranch)(carregado, fonte);
777
+ const fatos = {
778
+ thread: t.id, branch, fonte, base, integrada: null, cadeia: [], cadeiaLida: false, autores, ultimoDespachoDeAutor: ultimoDespacho,
779
+ revisoes: revisoesDaThread(raiz, t, eventos), verifies: verifiesDoLedger(eventos),
780
+ comandosEsperados: (0, verify_1.comandosDoManifesto)(daBranch.manifesto).length,
781
+ // A2 da rodada 1: a claim de mutante sem comando não executa nada e nunca prova a guarda; ela não conta como mutante.
782
+ claims: claimsAtivas.map(c => ({ id: c.id, mutante: exports.ALEGACAO_DE_MUTANTE.test(c.alegacao) && c.verificar.length > 0 })),
783
+ ciExigido: manifesto.ci.required_for_ship === true, ci: null, arquivos: [], diffLido: false, segredos: [], commitsVarridos: 0, polvos: [], changelog: null,
784
+ bundle: { ok: false, texto: 'a fonte não se lê' },
785
+ };
786
+ if (!fonte)
787
+ return fatos;
788
+ // O bundle que existe e não se leu é lacuna; o `conferirBundle` sozinho o daria por ausente, e a entrega o regravaria.
789
+ const arquivoDoBundle = `.ork-ci/${t.id}.json`;
790
+ const doBundle = blobLido(raiz, fonte, arquivoDoBundle);
791
+ if (!doBundle.ok)
792
+ fatos.bundle = { ok: false, lido: false, texto: `o bundle ${arquivoDoBundle} da fonte não se leu: ${doBundle.erro}` };
793
+ else {
794
+ const b = (0, ship_publicar_1.conferirBundle)(raiz, t, branch, fonte, (0, ci_1.comandosDoBundle)(daBranch));
795
+ fatos.bundle = { ok: b.estado === 'feita', texto: b.texto };
796
+ }
797
+ if (fatos.ciExigido) {
798
+ // RM-058, integração (D10): cada contexto que a proteção da base exige, no SHA fonte; o `consultarCi` lê o contexto do
799
+ // manifesto, então cada consulta leva o manifesto com aquele contexto.
800
+ const lidos = contextosDoCi(manifesto).map(contexto => opcoes.forja
801
+ ? (0, ci_1.consultarCiDoRepositorio)(opcoes.forja.repositorio, fonte, contexto, opcoes.executorCi)
802
+ : (0, ci_1.consultarCi)({ ...carregado, manifesto: { ...manifesto, ci: { ...manifesto.ci, context: contexto } } }, fonte, remoto, opcoes.executorCi));
803
+ fatos.ci = lidos[0];
804
+ if (lidos.length > 1)
805
+ fatos.cis = lidos;
806
+ }
807
+ if (!base.sha)
808
+ return fatos;
809
+ const integrada = git(raiz, ['merge-base', '--is-ancestor', base.sha, fonte]);
810
+ fatos.integrada = integrada.code === 0 ? true : integrada.code === 1 ? false : null;
811
+ const cadeia = cadeiaDaFonte(raiz, fonte, base.sha);
812
+ fatos.cadeia = cadeia ?? [];
813
+ fatos.cadeiaLida = cadeia !== null;
814
+ const mudados = git(raiz, ['diff', '--name-only', '-z', '--no-renames', `${base.sha}...${fonte}`]);
815
+ fatos.arquivos = mudados.ok ? mudados.stdout.split('\0').filter(Boolean) : [];
816
+ const varridos = segredosDaHistoria(raiz, base.sha, fonte);
817
+ fatos.segredos = varridos.achados;
818
+ fatos.commitsVarridos = varridos.commits;
819
+ fatos.polvos = varridos.polvos;
820
+ fatos.diffLido = mudados.ok && varridos.lido;
821
+ // O CHANGELOG do ancestral comum e o da fonte; a leitura que falha deixa `changelog` nulo, e a prova dele é lacuna.
822
+ const ancestral = git(raiz, ['merge-base', base.sha, fonte]);
823
+ if (!ancestral.ok || !SHA.test(ancestral.stdout.trim()))
824
+ return fatos;
825
+ const antes = blobLido(raiz, ancestral.stdout.trim(), noctum_changelog_1.ARQUIVO_DO_CHANGELOG);
826
+ const depois = blobLido(raiz, fonte, noctum_changelog_1.ARQUIVO_DO_CHANGELOG);
827
+ if (!antes.ok || !depois.ok)
828
+ return fatos;
829
+ const textoAntes = antes.bytes?.toString('utf8') ?? '';
830
+ fatos.changelog = depois.bytes === null && !fatos.arquivos.some(noctum_changelog_1.pedeLinha)
831
+ ? { ok: true, detalhe: 'sem CHANGELOG e sem código do produto', linhas: [] }
832
+ : (0, noctum_changelog_1.conferirChangelog)(textoAntes, depois.bytes?.toString('utf8') ?? '', fatos.arquivos);
833
+ return fatos;
834
+ }
835
+ /** As provas da entrega de uma thread agora. */
836
+ function provasDaEntrega(carregado, threadId, opcoes = {}) {
837
+ return avaliarProvas(lerFatosDaEntrega(carregado, threadId, opcoes));
838
+ }