chore(#604): rename get-shit-done/ runtime directory to gsd-core/ (#615)

* chore(#604): rename get-shit-done/ runtime directory to gsd-core/

Renames the installed runtime directory `get-shit-done/` to `gsd-core/` so the
on-disk name matches the package (`@opengsd/gsd-core`), repo, and binary
(`gsd-tools`). The npm package name and binary are unchanged; npx/npm consumers
are unaffected.

Mechanical (bulk, ~90% of the diff):
- `git mv get-shit-done gsd-core`
- Swept path/identifier references across the repo via
  `perl -pe 's/get-shit-done(?!-\w)/gsd-core/g'`. The negative lookahead
  preserves the five legitimate slug variants that are NOT the directory:
  get-shit-done-{OLD,cc,classic,cli,redux} (old package/repo names).
- Build/manifest wiring: package.json (bin, files, coverage globs),
  tsconfig.build.json (outDir), ~86 .gitignore build-output entries,
  stryker.config.mjs, scan-ignore files, install.js path strings.
- Frozen (not rewritten): CHANGELOG.md history; translated docs
  (README.<locale>.md and docs/{ja-JP,ko-KR,pt-BR,zh-CN}/).

New logic (review here):
- src/installer-migrations/003-rename-get-shit-done-to-gsd-core.cts: a proper
  ADR-0008 installer migration. On upgrade it walks the legacy
  `~/.claude/get-shit-done/` tree, classifies each file via the prior install
  manifest, and emits remove-managed / backup-and-remove for managed files
  while PRESERVING unknown user-added files. Symlink-safe (skips a symlinked
  root and symlinked entries; bounds-checks every path under configDir). The
  framework rolls back on install failure. Emptied dirs may remain (framework
  has no recursive dir-removal primitive) — documented.
- scripts/lint-legacy-dir-name.cjs: CI regression guard forbidding the bare
  `get-shit-done` directory token (split token to avoid self-match; case-
  insensitive; `(?!-\w)` lookahead allows the slug variants; allowlists
  CHANGELOG, translated docs, and `gsd-allow-legacy-name` marker lines).
  Wired into the lint-tests CI job.
- Restored scripts/lint-package-identity-drift.cjs detection regexes (the
  mechanical sweep had wrongly rewritten the old-name patterns it exists to
  detect) and marked them as intentional legacy references.
- TDD tests for the migration and the guard; do.md slash-command guard regex
  tightened so a `/gsd-core/bin` path segment is not mistaken for a command;
  changeset + docs/installer-migrations.md row added.

Breaking: the installed runtime path moves `~/.claude/get-shit-done/` ->
`~/.claude/gsd-core/`. Migration 003 removes the stale legacy dir's managed
files (preserving user files) on upgrade. Users with custom hooks/configs
hardcoding the old path must update them.

Closes #604

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): unsweep pending changesets + allowlist injection-example docs

CI fixes for the rename PR:
- Do not sweep pending .changeset/*.md (ephemeral release-note fragments,
  like CHANGELOG); reverted those body edits so 5 pre-existing malformed
  fragments (missing type/pr) no longer enter the PR diff and trip docs-lint.
  Allowlisted .changeset/ in the legacy-name guard accordingly.
- Allowlisted TEST-EXAMPLES.md and docs/explanation/security-model.md in
  prompt-injection-scan.sh: they contain intentional injection examples /
  security-model prose; the path-reference rewrites are kept.

CodeQL alerts on this PR are pre-existing (alert lines unchanged by this PR;
none in the new migration/guard) and are out of scope for the rename.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): resolve CodeQL alerts surfaced on this PR

The rename diff touched files carrying pre-existing CodeQL findings; per the
no-pre-existing-dismissal rule, fixing every surfaced alert rather than waving
them off. All behavior-preserving:

- scripts/ci-test-scope.cjs: build the config-path match from string
  .includes() instead of a RegExp over an arg-derived value (js/regex-injection).
- src/profile-output.cts: escape backslashes before pipe-escaping desc/safeName
  so the table-cell escape is complete (js/incomplete-sanitization).
- tests/{bug-2643,bug-2808,docs-parity-live-registry}: two-pass HTML-comment
  strip so a bare/unclosed `<!--` cannot survive (js/incomplete-multi-character-sanitization).
- tests/inline-plan-threshold: drop the no-op `\s`->`\s` identity replace,
  keep the meaningful POSIX-class conversion (js/identity-replacement).

Verified: build:lib green; the touched test files + ci-test-scope + profile-output
suites pass; lint:legacy-name clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): correctly resolve remaining CodeQL alerts (regex-injection + sanitization)

The prior commit's fixes for two alerts were ineffective:
- ci-test-scope.cjs js/regex-injection: the alert is the CLI-arg-derived `file`
  reaching static regex `.test(file)` calls (not the config rule). Removed ALL
  regex over file/t — startsWith/includes/=== string checks + an isWindowsHint
  helper — so there is no regex sink for the tainted value.
- js/incomplete-multi-character-sanitization (3 test files): a single
  `.replace(/<!--...-->/g,'')` can let `<!--` re-form. Replaced with a fixpoint
  loop (replace until stable) plus a final bare-opener strip.

Verified: no regex over file/t remains; ci-test-scope + the 3 test suites pass;
lint:legacy-name clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): make ci-test-scope + comment-strippers regex-free to clear CodeQL

CodeQL flags the regex PATTERNS syntactically (regex-injection on the
--files arg split; incomplete-multi-character-sanitization on the <!--...-->
replace), so loop fixes do not satisfy it. Made these paths regex-free:
- ci-test-scope.cjs splitFiles: char-by-char separator tokenizer (no /[,\\s]+/).
- 3 test files: indexOf/slice HTML-comment stripper (no .replace(/<!--/)).
Behavior preserved; ci-test-scope + the 3 suites pass; guard clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): unblock security base64 scan on the large rename diff

The security job hit its 10m timeout: base64-scan.sh choked on the binary
test fixture tests/feat-3594-parser-property-style.test.cjs (embedded NUL/
non-UTF8 bytes -> thousands of bogus blobs + "ignored null byte" warnings),
and the ~800-file rename diff is slow to scan regardless.

- scripts/base64-scan.sh: skip binary-by-content files (grep -Iq .) — they
  can't carry base64-obfuscated *text* and feeding NUL bytes through the
  per-line scanner is pathologically slow. collect_files already filtered
  binary *extensions*; this catches binary *content* in text extensions.
- .github/workflows/security-scan.yml: raise the security job timeout 10m->30m
  to accommodate very large diffs (the scan itself is unchanged).

Verified locally: scan skips the fixture, 0 "ignored null byte" warnings,
0 findings, exit 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): sweep get-shit-done refs introduced by merging next

The branch was updated with next (#614/#384/#618 etc.), which reference the
get-shit-done/ dir (still named that on next). Swept the stale references in
the merged files to gsd-core so the rename stays consistent and lint:legacy-name
passes:
- commands/gsd/discuss-phase.md (runtime-launcher shim paths)
- src/core.cts (getAgentsDir layout comments)
- tests/bug-384-agents-runtime-aware.test.cjs (require path to runtime lib)

Verified: guard 0 violations; build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): exclude gsd-core/ path segments from bug-3683 command cross-ref invariant

The #614 runtime-launcher shim added to discuss-phase.md references
`${_GSD_RUNTIME_ROOT}/gsd-core/bin/...`. bug-3683's REF_PATTERN excluded path-y
refs only via lookbehind, but `}` precedes `/gsd-core/` in the shim, so it
mis-read the directory path as a dangling `/gsd-core` command ref (same class as
the #604 bug-2954 fix). Added a trailing `(?![\w-]*\/)` so `/gsd-<x>/...` path
segments are not treated as slash-command references.

Verified locally on BOTH platforms before pushing:
- mac (node 26) full suite: 0 failures
- gsd-test-runner (linux, node22 image) full suite: 0 failures
- bug-3683 + bug-2954 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): lazily resolve findProjectRoot in gsd-tools (harden flaky CI)

CI intermittently failed state.test's gsd-tools subprocess with
"findProjectRoot is not a function" (flip-flopping across legs; not reproducible
on mac full suite, gsd-test linux full suite, test:unit, or state.test x8).
findProjectRoot is a re-export from core.cjs (sourced from project-root.cjs);
binding it via destructure at module-load can be undefined under a load-ordering
edge. Resolve it lazily at call time via a small wrapper so the lookup happens
after core.cjs is fully initialized.

Verified green on BOTH platforms before pushing:
- mac (node 26) full suite: 0 failures
- gsd-test-runner (linux, node22) full suite: 0 failures
- state.test.cjs: 106/106; gsd-tools loads cleanly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): allowlist verification-patterns.md placeholder examples in secret scan

The rename git-mv'd references/verification-patterns.md into gsd-core/, pulling
it into the secret-scan diff. It documents stub/placeholder RED-FLAG env-var
examples (illustrative Stripe test-key / database-URL / API-key placeholders) —
not real credentials. Added it to .secretscanignore with the strict annotation,
mirroring the existing gsd-core/workflows/plan-phase.md exception.

Verified locally: secret-scan-lint --strict OK; secret-scan --diff origin/next
exits 0 with 0 findings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-02 18:35:29 -04:00
committed by GitHub
parent b177c1704f
commit 463cffd894
845 changed files with 3316 additions and 2373 deletions

View File

@@ -18,7 +18,7 @@ const {
projectCodexHookTomlCommand,
shellHookOmitsBashRunner,
buildLocalShellHookCommand,
} = require('../get-shit-done/bin/lib/shell-command-projection.cjs');
} = require('../gsd-core/bin/lib/shell-command-projection.cjs');
// Bidirectional GSD slash-command namespace transformer (#3583).
// Required at module scope so the command list can be computed once per install
@@ -30,7 +30,7 @@ const {
} = require(path.join(__dirname, '..', 'scripts', 'fix-slash-commands.cjs'));
const {
resolveAntigravityGlobalDir,
} = require('../get-shit-done/bin/lib/runtime-homes.cjs');
} = require('../gsd-core/bin/lib/runtime-homes.cjs');
/**
* Runtimes that register hyphen-form `name:` per #2808 AND copy agent bodies
@@ -75,7 +75,7 @@ const dim = '\x1b[2m';
const reset = '\x1b[0m';
// Codex config.toml constants
const GSD_CODEX_MARKER = '# GSD Agent Configuration \u2014 managed by get-shit-done installer';
const GSD_CODEX_MARKER = '# GSD Agent Configuration \u2014 managed by gsd-core installer';
const GSD_CODEX_HOOKS_OWNERSHIP_PREFIX = '# GSD codex_hooks ownership: ';
// Codex's hook-enabling feature flag (issue #3566). Codex itself marks
// `codex_hooks` as a `legacy_key` in codex-rs/features/src/legacy.rs; the
@@ -92,7 +92,7 @@ function isCodexHooksFeatureKey(key) {
}
// Copilot instructions marker constants
const GSD_COPILOT_INSTRUCTIONS_MARKER = '<!-- GSD Configuration \u2014 managed by get-shit-done installer -->';
const GSD_COPILOT_INSTRUCTIONS_MARKER = '<!-- GSD Configuration \u2014 managed by gsd-core installer -->';
const GSD_COPILOT_INSTRUCTIONS_CLOSE_MARKER = '<!-- /GSD Configuration -->';
// GSD-managed files under hooks/lib/ (helpers required by gsd-*.sh hooks).
@@ -136,11 +136,11 @@ const pkg = require('../package.json');
// #2517 — runtime-aware tier resolution shared with core.cjs.
// Hoisted to top with absolute __dirname-based paths so `gsd install codex` works
// when invoked via npm global install (cwd is the user's project, not the gsd repo
// root). Inline `require('../get-shit-done/...')` from inside install functions
// root). Inline `require('../gsd-core/...')` from inside install functions
// works only because Node resolves it relative to the install.js file regardless
// of cwd, but keeping the require at the top makes the dependency explicit and
// surfaces resolution failures at process start instead of at first install call.
const _gsdLibDir = path.join(__dirname, '..', 'get-shit-done', 'bin', 'lib');
const _gsdLibDir = path.join(__dirname, '..', 'gsd-core', 'bin', 'lib');
const { MODEL_PROFILES: GSD_MODEL_PROFILES } = require(path.join(_gsdLibDir, 'model-profiles.cjs'));
const {
RUNTIME_PROFILE_MAP: GSD_RUNTIME_PROFILE_MAP,
@@ -163,7 +163,7 @@ function _getGsdEffortCatalog() {
const manifestPath = path.join(
__dirname,
'..',
'get-shit-done',
'gsd-core',
'bin',
'shared',
'config-defaults.manifest.json'
@@ -230,10 +230,10 @@ const {
const {
planLegacyCleanup,
applyLegacyCleanup,
} = require(path.join(__dirname, '..', 'get-shit-done', 'bin', 'lib', 'legacy-cleanup.cjs'));
} = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'legacy-cleanup.cjs'));
const {
updateCacheFileName,
} = require(path.join(__dirname, '..', 'get-shit-done', 'bin', 'lib', 'package-identity.cjs'));
} = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'package-identity.cjs'));
// Parse args
const args = process.argv.slice(2);
@@ -5234,14 +5234,14 @@ function installCodexConfig(targetDir, agentsSrc) {
const agents = [];
// Compute the Codex GSD install path (absolute, so subagents with empty $HOME work — #820)
const codexGsdPath = `${path.resolve(targetDir, 'get-shit-done').replace(/\\/g, '/')}/`;
const codexGsdPath = `${path.resolve(targetDir, 'gsd-core').replace(/\\/g, '/')}/`;
for (const file of agentEntries) {
let content = fs.readFileSync(path.join(agentsSrc, file), 'utf8');
// Replace full .claude/get-shit-done prefix so path resolves to the Codex
// Replace full .claude/gsd-core prefix so path resolves to the Codex
// GSD install before generic .claude → .codex conversion rewrites it.
content = content.replace(/~\/\.claude\/get-shit-done\//g, codexGsdPath);
content = content.replace(/\$HOME\/\.claude\/get-shit-done\//g, codexGsdPath);
content = content.replace(/~\/\.claude\/gsd-core\//g, codexGsdPath);
content = content.replace(/\$HOME\/\.claude\/gsd-core\//g, codexGsdPath);
// Route TOML emit through the same full Claude→Codex conversion pipeline
// used on the `.md` emit path (#2639). Covers: slash-command rewrites,
// $ARGUMENTS → {{GSD_ARGS}}, /clear removal, anchored and bare .claude/
@@ -6040,7 +6040,7 @@ function writeHermesCategoryDescription(categoryDir) {
*/
/**
* Single source of truth for user-owned artifacts inside get-shit-done/.
* Single source of truth for user-owned artifacts inside gsd-core/.
*
* These files are created/refreshed by user-facing workflows (e.g.
* /gsd-profile-user) and must be preserved across reinstalls. Critically, they
@@ -6053,7 +6053,7 @@ function writeHermesCategoryDescription(categoryDir) {
* both. Both preserveUserArtifacts call sites and writeManifest must agree on
* this list, which is why it lives here as a single constant.
*
* Paths are relative to the get-shit-done/ directory.
* Paths are relative to the gsd-core/ directory.
*/
const USER_OWNED_ARTIFACTS = ['USER-PROFILE.md'];
@@ -7186,8 +7186,8 @@ function uninstall(isGlobal, runtime = 'claude') {
}
}
// 2. Remove get-shit-done directory
const gsdDir = path.join(targetDir, 'get-shit-done');
// 2. Remove gsd-core directory
const gsdDir = path.join(targetDir, 'gsd-core');
if (fs.existsSync(gsdDir)) {
// Preserve user-generated files before wipe (#1423)
const userProfilePath = path.join(gsdDir, 'USER-PROFILE.md');
@@ -7195,14 +7195,14 @@ function uninstall(isGlobal, runtime = 'claude') {
fs.rmSync(gsdDir, { recursive: true });
removedCount++;
console.log(` ${green}✓${reset} Removed get-shit-done/`);
console.log(` ${green}✓${reset} Removed gsd-core/`);
// Restore user-generated files
if (preservedProfile) {
try {
fs.mkdirSync(gsdDir, { recursive: true });
fs.writeFileSync(userProfilePath, preservedProfile);
console.log(` ${green}✓${reset} Preserved get-shit-done/USER-PROFILE.md`);
console.log(` ${green}✓${reset} Preserved gsd-core/USER-PROFILE.md`);
} catch (err) {
console.error(` ${red}✗${reset} Failed to restore USER-PROFILE.md: ${err.message}`);
}
@@ -7359,7 +7359,7 @@ function uninstall(isGlobal, runtime = 'claude') {
if (config.permission[permType]) {
const keys = Object.keys(config.permission[permType]);
for (const key of keys) {
if (key.includes('get-shit-done')) {
if (key.includes('gsd-core')) {
delete config.permission[permType][key];
modified = true;
}
@@ -7400,7 +7400,7 @@ function uninstall(isGlobal, runtime = 'claude') {
if (config.permission[permType]) {
const keys = Object.keys(config.permission[permType]);
for (const key of keys) {
if (key.includes('get-shit-done')) {
if (key.includes('gsd-core')) {
delete config.permission[permType][key];
modified = true;
}
@@ -7509,7 +7509,7 @@ function parseJsonc(content) {
/**
* Configure OpenCode permissions to allow reading GSD reference docs
* This prevents permission prompts when GSD accesses the get-shit-done directory
* This prevents permission prompts when GSD accesses the gsd-core directory
* @param {boolean} isGlobal - Whether this is a global or local install
* @param {string|null} configDir - Resolved config directory when already known
*/
@@ -7555,8 +7555,8 @@ function configureOpencodePermissions(isGlobal = true, configDir = null) {
// Use ~ shorthand if it's in the default location, otherwise use full path
const defaultConfigDir = path.join(os.homedir(), '.config', 'opencode');
const gsdPath = opencodeConfigDir === defaultConfigDir
? '~/.config/opencode/get-shit-done/*'
: `${opencodeConfigDir.replace(/\\/g, '/')}/get-shit-done/*`;
? '~/.config/opencode/gsd-core/*'
: `${opencodeConfigDir.replace(/\\/g, '/')}/gsd-core/*`;
let modified = false;
@@ -7589,7 +7589,7 @@ function configureOpencodePermissions(isGlobal = true, configDir = null) {
/**
* Configure Kilo permissions to allow reading GSD reference docs
* This prevents permission prompts when GSD accesses the get-shit-done directory
* This prevents permission prompts when GSD accesses the gsd-core directory
* @param {boolean} isGlobal - Whether this is a global or local install
* @param {string|null} configDir - Resolved config directory when already known
*/
@@ -7629,8 +7629,8 @@ function configureKiloPermissions(isGlobal = true, configDir = null) {
// Use ~ shorthand if it's in the default location, otherwise use full path
const defaultConfigDir = path.join(os.homedir(), '.config', 'kilo');
const gsdPath = kiloConfigDir === defaultConfigDir
? '~/.config/kilo/get-shit-done/*'
: `${kiloConfigDir.replace(/\\/g, '/')}/get-shit-done/*`;
? '~/.config/kilo/gsd-core/*'
: `${kiloConfigDir.replace(/\\/g, '/')}/gsd-core/*`;
let modified = false;
@@ -7801,7 +7801,7 @@ function writeManifest(configDir, runtime = 'claude', options = {}) {
const isTrae = runtime === 'trae';
const isCline = runtime === 'cline';
const isHermes = runtime === 'hermes';
const gsdDir = path.join(configDir, 'get-shit-done');
const gsdDir = path.join(configDir, 'gsd-core');
const commandsDir = path.join(configDir, 'commands', 'gsd');
const opencodeCommandDir = path.join(configDir, 'command');
// Hermes nests GSD skills under skills/gsd/ as a single category (#2841).
@@ -7826,7 +7826,7 @@ function writeManifest(configDir, runtime = 'claude', options = {}) {
// as a "local patch" (bug #2771). Single source of truth:
// USER_OWNED_ARTIFACTS at top of file.
if (USER_OWNED_ARTIFACTS.includes(rel)) continue;
manifest.files['get-shit-done/' + rel] = hash;
manifest.files['gsd-core/' + rel] = hash;
}
// Record commands/gsd/ for any runtime that emits it (Gemini globally,
// Claude Code locally — see #2923). Manifest must reflect everything on
@@ -7931,7 +7931,7 @@ function writeManifest(configDir, runtime = 'claude', options = {}) {
function populatePristineDir({ packageSrc, pristineDir, modified, runtime, pathPrefix, isGlobal }) {
if (!modified || modified.length === 0) return 0;
// Modified paths come from manifest.files which can live under several
// install roots: get-shit-done/, commands/gsd/, command/, skills/, agents/,
// install roots: gsd-core/, commands/gsd/, command/, skills/, agents/,
// hooks/, plus runtime-specific root files (#3004 CR). Stage every
// top-level dir that actually contains a modified path; root-level files
// are copied directly without the transform pipeline (they don't need
@@ -8013,7 +8013,7 @@ function saveLocalPatches(configDir, pristineCtx) {
// that were incorrectly recorded so refreshes don't surface false patches warnings.
if (manifest.files) {
for (const artifact of USER_OWNED_ARTIFACTS) {
delete manifest.files[`get-shit-done/${artifact}`];
delete manifest.files[`gsd-core/${artifact}`];
}
}
@@ -8287,7 +8287,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
: targetDir.replace(process.cwd(), '.');
// Path prefix for file references in markdown content (e.g. gsd-tools.cjs).
// Replaces $HOME/.claude/ or ~/.claude/ so the result is <pathPrefix>get-shit-done/bin/...
// Replaces $HOME/.claude/ or ~/.claude/ so the result is <pathPrefix>gsd-core/bin/...
// For global installs: use $HOME/ so paths expand correctly inside double-quoted
// shell commands (~ does NOT expand inside double quotes, causing MODULE_NOT_FOUND).
// For local installs: use resolved absolute path (may be outside $HOME).
@@ -8414,7 +8414,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
// codexPreInstallAgentFiles — Set of gsd-*.{md,toml} filenames in agents/
// codexPreInstallAgentContents — Map<filename, Buffer> of pre-existing agent
// file bytes, enabling full content restore (not just deletion) on rollback.
// codexPreInstallVersionBytes — Buffer (or null) of get-shit-done/VERSION
// codexPreInstallVersionBytes — Buffer (or null) of gsd-core/VERSION
//
// These are referenced by restoreCodexSnapshot(), defined below inside the
// config block. Defining the variables here (outer scope) makes them
@@ -8466,7 +8466,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
}
}
}
const _preVersionPath = path.join(targetDir, 'get-shit-done', 'VERSION');
const _preVersionPath = path.join(targetDir, 'gsd-core', 'VERSION');
if (fs.existsSync(_preVersionPath)) {
try { codexPreInstallVersionBytes = fs.readFileSync(_preVersionPath); } catch (_) { /* best-effort */ }
}
@@ -8478,7 +8478,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
// must also trigger rollback so the caller is never left in a partially-installed state.
//
// _codexPreConfigRollback covers the four surfaces that can be mutated before
// config.toml is touched: skills/, agents/, get-shit-done/VERSION, and orphaned
// config.toml is touched: skills/, agents/, gsd-core/VERSION, and orphaned
// atomic-write temp files. It is safe to call before any writes have happened.
// The full restoreCodexSnapshot() (defined inside the config block) additionally
// handles config.toml, which is not yet touched at this point in the pipeline.
@@ -8535,8 +8535,8 @@ function install(isGlobal, runtime = 'claude', options = {}) {
}
} catch (_) { /* best-effort */ }
}
// get-shit-done/VERSION
const _earlyVersionPath = path.join(targetDir, 'get-shit-done', 'VERSION');
// gsd-core/VERSION
const _earlyVersionPath = path.join(targetDir, 'gsd-core', 'VERSION');
if (codexPreInstallVersionBytes !== null) {
try { fs.writeFileSync(_earlyVersionPath, codexPreInstallVersionBytes); } catch (_) { /* best-effort */ }
} else if (fs.existsSync(_earlyVersionPath)) {
@@ -8791,24 +8791,24 @@ function install(isGlobal, runtime = 'claude', options = {}) {
}
}
// Copy get-shit-done skill with path replacement
// Copy gsd-core skill with path replacement
// Preserve user-generated files before the wipe-and-copy so they survive re-install
const skillSrc = path.join(src, 'get-shit-done');
const skillDest = path.join(targetDir, 'get-shit-done');
const skillSrc = path.join(src, 'gsd-core');
const skillDest = path.join(targetDir, 'gsd-core');
const savedGsdArtifacts = preserveUserArtifacts(skillDest, USER_OWNED_ARTIFACTS);
copyWithPathReplacement(skillSrc, skillDest, pathPrefix, runtime, false, isGlobal);
restoreUserArtifacts(skillDest, savedGsdArtifacts);
if (verifyInstalled(skillDest, 'get-shit-done')) {
if (verifyInstalled(skillDest, 'gsd-core')) {
console.log(` ${green}✓${reset} Installed workflow assets`);
} else {
failures.push('get-shit-done');
failures.push('gsd-core');
}
// Copy shared manifests into the get-shit-done payload
// Copy shared manifests into the gsd-core payload
// at the co-located path that CJS modules resolve first:
// get-shit-done/bin/shared/*.json
// gsd-core/bin/shared/*.json
//
// This source now lives under get-shit-done/bin/shared in-repo.
// This source now lives under gsd-core/bin/shared in-repo.
const sharedPayloadFiles = [
'model-catalog.json',
'config-defaults.manifest.json',
@@ -8816,9 +8816,9 @@ function install(isGlobal, runtime = 'claude', options = {}) {
'runtime-aliases.manifest.json',
];
for (const fileName of sharedPayloadFiles) {
const sharedSrc = path.join(src, 'get-shit-done', 'bin', 'shared', fileName);
const sharedSrc = path.join(src, 'gsd-core', 'bin', 'shared', fileName);
const sharedDest = path.join(skillDest, 'bin', 'shared', fileName);
const displayPath = `get-shit-done/bin/shared/${fileName}`;
const displayPath = `gsd-core/bin/shared/${fileName}`;
if (fs.existsSync(sharedSrc)) {
fs.mkdirSync(path.dirname(sharedDest), { recursive: true });
fs.copyFileSync(sharedSrc, sharedDest);
@@ -8828,7 +8828,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
failures.push(displayPath);
}
} else {
failures.push(`get-shit-done/bin/shared/${fileName} (source missing)`);
failures.push(`gsd-core/bin/shared/${fileName} (source missing)`);
}
}
@@ -8980,7 +8980,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
// Copy CHANGELOG.md
const changelogSrc = path.join(src, 'CHANGELOG.md');
const changelogDest = path.join(targetDir, 'get-shit-done', 'CHANGELOG.md');
const changelogDest = path.join(targetDir, 'gsd-core', 'CHANGELOG.md');
if (fs.existsSync(changelogSrc)) {
fs.copyFileSync(changelogSrc, changelogDest);
if (verifyFileInstalled(changelogDest, 'CHANGELOG.md')) {
@@ -8991,7 +8991,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
}
// Write VERSION file
const versionDest = path.join(targetDir, 'get-shit-done', 'VERSION');
const versionDest = path.join(targetDir, 'gsd-core', 'VERSION');
fs.writeFileSync(versionDest, pkg.version);
if (verifyFileInstalled(versionDest, 'VERSION')) {
console.log(` ${green}✓${reset} Wrote VERSION (${pkg.version})`);
@@ -9225,7 +9225,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
// newly-created dirs (i.e. those not in the pre-install Set)
// agents/gsd-* — restore pre-existing files from content snapshot; remove
// newly-created files
// get-shit-done/VERSION — restore or remove
// gsd-core/VERSION — restore or remove
// *.tmp-* — best-effort cleanup of installer-owned atomic-write temps
//
// Safe to call multiple times (idempotent): each remove/write is guarded by
@@ -9324,8 +9324,8 @@ function install(isGlobal, runtime = 'claude', options = {}) {
} catch (_) { /* best-effort */ }
}
// 4. get-shit-done/VERSION
const _rollbackVersionPath = path.join(targetDir, 'get-shit-done', 'VERSION');
// 4. gsd-core/VERSION
const _rollbackVersionPath = path.join(targetDir, 'gsd-core', 'VERSION');
if (codexPreInstallVersionBytes !== null) {
try { fs.writeFileSync(_rollbackVersionPath, codexPreInstallVersionBytes); }
catch (_) { /* best-effort */ }
@@ -9544,7 +9544,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
if (isCopilot) {
// Generate copilot-instructions.md
const templatePath = path.join(targetDir, 'get-shit-done', 'templates', 'copilot-instructions.md');
const templatePath = path.join(targetDir, 'gsd-core', 'templates', 'copilot-instructions.md');
const instructionsPath = path.join(targetDir, 'copilot-instructions.md');
if (fs.existsSync(templatePath)) {
const template = fs.readFileSync(templatePath, 'utf8');
@@ -9580,10 +9580,10 @@ function install(isGlobal, runtime = 'claude', options = {}) {
const clinerules = [
'# GSD Core — Git. Ship. Done.',
'',
'- GSD workflows live in `get-shit-done/workflows/`. Load the relevant workflow when',
'- GSD workflows live in `gsd-core/workflows/`. Load the relevant workflow when',
' the user runs a `/gsd-*` command.',
'- GSD agents live in `agents/`. Use the matching agent when spawning subagents.',
'- GSD tools are at `get-shit-done/bin/gsd-tools.cjs`. Run with `node`.',
'- GSD tools are at `gsd-core/bin/gsd-tools.cjs`. Run with `node`.',
'- Planning artifacts live in `.planning/`. Never edit them outside a GSD workflow.',
'- Do not apply GSD workflows unless the user explicitly asks for them.',
'- When a GSD command triggers a deliverable (feature, fix, docs), offer the next',