@orkastery/cli 0.5.6 → 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 (385) 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 +118 -4
  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/atualizar-branch.js +245 -0
  105. package/dist/auditoria.js +13 -11
  106. package/dist/auditrun.js +51 -46
  107. package/dist/autorizacao-duravel.js +327 -0
  108. package/dist/board.js +94 -40
  109. package/dist/branch-de-estado.js +16 -8
  110. package/dist/busca-semantica.js +7 -5
  111. package/dist/catalogo.js +2 -1
  112. package/dist/changelog-reconciliacao.js +290 -0
  113. package/dist/check-agil.js +500 -0
  114. package/dist/check-modo.js +41 -0
  115. package/dist/check-paralelo.js +183 -0
  116. package/dist/ci.js +248 -45
  117. package/dist/ciclos.js +8 -6
  118. package/dist/claim-lint.js +12 -8
  119. package/dist/claims-conferir.js +47 -39
  120. package/dist/claims.js +10 -9
  121. package/dist/company-brain-client.js +10 -3
  122. package/dist/company-brain-migration.js +11 -5
  123. package/dist/conducao-batida.js +759 -0
  124. package/dist/conducao-sentinela.js +25 -8
  125. package/dist/conducao.js +84 -29
  126. package/dist/conhecimento-consulta.js +270 -0
  127. package/dist/conhecimento.js +111 -87
  128. package/dist/conta-esgotada.js +127 -0
  129. package/dist/dado-privado.js +816 -0
  130. package/dist/decisao-autonoma.js +5 -4
  131. package/dist/demo.js +14 -12
  132. package/dist/dependencias-da-worktree.js +25 -0
  133. package/dist/divida.js +38 -36
  134. package/dist/docs.js +81 -74
  135. package/dist/doctor-forja.js +345 -0
  136. package/dist/doctor-pulse-cron.js +113 -1
  137. package/dist/doctor.js +164 -6
  138. package/dist/dsn-da-memoria.js +417 -0
  139. package/dist/entrega-por-mod.js +503 -0
  140. package/dist/entrega-pr.js +143 -43
  141. package/dist/esgotamentos.js +13 -11
  142. package/dist/estado-thread.js +21 -18
  143. package/dist/evalrunner.js +35 -32
  144. package/dist/fabrica-batida.js +34 -21
  145. package/dist/fabrica-estado.js +13 -10
  146. package/dist/fix.js +21 -19
  147. package/dist/forja/adaptador.js +6 -0
  148. package/dist/forja/ambiente-de-ci.js +27 -0
  149. package/dist/forja/api.js +256 -0
  150. package/dist/forja/cache.js +89 -0
  151. package/dist/forja/cli.js +60 -0
  152. package/dist/forja/contrato.js +9 -0
  153. package/dist/forja/endereco.js +43 -0
  154. package/dist/forja/estado.js +22 -0
  155. package/dist/forja/executor.js +73 -0
  156. package/dist/forja/github/ci.js +95 -0
  157. package/dist/forja/github/dono-da-rede.js +18 -0
  158. package/dist/forja/github/metadados.js +15 -0
  159. package/dist/forja/github/pedidos.js +200 -0
  160. package/dist/forja/gitlab/ci.js +141 -0
  161. package/dist/forja/gitlab/consultas.js +59 -0
  162. package/dist/forja/gitlab/dono-da-rede.js +43 -0
  163. package/dist/forja/gitlab/metadados.js +15 -0
  164. package/dist/forja/gitlab/pedidos-abertos.js +158 -0
  165. package/dist/forja/gitlab/pedidos.js +141 -0
  166. package/dist/forja/produtor.js +32 -0
  167. package/dist/forja/registro.js +31 -0
  168. package/dist/forja/resolver.js +121 -0
  169. package/dist/forja/texto.js +13 -0
  170. package/dist/forja/versao.js +71 -0
  171. package/dist/forja.js +123 -80
  172. package/dist/handoff.js +23 -13
  173. package/dist/hitl-contract.js +46 -43
  174. package/dist/hitl-curto.js +37 -25
  175. package/dist/hitl-gates.js +13 -11
  176. package/dist/hitl-local-receipt.js +51 -0
  177. package/dist/hitl-local.js +11 -2
  178. package/dist/hitl-lock.js +5 -11
  179. package/dist/hitl-public-receipt.js +11 -0
  180. package/dist/hitl-resumo.js +44 -1
  181. package/dist/hitl-sessions.js +62 -9
  182. package/dist/hitl-texto-livre.js +7 -4
  183. package/dist/hitl.js +34 -31
  184. package/dist/horario.js +16 -0
  185. package/dist/hosts.js +104 -134
  186. package/dist/index.js +848 -548
  187. package/dist/indice-vetorial.js +14 -12
  188. package/dist/init.js +11 -2
  189. package/dist/integracoes-locais.js +2 -0
  190. package/dist/intelligence-graph-cli.js +123 -69
  191. package/dist/intelligence-graph-query.js +37 -22
  192. package/dist/leases.js +19 -18
  193. package/dist/ledger-stats.js +17 -16
  194. package/dist/ledger.js +31 -1
  195. package/dist/licoes.js +12 -11
  196. package/dist/locale-accounts.js +220 -0
  197. package/dist/locale-adapter.js +291 -0
  198. package/dist/locale-ajuda.js +76 -4
  199. package/dist/locale-audit.js +199 -0
  200. package/dist/locale-claims.js +213 -0
  201. package/dist/locale-conducao.js +138 -0
  202. package/dist/locale-conhecimento.js +181 -0
  203. package/dist/locale-decisao.js +205 -0
  204. package/dist/locale-docs.js +230 -0
  205. package/dist/locale-estado.js +101 -0
  206. package/dist/locale-forja.js +290 -0
  207. package/dist/locale-gerados.js +5 -0
  208. package/dist/locale-grafo.js +129 -0
  209. package/dist/locale-master.js +83 -0
  210. package/dist/locale-network.js +524 -0
  211. package/dist/locale-operacao-restante.js +931 -0
  212. package/dist/locale-projetos.js +100 -0
  213. package/dist/locale-prompts.js +194 -0
  214. package/dist/locale-pulse.js +140 -2
  215. package/dist/locale-reservas.js +87 -0
  216. package/dist/locale-retry.js +145 -6
  217. package/dist/locale-security-scan.js +69 -0
  218. package/dist/locale-setup.js +192 -0
  219. package/dist/locale-ship.js +169 -66
  220. package/dist/locale-thread.js +149 -5
  221. package/dist/locale-verify.js +80 -4
  222. package/dist/locale-worktree.js +246 -0
  223. package/dist/locale.js +553 -9
  224. package/dist/maestro-runtime.js +22 -3
  225. package/dist/manifest.js +287 -11
  226. package/dist/maquina-ociosa.js +211 -0
  227. package/dist/maquina.js +10 -5
  228. package/dist/master-nota-local.js +200 -0
  229. package/dist/master-nota.js +10 -2
  230. package/dist/master.js +118 -41
  231. package/dist/mcp-conhecimento.js +52 -0
  232. package/dist/mcp-experiencia.js +26 -5
  233. package/dist/mcp-install.js +73 -8
  234. package/dist/mcp-noctum.js +101 -0
  235. package/dist/mcp-server.js +82 -14
  236. package/dist/mcp-ship-transporte.js +977 -0
  237. package/dist/mcp-ship.js +68 -24
  238. package/dist/mcp-transportes.js +17 -0
  239. package/dist/memoria.js +63 -58
  240. package/dist/modos-migracao.js +8 -6
  241. package/dist/modos.js +17 -18
  242. package/dist/network-roadmap.js +85 -49
  243. package/dist/noctum-aceite.js +789 -0
  244. package/dist/noctum-arco.js +540 -33
  245. package/dist/noctum-capacidade.js +3 -3
  246. package/dist/noctum-changelog.js +399 -0
  247. package/dist/noctum-cli.js +482 -0
  248. package/dist/noctum-coordenacao.js +1225 -0
  249. package/dist/noctum-entrega.js +1169 -0
  250. package/dist/noctum-envelope.js +252 -16
  251. package/dist/noctum-execucao.js +95 -23
  252. package/dist/noctum-fechamento.js +405 -0
  253. package/dist/noctum-fila.js +43 -9
  254. package/dist/noctum-guarda.js +256 -0
  255. package/dist/noctum-integracao.js +507 -0
  256. package/dist/noctum-maquinas.js +383 -0
  257. package/dist/noctum-medicao.js +450 -0
  258. package/dist/noctum-modo.js +362 -0
  259. package/dist/noctum-pergunta-canal.js +96 -0
  260. package/dist/noctum-pergunta.js +115 -0
  261. package/dist/noctum-provas.js +838 -0
  262. package/dist/noctum-reconciliacao.js +591 -0
  263. package/dist/noctum-registro.js +215 -0
  264. package/dist/noctum-relatorio.js +827 -0
  265. package/dist/noctum-resposta.js +207 -0
  266. package/dist/noctum-sessoes-em-voo.js +93 -0
  267. package/dist/noctum-supervisao.js +649 -0
  268. package/dist/noctum-terminal.js +459 -0
  269. package/dist/noctum-transporte.js +655 -0
  270. package/dist/noctum-vigia.js +639 -0
  271. package/dist/ocupacao.js +3 -1
  272. package/dist/onboarding.js +16 -5
  273. package/dist/orkmind.js +39 -23
  274. package/dist/orquestracao.js +31 -35
  275. package/dist/parado-no-condutor.js +146 -124
  276. package/dist/phase.js +53 -27
  277. package/dist/policies.js +33 -0
  278. package/dist/postura-local.js +4 -3
  279. package/dist/procedencia.js +9 -14
  280. package/dist/projeto-alvo.js +59 -20
  281. package/dist/prompts.js +136 -42
  282. package/dist/prova-minima.js +5 -6
  283. package/dist/pulse-cadencia.js +5 -4
  284. package/dist/pulse-consentimento.js +53 -1
  285. package/dist/pulse-delivery.js +143 -27
  286. package/dist/pulse-resposta.js +368 -4
  287. package/dist/pulse.js +136 -3
  288. package/dist/ratelimit.js +12 -4
  289. package/dist/recall.js +14 -13
  290. package/dist/rede-adesao.js +13 -8
  291. package/dist/rede-forja.js +108 -24
  292. package/dist/rede-status.js +63 -45
  293. package/dist/rede.js +86 -46
  294. package/dist/retomada-checkpoint.js +101 -0
  295. package/dist/retry.js +228 -73
  296. package/dist/roadmap-reservas.js +32 -32
  297. package/dist/runtime-ambiente.js +5 -0
  298. package/dist/runtime-context.js +117 -43
  299. package/dist/runtime-escada.js +423 -0
  300. package/dist/runtime-profiles.js +103 -46
  301. package/dist/saida-segura.js +3 -2
  302. package/dist/security-scan/agil.js +355 -0
  303. package/dist/security-scan/cli.js +76 -0
  304. package/dist/security-scan/compare.js +149 -0
  305. package/dist/security-scan/config.js +93 -0
  306. package/dist/security-scan/detect.js +97 -0
  307. package/dist/security-scan/executor.js +188 -0
  308. package/dist/security-scan/guard.js +180 -0
  309. package/dist/security-scan/imagem-de-lint.js +96 -0
  310. package/dist/security-scan/normalize/actionlint.js +12 -0
  311. package/dist/security-scan/normalize/eslint.js +12 -0
  312. package/dist/security-scan/normalize/gitleaks.js +13 -0
  313. package/dist/security-scan/normalize/golangci-lint.js +13 -0
  314. package/dist/security-scan/normalize/govulncheck.js +36 -0
  315. package/dist/security-scan/normalize/index.js +25 -0
  316. package/dist/security-scan/normalize/npm-audit.js +30 -0
  317. package/dist/security-scan/normalize/osv-scanner.js +32 -0
  318. package/dist/security-scan/normalize/oxlint.js +51 -0
  319. package/dist/security-scan/normalize/pip-audit.js +16 -0
  320. package/dist/security-scan/normalize/ruff.js +12 -0
  321. package/dist/security-scan/normalize/semgrep.js +20 -0
  322. package/dist/security-scan/normalize/trivy.js +33 -0
  323. package/dist/security-scan/normalize/util.js +204 -0
  324. package/dist/security-scan/normalize/zizmor.js +19 -0
  325. package/dist/security-scan/report.js +156 -0
  326. package/dist/security-scan/run.js +448 -0
  327. package/dist/security-scan/tools.js +62 -0
  328. package/dist/security-scan/trabalhador.js +74 -0
  329. package/dist/security-scan/types.js +15 -0
  330. package/dist/session-events.js +19 -2
  331. package/dist/session-watcher-claude.js +74 -19
  332. package/dist/session-watcher.js +156 -10
  333. package/dist/sessoes-contas.js +9 -0
  334. package/dist/sessoes-inventario.js +1 -1
  335. package/dist/sessoes.js +25 -23
  336. package/dist/setup.js +59 -58
  337. package/dist/ship-publicar.js +263 -92
  338. package/dist/ship.js +160 -17
  339. package/dist/superficie.js +14 -11
  340. package/dist/thread-close.js +9 -9
  341. package/dist/thread.js +121 -18
  342. package/dist/tokens.js +15 -11
  343. package/dist/trabalho-thread.js +297 -0
  344. package/dist/transporte-do-canal.js +189 -0
  345. package/dist/types.js +12 -1
  346. package/dist/util.js +5 -2
  347. package/dist/varredura-acoes.js +507 -0
  348. package/dist/verify.js +472 -29
  349. package/dist/worktree-local.js +12 -10
  350. package/dist/worktree.js +81 -85
  351. package/eval/casos/check-quality.json +77 -0
  352. package/eval/casos/security-auditor.json +148 -0
  353. package/eval/casos/security-remediation.json +161 -0
  354. package/eval/fixtures/security-scan/README.md +8 -0
  355. package/eval/fixtures/security-scan/go/.github/workflows/ci.yml +12 -0
  356. package/eval/fixtures/security-scan/go/Dockerfile +4 -0
  357. package/eval/fixtures/security-scan/go/README.md +4 -0
  358. package/eval/fixtures/security-scan/go/config.go +4 -0
  359. package/eval/fixtures/security-scan/go/go.mod +5 -0
  360. package/eval/fixtures/security-scan/go/go.sum +2 -0
  361. package/eval/fixtures/security-scan/go/main.go +31 -0
  362. package/eval/fixtures/security-scan/js/.github/workflows/ci.yml +12 -0
  363. package/eval/fixtures/security-scan/js/Dockerfile +3 -0
  364. package/eval/fixtures/security-scan/js/README.md +4 -0
  365. package/eval/fixtures/security-scan/js/package-lock.json +844 -0
  366. package/eval/fixtures/security-scan/js/package.json +10 -0
  367. package/eval/fixtures/security-scan/js/src/config.js +4 -0
  368. package/eval/fixtures/security-scan/js/src/server.js +17 -0
  369. package/eval/fixtures/security-scan/js/src/util.ts +4 -0
  370. package/eval/fixtures/security-scan/js/tsconfig.json +4 -0
  371. package/eval/fixtures/security-scan/python/.github/workflows/ci.yml +12 -0
  372. package/eval/fixtures/security-scan/python/Dockerfile +3 -0
  373. package/eval/fixtures/security-scan/python/README.md +4 -0
  374. package/eval/fixtures/security-scan/python/app/__init__.py +0 -0
  375. package/eval/fixtures/security-scan/python/app/config.py +2 -0
  376. package/eval/fixtures/security-scan/python/app/main.py +19 -0
  377. package/eval/fixtures/security-scan/python/requirements.txt +1 -0
  378. package/package.json +9 -4
  379. package/references/security-checklist.md +3 -3
  380. package/skills/README.md +1 -0
  381. package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
  382. package/skills/phases/check-quality/SKILL.md +16 -0
  383. package/skills/phases/go-implementation/SKILL.md +6 -0
  384. package/skills/phases/security-remediation/SKILL.md +70 -0
  385. package/skills/reviewers/security-auditor/SKILL.md +25 -0
