@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,773 @@
1
+ "use strict";
2
+ /**
3
+ * A camada de memoria do `ork` (bloco B6): o que o nucleo grava, com que tags, e o que
4
+ * ele injeta de volta no prompt de cada fase.
5
+ *
6
+ * Divisao de trabalho deste bloco:
7
+ * - `orkmind.ts` sabe FALAR com o OrkMind (transporte, degradacao, governanca);
8
+ * - `memoria.ts` (aqui) sabe O QUE o Orkastery grava e le de volta (decisao, handoff,
9
+ * rule, learning, roadmap), e como isso vira contexto de prompt;
10
+ * - `recall.ts` sabe RESOLVER o que ficou fora do prompt, no momento indicado.
11
+ *
12
+ * Nenhuma regra de negocio nova nasce aqui. Decisao, claim, baseline, achado e score
13
+ * continuam sendo do nucleo que ja existe: esta camada os PUBLICA e os RECUPERA.
14
+ *
15
+ * A propriedade que o B6 precisa provar e a da injecao deterministica: um prompt de fase
16
+ * carrega 100 por cento das decisoes fechadas da thread, venham elas do `thread.json`
17
+ * (regime `files`) ou da colecao `decision` (regime `orkmind`). Por isso a injecao NAO
18
+ * depende do OrkMind estar ligado: ela troca de fonte, nao de garantia.
19
+ */
20
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ var desc = Object.getOwnPropertyDescriptor(m, k);
23
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
24
+ desc = { enumerable: true, get: function() { return m[k]; } };
25
+ }
26
+ Object.defineProperty(o, k2, desc);
27
+ }) : (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ o[k2] = m[k];
30
+ }));
31
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
32
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
33
+ }) : function(o, v) {
34
+ o["default"] = v;
35
+ });
36
+ var __importStar = (this && this.__importStar) || (function () {
37
+ var ownKeys = function(o) {
38
+ ownKeys = Object.getOwnPropertyNames || function (o) {
39
+ var ar = [];
40
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
41
+ return ar;
42
+ };
43
+ return ownKeys(o);
44
+ };
45
+ return function (mod) {
46
+ if (mod && mod.__esModule) return mod;
47
+ var result = {};
48
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
49
+ __setModuleDefault(result, mod);
50
+ return result;
51
+ };
52
+ })();
53
+ Object.defineProperty(exports, "__esModule", { value: true });
54
+ exports.abrirMemoria = abrirMemoria;
55
+ exports.tagsDoProjeto = tagsDoProjeto;
56
+ exports.situacaoDaThread = situacaoDaThread;
57
+ exports.situacaoDaFase = situacaoDaFase;
58
+ exports.skillDaColecao = skillDaColecao;
59
+ exports.tagsDaThread = tagsDaThread;
60
+ exports.gravarDecisoes = gravarDecisoes;
61
+ exports.gravarHandoff = gravarHandoff;
62
+ exports.gravarPolicies = gravarPolicies;
63
+ exports.gravarLearning = gravarLearning;
64
+ exports.gravarOrigemDaThread = gravarOrigemDaThread;
65
+ exports.gravarPropostaDeAuditoria = gravarPropostaDeAuditoria;
66
+ exports.decisaoDaEntrada = decisaoDaEntrada;
67
+ exports.threadDaEntrada = threadDaEntrada;
68
+ exports.arquivoDoHandoff = arquivoDoHandoff;
69
+ exports.postmortemDaLicao = postmortemDaLicao;
70
+ exports.decisoesDaThreadNaMemoria = decisoesDaThreadNaMemoria;
71
+ exports.licoesDoProduto = licoesDoProduto;
72
+ exports.regrasDoProjeto = regrasDoProjeto;
73
+ exports.injecaoDaFase = injecaoDaFase;
74
+ exports.textoDaInjecao = textoDaInjecao;
75
+ exports.caminhoRelativoDoHandoff = caminhoRelativoDoHandoff;
76
+ exports.sincronizarMemoria = sincronizarMemoria;
77
+ exports.publicar = publicar;
78
+ exports.publicarPropostas = publicarPropostas;
79
+ exports.textoDoEstado = textoDoEstado;
80
+ exports.textoDoSync = textoDoSync;
81
+ exports.parsePrioridade = parsePrioridade;
82
+ const fs = __importStar(require("node:fs"));
83
+ const path = __importStar(require("node:path"));
84
+ const master_1 = require("./master");
85
+ const modos_1 = require("./modos");
86
+ const orkmind_1 = require("./orkmind");
87
+ const ledger_1 = require("./ledger");
88
+ const thread_1 = require("./thread");
89
+ const util_1 = require("./util");
90
+ /** Abre a memoria do projeto, ja com o regime efetivo resolvido. */
91
+ function abrirMemoria(carregado, opcoes = {}) {
92
+ const { estado, driver } = (0, orkmind_1.resolverRegime)(carregado.manifesto, opcoes.driver ?? null);
93
+ const ativo = estado.efetivo === 'orkmind' && driver !== null;
94
+ return {
95
+ estado,
96
+ ativo,
97
+ regime: estado.efetivo,
98
+ gravar(entrada) {
99
+ const problemas = (0, orkmind_1.violacoesDeGovernanca)(entrada);
100
+ if (problemas.length > 0) {
101
+ return {
102
+ ok: false,
103
+ id: null,
104
+ duplicada: false,
105
+ collection: entrada.collection,
106
+ detalhe: `governanca: ${problemas.join('; ')}`,
107
+ };
108
+ }
109
+ if (!ativo || !driver) {
110
+ return {
111
+ ok: false,
112
+ id: null,
113
+ duplicada: false,
114
+ collection: entrada.collection,
115
+ detalhe: `regime ${estado.efetivo}: nada gravado na memoria semantica (${estado.detalhe})`,
116
+ };
117
+ }
118
+ return driver.adicionar(entrada);
119
+ },
120
+ buscar(consulta) {
121
+ if (!ativo || !driver)
122
+ return [];
123
+ const colecoes = consulta.collection ? [consulta.collection] : [...orkmind_1.COLECOES_DO_ORK];
124
+ const tudo = colecoes.flatMap((c) => driver.exportar(c));
125
+ return (0, orkmind_1.filtrarPorTags)(tudo, consulta);
126
+ },
127
+ porId(colecao, id) {
128
+ if (!ativo || !driver)
129
+ return null;
130
+ return driver.exportar(colecao).find((e) => e.id === id) ?? null;
131
+ },
132
+ };
133
+ }
134
+ // ---------------------------------------------------------------------------
135
+ // Tags: as 5 dimensoes, montadas em um lugar so.
136
+ // ---------------------------------------------------------------------------
137
+ /**
138
+ * As tags de projeto. Toda entrada gravada pelo `ork` carrega estas tres dimensoes, e e
139
+ * por elas que a busca deterministica encontra o que e daquele produto e daquele agente.
140
+ */
141
+ function tagsDoProjeto(manifesto) {
142
+ return {
143
+ project: [manifesto.memory.tenant || manifesto.project.name],
144
+ agent: ['ork'],
145
+ domain: ['orkastery'],
146
+ };
147
+ }
148
+ /** Tag de situacao que amarra uma entrada a uma thread. */
149
+ function situacaoDaThread(threadId) {
150
+ return `thread:${threadId}`;
151
+ }
152
+ /** Tag de situacao que amarra uma entrada a uma fase. */
153
+ function situacaoDaFase(fase) {
154
+ return `fase:${fase}`;
155
+ }
156
+ /** Tag de skill por colecao: e o recorte que separa decisao de licao na mesma base. */
157
+ function skillDaColecao(colecao) {
158
+ return `orkastery-${colecao}`;
159
+ }
160
+ /** As tags completas de uma entrada de thread. */
161
+ function tagsDaThread(manifesto, colecao, thread, fase) {
162
+ const situacao = [situacaoDaThread(thread.id), `modo:${thread.modo}`];
163
+ if (fase)
164
+ situacao.push(situacaoDaFase(fase));
165
+ return {
166
+ ...tagsDoProjeto(manifesto),
167
+ skill: [skillDaColecao(colecao)],
168
+ situation: situacao,
169
+ };
170
+ }
171
+ function juntar(resultados, regime) {
172
+ const duplicadas = resultados.filter((r) => r.ok && r.duplicada).length;
173
+ const falhas = resultados.filter((r) => !r.ok).length;
174
+ const novas = resultados.filter((r) => r.ok && !r.duplicada).length;
175
+ return {
176
+ regime,
177
+ gravadas: resultados,
178
+ duplicadas,
179
+ falhas,
180
+ detalhe: regime === 'files'
181
+ ? 'regime files: nada foi para a memoria semantica (degradacao honesta)'
182
+ : `${novas} nova(s), ${duplicadas} ja existente(s), ${falhas} falha(s)`,
183
+ };
184
+ }
185
+ /**
186
+ * Grava as decisoes FECHADAS (`locked`) da thread na colecao `decision`.
187
+ *
188
+ * Prioridade `high` e nunca `mandatory`: quem transforma uma decisao de thread em regra
189
+ * do ecossistema e o humano, no OrkMind, com a autenticacao dele.
190
+ */
191
+ function gravarDecisoes(memoria, manifesto, thread) {
192
+ const resultados = [];
193
+ for (const d of thread.decisoes ?? []) {
194
+ if (!d.locked)
195
+ continue;
196
+ resultados.push(memoria.gravar({
197
+ collection: 'decision',
198
+ content: `Decisao ${d.id} fechada na thread ${thread.id} ("${thread.nome}") do produto ` +
199
+ `${thread.projeto.name}.\n${d.texto}\n` +
200
+ `Fechada por ${d.decididaPor} em ${d.decididaEm}. Modo de conducao: ${(0, modos_1.tagDoModo)(thread.modo)}.`,
201
+ // A tag `decisao:<id>` NAO e enfeite: o CLI do OrkMind nao tem canal de
202
+ // metadata, entao a identidade da entrada precisa viajar por onde a busca por
203
+ // tag ja e exata. Sem ela, a mesma decisao voltaria duas vezes ao prompt (uma
204
+ // do thread.json, outra da memoria), porque nao haveria como deduplicar.
205
+ tags: {
206
+ ...tagsDaThread(manifesto, 'decision', thread),
207
+ situation: [
208
+ situacaoDaThread(thread.id),
209
+ `modo:${thread.modo}`,
210
+ `decisao:${d.id}`,
211
+ ],
212
+ },
213
+ priority: 'high',
214
+ metadata: {
215
+ thread: thread.id,
216
+ decisao: d.id,
217
+ locked: true,
218
+ produto: thread.projeto.name,
219
+ decididaPor: d.decididaPor,
220
+ decididaEm: d.decididaEm,
221
+ },
222
+ }));
223
+ }
224
+ return juntar(resultados, memoria.regime);
225
+ }
226
+ /**
227
+ * Grava o pacote de handoff da fase na colecao `handoff`.
228
+ *
229
+ * O corpo gravado e o RESUMO tipado (contagens, momento, itens criticos), nao o JSON
230
+ * inteiro: o handoff completo continua commitado em `.orkastery/threads/<id>/`, e o
231
+ * ponteiro do proprio handoff diz onde ele esta. Memoria semantica nao e backup.
232
+ */
233
+ function gravarHandoff(memoria, manifesto, thread, handoff, caminhoRelativo) {
234
+ const criticos = handoff.inline.map((i) => `- ${i.titulo} (${i.proveniencia.location})`).join('\n');
235
+ return memoria.gravar({
236
+ collection: 'handoff',
237
+ content: `Handoff da thread ${thread.id} de ${handoff.de.slug} (${handoff.de.fase}) para ` +
238
+ `${handoff.para.slug} (${handoff.para.fase}), regime ${handoff.memory}.\n` +
239
+ `Triagem: ${handoff.inline.length} inline (CRITICO), ${handoff.pointers.length} ponteiro(s) ` +
240
+ `(IMPORTANTE), ${handoff.summaries.length} resumo(s) (RESUMIVEL).\n` +
241
+ `Arquivo commitado: ${caminhoRelativo}\n` +
242
+ `Itens criticos:\n${criticos || '- (nenhum)'}`,
243
+ tags: {
244
+ ...tagsDaThread(manifesto, 'handoff', thread),
245
+ situation: [
246
+ situacaoDaThread(thread.id),
247
+ `modo:${thread.modo}`,
248
+ ...(handoff.para.fase ? [situacaoDaFase(handoff.para.fase)] : []),
249
+ `slug:${handoff.para.slug}`,
250
+ ],
251
+ },
252
+ priority: 'high',
253
+ metadata: {
254
+ thread: thread.id,
255
+ de: handoff.de,
256
+ para: handoff.para,
257
+ arquivo: caminhoRelativo,
258
+ inline: handoff.inline.length,
259
+ pointers: handoff.pointers.length,
260
+ summaries: handoff.summaries.length,
261
+ },
262
+ });
263
+ }
264
+ /**
265
+ * Grava as policies do manifesto na colecao `rule`.
266
+ *
267
+ * Sem `mandatory` e sem `critical`, sempre: o `ork` publica a policy do projeto como
268
+ * conhecimento recuperavel, e NAO se promove a autor de regra do ecossistema. Promover
269
+ * uma policy a regra mandatoria e ato de humano autenticado no OrkMind.
270
+ */
271
+ function gravarPolicies(memoria, manifesto) {
272
+ const resultados = [];
273
+ for (const [nome, postura] of Object.entries(manifesto.policies ?? {})) {
274
+ resultados.push(memoria.gravar({
275
+ collection: 'rule',
276
+ content: `Policy "${nome}" do produto ${manifesto.project.name}: postura ${postura}.\n` +
277
+ 'Declarada em orkastery.yaml (policies) e avaliada pelo gate tipado do ork antes ' +
278
+ 'de todo despacho de fase. Esta entrada e publicacao do ork (source: agent) e nao ' +
279
+ 'e regra mandatoria do ecossistema.',
280
+ tags: {
281
+ ...tagsDoProjeto(manifesto),
282
+ skill: [skillDaColecao('rule')],
283
+ situation: [`policy:${nome}`, `postura:${postura}`],
284
+ },
285
+ priority: 'high',
286
+ metadata: { policy: nome, postura, origem: 'orkastery.yaml' },
287
+ }));
288
+ }
289
+ return juntar(resultados, memoria.regime);
290
+ }
291
+ /**
292
+ * Grava a licao da thread fechada na colecao `learning`.
293
+ *
294
+ * E a entrada que uma thread NOVA do mesmo produto recebe no GOAL: score, classes de
295
+ * falha e o resumo do postmortem, com a thread de origem declarada.
296
+ */
297
+ function gravarLearning(memoria, manifesto, thread, postmortem, master) {
298
+ const classes = postmortem.classesDeFalha.join(', ') || 'sem-falha';
299
+ return memoria.gravar({
300
+ collection: 'learning',
301
+ content: `Licao da thread ${thread.id} ("${thread.nome}") do produto ${master.projeto.name}.\n` +
302
+ `Score humano: ${master.score}/5 por ${master.avaliadoPor}. Justificativa: ${master.justificativa}\n` +
303
+ `Classes de falha: ${classes}. Modo: ${master.tag}. Variante: ${master.variante ?? 'nenhuma'}.\n` +
304
+ `Fases percorridas: ${postmortem.fasesPercorridas.map((f) => f.fase).join(' -> ') || '(nenhuma)'}.\n` +
305
+ `Gates bloqueados: ${postmortem.gatesBloqueados.length}. Pausas humanas: ${postmortem.pausasHumanas}. ` +
306
+ `Decisoes autonomas: ${postmortem.decisoesAutonomas}.\n` +
307
+ `Resumo: ${master.resumo}\n` +
308
+ // Proveniencia no CORPO, e nao so em metadata: e o que permite ao `ork recall`
309
+ // achar o arquivo commitado desta licao quando o transporte nao carrega metadata.
310
+ `Postmortem: .orkastery/threads/${thread.id}/POSTMORTEM.json`,
311
+ tags: {
312
+ ...tagsDoProjeto(manifesto),
313
+ skill: [skillDaColecao('learning')],
314
+ situation: [
315
+ situacaoDaThread(thread.id),
316
+ `score:${master.score}`,
317
+ ...postmortem.classesDeFalha.map((c) => `classe:${c}`),
318
+ ],
319
+ },
320
+ // Licao com score baixo pesa mais na proxima thread: e o que o builder precisa ver
321
+ // antes de repetir a mesma classe de falha.
322
+ priority: master.score <= 2 ? 'high' : 'medium',
323
+ metadata: {
324
+ thread: thread.id,
325
+ score: master.score,
326
+ classes: postmortem.classesDeFalha,
327
+ produto: master.projeto.name,
328
+ avaliadoPor: master.avaliadoPor,
329
+ postmortem: `.orkastery/threads/${thread.id}/POSTMORTEM.json`,
330
+ },
331
+ });
332
+ }
333
+ /** Grava a origem da thread na colecao `roadmap` (de onde ela veio, e por que). */
334
+ function gravarOrigemDaThread(memoria, manifesto, thread, origem) {
335
+ return memoria.gravar({
336
+ collection: 'roadmap',
337
+ content: `Thread ${thread.id} ("${thread.nome}") aberta no produto ${thread.projeto.name}.\n` +
338
+ `Origem: ${origem.tipo}${origem.referencia ? ` ${origem.referencia}` : ''}.\n` +
339
+ `${origem.detalhe}\n` +
340
+ `Modo ${(0, modos_1.tagDoModo)(thread.modo)}, base ${thread.base.branch} @ ${thread.base.commit}.`,
341
+ tags: {
342
+ ...tagsDaThread(manifesto, 'roadmap', thread),
343
+ situation: [situacaoDaThread(thread.id), `origem:${origem.tipo}`],
344
+ },
345
+ priority: 'medium',
346
+ metadata: {
347
+ thread: thread.id,
348
+ origem: origem.tipo,
349
+ referencia: origem.referencia,
350
+ produto: thread.projeto.name,
351
+ },
352
+ });
353
+ }
354
+ /**
355
+ * Grava a proposta de ajuste de um achado de auditoria na colecao `roadmap`.
356
+ *
357
+ * E o mesmo bloco obrigatorio do B5 (impacto, fix, estimativa, passo irreversivel), agora
358
+ * enderecado ao roadmap do produto pela memoria em vez de so pelo board em arquivo.
359
+ */
360
+ function gravarPropostaDeAuditoria(memoria, manifesto, achado) {
361
+ const p = achado.proposta;
362
+ return memoria.gravar({
363
+ collection: 'roadmap',
364
+ content: `Proposta do achado ${achado.id} (${achado.pack}/${achado.regra}, severidade ${achado.severidade}) ` +
365
+ `para o roadmap de ${p.destino}.\n` +
366
+ `Titulo: ${achado.titulo}\n` +
367
+ `Evidencia: ${achado.arquivo}${achado.linha ? `:${achado.linha}` : ''}\n` +
368
+ `Impacto: ${p.impacto}\nFix: ${p.fix}\nEstimativa: ${p.estimativa}\n` +
369
+ `Passo irreversivel: ${p.passoIrreversivel || '(nenhum)'}\n` +
370
+ `Claim do auditor: ${achado.claim.alegacao} (verificar com: ${achado.claim.verificar.join(' && ') || 'sem comando'})`,
371
+ tags: {
372
+ ...tagsDoProjeto(manifesto),
373
+ skill: [skillDaColecao('roadmap')],
374
+ situation: [
375
+ `achado:${achado.id}`,
376
+ `rodada:${achado.rodada}`,
377
+ `pack:${achado.pack}`,
378
+ `regra:${achado.regra}`,
379
+ `severidade:${achado.severidade}`,
380
+ ],
381
+ },
382
+ priority: achado.severidade === 'critico' ? 'high' : 'medium',
383
+ metadata: {
384
+ achado: achado.id,
385
+ rodada: achado.rodada,
386
+ pack: achado.pack,
387
+ regra: achado.regra,
388
+ severidade: achado.severidade,
389
+ destino: p.destino,
390
+ arquivo: achado.arquivo,
391
+ linha: achado.linha,
392
+ },
393
+ });
394
+ }
395
+ // ---------------------------------------------------------------------------
396
+ // Leitura: injecao deterministica por tag na montagem de prompt.
397
+ // ---------------------------------------------------------------------------
398
+ /**
399
+ * Le um campo de identidade de uma entrada, na ordem em que ele sobrevive ao transporte.
400
+ *
401
+ * O CLI do OrkMind grava colecao, conteudo, tags, prioridade, escopo e origem, e NAO tem
402
+ * canal de metadata. Um leitor que dependesse so de `metadata` funcionaria com o driver
403
+ * em memoria e falharia contra o OrkMind de verdade, exatamente onde importa. Por isso a
404
+ * identidade viaja em tres lugares, e a leitura tenta os tres:
405
+ *
406
+ * 1. `metadata` (drivers que a carregam, como a API e o driver de teste);
407
+ * 2. a tag `situation` no formato `<campo>:<valor>` (busca por tag e exata);
408
+ * 3. uma linha `Rotulo: valor` no proprio conteudo (proveniencia declarada em texto).
409
+ */
410
+ function daMetadata(e, campo) {
411
+ const bruto = e.metadata?.[campo];
412
+ return typeof bruto === 'string' && bruto !== '' ? bruto : null;
413
+ }
414
+ function daTag(e, prefixo) {
415
+ const achada = (e.tags.situation ?? []).find((t) => t.startsWith(`${prefixo}:`));
416
+ return achada ? achada.slice(prefixo.length + 1) : null;
417
+ }
418
+ function doConteudo(e, rotulo) {
419
+ const linha = e.content.split('\n').find((l) => l.trim().startsWith(`${rotulo}:`));
420
+ return linha ? linha.trim().slice(rotulo.length + 1).trim() : null;
421
+ }
422
+ /** Id da decisao (`D1`) que originou a entrada, ou null. */
423
+ function decisaoDaEntrada(e) {
424
+ return daMetadata(e, 'decisao') ?? daTag(e, 'decisao');
425
+ }
426
+ /** Thread de origem da entrada, ou null. */
427
+ function threadDaEntrada(e) {
428
+ return daMetadata(e, 'thread') ?? daTag(e, 'thread');
429
+ }
430
+ /** Arquivo commitado do handoff publicado, ou null. */
431
+ function arquivoDoHandoff(e) {
432
+ return daMetadata(e, 'arquivo') ?? doConteudo(e, 'Arquivo commitado');
433
+ }
434
+ /** POSTMORTEM commitado da licao publicada, ou null. */
435
+ function postmortemDaLicao(e) {
436
+ return daMetadata(e, 'postmortem') ?? doConteudo(e, 'Postmortem');
437
+ }
438
+ /** As decisoes fechadas da thread na memoria semantica. */
439
+ function decisoesDaThreadNaMemoria(memoria, manifesto, thread) {
440
+ return memoria.buscar({
441
+ collection: 'decision',
442
+ tags: {
443
+ project: tagsDoProjeto(manifesto).project,
444
+ skill: [skillDaColecao('decision')],
445
+ situation: [situacaoDaThread(thread.id)],
446
+ },
447
+ });
448
+ }
449
+ /**
450
+ * As licoes das threads ANTERIORES do mesmo produto.
451
+ *
452
+ * A thread corrente e excluida de proposito: uma thread nao aprende consigo mesma antes
453
+ * de ter fechado, e repetir a propria licao no GOAL so gasta janela.
454
+ */
455
+ function licoesDoProduto(memoria, manifesto, threadCorrente, limite = 5) {
456
+ const todas = memoria.buscar({
457
+ collection: 'learning',
458
+ tags: {
459
+ project: tagsDoProjeto(manifesto).project,
460
+ skill: [skillDaColecao('learning')],
461
+ },
462
+ });
463
+ return todas.filter((e) => threadDaEntrada(e) !== threadCorrente).slice(0, limite);
464
+ }
465
+ /** As regras publicadas do projeto (policies e o que o humano marcou como mandatory). */
466
+ function regrasDoProjeto(memoria, manifesto) {
467
+ return memoria.buscar({
468
+ collection: 'rule',
469
+ tags: { project: tagsDoProjeto(manifesto).project },
470
+ });
471
+ }
472
+ /** A primeira linha util de um conteudo, usada como titulo do item injetado. */
473
+ function primeiraLinha(texto) {
474
+ return texto.split('\n').find((l) => l.trim() !== '')?.trim() ?? texto.slice(0, 80);
475
+ }
476
+ /**
477
+ * Monta o bloco de memoria do prompt de uma fase.
478
+ *
479
+ * O contrato que o B6 precisa cumprir, e que o teste cobra:
480
+ *
481
+ * - TODA decisao fechada (`locked`) da thread entra, em qualquer regime. Em `files` a
482
+ * fonte e o `thread.json`; em `orkmind` e a uniao do `thread.json` com a colecao
483
+ * `decision`, deduplicada por id da decisao. Uniao, e nao troca, porque uma gravacao
484
+ * que falhou no OrkMind nao pode fazer a decisao sumir do prompt.
485
+ * - No GOAL, entram as licoes das threads anteriores do produto (`learning`).
486
+ * - Entradas `mandatory` do OrkMind entram sempre que as tags casam, em qualquer fase.
487
+ * - Todo item declara a proveniencia: `path#ancora` ou `orkmind://<colecao>/<id>`.
488
+ */
489
+ function injecaoDaFase(carregado, memoria, thread, fase) {
490
+ const { manifesto } = carregado;
491
+ const itens = [];
492
+ const caminhoThread = `.orkastery/threads/${thread.id}/thread.json`;
493
+ let n = 0;
494
+ const add = (item) => {
495
+ n += 1;
496
+ itens.push({ id: `inj-${n}`, ...item });
497
+ };
498
+ // 1. Decisoes fechadas da thread. A fonte em arquivo vem primeiro porque ela e a que
499
+ // existe em qualquer regime; a memoria semantica completa o que o arquivo nao tem.
500
+ const vistas = new Set();
501
+ for (const d of thread.decisoes ?? []) {
502
+ if (!d.locked)
503
+ continue;
504
+ vistas.add(d.id);
505
+ add({
506
+ colecao: 'decision',
507
+ titulo: `Decisao ${d.id} (fechada por ${d.decididaPor})`,
508
+ conteudo: d.texto,
509
+ origem: 'thread.json',
510
+ location: `${caminhoThread}#json:decisoes.${d.id}`,
511
+ mandatory: false,
512
+ });
513
+ }
514
+ if (memoria.ativo) {
515
+ for (const e of decisoesDaThreadNaMemoria(memoria, manifesto, thread)) {
516
+ const id = decisaoDaEntrada(e);
517
+ if (id && vistas.has(id))
518
+ continue;
519
+ if (id)
520
+ vistas.add(id);
521
+ add({
522
+ colecao: 'decision',
523
+ titulo: id ? `Decisao ${id} (memoria semantica)` : 'Decisao fechada (memoria semantica)',
524
+ conteudo: e.content,
525
+ origem: 'orkmind',
526
+ location: (0, orkmind_1.enderecoDeMemoria)('decision', e.id),
527
+ mandatory: e.mandatory,
528
+ });
529
+ }
530
+ }
531
+ const decisoes = itens.length;
532
+ // 2. Licoes das threads anteriores do produto: entram no GOAL, que e onde o objetivo
533
+ // ainda pode mudar por causa delas.
534
+ let licoes = 0;
535
+ if (memoria.ativo && fase === 'GOAL') {
536
+ for (const e of licoesDoProduto(memoria, manifesto, thread.id)) {
537
+ licoes += 1;
538
+ add({
539
+ colecao: 'learning',
540
+ titulo: `Licao da thread ${threadDaEntrada(e) ?? '(anterior)'}`,
541
+ conteudo: e.content,
542
+ origem: 'orkmind',
543
+ location: (0, orkmind_1.enderecoDeMemoria)('learning', e.id),
544
+ mandatory: e.mandatory,
545
+ });
546
+ }
547
+ }
548
+ // 3. Regras `mandatory` do ecossistema: elas sempre voltam quando as tags casam, em
549
+ // qualquer fase. O `ork` nunca as cria; ele apenas as respeita.
550
+ let regras = 0;
551
+ if (memoria.ativo) {
552
+ for (const e of regrasDoProjeto(memoria, manifesto)) {
553
+ if (!e.mandatory)
554
+ continue;
555
+ regras += 1;
556
+ add({
557
+ colecao: 'rule',
558
+ titulo: `Regra mandatoria: ${primeiraLinha(e.content).slice(0, 70)}`,
559
+ conteudo: e.content,
560
+ origem: 'orkmind',
561
+ location: (0, orkmind_1.enderecoDeMemoria)('rule', e.id),
562
+ mandatory: true,
563
+ });
564
+ }
565
+ }
566
+ return {
567
+ regime: memoria.regime,
568
+ fase,
569
+ thread: thread.id,
570
+ itens,
571
+ decisoes,
572
+ licoes,
573
+ regras,
574
+ texto: textoDaInjecao(itens, memoria.regime),
575
+ };
576
+ }
577
+ /**
578
+ * O texto exato que vai para `{{memoria_injetada}}` no template da fase.
579
+ *
580
+ * Vazio quando nao ha item: a linha da variavel some do prompt e o texto fica byte a byte
581
+ * igual ao que o B4 ja despachava. Uma thread sem decisao fechada nao ganha secao vazia.
582
+ */
583
+ function textoDaInjecao(itens, regime) {
584
+ if (itens.length === 0)
585
+ return '';
586
+ const linhas = [];
587
+ linhas.push('');
588
+ linhas.push(`## Memoria injetada (regime ${regime})`);
589
+ linhas.push('Recuperacao deterministica por tag. Todo item declara de onde veio: nada entra neste ' +
590
+ 'prompt sem proveniencia. O que NAO esta aqui esta nos ponteiros do handoff, ' +
591
+ 'recuperavel por `ork recall`.');
592
+ const porColecao = {};
593
+ for (const i of itens)
594
+ (porColecao[i.colecao] ??= []).push(i);
595
+ const rotulos = {
596
+ decision: 'Decisoes fechadas desta thread (entram em TODA fase)',
597
+ learning: 'Licoes de threads anteriores deste produto',
598
+ rule: 'Regras mandatorias do ecossistema',
599
+ handoff: 'Handoffs anteriores',
600
+ roadmap: 'Roadmap',
601
+ };
602
+ for (const colecao of ['decision', 'learning', 'rule', 'handoff', 'roadmap']) {
603
+ const doGrupo = porColecao[colecao];
604
+ if (!doGrupo || doGrupo.length === 0)
605
+ continue;
606
+ linhas.push('');
607
+ linhas.push(`### ${rotulos[colecao] ?? colecao} (${doGrupo.length})`);
608
+ for (const i of doGrupo) {
609
+ linhas.push(`- ${i.titulo}${i.mandatory ? ' [MANDATORY]' : ''}`);
610
+ for (const l of i.conteudo.split('\n')) {
611
+ if (l.trim() !== '')
612
+ linhas.push(` ${l}`);
613
+ }
614
+ linhas.push(` origem: ${i.location}`);
615
+ }
616
+ }
617
+ return linhas.join('\n');
618
+ }
619
+ /** Caminho relativo do handoff da thread, o mesmo endereco que vai no ponteiro. */
620
+ function caminhoRelativoDoHandoff(raiz, threadId) {
621
+ const abs = `${(0, thread_1.dirThread)(raiz, threadId)}/handoff.json`;
622
+ return abs.startsWith(raiz) ? abs.slice(raiz.length + 1) : abs;
623
+ }
624
+ /**
625
+ * `ork memory sync`: publica na memoria semantica o que a thread ja fechou.
626
+ *
627
+ * Idempotente por construcao: o driver grava com `--dedupe`, entao rodar o sync duas
628
+ * vezes nao duplica decisao nem licao. Em regime `files` ele nao falha: ele relata que
629
+ * nada foi para a memoria semantica, e por que.
630
+ */
631
+ function sincronizarMemoria(carregado, memoria, threadId, origem) {
632
+ const { raiz, manifesto } = carregado;
633
+ const vazio = juntar([], memoria.regime);
634
+ let decisoes = vazio;
635
+ let handoff = null;
636
+ let learning = null;
637
+ let roadmap = null;
638
+ const policies = gravarPolicies(memoria, manifesto);
639
+ if (threadId) {
640
+ const thread = (0, thread_1.lerThread)(raiz, threadId);
641
+ decisoes = gravarDecisoes(memoria, manifesto, thread);
642
+ roadmap = gravarOrigemDaThread(memoria, manifesto, thread, origem ?? {
643
+ tipo: 'pedido',
644
+ referencia: '',
645
+ detalhe: `Thread aberta no modo ${(0, modos_1.tagDoModo)(thread.modo)} para "${thread.nome}".`,
646
+ });
647
+ const caminhoDoHandoff = path.join((0, thread_1.dirThread)(raiz, threadId), 'handoff.json');
648
+ if (fs.existsSync(caminhoDoHandoff)) {
649
+ const pacote = (0, util_1.lerJson)(caminhoDoHandoff);
650
+ handoff = gravarHandoff(memoria, manifesto, thread, pacote, caminhoRelativoDoHandoff(raiz, threadId));
651
+ }
652
+ // A licao so existe depois do MASTER: sem score humano nao ha o que ensinar a
653
+ // proxima thread, e inventar licao de thread aberta e self-report.
654
+ const caminhoDoPostmortem = (0, master_1.caminhoPostmortem)(raiz, threadId);
655
+ const master = (0, master_1.lerMasterLog)(raiz, threadId);
656
+ if (master && fs.existsSync(caminhoDoPostmortem)) {
657
+ const postmortem = (0, util_1.lerJson)(caminhoDoPostmortem);
658
+ learning = gravarLearning(memoria, manifesto, thread, postmortem, master);
659
+ }
660
+ }
661
+ const unitarios = [handoff, learning, roadmap].filter((g) => g !== null);
662
+ const total = decisoes.gravadas.length + policies.gravadas.length + unitarios.length;
663
+ const falhas = decisoes.falhas + policies.falhas + unitarios.filter((g) => !g.ok).length;
664
+ return {
665
+ regime: memoria.regime,
666
+ estado: memoria.estado,
667
+ thread: threadId,
668
+ decisoes,
669
+ policies,
670
+ handoff,
671
+ learning,
672
+ roadmap,
673
+ total,
674
+ falhas,
675
+ };
676
+ }
677
+ /**
678
+ * Gancho unico de publicacao usado pelos comandos do CLI.
679
+ *
680
+ * Ele existe para que `thread new`, `master` e `audit` chamem UMA linha cada, e para que
681
+ * nenhum deles precise saber se ha OrkMind ligado: em regime `files` a funcao devolve o
682
+ * resultado dizendo que nada foi gravado, e o comando segue igual.
683
+ */
684
+ function publicar(carregado, threadId, origem, memoriaAberta) {
685
+ const memoria = memoriaAberta ?? abrirMemoria(carregado);
686
+ const r = sincronizarMemoria(carregado, memoria, threadId, origem);
687
+ if (threadId && memoria.ativo) {
688
+ (0, ledger_1.registrar)((0, thread_1.dirThread)(carregado.raiz, threadId), threadId, ledger_1.TIPOS_DE_EVENTO.memoriaGravada, {
689
+ origem: origem?.tipo ?? 'sync',
690
+ regime: memoria.regime,
691
+ tenant: memoria.estado.tenant,
692
+ decisoes: r.decisoes.gravadas.length,
693
+ policies: r.policies.gravadas.length,
694
+ handoff: r.handoff?.id ?? null,
695
+ learning: r.learning?.id ?? null,
696
+ roadmap: r.roadmap?.id ?? null,
697
+ falhas: r.falhas,
698
+ });
699
+ }
700
+ return r;
701
+ }
702
+ /**
703
+ * Publica as propostas de achados de auditoria na colecao `roadmap`.
704
+ *
705
+ * O mesmo bloco obrigatorio do B5 (impacto, fix, estimativa, passo irreversivel) sai do
706
+ * board em arquivo e chega ao roadmap do produto pela memoria, quando ela esta ligada.
707
+ */
708
+ function publicarPropostas(carregado, achados, memoriaAberta) {
709
+ const memoria = memoriaAberta ?? abrirMemoria(carregado);
710
+ const resultados = achados.map((a) => gravarPropostaDeAuditoria(memoria, carregado.manifesto, a));
711
+ return juntar(resultados, memoria.regime);
712
+ }
713
+ /** Texto de `ork memory status`. */
714
+ function textoDoEstado(estado) {
715
+ const linhas = [];
716
+ linhas.push('Regime de memoria do projeto');
717
+ linhas.push('');
718
+ linhas.push(` pedido no manifesto ${estado.pedido}`);
719
+ linhas.push(` regime efetivo ${estado.efetivo}`);
720
+ linhas.push(` tenant ${estado.tenant}`);
721
+ linhas.push(` variavel da base ${estado.variavel || '(nao declarada)'}`);
722
+ linhas.push(` DSN no ambiente ${estado.dsnPresente ? 'sim (valor nunca impresso)' : 'nao'}`);
723
+ linhas.push(` cli do OrkMind ${estado.cli}`);
724
+ linhas.push('');
725
+ if (estado.motivo) {
726
+ linhas.push(` degradacao: ${estado.motivo}`);
727
+ linhas.push(` ${estado.detalhe}`);
728
+ linhas.push(` correcao: ${estado.correcao}`);
729
+ linhas.push('');
730
+ linhas.push(' O ciclo NAO para por isso: em regime files o handoff continua triado em 3 niveis,');
731
+ linhas.push(' os ponteiros continuam `path#ancora` e `ork recall` continua resolvendo.');
732
+ }
733
+ else {
734
+ linhas.push(` ${estado.detalhe}`);
735
+ linhas.push('');
736
+ linhas.push(` colecoes gravadas: ${orkmind_1.COLECOES_DO_ORK.join(', ')}`);
737
+ linhas.push(' governanca: source=agent, mandatory=false, rule/instruction nunca critical.');
738
+ }
739
+ return linhas.join('\n');
740
+ }
741
+ /** Texto de `ork memory sync`. */
742
+ function textoDoSync(r) {
743
+ const linhas = [];
744
+ linhas.push(`Sincronizacao de memoria (regime ${r.regime})${r.thread ? ` da thread ${r.thread}` : ''}`);
745
+ linhas.push('');
746
+ const linha = (nome, s) => {
747
+ linhas.push(` ${nome.padEnd(10)} ${s.gravadas.length} entrada(s): ${s.detalhe}`);
748
+ };
749
+ linha('decision', r.decisoes);
750
+ linha('rule', r.policies);
751
+ const um = (nome, g) => {
752
+ linhas.push(` ${nome.padEnd(10)} ${g ? (g.ok ? `${g.duplicada ? 'ja existente' : 'gravado'} (${g.id})` : `nao gravado: ${g.detalhe}`) : '(nada a gravar)'}`);
753
+ };
754
+ um('handoff', r.handoff);
755
+ um('learning', r.learning);
756
+ um('roadmap', r.roadmap);
757
+ linhas.push('');
758
+ linhas.push(` total ${r.total} tentativa(s), ${r.falhas} sem gravacao`);
759
+ if (r.regime === 'files') {
760
+ linhas.push('');
761
+ linhas.push(` ${r.estado.detalhe}`);
762
+ linhas.push(` correcao: ${r.estado.correcao}`);
763
+ }
764
+ return linhas.join('\n');
765
+ }
766
+ /** Prioridade valida a partir de texto livre, com o padrao do OrkMind. */
767
+ function parsePrioridade(bruto) {
768
+ const validas = ['critical', 'high', 'medium', 'low'];
769
+ if (!bruto)
770
+ return 'medium';
771
+ const limpo = bruto.trim().toLowerCase();
772
+ return validas.includes(limpo) ? limpo : null;
773
+ }