chore(#556): retire orphaned CJS↔SDK hand-sync tooling (#559)

The @opengsd/gsd-sdk package boundary was retired (ADR-0174, #191/#192) and
the sdk/ tree is no longer tracked. ADR-0174's Supersedes table explicitly
records that the generator-based Shared-Module hand-sync lint is deleted as
part of that collapse. This removes the now-orphaned machinery it left behind:

- scripts/lint-shared-module-handsync.cjs — paired bin/lib/*.cjs files with
  sdk/src/**/*.ts sources that no longer exist; wired into no CI workflow or
  npm script (dead).
- scripts/shared-module-handsync-allowlist.json — the lint's allowlist; every
  entry pointed at a non-existent sdk/src source / generated artifact /
  freshness check.
- tests/lint-shared-module-handsync.test.cjs — tested the deleted lint.

Docs corrected to match:
- CONTRIBUTING.md — removed the "CJS↔SDK seam" instruction (it linked the
  already-deleted docs/agents/cjs-sdk-seam.md and told contributors to
  maintain the allowlist under a retired generator pattern).
- docs/prd/3524-cjs-sdk-hard-seam.md + docs/prd/README.md — marked the PRD
  Superseded by ADR-0174, matching the already-superseded ADR-3524.

Added tests/no-cjs-sdk-handsync-tooling.test.cjs as a regression guard so the
retired tooling stays removed and is not silently re-wired into package.json.

ADR-3524 is left in place (already Superseded by ADR-0174); runtime modules and
regression tests that cite it in comments keep resolving. No user-facing change.

Closes #556

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-05-31 20:28:57 -04:00
committed by GitHub
parent 84e9888d25
commit 9b5ee37364
7 changed files with 55 additions and 992 deletions

View File

@@ -1,388 +0,0 @@
#!/usr/bin/env node
'use strict';
/**
* Shared Module hand-sync drift lint — Phase 6 of #3524 (#3575).
*
* Scans get-shit-done/bin/lib/ for .cjs files and checks whether a matching
* TypeScript file exists in sdk/src/<name>.ts, sdk/src/query/<name>.ts, or
* sdk/src/<name>/index.ts (excluding *.generated.ts and *.test.ts).
*
* Allowlist entries are keyed by the (cjs, ts) PAIR. An entry with cjs
* `bin/lib/foo.cjs` and ts `sdk/src/foo.ts` only allow-throughs that exact
* pair — a sibling at `sdk/src/query/foo.ts` is still flagged.
*
* Cross-name pairs are also supported when declared in the allowlist
* (for example `verify.cjs` <-> `validate.ts`), so cooperating siblings are
* observable even when basenames differ.
*
* If a pair is found:
* - cooperatingSiblings (matching cjs + ts): accepted silently (exit 0).
* - migrateMeBacklog (matching cjs + ts): emits a WARNING only when
* --warn-all is set; otherwise the pair passes silently. Backlog
* pairs never fail CI.
* - Unlisted pairs (cjs or ts not on either list): ERROR — exit 1.
*
* Usage:
* node scripts/lint-shared-module-handsync.cjs
* node scripts/lint-shared-module-handsync.cjs --root /path/to/repo
* node scripts/lint-shared-module-handsync.cjs --warn-all
* node scripts/lint-shared-module-handsync.cjs --cjs-dir custom/bin/lib --sdk-src custom/sdk/src
*/
const fs = require('fs');
const path = require('path');
// ---------------------------------------------------------------------------
// Argument parsing
// ---------------------------------------------------------------------------
const args = process.argv.slice(2);
let ROOT = path.resolve(__dirname, '..');
let CJS_DIR = null; // resolved below
let SDK_SRC = null; // resolved below
let SDK_SRC_EXPLICIT = false;
let ALLOWLIST_OVERRIDE = null; // resolved below
let WARN_ALL = false;
let JSON_OUTPUT = false;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--root' && args[i + 1]) {
ROOT = path.resolve(args[++i]);
} else if (args[i] === '--cjs-dir' && args[i + 1]) {
CJS_DIR = path.resolve(args[++i]);
} else if (args[i] === '--sdk-src' && args[i + 1]) {
SDK_SRC = path.resolve(args[++i]);
SDK_SRC_EXPLICIT = true;
} else if (args[i] === '--allowlist' && args[i + 1]) {
ALLOWLIST_OVERRIDE = path.resolve(args[++i]);
} else if (args[i] === '--warn-all') {
WARN_ALL = true;
} else if (args[i] === '--json') {
JSON_OUTPUT = true;
}
}
if (!CJS_DIR) CJS_DIR = path.join(ROOT, 'get-shit-done', 'bin', 'lib');
if (!SDK_SRC) SDK_SRC = path.join(ROOT, 'sdk', 'src');
// ---------------------------------------------------------------------------
// Load allowlist
// When --root is given (e.g. in tests), prefer <ROOT>/scripts/allowlist.json
// so fixture trees can supply their own allowlist. Fall back to the copy
// co-located with this script (default production path).
// ---------------------------------------------------------------------------
const ALLOWLIST_PATH = ALLOWLIST_OVERRIDE
? ALLOWLIST_OVERRIDE
: fs.existsSync(path.join(ROOT, 'scripts', 'shared-module-handsync-allowlist.json'))
? path.join(ROOT, 'scripts', 'shared-module-handsync-allowlist.json')
: path.join(__dirname, 'shared-module-handsync-allowlist.json');
let allowlist;
try {
allowlist = JSON.parse(fs.readFileSync(ALLOWLIST_PATH, 'utf8'));
} catch (err) {
process.stderr.write(
`lint-shared-module-handsync: failed to read allowlist at ${ALLOWLIST_PATH}: ${err.message}\n`
);
process.exit(1);
}
/**
* Pair identity = `${cjs}::${ts}`. Keying on the pair (not just cjs)
* prevents an allowlisted entry from silently passing an unintended
* sibling at a different ts path with the same basename.
*
* @type {Set<string>} pair identities in cooperatingSiblings
*/
const cooperatingPairs = new Set(
(allowlist.cooperatingSiblings || []).map((e) => `${e.cjs}::${e.ts}`)
);
/** @type {Map<string, object>} pair identity -> entry for migrateMeBacklog */
const migrateMap = new Map(
(allowlist.migrateMeBacklog || []).map((e) => [`${e.cjs}::${e.ts}`, e])
);
/** @type {Map<string, Set<string>>} cjs rel path -> explicit declared ts rel paths */
const explicitTsByCjs = new Map();
for (const entry of [
...(allowlist.cooperatingSiblings || []),
...(allowlist.migrateMeBacklog || []),
]) {
if (!entry || typeof entry.cjs !== 'string' || typeof entry.ts !== 'string') continue;
if (!explicitTsByCjs.has(entry.cjs)) explicitTsByCjs.set(entry.cjs, new Set());
explicitTsByCjs.get(entry.cjs).add(entry.ts);
}
// ---------------------------------------------------------------------------
// Build SDK name index: name -> array of absolute TS paths
// (excludes *.generated.ts and *.test.ts)
// ---------------------------------------------------------------------------
function buildSdkIndex(sdkSrc) {
const index = new Map(); // name -> [absPath, ...]
function addEntry(name, absPath) {
if (!index.has(name)) index.set(name, []);
index.get(name).push(absPath);
}
function walk(dir) {
let entries;
try {
entries = fs.readdirSync(dir, { withFileTypes: true });
} catch (_) {
return;
}
for (const ent of entries) {
const abs = path.join(dir, ent.name);
if (ent.isDirectory()) {
walk(abs);
} else if (ent.isFile() && ent.name.endsWith('.ts') &&
!ent.name.endsWith('.generated.ts') &&
!ent.name.endsWith('.test.ts')) {
const rel = path.relative(sdkSrc, abs);
const parts = rel.split(path.sep);
// sdk/src/<name>.ts (direct child, not in a subdir)
if (parts.length === 1) {
const name = parts[0].slice(0, -3); // strip .ts
addEntry(name, abs);
}
// sdk/src/<name>/index.ts (one subdir deep, file is index.ts)
else if (parts.length === 2 && parts[1] === 'index.ts') {
const name = parts[0];
addEntry(name, abs);
}
// sdk/src/query/<name>.ts (exactly: query/<something>.ts)
else if (parts.length === 2 && parts[0] === 'query' && parts[1] !== 'index.ts') {
const name = parts[1].slice(0, -3); // strip .ts
addEntry(name, abs);
}
}
}
}
walk(sdkSrc);
return index;
}
// ---------------------------------------------------------------------------
// Scan CJS files (direct children only; exclude *.generated.cjs)
// ---------------------------------------------------------------------------
function scanCjsFiles(cjsDir) {
let entries;
try {
entries = fs.readdirSync(cjsDir, { withFileTypes: true });
} catch (err) {
process.stderr.write(
`lint-shared-module-handsync: cannot read CJS dir ${cjsDir}: ${err.message}\n`
);
process.exit(1);
}
return entries
.filter(
(e) =>
e.isFile() &&
e.name.endsWith('.cjs') &&
!e.name.endsWith('.generated.cjs')
)
.map((e) => ({
name: e.name.slice(0, -4), // strip .cjs
absPath: path.join(cjsDir, e.name),
}));
}
// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
function emitJson(payload) {
process.stdout.write(JSON.stringify(payload) + '\n');
}
function main() {
// Check that the directories exist
if (!fs.existsSync(CJS_DIR)) {
if (JSON_OUTPUT) {
emitJson({ ok: false, reason: 'cjs_dir_missing', path: CJS_DIR });
} else {
process.stderr.write(
`lint-shared-module-handsync: CJS dir not found: ${CJS_DIR}\n` +
` Pass --root <repo-root> or --cjs-dir <path> to override.\n`
);
}
process.exit(1);
}
if (!fs.existsSync(SDK_SRC)) {
// SDK tree was retired in ADR-0174 Phase 5.2. In default repo mode this
// lint becomes a no-op success; explicit --sdk-src remains fail-loud.
if (SDK_SRC_EXPLICIT) {
if (JSON_OUTPUT) {
emitJson({ ok: false, reason: 'sdk_src_missing', path: SDK_SRC });
} else {
process.stderr.write(
`lint-shared-module-handsync: SDK src dir not found: ${SDK_SRC}\n` +
` Pass --root <repo-root> or --sdk-src <path> to override.\n`
);
}
process.exit(1);
}
if (JSON_OUTPUT) {
emitJson({
ok: true,
reason: 'sdk_retired',
cooperatingCount: 0,
backlogCount: 0,
warnings: [],
});
} else {
process.stdout.write(
'ok lint-shared-module-handsync: SDK source tree retired; no hand-sync pairs to lint.\n'
);
}
process.exit(0);
}
const sdkIndex = buildSdkIndex(SDK_SRC);
const cjsFiles = scanCjsFiles(CJS_DIR);
const errors = [];
const warnings = [];
function candidateTsPathsFor(relCjs, name) {
const byName = sdkIndex.has(name)
? sdkIndex
.get(name)
.map((p) => path.relative(ROOT, p).replace(/\\/g, '/'))
: [];
const declared = explicitTsByCjs.has(relCjs)
? Array.from(explicitTsByCjs.get(relCjs))
: [];
const declaredExisting = declared.filter((relTs) => {
const abs = path.join(ROOT, relTs);
return (
relTs.endsWith('.ts') &&
!relTs.endsWith('.generated.ts') &&
!relTs.endsWith('.test.ts') &&
fs.existsSync(abs) &&
fs.statSync(abs).isFile()
);
});
return Array.from(new Set([...byName, ...declaredExisting]));
}
for (const { name, absPath } of cjsFiles) {
// Compute relative CJS path and all declared/discovered TS candidates.
const relCjs = path.relative(ROOT, absPath).replace(/\\/g, '/');
const tsPaths = candidateTsPathsFor(relCjs, name);
if (tsPaths.length === 0) continue;
// Pair-aware matching, per ts sibling. Each ts candidate is classified
// independently against the allowlist so a partially-allowlisted set of
// siblings still surfaces the unauthorized ones. See #3632.
const unauthorizedTs = [];
const backlogTsForCjs = [];
for (const relTs of tsPaths) {
const pairKey = `${relCjs}::${relTs}`;
if (cooperatingPairs.has(pairKey)) continue;
if (migrateMap.has(pairKey)) {
backlogTsForCjs.push(relTs);
continue;
}
unauthorizedTs.push(relTs);
}
if (unauthorizedTs.length > 0) {
errors.push({ relCjs, tsPaths: unauthorizedTs });
}
if (backlogTsForCjs.length > 0) {
const entry = migrateMap.get(`${relCjs}::${backlogTsForCjs[0]}`);
warnings.push({ relCjs, tsPaths: backlogTsForCjs, entry });
}
}
// Count cjs files whose pair identity (cjs+ts) is on cooperatingSiblings.
// A file with multiple ts candidates is counted once if any pair matches.
const cooperatingCount = cjsFiles.filter((f) => {
const relCjs = path.relative(ROOT, f.absPath).replace(/\\/g, '/');
return candidateTsPathsFor(relCjs, f.name).some((relTs) =>
cooperatingPairs.has(`${relCjs}::${relTs}`)
);
}).length;
// -------------------------------------------------------------------------
// Report errors (exit 1)
// -------------------------------------------------------------------------
if (errors.length > 0) {
if (JSON_OUTPUT) {
emitJson({
ok: false,
reason: 'unauthorized_pairs',
errors,
warnings,
cooperatingCount,
});
} else {
process.stderr.write(
`\nERROR lint-shared-module-handsync: ${errors.length} unauthorized hand-sync pair(s) found.\n\n`
);
for (const { relCjs, tsPaths } of errors) {
process.stderr.write(` CJS: ${relCjs}\n`);
for (const ts of tsPaths) {
process.stderr.write(` TS: ${ts}\n`);
}
process.stderr.write('\n');
}
process.stderr.write(
'To resolve, choose one of:\n' +
' 1. Migrate to a Shared Module (preferred): create sdk/src/<name>/index.ts as the\n' +
' source-of-truth, write a generator script (sdk/scripts/gen-<name>.mjs), add a\n' +
' freshness check, and update CI. See docs/agents/cjs-sdk-seam.md for the pattern.\n' +
' 2. Add an explicit allowlist entry to scripts/shared-module-handsync-allowlist.json\n' +
' with a justification explaining why this pair is a legitimate cooperating sibling\n' +
' rather than a drift anti-pattern. Requires maintainer review via CODEOWNERS.\n\n'
);
}
process.exit(1);
}
// -------------------------------------------------------------------------
// Report warnings (no exit code change)
// -------------------------------------------------------------------------
if (warnings.length > 0 && WARN_ALL && !JSON_OUTPUT) {
process.stderr.write(
`\nWARNING lint-shared-module-handsync: ${warnings.length} known drift anti-pattern pair(s) in migrateMeBacklog.\n` +
`These are tracked for future Shared Module migration but do not block CI.\n\n`
);
for (const { relCjs, tsPaths, entry } of warnings) {
process.stderr.write(` CJS: ${relCjs}\n`);
for (const ts of tsPaths) {
process.stderr.write(` TS: ${ts}\n`);
}
process.stderr.write(` Tracked: ${entry.trackedIn}\n`);
process.stderr.write(` Hint: ${entry.justification}\n\n`);
}
}
// -------------------------------------------------------------------------
// Success
// -------------------------------------------------------------------------
if (JSON_OUTPUT) {
emitJson({
ok: true,
cooperatingCount,
backlogCount: warnings.length,
warnings,
});
} else {
process.stdout.write(
`ok lint-shared-module-handsync: no unauthorized hand-sync pairs found` +
` (${cooperatingCount} cooperating sibling(s), ${warnings.length} backlog pair(s))\n`
);
}
process.exit(0);
}
main();

