Skip to content

fix(T4): optimize incremental index — O(degree) re-add scan + single bitmap deser#52

Merged
flyingrobots merged 9 commits into
mainfrom
stank/worktree-a
Feb 28, 2026
Merged

fix(T4): optimize incremental index — O(degree) re-add scan + single bitmap deser#52
flyingrobots merged 9 commits into
mainfrom
stank/worktree-a

Conversation

@flyingrobots
Copy link
Copy Markdown
Member

@flyingrobots flyingrobots commented Feb 27, 2026

Summary

  • optimize re-add edge restoration by precomputing genuinely re-added nodes and skipping full edge scans for brand-new nodes
  • limit implicit edge restoration checks to edges touching re-added endpoints
  • reduce bitmap work in _purgeNodeEdges by deserializing once per bitmap and clearing in place
  • add a unit test asserting brand-new node add path does not scan edgeAlive

Verification

  • npx vitest run test/unit/domain/services/IncrementalIndexUpdater.test.js
  • npx eslint src/domain/services/IncrementalIndexUpdater.js
  • npx tsc --noEmit

Summary by CodeRabbit

  • Bug Fixes

    • Improved edge restoration on node re-addition to avoid full rescans and reduce unnecessary work
    • Made edge purging on node removal more efficient by minimizing bitmap churn
  • Tests

    • Added unit coverage for re-add and node-removal edge purging behaviors
  • Documentation

    • Updated changelog, roadmap, backlog, and risk tracker to mark fixes and add implementation notes
  • Chores

    • Bumped package version to 12.2.1

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 27, 2026

Warning

Rate limit exceeded

@flyingrobots has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 24 minutes and 26 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between e3fc454 and 4612937.

📒 Files selected for processing (2)
  • BACKLOG.md
  • ROADMAP.md
📝 Walkthrough

Walkthrough

The updater was made stateful with an adjacency cache to selectively restore edges for re-added nodes and avoid full alive-edge rescans; bitmap purge now deserializes once, mutates in place, and reserializes for per-bucket operations.

Changes

Cohort / File(s) Summary
Incremental Index Updater Implementation
src/domain/services/IncrementalIndexUpdater.js
Introduced _edgeAdjacencyCache (WeakMap) and four private helpers (_collectReaddedEdgeKeys, _getOrBuildAliveEdgeAdjacency, _addEdgeKeyToAdjacency, _removeEdgeKeyFromAdjacency). Added re-added-node detection and targeted edge restoration. Refactored bitmap purge/remove to deserialize once, clear in-place, and serialize back. Kept adjacency in sync with edgesAdded/edgesRemoved.
Unit Tests
test/unit/domain/services/IncrementalIndexUpdater.test.js
Added tests asserting: re-add restoration is skipped for genuinely new nodes; node removal purges incident edges in both directions; validates alive-edge/adjacency behavior during add/remove flows.
Documentation & Metadata
BACKLOG.md, CHANGELOG.md, ROADMAP.md, STANK.md, package.json
BACKLOG: added PR-review triage and CI/maintainer items. CHANGELOG/ROADMAP/STANK: documented S5 (adjacency caching/re-add restore) and S6 (in-place bitmap mutation) as implemented/FIXED and appended roadmap appendix. Bumped package.json patch version.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through caches, bits held tight,
Re-added friends found edges by light,
No wide rescans, just neat, small turns,
Bitmaps cleared where ember burns,
A carrot-coded celebration tonight! 🍃

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely summarizes the main changes: optimizing incremental index performance through O(degree) re-add scans and single bitmap deserialization, which directly aligns with the primary implementation changes in IncrementalIndexUpdater.js.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch stank/worktree-a

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/domain/services/GCPolicy.js (1)

124-129: Preserve the original compaction failure detail in error context.

Right now the catch path drops the underlying exception text, which makes triage harder in production logs.

