Conversation
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
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
|
Claude finished @danhdoan's task in 3m 59s —— View job PR Review: Proj/dreamingTasks
Overall Assessment: 4/5This 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 Critical Issue
Other Issues
Strengths
|
fix: resolve Review comments
|
Claude finished @danhdoan's task in 1m 18s —— View job PR Review (Follow-up): Proj/dreamingTasks
Follow-up Review: All Addressed Items Verified ✓Commit Critical Bug Fix — Verified ✓
Fixed correctly. // 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 Other Issues — All Resolved ✓
Remaining Tracking Items (acknowledged in PR risks, not blocking)
Overall Assessment: 5/5All critical and actionable issues from the first review are correctly resolved. The fix for This PR is ready to merge. |
Summary
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
Scope (select all touched areas)
Linked issues
--detachflag)Root cause (bug fixes only)
N/A - new feature.
Test plan
local-auto-test/dreaming/)test/unit/infra/dream/dream-state-schema.test.tstest/unit/infra/dream/dream-log-schema.test.tstest/unit/infra/dream/dream-state-service.test.tstest/unit/infra/dream/dream-lock-service.test.tstest/unit/infra/dream/dream-log-store.test.tstest/unit/infra/dream/dream-trigger.test.tstest/unit/infra/dream/parse-dream-response.test.tstest/unit/infra/dream/dream-undo.test.tstest/unit/infra/dream/operations/consolidate.test.tstest/unit/infra/dream/operations/synthesize.test.tstest/unit/infra/dream/operations/prune.test.tstest/unit/infra/executor/dream-executor.test.tspendingMergesconsumption (prune writes → next consolidate reads)needsReview: truesurface inbrv review pending)brv-server.tsbefore killing idle agenteach 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.
activity + queue + lock).
brv review pendingnow includes dream-generated operations flagged for review..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 historyEvidence
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 mergedforce-synthesize: three domains mentioningFlushCoordinator→ SYNTHESIZE operationwrites a
type: synthesisfile to the correct domainforce-archive: draft withimportance: 10and "TODO... safe to delete" body → ARCHIVEoperation moves the file to
_archived/with stub + full preservedAuto-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
npm test)npm run lint)tsc -b)npm run build)--help)mainRisks and mitigations
--detachbehavior from idle trigger). Operations requiring review (needsReview: true) surface inbrv review pendingbefore taking effect. User sees pending reviews and decides.previousTextsin the dream log forbrv dream --undoreversal. (2) All destructive operations require review before being applied (review backup store). (3) Outercatch {}ensures partial failures don't abort the whole dream.capPayloadSize()enforcesMAX_PAYLOAD_CHARS = 200_000with changed-files-first eviction._index.mdcontent - relies on summaries being small. Tracked as a follow-up to add a cap similar to consolidate's.catch {}blocks hide real errors (e.g., LLM auth failures). Dream log showserrors: 0even when every LLM call failed.summary.errorsshould reflect caught failures so users can see when LLM calls fail.