Skip to content

Releases: ReidenXerx/bearing

1.0.12 — an uninstall that stops hiding, and a warning that stopped naming what it forbade

Choose a tag to compare

@ReidenXerx ReidenXerx released this 17 Aug 15:34

1.0.11 was tagged but never published to npm, so its changes ship here. If you are coming from
1.0.10, this release contains both.

Fixed — a stealth uninstall left bearing wired in, and hid that it had

git status came back clean, so the repo looked untouched. It was not: eleven guards were still
registered in .claude/settings.local.json, every one of them pointing at a hook script the same
uninstall had just deleted — a failed spawn on every session start, prompt and tool call.

mergeClaudeSettings writes to settings.local.json under stealth and settings.json otherwise;
removeClaudeSettings only ever knew the second. The visible path was fine, which is why no
existing test saw it — they all install visibly.

Two things made it worse than a leftover file:

  • The concealment survived too. removeExclude() existed and was imported into kit.mjs, and
    was never called. So the leftovers stayed hidden and git status reported clean — the repo
    looked uninstalled precisely because the hiding mechanism outlived the thing it hid.
  • .bearing/contract.md was never recorded. 21KB of generated contract, written at install and
    absent from manifest.files, so uninstall could not know it owned it (NS-22).

All three fixed, with the negative control: a user's own hooks, permissions and .git/info/exclude
entries survive untouched.

Fixed — the block named npx gitnexus analyze in order to forbid it

Reported with a screenshot. The block correctly said node scripts/bearing-agent.mjs refresh, and
the agent ran npx gitnexus analyze anyway — because bearing told it to. The message ended "Run
yourself — never ask the user to run npx gitnexus analyze"
, and the only concrete command in that
sentence is the one it meant to prohibit, so it reads as the instruction. Following it lands on
the raw indexer and reintroduces the npx invocation the command resolver exists to remove. Naming a
command in order to forbid it is naming it; the message now names none.

The same screenshot showed the agent passing --skip-agents-md by hand, which bearing should do
itself under stealth. analyze writes its stats block into AGENTS.md / CLAUDE.md and the
stabilizer strips it after — but in between, the repo is dirty, so anything reading git status
in that window sees bearing having modified tracked files. Not writing it beats writing and
reverting. Passed on every stealth tier; the stabilizer stays as the net for an indexer run bearing
did not launch.

1.0.10 — minions: fan out to gather, and keep the thinking

Choose a tag to compare

@ReidenXerx ReidenXerx released this 17 Aug 15:34

Added — Minions, a fifth module: fan out to gather

The README carries a generated diagram for it like every other module — the fan-out is only half
the picture, so it shows what comes BACK (citations, not opinions) and where the conclusion is
drawn.

Your agent can already spawn subagents. What it does not know is when it should — so it grinds
through forty files serially, or samples five and generalises. That judgment is the module; the
fan-out is plumbing.

Fan out when the work is bounded, verifiable, independent and wide (3+ units): every call site
of a symbol, every file still on the old API, every migration site, every route to audit against one
rule. Don't when the judgment is the work, when the answer only survives verbatim, or when the
unit needs context the subagent was never in.

Each subagent carries the project's north-stars and pinned persona, and returns a fixed shape:

FOUND    src/fees.ts:88 — const fee = gross * RATE
CHECKED  rg "\* RATE" src/ --type ts
MISSED   dynamic dispatch in src/plugins/ — could not resolve

Minions gather; your agent concludes — they do minimal or zero reasoning. A subagent returning a
verdict puts a cheaper model's summary between the evidence and your decision, which is the drift
this product exists to prevent. And the CHECKED/FOUND split is load-bearing: CHECKED filled
with FOUND empty means it looked and there was nothing; both empty means it never understood the
task
. In prose those are the same sentence — the same error as reading a graph zero as absence.

Claude Code only, since it is the only runtime that can spawn subagents with a model choice — the
README says so rather than implying parity.

Added — the fan-out trigger is now enforced, and citations are checkable

