From 8d35cd8a72f3c7fd918863155d0e17cd412cc7f3 Mon Sep 17 00:00:00 2001 From: benjamin beres Date: Mon, 27 Jul 2026 15:14:34 +0200 Subject: [PATCH] Drop the hard jq dependency from gstack-gbrain-source-wireup check_source_state() shelled out to jq and hard-failed without it: jq required for source state detection. Install jq (brew install jq) and re-run. WARNING: gbrain wireup failed; run .../gstack-gbrain-source-wireup manually Two separate problems. The dependency itself was unnecessary. Its stated justification in the comment -- "jq is documented as a dependency of gstack-gbrain-detect (transitive)" -- is stale: gstack-gbrain-detect is TypeScript and no longer shells out to jq, as its own comments about "the bash version's jq" record. jq had become a hard dependency of this one code path. Meanwhile bun already is a hard dependency of gstack, several bins carrying a `#!/usr/bin/env bun` shebang, so parsing the two identical `.sources[] | select(.id==$id) | .local_path` extractions with bun removes a dependency instead of documenting one. The advice was not actionable. `brew install jq` does not apply on Windows/git-bash, where jq is absent by default. The practical effect there: the gbrain wireup is skipped on every run, as a warning that the run continues past. brain-restore carrying on is the right behaviour, but the breakage becomes routine and therefore stops being read. The loud-failure property that adversarial review asked for is kept -- without a JSON parser we still cannot tell "absent" from "missing-tool" and would falsely re-add an existing source, so check_source_state still probes and still dies. Only the tool it probes for changed, to one that is already required. Verified on Windows 11 / git-bash / bun 1.3.14: `--probe` returns identical output with jq present and with jq removed from PATH, where it previously died. The bun extraction was checked against jq on real `gbrain sources list --json` output, plus the absent-id and invalid-JSON cases, which both yield "". Co-Authored-By: Claude Opus 5 --- bin/gstack-gbrain-source-wireup | 51 ++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/bin/gstack-gbrain-source-wireup b/bin/gstack-gbrain-source-wireup index a8bf7e42d5..0a8ea30e3d 100755 --- a/bin/gstack-gbrain-source-wireup +++ b/bin/gstack-gbrain-source-wireup @@ -35,7 +35,7 @@ # at src/core/config.ts:53), so a process that flips config.json mid-sync # can't redirect us at a different brain mid-stream. # -# Depends on: jq (transitive via gstack-gbrain-detect). +# Depends on: bun (already a gstack dependency), gbrain, git. set -euo pipefail @@ -200,21 +200,46 @@ ensure_worktree() { } # ---- gbrain sources operations ---- +# Read .sources[] | select(.id==$id) | .local_path out of `gbrain sources list`. +# +# This used to shell out to jq, and hard-failed when jq was absent. The stated +# justification -- "jq is documented as a dependency of gstack-gbrain-detect +# (transitive)" -- is stale: gstack-gbrain-detect is TypeScript and no longer +# shells out to jq. So jq was a hard dependency of this path alone. +# +# bun, by contrast, already is a hard dependency of gstack (several bins carry a +# `#!/usr/bin/env bun` shebang), so parsing the JSON with it removes a +# dependency rather than documenting one. That matters on Windows/git-bash, +# where jq is not present by default and the old advice -- `brew install jq` -- +# is not actionable at all: the wireup was skipped on every run, in a warning, +# until the breakage became routine and therefore invisible. +# +# Prints the path, or nothing when the id is absent or the payload is not JSON. +source_local_path() { + gbrain sources list --json 2>/dev/null \ + | GB_SOURCE_ID="$1" bun -e ' + const raw = await Bun.stdin.text(); + try { + const s = (JSON.parse(raw).sources || []) + .find((x) => x.id === process.env.GB_SOURCE_ID); + process.stdout.write(s?.local_path ?? ""); + } catch { process.stdout.write(""); } + ' 2>/dev/null \ + | tr -d '[:space:]' +} + # Returns 0 if source with id exists at expected path. 1 if exists but path differs. 2 if absent. -# Hard-fails (exits non-zero via die) if jq is missing — without jq we cannot -# distinguish "absent" from "missing-tool" and would falsely re-add an existing -# source. jq is documented as a dependency of gstack-gbrain-detect (transitive) -# but adversarial review flagged the silent-fall-through path; this probe makes -# the failure mode loud. +# Hard-fails (exits non-zero via die) if the JSON parser is unavailable -- we +# cannot then distinguish "absent" from "missing-tool" and would falsely re-add +# an existing source. Adversarial review flagged that silent-fall-through path, +# so the probe stays; only the tool it probes for has changed. check_source_state() { local id="$1" - if ! command -v jq >/dev/null 2>&1; then - die "jq required for source state detection. Install jq (brew install jq) and re-run." 1 + if ! command -v bun >/dev/null 2>&1; then + die "bun required for source state detection. bun is a gstack dependency; see the install instructions in README.md." 1 fi local existing_path - existing_path=$(gbrain sources list --json 2>/dev/null \ - | jq -r --arg id "$id" '.sources[] | select(.id==$id) | .local_path' 2>/dev/null \ - | tr -d '[:space:]') || existing_path="" + existing_path=$(source_local_path "$id") || existing_path="" if [ -z "$existing_path" ]; then return 2 fi @@ -295,9 +320,7 @@ do_wireup() { # registration. Just sync from our local worktree — gbrain stores pages # by content, not by local_path. The metadata is informational only. local existing_path - existing_path=$(gbrain sources list --json 2>/dev/null \ - | jq -r --arg id "$id" '.sources[] | select(.id==$id) | .local_path' 2>/dev/null \ - | tr -d '[:space:]') || existing_path="" + existing_path=$(source_local_path "$id") || existing_path="" if [ "$(basename "$existing_path")" = "$(basename "$WORKTREE")" ] \ && [ "$existing_path" != "$WORKTREE" ]; then warn "source $id is registered at $existing_path (likely another machine's local copy of the same brain repo). Skipping re-registration; will sync from local worktree."