Skip to content

Conversation

@flyingrobots
Copy link
Member

@flyingrobots flyingrobots commented Feb 4, 2026

Summary

Completes all 10 tasks in the AUTOPILOT (v7.1.0) milestone, eliminating the "materialize tax" — the DX problem where developers must manually orchestrate state freshness across cached state, checkpoints, and indexes.

  • AP/INVAL/1–3: Dirty flag tracking + eager re-materialize on commit. Local writes via createPatch(), writer.commitPatch(), and PatchSession.commit() all apply patches to cached state immediately — no stale reads.
  • AP/LAZY/1–2: autoMaterialize: true option on WarpGraph.open(). Query methods (hasNode, getNodeProps, neighbors, getNodes, getEdges, query().run(), traverse.*) auto-materialize when state is null or dirty.
  • AP/CKPT/1–3: checkpointPolicy: { every: N } option. After materialize() processes N+ patches, a checkpoint is created automatically. Failures are swallowed — never breaks materialize.
  • AP/HOOK/1–2: Post-merge Git hook detects warp ref changes after git pull and prints a warning (or auto-materializes if warp.autoMaterialize git config is set). Installed via core.hooksPath on npm install.
  • ESLint config hardened to nuclear levels across the codebase.
  • Consolidated ROADMAP.md with task tracking script (scripts/roadmap.js).
  • git warp materialize CLI command.

Test plan

  • 1571 unit tests pass (67 files), including:
    • WarpGraph.invalidation.test.js — dirty flag + eager re-materialize (11 tests)
    • WarpGraph.writerInvalidation.test.js — Writer API invalidation (10 tests)
    • WarpGraph.lazyMaterialize.test.js — auto-materialize guard (46 tests)
    • WarpGraph.autoCheckpoint.test.js — auto-checkpoint trigger (14 tests)
    • WarpGraph.autoMaterialize.test.js — option validation (7 tests)
    • WarpGraph.checkpointPolicy.test.js — option validation (9 tests)
    • WarpGraph.patchCount.test.js — patch counter tracking (7 tests)
    • HookInstaller.test.js — hook install/upgrade/append/replace (29 tests)
    • WarpGraph.noCoordination.test.js — multi-writer safety regression suite (3 tests)
  • ESLint clean
  • CI (lint + Docker test suite + BATS CLI)

Summary by CodeRabbit

  • New Features

    • New CLI commands: materialize and install-hooks; install-hooks manages Git hooks interactively.
    • Git post-merge hook and repo-level auto-materialize + configurable checkpointing; query APIs now use async/promises for freshness.
  • Documentation

    • Added ROADMAP, DX/ideas reports, expanded hook/usage guides; removed legacy milestone task checklists.
  • Tests

    • Large suite covering checkpointing, auto-materialize, invalidation, patch counting, queries, and hook installer.
  • Chores

    • TypeScript/ESLint tooling, tsconfig, package script and roadmap CLI added.

Replace M1-M3-TASKS.md (all complete) and M4-M6-TASKS.md with a single
ROADMAP.md that combines the ideas roadmap with unfinished milestone
tasks. 52 tasks across 9 milestones (AUTOPILOT through ECHO), each
with user stories, acceptance criteria, test plans, and dependency
tracking.

Add scripts/roadmap.js for managing task lifecycle: close (with
status propagation), open, status, and show commands. The script
maintains an ASCII DAG and per-task status fields in ROADMAP.md.
…nuclear ESLint

Add `materialize` CLI subcommand that materializes + checkpoints all graphs
(or a single --graph). Add `warp.autoMaterialize` git config support to the
post-merge hook: when true, the hook runs `git warp materialize` automatically
after pulls that change warp refs.

Upgrade ESLint to typescript-eslint with typed linting. Key rules added:
- no-floating-promises: catches unawaited Promise return values
- no-misused-promises: catches Promises in boolean conditionals
- await-thenable: catches await on non-Promise values
- require-await: catches dead async keywords

