mirror of https://github.com/garrytan/gstack.git
fix(hosts): delete five dead HostConfig fields
metadataFormat (generator hardcodes openai.yaml), sidecar (behavior lives in setup's create_agents_sidecar — knowledge preserved as a comment in codex.ts), install.prefixable (skill_prefix is implemented entirely in bin/gstack-config), staticFiles (docstring cited a SOUL.md that never existed anywhere), and adapter (its only would-be consumer, openclaw-adapter.ts, was fully dead — with a test asserting the field was undefined). Kept: learningsMode (wired next), linkingStrategy (validation reads it), coAuthorTrailer (consumed by resolvers/utility.ts). Proof: JSON dump diff shows ONLY the deleted keys vanishing; zero-diff regen across all 10 hosts; host-config + gen-skill-docs suites green. Note: this commit also carries chunk-23 edits to the shared hosts/claude.ts + define-host.ts + host-config.test.ts files (skipSkills collapse, stale line-number comment drops) — pathspec commits, concurrent prep. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
2b27d89ae3
commit
9124559810
|
|
@ -117,7 +117,6 @@ gstack/
|
|||
│ ├── 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, gbrain, etc.)
|
||||
│ ├── skill-check.ts # Health dashboard
|
||||
│ ├── test-paid-shards.ts # Sharded paid-tier runner (one Bun process per shard)
|
||||
|
|
|
|||
|
|
@ -63,14 +63,14 @@ That expands to the full `HostConfig` with these defaults:
|
|||
- `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 sidecar, `skipSkills: ['codex']` (codex skill is Claude-only)
|
||||
- `generation`: no metadata file, `skipSkills: ['codex']` (codex skill is Claude-only)
|
||||
- `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`: `{ prefixable: false, linkingStrategy: 'symlink-generated' }`
|
||||
- `install`: `{ linkingStrategy: 'symlink-generated' }`
|
||||
- `learningsMode: 'basic'`
|
||||
|
||||
Override any field by passing it to `defineHost()`. Two path-rewrite options:
|
||||
|
|
@ -167,17 +167,6 @@ Key fields:
|
|||
| `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: set the `adapter` field
|
||||
to the adapter module path. See `scripts/host-adapters/openclaw-adapter.ts`
|
||||
for the reference implementation (no shipped host currently sets `adapter`).
|
||||
|
||||
The adapter runs as a post-processing step after all generic rewrites. It
|
||||
exports `transform(content: string, config: HostConfig): string`.
|
||||
|
||||
## Validation
|
||||
|
||||
|
|
|
|||
|
|
@ -14,14 +14,13 @@ const claude = defineHost({
|
|||
|
||||
generation: {
|
||||
generateMetadata: false,
|
||||
skipSkills: ['claude'], // Claude outside-voice skill is for non-Claude hosts
|
||||
skipSkills: [], // overrides the default ['codex'] — the /codex skill IS a Claude skill (wrapper around codex exec)
|
||||
},
|
||||
|
||||
pathRewrites: [], // Claude is the primary host — no rewrites needed
|
||||
toolRewrites: {},
|
||||
|
||||
install: {
|
||||
prefixable: true,
|
||||
linkingStrategy: 'real-dir-symlink',
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -15,9 +15,12 @@ const codex = defineHost({
|
|||
descriptionLimitBehavior: 'error',
|
||||
},
|
||||
|
||||
// generateMetadata emits agents/openai.yaml (the format is hardcoded in
|
||||
// gen-skill-docs.ts). Codex also gets a repo-local sidecar at
|
||||
// .agents/skills/gstack (symlinked runtime assets: bin, browse, review, qa,
|
||||
// ETHOS.md) — that behavior lives in setup's create_agents_sidecar, not here.
|
||||
generation: {
|
||||
generateMetadata: true,
|
||||
metadataFormat: 'openai.yaml',
|
||||
skipSkills: ['codex'], // Codex skill is a Claude wrapper around codex exec
|
||||
},
|
||||
|
||||
|
|
@ -34,11 +37,6 @@ const codex = defineHost({
|
|||
// The cross-model resolvers all shell out to Codex — Codex can't invoke itself.
|
||||
suppressedResolvers: [...CROSS_MODEL_RESOLVERS, ...GBRAIN_RESOLVERS],
|
||||
|
||||
sidecar: {
|
||||
path: '.agents/skills/gstack',
|
||||
symlinks: ['bin', 'browse', 'review', 'qa', 'ETHOS.md'],
|
||||
},
|
||||
|
||||
coAuthorTrailer: 'Co-Authored-By: OpenAI Codex <noreply@openai.com>',
|
||||
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.',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,11 +9,9 @@
|
|||
* paths, the shared runtimeRoot asset list, and symlink-generated install.
|
||||
*
|
||||
* Defaults are constructed fresh per call, so no two host configs ever share
|
||||
* a mutable array/object. Fields that are absent today (staticFiles, adapter,
|
||||
* sidecar, toolRewrites, coAuthorTrailer, boundaryInstruction) stay absent
|
||||
* unless a host explicitly sets them — the factory never default-populates
|
||||
* optional fields (test/host-config.test.ts pins e.g. openclaw.adapter as
|
||||
* undefined).
|
||||
* a mutable array/object. Optional fields that are absent today (toolRewrites,
|
||||
* coAuthorTrailer, boundaryInstruction) stay absent unless a host explicitly
|
||||
* sets them — the factory never default-populates optional fields.
|
||||
*/
|
||||
|
||||
import type { HostConfig } from '../scripts/host-config';
|
||||
|
|
@ -27,11 +25,11 @@ type PathRewrite = { from: string; to: string };
|
|||
* non-Claude agent runtimes (OpenClaw, Hermes, GBrain).
|
||||
*/
|
||||
export const CROSS_MODEL_RESOLVERS: string[] = [
|
||||
'DESIGN_OUTSIDE_VOICES', // design.ts:485 — invokes Codex for outside voices
|
||||
'ADVERSARIAL_STEP', // review.ts:408 — invokes Codex adversarially
|
||||
'CODEX_SECOND_OPINION', // review.ts:257 — invokes Codex
|
||||
'CODEX_PLAN_REVIEW', // review.ts:541 — invokes Codex
|
||||
'REVIEW_ARMY', // review-army.ts:180 — multi-model orchestration
|
||||
'DESIGN_OUTSIDE_VOICES', // design.ts — invokes Codex for outside voices
|
||||
'ADVERSARIAL_STEP', // review.ts — invokes Codex adversarially
|
||||
'CODEX_SECOND_OPINION', // review.ts — invokes Codex
|
||||
'CODEX_PLAN_REVIEW', // review.ts — invokes Codex
|
||||
'REVIEW_ARMY', // review-army.ts — multi-model orchestration
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -111,16 +109,12 @@ export function defineHost<const N extends string>(overrides: HostOverrides<N>):
|
|||
'review': ['checklist.md', 'TODOS-format.md'],
|
||||
},
|
||||
},
|
||||
sidecar,
|
||||
install = {
|
||||
prefixable: false,
|
||||
linkingStrategy: 'symlink-generated',
|
||||
},
|
||||
coAuthorTrailer,
|
||||
learningsMode = 'basic',
|
||||
boundaryInstruction,
|
||||
staticFiles,
|
||||
adapter,
|
||||
} = overrides;
|
||||
|
||||
if (pathRewrites && extraPathRewrites) {
|
||||
|
|
@ -156,12 +150,9 @@ export function defineHost<const N extends string>(overrides: HostOverrides<N>):
|
|||
...(toolRewrites !== undefined ? { toolRewrites } : {}),
|
||||
suppressedResolvers,
|
||||
runtimeRoot,
|
||||
...(sidecar !== undefined ? { sidecar } : {}),
|
||||
install,
|
||||
...(coAuthorTrailer !== undefined ? { coAuthorTrailer } : {}),
|
||||
learningsMode,
|
||||
...(boundaryInstruction !== undefined ? { boundaryInstruction } : {}),
|
||||
...(staticFiles !== undefined ? { staticFiles } : {}),
|
||||
...(adapter !== undefined ? { adapter } : {}),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,10 +56,8 @@ export interface HostConfig {
|
|||
|
||||
// --- Generation ---
|
||||
generation: {
|
||||
/** Whether to create sidecar metadata file (e.g., openai.yaml for Codex). */
|
||||
/** Whether to create a metadata file alongside skills (always openai.yaml; gen-skill-docs hardcodes the format). */
|
||||
generateMetadata: boolean;
|
||||
/** Metadata file format (e.g., 'openai.yaml'). */
|
||||
metadataFormat?: string | null;
|
||||
/** Skill directories to exclude from generation for this host. */
|
||||
skipSkills?: string[];
|
||||
/** Skill directories to include (allowlist). Union logic: include minus skip. */
|
||||
|
|
@ -81,18 +79,8 @@ export interface HostConfig {
|
|||
/** Dir → explicit file list for selective file linking. */
|
||||
globalFiles?: Record<string, string[]>;
|
||||
};
|
||||
/** Optional repo-local sidecar config (e.g., Codex uses .agents/skills/gstack). */
|
||||
sidecar?: {
|
||||
/** Sidecar path relative to repo root (e.g., '.agents/skills/gstack'). */
|
||||
path: string;
|
||||
/** Assets to symlink into sidecar (different set than global). */
|
||||
symlinks: string[];
|
||||
};
|
||||
|
||||
// --- Install Behavior ---
|
||||
install: {
|
||||
/** Whether gstack-config skill_prefix applies (Claude only). */
|
||||
prefixable: boolean;
|
||||
/** How skills are linked into the host dir. */
|
||||
linkingStrategy: 'real-dir-symlink' | 'symlink-generated';
|
||||
};
|
||||
|
|
@ -104,11 +92,6 @@ export interface HostConfig {
|
|||
learningsMode?: 'full' | 'basic';
|
||||
/** Anti-prompt-injection boundary instruction for cross-model invocations. */
|
||||
boundaryInstruction?: string;
|
||||
|
||||
/** Static files to copy alongside generated skills (e.g., { 'SOUL.md': 'openclaw/SOUL.md' }). */
|
||||
staticFiles?: Record<string, string>;
|
||||
/** Optional path to host-adapter module for complex transformations. */
|
||||
adapter?: string;
|
||||
}
|
||||
|
||||
// --- Validation ---
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ describe('validateHostConfig', () => {
|
|||
generation: { generateMetadata: false },
|
||||
pathRewrites: [],
|
||||
runtimeRoot: { globalSymlinks: ['bin'] },
|
||||
install: { prefixable: false, linkingStrategy: 'symlink-generated' },
|
||||
install: { linkingStrategy: 'symlink-generated' },
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -441,16 +441,6 @@ describe('golden-file regression', () => {
|
|||
// ─── Individual host config correctness ─────────────────────
|
||||
|
||||
describe('host config correctness', () => {
|
||||
test('claude is the only prefixable host', () => {
|
||||
for (const config of ALL_HOST_CONFIGS) {
|
||||
if (config.name === 'claude') {
|
||||
expect(config.install.prefixable).toBe(true);
|
||||
} else {
|
||||
expect(config.install.prefixable).toBe(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('claude is the only host with real-dir-symlink strategy', () => {
|
||||
for (const config of ALL_HOST_CONFIGS) {
|
||||
if (config.name === 'claude') {
|
||||
|
|
@ -476,14 +466,8 @@ describe('host config correctness', () => {
|
|||
expect(codex.frontmatter.descriptionLimitBehavior).toBe('error');
|
||||
});
|
||||
|
||||
test('codex generates openai.yaml metadata', () => {
|
||||
test('codex generates metadata (openai.yaml, format hardcoded in gen-skill-docs)', () => {
|
||||
expect(codex.generation.generateMetadata).toBe(true);
|
||||
expect(codex.generation.metadataFormat).toBe('openai.yaml');
|
||||
});
|
||||
|
||||
test('codex has sidecar config', () => {
|
||||
expect(codex.sidecar).toBeDefined();
|
||||
expect(codex.sidecar!.path).toBe('.agents/skills/gstack');
|
||||
});
|
||||
|
||||
test('factory has tool rewrites', () => {
|
||||
|
|
@ -521,17 +505,13 @@ describe('host config correctness', () => {
|
|||
expect(openclaw.pathRewrites.some(r => r.from === 'CLAUDE.md' && r.to === 'AGENTS.md')).toBe(true);
|
||||
});
|
||||
|
||||
test('openclaw has no adapter (dead code removed)', () => {
|
||||
expect(openclaw.adapter).toBeUndefined();
|
||||
});
|
||||
|
||||
test('openclaw has no staticFiles (SOUL.md removed)', () => {
|
||||
expect(openclaw.staticFiles).toBeUndefined();
|
||||
});
|
||||
|
||||
test('openclaw includeSkills is empty (native skills replaced generated ones)', () => {
|
||||
expect(openclaw.generation.includeSkills).toBeDefined();
|
||||
expect(openclaw.generation.includeSkills!.length).toBe(0);
|
||||
test('no host carries a no-op empty includeSkills allowlist', () => {
|
||||
// includeSkills: [] was a no-op (the generator's `?.length` guard treats an
|
||||
// empty allowlist as absent), so configs omit the field instead of
|
||||
// shipping a lie about "no skills generated".
|
||||
for (const config of ALL_HOST_CONFIGS) {
|
||||
expect(config.generation.includeSkills).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
test('every host has coAuthorTrailer or undefined', () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue