From a46215e44d3b6c24f64a5eec16540134a26d8cd6 Mon Sep 17 00:00:00 2001 From: rafaelscosta Date: Thu, 7 May 2026 12:58:08 -0300 Subject: [PATCH] fix: make Claude hook settings shell neutral --- .claude/settings.json | 6 +-- ...23.16-claude-hook-settings-windows-path.md | 44 +++++++++++++++++++ package-lock.json | 4 +- package.json | 2 +- tests/claude/subagent-governance.test.js | 20 +++++++++ 5 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 docs/stories/epic-123/STORY-123.16-claude-hook-settings-windows-path.md diff --git a/.claude/settings.json b/.claude/settings.json index 5913146edb..e90f019a4c 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -5,7 +5,7 @@ "hooks": [ { "type": "command", - "command": "node \"${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/synapse-wrapper.cjs\"", + "command": "node .claude/hooks/synapse-wrapper.cjs", "timeout": 10 } ] @@ -16,7 +16,7 @@ "hooks": [ { "type": "command", - "command": "node \"${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/precompact-wrapper.cjs\"", + "command": "node .claude/hooks/precompact-wrapper.cjs", "timeout": 10 } ] @@ -28,7 +28,7 @@ "hooks": [ { "type": "command", - "command": "node \"${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/enforce-git-push-authority.cjs\"", + "command": "node .claude/hooks/enforce-git-push-authority.cjs", "timeout": 10 } ] diff --git a/docs/stories/epic-123/STORY-123.16-claude-hook-settings-windows-path.md b/docs/stories/epic-123/STORY-123.16-claude-hook-settings-windows-path.md new file mode 100644 index 0000000000..dbfee3f362 --- /dev/null +++ b/docs/stories/epic-123/STORY-123.16-claude-hook-settings-windows-path.md @@ -0,0 +1,44 @@ +# STORY-123.16: Tornar comandos de hooks Claude shell-neutral + +## Status + +Done + +## Story + +Como mantenedor do AIOX Core em Windows e Unix, quero que o `.claude/settings.json` versionado registre hooks com caminhos relativos neutros de shell, para que o Claude Code não dependa de expansão POSIX como `${CLAUDE_PROJECT_DIR:-.}` ao executar os hooks do próprio repositório. + +## Acceptance Criteria + +- [x] AC1. Os comandos versionados de `UserPromptSubmit`, `PreCompact` e `PreToolUse` não usam `CLAUDE_PROJECT_DIR` nem expansão `${...}`. +- [x] AC2. Os hooks continuam apontando para os wrappers commitados em `.claude/hooks/`. +- [x] AC3. A governança de `PreToolUse` continua registrando o matcher `Bash`. +- [x] AC4. Há teste automatizado impedindo regressão para sintaxe dependente de shell POSIX. + +## Tasks + +- [x] Atualizar `.claude/settings.json`. +- [x] Adicionar teste de comando shell-neutral em `tests/claude/subagent-governance.test.js`. +- [x] Rodar validação focada de governança Claude. + +## Dev Notes + +- Este é o resíduo limpo do PR #551: o installer já usa caminho absoluto em Windows porque `$CLAUDE_PROJECT_DIR` é problemático nessa plataforma, mas o settings commitado ainda usava `${CLAUDE_PROJECT_DIR:-.}`. +- Mantivemos os wrappers atuais (`synapse-wrapper.cjs` e `precompact-wrapper.cjs`) em vez de voltar para os hooks diretos do PR antigo. + +## Validation + +- `npm test -- tests/claude/subagent-governance.test.js --runInBand --forceExit` -> PASS. +- `git diff --check` -> PASS. +- `npm run validate:semantic-lint` -> PASS. +- `npm run validate:manifest` -> PASS. +- `npm run validate:publish` -> PASS. +- `npm run lint -- --quiet` -> PASS. +- `npm run typecheck` -> PASS. +- `npm run test:ci` -> PASS, 315 suites / 7.847 tests, 149 skipped. + +## File List + +- `.claude/settings.json` +- `tests/claude/subagent-governance.test.js` +- `docs/stories/epic-123/STORY-123.16-claude-hook-settings-windows-path.md` diff --git a/package-lock.json b/package-lock.json index d42108b295..47cea1752f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@aiox-squads/core", - "version": "5.1.10", + "version": "5.1.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@aiox-squads/core", - "version": "5.1.10", + "version": "5.1.11", "license": "MIT", "workspaces": [ "packages/*" diff --git a/package.json b/package.json index 30db1c79d2..145c3e8ffb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@aiox-squads/core", - "version": "5.1.10", + "version": "5.1.11", "description": "Synkra AIOX: AI-Orchestrated System for Full Stack Development - Core Framework", "bin": { "aiox": "bin/aiox.js", diff --git a/tests/claude/subagent-governance.test.js b/tests/claude/subagent-governance.test.js index c0bb0a9ee4..a2a5018b05 100644 --- a/tests/claude/subagent-governance.test.js +++ b/tests/claude/subagent-governance.test.js @@ -69,6 +69,26 @@ describe('Claude native subagent governance', () => { expect(preToolUse.some(entry => entry.matcher === 'Bash')).toBe(true); }); + it('uses shell-neutral project hook commands in committed Claude settings', () => { + const settings = JSON.parse(fs.readFileSync(path.join(repoRoot, '.claude', 'settings.json'), 'utf8')); + const commands = Object.values(settings.hooks || {}) + .flat() + .flatMap(entry => entry.hooks || []) + .map(hook => hook.command); + + expect(commands).toEqual(expect.arrayContaining([ + 'node .claude/hooks/synapse-wrapper.cjs', + 'node .claude/hooks/precompact-wrapper.cjs', + 'node .claude/hooks/enforce-git-push-authority.cjs', + ])); + + for (const command of commands) { + expect(command).not.toContain('CLAUDE_PROJECT_DIR'); + expect(command).not.toContain('${'); + expect(command).toMatch(/^node \.claude\/hooks\/[-a-z]+\.cjs$/); + } + }); + it('blocks remote GitHub operations outside devops and allows devops-tagged commands', () => { const blockedCommands = [ 'git push origin main',