Fix missing `await` bugs in production code caught by the new rules:
- QueryBuilder.run() called getNodes()/getNodeProps() without await
- LogicalTraversal._prepare() used hasNode() in if-check without await
- Fix incorrect JSDoc @returns on async methods (string[] vs Promise<string[]>)

Fix missing `await` in test files for async query API methods.
- Remove `@typescript-eslint/no-base-to-string` disable — `remote` is
  already a string at that point, so `String(remote)` was redundant.
- Remove `@typescript-eslint/require-await` disable — the outer
  createServer handler never awaited; the async work lives in the
  `req.on('end')` callback. Drop `async` from the outer handler.
…CKPT/3, AP/HOOK/1, AP/HOOK/2

Add comprehensive test suites proving the remaining AUTOPILOT features
work end-to-end:

- AP/INVAL/3: Writer.commitPatch() triggers eager re-materialize (10 tests)
- AP/LAZY/2: query methods auto-materialize when state is null/dirty (46 tests)
- AP/CKPT/3: materialize() auto-checkpoints when patch threshold exceeded (14 tests)
- AP/HOOK/1: post-merge hook script verified and roadmap closed
- AP/HOOK/2: add post-merge hook to scripts/hooks/ for npm install activation

AUTOPILOT (v7.1.0) is now 10/10 tasks closed.
@coderabbitai
Copy link

coderabbitai bot commented Feb 4, 2026

Warning

Rate limit exceeded

@flyingrobots has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 26 minutes and 24 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.

📝 Walkthrough

Walkthrough

Added extensive documentation and roadmap files; introduced Git hook management (installer, scripts, CLI commands) and auto-materialize/auto-checkpoint workflows; converted several synchronous APIs to async with Promise returns; added many unit tests and TypeScript/ESLint tooling, plus multiple small doc/JSDoc and util refactors.

📝 Walkthrough

Added extensive documentation and roadmap files; introduced Git hook management (installer, scripts, CLI commands) and auto-materialize/auto-checkpoint workflows; converted several synchronous APIs to async with Promise returns; added many unit tests and TypeScript/ESLint tooling, plus multiple small doc/JSDoc and util refactors.

Changes

