enhance(#3545): widen no-source-grep with fold+hooks, migrate 76 sites (#4161)

* 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>
This commit is contained in:
Tom Boucher
2026-09-01 21:40:38 -04:00
committed by GitHub
parent ff0361071d
commit f16ff7d1b3
21 changed files with 360 additions and 83 deletions

View File

@@ -21,6 +21,12 @@
* call that originated the tracked value -- so annotating the read directly
* (the intuitive placement) suppresses the violation just as well as
* annotating the search call (adversarial-review fix, epic #3464 phase 4).
*
* A `readFileSync(...)` path argument that is a bare Identifier is resolved
* ONE hop back to its `VariableDeclarator` initializer before classification,
* so a path built once and passed by reference is recognized the same as an
* inline path expression; `hooks` is also a recognized source directory
* alongside `bin`/`lib`/`gsd-core`/`src` (#3545 / epic #3464 phase 7).
*/
// How many derivation hops from the original readFileSync() binding to
@@ -326,9 +332,27 @@ const rule = {
return identifierVariableMap.get(identifierNode) || null;
}
// WIDENING (fold, #3545/epic #3464 phase 7): resolve a bare Identifier
// path argument back to its variable initializer, ONE hop, before
// text-matching it -- so `const p = path.join(__dirname, '..', 'src',
// 'x.cjs'); readFileSync(p)` is classified the same as an inline
// `readFileSync(path.join(__dirname, '..', 'src', 'x.cjs'))`. Only
// resolves a `VariableDeclarator`'s `init` (not an `AssignmentExpression`
// — an assignment-bound identifier is a documented, deliberate miss, see
// 40-design.md "Known limits"), and only ONE hop (a chain of two or more
// indirections stays invisible -- also documented).
function resolveOneHopText(node) {
if (node.type !== 'Identifier') return sourceCode.getText(node);
const v = resolveVariable(node);
if (!v) return sourceCode.getText(node);
const def = v.defs.find((d) => d.type === 'Variable' && d.node && d.node.init);
if (!def) return sourceCode.getText(node);
return sourceCode.getText(def.node.init);
}
// Detect if a node represents a readFileSync call on a source file
// (.cjs/.cts/.js/.mjs/.mts/.ts) that lives in a source directory
// (bin, lib, gsd-core, src).
// (bin, lib, gsd-core, hooks, src).
function isSourceReadFileSync(node) {
if (!node || node.type !== 'CallExpression') return false;
@@ -345,7 +369,7 @@ const rule = {
if (!args || args.length === 0) return false;
const firstArg = args[0];
const fullSrc = sourceCode.getText(firstArg);
const fullSrc = resolveOneHopText(firstArg);
return looksLikeSourcePath(fullSrc);
}
@@ -359,8 +383,9 @@ const rule = {
const hasSourceExt = /['"`.][^'"`.]*\.(?:cts|mts|mjs|cjs|js|ts)['"`)]/i.test(src);
if (!hasSourceExt) return false;
// Must reference a source directory indicator somewhere in the expression
const hasSourceDir = /['"](?:bin|lib|gsd-core|src)['"]/i.test(src);
// Must reference a source directory indicator somewhere in the
// expression (bin, lib, gsd-core, hooks, src).
const hasSourceDir = /['"](?:bin|lib|gsd-core|hooks|src)['"]/i.test(src);
return hasSourceDir;
}

View File

@@ -1,4 +1,4 @@
{
"maxSites": 10,
"maxSites": 81,
"grace": 2
}

View File

@@ -55,6 +55,11 @@ describe('#1656: community .sh hooks must be present in hooks/dist', () => {
describe('#1657 / #191: installer/package metadata retires sdk seam', () => {
let src;
test('install.js does not contain the legacy promptSdk() prompt (#1657)', () => {
// allow-test-rule: structural-regression-guard (#3545) — regression guard
// against literally reintroducing dead/removed code strings (promptSdk(),
// --sdk/--no-sdk parsing, installSdkIfNeeded({) — none of them are live
// exports or behavior to exercise via require(), so the source text
// itself is the contract this test protects
src = fs.readFileSync(INSTALL_SRC, 'utf-8');
assert.ok(
!src.includes('promptSdk('),

View File

@@ -765,6 +765,10 @@ describe('--check drift detection', () => {
// helpers the CLI uses, applied to in-memory strings — giving identical coverage
// without touching the filesystem.
const originalContent = fs.readFileSync(REGISTRY_PATH, 'utf8');
// allow-test-rule: source-text-is-the-product (#3545) — checkPipeline() below is a
// raw-text diffing pipeline; this .replace() builds an in-memory tampered
// TEXT fixture to drive that real pipeline call, not a text-grep proxy for
// module behavior
const tamperedContent = originalContent.replace(
"version: '" + SCHEMA_VERSION + "'",
"version: '0-stale'",
@@ -790,6 +794,8 @@ describe('--check drift detection', () => {
// Also verify the tampered content contains the stale marker (so the above
// assertion is meaningful and not vacuously true due to other diff).
assert.ok(
// allow-test-rule: source-text-is-the-product (#3545) — sanity check on the
// same in-memory tampered TEXT fixture, not a proxy for module behavior
tamperedContent.includes("version: '0-stale'"),
'precondition: tampered content must contain the stale version marker',
);

View File

@@ -269,6 +269,9 @@ describe('bug #224: --pick stdout capture contract', () => {
let src;
before(() => {
// allow-test-rule: structural-implementation-guard (see #224) — locks the
// main()-body byte-length seam contract structurally (no deterministic
// Windows repro harness yet); see block header above.
src = fs.readFileSync(GSD_TOOLS_SRC, 'utf-8');
});

View File

@@ -3314,32 +3314,36 @@ test('writeNonClaudeDefaults is called before installCodexConfig in the Codex in
const src = fs.readFileSync(INSTALL_JS, 'utf8');
// Find the call to writeNonClaudeDefaults that precedes installCodexConfig.
const writeIdx = src.indexOf('writeNonClaudeDefaults(runtime);');
const writeIdx = src.indexOf('writeNonClaudeDefaults(runtime);'); // allow-test-rule: structural-implementation-guard (#2834)
assert.ok(writeIdx !== -1, 'writeNonClaudeDefaults(runtime) must be called in the install flow');
// Find the FIRST installCodexConfig call AFTER the writeNonClaudeDefaults call.
const codexGenIdx = src.indexOf('installCodexConfig(targetDir', writeIdx);
const codexGenIdx = src.indexOf('installCodexConfig(targetDir', writeIdx); // allow-test-rule: structural-implementation-guard (#2834)
assert.ok(codexGenIdx !== -1 && codexGenIdx > writeIdx,
'installCodexConfig must be called AFTER writeNonClaudeDefaults so defaults.json ' +
'(resolve_model_ids + runtime) exists before agent TOML generation reads it (#2834)');
// The #2834 comment must be present at the call site.
const callSite = src.slice(writeIdx - 300, writeIdx + 100);
assert.ok(/#2834/.test(callSite), 'the writeNonClaudeDefaults call must carry the #2834 rationale comment');
assert.ok(/#2834/.test(callSite), 'the writeNonClaudeDefaults call must carry the #2834 rationale comment'); // allow-test-rule: structural-implementation-guard (#2834)
});
test('writeNonClaudeDefaults function exists and is a no-op for Claude (#2834)', () => {
const src = fs.readFileSync(INSTALL_JS, 'utf8');
const fnIdx = src.indexOf('function writeNonClaudeDefaults(');
const fnIdx = src.indexOf('function writeNonClaudeDefaults('); // allow-test-rule: structural-implementation-guard (#2834)
assert.ok(fnIdx !== -1, 'writeNonClaudeDefaults must be defined as a function');
// Bound the slice by the next top-level declaration rather than a fixed
// character count, so adding a comment or a guard inside the function cannot
// push the asserted tokens out of the window and red this test spuriously.
const nextFnIdx = src.indexOf('\nfunction ', fnIdx + 1);
const nextFnIdx = src.indexOf('\nfunction ', fnIdx + 1); // allow-test-rule: structural-implementation-guard (#2834)
const fnBody = src.slice(fnIdx, nextFnIdx === -1 ? undefined : nextFnIdx);
assert.ok(/nativeModelAliases/.test(fnBody), 'writeNonClaudeDefaults must early-return for Claude (nativeModelAliases check)');
assert.ok(/resolve_model_ids/.test(fnBody), 'writeNonClaudeDefaults must write resolve_model_ids');
assert.ok(/defaults\.runtime/.test(fnBody), 'writeNonClaudeDefaults must write runtime');
// Source-text guard, not a behavioral call: writeNonClaudeDefaults() early-returns
// as a no-op whenever process.env.GSD_TEST_MODE is set (see its own body), and this
// suite sets GSD_TEST_MODE='1' file-wide (line 14), so invoking it here could never
// observe the resolve_model_ids/runtime writes it is supposed to make (#2834).
assert.ok(/nativeModelAliases/.test(fnBody), 'writeNonClaudeDefaults must early-return for Claude (nativeModelAliases check)'); // allow-test-rule: structural-implementation-guard (#2834)
assert.ok(/resolve_model_ids/.test(fnBody), 'writeNonClaudeDefaults must write resolve_model_ids'); // allow-test-rule: structural-implementation-guard (#2834)
assert.ok(/defaults\.runtime/.test(fnBody), 'writeNonClaudeDefaults must write runtime'); // allow-test-rule: structural-implementation-guard (#2834)
});
});
}
@@ -6254,16 +6258,18 @@ const src = fs.readFileSync(INSTALL_JS, 'utf8');
describe('bug #279: Codex adapter documents Agent() and deferred tool discovery', () => {
test('adapter mapping section includes explicit Agent(...) -> spawn_agent mapping', () => {
// allow-test-rule: source-text-is-the-product [adapter header contract in bin/install.js] (see #279)
assert.ok(
/Task\(subagent_type="X", prompt="Y"\).*spawn_agent\(agent_type="X", message="Y"\)/.test(src) &&
/Agent\(subagent_type="X", prompt="Y"\).*spawn_agent\(agent_type="X", message="Y"\)/.test(src),
/Task\(subagent_type="X", prompt="Y"\).*spawn_agent\(agent_type="X", message="Y"\)/.test(src) && // allow-test-rule: source-text-is-the-product [adapter header contract in bin/install.js] (see #279)
/Agent\(subagent_type="X", prompt="Y"\).*spawn_agent\(agent_type="X", message="Y"\)/.test(src), // allow-test-rule: source-text-is-the-product [adapter header contract in bin/install.js] (see #279)
'Codex adapter must explicitly map both Task(...) and Agent(...) to spawn_agent',
);
});
test('adapter includes deferred tool_search discovery guidance before inline fallback', () => {
// allow-test-rule: source-text-is-the-product [adapter header contract in bin/install.js] (see #279)
assert.ok(
src.includes('deferred') && src.includes('tool_search') && src.includes('spawn_agent'),
src.includes('deferred') && src.includes('tool_search') && src.includes('spawn_agent'), // allow-test-rule: source-text-is-the-product [adapter header contract in bin/install.js] (see #279)
'Codex adapter must instruct deferred tool discovery via tool_search before deciding to run inline',
);
});
@@ -10089,9 +10095,10 @@ const src = fs.readFileSync(INSTALL_JS, 'utf8');
// Helper: extract Section C from the raw source text.
// Anchors on the heading and ends at </codex_skill_adapter>.
function getSectionC() {
const headingIdx = src.indexOf('## C. Task() → spawn_agent Mapping');
// allow-test-rule: source-text-is-the-product (see #851)
const headingIdx = src.indexOf('## C. Task() → spawn_agent Mapping'); // allow-test-rule: source-text-is-the-product (see #851)
assert.ok(headingIdx >= 0, 'Section C heading must exist in bin/install.js');
const closeTag = src.indexOf('</codex_skill_adapter>', headingIdx);
const closeTag = src.indexOf('</codex_skill_adapter>', headingIdx); // allow-test-rule: source-text-is-the-product (see #851)
assert.ok(closeTag >= 0, 'Section C must be followed by </codex_skill_adapter>');
return src.slice(headingIdx, closeTag);
}
@@ -10281,8 +10288,9 @@ describe('bug #851: Codex adapter documents multi_agent_v1 schema limitation and
// Regression guard: deferred tool discovery must remain (bug-279 contract).
test('adapter deferred tool discovery instruction is preserved', () => {
// The pre-existing bug-279 contract must remain intact
// allow-test-rule: source-text-is-the-product (see #851)
assert.ok(
src.includes('deferred') && src.includes('tool_search') && src.includes('spawn_agent'),
src.includes('deferred') && src.includes('tool_search') && src.includes('spawn_agent'), // allow-test-rule: source-text-is-the-product (see #851)
'Adapter must still instruct deferred tool discovery via tool_search before deciding to run inline',
);
});

View File

@@ -83,17 +83,12 @@ describe('config-field-docs', () => {
});
test('every CONFIG_DEFAULTS key appears in the doc', () => {
// Extract CONFIG_DEFAULTS keys from config-loader.cjs source (moved from core.cjs by ADR-857 phase 2e)
const coreSource = fs.readFileSync(CORE_PATH, 'utf-8');
const defaultsMatch = coreSource.match(
// eslint-disable-next-line local/no-unbounded-quantifier -- parses this repo's own config-loader.cjs source, fixed-size author-controlled content
/const CONFIG_DEFAULTS\s*=\s*\{([\s\S]*?)\r?\n\};/
);
assert.ok(defaultsMatch, 'Could not find CONFIG_DEFAULTS in config-loader.cjs');
// Read CONFIG_DEFAULTS' actual keys straight from the module (moved from
// core.cjs by ADR-857 phase 2e) instead of regex-parsing its source text.
const { CONFIG_DEFAULTS } = require(CORE_PATH);
assert.ok(CONFIG_DEFAULTS && typeof CONFIG_DEFAULTS === 'object', 'Could not find CONFIG_DEFAULTS export in config-loader.cjs');
const body = defaultsMatch[1];
// Match property keys (word characters before the colon)
const keys = [...body.matchAll(/^\s*(\w+)\s*:/gm)].map(m => m[1]);
const keys = Object.keys(CONFIG_DEFAULTS);
assert.ok(keys.length > 0, 'Could not extract any keys from CONFIG_DEFAULTS');
// CONFIG_DEFAULTS uses flat keys; the doc may use namespaced equivalents.

View File

@@ -182,6 +182,9 @@ test('no `isAugment` occurrence gates a call to an Augment converter function (t
const converterCallPattern = new RegExp(`(${converterNames.join('|')})\\s*\\(`);
const file = path.join(__dirname, '..', 'bin', 'install.js');
// allow-test-rule: structural-regression-guard (#3545) — absence of an
// isAugment-gated converter call is a source-text property (see file
// header, #2097).
const lines = fs.readFileSync(file, 'utf8').split(/\r?\n/);
const offenders = [];
for (let i = 0; i < lines.length; i++) {
@@ -200,6 +203,9 @@ test('no `isAugment` occurrence gates a call to an Augment converter function (t
test('legitimate isAugment destructure/enumeration sites survive (not eliminated, unlike isAntigravity)', () => {
const file = path.join(__dirname, '..', 'bin', 'install.js');
// allow-test-rule: structural-regression-guard (#3545) — presence of the
// isAugment identifier itself is a source-text property (see file header,
// #2097).
const src = fs.readFileSync(file, 'utf8');
assert.ok(/isAugment/.test(src), 'isAugment must still be destructured from runtimeFlags() for non-conversion uses');
});
@@ -234,6 +240,9 @@ test('augment capability.json declares a real, converter-bearing agents kind for
test('_DESCRIPTOR_AGENTS_RUNTIMES no longer exists as a live declaration — the descriptor is authoritative for every runtime, not an allow-listed subset', () => {
const file = path.join(__dirname, '..', 'bin', 'install.js');
// allow-test-rule: structural-regression-guard (#3545) — absence of a live
// _DESCRIPTOR_AGENTS_RUNTIMES declaration is a source-text property (see
// file header, #2097).
const src = fs.readFileSync(file, 'utf8');
// A residual mention in a historical // comment (documenting the #2875
// deletion itself) is fine; a live `const _DESCRIPTOR_AGENTS_RUNTIMES =`

View File

@@ -541,6 +541,161 @@ describe('no-source-grep rule — widening (#3502)', () => {
],
});
});
// ─── fold + hooks widening (#3545 / Phase 7 of #3464) ─────────────────────
//
// One RuleTester case per row of
// .gsd/phase/chore-3545-fold-hooks-widening-migration/50-test-matrix.md,
// rows 1-8. Covers `fold` (a bare-Identifier readFileSync
// path argument resolved ONE hop back to its VariableDeclarator init) and
// `hooks` (now a recognized source directory alongside bin/lib/gsd-core/src).
test('#3545 row 1: baseline inline literal src path — unchanged by fold/hooks widening', () => {
ruleTester.run('no-source-grep', noSourceGrep, {
valid: [],
invalid: [
{
code: `
const fs = require('fs');
const path = require('path');
const s = fs.readFileSync(path.join(__dirname, '..', 'src', 'x.cjs'), 'utf-8');
s.includes('y');
`,
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'noSourceGrep' }],
},
],
});
});
test('#3545 row 2: one-hop identifier bound to a src-dir path is now flagged (fold)', () => {
ruleTester.run('no-source-grep', noSourceGrep, {
valid: [],
invalid: [
{
code: `
const fs = require('fs');
const path = require('path');
const p = path.join(__dirname, '..', 'src', 'x.cjs');
const s = fs.readFileSync(p, 'utf-8');
s.includes('y');
`,
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'noSourceGrep' }],
},
],
});
});
test('#3545 row 3: one-hop identifier bound to a hooks-dir path is now flagged (fold + hooks)', () => {
ruleTester.run('no-source-grep', noSourceGrep, {
valid: [],
invalid: [
{
code: `
const fs = require('fs');
const path = require('path');
const p = path.join(__dirname, '..', 'hooks', 'x.cjs');
const s = fs.readFileSync(p, 'utf-8');
s.includes('y');
`,
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'noSourceGrep' }],
},
],
});
});
test('#3545 row 4: identifier bound to a non-path value is not flagged (fold negative space)', () => {
ruleTester.run('no-source-grep', noSourceGrep, {
valid: [
{
code: `
const fs = require('fs');
const p = process.env.FOO;
const s = fs.readFileSync(p, 'utf-8');
s.includes('y');
`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('#3545 row 5: identifier bound to a non-source-extension path is not flagged (fold negative space)', () => {
ruleTester.run('no-source-grep', noSourceGrep, {
valid: [
{
code: `
const fs = require('fs');
const path = require('path');
const p = path.join(__dirname, '..', 'src', 'x.md');
const s = fs.readFileSync(p, 'utf-8');
s.includes('y');
`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('#3545 row 6: two-hop indirection is not flagged — fold only resolves one hop (boundary)', () => {
ruleTester.run('no-source-grep', noSourceGrep, {
valid: [
{
code: `
const fs = require('fs');
const path = require('path');
const a = path.join(__dirname, '..', 'src', 'x.cjs');
const p = a;
const s = fs.readFileSync(p, 'utf-8');
s.includes('y');
`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('#3545 row 7: assignment-bound path is not flagged — fold only resolves a VariableDeclarator init (boundary)', () => {
ruleTester.run('no-source-grep', noSourceGrep, {
valid: [
{
code: `
const fs = require('fs');
const path = require('path');
let p;
p = path.join(__dirname, '..', 'src', 'x.cjs');
const s = fs.readFileSync(p, 'utf-8');
s.includes('y');
`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('#3545 row 8: "hooks" as a substring of a longer quoted segment is not flagged (hasSourceDir requires an exact quoted segment)', () => {
ruleTester.run('no-source-grep', noSourceGrep, {
valid: [
{
code: `
const fs = require('fs');
const path = require('path');
const p = path.join(__dirname, '..', 'my-hooks-dir', 'x.cjs');
const s = fs.readFileSync(p, 'utf-8');
s.includes('y');
`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
});
// ─── no-source-grep site-scoped suppression (#3508 / Phase 4 of #3464) ──────

View File

@@ -477,18 +477,15 @@ describe('Issue #815: --next dist-tag support', () => {
* 4. Single-source: check-latest-version's PACKAGE_NAME === the seam's
* packageName === the scoped '@opengsd/gsd-core'.
*
* Source-grep policy: this test reads hook source via readFileSync. The repo's
* lint-no-source-grep rule targets bin/lib/gsd-core — hooks/ is out of
* scope. The behavior (correct name → no E404) only manifests at runtime
* Source-grep policy: this test reads hook source via readFileSync. Since
* #3545 lint-no-source-grep also covers hooks/, not just bin/lib/gsd-core.
* The behavior (correct name → no E404) only manifests at runtime
* against the live registry; structural assertions are the minimum-cost
* contract for the worker, the same rationale #378 carried.
* contract for the worker, the same rationale #378 carried. See the
* site-scoped `allow-test-rule` marker directly above the readFileSync()
* call in workerCodeOnly() below.
*/
// allow-test-rule: structural-regression-guard (see #378)
// structural assertion on hook delegation; the behavior being
// tested (correct package name → no E404) only manifests at runtime against the
// live npm registry, which CI does not call.
'use strict';
const { test, describe } = require('node:test');
@@ -502,6 +499,11 @@ const SEAM = require('../gsd-core/bin/lib/package-identity.cjs');
const { PACKAGE_NAME } = require('../gsd-core/bin/check-latest-version.cjs');
function workerCodeOnly() {
// allow-test-rule: structural-regression-guard (see #378) — the behavior
// being tested (correct scoped package name → no E404) only manifests at
// runtime against the live npm registry, which CI does not call; the
// stripped-source text is the minimum-cost contract for this worker
// (#3545 widening brings hooks/ into the rule's scope)
const src = fs.readFileSync(WORKER_PATH, 'utf8');
return src
// eslint-disable-next-line local/no-unbounded-quantifier -- parses this repo's own bounded hooks source, not adversarial input
@@ -566,10 +568,11 @@ describe('bug #378 / #498: update worker queries the scoped name via the seam',
{
const { describe: __foldDescribe } = require('node:test');
__foldDescribe("folded:bug-2784-update-cache-clear-path (consolidation epic #1969 B5 #1974)", () => {
// allow-test-rule: structural-regression-guard (see #2784)
// Reads hook .js or bin/install.js source to assert structural invariants
// (search array order, function wiring, path constants) that cannot be
// verified by observing runtime outputs alone. Per CONTRIBUTING.md exception matrix.
// verified by observing runtime outputs alone. Per CONTRIBUTING.md exception
// matrix. See the site-scoped `allow-test-rule` marker directly above the
// readFileSync() call below (#3545 widening brings hooks/ into scope).
/**
* Regression test for bug #2784
@@ -603,6 +606,9 @@ const CHECK_UPDATE_HOOK = path.join(REPO_ROOT, 'hooks', 'gsd-check-update.js');
describe('bug-2784: update.md cache-clear covers shared cache path', () => {
test('gsd-check-update.js hook constructs cache dir from .cache and gsd path segments', () => {
// allow-test-rule: structural-regression-guard (see #2784) — asserts
// the path.join() segment structure that cannot be verified by
// observing runtime outputs alone (#3545)
const hookContent = fs.readFileSync(CHECK_UPDATE_HOOK, 'utf-8');
// Parse the path.join() call structurally rather than text-grepping.
// eslint-disable-next-line local/no-unbounded-quantifier -- parses this repo's own bounded hooks/gsd-check-update.js source, not adversarial input

View File

@@ -533,6 +533,10 @@ describe('bug #1891: @file: resolution in gsd-tools.cjs', () => {
let src;
before(() => {
// allow-test-rule: structural-implementation-guard (see #1891) — gsd-tools.cjs's
// stdout @file: interception has no exported symbol to assert on directly; every
// src.includes()/indexOf()/match() call in this describe block traces back to this
// read (#3545)
src = fs.readFileSync(GSD_TOOLS_SRC, 'utf-8');
});

View File

@@ -54,9 +54,15 @@ const HOOK = path.join(__dirname, '..', 'hooks', 'gsd-worktree-path-guard.js');
function loadNormalizer() {
const src = fs.readFileSync(HOOK, 'utf8');
// allow-test-rule: source-text-is-the-product (#3545) — normalizeKimiPayload
// is deliberately inlined per hook script with no require()-able module (see
// file-header note above); text-index extraction is the only way to bind
// and eval it, mirroring tests/kimi-guard-normalization-parity.test.cjs
const start = src.indexOf('const KIMI_TOOL_NAMES');
assert.notEqual(start, -1, 'KIMI_TOOL_NAMES block not found in hook source');
const endMarker = ' return data;\n}';
// allow-test-rule: source-text-is-the-product (#3545) — same inlined-block
// extraction as above, locating the end of the block to eval
const end = src.indexOf(endMarker, start);
assert.notEqual(end, -1, 'normalizeKimiPayload end not found in hook source');
const block = src.slice(start, end + endMarker.length);

View File

@@ -24,6 +24,7 @@ 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';
@@ -103,17 +104,24 @@ describe('#3799: cleanupLegacyGsdCc honors an explicit configDirs scope', () =>
});
describe('#3799: --no-legacy-cleanup and --config-dir CLI flags', () => {
const HELP_TEXT = fs.readFileSync(INSTALL_BIN, 'utf-8');
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(
HELP_TEXT.includes('--no-legacy-cleanup'),
'the escape hatch must be documented in the installer help text',
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', () => {
const src = HELP_TEXT; // same file read — the shipped installer source
// 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');

View File

@@ -42,6 +42,9 @@ describe('orphaned hooks stale detection (#1750)', () => {
});
test('gsd-check-update-worker.js imports managed-hooks-registry.cjs (not inline array)', () => {
// allow-test-rule: structural-regression-guard (#3545) — presence of a
// require() wire-up and absence of a reintroduced inline array are source
// structure facts, not runtime behavior; see file header.
const content = fs.readFileSync(WORKER_PATH, 'utf8');
assert.ok(
content.includes('managed-hooks-registry.cjs'),
@@ -55,6 +58,9 @@ describe('orphaned hooks stale detection (#1750)', () => {
});
test('gsd-check-update.js spawns the worker by file path (not inline -e code)', () => {
// allow-test-rule: structural-regression-guard (#3545) — spawn-target
// wiring and absence of inline `-e` code are source structure facts, not
// runtime behavior; see file header.
const content = fs.readFileSync(CHECK_UPDATE_PATH, 'utf8');
assert.ok(
content.includes('gsd-check-update-worker.js'),

View File

@@ -367,6 +367,9 @@ describe('portability-vocab drift guard', () => {
const srcPath = path.join(__dirname, '..', 'bin', 'install.js');
const raw = fs.readFileSync(srcPath, 'utf8');
// bin/install.js starts with a shebang espree cannot parse — rewrite #! -> //.
// allow-test-rule: source-text-is-the-product (#3545) — this is shebang-stripping
// to make the source parseable by espree; the test then walks the real AST
// structurally (below), it is not a text-grep proxy for behavior
const src = raw.startsWith('#!') ? '//' + raw.slice(2) : raw;
const ast = espree.parse(src, { ecmaVersion: 2022, loc: true, range: true, tolerant: true });
@@ -432,6 +435,9 @@ describe('portability-vocab drift guard', () => {
test('bin/install.js: the curated INSTALL_JS_PATH_HELPERS still exist (no stale vocab entries after a rename)', () => {
const srcPath = path.join(__dirname, '..', 'bin', 'install.js');
const raw = fs.readFileSync(srcPath, 'utf8');
// allow-test-rule: source-text-is-the-product (#3545) — shebang-stripping to
// make the source parseable by espree; the test walks the real AST
// structurally (below), not a text-grep proxy for behavior
const src = raw.startsWith('#!') ? '//' + raw.slice(2) : raw;
const ast = espree.parse(src, { ecmaVersion: 2022, loc: true, range: true, tolerant: true });

View File

@@ -215,11 +215,12 @@ describe('gsd-read-guard hook', () => {
});
test('hook is registered in install.js uninstall hook list', () => {
const installPath = path.join(__dirname, '..', 'bin', 'install.js');
const content = fs.readFileSync(installPath, 'utf8');
// Check the actual exported uninstall hook list instead of grepping
// install.js source text.
const { GSD_UNINSTALL_HOOKS } = require('../bin/install.js');
assert.ok(
content.includes("'gsd-read-guard.js'"),
'gsd-read-guard.js must be in the uninstall gsdHooks list'
GSD_UNINSTALL_HOOKS.includes('gsd-read-guard.js'),
'gsd-read-guard.js must be in the uninstall GSD_UNINSTALL_HOOKS list'
);
});

View File

@@ -156,6 +156,10 @@ test('enhancement #191: published package no longer exposes gsd-sdk artifacts',
});
test('enhancement #191: installer does not maintain gsd-sdk shim compatibility path', () => {
// allow-test-rule: structural-regression-guard (#3545) — asserting the
// ABSENCE of a retired flag/function reference from install.js is a source
// structure fact (dead-code regression), not something observable by
// exercising the installer's runtime behavior.
const installJs = fs.readFileSync(INSTALL_PATH, 'utf8');
assert.equal(/\b--sdk\b/.test(installJs), false,

View File

@@ -1845,10 +1845,10 @@ const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { createTempDir, cleanup } = require('./helpers.cjs');
const ROOT = path.join(__dirname, '..');
// Require the module under test directly
const roadmapLib = path.join(ROOT, 'gsd-core', 'bin', 'lib', 'roadmap.cjs');
const planScanLib = path.join(ROOT, 'gsd-core', 'bin', 'lib', 'plan-scan.cjs');
// We test countPhasePlansAndSummaries indirectly via getManagerInfo since
@@ -1893,24 +1893,29 @@ describe('bug #3128: roadmap.cjs plan-count for {N}-PLAN-{NN}-{slug}.md layout',
assert.ok(!isPlanFile('5-RESEARCH.md'), 'RESEARCH.md must not match');
});
test('roadmap.cjs source uses the extended isPlanFile filter', () => {
const roadmapSrc = fs.readFileSync(roadmapLib, 'utf8');
// Verify the fix is in place: the old simple inline filter is gone from roadmap.cjs
test('roadmap.cjs source uses the extended isPlanFile filter', (t) => {
// roadmap.cjs's countPhasePlansAndSummaries (module-private) delegates its
// plan counting to plan-scan.cjs's scanPhasePlans/isRootPlanFile -- exercise
// the REAL exported module directly instead of grepping roadmap.cjs's source
// text for the delegation.
const planScan = require(planScanLib);
// isRootPlanFile must recognize the {N}-PLAN-{NN}-{slug}.md layout (#3128)
// that the old inline `f.endsWith('-PLAN.md') || f === 'PLAN.md'` filter missed.
assert.ok(
!roadmapSrc.includes("phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md')"),
'Old simple plan filter still present in roadmap.cjs — fix not applied',
);
// roadmap.cjs now delegates to plan-scan.cjs via require('./plan-scan.cjs')
assert.ok(
roadmapSrc.includes('plan-scan.cjs'),
'roadmap.cjs does not require plan-scan.cjs — delegation not applied',
);
// plan-scan.cjs is where the extended plan-file detection logic lives (isRootPlanFile)
const planScanSrc = fs.readFileSync(planScanLib, 'utf8');
assert.ok(
planScanSrc.includes('isRootPlanFile') && planScanSrc.includes('/PLAN/i'),
'isRootPlanFile with /PLAN/i not found in plan-scan.cjs — canonical helper missing extended filter',
planScan.isRootPlanFile('5-PLAN-01-setup-database.md'),
'isRootPlanFile must recognize the slug-form plan filename from bug #3128',
);
// Exercise scanPhasePlans against a synthetic phase directory containing
// only a slug-form plan file -- this is the SAME production function
// roadmap.cjs's countPhasePlansAndSummaries calls, so a correct count here
// proves the extended filter is what actually runs, not a copy of it.
const tmpDir = createTempDir('roadmap-plan-scan-');
t.after(() => cleanup(tmpDir));
fs.writeFileSync(path.join(tmpDir, '5-PLAN-01-setup-database.md'), '# plan\n');
const scanResult = planScan(tmpDir);
assert.equal(scanResult.planCount, 1, 'scanPhasePlans must count the slug-form plan file');
});
});
});

View File

@@ -130,6 +130,11 @@ describe('#3024 review finding 2: LEGACY_NON_REGISTRY_RUNTIME_IDS drift guard',
assert.ok(
!registryIds.includes(SENTINEL_ID) &&
!legacyIds.includes(SENTINEL_ID) &&
// allow-test-rule: source-text-is-the-product (#3545) — sourceParsedIds
// is enumeration scaffolding derived from readFileSync'd source text
// (see parseHardcodedBranchIds doc comment above); every id it turns
// up is verified BEHAVIORALLY below via getGlobalConfigDir, never
// trusted on its own
!sourceParsedIds.includes(SENTINEL_ID),
`sentinel id ${SENTINEL_ID} unexpectedly collides with a real candidate id — pick a different sentinel`,
);

View File

@@ -147,22 +147,35 @@ describe('stripJsonComments (#1461)', () => {
});
describe('readSettings null return on malformed files (#1461)', () => {
test('install.js contains JSONC stripping in readSettings', () => {
const installPath = path.join(__dirname, '..', 'bin', 'install.js');
const content = fs.readFileSync(installPath, 'utf8');
assert.ok(content.includes('stripJsonComments'),
'install.js should use stripJsonComments in readSettings');
test('readSettings strips JSONC comments when reading a real file', (t) => {
const tmpFile = path.join(os.tmpdir(), `gsd-settings-test-jsonc-${process.pid}.json`);
fs.writeFileSync(tmpFile, `{\n // a comment\n "key": "value"\n}`);
t.after(() => fs.unlinkSync(tmpFile));
const result = readSettings(tmpFile);
assert.deepStrictEqual(
result,
{ key: 'value' },
'readSettings should use stripJsonComments so a commented file parses, not warns-as-malformed'
);
});
test('readSettings returns null on truly malformed files (not empty object)', () => {
const installPath = path.join(__dirname, '..', 'bin', 'install.js');
const content = fs.readFileSync(installPath, 'utf8');
assert.ok(content.includes('return null'),
'readSettings should return null on parse failure, not empty object');
test('readSettings returns null on truly malformed files (not empty object)', (t) => {
const tmpFile = path.join(os.tmpdir(), `gsd-settings-test-malformed-return-${process.pid}.json`);
fs.writeFileSync(tmpFile, '{ this is not valid json');
t.after(() => fs.unlinkSync(tmpFile));
const result = readSettings(tmpFile);
assert.strictEqual(result, null, 'readSettings should return null on parse failure, not empty object');
});
test('callers guard against null readSettings return', () => {
const installPath = path.join(__dirname, '..', 'bin', 'install.js');
// allow-test-rule: structural-implementation-guard (#1461) (#3545) — structural
// assertion on internal wiring inside install()'s (~2500-line)
// settings-configuration call sites — the
// null-guard only manifests behaviorally deep inside a full install()
// run, so the source-text check is the minimum-cost regression guard
// that a caller was not added without also checking readSettings'
// documented null return
const content = fs.readFileSync(installPath, 'utf8');
// Should have null guards at the settings configuration call sites
assert.ok(
@@ -180,13 +193,16 @@ describe('readSettings null return on malformed files (#1461)', () => {
// the behavioural ones beneath it.
describe('readSettings: JSON null coalesced to empty, malformed warns (#1191)', () => {
test('source contains the null-coalescing guard (parsed === null ? {})', () => {
// Structural anchor: if someone removes the coalescing, this test catches it
// before the behavioural test below even runs.
const installPath = path.join(__dirname, '..', 'bin', 'install.js');
const content = fs.readFileSync(installPath, 'utf8');
assert.ok(
content.includes('parsed === null ? {}'),
test('valid JSON null coalesces to {} via the real function (early behavioral anchor)', (t) => {
// Behavioral anchor: if someone removes the coalescing, this test catches
// it before the more detailed behavioural test below even runs.
const tmpFile = path.join(os.tmpdir(), `gsd-settings-test-null-anchor-${process.pid}.json`);
fs.writeFileSync(tmpFile, 'null');
t.after(() => fs.unlinkSync(tmpFile));
const result = readSettings(tmpFile);
assert.deepStrictEqual(
result,
{},
'install.js readSettings must coalesce valid JSON null to {} (not malformed warning)'
);
});

View File

@@ -5490,6 +5490,10 @@ describe('install.js guard for gsd-worktree-path-guard.js', () => {
before(() => {
// ADR-857 phase 5f-1b: hook registration moved to runtime-hooks-surface.cts.
// Concatenate both sources so structural assertions find patterns in either file.
// allow-test-rule: structural-implementation-guard (#3545) — structural install.js
// guard; install.js has side effects on require and no exported symbol for hook-registration wiring;
// every src.includes()/indexOf() and block.includes() call below traces
// back to this read
const installSrc = fs.readFileSync(INSTALL_SRC, 'utf-8');
let hooksSurfaceSrc = '';
try { hooksSurfaceSrc = fs.readFileSync(HOOKS_SURFACE_SRC, 'utf-8'); } catch { /* ok */ }