mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-01 06:24:41 +01:00
v1.91.369.0 fix: gstack-wtree works when .bun/ is gitignored (GST-449) (#3685)
* gstack-wtree: exclude .bun only when it exists and is not gitignored (GST-449) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * gstack-wtree: ask the ignore rules alone (--no-index) so a tracked file under an ignored .bun/ still fingerprints (GST-449) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * changelog: GST-449 fragment covers the tracked-file case 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:
1 parent
4a2793ed82
commit
02da1a05e0
6 files changed
+88
-5
No files matched your search
@@ -1,5 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## [1.91.369.0] - 2026-10-31
|
||||
|
||||
**`gstack-wtree` works again in a repository that gitignores `.bun/`.** Since GST-421, its `git add -A` carried an exclude pathspec naming `.bun`, and git refuses a pathspec naming an ignored path with exit 1. Every fingerprint call then failed, which broke `gstack-review-log --start`, the evidence checks and /ship in harness-evals (HARN-1, HARN-7, harness-evals#148; GST-449).
|
||||
|
||||
### Fixed
|
||||
- **The `.bun/` exclude applies only when the root `.bun/` exists and is not ignored.** The check reads the ignore rules alone (`git check-ignore --no-index`), so a file still tracked under an ignored `.bun/` doesn't bring the failure back. An ignored `.bun/` already stays out of `git add -A`, so the fingerprint is the same either way.
|
||||
|
||||
### Tests
|
||||
- `test/evidence.test.ts`: with `.bun/` or `/.bun` in `.gitignore`, `gstack-wtree` prints a tree and a lane that writes `.bun/` grades FRESH. With a file tracked under an ignored `.bun/`, the tree matches a plain `git add -A`, and editing or deleting that file changes it. The existing unignored `.bun/` cases still pass.
|
||||
|
||||
## [1.91.368.0] - 2026-10-31
|
||||
|
||||
**`gstack-pregate --remote` no longer dies at 300 s while its gate waits on CI.** CAPY-1 (qm#2837) had a remote gate that waits for a CI run and prints nothing while it waits, so pregate killed it as stalled after 300 s, whatever `--timeout` said, and the heartbeat gave no hint of what the gate was doing (GST-431).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# gstack digest v1.91.368.0 — regenerate/re-copy after upgrading gstack
|
||||
# gstack digest v1.91.369.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,
|
||||
|
||||
+10
-2
@@ -7,7 +7,8 @@
|
||||
# that index. An untracked `.bun/` folder at the repository root (Bun's install
|
||||
# cache, which a test run can create) is left out so a passing run does not
|
||||
# look like a mid-run edit (GST-421); tracked files under it still count, and a
|
||||
# tree without that folder hashes exactly as a plain `git add -A` would.
|
||||
# tree without that folder (or with it gitignored) hashes exactly as a plain
|
||||
# `git add -A` would.
|
||||
# Properties that make this the right staleness fingerprint, vs
|
||||
# `git rev-parse HEAD^{tree}`:
|
||||
#
|
||||
@@ -87,7 +88,14 @@ if [ -n "$REAL_INDEX" ] && [ -f "$REAL_INDEX" ] && cp "$REAL_INDEX" "$TMPIDX" 2>
|
||||
else
|
||||
git -C "$TOP" read-tree HEAD 2>/dev/null || exit 1
|
||||
fi
|
||||
if ! ERR=$(git -C "$TOP" add -u 2>&1 >/dev/null) || ! ERR=$(git -C "$TOP" add -A -- . ':(exclude,top).bun' 2>&1 >/dev/null); then
|
||||
# The exclude is added only when the root .bun/ exists and is not ignored: an
|
||||
# exclude pathspec naming an ignored path makes `git add -A` exit 1 (GST-449),
|
||||
# and an ignored .bun/ stays out of a plain `git add -A` anyway. --no-index
|
||||
# asks the ignore rules alone; without it a tracked file under .bun/ makes git
|
||||
# call .bun not ignored.
|
||||
EXCLUDE_BUN=""
|
||||
if [ -e "$TOP/.bun" ] && ! git -C "$TOP" check-ignore --no-index -q .bun 2>/dev/null; then EXCLUDE_BUN=':(exclude,top).bun'; fi
|
||||
if ! ERR=$(git -C "$TOP" add -u 2>&1 >/dev/null) || ! ERR=$(git -C "$TOP" add -A -- . ${EXCLUDE_BUN:+"$EXCLUDE_BUN"} 2>&1 >/dev/null); then
|
||||
echo "gstack-wtree: cannot stage the working tree: $(printf '%s' "$ERR" | head -1)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gstack",
|
||||
"version": "1.91.368",
|
||||
"version": "1.91.369",
|
||||
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
@@ -304,6 +304,71 @@ describe('gstack-evidence check', () => {
|
||||
expect(chk.status).toBe(0);
|
||||
});
|
||||
|
||||
// Value: protects=a repository that gitignores .bun/ still gets a fingerprint (GST-449): an exclude pathspec naming an ignored path made
|
||||
// `git add -A` exit 1, so gstack-wtree failed and every review-log start and evidence check lost its fingerprint; fails_when=gstack-wtree
|
||||
// exits non-zero or the lane records no wtree with .bun/ (or /.bun) in .gitignore; why_new=the GST-421 test covered only an unignored
|
||||
// .bun/ (harness-evals#148); seam=none; no_claim=Windows git
|
||||
test('GST-449: with .bun/ gitignored, gstack-wtree still fingerprints and a lane that writes .bun/ grades FRESH', () => {
|
||||
for (const pattern of ['.bun/', '/.bun']) {
|
||||
fs.writeFileSync(path.join(repoDir, '.gitignore'), `${pattern}\n`);
|
||||
git('add .gitignore');
|
||||
git(`commit -q -m "ignore ${pattern}"`);
|
||||
fs.mkdirSync(path.join(repoDir, '.bun'), { recursive: true });
|
||||
fs.writeFileSync(path.join(repoDir, '.bun', 'x'), 'cache\n');
|
||||
const wt = runBin(path.join(ROOT, 'bin', 'gstack-wtree'), [], { cwd: repoDir });
|
||||
expect(wt.stderr).toBe('');
|
||||
expect(wt.status).toBe(0);
|
||||
expect(wt.stdout.trim()).toMatch(/^[0-9a-f]{40}$/);
|
||||
const r = run(['run', '--label', 'tests', '--', 'echo cache2 > .bun/y && echo green']);
|
||||
expect(r.status).toBe(0);
|
||||
expect(records().pop().wtree).toMatch(/^[0-9a-f]{40}$/);
|
||||
expect(run(['check', '--label', 'tests']).stdout).toContain('EVIDENCE: FRESH label=tests');
|
||||
fs.rmSync(path.join(repoDir, '.bun'), { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
// Value: protects=a repository that ignores .bun/ while still tracking a file under it gets a fingerprint (GST-449 review): the
|
||||
// index-aware check-ignore called .bun not ignored, so the exclude pathspec came back and `git add -A` exited 1; fails_when=gstack-wtree
|
||||
// exits non-zero, or its tree differs from a plain `git add -A`, or an edit or delete of the tracked file leaves it unchanged;
|
||||
// why_new=the pattern loop above starts from an untracked .bun/ only; seam=none; no_claim=Windows git
|
||||
test('GST-449: an ignored .bun/ that still tracks a file fingerprints like a plain git add -A, and edits to the tracked file count', () => {
|
||||
fs.mkdirSync(path.join(repoDir, '.bun'), { recursive: true });
|
||||
fs.writeFileSync(path.join(repoDir, '.bun', 'tracked'), 'v1\n');
|
||||
git('add -f .bun/tracked');
|
||||
git('commit -q -m "track .bun/tracked"');
|
||||
fs.writeFileSync(path.join(repoDir, '.gitignore'), '.bun/\n');
|
||||
git('add .gitignore');
|
||||
git('commit -q -m "ignore .bun/"');
|
||||
fs.writeFileSync(path.join(repoDir, '.bun', 'cache'), 'cache\n');
|
||||
const wtree = () => {
|
||||
const wt = runBin(path.join(ROOT, 'bin', 'gstack-wtree'), [], { cwd: repoDir });
|
||||
expect(wt.stderr).toBe('');
|
||||
expect(wt.status).toBe(0);
|
||||
return wt.stdout.trim();
|
||||
};
|
||||
const plain = () => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-wtree-idx-'));
|
||||
try {
|
||||
const env = { ...process.env, GIT_INDEX_FILE: path.join(dir, 'index') };
|
||||
spawnSync('git', ['read-tree', 'HEAD'], { cwd: repoDir, env, timeout: 10_000 });
|
||||
spawnSync('git', ['add', '-A'], { cwd: repoDir, env, timeout: 10_000 });
|
||||
return spawnSync('git', ['write-tree'], { cwd: repoDir, env, encoding: 'utf-8', timeout: 10_000 }).stdout.trim();
|
||||
} finally {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
};
|
||||
const clean = wtree();
|
||||
expect(clean).toBe(plain());
|
||||
fs.writeFileSync(path.join(repoDir, '.bun', 'tracked'), 'v2\n');
|
||||
const edited = wtree();
|
||||
expect(edited).not.toBe(clean);
|
||||
expect(edited).toBe(plain());
|
||||
fs.rmSync(path.join(repoDir, '.bun', 'tracked'));
|
||||
const deleted = wtree();
|
||||
expect(deleted).not.toBe(clean);
|
||||
expect(deleted).toBe(plain());
|
||||
});
|
||||
|
||||
test('GST-421: a lane that writes untracked source grades STALE and the reason names the path', () => {
|
||||
const r = run(['run', '--label', 'tests', '--', 'mkdir -p src && echo "export {}" > src/new.ts && echo green']);
|
||||
expect(r.status).toBe(0);
|
||||
|
||||
Reference in new issue
Block a user