Skip to content

Commit 9562ad4

Browse files
garrytanclaude
andauthored
v1.53.1.0 fix: non-interactive-safe plan-tune hook install (flags + smart defaults) (#1805)
* feat(config): add plan_tune_hooks setting (prompt|yes|no) Registers a new gstack-config key controlling whether ./setup installs the plan-tune Claude Code hooks. Default "prompt". Documented in the config header and surfaced in `gstack-config defaults` / `list`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(setup): make plan-tune hook install non-interactive-safe The plan-tune consent prompt used a blocking `read -r` with no timeout. Under a forwarded/automated TTY (conductor workspace setup, CI with a pty) it hung setup forever. Move the decision into flags + env + saved config with a smart default: --plan-tune-hooks / --no-plan-tune-hooks / --plan-tune-hooks=yes|no|prompt > GSTACK_PLAN_TUNE_HOOKS env > plan_tune_hooks config > prompt-on-real-TTY. Explicit yes/no act non-interactively. The remaining interactive branch is gated on a real (non-quiet) TTY and uses a time-bounded `read -t 10 </dev/tty` that defaults to skip, so it can never hang. A timeout no longer persists a decline marker, so a later hands-on run can still offer the install. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dev-setup): run setup non-interactively in dev/workspace mode Conductor runs bin/dev-setup under a forwarded pty, so any setup prompt (skill-prefix, plan-tune consent) would hang the workspace. Detach stdin (`setup </dev/null`) so every prompt takes its smart non-interactive default: flat skill names, skip the global plan-tune hook install without writing a decline marker. Saved prefix/config preferences are still honored, and a dev workspace no longer silently mutates ~/.claude/settings.json. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(setup): guard plan-tune hooks stay non-interactive Static + binary-level regression test (free, <1s): asserts the flags are wired, the plan-tune read is time-bounded (no bare blocking read), explicit yes/no decisions short-circuit before the prompt, and gstack-config knows the plan_tune_hooks key. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(setup,config): harden plan-tune decision against bad input Review follow-ups to the non-interactive plan-tune work: - setup now lowercases + whitespace-strips the resolved decision before the case match, so an explicit opt-in via flag/env ("YES", "Yes", " yes") is honored instead of silently falling through to "prompt"/skip. Also accepts on/off and 1/0. - gstack-config rejects out-of-domain plan_tune_hooks values (anything but prompt|yes|no) with a warning + fallback to prompt, matching the existing value-whitelist pattern for explain_level / artifacts_sync_mode. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dev-setup): never mutate global hooks during workspace setup Closing stdin alone only suppresses the prompt branch; a saved `plan_tune_hooks: yes` or exported GSTACK_PLAN_TUNE_HOOKS=yes would still resolve to "install" and rewrite the user's global ~/.claude/settings.json to point at THIS ephemeral worktree — which breaks once the workspace is deleted. Pass --plan-tune-hooks=prompt (highest precedence) so dev-setup pins resolution to prompt-mode; with stdin closed that is a guaranteed no-op skip (no install, no decline marker). To install the hooks, run ./setup --plan-tune-hooks directly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(setup): isolate config tests from host + cover new guards - Point gstack-config tests at a temp GSTACK_HOME so `get plan_tune_hooks` reads the built-in default, not whatever the host machine has in ~/.gstack/config.yaml (the prior test was non-deterministic). - Add behavioral coverage: yes/no/prompt round-trip, out-of-domain rejection. - Add a normalization guard (decision input is lowercased/trimmed) and a dev-setup guard (runs setup with --plan-tune-hooks=prompt + stdin detached). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: rebaseline parity-suite v1.44.1 -> v1.53.0.0 The frozen v1.44.1 anchor went stale: five planning skills (plan-ceo-review, plan-eng-review, plan-design-review, investigate, office-hours) crept past the 1.05x ceiling via legitimate v1.49-v1.53 growth (brain-aware planning + the v1.53 redaction guard), so `bun test` was red on a clean checkout of main. Capture a fresh baseline at HEAD (bun run scripts/capture-baseline.ts --tag v1.53.0.0) and re-point the test at it. The per-skill 1.05 ratio is kept, so future bloat is still caught; only the anchor moved. Mirrors the earlier skill-size-budget rebase (v1.44.1 -> v1.47.0.0). Historical v1.44.1 / v1.46.0.0 / v1.47.0.0 baselines are retained for the v1->v2 audit trail. The captured skill bytes equal origin/main exactly (this branch left every SKILL.md untouched). Clears the pre-existing failures noted in the v1.53.0.0 CHANGELOG. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(plan-tune): de-flake "derive pushes scope_appetite up" The test was ~25-50% flaky (worse on main). gstack-question-log fires a fire-and-forget background `--derive` after every write; the 5 rapid log writes spawned 5 racing background derives that collided with the test's explicit --derive — a late one that only saw 3 entries could clobber developer-profile.json after the explicit one wrote sample_size=5. Set GSTACK_QUESTION_LOG_NO_DERIVE=1 (the flag the binary documents for exactly this case) so the writes don't spawn background derives. The explicit --derive still runs, so real derive behavior is still asserted. 20/20 green after. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: bump version and changelog (v1.53.1.0) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: document non-interactive dev-setup + plan-tune hook flags (v1.53.1.0) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent dedfe42 commit 9562ad4

