@orkastery/cli 0.5.3 → 0.6.0

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 (502) 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 +195 -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/autoridade.json +56 -0
  16. package/adapters/claude-code/mods/ork-clima/.claude-plugin/plugin.json +14 -0
  17. package/adapters/claude-code/mods/ork-clima/hooks/contas.ts +283 -0
  18. package/adapters/claude-code/mods/ork-clima/hooks/hooks.json +3 -0
  19. package/adapters/claude-code/mods/ork-clima/hooks/previsao.ts +393 -0
  20. package/adapters/claude-code/mods/ork-clima/hooks/register.ts +205 -0
  21. package/adapters/claude-code/mods/ork-clima/orkastery-mod.json +5 -0
  22. package/adapters/claude-code/mods/ork-clima/types/index.d.ts +80 -0
  23. package/adapters/claude-code/mods/ork-freio/.claude-plugin/plugin.json +14 -0
  24. package/adapters/claude-code/mods/ork-freio/hooks/carga.ts +160 -0
  25. package/adapters/claude-code/mods/ork-freio/hooks/conferencia.ts +309 -0
  26. package/adapters/claude-code/mods/ork-freio/hooks/hooks.json +3 -0
  27. package/adapters/claude-code/mods/ork-freio/hooks/lote.ts +270 -0
  28. package/adapters/claude-code/mods/ork-freio/hooks/register.ts +285 -0
  29. package/adapters/claude-code/mods/ork-freio/orkastery-mod.json +5 -0
  30. package/adapters/claude-code/mods/ork-freio/types/index.d.ts +127 -0
  31. package/adapters/claude-code/mods/ork-mensageiro/.claude-plugin/plugin.json +14 -0
  32. package/adapters/claude-code/mods/ork-mensageiro/hooks/caixa.ts +215 -0
  33. package/adapters/claude-code/mods/ork-mensageiro/hooks/hooks.json +3 -0
  34. package/adapters/claude-code/mods/ork-mensageiro/hooks/painel.tsx +165 -0
  35. package/adapters/claude-code/mods/ork-mensageiro/hooks/recepcao.ts +256 -0
  36. package/adapters/claude-code/mods/ork-mensageiro/hooks/register.tsx +481 -0
  37. package/adapters/claude-code/mods/ork-mensageiro/orkastery-mod.json +5 -0
  38. package/adapters/claude-code/mods/ork-mensageiro/types/index.d.ts +85 -0
  39. package/adapters/claude-code/mods/ork-mesa/.claude-plugin/plugin.json +14 -0
  40. package/adapters/claude-code/mods/ork-mesa/hooks/cartoes.tsx +246 -0
  41. package/adapters/claude-code/mods/ork-mesa/hooks/decidir.ts +110 -0
  42. package/adapters/claude-code/mods/ork-mesa/hooks/fila.ts +253 -0
  43. package/adapters/claude-code/mods/ork-mesa/hooks/hooks.json +3 -0
  44. package/adapters/claude-code/mods/ork-mesa/hooks/obvia.ts +98 -0
  45. package/adapters/claude-code/mods/ork-mesa/hooks/register.tsx +493 -0
  46. package/adapters/claude-code/mods/ork-mesa/orkastery-mod.json +5 -0
  47. package/adapters/claude-code/mods/ork-mesa/types/index.d.ts +131 -0
  48. package/adapters/claude-code/mods/ork-pista/.claude-plugin/plugin.json +14 -0
  49. package/adapters/claude-code/mods/ork-pista/hooks/acoes.ts +71 -0
  50. package/adapters/claude-code/mods/ork-pista/hooks/ci.ts +121 -0
  51. package/adapters/claude-code/mods/ork-pista/hooks/hooks.json +3 -0
  52. package/adapters/claude-code/mods/ork-pista/hooks/pista.tsx +356 -0
  53. package/adapters/claude-code/mods/ork-pista/hooks/register.tsx +439 -0
  54. package/adapters/claude-code/mods/ork-pista/orkastery-mod.json +5 -0
  55. package/adapters/claude-code/mods/ork-pista/types/index.d.ts +85 -0
  56. package/adapters/claude-code/mods/ork-ponte/.claude-plugin/plugin.json +13 -0
  57. package/adapters/claude-code/mods/ork-ponte/hooks/faixa.tsx +57 -0
  58. package/adapters/claude-code/mods/ork-ponte/hooks/guarda.ts +410 -0
  59. package/adapters/claude-code/mods/ork-ponte/hooks/hooks.json +3 -0
  60. package/adapters/claude-code/mods/ork-ponte/hooks/mostrado.ts +55 -0
  61. package/adapters/claude-code/mods/ork-ponte/hooks/nucleo.ts +157 -0
  62. package/adapters/claude-code/mods/ork-ponte/hooks/papel.ts +42 -0
  63. package/adapters/claude-code/mods/ork-ponte/hooks/register.ts +331 -0
  64. package/adapters/claude-code/mods/ork-ponte/hooks/vigia.ts +76 -0
  65. package/adapters/claude-code/mods/ork-ponte/orkastery-mod.json +5 -0
  66. package/adapters/claude-code/mods/ork-ponte/types/index.d.ts +178 -0
  67. package/adapters/claude-code/mods/ork-portao/.claude-plugin/plugin.json +14 -0
  68. package/adapters/claude-code/mods/ork-portao/hooks/hooks.json +3 -0
  69. package/adapters/claude-code/mods/ork-portao/hooks/lease.ts +201 -0
  70. package/adapters/claude-code/mods/ork-portao/hooks/plano.ts +304 -0
  71. package/adapters/claude-code/mods/ork-portao/hooks/register.ts +339 -0
  72. package/adapters/claude-code/mods/ork-portao/hooks/verify.ts +152 -0
  73. package/adapters/claude-code/mods/ork-portao/orkastery-mod.json +5 -0
  74. package/adapters/claude-code/mods/ork-portao/types/index.d.ts +60 -0
  75. package/adapters/claude-code/mods/ork-radar/.claude-plugin/plugin.json +14 -0
  76. package/adapters/claude-code/mods/ork-radar/hooks/colisao.ts +420 -0
  77. package/adapters/claude-code/mods/ork-radar/hooks/hooks.json +3 -0
  78. package/adapters/claude-code/mods/ork-radar/hooks/mapa.tsx +322 -0
  79. package/adapters/claude-code/mods/ork-radar/hooks/register.tsx +470 -0
  80. package/adapters/claude-code/mods/ork-radar/orkastery-mod.json +5 -0
  81. package/adapters/claude-code/mods/ork-radar/types/index.d.ts +72 -0
  82. package/adapters/claude-code/mods/ork-ritual/.claude-plugin/plugin.json +14 -0
  83. package/adapters/claude-code/mods/ork-ritual/hooks/hooks.json +3 -0
  84. package/adapters/claude-code/mods/ork-ritual/hooks/nota.ts +88 -0
  85. package/adapters/claude-code/mods/ork-ritual/hooks/register.tsx +494 -0
  86. package/adapters/claude-code/mods/ork-ritual/hooks/replay.tsx +271 -0
  87. package/adapters/claude-code/mods/ork-ritual/hooks/ritual.ts +218 -0
  88. package/adapters/claude-code/mods/ork-ritual/orkastery-mod.json +5 -0
  89. package/adapters/claude-code/mods/ork-ritual/types/index.d.ts +106 -0
  90. package/adapters/claude-code/mods/ork-sentinela/.claude-plugin/plugin.json +14 -0
  91. package/adapters/claude-code/mods/ork-sentinela/hooks/alertas.ts +85 -0
  92. package/adapters/claude-code/mods/ork-sentinela/hooks/despacho.ts +156 -0
  93. package/adapters/claude-code/mods/ork-sentinela/hooks/hooks.json +3 -0
  94. package/adapters/claude-code/mods/ork-sentinela/hooks/register.ts +511 -0
  95. package/adapters/claude-code/mods/ork-sentinela/hooks/vigia.ts +186 -0
  96. package/adapters/claude-code/mods/ork-sentinela/orkastery-mod.json +5 -0
  97. package/adapters/claude-code/mods/ork-sentinela/types/index.d.ts +65 -0
  98. package/adapters/claude-code/mods/ork-torre/.claude-plugin/plugin.json +14 -0
  99. package/adapters/claude-code/mods/ork-torre/hooks/agenda.ts +60 -0
  100. package/adapters/claude-code/mods/ork-torre/hooks/detalhe.tsx +236 -0
  101. package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +42 -0
  102. package/adapters/claude-code/mods/ork-torre/hooks/hooks.json +3 -0
  103. package/adapters/claude-code/mods/ork-torre/hooks/painel.tsx +344 -0
  104. package/adapters/claude-code/mods/ork-torre/hooks/rede.ts +97 -0
  105. package/adapters/claude-code/mods/ork-torre/hooks/register.tsx +437 -0
  106. package/adapters/claude-code/mods/ork-torre/orkastery-mod.json +5 -0
  107. package/adapters/claude-code/mods/ork-torre/types/index.d.ts +108 -0
  108. package/adapters/codex/skills/ork/SKILL.md +2 -0
  109. package/adapters/hermes/README.md +64 -1
  110. package/adapters/hermes/bin/ork-conhecimento-colecoes.sh +14 -0
  111. package/adapters/hermes/bin/ork-conhecimento-listar.sh +15 -0
  112. package/adapters/hermes/bin/ork-conhecimento-mostrar.sh +16 -0
  113. package/adapters/hermes/bin/ork-conhecimento-verificar.sh +15 -0
  114. package/adapters/hermes/bin/ork-vigia-batida.py +127 -0
  115. package/adapters/hermes/hermes.plugin.json +4 -0
  116. package/adapters/hermes/hitl-ingress/__init__.py +101 -8
  117. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +3 -3
  118. package/adapters/openclaw/README.md +83 -10
  119. package/adapters/openclaw/bin/ork-vigia-batida.js +86 -0
  120. package/adapters/openclaw/dist/hitl-ingress.js +106 -9
  121. package/adapters/openclaw/dist/index.js +86 -0
  122. package/adapters/openclaw/openclaw.plugin.json +34 -4
  123. package/adapters/openclaw/package.json +2 -2
  124. package/adapters/openclaw/src/hitl-ingress.ts +103 -9
  125. package/adapters/openclaw/src/index.ts +94 -0
  126. package/assets/adapters-en/claude-code/README.md +403 -0
  127. package/assets/adapters-en/claude-code/agents/ork-check.md +33 -0
  128. package/assets/adapters-en/claude-code/agents/ork-go.md +33 -0
  129. package/assets/adapters-en/claude-code/agents/ork-goal.md +34 -0
  130. package/assets/adapters-en/claude-code/agents/ork-master.md +35 -0
  131. package/assets/adapters-en/claude-code/agents/ork-plan.md +33 -0
  132. package/assets/adapters-en/claude-code/agents/ork-ship.md +33 -0
  133. package/assets/adapters-en/claude-code/commands/check.md +46 -0
  134. package/assets/adapters-en/claude-code/commands/go.md +46 -0
  135. package/assets/adapters-en/claude-code/commands/goal.md +55 -0
  136. package/assets/adapters-en/claude-code/commands/master.md +54 -0
  137. package/assets/adapters-en/claude-code/commands/onboarding.md +22 -0
  138. package/assets/adapters-en/claude-code/commands/ork.md +126 -0
  139. package/assets/adapters-en/claude-code/commands/plan.md +47 -0
  140. package/assets/adapters-en/claude-code/commands/ship.md +45 -0
  141. package/assets/adapters-en/codex/skills/ork/SKILL.md +85 -0
  142. package/assets/adapters-en/hermes/README.md +273 -0
  143. package/assets/adapters-en/hermes/skills/orkastery-devmaster/SKILL.md +169 -0
  144. package/assets/docs-en/markdownlint-cli2.jsonc +22 -0
  145. package/assets/docs-en/padroes/documentacao-de-produto.md +135 -0
  146. package/assets/docs-en/padroes/roadmap-de-produto.md +100 -0
  147. package/assets/docs-en/produto/README.md +14 -0
  148. package/assets/docs-en/produto/_modelo-feature.md +62 -0
  149. package/assets/docs-en/roadmap/README.md +14 -0
  150. package/assets/docs-en/roadmap/_modelo-item.md +75 -0
  151. package/assets/references-en/README.md +25 -0
  152. package/assets/references-en/code-review-axes.md +82 -0
  153. package/assets/references-en/definition-of-done.md +74 -0
  154. package/assets/references-en/performance-checklist.md +53 -0
  155. package/assets/references-en/security-checklist.md +101 -0
  156. package/assets/references-en/testing-patterns.md +51 -0
  157. package/assets/security-scan/defaults/.golangci.yml +2 -0
  158. package/assets/security-scan/defaults/eslint.config.mjs +11 -0
  159. package/assets/security-scan/defaults/gitleaks.toml +3 -0
  160. package/assets/security-scan/defaults/ruff.toml +1 -0
  161. package/assets/security-scan/tools-agil.json +87 -0
  162. package/assets/security-scan/tools.json +625 -0
  163. package/assets/skills-en/core/onboarding/SKILL.md +58 -0
  164. package/assets/skills-en/core/orkastery-bootstrap/SKILL.md +119 -0
  165. package/assets/skills-en/core/thread-state/SKILL.md +88 -0
  166. package/assets/skills-en/governance/decision-triage/SKILL.md +65 -0
  167. package/assets/skills-en/governance/narrative-guardian/SKILL.md +66 -0
  168. package/assets/skills-en/governance/roadmap-keeper/SKILL.md +60 -0
  169. package/assets/skills-en/governance/scope-check-capability-map/SKILL.md +71 -0
  170. package/assets/skills-en/observability/thread-tracing/SKILL.md +63 -0
  171. package/assets/skills-en/phases/check-quality/SKILL.md +103 -0
  172. package/assets/skills-en/phases/go-implementation/SKILL.md +78 -0
  173. package/assets/skills-en/phases/goal-definition/SKILL.md +69 -0
  174. package/assets/skills-en/phases/master-metrics/SKILL.md +72 -0
  175. package/assets/skills-en/phases/plan-specification/SKILL.md +67 -0
  176. package/assets/skills-en/phases/security-remediation/SKILL.md +70 -0
  177. package/assets/skills-en/phases/ship-release/SKILL.md +71 -0
  178. package/assets/skills-en/reviewers/code-reviewer/SKILL.md +61 -0
  179. package/assets/skills-en/reviewers/security-auditor/SKILL.md +93 -0
  180. package/assets/skills-en/reviewers/test-engineer/SKILL.md +66 -0
  181. package/assets/skills-en/reviewers/web-performance-auditor/SKILL.md +63 -0
  182. package/dist/adapters/claude-bg.js +193 -15
  183. package/dist/adapters/codex-controller-worker.js +12 -5
  184. package/dist/adapters/codex-events.js +16 -4
  185. package/dist/adapters/codex.js +13 -2
  186. package/dist/agents-md.js +24 -15
  187. package/dist/atualizar-branch.js +245 -0
  188. package/dist/auditoria.js +13 -11
  189. package/dist/auditrun.js +68 -48
  190. package/dist/autorizacao-duravel.js +327 -0
  191. package/dist/board.js +98 -43
  192. package/dist/branch-de-estado.js +39 -9
  193. package/dist/busca-semantica.js +7 -5
  194. package/dist/catalogo.js +35 -1
  195. package/dist/changelog-reconciliacao.js +290 -0
  196. package/dist/check-agil.js +500 -0
  197. package/dist/check-modo.js +41 -0
  198. package/dist/check-paralelo.js +183 -0
  199. package/dist/ci.js +255 -46
  200. package/dist/ciclos.js +8 -6
  201. package/dist/claim-lint.js +12 -8
  202. package/dist/claims-conferir.js +350 -0
  203. package/dist/claims.js +13 -10
  204. package/dist/company-brain-client.js +45 -3
  205. package/dist/company-brain-journal.js +2 -1
  206. package/dist/company-brain-migration.js +47 -6
  207. package/dist/conducao-batida.js +759 -0
  208. package/dist/conducao-sentinela.js +405 -0
  209. package/dist/conducao-texto.js +12 -18
  210. package/dist/conducao.js +206 -59
  211. package/dist/conhecimento-consulta.js +270 -0
  212. package/dist/conhecimento.js +514 -0
  213. package/dist/conta-esgotada.js +127 -0
  214. package/dist/dado-privado.js +816 -0
  215. package/dist/decisao-autonoma.js +5 -4
  216. package/dist/demo.js +14 -12
  217. package/dist/dependencias-da-worktree.js +25 -0
  218. package/dist/divida.js +38 -36
  219. package/dist/docs.js +103 -82
  220. package/dist/doctor-forja.js +345 -0
  221. package/dist/doctor-pulse-cron.js +294 -27
  222. package/dist/doctor-rede.js +18 -17
  223. package/dist/doctor.js +278 -116
  224. package/dist/dsn-da-memoria.js +417 -0
  225. package/dist/entrega-por-mod.js +503 -0
  226. package/dist/entrega-pr.js +158 -57
  227. package/dist/escopo-escrita.js +10 -1
  228. package/dist/esgotamentos.js +13 -11
  229. package/dist/estado-thread.js +42 -17
  230. package/dist/evalrunner.js +35 -32
  231. package/dist/experiencia-instalacao.js +45 -25
  232. package/dist/fabrica-batida.js +323 -0
  233. package/dist/fabrica-estado.js +49 -16
  234. package/dist/fabrica-publicar.js +3 -1
  235. package/dist/fila-pesada.js +444 -0
  236. package/dist/fix.js +24 -21
  237. package/dist/forja/adaptador.js +6 -0
  238. package/dist/forja/ambiente-de-ci.js +27 -0
  239. package/dist/forja/api.js +256 -0
  240. package/dist/forja/cache.js +89 -0
  241. package/dist/forja/cli.js +60 -0
  242. package/dist/forja/contrato.js +9 -0
  243. package/dist/forja/endereco.js +43 -0
  244. package/dist/forja/estado.js +22 -0
  245. package/dist/forja/executor.js +73 -0
  246. package/dist/forja/github/ci.js +95 -0
  247. package/dist/forja/github/dono-da-rede.js +18 -0
  248. package/dist/forja/github/metadados.js +15 -0
  249. package/dist/forja/github/pedidos.js +200 -0
  250. package/dist/forja/gitlab/ci.js +141 -0
  251. package/dist/forja/gitlab/consultas.js +59 -0
  252. package/dist/forja/gitlab/dono-da-rede.js +43 -0
  253. package/dist/forja/gitlab/metadados.js +15 -0
  254. package/dist/forja/gitlab/pedidos-abertos.js +158 -0
  255. package/dist/forja/gitlab/pedidos.js +141 -0
  256. package/dist/forja/produtor.js +32 -0
  257. package/dist/forja/registro.js +31 -0
  258. package/dist/forja/resolver.js +121 -0
  259. package/dist/forja/texto.js +13 -0
  260. package/dist/forja/versao.js +71 -0
  261. package/dist/forja.js +124 -81
  262. package/dist/gates.js +3 -28
  263. package/dist/handoff.js +33 -19
  264. package/dist/hitl-contract.js +46 -43
  265. package/dist/hitl-curto.js +37 -25
  266. package/dist/hitl-gates.js +13 -11
  267. package/dist/hitl-local-receipt.js +53 -0
  268. package/dist/hitl-local.js +11 -2
  269. package/dist/hitl-lock.js +5 -11
  270. package/dist/hitl-public-receipt.js +11 -0
  271. package/dist/hitl-resumo.js +72 -3
  272. package/dist/hitl-sessions.js +62 -9
  273. package/dist/hitl-tempo-parado.js +4 -4
  274. package/dist/hitl-texto-livre.js +7 -4
  275. package/dist/hitl.js +63 -64
  276. package/dist/horario.js +30 -7
  277. package/dist/hosts.js +155 -134
  278. package/dist/impedimento.js +108 -14
  279. package/dist/index.js +1421 -1019
  280. package/dist/indice-vetorial.js +14 -12
  281. package/dist/indice.js +7 -1
  282. package/dist/init.js +54 -49
  283. package/dist/instabilidade.js +144 -0
  284. package/dist/integracoes-locais.js +4 -0
  285. package/dist/intelligence-graph-cli.js +213 -88
  286. package/dist/intelligence-graph-contexto.js +56 -17
  287. package/dist/intelligence-graph-contract.js +2 -1
  288. package/dist/intelligence-graph-extract-md.js +56 -4
  289. package/dist/intelligence-graph-extract-ts.js +71 -3
  290. package/dist/intelligence-graph-query.js +96 -34
  291. package/dist/leases.js +19 -18
  292. package/dist/ledger-stats.js +17 -16
  293. package/dist/ledger.js +46 -2
  294. package/dist/licoes.js +12 -11
  295. package/dist/liveness.js +6 -3
  296. package/dist/locale-accounts.js +220 -0
  297. package/dist/locale-adapter.js +291 -0
  298. package/dist/locale-ajuda.js +833 -0
  299. package/dist/locale-audit.js +199 -0
  300. package/dist/locale-claims.js +213 -0
  301. package/dist/locale-conducao.js +138 -0
  302. package/dist/locale-conhecimento.js +181 -0
  303. package/dist/locale-decisao.js +205 -0
  304. package/dist/locale-docs.js +230 -0
  305. package/dist/locale-estado.js +101 -0
  306. package/dist/locale-forja.js +290 -0
  307. package/dist/locale-gerados.js +165 -0
  308. package/dist/locale-grafo.js +129 -0
  309. package/dist/locale-master.js +182 -0
  310. package/dist/locale-network.js +524 -0
  311. package/dist/locale-onboarding.js +92 -0
  312. package/dist/locale-operacao-restante.js +931 -0
  313. package/dist/locale-phase.js +151 -0
  314. package/dist/locale-projetos.js +100 -0
  315. package/dist/locale-prompts.js +194 -0
  316. package/dist/locale-pulse.js +349 -0
  317. package/dist/locale-reservas.js +87 -0
  318. package/dist/locale-retry.js +524 -0
  319. package/dist/locale-roadmap.js +102 -0
  320. package/dist/locale-security-scan.js +69 -0
  321. package/dist/locale-setup.js +192 -0
  322. package/dist/locale-ship.js +597 -0
  323. package/dist/locale-thread.js +421 -0
  324. package/dist/locale-verify.js +201 -0
  325. package/dist/locale-worktree.js +246 -0
  326. package/dist/locale.js +1258 -0
  327. package/dist/maestro-runtime.js +22 -3
  328. package/dist/manifest.js +415 -13
  329. package/dist/maquina-ociosa.js +211 -0
  330. package/dist/maquina.js +31 -7
  331. package/dist/master-digest.js +6 -1
  332. package/dist/master-nota-local.js +200 -0
  333. package/dist/master-nota.js +14 -8
  334. package/dist/master.js +139 -65
  335. package/dist/mcp-artifacts.js +11 -2
  336. package/dist/mcp-conhecimento.js +52 -0
  337. package/dist/mcp-experiencia.js +26 -5
  338. package/dist/mcp-git.js +23 -6
  339. package/dist/mcp-grafo.js +7 -4
  340. package/dist/mcp-install.js +73 -8
  341. package/dist/mcp-noctum.js +101 -0
  342. package/dist/mcp-server.js +90 -16
  343. package/dist/mcp-ship-transporte.js +977 -0
  344. package/dist/mcp-ship.js +71 -25
  345. package/dist/mcp-transportes.js +17 -0
  346. package/dist/mcp-verify.js +6 -1
  347. package/dist/memoria.js +63 -58
  348. package/dist/modos-migracao.js +8 -6
  349. package/dist/modos.js +20 -24
  350. package/dist/network-roadmap.js +90 -51
  351. package/dist/noctum-aceite.js +789 -0
  352. package/dist/noctum-arco.js +1418 -0
  353. package/dist/noctum-capacidade.js +141 -0
  354. package/dist/noctum-changelog.js +399 -0
  355. package/dist/noctum-cli.js +482 -0
  356. package/dist/noctum-coordenacao.js +1225 -0
  357. package/dist/noctum-entrega.js +1169 -0
  358. package/dist/noctum-envelope.js +1046 -0
  359. package/dist/noctum-execucao.js +376 -0
  360. package/dist/noctum-fechamento.js +405 -0
  361. package/dist/noctum-fila.js +314 -0
  362. package/dist/noctum-guarda.js +256 -0
  363. package/dist/noctum-integracao.js +507 -0
  364. package/dist/noctum-maquinas.js +383 -0
  365. package/dist/noctum-medicao.js +450 -0
  366. package/dist/noctum-modo.js +362 -0
  367. package/dist/noctum-pergunta-canal.js +96 -0
  368. package/dist/noctum-pergunta.js +115 -0
  369. package/dist/noctum-provas.js +838 -0
  370. package/dist/noctum-reconciliacao.js +591 -0
  371. package/dist/noctum-registro.js +215 -0
  372. package/dist/noctum-relatorio.js +827 -0
  373. package/dist/noctum-resposta.js +207 -0
  374. package/dist/noctum-sessoes-em-voo.js +93 -0
  375. package/dist/noctum-supervisao.js +649 -0
  376. package/dist/noctum-terminal.js +459 -0
  377. package/dist/noctum-transporte.js +655 -0
  378. package/dist/noctum-vigia.js +639 -0
  379. package/dist/ocupacao.js +32 -3
  380. package/dist/onboarding.js +56 -26
  381. package/dist/orkmind.js +48 -25
  382. package/dist/orquestracao.js +44 -47
  383. package/dist/parado-no-condutor.js +174 -127
  384. package/dist/phase.js +144 -63
  385. package/dist/policies.js +37 -4
  386. package/dist/portfolio.js +4 -2
  387. package/dist/postura-local.js +235 -0
  388. package/dist/preflight.js +11 -10
  389. package/dist/procedencia.js +220 -0
  390. package/dist/projeto-alvo.js +186 -24
  391. package/dist/prompts.js +140 -42
  392. package/dist/prova-minima.js +5 -6
  393. package/dist/pulse-cadencia.js +5 -4
  394. package/dist/pulse-consentimento.js +53 -1
  395. package/dist/pulse-delivery.js +151 -24
  396. package/dist/pulse-resposta.js +368 -4
  397. package/dist/pulse.js +188 -22
  398. package/dist/ratelimit.js +16 -6
  399. package/dist/recall.js +14 -13
  400. package/dist/redacao-saida.js +63 -0
  401. package/dist/rede-adesao.js +13 -8
  402. package/dist/rede-forja.js +108 -24
  403. package/dist/rede-status.js +66 -47
  404. package/dist/rede.js +86 -46
  405. package/dist/retomada-checkpoint.js +101 -0
  406. package/dist/retry.js +451 -212
  407. package/dist/roadmap-reservas.js +45 -28
  408. package/dist/roadmap-status.js +129 -33
  409. package/dist/runtime-ambiente.js +5 -0
  410. package/dist/runtime-context.js +284 -32
  411. package/dist/runtime-escada.js +423 -0
  412. package/dist/runtime-profiles.js +103 -46
  413. package/dist/runtimes.js +2 -1
  414. package/dist/saida-segura.js +3 -2
  415. package/dist/security-scan/agil.js +355 -0
  416. package/dist/security-scan/cli.js +76 -0
  417. package/dist/security-scan/compare.js +149 -0
  418. package/dist/security-scan/config.js +93 -0
  419. package/dist/security-scan/detect.js +97 -0
  420. package/dist/security-scan/executor.js +188 -0
  421. package/dist/security-scan/guard.js +180 -0
  422. package/dist/security-scan/imagem-de-lint.js +96 -0
  423. package/dist/security-scan/normalize/actionlint.js +12 -0
  424. package/dist/security-scan/normalize/eslint.js +12 -0
  425. package/dist/security-scan/normalize/gitleaks.js +13 -0
  426. package/dist/security-scan/normalize/golangci-lint.js +13 -0
  427. package/dist/security-scan/normalize/govulncheck.js +36 -0
  428. package/dist/security-scan/normalize/index.js +25 -0
  429. package/dist/security-scan/normalize/npm-audit.js +30 -0
  430. package/dist/security-scan/normalize/osv-scanner.js +32 -0
  431. package/dist/security-scan/normalize/oxlint.js +51 -0
  432. package/dist/security-scan/normalize/pip-audit.js +16 -0
  433. package/dist/security-scan/normalize/ruff.js +12 -0
  434. package/dist/security-scan/normalize/semgrep.js +20 -0
  435. package/dist/security-scan/normalize/trivy.js +33 -0
  436. package/dist/security-scan/normalize/util.js +204 -0
  437. package/dist/security-scan/normalize/zizmor.js +19 -0
  438. package/dist/security-scan/report.js +156 -0
  439. package/dist/security-scan/run.js +448 -0
  440. package/dist/security-scan/tools.js +62 -0
  441. package/dist/security-scan/trabalhador.js +74 -0
  442. package/dist/security-scan/types.js +15 -0
  443. package/dist/session-events.js +26 -5
  444. package/dist/session-watcher-claude.js +81 -20
  445. package/dist/session-watcher.js +340 -42
  446. package/dist/sessoes-contas.js +10 -1
  447. package/dist/sessoes-inventario.js +1 -1
  448. package/dist/sessoes.js +25 -23
  449. package/dist/setup.js +59 -58
  450. package/dist/ship-publicar.js +971 -0
  451. package/dist/ship.js +243 -97
  452. package/dist/steal.js +77 -0
  453. package/dist/superficie.js +14 -11
  454. package/dist/thread-close.js +12 -11
  455. package/dist/thread.js +274 -100
  456. package/dist/tokens.js +15 -11
  457. package/dist/trabalho-thread.js +297 -0
  458. package/dist/transporte-do-canal.js +189 -0
  459. package/dist/types.js +12 -1
  460. package/dist/util.js +13 -3
  461. package/dist/varredura-acoes.js +507 -0
  462. package/dist/verify.js +608 -76
  463. package/dist/vida-da-thread.js +254 -0
  464. package/dist/worktree-local.js +167 -0
  465. package/dist/worktree.js +99 -83
  466. package/dist/yaml.js +27 -8
  467. package/eval/casos/check-quality.json +77 -0
  468. package/eval/casos/security-auditor.json +148 -0
  469. package/eval/casos/security-remediation.json +161 -0
  470. package/eval/fixtures/security-scan/README.md +8 -0
  471. package/eval/fixtures/security-scan/go/.github/workflows/ci.yml +12 -0
  472. package/eval/fixtures/security-scan/go/Dockerfile +4 -0
  473. package/eval/fixtures/security-scan/go/README.md +4 -0
  474. package/eval/fixtures/security-scan/go/config.go +4 -0
  475. package/eval/fixtures/security-scan/go/go.mod +5 -0
  476. package/eval/fixtures/security-scan/go/go.sum +2 -0
  477. package/eval/fixtures/security-scan/go/main.go +31 -0
  478. package/eval/fixtures/security-scan/js/.github/workflows/ci.yml +12 -0
  479. package/eval/fixtures/security-scan/js/Dockerfile +3 -0
  480. package/eval/fixtures/security-scan/js/README.md +4 -0
  481. package/eval/fixtures/security-scan/js/package-lock.json +844 -0
  482. package/eval/fixtures/security-scan/js/package.json +10 -0
  483. package/eval/fixtures/security-scan/js/src/config.js +4 -0
  484. package/eval/fixtures/security-scan/js/src/server.js +17 -0
  485. package/eval/fixtures/security-scan/js/src/util.ts +4 -0
  486. package/eval/fixtures/security-scan/js/tsconfig.json +4 -0
  487. package/eval/fixtures/security-scan/python/.github/workflows/ci.yml +12 -0
  488. package/eval/fixtures/security-scan/python/Dockerfile +3 -0
  489. package/eval/fixtures/security-scan/python/README.md +4 -0
  490. package/eval/fixtures/security-scan/python/app/__init__.py +0 -0
  491. package/eval/fixtures/security-scan/python/app/config.py +2 -0
  492. package/eval/fixtures/security-scan/python/app/main.py +19 -0
  493. package/eval/fixtures/security-scan/python/requirements.txt +1 -0
  494. package/package.json +9 -4
  495. package/references/security-checklist.md +3 -3
  496. package/schemas/code-artifact-graph.v1.schema.json +2 -1
  497. package/skills/README.md +1 -0
  498. package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
  499. package/skills/phases/check-quality/SKILL.md +16 -0
  500. package/skills/phases/go-implementation/SKILL.md +6 -0
  501. package/skills/phases/security-remediation/SKILL.md +70 -0
  502. package/skills/reviewers/security-auditor/SKILL.md +25 -0
