@orkastery/cli 0.4.3 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/adapters/README.md +11 -0
  2. package/adapters/claude-code/.claude-plugin/plugin.json +3 -1
  3. package/adapters/claude-code/README.md +11 -2
  4. package/adapters/claude-code/agents/ork-check.md +1 -1
  5. package/adapters/claude-code/agents/ork-go.md +2 -2
  6. package/adapters/claude-code/agents/ork-goal.md +1 -1
  7. package/adapters/claude-code/agents/ork-master.md +1 -1
  8. package/adapters/claude-code/agents/ork-plan.md +1 -1
  9. package/adapters/claude-code/agents/ork-ship.md +1 -1
  10. package/adapters/claude-code/commands/master.md +11 -8
  11. package/adapters/claude-code/commands/onboarding.md +3 -2
  12. package/adapters/claude-code/commands/ork.md +8 -0
  13. package/adapters/claude-code/commands/ship.md +2 -2
  14. package/adapters/codex/skills/ork/SKILL.md +12 -1
  15. package/adapters/hermes/README.md +3 -0
  16. package/adapters/hermes/bin/ork-abrir-thread.sh +17 -2
  17. package/adapters/hermes/bin/ork-brain.sh +3 -0
  18. package/adapters/hermes/bin/ork-maestro.sh +4 -1
  19. package/adapters/hermes/bin/ork-network-roadmap.sh +11 -0
  20. package/adapters/hermes/bin/ork-roadmap-status.sh +9 -0
  21. package/adapters/hermes/hermes.plugin.json +2 -0
  22. package/adapters/hermes/hitl-ingress/__init__.py +36 -3
  23. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +19 -20
  24. package/adapters/openclaw/README.md +45 -2
  25. package/adapters/openclaw/dist/hitl-ingress.js +37 -3
  26. package/adapters/openclaw/dist/index.js +93 -21
  27. package/adapters/openclaw/openclaw.plugin.json +10 -1
  28. package/adapters/openclaw/package.json +2 -2
  29. package/adapters/openclaw/src/hitl-ingress.ts +32 -3
  30. package/adapters/openclaw/src/index.ts +109 -21
  31. package/assets/docs/padroes/documentacao-de-produto.md +2 -2
  32. package/assets/docs/padroes/roadmap-de-produto.md +1 -1
  33. package/assets/docs/produto/_modelo-feature.md +2 -0
  34. package/assets/orkmind_bridge.py +214 -1
  35. package/dist/adapters/claude-bg.js +24 -7
  36. package/dist/auditrun.js +1 -1
  37. package/dist/board.js +74 -0
  38. package/dist/branch-de-estado.js +5 -4
  39. package/dist/busca-semantica.js +161 -0
  40. package/dist/ci.js +141 -10
  41. package/dist/claim-lint.js +3 -1
  42. package/dist/company-brain-cli.js +27 -2
  43. package/dist/company-brain-context.js +132 -0
  44. package/dist/company-brain-dossie.js +376 -0
  45. package/dist/company-brain-mcp.js +4 -1
  46. package/dist/conducao.js +71 -18
  47. package/dist/decisao-autonoma.js +42 -0
  48. package/dist/docs.js +135 -25
  49. package/dist/doctor.js +242 -32
  50. package/dist/entrega-pr.js +116 -5
  51. package/dist/experiencia-instalacao.js +256 -0
  52. package/dist/experiencia.js +72 -0
  53. package/dist/fabrica-estado.js +63 -12
  54. package/dist/fechamento.js +105 -0
  55. package/dist/fix.js +3 -3
  56. package/dist/forja.js +435 -0
  57. package/dist/gates.js +9 -1
  58. package/dist/hitl-classificacao.js +11 -1
  59. package/dist/hitl-contract.js +50 -7
  60. package/dist/hitl-curto.js +158 -0
  61. package/dist/hitl-gates.js +67 -1
  62. package/dist/hitl-lote.js +30 -15
  63. package/dist/hitl-presentation.js +10 -7
  64. package/dist/hitl-resumo.js +57 -13
  65. package/dist/hitl-texto-livre.js +150 -0
  66. package/dist/hosts.js +92 -3
  67. package/dist/index.js +637 -45
  68. package/dist/indice-vetorial.js +317 -0
  69. package/dist/init.js +24 -5
  70. package/dist/integracoes-locais.js +3 -1
  71. package/dist/intelligence-benchmark-contract.js +570 -0
  72. package/dist/intelligence-graph-cli.js +584 -0
  73. package/dist/intelligence-graph-contract.js +636 -0
  74. package/dist/intelligence-graph-extract-md.js +487 -0
  75. package/dist/intelligence-graph-extract-ts.js +980 -0
  76. package/dist/intelligence-graph-extract.js +546 -0
  77. package/dist/intelligence-graph-index.js +693 -0
  78. package/dist/intelligence-graph-parsers.js +327 -0
  79. package/dist/intelligence-graph-query.js +344 -0
  80. package/dist/intelligence-graph-repo.js +197 -0
  81. package/dist/leases.js +100 -0
  82. package/dist/ledger.js +23 -0
  83. package/dist/licoes.js +5 -4
  84. package/dist/liveness.js +2 -1
  85. package/dist/maestro-cli.js +10 -1
  86. package/dist/maestro-contract.js +5 -1
  87. package/dist/maestro-discovery.js +16 -2
  88. package/dist/maestro-runtime.js +3 -0
  89. package/dist/maestro-snapshot.js +6 -2
  90. package/dist/manifest.js +166 -5
  91. package/dist/master-batch.js +3 -2
  92. package/dist/master-digest.js +2 -2
  93. package/dist/master-nota.js +203 -0
  94. package/dist/master.js +4 -0
  95. package/dist/mcp-artifacts.js +2 -0
  96. package/dist/mcp-experiencia.js +164 -0
  97. package/dist/mcp-git.js +64 -12
  98. package/dist/mcp-grafo-worker.js +91 -0
  99. package/dist/mcp-grafo.js +233 -0
  100. package/dist/mcp-install.js +5 -2
  101. package/dist/mcp-maestro.js +3 -2
  102. package/dist/mcp-server.js +93 -5
  103. package/dist/mcp-ship.js +2 -0
  104. package/dist/mcp-verify.js +3 -1
  105. package/dist/memoria.js +147 -1
  106. package/dist/network-roadmap.js +907 -0
  107. package/dist/ocupacao.js +43 -3
  108. package/dist/onboarding.js +122 -11
  109. package/dist/orkmind.js +165 -9
  110. package/dist/orquestracao.js +2 -1
  111. package/dist/parado-no-condutor.js +933 -0
  112. package/dist/phase.js +166 -13
  113. package/dist/policies.js +29 -2
  114. package/dist/projeto-alvo.js +551 -0
  115. package/dist/prompts.js +1 -1
  116. package/dist/prova-ativacao.js +345 -0
  117. package/dist/pulse-consentimento.js +4 -2
  118. package/dist/pulse-delivery.js +32 -6
  119. package/dist/pulse-resposta.js +674 -49
  120. package/dist/pulse.js +74 -3
  121. package/dist/retry.js +51 -8
  122. package/dist/roadmap-reservas.js +262 -20
  123. package/dist/roadmap-status.js +348 -0
  124. package/dist/runtime-context.js +5 -1
  125. package/dist/sessoes-inventario.js +7 -1
  126. package/dist/sessoes.js +4 -1
  127. package/dist/ship.js +25 -1
  128. package/dist/thread-close.js +3 -0
  129. package/dist/thread.js +21 -2
  130. package/dist/util.js +71 -4
  131. package/dist/verify.js +6 -5
  132. package/dist/worktree.js +29 -6
  133. package/eval/casos/onboarding.json +1 -1
  134. package/eval/casos/orchestration-experience-pt-br.json +48 -0
  135. package/eval/casos/orchestration-experience.json +48 -0
  136. package/package.json +1 -1
  137. package/schemas/code-artifact-graph.v1.schema.json +581 -0
  138. package/schemas/graph-benchmark.v1.schema.json +2240 -0
  139. package/schemas/maestro-snapshot.schema.json +23 -0
  140. package/skills/README.md +7 -5
  141. package/skills/core/onboarding/SKILL.md +10 -2
  142. package/skills/core/orchestration-experience/SKILL.md +113 -0
  143. package/skills/core/orchestration-experience-pt-br/SKILL.md +112 -0
  144. package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
  145. package/skills/phases/go-implementation/SKILL.md +2 -0
  146. package/skills/phases/ship-release/SKILL.md +6 -3