Four follow-ups that turn minions from instructions into something with feedback.

  • A nudge when you are grinding. The trigger lived only in the always-on contract, which means
    it fired when the agent happened to recall it. A PostToolUse hook now notices 8 distinct gather
    targets in a row with no delegation and suggests fanning out — once per session, advisory, never a
    block (NS-5). Distinct targets, not calls: re-reading one file while editing it is not grinding,
    and a nudge that fires during every edit loop gets ignored forever. minionFanoutThreshold: 0
    disables it.
  • node .bearing/lib/verify-citations.mjs src/a.ts:88 prints what is actually on each cited
    line and exits non-zero if any do not resolve. "Spot-check one per minion" was advice; a
    fabricated file:line is the one failure the return shape cannot catch by itself. Deliberately
    not an npm script — those are all owned by the GitNexus module, so a minions-only install could
    not run it.
  • Fan-outs and grind-nudges are counted in the scorecard, so the module can be measured rather
    than assumed — the same reason the gates keep a tally instead of asserting they help.
  • Subagents inherit graph-first. A minion grepping for call sites in a repo with a graph is
    doing the exact thing the gates exist to redirect, one level down where no gate can see it.

Also fixed: the context-pressure nudge still named the old shared .bearing/.task-core.md, so it
sent the agent to write a core its own recovery would not read.

Added — minions run on a middle tier, and wanting a smarter one is a smell

sonnet by default, overridable per machine via minionModel in .bearing/hooks.local.json. If
the tier is unavailable the fan-out runs anyway — a costlier minion is a nuisance, a skipped unit is
a hole in the answer.

The tier is not a cost setting, it is a consequence: a middle model is sufficient precisely
because minions do no reasoning (NS-24). Which makes it a diagnostic. If you want a smarter
minion, you delegated judgment
— the fix is the split, not the model. Same when a unit keeps
returning MISSED: do that unit yourself rather than re-running it on a bigger model.

Changed — one spawn harness, shared by microscope and minions

Both modules send work to anchored subagents, and the mechanics are identical: the same pinned
persona from .bearing/domain.json, the same north-star subset, parallel where the runtime allows
it, and the duty to say what went unchecked. That is now authored once and rendered into both
skills, with a test that fails if a copy goes stale.

The return contract stays separate, deliberately — the two are opposite on the axis that
matters. A microscope lens must reason; opinions are the entire point of it. A minion must not
(NS-24). Unifying those would either silence the lenses or let the minions editorialise.

The fan-out threshold is now 3 units, not 5 — at three they already run concurrently, so the
round-trip is paid once rather than three times.

Fixed — domain inference read the wrong things, and a missing persona stayed quiet

Found by pinning personas on real repos, and all three failures pointed the same way: a
screen-capture app and a patient-facing health platform were both branded developer-tooling.

  • Dependencies are not identity. package.json's dependency list counted as the repo
    describing itself, so a jwt dependency scored as a strong identity signal — overriding the
    package's own description. Almost every web app depends on an auth library; almost none are
    identity products. Name, description and keywords only.
  • \bhealth\b does not match "Healthcare". The word boundary fails on the trailing care, so
    a package naming its own domain scored zero for it. A repo naming its domain should be the
    strongest signal there is.
  • The analyzer's block is not the repo's voice. bearing's own contract was stripped before
    inference, but the stats block analyze writes into CLAUDE.md — "indexed by GitNexus… use the
    MCP tools" — was not, and that alone suggested developer-tooling for a claims platform.

And an unresolved domain is now raised where it will be seen: the installer prints the
consequence ("reviews here will be generic") rather than one warn line, and the always-on
contract carries the ask so the agent meets it every session — phrased as a job it can usually
answer from the code, asked once, taking no for an answer.

Fixed — "would be committed" sent you to a .gitignore that was already correct

Found updating a real repo installed back at 1.0.6. The machine-local check reported
.bearing/.bearing-session-primed.flag as committable, but the ignore rule was present and
correct — the file had been COMMITTED before that rule existed, and git never ignores a tracked
file however good the rule is.

The check now separates the two causes and names the exit for the one you cannot fix by editing a
file: already COMMITTED, so the ignore rule cannot help — run git rm --cached <path> (NS-6).

1.0.9 — the agent stops fearing a window it isn't in, and a broken index stops blocking commits

Choose a tag to compare

@ReidenXerx ReidenXerx released this 08 Aug 21:09

Fixed — the agent thought every session was a 200k one

A 1M session carrying 300k tokens read as 152% full, so the agent hedged about running out and
wrote task-cores from the first hour — permanently, since it never got less full. The window was a
hardcoded 200,000.

