Skip to content

feat: git warp seek — time-travel through graph history#21

Merged
flyingrobots merged 13 commits intomainfrom
seek
Feb 10, 2026
Merged

feat: git warp seek — time-travel through graph history#21
flyingrobots merged 13 commits intomainfrom
seek

Conversation

@flyingrobots
Copy link
Member

@flyingrobots flyingrobots commented Feb 9, 2026

Summary

  • git warp seek: Cursor-based time-travel that navigates to any Lamport tick, saves/loads named bookmarks, and materializes state at any point in history. Existing commands (query, info, materialize, history) respect the active cursor.
  • ASCII seek dashboard (--view seek): Swimlane timeline showing per-writer patches, included/excluded zones, op receipts, and graph stats at the selected tick.
  • Arrowhead fix: ASCII graph renderer now places /// arrowheads correctly when ELK endpoints land inside node bounding boxes.
  • Seek demo GIF (docs/seek-demo.gif): Animated walkthrough showing graph topology changing at each tick while git status proves the worktree is untouched.
  • ROADMAP backlog: Two future ideas parked — structural seek diff and git-cas materialization cache.

Test plan

  • 2867 unit tests pass (Docker, Node 22)
  • 8 BATS CLI tests pass (Docker)
  • ESLint clean
  • Benchmarks pass
  • no-coordination regression suite passes
  • CI (GitHub Actions) — lint, test-node, test-bun, test-deno

Summary by CodeRabbit

  • New Features

    • Time-travel "seek" CLI: jump/step/latest, save/load/list/drop bookmarks, cursor-aware query/info/history/materialize, and an ASCII timeline dashboard; demo GIF and demo script added.
  • Bug Fixes

    • Improved ASCII arrowhead placement, safer cursor parsing/validation, correct tick handling (including duplicate tick 0), and ceiling/cache fixes for seeks.
  • Documentation

    • README, GUIDE, ROADMAP and changelog updated with seek usage and examples.
  • Tests

    • New CLI and unit tests covering seek, rendering, tick discovery, and materialization.
  • Chores

    • Version bumped to 10.3.1.

Introduces cursor-based time travel for exploring graph history:
- `seek --tick N` positions cursor at a specific Lamport tick
- `seek --save NAME` / `--load NAME` for named bookmarks
- `seek --latest` returns to the present
- `discoverTicks()` walks history without expensive reads
- `materialize()` accepts `ceiling` param for partial replay
- `info`, `materialize`, and `history` commands respect active cursor
- ASCII seek renderer with timeline, writer activity, and graph stats
Updates CHANGELOG, README, GUIDE, and package.json for the new
`git warp seek` time-travel command added in the previous commit.
@coderabbitai
Copy link

coderabbitai bot commented Feb 9, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a cursor-based time-travel "seek" feature: CLI commands and wiring, persistent cursor refs and parsing, ceiling-aware materialization and tick discovery in WarpGraph, ASCII seek renderer and op-summary extraction, docs/demo/changelog updates, and extensive unit and CLI tests.

Changes

