mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-05 16:34:00 +01:00
fix: adversarial review, pass 5 (rule-23 grant, one finding)
bin/gstack-worktree-audit: git status runs with --untracked-files=normal --ignore-submodules=none, so a user's status.showUntrackedFiles=no config cannot hide untracked files and let a worktree holding them read clean and safe. Test: the fresh worktree under that config via GIT_CONFIG_* still reads scratch:1 / review (red before the fix). Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
96927f2d4c
commit
0d372b7349
3 files changed
+15
-2
No files matched your search
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
### Tests
|
||||
- `test/decision-trail.test.ts`: header once (an empty file gets one, a second append does not), the formula guard on every leading byte it covers, flattened tabs and line breaks, the start row's text and its idempotent rerun, a second run's start row, the `--run` refusal and admission, a `-h` cell appended as data, a missing final newline, CRLF rows and a repeated header, a blank file and a BOM, run ids with whitespace or quotes refused, usage errors.
|
||||
- `test/worktree-audit.test.ts`: a bare origin, a clone and twelve worktrees (merged, wip with an open PR, scratch with a chat today, stale, closed PR, a merged one whose path has a space, a merged one with only an untracked file, a registered one whose directory is gone, a locked one whose directory is gone, a broken one inside the clone with a precious file, one whose branch shares its name with a tag, one with only a gitignored `.env`) classified into every bucket through a fake `gh` (which records its arguments and runs the audit's own `--jq` over canned JSON with a fork PR when `jq` is installed) and a `CLAUDE_CONFIG_DIR` transcript directory; the fixture, its worktree registry included, is byte-identical after the audit; `-b`, `--no-fetch`, an unpushed commit on a closed PR reading `review` and an open PR on the reused name winning, no `gh` on PATH (git resolved from `git --exec-path`, so a distribution's wrapper script does not matter), a failing `gh`, a missing base and a non-repo path.
|
||||
- `test/worktree-audit.test.ts`: a bare origin, a clone and twelve worktrees (merged, wip with an open PR, scratch with a chat today, stale, closed PR, a merged one whose path has a space, a merged one with only an untracked file, a registered one whose directory is gone, a locked one whose directory is gone, a broken one inside the clone with a precious file, one whose branch shares its name with a tag, one with only a gitignored `.env`) classified into every bucket (also under `status.showUntrackedFiles=no`, which must not hide untracked files) through a fake `gh` (which records its arguments and runs the audit's own `--jq` over canned JSON with a fork PR when `jq` is installed) and a `CLAUDE_CONFIG_DIR` transcript directory; the fixture, its worktree registry included, is byte-identical after the audit; `-b`, `--no-fetch`, an unpushed commit on a closed PR reading `review` and an open PR on the reused name winning, no `gh` on PATH (git resolved from `git --exec-path`, so a distribution's wrapper script does not matter), a failing `gh`, a missing base and a non-repo path.
|
||||
|
||||
### Gate integrity
|
||||
- No threshold, timeout, trial count or case kind changed. No pinned skill changed; `landing-report/SKILL.md` grows by one line and is unpinned. `test/autoplan-driver.test.ts`'s line cap on `autoplan/DRIVER.md` moves 160 → 162 (measured 162) for the decision-trail paragraph at the end of the coordinator contract section; the file was at the cap before.
|
||||
|
||||
@@ -154,7 +154,9 @@ git worktree list --porcelain | awk 'BEGIN { RS=""; FS="\n" } {
|
||||
# Tracked edits are work in progress; untracked files alone are scratch. A
|
||||
# clean tree still names its ignored entries (.env, build output): they do
|
||||
# not change the bucket, but they are what removing the worktree would lose.
|
||||
if porcelain=$(git -C "$wt" status --porcelain 2>/dev/null); then
|
||||
# The flags pin what status reports: a status.showUntrackedFiles=no config
|
||||
# would otherwise hide untracked files and let a worktree holding them read clean.
|
||||
if porcelain=$(git -C "$wt" status --porcelain --untracked-files=normal --ignore-submodules=none 2>/dev/null); then
|
||||
if [ -z "$porcelain" ]; then
|
||||
ignored=$(git -C "$wt" ls-files --others --ignored --exclude-standard --directory 2>/dev/null | grep -c .)
|
||||
[ "$ignored" -gt 0 ] && dirty="clean,ignored:$ignored" || dirty=clean
|
||||
|
||||
@@ -206,6 +206,17 @@ describe("gstack-worktree-audit", () => {
|
||||
expect(after).toBe(before);
|
||||
});
|
||||
|
||||
test("status.showUntrackedFiles=no in the user's config does not hide untracked files", () => {
|
||||
const r = spawnSync("bash", [BIN, "--no-fetch"], {
|
||||
cwd: repo, encoding: "utf8", timeout: 60_000,
|
||||
env: { ...process.env, PATH: `${fakeBin}:${process.env.PATH}`, CLAUDE_CONFIG_DIR: claudeDir, HOME: tmp, GIT_CONFIG_COUNT: "1", GIT_CONFIG_KEY_0: "status.showUntrackedFiles", GIT_CONFIG_VALUE_0: "no" },
|
||||
});
|
||||
expect(r.status).toBe(0);
|
||||
const row = r.stdout.split("\n").find(l => l.endsWith("\t" + wts.fresh))!;
|
||||
expect(row.split("\t").slice(3, 4)).toEqual(["scratch:1"]);
|
||||
expect(row.split("\t")[7]).toBe("review");
|
||||
});
|
||||
|
||||
test("an unpushed commit on a branch with a closed PR is review, an open PR on the same name wins and holds it, and -b picks the base", () => {
|
||||
git(wts.closed, "commit", "-q", "--allow-empty", "-m", "one more");
|
||||
const r1 = audit("-b", "origin/main", "--no-fetch", repo);
|
||||
|
||||
Reference in new issue
Block a user