Skip to content

chore(release): bump version to 1.5.13 - #758

Merged
EtanHey merged 1 commit into
mainfrom
wt/release-1.5.13
Sep 4, 2026
Merged

chore(release): bump version to 1.5.13#758
EtanHey merged 1 commit into
mainfrom
wt/release-1.5.13

Conversation

@EtanHey

@EtanHey EtanHey commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Size: XS

Version bump only. Six sites, seven lines: pyproject.toml, src/brainlayer/__init__.py, server.json (root + packages[0]), brain-bar/bundle/Info.plist (short, bundle, release).

Casks/brainbar.rb stays 1.5.9 — no Swift change this release. Declared to the gate rather than worked around:

BRAINLAYER_VERSION_CHECK_CASK_LAG_REASON="no BrainBar release for 1.5.13"
BRAINLAYER_VERSION_CHECK_TAP_ROOT=~/Gits/homebrew-layers
./scripts/brainlayer-version-check.sh
→ WARN: Casks/brainbar.rb is 1.5.9 (package 1.5.13) — allowed: no BrainBar release for 1.5.13
→ ERROR: latest git tag is 'v1.5.12', expected 'v1.5.13'

The tag error is expected pre-merge and is the next step in the chain.

Why a release rather than reusing 1.5.12

#757 (64bd6e98) changed scripts/launchd/**, which pyproject.toml force-includes into the wheel as brainlayer/launchd/. That is precisely the content the keg ships and precisely what the fix must reach — the renderer deciding which interpreter every com.brainlayer.* plist executes, and the hotlane daemon the plist now names inside the keg instead of copying out of it.

Reusing 1.5.12 would put new packaged content under an identical version number: the exact false-green this sprint is closing. The proof is the stamp — the running binary must answer a __build_sha__ descending from 64bd6e98.

What this release does NOT do

Stated up front because the deploy depends on it: shipping 1.5.13 fixes the renderer, not the machines.

brew upgrade does not re-render existing plists. Measured, not assumed — the M4's eight stale jobs are dated Jun 26 ×4, Jul 1, Aug 19 and Sep 2, and 1.5.12 moved none of them. Each Mac therefore needs a second step after the keg upgrade: re-render the affected labels from the keg so the new keg-resolving renderer actually rewrites them, with fleet-watchdog-quiesce before and fleet-watchdog-resume after.

com.brainlayer.watch stays => disabled; R3 owns re-enabling it.

Gate

Full scripts/run_tests.sh pre-push gate passed on push (not the changed-only scope). Signature gate baseline measured today on both Macs at 1.5.12: 442 valid / 0 invalid, unbridged, no hand re-sign — and it must read the same on 1.5.13 before any service is restarted.

🤖 Generated with Claude Code


Note

Low Risk
Metadata-only version string updates with no runtime or security logic changes in the diff.

Overview
Release-only version bump from 1.5.12 to 1.5.13 so the next PyPI/MCP keg publish gets a new version string and build stamp instead of reusing 1.5.12 after packaged scripts/launchd changes landed in #757.

The diff updates the same version in pyproject.toml, src/brainlayer/__init__.py __version__, server.json (root and PyPI package entry), and brain-bar/bundle/Info.plist (CFBundleVersion, short version, and BrainLayerReleaseVersion). There is no application or launchd logic change in this PR—only metadata alignment for the release chain (tag v1.5.13 is expected post-merge). Homebrew Casks/brainbar.rb is intentionally unchanged per the PR notes when there is no BrainBar app release for this version.

Reviewed by Cursor Bugbot for commit 8686b37. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Bump version to 1.5.13

Updates version strings from 1.5.12 to 1.5.13 across bundle metadata, Python packaging, server manifest, and the package version constant in init.py. Importers of brainlayer.__version__ now receive 1.5.13.

Macroscope summarized 8686b37.

All six sites: pyproject.toml, src/brainlayer/__init__.py, server.json
(root + packages[0]), brain-bar/bundle/Info.plist (short, bundle, release).
Casks/brainbar.rb stays 1.5.9: no Swift change this release, declared to
scripts/brainlayer-version-check.sh via
BRAINLAYER_VERSION_CHECK_CASK_LAG_REASON="no BrainBar release for 1.5.13".