@@ -0,0 +1,348 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TETO_DO_FECHO = exports.GRUPO_DO_CICLO = exports.GRUPOS_DO_ROADMAP = exports.CONTRATO_STATUS_DO_ROADMAP = void 0;
4
+ exports.esperaDoDono = esperaDoDono;
5
+ exports.fatosLocais = fatosLocais;
6
+ exports.batidaDaFabrica = batidaDaFabrica;
7
+ exports.montarStatusDoRoadmap = montarStatusDoRoadmap;
8
+ exports.montarStatusDeFatos = montarStatusDeFatos;
9
+ exports.textoDoStatusDoRoadmap = textoDoStatusDoRoadmap;
10
+ /**
11
+ * RM-048 (item 7, D7): o status report UNICO do roadmap, no formato aprovado pelo dono.
12
+ *
13
+ * Ate aqui cada canal escrevia o proprio relatorio de roadmap, e o dono lia uma coisa no Telegram
14
+ * e outra no terminal. O formato que ele aprovou (27/09 e 28/09):
15
+ *
16
+ * titulo "Roadmap do <Projeto> (DD/MM, HH:MM)", no fuso do dono;
17
+ * grupos com icones, nesta ordem: Concluidos, Disponiveis com algo em aberto, Entregue hoje,
18
+ * Piloto, Em desenvolvimento, Refinamento, Proposto, Descontinuado;
19
+ * um item por linha, com #HITL no que depende do dono;
20
+ * fecho com "O que precisa de voce" e "O que eu faco em seguida".
21
+ *
22
+ * Tudo sai de FATO: o frontmatter do item (`ork docs`), o ledger das threads ligadas a ele e o gate
23
+ * que o nucleo confere sem escrever (`prepararPedidoGate`). E leitura pura: nao abre pedido, nao
24
+ * grava ledger, nao toca a rede. Os canais chamam `ork roadmap status` e transportam o texto.
25
+ *
26
+ * RM-054: o montador recebe FATOS de thread (`FatoDeThread`), nao so as threads deste disco. O
27
+ * relatorio local continua com os fatos locais; o panorama da rede soma os retratos das outras
28
+ * maquinas, e so ai o fecho diz entre parenteses em que maquina a thread anda.
29
+ */
30
+ const docs_1 = require("./docs");
31
+ const ledger_1 = require("./ledger");
32
+ const thread_1 = require("./thread");
33
+ const hitl_contract_1 = require("./hitl-contract");
34
+ const hitl_gates_1 = require("./hitl-gates");
35
+ const hitl_classificacao_1 = require("./hitl-classificacao");
36
+ const horario_1 = require("./horario");
37
+ const projeto_alvo_1 = require("./projeto-alvo");
38
+ const fabrica_estado_1 = require("./fabrica-estado");
39
+ const hitl_resumo_1 = require("./hitl-resumo");
40
+ const manifest_1 = require("./manifest");
41
+ const maquina_1 = require("./maquina");
42
+ const parado_no_condutor_1 = require("./parado-no-condutor");
43
+ const util_1 = require("./util");
44
+ exports.CONTRATO_STATUS_DO_ROADMAP = 'ork.roadmap-status/v1';
45
+ /** Os grupos, na ordem e com os icones que o dono aprovou. */
46
+ exports.GRUPOS_DO_ROADMAP = Object.freeze([
47
+ { id: 'concluidos', icone: '✅', titulo: 'Concluídos' },
48
+ { id: 'disponiveis', icone: '🟢', titulo: 'Disponíveis com algo em aberto' },
49
+ { id: 'hoje', icone: '🚀', titulo: 'Entregue hoje' },
50
+ { id: 'piloto', icone: '🧪', titulo: 'Piloto' },
51
+ { id: 'desenvolvimento', icone: '🔨', titulo: 'Em desenvolvimento' },
52
+ { id: 'refinamento', icone: '🔍', titulo: 'Refinamento' },
53
+ { id: 'proposto', icone: '🆕', titulo: 'Proposto' },
54
+ { id: 'descontinuado', icone: '⛔', titulo: 'Descontinuado' },
55
+ ]);
56
+ /**
57
+ * D7: o grupo de cada ciclo do padrao de roadmap (`docs/padroes/roadmap-de-produto.md`). Mapa
58
+ * TOTAL sobre os doze ciclos; ciclo fora dele cai em "Proposto" e o texto o mostra como e.
59
+ */
60
+ exports.GRUPO_DO_CICLO = Object.freeze({
61
+ 'Concluído': 'concluidos',
62
+ 'Disponível': 'disponiveis',
63
+ 'Piloto': 'piloto',
64
+ 'Em desenvolvimento': 'desenvolvimento', 'Em validação': 'desenvolvimento', 'Bloqueado': 'desenvolvimento',
65
+ 'Refinamento': 'refinamento', 'Pronto para desenvolvimento': 'refinamento',
66
+ 'Discovery': 'proposto', 'Backlog': 'proposto',
67
+ 'Cancelado': 'descontinuado', 'Descontinuado': 'descontinuado',
68
+ });
69
+ /** Quantas linhas cada parte do fecho mostra antes de contar o resto. */
70
+ exports.TETO_DO_FECHO = 5;
71
+ /** A letra recomendada de uma pergunta v2, para o fecho dizer exatamente o que digitar. */
72
+ const recomendadaDe = (p) => (0, hitl_contract_1.ehV2)(p) && p.classe === 'pergunta' ? p.alternativas.find(a => a.recomendada)?.letra : undefined;
73
+ const mapa = (v) => (!!v && typeof v === 'object' && !Array.isArray(v) ? v : {});
74
+ const texto = (v) => (v === undefined || v === null ? '' : String(v));
75
+ /** O nome curto do item: o titulo ate o primeiro ":" ou 60 caracteres. */
76
+ function nomeCurto(titulo) {
77
+ const antes = titulo.split(/:\s/)[0].trim();
78
+ return antes.length > 60 ? `${antes.slice(0, 57)}...` : antes;
79
+ }
80
+ /**
81
+ * O que espera o dono nesta thread, lido sem escrever: um gate que ainda espera (pausa do modo ou
82
+ * escalacao tipada do dono) ou uma pergunta de sessao aberta. Impedimento tecnico nao conta (D6).
83
+ */
84
+ function esperaDoDono(raiz, t, quando) {
85
+ const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, t.id));
86
+ const escalacoes = [...new Set(eventos.filter(e => e.tipo === 'gate_blocked' && e.fase === t.faseAtual &&
87
+ hitl_gates_1.MOTIVOS_DE_ESCALACAO_HUMANA.includes(String(e.motivo)) && (0, hitl_classificacao_1.quemDecide)(String(e.motivo)) === 'dono')
88
+ .map(e => String(e.motivo)))];
89
+ // RM-037 (fatia 4): o fim de turno sem pergunta e do condutor, nunca "O que precisa de voce".
90
+ const doCondutor = (0, parado_no_condutor_1.esperaDoCondutor)(t, eventos, quando) !== null;
91
+ for (const motivo of [...(doCondutor ? [] : ['human.pending']), ...escalacoes]) {
92
+ try {
93
+ const preparado = (0, hitl_gates_1.prepararPedidoGate)(raiz, t.id, motivo, quando);
94
+ const pedido = 'aberto' in preparado ? preparado.aberto : preparado.novo;
95
+ const codigo = 'aberto' in preparado ? pedido.codigo : undefined;
96
+ const recomendada = recomendadaDe(pedido);
97
+ return { thread: t.id, pergunta: (0, hitl_contract_1.textoDoPedido)(pedido), ...(typeof codigo === 'string' ? { codigo } : {}),
98
+ ...(recomendada ? { recomendada } : {}) };
99
+ }
100
+ catch { /* este motivo nao espera o dono agora */ }
101
+ }
102
+ const contexto = (0, hitl_gates_1.contextoHitlDosEventos)(t, eventos);
103
+ const respondidos = new Set(eventos.filter(e => ['human_gate', 'session_answered'].includes(e.tipo)).map(e => String(e.pedidoId)));
104
+ for (const e of [...eventos].reverse()) {
105
+ if (e.tipo !== 'hitl_requested' || e.contexto !== contexto)
106
+ continue;
107
+ const pedido = e.pedido;
108
+ try {
109
+ if ((0, hitl_contract_1.alvoDoPedido)(pedido)?.tipo !== 'session' || respondidos.has(pedido.id) || (0, hitl_contract_1.estadoDoPedido)(pedido, quando) !== 'aberto')
110
+ continue;
111
+ const codigo = (0, hitl_contract_1.ehV2)(pedido) && pedido.classe === 'pergunta' ? pedido.codigo : undefined;
112
+ const recomendada = recomendadaDe(pedido);
113
+ return { thread: t.id, pergunta: (0, hitl_contract_1.textoDoPedido)(pedido), ...(codigo ? { codigo } : {}), ...(recomendada ? { recomendada } : {}) };
114
+ }
115
+ catch { /* pedido invalido nao e espera */ }
116
+ }
117
+ return undefined;
118
+ }
119
+ /**
120
+ * Os fatos das threads deste disco: `ship_done` do dia e espera do dono lidos do ledger local.
121
+ * `fuso`: o do projeto consultado, na visao da rede; sem ele, o do dono deste processo.
122
+ */
123
+ function fatosLocais(raiz, quando, fuso) {
124
+ const hoje = (0, horario_1.dataLocal)(quando, fuso);
125
+ const fatos = [];
126
+ // RM-037 (fatia 4): o estado das entregas sai uma vez, sob demanda, do git local e do retrato de PRs do pulse.
127
+ let entregas;
128
+ const lerEntregas = () => {
129
+ if (entregas !== undefined)
130
+ return entregas;
131
+ try {
132
+ const retrato = (0, parado_no_condutor_1.lerRetratoDePrs)(raiz);
133
+ entregas = (0, parado_no_condutor_1.entregasDoProjeto)((0, manifest_1.exigirManifesto)(raiz), { quando, lerPrs: () => (retrato ? { ok: true, retrato } : null) });
134
+ }
135
+ catch {
136
+ entregas = null;
137
+ }
138
+ return entregas;
139
+ };
140
+ // A leitura que falhou e dita, e nao vira o "sigo" de sempre.
141
+ const entrega = (id) => {
142
+ const lidas = lerEntregas();
143
+ return lidas ? entregaNoStatus(lidas.estados.find(e => e.thread === id)) : { estado: 'estado da entrega não lido' };
144
+ };
145
+ for (const id of (0, thread_1.listarIds)(raiz)) {
146
+ let t;
147
+ try {
148
+ t = (0, thread_1.lerThread)(raiz, id);
149
+ }
150
+ catch {
151
+ continue; /* thread ilegivel fica de fora */
152
+ }
153
+ fatos.push({
154
+ id: t.id, roadmap: t.roadmap ?? null, aberta: t.status !== 'fechada', fase: t.faseAtual,
155
+ entregueHoje: () => (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, t.id)).some(e => e.tipo === 'ship_done' && (0, horario_1.dataLocal)(e.ts, fuso) === hoje),
156
+ espera: () => esperaDoDono(raiz, t, quando),
157
+ entrega: () => (t.status === 'fechada' ? undefined : entrega(t.id)),
158
+ });
159
+ }
160
+ return fatos;
161
+ }
162
+ /** O estado da entrega como o status o diz: so quando ha o que dizer. */
163
+ function entregaNoStatus(e) {
164
+ if (!e || (!e.resumo && !e.parado))
165
+ return undefined;
166
+ return { estado: e.resumo, ...(e.conduzidaAgora ? { conduzida: true } : {}),
167
+ ...(e.parado ? { parado: { desdeEm: e.parado.desdeEm, proximoPasso: e.parado.proximoPasso,
168
+ ...(e.parado.prLidoEm ? { prLidoEm: e.parado.prLidoEm } : {}) } } : {}),
169
+ ...(e.prLidoEm ? { prLidoEm: e.prLidoEm } : {}) };
170
+ }
171
+ /**
172
+ * RM-037 (fatia 4, D8): as outras maquinas da fabrica sem batida alem do limiar da rede, lidas da copia
173
+ * local de `ork/fabrica-estado`, sem fetch. Sem fabrica compartilhada, nada; sem copia local, "nao lido".
174
+ */
175
+ function batidaDaFabrica(carregado, quando) {
176
+ if (!(0, maquina_1.fabricaCompartilhada)(carregado.manifesto))
177
+ return undefined;
178
+ // Sem rede escondida: num clone parcial, o `git show` dos retratos buscaria o objeto no remoto. O git anterior
179
+ // a 2.45 ignora o GIT_NO_LAZY_FETCH, entao o clone parcial nem e lido aqui (N4 da seguranca, rodada 2).
180
+ // N4 da seguranca (rodada 3): o git novo registra o clone parcial no `remote.<r>.promisor`, o velho no `extensions`.
181
+ if ((0, util_1.exec)('git', ['config', '--get-regexp', '^(extensions\\.partialclone|remote\\..+\\.promisor)$'], carregado.raiz).stdout.trim()) {
182
+ return { lido: false, motivo: `clone parcial: a cópia local de ${fabrica_estado_1.BRANCH_DA_FABRICA} não é lida sem rede`, semBatida: [] };
183
+ }
184
+ const antes = process.env.GIT_NO_LAZY_FETCH;
185
+ process.env.GIT_NO_LAZY_FETCH = '1';
186
+ let painel;
187
+ try {
188
+ painel = (0, fabrica_estado_1.lerFabrica)(carregado.raiz, { remoto: carregado.manifesto.fabrica.remoto, semRemoto: true });
189
+ }
190
+ catch {
191
+ return { lido: false, motivo: `a cópia local de ${fabrica_estado_1.BRANCH_DA_FABRICA} está ilegível`, semBatida: [] };
192
+ }
193
+ finally {
194
+ if (antes === undefined)
195
+ delete process.env.GIT_NO_LAZY_FETCH;
196
+ else
197
+ process.env.GIT_NO_LAZY_FETCH = antes;
198
+ }
199
+ if (!painel.ponta)
200
+ return { lido: false, semBatida: [] };
201
+ const eu = (0, maquina_1.nomeDaMaquina)(), agora = Date.parse(quando);
202
+ // A copia so diz quem esta sem batida quando esta maquina publicou nela ha pouco: e o fetch daqui que a atualiza.
203
+ const { publicadoEm: minha } = painel.maquinas.find(m => m.maquina === eu) ?? { publicadoEm: null };
204
+ if (!minha || !(agora - Date.parse(minha) <= fabrica_estado_1.LIMIAR_SEM_BATIDA_MS)) {
205
+ return { lido: true, copiaVelha: { ultimoDestaMaquina: minha }, semBatida: [] };
206
+ }
207
+ const semBatida = painel.maquinas.filter(m => m.maquina !== eu).map(m => {
208
+ const { publicadoEm: publicado } = m;
209
+ return { maquina: m.maquina, publicadoEm: publicado, idadeMs: agora - Date.parse(publicado) };
210
+ }).filter(m => Number.isFinite(m.idadeMs) && m.idadeMs > fabrica_estado_1.LIMIAR_SEM_BATIDA_MS)
211
+ .map(m => ({ maquina: m.maquina, publicadoEm: m.publicadoEm, idadeMin: Math.floor(m.idadeMs / 60000) }));
212
+ return { lido: true, semBatida };
213
+ }
214
+ /** Monta o relatorio. Leitura pura: nada e escrito, nenhum pedido e aberto. */
215
+ function montarStatusDoRoadmap(raiz, opcoes = {}) {
216
+ const quando = opcoes.quando ?? new Date().toISOString();
217
+ let fabrica;
218
+ try {
219
+ fabrica = batidaDaFabrica((0, manifest_1.exigirManifesto)(raiz), quando);
220
+ }
221
+ catch {
222
+ fabrica = undefined;
223
+ }
224
+ return montarStatusDeFatos((0, docs_1.carregarDocs)(raiz).docs, fatosLocais(raiz, quando), { quando, projeto: opcoes.projeto, consulta: opcoes.consulta,
225
+ ...(fabrica ? { fabrica } : {}) });
226
+ }
227
+ /** O relatorio a partir das paginas do roadmap e dos fatos das threads, de onde quer que venham. */
228
+ function montarStatusDeFatos(docs, fatos, opcoes) {
229
+ const quando = opcoes.quando;
230
+ const porId = new Map(fatos.map(f => [f.id, f]));
231
+ const porItem = new Map();
232
+ for (const f of fatos)
233
+ if (f.roadmap)
234
+ porItem.set(f.roadmap, new Set([...(porItem.get(f.roadmap) ?? []), f.id]));
235
+ const itens = [];
236
+ for (const d of docs.filter(x => x.tipo === 'roadmap')) {
237
+ const estado = mapa(d.dados.estado), sdlc = mapa(d.dados.sdlc);
238
+ const ciclo = texto(estado.ciclo);
239
+ const ligadas = [...new Set([...(porItem.get(d.id) ?? []), ...(texto(sdlc.thread) ? [texto(sdlc.thread)] : [])])]
240
+ .filter(id => porId.has(id)).sort();
241
+ const entregueHoje = ligadas.some(id => porId.get(id).entregueHoje());
242
+ const abertas = ligadas.map(id => porId.get(id)).filter(f => f.aberta);
243
+ let hitl;
244
+ for (const f of abertas) {
245
+ hitl = f.espera();
246
+ if (hitl)
247
+ break;
248
+ }
249
+ // RM-037 (fatia 4): a thread que diz o estado real vem primeiro: a parada no condutor, depois a entrega em curso.
250
+ const entregas = new Map(abertas.map(f => [f.id, f.entrega?.()]));
251
+ const conduz = abertas.find(f => entregas.get(f.id)?.parado) ?? abertas.find(f => entregas.get(f.id)?.estado) ?? abertas[0];
252
+ const entrega = conduz ? entregas.get(conduz.id) : undefined;
253
+ const emAberto = exports.GRUPO_DO_CICLO[ciclo] === 'disponiveis' ? [
254
+ ...(texto(estado.exposicao) && texto(estado.exposicao) !== 'Geral' ? [`exposição ${texto(estado.exposicao)}`] : []),
255
+ ...(texto(estado.habilitacao) && texto(estado.habilitacao) !== 'Concluída' ? [`habilitação ${texto(estado.habilitacao)}`] : []),
256
+ ] : [];
257
+ itens.push({
258
+ id: d.id, titulo: nomeCurto(texto(d.dados.titulo) || d.id), ciclo,
259
+ grupo: entregueHoje ? 'hoje' : exports.GRUPO_DO_CICLO[ciclo] ?? 'proposto',
260
+ emAberto, threads: ligadas,
261
+ ...(conduz ? { conduzindo: { thread: conduz.id, fase: conduz.fase, ...(conduz.maquina ? { maquina: conduz.maquina } : {}),
262
+ ...(entrega ? { entrega } : {}) } } : {}),
263
+ ...(hitl ? { hitl } : {}),
264
+ });
265
+ }
266
+ const grupos = exports.GRUPOS_DO_ROADMAP.map(g => ({ ...g, itens: itens.filter(i => i.grupo === g.id) }));
267
+ return {
268
+ contrato: exports.CONTRATO_STATUS_DO_ROADMAP, consultadoEm: quando,
269
+ projeto: opcoes.projeto ?? 'projeto', grupos,
270
+ precisaDeVoce: itens.filter(i => i.hitl).map(i => ({ item: i.id, espera: i.hitl })),
271
+ emSeguida: itens.filter(i => i.conduzindo && !i.hitl).map(i => ({ item: i.id, ...i.conduzindo })),
272
+ ...(opcoes.consulta ? { consulta: opcoes.consulta } : {}),
273
+ ...(opcoes.fabrica ? { fabrica: opcoes.fabrica } : {}),
274
+ };
275
+ }
276
+ /** "orkastery" vira "Orkastery": o titulo diz o nome do projeto como gente escreve. */
277
+ const capitalizar = (s) => s ? s[0].toUpperCase() + s.slice(1) : s;
278
+ /** A maquina entre parenteses, so quando o fato a traz (visao da rede, D7 da RM-054). */
279
+ const naMaquina = (maquina) => maquina ? ` (${maquina})` : '';
280
+ /**
281
+ * RM-037 (fatia 4): a linha do que vem a seguir diz o estado real. Parada no condutor sai com o desde e o
282
+ * proximo passo; entrega em curso, com o estado do PR e a hora em que ele foi lido; sem nada disso, a fase.
283
+ */
284
+ function linhaDoQueVem(x, agora, fuso) {
285
+ const e = x.entrega;
286
+ if (e?.parado) {
287
+ const { prLidoEm: lido } = e.parado;
288
+ return `• ${x.item}: ${(0, hitl_resumo_1.linhaDoParadoNoCondutor)({ thread: x.thread, ...e.parado }, { agora, fuso })}${naMaquina(x.maquina)}` +
289
+ `${lido ? ` (PR lido às ${(0, horario_1.formatarHora)(lido, { agora, fuso })})` : ''}.`;
290
+ }
291
+ if (e?.estado) {
292
+ const { prLidoEm: lido } = e;
293
+ const daLeitura = lido ? ` (PR lido às ${(0, horario_1.formatarHora)(lido, { agora, fuso })})` : '';
294
+ return e.conduzida ? `• ${x.item}: sigo ${x.thread} na fase ${x.fase}${naMaquina(x.maquina)}; ${e.estado}${daLeitura}.`
295
+ : `• ${x.item}: ${x.thread} na fase ${x.fase}${naMaquina(x.maquina)}, ${e.estado}${daLeitura}.`;
296
+ }
297
+ return `• ${x.item}: sigo ${x.thread} na fase ${x.fase}${naMaquina(x.maquina)}.`;
298
+ }
299
+ /** RM-037 (fatia 4, D8): uma linha so para a fabrica, e so quando ha o que avisar. */
300
+ function linhaDaFabrica(f, agora, fuso) {
301
+ if (!f)
302
+ return [];
303
+ if (!f.lido)
304
+ return [`Fábrica: não lido (${f.motivo ?? `esta máquina não tem cópia local de ${fabrica_estado_1.BRANCH_DA_FABRICA}`}).`];
305
+ if (f.copiaVelha) {
306
+ const { ultimoDestaMaquina: ultimo } = f.copiaVelha;
307
+ return [`Fábrica: a cópia local de ${fabrica_estado_1.BRANCH_DA_FABRICA} está velha (${ultimo ? `esta máquina publicou nela por último em ` +
308
+ `${(0, horario_1.formatarDataHora)(ultimo, { agora, fuso })}` : 'esta máquina não aparece nela'}); não dá para dizer quem está sem batida.`];
309
+ }
310
+ if (!f.semBatida.length)
311
+ return [];
312
+ return [`Fábrica: ${f.semBatida.map(m => `${m.maquina} sem batida há ${(0, horario_1.duracaoCurta)(m.idadeMin)} ` +
313
+ `(último retrato ${(0, horario_1.formatarDataHora)(m.publicadoEm, { agora, fuso })})`).join('; ')}, pela cópia local de ${fabrica_estado_1.BRANCH_DA_FABRICA}.`];
314
+ }
315
+ /**
316
+ * O texto do relatorio, igual em todo canal: os icones sao parte do formato aprovado. `fuso`: o do
317
+ * projeto consultado, na visao da rede; sem ele, o do dono deste processo.
318
+ *
319
+ * Fatia 2 do ensaio da 0.5.0 (P7): a hora do titulo vem com o fuso dito logo abaixo dele, na linha
320
+ * 2, sem mudar o titulo aprovado. `legenda: false` e para quem ja diz o fuso na mesma mensagem (o
321
+ * panorama da rede diz no fim).
322
+ */
323
+ function textoDoStatusDoRoadmap(s, fuso, opcoes = {}) {
324
+ if (s.contrato !== exports.CONTRATO_STATUS_DO_ROADMAP)
325
+ throw new Error('status do roadmap: contrato inválido');
326
+ const p = (0, horario_1.partesLocais)(s.consultadoEm, fuso);
327
+ const linhaDoItem = (i) => {
328
+ const detalhe = i.emAberto.length ? `: ${i.emAberto.join(', ')}` : i.conduzindo && i.grupo !== 'concluidos' && i.grupo !== 'hoje'
329
+ ? ` (${i.conduzindo.fase}${i.conduzindo.maquina ? `, ${i.conduzindo.maquina}` : ''})` : '';
330
+ return `• ${i.id} ${i.titulo}${detalhe}${i.hitl ? ' #HITL' : ''}`;
331
+ };
332
+ const cortar = (lista, f) => [...lista.slice(0, exports.TETO_DO_FECHO).map(f), ...(lista.length > exports.TETO_DO_FECHO ? [`• e mais ${lista.length - exports.TETO_DO_FECHO}`] : [])];
333
+ return [
334
+ `Roadmap do ${capitalizar(s.projeto)} (${p.dia}/${p.mes}, ${p.hora}:${p.minuto})`,
335
+ ...(opcoes.legenda === false ? [] : [(0, horario_1.legendaDoFuso)(fuso, s.consultadoEm)]),
336
+ // RM-052: logo abaixo do titulo aprovado (e do fuso), qual projeto foi lido e o que nao foi.
337
+ ...(s.consulta ? (0, projeto_alvo_1.linhasDaConsulta)(s.consulta) : []),
338
+ ...linhaDaFabrica(s.fabrica, s.consultadoEm, fuso),
339
+ ...s.grupos.filter(g => g.itens.length).flatMap(g => ['', `${g.icone} ${g.titulo}`, ...g.itens.map(linhaDoItem)]),
340
+ '', 'O que precisa de você',
341
+ ...(s.precisaDeVoce.length ? cortar(s.precisaDeVoce, x => `• ${x.item}${naMaquina(x.espera.maquina)}: ${x.espera.pergunta}` +
342
+ (x.espera.codigo ? ` Responda ${x.espera.codigo} ${x.espera.recomendada ?? 'a'} (ou outra letra).` : ' A pergunta chega no próximo resumo.'))
343
+ : ['• Nada agora.']),
344
+ '', 'O que eu faço em seguida',
345
+ ...(s.emSeguida.length ? cortar(s.emSeguida, x => linhaDoQueVem(x, s.consultadoEm, fuso))
346
+ : ['• Nada em andamento; sigo o que você priorizar.']),
347
+ ].join('\n');
348
+ }
@@ -46,6 +46,7 @@ const thread_1 = require("./thread");
46
46
  const hosts_1 = require("./hosts");