12 files changed

Lines changed: 939 additions & 59 deletions

CHANGELOG.md

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

3+
## [1.53.1.0] - 2026-05-30
4+
5+
## **Workspace and scripted setup never hang on a hidden prompt again. Installing the plan-tune hooks is now flag-driven with safe defaults.**
6+
7+
`./setup` asked "Install both hooks now? [y/N]" with a blocking read. Run under a Conductor workspace or any forwarded terminal, that prompt had nobody to answer it, so setup hung forever. Now the decision comes from a flag, an env var, or saved config, and when nobody is there to answer it takes a safe default instead of waiting. A real terminal still gets the prompt, but it is time-bounded (auto-skips after 10s) so it can never stall a pipeline.
8+
9+
### What this means for you
10+
11+
- Spinning up a new workspace just works. `bin/dev-setup` runs fully non-interactively and never rewrites your global Claude settings behind your back.
12+
- Want the plan-tune hooks installed without a prompt? `./setup --plan-tune-hooks` (or `GSTACK_PLAN_TUNE_HOOKS=yes`, or `gstack-config set plan_tune_hooks yes`). Don't want them? `--no-plan-tune-hooks`. Leave it unset and a real terminal still asks once, then remembers.
13+
14+
### Added
15+
16+
- `--plan-tune-hooks` / `--no-plan-tune-hooks` / `--plan-tune-hooks=yes|no|prompt` flags on `./setup`, plus the `GSTACK_PLAN_TUNE_HOOKS` env var and a `plan_tune_hooks` config key (default `prompt`). Precedence: flag > env > saved config > prompt on a real terminal.
17+
18+
### Fixed
19+
20+
- `./setup` no longer hangs in non-interactive or forwarded-TTY contexts (Conductor workspaces, CI). The plan-tune consent prompt is time-bounded and defaults to skip.
21+
- `bin/dev-setup` runs setup non-interactively and can no longer silently rewrite your global `~/.claude/settings.json` to point at an ephemeral workspace path that breaks when the workspace is deleted.
22+
- Opt-in values like `YES`, `Yes`, or ` yes` are honored instead of being silently downgraded to skip, and `gstack-config` now rejects out-of-domain `plan_tune_hooks` values.
23+
24+
### For contributors
25+
26+
- New regression suite `test/setup-plan-tune-hooks-noninteractive.test.ts` (flag wiring, no-blocking-read guard, decision normalization, config round-trip + domain rejection, dev-setup pin) with host-config isolation via a temp `GSTACK_HOME`.
27+
- Rebaselined `test/parity-suite.test.ts` from the stale v1.44.1 anchor to v1.53.0.0. The 1.05 per-skill ratio is kept (only the anchor moved), absorbing legitimate v1.49–v1.53 planning-skill growth and clearing the 5 pre-existing parity failures noted in the v1.53.0.0 entry. Historical baselines retained for the v1→v2 audit trail.
28+
- De-flaked `test/plan-tune.test.ts` "derive pushes scope_appetite up" (was ~25–50% flaky, worse on main): it now sets `GSTACK_QUESTION_LOG_NO_DERIVE=1` so gstack-question-log's fire-and-forget background `--derive` can't race the test's explicit one.
29+
330
## [1.53.0.0] - 2026-05-29
431

