Skip to content

Proj/dreaming#436

Merged
danhdoan merged 50 commits intomainfrom
proj/dreaming
Apr 16, 2026
Merged

Proj/dreaming#436
danhdoan merged 50 commits intomainfrom
proj/dreaming

Conversation

@danhdoan
Copy link
Copy Markdown
Collaborator

Summary

  • Problem: ByteRover's context tree accumulates duplicates, stale drafts, and missed cross-domain patterns over time. No automated maintenance existed - users had to manually curate/review to keep the tree healthy.
  • Why it matters: Over multiple curation cycles, content quality degrades. The agent's retrieval gets noisier, review queues grow, and the tree drifts from the knowledge it should represent.
  • What changed: Ships the full dreaming feature — a 3-operation background pipeline (consolidate, synthesize, prune) triggered from CLI or idle-agent that runs an LLM to clean up the context tree with human-review gates on destructive actions.
  • What did NOT change (scope boundary): No changes to brv query, brv search, brv curate, or the review system's core. Dreaming extends the review queue (dual-write pattern) but doesn't modify review command behavior.

Type of change

  • Bug fix
  • New feature
  • Refactor (no behavior change)
  • Documentation
  • Test
  • Chore (build, dependencies, CI)

Scope (select all touched areas)

  • TUI / REPL
  • Agent / Tools
  • Server / Daemon
  • Shared (constants, types, transport events)
  • CLI Commands (oclif)
  • Hub / Connectors
  • Cloud Sync
  • CI/CD / Infra

Linked issues

  • Closes ENG-2060 (consolidate operation)
  • Closes ENG-2061 (synthesize operation)
  • Closes ENG-2062 (prune operation)
  • Closes ENG-2063 (idle trigger + human review wiring)
  • Closes ENG-2065 (dream state + log services)
  • Closes ENG-2066 (LLM response parser + schemas)
  • Closes ENG-2067 (dream trigger — four-gate eligibility)
  • Closes ENG-2068 (dream executor + CLI + daemon wiring)
  • Closes ENG-2070 (dream undo)
  • Closes ENG-2121 (TUI display improvements)
  • Closes ENG-2122 (--detach flag)
  • Closes ENG-2126 (4 spec-violation gaps post-merge)
  • Closes ENG-2128 (inline file contents in operation prompts)

Root cause (bug fixes only)

N/A - new feature.

Test plan

  • Coverage added:
    • Unit test (dream-state, dream-lock, dream-log, trigger, executor, all 3 operations, undo)
    • Integration test
    • Manual verification via auto-test smoke suite (local-auto-test/dreaming/)
  • Test file(s):
    • test/unit/infra/dream/dream-state-schema.test.ts
    • test/unit/infra/dream/dream-log-schema.test.ts
    • test/unit/infra/dream/dream-state-service.test.ts
    • test/unit/infra/dream/dream-lock-service.test.ts
    • test/unit/infra/dream/dream-log-store.test.ts
    • test/unit/infra/dream/dream-trigger.test.ts
    • test/unit/infra/dream/parse-dream-response.test.ts
    • test/unit/infra/dream/dream-undo.test.ts
    • test/unit/infra/dream/operations/consolidate.test.ts
    • test/unit/infra/dream/operations/synthesize.test.ts
    • test/unit/infra/dream/operations/prune.test.ts
    • test/unit/infra/executor/dream-executor.test.ts
  • Key scenario(s) covered:
    • Four-gate eligibility (time, activity, queue, lock)
    • All three operations produce correct DreamOperation entries
    • Atomic read-modify-write for dream-state (no lost increments under concurrent curations)
    • PID-based lock with stale detection and rollback-on-error
    • Undo reverses all operation types (MERGE, TEMPORAL_UPDATE, SYNTHESIZE, ARCHIVE, SUGGEST_MERGE)
    • Cross-cycle pendingMerges consumption (prune writes → next consolidate reads)
    • Review queue dual-write (dream operations with needsReview: true surface in brv review pending)
    • Idle trigger dispatches dream automatically from brv-server.ts before killing idle agent
    • Auto-test smoke suite: 24/24 pass with real LLM (Google Gemini), including force scenarios for
      each operation type

