mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-01 19:25:10 +02:00
04b709d91a
* test: add golden-file baselines for host config refactor Snapshot generated SKILL.md output for ship skill across all 3 existing hosts (Claude, Codex, Factory). These baselines verify the config-driven refactor produces identical output to the current hardcoded system. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add HostConfig interface and validator for declarative host system New scripts/host-config.ts defines the typed HostConfig interface that captures all per-host variation: paths, frontmatter rules, path/tool rewrites, suppressed resolvers, runtime root symlinks, install strategy, and behavioral config (co-author trailer, learnings mode, boundary instruction). Includes validateHostConfig() and validateAllConfigs() with regex-based security validation and cross-config uniqueness checks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add typed host configs for Claude, Codex, Factory, and Kiro Extract all hardcoded host-specific values from gen-skill-docs.ts, types.ts, preamble.ts, review.ts, and setup into typed HostConfig objects. Each host is a single file in hosts/ with its paths, frontmatter rules, path/tool rewrites, runtime root manifest, and install behavior. hosts/index.ts exports all configs, derives the Host type, and provides resolveHostArg() for CLI alias handling (e.g., 'agents' -> 'codex', 'droid' -> 'factory'). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: derive Host type and HOST_PATHS from host configs types.ts no longer hardcodes host names or paths. The Host type is derived from ALL_HOST_CONFIGS in hosts/index.ts, and HOST_PATHS is built dynamically from each config's globalRoot/localSkillRoot/usesEnvVars. Adding a new host to hosts/index.ts automatically extends the type system. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: gen-skill-docs.ts consumes typed host configs Replace hardcoded EXTERNAL_HOST_CONFIG, transformFrontmatter host branches, path/tool rewrite if-chains, and ALL_HOSTS array with config-driven lookups from hosts/*.ts. - Host detection uses resolveHostArg() (handles aliases like agents/droid) - transformFrontmatter uses config's allowlist/denylist mode, extraFields, conditionalFields, renameFields, and descriptionLimitBehavior - Path rewrites use config's pathRewrites array (replaceAll, order matters) - Tool rewrites use config's toolRewrites object - Skill skipping uses config's generation.skipSkills - ALL_HOSTS derived from ALL_HOST_NAMES - Token budget display regex derived from host configs Golden-file comparison: all 3 hosts produce IDENTICAL output to baselines. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: preamble, co-author trailer, and resolver suppression use host configs - preamble.ts: hostConfigDir derived from config.globalRoot instead of hardcoded Record - utility.ts: generateCoAuthorTrailer reads from config.coAuthorTrailer instead of host switch statement - gen-skill-docs.ts: suppressedResolvers from config skip resolver execution at placeholder replacement time (belt+suspenders with existing ctx.host checks in individual resolvers) Golden-file comparison: all 3 hosts produce IDENTICAL output to baselines. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: setup tooling uses config-driven host detection - host-config-export.ts: new CLI that exposes host configs to bash (list, get, detect, validate, symlinks commands) - bin/gstack-platform-detect: reads host configs instead of hardcoded binary/path mapping - scripts/skill-check.ts: iterates host configs for skill validation and freshness checks instead of separate Codex/Factory blocks - lib/worktree.ts: iterates host configs for directory copy instead of hardcoded .agents Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add OpenCode, Slate, and Cursor host configs Three new hosts added to the declarative config system. Each is a typed HostConfig object with paths, frontmatter rules, and path rewrites. All generate valid SKILL.md output with zero .claude/skills path leakage. - hosts/opencode.ts: OpenCode (opencode.ai), skills at ~/.config/opencode/ - hosts/slate.ts: Slate (Random Labs), skills at ~/.slate/ - hosts/cursor.ts: Cursor, skills at ~/.cursor/ - .gitignore: add .kiro/, .opencode/, .slate/, .cursor/, .openclaw/ Zero code changes needed — just config files + re-export in index.ts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add OpenClaw host config with adapter for tool mapping OpenClaw gets a hybrid approach: typed config for paths/frontmatter/ detection + a post-processing adapter for semantic tool rewrites. Config handles: path rewrites, frontmatter (name+description+version), CLAUDE.md→AGENTS.md, tool name rewrites (Bash→exec, Read→read, etc.), suppressed resolvers, SOUL.md via staticFiles. Adapter handles: AskUserQuestion→prose, Agent→sessions_spawn, $B→exec $B. Zero .claude/skills path leakage. Zero hardcoded tool references remaining. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: contributor add-host skill + fix version sync - contrib/add-host/SKILL.md.tmpl: contributor-only skill that guides new host config creation. Lives in contrib/, excluded from user installs. - package.json: sync version with VERSION file (0.15.2.1) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add parameterized host smoke tests for all hosts 35 new tests covering all 7 external hosts (Codex, Factory, Kiro, OpenCode, Slate, Cursor, OpenClaw). Each host gets 4-5 tests: - output exists on disk with SKILL.md files - no .claude/skills path leakage in non-root skills - frontmatter has name + description fields - --dry-run freshness check passes - /codex skill excluded (for hosts with skipSkills: ['codex']) Tests are parameterized over ALL_HOST_CONFIGS so adding a new host automatically gets smoke-tested with zero new test code. Also updates --host all test to verify all registered hosts generate. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: 100% coverage for host config system 71 new tests in test/host-config.test.ts covering: - hosts/index.ts: ALL_HOST_CONFIGS, getHostConfig, resolveHostArg (aliases), getExternalHosts, uniqueness checks - host-config.ts validateHostConfig: name regex, displayName, cliCommand, cliAliases, globalRoot, localSkillRoot, hostSubdir, frontmatter.mode, linkingStrategy, shell injection attempts, paths with $ and ~ - host-config.ts validateAllConfigs: duplicate name/hostSubdir/globalRoot detection, error prefix format, real configs pass - HOST_PATHS derivation: env vars for external hosts, literal paths for Claude, localSkillRoot matches config, every host has entry - host-config-export.ts CLI: list, get (string/boolean/array), detect, validate, symlinks, error cases (missing args, unknown field/host) - Golden-file regression: claude/codex/factory ship SKILL.md vs baselines - Individual host config correctness: prefixable, linkingStrategy, usesEnvVars, description limits, metadata, sidecar, tool rewrites, conditional fields, suppressed resolvers, boundary instruction, co-author trailers, skip rules, path rewrites, runtime root assets Combined with the 35 parameterized smoke tests from gen-skill-docs.test.ts, total new test coverage for multi-host: 106 tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: update golden baselines and sync version after merge from main Golden files refreshed to match post-merge generated output. package.json version synced to VERSION file (0.15.4.0). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: bump version and changelog (v0.15.5.0) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: sidebar E2E tests now self-contained and passing - sidebar-url-accuracy: fix stale assertion that expected extensionUrl in prompt text (prompt format changed, URL is now in pageUrl field) - sidebar-css-interaction: simplify task from multi-step HN comment navigation to single-page example.com style injection (faster, more reliable, still exercises goto + style + completion flow) - Update golden baselines after merge from main All 3 sidebar tests now pass: 3/3, 0 fail, ~36s total. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add ADDING_A_HOST.md guide + update docs for multi-host system - docs/ADDING_A_HOST.md: step-by-step guide for adding a new host (create config, register, gitignore, generate, test). Covers the full HostConfig interface, adapter pattern, and validation. - CONTRIBUTING.md: replace stale "Dual-host development" section with "Multi-host development" covering all 8 hosts and linking to the guide. - README.md: consolidate Codex/Factory install sections into one "Other AI Agents" section listing all supported hosts with auto-detect. - CLAUDE.md: add hosts/, host-config.ts, host-adapters/, contrib/ to project structure tree. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: README per-host install instructions for all 8 agents Each supported agent now has its own copy-paste install block with the exact command and where skills end up on disk. Includes: auto-detect, Codex, OpenCode, Cursor, Factory, OpenClaw, Slate, and Kiro. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
183 lines
6.1 KiB
Markdown
183 lines
6.1 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) 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.
|
|
|
|
## How it works
|
|
|
|
```
|
|
hosts/
|
|
├── 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 (hybrid: config + adapter)
|
|
└── index.ts # Registry: imports all, derives Host type
|
|
```
|
|
|
|
Each config file exports a `HostConfig` object that 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
|
|
|
|
Copy an existing config as a starting point. `hosts/opencode.ts` is a good
|
|
minimal example. `hosts/factory.ts` shows tool rewrites and conditional fields.
|
|
`hosts/openclaw.ts` shows the adapter pattern for hosts with different tool models.
|
|
|
|
Create `hosts/myhost.ts`:
|
|
|
|
```typescript
|
|
import type { HostConfig } from '../scripts/host-config';
|
|
|
|
const myhost: HostConfig = {
|
|
name: 'myhost',
|
|
displayName: 'MyHost',
|
|
cliCommand: 'myhost', // binary name for `command -v` detection
|
|
cliAliases: [], // alternative binary names
|
|
|
|
globalRoot: '.myhost/skills/gstack',
|
|
localSkillRoot: '.myhost/skills/gstack',
|
|
hostSubdir: '.myhost',
|
|
usesEnvVars: true, // false only for Claude (uses literal ~ paths)
|
|
|
|
frontmatter: {
|
|
mode: 'allowlist', // 'allowlist' keeps only listed fields
|
|
keepFields: ['name', 'description'],
|
|
descriptionLimit: null, // set to 1024 for hosts with limits
|
|
},
|
|
|
|
generation: {
|
|
generateMetadata: false, // true only for Codex (openai.yaml)
|
|
skipSkills: ['codex'], // codex skill is Claude-only
|
|
},
|
|
|
|
pathRewrites: [
|
|
{ from: '~/.claude/skills/gstack', to: '~/.myhost/skills/gstack' },
|
|
{ from: '.claude/skills/gstack', to: '.myhost/skills/gstack' },
|
|
{ from: '.claude/skills', to: '.myhost/skills' },
|
|
],
|
|
|
|
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',
|
|
};
|
|
|
|
export default myhost;
|
|
```
|
|
|
|
### 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, myhost
|
|
];
|
|
|
|
// Add to re-exports:
|
|
export { claude, codex, factory, kiro, opencode, slate, cursor, openclaw, 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 and has no .claude/skills leakage
|
|
ls .myhost/skills/gstack-*/SKILL.md
|
|
grep -r ".claude/skills" .myhost/skills/ | head -5
|
|
# (should be empty)
|
|
|
|
# Generate for all hosts (includes the new one)
|
|
bun run gen:skill-docs --host all
|
|
|
|
# Health dashboard shows the new host
|
|
bun run skill:check
|
|
```
|
|
|
|
### 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. Zero test
|
|
code to write. They verify: output exists, no path leakage, valid frontmatter,
|
|
freshness check passes, codex skill excluded.
|
|
|
|
### 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 |
|
|
|-------|---------|
|
|
| `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. |
|
|
| `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 |
|
|
| `adapter` | Path to adapter module for complex transformations |
|
|
|
|
## Adapter pattern (for hosts with different tool models)
|
|
|
|
If string-replace tool rewrites aren't enough (the host has fundamentally
|
|
different tool semantics), use the adapter pattern. See `hosts/openclaw.ts`
|
|
and `scripts/host-adapters/openclaw-adapter.ts`.
|
|
|
|
The adapter runs as a post-processing step after all generic rewrites. It
|
|
exports `transform(content: string, config: HostConfig): string`.
|
|
|
|
## Validation
|
|
|
|
The `validateHostConfig()` function in `scripts/host-config.ts` checks:
|
|
- Name: lowercase alphanumeric with hyphens
|
|
- CLI command: alphanumeric with hyphens/underscores
|
|
- 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.
|