532
## **Secrets, PII, and legal landmines get caught before they reach a public sink. One redaction engine now guards /spec, /ship, /cso, and the /document-* skills.**

CONTRIBUTING.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,11 +326,13 @@ If you're using [Conductor](https://conductor.build) to run multiple Claude Code
326326

327327
| Hook | Script | What it does |
328328
|------|--------|-------------|
329-
| `setup` | `bin/dev-setup` | Copies `.env` from main worktree, installs deps, symlinks skills |
329+
| `setup` | `bin/dev-setup` | Copies `.env` from main worktree, installs deps, symlinks skills, runs `./setup` non-interactively |
330330
| `archive` | `bin/dev-teardown` | Removes skill symlinks, cleans up `.claude/` directory |
331331

332332
When Conductor creates a new workspace, `bin/dev-setup` runs automatically. It detects the main worktree (via `git worktree list`), copies your `.env` so API keys carry over, and sets up dev mode — no manual steps needed.
333333

334+
`bin/dev-setup` runs `./setup` fully non-interactively (it passes `--plan-tune-hooks=prompt` and closes stdin), so a forwarded Conductor TTY can never hang on a hidden setup prompt. It also never installs the plan-tune Claude Code hooks, which means a throwaway workspace can't rewrite your global `~/.claude/settings.json` to point at an ephemeral worktree path. To install the plan-tune hooks deliberately, run `./setup --plan-tune-hooks` outside dev-setup (or `gstack-config set plan_tune_hooks yes`).
335+
334336
**First-time setup:** Put your `ANTHROPIC_API_KEY` in `.env` in the main repo (see `.env.example`). Every Conductor workspace inherits it automatically.
335337

336338
**`GSTACK_*` env prefix (Conductor-injected keys).** Conductor explicitly strips `ANTHROPIC_API_KEY` and `OPENAI_API_KEY` from every workspace's process env. The `.env` copy path doesn't restore them either — the strip happens after env inheritance. Users who want paid evals, `/sync-gbrain` embeddings, or `claude-agent-sdk` calls to work in a Conductor workspace must set `GSTACK_ANTHROPIC_API_KEY` and `GSTACK_OPENAI_API_KEY` in Conductor's workspace env config; Conductor passes those through untouched. On the gstack side, TS entry points import `lib/conductor-env-shim.ts` as a side effect, which promotes `GSTACK_FOO_API_KEY` to `FOO_API_KEY` when the canonical name is empty. If you add a new TS entry point that hits a paid API, add `import "../lib/conductor-env-shim";` to the top of the file. Today the shim is imported from `bin/gstack-gbrain-sync.ts`, `bin/gstack-model-benchmark`, `scripts/preflight-agent-sdk.ts`, and `test/helpers/e2e-helpers.ts`.

TODOS.md

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,22 @@
22

33
## Test infrastructure
44

