This commit is contained in:
maxpetrusenkoagent 2026-07-14 19:16:51 -07:00 committed by GitHub
commit 7b03a32b55
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 93 additions and 21 deletions

View File

@ -128,8 +128,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];

49
setup
View File

@ -1277,24 +1277,26 @@ 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"
@ -1306,19 +1308,10 @@ if [ -x "$DETECT_BIN" ]; then
# 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 "${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 blocks via
# `gstack-config gbrain-refresh`.
log "gbrain detected — GSTACK_SKIP_GBRAIN_REGEN set: leaving tracked SKILL.md canonical (dev/source tree)."
if [ -n "$USER_LOCAL_RENDER_REASON" ]; then
USER_LOCAL_RENDER_REASON="$USER_LOCAL_RENDER_REASON + brain-aware blocks"
else
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"
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)."
@ -1330,6 +1323,22 @@ if [ -x "$DETECT_BIN" ]; then
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

@ -393,6 +393,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');
@ -2507,6 +2540,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');