* 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:
@@ -25,7 +25,7 @@ const PR_FULL_SUITES = ['unit', 'integration', 'security'];
|
||||
// Relative to repoRoot. We walk these to discover SUT-internal requires so that
|
||||
// a change to a deep helper propagates through re-export chains to tests.
|
||||
const SOURCE_TREES = [
|
||||
'get-shit-done/bin/lib',
|
||||
'gsd-core/bin/lib',
|
||||
'bin/lib',
|
||||
'bin',
|
||||
'scripts',
|
||||
|
||||
@@ -217,6 +217,18 @@ extract_and_check_blobs() {
|
||||
local found=0
|
||||
local line_num=0
|
||||
|
||||
# Skip binary-by-content files (e.g. adversarial parser fixtures with embedded
|
||||
# non-UTF8 / NUL bytes). They cannot meaningfully carry base64-obfuscated *text*,
|
||||
# and feeding NUL bytes through the per-line scanner spawns thousands of bogus
|
||||
# `base64 -d` subprocesses (the "ignored null byte" warnings) — slow enough to
|
||||
# blow the job timeout on a large diff. `grep -Iq .` treats a binary file as
|
||||
# non-matching, so this skips it with a coverage notice (collect_files already
|
||||
# filters binary *extensions*; this catches binary *content* in text extensions).
|
||||
if ! LC_ALL=C grep -Iq . "$file" 2>/dev/null; then
|
||||
echo "SKIP: $file (binary content — base64 text scan not applicable)" >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
while IFS= read -r line; do
|
||||
line_num=$((line_num + 1))
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ const { renderGithubReleaseNotes } = require('./github-release-notes.cjs');
|
||||
const {
|
||||
compareSemverCore,
|
||||
isStableTripletSemver,
|
||||
} = require('../../get-shit-done/bin/lib/semver-compare.cjs');
|
||||
const { packageName, repoSlug: defaultRepoSlug } = require('../../get-shit-done/bin/lib/package-identity.cjs');
|
||||
} = require('../../gsd-core/bin/lib/semver-compare.cjs');
|
||||
const { packageName, repoSlug: defaultRepoSlug } = require('../../gsd-core/bin/lib/package-identity.cjs');
|
||||
|
||||
function parseArgs(argv) {
|
||||
const opts = {
|
||||
|
||||
@@ -4,7 +4,7 @@ const cp = require('node:child_process');
|
||||
const path = require('node:path');
|
||||
|
||||
const { parseFragment } = require('./parse.cjs');
|
||||
const { packageName, repoSlug: defaultRepoSlug } = require('../../get-shit-done/bin/lib/package-identity.cjs');
|
||||
const { packageName, repoSlug: defaultRepoSlug } = require('../../gsd-core/bin/lib/package-identity.cjs');
|
||||
|
||||
const SECTION_ORDER = ['Fixed', 'Added', 'Changed', 'Deprecated', 'Removed', 'Security'];
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ const OPT_OUT_LABEL = 'no-changelog';
|
||||
// fragment or an explicit opt-out label. Test/CI/docs/lock files do not.
|
||||
const USER_FACING_PREFIXES = [
|
||||
'bin/',
|
||||
'get-shit-done/',
|
||||
'gsd-core/',
|
||||
'agents/',
|
||||
'commands/',
|
||||
'hooks/',
|
||||
|
||||
@@ -4,7 +4,7 @@ const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
const aliasesPath = path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'command-aliases.cjs');
|
||||
const aliasesPath = path.join(ROOT, 'gsd-core', 'bin', 'lib', 'command-aliases.cjs');
|
||||
|
||||
function fail(message) {
|
||||
process.stderr.write(`${message}\n`);
|
||||
@@ -37,37 +37,37 @@ const families = [
|
||||
{
|
||||
commandAliases: 'STATE_COMMAND_ALIASES',
|
||||
subcommands: 'STATE_SUBCOMMANDS',
|
||||
routerPath: path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'state-command-router.cjs'),
|
||||
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'state-command-router.cjs'),
|
||||
},
|
||||
{
|
||||
commandAliases: 'VERIFY_COMMAND_ALIASES',
|
||||
subcommands: 'VERIFY_SUBCOMMANDS',
|
||||
routerPath: path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'verify-command-router.cjs'),
|
||||
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'verify-command-router.cjs'),
|
||||
},
|
||||
{
|
||||
commandAliases: 'INIT_COMMAND_ALIASES',
|
||||
subcommands: 'INIT_SUBCOMMANDS',
|
||||
routerPath: path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'init-command-router.cjs'),
|
||||
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'init-command-router.cjs'),
|
||||
},
|
||||
{
|
||||
commandAliases: 'PHASE_COMMAND_ALIASES',
|
||||
subcommands: 'PHASE_SUBCOMMANDS',
|
||||
routerPath: path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'phase-command-router.cjs'),
|
||||
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'phase-command-router.cjs'),
|
||||
},
|
||||
{
|
||||
commandAliases: 'PHASES_COMMAND_ALIASES',
|
||||
subcommands: 'PHASES_SUBCOMMANDS',
|
||||
routerPath: path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'phases-command-router.cjs'),
|
||||
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'phases-command-router.cjs'),
|
||||
},
|
||||
{
|
||||
commandAliases: 'VALIDATE_COMMAND_ALIASES',
|
||||
subcommands: 'VALIDATE_SUBCOMMANDS',
|
||||
routerPath: path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'validate-command-router.cjs'),
|
||||
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'validate-command-router.cjs'),
|
||||
},
|
||||
{
|
||||
commandAliases: 'ROADMAP_COMMAND_ALIASES',
|
||||
subcommands: 'ROADMAP_SUBCOMMANDS',
|
||||
routerPath: path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'roadmap-command-router.cjs'),
|
||||
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'roadmap-command-router.cjs'),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ const RULES = [
|
||||
},
|
||||
{
|
||||
name: 'TS runtime sources (ADR-457 build-at-publish)',
|
||||
// src/*.cts compiles into get-shit-done/bin/lib/*.cjs; a source-only edit must
|
||||
// src/*.cts compiles into gsd-core/bin/lib/*.cjs; a source-only edit must
|
||||
// still trigger the migrated module's tests (otherwise CI silently skips them).
|
||||
match: path => path.startsWith('src/') || path === 'tsconfig.build.json',
|
||||
tests: [
|
||||
@@ -55,7 +55,7 @@ const RULES = [
|
||||
{
|
||||
name: 'installer and package layout',
|
||||
match: path => path.startsWith('bin/') ||
|
||||
path.startsWith('get-shit-done/bin/') ||
|
||||
path.startsWith('gsd-core/bin/') ||
|
||||
path.includes('install') ||
|
||||
path.includes('release-tarball-smoke'),
|
||||
fullMatrix: true,
|
||||
@@ -122,7 +122,7 @@ const RULES = [
|
||||
},
|
||||
{
|
||||
name: 'workflow prompts',
|
||||
match: path => path.startsWith('get-shit-done/workflows/'),
|
||||
match: path => path.startsWith('gsd-core/workflows/'),
|
||||
tests: [
|
||||
'tests/workflow-compat.test.cjs',
|
||||
'tests/workflow-size-budget.test.cjs',
|
||||
@@ -153,7 +153,7 @@ const RULES = [
|
||||
},
|
||||
{
|
||||
name: 'configuration',
|
||||
match: path => /config|configuration|model-catalog|model-profile/.test(path),
|
||||
match: path => ['config', 'configuration', 'model-catalog', 'model-profile'].some(k => path.includes(k)),
|
||||
tests: [
|
||||
'tests/config.test.cjs',
|
||||
'tests/config-get-default.test.cjs',
|
||||
@@ -208,7 +208,19 @@ function parseArgs(argv) {
|
||||
|
||||
function splitFiles(value) {
|
||||
if (!value) return [];
|
||||
return value.split(/[,\s]+/).map(v => v.trim()).filter(Boolean);
|
||||
const SEPARATORS = new Set([',', ' ', '\t', '\n', '\r', '\f', '\v']);
|
||||
const tokens = [];
|
||||
let current = '';
|
||||
for (const ch of value) {
|
||||
if (SEPARATORS.has(ch)) {
|
||||
if (current) tokens.push(current);
|
||||
current = '';
|
||||
} else {
|
||||
current += ch;
|
||||
}
|
||||
}
|
||||
if (current) tokens.push(current);
|
||||
return tokens.map(v => v.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
function changedFiles(args) {
|
||||
@@ -231,6 +243,9 @@ function addAll(set, values) {
|
||||
for (const value of values) set.add(value);
|
||||
}
|
||||
|
||||
const WINDOWS_HINTS = ['windows', 'path', 'shell', 'workflow', 'install', 'hook'];
|
||||
const isWindowsHint = s => WINDOWS_HINTS.some(k => s.toLowerCase().includes(k));
|
||||
|
||||
function classify(files) {
|
||||
const targeted = new Set();
|
||||
const windows = new Set();
|
||||
@@ -239,9 +254,9 @@ function classify(files) {
|
||||
let fullMatrix = false;
|
||||
|
||||
for (const file of files) {
|
||||
if (/^(bin|get-shit-done|agents|commands|docs|hooks|tests|scripts)\//.test(file) ||
|
||||
/^package(-lock)?\.json$/.test(file) ||
|
||||
/^tsconfig.*\.json$/.test(file) ||
|
||||
if (['bin/', 'gsd-core/', 'agents/', 'commands/', 'docs/', 'hooks/', 'tests/', 'scripts/'].some(p => file.startsWith(p)) ||
|
||||
file === 'package.json' || file === 'package-lock.json' ||
|
||||
(file.startsWith('tsconfig') && file.endsWith('.json')) ||
|
||||
file.startsWith('.github/workflows/') ||
|
||||
file.startsWith('.github/rulesets/')) {
|
||||
codeChanged = true;
|
||||
@@ -250,7 +265,7 @@ function classify(files) {
|
||||
if (file.startsWith('tests/') && file.endsWith('.test.cjs')) {
|
||||
targeted.add(file);
|
||||
fullMatrix = true;
|
||||
if (/windows|path|shell|workflow|install|hook/i.test(file)) {
|
||||
if (isWindowsHint(file)) {
|
||||
windows.add(file);
|
||||
}
|
||||
}
|
||||
@@ -272,7 +287,7 @@ function classify(files) {
|
||||
targetedTests.push('unit');
|
||||
}
|
||||
|
||||
const windowsTests = existingTests([...new Set([...windows, ...targetedTests.filter(t => /windows|path|shell|workflow|install|hook/i.test(t))])].sort());
|
||||
const windowsTests = existingTests([...new Set([...windows, ...targetedTests.filter(isWindowsHint)])].sort());
|
||||
|
||||
return {
|
||||
code_changed: codeChanged,
|
||||
|
||||
@@ -54,7 +54,7 @@ function executionContextRefs(content) {
|
||||
const trailingProse = line.length > token.length;
|
||||
const normalized = token
|
||||
.replace(/^@(?:~|\$HOME)\//, '')
|
||||
.replace(/^(?:\.claude\/)?(?:get-shit-done\/)?/, '');
|
||||
.replace(/^(?:\.claude\/)?(?:gsd-core\/)?/, '');
|
||||
refs.push({ token, normalized, trailingProse });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,11 @@ const path = require('node:path');
|
||||
|
||||
const COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'gsd');
|
||||
const SEARCH_DIRS = [
|
||||
path.join(__dirname, '..', 'get-shit-done', 'bin', 'lib'),
|
||||
path.join(__dirname, '..', 'get-shit-done', 'workflows'),
|
||||
path.join(__dirname, '..', 'get-shit-done', 'references'),
|
||||
path.join(__dirname, '..', 'get-shit-done', 'templates'),
|
||||
path.join(__dirname, '..', 'get-shit-done', 'contexts'),
|
||||
path.join(__dirname, '..', 'gsd-core', 'bin', 'lib'),
|
||||
path.join(__dirname, '..', 'gsd-core', 'workflows'),
|
||||
path.join(__dirname, '..', 'gsd-core', 'references'),
|
||||
path.join(__dirname, '..', 'gsd-core', 'templates'),
|
||||
path.join(__dirname, '..', 'gsd-core', 'contexts'),
|
||||
path.join(__dirname, '..', 'commands', 'gsd'),
|
||||
path.join(__dirname, '..', 'agents'),
|
||||
path.join(__dirname, '..', 'hooks'),
|
||||
|
||||
@@ -34,19 +34,19 @@ const FAMILIES = [
|
||||
},
|
||||
{
|
||||
name: 'workflows',
|
||||
dir: path.join(ROOT, 'get-shit-done', 'workflows'),
|
||||
dir: path.join(ROOT, 'gsd-core', 'workflows'),
|
||||
filter: (f) => f.endsWith('.md'),
|
||||
toName: (f) => f,
|
||||
},
|
||||
{
|
||||
name: 'references',
|
||||
dir: path.join(ROOT, 'get-shit-done', 'references'),
|
||||
dir: path.join(ROOT, 'gsd-core', 'references'),
|
||||
filter: (f) => f.endsWith('.md'),
|
||||
toName: (f) => f,
|
||||
},
|
||||
{
|
||||
name: 'cli_modules',
|
||||
dir: path.join(ROOT, 'get-shit-done', 'bin', 'lib'),
|
||||
dir: path.join(ROOT, 'gsd-core', 'bin', 'lib'),
|
||||
filter: (f) => f.endsWith('.cjs'),
|
||||
toName: (f) => f,
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*
|
||||
* `deriveIdentity(pkg)` is the pure core: it turns a parsed package.json into
|
||||
* the coordinate record every consumer needs. The generated runtime module
|
||||
* `get-shit-done/bin/lib/package-identity.cjs` bakes those values at build
|
||||
* `gsd-core/bin/lib/package-identity.cjs` bakes those values at build
|
||||
* time, because the installed tree carries only a synthetic
|
||||
* `{"type":"commonjs"}` package.json (no `.name`) — so a runtime
|
||||
* `require('package.json').name` resolves to `undefined` (the #378 bug this
|
||||
@@ -115,7 +115,7 @@ function main() {
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
||||
const out = path.join(__dirname, '..', 'get-shit-done', 'bin', 'lib', 'package-identity.cjs');
|
||||
const out = path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'package-identity.cjs');
|
||||
fs.writeFileSync(out, render(deriveIdentity(pkg)));
|
||||
process.stdout.write(`wrote ${path.relative(path.join(__dirname, '..'), out)}\n`);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ const path = require('path');
|
||||
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
const COMMANDS_DIR = path.join(ROOT, 'commands', 'gsd');
|
||||
const GSD_ROOT = path.join(ROOT, 'get-shit-done');
|
||||
const GSD_ROOT = path.join(ROOT, 'gsd-core');
|
||||
|
||||
const {
|
||||
CANONICAL_TOOLS,
|
||||
|
||||
156
scripts/lint-legacy-dir-name.cjs
Normal file
156
scripts/lint-legacy-dir-name.cjs
Normal file
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* lint-legacy-dir-name.cjs
|
||||
*
|
||||
* Prevents accidental re-introduction of the bare legacy directory token
|
||||
* `get-shit-done` now that the package has been renamed to `gsd-core` (#604).
|
||||
*
|
||||
* The forbidden token is constructed by splitting across the concat operator
|
||||
* so this guard script cannot flag itself:
|
||||
* const FORBIDDEN = 'get-shit' + '-done';
|
||||
*
|
||||
* Regex: FORBIDDEN + '(?!-\\w)' — allows any hyphenated slug variant
|
||||
* (get-shit-done-OLD, get-shit-done-classic, get-shit-done-cli, etc.)
|
||||
* while forbidding the bare word boundary that would indicate a stale
|
||||
* directory reference. This is the exact pattern that would appear in a
|
||||
* path like `~/.claude/get-shit-done/` or `'get-shit-done'` in code.
|
||||
*
|
||||
* Allowlist:
|
||||
* - CHANGELOG.md (historical record; reviewed manually)
|
||||
* - .changeset/ (ephemeral release-note fragments; consumed into CHANGELOG on
|
||||
* release — like CHANGELOG, not swept by rename PRs)
|
||||
* - Translated READMEs: README.ja-JP.md, README.ko-KR.md, README.pt-BR.md, README.zh-CN.md
|
||||
* - Locale-specific docs dirs: docs/ja-JP/, docs/ko-KR/, docs/pt-BR/, docs/zh-CN/
|
||||
* - Lines containing the marker `gsd-allow-legacy-name` (intentional uses)
|
||||
* - Binary files (detected by NUL byte scan)
|
||||
* - This guard script itself (by path)
|
||||
*
|
||||
* Exit 0 if no violations; exit 1 if any are found (with stderr diagnostics).
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { execFileSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Constructed with split to avoid self-match when this script is scanned.
|
||||
const FORBIDDEN = 'get-shit' + '-done';
|
||||
const FORBIDDEN_RE = new RegExp(FORBIDDEN + '(?!-\\w)', 'gi');
|
||||
|
||||
const ALLOW_MARKER = 'gsd-allow-legacy-name';
|
||||
const SELF_PATH = path.resolve(__filename);
|
||||
// GSD_LINT_LEGACY_REPO_ROOT is used by tests to redirect the guard to a
|
||||
// temporary fixture git repo without touching the real working tree.
|
||||
const REPO_ROOT = process.env.GSD_LINT_LEGACY_REPO_ROOT
|
||||
? path.resolve(process.env.GSD_LINT_LEGACY_REPO_ROOT)
|
||||
: path.resolve(__dirname, '..');
|
||||
|
||||
const ALLOWLIST_FILES = new Set([
|
||||
'CHANGELOG.md',
|
||||
'README.ja-JP.md',
|
||||
'README.ko-KR.md',
|
||||
'README.pt-BR.md',
|
||||
'README.zh-CN.md',
|
||||
]);
|
||||
|
||||
const ALLOWLIST_DIR_PREFIXES = [
|
||||
// Pending changeset fragments are ephemeral release-note stubs consumed into
|
||||
// CHANGELOG on release — like CHANGELOG itself, they should not be swept by
|
||||
// rename PRs and may legitimately contain the legacy token in historical prose.
|
||||
'.changeset/',
|
||||
'docs/ja-JP/',
|
||||
'docs/ko-KR/',
|
||||
'docs/pt-BR/',
|
||||
'docs/zh-CN/',
|
||||
];
|
||||
|
||||
function isAllowlisted(relPath) {
|
||||
if (ALLOWLIST_FILES.has(relPath)) return true;
|
||||
for (const prefix of ALLOWLIST_DIR_PREFIXES) {
|
||||
if (relPath.startsWith(prefix)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isBinary(fullPath) {
|
||||
// Read a small chunk and check for NUL bytes.
|
||||
let fd;
|
||||
try {
|
||||
fd = fs.openSync(fullPath, 'r');
|
||||
const buf = Buffer.allocUnsafe(512);
|
||||
const bytesRead = fs.readSync(fd, buf, 0, 512, 0);
|
||||
return buf.subarray(0, bytesRead).includes(0);
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
if (fd != null) {
|
||||
try { fs.closeSync(fd); } catch { /* best-effort */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enumerate tracked files via git ls-files so only committed/staged source is checked.
|
||||
let trackedFiles;
|
||||
try {
|
||||
trackedFiles = execFileSync('git', ['ls-files'], { cwd: REPO_ROOT, encoding: 'utf8' })
|
||||
.split('\n')
|
||||
.map((f) => f.trim())
|
||||
.filter(Boolean);
|
||||
} catch (err) {
|
||||
process.stderr.write('ERROR lint-legacy-dir-name: git ls-files failed: ' + err.message + '\n');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const violations = [];
|
||||
|
||||
for (const relPath of trackedFiles) {
|
||||
if (isAllowlisted(relPath)) continue;
|
||||
|
||||
const fullPath = path.join(REPO_ROOT, relPath);
|
||||
|
||||
// Skip this guard script itself.
|
||||
if (path.resolve(fullPath) === SELF_PATH) continue;
|
||||
|
||||
if (isBinary(fullPath)) continue;
|
||||
|
||||
let content;
|
||||
try {
|
||||
content = fs.readFileSync(fullPath, 'utf8');
|
||||
} catch {
|
||||
// Unreadable files (permissions, etc.) — skip silently.
|
||||
continue;
|
||||
}
|
||||
|
||||
const lines = content.split('\n');
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
// Skip lines that carry the explicit allow marker.
|
||||
if (line.includes(ALLOW_MARKER)) continue;
|
||||
|
||||
FORBIDDEN_RE.lastIndex = 0;
|
||||
let match;
|
||||
while ((match = FORBIDDEN_RE.exec(line)) !== null) {
|
||||
violations.push({
|
||||
file: relPath,
|
||||
line: i + 1,
|
||||
col: match.index + 1,
|
||||
text: match[0],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (violations.length === 0) {
|
||||
process.stdout.write('ok lint-legacy-dir-name: ' + trackedFiles.length + ' tracked file(s) checked, 0 violations\n');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
process.stderr.write('\nERROR lint-legacy-dir-name: ' + violations.length + ' violation(s) found\n\n');
|
||||
for (const v of violations) {
|
||||
process.stderr.write(' ' + v.file + ':' + v.line + ':' + v.col + ' — ' + JSON.stringify(v.text) + '\n');
|
||||
}
|
||||
process.stderr.write('\n');
|
||||
process.stderr.write('Fix: rename to gsd-core, or add `gsd-allow-legacy-name` marker on the line if the\n');
|
||||
process.stderr.write(' use is intentional (migration modules, tests, guard, changeset).\n\n');
|
||||
process.exit(1);
|
||||
@@ -8,7 +8,7 @@
|
||||
* not that the runtime behavior is correct.
|
||||
*
|
||||
* ALLOWED:
|
||||
* - require('../get-shit-done/bin/lib/foo.cjs') -- runs the module, not text inspection
|
||||
* - require('../gsd-core/bin/lib/foo.cjs') -- runs the module, not text inspection
|
||||
* - readFileSync on .md / .json / .txt files -- product-content or config output
|
||||
* - Files annotated: // allow-test-rule: <reason>
|
||||
*
|
||||
@@ -28,16 +28,16 @@ const TESTS_DIR = path.join(__dirname, '..', 'tests');
|
||||
const ALLOW_ANNOTATION = /\/\/\s*allow-test-rule:\s*\S/;
|
||||
|
||||
// Matches constant definitions that hold a .cjs path in a SOURCE directory.
|
||||
// Requires a source-dir indicator ('bin', 'lib', 'get-shit-done') to avoid
|
||||
// Requires a source-dir indicator ('bin', 'lib', 'gsd-core') to avoid
|
||||
// flagging temp files like path.join(tmpDir, 'example.cjs').
|
||||
// const CONFIG_PATH = path.join(__dirname, '..', 'get-shit-done', 'bin', 'lib', 'config-schema.cjs');
|
||||
const CJS_PATH_CONST_RE = /(?:const|let|var)\s+(\w+)\s*=\s*path\.join\s*\([^)]*(?:'bin'|"bin"|'lib'|"lib"|'get-shit-done'|"get-shit-done")[^)]*['"][^'"]*\.cjs['"]/gm;
|
||||
// const CONFIG_PATH = path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'config-schema.cjs');
|
||||
const CJS_PATH_CONST_RE = /(?:const|let|var)\s+(\w+)\s*=\s*path\.join\s*\([^)]*(?:'bin'|"bin"|'lib'|"lib"|'gsd-core'|"gsd-core")[^)]*['"][^'"]*\.cjs['"]/gm;
|
||||
|
||||
// Matches readFileSync with a named variable as first arg
|
||||
const READ_WITH_CONST_RE = /readFileSync\s*\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*,/gm;
|
||||
|
||||
// Matches readFileSync with an inline path.join(.cjs) as first arg
|
||||
const READ_WITH_INLINE_CJS_RE = /readFileSync\s*\([^,)]*path\.join\s*\([^)]*(?:'bin'|"bin"|'lib'|"lib"|'get-shit-done'|"get-shit-done")[^)]*['"][^'"]*\.cjs['"]/;
|
||||
const READ_WITH_INLINE_CJS_RE = /readFileSync\s*\([^,)]*path\.join\s*\([^)]*(?:'bin'|"bin"|'lib'|"lib"|'gsd-core'|"gsd-core")[^)]*['"][^'"]*\.cjs['"]/;
|
||||
|
||||
/**
|
||||
* #2962-class violations: raw text matching against process output or file
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
/**
|
||||
* Drift-guard lint for the Package Identity seam (issue #498).
|
||||
*
|
||||
* The seam (`get-shit-done/bin/lib/package-identity.cjs`, derived from
|
||||
* The seam (`get-shit-done/bin/lib/package-identity.cjs`, derived from // gsd-allow-legacy-name
|
||||
* package.json) is the single source of GSD's published coordinates. Many
|
||||
* runtime surfaces still carry a literal copy of those coordinates because
|
||||
* they cannot `require()` the seam at runtime: the bash launcher snippet (and
|
||||
@@ -19,7 +19,7 @@
|
||||
* literal is reported until updated. That is what turns a repoint into a
|
||||
* one-line change with mechanical enforcement.
|
||||
*
|
||||
* Scope: the runtime/code surface (bin/, hooks/, scripts/, get-shit-done/).
|
||||
* Scope: the runtime/code surface (bin/, hooks/, scripts/, get-shit-done/). // gsd-allow-legacy-name
|
||||
* Pure-prose docs and localized READMEs are intentionally out of scope.
|
||||
*/
|
||||
|
||||
@@ -27,11 +27,11 @@ const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
// A GSD package coordinate: a scoped npm name whose package part contains
|
||||
// "get-shit-done" (so @opengsd/gsd-sdk and unrelated scopes never match).
|
||||
const PACKAGE_RE = /@[A-Za-z0-9._-]+\/[A-Za-z0-9._-]*get-shit-done[A-Za-z0-9._-]*/g;
|
||||
// "get-shit-done" (so @opengsd/gsd-sdk and unrelated scopes never match). // gsd-allow-legacy-name
|
||||
const PACKAGE_RE = /@[A-Za-z0-9._-]+\/[A-Za-z0-9._-]*get-shit-done[A-Za-z0-9._-]*/g; // gsd-allow-legacy-name
|
||||
// A GSD repo slug, only inside a GitHub URL context so it never overlaps the
|
||||
// scoped package literal above. The `.git` suffix is trimmed before compare.
|
||||
const SLUG_RE = /(?:github\.com[/:]|raw\.githubusercontent\.com\/)([A-Za-z0-9._-]+\/[A-Za-z0-9._-]*get-shit-done[A-Za-z0-9._-]*)/g;
|
||||
const SLUG_RE = /(?:github\.com[/:]|raw\.githubusercontent\.com\/)([A-Za-z0-9._-]+\/[A-Za-z0-9._-]*get-shit-done[A-Za-z0-9._-]*)/g; // gsd-allow-legacy-name
|
||||
|
||||
function lineOf(text, index) {
|
||||
let line = 1;
|
||||
@@ -62,14 +62,14 @@ function findCoordinateDrift(text, { packageName, repoSlug }) {
|
||||
}
|
||||
|
||||
// Directories scanned, relative to repo root.
|
||||
const SCAN_DIRS = ['bin', 'hooks', 'scripts', 'get-shit-done'];
|
||||
const SCAN_DIRS = ['bin', 'hooks', 'scripts', 'gsd-core'];
|
||||
const SCAN_EXT = new Set(['.js', '.cjs', '.sh', '.md']);
|
||||
// Files exempt because they ARE the source of truth / the tooling that defines
|
||||
// the coordinate patterns. The generated seam holds the correct value by
|
||||
// construction; the generator and this lint carry regex/templates, not stray
|
||||
// literals.
|
||||
const EXEMPT = new Set([
|
||||
path.join('get-shit-done', 'bin', 'lib', 'package-identity.cjs'),
|
||||
path.join('gsd-core', 'bin', 'lib', 'package-identity.cjs'),
|
||||
path.join('scripts', 'generate-package-identity.cjs'),
|
||||
path.join('scripts', 'lint-package-identity-drift.cjs'),
|
||||
]);
|
||||
@@ -98,7 +98,7 @@ function walk(dir, acc) {
|
||||
* annotated with the repo-relative file path.
|
||||
*/
|
||||
function scanRepo(root) {
|
||||
const seam = require(path.join(root, 'get-shit-done', 'bin', 'lib', 'package-identity.cjs'));
|
||||
const seam = require(path.join(root, 'gsd-core', 'bin', 'lib', 'package-identity.cjs'));
|
||||
const expected = { packageName: seam.packageName, repoSlug: seam.repoSlug };
|
||||
const violations = [];
|
||||
for (const dir of SCAN_DIRS) {
|
||||
|
||||
@@ -52,6 +52,6 @@ process.stderr.write(`ERROR shell-projection-drift: inline serialized shim build
|
||||
for (const match of matches) {
|
||||
process.stderr.write(` - ${match.label}\n`);
|
||||
}
|
||||
process.stderr.write('Route shim/wrapper rendering through get-shit-done/bin/lib/shell-command-projection.cjs\n');
|
||||
process.stderr.write('Route shim/wrapper rendering through gsd-core/bin/lib/shell-command-projection.cjs\n');
|
||||
process.stderr.write('Safe subprocess execution via spawnSync/execFileSync is intentionally allowed.\n');
|
||||
process.exit(1);
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const PROFILES_MODULE = path.join(__dirname, '..', 'get-shit-done', 'bin', 'lib', 'install-profiles.cjs');
|
||||
const PROFILES_MODULE = path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'install-profiles.cjs');
|
||||
const { PROFILES, loadSkillsManifest, resolveProfile } = require(PROFILES_MODULE);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
/**
|
||||
* lint-test-file-count.cjs — max 2 test files per production module.
|
||||
*
|
||||
* Scans sdk/src/query/, sdk/src/, get-shit-done/bin/lib/, bin/ for production
|
||||
* Scans sdk/src/query/, sdk/src/, gsd-core/bin/lib/, bin/ for production
|
||||
* modules, then counts matching test files in tests/ and sdk/src (recursive). Cap is 2
|
||||
* (primary + one integration). Over-limit clusters must be in the allowlist with the
|
||||
* EXACT set of test filenames grandfathered (identity ratchet via allowlist-ratchet.cjs).
|
||||
@@ -22,7 +22,7 @@ const ROOT = path.join(__dirname, '..');
|
||||
const PROD_DIRS = [
|
||||
path.join(ROOT, 'sdk', 'src', 'query'),
|
||||
path.join(ROOT, 'sdk', 'src'),
|
||||
path.join(ROOT, 'get-shit-done', 'bin', 'lib'),
|
||||
path.join(ROOT, 'gsd-core', 'bin', 'lib'),
|
||||
path.join(ROOT, 'bin'),
|
||||
];
|
||||
const TEST_DIRS = [
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* "has_work": "true"|"false",
|
||||
* "matrix": {
|
||||
* "include": [
|
||||
* { "name": "<module>", "mutate": "get-shit-done/bin/lib/<module>.cjs", "tests": "<space-joined test files>" },
|
||||
* { "name": "<module>", "mutate": "gsd-core/bin/lib/<module>.cjs", "tests": "<space-joined test files>" },
|
||||
* ...
|
||||
* ]
|
||||
* }
|
||||
@@ -39,27 +39,27 @@ const { readFileSync } = require('fs');
|
||||
// only ever produce survived mutants — so we scope strictly to these 6.
|
||||
const COVERED = {
|
||||
'context-utilization': {
|
||||
cjs: 'get-shit-done/bin/lib/context-utilization.cjs',
|
||||
cjs: 'gsd-core/bin/lib/context-utilization.cjs',
|
||||
tests: [
|
||||
'tests/context-utilization.property.test.cjs',
|
||||
],
|
||||
},
|
||||
'prompt-budget': {
|
||||
cjs: 'get-shit-done/bin/lib/prompt-budget.cjs',
|
||||
cjs: 'gsd-core/bin/lib/prompt-budget.cjs',
|
||||
tests: [
|
||||
'tests/prompt-budget.property.test.cjs',
|
||||
'tests/prompt-budget.unit.test.cjs',
|
||||
],
|
||||
},
|
||||
frontmatter: {
|
||||
cjs: 'get-shit-done/bin/lib/frontmatter.cjs',
|
||||
cjs: 'gsd-core/bin/lib/frontmatter.cjs',
|
||||
tests: [
|
||||
'tests/frontmatter.property.test.cjs',
|
||||
'tests/frontmatter.unit.test.cjs',
|
||||
],
|
||||
},
|
||||
'adr-parser': {
|
||||
cjs: 'get-shit-done/bin/lib/adr-parser.cjs',
|
||||
cjs: 'gsd-core/bin/lib/adr-parser.cjs',
|
||||
tests: [
|
||||
'tests/adr-parser.property.test.cjs',
|
||||
'tests/adr-parser.test.cjs',
|
||||
@@ -67,13 +67,13 @@ const COVERED = {
|
||||
],
|
||||
},
|
||||
'config-schema': {
|
||||
cjs: 'get-shit-done/bin/lib/config-schema.cjs',
|
||||
cjs: 'gsd-core/bin/lib/config-schema.cjs',
|
||||
tests: [
|
||||
'tests/config-schema.property.test.cjs',
|
||||
],
|
||||
},
|
||||
'active-workstream-store': {
|
||||
cjs: 'get-shit-done/bin/lib/active-workstream-store.cjs',
|
||||
cjs: 'gsd-core/bin/lib/active-workstream-store.cjs',
|
||||
tests: [
|
||||
'tests/active-workstream-store.test.cjs',
|
||||
'tests/active-workstream-store.unit.test.cjs',
|
||||
|
||||
@@ -73,13 +73,17 @@ ALLOWLIST=(
|
||||
'tests/security.test.cjs'
|
||||
'tests/prompt-injection-scan.test.cjs'
|
||||
'tests/verify.test.cjs'
|
||||
'get-shit-done/bin/lib/security.cjs'
|
||||
'gsd-core/bin/lib/security.cjs'
|
||||
'hooks/gsd-prompt-guard.js'
|
||||
'hooks/gsd-read-injection-scanner.js'
|
||||
'tests/read-injection-scanner.test.cjs'
|
||||
'tests/security-prompt-injection.test.cjs'
|
||||
'tests/fixtures/adversarial/security/'
|
||||
'SECURITY.md'
|
||||
# These files contain intentional injection examples / security-model prose
|
||||
# and are not attack vectors — they explain/demonstrate injection patterns.
|
||||
'TEST-EXAMPLES.md'
|
||||
'docs/explanation/security-model.md'
|
||||
)
|
||||
|
||||
is_allowlisted() {
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
* Non-interactive: --local --claude flags skip all prompts.
|
||||
*
|
||||
* Workflow-body checks (Cycle 3 — informational):
|
||||
* - Scans all installed get-shit-done/workflows/*.md for /gsd:<known-cmd>
|
||||
* - Scans all installed gsd-core/workflows/*.md for /gsd:<known-cmd>
|
||||
* colon-namespace leaks (WORKFLOW_BODY_COLON_LEAK).
|
||||
* This check populates result.details with counters but does NOT return a
|
||||
* failure code by default; it is informational until enforcement is enabled.
|
||||
@@ -45,7 +45,7 @@ const { execFileSync, spawnSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const { PACKAGE_NAME } = require('../get-shit-done/bin/lib/package-identity.cjs');
|
||||
const { PACKAGE_NAME } = require('../gsd-core/bin/lib/package-identity.cjs');
|
||||
// 120 s proved too tight on Windows GitHub-hosted runners: cold-cache
|
||||
// `npm install -g` with a 1499-file tarball took ~120 s exactly, causing
|
||||
// spawnSync to fire SIGTERM and return { status: null, stdout: '', stderr: '' }
|
||||
@@ -182,15 +182,15 @@ function findInstallerBin(installPrefix) {
|
||||
* Structured parser — only inspects individual lines; never regexes on the
|
||||
* whole-file string. Two recognised forms (in priority order):
|
||||
*
|
||||
* 1. @-import line: `@~/.claude/get-shit-done/workflows/<name>.md`
|
||||
* 2. Inline mention: any line containing `~/.claude/get-shit-done/workflows/<name>.md`
|
||||
* 1. @-import line: `@~/.claude/gsd-core/workflows/<name>.md`
|
||||
* 2. Inline mention: any line containing `~/.claude/gsd-core/workflows/<name>.md`
|
||||
* (takes the LAST occurrence so conditional-dispatch files resolve to the
|
||||
* default / unconditional branch, e.g. discuss-phase.md)
|
||||
*
|
||||
* Returns the bare workflow filename (e.g. `"discuss-phase.md"`) or null.
|
||||
*/
|
||||
function parseWorkflowRef(mdContent) {
|
||||
const WORKFLOW_PREFIX = 'get-shit-done/workflows/';
|
||||
const WORKFLOW_PREFIX = 'gsd-core/workflows/';
|
||||
let atImportResult = null;
|
||||
let lastInlineResult = null;
|
||||
|
||||
@@ -433,7 +433,7 @@ function runSmoke({
|
||||
// Verify expected dirs were created
|
||||
const expectedDirs = [
|
||||
path.join(fixtureDir, '.claude', 'commands'),
|
||||
path.join(fixtureDir, '.claude', 'get-shit-done'),
|
||||
path.join(fixtureDir, '.claude', 'gsd-core'),
|
||||
];
|
||||
for (const dir of expectedDirs) {
|
||||
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
|
||||
@@ -472,9 +472,9 @@ function runSmoke({
|
||||
|
||||
let workflowPath = null;
|
||||
if (workflowName) {
|
||||
// Workflow files live at get-shit-done/workflows/<name> in the package.
|
||||
// Workflow files live at gsd-core/workflows/<name> in the package.
|
||||
// Some live in subdirectories; try flat first then scan once.
|
||||
const flat = path.join(pkg, 'get-shit-done', 'workflows', workflowName);
|
||||
const flat = path.join(pkg, 'gsd-core', 'workflows', workflowName);
|
||||
workflowPath = fs.existsSync(flat) ? flat : null;
|
||||
|
||||
if (!workflowPath) {
|
||||
@@ -503,7 +503,7 @@ function runSmoke({
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// --- Workflow-body checks (informational — #3668 not yet fixed) ----------
|
||||
const workflowsDir = path.join(pkg, 'get-shit-done', 'workflows');
|
||||
const workflowsDir = path.join(pkg, 'gsd-core', 'workflows');
|
||||
const installedCmdNames = readInstalledCmdNames(pkg);
|
||||
|
||||
let workflowsScanned = 0;
|
||||
|
||||
@@ -26,7 +26,7 @@ const { execFileSync } = require('child_process');
|
||||
|
||||
const SUITES = ['all', 'unit', 'integration', 'install', 'security', 'slow'];
|
||||
|
||||
// ADR-457 build-at-publish: get-shit-done/bin/lib/*.cjs is generated from
|
||||
// ADR-457 build-at-publish: gsd-core/bin/lib/*.cjs is generated from
|
||||
// src/*.cts and gitignored, so on a clean checkout (fresh CI, before any build)
|
||||
// the artifact is absent — yet test files require it. This is the universal
|
||||
// chokepoint every test path funnels through (test:unit, --files-from, direct
|
||||
@@ -38,7 +38,7 @@ const SUITES = ['all', 'unit', 'integration', 'install', 'security', 'slow'];
|
||||
// modules migrate into src/.
|
||||
function ensureBuiltArtifacts() {
|
||||
const root = join(__dirname, '..');
|
||||
const sentinel = join(root, 'get-shit-done', 'bin', 'lib', 'semver-compare.cjs');
|
||||
const sentinel = join(root, 'gsd-core', 'bin', 'lib', 'semver-compare.cjs');
|
||||
if (existsSync(sentinel)) return;
|
||||
const tscBin = require.resolve('typescript/bin/tsc');
|
||||
execFileSync(process.execPath, [tscBin, '-p', join(root, 'tsconfig.build.json')], {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
/**
|
||||
* strip-prose-atrefs.cjs
|
||||
*
|
||||
* Removes redundant @~/.claude/get-shit-done/ path tokens from prose lines
|
||||
* Removes redundant @~/.claude/gsd-core/ path tokens from prose lines
|
||||
* in <process> and <context> blocks. The path is already declared in
|
||||
* <execution_context> where it actually loads the file. Prose copies are
|
||||
* inert and add ~900 tokens/invocation of dead weight.
|
||||
@@ -30,7 +30,7 @@ const DRY_RUN = process.argv.includes('--dry-run');
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
const COMMANDS_DIR = path.join(ROOT, 'commands', 'gsd');
|
||||
|
||||
const AT_PATH_PATTERN = /@(?:~|\$HOME)\/.+?get-shit-done\/[^\s`\)]+/;
|
||||
const AT_PATH_PATTERN = /@(?:~|\$HOME)\/.+?gsd-core\/[^\s`\)]+/;
|
||||
const mkAtRe = () => new RegExp(AT_PATH_PATTERN.source, 'g');
|
||||
|
||||
function transformLine(line) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
/**
|
||||
* sync-runtime-launcher.cjs
|
||||
*
|
||||
* Idempotent transform: for every get-shit-done/workflows/*.md (and subdirs),
|
||||
* Idempotent transform: for every gsd-core/workflows/*.md (and subdirs),
|
||||
* rewrite all bash/sh/shell fenced blocks to:
|
||||
* 1. Strip ALL old resolver forms from every bash block (GSD_TOOLS=,
|
||||
* GSD_SDK=, the if/elif/else/fi resolver, _GSD_SHIM_NAME=, and any
|
||||
@@ -19,7 +19,7 @@
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const WORKFLOWS_DIR = path.join(__dirname, '..', 'get-shit-done', 'workflows');
|
||||
const WORKFLOWS_DIR = path.join(__dirname, '..', 'gsd-core', 'workflows');
|
||||
const SNIPPET_FILE = path.join(WORKFLOWS_DIR, '_runtime-launcher.snippet.sh');
|
||||
|
||||
// Read canonical preamble (full content of snippet file)
|
||||
|
||||
Reference in New Issue
Block a user