C4: no external render points at a section file that is not installed

design-html's detector install offer and plan-eng-review's self-check pointed
at $GSTACK_ROOT/<skill>/sections/<file> on every external host, where neither
skill is carved and no section tree exists. design-html now inlines the offer
off Claude, and a skill's pointer to its own {{SECTION}}-inlined section
names the inlined text.
This commit is contained in:
garrytan committed 2026-10-03 21:48:06 +00:00
1 parent d3640f28f8
commit ea0b4841ba
3 files changed
+29 -6

No files matched your search

+3 -1
View File
@@ -1,6 +1,7 @@
import { outsideVoiceFailurePolicy, outsideVoiceFor, outsideVoiceInvocation, outsideVoicePreflight, outsideVoiceProvenance } from './outside-voice';
import { type TemplateContext, quoteSafePath, toShellPath } from './types';
import { binaryAssignment } from './runtime-root';
import { usesLazySections } from './sections';
import { AI_SLOP_BLACKLIST, OPENAI_HARD_REJECTIONS, OPENAI_LITMUS_CHECKS, CC_BACKGROUND_DEFAULT_SINCE } from './constants';
import { OVERUSED_FONTS_DISPLAY, BANNED_FONTS, FONTS_BODY_UI_OK, FONTS_MONO_OK, FONTS_VERIFIED_FREE, HANDOFF_COMMANDS, selectCatalog, catalogEntries, renderCatalog, detectorSlopEntries, judgmentTellEntries } from '../../lib/design-catalog';
import { SENTINEL, DETECT_EXIT_ECHO, DETECT_LIMITS } from '../../lib/design-detect-contract';
@@ -951,7 +952,8 @@ Exit 2 → one surgical fix pass over the non-advisory rules in the \`${SENTINEL
// only), so it carries the brief inline; every other skill keeps its skeleton
// small and reads sections/detector-install-offer.md only when the probe printed
// the offer (a carved section costs nothing until it is read).
const offer = ctx.skillName === 'design-review'
// External hosts do not carve design-html, so the section file is never installed there: inline it.
const offer = ctx.skillName === 'design-review' || !usesLazySections(ctx.host, ctx.skillName)
? generateDesignDetector(ctx, ['offer'])
: `**Install offer (one question, asked once).** If the probe printed \`${SENTINEL.INSTALL_OFFER}\`, Read \`${ctx.paths.skillRoot}/${ctx.skillName}/sections/detector-install-offer.md\` and follow it before any other step; otherwise skip it.`;
return `**Design detector (optional, deterministic):** gstack runs impeccable's engine when one is installed under the user's home directory. gstack never runs impeccable's installer, its launcher, or \`npx impeccable\`; the one download it can make is the engine binary itself, only after the user says yes to the offer below, verified against a checksum pinned in gstack.
+9 -5
View File
@@ -39,14 +39,18 @@ export function usesLazySections(host: Host, skill: string): boolean {
/**
* External hosts: a template's literal `~/.claude/skills/gstack/<skill>/sections/<file>`
* reference arrives here as `$GSTACK_ROOT/<skill>/sections/<file>`, which external
* runtime roots do not have. For a carved skill, point it at the installed copy.
* runtime roots do not have. For a carved skill, point it at the installed copy;
* for this skill's own section inlined by {{SECTION}}, point at the inlined text.
*/
export function rewriteCarvedSectionRefs(content: string, ctx: TemplateContext): string {
if (ctx.host === 'claude') return content;
return content.replace(/`\$GSTACK_ROOT\/([a-z0-9-]+)\/sections\/([A-Za-z0-9._-]+)`/g, (match, skill: string, file: string) =>
usesLazySections(ctx.host, skill) && fs.existsSync(path.join(ROOT, skill, 'sections', `${file}.tmpl`))
? `\`sections/${file}\` relative to the installed \`gstack-${skill}\` SKILL.md directory`
: match);
return content.replace(/`\$GSTACK_ROOT\/([a-z0-9-]+)\/sections\/([A-Za-z0-9._-]+)`/g, (match, skill: string, file: string) => {
if (!fs.existsSync(path.join(ROOT, skill, 'sections', `${file}.tmpl`))) return match;
if (usesLazySections(ctx.host, skill)) return `\`sections/${file}\` relative to the installed \`gstack-${skill}\` SKILL.md directory`;
const entry = loadManifest(skill).sections.find(e => e.file === file);
const inlined = skill === ctx.skillName && entry && fs.readFileSync(path.join(ROOT, skill, 'SKILL.md.tmpl'), 'utf-8').includes(`{{SECTION:${entry.id}}}`);
return inlined ? `the \`${file.replace(/\.md$/, '')}\` section inlined in this SKILL.md` : match;
});
}
interface SectionEntry {