refactor(#1294): T-final — delete the core.cjs re-export spine (epic #1267 complete) (#1295)

* refactor(#1294): T-final — delete the core.cjs re-export spine (epic #1267 complete)

After T0–T6 nothing imports core, so retire the spine and its scaffolding:
- delete src/core.cts (and the gitignored gsd-core/bin/lib/core.cjs artifact;
  remove its .gitignore + eslint-ignore entries)
- delete scripts/lint-core-spine-imports.cjs + its allowlist; drop it from the
  package.json lint:ci chain
- regenerate docs/INVENTORY-MANIFEST.json (drops the core.cjs surface)
- sweep stale references: CONTEXT.md glossary back-compat clauses (spine retired,
  callers import the leaf directly), planning-config.md CONFIG_DEFAULTS owner,
  and false present-tense core.cjs claims in leaf-module docstrings

The ADR-857 decomposition is complete: the former Core god-module is fully
dissolved into its leaf modules; no re-export spine remains. No behaviour change.

Closes #1294

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

* fix(#1294): migrate the computed-path core.cjs importers the literal grep missed

bin/install.js used require(path.join(_gsdLibDir, 'core.cjs')) (a computed
path, and bin/install.js was never in the convergence lint's scan roots), and
~8 test files referenced core.cjs via path.join/readFileSync/existsSync/FILE_ARG
forms the literal-string migration grep missed. Route install.js's symbols to
their leaves (RUNTIME_PROFILE_MAP->model-catalog, resolveTierEntry/EFFORT_SET->
model-resolver) and repoint/adjust the test references to the leaves. Recovers
the 161 'Cannot find module core.cjs' failures from the spine deletion.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-15 18:50:46 -04:00
committed by GitHub
parent c76827afbc
commit 8c3d934a90
31 changed files with 62 additions and 501 deletions

View File

@@ -1,4 +0,0 @@
{
"_comment": "Files allowed to import the core re-export spine during the staged retirement (issue #1268). Entries are REMOVED as each tranche migrates a leaf; the file + this lint are deleted in T-final.",
"allow": []
}

View File

@@ -1,203 +0,0 @@
#!/usr/bin/env node
'use strict';
/**
* Migration-convergence lint for the core re-export spine (issue #1268).
*
* The spine (core.cjs) is being staged for retirement: each tranche migrates a
* set of leaf importers to consume the individual modules directly. This lint
* makes CI RED the moment a NEW file starts importing the spine, preventing
* regressions as the tranche work proceeds.
*
* Allowlisted importers (the "T0 set") are files that already imported the
* spine when this lint was introduced. Entries are REMOVED from the allowlist
* as each tranche migrates a leaf. The allowlist + this script are deleted in
* T-final.
*
* Model: scripts/lint-package-identity-drift.cjs
*/
const fs = require('node:fs');
const path = require('node:path');
// ---------------------------------------------------------------------------
// Core-spine import detection
// ---------------------------------------------------------------------------
/**
* Matches any import form that targets the core spine at any relative depth,
* including the TS `import x = require(...)` form, dynamic imports, and bare
* side-effect imports.
*
* Patterns matched (illustrative):
* require('./core.cjs')
* require('./core')
* require('../core.cjs')
* require('./lib/core.cjs')
* require('../lib/core.cjs')
* require('../../lib/core.cjs')
* from './core.cjs'
* import core = require('../lib/core.cjs')
* import('./core.cjs') dynamic import
* await import('../lib/core.cjs') dynamic import
* import './core.cjs'; bare side-effect import
*
* Patterns NOT matched (boundary after bare `core` is quote or `.cjs`):
* require('./core-utils.cjs')
* require('./core-schema.cjs')
*
* The `import\s+` branch (bare side-effect) will NOT match
* `import x = require(...)` because a non-quote token follows `import `.
*/
const CORE_IMPORT_RE = /(?:require\(\s*|import\s*\(\s*|from\s+|import\s+)['"](?:\.\.?\/)+(?:lib\/)?core(?:\.cjs)?['"]/;
/**
* Return true if `line` is a real (non-commented) core-spine import.
* @param {string} line
* @returns {boolean}
*/
function lineImportsSpine(line) {
if (line.trimStart().startsWith('//')) return false;
return CORE_IMPORT_RE.test(line);
}
// ---------------------------------------------------------------------------
// Directory walking
// ---------------------------------------------------------------------------
/**
* Recursively collect files under `dir` matching `extSet`, skipping
* `node_modules`, `.git`, `.memdb`, and any caller-supplied `skipDirs`.
* (The caller is responsible for scoping roots; this function does NOT itself
* skip `tests/` — that exclusion is achieved by not passing `tests/` as a root.)
*
* @param {string} dir - Absolute directory to walk.
* @param {Set<string>} extSet - File extensions to include (e.g. new Set(['.cts','.cjs'])).
* @param {Set<string>} skipDirs - Absolute paths of directories to skip entirely.
* @param {string[]} acc - Accumulator (modified in place).
* @returns {string[]}
*/
function walk(dir, extSet, skipDirs, acc) {
let entries;
try {
entries = fs.readdirSync(dir, { withFileTypes: true });
} catch {
return acc;
}
for (const entry of entries) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === '.memdb') continue;
if (skipDirs.has(full)) continue;
walk(full, extSet, skipDirs, acc);
} else if (entry.isFile() && extSet.has(path.extname(entry.name))) {
acc.push(full);
}
}
return acc;
}
// ---------------------------------------------------------------------------
// Pure exported API
// ---------------------------------------------------------------------------
/**
* Scan `roots` for source files that import the core spine but are not in
* `allowlistSet`.
*
* @param {string[]} roots - Absolute directories to scan.
* @param {Set<string>} allowlistSet - Repo-relative POSIX paths that are
* allowed to import the spine (the T0 set).
* @returns {{ file: string, line: number }[]}
* Repo-relative POSIX file path and 1-based line number for each violation.
*/
function scanCoreSpineImports(roots, allowlistSet) {
const repoRoot = path.join(__dirname, '..');
// gsd-core/bin/lib is excluded (generated artefacts — double-counts src/).
const skipDirs = new Set([path.join(repoRoot, 'gsd-core', 'bin', 'lib')]);
// src/*.cts → .cts only; gsd-core/bin/**/*.cjs → .cjs only.
// Accept either extension in each root; walk will naturally skip unsupported
// files via extSet filtering.
const extSet = new Set(['.cts', '.cjs']);
const violations = [];
for (const root of roots) {
const files = walk(root, extSet, skipDirs, []);
for (const abs of files) {
// Derive repo-relative POSIX path for allowlist lookup and reporting.
const rel = path.relative(repoRoot, abs).replace(/\\/g, '/');
let text;
try {
text = fs.readFileSync(abs, 'utf8');
} catch {
continue;
}
const lines = text.split('\n');
for (let i = 0; i < lines.length; i++) {
if (lineImportsSpine(lines[i])) {
if (!allowlistSet.has(rel)) {
violations.push({ file: rel, line: i + 1 });
}
// Do NOT break: report every spine-import line so no violation is masked.
}
}
}
}
return violations;
}
/**
* Load the allowlist JSON and return a Set of repo-relative paths.
*
* @param {string} allowlistPath - Absolute path to the JSON file.
* @returns {Set<string>}
*/
function loadAllowlist(allowlistPath) {
const raw = fs.readFileSync(allowlistPath, 'utf8');
const obj = JSON.parse(raw);
return new Set(obj.allow || []);
}
// ---------------------------------------------------------------------------
// CLI entry point
// ---------------------------------------------------------------------------
function main() {
const repoRoot = path.join(__dirname, '..');
const allowlistPath = path.join(__dirname, 'lint-core-spine-imports.allowlist.json');
const allowlistSet = loadAllowlist(allowlistPath);
const roots = [
path.join(repoRoot, 'src'),
path.join(repoRoot, 'gsd-core', 'bin'),
];
const violations = scanCoreSpineImports(roots, allowlistSet);
if (violations.length === 0) {
process.stdout.write(
`ok core-spine-imports: ${allowlistSet.size} allowlisted importer(s), 0 new\n`,
);
return;
}
process.stderr.write('core-spine-imports: new file(s) importing the core re-export spine detected.\n');
process.stderr.write('The spine (core.cjs) is being retired (issue #1268). Import the leaf module\n');
process.stderr.write('directly instead, or add to the allowlist only if genuinely transitional:\n');
for (const v of violations) {
process.stderr.write(` ${v.file}:${v.line}\n`);
}
process.stderr.write(`core-spine-imports: ${violations.length} violation(s)\n`);
process.exitCode = 1;
}
if (require.main === module) main();
module.exports = { scanCoreSpineImports, loadAllowlist };