auroq-os 2.5.1 → 2.5.4

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.
@@ -1,7 +1,7 @@
1
1
  project:
2
2
  name: "Auroq OS"
3
3
  type: greenfield
4
- version: 2.5.1
4
+ version: 2.5.4
5
5
  installedAt: '2026-03-16'
6
6
 
7
7
  ide:
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Coletor AHI. Camada ao lado do Synapse: nunca substitui synapse-engine.cjs.
4
+ * Falha = exit 0. Sem stdout. Sem MCP Arcane neste negocio = no-op.
5
+ */
6
+ process.on('uncaughtException', () => process.exit(0));
7
+ process.on('unhandledRejection', () => process.exit(0));
8
+ const kill = setTimeout(() => process.exit(0), 2500);
9
+ function done() {
10
+ try { clearTimeout(kill); } catch { /* ignore */ }
11
+ process.exit(0);
12
+ }
13
+
14
+ let input = '';
15
+ process.stdin.setEncoding('utf8');
16
+ process.stdin.on('data', (chunk) => {
17
+ input += chunk;
18
+ if (input.length > 512 * 1024) input = input.slice(0, 512 * 1024);
19
+ });
20
+ process.stdin.on('end', () => {
21
+ try { capture(); } catch { /* never break Claude */ }
22
+ done();
23
+ });
24
+
25
+ function redact(text) {
26
+ return String(text || '')
27
+ .replace(/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g, '[REDACTED]')
28
+ .replace(/\bBearer\s+[A-Za-z0-9\-._~+/]+=*/gi, '[REDACTED]')
29
+ .replace(/\bsk-(?:live|test|proj)?[-_]?[A-Za-z0-9]{16,}\b/g, '[REDACTED]')
30
+ .replace(/\bghp_[A-Za-z0-9]{20,}\b/g, '[REDACTED]')
31
+ .slice(0, 8000);
32
+ }
33
+
34
+ function capture() {
35
+ const fs = require('node:fs');
36
+ const path = require('node:path');
37
+ const cwd = process.cwd();
38
+ let mcp = false;
39
+ try {
40
+ const json = JSON.parse(fs.readFileSync(path.join(cwd, '.mcp.json'), 'utf8'));
41
+ mcp = Boolean(json && json.mcpServers && json.mcpServers.arcane);
42
+ } catch { /* sem MCP */ }
43
+ if (!mcp) return;
44
+ let event = {};
45
+ try { event = JSON.parse(input || '{}'); } catch { return; }
46
+ const name = event.hook_event_name || event.hookEventName;
47
+ if (!name) return;
48
+ const dir = path.join(cwd, '.auroq', 'ahi');
49
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
50
+ const row = {
51
+ at: new Date().toISOString(),
52
+ hook: String(name),
53
+ session_id: event.session_id || event.sessionId || null,
54
+ tool: event.tool_name || null,
55
+ text: redact(event.prompt || event.last_assistant_message || event.last_message || event.content || ''),
56
+ };
57
+ fs.appendFileSync(path.join(dir, 'events.jsonl'), `${JSON.stringify(row)}\n`, { mode: 0o600 });
58
+ }
@@ -3,6 +3,9 @@
3
3
  "allow": [],
4
4
  "deny": []
5
5
  },
6
+ "env": {
7
+ "MCP_TIMEOUT": "120000"
8
+ },
6
9
  "hooks": {
7
10
  "UserPromptSubmit": [
8
11
  {
@@ -14,6 +17,16 @@
14
17
  "timeout": 10000
15
18
  }
16
19
  ]
20
+ },
21
+ {
22
+ "matcher": "",
23
+ "hooks": [
24
+ {
25
+ "type": "command",
26
+ "command": "node .claude/hooks/ahi-capture.cjs",
27
+ "timeout": 3000
28
+ }
29
+ ]
17
30
  }
18
31
  ],
