@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,487 @@
1
+ "use strict";
2
+ /**
3
+ * Cliente do OrkMind (bloco B6): a camada semantica como ADAPTADOR, nunca como dependencia.
4
+ *
5
+ * O `ork` continua sendo TypeScript deterministico e sem LLM. O OrkMind e Python, com
6
+ * Postgres + pgvector, e mora fora deste repositorio. A integracao aqui e de cliente:
7
+ * subprocess no CLI do OrkMind, entrada e saida em JSON, tolerancia a falha em todas as
8
+ * chamadas. Nada de embutir o Python, nada de reimplementar a ontologia.
9
+ *
10
+ * Tres regras que este arquivo existe para garantir:
11
+ *
12
+ * 1. DEGRADACAO HONESTA. Se o OrkMind nao responde, o regime efetivo vira `files` com
13
+ * motivo TIPADO, e o ciclo segue. Memoria semantica melhora o produto; ela nao e
14
+ * requisito dele.
15
+ * 2. ISOLAMENTO POR TENANT, SEM FALLBACK. A DSN vem do NOME de uma variavel de ambiente
16
+ * declarada no manifesto (`memory.database_url_env`). Variavel nao declarada ou vazia
17
+ * nao vira "usa a base padrao": vira degradacao. E o precedente do incidente
18
+ * memory-orkmind, em que a memoria de um produto foi parar na base de outro.
19
+ * 3. GOVERNANCA. O `ork` grava com `source: agent` e NUNCA cria entrada `mandatory` nem
20
+ * `rule`/`instruction` com prioridade `critical`. Regra critica so nasce de humano
21
+ * autenticado. Aqui isso e checado antes de qualquer chamada, nao depois.
22
+ */
23
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ var desc = Object.getOwnPropertyDescriptor(m, k);
26
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
27
+ desc = { enumerable: true, get: function() { return m[k]; } };
28
+ }
29
+ Object.defineProperty(o, k2, desc);
30
+ }) : (function(o, m, k, k2) {
31
+ if (k2 === undefined) k2 = k;
32
+ o[k2] = m[k];
33
+ }));
34
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
35
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
36
+ }) : function(o, v) {
37
+ o["default"] = v;
38
+ });
39
+ var __importStar = (this && this.__importStar) || (function () {
40
+ var ownKeys = function(o) {
41
+ ownKeys = Object.getOwnPropertyNames || function (o) {
42
+ var ar = [];
43
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
44
+ return ar;
45
+ };
46
+ return ownKeys(o);
47
+ };
48
+ return function (mod) {
49
+ if (mod && mod.__esModule) return mod;
50
+ var result = {};
51
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
52
+ __setModuleDefault(result, mod);
53
+ return result;
54
+ };
55
+ })();
56
+ Object.defineProperty(exports, "__esModule", { value: true });
57
+ exports.DriverEmMemoria = exports.DriverCliOrkMind = exports.ESQUEMA_ORKMIND = exports.DIMENSOES = exports.COLECOES_DE_GOVERNANCA = exports.COLECOES_DO_ORK = void 0;
58
+ exports.enderecoDeMemoria = enderecoDeMemoria;
59
+ exports.lerEndereco = lerEndereco;
60
+ exports.violacoesDeGovernanca = violacoesDeGovernanca;
61
+ exports.casaTags = casaTags;
62
+ exports.entradaDoJson = entradaDoJson;
63
+ exports.configDoManifesto = configDoManifesto;
64
+ exports.resolverRegime = resolverRegime;
65
+ exports.filtrarPorTags = filtrarPorTags;
66
+ exports.relativoDaRaiz = relativoDaRaiz;
67
+ const path = __importStar(require("node:path"));
68
+ const node_child_process_1 = require("node:child_process");
69
+ /** As colecoes do OrkMind que o `ork` grava (visao unificada, tabela da secao 6). */
70
+ exports.COLECOES_DO_ORK = [
71
+ 'decision',
72
+ 'handoff',
73
+ 'rule',
74
+ 'learning',
75
+ 'roadmap',
76
+ ];
77
+ /**
78
+ * Colecoes de governanca dura do OrkMind: entrada aqui vira REGRA do ecossistema.
79
+ * O agente pode gravar (o B6 grava as policies do manifesto), mas nunca como `mandatory`
80
+ * nem com prioridade `critical`: isso e privilegio de humano autenticado.
81
+ */
82
+ exports.COLECOES_DE_GOVERNANCA = ['rule', 'instruction'];
83
+ /** As 5 dimensoes semanticas usadas pelo `ork`. Busca por tag e EXATA nelas. */
84
+ exports.DIMENSOES = [
85
+ 'skill',
86
+ 'agent',
87
+ 'domain',
88
+ 'project',
89
+ 'situation',
90
+ ];
91
+ /** Prefixo dos enderecos semanticos gravados nos ponteiros do handoff. */
92
+ exports.ESQUEMA_ORKMIND = 'orkmind://';
93
+ /** Monta o endereco semantico de uma entrada (`orkmind://<colecao>/<id>`). */
94
+ function enderecoDeMemoria(colecao, id) {
95
+ return `${exports.ESQUEMA_ORKMIND}${colecao}/${id}`;
96
+ }
97
+ /** Le um endereco semantico de volta. Devolve null quando nao e um endereco do OrkMind. */
98
+ function lerEndereco(endereco) {
99
+ if (!endereco.startsWith(exports.ESQUEMA_ORKMIND))
100
+ return null;
101
+ const resto = endereco.slice(exports.ESQUEMA_ORKMIND.length);
102
+ const corte = resto.indexOf('/');
103
+ if (corte <= 0 || corte === resto.length - 1)
104
+ return null;
105
+ return { colecao: resto.slice(0, corte), id: resto.slice(corte + 1) };
106
+ }
107
+ /**
108
+ * O que o `ork` recusa gravar, ANTES de chamar o OrkMind.
109
+ *
110
+ * Devolve a lista de violacoes; lista vazia quer dizer "pode gravar". Nao e defesa contra
111
+ * o OrkMind (ele tem a dele): e o `ork` recusando pedir o que ele nao tem direito de pedir.
112
+ */
113
+ function violacoesDeGovernanca(entrada) {
114
+ const problemas = [];
115
+ if (!exports.COLECOES_DO_ORK.includes(entrada.collection)) {
116
+ problemas.push(`colecao "${entrada.collection}" fora do escopo do ork (${exports.COLECOES_DO_ORK.join(', ')})`);
117
+ }
118
+ if (exports.COLECOES_DE_GOVERNANCA.includes(entrada.collection) && entrada.priority === 'critical') {
119
+ problemas.push(`prioridade critical em "${entrada.collection}": regra critica so nasce de humano autenticado`);
120
+ }
121
+ if (entrada.content.trim() === '') {
122
+ problemas.push('conteudo vazio: entrada sem conteudo nao e memoria, e ruido');
123
+ }
124
+ for (const dimensao of Object.keys(entrada.tags)) {
125
+ if (!exports.DIMENSOES.includes(dimensao)) {
126
+ problemas.push(`dimensao de tag desconhecida: "${dimensao}" (validas: ${exports.DIMENSOES.join(', ')})`);
127
+ }
128
+ }
129
+ return problemas;
130
+ }
131
+ /**
132
+ * Casamento de tags DETERMINISTICO, o mesmo contrato do OrkMind:
133
+ *
134
+ * - casa quando ha intersecao em TODA dimensao pedida (AND entre dimensoes, OR dentro);
135
+ * - entrada `mandatory` casa com qualquer intersecao em UMA dimensao: ela sempre volta
136
+ * quando o contexto toca o assunto dela.
137
+ *
138
+ * Nao ha similaridade nem ranking aqui: dois contextos iguais devolvem o mesmo conjunto.
139
+ */
140
+ function casaTags(entrada, consulta) {
141
+ const dimensoes = Object.entries(consulta).filter(([, v]) => v.length > 0);
142
+ if (dimensoes.length === 0)
143
+ return true;
144
+ let algumaCasou = false;
145
+ let todasCasaram = true;
146
+ for (const [dimensao, valores] of dimensoes) {
147
+ const daEntrada = entrada.tags[dimensao] ?? [];
148
+ const casou = valores.some((v) => daEntrada.includes(v));
149
+ if (casou)
150
+ algumaCasou = true;
151
+ else
152
+ todasCasaram = false;
153
+ }
154
+ return todasCasaram || (entrada.mandatory && algumaCasou);
155
+ }
156
+ /** Normaliza uma entrada como o OrkMind a devolve (`orkmind export`). */
157
+ function entradaDoJson(bruto) {
158
+ if (!bruto || typeof bruto !== 'object')
159
+ return null;
160
+ const o = bruto;
161
+ if (typeof o.id !== 'string' || typeof o.content !== 'string')
162
+ return null;
163
+ const tags = {};
164
+ if (o.tags && typeof o.tags === 'object' && !Array.isArray(o.tags)) {
165
+ for (const [k, v] of Object.entries(o.tags)) {
166
+ if (Array.isArray(v))
167
+ tags[k] = v.filter((x) => typeof x === 'string');
168
+ }
169
+ }
170
+ const prioridades = ['critical', 'high', 'medium', 'low'];
171
+ const prioridade = typeof o.priority === 'string' && prioridades.includes(o.priority)
172
+ ? o.priority
173
+ : 'medium';
174
+ return {
175
+ id: o.id,
176
+ collection: typeof o.collection === 'string' ? o.collection : '',
177
+ content: o.content,
178
+ tags,
179
+ priority: prioridade,
180
+ mandatory: o.mandatory === true,
181
+ scope: typeof o.scope === 'string' ? o.scope : 'project',
182
+ source: typeof o.source === 'string' ? o.source : 'agent',
183
+ metadata: o.metadata && typeof o.metadata === 'object' && !Array.isArray(o.metadata)
184
+ ? o.metadata
185
+ : {},
186
+ criadaEm: typeof o.created_at === 'string' ? o.created_at : '',
187
+ };
188
+ }
189
+ /**
190
+ * Driver real: subprocess no CLI do OrkMind.
191
+ *
192
+ * A DSN entra pelo ambiente do processo filho (`ORKMIND_DATABASE_URL`), nunca por
193
+ * argumento de linha de comando: argumento aparece em `ps` e no ledger de quem loga
194
+ * comando. E ela sai da variavel declarada no manifesto, sem nenhum outro candidato.
195
+ */
196
+ class DriverCliOrkMind {
197
+ nome;
198
+ config;
199
+ saude = null;
200
+ constructor(config) {
201
+ this.config = config;
202
+ this.nome = `cli:${config.cli}`;
203
+ }
204
+ rodar(args) {
205
+ const r = (0, node_child_process_1.spawnSync)(this.config.cli, args, {
206
+ encoding: 'utf8',
207
+ timeout: this.config.timeoutMs,
208
+ // A base do tenant e a UNICA que entra aqui. Sem `ORKMIND_DATABASE_URL` herdada do
209
+ // ambiente do builder: isso e como memoria de um produto vaza para a base de outro.
210
+ env: { ...process.env, ORKMIND_DATABASE_URL: this.config.dsn },
211
+ });
212
+ return {
213
+ ok: r.status === 0,
214
+ stdout: (r.stdout ?? '').toString(),
215
+ stderr: (r.stderr ?? '').toString() || (r.error ? r.error.message : ''),
216
+ };
217
+ }
218
+ disponivel() {
219
+ if (this.saude)
220
+ return this.saude;
221
+ if (this.config.dsn.trim() === '') {
222
+ this.saude = { ok: false, detalhe: `${this.config.variavel} vazia no ambiente` };
223
+ return this.saude;
224
+ }
225
+ const r = this.rodar(['stats']);
226
+ this.saude = r.ok
227
+ ? { ok: true, detalhe: r.stdout.split('\n')[0]?.trim() || 'OrkMind respondeu' }
228
+ : { ok: false, detalhe: (r.stderr || r.stdout).trim().split('\n').slice(-1)[0] ?? 'sem detalhe' };
229
+ return this.saude;
230
+ }
231
+ adicionar(entrada) {
232
+ const args = [
233
+ 'add',
234
+ '--collection',
235
+ entrada.collection,
236
+ '--content',
237
+ entrada.content,
238
+ '--tags',
239
+ JSON.stringify(entrada.tags),
240
+ '--priority',
241
+ entrada.priority,
242
+ '--scope',
243
+ 'project',
244
+ // `source: agent` sempre: e assim que o OrkMind sabe que isto nao veio de humano.
245
+ '--source',
246
+ 'agent',
247
+ // `--dedupe` torna a gravacao idempotente por content_hash: reprocessar um MASTER ou
248
+ // reexportar um handoff nao duplica memoria.
249
+ '--dedupe',
250
+ '--json',
251
+ ];
252
+ const r = this.rodar(args);
253
+ if (!r.ok) {
254
+ return {
255
+ ok: false,
256
+ id: null,
257
+ duplicada: false,
258
+ collection: entrada.collection,
259
+ detalhe: (r.stderr || r.stdout).trim().split('\n').slice(-1)[0] ?? 'falha sem detalhe',
260
+ };
261
+ }
262
+ const linha = r.stdout.trim().split('\n').filter((l) => l.trim() !== '').slice(-1)[0] ?? '';
263
+ try {
264
+ const json = JSON.parse(linha);
265
+ return {
266
+ ok: true,
267
+ id: json.entry_id ?? null,
268
+ duplicada: json.status === 'duplicate',
269
+ collection: entrada.collection,
270
+ detalhe: json.status ?? 'created',
271
+ };
272
+ }
273
+ catch {
274
+ return {
275
+ ok: false,
276
+ id: null,
277
+ duplicada: false,
278
+ collection: entrada.collection,
279
+ detalhe: `saida do OrkMind nao e JSON: ${linha.slice(0, 120)}`,
280
+ };
281
+ }
282
+ }
283
+ exportar(colecao) {
284
+ const r = this.rodar(['export', '--collection', colecao, '--output', '-']);
285
+ if (!r.ok)
286
+ return [];
287
+ try {
288
+ const dados = JSON.parse(r.stdout);
289
+ if (!Array.isArray(dados))
290
+ return [];
291
+ return dados.map(entradaDoJson).filter((e) => e !== null);
292
+ }
293
+ catch {
294
+ return [];
295
+ }
296
+ }
297
+ }
298
+ exports.DriverCliOrkMind = DriverCliOrkMind;
299
+ /**
300
+ * Driver em memoria, usado pelos testes e por `--dry-run`.
301
+ *
302
+ * Ele implementa o MESMO contrato do driver real (governanca inclusa, ids estaveis), para
303
+ * que os criterios do B6 sejam provados de forma deterministica mesmo sem OrkMind no CI.
304
+ */
305
+ class DriverEmMemoria {
306
+ nome = 'memoria';
307
+ entradas = [];
308
+ contador = 0;
309
+ /** Quando false, o driver se declara indisponivel: e o teste de degradacao. */
310
+ ligado = true;
311
+ constructor(entradas = []) {
312
+ for (const e of entradas)
313
+ this.entradas.push(e);
314
+ this.contador = this.entradas.length;
315
+ }
316
+ disponivel() {
317
+ return this.ligado
318
+ ? { ok: true, detalhe: `driver em memoria com ${this.entradas.length} entrada(s)` }
319
+ : { ok: false, detalhe: 'driver em memoria desligado' };
320
+ }
321
+ adicionar(entrada) {
322
+ if (!this.ligado) {
323
+ return {
324
+ ok: false,
325
+ id: null,
326
+ duplicada: false,
327
+ collection: entrada.collection,
328
+ detalhe: 'driver em memoria desligado',
329
+ };
330
+ }
331
+ const igual = this.entradas.find((e) => e.collection === entrada.collection && e.content === entrada.content);
332
+ if (igual) {
333
+ return {
334
+ ok: true,
335
+ id: igual.id,
336
+ duplicada: true,
337
+ collection: entrada.collection,
338
+ detalhe: 'duplicate',
339
+ };
340
+ }
341
+ this.contador += 1;
342
+ const nova = {
343
+ id: `mem-${String(this.contador).padStart(4, '0')}`,
344
+ collection: entrada.collection,
345
+ content: entrada.content,
346
+ tags: entrada.tags,
347
+ priority: entrada.priority,
348
+ mandatory: false,
349
+ scope: 'project',
350
+ source: 'agent',
351
+ metadata: entrada.metadata,
352
+ criadaEm: new Date().toISOString(),
353
+ };
354
+ this.entradas.push(nova);
355
+ return {
356
+ ok: true,
357
+ id: nova.id,
358
+ duplicada: false,
359
+ collection: entrada.collection,
360
+ detalhe: 'created',
361
+ };
362
+ }
363
+ exportar(colecao) {
364
+ if (!this.ligado)
365
+ return [];
366
+ return this.entradas.filter((e) => e.collection === colecao);
367
+ }
368
+ /** So para os testes: injeta uma entrada que o `ork` nunca gravaria (humano, mandatory). */
369
+ semear(entrada) {
370
+ this.entradas.push(entrada);
371
+ return entrada;
372
+ }
373
+ /** Tudo que existe no dublê, para as asserçoes dos testes. */
374
+ tudo() {
375
+ return [...this.entradas];
376
+ }
377
+ }
378
+ exports.DriverEmMemoria = DriverEmMemoria;
379
+ /** Config de memoria efetiva a partir do manifesto, ja com o tenant resolvido. */
380
+ function configDoManifesto(manifesto) {
381
+ const variavel = manifesto.memory.database_url_env ?? '';
382
+ return {
383
+ modo: manifesto.memory.mode,
384
+ cli: manifesto.memory.cli || 'orkmind',
385
+ variavel,
386
+ dsn: variavel ? (process.env[variavel] ?? '') : '',
387
+ timeoutMs: manifesto.memory.timeout_ms || 15000,
388
+ tenant: manifesto.memory.tenant || manifesto.project.name,
389
+ };
390
+ }
391
+ /** Texto acionavel para cada motivo de degradacao. */
392
+ function correcaoDe(motivo, config) {
393
+ switch (motivo) {
394
+ case 'modo.files':
395
+ return 'para ligar a memoria semantica: memory.mode: orkmind + memory.database_url_env no manifesto';
396
+ case 'modo.desconhecido':
397
+ return 'memory.mode aceita files ou orkmind';
398
+ case 'dsn.nao-declarado':
399
+ return 'declare memory.database_url_env com o NOME da variavel de ambiente da base do tenant';
400
+ case 'dsn.env-ausente':
401
+ return `exporte ${config.variavel} com a DSN da base propria do tenant (o ork nao adivinha base)`;
402
+ case 'cli.ausente':
403
+ return `instale o OrkMind e garanta "${config.cli}" no PATH`;
404
+ case 'orkmind.indisponivel':
405
+ return 'suba o OrkMind (container do Postgres e a base do tenant) e rode ork memory status';
406
+ }
407
+ }
408
+ /**
409
+ * Resolve o regime EFETIVO de memoria.
410
+ *
411
+ * Este e o ponto unico em que "o manifesto pediu orkmind" vira "o orkmind esta valendo".
412
+ * Toda degradacao sai daqui com motivo tipado e correcao, e nenhuma delas quebra ciclo.
413
+ */
414
+ function resolverRegime(manifesto, driver) {
415
+ const config = configDoManifesto(manifesto);
416
+ const base = {
417
+ pedido: config.modo,
418
+ variavel: config.variavel,
419
+ dsnPresente: config.dsn.trim() !== '',
420
+ cli: config.cli,
421
+ tenant: config.tenant,
422
+ };
423
+ const degradar = (motivo, detalhe) => ({
424
+ estado: {
425
+ ...base,
426
+ efetivo: 'files',
427
+ motivo,
428
+ detalhe,
429
+ correcao: correcaoDe(motivo, config),
430
+ },
431
+ driver: null,
432
+ });
433
+ if (config.modo === 'files') {
434
+ return degradar('modo.files', 'manifesto opera em regime files (fallback honesto do B1)');
435
+ }
436
+ if (config.modo !== 'orkmind') {
437
+ return degradar('modo.desconhecido', `memory.mode "${config.modo}" nao e um regime conhecido`);
438
+ }
439
+ // Isolamento por tenant: sem NOME de variavel declarado nao ha base, e nao ha palpite.
440
+ if (config.variavel === '') {
441
+ return degradar('dsn.nao-declarado', 'memory.database_url_env vazio: sem base declarada o ork nao conecta em base nenhuma');
442
+ }
443
+ if (config.dsn.trim() === '' && !driver) {
444
+ return degradar('dsn.env-ausente', `${config.variavel} nao esta no ambiente: o ork nao cai para outra DSN (precedente memory-orkmind)`);
445
+ }
446
+ const efetivoDriver = driver ?? new DriverCliOrkMind(config);
447
+ const saude = efetivoDriver.disponivel();
448
+ if (!saude.ok) {
449
+ const motivo = /não encontrad|not found|ENOENT|spawn/i.test(saude.detalhe)
450
+ ? 'cli.ausente'
451
+ : 'orkmind.indisponivel';
452
+ return degradar(motivo, `OrkMind nao respondeu: ${saude.detalhe}`);
453
+ }
454
+ return {
455
+ estado: {
456
+ ...base,
457
+ efetivo: 'orkmind',
458
+ motivo: null,
459
+ detalhe: `${efetivoDriver.nome} ativo na base declarada por ${config.variavel}: ${saude.detalhe}`,
460
+ correcao: '',
461
+ },
462
+ driver: efetivoDriver,
463
+ };
464
+ }
465
+ /** Consulta deterministica por tag sobre uma colecao ja exportada. */
466
+ function filtrarPorTags(entradas, consulta) {
467
+ const casadas = entradas.filter((e) => casaTags(e, consulta.tags));
468
+ // Ordem estavel e independente do que o Postgres devolveu: mandatory primeiro, depois
469
+ // prioridade, depois data e id. Duas execucoes iguais montam o MESMO prompt.
470
+ const peso = { critical: 0, high: 1, medium: 2, low: 3 };
471
+ casadas.sort((a, b) => {
472
+ if (a.mandatory !== b.mandatory)
473
+ return a.mandatory ? -1 : 1;
474
+ const pa = peso[a.priority] ?? 9;
475
+ const pb = peso[b.priority] ?? 9;
476
+ if (pa !== pb)
477
+ return pa - pb;
478
+ if (a.criadaEm !== b.criadaEm)
479
+ return a.criadaEm.localeCompare(b.criadaEm);
480
+ return a.id.localeCompare(b.id);
481
+ });
482
+ return consulta.limite ? casadas.slice(0, consulta.limite) : casadas;
483
+ }
484
+ /** Caminho relativo a raiz, no formato usado pelos ponteiros do handoff. */
485
+ function relativoDaRaiz(raiz, caminho) {
486
+ return path.relative(raiz, caminho).split(path.sep).join('/');
487
+ }