test(fixtures): leak scan skips Capy's git shim log in fixture homes (#3860)

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-02 13:28:00 +00:00
1 parent 322906ab2b
commit 0542230e63
6 files changed
+34 -5

No files matched your search

+8
View File
@@ -1,5 +1,13 @@
# Changelog
## [1.92.80.0] - 2026-11-02
**On a Capy machine, the git fixture tests no longer fail locally because of Capy's git shim log.** Capy's `/usr/bin/git` shim appends to `$HOME/.capy/git-shim.log`, so each fixture home that ran git during its template build ended up with a log naming the template. The leak scan in `test/helpers/git-fixture-template.ts` flagged it, and 34 tests in `test/train-backstop.test.ts`, `test/restamp-mentions.test.ts` and `test/pregate-user-policy.test.ts` failed on every Capy lane, though they passed on the remote gate.
### Tests
- `templateReferences` skips `.capy/git-shim.log`. Nothing reads that file back, and every other file is still scanned.
- `test/git-fixture-template.test.ts` plants the shim log in a template's `home/.capy/` (the copy succeeds), and plants `home/.capy/state.json` as a control (the copy still throws).
## [1.92.79.0] - 2026-11-02
**/plan-tune no longer reports a mismatch between your stated and observed preferences when it has no observed answers behind it (#3436).** `--check-mismatch` compared a declared value against an inferred value that had never moved from its 0.5 starting point, so a strong declaration with no matching answers printed `scope_appetite: declared 0.9 vs inferred 0.5 (gap 0.4)` even though no logged answer touched that dimension.
+1 -1
View File
@@ -1 +1 @@
1.92.79.0
1.92.80.0
+1 -1
View File
@@ -1,4 +1,4 @@
# gstack digest v1.92.79.0 — regenerate/re-copy after upgrading gstack
# gstack digest v1.92.80.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,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "gstack",
"version": "1.92.79",
"version": "1.92.80",
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
"license": "MIT",
"type": "module",
+17
View File
@@ -71,6 +71,23 @@ describe('git fixture template', () => {
expect(() => leaky.copy()).toThrow(/still references its template [^\n]*\n.*config/);
});
test("Capy's git shim log in a fixture home (home/.capy/git-shim.log) is not a leak; any other file naming the template still is", () => {
const plant = (rel: string) => gitFixtureTemplate({ prefix: 'gstack-fixture-template-shim-', build: (root) => {
const meta = build(root);
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
fs.writeFileSync(path.join(root, rel), '# capy git shim v2: ts cwd ppid parent verb\n2026-11-02T02:15:30Z\t' + path.join(root, 'work') + '\t1\tbun\tcat-file\n');
return meta;
}, rebind: (copy, tpl) => rebindGitDir(path.join(copy, 'work', '.git'), tpl, copy) });
const shim = plant(path.join('home', '.capy', 'git-shim.log'));
const other = plant(path.join('home', '.capy', 'state.json'));
try {
const c = shim.copy();
roots.push(c.root);
expect(git(path.join(c.root, 'work'), 'remote', 'get-url', 'origin')).toBe(path.join(c.root, 'origin.git'));
expect(() => other.copy()).toThrow(/still references its template [^\n]*\n.*state\.json/);
} finally { shim.dispose(); other.dispose(); }
});
test('native, forward-slash, backslash-escaped and Git Bash spellings are each rewritten, and each one left behind is found', () => {
const to = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-fixture-spellings-'));
roots.push(to);
+6 -2
View File
@@ -52,7 +52,11 @@ export function rebindGitDir(gitDir: string, from: string, to: string): void {
}
}
/** Files and symlinks under dir (and the template's meta value) that still name templateRoot; zlib-compressed object storage is skipped. */
/**
* Files and symlinks under dir (and the template's meta value) that still name templateRoot. Skipped: zlib-compressed object
* storage, and the log Capy's /usr/bin/git shim appends to in whatever HOME it runs under (.capy/git-shim.log), which names
* the template whenever a fixture home ran git during build; nothing reads it back.
*/
export function templateReferences(dir: string, templateRoot: string, meta?: unknown): string[] {
const needles = pathSpellings(templateRoot).map(s => Buffer.from(s));
const names = (buf: Buffer) => needles.some(n => buf.includes(n));
@@ -62,7 +66,7 @@ export function templateReferences(dir: string, templateRoot: string, meta?: unk
const p = path.join(d, entry.name);
if (entry.isSymbolicLink()) { if (names(Buffer.from(fs.readlinkSync(p)))) hits.push(p); }
else if (entry.isDirectory()) { if (!(path.basename(d) === 'objects' && /^(pack|[0-9a-f]{2})$/.test(entry.name))) walk(p); }
else if (entry.isFile() && names(fs.readFileSync(p))) hits.push(p);
else if (entry.isFile() && !(entry.name === 'git-shim.log' && path.basename(d) === '.capy') && names(fs.readFileSync(p))) hits.push(p);
}
};
walk(dir);