19
32
  "PreCompact": [
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
+ ## 2.5.4 — 2026-09-14
2
+
3
+ - Historico do trabalho Arcane: se o negocio tem MCP `arcane`, o hook `ahi-capture.cjs` registra a sessao. Sem clique, sem pause. Falha do coletor nao quebra o Claude (exit 0, sem stdout). Nao substitui o Synapse.
4
+ - `auroq-os historico` mostra ligado/desligado. Manutencao acrescenta o hook que faltar em `.claude/settings.local.json`.
5
+
6
+ ## 2.5.2 — 2026-09-11
7
+
8
+ - **"UserPromptSubmit hook error ... hookEventName" a cada mensagem (EMB-040).** Quem instalou ate a 2.4.9 ficou com o Synapse antigo para sempre: a pasta era copiada sem sobrescrever e a correcao da 2.4.10 nunca chegava — o Claude Code recusava o pacote de regras em toda mensagem, enquanto a manutencao carimbava a versao nova. A manutencao agora troca todo arquivo do Synapse que seja uma versao oficial antiga (hashes das 90 versoes publicadas em `lib/synapse-hashes.json`) e guarda em `.auroq-core/preservado/synapse/` qualquer arquivo diferente de todas elas antes da troca. Integridade e `doctor` apontam o Synapse antigo em vez de dizer que esta tudo em dia.
9
+ - **MCP arcane com CONNECT_TIMEOUT no Claude Code (EMB-041).** O Claude Code desiste do conteudo ao vivo aos 30s; o npx a frio no Windows leva 44-77s. Instalar, manutencao, register-mcp e clone gravam `env.MCP_TIMEOUT = 120000` em `.claude/settings.local.json` (o Codex ja tinha 120s); o `doctor` cobra esse tempo.
10
+ - `.claude/settings.local.json` ilegivel deixa de virar `{}` (o aluno perdia os hooks): o original fica guardado ao lado e o arquivo volta a partir do modelo.
11
+ - Instaladores fixam 2.5.2. Correcao feita a partir da 2.5.1 publicada; o candidato de telemetria segue separado.
12
+
1
13
  ## 2.5.1 — 2026-09-04
2
14
 
15
+ ## 2.5.3 — candidato local, nao publicado
16
+
17
+ - Bootstrap dependency-light e outbox privada compartilhada com o MCP; retries autenticados com IDs estaveis e eventos sem dono exclusivamente locais.
18
+ - Spool de etapas dos instaladores antes de Node, importado depois sem inferir identidade.
19
+ - Pin MCP 0.6.0 e orientacoes de doctor/manutencao; studentFiles e consentimento legado preservados.
20
+ - Instaladores oficiais fixam o motor publicado 2.5.2 (hotfix EMB-040/EMB-041); este candidato ainda nao foi publicado. Transporte/doctor novos dependem de publicacao/adocao.
21
+ - Locks e publicacao atomicos, claim do spool e TTL pre-Node; runtime Windows faz fail-closed por ACL/no-follow indisponiveis.
22
+ - Validacao Linux; Windows/macOS pendentes de execucao nativa.
23
+
3
24
  - Motor registra arcane-mcp 0.5.0: diagnostico de tentativas, erros, ambiente e reenvio apos falha de rede. Conteudo de conversas e arquivos nao e coletado. Instaladores fixam esta versao validada.
4
25
 
5
26
  ## 2.5.0 — 2026-09-04
package/README.md CHANGED
@@ -164,3 +164,41 @@ agents/ → Seu exercito (companion, workers, minds, squads)
164
164
  ---
165
165
 
166
166
  *Auroq OS — by Euriler Jube / Arka*
167
+
168
+
169
+ ## Observabilidade do motor (candidato 2.5.2)
170
+
171
+ Apos publicacao, `npx -y auroq-os@2.5.3 doctor` mostrara a fila local segura e os comandos pinados de
172
+ manutencao/diagnostico. `npx -y auroq-os@2.5.3 manutencao` preserva o caminho existente
173
+ de consentimento legado, studentFiles, customizacoes e dados do negocio. Nao ha
174
+ migracao forcada. O registro MCP passa a apontar para `arcane-mcp@0.6.0`.
175
+ Estes candidatos precisam ser publicados pelo coordenador apos review; consultar
176
+ novamente colisao no registry antes de publicar. A promocao esta bloqueada por
177
+ autenticacao npm (401 nas duas vias verificadas pelo coordenador). Os instaladores
178
+ oficiais continuam em `auroq-os@2.5.2`, publicado; o spool nativo local pode ser servido
179
+ com esse pin. Transporte/doctor novos dependem de publicacao e adocao dos candidatos.
180
+
181
+ O CLI inicia captura antes das dependencias pesadas e registra resultado ao sair.
182
+ Eventos com identidade ja existente podem seguir pela rota autenticada na proxima
183
+ inicializacao. Shell e PowerShell gravam somente etapas/codigos em spool privado
184
+ `~/.arcane/installer-outbox`, sem ler credenciais nem conteudo pessoal; 200 slots por
185
+ formato limitam o disco antes de haver Node. Saturado, deixa de capturar. Quando Node
186
+ executa nosso CLI, ele importa/expurga o spool com TTL de sete dias, preserva proveniencia
187
+ local e jamais atribui esses eventos ao login posterior. Cada execucao do instalador
188
+ expurga sete dias e slots interrompidos sob lock; nao precisa esperar Node. Nao existe daemon. O runtime Windows falha fechado (`OUTBOX_UNAVAILABLE`), sem
189
+ ler/enviar/importar fila enquanto nao houver garantia nativa de ACL/no-follow.
190
+ O PowerShell verifica DACL nativa e conserva seus eventos locais. O spool nao substitui o install.log legado,
191
+ que nao e lido nem enviado pela telemetria nova.
192
+
193
+ Captura cobre inicio e etapas Git/motor/acesso/pasta/montagem do instalador; no CLI,
194
+ instalacao/manutencao/doctor/login/register-mcp. Bootstrap conversacional e o trabalho
195
+ final do squad nao sao inferidos como sucesso por sair do CLI ou preparar arquivos.
196
+ Antes de o script chegar ou em computador que nunca executa nosso codigo, nao ha sinal.
197
+ A rota events continua autenticada, limitada e idempotente. A plataforma deve receber
198
+ a whitelist antes de distribuir/promover os clientes; os instaladores sincronizados
199
+ mantem o pin 2.5.1 publicado ate a promocao npm ser possivel. Nao precisa migration adicional.
200
+
201
+ O canal de adocao e o instalador oficial, o README/changelog, o doctor e o aviso de
202
+ comando aposentado quando a versao nova e executada. Isso nao atualiza silenciosamente
203
+ quem continua com o pacote antigo. Testado nesta VPS em Linux; sintaxe Bash e verificacoes
204
+ estaticas ASCII/compatibilidade PS passaram, mas Windows PS5.1 e macOS nao foram executados.
package/bin/auroq-os.js CHANGED
@@ -12,6 +12,30 @@
12
12
 
13
13
  'use strict';
14
14
 
15
+ async function observedMain() {
16
+ const telemetry = {...await import('../lib/telemetry.mjs')};
17
+ const credentials = require('../lib/credentials');
18
+ const owner = credentials.load()?.user?.id || null;
19
+ const stage = ({instalar:'install',manutencao:'update',doctor:'doctor',diagnostico:'doctor',login:'login','register-mcp':'bootstrap'})[process.argv[2]] || 'runtime';
20
+ const record = (outcome, error_code) => telemetry.enqueue(owner,{endpoint:'lifecycle',stage,outcome,error_code});
21
+ telemetry.setHost('auroq-os');telemetry.importInstallerSpool();record('started');
22
+ let importing = true;
23
+ process.once('exit',code=>record(code===0?'ok':'error',code===0?null:importing?'BOOTSTRAP_IMPORT':stage==='update'?'UPDATE_FAILED':'INSTALL_FAILED'));
24
+ process.once('uncaughtException',()=>{console.error('[auroq-os] RUNTIME_FAILURE');process.exit(1);});
25
+ process.once('unhandledRejection',()=>{console.error('[auroq-os] RUNTIME_FAILURE');process.exit(1);});
26
+ const cred = credentials.load();await telemetry.flush(cred?.user?.id,cred?.access_token);
27
+ try {
28
+ telemetry.runtimeReady=()=>{importing=false;};
29
+ runRuntime(telemetry);
30
+ } catch {
31
+ console.error('[auroq-os] '+(importing?'BOOTSTRAP_IMPORT':'RUNTIME_FAILURE')+'. Rode: npx -y auroq-os@2.5.3 doctor');
32
+ process.exitCode=1;
33
+ }
34
+ }
35
+ if(process.env.AUROQ_EXPORTA_INTERNOS==='1')runRuntime({runtimeReady(){}});
36
+ else observedMain().catch(()=>{console.error('[auroq-os] BOOTSTRAP_IMPORT');process.exitCode=1;});
37
+
38
+ function runRuntime(telemetry) {
15
39
  const path = require('path');
16
40
  const fs = require('fs-extra');
17
41
  const { execSync, execFileSync } = require('child_process');
@@ -164,6 +188,7 @@ const HEALTH_CHECKS = [
164
188
  ['.claude/output-styles/direto.md', 'Output style Direto'],
165
189
  ['.claude/rules/operacao-autonoma.md', 'Rule de operacao autonoma'],
166
190
  ['.claude/hooks/synapse-engine.cjs', 'Synapse Hook'],
191
+ ['.claude/hooks/ahi-capture.cjs', 'AHI Hook'],
167
192
  ['.claude/hooks/auroq-sync.cjs', 'Sync Hook'],
168
193
  ['.claude/rules/puxar-e-entregar.md', 'Rule puxar-e-entregar'],
169
194
  ['.claude/rules/mcp-usage.md', 'Rule MCP (carve-out Arcane)'],
@@ -246,6 +271,11 @@ async function runHealthChecks(targetDir) {
246
271
  missing.push(`${label} (${file})`);
247
272
  }
248
273
  }
274
+ if (await synapseDesatualizado(targetDir)) {
275
+ // Aviso, nao trava: um clone de negocio antigo traz o arquivo velho pelo git e so a
276
+ // manutencao nesta maquina (ou na outra, depois entregue) troca.
277
+ warn('Synapse antigo nesta pasta (erro "hookEventName" a cada mensagem). Peca ao Ops: manutencao do motor.');
278
+ }
249
279
  if (passed === HEALTH_CHECKS.length) {
250
280
  success(`${passed}/${HEALTH_CHECKS.length} verificacoes OK`);
251
281
  } else {
@@ -280,6 +310,14 @@ async function runDoctor(targetDir) {
280
310
  error(`Arquivos do sistema: faltam ${faltando}`);
281
311
  problemas.push('Peca manutencao do motor ao Ops dentro do Claude Code pra repor o que falta.');
282
312
  }
313
+ if (!(await synapseRegistrado(targetDir))) {
314
+ error('O hook do Synapse nao esta registrado: as regras do sistema nao chegam ao modelo');
315
+ problemas.push('Rode: npx auroq-os@latest register-mcp na pasta (repoe o registro sem tirar nada seu). Depois feche e abra o Claude Code.');
316
+ }
317
+ if (await synapseDesatualizado(targetDir)) {
318
+ error('Synapse antigo: o Claude Code recusa as regras a cada mensagem ("hookEventName")');
319
+ problemas.push('Peca manutencao do motor ao Ops dentro do Claude Code (ou rode npx auroq-os@latest manutencao na pasta) pra trocar o Synapse antigo.');
320
+ }
283
321
 
284
322
  // 2. O motor — e, principalmente, se quem abre pelo icone vai enxergar ele.
285
323
  // Esta e a falha silenciosa do §5.4: no terminal funciona, no app nao, e
@@ -327,6 +365,15 @@ async function runDoctor(targetDir) {
327
365
  }
328
366
  if (pin) {
329
367
  success(`MCP arcane registrado no Claude Code (${pin})`);
368
+ let espera = NaN;
369
+ try {
370
+ const local = JSON.parse((await fs.readFile(path.join(targetDir, '.claude', 'settings.local.json'), 'utf8')).replace(/^\uFEFF/, ''));
371
+ espera = Number(local?.env?.MCP_TIMEOUT);
372
+ } catch { /* sem arquivo ou ilegivel = sem tempo de espera */ }
373
+ if (!(espera >= CLAUDE_MCP_TIMEOUT_MS)) {
374
+ error('O Claude Code esta com a espera padrao de 30s pelo conteudo ao vivo — no Windows a abertura pode cair (CONNECT_TIMEOUT)');
375
+ problemas.push('Rode: npx auroq-os@latest register-mcp (da ao Claude Code 2 minutos de espera). Depois feche e abra o Claude Code.');
376
+ }
330
377
  } else {
331
378
  error('MCP arcane nao registrado em .mcp.json');
332
379
  problemas.push('Rode: npx auroq-os register-mcp (reescreve o registro). Depois feche e abra o Claude Code.');
@@ -459,8 +506,12 @@ async function runDoctor(targetDir) {
459
506
  // (rede domestica + antivirus) a aluna viu timeout em todas as que fez. Por isso:
460
507
  // (1) o registro do Codex pede mais tempo, (2) instalacao/atualizacao ja baixam o
461
508
  // pacote, (3) o doctor mede o arranque de verdade em vez de so olhar o registro.
462
- const ARCANE_MCP_PIN = 'arcane-mcp@0.5.0';
509
+ const ARCANE_MCP_PIN = 'arcane-mcp@0.6.0';
463
510
  const CODEX_MCP_STARTUP_SEC = 120;
511
+ // O Claude Code tambem desistia aos 30s (MCP_TIMEOUT) e o aluno via CONNECT_TIMEOUT com o
512
+ // servidor no ar e nenhum pedido chegando nele (11/09, EMB-041). Ele le o tempo do `env` das
513
+ // configuracoes da pasta: provado no 2.1.267, servidor de 40s cai aos 30s sem isto e conecta com isto.
514
+ const CLAUDE_MCP_TIMEOUT_MS = 120000;
464
515
  const CODEX_ENV_MINIMO = ['PATH', 'PATHEXT', 'USERNAME', 'USERDOMAIN', 'USERPROFILE', 'TEMP', 'TMP',
465
516
  'HOME', 'LOGNAME', 'SHELL', 'TERM', 'USER'];
466
517
 
@@ -508,7 +559,7 @@ function blocoCodexArcane(pin, opcoes) {
508
559
  }
509
560
 
510
561
  // Baixa o pacote do conteudo ao vivo com o MESMO nome que o registro usa (o cache do
511
- // npx e por nome+versao: `arcane-mcp` e `arcane-mcp@0.5.0` sao caches diferentes).
562
+ // npx e por nome+versao: `arcane-mcp` e `arcane-mcp@0.6.0` sao caches diferentes).
512
563
  // `npx` no Windows e um .cmd: sem shell o Node recusa (EINVAL) e com `shell: true` o Node 24
513
564
  // imprime um DeprecationWarning na tela do aluno (DEP0190). Chamar o cmd.exe direto, sem shell.
514
565
  function comandoNpx(args) {
@@ -768,6 +819,16 @@ function rodarNoCodex(pasta) {
768
819
  }
769
820
 
770
821
  async function writeArcaneMcpConfig(targetDir) {
822
+ // Todo caminho que registra o MCP (instalar, manutencao, register-mcp, clone) passa aqui,
823
+ // entao e aqui que o Claude Code ganha o tempo de espera. Falhar em gravar isso (arquivo
824
+ // somente leitura, dono root) nao pode derrubar o registro nem o resto da manutencao.
825
+ let esperaOk = false;
826
+ try {
827
+ await patchEnableAllProjectMcpServers(targetDir);
828
+ esperaOk = true;
829
+ } catch (e) {
830
+ warn(`Nao consegui gravar .claude/settings.local.json (${e.code || e.message}): o Claude Code segue esperando so 30s pelo conteudo ao vivo. Confira a permissao desse arquivo e rode register-mcp de novo.`);
831
+ }
771
832
  const pin = ARCANE_MCP_PIN;
772
833
  const mcpPath = path.join(targetDir, '.mcp.json');
773
834
  let json = { mcpServers: {} };
@@ -791,22 +852,181 @@ async function writeArcaneMcpConfig(targetDir) {
791
852
  const next = (toml.trimEnd() ? `${toml.trimEnd()}\n\n` : '') + block;
792
853
  await fs.writeFile(tomlPath, next);
793
854
  }
855
+ return { esperaOk };
794
856
  }
795
857
 
858
+ async function modeloSettingsLocal() {
859
+ try {
860
+ return await fs.readJSON(path.resolve(__dirname, '..', '.claude', 'settings.local.json'));
861
+ } catch {
862
+ return {};
863
+ }
864
+ }
865
+
866
+ // Copia dos bytes do aluno antes de reescrever o arquivo dele. Vai pra `.auroq/` (fora do
867
+ // git): um settings.local.json pode ter token no `env` e nao pode subir pro GitHub.
868
+ async function guardarSettingsLocal(targetDir, bruto, motivo) {
869
+ const quando = new Date().toISOString().slice(0, 19).replace(/[:T]/g, '-');
870
+ const guarda = path.join(targetDir, '.auroq', 'preservado', `settings.local.json.${motivo}-${quando}`);
871
+ await fs.ensureDir(path.dirname(guarda));
872
+ await fs.writeFile(guarda, bruto);
873
+ const giPath = path.join(targetDir, '.gitignore');
874
+ const gi = (await fs.pathExists(giPath)) ? await fs.readFile(giPath, 'utf8') : '';
875
+ if (!gi.split('\n').map(l => l.trim()).includes('.auroq/')) {
876
+ await fs.writeFile(giPath, `${gi.replace(/\s*$/, gi.trim() ? '\n' : '')}.auroq/\n`);
877
+ }
878
+ return path.relative(targetDir, guarda).split(path.sep).join('/');
879
+ }
880
+
881
+ const ehObjeto = v => Boolean(v) && typeof v === 'object' && !Array.isArray(v);
882
+
883
+ // `.claude/settings.local.json` e do aluno: so acrescenta o que falta, nunca tira nada.
884
+ // E o unico lugar que registra os hooks do Synapse e do PreCompact; se o arquivo nasce
885
+ // sem eles (clone numa maquina nova, ou criado pelo proprio Claude Code), as regras
886
+ // deixam de chegar ao modelo em silencio. Por isso os hooks do modelo que faltarem entram.
796
887
  async function patchEnableAllProjectMcpServers(targetDir) {
797
888
  const settingsPath = path.join(targetDir, '.claude', 'settings.local.json');
798
- let json = {};
889
+ const modelo = await modeloSettingsLocal();
890
+ let json;
891
+ let mudou = false;
799
892
  if (await fs.pathExists(settingsPath)) {
893
+ const bruto = await fs.readFile(settingsPath);
800
894
  try {
801
- json = await fs.readJSON(settingsPath);
895
+ json = JSON.parse(bruto.toString('utf8').replace(/^\uFEFF/, ''));
896
+ if (!ehObjeto(json)) throw new Error('nao e um objeto');
802
897
  } catch {
803
- json = {};
898
+ // Antes virava {} e o aluno perdia os hooks e o que mais tivesse ali.
899
+ const guarda = await guardarSettingsLocal(targetDir, bruto, 'ilegivel');
900
+ warn(`.claude/settings.local.json estava ilegivel: guardei o original em ${guarda} e refiz a partir do modelo`);
901
+ json = JSON.parse(JSON.stringify(modelo));
902
+ mudou = true;
903
+ }
904
+ if (json.env !== undefined && !ehObjeto(json.env)) {
905
+ const guarda = await guardarSettingsLocal(targetDir, bruto, 'env-invalido');
906
+ warn(`O "env" de .claude/settings.local.json nao era uma lista de variaveis: guardei o original em ${guarda}`);
907
+ delete json.env;
908
+ mudou = true;
804
909
  }
910
+ } else {
911
+ json = JSON.parse(JSON.stringify(modelo));
912
+ mudou = true;
913
+ }
914
+ if (ehObjeto(modelo.hooks)) {
915
+ // Hook ja registrado (aqui ou no settings.json, com qualquer forma de caminho) nao
916
+ // entra de novo: rodar o Synapse duas vezes duplicaria as regras.
917
+ let doProjeto = {};
918
+ try {
919
+ doProjeto = JSON.parse((await fs.readFile(path.join(targetDir, '.claude', 'settings.json'), 'utf8')).replace(/^/, ''));
920
+ } catch { /* sem settings.json legivel */ }
921
+ const scripts = (grupos) => (Array.isArray(grupos) ? grupos : [])
922
+ .flatMap(g => (Array.isArray(g?.hooks) ? g.hooks : []))
923
+ .map(h => (String(h?.command || '').match(/[\w.-]+\.c?js\b/) || [''])[0]);
924
+ if (!ehObjeto(json.hooks)) json.hooks = {};
925
+ for (const [evento, grupos] of Object.entries(modelo.hooks)) {
926
+ const atuais = Array.isArray(json.hooks[evento]) ? json.hooks[evento] : [];
927
+ const presentes = new Set([...scripts(atuais), ...scripts(doProjeto?.hooks?.[evento])].filter(Boolean));
928
+ const faltando = (grupos || []).filter(g => scripts([g]).some(s => s && !presentes.has(s)));
929
+ if (faltando.length) {
930
+ json.hooks[evento] = [...atuais, ...faltando];
931
+ mudou = true;
932
+ }
933
+ }
934
+ }
935
+ if (json.enableAllProjectMcpServers !== true) {
936
+ json.enableAllProjectMcpServers = true;
937
+ mudou = true;
805
938
  }
806
- if (json.enableAllProjectMcpServers === true) return;
807
- json.enableAllProjectMcpServers = true;
939
+ const env = ehObjeto(json.env) ? json.env : {};
940
+ const atual = Number(env.MCP_TIMEOUT);
941
+ if (!(Number.isFinite(atual) && atual >= CLAUDE_MCP_TIMEOUT_MS)) {
942
+ json.env = { ...env, MCP_TIMEOUT: String(CLAUDE_MCP_TIMEOUT_MS) };
943
+ mudou = true;
944
+ }
945
+ if (!mudou) return false;
808
946
  await fs.ensureDir(path.dirname(settingsPath));
809
947
  await fs.writeJSON(settingsPath, json, { spaces: 2 });
948
+ return true;
949
+ }
950
+
951
+ // ─── Synapse: o motor que reforca as regras do sistema a cada mensagem ───
952
+ // Ate a 2.5.1 esta pasta era copiada com overwrite:false. Quem instalou ate a 2.4.9 ficou
953
+ // para sempre com o hook-runtime.js sem `hookEventName`: o Claude Code recusava o pacote
954
+ // inteiro ("UserPromptSubmit hook error" a cada mensagem) e nenhuma regra chegava ao modelo,
955
+ // enquanto a manutencao carimbava a versao nova (EMB-040). Agora: arquivo identico a uma
956
+ // versao que publicamos e trocado; arquivo que ninguem publicou (mexido a mao) e guardado
957
+ // em .auroq-core/preservado/synapse/ antes da troca.
958
+ function hashNormalizado(buf) {
959
+ return require('crypto').createHash('sha256').update(buf.toString('utf8').replace(/\r\n/g, '\n')).digest('hex');
960
+ }
961
+
962
+ async function listarArquivos(dir, base = dir) {
963
+ const saida = [];
964
+ for (const nome of await fs.readdir(dir)) {
965
+ const abs = path.join(dir, nome);
966
+ const st = await fs.lstat(abs);
967
+ if (st.isDirectory()) saida.push(...(await listarArquivos(abs, base)));
968
+ else if (st.isFile()) saida.push(path.relative(base, abs).split(path.sep).join('/'));
969
+ }
970
+ return saida;
971
+ }
972
+
973
+ async function atualizarSynapse(sourceDir, targetDir) {
974
+ const origem = path.join(sourceDir, '.auroq-core', 'core', 'synapse');
975
+ const destino = path.join(targetDir, '.auroq-core', 'core', 'synapse');
976
+ if (!(await fs.pathExists(origem))) return null;
977
+ const oficiais = require('../lib/synapse-hashes.json');
978
+ const r = { novos: 0, atualizados: 0, iguais: 0, preservados: [], falhas: [] };
979
+ for (const rel of await listarArquivos(origem)) {
980
+ const src = path.join(origem, ...rel.split('/'));
981
+ const dst = path.join(destino, ...rel.split('/'));
982
+ try {
983
+ if (!(await fs.pathExists(dst))) {
984
+ await fs.copy(src, dst);
985
+ r.novos++;
986
+ continue;
987
+ }
988
+ const atual = await fs.readFile(dst);
989
+ const hAtual = hashNormalizado(atual);
990
+ if (hAtual === hashNormalizado(await fs.readFile(src))) {
991
+ r.iguais++;
992
+ continue;
993
+ }
994
+ if (!(oficiais[rel] || []).includes(hAtual)) {
995
+ const guarda = path.join(targetDir, '.auroq-core', 'preservado', 'synapse', `${rel}.${hAtual.slice(0, 12)}`);
996
+ if (!(await fs.pathExists(guarda))) {
997
+ await fs.ensureDir(path.dirname(guarda));
998
+ await fs.writeFile(guarda, atual);
999
+ }
1000
+ r.preservados.push(rel);
1001
+ }
1002
+ await fs.copy(src, dst, { overwrite: true });
1003
+ r.atualizados++;
1004
+ } catch (e) {
1005
+ r.falhas.push(`${rel} (${e.code || e.message})`);
1006
+ }
1007
+ }
1008
+ return r;
1009
+ }
1010
+
1011
+ async function synapseRegistrado(targetDir) {
1012
+ for (const nome of ['settings.local.json', 'settings.json']) {
1013
+ try {
1014
+ const json = JSON.parse((await fs.readFile(path.join(targetDir, '.claude', nome), 'utf8')).replace(/^/, ''));
1015
+ const grupos = Array.isArray(json?.hooks?.UserPromptSubmit) ? json.hooks.UserPromptSubmit : [];
1016
+ if (grupos.some(g => (g?.hooks || []).some(h => String(h?.command || '').includes('synapse-engine.cjs')))) return true;
1017
+ } catch { /* arquivo ausente ou ilegivel */ }
1018
+ }
1019
+ return false;
1020
+ }
1021
+
1022
+ // O hook-runtime sem `hookEventName` e a assinatura do Synapse antigo (EMB-040).
1023
+ async function synapseDesatualizado(targetDir) {
1024
+ const arq = path.join(targetDir, '.auroq-core', 'core', 'synapse', 'runtime', 'hook-runtime.js');
1025
+ try {
1026
+ return !(await fs.readFile(arq, 'utf8')).includes('hookEventName');
1027
+ } catch {
1028
+ return false;
1029
+ }
810
1030
  }
811
1031
 
812
1032
  // ─── INSTALACAO ──────────────────────────────────────────
@@ -992,6 +1212,7 @@ async function init({ pasta = null, maintenance = false } = {}) {
992
1212
 
993
1213
  // Hooks
994
1214
  '.claude/hooks/synapse-engine.cjs',
1215
+ '.claude/hooks/ahi-capture.cjs',
995
1216
  '.claude/hooks/precompact-session-digest.cjs',
996
1217
  '.claude/hooks/auroq-sync.cjs',
997
1218
 
@@ -1196,25 +1417,26 @@ async function init({ pasta = null, maintenance = false } = {}) {
1196
1417
  await fs.copy(seedRoot, targetDir, { overwrite: false });
1197
1418
  success('Semente do aluno instalada (memoria, docs, cockpit — nunca sobrescreve)');
1198
1419
  }
1199
- await patchEnableAllProjectMcpServers(targetDir);
1200
- await writeArcaneMcpConfig(targetDir);
1201
- step('Preparando o conteudo ao vivo (baixa uma vez; no Windows prepara tambem pro Codex; pode levar 1-3 min)...');
1202
- if (aquecerArcaneMcp()) success('Conteudo ao vivo pronto pra abrir rapido');
1203
- else warn('Nao consegui baixar o conteudo ao vivo agora — a primeira abertura vai demorar mais');
1204
-
1205
- // ─── Fase 4: Copiar Synapse Engine
1420
+ // ─── Fase 4: Synapse Engine (troca versao oficial antiga; guarda o que foi mexido a mao).
1421
+ // Antes do registro do MCP: se algo ali falhar, a correcao do hook (EMB-040) ja entrou.
1206
1422
  step('Instalando Synapse Engine...');
1207
1423
 
1208
- const synapseSrc = path.join(sourceDir, '.auroq-core', 'core', 'synapse');
1209
- const synapseDst = path.join(targetDir, '.auroq-core', 'core', 'synapse');
1210
- if (await fs.pathExists(synapseSrc)) {
1211
- await fs.copy(synapseSrc, synapseDst, { overwrite: false });
1212
- const synapseFiles = await fs.readdir(synapseSrc, { recursive: true });
1213
- success(`Synapse Engine instalado (${synapseFiles.length} arquivos)`);
1214
- } else {
1424
+ const sinapse = await atualizarSynapse(sourceDir, targetDir);
1425
+ if (!sinapse) {
1215
1426
  warn('Synapse Engine nao encontrado na fonte — pulando');
1427
+ } else {
1428
+ success(`Synapse Engine em dia (${sinapse.novos} novo(s), ${sinapse.atualizados} atualizado(s))`);
1429
+ if (sinapse.preservados.length) {
1430
+ warn(`Arquivo(s) do Synapse diferente(s) de qualquer versao oficial guardado(s) em .auroq-core/preservado/synapse antes da troca: ${sinapse.preservados.join(', ')}`);
1431
+ }
1432
+ if (sinapse.falhas.length) warn(`Nao consegui atualizar: ${sinapse.falhas.join(', ')}`);
1216
1433
  }
1217
1434
 
1435
+ await writeArcaneMcpConfig(targetDir);
1436
+ step('Preparando o conteudo ao vivo (baixa uma vez; no Windows prepara tambem pro Codex; pode levar 1-3 min)...');
1437
+ if (aquecerArcaneMcp()) success('Conteudo ao vivo pronto pra abrir rapido');
1438
+ else warn('Nao consegui baixar o conteudo ao vivo agora — a primeira abertura vai demorar mais');
1439
+
1218
1440
  // ─── Fase 4b: Copiar Meta Squads e scripts (diretorios inteiros)
1219
1441
  step('Instalando Meta Squads e scripts...');
1220
1442
 
@@ -1510,6 +1732,11 @@ ${DIM} Auroq OS v${PKG_VERSION} — Sistema Operacional de IA para Experts${RES
1510
1732
  `);
1511
1733
  }
1512
1734
 
1735
+ telemetry.runtimeReady();
1736
+ if (['doctor','diagnostico'].includes(process.argv[2])) {
1737
+ console.log('Observabilidade local:',JSON.stringify(telemetry.diagnostics()));
1738
+ console.log('Manutencao: npx -y auroq-os@2.5.3 manutencao; MCP: npx -y arcane-mcp@0.6.0 doctor');
1739
+ }
1513
1740
  // ─── CLI ─────────────────────────────────────────────────
1514
1741
  const command = process.argv[2];
1515
1742
 
@@ -1517,6 +1744,7 @@ if (['init', 'install', 'update'].includes(command)) {
1517
1744
  error('Comando aposentado. Nada foi instalado ou alterado.');
1518
1745
  error('Primeira instalacao: https://arcane.arka.education/auroq');
1519
1746
  error('No negocio existente, peca ao Ops: manutencao do motor. Conteudo chega pelo MCP.');
1747
+ error('Diagnostico: npx -y auroq-os@2.5.3 doctor. Manutencao: npx -y auroq-os@2.5.3 manutencao.');
1520
1748
  process.exitCode = 1;
1521
1749
  } else if (command === 'instalar' || command === 'manutencao') {
1522
1750
  // `--pasta <nome>`: o instalador manda o CLI criar e entrar na pasta do negocio,
@@ -1591,9 +1819,9 @@ if (['init', 'install', 'update'].includes(command)) {
1591
1819
  error('Rode este comando na raiz do seu negocio.');
1592
1820
  process.exit(1);
1593
1821
  }
1594
- await patchEnableAllProjectMcpServers(targetDir);
1595
- await writeArcaneMcpConfig(targetDir);
1822
+ const { esperaOk } = await writeArcaneMcpConfig(targetDir);
1596
1823
  success('Conteudo ao vivo registrado (.mcp.json e .codex/config.toml).');
1824
+ if (esperaOk) success(`Claude Code espera o conteudo ao vivo por ate ${CLAUDE_MCP_TIMEOUT_MS / 1000}s (.claude/settings.local.json)`);
1597
1825
  log(`${DIM}Baixando o conteudo ao vivo uma vez (pode levar 1-2 min)...${RESET}`);
1598
1826
  if (aquecerArcaneMcp()) success('Conteudo ao vivo pronto pra abrir rapido');
1599
1827
  else warn('Nao consegui baixar o conteudo ao vivo agora — a primeira abertura vai demorar mais');
@@ -1603,6 +1831,15 @@ if (['init', 'install', 'update'].includes(command)) {
1603
1831
  error(`Falha ao registrar o MCP: ${err.message}`);
1604
1832
  process.exit(1);
1605
1833
  });
1834
+ } else if (command === 'historico') {
1835
+ import('../lib/ahi/historico.mjs').then((mod) => {
1836
+ const result = mod.run(process.argv.slice(3));
1837
+ console.log(JSON.stringify(result));
1838
+ process.exit(result.ok ? 0 : 1);
1839
+ }).catch((err) => {
1840
+ error(`Falha no historico: ${err.message}`);
1841
+ process.exit(1);
1842
+ });
1606
1843
  } else if (command === 'sync-codex') {
1607
1844
  // Regenera as skills do Codex CLI a partir dos comandos atuais do projeto.
1608
1845
  try {
@@ -1627,6 +1864,7 @@ if (['init', 'install', 'update'].includes(command)) {
1627
1864
  log(` ${CYAN}doctor${RESET} Diagnostico: arquivos, Node, MCP arcane, login e servidor`);
1628
1865
  log(` ${CYAN}register-mcp${RESET} Reescrever o registro do conteudo ao vivo (MCP arcane) no projeto`);
1629
1866
  log(` ${CYAN}sync-codex${RESET} Gerar e verificar as skills locais do Codex CLI ($nome)`);
1867
+ log(` ${CYAN}historico${RESET} Estado da captura do trabalho (ligada com o MCP Arcane)`);
1630
1868
  log(` ${CYAN}conectar-1password${RESET} Conectar o 1Password CLI (token lido do Ctrl+C, nunca digitado)`);
1631
1869
  log('');
1632
1870
  log(`${DIM}Primeira instalacao: https://arcane.arka.education/auroq${RESET}`);
@@ -1634,3 +1872,5 @@ if (['init', 'install', 'update'].includes(command)) {
1634
1872
  }
1635
1873
 
1636
1874
  if (process.env.AUROQ_EXPORTA_INTERNOS === '1') module.exports = { blocoCodexArcane, envCodexArcane, lerBlocoArcane, lerEnvDoBloco, limparArcaneDoGlobal, envComoCodex };
1875
+
1876
+ }
@@ -100,3 +100,23 @@ em vez do original, o teste quebra e diz qual é o certo.
100
100
  Existe pra quem não pode depender de um `cd` ter funcionado. O instalador do Mac
101
101
  entra na pasta antes de chamar o `init`, então não precisa da opção — mas ela é a
102
102
  rede de segurança de qualquer outro chamador.
103
+
104
+
105
+ ### Spool de observabilidade (npm 2.5.1 publicado; promocao pendente)
106
+
107
+ O script registra metadados fechados em `~/.arcane/installer-outbox` antes de Node.
108
+ Shell usa Perl nativo do Mac, umask 077 e lock com dono publicado atomicamente.
109
+ PowerShell verifica ACL privada e usa handle exclusivo liberado pelo kernel em crash.
110
+ Nao coleta identidade nem conteudo pessoal. O CLI importa como LOCAL, com TTL de sete
111
+ dias, e o doctor mostra os codigos; nao envia retroativamente ao primeiro login.
112
+ O proprio instalador expurga TTL e slots interrompidos a cada execucao, sem Node.
113
+ Limites: 200 slots por formato, sem captura antes
114
+ do download do proprio script, sem garantia de reconexao/adocao. Fonte oficial segue
115
+ neste diretorio; `plataforma-arcane/npm run sync:installer` sincroniza as duas copias.
116
+
117
+ Pins oficiais permanecem `auroq-os@2.5.2`, atualmente publicado. A publicacao do candidato
118
+ por 401 nas duas vias testadas pelo coordenador; nao alterar autenticacao aqui.
119
+ Candidatos motor 2.5.2/cliente 0.6.0 nao publicados. O spool local pode ser servido
120
+ com o pin antigo; novo transporte/doctor depende de npm e adocao. Runtime Windows
121
+ faz fail-closed ate dispor de ACL/no-follow seguros; spool PowerShell permanece local.
122
+ Execucao Linux de Bash/Perl nao valida macOS nem Windows/PowerShell 5.1.