Cohort / File(s) Summary
CLI & Hooks
bin/warp-graph.js, scripts/hooks/post-merge, scripts/roadmap.js, scripts/roadmap.js, scripts/roadmap.js
Adds new CLI commands (materialize, install-hooks), hook-install workflows, interactive prompts, check payload hook status, materialize handling, and a roadmap management script.
Hook implementation & tests
src/domain/services/HookInstaller.js, src/hooks/post-merge.sh, test/unit/domain/services/HookInstaller.test.js
New HookInstaller service with classification/installation APIs, post-merge hook script, and comprehensive unit tests covering install/upgrade/append/replace and status reporting.
WarpGraph core
src/domain/WarpGraph.js, src/domain/warp/Writer.js, src/domain/warp/PatchSession.js, src/domain/services/PatchBuilderV2.js
Makes materialization/checkpointing first-class (checkpointPolicy, autoMaterialize), introduces _stateDirty/_patchesSinceCheckpoint, converts query APIs to async Promises, and propagates onCommitSuccess payloads ({patch, sha}) for eager in-memory updates.
Persistence / Git adapter
src/infrastructure/adapters/GitGraphAdapter.js, src/ports/GraphPersistencePort.js
Adds many Git plumbing operations, improved JSDoc and error contracts, and new commit/tree/blob/ref helper methods for repository interactions.
Indexing, rebuild & services
src/domain/services/IndexRebuildService.js, src/domain/services/BitmapIndexBuilder.js, src/domain/services/BitmapIndexReader.js, src/domain/services/JoinReducer.js, src/domain/services/CheckpointService.js, src/domain/services/CheckpointSerializerV5.js
Mostly async-await adjustments, minor JSDoc/rules, a default GC policy, and small robustness/forward-compatibility tweaks.
Query, traversal, and builders
src/domain/services/QueryBuilder.js, src/domain/services/LogicalTraversal.js, src/domain/services/WarpStateIndexBuilder.js
QueryBuilder and traversal now perform asynchronous materialization-aware calls; doc updates and added error/@throws annotations.
Types, utils, heap
src/domain/types/WarpTypes.js, src/domain/utils/MinHeap.js, src/domain/crdt/*, src/domain/utils/WriterId.js
Adds typed operation factories, renames MinHeap internals, and updates JSDoc typedefs and small docs.
GC, health, ports
src/domain/services/GCPolicy.js, src/domain/services/HealthCheckService.js, src/ports/GraphPersistencePort.js
Introduces DEFAULT_GC_POLICY, enhanced JSDoc for health and persistence port contracts.
Tests
test/unit/domain/*.test.js (many files)
Large set of new and updated unit tests covering auto-checkpointing, auto-materialize, invalidation, patch counting, query async changes, writer invalidation, and related flows.
Docs & tooling
ROADMAP.md, docs/GUIDE.md, docs/ideas/*, eslint.config.js, package.json, tsconfig.json
Adds ROADMAP and IDEA docs, duplicates GUIDE sections for hooks, migrates ESLint config to TypeScript-aware config, adds TS tooling deps and npm script, and introduces tsconfig.json.
Ignored files
.gitignore
Adds an ignore pattern for CLAUDE.md.
Removed docs
M1-M3-TASKS.md, M4-M6-TASKS.md
Removes milestone/task checklist files.

Sequence Diagram(s)

sequenceDiagram
participant User as "User"
participant CLI as "warp-graph CLI"
participant HookInst as "HookInstaller"
participant FS as "Repo FS / Git hooks dir"
participant GitCfg as "git config"

User->>CLI: run `install-hooks` (optionally --force)
CLI->>HookInst: getHookStatus(repoPath)
HookInst->>FS: read existing hook file
FS-->>HookInst: return content / null
HookInst->>CLI: classifyExistingHook(content)
CLI->>User: prompt strategy (interactive) / decide strategy
User-->>CLI: choose strategy
CLI->>HookInst: install(repoPath, {strategy})
HookInst->>FS: write/backup/append hook file
HookInst->>FS: set executable bit
HookInst->>GitCfg: (maybe) read core.hooksPath
HookInst-->>CLI: return install result (action, hookPath, version)
CLI-->>User: renderInstallHooks(result)
Loading
sequenceDiagram
participant User as "User"
participant CLI as "warp-graph CLI"
participant Warp as "WarpGraph(materialize)"
participant Persistence as "Git Persistence"
participant Checkpoint as "CheckpointService"

User->>CLI: run `materialize` <graph>
CLI->>Warp: handleMaterialize(graph)
Warp->>Persistence: load latest checkpoint & patches since
Persistence-->>Warp: checkpoint + patches
Warp->>Warp: apply join/reduce to materialize state
Warp->>Checkpoint: maybe create checkpoint (policy)
Checkpoint-->>Persistence: write checkpoint
Warp-->>CLI: materialize result (state summary / checkpointSha)
CLI-->>User: renderMaterialize(result)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

🐇 I stitched hooks and docs in dawn's bright light,
A tiny shell to keep refs snug and tight.
I hop, I test, I stamp each version true,
Materialize, checkpoint — a carrot for you!
Cheers from a rabbit, code warm and spry 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately describes the main objective: completing the AUTOPILOT milestone (v7.1.0) to eliminate the materialize tax through cached state and automatic refresh mechanisms.

✏️ 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 phase-1-kill-materialize-tax

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

@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

Caution

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

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

135-143: ⚠️ Potential issue | 🟠 Major

Avoid breaking the public API: registerNode now returns sync value.

This change removes the Promise return type but the JSDoc still promises Promise<number>. It can break callers using .then(...) and changes scheduling semantics. Consider keeping the async signature for compatibility (or update all call sites + docs).

✅ Minimal compatibility fix
-  registerNode(sha) {
-    return this._getOrCreateId(sha);
-  }
+  async registerNode(sha) {
+    return this._getOrCreateId(sha);
+  }
src/domain/WarpGraph.js (1)

93-129: ⚠️ Potential issue | 🔴 Critical

Prevent recursive auto-checkpointing when createCheckpoint() calls materialize().

materialize() auto-checkpoints when the threshold is met, but createCheckpoint() calls materialize() unconditionally (Line 698). When a checkpoint policy is enabled and the threshold is reached, this can recurse indefinitely and overflow. Add a guard flag and reuse cached state when already fresh.

🛠️ Proposed fix to avoid recursion
@@
     /** `@type` {{every: number}|null} */
     this._checkpointPolicy = checkpointPolicy || null;
