fix(install): copy sdk/shared/model-catalog.json + resolve chain in CJS (#3288) (#3293)

* test: reproduce model-catalog MODULE_NOT_FOUND in install layout (#3288)

Tests A/B/C exercise the install-layout regression introduced by #3230:
- A: confirms the old 3-level __dirname path fails when sdk/shared/ is absent
- B: confirms the new co-located bin/shared/ path resolves correctly (RED)
- C: confirms install() copies model-catalog.json to co-located path (RED)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(install): copy sdk/shared/model-catalog.json + resolve chain in CJS (#3288)

Two-part fix for the CRITICAL regression introduced by #3230:

Install-side: bin/install.js now copies sdk/shared/model-catalog.json into
get-shit-done/bin/shared/model-catalog.json immediately after the main
get-shit-done/ copy step. Every runtime install (Claude Code, Codex, OpenCode,
Gemini, etc.) now includes this file in the payload.

CJS-side: model-catalog.cjs replaces the brittle single-path require with a
resolve-chain that checks candidates in order:
  1. get-shit-done/bin/shared/model-catalog.json (co-located, preferred post-install)
  2. sdk/shared/model-catalog.json (source-repo dev path, legacy fallback)
  3. GSD_MODEL_CATALOG env override (custom deployments / test harnesses)

When no candidate resolves, throws with a diagnostic listing all tried paths
(PRED.k301 — throw must include candidate paths for debuggability).

REFACTOR audit: three other __dirname traversals in bin/lib/ were inspected:
  - core.cjs:1242 (3 levels up → agents/) — safe; agents/ IS copied to targetDir
  - profile-output.cjs:547,740 (2 levels up → templates/) — safe; templates/ is
    inside get-shit-done/ and IS copied

Only model-catalog.cjs traversed outside the installed payload.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* changeset: pr=3293 for #3288

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(model-catalog): narrow catch to missing-file errors; clear env in test (#3288)

Two CR findings from PR #3293 review:

1. model-catalog.cjs catch block swallowed ALL errors — malformed JSON,
   permission errors, and other real failures were silently absorbed into
   the fallback chain. Now only MODULE_NOT_FOUND (with matching path in
   message) and ENOENT are treated as recoverable; any other error is
   rethrown immediately.

2. test beforeEach saved GSD_EXPLICIT_CONFIG_DIR but didn't clear it —
   a CI-set value could leak into install() and redirect the install to
   an unexpected directory, making test C non-deterministic. Added
   `delete process.env.GSD_EXPLICIT_CONFIG_DIR` to beforeEach.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-05-09 07:40:17 -04:00
committed by GitHub
parent 90b33d050c
commit b08e57c92d
4 changed files with 317 additions and 1 deletions

View File

@@ -7951,6 +7951,28 @@ function install(isGlobal, runtime = 'claude') {
failures.push('get-shit-done');
}
// #3288 — Copy sdk/shared/model-catalog.json into the get-shit-done payload
// at the co-located path that model-catalog.cjs resolves first:
// get-shit-done/bin/shared/model-catalog.json
//
// The install copies get-shit-done/ but NOT sdk/ — the CJS module's legacy
// path (3 levels up → sdk/shared/) therefore resolves to a non-existent
// location in every post-install layout. Copying the catalog alongside the
// CJS files ensures require() succeeds without needing sdk/ to exist.
const modelCatalogSrc = path.join(src, 'sdk', 'shared', 'model-catalog.json');
const modelCatalogDest = path.join(skillDest, 'bin', 'shared', 'model-catalog.json');
if (fs.existsSync(modelCatalogSrc)) {
fs.mkdirSync(path.dirname(modelCatalogDest), { recursive: true });
fs.copyFileSync(modelCatalogSrc, modelCatalogDest);
if (verifyFileInstalled(modelCatalogDest, 'get-shit-done/bin/shared/model-catalog.json')) {
console.log(` ${green}✓${reset} Installed get-shit-done/bin/shared/model-catalog.json`);
} else {
failures.push('get-shit-done/bin/shared/model-catalog.json');
}
} else {
failures.push('sdk/shared/model-catalog.json (source missing)');
}
// Copy agents to agents directory.
// Skipped under --minimal: gsd-* subagent descriptions are eagerly loaded
// into the runtime's Agent tool schema, costing ~6k tokens per turn even