refactor(#1281): T2 — migrate 12 single-leaf callers off the core spine (batch 1) (#1282)

Per the T1 design rubber-duck, batch by FILE so each tranche drops
convergence-lint allowlist entries. Migrate 12 files' core imports to the
leaf modules directly (behaviour-identical — leaves are the objects core
re-exports by reference):
- io (output/error/ERROR_REASON): agent-command-router, capability-state,
  capability-writer, frontmatter, gsd2-import, learnings, loop-resolver,
  task-command-router
- roadmap-command-router -> config-loader; workstream-inventory -> core-utils
- milestone, verify -> their full leaf sets (both were multi-leaf, not
  single-leaf as first scoped; migrated completely)

All 12 files now import zero core symbols and are removed from the
allowlist (30 -> 18). core.cts re-exports untouched (still serve the
remaining 18 files); teardown is T-final. Stale core.cjs docstrings in the
migrated files corrected to reference io.cjs. No behaviour change.

Closes #1281

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-15 16:18:54 -04:00
committed by GitHub
parent 13414cb168
commit a5f213e73e
13 changed files with 59 additions and 72 deletions

View File

@@ -2,34 +2,22 @@
"_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": [
"gsd-core/bin/gsd-tools.cjs",
"src/agent-command-router.cts",
"src/audit-command-router.cts",
"src/capability-state.cts",
"src/capability-writer.cts",
"src/check-command-router.cts",
"src/commands.cts",
"src/config.cts",
"src/docs.cts",
"src/frontmatter.cts",
"src/gap-checker.cts",
"src/graphify-command-router.cts",
"src/gsd2-import.cts",
"src/init.cts",
"src/intel-command-router.cts",
"src/learnings.cts",
"src/loop-resolver.cts",
"src/milestone.cts",
"src/phase.cts",
"src/profile-output.cts",
"src/roadmap-command-router.cts",
"src/roadmap.cts",
"src/state.cts",
"src/task-command-router.cts",
"src/template.cts",
"src/uat.cts",
"src/verification.cts",
"src/verify.cts",
"src/workstream-inventory.cts",
"src/workstream.cts"
]
}

View File

@@ -7,8 +7,8 @@
*/
// eslint-disable-next-line @typescript-eslint/no-require-imports
import core = require('./core.cjs');
const { output, error, ERROR_REASON } = core;
import io = require('./io.cjs');
const { output, error, ERROR_REASON } = io;
// ─── Types ────────────────────────────────────────────────────────────────────

View File