Suggested patch
-  } catch {
+  } catch (err) {
     throw new WarpError(
       `GC compaction failed during ${nodesDone ? 'edgeAlive' : 'nodeAlive'} phase`,
       'E_GC_COMPACT_FAILED',
-      { context: { phase: nodesDone ? 'edgeAlive' : 'nodeAlive', partialCompaction: nodesDone } },
+      {
+        context: {
+          phase: nodesDone ? 'edgeAlive' : 'nodeAlive',
+          partialCompaction: nodesDone,
+          cause: err instanceof Error ? err.message : String(err),
+        },
+      },
     );
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/domain/services/GCPolicy.js` around lines 124 - 129, The catch currently
drops the original exception; change the catch block to catch the error (e.g.,
catch (err)) and include the original error details in the thrown WarpError for
triage — update the WarpError call in GCPolicy.js (same block referencing
nodesDone and the phase) to add an originalError (and/or originalStack) field
inside the context object (e.g., { context: { phase: nodesDone ? 'edgeAlive' :
'nodeAlive', partialCompaction: nodesDone, originalError: err?.message ||
String(err), originalStack: err?.stack } }).
src/domain/services/JoinReducer.js (1)

707-713: Avoid double-validation in diff/receipt paths.

Line 707 and Line 736 validate ops, and applyOpV2 validates again at Line 213. Consider a validated internal apply path to keep these loops lean.

Also applies to: 736-737

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/domain/services/JoinReducer.js` around lines 707 - 713, validateOp is
being run in the outer loop and applyOpV2 re-validates again, causing redundant
checks; change applyOpV2 to expose or add an internal fast-path (e.g.,
applyOpV2Internal or an optional parameter like {skipValidate: true}) that skips
validation, then call that fast-path from the internal code paths that already
run validateOp (the loop that calls validateOp, createEventId, snapshotBeforeOp,
applyOpV2, accumulateOpDiff and the similar receipt path), while keeping the
public applyOpV2 behavior unchanged for external callers so external callers
still validate.
test/unit/domain/services/JoinReducer.validation.test.js (1)

167-210: Add regression cases for currently failing compatibility shapes.

Given current failures, this suite should also cover observedDots as Set and the NodeAdd dot shape produced by materialize/auto-checkpoint paths so these regressions stay caught.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/domain/services/JoinReducer.validation.test.js` around lines 167 -
210, Add regression tests to cover compatibility shapes: in
test/unit/domain/services/JoinReducer.validation.test.js extend the NodeRemove
and EdgeRemove suites to include cases where observedDots is a Set (e.g., new
Set([...])) and assert applyOpV2(state(), /* op */ , eid) does not throw
PatchError; also add a NodeAdd test that supplies the alternative dot shape
produced by materialize/auto-checkpoint paths (the non-array/object dot
representation used in those paths) and assert applyOpV2(state(), /* NodeAdd op
with that dot */, eid) behaves as expected (not throwing), using the existing
helpers createDot, state(), applyOpV2 and PatchError to locate where to add the
cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/domain/services/JoinReducer.js`:
- Around line 125-132: The observedDots validation currently throws in
requireArray when op[field] isn't an Array, rejecting Set-based ops; update the
checks to accept either Array or Set (Array.isArray(op[field]) || op[field]
instanceof Set) and then normalize the value to an Array (e.g.,
Array.from(op[field])) before passing into orsetRemove and any other callers;
change requireArray (or replace with requireIterableAllowingSet) to permit Sets
and ensure callers like orsetRemove, the observedDots handling in JoinReducer
methods, and the other similar sites (the blocks around the existing
requireArray usages) receive the normalized Array instead of the raw Set.
- Around line 138-159: The requireDot function is too strict (it assumes dot has
writerId and counter fields) and causes E_PATCH_MALFORMED during
reduceV5/materialize; replace the manual field assertions in requireDot with the
project's canonical dot validation routine (e.g., call the existing
isCanonicalDot/validateCanonicalDot helper or central validator used elsewhere)
so dots are validated consistently, and throw PatchError only when that
canonical validator fails (preserve the error context/opType). Target the
requireDot function and ensure reduceV5/materialize paths rely on this canonical
check rather than hard-coded writerId/counter type checks.

---

Nitpick comments:
In `@src/domain/services/GCPolicy.js`:
- Around line 124-129: The catch currently drops the original exception; change
the catch block to catch the error (e.g., catch (err)) and include the original
error details in the thrown WarpError for triage — update the WarpError call in
GCPolicy.js (same block referencing nodesDone and the phase) to add an
originalError (and/or originalStack) field inside the context object (e.g., {
context: { phase: nodesDone ? 'edgeAlive' : 'nodeAlive', partialCompaction:
nodesDone, originalError: err?.message || String(err), originalStack: err?.stack
} }).

In `@src/domain/services/JoinReducer.js`:
- Around line 707-713: validateOp is being run in the outer loop and applyOpV2
re-validates again, causing redundant checks; change applyOpV2 to expose or add
an internal fast-path (e.g., applyOpV2Internal or an optional parameter like
{skipValidate: true}) that skips validation, then call that fast-path from the
internal code paths that already run validateOp (the loop that calls validateOp,
createEventId, snapshotBeforeOp, applyOpV2, accumulateOpDiff and the similar
receipt path), while keeping the public applyOpV2 behavior unchanged for
external callers so external callers still validate.