It cannot simply be looked up: the transcript records no window, and claude-opus-5 is the same
model id on a 200k and a 1M session. So it is corrected by evidence instead — a session cannot have
carried more tokens than it can hold, so usage above the assumed window disproves the assumption.
The observation is rounded up to a real window rather than trusted exactly, since usage is sampled
at the last assistant turn and the true ceiling is higher than whatever was seen.

Revises upward only: too small is the failure being fixed, too large merely delays a warning.
An explicit contextWindowTokens in .bearing/hooks.local.json is your own statement of fact and
still wins.

Fixed — the pre-commit hook blocked commits when the INDEX was broken

Reported from a real repo: bearing:full-pdg ran unguarded under set -e, so any indexer failure
blocked every commit. Theirs ended "graph write collapsed — 200,722 relationships produced, 64,983
readable"
, reproducibly, and the team committed with --no-verify for days.

Blocking a commit because an index could not be built fails the developer for something that is
not their fault and that they cannot fix from there (NS-5) — and --no-verify teaches people to
skip the hook permanently, which costs far more than one stale index. bearing's CI has always been
report-not-gate for exactly this reason; the commit hook, where a block is more disruptive, was the
opposite.

The two halves are now split (NS-8): fail open for the developer — the commit proceeds — and
fail closed for the graph — the index is marked failed, so the session brief tells the agent
Index is STALE instead of letting it answer from a broken one. The warning names how to fix it
(bearing:agent-refresh), how to report it (bearing:fallback), and how to get the old behaviour
(BEARING_PRECOMMIT=block, mirroring GITNEXUS_CI_MODE=block).

1.0.8 — an uninstall that actually leaves, and a task-core per chat

Choose a tag to compare

@ReidenXerx ReidenXerx released this 08 Aug 15:50

Fixed — uninstall did not leave the repo as it found it

A shakedown of install / update / uninstall across every runtime and module combination. The
install and update paths held up — idempotent over repeated runs, recorded choices preserved,
feature downgrade removing what it should. Uninstall was the weak one, and every defect below needs
a SECOND install to appear, which is why a suite that installs once never saw them.

  • The Node floor stayed in the user's package.json forever. Install adds
    engines.node >= 22.9.0 because bearing's own scripts need it; uninstall never took it back. Left
    behind it is enforced by npm under engine-strict, by Yarn always, and by CI — so a project on
    Node 20 fails to install because of a tool it removed. Now recorded at install and removed at
    uninstall, and a floor the user set themselves is never touched in either direction.
  • Uninstall restored bearing's own Cursor config. A backup answers "was there a file of the
    user's here before bearing?", and only a first install can observe that — by the second run
    .cursor/hooks.json exists because we wrote it. So update backed up our own file and uninstall
    faithfully restored it, leaving Cursor registering hooks whose scripts the same uninstall had just
    deleted: a failed spawn on every session start, prompt and tool call, in a repo the user believes
    is clean. The earlier install's answer now wins, per adapter so a runtime added later still backs
    up a genuinely user-owned file. Uninstall also strips a leftover registration written by an older
    version, and only when the entry is the one we write — a user's own gitnexus MCP server survives.
  • Empty .zed/ and .cursor/ shells were left behind. Install creates these in repos that
    never had them; uninstall wrote back {"context_servers":{},"agent":{"profiles":{}}} and stopped.
    Both are now removed once nothing of the user's is left in them — and a settings file holding
    their theme, keymap or own servers is left exactly as it is.
  • An intel-only contract pointed at a section that isn't there. The north-stars text
    distinguished itself from "the graph-first North star above", which in a repo without the GitNexus
    module is a pointer to a section the filter had just removed (NS-13). Contract tags now apply to a
    paragraph as well as a section.
  • A bad target printed a stack trace. "Not a git repository" is a fact the user needs, not a
    crash to read; the stack is one BEARING_DEBUG=1 away.

Known and accepted: installing rewrites package.json as 2-space JSON, so a repo that minified or
tab-indented it sees a whitespace-only diff that uninstall cannot undo.

Fixed — the task-core was one file per REPO, not per chat

