wendkeep 0.66.4 → 0.67.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 (40) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.en.md +78 -5
  3. package/README.md +78 -5
  4. package/docs/en/commands/costs-and-observability.md +21 -7
  5. package/docs/en/commands/maintenance-and-diagnostics.md +13 -1
  6. package/docs/en/commands/operating-profiles.md +65 -10
  7. package/docs/en/commands/sessions-and-import.md +22 -1
  8. package/docs/en/commands/verify.md +5 -3
  9. package/docs/pt-BR/commands/costs-and-observability.md +21 -7
  10. package/docs/pt-BR/commands/maintenance-and-diagnostics.md +12 -1
  11. package/docs/pt-BR/commands/operating-profiles.md +66 -11
  12. package/docs/pt-BR/commands/sessions-and-import.md +20 -0
  13. package/docs/pt-BR/commands/verify.md +6 -3
  14. package/hooks/change-nag.mjs +8 -0
  15. package/hooks/codex-rollout-meta.mjs +112 -0
  16. package/hooks/codex-subagent-graph.mjs +903 -0
  17. package/hooks/harness-doctor.mjs +82 -1
  18. package/hooks/import-sessions.mjs +185 -50
  19. package/hooks/operating-profile-runtime.mjs +36 -2
  20. package/hooks/operating-profile-task-store.mjs +77 -0
  21. package/hooks/session-identity.mjs +40 -5
  22. package/hooks/session-observability-lifecycle.mjs +129 -0
  23. package/hooks/session-observability-state.mjs +241 -0
  24. package/hooks/session-observability-store.mjs +436 -0
  25. package/hooks/session-observability.mjs +647 -21
  26. package/hooks/session-stop.mjs +339 -11
  27. package/hooks/subagent-stop.mjs +266 -12
  28. package/hooks/subagent-usage.mjs +65 -0
  29. package/hooks/token-usage.mjs +81 -4
  30. package/package.json +3 -3
  31. package/packages/harness/src/operating-profile.mjs +127 -0
  32. package/packages/harness/src/sensors-core.mjs +41 -1
  33. package/packages/integrations/src/prompt-content.mjs +123 -0
  34. package/packages/integrations/src/transcripts.mjs +16 -10
  35. package/src/cost.mjs +40 -6
  36. package/src/doctor.mjs +4 -1
  37. package/src/profile.mjs +95 -17
  38. package/src/rebuild-costs.mjs +220 -34
  39. package/src/skills-seed.mjs +38 -2
  40. package/src/sync-defs.mjs +6 -1
@@ -26,7 +26,7 @@ Registry consistente, tabela de preços completa e acesso aos transcripts das se
26
26
  ```bash
27
27
  npx wendkeep stats [--vault <cofre>] [--json]
28
28
  npx wendkeep cost [--since <data>] [--top [N]] [--trend day|week|month] [--write] [--json]
29
- npx wendkeep cost rebuild [--session <id|arquivo>] [--limit N] [--apply] [--json]
29
+ npx wendkeep cost rebuild [--session <id|arquivo>] [--limit N] [--max-graph-nodes N] [--max-fallback-days N] [--max-fallback-candidates N] [--apply] [--json]
30
30
  ```
31
31
 
32
32
  ## Opções e códigos de saída
