chore(release): bump version to 1.5.13 - #758
Conversation
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>
Bugbot couldn't run - usage limit reachedBugbot 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) |
|
Warning Review limit reachedNext included review available in 18 minutes. View limit detailsLimit 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. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Team Run ID: 📒 Files selected for processing (4)
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. Comment |
|
|
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.
BrainLayer ratchetEvery Value below was measured by this run. A row this machine cannot measure says
🟢 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 |
…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>
…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>
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.rbstays 1.5.9 — no Swift change this release. Declared to the gate rather than worked around: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) changedscripts/launchd/**, whichpyproject.tomlforce-includes into the wheel asbrainlayer/launchd/. That is precisely the content the keg ships and precisely what the fix must reach — the renderer deciding which interpreter everycom.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 from64bd6e98.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 upgradedoes 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, withfleet-watchdog-quiescebefore andfleet-watchdog-resumeafter.com.brainlayer.watchstays=> disabled; R3 owns re-enabling it.Gate
Full
scripts/run_tests.shpre-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.12to1.5.13so the next PyPI/MCP keg publish gets a new version string and build stamp instead of reusing1.5.12after packagedscripts/launchdchanges landed in #757.The diff updates the same version in
pyproject.toml,src/brainlayer/__init__.py__version__,server.json(root and PyPI package entry), andbrain-bar/bundle/Info.plist(CFBundleVersion, short version, andBrainLayerReleaseVersion). There is no application or launchd logic change in this PR—only metadata alignment for the release chain (tagv1.5.13is expected post-merge). HomebrewCasks/brainbar.rbis 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.