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 <noreply@anthropic.com>
This commit is contained in:
benjamin beres 2026-07-27 15:14:34 +02:00
parent a3259400a3
commit 8d35cd8a72
1 changed files with 37 additions and 14 deletions

View File

@ -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."