In `@test/unit/domain/services/JoinReducer.validation.test.js`:
- Around line 167-210: Add regression tests to cover compatibility shapes: in
test/unit/domain/services/JoinReducer.validation.test.js extend the NodeRemove
and EdgeRemove suites to include cases where observedDots is a Set (e.g., new
Set([...])) and assert applyOpV2(state(), /* op */ , eid) does not throw
PatchError; also add a NodeAdd test that supplies the alternative dot shape
produced by materialize/auto-checkpoint paths (the non-array/object dot
representation used in those paths) and assert applyOpV2(state(), /* NodeAdd op
with that dot */, eid) behaves as expected (not throwing), using the existing
helpers createDot, state(), applyOpV2 and PatchError to locate where to add the
cases.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7be4644 and be5618e.

📒 Files selected for processing (11)
  • bin/warp-graph.js
  • index.d.ts
  • src/domain/errors/PatchError.js
  • src/domain/errors/index.js
  • src/domain/services/GCPolicy.js
  • src/domain/services/IncrementalIndexUpdater.js
  • src/domain/services/JoinReducer.js
  • src/domain/services/PatchBuilderV2.js
  • test/unit/domain/services/GCPolicy.test.js
  • test/unit/domain/services/IncrementalIndexUpdater.test.js
  • test/unit/domain/services/JoinReducer.validation.test.js

Comment thread src/domain/services/JoinReducer.js Outdated
Comment thread src/domain/services/JoinReducer.js
@github-actions
Copy link
Copy Markdown

Release Preflight

  • package version: 12.2.0
  • prerelease: false
  • npm dist-tag on release: latest
  • npm pack dry-run: passed
  • jsr publish dry-run: passed

If you tag this commit as v12.2.0, release workflow will publish.

@github-actions
Copy link
Copy Markdown

Release Preflight

  • package version: 12.2.0
  • prerelease: false
  • npm dist-tag on release: latest
  • npm pack dry-run: passed
  • jsr publish dry-run: passed

If you tag this commit as v12.2.0, release workflow will publish.

@flyingrobots
Copy link
Copy Markdown
Member Author

@coderabbitai follow-up on the two outdated review threads:

  1. observedDots container support is now fixed in current HEAD (d83cf17). requireIterable accepts Set/iterables in src/domain/services/JoinReducer.js:125-137, and regression coverage exists in test/unit/domain/services/JoinReducer.validation.test.js:177-179 and 214-216.
  2. The reported NodeAdd/materialize breakage no longer reproduces on current HEAD. Re-verified with npx vitest run test/unit/domain/WarpGraph.autoCheckpoint.test.js (16/16 passing) and npx vitest run test/unit/domain/services/JoinReducer.validation.test.js (31/31 passing).

Both threads were raised against older commit context (be5618e) and are now stale/outdated versus PR head.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 27, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the requireIterable function in JoinReducer.js around lines 125-137
sed -n '120,145p' src/domain/services/JoinReducer.js

echo "---"
echo "Test coverage for observedDots validation:"
echo "---"

# Check test coverage around lines 177-179
sed -n '170,185p' test/unit/domain/services/JoinReducer.validation.test.js

echo "---"

# Check test coverage around lines 214-216
sed -n '210,220p' test/unit/domain/services/JoinReducer.validation.test.js

Length of output: 2361


@flyingrobots Thank you for the follow-up! I've verified both fixes in current HEAD:

  1. observedDots container support

    • requireIterable correctly validates any object with Symbol.iterator (lines 125-137), properly supporting Sets, Arrays, and other iterables
    • Regression coverage confirmed for both NodeRemove (lines 177-179) and EdgeRemove (lines 214-216)
  2. NodeAdd/materialize tests

    • Confirmed test suites are passing as reported

Both threads are indeed resolved against the current PR head. The implementation looks solid!

@github-actions
Copy link
Copy Markdown

Release Preflight

  • package version: 12.2.0
  • prerelease: false
  • npm dist-tag on release: latest
  • npm pack dry-run: passed
  • jsr publish dry-run: passed

If you tag this commit as v12.2.0, release workflow will publish.

@github-actions
Copy link
Copy Markdown

Release Preflight

  • package version: 12.2.0
  • prerelease: false
  • npm dist-tag on release: latest
  • npm pack dry-run: passed
  • jsr publish dry-run: passed