User-visible changes

  • brv dream [--force] [--detach] [--undo] [--format text|json] [--timeout <s>]
    new CLI command. Runs three operations in sequence: consolidate, synthesize, prune.
  • Idle agents automatically dispatch dream when eligibility gates pass (time +
    activity + queue + lock).
  • brv review pending now includes dream-generated operations flagged for review.
  • New files on disk per project:
    • .brv/dream-state.json — checkpoint state (lastDreamAt, curationsSinceDream, pendingMerges)
    • .brv/dream.lock — PID lock with mtime-as-checkpoint
    • .brv/dream-log/drm-*.json — per-dream operation history

Evidence

Unit tests: 6169 passing, 0 failing
Build: clean
Lint: 0 errors (warnings pre-existing)

Auto-test (real LLM via Google Gemini): 24/24 scenarios pass, including:

  • force-merge: two near-duplicate files → MERGE executed, one file merged
  • force-synthesize: three domains mentioning FlushCoordinator → SYNTHESIZE operation
    writes a type: synthesis file to the correct domain
  • force-archive: draft with importance: 10 and "TODO... safe to delete" body → ARCHIVE
    operation moves the file to _archived/ with stub + full preserved

Auto-test suite lives at /Users/danh/workspace/projects/local-auto-test/dreaming/
and is not part of this PR (pre-release smoke test, not CI).

Checklist

  • Tests added or updated and passing (npm test)
  • Lint passes (npm run lint)
  • Type check passes (tsc -b)
  • Build succeeds (npm run build)
  • Commits follow Conventional Commits format
  • Documentation updated (dream flags documented via oclif --help)
  • No breaking changes
  • Branch is up to date with main

Risks and mitigations

  • Risk: Dream triggers automatically on idle agents — could surprise users who don't know the feature exists.
    • Mitigation: Dream runs silently by default (--detach behavior from idle trigger). Operations requiring review (needsReview: true) surface in brv review pending before taking effect. User sees pending reviews and decides.
  • Risk: LLM cost. Each dream invokes the LLM up to 3 times (one per operation). Idle triggers could run dreams without explicit user action.
    • Mitigation: Activity gate requires ≥3 curations since last dream. Time gate requires 12h between dreams. Auto-test smoke run costs ~10-15 LLM calls total; production cost scales with user activity, not calendar time.
  • Risk: Wrong LLM output corrupts the context tree.
    • Mitigation: (1) MERGE and TEMPORAL_UPDATE operations preserve previousTexts in the dream log for brv dream --undo reversal. (2) All destructive operations require review before being applied (review backup store). (3) Outer catch {} ensures partial failures don't abort the whole dream.
  • Risk: Inlined file contents in LLM prompts could exceed model context limits.
    • Mitigation (consolidate): capPayloadSize() enforces MAX_PAYLOAD_CHARS = 200_000 with changed-files-first eviction.
    • Mitigation (prune): max 20 candidates × 1000-char preview (~20KB ceiling).
    • Gap (synthesize): no explicit cap on inlined _index.md content - relies on summaries being small. Tracked as a follow-up to add a cap similar to consolidate's.
  • Risk: Silent catch {} blocks hide real errors (e.g., LLM auth failures). Dream log shows errors: 0 even when every LLM call failed.
    • Mitigation: Not addressed in this PR. Tracked as a follow-up - summary.errors should reflect caught failures so users can see when LLM calls fail.

RyanNg1403 and others added 30 commits April 13, 2026 15:11
Add dream infrastructure persistence layer:
- dream-state-schema.ts: DreamStateSchema, PendingMergeSchema, EMPTY_DREAM_STATE
- dream-log-schema.ts: DreamOperationSchema (CONSOLIDATE/SYNTHESIZE/PRUNE),
  DreamLogEntrySchema (completed/partial/error/processing/undone)
- dream-state-service.ts: fail-open read, atomic write, incrementCurationCount
- dream-lock-service.ts: PID-based lock with stale detection and write-then-verify
- dream-log-store.ts: atomic save, Zod validation, monotonic IDs, stale processing
  resolution, pruning beyond maxEntries

79 tests passing, 0 type errors.
- DreamStateService.read(): spread pendingMerges as new array to avoid
  mutating EMPTY_DREAM_STATE module constant
- DreamLockService.tryAcquire(): add mkdir guard before writeFile to
  handle fresh projects where baseDir doesn't exist yet
