@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.
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/adapters/README.md +22 -0
- package/adapters/claude-code/.claude-plugin/marketplace.json +15 -0
- package/adapters/claude-code/.claude-plugin/plugin.json +31 -0
- package/adapters/claude-code/README.md +102 -0
- package/adapters/claude-code/agents/ork-check.md +32 -0
- package/adapters/claude-code/agents/ork-go.md +32 -0
- package/adapters/claude-code/agents/ork-goal.md +33 -0
- package/adapters/claude-code/agents/ork-master.md +32 -0
- package/adapters/claude-code/agents/ork-plan.md +32 -0
- package/adapters/claude-code/agents/ork-ship.md +32 -0
- package/adapters/claude-code/commands/check.md +45 -0
- package/adapters/claude-code/commands/go.md +46 -0
- package/adapters/claude-code/commands/goal.md +54 -0
- package/adapters/claude-code/commands/master.md +42 -0
- package/adapters/claude-code/commands/ork.md +37 -0
- package/adapters/claude-code/commands/plan.md +47 -0
- package/adapters/claude-code/commands/ship.md +45 -0
- package/adapters/claude-code/hooks/hooks.json +17 -0
- package/adapters/claude-code/hooks/ork-guard.js +130 -0
- package/adapters/hermes/README.md +45 -0
- package/adapters/hermes/bin/ork-abrir-thread.sh +25 -0
- package/adapters/hermes/hermes.plugin.json +21 -0
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +116 -0
- package/adapters/openclaw/README.md +63 -0
- package/adapters/openclaw/bin/ork-abrir-thread.sh +24 -0
- package/adapters/openclaw/openclaw.plugin.json +138 -0
- package/dist/adapters/claude-bg.js +308 -0
- package/dist/auditoria.js +848 -0
- package/dist/auditrun.js +976 -0
- package/dist/board.js +314 -0
- package/dist/canarios.js +271 -0
- package/dist/catalogo.js +124 -0
- package/dist/ciclos.js +156 -0
- package/dist/claims.js +226 -0
- package/dist/divida.js +374 -0
- package/dist/doctor.js +246 -0
- package/dist/evalrunner.js +458 -0
- package/dist/fix.js +450 -0
- package/dist/gates.js +92 -0
- package/dist/handoff.js +521 -0
- package/dist/hosts.js +331 -0
- package/dist/index.js +1910 -0
- package/dist/init.js +231 -0
- package/dist/leases.js +508 -0
- package/dist/ledger.js +138 -0
- package/dist/manifest.js +255 -0
- package/dist/master.js +453 -0
- package/dist/memoria.js +773 -0
- package/dist/modos.js +220 -0
- package/dist/orkmind.js +487 -0
- package/dist/orquestracao.js +559 -0
- package/dist/phase.js +572 -0
- package/dist/policies.js +176 -0
- package/dist/prompts.js +406 -0
- package/dist/ratelimit.js +179 -0
- package/dist/recall.js +252 -0
- package/dist/retry.js +917 -0
- package/dist/sandbox.js +94 -0
- package/dist/sessoes.js +104 -0
- package/dist/ship.js +551 -0
- package/dist/slug.js +100 -0
- package/dist/superficie.js +1347 -0
- package/dist/thread.js +333 -0
- package/dist/tokens.js +228 -0
- package/dist/types.js +20 -0
- package/dist/util.js +156 -0
- package/dist/verify.js +262 -0
- package/dist/worktree.js +576 -0
- package/dist/yaml.js +112 -0
- package/eval/README.md +85 -0
- package/eval/casos/check-quality.json +103 -0
- package/eval/casos/code-reviewer.json +102 -0
- package/eval/casos/decision-triage.json +103 -0
- package/eval/casos/go-implementation.json +103 -0
- package/eval/casos/goal-definition.json +103 -0
- package/eval/casos/master-metrics.json +103 -0
- package/eval/casos/narrative-guardian.json +163 -0
- package/eval/casos/orkastery-bootstrap.json +102 -0
- package/eval/casos/plan-specification.json +102 -0
- package/eval/casos/roadmap-keeper.json +103 -0
- package/eval/casos/scope-check-capability-map.json +83 -0
- package/eval/casos/security-auditor.json +102 -0
- package/eval/casos/ship-release.json +122 -0
- package/eval/casos/test-engineer.json +122 -0
- package/eval/casos/thread-state.json +82 -0
- package/eval/casos/thread-tracing.json +83 -0
- package/eval/casos/web-performance-auditor.json +102 -0
- package/eval/fixtures/b0-slug-e-modos/caso.json +56 -0
- package/eval/fixtures/b2-master-log/caso.json +91 -0
- package/eval/fixtures/fx-concurrency/caso.json +15 -0
- package/eval/fixtures/fx-hallucination/caso.json +12 -0
- package/eval/fixtures/fx-happy/caso.json +16 -0
- package/eval/fixtures/fx-schema-drift/caso.json +15 -0
- package/eval/fixtures/fx-stale-base/caso.json +12 -0
- package/eval/fixtures/fx-wiki-destroy/caso.json +16 -0
- package/eval/fixtures/superficie-de-rede/README.md +22 -0
- package/eval/fixtures/superficie-de-rede/api-express.js +30 -0
- package/eval/fixtures/superficie-de-rede/api_fastapi.py +17 -0
- package/eval/fixtures/superficie-de-rede/api_gin.go +18 -0
- package/package.json +56 -0
- package/references/README.md +25 -0
- package/references/code-review-axes.md +82 -0
- package/references/definition-of-done.md +74 -0
- package/references/performance-checklist.md +53 -0
- package/references/security-checklist.md +101 -0
- package/references/testing-patterns.md +51 -0
- package/skills/README.md +43 -0
- package/skills/core/orkastery-bootstrap/SKILL.md +89 -0
- package/skills/core/thread-state/SKILL.md +86 -0
- package/skills/governance/decision-triage/SKILL.md +65 -0
- package/skills/governance/narrative-guardian/SKILL.md +66 -0
- package/skills/governance/roadmap-keeper/SKILL.md +60 -0
- package/skills/governance/scope-check-capability-map/SKILL.md +61 -0
- package/skills/observability/thread-tracing/SKILL.md +63 -0
- package/skills/phases/check-quality/SKILL.md +87 -0
- package/skills/phases/go-implementation/SKILL.md +70 -0
- package/skills/phases/goal-definition/SKILL.md +69 -0
- package/skills/phases/master-metrics/SKILL.md +65 -0
- package/skills/phases/plan-specification/SKILL.md +67 -0
- package/skills/phases/ship-release/SKILL.md +68 -0
- package/skills/reviewers/code-reviewer/SKILL.md +61 -0
- package/skills/reviewers/security-auditor/SKILL.md +68 -0
- package/skills/reviewers/test-engineer/SKILL.md +66 -0
- package/skills/reviewers/web-performance-auditor/SKILL.md +63 -0
package/dist/handoff.js
ADDED
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Handoff triado entre sessoes, regime `files` (visao unificada, secoes 6.1 e 6.2).
|
|
4
|
+
*
|
|
5
|
+
* Quando o gate de tokens veredicta `new-session`, a sessao que abre NAO recebe copia
|
|
6
|
+
* integral da anterior. Ela recebe um handoff com triagem em 3 niveis:
|
|
7
|
+
*
|
|
8
|
+
* CRITICO -> vai INLINE, sempre: estado do mundo, decisoes locked, criterios de
|
|
9
|
+
* sucesso, claims pendentes, baseline.
|
|
10
|
+
* IMPORTANTE-> vira PONTEIRO `path#ancora`, com instrucao de recuperacao e o momento
|
|
11
|
+
* de resolve-lo. Nao entra no prompt: entra o endereco.
|
|
12
|
+
* RESUMIVEL -> vira resumo curto COM proveniencia: historico de tentativas, logs,
|
|
13
|
+
* exploracao ja concluida.
|
|
14
|
+
*
|
|
15
|
+
* Proveniencia e obrigatoria em todo item: source, location e sha256 do arquivo de
|
|
16
|
+
* origem. Nada entra no contexto da nova sessao sem dizer de onde veio, e tudo o que
|
|
17
|
+
* ficou de fora esta listado como ponteiro, com onde esta e como recuperar.
|
|
18
|
+
*
|
|
19
|
+
* Sem OrkMind, a recuperacao tardia e leitura dirigida (`ork handoff recall`), nao busca
|
|
20
|
+
* semantica. O campo `memory` declara o regime, entao a sessao seguinte sabe o que pedir.
|
|
21
|
+
*/
|
|
22
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
25
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
26
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
27
|
+
}
|
|
28
|
+
Object.defineProperty(o, k2, desc);
|
|
29
|
+
}) : (function(o, m, k, k2) {
|
|
30
|
+
if (k2 === undefined) k2 = k;
|
|
31
|
+
o[k2] = m[k];
|
|
32
|
+
}));
|
|
33
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
34
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
35
|
+
}) : function(o, v) {
|
|
36
|
+
o["default"] = v;
|
|
37
|
+
});
|
|
38
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
39
|
+
var ownKeys = function(o) {
|
|
40
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
41
|
+
var ar = [];
|
|
42
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
43
|
+
return ar;
|
|
44
|
+
};
|
|
45
|
+
return ownKeys(o);
|
|
46
|
+
};
|
|
47
|
+
return function (mod) {
|
|
48
|
+
if (mod && mod.__esModule) return mod;
|
|
49
|
+
var result = {};
|
|
50
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
51
|
+
__setModuleDefault(result, mod);
|
|
52
|
+
return result;
|
|
53
|
+
};
|
|
54
|
+
})();
|
|
55
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
56
|
+
exports.sha256DoArquivo = sha256DoArquivo;
|
|
57
|
+
exports.sha256DoTexto = sha256DoTexto;
|
|
58
|
+
exports.caminhoDoArtefato = caminhoDoArtefato;
|
|
59
|
+
exports.caminhoHandoff = caminhoHandoff;
|
|
60
|
+
exports.dirHandoffs = dirHandoffs;
|
|
61
|
+
exports.exportarHandoff = exportarHandoff;
|
|
62
|
+
exports.ancoraDeTitulo = ancoraDeTitulo;
|
|
63
|
+
exports.recall = recall;
|
|
64
|
+
exports.textoDoHandoff = textoDoHandoff;
|
|
65
|
+
const node_crypto_1 = require("node:crypto");
|
|
66
|
+
const fs = __importStar(require("node:fs"));
|
|
67
|
+
const path = __importStar(require("node:path"));
|
|
68
|
+
const claims_1 = require("./claims");
|
|
69
|
+
const ledger_1 = require("./ledger");
|
|
70
|
+
const modos_1 = require("./modos");
|
|
71
|
+
const phase_1 = require("./phase");
|
|
72
|
+
const thread_1 = require("./thread");
|
|
73
|
+
const memoria_1 = require("./memoria");
|
|
74
|
+
const util_1 = require("./util");
|
|
75
|
+
/** sha256 de um arquivo em disco, a prova de proveniencia de cada item. */
|
|
76
|
+
function sha256DoArquivo(caminho) {
|
|
77
|
+
if (!fs.existsSync(caminho))
|
|
78
|
+
return '(arquivo ausente)';
|
|
79
|
+
return (0, node_crypto_1.createHash)('sha256').update(fs.readFileSync(caminho)).digest('hex');
|
|
80
|
+
}
|
|
81
|
+
/** sha256 de um conteudo montado pelo proprio `ork` (itens derivados do estado). */
|
|
82
|
+
function sha256DoTexto(texto) {
|
|
83
|
+
return (0, node_crypto_1.createHash)('sha256').update(texto, 'utf8').digest('hex');
|
|
84
|
+
}
|
|
85
|
+
function relativo(raiz, caminho) {
|
|
86
|
+
return path.relative(raiz, caminho).split(path.sep).join('/');
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Resolve o arquivo citado por uma claim.
|
|
90
|
+
*
|
|
91
|
+
* A claim e escrita do ponto de vista de quem trabalha: o caminho vale primeiro dentro da
|
|
92
|
+
* worktree da thread (onde a fase roda e onde o `ork verify` executa os comandos) e so
|
|
93
|
+
* depois na raiz do projeto.
|
|
94
|
+
*/
|
|
95
|
+
function caminhoDoArtefato(raiz, thread, arquivo) {
|
|
96
|
+
const candidatos = thread.worktree
|
|
97
|
+
? [path.resolve(thread.worktree, arquivo), path.resolve(raiz, arquivo)]
|
|
98
|
+
: [path.resolve(raiz, arquivo)];
|
|
99
|
+
for (const c of candidatos) {
|
|
100
|
+
if (fs.existsSync(c) && fs.statSync(c).isFile())
|
|
101
|
+
return c;
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
function proveniencia(raiz, caminho, ancora) {
|
|
106
|
+
const rel = relativo(raiz, caminho);
|
|
107
|
+
return { source: rel, location: `${rel}#${ancora}`, sha256: sha256DoArquivo(caminho) };
|
|
108
|
+
}
|
|
109
|
+
/** Caminho canonico do handoff mais recente da thread. */
|
|
110
|
+
function caminhoHandoff(raiz, threadId) {
|
|
111
|
+
return path.join((0, thread_1.dirThread)(raiz, threadId), 'handoff.json');
|
|
112
|
+
}
|
|
113
|
+
/** Caminho do historico de handoffs da thread. */
|
|
114
|
+
function dirHandoffs(raiz, threadId) {
|
|
115
|
+
return path.join((0, thread_1.dirThread)(raiz, threadId), 'handoffs');
|
|
116
|
+
}
|
|
117
|
+
/** Resumo textual do estado do mundo da thread: o item CRITICO que sempre vai inline. */
|
|
118
|
+
function estadoDoMundo(thread) {
|
|
119
|
+
const linhas = [
|
|
120
|
+
`thread: ${thread.id} (${thread.nome})`,
|
|
121
|
+
`slug atual: ${thread.slug}`,
|
|
122
|
+
`modo de conducao: ${(0, modos_1.tagDoModo)(thread.modo)}`,
|
|
123
|
+
`fase atual: ${thread.faseAtual}`,
|
|
124
|
+
`status: ${thread.status}`,
|
|
125
|
+
`base carimbada pelo ork: ${thread.base.branch} @ ${thread.base.commit}`,
|
|
126
|
+
`diretorio de trabalho: ${thread.worktree ?? '(raiz do projeto)'}`,
|
|
127
|
+
`blocos do modo: ${thread.blocos.map((b) => b.fases.join('-') + (b.pausa ? '*' : '')).join(' / ')}`,
|
|
128
|
+
`* = bloco que pausa e espera veredito humano`,
|
|
129
|
+
];
|
|
130
|
+
return linhas.join('\n');
|
|
131
|
+
}
|
|
132
|
+
/** Exporta o handoff triado da thread. Deterministico: mesma entrada, mesmo arquivo. */
|
|
133
|
+
function exportarHandoff(carregado, threadId, opcoes = {}) {
|
|
134
|
+
const { raiz, manifesto } = carregado;
|
|
135
|
+
const thread = (0, thread_1.lerThread)(raiz, threadId);
|
|
136
|
+
const dir = (0, thread_1.dirThread)(raiz, threadId);
|
|
137
|
+
const proximaFase = opcoes.proximaFase ?? thread.faseAtual;
|
|
138
|
+
const slugDestino = opcoes.slugDestino ?? (0, phase_1.slugDaSessao)(thread, proximaFase);
|
|
139
|
+
const caminhoThreadJson = path.join(dir, 'thread.json');
|
|
140
|
+
const caminhoClaimsJsonl = path.join(dir, 'claims.jsonl');
|
|
141
|
+
const caminhoLedgerJsonl = path.join(dir, 'ledger.jsonl');
|
|
142
|
+
const inline = [];
|
|
143
|
+
const pointers = [];
|
|
144
|
+
const summaries = [];
|
|
145
|
+
let nInline = 0;
|
|
146
|
+
let nPonteiro = 0;
|
|
147
|
+
let nResumo = 0;
|
|
148
|
+
const addInline = (titulo, conteudo, prov) => {
|
|
149
|
+
nInline += 1;
|
|
150
|
+
inline.push({ id: `inline-${nInline}`, tier: 'CRITICO', titulo, conteudo, proveniencia: prov });
|
|
151
|
+
};
|
|
152
|
+
const addPonteiro = (titulo, caminho, ancora, quando, endereco) => {
|
|
153
|
+
const rel = relativo(raiz, caminho);
|
|
154
|
+
// Dois artefatos citados por claims diferentes podem ser o mesmo arquivo: o handoff
|
|
155
|
+
// guarda um endereco por local, nao um por citacao.
|
|
156
|
+
if (pointers.some((p) => p.location === `${rel}#${ancora}`))
|
|
157
|
+
return;
|
|
158
|
+
nPonteiro += 1;
|
|
159
|
+
const ponteiro = {
|
|
160
|
+
id: `ptr-${nPonteiro}`,
|
|
161
|
+
tier: 'IMPORTANTE',
|
|
162
|
+
titulo,
|
|
163
|
+
source: rel,
|
|
164
|
+
location: `${rel}#${ancora}`,
|
|
165
|
+
sha256: sha256DoArquivo(caminho),
|
|
166
|
+
// O endereco cru continua sendo o contrato do B1. Quem quer respeitar o momento usa
|
|
167
|
+
// `ork recall <thread> --fase <quando>` (bloco B6), que resolve so o que e da hora.
|
|
168
|
+
retrieve_via: `ork handoff recall ${threadId} "${rel}#${ancora}"`,
|
|
169
|
+
retrieve_when: quando,
|
|
170
|
+
};
|
|
171
|
+
// O endereco semantico e ACRESCIMO: o `location` continua sendo path#ancora, e e por
|
|
172
|
+
// isso que o mesmo ponteiro resolve com o OrkMind fora do ar.
|
|
173
|
+
if (endereco)
|
|
174
|
+
ponteiro.orkmind = endereco;
|
|
175
|
+
pointers.push(ponteiro);
|
|
176
|
+
};
|
|
177
|
+
const addResumo = (text, prov) => {
|
|
178
|
+
nResumo += 1;
|
|
179
|
+
summaries.push({ id: `sum-${nResumo}`, tier: 'RESUMIVEL', text, provenance: prov });
|
|
180
|
+
};
|
|
181
|
+
// CRITICO 1: estado do mundo (base carimbada, modo, fase, worktree).
|
|
182
|
+
addInline('Estado do mundo da thread', estadoDoMundo(thread), proveniencia(raiz, caminhoThreadJson, 'json:base'));
|
|
183
|
+
// CRITICO 2: regra de pausa do bloco que a proxima sessao vai conduzir.
|
|
184
|
+
const bloco = (0, modos_1.blocoDaFase)(thread.modo, proximaFase);
|
|
185
|
+
addInline(`Regra de pausa do bloco ${bloco.fases.join('-')}`, bloco.pausa
|
|
186
|
+
? `Ao fim deste bloco HA PAUSA humana sobre: ${bloco.pausaSobre}.`
|
|
187
|
+
: 'Ao fim deste bloco NAO ha pausa humana: decisao autonoma, registrada no ledger.', proveniencia(raiz, caminhoThreadJson, 'json:blocos'));
|
|
188
|
+
// CRITICO 3: decisoes locked, que precisam voltar em toda sessao da thread.
|
|
189
|
+
for (const decisao of thread.decisoes ?? []) {
|
|
190
|
+
if (!decisao || typeof decisao !== 'object' || !decisao.locked)
|
|
191
|
+
continue;
|
|
192
|
+
const d = decisao;
|
|
193
|
+
addInline(`Decisao locked ${d.id}`, `${d.texto}\n(fechada por ${d.decididaPor})`, proveniencia(raiz, caminhoThreadJson, `json:decisoes.${d.id}`));
|
|
194
|
+
}
|
|
195
|
+
// CRITICO 4: claims ainda nao verificadas. IMPORTANTE: as ja verificadas viram ponteiro.
|
|
196
|
+
const claims = (0, claims_1.lerClaims)(raiz, threadId);
|
|
197
|
+
for (const c of claims) {
|
|
198
|
+
if (c.estado === 'verificado') {
|
|
199
|
+
addPonteiro(`Claim ${c.id} ja verificada: ${c.alegacao}`, caminhoClaimsJsonl, `claim:${c.id}`, proximaFase);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
addInline(`Claim ${c.id} pendente (${c.estado})`, `${c.alegacao}\narquivo: ${c.arquivo}\n` +
|
|
203
|
+
`verificar com: ${c.verificar.join(' && ') || '(sem comando declarado)'}`, proveniencia(raiz, caminhoClaimsJsonl, `claim:${c.id}`));
|
|
204
|
+
}
|
|
205
|
+
// O artefato citado pela claim e IMPORTANTE: vai como endereco, nao colado.
|
|
206
|
+
const artefato = caminhoDoArtefato(raiz, thread, c.arquivo);
|
|
207
|
+
if (artefato)
|
|
208
|
+
addPonteiro(`Artefato da claim ${c.id}`, artefato, 'tudo', proximaFase);
|
|
209
|
+
}
|
|
210
|
+
// CRITICO 5: baseline, quando existe (e a linha que separa regressao de divida).
|
|
211
|
+
if (thread.baseline) {
|
|
212
|
+
const b = thread.baseline;
|
|
213
|
+
addInline('Baseline gravada antes do GO', `commit ${b.commit} em ${b.gravadaEm}\n` +
|
|
214
|
+
b.comandos.map((c) => ` ${c.nome}: ${c.ok ? 'passava' : 'ja falhava'} (${c.comando})`).join('\n'), proveniencia(raiz, caminhoThreadJson, 'json:baseline'));
|
|
215
|
+
}
|
|
216
|
+
// IMPORTANTE: os prompts ja despachados, com o pedido original do builder.
|
|
217
|
+
for (const s of thread.sessoes) {
|
|
218
|
+
const caminhoPrompt = path.resolve(raiz, s.promptPath);
|
|
219
|
+
if (!fs.existsSync(caminhoPrompt))
|
|
220
|
+
continue;
|
|
221
|
+
addPonteiro(`Prompt da sessao ${s.slug} (fase ${s.fase})`, caminhoPrompt, 'pedido-do-builder', s.fase);
|
|
222
|
+
}
|
|
223
|
+
// RESUMIVEL: historico de tentativas e movimentacao, com proveniencia no ledger.
|
|
224
|
+
const eventos = (0, ledger_1.lerLedger)(dir);
|
|
225
|
+
if (eventos.length > 0) {
|
|
226
|
+
const porTipo = new Map();
|
|
227
|
+
for (const e of eventos)
|
|
228
|
+
porTipo.set(e.tipo, (porTipo.get(e.tipo) ?? 0) + 1);
|
|
229
|
+
addResumo(`${eventos.length} eventos no ledger da thread: ` +
|
|
230
|
+
[...porTipo.entries()].map(([t, n]) => `${t} x${n}`).join(', ') +
|
|
231
|
+
`. Ultimo evento: ${eventos[eventos.length - 1].tipo} em ${eventos[eventos.length - 1].ts}.`, proveniencia(raiz, caminhoLedgerJsonl, `evento:${eventos.length}`));
|
|
232
|
+
const falhas = eventos.filter((e) => e.tipo === ledger_1.TIPOS_DE_EVENTO.despachoFalhou);
|
|
233
|
+
if (falhas.length > 0) {
|
|
234
|
+
addResumo(`${falhas.length} despacho(s) falharam nesta thread; o motivo de cada um esta no ledger.`, proveniencia(raiz, caminhoLedgerJsonl, `evento:${eventos.indexOf(falhas[falhas.length - 1]) + 1}`));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (thread.sessoes.length > 0) {
|
|
238
|
+
addResumo(`${thread.sessoes.length} sessao(oes) despachada(s): ` +
|
|
239
|
+
thread.sessoes.map((s) => `${s.slug} (${s.fase})`).join(', ') +
|
|
240
|
+
'. Os prompts estao nos ponteiros acima; a exploracao ja concluida nao vai inline.', proveniencia(raiz, caminhoThreadJson, 'json:sessoes'));
|
|
241
|
+
}
|
|
242
|
+
// Bloco B6: com o regime `orkmind` valendo, o que a memoria semantica JA guarda deste
|
|
243
|
+
// produto vira ponteiro, nao texto colado. O `location` continua sendo o arquivo
|
|
244
|
+
// commitado, entao o mesmo ponteiro resolve por leitura dirigida quando o OrkMind cai.
|
|
245
|
+
const memoria = opcoes.memoria ?? (0, memoria_1.abrirMemoria)(carregado);
|
|
246
|
+
if (memoria.ativo) {
|
|
247
|
+
const anteriores = memoria.buscar({
|
|
248
|
+
collection: 'handoff',
|
|
249
|
+
tags: {
|
|
250
|
+
project: (0, memoria_1.tagsDoProjeto)(manifesto).project,
|
|
251
|
+
skill: [(0, memoria_1.skillDaColecao)('handoff')],
|
|
252
|
+
situation: [(0, memoria_1.situacaoDaThread)(threadId)],
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
for (const e of anteriores) {
|
|
256
|
+
const arquivo = (0, memoria_1.arquivoDoHandoff)(e);
|
|
257
|
+
const emDisco = arquivo ? path.resolve(raiz, arquivo) : '';
|
|
258
|
+
if (!emDisco || !fs.existsSync(emDisco))
|
|
259
|
+
continue;
|
|
260
|
+
addPonteiro(`Handoff anterior desta thread (${arquivo})`, emDisco, 'tudo', proximaFase, `orkmind://handoff/${e.id}`);
|
|
261
|
+
}
|
|
262
|
+
// Licoes das threads anteriores do produto: elas entram inline no GOAL (injecao por
|
|
263
|
+
// tag), e aqui viram endereco para as demais fases, que nao precisam carrega-las.
|
|
264
|
+
for (const e of (0, memoria_1.licoesDoProduto)(memoria, manifesto, threadId)) {
|
|
265
|
+
const postmortem = (0, memoria_1.postmortemDaLicao)(e);
|
|
266
|
+
const emDisco = postmortem ? path.resolve(raiz, postmortem) : '';
|
|
267
|
+
if (!emDisco || !fs.existsSync(emDisco))
|
|
268
|
+
continue;
|
|
269
|
+
addPonteiro(`Licao da thread ${(0, memoria_1.threadDaEntrada)(e) ?? '(anterior)'}`, emDisco, 'json:resumo', 'MASTER', `orkmind://learning/${e.id}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const handoff = {
|
|
273
|
+
versao: 1,
|
|
274
|
+
thread: threadId,
|
|
275
|
+
geradoEm: (0, util_1.agora)(),
|
|
276
|
+
// O regime EFETIVO, nao o pedido: um manifesto que pede `orkmind` com o OrkMind fora
|
|
277
|
+
// do ar grava `files`, e a sessao seguinte sabe exatamente o que pode pedir.
|
|
278
|
+
memory: memoria.regime,
|
|
279
|
+
de: { slug: thread.slug, fase: thread.faseAtual },
|
|
280
|
+
para: { slug: slugDestino, fase: proximaFase },
|
|
281
|
+
inline,
|
|
282
|
+
pointers,
|
|
283
|
+
summaries,
|
|
284
|
+
};
|
|
285
|
+
const caminho = caminhoHandoff(raiz, threadId);
|
|
286
|
+
const carimbo = handoff.geradoEm.replace(/[:.]/g, '-');
|
|
287
|
+
const caminhoHistorico = path.join(dirHandoffs(raiz, threadId), `${slugDestino}-${carimbo}.json`);
|
|
288
|
+
(0, util_1.gravarJson)(caminho, handoff);
|
|
289
|
+
(0, util_1.gravarJson)(caminhoHistorico, handoff);
|
|
290
|
+
// Publicacao na memoria semantica: o pacote de fase (colecao `handoff`) e as decisoes
|
|
291
|
+
// fechadas (colecao `decision`), que sao o que uma thread futura precisa herdar.
|
|
292
|
+
if (memoria.ativo) {
|
|
293
|
+
const publicado = (0, memoria_1.gravarHandoff)(memoria, manifesto, thread, handoff, relativo(raiz, caminhoHistorico));
|
|
294
|
+
const decisoes = (0, memoria_1.gravarDecisoes)(memoria, manifesto, thread);
|
|
295
|
+
(0, ledger_1.registrar)(dir, threadId, ledger_1.TIPOS_DE_EVENTO.memoriaGravada, {
|
|
296
|
+
origem: 'handoff export',
|
|
297
|
+
regime: memoria.regime,
|
|
298
|
+
handoff: publicado.ok ? publicado.id : null,
|
|
299
|
+
handoffDetalhe: publicado.detalhe,
|
|
300
|
+
decisoes: decisoes.gravadas.length,
|
|
301
|
+
decisoesFalhas: decisoes.falhas,
|
|
302
|
+
tenant: memoria.estado.tenant,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
else if (memoria.estado.pedido === 'orkmind') {
|
|
306
|
+
(0, ledger_1.registrar)(dir, threadId, ledger_1.TIPOS_DE_EVENTO.memoriaDegradada, {
|
|
307
|
+
origem: 'handoff export',
|
|
308
|
+
pedido: memoria.estado.pedido,
|
|
309
|
+
efetivo: memoria.estado.efetivo,
|
|
310
|
+
motivo: memoria.estado.motivo,
|
|
311
|
+
detalhe: memoria.estado.detalhe,
|
|
312
|
+
correcao: memoria.estado.correcao,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
(0, ledger_1.registrar)(dir, threadId, ledger_1.TIPOS_DE_EVENTO.handoffExportado, {
|
|
316
|
+
memory: handoff.memory,
|
|
317
|
+
de: handoff.de,
|
|
318
|
+
para: handoff.para,
|
|
319
|
+
inline: inline.length,
|
|
320
|
+
pointers: pointers.length,
|
|
321
|
+
summaries: summaries.length,
|
|
322
|
+
arquivo: relativo(raiz, caminho),
|
|
323
|
+
historico: relativo(raiz, caminhoHistorico),
|
|
324
|
+
triagem: 'CRITICO inline, IMPORTANTE por ponteiro, RESUMIVEL por resumo com proveniencia',
|
|
325
|
+
});
|
|
326
|
+
return { handoff, caminho, caminhoHistorico };
|
|
327
|
+
}
|
|
328
|
+
/** Normaliza um titulo de markdown para ancora (`## Pedido do builder` -> `pedido-do-builder`). */
|
|
329
|
+
function ancoraDeTitulo(titulo) {
|
|
330
|
+
return titulo
|
|
331
|
+
.normalize('NFD')
|
|
332
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
333
|
+
.toLowerCase()
|
|
334
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
335
|
+
.replace(/^-+|-+$/g, '');
|
|
336
|
+
}
|
|
337
|
+
function trechoPorLinhas(linhas, inicio, fim) {
|
|
338
|
+
return linhas.slice(inicio - 1, fim).join('\n');
|
|
339
|
+
}
|
|
340
|
+
/** Resolve uma ancora de markdown: da linha do titulo ate o proximo titulo de mesmo nivel. */
|
|
341
|
+
function secaoDeMarkdown(linhas, ancora) {
|
|
342
|
+
for (let i = 0; i < linhas.length; i++) {
|
|
343
|
+
const casa = /^(#{1,6})\s+(.*)$/.exec(linhas[i]);
|
|
344
|
+
if (!casa)
|
|
345
|
+
continue;
|
|
346
|
+
if (ancoraDeTitulo(casa[2]) !== ancora)
|
|
347
|
+
continue;
|
|
348
|
+
const nivel = casa[1].length;
|
|
349
|
+
let fim = linhas.length;
|
|
350
|
+
for (let j = i + 1; j < linhas.length; j++) {
|
|
351
|
+
const seguinte = /^(#{1,6})\s+/.exec(linhas[j]);
|
|
352
|
+
if (seguinte && seguinte[1].length <= nivel) {
|
|
353
|
+
fim = j;
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return { inicio: i + 1, fim };
|
|
358
|
+
}
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
function valorEmJson(dados, caminho) {
|
|
362
|
+
let atual = dados;
|
|
363
|
+
for (const parte of caminho.split('.')) {
|
|
364
|
+
if (atual === null || typeof atual !== 'object')
|
|
365
|
+
return undefined;
|
|
366
|
+
atual = atual[parte];
|
|
367
|
+
}
|
|
368
|
+
return atual;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* `ork handoff recall`: resolve um ponteiro `path#ancora` de volta ao conteudo.
|
|
372
|
+
*
|
|
373
|
+
* E a prova de que a recuperacao tardia funciona: a sessao nova nasce so com o inline e
|
|
374
|
+
* busca o resto quando precisa, sem queimar a janela recem-aberta.
|
|
375
|
+
*/
|
|
376
|
+
function recall(carregado, threadId, ponteiro) {
|
|
377
|
+
const { raiz } = carregado;
|
|
378
|
+
const corte = ponteiro.lastIndexOf('#');
|
|
379
|
+
const bruto = corte >= 0 ? ponteiro.slice(0, corte) : ponteiro;
|
|
380
|
+
const ancora = corte >= 0 ? ponteiro.slice(corte + 1) : 'tudo';
|
|
381
|
+
const caminho = path.resolve(raiz, bruto);
|
|
382
|
+
if (!fs.existsSync(caminho) || !fs.statSync(caminho).isFile()) {
|
|
383
|
+
throw new Error(`ponteiro nao resolve: arquivo "${bruto}" nao existe a partir de ${raiz}`);
|
|
384
|
+
}
|
|
385
|
+
const conteudoBruto = fs.readFileSync(caminho, 'utf8');
|
|
386
|
+
const linhas = conteudoBruto.split('\n');
|
|
387
|
+
const sha = sha256DoArquivo(caminho);
|
|
388
|
+
const base = { location: `${bruto}#${ancora}`, caminho, ancora, sha256: sha };
|
|
389
|
+
const registrarRecall = (r) => {
|
|
390
|
+
(0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, threadId), threadId, ledger_1.TIPOS_DE_EVENTO.handoffRecuperado, {
|
|
391
|
+
location: r.location,
|
|
392
|
+
sha256: r.sha256,
|
|
393
|
+
metodo: r.metodo,
|
|
394
|
+
intervalo: r.intervalo,
|
|
395
|
+
bytes: Buffer.byteLength(r.conteudo, 'utf8'),
|
|
396
|
+
});
|
|
397
|
+
return r;
|
|
398
|
+
};
|
|
399
|
+
if (ancora === 'tudo' || ancora === '') {
|
|
400
|
+
return registrarRecall({
|
|
401
|
+
...base,
|
|
402
|
+
conteudo: conteudoBruto,
|
|
403
|
+
intervalo: { inicio: 1, fim: linhas.length },
|
|
404
|
+
metodo: 'arquivo inteiro',
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
const faixa = /^L(\d+)(?:-L?(\d+))?$/.exec(ancora);
|
|
408
|
+
if (faixa) {
|
|
409
|
+
const inicio = Math.max(1, Number(faixa[1]));
|
|
410
|
+
const fim = Math.min(linhas.length, Number(faixa[2] ?? faixa[1]));
|
|
411
|
+
if (inicio > linhas.length) {
|
|
412
|
+
throw new Error(`ponteiro nao resolve: ${bruto} tem ${linhas.length} linhas, e a ancora pede ${inicio}`);
|
|
413
|
+
}
|
|
414
|
+
return registrarRecall({
|
|
415
|
+
...base,
|
|
416
|
+
conteudo: trechoPorLinhas(linhas, inicio, fim),
|
|
417
|
+
intervalo: { inicio, fim },
|
|
418
|
+
metodo: 'faixa de linhas',
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
const claim = /^claim:(.+)$/.exec(ancora);
|
|
422
|
+
if (claim) {
|
|
423
|
+
const alvo = claim[1];
|
|
424
|
+
for (let i = linhas.length - 1; i >= 0; i--) {
|
|
425
|
+
if (linhas[i].trim() === '')
|
|
426
|
+
continue;
|
|
427
|
+
try {
|
|
428
|
+
const dados = JSON.parse(linhas[i]);
|
|
429
|
+
if (dados.id !== alvo)
|
|
430
|
+
continue;
|
|
431
|
+
return registrarRecall({
|
|
432
|
+
...base,
|
|
433
|
+
conteudo: JSON.stringify(dados, null, 2),
|
|
434
|
+
intervalo: { inicio: i + 1, fim: i + 1 },
|
|
435
|
+
metodo: 'claim por id no JSONL (a gravacao mais recente vence)',
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
/* linha corrompida nao interrompe a busca */
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
throw new Error(`ponteiro nao resolve: claim "${alvo}" nao existe em ${bruto}`);
|
|
443
|
+
}
|
|
444
|
+
const evento = /^evento:(\d+)$/.exec(ancora);
|
|
445
|
+
if (evento) {
|
|
446
|
+
const n = Number(evento[1]);
|
|
447
|
+
const uteis = linhas.filter((l) => l.trim() !== '');
|
|
448
|
+
if (n < 1 || n > uteis.length) {
|
|
449
|
+
throw new Error(`ponteiro nao resolve: ${bruto} tem ${uteis.length} eventos, e a ancora pede o ${n}`);
|
|
450
|
+
}
|
|
451
|
+
let conteudo = uteis[n - 1];
|
|
452
|
+
try {
|
|
453
|
+
conteudo = JSON.stringify(JSON.parse(conteudo), null, 2);
|
|
454
|
+
}
|
|
455
|
+
catch {
|
|
456
|
+
/* mantem a linha crua quando ela nao e JSON valido */
|
|
457
|
+
}
|
|
458
|
+
return registrarRecall({
|
|
459
|
+
...base,
|
|
460
|
+
conteudo,
|
|
461
|
+
intervalo: { inicio: n, fim: n },
|
|
462
|
+
metodo: 'evento por posicao no ledger JSONL',
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
const json = /^json:(.+)$/.exec(ancora);
|
|
466
|
+
if (json) {
|
|
467
|
+
const valor = valorEmJson(JSON.parse(conteudoBruto), json[1]);
|
|
468
|
+
if (valor === undefined) {
|
|
469
|
+
throw new Error(`ponteiro nao resolve: caminho "${json[1]}" nao existe em ${bruto}`);
|
|
470
|
+
}
|
|
471
|
+
return registrarRecall({
|
|
472
|
+
...base,
|
|
473
|
+
conteudo: typeof valor === 'string' ? valor : JSON.stringify(valor, null, 2),
|
|
474
|
+
intervalo: null,
|
|
475
|
+
metodo: 'caminho de campo dentro do JSON',
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
const secao = secaoDeMarkdown(linhas, ancora);
|
|
479
|
+
if (secao) {
|
|
480
|
+
return registrarRecall({
|
|
481
|
+
...base,
|
|
482
|
+
conteudo: trechoPorLinhas(linhas, secao.inicio, secao.fim),
|
|
483
|
+
intervalo: secao,
|
|
484
|
+
metodo: 'secao de markdown pelo titulo',
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
throw new Error(`ponteiro nao resolve: ancora "${ancora}" nao encontrada em ${bruto} ` +
|
|
488
|
+
'(ancoras aceitas: tudo, L<n>-L<m>, claim:<id>, evento:<n>, json:<campo.campo>, <titulo-de-secao>)');
|
|
489
|
+
}
|
|
490
|
+
/** Texto de `ork handoff export`. */
|
|
491
|
+
function textoDoHandoff(h, caminho) {
|
|
492
|
+
const linhas = [];
|
|
493
|
+
linhas.push(`Handoff exportado da thread ${h.thread} (regime memory: ${h.memory})`);
|
|
494
|
+
linhas.push(` de ${h.de.slug} (fase ${h.de.fase})`);
|
|
495
|
+
linhas.push(` para ${h.para.slug} (fase ${h.para.fase})`);
|
|
496
|
+
linhas.push(` arquivo ${caminho}`);
|
|
497
|
+
linhas.push('');
|
|
498
|
+
linhas.push(`CRITICO (${h.inline.length} item(ns), vao inline no prompt da nova sessao):`);
|
|
499
|
+
for (const i of h.inline) {
|
|
500
|
+
linhas.push(` ${i.id} ${i.titulo}`);
|
|
501
|
+
linhas.push(` proveniencia: ${i.proveniencia.location} (sha256 ${i.proveniencia.sha256.slice(0, 12)})`);
|
|
502
|
+
}
|
|
503
|
+
linhas.push('');
|
|
504
|
+
linhas.push(`IMPORTANTE (${h.pointers.length} ponteiro(s), recuperados sob demanda):`);
|
|
505
|
+
for (const p of h.pointers) {
|
|
506
|
+
linhas.push(` ${p.id} ${p.titulo}`);
|
|
507
|
+
linhas.push(` location: ${p.location} (sha256 ${p.sha256.slice(0, 12)})`);
|
|
508
|
+
if (p.orkmind)
|
|
509
|
+
linhas.push(` orkmind: ${p.orkmind}`);
|
|
510
|
+
linhas.push(` quando: ${p.retrieve_when}`);
|
|
511
|
+
linhas.push(` recuperar: ${p.retrieve_via}`);
|
|
512
|
+
linhas.push(` no momento: ork recall ${h.thread} --fase ${p.retrieve_when}`);
|
|
513
|
+
}
|
|
514
|
+
linhas.push('');
|
|
515
|
+
linhas.push(`RESUMIVEL (${h.summaries.length} resumo(s), com proveniencia declarada):`);
|
|
516
|
+
for (const s of h.summaries) {
|
|
517
|
+
linhas.push(` ${s.id} ${s.text}`);
|
|
518
|
+
linhas.push(` proveniencia: ${s.provenance.location} (sha256 ${s.provenance.sha256.slice(0, 12)})`);
|
|
519
|
+
}
|
|
520
|
+
return linhas.join('\n');
|
|
521
|
+
}
|