Plan-tune cathedral T16 (per D12 — all 5 in gate tier). One consolidated
file with five describeIfSelected scenarios, each selectable by its own
touchfile entry so they only run when the relevant code changes (or
EVALS_ALL=1 forces all):
plan-tune-hook-capture — PostToolUse hook fires → question-log fills
plan-tune-enforcement — never-ask + marker + 2-way → deny+reason
+ auto-decided event logged
plan-tune-annotation — declared profile + memory nugget
→ additionalContext surfaced on defer
plan-tune-codex-import — synthetic JSONL → import bin → log with
source=codex-import-marker
plan-tune-dream-cycle — apply proposal → re-fire question
→ memory injected via additionalContext
Each scenario fixtures an isolated git repo + bins + scripts + hooks
under tmp, then exercises the cathedral chain end-to-end against real
on-disk binaries (no mocks at the bin layer). GSTACK_STATE_ROOT keeps
the user's real ~/.gstack untouched.
These five complement the existing unit tests by proving the full
sub-process chain works (not just individual functions in isolation).
They DON'T spawn claude -p because the cathedral's substrate behavior is
deterministic — agent compliance is no longer the variable. The existing
test/skill-e2e-plan-tune.test.ts (plan-tune-inspect) still covers the
LLM-driven intent-routing behavior.
Cost: each scenario runs in ~1s with $0 because no claude -p invocations.
Touchfile-gated, so they only run on PRs that touch cathedral code.
Also fixes a bug found by the E2E: question-log-hook didn't pass the
incoming tool call's cwd to spawnSync when invoking gstack-question-log,
so the bin used the hook process's cwd (the repo root) instead of the
session's cwd. Result: log writes landed in the wrong project bucket.
Fix mirrors the same cwd-passing pattern from question-preference-hook.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan-tune cathedral T5. Closes the substrate hole that motivated this
entire branch: agent-compliance-only logging produced zero events in weeks
of dogfood. PostToolUse hook captures every AUQ fire deterministically.
What ships:
- hosts/claude/hooks/question-log-hook.ts — TS hook that reads Claude
Code's hook stdin, walks tool_input.questions[*], extracts user choice
+ recommended option from tool_response, spawns gstack-question-log per
question.
- hosts/claude/hooks/question-log-hook — bash shim Claude Code's hook
runner invokes; execs bun against the .ts file.
- Marker-first question_id extraction (D18 progressive markers):
<gstack-qid:foo-bar> stripped from question text, used as the id.
Hash fallback hook-<sha1[:10]> for unmarked questions (observed-only,
never used as preference key — D18 hash drift mitigation).
- (recommended) label parsing for the user_choice/recommended fields,
with refuse-on-ambiguous when two labels are present (D2 safety).
- Free-text capture: source=auq-other + free_text field when user picks
Other and types (Layer 8 dream cycle input).
- Matcher covers both native AskUserQuestion and mcp__*__AskUserQuestion
(Codex/Conductor catch from outside voice review).
- Crash safety: always exits 0; errors land in ~/.gstack/hook-errors.log
so the user's session is never blocked by a hook failure.
gstack-question-log extended to:
- Accept `source` field (default 'agent', new values: hook, auq-other,
auto-decided, codex-import-marker, codex-import-pattern).
- Accept `tool_use_id` (<=128 chars) for dedup.
- Composite dedup on (source, tool_use_id) across the last 100 lines —
protects against hook + preamble both firing on the same tool call
(D3 belt+suspenders).
- Async fire `gstack-developer-profile --derive` after each successful
write so inferred.sample_size actually grows (D17 — without this, the
cathedral's "before 0, after >0" metric never moves).
- GSTACK_QUESTION_LOG_NO_DERIVE=1 escape hatch for tests.
9 new unit tests covering capture, marker extraction, MCP variant,
free-text, dedup, ambiguous-recommended safety, crash paths. All pass
plus the existing 88 tests across related files.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>