.bearing/.task-core.md was a single path. The moment two agent sessions ran in the same
repository — a second editor window is enough, and three is routine — they overwrote each other's
save-state. The failure is worse than losing the file: on recovery a session reads whatever the
last writer left, so it reconstructs from another chat's task with full confidence. That is
precisely the drift a task-core exists to prevent, manufactured by the task-core itself.

  • One file per chat, at .bearing/task-cores/<chat-id>.md. The key comes from the transcript
    path every hook already receives; its basename is the session id and it stays stable across
    compaction, which is exactly when the core must be found again.
  • The path is now unguessable, so the session brief states it — on a fresh start as well as on
    recovery. Before, the agent could name the one documented file from memory; without this it
    could not write a core proactively at a milestone and would only learn its path once compaction
    hit, which is too late.
  • A pre-existing single-file core is still read, so upgrading mid-task loses nothing.
  • Old chats' cores are pruned after 30 days, never the current chat's however old it looks.
  • .bearing/task-cores/ is gitignored and covered by the post-install check — the previous rule
    matched a file, not a folder, so every chat's save-state would have been committed. Uninstall
    removes the empty directory but keeps any core still in it, the same way it keeps north-stars.

Fixed — the contract promised commands the install did not have

Found by dogfooding: bearing's own repo runs an intel-only install, and npm run bearing:northstars
— the command its own CLAUDE.md advertises — does not exist there.

Every npm script is owned by the GitNexus module, so an intel-only install has none of them. The
north-stars section was advertising a command you only get by installing a different module, in
the one file every agent reads as authoritative (NS-13, NS-20). The contract now points at the
skill and the file path, both of which are always installed.

  • The generated-file note shipped into user repos said "edit there, run npm run gen:contract" — a
    path and a command that exist only in bearing's own repo. It now tells the user what they actually
    need to know: edits to that block are replaced on the next update.
  • The post-install check that catches dangling commands scanned only executable files, on the
    reasoning that docs mention commands illustratively. True in general, but the contract is an
    instruction, so those three files are now held to what is installed — including when
    package.json has no scripts at all, which the original scan skipped as uninteresting and was in
    fact the broken case.
  • README lists those commands under "With the GitNexus module".

Added — bearing update tells you what changed

Nobody visits a changelog; everybody reads the terminal they just typed into. An update is the one
moment you are asking "what did this just do to my repo?", and bearing already records the version
you were on, so it can answer:

  What's new since 1.0.4
    1.0.5 — the compound-command notice was silent on the shape it exists for
    1.0.6 — one MCP server for the whole machine, instead of one per client
    1.0.7 — a domain expert on every review, and an installer that checks its own claims
    Full notes: https://github.com/ReidenXerx/bearing/releases

Titles only — 1.0.7's section alone is 13k characters, and burying the next steps under it would
make the useful part unreadable. It stays silent rather than guess: a fresh install, an unknown
previous version, no packaged changelog, or nothing new all print nothing, and an update never
fails because release notes could not be read.

CHANGELOG.md now ships in the npm package, so this works from an install as well as a checkout.

Added — every release is on GitHub

The repo had zero tags and zero releases, so the changelog was visible only to someone who thought
to open the file. All eight — v1.0.0 through v1.0.7 — are now published, each tagged at the
commit that actually shipped it, with its changelog section as the body. npm run release:notes -- --list shows what is releasable; it refuses Unreleased and the pre-rename 1.2.0.

The README's changelog link is now absolute, since npm does not reliably rewrite relative paths
(NS-17), and points at the releases page too.

1.0.7 — a domain expert on every review, and an installer that checks its own claims

Choose a tag to compare

@ReidenXerx ReidenXerx released this 08 Aug 15:34

The kit was renamed gitnexus-agent-kitbearing, but three identifiers kept the old name
because renaming them touches files in your repository. Two were merely stale. One was a bug.

Fixed

  • An intel-only install no longer creates .gitnexus/. The install manifest lived at
    .gitnexus/agent-kit-manifest.json, and writing it created the graph tool's index directory in
    repos that had explicitly declined the GitNexus module — an index directory for an indexer that
    was never installed. NS-13 names four channels through which enforcement must not leak; this was
    a fifth. The manifest now lives at .bearing/manifest.json, and the four gitnexus entries in
    the managed .gitignore block are gated on the module too. .bearing/.gitnexus-* stays
    ungated: the core session-primer writes it, and .gitnexus-northstar-counter.json belongs
    to the north-stars module rather than the graph.
  • The managed AGENTS.md / CLAUDE.md block is <!-- bearing:BEGIN -->. Matching only a new
    marker would have left the old block in place in every installed repo and appended a second one
    beside it — the exact failure GITIGNORE_MARKERS_LEGACY already documents. Both adapters now
    match the current marker or any legacy one, replace the first in place, and drop any duplicate.
  • The gitignore migration pointed at a command that does not exist. It rewrote
    (safe to remove via gn-kit uninstall) into (safe to remove via gn-agent-kit uninstall)
    one dead binary name for another. Both now collapse to bearing-uninstall (NS-6).
  • Renamed the remaining user-visible strings: the bearing verification banner, the
    bearing installed health detail, and a sync script that told the user to run gn-agent-kit.

