From 9b885860c5b0b10d125bb86a2f18333885316e97 Mon Sep 17 00:00:00 2001 From: aarroyo Date: Sat, 8 Aug 2026 11:32:18 -0500 Subject: [PATCH 1/2] chore: ignore macOS Finder metadata, and delete the three copies in the tree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three `.DS_Store` files were sitting untracked in the root, `src/` and `src/packages/`. None was tracked by git, so nothing is removed from history — they were deleted from the working tree and the pattern added. The root one is what made `03-validate-root-cleanliness` exit 1 locally throughout the GT-622/656/657 work, surfacing as the single non-zero command in `41-validate-evidence-commands --execute` while CI, which checks out fresh, never saw it. Verified rather than assumed: with the rule in place, a planted `.DS_Store` in the root and in `src/` leaves `git status --porcelain` with 0 entries. Known and NOT fixed here: `03-validate-root-cleanliness` reads the filesystem, not git, so it still exits 1 while a `.DS_Store` exists on disk — measured by planting one after adding the rule. Deleting is therefore not durable; the next Finder window recreates the file and the guard goes red again. Making the guard skip what git ignores is the durable fix and is a change to a CI guard, so it is left as a separate decision rather than folded into a chore. Co-Authored-By: Claude Opus 5 --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitignore b/.gitignore index 1867d4be..582c8ac2 100644 --- a/.gitignore +++ b/.gitignore @@ -207,3 +207,11 @@ rag-backfill-receipt.json # worktree. /src/packages/mcp-server/__capability-operations.entry.ts /src/packages/mcp-server/__capability-operations.json + +# macOS Finder metadata. Written by the OS anywhere a directory is opened in +# Finder, so it reappears without anyone editing a file — three copies were +# sitting untracked in the root, src/ and src/packages/ on 2026-08-08. They are +# ignored rather than merely deleted because deleting is not durable: the next +# Finder window recreates them, and an untracked file in the root is what +# `03-validate-root-cleanliness` fails on. +.DS_Store From caf55d83b2a347ccfe3334a7c271c0443faddad8 Mon Sep 17 00:00:00 2001 From: aarroyo Date: Sat, 8 Aug 2026 11:37:31 -0500 Subject: [PATCH 2/2] fix(harness): root cleanliness governs what the repository holds, not what the OS leaves lying around MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The durable half of the .DS_Store chore, measured in the same change: adding the ignore rule did NOT stop `03-validate-root-cleanliness` exiting 1, because it reads the filesystem rather than git. Deleting the files was therefore not a fix — the next Finder window recreates them and the guard goes red again on a developer's machine, for a reason no commit can address. CI always checks out fresh and never saw it, which is why it survived this long. What git ignores is by definition not in the repository, so it is not what "root cleanliness" governs. Entries git reports as ignored are skipped. This does not weaken the taxonomy. An unauthorized file that git does NOT ignore still fails — which is every file a commit could actually introduce — and both directions are observed rather than argued: planted .DS_Store -> exit 0, "35 read, 2 ignored by git, 33 checked" planted unauthorized file -> exit 1, named in the failure list planted unauthorized dir -> exit 1, named in the failure list Two paranoia rules, because this set SUBTRACTS from what is checked and a bogus answer would hollow the guard out silently: - `git check-ignore` exit 128 (no git, not a work tree) falls back to the EMPTY set, so every entry stays under the taxonomy, and it warns rather than degrading quietly. The failure of an optional query must never widen what a guard forgives. - An answer claiming package.json, .github or .harness is ignored stops the run. Those are tracked by construction, so such an answer is a broken query, not an unusual repository. The pass line now reports read / ignored / checked separately, so the denominator that matters is visible instead of implied. Guards 03, 39, 40, 42 (denominators) and 43 (negative fixtures) all exit 0. Co-Authored-By: Claude Opus 5 --- .../ci/03-validate-root-cleanliness.mjs | 62 ++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/.harness/scripts/ci/03-validate-root-cleanliness.mjs b/.harness/scripts/ci/03-validate-root-cleanliness.mjs index 48fcfb8d..c2ba0d07 100755 --- a/.harness/scripts/ci/03-validate-root-cleanliness.mjs +++ b/.harness/scripts/ci/03-validate-root-cleanliness.mjs @@ -2,6 +2,7 @@ import fs from "node:fs"; import path from "node:path"; +import { execFileSync } from "node:child_process"; // GT-578: this guard reads exactly one directory. If `process.cwd()` is not the // repository root — the shape that broke 12/21/31/33/34 in GT-556 — readdirSync @@ -82,6 +83,34 @@ const explicitlyDeniedDirectories = new Map([ ] ]); +/** + * The subset of `names` that git ignores, as a Set. + * + * `git check-ignore --stdin` exits 0 when it ignored something, 1 when it + * ignored nothing, and 128 when it could not answer at all — a missing git, a + * directory that is not a work tree. Only the first two are answers. Anything + * else falls back to the EMPTY set, which is the strict reading: every entry + * stays under the taxonomy. The failure of an optional query must never widen + * what a guard forgives, and it says so out loud rather than degrading quietly. + */ +function gitIgnoredRootEntries(cwd, names) { + if (names.length === 0) return new Set(); + try { + const out = execFileSync("git", ["check-ignore", "--stdin"], { + cwd, input: `${names.join("\n")}\n`, encoding: "utf8", stdio: ["pipe", "pipe", "ignore"], + }); + return new Set(out.split("\n").map((l) => l.trim()).filter(Boolean)); + } catch (error) { + // Exit 1 is the real answer "none of these are ignored", not a failure. + if (error.status === 1) return new Set(); + console.warn( + `⚠️ Root Cleanliness: could not ask git which entries are ignored (${error.status ?? error.code}).\n` + + ` Continuing with NOTHING treated as ignored, so every root entry is checked.`, + ); + return new Set(); + } +} + const failures = []; const rootEntries = fs.readdirSync(root, { withFileTypes: true }); @@ -101,7 +130,35 @@ if (missingAnchors.length > 0) { process.exit(1); } +// What git ignores is, by definition, not in the repository — and this guard +// governs what the repository root HOLDS, not what an operating system leaves +// lying in the directory. Before this, a single `.DS_Store` — written by Finder +// merely for opening the folder, tracked by nothing, recreated the moment it is +// deleted — failed the guard on a developer's machine while CI, which always +// checks out fresh, never saw it. A guard that is red for a reason no commit can +// fix is the failure mode GT-622 was opened to remove. +// +// This does NOT weaken the taxonomy: an unauthorized file that git does not +// ignore still fails, which is every file a commit could actually introduce. +const ignoredNames = gitIgnoredRootEntries(root, rootEntries.map((e) => e.name)); + +// Paranoia in the direction that matters. This set SUBTRACTS from what is +// checked, so a bogus answer from git would hollow the guard out silently. The +// anchors are tracked by construction, so an answer claiming they are ignored is +// not an unusual repository — it is a broken query, and must stop the run. +const ignoredAnchors = ANCHORS.filter((a) => ignoredNames.has(a)); +if (ignoredAnchors.length > 0) { + console.error( + `❌ Root Cleanliness Validation cannot run: git reports ${ignoredAnchors.join(", ")} as ignored.\n` + + `These are tracked by construction, so the ignore query is wrong, and trusting it would\n` + + `subtract real entries from the check and report a pass over them.`, + ); + process.exit(1); +} + for (const entry of rootEntries) { + // Ignored by git: not in the repository, so not this guard's business. + if (ignoredNames.has(entry.name)) continue; // `.git` is only in `allowedDirectories`, which is correct in a normal // checkout but not in a `git worktree`: there, `.git` at the root is a // plain text file (`gitdir: `) redirecting to the real one, so it @@ -135,4 +192,7 @@ if (failures.length > 0) { process.exit(1); } -console.log(`✓ Root Cleanliness Validation Passed (${rootEntries.length} root entr(ies) inspected)`); +console.log( + `✓ Root Cleanliness Validation Passed (${rootEntries.length} root entr(ies) read, ` + + `${ignoredNames.size} ignored by git, ${rootEntries.length - ignoredNames.size} checked against the taxonomy)`, +);