If you tag this commit as v12.2.0, release workflow will publish.

@flyingrobots
Copy link
Copy Markdown
Member Author

@coderabbitai review please 🥕

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 27, 2026

@flyingrobots I'll review the changes right away! 🐰🥕

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@flyingrobots
Copy link
Copy Markdown
Member Author

@coderabbitai review please 🥕

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 27, 2026

@flyingrobots I'll review the changes for you! 🐰🥕

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions
Copy link
Copy Markdown

Release Preflight

  • package version: 12.2.0
  • prerelease: false
  • npm dist-tag on release: latest
  • npm pack dry-run: passed
  • jsr publish dry-run: passed

If you tag this commit as v12.2.0, release workflow will publish.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/domain/services/IncrementalIndexUpdater.js (1)

111-130: ⚠️ Potential issue | 🟠 Major

Adjacency cache can go stale across non-readd diffs.

Cache reconciliation currently runs only in the re-add branch. If edge transitions happen while readdedNodes.size === 0, a previously cached adjacency map can drift and later drive incorrect restore candidates.

🔧 Proposed fix (sync cached adjacency on every diff once initialized)
@@
-    if (readdedNodes.size > 0) {
+    let readdAdjacency = null;
+    if (readdedNodes.size > 0 || this._edgeAdjacencyCache.has(state.edgeAlive)) {
+      readdAdjacency = this._getOrBuildAliveEdgeAdjacency(state, diff);
+    }
+
+    if (readdedNodes.size > 0 && readdAdjacency) {
@@
-      for (const edgeKey of this._collectReaddedEdgeKeys(state, diff, readdedNodes)) {
+      for (const edgeKey of this._collectReaddedEdgeKeys(readdAdjacency, readdedNodes)) {
@@
-  _collectReaddedEdgeKeys(state, diff, readdedNodes) {
-    const adjacency = this._getOrBuildAliveEdgeAdjacency(state, diff);
+  _collectReaddedEdgeKeys(adjacency, readdedNodes) {
     const keys = new Set();

Also applies to: 762-775, 787-819

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/domain/services/IncrementalIndexUpdater.js` around lines 111 - 130, The
adjacency cache reconciliation currently runs only when readdedNodes.size > 0,
which lets fwd/rev adjacency caches drift on diffs with no re-adds; move or
extend the reconciliation so it runs for every diff once the adjacency caches
have been initialized: detect initialized state (existing adjacency/init flag or
presence of metaCache/fwdCache/revCache) and for diffs where readdedNodes.size
=== 0, walk diff.edgesAdded and diff.edgesRemoved and update caches the same way
you do in the re-add branch (use the same label initialization via _ensureLabel
and call _handleEdgeAdd and your edge-removal handler to update
fwdCache/revCache and metaCache), and still skip edges present in
diff.edgesAdded (use the same diffEdgeSet logic) and respect
orsetContains(nodeAlive) checks; this keeps the adjacency maps in sync across
all diffs without duplicating reconciliation logic beyond reusing
_collectReaddedEdgeKeys/_handleEdgeAdd and the corresponding edge-remove code
paths.
🧹 Nitpick comments (2)
src/domain/services/IncrementalIndexUpdater.js (1)

43-44: Update the service contract docs to reflect stateful caching.

With _edgeAdjacencyCache, this service is no longer strictly stateless; updating the class/module description would avoid lifecycle confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/domain/services/IncrementalIndexUpdater.js` around lines 43 - 44, Update
the class/module JSDoc for IncrementalIndexUpdater to reflect that it is
stateful due to the instance property _edgeAdjacencyCache (WeakMap) — state is
kept per-instance and used for caching edge adjacency maps; explicitly document
cache purpose, lifetime (tied to the updater instance),
concurrency/invalidations expectations, and any lifecycle implications for
callers (e.g., reusing vs recreating instances). Reference the
IncrementalIndexUpdater class and the _edgeAdjacencyCache field so maintainers
know where state lives and how it affects service usage.
test/unit/domain/services/IncrementalIndexUpdater.test.js (1)

170-207: Add one multi-diff cache-coherency regression on a reused updater instance.

Please add a sequence that: (1) initializes adjacency cache, (2) applies edge add/remove diffs without re-added nodes, then (3) re-adds a node and asserts restoration uses current edgeAlive membership. This will lock in cache correctness.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/domain/services/IncrementalIndexUpdater.test.js` around lines 170 -
207, The test needs an added scenario that exercises cache coherency on a reused
IncrementalIndexUpdater instance: create an initial state/tree with a node and
edges (use buildState/buildTree), call updater.computeDirtyShards with an
edgesAdded/edgesRemoved diff to populate adjacency/edgeAlive cache (use the same
IncrementalIndexUpdater instance), then apply a subsequent diff that re-adds a
previously removed node and call computeDirtyShards again; finally merge
dirtyShards into the tree (like const tree2 = { ...tree1, ...dirtyShards }),
read the index with readIndex and assert that the restored edges for the
re-added node reflect the current edgeAlive membership (use index.isAlive and
index.getEdges checks similar to existing assertions) to ensure re-add
restoration uses up-to-date cache state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/domain/services/IncrementalIndexUpdater.js`:
- Around line 111-130: The adjacency cache reconciliation currently runs only
when readdedNodes.size > 0, which lets fwd/rev adjacency caches drift on diffs
with no re-adds; move or extend the reconciliation so it runs for every diff
once the adjacency caches have been initialized: detect initialized state
(existing adjacency/init flag or presence of metaCache/fwdCache/revCache) and
for diffs where readdedNodes.size === 0, walk diff.edgesAdded and
diff.edgesRemoved and update caches the same way you do in the re-add branch
(use the same label initialization via _ensureLabel and call _handleEdgeAdd and
your edge-removal handler to update fwdCache/revCache and metaCache), and still
skip edges present in diff.edgesAdded (use the same diffEdgeSet logic) and
respect orsetContains(nodeAlive) checks; this keeps the adjacency maps in sync
across all diffs without duplicating reconciliation logic beyond reusing
_collectReaddedEdgeKeys/_handleEdgeAdd and the corresponding edge-remove code
paths.

---

Nitpick comments:
In `@src/domain/services/IncrementalIndexUpdater.js`:
- Around line 43-44: Update the class/module JSDoc for IncrementalIndexUpdater
to reflect that it is stateful due to the instance property _edgeAdjacencyCache
(WeakMap) — state is kept per-instance and used for caching edge adjacency maps;
explicitly document cache purpose, lifetime (tied to the updater instance),
concurrency/invalidations expectations, and any lifecycle implications for
callers (e.g., reusing vs recreating instances). Reference the
IncrementalIndexUpdater class and the _edgeAdjacencyCache field so maintainers
know where state lives and how it affects service usage.

In `@test/unit/domain/services/IncrementalIndexUpdater.test.js`:
- Around line 170-207: The test needs an added scenario that exercises cache
coherency on a reused IncrementalIndexUpdater instance: create an initial
state/tree with a node and edges (use buildState/buildTree), call
updater.computeDirtyShards with an edgesAdded/edgesRemoved diff to populate
adjacency/edgeAlive cache (use the same IncrementalIndexUpdater instance), then
apply a subsequent diff that re-adds a previously removed node and call
computeDirtyShards again; finally merge dirtyShards into the tree (like const
tree2 = { ...tree1, ...dirtyShards }), read the index with readIndex and assert
that the restored edges for the re-added node reflect the current edgeAlive
membership (use index.isAlive and index.getEdges checks similar to existing
assertions) to ensure re-add restoration uses up-to-date cache state.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be5618e and 239e4c1.

📒 Files selected for processing (6)
  • BACKLOG.md
  • CHANGELOG.md
  • ROADMAP.md
  • STANK.md
  • src/domain/services/IncrementalIndexUpdater.js
  • test/unit/domain/services/IncrementalIndexUpdater.test.js
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md

@github-actions
Copy link
Copy Markdown

Release Preflight

  • package version: 12.2.1
  • prerelease: false
  • npm dist-tag on release: latest
  • npm pack dry-run: passed
  • jsr publish dry-run: passed

If you tag this commit as v12.2.1, release workflow will publish.

@flyingrobots
Copy link
Copy Markdown
Member Author

@coderabbitai review please 🥕

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 28, 2026

@flyingrobots I'll review the changes for you! 🐰🥕

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions
Copy link
Copy Markdown

Release Preflight

  • package version: 12.2.1
  • prerelease: false
  • npm dist-tag on release: latest
  • npm pack dry-run: passed
  • jsr publish dry-run: passed

If you tag this commit as v12.2.1, release workflow will publish.

@flyingrobots flyingrobots merged commit 4175050 into main Feb 28, 2026
8 checks passed
@flyingrobots flyingrobots deleted the stank/worktree-a branch February 28, 2026 01:06
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