mirror of
https://github.com/garrytan/gstack.git
synced 2026-06-01 15:51:41 +02:00
ce5fbfa99f
* feat(plan-tune): explicit-consent surface + setup gate for question_tuning Step 0 grows two implicit gates that run before user-intent routing: - Consent gate: question_tuning=false + no marker → offer opt-in (contributor-specific copy variant) - Setup gate: question_tuning=true + declared empty + no marker → run 5-Q wizard Markers (~/.gstack/.question-tuning-prompted, ~/.gstack/.declared-setup-prompted) ensure each user is asked at most once. The Enable+setup section split into "Consent + opt-in" (with contributor framing) and standalone "5-Q setup" reachable from both the consent flow and the setup gate. Also aligns the calibration gate across three docs (V0 said 90+ days, TODOS said 2+ weeks, binary uses 7 days). The fix distinguishes: - Display gate (sample_size>=20, skills>=3, question_ids>=8, days_span>=7): for rendering inferred values in /plan-tune output - Promotion gate (90+ days stable across 3+ skills): for shipping E1 behavior-adapting defaults TODOS.md E1 card updated to reference 90+ days, plus Codex's substrate risk note: generated skill prose is agent-compliance-based, so E1 ships as advisory annotations on AskUserQuestion recommendations, not silent AUTO_DECIDE. Tests can verify templates contain right reads but can't prove agents obey them. Per /plan-eng-review + Codex outside-voice 2026-05-26. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore: bump version and changelog (v1.49.0.0) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(bins): honor GSTACK_STATE_ROOT override for test isolation Plan-tune cathedral T1 (per D16 / Codex outside voice). The 3 bins that back /plan-tune (question-log, question-preference, developer-profile) previously ignored GSTACK_STATE_ROOT, so tests that tried to point state at a tempdir via that env var silently wrote to the real ~/.gstack. Make STATE_ROOT take precedence over GSTACK_HOME so the cathedral's E2E + unit tests can isolate cleanly without sledgehammering HOME. Order of precedence: GSTACK_STATE_ROOT > GSTACK_HOME > $HOME/.gstack Matches the existing gstack-paths emission order. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(plan-tune): regression coverage for v1.49 consent + setup gates Plan-tune cathedral T2 + part of T1 follow-up (Codex IRON RULE — regressions get tests). v1.49 shipped two prose-driven implicit gates inside plan-tune Step 0 (consent, setup) with zero test coverage. The cathedral refactors that template heavily; without tests, silent breakage is possible. Three regression families plus a static template assertion: 1. Consent gate fires under qt=false + no marker; goes silent on marker write or qt=true flip. 2. Setup gate fires under qt=true + empty declared + no marker; goes silent when declared populates, marker is written, or qt is still false. 3. Marker idempotency: gates stay silent across 5 re-invocations after a single decline/bail. Markers honored independently. 4. Static template assertion: gate language can't be silently deleted without breaking a test. Also extends gstack-config to honor GSTACK_STATE_ROOT (it was the last bin still ignoring it — caught while writing the tests; without this, tests would silently mutate the user's real config.yaml). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(spikes): Claude hook mutation + Codex session format Plan-tune cathedral T4 (per D5/D10). Two Phase 1 design spikes that downstream tasks (T3, T5, T6, T8, T9) depend on. claude-code-hook-mutation.md - Confirms PreToolUse allow + updatedInput is supported and is the right mechanism for substituting an auto-decided answer. - Pins stdin/stdout JSON schemas with field-by-field reference. - Documents matcher regex syntax for "(AskUserQuestion|mcp__.*__AskUserQuestion)" so Conductor's MCP-routed AUQ is covered. - Captures parallel-hook merge order caveat and our settings.json snippet. codex-session-format.md - Maps the on-disk ~/.codex/sessions/<date>/rollout-*.jsonl schema by event type (response_item 76%, event_msg 19%, turn_context, session_meta). - Critical finding: Codex has NO AskUserQuestion tool. Gstack AUQ-shaped Decision Briefs surface as agent_message text; answer is the next user_message. Two-tier recovery: marker-first (D18), then pattern fallback for hash-only logging. - Confirms logs_2.sqlite is internal telemetry, not session content. - Lists open questions to answer during T9 implementation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(settings-hook): schema-aware PreToolUse/PostToolUse registration Plan-tune cathedral T3 (per D4 + Codex correction). The previous bin only knew SessionStart and dedup'd on the hardcoded `gstack-session-update` substring. The cathedral needs PreToolUse + PostToolUse hooks registered side-by-side with the user's own hooks, with explicit consent UX, backups, and rollback. New subcommands: - add-event --event <SessionStart|PreToolUse|PostToolUse|...> --command <cmd> --source <tag> [--matcher <re>] [--timeout <s>] - remove-source --source <tag> # removes all entries tagged by source - diff-event ... # preview without mutating - rollback # restore latest backup - list-sources # audit gstack-tagged hooks Multi-source dedup via a new `_gstack_source` field on each hook entry (Claude Code preserves unknown fields). Source tag lets plan-tune-cathedral register PreToolUse + PostToolUse without colliding with the existing SessionStart wiring, and lets remove-source clean up cleanly during gstack-uninstall. Backups written automatically to settings.json.bak.<ts> before any mutation, with a .bak-latest pointer the rollback subcommand reads. Existing legacy `add <cmd>` / `remove <cmd>` shape preserved verbatim so setup --team and gstack-uninstall keep working unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(hooks): PostToolUse capture hook for AskUserQuestion 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> * feat(hooks): PreToolUse enforcement hook for AskUserQuestion preferences Plan-tune cathedral T6 — the keystone that makes never-ask actually bind. Today preferences are agent-convention (silently ignored). This hook enforces them via Claude Code's hook protocol: when a never-ask preference matches an AUQ that is two-way + has a marker + has a clear recommendation, the hook returns permissionDecision: "deny" with permissionDecisionReason naming the auto-decided option. The agent obeys the rejection feedback and proceeds with the recommended option without re-firing AUQ. Decision tree (per question): - marker absent → defer (D18: hash IDs are observed-only) - one-way door → defer (safety override — never auto-decide one-way) - always-ask preference → defer - no preference set → defer - ambiguous recommendation (two (recommended) labels OR no parseable rec) → defer (D2 refuse-on-ambiguous) - never-ask / ask-only-for-one-way + two-way + clean rec → deny+reason Preference precedence per D8: project-local (~/.gstack/projects/<slug>/question-preferences.json) wins, global (~/.gstack/global-question-preferences.json) is fallback. Why deny+reason instead of allow+updatedInput: AskUserQuestion's updatedInput shape for "pre-resolve this question" isn't structurally pinned in Claude Code docs (T4 spike open question). deny with a reason that names the auto-decided option is the conservative + reliable v1 — the model receives the rejection, reads the recommended option from the reason, proceeds without re-prompting. Swap to allow+updatedInput once the AUQ input shape is verified against real Claude Code. Since deny prevents PostToolUse from firing, this hook logs the auto-decided event itself via gstack-question-log (source=auto-decided) so /plan-tune's Recent auto-decisions surface picks it up. Also writes a session marker ~/.gstack/sessions/<id>/.auto-decided-<tool_use_id> for coordination when the AUQ-shape switch lands. Multi-question AUQ: enforcement is all-or-nothing per call. If any question in the batch isn't eligible (no marker, no preference, ambiguous rec, etc.), the whole call defers so the user still gets to answer the rest normally. Registry lookup: cheap regex extraction from scripts/question-registry.ts (reading + bun-importing the TS file from a hook is too slow). Door type defaults to two-way for unregistered. Matcher covers both native AskUserQuestion and mcp__*__AskUserQuestion (Conductor disables native — Codex outside-voice catch). 15 unit tests cover defer paths, enforcement, one-way safety override, ambiguous-rec refuse, precedence (project wins, global fallback, project-overrides-global), MCP matcher, auto-decided event logging, session marker writing, crash safety. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(scripts): declared-annotation helper + autonomy signal_key wiring Plan-tune cathedral T7. Adds the helper that lets skills inject one-line plain-English annotations on AUQ recommendations based on the user's declared profile — read-only, advisory-only, per TODOS.md E1 substrate-risk guidance (no AUTO_DECIDE off inferred). scripts/declared-annotation.ts - getDeclaredAnnotation(signal_key) → annotation | null - primaryDimensionFor(signal_key) → Dimension | null - Signature uses kebab signal_key per D2/Codex correction (registry uses hyphens; profile dimensions use underscores; helper maps internally). - Bands: >= 0.7 high, <= 0.3 low, else null. Middle band stays silent. - Per-dimension plain-English phrasing: 5 dimensions × 2 bands = 10 phrases. - Reads ~/.gstack/developer-profile.json (honors GSTACK_STATE_ROOT). scripts/psychographic-signals.ts - New signal_key 'decision-autonomy' that maps user_choice → autonomy dimension nudges. This was the missing signal for the 'autonomy' dimension — without it, the cathedral could annotate four of five declared dimensions but autonomy stayed silent. scripts/question-registry.ts - Add signal_key: 'decision-autonomy' to land-and-deploy-merge-confirm and land-and-deploy-rollback. These are the highest-leverage autonomy questions in the surface — "let me decide" vs "go ahead" is exactly what the dimension captures. 13 unit tests cover the helper's full contract (unknown keys, missing profile, middle-band null, both band thresholds, all five dimensions rendering distinct phrases). Existing 47 plan-tune.test.ts tests still pass after the registry + signal-map enrichment. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(setup): install plan-tune cathedral hooks with explicit consent UX Plan-tune cathedral T8. Wires the new PostToolUse capture hook and PreToolUse enforcement hook into ~/.claude/settings.json via the schema-aware gstack-settings-hook (T3) — respecting D4's "never mutate settings.json silently" boundary and the Codex outside-voice warning. Behavior at setup time: - Idempotency: if list-sources already shows 'plan-tune-cathedral', no-op with a one-line note. - Marker present (previously declined): no-op, no re-prompt. - Interactive terminal: print rationale + diff preview from settings-hook, rollback command, and prompt y/N. On accept, register both hooks (PostToolUse and PreToolUse) with --source plan-tune-cathedral. On decline, touch ~/.gstack/.plan-tune-hooks-prompted so we don't re-ask. - Non-interactive (CI / scripted): no prompt; print the two exact commands the user would need to install manually. - --no-team teardown also removes the plan-tune hooks via remove-source. gstack-uninstall extended to clean up plan-tune-cathedral hooks alongside the existing SessionStart cleanup. Listed as a separate "plan-tune cathedral hooks" line in the REMOVED summary when it fires. No new test file — coverage from T3's gstack-settings-hook-schema-aware tests proves the underlying bin behavior; setup-level integration is verified manually (re-running ./setup is cheap and the prompt makes it obvious whether install happened). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(bin): gstack-codex-session-import — structured Codex transcript parser Plan-tune cathedral T9. Backfills question-log.jsonl from Codex sessions since Codex has no AskUserQuestion tool (per docs/spikes/codex-session-format.md) and gstack AUQ-shaped Decision Briefs show up as agent_message prose. Walks ~/.codex/sessions/<date>/rollout-*.jsonl, matches each agent_message that contains either a <gstack-qid:foo-bar> marker or a D-numbered Decision Brief header, then pairs it with the next user_message for the answer. Two-tier recovery per D5: - marker present → source=codex-import-marker, stable question_id - no marker but D-shape detected → source=codex-import-pattern with hash-only question_id (never used as preference key per D18) Subcommands: gstack-codex-session-import # latest session gstack-codex-session-import <file> # explicit path gstack-codex-session-import --since <iso> # all sessions newer than User-choice extraction handles A/B/C letter responses and prose responses that start with the option label. Recommended option parsed via the "(recommended)" label suffix (same convention as Layer 2). Each extracted event written via gstack-question-log, so source tagging, dedup, and async derive all apply uniformly. spawnSync uses the cwd from session_meta so gstack-slug buckets events into the project the user was actually working in, not the importer's cwd. 7 unit tests cover marker path, pattern fallback, multiple briefs in sequence, missing user_message, numeric/letter user response forms, empty-sessions-dir handling. Smoke-tested against a real ~/.codex/sessions/ file from earlier today — returns IMPORTED: 0 because that session was autonomous (no AUQ-shaped prose), proving the bin doesn't false-positive on unrelated agent_message events. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(bin): gstack-distill-free-text — Layer 8 dream cycle distiller Plan-tune cathedral T10. Reads auq-other free-text events from this project's question-log.jsonl, calls Claude via the Anthropic SDK to extract structured proposals (preference candidates, declared-profile nudges, memory nuggets), writes them to distillation-proposals.json for the user to review via /plan-tune (never autonomous — every apply requires explicit Y). Subcommands: gstack-distill-free-text # sync distill gstack-distill-free-text --background # detach + return PID gstack-distill-free-text --dry-run # emit prompt + events, no API call gstack-distill-free-text --status # run history + cost-to-date D7 rate cap: 3 distills per slug per day. Reads ~/.gstack/distill-cost.jsonl for the count, exits with RATE_CAPPED when limit hit. Cost log lines tagged by slug so sibling projects don't share the cap. Yesterday runs don't count. D6 API auth: Anthropic SDK direct, fail-loud on missing ANTHROPIC_API_KEY with explicit message that distill is a separate billing surface from the interactive Claude Code session. Uses claude-haiku-4-5 for cost (~$0.001/ 1k input, $0.005/1k output) — sufficient for structured extraction. D14 execution context: --background spawns detached (nohup) so auto-trigger during /ship doesn't add 30s of pause; results surface on next /plan-tune. Source events get distilled_at:<ts> stamped on them after the run so they don't re-propose on the next distill. Match by ts + question_id. Cost-log line per run includes: slug, proposals_count, rejected_low_confidence, input_tokens, output_tokens, cost_usd_est. /plan-tune stats reads this to show "$X estimated, N runs this month" per Layer 4 surface. 10 unit tests cover --status, rate cap (3/day, yesterday-not-counted, other-slug-not-counted), no-log/no-free-text paths, --dry-run, missing API key, --background spawn. The actual SDK call is exercised by the T16 E2E test (uses real key, ~$0.001 per run). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(bin): gstack-distill-apply — apply distillation proposals with gbrain tag Plan-tune cathedral T11. Bin that applies a single user-approved proposal from distillation-proposals.json to the right surface: - memory-nugget → appended to ~/.gstack/free-text-memory.json (durable local source-of-truth; gbrain is mirror when configured). - preference → routed through gstack-question-preference --write with source=plan-tune (clears the user-origin gate). - declared-nudge → atomic update to developer-profile.json declared dim, small=0.05, medium=0.10, large=0.15, clamped to [0, 1]. Why a separate bin (not inline in the skill template): /plan-tune's apply step needs to be invokable from any host (Claude, Codex, etc) and must write to multiple state files atomically. A bin centralizes the schema + clamp logic; the skill template just calls it after user Y. gbrain coordination: --gbrain-published true marks the nugget so /plan-tune stats can show "12 nuggets, 8 mirrored to gbrain". The skill template invokes mcp__gbrain__put_page / extract_facts / add_tag in the same turn (those are MCP tools, not CLI-callable) before calling this bin. Local file remains canonical so the PreToolUse hook injection path (T12) doesn't depend on gbrain availability. Subcommands: gstack-distill-apply --list # show pending proposals gstack-distill-apply --proposal <N> # apply, file fallback gstack-distill-apply --proposal <N> --gbrain-published true Applied proposals get applied_at + gbrain_published stamped on them so re-running --list shows only unconsumed ones. 11 unit tests cover --list (all three kinds + quotes), memory-nugget append + non-clobber, preference routing through the gate-respecting bin, declared-nudge math (medium=0.10, small=0.05, large=0.15, clamp at [0,1]), proposal mark-applied with gbrain flag, and error paths (bad index, missing --proposal). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(hooks): Layer 8 memory injection via per-session cache Plan-tune cathedral T12. Extends the PreToolUse hook to inject matching free-text-memory.json nuggets into AskUserQuestion responses, giving the agent + user the distilled context from past 'Other' answers right when the related question fires. Per-session cache (D13 perf): first read of free-text-memory.json writes ~/.gstack/sessions/<id>/memory-cache.json. Subsequent hooks on the same session take the cached path. Invalidation is by file-missing: when the canonical file changes (via gstack-distill-apply), the per-session cache either reflects the staler view for the rest of the session or the session restarts and the cache rebuilds. Cheap, correct enough for v1. Matching logic: - Walk this AUQ batch's questions, extract marker question_ids. - Look up signal_key in scripts/question-registry.ts. - Collect nuggets whose applies_to_signal_keys include any of the matched signal_keys. - Cap to 3 most-recent (by applied_at) so the additionalContext stays short. - Surface as additionalContext on the hookSpecificOutput response. Memory + enforcement interact cleanly: the same hook can both surface nuggets AND deny the tool when a never-ask preference matches. Memory context isn't doubled in the deny reason — the auto-decided option name in the deny path is sufficient signal. 6 new tests cover injection on defer, no-match silence, 3-most-recent cap, memory-alongside-deny enforcement, cache file write-through, empty-canonical graceful degradation. Existing 15 preference-hook tests still green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(plan-tune): SKILL.md surfaces for cathedral T13 Plan-tune cathedral T13. Rewires plan-tune/SKILL.md.tmpl to expose the new cathedral surfaces: Step 0 routing: - Implicit gate #3 (dream-cycle): fires when distillation-proposals.json has unapplied proposals. Marker is per-proposal applied_at so re-firing naturally skips already-handled items. - Added user-intent route for "dream cycle" / "distill" / "what have I been free-texting". - Power-user shortcuts: distill, dream, audit. Stats: - Host-aware source breakdown (SOURCE_HOOK, SOURCE_AGENT, SOURCE_AUTO_DECIDED, SOURCE_CODEX_IMPORT_*, SOURCE_AUQ_OTHER). - MARKED percentage so D18 progressive-markers progress is visible. - Distill cost-to-date via gstack-distill-free-text --status. Recent auto-decisions: - Last 10 source=auto-decided events with question_id + user_choice. Lets the user spot-check enforcement and flip via always-ask. Audit unmarked questions: - Top N hash-only ids by frequency. Surfaces next candidates for the D18 marker retrofit. Dream cycle review + manual distill: - Walks unapplied proposals via AskUserQuestion (one per call), routes accepts through gstack-distill-apply with --gbrain-published flag. Skill template invokes mcp__gbrain__put_page when MCP is available; local file remains source-of-truth. Regenerated SKILL.md via `bun run gen:skill-docs`. All 60 plan-tune tests still green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(preamble): inject <gstack-qid:...> marker convention into question-tuning resolver Plan-tune cathedral T14. Per D18 progressive markers, the PreToolUse enforcement hook only fires when the AUQ question text contains a <gstack-qid:foo-bar> marker the hook can extract. Without a marker, the hook logs the fire as observed-only and skips enforcement (hash IDs drift with prose so they're never used as preference keys). The high-leverage retrofit point is the preamble's Question Tuning section, not 10 individual skill templates. Updating scripts/resolvers/question-tuning.ts adds the marker convention to every tier-≥2 skill in one change — agents running ANY of the 30+ tier-≥2 skills now embed the marker by default when the question matches a registered question_id. Two convention additions in the preamble: 1. "Embed the question_id as a marker (<gstack-qid:{id}>) somewhere in the rendered question." With explanation that the marker is the only path for the PreToolUse hook to enforce preferences. 2. "Embed the option recommendation via the (recommended) label suffix on exactly one option per AUQ." Documents the D2 parser contract: label first, prose fallback, refuse-on-ambiguous. Net cost: ~700 bytes added to the preamble per generated skill. Plan-review preamble budget ratcheted from 39000 → 40000 (test/gen-skill-docs.test.ts) with a comment explaining the cathedral T14 expansion is load-bearing. Regenerated 42 SKILL.md files via `bun run gen:skill-docs`. The token ceiling warning on ship/SKILL.md (~41K tokens) is pre-existing; this PR doesn't change ship's preamble materially. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ship): plan-tune discoverability nudge after first successful ship Plan-tune cathedral T15 (the ship-side surface; the setup-side surface shipped in T8 with explicit hook-install consent UX). Adds Step 21 to ship/SKILL.md.tmpl: after Step 20 (persist metrics) succeeds, surface /plan-tune once per machine via a marker-gated single-line nudge. Behavior: - If ~/.gstack/.plan-tune-nudge-shown exists → no-op. - If question_tuning is already true → no-op (user already on board). - Otherwise: print one nudge line, touch marker. The nudge mentions both the observational substrate AND the hook-installed auto-decide enforcement so users know what they get when they opt in. Non-blocking — never asks a question, doesn't gate ship completion. To re-show: rm ~/.gstack/.plan-tune-nudge-shown before next ship. Setup-side discoverability shipped in T8 via the hook install prompt (explicit consent + diff preview + backup). Together these two surfaces cover first-install AND first-ship moments — the user discovers plan-tune organically rather than needing to know /plan-tune exists. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(plan-tune): 5 cathedral E2E scenarios + touchfile registration 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> * chore: bump VERSION to 1.50.0.0 + plan-tune cathedral CHANGELOG Plan-tune cathedral T17. Bumps VERSION 1.49.0.0 → 1.50.0.0 (MINOR per CLAUDE.md scale-aware rule: this is substantial new capability — 8 layers, ~3000 LOC, 96 new tests, deterministic substrate + dream-cycle distillation). CHANGELOG entry follows the release-summary format from CLAUDE.md: - Two-line bold headline naming what changed for users (deterministic capture, binding preferences, free-text memory loop) - Lead paragraph: before/after framed concretely (zero events captured → every fire, agent-honored → hook-enforced, declared profile → injected context, regex backfill → structured JSONL parser) - Two tables: metric deltas + layer/where-it-lives. Real numbers (96 tests, ~$0.01 per distill, 3/day cap), no AI vocabulary, no em dashes. - "What this means for solo builders" close: ties dream cycle to the compounding loop and points to ./setup as the on-ramp. - Itemized Added/Changed/For contributors sections list every layer's surfaces with file paths. Also: - Refreshed test/fixtures/golden/{claude,codex,factory}-ship-SKILL.md to match the regenerated ship templates (Step 21 nudge added). - Rebased plan-tune entry in parity-baseline-v1.47.0.0.json from 51717 → 64017 bytes with a baseline_note explaining the cathedral T13 expansion. Documents that the new Dream cycle, Recent auto-decisions, Audit unmarked, Dream cycle review/distill sections are load-bearing, not bloat. Without the rebase, the size-budget gate fails — and the cathedral's whole point is making /plan-tune do more, not less. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: bump VERSION 1.50.0.0 → 1.52.0.0 (queue collision with #1742) CI version gate caught: PR #1742 (garrytan/upgrade-gstack-gbrain-v1) already claims v1.50.0.0 and #1751 (garrytan/browser-memory-leak) claims v1.51.0.0. gstack-next-version util recommends v1.52.0.0 as the next free slot. Updates: - VERSION 1.50.0.0 → 1.52.0.0 - package.json version sync - CHANGELOG.md header + metric table label - parity-baseline-v1.47.0.0.json baseline_note reference No content changes; pure slot rebase per the queue. The cathedral scope (8 layers, 96 tests) and CHANGELOG narrative stay identical — same ship, different release number. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: cap audit — remove distill rate cap, loosen size/budget gates Plan-tune cathedral follow-up. The 3/day distill cap was theatrical: at ~$0.01 per Haiku call, even a runaway loop firing every minute would cost ~$14/day, and free-text events are rare enough that the natural input rate self-limits to 1-2 fires/day. Count caps don't protect against runaway bugs (which fire 1000x/second, not 4 times/day) but DO punish heavy users who'd legitimately distill multiple times during a busy week. Removed: 3/day rate cap on bin/gstack-distill-free-text. --status output swapped from "TODAY: N / 3" to "TODAY: N run(s), $X" so users see what they're spending instead of how close they are to a meaningless count. Loosened (caps that exist for real-runaway protection, not normal scope): - EVALS_BUDGET_HARD_CAP_GATE $25 → $200/run - EVALS_BUDGET_HARD_CAP_PERIODIC $70 → $500/run - EVALS_BUDGET_HARD_CAP $30 → $300/run (umbrella fallback) - GSTACK_SIZE_BUDGET_RATIO 1.05 → 1.50 per-skill ratio - plan-review preamble byte budget 40K → 60K Principle: caps exist to catch obvious bugs (infinite retry, model price change, prompt blowup), not to gate legitimate scope growth. Set high enough that real growth never trips them, only bug territory does. Adjusted defaults are 4-8× historical worst case, leaving ample headroom for the next 12 months of legitimate expansion. Tests updated: distill-free-text removes the 3-test rate-cap describe block in favor of "no rate cap" assertion that 10 runs/day pass. Other budget tests still pass because they were never near the old ceilings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
798 lines
49 KiB
TypeScript
798 lines
49 KiB
TypeScript
/**
|
|
* Diff-based test selection for E2E and LLM-judge evals.
|
|
*
|
|
* Each test declares which source files it depends on ("touchfiles").
|
|
* The test runner checks `git diff` and only runs tests whose
|
|
* dependencies were modified. Override with EVALS_ALL=1 to run everything.
|
|
*/
|
|
|
|
import { spawnSync } from 'child_process';
|
|
|
|
// --- Glob matching ---
|
|
|
|
/**
|
|
* Match a file path against a glob pattern.
|
|
* Supports:
|
|
* ** — match any number of path segments
|
|
* * — match within a single segment (no /)
|
|
*/
|
|
export function matchGlob(file: string, pattern: string): boolean {
|
|
const regexStr = pattern
|
|
.replace(/\./g, '\\.')
|
|
.replace(/\*\*/g, '{{GLOBSTAR}}')
|
|
.replace(/\*/g, '[^/]*')
|
|
.replace(/\{\{GLOBSTAR\}\}/g, '.*');
|
|
return new RegExp(`^${regexStr}$`).test(file);
|
|
}
|
|
|
|
// --- Touchfile maps ---
|
|
|
|
/**
|
|
* E2E test touchfiles — keyed by testName (the string passed to runSkillTest).
|
|
* Each test lists the file patterns that, if changed, require the test to run.
|
|
*/
|
|
export const E2E_TOUCHFILES: Record<string, string[]> = {
|
|
// Browse core (+ test-server dependency)
|
|
'browse-basic': ['browse/src/**', 'browse/test/test-server.ts'],
|
|
'browse-snapshot': ['browse/src/**', 'browse/test/test-server.ts'],
|
|
|
|
// SKILL.md setup + preamble (depend on ROOT SKILL.md + gen-skill-docs)
|
|
'skillmd-setup-discovery': ['SKILL.md', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
|
|
'skillmd-no-local-binary': ['SKILL.md', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
|
|
'skillmd-outside-git': ['SKILL.md', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
|
|
|
|
'session-awareness': ['SKILL.md', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
|
|
'operational-learning': ['scripts/resolvers/preamble.ts', 'bin/gstack-learnings-log'],
|
|
|
|
// QA (+ test-server dependency)
|
|
'qa-quick': ['qa/**', 'browse/src/**', 'browse/test/test-server.ts'],
|
|
'qa-b6-static': ['qa/**', 'browse/src/**', 'browse/test/test-server.ts', 'test/helpers/llm-judge.ts', 'browse/test/fixtures/qa-eval.html', 'test/fixtures/qa-eval-ground-truth.json'],
|
|
'qa-b7-spa': ['qa/**', 'browse/src/**', 'browse/test/test-server.ts', 'test/helpers/llm-judge.ts', 'browse/test/fixtures/qa-eval-spa.html', 'test/fixtures/qa-eval-spa-ground-truth.json'],
|
|
'qa-b8-checkout': ['qa/**', 'browse/src/**', 'browse/test/test-server.ts', 'test/helpers/llm-judge.ts', 'browse/test/fixtures/qa-eval-checkout.html', 'test/fixtures/qa-eval-checkout-ground-truth.json'],
|
|
'qa-only-no-fix': ['qa-only/**', 'qa/templates/**'],
|
|
'qa-fix-loop': ['qa/**', 'browse/src/**', 'browse/test/test-server.ts'],
|
|
'qa-bootstrap': ['qa/**', 'ship/**'],
|
|
|
|
// Review
|
|
'review-sql-injection': ['review/**', 'test/fixtures/review-eval-vuln.rb'],
|
|
'review-enum-completeness': ['review/**', 'test/fixtures/review-eval-enum*.rb'],
|
|
'review-base-branch': ['review/**'],
|
|
'review-design-lite': ['review/**', 'test/fixtures/review-eval-design-slop.*'],
|
|
|
|
// Review Army (specialist dispatch)
|
|
'review-army-migration-safety': ['review/**', 'scripts/resolvers/review-army.ts', 'bin/gstack-diff-scope'],
|
|
'review-army-perf-n-plus-one': ['review/**', 'scripts/resolvers/review-army.ts', 'bin/gstack-diff-scope'],
|
|
'review-army-delivery-audit': ['review/**', 'scripts/resolvers/review.ts', 'scripts/resolvers/review-army.ts'],
|
|
'review-army-quality-score': ['review/**', 'scripts/resolvers/review-army.ts'],
|
|
'review-army-json-findings': ['review/**', 'scripts/resolvers/review-army.ts'],
|
|
'review-army-red-team': ['review/**', 'scripts/resolvers/review-army.ts'],
|
|
'review-army-consensus': ['review/**', 'scripts/resolvers/review-army.ts'],
|
|
|
|
// Office Hours
|
|
'office-hours-spec-review': ['office-hours/**', 'scripts/gen-skill-docs.ts'],
|
|
'office-hours-forcing-energy': ['office-hours/**', 'scripts/resolvers/preamble.ts', 'test/fixtures/mode-posture/**', 'test/helpers/llm-judge.ts'],
|
|
'office-hours-builder-wildness': ['office-hours/**', 'scripts/resolvers/preamble.ts', 'test/fixtures/mode-posture/**', 'test/helpers/llm-judge.ts'],
|
|
|
|
// Plan reviews
|
|
'plan-ceo-review': ['plan-ceo-review/**'],
|
|
'plan-ceo-review-selective': ['plan-ceo-review/**'],
|
|
'plan-ceo-review-benefits': ['plan-ceo-review/**', 'scripts/gen-skill-docs.ts'],
|
|
'plan-ceo-review-expansion-energy': ['plan-ceo-review/**', 'scripts/resolvers/preamble.ts', 'test/fixtures/mode-posture/**', 'test/helpers/llm-judge.ts'],
|
|
'plan-eng-review': ['plan-eng-review/**'],
|
|
'plan-eng-review-artifact': ['plan-eng-review/**'],
|
|
'plan-review-report': ['plan-eng-review/**', 'scripts/gen-skill-docs.ts'],
|
|
|
|
// Plan-mode smoke tests — gate-tier safety regression tests. Each test file
|
|
// contains TWO test cases as of v1.21: the baseline plan-mode case and the
|
|
// AskUserQuestion-blocked regression case (--disallowedTools AskUserQuestion
|
|
// parameterized — the flag set Conductor uses by default). Touchfiles
|
|
// include question-tuning.ts and generate-ask-user-format.ts because the
|
|
// AUTO_DECIDE preamble injection lives there and changes can flip the
|
|
// regression test outcome between 'asked' and 'auto_decided'.
|
|
'plan-ceo-review-plan-mode': ['plan-ceo-review/**', 'scripts/resolvers/preamble/generate-completion-status.ts', 'scripts/resolvers/question-tuning.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/review.ts', 'test/helpers/claude-pty-runner.ts'],
|
|
'plan-eng-review-plan-mode': ['plan-eng-review/**', 'scripts/resolvers/preamble/generate-completion-status.ts', 'scripts/resolvers/question-tuning.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/review.ts', 'test/helpers/claude-pty-runner.ts'],
|
|
'plan-design-review-plan-mode': ['plan-design-review/**', 'scripts/resolvers/preamble/generate-completion-status.ts', 'scripts/resolvers/question-tuning.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/review.ts', 'test/helpers/claude-pty-runner.ts'],
|
|
'plan-devex-review-plan-mode': ['plan-devex-review/**', 'scripts/resolvers/preamble/generate-completion-status.ts', 'scripts/resolvers/question-tuning.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/review.ts', 'test/helpers/claude-pty-runner.ts'],
|
|
'plan-mode-no-op': ['plan-ceo-review/**', 'scripts/resolvers/preamble/generate-completion-status.ts', 'scripts/resolvers/preamble.ts', 'test/helpers/claude-pty-runner.ts'],
|
|
|
|
// v1.21+ AskUserQuestion-blocked regression tests — Conductor launches
|
|
// claude with `--disallowedTools AskUserQuestion --permission-mode default`
|
|
// (verified via `ps`); skills must still surface user-decisions through a
|
|
// fallback path (mcp__conductor__AskUserQuestion or plan-file flow) rather
|
|
// than silently auto-deciding. Parameterized regression test cases live
|
|
// INSIDE the existing 4 plan-X-review-plan-mode test files (covered
|
|
// transitively by the entries above). Two new standalone files exist for
|
|
// skills with no prior plan-mode test:
|
|
'office-hours-auto-mode': ['office-hours/**', 'scripts/resolvers/preamble/generate-completion-status.ts', 'scripts/resolvers/question-tuning.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'test/helpers/claude-pty-runner.ts'],
|
|
'office-hours-phase4-fork': ['office-hours/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completion-status.ts', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/question-tuning.ts', 'test/helpers/llm-judge.ts', 'test/skill-e2e-office-hours-phase4.test.ts'],
|
|
'llm-judge-recommendation': ['test/helpers/llm-judge.ts', 'test/llm-judge-recommendation.test.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'codex/SKILL.md.tmpl', 'scripts/resolvers/review.ts'],
|
|
// v1.21+ AUTO_DECIDE preserve eval (periodic). Verifies the Tool resolution
|
|
// fix doesn't trip the legitimate /plan-tune opt-in path: when the user has
|
|
// written a never-ask preference, AUQ should still auto-decide rather than
|
|
// surfacing the question. Touches the question-tuning + preference
|
|
// infrastructure plus the resolvers that own the AUTO_DECIDE preamble.
|
|
'auto-decide-preserved': ['scripts/resolvers/question-tuning.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completion-status.ts', 'plan-ceo-review/**', 'bin/gstack-question-preference', 'bin/gstack-config', 'bin/gstack-slug', 'test/helpers/claude-pty-runner.ts'],
|
|
|
|
// Real-PTY E2E batch (#6 new tests on the harness).
|
|
// Each one tests behavior the SDK harness can't observe (rendered TTY,
|
|
// numbered-option lists, multi-phase ordering, idempotency state echo).
|
|
'ask-user-question-format-pty': ['plan-ceo-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completeness-section.ts', 'scripts/resolvers/preamble.ts', 'test/helpers/claude-pty-runner.ts'],
|
|
'plan-ceo-mode-routing': ['plan-ceo-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'test/helpers/claude-pty-runner.ts'],
|
|
'plan-design-with-ui-scope': ['plan-design-review/**', 'test/fixtures/plans/ui-heavy-feature.md', 'test/helpers/claude-pty-runner.ts'],
|
|
'budget-regression-pty': ['test/helpers/eval-store.ts', 'test/skill-budget-regression.test.ts'],
|
|
'ship-idempotency-pty': ['ship/**', 'bin/gstack-next-version', 'lib/worktree.ts', 'test/helpers/claude-pty-runner.ts'],
|
|
'autoplan-chain-pty': ['autoplan/**', 'plan-ceo-review/**', 'plan-design-review/**', 'plan-eng-review/**', 'plan-devex-review/**', 'test/fixtures/plans/ui-heavy-feature.md', 'test/helpers/claude-pty-runner.ts'],
|
|
'e2e-harness-audit': ['plan-ceo-review/**', 'plan-eng-review/**', 'plan-design-review/**', 'plan-devex-review/**', 'scripts/resolvers/preamble/generate-completion-status.ts', 'test/helpers/agent-sdk-runner.ts', 'test/helpers/claude-pty-runner.ts'],
|
|
|
|
// Per-finding AskUserQuestion count + review-report-at-bottom assertion.
|
|
// Each test drives its skill end-to-end; touchfiles include preamble +
|
|
// completion-status resolvers because they affect question cadence and
|
|
// terminal output (the regression surface this test catches).
|
|
'plan-ceo-finding-count': ['plan-ceo-review/**', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completion-status.ts', 'test/helpers/claude-pty-runner.ts', 'test/skill-e2e-plan-ceo-finding-count.test.ts'],
|
|
'plan-eng-finding-count': ['plan-eng-review/**', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completion-status.ts', 'test/helpers/claude-pty-runner.ts', 'test/skill-e2e-plan-eng-finding-count.test.ts'],
|
|
'plan-design-finding-count': ['plan-design-review/**', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completion-status.ts', 'test/helpers/claude-pty-runner.ts', 'test/skill-e2e-plan-design-finding-count.test.ts'],
|
|
'plan-devex-finding-count': ['plan-devex-review/**', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completion-status.ts', 'test/helpers/claude-pty-runner.ts', 'test/skill-e2e-plan-devex-finding-count.test.ts'],
|
|
|
|
// Gate-tier reviewCount-floor counterparts. Catch the May 2026 transcript
|
|
// bug (model wrote a plan-mode plan and ExitPlanMode'd without firing any
|
|
// review-phase AskUserQuestion). Uses runPlanSkillFloorCheck — minimal
|
|
// "did agent fire ANY AUQ?" observer that exits early on first non-permission
|
|
// numbered-option render. ~1-3 min typical wall time per test, ~$2-6 total.
|
|
'plan-eng-finding-floor': ['plan-eng-review/**', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completion-status.ts', 'scripts/resolvers/review.ts', 'test/helpers/claude-pty-runner.ts', 'test/fixtures/forcing-finding-seeds.ts', 'test/skill-e2e-plan-eng-finding-floor.test.ts'],
|
|
'plan-ceo-finding-floor': ['plan-ceo-review/**', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completion-status.ts', 'scripts/resolvers/review.ts', 'test/helpers/claude-pty-runner.ts', 'test/fixtures/forcing-finding-seeds.ts', 'test/skill-e2e-plan-ceo-finding-floor.test.ts'],
|
|
'plan-design-finding-floor': ['plan-design-review/**', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completion-status.ts', 'scripts/resolvers/review.ts', 'test/helpers/claude-pty-runner.ts', 'test/fixtures/forcing-finding-seeds.ts', 'test/skill-e2e-plan-design-finding-floor.test.ts'],
|
|
'plan-devex-finding-floor': ['plan-devex-review/**', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completion-status.ts', 'scripts/resolvers/review.ts', 'test/helpers/claude-pty-runner.ts', 'test/fixtures/forcing-finding-seeds.ts', 'test/skill-e2e-plan-devex-finding-floor.test.ts'],
|
|
|
|
// Multi-finding batching regression — periodic tier complement to the
|
|
// gate-tier finding-floor. Catches the May 2026 transcript shape where
|
|
// a model fires one AUQ then batches the rest into a "## Decisions to
|
|
// confirm" plan write. runPlanSkillFloorCheck cannot detect that shape
|
|
// (it exits on first AUQ); runPlanSkillCounting can.
|
|
'plan-eng-multi-finding-batching': ['plan-eng-review/**', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completion-status.ts', 'scripts/resolvers/review.ts', 'test/helpers/claude-pty-runner.ts', 'test/fixtures/forcing-finding-seeds.ts', 'test/skill-e2e-plan-eng-multi-finding-batching.test.ts'],
|
|
'plan-ceo-split-overflow': ['plan-ceo-review/**', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'bin/gstack-question-preference', 'test/helpers/claude-pty-runner.ts', 'test/fixtures/forcing-finding-seeds.ts', 'test/skill-e2e-plan-ceo-split-overflow.test.ts'],
|
|
'brain-privacy-gate': ['scripts/resolvers/preamble/generate-brain-sync-block.ts', 'scripts/resolvers/preamble.ts', 'bin/gstack-brain-sync', 'bin/gstack-artifacts-init', 'bin/gstack-config', 'test/helpers/agent-sdk-runner.ts'],
|
|
|
|
// /setup-gbrain Path 4 (Remote MCP) — happy + bad-token end-to-end via
|
|
// Agent SDK. Gate-tier (deterministic stub server, fixed inputs); fires
|
|
// when the skill template, the verify helper, the artifacts-init helper,
|
|
// or the detect script changes.
|
|
'setup-gbrain-remote': ['setup-gbrain/SKILL.md.tmpl', 'bin/gstack-gbrain-mcp-verify', 'bin/gstack-artifacts-init', 'bin/gstack-gbrain-detect', 'test/helpers/agent-sdk-runner.ts'],
|
|
'setup-gbrain-bad-token': ['setup-gbrain/SKILL.md.tmpl', 'bin/gstack-gbrain-mcp-verify', 'test/helpers/agent-sdk-runner.ts'],
|
|
// v1.34.0.0 split-engine Path 4 + Step 4.5 Yes (local PGLite for code).
|
|
// Periodic-tier per codex #12 (AgentSDK harness is non-deterministic).
|
|
// Fires when the setup-gbrain template, install/verify/init helpers, or
|
|
// the agent-sdk-runner harness changes.
|
|
'setup-gbrain-path4-local-pglite': ['setup-gbrain/SKILL.md.tmpl', 'bin/gstack-gbrain-mcp-verify', 'bin/gstack-gbrain-install', 'bin/gstack-gbrain-detect', 'lib/gbrain-local-status.ts', 'test/helpers/agent-sdk-runner.ts'],
|
|
|
|
// AskUserQuestion format regression (RECOMMENDATION + Completeness: N/10)
|
|
// Fires when either template OR the two preamble resolvers change.
|
|
'plan-ceo-review-format-mode': ['plan-ceo-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completeness-section.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md', 'test/helpers/llm-judge.ts'],
|
|
'plan-ceo-review-format-approach': ['plan-ceo-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completeness-section.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md', 'test/helpers/llm-judge.ts'],
|
|
'plan-eng-review-format-coverage': ['plan-eng-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completeness-section.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md', 'test/helpers/llm-judge.ts'],
|
|
'plan-eng-review-format-kind': ['plan-eng-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completeness-section.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md', 'test/helpers/llm-judge.ts'],
|
|
|
|
// v1.7.0.0 Pros/Cons format cadence + format + negative-escape evals.
|
|
// Dependencies: same as format-mode + the 4 plan-review templates + overlay.
|
|
// All periodic-tier (non-deterministic Opus 4.7 behavior).
|
|
'plan-ceo-review-prosons-cadence': ['plan-ceo-review/**', 'plan-eng-review/**', 'plan-design-review/**', 'plan-devex-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md'],
|
|
'plan-review-prosons-format': ['plan-ceo-review/**', 'plan-eng-review/**', 'plan-design-review/**', 'plan-devex-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md'],
|
|
'plan-review-prosons-hardstop-neg': ['plan-ceo-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md'],
|
|
'plan-review-prosons-neutral-neg': ['plan-ceo-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md'],
|
|
|
|
// Expanded coverage (CT3) — 6 non-plan-review skills inherit Pros/Cons via preamble
|
|
'ship-prosons-format': ['ship/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md'],
|
|
'office-hours-prosons-format': ['office-hours/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md'],
|
|
'investigate-prosons-format': ['investigate/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md'],
|
|
'qa-prosons-format': ['qa/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md'],
|
|
'review-prosons-format': ['review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md'],
|
|
'design-review-prosons-format': ['design-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md'],
|
|
'document-release-prosons-format': ['document-release/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md'],
|
|
|
|
// /plan-tune (v1 observational)
|
|
'plan-tune-inspect': ['plan-tune/**', 'scripts/question-registry.ts', 'scripts/psychographic-signals.ts', 'scripts/one-way-doors.ts', 'bin/gstack-question-log', 'bin/gstack-question-preference', 'bin/gstack-developer-profile'],
|
|
|
|
// /plan-tune cathedral (T16 — 5 E2E scenarios, all gate per D12)
|
|
'plan-tune-hook-capture': ['hosts/claude/hooks/**', 'bin/gstack-question-log', 'bin/gstack-developer-profile', 'plan-tune/**'],
|
|
'plan-tune-enforcement': ['hosts/claude/hooks/**', 'bin/gstack-question-preference', 'scripts/question-registry.ts'],
|
|
'plan-tune-annotation': ['hosts/claude/hooks/**', 'scripts/declared-annotation.ts', 'scripts/psychographic-signals.ts', 'scripts/question-registry.ts'],
|
|
'plan-tune-codex-import': ['bin/gstack-codex-session-import', 'bin/gstack-question-log', 'docs/spikes/codex-session-format.md'],
|
|
'plan-tune-dream-cycle': ['bin/gstack-distill-free-text', 'bin/gstack-distill-apply', 'hosts/claude/hooks/**', 'plan-tune/**'],
|
|
|
|
// Codex offering verification
|
|
'codex-offered-office-hours': ['office-hours/**', 'scripts/gen-skill-docs.ts'],
|
|
'codex-offered-ceo-review': ['plan-ceo-review/**', 'scripts/gen-skill-docs.ts'],
|
|
'codex-offered-design-review': ['plan-design-review/**', 'scripts/gen-skill-docs.ts'],
|
|
'codex-offered-eng-review': ['plan-eng-review/**', 'scripts/gen-skill-docs.ts'],
|
|
|
|
// Ship
|
|
'ship-base-branch': ['ship/**', 'bin/gstack-repo-mode'],
|
|
'ship-local-workflow': ['ship/**', 'scripts/gen-skill-docs.ts'],
|
|
'review-dashboard-via': ['ship/**', 'scripts/resolvers/review.ts', 'codex/**', 'autoplan/**', 'land-and-deploy/**'],
|
|
'ship-plan-completion': ['ship/**', 'scripts/gen-skill-docs.ts'],
|
|
'ship-plan-verification': ['ship/**', 'scripts/gen-skill-docs.ts'],
|
|
|
|
// Retro
|
|
'retro': ['retro/**'],
|
|
'retro-base-branch': ['retro/**'],
|
|
|
|
// Global discover
|
|
'global-discover': ['bin/gstack-global-discover.ts', 'test/global-discover.test.ts'],
|
|
|
|
// CSO
|
|
'cso-full-audit': ['cso/**'],
|
|
'cso-diff-mode': ['cso/**'],
|
|
'cso-infra-scope': ['cso/**'],
|
|
|
|
// Learnings
|
|
'learnings-show': ['learn/**', 'bin/gstack-learnings-search', 'bin/gstack-learnings-log', 'scripts/resolvers/learnings.ts'],
|
|
|
|
// Session Intelligence (timeline, context recovery, /context-save + /context-restore)
|
|
'timeline-event-flow': ['bin/gstack-timeline-log', 'bin/gstack-timeline-read'],
|
|
'context-recovery-artifacts': ['scripts/resolvers/preamble.ts', 'bin/gstack-timeline-log', 'bin/gstack-slug', 'learn/**'],
|
|
'context-save-writes-file': ['context-save/**', 'bin/gstack-slug'],
|
|
'context-restore-loads-latest': ['context-restore/**', 'bin/gstack-slug'],
|
|
|
|
// Context skills E2E (live-fire, Skill-tool routing path) — see
|
|
// test/skill-e2e-context-skills.test.ts. These are periodic-tier because
|
|
// each one spawns claude -p and costs ~$0.20-$0.40. Collectively they
|
|
// verify the thing the /checkpoint → /context-save rename was for.
|
|
'context-save-routing': ['context-save/**', 'scripts/resolvers/preamble.ts'],
|
|
'context-save-then-restore-roundtrip': ['context-save/**', 'context-restore/**', 'bin/gstack-slug'],
|
|
'context-restore-fragment-match': ['context-restore/**'],
|
|
'context-restore-empty-state': ['context-restore/**'],
|
|
'context-restore-list-delegates': ['context-restore/**'],
|
|
'context-restore-legacy-compat': ['context-restore/**'],
|
|
'context-save-list-current-branch': ['context-save/**'],
|
|
'context-save-list-all-branches': ['context-save/**'],
|
|
|
|
// Document-release
|
|
'document-release': ['document-release/**'],
|
|
|
|
// Codex (Claude E2E — tests /codex skill via Claude)
|
|
'codex-review': ['codex/**'],
|
|
|
|
// Codex E2E (tests skills via Codex CLI + worktree)
|
|
'codex-discover-skill': ['codex/**', '.agents/skills/**', 'test/helpers/codex-session-runner.ts', 'lib/worktree.ts'],
|
|
'codex-review-findings': ['review/**', '.agents/skills/gstack-review/**', 'codex/**', 'test/helpers/codex-session-runner.ts', 'lib/worktree.ts'],
|
|
|
|
// Gemini E2E — smoke test only (Gemini gets lost in worktrees on complex tasks)
|
|
'gemini-smoke': ['.agents/skills/**', 'test/helpers/gemini-session-runner.ts', 'lib/worktree.ts'],
|
|
|
|
|
|
// Coverage audit (shared fixture) + triage + gates
|
|
'ship-coverage-audit': ['ship/**', 'test/fixtures/coverage-audit-fixture.ts', 'bin/gstack-repo-mode'],
|
|
'review-coverage-audit': ['review/**', 'test/fixtures/coverage-audit-fixture.ts'],
|
|
'plan-eng-coverage-audit': ['plan-eng-review/**', 'test/fixtures/coverage-audit-fixture.ts'],
|
|
'ship-triage': ['ship/**', 'bin/gstack-repo-mode'],
|
|
|
|
// Plan completion audit + verification
|
|
'ship-plan-completion': ['ship/**', 'scripts/gen-skill-docs.ts'],
|
|
'ship-plan-verification': ['ship/**', 'qa-only/**', 'scripts/gen-skill-docs.ts'],
|
|
'ship-idempotency': ['ship/**', 'scripts/resolvers/utility.ts'],
|
|
'review-plan-completion': ['review/**', 'scripts/gen-skill-docs.ts'],
|
|
|
|
// Design
|
|
'design-consultation-core': ['design-consultation/**', 'scripts/gen-skill-docs.ts', 'test/helpers/llm-judge.ts'],
|
|
'design-consultation-existing': ['design-consultation/**', 'scripts/gen-skill-docs.ts'],
|
|
'design-consultation-research': ['design-consultation/**', 'scripts/gen-skill-docs.ts'],
|
|
'design-consultation-preview': ['design-consultation/**', 'scripts/gen-skill-docs.ts'],
|
|
'plan-design-review-no-ui-scope': ['plan-design-review/**', 'scripts/gen-skill-docs.ts'],
|
|
'design-review-fix': ['design-review/**', 'browse/src/**', 'scripts/gen-skill-docs.ts'],
|
|
|
|
// Design Shotgun
|
|
'design-shotgun-path': ['design-shotgun/**', 'design/src/**', 'scripts/resolvers/design.ts'],
|
|
'design-shotgun-session': ['design-shotgun/**', 'scripts/resolvers/design.ts'],
|
|
'design-shotgun-full': ['design-shotgun/**', 'design/src/**', 'browse/src/**'],
|
|
|
|
// gstack-upgrade
|
|
'gstack-upgrade-happy-path': ['gstack-upgrade/**'],
|
|
|
|
// Deploy skills
|
|
'land-and-deploy-workflow': ['land-and-deploy/**', 'scripts/gen-skill-docs.ts'],
|
|
'land-and-deploy-first-run': ['land-and-deploy/**', 'scripts/gen-skill-docs.ts', 'bin/gstack-slug'],
|
|
'land-and-deploy-review-gate': ['land-and-deploy/**', 'bin/gstack-review-read'],
|
|
'canary-workflow': ['canary/**', 'browse/src/**'],
|
|
'benchmark-workflow': ['benchmark/**', 'browse/src/**'],
|
|
'setup-deploy-workflow': ['setup-deploy/**', 'scripts/gen-skill-docs.ts'],
|
|
|
|
// Sidebar agent
|
|
'sidebar-navigate': ['browse/src/server.ts', 'browse/src/sidebar-agent.ts', 'browse/src/sidebar-utils.ts', 'extension/**'],
|
|
'sidebar-url-accuracy': ['browse/src/server.ts', 'browse/src/sidebar-agent.ts', 'browse/src/sidebar-utils.ts', 'extension/background.js'],
|
|
'sidebar-css-interaction': ['browse/src/server.ts', 'browse/src/sidebar-agent.ts', 'browse/src/write-commands.ts', 'browse/src/read-commands.ts', 'browse/src/cdp-inspector.ts', 'extension/**'],
|
|
|
|
// Autoplan
|
|
'autoplan-core': ['autoplan/**', 'plan-ceo-review/**', 'plan-eng-review/**', 'plan-design-review/**'],
|
|
'autoplan-dual-voice': ['autoplan/**', 'codex/**', 'bin/gstack-codex-probe', 'scripts/resolvers/review.ts', 'scripts/resolvers/design.ts'],
|
|
|
|
// Multi-provider benchmark adapters — live API smoke against real claude/codex/gemini CLIs
|
|
'benchmark-providers-live': ['bin/gstack-model-benchmark', 'test/helpers/providers/**', 'test/helpers/benchmark-runner.ts', 'test/helpers/pricing.ts'],
|
|
|
|
// Browser-skills Phase 2a — /scrape + /skillify (v1.19.0.0). Gate-tier
|
|
// E2E covers the D1 (provenance guard), D3 (atomic write) contracts plus
|
|
// the basic loop. Shared deps: both skill templates, the D3 helper, the
|
|
// Phase 1 runtime, and the bundled hackernews-frontpage reference (the
|
|
// match-path test relies on it).
|
|
'scrape-match-path': [
|
|
'scrape/**', 'browse/src/browser-skills.ts', 'browse/src/browser-skill-commands.ts',
|
|
'browser-skills/hackernews-frontpage/**',
|
|
],
|
|
'scrape-prototype-path': [
|
|
'scrape/**', 'browse/src/browser-skills.ts', 'browse/src/browser-skill-commands.ts',
|
|
],
|
|
'skillify-happy-path': [
|
|
'skillify/**', 'scrape/**', 'browse/src/browser-skill-write.ts',
|
|
'browse/src/browser-skills.ts', 'browse/src/browser-skill-commands.ts',
|
|
],
|
|
'skillify-provenance-refusal': [
|
|
'skillify/**', 'browse/src/browser-skill-write.ts',
|
|
],
|
|
'skillify-approval-reject': [
|
|
'skillify/**', 'scrape/**', 'browse/src/browser-skill-write.ts',
|
|
],
|
|
|
|
// Skill routing — journey-stage tests (depend on ALL skill descriptions)
|
|
'journey-ideation': ['*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
|
|
'journey-plan-eng': ['*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
|
|
'journey-debug': ['*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
|
|
'journey-qa': ['*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
|
|
'journey-code-review': ['*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
|
|
'journey-ship': ['*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
|
|
'journey-docs': ['*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
|
|
'journey-retro': ['*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
|
|
'journey-design-system': ['*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
|
|
'journey-visual-qa': ['*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
|
|
|
|
// Opus 4.7 behavior evals — keys match testName: values in the test file.
|
|
// Routing sub-tests use template literal `routing-${c.name}` testNames,
|
|
// which the touchfile completeness scanner skips; they inherit selection
|
|
// from the file-level touchfile entry via GLOBAL_TOUCHFILES.
|
|
'fanout-arm-overlay-on':
|
|
['model-overlays/claude.md', 'model-overlays/opus-4-7.md', 'scripts/models.ts', 'scripts/resolvers/model-overlay.ts'],
|
|
'fanout-arm-overlay-off':
|
|
['model-overlays/claude.md', 'model-overlays/opus-4-7.md', 'scripts/models.ts', 'scripts/resolvers/model-overlay.ts'],
|
|
|
|
// Overlay efficacy harness (SDK) — measures whether overlay nudges change
|
|
// behavior under @anthropic-ai/claude-agent-sdk (closer to real Claude Code
|
|
// than `claude -p`). testNames in the file are template literals so the
|
|
// completeness scanner doesn't require them; these entries exist for
|
|
// diff-based selection accuracy.
|
|
'overlay-harness-opus-4-7-fanout-toy': [
|
|
'model-overlays/**',
|
|
'test/fixtures/overlay-nudges.ts',
|
|
'test/helpers/agent-sdk-runner.ts',
|
|
'scripts/resolvers/model-overlay.ts',
|
|
],
|
|
'overlay-harness-opus-4-7-fanout-realistic': [
|
|
'model-overlays/**',
|
|
'test/fixtures/overlay-nudges.ts',
|
|
'test/helpers/agent-sdk-runner.ts',
|
|
'scripts/resolvers/model-overlay.ts',
|
|
],
|
|
|
|
// /ios-qa — agent flow E2E. Daemon + stub StateServer + codegen
|
|
// exercised end-to-end. The no-device path is gate-tier; the with-device
|
|
// path requires GSTACK_HAS_IOS_DEVICE=1 and is periodic-tier.
|
|
'ios-qa-e2e': ['ios-qa/**', 'ios-fix/**', 'ios-design-review/**', 'ios-clean/**', 'ios-sync/**', 'test/skill-e2e-ios.test.ts'],
|
|
// Swift-build invariant test — requires the Swift toolchain. Compiles the
|
|
// fixture SPM package + runs the XCTest suite that validates the real
|
|
// Swift StateServer implementation (loopback bind, boot token rotation,
|
|
// session lock). Periodic-tier — Swift build is heavier than TS unit tests.
|
|
'ios-qa-swift-build': ['ios-qa/templates/**', 'test/fixtures/ios-qa/FixtureApp/**', 'test/skill-e2e-ios-swift-build.test.ts'],
|
|
// Real-device path — only runs with GSTACK_HAS_IOS_DEVICE=1 + a paired
|
|
// iPhone. Validates the CoreDevice agent + iOS SDK toolchain. Periodic-tier.
|
|
'ios-qa-device': ['ios-qa/templates/**', 'test/fixtures/ios-qa/FixtureApp/**', 'test/skill-e2e-ios-device.test.ts'],
|
|
|
|
// /spec end-to-end via PTY — exercises the full Phase 1→5 pipeline
|
|
// including --execute spawn. Periodic-tier — paid + non-deterministic.
|
|
'spec-execute': ['spec/**', 'test/skill-e2e-spec-execute.test.ts'],
|
|
};
|
|
|
|
/**
|
|
* E2E test tiers — 'gate' blocks PRs, 'periodic' runs weekly/on-demand.
|
|
* Must have exactly the same keys as E2E_TOUCHFILES.
|
|
*/
|
|
export const E2E_TIERS: Record<string, 'gate' | 'periodic'> = {
|
|
// Browse core — gate (if browse breaks, everything breaks)
|
|
'browse-basic': 'gate',
|
|
'browse-snapshot': 'gate',
|
|
|
|
// SKILL.md setup — gate (if setup breaks, no skill works)
|
|
'skillmd-setup-discovery': 'gate',
|
|
'skillmd-no-local-binary': 'gate',
|
|
'skillmd-outside-git': 'gate',
|
|
'session-awareness': 'gate',
|
|
'operational-learning': 'gate',
|
|
|
|
// QA — gate for functional, periodic for quality/benchmarks
|
|
'qa-quick': 'gate',
|
|
'qa-b6-static': 'periodic',
|
|
'qa-b7-spa': 'periodic',
|
|
'qa-b8-checkout': 'periodic',
|
|
'qa-only-no-fix': 'gate', // CRITICAL guardrail: Edit tool forbidden
|
|
'qa-fix-loop': 'periodic',
|
|
'qa-bootstrap': 'gate',
|
|
|
|
// Review — gate for functional/guardrails, periodic for quality
|
|
'review-sql-injection': 'gate', // Security guardrail
|
|
'review-enum-completeness': 'gate',
|
|
'review-base-branch': 'gate',
|
|
'review-design-lite': 'periodic', // 4/7 threshold is subjective
|
|
'review-coverage-audit': 'gate',
|
|
'review-plan-completion': 'gate',
|
|
'review-dashboard-via': 'gate',
|
|
|
|
// Review Army — gate for core functionality, periodic for multi-specialist
|
|
'review-army-migration-safety': 'gate', // Specialist activation guardrail
|
|
'review-army-perf-n-plus-one': 'gate', // Specialist activation guardrail
|
|
'review-army-delivery-audit': 'gate', // Delivery integrity guardrail
|
|
'review-army-quality-score': 'gate', // Score computation
|
|
'review-army-json-findings': 'gate', // JSON schema compliance
|
|
'review-army-red-team': 'periodic', // Multi-agent coordination
|
|
'review-army-consensus': 'periodic', // Multi-specialist agreement
|
|
|
|
// Office Hours
|
|
'office-hours-spec-review': 'gate',
|
|
'office-hours-forcing-energy': 'gate', // V1.1 mode-posture regression gate (Sonnet generator)
|
|
// 'office-hours-builder-wildness' retiered to periodic in v1.32 contributor
|
|
// wave: this is an LLM-judge creativity score (axis_a ≥4 on a "wildness"
|
|
// posture). Per CLAUDE.md tier-classification rules, non-deterministic
|
|
// quality benchmarks belong in periodic, not gate. The wave's +21-line
|
|
// CJK preamble cascade (#1205) pushed the score from 5/5 → 3/3 on the
|
|
// same /office-hours BUILDER prompt — same model, same fixture — proving
|
|
// the bar is sensitive to preamble-byte changes that have nothing to do
|
|
// with the test's intent (creativity, not preamble compliance).
|
|
'office-hours-builder-wildness': 'periodic',
|
|
|
|
// Plan reviews — gate for cheap functional, periodic for Opus quality
|
|
'plan-ceo-review': 'periodic',
|
|
'plan-ceo-review-selective': 'periodic',
|
|
'plan-ceo-review-benefits': 'gate',
|
|
'plan-ceo-review-expansion-energy': 'gate', // V1.1 mode-posture regression gate (Opus generator, Sonnet judge)
|
|
'plan-eng-review': 'periodic',
|
|
'plan-eng-review-artifact': 'periodic',
|
|
'plan-eng-coverage-audit': 'gate',
|
|
'plan-review-report': 'gate',
|
|
|
|
// Plan-mode handshake — deterministic safety regression, gate-tier
|
|
'plan-ceo-review-plan-mode': 'gate',
|
|
'plan-eng-review-plan-mode': 'gate',
|
|
'plan-design-review-plan-mode': 'gate',
|
|
'plan-devex-review-plan-mode': 'gate',
|
|
'plan-mode-no-op': 'gate',
|
|
// v1.21+ auto-mode regression tests
|
|
'office-hours-auto-mode': 'gate',
|
|
'auto-decide-preserved': 'periodic',
|
|
'e2e-harness-audit': 'gate',
|
|
|
|
// Real-PTY E2E batch — tier classification:
|
|
// gate: cheap, deterministic, run on every PR
|
|
// periodic: long-running or expensive (>$3/run), run weekly
|
|
'ask-user-question-format-pty': 'gate', // ~$0.50/run, single skill probe
|
|
'plan-ceo-mode-routing': 'periodic', // ~$3/run, deep navigation through 8-12 prior AskUserQuestions
|
|
'plan-design-with-ui-scope': 'gate', // ~$0.80/run
|
|
'budget-regression-pty': 'gate', // free, library-only assertion
|
|
'ship-idempotency-pty': 'periodic', // ~$3/run, real /ship in plan mode
|
|
'autoplan-chain-pty': 'periodic', // ~$8/run, all 3 phases sequential
|
|
|
|
// Per-finding count + review-report-at-bottom — periodic because each
|
|
// run drives a full skill end-to-end (~25 min, ~$5/run). Sequential
|
|
// execution during calibration; concurrent opt-in only after measured
|
|
// comparison agrees (plan §D15).
|
|
'plan-ceo-finding-count': 'periodic',
|
|
'plan-eng-finding-count': 'periodic',
|
|
'plan-design-finding-count': 'periodic',
|
|
'plan-devex-finding-count': 'periodic',
|
|
'plan-eng-finding-floor': 'gate',
|
|
'plan-ceo-finding-floor': 'gate',
|
|
'plan-design-finding-floor': 'gate',
|
|
'plan-devex-finding-floor': 'gate',
|
|
'plan-eng-multi-finding-batching': 'periodic',
|
|
'plan-ceo-split-overflow': 'periodic',
|
|
|
|
// Privacy gate for gstack-brain-sync — periodic (non-deterministic LLM call,
|
|
// costs ~$0.30-$0.50 per run, not needed on every commit)
|
|
'brain-privacy-gate': 'periodic',
|
|
|
|
// /setup-gbrain Path 4 (Remote MCP) — periodic-tier. The stub HTTP
|
|
// server is deterministic but the model's interpretation of "follow
|
|
// Path 4 only" is not — assertions on which steps the model ran are
|
|
// flaky. The deterministic gate-tier coverage for Path 4 lives in
|
|
// test/setup-gbrain-path4-structure.test.ts (free, <200ms). These
|
|
// E2E tests stay available for on-demand verification of the live
|
|
// model's behavior against a stub MCP server.
|
|
'setup-gbrain-remote': 'periodic',
|
|
'setup-gbrain-bad-token': 'periodic',
|
|
'setup-gbrain-path4-local-pglite': 'periodic',
|
|
|
|
// AskUserQuestion format regression — periodic (Opus 4.7 non-deterministic benchmark)
|
|
'plan-ceo-review-format-mode': 'periodic',
|
|
'plan-ceo-review-format-approach': 'periodic',
|
|
'plan-eng-review-format-coverage': 'periodic',
|
|
'plan-eng-review-format-kind': 'periodic',
|
|
|
|
// Office-hours Phase 4 silent-auto-decide regression — periodic (Phase 4
|
|
// requires the agent to invent 2-3 architectures, more open-ended than the
|
|
// 4 plan-format cases above). Reclassify to gate if it turns out stable.
|
|
'office-hours-phase4-fork': 'periodic',
|
|
// judgeRecommendation rubric sanity (fixture-based, ~$0.04/run via Haiku)
|
|
'llm-judge-recommendation': 'periodic',
|
|
|
|
// v1.7.0.0 Pros/Cons format — cadence + negative-escape evals (all periodic)
|
|
'plan-ceo-review-prosons-cadence': 'periodic',
|
|
'plan-review-prosons-format': 'periodic',
|
|
'plan-review-prosons-hardstop-neg': 'periodic',
|
|
'plan-review-prosons-neutral-neg': 'periodic',
|
|
|
|
// CT3 expanded coverage — non-plan-review skills inheriting Pros/Cons (all periodic)
|
|
'ship-prosons-format': 'periodic',
|
|
'office-hours-prosons-format': 'periodic',
|
|
'investigate-prosons-format': 'periodic',
|
|
'qa-prosons-format': 'periodic',
|
|
'review-prosons-format': 'periodic',
|
|
'design-review-prosons-format': 'periodic',
|
|
'document-release-prosons-format': 'periodic',
|
|
|
|
// /plan-tune — gate (core v1 DX promise: plain-English intent routing)
|
|
'plan-tune-inspect': 'gate',
|
|
|
|
// /plan-tune cathedral (T16 per D12 — all gate)
|
|
'plan-tune-hook-capture': 'gate',
|
|
'plan-tune-enforcement': 'gate',
|
|
'plan-tune-annotation': 'gate',
|
|
'plan-tune-codex-import': 'gate',
|
|
'plan-tune-dream-cycle': 'gate',
|
|
|
|
// Codex offering verification
|
|
'codex-offered-office-hours': 'gate',
|
|
'codex-offered-ceo-review': 'gate',
|
|
'codex-offered-design-review': 'gate',
|
|
'codex-offered-eng-review': 'gate',
|
|
|
|
// Session Intelligence — gate for data flow, periodic for agent integration
|
|
'timeline-event-flow': 'gate', // Binary data flow (no LLM needed)
|
|
'context-recovery-artifacts': 'gate', // Preamble reads seeded artifacts
|
|
'context-save-writes-file': 'gate', // /context-save writes a file
|
|
'context-restore-loads-latest': 'gate', // Cross-branch newest-by-filename restore
|
|
|
|
// Context skills live-fire — periodic (each test spawns claude -p, ~$0.20-$0.40)
|
|
'context-save-routing': 'periodic', // Proves /context-save routes via Skill tool
|
|
'context-save-then-restore-roundtrip': 'periodic', // Full cycle in one session
|
|
'context-restore-fragment-match': 'periodic', // /context-restore <fragment>
|
|
'context-restore-empty-state': 'periodic', // Graceful zero-saves message
|
|
'context-restore-list-delegates': 'periodic', // /context-restore list redirect
|
|
'context-restore-legacy-compat': 'periodic', // Pre-rename files still load
|
|
'context-save-list-current-branch': 'periodic', // Default branch filter
|
|
'context-save-list-all-branches': 'periodic', // --all flag
|
|
|
|
// Ship — gate (end-to-end ship path)
|
|
'ship-base-branch': 'gate',
|
|
'ship-local-workflow': 'gate',
|
|
'ship-coverage-audit': 'gate',
|
|
'ship-triage': 'gate',
|
|
'ship-plan-completion': 'gate',
|
|
'ship-plan-verification': 'gate',
|
|
'ship-idempotency': 'periodic',
|
|
|
|
// Retro — gate for cheap branch detection, periodic for full Opus retro
|
|
'retro': 'periodic',
|
|
'retro-base-branch': 'gate',
|
|
|
|
// Global discover
|
|
'global-discover': 'gate',
|
|
|
|
// CSO — gate for security guardrails, periodic for quality
|
|
'cso-full-audit': 'gate', // Hardcoded secrets detection
|
|
'cso-diff-mode': 'gate',
|
|
'cso-infra-scope': 'periodic',
|
|
|
|
// Learnings — gate (functional guardrail: seeded learnings must appear)
|
|
'learnings-show': 'gate',
|
|
|
|
// Document-release — gate (CHANGELOG guardrail)
|
|
'document-release': 'gate',
|
|
|
|
// Codex — periodic (Opus, requires codex CLI)
|
|
'codex-review': 'periodic',
|
|
|
|
// Multi-AI — periodic (require external CLIs)
|
|
'codex-discover-skill': 'periodic',
|
|
'codex-review-findings': 'periodic',
|
|
'gemini-smoke': 'periodic',
|
|
|
|
// Design — gate for cheap functional, periodic for Opus/quality
|
|
'design-consultation-core': 'periodic',
|
|
'design-consultation-existing': 'periodic',
|
|
'design-consultation-research': 'gate',
|
|
'design-consultation-preview': 'gate',
|
|
'plan-design-review-no-ui-scope': 'gate',
|
|
'design-review-fix': 'periodic',
|
|
'design-shotgun-path': 'gate',
|
|
'design-shotgun-session': 'gate',
|
|
'design-shotgun-full': 'periodic',
|
|
|
|
// gstack-upgrade
|
|
'gstack-upgrade-happy-path': 'gate',
|
|
|
|
// Deploy skills
|
|
'land-and-deploy-workflow': 'gate',
|
|
'land-and-deploy-first-run': 'gate',
|
|
'land-and-deploy-review-gate': 'gate',
|
|
'canary-workflow': 'gate',
|
|
'benchmark-workflow': 'gate',
|
|
'setup-deploy-workflow': 'gate',
|
|
|
|
// Sidebar agent
|
|
'sidebar-navigate': 'periodic',
|
|
'sidebar-url-accuracy': 'periodic',
|
|
'sidebar-css-interaction': 'periodic',
|
|
|
|
// Autoplan — periodic (not yet implemented)
|
|
'autoplan-core': 'periodic',
|
|
'autoplan-dual-voice': 'periodic',
|
|
|
|
// Multi-provider benchmark — periodic (requires external CLIs + auth, paid)
|
|
'benchmark-providers-live': 'periodic',
|
|
|
|
// Browser-skills Phase 2a — gate (D1/D3 contracts must not silently break)
|
|
'scrape-match-path': 'gate',
|
|
'scrape-prototype-path': 'gate',
|
|
'skillify-happy-path': 'gate',
|
|
'skillify-provenance-refusal': 'gate',
|
|
'skillify-approval-reject': 'gate',
|
|
|
|
// Skill routing — periodic (LLM routing is non-deterministic)
|
|
'journey-ideation': 'periodic',
|
|
'journey-plan-eng': 'periodic',
|
|
'journey-debug': 'periodic',
|
|
'journey-qa': 'periodic',
|
|
'journey-code-review': 'periodic',
|
|
'journey-ship': 'periodic',
|
|
'journey-docs': 'periodic',
|
|
'journey-retro': 'periodic',
|
|
'journey-design-system': 'periodic',
|
|
'journey-visual-qa': 'periodic',
|
|
|
|
// Opus 4.7 overlay evals — periodic (non-deterministic LLM behavior + Opus cost)
|
|
'fanout-arm-overlay-on': 'periodic',
|
|
'fanout-arm-overlay-off': 'periodic',
|
|
|
|
// Overlay efficacy harness (SDK, paid) — periodic only
|
|
'overlay-harness-opus-4-7-fanout-toy': 'periodic',
|
|
'overlay-harness-opus-4-7-fanout-realistic': 'periodic',
|
|
|
|
// /ios-qa daemon + codegen — no-device path runs every PR (no hardware
|
|
// dependency, deterministic). with-device path requires GSTACK_HAS_IOS_DEVICE.
|
|
'ios-qa-e2e': 'gate',
|
|
// Swift toolchain only, no device required, but heavier than TS unit tests.
|
|
'ios-qa-swift-build': 'periodic',
|
|
// Requires a real connected + paired iPhone. Manual-trigger only.
|
|
'ios-qa-device': 'periodic',
|
|
// /spec end-to-end PTY pipeline (paid, non-deterministic — periodic-tier).
|
|
'spec-execute': 'periodic',
|
|
};
|
|
|
|
/**
|
|
* LLM-judge test touchfiles — keyed by test description string.
|
|
*/
|
|
export const LLM_JUDGE_TOUCHFILES: Record<string, string[]> = {
|
|
'command reference table': ['SKILL.md', 'SKILL.md.tmpl', 'browse/src/commands.ts'],
|
|
'snapshot flags reference': ['SKILL.md', 'SKILL.md.tmpl', 'browse/src/snapshot.ts'],
|
|
'browse/SKILL.md reference': ['browse/SKILL.md', 'browse/SKILL.md.tmpl', 'browse/src/**'],
|
|
'setup block': ['SKILL.md', 'SKILL.md.tmpl'],
|
|
'regression vs baseline': ['SKILL.md', 'SKILL.md.tmpl', 'browse/src/commands.ts', 'test/fixtures/eval-baselines.json'],
|
|
'qa/SKILL.md workflow': ['qa/SKILL.md', 'qa/SKILL.md.tmpl'],
|
|
'qa/SKILL.md health rubric': ['qa/SKILL.md', 'qa/SKILL.md.tmpl'],
|
|
'qa/SKILL.md anti-refusal': ['qa/SKILL.md', 'qa/SKILL.md.tmpl', 'qa-only/SKILL.md', 'qa-only/SKILL.md.tmpl'],
|
|
'cross-skill greptile consistency': ['review/SKILL.md', 'review/SKILL.md.tmpl', 'ship/SKILL.md', 'ship/SKILL.md.tmpl', 'review/greptile-triage.md', 'retro/SKILL.md', 'retro/SKILL.md.tmpl'],
|
|
'baseline score pinning': ['SKILL.md', 'SKILL.md.tmpl', 'test/fixtures/eval-baselines.json'],
|
|
|
|
// Ship & Release
|
|
'ship/SKILL.md workflow': ['ship/SKILL.md', 'ship/SKILL.md.tmpl'],
|
|
'document-release/SKILL.md workflow': ['document-release/SKILL.md', 'document-release/SKILL.md.tmpl'],
|
|
|
|
// Plan Reviews
|
|
'plan-ceo-review/SKILL.md modes': ['plan-ceo-review/SKILL.md', 'plan-ceo-review/SKILL.md.tmpl'],
|
|
'plan-eng-review/SKILL.md sections': ['plan-eng-review/SKILL.md', 'plan-eng-review/SKILL.md.tmpl'],
|
|
|
|
// /spec authored-spec quality (paid LLM-judge — periodic-tier).
|
|
'spec authored quality': ['spec/SKILL.md', 'spec/SKILL.md.tmpl', 'test/fixtures/spec/**'],
|
|
'plan-design-review/SKILL.md passes': ['plan-design-review/SKILL.md', 'plan-design-review/SKILL.md.tmpl'],
|
|
|
|
// Design skills
|
|
'design-review/SKILL.md fix loop': ['design-review/SKILL.md', 'design-review/SKILL.md.tmpl'],
|
|
'design-consultation/SKILL.md research': ['design-consultation/SKILL.md', 'design-consultation/SKILL.md.tmpl'],
|
|
|
|
// Office Hours
|
|
'office-hours/SKILL.md spec review': ['office-hours/SKILL.md', 'office-hours/SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
|
|
'office-hours/SKILL.md design sketch': ['office-hours/SKILL.md', 'office-hours/SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
|
|
|
|
// Deploy skills
|
|
'land-and-deploy/SKILL.md workflow': ['land-and-deploy/SKILL.md', 'land-and-deploy/SKILL.md.tmpl'],
|
|
'canary/SKILL.md monitoring loop': ['canary/SKILL.md', 'canary/SKILL.md.tmpl'],
|
|
'benchmark/SKILL.md perf collection': ['benchmark/SKILL.md', 'benchmark/SKILL.md.tmpl'],
|
|
'setup-deploy/SKILL.md platform setup': ['setup-deploy/SKILL.md', 'setup-deploy/SKILL.md.tmpl'],
|
|
|
|
// Other skills
|
|
'retro/SKILL.md instructions': ['retro/SKILL.md', 'retro/SKILL.md.tmpl'],
|
|
'qa-only/SKILL.md workflow': ['qa-only/SKILL.md', 'qa-only/SKILL.md.tmpl'],
|
|
'gstack-upgrade/SKILL.md upgrade flow': ['gstack-upgrade/SKILL.md', 'gstack-upgrade/SKILL.md.tmpl'],
|
|
|
|
// Voice directive
|
|
'voice directive tone': ['scripts/resolvers/preamble.ts', 'review/SKILL.md', 'review/SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
|
|
};
|
|
|
|
/**
|
|
* Changes to any of these files trigger ALL tests (both E2E and LLM-judge).
|
|
*
|
|
* Keep this list minimal — only files that genuinely affect every test.
|
|
* Scoped dependencies (gen-skill-docs, llm-judge, test-server, worktree,
|
|
* codex/gemini session runners) belong in individual test entries instead.
|
|
*/
|
|
export const GLOBAL_TOUCHFILES = [
|
|
'test/helpers/session-runner.ts', // All E2E tests use this runner
|
|
'test/helpers/eval-store.ts', // All E2E tests store results here
|
|
'test/helpers/touchfiles.ts', // Self-referential — reclassifying wrong is dangerous
|
|
];
|
|
|
|
// --- Base branch detection ---
|
|
|
|
/**
|
|
* Detect the base branch by trying refs in order.
|
|
* Returns the first valid ref, or null if none found.
|
|
*/
|
|
export function detectBaseBranch(cwd: string): string | null {
|
|
for (const ref of ['origin/main', 'origin/master', 'main', 'master']) {
|
|
const result = spawnSync('git', ['rev-parse', '--verify', ref], {
|
|
cwd, stdio: 'pipe', timeout: 3000,
|
|
});
|
|
if (result.status === 0) return ref;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get list of files changed between base branch and HEAD.
|
|
*/
|
|
export function getChangedFiles(baseBranch: string, cwd: string): string[] {
|
|
const result = spawnSync('git', ['diff', '--name-only', `${baseBranch}...HEAD`], {
|
|
cwd, stdio: 'pipe', timeout: 5000,
|
|
});
|
|
if (result.status !== 0) return [];
|
|
return result.stdout.toString().trim().split('\n').filter(Boolean);
|
|
}
|
|
|
|
// --- Test selection ---
|
|
|
|
/**
|
|
* Select tests to run based on changed files.
|
|
*
|
|
* Algorithm:
|
|
* 1. If any changed file matches a global touchfile → run ALL tests
|
|
* 2. Otherwise, for each test, check if any changed file matches its patterns
|
|
* 3. Return selected + skipped lists with reason
|
|
*/
|
|
export function selectTests(
|
|
changedFiles: string[],
|
|
touchfiles: Record<string, string[]>,
|
|
globalTouchfiles: string[] = GLOBAL_TOUCHFILES,
|
|
): { selected: string[]; skipped: string[]; reason: string } {
|
|
const allTestNames = Object.keys(touchfiles);
|
|
|
|
// Global touchfile hit → run all
|
|
for (const file of changedFiles) {
|
|
if (globalTouchfiles.some(g => matchGlob(file, g))) {
|
|
return { selected: allTestNames, skipped: [], reason: `global: ${file}` };
|
|
}
|
|
}
|
|
|
|
// Per-test matching
|
|
const selected: string[] = [];
|
|
const skipped: string[] = [];
|
|
for (const [testName, patterns] of Object.entries(touchfiles)) {
|
|
const hit = changedFiles.some(f => patterns.some(p => matchGlob(f, p)));
|
|
(hit ? selected : skipped).push(testName);
|
|
}
|
|
|
|
return { selected, skipped, reason: 'diff' };
|
|
}
|