mirror of
https://github.com/garrytan/gstack.git
synced 2026-08-31 10:20:42 +02:00
feat(gen): --out-dir renders every host, outputs-only
--out-dir was Claude-host-only (gen-skill-docs.ts:842), which forced the codex/factory-regenerating tests (gen-skill-docs, skill-validation, host-config) to mutate the live tree — the reason they sit in the TREE_MUTATING serial shard. The flag now mirrors ALL outputs into the out-dir: external-host trees (.agents/.factory/... via processExternalHost), external section files, openclaw docs, and gstack/llms.txt (a catalog-mode render must never rewrite the tracked index). OUTPUTS ONLY — inputs (templates, sections/, host configs) are always read from ROOT, so an empty out-dir can never feed the render. rewriteSectionBase stays Claude-only (external hosts have their own path grammar). Proofs: in-place --host all is byte-identical (tree clean); --host all --out-dir <mkdtemp> renders the full multi-host tree with ROOT untouched; gen-skill-docs-out-dir tests + 415/415 gen-skill-docs.test.ts green (bin/dev-setup's claude rendering byte-compat). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
57501f5d6f
commit
95b779f7cd
+28
-15
@@ -146,13 +146,18 @@ const EXPLAIN_LEVEL: 'default' | 'terse' = (() => {
|
||||
})();
|
||||
|
||||
// ─── Out-dir (dev workspace render isolation) ───────────────
|
||||
// --out-dir <abs-dir> redirects Claude SKILL.md + section output to a separate
|
||||
// (untracked) directory instead of writing in place, AND rewrites the literal
|
||||
// section-base path (`~/.claude/skills/gstack/<skill>/sections/`) inside the
|
||||
// generated content to point at the out-dir, so section Reads resolve to the
|
||||
// rendered copy rather than the global install. Used by bin/dev-setup to render
|
||||
// the gbrain `:user` variant for a Conductor workspace without dirtying tracked
|
||||
// source. Default (unset) = in-place, behavior unchanged. Claude host only.
|
||||
// --out-dir <abs-dir> redirects ALL generated output (Claude SKILL.md +
|
||||
// sections, external-host trees like .agents/.factory, openclaw docs,
|
||||
// gstack/llms.txt) into a separate (untracked) directory instead of writing
|
||||
// in place. OUTPUTS ONLY: inputs (templates, sections/, host configs) are
|
||||
// always read from ROOT. For the Claude host it ALSO rewrites the literal
|
||||
// section-base path (`~/.claude/skills/gstack/<skill>/sections/`) inside
|
||||
// generated content so section Reads resolve to the rendered copy — that
|
||||
// rewrite stays Claude-only (external hosts have their own path grammar).
|
||||
// Consumers: bin/dev-setup (renders the gbrain `:user` variant for a
|
||||
// Conductor workspace — byte-compat pinned by gen-skill-docs-out-dir tests)
|
||||
// and the former TREE_MUTATING tests, which render into a mkdtemp instead
|
||||
// of mutating the live tree. Default (unset) = in-place, unchanged.
|
||||
const OUT_DIR_ARG = process.argv.find(a => a.startsWith('--out-dir'));
|
||||
const OUT_DIR: string | null = (() => {
|
||||
if (!OUT_DIR_ARG) return null;
|
||||
@@ -778,7 +783,8 @@ function processExternalHost(
|
||||
const hostConfig = getHostConfig(host);
|
||||
|
||||
const name = externalSkillName(skillDir === '.' ? '' : skillDir, frontmatterName);
|
||||
const outputDir = path.join(ROOT, hostConfig.hostSubdir, 'skills', name);
|
||||
// --out-dir mirrors the host tree (outputs only; inputs read from ROOT).
|
||||
const outputDir = path.join(OUT_DIR ?? ROOT, hostConfig.hostSubdir, 'skills', name);
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
const outputPath = path.join(outputDir, 'SKILL.md');
|
||||
|
||||
@@ -837,8 +843,8 @@ function processTemplate(tmplPath: string, host: Host = 'claude'): { outputPath:
|
||||
// Determine skill directory relative to ROOT
|
||||
const skillDir = path.relative(ROOT, path.dirname(tmplPath));
|
||||
|
||||
// --out-dir (Claude only): mirror the skill tree into the out-dir instead of
|
||||
// writing in place. External hosts compute their own paths below.
|
||||
// --out-dir: mirror the skill tree into the out-dir instead of writing in
|
||||
// place (external hosts compute their own OUT_DIR-aware paths below).
|
||||
if (OUT_DIR && host === 'claude') {
|
||||
outputPath = path.join(OUT_DIR, skillDir, path.basename(tmplPath).replace(/\.tmpl$/, ''));
|
||||
}
|
||||
@@ -949,7 +955,7 @@ function processSectionTemplate(
|
||||
outputPath = path.join(OUT_DIR || ROOT, skillDir, 'sections', fileName);
|
||||
} else {
|
||||
const externalName = externalSkillName(skillDir, parentName);
|
||||
outputPath = path.join(ROOT, hostConfig.hostSubdir, 'skills', externalName, 'sections', fileName);
|
||||
outputPath = path.join(OUT_DIR ?? ROOT, hostConfig.hostSubdir, 'skills', externalName, 'sections', fileName);
|
||||
}
|
||||
if (!DRY_RUN) fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
return { outputPath, content };
|
||||
@@ -1063,6 +1069,7 @@ for (const currentHost of hostsToRun) {
|
||||
console.log(`FRESH: ${relOutput}`);
|
||||
}
|
||||
} else {
|
||||
if (OUT_DIR) fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
fs.writeFileSync(outputPath, content);
|
||||
console.log(`GENERATED: ${relOutput}`);
|
||||
}
|
||||
@@ -1079,12 +1086,14 @@ for (const currentHost of hostsToRun) {
|
||||
// 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');
|
||||
const openclawTemplatesDir = path.join(openclawDir, 'templates');
|
||||
// Inputs from ROOT, outputs into OUT_DIR when set (outputs-only rule).
|
||||
const openclawTemplatesDir = path.join(ROOT, 'openclaw', 'templates');
|
||||
const openclawOutDir = path.join(OUT_DIR ?? ROOT, 'openclaw');
|
||||
if (OUT_DIR) fs.mkdirSync(openclawOutDir, { recursive: true });
|
||||
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);
|
||||
fs.writeFileSync(path.join(openclawOutDir, fileName), content);
|
||||
console.log(`GENERATED: openclaw/${fileName}`);
|
||||
}
|
||||
}
|
||||
@@ -1152,7 +1161,11 @@ if (!DRY_RUN) {
|
||||
if (!DRY_RUN) {
|
||||
void (async () => {
|
||||
try {
|
||||
const result = await writeLlmsTxt();
|
||||
const result = await writeLlmsTxt(
|
||||
// Outputs-only rule: under --out-dir even this index lands there
|
||||
// (a catalog-mode render must never rewrite the tracked llms.txt).
|
||||
OUT_DIR ? { outputPath: path.join(OUT_DIR, 'gstack', 'llms.txt') } : {},
|
||||
);
|
||||
if (result.warnings.length > 0) {
|
||||
for (const w of result.warnings) console.error(`[gen-llms-txt] WARN: ${w}`);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user