From 39ec619e90f7b640e898294323445ab9006449ed Mon Sep 17 00:00:00 2001 From: think Date: Tue, 4 Aug 2026 03:58:51 -0700 Subject: [PATCH 1/3] fix: ignore node_modules regardless of file type, and untrack it again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A trailing slash in .gitignore matches a directory only, so a symlink of the same name is still picked up by `git add -A`. Demonstrated: pattern 'node_modules/' -> staged: 1 pattern 'node_modules' -> staged: 0 66bb72b untracked the symlink but left the pattern alone, so the very next `git add -A` — in f866f14, the review-fix commit — re-added it, and it reached main. The link points at an absolute path in a local worktree, so a fresh clone gets a dangling node_modules. Untracking only sticks once the pattern does, so both happen here. The other never-tracked tool directories lose their trailing slashes for the same reason. Only the index entry is removed; the local symlink stays. --- .gitignore | 21 +++++++++++++-------- node_modules | 1 - 2 files changed, 13 insertions(+), 9 deletions(-) delete mode 120000 node_modules diff --git a/.gitignore b/.gitignore index 3d6c5012..ccf9822c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,21 +7,26 @@ Icon? .Spotlight-V100 .Trashes +# A trailing slash matches directories only, so a symlink of the same name is +# still staged by `git add -A` — which is how the node_modules symlink reached +# a commit. These names are never something to track, whatever their file type, +# so they are matched without the slash. + # Editor and local workspace files *.swp *.swo *~ -.idea/ -.vscode/ -.obsidian/ -.claude/ -.codex/ +.idea +.vscode +.obsidian +.claude +.codex .audit_reports/* EDITORS-REPORT.md # Dependencies -node_modules/ +node_modules # SwiftPM build output -macos/.build/ -macos/.dist/ +macos/.build +macos/.dist diff --git a/node_modules b/node_modules deleted file mode 120000 index 09e149a8..00000000 --- a/node_modules +++ /dev/null @@ -1 +0,0 @@ -/Users/james/git/think-agents/node_modules \ No newline at end of file From 9d555f094a425408a81b803c2ac3ee688bbe566e Mon Sep 17 00:00:00 2001 From: think Date: Tue, 4 Aug 2026 03:59:13 -0700 Subject: [PATCH 2/3] ci: expose a stable required status check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Branch protection matches a status check by its literal context string, and a matrix job reports as " ()". The required check on main is "test (22)", so the day the matrix moves off Node 22 that context stops being reported entirely — pull requests block forever while the ruleset still shows a green, satisfied rule. The failure mode is silence, which is the hardest kind to diagnose. An aggregate job named "ci" fixes the context in place. It gates on needs.test.result, which is success only when every matrix leg succeeded, so the matrix can grow without touching branch protection. always() rather than !cancelled(): a check that never reports looks identical to a blocked branch, so a cancelled run has to fail loudly. needs.test.result reaches the shell through env rather than ${{ }} interpolation, so the value cannot be expanded as shell syntax. The required context must move from "test (22)" to "ci" once this is on main; until then this job runs without being required. --- .github/workflows/ci.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca129c3e..c3aee6d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,3 +28,30 @@ jobs: - run: npm ci - run: npm run lint - run: npm test + + # The single required status check. + # + # Branch protection matches a check by its literal context string, and a matrix + # job's context is " ()" — so requiring "test (22)" + # silently stops reporting the moment the matrix changes, and every pull request + # blocks forever with a rule that still looks green in the ruleset UI. This job + # keeps one stable context, "ci", no matter how the matrix grows. + # + # always() rather than !cancelled(): a check that never reports is + # indistinguishable from a blocked branch, so a cancelled run must fail loudly + # instead of going silent. + ci: + name: ci + runs-on: ubuntu-latest + needs: [test] + if: always() + steps: + - name: Require every test job to have succeeded + env: + TEST_RESULT: ${{ needs.test.result }} + run: | + if [ "$TEST_RESULT" != "success" ]; then + echo "test job result: $TEST_RESULT" + exit 1 + fi + echo "test job result: success" From 6d7363cc9dba333b131b8c71b8340b14ff2857cf Mon Sep 17 00:00:00 2001 From: think Date: Tue, 4 Aug 2026 04:08:36 -0700 Subject: [PATCH 3/3] test: prove the ci gate fails when the test job fails Throwaway branch. Adds a deliberately failing step to the test job so the aggregate ci job can be observed reporting failure. Not for merge. --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3aee6d5..3707c471 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,8 @@ jobs: - run: npm ci - run: npm run lint - run: npm test + - name: DELIBERATE FAILURE (throwaway branch, proves the ci gate fails) + run: exit 1 # The single required status check. #