Skip to content

Commit 08110f2

Browse files
committed
feat: implement deterministic skill router using BeforeAgent hook
Gemini CLI's natively built Agent Skills feature relies on probabilistic semantic model matching for auto-activation. For Superpowers, we need guaranteed deterministic routing (e.g., 'write a plan' must always map to the writing-plans skill). This commit: - Creates agents/superpowers-router.js, a Node hook that intercepts prompts on the BeforeAgent lifecycle event, pattern-matches against Superpowers triggers, and injects strict deterministic overrides into the systemMessage. - Updates .gemini/install.sh to auto-register this hook in the user's ~/.gemini/settings.json file.
1 parent 933cfb4 commit 08110f2

File tree

2 files changed

+115
-1
lines changed

2 files changed

+115
-1
lines changed

.gemini/install.sh

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,65 @@ if [ -d "$REPO_AGENTS_DIR" ]; then
113113
echo "$agent_name"
114114
fi
115115
done
116-
else
116+
echo "No agents directory found at "$REPO_AGENTS_DIR", skipping agent linking."
117117
fi
118118

119+
# --- Register deterministic hook router in settings.json ---
120+
SETTINGS_FILE="$GEMINI_DIR/settings.json"
121+
122+
enable_router_hook() {
123+
# Ensure settings.json exists
124+
if [ ! -f "$SETTINGS_FILE" ]; then
125+
echo "Creating basic $SETTINGS_FILE for hook registration..."
126+
printf '{\n "hooks": {\n "beforeAgent": []\n }\n}\n' > "$SETTINGS_FILE"
127+
fi
128+
129+
echo "Registering superpowers-router hook in $SETTINGS_FILE..."
130+
131+
# Path to the script
132+
ROUTER_PATH="$REPO_DIR/agents/superpowers-router.js"
133+
134+
# Python script to safely inject/update the hook without breaking existing ones
135+
if command -v python3 >/dev/null 2>&1; then
136+
python3 -c "
137+
import json, sys
138+
settings_path = sys.argv[1]
139+
router_path = sys.argv[2]
140+
try:
141+
with open(settings_path, 'r') as f:
142+
d = json.load(f)
143+
except (FileNotFoundError, json.JSONDecodeError):
144+
d = {}
145+
146+
hooks = d.setdefault('hooks', {})
147+
beforeAgent = hooks.setdefault('beforeAgent', [])
148+
149+
# Remove existing superpower-router if it exists
150+
beforeAgent = [h for h in beforeAgent if h.get('name') != 'superpowers-router']
151+
152+
# Append our router
153+
beforeAgent.append({
154+
'name': 'superpowers-router',
155+
'command': 'node',
156+
'args': [router_path],
157+
'matcher': '.*'
158+
})
159+
160+
hooks['beforeAgent'] = beforeAgent
161+
162+
with open(settings_path, 'w') as f:
163+
json.dump(d, f, indent=2)
164+
f.write('\n')
165+
" "$SETTINGS_FILE" "$ROUTER_PATH"
166+
echo " ✓ Registered superpowers-router hook."
167+
else
168+
echo " ⚠ python3 not found. Could not auto-register hooks. Please add manually:"
169+
echo " \"hooks\": { \"beforeAgent\": [{ \"name\": \"superpowers-router\", \"command\": \"node\", \"args\": [\"$ROUTER_PATH\"], \"matcher\": \".*\" }] }"
170+
fi
171+
}
172+
173+
enable_router_hook
174+
119175
# --- Context injection into GEMINI.md ---
120176
CONTEXT_HEADER="<!-- SUPERPOWERS-CONTEXT-START -->"
121177
CONTEXT_FOOTER="<!-- SUPERPOWERS-CONTEXT-END -->"

agents/superpowers-router.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/usr/bin/env node
2+
const fs = require('fs');
3+
4+
try {
5+
// Read stdin JSON provided by Gemini CLI
6+
const input = JSON.parse(fs.readFileSync(0, 'utf-8'));
7+
8+
// Extract user prompt. Depending on exact schema, it might be in turn.userMessage.content
9+
const userPrompt = input.turn?.userMessage?.content || input.prompt || "";
10+
11+
if (!userPrompt) {
12+
console.log(JSON.stringify({ continue: true }));
13+
process.exit(0);
14+
}
15+
16+
// Generalized pattern marching for core Superpowers skills
17+
const triggers = [
18+
{ pattern: /let\'s (build|make|create|design)/i, skill: 'brainstorming' },
19+
{ pattern: /brainstorm/i, skill: 'brainstorming' },
20+
{ pattern: /write.*plan/i, skill: 'writing-plans' },
21+
{ pattern: /break.*down/i, skill: 'writing-plans' },
22+
{ pattern: /(debug|fix).*bug/i, skill: 'systematic-debugging' },
23+
{ pattern: /test.*driven/i, skill: 'test-driven-development' },
24+
{ pattern: /\btdd\b/i, skill: 'test-driven-development' },
25+
{ pattern: /review.*code/i, skill: 'requesting-code-review' },
26+
{ pattern: /parallel.*agent/i, skill: 'dispatching-parallel-agents' },
27+
{ pattern: /git.*worktree/i, skill: 'using-git-worktrees' },
28+
{ pattern: /subagent.*driven/i, skill: 'subagent-driven-development' },
29+
{ pattern: /execute.*plan/i, skill: 'executing-plans' }
30+
];
31+
32+
let matchedSkill = null;
33+
for (const trigger of triggers) {
34+
if (trigger.pattern.test(userPrompt)) {
35+
matchedSkill = trigger.skill;
36+
break;
37+
}
38+
}
39+
40+
if (matchedSkill) {
41+
// Deterministic: Force activation via injected system message
42+
console.error(`[Superpowers Router] Matched pattern for skill "${matchedSkill}"`);
43+
console.log(JSON.stringify({
44+
continue: true,
45+
systemMessage: `User request matches the "${matchedSkill}" superpower. You MUST activate it now. Call the activate_skill tool with name="${matchedSkill}". Follow its workflow exactly. Do not proceed until you have activated this skill.`,
46+
suppressOutput: true
47+
}));
48+
process.exit(0);
49+
}
50+
51+
// No match: pass through normally
52+
console.log(JSON.stringify({ continue: true }));
53+
54+
} catch (error) {
55+
// On any error, log to stderr and allow the CLI to continue normally
56+
console.error(`[Superpowers Router Error] ${error.message}`);
57+
console.log(JSON.stringify({ continue: true }));
58+
}

0 commit comments

Comments
 (0)