5-
### P0: Rebaseline parity-suite (v1.44.1) — stale, 5 pre-existing failures
6-
7-
**What:** `test/parity-suite.test.ts` checks every skill's SKILL.md size against
8-
the frozen `test/fixtures/parity-baseline-v1.44.1.json`. Five planning skills now
9-
exceed the 1.05x ceiling: `plan-ceo-review` (1.052), `plan-eng-review` (1.062),
10-
`plan-design-review` (1.068), `investigate` (1.053), `office-hours` (1.065).
11-
12-
**Why:** These grew during the brain-aware-planning releases (v1.49–v1.52) which
13-
added the `BRAIN_PREFLIGHT`/`BRAIN_CACHE_REFRESH`/`BRAIN_WRITE_BACK` resolvers to
14-
those skills. The v1.44.1 baseline was never regenerated, so it's four releases
15-
stale. The failures are pre-existing on `origin/main` (proven: they fail with the
16-
redaction branch absent). The active size gate (`skill-size-budget`, v1.47 baseline)
17-
passes, and parity-suite is not in CI's `test:gate`, so nothing is blocked — but the
18-
local `bun test` shows red until rebaselined.
19-
20-
**How to start:** Either regenerate the fixture to a current baseline
21-
(`bun run scripts/capture-baseline.ts <tag>` and point the test at it), or bump the
22-
per-skill ratio for the planning skills. Decide whether v1.44.1 should be retired in
23-
favor of the v1.47 baseline the size-budget test already uses.
24-
25-
**Depends on:** nothing. Standalone.
5+
### ✅ DONE (v1.53.1.0): Rebaseline parity-suite (v1.44.1 → v1.53.0.0)
6+
7+
**What:** `test/parity-suite.test.ts` checked every skill's SKILL.md size against
8+
the frozen `test/fixtures/parity-baseline-v1.44.1.json`. Five planning skills had
9+
crept past the 1.05x ceiling: `plan-ceo-review` (1.052), `plan-eng-review` (1.062),
10+
`plan-design-review` (1.068), `investigate` (1.053), `office-hours` (1.065) — growth
11+
from the brain-aware-planning releases (v1.49–v1.52) plus the v1.53 redaction guard.
12+
13+
**Resolved:** Captured a fresh baseline at HEAD via
14+
`bun run scripts/capture-baseline.ts --tag v1.53.0.0` and re-pointed the test at
15+
`test/fixtures/parity-baseline-v1.53.0.0.json`. The per-skill 1.05 ratio is kept, so
16+
future bloat is still caught — only the stale anchor moved. Mirrors the earlier
17+
`skill-size-budget` rebase (v1.44.1 → v1.47.0.0). Historical v1.44.1 / v1.46.0.0 /
18+
v1.47.0.0 baselines retained in `test/fixtures/` for the v1→v2 audit trail. The
19+
captured skill bytes match `origin/main` exactly (the rebasing branch left every
20+
SKILL.md untouched). `bun test` is green again.
2621

2722
## gbrowser memory follow-ups (filed via /plan-eng-review + /codex on the v1.49 leak-fix PR)
2823

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.53.0.0
1+
1.53.1.0

bin/dev-setup

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,23 @@ if [ ! -e "$AGENTS_LINK" ]; then
5656
ln -s "$REPO_ROOT" "$AGENTS_LINK"
5757
fi
5858

59-
# 6. Run setup via the symlink so it detects .claude/skills/ as its parent
60-
"$GSTACK_LINK/setup"
59+
# 6. Run setup via the symlink so it detects .claude/skills/ as its parent.
60+
#
61+
# Workspace/dev setup MUST be non-interactive: Conductor runs this under a
62+
# forwarded pty, so any `read` in setup (skill-prefix prompt, plan-tune hook
63+
# consent) would hang the workspace forever. Detaching stdin makes every setup
64+
# prompt take its smart non-interactive default (flat skill names, etc.).
65+
#
66+
# `--plan-tune-hooks=prompt` is load-bearing, not redundant: stdin alone only
67+
# suppresses the *prompt* branch. A saved `plan_tune_hooks: yes` or an exported
68+
# GSTACK_PLAN_TUNE_HOOKS=yes would still resolve to "install" and rewrite the
69+
# user's global ~/.claude/settings.json to point at THIS ephemeral worktree —
70+
# which breaks once the workspace is deleted. The flag has highest precedence,
71+
# so it pins resolution to "prompt", and closed stdin then makes prompt-mode a
72+
# no-op skip (no install, no decline marker). A dev workspace must never mutate
73+
# global settings.json. To install the hooks, run `./setup --plan-tune-hooks`
74+
# directly (outside dev-setup). Saved prefix/other config preferences still apply.
75+
"$GSTACK_LINK/setup" --plan-tune-hooks=prompt </dev/null
6176

6277
echo ""
6378
echo "Dev mode active. Skills resolve from this working tree."

