1.0.7 — a domain expert on every review, and an installer that checks its own claims
The kit was renamed gitnexus-agent-kit → bearing, 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 fourgitnexusentries in
the managed.gitignoreblock are gated on the module too..bearing/.gitnexus-*stays
ungated: the core session-primer writes it, and.gitnexus-northstar-counter.jsonbelongs
to the north-stars module rather than the graph. - The managed
AGENTS.md/CLAUDE.mdblock 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 failureGITIGNORE_MARKERS_LEGACYalready 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 tobearing-uninstall(NS-6). - Renamed the remaining user-visible strings: the
bearing verificationbanner, the
bearing installedhealth detail, and a sync script that told the user to rungn-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.jsonis 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.jsonwas left as{}once our hooks were stripped out. It now follows
the rule.mcp.jsonin 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
.gitignorethe 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
.gitignoreit 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.mjsis 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.mjsis run bybearing-setup.shafter the
installer has written the scripts, and rebuilt every one of them fromnpx 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.shoverwrote.cursor/mcp.jsonwith a hardcodednpx -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 newmcpEntryFor()resolver. Its ownGITNEXUS_CLIwas 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 runninganalyzefrom
~/Library/Application Support/Zed/node/cache/_npxagainst 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.mjsspawnednpx gitnexus@latest cypherdirectly, with
gitnexusSpawnsitting 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
ProgramArgumentspath is not
enough: gitnexus is an "env node" shebang script, and launchd starts with a minimal PATH that has
no version-manager bin dir, soenvcould not findnode.servicePathEnv()now puts the
binary's own directory first — where nvm, volta, fnm and nodenv all keep the matchingnode—
and the systemd unit had the identical latent bug. installServicereportedok: true, "listening on 127.0.0.1:39100"for that dead agent,
because it only checked thatlaunchctl bootstraphad 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 install's identity —
update,uninstallandupdate-alldiscovery all key off it, so a reader that knew only the
new path would report an installed repo as never installed andupdate-allwould silently stop
seeing every repo installed before this release. Install and update move the file and prune the
emptied.gitnexus/(rmdir only, so a real index is never touched). - The
gitnexus:*npm script aliases are unchanged (NS-15), and so is the Zedzed-gitnexus
profile — that one names the actual GitNexus MCP server, not the kit's old name.
Added — a domain persona, resolved once and used everywhere
The headline claim — a trading repo reviewed by a quant trader, a payments repo by a ledger
engineer — was implemented in exactly ONE skill. .bearing/domain.json appeared in the README and
in bearing-microscope and nowhere else: nothing created it, nothing seeded it, and no other skill,
contract or session brief read it. Every review skill except microscope worked as a generic
engineer, and microscope re-inferred the domain on each wave, so wave 2 could adopt a different
persona than wave 1 while folding in wave 1's findings.
- Resolved once at install, written to
.bearing/domain.json, and substituted into the
always-on contract every runtime reads. Inference reads only what the repo says about ITSELF —
package metadata, README,CLAUDE.md— and weightspackage.jsonabove prose. - A near-miss is recorded as
suggestedDomainrather than adopted. A wrong specialism biases
every downstream judgement; "senior engineer" biases nothing. Equal weighting classified bearing
itself as a trading repo, purely because its README explains a feature using a trading example. - The file is yours. Edit it and the contract follows; update never overwrites it.
Changed — CI reports instead of gating
The merge gate failed PRs when a high-blast-radius symbol changed without tests, and that is the
wrong shape for this signal: the graph cannot distinguish "nothing calls this" from "I could not
resolve the callers", so a hard block on it fails honest PRs until people learn [skip ci]. It now
posts a sticky PR comment, a job summary and inline annotations — blast radius per changed symbol,
affected flows, security-sensitive paths, import cycles — and never fails the build.
GITNEXUS_CI_MODE=block is opt-in.
Fixed — the placeholder check missed the placeholder it was written for
checkPersonaResolved looked only for __BEARING_PERSONA__. The bug it shipped alongside was
__GITNEXUS_REPO__ reaching the Cursor rule verbatim, so the regression guard could not have caught
the regression — it covered the right FILES and the wrong TOKEN. Both are now read from the exported
constants, so substitution and verification share one source, and the failure names which token in
which file.
Changed — the README and diagrams
- 3374 words to 1335. The old page explained the product to someone already convinced. Hook,
four modules and install now sit above the fold, one section per module led by its diagram, and
the deep material stays indocs/. - The diagrams draw one claim each. The microscope one had twelve boxes at 11px, which reads as
"this is complicated" — and it understated the feature by drawing two lenses, when the skill
spawns one lens agent per slice, in parallel where the runtime allows, each carrying the same
pinned persona. - Rasterising caught three defects invisible in the source: arrows missing a
y2rendered
undefined, sub-labels overlapped the card beneath them, and a backward connector routed straight
through the boxes it was meant to pass under. The generator now fails on text that would
overflow the canvas, since SVG clips silently.