mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-05 00:03:15 +01:00
* fix(make-pdf,design): real TOC page numbers and anchors; design models move to gpt-5.5 + gpt-image-2 (#2903, #2807) * ci: pin every action to a SHA, Scorecard, checksummed Bun, scoped Codex CLI in the CI image, macOS named-regressions job (#1948, #1706, #1997) * fix(cso,browse): Windows/macOS lease identity, retention skips bad runs, truthful 'not assessed' reports, env.kv false positive, Windows browse spawn fixes (#2894, #2912, #2048, #2637, #2805) * fix(outside-voice): one Codex model precedence for probe and dispatch, bounded timeouts with KILL escalation, probe cache identity, guarded mktemp, skills hidden in nested Codex (#2776, #2914, #2787, #2881, #2847) * feat(setup): install registry, ./setup --status, staged activation, per-scope migrations, host tiers and conformance kit, OpenCode/Hermes fixes, Copilot CLI (experimental), disabled_skills, slimmer CLAUDE.md (#1925, #2879, #2347, #2338, #2626, #2629, #2825, #2015, #393, #1206, #2096) Co-authored-by: Andrey Esipov <61357035+andrey-esipov@users.noreply.github.com> Co-authored-by: Matthew Williams <5863886+ridermw@users.noreply.github.com> Co-authored-by: Rocky Wu <92764750+lolisaigao1234@users.noreply.github.com> * fix(preamble): skills start in worktree-isolated Claude Code sessions — one literal skill-start line, context-recovery helper, no eval in skill blocks, $N token tripwire, honest plan-mode precedence (#2763, #2896, #2851) * fix(setup): per-install renders for renamed, vendored and custom-config installs, space-safe alias roots, per-host /cso launcher path (#1882, #2906) * test: free tests never write the real HOME (home-write tripwire), no orphaned browse daemons, symlinked-TMPDIR (macOS) fixes, gbrain-on-PATH isolation (#2895, #2816, #2829, #2841, #2982, #2911, #2909, #2908, #2910) * chore: integrate wave follow-ups — regenerate all hosts, README model docs, TODOS, touchfiles, duration seeds, Bun 1.3 telemetry test fix, replay fixtures pinned to their recorded bytes * v1.91.14.0: release metadata * test(browse): group-kill cleanup falls back to the leader when macOS returns EPERM for a live group * ci: Codex login steps run under bash (the CI container's default sh rejects pipefail) * fix: full-suite repairs — private state roots for 7 real-HOME writers, truthful home-guard attribution with --attribute-home, gstack-paths shim in ship-hook fixtures, Copilot in team-init probes, test-of-test detector, cookie review fixture pinned to the approved excerpt * fix(windows): LF for Dockerfiles, normalized home-guard exclusions, path.relative fixture containment, ship push test spawn timeouts * test(design-detect): widen the whole-scan budget test's timing margins so a loaded macOS runner can't time out the single-batch call * fix(evals): qa protocol names defined for the judge, async PTY judge so overlapping sessions keep their cleanup deadline, bws setup cases use the standard JUDGE_MS tier * test(pty): the non-blocking judge case is POSIX-only (its fake judge is a shebang script, like the PTY harness it covers) * test(evals): ship-coverage-audit names absolute skill paths, uses Read, and skips the Step 7 artifact nothing consumes (removes the mechanism behind 3 of 4 historical timeouts) * test: deflake terminal-agent concurrent startup (write the placeholder record before spawning, like the product) and qa-evidence resumed pipe (barrier on a full high-water mark instead of a sleep) * fix(design): daemon log stream handles open errors instead of crashing the daemon; shutdown test awaits the stubbed exit * test(evals): skillify checks the product's resolved state root (no GSTACK_HOME bypass), negative cases check every root; tpa shims live outside the agent's cwd and answer consistently * test(evals): coverage-audit detector splits parallel columns at │ rails and [+] groups; coverage prompts name absolute skill paths; TODOs from the gate eval census * fix(plan-eng-review): scope gate always sends the menu (no headless 'Scope pending' exit); PTY harness ends idle turn-ends and API-error panels promptly with a precise diagnostic * test(evals): one position-bound coverage-read rule credits read-only && chains (labels, context ranges, git displays in either order) and still rejects forged output * fix(ship): documentation section has one saved source inserted by cat (never retyped); spawned-marker evidence is the child output; refreshed codex/factory ship goldens * test(docsync): the byte-exact insert positive is POSIX-only (the bash-command observer refuses backslash paths by design) * test(evals): plan-design-with-ui focus-title rule accepts the comma-less 'dimensions or focus' wording (replayed on the captured CI run, negative controls kept) * fix(qa-callers): exploratory preflight says the scope/method Reads come after exploratory.md; design-review scan check credits an executed scan proven by one engine run over the changed files --------- Co-authored-by: Andrey Esipov <61357035+andrey-esipov@users.noreply.github.com> Co-authored-by: Matthew Williams <5863886+ridermw@users.noreply.github.com> Co-authored-by: Rocky Wu <92764750+lolisaigao1234@users.noreply.github.com>
373 lines
19 KiB
Markdown
373 lines
19 KiB
Markdown
# 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, Hermes,
|
|
GBrain) is defined as a typed TypeScript config object built by the
|
|
`defineHost()` factory. Rendering a new host means creating one file and
|
|
re-exporting it: the generator, tests, and validation pick it up. Installing
|
|
it is not free: setup still has one install arm per installable host (see
|
|
"What setup changes on disk" below), so an installable host also needs a setup
|
|
arm, an entry in the install registry tier table, and a certification run
|
|
before it is labelled `full`.
|
|
|
|
## Install ownership rules
|
|
|
|
These rules hold for every host, every scope, and every upgrade path
|
|
(`./setup`, `./setup --refresh-registered`, `/gstack-upgrade`, the team-mode
|
|
auto-update). Each refusal names what was left alone, the rule that applied,
|
|
and the exact command to proceed.
|
|
|
|
1. **Project-local setup never silently replaces a global install.** Setup
|
|
run from a project-vendored copy, or from any checkout other than the one a
|
|
global install links to, leaves that global install alone and prints
|
|
`./setup --global` as the way to replace it on purpose. (Codex:
|
|
`setup:186-207`, #2879. Claude: the global `~/.claude/skills/gstack` link.)
|
|
2. **An explicit `--host X` never mutates another host.** `--host codex`
|
|
writes only Codex's skills directory and the checkout's own `.agents/`
|
|
render. It does not heal, register, or remove Claude Code hooks in
|
|
`settings.json`, and it does not touch the Claude gbrain render (#2347).
|
|
One deliberate exception: the one-time `/claude` → `/claude-code` rename
|
|
repair (`bin/gstack-migrate-claude-code`) renames gstack-owned entries in
|
|
every install that shares the checkout.
|
|
3. **Upgrades refresh exactly the registered installs.** Every install setup
|
|
activates is recorded in the install registry. Upgrades run
|
|
`./setup --refresh-registered`, which runs setup once per registered host
|
|
of that checkout (printing the source first) and nothing else. Installs
|
|
owned by another checkout, and project installs of another project, are
|
|
listed with the command that refreshes them there; they are never repointed
|
|
(#1925).
|
|
4. **User-owned files survive.** Setup, `gstack-relink`, and
|
|
`gstack-uninstall` delete or link over an entry only with proof that gstack
|
|
created it (the #2119 ownership gate); anything else is reported and left.
|
|
|
|
### The install registry
|
|
|
|
`$GSTACK_STATE_ROOT/installs.tsv` (mode 0600) is the one record of installs.
|
|
Only `bin/gstack-install-registry.sh` writes it, under a lock directory with an
|
|
atomic rename. Columns: host, scope (`global`/`project`), project, destination
|
|
(the skills directory the host discovers), install root, source realpath,
|
|
version, prefix setting, render, updated-at. A row publishes only after the
|
|
host arm finished activating. Rows whose install root is gone are dropped
|
|
(`gstack_install_registry_reconcile`) by every refresh and by
|
|
`gstack-uninstall`, so an upgrade never resurrects an uninstalled host.
|
|
|
|
`./setup --status` prints one row per install (host, tier, scope, result,
|
|
version, destination, source) without building or writing anything, plus any
|
|
pre-registry install it finds on disk, labelled `unregistered` with the
|
|
command that registers it. A Codex install captured by a project-vendored copy
|
|
(#2879) is reported with both choices instead of being repointed. The setup
|
|
summary, the `/gstack-upgrade` summary, and `--status` share one renderer
|
|
(`gstack_install_registry_render`).
|
|
|
|
### Migrations
|
|
|
|
`gstack-upgrade/migrations/v*.sh` are state-root migrations. Their marker is
|
|
`$GSTACK_STATE_ROOT/.last-setup-version` (read from `~/.gstack` once for
|
|
installs that predate the move). It advances only past migrations that
|
|
succeeded; a failed migration stops the sequence, prints a `failed` row with
|
|
the retry command, and runs again on the next `./setup`. Install and host
|
|
migrations are tracked per install by the registry row's version.
|
|
|
|
### What setup changes on disk
|
|
|
|
Inventory of every setup side effect, by phase (line numbers are approximate):
|
|
|
|
| Phase | Writes | Scope |
|
|
|---|---|---|
|
|
| Build | `browse/dist`, `design/dist`, `make-pdf/dist`, `bin/gstack-cso-*` in the source checkout | source |
|
|
| Generation | `.agents/`, `.kiro/`, `.factory/`, `.opencode/`, `.cursor/` renders in the source checkout; `gstack-patch-names` rewrites Claude `name:` fields in place | source |
|
|
| Chromium | Playwright cache (`~/.cache/ms-playwright`), lock under the state root | machine |
|
|
| Config | `skill_prefix`, `timeline_stop_hook`, team-mode keys in `$GSTACK_STATE_ROOT/config.yaml` | state root |
|
|
| Claude arm | `~/.claude/skills/gstack` link, one dir per skill with a `SKILL.md` link, alias copies | selected host |
|
|
| Codex arm | `${CODEX_HOME:-~/.codex}/skills/gstack` runtime root, `gstack-*` links into `.agents/skills`, `.agents/skills/gstack` sidecar | selected host |
|
|
| Kiro, Factory, OpenCode, Cursor, Copilot arms | the host's `skills/gstack` runtime root (built beside the live one and swapped in) and `gstack-*` links | selected host |
|
|
| OpenCode commands | `~/.config/opencode/commands/gstack-*.md` with a managed marker (#2629); user command files are never touched | selected host |
|
|
| Registry | one row per activated install in `installs.tsv` | state root |
|
|
| Migrations | whatever each `v*.sh` repairs; marker in the state root | state root |
|
|
| Claude hooks | `~/.claude/settings.json` (heal, SessionStart, plan-tune, Stop) — only when Claude is selected | Claude |
|
|
| gbrain | `gbrain-detection.json`; the Claude render in `$GSTACK_STATE_ROOT/render/claude` — only when Claude is selected | state root / Claude |
|
|
|
|
Activation today: on macOS and Linux every install is a set of symlinks into
|
|
the source checkout, so a `git pull` changes the active payload before setup
|
|
runs and "keep the prior payload" means "keep the prior checkout" — which the
|
|
upgrade skill does on the vendored path (`.bak` restore) and records on the
|
|
git path (`PRE_UPGRADE_COMMIT`). On Windows, where setup copies instead of
|
|
linking, every copy is staged as `<dst>.gstack-new.<pid>` and swapped in only
|
|
when complete; an interrupted swap is repaired on the next run (a missing
|
|
destination is restored from `<dst>.gstack-old.<pid>`). Versioned payload
|
|
directories for the symlink platforms are not built yet.
|
|
|
|
### Install-context render contract
|
|
|
|
Committed renders name each host's default install root. A render for any
|
|
other root (project install, `CLAUDE_CONFIG_DIR`, renamed checkout) is made
|
|
with `gen-skill-docs --host <h> --install-root <abs path> --out-dir <tmp>
|
|
--link-root <dir>`: every default-root path becomes the given root, and
|
|
resolvers see it as `TemplateContext.installRoot` (null keeps committed bytes).
|
|
Per-install renders live in `$GSTACK_STATE_ROOT/render/installs/<host>-<id>/`
|
|
(`<id>` keyed by the root's realpath) and are recorded in the registry's render
|
|
column. Setup makes one for a Claude install outside `~/.claude/skills/gstack`
|
|
(renamed checkout, project-vendored copy, `CLAUDE_CONFIG_DIR`) and a global Codex
|
|
install outside `~/.codex/skills/gstack` (`CODEX_HOME`); repo-local Codex installs
|
|
resolve their root at run time. A root that is not a plain path (whitespace,
|
|
shell metacharacters) is named through a `<host>-<id>.root` alias symlink:
|
|
worktree-isolated Claude Code refuses a command path containing a space in any
|
|
quoting.
|
|
|
|
## Host tiers and capabilities
|
|
|
|
Every host declares `tier` and `capabilities` (`scripts/host-config.ts`).
|
|
One vocabulary is used everywhere: `./setup --help`, setup and `--status`
|
|
rows, host errors, and the README host matrix, all checked against
|
|
`hosts/index.ts` by `test/host-config.test.ts`.
|
|
|
|
| Tier | Meaning |
|
|
|---|---|
|
|
| `full` | Installs, renders, passes the conformance kit, and has a dated certification record below. |
|
|
| `experimental` | Installs and renders and passes the conformance kit; no certification record yet. |
|
|
| `instruction-only` | No install arm. `./setup --host X` prints what to copy and changes nothing. |
|
|
|
|
Rendering alone never certifies `full`.
|
|
|
|
| Capability | Values | Used for |
|
|
|---|---|---|
|
|
| `toolExecution` | boolean | preambles and `bin/` helpers run |
|
|
| `questions` | `native` / `prose` | structured question tool vs plain-text questions |
|
|
| `planMode` | boolean | plan-mode transitions |
|
|
| `delegation` | boolean | sub-agents |
|
|
| `browser` | boolean (needs `toolExecution`) | gstack's browser |
|
|
| `safetyHooks` | `enforced` / `advisory` | `/careful`, `/freeze`, `/guard` block (Claude) or only warn |
|
|
|
|
Hosts with `safetyHooks: 'advisory'` get one line at the top of `/careful`,
|
|
`/freeze` and `/guard`: "not enforced on <host>: advisory, not blocked".
|
|
|
|
## Certify your host
|
|
|
|
1. Pass the conformance kit: `bun test test/host-conformance.test.ts` installs
|
|
every installable host into a temp HOME, checks discovery directories, no
|
|
other host's variant, no duplicates, `./setup --status`, and an upgrade from
|
|
the previous release's install layout.
|
|
2. Run one representative workflow for real on the host (for example
|
|
`/review` on a small diff), and an upgrade from an existing install.
|
|
3. Add a row below with the date, host version, gstack version, and where the
|
|
transcript lives, then set `tier: 'full'`.
|
|
|
|
| Host | Date | Host version | gstack version | Evidence |
|
|
|---|---|---|---|---|
|
|
| claude | 2026-10-02 | Claude Code 2.1.284 (CI image pin) | v1.91.13.0 (1c54555) | gate-tier evals, real Claude Code sessions: https://github.com/garrytan/gstack/actions/runs/37033755821 |
|
|
|
|
Codex stays `experimental` until its periodic cases (`codex-discover-skill`,
|
|
`codex-review`) pass on the CI image's pinned Codex CLI and a row is added.
|
|
|
|
## GitHub Copilot CLI (#393)
|
|
|
|
Shipped as `experimental` (`hosts/copilot.ts`, `./setup --host copilot`):
|
|
skills in `~/.copilot/skills/gstack-*`, runtime root `~/.copilot/skills/gstack`
|
|
with a `.source-path` marker so `/gstack-upgrade` finds the checkout, names
|
|
prefixed `gstack-` (Copilot's built-in `/review` would win otherwise),
|
|
`disable-model-invocation: true` on sensitive skills, and a one-paragraph
|
|
tool-name glossary (`ask_user`, `exit_plan_mode`, `task`, `view`, `skill`)
|
|
inserted ahead of the preamble's STATUS rules. A shared directory was not
|
|
enough: Copilot CLI stopped reading `~/.claude/skills` in 1.0.36, gstack never
|
|
writes `~/.agents/skills`, and a Codex render carries Codex paths and the
|
|
Codex model profile.
|
|
|
|
Disposition of the community attempts:
|
|
|
|
| PR | Decision | Why |
|
|
|---|---|---|
|
|
| #2323 (@andrey-esipov) | Selectively reused, with credit | Kept: `~/.copilot/skills` and `.github/skills` roots, `copilot`-binary detection, `.source-path`, the `gstack-` prefix, the 1024-char description limit, uninstall coverage. Not kept: new schema fields and generator edits (predate `defineHost()`), an unguarded `rm -rf` of the runtime root (#2142), links to `setup`/`scripts`/Claude sources inside a recursively scanned dir, a test-only install backdoor in setup, and a blanket AskUserQuestion → `ask_user` rewrite. |
|
|
| #396 (@ridermw) | Rejected | Sed-rewrites Codex output into a shared `.agents/skills`; its E2E runner copies real `~/.copilot` credentials into a temp HOME. Its session scanner is a candidate for a later `/retro global` change. |
|
|
| #487 (@ridermw) | Rejected | Relies on `~/.claude/skills`, which Copilot CLI no longer reads. |
|
|
| #1852 (@lolisaigao1234) | Rejected | Detects `gh` instead of `copilot`, installs to a directory Copilot does not scan, links `bin` without `lib`. |
|
|
|
|
Unverified without a real Copilot CLI (why it is not `full`): autopilot may
|
|
answer `ask_user` itself, plan mode blocks mutating shell commands, symlinked
|
|
skill dirs need Copilot CLI 1.0.62 or later, and `COPILOT_HOME` other than
|
|
`~/.copilot` is refused for now.
|
|
|
|
## Instruction-only hosts
|
|
|
|
OpenClaw, Hermes, Slate and GBrain have no install arm. Hermes stays
|
|
instruction-only until someone certifies a native install (#2826 measured one
|
|
on Hermes 0.21.0): `./setup --host hermes` prints the digest path and the
|
|
generator command, and the Hermes render writes `name: gstack-<skill>` so
|
|
Hermes, which indexes skills by frontmatter name, cannot shadow them (#2825).
|
|
|
|
## How it works
|
|
|
|
```
|
|
hosts/
|
|
├── define-host.ts # defineHost() factory: shared defaults + derived fields
|
|
├── claude.ts # Primary host
|
|
├── codex.ts # OpenAI Codex CLI
|
|
├── factory.ts # Factory Droid
|
|
├── kiro.ts # Amazon Kiro
|
|
├── opencode.ts # OpenCode
|
|
├── slate.ts # Slate (Random Labs)
|
|
├── cursor.ts # Cursor
|
|
├── openclaw.ts # OpenClaw
|
|
├── hermes.ts # Hermes (Nous Research)
|
|
├── gbrain.ts # GBrain
|
|
└── index.ts # Registry: imports all, derives Host type
|
|
```
|
|
|
|
Each config file calls `defineHost()` and exports the resulting `HostConfig`
|
|
object, which tells the generator:
|
|
- Where to put generated skills (paths)
|
|
- How to transform frontmatter (allowlist/denylist fields)
|
|
- What Claude-specific references to rewrite (paths, tool names)
|
|
- What binary to detect for auto-install
|
|
- What resolver sections to suppress
|
|
- What assets to symlink at install time
|
|
|
|
The generator, setup script, platform-detect, uninstall, health checks, worktree
|
|
copy, and tests all read from these configs. None of them have per-host code.
|
|
|
|
## Step-by-step: add a new host
|
|
|
|
### 1. Create the config file
|
|
|
|
Configs are built with the `defineHost()` factory in `hosts/define-host.ts`.
|
|
You only write the fields that differ from the common external-host defaults;
|
|
everything else is derived from the host name. A fully-default host is two
|
|
fields (see `hosts/slate.ts` or `hosts/cursor.ts`):
|
|
|
|
```typescript
|
|
import { defineHost } from './define-host';
|
|
|
|
const myhost = defineHost({
|
|
name: 'myhost',
|
|
displayName: 'MyHost',
|
|
});
|
|
|
|
export default myhost;
|
|
```
|
|
|
|
That expands to the full `HostConfig` with these defaults:
|
|
|
|
- `cliCommand: 'myhost'` (the name; binary for `command -v` detection)
|
|
- `cliAliases: []`
|
|
- `defaultModel: 'claude'` (model overlay used when generation gets no explicit `--model`; codex overrides to `'gpt'`)
|
|
- `globalRoot` / `localSkillRoot`: `.myhost/skills/gstack`, `hostSubdir`: `.myhost`
|
|
- `usesEnvVars: true` (false only for Claude, which uses literal `~` paths)
|
|
- `frontmatter`: allowlist keeping `name` + `description`, no description limit
|
|
- `generation`: no metadata file, `skipSkills: []` (both outside-review skills are enabled; Claude and Codex explicitly omit their own wrapper)
|
|
- `pathRewrites`: the standard trio derived from the resolved paths
|
|
(`~/.claude/skills/gstack` → `~/{globalRoot}`, `.claude/skills/gstack` →
|
|
`{localSkillRoot}`, `.claude/skills` → `{hostSubdir}/skills`)
|
|
- `suppressedResolvers`: the GBrain pair (`GBRAIN_CONTEXT_LOAD`, `GBRAIN_SAVE_RESULTS`)
|
|
- `runtimeRoot`: the shared asset list (`bin`, `browse/dist`, `browse/bin`,
|
|
`gstack-upgrade`, `ETHOS.md` + review checklist files)
|
|
- `install`: `{ linkingStrategy: 'symlink-generated' }`
|
|
- `learningsMode: 'basic'`
|
|
|
|
Override any field by passing it to `defineHost()`. Two path-rewrite options:
|
|
|
|
- `extraPathRewrites`: appends entries AFTER the derived trio (e.g. kiro's
|
|
codex-path cleanup, or `{ from: 'CLAUDE.md', to: 'AGENTS.md' }` for
|
|
AGENTS.md hosts). Use this when the standard trio is right but you need more.
|
|
- `pathRewrites`: replaces the derived list entirely. Only for non-mechanical
|
|
cases — codex and factory rewrite the global path to `$GSTACK_ROOT` and add
|
|
an extra review-path rewrite; claude has an empty list.
|
|
|
|
The two are mutually exclusive (the factory throws if you pass both).
|
|
|
|
Shared constants exported from `define-host.ts` for spread-composition:
|
|
`CROSS_MODEL_RESOLVERS` (outside-provider review resolvers plus Review Army,
|
|
suppressed on hosts that opt out; Codex keeps outside reviews and suppresses
|
|
Review Army), `GBRAIN_RESOLVERS` (the default
|
|
suppression pair), and `EXEC_STYLE_TOOL_REWRITES` (the OpenClaw-style
|
|
lowercase-tool rewrites shared by openclaw and gbrain).
|
|
|
|
Good examples: `hosts/opencode.ts` (path + runtimeRoot overrides),
|
|
`hosts/factory.ts` (tool rewrites and conditional fields), `hosts/hermes.ts`
|
|
(AGENTS.md host with custom tool rewrites and resolver composition).
|
|
|
|
### 2. Register in the index
|
|
|
|
Edit `hosts/index.ts`:
|
|
|
|
```typescript
|
|
import myhost from './myhost';
|
|
|
|
// Add to ALL_HOST_CONFIGS array:
|
|
export const ALL_HOST_CONFIGS: HostConfig[] = [
|
|
claude, codex, factory, kiro, opencode, slate, cursor, openclaw, hermes, gbrain, myhost
|
|
];
|
|
|
|
// Add to re-exports:
|
|
export { claude, codex, factory, kiro, opencode, slate, cursor, openclaw, hermes, gbrain, myhost };
|
|
```
|
|
|
|
### 3. Add to .gitignore
|
|
|
|
Add `.myhost/` to `.gitignore` (generated skill docs are gitignored).
|
|
|
|
### 4. Generate and verify
|
|
|
|
```bash
|
|
# Generate skill docs for the new host
|
|
bun run gen:skill-docs --host myhost
|
|
|
|
# Verify output exists
|
|
ls .myhost/skills/gstack-*/SKILL.md
|
|
|
|
# Generate for all hosts (includes the new one)
|
|
bun run gen:skill-docs --host all
|
|
|
|
# Validate all host content and tracked output freshness, including the new host
|
|
bun run skill:check
|
|
# Claude install paths are rejected in prose; legitimate Bash fallbacks are allowed.
|
|
```
|
|
|
|
### 5. Run tests
|
|
|
|
```bash
|
|
bun test test/gen-skill-docs.test.ts
|
|
bun test test/host-config.test.ts
|
|
```
|
|
|
|
The parameterized smoke tests automatically pick up the new host. They verify: output exists, no path leakage, valid frontmatter,
|
|
freshness check passes, and outside-review skills match each host's exclusions.
|
|
|
|
### 6. Update README.md
|
|
|
|
Add install instructions for the new host in the appropriate section.
|
|
|
|
## Config field reference
|
|
|
|
See `scripts/host-config.ts` for the full `HostConfig` interface with JSDoc
|
|
comments on every field.
|
|
|
|
Key fields:
|
|
|
|
| Field | Purpose |
|
|
|-------|---------|
|
|
| `tier` | `full`, `experimental`, or `instruction-only` (see Host tiers) |
|
|
| `capabilities` | What the host runtime offers; rendering and docs must not claim more |
|
|
| `frontmatter.nameMatchesDirectory` | Write `gstack-<skill>` into `name:` for hosts that index by frontmatter name |
|
|
| `defaultModel` | Model overlay rendered when generation gets no explicit `--model` (validated against `ALL_MODEL_NAMES` in `scripts/models.ts`) |
|
|
| `frontmatter.mode` | `allowlist` (keep only listed) or `denylist` (strip listed) |
|
|
| `frontmatter.descriptionLimit` | Max chars, `null` for no limit |
|
|
| `frontmatter.descriptionLimitBehavior` | `error` (fail build), `truncate`, `warn` |
|
|
| `frontmatter.conditionalFields` | Add fields based on template values (e.g., sensitive → disable-model-invocation) |
|
|
| `frontmatter.renameFields` | Rename template fields (e.g., voice-triggers → triggers) |
|
|
| `pathRewrites` | Literal replaceAll on content. Order matters. Replaces the derived trio. |
|
|
| `extraPathRewrites` | (defineHost input only) Appended after the derived trio. |
|
|
| `toolRewrites` | Rewrite Claude tool names (e.g., "use the Bash tool" → "run this command") |
|
|
| `suppressedResolvers` | Resolver functions that return empty for this host |
|
|
| `coAuthorTrailer` | Git co-author string for commits |
|
|
| `boundaryInstruction` | Anti-prompt-injection warning for cross-model invocations |
|
|
|
|
## Validation
|
|
|
|
The `validateHostConfig()` function in `scripts/host-config.ts` checks:
|
|
- Name: lowercase alphanumeric with hyphens
|
|
- CLI command: alphanumeric with hyphens/underscores
|
|
- `defaultModel`: must be a known model family from `scripts/models.ts` `ALL_MODEL_NAMES`
|
|
- Paths: safe characters only (alphanumeric, `.`, `/`, `$`, `{}`, `~`, `-`, `_`)
|
|
- No duplicate names, hostSubdirs, or globalRoots across configs
|
|
|
|
Run `bun run scripts/host-config-export.ts validate` to check all configs.
|