Fixed — uninstall now leaves the repo as it found it

Six leaks, all pre-dating this release. After install; update; uninstall a repo was left holding
.bearing/hooks.json, an empty .claude/ with a {} settings file, empty .claude/skills/ and
.agents/skills/ directories, and a .gitignore it never had. It now comes back byte-identical.

  • An update disowned the file the install created. .bearing/hooks.json is seeded only when
    absent, because it is team-shared config the user edits — but skipping the copy also dropped it
    from the manifest's file list, so uninstall no longer knew the kit had put it there. It is now
    re-claimed on update, and only when a previous manifest says the kit wrote it: a hooks.json
    that existed before the first install was never ours and is still never touched.
  • .claude/settings.json was left as {} once our hooks were stripped out. It now follows
    the rule .mcp.json in the same adapter already used — remove the file when what remains is
    empty, keep it when the user has anything else in it.
  • The skill link directories were never removed, only their contents, and .claude/ was
    missing from uninstall's prune list
    entirely. Both are rmdir-only, so a directory holding
    anything of the user's survives.
  • A .gitignore the kit created was left behind empty. Whether the repo had one is now
    recorded in the manifest at install time, because by the second run the file exists because we
    made it
    — indistinguishable, after the fact, from the user's own (NS-1).
  • Uninstall stripped the final newline from a .gitignore it was otherwise leaving alone,
    which was enough to show the file as modified in the user's diff.

Added — the installer now checks its own claims

Every defect below was found by an agent inspecting a real install by hand. A person running
npx bearing would have been told about none of them — and three would have printed success
while broken. The existing verifier could not have caught a single one: it asks does this file
exist
, and every failure had the right files in the right places with the wrong content.

lib/postcheck.mjs asserts post-conditions against the disk at the end of every install and
update. Each check exists because a real defect shipped through the gap it covers:

check the defect it would have caught
scripts_binary all 16 npm scripts reverted to npx gitnexus@latest after step 7
mcp_entries setup overwrote .cursor/mcp.json; the Zed adapter hardcoded npx
mcp_http_live the repo pointed at a port where the LaunchAgent had died on exit 127
local_state_ignored task-core, session flags and install backups became committable
agent_docs a marker rename appended a second contract block
no_legacy two manifests left side by side, free to disagree
declined_clean .gitnexus/ created in a repo that declined the graph module
files_present a recorded file missing from disk

Three deliberate properties:

  • Not behind --skip-verify. That flag exists to skip the slow index build, and every
    automated path in this repo passes it — which is precisely how these reached a real machine.
    The checks read the disk we just wrote and cost milliseconds.
  • In lib/, not the bundle. scripts/bearing-verify.mjs is owned by the gitnexus feature and
    so is its fallback, so an intel-only install had no verification at all. The configuration least
    exercised by the author was also the least checked.
  • A failure changes the headline and the exit code. The summary reads "finished with N FAILED
    checks" rather than "complete", and the process exits non-zero. Environmental problems (a server
    that is not running) print a fix instead of asking for a bug report.

Recorded as NS-20 and NS-21. The negative-case test immediately earned its keep: the first draft
of scripts_binary compared with String.includes, and "npx gitnexus@latest".includes("gitnexus")
is true — a check that could never fail, which is the exact trap NS-9 describes.

Fixed — npx gitnexus@latest came back after every install