@@ -0,0 +1,410 @@
1
+ // A regra de autoridade dos mods do Orkastery (RM-059, T0.5 e T0.6; D5 do plano dos mods): nenhum mod
2
+ // aprova gate, permissao, nota do MASTER, push ou tag pelo dono, nem fala como o dono. A lista mora em
3
+ // `adapters/claude-code/mods/autoridade.json`; este modulo so a interpreta, e e o mesmo casamento para a
4
+ // guarda estatica (`core/scripts/checar-mods.cjs`, por transpilacao) e para a guarda em tempo de execucao
5
+ // da ponte. Modulo puro: so dados entram e saem, nenhum `$` passa por aqui. E melhor esforco: argv montado
6
+ // em tempo de execucao escapa da varredura estatica, e um script em arquivo escapa das duas.
7
+
8
+ export const FORMATO_DA_AUTORIDADE = 'ork.mods-autoridade/v1'
9
+ export const NOME_DA_PONTE = 'ork-ponte'
10
+ export const MARCA_DE_MOD = 'orkastery-mod.json'
11
+
12
+ export type RegraDeArgv = {
13
+ id: string
14
+ porque: string
15
+ /** Comando do `ork`: o prefixo das palavras posicionais, como o `parseArgs` do CLI as separa. */
16
+ sequencia?: string[]
17
+ /** Opcao que precisa estar junto (`--score`, `--por`, `--autorizar-push`). */
18
+ opcao?: string
19
+ /** Programa na posicao de comando (`git`, `gh`). */
20
+ programa?: string
21
+ /** Subcomando do programa (`push`, `tag`); sem ele, o programa inteiro. */
22
+ subcomando?: string
23
+ }
24
+
25
+ export type Autoridade = {
26
+ formato: string
27
+ regra: string
28
+ ganchosProibidos: Array<{ id: string; evento: string; componente?: string; porque: string }>
29
+ decisoesProibidas: Array<{ id: string; evento: string; decisao: string; porque: string }>
30
+ chamadasProibidas: Array<{ id: string; chamada: string; tipo?: string; porque: string }>
31
+ argvProibido: RegraDeArgv[]
32
+ ambienteProibido: Array<{ id: string; prefixo: string; porque: string }>
33
+ caminhosProibidos: Array<{ id: string; trecho: string; porque: string }>
34
+ reservadosAPonte: Array<{ id: string; evento: string; porque: string }>
35
+ permitidos: Array<{ id: string; sequencia: string[]; porque: string }>
36
+ }
37
+
38
+ /** O que a guarda achou: a regra (id) e o porque, para a recusa dizer o motivo. */
39
+ export type Achado = { regra: string; porque: string }
40
+
41
+ const SECOES = ['ganchosProibidos', 'decisoesProibidas', 'chamadasProibidas', 'argvProibido', 'ambienteProibido',
42
+ 'caminhosProibidos', 'reservadosAPonte', 'permitidos'] as const
43
+
44
+ function texto(v: unknown): v is string {
45
+ return typeof v === 'string' && v.trim() !== ''
46
+ }
47
+
48
+ function listaDeTexto(v: unknown): v is string[] {
49
+ return Array.isArray(v) && v.length > 0 && v.every(texto)
50
+ }
51
+
52
+ /** Confere a forma da lista; qualquer desvio e erro, porque a guarda fecha sem lista valida. */
53
+ export function validarAutoridade(x: unknown): Autoridade {
54
+ const a = x as Record<string, unknown> | null
55
+ if (!a || typeof a !== 'object' || Array.isArray(a)) throw new Error('lista de autoridade nao e um objeto')
56
+ if (a.formato !== FORMATO_DA_AUTORIDADE) throw new Error(`formato da lista de autoridade: ${String(a.formato)}`)
57
+ if (!texto(a.regra)) throw new Error('lista de autoridade sem regra')
58
+ const ids = new Set<string>()
59
+ for (const secao of SECOES) {
60
+ const itens = a[secao]
61
+ if (!Array.isArray(itens)) throw new Error(`lista de autoridade sem a secao ${secao}`)
62
+ for (const bruto of itens) {
63
+ const item = bruto as Record<string, unknown>
64
+ if (!item || !texto(item.id) || !texto(item.porque)) throw new Error(`item sem id ou porque em ${secao}`)
65
+ if (ids.has(item.id as string)) throw new Error(`id repetido na lista de autoridade: ${String(item.id)}`)
66
+ ids.add(item.id as string)
67
+ const falta = (campo: string) => { throw new Error(`${secao}/${String(item.id)} sem ${campo}`) }
68
+ if ((secao === 'ganchosProibidos' || secao === 'reservadosAPonte' || secao === 'decisoesProibidas') && !texto(item.evento)) falta('evento')
69
+ if (secao === 'decisoesProibidas' && !texto(item.decisao)) falta('decisao')
70
+ if (secao === 'chamadasProibidas' && !texto(item.chamada)) falta('chamada')
71
+ if (secao === 'ambienteProibido' && !texto(item.prefixo)) falta('prefixo')
72
+ if (secao === 'caminhosProibidos' && !texto(item.trecho)) falta('trecho')
73
+ if (secao === 'permitidos' && !listaDeTexto(item.sequencia)) falta('sequencia')
74
+ if (secao === 'argvProibido') {
75
+ const porSequencia = listaDeTexto(item.sequencia)
76
+ const porPrograma = texto(item.programa)
77
+ if (porSequencia === porPrograma) throw new Error(`argvProibido/${String(item.id)} precisa de sequencia ou programa, um so`)
78
+ if (item.opcao !== undefined && !(texto(item.opcao) && (item.opcao as string).startsWith('--'))) falta('opcao com --')
79
+ if (item.subcomando !== undefined && !texto(item.subcomando)) falta('subcomando')
80
+ }
81
+ }
82
+ }
83
+ return a as unknown as Autoridade
84
+ }
85
+
86
+ /** Le e confere o texto do `autoridade.json`. */
87
+ export function lerAutoridade(conteudo: string): Autoridade {
88
+ return validarAutoridade(JSON.parse(conteudo))
89
+ }
90
+
91
+ /** Mod do Orkastery: nome `ork-*` ou pasta com `orkastery-mod.json` (os conhecidos vem do `plugin.register`). */
92
+ export function ehModDoOrkastery(nome: unknown, conhecidos?: { has: (n: string) => boolean }): boolean {
93
+ return typeof nome === 'string' && (nome.startsWith('ork-') || (conhecidos?.has(nome) ?? false))
94
+ }
95
+
96
+ function nomeBase(token: string): string {
97
+ const t = token.replace(/\/+$/, '')
98
+ return t.slice(t.lastIndexOf('/') + 1)
99
+ }
100
+
101
+ /** As palavras posicionais e as opcoes, exatamente como o `parseArgs` do CLI do ork as separa. */
102
+ export function separarArgs(resto: readonly string[]): { posicionais: string[]; opcoes: Set<string> } {
103
+ const posicionais: string[] = []
104
+ const opcoes = new Set<string>()
105
+ for (let i = 0; i < resto.length; i++) {
106
+ const a = resto[i] as string
107
+ if (a.startsWith('--')) {
108
+ const igual = a.indexOf('=')
109
+ if (igual > 0) { opcoes.add(a.slice(2, igual)); continue }
110
+ opcoes.add(a.slice(2))
111
+ const proximo = resto[i + 1]
112
+ if (proximo !== undefined && !proximo.startsWith('--')) i++
113
+ } else posicionais.push(a)
114
+ }
115
+ return { posicionais, opcoes }
116
+ }
117
+
118
+ function comecaCom(lista: readonly string[], prefixo: readonly string[]): boolean {
119
+ return prefixo.length > 0 && prefixo.every((p, i) => lista[i] === p)
120
+ }
121
+
122
+ const WRAPPERS = new Set(['env', 'nohup', 'timeout', 'nice', 'ionice', 'setsid', 'stdbuf', 'sudo', 'doas', 'command',
123
+ 'exec', 'time', 'xargs', 'chrt', 'taskset', 'unbuffer', 'then', 'do', 'else', 'if', 'while', 'until', '!'])
124
+ const OPCOES_COM_VALOR: Record<string, readonly string[]> = {
125
+ env: ['-u', '--unset', '-C', '--chdir'], sudo: ['-u', '-g', '-C', '-D', '-h', '-p', '-r', '-t', '-U'],
126
+ nice: ['-n', '--adjustment'], ionice: ['-c', '-n', '-p'], timeout: ['-k', '--kill-after', '-s', '--signal'],
127
+ xargs: ['-a', '-d', '-E', '-I', '-L', '-n', '-P', '-s'], chrt: ['-p'], taskset: ['-p'], doas: ['-u', '-C'],
128
+ }
129
+ const SHELLS = new Set(['sh', 'bash', 'dash', 'zsh', 'ksh', 'mksh', 'ash', 'fish', 'busybox'])
130
+ const INTERPRETADORES: Record<string, readonly string[]> = {
131
+ node: ['-e', '--eval', '-p', '--print'], nodejs: ['-e', '--eval', '-p', '--print'], deno: ['eval'],
132
+ bun: ['-e', '--eval'], python: ['-c'], python3: ['-c'], perl: ['-e', '-E'], ruby: ['-e'], php: ['-r'],
133
+ }
134
+ const OPCOES_GLOBAIS_DO_GIT = ['-C', '-c', '--git-dir', '--work-tree', '--namespace', '--super-prefix', '--config-env']
135
+ const ENTRADA_DO_ORK = /(^|\/)(ork|ork\.c?js|ork\.mjs|index\.c?js|cli\.c?js)$/
136
+ const MAX_PROFUNDIDADE = 4
137
+
138
+ /**
139
+ * Tira o que so embrulha o comando (`env A=1`, `nohup`, `timeout 10`, `sudo -u x`) e devolve o comando de fato.
140
+ * `env` sem comando imprime o ambiente inteiro: `despejo` avisa isso.
141
+ */
142
+ function semEmbrulho(tokens: readonly string[]): { tokens: string[]; scriptDoEnv?: string; despejo?: boolean } {
143
+ let t = [...tokens]
144
+ let ultimo = ''
145
+ for (let voltas = 0; voltas < 8 && t.length > 0; voltas++) {
146
+ while (t.length > 0 && /^[A-Za-z_][A-Za-z0-9_]*=/.test(t[0] as string)) t = t.slice(1)
147
+ const base = nomeBase(t[0] ?? '')
148
+ if (!WRAPPERS.has(base)) break
149
+ ultimo = base
150
+ t = t.slice(1)
151
+ const comValor = OPCOES_COM_VALOR[base] ?? []
152
+ while (t.length > 0) {
153
+ const a = t[0] as string
154
+ if (base === 'env' && (a === '-S' || a === '--split-string')) return { tokens: [], scriptDoEnv: t[1] ?? '' }
155
+ if (comValor.includes(a)) { t = t.slice(2); continue }
156
+ if (a.startsWith('-') || /^[A-Za-z_][A-Za-z0-9_]*=/.test(a)) { t = t.slice(1); continue }
157
+ if (base === 'timeout' && /^\d+(\.\d+)?[smhd]?$/.test(a)) { t = t.slice(1); continue }
158
+ break
159
+ }
160
+ }
161
+ return { tokens: t, ...(t.length === 0 && ultimo === 'env' ? { despejo: true } : {}) }
162
+ }
163
+
164
+ /**
165
+ * O comando expoe o ambiente inteiro do processo (onde mora `ORK_HITL_*` quando o host o exporta):
166
+ * `printenv` sem nome, `export -p`, `declare -x`, `set` sem argumento, `compgen -e` ou `/proc/<pid>/environ`.
167
+ */
168
+ function despejaOAmbiente(tokens: readonly string[]): boolean {
169
+ const base = nomeBase(tokens[0] ?? '')
170
+ const args = tokens.slice(1)
171
+ if (base === 'printenv') return args.every(t => t.startsWith('-'))
172
+ if (base === 'export') return args.length === 0 || args.includes('-p')
173
+ if (base === 'declare' || base === 'typeset') return args.some(t => /^-[A-Za-z]*[xp]/.test(t))
174
+ if (base === 'set') return args.length === 0
175
+ if (base === 'compgen') return args.includes('-e')
176
+ return tokens.some(t => /^\/proc\/[^/]+\/environ$/.test(t.replace(/\/{2,}/g, '/')))
177
+ }
178
+
179
+ /** Uma variavel da lista citada no texto (`echo $ORK_HITL_TOKEN`, `printenv ORK_HITL_TOKEN`). */
180
+ function citaAmbienteProibido(a: Autoridade, texto: string): Achado | null {
181
+ const r = a.ambienteProibido.find(p => texto.includes(p.prefixo))
182
+ return r ? { regra: r.id, porque: r.porque } : null
183
+ }
184
+
185
+ /** Um texto que cita caminho ou variavel da lista, seja ou nao linha de comando (a varredura estatica usa). */
186
+ export function avaliarCitacao(a: Autoridade, texto: unknown): Achado | null {
187
+ return typeof texto === 'string' ? avaliarCaminho(a, texto) ?? citaAmbienteProibido(a, texto) : null
188
+ }
189
+
190
+ function despejo(a: Autoridade): Achado | null {
191
+ const r = a.ambienteProibido[0]
192
+ return r ? { regra: r.id, porque: `${r.porque} (o comando imprime o ambiente inteiro)` } : null
193
+ }
194
+
195
+ /** O resto do argv quando ele chama o CLI do ork (por `ork`, `node .../index.js` ou `npx ork`); `null` se nao chama. */
196
+ function restoDoOrk(tokens: readonly string[]): string[] | null {
197
+ const base = nomeBase(tokens[0] ?? '')
198
+ if (base === 'ork') return tokens.slice(1)
199
+ if (base === 'node' || base === 'nodejs') {
200
+ let i = 1
201
+ while (i < tokens.length && (tokens[i] as string).startsWith('-')) i += ['-r', '--require', '--import', '--loader'].includes(tokens[i] as string) ? 2 : 1
202
+ const script = tokens[i]
203
+ if (script !== undefined && (ENTRADA_DO_ORK.test(script) || script.includes('@orkastery/'))) return tokens.slice(i + 1)
204
+ }
205
+ return null
206
+ }
207
+
208
+ function avaliarOrk(a: Autoridade, resto: readonly string[]): Achado | null {
209
+ const { posicionais, opcoes } = separarArgs(resto)
210
+ if (a.permitidos.some(p => comecaCom(posicionais, p.sequencia))) return null
211
+ for (const r of a.argvProibido) {
212
+ if (!r.sequencia || !comecaCom(posicionais, r.sequencia)) continue
213
+ if (r.opcao !== undefined && !opcoes.has(r.opcao.replace(/^--/, ''))) continue
214
+ return { regra: r.id, porque: r.porque }
215
+ }
216
+ return null
217
+ }
218
+
219
+ function avaliarPrograma(a: Autoridade, tokens: readonly string[]): Achado | null {
220
+ const base = nomeBase(tokens[0] ?? '')
221
+ for (const r of a.argvProibido) {
222
+ if (!r.programa || r.programa !== base) continue
223
+ if (r.subcomando === undefined) return { regra: r.id, porque: r.porque }
224
+ let i = 1
225
+ while (i < tokens.length) {
226
+ const t = tokens[i] as string
227
+ if (base === 'git' && OPCOES_GLOBAIS_DO_GIT.includes(t)) { i += 2; continue }
228
+ if (t.startsWith('-')) { i++; continue }
229
+ break
230
+ }
231
+ if (tokens[i] === r.subcomando) return { regra: r.id, porque: r.porque }
232
+ }
233
+ return null
234
+ }
235
+
236
+ /** Um caminho lido ou citado que esta na lista (`.hermes/.env`), com as barras normalizadas. */
237
+ export function avaliarCaminho(a: Autoridade, caminho: unknown): Achado | null {
238
+ if (typeof caminho !== 'string') return null
239
+ // Grafia normalizada e sem diferenca de maiusculas: num sistema que as ignora, `.HERMES/.env` e o mesmo arquivo.
240
+ const normal = caminho.replace(/\\/g, '/').replace(/\/(\.\/)+/g, '/').replace(/\/{2,}/g, '/').toLowerCase()
241
+ const r = a.caminhosProibidos.find(c => normal.includes(c.trecho.toLowerCase()))
242
+ return r ? { regra: r.id, porque: r.porque } : null
243
+ }
244
+
245
+ /** Uma variavel de ambiente da lista (`ORK_HITL_*`). */
246
+ export function avaliarAmbiente(a: Autoridade, nome: unknown): Achado | null {
247
+ if (typeof nome !== 'string') return null
248
+ const r = a.ambienteProibido.find(p => nome.startsWith(p.prefixo))
249
+ return r ? { regra: r.id, porque: r.porque } : null
250
+ }
251
+
252
+ /** Separa um texto de shell em comandos e palavras: aspas respeitadas; `;`, `&`, `|`, `(`, `)`, crase e `$(` cortam. */
253
+ export function comandosDoTexto(textoDoShell: string): string[][] {
254
+ const comandos: string[][] = []
255
+ let atual: string[] = []
256
+ let palavra = ''
257
+ let temPalavra = false
258
+ let aspa: '"' | "'" | null = null
259
+ const fecharPalavra = () => { if (temPalavra) atual.push(palavra); palavra = ''; temPalavra = false }
260
+ const fecharComando = () => { fecharPalavra(); if (atual.length > 0) comandos.push(atual); atual = [] }
261
+ for (let i = 0; i < textoDoShell.length; i++) {
262
+ const c = textoDoShell[i] as string
263
+ if (aspa !== null) {
264
+ if (c === aspa) aspa = null
265
+ else palavra += c
266
+ continue
267
+ }
268
+ if (c === '"' || c === "'") { aspa = c; temPalavra = true; continue }
269
+ if (c === '\\' && i + 1 < textoDoShell.length) { palavra += textoDoShell[++i]; temPalavra = true; continue }
270
+ if (c === ' ' || c === '\t') { fecharPalavra(); continue }
271
+ if (c === ';' || c === '&' || c === '|' || c === '\n' || c === '(' || c === ')' || c === '`' || c === '{' || c === '}') { fecharComando(); continue }
272
+ if (c === '$' && textoDoShell[i + 1] === '(') { fecharComando(); i++; continue }
273
+ palavra += c
274
+ temPalavra = true
275
+ }
276
+ fecharComando()
277
+ return comandos
278
+ }
279
+
280
+ /** Codigo de interpretador (`node -e`, `python -c`): leitura solta, por palavras, so para pegar o obvio. */
281
+ function avaliarCodigoSolto(a: Autoridade, codigo: string): Achado | null {
282
+ const palavras = codigo.split(/[^A-Za-z0-9_.\/@-]+/).filter(Boolean)
283
+ for (let i = 0; i < palavras.length; i++) {
284
+ const base = nomeBase(palavras[i] as string)
285
+ for (const r of a.argvProibido) {
286
+ if (r.programa && r.programa === base) {
287
+ if (r.subcomando === undefined || palavras.slice(i + 1, i + 6).includes(r.subcomando)) return { regra: r.id, porque: r.porque }
288
+ }
289
+ if (r.sequencia && comecaCom(palavras.slice(i), r.sequencia)) {
290
+ if (r.opcao === undefined || palavras.includes(r.opcao)) return { regra: r.id, porque: r.porque }
291
+ }
292
+ }
293
+ }
294
+ return avaliarCaminho(a, codigo)
295
+ }
296
+
297
+ /** Um texto de shell: cada comando dele passa pela regra de argv. */
298
+ export function avaliarTexto(a: Autoridade, textoDoShell: string, profundidade = 0): Achado | null {
299
+ const direto = avaliarCaminho(a, textoDoShell) ?? citaAmbienteProibido(a, textoDoShell)
300
+ if (direto) return direto
301
+ for (const comando of comandosDoTexto(textoDoShell)) {
302
+ const achado = avaliarArgv(a, comando, undefined, profundidade + 1)
303
+ if (achado) return achado
304
+ }
305
+ return null
306
+ }
307
+
308
+ /**
309
+ * Um argv que um mod quer rodar (`$.process.run`, `$.process.spawn`), com o texto do `stdin` quando o comando e
310
+ * um shell que le o script da entrada. Devolve o primeiro achado ou `null`.
311
+ */
312
+ export function avaliarArgv(a: Autoridade, argv: readonly unknown[], stdin?: unknown, profundidade = 0): Achado | null {
313
+ if (profundidade > MAX_PROFUNDIDADE) return { regra: 'profundidade', porque: 'comando aninhado demais para a guarda conferir' }
314
+ const brutos = argv.map(t => String(t))
315
+ for (const t of brutos) {
316
+ const achado = avaliarCaminho(a, t) ?? citaAmbienteProibido(a, t)
317
+ if (achado) return achado
318
+ }
319
+ if (typeof stdin === 'string') {
320
+ const noStdin = citaAmbienteProibido(a, stdin)
321
+ if (noStdin) return noStdin
322
+ }
323
+ const { tokens, scriptDoEnv, despejo: soEnv } = semEmbrulho(brutos)
324
+ if (scriptDoEnv !== undefined) return avaliarTexto(a, scriptDoEnv, profundidade)
325
+ if (soEnv || despejaOAmbiente(tokens)) return despejo(a)
326
+ if (tokens.length === 0) return null
327
+ const base = nomeBase(tokens[0] as string)
328
+ if (base === 'eval') return avaliarTexto(a, tokens.slice(1).join(' '), profundidade)
329
+ if (SHELLS.has(base)) {
330
+ const i = tokens.findIndex((t, j) => j > 0 && /^-[A-Za-z]*c[A-Za-z]*$/.test(t))
331
+ if (i > 0) return tokens[i + 1] === undefined ? null : avaliarTexto(a, tokens[i + 1] as string, profundidade)
332
+ const temScript = tokens.slice(1).some(t => !t.startsWith('-'))
333
+ if (!temScript && typeof stdin === 'string') return avaliarTexto(a, stdin, profundidade)
334
+ return null
335
+ }
336
+ const inline = INTERPRETADORES[base]
337
+ if (inline) {
338
+ const i = tokens.findIndex((t, j) => j > 0 && inline.includes(t))
339
+ if (i > 0 && tokens[i + 1] !== undefined) return avaliarCodigoSolto(a, tokens[i + 1] as string)
340
+ }
341
+ if (['npx', 'pnpx', 'bunx'].includes(base)) {
342
+ let i = 1
343
+ while (i < tokens.length && (tokens[i] as string).startsWith('-')) i++
344
+ const alvo = tokens[i] ?? ''
345
+ if (alvo === 'ork' || alvo.startsWith('@orkastery/')) return avaliarOrk(a, tokens.slice(i + 1))
346
+ return avaliarArgv(a, tokens.slice(i), undefined, profundidade + 1)
347
+ }
348
+ if (['npm', 'pnpm', 'yarn', 'bun'].includes(base)) {
349
+ // `npm exec`, `pnpm dlx`, `yarn run` e tambem `yarn <bin>` e `pnpm <bin>`, que rodam o binario sem subcomando, e
350
+ // opcao com valor no meio (`yarn --cwd x ork ...`): cada posicao depois do gerenciador e tentada como comando.
351
+ for (let i = 1; i < tokens.length && i <= 12; i++) {
352
+ const alvo = tokens[i] as string
353
+ if (alvo.startsWith('-')) continue
354
+ const achado = alvo === 'ork' || alvo.startsWith('@orkastery/') ? avaliarOrk(a, tokens.slice(i + 1))
355
+ : avaliarArgv(a, tokens.slice(i), undefined, profundidade + 1)
356
+ if (achado) return achado
357
+ }
358
+ return null
359
+ }
360
+ const programa = avaliarPrograma(a, tokens)
361
+ if (programa) return programa
362
+ const resto = restoDoOrk(tokens)
363
+ return resto === null ? null : avaliarOrk(a, resto)
364
+ }
365
+
366
+ type Glob = { exato: string } | { prefixo: string }
367
+
368
+ function glob(padrao: string): Glob {
369
+ if (padrao === '*') return { prefixo: '' }
370
+ return padrao.endsWith('.*') ? { prefixo: padrao.slice(0, -1) } : { exato: padrao }
371
+ }
372
+
373
+ /** O padrao de um `on(...)` (`*`, `fs.*`, `fs.read`) alcanca o evento ou o padrao da lista? */
374
+ export function sobrepoe(padraoDoMod: string, padraoDaLista: string): boolean {
375
+ if (padraoDoMod.startsWith('!')) return false
376
+ if (padraoDoMod.startsWith('telemetry.') || padraoDaLista.startsWith('telemetry.')) return padraoDoMod === padraoDaLista
377
+ const m = glob(padraoDoMod)
378
+ const l = glob(padraoDaLista)
379
+ if ('exato' in m && 'exato' in l) return m.exato === l.exato
380
+ if ('exato' in m) return m.exato.startsWith((l as { prefixo: string }).prefixo)
381
+ if ('exato' in l) return l.exato.startsWith(m.prefixo)
382
+ return m.prefixo.startsWith(l.prefixo) || l.prefixo.startsWith(m.prefixo)
383
+ }
384
+
385
+ /** Ganchos de um mod (os padroes do `on`): proibidos (sem componente) ou reservados a ponte. */
386
+ export function avaliarEventos(a: Autoridade, eventos: readonly string[], ehPonte = false): Achado | null {
387
+ for (const evento of eventos) {
388
+ const proibido = a.ganchosProibidos.find(g => g.componente === undefined && sobrepoe(evento, g.evento))
389
+ if (proibido) return { regra: proibido.id, porque: proibido.porque }
390
+ if (ehPonte) continue
391
+ const reservado = a.reservadosAPonte.find(r => sobrepoe(evento, r.evento))
392
+ if (reservado) return { regra: reservado.id, porque: `gancho reservado a ponte: ${reservado.porque}` }
393
+ }
394
+ return null
395
+ }
396
+
397
+ /** O que o motor escaneou de um modulo no `plugin.register`: ganchos, chamadas e variaveis lidas. */
398
+ export function avaliarUses(a: Autoridade, uses: { events?: readonly string[]; calls?: readonly string[]; env?: { reads?: readonly string[] } },
399
+ ehPonte = false): Achado | null {
400
+ const eventos = avaliarEventos(a, uses.events ?? [], ehPonte)
401
+ if (eventos) return eventos
402
+ for (const c of a.chamadasProibidas) {
403
+ if (c.tipo === undefined && (uses.calls ?? []).includes(c.chamada)) return { regra: c.id, porque: c.porque }
404
+ }
405
+ for (const nome of uses.env?.reads ?? []) {
406
+ const achado = avaliarAmbiente(a, nome)
407
+ if (achado) return achado
408
+ }
409
+ return null
410
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "modules": ["./register.ts"]
3
+ }
@@ -0,0 +1,55 @@
1
+ // O registro do que um mod mostrou ao dono (RM-059, M5 do dono; D8 da thread). O Telegram nao deve
2
+ // repetir o que o dono ja viu no terminal: a ponte guarda o que foi mostrado num formato versionado, no
3
+ // proprio `$.store`, para o nucleo deduplicar depois. Um item por mod, chave e texto. Modulo puro.
4
+ import type { OrkCanal, OrkMostrado, OrkRegistroDeMostrados } from '../types'
5
+
6
+ export const FORMATO_DO_MOSTRADO = 'ork.mod-mostrado/v1'
7
+ export const CHAVE_NO_STORE = 'mostrados'
8
+ export const TETO_DE_MOSTRADOS = 200
9
+ export const TETO_DO_TEXTO_MOSTRADO = 500
10
+ export const CANAIS: readonly OrkCanal[] = ['faixa', 'painel', 'notificacao', 'toast', 'status', 'transcript']
11
+ const NOME_DE_MOD = /^[a-z0-9][a-z0-9-]{0,63}$/
12
+ const ID_DE_THREAD = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/
13
+
14
+ function vazio(): OrkRegistroDeMostrados {
15
+ return { formato: FORMATO_DO_MOSTRADO, itens: [] }
16
+ }
17
+
18
+ /** O registro guardado, ou um vazio se o que estava no store nao e deste formato. */
19
+ export function lerRegistro(guardado: unknown): OrkRegistroDeMostrados {
20
+ const r = guardado as Partial<OrkRegistroDeMostrados> | null | undefined
21
+ if (!r || r.formato !== FORMATO_DO_MOSTRADO || !Array.isArray(r.itens)) return vazio()
22
+ return { formato: FORMATO_DO_MOSTRADO, itens: r.itens.filter(i => i && typeof i.mod === 'string' && typeof i.texto === 'string') }
23
+ }
24
+
25
+ /** Confere o que o mod diz ter mostrado. */
26
+ export function conferirMostrado(m: unknown): OrkMostrado {
27
+ const x = (m ?? {}) as Record<string, unknown>
28
+ if (typeof x.mod !== 'string' || !NOME_DE_MOD.test(x.mod)) throw new Error('ork-ponte: nome de mod invalido')
29
+ if (typeof x.canal !== 'string' || !CANAIS.includes(x.canal as OrkCanal)) throw new Error(`ork-ponte: canal desconhecido: ${String(x.canal)}`)
30
+ if (typeof x.texto !== 'string' || x.texto.trim() === '') throw new Error('ork-ponte: texto mostrado vazio')
31
+ if (x.chave !== undefined && (typeof x.chave !== 'string' || x.chave.length > 200)) throw new Error('ork-ponte: chave invalida')
32
+ if (x.thread !== undefined && (typeof x.thread !== 'string' || !ID_DE_THREAD.test(x.thread))) throw new Error('ork-ponte: id de thread invalido')
33
+ return {
34
+ mod: x.mod, canal: x.canal as OrkCanal, texto: x.texto,
35
+ ...(x.chave !== undefined ? { chave: x.chave as string } : {}),
36
+ ...(x.thread !== undefined ? { thread: x.thread as string } : {}),
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Acrescenta o que foi mostrado: o mesmo mod, chave e texto so ganham `ultimaVez` e mais uma vez; o resto
42
+ * entra no fim. Acima do teto saem os vistos ha mais tempo.
43
+ */
44
+ export function registrarMostrado(guardado: unknown, m: OrkMostrado, agoraIso: string): OrkRegistroDeMostrados {
45
+ const r = lerRegistro(guardado)
46
+ const texto = m.texto.replace(/\s+/g, ' ').trim().slice(0, TETO_DO_TEXTO_MOSTRADO)
47
+ const chave = m.chave ?? null
48
+ const mesmo = (i: OrkRegistroDeMostrados['itens'][number]) => i.mod === m.mod && i.chave === chave && i.texto === texto
49
+ const anterior = r.itens.find(mesmo)
50
+ const resto = r.itens.filter(i => !mesmo(i))
51
+ const item = anterior
52
+ ? { ...anterior, canal: m.canal, ultimaVez: agoraIso, vezes: anterior.vezes + 1 }
53
+ : { mod: m.mod, canal: m.canal, chave, thread: m.thread ?? null, texto, primeiraVez: agoraIso, ultimaVez: agoraIso, vezes: 1 }
54
+ return { formato: FORMATO_DO_MOSTRADO, itens: [...resto, item].slice(-TETO_DE_MOSTRADOS) }
55
+ }
@@ -0,0 +1,157 @@
1
+ // A leitura do nucleo pela ponte (RM-059, T0.3; D3 e D10 da thread). Modulo puro: monta o pedido ao
2
+ // `ork_maestro`, a lista fechada de consultas do CLI e o cache por `stat`. As chamadas ao `$` ficam
3
+ // no register.ts. Dados so pelo nucleo: nada aqui le ou escreve estado em `.orkastery/`.
4
+ import type { OrkAssinatura, OrkConsulta, OrkPedidoDeLeitura, OrkPedidoDeRetrato, OrkRetratoGuardado, OrkSecao } from '../types'
5
+
6
+ export const SERVIDOR = 'orkastery'
7
+ export const FERRAMENTA_DO_RETRATO = 'ork_maestro'
8
+ /** Teto de cada consulta pelo CLI (D10): o mesmo teto total do retrato no nucleo. */
9
+ export const TETO_DO_CLI_MS = 10000
10
+ /** O cache vale enquanto os ledgers cobertos nao mudam, e nunca por mais que isto. */
11
+ export const TETO_DO_CACHE_MS = 60000
12
+ export const TETO_DE_ENTRADAS = 16
13
+ export const TETO_DE_LEDGERS = 50
14
+ /** As secoes do `ork.maestro-snapshot/v1` (core/src/maestro-contract.ts). */
15
+ export const SECOES: readonly OrkSecao[] = ['portfolio', 'demands', 'threads', 'sessions', 'blockers', 'leases',
16
+ 'retries', 'hitl', 'ship', 'master', 'nextActions']
17
+
18
+ const ID_DE_THREAD = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/
19
+
20
+ /** A lista fechada de consultas de leitura (D10): o argv depois de `ork`, e se exige thread. */
21
+ export const CONSULTAS: Readonly<Record<OrkConsulta, { argv: readonly string[]; exigeThread: boolean }>> = {
22
+ 'maestro': { argv: ['maestro', '--json'], exigeThread: false },
23
+ 'thread.status': { argv: ['thread', 'status', '<thread>', '--json'], exigeThread: true },
24
+ 'thread.list': { argv: ['thread', 'list', '--json'], exigeThread: false },
25
+ 'conducao.status': { argv: ['conducao', 'status', '<thread>', '--json'], exigeThread: true },
26
+ 'retry.plan': { argv: ['retry', 'plan', '<thread>', '--json'], exigeThread: true },
27
+ 'retry.list': { argv: ['retry', 'list', '--json'], exigeThread: false },
28
+ 'pulse': { argv: ['pulse', '--json'], exigeThread: false },
29
+ 'orquestracao.status': { argv: ['orquestracao', 'status', '--json'], exigeThread: false },
30
+ 'sessions.hitl': { argv: ['sessions', 'hitl', '--json'], exigeThread: false },
31
+ 'roadmap.status': { argv: ['roadmap', 'status', '--json'], exigeThread: false },
32
+ 'accounts.esgotamentos': { argv: ['accounts', 'esgotamentos', '--json'], exigeThread: false },
33
+ 'decisao.placar': { argv: ['decisao', 'placar', '<thread>', '--json'], exigeThread: true },
34
+ }
35
+
36
+ export class ErroDeLeitura extends Error {}
37
+
38
+ function conferirThread(thread: unknown): string | undefined {
39
+ if (thread === undefined) return undefined
40
+ if (typeof thread !== 'string' || !ID_DE_THREAD.test(thread)) throw new ErroDeLeitura('ork-ponte: id de thread invalido')
41
+ return thread
42
+ }
43
+
44
+ /** Confere o pedido de retrato: secao conhecida, offset inteiro e so junto da secao. */
45
+ export function conferirPedidoDeRetrato(p: OrkPedidoDeRetrato | undefined): OrkPedidoDeRetrato {
46
+ const pedido = p ?? {}
47
+ const thread = conferirThread(pedido.thread)
48
+ if (pedido.secao !== undefined && !SECOES.includes(pedido.secao)) throw new ErroDeLeitura(`ork-ponte: secao desconhecida: ${String(pedido.secao)}`)
49
+ if (pedido.offset !== undefined) {
50
+ if (pedido.secao === undefined) throw new ErroDeLeitura('ork-ponte: offset so vale com secao')
51
+ if (!Number.isInteger(pedido.offset) || pedido.offset < 0 || pedido.offset > 100000) throw new ErroDeLeitura('ork-ponte: offset fora da faixa')
52
+ }
53
+ return { ...(thread ? { thread } : {}), ...(pedido.secao ? { secao: pedido.secao, offset: pedido.offset ?? 0 } : {}) }
54
+ }
55
+
56
+ /** Os argumentos do `ork_maestro`: secao e offset andam juntos, como o nucleo exige. */
57
+ export function argsDoMaestro(p: OrkPedidoDeRetrato): Record<string, unknown> {
58
+ const a: Record<string, unknown> = {}
59
+ if (p.thread) a.threadId = p.thread
60
+ if (p.secao) { a.section = p.secao; a.offset = p.offset ?? 0 }
61
+ return a
62
+ }
63
+
64
+ /** O argv completo de uma consulta da lista fechada; qualquer outra coisa e recusada. */
65
+ export function argvDaConsulta(p: OrkPedidoDeLeitura & { secao?: OrkSecao; offset?: number }): string[] {
66
+ const modelo = Object.prototype.hasOwnProperty.call(CONSULTAS, p.consulta) ? CONSULTAS[p.consulta] : undefined
67
+ if (!modelo) throw new ErroDeLeitura(`ork-ponte: consulta fora da lista fechada: ${String(p.consulta)}`)
68
+ const thread = conferirThread(p.thread)
69
+ if (modelo.exigeThread && !thread) throw new ErroDeLeitura(`ork-ponte: a consulta ${p.consulta} exige thread`)
70
+ const argv = ['ork', ...modelo.argv.map(a => (a === '<thread>' ? (thread as string) : a))]
71
+ if (p.consulta === 'maestro') {
72
+ const r = conferirPedidoDeRetrato({ thread, secao: p.secao, offset: p.offset })
73
+ if (r.thread) argv.push('--thread', r.thread)
74
+ if (r.secao) argv.push('--section', r.secao, '--offset', String(r.offset ?? 0))
75
+ }
76
+ return argv
77
+ }
78
+
79
+ /** O JSON que o `ork_maestro` devolve no primeiro bloco de texto; recusa resultado com erro. */
80
+ export function interpretarMcp(r: { content?: ReadonlyArray<{ type: string; text?: string }>; isError?: boolean }): unknown {
81
+ if (r.isError) throw new ErroDeLeitura('ork-ponte: o ork_maestro respondeu com erro')
82
+ const texto = (r.content ?? []).find(c => c.type === 'text' && typeof c.text === 'string')?.text
83
+ if (texto === undefined) throw new ErroDeLeitura('ork-ponte: o ork_maestro nao devolveu texto')
84
+ return JSON.parse(texto)
85
+ }
86
+
87
+ /** O JSON que o CLI imprime; saida diferente de zero vira erro com o fim do stderr. */
88
+ export function interpretarCli(argv: readonly string[], r: { exitCode: number; stdout: string; stderr: string }): unknown {
89
+ if (r.exitCode !== 0) {
90
+ const fim = r.stderr.trim().split('\n').slice(-2).join(' ').slice(0, 300)
91
+ throw new ErroDeLeitura(`ork-ponte: ${argv.slice(0, 3).join(' ')} saiu ${r.exitCode}${fim ? `: ${fim}` : ''}`)
92
+ }
93
+ return JSON.parse(r.stdout)
94
+ }
95
+
96
+ export type Assinatura = OrkAssinatura
97
+ export type EntradaDoCache = OrkRetratoGuardado
98
+
99
+ export function chaveDoPedido(p: OrkPedidoDeRetrato): string {
100
+ return JSON.stringify([p.thread ?? null, p.secao ?? null, p.offset ?? null])
101
+ }
102
+
103
+ function pai(dir: string): string {
104
+ const sem = dir.replace(/\/+$/, '')
105
+ const i = sem.lastIndexOf('/')
106
+ return i <= 0 ? '/' : sem.slice(0, i)
107
+ }
108
+
109
+ function juntar(pasta: string, nome: string): string {
110
+ return pasta.replace(/\/+$/, '') + '/' + nome
111
+ }
112
+
113
+ /** Os ledgers que o retrato cobre: o da thread pedida, ou os das threads da secao `threads` e a pasta delas. */
114
+ export function caminhosCobertos(raizDoEstado: string | null, p: OrkPedidoDeRetrato, retrato: unknown): string[] {
115
+ if (!raizDoEstado) return []
116
+ const base = juntar(raizDoEstado, '.orkastery/threads')
117
+ if (p.thread) return [juntar(base, `${p.thread}/ledger.jsonl`)]
118
+ const itens = (retrato as { sections?: { threads?: { items?: Array<{ id?: unknown }> } } } | null)?.sections?.threads?.items ?? []
119
+ const ids = [...new Set(itens.map(i => i.id).filter((id): id is string => typeof id === 'string' && ID_DE_THREAD.test(id)))]
120
+ return [base, ...ids.slice(0, TETO_DE_LEDGERS).map(id => juntar(base, `${id}/ledger.jsonl`))]
121
+ }
122
+
123
+ /** O cache serve so com a mesma assinatura de `stat` e dentro do teto de idade. */
124
+ export function cacheValido(e: EntradaDoCache | undefined, atual: Assinatura, agora: number, teto = TETO_DO_CACHE_MS): boolean {
125
+ if (!e || e.caminhos.length === 0) return false
126
+ const idade = agora - e.lidoEm
127
+ if (!(idade >= 0 && idade < teto)) return false
128
+ return JSON.stringify(e.assinatura) === JSON.stringify(atual)
129
+ }
130
+
131
+ /** Guarda a entrada e corta as mais velhas acima do teto de entradas. */
132
+ export function guardar(cache: Readonly<Record<string, EntradaDoCache>> | undefined, chave: string, e: EntradaDoCache,
133
+ teto = TETO_DE_ENTRADAS): Record<string, EntradaDoCache> {
134
+ const todas = Object.entries({ ...(cache ?? {}), [chave]: e }).sort((a, b) => b[1].lidoEm - a[1].lidoEm)
135
+ return Object.fromEntries(todas.slice(0, teto))
136
+ }
137
+
138
+ /**
139
+ * Acha a raiz do estado subindo da raiz da sessao ate a pasta com `orkastery.yaml` e `.orkastery/`
140
+ * ao lado (a worktree da thread fica dentro do checkout principal). `existe` vem do register.ts.
141
+ */
142
+ export async function subirAteOEstado(raizDaSessao: string, existe: (caminho: string) => Promise<boolean>, niveis = 8): Promise<string | null> {
143
+ let dir = raizDaSessao.replace(/\/+$/, '') || '/'
144
+ for (let i = 0; i <= niveis; i++) {
145
+ if (await existe(juntar(dir, 'orkastery.yaml')) && await existe(juntar(dir, '.orkastery'))) return dir
146
+ const acima = pai(dir)
147
+ if (acima === dir) break
148
+ dir = acima
149
+ }
150
+ return null
151
+ }
152
+
153
+ /** A raiz do checkout principal pelo `git rev-parse --git-common-dir`, quando ele termina em `/.git`. */
154
+ export function raizPeloGit(saida: string): string | null {
155
+ const linha = saida.trim().split('\n')[0] ?? ''
156
+ return /^\/.*\/\.git\/?$/.test(linha) ? linha.replace(/\/\.git\/?$/, '') : null
157
+ }