bin/gstack-config

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@ CONFIG_HEADER='# gstack configuration — edit freely, changes take effect on ne
7575
# # Set to true once the privacy gate has asked the user.
7676
# # Flip back to false to be re-prompted.
7777
#
78+
# ─── Plan-tune hooks ─────────────────────────────────────────────────
79+
# plan_tune_hooks: prompt # Controls whether ./setup installs the plan-tune
80+
# # Claude Code hooks (PostToolUse capture +
81+
# # PreToolUse preference enforcement).
82+
# # prompt — ask on a real TTY, skip otherwise (default)
83+
# # yes — install non-interactively
84+
# # no — skip non-interactively
85+
# # Override per-run: ./setup --plan-tune-hooks /
86+
# # --no-plan-tune-hooks, or env GSTACK_PLAN_TUNE_HOOKS.
87+
#
7888
# ─── Advanced ────────────────────────────────────────────────────────
7989
# codex_reviews: enabled # disabled = skip Codex adversarial reviews in /ship
8090
# gstack_contributor: false # true = file field reports when gstack misbehaves
@@ -110,6 +120,8 @@ lookup_default() {
110120
cross_project_learnings) echo "" ;; # intentionally empty → unset triggers first-time prompt
111121
artifacts_sync_mode) echo "off" ;;
112122
artifacts_sync_mode_prompted) echo "false" ;;
123+
plan_tune_hooks) echo "prompt" ;; # prompt | yes | no — controls ./setup plan-tune hook install
124+
113125
redact_repo_visibility) echo "" ;; # empty → fall through to gh/glab detection
114126
redact_prepush_hook) echo "false" ;;
115127
# Brain-aware planning (v1.48 / T5+T10+T16). Defaults documented inline:
@@ -286,6 +298,10 @@ case "${1:-}" in
286298
echo "Warning: redact_prepush_hook '$VALUE' not recognized. Valid values: true, false. Using false." >&2
287299
VALUE="false"
288300
fi
301+
if [ "$KEY" = "plan_tune_hooks" ] && [ "$VALUE" != "prompt" ] && [ "$VALUE" != "yes" ] && [ "$VALUE" != "no" ]; then
302+
echo "Warning: plan_tune_hooks '$VALUE' not recognized. Valid values: prompt, yes, no. Using prompt." >&2
303+
VALUE="prompt"
304+
fi
289305
mkdir -p "$STATE_DIR"
290306
# Write annotated header on first creation
291307
if [ ! -f "$CONFIG_FILE" ]; then
@@ -315,7 +331,7 @@ case "${1:-}" in
315331
for KEY in proactive routing_declined telemetry auto_upgrade update_check \
316332
skill_prefix checkpoint_mode checkpoint_push explain_level \
317333
codex_reviews gstack_contributor skip_eng_review workspace_root \
318-
artifacts_sync_mode artifacts_sync_mode_prompted; do
334+
artifacts_sync_mode artifacts_sync_mode_prompted plan_tune_hooks; do
319335
VALUE=$(grep -E "^${KEY}:" "$CONFIG_FILE" 2>/dev/null | tail -1 | awk '{print $2}' | tr -d '[:space:]' || true)
320336
SOURCE="default"
321337
if [ -n "$VALUE" ]; then
@@ -331,7 +347,7 @@ case "${1:-}" in
331347
for KEY in proactive routing_declined telemetry auto_upgrade update_check \
332348
skill_prefix checkpoint_mode checkpoint_push explain_level \
333349
codex_reviews gstack_contributor skip_eng_review workspace_root \
334-
artifacts_sync_mode artifacts_sync_mode_prompted; do
350+
artifacts_sync_mode artifacts_sync_mode_prompted plan_tune_hooks; do
335351
printf ' %-24s %s\n' "$KEY:" "$(lookup_default "$KEY")"
336352
done
337353
;;

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.53.0.0",
3+
"version": "1.53.1.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",

setup

Lines changed: 80 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ SKILL_PREFIX=1
8282
SKILL_PREFIX_FLAG=0
8383
TEAM_MODE=0
8484
NO_TEAM_MODE=0
85+
PLAN_TUNE_HOOKS_MODE="" # "" = resolve from env/config/prompt; "yes"/"no" = explicit
8586
while [ $# -gt 0 ]; do
8687
case "$1" in
8788
--host) [ -z "$2" ] && echo "Missing value for --host (expected claude, codex, kiro, factory, opencode, openclaw, hermes, gbrain, or auto)" >&2 && exit 1; HOST="$2"; shift 2 ;;
@@ -91,6 +92,9 @@ while [ $# -gt 0 ]; do
9192
--no-prefix) SKILL_PREFIX=0; SKILL_PREFIX_FLAG=1; shift ;;
9293
--team) TEAM_MODE=1; shift ;;
9394
--no-team) NO_TEAM_MODE=1; shift ;;
95+
--plan-tune-hooks) PLAN_TUNE_HOOKS_MODE="yes"; shift ;;
96+
--no-plan-tune-hooks) PLAN_TUNE_HOOKS_MODE="no"; shift ;;
97+
--plan-tune-hooks=*) PLAN_TUNE_HOOKS_MODE="${1#--plan-tune-hooks=}"; shift ;;
9498
-q|--quiet) QUIET=1; shift ;;
9599
*) shift ;;
96100
esac
@@ -1304,14 +1308,65 @@ if [ "$NO_TEAM_MODE" -ne 1 ] \
13041308
ALREADY_INSTALLED=1
13051309
fi
13061310