1.0.6 added a recorded gitnexusCmd so a repo could pin the binary it runs. The manifest recorded
it correctly and kit.mjs wrote it correctly — and then step 7 undid all of it, because three
shipped components rebuilt their commands from the bare default. Found by installing into a real
repo and reading what actually landed: manifest said gitnexus, all 16 npm scripts said @latest.

  • scripts/bearing-teaching/merge-package-scripts.mjs is run by bearing-setup.sh after the
    installer has written the scripts, and rebuilt every one of them from npx gitnexus@latest. The
    existing regression test passed --no-setup, so the fixture skipped exactly the step that broke
    it (NS-9). It now asks .bearing/lib/gitnexus-cmd.mjs, and so does --snippet.
  • bearing-setup.sh overwrote .cursor/mcp.json with a hardcoded npx -y gitnexus@latest mcp
    stdio entry, reverting both recorded choices — a repo pointed at a shared http server went
    back to spawning one server per client, recreating the pile-up http exists to prevent. It now
    builds the entry from the new mcpEntryFor() resolver. Its own GITNEXUS_CLI was pinned the
    same way and now resolves too.
  • The Zed adapter never honoured either choice — alone among the three, it hardcoded the npx
    entry. Because Zed project settings win over user settings, that committed entry superseded a
    correctly configured global one: observed on a real machine as Zed running analyze from
    ~/Library/Application Support/Zed/node/cache/_npx against the same index a bearing refresh was
    writing. Zed now writes the resolved binary. It stays on stdio deliberately even for an http
    repo — Zed's remote-context-server shape is unverified, and guessing a schema in someone's real
    editor config risks a server that will not start at all, which is worse than one that works
    locally. The installer now says so in its next steps instead of leaving it to be discovered.
  • .bearing/lib/detect-api-router.mjs spawned npx gitnexus@latest cypher directly, with
    gitnexusSpawn sitting unused beside it — so it queried the published analyzer's graph while
    everything else used the installed one.

Fixed — the CLI silently did nothing through a symlink

lib/kit.mjs decided whether it was the entry point by comparing import.meta.url against
process.argv[1] unresolved. Any symlink on the way in made those differ, so the CLI fell through
and exited 0 having done nothing. On macOS this is the ordinary case, not an exotic one: /tmp
is a link to /private/tmp, so node /tmp/checkout/lib/kit.mjs install … looked like a
successful install that installed nothing. Both sides are now resolved with realpath.

Fixed — the macOS shared MCP server never actually started

The launchd path shipped in 1.0.6 marked UNVERIFIED. Running it on a Mac showed the warning was
right, and in the predicted place.

  • The LaunchAgent exited 127 in a restart loop. An absolute ProgramArguments path is not
    enough: gitnexus is an "env node" shebang script, and launchd starts with a minimal PATH that has
    no version-manager bin dir, so env could not find node. servicePathEnv() now puts the
    binary's own directory first — where nvm, volta, fnm and nodenv all keep the matching node
    and the systemd unit had the identical latent bug.
  • installService reported ok: true, "listening on 127.0.0.1:39100" for that dead agent,
    because it only checked that launchctl bootstrap had loaded the definition. Loading a
    service and running a server are different events. The caller's fallback-to-stdio path was
    therefore unreachable — it would have written an http entry pointing at nothing, which fails
    every graph call. It now confirms the port answers before claiming success.

Verified on macOS 27: agent runs, survives kill -9 via KeepAlive, and is listening again ~1s
later. The Task Scheduler path remains unverified.

Compatibility

  • Every manifest reader consults the old paths. The manifest is an inst...
Read more

1.0.6 — one MCP server for the whole machine, instead of one per client

Choose a tag to compare

@ReidenXerx ReidenXerx released this 08 Aug 15:34

MCP stdio spawns one child process per client, by protocol design. Every editor window and
every agent session therefore gets its own GitNexus server — seven were observed on one machine,
all watching the same index, all auto-refreshing when HEAD moved, and all queueing behind a single
index lock with a 600 s timeout. That contention blocked real work.

GitNexus already ships the fix: gitnexus mcp --http (v1.6.9) is one long-running server that
resolves repositories per request, so a single process serves every repo on the machine.

Added

  • The installer asks how the MCP server should run, rather than deciding for you. The http
    option installs a background service on your machine, and that is not something to arrange
    behind your back. Only asked when the GitNexus module is actually selected.
  • The choice is recorded and re-applied on every update. bearing still always writes the MCP
    entry — that is what keeps it predictable — but it writes what you chose instead of a hardcoded
    default. Non-interactive: --mcp http|stdio|<port>|<url>, or BEARING_MCP.
  • Optional service setup: a systemd user unit on Linux, a LaunchAgent on macOS, a scheduled
    task on Windows. No root anywhere, loopback only, and the removal command is printed with it.

