fix: honor terse skill rendering in user installs

This commit is contained in:
maxpetrusenkoagent 2026-06-12 21:04:17 -04:00
parent cab774cced
commit 7b7e2662e5
3 changed files with 105 additions and 18 deletions

View File

@ -126,8 +126,24 @@ const CATALOG_MODE: 'trim' | 'full' = (() => {
// the model skips them when EXPLAIN_LEVEL: terse appears in the preamble echo).
// Opt-in via the build flag so most users get the runtime-flexible default.
const EXPLAIN_LEVEL_ARG = process.argv.find(a => a.startsWith('--explain-level'));
function loadConfiguredExplainLevel(): 'default' | 'terse' {
if (!RESPECT_DETECTION) return 'default';
const stateDir = process.env.GSTACK_STATE_ROOT
|| process.env.GSTACK_HOME
|| process.env.GSTACK_STATE_DIR
|| path.join(process.env.HOME || '', '.gstack');
const configPath = path.join(stateDir, 'config.yaml');
try {
const config = fs.readFileSync(configPath, 'utf-8');
const match = config.match(/^\s*explain_level\s*:\s*(default|terse)\s*$/m);
return match?.[1] === 'terse' ? 'terse' : 'default';
} catch {
return 'default';
}
}
const EXPLAIN_LEVEL: 'default' | 'terse' = (() => {
if (!EXPLAIN_LEVEL_ARG) return 'default';
if (!EXPLAIN_LEVEL_ARG) return loadConfiguredExplainLevel();
const val = EXPLAIN_LEVEL_ARG.includes('=')
? EXPLAIN_LEVEL_ARG.split('=')[1]
: process.argv[process.argv.indexOf(EXPLAIN_LEVEL_ARG) + 1];

58
setup
View File

@ -1268,44 +1268,68 @@ if [ "$NO_TEAM_MODE" -eq 1 ]; then
log "Team mode disabled: auto-update hook removed."
fi
# ─── GBrain detection + conditional SKILL.md regen ──────────────────────
# ─── User-local SKILL.md regen ───────────────────────────────────────────
#
# Detect whether gbrain is installed and persist the result to
# ~/.gstack/gbrain-detection.json so gen-skill-docs can decide whether to
# render GBRAIN_CONTEXT_LOAD and GBRAIN_SAVE_RESULTS blocks. If detected,
# regenerate the Claude-host SKILL.md files with the un-suppressed
# (compressed) brain-aware blocks via `bun run gen:skill-docs:user`.
# render GBRAIN_CONTEXT_LOAD and GBRAIN_SAVE_RESULTS blocks.
#
# If gbrain is not detected, the canonical no-gbrain SKILL.md files
# (which were just generated above by `gen:skill-docs --host claude` if
# applicable, or which are checked in) stay as-is. Zero token overhead
# for non-gbrain users.
# Also honor user-local explain_level=terse at setup/session-update time. The
# canonical checked-in SKILL.md files stay full-prose; `gen:skill-docs:user`
# applies local config only when setup updates a user's installed skills.
#
# Users who install gbrain after running ./setup should re-run setup OR
# call `gstack-config gbrain-refresh` + `bun run gen:skill-docs:user`.
DETECT_BIN="$SOURCE_GSTACK_DIR/bin/gstack-gbrain-detect"
GBRAIN_STATE_DIR="${GSTACK_HOME:-$HOME/.gstack}"
DETECTION_FILE="$GBRAIN_STATE_DIR/gbrain-detection.json"
USER_LOCAL_RENDER_REASON=""
USER_EXPLAIN_LEVEL="$("$GSTACK_CONFIG" get explain_level 2>/dev/null || echo default)"
if [ "$USER_EXPLAIN_LEVEL" = "terse" ]; then
USER_LOCAL_RENDER_REASON="explain_level=terse"
fi
# PID-unique tmp so concurrent setups (parallel Conductor workspaces) can't
# clobber each other's in-flight detection write.
DETECTION_TMP="$DETECTION_FILE.$$.tmp"
mkdir -p "$GBRAIN_STATE_DIR"
if [ -x "$DETECT_BIN" ]; then
if "$DETECT_BIN" > "$DETECTION_FILE.tmp" 2>/dev/null; then
mv "$DETECTION_FILE.tmp" "$DETECTION_FILE"
if grep -q '"gbrain_local_status": "ok"' "$DETECTION_FILE" 2>/dev/null; then
log "gbrain detected — regenerating Claude SKILL.md with brain-aware blocks (~250 token overhead per planning skill)..."
(
cd "$SOURCE_GSTACK_DIR"
bun_cmd run gen:skill-docs:user --host claude 2>&1 | tail -3
) || log " warning: gen:skill-docs:user failed — run 'bun run gen:skill-docs:user' manually if you want brain-aware blocks"
if "$DETECT_BIN" > "$DETECTION_TMP" 2>/dev/null; then
mv "$DETECTION_TMP" "$DETECTION_FILE"
# Single source of truth for "is gbrain usable" — `--is-ok` runs live
# detection (exit 0 iff ok), so setup, bin/dev-setup, and gstack-config
# all gate on the same check instead of re-grepping the JSON.
if "$DETECT_BIN" --is-ok 2>/dev/null; then
if [ -n "$USER_LOCAL_RENDER_REASON" ]; then
USER_LOCAL_RENDER_REASON="$USER_LOCAL_RENDER_REASON + brain-aware blocks"
else
USER_LOCAL_RENDER_REASON="brain-aware blocks"
fi
else
log "gbrain not detected — brain-aware blocks suppressed in planning-skill SKILL.md files (zero token overhead)."
log " To enable: install gbrain via /setup-gbrain, then re-run ./setup or 'gstack-config gbrain-refresh'."
fi
else
rm -f "$DETECTION_FILE.tmp"
rm -f "$DETECTION_TMP"
log " warning: gstack-gbrain-detect failed — brain-aware blocks will stay suppressed"
fi
fi
if [ -n "$USER_LOCAL_RENDER_REASON" ]; then
if [ -n "${GSTACK_SKIP_GBRAIN_REGEN:-}" ]; then
# Dev/source tree (set by bin/dev-setup): never regenerate tracked
# SKILL.md in place — that dirties checked-in source. Detection is still
# persisted above; the dev workspace renders the :user variant into an
# untracked dir, and other projects get local blocks via setup/session-update.
log "user-local SKILL.md render needed ($USER_LOCAL_RENDER_REASON) — GSTACK_SKIP_GBRAIN_REGEN set: leaving tracked SKILL.md canonical (dev/source tree)."
else
log "regenerating Claude SKILL.md with user-local settings ($USER_LOCAL_RENDER_REASON)..."
(
cd "$SOURCE_GSTACK_DIR"
bun_cmd run gen:skill-docs:user --host claude 2>&1 | tail -3
) || log " warning: gen:skill-docs:user failed — run 'bun run gen:skill-docs:user --host claude' manually to apply user-local settings"
fi
fi
# 11. Plan-tune cathedral hook install (T8).
#
# Registers PostToolUse (deterministic AUQ capture) + PreToolUse (preference

View File

@ -391,6 +391,39 @@ describe('gen-skill-docs', () => {
expect(Buffer.byteLength(writingStyle, 'utf-8')).toBeLessThan(2_000);
});
test('user-local generation structurally honors explain_level: terse', () => {
const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-user-config-'));
const outDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-user-render-'));
try {
fs.writeFileSync(path.join(stateDir, 'config.yaml'), 'explain_level: terse\n');
const result = Bun.spawnSync([
'bun', 'run', 'scripts/gen-skill-docs.ts',
'--respect-detection',
'--host', 'claude',
'--out-dir', outDir,
], {
cwd: ROOT,
stdout: 'pipe',
stderr: 'pipe',
env: { ...process.env, GSTACK_HOME: stateDir, GSTACK_STATE_ROOT: stateDir },
});
const stderr = result.stderr.toString();
const stdout = result.stdout.toString();
expect(result.exitCode, `${stdout}\n${stderr}`).toBe(0);
const content = fs.readFileSync(path.join(outDir, 'plan-eng-review', 'SKILL.md'), 'utf-8');
expect(content).toContain('Terse mode (build-time)');
expect(content).not.toContain('Curated jargon list lives');
expect(content).not.toContain('## Completeness Principle — Boil the Ocean');
expect(content).not.toContain('## Confusion Protocol');
expect(content).not.toContain('## Context Health (soft directive)');
} finally {
fs.rmSync(stateDir, { recursive: true, force: true });
fs.rmSync(outDir, { recursive: true, force: true });
}
});
test('slim voice section preserves the gstack voice contract', () => {
const content = readSkillUnion('plan-eng-review'); // carved: review body moved to section
const voice = extractMarkdownSection(content, '## Voice');
@ -2478,6 +2511,20 @@ describe('setup script validation', () => {
expect(setupContent).toContain('GSTACK_CONFIG');
});
test('setup applies user-local skill rendering for terse mode without requiring gbrain', () => {
const regenSectionStart = setupContent.indexOf('# ─── User-local SKILL.md regen');
const regenSectionEnd = setupContent.indexOf('# 11. Plan-tune cathedral hook install', regenSectionStart);
const regenSection = setupContent.slice(regenSectionStart, regenSectionEnd);
const gbrainElseStart = regenSection.indexOf('gbrain not detected');
const userRenderIndex = regenSection.indexOf('gen:skill-docs:user --host claude');
expect(regenSection).toContain('get explain_level');
expect(regenSection).toContain('explain_level=terse');
expect(userRenderIndex).toBeGreaterThan(-1);
expect(gbrainElseStart).toBeGreaterThan(-1);
expect(userRenderIndex).toBeGreaterThan(gbrainElseStart);
});
test('setup supports --prefix flag', () => {
expect(setupContent).toContain('--prefix)');
expect(setupContent).toContain('SKILL_PREFIX=1; SKILL_PREFIX_FLAG=1');