From 0d7c0617be42eab0a9cfe3d834451387d816cedc Mon Sep 17 00:00:00 2001 From: "Mammad M." Date: Thu, 23 Apr 2026 10:53:01 +0200 Subject: [PATCH] Fix Codex design binary path resolution --- hosts/codex.ts | 4 ++-- scripts/resolvers/browse.ts | 8 +++++++- scripts/resolvers/design.ts | 13 ++++++++---- setup | 9 ++++++--- test/codex-design-paths.test.ts | 36 +++++++++++++++++++++++++++++++++ test/gen-skill-docs.test.ts | 4 +++- test/host-config.test.ts | 1 + 7 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 test/codex-design-paths.test.ts diff --git a/hosts/codex.ts b/hosts/codex.ts index 7dc80ea87..733415746 100644 --- a/hosts/codex.ts +++ b/hosts/codex.ts @@ -42,14 +42,14 @@ const codex: HostConfig = { ], runtimeRoot: { - globalSymlinks: ['bin', 'browse/dist', 'browse/bin', 'gstack-upgrade', 'ETHOS.md'], + globalSymlinks: ['bin', 'browse/dist', 'browse/bin', 'design/dist', 'gstack-upgrade', 'ETHOS.md'], globalFiles: { 'review': ['checklist.md', 'TODOS-format.md'], }, }, sidecar: { path: '.agents/skills/gstack', - symlinks: ['bin', 'browse', 'review', 'qa', 'ETHOS.md'], + symlinks: ['bin', 'browse', 'design', 'review', 'qa', 'ETHOS.md'], }, install: { diff --git a/scripts/resolvers/browse.ts b/scripts/resolvers/browse.ts index a0ae37a70..0f4d5dd7a 100644 --- a/scripts/resolvers/browse.ts +++ b/scripts/resolvers/browse.ts @@ -2,6 +2,12 @@ import type { TemplateContext } from './types'; import { COMMAND_DESCRIPTIONS } from '../../browse/src/commands'; import { SNAPSHOT_FLAGS } from '../../browse/src/snapshot'; +function resolveBinaryPath(dirExpr: string, binaryName: string): string { + return dirExpr.startsWith('$') + ? `${dirExpr}/${binaryName}` + : `$HOME${dirExpr.replace(/^~/, '')}/${binaryName}`; +} + export function generateCommandReference(_ctx: TemplateContext): string { // Group commands by category const groups = new Map>(); @@ -106,7 +112,7 @@ export function generateBrowseSetup(ctx: TemplateContext): string { _ROOT=$(git rev-parse --show-toplevel 2>/dev/null) B="" [ -n "$_ROOT" ] && [ -x "$_ROOT/${ctx.paths.localSkillRoot}/browse/dist/browse" ] && B="$_ROOT/${ctx.paths.localSkillRoot}/browse/dist/browse" -[ -z "$B" ] && B="$HOME${ctx.paths.browseDir.replace(/^~/, '')}/browse" +[ -z "$B" ] && B="${resolveBinaryPath(ctx.paths.browseDir, 'browse')}" if [ -x "$B" ]; then echo "READY: $B" else diff --git a/scripts/resolvers/design.ts b/scripts/resolvers/design.ts index fc6d6ecee..1344d3383 100644 --- a/scripts/resolvers/design.ts +++ b/scripts/resolvers/design.ts @@ -1,6 +1,12 @@ import type { TemplateContext } from './types'; import { AI_SLOP_BLACKLIST, OPENAI_HARD_REJECTIONS, OPENAI_LITMUS_CHECKS } from './constants'; +function resolveBinaryPath(dirExpr: string, binaryName: string): string { + return dirExpr.startsWith('$') + ? `${dirExpr}/${binaryName}` + : `$HOME${dirExpr.replace(/^~/, '')}/${binaryName}`; +} + export function generateDesignReviewLite(ctx: TemplateContext): string { const litmusList = OPENAI_LITMUS_CHECKS.map((item, i) => `${i + 1}. ${item}`).join(' '); const rejectionList = OPENAI_HARD_REJECTIONS.map((item, i) => `${i + 1}. ${item}`).join(' '); @@ -792,7 +798,7 @@ export function generateDesignSetup(ctx: TemplateContext): string { _ROOT=$(git rev-parse --show-toplevel 2>/dev/null) D="" [ -n "$_ROOT" ] && [ -x "$_ROOT/${ctx.paths.localSkillRoot}/design/dist/design" ] && D="$_ROOT/${ctx.paths.localSkillRoot}/design/dist/design" -[ -z "$D" ] && D="$HOME${ctx.paths.designDir.replace(/^~/, '')}/design" +[ -z "$D" ] && D="${resolveBinaryPath(ctx.paths.designDir, 'design')}" if [ -x "$D" ]; then echo "DESIGN_READY: $D" else @@ -800,7 +806,7 @@ else fi B="" [ -n "$_ROOT" ] && [ -x "$_ROOT/${ctx.paths.localSkillRoot}/browse/dist/browse" ] && B="$_ROOT/${ctx.paths.localSkillRoot}/browse/dist/browse" -[ -z "$B" ] && B="$HOME${ctx.paths.browseDir.replace(/^~/, '')}/browse" +[ -z "$B" ] && B="${resolveBinaryPath(ctx.paths.browseDir, 'browse')}" if [ -x "$B" ]; then echo "BROWSE_READY: $B" else @@ -837,7 +843,7 @@ export function generateDesignMockup(ctx: TemplateContext): string { _ROOT=$(git rev-parse --show-toplevel 2>/dev/null) D="" [ -n "$_ROOT" ] && [ -x "$_ROOT/${ctx.paths.localSkillRoot}/design/dist/design" ] && D="$_ROOT/${ctx.paths.localSkillRoot}/design/dist/design" -[ -z "$D" ] && D="$HOME${ctx.paths.designDir.replace(/^~/, '')}/design" +[ -z "$D" ] && D="${resolveBinaryPath(ctx.paths.designDir, 'design')}" [ -x "$D" ] && echo "DESIGN_READY" || echo "DESIGN_NOT_AVAILABLE" \`\`\` @@ -1139,4 +1145,3 @@ Flat design can strip away useful visual information that signals interactivity. Prioritize ruthlessly: things needed in a hurry go close at hand, everything else a few taps away with an obvious path to get there.`; } - diff --git a/setup b/setup index 4c1763f9f..b71d5b626 100755 --- a/setup +++ b/setup @@ -532,7 +532,7 @@ link_codex_skill_dirs() { # ─── Helper: create .agents/skills/gstack/ sidecar symlinks ────────── # Codex/Gemini/Cursor read skills from .agents/skills/. We link runtime -# assets (bin/, browse/dist/, review/, qa/, etc.) so skill templates can +# assets (bin/, browse/dist/, design/dist/, review/, qa/, etc.) so skill templates can # resolve paths like $SKILL_ROOT/review/design-checklist.md. create_agents_sidecar() { local repo_root="$1" @@ -540,7 +540,7 @@ create_agents_sidecar() { mkdir -p "$agents_gstack" # Sidecar directories that skills reference at runtime - for asset in bin browse review qa; do + for asset in bin browse design review qa; do local src="$SOURCE_GSTACK_DIR/$asset" local dst="$agents_gstack/$asset" if [ -d "$src" ] || [ -f "$src" ]; then @@ -579,7 +579,7 @@ create_codex_runtime_root() { rm -rf "$codex_gstack" fi - mkdir -p "$codex_gstack" "$codex_gstack/browse" "$codex_gstack/gstack-upgrade" "$codex_gstack/review" + mkdir -p "$codex_gstack" "$codex_gstack/browse" "$codex_gstack/design" "$codex_gstack/gstack-upgrade" "$codex_gstack/review" if [ -f "$agents_dir/gstack/SKILL.md" ]; then ln -snf "$agents_dir/gstack/SKILL.md" "$codex_gstack/SKILL.md" @@ -593,6 +593,9 @@ create_codex_runtime_root() { if [ -d "$gstack_dir/browse/bin" ]; then ln -snf "$gstack_dir/browse/bin" "$codex_gstack/browse/bin" fi + if [ -d "$gstack_dir/design/dist" ]; then + ln -snf "$gstack_dir/design/dist" "$codex_gstack/design/dist" + fi if [ -f "$agents_dir/gstack-upgrade/SKILL.md" ]; then ln -snf "$agents_dir/gstack-upgrade/SKILL.md" "$codex_gstack/gstack-upgrade/SKILL.md" fi diff --git a/test/codex-design-paths.test.ts b/test/codex-design-paths.test.ts new file mode 100644 index 000000000..4b7612746 --- /dev/null +++ b/test/codex-design-paths.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, test } from 'bun:test'; +import type { TemplateContext } from '../scripts/resolvers/types'; +import { HOST_PATHS } from '../scripts/resolvers/types'; +import { generateBrowseSetup } from '../scripts/resolvers/browse'; +import { generateDesignMockup, generateDesignSetup } from '../scripts/resolvers/design'; + +function makeCodexCtx(): TemplateContext { + return { + skillName: 'test-skill', + tmplPath: 'test.tmpl', + host: 'codex', + paths: HOST_PATHS.codex, + }; +} + +describe('Codex design/browse path generation', () => { + test('generated Codex browse setup uses GSTACK_BROWSE directly', () => { + const out = generateBrowseSetup(makeCodexCtx()); + expect(out).toContain('B="$GSTACK_BROWSE/browse"'); + expect(out).not.toContain('$HOME$GSTACK_BROWSE'); + }); + + test('generated Codex design setup uses GSTACK_DESIGN and GSTACK_BROWSE directly', () => { + const out = generateDesignSetup(makeCodexCtx()); + expect(out).toContain('D="$GSTACK_DESIGN/design"'); + expect(out).toContain('B="$GSTACK_BROWSE/browse"'); + expect(out).not.toContain('$HOME$GSTACK_DESIGN'); + expect(out).not.toContain('$HOME$GSTACK_BROWSE'); + }); + + test('generated Codex design mockup uses GSTACK_DESIGN directly', () => { + const out = generateDesignMockup(makeCodexCtx()); + expect(out).toContain('D="$GSTACK_DESIGN/design"'); + expect(out).not.toContain('$HOME$GSTACK_DESIGN'); + }); +}); diff --git a/test/gen-skill-docs.test.ts b/test/gen-skill-docs.test.ts index 6c40710b4..ba9045cef 100644 --- a/test/gen-skill-docs.test.ts +++ b/test/gen-skill-docs.test.ts @@ -2188,12 +2188,13 @@ describe('setup script validation', () => { }); test('create_agents_sidecar links runtime assets', () => { - // Sidecar must link bin, browse, review, qa + // Sidecar must link bin, browse, design, review, qa const fnStart = setupContent.indexOf('create_agents_sidecar()'); const fnEnd = setupContent.indexOf('}', setupContent.indexOf('done', fnStart)); const fnBody = setupContent.slice(fnStart, fnEnd); expect(fnBody).toContain('bin'); expect(fnBody).toContain('browse'); + expect(fnBody).toContain('design'); expect(fnBody).toContain('review'); expect(fnBody).toContain('qa'); }); @@ -2205,6 +2206,7 @@ describe('setup script validation', () => { expect(fnBody).toContain('gstack/SKILL.md'); expect(fnBody).toContain('browse/dist'); expect(fnBody).toContain('browse/bin'); + expect(fnBody).toContain('design/dist'); expect(fnBody).toContain('gstack-upgrade/SKILL.md'); // Review runtime assets (individual files, not the whole dir) expect(fnBody).toContain('checklist.md'); diff --git a/test/host-config.test.ts b/test/host-config.test.ts index 577057033..ee53f4231 100644 --- a/test/host-config.test.ts +++ b/test/host-config.test.ts @@ -350,6 +350,7 @@ describe('host-config-export.ts CLI', () => { expect(exitCode).toBe(0); const lines = stdout.split('\n'); expect(lines).toContain('bin'); + expect(lines).toContain('design/dist'); expect(lines).toContain('ETHOS.md'); expect(lines).toContain('review/checklist.md'); });