47
47
  const worktree_1 = require("./worktree");
48
48
  const mcp_install_1 = require("./mcp-install");
49
+ const mcp_grafo_1 = require("./mcp-grafo");
49
50
  const hitl_public_receipt_1 = require("./hitl-public-receipt");
50
51
  /** Correlação interna; nenhuma identidade de despacho concede poder de gate. */
51
52
  function novaIdentidadeDeDespacho(threadId, fase) {
@@ -207,9 +208,12 @@ function validarContextoRuntime(contexto, cwd) {
207
208
  throw Error('runtime.context.installation: catalogo ou plugin incompleto');
208
209
  // Capacidade derivada do bloco atual, nunca aceita como entrada do modelo.
209
210
  const permiteEditarProduto = permissoesFilho === 'worktree' && (0, thread_1.blocoDaThread)(thread, thread.faseAtual).fases.includes('GO');
211
+ // RM-031 KG5 (D2, D7): as tools do grafo seguem a flag do manifesto da raiz, que o servidor do filho tambem le;
212
+ // o manifesto da worktree da thread e o pedido do modelo nao ligam nada.
213
+ const grafoMcp = (0, mcp_grafo_1.grafoLigado)((0, manifest_1.exigirManifesto)(raiz).manifesto);
210
214
  const publicVerifiers = (0, hitl_public_receipt_1.publicHitlVerifiers)();
211
215
  const servidor = { ...(0, mcp_install_1.configuracaoServidorMcp)(raiz, contexto.host, contexto.threadId, transporteShip, permissoesFilho, contexto.identidade?.dispatchId),
212
216
  ...(publicVerifiers ? { env: { [hitl_public_receipt_1.ENV_HITL_VERIFIERS]: publicVerifiers } } : {}),
213
217
  ...(contexto.host === 'codex' && permissoesFilho === 'worktree' ? (0, mcp_install_1.politicasFilhoCodex)(perfil.politicasCodex ?? {}) : {}) };
214
- return { projeto: raiz, instalacao, bootstrap, permissoesFilho, permiteEditarProduto, servidor };
218
+ return { projeto: raiz, instalacao, bootstrap, permissoesFilho, permiteEditarProduto, grafoMcp, servidor };
215
219
  }
@@ -73,8 +73,14 @@ function inventariarSessoes(raiz, opcoes = {}) {
73
73
  const canonica = (0, estado_thread_1.raizDoEstado)(raiz);
74
74
  const claude = (0, claude_bg_1.consultarSessoes)(undefined, opcoes.todas);
75
75
  const codex = (0, codex_1.consultarRollouts)(opcoes.todas);
76
+ const origemClaude = `claude agents --json${opcoes.todas ? ' --all' : ''}`;
77
+ // Fatia 2 do ensaio da 0.5.0 (P1): como o diretório opcional do Codex, só o ENOENT antes da consulta
78
+ // prova a fonte ausente; qualquer outra falha do `claude` continua deixando o inventário incompleto.
76
79
  const fontes = [
77
- { origem: `claude agents --json${opcoes.todas ? ' --all' : ''}`, ok: claude.ok, detalhe: claude.detalhe },
80
+ claude.ausente
81
+ ? { origem: origemClaude, ok: true, ausente: true, detalhe: `${claude.detalhe}: nenhuma sessão claude-bg a listar`,
82
+ correcao: 'para despachar e listar pelo claude-bg, instale o Claude Code e garanta `claude` no PATH' }
83
+ : { origem: origemClaude, ok: claude.ok, detalhe: claude.detalhe },
78
84
  ...codex.resultadosFontes,
79
85
  ];
80
86
  const todas = [
package/dist/sessoes.js CHANGED
@@ -51,7 +51,7 @@ const util_1 = require("./util");
51
51
  function textoDoInventario(r) {
52
52
  const linhas = [
53
53
  `Inventário ${r.escopo.global ? 'global' : 'do projeto'} da conta ${r.escopo.usuario}; histórico: ${r.escopo.historico ? 'incluído' : 'omitido'}.`,
54
- `Fontes: ${r.fontes.map(f => `${f.origem} (${f.ok ? 'ok' : 'FALHA'})`).join('; ')}`,
54
+ `Fontes: ${r.fontes.map(f => `${f.origem} (${f.ausente ? 'ausente' : f.ok ? 'ok' : 'FALHA'})`).join('; ')}`,
55
55
  `Total: ${r.total}; sem thread: ${r.semThread}; ambíguas: ${r.ambiguas}; consulta: ${r.ok ? 'válida' : 'INCOMPLETA'}.`,
56
56
  ];
57
57
  if (r.sessoes.length)
@@ -59,6 +59,9 @@ function textoDoInventario(r) {
59
59
  s.vinculos.map(v => `${v.thread}/${v.fase}`).join(', ') || '(fora do ork)'])));
60
60
  for (const f of r.fontes.filter(f => !f.ok))
61
61
  linhas.push(`Falha: ${f.detalhe}`);
62
+ // Fatia 2 do ensaio da 0.5.0 (P1): a fonte ausente nao e falha, mas diz o que falta e como ter.
63
+ for (const f of r.fontes.filter(f => f.ausente))
64
+ linhas.push(`Ausente: ${f.detalhe}${f.correcao ? `; correção: ${f.correcao}` : ''}`);
62
65
  return linhas.join('\n');
63
66
  }
