v1.92.57.0 fix(hermes): plain skill-start fence that Tirith can resolve, so Hermes can start gstack skills (GST-882) (#3825)

* fix: plain skill-start fence that a static resolver can follow (GST-882)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: checked-in renders keep the lookup as plain lines; regenerate goldens, lower size pins (GST-882)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: Codex preamble expectation follows the plain start lookup (GST-882)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (runtime-root header documents the plain skill-start fence, GST-882)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: Step 11 review fixes: troubleshooting names the skill-start exception; parity test pins the start lookup to the later-fence root (GST-882)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: type the parity test's env so the test typecheck ratchet stays clean (GST-882)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: changelog fragment for GST-882

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: writing-style Codex fence expectation follows the plain start lookup (GST-882)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
Garry Tanandcapy-ai[bot] authored and GitHub committed 2026-11-02 05:02:04 +00:00
1 parent f5cb6ec1eb
commit e2d9c798b2
15 files changed
+225 -80

No files matched your search

@@ -1,6 +1,6 @@
import type { TemplateContext } from '../types';
import { getHostConfig } from '../../../hosts/index';
import { runtimeRootPrelude } from '../runtime-root';
import { skillStartLines } from '../runtime-root';
/**
* Preamble bootstrap (token-reduction Phase 1).
@@ -25,11 +25,10 @@ import { runtimeRootPrelude } from '../runtime-root';
*/
export function generatePreambleBash(ctx: TemplateContext): string {
const hostConfig = getHostConfig(ctx.host);
const runtimeRoot = hostConfig.usesEnvVars ? `${runtimeRootPrelude(ctx)}\n` : '';
const start = hostConfig.usesEnvVars ? skillStartLines(ctx) : null;
const runtimeRoot = start ? `${start.lookup}\n` : '';
const brainHealthFlag = ctx.host === 'gbrain' || ctx.host === 'hermes' ? ' --brain-health' : '';
const startCommand = hostConfig.usesEnvVars
? `"${ctx.paths.binDir}/gstack-skill-start"`
: `${ctx.paths.binDir}/gstack-skill-start`;
const startCommand = start ? start.command : `${ctx.paths.binDir}/gstack-skill-start`;
const entry = ['plan-design-review', 'plan-eng-review'].includes(ctx.skillName)
? `## Preamble (after scope gate)
+41 -5
View File
@@ -3,8 +3,9 @@
* bash finds gstack on Codex and every other `usesEnvVars` host.
*
* Those hosts run each fenced block in a fresh shell, so `GSTACK_ROOT`, `B`,
* `D` and `P` set by an earlier block are gone. The preamble's root resolution and
* the per-fence prelude come from `runtimeRootPrelude()`; `insertRuntimePreludes()`
* `D` and `P` set by an earlier block are gone. The preamble's skill-start fence
* comes from `skillStartLines()` and the per-fence prelude from
* `runtimeRootPrelude()`; `insertRuntimePreludes()`
* is the single post-render pass (gen-skill-docs, before rewriteInstallRoot)
* that puts it into every fence that uses these variables without assigning
* them. Claude output is returned unchanged.
@@ -13,6 +14,9 @@
* on a per-install render, the literal install root (no git call); otherwise the
* repo-local install, then the host's global root (`CODEX_HOME` on Codex). No
* root prints one line naming what was tried and the setup command, and exits.
* The skill-start fence differs (GST-882): an install-root render calls the
* literal install root's start script and ignores an exported `GSTACK_ROOT`, and
* with no root either render prints that line and lets the call fail.
*/
import { getHostConfig } from '../../hosts/index';
import { toShellPath, type TemplateContext } from './types';
@@ -37,13 +41,45 @@ const uses = (name: string) => new RegExp(`\\$\\{?${name}\\b`);
*/
function resolveRoot(ctx: TemplateContext): string {
const host = getHostConfig(ctx.host);
const fix = `Fix: ./setup --host ${host.name} from your gstack checkout; ./setup --status shows it.`;
const fix = installFix(ctx);
const exported = '[ -d "${GSTACK_ROOT:-/-}/bin" ]&&[ -d "$GSTACK_ROOT/lib" ]||';
if (ctx.installRoot) {
return `${exported}{ GSTACK_ROOT="${ctx.installRoot.replace(/\/+$/, '')}";[ -d "$GSTACK_ROOT/bin" ]||{ echo "gstack: no install found (tried $GSTACK_ROOT). ${fix}">&2;exit 1;};}`;
}
const global = host.name === 'codex' ? '${CODEX_HOME:-~/.codex}/skills/gstack' : `~/${host.globalRoot}`;
return `${exported}{ _r=$(git rev-parse --show-toplevel 2>/dev/null)/${host.localSkillRoot};[ -d "$_r/bin" ]||_r=${global};[ -d "$_r/bin" ]||{ echo "gstack: no install found (tried $_r). ${fix}">&2;exit 1;};GSTACK_ROOT=$_r;}`;
return `${exported}{ _r=$(git rev-parse --show-toplevel 2>/dev/null)/${host.localSkillRoot};[ -d "$_r/bin" ]||_r=${globalRoot(ctx)};[ -d "$_r/bin" ]||{ echo "gstack: no install found (tried $_r). ${fix}">&2;exit 1;};GSTACK_ROOT=$_r;}`;
}
const installFix = (ctx: TemplateContext) => `Fix: ./setup --host ${getHostConfig(ctx.host).name} from your gstack checkout; ./setup --status shows it.`;
const globalRoot = (ctx: TemplateContext) => {
const host = getHostConfig(ctx.host);
return host.name === 'codex' ? '${CODEX_HOME:-~/.codex}/skills/gstack' : `~/${host.globalRoot}`;
};
/**
* The skill-start fence on env-var hosts (GST-882). Hermes's Tirith blocks a
* start command it cannot resolve statically, a grouped fallback or a
* variable-selected path alike, and no allowlist entry clears that finding. An
* install-root render therefore names the literal start path; a checked-in
* render keeps resolveRoot's lookup order as plain lines. Either way a missing
* install prints the fix, and the call then fails.
*/
export function skillStartLines(ctx: TemplateContext): { lookup: string; command: string } {
const fix = installFix(ctx);
if (ctx.installRoot) {
const root = ctx.installRoot.replace(/\/+$/, '');
return {
lookup: `[ -x "${root}/bin/gstack-skill-start" ]||echo "gstack: no install found (tried ${root}). ${fix}">&2`,
command: `"${root}/bin/gstack-skill-start"`,
};
}
return {
lookup: [
`[ -d "\${GSTACK_ROOT:-/-}/lib" ]&&[ -d "$GSTACK_ROOT/bin" ]||GSTACK_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)/${getHostConfig(ctx.host).localSkillRoot}`,
`[ -d "$GSTACK_ROOT/bin" ]||GSTACK_ROOT=${globalRoot(ctx)}`,
`[ -d "$GSTACK_ROOT/bin" ]||echo "gstack: no install found (tried $GSTACK_ROOT). ${fix}">&2`,
].join('\n'),
command: '"$GSTACK_ROOT/bin/gstack-skill-start"',
};
}
const DERIVED: Record<string, string> = { BIN: 'bin', BROWSE: 'browse/dist', DESIGN: 'design/dist', MAKE_PDF: 'make-pdf/dist' };