View File

@@ -1,183 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"_comment": "Allowlist for scripts/lint-shared-module-handsync.cjs — Phase 6 of #3524 (#3575). Two categories: cooperatingSiblings (legitimate pairs, lint accepts silently) and migrateMeBacklog (known drift anti-patterns, lint warns but does not fail). All entries require cjs + ts path + classification + justification.",
"cooperatingSiblings": [
{
"cjs": "get-shit-done/bin/lib/active-workstream-store.cjs",
"ts": "sdk/src/query/active-workstream-store.ts",
"classification": "cooperating-sibling",
"justification": "CJS manages filesystem-backed workstream store; SDK layer wraps via Adapter for query dispatch. Different responsibilities, not drift."
},
{
"cjs": "get-shit-done/bin/lib/config-schema.cjs",
"ts": "sdk/src/query/config-schema.ts",
"classification": "cooperating-sibling",
"justification": "SDK config-schema.ts is the generated source-of-truth derived from sdk/shared/config-schema.manifest.json (Phase 2/#3540). CJS config-schema.cjs is the Adapter that reads from that manifest. Not a hand-sync pair; freshness check enforces alignment."
},
{
"cjs": "get-shit-done/bin/lib/frontmatter.cjs",
"ts": "sdk/src/query/frontmatter.ts",
"classification": "cooperating-sibling",
"justification": "CJS implements full frontmatter parsing/mutation; SDK frontmatter.ts is the native SDK query handler delegating to the CJS runtime via the seam bridge. Not duplicating logic."
},
{
"cjs": "get-shit-done/bin/lib/init.cjs",
"ts": "sdk/src/query/init.ts",
"classification": "cooperating-sibling",
"justification": "CJS init.cjs is the authoritative initializer; SDK init.ts provides the native handler layer for the SDK query seam. Phase 5.2+ will migrate remaining subcommands, but current architecture is intentional."
},
{
"cjs": "get-shit-done/bin/lib/phase.cjs",
"ts": "sdk/src/query/phase.ts",
"classification": "cooperating-sibling",
"justification": "CJS phase.cjs implements sync mutation handlers (phaseAdd, phaseInsert, phaseRemove, phaseComplete) and query handlers with sync I/O; SDK phase.ts provides the native async query handler. The async mutation handlers (phase-lifecycle.ts) and pure policy helpers (phase-lifecycle-policy.ts) are I/O-bound or pure and remain per-side (ADR-3524 §4). Issue #4 fix: phase.cjs cmdPhaseComplete now consumes pure helpers from phase-lifecycle.generated.cjs (generated from sdk/src/query/phase-lifecycle.ts) for idempotent Completed Phases counting and clamped percent. *.generated.cjs files are excluded from lint scanner; see sdk/scripts/check-phase-lifecycle-fresh.mjs for freshness enforcement."
},
{
"cjs": "get-shit-done/bin/lib/phase-lifecycle.cjs",
"ts": "sdk/src/query/phase-lifecycle.ts",
"classification": "ADAPTER-OVER-MODULE",
"justification": "CJS phase-lifecycle.cjs is the pure helper module consumed by phase.cjs; sdk/src/query/phase-lifecycle.ts remains the typed source counterpart. This pair is intentionally tracked as adapter-over-module rather than an unauthorized hand-sync pair."
},
{
"cjs": "get-shit-done/bin/lib/profile-output.cjs",
"ts": "sdk/src/query/profile-output.ts",
"classification": "cooperating-sibling",
"justification": "CJS profile-output.cjs handles profiling output rendering; SDK profile-output.ts is the corresponding SDK query handler. Separate responsibilities across the seam."
},
{
"cjs": "get-shit-done/bin/lib/roadmap.cjs",
"ts": "sdk/src/query/roadmap.ts",
"classification": "cooperating-sibling",
"justification": "CJS roadmap.cjs is the full roadmap implementation; SDK roadmap.ts provides the native handler for SDK query dispatch. Phase 5.2+ candidate."
},
{
"cjs": "get-shit-done/bin/lib/state.cjs",
"ts": "sdk/src/query/state.ts",
"classification": "cooperating-sibling",
"justification": "CJS state.cjs is the full state implementation; SDK state.ts routes known subcommands via executeForCjs (Phase 5.0/#3558, Phase 5.1/#3574). Intentional seam delegation pattern."
},
{
"cjs": "get-shit-done/bin/lib/state-document.cjs",
"ts": "sdk/src/state/index.ts",
"classification": "ADAPTER-OVER-MODULE",
"justification": "CJS state-document.cjs is a thin Adapter over get-shit-done/bin/lib/state-document.generated.cjs. That generated artifact is emitted from sdk/src/state/index.ts by sdk/scripts/gen-state-document.ts; freshness is enforced by sdk/scripts/check-state-document-fresh.mjs."
},
{
"cjs": "get-shit-done/bin/lib/template.cjs",
"ts": "sdk/src/query/template.ts",
"classification": "cooperating-sibling",
"justification": "CJS template.cjs handles template operations; SDK template.ts is the corresponding SDK native handler. Separate responsibilities across the seam."
},
{
"cjs": "get-shit-done/bin/lib/uat.cjs",
"ts": "sdk/src/query/uat.ts",
"classification": "cooperating-sibling",
"justification": "CJS uat.cjs implements UAT workflows; SDK uat.ts provides the SDK query handler layer. Separate responsibilities."
},
{
"cjs": "get-shit-done/bin/lib/verify.cjs",
"ts": "sdk/src/query/verify.ts",
"classification": "cooperating-sibling",
"justification": "CJS verify.cjs is the full verify implementation; SDK verify.ts provides the native handler. Phase 5.2+ candidate for further delegation. Check 8 (W006/W007) helpers now generated from sdk/src/query/validate.ts via sdk/scripts/gen-validate.mjs (issue #6); freshness check: sdk/scripts/check-validate-fresh.mjs."
},
{
"cjs": "get-shit-done/bin/lib/validate.cjs",
"ts": "sdk/src/query/validate.ts",
"classification": "ADAPTER-OVER-MODULE",
"justification": "CJS validate.cjs provides the pure validation helpers consumed by verify.cjs while sdk/src/query/validate.ts is the SDK handler-side counterpart. Keep this explicit adapter pair allowlisted to avoid false-positive hand-sync lint failures."
},
{
"cjs": "get-shit-done/bin/lib/workstream.cjs",
"ts": "sdk/src/query/workstream.ts",
"classification": "cooperating-sibling",
"justification": "CJS workstream.cjs handles workstream management; SDK workstream.ts provides the SDK query handler. Workstream support inside sync bridge is an open follow-up item."
},
{
"cjs": "get-shit-done/bin/lib/workstream-inventory.cjs",
"ts": "sdk/src/query/workstream-inventory.ts",
"classification": "cooperating-sibling",
"justification": "CJS workstream-inventory.cjs is the generated Adapter for the workstream-inventory Shared Module (Phase 3/#3548). SDK workstream-inventory.ts is the source-of-truth query handler. Freshness check enforces alignment."
},
{
"cjs": "get-shit-done/bin/lib/config.cjs",
"ts": "sdk/src/config.ts",
"classification": "CJS-CLI-ONLY",
"justification": "Phase 2 (#3536) already migrated CONFIG_DEFAULTS and loadConfig/mergeDefaults to the Configuration Module and sdk/src/config.ts. What remains in config.cjs is exclusively CLI command handlers (cmdConfigGet, cmdConfigSet, cmdConfigNewProject, cmdConfigEnsureSection, cmdConfigSetModelProfile, cmdConfigPath, cmdMigrateConfig, buildNewProjectConfig, setConfigValue, ensureConfigFile) that depend on CJS-only APIs (withPlanningLock, platformWriteSync/ReadSync/EnsureDir, sync fs ops, process.exit). sdk/src/config.ts provides only the async loadConfig/mergeDefaults SDK layer. The two files serve disjoint surfaces with no logical overlap — not a hand-sync drift anti-pattern."
},
{
"cjs": "get-shit-done/bin/lib/config.cjs",
"ts": "sdk/src/config/index.ts",
"classification": "ADAPTER-OVER-MODULE",
"justification": "sdk/src/config/index.ts is the Configuration Module source-of-truth; CJS config.cjs consumes its generated Adapter through core/config-schema integration. This is an intentional seam relationship, not manual hand-sync."
},
{
"cjs": "get-shit-done/bin/lib/state.cjs",
"ts": "sdk/src/state/index.ts",
"classification": "ADAPTER-OVER-MODULE",
"justification": "sdk/src/state/index.ts is the STATE.md Document Module source-of-truth that emits get-shit-done/bin/lib/state-document.generated.cjs. state.cjs consumes that generated module as an Adapter dependency; basename overlap is intentional and not a duplicated implementation."
},
{
"cjs": "get-shit-done/bin/lib/intel.cjs",
"ts": "sdk/src/query/intel.ts",
"classification": "cooperating-sibling",
"justification": "CJS intel.cjs is the synchronous runtime implementation used by gsd-tools.cjs; sdk/src/query/intel.ts is the async QueryHandler port for the SDK query seam (explicitly documented as a port in its file header). The two files intentionally diverge on INTEL_FILES naming (CJS: file-roles.json/api-map.json/dependency-graph.json/arch-decisions.json; SDK: files.json/apis.json/deps.json/arch.md) — existing CJS tests are locked to the old naming. Not a hand-sync drift pattern; separate runtime responsibilities across the seam."
},
{
"cjs": "get-shit-done/bin/lib/model-catalog.cjs",
"ts": "sdk/src/model-catalog.ts",
"classification": "ADAPTER-OVER-MODULE",
"justification": "Both files read from sdk/shared/model-catalog.json (ADR-0003 precedent) as independent consumers of the shared manifest. CJS exposes VALID_AGENT_TIERS, MODEL_ALIAS_MAP, RUNTIME_PROFILE_MAP, KNOWN_RUNTIMES, RUNTIMES_WITH_REASONING_EFFORT, nextTier, formatAgentToModelMapAsTable for core.cjs and model-profiles.cjs consumers. SDK exposes resolveRuntimeTierDefault, runtimesWithReasoningEffort for session-runner.ts and query handlers. The shared JSON is the single source-of-truth; both adapters derive their exports from it without duplicating any logic between themselves."
},
{
"cjs": "get-shit-done/bin/lib/plan-scan.cjs",
"ts": "sdk/src/query/plan-scan.ts",
"classification": "ADAPTER-OVER-MODULE",
"justification": "CJS plan-scan.cjs is the generated Adapter reading from sdk/src/query/plan-scan.ts Shared Module (Phase 6/#3575). SDK plan-scan.ts is the source-of-truth. Freshness check (check-plan-scan-fresh.mjs) enforces alignment."
},
{
"cjs": "get-shit-done/bin/lib/secrets.cjs",
"ts": "sdk/src/query/secrets.ts",
"classification": "ADAPTER-OVER-MODULE",
"justification": "CJS secrets.cjs is the generated Adapter reading from sdk/src/query/secrets.ts Shared Module (Phase 6/#3575). SDK secrets.ts is the source-of-truth. Freshness check (check-secrets-fresh.mjs) enforces alignment."
},
{
"cjs": "get-shit-done/bin/lib/schema-detect.cjs",
"ts": "sdk/src/query/schema-detect.ts",
"classification": "ADAPTER-OVER-MODULE",
"justification": "CJS schema-detect.cjs is the generated Adapter reading from sdk/src/query/schema-detect.ts Shared Module (Phase 6/#3575). SDK schema-detect.ts is the source-of-truth. Generated CJS adds detectSchemaOrm compat export (not in SDK) and exports SCHEMA_PATTERNS/ORM_INFO for backward compatibility. Freshness check (check-schema-detect-fresh.mjs) enforces alignment."
},
{
"cjs": "get-shit-done/bin/lib/decisions.cjs",
"ts": "sdk/src/query/decisions.ts",
"classification": "ADAPTER-OVER-MODULE",
"justification": "Phase 6 (#3575): CJS decisions.cjs is the generated Adapter reading from sdk/src/query/decisions.ts Shared Module. SDK source-of-truth; regex aligned to accept alphanumeric IDs (D-INFRA-01). CJS callers (gap-checker.cjs) use {id, text} subset; extra fields {category, tags, trackable} are present but ignored. Freshness check (check-decisions-fresh.mjs) enforces alignment."
},
{
"cjs": "get-shit-done/bin/lib/workstream-name-policy.cjs",
"ts": "sdk/src/workstream-name-policy.ts",
"classification": "ADAPTER-OVER-MODULE",
"justification": "Phase 6 (#3575): CJS workstream-name-policy.cjs is the generated Adapter reading from sdk/src/workstream-name-policy.ts Shared Module. SDK source-of-truth now exports all three functions used by CJS callers (toWorkstreamSlug, hasInvalidPathSegment, isValidActiveWorkstreamName) plus validateWorkstreamName alias. Freshness check (check-workstream-name-policy-fresh.mjs) enforces alignment."
},
{
"cjs": "get-shit-done/bin/lib/runtime-name-policy.cjs",
"ts": "sdk/src/runtime-name-policy.ts",
"classification": "ADAPTER-OVER-MODULE",
"justification": "Both CJS and SDK runtime-name adapters consume sdk/shared/runtime-aliases.manifest.json as the single source of truth for alias canonicalization. This pair is an intentional seam-level adapter split, not a drift-prone hand-sync anti-pattern."
},
{
"cjs": "get-shit-done/bin/lib/prompt-budget.cjs",
"ts": "sdk/src/query/prompt-budget.ts",
"classification": "cooperating-sibling",
"justification": "CJS prompt-budget.cjs provides the applyBudget implementation used by gsd-tools.cjs case 'prompt-budget'. SDK prompt-budget.ts is the native QueryHandler port for gsd-sdk query dispatch (#3081). The SDK handler ports the pure budget logic and adds CLI arg parsing / file I/O directly, satisfying the registry-integration drift-guard without duplicating shared state."
}
],
"migrateMeBacklog": [
{
"cjs": "get-shit-done/bin/lib/verify.cjs",
"ts": "sdk/src/query/validate.ts",
"classification": "drift-anti-pattern",
"justification": "Cross-name cooperating pair where validate policy changes can drift from verify checks; keep explicitly visible until Shared Module extraction removes hand-sync risk.",
"trackedIn": "#11"
}
]
}