pregate: map tests to dotfiles named in quoted literals

The quoted-literal pattern required [A-Za-z0-9_@] after the relative prefix, so "../.gitleaks.toml" never matched, and a dot-led literal resolved beside the test instead of from the repo root. A branch touching only the dotfile ended in PREGATE_NO_LANE (GST-246, garrytan/truther#956).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-31 01:22:19 +00:00
1 parent 21277291c8
commit acd22749b1
3 files changed
+28 -2

No files matched your search

+1
View File
@@ -0,0 +1 @@
- **Pregate now maps tests to dotfiles they name.** A test that reads `../.gitleaks.toml`, `.gitleaks.toml` or `join('.github', 'workflows', 'ci.yml')` used to get no edge to that file, so a branch touching only the dotfile ended in `PREGATE_NO_LANE`. Quoted literals may now start with a dot, and a dot-led literal that isn't `./` or `../` resolves from the repository root. A literal still forms an edge only when it names a file in the repo, so strings like `'.ts'` add nothing. On gstack this adds 179 edges, all to real dotfiles such as `.github/workflows/*.yml` and `.gstack/*.json`. (GST-246, reported from garrytan/truther#956.)
+2 -2
View File
@@ -55,7 +55,7 @@ const scanner = new Bun.Transpiler({ loader: 'tsx' });
// `new URL(…, import.meta.url)` or `readFileSync`) resolves from the file that
// names it, like a relative import; without it a test reading a template by
// relative path had no edge to the template (GSTA-121's test/ship-publication-gates.test.ts).
const QUOTED = /['"`]((?:\.\.?\/)*[A-Za-z0-9_@][A-Za-z0-9_./@()[\]+-]*)['"`]/g;
const QUOTED = /['"`]((?:\.\.?\/)*\.?[A-Za-z0-9_@][A-Za-z0-9_./@()[\]+-]*)['"`]/g;
const JOIN = /\bjoin\(([^()]*)\)/g;
const RESOLVE_EXT = ['', '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts', '/index.ts', '/index.tsx', '/index.js', '/index.jsx', '/index.mjs'];
// A statement starts a line or follows `;` or a compound statement's `:`. The module spec is
@@ -291,7 +291,7 @@ function resolveCandidate(files: Set<string>, dirs: Set<string>, c: string, lite
*/
function resolveRefs(files: Set<string>, dirs: Set<string>, fromFile: string, ref: string, literal = false, config: PathAliases = NO_ALIASES): string[] {
if (!ref || ref.startsWith('node:') || ref.startsWith('bun:')) return [];
if (ref.startsWith('.')) { const t = resolveCandidate(files, dirs, normalize(path.posix.join(path.posix.dirname(fromFile), ref)), literal); return t ? [t] : []; }
if (/^\.\.?(?:\/|$)/.test(ref)) { const t = resolveCandidate(files, dirs, normalize(path.posix.join(path.posix.dirname(fromFile), ref)), literal); return t ? [t] : []; }
const { chosen, others } = matchAliases(config.aliases, ref);
for (const c of chosen ?? []) { const t = resolveCandidate(files, dirs, c, literal); if (t) return [t]; }
if (!chosen && others.length === 0 && ref.startsWith('@') && !ref.includes('/')) return [];
+25
View File
@@ -48,6 +48,31 @@ describe('import graph: route-group / dynamic-segment paths and tsconfig path al
expect(testsFor(buildGraph(repo.work), 'ship/sections/pr-body.md.tmpl').tests).toEqual(['test/publication.test.ts']);
});
test('a dotfile named by a quoted literal or a join() maps, relative or from the repo root', () => {
const repo = fixture();
commit(repo, {
'.gitleaks.toml': 'title = "x"\n',
'.github/workflows/ci.yml': 'on: push\n',
'test/relative.test.ts': "import { readFileSync } from 'node:fs';\nreadFileSync(new URL('../.gitleaks.toml', import.meta.url), 'utf8');\n",
'test/rooted.test.ts': "import { readFileSync } from 'node:fs';\nreadFileSync('.gitleaks.toml', 'utf8');\n",
'test/joined.test.ts': "import { join } from 'node:path';\nimport { readFileSync } from 'node:fs';\nreadFileSync(join('.github', 'workflows', 'ci.yml'), 'utf8');\n",
});
const g = buildGraph(repo.work);
expect(testsFor(g, '.gitleaks.toml').tests).toEqual(['test/relative.test.ts', 'test/rooted.test.ts']);
expect(testsFor(g, '.github/workflows/ci.yml').tests).toEqual(['test/joined.test.ts']);
});
test('a dot-led literal that names no repo file adds no edge (extensions, missing dotfiles)', () => {
const repo = fixture();
commit(repo, {
'lib/plan.ts': 'export const plan = 1;\n',
'test/ext.test.ts': "const exts = ['.ts', '.md', '.env', '../.missing.toml', '.github'];\nexport { exts };\n",
});
const g = buildGraph(repo.work);
expect(testsFor(g, 'lib/plan.ts').tests).toEqual([]);
expect([...g.reverse.values()].flat().filter(e => e.from === 'test/ext.test.ts')).toEqual([]);
});
test('a relative import of a bracketed path already maps (control: the import scanner is not the bug)', () => {
const repo = fixture();
commit(repo, {