Fixed

  • bearing update silently reverted a hand-configured MCP entry. A repo deliberately pointed
    at a shared server, or at a locally built gitnexus, had that overwritten with
    npx gitnexus@latest at the next update — undoing a whole daemon setup without a word and
    recreating the pile-up it was installed to fix.
  • The README documented two commands that 404. npx bearing-update <repo> makes npx resolve a
    package by that name, which does not exist. The subcommand form (npx bearing update <repo>)
    is the one that works through npx.

Known limitations

  • Only the systemd path has been executed. The launchd and Task Scheduler paths are written
    from documented behaviour and are untested — the file says so, a successful install on those
    platforms says so, and the manual gitnexus mcp --http command is printed either way. The rule
    that broke the first systemd draft (these supervisors do not inherit your shell's PATH, so the
    binary must be named absolutely) is applied and tested on all three.
  • Task Scheduler has no equivalent of Restart=on-failure, so on Windows the server stays down
    until next logon if it dies mid-session.
  • A service definition bearing did not write is never touched, and a failed service install falls
    back to stdio rather than leaving you with a config pointing at a dead port.

1.0.5 — the compound-command notice was silent on the shape it exists for

Choose a tag to compare

@ReidenXerx ReidenXerx released this 08 Aug 15:34

1.0.4 added a notice telling the agent that a blocked shell command was blocked WHOLE, so
python3 edit.py && grep ... could not be read as "the edit landed, only the grep was blocked".
It fired on &&, || and ;.

The incident it was written for has none of those. It was a heredoc followed by a search on
the next line — a python3 heredoc that rewrote several call sites, then a grep beneath it.
Bash rejects the whole line, so the rewrites never ran, and the notice stayed silent because a
newline is not an operator. A newline separates steps exactly as ; does. It now counts, so the
notice covers the shape that actually costs silent edits.

Backslash line-continuations are excluded — foo \ then --bar is one step, and warning there
would report lost work when none was. That exclusion also handles CRLF, where the byte before the
newline is the carriage return rather than the backslash; without it the exclusion failed silently
on Windows.

Over-warning is the deliberate direction: the notice fires only on a deny, where "nothing ran"
is true by construction. A missing notice costs silently-lost work; a redundant one costs a line.

Added

  • lib/classify.test.mjs — behavioural coverage for the shipped guard core, which previously had
    none. Pins the compound-notice shapes and the scoped-grep allow with its paired deny, since an
    allow with no paired deny widens silently into "greps are fine".

1.0.4 — dogfooding, a broken uninstall, two dead gates, and field feedback

Choose a tag to compare

@ReidenXerx ReidenXerx released this 08 Aug 15:34

Installing bearing into its own repo for the first time, then auditing the paths dogfooding
doesn't reach, then acting on a report from an agent using the kit in a live project.
Anyone on 1.0.3 should upgrade — it cannot uninstall.

Fixed — critical

  • uninstall crashed partway through. It threw whenever no backup was recorded (the normal
    case) and, being the first adapter in the cleanup loop, took the rest with it: hooks stayed
    registered against deleted files, the MCP server stayed configured, and the manifest survived so
    the repo still looked installed.
  • uninstall and module-deselection deleted files they had overwritten. Install stashes a
    colliding .githooks/pre-commit or .vscode/settings.json beside itself; removing ours without
    restoring theirs left a hole with the content stranded in a .bearing-backup. Both now put the
    original back.
  • Deselecting a module removed nothing. Re-installing with fewer features left every file,
    hook, MCP entry and npm script in place — so turning GitNexus off left its gates enforcing.
  • The large-source-read gate had never fired once. It referenced an unbound config; the
    ReferenceError was swallowed by its own fail-open catch and reported as "0 lines", so no file
    was ever large enough to gate.

