refactor(#1291): T6 — migrate test files off the core spine ahead of deletion (#1293)

The convergence lint only scanned src/ + gsd-core/bin, so ~35 test files
still imported core.cjs. Repoint all 33 behaviour importers to the leaf
modules directly (same symbol->leaf map as the src migration; leaves are the
objects core re-exported by reference), delete the now-meaningless
shim-identity describe blocks in the 8 leaf tests, and delete tests/core.test.cjs
(forwarded-behaviour coverage now lives at the leaves; resolveWorktreeRoot
test relocated to worktree-safety in T0) and tests/lint-core-spine-imports.test.cjs
(the lint is removed in T-final). Dropped the stale core.test.cjs entries from
the allow-test-rule-refs allowlist; eslint-rules RuleTester fixture path
pointed at io.cjs.

After T6: ZERO test imports core.cjs. core.cts still builds (now fully unused);
T-final deletes it. No behaviour change.

Closes #1291

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-15 18:11:20 -04:00
committed by GitHub
parent 76765bc24d
commit c76827afbc
36 changed files with 51 additions and 2650 deletions

View File

@@ -152,8 +152,6 @@
"tests/context-enrichment.test.cjs :: source-text-is-the-product",
"tests/contributor-standards.test.cjs :: source-text-is-the-product",
"tests/copilot-install.test.cjs :: integration-test-input",
"tests/core.test.cjs :: architectural-invariant",
"tests/core.test.cjs :: structural-regression-guard",
"tests/cursor-hooks.test.cjs :: source-text-is-the-product",
"tests/cursor-reviewer.test.cjs :: source-text-is-the-product",
"tests/debug-session-management.test.cjs :: source-text-is-the-product",

View File

@@ -28,7 +28,7 @@ const fs = require('node:fs');
const path = require('node:path');
const { createTempProject, cleanup, captureConsole } = require('./helpers.cjs');
const { getMilestonePhaseFilter } = require('../gsd-core/bin/lib/core.cjs');
const { getMilestonePhaseFilter } = require('../gsd-core/bin/lib/roadmap-parser.cjs');
// ─── helpers ─────────────────────────────────────────────────────────────────

View File

@@ -24,7 +24,7 @@ const fs = require('fs');
const path = require('path');
const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
const { resolveModelInternal } = require('../gsd-core/bin/lib/core.cjs');
const { resolveModelInternal } = require('../gsd-core/bin/lib/model-resolver.cjs');
// ─── Helpers ──────────────────────────────────────────────────────────────────

View File

@@ -15,7 +15,7 @@ const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { createTempProject, cleanup } = require('./helpers.cjs');
const { getMilestonePhaseFilter } = require('../gsd-core/bin/lib/core.cjs');
const { getMilestonePhaseFilter } = require('../gsd-core/bin/lib/roadmap-parser.cjs');
describe('bug #2554 — getMilestonePhaseFilter decimal phase dirs', () => {
let tmpDir;

View File

@@ -17,7 +17,7 @@ const path = require('path');
const { execFileSync } = require('child_process');
const { createTempProject, cleanup } = require('./helpers.cjs');
const { loadConfig } = require('../gsd-core/bin/lib/core.cjs');
const { loadConfig } = require('../gsd-core/bin/lib/config-loader.cjs');
function makeSubRepo(parent, name) {
const dir = path.join(parent, name);

View File

@@ -29,7 +29,7 @@ const fs = require('node:fs');
const path = require('node:path');
const os = require('node:os');
const { cleanup } = require('./helpers.cjs');
const { resolveEffortInternal } = require('../gsd-core/bin/lib/core.cjs');
const { resolveEffortInternal } = require('../gsd-core/bin/lib/model-resolver.cjs');
/** Create a bare temp directory with no .planning/ structure */
function createBareTmpDir(prefix = 'gsd-test-') {

View File

@@ -107,9 +107,9 @@ describe('bug #730 — milestone (Phase Details) section scope resolution', () =
// directly before any phases/ dir exists
// -------------------------------------------------------------------------
test('getRoadmapPhaseInternal resolves the current-milestone phase directly before any dir exists', () => {
const core = require('../gsd-core/bin/lib/core.cjs');
const { getRoadmapPhaseInternal } = require('../gsd-core/bin/lib/roadmap-parser.cjs');
const res = core.getRoadmapPhaseInternal(dir, '2');
const res = getRoadmapPhaseInternal(dir, '2');
assert.ok(res !== null && res !== undefined, `getRoadmapPhaseInternal returned null/undefined for phase 2`);
assert.strictEqual(res.found, true, `res.found should be true; got ${JSON.stringify(res)}`);
assert.strictEqual(res.phase_name, 'Feature', `res.phase_name should be 'Feature'; got '${res.phase_name}'`);

View File

@@ -534,7 +534,7 @@ describe('dispatchCapabilityCommand — non-ExitError from router → structured
assert.ok(msg.includes('fake.cjs'), 'message must name the module; got: ' + msg);
assert.ok(msg.includes('boom'), 'message must include original error message; got: ' + msg);
// Reason must be SDK_FAIL_FAST
const { ERROR_REASON } = require('../gsd-core/bin/lib/core.cjs');
const { ERROR_REASON } = require('../gsd-core/bin/lib/io.cjs');
assert.strictEqual(reason, ERROR_REASON.SDK_FAIL_FAST, 'reason must be SDK_FAIL_FAST');
});
@@ -625,7 +625,7 @@ describe('dispatchCapabilityCommand — non-ExitError from router → structured
// ─── 10. Async router (thenable) → structured error ─────────────────────────
describe('dispatchCapabilityCommand — async router returns a Promise → structured error', () => {
const { ERROR_REASON } = require('../gsd-core/bin/lib/core.cjs');
const { ERROR_REASON } = require('../gsd-core/bin/lib/io.cjs');
test('router returns Promise.resolve() → error() called with "must be synchronous" + SDK_FAIL_FAST', () => {
const errorCalls = [];

View File

@@ -27,7 +27,6 @@ const { cleanup } = require('./helpers.cjs');
// ─── module under test ────────────────────────────────────────────────────────
const configLoader = require('../gsd-core/bin/lib/config-loader.cjs');
const coreModule = require('../gsd-core/bin/lib/core.cjs');
const { loadConfig, _resetRuntimeWarningCacheForTests } = configLoader;
@@ -50,25 +49,6 @@ function writeWorkstreamConfig(tmpDir, wsName, obj) {
fs.writeFileSync(path.join(wsDir, 'config.json'), JSON.stringify(obj, null, 2), 'utf-8');
}
// ─── shim identity ────────────────────────────────────────────────────────────
describe('config-loader shim identity', () => {
test('core.loadConfig === configLoader.loadConfig (same function object)', () => {
assert.strictEqual(
coreModule.loadConfig,
configLoader.loadConfig,
'core.cjs must re-export the same loadConfig function as config-loader.cjs'
);
});
test('core.isGitIgnored === configLoader.isGitIgnored (same function object)', () => {
assert.strictEqual(
coreModule.isGitIgnored,
configLoader.isGitIgnored,
'core.cjs must re-export the same isGitIgnored function as config-loader.cjs'
);
});
});
// ─── defaults when no config.json ────────────────────────────────────────────

View File

@@ -30,50 +30,8 @@ const path = require('node:path');
const os = require('node:os');
const coreUtils = require('../gsd-core/bin/lib/core-utils.cjs');
const core = require('../gsd-core/bin/lib/core.cjs');
const { cleanup } = require('./helpers.cjs');
// ─── Shim-identity assertions ─────────────────────────────────────────────────
describe('core-utils: shim-identity — core.cjs re-exports same function objects', () => {
test('core.toPosixPath === coreUtils.toPosixPath', () => {
assert.strictEqual(core.toPosixPath, coreUtils.toPosixPath);
});
test('core.detectSubRepos === coreUtils.detectSubRepos', () => {
assert.strictEqual(core.detectSubRepos, coreUtils.detectSubRepos);
});
test('core.extractOneLinerFromBody === coreUtils.extractOneLinerFromBody', () => {
assert.strictEqual(core.extractOneLinerFromBody, coreUtils.extractOneLinerFromBody);
});
test('core.pathExistsInternal === coreUtils.pathExistsInternal', () => {
assert.strictEqual(core.pathExistsInternal, coreUtils.pathExistsInternal);
});
test('core.generateSlugInternal === coreUtils.generateSlugInternal', () => {
assert.strictEqual(core.generateSlugInternal, coreUtils.generateSlugInternal);
});
test('core.filterPlanFiles === coreUtils.filterPlanFiles', () => {
assert.strictEqual(core.filterPlanFiles, coreUtils.filterPlanFiles);
});
test('core.filterSummaryFiles === coreUtils.filterSummaryFiles', () => {
assert.strictEqual(core.filterSummaryFiles, coreUtils.filterSummaryFiles);
});
test('core.getPhaseFileStats === coreUtils.getPhaseFileStats', () => {
assert.strictEqual(core.getPhaseFileStats, coreUtils.getPhaseFileStats);
});
test('core.readSubdirectories === coreUtils.readSubdirectories', () => {
assert.strictEqual(core.readSubdirectories, coreUtils.readSubdirectories);
});
test('core.timeAgo === coreUtils.timeAgo', () => {
assert.strictEqual(core.timeAgo, coreUtils.timeAgo);
});
test('extractCanonicalPlanId is NOT re-exported from core', () => {
assert.strictEqual(typeof core.extractCanonicalPlanId, 'undefined');
});
test('extractCanonicalPlanId IS exported from coreUtils', () => {
assert.strictEqual(typeof coreUtils.extractCanonicalPlanId, 'function');
});
});
// ─── toPosixPath ─────────────────────────────────────────────────────────────
describe('toPosixPath', () => {

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@ const path = require('path');
const os = require('os');
const { cleanup } = require('./helpers.cjs');
const { loadConfig } = require('../gsd-core/bin/lib/core.cjs');
const { loadConfig } = require('../gsd-core/bin/lib/config-loader.cjs');
/** Create a bare temp dir (no .planning/) to simulate pre-project context */
function createBareTmpDir() {

View File

@@ -120,7 +120,7 @@ describe('no-source-grep rule', () => {
valid: [
{
code: `
const mod = require('../gsd-core/bin/lib/core.cjs');
const mod = require('../gsd-core/bin/lib/io.cjs');
mod.someMethod();
`,
filename: 'tests/foo.test.cjs',

View File

@@ -27,7 +27,7 @@ const path = require('node:path');
const {
resolveModelInternal,
} = require('../gsd-core/bin/lib/core.cjs');
} = require('../gsd-core/bin/lib/model-resolver.cjs');
const {
AGENT_TO_PHASE_TYPE,
VALID_PHASE_TYPES,
@@ -254,7 +254,7 @@ describe('#3023 resolver: models.<phase_type> overrides profile-based tier', ()
// ─── #443 Unified effort: resolveEffortInternal + renderEffortForRuntime ────
const { resolveEffortInternal } = require('../gsd-core/bin/lib/core.cjs');
const { resolveEffortInternal } = require('../gsd-core/bin/lib/model-resolver.cjs');
const { renderEffortForRuntime } = require('../gsd-core/bin/lib/model-catalog.cjs');
describe('#3023 + #443: unified effort resolver (resolveEffortInternal) for Codex', () => {

View File

@@ -49,7 +49,7 @@ const path = require('node:path');
const {
resolveModelInternal,
resolveModelForTier,
} = require('../gsd-core/bin/lib/core.cjs');
} = require('../gsd-core/bin/lib/model-resolver.cjs');
const {
AGENT_DEFAULT_TIERS,
VALID_AGENT_TIERS,

View File

@@ -52,7 +52,7 @@ const {
resolveFastModeInternal,
resolveEffortForTier,
VALID_EFFORTS,
} = require('../gsd-core/bin/lib/core.cjs');
} = require('../gsd-core/bin/lib/model-resolver.cjs');
const {
renderEffortForRuntime,

View File

@@ -25,7 +25,7 @@ const {
resolveEffortInternal,
resolveFastModeInternal,
resolveEffortForTier,
} = require('../gsd-core/bin/lib/core.cjs');
} = require('../gsd-core/bin/lib/model-resolver.cjs');
const {
renderEffortForRuntime,

View File

@@ -63,8 +63,10 @@ const {
resolveModelInternal,
resolveModelPolicy,
resolveModelForTier,
} = require('../gsd-core/bin/lib/model-resolver.cjs');
const {
KNOWN_PROVIDERS,
} = require('../gsd-core/bin/lib/core.cjs');
} = require('../gsd-core/bin/lib/model-catalog.cjs');
// KNOWN_PROVIDERS must also be exported directly from model-catalog.cjs
const modelCatalog = require('../gsd-core/bin/lib/model-catalog.cjs');

View File

@@ -30,7 +30,7 @@ const path = require('node:path');
const {
resolveGranularityInternal,
VALID_GRANULARITIES,
} = require('../gsd-core/bin/lib/core.cjs');
} = require('../gsd-core/bin/lib/model-resolver.cjs');
const commands = require('../gsd-core/bin/lib/commands.cjs');
const {
VALID_PHASE_TYPES,

View File

@@ -19,7 +19,6 @@ const os = require('node:os');
const fs = require('node:fs');
const io = require('../gsd-core/bin/lib/io.cjs');
const core = require('../gsd-core/bin/lib/core.cjs');
// ─── ERROR_REASON constants ───────────────────────────────────────────────────
@@ -308,37 +307,6 @@ describe('reapStaleTempFiles (via io)', () => {
});
});
// ─── core.cjs re-export shim parity ──────────────────────────────────────────
describe('core.cjs re-export shims', () => {
test('core.output is the same function as io.output', () => {
assert.strictEqual(core.output, io.output);
});
test('core.error is the same function as io.error', () => {
assert.strictEqual(core.error, io.error);
});
test('core.ERROR_REASON is the same object as io.ERROR_REASON', () => {
assert.strictEqual(core.ERROR_REASON, io.ERROR_REASON);
});
test('core.setJsonErrorMode is the same function as io.setJsonErrorMode', () => {
assert.strictEqual(core.setJsonErrorMode, io.setJsonErrorMode);
});
test('core.getJsonErrorMode is the same function as io.getJsonErrorMode', () => {
assert.strictEqual(core.getJsonErrorMode, io.getJsonErrorMode);
});
test('core.reapStaleTempFiles is the same function as io.reapStaleTempFiles', () => {
assert.strictEqual(core.reapStaleTempFiles, io.reapStaleTempFiles);
});
test('core.GSD_TEMP_DIR is the same value as io.GSD_TEMP_DIR', () => {
assert.strictEqual(core.GSD_TEMP_DIR, io.GSD_TEMP_DIR);
});
});
// ─── bug #1008: output()/error() tolerate a full / slow non-blocking pipe ─────
//

View File

@@ -36,9 +36,11 @@ const {
resolveModelInternal,
resolveEffortInternal,
resolveTierEntry,
} = require('../gsd-core/bin/lib/model-resolver.cjs');
const {
RUNTIME_PROFILE_MAP,
KNOWN_RUNTIMES,
} = require('../gsd-core/bin/lib/core.cjs');
} = require('../gsd-core/bin/lib/model-catalog.cjs');
const { renderEffortForRuntime } = require('../gsd-core/bin/lib/model-catalog.cjs');
const { isValidConfigKey } = require('../gsd-core/bin/lib/config-schema.cjs');
@@ -471,7 +473,7 @@ describe('issue #2517: VALID_CONFIG_KEYS schema', () => {
// ─── loadConfig validation warnings (review findings #10, #13) ──────────────
describe('issue #2517: loadConfig warns on unknown runtime/tier (findings #10, #13)', () => {
const { loadConfig } = require('../gsd-core/bin/lib/core.cjs');
const { loadConfig } = require('../gsd-core/bin/lib/config-loader.cjs');
let tmpDir;
let origWrite;
let captured;

View File

@@ -1,279 +0,0 @@
'use strict';
process.env.GSD_TEST_MODE = '1';
/**
* Tests for scripts/lint-core-spine-imports.cjs (issue #1268).
*
* Behavioural, no source-grep. Asserts on structured return values from the
* exported pure functions and on process exit codes from the CLI.
*/
const { test, describe, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const { createTempDir, cleanup } = require('./helpers.cjs');
const ROOT = path.join(__dirname, '..');
const SCRIPT_PATH = path.join(ROOT, 'scripts', 'lint-core-spine-imports.cjs');
const ALLOWLIST_PATH = path.join(ROOT, 'scripts', 'lint-core-spine-imports.allowlist.json');
const { scanCoreSpineImports, loadAllowlist } = require(SCRIPT_PATH);
// ---------------------------------------------------------------------------
// Pure-function tests
// ---------------------------------------------------------------------------
describe('lint-core-spine-imports: scanCoreSpineImports (pure)', () => {
let tmpDir;
afterEach(() => {
cleanup(tmpDir);
tmpDir = undefined;
});
test('happy path: allowlisted importer is not a violation', () => {
tmpDir = createTempDir('lint-spine-test-');
// File that imports the core spine
const importerPath = path.join(tmpDir, 'importer.cts');
fs.writeFileSync(importerPath, [
"import core = require('./core.cjs');",
'export = { x: 1 };',
].join('\n'), 'utf8');
// File that does NOT import the core spine
const cleanPath = path.join(tmpDir, 'clean.cts');
fs.writeFileSync(cleanPath, [
"import io = require('./io.cjs');",
'export = { y: 2 };',
].join('\n'), 'utf8');
// Allowlist contains the importer's repo-relative path
const importerRel = path.relative(ROOT, importerPath).replace(/\\/g, '/');
const allowlistSet = new Set([importerRel]);
const violations = scanCoreSpineImports([tmpDir], allowlistSet);
assert.deepEqual(violations, []);
});
test('violation path: non-allowlisted importer is returned with correct file and 1-based line', () => {
tmpDir = createTempDir('lint-spine-test-');
// File with a blank first line, then the core import on line 2
const importerPath = path.join(tmpDir, 'new-squatter.cts');
fs.writeFileSync(importerPath, [
'// a comment',
"import core = require('./core.cjs');",
'export = {};',
].join('\n'), 'utf8');
// Empty allowlist — importer is NOT allowed
const violations = scanCoreSpineImports([tmpDir], new Set());
assert.equal(violations.length, 1, 'expected exactly one violation');
const v = violations[0];
// File path is repo-relative POSIX
const expectedRel = path.relative(ROOT, importerPath).replace(/\\/g, '/');
assert.equal(v.file, expectedRel);
// Line 2 (1-based): the second line contains the require
assert.equal(v.line, 2);
});
test('require("../lib/core.cjs") form is detected', () => {
tmpDir = createTempDir('lint-spine-test-');
const f = path.join(tmpDir, 'leaf.cjs');
fs.writeFileSync(f, 'const core = require("../lib/core.cjs");\n', 'utf8');
const violations = scanCoreSpineImports([tmpDir], new Set());
assert.equal(violations.length, 1);
assert.equal(violations[0].line, 1);
});
test('require("./lib/core.cjs") form is detected', () => {
tmpDir = createTempDir('lint-spine-test-');
const f = path.join(tmpDir, 'leaf.cjs');
fs.writeFileSync(f, 'const core = require("./lib/core.cjs");\n', 'utf8');
const violations = scanCoreSpineImports([tmpDir], new Set());
assert.equal(violations.length, 1);
});
test('clean file (no spine import) is never a violation', () => {
tmpDir = createTempDir('lint-spine-test-');
const f = path.join(tmpDir, 'pure.cts');
fs.writeFileSync(f, [
"import io = require('./io.cjs');",
"import roadmap = require('./roadmap-parser.cjs');",
].join('\n'), 'utf8');
const violations = scanCoreSpineImports([tmpDir], new Set());
assert.deepEqual(violations, []);
});
test('commented-out core require is NOT a violation', () => {
tmpDir = createTempDir('lint-spine-test-');
const f = path.join(tmpDir, 'commented.cts');
fs.writeFileSync(f, [
"// import core = require('./core.cjs');",
"// const core = require('../lib/core.cjs');",
"import io = require('./io.cjs');",
].join('\n'), 'utf8');
const violations = scanCoreSpineImports([tmpDir], new Set());
assert.deepEqual(violations, [], 'commented-out requires should not count as violations');
});
test('unconventional relative path ../lib/core.cjs IS detected', () => {
tmpDir = createTempDir('lint-spine-test-');
const f = path.join(tmpDir, 'deep-leaf.cjs');
fs.writeFileSync(f, "const core = require('../lib/core.cjs');\n", 'utf8');
const violations = scanCoreSpineImports([tmpDir], new Set());
assert.equal(violations.length, 1, 'expected exactly one violation for ../lib/core.cjs');
assert.equal(violations[0].line, 1);
});
test('core-utils.cjs and core-schema.cjs are NOT detected as core spine imports', () => {
tmpDir = createTempDir('lint-spine-test-');
const f = path.join(tmpDir, 'sibling.cts');
fs.writeFileSync(f, [
"import coreUtils = require('./core-utils.cjs');",
"import coreSchema = require('./core-schema.cjs');",
].join('\n'), 'utf8');
const violations = scanCoreSpineImports([tmpDir], new Set());
assert.deepEqual(violations, [], 'core-utils.cjs and core-schema.cjs should not match');
});
test('dynamic import("./core.cjs") form is detected', () => {
tmpDir = createTempDir('lint-spine-test-');
const f = path.join(tmpDir, 'dyn-importer.cjs');
fs.writeFileSync(f, [
'// dynamic import form',
"const core = await import('./core.cjs');",
].join('\n'), 'utf8');
const violations = scanCoreSpineImports([tmpDir], new Set());
assert.equal(violations.length, 1, 'expected exactly one violation for dynamic import');
assert.equal(violations[0].line, 2);
});
test('bare side-effect import \'./core.cjs\' form is detected', () => {
tmpDir = createTempDir('lint-spine-test-');
const f = path.join(tmpDir, 'side-effect-importer.cjs');
fs.writeFileSync(f, [
'// bare side-effect import form',
"import './core.cjs';",
].join('\n'), 'utf8');
const violations = scanCoreSpineImports([tmpDir], new Set());
assert.equal(violations.length, 1, 'expected exactly one violation for bare side-effect import');
assert.equal(violations[0].line, 2);
});
test('TS import x = require("./core.cjs") form is still flagged', () => {
tmpDir = createTempDir('lint-spine-test-');
const f = path.join(tmpDir, 'ts-require.cts');
fs.writeFileSync(f, "import core = require('./core.cjs');\n", 'utf8');
const violations = scanCoreSpineImports([tmpDir], new Set());
assert.equal(violations.length, 1, 'TS import = require() should still be flagged');
assert.equal(violations[0].line, 1);
});
test('import of core-utils.cjs (sibling) is NOT flagged even with new alternations', () => {
tmpDir = createTempDir('lint-spine-test-');
const f = path.join(tmpDir, 'sibling-check.cjs');
fs.writeFileSync(f, [
"const u = require('./core-utils.cjs');",
"const cu = await import('./core-utils.cjs');",
"import './core-utils.cjs';",
].join('\n'), 'utf8');
const violations = scanCoreSpineImports([tmpDir], new Set());
assert.deepEqual(violations, [], 'core-utils.cjs imports should not match even with new alternations');
});
});
// ---------------------------------------------------------------------------
// loadAllowlist
// ---------------------------------------------------------------------------
describe('lint-core-spine-imports: loadAllowlist', () => {
let tmpDir;
afterEach(() => {
cleanup(tmpDir);
tmpDir = undefined;
});
test('returns a Set of the allow entries', () => {
tmpDir = createTempDir('lint-spine-al-');
const jsonPath = path.join(tmpDir, 'allowlist.json');
fs.writeFileSync(jsonPath, JSON.stringify({ allow: ['src/foo.cts', 'src/bar.cts'] }), 'utf8');
const s = loadAllowlist(jsonPath);
assert.ok(s instanceof Set);
assert.ok(s.has('src/foo.cts'));
assert.ok(s.has('src/bar.cts'));
assert.equal(s.size, 2);
});
});
// ---------------------------------------------------------------------------
// T0-green / allowlist-completeness guard (real repo)
// ---------------------------------------------------------------------------
describe('lint-core-spine-imports: T0 allowlist covers all current importers', () => {
test('scanCoreSpineImports against real src/ + gsd-core/bin/ returns [] with the shipped allowlist', () => {
const roots = [
path.join(ROOT, 'src'),
path.join(ROOT, 'gsd-core', 'bin'),
];
const allowlistSet = loadAllowlist(ALLOWLIST_PATH);
const violations = scanCoreSpineImports(roots, allowlistSet);
assert.deepEqual(
violations,
[],
'T0 allowlist is incomplete — new core-spine importers detected:\n' +
violations.map((v) => ` ${v.file}:${v.line}`).join('\n'),
);
});
});
// ---------------------------------------------------------------------------
// CLI contract
// ---------------------------------------------------------------------------
describe('lint-core-spine-imports: CLI', () => {
test('clean repo → exit 0 and stdout contains the structured ok line', () => {
const result = spawnSync(process.execPath, [SCRIPT_PATH], {
cwd: ROOT,
encoding: 'utf8',
});
assert.equal(
result.status,
0,
`expected exit 0 but got ${result.status}.\nstdout: ${result.stdout}\nstderr: ${result.stderr}`,
);
assert.ok(
result.stdout.includes('ok core-spine-imports:'),
`stdout should contain "ok core-spine-imports:", got: ${result.stdout}`,
);
});
});

View File

@@ -8,7 +8,7 @@ const assert = require('node:assert/strict');
const {
getMilestoneFromPhaseId,
getPhaseDirFromPhaseId,
} = require('../gsd-core/bin/lib/core.cjs');
} = require('../gsd-core/bin/lib/phase-id.cjs');
// ─── getMilestoneFromPhaseId ────────────────────────────────────────────────

View File

@@ -10,7 +10,7 @@
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const { MODEL_ALIAS_MAP } = require('../gsd-core/bin/lib/core.cjs');
const { MODEL_ALIAS_MAP } = require('../gsd-core/bin/lib/model-catalog.cjs');
describe('MODEL_ALIAS_MAP (#1690 regression)', () => {
test('opus maps to claude-opus-4-8', () => {

View File

@@ -29,7 +29,6 @@ const { cleanup } = require('./helpers.cjs');
// ─── modules under test ───────────────────────────────────────────────────────
const modelResolver = require('../gsd-core/bin/lib/model-resolver.cjs');
const coreModule = require('../gsd-core/bin/lib/core.cjs');
const {
resolveTierEntry,
@@ -60,61 +59,6 @@ function writeConfig(tmpDir, obj) {
fs.writeFileSync(configPath, JSON.stringify(obj, null, 2), 'utf-8');
}
// ─── shim identity ────────────────────────────────────────────────────────────
describe('model-resolver shim identity', () => {
test('core.resolveTierEntry === modelResolver.resolveTierEntry', () => {
assert.strictEqual(coreModule.resolveTierEntry, modelResolver.resolveTierEntry);
});
test('core.resolveModelPolicy === modelResolver.resolveModelPolicy', () => {
assert.strictEqual(coreModule.resolveModelPolicy, modelResolver.resolveModelPolicy);
});
test('core.resolveModelInternal === modelResolver.resolveModelInternal', () => {
assert.strictEqual(coreModule.resolveModelInternal, modelResolver.resolveModelInternal);
});
test('core.VALID_GRANULARITIES === modelResolver.VALID_GRANULARITIES (same Set object)', () => {
assert.strictEqual(coreModule.VALID_GRANULARITIES, modelResolver.VALID_GRANULARITIES);
});
test('core.resolveGranularityInternal === modelResolver.resolveGranularityInternal', () => {
assert.strictEqual(coreModule.resolveGranularityInternal, modelResolver.resolveGranularityInternal);
});
test('core.assertValidGranularityOverride === modelResolver.assertValidGranularityOverride', () => {
assert.strictEqual(coreModule.assertValidGranularityOverride, modelResolver.assertValidGranularityOverride);
});
test('core.resolveModelForTier === modelResolver.resolveModelForTier', () => {
assert.strictEqual(coreModule.resolveModelForTier, modelResolver.resolveModelForTier);
});
test('core.VALID_EFFORTS === modelResolver.VALID_EFFORTS (same Array object)', () => {
assert.strictEqual(coreModule.VALID_EFFORTS, modelResolver.VALID_EFFORTS);
});
test('core.EFFORT_SET === modelResolver.EFFORT_SET (same Set object)', () => {
assert.strictEqual(coreModule.EFFORT_SET, modelResolver.EFFORT_SET);
});
test('core.nextEffort === modelResolver.nextEffort', () => {
assert.strictEqual(coreModule.nextEffort, modelResolver.nextEffort);
});
test('core.resolveEffortInternal === modelResolver.resolveEffortInternal', () => {
assert.strictEqual(coreModule.resolveEffortInternal, modelResolver.resolveEffortInternal);
});
test('core.resolveFastModeInternal === modelResolver.resolveFastModeInternal', () => {
assert.strictEqual(coreModule.resolveFastModeInternal, modelResolver.resolveFastModeInternal);
});
test('core.resolveEffortForTier === modelResolver.resolveEffortForTier', () => {
assert.strictEqual(coreModule.resolveEffortForTier, modelResolver.resolveEffortForTier);
});
});
// ─── resolveModelInternal ─────────────────────────────────────────────────────

View File

@@ -28,7 +28,7 @@ const os = require('node:os');
const { cleanup } = require('./helpers.cjs');
// Import loadConfig directly (sync, no CLI subprocess needed)
const { loadConfig } = require('../gsd-core/bin/lib/core.cjs');
const { loadConfig } = require('../gsd-core/bin/lib/config-loader.cjs');
// ─── helpers ──────────────────────────────────────────────────────────────────

View File

@@ -22,7 +22,6 @@ const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const phaseId = require('../gsd-core/bin/lib/phase-id.cjs');
const core = require('../gsd-core/bin/lib/core.cjs');
// ─── escapeRegex ─────────────────────────────────────────────────────────────
@@ -388,42 +387,3 @@ describe('getPhaseDirFromPhaseId', () => {
});
});
// ─── core.cjs re-export shim identity assertions ──────────────────────────────
describe('core.cjs re-export shim identity (single instance)', () => {
test('core.escapeRegex === phaseId.escapeRegex', () => {
assert.strictEqual(core.escapeRegex, phaseId.escapeRegex);
});
test('core.normalizePhaseName === phaseId.normalizePhaseName', () => {
assert.strictEqual(core.normalizePhaseName, phaseId.normalizePhaseName);
});
test('core.comparePhaseNum === phaseId.comparePhaseNum', () => {
assert.strictEqual(core.comparePhaseNum, phaseId.comparePhaseNum);
});
test('core.extractPhaseToken === phaseId.extractPhaseToken', () => {
assert.strictEqual(core.extractPhaseToken, phaseId.extractPhaseToken);
});
test('core.phaseTokenMatches === phaseId.phaseTokenMatches', () => {
assert.strictEqual(core.phaseTokenMatches, phaseId.phaseTokenMatches);
});
test('core.phaseMarkdownRegexSource === phaseId.phaseMarkdownRegexSource', () => {
assert.strictEqual(core.phaseMarkdownRegexSource, phaseId.phaseMarkdownRegexSource);
});
test('core.phaseMarkdownRegexSourceExact === phaseId.phaseMarkdownRegexSourceExact', () => {
assert.strictEqual(core.phaseMarkdownRegexSourceExact, phaseId.phaseMarkdownRegexSourceExact);
});
test('core.getMilestoneFromPhaseId === phaseId.getMilestoneFromPhaseId', () => {
assert.strictEqual(core.getMilestoneFromPhaseId, phaseId.getMilestoneFromPhaseId);
});
test('core.getPhaseDirFromPhaseId === phaseId.getPhaseDirFromPhaseId', () => {
assert.strictEqual(core.getPhaseDirFromPhaseId, phaseId.getPhaseDirFromPhaseId);
});
});

View File

@@ -24,25 +24,8 @@ const fs = require('node:fs');
const path = require('node:path');
const phaseLocator = require('../gsd-core/bin/lib/phase-locator.cjs');
const core = require('../gsd-core/bin/lib/core.cjs');
const { createTempProject, cleanup } = require('./helpers.cjs');
// ─── Shim-identity assertions ─────────────────────────────────────────────────
describe('phase-locator: shim-identity — core.cjs re-exports same function objects', () => {
test('core.findPhaseInternal === phaseLocator.findPhaseInternal', () => {
assert.strictEqual(core.findPhaseInternal, phaseLocator.findPhaseInternal);
});
test('core.getArchivedPhaseDirs === phaseLocator.getArchivedPhaseDirs', () => {
assert.strictEqual(core.getArchivedPhaseDirs, phaseLocator.getArchivedPhaseDirs);
});
test('core.searchPhaseInDir === phaseLocator.searchPhaseInDir', () => {
assert.strictEqual(core.searchPhaseInDir, phaseLocator.searchPhaseInDir);
});
});
// ─── findPhaseInternal — basic active-phase lookup ────────────────────────────
describe('findPhaseInternal: active phase lookup', () => {

View File

@@ -2848,7 +2848,7 @@ Plans:
// comparePhaseNum and normalizePhaseName (imported directly)
// ─────────────────────────────────────────────────────────────────────────────
const { comparePhaseNum, normalizePhaseName } = require('../gsd-core/bin/lib/core.cjs');
const { comparePhaseNum, normalizePhaseName } = require('../gsd-core/bin/lib/phase-id.cjs');
describe('comparePhaseNum', () => {
test('sorts integer phases numerically', () => {

View File

@@ -15,7 +15,7 @@ const {
setActiveWorkstream,
} = require('../gsd-core/bin/lib/planning-workspace.cjs');
const core = require('../gsd-core/bin/lib/core.cjs');
const planningWorkspaceDirect = require('../gsd-core/bin/lib/planning-workspace.cjs');
describe('planning-workspace: planningDir/planningPaths parity', () => {
const cwd = '/fake/repo';
@@ -147,7 +147,7 @@ describe('planning-workspace: lock seam', () => {
});
});
describe('core compatibility adapter: planning workspace functions', () => {
describe('planning-workspace direct: functions expose matching behavior', () => {
let savedSession;
beforeEach(() => {
@@ -160,26 +160,26 @@ describe('core compatibility adapter: planning workspace functions', () => {
else delete process.env.GSD_SESSION_KEY;
});
test('core and planning-workspace expose matching behavior', () => {
test('planning-workspace functions work consistently', () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-core-compat-'));
try {
fs.mkdirSync(path.join(tmpDir, '.planning', 'workstreams', 'alpha'), { recursive: true });
core.setActiveWorkstream(tmpDir, 'alpha');
assert.strictEqual(core.getActiveWorkstream(tmpDir), 'alpha');
planningWorkspaceDirect.setActiveWorkstream(tmpDir, 'alpha');
assert.strictEqual(planningWorkspaceDirect.getActiveWorkstream(tmpDir), 'alpha');
assert.strictEqual(getActiveWorkstream(tmpDir), 'alpha');
assert.strictEqual(
core.planningDir(tmpDir, 'feature-x', 'my-project'),
planningWorkspaceDirect.planningDir(tmpDir, 'feature-x', 'my-project'),
planningDir(tmpDir, 'feature-x', 'my-project')
);
assert.deepStrictEqual(
core.planningPaths(tmpDir, 'feature-x'),
planningWorkspaceDirect.planningPaths(tmpDir, 'feature-x'),
planningPaths(tmpDir, 'feature-x')
);
setActiveWorkstream(tmpDir, null);
assert.strictEqual(core.getActiveWorkstream(tmpDir), null);
assert.strictEqual(planningWorkspaceDirect.getActiveWorkstream(tmpDir), null);
} finally {
cleanup(tmpDir);
}

View File

@@ -382,7 +382,7 @@ describe('workflow.post_planning_gaps config (#2493)', () => {
test('CONFIG_DEFAULTS contains post_planning_gaps default true', () => {
// CONFIG_DEFAULTS is exported from core.cjs
const { CONFIG_DEFAULTS } = require('../gsd-core/bin/lib/core.cjs');
const { CONFIG_DEFAULTS } = require('../gsd-core/bin/lib/config-loader.cjs');
assert.strictEqual(CONFIG_DEFAULTS.post_planning_gaps, true);
});
@@ -419,7 +419,7 @@ describe('workflow.post_planning_gaps config (#2493)', () => {
// in its return so callers can read config.post_planning_gaps regardless of whether
// config.json exists, has the workflow section, or sets the flat key.
test('loadConfig() returns post_planning_gaps default true when key absent', () => {
const { loadConfig } = require('../gsd-core/bin/lib/core.cjs');
const { loadConfig } = require('../gsd-core/bin/lib/config-loader.cjs');
runGsdTools('config-ensure-section', tmpDir);
// Remove the key to simulate older configs that pre-date the toggle
const cfgPath = path.join(tmpDir, '.planning', 'config.json');
@@ -431,7 +431,7 @@ describe('workflow.post_planning_gaps config (#2493)', () => {
});
test('loadConfig() returns post_planning_gaps:false when workflow.post_planning_gaps=false', () => {
const { loadConfig } = require('../gsd-core/bin/lib/core.cjs');
const { loadConfig } = require('../gsd-core/bin/lib/config-loader.cjs');
runGsdTools('config-ensure-section', tmpDir);
runGsdTools(['config-set', 'workflow.post_planning_gaps', 'false'], tmpDir);
const config = loadConfig(tmpDir);
@@ -439,7 +439,7 @@ describe('workflow.post_planning_gaps config (#2493)', () => {
});
test('loadConfig() returns post_planning_gaps:true when workflow.post_planning_gaps=true', () => {
const { loadConfig } = require('../gsd-core/bin/lib/core.cjs');
const { loadConfig } = require('../gsd-core/bin/lib/config-loader.cjs');
runGsdTools('config-ensure-section', tmpDir);
runGsdTools(['config-set', 'workflow.post_planning_gaps', 'true'], tmpDir);
const config = loadConfig(tmpDir);

View File

@@ -16,7 +16,7 @@ const { createTempDir, cleanup } = require('./helpers.cjs');
// Lazy-loaded so tests can fail clearly when the export doesn't exist yet.
function getPruneOrphanedWorktrees() {
const { pruneOrphanedWorktrees } = require('../gsd-core/bin/lib/core.cjs');
const { pruneOrphanedWorktrees } = require('../gsd-core/bin/lib/worktree-safety.cjs');
return pruneOrphanedWorktrees;
}

View File

@@ -23,7 +23,6 @@ const fs = require('node:fs');
const path = require('node:path');
const roadmapParser = require('../gsd-core/bin/lib/roadmap-parser.cjs');
const core = require('../gsd-core/bin/lib/core.cjs');
const { createTempProject, cleanup } = require('./helpers.cjs');
const {
@@ -46,28 +45,6 @@ function writeState(tmpDir, fields) {
fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), lines.join('\n') + '\n');
}
// ─── Shim-identity assertions ─────────────────────────────────────────────────
describe('roadmap-parser: shim-identity — core.cjs re-exports same function objects', () => {
test('core.extractCurrentMilestone === roadmapParser.extractCurrentMilestone', () => {
assert.strictEqual(core.extractCurrentMilestone, roadmapParser.extractCurrentMilestone);
});
test('core.stripShippedMilestones === roadmapParser.stripShippedMilestones', () => {
assert.strictEqual(core.stripShippedMilestones, roadmapParser.stripShippedMilestones);
});
test('core.replaceInCurrentMilestone === roadmapParser.replaceInCurrentMilestone', () => {
assert.strictEqual(core.replaceInCurrentMilestone, roadmapParser.replaceInCurrentMilestone);
});
test('core.getRoadmapPhaseInternal === roadmapParser.getRoadmapPhaseInternal', () => {
assert.strictEqual(core.getRoadmapPhaseInternal, roadmapParser.getRoadmapPhaseInternal);
});
test('core.getMilestoneInfo === roadmapParser.getMilestoneInfo', () => {
assert.strictEqual(core.getMilestoneInfo, roadmapParser.getMilestoneInfo);
});
test('core.getMilestonePhaseFilter === roadmapParser.getMilestonePhaseFilter', () => {
assert.strictEqual(core.getMilestonePhaseFilter, roadmapParser.getMilestonePhaseFilter);
});
});
// ─── stripShippedMilestones ───────────────────────────────────────────────────

View File

@@ -231,7 +231,7 @@ describe('roadmap get-phase fallback to full ROADMAP.md (#1634)', () => {
// Regression: phase heading like "### Phase 12: v1.0 Tech-Debt Closure"
// was incorrectly treated as a milestone boundary because the greedy
// `.*v\d+\.\d+` subpattern in nextMilestonePattern matched it.
const core = require('../gsd-core/bin/lib/core.cjs');
const core = require('../gsd-core/bin/lib/roadmap-parser.cjs');
writeState(tmpDir, 'v1.1');
const roadmap = `# Roadmap
@@ -262,7 +262,7 @@ describe('roadmap get-phase fallback to full ROADMAP.md (#1634)', () => {
test('extractCurrentMilestone handles PHASE/phase (case-insensitive) containing vX.Y (#2619 follow-up)', () => {
// CodeRabbit follow-up: the negative lookahead `(?!Phase\s+\S)` must be
// case-insensitive so PHASE/phase variants are also excluded.
const core = require('../gsd-core/bin/lib/core.cjs');
const core = require('../gsd-core/bin/lib/roadmap-parser.cjs');
writeState(tmpDir, 'v1.1');
const roadmap = `# Roadmap
@@ -333,7 +333,7 @@ This phase covers:
describe('extractCurrentMilestone — closed-sibling heading selection (#145)', () => {
let tmpDir;
const core = require('../gsd-core/bin/lib/core.cjs');
const core = require('../gsd-core/bin/lib/roadmap-parser.cjs');
beforeEach(() => {
tmpDir = createTempProject();
@@ -656,7 +656,7 @@ This is the only milestone.
describe('extractCurrentMilestone — boundary / active-override hardening (#145 follow-up)', () => {
let tmpDir;
const core = require('../gsd-core/bin/lib/core.cjs');
const core = require('../gsd-core/bin/lib/roadmap-parser.cjs');
beforeEach(() => {
tmpDir = createTempProject();

View File

@@ -14,7 +14,7 @@ const os = require('os');
const {
reapStaleTempFiles,
} = require('../gsd-core/bin/lib/core.cjs');
} = require('../gsd-core/bin/lib/io.cjs');
const GSD_TEMP_DIR = path.join(os.tmpdir(), 'gsd');
@@ -121,14 +121,14 @@ describe('dedicated gsd temp subdirectory', () => {
// The legacy reap function should still clean old-location files
// We import it if exported, or verify the main reap handles both
const core = require('../gsd-core/bin/lib/core.cjs');
if (typeof core.reapStaleTempFilesLegacy === 'function') {
core.reapStaleTempFilesLegacy(testPrefix, { maxAgeMs: 5 * 60 * 1000 });
const ioModule = require('../gsd-core/bin/lib/io.cjs');
if (typeof ioModule.reapStaleTempFilesLegacy === 'function') {
ioModule.reapStaleTempFilesLegacy(testPrefix, { maxAgeMs: 5 * 60 * 1000 });
assert.ok(!fs.existsSync(oldLocationPath), 'legacy reap should clean old location');
} else {
// If no separate legacy function, the main output() should do a one-time
// migration sweep. We just verify the export shape is correct.
assert.ok(typeof core.reapStaleTempFiles === 'function');
assert.ok(typeof ioModule.reapStaleTempFiles === 'function');
// Clean up manually since we're not testing migration here
fs.unlinkSync(oldLocationPath);
}

View File

@@ -833,7 +833,7 @@ describe('path traversal rejection', () => {
});
describe('setActiveWorkstream rejects invalid names directly', () => {
const { setActiveWorkstream } = require('../gsd-core/bin/lib/core.cjs');
const { setActiveWorkstream } = require('../gsd-core/bin/lib/planning-workspace.cjs');
for (const name of maliciousNames) {
test(`throws for ${name}`, () => {
assert.throws(