64
67
  function tabelaDeSessoes(raiz, todas = false) {
package/dist/ship.js CHANGED
@@ -348,6 +348,8 @@ function ship(carregado, threadId, opcoes) {
348
348
  correcao,
349
349
  de,
350
350
  para,
351
+ // Fatia 2 do ensaio da 0.5.0 (P2): o gate do ensaio e marcado, e os leitores de estado o ignoram.
352
+ ...(r.dryRun ? { dryRun: true } : {}),
351
353
  });
352
354
  (0, ledger_1.registrar)(dir, threadId, ledger_1.TIPOS_DE_EVENTO.shipBloqueado, {
353
355
  de,
@@ -373,6 +375,26 @@ function ship(carregado, threadId, opcoes) {
373
375
  const branchAtrasDaBase = pontaDe && pontaPara && de !== para
374
376
  ? !(0, util_1.exec)('git', ['merge-base', '--is-ancestor', pontaPara, pontaDe], raiz).ok
375
377
  : undefined;
378
+ // Fatia 2 do ensaio da 0.5.0 (P9): sem delta (a branch da thread ja esta contida na base) e com a base
379
+ // local a frente da ref de rastreio do remoto, o push publicaria a base sem merge de thread. A medida e
380
+ // local, sem rede; sem remoto, sem ref de rastreio ou com --sem-push, o fato nao vem e nada muda.
381
+ // CHECK, rodada 1 (B1): o merge que o proprio ship ja fez e nao chegou ao remoto (push recusado,
382
+ // --sem-push, retomada do MCP) tambem deixa a base a frente sem delta, mas leva a ponta da thread como
383
+ // pai que nao e o primeiro: e entrega de thread, e o retry empurra como antes. Rodada 2 (S-R2-1): so
384
+ // quando a linha principal da base, alem do remoto, e toda de merge; commit direto nela segue barrado.
385
+ const rastreio = pontaDe && pontaPara && de !== para && !opcoes.semPush && remotoConfigurado(raiz, remoto)
386
+ ? shaDaRef(raiz, `refs/remotes/${remoto}/${para}`) : null;
387
+ const mergeDaThreadNaBase = (desde) => {
388
+ const diretos = (0, util_1.exec)('git', ['rev-list', '--first-parent', '--no-merges', `${desde}..${pontaPara}`], raiz);
389
+ return diretos.ok && diretos.stdout.trim() === '' &&
390
+ (0, util_1.exec)('git', ['rev-list', '--merges', '--parents', `${desde}..${pontaPara}`], raiz).stdout.split('\n')
391
+ .some((linha) => linha.trim().split(' ').slice(2).includes(pontaDe));
392
+ };
393
+ const semDeltaComBaseAFrente = rastreio
394
+ ? (0, util_1.exec)('git', ['merge-base', '--is-ancestor', pontaDe, pontaPara], raiz).ok &&
395
+ !(0, util_1.exec)('git', ['merge-base', '--is-ancestor', pontaPara, rastreio], raiz).ok &&
396
+ !mergeDaThreadNaBase(rastreio)
397
+ : undefined;
376
398
  const violacoes = (0, policies_1.avaliarPolicies)(manifesto, {
377
399
  gate: 'ship',
378
400
  de,
@@ -380,6 +402,8 @@ function ship(carregado, threadId, opcoes) {
380
402
  baseBranch: manifesto.worktree.base_branch,
381
403
  threadId,
382
404
  branchAtrasDaBase,
405
+ semDeltaComBaseAFrente,
406
+ remoto,
383
407
  });
384
408
  r.violacoes = violacoes;
385
409
  if (!r.dryRun) {
@@ -647,7 +671,7 @@ function textoDoShip(r) {
647
671
  linhas.push(` ${local(r.autorizacao.razao)}`);
648
672
  if (r.verificacao) {
649
673
  const v = r.verificacao;
650
- linhas.push(` verificacao ${v.ok ? 'passou' : 'REPROVOU'} no HEAD ${v.commit.slice(0, 8)} ` +
674
+ linhas.push(` verificacao ${v.ok ? 'passou' : 'REPROVOU'} no HEAD ${(0, util_1.shaCurto)(v.commit)} ` +
651
675
  `(${v.claims.filter((c) => c.verificado).length}/${v.claims.length} claims, ` +
652
676
  `${v.comandos.filter((c) => c.ok).length}/${v.comandos.length} comandos)`);
653
677
  }
@@ -47,6 +47,7 @@ const ledger_1 = require("./ledger");
47
47
  const estado_thread_1 = require("./estado-thread");
48
48
  const util_1 = require("./util");
49
49
  const fabrica_publicar_1 = require("./fabrica-publicar");
50
+ const fechamento_1 = require("./fechamento");
50
51
  function validarArtefato(raiz, arquivo, sha256) {
51
52
  if (!/^[a-f0-9]{64}$/.test(sha256))
52
53
  throw new Error('sha256 inválido');
@@ -111,5 +112,7 @@ function fecharAdministrativamente(raiz, id, opcoes) {
111
112
  t.status = 'fechada';
112
113
  (0, thread_1.gravarThread)(raiz, t);
113
114
  (0, fabrica_publicar_1.publicarEmSegundoPlano)(raiz);
115
+ // RM-037 (rm037noite): a thread fechada solta o que segurava, com registro no ledger dela.
116
+ (0, fechamento_1.liberarAoFechar)(raiz, id);
114
117
  return t;
115
118
  }
package/dist/thread.js CHANGED
@@ -45,6 +45,7 @@ exports.caminhoThread = caminhoThread;
45
45
  exports.listarIds = listarIds;
46
46
  exports.lerThread = lerThread;
47
47
  exports.gravarThread = gravarThread;
48
+ exports.avisoDeThreadSemBase = avisoDeThreadSemBase;
48
49
  exports.idDisponivel = idDisponivel;
49
50
  exports.criarWorktree = criarWorktree;
50
51
  exports.novaThread = novaThread;
@@ -113,9 +114,22 @@ function carimbarBase(raiz) {
113
114
  const commit = (0, util_1.exec)('git', ['rev-parse', 'HEAD'], raiz);
114
115
  return {
115
116
  branch: branch.ok ? branch.stdout.trim() : 'desconhecida',
116
- commit: commit.ok ? commit.stdout.trim() : 'desconhecido',
117
+ commit: commit.ok ? commit.stdout.trim() : util_1.COMMIT_DESCONHECIDO,
117
118
  };
118
119
  }
120
+ /**
121
+ * Ensaio da 0.5.0: a thread aberta antes do primeiro commit nascia sem base, e nada avisava; o
122
+ * ship depois nao tem de onde partir. O aviso vai ao stderr e nao muda a saida do comando.
123
+ */
124
+ function avisoDeThreadSemBase(thread, gravada) {
125
+ if (thread.base.commit !== util_1.COMMIT_DESCONHECIDO)
126
+ return null;
127
+ return gravada
128
+ ? `Aviso: o repositório ainda não tem commit, e a thread ${thread.id} nasceu sem base: o ship não tem de onde partir. ` +
129
+ `Faça o primeiro commit, feche esta com ork thread close ${thread.id} --motivo engano --por <quem> ` +
130
+ '--justificativa "aberta antes do primeiro commit" e abra outra.'
131
+ : 'Aviso: o repositório ainda não tem commit, e a thread nasceria sem base: faça o primeiro commit antes de abrir a primeira thread.';
132
+ }
119
133
  /** Resolve um id livre a partir do assunto normalizado (`<abbrev>-<assunto>`). */
120
134
  function idDisponivel(raiz, abbrev, assunto) {
121
135
  let candidato = assunto;
@@ -144,6 +158,9 @@ function criarWorktree(carregado, id, slug, opcoes = {}) {
144
158
  const base = opcoes.base ?? manifesto.worktree.base_branch;
145
159
  const commitBase = (0, util_1.exec)('git', ['rev-parse', '--verify', base], raiz);
146
160
  const ref = commitBase.ok ? commitBase.stdout.trim() : 'HEAD';
161
+ // Fatia 2 do ensaio da 0.5.0 (P3): a pasta das worktrees que o ork cria agora nasce fora do git.
162
+ const pastaDasWorktrees = path.dirname(dir);
163
+ const pastaNova = !fs.existsSync(pastaDasWorktrees);
147
164
  // Branch nova (`-b`) no caso comum; branch que ja existe no ciclo `merge-branch`.
148
165
  const argumentos = opcoes.branchExistente
149
166
  ? ['worktree', 'add', dir, opcoes.branchExistente]
@@ -152,6 +169,8 @@ function criarWorktree(carregado, id, slug, opcoes = {}) {
152
169
  if (!r.ok) {
153
170
  throw new Error(`git worktree add falhou: ${(r.stderr || r.stdout).trim()}`);
154
171
  }
172
+ if (pastaNova)
173
+ (0, util_1.ignorarPastaNoGit)(pastaDasWorktrees, raiz);
155
174
  const lista = (0, util_1.exec)('git', ['worktree', 'list', '--porcelain'], raiz);
156
175
  const criada = lista.ok && lista.stdout.split('\n').some((l) => l.trim() === `worktree ${dir}`);
157
176
  if (!criada) {
@@ -362,7 +381,7 @@ function resumoDaThread(thread, conducao) {
362
381
  linhas.push(` ciclo ${thread.variante ?? 'padrao do modo'}`);
363
382
  linhas.push(` fase ${thread.faseAtual}`);
364
383
  linhas.push(` status ${thread.status}`);
365
- linhas.push(` base ${thread.base.branch} @ ${thread.base.commit.slice(0, 8)}`);
384
+ linhas.push(` base ${thread.base.branch} @ ${(0, util_1.shaCurto)(thread.base.commit)}`);
366
385
  linhas.push(` worktree ${thread.worktree ?? '(raiz do projeto)'}`);
367
386
  // I-36 (T17): quem passa a leitura da conducao ve a linha unica; sem ela o resumo fica como era.
368
387
  if (conducao !== undefined)
package/dist/util.js CHANGED
@@ -34,8 +34,12 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  };
35
35
  })();
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.GITIGNORE_DA_MAQUINA = exports.COMMIT_DESCONHECIDO = void 0;
37
38
  exports.exec = exec;
39
+ exports.shaCurto = shaCurto;
40
+ exports.branchDoHead = branchDoHead;
38
41
  exports.noPath = noPath;
42
+ exports.ignorarPastaNoGit = ignorarPastaNoGit;
39
43
  exports.subirAte = subirAte;
40
44
  exports.gravar = gravar;
41
45
  exports.lerJson = lerJson;
@@ -66,10 +70,73 @@ function exec(cmd, args, cwd, timeoutMs = 120000, env = process.env) {
66
70
  ...(r.error ? { error: r.error.code ?? r.error.message } : {}),
67
71
  };
68
72
  }
69
- /** O binario esta no PATH? */
70
- function noPath(bin) {
71
- const r = exec('which', [bin]);
72
- return r.ok ? r.stdout.trim() : null;
73
+ /** O commit quando o repositorio ainda nao tem commit (base da thread, HEAD do verify). */
74
+ exports.COMMIT_DESCONHECIDO = 'desconhecido';
75
+ /**
76
+ * Sha para exibir: 8 caracteres quando o valor e um sha hexadecimal (SHA-1 ou SHA-256); qualquer
77
+ * outro valor, como o marcador de repositorio sem commit, sai inteiro (o ensaio da 0.5.0 viu "desconhe").
78
+ */
79
+ function shaCurto(valor) {
80
+ return /^[0-9a-f]{7,64}$/i.test(valor) ? valor.slice(0, 8) : valor;
81
+ }
82
+ /** A branch para onde o HEAD aponta, com ou sem commit; `null` com o HEAD destacado. */
83
+ function branchDoHead(dir) {
84
+ const r = exec('git', ['symbolic-ref', '--quiet', '--short', 'HEAD'], dir);
85
+ return r.ok && r.stdout.trim() ? r.stdout.trim() : null;
86
+ }
87
+ /**
88
+ * O binario esta no PATH? A busca e do proprio processo, como o `which` fazia: o primeiro arquivo
89
+ * regular executavel na ordem do PATH. Registro R3 do ensaio da 0.5.0: sem o binario `which` na
90
+ * maquina, o doctor dizia que o `git` e o `claude` faltavam. Entrada vazia do PATH e pulada (nao vira
91
+ * o diretorio atual), e nome com barra e conferido direto.
92
+ */
93
+ function noPath(bin, env = process.env) {
94
+ const executavel = (alvo) => {
95
+ try {
96
+ if (!fs.statSync(alvo).isFile())
97
+ return false;
98
+ fs.accessSync(alvo, fs.constants.X_OK);
99
+ return true;
100
+ }
101
+ catch {
102
+ return false;
103
+ }
104
+ };
105
+ if (bin.includes('/'))
106
+ return executavel(bin) ? bin : null;
107
+ for (const dir of (env.PATH ?? '').split(path.delimiter)) {
108
+ if (dir && executavel(path.join(dir, bin)))
109
+ return path.join(dir, bin);
110
+ }
111
+ return null;
112
+ }
113
+ /** O `.gitignore` que o ork poe na pasta que e da maquina (fatia 2 do ensaio da 0.5.0, P3). */
114
+ exports.GITIGNORE_DA_MAQUINA = '# Criado pelo ork: esta pasta é da máquina, não do repositório.\n*\n';
115
+ /**
116
+ * Fatia 2 do ensaio da 0.5.0 (P3): a pasta que o ork cria para a maquina (o estado em `.orkastery/`
117
+ * e a pasta das worktrees) fica fora do git por um `.gitignore` proprio com `*`; o `.gitignore` do
118
+ * usuario nao e tocado. Nunca sobrescreve um existente, so vale para pasta dentro do repositorio e
119
+ * nao cria quando o repositorio ja rastreia algo nela: o projeto que versiona o proprio estado segue
120
+ * versionando. Devolve se criou.
121
+ */
122
+ function ignorarPastaNoGit(dir, raizDoRepo) {
123
+ const relativo = path.relative(raizDoRepo, dir);
124
+ if (!relativo || relativo.startsWith('..') || path.isAbsolute(relativo))
125
+ return false;
126
+ const arquivo = path.join(dir, '.gitignore');
127
+ if (fs.existsSync(arquivo))
128
+ return false;
129
+ const rastreados = exec('git', ['ls-files', '-z', '--', relativo], raizDoRepo);
130
+ if (rastreados.ok && rastreados.stdout.length > 0)
131
+ return false;
132
+ fs.mkdirSync(dir, { recursive: true });
133
+ try {
134
+ fs.writeFileSync(arquivo, exports.GITIGNORE_DA_MAQUINA, { flag: 'wx' });
135
+ }
136
+ catch {
137
+ return false;
138
+ }
139
+ return true;
73
140
  }
74
141
  /** Sobe a arvore de diretorios procurando um arquivo. Retorna o diretorio que o contem. */
75
142
  function subirAte(dirInicial, arquivo) {