@orkastery/cli 0.2.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 (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -0
  3. package/adapters/README.md +22 -0
  4. package/adapters/claude-code/.claude-plugin/marketplace.json +15 -0
  5. package/adapters/claude-code/.claude-plugin/plugin.json +31 -0
  6. package/adapters/claude-code/README.md +102 -0
  7. package/adapters/claude-code/agents/ork-check.md +32 -0
  8. package/adapters/claude-code/agents/ork-go.md +32 -0
  9. package/adapters/claude-code/agents/ork-goal.md +33 -0
  10. package/adapters/claude-code/agents/ork-master.md +32 -0
  11. package/adapters/claude-code/agents/ork-plan.md +32 -0
  12. package/adapters/claude-code/agents/ork-ship.md +32 -0
  13. package/adapters/claude-code/commands/check.md +45 -0
  14. package/adapters/claude-code/commands/go.md +46 -0
  15. package/adapters/claude-code/commands/goal.md +54 -0
  16. package/adapters/claude-code/commands/master.md +42 -0
  17. package/adapters/claude-code/commands/ork.md +37 -0
  18. package/adapters/claude-code/commands/plan.md +47 -0
  19. package/adapters/claude-code/commands/ship.md +45 -0
  20. package/adapters/claude-code/hooks/hooks.json +17 -0
  21. package/adapters/claude-code/hooks/ork-guard.js +130 -0
  22. package/adapters/hermes/README.md +45 -0
  23. package/adapters/hermes/bin/ork-abrir-thread.sh +25 -0
  24. package/adapters/hermes/hermes.plugin.json +21 -0
  25. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +116 -0
  26. package/adapters/openclaw/README.md +63 -0
  27. package/adapters/openclaw/bin/ork-abrir-thread.sh +24 -0
  28. package/adapters/openclaw/openclaw.plugin.json +138 -0
  29. package/dist/adapters/claude-bg.js +308 -0
  30. package/dist/auditoria.js +848 -0
  31. package/dist/auditrun.js +976 -0
  32. package/dist/board.js +314 -0
  33. package/dist/canarios.js +271 -0
  34. package/dist/catalogo.js +124 -0
  35. package/dist/ciclos.js +156 -0
  36. package/dist/claims.js +226 -0
  37. package/dist/divida.js +374 -0
  38. package/dist/doctor.js +246 -0
  39. package/dist/evalrunner.js +458 -0
  40. package/dist/fix.js +450 -0
  41. package/dist/gates.js +92 -0
  42. package/dist/handoff.js +521 -0
  43. package/dist/hosts.js +331 -0
  44. package/dist/index.js +1910 -0
  45. package/dist/init.js +231 -0
  46. package/dist/leases.js +508 -0
  47. package/dist/ledger.js +138 -0
  48. package/dist/manifest.js +255 -0
  49. package/dist/master.js +453 -0
  50. package/dist/memoria.js +773 -0
  51. package/dist/modos.js +220 -0
  52. package/dist/orkmind.js +487 -0
  53. package/dist/orquestracao.js +559 -0
  54. package/dist/phase.js +572 -0
  55. package/dist/policies.js +176 -0
  56. package/dist/prompts.js +406 -0
  57. package/dist/ratelimit.js +179 -0
  58. package/dist/recall.js +252 -0
  59. package/dist/retry.js +917 -0
  60. package/dist/sandbox.js +94 -0
  61. package/dist/sessoes.js +104 -0
  62. package/dist/ship.js +551 -0
  63. package/dist/slug.js +100 -0
  64. package/dist/superficie.js +1347 -0
  65. package/dist/thread.js +333 -0
  66. package/dist/tokens.js +228 -0
  67. package/dist/types.js +20 -0
  68. package/dist/util.js +156 -0
  69. package/dist/verify.js +262 -0
  70. package/dist/worktree.js +576 -0
  71. package/dist/yaml.js +112 -0
  72. package/eval/README.md +85 -0
  73. package/eval/casos/check-quality.json +103 -0
  74. package/eval/casos/code-reviewer.json +102 -0
  75. package/eval/casos/decision-triage.json +103 -0
  76. package/eval/casos/go-implementation.json +103 -0
  77. package/eval/casos/goal-definition.json +103 -0
  78. package/eval/casos/master-metrics.json +103 -0
  79. package/eval/casos/narrative-guardian.json +163 -0
  80. package/eval/casos/orkastery-bootstrap.json +102 -0
  81. package/eval/casos/plan-specification.json +102 -0
  82. package/eval/casos/roadmap-keeper.json +103 -0
  83. package/eval/casos/scope-check-capability-map.json +83 -0
  84. package/eval/casos/security-auditor.json +102 -0
  85. package/eval/casos/ship-release.json +122 -0
  86. package/eval/casos/test-engineer.json +122 -0
  87. package/eval/casos/thread-state.json +82 -0
  88. package/eval/casos/thread-tracing.json +83 -0
  89. package/eval/casos/web-performance-auditor.json +102 -0
  90. package/eval/fixtures/b0-slug-e-modos/caso.json +56 -0
  91. package/eval/fixtures/b2-master-log/caso.json +91 -0
  92. package/eval/fixtures/fx-concurrency/caso.json +15 -0
  93. package/eval/fixtures/fx-hallucination/caso.json +12 -0
  94. package/eval/fixtures/fx-happy/caso.json +16 -0
  95. package/eval/fixtures/fx-schema-drift/caso.json +15 -0
  96. package/eval/fixtures/fx-stale-base/caso.json +12 -0
  97. package/eval/fixtures/fx-wiki-destroy/caso.json +16 -0
  98. package/eval/fixtures/superficie-de-rede/README.md +22 -0
  99. package/eval/fixtures/superficie-de-rede/api-express.js +30 -0
  100. package/eval/fixtures/superficie-de-rede/api_fastapi.py +17 -0
  101. package/eval/fixtures/superficie-de-rede/api_gin.go +18 -0
  102. package/package.json +56 -0
  103. package/references/README.md +25 -0
  104. package/references/code-review-axes.md +82 -0
  105. package/references/definition-of-done.md +74 -0
  106. package/references/performance-checklist.md +53 -0
  107. package/references/security-checklist.md +101 -0
  108. package/references/testing-patterns.md +51 -0
  109. package/skills/README.md +43 -0
  110. package/skills/core/orkastery-bootstrap/SKILL.md +89 -0
  111. package/skills/core/thread-state/SKILL.md +86 -0
  112. package/skills/governance/decision-triage/SKILL.md +65 -0
  113. package/skills/governance/narrative-guardian/SKILL.md +66 -0
  114. package/skills/governance/roadmap-keeper/SKILL.md +60 -0
  115. package/skills/governance/scope-check-capability-map/SKILL.md +61 -0
  116. package/skills/observability/thread-tracing/SKILL.md +63 -0
  117. package/skills/phases/check-quality/SKILL.md +87 -0
  118. package/skills/phases/go-implementation/SKILL.md +70 -0
  119. package/skills/phases/goal-definition/SKILL.md +69 -0
  120. package/skills/phases/master-metrics/SKILL.md +65 -0
  121. package/skills/phases/plan-specification/SKILL.md +67 -0
  122. package/skills/phases/ship-release/SKILL.md +68 -0
  123. package/skills/reviewers/code-reviewer/SKILL.md +61 -0
  124. package/skills/reviewers/security-auditor/SKILL.md +68 -0
  125. package/skills/reviewers/test-engineer/SKILL.md +66 -0
  126. package/skills/reviewers/web-performance-auditor/SKILL.md +63 -0
@@ -0,0 +1,976 @@
1
+ "use strict";
2
+ /**
3
+ * `ork audit run|list|show|ingest|verify|report`: o ciclo de vida de uma rodada de
4
+ * auditoria periodica (bloco B5).
5
+ *
6
+ * O contrato de disparo da visao (secao 5.1) esta inteiro aqui, e nada dele e novo:
7
+ *
8
+ * - o orquestrador (cron do Hermes, schedule do OpenClaw, hook do Claude Code) chama
9
+ * `ork audit run <pack> --profile <p> [--since Nd]`;
10
+ * - o `ork` monta o prompt do pack e despacha pelo RUNTIME ADAPTER QUE JA EXISTE
11
+ * (`adapters/claude-bg`), o mesmo do `ork phase run`. Nenhum mecanismo de runtime novo
12
+ * foi inventado para o auditor, e as policies do manifesto valem no despacho da rodada
13
+ * exatamente como valem no despacho de uma fase;
14
+ * - os achados voltam pelo `ork` (unico escritor) e ficam sujeitos a claims;
15
+ * - a saida termina no bloco obrigatorio de propostas para o roadmap do produto.
16
+ */
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || (function () {
34
+ var ownKeys = function(o) {
35
+ ownKeys = Object.getOwnPropertyNames || function (o) {
36
+ var ar = [];
37
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
+ return ar;
39
+ };
40
+ return ownKeys(o);
41
+ };
42
+ return function (mod) {
43
+ if (mod && mod.__esModule) return mod;
44
+ var result = {};
45
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
+ __setModuleDefault(result, mod);
47
+ return result;
48
+ };
49
+ })();
50
+ Object.defineProperty(exports, "__esModule", { value: true });
51
+ exports.PACK_DA_SUPERFICIE = exports.VERSAO_DA_RODADA = void 0;
52
+ exports.dirAuditorias = dirAuditorias;
53
+ exports.dirRodada = dirRodada;
54
+ exports.caminhoRodada = caminhoRodada;
55
+ exports.listarRodadas = listarRodadas;
56
+ exports.lerRodada = lerRodada;
57
+ exports.gravarRodada = gravarRodada;
58
+ exports.novoIdDeRodada = novoIdDeRodada;
59
+ exports.formatoDosAchados = formatoDosAchados;
60
+ exports.varrerSuperficieDaRodada = varrerSuperficieDaRodada;
61
+ exports.rodarAuditoria = rodarAuditoria;
62
+ exports.entradaDoBruto = entradaDoBruto;
63
+ exports.registrarAchadoDaRodada = registrarAchadoDaRodada;
64
+ exports.ingerirAchados = ingerirAchados;
65
+ exports.verificarRodada = verificarRodada;
66
+ exports.caminhoRelatorio = caminhoRelatorio;
67
+ exports.relatorioDaRodada = relatorioDaRodada;
68
+ exports.tabelaDeRodadas = tabelaDeRodadas;
69
+ exports.textoDaRodada = textoDaRodada;
70
+ exports.tabelaDoLedgerDaRodada = tabelaDoLedgerDaRodada;
71
+ exports.resumoDoBoard = resumoDoBoard;
72
+ exports.abrirThreadDoAchado = abrirThreadDoAchado;
73
+ const fs = __importStar(require("node:fs"));
74
+ const path = __importStar(require("node:path"));
75
+ const adapter = __importStar(require("./adapters/claude-bg"));
76
+ const auditoria_1 = require("./auditoria");
77
+ const phase_1 = require("./phase");
78
+ const policies_1 = require("./policies");
79
+ const manifest_1 = require("./manifest");
80
+ const ledger_1 = require("./ledger");
81
+ const divida_1 = require("./divida");
82
+ const claims_1 = require("./claims");
83
+ const superficie_1 = require("./superficie");
84
+ const thread_1 = require("./thread");
85
+ const util_1 = require("./util");
86
+ const verify_1 = require("./verify");
87
+ /** Versao do formato de `run.json`. */
88
+ exports.VERSAO_DA_RODADA = 1;
89
+ /** Diretorio das rodadas de auditoria do projeto. */
90
+ function dirAuditorias(raiz) {
91
+ return path.join((0, manifest_1.dirEstado)(raiz), 'audits');
92
+ }
93
+ /** Diretorio de uma rodada. */
94
+ function dirRodada(raiz, id) {
95
+ return path.join(dirAuditorias(raiz), id);
96
+ }
97
+ /** Caminho do `run.json` da rodada. */
98
+ function caminhoRodada(raiz, id) {
99
+ return path.join(dirRodada(raiz, id), 'run.json');
100
+ }
101
+ /** Ids das rodadas existentes, em ordem alfabetica (que e cronologica pelo carimbo). */
102
+ function listarRodadas(raiz) {
103
+ const dir = dirAuditorias(raiz);
104
+ if (!fs.existsSync(dir))
105
+ return [];
106
+ return fs
107
+ .readdirSync(dir, { withFileTypes: true })
108
+ .filter((e) => e.isDirectory() && fs.existsSync(caminhoRodada(raiz, e.name)))
109
+ .map((e) => e.name)
110
+ .sort();
111
+ }
112
+ /** Le uma rodada do disco. */
113
+ function lerRodada(raiz, id) {
114
+ const caminho = caminhoRodada(raiz, id);
115
+ if (!fs.existsSync(caminho)) {
116
+ throw new Error(`rodada de auditoria "${id}" nao encontrada em ${dirAuditorias(raiz)}`);
117
+ }
118
+ return (0, util_1.lerJson)(caminho);
119
+ }
120
+ /** Grava a rodada. */
121
+ function gravarRodada(raiz, rodada) {
122
+ (0, util_1.gravarJson)(caminhoRodada(raiz, rodada.id), rodada);
123
+ }
124
+ /**
125
+ * `<abbrev>-<pack>-<AAAA-MM-DD>-<n>`: id da rodada, tambem usado como `--name` da sessao.
126
+ *
127
+ * A data vai com hifen de proposito. MEDIDO em 03/09/2026: com `AAAAMMDD` colado, o
128
+ * `extrairSessionId` do adapter casa `20260903` como id curto de sessao (8 caracteres
129
+ * hexadecimais) e a rodada grava um sessionId que nao existe. Com a data separada nao ha
130
+ * token de 8 hex no nome, e o id que fica no `run.json` e o da sessao de verdade.
131
+ */
132
+ function novoIdDeRodada(raiz, abbrev, pack, quando = new Date()) {
133
+ const dia = String(quando.getFullYear()) +
134
+ '-' +
135
+ String(quando.getMonth() + 1).padStart(2, '0') +
136
+ '-' +
137
+ String(quando.getDate()).padStart(2, '0');
138
+ const base = `${abbrev}-${pack}-${dia}`;
139
+ const existentes = listarRodadas(raiz).filter((id) => id.startsWith(base + '-'));
140
+ return `${base}-${existentes.length + 1}`;
141
+ }
142
+ /**
143
+ * O texto do `FORMATO.md` que a rodada deixa para o auditor saber o que produzir.
144
+ *
145
+ * Os campos obrigatorios vem de `CAMPOS_DA_PROPOSTA`, a MESMA lista que `faltasDaProposta`
146
+ * usa para recusar achado incompleto: o documento nao pode prometer um formato diferente do
147
+ * que o `ork` aceita.
148
+ */
149
+ function formatoDosAchados(rodada, varredura) {
150
+ const pack = auditoria_1.PACKS[rodada.pack];
151
+ const dir = path.join(manifest_1.DIR_ESTADO, 'audits', rodada.id);
152
+ const exemplo = {
153
+ regra: pack.regras[0].id,
154
+ severidade: 'critico | maior | menor',
155
+ };
156
+ for (const c of divida_1.CAMPOS_DA_PROPOSTA) {
157
+ if (c.campo === 'arquivo') {
158
+ exemplo.arquivo = c.descricao.replace('a evidencia principal, no formato ', '');
159
+ exemplo.descricao = 'o contexto que a linha nao cabe';
160
+ exemplo.alegacao = 'a alegacao verificavel do auditor sobre este achado';
161
+ exemplo.verificar = ['comando que reexecuta a evidencia e sai 0 quando o achado procede'];
162
+ continue;
163
+ }
164
+ exemplo[c.campo] = c.descricao;
165
+ }
166
+ return `# Formato dos achados da rodada ${rodada.id}
167
+
168
+ Escreva \`achados.json\` neste diretorio e registre com:
169
+
170
+ ork audit ingest ${rodada.id} --arquivo ${dir}/achados.json
171
+
172
+ Estrutura (o \`ork\` recusa o achado que nao trouxer a proposta completa):
173
+
174
+ \`\`\`json
175
+ ${JSON.stringify({ achados: [exemplo] }, null, 2)}
176
+ \`\`\`
177
+
178
+ Regras que o \`ork\` verifica sozinho:
179
+
180
+ - \`regra\` precisa ser uma das do pack ${pack.id}: ${pack.regras.map((r) => r.id).join(', ')}.
181
+ - Campos obrigatorios (motivo tipado \`achado.sem-proposta\` quando faltar algum):
182
+ ${divida_1.CAMPOS_DA_PROPOSTA.map((c) => ` - \`${c.campo}\`: ${c.descricao}`).join('\n')}
183
+ - \`verificar\` e o que torna o achado sustentavel: sem comando, uma alegacao negativa ou
184
+ absoluta REPROVA em \`ork audit verify ${rodada.id}\`. O auditor nao tem self-report.
185
+ - A ingestao e idempotente: rodar \`ork audit ingest\` duas vezes nao duplica achado.
186
+ ${varredura ? '\n' + (0, superficie_1.blocoDoFormato)(varredura.resultado, varredura.registrados) + '\n' : ''}`;
187
+ }
188
+ // ---------------------------------------------------------------------------
189
+ // A varredura deterministica de superficie de rede dentro da rodada (SP8..SP12)
190
+ // ---------------------------------------------------------------------------
191
+ /** O unico pack com varredura deterministica propria: a extensao de superficie do B5. */
192
+ exports.PACK_DA_SUPERFICIE = 'security-privacy';
193
+ /**
194
+ * Roda a varredura de superficie de rede no escopo da rodada e, quando pedido, registra os
195
+ * achados no MESMO board de divida.
196
+ *
197
+ * Ela nao inventa um segundo caminho de achado: cada resultado da varredura vira
198
+ * `AchadoBruto` e passa por `registrarAchadoDaRodada`, com a mesma validacao de proposta
199
+ * completa, a mesma claim e a mesma guarda de duplicata da ingestao do auditor com LLM.
200
+ *
201
+ * `--dry-run` varre e NAO registra: a simulacao continua sem gravar achado, e o operador
202
+ * registra depois com `ork audit surface --registrar <rodada>` se quiser.
203
+ */
204
+ function varrerSuperficieDaRodada(carregado, rodadaId, opcoes = {}) {
205
+ const { raiz } = carregado;
206
+ const rodada = lerRodada(raiz, rodadaId);
207
+ const resultado = (0, superficie_1.varrerSuperficie)(raiz, {
208
+ escopo: rodada.escopo,
209
+ limitePorRegra: opcoes.limitePorRegra,
210
+ });
211
+ const registrados = [];
212
+ const duplicados = [];
213
+ const recusados = [];
214
+ if (opcoes.registrar) {
215
+ for (const a of resultado.achados) {
216
+ const bruto = (0, superficie_1.entradaBrutaDaSuperficie)(a);
217
+ const atual = lerRodada(raiz, rodadaId);
218
+ const jaTinha = (0, divida_1.acharDuplicado)((0, divida_1.achadosDaRodada)(raiz, rodadaId), entradaDoBruto(atual, bruto));
219
+ if (jaTinha) {
220
+ duplicados.push(jaTinha);
221
+ continue;
222
+ }
223
+ try {
224
+ registrados.push(registrarAchadoDaRodada(carregado, rodadaId, bruto));
225
+ }
226
+ catch (e) {
227
+ recusados.push({ achado: a, erro: e.message });
228
+ }
229
+ }
230
+ }
231
+ const resumo = {
232
+ arquivosLidos: resultado.arquivosLidos,
233
+ arquivosComRota: resultado.arquivosComRota,
234
+ rotas: resultado.rotas,
235
+ frameworks: resultado.frameworks,
236
+ encontrados: resultado.achados.length,
237
+ registrados: registrados.length,
238
+ aConfirmar: resultado.achados.filter((a) => a.confianca === 'baixa').length,
239
+ detalhe: resultado.detalhe,
240
+ };
241
+ const dir = dirRodada(raiz, rodadaId);
242
+ (0, util_1.gravarJson)(path.join(dir, 'superficie.json'), {
243
+ versao: 1,
244
+ rodada: rodadaId,
245
+ varredura: resultado,
246
+ registrados: registrados.map((a) => a.id),
247
+ });
248
+ const depois = lerRodada(raiz, rodadaId);
249
+ depois.superficie = resumo;
250
+ gravarRodada(raiz, depois);
251
+ (0, ledger_1.registrar)(dir, rodadaId, ledger_1.TIPOS_DE_EVENTO.superficieVarrida, {
252
+ pack: rodada.pack,
253
+ arquivos: resumo.arquivosLidos,
254
+ rotas: resumo.rotas,
255
+ frameworks: resumo.frameworks,
256
+ encontrados: resumo.encontrados,
257
+ registrados: registrados.map((a) => a.id),
258
+ duplicados: duplicados.map((a) => a.id),
259
+ recusados: recusados.map((r) => `${r.achado.regra} ${r.achado.arquivo}:${r.achado.linha}: ${r.erro}`),
260
+ aConfirmar: resumo.aConfirmar,
261
+ truncados: resultado.truncados,
262
+ detalhe: resumo.detalhe,
263
+ determinista: true,
264
+ });
265
+ return { rodada: rodadaId, resultado, registrados, duplicados, recusados, resumo };
266
+ }
267
+ /**
268
+ * `ork audit run <pack>`: monta o prompt do pack e despacha pelo runtime adapter existente.
269
+ *
270
+ * A ordem das reprovacoes e deliberada: estagio, depois custo, depois policy. Um pack que
271
+ * nem devia estar ativo nao chega a gastar janela nenhuma da assinatura.
272
+ */
273
+ function rodarAuditoria(carregado, pack, opcoes = {}) {
274
+ const { raiz, manifesto } = carregado;
275
+ const quando = opcoes.quando ?? new Date();
276
+ const estagio = manifesto.project.stage;
277
+ const postura = (0, auditoria_1.posturaDoPack)(estagio);
278
+ const perfil = opcoes.perfil ?? manifesto.board.default;
279
+ const produto = manifesto.project.name;
280
+ const id = novoIdDeRodada(raiz, manifesto.project.abbrev, pack, quando);
281
+ const dir = dirRodada(raiz, id);
282
+ const janela = (0, auditoria_1.janelaDeCusto)(manifesto, quando);
283
+ const escopo = (0, auditoria_1.escopoDaRodada)(raiz, manifesto, { since: opcoes.since, tudo: opcoes.tudo, pack });
284
+ const effort = opcoes.effort ?? manifesto.audit?.effort ?? 'eco';
285
+ const base = {
286
+ versao: exports.VERSAO_DA_RODADA,
287
+ id,
288
+ pack,
289
+ perfil,
290
+ produto,
291
+ estagio,
292
+ postura,
293
+ escopo,
294
+ custo: { janela, effort, model: opcoes.model ?? manifesto.runtime.model, autorizadaPor: null },
295
+ runtime: manifesto.runtime.adapter,
296
+ sessionId: null,
297
+ slug: id,
298
+ promptPath: path.relative(raiz, path.join(dir, 'prompt.md')),
299
+ promptSha256: '',
300
+ verificada: false,
301
+ memory: manifesto.memory.mode,
302
+ criadaEm: (0, util_1.agora)(),
303
+ status: 'bloqueada',
304
+ motivo: null,
305
+ detalhe: '',
306
+ achados: [],
307
+ veredito: null,
308
+ };
309
+ const bloquear = (motivo, detalhe, correcao) => {
310
+ const rodada = { ...base, status: 'bloqueada', motivo, detalhe };
311
+ gravarRodada(raiz, rodada);
312
+ (0, ledger_1.registrar)(dir, id, ledger_1.TIPOS_DE_EVENTO.auditoriaBloqueada, {
313
+ pack,
314
+ perfil,
315
+ estagio,
316
+ motivo,
317
+ detalhe,
318
+ correcao,
319
+ reprovaEmTodoModo: true,
320
+ });
321
+ return rodada;
322
+ };
323
+ // 1. Hardening por estagio: o pack inativo nem monta prompt.
324
+ if (!(0, auditoria_1.packAtivo)(pack, estagio) && !opcoes.forcar) {
325
+ return bloquear('pack.inativo-no-estagio', `o pack ${pack} entra a partir do estagio ${auditoria_1.PACKS[pack].estagioMinimo}, e o produto declara stage ${estagio}`, `promova project.stage no manifesto, ou rode com --forcar assumindo o custo (ativos agora: ${(0, auditoria_1.packsAtivos)(estagio).join(', ')})`);
326
+ }
327
+ // 2. Governanca de custo: fora da janela ociosa a rodada so anda com autorizacao nomeada.
328
+ if (janela.declarada && !janela.dentro && !opcoes.agora) {
329
+ return bloquear('custo.fora-da-janela', janela.detalhe, `espere a janela ${janela.inicio}-${janela.fim}, ou autorize agora: ork audit run ${pack} --agora "<quem autoriza>"`);
330
+ }
331
+ if (opcoes.agora)
332
+ base.custo.autorizadaPor = opcoes.agora;
333
+ // 3. O prompt exato, com o mesmo hash que vai para o run.json e para o ledger.
334
+ const prompt = (0, auditoria_1.montarPromptDeAuditoria)({
335
+ pack,
336
+ produto,
337
+ perfil,
338
+ estagio,
339
+ postura,
340
+ rodada: id,
341
+ dirDaRodada: path.join(manifest_1.DIR_ESTADO, 'audits', id),
342
+ escopo,
343
+ janela,
344
+ effort,
345
+ }, raiz);
346
+ const sha = (0, phase_1.hashDoPrompt)(prompt);
347
+ base.promptSha256 = sha;
348
+ // 4. As MESMAS policies do despacho de fase valem aqui: o auditor nao tem regime proprio.
349
+ const violacoes = (0, policies_1.avaliarPolicies)(manifesto, { gate: 'phase.dispatch', prompt });
350
+ const bloqueiam = (0, policies_1.bloqueantes)(violacoes);
351
+ if (bloqueiam.length > 0) {
352
+ return bloquear('policy.violation', bloqueiam.map((v) => `${v.policy}: ${v.detalhe}`).join('; '), bloqueiam.map((v) => v.correcao).join('; '));
353
+ }
354
+ (0, util_1.gravar)(path.join(dir, 'prompt.md'), prompt);
355
+ if (opcoes.dryRun) {
356
+ const rodada = {
357
+ ...base,
358
+ status: 'ensaio',
359
+ detalhe: 'simulacao (--dry-run): o prompt foi montado e nada foi despachado',
360
+ };
361
+ gravarRodada(raiz, rodada);
362
+ // A varredura de superficie e local e nao gasta janela nenhuma, entao ela roda ate no
363
+ // ensaio. O que o ensaio NAO faz e gravar achado no board.
364
+ const varredura = pack === exports.PACK_DA_SUPERFICIE ? varrerSuperficieDaRodada(carregado, id, { registrar: false }) : null;
365
+ (0, util_1.gravar)(path.join(dir, 'FORMATO.md'), formatoDosAchados(rodada, varredura ? { resultado: varredura.resultado, registrados: false } : undefined));
366
+ return varredura ? lerRodada(raiz, id) : rodada;
367
+ }
368
+ const resultado = adapter.despachar({
369
+ prompt,
370
+ nome: id,
371
+ cwd: raiz,
372
+ model: base.custo.model,
373
+ effort,
374
+ });
375
+ if (!resultado.ok || !resultado.sessionId) {
376
+ const rodada = {
377
+ ...base,
378
+ status: 'bloqueada',
379
+ motivo: 'runtime.unavailable',
380
+ detalhe: resultado.erro ?? 'sem sessionId na saida do runtime',
381
+ };
382
+ gravarRodada(raiz, rodada);
383
+ (0, ledger_1.registrar)(dir, id, ledger_1.TIPOS_DE_EVENTO.auditoriaBloqueada, {
384
+ pack,
385
+ motivo: 'runtime.unavailable',
386
+ detalhe: rodada.detalhe,
387
+ stderr: resultado.stderr.slice(0, 500),
388
+ comando: resultado.comando.map((c, i) => (i === 2 ? `<prompt:${sha.slice(0, 8)}>` : c)),
389
+ });
390
+ return rodada;
391
+ }
392
+ const rodada = {
393
+ ...base,
394
+ status: 'despachada',
395
+ sessionId: resultado.sessionId,
396
+ verificada: resultado.verificada,
397
+ detalhe: `rodada despachada pelo runtime adapter ${manifesto.runtime.adapter}`,
398
+ };
399
+ gravarRodada(raiz, rodada);
400
+ const varredura = pack === exports.PACK_DA_SUPERFICIE ? varrerSuperficieDaRodada(carregado, id, { registrar: true }) : null;
401
+ (0, util_1.gravar)(path.join(dir, 'FORMATO.md'), formatoDosAchados(rodada, varredura ? { resultado: varredura.resultado, registrados: true } : undefined));
402
+ (0, ledger_1.registrar)(dir, id, ledger_1.TIPOS_DE_EVENTO.auditoriaDespachada, {
403
+ pack,
404
+ perfil,
405
+ produto,
406
+ estagio,
407
+ postura,
408
+ sessionId: resultado.sessionId,
409
+ runtime: manifesto.runtime.adapter,
410
+ effort,
411
+ model: base.custo.model,
412
+ janela: janela.detalhe,
413
+ autorizadaPor: base.custo.autorizadaPor,
414
+ escopo: escopo.detalhe,
415
+ arquivosNoEscopo: escopo.total,
416
+ graphify: escopo.graphify,
417
+ promptPath: rodada.promptPath,
418
+ promptSha256: sha,
419
+ encontrada: resultado.verificada,
420
+ comando: resultado.comando.map((c, i) => (i === 2 ? `<prompt:${sha.slice(0, 8)}>` : c)),
421
+ superficie: varredura ? varredura.resumo.detalhe : null,
422
+ });
423
+ return varredura ? lerRodada(raiz, id) : rodada;
424
+ }
425
+ /** Converte o achado bruto do auditor na entrada tipada do board. */
426
+ function entradaDoBruto(rodada, bruto) {
427
+ const severidade = (0, divida_1.parseSeveridade)(bruto.severidade) ?? 'menor';
428
+ const verificar = Array.isArray(bruto.verificar)
429
+ ? bruto.verificar
430
+ : typeof bruto.verificar === 'string' && bruto.verificar.trim() !== ''
431
+ ? [bruto.verificar]
432
+ : [];
433
+ return {
434
+ rodada: rodada.id,
435
+ pack: rodada.pack,
436
+ regra: (bruto.regra ?? '').trim().toUpperCase(),
437
+ severidade,
438
+ titulo: bruto.titulo ?? '',
439
+ arquivo: bruto.arquivo ?? '',
440
+ descricao: bruto.descricao ?? '',
441
+ alegacao: bruto.alegacao,
442
+ verificar,
443
+ impacto: bruto.impacto ?? '',
444
+ fix: bruto.fix ?? '',
445
+ estimativa: bruto.estimativa ?? '',
446
+ irreversivel: bruto.irreversivel ?? '',
447
+ produto: rodada.produto,
448
+ estagio: rodada.estagio,
449
+ postura: rodada.postura,
450
+ memory: rodada.memory,
451
+ };
452
+ }
453
+ /** Registra um achado da rodada no board e no ledger da rodada. */
454
+ function registrarAchadoDaRodada(carregado, rodadaId, bruto) {
455
+ const { raiz } = carregado;
456
+ const rodada = lerRodada(raiz, rodadaId);
457
+ // Rodada reprovada no gate (estagio, custo ou policy) nao produz achado: aceitar achado
458
+ // aqui seria o caminho obvio para contornar o hardening por estagio pela porta dos fundos.
459
+ if (rodada.status === 'bloqueada') {
460
+ throw new Error(`a rodada ${rodadaId} esta BLOQUEADA (motivo tipado: ${rodada.motivo}) e nao registra achado. ` +
461
+ `Detalhe: ${rodada.detalhe}`);
462
+ }
463
+ const entrada = entradaDoBruto(rodada, bruto);
464
+ // Idempotencia: o proprio prompt manda o auditor rodar `ork audit ingest`, entao a
465
+ // ingestao roda de novo toda vez que alguem reprocessa o `achados.json`. Sem esta
466
+ // guarda, a segunda passada duplicaria os achados e inflaria a recorrencia por regra,
467
+ // que e exatamente o numero que propoe promover uma policy para bloqueante.
468
+ const duplicado = (0, divida_1.acharDuplicado)((0, divida_1.achadosDaRodada)(raiz, rodadaId), entrada);
469
+ if (duplicado) {
470
+ throw new Error(`achado.duplicado-na-rodada: a rodada ${rodadaId} ja registrou este achado como ` +
471
+ `${duplicado.id} (${duplicado.regra} em ${duplicado.arquivo}${duplicado.linha !== null ? ':' + duplicado.linha : ''})`);
472
+ }
473
+ const achado = (0, divida_1.registrarAchado)(raiz, entrada);
474
+ rodada.achados = [...new Set([...rodada.achados, achado.id])];
475
+ gravarRodada(raiz, rodada);
476
+ const dir = dirRodada(raiz, rodadaId);
477
+ (0, ledger_1.registrar)(dir, rodadaId, ledger_1.TIPOS_DE_EVENTO.achadoRegistrado, {
478
+ achado: achado.id,
479
+ pack: achado.pack,
480
+ regra: achado.regra,
481
+ severidade: achado.severidade,
482
+ arquivo: `${achado.arquivo}${achado.linha !== null ? ':' + achado.linha : ''}`,
483
+ claim: achado.claim.id,
484
+ negativa: achado.claim.negativa,
485
+ verificar: achado.claim.verificar,
486
+ estado: achado.claim.estado,
487
+ });
488
+ (0, ledger_1.registrar)(dir, rodadaId, ledger_1.TIPOS_DE_EVENTO.propostaGravada, {
489
+ achado: achado.id,
490
+ destino: achado.proposta.destino,
491
+ regime: achado.proposta.regime,
492
+ impacto: achado.proposta.impacto,
493
+ estimativa: achado.proposta.estimativa,
494
+ passoIrreversivel: achado.proposta.passoIrreversivel,
495
+ });
496
+ return achado;
497
+ }
498
+ /** `ork audit ingest`: registra em lote os achados que o auditor escreveu. */
499
+ function ingerirAchados(carregado, rodadaId, arquivo) {
500
+ const { raiz } = carregado;
501
+ lerRodada(raiz, rodadaId);
502
+ const caminho = path.isAbsolute(arquivo) ? arquivo : path.resolve(raiz, arquivo);
503
+ if (!fs.existsSync(caminho)) {
504
+ throw new Error(`arquivo de achados nao encontrado: ${caminho}`);
505
+ }
506
+ const conteudo = (0, util_1.lerJson)(caminho);
507
+ const brutos = Array.isArray(conteudo) ? conteudo : (conteudo.achados ?? []);
508
+ const registrados = [];
509
+ const duplicados = [];
510
+ const recusados = [];
511
+ brutos.forEach((bruto, i) => {
512
+ const rodada = lerRodada(raiz, rodadaId);
513
+ const jaTinha = (0, divida_1.acharDuplicado)((0, divida_1.achadosDaRodada)(raiz, rodadaId), entradaDoBruto(rodada, bruto));
514
+ if (jaTinha) {
515
+ duplicados.push(jaTinha);
516
+ return;
517
+ }
518
+ try {
519
+ registrados.push(registrarAchadoDaRodada(carregado, rodadaId, bruto));
520
+ }
521
+ catch (e) {
522
+ recusados.push({ indice: i, erro: e.message });
523
+ }
524
+ });
525
+ return { rodada: rodadaId, registrados, duplicados, recusados };
526
+ }
527
+ // ---------------------------------------------------------------------------
528
+ // O auditor sujeito a claims
529
+ // ---------------------------------------------------------------------------
530
+ /**
531
+ * `ork audit verify`: reexecuta no HEAD real a claim de cada achado da rodada.
532
+ *
533
+ * Chama a MESMA `verificarClaim` do `ork verify`. Nao existe julgamento de claim proprio da
534
+ * auditoria: a alegacao negativa sem comando reprova aqui como reprova numa fase, e a claim
535
+ * positiva sem comando avisa sem bloquear, como no gate do B1.
536
+ */
537
+ function verificarRodada(carregado, rodadaId) {
538
+ const { raiz } = carregado;
539
+ const rodada = lerRodada(raiz, rodadaId);
540
+ const cwd = raiz;
541
+ const commit = (0, verify_1.commitReal)(cwd);
542
+ // Achado descartado (nao procedia) e achado resolvido (divida paga) saem da reexecucao:
543
+ // no primeiro caso nao ha o que provar, e no segundo a claim REPROVARIA justamente porque
544
+ // o defeito foi corrigido, o que seria o veredito errado sobre o auditor.
545
+ const achados = (0, divida_1.achadosDaRodada)(raiz, rodadaId).filter((a) => a.estado !== 'descartado' && a.estado !== 'resolvido');
546
+ const resultados = achados.map((a) => (0, verify_1.verificarClaim)(a.claim, cwd));
547
+ for (let i = 0; i < achados.length; i++) {
548
+ const r = resultados[i];
549
+ const estado = r.verificado
550
+ ? 'verificado'
551
+ : r.motivo === 'claims.unverifiable'
552
+ ? 'nao-verificavel'
553
+ : 'reprovado';
554
+ if (achados[i].claim.estado !== estado) {
555
+ (0, divida_1.carimbarAchado)(raiz, achados[i], { claim: { ...achados[i].claim, estado } });
556
+ }
557
+ }
558
+ const motivos = [];
559
+ if (resultados.some((r) => r.motivo === 'claims.failed'))
560
+ motivos.push('claims.failed');
561
+ if (resultados.some((r) => r.motivo === 'claims.unverifiable'))
562
+ motivos.push('claims.unverifiable');
563
+ // `claims.unverifiable` avisa e nao bloqueia, igual ao gate do B1.
564
+ const ok = !motivos.includes('claims.failed');
565
+ const veredito = {
566
+ rodada: rodadaId,
567
+ commit,
568
+ cwd,
569
+ resultados,
570
+ motivos,
571
+ ok,
572
+ verificadoEm: (0, util_1.agora)(),
573
+ };
574
+ rodada.veredito = veredito;
575
+ if (rodada.status !== 'relatada')
576
+ rodada.status = 'verificada';
577
+ gravarRodada(raiz, rodada);
578
+ (0, ledger_1.registrar)(dirRodada(raiz, rodadaId), rodadaId, ledger_1.TIPOS_DE_EVENTO.auditoriaVerificada, {
579
+ pack: rodada.pack,
580
+ commit,
581
+ achados: achados.length,
582
+ claims: resultados.map((r) => ({
583
+ id: r.claim.id,
584
+ verificado: r.verificado,
585
+ motivo: r.motivo,
586
+ detalhe: r.detalhe,
587
+ })),
588
+ motivos,
589
+ veredito: ok ? 'achados sustentados' : 'reprovado',
590
+ });
591
+ return veredito;
592
+ }
593
+ /** Caminho do relatorio da rodada dentro do estado. */
594
+ function caminhoRelatorio(raiz, id) {
595
+ return path.join(dirRodada(raiz, id), 'RELATORIO.md');
596
+ }
597
+ /**
598
+ * O relatorio da rodada.
599
+ *
600
+ * O bloco "PROPOSTAS DE AJUSTE PARA O ROADMAP" e obrigatorio e sempre existe: com achados,
601
+ * ele lista as propostas; sem achados, ele diz que a rodada nao produziu proposta nenhuma.
602
+ * Lacuna publicada como lacuna, nunca omitida.
603
+ */
604
+ function relatorioDaRodada(carregado, rodadaId, opcoes = {}) {
605
+ const { raiz } = carregado;
606
+ const rodada = lerRodada(raiz, rodadaId);
607
+ const achados = (0, divida_1.achadosDaRodada)(raiz, rodadaId);
608
+ const pack = auditoria_1.PACKS[rodada.pack];
609
+ const L = [];
610
+ L.push(`# Auditoria ${rodada.pack} do produto ${rodada.produto} (rodada ${rodada.id})`);
611
+ L.push('');
612
+ L.push('## Contexto da rodada');
613
+ L.push('');
614
+ L.push(`- pack: ${pack.id} (${pack.titulo}), ${pack.regras.length} regras`);
615
+ L.push(`- perfil: ${rodada.perfil}`);
616
+ L.push(`- estagio do produto: ${rodada.estagio}`);
617
+ L.push(`- postura: ${rodada.postura} (${auditoria_1.DESCRICAO_DA_POSTURA[rodada.postura]})`);
618
+ L.push(`- escopo: ${rodada.escopo.detalhe}`);
619
+ L.push(`- Graphify: ${rodada.escopo.graphify}`);
620
+ L.push(`- custo: effort ${rodada.custo.effort}, model ${rodada.custo.model}, ${rodada.custo.janela.detalhe}`);
621
+ if (rodada.custo.autorizadaPor) {
622
+ L.push(`- rodada fora da janela ociosa AUTORIZADA por: ${rodada.custo.autorizadaPor}`);
623
+ }
624
+ L.push(`- runtime: ${rodada.runtime}, sessao ${rodada.sessionId ?? '(nao despachada)'}`);
625
+ if (rodada.superficie) {
626
+ const sup = rodada.superficie;
627
+ L.push(`- varredura deterministica de superficie de rede (SP8..SP12): ${sup.detalhe}` +
628
+ `; frameworks: ${sup.frameworks.length > 0 ? sup.frameworks.join(', ') : '(nenhum reconhecido)'}` +
629
+ `; registrados no board: ${sup.registrados}` +
630
+ (sup.aConfirmar > 0 ? `; ${sup.aConfirmar} com confianca baixa (pedem confirmacao humana)` : ''));
631
+ }
632
+ L.push(`- prompt: ${rodada.promptPath} (sha256 ${rodada.promptSha256})`);
633
+ L.push(`- status: ${rodada.status}${rodada.motivo ? ` (motivo tipado: ${rodada.motivo})` : ''}`);
634
+ L.push('');
635
+ L.push('## Achados');
636
+ L.push('');
637
+ if (achados.length === 0) {
638
+ L.push('Nenhum achado registrado nesta rodada.');
639
+ }
640
+ for (const a of achados) {
641
+ L.push(`### ${a.id} [${a.severidade}] ${a.regra}: ${a.titulo}`);
642
+ L.push('');
643
+ L.push(`- evidencia: \`${a.arquivo}${a.linha !== null ? ':' + a.linha : ''}\``);
644
+ if (a.descricao)
645
+ L.push(`- descricao: ${a.descricao}`);
646
+ L.push(`- claim ${a.claim.id} (${a.claim.estado}${a.claim.negativa ? ', negativa' : ''}): ${a.claim.alegacao}`);
647
+ L.push(`- reexecucao: ${a.claim.verificar.length > 0 ? '`' + a.claim.verificar.join(' && ') + '`' : 'NAO DECLARADA pelo auditor'}`);
648
+ L.push(`- estado no board: ${a.estado}${a.thread ? ` (thread ${a.thread})` : ''}`);
649
+ L.push('');
650
+ }
651
+ // O bloco obrigatorio. Ele existe em toda rodada, com ou sem achado.
652
+ L.push(`## PROPOSTAS DE AJUSTE PARA O ROADMAP DE ${rodada.produto.toUpperCase()}`);
653
+ L.push('');
654
+ L.push('Bloco obrigatorio da rodada (visao, secao 5.1). Nenhuma correcao foi aplicada: cada item ' +
655
+ 'abaixo e proposta enderecada ao roadmap do produto, para o humano decidir o que vira thread.');
656
+ L.push('');
657
+ // Achado resolvido (divida paga) e descartado (nao procedia) saem do bloco de propostas:
658
+ // o bloco e a fila do roadmap, e nao o historico da rodada, que fica em "Achados".
659
+ const propostas = achados.filter((a) => a.estado !== 'resolvido' && a.estado !== 'descartado');
660
+ const fechados = achados.filter((a) => a.estado === 'resolvido' || a.estado === 'descartado');
661
+ if (propostas.length === 0) {
662
+ L.push(achados.length === 0
663
+ ? '**Nenhuma proposta nesta rodada.** A rodada nao produziu achado, entao nao ha ajuste a propor.'
664
+ : '**Nenhuma proposta em aberto.** Todos os achados desta rodada ja foram resolvidos ou descartados.');
665
+ }
666
+ else {
667
+ L.push('| # | achado | evidencia | impacto | fix sugerido | passo irreversivel | estimativa | estado |');
668
+ L.push('|---|---|---|---|---|---|---|---|');
669
+ propostas.forEach((a, i) => {
670
+ const evidencia = `${a.arquivo}${a.linha !== null ? ':' + a.linha : ''}`;
671
+ L.push(`| ${i + 1} | ${a.id} ${a.titulo} | \`${evidencia}\` | ${a.proposta.impacto} | ` +
672
+ `${a.proposta.fix} | ${a.proposta.passoIrreversivel} | ${a.proposta.estimativa} | ${a.estado} |`);
673
+ });
674
+ const abertos = propostas.filter((a) => a.estado === 'aberto');
675
+ if (abertos.length > 0) {
676
+ L.push('');
677
+ L.push('Abrir a thread de qualquer uma delas, com a evidencia junto e sem retrabalho:');
678
+ L.push('');
679
+ L.push('```bash');
680
+ for (const a of abertos) {
681
+ L.push(`ork thread new "${a.titulo.slice(0, 40)}" --from-finding ${a.id}`);
682
+ }
683
+ L.push('```');
684
+ }
685
+ }
686
+ if (fechados.length > 0) {
687
+ L.push('');
688
+ L.push(`Fora do bloco por ja estarem fechados: ` +
689
+ fechados.map((a) => `${a.id} (${a.estado})`).join(', ') + '.');
690
+ }
691
+ L.push('');
692
+ L.push('## Destino das propostas');
693
+ L.push('');
694
+ L.push(`- regime de memoria declarado: \`${rodada.memory}\``);
695
+ L.push(`- board de divida: \`.orkastery/divida/board.jsonl\` (consulta: \`ork audit divida\`)`);
696
+ L.push(rodada.memory === 'orkmind'
697
+ ? '- colecao `roadmap` do OrkMind: gravacao chega no bloco B6.'
698
+ : '- colecao `roadmap` do OrkMind: NAO gravada, porque `memory: files`. Degradacao honesta, nao omissao.');
699
+ L.push('');
700
+ L.push('## Veredito das claims do auditor');
701
+ L.push('');
702
+ if (!rodada.veredito) {
703
+ L.push(`Ainda nao verificado. Rode: \`ork audit verify ${rodada.id}\``);
704
+ L.push('');
705
+ L.push('O auditor NAO tem direito a self-report: enquanto as claims dos achados nao forem');
706
+ L.push('reexecutadas no HEAD real, nenhum achado desta rodada pode virar thread.');
707
+ }
708
+ else {
709
+ const v = rodada.veredito;
710
+ L.push(`- HEAD real: ${v.commit}`);
711
+ L.push(`- verificado em: ${v.verificadoEm}`);
712
+ L.push(`- veredito: ${v.ok ? 'ACHADOS SUSTENTADOS' : 'REPROVADO'}`);
713
+ L.push(`- motivos tipados: ${v.motivos.length > 0 ? v.motivos.join(', ') : '(nenhum)'}`);
714
+ L.push('');
715
+ for (const r of v.resultados) {
716
+ L.push(` - ${r.claim.id}: ${r.verificado ? 'verificado' : 'NAO verificado'}` +
717
+ `${r.motivo ? ` (${r.motivo})` : ''} - ${r.detalhe}`);
718
+ }
719
+ }
720
+ L.push('');
721
+ const texto = L.join('\n') + '\n';
722
+ const caminho = caminhoRelatorio(raiz, rodadaId);
723
+ (0, util_1.gravar)(caminho, texto);
724
+ let publicado = null;
725
+ if (opcoes.publicar) {
726
+ publicado = path.join(raiz, 'docs', 'audit', `${rodada.id}.md`);
727
+ (0, util_1.gravar)(publicado, texto);
728
+ }
729
+ rodada.status = 'relatada';
730
+ gravarRodada(raiz, rodada);
731
+ (0, ledger_1.registrar)(dirRodada(raiz, rodadaId), rodadaId, ledger_1.TIPOS_DE_EVENTO.relatorioGerado, {
732
+ pack: rodada.pack,
733
+ achados: achados.length,
734
+ propostas: propostas.length,
735
+ relatorio: path.relative(raiz, caminho),
736
+ publicado: publicado ? path.relative(raiz, publicado) : null,
737
+ regime: rodada.memory,
738
+ });
739
+ return { rodada, texto, caminho, publicado, propostas: propostas.length };
740
+ }
741
+ // ---------------------------------------------------------------------------
742
+ // Saidas do CLI
743
+ // ---------------------------------------------------------------------------
744
+ /** Tabela de `ork audit list`. */
745
+ function tabelaDeRodadas(raiz) {
746
+ const ids = listarRodadas(raiz);
747
+ if (ids.length === 0) {
748
+ return 'Nenhuma rodada de auditoria em .orkastery/audits. Rode: ork audit run <pack>';
749
+ }
750
+ const linhas = ids.map((id) => {
751
+ const r = lerRodada(raiz, id);
752
+ return [
753
+ r.id,
754
+ r.pack,
755
+ r.perfil,
756
+ r.estagio,
757
+ r.status,
758
+ r.motivo ?? '-',
759
+ String(r.achados.length),
760
+ r.veredito ? (r.veredito.ok ? 'sustentado' : 'REPROVADO') : 'nao verificado',
761
+ r.escopo.since ?? 'total',
762
+ ];
763
+ });
764
+ return (0, util_1.tabela)(['RODADA', 'PACK', 'PERFIL', 'ESTAGIO', 'STATUS', 'MOTIVO', 'ACHADOS', 'CLAIMS', 'ESCOPO'], linhas);
765
+ }
766
+ /** Texto de `ork audit show`. */
767
+ function textoDaRodada(raiz, id) {
768
+ const r = lerRodada(raiz, id);
769
+ const achados = (0, divida_1.achadosDaRodada)(raiz, id);
770
+ const L = [];
771
+ L.push(`Rodada de auditoria ${r.id}`);
772
+ L.push(` pack ${r.pack} (${auditoria_1.PACKS[r.pack].titulo})`);
773
+ L.push(` produto ${r.produto} (perfil ${r.perfil})`);
774
+ L.push(` estagio ${r.estagio}, postura ${r.postura}`);
775
+ L.push(` escopo ${r.escopo.detalhe}`);
776
+ L.push(` graphify ${r.escopo.graphify}`);
777
+ L.push(` custo effort ${r.custo.effort}, model ${r.custo.model}`);
778
+ L.push(` janela ${r.custo.janela.detalhe}`);
779
+ if (r.custo.autorizadaPor)
780
+ L.push(` autorizada fora da janela por ${r.custo.autorizadaPor}`);
781
+ L.push(` runtime ${r.runtime}, sessao ${r.sessionId ?? '(nao despachada)'}`);
782
+ L.push(` prompt ${r.promptPath} (sha256 ${r.promptSha256 || '(nao montado)'})`);
783
+ L.push(` status ${r.status}${r.motivo ? ` (motivo tipado: ${r.motivo})` : ''}`);
784
+ if (r.superficie) {
785
+ L.push(` superficie ${r.superficie.detalhe}`);
786
+ L.push(` registrados ${r.superficie.registrados}, a confirmar ${r.superficie.aConfirmar}, ` +
787
+ `frameworks: ${r.superficie.frameworks.join(', ') || '(nenhum reconhecido)'}`);
788
+ }
789
+ if (r.detalhe)
790
+ L.push(` detalhe ${r.detalhe}`);
791
+ L.push('');
792
+ L.push(` Achados (${achados.length}):`);
793
+ if (achados.length === 0)
794
+ L.push(' (nenhum achado registrado nesta rodada ainda)');
795
+ for (const a of achados)
796
+ L.push((0, divida_1.textoDoAchado)(a));
797
+ L.push('');
798
+ if (r.veredito) {
799
+ L.push(` Claims do auditor: ${r.veredito.ok ? 'SUSTENTADAS' : 'REPROVADAS'} ` +
800
+ `(motivos: ${r.veredito.motivos.join(', ') || 'nenhum'}) no HEAD ${r.veredito.commit.slice(0, 8)}`);
801
+ }
802
+ else {
803
+ L.push(` Claims do auditor: NAO verificadas. Rode: ork audit verify ${r.id}`);
804
+ }
805
+ L.push('');
806
+ L.push(` Relatorio com o bloco de propostas: ork audit report ${r.id}`);
807
+ return L.join('\n');
808
+ }
809
+ /** Ledger de uma rodada, para `ork audit show --ledger`. */
810
+ function tabelaDoLedgerDaRodada(raiz, id) {
811
+ lerRodada(raiz, id);
812
+ const eventos = (0, ledger_1.lerLedger)(dirRodada(raiz, id));
813
+ if (eventos.length === 0)
814
+ return `Ledger vazio para a rodada ${id}.`;
815
+ const linhas = eventos.map((e) => {
816
+ const detalhe = [];
817
+ if (e.pack)
818
+ detalhe.push(String(e.pack));
819
+ if (e.achado)
820
+ detalhe.push(`achado ${String(e.achado)}`);
821
+ if (e.motivo)
822
+ detalhe.push(`motivo ${String(e.motivo)}`);
823
+ if (e.sessionId)
824
+ detalhe.push(`sessao ${String(e.sessionId).slice(0, 8)}`);
825
+ if (e.veredito)
826
+ detalhe.push(`veredito: ${String(e.veredito)}`);
827
+ if (e.destino)
828
+ detalhe.push(`destino ${String(e.destino)}`);
829
+ // `e.thread` e o dono do evento, que aqui e a propria rodada: so vale imprimir quando
830
+ // for OUTRO id, que e o caso do evento finding_to_thread.
831
+ if (e.thread && e.thread !== id)
832
+ detalhe.push(`thread ${String(e.thread)}`);
833
+ if (e.detalhe)
834
+ detalhe.push(String(e.detalhe).slice(0, 90));
835
+ return [e.ts.replace('T', ' ').slice(0, 19), e.tipo, detalhe.join(' | ')];
836
+ });
837
+ return (0, util_1.tabela)(['QUANDO', 'EVENTO', 'DETALHE'], linhas);
838
+ }
839
+ /** Quantos achados abertos existem por pack, para o resumo do CLI. */
840
+ function resumoDoBoard(raiz) {
841
+ const board = (0, divida_1.lerBoard)(raiz);
842
+ if (board.length === 0)
843
+ return 'board de divida vazio';
844
+ const abertos = board.filter((a) => a.estado === 'aberto').length;
845
+ return `${board.length} achado(s) no board de divida, ${abertos} aberto(s)`;
846
+ }
847
+ /**
848
+ * Abre a thread que nasce de um achado de auditoria.
849
+ *
850
+ * Duas recusas tipadas, e as duas existem pela mesma razao: o auditor nao tem direito a
851
+ * self-report. Um achado cuja claim ainda nao foi reexecutada no HEAD real, ou que foi
852
+ * reexecutada e REPROVOU, nao pode virar trabalho, porque a evidencia que abriria a thread
853
+ * nao existe. Claim `nao-verificavel` apenas AVISA e deixa passar, exatamente como o gate
854
+ * do B1 trata `claims.unverifiable`.
855
+ */
856
+ function abrirThreadDoAchado(carregado, achadoId, opcoes = {}) {
857
+ const { raiz, manifesto } = carregado;
858
+ const achado = (0, divida_1.exigirAchado)(raiz, achadoId);
859
+ const pedido = (0, divida_1.pedidoDoAchado)(achado);
860
+ if (achado.estado === 'virou-thread' && achado.thread) {
861
+ return {
862
+ ok: false,
863
+ achado,
864
+ thread: null,
865
+ pedido,
866
+ motivo: 'achado.ja-virou-thread',
867
+ detalhe: `o achado ${achado.id} ja originou a thread ${achado.thread}`,
868
+ correcao: `continue naquela thread: ork thread status ${achado.thread}`,
869
+ };
870
+ }
871
+ if (achado.claim.estado === 'pendente') {
872
+ return {
873
+ ok: false,
874
+ achado,
875
+ thread: null,
876
+ pedido,
877
+ motivo: 'claims.failed',
878
+ detalhe: `a claim ${achado.claim.id} do achado ainda nao foi reexecutada no HEAD real: ` +
879
+ 'o auditor nao tem direito a self-report',
880
+ correcao: `prove o achado antes de abrir trabalho: ork audit verify ${achado.rodada}`,
881
+ };
882
+ }
883
+ if (achado.claim.estado === 'reprovado') {
884
+ return {
885
+ ok: false,
886
+ achado,
887
+ thread: null,
888
+ pedido,
889
+ motivo: 'claims.failed',
890
+ detalhe: `a claim ${achado.claim.id} do achado REPROVOU na reexecucao no HEAD real`,
891
+ correcao: 'o achado nao se sustenta: corrija a evidencia na rodada, ou descarte o achado, em vez de abrir thread sobre ele',
892
+ };
893
+ }
894
+ const modo = opcoes.modo ?? manifesto.conduction.default_mode;
895
+ const { thread, gravada } = (0, thread_1.novaThread)(carregado, {
896
+ nome: opcoes.nome ?? achado.titulo,
897
+ modo,
898
+ slug: opcoes.slug,
899
+ assunto: opcoes.assunto,
900
+ worktree: opcoes.worktree ?? null,
901
+ criarWorktree: opcoes.criarWorktree,
902
+ dryRun: opcoes.dryRun,
903
+ variante: opcoes.variante ?? null,
904
+ });
905
+ if (!gravada) {
906
+ return {
907
+ ok: true,
908
+ achado,
909
+ thread,
910
+ pedido,
911
+ motivo: null,
912
+ detalhe: 'simulacao (--dry-run): nenhuma thread e nenhum carimbo no board foram gravados',
913
+ correcao: '',
914
+ };
915
+ }
916
+ // A evidencia viaja junto e continua sob verificacao: a claim do achado vira claim da
917
+ // thread pelo caminho normal (`ork claims add`), sem uma segunda regra de claim.
918
+ const claimNaThread = (0, claims_1.adicionarClaim)(raiz, thread.id, {
919
+ arquivo: achado.claim.arquivo,
920
+ alegacao: achado.claim.alegacao,
921
+ verificar: achado.claim.verificar,
922
+ fase: 'GOAL',
923
+ });
924
+ // O pedido pronto vai para arquivo, e nao so para a tela: o proximo comando do operador
925
+ // e `--prompt "$(cat .../pedido-goal.md)"`, sem copiar e colar dez linhas de evidencia.
926
+ (0, util_1.gravar)(path.join((0, thread_1.dirThread)(raiz, thread.id), 'pedido-goal.md'), pedido + '\n');
927
+ (0, util_1.gravarJson)(path.join((0, thread_1.dirThread)(raiz, thread.id), 'achado.json'), {
928
+ versao: 1,
929
+ origem: {
930
+ achado: achado.id,
931
+ rodada: achado.rodada,
932
+ pack: achado.pack,
933
+ regra: achado.regra,
934
+ severidade: achado.severidade,
935
+ produto: achado.produto,
936
+ estagio: achado.estagio,
937
+ postura: achado.postura,
938
+ },
939
+ evidencia: `${achado.arquivo}${achado.linha !== null ? ':' + achado.linha : ''}`,
940
+ proposta: achado.proposta,
941
+ claimNoAchado: achado.claim,
942
+ claimNaThread: claimNaThread.id,
943
+ pedidoDeGoal: pedido,
944
+ });
945
+ (0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, thread.id), thread.id, ledger_1.TIPOS_DE_EVENTO.achadoVirouThread, {
946
+ achado: achado.id,
947
+ rodada: achado.rodada,
948
+ pack: achado.pack,
949
+ regra: achado.regra,
950
+ severidade: achado.severidade,
951
+ evidencia: `${achado.arquivo}${achado.linha !== null ? ':' + achado.linha : ''}`,
952
+ claimNoAchado: achado.claim.id,
953
+ claimNaThread: claimNaThread.id,
954
+ proposta: achado.proposta.fix,
955
+ passoIrreversivel: achado.proposta.passoIrreversivel,
956
+ estimativa: achado.proposta.estimativa,
957
+ });
958
+ (0, ledger_1.registrar)(dirRodada(raiz, achado.rodada), achado.rodada, ledger_1.TIPOS_DE_EVENTO.achadoVirouThread, {
959
+ achado: achado.id,
960
+ thread: thread.id,
961
+ slug: thread.slug,
962
+ modo,
963
+ });
964
+ (0, divida_1.carimbarAchado)(raiz, achado, { estado: 'virou-thread', thread: thread.id });
965
+ return {
966
+ ok: true,
967
+ achado,
968
+ thread,
969
+ pedido,
970
+ motivo: achado.claim.estado === 'nao-verificavel' ? 'claims.unverifiable' : null,
971
+ detalhe: achado.claim.estado === 'nao-verificavel'
972
+ ? `AVISO: a claim ${achado.claim.id} do achado nao tem comando de verificacao (motivo tipado claims.unverifiable, que avisa e nao bloqueia)`
973
+ : `achado ${achado.id} aberto como thread ${thread.id}, com a evidencia e a claim junto`,
974
+ correcao: '',
975
+ };
976
+ }