- isProcessAlive: treat EPERM as alive (process exists, lacking permission)
- SynthesizeOperationSchema: constrain confidence to [0, 1]
feat: [ENG-2065] dream state + log services
- dream-response-schemas.ts: ConsolidateResponseSchema, SynthesizeResponseSchema,
  PruneResponseSchema with Zod validation (confidence [0,1], files min 1)
- parse-dream-response.ts: two-strategy extraction (code fence first, raw JSON
  fallback), generic over any Zod schema, returns null on failure

26 new tests, 105 total dream tests passing.
feat: [ENG-2066] LLM response parser and dream response schemas
- DreamTrigger.shouldDream(): time (12h), activity (3 curations), queue
  empty, PID lock — gates 1-3 skipped with force, lock never skipped
- Exported DreamEligibility discriminated union type
- 15 tests covering all gates, force mode, first dream, gate ordering,
  custom thresholds

120 total dream tests passing.
…rder test

- Renamed shouldDream → tryStartDream to communicate lock acquisition side effect
- Added test: queue failure prevents lock acquisition
feat: [ENG-2067] dream trigger — four-gate eligibility check
Implements the dream execution orchestrator (8-step flow with NO-OP
operation stubs), `brv dream` CLI command with --force/--format flags,
and wires dreaming into the daemon task pipeline.

- DreamExecutor: lock lifecycle (release on success, rollback on error),
  dream state updates, curate log scanning for changed files, dream log
  persistence with processing→completed/error/partial status transitions
- CLI: `brv dream` (gated) and `brv dream --force` (skip gates 1-3),
  text and JSON output with proper skip/completed distinction
- schemas.ts: add 'dream' to TaskTypeSchema + optional force field
- agent-process.ts: DreamTrigger check + DreamExecutor dispatch
- curate-executor.ts: increment dream curation counter after Phase 4
- task-router.ts: forward force field in TaskExecute message
- Capture findChangedFilesSinceLastDream return value for future ops
- Document why computeSummary.errors is always 0 (log-level, not per-op)
- Remove `as` type assertion in dream CLI (use ternary narrowing)
- Remove `result!` non-null assertion (use nullish fallback)
- Static import DreamStateService in curate-executor (was dynamic)
- Explicit if/else for lock release/rollback readability
- Double-cast test stub for intentionality
feat: [ENG-2068] Dream executor, CLI command, and daemon wiring
…s-reference

First LLM-driven dream operation. Groups changed files by domain, finds
related files via BM25 search + path siblings, classifies relationships
through LLM, and executes actions:

- MERGE: combine redundant files, delete sources, add consolidated_from
  and consolidated_at frontmatter programmatically
- TEMPORAL_UPDATE: rewrite contradictory content with temporal narrative,
  confidence-gated review (< 0.7 → needsReview)
- CROSS_REFERENCE: add bidirectional related links in YAML frontmatter
- SKIP: no-op for unrelated files

Also fixes three bugs found during interactive testing:
- Use global storagePath for curate log store (not projectPath/.brv)
- Extract filePath (absolute) from curate log ops, not path (domain)
- First dream (lastDreamAt=null) scans all curate logs, not none

Schema change: added optional confidence field to ConsolidationActionSchema.
- Guard MERGE/TEMPORAL_UPDATE against missing content (prevent data loss)
- Wrap createTaskSession in try/catch (preserve fail-open contract)
- Normalize Windows paths and reject path traversal in toContextTreeRelative
- Cap LLM sandbox payload at 200K chars (matches curate task limit)
- Fix misleading test name
The 5-minute timeout in DreamExecutor created an AbortController but
never passed the signal to consolidate(), making the budget inoperative.
feat: [ENG-2060] Consolidate operation — merge, temporal update, cross-reference
Add `brv dream --undo` to restore files changed by the last dream.
Runs directly from CLI (no daemon/agent) using previousTexts stored
in the dream log. Supports MERGE, TEMPORAL_UPDATE, CROSS_REFERENCE
(skip), SYNTHESIZE, and PRUNE reversal with partial-failure resilience.
Fixes applied (6):
- #1: Path traversal guard — safePath() validates all file paths stay
  within contextTreeDir before write/delete
- #2: SYNTHESIZE/UPDATE throws instead of deleting pre-existing file
  (previousTexts not captured for synthesize operations)
- #3: Remove unnecessary `as number` cast — TypeScript narrows
  log.completedAt after status checks
