* feat(#3545): widen no-source-grep with one-hop path-fold and hooks dir Resolve a readFileSync() path argument that is a bare Identifier one hop back to its VariableDeclarator initializer before classification, and recognize `hooks` as a source directory alongside bin/lib/gsd-core/src. Measured (epic #3464 phase 7): fold+hooks together newly flag 76 unsuppressed sites across 18 files that were previously invisible to identifier-indirected or hooks/-rooted source reads. Neither widening alone is sufficient — hooks-only surfaces 0 new sites, confirming #3520's prior finding that the identifier-indirection gap must close first. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * test(#3545): migrate 76 sites newly flagged by the fold+hooks widening Per-site classification: rewrite behaviorally (require() the real module, assert on its actual exported behavior) wherever the read was a proxy for code behavior; add a site-scoped `// allow-test-rule: <reason> (#3545)` marker only where the raw source text genuinely is the product under test (codex-config.test.cjs's adapter-header-contract checks, install.js structural-wiring guards with no exported symbol, AST-parse fixture inputs, etc.) — each marker cites an existing repo-sanctioned category from CONTRIBUTING.md's allow-test-rule exception table. Also converts two try/finally test bodies (introduced during this same migration) to the required t.after() cleanup pattern per CONTRIBUTING.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore(#3545): re-baseline effective-exemption ceiling to 81 The fold+hooks widening's own newly-detected sites are now suppressed by site-scoped markers, moving them from invisible into the tightly-ratcheted effective-exemption count. Ceiling rises from 10 to 81 (the exact measured high-water mark, grace unchanged at 2) — a deliberate, measured re-baseline per the widening working as intended, not an ordinary ceiling bump. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(#3545): use canonical allow-test-rule category tokens 4 markers added during migration cited an issue ref correctly but didn't use one of CONTRIBUTING.md's seven recognized category tokens, unlike every other marker in this change. Cosmetic only — same suppression lines, same effective/live counts (81/81, 0 live). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(#3545): correct stale phase-artifact path in test comment Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: sim <sim@local> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
141 lines
6.1 KiB
JavaScript
141 lines
6.1 KiB
JavaScript
'use strict';
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// #3799 — legacy cleanup must honor the install's resolved destination.
|
|
//
|
|
// `install()` called `cleanupLegacyGsdCc({ dryRun: false })` with no
|
|
// arguments, so the scan always ran against `os.homedir()` +
|
|
// `_LEGACY_SCAN_SUBDIR_NAMES` — an install redirected with `--config-dir
|
|
// ~/.sandbox` planned removals of a LIVE legacy install under the DEFAULT
|
|
// home (the reporter's dry-run listed 60 default-home paths; a real install
|
|
// would have deleted 59 files). The cleanup now accepts an explicit
|
|
// `configDirs` scope (install() passes `[targetDir]` when --config-dir
|
|
// redirected the destination), and `--no-legacy-cleanup` skips the scan.
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
const { test, describe, beforeEach, afterEach } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
process.env.GSD_TEST_MODE = '1';
|
|
|
|
const REPO_ROOT = path.join(__dirname, '..');
|
|
const INSTALL_BIN = path.join(REPO_ROOT, 'bin', 'install.js');
|
|
const { cleanupLegacyGsdCc } = require(INSTALL_BIN);
|
|
const { createTempDir, cleanup } = require('./helpers.cjs');
|
|
const { runNode } = require('./helpers/process-seam.cjs');
|
|
|
|
const LEGACY_PKG_SIGNAL = 'get-shit-done-cc';
|
|
|
|
function seedLegacySkill(configDir) {
|
|
// configDir is a CONFIG DIR root (like ~/.claude): artifacts live at
|
|
// <configDir>/skills/gsd-*/SKILL.md. The scan's content signal is a stale
|
|
// '/get-shit-done/' PATH reference (legacy-cleanup.cjs // gsd-allow-legacy-name
|
|
// LEGACY_SKILL_PATH_SIGNAL), not the bare package name.
|
|
const skillDir = path.join(configDir, 'skills', 'gsd-add-tests');
|
|
fs.mkdirSync(skillDir, { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(skillDir, 'SKILL.md'),
|
|
`<!-- installed via ${LEGACY_PKG_SIGNAL} -->\nSee ~/.claude/get-shit-done/skills/gsd-add-tests/SKILL.md\n`, // gsd-allow-legacy-name
|
|
);
|
|
return path.join(skillDir, 'SKILL.md');
|
|
}
|
|
|
|
describe('#3799: cleanupLegacyGsdCc honors an explicit configDirs scope', () => {
|
|
let tmpRoot;
|
|
let defaultHome;
|
|
let sandboxDir;
|
|
|
|
beforeEach(() => {
|
|
tmpRoot = createTempDir('gsd-3799-cleanup-');
|
|
defaultHome = path.join(tmpRoot, 'home');
|
|
sandboxDir = path.join(tmpRoot, 'pilot-sandbox');
|
|
fs.mkdirSync(defaultHome, { recursive: true });
|
|
fs.mkdirSync(sandboxDir, { recursive: true });
|
|
});
|
|
|
|
afterEach(() => {
|
|
cleanup(tmpRoot);
|
|
});
|
|
|
|
test('#3799: an explicit configDirs scope never plans removals outside it', () => {
|
|
// A LIVE legacy install under the default home (at <home>/.claude, one
|
|
// of _LEGACY_SCAN_SUBDIR_NAMES)…
|
|
const liveArtifact = seedLegacySkill(path.join(defaultHome, '.claude'));
|
|
// …and one stale artifact inside the redirected sandbox.
|
|
const sandboxArtifact = seedLegacySkill(sandboxDir);
|
|
|
|
const { plan } = cleanupLegacyGsdCc({
|
|
homeDir: defaultHome,
|
|
configDirs: [sandboxDir],
|
|
dryRun: true,
|
|
logger: { log: () => {} },
|
|
});
|
|
|
|
const paths = plan.map((p) => p.path);
|
|
assert.ok(
|
|
paths.every((p) => p.startsWith(sandboxDir)),
|
|
`#3799: every plan entry must live inside the explicit scope; got ${JSON.stringify(paths)}`,
|
|
);
|
|
assert.ok(paths.includes(sandboxArtifact), 'the sandbox artifact is in scope and planned');
|
|
assert.ok(
|
|
!paths.includes(liveArtifact),
|
|
'#3799: the default home\'s live legacy install must not be planned for removal',
|
|
);
|
|
assert.ok(
|
|
fs.existsSync(liveArtifact),
|
|
'dry-run leaves everything in place',
|
|
);
|
|
});
|
|
|
|
test('#3799 control: without the override, the home scan is unchanged', () => {
|
|
const liveArtifact = seedLegacySkill(path.join(defaultHome, '.claude'));
|
|
const { plan } = cleanupLegacyGsdCc({
|
|
homeDir: defaultHome,
|
|
dryRun: true,
|
|
logger: { log: () => {} },
|
|
});
|
|
assert.ok(
|
|
plan.map((p) => p.path).includes(liveArtifact),
|
|
'default behavior (no scope) still scans homeDir subdirs as before',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('#3799: --no-legacy-cleanup and --config-dir CLI flags', () => {
|
|
test('the --no-legacy-cleanup flag exists and is documented in --help', () => {
|
|
const result = runNode([INSTALL_BIN, '--help']);
|
|
assert.equal(result.outcome, 'exited', `install.js --help did not exit cleanly: ${result.stderr}`);
|
|
assert.ok(
|
|
result.stdout.includes('--no-legacy-cleanup'),
|
|
'the escape hatch must be documented in the installer --help output',
|
|
);
|
|
});
|
|
|
|
test('the install() call site threads the config-dir scope and the skip flag', () => {
|
|
// allow-test-rule: structural-implementation-guard (#3545) — structural assertion
|
|
// on install()'s internal wiring (that its cleanupLegacyGsdCc call site threads configDirs/
|
|
// skipNoLegacyCleanup) — install() is a ~2500-line, side-effect-heavy
|
|
// top-level installer routine, so exercising this specific plumbing
|
|
// behaviorally would require a full install() run; the source-slice
|
|
// check is the minimum-cost regression guard for the exact #3799 defect
|
|
// shape
|
|
const src = fs.readFileSync(INSTALL_BIN, 'utf-8');
|
|
// Slice the install() body up to its cleanup call so the conditional
|
|
// spread's braces cannot defeat a single-regex match.
|
|
const fnStart = src.indexOf('function install(isGlobal, runtime = DEFAULT_RUNTIME');
|
|
const callIdx = src.indexOf('cleanupLegacyGsdCc({ dryRun: false', fnStart);
|
|
assert.ok(fnStart > 0 && callIdx > fnStart, 'install() still calls cleanupLegacyGsdCc');
|
|
const callSite = src.slice(callIdx, callIdx + 240);
|
|
assert.ok(
|
|
/configDirs/.test(callSite),
|
|
'#3799: the call site must pass a configDirs scope',
|
|
);
|
|
assert.ok(
|
|
/skipNoLegacyCleanup/.test(src.slice(fnStart, callIdx + 400)),
|
|
'#3799: the call site must honor the --no-legacy-cleanup skip',
|
|
);
|
|
});
|
|
});
|