Skip to content

Commit 23c4d7b

Browse files
authored
v1.13.0.0 feat: add Claude outside-voice skill (#1212)
* Add Claude outside-voice skill * Fix gbrain config isolation test * Restore Opus fanout overlay nudge * Warn on oversized tracked files * Release v1.13.0.0 * Fix Claude diff temp file handling * Remove Opus fanout overlay nudge
1 parent 6209163 commit 23c4d7b

10 files changed

Lines changed: 450 additions & 31 deletions

CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,31 @@
11
# Changelog
22

3+
## [1.13.0.0] - 2026-04-25
4+
5+
## **`/gstack-claude` gives non-Claude hosts a read-only outside voice.**
6+
7+
This release adds the reverse of `/codex`: external hosts can now ask Claude for review, adversarial challenge, or read-only consultation without handing nested Claude mutation tools.
8+
9+
### Added
10+
11+
- `claude/SKILL.md.tmpl`: new external-only `/gstack-claude` skill with `review`, `challenge`, and `consult` modes.
12+
- Review and challenge mode feed the detected base-branch diff to `claude -p --tools ""` with `--disable-slash-commands`.
13+
- Consult mode allows only `Read,Grep,Glob`, explicitly disallows `Bash,Edit,Write`, saves `.context/claude-session-id`, and can resume the prior consult session.
14+
- Claude prompt transport now uses a `/tmp/gstack-claude-prompt-*` file piped over stdin with cleanup.
15+
- Auth checks require the `claude` CLI plus either `~/.claude/.credentials.json` or `ANTHROPIC_API_KEY`.
16+
- JSON output parsing extracts `result`, `usage`, `model`, `session_id`, and `is_error`.
17+
18+
### Fixed
19+
20+
- `hosts/claude.ts`: excludes the Claude outside-voice skill from Claude-host generation.
21+
- `test/brain-sync.test.ts`: the `GSTACK_HOME` isolation test now snapshots and preserves the real config file instead of assuming local machine state.
22+
- `claude/SKILL.md.tmpl`: uses `mktemp` for diff capture in review/challenge mode instead of a `$$`-based temp path, avoiding collisions across concurrent invocations.
23+
24+
### Changed
25+
26+
- `test/skill-validation.test.ts`: the tracked-file-size check is now advisory. Large fixtures remain allowed in git and are reported as `[size-warning]` instead of failing the suite.
27+
- `test/gen-skill-docs.test.ts`: generation coverage now asserts external host docs include `gstack-claude/SKILL.md` while Claude host output omits `claude/SKILL.md`.
28+
329
## [1.12.2.0] - 2026-04-24
430

531
## **`/setup-gbrain` polish: PATH parsing, repo init order, MCP user scope.**

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.12.2.0
1+
1.13.0.0

claude/SKILL.md.tmpl

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
---
2+
name: claude
3+
preamble-tier: 3
4+
version: 1.0.0
5+
description: |
6+
Claude Code CLI wrapper for non-Claude hosts - three modes. Review: independent
7+
diff review via claude -p. Challenge: adversarial failure-mode review. Consult:
8+
ask Claude about the repo with read-only file tools. Use when asked for "claude
9+
review", "claude challenge", "ask claude", "second opinion from claude", or
10+
"outside voice". (gstack)
11+
triggers:
12+
- claude review
13+
- claude challenge
14+
- ask claude
15+
allowed-tools:
16+
- Bash
17+
- Read
18+
- AskUserQuestion
19+
---
20+
21+
{{PREAMBLE}}
22+
23+
{{BASE_BRANCH_DETECT}}
24+
25+
# /claude - Claude Outside Voice
26+
27+
You are running the `/claude` skill from a non-Claude host. This wraps `claude -p`
28+
to get an independent Claude Code second opinion without allowing nested Claude to
29+
modify files.
30+
31+
The generated external invocation name is `gstack-claude`.
32+
33+
---
34+
35+
## Step 0: Check Claude CLI
36+
37+
```bash
38+
CLAUDE_BIN=$(command -v claude 2>/dev/null || echo "")
39+
[ -z "$CLAUDE_BIN" ] && echo "NOT_FOUND" || echo "FOUND: $CLAUDE_BIN"
40+
```
41+
42+
If `NOT_FOUND`, stop and tell the user:
43+
"Claude CLI not found. Install Claude Code, then re-run this skill."
44+
45+
Check auth:
46+
47+
```bash
48+
if [ -f "$HOME/.claude/.credentials.json" ] || [ -n "${ANTHROPIC_API_KEY:-}" ]; then
49+
echo "AUTH_FOUND"
50+
else
51+
echo "AUTH_MISSING"
52+
fi
53+
```
54+
55+
If `AUTH_MISSING`, stop and tell the user:
56+
"No Claude authentication found. Run `claude` interactively to log in, or export `ANTHROPIC_API_KEY`, then re-run this skill."
57+
58+
---
59+
60+
## Safety Boundary
61+
62+
Nested Claude must stay focused on the user's repository and must not run gstack
63+
skills from inside this skill.
64+
65+
All `claude -p` calls MUST include:
66+
67+
- `--disable-slash-commands`
68+
- Review/challenge: `--tools ""`
69+
- Consult: `--allowedTools Read,Grep,Glob --disallowedTools Bash,Edit,Write`
70+
71+
Never pass `Bash`, `Edit`, or `Write` to nested Claude in this skill.
72+
73+
All prompts MUST be written to a temp file and fed through stdin. Never interpolate
74+
user text directly into the shell command.
75+
76+
---
77+
78+
## Step 1: Detect Mode
79+
80+
Parse the user's input:
81+
82+
1. `/claude review` or `/claude review <instructions>` - **Review mode** (Step 2A)
83+
2. `/claude challenge` or `/claude challenge <focus>` - **Challenge mode** (Step 2B)
84+
3. `/claude` with no arguments, or `/claude <anything else>` - **Consult mode** (Step 2C)
85+
86+
If no mode is obvious and a diff exists, ask whether to review, challenge, or consult.
87+
88+
---
89+
90+
## Shared Helpers
91+
92+
Use these shell snippets in every mode.
93+
94+
Create temp files:
95+
96+
```bash
97+
PROMPT_FILE=$(mktemp /tmp/gstack-claude-prompt-XXXXXX)
98+
RESP_FILE=$(mktemp /tmp/gstack-claude-response-XXXXXX.json)
99+
ERR_FILE=$(mktemp /tmp/gstack-claude-error-XXXXXX.txt)
100+
```
101+
102+
Cleanup at the end of every mode:
103+
104+
```bash
105+
rm -f "$PROMPT_FILE" "$RESP_FILE" "$ERR_FILE"
106+
```
107+
108+
Parse JSON output:
109+
110+
```bash
111+
python3 - "$RESP_FILE" <<'PY'
112+
import json, sys
113+
path = sys.argv[1]
114+
try:
115+
obj = json.load(open(path))
116+
except Exception as exc:
117+
print(f"CLAUDE_JSON_PARSE_ERROR: {exc}")
118+
sys.exit(0)
119+
120+
if obj.get("is_error"):
121+
print("CLAUDE_ERROR: true")
122+
123+
result = obj.get("result") or obj.get("response") or ""
124+
if result:
125+
print(result)
126+
127+
usage = obj.get("usage") or {}
128+
input_tokens = usage.get("input_tokens", 0) or 0
129+
output_tokens = usage.get("output_tokens", 0) or 0
130+
cache_read = usage.get("cache_read_input_tokens", 0) or 0
131+
model = obj.get("model") or "unknown"
132+
session_id = obj.get("session_id") or ""
133+
134+
print(f"\nTokens: input={input_tokens} output={output_tokens} cache_read={cache_read} | Model: {model}")
135+
if session_id:
136+
print(f"SESSION_ID:{session_id}")
137+
PY
138+
```
139+
140+
If stderr contains `auth`, `login`, or `unauthorized`, tell the user:
141+
"Claude authentication failed. Run `claude` interactively to authenticate or export `ANTHROPIC_API_KEY`."
142+
143+
---
144+
145+
## Step 2A: Review Mode
146+
147+
Review the current branch diff with nested Claude in tool-less mode.
148+
149+
1. Fetch base and capture diff:
150+
151+
```bash
152+
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
153+
cd "$_REPO_ROOT"
154+
DIFF_FILE=$(mktemp /tmp/gstack-claude-diff-XXXXXX.patch)
155+
git fetch origin <base> --quiet 2>/dev/null || true
156+
git diff "origin/<base>" > "$DIFF_FILE" 2>/dev/null || git diff "<base>" > "$DIFF_FILE"
157+
```
158+
159+
If the diff file is empty, stop and say:
160+
"Nothing to review - no changes against the base branch."
161+
162+
2. Write the prompt file:
163+
164+
```bash
165+
cat > "$PROMPT_FILE" <<'EOF'
166+
You are a brutally honest Claude Code reviewer. Review this git diff for bugs,
167+
production failure modes, security issues, missing tests, and maintainability
168+
problems. Be direct. No compliments. Reference files and changed code where possible.
169+
170+
Additional user instructions, if any:
171+
<custom review instructions>
172+
173+
DIFF:
174+
EOF
175+
cat "$DIFF_FILE" >> "$PROMPT_FILE"
176+
```
177+
178+
3. Run Claude:
179+
180+
```bash
181+
cat "$PROMPT_FILE" | claude -p --output-format json --disable-slash-commands --tools "" > "$RESP_FILE" 2>"$ERR_FILE"
182+
```
183+
184+
4. Present the parsed output:
185+
186+
```
187+
CLAUDE SAYS (code review):
188+
============================================================
189+
<parsed result from RESP_FILE>
190+
============================================================
191+
```
192+
193+
5. Cleanup:
194+
195+
```bash
196+
rm -f "$DIFF_FILE" "$PROMPT_FILE" "$RESP_FILE" "$ERR_FILE"
197+
```
198+
199+
---
200+
201+
## Step 2B: Challenge Mode
202+
203+
Run an adversarial failure-mode review with nested Claude in tool-less mode.
204+
205+
1. Capture the diff using the same diff commands from Review mode.
206+
207+
2. Write the prompt:
208+
209+
```bash
210+
cat > "$PROMPT_FILE" <<'EOF'
211+
You are an adversarial Claude Code reviewer. Try to break this change before users do.
212+
Find edge cases, race conditions, security holes, resource leaks, silent data
213+
corruption, bad error handling, and operational failure modes. Be thorough. No
214+
compliments. If the user provided a focus area, prioritize it.
215+
216+
Focus area, if any:
217+
<focus>
218+
219+
DIFF:
220+
EOF
221+
cat "$DIFF_FILE" >> "$PROMPT_FILE"
222+
```
223+
224+
3. Run Claude:
225+
226+
```bash
227+
cat "$PROMPT_FILE" | claude -p --output-format json --disable-slash-commands --tools "" > "$RESP_FILE" 2>"$ERR_FILE"
228+
```
229+
230+
4. Present the parsed output:
231+
232+
```
233+
CLAUDE SAYS (adversarial challenge):
234+
============================================================
235+
<parsed result from RESP_FILE>
236+
============================================================
237+
```
238+
239+
5. Cleanup:
240+
241+
```bash
242+
rm -f "$DIFF_FILE" "$PROMPT_FILE" "$RESP_FILE" "$ERR_FILE"
243+
```
244+
245+
---
246+
247+
## Step 2C: Consult Mode
248+
249+
Ask Claude about the repository. Consult mode may inspect files, but only with
250+
read-only tools.
251+
252+
1. Check for an existing Claude session:
253+
254+
```bash
255+
cat .context/claude-session-id 2>/dev/null || echo "NO_SESSION"
256+
```
257+
258+
If a session exists, ask the user whether to continue it or start fresh.
259+
260+
2. Write the prompt:
261+
262+
```bash
263+
cat > "$PROMPT_FILE" <<'EOF'
264+
You are Claude Code acting as an independent outside voice for this repository.
265+
Answer the user's question directly. You may inspect repository files with Read,
266+
Grep, and Glob only. Do not use Bash. Do not edit or write files. Do not invoke
267+
slash commands or gstack skills.
268+
269+
USER QUESTION:
270+
<user prompt>
271+
EOF
272+
```
273+
274+
3. Run Claude.
275+
276+
For a new session:
277+
278+
```bash
279+
cat "$PROMPT_FILE" | claude -p --output-format json --disable-slash-commands --allowedTools Read,Grep,Glob --disallowedTools Bash,Edit,Write > "$RESP_FILE" 2>"$ERR_FILE"
280+
```
281+
282+
For a resumed session:
283+
284+
```bash
285+
cat "$PROMPT_FILE" | claude -p --resume "<session-id>" --output-format json --disable-slash-commands --allowedTools Read,Grep,Glob --disallowedTools Bash,Edit,Write > "$RESP_FILE" 2>"$ERR_FILE"
286+
```
287+
288+
4. Parse and save the session id:
289+
290+
```bash
291+
SESSION_ID=$(python3 - "$RESP_FILE" <<'PY'
292+
import json, sys
293+
try:
294+
obj = json.load(open(sys.argv[1]))
295+
print(obj.get("session_id") or "")
296+
except Exception:
297+
print("")
298+
PY
299+
)
300+
if [ -n "$SESSION_ID" ]; then
301+
mkdir -p .context
302+
printf "%s\n" "$SESSION_ID" > .context/claude-session-id
303+
fi
304+
```
305+
306+
5. Present the parsed output:
307+
308+
```
309+
CLAUDE SAYS (consult):
310+
============================================================
311+
<parsed result from RESP_FILE>
312+
============================================================
313+
Session saved - run /claude again to continue this conversation.
314+
```
315+
316+
6. Cleanup:
317+
318+
```bash
319+
rm -f "$PROMPT_FILE" "$RESP_FILE" "$ERR_FILE"
320+
```
321+
322+
---
323+
324+
## Error Handling
325+
326+
- **Binary not found:** Stop with install instructions.
327+
- **Auth missing:** Stop with login/API key instructions.
328+
- **Auth failure from stderr:** Surface the stderr line and ask the user to re-authenticate.
329+
- **JSON parse failure:** Show raw stdout from `$RESP_FILE` and stderr from `$ERR_FILE`.
330+
- **Empty response:** Tell the user "Claude returned no response. Check stderr for errors."
331+
- **Resume failure:** Delete `.context/claude-session-id` and retry with a fresh session.
332+
333+
---
334+
335+
## Important Rules
336+
337+
- Nested Claude is read-only in consult mode and tool-less in review/challenge.
338+
- Always include `--disable-slash-commands`.
339+
- Never pass nested Claude `Bash`, `Edit`, or `Write`.
340+
- Never interpolate user text into a shell command.
341+
- Present Claude's response faithfully, then add any host-agent synthesis after it.

hosts/claude.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const claude: HostConfig = {
1919

2020
generation: {
2121
generateMetadata: false,
22-
skipSkills: [],
22+
skipSkills: ['claude'], // Claude outside-voice skill is for non-Claude hosts
2323
},
2424

2525
pathRewrites: [], // Claude is the primary host — no rewrites needed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "gstack",
3-
"version": "1.12.2.0",
3+
"version": "1.13.0.0",
44
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
55
"license": "MIT",
66
"type": "module",

0 commit comments

Comments
 (0)