Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9ff4c75
test: add golden-file baselines for host config refactor
garrytan Apr 3, 2026
a382cdb
feat: add HostConfig interface and validator for declarative host system
garrytan Apr 3, 2026
ae7dd31
feat: add typed host configs for Claude, Codex, Factory, and Kiro
garrytan Apr 3, 2026
4a1a70c
refactor: derive Host type and HOST_PATHS from host configs
garrytan Apr 3, 2026
9612b1c
refactor: gen-skill-docs.ts consumes typed host configs
garrytan Apr 3, 2026
d82d2c5
refactor: preamble, co-author trailer, and resolver suppression use h…
garrytan Apr 3, 2026
9ac662f
refactor: setup tooling uses config-driven host detection
garrytan Apr 3, 2026
8b52563
feat: add OpenCode, Slate, and Cursor host configs
garrytan Apr 3, 2026
86b2156
feat: add OpenClaw host config with adapter for tool mapping
garrytan Apr 3, 2026
6a36d6e
feat: contributor add-host skill + fix version sync
garrytan Apr 3, 2026
720d347
test: add parameterized host smoke tests for all hosts
garrytan Apr 3, 2026
ab85096
test: 100% coverage for host config system
garrytan Apr 4, 2026
cb238b4
chore: resolve merge conflict in package.json (take main's 0.15.3.0)
garrytan Apr 4, 2026
b35d572
chore: update golden baselines and sync version after merge from main
garrytan Apr 4, 2026
202dfab
chore: bump version and changelog (v0.15.5.0)
garrytan Apr 4, 2026
f710271
chore: resolve merge conflict in package.json (take main's 0.15.3.0)
garrytan Apr 4, 2026
8247161
fix: sidebar E2E tests now self-contained and passing
garrytan Apr 4, 2026
4b647fa
docs: add ADDING_A_HOST.md guide + update docs for multi-host system
garrytan Apr 4, 2026
6cfd5a4
docs: README per-host install instructions for all 8 agents
garrytan Apr 4, 2026
80a06d8
chore: resolve merge conflict in CHANGELOG.md, bump to v0.15.6.0
garrytan Apr 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ bin/gstack-global-discover
.claude/skills/
.agents/
.factory/
.kiro/
.opencode/
.slate/
.cursor/
.openclaw/
.context/
extension/.auth.json
.gstack-worktrees/
Expand Down
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
# Changelog

## [0.15.6.0] - 2026-04-04 — Declarative Multi-Host Platform

Adding a new coding agent to gstack used to mean touching 9 files and knowing the internals of `gen-skill-docs.ts`. Now it's one TypeScript config file and a re-export. Zero code changes elsewhere. Tests auto-parameterize.

### Added

- **Declarative host config system.** Every host is a typed `HostConfig` object in `hosts/*.ts`. The generator, setup, skill-check, platform-detect, uninstall, and worktree copy all consume configs instead of hardcoded switch statements. Adding a host = one file + re-export in `hosts/index.ts`.
- **4 new hosts: OpenCode, Slate, Cursor, OpenClaw.** `bun run gen:skill-docs --host all` now generates for 8 hosts. Each produces valid SKILL.md output with zero `.claude/skills` path leakage.
- **OpenClaw adapter.** OpenClaw gets a hybrid approach: config for paths/frontmatter/detection + a post-processing adapter for semantic tool mapping (Bash→exec, Agent→sessions_spawn, AskUserQuestion→prose). Includes `SOUL.md` via `staticFiles` config.
- **106 new tests.** 71 tests for config validation, HOST_PATHS derivation, export CLI, golden-file regression, and per-host correctness. 35 parameterized smoke tests covering all 7 external hosts (output exists, no path leakage, frontmatter valid, freshness, skip rules).
- **`host-config-export.ts` CLI.** Exposes host configs to bash scripts via `list`, `get`, `detect`, `validate`, `symlinks` commands. No YAML parsing needed in bash.
- **Contributor `/gstack-contrib-add-host` skill.** Guides new host config creation. Lives in `contrib/`, excluded from user installs.
- **Golden-file baselines.** Snapshots of ship/SKILL.md for Claude, Codex, and Factory verify the refactor produces identical output.
- **Per-host install instructions in README.** Every supported agent has its own copy-paste install block.

### Changed

- **`gen-skill-docs.ts` is now config-driven.** EXTERNAL_HOST_CONFIG, transformFrontmatter host branches, path/tool rewrite if-chains, ALL_HOSTS array, and skill skip logic all replaced with config lookups.
- **`types.ts` derives Host type from configs.** No more hardcoded `'claude' | 'codex' | 'factory'`. HOST_PATHS built dynamically from each config's globalRoot/usesEnvVars.
- **Preamble, co-author trailer, resolver suppression all read from config.** hostConfigDir, co-author strings, and suppressedResolvers driven by host configs instead of per-host switch statements.
- **`skill-check.ts`, `worktree.ts`, `platform-detect` iterate configs.** No per-host blocks to maintain.

### Fixed

- **Sidebar E2E tests now self-contained.** Fixed stale URL assertion in sidebar-url-accuracy, simplified sidebar-css-interaction task. All 3 sidebar tests pass without external browser dependencies.

## [0.15.5.0] - 2026-04-04 — Interactive DX Review + Plan Mode Skill Fix

`/plan-devex-review` now feels like sitting down with a developer advocate who has used 100 CLI tools. Instead of speed-running 8 scores, it asks who your developer is, benchmarks you against competitors' onboarding times, makes you design your magical moment, and traces every friction point step by step before scoring anything.
Expand Down
12 changes: 11 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,16 @@ gstack/
│ │ └── snapshot.ts # SNAPSHOT_FLAGS metadata array
│ ├── test/ # Integration tests + fixtures
│ └── dist/ # Compiled binary
├── hosts/ # Typed host configs (one per AI agent)
│ ├── claude.ts # Primary host config
│ ├── codex.ts, factory.ts, kiro.ts # Existing hosts
│ ├── opencode.ts, slate.ts, cursor.ts, openclaw.ts # New hosts
│ └── index.ts # Registry: exports all, derives Host type
├── scripts/ # Build + DX tooling
│ ├── gen-skill-docs.ts # Template → SKILL.md generator
│ ├── gen-skill-docs.ts # Template → SKILL.md generator (config-driven)
│ ├── host-config.ts # HostConfig interface + validator
│ ├── host-config-export.ts # Shell bridge for setup script
│ ├── host-adapters/ # Host-specific adapters (OpenClaw tool mapping)
│ ├── resolvers/ # Template resolver modules (preamble, design, review, etc.)
│ ├── skill-check.ts # Health dashboard
│ └── dev-skill.ts # Watch mode
Expand Down Expand Up @@ -108,6 +116,8 @@ gstack/
├── .github/ # CI workflows + Docker image
│ ├── workflows/ # evals.yml (E2E on Ubicloud), skill-docs.yml, actionlint.yml
│ └── docker/ # Dockerfile.ci (pre-baked toolchain + Playwright/Chromium)
├── contrib/ # Contributor-only tools (never installed for users)
│ └── add-host/ # /gstack-contrib-add-host skill
├── setup # One-time setup: build binary + symlink skills
├── SKILL.md # Generated from SKILL.md.tmpl (don't edit directly)
├── SKILL.md.tmpl # Template: edit this, run gen:skill-docs
Expand Down
80 changes: 47 additions & 33 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,10 @@ SKILL.md files are **generated** from `.tmpl` templates. Don't edit the `.md` di
# 1. Edit the template
vim SKILL.md.tmpl # or browse/SKILL.md.tmpl

# 2. Regenerate for both hosts
bun run gen:skill-docs
bun run gen:skill-docs --host codex
# 2. Regenerate for all hosts
bun run gen:skill-docs --host all

# 3. Check health (reports both Claude and Codex)
# 3. Check health (reports all hosts)
bun run skill:check

# Or use watch mode — auto-regenerates on save
Expand All @@ -231,59 +230,74 @@ For template authoring best practices (natural language over bash-isms, dynamic

To add a browse command, add it to `browse/src/commands.ts`. To add a snapshot flag, add it to `SNAPSHOT_FLAGS` in `browse/src/snapshot.ts`. Then rebuild.

## Dual-host development (Claude + Codex)
## Multi-host development

gstack generates SKILL.md files for two hosts: **Claude** (`.claude/skills/`) and **Codex** (`.agents/skills/`). Every template change needs to be generated for both.
gstack generates SKILL.md files for 8 hosts from one set of `.tmpl` templates.
Each host is a typed config in `hosts/*.ts`. The generator reads these configs
to produce host-appropriate output (different frontmatter, paths, tool names).

### Generating for both hosts
**Supported hosts:** Claude (primary), Codex, Factory, Kiro, OpenCode, Slate, Cursor, OpenClaw.

```bash
# Generate Claude output (default)
bun run gen:skill-docs
### Generating for all hosts

# Generate Codex output
bun run gen:skill-docs --host codex
# --host agents is an alias for --host codex
```bash
# Generate for a specific host
bun run gen:skill-docs # Claude (default)
bun run gen:skill-docs --host codex # Codex
bun run gen:skill-docs --host opencode # OpenCode
bun run gen:skill-docs --host all # All 8 hosts

# Or use build, which does both + compiles binaries
# Or use build, which does all hosts + compiles binaries
bun run build
```

### What changes between hosts

| Aspect | Claude | Codex |
|--------|--------|-------|
| Output directory | `{skill}/SKILL.md` | `.agents/skills/gstack-{skill}/SKILL.md` (generated at setup, gitignored) |
| Frontmatter | Full (name, description, voice-triggers, allowed-tools, hooks, version) | Minimal (name + description only) |
| Paths | `~/.claude/skills/gstack` | `$GSTACK_ROOT` (`.agents/skills/gstack` in a repo, otherwise `~/.codex/skills/gstack`) |
| Hook skills | `hooks:` frontmatter (enforced by Claude) | Inline safety advisory prose (advisory only) |
| `/codex` skill | Included (Claude wraps codex exec) | Excluded (self-referential) |
Each host config (`hosts/*.ts`) controls:

| Aspect | Example (Claude vs Codex) |
|--------|---------------------------|
| Output directory | `{skill}/SKILL.md` vs `.agents/skills/gstack-{skill}/SKILL.md` |
| Frontmatter | Full (name, description, hooks, version) vs minimal (name + description) |
| Paths | `~/.claude/skills/gstack` vs `$GSTACK_ROOT` |
| Tool names | "use the Bash tool" vs same (Factory rewrites to "run this command") |
| Hook skills | `hooks:` frontmatter vs inline safety advisory prose |
| Suppressed sections | None vs Codex self-invocation sections stripped |

### Testing Codex output
See `scripts/host-config.ts` for the full `HostConfig` interface.

### Testing host output

```bash
# Run all static tests (includes Codex validation)
# Run all static tests (includes parameterized smoke tests for all hosts)
bun test

# Check freshness for both hosts
bun run gen:skill-docs --dry-run
bun run gen:skill-docs --host codex --dry-run
# Check freshness for all hosts
bun run gen:skill-docs --host all --dry-run

# Health dashboard covers both hosts
# Health dashboard covers all hosts
bun run skill:check
```

### Dev setup for .agents/
### Adding a new host

See [docs/ADDING_A_HOST.md](docs/ADDING_A_HOST.md) for the full guide. Short version:

1. Create `hosts/myhost.ts` (copy from `hosts/opencode.ts`)
2. Add to `hosts/index.ts`
3. Add `.myhost/` to `.gitignore`
4. Run `bun run gen:skill-docs --host myhost`
5. Run `bun test` (parameterized tests auto-cover it)

When you run `bin/dev-setup`, it creates symlinks in both `.claude/skills/` and `.agents/skills/` (if applicable), so Codex-compatible agents can discover your dev skills too. The `.agents/` directory is generated at setup time from `.tmpl` templates — it is gitignored and not committed.
Zero generator, setup, or tooling code changes needed.

### Adding a new skill

When you add a new skill template, both hosts get it automatically:
When you add a new skill template, all hosts get it automatically:
1. Create `{skill}/SKILL.md.tmpl`
2. Run `bun run gen:skill-docs` (Claude output) and `bun run gen:skill-docs --host codex` (Codex output)
3. The dynamic template discovery picks it up no static list to update
4. Commit `{skill}/SKILL.md` — `.agents/` is generated at setup time and gitignored
2. Run `bun run gen:skill-docs --host all`
3. The dynamic template discovery picks it up, no static list to update
4. Commit `{skill}/SKILL.md`, external host output is generated at setup time and gitignored

## Conductor workspaces

Expand Down
64 changes: 47 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,49 +59,79 @@ Real files get committed to your repo (not a submodule), so `git clone` just wor
> git clone https://github.com/garrytan/gstack.git ~/.claude/skills/gstack
> ```

### Codex, Gemini CLI, or Cursor
### Other AI Agents

gstack works on any agent that supports the [SKILL.md standard](https://github.com/anthropics/claude-code). Skills live in `.agents/skills/` and are discovered automatically.
gstack works on 8 AI coding agents, not just Claude. All 31 skills work across
every supported agent. Setup auto-detects which agents you have installed, or
you can target a specific one.

Install to one repo:
#### Auto-detect (installs for every agent on your machine)

```bash
git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git .agents/skills/gstack
cd .agents/skills/gstack && ./setup --host codex
git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/gstack
cd ~/gstack && ./setup
```

When setup runs from `.agents/skills/gstack`, it installs the generated Codex skills next to it in the same repo and does not write to `~/.codex/skills`.

Install once for your user account:
#### OpenAI Codex CLI

```bash
git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/gstack
cd ~/gstack && ./setup --host codex
```

`setup --host codex` creates the runtime root at `~/.codex/skills/gstack` and
links the generated Codex skills at the top level. This avoids duplicate skill
discovery from the source repo checkout.
Skills install to `~/.codex/skills/gstack-*/`. For repo-local installs, clone
into `.agents/skills/gstack` instead.

Or let setup auto-detect which agents you have installed:
#### OpenCode

```bash
git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/gstack
cd ~/gstack && ./setup --host auto
cd ~/gstack && ./setup --host opencode
```

For Codex-compatible hosts, setup now supports both repo-local installs from `.agents/skills/gstack` and user-global installs from `~/.codex/skills/gstack`. All 31 skills work across all supported agents. Hook-based safety skills (careful, freeze, guard) use inline safety advisory prose on non-Claude hosts.
Skills install to `~/.config/opencode/skills/gstack-*/`.

#### Cursor

```bash
git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/gstack
cd ~/gstack && ./setup --host cursor
```

### Factory Droid
Skills install to `~/.cursor/skills/gstack-*/`.

gstack works with [Factory Droid](https://factory.ai). Skills install to `.factory/skills/` and are discovered automatically. Sensitive skills (ship, land-and-deploy, guard) use `disable-model-invocation: true` so Droids don't auto-invoke them.
#### Factory Droid

```bash
git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/gstack
cd ~/gstack && ./setup --host factory
```

Skills install to `~/.factory/skills/gstack-*/`. Restart `droid` to rescan skills, then type `/qa` to get started.
Skills install to `~/.factory/skills/gstack-*/`. Sensitive skills use
`disable-model-invocation: true` so Droids don't auto-invoke them.

#### OpenClaw

```bash
git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/gstack
cd ~/gstack && ./setup --host openclaw
```

Skills install to `~/.openclaw/skills/gstack-*/`. Tool names are rewritten
for OpenClaw's tool system (exec, read, write, edit, sessions_spawn).

#### Slate / Kiro

```bash
./setup --host slate # Slate (Random Labs)
./setup --host kiro # Amazon Kiro
```

Hook-based safety skills (careful, freeze, guard) use inline safety advisory
prose on all non-Claude hosts.

**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.

### Voice input (AquaVoice, Whisper, etc.)

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.15.5.0
0.15.6.0
31 changes: 19 additions & 12 deletions bin/gstack-platform-detect
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,26 @@
set -euo pipefail

# gstack-platform-detect: show which AI coding agents are installed and gstack status
# Config-driven: reads host definitions from hosts/*.ts via host-config-export.ts

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
GSTACK_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"

printf "%-16s %-10s %-40s %s\n" "Agent" "Version" "Skill Path" "gstack"
printf "%-16s %-10s %-40s %s\n" "-----" "-------" "----------" "------"
for entry in "claude:claude" "codex:codex" "droid:factory" "kiro-cli:kiro"; do
bin="${entry%%:*}"; label="${entry##*:}"
if command -v "$bin" >/dev/null 2>&1; then
ver=$("$bin" --version 2>/dev/null | head -1 || echo "unknown")
case "$label" in
claude) spath="$HOME/.claude/skills/gstack" ;;
codex) spath="$HOME/.codex/skills/gstack" ;;
factory) spath="$HOME/.factory/skills/gstack" ;;
kiro) spath="$HOME/.kiro/skills/gstack" ;;
esac
status=$([ -d "$spath" ] && echo "INSTALLED" || echo "NOT INSTALLED")
printf "%-16s %-10s %-40s %s\n" "$label" "$ver" "$spath" "$status"

for host in $(bun run "$GSTACK_DIR/scripts/host-config-export.ts" list 2>/dev/null); do
cmd=$(bun run "$GSTACK_DIR/scripts/host-config-export.ts" get "$host" cliCommand 2>/dev/null)
root=$(bun run "$GSTACK_DIR/scripts/host-config-export.ts" get "$host" globalRoot 2>/dev/null)
spath="$HOME/$root"

if command -v "$cmd" >/dev/null 2>&1; then
ver=$("$cmd" --version 2>/dev/null | head -1 || echo "unknown")
if [ -d "$spath" ] || [ -L "$spath" ]; then
status="INSTALLED"
else
status="NOT INSTALLED"
fi
printf "%-16s %-10s %-40s %s\n" "$host" "$ver" "$spath" "$status"
fi
done
63 changes: 63 additions & 0 deletions contrib/add-host/SKILL.md.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
name: gstack-contrib-add-host
description: |
Contributor-only skill: create a new host config for gstack's multi-host system.
NOT installed for end users. Only usable from the gstack source repo.
---

# /gstack-contrib-add-host — Add a New Host

This skill helps contributors add support for a new AI coding agent to gstack.

## What you'll create

A single TypeScript file in `hosts/<name>.ts` that defines:
- CLI binary name for detection
- Skill directory paths (global + local)
- Frontmatter transformation rules
- Path and tool rewrites
- Runtime root symlink manifest

## Steps

### 1. Gather host info

Ask the contributor:
- What's the agent's name? (e.g., "OpenCode")
- What's the CLI binary? (e.g., "opencode")
- Where does it store skills globally? (e.g., "~/.config/opencode/skills/")
- Where does it store skills locally in a project? (e.g., ".opencode/skills/")
- What frontmatter fields does it support? (name + description is the minimum)
- Does it have its own tool names? (e.g., "exec" instead of "Bash")

### 2. Create the config file

Use `hosts/opencode.ts` as a reference. Create `hosts/<name>.ts` with the
gathered info. Follow the HostConfig interface in `scripts/host-config.ts`.

### 3. Register in index

Add the import and re-export in `hosts/index.ts`.

### 4. Add to .gitignore

Add `.<name>/` to `.gitignore`.

### 5. Generate and verify

```bash
bun run gen:skill-docs --host <name>
```

Check:
- Output exists at `.<name>/skills/gstack-*/SKILL.md`
- No `.claude/skills` path leakage
- Frontmatter matches expected format

### 6. Run tests

```bash
bun test test/gen-skill-docs.test.ts
```

All parameterized tests auto-include the new host.
Loading
Loading