diff --git a/.aiox-core/install-manifest.yaml b/.aiox-core/install-manifest.yaml index 1f6e605432..9c36232447 100644 --- a/.aiox-core/install-manifest.yaml +++ b/.aiox-core/install-manifest.yaml @@ -7,8 +7,8 @@ # - SHA256 hashes for change detection # - File types for categorization # -version: 5.1.4 -generated_at: "2026-05-07T11:20:24.845Z" +version: 5.1.5 +generated_at: "2026-05-07T11:58:48.672Z" generator: scripts/generate-install-manifest.js file_count: 1103 files: diff --git a/.claude/agents/aiox-analyst.md b/.claude/agents/aiox-analyst.md index 5ce954cb1e..2bdbe04d36 100644 --- a/.claude/agents/aiox-analyst.md +++ b/.claude/agents/aiox-analyst.md @@ -17,13 +17,14 @@ permissionMode: bypassPermissions memory: project hooks: PreToolUse: - - matcher: "Bash" + - matcher: Bash hooks: - type: command - command: ".claude/hooks/enforce-git-push-authority.sh" + command: node .claude/hooks/enforce-git-push-authority.cjs skills: - synapse:tasks:diagnose-synapse - tech-search +color: cyan --- # AIOX Analyst - Autonomous Agent diff --git a/.claude/agents/aiox-architect.md b/.claude/agents/aiox-architect.md index d8f2dc9186..484b435ab1 100644 --- a/.claude/agents/aiox-architect.md +++ b/.claude/agents/aiox-architect.md @@ -17,13 +17,14 @@ permissionMode: bypassPermissions memory: project hooks: PreToolUse: - - matcher: "Bash" + - matcher: Bash hooks: - type: command - command: ".claude/hooks/enforce-git-push-authority.sh" + command: node .claude/hooks/enforce-git-push-authority.cjs skills: - synapse:tasks:diagnose-synapse - architect-first +color: purple --- # AIOX Architect - Autonomous Agent diff --git a/.claude/agents/aiox-data-engineer.md b/.claude/agents/aiox-data-engineer.md index 83c185e6d2..f4f003f305 100644 --- a/.claude/agents/aiox-data-engineer.md +++ b/.claude/agents/aiox-data-engineer.md @@ -15,13 +15,14 @@ permissionMode: bypassPermissions memory: project hooks: PreToolUse: - - matcher: "Bash" + - matcher: Bash hooks: - type: command - command: ".claude/hooks/enforce-git-push-authority.sh" + command: node .claude/hooks/enforce-git-push-authority.cjs skills: - synapse:tasks:diagnose-synapse - checklist-runner +color: blue --- # AIOX Data Engineer - Autonomous Agent diff --git a/.claude/agents/aiox-dev.md b/.claude/agents/aiox-dev.md index db0f7b2ed8..49f7375657 100644 --- a/.claude/agents/aiox-dev.md +++ b/.claude/agents/aiox-dev.md @@ -12,19 +12,19 @@ tools: - Write - Edit - Bash - - Task permissionMode: bypassPermissions memory: project hooks: PreToolUse: - - matcher: "Bash" + - matcher: Bash hooks: - type: command - command: ".claude/hooks/enforce-git-push-authority.sh" + command: node .claude/hooks/enforce-git-push-authority.cjs skills: - synapse:tasks:diagnose-synapse - coderabbit-review - checklist-runner +color: green --- # AIOX Developer - Autonomous Agent diff --git a/.claude/agents/aiox-devops.md b/.claude/agents/aiox-devops.md index 35917bc916..8d6210f73d 100644 --- a/.claude/agents/aiox-devops.md +++ b/.claude/agents/aiox-devops.md @@ -18,6 +18,7 @@ skills: - synapse:manager - coderabbit-review - checklist-runner +color: orange --- # AIOX DevOps - Autonomous Agent diff --git a/.claude/agents/aiox-pm.md b/.claude/agents/aiox-pm.md index 00ee5a054d..47aa2bd2bb 100644 --- a/.claude/agents/aiox-pm.md +++ b/.claude/agents/aiox-pm.md @@ -15,13 +15,14 @@ permissionMode: bypassPermissions memory: project hooks: PreToolUse: - - matcher: "Bash" + - matcher: Bash hooks: - type: command - command: ".claude/hooks/enforce-git-push-authority.sh" + command: node .claude/hooks/enforce-git-push-authority.cjs skills: - synapse:tasks:diagnose-synapse - checklist-runner +color: pink --- # AIOX Project Manager - Autonomous Agent diff --git a/.claude/agents/aiox-po.md b/.claude/agents/aiox-po.md index 868f1afe7e..5e3ad06ab5 100644 --- a/.claude/agents/aiox-po.md +++ b/.claude/agents/aiox-po.md @@ -15,13 +15,14 @@ permissionMode: bypassPermissions memory: project hooks: PreToolUse: - - matcher: "Bash" + - matcher: Bash hooks: - type: command - command: ".claude/hooks/enforce-git-push-authority.sh" + command: node .claude/hooks/enforce-git-push-authority.cjs skills: - synapse:tasks:diagnose-synapse - checklist-runner +color: yellow --- # AIOX Product Owner - Autonomous Agent diff --git a/.claude/agents/aiox-qa.md b/.claude/agents/aiox-qa.md index 727ca3a010..4e4e526f06 100644 --- a/.claude/agents/aiox-qa.md +++ b/.claude/agents/aiox-qa.md @@ -15,14 +15,15 @@ permissionMode: bypassPermissions memory: project hooks: PreToolUse: - - matcher: "Bash" + - matcher: Bash hooks: - type: command - command: ".claude/hooks/enforce-git-push-authority.sh" + command: node .claude/hooks/enforce-git-push-authority.cjs skills: - synapse:tasks:diagnose-synapse - coderabbit-review - checklist-runner +color: red --- # AIOX QA - Autonomous Agent diff --git a/.claude/agents/aiox-sm.md b/.claude/agents/aiox-sm.md index 5b4c861e56..ed2f2e5af6 100644 --- a/.claude/agents/aiox-sm.md +++ b/.claude/agents/aiox-sm.md @@ -15,13 +15,14 @@ permissionMode: bypassPermissions memory: project hooks: PreToolUse: - - matcher: "Bash" + - matcher: Bash hooks: - type: command - command: ".claude/hooks/enforce-git-push-authority.sh" + command: node .claude/hooks/enforce-git-push-authority.cjs skills: - synapse:tasks:diagnose-synapse - checklist-runner +color: cyan --- # AIOX Scrum Master - Autonomous Agent diff --git a/.claude/agents/aiox-ux.md b/.claude/agents/aiox-ux.md index 053fd33b09..e317a18cf6 100644 --- a/.claude/agents/aiox-ux.md +++ b/.claude/agents/aiox-ux.md @@ -15,13 +15,14 @@ permissionMode: bypassPermissions memory: project hooks: PreToolUse: - - matcher: "Bash" + - matcher: Bash hooks: - type: command - command: ".claude/hooks/enforce-git-push-authority.sh" + command: node .claude/hooks/enforce-git-push-authority.cjs skills: - synapse:tasks:diagnose-synapse - checklist-runner +color: purple --- # AIOX UX Design Expert - Autonomous Agent diff --git a/.claude/agents/brad-frost.md b/.claude/agents/brad-frost.md index 8b8f964729..00c037c001 100644 --- a/.claude/agents/brad-frost.md +++ b/.claude/agents/brad-frost.md @@ -1,7 +1,8 @@ --- name: brad-frost -description: | - design/brad-frost: Use for complete design system workflow - brownfield audit, pattern consolidation, token extraction, migration planning, component building, or greenfield setup +description: > + design/brad-frost: Use for complete design system workflow - brownfield audit, pattern + consolidation, token extraction, migration planning, component building, or greenfield setup model: sonnet tools: - Read @@ -12,9 +13,15 @@ tools: - Bash - WebSearch - WebFetch - - Task permissionMode: bypassPermissions memory: project +color: green +hooks: + PreToolUse: + - matcher: Bash + hooks: + - type: command + command: node .claude/hooks/enforce-git-push-authority.cjs --- # Brad Frost - Design Squad diff --git a/.claude/agents/copy-chief.md b/.claude/agents/copy-chief.md index 6e9440a75c..e3b5f79fd9 100644 --- a/.claude/agents/copy-chief.md +++ b/.claude/agents/copy-chief.md @@ -15,6 +15,13 @@ tools: - WebFetch permissionMode: bypassPermissions memory: project +color: pink +hooks: + PreToolUse: + - matcher: Bash + hooks: + - type: command + command: node .claude/hooks/enforce-git-push-authority.cjs --- # Copy Chief - Autonomous Agent diff --git a/.claude/agents/cyber-chief.md b/.claude/agents/cyber-chief.md index 090c84da02..bfe862002f 100644 --- a/.claude/agents/cyber-chief.md +++ b/.claude/agents/cyber-chief.md @@ -15,6 +15,13 @@ tools: - WebFetch permissionMode: bypassPermissions memory: project +color: red +hooks: + PreToolUse: + - matcher: Bash + hooks: + - type: command + command: node .claude/hooks/enforce-git-push-authority.cjs --- # Cyber Chief - Autonomous Agent diff --git a/.claude/agents/dan-mall.md b/.claude/agents/dan-mall.md index c90a9a1f45..ba799322a4 100644 --- a/.claude/agents/dan-mall.md +++ b/.claude/agents/dan-mall.md @@ -1,7 +1,8 @@ --- name: dan-mall -description: | - design/dan-mall: Use for design system adoption - stakeholder buy-in, ROI calculation, shock reports, adoption narrative, documentation +description: > + design/dan-mall: Use for design system adoption - stakeholder buy-in, ROI calculation, shock + reports, adoption narrative, documentation model: sonnet tools: - Read @@ -14,6 +15,13 @@ tools: - WebFetch permissionMode: bypassPermissions memory: project +color: cyan +hooks: + PreToolUse: + - matcher: Bash + hooks: + - type: command + command: node .claude/hooks/enforce-git-push-authority.cjs --- # Dan Mall - Design Squad diff --git a/.claude/agents/data-chief.md b/.claude/agents/data-chief.md index 1fa93444b0..8a38c802d0 100644 --- a/.claude/agents/data-chief.md +++ b/.claude/agents/data-chief.md @@ -15,6 +15,13 @@ tools: - WebFetch permissionMode: bypassPermissions memory: project +color: blue +hooks: + PreToolUse: + - matcher: Bash + hooks: + - type: command + command: node .claude/hooks/enforce-git-push-authority.cjs --- # Data Chief - Autonomous Agent diff --git a/.claude/agents/dave-malouf.md b/.claude/agents/dave-malouf.md index d6a4a42666..af35e30433 100644 --- a/.claude/agents/dave-malouf.md +++ b/.claude/agents/dave-malouf.md @@ -1,7 +1,8 @@ --- name: dave-malouf -description: | - design/dave-malouf: Use for DesignOps - maturity assessment, process optimization, metrics setup, team scaling, tooling audit, triage, review orchestration +description: > + design/dave-malouf: Use for DesignOps - maturity assessment, process optimization, metrics setup, + team scaling, tooling audit, triage, review orchestration model: sonnet tools: - Read @@ -14,6 +15,13 @@ tools: - WebFetch permissionMode: bypassPermissions memory: project +color: purple +hooks: + PreToolUse: + - matcher: Bash + hooks: + - type: command + command: node .claude/hooks/enforce-git-push-authority.cjs --- # Dave Malouf - Design Squad diff --git a/.claude/agents/db-sage.md b/.claude/agents/db-sage.md index 426a354df0..b18a896847 100644 --- a/.claude/agents/db-sage.md +++ b/.claude/agents/db-sage.md @@ -13,6 +13,13 @@ tools: - Bash permissionMode: bypassPermissions memory: project +color: blue +hooks: + PreToolUse: + - matcher: Bash + hooks: + - type: command + command: node .claude/hooks/enforce-git-push-authority.cjs --- # DB Sage - Autonomous Agent diff --git a/.claude/agents/design-chief.md b/.claude/agents/design-chief.md index 2056bb2ceb..a05aa434fd 100644 --- a/.claude/agents/design-chief.md +++ b/.claude/agents/design-chief.md @@ -15,6 +15,13 @@ tools: - WebFetch permissionMode: bypassPermissions memory: project +color: purple +hooks: + PreToolUse: + - matcher: Bash + hooks: + - type: command + command: node .claude/hooks/enforce-git-push-authority.cjs --- # Design Chief - Autonomous Agent diff --git a/.claude/agents/design-system.md b/.claude/agents/design-system.md index 8ba4886497..638d1db306 100644 --- a/.claude/agents/design-system.md +++ b/.claude/agents/design-system.md @@ -11,9 +11,15 @@ tools: - Write - Edit - Bash - - Task permissionMode: bypassPermissions memory: project +color: green +hooks: + PreToolUse: + - matcher: Bash + hooks: + - type: command + command: node .claude/hooks/enforce-git-push-authority.cjs --- # Design System (Brad Frost) - Autonomous Agent diff --git a/.claude/agents/legal-chief.md b/.claude/agents/legal-chief.md index b0f1df9d57..bf901d5e52 100644 --- a/.claude/agents/legal-chief.md +++ b/.claude/agents/legal-chief.md @@ -15,6 +15,13 @@ tools: - WebFetch permissionMode: bypassPermissions memory: project +color: yellow +hooks: + PreToolUse: + - matcher: Bash + hooks: + - type: command + command: node .claude/hooks/enforce-git-push-authority.cjs --- # Legal Chief - Autonomous Agent diff --git a/.claude/agents/nano-banana-generator.md b/.claude/agents/nano-banana-generator.md index 8b6a1d90cc..da9b6d1950 100644 --- a/.claude/agents/nano-banana-generator.md +++ b/.claude/agents/nano-banana-generator.md @@ -1,7 +1,8 @@ --- name: nano-banana-generator -description: | - design/nano-banana-generator: Use for visual artifact generation - thumbnails, icons, illustrations, AI image prompts, brand-aligned assets +description: > + design/nano-banana-generator: Use for visual artifact generation - thumbnails, icons, + illustrations, AI image prompts, brand-aligned assets model: haiku tools: - Read @@ -14,6 +15,13 @@ tools: - WebFetch permissionMode: bypassPermissions memory: project +color: orange +hooks: + PreToolUse: + - matcher: Bash + hooks: + - type: command + command: node .claude/hooks/enforce-git-push-authority.cjs --- # Nano Banana Generator - Design Squad diff --git a/.claude/agents/oalanicolas.md b/.claude/agents/oalanicolas.md index 96ee334c10..003747a336 100644 --- a/.claude/agents/oalanicolas.md +++ b/.claude/agents/oalanicolas.md @@ -3,9 +3,7 @@ name: oalanicolas description: | Mind cloning architect. Expert in Voice DNA and Thinking DNA extraction. Captures mental models, communication patterns, and frameworks from elite minds. - model: opus - tools: - Read - Grep @@ -13,14 +11,12 @@ tools: - WebFetch - Write - Edit - disallowedTools: - Bash - Task - permissionMode: acceptEdits - memory: project +color: cyan --- # 🧬 @oalanicolas - Mind Cloning Architect diff --git a/.claude/agents/pedro-valerio.md b/.claude/agents/pedro-valerio.md index d5b4875357..5da9abb957 100644 --- a/.claude/agents/pedro-valerio.md +++ b/.claude/agents/pedro-valerio.md @@ -3,17 +3,14 @@ name: pedro-valerio description: | Process absolutist. Validates workflows for zero wrong paths. Audits veto conditions, unidirectional flow, and checkpoint coverage. - model: opus - tools: - Read - Grep - Glob - permissionMode: default - memory: project +color: yellow --- # 🔍 @pedro-valerio - Process Absolutist diff --git a/.claude/agents/sop-extractor.md b/.claude/agents/sop-extractor.md index fc950bc0c4..d8bf2e0b2b 100644 --- a/.claude/agents/sop-extractor.md +++ b/.claude/agents/sop-extractor.md @@ -3,17 +3,14 @@ name: sop-extractor description: | SOP extraction specialist. Extracts standard operating procedures from content, interviews, and documentation. - model: sonnet - tools: - Read - Grep - Write - permissionMode: acceptEdits - memory: project +color: blue --- # 📋 @sop-extractor - SOP Extraction Specialist diff --git a/.claude/agents/squad-chief.md b/.claude/agents/squad-chief.md index bce236b8ac..0bbc2d8760 100644 --- a/.claude/agents/squad-chief.md +++ b/.claude/agents/squad-chief.md @@ -1,3 +1,25 @@ +--- +name: squad-chief +description: Squad Creator chief for creating, upgrading, validating, and orchestrating AIOX squads. +model: opus +tools: + - Read + - Grep + - Glob + - Write + - Edit + - Bash +permissionMode: bypassPermissions +memory: project +color: orange +hooks: + PreToolUse: + - matcher: Bash + hooks: + - type: command + command: node .claude/hooks/enforce-git-push-authority.cjs +--- + # squad-chief ACTIVATION-NOTICE: This file contains your full agent operating guidelines. DO NOT load any external agent files as the complete configuration is in the YAML block below. diff --git a/.claude/agents/squad.md b/.claude/agents/squad.md index 3af6eb8c73..5e64b40d94 100644 --- a/.claude/agents/squad.md +++ b/.claude/agents/squad.md @@ -4,23 +4,19 @@ description: | Master orchestrator for squad creation. Creates teams of AI agents specialized in any domain. Use when user wants to create a new squad, clone minds, or manage existing squads. - model: opus - tools: - Read - Grep - Glob - - Task - Write - Edit - Bash - WebSearch - WebFetch - permissionMode: acceptEdits - memory: project +color: orange --- # 🎨 Squad Architect diff --git a/.claude/agents/story-chief.md b/.claude/agents/story-chief.md index 0a2875ad7e..3c6ba64bab 100644 --- a/.claude/agents/story-chief.md +++ b/.claude/agents/story-chief.md @@ -15,6 +15,13 @@ tools: - WebFetch permissionMode: bypassPermissions memory: project +color: pink +hooks: + PreToolUse: + - matcher: Bash + hooks: + - type: command + command: node .claude/hooks/enforce-git-push-authority.cjs --- # Story Chief - Autonomous Agent diff --git a/.claude/agents/tools-orchestrator.md b/.claude/agents/tools-orchestrator.md index 4c88e23f8d..5a51b3c4a0 100644 --- a/.claude/agents/tools-orchestrator.md +++ b/.claude/agents/tools-orchestrator.md @@ -15,6 +15,13 @@ tools: - WebFetch permissionMode: bypassPermissions memory: project +color: cyan +hooks: + PreToolUse: + - matcher: Bash + hooks: + - type: command + command: node .claude/hooks/enforce-git-push-authority.cjs --- # Tools Orchestrator - Autonomous Agent diff --git a/.claude/agents/traffic-masters-chief.md b/.claude/agents/traffic-masters-chief.md index 47c2b7ffa1..bfcf37426e 100644 --- a/.claude/agents/traffic-masters-chief.md +++ b/.claude/agents/traffic-masters-chief.md @@ -15,6 +15,13 @@ tools: - WebFetch permissionMode: bypassPermissions memory: project +color: orange +hooks: + PreToolUse: + - matcher: Bash + hooks: + - type: command + command: node .claude/hooks/enforce-git-push-authority.cjs --- # Traffic Masters Chief - Autonomous Agent diff --git a/.claude/hooks/README.md b/.claude/hooks/README.md index 75f44d7d7a..1fe501f570 100644 --- a/.claude/hooks/README.md +++ b/.claude/hooks/README.md @@ -16,6 +16,7 @@ PreToolUse Hooks │ → code-intel-pretool.cjs └── Bash → sql-governance.py → slug-validation.py + → enforce-git-push-authority.cjs PreCompact Hooks └── (manual+auto) → precompact-session-digest.cjs @@ -104,6 +105,19 @@ Impede criação de mind clones sem DNA extraído previamente. 1. Execute o pipeline de extração de DNA: `/squad-creator` → `*collect-sources` → `*extract-voice-dna` → `*extract-thinking-dna` 2. OU se é agent funcional, renomeie com sufixo apropriado +### 7. enforce-git-push-authority.cjs +**Trigger:** `Bash` +**Comportamento:** BLOQUEIA via `permissionDecision: deny` + +Impede operações remotas que são exclusivas do `@devops`: +- `git push` +- `gh pr create` +- `gh pr merge` + +**Exceções permitidas:** +- Sessões/comandos com `AIOX_ACTIVE_AGENT=devops` +- Alias compatíveis: `github-devops`, `aiox-devops` + ## Exit Codes | Code | Significado | @@ -150,6 +164,7 @@ Hooks são registrados em `.claude/settings.json` (framework, commitado) ou `.cl |------|--------|---------|-----------| | `synapse-engine.cjs` | `UserPromptSubmit` | — | SYNAPSE context engine | | `code-intel-pretool.cjs` | `PreToolUse` | `Write\|Edit` | Code intelligence injection | +| `enforce-git-push-authority.cjs` | `PreToolUse` | `Bash` | Agent Authority para operações remotas | | `precompact-session-digest.cjs` | `PreCompact` | — | Session digest capture | ### Exemplo de Configuração diff --git a/.claude/hooks/enforce-git-push-authority.cjs b/.claude/hooks/enforce-git-push-authority.cjs new file mode 100644 index 0000000000..b50c6e5d0f --- /dev/null +++ b/.claude/hooks/enforce-git-push-authority.cjs @@ -0,0 +1,143 @@ +#!/usr/bin/env node +'use strict'; + +/** + * Claude Code PreToolUse hook for Constitution Article II. + * + * Blocks remote Git/GitHub publication commands unless the active agent is + * @devops. The hook is intentionally dependency-free so it can run from a + * freshly installed AIOX package on macOS, Linux, WSL, and Windows. + */ + +const fs = require('fs'); + +const REMOTE_OPERATION_PATTERNS = [ + { + pattern: /\bgit\s+push\b/i, + operation: 'git push', + }, + { + pattern: /\bgh\s+pr\s+create\b/i, + operation: 'gh pr create', + }, + { + pattern: /\bgh\s+pr\s+merge\b/i, + operation: 'gh pr merge', + }, +]; + +const DEVOPS_AGENT_ALIASES = new Set([ + 'devops', + '@devops', + 'github-devops', + '@github-devops', + 'aiox-devops', + '@aiox-devops', +]); + +function readStdin() { + try { + return fs.readFileSync(0, 'utf8'); + } catch { + return ''; + } +} + +function parseInput(rawInput) { + try { + return JSON.parse(rawInput || '{}'); + } catch { + return null; + } +} + +function normalizeCommand(command) { + return String(command || '') + .replace(/\\\r?\n/g, ' ') + .replace(/\s+/g, ' ') + .trim(); +} + +function getCommandScopedAgent(command) { + const match = String(command || '').match( + /(?:^|\s)(?:export\s+)?(?:AIOX_ACTIVE_AGENT|AIOX_AGENT|ACTIVE_AGENT|CLAUDE_AGENT_NAME)=["']?(@?[a-z0-9-]+)["']?/i, + ); + + return match ? match[1].toLowerCase() : ''; +} + +function getActiveAgent(command) { + const candidates = [ + process.env.AIOX_ACTIVE_AGENT, + process.env.AIOX_AGENT, + process.env.ACTIVE_AGENT, + process.env.CLAUDE_AGENT_NAME, + process.env.CLAUDE_CODE_AGENT, + process.env.AIOX_CURRENT_AGENT, + getCommandScopedAgent(command), + ]; + + return String(candidates.find(Boolean) || '').toLowerCase(); +} + +function isDevOpsAgent(agent) { + return DEVOPS_AGENT_ALIASES.has(String(agent || '').toLowerCase()); +} + +function findRemoteOperation(command) { + const normalized = normalizeCommand(command); + return REMOTE_OPERATION_PATTERNS.find(({ pattern }) => pattern.test(normalized)) || null; +} + +function emitDecision(permissionDecision, permissionDecisionReason) { + process.stdout.write(JSON.stringify({ + hookSpecificOutput: { + hookEventName: 'PreToolUse', + permissionDecision, + permissionDecisionReason, + }, + })); +} + +function main() { + const rawInput = readStdin(); + const input = parseInput(rawInput); + + if (!input) { + emitDecision( + 'deny', + 'Hook failed to parse PreToolUse input. Blocking remote Git operation for safety; retry via @devops.', + ); + return; + } + + const command = input?.tool_input?.command || ''; + const operation = findRemoteOperation(command); + + if (!operation) { + return; + } + + const activeAgent = getActiveAgent(command); + if (isDevOpsAgent(activeAgent)) { + return; + } + + emitDecision( + 'deny', + `${operation.operation} is exclusive to @devops (Constitution Article II). Current agent: ${activeAgent || '@unknown'}.`, + ); +} + +if (require.main === module) { + main(); +} + +module.exports = { + DEVOPS_AGENT_ALIASES, + REMOTE_OPERATION_PATTERNS, + findRemoteOperation, + getActiveAgent, + isDevOpsAgent, + normalizeCommand, +}; diff --git a/.claude/hooks/enforce-git-push-authority.sh b/.claude/hooks/enforce-git-push-authority.sh index c56b7c4786..aa6a54231a 100644 --- a/.claude/hooks/enforce-git-push-authority.sh +++ b/.claude/hooks/enforce-git-push-authority.sh @@ -1,33 +1,6 @@ #!/bin/bash # enforce-git-push-authority.sh -# PreToolUse hook: blocks "git push" commands in Bash tool -# Only meant to run when agent is NOT @devops -# Uses node (not jq) for JSON parsing — works on Windows/Git Bash -# FAIL-CLOSED: if parsing fails, blocks the command (exit 2) +# Compatibility wrapper for the cross-platform CJS hook. -INPUT=$(cat) - -# Extract command from JSON using node (available on all AIOX systems) -COMMAND=$(echo "$INPUT" | node -e " - let d=''; - process.stdin.on('data',c=>d+=c); - process.stdin.on('end',()=>{ - try{console.log(JSON.parse(d).tool_input.command||'')} - catch(e){process.exit(1)} - }); -" 2>/dev/null) - -# Fail-closed: if node parsing failed, block the command -if [ $? -ne 0 ]; then - echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Hook failed to parse input — blocking for safety. Contact @devops."}}' - exit 0 -fi - -# Block git push in all forms (push, push --force, push origin, etc.) -if echo "$COMMAND" | grep -qiE '\bgit\s+push\b'; then - echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Git push is EXCLUSIVE to @devops agent. Activate @devops for push operations."}}' - exit 0 -fi - -# Allow all other commands -exit 0 +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +exec node "$SCRIPT_DIR/enforce-git-push-authority.cjs" diff --git a/.claude/settings.json b/.claude/settings.json index cb4b50dadd..5913146edb 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -21,6 +21,18 @@ } ] } + ], + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "node \"${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/enforce-git-push-authority.cjs\"", + "timeout": 10 + } + ] + } ] } } diff --git a/docs/stories/epic-123/STORY-123.7-subagent-governance-distribution.md b/docs/stories/epic-123/STORY-123.7-subagent-governance-distribution.md new file mode 100644 index 0000000000..65868d3625 --- /dev/null +++ b/docs/stories/epic-123/STORY-123.7-subagent-governance-distribution.md @@ -0,0 +1,51 @@ +# STORY-123.7: Corrigir governança e distribuição de subagents nativos Claude + +Status: Done + +Issue: #604 + +## Contexto + +O issue #604 apontou lacunas nos subagents nativos em `.claude/agents/`: hook de autoridade não registrado em runtime, frontmatter fora do formato suportado pelo Claude Code, uso inválido da ferramenta `Task` em subagents e ausência de distribuição dos subagents pelo installer. + +## Acceptance Criteria + +- [x] AC1. `.claude/settings.json` registra um hook `PreToolUse` para `Bash` capaz de bloquear operações remotas exclusivas do `@devops`. +- [x] AC2. Todos os subagents com `permissionMode: bypassPermissions` e acesso a `Bash`, exceto o próprio `aiox-devops`, carregam o hook de autoridade. +- [x] AC3. Os 29 subagents nativos têm frontmatter válido com `name`, `description` e `color`; nenhum declara `Task` em `tools`. +- [x] AC4. O installer copia `.claude/agents/` e registra o hook de autoridade em `.claude/settings.local.json`. +- [x] AC5. Há testes automatizados cobrindo schema, hook, registro em settings e distribuição pelo installer. + +## Tasks + +- [x] Criar hook cross-platform `enforce-git-push-authority.cjs`. +- [x] Atualizar `.claude/settings.json` e documentação de hooks. +- [x] Corrigir frontmatter dos subagents nativos. +- [x] Atualizar metadata/gerador do installer para copiar `.claude/agents/`. +- [x] Adicionar testes de governança e installer. +- [x] Rodar gates locais. + +## Dev Notes + +- Fonte oficial consultada: Claude Code Docs, página “Criar subagentes personalizados”. +- O campo `color` é suportado e aceita `red`, `blue`, `green`, `yellow`, `purple`, `orange`, `pink` ou `cyan`. +- Subagents não podem gerar outros subagents; por isso `Task` foi removido de `tools` nos arquivos em `.claude/agents/`. +- O `aiox-devops` fica sem o hook em frontmatter porque é o agente autorizado para operações remotas; o hook de projeto permite o mesmo fluxo quando `AIOX_ACTIVE_AGENT=devops`. +- Validações locais: `npm test -- packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js tests/claude/subagent-governance.test.js tests/unit/wizard/ide-config-generator.test.js --runInBand`, `npm run test:ci`, `npm run validate:manifest`, `npm run lint -- --quiet`, `npm run typecheck`, `npm run validate:publish`, `git diff --check`, smoke local do tarball/installer. + +## File List + +- [docs/stories/epic-123/STORY-123.7-subagent-governance-distribution.md](./STORY-123.7-subagent-governance-distribution.md) +- [.claude/agents/](../../../.claude/agents/) +- [.claude/hooks/enforce-git-push-authority.cjs](../../../.claude/hooks/enforce-git-push-authority.cjs) +- [.claude/hooks/enforce-git-push-authority.sh](../../../.claude/hooks/enforce-git-push-authority.sh) +- [.claude/hooks/README.md](../../../.claude/hooks/README.md) +- [.claude/settings.json](../../../.claude/settings.json) +- [.aiox-core/install-manifest.yaml](../../../.aiox-core/install-manifest.yaml) +- [package.json](../../../package.json) +- [package-lock.json](../../../package-lock.json) +- [packages/installer/src/config/ide-configs.js](../../../packages/installer/src/config/ide-configs.js) +- [packages/installer/src/wizard/ide-config-generator.js](../../../packages/installer/src/wizard/ide-config-generator.js) +- [packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js](../../../packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js) +- [tests/claude/subagent-governance.test.js](../../../tests/claude/subagent-governance.test.js) +- [tests/unit/wizard/ide-config-generator.test.js](../../../tests/unit/wizard/ide-config-generator.test.js) diff --git a/package-lock.json b/package-lock.json index 3be3b16448..6b0d4546fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@aiox-squads/core", - "version": "5.1.4", + "version": "5.1.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@aiox-squads/core", - "version": "5.1.4", + "version": "5.1.5", "license": "MIT", "workspaces": [ "packages/*" diff --git a/package.json b/package.json index 14e186a8e2..f808df13fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@aiox-squads/core", - "version": "5.1.4", + "version": "5.1.5", "description": "Synkra AIOX: AI-Orchestrated System for Full Stack Development - Core Framework", "bin": { "aiox": "bin/aiox.js", @@ -18,6 +18,7 @@ "packages/", ".aiox-core/", ".claude/CLAUDE.md", + ".claude/agents/", ".claude/commands/", ".claude/skills/", ".claude/rules/", diff --git a/packages/installer/src/config/ide-configs.js b/packages/installer/src/config/ide-configs.js index 50f3e38da6..d7a56f3839 100644 --- a/packages/installer/src/config/ide-configs.js +++ b/packages/installer/src/config/ide-configs.js @@ -43,6 +43,7 @@ const IDE_CONFIGS = { format: 'text', recommended: true, agentFolder: path.join('.claude', 'commands', 'AIOX', 'agents'), + nativeAgentFolder: path.join('.claude', 'agents'), }, codex: { name: 'Codex CLI', diff --git a/packages/installer/src/wizard/ide-config-generator.js b/packages/installer/src/wizard/ide-config-generator.js index fbf35e8410..cc3147a846 100644 --- a/packages/installer/src/wizard/ide-config-generator.js +++ b/packages/installer/src/wizard/ide-config-generator.js @@ -548,6 +548,16 @@ async function generateIDEConfigs(selectedIDEs, wizardState, options = {}) { // For Claude Code, also copy .claude/rules folder, hooks, and settings if (ideKey === 'claude-code') { + spinner.start('Copying Claude Code native subagents...'); + const nativeAgentFiles = await copyClaudeNativeAgentsFolder(projectRoot); + createdFiles.push(...nativeAgentFiles); + if (nativeAgentFiles.length > 0) { + createdFolders.push(path.join(projectRoot, ide.nativeAgentFolder || '.claude/agents')); + spinner.succeed(`Copied ${nativeAgentFiles.length} native subagent file(s) to .claude/agents`); + } else { + spinner.info('No native subagent files to copy'); + } + spinner.start('Copying Claude Code rules...'); const rulesFiles = await copyClaudeRulesFolder(projectRoot); createdFiles.push(...rulesFiles); @@ -700,6 +710,7 @@ async function copyClaudeHooksFolder(projectRoot, wizardState = {}) { const HOOKS_FREE = [ 'synapse-engine.cjs', 'code-intel-pretool.cjs', + 'enforce-git-push-authority.cjs', 'README.md', ]; const HOOKS_PRO_ONLY = [ @@ -773,6 +784,11 @@ const HOOK_EVENT_MAP = { matcher: 'Write|Edit', timeout: 10, }, + 'enforce-git-push-authority.cjs': { + event: 'PreToolUse', + matcher: 'Bash', + timeout: 10, + }, 'precompact-session-digest.cjs': { event: 'PreCompact', matcher: null, @@ -787,6 +803,43 @@ const DEFAULT_HOOK_CONFIG = { timeout: 10, }; +async function copyClaudeNativeAgentsFolder(projectRoot) { + const sourceDir = resolveAioxCorePath('.claude', 'agents'); + const targetDir = path.join(projectRoot, '.claude', 'agents'); + const copiedFiles = []; + + if (!await fs.pathExists(sourceDir)) { + return copiedFiles; + } + + // Framework-dev mode: source and destination are the same checkout. + if (path.resolve(sourceDir) === path.resolve(targetDir)) { + return copiedFiles; + } + + await fs.ensureDir(targetDir); + + const files = await fs.readdir(sourceDir); + const agentFiles = files.filter(file => + file.endsWith('.md') && + !file.includes('.backup') && + !file.startsWith('test-'), + ); + + for (const file of agentFiles) { + const sourcePath = path.join(sourceDir, file); + const targetPath = path.join(targetDir, file); + const stat = await fs.stat(sourcePath); + + if (stat.isFile()) { + await fs.copy(sourcePath, targetPath, { overwrite: true }); + copiedFiles.push(targetPath); + } + } + + return copiedFiles; +} + /** * BUG-4 fix (INS-1) + MIS-3.1: Create .claude/settings.local.json with hook registration * Creates or merges hook entries into settings.local.json using HOOK_EVENT_MAP @@ -1256,6 +1309,7 @@ module.exports = { promptFileExists, generateTemplateVariables, copyClaudeHooksFolder, + copyClaudeNativeAgentsFolder, shouldCopyProHooks, createClaudeSettingsLocal, copySkillFiles, diff --git a/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js b/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js index 0128df0c66..edac2f5312 100644 --- a/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +++ b/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js @@ -190,6 +190,7 @@ describe('artifact-copy-pipeline (Story INS-4.3)', () => { expect(fileNames).toEqual(expectedAvailableHooks( 'README.md', 'code-intel-pretool.cjs', + 'enforce-git-push-authority.cjs', 'synapse-engine.cjs', )); @@ -198,8 +199,12 @@ describe('artifact-copy-pipeline (Story INS-4.3)', () => { const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); expect(settings.hooks.UserPromptSubmit).toHaveLength(1); - if (fileNames.includes('code-intel-pretool.cjs')) { - expect(settings.hooks.PreToolUse).toHaveLength(1); + const preToolUseHookCount = [ + 'code-intel-pretool.cjs', + 'enforce-git-push-authority.cjs', + ].filter(file => fileNames.includes(file)).length; + if (preToolUseHookCount > 0) { + expect(settings.hooks.PreToolUse).toHaveLength(preToolUseHookCount); } else { expect(settings.hooks.PreToolUse).toBeUndefined(); } @@ -219,6 +224,7 @@ describe('artifact-copy-pipeline (Story INS-4.3)', () => { expect(fileNames).toEqual(expectedAvailableHooks( 'README.md', 'code-intel-pretool.cjs', + 'enforce-git-push-authority.cjs', 'precompact-session-digest.cjs', 'synapse-engine.cjs', )); @@ -228,8 +234,12 @@ describe('artifact-copy-pipeline (Story INS-4.3)', () => { const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); expect(settings.hooks.UserPromptSubmit).toHaveLength(1); - if (fileNames.includes('code-intel-pretool.cjs')) { - expect(settings.hooks.PreToolUse).toHaveLength(1); + const preToolUseHookCount = [ + 'code-intel-pretool.cjs', + 'enforce-git-push-authority.cjs', + ].filter(file => fileNames.includes(file)).length; + if (preToolUseHookCount > 0) { + expect(settings.hooks.PreToolUse).toHaveLength(preToolUseHookCount); } else { expect(settings.hooks.PreToolUse).toBeUndefined(); } @@ -268,6 +278,14 @@ describe('artifact-copy-pipeline (Story INS-4.3)', () => { expect(config.timeout).toBe(10); }); + test('maps enforce-git-push-authority.cjs to PreToolUse with Bash matcher', () => { + const config = HOOK_EVENT_MAP['enforce-git-push-authority.cjs']; + expect(config).toBeDefined(); + expect(config.event).toBe('PreToolUse'); + expect(config.matcher).toBe('Bash'); + expect(config.timeout).toBe(10); + }); + test('maps precompact-session-digest.cjs to PreCompact', () => { const config = HOOK_EVENT_MAP['precompact-session-digest.cjs']; expect(config).toBeDefined(); @@ -276,11 +294,12 @@ describe('artifact-copy-pipeline (Story INS-4.3)', () => { expect(config.timeout).toBe(10); }); - test('covers all 3 known hooks', () => { + test('covers all 4 known hooks', () => { const keys = Object.keys(HOOK_EVENT_MAP); - expect(keys).toHaveLength(3); + expect(keys).toHaveLength(4); expect(keys).toContain('synapse-engine.cjs'); expect(keys).toContain('code-intel-pretool.cjs'); + expect(keys).toContain('enforce-git-push-authority.cjs'); expect(keys).toContain('precompact-session-digest.cjs'); }); diff --git a/tests/claude/subagent-governance.test.js b/tests/claude/subagent-governance.test.js new file mode 100644 index 0000000000..c0bb0a9ee4 --- /dev/null +++ b/tests/claude/subagent-governance.test.js @@ -0,0 +1,90 @@ +const fs = require('fs'); +const path = require('path'); +const { spawnSync } = require('child_process'); +const yaml = require('js-yaml'); + +const repoRoot = path.resolve(__dirname, '..', '..'); +const agentsDir = path.join(repoRoot, '.claude', 'agents'); +const authorityHookPath = path.join(repoRoot, '.claude', 'hooks', 'enforce-git-push-authority.cjs'); +const allowedColors = new Set(['red', 'blue', 'green', 'yellow', 'purple', 'orange', 'pink', 'cyan']); + +function readFrontmatter(filePath) { + const content = fs.readFileSync(filePath, 'utf8'); + const match = content.match(/^---\n([\s\S]*?)\n---\n/); + if (!match) return null; + return yaml.load(match[1]); +} + +function runAuthorityHook(command, env = {}) { + return spawnSync(process.execPath, [authorityHookPath], { + input: JSON.stringify({ + hook_event_name: 'PreToolUse', + tool_name: 'Bash', + tool_input: { command }, + }), + encoding: 'utf8', + env: { ...process.env, ...env }, + }); +} + +describe('Claude native subagent governance', () => { + it('keeps all native subagents compliant with supported frontmatter fields', () => { + const files = fs.readdirSync(agentsDir).filter(file => file.endsWith('.md')).sort(); + + expect(files).toHaveLength(29); + + for (const file of files) { + const frontmatter = readFrontmatter(path.join(agentsDir, file)); + + expect(frontmatter).toBeTruthy(); + expect(frontmatter.name).toEqual(expect.stringMatching(/^[a-z0-9-]+$/)); + expect(frontmatter.description).toBeTruthy(); + expect(allowedColors.has(frontmatter.color)).toBe(true); + expect(frontmatter.tools || []).not.toContain('Task'); + } + }); + + it('requires the remote Git authority hook for every non-devops bypass agent with Bash', () => { + const files = fs.readdirSync(agentsDir).filter(file => file.endsWith('.md')).sort(); + + for (const file of files) { + const frontmatter = readFrontmatter(path.join(agentsDir, file)); + const tools = frontmatter.tools || []; + const isNonDevopsBypassBash = + frontmatter.permissionMode === 'bypassPermissions' && + tools.includes('Bash') && + !['aiox-devops', 'devops', 'github-devops'].includes(frontmatter.name); + + if (isNonDevopsBypassBash) { + expect(JSON.stringify(frontmatter.hooks)).toContain('enforce-git-push-authority.cjs'); + } + } + }); + + it('registers the remote Git authority hook at project settings level', () => { + const settings = JSON.parse(fs.readFileSync(path.join(repoRoot, '.claude', 'settings.json'), 'utf8')); + const preToolUse = settings.hooks?.PreToolUse || []; + + expect(JSON.stringify(preToolUse)).toContain('enforce-git-push-authority.cjs'); + expect(preToolUse.some(entry => entry.matcher === 'Bash')).toBe(true); + }); + + it('blocks remote GitHub operations outside devops and allows devops-tagged commands', () => { + const blockedCommands = [ + 'git push origin main', + 'gh pr create --title test --body test', + 'gh pr merge 123 --admin', + ]; + + for (const command of blockedCommands) { + const result = runAuthorityHook(command, { AIOX_ACTIVE_AGENT: 'dev' }); + expect(result.status).toBe(0); + const decision = JSON.parse(result.stdout); + expect(decision.hookSpecificOutput.permissionDecision).toBe('deny'); + } + + const allowed = runAuthorityHook('git push origin main', { AIOX_ACTIVE_AGENT: 'devops' }); + expect(allowed.status).toBe(0); + expect(allowed.stdout).toBe(''); + }); +}); diff --git a/tests/unit/wizard/ide-config-generator.test.js b/tests/unit/wizard/ide-config-generator.test.js index b8a4800118..65d17a7c9d 100644 --- a/tests/unit/wizard/ide-config-generator.test.js +++ b/tests/unit/wizard/ide-config-generator.test.js @@ -14,6 +14,7 @@ const { generateIDEConfigs, generateCodexSkills, linkGeminiExtension, + HOOK_EVENT_MAP, } = require('../../../packages/installer/src/wizard/ide-config-generator'); describe('IDE Config Generator', () => { @@ -194,6 +195,31 @@ describe('IDE Config Generator', () => { expect(await fs.pathExists(path.join(testDir, '.gemini', 'rules', 'AIOX', 'agents'))).toBe(true); }); + it('should install Claude Code native subagents and authority hook registration', async () => { + const selectedIDEs = ['claude-code']; + const wizardState = { projectName: 'test', projectType: 'greenfield' }; + + const result = await generateIDEConfigs(selectedIDEs, wizardState, { + projectRoot: testDir, + }); + + expect(result.success).toBe(true); + expect(await fs.pathExists(path.join(testDir, '.claude', 'agents', 'aiox-dev.md'))).toBe(true); + expect(await fs.pathExists( + path.join(testDir, '.claude', 'hooks', 'enforce-git-push-authority.cjs'), + )).toBe(true); + + const settingsPath = path.join(testDir, '.claude', 'settings.local.json'); + const settings = JSON.parse(await fs.readFile(settingsPath, 'utf8')); + + expect(JSON.stringify(settings.hooks.PreToolUse)).toContain('enforce-git-push-authority.cjs'); + expect(settings.hooks.PreToolUse.some(entry => entry.matcher === 'Bash')).toBe(true); + expect(HOOK_EVENT_MAP['enforce-git-push-authority.cjs']).toMatchObject({ + event: 'PreToolUse', + matcher: 'Bash', + }); + }); + it('should create directory for IDEs that require it', async () => { const selectedIDEs = ['antigravity']; const wizardState = { projectName: 'test', projectType: 'greenfield' };