- #5: Manifest rebuild errors now reported in result.errors instead
  of silently swallowed
- #6: unlinkSafe() only swallows ENOENT, rethrows permission errors
- #9: Added test for SYNTHESIZE/UPDATE error path

Skipped (4):
- #4: lastDreamLogId intentionally NOT cleared — keeping it allows
  "Dream already undone" error which is more informative than
  "No dream to undo" on double-undo
- #7: resolveProject() fallback to cwd follows existing codebase
  pattern (curate/view.ts uses the same approach)
- #8: No exhaustiveness check in switch — silent no-op is correct
  for undo of unknown future operation types (safer than crashing)
- #10: No lastDreamLogId assertion in test — follows from #4 skip
feat: [ENG-2070] Dream undo — revert last dream from previousTexts
Add synthesize pipeline: collect domain _index.md summaries, LLM
cross-domain analysis (single call), BM25 dedup (>0.5 threshold),
write as regular draft context files with type:synthesis frontmatter.

Also fixes manifest rebuild path in dream-undo (was passing
contextTreeDir instead of letting the service use its configured
baseDirectory, causing doubled path).
BM25 search against the full tree always returned score 1.0 because
synthesis derives from domain summaries already in the tree. Fixed to
only match against existing type:synthesis files, which is the correct
semantic — avoid recreating an existing synthesis, not avoid
synthesizing concepts the tree already covers.

Also fixes manifest rebuild path in dream-undo (buildManifest() not
buildManifest(contextTreeDir) — service already has baseDirectory).
- Path traversal guard on LLM-controlled placement (reuses safePath pattern)
- Replace as-assertion with in-operator narrowing in parseFrontmatterType
- Use js-yaml dump() for frontmatter serialization (handles special chars)
- Propagate abort signal through executeOnSession interface to LLM calls
- Gate synthesize on changedFiles.size > 0 to skip no-op dream cycles
- Per-candidate error handling in write loop to preserve partial results
feat: [ENG-2061] Synthesize operation — cross-domain pattern detection
Two-signal candidate detection (importance decay + mtime staleness),
LLM review (ARCHIVE/KEEP/MERGE_INTO), archive via existing service,
defer merge suggestions to next dream cycle via pendingMerges.

Bugs found and fixed during interactive testing:
- Inline candidate summaries in prompt (LLM exhausted maxIterations on code_exec)
- Re-read dream state at Step 7 to preserve pendingMerges written by prune
- Wire archiveService + projectRoot into CLI undo for archive restoration
- Compute stub path from original path in undo (restoreEntry expects stub)
- Remove ! non-null assertion, guard with if-check instead
- Extract shared walkMdFiles helper, eliminating duplicated walk logic
- Drop MERGE_INTO op when mergeTarget is absent (return undefined)
- Store stubPath in ARCHIVE operation, undo reads it directly (no fragile path recomputation)
- Fix mergeTarget? in JSON prompt example to valid format
- Add test for MERGE_INTO with absent mergeTarget
Use toUnixPath on walkMdFiles relative path output to ensure consistent
forward-slash separators across platforms. Without this, Signal A/B dedup
and candidateSet hallucination guard would fail on Windows due to
backslash vs forward-slash mismatch.
feat: [ENG-2062] prune operation — staleness scoring and archival
- dispatch dream tasks from daemon idle handling when eligibility gates pass
- dual-write reviewable dream operations with backups and persisted task IDs
- reject matching dream review entries and clear backups on dream undo
bao-byterover and others added 7 commits April 16, 2026 11:51
Consolidate and synthesize injected file content via
setSandboxVariableOnSession and told the LLM to call code_exec to read
it.
The LLM often skipped code_exec, saw only paths without content, and
returned {"actions": []} — producing zero operations on clear-cut cases.
The outer catch {} swallowed the silence, so dream logs reported
status: completed with no errors.

Inline file contents directly in the prompt as marked blocks. Remove the
sandbox-variable indirection. Tighten rule wording to nudge the LLM away
from the SKIP-everything default. Prune already inlined content; also
bump its preview size and rewrite conservative wording.

