fix: stop env-var hosts from doubling $HOME in binary-resolver fallback

Non-Claude hosts (codex, factory, cursor, opencode, slate, kiro, hermes,
gbrain) resolve the browse/design/make-pdf binaries through absolute
$GSTACK_* env vars. The setup blocks built the global-install fallback as
`$HOME${dir.replace(/^~/, '')}/bin`, which assumes a ~-rooted dir. For
env-var hosts `dir` is already `$GSTACK_BROWSE` (absolute, contains $HOME),
so $HOME got prepended twice — e.g. `$HOME$GSTACK_BROWSE/browse` expands to
/home/me/home/me/.codex/.../browse and never resolves, leaving the skill
stuck on BROWSE_NOT_AVAILABLE / NEEDS_SETUP even when the binary is built.

Add toShellPath() in resolvers/types.ts: ~-rooted dirs expand via $HOME,
absolute env-var dirs pass through untouched. Applied at all six call
sites in browse.ts, design.ts, make-pdf.ts.

Claude output is byte-identical; env-var hosts now emit $GSTACK_BROWSE/browse.
tsc clean; resolver + make-pdf + golden host-config suites pass (270).
This commit is contained in:
Simonas 2026-06-19 11:44:00 +03:00
parent a861c00cfa
commit 8dd54779f7
4 changed files with 18 additions and 8 deletions

View File

@ -1,4 +1,4 @@
import type { TemplateContext } from './types';
import { type TemplateContext, toShellPath } from './types';
import { COMMAND_DESCRIPTIONS } from '../../browse/src/commands';
import { SNAPSHOT_FLAGS } from '../../browse/src/snapshot';
@ -106,7 +106,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="${toShellPath(ctx.paths.browseDir)}/browse"
if [ -x "$B" ]; then
echo "READY: $B"
else

View File

@ -1,4 +1,4 @@
import type { TemplateContext } from './types';
import { type TemplateContext, toShellPath } from './types';
import { AI_SLOP_BLACKLIST, OPENAI_HARD_REJECTIONS, OPENAI_LITMUS_CHECKS } from './constants';
export function generateDesignReviewLite(ctx: TemplateContext): string {
@ -792,7 +792,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="${toShellPath(ctx.paths.designDir)}/design"
if [ -x "$D" ]; then
echo "DESIGN_READY: $D"
else
@ -800,7 +800,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="${toShellPath(ctx.paths.browseDir)}/browse"
if [ -x "$B" ]; then
echo "BROWSE_READY: $B"
else
@ -837,7 +837,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="${toShellPath(ctx.paths.designDir)}/design"
[ -x "$D" ] && echo "DESIGN_READY" || echo "DESIGN_NOT_AVAILABLE"
\`\`\`

View File

@ -1,4 +1,4 @@
import type { TemplateContext } from './types';
import { type TemplateContext, toShellPath } from './types';
/**
* {{MAKE_PDF_SETUP}} emits the shell preamble that resolves $P to the
@ -19,7 +19,7 @@ _ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
P=""
[ -n "$MAKE_PDF_BIN" ] && [ -x "$MAKE_PDF_BIN" ] && P="$MAKE_PDF_BIN"
[ -z "$P" ] && [ -n "$_ROOT" ] && [ -x "$_ROOT/${ctx.paths.localSkillRoot}/make-pdf/dist/pdf" ] && P="$_ROOT/${ctx.paths.localSkillRoot}/make-pdf/dist/pdf"
[ -z "$P" ] && P="$HOME${ctx.paths.makePdfDir.replace(/^~/, '')}/pdf"
[ -z "$P" ] && P="${toShellPath(ctx.paths.makePdfDir)}/pdf"
if [ -x "$P" ]; then
echo "MAKE_PDF_READY: $P"
alias _p_="$P" # shellcheck alias helper (not exported)

View File

@ -50,6 +50,16 @@ function buildHostPaths(): Record<string, HostPaths> {
export const HOST_PATHS: Record<string, HostPaths> = buildHostPaths();
/**
* Render a HostPaths binary dir as a shell-expandable absolute path.
* Claude-style dirs are `~`-rooted (e.g. `~/.claude/skills/gstack/browse/dist`)
* and expand via `$HOME`; env-var hosts already carry an absolute `$GSTACK_*`
* value, so they pass through untouched prepending `$HOME` would double it.
*/
export function toShellPath(dir: string): string {
return dir.startsWith('~') ? `$HOME${dir.slice(1)}` : dir;
}
import type { Model } from '../models';
export type { Model } from '../models';