Cohort / File(s) Summary
Changelog & Docs
CHANGELOG.md, README.md, docs/GUIDE.md, ROADMAP.md, docs/seek-demo.tape, package.json
New 10.3.1 changelog; README/GUIDE additions documenting seek/time-travel CLI and view; demo tape and roadmap backlog entries; package version bump.
CLI & Command Wiring
bin/warp-graph.js
Adds seek CLI command and sub-actions, seek arg parsing/resolution, cursor I/O helpers (read/write/list/save/load/drop), threads cursor/ceiling through payloads, integrates renderSeekView, updates help and view support.
Domain: WarpGraph (time-travel)
src/domain/WarpGraph.js, src/domain/utils/parseCursorBlob.js, src/domain/utils/RefLayout.js
Introduces _seekCeiling/_cachedCeiling/_cachedFrontier, ceiling-aware materialization path (_materializeWithCeiling), _resolveCeiling, discoverTicks(), loadPatchBySha(), cursor ref builders, and parseCursorBlob for cursor validation.
ASCII Renderers & OpSummary
src/visualization/renderers/ascii/seek.js, src/visualization/renderers/ascii/opSummary.js, src/visualization/renderers/ascii/history.js, src/visualization/renderers/ascii/index.js, src/visualization/renderers/ascii/graph.js
Adds renderSeekView (timeline + per-writer lanes + receipts/stats); extracts op-summary utilities (summarizeOps, formatOpSummary, OP_DISPLAY, EMPTY_OP_SUMMARY) and re-exports them; fixes arrowhead placement when edge endpoints intersect node boxes.
Tests: CLI & Unit
test/bats/cli-seek.bats, test/unit/domain/WarpGraph.seek.test.js, test/unit/visualization/ascii-seek-renderer.test.js, test/unit/visualization/ascii-op-summary.test.js, test/unit/domain/parseCursorBlob.test.js
Adds BATS CLI tests for seek flows and unit tests covering tick discovery, ceiling materialization/caching, ASCII seek renderer, op-summary utilities, and cursor parsing.
Misc / Hooks & Demo
scripts/hooks/pre-push
Expands pre-push test scope to run bats test directory.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as bin/warp-graph.js
    participant WarpGraph as src/domain/WarpGraph.js
    participant Persistence
    participant Renderer as ascii/seek.js

    User->>CLI: git warp seek --tick 5
    CLI->>CLI: parseSeekArgs(), resolveTickValue()
    CLI->>Persistence: readActiveCursor (refs/warp/.../cursor/active)
    Persistence-->>CLI: cursor blob
    CLI->>WarpGraph: discoverTicks()
    WarpGraph->>Persistence: read writer patch chains
    Persistence-->>WarpGraph: patches with lamport ticks
    WarpGraph-->>CLI: ticks, perWriter, maxTick
    CLI->>WarpGraph: materialize({ ceiling:5 })
    WarpGraph->>WarpGraph: _materializeWithCeiling(5)
    WarpGraph->>Persistence: replay patches up to ceiling
    Persistence-->>WarpGraph: materialized state
    WarpGraph-->>CLI: materialized payload + cursor metadata
    CLI->>Persistence: writeActiveCursor({ tick:5 }) / save/load as needed
    Persistence-->>CLI: cursor persisted / listed / dropped
    CLI->>Renderer: renderSeekView(payload)
    Renderer-->>CLI: ASCII dashboard
    CLI-->>User: rendered seek dashboard
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 Hopping through ticks with a twitchy nose,

I bookmark moments where the timeline glows.
Rewind a patch, then step back to play—
Patches and lanes in a neat display.
A rabbit's cheer for seek: hip-hop hooray!

🚥 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 title directly and clearly summarizes the main feature of the pull request: a new time-travel capability for graphs using the seek command.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% 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 seek

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.

@github-actions
Copy link

github-actions bot commented Feb 9, 2026

Release Preflight

  • package version: 10.3.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 v10.3.0, release workflow will publish.

Copy link

@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: 6

Caution

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

⚠️ Outside diff range comments (1)
docs/GUIDE.md (1)

1009-1016: ⚠️ Potential issue | 🟡 Minor

The --view seek command is missing from the "Supported commands" table.

It's shown in the example block at line 907 (git warp --view seek) but not listed in the table here. Adding it keeps the reference consistent.

Suggested addition
 | `--view materialize` | Progress dashboard with statistics |
+| `--view seek` | Time-travel dashboard with timeline |
🤖 Fix all issues with AI agents
In `@bin/warp-graph.js`:
- Around line 1606-1667: readActiveCursor, readSavedCursor and listSavedCursors
assume blobs are valid JSON with a numeric tick and will crash or produce
invalid _seekCeiling on corrupted data; add a shared parser/validator (e.g.,
parseCursorBlob) that takes the blob, tries JSON.parse inside a try/catch,
verifies the resulting object has a numeric tick (and any other required
fields), and either returns the validated object or throws a friendly E_USAGE
error (or returns null) as appropriate; call this parser from readActiveCursor
and readSavedCursor after readBlob, and from listSavedCursors before pushing
each cursor so corrupted entries are handled gracefully.

