@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
package/dist/leases.js ADDED
@@ -0,0 +1,508 @@
1
+ "use strict";
2
+ /**
3
+ * Leases de exclusao mutua entre threads (`.orkastery/leases/<nome>.json`).
4
+ *
5
+ * O lease `main-tree` serializa o SHIP: com N threads paralelas, so uma mergeia por vez.
6
+ * A aquisicao e atomica de verdade (`open` com flag `wx`, que falha se o arquivo existe),
7
+ * nao um "checa e depois escreve" que perde a corrida.
8
+ *
9
+ * O lease tem TTL: sessao morta nao pode travar a fila para sempre. Um lease vencido e
10
+ * tomado, e a tomada vai ao ledger da thread que tomou, com quem estava segurando.
11
+ *
12
+ * Bloco B2: alem do `main-tree`, existem as familias `worktree-write:<thread>`,
13
+ * `path:<glob>`, `board:<card>` e `service:<porta>`, e uma FILA por colisao. Duas threads
14
+ * que pedem a mesma regiao de path nao brigam: a segunda entra na fila em FIFO e recebe
15
+ * o motivo tipado `lease.busy` com a correcao acionavel.
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.NOME_ARQUIVO_FILA = exports.FAMILIAS_DE_LEASE = exports.TTL_PADRAO_MS = exports.LEASE_MAIN_TREE = void 0;
52
+ exports.dirLeases = dirLeases;
53
+ exports.nomeDeLease = nomeDeLease;
54
+ exports.tipoDoLease = tipoDoLease;
55
+ exports.alvoDoLease = alvoDoLease;
56
+ exports.caminhoLease = caminhoLease;
57
+ exports.lerLease = lerLease;
58
+ exports.expirado = expirado;
59
+ exports.globParaRegex = globParaRegex;
60
+ exports.caminhoBateComGlob = caminhoBateComGlob;
61
+ exports.globsColidem = globsColidem;
62
+ exports.leasesColidem = leasesColidem;
63
+ exports.adquirir = adquirir;
64
+ exports.liberar = liberar;
65
+ exports.listarLeases = listarLeases;
66
+ exports.leasesColidentes = leasesColidentes;
67
+ exports.leasesDaThread = leasesDaThread;
68
+ exports.caminhoFila = caminhoFila;
69
+ exports.lerFila = lerFila;
70
+ exports.enfileirar = enfileirar;
71
+ exports.sairDaFila = sairDaFila;
72
+ exports.esperandoPor = esperandoPor;
73
+ exports.posicaoNaFila = posicaoNaFila;
74
+ exports.proximoDaFila = proximoDaFila;
75
+ exports.adquirirRegiao = adquirirRegiao;
76
+ exports.tabelaDeLeases = tabelaDeLeases;
77
+ const fs = __importStar(require("node:fs"));
78
+ const path = __importStar(require("node:path"));
79
+ const manifest_1 = require("./manifest");
80
+ const util_1 = require("./util");
81
+ /** Nome do lease que serializa o merge na base (visao, paridade 5). */
82
+ exports.LEASE_MAIN_TREE = 'main-tree';
83
+ /** TTL padrao de 30 minutos: um SHIP que passe disso deixou de existir. */
84
+ exports.TTL_PADRAO_MS = 30 * 60 * 1000;
85
+ /** As familias de lease do B2, com o que cada uma protege. */
86
+ exports.FAMILIAS_DE_LEASE = {
87
+ 'main-tree': 'a arvore que tem a branch base em check-out (serializa o merge)',
88
+ 'worktree-write': 'a escrita na worktree de uma thread',
89
+ path: 'uma regiao de arquivos por glob (colisao entre threads paralelas)',
90
+ board: 'um card do board, para duas threads nao pegarem o mesmo cartao',
91
+ service: 'uma porta de servico local, para dois runs nao subirem no mesmo lugar',
92
+ };
93
+ /** Diretorio dos leases do projeto. */
94
+ function dirLeases(raiz) {
95
+ return path.join((0, manifest_1.dirEstado)(raiz), 'leases');
96
+ }
97
+ /**
98
+ * Nome canonico de um lease de familia tipada: `path:core/src/**`, `service:5173`.
99
+ * `main-tree` nao tem alvo: e um so no projeto.
100
+ */
101
+ function nomeDeLease(tipo, alvo) {
102
+ if (tipo === 'main-tree')
103
+ return exports.LEASE_MAIN_TREE;
104
+ const limpo = (alvo ?? '').trim();
105
+ if (!limpo)
106
+ throw new Error(`lease ${tipo} exige um alvo (ex.: ${tipo}:<alvo>)`);
107
+ return `${tipo}:${limpo}`;
108
+ }
109
+ /** Familia de um nome de lease. Nome fora do catalogo cai em `path` por seguranca. */
110
+ function tipoDoLease(nome) {
111
+ if (nome === exports.LEASE_MAIN_TREE)
112
+ return 'main-tree';
113
+ const prefixo = nome.split(':')[0];
114
+ if (prefixo === 'worktree-write' || prefixo === 'path' || prefixo === 'board' || prefixo === 'service') {
115
+ return prefixo;
116
+ }
117
+ return 'path';
118
+ }
119
+ /** Alvo de um lease tipado (o que vem depois dos dois pontos). */
120
+ function alvoDoLease(nome) {
121
+ const corte = nome.indexOf(':');
122
+ return corte < 0 ? '' : nome.slice(corte + 1);
123
+ }
124
+ /**
125
+ * Caminho do arquivo de um lease. O nome canonico tem `:`, `/` e `*`, entao ele e
126
+ * codificado para virar um nome de arquivo unico e reversivel. `main-tree` fica
127
+ * inalterado, o que preserva os leases gravados pelo bloco B1.
128
+ */
129
+ function caminhoLease(raiz, nome) {
130
+ return path.join(dirLeases(raiz), `${encodeURIComponent(nome)}.json`);
131
+ }
132
+ /** Le o lease em disco. Arquivo corrompido conta como lease ausente. */
133
+ function lerLease(raiz, nome) {
134
+ const caminho = caminhoLease(raiz, nome);
135
+ if (!fs.existsSync(caminho))
136
+ return null;
137
+ try {
138
+ return JSON.parse(fs.readFileSync(caminho, 'utf8'));
139
+ }
140
+ catch {
141
+ return null;
142
+ }
143
+ }
144
+ /** O lease venceu? */
145
+ function expirado(lease, referencia = new Date()) {
146
+ const fim = Date.parse(lease.expiraEm);
147
+ return Number.isFinite(fim) ? fim <= referencia.getTime() : true;
148
+ }
149
+ // ---------------------------------------------------------------------------
150
+ // Colisao de regiao: glob para regex, e a pergunta "estes dois globs se cruzam?".
151
+ // ---------------------------------------------------------------------------
152
+ /** Converte um glob de path (`*`, `**`, `?`) na regex equivalente. */
153
+ function globParaRegex(glob) {
154
+ let re = '';
155
+ for (let i = 0; i < glob.length; i++) {
156
+ const c = glob[i];
157
+ if (c === '*') {
158
+ if (glob[i + 1] === '*') {
159
+ // `**` atravessa quantos segmentos vierem; `**/` consome a propria barra.
160
+ re += '.*';
161
+ i++;
162
+ if (glob[i + 1] === '/')
163
+ i++;
164
+ }
165
+ else {
166
+ re += '[^/]*';
167
+ }
168
+ }
169
+ else if (c === '?') {
170
+ re += '[^/]';
171
+ }
172
+ else {
173
+ re += c.replace(/[.+^${}()|[\]\\]/g, '\\$&');
174
+ }
175
+ }
176
+ return new RegExp(`^${re}$`);
177
+ }
178
+ /** O caminho concreto cai dentro do glob? */
179
+ function caminhoBateComGlob(caminho, glob) {
180
+ return globParaRegex(glob).test(caminho);
181
+ }
182
+ /** Parte literal do glob, antes do primeiro curinga. */
183
+ function prefixoLiteral(glob) {
184
+ const corte = glob.search(/[*?]/);
185
+ return corte < 0 ? glob : glob.slice(0, corte);
186
+ }
187
+ /** `a` e prefixo de `b` numa fronteira de caminho (evita casar `core/s` com `core/src`). */
188
+ function prefixoDeCaminho(a, b) {
189
+ if (a === '')
190
+ return false;
191
+ if (!b.startsWith(a))
192
+ return false;
193
+ return a.length === b.length || a.endsWith('/') || b[a.length] === '/';
194
+ }
195
+ /**
196
+ * Dois globs disputam a mesma regiao?
197
+ *
198
+ * Conservador de proposito: na duvida o `ork` prefere serializar duas threads a deixar
199
+ * as duas escreverem no mesmo lugar. Falso positivo custa espera; falso negativo custa
200
+ * conflito de merge no meio do GO.
201
+ */
202
+ function globsColidem(a, b) {
203
+ if (a === b)
204
+ return true;
205
+ if (globParaRegex(a).test(b) || globParaRegex(b).test(a))
206
+ return true;
207
+ const pa = prefixoLiteral(a);
208
+ const pb = prefixoLiteral(b);
209
+ return prefixoDeCaminho(pa, pb) || prefixoDeCaminho(pb, pa);
210
+ }
211
+ /**
212
+ * Dois nomes de lease colidem?
213
+ *
214
+ * Familias diferentes nunca colidem. Dentro da familia `path` vale a colisao de glob;
215
+ * nas demais familias o alvo e discreto (uma thread, um card, uma porta) e a colisao
216
+ * e a igualdade exata.
217
+ */
218
+ function leasesColidem(a, b) {
219
+ if (a === b)
220
+ return true;
221
+ const ta = tipoDoLease(a);
222
+ const tb = tipoDoLease(b);
223
+ if (ta !== tb)
224
+ return false;
225
+ if (ta === 'path')
226
+ return globsColidem(alvoDoLease(a), alvoDoLease(b));
227
+ return false;
228
+ }
229
+ /**
230
+ * Adquire o lease. Retorna `ok: false` com `ocupadoPor` quando outra thread o segura,
231
+ * em vez de esperar: quem decide esperar ou desistir e a fila (`adquirirRegiao`).
232
+ */
233
+ function adquirir(raiz, nome, opcoes) {
234
+ const caminho = caminhoLease(raiz, nome);
235
+ fs.mkdirSync(path.dirname(caminho), { recursive: true });
236
+ const ttl = opcoes.ttlMs ?? exports.TTL_PADRAO_MS;
237
+ const lease = {
238
+ nome,
239
+ thread: opcoes.thread,
240
+ motivo: opcoes.motivo,
241
+ pid: process.pid,
242
+ adquiridoEm: (0, util_1.agora)(),
243
+ expiraEm: new Date(Date.now() + ttl).toISOString(),
244
+ };
245
+ const corpo = JSON.stringify(lease, null, 2) + '\n';
246
+ for (const tentativa of [1, 2]) {
247
+ try {
248
+ // `wx` falha se o arquivo existe: e a atomicidade do lease, sem corrida.
249
+ fs.writeFileSync(caminho, corpo, { encoding: 'utf8', flag: 'wx' });
250
+ return { ok: true, lease, ocupadoPor: null, tomadoDeVencido: tentativa === 2 };
251
+ }
252
+ catch (e) {
253
+ if (e.code !== 'EEXIST')
254
+ throw e;
255
+ const atual = lerLease(raiz, nome);
256
+ if (atual && !expirado(atual)) {
257
+ return { ok: false, lease: null, ocupadoPor: atual, tomadoDeVencido: false };
258
+ }
259
+ // Lease vencido ou corrompido: remove e tenta uma unica vez mais.
260
+ try {
261
+ fs.unlinkSync(caminho);
262
+ }
263
+ catch {
264
+ /* outra thread removeu antes; a segunda tentativa decide */
265
+ }
266
+ }
267
+ }
268
+ const atual = lerLease(raiz, nome);
269
+ return { ok: false, lease: null, ocupadoPor: atual, tomadoDeVencido: false };
270
+ }
271
+ /**
272
+ * Libera o lease. Sem `forcar`, so a thread que o segura pode liberar: liberar o lease
273
+ * dos outros e exatamente o bug que o lease existe para impedir.
274
+ */
275
+ function liberar(raiz, nome, thread, forcar = false) {
276
+ const atual = lerLease(raiz, nome);
277
+ if (!atual) {
278
+ sairDaFila(raiz, nome, thread);
279
+ return { ok: true, detalhe: `lease ${nome} ja estava livre` };
280
+ }
281
+ if (atual.thread !== thread && !forcar) {
282
+ return {
283
+ ok: false,
284
+ detalhe: `lease ${nome} pertence a thread ${atual.thread}; use --forcar para tomar`,
285
+ };
286
+ }
287
+ fs.unlinkSync(caminhoLease(raiz, nome));
288
+ sairDaFila(raiz, nome, atual.thread);
289
+ const proximo = proximoDaFila(raiz, nome);
290
+ const seguinte = proximo ? `; proximo da fila: thread ${proximo.thread}` : '';
291
+ return { ok: true, detalhe: `lease ${nome} liberado${seguinte}` };
292
+ }
293
+ /** Todos os leases existentes no projeto, pelo nome canonico gravado no arquivo. */
294
+ function listarLeases(raiz) {
295
+ const dir = dirLeases(raiz);
296
+ if (!fs.existsSync(dir))
297
+ return [];
298
+ const leases = [];
299
+ for (const arquivo of fs.readdirSync(dir)) {
300
+ if (!arquivo.endsWith('.json') || arquivo === exports.NOME_ARQUIVO_FILA)
301
+ continue;
302
+ try {
303
+ const bruto = fs.readFileSync(path.join(dir, arquivo), 'utf8');
304
+ const lease = JSON.parse(bruto);
305
+ if (lease && typeof lease.nome === 'string')
306
+ leases.push(lease);
307
+ }
308
+ catch {
309
+ /* arquivo corrompido conta como lease ausente */
310
+ }
311
+ }
312
+ return leases.sort((a, b) => a.nome.localeCompare(b.nome));
313
+ }
314
+ /** Leases ativos (nao vencidos) que colidem com o nome pedido. */
315
+ function leasesColidentes(raiz, nome, thread) {
316
+ return listarLeases(raiz).filter((l) => !expirado(l) && l.thread !== thread && leasesColidem(nome, l.nome));
317
+ }
318
+ /** Leases ativos segurados por uma thread. */
319
+ function leasesDaThread(raiz, thread) {
320
+ return listarLeases(raiz).filter((l) => l.thread === thread && !expirado(l));
321
+ }
322
+ // ---------------------------------------------------------------------------
323
+ // Fila por colisao.
324
+ // ---------------------------------------------------------------------------
325
+ /** Arquivo da fila de espera, ao lado dos leases. */
326
+ exports.NOME_ARQUIVO_FILA = 'fila.json';
327
+ /** Caminho da fila de espera do projeto. */
328
+ function caminhoFila(raiz) {
329
+ return path.join(dirLeases(raiz), exports.NOME_ARQUIVO_FILA);
330
+ }
331
+ /** Le a fila inteira, em ordem FIFO. Arquivo corrompido conta como fila vazia. */
332
+ function lerFila(raiz) {
333
+ const caminho = caminhoFila(raiz);
334
+ if (!fs.existsSync(caminho))
335
+ return [];
336
+ try {
337
+ const dados = JSON.parse(fs.readFileSync(caminho, 'utf8'));
338
+ return Array.isArray(dados) ? dados.sort((a, b) => a.desdeEm.localeCompare(b.desdeEm)) : [];
339
+ }
340
+ catch {
341
+ return [];
342
+ }
343
+ }
344
+ function gravarFila(raiz, fila) {
345
+ const caminho = caminhoFila(raiz);
346
+ fs.mkdirSync(path.dirname(caminho), { recursive: true });
347
+ fs.writeFileSync(caminho, JSON.stringify(fila, null, 2) + '\n', 'utf8');
348
+ }
349
+ /** Coloca o pedido na fila (idempotente por thread e nome). Devolve a posicao, 1 = proxima. */
350
+ function enfileirar(raiz, nome, dados) {
351
+ const fila = lerFila(raiz);
352
+ const jaEsta = fila.find((p) => p.nome === nome && p.thread === dados.thread);
353
+ if (!jaEsta) {
354
+ fila.push({
355
+ nome,
356
+ tipo: tipoDoLease(nome),
357
+ thread: dados.thread,
358
+ motivo: dados.motivo,
359
+ desdeEm: (0, util_1.agora)(),
360
+ colidiuCom: dados.colidiuCom,
361
+ bloqueadaPor: dados.bloqueadaPor,
362
+ });
363
+ gravarFila(raiz, fila);
364
+ }
365
+ return posicaoNaFila(raiz, nome, dados.thread);
366
+ }
367
+ /** Tira a thread da fila do lease informado. */
368
+ function sairDaFila(raiz, nome, thread) {
369
+ const fila = lerFila(raiz);
370
+ const restante = fila.filter((p) => !(p.nome === nome && p.thread === thread));
371
+ if (restante.length !== fila.length)
372
+ gravarFila(raiz, restante);
373
+ }
374
+ /** Espera da regiao: pedidos na fila cujo nome colide com o nome informado, em FIFO. */
375
+ function esperandoPor(raiz, nome) {
376
+ return lerFila(raiz).filter((p) => leasesColidem(nome, p.nome));
377
+ }
378
+ /** Posicao da thread na fila da regiao (1 = proxima da vez, 0 = fora da fila). */
379
+ function posicaoNaFila(raiz, nome, thread) {
380
+ const espera = esperandoPor(raiz, nome);
381
+ const i = espera.findIndex((p) => p.thread === thread && p.nome === nome);
382
+ return i < 0 ? 0 : i + 1;
383
+ }
384
+ /** Quem esta na frente da fila desta regiao. */
385
+ function proximoDaFila(raiz, nome) {
386
+ return esperandoPor(raiz, nome)[0] ?? null;
387
+ }
388
+ /**
389
+ * Adquire uma regiao com fila: se algo colidente estiver ativo, a thread ENTRA NA FILA
390
+ * e recebe `lease.busy` com correcao acionavel, em vez de escrever por cima.
391
+ *
392
+ * A ordem e FIFO pela entrada na fila: quem chegou antes leva, mesmo que a outra thread
393
+ * tente de novo primeiro. Sem isso a fila vira loteria e a thread azarada nunca entra.
394
+ */
395
+ function adquirirRegiao(raiz, nome, opcoes) {
396
+ const tipo = tipoDoLease(nome);
397
+ const base = { nome, tipo, lease: null, ocupadoPor: null, tomadoDeVencido: false };
398
+ const colidentes = leasesColidentes(raiz, nome, opcoes.thread);
399
+ if (colidentes.length > 0) {
400
+ const dono = colidentes[0];
401
+ const posicao = enfileirar(raiz, nome, {
402
+ thread: opcoes.thread,
403
+ motivo: opcoes.motivo,
404
+ colidiuCom: dono.nome,
405
+ bloqueadaPor: dono.thread,
406
+ });
407
+ return {
408
+ ...base,
409
+ ok: false,
410
+ ocupadoPor: dono,
411
+ esperando: true,
412
+ posicaoNaFila: posicao,
413
+ colidiuCom: dono,
414
+ motivo: 'lease.busy',
415
+ detalhe: `a regiao "${nome}" colide com "${dono.nome}", que esta com a thread ${dono.thread} ` +
416
+ `desde ${dono.adquiridoEm}`,
417
+ correcao: `espere a thread ${dono.thread} liberar (posicao ${posicao} na fila), ` +
418
+ `ou reduza o glob da sua regiao, ou libere com: ork lease release "${dono.nome}" --forcar`,
419
+ };
420
+ }
421
+ // Fila vazia para a regiao? Entao adquire. Se ha fila e a vez nao e desta thread,
422
+ // ela espera: e isso que impede a thread que chega depois de furar a fila.
423
+ const espera = esperandoPor(raiz, nome);
424
+ if (espera.length > 0 && espera[0].thread !== opcoes.thread) {
425
+ const frente = espera[0];
426
+ const posicao = enfileirar(raiz, nome, {
427
+ thread: opcoes.thread,
428
+ motivo: opcoes.motivo,
429
+ colidiuCom: frente.nome,
430
+ bloqueadaPor: frente.thread,
431
+ });
432
+ return {
433
+ ...base,
434
+ ok: false,
435
+ ocupadoPor: null,
436
+ esperando: true,
437
+ posicaoNaFila: posicao,
438
+ colidiuCom: null,
439
+ motivo: 'lease.busy',
440
+ detalhe: `a thread ${frente.thread} esta na frente da fila da regiao "${frente.nome}"`,
441
+ correcao: `espere a vez (posicao ${posicao} na fila) ou veja a fila com: ork lease list`,
442
+ };
443
+ }
444
+ const r = adquirir(raiz, nome, opcoes);
445
+ if (!r.ok) {
446
+ const dono = r.ocupadoPor;
447
+ const posicao = enfileirar(raiz, nome, {
448
+ thread: opcoes.thread,
449
+ motivo: opcoes.motivo,
450
+ colidiuCom: nome,
451
+ bloqueadaPor: dono?.thread ?? '(desconhecida)',
452
+ });
453
+ return {
454
+ ...base,
455
+ ok: false,
456
+ ocupadoPor: dono,
457
+ esperando: true,
458
+ posicaoNaFila: posicao,
459
+ colidiuCom: dono,
460
+ motivo: 'lease.busy',
461
+ detalhe: `o lease "${nome}" esta com a thread ${dono?.thread ?? '(desconhecida)'}`,
462
+ correcao: `espere a vez (posicao ${posicao} na fila) ou: ork lease release "${nome}" --forcar`,
463
+ };
464
+ }
465
+ sairDaFila(raiz, nome, opcoes.thread);
466
+ return {
467
+ ...base,
468
+ ok: true,
469
+ lease: r.lease,
470
+ tomadoDeVencido: r.tomadoDeVencido,
471
+ esperando: false,
472
+ posicaoNaFila: 0,
473
+ colidiuCom: null,
474
+ motivo: null,
475
+ detalhe: `lease ${nome} adquirido pela thread ${opcoes.thread}`,
476
+ correcao: '',
477
+ };
478
+ }
479
+ /** Texto de `ork lease list`: leases ativos, familias e a fila por colisao. */
480
+ function tabelaDeLeases(raiz) {
481
+ const leases = listarLeases(raiz);
482
+ const fila = lerFila(raiz);
483
+ const linhas = ['Leases do projeto', ''];
484
+ if (leases.length === 0) {
485
+ linhas.push(' Nenhum lease tomado. O merge na base esta liberado.');
486
+ }
487
+ else {
488
+ for (const l of leases) {
489
+ const situacao = expirado(l) ? 'VENCIDO (tomavel)' : 'ativo';
490
+ linhas.push(` ${l.nome.padEnd(28)} [${tipoDoLease(l.nome)}] thread ${l.thread.padEnd(18)} ${situacao}`);
491
+ linhas.push(` desde ${l.adquiridoEm} ate ${l.expiraEm} (pid ${l.pid})`);
492
+ linhas.push(` motivo: ${l.motivo}`);
493
+ }
494
+ }
495
+ linhas.push('');
496
+ const merge = fila.filter((p) => p.tipo === 'main-tree');
497
+ const regiao = fila.filter((p) => p.tipo !== 'main-tree');
498
+ linhas.push(`Fila de merge (lease ${exports.LEASE_MAIN_TREE}): ${merge.length === 0 ? 'vazia' : `${merge.length} na espera`}`);
499
+ merge.forEach((p, i) => {
500
+ linhas.push(` ${i + 1}. thread ${p.thread} desde ${p.desdeEm} (bloqueada por ${p.bloqueadaPor})`);
501
+ });
502
+ linhas.push(`Fila por colisao de regiao: ${regiao.length === 0 ? 'vazia' : `${regiao.length} na espera`}`);
503
+ regiao.forEach((p, i) => {
504
+ linhas.push(` ${i + 1}. thread ${p.thread} quer ${p.nome}, colide com ${p.colidiuCom} ` +
505
+ `(thread ${p.bloqueadaPor}) desde ${p.desdeEm}`);
506
+ });
507
+ return linhas.join('\n');
508
+ }
package/dist/ledger.js ADDED
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ /**
3
+ * Ledger de eventos JSONL por thread (`.orkastery/threads/<id>/ledger.jsonl`).
4
+ *
5
+ * O ledger e append-only: e a rastreabilidade da conducao. Toda decisao autonoma
6
+ * (modo sem pausa) precisa estar aqui com quem decidiu, evidencia e razao.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.TIPOS_DE_EVENTO = void 0;
43
+ exports.caminhoLedger = caminhoLedger;
44
+ exports.registrar = registrar;
45
+ exports.lerLedger = lerLedger;
46
+ const fs = __importStar(require("node:fs"));
47
+ const path = __importStar(require("node:path"));
48
+ const util_1 = require("./util");
49
+ /** Tipos de evento do ledger. B0 abriu a lista; o B1 acrescenta os seus. */
50
+ exports.TIPOS_DE_EVENTO = {
51
+ // Bloco B0
52
+ threadCriada: 'thread_created',
53
+ worktreeCriada: 'worktree_created',
54
+ faseDespachada: 'phase_dispatch',
55
+ despachoVerificado: 'phase_dispatch_verified',
56
+ despachoFalhou: 'phase_dispatch_failed',
57
+ decisaoAutonoma: 'autonomous_decision',
58
+ pausaHumana: 'human_gate',
59
+ // Bloco B1: verdade, leases, ship e handoff
60
+ gateBloqueado: 'gate_blocked',
61
+ gateLiberado: 'gate_passed',
62
+ claimRegistrada: 'claim_added',
63
+ baselineGravada: 'baseline_recorded',
64
+ verificacao: 'verify_run',
65
+ leaseAdquirido: 'lease_acquired',
66
+ leaseLiberado: 'lease_released',
67
+ shipIniciado: 'ship_started',
68
+ shipConcluido: 'ship_done',
69
+ shipBloqueado: 'ship_blocked',
70
+ gateDeTokens: 'token_gate',
71
+ handoffExportado: 'handoff_exported',
72
+ handoffRecuperado: 'handoff_recalled',
73
+ // Bloco B2: worktree gerenciada, fila de leases, escalonador e MASTER/score
74
+ worktreeGarantida: 'worktree_ensured',
75
+ worktreeSincronizada: 'worktree_synced',
76
+ worktreeAuditada: 'worktree_audited',
77
+ worktreeLiberada: 'worktree_released',
78
+ leaseEnfileirado: 'lease_queued',
79
+ postmortemGravado: 'postmortem_recorded',
80
+ masterConcluido: 'master_done',
81
+ // Bloco B5: auditores periodicos (rodada, achados, propostas e board de divida)
82
+ auditoriaDespachada: 'audit_dispatch',
83
+ auditoriaBloqueada: 'audit_blocked',
84
+ auditoriaVerificada: 'audit_verified',
85
+ achadoRegistrado: 'finding_added',
86
+ propostaGravada: 'proposal_recorded',
87
+ relatorioGerado: 'audit_report',
88
+ achadoVirouThread: 'finding_to_thread',
89
+ // Extensao do B5: varredura deterministica da superficie de ataque de rede (SP8..SP12)
90
+ superficieVarrida: 'attack_surface_scanned',
91
+ // Bloco B6: camada OrkMind (memoria semantica opcional)
92
+ memoriaGravada: 'memory_written',
93
+ memoriaInjetada: 'memory_injected',
94
+ memoriaDegradada: 'memory_degraded',
95
+ recallResolvido: 'recall_resolved',
96
+ // Bloco B3: autonomia (retry tipado, fila de rate limit, GO-FIX/CHECK-REVERIFY)
97
+ retryPlanejado: 'retry_planned',
98
+ retryTentado: 'retry_attempt',
99
+ retryEscalado: 'retry_escalated',
100
+ rateLimitDetectado: 'rate_limit_detected',
101
+ rateLimitEnfileirado: 'rate_limit_queued',
102
+ rateLimitRetomado: 'rate_limit_resumed',
103
+ fixAberto: 'go_fix_opened',
104
+ fixDespachado: 'go_fix_dispatched',
105
+ reverifyConcluido: 'check_reverify',
106
+ };
107
+ /** Caminho do ledger de uma thread. */
108
+ function caminhoLedger(dirThread) {
109
+ return path.join(dirThread, 'ledger.jsonl');
110
+ }
111
+ /** Anexa um evento ao ledger da thread e devolve o evento gravado. */
112
+ function registrar(dirThread, threadId, tipo, dados = {}) {
113
+ const evento = { ts: (0, util_1.agora)(), thread: threadId, tipo, ...dados };
114
+ fs.mkdirSync(dirThread, { recursive: true });
115
+ fs.appendFileSync(caminhoLedger(dirThread), JSON.stringify(evento) + '\n', 'utf8');
116
+ return evento;
117
+ }
118
+ /** Le o ledger inteiro. Linha corrompida vira evento `ledger_corrupted` em vez de quebrar a leitura. */
119
+ function lerLedger(dirThread) {
120
+ const caminho = caminhoLedger(dirThread);
121
+ if (!fs.existsSync(caminho))
122
+ return [];
123
+ const linhas = fs.readFileSync(caminho, 'utf8').split('\n').filter((l) => l.trim() !== '');
124
+ return linhas.map((linha, i) => {
125
+ try {
126
+ return JSON.parse(linha);
127
+ }
128
+ catch {
129
+ return {
130
+ ts: '',
131
+ thread: '',
132
+ tipo: 'ledger_corrupted',
133
+ linha: i + 1,
134
+ conteudo: linha.slice(0, 200),
135
+ };
136
+ }
137
+ });
138
+ }