Fixed — the tool blocking legitimate work

  • A stale index was a total lockout. One commit of drift and the agent could not run ls, tail
    a log, read a .csv, or run tests until a full reindex. Staleness now gates only what a stale
    graph would have answered.
  • Glob was inverted. **/* — the broadest sweep there is — was allowed, while src/order.js
    was denied and told to use query with a concept, which cannot find a file you named by path.
  • Scoped searches were denied and sent to a tool that can't answer them: a grep in one named
    file, a search in tests/, and counting occurrences, all redirected to cypher ACCESSES.
  • A blocked shell command implied the rest of it ran. A denied Bash call blocks the whole
    line, but the message named only the offending segment — so python3 edit.py && grep … read as
    "the edit landed". Sequenced commands now say outright that nothing executed.

Added — the kit distrusts the graph, and itself

  • Unreliable impact verdicts are flagged. When the pre-edit gate grades a change risk: LOW
    but resolved no callers — or only test files — the agent is told to treat it as unknown blast
    radius and confirm classically. It warns rather than blocks: re-running impact returns the same
    empty answer.
  • "A graph ZERO is not evidence of absence" is now part of the always-on contract. Positives
    are strong evidence; zeros mean unknown and must be confirmed before any "dead code" call.
  • The kit audits its own enforcement. bearing:scorecard and the session brief report whether
    the gates are earning their keep, and name the evidence that distinguishes "gates misfiring" from
    "gates working on a grep-happy agent".

Fixed — first-run experience

  • npx bearing advertised the wrong product. Six strings still listed three runtimes after
    Codex shipped, including the runtime picker's "All" option — so choosing All looked like it
    excluded Codex.
  • The banner box stopped closing once the greeting grew, and the installer asked "quick or full
    index?" in installs with no indexer.
  • update-all claimed "Index built" for repos with no GitNexus module, and reported a failed
    reindex as the whole update failing.

1.0.3 — 20 defects fixed after an adversarial review

Choose a tag to compare

@ReidenXerx ReidenXerx released this 08 Aug 15:34

Two independent reviewers audited the install/migration core and the runtime hooks, reproducing
every finding end-to-end. Anyone on 1.0.x should upgrade — several of these made the tool
unusable or destructive.

Fixed — critical

  • npx bearing died immediately. The interactive installer — the command the README leads with
    — threw a ReferenceError on its first line.
  • Any install without the gitnexus module crashed, after writing the manifest, leaving a
    half-installed repo.
  • The feature filter applied to FILES only. settings.json still registered every hook,
    package.json still got every script, .mcp.json still wired GitNexus — so a filtered install
    spawned a missing module on every tool call.
  • uninstall deleted your north-stars and hooks.local.json (untracked by the kit's own
    gitignore, so unrecoverable). It now removes only what it installed and reports what it kept.

Fixed — the tool blocking legitimate work

  • Repos living under ~/src or ~/go/src had every file treated as source: every large Read
    denied and every Edit gated, repo-wide. Classification is now repo-relative.
  • Piping into rg/ag/ack was denied — npm run build 2>&1 | rg error blocked, with a Cypher
    query offered as the fix.
  • TODO/FIXME greps were denied as symbols, redirected to a lookup that cannot resolve.
  • A failed refresh locked the session permanently and re-locked it every session; the escape
    hatch existed but was unreachable.
  • A repo with no commits denied ls, cat, Read, Grep and Edit.
  • bearing update tripped its own drift gate, blocking graph queries right after updating.
  • Deny messages now name the two ways out (bearing:fallback, mode: guide).

Fixed — protecting your files

  • Install overwrote .vscode/settings.json and .githooks/pre-commit without a backup, and
    uninstall then deleted them. Pre-existing files are now saved to <file>.bearing-backup.
  • The pre-install backup was re-taken from the already-modified file on every update, so
    uninstall "restored" kit artifacts.
  • Team-tuned .bearing/hooks.json was reverted on every update (e.g. mode: guide → blocking).
  • A .gitignore rule appended with >> was absorbed into the managed block and deleted on the
    next update.
  • update --features was silently ignored; migration failures were rendered as successes.

Fixed — performance and precision

  • The read guard read entire files to compare one line count (398ms / 230MB on 54MB); now a bounded
    scan (104ms → 2ms on 9.4MB).
  • Context-pressure tail cap 8MB → 32MB — past it the estimator reports "unknown", which reads as
    "not full" exactly when the window is filling.
  • git … commit was a substring match, denying git rev-parse HEAD^{commit} and friends.

75 tests, up from 69 — every fix has a test that fails when the fix is reverted.

1.0.2 — proper diagrams

Choose a tag to compare

@ReidenXerx ReidenXerx released this 08 Aug 15:34

Changed

  • README diagrams are now generated SVG (npm run gen:diagrams) rather than ASCII: rendered
    boxes, colour-coded outcomes, readable on light and dark. Served over absolute raw URLs so they
    display on npm and GitHub alike, with no external renderer and no dependency on npm's
    relative-link rewriting. 1.0.1 shipped the ASCII interim — this is the version worth looking at.