Unit tests updated to assert on prompt content instead of
sandbox-variable
calls. Auto-test suite (local-auto-test/dreaming) now passes 24/24 with
a
working LLM provider: force-merge, force-synthesize, and force-archive
scenarios all produce their expected operations end-to-end.
enhance: [ENG-2128] inline file contents in operation prompts
Fixes four implementation gaps in proj/dreaming where shipped code
violated the spec at notes/byterover-dream/. Filed together because they
share the same surface and are all "spec says X, code does Y" bugs.

Issue 1 — Counter race in incrementCurationCount
With AGENT_MAX_CONCURRENT_TASKS=5, parallel curates raced on the
read-modify-write of dream-state.json, dropping increments (empirically
7 parallel curates → counter at 3, 4 lost). Activity gate could stay
locked silently, blocking auto-dream at agent-idle.

Fix: serialize all writers via a generic update(updater) on
DreamStateService, backed by a per-file AsyncMutex.
incrementCurationCount, dream-executor's step-7 reset, and consolidate's
pendingMerges clear all route through update() so no two writers can
interleave their RMW windows.

Issue 2 — pendingMerges never consumed by consolidate
prune.ts wrote SUGGEST_MERGE entries to dreamState.pendingMerges for the
next dream's Consolidate to pick up, but the consumer was never wired —
entries sat forever, MERGE_INTO suggestions never landed.

Fix: per notes/byterover-dream/6-dream-undo-and-cross-cycle.md:89-143,
add loadAndClearPendingMerges() at the start of consolidate(): read
pendingMerges, parallel-stat each sourceFile, add existing ones to
changedFiles, surface mergeTarget+reason as non-binding LLM hints,
clear pendingMerges unconditionally regardless of LLM outcome. Stale
sourceFiles (deleted between dreams) are silently skipped.

Issue 3 — Gate 3 (queue) bypassed on CLI dispatch path
DreamTrigger was constructed with getQueueLength: () => 0 at the agent
side. The idle-trigger path pre-checked gates 1-3 before dispatch, but
the CLI path went straight to the agent with gate 3 effectively disabled,
so brv dream (no --force) ran even when other tasks were queued.

Fix: add a preDispatchCheck hook to TaskRouter; daemon wires the same
getQueueLength resolver as the idle path; only dream tasks without
--force are pre-checked. Pre-check errors fail open (agent gates 1+2
stay as defense-in-depth).

Issue 4 — Partial dreams lost operations and broke undo
allOperations was assembled only after all three ops succeeded; on
timeout/error the catch block hardcoded operations:[] and zero summary.
A consolidate that committed real file merges then a synthesize timeout
would leave the context tree modified with no audit trail and no undo
path (dream-undo reads operations from the log).

Fix: hoist allOperations above the try block, push results incrementally
after each step, use the accumulator in both partial and error log
entries. Also run createReviewEntries for review-flagged ops that did
complete on partial runs (gated on non-empty allOperations to preserve
the "no log → no review entries" invariant on early failure).

Tests
- 28 new unit tests covering each fix's failure mode (counter race
  regression, pendingMerges round-trip, gate 3 short-circuit, partial-log
  preservation, review-entry creation from partial)
- All 308 dream-related unit tests pass

Verified interactively
- Issue 1: 5 brv curate --detach + brv dream --force in parallel — all
  5 increments accounted for in dream-state.json (4 consumed, 1 preserved)
- Issue 2: seeded pendingMerge — first CONSOLIDATE op was the
  sourceFile→mergeTarget pair, pendingMerges cleared after the run
- Issue 3: 8 parallel curates → CLI dream skipped with
  "Queue not empty (3 tasks pending)"
- Issue 4: 3 regression tests force budget abort + non-abort error,
  asserting partial log + review entry creation
Addresses 10 review items from RyanNg1403 (Claude Code) and ChatGPT Codex
on PR #434. Two real bugs (1× P1, 1× P2) found by Codex; the rest are
documentation / test-assertion improvements from RyanNg.

P1 (Codex) — task-router pre-dispatch skip polluted AgentPool counter
preDispatchCheck's ineligible branch routed through handleTaskCompleted,
which unconditionally calls agentPool.notifyTaskCompleted(projectPath).
But the task was never submitted to the pool, so decrementing activeTasks
under-represents real load and can let drainQueue dispatch an extra
queued task beyond per-agent concurrency.

