v1.92.44.0 fix(pregate): literals stops treating same-stem sibling files as the changed module (GST-820) (#3814)

* fix(pregate): literals stops treating a same-stem sibling file as the changed module (GST-820)

A path names the module only when its stem is followed by a quote, the line end, or the module's own or a JS/TS extension; test/<stem>.txt no longer makes a file a consumer. Red first on main 4f5b043.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(pregate): literals keeps a stem followed by a space, bracket, sentence end or .d.ts as the module (GST-820 review)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(pregate): literals keeps JS/TS aliases to JS/TS modules and stops matching a file name with a further extension (GST-820 review)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(pregate): literals matches the module only for old values longer than one character (GST-820 docs audit)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
Garry Tanandcapy-ai[bot] authored and GitHub committed 2026-11-01 22:50:47 +00:00
1 parent 775fe2737c
commit 4d388f44bd
7 files changed
+64 -7

No files matched your search

+7
View File
@@ -1,5 +1,12 @@
# Changelog
## [1.92.44.0] - 2026-11-01
**`literals` stops treating a same-name sibling file as the changed module (GST-820).** A test that only mentioned `test/postgres-unit-arms.txt` was counted as a consumer of `scripts/postgres-unit-arms.ts`, so any `12` in it failed `PREGATE_LITERALS` when `ARM_SHARDS` moved from 12 to 18. In gbrain #8372 that gave 17 false files. A path now names the module only when its stem is followed by the module's own extension (for a JS/TS module, any JS/TS extension), or by anything other than a letter, digit, `_`, `-` or another extension, and the full file name no longer matches with a further extension such as `.snap`. Extensionless imports, `.js` imports and the module's own path still count.
### Fixed
- **`literals` module matching.** A sibling file that shares the module's stem (`<stem>.txt`, `<stem>.json`, `<stem>-v2.ts`, `<file>.snap`, or `<stem>.ts` beside a shell script) is no longer the module.
## [1.92.43.0] - 2026-11-01
**New page: [docs/support-tiers.md](docs/support-tiers.md) says what gstack supports fully and what it supports minimally.** macOS and Linux, with Claude and OpenAI models, get full support: the whole free suite and the paid evals. Windows (Git Bash or MSYS) and Gemini models get minimal support: install and the core skills (`/ship`, `/review`, `/qa-only`, `/investigate`, `/browse` on gstack's own browser) work without errors, checked on Windows by the `windows-smoke` job, which replaces the six-shard suite as the required Windows check when it lands, and by hand or one cheap smoke run on Gemini. Windows-only and Gemini-only reports that don't break install, startup or a core workflow close as won't fix with a link to the page.
+1 -1
View File
@@ -1 +1 @@
1.92.43.0
1.92.44.0
+1 -1
View File
@@ -1,4 +1,4 @@
# gstack digest v1.92.43.0 — regenerate/re-copy after upgrading gstack
# gstack digest v1.92.44.0 — regenerate/re-copy after upgrading gstack
Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed
for agent hosts without a full skill install. The full skills add workflows,
+8 -1
View File
@@ -200,7 +200,14 @@ Two stages with separate clocks, both printed:
scratch name (`zz-*`, `tmp*`, `scratch*`) anywhere.
- `literals`: for every changed exported constant or named timeout/retry
literal, the whole test tree (nightly- and Heavy-only files included) is
searched for the old value; a consumer the branch did not update fails with
searched for the old value in files that name the constant or, when the old
value is longer than one character, its module (the module's file name not
followed by another extension, or a path ending
in its stem followed by its own extension, for a JS/TS module also any JS/TS
extension or `.d.ts`, a sentence-ending `.`, or anything but a word
character, `.` or `-`; so a sibling `<stem>.txt`, `<stem>-v2.ts` or
`<file>.snap`, or `<stem>.ts` beside a shell module, is not the module); a
consumer the branch did not update fails with
`file:line`. A version-history row, a line that starts a bracketed row with
the old value (`[45, "<digest>"]`), is skipped when the same table (a run of
consecutive lines starting with `[`) also holds a row for the new value that
+11 -3
View File
@@ -61,8 +61,12 @@ function unquote(v: string): string { return /^['"`]/.test(v) ? v.slice(1, -1) :
/**
* Test files that still carry the old literal beside the constant's name or
* its module. The module counts as its full file name (`install.sh`) or as a
* path fragment (`/retry.js`, `/retry'`): a bare stem such as `install` or `retry` is an
* its module. The module counts as its full file name (`install.sh`, not
* `install.sh.tmpl`) or as a path fragment (`/retry.js`, `/retry'`) whose stem is
* followed by the module's own extension (for a JS/TS module also any JS/TS
* extension or `.d.ts`), a sentence-ending `.`, or anything but a word
* character, `.` or `-`, so a sibling `/retry.txt`, `/retry-v2.ts` or
* `/retry.ts.snap`, or `lib/install.ts` for `install.sh`, is not the module (GST-820): a bare stem such as `install` or `retry` is an
* English word in half the test tree, and a one-character old value (`2`)
* sits in every test, so that value needs the constant's name (a wrapper
* version 2 → 3 flagged 575 consumers through the stem alone).
@@ -83,7 +87,11 @@ export function consumersOf(repoRoot: string, testFiles: readonly string[], chan
const newRowRe = row(change.new);
const base = path.basename(change.file);
const stem = base.replace(/\.[^.]+$/, '');
const moduleRe = new RegExp(`(?:(?:^|[/\\\\])${escape(stem)}|(?<![\\w-])${escape(base)})(?![\\w-])`, 'm');
const own = base.slice(stem.length + 1);
const jsLike = /^[cm]?[jt]sx?$/.test(own);
const exts = (jsLike ? [...new Set([own, 'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs', 'mts', 'cts'])] : [own]).map(escape).join('|');
const dts = jsLike ? '(?:d\\.)?' : '';
const moduleRe = new RegExp(`(?:^|[/\\\\])${escape(stem)}(?=[^\\w.-]|$|\\.${dts}(?:${exts})(?![\\w.-])|\\.(?!\\w))|(?<![\\w-])${escape(base)}(?![\\w-]|\\.\\w)`, 'm');
const nameRe = new RegExp(`\\b${escape(change.name)}\\b`);
const nameRequired = value.length < 2;
const hits: string[] = [];
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "gstack",
"version": "1.92.43",
"version": "1.92.44",
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
"license": "MIT",
"type": "module",
+35
View File
@@ -201,6 +201,41 @@ describe('literals (pure)', () => {
{ name: 'MAX', old: '3', new: '4', file: 'f.ts' }, { name: 'timeout', old: '5000', new: '9000', file: 'f.ts' },
]);
});
// Value: protects=a sibling file that shares the module's stem (`postgres-unit-arms.txt` beside `postgres-unit-arms.ts`) is not the
// module, so a test that only names it is not a consumer, while an extensionless import, a JS/TS import extension, the module's own
// file name and a path ending at the stem (then a quote, space, bracket, sentence end or `.d.ts`) still are, and neither `<file>.snap` nor a JS twin of a
// shell module is; fails_when=`/<stem>` again accepts any following `.<word>`, the file name accepts a further extension,
// a non-JS module takes JS/TS extensions, or stops accepting a following space, bracket or sentence end
// (GST-820: 17 false files in gbrain #8372 on ARM_SHARDS 12 -> 18); why_new=no test had a same-stem sibling; seam=none;
// no_claim=name-gated hits (a file naming ARM_SHARDS still counts any 12)
test('consumersOf: a sibling file sharing the module stem is not the module; real imports of it still are (GST-820)', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-literals-stem-'));
const nl = String.fromCharCode(10);
const files: Record<string, string> = {
'test/a.test.ts': ['// listed in test/postgres-unit-arms.txt', 'expect(n).toBe(12);', ''].join(nl),
'test/json.test.ts': ["const arms = read('fixtures/postgres-unit-arms.json');", 'expect(arms.length).toBe(12);', ''].join(nl),
'test/longer.test.ts': ["run('scripts/postgres-unit-arms-v2.ts');", 'expect(n).toBe(12);', ''].join(nl),
'test/import.test.ts': ["import { shards } from '../scripts/postgres-unit-arms';", 'expect(shards()).toBe(12);', ''].join(nl),
'test/import-js.test.ts': ["import { shards } from '../scripts/postgres-unit-arms.js';", 'expect(shards()).toBe(12);', ''].join(nl),
'test/own.test.ts': ["const out = run('scripts/postgres-unit-arms.ts');", 'expect(out).toBe(12);', ''].join(nl),
'test/line-end.test.ts': ['// covers scripts/postgres-unit-arms', 'expect(n).toBe(12);', ''].join(nl),
'test/prose.test.ts': ['// covers scripts/postgres-unit-arms and more', 'expect(n).toBe(12);', ''].join(nl),
'test/paren.test.ts': ['// (scripts/postgres-unit-arms)', 'expect(n).toBe(12);', ''].join(nl),
'test/sentence.test.ts': ['// see scripts/postgres-unit-arms.', 'expect(n).toBe(12);', ''].join(nl),
'test/dts.test.ts': ["import type { Arms } from '../scripts/postgres-unit-arms.d.ts';", 'expect(n).toBe(12);', ''].join(nl),
'test/sibling-test.test.ts': ["run('scripts/postgres-unit-arms.test.ts');", 'expect(n).toBe(12);', ''].join(nl),
'test/snap.test.ts': ["read('scripts/__snapshots__/postgres-unit-arms.ts.snap');", 'expect(n).toBe(12);', ''].join(nl),
'test/sh-twin.test.ts': ["import { arms } from '../lib/arms-tool.ts';", 'expect(n).toBe(12);', ''].join(nl),
'test/sh-own.test.ts': ["run('scripts/arms-tool.sh');", 'expect(n).toBe(12);', ''].join(nl),
};
for (const [rel, text] of Object.entries(files)) { fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true }); fs.writeFileSync(path.join(root, rel), text); }
expect(consumersOf(root, Object.keys(files), { name: 'ARM_SHARDS', old: '12', new: '18', file: 'scripts/postgres-unit-arms.ts' })).toEqual([
'test/import.test.ts:2', 'test/import-js.test.ts:2', 'test/own.test.ts:2', 'test/line-end.test.ts:2',
'test/prose.test.ts:2', 'test/paren.test.ts:2', 'test/sentence.test.ts:2', 'test/dts.test.ts:2',
]);
expect(consumersOf(root, ['test/sh-twin.test.ts', 'test/sh-own.test.ts'], { name: 'ARMS', old: '12', new: '18', file: 'scripts/arms-tool.sh' })).toEqual(['test/sh-own.test.ts:2']);
fs.rmSync(root, { recursive: true, force: true });
});
test('consumersOf: a module stem is a path fragment, never a bare word, and a one-character old value needs the constant name (GSTA-114 flood: install.sh 2 -> 3 flagged 575 consumers)', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-literals-'));
const nl = String.fromCharCode(10);