auroq-os 2.6.6 → 2.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.auroq-core/core-config.yaml +1 -1
- package/.claude/hooks/ahi/security.cjs +18 -9
- package/.claude/hooks/ahi/win-vault.ps1 +102 -0
- package/CHANGELOG.md +11 -0
- package/README.md +3 -3
- package/bin/auroq-os.js +1 -0
- package/installer/install.ps1 +2 -2
- package/installer/install.sh +2 -2
- package/lib/runtime-integrity.json +10 -6
- package/package.json +1 -1
- package/scripts/runtime-integrity.mjs +1 -1
|
@@ -53,11 +53,15 @@ function safePath(file) {
|
|
|
53
53
|
cursor = path.dirname(cursor);
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
-
|
|
56
|
+
// Windows only. The vault logic lives in win-vault.ps1, shipped as plain readable text next to this file:
|
|
57
|
+
// no encoded command, no script text built here. Data goes through stdin as UTF-8; the answer is hex on stdout.
|
|
58
|
+
// The script is started by relative name from its own folder, so quotes, brackets or apostrophes in the
|
|
59
|
+
// student's folder names never reach the PowerShell command line.
|
|
60
|
+
function powershell(action, input) {
|
|
57
61
|
// Windows PowerShell 5.1 must build its own module path when launched through Node/pwsh 7.
|
|
58
62
|
// Keep the caller's environment unchanged, including differently cased Windows names.
|
|
59
63
|
const env = Object.fromEntries(Object.entries(process.env).filter(([name]) => name.toLowerCase() !== 'psmodulepath'));
|
|
60
|
-
const result = spawnSync('powershell.exe', ['-NoLogo', '-NoProfile', '-NonInteractive', '-
|
|
64
|
+
const result = spawnSync('powershell.exe', ['-NoLogo', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', '.\\win-vault.ps1', action], { cwd: __dirname, input, env, encoding: 'utf8', timeout: 5000, windowsHide: true, maxBuffer: 65536 });
|
|
61
65
|
if (result.status !== 0 || result.error) throw new Error('SECURE_STORAGE_UNAVAILABLE');
|
|
62
66
|
return result.stdout.trim();
|
|
63
67
|
}
|
|
@@ -65,10 +69,10 @@ function protectMany(dirs) {
|
|
|
65
69
|
const unique = [...new Set(dirs)]; if (!unique.length) return;
|
|
66
70
|
for (const dir of unique) { safePath(dir); fs.mkdirSync(dir, { recursive: true, mode: 0o700 }); }
|
|
67
71
|
if (process.platform === 'win32') {
|
|
68
|
-
// Core .NET APIs avoid the variable cold cost of preparing PowerShell cmdlet modules.
|
|
72
|
+
// Core .NET APIs (win-vault.ps1) avoid the variable cold cost of preparing PowerShell cmdlet modules.
|
|
69
73
|
// Keep the exact same private DACL: current owner + SYSTEM, no inherited external access.
|
|
70
|
-
|
|
71
|
-
powershell(
|
|
74
|
+
// The script confirms with "ok <count>": an empty or truncated .ps1 exits 0 without doing anything, and must not pass for success.
|
|
75
|
+
if (powershell('protect', unique.map(dir => path.resolve(dir)).join('\n')) !== `ok ${unique.length}`) throw new Error('SECURE_STORAGE_UNAVAILABLE');
|
|
72
76
|
} else {
|
|
73
77
|
for (const dir of unique) {
|
|
74
78
|
fs.chmodSync(dir, 0o700);
|
|
@@ -119,14 +123,19 @@ function keyFor(ctx, create = false) {
|
|
|
119
123
|
const file = path.join(ctx.accountDir, 'key.dpapi');
|
|
120
124
|
if (!fs.existsSync(file)) {
|
|
121
125
|
if (!create) throw new Error('SECURE_STORAGE_UNAVAILABLE');
|
|
122
|
-
const
|
|
126
|
+
const created = powershell('create-key');
|
|
127
|
+
if (!/^(?:[a-f0-9]{2})+$/.test(created)) throw new Error('SECURE_STORAGE_UNAVAILABLE');
|
|
128
|
+
const wrapped = Buffer.from(created, 'hex').toString('base64');
|
|
123
129
|
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(wrapped) || Buffer.from(wrapped, 'base64').length < 32) throw new Error('SECURE_STORAGE_UNAVAILABLE');
|
|
124
130
|
try { atomic(file, wrapped, true); } catch (e) { if (e.code !== 'EEXIST') throw e; }
|
|
125
131
|
}
|
|
126
132
|
safePath(file);
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
133
|
+
// key.dpapi keeps its original format: the DPAPI blob as base64 text. Only hex goes through the PowerShell side.
|
|
134
|
+
const wrapped = fs.readFileSync(file, 'utf8').trim();
|
|
135
|
+
if (wrapped.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(wrapped)) throw new Error('SECURE_STORAGE_UNAVAILABLE');
|
|
136
|
+
const raw = powershell('open-key', Buffer.from(wrapped, 'base64').toString('hex'));
|
|
137
|
+
if (!/^[a-f0-9]{64}$/.test(raw)) throw new Error('SECURE_STORAGE_UNAVAILABLE');
|
|
138
|
+
return Buffer.from(raw, 'hex');
|
|
130
139
|
}
|
|
131
140
|
throw new Error('UNSUPPORTED_OS_KEYSTORE');
|
|
132
141
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# win-vault.ps1 - cofre local do Auroq OS no Windows (captura AHI).
|
|
2
|
+
#
|
|
3
|
+
# Faz so isto, no computador do proprio aluno, e nada mais:
|
|
4
|
+
# protect deixa as pastas recebidas privadas: so o dono da conta do Windows e o SISTEMA
|
|
5
|
+
# acessam, sem heranca de permissao de fora.
|
|
6
|
+
# create-key gera uma chave aleatoria de 32 bytes e a protege com o DPAPI do Windows
|
|
7
|
+
# (escopo do usuario atual: so este usuario, neste computador, consegue abrir).
|
|
8
|
+
# open-key abre com o DPAPI uma chave que o create-key protegeu antes.
|
|
9
|
+
#
|
|
10
|
+
# Nao acessa a rede, nao baixa nem executa codigo de fora, nao inicia outros programas e nao grava
|
|
11
|
+
# arquivo nenhum: so troca a permissao das pastas recebidas (protect) ou escreve o resultado na
|
|
12
|
+
# saida padrao. Quem chama e o security.cjs, desta mesma pasta, por -File, passando a acao como
|
|
13
|
+
# argumento. Os dados entram por stdin em UTF-8 (uma pasta por linha em protect; a chave protegida
|
|
14
|
+
# em hex em open-key) e a resposta sai por stdout: "ok N" em protect (N pastas protegidas) e a chave
|
|
15
|
+
# em hex nas outras duas. Qualquer erro, ou resposta fora desse formato, e tratado pelo security.cjs
|
|
16
|
+
# como cofre indisponivel (falha fechada).
|
|
17
|
+
#
|
|
18
|
+
# Usa so a API do .NET, sem cmdlets: preparar os modulos do PowerShell numa maquina fria passava
|
|
19
|
+
# do limite de 5 segundos do security.cjs.
|
|
20
|
+
param([string]$Action)
|
|
21
|
+
|
|
22
|
+
$ErrorActionPreference = 'Stop'
|
|
23
|
+
$PSModuleAutoLoadingPreference = 'None'
|
|
24
|
+
|
|
25
|
+
# stdin em bytes crus, lidos como UTF-8: o console do Windows usa outra codificacao (OEM) e
|
|
26
|
+
# estragaria o acento no nome da pasta do aluno.
|
|
27
|
+
function Get-StdinText {
|
|
28
|
+
$stream = [Console]::OpenStandardInput()
|
|
29
|
+
$buffer = [IO.MemoryStream]::new()
|
|
30
|
+
try { $stream.CopyTo($buffer) } finally { $stream.Dispose() }
|
|
31
|
+
return [Text.UTF8Encoding]::new($false).GetString($buffer.ToArray())
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function ConvertTo-Hex([byte[]]$Bytes) {
|
|
35
|
+
return [BitConverter]::ToString($Bytes).Replace('-', '').ToLowerInvariant()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function ConvertFrom-Hex([string]$Hex) {
|
|
39
|
+
if ($Hex.Length -eq 0 -or ($Hex.Length % 2) -ne 0) { throw 'hex invalido' }
|
|
40
|
+
$bytes = [byte[]]::new([int]($Hex.Length / 2))
|
|
41
|
+
for ($i = 0; $i -lt $bytes.Length; $i++) { $bytes[$i] = [Convert]::ToByte($Hex.Substring(2 * $i, 2), 16) }
|
|
42
|
+
return , $bytes
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function Protect-Folders([string]$Text) {
|
|
46
|
+
$owner = [Security.Principal.WindowsIdentity]::GetCurrent().User
|
|
47
|
+
$system = [Security.Principal.SecurityIdentifier]::new('S-1-5-18')
|
|
48
|
+
$count = 0
|
|
49
|
+
foreach ($line in $Text.Split([char[]]@([char]10), [StringSplitOptions]::RemoveEmptyEntries)) {
|
|
50
|
+
$folder = $line.TrimEnd([char]13)
|
|
51
|
+
if ($folder.Length -eq 0) { continue }
|
|
52
|
+
# Caminho relativo seria resolvido a partir da pasta do hook, nao da pasta do aluno.
|
|
53
|
+
if (-not [IO.Path]::IsPathRooted($folder)) { throw 'caminho nao absoluto' }
|
|
54
|
+
$acl = [Security.AccessControl.DirectorySecurity]::new()
|
|
55
|
+
$acl.SetOwner($owner)
|
|
56
|
+
$acl.SetAccessRuleProtection($true, $false)
|
|
57
|
+
foreach ($who in @($owner, $system)) {
|
|
58
|
+
$rule = [Security.AccessControl.FileSystemAccessRule]::new($who, [Security.AccessControl.FileSystemRights]::FullControl, [Security.AccessControl.InheritanceFlags]'ContainerInherit,ObjectInherit', [Security.AccessControl.PropagationFlags]::None, [Security.AccessControl.AccessControlType]::Allow)
|
|
59
|
+
[void]$acl.AddAccessRule($rule)
|
|
60
|
+
}
|
|
61
|
+
[void][IO.Directory]::SetAccessControl($folder, $acl)
|
|
62
|
+
$count++
|
|
63
|
+
}
|
|
64
|
+
if ($count -eq 0) { throw 'nenhuma pasta recebida' }
|
|
65
|
+
[Console]::Write("ok $count")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function Import-DpapiAssembly {
|
|
69
|
+
[void][Reflection.Assembly]::Load('System.Security, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function New-ProtectedKey {
|
|
73
|
+
Import-DpapiAssembly
|
|
74
|
+
$key = [byte[]]::new(32)
|
|
75
|
+
$random = [Security.Cryptography.RandomNumberGenerator]::Create()
|
|
76
|
+
try {
|
|
77
|
+
$random.GetBytes($key)
|
|
78
|
+
$wrapped = [Security.Cryptography.ProtectedData]::Protect($key, $null, [Security.Cryptography.DataProtectionScope]::CurrentUser)
|
|
79
|
+
[Console]::Write((ConvertTo-Hex $wrapped))
|
|
80
|
+
} finally {
|
|
81
|
+
$random.Dispose()
|
|
82
|
+
[Array]::Clear($key, 0, $key.Length)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function Open-ProtectedKey([string]$Text) {
|
|
87
|
+
Import-DpapiAssembly
|
|
88
|
+
$wrapped = ConvertFrom-Hex $Text.Trim()
|
|
89
|
+
$key = [Security.Cryptography.ProtectedData]::Unprotect($wrapped, $null, [Security.Cryptography.DataProtectionScope]::CurrentUser)
|
|
90
|
+
try {
|
|
91
|
+
[Console]::Write((ConvertTo-Hex $key))
|
|
92
|
+
} finally {
|
|
93
|
+
[Array]::Clear($key, 0, $key.Length)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
switch ($Action) {
|
|
98
|
+
'protect' { Protect-Folders (Get-StdinText) }
|
|
99
|
+
'create-key' { New-ProtectedKey }
|
|
100
|
+
'open-key' { Open-ProtectedKey (Get-StdinText) }
|
|
101
|
+
default { throw 'acao desconhecida' }
|
|
102
|
+
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## 2.6.7 — 2026-10-16
|
|
2
|
+
|
|
3
|
+
### Antivírus Kaspersky não bloqueia mais a atualização no Windows
|
|
4
|
+
|
|
5
|
+
- Uma aluna com Kaspersky não conseguiu atualizar: o antivírus barrou o arquivo `security.cjs` do cofre local da captura (alerta "Cavalo de Troia", análise heurística) e a instalação parou. Era falso positivo, mas o jeito de chamar o PowerShell lembrava um programa malicioso: o comando ia embaralhado em base64, e o mesmo arquivo trocava permissões de pasta.
|
|
6
|
+
- Agora o PowerShell roda um script legível e versionado, `.claude/hooks/ahi/win-vault.ps1`, chamado por arquivo. Caminhos e dados vão por entrada padrão, nada embaralhado na linha de comando. O arquivo tem comentário dizendo tudo o que faz: deixa as pastas do AHI privadas (dono + SISTEMA, sem herança) e protege a chave local com o DPAPI do Windows. Não usa rede, não baixa nada e não executa texto de fora.
|
|
7
|
+
- Segurança igual à anterior: mesma permissão privada, mesma proteção DPAPI do usuário atual, limite de 5 segundos e falha fechada (sem cofre, nada é gravado). O `key.dpapi` mantém o formato; quem já tinha chave guardada continua abrindo e a chave nova abre nas versões antigas.
|
|
8
|
+
- A manutenção (`atualiza o sistema`) leva o `win-vault.ps1` junto, antes do `security.cjs`, e o inventário de integridade passa a exigi-lo.
|
|
9
|
+
- Testes novos (`tests/ahi-windows-vault.test.mjs`): nenhum arquivo publicado pode voltar a usar comando do PowerShell em base64; o script é ASCII, sem rede e sem base64; o contrato com o PowerShell (argumentos, pasta, entrada, falha fechada); no Windows de verdade, compatibilidade do `key.dpapi` nos dois sentidos e pastas com apóstrofo, colchetes e acento.
|
|
10
|
+
- Mantem `arcane-mcp@0.7.0`. Instaladores do repositorio preparados para 2.6.7.
|
|
11
|
+
|
|
1
12
|
## 2.6.6 — 2026-10-03
|
|
2
13
|
|
|
3
14
|
### Squad Edição Euriliana no Pack
|
package/README.md
CHANGED
|
@@ -246,13 +246,13 @@ agents/ → Seu exercito (companion, workers, minds, squads)
|
|
|
246
246
|
|
|
247
247
|
## Observabilidade e integridade do motor
|
|
248
248
|
|
|
249
|
-
Apos publicacao, `npx -y auroq-os@2.6.
|
|
250
|
-
manutencao/diagnostico. `npx -y auroq-os@2.6.
|
|
249
|
+
Apos publicacao, `npx -y auroq-os@2.6.7 doctor` mostra a fila local segura e os comandos pinados de
|
|
250
|
+
manutencao/diagnostico. `npx -y auroq-os@2.6.7 manutencao` preserva o caminho existente
|
|
251
251
|
de consentimento legado, studentFiles, customizacoes e dados do negocio. Nao ha
|
|
252
252
|
migracao forcada. O registro MCP aponta para `arcane-mcp@0.7.0` — o pin mora no campo
|
|
253
253
|
`arcaneMcpPin` do package.json (fonte unica; o servidor le o mesmo campo no npm).
|
|
254
254
|
`npm run release:verify` recusa publicar com um pin que o npm nao resolva.
|
|
255
|
-
Os instaladores do repositorio ficam preparados para 2.6.
|
|
255
|
+
Os instaladores do repositorio ficam preparados para 2.6.7; as copias publicas so recebem
|
|
256
256
|
a versao apos os gates nativos e a verificacao do tarball publicado.
|
|
257
257
|
O pacote inclui `lib/runtime-integrity.json`: inventario SHA-256 gerado depois da montagem da casca, verificavel com `npm run integrity:verify` dentro do pacote extraido. Ele comprova consistencia do conteudo; nao e uma assinatura de autoria.
|
|
258
258
|
|
package/bin/auroq-os.js
CHANGED
|
@@ -1749,6 +1749,7 @@ async function init({ pasta = null, maintenance = false, modoAgente = false, mig
|
|
|
1749
1749
|
// Hooks
|
|
1750
1750
|
'.claude/hooks/synapse-engine.cjs',
|
|
1751
1751
|
'.claude/hooks/ahi-capture.cjs',
|
|
1752
|
+
'.claude/hooks/ahi/win-vault.ps1', // 2.6.7 — antes do security.cjs, que o chama
|
|
1752
1753
|
'.claude/hooks/ahi/security.cjs',
|
|
1753
1754
|
'.claude/hooks/ahi/runtime.cjs',
|
|
1754
1755
|
'.claude/hooks/ahi/worker.cjs',
|
package/installer/install.ps1
CHANGED
|
@@ -589,7 +589,7 @@ function Invoke-AuroqLogin {
|
|
|
589
589
|
# sucesso. Usado como pergunta de sim/nao, ele dizia "voce ja estava logado"
|
|
590
590
|
# pra quem nunca logou, pulava o portao e deixava a conta sem acesso chegar ate
|
|
591
591
|
# a criacao da pasta.
|
|
592
|
-
& (Get-AuroqNpx) -y auroq-os@2.6.
|
|
592
|
+
& (Get-AuroqNpx) -y auroq-os@2.6.7 check-access
|
|
593
593
|
if ($LASTEXITCODE -ne 0) {
|
|
594
594
|
Write-AuroqLog 'login' 'recusado'
|
|
595
595
|
Write-Host ''
|
|
@@ -677,7 +677,7 @@ function Invoke-AuroqInit {
|
|
|
677
677
|
if ($env:AUROQ_INSTALL_DRY_RUN) { Write-AuroqOk 'Sistema - seria montado aqui'; return }
|
|
678
678
|
|
|
679
679
|
Set-Location $script:AuroqPastaAlvo
|
|
680
|
-
& (Get-AuroqNpx) -y auroq-os@2.6.
|
|
680
|
+
& (Get-AuroqNpx) -y auroq-os@2.6.7 instalar
|
|
681
681
|
if ($LASTEXITCODE -ne 0) {
|
|
682
682
|
Write-AuroqLog 'init' 'falhou'
|
|
683
683
|
Write-AuroqErro 'A montagem do sistema parou no meio.' 'Rode o mesmo comando de novo. Se repetir, mande pro suporte o arquivo install.log da pasta .auroq.'
|
package/installer/install.sh
CHANGED
|
@@ -219,9 +219,9 @@ auroq_tem_terminal() {
|
|
|
219
219
|
# recebem pedaco do script no lugar da resposta dele.
|
|
220
220
|
auroq_npx_aluno() {
|
|
221
221
|
if [ -t 0 ]; then
|
|
222
|
-
npx -y auroq-os@2.6.
|
|
222
|
+
npx -y auroq-os@2.6.7 "$@"
|
|
223
223
|
else
|
|
224
|
-
npx -y auroq-os@2.6.
|
|
224
|
+
npx -y auroq-os@2.6.7 "$@" < /dev/tty
|
|
225
225
|
fi
|
|
226
226
|
}
|
|
227
227
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schema": 1,
|
|
3
3
|
"algorithm": "sha256",
|
|
4
4
|
"scope": "packaged-runtime-and-cascas",
|
|
5
|
-
"version": "2.6.
|
|
5
|
+
"version": "2.6.7",
|
|
6
6
|
"files": [
|
|
7
7
|
{
|
|
8
8
|
"path": ".auroq-core/constitution.md",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
"path": ".auroq-core/core-config.yaml",
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "59ba6487bbd6defc87ccda7d174503c8d18af089e284b61cb6f202a646778548"
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"path": ".auroq-core/core/synapse/context/context-builder.js",
|
|
@@ -178,7 +178,11 @@
|
|
|
178
178
|
},
|
|
179
179
|
{
|
|
180
180
|
"path": ".claude/hooks/ahi/security.cjs",
|
|
181
|
-
"sha256": "
|
|
181
|
+
"sha256": "ff8b4f7069a0a17506e4ea75aeb6cc0fd40cebe350e2e59048372380f059a3c6"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"path": ".claude/hooks/ahi/win-vault.ps1",
|
|
185
|
+
"sha256": "b3332d3387ed2eea0990bd589cdf0213623e7e30b4daa187bb2930948bbdac29"
|
|
182
186
|
},
|
|
183
187
|
{
|
|
184
188
|
"path": ".claude/hooks/ahi/worker.cjs",
|
|
@@ -206,7 +210,7 @@
|
|
|
206
210
|
},
|
|
207
211
|
{
|
|
208
212
|
"path": "bin/auroq-os.js",
|
|
209
|
-
"sha256": "
|
|
213
|
+
"sha256": "3ba86acde44b47edfae407e328500855eaa86281b0429da1717d517a0a7e56a3"
|
|
210
214
|
},
|
|
211
215
|
{
|
|
212
216
|
"path": "lib/ahi/client.mjs",
|
|
@@ -258,7 +262,7 @@
|
|
|
258
262
|
},
|
|
259
263
|
{
|
|
260
264
|
"path": "package.json",
|
|
261
|
-
"sha256": "
|
|
265
|
+
"sha256": "cce3f831abf590fffbd6af0356a6b0899b17ba7f8a5964dcfced577637e3be19"
|
|
262
266
|
},
|
|
263
267
|
{
|
|
264
268
|
"path": "scripts/build-pack.mjs",
|
|
@@ -278,7 +282,7 @@
|
|
|
278
282
|
},
|
|
279
283
|
{
|
|
280
284
|
"path": "scripts/runtime-integrity.mjs",
|
|
281
|
-
"sha256": "
|
|
285
|
+
"sha256": "d2d6d30fa3a288c019a4f7e6d06ba25742775258e5631466e48a9aeb55d030c5"
|
|
282
286
|
},
|
|
283
287
|
{
|
|
284
288
|
"path": "scripts/sync-codex-skills.mjs",
|
package/package.json
CHANGED
|
@@ -27,7 +27,7 @@ export const REQUIRED_FILES = [
|
|
|
27
27
|
'.claude/commands/AuroqOS/agents/ops.md', '.claude/commands/auroq-companion.md',
|
|
28
28
|
'.claude/hooks/synapse-engine.cjs', '.claude/hooks/auroq-sync.cjs',
|
|
29
29
|
'.claude/hooks/ahi-capture.cjs', '.claude/hooks/ahi/runtime.cjs',
|
|
30
|
-
'.claude/hooks/ahi/security.cjs', '.claude/hooks/ahi/worker.cjs', '.claude/hooks/ahi/auth-config.cjs',
|
|
30
|
+
'.claude/hooks/ahi/security.cjs', '.claude/hooks/ahi/win-vault.ps1', '.claude/hooks/ahi/worker.cjs', '.claude/hooks/ahi/auth-config.cjs',
|
|
31
31
|
];
|
|
32
32
|
const PROTECTED_TREES = ['lib/', '.auroq-core/core/synapse/', '.claude/hooks/'];
|
|
33
33
|
const FORBIDDEN_PREFIXES = ['business/', 'docs/knowledge/', 'casca/', '.git/', '.pack-casca-backup/'];
|