Fix Codex design binary path resolution

This commit is contained in:
Mammad M. 2026-04-23 10:53:01 +02:00
parent 656df0e37e
commit 0d7c0617be
7 changed files with 64 additions and 11 deletions

View File

@ -42,14 +42,14 @@ const codex: HostConfig = {
], ],
runtimeRoot: { runtimeRoot: {
globalSymlinks: ['bin', 'browse/dist', 'browse/bin', 'gstack-upgrade', 'ETHOS.md'], globalSymlinks: ['bin', 'browse/dist', 'browse/bin', 'design/dist', 'gstack-upgrade', 'ETHOS.md'],
globalFiles: { globalFiles: {
'review': ['checklist.md', 'TODOS-format.md'], 'review': ['checklist.md', 'TODOS-format.md'],
}, },
}, },
sidecar: { sidecar: {
path: '.agents/skills/gstack', path: '.agents/skills/gstack',
symlinks: ['bin', 'browse', 'review', 'qa', 'ETHOS.md'], symlinks: ['bin', 'browse', 'design', 'review', 'qa', 'ETHOS.md'],
}, },
install: { install: {

View File

@ -2,6 +2,12 @@ import type { TemplateContext } from './types';
import { COMMAND_DESCRIPTIONS } from '../../browse/src/commands'; import { COMMAND_DESCRIPTIONS } from '../../browse/src/commands';
import { SNAPSHOT_FLAGS } from '../../browse/src/snapshot'; 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 { export function generateCommandReference(_ctx: TemplateContext): string {
// Group commands by category // Group commands by category
const groups = new Map<string, Array<{ command: string; description: string; usage?: string }>>(); const groups = new Map<string, Array<{ command: string; description: string; usage?: string }>>();
@ -106,7 +112,7 @@ export function generateBrowseSetup(ctx: TemplateContext): string {
_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) _ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
B="" B=""
[ -n "$_ROOT" ] && [ -x "$_ROOT/${ctx.paths.localSkillRoot}/browse/dist/browse" ] && B="$_ROOT/${ctx.paths.localSkillRoot}/browse/dist/browse" [ -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 if [ -x "$B" ]; then
echo "READY: $B" echo "READY: $B"
else else

View File

@ -1,6 +1,12 @@
import type { TemplateContext } from './types'; import type { TemplateContext } from './types';
import { AI_SLOP_BLACKLIST, OPENAI_HARD_REJECTIONS, OPENAI_LITMUS_CHECKS } from './constants'; 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 { export function generateDesignReviewLite(ctx: TemplateContext): string {
const litmusList = OPENAI_LITMUS_CHECKS.map((item, i) => `${i + 1}. ${item}`).join(' '); const litmusList = OPENAI_LITMUS_CHECKS.map((item, i) => `${i + 1}. ${item}`).join(' ');
const rejectionList = OPENAI_HARD_REJECTIONS.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) _ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
D="" D=""
[ -n "$_ROOT" ] && [ -x "$_ROOT/${ctx.paths.localSkillRoot}/design/dist/design" ] && D="$_ROOT/${ctx.paths.localSkillRoot}/design/dist/design" [ -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 if [ -x "$D" ]; then
echo "DESIGN_READY: $D" echo "DESIGN_READY: $D"
else else
@ -800,7 +806,7 @@ else
fi fi
B="" B=""
[ -n "$_ROOT" ] && [ -x "$_ROOT/${ctx.paths.localSkillRoot}/browse/dist/browse" ] && B="$_ROOT/${ctx.paths.localSkillRoot}/browse/dist/browse" [ -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 if [ -x "$B" ]; then
echo "BROWSE_READY: $B" echo "BROWSE_READY: $B"
else else
@ -837,7 +843,7 @@ export function generateDesignMockup(ctx: TemplateContext): string {
_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) _ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
D="" D=""
[ -n "$_ROOT" ] && [ -x "$_ROOT/${ctx.paths.localSkillRoot}/design/dist/design" ] && D="$_ROOT/${ctx.paths.localSkillRoot}/design/dist/design" [ -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" [ -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 Prioritize ruthlessly: things needed in a hurry go close at hand, everything
else a few taps away with an obvious path to get there.`; else a few taps away with an obvious path to get there.`;
} }

9
setup
View File

@ -532,7 +532,7 @@ link_codex_skill_dirs() {
# ─── Helper: create .agents/skills/gstack/ sidecar symlinks ────────── # ─── Helper: create .agents/skills/gstack/ sidecar symlinks ──────────
# Codex/Gemini/Cursor read skills from .agents/skills/. We link runtime # 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. # resolve paths like $SKILL_ROOT/review/design-checklist.md.
create_agents_sidecar() { create_agents_sidecar() {
local repo_root="$1" local repo_root="$1"
@ -540,7 +540,7 @@ create_agents_sidecar() {
mkdir -p "$agents_gstack" mkdir -p "$agents_gstack"
# Sidecar directories that skills reference at runtime # 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 src="$SOURCE_GSTACK_DIR/$asset"
local dst="$agents_gstack/$asset" local dst="$agents_gstack/$asset"
if [ -d "$src" ] || [ -f "$src" ]; then if [ -d "$src" ] || [ -f "$src" ]; then
@ -579,7 +579,7 @@ create_codex_runtime_root() {
rm -rf "$codex_gstack" rm -rf "$codex_gstack"
fi 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 if [ -f "$agents_dir/gstack/SKILL.md" ]; then
ln -snf "$agents_dir/gstack/SKILL.md" "$codex_gstack/SKILL.md" 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 if [ -d "$gstack_dir/browse/bin" ]; then
ln -snf "$gstack_dir/browse/bin" "$codex_gstack/browse/bin" ln -snf "$gstack_dir/browse/bin" "$codex_gstack/browse/bin"
fi 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 if [ -f "$agents_dir/gstack-upgrade/SKILL.md" ]; then
ln -snf "$agents_dir/gstack-upgrade/SKILL.md" "$codex_gstack/gstack-upgrade/SKILL.md" ln -snf "$agents_dir/gstack-upgrade/SKILL.md" "$codex_gstack/gstack-upgrade/SKILL.md"
fi fi

View File

@ -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');
});
});

View File

@ -2188,12 +2188,13 @@ describe('setup script validation', () => {
}); });
test('create_agents_sidecar links runtime assets', () => { 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 fnStart = setupContent.indexOf('create_agents_sidecar()');
const fnEnd = setupContent.indexOf('}', setupContent.indexOf('done', fnStart)); const fnEnd = setupContent.indexOf('}', setupContent.indexOf('done', fnStart));
const fnBody = setupContent.slice(fnStart, fnEnd); const fnBody = setupContent.slice(fnStart, fnEnd);
expect(fnBody).toContain('bin'); expect(fnBody).toContain('bin');
expect(fnBody).toContain('browse'); expect(fnBody).toContain('browse');
expect(fnBody).toContain('design');
expect(fnBody).toContain('review'); expect(fnBody).toContain('review');
expect(fnBody).toContain('qa'); expect(fnBody).toContain('qa');
}); });
@ -2205,6 +2206,7 @@ describe('setup script validation', () => {
expect(fnBody).toContain('gstack/SKILL.md'); expect(fnBody).toContain('gstack/SKILL.md');
expect(fnBody).toContain('browse/dist'); expect(fnBody).toContain('browse/dist');
expect(fnBody).toContain('browse/bin'); expect(fnBody).toContain('browse/bin');
expect(fnBody).toContain('design/dist');
expect(fnBody).toContain('gstack-upgrade/SKILL.md'); expect(fnBody).toContain('gstack-upgrade/SKILL.md');
// Review runtime assets (individual files, not the whole dir) // Review runtime assets (individual files, not the whole dir)
expect(fnBody).toContain('checklist.md'); expect(fnBody).toContain('checklist.md');

View File

@ -350,6 +350,7 @@ describe('host-config-export.ts CLI', () => {
expect(exitCode).toBe(0); expect(exitCode).toBe(0);
const lines = stdout.split('\n'); const lines = stdout.split('\n');
expect(lines).toContain('bin'); expect(lines).toContain('bin');
expect(lines).toContain('design/dist');
expect(lines).toContain('ETHOS.md'); expect(lines).toContain('ETHOS.md');
expect(lines).toContain('review/checklist.md'); expect(lines).toContain('review/checklist.md');
}); });