diff --git a/.gitignore b/.gitignore index 5196c0d05a..9a691e2f5a 100644 --- a/.gitignore +++ b/.gitignore @@ -19,10 +19,15 @@ bin/gstack-global-discover* .opencode/ .slate/ .cursor/ +.grok/ .openclaw/ +.pi/ .hermes/ .gbrain/ +.agy/ +.vibe/ .gbrain-source +.qoder/ .context/ extension/.auth.json # xterm assets are vendored from npm at build time; not source-of-truth. diff --git a/README.md b/README.md index 4bb177c3a7..a29f162835 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ From inside your repo, paste this. Switches you to team mode, bootstraps the rep (cd ~/.claude/skills/gstack && ./setup --team) && ~/.claude/skills/gstack/bin/gstack-team-init required && git add .claude/ CLAUDE.md && git commit -m "require gstack for AI-assisted work" ``` -No vendored files in your repo, no version drift, no manual upgrades. Every Claude Code session starts with a fast auto-update check (throttled to once/hour, network-failure-safe, completely silent). +Your project stays small, and everyone gets the same up-to-date version of gstack. If gstack is missing or its safety check cannot run, Claude Code and Copilot stop and explain what to do. If everything is ready, the check stays out of the way and your usual permission questions still appear. Updates happen quietly when a Claude Code session starts, at most once an hour. Swap `required` for `optional` if you'd rather nudge teammates than block them. @@ -101,7 +101,7 @@ These are conversational skills. Your OpenClaw agent runs them directly via chat ### Other AI Agents -gstack works on 10 AI coding agents, not just Claude. Setup auto-detects which +gstack works on 15 AI coding agents, not just Claude. Setup auto-detects which agents you have installed: ```bash @@ -113,18 +113,41 @@ Or target a specific agent with `./setup --host `: | Agent | Flag | Skills install to | |-------|------|-------------------| -| OpenAI Codex CLI | `--host codex` | `~/.codex/skills/gstack-*/` | +| OpenAI Codex CLI | `--host codex` | `~/.agents/skills/gstack-*/` | | OpenCode | `--host opencode` | `~/.config/opencode/skills/gstack-*/` | | Cursor | `--host cursor` | `~/.cursor/skills/gstack-*/` | | Factory Droid | `--host factory` | `~/.factory/skills/gstack-*/` | | Slate | `--host slate` | `~/.slate/skills/gstack-*/` | | Kiro | `--host kiro` | `~/.kiro/skills/gstack-*/` | -| Hermes | `--host hermes` | `~/.hermes/skills/gstack-*/` | +| Hermes | `--host hermes` | Prints integration instructions; `bun run gen:skill-docs --host hermes` writes `.hermes/skills/` | +| Pi | `--host pi` | Prints integration instructions; `bun run gen:skill-docs --host pi` writes `.pi/skills/` | +| Antigravity | `--host agy` | Prints integration instructions; `bun run gen:skill-docs --host agy` writes `.agy/skills/` | +| Mistral Vibe | `--host vibe` | Prints integration instructions; `bun run gen:skill-docs --host vibe` writes `.vibe/skills/` | | GBrain (mod) | `--host gbrain` | `~/.gbrain/skills/gstack-*/` | +| Qoder | `--host qoder` | `~/.qoder/skills/gstack-*/` | **Want to add support for another agent?** See [docs/ADDING_A_HOST.md](docs/ADDING_A_HOST.md). It's one TypeScript config file, zero code changes. +### Pi + +[Pi](https://pi.dev) implements the [Agent Skills standard](https://agentskills.io/specification) +end-to-end, so every gstack skill works out of the box once the skills directory +is configured. `./setup --host pi` prints these instructions; the install itself +is two commands: + +```bash +git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/gstack +cd ~/gstack && bun run gen:skill-docs --host pi +mkdir -p ~/.pi/agent/skills && ln -snf "$(pwd)"/.pi/skills/gstack* ~/.pi/agent/skills/ +``` + +Skills land at `~/.pi/agent/skills/gstack-*/`. For project-local skills (trusted +project), pi also discovers `.pi/skills/`. Pi loads skill descriptions at startup +and lazy-loads the full `SKILL.md` on demand, matching Claude Code's behavior. + +Invoke a skill with `/skill:` (e.g. `/skill:review`, `/skill:ship`). + ## See it work ``` @@ -356,10 +379,11 @@ rm -rf ~/.claude/skills/gstack rm -rf ~/.gstack # 5. Remove integrations (skip any you never installed) -rm -rf ~/.codex/skills/gstack* 2>/dev/null +rm -rf ~/.agents/skills/gstack* ~/.codex/skills/gstack* 2>/dev/null rm -rf ~/.factory/skills/gstack* 2>/dev/null rm -rf ~/.kiro/skills/gstack* 2>/dev/null rm -rf ~/.openclaw/skills/gstack* 2>/dev/null +rm -rf ~/.qoder/skills/gstack* 2>/dev/null # 6. Remove temp files rm -f /tmp/gstack-* 2>/dev/null @@ -466,7 +490,7 @@ Data is stored in [Supabase](https://supabase.com) (open source Firebase alterna **Want namespaced commands?** `cd ~/.claude/skills/gstack && ./setup --prefix` — switches from `/qa` to `/gstack-qa`. Useful if you run other skill packs alongside gstack. -**Codex says "Skipped loading skill(s) due to invalid SKILL.md"?** Your Codex skill descriptions are stale. Fix: `cd ~/.codex/skills/gstack && git pull && ./setup --host codex` — or for repo-local installs: `cd "$(readlink -f .agents/skills/gstack)" && git pull && ./setup --host codex` +**Codex says "Skipped loading skill(s) due to invalid SKILL.md"?** Your Codex skill descriptions are stale. Fix: `cd ~/.agents/skills/gstack && git pull && ./setup --host codex` — or for repo-local installs: `cd "$(readlink -f .agents/skills/gstack)" && git pull && ./setup --host codex` **Windows users:** gstack works on Windows 11 via Git Bash or WSL. Node.js is required in addition to Bun — Bun has a known bug with Playwright's pipe transport on Windows ([bun#4253](https://github.com/oven-sh/bun/issues/4253)). The browse server automatically falls back to Node.js. Make sure both `bun` and `node` are on your PATH. diff --git a/TODOS.md b/TODOS.md index 29721e6e50..5eab5ab36d 100644 --- a/TODOS.md +++ b/TODOS.md @@ -2,6 +2,55 @@ ## NEXT PRIORITY +### P2: Grok Build full-compat follow-ups (after `feat/grok-build-host`) + +**What:** Finish / harden the Grok-native packaging work landed on +`feat/grok-build-host` (runtime root, SETUP path fix, host-bleed cleanup, +bridges, `bin/gstack-grok-compat-audit`, section pointers for token ceiling). +These are the remaining product/ship items — not blockers for local Grok use. + +**Context (landed on branch):** +- U1–U3 packaging + U4–U7 bridges + U9 audit gate +- Setup host-scoping (no always-on Codex `.agents/` regen / Claude plan-tune + prompt on `--host grok-build`) +- Option A: Grok carved skills use STOP-Read section pointers + (`~/.grok/skills/gstack-*/sections/`) — `/ship` always-loaded ~20k tokens + (was ~45k) + +**Follow-ups to track:** + +1. **Upstream PR** — open/update PR against `garrytan/gstack` for + `feat/grok-build-host` (VERSION/CHANGELOG/title deferred until local DoD + green on main merge path). +2. **U4 behavioral smoke** — if claiming `/spec --execute` COMPATIBLE (not + file-only), record multi-turn tool-use / cwd isolation / fail-closed auth + smoke or fixture; flag-string greps alone are not enough. +3. **Section pointers for other external hosts (optional)** — Codex ship is + still monolith ~39k (near soft ceiling). Reuse + `hostUsesSectionPointers` for Codex/Cursor/Factory when package paths are + portable (Codex: `.agents/skills/gstack-*` or global + `~/.codex/skills/gstack-*`). +4. **R10 / U8 optional `gstack-codex`** — only if product wants suite + completeness; keep bare `/codex` = OpenAI plugin; deny bare `codex` alias + on Grok install (partial deny already in `link_grok_skill_dirs`). +5. **Gbrain Grok MCP stretch** — optional `~/.grok/config.toml` gbrain stanza + (absolute path, TOML RMW, atomic write + backup; non-interactive refuse + overwrite without `--force`). Success path remains CLI + AGENTS.md. +6. **Audit harness productization** — keep `bin/gstack-grok-compat-audit` as + long-lived multi-host CI later, or document as branch/DoD gate only + (open question from plan ce-doc-review). +7. **Name collision note** — document `/review` vs compound-engineering + `code-review` routing in `~/.grok/AGENTS.md` (do not delete CE skill). + +**Why:** Branch is implementation-ready for local Grok; these items close +honesty gaps, upstream ship, and optional suite completeness without +reopening Phase A packaging. + +**Depends on / blocked by:** Nothing for local use. Upstream PR waits on +clean validation + VERSION/CHANGELOG if that is the release process. + +--- + ### P1: #1882 — portable skill-install prefix (non-`gstack` install dirs break silently) **What:** Every generated SKILL.md hardcodes the literal `~/.claude/skills/gstack/...` diff --git a/autoplan/SKILL.md b/autoplan/SKILL.md index 5346f1d437..683b367c60 100644 --- a/autoplan/SKILL.md +++ b/autoplan/SKILL.md @@ -1098,12 +1098,11 @@ Loaded review skills from disk. Starting full review pipeline with auto-decision --- -## Phase 0.5: Codex auth + version preflight +## Phase 0.5: Outside-voice preflight -Before invoking any Codex voice, preflight the CLI: verify auth (multi-signal) and -warn on known-bad CLI versions. This is infrastructure for all 4 phases below — -source it once here and the helper functions stay in scope for the rest of the -workflow. +Before invoking any outside voice, preflight the CLI: verify auth (multi-signal) and +warn on known-bad CLI versions. On non-Codex hosts, the outside voice is Codex CLI +and the subagent is the host-native subagent. ```bash _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || echo off) @@ -1113,27 +1112,25 @@ source ~/.claude/skills/gstack/bin/gstack-codex-probe # Master switch first: codex_reviews=disabled turns off ALL Codex work globally, # including autoplan's own dual-voice orchestration. Honor it before probing. if [ "$_CODEX_CFG" = "disabled" ]; then - echo "[codex disabled by config — Claude-only voices] Re-enable: gstack-config set codex_reviews enabled" - _CODEX_AVAILABLE=false -# Check Codex binary. If missing, tag the degradation matrix and continue -# with Claude subagent only (autoplan's existing degradation fallback). + echo "[codex disabled by config — subagent-only voices] Re-enable: gstack-config set codex_reviews enabled" + _OUTSIDE_VOICE_AVAILABLE=false elif ! command -v codex >/dev/null 2>&1; then _gstack_codex_log_event "codex_cli_missing" - echo "[codex-unavailable: binary not found] — proceeding with Claude subagent only" - _CODEX_AVAILABLE=false + echo "[outside-voice-unavailable: Codex CLI not found] — proceeding with subagent only" + _OUTSIDE_VOICE_AVAILABLE=false elif ! _gstack_codex_auth_probe >/dev/null; then _gstack_codex_log_event "codex_auth_failed" - echo "[codex-unavailable: auth missing] — proceeding with Claude subagent only. Run \`codex login\` or set \$CODEX_API_KEY to enable dual-voice review." - _CODEX_AVAILABLE=false + echo "[outside-voice-unavailable: Codex auth missing] — proceeding with subagent only. Run `codex login` or set $CODEX_API_KEY to enable dual-voice review." + _OUTSIDE_VOICE_AVAILABLE=false else - _gstack_codex_version_check # non-blocking warn if known-bad - _CODEX_AVAILABLE=true + _gstack_codex_version_check + _OUTSIDE_VOICE_AVAILABLE=true fi ``` -If `_CODEX_AVAILABLE=false`, all Phase 1-3.5 Codex voices below degrade to -`[codex-unavailable]` in the degradation matrix. /autoplan completes with -Claude subagent only — saves token spend on Codex prompts we can't use. +If `_OUTSIDE_VOICE_AVAILABLE=false`, all outside-voice phases below degrade to +`[outside-voice-unavailable]`. /autoplan still completes with the host-native +subagent only — saves token spend on prompts we can't use. --- @@ -1152,33 +1149,33 @@ Override: every AskUserQuestion → auto-decide using the 6 principles. - Scope expansion: in blast radius + <1d CC → approve (P2). Outside → defer to TODOS.md (P3). Duplicates → reject (P4). Borderline (3-5 files) → mark TASTE DECISION. - All 10 review sections: run fully, auto-decide each issue, log every decision. -- Dual voices: always run BOTH Claude subagent AND Codex if available (P6). - Run them sequentially in foreground. First the Claude subagent (Agent tool, - foreground — do NOT use run_in_background), then Codex (Bash). Both must +- Dual voices: always run BOTH the host-native subagent AND the outside voice if available (P6). + Run them sequentially in foreground. First the subagent (Agent tool, + foreground — do NOT use run_in_background), then the outside voice (Bash). Both must complete before building the consensus table. - **Codex CEO voice** (via Bash): + **Codex outside voice** (via Bash): ```bash _REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; } _gstack_codex_timeout_wrapper 600 codex exec "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only. - You are a CEO/founder advisor reviewing a development plan. - Challenge the strategic foundations: Are the premises valid or assumed? Is this the - right problem to solve, or is there a reframing that would be 10x more impactful? - What alternatives were dismissed too quickly? What competitive or market risks are - unaddressed? What scope decisions will look foolish in 6 months? Be adversarial. - No compliments. Just the strategic blind spots. - File: " -C "$_REPO_ROOT" -s read-only --enable web_search_cached < /dev/null +You are a CEO/founder advisor reviewing a development plan. +Challenge the strategic foundations: Are the premises valid or assumed? Is this the +right problem to solve, or is there a reframing that would be 10x more impactful? +What alternatives were dismissed too quickly? What competitive or market risks are +unaddressed? What scope decisions will look foolish in 6 months? Be adversarial. +No compliments. Just the strategic blind spots. +File: " -C "$_REPO_ROOT" -s read-only --enable web_search_cached < /dev/null _CODEX_EXIT=$? if [ "$_CODEX_EXIT" = "124" ]; then _gstack_codex_log_event "codex_timeout" "600" _gstack_codex_log_hang "autoplan" "0" - echo "[codex stalled past 10 minutes — tagging as [codex-unavailable] for this phase and proceeding with Claude subagent only]" + echo "[outside voice stalled past 10 minutes — tagging as [outside-voice-unavailable] for this phase and proceeding with subagent only]" fi ``` - Timeout: 10 minutes (shell-wrapper) + 12 minutes (Bash outer gate). On hang, auto-degrades this phase's Codex voice. + Timeout: 10 minutes (shell-wrapper) + 12 minutes (Bash outer gate). On hang, auto-degrades this phase's outside voice. - **Claude CEO subagent** (via Agent tool): + **Host-native CEO subagent** (via Agent tool): "Read the plan file at . You are an independent CEO/strategist reviewing this plan. You have NOT seen any prior review. Evaluate: 1. Is this the right problem to solve? Could a reframing yield 10x impact? @@ -1188,12 +1185,12 @@ Override: every AskUserQuestion → auto-decide using the 6 principles. 5. What's the competitive risk — could someone else solve this first/better? For each finding: what's wrong, severity (critical/high/medium), and the fix." - **Error handling:** Both calls block in foreground. Codex auth/timeout/empty → proceed with - Claude subagent only, tagged `[single-model]`. If Claude subagent also fails → + **Error handling:** Both calls block in foreground. Outside voice auth/timeout/empty → proceed with + subagent only, tagged `[single-model]`. If the subagent also fails → "Outside voices unavailable — continuing with primary review." - **Degradation matrix:** Both fail → "single-reviewer mode". Codex only → - tag `[codex-only]`. Subagent only → tag `[subagent-only]`. + **Degradation matrix:** Both fail → "single-reviewer mode". Outside voice only → + tag `[outside-only]`. Subagent only → tag `[subagent-only]`. - Strategy choices: if codex disagrees with a premise or scope decision with valid strategic reason → TASTE DECISION. If both models agree the user's stated structure @@ -1210,15 +1207,15 @@ Step 0 (0A-0F) — run each sub-step and produce: - 0E: Temporal interrogation (HOUR 1 → HOUR 6+) - 0F: Mode selection confirmation -Step 0.5 (Dual Voices): Run Claude subagent (foreground Agent tool) first, then -Codex (Bash). Present Codex output under CODEX SAYS (CEO — strategy challenge) -header. Present subagent output under CLAUDE SUBAGENT (CEO — strategic independence) -header. Produce CEO consensus table: +Step 0.5 (Dual Voices): Run the host-native subagent (foreground Agent tool) first, +then the outside voice (Bash). Present outside-voice output under OUTSIDE VOICE +(CEO — strategy challenge) header. Present subagent output under HOST SUBAGENT +(CEO — strategic independence) header. Produce CEO consensus table: ``` CEO DUAL VOICES — CONSENSUS TABLE: ═══════════════════════════════════════════════════════════════ - Dimension Claude Codex Consensus + Dimension Subagent Outside Consensus ──────────────────────────────────── ─────── ─────── ───────── 1. Premises valid? — — — 2. Right problem to solve? — — — @@ -1246,7 +1243,7 @@ Sections 1-10 — for EACH section, run the evaluation criteria from the loaded - Completion Summary (the full summary table from the CEO skill) **PHASE 1 COMPLETE.** Emit phase-transition summary: -> **Phase 1 complete.** Codex: [N concerns]. Claude subagent: [N issues]. +> **Phase 1 complete.** Outside voice: [N concerns]. Subagent: [N issues]. > Consensus: [X/6 confirmed, Y disagreements → surfaced at gate]. > Passing to Phase 2. @@ -1257,7 +1254,7 @@ and the premise gate has been passed. **Pre-Phase 2 checklist (verify before starting):** - [ ] CEO completion summary written to plan file -- [ ] CEO dual voices ran (Codex + Claude subagent, or noted unavailable) +- [ ] CEO dual voices ran (subagent + outside voice, or noted unavailable) - [ ] CEO consensus table produced - [ ] Premise gate passed (user confirmed) - [ ] Phase-transition summary emitted @@ -1272,36 +1269,36 @@ Override: every AskUserQuestion → auto-decide using the 6 principles. - Structural issues (missing states, broken hierarchy): auto-fix (P5) - Aesthetic/taste issues: mark TASTE DECISION - Design system alignment: auto-fix if DESIGN.md exists and fix is obvious -- Dual voices: always run BOTH Claude subagent AND Codex if available (P6). +- Dual voices: always run BOTH the host-native subagent AND the outside voice if available (P6). - **Codex design voice** (via Bash): + **Codex outside voice** (via Bash): ```bash _REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; } _gstack_codex_timeout_wrapper 600 codex exec "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only. - Read the plan file at . Evaluate this plan's - UI/UX design decisions. +Read the plan file at . Evaluate this plan's +UI/UX design decisions. - Also consider these findings from the CEO review phase: - +Also consider these findings from the CEO review phase: + - Does the information hierarchy serve the user or the developer? Are interaction - states (loading, empty, error, partial) specified or left to the implementer's - imagination? Is the responsive strategy intentional or afterthought? Are - accessibility requirements (keyboard nav, contrast, touch targets) specified or - aspirational? Does the plan describe specific UI decisions or generic patterns? - What design decisions will haunt the implementer if left ambiguous? - Be opinionated. No hedging." -C "$_REPO_ROOT" -s read-only --enable web_search_cached < /dev/null +Does the information hierarchy serve the user or the developer? Are interaction +states (loading, empty, error, partial) specified or left to the implementer's +imagination? Is the responsive strategy intentional or afterthought? Are +accessibility requirements (keyboard nav, contrast, touch targets) specified or +aspirational? Does the plan describe specific UI decisions or generic patterns? +What design decisions will haunt the implementer if left ambiguous? +Be opinionated. No hedging." -C "$_REPO_ROOT" -s read-only --enable web_search_cached < /dev/null _CODEX_EXIT=$? if [ "$_CODEX_EXIT" = "124" ]; then _gstack_codex_log_event "codex_timeout" "600" _gstack_codex_log_hang "autoplan" "0" - echo "[codex stalled past 10 minutes — tagging as [codex-unavailable] for this phase and proceeding with Claude subagent only]" + echo "[outside voice stalled past 10 minutes — tagging as [outside-voice-unavailable] for this phase and proceeding with subagent only]" fi ``` - Timeout: 10 minutes (shell-wrapper) + 12 minutes (Bash outer gate). On hang, auto-degrades this phase's Codex voice. + Timeout: 10 minutes (shell-wrapper) + 12 minutes (Bash outer gate). On hang, auto-degrades this phase's outside voice. - **Claude design subagent** (via Agent tool): + **Host-native design subagent** (via Agent tool): "Read the plan file at . You are an independent senior product designer reviewing this plan. You have NOT seen any prior review. Evaluate: 1. Information hierarchy: what does the user see first, second, third? Is it right? @@ -1321,17 +1318,18 @@ Override: every AskUserQuestion → auto-decide using the 6 principles. 1. Step 0 (Design Scope): Rate completeness 0-10. Check DESIGN.md. Map existing patterns. -2. Step 0.5 (Dual Voices): Run Claude subagent (foreground) first, then Codex. Present under - CODEX SAYS (design — UX challenge) and CLAUDE SUBAGENT (design — independent review) +2. Step 0.5 (Dual Voices): Run the host-native subagent (foreground) first, then the + outside voice. Present under OUTSIDE VOICE (design — UX challenge) and HOST + SUBAGENT (design — independent review) headers. Produce design litmus scorecard (consensus table). Use the litmus scorecard - format from plan-design-review. Include CEO phase findings in Codex prompt ONLY - (not Claude subagent — stays independent). + format from plan-design-review. Include CEO phase findings in the outside-voice prompt ONLY + (not the subagent — stays independent). 3. Passes 1-7: Run each from loaded skill. Rate 0-10. Auto-decide each issue. DISAGREE items from scorecard → raised in the relevant pass with both perspectives. **PHASE 2 COMPLETE.** Emit phase-transition summary: -> **Phase 2 complete.** Codex: [N concerns]. Claude subagent: [N issues]. +> **Phase 2 complete.** Outside voice: [N concerns]. Subagent: [N issues]. > Consensus: [X/Y confirmed, Z disagreements → surfaced at gate]. > Passing to Phase 3. @@ -1353,31 +1351,31 @@ Override: every AskUserQuestion → auto-decide using the 6 principles. **Override rules:** - Scope challenge: never reduce (P2) -- Dual voices: always run BOTH Claude subagent AND Codex if available (P6). +- Dual voices: always run BOTH the host-native subagent AND the outside voice if available (P6). - **Codex eng voice** (via Bash): + **Codex outside voice** (via Bash): ```bash _REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; } _gstack_codex_timeout_wrapper 600 codex exec "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only. - Review this plan for architectural issues, missing edge cases, - and hidden complexity. Be adversarial. +Review this plan for architectural issues, missing edge cases, +and hidden complexity. Be adversarial. - Also consider these findings from prior review phases: - CEO: - Design: +Also consider these findings from prior review phases: +CEO: +Design: - File: " -C "$_REPO_ROOT" -s read-only --enable web_search_cached < /dev/null +File: " -C "$_REPO_ROOT" -s read-only --enable web_search_cached < /dev/null _CODEX_EXIT=$? if [ "$_CODEX_EXIT" = "124" ]; then _gstack_codex_log_event "codex_timeout" "600" _gstack_codex_log_hang "autoplan" "0" - echo "[codex stalled past 10 minutes — tagging as [codex-unavailable] for this phase and proceeding with Claude subagent only]" + echo "[outside voice stalled past 10 minutes — tagging as [outside-voice-unavailable] for this phase and proceeding with subagent only]" fi ``` - Timeout: 10 minutes (shell-wrapper) + 12 minutes (Bash outer gate). On hang, auto-degrades this phase's Codex voice. + Timeout: 10 minutes (shell-wrapper) + 12 minutes (Bash outer gate). On hang, auto-degrades this phase's outside voice. - **Claude eng subagent** (via Agent tool): + **Host-native eng subagent** (via Agent tool): "Read the plan file at . You are an independent senior engineer reviewing this plan. You have NOT seen any prior review. Evaluate: 1. Architecture: Is the component structure sound? Coupling concerns? @@ -1400,15 +1398,15 @@ Override: every AskUserQuestion → auto-decide using the 6 principles. 1. Step 0 (Scope Challenge): Read actual code referenced by the plan. Map each sub-problem to existing code. Run the complexity check. Produce concrete findings. -2. Step 0.5 (Dual Voices): Run Claude subagent (foreground) first, then Codex. Present - Codex output under CODEX SAYS (eng — architecture challenge) header. Present subagent - output under CLAUDE SUBAGENT (eng — independent review) header. Produce eng consensus +2. Step 0.5 (Dual Voices): Run the host-native subagent (foreground) first, then the + outside voice. Present outside-voice output under OUTSIDE VOICE (eng — architecture challenge) + header. Present subagent output under HOST SUBAGENT (eng — independent review) header. Produce eng consensus table: ``` ENG DUAL VOICES — CONSENSUS TABLE: ═══════════════════════════════════════════════════════════════ - Dimension Claude Codex Consensus + Dimension Subagent Outside Consensus ──────────────────────────────────── ─────── ─────── ───────── 1. Architecture sound? — — — 2. Test coverage sufficient? — — — @@ -1451,7 +1449,7 @@ Missing voice = N/A (not CONFIRMED). Single critical finding from one voice = fl - TODOS.md updates (collected from all phases) **PHASE 3 COMPLETE.** Emit phase-transition summary: -> **Phase 3 complete.** Codex: [N concerns]. Claude subagent: [N issues]. +> **Phase 3 complete.** Outside voice: [N concerns]. Subagent: [N issues]. > Consensus: [X/6 confirmed, Y disagreements → surfaced at gate]. > Passing to Phase 3.5 (DX Review) or Phase 4 (Final Gate). @@ -1474,36 +1472,36 @@ Log: "Phase 3.5 skipped — no developer-facing scope detected." - Error message quality: always require problem + cause + fix (P1, completeness) - API/CLI naming: consistency wins over cleverness (P5) - DX taste decisions (e.g., opinionated defaults vs flexibility): mark TASTE DECISION -- Dual voices: always run BOTH Claude subagent AND Codex if available (P6). +- Dual voices: always run BOTH the host-native subagent AND the outside voice if available (P6). - **Codex DX voice** (via Bash): + **Codex outside voice** (via Bash): ```bash _REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; } _gstack_codex_timeout_wrapper 600 codex exec "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only. - Read the plan file at . Evaluate this plan's developer experience. +Read the plan file at . Evaluate this plan's developer experience. - Also consider these findings from prior review phases: - CEO: - Eng: +Also consider these findings from prior review phases: +CEO: +Eng: - You are a developer who has never seen this product. Evaluate: - 1. Time to hello world: how many steps from zero to working? Target is under 5 minutes. - 2. Error messages: when something goes wrong, does the dev know what, why, and how to fix? - 3. API/CLI design: are names guessable? Are defaults sensible? Is it consistent? - 4. Docs: can a dev find what they need in under 2 minutes? Are examples copy-paste-complete? - 5. Upgrade path: can devs upgrade without fear? Migration guides? Deprecation warnings? - Be adversarial. Think like a developer who is evaluating this against 3 competitors." -C "$_REPO_ROOT" -s read-only --enable web_search_cached < /dev/null +You are a developer who has never seen this product. Evaluate: +1. Time to hello world: how many steps from zero to working? Target is under 5 minutes. +2. Error messages: when something goes wrong, does the dev know what, why, and how to fix? +3. API/CLI design: are names guessable? Are defaults sensible? Is it consistent? +4. Docs: can a dev find what they need in under 2 minutes? Are examples copy-paste-complete? +5. Upgrade path: can devs upgrade without fear? Migration guides? Deprecation warnings? +Be adversarial. Think like a developer who is evaluating this against 3 competitors." -C "$_REPO_ROOT" -s read-only --enable web_search_cached < /dev/null _CODEX_EXIT=$? if [ "$_CODEX_EXIT" = "124" ]; then _gstack_codex_log_event "codex_timeout" "600" _gstack_codex_log_hang "autoplan" "0" - echo "[codex stalled past 10 minutes — tagging as [codex-unavailable] for this phase and proceeding with Claude subagent only]" + echo "[outside voice stalled past 10 minutes — tagging as [outside-voice-unavailable] for this phase and proceeding with subagent only]" fi ``` - Timeout: 10 minutes (shell-wrapper) + 12 minutes (Bash outer gate). On hang, auto-degrades this phase's Codex voice. + Timeout: 10 minutes (shell-wrapper) + 12 minutes (Bash outer gate). On hang, auto-degrades this phase's outside voice. - **Claude DX subagent** (via Agent tool): + **Host-native DX subagent** (via Agent tool): "Read the plan file at . You are an independent DX engineer reviewing this plan. You have NOT seen any prior review. Evaluate: 1. Getting started: how many steps from zero to hello world? What's the TTHW? @@ -1524,14 +1522,14 @@ Log: "Phase 3.5 skipped — no developer-facing scope detected." 1. Step 0 (DX Scope Assessment): Auto-detect product type. Map the developer journey. Rate initial DX completeness 0-10. Assess TTHW. -2. Step 0.5 (Dual Voices): Run Claude subagent (foreground) first, then Codex. Present - under CODEX SAYS (DX — developer experience challenge) and CLAUDE SUBAGENT - (DX — independent review) headers. Produce DX consensus table: +2. Step 0.5 (Dual Voices): Run the host-native subagent (foreground) first, then the + outside voice. Present under OUTSIDE VOICE (DX — developer experience challenge) + and HOST SUBAGENT (DX — independent review) headers. Produce DX consensus table: ``` DX DUAL VOICES — CONSENSUS TABLE: ═══════════════════════════════════════════════════════════════ - Dimension Claude Codex Consensus + Dimension Subagent Outside Consensus ──────────────────────────────────── ─────── ─────── ───────── 1. Getting started < 5 min? — — — 2. API/CLI naming guessable? — — — @@ -1558,7 +1556,7 @@ Missing voice = N/A (not CONFIRMED). Single critical finding from one voice = fl **PHASE 3.5 COMPLETE.** Emit phase-transition summary: > **Phase 3.5 complete.** DX overall: [N]/10. TTHW: [N] min → [target] min. -> Codex: [N concerns]. Claude subagent: [N issues]. +> Outside voice: [N concerns]. Subagent: [N issues]. > Consensus: [X/6 confirmed, Y disagreements → surfaced at gate]. > Passing to Phase 4 (Final Gate). @@ -1595,7 +1593,7 @@ produced. Check the plan file and conversation for each item. - [ ] "What already exists" section written - [ ] Dream state delta written - [ ] Completion Summary produced -- [ ] Dual voices ran (Codex + Claude subagent, or noted unavailable) +- [ ] Dual voices ran (subagent + outside voice, or noted unavailable) - [ ] CEO consensus table produced **Phase 2 (Design) outputs — only if UI scope detected:** @@ -1613,7 +1611,7 @@ produced. Check the plan file and conversation for each item. - [ ] "What already exists" section written - [ ] Failure modes registry with critical gap assessment - [ ] Completion Summary produced -- [ ] Dual voices ran (Codex + Claude subagent, or noted unavailable) +- [ ] Dual voices ran (subagent + outside voice, or noted unavailable) - [ ] Eng consensus table produced **Phase 3.5 (DX) outputs — only if DX scope detected:** @@ -1749,13 +1747,13 @@ I recommend [X] — [principle]. But [Y] is also viable: ### Review Scores - CEO: [summary] -- CEO Voices: Codex [summary], Claude subagent [summary], Consensus [X/6 confirmed] +- CEO Voices: Outside [summary], Subagent [summary], Consensus [X/6 confirmed] - Design: [summary or "skipped, no UI scope"] -- Design Voices: Codex [summary], Claude subagent [summary], Consensus [X/7 confirmed] (or "skipped") +- Design Voices: Outside [summary], Subagent [summary], Consensus [X/7 confirmed] (or "skipped") - Eng: [summary] -- Eng Voices: Codex [summary], Claude subagent [summary], Consensus [X/6 confirmed] +- Eng Voices: Outside [summary], Subagent [summary], Consensus [X/6 confirmed] - DX: [summary or "skipped, no developer-facing scope"] -- DX Voices: Codex [summary], Claude subagent [summary], Consensus [X/6 confirmed] (or "skipped") +- DX Voices: Outside [summary], Subagent [summary], Consensus [X/6 confirmed] (or "skipped") ### Cross-Phase Themes [For any concern that appeared in 2+ phases' dual voices independently:] @@ -1835,7 +1833,7 @@ If Phase 3.5 ran (DX scope), also log: ~/.claude/skills/gstack/bin/gstack-review-log '{"skill":"autoplan-voices","timestamp":"'"$TIMESTAMP"'","status":"STATUS","source":"SOURCE","phase":"dx","via":"autoplan","consensus_confirmed":N,"consensus_disagree":N,"commit":"'"$COMMIT"'"}' ``` -SOURCE = "codex+subagent", "codex-only", "subagent-only", or "unavailable". +SOURCE = "subagent+outside", "outside-only", "subagent-only", or "unavailable". Replace N values with actual consensus counts from the tables. Suggest next step: `/ship` when ready to create the PR. diff --git a/autoplan/SKILL.md.tmpl b/autoplan/SKILL.md.tmpl index b2eaca9fde..ef6693dd3b 100644 --- a/autoplan/SKILL.md.tmpl +++ b/autoplan/SKILL.md.tmpl @@ -234,42 +234,7 @@ Loaded review skills from disk. Starting full review pipeline with auto-decision --- -## Phase 0.5: Codex auth + version preflight - -Before invoking any Codex voice, preflight the CLI: verify auth (multi-signal) and -warn on known-bad CLI versions. This is infrastructure for all 4 phases below — -source it once here and the helper functions stay in scope for the rest of the -workflow. - -```bash -_TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || echo off) -_CODEX_CFG=$(~/.claude/skills/gstack/bin/gstack-config get codex_reviews 2>/dev/null || echo enabled) -source ~/.claude/skills/gstack/bin/gstack-codex-probe - -# Master switch first: codex_reviews=disabled turns off ALL Codex work globally, -# including autoplan's own dual-voice orchestration. Honor it before probing. -if [ "$_CODEX_CFG" = "disabled" ]; then - echo "[codex disabled by config — Claude-only voices] Re-enable: gstack-config set codex_reviews enabled" - _CODEX_AVAILABLE=false -# Check Codex binary. If missing, tag the degradation matrix and continue -# with Claude subagent only (autoplan's existing degradation fallback). -elif ! command -v codex >/dev/null 2>&1; then - _gstack_codex_log_event "codex_cli_missing" - echo "[codex-unavailable: binary not found] — proceeding with Claude subagent only" - _CODEX_AVAILABLE=false -elif ! _gstack_codex_auth_probe >/dev/null; then - _gstack_codex_log_event "codex_auth_failed" - echo "[codex-unavailable: auth missing] — proceeding with Claude subagent only. Run \`codex login\` or set \$CODEX_API_KEY to enable dual-voice review." - _CODEX_AVAILABLE=false -else - _gstack_codex_version_check # non-blocking warn if known-bad - _CODEX_AVAILABLE=true -fi -``` - -If `_CODEX_AVAILABLE=false`, all Phase 1-3.5 Codex voices below degrade to -`[codex-unavailable]` in the degradation matrix. /autoplan completes with -Claude subagent only — saves token spend on Codex prompts we can't use. +{{AUTOPLAN_OUTSIDE_VOICE_PREFLIGHT}} --- @@ -288,33 +253,14 @@ Override: every AskUserQuestion → auto-decide using the 6 principles. - Scope expansion: in blast radius + <1d CC → approve (P2). Outside → defer to TODOS.md (P3). Duplicates → reject (P4). Borderline (3-5 files) → mark TASTE DECISION. - All 10 review sections: run fully, auto-decide each issue, log every decision. -- Dual voices: always run BOTH Claude subagent AND Codex if available (P6). - Run them sequentially in foreground. First the Claude subagent (Agent tool, - foreground — do NOT use run_in_background), then Codex (Bash). Both must +- Dual voices: always run BOTH the host-native subagent AND the outside voice if available (P6). + Run them sequentially in foreground. First the subagent (Agent tool, + foreground — do NOT use run_in_background), then the outside voice (Bash). Both must complete before building the consensus table. - **Codex CEO voice** (via Bash): - ```bash - _REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; } - _gstack_codex_timeout_wrapper 600 codex exec "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only. - - You are a CEO/founder advisor reviewing a development plan. - Challenge the strategic foundations: Are the premises valid or assumed? Is this the - right problem to solve, or is there a reframing that would be 10x more impactful? - What alternatives were dismissed too quickly? What competitive or market risks are - unaddressed? What scope decisions will look foolish in 6 months? Be adversarial. - No compliments. Just the strategic blind spots. - File: " -C "$_REPO_ROOT" -s read-only --enable web_search_cached < /dev/null - _CODEX_EXIT=$? - if [ "$_CODEX_EXIT" = "124" ]; then - _gstack_codex_log_event "codex_timeout" "600" - _gstack_codex_log_hang "autoplan" "0" - echo "[codex stalled past 10 minutes — tagging as [codex-unavailable] for this phase and proceeding with Claude subagent only]" - fi - ``` - Timeout: 10 minutes (shell-wrapper) + 12 minutes (Bash outer gate). On hang, auto-degrades this phase's Codex voice. - - **Claude CEO subagent** (via Agent tool): +{{AUTOPLAN_OUTSIDE_VOICE_BLOCK:ceo}} + + **Host-native CEO subagent** (via Agent tool): "Read the plan file at . You are an independent CEO/strategist reviewing this plan. You have NOT seen any prior review. Evaluate: 1. Is this the right problem to solve? Could a reframing yield 10x impact? @@ -324,12 +270,12 @@ Override: every AskUserQuestion → auto-decide using the 6 principles. 5. What's the competitive risk — could someone else solve this first/better? For each finding: what's wrong, severity (critical/high/medium), and the fix." - **Error handling:** Both calls block in foreground. Codex auth/timeout/empty → proceed with - Claude subagent only, tagged `[single-model]`. If Claude subagent also fails → + **Error handling:** Both calls block in foreground. Outside voice auth/timeout/empty → proceed with + subagent only, tagged `[single-model]`. If the subagent also fails → "Outside voices unavailable — continuing with primary review." - **Degradation matrix:** Both fail → "single-reviewer mode". Codex only → - tag `[codex-only]`. Subagent only → tag `[subagent-only]`. + **Degradation matrix:** Both fail → "single-reviewer mode". Outside voice only → + tag `[outside-only]`. Subagent only → tag `[subagent-only]`. - Strategy choices: if codex disagrees with a premise or scope decision with valid strategic reason → TASTE DECISION. If both models agree the user's stated structure @@ -346,15 +292,15 @@ Step 0 (0A-0F) — run each sub-step and produce: - 0E: Temporal interrogation (HOUR 1 → HOUR 6+) - 0F: Mode selection confirmation -Step 0.5 (Dual Voices): Run Claude subagent (foreground Agent tool) first, then -Codex (Bash). Present Codex output under CODEX SAYS (CEO — strategy challenge) -header. Present subagent output under CLAUDE SUBAGENT (CEO — strategic independence) -header. Produce CEO consensus table: +Step 0.5 (Dual Voices): Run the host-native subagent (foreground Agent tool) first, +then the outside voice (Bash). Present outside-voice output under OUTSIDE VOICE +(CEO — strategy challenge) header. Present subagent output under HOST SUBAGENT +(CEO — strategic independence) header. Produce CEO consensus table: ``` CEO DUAL VOICES — CONSENSUS TABLE: ═══════════════════════════════════════════════════════════════ - Dimension Claude Codex Consensus + Dimension Subagent Outside Consensus ──────────────────────────────────── ─────── ─────── ───────── 1. Premises valid? — — — 2. Right problem to solve? — — — @@ -382,7 +328,7 @@ Sections 1-10 — for EACH section, run the evaluation criteria from the loaded - Completion Summary (the full summary table from the CEO skill) **PHASE 1 COMPLETE.** Emit phase-transition summary: -> **Phase 1 complete.** Codex: [N concerns]. Claude subagent: [N issues]. +> **Phase 1 complete.** Outside voice: [N concerns]. Subagent: [N issues]. > Consensus: [X/6 confirmed, Y disagreements → surfaced at gate]. > Passing to Phase 2. @@ -393,7 +339,7 @@ and the premise gate has been passed. **Pre-Phase 2 checklist (verify before starting):** - [ ] CEO completion summary written to plan file -- [ ] CEO dual voices ran (Codex + Claude subagent, or noted unavailable) +- [ ] CEO dual voices ran (subagent + outside voice, or noted unavailable) - [ ] CEO consensus table produced - [ ] Premise gate passed (user confirmed) - [ ] Phase-transition summary emitted @@ -408,36 +354,11 @@ Override: every AskUserQuestion → auto-decide using the 6 principles. - Structural issues (missing states, broken hierarchy): auto-fix (P5) - Aesthetic/taste issues: mark TASTE DECISION - Design system alignment: auto-fix if DESIGN.md exists and fix is obvious -- Dual voices: always run BOTH Claude subagent AND Codex if available (P6). - - **Codex design voice** (via Bash): - ```bash - _REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; } - _gstack_codex_timeout_wrapper 600 codex exec "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only. - - Read the plan file at . Evaluate this plan's - UI/UX design decisions. - - Also consider these findings from the CEO review phase: - - - Does the information hierarchy serve the user or the developer? Are interaction - states (loading, empty, error, partial) specified or left to the implementer's - imagination? Is the responsive strategy intentional or afterthought? Are - accessibility requirements (keyboard nav, contrast, touch targets) specified or - aspirational? Does the plan describe specific UI decisions or generic patterns? - What design decisions will haunt the implementer if left ambiguous? - Be opinionated. No hedging." -C "$_REPO_ROOT" -s read-only --enable web_search_cached < /dev/null - _CODEX_EXIT=$? - if [ "$_CODEX_EXIT" = "124" ]; then - _gstack_codex_log_event "codex_timeout" "600" - _gstack_codex_log_hang "autoplan" "0" - echo "[codex stalled past 10 minutes — tagging as [codex-unavailable] for this phase and proceeding with Claude subagent only]" - fi - ``` - Timeout: 10 minutes (shell-wrapper) + 12 minutes (Bash outer gate). On hang, auto-degrades this phase's Codex voice. - - **Claude design subagent** (via Agent tool): +- Dual voices: always run BOTH the host-native subagent AND the outside voice if available (P6). + +{{AUTOPLAN_OUTSIDE_VOICE_BLOCK:design}} + + **Host-native design subagent** (via Agent tool): "Read the plan file at . You are an independent senior product designer reviewing this plan. You have NOT seen any prior review. Evaluate: 1. Information hierarchy: what does the user see first, second, third? Is it right? @@ -457,17 +378,18 @@ Override: every AskUserQuestion → auto-decide using the 6 principles. 1. Step 0 (Design Scope): Rate completeness 0-10. Check DESIGN.md. Map existing patterns. -2. Step 0.5 (Dual Voices): Run Claude subagent (foreground) first, then Codex. Present under - CODEX SAYS (design — UX challenge) and CLAUDE SUBAGENT (design — independent review) +2. Step 0.5 (Dual Voices): Run the host-native subagent (foreground) first, then the + outside voice. Present under OUTSIDE VOICE (design — UX challenge) and HOST + SUBAGENT (design — independent review) headers. Produce design litmus scorecard (consensus table). Use the litmus scorecard - format from plan-design-review. Include CEO phase findings in Codex prompt ONLY - (not Claude subagent — stays independent). + format from plan-design-review. Include CEO phase findings in the outside-voice prompt ONLY + (not the subagent — stays independent). 3. Passes 1-7: Run each from loaded skill. Rate 0-10. Auto-decide each issue. DISAGREE items from scorecard → raised in the relevant pass with both perspectives. **PHASE 2 COMPLETE.** Emit phase-transition summary: -> **Phase 2 complete.** Codex: [N concerns]. Claude subagent: [N issues]. +> **Phase 2 complete.** Outside voice: [N concerns]. Subagent: [N issues]. > Consensus: [X/Y confirmed, Z disagreements → surfaced at gate]. > Passing to Phase 3. @@ -489,31 +411,11 @@ Override: every AskUserQuestion → auto-decide using the 6 principles. **Override rules:** - Scope challenge: never reduce (P2) -- Dual voices: always run BOTH Claude subagent AND Codex if available (P6). - - **Codex eng voice** (via Bash): - ```bash - _REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; } - _gstack_codex_timeout_wrapper 600 codex exec "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only. - - Review this plan for architectural issues, missing edge cases, - and hidden complexity. Be adversarial. - - Also consider these findings from prior review phases: - CEO: - Design: - - File: " -C "$_REPO_ROOT" -s read-only --enable web_search_cached < /dev/null - _CODEX_EXIT=$? - if [ "$_CODEX_EXIT" = "124" ]; then - _gstack_codex_log_event "codex_timeout" "600" - _gstack_codex_log_hang "autoplan" "0" - echo "[codex stalled past 10 minutes — tagging as [codex-unavailable] for this phase and proceeding with Claude subagent only]" - fi - ``` - Timeout: 10 minutes (shell-wrapper) + 12 minutes (Bash outer gate). On hang, auto-degrades this phase's Codex voice. - - **Claude eng subagent** (via Agent tool): +- Dual voices: always run BOTH the host-native subagent AND the outside voice if available (P6). + +{{AUTOPLAN_OUTSIDE_VOICE_BLOCK:eng}} + + **Host-native eng subagent** (via Agent tool): "Read the plan file at . You are an independent senior engineer reviewing this plan. You have NOT seen any prior review. Evaluate: 1. Architecture: Is the component structure sound? Coupling concerns? @@ -536,15 +438,15 @@ Override: every AskUserQuestion → auto-decide using the 6 principles. 1. Step 0 (Scope Challenge): Read actual code referenced by the plan. Map each sub-problem to existing code. Run the complexity check. Produce concrete findings. -2. Step 0.5 (Dual Voices): Run Claude subagent (foreground) first, then Codex. Present - Codex output under CODEX SAYS (eng — architecture challenge) header. Present subagent - output under CLAUDE SUBAGENT (eng — independent review) header. Produce eng consensus +2. Step 0.5 (Dual Voices): Run the host-native subagent (foreground) first, then the + outside voice. Present outside-voice output under OUTSIDE VOICE (eng — architecture challenge) + header. Present subagent output under HOST SUBAGENT (eng — independent review) header. Produce eng consensus table: ``` ENG DUAL VOICES — CONSENSUS TABLE: ═══════════════════════════════════════════════════════════════ - Dimension Claude Codex Consensus + Dimension Subagent Outside Consensus ──────────────────────────────────── ─────── ─────── ───────── 1. Architecture sound? — — — 2. Test coverage sufficient? — — — @@ -587,7 +489,7 @@ Missing voice = N/A (not CONFIRMED). Single critical finding from one voice = fl - TODOS.md updates (collected from all phases) **PHASE 3 COMPLETE.** Emit phase-transition summary: -> **Phase 3 complete.** Codex: [N concerns]. Claude subagent: [N issues]. +> **Phase 3 complete.** Outside voice: [N concerns]. Subagent: [N issues]. > Consensus: [X/6 confirmed, Y disagreements → surfaced at gate]. > Passing to Phase 3.5 (DX Review) or Phase 4 (Final Gate). @@ -610,36 +512,11 @@ Log: "Phase 3.5 skipped — no developer-facing scope detected." - Error message quality: always require problem + cause + fix (P1, completeness) - API/CLI naming: consistency wins over cleverness (P5) - DX taste decisions (e.g., opinionated defaults vs flexibility): mark TASTE DECISION -- Dual voices: always run BOTH Claude subagent AND Codex if available (P6). - - **Codex DX voice** (via Bash): - ```bash - _REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; } - _gstack_codex_timeout_wrapper 600 codex exec "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only. - - Read the plan file at . Evaluate this plan's developer experience. - - Also consider these findings from prior review phases: - CEO: - Eng: - - You are a developer who has never seen this product. Evaluate: - 1. Time to hello world: how many steps from zero to working? Target is under 5 minutes. - 2. Error messages: when something goes wrong, does the dev know what, why, and how to fix? - 3. API/CLI design: are names guessable? Are defaults sensible? Is it consistent? - 4. Docs: can a dev find what they need in under 2 minutes? Are examples copy-paste-complete? - 5. Upgrade path: can devs upgrade without fear? Migration guides? Deprecation warnings? - Be adversarial. Think like a developer who is evaluating this against 3 competitors." -C "$_REPO_ROOT" -s read-only --enable web_search_cached < /dev/null - _CODEX_EXIT=$? - if [ "$_CODEX_EXIT" = "124" ]; then - _gstack_codex_log_event "codex_timeout" "600" - _gstack_codex_log_hang "autoplan" "0" - echo "[codex stalled past 10 minutes — tagging as [codex-unavailable] for this phase and proceeding with Claude subagent only]" - fi - ``` - Timeout: 10 minutes (shell-wrapper) + 12 minutes (Bash outer gate). On hang, auto-degrades this phase's Codex voice. - - **Claude DX subagent** (via Agent tool): +- Dual voices: always run BOTH the host-native subagent AND the outside voice if available (P6). + +{{AUTOPLAN_OUTSIDE_VOICE_BLOCK:dx}} + + **Host-native DX subagent** (via Agent tool): "Read the plan file at . You are an independent DX engineer reviewing this plan. You have NOT seen any prior review. Evaluate: 1. Getting started: how many steps from zero to hello world? What's the TTHW? @@ -660,14 +537,14 @@ Log: "Phase 3.5 skipped — no developer-facing scope detected." 1. Step 0 (DX Scope Assessment): Auto-detect product type. Map the developer journey. Rate initial DX completeness 0-10. Assess TTHW. -2. Step 0.5 (Dual Voices): Run Claude subagent (foreground) first, then Codex. Present - under CODEX SAYS (DX — developer experience challenge) and CLAUDE SUBAGENT - (DX — independent review) headers. Produce DX consensus table: +2. Step 0.5 (Dual Voices): Run the host-native subagent (foreground) first, then the + outside voice. Present under OUTSIDE VOICE (DX — developer experience challenge) + and HOST SUBAGENT (DX — independent review) headers. Produce DX consensus table: ``` DX DUAL VOICES — CONSENSUS TABLE: ═══════════════════════════════════════════════════════════════ - Dimension Claude Codex Consensus + Dimension Subagent Outside Consensus ──────────────────────────────────── ─────── ─────── ───────── 1. Getting started < 5 min? — — — 2. API/CLI naming guessable? — — — @@ -694,7 +571,7 @@ Missing voice = N/A (not CONFIRMED). Single critical finding from one voice = fl **PHASE 3.5 COMPLETE.** Emit phase-transition summary: > **Phase 3.5 complete.** DX overall: [N]/10. TTHW: [N] min → [target] min. -> Codex: [N concerns]. Claude subagent: [N issues]. +> Outside voice: [N concerns]. Subagent: [N issues]. > Consensus: [X/6 confirmed, Y disagreements → surfaced at gate]. > Passing to Phase 4 (Final Gate). @@ -731,7 +608,7 @@ produced. Check the plan file and conversation for each item. - [ ] "What already exists" section written - [ ] Dream state delta written - [ ] Completion Summary produced -- [ ] Dual voices ran (Codex + Claude subagent, or noted unavailable) +- [ ] Dual voices ran (subagent + outside voice, or noted unavailable) - [ ] CEO consensus table produced **Phase 2 (Design) outputs — only if UI scope detected:** @@ -749,7 +626,7 @@ produced. Check the plan file and conversation for each item. - [ ] "What already exists" section written - [ ] Failure modes registry with critical gap assessment - [ ] Completion Summary produced -- [ ] Dual voices ran (Codex + Claude subagent, or noted unavailable) +- [ ] Dual voices ran (subagent + outside voice, or noted unavailable) - [ ] Eng consensus table produced **Phase 3.5 (DX) outputs — only if DX scope detected:** @@ -812,13 +689,13 @@ I recommend [X] — [principle]. But [Y] is also viable: ### Review Scores - CEO: [summary] -- CEO Voices: Codex [summary], Claude subagent [summary], Consensus [X/6 confirmed] +- CEO Voices: Outside [summary], Subagent [summary], Consensus [X/6 confirmed] - Design: [summary or "skipped, no UI scope"] -- Design Voices: Codex [summary], Claude subagent [summary], Consensus [X/7 confirmed] (or "skipped") +- Design Voices: Outside [summary], Subagent [summary], Consensus [X/7 confirmed] (or "skipped") - Eng: [summary] -- Eng Voices: Codex [summary], Claude subagent [summary], Consensus [X/6 confirmed] +- Eng Voices: Outside [summary], Subagent [summary], Consensus [X/6 confirmed] - DX: [summary or "skipped, no developer-facing scope"] -- DX Voices: Codex [summary], Claude subagent [summary], Consensus [X/6 confirmed] (or "skipped") +- DX Voices: Outside [summary], Subagent [summary], Consensus [X/6 confirmed] (or "skipped") ### Cross-Phase Themes [For any concern that appeared in 2+ phases' dual voices independently:] @@ -898,7 +775,7 @@ If Phase 3.5 ran (DX scope), also log: ~/.claude/skills/gstack/bin/gstack-review-log '{"skill":"autoplan-voices","timestamp":"'"$TIMESTAMP"'","status":"STATUS","source":"SOURCE","phase":"dx","via":"autoplan","consensus_confirmed":N,"consensus_disagree":N,"commit":"'"$COMMIT"'"}' ``` -SOURCE = "codex+subagent", "codex-only", "subagent-only", or "unavailable". +SOURCE = "subagent+outside", "outside-only", "subagent-only", or "unavailable". Replace N values with actual consensus counts from the tables. Suggest next step: `/ship` when ready to create the PR. diff --git a/benchmark-models/SKILL.md b/benchmark-models/SKILL.md index 6a9d62616d..70fdacdd5f 100644 --- a/benchmark-models/SKILL.md +++ b/benchmark-models/SKILL.md @@ -20,11 +20,12 @@ allowed-tools: ## When to invoke this skill Runs the same prompt through Claude, -GPT (via Codex CLI), and Gemini side-by-side — compares latency, tokens, cost, -and optionally quality via LLM judge. Answers "which model is actually best -for this skill?" with data instead of vibes. Separate from /benchmark, which -measures web page performance. Use when: "benchmark models", "compare models", -"which model is best for X", "cross-model comparison", "model shootout". +GPT (via Codex CLI), Gemini, and Grok side-by-side — compares latency, tokens, +cost, and optionally quality via LLM judge. Answers "which model is actually +best for this skill?" with data instead of vibes. Separate from /benchmark, +which measures web page performance. Use when: "benchmark models", "compare +models", "which model is best for X", "cross-model comparison", "model +shootout". Voice triggers (speech-to-text aliases): "compare models", "model shootout", "which model is best". @@ -578,12 +579,12 @@ If C: ask for the path. Verify it exists. Use as positional argument. ## Step 2: Choose providers ```bash -"$BIN" --prompt "unused, dry-run" --models claude,gpt,gemini --dry-run +"$BIN" --prompt "unused, dry-run" --models claude,gpt,gemini,grok --dry-run ``` Show the dry-run output. The "Adapter availability" section tells the user which providers will actually run (OK) vs skip (NOT READY — remediation hint included). -If ALL three show NOT READY: stop with a clear message — benchmark can't run without at least one authed provider. Suggest `claude login`, `codex login`, or `gemini login` / `export GOOGLE_API_KEY`. +If ALL show NOT READY: stop with a clear message — benchmark can't run without at least one authed provider. Suggest `claude login`, `codex login`, `gemini login` / `export GOOGLE_API_KEY`, or `grok` login / `export XAI_API_KEY`. If at least one is OK: AskUserQuestion: - **Simplify:** "Which models should we include? The dry-run above showed which are authed. Unauthed ones will be skipped cleanly — they won't abort the batch." @@ -591,7 +592,8 @@ If at least one is OK: AskUserQuestion: - **Options:** - A) All authed providers. Completeness: 10/10. - B) Only Claude. Completeness: 6/10 (no cross-model signal — use /ship's review for solo claude benchmarks instead). - - C) Pick two — specify on next turn. Completeness: 8/10. + - C) Only Grok (Grok-only machine). Completeness: 7/10. + - D) Pick two — specify on next turn. Completeness: 8/10. --- @@ -608,7 +610,8 @@ If judge is available, AskUserQuestion: - A) Enable judge (adds ~$0.05). Completeness: 10/10. - B) Skip judge — speed/cost/tokens only. Completeness: 7/10. -If judge is NOT available, skip this question and omit the `--judge` flag. +If judge is NOT available (Grok-only / no ANTHROPIC_API_KEY): skip this question +and omit `--judge` (Grok-or-skip). Speed/cost/tokens still work. --- diff --git a/benchmark-models/SKILL.md.tmpl b/benchmark-models/SKILL.md.tmpl index 034cda1824..bf3458db32 100644 --- a/benchmark-models/SKILL.md.tmpl +++ b/benchmark-models/SKILL.md.tmpl @@ -4,11 +4,12 @@ preamble-tier: 1 version: 1.0.0 description: | Cross-model benchmark for gstack skills. Runs the same prompt through Claude, - GPT (via Codex CLI), and Gemini side-by-side — compares latency, tokens, cost, - and optionally quality via LLM judge. Answers "which model is actually best - for this skill?" with data instead of vibes. Separate from /benchmark, which - measures web page performance. Use when: "benchmark models", "compare models", - "which model is best for X", "cross-model comparison", "model shootout". (gstack) + GPT (via Codex CLI), Gemini, and Grok side-by-side — compares latency, tokens, + cost, and optionally quality via LLM judge. Answers "which model is actually + best for this skill?" with data instead of vibes. Separate from /benchmark, + which measures web page performance. Use when: "benchmark models", "compare + models", "which model is best for X", "cross-model comparison", "model + shootout". (gstack) voice-triggers: - "compare models" - "model shootout" @@ -69,12 +70,12 @@ If C: ask for the path. Verify it exists. Use as positional argument. ## Step 2: Choose providers ```bash -"$BIN" --prompt "unused, dry-run" --models claude,gpt,gemini --dry-run +"$BIN" --prompt "unused, dry-run" --models claude,gpt,gemini,grok --dry-run ``` Show the dry-run output. The "Adapter availability" section tells the user which providers will actually run (OK) vs skip (NOT READY — remediation hint included). -If ALL three show NOT READY: stop with a clear message — benchmark can't run without at least one authed provider. Suggest `claude login`, `codex login`, or `gemini login` / `export GOOGLE_API_KEY`. +If ALL show NOT READY: stop with a clear message — benchmark can't run without at least one authed provider. Suggest `claude login`, `codex login`, `gemini login` / `export GOOGLE_API_KEY`, or `grok` login / `export XAI_API_KEY`. If at least one is OK: AskUserQuestion: - **Simplify:** "Which models should we include? The dry-run above showed which are authed. Unauthed ones will be skipped cleanly — they won't abort the batch." @@ -82,7 +83,8 @@ If at least one is OK: AskUserQuestion: - **Options:** - A) All authed providers. Completeness: 10/10. - B) Only Claude. Completeness: 6/10 (no cross-model signal — use /ship's review for solo claude benchmarks instead). - - C) Pick two — specify on next turn. Completeness: 8/10. + - C) Only Grok (Grok-only machine). Completeness: 7/10. + - D) Pick two — specify on next turn. Completeness: 8/10. --- @@ -99,7 +101,8 @@ If judge is available, AskUserQuestion: - A) Enable judge (adds ~$0.05). Completeness: 10/10. - B) Skip judge — speed/cost/tokens only. Completeness: 7/10. -If judge is NOT available, skip this question and omit the `--judge` flag. +If judge is NOT available (Grok-only / no ANTHROPIC_API_KEY): skip this question +and omit `--judge` (Grok-or-skip). Speed/cost/tokens still work. --- diff --git a/bin/gstack-grok-compat-audit b/bin/gstack-grok-compat-audit new file mode 100755 index 0000000000..67b3df488a --- /dev/null +++ b/bin/gstack-grok-compat-audit @@ -0,0 +1,362 @@ +#!/usr/bin/env bun +/** + * gstack-grok-compat-audit — Definition of Done gate for Grok Build packaging + * + bridge honesty (plan U9 / R11). + * + * Exit 0 only when packaging checks pass for installed gstack membership + * packages and bridge gates are not theater-greened. + * + * Usage: + * bin/gstack-grok-compat-audit + * bin/gstack-grok-compat-audit --skills-dir ~/.grok/skills + * bin/gstack-grok-compat-audit --phase a # packaging only + * bin/gstack-grok-compat-audit --phase ab # packaging + bridge gates (default) + */ + +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; +import { execFileSync } from 'child_process'; + +const ROOT = path.resolve(import.meta.dir, '..'); +const args = process.argv.slice(2); + +function arg(name: string, def?: string): string | undefined { + const idx = args.findIndex(a => a === name || a.startsWith(name + '=')); + if (idx < 0) return def; + const eq = args[idx].indexOf('='); + if (eq >= 0) return args[idx].slice(eq + 1); + return args[idx + 1] ?? def; +} + +const SKILLS_DIR = path.resolve( + (arg('--skills-dir') ?? path.join(os.homedir(), '.grok', 'skills')).replace(/^~/, os.homedir()), +); +const PHASE = (arg('--phase') ?? 'ab').toLowerCase(); +const GSTACK_ROOT = path.join(SKILLS_DIR, 'gstack'); + +type Severity = 'fail' | 'warn' | 'ok'; +interface Finding { + severity: Severity; + area: string; + message: string; +} + +const findings: Finding[] = []; + +function fail(area: string, message: string) { + findings.push({ severity: 'fail', area, message }); +} +function warn(area: string, message: string) { + findings.push({ severity: 'warn', area, message }); +} +function ok(area: string, message: string) { + findings.push({ severity: 'ok', area, message }); +} + +function exists(p: string): boolean { + try { + fs.accessSync(p); + return true; + } catch { + return false; + } +} + +function readText(p: string): string { + try { + return fs.readFileSync(p, 'utf-8'); + } catch { + return ''; + } +} + +function isGstackMembership(dirName: string): boolean { + if (dirName === 'gstack' || dirName === 'connect-chrome' || dirName === 'browse') return true; + if (dirName.startsWith('gstack-')) return true; + // bare aliases that point at gstack packages (symlink or real SKILL with gstack markers) + const skill = path.join(SKILLS_DIR, dirName, 'SKILL.md'); + if (!exists(skill)) return false; + const body = readText(skill); + return body.includes('$GSTACK_ROOT') || body.includes('gstack') || body.includes('AUTO-GENERATED'); +} + +function listMembershipPackages(): string[] { + if (!exists(SKILLS_DIR)) return []; + return fs + .readdirSync(SKILLS_DIR, { withFileTypes: true }) + .filter(d => d.isDirectory() || d.isSymbolicLink()) + .map(d => d.name) + .filter(isGstackMembership) + .sort(); +} + +// ─── Packaging suite (Phase A) ──────────────────────────────── + +function auditRuntimeRoot() { + const area = 'runtime-root'; + if (!exists(GSTACK_ROOT)) { + fail(area, `missing runtime root: ${GSTACK_ROOT}`); + return; + } + const requiredDirs = [ + 'bin', + 'browse/dist', + 'browse/src', + 'review/specialists', + 'scripts', + ]; + for (const rel of requiredDirs) { + const p = path.join(GSTACK_ROOT, rel); + if (!exists(p)) fail(area, `missing required asset: ${rel}`); + else ok(area, `present: ${rel}`); + } + // Conditional when monorepo had them at install — soft-check if src monorepo has them + const monorepoHas = (rel: string) => exists(path.join(ROOT, rel)); + if (monorepoHas('design/dist') && !exists(path.join(GSTACK_ROOT, 'design/dist'))) { + fail(area, 'monorepo has design/dist but runtime root does not'); + } + if (monorepoHas('extension') && !exists(path.join(GSTACK_ROOT, 'extension'))) { + fail(area, 'monorepo has extension/ but runtime root does not'); + } + if (monorepoHas('make-pdf/dist') && !exists(path.join(GSTACK_ROOT, 'make-pdf/dist'))) { + warn(area, 'monorepo has make-pdf/dist but runtime root does not (membership optional)'); + } + for (const f of ['checklist.md', 'TODOS-format.md']) { + const p = path.join(GSTACK_ROOT, 'review', f); + if (!exists(p)) fail(area, `missing review/${f}`); + else ok(area, `present: review/${f}`); + } +} + +function auditNoDoubleHome() { + const area = 'no-double-home'; + const pkgs = listMembershipPackages(); + let hits = 0; + for (const name of pkgs) { + const skill = path.join(SKILLS_DIR, name, 'SKILL.md'); + if (!exists(skill)) continue; + const body = readText(skill); + if (/\$HOME\$GSTACK/.test(body)) { + fail(area, `${name}: contains $HOME$GSTACK (double-home path bug)`); + hits++; + } + } + if (hits === 0) ok(area, `no $HOME$GSTACK in ${pkgs.length} packages`); +} + +function auditNoUngeneratedMonorepoPointers() { + const area = 'no-monorepo-skill-pointer'; + // Skills under ~/.grok/skills must not be bare monorepo dirs with unrewritten Claude SKILL.md + // Heuristic: ungenerated monorepo package often has MODEL_OVERLAY: claude section + ~/.claude paths without rewrite + const pkgs = listMembershipPackages(); + for (const name of pkgs) { + const skill = path.join(SKILLS_DIR, name, 'SKILL.md'); + if (!exists(skill)) continue; + const body = readText(skill); + // Skip runtime root + if (name === 'gstack' && !body.includes('name:')) continue; + if (body.includes('~/.claude/skills/gstack') && !body.includes('$GSTACK_ROOT')) { + // Still has unrewritten Claude global path without env var form + fail(area, `${name}: unrewritten ~/.claude/skills/gstack pointer`); + } + } + ok(area, 'membership packages scanned for unrewritten Claude monorepo pointers'); +} + +function auditConnectChrome() { + const area = 'connect-chrome'; + const link = path.join(SKILLS_DIR, 'connect-chrome'); + if (!exists(link)) { + fail(area, 'connect-chrome missing under skills dir'); + return; + } + try { + const target = fs.realpathSync(link); + if (!/open-gstack-browser/.test(target) && !/gstack-open-gstack-browser/.test(target)) { + fail(area, `connect-chrome does not resolve to open-gstack-browser (got ${target})`); + } else { + ok(area, `connect-chrome → ${target}`); + } + } catch (e) { + fail(area, `connect-chrome realpath failed: ${(e as Error).message}`); + } +} + +function auditHostBleedSample() { + const area = 'host-bleed'; + const samples = ['gstack-ship', 'ship', 'gstack-browse', 'browse', 'gstack-claude', 'claude']; + for (const name of samples) { + const skill = path.join(SKILLS_DIR, name, 'SKILL.md'); + if (!exists(skill)) continue; + const body = readText(skill); + if (/MODEL_OVERLAY:\s*claude/.test(body)) { + fail(area, `${name}: MODEL_OVERLAY: claude still present`); + } + if (/## Model-Specific Behavioral Patch \(claude\)/.test(body)) { + fail(area, `${name}: Claude model overlay section body present`); + } + // Claude skill is allowed to mention claude CLI extensively + if (name !== 'claude' && name !== 'gstack-claude') { + if (/\bAskUserQuestion\b/.test(body) && !/ask_user_question/.test(body)) { + // May still appear in examples — soft + warn(area, `${name}: raw AskUserQuestion token (prefer ask_user_question rewrite)`); + } + } + if (name === 'claude' || name === 'gstack-claude') { + if (!/MULTI_CLI_BRIDGE/.test(body) && !/Step 0: Check Claude CLI/.test(body)) { + fail(area, `${name}: missing MULTI_CLI_BRIDGE / Step 0 detect`); + } else { + ok(area, `${name}: Step 0 / multi-CLI packaging present`); + } + } + } + ok(area, 'host-bleed sample scan complete'); +} + +function auditSkillifySdkPath() { + const area = 'skillify-sdk'; + const src = path.join(GSTACK_ROOT, 'browse', 'src', 'browse-client.ts'); + if (exists(src)) ok(area, 'browse/src/browse-client.ts resolvable under $GSTACK_ROOT'); + else fail(area, 'browse/src/browse-client.ts missing — skillify SDK path broken'); +} + +// ─── Bridge gates (Phase B) ─────────────────────────────────── + +function auditBridges() { + // Install prose alone must not claim primary READY when CLI missing + const area = 'bridge-policy'; + + // Prefer installed runtime root binary (what users run); fall back to monorepo. + const installedBench = path.join(GSTACK_ROOT, 'bin', 'gstack-model-benchmark'); + const monorepoBench = path.join(ROOT, 'bin', 'gstack-model-benchmark'); + const benchBin = exists(installedBench) ? installedBench : monorepoBench; + if (!exists(installedBench) && exists(monorepoBench)) { + warn(area, 'benchmark: using monorepo binary (installed runtime bin missing) — packaging incomplete?'); + } + try { + // No shell: argv list avoids monorepo-path injection. + const help = execFileSync(benchBin, ['--prompt', 'x', '--models', 'grok', '--dry-run'], { + encoding: 'utf-8', + timeout: 15000, + maxBuffer: 2 * 1024 * 1024, + }); + if (/unknown provider.*grok/i.test(help)) { + fail(area, 'benchmark: grok still unknown provider'); + } else if (/grok:\s*(OK|NOT READY)/i.test(help)) { + ok(area, `benchmark: grok provider wired via ${benchBin} (dry-run READY/NOT READY is boolean auth)`); + } else { + warn(area, `benchmark dry-run unexpected output: ${help.slice(0, 200)}`); + } + } catch (e) { + const msg = (e as { stdout?: string; message?: string }).stdout + ?? (e as Error).message + ?? String(e); + if (/unknown provider.*grok/i.test(msg)) fail(area, 'benchmark: grok unknown provider'); + else warn(area, `benchmark dry-run error: ${String(msg).slice(0, 200)}`); + } + + // spec: at least one installed package must exist and use Grok-native spawn + let specSeen = 0; + for (const name of ['gstack-spec', 'spec']) { + const skill = path.join(SKILLS_DIR, name, 'SKILL.md'); + if (!exists(skill)) continue; + specSeen++; + const body = readText(skill); + if (/\$\(cat\s+["']?\$ARCHIVE/.test(body) || /grok[^\n]*\$\(cat/.test(body)) { + fail(area, `${name}: banned $(cat …) into grok argv`); + } + if (/grok --prompt-file/.test(body) || /Spawn \*\*Grok\*\*/.test(body)) { + ok(area, `${name}: Grok-native spawn present`); + } else if (/Spawned:.*claude -p/.test(body) && !/--execute-claude/.test(body)) { + fail(area, `${name}: default execute still only claude -p (no Grok spawn)`); + } else { + fail(area, `${name}: missing researched grok --prompt-file spawn`); + } + } + if (specSeen === 0) { + fail(area, 'spec: neither gstack-spec nor spec package installed under skills dir'); + } + + // setup-gbrain: must not hard-require which claude for success + let gbrainSeen = 0; + for (const name of ['gstack-setup-gbrain', 'setup-gbrain']) { + const skill = path.join(SKILLS_DIR, name, 'SKILL.md'); + if (!exists(skill)) continue; + gbrainSeen++; + const body = readText(skill); + if (/Grok Build success path/.test(body) || /no Claude required/i.test(body)) { + ok(area, `${name}: Grok success path documented`); + } else { + warn(area, `${name}: missing explicit Grok success path (CLI+AGENTS)`); + } + } + if (gbrainSeen === 0) { + warn(area, 'setup-gbrain: package not installed (bridge not audited)'); + } + + // pair-agent: local must not hard-require ngrok; remote has install+security+teardown + let pairSeen = 0; + for (const name of ['gstack-pair-agent', 'pair-agent']) { + const skill = path.join(SKILLS_DIR, name, 'SKILL.md'); + if (!exists(skill)) continue; + pairSeen++; + const body = readText(skill); + if (/teardown/i.test(body) && /ngrok/i.test(body) && /127\.0\.0\.1|security/i.test(body)) { + ok(area, `${name}: remote ngrok install+security+teardown present`); + } else { + warn(area, `${name}: remote security/teardown checklist incomplete`); + } + if (/DEPENDENT/.test(body)) ok(area, `${name}: honest DEPENDENT labeling`); + } + if (pairSeen === 0) { + warn(area, 'pair-agent: package not installed (bridge not audited)'); + } +} + +// ─── Main ───────────────────────────────────────────────────── + +function main() { + console.log('== gstack-grok-compat-audit =='); + console.log(` skills-dir: ${SKILLS_DIR}`); + console.log(` phase: ${PHASE}`); + console.log(` monorepo: ${ROOT}`); + console.log(''); + + if (!exists(SKILLS_DIR)) { + fail('install', `skills dir missing: ${SKILLS_DIR} — run: cd ${ROOT} && ./setup --host grok-build`); + } else { + const pkgs = listMembershipPackages(); + console.log(` membership packages: ${pkgs.length}`); + auditRuntimeRoot(); + auditNoDoubleHome(); + auditNoUngeneratedMonorepoPointers(); + auditConnectChrome(); + auditHostBleedSample(); + auditSkillifySdkPath(); + + if (PHASE === 'ab' || PHASE === 'b' || PHASE === 'full') { + auditBridges(); + } + } + + const fails = findings.filter(f => f.severity === 'fail'); + const warns = findings.filter(f => f.severity === 'warn'); + const oks = findings.filter(f => f.severity === 'ok'); + + for (const f of findings) { + const tag = f.severity === 'fail' ? 'FAIL' : f.severity === 'warn' ? 'WARN' : 'OK '; + console.log(`[${tag}] ${f.area}: ${f.message}`); + } + + console.log(''); + console.log(`Summary: ${oks.length} ok, ${warns.length} warn, ${fails.length} fail`); + if (fails.length > 0) { + console.log('VERDICT: INCOMPATIBLE (packaging/bridge gate failed)'); + process.exit(1); + } + console.log('VERDICT: packaging COMPATIBLE' + (PHASE.includes('b') || PHASE === 'full' ? ' (+ bridge gates honest)' : '')); + process.exit(0); +} + +main(); diff --git a/bin/gstack-model-benchmark b/bin/gstack-model-benchmark index c5f5cb5b65..c199bed634 100755 --- a/bin/gstack-model-benchmark +++ b/bin/gstack-model-benchmark @@ -7,7 +7,7 @@ * gstack-model-benchmark [options] * * Options: - * --models claude,gpt,gemini Comma-separated provider list (default: claude) + * --models claude,gpt,gemini,grok Comma-separated provider list (default: claude) * --prompt "" Inline prompt instead of a file * --workdir Working dir passed to each CLI (default: cwd) * --timeout-ms Per-provider timeout (default: 300000) @@ -15,27 +15,29 @@ * --skip-unavailable Skip providers that fail available() check * (default: include them with unavailable marker) * --judge Run Anthropic SDK judge on outputs for quality score - * (requires ANTHROPIC_API_KEY; adds ~$0.05 per call) + * (requires ANTHROPIC_API_KEY; Grok-only skips judge) * --dry-run Validate flags + resolve auth, don't invoke providers * * Examples: * gstack-model-benchmark --prompt "Write a haiku about databases" --models claude,gpt * gstack-model-benchmark ./test-prompt.txt --models claude,gpt,gemini --judge - * gstack-model-benchmark --prompt "hi" --models claude,gpt,gemini --dry-run + * gstack-model-benchmark --prompt "hi" --models grok --dry-run */ import '../lib/conductor-env-shim'; import * as fs from 'fs'; import * as path from 'path'; -import { runBenchmark, formatTable, formatJson, formatMarkdown, type BenchmarkInput } from '../test/helpers/benchmark-runner'; +import { runBenchmark, formatTable, formatJson, formatMarkdown, type BenchmarkInput, type ProviderName } from '../test/helpers/benchmark-runner'; import { ClaudeAdapter } from '../test/helpers/providers/claude'; import { GptAdapter } from '../test/helpers/providers/gpt'; import { GeminiAdapter } from '../test/helpers/providers/gemini'; +import { GrokAdapter } from '../test/helpers/providers/grok'; -const ADAPTER_FACTORIES = { +const ADAPTER_FACTORIES: Record { name: string; available: () => Promise<{ ok: boolean; reason?: string }> }> = { claude: () => new ClaudeAdapter(), gpt: () => new GptAdapter(), gemini: () => new GeminiAdapter(), + grok: () => new GrokAdapter(), }; type OutputFormat = 'table' | 'json' | 'markdown'; @@ -76,13 +78,13 @@ function positionalArgs(args: string[]): string[] { return positional; } -function parseProviders(s: string | undefined): Array<'claude' | 'gpt' | 'gemini'> { +function parseProviders(s: string | undefined): ProviderName[] { if (!s) return ['claude']; - const seen = new Set<'claude' | 'gpt' | 'gemini'>(); + const seen = new Set(); for (const p of s.split(',').map(x => x.trim()).filter(Boolean)) { - if (p === 'claude' || p === 'gpt' || p === 'gemini') seen.add(p); + if (p === 'claude' || p === 'gpt' || p === 'gemini' || p === 'grok') seen.add(p); else { - console.error(`WARN: unknown provider '${p}' — skipping. Valid: claude, gpt, gemini.`); + console.error(`WARN: unknown provider '${p}' — skipping. Valid: claude, gpt, gemini, grok.`); } } return seen.size ? Array.from(seen) : ['claude']; @@ -129,11 +131,16 @@ async function main(): Promise { const report = await runBenchmark(input); if (doJudge) { - try { - const { judgeEntries } = await import('../test/helpers/benchmark-judge'); - await judgeEntries(report); - } catch (err) { - console.error(`WARN: judge unavailable: ${(err as Error).message}`); + // Grok-or-skip: judge requires Anthropic; Grok-only machines skip cleanly + if (!process.env.ANTHROPIC_API_KEY) { + console.error('WARN: judge skipped — ANTHROPIC_API_KEY not set (Grok-or-skip judge).'); + } else { + try { + const { judgeEntries } = await import('../test/helpers/benchmark-judge'); + await judgeEntries(report); + } catch (err) { + console.error(`WARN: judge unavailable: ${(err as Error).message}`); + } } } @@ -149,7 +156,7 @@ async function main(): Promise { async function dryRunReport(opts: { prompt: string; - providers: Array<'claude' | 'gpt' | 'gemini'>; + providers: ProviderName[]; workdir: string; timeoutMs: number; output: OutputFormat; diff --git a/bin/gstack-team-init b/bin/gstack-team-init index 256735f8b4..6157bad9c6 100755 --- a/bin/gstack-team-init +++ b/bin/gstack-team-init @@ -65,12 +65,13 @@ cd ~/.claude/skills/gstack && ./setup --team Skills like /qa, /ship, /review, /investigate, and /browse become available after install. Use /browse for all web browsing. Use ~/.claude/skills/gstack/... for gstack file paths.' else - SNIPPET='## gstack (REQUIRED — global install) + SNIPPET=$(cat <<'SNIPPET_EOF' +## gstack (REQUIRED — global install) **Before doing ANY work, verify gstack is installed:** -```bash -test -d ~/.claude/skills/gstack/bin && echo "GSTACK_OK" || echo "GSTACK_MISSING" +``` +node -e "const fs=require('node:fs'),os=require('node:os'),p=require('node:path'); console.log(fs.existsSync(p.join(os.homedir(),'.claude','skills','gstack','bin')) ? 'GSTACK_OK' : 'GSTACK_MISSING')" ``` If GSTACK_MISSING: STOP. Do not proceed. Tell the user: @@ -87,7 +88,9 @@ Do not skip skills, ignore gstack errors, or work around missing gstack. Using gstack skills: After install, skills like /qa, /ship, /review, /investigate, and /browse are available. Use /browse for all web browsing. -Use ~/.claude/skills/gstack/... for gstack file paths (the global path).' +Use ~/.claude/skills/gstack/... for gstack file paths (the global path). +SNIPPET_EOF +) fi # Check if CLAUDE.md already has a gstack section @@ -109,33 +112,64 @@ if [ "$MODE" = "required" ]; then HOOKS_DIR="$REPO_ROOT/.claude/hooks" SETTINGS="$REPO_ROOT/.claude/settings.json" - # Create enforcement hook script + # Create a CommonJS hook so consumer package.json module settings cannot change + # how Node 18+ loads it. mkdir -p "$HOOKS_DIR" - cat > "$HOOKS_DIR/check-gstack.sh" << 'HOOK_EOF' -#!/bin/bash -# Block skill usage when gstack is not installed globally. - -if [ ! -d "$HOME/.claude/skills/gstack/bin" ]; then - cat >&2 <<'MSG' -BLOCKED: gstack is not installed globally. - -gstack is required for AI-assisted work in this repo. + cat > "$HOOKS_DIR/check-gstack.cjs" << 'HOOK_EOF' +'use strict'; + +// Block skill usage when gstack is not installed globally. +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); + +const homeDir = process.env.HOME || process.env.USERPROFILE || os.homedir(); +const gstackBin = path.join(homeDir, '.claude', 'skills', 'gstack', 'bin'); +let verificationError = null; +let installed = false; + +try { + installed = fs.statSync(gstackBin).isDirectory(); +} catch (error) { + if (error && error.code !== 'ENOENT') { + verificationError = error; + } +} + +if (installed) { + process.stdout.write('{}\n'); +} else { + const projectDir = process.env.CLAUDE_PROJECT_DIR || '(unknown project)'; + const heading = verificationError + ? 'BLOCKED: the global gstack install could not be verified.' + : 'BLOCKED: gstack is not installed globally.'; + const instructions = `${heading} + +gstack is required for AI-assisted work in ${projectDir}. Install it: git clone --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack cd ~/.claude/skills/gstack && ./setup --team Then restart your AI coding tool. -MSG - echo '{"permissionDecision":"deny","message":"gstack is required but not installed. See stderr for install instructions."}' - exit 0 -fi - -echo '{}' +`; + + process.stderr.write(instructions); + const decision = { + permissionDecision: 'deny', + permissionDecisionReason: instructions, + }; + process.stdout.write(`${JSON.stringify({ + ...decision, + hookSpecificOutput: { + hookEventName: 'PreToolUse', + ...decision, + }, + })}\n`); +} HOOK_EOF - chmod +x "$HOOKS_DIR/check-gstack.sh" - GENERATED+=(".claude/hooks/check-gstack.sh") - echo " + .claude/hooks/check-gstack.sh — enforcement hook" + GENERATED+=(".claude/hooks/check-gstack.cjs") + echo " + .claude/hooks/check-gstack.cjs — cross-platform enforcement hook" # Add hook to project-level settings.json if command -v bun >/dev/null 2>&1; then @@ -149,18 +183,34 @@ HOOK_EOF if (!settings.hooks) settings.hooks = {}; if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = []; - // Dedup - const exists = settings.hooks.PreToolUse.some(entry => - entry.matcher === 'Skill' && - entry.hooks && entry.hooks.some(h => h.command && h.command.includes('check-gstack')) - ); - - if (!exists) { + const hookMatcher = 'Skill|skill'; + const hookCommand = \"node -e \\\"const deny=reason=>{const decision={permissionDecision:'deny',permissionDecisionReason:reason};console.error(reason);console.log(JSON.stringify({...decision,hookSpecificOutput:{hookEventName:'PreToolUse',...decision}}))};const projectDir=process.env.CLAUDE_PROJECT_DIR;if(!projectDir){deny('BLOCKED: CLAUDE_PROJECT_DIR is unavailable, so the required gstack hook cannot be loaded.')}else{try{require(require('node:path').join(projectDir, '.claude', 'hooks', 'check-gstack.cjs'))}catch{deny('BLOCKED: the required gstack hook could not be loaded. Verify project hook setup and retry.')}}\\\"\"; + let found = false; + settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(entry => { + if (!['Skill', hookMatcher].includes(entry.matcher) || !entry.hooks) return true; + + const matchingHooks = entry.hooks.filter( + h => h.command && h.command.includes('check-gstack') + ); + if (matchingHooks.length === 0) return true; + + entry.hooks = entry.hooks.filter( + h => !h.command || !h.command.includes('check-gstack') + ); + if (!found) { + entry.matcher = hookMatcher; + entry.hooks.push({ type: 'command', command: hookCommand }); + found = true; + } + return entry.hooks.length > 0; + }); + + if (!found) { settings.hooks.PreToolUse.push({ - matcher: 'Skill', + matcher: hookMatcher, hooks: [{ type: 'command', - command: '\"\$CLAUDE_PROJECT_DIR/.claude/hooks/check-gstack.sh\"' + command: hookCommand }] }); } @@ -171,6 +221,16 @@ HOOK_EOF " 2>/dev/null GENERATED+=(".claude/settings.json") echo " + .claude/settings.json — PreToolUse hook registered" + + # Remove the obsolete Bash hook only after its replacement is generated and + # registered successfully. Other project hooks are left untouched. + if [ -e "$HOOKS_DIR/check-gstack.sh" ] || [ -L "$HOOKS_DIR/check-gstack.sh" ]; then + rm "$HOOKS_DIR/check-gstack.sh" + if (cd "$REPO_ROOT" && git ls-files --error-unmatch -- .claude/hooks/check-gstack.sh >/dev/null 2>&1); then + GENERATED+=(".claude/hooks/check-gstack.sh") + fi + echo " - .claude/hooks/check-gstack.sh — removed legacy enforcement hook" + fi else echo " ! bun not found — manually add the PreToolUse hook to .claude/settings.json" fi diff --git a/claude/SKILL.md.tmpl b/claude/SKILL.md.tmpl index 94552cbe4e..1b461e05e1 100644 --- a/claude/SKILL.md.tmpl +++ b/claude/SKILL.md.tmpl @@ -30,6 +30,11 @@ modify files. The generated external invocation name is `gstack-claude`. +**MULTI_CLI_BRIDGE:** Primary modes always call the Claude Code CLI. Packaging and +Step 0 detect/install are first-class on this host; this skill does **not** work +without Claude installed and authenticated. Residual foreign-CLI dependency is +expected and labeled — never claim "works without Claude." + --- ## Step 0: Check Claude CLI @@ -42,18 +47,10 @@ CLAUDE_BIN=$(command -v claude 2>/dev/null || echo "") If `NOT_FOUND`, stop and tell the user: "Claude CLI not found. Install Claude Code, then re-run this skill." -Check auth: - -```bash -if [ -f "$HOME/.claude/.credentials.json" ] || [ -n "${ANTHROPIC_API_KEY:-}" ]; then - echo "AUTH_FOUND" -else - echo "AUTH_MISSING" -fi -``` - -If `AUTH_MISSING`, stop and tell the user: -"No Claude authentication found. Run `claude` interactively to log in, or export `ANTHROPIC_API_KEY`, then re-run this skill." +Do not preflight Claude authentication by checking credential files. Claude Code +may store login state in OS keychain or another host-managed location that is not +visible to this skill. Treat the first `claude -p` invocation as the auth check, +then surface any login/auth failure from its JSON or stderr output. --- @@ -117,10 +114,14 @@ except Exception as exc: print(f"CLAUDE_JSON_PARSE_ERROR: {exc}") sys.exit(0) +result = obj.get("result") or obj.get("response") or "" +result_lower = result.lower() + if obj.get("is_error"): print("CLAUDE_ERROR: true") + if any(term in result_lower for term in ("auth", "login", "unauthorized", "not logged in")): + print("CLAUDE_AUTH_ERROR: true") -result = obj.get("result") or obj.get("response") or "" if result: print(result) @@ -137,7 +138,8 @@ if session_id: PY ``` -If stderr contains `auth`, `login`, or `unauthorized`, tell the user: +If the parsed output contains `CLAUDE_AUTH_ERROR: true`, or stderr contains +`auth`, `login`, `unauthorized`, or `not logged in`, tell the user: "Claude authentication failed. Run `claude` interactively to authenticate or export `ANTHROPIC_API_KEY`." --- @@ -324,8 +326,7 @@ rm -f "$PROMPT_FILE" "$RESP_FILE" "$ERR_FILE" ## Error Handling - **Binary not found:** Stop with install instructions. -- **Auth missing:** Stop with login/API key instructions. -- **Auth failure from stderr:** Surface the stderr line and ask the user to re-authenticate. +- **Auth failure from Claude output or stderr:** Surface the auth line and ask the user to re-authenticate. - **JSON parse failure:** Show raw stdout from `$RESP_FILE` and stderr from `$ERR_FILE`. - **Empty response:** Tell the user "Claude returned no response. Check stderr for errors." - **Resume failure:** Delete `.context/claude-session-id` and retry with a fresh session. diff --git a/docs/ADDING_A_HOST.md b/docs/ADDING_A_HOST.md index 50654e4e23..947c91dc65 100644 --- a/docs/ADDING_A_HOST.md +++ b/docs/ADDING_A_HOST.md @@ -1,7 +1,7 @@ # Adding a New Host to gstack gstack uses a declarative host config system. Each supported AI coding agent -(Claude, Codex, Factory, Kiro, OpenCode, Slate, Cursor, OpenClaw) is defined +(Claude, Codex, Factory, Kiro, OpenCode, Slate, Cursor, OpenClaw, Qoder) is defined as a typed TypeScript config object. Adding a new host means creating one file and re-exporting it. Zero code changes to the generator, setup, or tooling. @@ -17,6 +17,7 @@ hosts/ ├── slate.ts # Slate (Random Labs) ├── cursor.ts # Cursor ├── openclaw.ts # OpenClaw (hybrid: config + adapter) +├── qoder.ts # Qoder CLI (qodercli) └── index.ts # Registry: imports all, derives Host type ``` diff --git a/docs/REMOTE_BROWSER_ACCESS.md b/docs/REMOTE_BROWSER_ACCESS.md index 88dc30bb2a..a2b7a57f06 100644 --- a/docs/REMOTE_BROWSER_ACCESS.md +++ b/docs/REMOTE_BROWSER_ACCESS.md @@ -186,7 +186,7 @@ If both agents are on the same machine, skip the copy-paste: ```bash $B pair-agent --local openclaw # writes to ~/.openclaw/skills/gstack/browse-remote.json -$B pair-agent --local codex # writes to ~/.codex/skills/gstack/browse-remote.json +$B pair-agent --local codex # writes to ~/.agents/skills/gstack/browse-remote.json $B pair-agent --local cursor # writes to ~/.cursor/skills/gstack/browse-remote.json ``` diff --git a/docs/spikes/claude-code-hook-mutation.md b/docs/spikes/claude-code-hook-mutation.md index 9d91e16cd4..e58f378f91 100644 --- a/docs/spikes/claude-code-hook-mutation.md +++ b/docs/spikes/claude-code-hook-mutation.md @@ -51,7 +51,18 @@ Optional in subagent context: `agent_id`, `agent_type`. - `"deny"` — block (feedback to Claude, NOT a synthetic answer per Codex correction in D-prefixed decisions) - `"ask"` — escalate to user -- `"defer"` — let permission flow continue + +> ⚠️ **There is no `"defer"` permissionDecision value.** The spec defines only +> `allow` / `deny` / `ask`. To express "no opinion — let the tool run and +> permission flow continue", emit **no `permissionDecision`** (an empty +> `hookSpecificOutput`, or no stdout at all). Earlier revisions of this spike +> and the hook emitted `permissionDecision: "defer"`; native Claude Code +> silently ignored the unknown value and fell through, so it appeared to work. +> **Conductor's `mcp__conductor__AskUserQuestion` bridge does not ignore it** — +> an unrecognized permissionDecision on its own injected tool hangs the +> round-trip, so the question never renders and no `tool_result` is returned. +> Always use the no-permissionDecision shape for pass-through. See the +> pass-through example below. **`updatedInput` semantics:** shallow merge of fields present in the returned object onto the original `tool_input`. Only valid with @@ -88,7 +99,8 @@ required for our hook to fire there. accepting. **`permissionDecision` precedence (when multiple hooks decide):** -`deny > ask > allow > defer` — most restrictive wins. +`deny > ask > allow > (no decision)` — most restrictive wins; a hook that +emits no `permissionDecision` defers to whatever the others decide. ## Implementation hookSpecificOutput examples @@ -107,11 +119,16 @@ required for our hook to fire there. ``` **Pass-through (no preference, or one-way safety override):** + +Emit **no `permissionDecision`** — surface `additionalContext` if there is any, +otherwise emit nothing at all (empty stdout + exit 0). Do NOT emit +`permissionDecision: "defer"` (not a real value; breaks Conductor's AUQ bridge). + ```json { "hookSpecificOutput": { "hookEventName": "PreToolUse", - "permissionDecision": "defer" + "additionalContext": "optional plan-tune memory context" } } ``` diff --git a/document-generate/SKILL.md b/document-generate/SKILL.md index 30846fc4dd..f6b3b1a39e 100644 --- a/document-generate/SKILL.md +++ b/document-generate/SKILL.md @@ -1194,7 +1194,7 @@ docs: generate [scope] documentation (Diataxis) Quadrants: [list which quadrants were produced] -Co-Authored-By: Claude Opus 4.7 +Co-Authored-By: Claude Opus 4.8 EOF )" ``` diff --git a/document-release/sections/release-body.md b/document-release/sections/release-body.md index 0f05f13b51..00f344ae27 100644 --- a/document-release/sections/release-body.md +++ b/document-release/sections/release-body.md @@ -196,7 +196,7 @@ committing. git commit -m "$(cat <<'EOF' docs: update project documentation for vX.Y.Z.W -Co-Authored-By: Claude Opus 4.7 +Co-Authored-By: Claude Opus 4.8 EOF )" ``` diff --git a/gstack-upgrade/migrations/v1.61.0.0.sh b/gstack-upgrade/migrations/v1.61.0.0.sh new file mode 100755 index 0000000000..8cb42d12ee --- /dev/null +++ b/gstack-upgrade/migrations/v1.61.0.0.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Migration: v1.61.0.0 — Codex global install moved from ~/.codex/skills to ~/.agents/skills +# Affected: users who installed for Codex before v1.61.0.0. +# +# Codex's documented user-scope skill discovery path is $HOME/.agents/skills; +# setup now installs there (community PR #2123). The old ~/.codex/skills +# location leaves stale gstack symlinks/dirs behind that would be discovered +# twice if Codex ever scans the legacy dir. Remove only gstack-owned entries +# (gstack and gstack-*); never touch anything else the user put there. +set -euo pipefail + +LEGACY="$HOME/.codex/skills" +[ -d "$LEGACY" ] || exit 0 + +for entry in "$LEGACY"/gstack "$LEGACY"/gstack-*; do + [ -e "$entry" ] || [ -L "$entry" ] || continue + if [ -L "$entry" ]; then + rm -f "$entry" 2>/dev/null || true + elif [ -d "$entry" ]; then + # Runtime roots are real dirs full of symlinks created by setup; safe to remove. + rm -rf "$entry" 2>/dev/null || true + fi +done + +# Drop the parent dirs if gstack was the only thing in them. +rmdir "$LEGACY" 2>/dev/null || true +rmdir "$HOME/.codex" 2>/dev/null || true +exit 0 diff --git a/hosts/agy.ts b/hosts/agy.ts new file mode 100644 index 0000000000..198df38270 --- /dev/null +++ b/hosts/agy.ts @@ -0,0 +1,48 @@ +import type { HostConfig } from '../scripts/host-config'; + +const agy: HostConfig = { + name: 'agy', + displayName: 'Antigravity', + cliCommand: 'agy', + cliAliases: ['antigravity'], + + globalRoot: '.gemini/config/skills/gstack', + localSkillRoot: '.agents/skills/gstack', + hostSubdir: '.agy', + usesEnvVars: true, + + frontmatter: { + mode: 'allowlist', + keepFields: ['name', 'description'], + descriptionLimit: null, + }, + + generation: { + generateMetadata: false, + skipSkills: ['codex'], // Codex skill is a Claude wrapper around codex exec + }, + + pathRewrites: [ + { from: '~/.claude/skills/gstack', to: '~/.gemini/config/skills/gstack' }, + { from: '.claude/skills/gstack', to: '.agents/skills/gstack' }, + { from: '.claude/skills', to: '.agents/skills' }, + ], + + suppressedResolvers: ['GBRAIN_CONTEXT_LOAD', 'GBRAIN_SAVE_RESULTS'], + + runtimeRoot: { + globalSymlinks: ['bin', 'browse/dist', 'browse/bin', 'design/dist', 'gstack-upgrade', 'ETHOS.md', 'review/specialists', 'qa/templates', 'qa/references', 'plan-devex-review/dx-hall-of-fame.md'], + globalFiles: { + 'review': ['checklist.md', 'design-checklist.md', 'greptile-triage.md', 'TODOS-format.md'], + }, + }, + + install: { + prefixable: false, + linkingStrategy: 'symlink-generated', + }, + + learningsMode: 'basic', +}; + +export default agy; diff --git a/hosts/claude.ts b/hosts/claude.ts index f805da040e..097fc13600 100644 --- a/hosts/claude.ts +++ b/hosts/claude.ts @@ -38,7 +38,7 @@ const claude: HostConfig = { linkingStrategy: 'real-dir-symlink', }, - coAuthorTrailer: 'Co-Authored-By: Claude Opus 4.7 ', + coAuthorTrailer: 'Co-Authored-By: Claude Opus 4.8 ', learningsMode: 'full', }; diff --git a/hosts/claude/hooks/question-preference-hook.ts b/hosts/claude/hooks/question-preference-hook.ts index 12cbd5ea28..2654438579 100644 --- a/hosts/claude/hooks/question-preference-hook.ts +++ b/hosts/claude/hooks/question-preference-hook.ts @@ -93,12 +93,23 @@ function readStdin(): Promise { } function defer(additionalContext?: string): void { - const out: Record = { - hookEventName: 'PreToolUse', - permissionDecision: 'defer', - }; - if (additionalContext) out.additionalContext = additionalContext; - process.stdout.write(JSON.stringify({ hookSpecificOutput: out })); + // "No opinion" must be a SILENT exit 0 (optionally with additionalContext + // only), never an explicit `permissionDecision: 'defer'`. `defer` is a real + // value in Claude Code, but it means "pause this tool call and hand control + // back" and is honored in print/non-interactive mode. Emitting it here made + // every AskUserQuestion get deferred in non-interactive sessions (e.g. the + // desktop app) — the question UI never rendered. Interactive mode merely + // warns and ignores it. + if (additionalContext) { + process.stdout.write( + JSON.stringify({ + hookSpecificOutput: { + hookEventName: 'PreToolUse', + additionalContext, + }, + }), + ); + } process.exit(0); } @@ -451,15 +462,25 @@ async function main(): Promise { return; } - // Not fully auto-decidable. In Conductor, AskUserQuestion is unreliable - // (native is disabled, the mcp__conductor__AskUserQuestion variant is flaky), - // so deny the tool and redirect to a prose decision brief. This is TRANSPORT - // AVOIDANCE, not preference enforcement: it fires regardless of marker, - // preference, or door type — including one-way doors, which must reach the - // human via prose rather than the unreliable tool. - if (isConductor()) { - const conductorReason = - '[conductor] AskUserQuestion is unreliable in Conductor (native disabled, MCP variant flaky). ' + + // Not fully auto-decidable. On some hosts AskUserQuestion is unreliable and + // must be avoided entirely: + // - Conductor: native AUQ is disabled and the mcp__conductor__AskUserQuestion + // variant is flaky. + // - Claude Desktop app (CLAUDE_CODE_ENTRYPOINT=claude-desktop): the tool is + // advertised + enabled (CLAUDE_CODE_ENABLE_ASK_USER_QUESTION_TOOL=true) but + // its handler returns "[Tool result missing due to internal error]" — a host + // bug verified 2026-07-01 on claude.exe 2.1.197. Critically, PostToolUse + // hooks do NOT fire on that transport-error path, so auq-error-fallback-hook + // can't rescue it after the fact; it must be pre-empted here in PreToolUse. + // On those hosts, deny the tool and redirect to a prose decision brief. This is + // TRANSPORT AVOIDANCE, not preference enforcement: it fires regardless of + // marker, preference, or door type — including one-way doors, which must reach + // the human via prose rather than the unreliable tool. + const isClaudeDesktop = (process.env.CLAUDE_CODE_ENTRYPOINT || '') === 'claude-desktop'; + if (isConductor() || isClaudeDesktop) { + const host = isConductor() ? 'conductor' : 'claude-desktop'; + const unreliableReason = + `[${host}] AskUserQuestion is unreliable in this host (missing-result / transport error). ` + 'Do NOT call AskUserQuestion (native or any mcp__*__AskUserQuestion). Render this decision as a ' + 'PROSE decision brief now: a D label, an ELI10 of the issue, a Recommendation line, then one ' + 'paragraph per choice carrying its `(recommended)` marker and `Completeness: X/10`; tell the user ' + @@ -467,7 +488,7 @@ async function main(): Promise { 'typed confirmation and do NOT proceed on a vague reply. Capture the decision with gstack-question-log ' + '(PostToolUse will not fire on a prose path).' + (memoryContext ? `\n${memoryContext}` : ''); - deny(conductorReason); + deny(unreliableReason); return; } diff --git a/hosts/codex.ts b/hosts/codex.ts index 7dc80ea877..8057e4e6e9 100644 --- a/hosts/codex.ts +++ b/hosts/codex.ts @@ -6,7 +6,7 @@ const codex: HostConfig = { cliCommand: 'codex', cliAliases: ['agents'], - globalRoot: '.codex/skills/gstack', + globalRoot: '.agents/skills/gstack', localSkillRoot: '.agents/skills/gstack', hostSubdir: '.agents', usesEnvVars: true, @@ -29,8 +29,14 @@ const codex: HostConfig = { { from: '.claude/skills/gstack', to: '.agents/skills/gstack' }, { from: '.claude/skills/review', to: '.agents/skills/gstack/review' }, { from: '.claude/skills', to: '.agents/skills' }, + { from: '${CLAUDE_SKILL_DIR}/../qa-only/SKILL.md', to: '$GSTACK_ROOT/../gstack-qa-only/SKILL.md' }, + { from: 'CLAUDE.md', to: 'AGENTS.md' }, ], + toolRewrites: { + 'AskUserQuestion': 'request_user_input', + }, + suppressedResolvers: [ 'DESIGN_OUTSIDE_VOICES', // design.ts:485 — Codex can't invoke itself 'ADVERSARIAL_STEP', // review.ts:408 — Codex can't invoke itself @@ -42,14 +48,14 @@ const codex: HostConfig = { ], runtimeRoot: { - globalSymlinks: ['bin', 'browse/dist', 'browse/bin', 'gstack-upgrade', 'ETHOS.md'], + globalSymlinks: ['bin', 'browse/dist', 'browse/bin', 'design/dist', 'gstack-upgrade', 'ETHOS.md'], globalFiles: { 'review': ['checklist.md', 'TODOS-format.md'], }, }, sidecar: { path: '.agents/skills/gstack', - symlinks: ['bin', 'browse', 'review', 'qa', 'ETHOS.md'], + symlinks: ['bin', 'browse', 'design', 'review', 'qa', 'ETHOS.md'], }, install: { diff --git a/hosts/factory.ts b/hosts/factory.ts index 08ac2f9a13..11cfd4e8d4 100644 --- a/hosts/factory.ts +++ b/hosts/factory.ts @@ -41,6 +41,12 @@ const factory: HostConfig = { 'use the Agent tool': 'dispatch a subagent', 'use the Grep tool': 'search for', 'use the Glob tool': 'find files matching', + // Agent tool: catch every phrasing, not just 'use the Agent tool'. Factory uses the plain + // noun 'delegation' (reads clean mid-sentence without echoing the surrounding 'subagent' + // prose); possessive form first, then article form, then a bare sweep. + "Claude Code's Agent tool": 'delegation', + 'the Agent tool': 'delegation', + 'Agent tool': 'delegation', }, suppressedResolvers: ['GBRAIN_CONTEXT_LOAD', 'GBRAIN_SAVE_RESULTS'], diff --git a/hosts/gbrain.ts b/hosts/gbrain.ts index ae777f2f18..962437845e 100644 --- a/hosts/gbrain.ts +++ b/hosts/gbrain.ts @@ -46,6 +46,10 @@ const gbrain: HostConfig = { 'the Read tool': 'the read tool', 'the Write tool': 'the write tool', 'the Edit tool': 'the edit tool', + // Agent tool: catch every phrasing (using/via/Use/bare), not just 'use the Agent tool'. + // Article form first (drops 'the'), then a bare sweep for the article-less forms. + 'the Agent tool': 'sessions_spawn', + 'Agent tool': 'sessions_spawn', }, // GBrain gets brain-aware resolvers. All other hosts suppress these. diff --git a/hosts/grok-build.ts b/hosts/grok-build.ts new file mode 100644 index 0000000000..423161aa8b --- /dev/null +++ b/hosts/grok-build.ts @@ -0,0 +1,113 @@ +import type { HostConfig } from '../scripts/host-config'; + +/** + * Grok Build (xAI) host — based on community PR #2028 (adamouabakar), + * aligned with cursor/factory host patterns and Grok skill discovery. + * + * Skills generate into /.grok/skills/gstack-* and install under + * ~/.grok/skills/ (flat skill packages + runtime root at ~/.grok/skills/gstack). + * + * Grok discovers user skills from ~/.grok/skills//SKILL.md. + * Frontmatter `name:` stays unprefixed (browse, ship, …) so slash commands + * remain /browse, /ship, etc. Directory names use gstack- prefix (external hosts). + */ +const grokBuild: HostConfig = { + name: 'grok-build', + displayName: 'Grok Build', + cliCommand: 'grok', + cliAliases: ['grok-build'], + + // Relative to $HOME for global install path docs / preamble + globalRoot: '.grok/skills/gstack', + // Project-local runtime root (optional team mode) + localSkillRoot: '.grok/skills/gstack', + // Gitignored generated skill docs live here + hostSubdir: '.grok', + usesEnvVars: true, + + frontmatter: { + mode: 'allowlist', + // Keep triggers so Grok model-invocation routing still works + keepFields: ['name', 'description', 'triggers', 'allowed-tools'], + descriptionLimit: null, + }, + + generation: { + generateMetadata: false, + // Upstream convention: /codex skill is a Claude↔Codex bridge; all external + // hosts skip it (host-config.test.ts). Grok users already have the openai-codex + // plugin; keep /claude as an optional outside-voice skill. + skipSkills: ['codex'], // Codex skill is a Claude wrapper around codex exec + }, + + pathRewrites: [ + { from: '~/.claude/skills/gstack', to: '$GSTACK_ROOT' }, + { from: '.claude/skills/gstack', to: '.grok/skills/gstack' }, + { from: '.claude/skills/review', to: '.grok/skills/gstack/review' }, + { from: '.claude/skills', to: '.grok/skills' }, + { from: '~/.claude/skills', to: '~/.grok/skills' }, + { from: 'CLAUDE.md', to: 'AGENTS.md' }, + // Defense-in-depth if a Claude model overlay ever slips through (U3 / KTD6) + { from: 'MODEL_OVERLAY: claude', to: 'MODEL_OVERLAY: none' }, + { from: 'use the Skill tool', to: 'invoke the skill via slash command or skill load' }, + ], + + toolRewrites: { + 'use the Bash tool': 'run this command in the shell', + 'use the Write tool': 'create this file', + 'use the Read tool': 'read the file', + 'use the Edit tool': 'edit the file', + 'use the Agent tool': 'dispatch a subagent', + 'use the Grep tool': 'search for', + 'use the Glob tool': 'find files matching', + 'use the Skill tool': 'invoke the skill via slash command or skill load', + AskUserQuestion: 'ask_user_question', + ExitPlanMode: 'exit_plan_mode', + }, + + // Suppress Claude-only outside-voice orchestration that assumes Claude can + // spawn Codex as itself. Keep plan/review skills; they still run on Grok. + suppressedResolvers: [ + 'GBRAIN_CONTEXT_LOAD', + 'GBRAIN_SAVE_RESULTS', + ], + + // Thin runtime root: every asset skills resolve via $GSTACK_ROOT (R1 / U1). + // Dual-write with setup create_grok_runtime_root — keep lists in sync. + runtimeRoot: { + globalSymlinks: [ + 'bin', + 'browse/dist', + 'browse/bin', + 'browse/src', + 'design/dist', + 'make-pdf/dist', + 'extension', + 'scripts', + 'review/specialists', + 'gstack-upgrade', + 'ETHOS.md', + ], + globalFiles: { + review: [ + 'checklist.md', + 'TODOS-format.md', + 'design-checklist.md', + 'greptile-triage.md', + ], + }, + }, + + install: { + prefixable: false, + linkingStrategy: 'symlink-generated', + }, + + learningsMode: 'basic', + boundaryInstruction: + 'IMPORTANT: Prefer ~/.grok/skills/gstack and $GSTACK_ROOT over ~/.claude/skills/gstack. ' + + 'Do not assume Claude Code tools (TodoWrite, Skill tool, Claude-in-Chrome MCP). ' + + 'Use Grok shell/read/edit/web tools and ask_user_question. Prefer /browse over browser MCPs.', +}; + +export default grokBuild; diff --git a/hosts/hermes.ts b/hosts/hermes.ts index 43598989df..4a1ecd198d 100644 --- a/hosts/hermes.ts +++ b/hosts/hermes.ts @@ -15,6 +15,7 @@ const hermes: HostConfig = { mode: 'allowlist', keepFields: ['name', 'description'], descriptionLimit: null, + nameTransform: 'external-skill-name', }, generation: { @@ -41,6 +42,11 @@ const hermes: HostConfig = { 'the Read tool': 'the read_file tool', 'the Write tool': 'the patch tool', 'the Edit tool': 'the patch tool', + // Agent tool: catch every phrasing (using/via/Use/bare), not just 'use the Agent tool'. + // Article form first (drops 'the' -> no dangling article), then a bare sweep for the + // article-less forms ('via Agent tool', '(Agent tool', "Claude Code's Agent tool"). + 'the Agent tool': 'delegate_task', + 'Agent tool': 'delegate_task', }, suppressedResolvers: [ diff --git a/hosts/index.ts b/hosts/index.ts index cc1c213b53..5a0f81a967 100644 --- a/hosts/index.ts +++ b/hosts/index.ts @@ -16,9 +16,14 @@ import cursor from './cursor'; import openclaw from './openclaw'; import hermes from './hermes'; import gbrain from './gbrain'; +import pi from './pi'; +import agy from './agy'; +import vibe from './vibe'; +import qoder from './qoder'; +import grokBuild from './grok-build'; /** All registered host configs. Add new hosts here. */ -export const ALL_HOST_CONFIGS: HostConfig[] = [claude, codex, factory, kiro, opencode, slate, cursor, openclaw, hermes, gbrain]; +export const ALL_HOST_CONFIGS: HostConfig[] = [claude, codex, factory, kiro, opencode, slate, cursor, openclaw, hermes, gbrain, pi, agy, vibe, qoder, grokBuild]; /** Map from host name to config. */ export const HOST_CONFIG_MAP: Record = Object.fromEntries( @@ -65,4 +70,4 @@ export function getExternalHosts(): HostConfig[] { } // Re-export individual configs for direct import -export { claude, codex, factory, kiro, opencode, slate, cursor, openclaw, hermes, gbrain }; +export { claude, codex, factory, kiro, opencode, slate, cursor, openclaw, hermes, gbrain, pi, agy, vibe, qoder, grokBuild }; diff --git a/hosts/openclaw.ts b/hosts/openclaw.ts index f8268b5c7e..9c9082617c 100644 --- a/hosts/openclaw.ts +++ b/hosts/openclaw.ts @@ -44,6 +44,10 @@ const openclaw: HostConfig = { 'the Read tool': 'the read tool', 'the Write tool': 'the write tool', 'the Edit tool': 'the edit tool', + // Agent tool: catch every phrasing (using/via/Use/bare), not just 'use the Agent tool'. + // Article form first (drops 'the'), then a bare sweep for the article-less forms. + 'the Agent tool': 'sessions_spawn', + 'Agent tool': 'sessions_spawn', }, // Suppress Claude-specific preamble sections that don't apply to OpenClaw diff --git a/hosts/pi.ts b/hosts/pi.ts new file mode 100644 index 0000000000..540aabebd5 --- /dev/null +++ b/hosts/pi.ts @@ -0,0 +1,61 @@ +import type { HostConfig } from '../scripts/host-config'; + +const pi: HostConfig = { + name: 'pi', + displayName: 'Pi', + cliCommand: 'pi', + cliAliases: [], + + globalRoot: '.pi/agent/skills/gstack', + localSkillRoot: '.pi/skills/gstack', + hostSubdir: '.pi', + usesEnvVars: true, + + frontmatter: { + mode: 'allowlist', + keepFields: ['name', 'description'], + descriptionLimit: 1024, + descriptionLimitBehavior: 'warn', // no committed pi output to catch an error early + }, + + generation: { + generateMetadata: false, + skipSkills: ['codex'], // Codex skill is a Claude wrapper around codex exec + }, + + pathRewrites: [ + { from: '~/.claude/skills/gstack', to: '~/.pi/agent/skills/gstack' }, + { from: '.claude/skills/gstack', to: '.pi/skills/gstack' }, + { from: '.claude/skills', to: '.pi/skills' }, + ], + toolRewrites: { + 'use the Bash tool': 'use the bash tool', + 'use the Write tool': 'use the write tool', + 'use the Read tool': 'use the read tool', + 'use the Edit tool': 'use the edit tool', + 'use the Grep tool': 'use the grep tool', + 'use the Glob tool': 'use the find tool', + 'use the Agent tool': 'use the agent tool', + 'the Bash tool': 'the bash tool', + 'the Read tool': 'the read tool', + 'the Write tool': 'the write tool', + 'the Edit tool': 'the edit tool', + }, + suppressedResolvers: ['GBRAIN_CONTEXT_LOAD', 'GBRAIN_SAVE_RESULTS'], + + runtimeRoot: { + globalSymlinks: ['bin', 'browse/dist', 'browse/bin', 'design/dist', 'gstack-upgrade', 'ETHOS.md', 'review/specialists', 'qa/templates', 'qa/references', 'plan-devex-review/dx-hall-of-fame.md'], + globalFiles: { + 'review': ['checklist.md', 'design-checklist.md', 'greptile-triage.md', 'TODOS-format.md'], + }, + }, + + install: { + prefixable: false, + linkingStrategy: 'symlink-generated', + }, + + learningsMode: 'basic', +}; + +export default pi; diff --git a/hosts/qoder.ts b/hosts/qoder.ts new file mode 100644 index 0000000000..bd87d9423d --- /dev/null +++ b/hosts/qoder.ts @@ -0,0 +1,48 @@ +import type { HostConfig } from '../scripts/host-config'; + +const qoder: HostConfig = { + name: 'qoder', + displayName: 'Qoder', + cliCommand: 'qodercli', + cliAliases: ['qoder'], + + globalRoot: '.qoder/skills/gstack', + localSkillRoot: '.qoder/skills/gstack', + hostSubdir: '.qoder', + usesEnvVars: true, + + frontmatter: { + mode: 'allowlist', + keepFields: ['name', 'description'], + descriptionLimit: null, + }, + + generation: { + generateMetadata: false, + skipSkills: ['codex'], // Codex skill is a Claude wrapper around codex exec + }, + + pathRewrites: [ + { from: '~/.claude/skills/gstack', to: '~/.qoder/skills/gstack' }, + { from: '.claude/skills/gstack', to: '.qoder/skills/gstack' }, + { from: '.claude/skills', to: '.qoder/skills' }, + ], + + suppressedResolvers: ['GBRAIN_CONTEXT_LOAD', 'GBRAIN_SAVE_RESULTS'], + + runtimeRoot: { + globalSymlinks: ['bin', 'browse/dist', 'browse/bin', 'design/dist', 'gstack-upgrade', 'ETHOS.md', 'review/specialists', 'qa/templates', 'qa/references', 'plan-devex-review/dx-hall-of-fame.md'], + globalFiles: { + 'review': ['checklist.md', 'design-checklist.md', 'greptile-triage.md', 'TODOS-format.md'], + }, + }, + + install: { + prefixable: false, + linkingStrategy: 'symlink-generated', + }, + + learningsMode: 'basic', +}; + +export default qoder; diff --git a/hosts/vibe.ts b/hosts/vibe.ts new file mode 100644 index 0000000000..e22ac875d2 --- /dev/null +++ b/hosts/vibe.ts @@ -0,0 +1,58 @@ +import type { HostConfig } from '../scripts/host-config'; + +const vibe: HostConfig = { + name: 'vibe', + displayName: 'Mistral Vibe', + cliCommand: 'vibe', + cliAliases: ['mistral-vibe'], + + globalRoot: '.vibe/skills/gstack', + localSkillRoot: '.vibe/skills/gstack', + hostSubdir: '.vibe', + usesEnvVars: true, + + frontmatter: { + mode: 'allowlist', + keepFields: ['name', 'description'], + descriptionLimit: null, + }, + + generation: { + generateMetadata: false, + skipSkills: ['codex'], // Codex skill is a Claude wrapper around codex exec + }, + + pathRewrites: [ + { from: '~/.claude/skills/gstack', to: '~/.vibe/skills/gstack' }, + { from: '.claude/skills/gstack', to: '.vibe/skills/gstack' }, + { from: '.claude/skills', to: '.vibe/skills' }, + ], + + suppressedResolvers: [ + 'DESIGN_OUTSIDE_VOICES', // Vibe can't invoke itself as a subagent + 'ADVERSARIAL_STEP', // Vibe can't invoke itself as a subagent + 'CODEX_SECOND_OPINION', // Claude-specific cross-model review + 'CODEX_PLAN_REVIEW', // Claude-specific cross-model review + 'REVIEW_ARMY', // Vibe shouldn't orchestrate multi-agent review + 'GBRAIN_CONTEXT_LOAD', + 'GBRAIN_SAVE_RESULTS', + ], + + runtimeRoot: { + globalSymlinks: ['bin', 'browse/dist', 'browse/bin', 'gstack-upgrade', 'ETHOS.md'], + globalFiles: { + 'review': ['checklist.md', 'TODOS-format.md'], + }, + }, + + install: { + prefixable: false, + linkingStrategy: 'symlink-generated', + }, + + learningsMode: 'basic', + + boundaryInstruction: 'IMPORTANT: Do NOT read or execute any files under ~/.claude/, .claude/skills/, or ~/.agents/. These are Claude Code skill definitions meant for a different AI system. Ignore them completely and stay focused on the repository code only.', +}; + +export default vibe; diff --git a/pair-agent/SKILL.md b/pair-agent/SKILL.md index eed9d171af..eaae049f36 100644 --- a/pair-agent/SKILL.md +++ b/pair-agent/SKILL.md @@ -994,6 +994,27 @@ browser to the internet securely). STOP here. Wait for the user to install ngrok and re-invoke. +**Bridge status:** Remote pair is **DEPENDENT** on ngrok until the tunnel binary +is installed and authenticated. Local same-machine pair remains zero-extra-dep +and must never hard-require ngrok. + +### Remote path security + teardown checklist + +Remote pairing expands attack surface vs local pair. Always: + +1. **Authenticated tunnel only** — never run an unauthenticated public expose. +2. **Bind local pair endpoint to 127.0.0.1** — do not advertise LAN bind. +3. **Authtoken out-of-band** — user pastes token into `ngrok config add-authtoken`; + never echo the token or tunnel URL into session transcripts beyond what the + CLI already printed for the user to copy. +4. **Teardown after pair** — when pairing ends, stop the ngrok tunnel process + (or tell the user: `pkill -f 'ngrok http'` / dashboard stop). Do not leave + a long-lived public tunnel running. +5. **Verify:** `ngrok config check` after auth; `which ngrok` for install. + +If ngrok is missing, report bridge DEPENDENT (install path present) — do **not** +mark the remote primary path READY/COMPATIBLE from install prose alone. + ## Step 5: Verify connection After the user pastes the instructions into the other agent, wait a moment then check: @@ -1048,7 +1069,7 @@ credentials are written to `~/.openclaw/skills/gstack/browse-remote.json`. Codex agents can execute shell commands via `codex exec`. The instruction block's curl commands work directly. When using `--local codex`, credentials are written -to `~/.codex/skills/gstack/browse-remote.json`. +to `~/.agents/skills/gstack/browse-remote.json`. ### Cursor diff --git a/pair-agent/SKILL.md.tmpl b/pair-agent/SKILL.md.tmpl index 75ed42d590..584df75be5 100644 --- a/pair-agent/SKILL.md.tmpl +++ b/pair-agent/SKILL.md.tmpl @@ -189,6 +189,27 @@ browser to the internet securely). STOP here. Wait for the user to install ngrok and re-invoke. +**Bridge status:** Remote pair is **DEPENDENT** on ngrok until the tunnel binary +is installed and authenticated. Local same-machine pair remains zero-extra-dep +and must never hard-require ngrok. + +### Remote path security + teardown checklist + +Remote pairing expands attack surface vs local pair. Always: + +1. **Authenticated tunnel only** — never run an unauthenticated public expose. +2. **Bind local pair endpoint to 127.0.0.1** — do not advertise LAN bind. +3. **Authtoken out-of-band** — user pastes token into `ngrok config add-authtoken`; + never echo the token or tunnel URL into session transcripts beyond what the + CLI already printed for the user to copy. +4. **Teardown after pair** — when pairing ends, stop the ngrok tunnel process + (or tell the user: `pkill -f 'ngrok http'` / dashboard stop). Do not leave + a long-lived public tunnel running. +5. **Verify:** `ngrok config check` after auth; `which ngrok` for install. + +If ngrok is missing, report bridge DEPENDENT (install path present) — do **not** +mark the remote primary path READY/COMPATIBLE from install prose alone. + ## Step 5: Verify connection After the user pastes the instructions into the other agent, wait a moment then check: @@ -243,7 +264,7 @@ credentials are written to `~/.openclaw/skills/gstack/browse-remote.json`. Codex agents can execute shell commands via `codex exec`. The instruction block's curl commands work directly. When using `--local codex`, credentials are written -to `~/.codex/skills/gstack/browse-remote.json`. +to `~/.agents/skills/gstack/browse-remote.json`. ### Cursor diff --git a/scripts/gen-skill-docs.ts b/scripts/gen-skill-docs.ts index 71aa1a34ca..5cce983e4b 100644 --- a/scripts/gen-skill-docs.ts +++ b/scripts/gen-skill-docs.ts @@ -18,6 +18,7 @@ import * as path from 'path'; import type { Host, TemplateContext } from './resolvers/types'; import { HOST_PATHS, unwrapResolver } from './resolvers/types'; import { RESOLVERS } from './resolvers/index'; +import { hostUsesSectionPointers } from './resolvers/sections'; import { externalSkillName, extractHookSafetyProse as _extractHookSafetyProse, extractNameAndDescription as _extractNameAndDescription, condenseOpenAIShortDescription as _condenseOpenAIShortDescription, generateOpenAIYaml as _generateOpenAIYaml } from './resolvers/codex-helpers'; import { generatePlanCompletionAuditShip, generatePlanCompletionAuditReview, generatePlanVerificationExec } from './resolvers/review'; import { ALL_HOST_CONFIGS, ALL_HOST_NAMES, resolveHostArg, getHostConfig } from '../hosts/index'; @@ -91,9 +92,11 @@ let HOST: Host = HOST_ARG_VAL === 'all' ? 'claude' : HOST_ARG_VAL; // ─── Model Overlay Selection ──────────────────────────────── // --model is explicit. We do NOT auto-detect from host (host ≠ model). -// Default is 'claude'. Missing overlay file → empty string (graceful). +// Default is 'claude' for most hosts. Grok Build suppresses the Claude +// model overlay body unless --model is passed explicitly (U3 / KTD6). import { ALL_MODEL_NAMES, resolveModel, type Model } from './models'; const MODEL_ARG = process.argv.find(a => a.startsWith('--model')); +const MODEL_EXPLICIT = !!MODEL_ARG; const MODEL_ARG_VAL: Model = (() => { if (!MODEL_ARG) return 'claude'; const val = MODEL_ARG.includes('=') ? MODEL_ARG.split('=')[1] : process.argv[process.argv.indexOf(MODEL_ARG) + 1]; @@ -507,7 +510,7 @@ policy: * Codex: keeps name + description only, enforces 1024-char limit. * Factory: keeps name + description + user-invocable, conditionally adds disable-model-invocation. */ -function transformFrontmatter(content: string, host: Host): string { +function transformFrontmatter(content: string, host: Host, generatedSkillName?: string): string { const hostConfig = getHostConfig(host); const fm = hostConfig.frontmatter; @@ -531,6 +534,9 @@ function transformFrontmatter(content: string, host: Host): string { const frontmatter = content.slice(fmStart + 4, fmEnd); const body = content.slice(fmEnd + 4); const { name, description } = extractNameAndDescription(content); + const emittedName = fm.nameTransform === 'external-skill-name' && generatedSkillName + ? generatedSkillName + : name; // Description limit enforcement if (fm.descriptionLimit) { @@ -538,11 +544,11 @@ function transformFrontmatter(content: string, host: Host): string { if (description.length > fm.descriptionLimit) { if (behavior === 'error') { throw new Error( - `${hostConfig.displayName} description for "${name}" is ${description.length} chars (max ${fm.descriptionLimit}). ` + + `${hostConfig.displayName} description for "${emittedName}" is ${description.length} chars (max ${fm.descriptionLimit}). ` + `Compress the description in the .tmpl file.` ); } else if (behavior === 'warn') { - console.warn(`WARNING: ${hostConfig.displayName} description for "${name}" exceeds ${fm.descriptionLimit} chars`); + console.warn(`WARNING: ${hostConfig.displayName} description for "${emittedName}" exceeds ${fm.descriptionLimit} chars`); } // 'truncate' — silently proceed } @@ -550,7 +556,7 @@ function transformFrontmatter(content: string, host: Host): string { // Build frontmatter with allowed fields const indentedDesc = description.split('\n').map(l => ` ${l}`).join('\n'); - let newFm = `---\nname: ${name}\ndescription: |\n${indentedDesc}\n`; + let newFm = `---\nname: ${emittedName}\ndescription: |\n${indentedDesc}\n`; // Add extra fields (host-wide) if (fm.extraFields) { @@ -729,9 +735,12 @@ function buildContext( const preambleTier = tierMatch ? parseInt(tierMatch[1], 10) : undefined; const interactiveMatch = tmplContent.match(/^interactive:\s*(true|false)\s*$/m); const interactive = interactiveMatch ? interactiveMatch[1] === 'true' : undefined; + // Grok-native packaging: no Claude MODEL_OVERLAY section unless user forced --model + const modelForHost: Model | undefined = + host === 'grok-build' && !MODEL_EXPLICIT ? undefined : MODEL_ARG_VAL; return { skillName, tmplPath, benefitsFrom, host, paths: HOST_PATHS[host], - preambleTier, model: MODEL_ARG_VAL, interactive, explainLevel: EXPLAIN_LEVEL, + preambleTier, model: modelForHost, interactive, explainLevel: EXPLAIN_LEVEL, }; } @@ -772,7 +781,7 @@ function processExternalHost( const safetyProse = extractHookSafetyProse(tmplContent); // Transform frontmatter (host-aware) - let result = transformFrontmatter(content, host); + let result = transformFrontmatter(content, host, name); // Insert safety advisory at the top of the body (after frontmatter) if (safetyProse) { @@ -1019,14 +1028,15 @@ for (const currentHost of hostsToRun) { } } - // ─── Section generation (v2 plan T9, Claude-first carve) ─── - // On-demand sections/*.md for carved skills. Generated for CLAUDE ONLY: - // every other host inlines section content via the {{SECTION:id}} resolver - // (keeping the full monolith skill), so they need no section files and we - // sidestep host-portable section paths until that plumbing lands. No-op for - // any skill without a sections/ dir. Mirrors the SKILL.md DRY_RUN handling so - // sections participate in the freshness gate. - for (const sec of currentHost === 'claude' ? discoverSectionTemplates(ROOT) : []) { + // ─── Section generation (v2 plan T9 carve) ─── + // On-demand sections/*.md for carved skills. + // Pointer hosts (Claude, Grok Build): emit section files next to the skill + // package; {{SECTION:id}} is a STOP-Read pointer (keeps the skeleton small). + // Inline hosts (Codex, Factory, …): {{SECTION:id}} inlines content into the + // monolith SKILL.md — no separate section files. + // No-op for any skill without a sections/ dir. Mirrors the SKILL.md DRY_RUN + // handling so sections participate in the freshness gate. + for (const sec of hostUsesSectionPointers(currentHost) ? discoverSectionTemplates(ROOT) : []) { if (currentHostConfig.generation.includeSkills?.length && !currentHostConfig.generation.includeSkills.includes(sec.skillDir)) continue; if (currentHostConfig.generation.skipSkills?.length && diff --git a/scripts/host-config.ts b/scripts/host-config.ts index 4421c4a799..cd9503027f 100644 --- a/scripts/host-config.ts +++ b/scripts/host-config.ts @@ -46,6 +46,8 @@ export interface HostConfig { descriptionLimit?: number | null; /** What to do when description exceeds limit. Default: 'error'. */ descriptionLimitBehavior?: 'error' | 'truncate' | 'warn'; + /** Override the emitted frontmatter name for generated skills. */ + nameTransform?: 'identity' | 'external-skill-name'; /** Additional frontmatter fields to inject (host-wide). */ extraFields?: Record; /** Rename fields from template (e.g., { 'voice-triggers': 'triggers' }). */ diff --git a/scripts/proactive-suggestions.json b/scripts/proactive-suggestions.json index d08c608533..ec7f7ab612 100644 --- a/scripts/proactive-suggestions.json +++ b/scripts/proactive-suggestions.json @@ -15,7 +15,7 @@ }, "benchmark-models": { "lead": "Cross-model benchmark for gstack skills.", - "routing": "Runs the same prompt through Claude,\nGPT (via Codex CLI), and Gemini side-by-side — compares latency, tokens, cost,\nand optionally quality via LLM judge. Answers \"which model is actually best\nfor this skill?\" with data instead of vibes. Separate from /benchmark, which\nmeasures web page performance. Use when: \"benchmark models\", \"compare models\",\n\"which model is best for X\", \"cross-model comparison\", \"model shootout\".", + "routing": "Runs the same prompt through Claude,\nGPT (via Codex CLI), Gemini, and Grok side-by-side — compares latency, tokens,\ncost, and optionally quality via LLM judge. Answers \"which model is actually\nbest for this skill?\" with data instead of vibes. Separate from /benchmark,\nwhich measures web page performance. Use when: \"benchmark models\", \"compare\nmodels\", \"which model is best for X\", \"cross-model comparison\", \"model\nshootout\".", "voice_line": "Voice triggers (speech-to-text aliases): \"compare models\", \"model shootout\", \"which model is best\"." }, "browse": { diff --git a/scripts/resolvers/autoplan.ts b/scripts/resolvers/autoplan.ts new file mode 100644 index 0000000000..ae1c1e535f --- /dev/null +++ b/scripts/resolvers/autoplan.ts @@ -0,0 +1,253 @@ +import type { TemplateContext } from './types'; + +type Phase = 'ceo' | 'design' | 'eng' | 'dx'; + +const CLAUDE_JSON_PARSE_SNIPPET = String.raw`python3 - "$CLAUDE_RESP_FILE" <<'PY' +import json, sys +path = sys.argv[1] +try: + obj = json.load(open(path)) +except Exception as exc: + print(f"CLAUDE_JSON_PARSE_ERROR: {exc}") + sys.exit(0) + +if obj.get("is_error"): + print("CLAUDE_ERROR: true") + +result = obj.get("result") or obj.get("response") or "" +if result: + print(result) + +usage = obj.get("usage") or {} +input_tokens = usage.get("input_tokens", 0) or 0 +output_tokens = usage.get("output_tokens", 0) or 0 +cache_read = usage.get("cache_read_input_tokens", 0) or 0 +model = obj.get("model") or "unknown" +session_id = obj.get("session_id") or "" + +print(f"\nTokens: input={input_tokens} output={output_tokens} cache_read={cache_read} | Model: {model}") +if session_id: + print(f"SESSION_ID:{session_id}") +PY`; + +const PHASE_PROMPTS: Record = { + ceo: { + tmpPrefix: 'ceo', + codex: `IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only. + +You are a CEO/founder advisor reviewing a development plan. +Challenge the strategic foundations: Are the premises valid or assumed? Is this the +right problem to solve, or is there a reframing that would be 10x more impactful? +What alternatives were dismissed too quickly? What competitive or market risks are +unaddressed? What scope decisions will look foolish in 6 months? Be adversarial. +No compliments. Just the strategic blind spots. +File: `, + claude: `Read the plan file at . You are an independent CEO/founder advisor +reviewing a development plan. Challenge the strategic foundations: +1. Are the premises valid or assumed? +2. Is this the right problem to solve, or is there a reframing that would be 10x more impactful? +3. What alternatives were dismissed too quickly? +4. What competitive or market risks are unaddressed? +5. What scope decisions will look foolish in 6 months? +Be adversarial. No compliments. Just the strategic blind spots. +You may use read-only file tools (Read, Grep, Glob). Do NOT modify files or run commands.`, + }, + design: { + tmpPrefix: 'design', + codex: `IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only. + +Read the plan file at . Evaluate this plan's +UI/UX design decisions. + +Also consider these findings from the CEO review phase: + + +Does the information hierarchy serve the user or the developer? Are interaction +states (loading, empty, error, partial) specified or left to the implementer's +imagination? Is the responsive strategy intentional or afterthought? Are +accessibility requirements (keyboard nav, contrast, touch targets) specified or +aspirational? Does the plan describe specific UI decisions or generic patterns? +What design decisions will haunt the implementer if left ambiguous? +Be opinionated. No hedging.`, + claude: `Read the plan file at . You are an independent senior product designer +reviewing this plan's UI/UX design decisions. + +Also consider these findings from the CEO review phase: + + +Evaluate: +1. Does the information hierarchy serve the user or the developer? +2. Which interaction states (loading, empty, error, partial) are unspecified? +3. Is the responsive strategy intentional or an afterthought? +4. Are accessibility requirements concrete or aspirational? +5. What design decisions will haunt the implementer if left ambiguous? +Be opinionated. No hedging. +You may use read-only file tools (Read, Grep, Glob). Do NOT modify files or run commands.`, + }, + eng: { + tmpPrefix: 'eng', + codex: `IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only. + +Review this plan for architectural issues, missing edge cases, +and hidden complexity. Be adversarial. + +Also consider these findings from prior review phases: +CEO: +Design: + +File: `, + claude: `Read the plan file at . You are an independent senior engineer +reviewing this plan for architectural issues, missing edge cases, and hidden complexity. + +Also consider these findings from prior review phases: +CEO: +Design: + +Evaluate: +1. Architecture: Is the component structure sound? Coupling concerns? +2. Edge cases: What breaks under 10x load? What's the nil/empty/error path? +3. Tests: What's missing from the test plan? What would break at 2am Friday? +4. Security: New attack surface? Auth boundaries? Input validation? +5. Hidden complexity: What looks simple but isn't? +Be adversarial. +You may use read-only file tools (Read, Grep, Glob). Do NOT modify files or run commands.`, + }, + dx: { + tmpPrefix: 'dx', + codex: `IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only. + +Read the plan file at . Evaluate this plan's developer experience. + +Also consider these findings from prior review phases: +CEO: +Eng: + +You are a developer who has never seen this product. Evaluate: +1. Time to hello world: how many steps from zero to working? Target is under 5 minutes. +2. Error messages: when something goes wrong, does the dev know what, why, and how to fix? +3. API/CLI design: are names guessable? Are defaults sensible? Is it consistent? +4. Docs: can a dev find what they need in under 2 minutes? Are examples copy-paste-complete? +5. Upgrade path: can devs upgrade without fear? Migration guides? Deprecation warnings? +Be adversarial. Think like a developer who is evaluating this against 3 competitors.`, + claude: `Read the plan file at . You are an independent DX engineer +reviewing this plan's developer experience. + +Also consider these findings from prior review phases: +CEO: +Eng: + +Evaluate: +1. Getting started: how many steps from zero to hello world? What's the TTHW? +2. API/CLI ergonomics: naming consistency, sensible defaults, progressive disclosure? +3. Error handling: does every error path specify problem + cause + fix + docs link? +4. Documentation: copy-paste examples? Information architecture? Interactive elements? +5. Escape hatches: can developers override every opinionated default? +Be adversarial. +You may use read-only file tools (Read, Grep, Glob). Do NOT modify files or run commands.`, + }, +}; + +function codexOutsideVoiceBlock(ctx: TemplateContext, phase: Phase): string { + const prompt = PHASE_PROMPTS[phase].codex; + return ` **Codex outside voice** (via Bash): + \`\`\`bash + _REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; } + _gstack_codex_timeout_wrapper 600 codex exec "${prompt}" -C "$_REPO_ROOT" -s read-only --enable web_search_cached < /dev/null + _CODEX_EXIT=$? + if [ "$_CODEX_EXIT" = "124" ]; then + _gstack_codex_log_event "codex_timeout" "600" + _gstack_codex_log_hang "autoplan" "0" + echo "[outside voice stalled past 10 minutes — tagging as [outside-voice-unavailable] for this phase and proceeding with subagent only]" + fi + \`\`\` + Timeout: 10 minutes (shell-wrapper) + 12 minutes (Bash outer gate). On hang, auto-degrades this phase's outside voice.`; +} + +function claudeOutsideVoiceBlock(phase: Phase): string { + const meta = PHASE_PROMPTS[phase]; + return ` **Claude outside voice** (via Bash): + \`\`\`bash + _REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; } + cd "$_REPO_ROOT" + CLAUDE_PROMPT_FILE=$(mktemp /tmp/gstack-autoplan-claude-${meta.tmpPrefix}-txt-XXXXXXXX) + CLAUDE_RESP_FILE=$(mktemp /tmp/gstack-autoplan-claude-${meta.tmpPrefix}-json-XXXXXXXX) + CLAUDE_ERR_FILE=$(mktemp /tmp/gstack-autoplan-claude-${meta.tmpPrefix}-txt-XXXXXXXX) + cat > "$CLAUDE_PROMPT_FILE" <<'EOF' +${meta.claude} +EOF + cat "$CLAUDE_PROMPT_FILE" | claude -p --output-format json --disable-slash-commands --allowedTools Read,Grep,Glob --disallowedTools Bash,Edit,Write > "$CLAUDE_RESP_FILE" 2>"$CLAUDE_ERR_FILE" + ${CLAUDE_JSON_PARSE_SNIPPET} + cat "$CLAUDE_ERR_FILE" + rm -f "$CLAUDE_PROMPT_FILE" "$CLAUDE_RESP_FILE" "$CLAUDE_ERR_FILE" + \`\`\` + Timeout: 10 minutes (Bash outer gate). On auth failure, empty response, parse failure, or timeout, auto-degrades this phase's outside voice.`; +} + +export function generateAutoplanOutsideVoicePreflight(ctx: TemplateContext): string { + if (ctx.host === 'codex') { + return `## Phase 0.5: Outside-voice preflight + +Before invoking any outside voice, preflight the CLI and auth once for the rest of +the workflow. On Codex hosts, the outside voice is Claude CLI and the subagent is +the host-native Codex subagent. + +\`\`\`bash +CLAUDE_BIN=$(command -v claude 2>/dev/null || echo "") +if [ -z "$CLAUDE_BIN" ]; then + echo "[outside-voice-unavailable: Claude CLI not found] — proceeding with subagent only" + _OUTSIDE_VOICE_AVAILABLE=false +else + # No credentials-file probe: modern Claude Code stores auth in the OS + # keychain, so a file check false-negatives. The first claude -p call is + # the auth check — on failure that phase auto-degrades. + _OUTSIDE_VOICE_AVAILABLE=true +fi +\`\`\` + +If \`_OUTSIDE_VOICE_AVAILABLE=false\`, all outside-voice phases below degrade to +\`[outside-voice-unavailable]\`. /autoplan still completes with the host-native +subagent only.`; + } + + return `## Phase 0.5: Outside-voice preflight + +Before invoking any outside voice, preflight the CLI: verify auth (multi-signal) and +warn on known-bad CLI versions. On non-Codex hosts, the outside voice is Codex CLI +and the subagent is the host-native subagent. + +\`\`\`bash +_TEL=$(${ctx.paths.binDir}/gstack-config get telemetry 2>/dev/null || echo off) +_CODEX_CFG=$(${ctx.paths.binDir}/gstack-config get codex_reviews 2>/dev/null || echo enabled) +source ${ctx.paths.binDir}/gstack-codex-probe + +# Master switch first: codex_reviews=disabled turns off ALL Codex work globally, +# including autoplan's own dual-voice orchestration. Honor it before probing. +if [ "$_CODEX_CFG" = "disabled" ]; then + echo "[codex disabled by config — subagent-only voices] Re-enable: gstack-config set codex_reviews enabled" + _OUTSIDE_VOICE_AVAILABLE=false +elif ! command -v codex >/dev/null 2>&1; then + _gstack_codex_log_event "codex_cli_missing" + echo "[outside-voice-unavailable: Codex CLI not found] — proceeding with subagent only" + _OUTSIDE_VOICE_AVAILABLE=false +elif ! _gstack_codex_auth_probe >/dev/null; then + _gstack_codex_log_event "codex_auth_failed" + echo "[outside-voice-unavailable: Codex auth missing] — proceeding with subagent only. Run \`codex login\` or set \$CODEX_API_KEY to enable dual-voice review." + _OUTSIDE_VOICE_AVAILABLE=false +else + _gstack_codex_version_check + _OUTSIDE_VOICE_AVAILABLE=true +fi +\`\`\` + +If \`_OUTSIDE_VOICE_AVAILABLE=false\`, all outside-voice phases below degrade to +\`[outside-voice-unavailable]\`. /autoplan still completes with the host-native +subagent only — saves token spend on prompts we can't use.`; +} + +export function generateAutoplanOutsideVoiceBlock(ctx: TemplateContext, args?: string[]): string { + const phase = args?.[0] as Phase | undefined; + if (!phase || !(phase in PHASE_PROMPTS)) { + throw new Error('{{AUTOPLAN_OUTSIDE_VOICE_BLOCK}} requires one of: ceo, design, eng, dx'); + } + return ctx.host === 'codex' ? claudeOutsideVoiceBlock(phase) : codexOutsideVoiceBlock(ctx, phase); +} diff --git a/scripts/resolvers/browse.ts b/scripts/resolvers/browse.ts index a0ae37a70e..1ffc366da9 100644 --- a/scripts/resolvers/browse.ts +++ b/scripts/resolvers/browse.ts @@ -99,14 +99,24 @@ export function generateSnapshotFlags(_ctx: TemplateContext): string { return lines.join('\n'); } +/** Resolve dist binary path: env-var hosts use $GSTACK_* (never $HOME+$GSTACK_*). */ +export function resolveDistBinary(dir: string, binary: string): string { + if (dir.startsWith('$')) { + // e.g. $GSTACK_BROWSE already points at .../browse/dist + return `${dir}/${binary}`; + } + return `$HOME${dir.replace(/^~/, '')}/${binary}`; +} + export function generateBrowseSetup(ctx: TemplateContext): string { + const globalBrowse = resolveDistBinary(ctx.paths.browseDir, 'browse'); return `## SETUP (run this check BEFORE any browse command) \`\`\`bash _ROOT=$(git rev-parse --show-toplevel 2>/dev/null) B="" [ -n "$_ROOT" ] && [ -x "$_ROOT/${ctx.paths.localSkillRoot}/browse/dist/browse" ] && B="$_ROOT/${ctx.paths.localSkillRoot}/browse/dist/browse" -[ -z "$B" ] && B="$HOME${ctx.paths.browseDir.replace(/^~/, '')}/browse" +[ -z "$B" ] && B="${globalBrowse}" if [ -x "$B" ]; then echo "READY: $B" else diff --git a/scripts/resolvers/design.ts b/scripts/resolvers/design.ts index 9f31b36197..36441674ba 100644 --- a/scripts/resolvers/design.ts +++ b/scripts/resolvers/design.ts @@ -1,5 +1,6 @@ import type { TemplateContext } from './types'; import { AI_SLOP_BLACKLIST, OPENAI_HARD_REJECTIONS, OPENAI_LITMUS_CHECKS } from './constants'; +import { resolveDistBinary } from './browse'; export function generateDesignReviewLite(ctx: TemplateContext): string { const litmusList = OPENAI_LITMUS_CHECKS.map((item, i) => `${i + 1}. ${item}`).join(' '); @@ -786,13 +787,15 @@ Source: [OpenAI "Designing Delightful Frontends with GPT-5.4"](https://developer } export function generateDesignSetup(ctx: TemplateContext): string { + const globalDesign = resolveDistBinary(ctx.paths.designDir, 'design'); + const globalBrowse = resolveDistBinary(ctx.paths.browseDir, 'browse'); return `## DESIGN SETUP (run this check BEFORE any design mockup command) \`\`\`bash _ROOT=$(git rev-parse --show-toplevel 2>/dev/null) D="" [ -n "$_ROOT" ] && [ -x "$_ROOT/${ctx.paths.localSkillRoot}/design/dist/design" ] && D="$_ROOT/${ctx.paths.localSkillRoot}/design/dist/design" -[ -z "$D" ] && D="$HOME${ctx.paths.designDir.replace(/^~/, '')}/design" +[ -z "$D" ] && D="${globalDesign}" if [ -x "$D" ]; then echo "DESIGN_READY: $D" else @@ -800,7 +803,7 @@ else fi B="" [ -n "$_ROOT" ] && [ -x "$_ROOT/${ctx.paths.localSkillRoot}/browse/dist/browse" ] && B="$_ROOT/${ctx.paths.localSkillRoot}/browse/dist/browse" -[ -z "$B" ] && B="$HOME${ctx.paths.browseDir.replace(/^~/, '')}/browse" +[ -z "$B" ] && B="${globalBrowse}" if [ -x "$B" ]; then echo "BROWSE_READY: $B" else @@ -831,13 +834,14 @@ data, not project files. They persist across branches, conversations, and worksp } export function generateDesignMockup(ctx: TemplateContext): string { + const globalDesign = resolveDistBinary(ctx.paths.designDir, 'design'); return `## Visual Design Exploration \`\`\`bash _ROOT=$(git rev-parse --show-toplevel 2>/dev/null) D="" [ -n "$_ROOT" ] && [ -x "$_ROOT/${ctx.paths.localSkillRoot}/design/dist/design" ] && D="$_ROOT/${ctx.paths.localSkillRoot}/design/dist/design" -[ -z "$D" ] && D="$HOME${ctx.paths.designDir.replace(/^~/, '')}/design" +[ -z "$D" ] && D="${globalDesign}" [ -x "$D" ] && echo "DESIGN_READY" || echo "DESIGN_NOT_AVAILABLE" \`\`\` @@ -1154,4 +1158,3 @@ Flat design can strip away useful visual information that signals interactivity. Prioritize ruthlessly: things needed in a hurry go close at hand, everything else a few taps away with an obvious path to get there.`; } - diff --git a/scripts/resolvers/index.ts b/scripts/resolvers/index.ts index aa598b867b..08adfe3f1b 100644 --- a/scripts/resolvers/index.ts +++ b/scripts/resolvers/index.ts @@ -36,6 +36,8 @@ import { generateMakePdfSetup } from './make-pdf'; import { generateTasksSectionEmit, generateTasksSectionAggregate } from './tasks-section'; import { SECTION, SECTION_INDEX } from './sections'; import { generateRedactTaxonomyTable, generateRedactInvocationBlock } from './redact-doc'; +import { generateAutoplanOutsideVoiceBlock, generateAutoplanOutsideVoicePreflight } from './autoplan'; +import { generateSpecSpawn, generateSpecExecuteFlag } from './spec-spawn'; export const RESOLVERS: Record = { SLUG_EVAL: generateSlugEval, @@ -100,6 +102,10 @@ export const RESOLVERS: Record = { MAKE_PDF_SETUP: generateMakePdfSetup, TASKS_SECTION_EMIT: generateTasksSectionEmit, TASKS_SECTION_AGGREGATE: generateTasksSectionAggregate, + SPEC_SPAWN: generateSpecSpawn, + SPEC_EXECUTE_FLAG: generateSpecExecuteFlag, SECTION, SECTION_INDEX, + AUTOPLAN_OUTSIDE_VOICE_PREFLIGHT: generateAutoplanOutsideVoicePreflight, + AUTOPLAN_OUTSIDE_VOICE_BLOCK: generateAutoplanOutsideVoiceBlock, }; diff --git a/scripts/resolvers/make-pdf.ts b/scripts/resolvers/make-pdf.ts index c73d0bf136..be92d1509a 100644 --- a/scripts/resolvers/make-pdf.ts +++ b/scripts/resolvers/make-pdf.ts @@ -1,4 +1,5 @@ import type { TemplateContext } from './types'; +import { resolveDistBinary } from './browse'; /** * {{MAKE_PDF_SETUP}} — emits the shell preamble that resolves $P to the @@ -8,10 +9,11 @@ import type { TemplateContext } from './types'; * * Resolution order (matches src/browseClient.ts::resolveBrowseBin): * 1. Local skill root: $_ROOT/{localSkillRoot}/make-pdf/dist/pdf - * 2. Global: ~/{globalRoot}/make-pdf/dist/pdf + * 2. Global: $GSTACK_MAKE_PDF/pdf or ~/{globalRoot}/make-pdf/dist/pdf * 3. Env override (MAKE_PDF_BIN) — for contributor dev builds */ export function generateMakePdfSetup(ctx: TemplateContext): string { + const globalPdf = resolveDistBinary(ctx.paths.makePdfDir, 'pdf'); return `## MAKE-PDF SETUP (run this check BEFORE any make-pdf command) \`\`\`bash @@ -19,7 +21,7 @@ _ROOT=$(git rev-parse --show-toplevel 2>/dev/null) P="" [ -n "$MAKE_PDF_BIN" ] && [ -x "$MAKE_PDF_BIN" ] && P="$MAKE_PDF_BIN" [ -z "$P" ] && [ -n "$_ROOT" ] && [ -x "$_ROOT/${ctx.paths.localSkillRoot}/make-pdf/dist/pdf" ] && P="$_ROOT/${ctx.paths.localSkillRoot}/make-pdf/dist/pdf" -[ -z "$P" ] && P="$HOME${ctx.paths.makePdfDir.replace(/^~/, '')}/pdf" +[ -z "$P" ] && P="${globalPdf}" if [ -x "$P" ]; then echo "MAKE_PDF_READY: $P" alias _p_="$P" # shellcheck alias helper (not exported) diff --git a/scripts/resolvers/preamble/generate-preamble-bash.ts b/scripts/resolvers/preamble/generate-preamble-bash.ts index f0bc57b4bb..507fa6f10e 100644 --- a/scripts/resolvers/preamble/generate-preamble-bash.ts +++ b/scripts/resolvers/preamble/generate-preamble-bash.ts @@ -10,6 +10,7 @@ GSTACK_ROOT="$HOME/${hostConfig.globalRoot}" GSTACK_BIN="$GSTACK_ROOT/bin" GSTACK_BROWSE="$GSTACK_ROOT/browse/dist" GSTACK_DESIGN="$GSTACK_ROOT/design/dist" +GSTACK_MAKE_PDF="$GSTACK_ROOT/make-pdf/dist" ` : ''; diff --git a/scripts/resolvers/sections.ts b/scripts/resolvers/sections.ts index c6425e19b9..d2a379d44d 100644 --- a/scripts/resolvers/sections.ts +++ b/scripts/resolvers/sections.ts @@ -1,25 +1,28 @@ /** - * Section resolvers (v2 plan T9, Claude-first carve). + * Section resolvers (v2 plan T9 carve). * * A carved skill keeps its prose-heavy steps in `/sections/.md`, read * on demand. The SAME template ships to every host, so these resolvers make the * carve host-aware: * - * - On CLAUDE: {{SECTION:id}} emits a STOP-Read pointer to the generated section - * file (the skeleton), and the section .md is generated + installed separately. - * - On every OTHER host: {{SECTION:id}} INLINES the section template's content, - * so external hosts keep the full monolith ship skill (no section files, no - * host-portable-path problem). Inlined content keeps its own {{RESOLVER}} - * tokens, which the generator's multi-pass resolve expands. + * - On CLAUDE: SECTION:id emits a STOP-Read pointer to the generated section + * file under the nested monorepo install ({skillRoot}/{skill}/sections/). + * - On GROK-BUILD: same pointer mode, but paths use the flat Grok package layout + * (~/.grok/skills/gstack-{skill}/sections/). Section files are generated into + * each package's sections/ dir and ride along with package install. + * - On every OTHER host: SECTION placeholders INLINE the section template content, + * so those hosts keep the full monolith skill (no section files, no + * host-portable-path problem). Inlined content keeps its own resolver tokens, + * which the generator's multi-pass resolve expands. * - * {{SECTION_INDEX:skill}} renders the situation→section table from the PASSIVE - * manifest on Claude (empty on other hosts — they have no sections). The manifest + * SECTION_INDEX renders the situation-to-section table from the PASSIVE + * manifest on pointer hosts (empty when sections are inlined). The manifest * is the single source of id/file/title/trigger text (CM2; v2_PLAN.md:663). */ import * as fs from 'fs'; import * as path from 'path'; -import type { ResolverFn, TemplateContext } from './types'; +import type { Host, ResolverFn, TemplateContext } from './types'; const ROOT = path.resolve(import.meta.dir, '..', '..'); @@ -34,6 +37,21 @@ interface SectionManifest { sections: SectionEntry[]; } +/** Hosts that load carved sections on demand (not monolith-inline). */ +export function hostUsesSectionPointers(host: Host | string): boolean { + return host === 'claude' || host === 'grok-build'; +} + +/** + * External package dir name for flat hosts (gstack-ship, gstack-upgrade, …). + * Mirrors gen-skill-docs externalSkillName for skill dirs. + */ +export function externalSkillPackageName(skillName: string): string { + if (skillName === '.' || skillName === '' || skillName === 'gstack') return 'gstack'; + if (skillName.startsWith('gstack-')) return skillName; + return `gstack-${skillName}`; +} + function loadManifest(skill: string): SectionManifest { const p = path.join(ROOT, skill, 'sections', 'manifest.json'); const raw = fs.readFileSync(p, 'utf-8'); @@ -49,35 +67,56 @@ function findSection(skill: string, id: string): SectionEntry { } /** - * {{SECTION:id}} — pointer on Claude, inline on other hosts. - * Claude path uses the stable gstack-root install (`{skillRoot}/{skill}/sections/`), - * which always exists, instead of a naked relative path (Codex outside-voice #7). + * Absolute-style path the agent should Read for a section file. + * Claude: nested monorepo install under skillRoot. + * Grok: flat package next to the thin runtime root. + */ +export function sectionPointerPath( + host: Host | string, + skillName: string, + sectionFile: string, + skillRoot: string, +): string { + if (host === 'grok-build') { + const pkg = externalSkillPackageName(skillName); + return `~/.grok/skills/${pkg}/sections/${sectionFile}`; + } + // Claude (and any future nested-install pointer host) + return `${skillRoot}/${skillName}/sections/${sectionFile}`; +} + +function stopReadDirective(sectionPath: string, trigger: string): string { + return [ + `> **STOP.** Before ${trigger}, Read \`${sectionPath}\` and execute it`, + `> in full. Do not work from memory — that section is the source of truth for this step.`, + ].join('\n'); +} + +/** + * SECTION:id — pointer on Claude/Grok, inline on other hosts. */ export const SECTION: ResolverFn = (ctx: TemplateContext, args?: string[]): string => { const id = args?.[0]; if (!id) throw new Error('{{SECTION:id}} requires a section id'); const entry = findSection(ctx.skillName, id); - if (ctx.host === 'claude') { - const sectionPath = `${ctx.paths.skillRoot}/${ctx.skillName}/sections/${entry.file}`; - return [ - `> **STOP.** Before ${entry.trigger}, Read \`${sectionPath}\` and execute it`, - `> in full. Do not work from memory — that section is the source of truth for this step.`, - ].join('\n'); + if (hostUsesSectionPointers(ctx.host)) { + const sectionPath = sectionPointerPath(ctx.host, ctx.skillName, entry.file, ctx.paths.skillRoot); + return stopReadDirective(sectionPath, entry.trigger); } - // Non-Claude hosts inline the section template content (monolith preserved). + // Non-pointer hosts inline the section template content (monolith preserved). // Inner {{RESOLVER}} tokens are expanded by the generator's multi-pass resolve. const tmplPath = path.join(ROOT, ctx.skillName, 'sections', `${entry.file}.tmpl`); return fs.readFileSync(tmplPath, 'utf-8').trimEnd(); }; /** - * {{SECTION_INDEX:skill}} — situation→section table from the passive manifest. - * Claude only; other hosts inline everything so an index would be noise. + * SECTION_INDEX — situation-to-section table from the passive manifest. + * Pointer hosts only; inline hosts have no separate section files. */ export const SECTION_INDEX: ResolverFn = (ctx: TemplateContext, args?: string[]): string => { - if (ctx.host !== 'claude') return ''; + if (!hostUsesSectionPointers(ctx.host)) return ''; const skill = args?.[0] ?? ctx.skillName; const manifest = loadManifest(skill); const lines: string[] = [ @@ -90,7 +129,11 @@ export const SECTION_INDEX: ResolverFn = (ctx: TemplateContext, args?: string[]) '|------|-------------------|', ]; for (const s of manifest.sections) { - lines.push(`| ${s.trigger} | \`sections/${s.file}\` |`); + const sectionPath = sectionPointerPath(ctx.host, skill, s.file, ctx.paths.skillRoot); + // Table shows the resolvable path (Grok: full ~/.grok/...; Claude: short sections/) + const display = + ctx.host === 'grok-build' ? `\`${sectionPath}\`` : `\`sections/${s.file}\``; + lines.push(`| ${s.trigger} | ${display} |`); } return lines.join('\n'); }; diff --git a/scripts/resolvers/spec-spawn.ts b/scripts/resolvers/spec-spawn.ts new file mode 100644 index 0000000000..5a05520335 --- /dev/null +++ b/scripts/resolvers/spec-spawn.ts @@ -0,0 +1,118 @@ +import type { TemplateContext } from './types'; + +/** + * Host-native agent spawn for /spec --execute (U4 / R3). + * + * Grok research matrix (local CLI grok 0.2.x): + * -p / --single short single-turn + * --prompt-file single-turn from file (preferred for archives) + * --cwd working directory + * --always-approve elevated auto-approve (opt-in / documented) + * Never: $(cat …) into argv (ARG_MAX); never invent --permission-mode acceptEdits. + * + * Claude host generation unchanged: stdin pipe into claude -p. + */ +export function generateSpecSpawn(ctx: TemplateContext): string { + if (ctx.host === 'grok-build') { + return `If A and worktree created: spawn **Grok** headless with the archived +spec as a prompt file (never \`$(cat …)\` into argv — ARG_MAX / quoting risk). + +**Auth gate (fail closed):** before spawn, verify Grok is available and configured: + +\`\`\`bash +command -v grok >/dev/null 2>&1 || { echo "STOP: grok CLI not on PATH. Install Grok Build, or re-run with --no-execute / --file-only."; exit 1; } +if [ ! -f "$HOME/.grok/auth.json" ] && [ -z "\${XAI_API_KEY:-}\${GROK_API_KEY:-}" ]; then + echo "STOP: Grok not authenticated (no ~/.grok/auth.json and no XAI_API_KEY/GROK_API_KEY). Log in via \`grok\`, or use --no-execute." + exit 1 +fi +# ARCHIVE_PATH must stay under SPAWN_PATH or the gstack projects allowlist (fail closed). +if [ ! -f "$ARCHIVE_PATH" ]; then + echo "STOP: ARCHIVE_PATH missing: $ARCHIVE_PATH"; exit 1 +fi +if command -v realpath >/dev/null 2>&1; then + ARCHIVE_REAL=$(realpath "$ARCHIVE_PATH") +else + ARCHIVE_REAL=$(cd "$(dirname "$ARCHIVE_PATH")" && pwd -P)/$(basename "$ARCHIVE_PATH") +fi +SPAWN_REAL=$(cd "$SPAWN_PATH" 2>/dev/null && pwd -P || echo "") +if [ -z "$SPAWN_REAL" ]; then + echo "STOP: SPAWN_PATH is not a real directory: $SPAWN_PATH"; exit 1 +fi +STATE_PROJECTS="\${GSTACK_STATE_ROOT:-\$HOME/.gstack}/projects" +case "$ARCHIVE_REAL" in + "$STATE_PROJECTS"/*|"$SPAWN_REAL"/*) ;; # allowlisted + *) + echo "STOP: ARCHIVE_PATH realpath not under SPAWN_PATH or allowlisted archive dir ($STATE_PROJECTS)."; exit 1 + ;; +esac +\`\`\` + +**Security:** default spawn does **not** pass \`--always-approve\` (elevated +auto-approve). Only spawn after the user confirmed the D16 gate. If the user +explicitly opts into unattended tool use, append \`--always-approve\` to the +command below — never enable it by default. Spec archives must not contain +secrets. Third-party note: archive body is sent to xAI for processing. + +\`\`\`bash +# Prefer --prompt-file (researched). Elevated --always-approve is opt-in only. +(cd "$SPAWN_PATH" && grok --prompt-file "$ARCHIVE_PATH" --cwd "$SPAWN_PATH" 2>&1) & +SPAWN_PID=$! +echo "Spawned: PID $SPAWN_PID in $SPAWN_PATH (branch $SPAWN_BRANCH)" +echo "Follow with: cd $SPAWN_PATH && grok --continue" +\`\`\` + +**Optional Claude execute:** if the user asked for \`--execute-claude\` instead of +default Grok execute, and \`claude\` is on PATH, you may spawn — only after the +same ARCHIVE_PATH allowlist gate as the Grok path (reuse the block above; never +pipe an unallowlisted archive): + +\`\`\`bash +# Re-run allowlist (same fail-closed rules as Grok path) before cat|claude. +if [ ! -f "$ARCHIVE_PATH" ]; then + echo "STOP: ARCHIVE_PATH missing: $ARCHIVE_PATH"; exit 1 +fi +if command -v realpath >/dev/null 2>&1; then + ARCHIVE_REAL=$(realpath "$ARCHIVE_PATH") +else + ARCHIVE_REAL=$(cd "$(dirname "$ARCHIVE_PATH")" && pwd -P)/$(basename "$ARCHIVE_PATH") +fi +SPAWN_REAL=$(cd "$SPAWN_PATH" 2>/dev/null && pwd -P || echo "") +if [ -z "$SPAWN_REAL" ]; then + echo "STOP: SPAWN_PATH is not a real directory: $SPAWN_PATH"; exit 1 +fi +STATE_PROJECTS="\${GSTACK_STATE_ROOT:-\$HOME/.gstack}/projects" +case "$ARCHIVE_REAL" in + "$STATE_PROJECTS"/*|"$SPAWN_REAL"/*) ;; # allowlisted + *) + echo "STOP: ARCHIVE_PATH realpath not under SPAWN_PATH or allowlisted archive dir ($STATE_PROJECTS)."; exit 1 + ;; +esac +cat "$ARCHIVE_PATH" | (cd "$SPAWN_PATH" && claude -p 2>&1) & +\`\`\` + +Do **not** silently fall through to Claude when Grok is missing — STOP instead. + +If no safe multi-line file ingest is available on an older Grok CLI (no +\`--prompt-file\`), demote to \`--no-execute\` / file-only and tell the user: +"This Grok CLI lacks --prompt-file; filed the issue only. Upgrade Grok Build or +use --execute-claude if Claude is installed."`; + } + + // Claude + all other hosts: classic claude -p stdin pipe + return `If A and worktree created: spawn \`claude -p\` with the spec piped via stdin: + +\`\`\`bash +cat "$ARCHIVE_PATH" | (cd "$SPAWN_PATH" && claude -p 2>&1) & +SPAWN_PID=$! +echo "Spawned: PID $SPAWN_PID in $SPAWN_PATH (branch $SPAWN_BRANCH)" +echo "Follow with: cd $SPAWN_PATH && claude --resume" +\`\`\``; +} + +/** Flag-table row for --execute (host-aware description). */ +export function generateSpecExecuteFlag(ctx: TemplateContext): string { + if (ctx.host === 'grok-build') { + return '| `--execute` | conditional default (see Phase 5) | Spawn `grok --prompt-file` headless in a fresh worktree after filing the issue. |'; + } + return '| `--execute` | conditional default (see Phase 5) | Spawn `claude -p` in a fresh worktree after filing the issue. |'; +} diff --git a/scripts/resolvers/utility.ts b/scripts/resolvers/utility.ts index 3d2e368a29..a6bfbbef7e 100644 --- a/scripts/resolvers/utility.ts +++ b/scripts/resolvers/utility.ts @@ -369,7 +369,7 @@ Minimum 0 per category. export function generateCoAuthorTrailer(ctx: TemplateContext): string { const { getHostConfig } = require('../../hosts/index'); const hostConfig = getHostConfig(ctx.host); - return hostConfig.coAuthorTrailer || 'Co-Authored-By: Claude Opus 4.7 '; + return hostConfig.coAuthorTrailer || 'Co-Authored-By: Claude Opus 4.8 '; } export function generateChangelogWorkflow(_ctx: TemplateContext): string { diff --git a/scripts/skill-check.ts b/scripts/skill-check.ts index 9182737ee1..11b3ab4fd5 100644 --- a/scripts/skill-check.ts +++ b/scripts/skill-check.ts @@ -10,6 +10,7 @@ import { validateSkill } from '../test/helpers/skill-parser'; import { discoverTemplates, discoverSkillFiles } from './discover-skills'; +import { claude as PRIMARY_HOST } from '../hosts/index'; import * as fs from 'fs'; import * as path from 'path'; import { execSync } from 'child_process'; @@ -64,15 +65,25 @@ for (const file of SKILL_FILES) { console.log('\n Templates:'); const TEMPLATES = discoverTemplates(ROOT); +// Repo-root SKILL.md outputs reflect what the primary host (claude) generates. +// If a skill is in claude's skipSkills (e.g. the `/claude` outside-voice skill +// is intentionally not bundled into the Claude host), its generated file is +// expected to be absent — flagging it as missing here is a false positive. +const PRIMARY_SKIP_SKILLS = new Set(PRIMARY_HOST.generation?.skipSkills ?? []); for (const { tmpl, output } of TEMPLATES) { const tmplPath = path.join(ROOT, tmpl); const outPath = path.join(ROOT, output); + const skillDir = output.includes('/') ? output.split('/')[0] : ''; if (!fs.existsSync(tmplPath)) { console.log(` \u26a0\ufe0f ${output.padEnd(30)} — no template`); continue; } if (!fs.existsSync(outPath)) { + if (skillDir && PRIMARY_SKIP_SKILLS.has(skillDir)) { + console.log(` - ${output.padEnd(30)} — skipped per ${PRIMARY_HOST.name} host config`); + continue; + } hasErrors = true; console.log(` \u274c ${output.padEnd(30)} — generated file missing! Run: bun run gen:skill-docs`); continue; diff --git a/setup b/setup index 275236cd36..c2d1672c59 100755 --- a/setup +++ b/setup @@ -18,12 +18,20 @@ INSTALL_GSTACK_DIR="$(cd "$(dirname "$0")" && pwd)" SOURCE_GSTACK_DIR="$(cd "$(dirname "$0")" && pwd -P)" INSTALL_SKILLS_DIR="$(dirname "$INSTALL_GSTACK_DIR")" BROWSE_BIN="$SOURCE_GSTACK_DIR/browse/dist/browse" -CODEX_SKILLS="$HOME/.codex/skills" +CODEX_SKILLS="$HOME/.agents/skills" CODEX_GSTACK="$CODEX_SKILLS/gstack" FACTORY_SKILLS="$HOME/.factory/skills" FACTORY_GSTACK="$FACTORY_SKILLS/gstack" OPENCODE_SKILLS="$HOME/.config/opencode/skills" OPENCODE_GSTACK="$OPENCODE_SKILLS/gstack" +CURSOR_SKILLS="$HOME/.cursor/skills" +CURSOR_GSTACK="$CURSOR_SKILLS/gstack" +SLATE_SKILLS="$HOME/.slate/skills" +SLATE_GSTACK="$SLATE_SKILLS/gstack" +QODER_SKILLS="$HOME/.qoder/skills" +QODER_GSTACK="$QODER_SKILLS/gstack" +GROK_SKILLS="$HOME/.grok/skills" +GROK_GSTACK="$GROK_SKILLS/gstack" IS_WINDOWS=0 case "$(uname -s)" in @@ -85,7 +93,7 @@ NO_TEAM_MODE=0 PLAN_TUNE_HOOKS_MODE="" # "" = resolve from env/config/prompt; "yes"/"no" = explicit while [ $# -gt 0 ]; do case "$1" in - --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 ;; + --host) [ -z "$2" ] && echo "Missing value for --host (expected claude, codex, kiro, factory, opencode, cursor, slate, qoder, grok-build, grok, openclaw, hermes, gbrain, or auto)" >&2 && exit 1; HOST="$2"; shift 2 ;; --host=*) HOST="${1#--host=}"; shift ;; --local) LOCAL_INSTALL=1; shift ;; --prefix) SKILL_PREFIX=1; SKILL_PREFIX_FLAG=1; shift ;; @@ -100,8 +108,11 @@ while [ $# -gt 0 ]; do esac done +# Alias: --host grok → grok-build +if [ "$HOST" = "grok" ]; then HOST="grok-build"; fi + case "$HOST" in - claude|codex|kiro|factory|opencode|auto) ;; + claude|codex|kiro|factory|opencode|cursor|slate|qoder|grok-build|auto) ;; openclaw) echo "" echo "OpenClaw integration uses a different model — OpenClaw spawns Claude Code" @@ -116,12 +127,57 @@ case "$HOST" in exit 0 ;; hermes) echo "" - echo "Hermes integration uses the same model as OpenClaw — Hermes spawns" + echo "Hermes integration uses the same model as OpenClaw: Hermes spawns" echo "Claude Code sessions, and gstack provides methodology artifacts." echo "" + echo "./setup --host hermes does not install files into Hermes today." + echo "It only prints integration instructions." + echo "" echo "To integrate gstack with Hermes:" echo " 1. Tell your Hermes agent: 'install gstack for hermes'" - echo " 2. Or generate artifacts: bun run gen:skill-docs --host hermes" + echo " 2. Or generate reviewable artifacts locally:" + echo " bun run gen:skill-docs --host hermes" + echo " This writes .hermes/skills/ in this checkout." + echo "" + exit 0 ;; + agy|antigravity) + echo "" + echo "./setup --host agy does not install files into Antigravity today." + echo "It only prints integration instructions." + echo "" + echo "To integrate gstack with Antigravity (agy):" + echo " 1. Generate Antigravity-format skills locally:" + echo " bun run gen:skill-docs --host agy" + echo " This writes .agy/skills/ in this checkout." + echo " 2. Symlink each generated .agy/skills/gstack* folder into" + echo " ~/.gemini/config/skills/ (create the directory first)." + echo "" + exit 0 ;; + vibe) + echo "" + echo "./setup --host vibe does not install files into Mistral Vibe today." + echo "It only prints integration instructions." + echo "" + echo "To integrate gstack with Mistral Vibe:" + echo " 1. Generate Vibe-format skills locally:" + echo " bun run gen:skill-docs --host vibe" + echo " This writes .vibe/skills/ in this checkout." + echo " 2. Symlink each generated .vibe/skills/gstack* folder into" + echo " ~/.vibe/skills/ (create the directory first)." + echo "" + exit 0 ;; + pi) + echo "" + echo "./setup --host pi does not install files into Pi today." + echo "It only prints integration instructions." + echo "" + echo "To integrate gstack with Pi:" + echo " 1. Generate Pi-format skills locally:" + echo " bun run gen:skill-docs --host pi" + echo " This writes .pi/skills/ in this checkout." + echo " 2. Symlink each generated .pi/skills/gstack* folder into" + echo " ~/.pi/agent/skills/ — see the Pi section in README.md for" + echo " the exact command." echo "" exit 0 ;; gbrain) @@ -136,7 +192,7 @@ case "$HOST" in echo "GBrain setup and brain skills ship from the GBrain repo." echo "" exit 0 ;; - *) echo "Unknown --host value: $HOST (expected claude, codex, kiro, factory, opencode, openclaw, hermes, gbrain, or auto)" >&2; exit 1 ;; + *) echo "Unknown --host value: $HOST (expected claude, codex, kiro, factory, opencode, cursor, slate, qoder, grok-build, grok, openclaw, hermes, gbrain, or auto)" >&2; exit 1 ;; esac # ─── Resolve skill prefix preference ───────────────────────── @@ -200,14 +256,22 @@ INSTALL_CODEX=0 INSTALL_KIRO=0 INSTALL_FACTORY=0 INSTALL_OPENCODE=0 +INSTALL_CURSOR=0 +INSTALL_SLATE=0 +INSTALL_QODER=0 +INSTALL_GROK=0 if [ "$HOST" = "auto" ]; then command -v claude >/dev/null 2>&1 && INSTALL_CLAUDE=1 command -v codex >/dev/null 2>&1 && INSTALL_CODEX=1 command -v kiro-cli >/dev/null 2>&1 && INSTALL_KIRO=1 command -v droid >/dev/null 2>&1 && INSTALL_FACTORY=1 command -v opencode >/dev/null 2>&1 && INSTALL_OPENCODE=1 + command -v cursor >/dev/null 2>&1 && INSTALL_CURSOR=1 + command -v slate >/dev/null 2>&1 && INSTALL_SLATE=1 + command -v qodercli >/dev/null 2>&1 && INSTALL_QODER=1 + command -v grok >/dev/null 2>&1 && INSTALL_GROK=1 # If none found, default to claude - if [ "$INSTALL_CLAUDE" -eq 0 ] && [ "$INSTALL_CODEX" -eq 0 ] && [ "$INSTALL_KIRO" -eq 0 ] && [ "$INSTALL_FACTORY" -eq 0 ] && [ "$INSTALL_OPENCODE" -eq 0 ]; then + if [ "$INSTALL_CLAUDE" -eq 0 ] && [ "$INSTALL_CODEX" -eq 0 ] && [ "$INSTALL_KIRO" -eq 0 ] && [ "$INSTALL_FACTORY" -eq 0 ] && [ "$INSTALL_OPENCODE" -eq 0 ] && [ "$INSTALL_CURSOR" -eq 0 ] && [ "$INSTALL_SLATE" -eq 0 ] && [ "$INSTALL_QODER" -eq 0 ] && [ "$INSTALL_GROK" -eq 0 ]; then INSTALL_CLAUDE=1 fi elif [ "$HOST" = "claude" ]; then @@ -220,6 +284,14 @@ elif [ "$HOST" = "factory" ]; then INSTALL_FACTORY=1 elif [ "$HOST" = "opencode" ]; then INSTALL_OPENCODE=1 +elif [ "$HOST" = "cursor" ]; then + INSTALL_CURSOR=1 +elif [ "$HOST" = "slate" ]; then + INSTALL_SLATE=1 +elif [ "$HOST" = "qoder" ]; then + INSTALL_QODER=1 +elif [ "$HOST" = "grok-build" ]; then + INSTALL_GROK=1 fi migrate_direct_codex_install() { @@ -438,16 +510,13 @@ if [ ! -x "$BROWSE_BIN" ]; then exit 1 fi -# 1b. Generate .agents/ Codex skill docs — always regenerate to prevent stale descriptions. +# 1b. Generate .agents/ Codex skill docs — only when installing for Codex. # .agents/ is no longer committed — generated at setup time from .tmpl templates. # bun run build already does this, but we need it when NEEDS_BUILD=0 (binary is fresh). -# Always regenerate: generation is fast (<2s) and mtime-based staleness checks are fragile -# (miss stale files when timestamps match after clone/checkout/upgrade). -AGENTS_DIR="$SOURCE_GSTACK_DIR/.agents/skills" -NEEDS_AGENTS_GEN=1 - -if [ "$NEEDS_AGENTS_GEN" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then - log "Generating .agents/ skill docs..." +# Host-scoped: --host grok-build / claude / factory / etc. must not pay Codex regen cost +# or imply a Codex install is in progress. +if [ "$INSTALL_CODEX" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then + log "Generating .agents/ skill docs for Codex..." ( cd "$SOURCE_GSTACK_DIR" bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install @@ -475,6 +544,36 @@ if [ "$INSTALL_OPENCODE" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then ) fi +# 1e. Generate .cursor/ Cursor skill docs +if [ "$INSTALL_CURSOR" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then + log "Generating .cursor/ skill docs..." + ( + cd "$SOURCE_GSTACK_DIR" + bun install --frozen-lockfile 2>/dev/null || bun install + bun run gen:skill-docs --host cursor + ) +fi + +# 1f. Generate .slate/ Slate skill docs +if [ "$INSTALL_SLATE" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then + log "Generating .slate/ skill docs..." + ( + cd "$SOURCE_GSTACK_DIR" + bun install --frozen-lockfile 2>/dev/null || bun install + bun run gen:skill-docs --host slate + ) +fi + +# 1g. Generate .grok/ Grok Build skill docs +if [ "$INSTALL_GROK" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then + log "Generating .grok/ skill docs for Grok Build..." + ( + cd "$SOURCE_GSTACK_DIR" + bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install + bun_cmd run gen:skill-docs --host grok-build + ) +fi + # 2. Ensure Playwright's Chromium is available if ! ensure_playwright_browser; then echo "Installing Playwright Chromium..." @@ -741,7 +840,7 @@ link_codex_skill_dirs() { # ─── Helper: create .agents/skills/gstack/ sidecar symlinks ────────── # Codex/Gemini/Cursor read skills from .agents/skills/. We link runtime -# assets (bin/, browse/dist/, review/, qa/, etc.) so skill templates can +# assets (bin/, browse/dist/, design/dist/, review/, qa/, etc.) so skill templates can # resolve paths like $SKILL_ROOT/review/design-checklist.md. create_agents_sidecar() { local repo_root="$1" @@ -749,7 +848,8 @@ create_agents_sidecar() { mkdir -p "$agents_gstack" # Sidecar directories that skills reference at runtime - for asset in bin browse review qa; do + # (lib: bin/*.ts helpers import ../lib/*; required on copy-mode installs) + for asset in bin lib browse design review qa; do local src="$SOURCE_GSTACK_DIR/$asset" local dst="$agents_gstack/$asset" if [ -d "$src" ] || [ -f "$src" ]; then @@ -771,8 +871,8 @@ create_agents_sidecar() { done } -# ─── Helper: create a minimal ~/.codex/skills/gstack runtime root ─────────── -# Codex scans ~/.codex/skills recursively. Exposing the whole repo here causes +# ─── Helper: create a minimal ~/.agents/skills/gstack runtime root ─────────── +# Codex scans ~/.agents/skills recursively. Exposing the whole repo here causes # duplicate skills because source SKILL.md files and generated Codex skills are # both discoverable. Keep this directory limited to runtime assets + root skill. create_codex_runtime_root() { @@ -788,7 +888,7 @@ create_codex_runtime_root() { rm -rf "$codex_gstack" fi - mkdir -p "$codex_gstack" "$codex_gstack/browse" "$codex_gstack/gstack-upgrade" "$codex_gstack/review" + mkdir -p "$codex_gstack" "$codex_gstack/browse" "$codex_gstack/design" "$codex_gstack/gstack-upgrade" "$codex_gstack/review" if [ -f "$agents_dir/gstack/SKILL.md" ]; then _link_or_copy "$agents_dir/gstack/SKILL.md" "$codex_gstack/SKILL.md" @@ -796,12 +896,19 @@ create_codex_runtime_root() { if [ -d "$gstack_dir/bin" ]; then _link_or_copy "$gstack_dir/bin" "$codex_gstack/bin" fi + # lib/ — bin/*.ts helpers import ../lib/*; required on copy-mode installs + if [ -d "$gstack_dir/lib" ]; then + _link_or_copy "$gstack_dir/lib" "$codex_gstack/lib" + fi if [ -d "$gstack_dir/browse/dist" ]; then _link_or_copy "$gstack_dir/browse/dist" "$codex_gstack/browse/dist" fi if [ -d "$gstack_dir/browse/bin" ]; then _link_or_copy "$gstack_dir/browse/bin" "$codex_gstack/browse/bin" fi + if [ -d "$gstack_dir/design/dist" ]; then + _link_or_copy "$gstack_dir/design/dist" "$codex_gstack/design/dist" + fi if [ -f "$agents_dir/gstack-upgrade/SKILL.md" ]; then _link_or_copy "$agents_dir/gstack-upgrade/SKILL.md" "$codex_gstack/gstack-upgrade/SKILL.md" fi @@ -815,6 +922,11 @@ create_codex_runtime_root() { if [ -f "$gstack_dir/ETHOS.md" ]; then _link_or_copy "$gstack_dir/ETHOS.md" "$codex_gstack/ETHOS.md" fi + # supabase/config.sh — required by gstack-telemetry-sync to resolve GSTACK_SUPABASE_URL + if [ -f "$gstack_dir/supabase/config.sh" ]; then + mkdir -p "$codex_gstack/supabase" + _link_or_copy "$gstack_dir/supabase/config.sh" "$codex_gstack/supabase/config.sh" + fi } create_factory_runtime_root() { @@ -836,6 +948,10 @@ create_factory_runtime_root() { if [ -d "$gstack_dir/bin" ]; then _link_or_copy "$gstack_dir/bin" "$factory_gstack/bin" fi + # lib/ — bin/*.ts helpers import ../lib/*; required on copy-mode installs + if [ -d "$gstack_dir/lib" ]; then + _link_or_copy "$gstack_dir/lib" "$factory_gstack/lib" + fi if [ -d "$gstack_dir/browse/dist" ]; then _link_or_copy "$gstack_dir/browse/dist" "$factory_gstack/browse/dist" fi @@ -853,6 +969,11 @@ create_factory_runtime_root() { if [ -f "$gstack_dir/ETHOS.md" ]; then _link_or_copy "$gstack_dir/ETHOS.md" "$factory_gstack/ETHOS.md" fi + # supabase/config.sh — required by gstack-telemetry-sync to resolve GSTACK_SUPABASE_URL + if [ -f "$gstack_dir/supabase/config.sh" ]; then + mkdir -p "$factory_gstack/supabase" + _link_or_copy "$gstack_dir/supabase/config.sh" "$factory_gstack/supabase/config.sh" + fi } create_opencode_runtime_root() { @@ -874,6 +995,9 @@ create_opencode_runtime_root() { if [ -d "$gstack_dir/bin" ]; then _link_or_copy "$gstack_dir/bin" "$opencode_gstack/bin" fi + if [ -d "$gstack_dir/lib" ]; then + _link_or_copy "$gstack_dir/lib" "$opencode_gstack/lib" + fi if [ -d "$gstack_dir/browse/dist" ]; then _link_or_copy "$gstack_dir/browse/dist" "$opencode_gstack/browse/dist" fi @@ -906,6 +1030,110 @@ create_opencode_runtime_root() { if [ -f "$gstack_dir/ETHOS.md" ]; then _link_or_copy "$gstack_dir/ETHOS.md" "$opencode_gstack/ETHOS.md" fi + # supabase/config.sh — required by gstack-telemetry-sync to resolve GSTACK_SUPABASE_URL + if [ -f "$gstack_dir/supabase/config.sh" ]; then + mkdir -p "$opencode_gstack/supabase" + _link_or_copy "$gstack_dir/supabase/config.sh" "$opencode_gstack/supabase/config.sh" + fi +} + +create_cursor_runtime_root() { + local gstack_dir="$1" + local cursor_gstack="$2" + local cursor_dir="$gstack_dir/.cursor/skills" + + if [ -L "$cursor_gstack" ]; then + rm -f "$cursor_gstack" + elif [ -d "$cursor_gstack" ] && [ "$cursor_gstack" != "$gstack_dir" ]; then + rm -rf "$cursor_gstack" + fi + + mkdir -p "$cursor_gstack" "$cursor_gstack/browse" "$cursor_gstack/gstack-upgrade" "$cursor_gstack/review" + + if [ -f "$cursor_dir/gstack/SKILL.md" ]; then + _link_or_copy "$cursor_dir/gstack/SKILL.md" "$cursor_gstack/SKILL.md" + fi + if [ -d "$gstack_dir/bin" ]; then + _link_or_copy "$gstack_dir/bin" "$cursor_gstack/bin" + fi + # lib/ — bin/*.ts helpers import ../lib/*; required on copy-mode installs + if [ -d "$gstack_dir/lib" ]; then + _link_or_copy "$gstack_dir/lib" "$cursor_gstack/lib" + fi + if [ -d "$gstack_dir/browse/dist" ]; then + _link_or_copy "$gstack_dir/browse/dist" "$cursor_gstack/browse/dist" + fi + if [ -d "$gstack_dir/browse/bin" ]; then + _link_or_copy "$gstack_dir/browse/bin" "$cursor_gstack/browse/bin" + fi + if [ -f "$cursor_dir/gstack-upgrade/SKILL.md" ]; then + _link_or_copy "$cursor_dir/gstack-upgrade/SKILL.md" "$cursor_gstack/gstack-upgrade/SKILL.md" + fi + for f in checklist.md TODOS-format.md; do + if [ -f "$gstack_dir/review/$f" ]; then + _link_or_copy "$gstack_dir/review/$f" "$cursor_gstack/review/$f" + fi + done + if [ -f "$gstack_dir/ETHOS.md" ]; then + _link_or_copy "$gstack_dir/ETHOS.md" "$cursor_gstack/ETHOS.md" + fi +} + +create_qoder_runtime_root() { + local gstack_dir="$1" + local qoder_gstack="$2" + local qoder_dir="$gstack_dir/.qoder/skills" + + if [ -L "$qoder_gstack" ]; then + rm -f "$qoder_gstack" + elif [ -d "$qoder_gstack" ] && [ "$qoder_gstack" != "$gstack_dir" ]; then + rm -rf "$qoder_gstack" + fi + + mkdir -p "$qoder_gstack" "$qoder_gstack/browse" "$qoder_gstack/design" "$qoder_gstack/gstack-upgrade" "$qoder_gstack/review" "$qoder_gstack/qa" "$qoder_gstack/plan-devex-review" + + if [ -f "$qoder_dir/gstack/SKILL.md" ]; then + _link_or_copy "$qoder_dir/gstack/SKILL.md" "$qoder_gstack/SKILL.md" + fi + if [ -d "$gstack_dir/bin" ]; then + _link_or_copy "$gstack_dir/bin" "$qoder_gstack/bin" + fi + # lib/ — bin/*.ts helpers import ../lib/*; required on copy-mode installs + if [ -d "$gstack_dir/lib" ]; then + _link_or_copy "$gstack_dir/lib" "$qoder_gstack/lib" + fi + if [ -d "$gstack_dir/browse/dist" ]; then + _link_or_copy "$gstack_dir/browse/dist" "$qoder_gstack/browse/dist" + fi + if [ -d "$gstack_dir/browse/bin" ]; then + _link_or_copy "$gstack_dir/browse/bin" "$qoder_gstack/browse/bin" + fi + if [ -d "$gstack_dir/design/dist" ]; then + _link_or_copy "$gstack_dir/design/dist" "$qoder_gstack/design/dist" + fi + if [ -f "$qoder_dir/gstack-upgrade/SKILL.md" ]; then + _link_or_copy "$qoder_dir/gstack-upgrade/SKILL.md" "$qoder_gstack/gstack-upgrade/SKILL.md" + fi + for f in checklist.md design-checklist.md greptile-triage.md TODOS-format.md; do + if [ -f "$gstack_dir/review/$f" ]; then + _link_or_copy "$gstack_dir/review/$f" "$qoder_gstack/review/$f" + fi + done + if [ -d "$gstack_dir/review/specialists" ]; then + _link_or_copy "$gstack_dir/review/specialists" "$qoder_gstack/review/specialists" + fi + if [ -d "$gstack_dir/qa/templates" ]; then + _link_or_copy "$gstack_dir/qa/templates" "$qoder_gstack/qa/templates" + fi + if [ -d "$gstack_dir/qa/references" ]; then + _link_or_copy "$gstack_dir/qa/references" "$qoder_gstack/qa/references" + fi + if [ -f "$gstack_dir/plan-devex-review/dx-hall-of-fame.md" ]; then + _link_or_copy "$gstack_dir/plan-devex-review/dx-hall-of-fame.md" "$qoder_gstack/plan-devex-review/dx-hall-of-fame.md" + fi + if [ -f "$gstack_dir/ETHOS.md" ]; then + _link_or_copy "$gstack_dir/ETHOS.md" "$qoder_gstack/ETHOS.md" + fi } link_factory_skill_dirs() { @@ -972,6 +1200,398 @@ link_opencode_skill_dirs() { fi } +link_cursor_skill_dirs() { + local gstack_dir="$1" + local skills_dir="$2" + local cursor_dir="$gstack_dir/.cursor/skills" + local linked=() + + if [ ! -d "$cursor_dir" ]; then + echo " Generating .cursor/ skill docs..." + ( cd "$gstack_dir" && bun run gen:skill-docs --host cursor ) + fi + + if [ ! -d "$cursor_dir" ]; then + echo " warning: .cursor/skills/ generation failed — run 'bun run gen:skill-docs --host cursor' manually" >&2 + return 1 + fi + + for skill_dir in "$cursor_dir"/gstack*/; do + if [ -f "$skill_dir/SKILL.md" ]; then + skill_name="$(basename "$skill_dir")" + [ "$skill_name" = "gstack" ] && continue + target="$skills_dir/$skill_name" + if [ -L "$target" ] || [ ! -e "$target" ]; then + _link_or_copy "$skill_dir" "$target" + linked+=("$skill_name") + fi + fi + done + if [ ${#linked[@]} -gt 0 ]; then + echo " linked skills: ${linked[*]}" + fi +} + +# Slate mirrors the Cursor install exactly (hosts/slate.ts runtimeRoot is +# identical to hosts/cursor.ts); only the paths differ. +create_slate_runtime_root() { + local gstack_dir="$1" + local slate_gstack="$2" + local slate_dir="$gstack_dir/.slate/skills" + + if [ -L "$slate_gstack" ]; then + rm -f "$slate_gstack" + elif [ -d "$slate_gstack" ] && [ "$slate_gstack" != "$gstack_dir" ]; then + rm -rf "$slate_gstack" + fi + + mkdir -p "$slate_gstack" "$slate_gstack/browse" "$slate_gstack/gstack-upgrade" "$slate_gstack/review" + + if [ -f "$slate_dir/gstack/SKILL.md" ]; then + _link_or_copy "$slate_dir/gstack/SKILL.md" "$slate_gstack/SKILL.md" + fi + if [ -d "$gstack_dir/bin" ]; then + _link_or_copy "$gstack_dir/bin" "$slate_gstack/bin" + fi + # lib/ — bin/*.ts helpers import ../lib/*; required on copy-mode installs + if [ -d "$gstack_dir/lib" ]; then + _link_or_copy "$gstack_dir/lib" "$slate_gstack/lib" + fi + if [ -d "$gstack_dir/browse/dist" ]; then + _link_or_copy "$gstack_dir/browse/dist" "$slate_gstack/browse/dist" + fi + if [ -d "$gstack_dir/browse/bin" ]; then + _link_or_copy "$gstack_dir/browse/bin" "$slate_gstack/browse/bin" + fi + if [ -f "$slate_dir/gstack-upgrade/SKILL.md" ]; then + _link_or_copy "$slate_dir/gstack-upgrade/SKILL.md" "$slate_gstack/gstack-upgrade/SKILL.md" + fi + for f in checklist.md TODOS-format.md; do + if [ -f "$gstack_dir/review/$f" ]; then + _link_or_copy "$gstack_dir/review/$f" "$slate_gstack/review/$f" + fi + done + if [ -f "$gstack_dir/ETHOS.md" ]; then + _link_or_copy "$gstack_dir/ETHOS.md" "$slate_gstack/ETHOS.md" + fi +} + +link_slate_skill_dirs() { + local gstack_dir="$1" + local skills_dir="$2" + local slate_dir="$gstack_dir/.slate/skills" + local linked=() + + if [ ! -d "$slate_dir" ]; then + echo " Generating .slate/ skill docs..." + ( cd "$gstack_dir" && bun run gen:skill-docs --host slate ) + fi + + if [ ! -d "$slate_dir" ]; then + echo " warning: .slate/skills/ generation failed — run 'bun run gen:skill-docs --host slate' manually" >&2 + return 1 + fi + + for skill_dir in "$slate_dir"/gstack*/; do + if [ -f "$skill_dir/SKILL.md" ]; then + skill_name="$(basename "$skill_dir")" + [ "$skill_name" = "gstack" ] && continue + target="$skills_dir/$skill_name" + if [ -L "$target" ] || [ ! -e "$target" ]; then + _link_or_copy "$skill_dir" "$target" + linked+=("$skill_name") + fi + fi + done + if [ ${#linked[@]} -gt 0 ]; then + echo " linked skills: ${linked[*]}" + fi +} + +link_qoder_skill_dirs() { + local gstack_dir="$1" + local skills_dir="$2" + local qoder_dir="$gstack_dir/.qoder/skills" + local linked=() + + if [ ! -d "$qoder_dir" ]; then + echo " Generating .qoder/ skill docs..." + ( cd "$gstack_dir" && bun run gen:skill-docs --host qoder ) + fi + + if [ ! -d "$qoder_dir" ]; then + echo " warning: .qoder/skills/ generation failed — run 'bun run gen:skill-docs --host qoder' manually" >&2 + return 1 + fi + + for skill_dir in "$qoder_dir"/gstack*/; do + if [ -f "$skill_dir/SKILL.md" ]; then + skill_name="$(basename "$skill_dir")" + [ "$skill_name" = "gstack" ] && continue + target="$skills_dir/$skill_name" + if [ -L "$target" ] || [ ! -e "$target" ]; then + _link_or_copy "$skill_dir" "$target" + linked+=("$skill_name") + fi + fi + done + if [ ${#linked[@]} -gt 0 ]; then + echo " linked skills: ${linked[*]}" + fi +} + +# ─── Grok Build (xAI) runtime root + skill links ─────────────────────────── +# Grok discovers skills from ~/.grok/skills//SKILL.md. +# Generated packages live in /.grok/skills/gstack-* (path-rewritten). +# Runtime assets (bin, browse, design, specialists, …) live at ~/.grok/skills/gstack. +# +# Security: every required link target must realpath under monorepo install root. +# Dual-write with hosts/grok-build.ts runtimeRoot — keep lists in sync (R1 / U1). +_grok_link_under_monorepo() { + local monorepo_root="$1" + local src="$2" + local dst="$3" + local required="${4:-0}" # 1 = fail closed if missing when expected + + if [ ! -e "$src" ]; then + if [ "$required" = "1" ]; then + echo "error: required Grok runtime asset missing: $src" >&2 + echo " monorepo root: $monorepo_root" >&2 + echo " Re-run from a full gstack checkout after builds complete." >&2 + return 1 + fi + return 0 + fi + + local src_real monorepo_real + monorepo_real=$(cd "$monorepo_root" 2>/dev/null && pwd -P) + # Fail closed: empty monorepo_real makes "$monorepo_real"/* expand to /* and + # match every absolute path — never allow that. + if [ -z "$monorepo_real" ] || [ ! -d "$monorepo_real" ]; then + echo "error: refusing Grok runtime link — monorepo root unresolved: $monorepo_root" >&2 + return 1 + fi + # Prefer full realpath when available so a final-component symlink cannot escape. + if command -v realpath >/dev/null 2>&1; then + src_real=$(realpath "$src" 2>/dev/null) || src_real="" + else + src_real=$(cd "$(dirname "$src")" 2>/dev/null && pwd -P)/$(basename "$src") + fi + if [ -z "$src_real" ]; then + echo "error: refusing Grok runtime link — cannot realpath src: $src" >&2 + return 1 + fi + case "$src_real" in + "$monorepo_real"|"$monorepo_real"/*) ;; + *) + echo "error: refusing Grok runtime link outside monorepo:" >&2 + echo " src=$src_real" >&2 + echo " monorepo=$monorepo_real" >&2 + return 1 + ;; + esac + + _link_or_copy "$src" "$dst" +} + +create_grok_runtime_root() { + local gstack_dir="$1" + local grok_gstack="$2" + local generated_root="$gstack_dir/.grok/skills/gstack" + local staging="" + local req + + # Preflight required monorepo assets BEFORE wiping the live install. + # Without this, a mid-install failure under set -e leaves an empty/half tree + # after rm -rf (review finding #6). + for req in bin browse/dist browse/src scripts review/specialists; do + if [ ! -e "$gstack_dir/$req" ]; then + echo "error: preflight — required monorepo asset missing: $gstack_dir/$req" >&2 + echo " Run a full monorepo build (./setup or bun run build) before installing Grok runtime." >&2 + return 1 + fi + done + # Core review files audited by gstack-grok-compat-audit — fail closed when monorepo has review/ + if [ -d "$gstack_dir/review" ]; then + for req in checklist.md TODOS-format.md; do + if [ ! -f "$gstack_dir/review/$req" ]; then + echo "error: preflight — core review file missing: $gstack_dir/review/$req" >&2 + return 1 + fi + done + fi + # design/dist required when monorepo ships the design package + if [ -d "$gstack_dir/design" ] && [ ! -d "$gstack_dir/design/dist" ]; then + echo "error: preflight — monorepo has design/ but design/dist is missing — run design build" >&2 + return 1 + fi + + # Stage into a sibling .next tree, then atomic rename into place so a partial + # link failure never leaves the live ~/.grok/skills/gstack empty. + staging="${grok_gstack}.next.$$" + rm -rf "$staging" + mkdir -p "$staging" \ + "$staging/browse" \ + "$staging/design" \ + "$staging/make-pdf" \ + "$staging/gstack-upgrade" \ + "$staging/review" || return 1 + + # Cleanup staging on any failure from here until atomic swap + # shellcheck disable=SC2064 + trap 'rm -rf "$staging"' RETURN + + if [ -f "$generated_root/SKILL.md" ]; then + _link_or_copy "$generated_root/SKILL.md" "$staging/SKILL.md" + elif [ -f "$gstack_dir/SKILL.md" ]; then + _link_or_copy "$gstack_dir/SKILL.md" "$staging/SKILL.md" + fi + + # Required core assets (fail closed) — dual-write with hosts/grok-build.ts runtimeRoot + _grok_link_under_monorepo "$gstack_dir" "$gstack_dir/bin" "$staging/bin" 1 || return 1 + # lib/ — bin/*.ts helpers import ../lib/*; required on copy-mode installs + if [ -d "$gstack_dir/lib" ]; then + _grok_link_under_monorepo "$gstack_dir" "$gstack_dir/lib" "$staging/lib" 0 || return 1 + fi + _grok_link_under_monorepo "$gstack_dir" "$gstack_dir/browse/dist" "$staging/browse/dist" 1 || return 1 + if [ -d "$gstack_dir/browse/bin" ]; then + _grok_link_under_monorepo "$gstack_dir" "$gstack_dir/browse/bin" "$staging/browse/bin" 0 || return 1 + fi + _grok_link_under_monorepo "$gstack_dir" "$gstack_dir/browse/src" "$staging/browse/src" 1 || return 1 + + if [ -d "$gstack_dir/design/dist" ]; then + _grok_link_under_monorepo "$gstack_dir" "$gstack_dir/design/dist" "$staging/design/dist" 1 || return 1 + fi + + # make-pdf/dist — link when present (membership skill) + if [ -d "$gstack_dir/make-pdf/dist" ]; then + _grok_link_under_monorepo "$gstack_dir" "$gstack_dir/make-pdf/dist" "$staging/make-pdf/dist" 0 || return 1 + fi + + # extension/ — required when monorepo has it + if [ -d "$gstack_dir/extension" ]; then + _grok_link_under_monorepo "$gstack_dir" "$gstack_dir/extension" "$staging/extension" 1 || return 1 + fi + + _grok_link_under_monorepo "$gstack_dir" "$gstack_dir/scripts" "$staging/scripts" 1 || return 1 + + # Core review files — required=1 so install policy matches gstack-grok-compat-audit (#7) + for f in checklist.md TODOS-format.md; do + _grok_link_under_monorepo "$gstack_dir" "$gstack_dir/review/$f" "$staging/review/$f" 1 || return 1 + done + # Optional review assets (soft when monorepo lacks them) + for f in design-checklist.md greptile-triage.md; do + if [ -f "$gstack_dir/review/$f" ]; then + _grok_link_under_monorepo "$gstack_dir" "$gstack_dir/review/$f" "$staging/review/$f" 0 || return 1 + fi + done + _grok_link_under_monorepo "$gstack_dir" "$gstack_dir/review/specialists" "$staging/review/specialists" 1 || return 1 + + if [ -f "$gstack_dir/gstack-upgrade/SKILL.md" ] || [ -f "$gstack_dir/.grok/skills/gstack-upgrade/SKILL.md" ]; then + if [ -f "$gstack_dir/.grok/skills/gstack-upgrade/SKILL.md" ]; then + _link_or_copy "$gstack_dir/.grok/skills/gstack-upgrade/SKILL.md" "$staging/gstack-upgrade/SKILL.md" + else + _link_or_copy "$gstack_dir/gstack-upgrade/SKILL.md" "$staging/gstack-upgrade/SKILL.md" + fi + fi + if [ -f "$gstack_dir/ETHOS.md" ]; then + _grok_link_under_monorepo "$gstack_dir" "$gstack_dir/ETHOS.md" "$staging/ETHOS.md" 0 || return 1 + fi + + # Atomic swap: move the live install aside (not delete) so a failed + # promotion can roll back instead of leaving no runtime at all. + trap - RETURN + local _old_tree="" + if [ -L "$grok_gstack" ]; then + rm -f "$grok_gstack" + elif [ -d "$grok_gstack" ] && [ "$grok_gstack" != "$gstack_dir" ]; then + _old_tree="${grok_gstack}.old.$$" + rm -rf "$_old_tree" + mv "$grok_gstack" "$_old_tree" 2>/dev/null || _old_tree="" + fi + if ! mv "$staging" "$grok_gstack" 2>/dev/null; then + echo "error: failed to promote staged Grok runtime root to $grok_gstack" >&2 + if [ -n "$_old_tree" ] && [ -d "$_old_tree" ]; then + mv "$_old_tree" "$grok_gstack" 2>/dev/null && echo " previous Grok runtime restored" >&2 + fi + rm -rf "$staging" + return 1 + fi + if [ -n "$_old_tree" ]; then rm -rf "$_old_tree"; fi +} + +link_grok_skill_dirs() { + local gstack_dir="$1" + local skills_dir="$2" + local grok_dir="$gstack_dir/.grok/skills" + local linked=() + + if [ ! -d "$grok_dir" ]; then + echo " Generating .grok/ skill docs..." + ( cd "$gstack_dir" && bun run gen:skill-docs --host grok-build ) + fi + + if [ ! -d "$grok_dir" ]; then + echo " warning: .grok/skills/ generation failed — run 'bun run gen:skill-docs --host grok-build' manually" >&2 + return 1 + fi + + mkdir -p "$skills_dir" + + for skill_dir in "$grok_dir"/gstack*/; do + if [ -f "$skill_dir/SKILL.md" ]; then + skill_name="$(basename "$skill_dir")" + # Runtime root is installed separately as ~/.grok/skills/gstack + [ "$skill_name" = "gstack" ] && continue + target="$skills_dir/$skill_name" + # Replace stale monorepo flat symlinks (browse → ~/gstack/browse) with generated packages + if [ -L "$target" ] || [ ! -e "$target" ]; then + _link_or_copy "$skill_dir" "$target" + linked+=("$skill_name") + elif [ -d "$target" ] && [ ! -f "$target/SKILL.md" ]; then + rm -rf "$target" + _link_or_copy "$skill_dir" "$target" + linked+=("$skill_name") + else + # Force refresh: generated content is authoritative for this host + rm -rf "$target" + _link_or_copy "$skill_dir" "$target" + linked+=("$skill_name") + fi + + # Also expose unprefixed alias (browse) so slash names match frontmatter + # without depending on Grok's name: field alone. + # Deny bare `codex` alias (OpenAI plugin owns /codex); optional gstack-codex only. + bare_name="${skill_name#gstack-}" + if [ -n "$bare_name" ] && [ "$bare_name" != "$skill_name" ] && [ "$bare_name" != "codex" ]; then + bare_target="$skills_dir/$bare_name" + # Only alias if missing or already a symlink (don't clobber real non-gstack skills) + if [ -L "$bare_target" ] || [ ! -e "$bare_target" ]; then + _link_or_copy "$skill_dir" "$bare_target" + fi + fi + fi + done + + # Backwards-compat alias: /connect-chrome → generated open-gstack-browser (U2 / KTD7) + # Force-refresh stale monorepo targets that pointed at source tree packages. + local _ogb_src="" + if [ -d "$skills_dir/gstack-open-gstack-browser" ]; then + _ogb_src="$skills_dir/gstack-open-gstack-browser" + elif [ -d "$grok_dir/gstack-open-gstack-browser" ]; then + _ogb_src="$grok_dir/gstack-open-gstack-browser" + fi + if [ -n "$_ogb_src" ]; then + rm -rf "$skills_dir/connect-chrome" + _link_or_copy "$_ogb_src" "$skills_dir/connect-chrome" + linked+=("connect-chrome→open-gstack-browser") + fi + + if [ ${#linked[@]} -gt 0 ]; then + echo " linked skills: ${linked[*]}" + fi +} + # 4. Install for Claude (default) SKILLS_BASENAME="$(basename "$INSTALL_SKILLS_DIR")" SKILLS_PARENT_BASENAME="$(basename "$(dirname "$INSTALL_SKILLS_DIR")")" @@ -1115,12 +1735,21 @@ if [ "$INSTALL_KIRO" -eq 1 ]; then [ -L "$KIRO_GSTACK" ] && rm -f "$KIRO_GSTACK" mkdir -p "$KIRO_GSTACK" "$KIRO_GSTACK/browse" "$KIRO_GSTACK/gstack-upgrade" "$KIRO_GSTACK/review" _link_or_copy "$SOURCE_GSTACK_DIR/bin" "$KIRO_GSTACK/bin" + # lib/ — bin/*.ts helpers import ../lib/*; required on copy-mode installs + if [ -d "$SOURCE_GSTACK_DIR/lib" ]; then + _link_or_copy "$SOURCE_GSTACK_DIR/lib" "$KIRO_GSTACK/lib" + fi _link_or_copy "$SOURCE_GSTACK_DIR/browse/dist" "$KIRO_GSTACK/browse/dist" _link_or_copy "$SOURCE_GSTACK_DIR/browse/bin" "$KIRO_GSTACK/browse/bin" # ETHOS.md — referenced by "Search Before Building" in all skill preambles if [ -f "$SOURCE_GSTACK_DIR/ETHOS.md" ]; then _link_or_copy "$SOURCE_GSTACK_DIR/ETHOS.md" "$KIRO_GSTACK/ETHOS.md" fi + # supabase/config.sh — required by gstack-telemetry-sync to resolve GSTACK_SUPABASE_URL + if [ -f "$SOURCE_GSTACK_DIR/supabase/config.sh" ]; then + mkdir -p "$KIRO_GSTACK/supabase" + _link_or_copy "$SOURCE_GSTACK_DIR/supabase/config.sh" "$KIRO_GSTACK/supabase/config.sh" + fi # gstack-upgrade skill if [ -f "$AGENTS_DIR/gstack-upgrade/SKILL.md" ]; then _link_or_copy "$AGENTS_DIR/gstack-upgrade/SKILL.md" "$KIRO_GSTACK/gstack-upgrade/SKILL.md" @@ -1146,9 +1775,10 @@ if [ "$INSTALL_KIRO" -eq 1 ]; then skill_name="$(basename "$skill_dir")" target_dir="$KIRO_SKILLS/$skill_name" mkdir -p "$target_dir" - # Generated Codex skills use $HOME/.codex (not ~/), plus $GSTACK_ROOT variables. + # Generated Codex skills use $HOME/.agents (not ~/), plus $GSTACK_ROOT variables. # Rewrite the default GSTACK_ROOT value and any remaining literal paths. - sed -e 's|\$HOME/.codex/skills/gstack|$HOME/.kiro/skills/gstack|g' \ + sed -e 's|\$HOME/.agents/skills/gstack|$HOME/.kiro/skills/gstack|g' \ + -e 's|\$HOME/.codex/skills/gstack|$HOME/.kiro/skills/gstack|g' \ -e "s|~/.codex/skills/gstack|~/.kiro/skills/gstack|g" \ -e "s|~/.claude/skills/gstack|~/.kiro/skills/gstack|g" \ "$skill_dir/SKILL.md" > "$target_dir/SKILL.md" @@ -1160,7 +1790,8 @@ if [ "$INSTALL_KIRO" -eq 1 ]; then mkdir -p "$target_dir/sections" for section_file in "$skill_dir/sections"/*; do [ -f "$section_file" ] || continue - sed -e 's|\$HOME/.codex/skills/gstack|$HOME/.kiro/skills/gstack|g' \ + sed -e 's|\$HOME/.agents/skills/gstack|$HOME/.kiro/skills/gstack|g' \ + -e 's|\$HOME/.codex/skills/gstack|$HOME/.kiro/skills/gstack|g' \ -e "s|~/.codex/skills/gstack|~/.kiro/skills/gstack|g" \ -e "s|~/.claude/skills/gstack|~/.kiro/skills/gstack|g" \ "$section_file" > "$target_dir/sections/$(basename "$section_file")" @@ -1193,6 +1824,65 @@ if [ "$INSTALL_OPENCODE" -eq 1 ]; then echo " opencode skills: $OPENCODE_SKILLS" fi +# 6d. Install for Cursor +if [ "$INSTALL_CURSOR" -eq 1 ]; then + # Generate before building the runtime root — on fresh installs the gen step + # above is skipped when NEEDS_BUILD=1, and the runtime root needs the + # generated root SKILL.md to exist. + if [ ! -d "$SOURCE_GSTACK_DIR/.cursor/skills" ]; then + ( cd "$SOURCE_GSTACK_DIR" && bun run gen:skill-docs --host cursor ) + fi + mkdir -p "$CURSOR_SKILLS" + create_cursor_runtime_root "$SOURCE_GSTACK_DIR" "$CURSOR_GSTACK" + link_cursor_skill_dirs "$SOURCE_GSTACK_DIR" "$CURSOR_SKILLS" + echo "gstack ready (cursor)." + echo " browse: $BROWSE_BIN" + echo " cursor skills: $CURSOR_SKILLS" +fi + +# 6e. Install for Slate +if [ "$INSTALL_SLATE" -eq 1 ]; then + # Generate before building the runtime root — on fresh installs the gen step + # above is skipped when NEEDS_BUILD=1, and the runtime root needs the + # generated root SKILL.md to exist. + if [ ! -d "$SOURCE_GSTACK_DIR/.slate/skills" ]; then + ( cd "$SOURCE_GSTACK_DIR" && bun run gen:skill-docs --host slate ) + fi + mkdir -p "$SLATE_SKILLS" + create_slate_runtime_root "$SOURCE_GSTACK_DIR" "$SLATE_GSTACK" + link_slate_skill_dirs "$SOURCE_GSTACK_DIR" "$SLATE_SKILLS" + echo "gstack ready (slate)." + echo " browse: $BROWSE_BIN" + echo " slate skills: $SLATE_SKILLS" +fi + +# 6f. Install for Qoder +if [ "$INSTALL_QODER" -eq 1 ]; then + # Generate before building the runtime root — on fresh installs the gen step + # above is skipped when NEEDS_BUILD=1, and the runtime root needs the + # generated root SKILL.md to exist. + if [ ! -d "$SOURCE_GSTACK_DIR/.qoder/skills" ]; then + ( cd "$SOURCE_GSTACK_DIR" && bun run gen:skill-docs --host qoder ) + fi + mkdir -p "$QODER_SKILLS" + create_qoder_runtime_root "$SOURCE_GSTACK_DIR" "$QODER_GSTACK" + link_qoder_skill_dirs "$SOURCE_GSTACK_DIR" "$QODER_SKILLS" + echo "gstack ready (qoder)." + echo " browse: $BROWSE_BIN" + echo " qoder skills: $QODER_SKILLS" +fi + +# 6g. Install for Grok Build (xAI) +if [ "$INSTALL_GROK" -eq 1 ]; then + mkdir -p "$GROK_SKILLS" + create_grok_runtime_root "$SOURCE_GSTACK_DIR" "$GROK_GSTACK" + link_grok_skill_dirs "$SOURCE_GSTACK_DIR" "$GROK_SKILLS" + echo "gstack ready (grok-build)." + echo " browse: $BROWSE_BIN" + echo " grok skills: $GROK_SKILLS" + echo " runtime root: $GROK_GSTACK" +fi + # 7. Create .agents/ sidecar symlinks for the real Codex skill target. # The root Codex skill ends up pointing at $SOURCE_GSTACK_DIR/.agents/skills/gstack, # so the runtime assets must live there for both global and repo-local installs. @@ -1337,6 +2027,10 @@ fi # something at runtime instead of being agent-convention. Explicit consent UX # per D4 + Codex: never mutate settings.json silently. # +# Host-scoped: these hooks only affect Claude Code. Skip the prompt entirely for +# non-Claude host installs (e.g. --host grok-build) unless the user explicitly +# passed --plan-tune-hooks. Grok /plan-tune still works via skill-text path. +# # Idempotent via _gstack_source tag = 'plan-tune-cathedral'. If both hooks # already registered under that tag, the install is a no-op (no prompt). PLAN_TUNE_LOG_HOOK="$SOURCE_GSTACK_DIR/hosts/claude/hooks/question-log-hook" @@ -1344,7 +2038,14 @@ PLAN_TUNE_PREF_HOOK="$SOURCE_GSTACK_DIR/hosts/claude/hooks/question-preference-h AUQ_ERROR_FALLBACK_HOOK="$SOURCE_GSTACK_DIR/hosts/claude/hooks/auq-error-fallback-hook" PLAN_TUNE_INSTALL_MARKER="$HOME/.gstack/.plan-tune-hooks-prompted" +# Explicit --plan-tune-hooks still allowed from any host install (user intent). +_PT_EXPLICIT_YES=0 +case "$(printf '%s' "${PLAN_TUNE_HOOKS_MODE:-}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')" in + y|yes|true|install|on|1) _PT_EXPLICIT_YES=1 ;; +esac + if [ "$NO_TEAM_MODE" -ne 1 ] \ + && { [ "$INSTALL_CLAUDE" -eq 1 ] || [ "$_PT_EXPLICIT_YES" -eq 1 ]; } \ && [ -x "$SETTINGS_HOOK" ] \ && [ -x "$PLAN_TUNE_LOG_HOOK" ] \ && [ -x "$PLAN_TUNE_PREF_HOOK" ]; then diff --git a/setup-gbrain/SKILL.md b/setup-gbrain/SKILL.md index 89c2ffbc37..ca4777e7c8 100644 --- a/setup-gbrain/SKILL.md +++ b/setup-gbrain/SKILL.md @@ -788,8 +788,22 @@ Claude Code) can call it as both a CLI and an MCP tool. **Scope honesty:** This skill's MCP registration step (5a) uses `claude mcp add` and targets Claude Code specifically. Other local hosts -(Cursor, Codex CLI, etc.) will still get the gbrain CLI on PATH — they can -register `gbrain serve` in their own MCP config manually after setup. +(Cursor, Codex CLI, **Grok Build**, etc.) will still get the gbrain CLI on +PATH — they can register `gbrain serve` in their own MCP config manually +after setup. + +**Grok Build success path (no Claude required):** setup is **COMPATIBLE** when +(1) `gbrain` is on PATH, (2) the engine is healthy (`gbrain doctor`), and +(3) AGENTS.md (or project docs) carry gbrain guidance via `/sync-gbrain`. +Claude MCP registration is optional. Never mark setup FAILED only because +`claude` is missing. Optional Grok MCP into `~/.grok/config.toml` is a stretch +(absolute path, structured TOML RMW, gbrain stanza only, atomic write + +backup; non-interactive refuses non-equivalent overwrite unless `--force` or +a real TTY confirm). + +**Bridge policy:** install prose alone does not green the primary path when +the CLI is missing — detect/install guidance may be READY; overall bridge is +DEPENDENT until `gbrain` is present. **Audience:** local-Mac users. openclaw/hermes agents typically run in cloud docker containers with their own gbrain; "sharing" a brain between them and @@ -1258,9 +1272,14 @@ doctor output and STOP. --- -## Step 5a: Register gbrain as Claude Code MCP (D18) +## Step 5a: Register gbrain as Claude Code MCP (D18) — optional -Only if `which claude` resolves. Ask: "Give Claude Code a typed tool surface +Only if `which claude` resolves. If `claude` is **not** on PATH (typical on +Grok-only machines): emit "MCP registration skipped — Claude Code not present. +Grok/other hosts succeed via CLI + AGENTS.md; register `gbrain serve` in host +MCP config only if desired." Continue to step 6 — **do not fail setup**. + +If `claude` is present, ask: "Give Claude Code a typed tool surface for gbrain? (recommended yes)" The registration form depends on the path picked in Step 2: diff --git a/setup-gbrain/SKILL.md.tmpl b/setup-gbrain/SKILL.md.tmpl index f48581543b..f785154660 100644 --- a/setup-gbrain/SKILL.md.tmpl +++ b/setup-gbrain/SKILL.md.tmpl @@ -34,8 +34,22 @@ Claude Code) can call it as both a CLI and an MCP tool. **Scope honesty:** This skill's MCP registration step (5a) uses `claude mcp add` and targets Claude Code specifically. Other local hosts -(Cursor, Codex CLI, etc.) will still get the gbrain CLI on PATH — they can -register `gbrain serve` in their own MCP config manually after setup. +(Cursor, Codex CLI, **Grok Build**, etc.) will still get the gbrain CLI on +PATH — they can register `gbrain serve` in their own MCP config manually +after setup. + +**Grok Build success path (no Claude required):** setup is **COMPATIBLE** when +(1) `gbrain` is on PATH, (2) the engine is healthy (`gbrain doctor`), and +(3) AGENTS.md (or project docs) carry gbrain guidance via `/sync-gbrain`. +Claude MCP registration is optional. Never mark setup FAILED only because +`claude` is missing. Optional Grok MCP into `~/.grok/config.toml` is a stretch +(absolute path, structured TOML RMW, gbrain stanza only, atomic write + +backup; non-interactive refuses non-equivalent overwrite unless `--force` or +a real TTY confirm). + +**Bridge policy:** install prose alone does not green the primary path when +the CLI is missing — detect/install guidance may be READY; overall bridge is +DEPENDENT until `gbrain` is present. **Audience:** local-Mac users. openclaw/hermes agents typically run in cloud docker containers with their own gbrain; "sharing" a brain between them and @@ -504,9 +518,14 @@ doctor output and STOP. --- -## Step 5a: Register gbrain as Claude Code MCP (D18) +## Step 5a: Register gbrain as Claude Code MCP (D18) — optional -Only if `which claude` resolves. Ask: "Give Claude Code a typed tool surface +Only if `which claude` resolves. If `claude` is **not** on PATH (typical on +Grok-only machines): emit "MCP registration skipped — Claude Code not present. +Grok/other hosts succeed via CLI + AGENTS.md; register `gbrain serve` in host +MCP config only if desired." Continue to step 6 — **do not fail setup**. + +If `claude` is present, ask: "Give Claude Code a typed tool surface for gbrain? (recommended yes)" The registration form depends on the path picked in Step 2: diff --git a/ship/SKILL.md b/ship/SKILL.md index eadffaa8f6..acfe8e74e8 100644 --- a/ship/SKILL.md +++ b/ship/SKILL.md @@ -1229,7 +1229,7 @@ user via AskUserQuestion rather than destroying non-WIP commits. git commit -m "$(cat <<'EOF' chore: bump version and changelog (vX.Y.Z.W) -Co-Authored-By: Claude Opus 4.7 +Co-Authored-By: Claude Opus 4.8 EOF )" ``` diff --git a/spec/SKILL.md.tmpl b/spec/SKILL.md.tmpl index 6c0c14e1b3..e7d5991f7c 100644 --- a/spec/SKILL.md.tmpl +++ b/spec/SKILL.md.tmpl @@ -60,7 +60,7 @@ separated tokens starting with `--`. Last flag wins on conflict. | `--no-dedupe` | — | Skip the dedupe check. | | `--no-gate` | OFF (gate is ON) | Skip the codex quality-score gate between Phase 4 and Phase 5. **Redaction (Phase 4.5a semantic + 4.5b regex) still runs — there is no flag that disables it.** | | `--audit` | OFF | Route Phase 5 to the Audit/Cleanup template (instead of Standard). | -| `--execute` | conditional default (see Phase 5) | Spawn `claude -p` in a fresh worktree after filing the issue. | +{{SPEC_EXECUTE_FLAG}} | `--no-execute` | — | File issue only; do NOT spawn agent (alias: `--file-only`). | | `--file-only` | — | Same as `--no-execute`. | | `--plan-file ` | inferred from harness | Load the spec into the specified plan file instead of inferring. | @@ -447,14 +447,7 @@ git worktree add "$SPAWN_PATH" -b "$SPAWN_BRANCH" "$PIN_SHA" 2>&1 in-progress changes will be visible to the agent. Cancel with Ctrl+C if not desired." Then fall back to current dir (still spawn). -If A and worktree created: spawn `claude -p` with the spec piped via stdin: - -```bash -cat "$ARCHIVE_PATH" | (cd "$SPAWN_PATH" && claude -p 2>&1) & -SPAWN_PID=$! -echo "Spawned: PID $SPAWN_PID in $SPAWN_PATH (branch $SPAWN_BRANCH)" -echo "Follow with: cd $SPAWN_PATH && claude --resume" -``` +{{SPEC_SPAWN}} Update archive frontmatter with `spec_worktree_path: $SPAWN_PATH` and `spec_executed: true` (atomic re-write). diff --git a/sync-gbrain/SKILL.md b/sync-gbrain/SKILL.md index bfaf291d22..8467f5e5a9 100644 --- a/sync-gbrain/SKILL.md +++ b/sync-gbrain/SKILL.md @@ -785,8 +785,12 @@ Skills that run plan reviews (`/plan-*-review`, `/codex review`) include the EXI You are running the canonical "keep this brain up to date" verb. /setup-gbrain installs gbrain once; /sync-gbrain runs every time the user wants the brain refreshed against this repo's current state, and refreshes the agent-side -guidance in CLAUDE.md so the coding agent knows when to prefer `gbrain` -search over Grep. +guidance in CLAUDE.md / AGENTS.md so the coding agent knows when to prefer +`gbrain` search over Grep. + +**Grok Build:** success does not require Claude MCP. Require `gbrain` on PATH + +engine healthy + AGENTS.md guidance refresh. If gbrain is absent, give install +steps (DEPENDENT bridge) — do not silent-fail or demand `which claude`. **Architecture (post-codex review):** This skill uses gbrain v0.20.0+'s **native code surfaces** (`gbrain sources add`, `gbrain sync --strategy code`, diff --git a/sync-gbrain/SKILL.md.tmpl b/sync-gbrain/SKILL.md.tmpl index 2ec065472e..d3d8d6e085 100644 --- a/sync-gbrain/SKILL.md.tmpl +++ b/sync-gbrain/SKILL.md.tmpl @@ -31,8 +31,12 @@ allowed-tools: You are running the canonical "keep this brain up to date" verb. /setup-gbrain installs gbrain once; /sync-gbrain runs every time the user wants the brain refreshed against this repo's current state, and refreshes the agent-side -guidance in CLAUDE.md so the coding agent knows when to prefer `gbrain` -search over Grep. +guidance in CLAUDE.md / AGENTS.md so the coding agent knows when to prefer +`gbrain` search over Grep. + +**Grok Build:** success does not require Claude MCP. Require `gbrain` on PATH + +engine healthy + AGENTS.md guidance refresh. If gbrain is absent, give install +steps (DEPENDENT bridge) — do not silent-fail or demand `which claude`. **Architecture (post-codex review):** This skill uses gbrain v0.20.0+'s **native code surfaces** (`gbrain sources add`, `gbrain sync --strategy code`, diff --git a/test/benchmark-cli.test.ts b/test/benchmark-cli.test.ts index 8edea3b245..6f5c7f03c3 100644 --- a/test/benchmark-cli.test.ts +++ b/test/benchmark-cli.test.ts @@ -133,7 +133,7 @@ describe('gstack-model-benchmark --dry-run', () => { const notReadyLines = out.split('\n').filter(l => l.includes('NOT READY')); expect(notReadyLines.length).toBeGreaterThanOrEqual(2); for (const line of notReadyLines) { - expect(line).toMatch(/(install|Install|login|export|Run|Log in)/); + expect(line).toMatch(/(install|login|export|Run|Log in)/i); } } finally { fs.rmSync(emptyHome, { recursive: true, force: true }); diff --git a/test/codex-design-paths.test.ts b/test/codex-design-paths.test.ts new file mode 100644 index 0000000000..4b76127468 --- /dev/null +++ b/test/codex-design-paths.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, test } from 'bun:test'; +import type { TemplateContext } from '../scripts/resolvers/types'; +import { HOST_PATHS } from '../scripts/resolvers/types'; +import { generateBrowseSetup } from '../scripts/resolvers/browse'; +import { generateDesignMockup, generateDesignSetup } from '../scripts/resolvers/design'; + +function makeCodexCtx(): TemplateContext { + return { + skillName: 'test-skill', + tmplPath: 'test.tmpl', + host: 'codex', + paths: HOST_PATHS.codex, + }; +} + +describe('Codex design/browse path generation', () => { + test('generated Codex browse setup uses GSTACK_BROWSE directly', () => { + const out = generateBrowseSetup(makeCodexCtx()); + expect(out).toContain('B="$GSTACK_BROWSE/browse"'); + expect(out).not.toContain('$HOME$GSTACK_BROWSE'); + }); + + test('generated Codex design setup uses GSTACK_DESIGN and GSTACK_BROWSE directly', () => { + const out = generateDesignSetup(makeCodexCtx()); + expect(out).toContain('D="$GSTACK_DESIGN/design"'); + expect(out).toContain('B="$GSTACK_BROWSE/browse"'); + expect(out).not.toContain('$HOME$GSTACK_DESIGN'); + expect(out).not.toContain('$HOME$GSTACK_BROWSE'); + }); + + test('generated Codex design mockup uses GSTACK_DESIGN directly', () => { + const out = generateDesignMockup(makeCodexCtx()); + expect(out).toContain('D="$GSTACK_DESIGN/design"'); + expect(out).not.toContain('$HOME$GSTACK_DESIGN'); + }); +}); diff --git a/test/fixtures/golden/claude-ship-SKILL.md b/test/fixtures/golden/claude-ship-SKILL.md index eadffaa8f6..acfe8e74e8 100644 --- a/test/fixtures/golden/claude-ship-SKILL.md +++ b/test/fixtures/golden/claude-ship-SKILL.md @@ -1229,7 +1229,7 @@ user via AskUserQuestion rather than destroying non-WIP commits. git commit -m "$(cat <<'EOF' chore: bump version and changelog (vX.Y.Z.W) -Co-Authored-By: Claude Opus 4.7 +Co-Authored-By: Claude Opus 4.8 EOF )" ``` diff --git a/test/fixtures/golden/codex-ship-SKILL.md b/test/fixtures/golden/codex-ship-SKILL.md index d99630c4b3..94025080ba 100644 --- a/test/fixtures/golden/codex-ship-SKILL.md +++ b/test/fixtures/golden/codex-ship-SKILL.md @@ -14,11 +14,12 @@ description: | ```bash _ROOT=$(git rev-parse --show-toplevel 2>/dev/null) -GSTACK_ROOT="$HOME/.codex/skills/gstack" +GSTACK_ROOT="$HOME/.agents/skills/gstack" [ -n "$_ROOT" ] && [ -d "$_ROOT/.agents/skills/gstack" ] && GSTACK_ROOT="$_ROOT/.agents/skills/gstack" GSTACK_BIN="$GSTACK_ROOT/bin" GSTACK_BROWSE="$GSTACK_ROOT/browse/dist" GSTACK_DESIGN="$GSTACK_ROOT/design/dist" +GSTACK_MAKE_PDF="$GSTACK_ROOT/make-pdf/dist" _UPD=$($GSTACK_BIN/gstack-update-check 2>/dev/null || .agents/skills/gstack/bin/gstack-update-check 2>/dev/null || true) [ -n "$_UPD" ] && echo "$_UPD" || true mkdir -p ~/.gstack/sessions @@ -39,7 +40,7 @@ echo "REPO_MODE: $REPO_MODE" _SESSION_KIND=$($GSTACK_BIN/gstack-session-kind 2>/dev/null || echo "interactive") case "$_SESSION_KIND" in spawned|headless|interactive) ;; *) _SESSION_KIND="interactive" ;; esac echo "SESSION_KIND: $_SESSION_KIND" -# Conductor host: AskUserQuestion is unreliable here (native disabled, MCP +# Conductor host: request_user_input is unreliable here (native disabled, MCP # variant flaky), so skills render decisions as prose instead of calling the # tool. Gated on !headless so an eval/CI run INSIDE Conductor (GSTACK_HEADLESS) # still BLOCKs rather than rendering prose to nobody. @@ -96,7 +97,7 @@ else fi $GSTACK_BIN/gstack-timeline-log '{"skill":"ship","event":"started","branch":"'"$_BRANCH"'","session":"'"$_SESSION_ID"'"}' 2>/dev/null & _HAS_ROUTING="no" -if [ -f CLAUDE.md ] && grep -q "## Skill routing" CLAUDE.md 2>/dev/null; then +if [ -f AGENTS.md ] && grep -q "## Skill routing" AGENTS.md 2>/dev/null; then _HAS_ROUTING="yes" fi _ROUTING_DECLINED=$($GSTACK_BIN/gstack-config get routing_declined 2>/dev/null || echo "false") @@ -136,18 +137,18 @@ In plan mode, allowed because they inform the plan: `$B`, `$D`, `codex exec`/`co ## Skill Invocation During Plan Mode -If the user invokes a skill in plan mode, the skill takes precedence over generic plan mode behavior. **Treat the skill file as executable instructions, not reference.** Follow it step by step starting from Step 0; the first AskUserQuestion is the workflow entering plan mode, not a violation of it. AskUserQuestion (any variant — `mcp__*__AskUserQuestion` or native; see "AskUserQuestion Format → Tool resolution") satisfies plan mode's end-of-turn requirement. If AskUserQuestion is unavailable or a call fails, follow the AskUserQuestion Format failure fallback: `headless` → BLOCKED; `interactive` → the prose fallback (also satisfies end-of-turn). At a STOP point, stop immediately. Do not continue the workflow or call ExitPlanMode there. Commands marked "PLAN MODE EXCEPTION — ALWAYS RUN" execute. Call ExitPlanMode only after the skill workflow completes, or if the user tells you to cancel the skill or leave plan mode. +If the user invokes a skill in plan mode, the skill takes precedence over generic plan mode behavior. **Treat the skill file as executable instructions, not reference.** Follow it step by step starting from Step 0; the first request_user_input is the workflow entering plan mode, not a violation of it. request_user_input (any variant — `mcp__*__request_user_input` or native; see "request_user_input Format → Tool resolution") satisfies plan mode's end-of-turn requirement. If request_user_input is unavailable or a call fails, follow the request_user_input Format failure fallback: `headless` → BLOCKED; `interactive` → the prose fallback (also satisfies end-of-turn). At a STOP point, stop immediately. Do not continue the workflow or call ExitPlanMode there. Commands marked "PLAN MODE EXCEPTION — ALWAYS RUN" execute. Call ExitPlanMode only after the skill workflow completes, or if the user tells you to cancel the skill or leave plan mode. If `PROACTIVE` is `"false"`, do not auto-invoke or proactively suggest skills. If a skill seems useful, ask: "I think /skillname might help here — want me to run it?" If `SKILL_PREFIX` is `"true"`, suggest/invoke `/gstack-*` names. Disk paths stay `$GSTACK_ROOT/[skill-name]/SKILL.md`. -If output shows `UPGRADE_AVAILABLE `: read `$GSTACK_ROOT/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). +If output shows `UPGRADE_AVAILABLE `: read `$GSTACK_ROOT/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise request_user_input with 4 options, write snooze state if declined). If output shows `JUST_UPGRADED `: print "Running gstack v{to} (just updated!)". If `SPAWNED_SESSION` is true, skip feature discovery. Feature discovery, max one prompt per session: -- Missing `$GSTACK_ROOT/.feature-prompted-continuous-checkpoint`: AskUserQuestion for Continuous checkpoint auto-commits. If accepted, run `$GSTACK_BIN/gstack-config set checkpoint_mode continuous`. Always touch marker. +- Missing `$GSTACK_ROOT/.feature-prompted-continuous-checkpoint`: request_user_input for Continuous checkpoint auto-commits. If accepted, run `$GSTACK_BIN/gstack-config set checkpoint_mode continuous`. Always touch marker. - Missing `$GSTACK_ROOT/.feature-prompted-model-overlay`: inform "Model overlays are active. MODEL_OVERLAY shows the patch." Always touch marker. After upgrade prompts, continue workflow. @@ -180,7 +181,7 @@ touch ~/.gstack/.completeness-intro-seen Only run `open` if yes. Always run `touch`. -If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion: +If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via request_user_input: > Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload. @@ -245,17 +246,17 @@ Then run `touch ~/.gstack/.first-loop-tip-shown 2>/dev/null || true`. Skip this section if `ACTIVATED` and `FIRST_LOOP_SHOWN` are both `yes`. If `HAS_ROUTING` is `no` AND `ROUTING_DECLINED` is `false` AND `PROACTIVE_PROMPTED` is `yes`: -Check if a CLAUDE.md file exists in the project root. If it does not exist, create it. +Check if a AGENTS.md file exists in the project root. If it does not exist, create it. -Use AskUserQuestion: +Use request_user_input: -> gstack works best when your project's CLAUDE.md includes skill routing rules. +> gstack works best when your project's AGENTS.md includes skill routing rules. Options: -- A) Add routing rules to CLAUDE.md (recommended) +- A) Add routing rules to AGENTS.md (recommended) - B) No thanks, I'll invoke skills manually -If A: Append this section to the end of CLAUDE.md: +If A: Append this section to the end of AGENTS.md: ```markdown @@ -279,13 +280,13 @@ Key routing rules: - Author a backlog-ready spec/issue → invoke /spec ``` -Then commit the change: `git add CLAUDE.md && git commit -m "chore: add gstack skill routing rules to CLAUDE.md"` +Then commit the change: `git add AGENTS.md && git commit -m "chore: add gstack skill routing rules to AGENTS.md"` If B: run `$GSTACK_BIN/gstack-config set routing_declined true` and say they can re-enable with `gstack-config set routing_declined false`. This only happens once per project. Skip if `HAS_ROUTING` is `yes` or `ROUTING_DECLINED` is `true`. -If `VENDORED_GSTACK` is `yes`, warn once via AskUserQuestion unless `~/.gstack/.vendoring-warned-$SLUG` exists: +If `VENDORED_GSTACK` is `yes`, warn once via request_user_input unless `~/.gstack/.vendoring-warned-$SLUG` exists: > This project has gstack vendored in `.agents/skills/gstack/`. Vendoring is deprecated. > Migrate to team mode? @@ -298,7 +299,7 @@ If A: 1. Run `git rm -r .agents/skills/gstack/` 2. Run `echo '.agents/skills/gstack/' >> .gitignore` 3. Run `$GSTACK_BIN/gstack-team-init required` (or `optional`) -4. Run `git add .claude/ .gitignore CLAUDE.md && git commit -m "chore: migrate gstack from vendored to team mode"` +4. Run `git add .claude/ .gitignore AGENTS.md && git commit -m "chore: migrate gstack from vendored to team mode"` 5. Tell the user: "Done. Each developer now runs: `cd $GSTACK_ROOT && ./setup --team`" If B: say "OK, you're on your own to keep the vendored copy up to date." @@ -313,33 +314,33 @@ If marker exists, skip. If `SPAWNED_SESSION` is `"true"`, you are running inside a session spawned by an AI orchestrator (e.g., OpenClaw). In spawned sessions: -- Do NOT use AskUserQuestion for interactive prompts. Auto-choose the recommended option. +- Do NOT use request_user_input for interactive prompts. Auto-choose the recommended option. - Do NOT run upgrade checks, telemetry prompts, routing injection, or lake intro. - Focus on completing the task and reporting results via prose output. - End with a completion report: what shipped, decisions made, anything uncertain. -## AskUserQuestion Format +## request_user_input Format ### Tool resolution (read first) -"AskUserQuestion" can resolve to two tools at runtime: the **host MCP variant** (e.g. `mcp__conductor__AskUserQuestion` — appears in your tool list when the host registers it) or the **native** Claude Code tool. +"request_user_input" can resolve to two tools at runtime: the **host MCP variant** (e.g. `mcp__conductor__request_user_input` — appears in your tool list when the host registers it) or the **native** Claude Code tool. -**Conductor rule (read before the MCP rule):** if `CONDUCTOR_SESSION: true` was echoed by the preamble, do NOT call AskUserQuestion at all — neither native nor any `mcp__*__AskUserQuestion` variant. Render EVERY decision brief as the **prose form** below and STOP. This is proactive, not a reaction to a failure: Conductor disables native AUQ and its MCP variant is flaky (it returns `[Tool result missing due to internal error]`), so prose is the reliable path. **Auto-decide preferences still apply first:** if a `[plan-tune auto-decide]