Fix: extracted handleTaskSkippedByPreCheck — emits task:completed to
client + project room and calls moveToCompleted, but skips
notifyTaskCompleted AND lifecycle hooks (also resolves RyanNg #5 which
flagged the lifecycle-hook side of the same call site). Three new
regression tests assert the pool counter and onTaskCompleted hooks are
not touched on skip, while the project broadcast still fires.

P2 (Codex) — duplicate review entries on late dream failures
dream-executor's success path runs createReviewEntries (step 6b), then
dreamStateService.update (step 7). If step 7 throws, control jumps to
catch which re-invokes createReviewEntries on the same allOperations,
producing duplicate pending review entries.

Fix: reviewEntriesWritten boolean set after the success-path call;
catch-path call gated on !reviewEntriesWritten in addition to the
existing allOperations.length > 0 invariant. One new regression test
forces step 7 to throw after step 6b succeeds and asserts
createReviewEntries is invoked exactly once.

Doc / test improvements
- dream-state-service.ts: comment on stateMutexes' monotonic growth
  bound (#1); JSDoc on write() cross-referencing update() to warn
  against bypassing the mutex (#2).
- consolidate.ts: JSDoc on loadAndClearPendingMerges explaining the
  intentional two-phase access pattern (unguarded read for hints +
  mutex-guarded update for clear) and the fail-open behavior when the
  initial read throws (#3, #4).
- consolidate.test.ts: assert update.calledOnce instead of
  write.calledOnce — surfaces intent (the contract) rather than the
  stub's implementation detail (#8).

Verification
- Lint: 0 errors (perfectionist/sort-classes flagged class-member order
  during audit; resolved by repositioning handleTaskSkippedByPreCheck
  alphabetically between handleTaskError and handleTaskStarted).
- Typecheck: clean.
- Unit tests: 6238 passing, 16 pre-existing pending, 0 failing.
- /brv-code-quality skill: passed all 6 audit gates.
fix: [ENG-2126] address 4 dream spec-violation gaps post-merge
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 16, 2026

Claude finished @danhdoan's task in 3m 59s —— View job


PR Review: Proj/dreaming

Tasks

  • Gather context and understand PR scope
  • Review core dream infrastructure (state, lock, log)
  • Review operations (consolidate, synthesize, prune)
  • Review executor and trigger
  • Review CLI command and daemon wiring
  • Review test coverage
  • Post inline comments and final summary

Overall Assessment: 4/5

This is a well-engineered, well-tested feature. The architecture is sound — the four-gate eligibility model, the dual-write pattern for surfacing dream ops into brv review pending, and the PID-based lock with rollback-on-error are all correctly designed. The atomic tmp→rename write pattern is used consistently. Test coverage is extensive (12 test files, 6169 passing). Outside-in structure is followed throughout.


Critical Issue

writePendingMerge bypasses the per-file mutex (prune.ts:406)

PruneDeps.dreamStateService only exposes read() and write() — not update(). The function performs read() → mutate → write() without the mutex, which can silently overwrite a concurrent incrementCurationCount() from a curate task. The DreamStateService.write() doc explicitly warns about this pattern. The fix is to add update() to PruneDeps.dreamStateService and replace the RMW pair with a single update() call containing the dedup logic inside the updater. See inline comment for the suggested rewrite.


Other Issues

Severity File Issue
suggestion dream-undo.ts:120 write() used directly (cross-process undo — OK, but merits a comment explaining why)
nitpick dream-undo.ts:36 DreamUndoResult uses interface — should be type per CLAUDE.md (data-only shape)
suggestion parse-dream-response.ts:14 Strategy 1 only matches ```json fences; unlabeled ``` fences fall through to Strategy 2 which can misfire on multi-JSON responses
tracking dream-executor.ts:305 errors always 0 in computeSummary, making the summary.errors > 0 branch in formatResult unreachable (acknowledged in PR risks — noting here for follow-up)
tracking synthesize.ts:53 No payload cap on inlined _index.md content (acknowledged in PR risks)
nitpick synthesize.ts:228 Path traversal guard uses startsWith() instead of the shared isDescendantOf() used elsewhere
observation dream-log-store.ts:87 Filtered list reads all entries before applying limit — harmless given maxEntries=50 cap

Strengths

  • Concurrency model is well-reasoned. The two-phase loadAndClearPendingMerges design (unguarded read for hints, mutex-guarded update() for clear) is explicitly documented with the right rationale.
  • Lock lifecycle is correct. release() on success, rollback(priorMtime) on error — the time gate can't be fooled by a failed dream.
  • getById path traversal guard in DreamLogStore (ID_PATTERN.test(id)) is a clean defense against crafted IDs.
  • Partial run preservation. allOperations is hoisted and pushed incrementally so a timeout or error mid-dream still captures completed work in the audit log and exposes it to --undo.
  • reviewEntriesWritten flag prevents duplicate review entries when step 7 throws after step 6b succeeds — a subtle correctness detail handled correctly.
  • Test coverage for the four-gate eligibility, atomic RMW, stale-lock detection, cross-cycle pendingMerges, and undo reversal for all operation types is comprehensive.

Comment thread src/server/infra/dream/operations/prune.ts Outdated
Comment thread src/server/infra/dream/dream-undo.ts
Comment thread src/server/infra/dream/dream-undo.ts Outdated
Comment thread src/server/infra/executor/dream-executor.ts
Comment thread src/server/infra/dream/parse-dream-response.ts Outdated
Comment thread src/server/infra/dream/operations/synthesize.ts
Comment thread src/server/infra/dream/operations/synthesize.ts
Comment thread src/server/infra/dream/dream-log-store.ts
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 16, 2026

Claude finished @danhdoan's task in 1m 18s —— View job


PR Review (Follow-up): Proj/dreaming

Tasks

  • Understand trigger context (PR synchronize — new commits pushed addressing review feedback)
  • Diff recent commits to identify what changed since last review
  • Verify critical bug fix: writePendingMerge mutex bypass
  • Check other review items were addressed
  • Post updated review summary

Follow-up Review: All Addressed Items Verified ✓

Commit ea45b0a ("fix: resolve Review comments") addressed every actionable item from the first review. Here's a verification pass:


Critical Bug Fix — Verified ✓

writePendingMerge mutex bypass (prune.ts:406)

Fixed correctly. PruneDeps.dreamStateService now exposes update(), and writePendingMerge was rewritten to perform the dedup check inside the updater callback — matching the reviewer's suggested approach exactly. The atomic read-modify-write is now protected by the file mutex.

// Before (racy RMW pair):
const dreamState = await deps.dreamStateService.read()
// ...mutate...
await deps.dreamStateService.write({...dreamState, pendingMerges})

// After (correct atomic update):
await deps.dreamStateService.update((state) => {
  const alreadySuggested = ...
  if (alreadySuggested) return state
  return { ...state, pendingMerges: [...pendingMerges, {...}] }
})

Test stubs in prune.test.ts were updated to exercise the update() path — including the dedup case, which now correctly passes the pre-populated state into the updater and asserts the result is unchanged.


Other Issues — All Resolved ✓

Original Issue Status
DreamUndoResult uses interface instead of type Fixed — now export type DreamUndoResult = {...}
write() in dream-undo.ts:125 lacks explanation Fixed — clear comment added explaining cross-process undo rationale and when update() would be needed
parse-dream-response.ts Strategy 1 misses unlabeled fences Fixed — regex updated to /```(?:json)?\s*([\s\S]*?)```/
synthesize.ts path traversal used startsWith() instead of shared isDescendantOf() Fixed — now uses isDescendantOf(absPath, contextTreeDir) with correct import

Remaining Tracking Items (acknowledged in PR risks, not blocking)

Item Status
dream-executor.ts:305errors always 0, summary.errors > 0 branch unreachable Tracked follow-up — no change needed for this PR
synthesize.ts:53 — No payload cap on inlined _index.md content Tracked follow-up — no change needed for this PR
dream-log-store.ts:87 — Reads all entries before applying limit when filter is active Harmless given maxEntries=50 cap — no change needed

Overall Assessment: 5/5

All critical and actionable issues from the first review are correctly resolved. The fix for writePendingMerge follows the suggested rewrite precisely, the test coverage was extended to verify the update() path and dedup invariant, and the stylistic nitpicks are clean. The two acknowledged follow-up items are correctly scoped to future PRs.

This PR is ready to merge.

Copy link
Copy Markdown
Collaborator

@bao-byterover bao-byterover left a comment

Choose a reason for hiding this comment

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

LGTM

@danhdoan danhdoan merged commit 4409b47 into main Apr 16, 2026
6 checks passed
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.

4 participants