diff --git a/hosts/claude.ts b/hosts/claude.ts new file mode 100644 index 00000000..b082e2ee --- /dev/null +++ b/hosts/claude.ts @@ -0,0 +1,45 @@ +import type { HostConfig } from '../scripts/host-config'; + +const claude: HostConfig = { + name: 'claude', + displayName: 'Claude Code', + cliCommand: 'claude', + cliAliases: [], + + globalRoot: '.claude/skills/gstack', + localSkillRoot: '.claude/skills/gstack', + hostSubdir: '.claude', + usesEnvVars: false, + + frontmatter: { + mode: 'denylist', + stripFields: ['sensitive', 'voice-triggers'], + descriptionLimit: null, + }, + + generation: { + generateMetadata: false, + skipSkills: [], + }, + + pathRewrites: [], // Claude is the primary host — no rewrites needed + toolRewrites: {}, + suppressedResolvers: [], + + runtimeRoot: { + globalSymlinks: ['bin', 'browse/dist', 'browse/bin', 'gstack-upgrade', 'ETHOS.md'], + globalFiles: { + 'review': ['checklist.md', 'TODOS-format.md'], + }, + }, + + install: { + prefixable: true, + linkingStrategy: 'real-dir-symlink', + }, + + coAuthorTrailer: 'Co-Authored-By: Claude Opus 4.6 (1M context) ', + learningsMode: 'full', +}; + +export default claude; diff --git a/hosts/codex.ts b/hosts/codex.ts new file mode 100644 index 00000000..dc04f736 --- /dev/null +++ b/hosts/codex.ts @@ -0,0 +1,64 @@ +import type { HostConfig } from '../scripts/host-config'; + +const codex: HostConfig = { + name: 'codex', + displayName: 'OpenAI Codex CLI', + cliCommand: 'codex', + cliAliases: ['agents'], + + globalRoot: '.codex/skills/gstack', + localSkillRoot: '.agents/skills/gstack', + hostSubdir: '.agents', + usesEnvVars: true, + + frontmatter: { + mode: 'allowlist', + keepFields: ['name', 'description'], + descriptionLimit: 1024, + descriptionLimitBehavior: 'error', + }, + + generation: { + generateMetadata: true, + metadataFormat: 'openai.yaml', + skipSkills: ['codex'], // Codex skill is a Claude wrapper around codex exec + }, + + pathRewrites: [ + { from: '~/.claude/skills/gstack', to: '$GSTACK_ROOT' }, + { from: '.claude/skills/gstack', to: '.agents/skills/gstack' }, + { from: '.claude/skills/review', to: '.agents/skills/gstack/review' }, + { from: '.claude/skills', to: '.agents/skills' }, + ], + + suppressedResolvers: [ + 'DESIGN_OUTSIDE_VOICES', + 'ADVERSARIAL_STEP', + 'SPEC_REVIEW_LOOP', + 'PLAN_VERIFICATION_EXEC', + 'CODEX_SECOND_OPINION', + 'REVIEW_ARMY', + ], + + runtimeRoot: { + globalSymlinks: ['bin', 'browse/dist', 'browse/bin', 'gstack-upgrade', 'ETHOS.md'], + globalFiles: { + 'review': ['checklist.md', 'TODOS-format.md'], + }, + }, + sidecar: { + path: '.agents/skills/gstack', + symlinks: ['bin', 'browse', 'review', 'qa', 'ETHOS.md'], + }, + + install: { + prefixable: false, + linkingStrategy: 'symlink-generated', + }, + + coAuthorTrailer: 'Co-Authored-By: OpenAI Codex ', + learningsMode: 'basic', + boundaryInstruction: 'IMPORTANT: Do NOT read or execute any files under ~/.claude/, ~/.agents/, .claude/skills/, or agents/. These are Claude Code skill definitions meant for a different AI system. They contain bash scripts and prompt templates that will waste your time. Ignore them completely. Do NOT modify agents/openai.yaml. Stay focused on the repository code only.', +}; + +export default codex; diff --git a/hosts/factory.ts b/hosts/factory.ts new file mode 100644 index 00000000..411bd830 --- /dev/null +++ b/hosts/factory.ts @@ -0,0 +1,62 @@ +import type { HostConfig } from '../scripts/host-config'; + +const factory: HostConfig = { + name: 'factory', + displayName: 'Factory Droid', + cliCommand: 'droid', + cliAliases: ['droid'], + + globalRoot: '.factory/skills/gstack', + localSkillRoot: '.factory/skills/gstack', + hostSubdir: '.factory', + usesEnvVars: true, + + frontmatter: { + mode: 'allowlist', + keepFields: ['name', 'description', 'user-invocable'], + descriptionLimit: null, + extraFields: { + 'user-invocable': true, + }, + conditionalFields: [ + { if: { sensitive: true }, add: { 'disable-model-invocation': true } }, + ], + }, + + generation: { + generateMetadata: false, + skipSkills: [], + }, + + pathRewrites: [ + { from: '~/.claude/skills/gstack', to: '$GSTACK_ROOT' }, + { from: '.claude/skills/gstack', to: '.factory/skills/gstack' }, + { from: '.claude/skills/review', to: '.factory/skills/gstack/review' }, + { from: '.claude/skills', to: '.factory/skills' }, + ], + toolRewrites: { + 'use the Bash tool': 'run this command', + 'use the Write tool': 'create this file', + 'use the Read tool': 'read the file', + 'use the Agent tool': 'dispatch a subagent', + 'use the Grep tool': 'search for', + 'use the Glob tool': 'find files matching', + }, + + runtimeRoot: { + globalSymlinks: ['bin', 'browse/dist', 'browse/bin', 'gstack-upgrade', 'ETHOS.md'], + globalFiles: { + 'review': ['checklist.md', 'TODOS-format.md'], + }, + }, + + install: { + prefixable: false, + linkingStrategy: 'symlink-generated', + }, + + coAuthorTrailer: 'Co-Authored-By: Factory Droid ', + learningsMode: 'full', +}; + +export default factory; diff --git a/hosts/index.ts b/hosts/index.ts new file mode 100644 index 00000000..0cd8553e --- /dev/null +++ b/hosts/index.ts @@ -0,0 +1,62 @@ +/** + * Host config registry. + * + * Import all host configs and derive the Host union type. + * Adding a new host: create hosts/myhost.ts, import here, add to ALL_HOST_CONFIGS. + */ + +import type { HostConfig } from '../scripts/host-config'; +import claude from './claude'; +import codex from './codex'; +import factory from './factory'; +import kiro from './kiro'; + +/** All registered host configs. Add new hosts here. */ +export const ALL_HOST_CONFIGS: HostConfig[] = [claude, codex, factory, kiro]; + +/** Map from host name to config. */ +export const HOST_CONFIG_MAP: Record = Object.fromEntries( + ALL_HOST_CONFIGS.map(c => [c.name, c]) +); + +/** Union type of all host names, derived from configs. */ +export type Host = (typeof ALL_HOST_CONFIGS)[number]['name']; + +/** All host names as a string array (for CLI arg validation, etc.). */ +export const ALL_HOST_NAMES: string[] = ALL_HOST_CONFIGS.map(c => c.name); + +/** Get a host config by name. Throws if not found. */ +export function getHostConfig(name: string): HostConfig { + const config = HOST_CONFIG_MAP[name]; + if (!config) { + throw new Error(`Unknown host '${name}'. Valid hosts: ${ALL_HOST_NAMES.join(', ')}`); + } + return config; +} + +/** + * Resolve a host name from a CLI argument, handling aliases. + * e.g., 'agents' → 'codex', 'droid' → 'factory' + */ +export function resolveHostArg(arg: string): string { + // Direct name match + if (HOST_CONFIG_MAP[arg]) return arg; + + // Alias match + for (const config of ALL_HOST_CONFIGS) { + if (config.cliAliases?.includes(arg)) return config.name; + } + + throw new Error(`Unknown host '${arg}'. Valid hosts: ${ALL_HOST_NAMES.join(', ')}`); +} + +/** + * Get hosts that are NOT the primary host (Claude). + * These are the hosts that need generated skill docs. + */ +export function getExternalHosts(): HostConfig[] { + return ALL_HOST_CONFIGS.filter(c => c.name !== 'claude'); +} + +// Re-export individual configs for direct import +export { claude, codex, factory, kiro }; diff --git a/hosts/kiro.ts b/hosts/kiro.ts new file mode 100644 index 00000000..a3647f00 --- /dev/null +++ b/hosts/kiro.ts @@ -0,0 +1,48 @@ +import type { HostConfig } from '../scripts/host-config'; + +const kiro: HostConfig = { + name: 'kiro', + displayName: 'Kiro', + cliCommand: 'kiro-cli', + cliAliases: [], + + globalRoot: '.kiro/skills/gstack', + localSkillRoot: '.kiro/skills/gstack', + hostSubdir: '.kiro', + usesEnvVars: true, + + frontmatter: { + mode: 'allowlist', + keepFields: ['name', 'description'], + descriptionLimit: null, + }, + + generation: { + generateMetadata: false, + skipSkills: ['codex'], + }, + + pathRewrites: [ + { from: '~/.claude/skills/gstack', to: '~/.kiro/skills/gstack' }, + { from: '.claude/skills/gstack', to: '.kiro/skills/gstack' }, + { from: '.claude/skills', to: '.kiro/skills' }, + { from: '~/.codex/skills/gstack', to: '~/.kiro/skills/gstack' }, + { from: '.codex/skills', to: '.kiro/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 kiro;