mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-11 07:29:00 +02:00
fix(gen): small config scrubs — openclaw blobs to real files, setup host drift, dead artifacts
- The three openclaw markdown blobs hardcoded inside gen-skill-docs.ts (which silently reverted any hand edit to their tracked outputs on regen) move to openclaw/templates/*.md source files; output shasums byte-identical. - setup's --host allowlists gain cursor + slate — both fully registered hosts with generated output, but './setup --host cursor' exited 1 because two hand-rolled lists in setup had drifted from hosts/index.ts. - scripts/proactive-suggestions.json deleted: 31KB regenerated on every run, read by nobody (the catalog-trim design's reader was never built); its emitter and three determinism tests (which guaranteed a file nothing reads didn't churn) retired with stays-retired pins. - claude/SKILL.md.tmpl deleted: a complete 8.9KB skill that never generated output (directory name collides with the host id 'claude'), in no registry. Recoverable from git if ever wanted under a non-colliding name. - openclaw's frozen extraFields.version '0.15.2.0' stamp dropped; includeSkills: [] no-ops omitted (the generator treats [] as absent); llms.txt 55 -> 54 skills. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
5586f57c62
commit
b60fe8bd64
+19
-135
@@ -107,9 +107,8 @@ const MODEL_ARG_VAL: Model = (() => {
|
||||
})();
|
||||
|
||||
// ─── Catalog Mode (v1.45.0.0 T4) ────────────────────────────
|
||||
// 'trim' (default): shorten frontmatter description to lead sentence,
|
||||
// move routing/voice prose into a "## When to invoke" body section, and
|
||||
// emit scripts/proactive-suggestions.json (single file across all skills).
|
||||
// 'trim' (default): shorten frontmatter description to lead sentence and
|
||||
// move routing/voice prose into a "## When to invoke" body section.
|
||||
// 'full': legacy v1.44 behavior — full description stays in frontmatter.
|
||||
const CATALOG_MODE_ARG = process.argv.find(a => a.startsWith('--catalog-mode'));
|
||||
const CATALOG_MODE: 'trim' | 'full' = (() => {
|
||||
@@ -296,9 +295,7 @@ export { extractVoiceTriggers, processVoiceTriggers };
|
||||
// session pays for the full text. The catalog trim splits the description
|
||||
// into a one-line catalog entry (lead sentence + "(gstack)") that stays in
|
||||
// the frontmatter, and a "## When to invoke" body section that holds the
|
||||
// routing/voice triggers prose for in-skill discovery. A registry written
|
||||
// to scripts/proactive-suggestions.json (one entry per skill) makes routing
|
||||
// available to agents that need it without paying the always-loaded cost.
|
||||
// routing/voice triggers prose for in-skill discovery.
|
||||
//
|
||||
// Opt-out: `--catalog-mode=full` keeps v1.44 behavior (no trim, full
|
||||
// description in frontmatter). Use when debugging routing regressions or
|
||||
@@ -423,8 +420,7 @@ export function toYamlInlineScalar(s: string): string {
|
||||
* (so it lands near the top of body content, where routing guidance
|
||||
* belongs)
|
||||
*
|
||||
* Returns the rewritten content plus the parts (used for proactive-suggestions
|
||||
* JSON aggregation at the end of the run).
|
||||
* Returns the rewritten content plus the extracted parts.
|
||||
*/
|
||||
export function applyCatalogTrim(content: string, skillName: string): { content: string; parts: CatalogParts } | null {
|
||||
// Locate description block in frontmatter
|
||||
@@ -796,7 +792,7 @@ function processExternalHost(
|
||||
return { content: result, outputPath, outputDir, symlinkLoop };
|
||||
}
|
||||
|
||||
function processTemplate(tmplPath: string, host: Host = 'claude'): { outputPath: string; content: string; symlinkLoop?: boolean; catalogParts?: CatalogParts | null } {
|
||||
function processTemplate(tmplPath: string, host: Host = 'claude'): { outputPath: string; content: string; symlinkLoop?: boolean } {
|
||||
const tmplContent = fs.readFileSync(tmplPath, 'utf-8');
|
||||
const relTmplPath = path.relative(ROOT, tmplPath);
|
||||
let outputPath = tmplPath.replace(/\.tmpl$/, '');
|
||||
@@ -855,19 +851,15 @@ function processTemplate(tmplPath: string, host: Host = 'claude'): { outputPath:
|
||||
}
|
||||
|
||||
// Catalog trim (Claude only — external hosts have their own frontmatter shapes)
|
||||
let catalogParts: CatalogParts | null = null;
|
||||
if (host === 'claude' && CATALOG_MODE === 'trim') {
|
||||
const trimmed = applyCatalogTrim(content, skillName);
|
||||
if (trimmed) {
|
||||
content = trimmed.content;
|
||||
catalogParts = trimmed.parts;
|
||||
}
|
||||
if (trimmed) content = trimmed.content;
|
||||
}
|
||||
|
||||
// --out-dir: repoint section-base paths to the out-dir (no-op otherwise).
|
||||
if (host === 'claude') content = rewriteSectionBase(content);
|
||||
|
||||
return { outputPath, content, symlinkLoop, catalogParts };
|
||||
return { outputPath, content, symlinkLoop };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -943,14 +935,6 @@ for (const currentHost of hostsToRun) {
|
||||
let hasChanges = false;
|
||||
const tokenBudget: Array<{ skill: string; lines: number; tokens: number }> = [];
|
||||
|
||||
// T4 catalog trim: collect routing/voice parts across all Claude skills,
|
||||
// then write scripts/proactive-suggestions.json once per gen-skill-docs run.
|
||||
const proactiveAggregate: Record<string, {
|
||||
lead: string;
|
||||
routing: string;
|
||||
voice_line: string | null;
|
||||
}> = {};
|
||||
|
||||
const currentHostConfig = getHostConfig(currentHost);
|
||||
for (const tmplPath of findTemplates()) {
|
||||
const dir = path.basename(path.dirname(tmplPath));
|
||||
@@ -964,24 +948,7 @@ for (const currentHost of hostsToRun) {
|
||||
if (currentHostConfig.generation.skipSkills.includes(dir)) continue;
|
||||
}
|
||||
|
||||
const { outputPath, content, symlinkLoop, catalogParts } = processTemplate(tmplPath, currentHost);
|
||||
if (catalogParts) {
|
||||
// Root-skill detection: when the template lives at ROOT/SKILL.md.tmpl,
|
||||
// path.basename(path.dirname(tmplPath)) returns the repo's directory
|
||||
// name (e.g. "seville-v3" in a Conductor worktree, "gstack" on CI).
|
||||
// That's non-deterministic across machines and breaks CI freshness
|
||||
// checks. Use the frontmatter `name` field as the registry key — the
|
||||
// root SKILL.md.tmpl declares `name: gstack` explicitly. For all other
|
||||
// skills, `dir` matches the directory name which matches the
|
||||
// frontmatter name by convention.
|
||||
const isRoot = path.dirname(tmplPath) === ROOT;
|
||||
const key = isRoot ? 'gstack' : dir;
|
||||
proactiveAggregate[key] = {
|
||||
lead: catalogParts.lead,
|
||||
routing: catalogParts.routingProse,
|
||||
voice_line: catalogParts.voiceLine,
|
||||
};
|
||||
}
|
||||
const { outputPath, content, symlinkLoop } = processTemplate(tmplPath, currentHost);
|
||||
const relOutput = path.relative(OUT_DIR || ROOT, outputPath);
|
||||
|
||||
if (symlinkLoop) {
|
||||
@@ -1056,66 +1023,19 @@ for (const currentHost of hostsToRun) {
|
||||
});
|
||||
}
|
||||
|
||||
// Generate gstack-lite and gstack-full for OpenClaw host
|
||||
// Generate the OpenClaw orchestrator-injection docs (gstack-lite / gstack-full /
|
||||
// gstack-plan CLAUDE.md snippets). Sources live in openclaw/templates/ —
|
||||
// plain markdown, no placeholder resolution — and are copied byte-for-byte
|
||||
// to openclaw/ at gen time.
|
||||
if (currentHost === 'openclaw' && !DRY_RUN) {
|
||||
const openclawDir = path.join(ROOT, 'openclaw');
|
||||
if (!fs.existsSync(openclawDir)) fs.mkdirSync(openclawDir, { recursive: true });
|
||||
|
||||
const gstackLite = `# gstack-lite Planning Discipline
|
||||
|
||||
Injected by the orchestrator into spawned Claude Code sessions. Append to existing CLAUDE.md.
|
||||
|
||||
## Planning Discipline
|
||||
1. Read every file you will modify. Understand existing patterns first.
|
||||
2. Before writing code, state your plan: what, why, which files, test case, risk.
|
||||
3. When ambiguous, prefer: completeness over shortcuts, existing patterns over new ones,
|
||||
reversible choices over irreversible ones, safe defaults over clever ones.
|
||||
4. Self-review your changes before reporting done. Check for: missed files, broken
|
||||
imports, untested paths, style inconsistencies.
|
||||
5. Report when done: what shipped, what decisions you made, anything uncertain.
|
||||
`;
|
||||
fs.writeFileSync(path.join(openclawDir, 'gstack-lite-CLAUDE.md'), gstackLite);
|
||||
console.log('GENERATED: openclaw/gstack-lite-CLAUDE.md');
|
||||
|
||||
const gstackFull = `# gstack-full Pipeline
|
||||
|
||||
Injected by the orchestrator for complete feature builds. Append to existing CLAUDE.md.
|
||||
|
||||
## Full Pipeline
|
||||
1. Read CLAUDE.md and understand the project context.
|
||||
2. Run /autoplan to review your approach (CEO + eng + design review pipeline).
|
||||
3. Implement the approved plan. Follow the planning discipline above.
|
||||
4. Run /ship to create a PR with tests, changelog, and version bump.
|
||||
5. Report back: PR URL, what shipped, decisions made, anything uncertain.
|
||||
|
||||
Do not ask for human input until the PR is ready for review.
|
||||
`;
|
||||
fs.writeFileSync(path.join(openclawDir, 'gstack-full-CLAUDE.md'), gstackFull);
|
||||
console.log('GENERATED: openclaw/gstack-full-CLAUDE.md');
|
||||
|
||||
const gstackPlan = `# gstack-plan: Full Review Gauntlet
|
||||
|
||||
Injected by the orchestrator when the user wants to plan a Claude Code project.
|
||||
Append to existing CLAUDE.md.
|
||||
|
||||
## Planning Pipeline
|
||||
1. Read CLAUDE.md and understand the project context.
|
||||
2. Run /office-hours to produce a design doc (problem statement, premises, alternatives).
|
||||
3. Run /autoplan to review the design (CEO + eng + design + DX reviews + codex adversarial).
|
||||
4. Save the final reviewed plan to a file the orchestrator can reference later.
|
||||
Write it to: plans/<project-slug>-plan-<date>.md in the current repo.
|
||||
Include the design doc, all review decisions, and the implementation sequence.
|
||||
5. Report back to the orchestrator:
|
||||
- Plan file path
|
||||
- One-paragraph summary of what was designed and the key decisions
|
||||
- List of accepted scope expansions (if any)
|
||||
- Recommended next step (usually: spawn a new session with gstack-full to implement)
|
||||
|
||||
Do not implement anything. This is planning only.
|
||||
The orchestrator will persist the plan link to its own memory/knowledge store.
|
||||
`;
|
||||
fs.writeFileSync(path.join(openclawDir, 'gstack-plan-CLAUDE.md'), gstackPlan);
|
||||
console.log('GENERATED: openclaw/gstack-plan-CLAUDE.md');
|
||||
const openclawTemplatesDir = path.join(openclawDir, 'templates');
|
||||
for (const variant of ['lite', 'full', 'plan'] as const) {
|
||||
const fileName = `gstack-${variant}-CLAUDE.md`;
|
||||
const content = fs.readFileSync(path.join(openclawTemplatesDir, fileName), 'utf-8');
|
||||
fs.writeFileSync(path.join(openclawDir, fileName), content);
|
||||
console.log(`GENERATED: openclaw/${fileName}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (DRY_RUN && hasChanges) {
|
||||
@@ -1124,42 +1044,6 @@ The orchestrator will persist the plan link to its own memory/knowledge store.
|
||||
failures.push({ host: currentHost, error: new Error('Stale files detected') });
|
||||
}
|
||||
|
||||
// T4 catalog trim: write aggregated proactive-suggestions.json (Claude only).
|
||||
// The JSON registry lets agents pull voice triggers / routing prose for any
|
||||
// skill on demand instead of paying for it always-loaded in the catalog.
|
||||
//
|
||||
// No timestamp field — keeps the file content-deterministic across runs so
|
||||
// CI dry-run freshness checks don't flap on regen. If a per-run timestamp
|
||||
// is ever needed for debugging, write it to a separate `.gen-stamp` file.
|
||||
// Skip the global proactive-suggestions.json in --out-dir mode: it lives at
|
||||
// a repo path (scripts/) and the dev workspace render doesn't need it.
|
||||
if (currentHost === 'claude' && CATALOG_MODE === 'trim' && Object.keys(proactiveAggregate).length > 0 && !DRY_RUN && !OUT_DIR) {
|
||||
const proactivePath = path.join(ROOT, 'scripts', 'proactive-suggestions.json');
|
||||
// Sort keys alphabetically so the serialized JSON is identical across
|
||||
// machines regardless of filesystem-iteration order. Without this, CI
|
||||
// freshness checks fail when the local dev machine and CI runner
|
||||
// discover templates in different orders.
|
||||
const sortedSkills: typeof proactiveAggregate = {};
|
||||
for (const key of Object.keys(proactiveAggregate).sort()) {
|
||||
sortedSkills[key] = proactiveAggregate[key];
|
||||
}
|
||||
const payload = {
|
||||
$schema: 'https://gstack.dev/schemas/proactive-suggestions.json',
|
||||
catalog_mode: 'trim',
|
||||
note: 'Routing / voice-trigger prose extracted from SKILL.md frontmatter descriptions during catalog trim. Loaded on demand when routing guidance is needed.',
|
||||
skills: sortedSkills,
|
||||
};
|
||||
const serialized = JSON.stringify(payload, null, 2) + '\n';
|
||||
// Only write if content actually changed — prevents needless touches that
|
||||
// would flap CI freshness checks. Read existing file, compare, skip write
|
||||
// when identical.
|
||||
let existing = '';
|
||||
try { existing = fs.readFileSync(proactivePath, 'utf-8'); } catch { /* first run */ }
|
||||
if (existing !== serialized) {
|
||||
fs.writeFileSync(proactivePath, serialized);
|
||||
}
|
||||
}
|
||||
|
||||
// Print token budget summary
|
||||
if (!DRY_RUN && tokenBudget.length > 0) {
|
||||
tokenBudget.sort((a, b) => b.lines - a.lines);
|
||||
|
||||
Reference in New Issue
Block a user