Why a release. #757 (64bd6e9) changed `scripts/launchd/**`, which pyproject
force-includes into the wheel as `brainlayer/launchd/`. That content is exactly
what the keg ships and exactly what the fix has to reach: the renderer that
decides which interpreter every com.brainlayer.* plist executes, and the
hotlane daemon the plist now names inside the keg instead of copying out of it.
Reusing 1.5.12 would put new packaged content under an identical version number
— the false-green this sprint exists to close. The running binary must answer a
`__build_sha__` descending from 64bd6e9.

What this release does NOT do, stated because the deploy depends on it. Shipping
1.5.13 fixes the RENDERER, not the machines. `brew upgrade` does not re-render
existing plists — measured: the M4's eight stale jobs are dated Jun 26 x4,
Jul 1, Aug 19 and Sep 2, and 1.5.12 moved none of them. Each Mac therefore needs
a second step after the keg upgrade: re-render the affected labels from the keg,
so the new keg-resolving renderer actually rewrites them.

Co-Authored-By: brainlayerClaude running claude-opus-5 <noreply@anthropic.com>
@cursor

cursor Bot commented Sep 4, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_c2b210d2-fada-4148-ba29-98d9d661f681)

@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 18 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Team

Run ID: 58e9f863-3046-4c16-baeb-eb38f8037a26

📥 Commits

Reviewing files that changed from the base of the PR and between 64bd6e9 and 8686b37.

📒 Files selected for processing (4)
  • brain-bar/bundle/Info.plist
  • pyproject.toml
  • server.json
  • src/brainlayer/__init__.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@deepsource-io

deepsource-io Bot commented Sep 4, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 64bd6e9...8686b37 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
Python Sep 4, 2026 11:42a.m. Review ↗
Swift Sep 4, 2026 11:42a.m. Review ↗
JavaScript Sep 4, 2026 11:42a.m. Review ↗
Shell Sep 4, 2026 11:42a.m. Review ↗
Secrets Sep 4, 2026 11:42a.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

BrainLayer ratchet

Every Value below was measured by this run. A row this machine cannot measure says n/a — <reason> instead of a number; baselines in Notes name their own machine, method and date and were not measured here.