+
+    /** `@type` {boolean} */
+    this._checkpointing = false;
@@
-    if (this._checkpointPolicy && patchCount >= this._checkpointPolicy.every) {
+    if (this._checkpointPolicy && !this._checkpointing && patchCount >= this._checkpointPolicy.every) {
       try {
         await this.createCheckpoint();
         this._patchesSinceCheckpoint = 0;
       } catch {
         // Checkpoint failure does not break materialize — continue silently
       }
     }
@@
-    const state = await this.materialize();
+    let state;
+    const prevCheckpointing = this._checkpointing;
+    this._checkpointing = true;
+    try {
+      state = (this._cachedState && !this._stateDirty)
+        ? this._cachedState
+        : await this.materialize();
+    } finally {
+      this._checkpointing = prevCheckpointing;
+    }

Also applies to: 493-500, 680-708

🤖 Fix all issues with AI agents
In `@bin/warp-graph.js`:
- Around line 1066-1076: The execGitConfigValue function currently constructs
shell command strings using repoPath and key which allows injection and breaks
on spaces; change it to use child_process.execFileSync (or execFileSync) with an
argument array instead of a single shell string: call git as the executable and
pass ['-C', repoPath, ...] plus either ['rev-parse','--git-dir'] for the
--git-dir branch or ['config', key] for the normal branch, preserve options like
{ encoding: 'utf8' }, call .trim() on the result, and keep the existing
try/catch behavior so errors are still handled.
🧹 Nitpick comments (9)
docs/ideas/IDEA-003-DX.md (2)

48-48: Add language identifiers to fenced code blocks.

markdownlint MD040 flags missing language tags; use ```bash (or appropriate).

✅ Suggested fix
-```
+```bash

Also applies to: 68-68


17-17: Reduce repeated phrasing for readability.

You repeat “I need to understand…” in nearby sentences; consider rewording one instance.

tsconfig.json (1)

1-16: Consider including scripts/tests if you want them type-checked.

Right now only src and bin are in scope; if scripts/ or test/ should be covered by TS checks, add them to include.

💡 Optional include expansion
   "include": [
     "src/**/*.js",
-    "bin/**/*.js"
+    "bin/**/*.js",
+    "scripts/**/*.js",
+    "test/**/*.js"
   ]
src/domain/services/QueryBuilder.js (2)

241-269: Avoid serial getNodeProps calls in where filtering.

This loop now awaits per node, which can add avoidable latency on larger working sets. Consider batching with Promise.all to keep order but parallelize fetches.

⚡️ Possible parallelization
-        const filtered = [];
-        for (const nodeId of workingSet) {
-          const propsMap = (await this._graph.getNodeProps(nodeId)) || new Map();
-          const edgesOut = adjacency.outgoing.get(nodeId) || [];
-          const edgesIn = adjacency.incoming.get(nodeId) || [];
-          const snapshot = createNodeSnapshot({
-            id: nodeId,
-            propsMap,
-            edgesOut,
-            edgesIn,
-          });
-          if (op.fn(snapshot)) {
-            filtered.push(nodeId);
-          }
-        }
-        workingSet = sortIds(filtered);
+        const snapshots = await Promise.all(
+          workingSet.map(async (nodeId) => {
+            const propsMap = (await this._graph.getNodeProps(nodeId)) || new Map();
+            const edgesOut = adjacency.outgoing.get(nodeId) || [];
+            const edgesIn = adjacency.incoming.get(nodeId) || [];
+            return {
+              nodeId,
+              snapshot: createNodeSnapshot({ id: nodeId, propsMap, edgesOut, edgesIn }),
+            };
+          })
+        );
+        const filtered = snapshots
+          .filter(({ snapshot }) => op.fn(snapshot))
+          .map(({ nodeId }) => nodeId);
+        workingSet = sortIds(filtered);

299-313: Consider batching getNodeProps in the selection loop.

The final selection now awaits one-by-one; parallelization can reduce latency without changing ordering.

⚡️ Possible parallelization
-    const nodes = [];
-    for (const nodeId of workingSet) {
-      const entry = {};
-      if (includeId) {
-        entry.id = nodeId;
-      }
-      if (includeProps) {
-        const propsMap = (await this._graph.getNodeProps(nodeId)) || new Map();
-        const props = buildPropsSnapshot(propsMap);
-        if (selectFields || Object.keys(props).length > 0) {
-          entry.props = props;
-        }
-      }
-      nodes.push(entry);
-    }
+    const nodes = await Promise.all(
+      workingSet.map(async (nodeId) => {
+        const entry = {};
+        if (includeId) {
+          entry.id = nodeId;
+        }
+        if (includeProps) {
+          const propsMap = (await this._graph.getNodeProps(nodeId)) || new Map();
+          const props = buildPropsSnapshot(propsMap);
+          if (selectFields || Object.keys(props).length > 0) {
+            entry.props = props;
+          }
+        }
+        return entry;
+      })
+    );
src/domain/services/PatchBuilderV2.js (1)

271-274: Consider documenting the new callback payload.

Since onCommitSuccess now receives { patch, sha }, updating the constructor JSDoc (or a typedef) would help downstream implementers.

ROADMAP.md (1)

203-213: Add language specifiers to fenced code blocks.

The ASCII diagrams at lines 203 and 226 lack language specifiers. For ASCII art/diagrams, use text or plaintext to satisfy linting and improve accessibility for screen readers.

Proposed fix
-```
+```text
 AUTOPILOT ──→ GROUNDSKEEPER ──→ PULSE

And similarly for the Task DAG block at line 226.

Also applies to: 226-252

src/domain/warp/Writer.js (1)

47-54: Add JSDoc for the onCommitSuccess parameter.

The new onCommitSuccess parameter is not documented in the JSDoc block. Consider adding:

    * `@param` {Function} options.getCurrentState - Function to get current materialized state
+   * `@param` {Function} [options.onCommitSuccess] - Callback invoked after successful commit
    */
eslint.config.js (1)

23-31: Verify ESLint runtime compatibility for import.meta.dirname.

Line 30 relies on import.meta.dirname, which isn’t supported in older Node runtimes. If CI/local Node isn’t guaranteed to support it, the config will fail to load. Consider deriving __dirname via fileURLToPath(import.meta.url) for broader compatibility.

♻️ Safer dirname derivation
 import js from "@eslint/js";
 import tseslint from "typescript-eslint";
+import { fileURLToPath } from "node:url";
+import { dirname } from "node:path";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
@@
-        tsconfigRootDir: import.meta.dirname,
+        tsconfigRootDir: __dirname,

- Prevent recursive auto-checkpointing: add _checkpointing guard flag
  so createCheckpoint() → materialize() doesn't re-trigger auto-checkpoint.
  createCheckpoint() now reuses cached state when fresh.
- Fix command injection in execGitConfigValue: replace execSync shell
  string with execFileSync argument array.
- Restore Promise return type on StreamingBitmapIndexBuilder.registerNode()
  to preserve public API compatibility.
- Parallelize QueryBuilder where/select loops with Promise.all.
- Add JSDoc for Writer.onCommitSuccess parameter.
- Add language specifiers to ROADMAP.md fenced code blocks.
- Use fileURLToPath-based __dirname in eslint.config.js for broader
  Node.js compatibility (import.meta.dirname requires Node 21.2+).
- Expand tsconfig.json include to cover scripts/ and test/.
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

Caution

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

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

1669-1819: ⚠️ Potential issue | 🟡 Minor

Update examples to await async query APIs.
The docs still show sync usage (e.g., Line 1680, Line 1703, Line 1747, Line 1794, Line 1813). These methods now return Promises.

✏️ Example adjustment (apply similarly elsewhere)
- if (graph.hasNode('user:alice')) {
+ if (await graph.hasNode('user:alice')) {
🤖 Fix all issues with AI agents
In `@src/domain/WarpGraph.js`:
- Around line 1646-1654: The _ensureFreshState method currently only throws when
there is no cached state, so if this._stateDirty is true and
this._autoMaterialize is false reads will use stale data; update
_ensureFreshState (in class WarpGraph, method _ensureFreshState) to check for a
dirty state when auto-materialize is disabled and throw an Error (e.g., "Cached
state is dirty; call materialize()") before returning, while preserving the
existing materialize path when this._autoMaterialize is true and the
no-cached-state error when _cachedState is absent.
🧹 Nitpick comments (1)
ROADMAP.md (1)

1-1892: Exceptional planning document with outstanding internal consistency.

This 1892-line roadmap demonstrates remarkable attention to detail:

Strengths:

  • All cross-references verified accurate (task dependencies, blocking relationships, cross-milestone links)
  • Arithmetic confirmed correct (task counts, hour estimates)
  • Comprehensive task specifications with clear acceptance criteria
  • Excellent scope management (explicit non-goals)
  • Logical milestone progression from practical to theoretical

💡 Optional enhancements for navigability:

  • Consider adding a table of contents for a document of this size
  • Could add a "Document History" section to track roadmap evolution
  • Might benefit from section anchors for deep-linking

This level of consistency and completeness in a planning document significantly reduces ambiguity and sets clear expectations for implementation.

- _ensureFreshState now throws when state is dirty and autoMaterialize
  is off, preventing silent reads of stale data.
- Update all query method JSDoc examples to await async return values.
Replace bare Error throws in _ensureFreshState, join, runGC, and
applySyncResponse with QueryError instances carrying distinct codes:
- E_NO_STATE: no cached state (call materialize() first)
- E_STALE_STATE: cached state is dirty (call materialize() to refresh)

Update tests to assert on error type (QueryError) instead of brittle
message string matching.
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: 2

🤖 Fix all issues with AI agents
In `@src/domain/WarpGraph.js`:
- Around line 239-248: The onCommitSuccess handler currently applies joinPatch
to this._cachedState even when this._stateDirty is true, which can materialize a
stale cache and clear the dirty flag incorrectly; update the gating so eager
re-materialization only happens when the cache is clean (i.e., !_stateDirty) and
this._cachedState, patch, and sha are present, otherwise leave this._stateDirty
true; apply the same change to the other onCommitSuccess occurrences referenced
(around lines 1569-1577 and 1622-1630) so all eager patch applications check
!_stateDirty before calling joinPatch and _setMaterializedState.

In `@test/unit/domain/WarpGraph.lazyMaterialize.test.js`:
- Around line 153-163: The mock for persistence.listRefs returns an array of
objects but discoverWriters expects an array of ref path strings; update the
test in WarpGraph.lazyMaterialize.test.js to have
persistence.listRefs.mockResolvedValue return an array of string ref paths (e.g.
'refs/warp/test/writers/writer-1') instead of objects with {ref, sha} so
discoverWriters can iterate correctly and the test accurately exercises
rematerialization behavior.

Audit and fix JSDoc in 21 files:

- WarpGraph.js: document constructor options (checkpointPolicy,
  autoMaterialize), fix @throws to use QueryError with E_NO_STATE /
  E_STALE_STATE codes, document onCommitSuccess callback shape,
  add @throws for syncWith/serve/join/runGC/applySyncResponse
- Services (10 files): add missing @param/@returns/@throws to
  QueryBuilder, LogicalTraversal, HookInstaller, CommitDagTraversal,
  BitmapIndexBuilder/Reader, Frontier, GCPolicy, HealthCheckService,
  WarpStateIndexBuilder
- CRDT/Utils/Warp (7 files): fix ORSet serialize/deserialize types,
  add @throws to VersionVector, fix MinHeap param names, add
  WarpTypes.js typedefs, document Writer/PatchSession throws
- Infrastructure (3 files): expand GraphPersistencePort abstract
  contract with @throws and param descriptions, complete
  GitGraphAdapter method docs, document CLI command handlers
CHANGELOG: document all AUTOPILOT features (auto-invalidation,
auto-materialize, auto-checkpoint, post-merge hook, CLI commands),
ESLint hardening, QueryError codes, and 133 new tests (1571 total).

README: add autoMaterialize to Quick Start, fix missing await in
query examples, document checkpointPolicy option, update querying
section intro.

GUIDE: add Auto-Materialize and Auto-Checkpoint section with code
examples, document eager re-materialize after writes, update
troubleshooting and performance tips for auto-checkpointing.
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: 2

Caution

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

⚠️ Outside diff range comments (2)
src/infrastructure/adapters/GitGraphAdapter.js (2)

436-449: ⚠️ Potential issue | 🟠 Major

nodeExists masks non-missing errors.

Catching all errors and returning false silently hides repository failures. The codebase already uses this pattern elsewhere (refExists, getConfig) to distinguish between "object doesn't exist" (exit code 1) and actual errors that should be rethrown.

Suggested fix
  async nodeExists(sha) {
    this._validateOid(sha);
    try {
      await this._executeWithRetry({ args: ['cat-file', '-e', sha] });
      return true;
-   } catch {
-     return false;
+   } catch (err) {
+     const exitCode = err?.details?.code ?? err?.exitCode ?? err?.code;
+     if (exitCode === 1) {
+       return false;
+     }
+     throw err;
+   }
  }

38-55: ⚠️ Potential issue | 🟡 Minor

Handle missing-ref errors consistently and close the TOCTOU gap in readRef.

refExists only checks err?.details?.code === 1 while configGet (line 540) checks err.exitCode === 1, indicating inconsistent error shapes from the plumbing library. Additionally, readRef has a race condition: the ref can be deleted between the refExists check and rev-parse execution, causing it to throw instead of returning null per its contract.

Fix by adding fallback error field checks in refExists and wrapping rev-parse in a try-catch that treats exit code 1 as a missing ref:

Suggested changes
 async function refExists(execute, ref) {
   try {
     await execute({ args: ['show-ref', '--verify', '--quiet', ref] });
     return true;
   } catch (err) {
-    if (err?.details?.code === 1) {
+    const exitCode = err?.details?.code ?? err?.exitCode ?? err?.code;
+    if (exitCode === 1) {
       return false;
     }
     throw err;
   }
 }
   async readRef(ref) {
     this._validateRef(ref);
     const exists = await refExists(this._executeWithRetry.bind(this), ref);
     if (!exists) {
       return null;
     }
-    const oid = await this._executeWithRetry({
-      args: ['rev-parse', ref]
-    });
-    return oid.trim();
+    try {
+      const oid = await this._executeWithRetry({ args: ['rev-parse', ref] });
+      return oid.trim();
+    } catch (err) {
+      const exitCode = err?.details?.code ?? err?.exitCode ?? err?.code;
+      if (exitCode === 1) {
+        return null;
+      }
+      throw err;
+    }
   }
🤖 Fix all issues with AI agents
In `@src/domain/services/HookInstaller.js`:
- Around line 144-153: In _upgradeInstall, when classification.appended is true
call replaceDelimitedSection(existing, stamped) and then detect if the returned
updated content is identical to existing (which indicates missing/corrupted
DELIMITER_END); if identical, fall back to writing stamped (overwrite) instead
of writing the no-op result so the upgrade isn't silently skipped. Reference
_upgradeInstall, classifyExistingHook, replaceDelimitedSection and DELIMITER_END
when making this change.
- Around line 144-183: The writes in _upgradeInstall, _appendInstall, and
_replaceInstall use this._fs.writeFileSync(...) with a mode option that only
applies on file creation, so after each writeFileSync call (the ones in
_upgradeInstall where updated or stamped are written, the one in _appendInstall
where appended is written, and the one in _replaceInstall where stamped and the
backup are written) call this._fs.chmodSync(hookPath, 0o755) (or the appropriate
backup path for the backup write) to ensure the hook file is executable; locate
the writeFileSync calls in these methods and add matching chmodSync calls
referencing hookPath (and backupPath for the backup) immediately after each
write.
🧹 Nitpick comments (2)
src/domain/types/WarpTypes.js (1)

89-133: Well-structured operation type definitions.

The new JSDoc typedefs are clean, consistent with existing patterns in the file, and correctly correspond to the factory function return types.

Optional enhancement: Consider adding a union type to enable type-safe handling of any operation:

/**
 * Any graph operation
 * `@typedef` {OpNodeAdd | OpNodeTombstone | OpEdgeAdd | OpEdgeTombstone | OpPropSet} Op
 */

This would parallel the existing ValueRef pattern and benefit consumers that process operations polymorphically.

,

src/domain/services/GCPolicy.js (1)

39-46: Avoid shared mutable DEFAULT_GC_POLICY.
Consider freezing (or returning a fresh copy) to prevent accidental mutation by consumers.

Proposed change
-export const DEFAULT_GC_POLICY = {
+export const DEFAULT_GC_POLICY = Object.freeze({
   tombstoneRatioThreshold: 0.3, // 30% tombstones triggers GC
   entryCountThreshold: 50000, // 50K entries triggers GC
   minPatchesSinceCompaction: 1000, // Min patches between GCs
   maxTimeSinceCompaction: 86400000, // 24 hours max between GCs
   compactOnCheckpoint: true, // Auto-compact on checkpoint
-};
+});

- GitGraphAdapter.nodeExists: only return false for exit code 1, rethrow real errors
- GitGraphAdapter.refExists: normalize exit code check across error field shapes
- GitGraphAdapter.readRef: wrap rev-parse in try-catch to close TOCTOU race
- HookInstaller: detect no-op in _upgradeInstall when delimiters are corrupted,
  add chmodSync after writes to ensure hooks are executable
- GCPolicy: freeze DEFAULT_GC_POLICY to prevent accidental mutation
- WarpTypes: add Op union typedef covering all operation types
The mode option in writeFileSync only applies on file creation, not when
overwriting. Add explicit chmodSync(0o755) after each write to ensure
hooks are always executable, including _freshInstall and the backup
write in _replaceInstall.
listRefs returns an array of ref path strings, not objects with
{ref, sha}. The mock was returning the wrong shape, preventing
discoverWriters from iterating correctly during rematerialization.
The onCommitSuccess handler was applying joinPatch to _cachedState even
when _stateDirty was true, which would layer a single patch on top of
stale state and then incorrectly clear the dirty flag. Now all three
onCommitSuccess callbacks (createPatch, writer, createWriter) check
!_stateDirty before eager application, falling through to mark dirty
when the cache is already stale.
@flyingrobots flyingrobots merged commit bc1440c into main Feb 4, 2026
3 checks passed
@flyingrobots flyingrobots deleted the phase-1-kill-materialize-tax branch February 4, 2026 10:36
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.

2 participants