In `@CHANGELOG.md`:
- Line 33: Update the CHANGELOG entry so the helper names match the exported
API: replace the listed buildCursorRef(), buildSavedCursorRef(),
buildSavedCursorPrefix() with the actual exported names buildCursorActiveRef(),
buildCursorSavedRef(), and buildCursorSavedPrefix() in the RefLayout line so
searching the changelog matches the implementation.

In `@src/domain/WarpGraph.js`:
- Around line 741-748: The fast-path in _materializeWithCeiling incorrectly
returns receipts: [] on a cache hit, so when collectReceipts is true bypass the
cached-state fast path: in _materializeWithCeiling check collectReceipts before
returning this._cachedState/receipts and fall through to the full
materialization logic (or alternatively store/return cached receipts alongside
this._cachedState and this._cachedCeiling); update the branch that tests
this._cachedState && !this._stateDirty && ceiling === this._cachedCeiling to
avoid returning an empty receipts array when collectReceipts is truthy.
- Around line 721-726: The _resolveCeiling method currently ignores an explicit
null because it checks for options.ceiling !== undefined && options.ceiling !==
null; change the check to detect whether the caller provided a ceiling property
(even if null) — e.g. use Object.prototype.hasOwnProperty.call(options,
'ceiling') or ("ceiling" in options) — and return options.ceiling when the
property is present; otherwise fall back to this._seekCeiling. This ensures
callers can override/clear _seekCeiling by passing ceiling: null.

In `@src/visualization/renderers/ascii/seek.js`:
- Around line 25-26: The current construction of allPoints in
src/visualization/renderers/ascii/seek.js unconditionally prepends 0 which can
create a duplicate if ticks already contains 0; change the logic that creates
allPoints (the allPoints variable derived from ticks) to only prepend 0 when
ticks does not already include 0 (e.g., check ticks && ticks.includes(0) or
ticks[0] === 0) so that allPoints becomes either [0, ...ticks] or [...ticks]
accordingly; update any downstream assumptions about allPoints length if needed.
- Around line 20-69: The labels drift because labels only reserve segLen spaces
for the segment but not the dot character itself, so multi-char tick labels
shift following labels; in buildSeekTimeline (variables displayPoints, segLen,
timeline, labels) ensure labels reserve space for the dot before appending the
tick string (e.g., add one extra space per dot by using ' '.repeat(segLen + 1)
when i > 0 and likewise ensure the first tick label accounts for the initial
dot) so that the labels string and timeline string grow in lockstep and
multi-digit ticks stay vertically aligned under their dots.
🧹 Nitpick comments (2)
src/domain/utils/RefLayout.js (1)

1-13: Module docstring doesn't document the new cursor/* ref paths.

The header lists the ref layout but omits the newly added cursor/active and cursor/saved/<name> paths. Keeping it in sync helps future contributors discover the full ref namespace at a glance.

Suggested addition
  * Ref layout:
  * - refs/warp/<graph>/writers/<writer_id>
  * - refs/warp/<graph>/checkpoints/head
  * - refs/warp/<graph>/coverage/head
+ * - refs/warp/<graph>/cursor/active
+ * - refs/warp/<graph>/cursor/saved/<name>
test/bats/cli-seek.bats (1)

127-139: --tick -1 may be fragile with some CLI argument parsers.

Negative numbers as option values are a common source of parser ambiguity (e.g., yargs/commander may interpret -1 as a flag). Since these tests are passing, the parser handles it today — but consider using --tick=-1 syntax for robustness against future parser changes or library upgrades.

…G names

- Add parseCursorBlob() utility with JSON + numeric tick validation;
  replace raw JSON.parse in readActiveCursor, readSavedCursor, and
  listSavedCursors with descriptive errors on corrupted data.