1311+
# Resolve the desired action without ever blocking.
1312+
# Priority: CLI flag (--plan-tune-hooks / --no-plan-tune-hooks)
1313+
# > env (GSTACK_PLAN_TUNE_HOOKS=yes|no)
1314+
# > saved config (plan_tune_hooks)
1315+
# > smart default ("prompt" → timed prompt on a real TTY, else skip).
1316+
# This guarantees scripted/workspace setups (conductor, CI) are never
1317+
# interactive: pass --no-plan-tune-hooks (or --plan-tune-hooks) and the
1318+
# block runs to completion with no `read`.
1319+
PT_DECISION="$PLAN_TUNE_HOOKS_MODE"
1320+
[ -z "$PT_DECISION" ] && PT_DECISION="${GSTACK_PLAN_TUNE_HOOKS:-}"
1321+
[ -z "$PT_DECISION" ] && PT_DECISION="$("$GSTACK_CONFIG" get plan_tune_hooks 2>/dev/null || true)"
1322+
# Normalize: strip whitespace + lowercase so "YES", "Yes", " yes" from a flag
1323+
# or env var all resolve correctly (an unrecognized opt-in must NOT silently
1324+
# downgrade to skip). Unknown values fall through to "prompt".
1325+
PT_DECISION=$(printf '%s' "$PT_DECISION" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')
1326+
case "$PT_DECISION" in
1327+
y|yes|true|install|on|1) PT_DECISION="yes" ;;
1328+
n|no|false|skip|off|0) PT_DECISION="no" ;;
1329+
*) PT_DECISION="prompt" ;;
1330+
esac
1331+
1332+
_install_plan_tune_hooks() {
1333+
"$SETTINGS_HOOK" add-event \
1334+
--event PostToolUse \
1335+
--matcher '(AskUserQuestion|mcp__.*__AskUserQuestion)' \
1336+
--command "$PLAN_TUNE_LOG_HOOK" \
1337+
--source plan-tune-cathedral \
1338+
--timeout 5
1339+
"$SETTINGS_HOOK" add-event \
1340+
--event PreToolUse \
1341+
--matcher '(AskUserQuestion|mcp__.*__AskUserQuestion)' \
1342+
--command "$PLAN_TUNE_PREF_HOOK" \
1343+
--source plan-tune-cathedral \
1344+
--timeout 5
1345+
}
1346+
13071347
if [ "$ALREADY_INSTALLED" -eq 1 ]; then
13081348
log ""
13091349
log "Plan-tune hooks already installed. Run \`$SETTINGS_HOOK list-sources\` to inspect."
1350+
elif [ "$PT_DECISION" = "yes" ]; then
1351+
# Explicit opt-in (flag / env / config). Non-interactive.
1352+
_install_plan_tune_hooks
1353+
log ""
1354+
log "Plan-tune hooks installed. Run /plan-tune anytime to inspect."
1355+
touch "$PLAN_TUNE_INSTALL_MARKER"
1356+
elif [ "$PT_DECISION" = "no" ]; then
1357+
# Explicit opt-out (flag / env / config). Non-interactive.
1358+
log ""
1359+
log "Plan-tune cathedral hooks not installed (opted out)."
1360+
log "Install later with: ./setup --plan-tune-hooks (or /update-config)."
1361+
touch "$PLAN_TUNE_INSTALL_MARKER"
13101362
elif [ -f "$PLAN_TUNE_INSTALL_MARKER" ]; then
13111363
# Previously declined. Don't re-ask. User can re-enable via /update-config.
13121364
:
1313-
elif [ -t 0 ] && [ -t 1 ]; then
1314-
# Interactive install with explicit consent + diff preview.
1365+
elif [ "$QUIET" -ne 1 ] && [ -t 0 ] && [ -t 1 ]; then
1366+
# Real interactive terminal with no recorded preference: ask, with explicit
1367+
# consent + diff preview. The read is time-bounded and defaults to "skip" so
1368+
# it can never hang an automated/forwarded TTY (the conductor failure mode).
1369+
_PT_PROMPT_TIMEOUT=10 # single source of truth for the read + the countdown text
13151370
log ""
13161371
log "──────────────────────────────────────────────────────────"
13171372
log "Plan-tune cathedral: install Claude Code hooks?"
@@ -1336,33 +1391,32 @@ if [ "$NO_TEAM_MODE" -ne 1 ] \
13361391
log "Backup: settings.json.bak.<ts> written before any mutation."
13371392
log "Rollback: $SETTINGS_HOOK rollback"
13381393
log ""
1339-
printf "Install both hooks now? [y/N] "
1340-
read -r PLAN_TUNE_INSTALL_REPLY
1341-
if [ "$PLAN_TUNE_INSTALL_REPLY" = "y" ] || [ "$PLAN_TUNE_INSTALL_REPLY" = "Y" ]; then
1342-
"$SETTINGS_HOOK" add-event \
1343-
--event PostToolUse \
1344-
--matcher '(AskUserQuestion|mcp__.*__AskUserQuestion)' \
1345-
--command "$PLAN_TUNE_LOG_HOOK" \
1346-
--source plan-tune-cathedral \
1347-
--timeout 5
1348-
"$SETTINGS_HOOK" add-event \
1349-
--event PreToolUse \
1350-
--matcher '(AskUserQuestion|mcp__.*__AskUserQuestion)' \
1351-
--command "$PLAN_TUNE_PREF_HOOK" \
1352-
--source plan-tune-cathedral \
1353-
--timeout 5
1354-
log ""
1355-
log "Plan-tune hooks installed. Run /plan-tune anytime to inspect."
1356-
else
1357-
log ""
1358-
log "Skipped. Re-run ./setup or use /update-config to install later."
1359-
fi
1360-
touch "$PLAN_TUNE_INSTALL_MARKER"
1394+
printf "Install both hooks now? [y/N] (default: N, auto-skips in %ss): " "$_PT_PROMPT_TIMEOUT"
1395+
read -t "$_PT_PROMPT_TIMEOUT" -r PLAN_TUNE_INSTALL_REPLY </dev/tty 2>/dev/null || PLAN_TUNE_INSTALL_REPLY=""
1396+
case "$PLAN_TUNE_INSTALL_REPLY" in
1397+
y|Y)
1398+
_install_plan_tune_hooks
1399+
log ""
1400+
log "Plan-tune hooks installed. Run /plan-tune anytime to inspect."
1401+
touch "$PLAN_TUNE_INSTALL_MARKER"
1402+
;;
1403+
n|N)
1404+
log ""
1405+
log "Skipped. Re-run ./setup --plan-tune-hooks or use /update-config to install later."
1406+
touch "$PLAN_TUNE_INSTALL_MARKER"
1407+
;;
1408+
*)
1409+
# Empty / timed out — treat as "ask me again" (don't persist a decline).
1410+
log ""
1411+
log "No response — skipped for now. Re-run ./setup --plan-tune-hooks to install."
1412+
;;
1413+
esac
13611414
else
1362-
# Non-interactive (CI, scripted setup). Don't prompt; print one-liner.
1415+
# Non-interactive (CI, scripted/workspace setup, quiet). Never prompt.
13631416
log ""
13641417
log "Plan-tune cathedral hooks not installed (non-interactive setup)."
1365-
log "Install with:"
1418+
log "Install with: ./setup --plan-tune-hooks"
1419+
log " (or set GSTACK_PLAN_TUNE_HOOKS=yes, or run the commands below)"
13661420
log " $SETTINGS_HOOK add-event --event PostToolUse \\"
13671421
log " --matcher '(AskUserQuestion|mcp__.*__AskUserQuestion)' \\"
13681422
log " --command $PLAN_TUNE_LOG_HOOK --source plan-tune-cathedral --timeout 5"

0 commit comments

Comments
 (0)