@@ -34,10 +34,18 @@ npx wendkeep cost rebuild [--session <id|arquivo>] [--limit N] [--apply] [--json
34
34
  - `wendkeep stats` gera uma linha compartilhável ou JSON.
35
35
  - `wendkeep cost` agrega total/modelo/dia; `--trend` inclui projeção e `--write` atualiza
36
36
  `00-Custo.md`.
37
- - `wendkeep cost rebuild` é dry-run por padrão; `--apply` grava notas e
38
- `.brain/COST_REBUILD.json`.
39
- - Exit `0` indica cálculo consistente; não zero indica registry, preço, transcript ou parsing
40
- insuficiente.
37
+ - `wendkeep cost rebuild` é dry-run por padrão e tem **zero escrita**: não adquire lock de
38
+ gravação, não altera nota, registry ou runtime e não cria `.brain/COST_REBUILD.json`.
39
+ - `--apply` publica somente candidatos `complete` ou `none`. O estado `none` zera a seção
40
+ quando um scan offline estável comprovou que nenhum subagente foi iniciado.
41
+ - Um candidato `degraded` ou `stale` produz exit `1` e preserva a nota sem alteração; o lote
42
+ continua para que outras sessões seguras possam ser processadas e o relatório exponha os
43
+ códigos sanitizados.
44
+ - Os overrides `--max-graph-nodes`, `--max-fallback-days` e `--max-fallback-candidates` são
45
+ exclusivamente para rebuild direcionado com `--session`. Usá-los sem `--session` é uso inválido
46
+ e produz exit `2`; hooks, import e rebuild em lote mantêm os limites padrão.
47
+ - Exit `0` significa preview/aplicação consistente; exit `1` indica resultado parcial
48
+ `degraded`/`stale`; exit `2` indica sintaxe ou contexto inválido.
41
49
 
42
50
  ## Exemplos
43
51
 
@@ -45,12 +53,16 @@ npx wendkeep cost rebuild [--session <id|arquivo>] [--limit N] [--apply] [--json
45
53
  npx wendkeep stats --vault .MeuApp-vault
46
54
  npx wendkeep cost --since 2026-07-01 --top 10 --trend week
47
55
  npx wendkeep cost rebuild --session 019abc --json
56
+ npx wendkeep cost rebuild --session 019abc --max-graph-nodes 8192 --json
57
+ npx wendkeep cost rebuild --session 019abc --apply
48
58
  ```
49
59
 
50
60
  ## Resultado esperado
51
61
 
52
- Totais preservam dimensões de input/output/cache/reasoning por modelo e período. Rebuild mostra a
53
- prévia antes de alterar notas e deixa um relatório reproduzível quando aplicado.
62
+ Totais preservam dimensões de input/output/cache/reasoning por modelo e período. A composição
63
+ tri-state devolve `complete`, `none` ou `degraded`, mais frontier, manifest e diagnostics
64
+ sanitizados. Rode e revise o dry-run antes de repetir o mesmo comando com `--apply`; uma segunda
65
+ aplicação semanticamente idêntica preserva nota, checkpoint, relatório e mtime.
54
66
 
55
67
  ## Erros comuns e diagnóstico
56
68
 
@@ -58,6 +70,8 @@ prévia antes de alterar notas e deixa um relatório reproduzível quando aplica
58
70
  - Custos de provider errado: valide a cadeia de identidade da sessão.
59
71
  - Transcript ausente: não estime silenciosamente; mantenha a lacuna visível.
60
72
  - Total duplicado por subagent/fork: confirme relação pai/subagent e deduplicação do registry.
73
+ - `degraded`/`stale`: preserve a nota e investigue diagnostics/frontier antes de autorizar apply.
74
+ - Override rejeitado: acrescente `--session <id|arquivo>` ou remova os três limites direcionados.
61
75
 
62
76
  ## Próximos passos
63
77
 
@@ -39,6 +39,11 @@ npx wendkeep --help
39
39
  continuam duráveis na outbox/ledger é warning recuperável.
40
40
  - Attempt ambíguo, event ID perdido (ausente de ledger e outbox), estado `projected` apenas na
41
41
  outbox ou checkpoint divergente são falhas bloqueantes.
42
+ - Para observabilidade de sessão, `legacy`, `degraded`, `stale` e `manifest-unproven` exigem
43
+ reconciliação ou evidência adicional. Somente `none` fresco e `complete` fresco são saudáveis:
44
+ frontier, checkpoint, root stat e source manifest precisam concordar.
45
+ - O `doctor` permanece somente leitura/read-only. Ele recomenda primeiro o dry-run direcionado;
46
+ somente depois da revisão humana orienta repetir com `--apply`.
42
47
  - `sync-defs --check` detecta drift sem gravar; `--reseed` restaura skills `wk-*` do pacote.
43
48
  - `theme sync` reaplica snippet CSS e grupos do grafo sem recriar o cofre.
44
49
  - `wendkeep --version` imprime a versão executada; `wendkeep --help` lista a interface pública.
@@ -52,6 +57,8 @@ npx wendkeep --version
52
57
  npx wendkeep sync-defs --check --vault .MeuApp-vault --project .
53
58
  npx wendkeep doctor --vault .MeuApp-vault
54
59
  npx wendkeep memory status --gate --vault .MeuApp-vault
60
+ npx wendkeep cost rebuild --session <id> --json
61
+ npx wendkeep cost rebuild --session <id> --apply
55
62
  ```
56
63
 
57
64
  ## Resultado esperado
@@ -59,7 +66,9 @@ npx wendkeep memory status --gate --vault .MeuApp-vault
59
66
  O doctor nomeia sessões, registry, links, notas, preços, derivadas e memória como saudáveis ou
60
67
  fornece um comando específico de diagnóstico/reparo. Na memória, ele distingue vazio inicial
61
68
  válido, replay pendente recuperável e lifecycle perdido/divergente. Nenhum reparo é aplicado
62
- implicitamente nem o conteúdo privado do erro do projector é reproduzido no relatório.
69
+ implicitamente nem o conteúdo privado do erro do projector é reproduzido no relatório. Na
70
+ observabilidade de sessão, ele separa `none`/`complete` frescos de estado legado, degradado, stale
71
+ ou sem manifest comprovado e oferece um caminho dry-run antes de qualquer escrita.
63
72
 
64
73
  ## Erros comuns e diagnóstico
65
74
 
@@ -70,6 +79,8 @@ implicitamente nem o conteúdo privado do erro do projector é reproduzido no re
70
79
  - `ambiguous`, publicação perdida ou checkpoint divergente: bloqueante; preserve registry, ledger,
71
80
  outbox e SHARED para correlacionar `last_memory_attempt` antes de reparar.
72
81
  - Bundle corrompido: preserve a evidência e use `memory status --gate` antes de `memory repair`.
82
+ - Observabilidade `legacy`/`degraded`/`stale`/`manifest-unproven`: rode
83
+ `wendkeep cost rebuild --session <id> --json`, revise diagnostics e só então autorize `--apply`.
73
84
 
74
85
  ## Próximos passos
75
86
 
@@ -14,9 +14,9 @@ deliberado e executa as validações e gates próprios daquele comando.
14
14
 
15
15
  ## Quando usar
16
16
 
17
- Use `profile` para consultar ou selecionar explicitamente um Perfil de Operação. Use `FLOW` para
18
- manutenção local, reversível e com `spec_impact:none` que caiba num microcontrato Executar
19
- Validar, sem change.
17
+ Use `profile use` para uma seleção humana persistente e `profile route` para o harness registrar a
18
+ rota temporária da implementação atual. Use `FLOW` para manutenção local, reversível e com
19
+ `spec_impact:none` que caiba num microcontrato Executar → Validar, sem change.
20
20
 
21
21
  ## Quando não usar
22
22
 
@@ -28,7 +28,8 @@ gates/policies do WendKeep; promova o trabalho para uma change.
28
28
  ## Pré-requisitos
29
29
 
30
30
  - Projeto inicializado, com `.wendkeep.json` vinculado ao Vault correto.
31
- - Para override de sessão, uma sessão inequívoca no `SESSION_REGISTRY.json`.
31
+ - Para override ou rota temporária, uma sessão inequívoca no `SESSION_REGISTRY.json`; `route`
32
+ também exige que o prompt atual já tenha fronteira causal registrada.
32
33
  - Para FLOW, repositório Git, allowlist de paths, motivo e ao menos um sensor existente em
33
34
  `wendkeep.sensors.json`.
34
35
 
@@ -37,6 +38,7 @@ gates/policies do WendKeep; promova o trabalho para uma change.
37
38
  ```bash
38
39
  npx wendkeep profile status [--project <path>] [--vault <path>] [--session <id>] [--json]
39
40
  npx wendkeep profile use <perfil> [--project <path>] [--vault <path>] [--session <id>] [--json]
41
+ npx wendkeep profile route <FLOW|GUIDE|GOVERN|ASSURE> --session <id> --reason <texto> [--project <path>] [--vault <path>] [--json]
40
42
  npx wendkeep flow start <slug> --allow <path> [--allow <path>...] --sensor <id> [--sensor <id>...] --reason <texto> [--session <id>]
41
43
  npx wendkeep flow status [<id>]
42
44
  npx wendkeep flow show <id> [--session <id>]
@@ -76,15 +78,42 @@ independentes.
76
78
  | `GOVERN` | P → R → E → V | Loop a2 atual e fallback conservador. |
77
79
  | `ASSURE` | P → R → E → V → C | Governança acrescida de confirmação e handoff. |
78
80
 
79
- - A resolução segue override explícito da sessão → `harness.profile` do projeto → `GOVERN`.
80
- Heurística, tamanho do diff, texto do prompt, variável de ambiente ou erro de leitura nunca
81
+ ### Legenda da rota
82
+
83
+ As letras são etapas do trabalho, não comandos individuais:
84
+
85
+ - `P` = **Planejar/Propor** — entender o pedido, delimitar o escopo e registrar a abordagem.
86
+ - `R` = **Revisar** — revisar proposta/design antes da execução; é a revisão formal do loop a2.
87
+ - `E` = **Executar** — editar os paths e artefatos permitidos.
88
+ - `V` = **Validar** — rodar testes, sensores e verificações e registrar evidência.
89
+ - `C` = **Confirmar/entregar** — obter confirmação explícita e fazer handoff.
90
+
91
+ Logo, `P → R → E → V` é “planejar/propor, revisar, executar e validar”. `FLOW` começa no
92
+ microcontrato de execução/validação; `OFF` não impõe rota Wend automática e entrega o processo ao
93
+ harness nativo da LLM.
94
+
95
+ - O harness da LLM classifica semanticamente o pedido e registra `profile route`; o Wend Runtime
96
+ não classifica texto, tamanho do diff, heurística ou variável de ambiente. Ele valida e aplica a
97
+ lease determinística.
98
+ - Para correção local, reversível e sem contrato/spec, escolha `FLOW`. Para mudança compacta de
99
+ comportamento que precisa de change sem revisão formal, escolha `GUIDE`. Em dúvida, risco,
100
+ segurança, contrato público, dependências, CI/release ou policy, escolha `GOVERN`. Use `ASSURE`
101
+ quando confirmação e handoff forem parte do contrato.
102
+ - `OFF` nunca pode ser uma rota adaptativa; somente uma pessoa o persiste explicitamente por
103
+ `profile use OFF`. Uma base `OFF` ainda pode receber uma elevação temporária para rota Wend.
104
+
105
+ - A resolução segue lease ativa do prompt → override persistente da sessão →
106
+ `harness.profile` do projeto → `GOVERN`. Lease inválida/expirada e erro de leitura nunca
81
107
  selecionam `OFF`.
82
- - `profile status` mostra perfil efetivo e origem; `--json` produz saída estruturada. Quando um
83
- Vault explícito preserva a escolha apesar de binding corrompido, a saída inclui `binding_error`
84
- e o diagnóstico também vai para stderr.
108
+ - `profile status` mostra perfil efetivo e origem. Com `--session`, a saída humana acrescenta
109
+ `base=<perfil>/<origem>` e `lease=<estado>`; `--json` produz os mesmos dados estruturados. Quando
110
+ um Vault explícito preserva a escolha apesar de binding corrompido, a saída inclui
111
+ `binding_error` e o diagnóstico também vai para stderr.
85
112
  - `profile use` valida nome e flags estritamente; opção singleton duplicada, incompleta ou com
86
113
  valor iniciado por `--` falha antes de I/O. Sem `--session`, altera atomicamente o binding do
87
114
  projeto; com `--session`, grava override, origem e timestamp sem trocar a identidade da sessão.
115
+ - `profile route` exige `--session` e `--reason`, aceita somente os quatro perfis adaptativos e
116
+ grava `lease_id`, motivo, turno/sequência e timestamp sem tocar no perfil persistente.
88
117
  - `.wendkeep.json` continua em `schemaVersion: 1`; o campo aditivo usa, por exemplo,
89
118
  `"harness": { "profile": "GOVERN" }`. Binding legado sem o campo também resolve `GOVERN`.
90
119
  - Binding corrompido nunca equivale a `OFF`. Quando o payload ou a integração legada identifica
@@ -126,13 +155,39 @@ independentes.
126
155
  - Exit `0` indica consulta ou transição concluída; exit `1` indica política/sensor vermelho; exit
127
156
  `2` indica perfil, sessão, flow ou argumentos inválidos, sem mutação parcial.
128
157
 
158
+ ### Escopo da escolha
159
+
160
+ Sem `--session`, `profile use` grava `harness.profile` no `.wendkeep.json` e muda o padrão do
161
+ projeto para as conversas/hooks que não tenham override de sessão. Com `--session <id>`, grava o
162
+ override somente no `SESSION_REGISTRY.json` daquela sessão e preserva o padrão do projeto. Por
163
+ isso, `profile use OFF` sem `--session` não é um teste isolado; se o `.wendkeep.json` for commitado,
164
+ essa escolha também será compartilhada com outros checkouts.
165
+
166
+ `profile route` cria uma lease apenas para a solicitação atual. Um `Stop` aceito a consome por
167
+ CAS; se o cleanup não rodar, o próximo `UserPromptSubmit` avança a sequência e a lease deixa de ser
168
+ efetiva. Stop bloqueado preserva a lease para o retry do mesmo pedido. Não há TTL de relógio que
169
+ interrompa trabalho longo. Sessão ainda sem prompt causal registrado (turno ausente, sequência
170
+ zero, mapa causal ausente ou divergente) é rejeitada antes de qualquer mutação. `status --session`
171
+ inclui o perfil-base e o estado da lease tanto na saída humana quanto em `--json`; neste, os campos
172
+ são `base_profile`, `base_source` e `task_lease.state` (`active`, `consumed`, `expired`, `invalid` ou
173
+ `absent`).
174
+
175
+ ```bash
176
+ npx wendkeep profile status # padrão do projeto
177
+ npx wendkeep profile use GUIDE # altera o padrão do projeto
178
+ npx wendkeep profile use FLOW --session <id> # altera somente uma sessão
179
+ npx wendkeep profile route FLOW --session <id> --reason "ajuste local" # pedido atual
180
+ npx wendkeep profile status --session <id> # consulta a sessão efetiva
181
+ ```
182
+
129
183
  ## Exemplos
130
184
 
131
- Consultar o padrão efetivo e aplicar override somente à sessão atual:
185
+ Consultar o padrão efetivo e rotear somente a implementação atual:
132
186
 
133
187
  ```bash
134
188
  npx wendkeep profile status
135
- npx wendkeep profile use OFF --session 019abc-session-id --json
189
+ npx wendkeep profile route FLOW --session 019abc-session-id --reason "corrige typo local" --json
190
+ npx wendkeep profile status --session 019abc-session-id --json
136
191
  ```
137
192
 
138
193
  Executar uma manutenção FLOW capturando o `flow_id` retornado por `start`:
@@ -46,6 +46,13 @@ npx wendkeep import [opções]
46
46
  - `Stop` aceita somente o turno comprovado pelo transcript e pela activation ativa compatível.
47
47
  Duplicatas são no-op; Stops stale/superseded não publicam memória nem sobrescrevem o checkpoint
48
48
  de um epoch mais novo.
49
+ - `Stop` recebe deadline absoluto de **45 s** desde a entrada do hook. A leitura verifica o relógio
50
+ entre rollouts e a cada chunk; ao atingir o limite, devolve `degraded` antes do timeout do host.
51
+ - `SubagentStop` recebe deadline absoluto de **15 s**. Sinais que chegam na janela de **250 ms**
52
+ são coalescidos: somente a maior sequência recompõe/publica, sem perder o último filho.
53
+ - A observabilidade usa tri-state: `complete` publica o snapshot integral; `none` representa zero
54
+ comprovado por Stop causal ou scan offline estável; `degraded` preserva o snapshot anterior e
55
+ diagnostics allowlisted. `SubagentStop` isolado nunca publica `none`.
49
56
  - Ao compactar conversas em `## Iterações`, o hook escapa delimitadores de código cortados pelo
50
57
  limite de tamanho; backticks inline ou fences nunca ficam abertos para engolir a linha seguinte.
51
58
  - `session list` lê `SESSION_REGISTRY`; `show` exibe uma sessão e `use` muda apenas o foco humano
@@ -53,6 +60,9 @@ npx wendkeep import [opções]
53
60
  - `import --source all|claude|codex`, `--since`, `--limit`, `--from` e `--codex-from` limitam escopo.
54
61
  - `--dry-run`/`--json` permitem auditar antes de gravar; `--stamp-ids` e `--rescan-decisions`
55
62
  corrigem históricos específicos.
63
+ - `import` reconcilia a observabilidade mesmo quando nenhum `wk-turn` está ausente: schema legado,
64
+ frontier stale ou manifest não comprovado disparam recomposição sem duplicar iterações. Um
65
+ checkpoint fresco permanece byte-idêntico; `degraded` é reportado e não altera a nota.
56
66
  - Exit `0` indica processamento consistente; exit não zero indica configuração, fonte ou escrita
57
67
  inválida sem transformar isso em sucesso parcial silencioso.
58
68
 
@@ -71,6 +81,14 @@ O registry mantém um epoch de `SessionStart` por activation e o turno nativo ma
71
81
  `Stop` podem confirmar turnos do mesmo epoch sem fechá-lo. Importações repetidas do mesmo
72
82
  `session_id` são deduplicadas; o foco humano não encerra nem altera a identidade dos hooks. Cada
73
83
  iteração automática permanece Markdown válido mesmo quando uma fala precisa ser truncada.
84
+ Metadados internos terminais completos ou truncados são removidos somente das mensagens do
85
+ assistente; uma reprodução escrita pelo usuário permanece no transcript. Na nota, tags XML-like
86
+ são codificadas como texto visível — inclusive placeholders como `<session>` — sem alterar
87
+ autolinks `<https://...>`. Reimport e `SessionStop` compartilham o mesmo normalizador idempotente;
88
+ ao finalizar uma nota antiga, somente campos gerados reconhecíveis em `Iterações` e
89
+ `Encerramento` são migrados, sem reescrever a prosa autoral. Hooks
90
+ duplicados/stale convergem no mesmo frontier, e importações podem atualizar só a observabilidade
91
+ sem criar um novo bloco de turno.
74
92
 
75
93
  ## Erros comuns e diagnóstico
76
94
 
@@ -82,6 +100,8 @@ iteração automática permanece Markdown válido mesmo quando uma fala precisa
82
100
  - Duplicatas de forks: limite por fonte/data e revise `forked_from_id`/`source.subagent`.
83
101
  - Codex não captura: aprove os hooks e reinicie a sessão após `sync`.
84
102
  - Custo contaminado: valide `session_id → session_file → transcript_path → provider`.
103
+ - Observabilidade `degraded`: preserve a nota e rode o rebuild direcionado em dry-run; não force
104
+ um snapshot parcial sobre o último `complete`.
85
105
 
86
106
  ## Próximos passos
87
107
 
@@ -74,15 +74,18 @@ npx wendkeep memory status --gate --vault .MeuApp-vault
74
74
  ## Resultado esperado
75
75
 
76
76
  `evidencia.json` contém resultados dos sensores e um selo liga a prova ao hash atual de
77
- `tarefas.md`. No deep, o pacote contém requisitos, tarefas e evidência suficientes para revisão
78
- read-only; o verdict cobre cada `[req:]` antes do archive.
77
+ `tarefas.md`. Quando um sensor fica vermelho, sua entrada recebe somente um diagnóstico local
78
+ sanitizado e limitado a 2.000 caracteres; stdout/stderr de sensores verdes não é persistido. No
79
+ deep, o pacote contém requisitos, tarefas e evidência suficientes para revisão read-only; o
80
+ verdict cobre cada `[req:]` antes do archive.
79
81
 
80
82
  ## Erros comuns e diagnóstico
81
83
 
82
84
  - `no change`: isso é exit 2 e estado ocioso válido; crie/use uma change ou não rode verify.
83
85
  - Zero/sensores ausentes: confira todas as tags na mesma linha e `sensors list`; várias tags na
84
86
  mesma tarefa são válidas e todas entram no gate.
85
- - Gate vermelho: corrija a causa e repita; não use `archive --force` por conta própria.
87
+ - Gate vermelho: consulte o campo `note` limitado da entrada em `evidencia.json`, corrija a causa
88
+ e repita; não use `archive --force` por conta própria.
86
89
  - Verdict stale/ausente: regenere `--deep` e peça novo passe independente.
87
90
  - Mutantes sobreviventes: fortaleça o teste discriminante; após três rodadas, revise manualmente.
88
91
 
@@ -13,6 +13,7 @@ import {
13
13
  profileSentinelId,
14
14
  resolveHookOperatingProfile,
15
15
  } from './operating-profile-runtime.mjs';
16
+ import { consumeSessionTaskOperatingProfile } from './operating-profile-task-store.mjs';
16
17
 
17
18
  export function nagDecision(input, vaultBase, { profile = 'GOVERN' } = {}) {
18
19
  if (input && input.stop_hook_active) return null; // anti-loop: sempre primeiro
@@ -39,6 +40,13 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
39
40
  : runtime.bindingError
40
41
  ? { decision: 'block', reason: profileRuntimeError(runtime.bindingError) }
41
42
  : nagDecision(input, runtime.vaultBase, { profile: runtime.profile });
43
+ if (!decision && runtime.taskLease?.state === 'active') {
44
+ consumeSessionTaskOperatingProfile(
45
+ runtime.vaultBase,
46
+ runtime.identity?.canonicalConversationId || input?.session_id || input?.sessionId || '',
47
+ runtime.taskLease.lease_id,
48
+ );
49
+ }
42
50
  writeHookOutput(decision || {});
43
51
  } catch {
44
52
  writeHookOutput({});
@@ -0,0 +1,112 @@
1
+ import {
2
+ closeSync,
3
+ openSync,
4
+ readSync,
5
+ } from 'node:fs';
6
+
7
+ export const DEFAULT_CODEX_META_LINE_BYTES = 4 * 1024 * 1024;
8
+ const READ_CHUNK_BYTES = 64 * 1024;
9
+
10
+ function trimCarriageReturn(buffer) {
11
+ return buffer.length > 0 && buffer[buffer.length - 1] === 0x0d
12
+ ? buffer.subarray(0, buffer.length - 1)
13
+ : buffer;
14
+ }
15
+
16
+ // Codex writes session_meta as the first physical JSONL line. Read only that line: the
17
+ // transcript body can be hundreds of MiB and is irrelevant to identity/discovery.
18
+ export function readFirstJsonlLine(
19
+ path,
20
+ { maxBytes = DEFAULT_CODEX_META_LINE_BYTES } = {},
21
+ ) {
22
+ if (typeof path !== 'string' || !path) {
23
+ return { ok: false, reason: 'INVALID_PATH' };
24
+ }
25
+
26
+ const limit = Number(maxBytes);
27
+ if (!Number.isSafeInteger(limit) || limit <= 0) {
28
+ return { ok: false, reason: 'READ_ERROR' };
29
+ }
30
+
31
+ let fd;
32
+ try {
33
+ fd = openSync(path, 'r');
34
+ const parts = [];
35
+ let totalBytes = 0;
36
+
37
+ while (totalBytes <= limit) {
38
+ // The extra byte distinguishes an exactly-at-limit line followed by LF from a line
39
+ // whose content exceeds the limit.
40
+ const remaining = (limit + 1) - totalBytes;
41
+ const chunk = Buffer.allocUnsafe(Math.min(READ_CHUNK_BYTES, remaining));
42
+ const bytesRead = readSync(fd, chunk, 0, chunk.length, null);
43
+
44
+ if (bytesRead === 0) {
45
+ if (totalBytes === 0) return { ok: false, reason: 'EMPTY_FILE' };
46
+ const lineBuffer = trimCarriageReturn(Buffer.concat(parts, totalBytes));
47
+ return {
48
+ ok: true,
49
+ line: lineBuffer.toString('utf8'),
50
+ lineBytes: lineBuffer.length,
51
+ };
52
+ }
53
+
54
+ const bytes = chunk.subarray(0, bytesRead);
55
+ const newlineAt = bytes.indexOf(0x0a);
56
+ if (newlineAt !== -1) {
57
+ if (totalBytes + newlineAt > limit) {
58
+ return { ok: false, reason: 'LINE_TOO_LONG' };
59
+ }
60
+ parts.push(bytes.subarray(0, newlineAt));
61
+ const lineBuffer = trimCarriageReturn(Buffer.concat(parts, totalBytes + newlineAt));
62
+ if (lineBuffer.length === 0) return { ok: false, reason: 'EMPTY_FILE' };
63
+ return {
64
+ ok: true,
65
+ line: lineBuffer.toString('utf8'),
66
+ lineBytes: lineBuffer.length,
67
+ };
68
+ }
69
+
70
+ parts.push(bytes);
71
+ totalBytes += bytesRead;
72
+ if (totalBytes > limit) return { ok: false, reason: 'LINE_TOO_LONG' };
73
+ }
74
+
75
+ return { ok: false, reason: 'LINE_TOO_LONG' };
76
+ } catch {
77
+ return { ok: false, reason: 'READ_ERROR' };
78
+ } finally {
79
+ if (fd !== undefined) {
80
+ try { closeSync(fd); } catch { /* already closed */ }
81
+ }
82
+ }
83
+ }
84
+
85
+ export function readCodexRolloutMeta(
86
+ path,
87
+ { maxLineBytes = DEFAULT_CODEX_META_LINE_BYTES } = {},
88
+ ) {
89
+ const first = readFirstJsonlLine(path, { maxBytes: maxLineBytes });
90
+ if (!first.ok) return first;
91
+
92
+ let event;
93
+ try {
94
+ event = JSON.parse(first.line);
95
+ } catch {
96
+ return { ok: false, reason: 'INVALID_JSON' };
97
+ }
98
+
99
+ if (!event || typeof event !== 'object' || Array.isArray(event)
100
+ || event.type !== 'session_meta') {
101
+ return { ok: false, reason: 'NOT_SESSION_META' };
102
+ }
103
+ if (!event.payload || typeof event.payload !== 'object' || Array.isArray(event.payload)) {
104
+ return { ok: false, reason: 'INVALID_META' };
105
+ }
106
+
107
+ return {
108
+ ok: true,
109
+ meta: event.payload,
110
+ lineBytes: first.lineBytes,
111
+ };
112
+ }