@@ -0,0 +1,977 @@
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.ESQUEMA_DA_FIXACAO = exports.linhasDoKnownHosts = exports.nomeNoKnownHosts = exports.impressaoDaChave = exports.RECUSAS_DO_TRANSPORTE = exports.URL_GITHUB_SSH = void 0;
37
+ exports.recusar = recusar;
38
+ exports.originUnicoMcp = originUnicoMcp;
39
+ exports.enderecoDoOrigin = enderecoDoOrigin;
40
+ exports.lerKeyscan = lerKeyscan;
41
+ exports.lerKnownHosts = lerKnownHosts;
42
+ exports.comandoSsh = comandoSsh;
43
+ exports.alvoDaCredencial = alvoDaCredencial;
44
+ exports.configDeRede = configDeRede;
45
+ exports.protocoloDaConfigDeRede = protocoloDaConfigDeRede;
46
+ exports.urlDaConfigDeRede = urlDaConfigDeRede;
47
+ exports.criarGitdirDoPush = criarGitdirDoPush;
48
+ exports.chaveRecusadaDaConfigDeRede = chaveRecusadaDaConfigDeRede;
49
+ exports.configDaCliDoAmbiente = configDaCliDoAmbiente;
50
+ exports.ambienteDaCliDoTransporte = ambienteDaCliDoTransporte;
51
+ exports.conferirConfigDaCli = conferirConfigDaCli;
52
+ exports.ambienteDoTransporte = ambienteDoTransporte;
53
+ exports.ambienteDoWorkerShip = ambienteDoWorkerShip;
54
+ exports.ambienteDoWorkerDoTransporte = ambienteDoWorkerDoTransporte;
55
+ exports.dirDaFixacao = dirDaFixacao;
56
+ exports.lerCaDoTransporte = lerCa;
57
+ exports.binarioRealDoTransporte = binarioReal;
58
+ exports.gravarFixacao = gravarFixacao;
59
+ exports.conferirFixacao = conferirFixacao;
60
+ exports.lerFixacao = lerFixacao;
61
+ exports.textoDaFixacao = textoDaFixacao;
62
+ exports.fixacaoInstalada = fixacaoInstalada;
63
+ exports.proporTransporte = proporTransporte;
64
+ exports.sondasPadraoDoTransporte = sondasPadraoDoTransporte;
65
+ exports.prepararFixacao = prepararFixacao;
66
+ /**
67
+ * Os transportes do `ork_ship` que a instalacao fixa. `forja-ssh`: a chave do servidor confirmada pela pessoa vai
68
+ * para um known_hosts proprio, e o ssh roda sem config do usuario nem do sistema, com `StrictHostKeyChecking=yes`.
69
+ * `forja-https`: o git zera os helpers de credencial da config e recebe um so, o da CLI da forja com login no host,
70
+ * fixado pela origem; a CA do manifesto entra conferida pelo hash. A fixacao mora fora do projeto, numa pasta 700
71
+ * do usuario por raiz; o servidor MCP a le no startup e o worker do SHIP a confere antes de cada passo. O token
72
+ * nunca passa por aqui: o git o pede ao helper pelo pipe.
73
+ */
74
+ const fs = __importStar(require("node:fs"));
75
+ const os = __importStar(require("node:os"));
76
+ const path = __importStar(require("node:path"));
77
+ const node_crypto_1 = require("node:crypto");
78
+ const node_child_process_1 = require("node:child_process");
79
+ const contrato_1 = require("./forja/contrato");
80
+ const cli_1 = require("./forja/cli");
81
+ const api_1 = require("./forja/api");
82
+ const executor_1 = require("./forja/executor");
83
+ const endereco_1 = require("./forja/endereco");
84
+ const resolver_1 = require("./forja/resolver");
85
+ const mcp_git_1 = require("./mcp-git");
86
+ const rede_forja_1 = require("./rede-forja");
87
+ const locale_1 = require("./locale");
88
+ const mcp_transportes_1 = require("./mcp-transportes");
89
+ /** O unico remoto que o `github-ssh` aceita, como sempre. */
90
+ exports.URL_GITHUB_SSH = /^git@github\.com:[A-Za-z0-9][A-Za-z0-9-]*\/[A-Za-z0-9][A-Za-z0-9._-]*\.git$/;
91
+ /** Cada recusa dos transportes fixados; todas tem a frase no catalogo (`transporteShip.recusa`). */
92
+ exports.RECUSAS_DO_TRANSPORTE = ['urls', 'modo', 'forja', 'ssh.divergente', 'ssh.sem-resposta', 'ssh.revogada',
93
+ 'ssh.nao-confirmada', 'ssh.impressao-divergente', 'ssh.known-hosts-ilegivel', 'ssh.caminho', 'ssh.known-hosts-alterado', 'https.sem-tls',
94
+ 'ssh.credencial-na-url', 'https.credencial-na-url', 'https.divergente', 'sem-cli', 'sem-helper', 'sem-login', 'binario', 'ca', 'ca-alterada',
95
+ 'fixacao-ausente', 'fixacao-insegura', 'fixacao-divergente', 'mudou-no-push', 'cli-config'];
96
+ /** A recusa sai com o codigo `mcp.ship.transport.*` na frente: o servidor e o worker do SHIP so repassam `mcp.*`. */
97
+ function recusar(codigo, detalhe) {
98
+ throw new locale_1.ErroDoCatalogo((m) => m.forja.transporteShip.recusa(`mcp.ship.transport.${codigo}`, detalhe));
99
+ }
100
+ /** A URL efetiva do origin, a mesma no fetch e no push. O push usa todas as URLs configuradas, nunca so a primeira. */
101
+ function originUnicoMcp(raiz) {
102
+ const ler = (push) => {
103
+ const raw = (0, mcp_git_1.gitPassivoMcp)(raiz, ['remote', 'get-url', ...(push ? ['--push'] : []), '--all', 'origin']);
104
+ const urls = raw.split('\n');
105
+ if (urls[urls.length - 1] === '')
106
+ urls.pop();
107
+ if (urls.length !== 1 || !urls[0] || /[\x00-\x1f\x7f]/.test(urls[0]))
108
+ throw Error('mcp.ship.transport.urls: origin exige uma URL efetiva');
109
+ return urls[0];
110
+ };
111
+ const fetch = ler(false), push = ler(true);
112
+ if (fetch !== push)
113
+ throw Error('mcp.ship.transport.pushurl');
114
+ return fetch;
115
+ }
116
+ const sha256 = (b) => (0, node_crypto_1.createHash)('sha256').update(b).digest('hex');
117
+ const CAMINHO_SEGURO = /^[A-Za-z0-9_./+-]+$/;
118
+ const LIMITE_DA_FIXACAO = 65536, LIMITE_DA_CA = 1048576;
119
+ const meuUid = () => process.getuid?.();
120
+ /**
121
+ * Senha ou token na parte de usuario (`user:segredo@`), antes do primeiro `/` do endereco: `ssh://u:s@h/…` ou
122
+ * `u:s@h:caminho`. O usuario sozinho (`git@`) passa.
123
+ */
124
+ function senhaNaUrl(bruta) {
125
+ const resto = bruta.replace(/^[a-z][a-z0-9+.-]*:\/\//i, ''), barra = resto.indexOf('/');
126
+ const autoridade = barra < 0 ? resto : resto.slice(0, barra), arroba = autoridade.lastIndexOf('@');
127
+ return arroba >= 0 && autoridade.slice(0, arroba).includes(':');
128
+ }
129
+ /** O usuario do SSH, quando vem: `ssh://user@h/…` ou `user@h:caminho`. */
130
+ function usuarioDoSsh(bruta) {
131
+ const esquema = /^[a-z][a-z0-9+.-]*:\/\/([^/]*)/i.exec(bruta);
132
+ if (esquema) {
133
+ const i = esquema[1].lastIndexOf('@');
134
+ return i < 0 ? null : esquema[1].slice(0, i);
135
+ }
136
+ const scp = /^([^@:/]*)@/.exec(bruta);
137
+ return scp ? scp[1] : null;
138
+ }
139
+ const USUARIO_SSH = /^[A-Za-z0-9._][A-Za-z0-9._-]*$/;
140
+ /**
141
+ * O que o origin diz ao transporte, de um parse so (`lerEnderecoDoRemoto`). `forja-ssh`: `ssh://` ou a forma scp,
142
+ * porta 22 quando nao vem, usuario e host que nao parecem opcao. `forja-https`: so `https://`, sem usuario nem token
143
+ * na URL; `http://` levaria a credencial em claro. Nos dois, senha ou token na URL recusa. O detalhe de uma recusa
144
+ * nunca leva o origin cru (pode ter token na query, no fragmento ou num userinfo malformado): no maximo o esquema e o
145
+ * host do parse, ou a palavra `origin` quando nao houve parse.
146
+ */
147
+ function enderecoDoOrigin(transporte, url) {
148
+ return lerOrigin(transporte, url).endereco;
149
+ }
150
+ /** O parse do origin e o que ele diz ao transporte; a sonda da instalacao leva os dois, de uma leitura so. */
151
+ function lerOrigin(transporte, url) {
152
+ const bruta = url.trim(), e = (0, endereco_1.lerEnderecoDoRemoto)(bruta);
153
+ const resumo = () => (e ? `${e.protocolo}://${e.host}${e.porta ? `:${e.porta}` : ''}` : 'origin');
154
+ if (transporte === 'forja-https') {
155
+ if (/^http:/i.test(bruta))
156
+ return recusar('https.sem-tls', resumo());
157
+ if (/^https:\/\//i.test(bruta)) {
158
+ let comCredencial = true;
159
+ try {
160
+ const u = new URL(bruta);
161
+ comCredencial = u.username !== '' || u.password !== '';
162
+ }
163
+ catch { /* sem parse: recusa */ }
164
+ if (comCredencial || /^https:\/\/[^/]*@/i.test(bruta))
165
+ return recusar('https.credencial-na-url', 'origin');
166
+ }
167
+ }
168
+ if (transporte === 'forja-ssh' && senhaNaUrl(bruta))
169
+ return recusar('ssh.credencial-na-url', 'origin');
170
+ if (!e)
171
+ return recusar(path.isAbsolute(bruta) ? 'modo' : 'urls', path.isAbsolute(bruta) ? 'bare-local' : 'origin');
172
+ if (transporte === 'forja-ssh') {
173
+ if (e.protocolo === 'https' || e.protocolo === 'http')
174
+ return recusar('modo', 'forja-https');
175
+ if (e.protocolo !== 'ssh' && e.protocolo !== 'scp')
176
+ return recusar('urls', resumo());
177
+ const usuario = usuarioDoSsh(bruta);
178
+ if (usuario !== null && !USUARIO_SSH.test(usuario))
179
+ return recusar('urls', resumo());
180
+ return { endereco: { transporte, host: e.host, porta: e.porta ?? 22 }, remoto: e };
181
+ }
182
+ if (e.protocolo === 'ssh' || e.protocolo === 'scp')
183
+ return recusar('modo', 'forja-ssh');
184
+ if (e.protocolo !== 'https')
185
+ return recusar('urls', resumo());
186
+ const porta = e.porta === 443 ? null : e.porta;
187
+ return { endereco: { transporte, host: e.host, porta, origem: `https://${porta === null ? e.host : `${e.host}:${porta}`}` }, remoto: e };
188
+ }
189
+ const TIPO_DE_CHAVE = /^(?:ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp(?:256|384|521))$/;
190
+ const BASE64 = /^[A-Za-z0-9+/]+={0,2}$/;
191
+ const IMPRESSAO = /^SHA256:[A-Za-z0-9+/]{43}$/;
192
+ const mesma = (a, b) => a.tipo === b.tipo && a.chave === b.chave;
193
+ /** A impressao que o OpenSSH mostra (`ssh-keygen -lf -E sha256`): o sha256 do blob em base64, sem o `=` final. */
194
+ const impressaoDaChave = (chave) => `SHA256:${(0, node_crypto_1.createHash)('sha256').update(Buffer.from(chave, 'base64')).digest('base64').replace(/=+$/, '')}`;
195
+ exports.impressaoDaChave = impressaoDaChave;
196
+ /** O nome do host no known_hosts: `host` na porta 22, `[host]:porta` nas outras. */
197
+ const nomeNoKnownHosts = (host, porta) => (porta === 22 ? host : `[${host}]:${porta}`);
198
+ exports.nomeNoKnownHosts = nomeNoKnownHosts;
199
+ /** As chaves que o `ssh-keyscan` imprimiu para o nome exato, sem repeticao; comentario e tipo fora da lista caem. */
200
+ function lerKeyscan(saida, nome) {
201
+ const chaves = [];
202
+ for (const bruta of saida.split('\n')) {
203
+ const linha = bruta.trim();
204
+ if (!linha || linha.startsWith('#'))
205
+ continue;
206
+ const c = linha.split(/\s+/);
207
+ if (c.length < 3 || c[0] !== nome || !TIPO_DE_CHAVE.test(c[1]) || !BASE64.test(c[2]))
208
+ continue;
209
+ const k = { tipo: c[1], chave: c[2] };
210
+ if (!chaves.some((x) => mesma(x, k)))
211
+ chaves.push(k);
212
+ }
213
+ return chaves;
214
+ }
215
+ /** Um padrao do known_hosts (`*` e `?`) casa o nome inteiro. */
216
+ const casaPadrao = (padrao, nome) => new RegExp(`^${padrao.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.')}$`).test(nome);
217
+ /**
218
+ * O campo de hosts aceita o nome: o hash `|1|sal|hmac` do `HashKnownHosts`, ou a lista separada por virgula com o nome
219
+ * exato. Como no OpenSSH, um `!padrao` que casa o nome tira a linha, mesmo com um positivo que case; curinga positivo
220
+ * nunca confirma.
221
+ */
222
+ function casaNome(campo, nome) {
223
+ if (campo.startsWith('|1|')) {
224
+ const [, , sal, hash] = campo.split('|');
225
+ return !!sal && !!hash && (0, node_crypto_1.createHmac)('sha1', Buffer.from(sal, 'base64')).update(nome).digest('base64') === hash;
226
+ }
227
+ const padroes = campo.split(',');
228
+ if (padroes.some((p) => p.startsWith('!') && casaPadrao(p.slice(1), nome)))
229
+ return false;
230
+ return padroes.includes(nome);
231
+ }
232
+ /**
233
+ * As chaves que o known_hosts da pessoa aceita para o nome, e as que ele revoga. Toda linha `@revoked` conta, qualquer
234
+ * que seja o campo de hosts (`*`, curinga, hash, outro nome): a chave revogada nao entra por nenhum nome.
235
+ * `@cert-authority` cai.
236
+ */
237
+ function lerKnownHosts(conteudo, nome) {
238
+ const aceitas = [], revogadas = [];
239
+ for (const bruta of conteudo.split('\n')) {
240
+ const linha = bruta.trim();
241
+ if (!linha || linha.startsWith('#'))
242
+ continue;
243
+ let c = linha.split(/\s+/), marca = null;
244
+ if (c[0].startsWith('@')) {
245
+ marca = c[0];
246
+ c = c.slice(1);
247
+ }
248
+ if (c.length < 3 || !TIPO_DE_CHAVE.test(c[1]) || !BASE64.test(c[2]))
249
+ continue;
250
+ if (marca === '@revoked')
251
+ revogadas.push({ tipo: c[1], chave: c[2] });
252
+ else if (marca === null && casaNome(c[0], nome))
253
+ aceitas.push({ tipo: c[1], chave: c[2] });
254
+ }
255
+ return { aceitas, revogadas };
256
+ }
257
+ const linhasDoKnownHosts = (chaves, nome) => chaves.map((k) => `${nome} ${k.tipo} ${k.chave}\n`).join('');
258
+ exports.linhasDoKnownHosts = linhasDoKnownHosts;
259
+ /**
260
+ * O ssh do push: `-F /dev/null` tira a config do usuario e a do sistema (nada de ProxyCommand nem HostName trocado);
261
+ * so o known_hosts fixado vale, sem pergunta e sem regravar chave. O caminho vai sem aspas porque o ssh separa
262
+ * `UserKnownHostsFile` por espaco: caminho com espaco ou caractere especial recusa.
263
+ */
264
+ function comandoSsh(knownHosts) {
265
+ if (!path.isAbsolute(knownHosts) || !CAMINHO_SEGURO.test(knownHosts))
266
+ recusar('ssh.caminho', knownHosts);
267
+ return ['ssh', '-F', '/dev/null', '-o', 'BatchMode=yes', '-o', 'StrictHostKeyChecking=yes', '-o', `UserKnownHostsFile=${knownHosts}`,
268
+ '-o', 'GlobalKnownHostsFile=/dev/null', '-o', 'UpdateHostKeys=no', '-o', 'CheckHostIP=no', '-o', 'ConnectTimeout=30'].join(' ');
269
+ }
270
+ // ---------------------------------------------------------------------------
271
+ // HTTPS: a credencial pela CLI logada.
272
+ // ---------------------------------------------------------------------------
273
+ /**
274
+ * A origem da credencial sai da chave do login da CLI (`host[:porta]`, nunca com o caminho base): o helper responde
275
+ * pelo mesmo host em que a CLI tem login. O git casa `credential.<url>` pelo esquema, host e porta; o caminho base
276
+ * vai a parte, para o `useHttpPath`.
277
+ */
278
+ function alvoDaCredencial(f) {
279
+ const chave = (0, api_1.chaveDoLogin)(f);
280
+ if (!/^[a-z0-9.-]+(?::\d+)?$/.test(chave))
281
+ recusar('https.divergente', chave);
282
+ return { origem: `https://${chave}`, caminhoBase: f.caminhoBase };
283
+ }
284
+ // ---------------------------------------------------------------------------
285
+ // A fixacao gravada na instalacao.
286
+ // ---------------------------------------------------------------------------
287
+ exports.ESQUEMA_DA_FIXACAO = 'ork.mcp-ship-transport/v1';
288
+ /**
289
+ * O `-c` que o push e o `ls-remote` do SHIP levam. Nada de token: so o comando do ssh, o helper e a CA.
290
+ *
291
+ * No HTTPS, o git escolhe cada `http.*` pela regra de URL mais especifica (host, depois o caminho mais longo), e o
292
+ * `-c` vem por ultimo: so a URL exata do origin garante que nenhuma regra `http.<url>.*` do `~/.gitconfig` vence.
293
+ * Nela, o TLS fica ligado, o proxy sai (vazio desliga, inclusive o do ambiente), a lista de cabecalhos extras e
294
+ * zerada (vazio limpa o que veio antes), nenhum redirecionamento e seguido e a CA entra pela copia fixada. O certificado de cliente (`sslCert`, `sslKey`) fica com a
295
+ * config da pessoa: nao afrouxa a verificacao do servidor. A credencial casa sem o caminho (`useHttpPath`), pela origem.
296
+ */
297
+ function configDeRede(fix, dir) {
298
+ if (fix.transporte === 'forja-ssh') {
299
+ return ['-c', `core.sshCommand=${comandoSsh(path.join(dir, 'known_hosts'))}`, '-c', 'ssh.variant=ssh', ...travasDoRemoto('ssh')];
300
+ }
301
+ const e = enderecoDoOrigin('forja-https', fix.url);
302
+ if (e.transporte !== 'forja-https' || e.origem !== fix.origem)
303
+ return recusar('https.divergente', fix.origem);
304
+ const url = fix.url;
305
+ return ['-c', 'credential.helper=', '-c', `credential.${fix.origem}.helper=${(0, rede_forja_1.helperDeCredencialDaCli)(fix.binario)}`,
306
+ '-c', `credential.${fix.origem}.useHttpPath=${fix.caminho}`, '-c', 'http.sslVerify=true',
307
+ '-c', `http.${url}.sslVerify=true`, '-c', `http.${url}.proxy=`, '-c', `http.${url}.extraHeader=`,
308
+ '-c', `http.${url}.followRedirects=false`,
309
+ ...(fix.ca ? ['-c', `http.${url}.sslCAInfo=${path.join(dir, ARQUIVO_DA_CA)}`] : []), ...travasDoRemoto('https')];
310
+ }
311
+ /**
312
+ * O que o git le da `.git/config` e da `.git/hooks` a cada comando, inclusive entre a ultima conferencia e o push:
313
+ * nenhum hook roda (`core.hooksPath=/dev/null` vence o da config do projeto e a pasta `hooks`) e so o protocolo do
314
+ * modo vale (`protocol.allow=never`). Um origin trocado para caminho local ou `file://`, com `receivepack` ou
315
+ * `uploadpack` proprios, recusa no protocolo sem rodar nada. O `-c remote.origin.url=` nao serviria: a chave tem
316
+ * varios valores, e o `-c` acrescenta um em vez de trocar.
317
+ */
318
+ function travasDoRemoto(protocolo) {
319
+ return ['-c', 'core.hooksPath=/dev/null', '-c', 'protocol.allow=never', '-c', `protocol.${protocolo}.allow=always`];
320
+ }
321
+ /** O protocolo de uma lista de `configDeRede` ja validada: ssh com o ssh fixado, https sem ele; vazia, nenhum. */
322
+ function protocoloDaConfigDeRede(config) {
323
+ if (!config.length)
324
+ return null;
325
+ return config.some((c) => c.startsWith('core.sshCommand=')) ? 'ssh' : 'https';
326
+ }
327
+ /** A URL exata das regras `http.<url>.*` de uma lista https validada (todas tem a mesma). */
328
+ function urlDaConfigDeRede(config) {
329
+ for (const c of config) {
330
+ const m = /^http\.(https:\/\/[^=\s]+)\.sslVerify=/.exec(c);
331
+ if (m)
332
+ return m[1];
333
+ }
334
+ return null;
335
+ }
336
+ /**
337
+ * O gitdir de onde saem o push e os `ls-remote` do transporte fixado: um bare novo, 700, numa pasta do usuario, sem
338
+ * modelo (nenhum hook copiado), que le os objetos do projeto pelo `objects/info/alternates`. Nada da config, dos hooks,
339
+ * dos `insteadOf` nem das regras de protocolo do projeto entra no git que vai a rede: so o objeto que o ship decidiu.
340
+ */
341
+ function criarGitdirDoPush(raiz, env = process.env) {
342
+ const comum = (0, mcp_git_1.gitPassivoMcp)(raiz, ['rev-parse', '--path-format=absolute', '--git-common-dir']).trim();
343
+ if (!path.isAbsolute(comum) || CONTROLE.test(comum))
344
+ recusar('urls', 'gitdir');
345
+ const objetos = fs.realpathSync(path.join(comum, 'objects'));
346
+ if (!fs.statSync(objetos).isDirectory() || CONTROLE.test(objetos))
347
+ recusar('urls', 'gitdir');
348
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ork-ship-push-'));
349
+ try {
350
+ fs.chmodSync(dir, 0o700);
351
+ conferirAncestrais(path.join(dir, 'x'), 'fixacao-insegura');
352
+ // Fora do projeto: com o cwd nele, o init leria a config local (`core.sharedRepository` afrouxaria os modos).
353
+ const r = (0, node_child_process_1.spawnSync)('git', ['init', '--bare', '--quiet', '--template=', '--', dir], { cwd: '/', encoding: 'utf8', timeout: 30000,
354
+ env: { ...env, GIT_ALLOW_PROTOCOL: '' }, stdio: ['ignore', 'pipe', 'pipe'] });
355
+ if (r.status !== 0)
356
+ recusar('urls', 'gitdir');
357
+ fs.mkdirSync(path.join(dir, 'objects', 'info'), { recursive: true, mode: 0o700 });
358
+ fs.writeFileSync(path.join(dir, 'objects', 'info', 'alternates'), `${objetos}\n`, { mode: 0o600, flag: 'wx' });
359
+ return dir;
360
+ }
361
+ catch (e) {
362
+ fs.rmSync(dir, { recursive: true, force: true });
363
+ throw e;
364
+ }
365
+ }
366
+ const ESCAPE_DE_REGEX = (t) => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
367
+ const MARCA_DO_KNOWN_HOSTS = '/__known_hosts__';
368
+ const FORMA_DO_SSH = new RegExp(`^${ESCAPE_DE_REGEX(comandoSsh(MARCA_DO_KNOWN_HOSTS)).replace(ESCAPE_DE_REGEX(MARCA_DO_KNOWN_HOSTS), '(/[A-Za-z0-9_./+-]+/known_hosts)')}$`);
369
+ const SUFIXO_DO_HELPER = ' auth git-credential';
370
+ const CHAVE_DO_HTTP = /^http\.(https:\/\/[^=\s]+)\.(sslVerify|proxy|extraHeader|followRedirects|sslCAInfo)$/;
371
+ const CHAVE_DA_CREDENCIAL = /^credential\.(https:\/\/[^=\s]+)\.(helper|useHttpPath)$/;
372
+ /** O binario de um helper `!<caminho> auth git-credential` na forma exata de `helperDeCredencialDaCli`; senao, null. */
373
+ function binarioDoHelper(valor) {
374
+ if (!valor.startsWith('!') || !valor.endsWith(SUFIXO_DO_HELPER))
375
+ return null;
376
+ const resto = valor.slice(1, -SUFIXO_DO_HELPER.length);
377
+ let binario;
378
+ if (CAMINHO_SEGURO.test(resto))
379
+ binario = resto;
380
+ else if (resto.length >= 2 && resto.startsWith("'") && resto.endsWith("'") && !resto.slice(1, -1).replace(/'\\''/g, '').includes("'")) {
381
+ binario = resto.slice(1, -1).replace(/'\\''/g, "'");
382
+ }
383
+ else
384
+ return null;
385
+ return path.isAbsolute(binario) && (0, rede_forja_1.helperDeCredencialDaCli)(binario) === valor ? binario : null;
386
+ }
387
+ /**
388
+ * Confere uma lista `-c chave=valor` contra o que `configDeRede` emite: cada chave tem so o valor da sua forma
389
+ * (TLS ligado, proxy e cabecalho vazios, sem redirecionamento, ssh sem config do usuario, helper da CLI, CA `ca.pem`),
390
+ * e toda URL da lista e a mesma. Devolve a chave recusada (nunca o valor) ou null quando a lista toda e valida.
391
+ */
392
+ function chaveRecusadaDaConfigDeRede(config) {
393
+ const origens = new Set();
394
+ const urls = new Set();
395
+ const vistas = new Set();
396
+ if (config.length % 2 !== 0)
397
+ return '';
398
+ for (let i = 0; i < config.length; i += 2) {
399
+ const par = config[i + 1];
400
+ const igual = typeof par === 'string' ? par.indexOf('=') : -1;
401
+ const chave = igual > 0 ? par.slice(0, igual) : '';
402
+ const valor = igual > 0 ? par.slice(igual + 1) : '';
403
+ if (config[i] !== '-c' || chave === '' || /[\0\n\r]/.test(valor))
404
+ return chave;
405
+ let ok = false;
406
+ let m;
407
+ vistas.add(chave);
408
+ if (chave === 'core.sshCommand')
409
+ ok = FORMA_DO_SSH.test(valor);
410
+ else if (chave === 'core.hooksPath')
411
+ ok = valor === '/dev/null';
412
+ else if (chave === 'protocol.allow')
413
+ ok = valor === 'never';
414
+ else if (chave === 'protocol.ssh.allow' || chave === 'protocol.https.allow')
415
+ ok = valor === 'always';
416
+ else if (chave === 'ssh.variant')
417
+ ok = valor === 'ssh';
418
+ else if (chave === 'credential.helper')
419
+ ok = valor === '';
420
+ else if (chave === 'http.sslVerify')
421
+ ok = valor === 'true';
422
+ else if ((m = CHAVE_DA_CREDENCIAL.exec(chave))) {
423
+ origens.add(m[1]);
424
+ ok = m[2] === 'helper' ? binarioDoHelper(valor) !== null : valor === 'true' || valor === 'false';
425
+ }
426
+ else if ((m = CHAVE_DO_HTTP.exec(chave))) {
427
+ urls.add(m[1]);
428
+ const v = m[2];
429
+ ok = v === 'sslVerify' ? valor === 'true'
430
+ : v === 'followRedirects' ? valor === 'false'
431
+ : v === 'sslCAInfo' ? path.isAbsolute(valor) && CAMINHO_SEGURO.test(valor) && path.basename(valor) === ARQUIVO_DA_CA
432
+ : valor === '';
433
+ }
434
+ if (!ok)
435
+ return chave;
436
+ }
437
+ // Lista nao vazia leva as travas, e o protocolo liberado e o do modo: ssh com o ssh fixado, https sem ele.
438
+ if (config.length) {
439
+ const ssh = vistas.has('core.sshCommand') || vistas.has('ssh.variant');
440
+ const http = urls.size > 0 || origens.size > 0 || vistas.has('credential.helper') || vistas.has('http.sslVerify');
441
+ if (!vistas.has('core.hooksPath'))
442
+ return 'core.hooksPath';
443
+ if (!vistas.has('protocol.allow'))
444
+ return 'protocol.allow';
445
+ if (ssh === http)
446
+ return 'core.sshCommand/http';
447
+ if (vistas.has(ssh ? 'protocol.https.allow' : 'protocol.ssh.allow'))
448
+ return ssh ? 'protocol.https.allow' : 'protocol.ssh.allow';
449
+ if (!vistas.has(ssh ? 'protocol.ssh.allow' : 'protocol.https.allow'))
450
+ return ssh ? 'protocol.ssh.allow' : 'protocol.https.allow';
451
+ }
452
+ // Uma URL so, e a origem das credenciais e a do endereco dela.
453
+ if (urls.size > 1 || origens.size > 1)
454
+ return 'http/credential';
455
+ const [u] = [...urls];
456
+ const [o] = [...origens];
457
+ if (u !== undefined) {
458
+ try {
459
+ const e = enderecoDoOrigin('forja-https', u);
460
+ if (e.transporte !== 'forja-https' || (o !== undefined && e.origem !== o))
461
+ return 'http/credential';
462
+ }
463
+ catch {
464
+ return 'http/credential';
465
+ }
466
+ }
467
+ else if (o !== undefined)
468
+ return 'http/credential';
469
+ return null;
470
+ }
471
+ /** A variavel que diz a cada CLI onde esta a config (e o login) dela. */
472
+ const CONFIG_DA_CLI = { gh: 'GH_CONFIG_DIR', glab: 'GLAB_CONFIG_DIR' };
473
+ /** O que cada CLI recebe para nunca perguntar, checar versao ou enviar telemetria no meio do push. */
474
+ const FIXAS_DA_CLI = {
475
+ gh: { GH_NO_UPDATE_NOTIFIER: '1', GH_PROMPT_DISABLED: '1' },
476
+ glab: { GLAB_CHECK_UPDATE: 'false', GLAB_SEND_TELEMETRY: 'false' },
477
+ };
478
+ const TOKENS_DA_CLI = ['GITLAB_TOKEN', 'GLAB_TOKEN', 'GH_TOKEN', 'GITHUB_TOKEN', 'GH_ENTERPRISE_TOKEN', 'GITHUB_ENTERPRISE_TOKEN'];
479
+ /**
480
+ * A pasta de config da CLI no ambiente de quem chama: `null` quando nao vem; pasta absoluta sem caractere de controle
481
+ * quando vem; outra coisa recusa (so o nome da variavel sai na recusa, nunca o valor).
482
+ */
483
+ function configDaCliDoAmbiente(cli, env) {
484
+ const nome = CONFIG_DA_CLI[cli], v = env[nome];
485
+ if (v === undefined || v === '')
486
+ return null;
487
+ if (!absoluto(v))
488
+ return recusar('cli-config', nome);
489
+ return v;
490
+ }
491
+ /**
492
+ * O ambiente da CLI da forja no transporte HTTPS, o mesmo na sonda da instalacao e no worker do push: a pasta de
493
+ * config (so a variavel da propria CLI, quando o ambiente a traz), as variaveis que a mantem calada e, em `apagar`, o
494
+ * que nunca passa: token de qualquer nome conhecido e a variavel de config quando ela nao vem.
495
+ */
496
+ function ambienteDaCliDoTransporte(cli, env) {
497
+ const config = configDaCliDoAmbiente(cli, env), acrescentar = { ...FIXAS_DA_CLI[cli] };
498
+ if (config !== null)
499
+ acrescentar[CONFIG_DA_CLI[cli]] = config;
500
+ const tokens = Object.keys(env).filter((n) => /^(GH|GLAB|GITLAB|GITHUB)_/.test(n) && /TOKEN/i.test(n));
501
+ return { acrescentar, apagar: [...new Set([...TOKENS_DA_CLI, ...tokens]), ...(config === null ? [CONFIG_DA_CLI[cli]] : [])] };
502
+ }
503
+ /** A pasta de config que o worker leva tem de ser a fixada na instalacao (ambas `null` quando nao havia). */
504
+ function conferirConfigDaCli(f, envDoWorker) {
505
+ if (f.transporte !== 'forja-https')
506
+ return;
507
+ const nome = CONFIG_DA_CLI[f.cli], v = envDoWorker[nome];
508
+ if ((v === undefined || v === '' ? null : v) !== f.configDaCli)
509
+ recusar('fixacao-divergente', nome);
510
+ }
511
+ /**
512
+ * O que o transporte fixado acrescenta ao ambiente do worker: nenhuma pergunta (`GIT_ASKPASS` vazio desliga tambem o
513
+ * `core.askPass` e o `SSH_ASKPASS` no git). No HTTPS, o barramento da sessao, para a CLI ler o token do keyring do
514
+ * sistema, a pasta de config da CLI fixada (o login que o helper le) e as variaveis que a mantem calada. Nada de
515
+ * token do ambiente de quem chama.
516
+ */
517
+ function ambienteDoTransporte(t, env = process.env, cli) {
518
+ const extra = { GIT_TERMINAL_PROMPT: '0', GIT_ASKPASS: '', SSH_ASKPASS_REQUIRE: 'never' };
519
+ if (t === 'forja-https') {
520
+ if (!cli)
521
+ throw Error('mcp.ship.transport.cli: a CLI fixada e obrigatoria no HTTPS');
522
+ for (const nome of ['DBUS_SESSION_BUS_ADDRESS', 'XDG_RUNTIME_DIR']) {
523
+ const v = env[nome];
524
+ if (v)
525
+ extra[nome] = v;
526
+ }
527
+ Object.assign(extra, ambienteDaCliDoTransporte(cli, env).acrescentar);
528
+ }
529
+ return extra;
530
+ }
531
+ /**
532
+ * O ambiente do worker do SHIP, por lista fechada: nada de `GIT_SSH*`, `GIT_ASKPASS`, `GIT_SSL_NO_VERIFY` ou
533
+ * `GIT_CONFIG_*` de quem iniciou o servidor chega ao git do push.
534
+ */
535
+ function ambienteDoWorkerShip(origem = process.env) {
536
+ return { HOME: origem.HOME, USER: origem.USER, LOGNAME: origem.LOGNAME, XDG_CONFIG_HOME: origem.XDG_CONFIG_HOME,
537
+ SSH_AUTH_SOCK: origem.SSH_AUTH_SOCK, PATH: '/usr/bin:/bin', LANG: 'C.UTF-8', TMPDIR: '/tmp' };
538
+ }
539
+ /**
540
+ * O ambiente do worker para cada transporte: a lista fechada de `ambienteDoWorkerShip` e, nos transportes fixados,
541
+ * o que `ambienteDoTransporte` acrescenta (nenhuma pergunta; no HTTPS, o barramento da sessao). Nada mais.
542
+ */
543
+ function ambienteDoWorkerDoTransporte(transporte, origem = process.env, cli) {
544
+ const base = ambienteDoWorkerShip(origem);
545
+ return (0, mcp_transportes_1.ehTransporteFixado)(transporte) ? { ...base, ...ambienteDoTransporte(transporte, origem, cli) } : base;
546
+ }
547
+ /** Uma pasta por raiz, na config do usuario: fora do projeto, longe do que o agente escreve. */
548
+ function dirDaFixacao(raiz, env = process.env) {
549
+ const xdg = env.XDG_CONFIG_HOME;
550
+ const base = xdg && path.isAbsolute(xdg) ? xdg : path.join(env.HOME || os.homedir(), '.config');
551
+ return path.join(base, 'orkastery', 'mcp-ship', sha256(raiz).slice(0, 32));
552
+ }
553
+ const ARQUIVO_DA_CA = 'ca.pem';
554
+ const CONTROLE = /[\x00-\x1f\x7f]/;
555
+ /**
556
+ * Cada pasta acima de `alvo`, ate a raiz, como o `secure_path` do OpenSSH: pasta de verdade (sem link), do usuario
557
+ * ou do root, sem escrita de grupo e de outros; a do root com sticky (`/tmp`) passa, porque ali ninguem troca o que
558
+ * e de outro dono.
559
+ */
560
+ function conferirAncestrais(alvo, codigo) {
561
+ for (let atual = path.dirname(alvo);; atual = path.dirname(atual)) {
562
+ const s = fs.lstatSync(atual, { throwIfNoEntry: false });
563
+ const dono = !!s && (s.uid === meuUid() || s.uid === 0);
564
+ const escrita = !!s && (s.mode & 0o022) !== 0 && !(s.uid === 0 && (s.mode & 0o1000) !== 0);
565
+ if (!s || !s.isDirectory() || !dono || escrita)
566
+ recusar(codigo, atual);
567
+ if (atual === path.dirname(atual))
568
+ return;
569
+ }
570
+ }
571
+ /** A pasta da fixacao: diretorio de verdade (sem link), do usuario, sem acesso de grupo e de outros; e toda pasta acima. */
572
+ function conferirPasta(dir) {
573
+ const st = fs.lstatSync(dir, { throwIfNoEntry: false });
574
+ if (!st)
575
+ return recusar('fixacao-ausente', dir);
576
+ if (!st.isDirectory() || st.uid !== meuUid() || (st.mode & 0o077) !== 0)
577
+ recusar('fixacao-insegura', dir);
578
+ conferirAncestrais(dir, 'fixacao-insegura');
579
+ }
580
+ /** Arquivo da fixacao: regular, do usuario, sem acesso de grupo e de outros, um vinculo so, sem seguir link. */
581
+ function lerDaFixacao(arquivo, limite = LIMITE_DA_FIXACAO) {
582
+ let fd;
583
+ try {
584
+ fd = fs.openSync(arquivo, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
585
+ }
586
+ catch (e) {
587
+ return recusar(e.code === 'ENOENT' ? 'fixacao-ausente' : 'fixacao-insegura', arquivo);
588
+ }
589
+ try {
590
+ const st = fs.fstatSync(fd);
591
+ if (!st.isFile() || st.uid !== meuUid() || (st.mode & 0o077) !== 0 || st.nlink !== 1 || st.size > limite) {
592
+ recusar('fixacao-insegura', arquivo);
593
+ }
594
+ return fs.readFileSync(fd);
595
+ }
596
+ finally {
597
+ fs.closeSync(fd);
598
+ }
599
+ }
600
+ const ehPem = (bytes) => bytes.toString('latin1').includes('-----BEGIN CERTIFICATE-----');
601
+ /** Le ate `limite + 1` bytes do descritor: o que passa do teto nunca entra inteiro na memoria. */
602
+ function lerComTeto(fd, limite) {
603
+ const partes = [];
604
+ let total = 0;
605
+ for (;;) {
606
+ const b = Buffer.alloc(Math.min(65536, limite + 1 - total));
607
+ const n = fs.readSync(fd, b, 0, b.length, null);
608
+ if (n === 0)
609
+ break;
610
+ partes.push(b.subarray(0, n));
611
+ total += n;
612
+ if (total > limite)
613
+ break;
614
+ }
615
+ return Buffer.concat(partes, total);
616
+ }
617
+ /**
618
+ * A CA do manifesto, lida uma vez na instalacao: caminho absoluto, um PEM, ate 1 MiB, arquivo regular do usuario ou
619
+ * do root sem escrita de grupo ou de outros, e toda pasta do caminho real com a mesma regra. O caminho real e aberto
620
+ * sem seguir link e conferido pelo descritor aberto, o mesmo que se le. Os bytes vao para a copia da fixacao; o
621
+ * arquivo de origem nunca mais e lido.
622
+ */
623
+ function lerCa(arquivo) {
624
+ if (!path.isAbsolute(arquivo) || CONTROLE.test(arquivo))
625
+ return recusar('ca', arquivo);
626
+ let bytes;
627
+ try {
628
+ const real = fs.realpathSync(arquivo);
629
+ conferirAncestrais(real, 'ca');
630
+ const fd = fs.openSync(real, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
631
+ try {
632
+ const st = fs.fstatSync(fd);
633
+ if (!st.isFile() || st.size > LIMITE_DA_CA || (st.mode & 0o022) !== 0 || (st.uid !== meuUid() && st.uid !== 0))
634
+ recusar('ca', arquivo);
635
+ bytes = lerComTeto(fd, LIMITE_DA_CA);
636
+ }
637
+ finally {
638
+ fs.closeSync(fd);
639
+ }
640
+ }
641
+ catch (e) {
642
+ if (e instanceof locale_1.ErroDoCatalogo)
643
+ throw e;
644
+ return recusar('ca', arquivo);
645
+ }
646
+ if (bytes.length > LIMITE_DA_CA || !ehPem(bytes))
647
+ recusar('ca', arquivo);
648
+ return bytes;
649
+ }
650
+ /**
651
+ * A CLI do helper, pelo caminho real: arquivo executavel do usuario ou do root, sem escrita de grupo ou de outros,
652
+ * e toda pasta do caminho real com a mesma regra. Devolve o caminho real, que e o que se fixa e o que o git roda.
653
+ */
654
+ function binarioReal(binario) {
655
+ try {
656
+ if (!path.isAbsolute(binario) || CONTROLE.test(binario))
657
+ recusar('binario', binario);
658
+ const real = fs.realpathSync(binario), st = fs.lstatSync(real);
659
+ const dono = st.uid === meuUid() || st.uid === 0;
660
+ if (!st.isFile() || !dono || (st.mode & 0o022) !== 0 || (st.mode & 0o111) === 0 || CONTROLE.test(real))
661
+ recusar('binario', binario);
662
+ conferirAncestrais(real, 'binario');
663
+ return real;
664
+ }
665
+ catch (e) {
666
+ if (e instanceof locale_1.ErroDoCatalogo)
667
+ throw e;
668
+ return recusar('binario', binario);
669
+ }
670
+ }
671
+ function gravarAtomico(arquivo, conteudo) {
672
+ const temp = `${arquivo}.${(0, node_crypto_1.randomUUID)()}.tmp`;
673
+ try {
674
+ fs.writeFileSync(temp, conteudo, { flag: 'wx', mode: 0o600 });
675
+ fs.renameSync(temp, arquivo);
676
+ }
677
+ finally {
678
+ fs.rmSync(temp, { force: true });
679
+ }
680
+ }
681
+ /**
682
+ * Grava a fixacao (pasta 700, arquivos 600, troca atomica). O known_hosts e a copia da CA so ficam quando a fixacao
683
+ * os pede; o que sobrou de outra instalacao sai. Os bytes da CA tem de ter o hash fixado.
684
+ */
685
+ function gravarFixacao(dir, p) {
686
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
687
+ conferirPasta(dir);
688
+ if (p.knownHosts !== undefined)
689
+ gravarAtomico(path.join(dir, 'known_hosts'), p.knownHosts);
690
+ else
691
+ fs.rmSync(path.join(dir, 'known_hosts'), { force: true });
692
+ const ca = p.fixacao.transporte === 'forja-https' ? p.fixacao.ca : null;
693
+ if (ca) {
694
+ if (!p.ca || sha256(p.ca) !== ca.sha256 || !ehPem(p.ca) || p.ca.length > LIMITE_DA_CA)
695
+ recusar('ca', ca.arquivo);
696
+ gravarAtomico(path.join(dir, ARQUIVO_DA_CA), p.ca);
697
+ }
698
+ else
699
+ fs.rmSync(path.join(dir, ARQUIVO_DA_CA), { force: true });
700
+ gravarAtomico(path.join(dir, 'transporte.json'), `${JSON.stringify(p.fixacao, null, 2)}\n`);
701
+ }
702
+ const texto = (v) => typeof v === 'string' && v.length > 0 && v.length <= 4096 && !CONTROLE.test(v);
703
+ const absoluto = (v) => texto(v) && path.isAbsolute(v);
704
+ const hex64 = (v) => typeof v === 'string' && /^[a-f0-9]{64}$/.test(v);
705
+ function formatoValido(v) {
706
+ const o = v;
707
+ if (!o || typeof o !== 'object' || o.schema !== exports.ESQUEMA_DA_FIXACAO || !texto(o.raiz) || !path.isAbsolute(o.raiz) || !texto(o.url))
708
+ return false;
709
+ if (o.transporte === 'forja-ssh') {
710
+ const chaves = o.chaves;
711
+ return texto(o.host) && endereco_1.HOST_DO_REMOTO.test(o.host) && Number.isInteger(o.porta) && o.porta >= 1 && o.porta <= 65535
712
+ && Array.isArray(chaves) && chaves.length > 0 && hex64(o.knownHostsSha256)
713
+ && chaves.every((k) => !!k && typeof k === 'object' && TIPO_DE_CHAVE.test(String(k.tipo))
714
+ && IMPRESSAO.test(String(k.impressao)));
715
+ }
716
+ if (o.transporte !== 'forja-https')
717
+ return false;
718
+ const ca = o.ca;
719
+ return contrato_1.TIPOS_DE_FORJA.includes(o.tipo) && typeof o.origem === 'string'
720
+ && /^https:\/\/[a-z0-9.-]+(?::\d+)?$/.test(o.origem) && typeof o.caminho === 'boolean' && (o.cli === 'gh' || o.cli === 'glab')
721
+ && absoluto(o.binario) && texto(o.versaoCli) && (o.configDaCli === null || absoluto(o.configDaCli)) && (ca === null || (typeof ca === 'object' && absoluto(ca.arquivo) && hex64(ca.sha256)));
722
+ }
723
+ /**
724
+ * Confere o que o push vai usar: o known_hosts pelo hash; a CLI pelo caminho real, pelo dono e pela escrita dela e de
725
+ * cada pasta acima; a copia da CA pelo hash, lida como os outros arquivos da fixacao (sem seguir link).
726
+ */
727
+ function conferirFixacao(f, dir) {
728
+ conferirPasta(dir);
729
+ if (f.transporte === 'forja-ssh') {
730
+ const arquivo = path.join(dir, 'known_hosts');
731
+ if (sha256(lerDaFixacao(arquivo)) !== f.knownHostsSha256)
732
+ recusar('ssh.known-hosts-alterado', arquivo);
733
+ return;
734
+ }
735
+ if (binarioReal(f.binario) !== f.binario)
736
+ recusar('binario', f.binario);
737
+ const copia = path.join(dir, ARQUIVO_DA_CA);
738
+ if (f.ca && sha256(lerDaFixacao(copia, LIMITE_DA_CA)) !== f.ca.sha256)
739
+ recusar('ca-alterada', copia);
740
+ }
741
+ /** A fixacao desta raiz, deste transporte e deste origin, conferida; outra coisa recusa antes de qualquer rede. */
742
+ function lerFixacao(raiz, transporte, url, env = process.env) {
743
+ const dir = dirDaFixacao(raiz, env);
744
+ if (!fs.lstatSync(dir, { throwIfNoEntry: false }))
745
+ recusar('fixacao-ausente', `ork mcp install --project ${raiz} --ship-transport ${transporte}`);
746
+ conferirPasta(dir);
747
+ const arquivo = path.join(dir, 'transporte.json');
748
+ let fixacao;
749
+ try {
750
+ fixacao = JSON.parse(lerDaFixacao(arquivo).toString('utf8'));
751
+ }
752
+ catch (e) {
753
+ if (e instanceof locale_1.ErroDoCatalogo)
754
+ throw e;
755
+ return recusar('fixacao-insegura', arquivo);
756
+ }
757
+ if (!formatoValido(fixacao))
758
+ return recusar('fixacao-insegura', arquivo);
759
+ if (fixacao.raiz !== raiz || fixacao.transporte !== transporte)
760
+ recusar('fixacao-divergente', `${fixacao.transporte} != ${transporte}`);
761
+ if (fixacao.url !== url)
762
+ recusar('fixacao-divergente', 'origin');
763
+ // O origin fixado tem de levar ao mesmo host e porta (SSH) ou a mesma origem (HTTPS) da chave e do helper fixados.
764
+ const e = enderecoDoOrigin(transporte, url);
765
+ if (fixacao.transporte === 'forja-ssh' && e.transporte === 'forja-ssh' && (e.host !== fixacao.host || e.porta !== fixacao.porta)) {
766
+ recusar('ssh.divergente', `${(0, exports.nomeNoKnownHosts)(e.host, e.porta)} != ${(0, exports.nomeNoKnownHosts)(fixacao.host, fixacao.porta)}`);
767
+ }
768
+ if (fixacao.transporte === 'forja-https' && e.transporte === 'forja-https' && e.origem !== fixacao.origem) {
769
+ recusar('https.divergente', `${e.origem} != ${fixacao.origem}`);
770
+ }
771
+ conferirFixacao(fixacao, dir);
772
+ return { fixacao, dir };
773
+ }
774
+ /** A linha que a instalacao mostra para a pessoa; na simulacao, o que seria fixado. */
775
+ function textoDaFixacao(f, dir, simulacao = false) {
776
+ const t = (0, locale_1.msg)().forja.transporteShip;
777
+ const ssh = simulacao ? t.fixariaSsh : t.fixadoSsh, https = simulacao ? t.fixariaHttps : t.fixadoHttps;
778
+ return f.transporte === 'forja-ssh'
779
+ ? ssh((0, exports.nomeNoKnownHosts)(f.host, f.porta), f.chaves.map((k) => `${k.tipo} ${k.impressao}`).join(', '), path.join(dir, 'known_hosts'))
780
+ : https(f.origem, `${f.cli} ${f.versaoCli}`, f.ca?.arquivo ?? null);
781
+ }
782
+ /**
783
+ * A fixacao que esta raiz ja tem, para uma instalacao nao trocar a de outro host do projeto. `null` sem fixacao, ou
784
+ * com uma que nenhum servidor aceitaria (JSON quebrado, formato desconhecido, outra raiz): essa a instalacao refaz.
785
+ * Pasta ou arquivo inseguro (link, dono, modo, segundo vinculo) recusa: sobrescrever apagaria o sinal de que alguem
786
+ * mexeu ali, e a gravacao recusaria a pasta do mesmo jeito.
787
+ */
788
+ function fixacaoInstalada(raiz, env = process.env) {
789
+ const dir = dirDaFixacao(raiz, env);
790
+ if (!fs.lstatSync(dir, { throwIfNoEntry: false }))
791
+ return null;
792
+ conferirPasta(dir);
793
+ const arquivo = path.join(dir, 'transporte.json');
794
+ if (!fs.lstatSync(arquivo, { throwIfNoEntry: false }))
795
+ return null;
796
+ const bytes = lerDaFixacao(arquivo);
797
+ let v;
798
+ try {
799
+ v = JSON.parse(bytes.toString('utf8'));
800
+ }
801
+ catch {
802
+ return null;
803
+ }
804
+ return formatoValido(v) && v.raiz === raiz ? v : null;
805
+ }
806
+ // ---------------------------------------------------------------------------
807
+ // A sonda da instalacao.
808
+ // ---------------------------------------------------------------------------
809
+ /** O transporte que o origin pede, sem rede: bare local, o SSH do GitHub de sempre, SSH de outra forja ou HTTPS. */
810
+ function proporTransporte(url) {
811
+ const t = url.trim();
812
+ if (path.isAbsolute(t))
813
+ return 'bare-local';
814
+ if (exports.URL_GITHUB_SSH.test(t))
815
+ return 'github-ssh';
816
+ const e = (0, endereco_1.lerEnderecoDoRemoto)(t);
817
+ if (!e)
818
+ return null;
819
+ return e.protocolo === 'ssh' || e.protocolo === 'scp' ? 'forja-ssh' : e.protocolo === 'https' ? 'forja-https' : null;
820
+ }
821
+ /** O `ssh-keyscan` desiste de cada host em 10 s; o processo inteiro, em 30 s. A CLI responde a versao e a ajuda em 10 s. */
822
+ const PRAZO_DO_KEYSCAN_MS = 30_000, PRAZO_DA_CLI_MS = 10_000;
823
+ const LIMITE_DO_KNOWN_HOSTS = 4 * 1048576;
824
+ function sondasPadraoDoTransporte(env = process.env) {
825
+ const home = env.HOME || os.homedir();
826
+ return {
827
+ rodar: (binario, args, prazoMs) => {
828
+ const r = (0, node_child_process_1.spawnSync)(binario, [...args], { encoding: 'utf8', timeout: prazoMs, killSignal: 'SIGKILL', maxBuffer: 1024 * 1024,
829
+ stdio: ['ignore', 'pipe', 'pipe'],
830
+ env: { PATH: '/usr/bin:/bin', HOME: home, LANG: 'C', LC_ALL: 'C', NO_COLOR: '1', GH_NO_UPDATE_NOTIFIER: '1',
831
+ GH_PROMPT_DISABLED: '1', GLAB_CHECK_UPDATE: 'false', GLAB_SEND_TELEMETRY: 'false' } });
832
+ return { status: r.status, stdout: (r.stdout ?? '').toString(), stderr: (r.stderr ?? '').toString() };
833
+ },
834
+ env,
835
+ acharBinario: (cmd) => (0, rede_forja_1.acharBinario)(cmd, { env }),
836
+ knownHostsDoUsuario: path.join(home, '.ssh', 'known_hosts'),
837
+ knownHostsDoSistema: ['/etc/ssh/ssh_known_hosts', '/etc/ssh/ssh_known_hosts2'],
838
+ };
839
+ }
840
+ /**
841
+ * Sonda o origin no modo pedido e devolve a fixacao, sem gravar nada; o que nao fecha recusa com o motivo. Nesta
842
+ * ordem: o modo serve ao origin (um parse so dele), a forja resolve, e o SSH ou o HTTPS confere o resto.
843
+ */
844
+ function prepararFixacao(e) {
845
+ const { endereco, remoto } = lerOrigin(e.transporte, e.url);
846
+ const r = (0, resolver_1.resolverForja)({ endereco: remoto, manifesto: e.manifesto, sondas: e.sondas.sondas });
847
+ // O detalhe do resolver pode citar o caminho do projeto: sai so o host, como em toda recusa do origin.
848
+ if (!r.ok)
849
+ return recusar('forja', (0, locale_1.msg)().forja.motivo({ codigo: r.erro.codigo, detalhe: r.erro.detalhe.split(remoto.caminho).join('<caminho>') }, null));
850
+ return endereco.transporte === 'forja-ssh' ? fixarSsh(e, endereco, r.valor) : fixarHttps(e, endereco, r.valor);
851
+ }
852
+ /** O conteudo de um known_hosts: `null` quando nao existe; `undefined` quando existe e nao se le (nao regular, grande demais). */
853
+ function lerArquivoDeKnownHosts(arquivo) {
854
+ try {
855
+ const fd = fs.openSync(arquivo, fs.constants.O_RDONLY | fs.constants.O_NONBLOCK);
856
+ try {
857
+ if (!fs.fstatSync(fd).isFile())
858
+ return undefined;
859
+ const conteudo = lerComTeto(fd, LIMITE_DO_KNOWN_HOSTS);
860
+ return conteudo.length > LIMITE_DO_KNOWN_HOSTS ? undefined : conteudo.toString('utf8');
861
+ }
862
+ finally {
863
+ fs.closeSync(fd);
864
+ }
865
+ }
866
+ catch (e) {
867
+ return e.code === 'ENOENT' ? null : undefined;
868
+ }
869
+ }
870
+ /**
871
+ * O known_hosts da pessoa para o nome: o que ele aceita e o que ele revoga. Sem arquivo, nada confirma (so a
872
+ * impressao dita); arquivo que existe e nao se le recusa, porque esconderia uma revogacao. Os known_hosts do sistema
873
+ * so acrescentam revogacoes, nunca confirmam: ausente ou ilegivel, nao e a confirmacao da pessoa e fica de fora.
874
+ */
875
+ function conhecidasDoUsuario(arquivo, globais, nome) {
876
+ let conhecidas = { aceitas: [], revogadas: [] };
877
+ if (arquivo) {
878
+ const conteudo = lerArquivoDeKnownHosts(arquivo);
879
+ if (conteudo === undefined)
880
+ recusar('ssh.known-hosts-ilegivel', arquivo);
881
+ if (conteudo)
882
+ conhecidas = lerKnownHosts(conteudo, nome);
883
+ }
884
+ for (const global of globais) {
885
+ const conteudo = lerArquivoDeKnownHosts(global);
886
+ if (conteudo)
887
+ conhecidas.revogadas.push(...lerKnownHosts(conteudo, nome).revogadas);
888
+ }
889
+ return conhecidas;
890
+ }
891
+ const impressoes = (ks) => ks.map((k) => `${k.tipo} ${(0, exports.impressaoDaChave)(k.chave)}`).join(', ');
892
+ /**
893
+ * A chave do servidor so entra confirmada: o known_hosts da pessoa a aceita, ou a impressao dita (`--ssh-fingerprint`)
894
+ * e a dela. Nunca a primeira que aparece. Revogada no known_hosts, trocada (o known_hosts guarda outra do mesmo tipo)
895
+ * ou impressao que o servidor nao apresentou recusam.
896
+ */
897
+ function fixarSsh(e, origin, f) {
898
+ const { host, porta } = origin;
899
+ const alvo = { host: f.sshHost ?? f.host, porta: f.sshPorta ?? 22 };
900
+ if (host !== alvo.host || porta !== alvo.porta)
901
+ recusar('ssh.divergente', `${host}:${porta} != ${alvo.host}:${alvo.porta}`);
902
+ // O host vem do parse e nunca comeca por `-`; mesmo assim vai depois do `--`, para nunca virar opcao.
903
+ if (!endereco_1.HOST_DO_REMOTO.test(host) || !Number.isInteger(porta) || porta < 1 || porta > 65535)
904
+ recusar('urls', 'origin');
905
+ const nome = (0, exports.nomeNoKnownHosts)(host, porta);
906
+ const scan = e.sondas.rodar('ssh-keyscan', ['-T', '10', '-p', String(porta), '-t', 'ed25519,ecdsa,rsa', '--', host], PRAZO_DO_KEYSCAN_MS);
907
+ const doServidor = lerKeyscan(scan.stdout, nome);
908
+ if (!doServidor.length)
909
+ recusar('ssh.sem-resposta', nome);
910
+ const conhecidas = conhecidasDoUsuario(e.sondas.knownHostsDoUsuario, e.sondas.knownHostsDoSistema ?? [], nome);
911
+ if (doServidor.some((k) => conhecidas.revogadas.some((r) => mesma(k, r))))
912
+ recusar('ssh.revogada', nome);
913
+ const trocadas = doServidor.filter((k) => conhecidas.aceitas.some((u) => u.tipo === k.tipo) && !conhecidas.aceitas.some((u) => mesma(k, u)));
914
+ if (trocadas.length) {
915
+ const guardadas = conhecidas.aceitas.filter((u) => trocadas.some((t) => t.tipo === u.tipo));
916
+ recusar('ssh.impressao-divergente', `known_hosts ${nome}: ${impressoes(guardadas)} != ${impressoes(trocadas)}`);
917
+ }
918
+ const lista = impressoes(doServidor);
919
+ let dita = null;
920
+ if (e.impressao !== undefined) {
921
+ dita = e.impressao.trim().replace(/=+$/, '');
922
+ const valida = IMPRESSAO.test(dita);
923
+ if (!valida || !doServidor.some((k) => (0, exports.impressaoDaChave)(k.chave) === dita)) {
924
+ recusar('ssh.impressao-divergente', `${valida ? dita : '--ssh-fingerprint'}; ${nome}: ${lista}`);
925
+ }
926
+ }
927
+ const confirmadas = doServidor.filter((k) => conhecidas.aceitas.some((u) => mesma(k, u)) || (0, exports.impressaoDaChave)(k.chave) === dita);
928
+ if (!confirmadas.length)
929
+ recusar('ssh.nao-confirmada', `${nome}: ${lista}`);
930
+ const knownHosts = (0, exports.linhasDoKnownHosts)(confirmadas, nome);
931
+ return {
932
+ knownHosts,
933
+ fixacao: { schema: exports.ESQUEMA_DA_FIXACAO, raiz: e.raiz, url: e.url, transporte: 'forja-ssh', host, porta,
934
+ chaves: confirmadas.map((k) => ({ tipo: k.tipo, impressao: (0, exports.impressaoDaChave)(k.chave) })), knownHostsSha256: sha256(knownHosts) },
935
+ };
936
+ }
937
+ /**
938
+ * O helper e o da CLI do tipo, pelo caminho real, na versao minima, com `auth git-credential` e login no host exato
939
+ * (`host[:porta]`, nunca o caminho base). A CA do manifesto e lida uma vez aqui e segue em bytes para a copia fixada.
940
+ * A saida da CLI nunca e repetida: so o codigo de saida e a versao lida.
941
+ */
942
+ function fixarHttps(e, origin, f) {
943
+ const alvo = { tipo: f.tipo, host: f.host, porta: f.porta, caminhoBase: f.caminhoBase, ca: f.ca };
944
+ const { origem } = alvoDaCredencial(alvo);
945
+ if (origin.origem !== origem)
946
+ recusar('https.divergente', `${origin.origem} != ${origem}`);
947
+ const ca = f.ca === null ? null : lerCa(f.ca);
948
+ const cli = cli_1.CLI_DA_FORJA[f.tipo];
949
+ const achado = e.sondas.acharBinario(cli.cmd);
950
+ if (!achado)
951
+ return recusar('sem-cli', `${cli.cmd} ${cli.minima}`);
952
+ const binario = binarioReal(achado);
953
+ const versao = cli.lerVersao(e.sondas.rodar(binario, ['--version'], PRAZO_DA_CLI_MS).stdout);
954
+ if (versao === null || (0, cli_1.versaoAbaixo)(versao, cli.minima))
955
+ return recusar('sem-helper', `${cli.cmd} ${versao ?? '?'} < ${cli.minima}`);
956
+ const ajuda = e.sondas.rodar(binario, ['auth', 'git-credential', '--help'], PRAZO_DA_CLI_MS);
957
+ if (ajuda.status !== 0 || !/git-credential/.test(`${ajuda.stdout}\n${ajuda.stderr}`))
958
+ recusar('sem-helper', `${cli.cmd} ${versao}, >= ${cli.minima}`);
959
+ // O login confere a mesma CLI que vai ser fixada, pelo caminho real, sem nova busca no PATH.
960
+ // O login confere no ambiente que o worker vai ter: a mesma pasta de config e as mesmas variaveis, sem token.
961
+ // A base e o ambiente literal do worker (a lista fechada mais o do transporte), e nao o reduzido do `ork`: proxy ou
962
+ // variavel da CLI que so quem instala tem nao aprova um login que o worker nao veria.
963
+ const env = e.sondas.env ?? process.env, amb = ambienteDaCliDoTransporte(cli.cmd, env), executor = e.sondas.rodarCli ?? executor_1.rodarCli;
964
+ const doWorker = ambienteDoWorkerDoTransporte('forja-https', env, cli.cmd);
965
+ const rodarLogin = (c) => executor({ ...c, base: doWorker, acrescentar: { ...c.acrescentar, ...amb.acrescentar },
966
+ apagar: [...(c.apagar ?? []), ...amb.apagar] });
967
+ if (!(0, api_1.temLogin)(alvo, rodarLogin, binario))
968
+ recusar('sem-login', `${cli.cmd} ${(0, api_1.chaveDoLogin)(alvo)}`);
969
+ return {
970
+ fixacao: { schema: exports.ESQUEMA_DA_FIXACAO, raiz: e.raiz, url: e.url, transporte: 'forja-https', tipo: f.tipo, origem,
971
+ // O git nunca manda o caminho ao helper (`useHttpPath=false`): a credencial casa pela origem `host[:porta]`.
972
+ caminho: false, cli: cli.cmd, binario, versaoCli: versao,
973
+ configDaCli: configDaCliDoAmbiente(cli.cmd, e.sondas.env ?? process.env),
974
+ ca: ca && f.ca ? { arquivo: f.ca, sha256: sha256(ca) } : null },
975
+ ...(ca ? { ca } : {}),
976
+ };
977
+ }