@@ -17,9 +17,9 @@
* pure, config-only resolution with no filesystem I/O.
* cmdCapabilityState is the I/O handler.
*
* Dependencies (leaf modules only — no core.cjs circular risk):
* Dependencies (leaf modules only — no circular risk):
* - node:path
* - ./core.cjs (output, error)
* - ./io.cjs (output, error)
* - ./capability-activation.cjs (_resolveActivationValue)
* - ./install-profiles.cjs (readActiveProfile, loadSkillsManifest, resolveProfile)
* - ./surface.cjs (resolveSurface)
@@ -32,8 +32,8 @@ import path from 'node:path';
import fs from 'node:fs';
// eslint-disable-next-line @typescript-eslint/no-require-imports
import core = require('./core.cjs');
const { output: coreOutput, error: coreError } = core;
import ioMod = require('./io.cjs');
const { output: coreOutput, error: coreError } = ioMod;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import activationMod = require('./capability-activation.cjs');
@@ -363,7 +363,7 @@ function _resolveManifest(commandsGsdDir: string, configDir: string): Map<string
/**
* Command entry point: resolve install profile, surface, and config; compute
* capability state; emit the envelope via core.output.
* capability state; emit the envelope via io.output.
*
* Envelope: { runtimeConfigDir, warnings?: string[], capabilities: CapabilityStateEntry[] }
*
@@ -388,7 +388,7 @@ function _resolveManifest(commandsGsdDir: string, configDir: string): Map<string
* Providing a value without a next token (e.g. the flag
* is last in argv with no following value) should be
* caught by the caller before invoking this function.
* @param raw Whether to emit raw JSON (core.output raw mode)
* @param raw Whether to emit raw JSON (io.output raw mode)
* @param _options Reserved for future use
*/
function resolveCapabilityRuntimeState(

View File

@@ -13,7 +13,7 @@
* - re-resolve: always calls resolveCapabilityRuntimeState for the return value
*
* Dependencies (leaf modules only — no circular risk):
* - ./core.cjs (output, error)
* - ./io.cjs (output, error)
* - ./capability-state.cjs (resolveCapabilityRuntimeState, _resolveManifest, _resolveCommandsGsdDir)
* - ./surface.cjs (readSurface, writeSurface, applySurface)
* - ./install-profiles.cjs (readActiveProfile)
@@ -23,8 +23,8 @@
*/
// eslint-disable-next-line @typescript-eslint/no-require-imports
import core = require('./core.cjs');
const { output: coreOutput, error: coreError } = core;
import ioMod = require('./io.cjs');
const { output: coreOutput, error: coreError } = ioMod;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import capabilityStateMod = require('./capability-state.cjs');

View File

@@ -9,8 +9,8 @@
import fs from 'node:fs';
import path from 'node:path';
// eslint-disable-next-line @typescript-eslint/no-require-imports
import core = require('./core.cjs');
const { output, error } = core;
import ioMod = require('./io.cjs');
const { output, error } = ioMod;
import { platformReadSync as safeReadFile, platformWriteSync } from './shell-command-projection.cjs';
// ─── Types ────────────────────────────────────────────────────────────────────

View File

@@ -24,8 +24,8 @@ import path from 'node:path';
import { platformWriteSync } from './shell-command-projection.cjs';
import { formatGsdSlash, resolveRuntime } from './runtime-slash.cjs';
// eslint-disable-next-line @typescript-eslint/no-require-imports
import core = require('./core.cjs');
const { output } = core;
import ioMod = require('./io.cjs');
const { output } = ioMod;
// ─── Types ───────────────────────────────────────────────────────────────────

View File

@@ -20,8 +20,8 @@ import path from 'node:path';
import crypto from 'node:crypto';
import os from 'node:os';
// eslint-disable-next-line @typescript-eslint/no-require-imports
import core = require('./core.cjs');
const { output, error: coreError } = core;
import ioMod = require('./io.cjs');
const { output, error: coreError } = ioMod;
import { platformWriteSync } from './shell-command-projection.cjs';
// ─── Types ───────────────────────────────────────────────────────────────────

View File

@@ -19,11 +19,11 @@
* Both pure functions (resolveLoopHooks, renderLoopHooks) take explicit
* registry/config arguments so they are trivially testable without I/O.
*
* Dependencies (leaf modules only — no core.cjs circular risk):
* Dependencies (leaf modules only — no circular risk):
* - node:fs / node:path (raw config.json read for capability-key activation)
* - ./config-loader.cjs (loadConfig)
* - ./planning-workspace.cjs (planningDir — to locate config.json)
* - ./core.cjs (output, error)
* - ./io.cjs (output, error)
* - loop-host-contract.cjs (CANONICAL_POINTS via LOOP_HOST_CONTRACT)
* - capability-registry.cjs (byLoopPoint, consumed at call time)
*/
@@ -32,8 +32,8 @@ import fs from 'node:fs';
import path from 'node:path';
// eslint-disable-next-line @typescript-eslint/no-require-imports
import core = require('./core.cjs');
const { output: coreOutput, error: coreError } = core;
import ioMod = require('./io.cjs');
const { output: coreOutput, error: coreError } = ioMod;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import configLoaderModule = require('./config-loader.cjs');
@@ -284,7 +284,7 @@ interface ResolveLoopHooksResult {
* Pure resolver: given a point, registry, and config, returns the active hooks.
*
* Throws if `point` is not one of the 12 canonical points (caller converts to
* core.error). Never throws for malformed registry/hook entries — skips and
* io.error). Never throws for malformed registry/hook entries — skips and
* continues.
*
* Ordering: steps first, then contributions, then gates. Within each array,
@@ -571,7 +571,7 @@ function renderLoopHooks(resolved: ResolveLoopHooksResult): string {
* Command entry point: load registry + config, resolve + render, emit envelope.
*
* Envelope: { point, activeHooks, rendered }
* On invalid point, emits core.error instead of throwing.
* On invalid point, emits io.error instead of throwing.
*
* Config note: FIX 1 replaced _loadMergedConfig (whole-config deep-merge) with a
* per-hook single-key activation resolver (_resolveActivationValue). The resolver

View File

@@ -8,8 +8,6 @@
import fs from 'node:fs';
import path from 'node:path';
// eslint-disable-next-line @typescript-eslint/no-require-imports -- core.cjs is an export= CommonJS module
import core = require('./core.cjs');
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
import planningWorkspace = require('./planning-workspace.cjs');
// eslint-disable-next-line @typescript-eslint/no-require-imports -- frontmatter.cjs is an export= CommonJS module
@@ -18,16 +16,18 @@ import frontmatterMod = require('./frontmatter.cjs');
import stateMod = require('./state.cjs');
import { platformWriteSync, platformEnsureDir } from './shell-command-projection.cjs';
import { formatGsdSlash, resolveRuntime } from './runtime-slash.cjs';
const {
escapeRegex,
getMilestonePhaseFilter,
extractOneLinerFromBody,
normalizePhaseName,
phaseTokenMatches,
output,
error,
} = core;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import ioMod = require('./io.cjs');
const { output, error } = ioMod;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import phaseIdMod = require('./phase-id.cjs');
const { escapeRegex, normalizePhaseName, phaseTokenMatches } = phaseIdMod;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import roadmapParserMod = require('./roadmap-parser.cjs');
const { getMilestonePhaseFilter, extractCurrentMilestone } = roadmapParserMod;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import coreUtilsMod = require('./core-utils.cjs');
const { extractOneLinerFromBody } = coreUtilsMod;
const { planningPaths } = planningWorkspace;
const { extractFrontmatter } = frontmatterMod;
const { writeStateMd, stateReplaceFieldWithFallback } = stateMod;
@@ -167,7 +167,6 @@ function cmdMilestoneComplete(cwd: string, version: string, options: MilestoneCo
}
if (stateVersion && stateVersion === version) {
const { extractCurrentMilestone } = core;
const roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
const scopedContent = extractCurrentMilestone(roadmapContent, cwd);
const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi;

View File

@@ -19,8 +19,8 @@ import roadmapUpgrade = require('./roadmap-upgrade.cjs');
import planningWorkspace = require('./planning-workspace.cjs');
const { planningDir } = planningWorkspace;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import core = require('./core.cjs');
const { loadConfig } = core;
import configLoaderMod = require('./config-loader.cjs');
const { loadConfig } = configLoaderMod;
// ─── Types ────────────────────────────────────────────────────────────────────

View File

@@ -9,8 +9,8 @@
import fs from 'node:fs';
import path from 'node:path';
// eslint-disable-next-line @typescript-eslint/no-require-imports
import core = require('./core.cjs');
const { output, error, ERROR_REASON } = core;
import ioMod = require('./io.cjs');
const { output, error, ERROR_REASON } = ioMod;
// ─── Types ────────────────────────────────────────────────────────────────────

View File

@@ -11,8 +11,6 @@ import path from 'node:path';
import os from 'node:os';
import { phaseVariants, buildRoadmapPhaseVariants, buildNotStartedPhaseVariants } from './validate.cjs';
import { phaseDirNameRe, PHASE_TOKEN_FROM_DIR_RE, MILESTONE_ARCHIVE_DIR_RE, canonicalPlanStem } from './validate.cjs';
// eslint-disable-next-line @typescript-eslint/no-require-imports -- core.cjs is an export= CommonJS module
import core = require('./core.cjs');
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
import planningWorkspace = require('./planning-workspace.cjs');
// eslint-disable-next-line @typescript-eslint/no-require-imports -- frontmatter.cjs is an export= CommonJS module
@@ -31,21 +29,24 @@ import { isCanonicalPlanningFile } from './artifacts.cjs';
// eslint-disable-next-line @typescript-eslint/no-require-imports -- agent-install-check.cjs is an export= CommonJS module
import agentInstallCheck = require('./agent-install-check.cjs');
const { checkAgentsInstalled } = agentInstallCheck;
const {
loadConfig,
normalizePhaseName,
phaseTokenMatches,
escapeRegex,
findPhaseInternal,
getMilestoneInfo,
stripShippedMilestones,
extractCurrentMilestone,
output,
error,
CONFIG_DEFAULTS,
inspectWorktreeHealth,
} = core;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import ioMod = require('./io.cjs');
const { output, error } = ioMod;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import configLoaderMod = require('./config-loader.cjs');
const { loadConfig, CONFIG_DEFAULTS } = configLoaderMod;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import phaseIdMod = require('./phase-id.cjs');
const { normalizePhaseName, phaseTokenMatches, escapeRegex, getMilestoneFromPhaseId } = phaseIdMod;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import phaseLocatorMod = require('./phase-locator.cjs');
const { findPhaseInternal } = phaseLocatorMod;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import roadmapParserMod = require('./roadmap-parser.cjs');
const { getMilestoneInfo, stripShippedMilestones, extractCurrentMilestone } = roadmapParserMod;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import worktreeSafetyMod = require('./worktree-safety.cjs');
const { inspectWorktreeHealth } = worktreeSafetyMod;
const { planningDir } = planningWorkspace;
const { extractFrontmatter, parseMustHavesBlock } = frontmatterMod;
@@ -1402,7 +1403,6 @@ function cmdValidateHealth(
if (phaseConvention === 'milestone-prefixed') {
if (fs.existsSync(roadmapPath)) {
const roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
const { getMilestoneFromPhaseId } = core;
const mismatches = checkMilestonePrefixMismatches(roadmapContent, {
getMilestoneFromPhaseId: getMilestoneFromPhaseId,
});

View File

@@ -16,8 +16,8 @@
import fs from 'node:fs';
import path from 'node:path';
// eslint-disable-next-line @typescript-eslint/no-require-imports
import core = require('./core.cjs');
const { readSubdirectories } = core;
import coreUtilsMod = require('./core-utils.cjs');
const { readSubdirectories } = coreUtilsMod;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import planScan = require('./plan-scan.cjs');
// eslint-disable-next-line @typescript-eslint/no-require-imports