- Add --view seek to GUIDE.md supported commands table.
- Fix CHANGELOG RefLayout helper names to match exported API.
- 11 new unit tests for parseCursorBlob.
…ignment

- _materializeWithCeiling: bypass cache when collectReceipts is true
  so callers get real receipts instead of empty array.
- _resolveCeiling: use 'ceiling' in options so explicit null clears
  _seekCeiling and materializes latest state.
- buildSeekTimeline: skip prepending 0 when ticks already contains it;
  compute target column positions for labels so multi-digit ticks stay
  aligned under their dots.
- RefLayout.js: add cursor/* paths to module docstring.
- 4 new regression tests (2 domain, 2 renderer).
Avoids parser ambiguity where -1 or +1 could be misinterpreted as
flags by future CLI parser upgrades.
@github-actions
Copy link

github-actions bot commented Feb 9, 2026

Release Preflight

  • package version: 10.3.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 v10.3.0, release workflow will publish.

Copy link

@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: 4

🤖 Fix all issues with AI agents
In `@bin/warp-graph.js`:
- Around line 1950-1964: The no-cursor status branch currently returns hardcoded
nodes:0, edges:0, patchCount:0; instead, materialize the graph at maxTick like
the active-cursor path to compute real counts. In the branch that builds the
payload (where cursor: { active: false }) call the same
materialization/inspection logic used in the active-cursor status path to obtain
node/edge counts and patchCount, then populate nodes, edges, and patchCount
accordingly before returning the payload (keep perWriter:
serializePerWriter(perWriter), tick/maxTick/ticks and exitCode as-is). Ensure
you reuse the existing materialization routine rather than duplicating heavy
logic.
- Around line 1311-1315: The code mutates the internal field graph._seekCeiling
(seen in materializeOneGraph and handleSeek) instead of using the public API;
change these sites to call the public materialize method with the ceiling option
(e.g., after opening the graph with WarpGraph.open(...) in materializeOneGraph
and inside handleSeek, replace assignments to graph._seekCeiling with await
graph.materialize({ ceiling }) so the ceiling is passed as an option rather than
setting the private field).

In `@docs/GUIDE.md`:
- Around line 916-937: Docs show examples of signed tick arguments using `--tick
+1`/`--tick -1` which can be ambiguous to the CLI parser; update the examples
for the `git warp seek` command (and any related examples in GUIDE.md) to use
the `--tick=` form for signed values (e.g., `--tick=+1` and `--tick=-1`) and
mention that signed values should use the `=` form to match the BATS tests and
avoid parser ambiguity.

In `@src/visualization/renderers/ascii/seek.js`:
- Around line 26-37: When sampling points into displayPoints (using allPoints,
MAX_TIMELINE_WIDTH and step), ensure currentTick is always present: after
building displayPoints (first, last, and interior sampled indices), check if any
point.index or tick equals currentTick; if not, find the closest sampled index
in displayPoints to currentTick and replace that element with the actual
allPoints entry for currentTick (or nearest exact match in allPoints) so the
active dot/pointer logic referencing currentTick will always render; adjust
selection logic around displayPoints, step and Math.round to use the
replacement.
🧹 Nitpick comments (7)
test/unit/visualization/ascii-seek-renderer.test.js (2)

153-176: Guard clause may let the test pass vacuously if timeline rendering changes.

The if (timelineLine) guard on line 172 means that if the timeline line is not found (e.g., due to a rendering change), the test silently passes without asserting anything. Consider failing explicitly if no timeline line is found.

Suggested fix
     const timelineLine = lines.find((l) => /[●○\u25CB\u25CF]/.test(l));
-    if (timelineLine) {
-      const dotCount = (timelineLine.match(/[●○\u25CB\u25CF]/g) || []).length;
-      expect(dotCount).toBe(3);
-    }
+    expect(timelineLine).toBeDefined();
+    const dotCount = (timelineLine.match(/[●○\u25CB\u25CF]/g) || []).length;
+    expect(dotCount).toBe(3);

196-216: Same vacuous-pass pattern here — consider asserting timelineIdx >= 0.

-    if (timelineIdx >= 0) {
+    expect(timelineIdx).toBeGreaterThanOrEqual(0);
+    {

This ensures the test fails loudly if the timeline output format changes unexpectedly.

bin/warp-graph.js (1)

1262-1275: History handler builds cursor info manually instead of using applyCursorCeiling.

Other commands (handleQuery, handlePath, handleCheck) use applyCursorCeiling to read and apply the cursor. handleHistory reads the cursor directly and filters patches in its own code path. While this works (history doesn't need materialization), the inconsistency makes it easy to miss if applyCursorCeiling logic changes later.

test/bats/cli-seek.bats (4)

14-24: Consider asserting the shape of ticks entries, not just array length.

The test checks len(data["ticks"]) > 0 but doesn't validate the structure of individual tick entries. If the schema of ticks changes (e.g., from integers to objects), this test would still pass silently.


141-153: Cross-command cursor test is valuable — consider adding a negative case too.

This test validates that query respects the active cursor, which is a key integration point. A complementary test verifying that after --latest, the query returns the full (unconstrained) node set would strengthen confidence in cursor clearing.


155-159: Plain-text test is minimal — grep -q "demo" is a weak assertion.

This only confirms the word "demo" appears somewhere in the output. It wouldn't catch regressions where the seek view format is broken but the graph name still appears (e.g., in an error message). Consider checking for a more specific seek-related string (e.g., "tick" or "cursor").

Proposed improvement
 `@test` "seek plain text output" {
   run git warp --repo "${TEST_REPO}" --graph demo seek
   assert_success
-  echo "$output" | grep -q "demo"
+  echo "$output" | grep -q "demo"
+  echo "$output" | grep -qiE "tick|cursor"
 }

18-23: JSON assertions via python3 env var pattern works but is fragile at scale.

Passing $output through an environment variable has OS-level size limits (typically ~128KB–2MB depending on platform). This is fine for these small test payloads, but worth noting if future tests produce larger JSON output. An alternative would be piping through stdin:

echo "$output" | python3 -c "import json,sys; data=json.load(sys.stdin); ..."

Not blocking — the current approach is fine for this test suite's scope.

Also applies to: 30-36, 46-50, 60-64, 80-85, 98-103, 119-124, 134-138, 148-152

- Fix ASCII graph arrowheads being swallowed by node bounding boxes
- Add seek demo GIF (docs/seek-demo.gif) showing time-travel with
  graph topology changes while worktree stays clean
- Embed seek demo in README CLI section
- Extract op summary renderer into shared module (opSummary.js)
- Rework seek ASCII renderer with improved swimlanes and receipts
- Add ROADMAP backlog section (structural diff, git-cas cache)
- Bump version 10.3.0 → 10.3.1
@flyingrobots flyingrobots changed the title feat: time-travel with git warp seek feat: git warp seek — time-travel through graph history Feb 10, 2026
@github-actions
Copy link

Release Preflight

  • package version: 10.3.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 v10.3.1, release workflow will publish.

Copy link

@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: 3

🤖 Fix all issues with AI agents
In `@bin/warp-graph.js`:
- Around line 1858-1884: The allPoints array in resolveTickValue blindly
prepends 0 which can duplicate an existing 0 from ticks; change construction of
allPoints so 0 is added only if not already present (e.g., check
ticks.includes(0) or deduplicate while preserving order), keeping the rest of
the logic using currentTick, ticks, startIdx and targetIdx unchanged so
indexOf(currentTick) and relative stepping behave correctly.
- Around line 1765-1841: parseSeekArgs currently accepts --tick=VALUE but
rejects --save=NAME, --load=NAME and --drop=NAME; update parseSeekArgs to also
recognize arg.startsWith('--save='), arg.startsWith('--load='), and
arg.startsWith('--drop=') (similar to the existing --tick= branch): when such an
arg is seen, ensure spec.action is 'status', set spec.action to
'save'/'load'/'drop' and set spec.name to the substring after the "="; keep
existing validation (use usageError when action already set or name missing) and
leave the existing space-separated handling intact so both forms work; reference
parseSeekArgs, spec, and usageError when making the changes.

In `@src/domain/WarpGraph.js`:
- Around line 763-768: The cache check in _materializeWithCeiling uses
_cachedState/_stateDirty/_cachedCeiling but not the current writer frontier, so
it can return stale state when refs/tips advance; modify the caching logic to
store a snapshot of the frontier (e.g., set this._cachedFrontier when populating
this._cachedState) and on each call to _materializeWithCeiling compare the
current frontier snapshot to this._cachedFrontier and bypass/invalidates the
cache if they differ (i.e., return a fresh materialization when frontier
changed) — update cache population and the early-return condition accordingly to
reference _cachedFrontier along with _cachedCeiling/_stateDirty.
🧹 Nitpick comments (3)
docs/seek-demo.tape (1)

19-27: Make the demo tape portable by deriving PROJECT_ROOT.

The hard-coded path ties recording to one machine. Capturing the repo root before switching to the temp repo lets others re-run the tape without edits.

♻️ Suggested update
- Type "TMPDIR=$(mktemp -d) && cd $TMPDIR && git init -q sandbox && cd sandbox"
+ Type "export PROJECT_ROOT=$(pwd)"
+ Enter
+ Type "TMPDIR=$(mktemp -d) && cd $TMPDIR && git init -q sandbox && cd sandbox"
@@
- Type "export PROJECT_ROOT=$HOME/git/git-stunts/git-warp"
- Enter
bin/warp-graph.js (2)

2376-2381: emitCursorWarning always receives null for maxTick from non-seek commands.

Every call site passes null as the second argument (lines 986, 1079, 1126, 1269, 1377), so the warning banner never shows "of {maxTick}". If this is intentional to avoid the overhead of discoverTicks() in non-seek commands, consider updating the JSDoc to clarify, or consider piggybacking on discoverTicks() when a cursor is active.

Also applies to: 984-986, 1077-1079, 1125-1126, 1264-1269


1264-1275: History filtering by cursor tick is correct but maxTick is never populated.

Line 1267 sets maxTick: null in cursorInfo, and it's never updated. This is only used by emitCursorWarning (which handles null gracefully), so there's no functional issue — just a dead field in this context.

…ntax

- Replace graph._seekCeiling mutation in materializeOneGraph and
  handleSeek with graph.materialize({ ceiling }) public option.
- Ensure the seek renderer always includes the active cursor tick in
  the timeline window, even when the tick is absent from discovered
  ticks (inserts at correct sorted position before windowing).
- Update GUIDE.md, README.md, and CHANGELOG examples to use --tick=+N
  / --tick=-N equals form for signed relative values, matching BATS
  tests and avoiding CLI parser ambiguity.
- Replace manual cursor reading in handleHistory with
  applyCursorCeiling for consistency with query/path/check handlers.
- Assert tick entries are integers, not just that the array is non-empty.
- Add negative test: query returns full node set after --latest clears
  the cursor.
- Strengthen plain-text seek output assertion to check for tick/cursor
  keywords, not just the graph name.
@github-actions
Copy link

Release Preflight

  • package version: 10.3.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 v10.3.1, release workflow will publish.

- _materializeWithCeiling now snapshots the writer frontier alongside
  the ceiling and invalidates the cache when tips advance, preventing
  stale state when new writers appear or patches are appended.
- resolveTickValue avoids duplicating tick 0 when ticks already
  contains it.
- Fix BATS seek tests: correct JSON key (nodes, not state.nodes),
  use tick 0 → latest for cursor-clearing test, switch cross-command
  tests to stdin piping, add receipt/diff/frontier-change assertions.
- Pre-push hook now runs all BATS test files in test/bats/.
Work around empty $output from BATS run when query command emits a
cursor warning to stderr — capture to a temp file instead.
@github-actions
Copy link

Release Preflight

  • package version: 10.3.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 v10.3.1, release workflow will publish.

The file was untracked so Docker builds (which copy from the git
context) never included it, causing test 55 to fail with
MODULE_NOT_FOUND on Node 20.
@github-actions
Copy link

Release Preflight

  • package version: 10.3.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 v10.3.1, release workflow will publish.

Copy link

@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: 1

🤖 Fix all issues with AI agents
In `@src/domain/WarpGraph.js`:
- Around line 737-741: The _resolveCeiling method currently uses the 'in'
operator and returns options.ceiling even when it's undefined; change the
presence check to treat undefined as "not provided" so null remains valid.
Update _resolveCeiling to return options.ceiling only when options exists and
options.ceiling !== undefined (e.g., if (options && options.ceiling !==
undefined) return options.ceiling;), otherwise fall back to this._seekCeiling;
this preserves explicit null while preventing an undefined ceiling from being
used.

Comment on lines 737 to 741
_resolveCeiling(options) {
if (options && 'ceiling' in options) {
return options.ceiling;
}
return this._seekCeiling;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n src/domain/WarpGraph.js | sed -n '730,750p'

Repository: git-stunts/git-warp

Length of output: 965


🏁 Script executed:

rg -n "_resolveCeiling" src/domain/WarpGraph.js

Repository: git-stunts/git-warp

Length of output: 150


🏁 Script executed:

rg -n "ceiling" src/domain/WarpGraph.js | head -30

Repository: git-stunts/git-warp

Length of output: 2170


🏁 Script executed:

rg -n "materialize\s*\(" src/domain/WarpGraph.js | head -20

Repository: git-stunts/git-warp

Length of output: 1329


🏁 Script executed:

rg -B3 -A3 "ceiling\s*:" src/ --type=js

Repository: git-stunts/git-warp

Length of output: 1165


🏁 Script executed:

sed -n '768,800p' src/domain/WarpGraph.js

Repository: git-stunts/git-warp

Length of output: 1288


🏁 Script executed:

fd -t f "test|spec" src/ --type=js

Repository: git-stunts/git-warp

Length of output: 232


🏁 Script executed:

rg -n "materialize\s*\(" --type=js test/ spec/ 2>/dev/null | head -20

Repository: git-stunts/git-warp

Length of output: 1770


🏁 Script executed:

rg -B2 -A2 "\.materialize\(" src/ --type=js | head -40

Repository: git-stunts/git-warp

Length of output: 2740


🏁 Script executed:

sed -n '595,620p' src/domain/WarpGraph.js

Repository: git-stunts/git-warp

Length of output: 1066


🏁 Script executed:

rg -n "ceiling\s*:" test/ --type=js

Repository: git-stunts/git-warp

Length of output: 1875


🏁 Script executed:

rg -n "undefined" src/domain/WarpGraph.js | grep -i ceiling

Repository: git-stunts/git-warp

Length of output: 45


🏁 Script executed:

rg -B5 -A5 "ceiling\s*:\s*undefined" . --type=js

Repository: git-stunts/git-warp

Length of output: 45


🏁 Script executed:

grep -n "{ ceiling" test/unit/domain/ -R

Repository: git-stunts/git-warp

Length of output: 1779


🏁 Script executed:

sed -n '380,395p' test/unit/domain/WarpGraph.seek.test.js

Repository: git-stunts/git-warp

Length of output: 585


🏁 Script executed:

rg -B10 -A10 "ceiling.*undefined" src/ test/

Repository: git-stunts/git-warp

Length of output: 45


🏁 Script executed:

sed -n '768,800p' src/domain/WarpGraph.js | grep -A5 "ceiling <="

Repository: git-stunts/git-warp

Length of output: 330


🏁 Script executed:

rg -B3 -A3 "materialize\s*\(\s*\{" test/ --max-count=20

Repository: git-stunts/git-warp

Length of output: 17529


🏁 Script executed:

python3 << 'EOF'
# Check what happens when ceiling is undefined in _materializeWithCeiling
# Specifically the filter operation at the patch filtering stage

test_cases = [
    ("ceiling = 2", "5 <= 2 = False (would filter out patches with lamport > 2)"),
    ("ceiling = null", "5 <= null = False (would filter out all patches)"),
    ("ceiling = undefined", "5 <= undefined = False (would filter out all patches)"),
    ("ceiling = 0", "5 <= 0 = False (would filter out all patches)"),
]

for case, result in test_cases:
    print(f"{case}: {result}")
EOF

Repository: git-stunts/git-warp

Length of output: 333


🏁 Script executed:

rg -n "lamport <=" src/domain/WarpGraph.js -A2 -B2

Repository: git-stunts/git-warp

Length of output: 1096


🏁 Script executed:

rg -n "lamport" src/domain/ --type-list | head -20

Repository: git-stunts/git-warp

Length of output: 452


🏁 Script executed:

sed -n '800,850p' src/domain/WarpGraph.js

Repository: git-stunts/git-warp

Length of output: 1643


🏁 Script executed:

fd -t f "tsconfig\|package.json" . | head -5

Repository: git-stunts/git-warp

Length of output: 45


🏁 Script executed:

head -50 package.json

Repository: git-stunts/git-warp

Length of output: 1362


🏁 Script executed:

rg -n "ceiling.*undefined" src/ test/ 2>/dev/null

Repository: git-stunts/git-warp

Length of output: 45


🏁 Script executed:

rg -n "const.*ceiling\s*=" src/ -A2 -B2 | head -40

Repository: git-stunts/git-warp

Length of output: 480


Guard against ceiling: undefined by treating it as "not provided".

The current implementation uses the 'in' operator to check if ceiling is present in options, then returns its value unconditionally. If a caller passes { ceiling: undefined }, the method returns undefined, which causes ceiling !== null at line 604 to evaluate true. This enters the ceiling-aware path with undefined as the ceiling value, making the filter at line 802 (entry.patch.lamport <= undefined) always false, resulting in an empty state.

While the JSDoc type signature declares {ceiling?: number|null} (excluding undefined), defensive handling to treat undefined as "not provided" aligns with the documented behavior where omitting the key falls through to _seekCeiling.

🛠️ Suggested fix
  _resolveCeiling(options) {
-   if (options && 'ceiling' in options) {
-     return options.ceiling;
-   }
+   if (options && 'ceiling' in options && options.ceiling !== undefined) {
+     return options.ceiling;
+   }
    return this._seekCeiling;
  }
🤖 Prompt for AI Agents
In `@src/domain/WarpGraph.js` around lines 737 - 741, The _resolveCeiling method
currently uses the 'in' operator and returns options.ceiling even when it's
undefined; change the presence check to treat undefined as "not provided" so
null remains valid. Update _resolveCeiling to return options.ceiling only when
options exists and options.ceiling !== undefined (e.g., if (options &&
options.ceiling !== undefined) return options.ceiling;), otherwise fall back to
this._seekCeiling; this preserves explicit null while preventing an undefined
ceiling from being used.

- parseSeekArgs now accepts = form for --save, --load, --drop
- seek-demo.tape derives PROJECT_ROOT from $(pwd) instead of hardcoding
- Clarified emitCursorWarning/applyCursorCeiling JSDoc re: null maxTick
@github-actions
Copy link

Release Preflight

  • package version: 10.3.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 v10.3.1, release workflow will publish.

The 'in' operator returned options.ceiling even when undefined; now uses
!== undefined so explicit null still overrides the instance ceiling.
@flyingrobots flyingrobots merged commit b5659d1 into main Feb 10, 2026
8 checks passed
@flyingrobots flyingrobots deleted the seek branch February 10, 2026 03:42
@github-actions
Copy link

Release Preflight

  • package version: 10.3.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 v10.3.1, release workflow will publish.

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