Row Status Value (measured by this run) Method Notes
provenance 🟢 GREEN stamped 178d65538875 == HEAD, tree clean wheel stamp · in-process · runner Sha half of #749 keg-mode provenance: a keg built from this wheel can answer __build_sha__. The helper-age and served-process predicates need a running BrainBar and are measured only by scripts/sprint_gate.py on an installed Mac.
mapped bytes ⚪ n/a n/a — no BrainBar daemon at /tmp/brainbar.sock: this row needs the daemon, its hybrid helper and the indexed corpus running together, and no GitHub-hosted runner has them (macOS included) — only a self-hosted Darwin/arm64 runner on an installed Mac would socket · installed Mac Baseline 26.2 GB — installed Mac, socket, 2026-09-03, after R2 drained 15,070 → 0. Up from 16.8 GB because the drain left more vectors mapped under the same cap: the change is the drain, not a leak. Not measured by this run.
search p50/p95 ⚪ n/a n/a — no BrainBar daemon at /tmp/brainbar.sock: this row needs the daemon, its hybrid helper and the indexed corpus running together, and no GitHub-hosted runner has them (macOS included) — only a self-hosted Darwin/arm64 runner on an installed Mac would socket · installed Mac Baseline p50 911.887 ms / p95 3167.985 ms, captured 2026-09-01T08:42:22Z on MacBook-Pro.local under active_sprint_load (tests/fixtures/sprint_gate/corpus.json). Not measured by this run.
idle CPU ⚪ n/a n/a — no BrainBar daemon at /tmp/brainbar.sock: this row needs the daemon, its hybrid helper and the indexed corpus running together, and no GitHub-hosted runner has them (macOS included) — only a self-hosted Darwin/arm64 runner on an installed Mac would ps sampling · installed Mac Budget: average CPU < 30% over a 60 s window (resource_budget in scripts/sprint_gate.py). Needs the BrainBar daemon, helper and watcher actually running. Not measured by this run.
signature_valid 🟢 GREEN 442 valid / 0 invalid · brainlayer 1.5.12 · macos-15/ARM64 · brew install exited non-zero (outcome: failure); the keg installed and the sweep ran after it codesign · brew keg · GitHub macOS runner scripts/release-verify-signatures.sh <keg> codesign-verifies every *.so/*.dylib under libexec/venv. The macOS parity job installs the published tap formula (etanhey/layers/brainlayer), so this row measures the release path — formula, published sdist and Homebrew's relocation — and not this PR's tree. Release-time baseline for the same keg on a different machine: 442 valid / 0 invalid — installed Mac (M4 Max), brew --prefix brainlayer 1.5.11, 2026-09-03.

🟢 GREEN measured, within budget · 🔴 RED measured, out of budget — a finding to clear before merge · ⚪ n/a not measurable on this machine, never guessed.

No RED rows.

Measured on Linux/x86_64 · checked-out HEAD 178d65538875 · run · updated 2026-09-04 11:50:29 UTC

@EtanHey
EtanHey merged commit 3126ac1 into main Sep 4, 2026
18 checks passed
EtanHey added a commit that referenced this pull request Sep 4, 2026
…faster doing it

DeepSource: Python is failure on this branch while green on the base commit and
on #755-#758, so the regression is mine. Its findings are reported as "outside of
the diff" and the check-run API returns no annotations, so after my complexity
hypothesis failed to clear it (2b8d5b0 took poll_once to cx 35, three BELOW the
pre-PR baseline of 38) I stopped guessing and measured instead.

Diffing a full `ruff --select ALL` sweep of watcher.py against origin/main
isolates exactly one new rule occurrence in the entire file:

    PTH120  base=0  head=1   os.path.dirname() should be Path.parent

introduced by the parent-dir change-detector in _maybe_prune_offsets.

Complying with it literally would have been a real regression. Measured over
12,000 paths, which is one poll's worth:

    str.rpartition     0.8 ms
    os.path.dirname    2.4 ms
    Path(p).parent    24.9 ms

Path.parent is 10x slower than what was there and would have added ~22ms to
every poll in the loop this PR exists to speed up. str.rpartition satisfies the
rule's intent, is 3x faster than os.path.dirname, and is a pure string operation
on paths that come straight from glob.

watcher.py now introduces zero new lint findings against base: 74 = 74, no rule
code changed in either direction.

Co-Authored-By: brainlayerClaude-e358a78e running claude-opus-5 <noreply@anthropic.com>
EtanHey added a commit that referenced this pull request Sep 4, 2026
…ive the watcher a heartbeat (#759)

* fix(watcher): stop the idle burn, and give the watcher a heartbeat at last

The JSONL watcher was disabled after burning ~100% of one core at idle. This
fixes the two defects actually responsible, plus the instrumentation gap that
made them invisible for 107 days.

The burn was NOT the poll interval. `poll_once` gated `prune_missing_files` on
`_offset_prune_complete`, which requires `registry.last_prune_complete` — and
that is permanently False whenever the registry holds entries whose parent
directory no longer exists (8,744 of 21,529 on the dev machine), because
`_has_live_parent_evidence` correctly refuses to prune them. The flag never
flipped, so a 10-12s full-registry filesystem scan ran on EVERY poll forever,
pruning nothing after the first pass:

    prune run 1: 12.28s  pruned=30  last_prune_complete=False
    prune run 2: 10.36s  pruned= 0  last_prune_complete=False
    prune run 3: 10.17s  pruned= 0  last_prune_complete=False

The guard is right; retrying it every poll was not. It now retries when the set
of parent directories among discovered files changes (new evidence can unblock a
previously unmountable root) and otherwise backs off on a timer. A pre-existing
test, test_poll_retries_pruning_after_unavailable_startup_root, caught an earlier
flat-timer version of this change as a regression; the change was fixed, not the
test, and it passes unmodified.

Second cost: `is_denylisted` was evaluated 4x per file per poll at 0.82s per
warm sweep over 12,796 files. Now memoised for one poll and cleared each cycle,
so a changed BRAINLAYER_INGEST_DENYLIST is still picked up promptly.

Measured, steady state against the real corpus:
    before                18.0s/poll
    after prune fix        5.0s/poll
    after denylist memo    2.4s/poll   (7.4x)

`brainlayer watch` was also the one long-running daemon command that never
called logging.basicConfig, so every logger.info in watcher.py — including the
60-second liveness heartbeat and the started/stopped markers — was discarded by
logging's lastResort handler. watch.err.log contained zero heartbeats across 107
days. Adding it (with timestamps) is what makes the numbers above observable at
all, and it is why 1,032 banner-only runs could never be classified as healthy
or dead from the logs.

Also: an (mtime,size) skip gate so an unchanged file costs nothing; the offset
lag probe reuses discovery's stats instead of re-statting the corpus; poll
default 1.0s -> 30.0s; and AGENTS.md now points at the real watcher log paths,
which moved in 3dca26a (2026-05-18) and had been wrong ever since.

NOT a re-enable. The idle-CPU soak gate still FAILS on this code (6.41% against
a <5% gate) and com.brainlayer.watch stays `=> disabled`. These fixes are worth
landing on their own; clearing the gate needs event-driven discovery (FSEvents),
which is a separate change.

290 tests pass; ruff check and ruff format --check clean.

Co-Authored-By: brainlayerClaude-e358a78e running claude-opus-5 <noreply@anthropic.com>

* refactor(watcher): cut the complexity this PR added, below the pre-PR baseline

DeepSource: Python went from success on the last five main commits to failure on
this branch ("Blocking issues or failing metrics found"), so the regression is
mine. Its findings are reported as "outside of the diff" and not shown inline,
so I measured what my diff actually changed instead of guessing at the rule:

    _can_skip_unchanged   new, cx=9, 7 return statements
    poll_once             cx 38 -> 43  (+5)

Seven returns crosses the usual too-many-returns threshold, and poll_once was
already the largest function in the file before I added to it. Both are worth
fixing on their own merits regardless of which one DeepSource flagged.

_can_skip_unchanged now states its guards as one short-circuiting expression:
7 returns -> 2, cx 9 -> 8. Evaluation order is unchanged -- `tailer is not None`
still guards the attribute access that follows it, exactly as the sequential
early-returns did.

The prune retry moves into _maybe_prune_offsets(), which also gives that logic a
docstring of its own explaining why both halves (change-detector and timer) are
load-bearing. poll_once drops to cx 35 -- three BELOW the pre-PR baseline of 38.

Pure refactor: no behaviour change. 191 tests pass, and re-profiling against the
real corpus shows the same steady-state cost (~2.6s/poll, 12,177 of 12,191 files
skippable).

Co-Authored-By: brainlayerClaude-e358a78e running claude-opus-5 <noreply@anthropic.com>

* fix(watcher): close three fail-open holes in the new skip gate

All three are the same shape as the bug this PR set out to fix -- a condition
that can never flip -- found by the Cursor pass, Macroscope, and confirmed
in-tree before fixing. Each test below fails on 6f9c073 and passes here.

HIGH, data loss: the skip fingerprint was (mtime, size) and omitted the inode,
while discovery already held the stat_result carrying st_ino. Rotation is only
detected inside _ensure_tailer/read_new_lines, which a skip `continue`s past --
so a file replaced at the same path with the same size AND the same mtime was
skipped on every poll, forever, and its new content was never ingested. Silent:
no error, no log, no alarm. The pre-existing replacement tests all change size,
so none of them covered it.

The fingerprint is now (mtime, size, inode) and the gate additionally refuses
when tailer.observed_inode disagrees with the inode discovery just saw.

MEDIUM, bypasses rewind handling: `tailer.offset >= size` also accepted
`offset > size` -- a tailer that believes it read more bytes than the file holds,
which is the signature of a truncation. Skipping there bypasses check_rewind,
the checkpoint-restore path AGENTS.md documents as soft-archiving reverted
chunks. Only `offset == size` is a provably safe skip.

MEDIUM, disables the retry timer: float("nan") and float("inf") survive both the
ValueError guard and `<= 0`, and `monotonic() - attempt >= nan/inf` is never
true -- so an incomplete prune would retry only on parent-dir changes, never on
the timer. Now gated on math.isfinite.

Also strengthens the poll-interval test. Asserting the constructor default is
the wrong surface: the plists pass --poll explicitly, so that test would still
pass if a plist said `--poll 1` -- the exact configuration this PR removes. It
now asserts the CLI option default and the --poll value in BOTH plists.

206 tests pass; ruff check and ruff format --check clean.

Co-Authored-By: brainlayerClaude-e358a78e running claude-opus-5 <noreply@anthropic.com>

* perf(watcher): drop the one lint finding this PR introduced, and get faster doing it

DeepSource: Python is failure on this branch while green on the base commit and
on #755-#758, so the regression is mine. Its findings are reported as "outside of
the diff" and the check-run API returns no annotations, so after my complexity
hypothesis failed to clear it (2b8d5b0 took poll_once to cx 35, three BELOW the
pre-PR baseline of 38) I stopped guessing and measured instead.

Diffing a full `ruff --select ALL` sweep of watcher.py against origin/main
isolates exactly one new rule occurrence in the entire file:

    PTH120  base=0  head=1   os.path.dirname() should be Path.parent

introduced by the parent-dir change-detector in _maybe_prune_offsets.

Complying with it literally would have been a real regression. Measured over
12,000 paths, which is one poll's worth:

    str.rpartition     0.8 ms
    os.path.dirname    2.4 ms
    Path(p).parent    24.9 ms

Path.parent is 10x slower than what was there and would have added ~22ms to
every poll in the loop this PR exists to speed up. str.rpartition satisfies the
rule's intent, is 3x faster than os.path.dirname, and is a pure string operation
on paths that come straight from glob.

watcher.py now introduces zero new lint findings against base: 74 = 74, no rule
code changed in either direction.

Co-Authored-By: brainlayerClaude-e358a78e running claude-opus-5 <noreply@anthropic.com>

* fix(watcher): enforce the >=30s poll floor where it can actually be violated

The R3 batching constraint was asserted three ways against values that cannot
change behind the code's back -- the CLI option default and the `--poll` argument
in both repo plists -- and against nothing at the one boundary a value actually
arrives through. `cli/__init__.py` took `poll_interval: float` with help text
saying ">=30s batching", `watcher.py` stored it verbatim, and `start()` waited on
it. The constraint was enforced where it cannot be violated and unenforced where
it can.

Not theoretical: the installed ~/Library/LaunchAgents/com.brainlayer.watch.plist
still passes `--poll 1.0` (installed Sep 1, unchanged by this PR, verified by
PlistBuddy on this machine). Re-enabling that label without re-running
`scripts/launchd/install.sh watch` hands the poll loop the exact configuration R3
exists to remove.

`enforce_min_poll_interval()` clamps to the floor and logs at WARNING. Clamp
rather than exit, because `watch` runs under launchd KeepAlive: refusing a stale
plist trades a CPU burn for a total ingestion outage. isfinite before the bounds
test -- `float("nan") < 30.0` is False, so nan sails past a bare bounds check into
`Event.wait(nan)`, and `inf` parks the loop forever. Same fail-open shape fixed for
the prune retry timer in 8903e3c.

Not clamped in `JSONLWatcher.__init__`: the in-process tests drive `poll_once`
through a real loop at 0.01-0.05s, so a constructor floor would stall the suite
rather than the burn.

Tests cover both directions and, critically, the wiring -- a validator that exists
but is never called is the same defect one level down. Verified against 26149d7
by source swap: `test_watch_command_clamps_the_installed_plists_poll_argument`
fails there with `poll_interval_s=1.0`.

Also fixes one AGENTS.md line (F4, advisory): the heartbeat is checked once per
poll iteration, so at the 30s default the real spacing is ~60-95s, not the
documented 60s. Do not alert on a 60s cadence.

Closes the CodeRabbit finding at cli/__init__.py:3375.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: brainlayerClaude-e358a78e running claude-opus-5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant