Skip to content

refactor: [ENG-2884] remove legacy LLM-driven brv dream path#689

Merged
danhdoan merged 9 commits into
proj/byterover-tool-modefrom
refactor/ENG-2884
May 22, 2026
Merged

refactor: [ENG-2884] remove legacy LLM-driven brv dream path#689
danhdoan merged 9 commits into
proj/byterover-tool-modefrom
refactor/ENG-2884

Conversation

@cuongdo-byterover
Copy link
Copy Markdown
Collaborator

@cuongdo-byterover cuongdo-byterover commented May 21, 2026

Tool-mode subcommands (brv dream scan / finalize / undo / sessions / cancel) cover the same workflow deterministically — daemon enumerates candidates, agent decides via brv curate, daemon archives via finalize. No provider required. With tool-mode as the project default the legacy LLM-driven entry surface is orphaned, so remove it cleanly.

brv dream (no subcommand) is now a thin topic root that prints a migration hint. --force, --timeout, --undo on the topic root are gone; users hit brv dream undo instead. The buildUndoDeps export stays here since the undo subcommand imports it.

Scope expansion (5 polish commits on top of the original refactor)

After the legacy removal landed, a 12-scenario regression test against the surviving tool-mode surface surfaced 6 pre-existing P1/P2 defects. Without the legacy escape hatch, consumer agents are now forced through these rough edges, so they ship in the same PR rather than as a follow-up.

Commit What it does
7a397ba0 strip trailing .html in topicPathToFilePath Writer no longer produces *.html.html when the agent puts .html in <bv-topic path=>. Closes a silent-data-corruption window in the documented dream→curate merge workflow: dream scan emits candidate paths with .html; the agent following the docs literally would have created a stale survivor while archiving the loser.
f5566acd search-service refreshIndex + combineWith; capture pre-archive mtime/signals for bit-exact undo (a) ISearchKnowledgeService gains refreshIndex() so scanDreamCandidates bypasses the 5s TTL fast-path — first scan after seed now returns full results, no warm-up needed. (b) SearchKnowledgeOptions gains combineWith?: 'AND' | 'auto' | 'OR'; dream pair-discovery passes 'OR' so multi-word distinctive titles (e.g. "Redis Caching Layer") surface cross-pairs. (c) finalizeDreamSession captures pre-archive mtimeMs + sidecar RuntimeSignals; DreamLogSchema PRUNE op carries them as optional fields; undoPrune restores via utimes() + runtimeSignalStore.set(). Undo is now bit-exact for content + mtime + signals — pruned topics that re-qualify re-surface on the next scan.
2e138a6c disclose v1-stub status on dream sessions / dream cancel Both commands are intentional v1 placeholders (daemon doesn't persist session state). The previous surface advertised them as functional. Descriptions now prefix [v1 stub], JSON envelopes carry a note field disclosing stateless/no-op behavior, topic-root listing flags both. Honest surface without doing the real persistence work, which stays a separate ticket.
53004fd7 expand SKILL.md §7 with sample envelopes, thresholds, worked example Doc-only. Adds sample dream scan + dream finalize JSON envelopes, prune-signal thresholds inline (importance < 35, draft >60d / validated >120d), end-to-end "prune the stalest topic" walkthrough, path-attribute warning, and updates the undo description for the new bit-exact behavior. Companion sync-hint comment in prune-candidates.ts so future threshold changes can't drift the docs.
8c7c1cc3 await in-flight build in refreshIndex (race fix on f5566acd) Codex review caught a race: clearing state.buildingPromise while a build was running let the orphan build write back to state.cachedIndex after invalidation, reintroducing TTL-stale behavior under concurrent brv search + dream-scan. refreshIndex() now awaits any in-flight build before clearing — by the time it returns, no orphan publisher can write back.

Removed (original scope)

  • src/oclif/commands/dream.ts LLM-driven run() body (was a 200-line block dispatching dream tasks through the daemon). Topic root keeps the buildUndoDeps helper for the undo subcommand.
  • case 'dream' in agent-process.ts — daemon-side LLM dispatch.
  • AgentIdleTimeoutPolicy.onAgentIdle dream-eligibility branch and preDispatchCheck for task.type === 'dream' in brv-server.ts. The auto-dispatch had no provider-config guard, so on tool-mode projects it would silently try to dispatch and fail — removal closes that latent issue. Idle agents are now just cleaned up.
  • src/server/infra/executor/dream-executor.ts + its test.
  • src/server/infra/dream/dream-trigger.ts + its test.
  • src/server/infra/dream/operations/{consolidate,synthesize,prune}.ts + their tests. Tool-mode subcommands use src/server/infra/dream/tool-mode/* instead.
  • src/server/infra/dream/parse-dream-response.ts + dream-response-schemas.ts + their tests — no consumer after the operations files are gone.
  • src/oclif/lib/timeout-deprecation.ts + its test — only consumer was the legacy dream command.

Trimmed

  • test/commands/dream.test.ts rewritten as a 1-case smoke that asserts the new topic-root behaviour (prints the subcommand listing), then extended in 2e138a6c with 4 tests for v1-stub disclosure (descriptions + JSON note).
  • test/unit/infra/runtime-signals/sidecar-failure-logging.test.ts dropped the dream operations describe block (consolidate / prune sidecar warnings). Other describe blocks (curate-tool, archive service, manifest, search, commit-6 wiring) keep their coverage.

Kept

  • src/server/infra/dream/dream-undo.ts, dream-state-service.ts, dream-lock-service.ts, dream-log-store.ts — LLM-agnostic services reused by the tool-mode pipeline. dream-undo.ts extended in f5566acd with optional runtimeSignalStore dep and mtime/signal restore in undoPrune.
  • src/server/infra/dream/tool-mode/*.ts — the subcommand pipeline. dream-session.ts extended in f5566acd (refreshIndex call + mtime/signal capture in finalize).
  • src/oclif/commands/dream/{scan,finalize,sessions,cancel,undo}.ts. sessions.ts and cancel.ts extended in 2e138a6c.

Verified

  • npm run typecheck: clean
  • npm run lint -- --quiet: 0 errors
  • npm run build: clean
  • npm test: 8379 passing, 16 pending, 0 failing
  • 12-scenario regression replay against the patched binary, fresh sub-agents per scenario: 11 PASS + 1 expected-PARTIAL (the partial is schema-gate wire enforcement, deferred per plan — type: 'dream' IS rejected; force / trigger: 'agent-idle' are silently stripped because the surrounding schemas aren't .parse'd at runtime today). Every previously-flagged P1/P2 defect verified patched.

Migration

  • brv dream (no subcommand) now prints Use a subcommand: brv dream {scan|finalize|undo|sessions [v1 stub]|cancel [v1 stub]} and exits 0.
  • Legacy topic-root flags (--force / --timeout / --undo) removed. Use the subcommands.
  • brv dream sessions / brv dream cancel are v1 stubs (disclosed in help text and JSON output). Real persistence is a separate ticket.
  • brv dream undo now restores file content + pre-archive mtime + sidecar runtime signals (bit-exact). Pruned topics that re-qualify re-surface on the next scan.
  • Background dream is no longer daemon-scheduled; the agent decides when to run brv dream scan (e.g. post-curate burst, on session start). The v2 model is agent-driven cadence.

What did NOT change (scope boundary)

Deliberately deferred to follow-up tickets:

  • Real session persistence for dream sessions / dream cancel (v1 stubs are the bridge).
  • Wire-level schema rejection of removed task fields (force, trigger: 'agent-idle') — fields are behaviorally inert but TaskCreateRequestSchema / TaskExecuteSchema aren't .parse'd at runtime today.
  • Several LOW / INFO items: scan-mutates-signals via search-read side effect; dream undo exits 0 on no-op (JSON envelope's success: false is the current scripted-caller signal); dead restoredArchives field in undo response; empty archive dir shells after undo; server-injected createdat / updatedat despite prompt wording.

Type of change

  • Refactor (no behavior change) — legacy LLM-driven path removal
  • Bug fix — 5 polish fixes for tool-mode dream
  • Documentation — SKILL.md §7 expansion

Scope

  • Agent / Tools (search-service interface)
  • Server / Daemon (dream tool-mode pipeline, schemas)
  • CLI Commands (oclif) — dream topic + subcommands

Test plan

  • Unit test — 17 new tests across html-writer, dream-session, dream-undo, dream commands, search-service refresh
  • Integration — 12-scenario regression replay with fresh sub-agents per scenario
  • Manual verification — interactive CLI for v1-stub disclosure (help text + JSON envelope)

User-visible changes

  • brv dream topic root now lists subcommands with [v1 stub] markers on sessions/cancel.
  • brv dream sessions --help and brv dream cancel --help descriptions begin with [v1 stub] and document v1 stateless/no-op behavior.
  • brv dream sessions --format json and brv dream cancel --format json carry a note field disclosing v1 status.
  • brv dream undo restores bit-exact mtime + sidecar signals (previously only content was restored; mtime reset to wall-clock and signals to defaults).
  • brv dream scan first call after a fresh seed now returns full candidate set (previously empty on cold-cache, only warmed up on a second call).

Risks and mitigations

  • Risk: DreamLogSchema PRUNE op gained optional previousSignals + previousMtimes fields.
    • Mitigation: fields are optional. Older dream-log entries written before this change still undo cleanly via fallback to the previous behavior (file restored, mtime resets to now, signals default).
  • Risk: SearchKnowledgeOptions.combineWith and ISearchKnowledgeService.refreshIndex() change the search-service contract.
    • Mitigation: combineWith default 'auto' preserves the historical AND-first-with-OR-fallback behavior; only dream pair-discovery opts in to 'OR'. refreshIndex() is additive; no caller is required to invoke it.
  • Risk: DreamUndoDeps.runtimeSignalStore newly required for signal restore.
    • Mitigation: dep is optional. undoPrune no-ops the signal restore when the dep isn't injected. buildUndoDeps in dream.ts wires it through; the only callers that need updating are external embedders.

Tool-mode subcommands (`brv dream scan / finalize / undo / sessions /
cancel`, shipped in ENG-2858) cover the same workflow deterministically
— daemon enumerates candidates, agent decides via `brv curate`, daemon
archives via `finalize`. No provider required. With tool-mode as the
project default the legacy LLM-driven entry surface is orphaned, so
remove it cleanly.

`brv dream` (no subcommand) is now a thin topic root that prints a
migration hint. `--force`, `--timeout`, `--undo` on the topic root are
gone; users hit `brv dream undo` instead. The `buildUndoDeps` export
stays here since the `undo` subcommand imports it.

Removed
- `src/oclif/commands/dream.ts` LLM-driven `run()` body (was a 200-line
  block dispatching dream tasks through the daemon). Topic root keeps
  the `buildUndoDeps` helper for the `undo` subcommand.
- `case 'dream'` in `agent-process.ts` — daemon-side LLM dispatch.
- `AgentIdleTimeoutPolicy.onAgentIdle` dream-eligibility branch and
  `preDispatchCheck` for `task.type === 'dream'` in `brv-server.ts`.
  The auto-dispatch had no provider-config guard, so on tool-mode
  projects it would silently try to dispatch and fail — removal closes
  that latent issue. Idle agents are now just cleaned up.
- `src/server/infra/executor/dream-executor.ts` + its test.
- `src/server/infra/dream/dream-trigger.ts` + its test.
- `src/server/infra/dream/operations/{consolidate,synthesize,prune}.ts`
  + their tests. Tool-mode subcommands use `src/server/infra/dream/
  tool-mode/*` instead.
- `src/server/infra/dream/parse-dream-response.ts` +
  `dream-response-schemas.ts` + their tests — no consumer after the
  operations files are gone.
- `src/oclif/lib/timeout-deprecation.ts` + its test — only consumer
  was the legacy dream command.

Trimmed
- `test/commands/dream.test.ts` rewritten as a 1-case smoke that
  asserts the new topic-root behaviour (prints the subcommand listing).
- `test/unit/infra/runtime-signals/sidecar-failure-logging.test.ts`
  dropped the `dream operations` describe block (consolidate / prune
  sidecar warnings). Other describe blocks (curate-tool, archive
  service, manifest, search, commit-6 wiring) keep their coverage.

Kept
- `src/server/infra/dream/dream-undo.ts`, `dream-state-service.ts`,
  `dream-lock-service.ts`, `dream-log-store.ts` — LLM-agnostic services
  reused by the tool-mode pipeline.
- `src/server/infra/dream/tool-mode/*.ts` — the subcommand pipeline.
- `src/oclif/commands/dream/{scan,finalize,sessions,cancel,undo}.ts`.

Verified
- npm run typecheck: clean
- npm run lint: 0 errors (256 pre-existing warnings)
- npm test: 8337 passing, 16 pending, 0 failing (was 8521 — net -184
  from the dead test deletions; matches the deleted file inventory).

Migration
- `brv dream` (no subcommand) now prints "Use a subcommand: brv dream
  {scan|finalize|undo|sessions|cancel}" and exits 0.
- `brv dream --force` / `--timeout` / `--undo` removed. Use the
  subcommands.
- Background dream is no longer daemon-scheduled; the agent decides
  when to run `brv dream scan` (e.g. post-curate burst, on session
  start). The v2 model is agent-driven cadence.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

Claude finished @cuongdo-byterover's task in 5m 32s —— View job


Claude PR Review — ENG-2884 (remove legacy LLM-driven brv dream)

  • Read PR description and changed-file inventory
  • Diff vs origin/proj/byterover-tool-mode
  • Topic-root command rewrite (dream.ts) + removed-flags.ts
  • Daemon-side changes (agent-process.ts, brv-server.ts)
  • Audit for orphaned imports / dead references
  • Test updates (dream.test.ts, sidecar-failure-logging.test.ts, removed-flags.test.ts)
  • Post inline comments + summary

Summary

Score: 4 / 5 — A clean, well-scoped removal. The diff matches the PR description file-for-file (net ‑6,603 lines), the deleted-tests footprint matches the deleted-impl footprint, and the few survivors (DreamLogStore, DreamStateService, dream-undo, the tool-mode subcommand pipeline) are exactly the LLM-agnostic services the new dream-finalize / dream-scan task types still need. brv-server.ts correctly drops both the onAgentIdle dream-eligibility branch and the preDispatchCheck task.type === 'dream' plumbing; the latent "dispatch-without-provider-gate" bug called out in the PR description is genuinely closed by this removal. The smoke test in test/commands/dream.test.ts is appropriately minimal for a topic root.

What knocks it off 5/5 is one should-fix and one should-fix loose end (full detail in inline comments):

  1. DREAM_REMOVED_FLAGS is exported + unit-tested but never wired. dream.ts does not call findRemovedFlagMessage(...) the way curate/index.ts:81 and query.ts:67 do. A user running brv dream --timeout 30 today gets the topic-root hint, never the per-flag migration text. Either wire it up or delete the constant — current state is the worst of both. (See inline on removed-flags.ts:109-120.)
  2. type === 'dream' is still in the postWorkRegistry gate at agent-process.ts:603, even though the case 'dream' is gone from the switch. If any straggler 'dream' task hits the daemon (older client, stale queue, stray internal producer), it now silently completes with result = '' instead of failing loudly — and TaskTypeSchema (schemas.ts:553) still accepts 'dream', so the daemon won't reject it at the boundary either. Best fix: drop 'dream' from TaskTypeSchema, then the compiler forces the gate fix and any straggler producer fails Zod validation. The force field and 'agent-idle' variant of trigger in TaskExecuteSchema are similarly orphaned. (See inline on agent-process.ts:600-608.)

Plus a few cosmetic nits:

  • review.ts:17 still claims "brv dream' no longer surfaces its own needsReview operations as pending reviews" — the new tool-mode dream-finalize writes needsReview: false unconditionally, so the bullet describes a guarantee that's now structural, not toggleable. (Inline comment.)
  • Several comments still reference dream-executor (curate-session.ts:398, dream-state-service.ts:13,124, agent-process.ts:761, two curate-html-log tests, dream-state-service.test.ts:186). Search-and-replace pass to drop the dead reference would be cheap. (Inline comment on the agent-process one.)
  • Topic-root exits 0 — brv dream && echo ok will silently print "ok". Worth a conscious decision to either keep that or use this.error(..., {exit: 1}). (Inline comment.)
  • test/unit/infra/process/task-router.test.ts still uses type: 'dream' as the test fixture for the generic preDispatchCheck plumbing (lines 520-654). Valid as long as 'dream' stays in the schema; if you do remove it per Feat/refactor structure #2, swap to 'curate' here.

Praise: keeping buildUndoDeps co-located on the topic root rather than splintering into a one-import helper module is the right call. The doc comment explaining why pre-empts a future refactor instinct.

Verification stance: Spot-checked grep across the repo — no orphaned imports of dream-trigger, dream-executor, dream-response-schemas, parse-dream-response, dream/operations/, or timeout-deprecation. Only stale references are comment-string mentions of dream-executor. Couldn't run npm test (no node_modules in fresh checkout) but the PR description's "8337 passing, 16 pending, 0 failing (was 8521, net −184)" delta lines up exactly with the deleted-test row counts I tallied from the diff.
· branch refactor/ENG-2884

Comment thread src/oclif/lib/removed-flags.ts
Comment thread src/oclif/commands/dream.ts Outdated
Comment thread src/oclif/commands/dream.ts
…lose schema/post-work straggler gates

Two should-fix items + comment cleanup from the reviewer:

1. DREAM_REMOVED_FLAGS was exported and unit-tested but never consumed.
   `brv dream --timeout 30` printed the topic-root hint instead of the
   migration message that names --timeout. Wired
   `findRemovedFlagMessage(this.argv, DREAM_REMOVED_FLAGS)` into the
   topic root with the same JSON-envelope / this.error branching the
   curate and query commands use.

2. Dropped 'dream' from the transport schemas + post-work gate so any
   straggler `type: 'dream'` task that still hits the daemon (older
   client, stale queue, internal stray producer) fails at the schema
   boundary instead of silently completing with result = ''.

   - TaskTypeSchema + TaskExecuteSchema type enums: 'dream' removed.
   - TaskExecuteSchema.force field: removed (dream was the only consumer).
   - TaskExecuteSchema.trigger enum: 'agent-idle' removed (idle-trigger
     dispatch is gone). 'cli' and 'manual' remain.
   - TaskCreateRequestSchema.force field: removed.
   - agent-process.ts post-work gate: dropped `type === 'dream'`.
   - task-router.ts: dropped the orphan `data.force` plumbing.

Cosmetic
- task-router.test.ts: swapped the `type: 'dream'` fixtures (used to
  exercise the generic preDispatchCheck plumbing) to `type: 'curate'`.
  The tests stay — they cover the gate mechanism, not dream-specific
  logic.
- review.ts description: dropped the stale `brv dream needsReview`
  bullet. Tool-mode dream-finalize writes `needsReview: false`
  unconditionally; the toggle has no dream-side effect to describe.
- Cleared stale `dream-executor` comment refs in curate-session.ts,
  dream-state-service.ts, agent-process.ts, and two test files.
- Topic-root exit code: kept exit 0 (matches oclif topic-root default)
  with a clarifying code comment noting the deliberate choice.

Kept
- `'agent-idle'` in dream-log-schema.ts — backwards-read compat for
  existing dream-log entries on disk. No new entries will carry that
  trigger; existing files still parse.

Verified
- npm run typecheck clean
- npm run lint 0 errors (256 pre-existing warnings)
- npm test: 8337 passing, 16 pending, 0 failing
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

Claude finished @cuongdo-byterover's task in 3m 52s —— View job


Claude PR Review — ENG-2884 (synchronize)

  • Diff vs origin/proj/byterover-tool-mode
  • Verify previous review feedback was addressed (DREAM_REMOVED_FLAGS wiring, schema/post-work straggler gates)
  • Re-audit for orphaned references (dream-executor mentions, 'dream' task type)
  • Audit review.ts doc comment, exit-code on topic root
  • Post inline comments + summary

Summary

Score: 4.5 / 5 — Both should-fix items from the prior review are cleanly addressed and the cosmetic loose ends (review.ts doc comment, stale dream-executor mentions, exit-code intent) are all closed. The only follow-ups worth flagging are nits.

Previous review items: status

# Item Status
1 Wire DREAM_REMOVED_FLAGS into Dream.run() dream.ts:86-94findRemovedFlagMessage(this.argv, …) mirrors curate / query precedent, JSON-envelope branch included
2 Drop 'dream' from TaskTypeSchema + postWorkRegistry gate schemas.ts:547-556 (TaskTypeSchema), schemas.ts:446 (TaskExecuteSchema inline), agent-process.ts:600-607 (gate). data.force removed from task-router.ts. force + 'agent-idle' trigger removed from TaskExecuteSchema / TaskCreateRequestSchema
3 review.ts stale brv dream needsReview bullet ✅ removed at review.ts:17
4 Stale dream-executor comments ✅ all five locations cleaned (curate-session.ts, dream-state-service.ts, agent-process.ts, two test files)
5 Topic-root exit code ✅ left as exit-0 with a deliberate comment at dream.ts:96-101 explaining the choice
6 task-router.test.ts type: 'dream' fixtures ✅ swapped to 'curate' for the generic preDispatchCheck plumbing

Praise — the this.argv choice over process.argv.slice(2) is the better call for testability and the doc comment calls it out explicitly. dream-log-schema.ts:67 keeps 'agent-idle' in the read-side enum for backwards-compat with existing on-disk entries while new entries can no longer be tagged that way — exactly the right scope for a backwards-compat exception (called out in the commit message too).

Inline comments (both nits)

  1. schemas.ts:442-449 (SSOT drift)TaskExecuteSchema.type still uses an inline z.enum([...]) duplicating TaskTypeSchema, but the doc block on TaskTypeSchema (lines 542-546) claims the request schema references it. The commit message even says "TaskTypeSchema + TaskExecuteSchema type enums: 'dream' removed" — i.e. the divergence cost was paid in this PR. Mechanical fix needs a forward-declaration to avoid a Zod TDZ. Not blocking.
  2. test/commands/dream.test.ts (coverage gap) — the new flag-rejection branch in dream.ts:86-94 has no integration coverage. The smoke test only exercises the no-args path. If the wire-up gets dropped in a future refactor, npm test stays green and we silently regress to the exact state the previous reviewer flagged. Added a 2-case suggestion (no-args + --timeout 30 rejection) inline.

Verification stance — spot-checked grep across the repo: no orphaned dream-executor / dream-trigger / dream-response-schemas / parse-dream-response / dream/operations/ / timeout-deprecation imports remain. Surviving 'dream' string occurrences (dream-undo.ts:337, dream.ts:89 JSON command) are correctly not task-type references. Did not run npm test locally (no node_modules in fresh checkout), trusting the PR-description test counts (8337 passing, 16 pending, 0 failing).
· branch refactor/ENG-2884

Comment thread src/server/core/domain/transport/schemas.ts
Comment thread test/commands/dream.test.ts
Comment thread src/oclif/commands/dream.ts
…nt path normalization)

The dream-scan output emits candidate paths with .html (e.g.
auth/jwt.html) and the documented dream→curate merge workflow tells
the agent to write the survivor at that path. The writer previously
appended .html unconditionally, producing auth/jwt.html.html on disk
while reporting status: done and bypassing the path-exists guard —
silent data corruption during merge: the survivor's merged content
landed in a stale file while the agent archived the loser thinking
the merge had succeeded.

topicPathToFilePath now strips a trailing .html before appending.
Both path="x/y" (bare convention) and path="x/y.html" (the form
dream-scan emits) collapse to the same on-disk file at x/y.html and
trigger the path-exists guard identically.

4 new tests in html-writer.test.ts cover: .html-suffixed path writes
to single-extension file; path-exists fires across both forms in
both orderings; --overwrite leaves only one file.
… capture pre-archive mtime/signals so dream undo is bit-exact

Three coupled changes that touch the search-service contract and the
tool-mode dream finalize/undo path.

Search service contract:
- ISearchKnowledgeService gains refreshIndex(): invalidates the cached
  MiniSearch index so the next search() call rebuilds from disk
  regardless of TTL. Required because the service's 5s TTL fast-path
  would otherwise serve a cached index that pre-dates just-loaded
  topics, surfacing zero candidates on a fresh scan.
- SearchKnowledgeOptions gains combineWith?: 'AND' | 'auto' | 'OR'.
  Default 'auto' preserves the historical AND-first-with-OR-fallback
  behavior. Internal SearchOptions references the public option's type
  to keep the two declarations from drifting if a new value is added.
- bm25-pair-discovery passes combineWith: 'OR'. Multi-word distinctive
  titles (e.g. "Redis Caching Layer") AND-combine to terms only the
  source itself contains; OR-mode lets cross-pairs surface based on
  any-term overlap.

Dream finalize / undo signal+mtime restore:
- finalizeDreamSession captures each archived file's mtimeMs (via stat)
  and current RuntimeSignals (via runtimeSignalStore.get) before the
  rename, and returns them on the result alongside previousTexts.
- DreamLogSchema PRUNE op gains optional previousSignals +
  previousMtimes (backward-compat — older logs still undo via fallback).
- agent-process dream-finalize branch writes the new metadata into
  each PRUNE op.
- DreamUndoDeps gains optional runtimeSignalStore. undoPrune restores
  mtime via utimes() and signals via runtimeSignalStore.set() when
  the PRUNE op carries the new fields; falls back gracefully when not.
- buildUndoDeps threads runtimeSignalStore through.

scanDreamCandidates now invokes searchService.refreshIndex() after
loadToolModeTopics and before the parallel candidate-generator block,
so the first scan after seed produces full results.

Tests cover: refreshIndex called pre-discovery; combineWith: 'OR'
passed to search; mtime+signal capture on finalize; mtime+signal
restoration on undo; backward-compat fallback path; mock updates for
the extended ISearchKnowledgeService in unrelated test files.
Both subcommands are intentional v1 placeholders — the daemon doesn't
persist session state, so dream sessions always returns [] and dream
cancel is a no-op. The previous surface advertised them as if they
were functional: descriptions said 'List active...' / 'Discard a...',
JSON envelopes returned success-looking shapes ({sessions: [], status:
ok} / {status: cancelled}), and machine-readable consumers that
branched on the response had no way to know the daemon was never
consulted.

This is a cheap-honesty fix, not a real persistence implementation.
Real persistence is a separate ticket.

Changes:
- DreamSessions.description prefixed with '[v1 stub]' and now names
  the stateless behavior inline.
- DreamCancel.description prefixed with '[v1 stub]' and now names the
  no-op behavior inline.
- Both JSON envelopes gain a 'note' field disclosing v1 status so
  machine consumers see the caveat at the same key they'd branch on.
- Topic-root listing in brv dream's run() body flags both subcommands
  as '[v1 stub]' in the one-liner hint.

4 tests in test/commands/dream.test.ts cover: each static description
contains 'v1 stub'; each --format json envelope's data.note discloses
stateless / no-op semantics.
…, worked example

A skill-discovery sub-agent test surfaced gaps in §7 (brv dream):
prose described the surface but no sample JSON envelopes existed,
prune-signal thresholds were left to the agent to guess, and there
was no end-to-end walkthrough — so consumers had to grep the source
to learn field shapes.

Additions to §7:
- Sample dream scan --format json envelope (sessionId + candidates
  with one example each of link / merge / prune / synthesize shapes,
  including signals + daysSinceModified for prune).
- Sample dream finalize response (archived, skipped, logId, status).
- Prune signal thresholds inline: importance < 35 → low-importance,
  draft >60d / validated >120d → stale-mtime, maturity 'core' never
  surfaced.
- 'Worked example — prune the stalest topic (no curate detour)':
  three-block scan → finalize → undo walkthrough.
- Path-attribute warning in the merge bullet so agents stripping the
  scan-emitted .html before <bv-topic path=> see the guidance.
- Updated undo description to reflect the symmetric mtime + signal
  restoration shipped in this PR — was 'mtime resets to current time';
  is 'content, original mtime, and sidecar runtime signals... all
  restored'.

Companion change in prune-candidates.ts: sync-hint comment near the
threshold constants pointing at SKILL.md §7, so future threshold
changes don't silently drift the agent-facing docs.
…566ac)

Reviewer caught a real race in the refreshIndex() shipped in f5566ac.
The orphan-builder problem:

  T0: caller A invokes search() — acquireIndex starts buildA;
      state.buildingPromise = buildA. The IIFE body is still awaiting
      fileSystem.listDirectory and the eventual buildFreshIndex.
  T1: scanDreamCandidates calls refreshIndex(). Old code cleared
      state.cachedIndex AND state.buildingPromise immediately and
      returned.
  T2: caller C invokes search() — sees both fields cleared, kicks off
      a NEW build (buildB). state.buildingPromise = buildB.
  T3: buildA finally completes and writes state.cachedIndex =
      freshIndexA (line 1016 of acquireIndex).
  T4: buildB completes and writes state.cachedIndex = freshIndexB.

If buildA happens to settle AFTER buildB (e.g. slower disk read,
larger source set), the older freshIndexA wins — reintroducing the
exact TTL-stale-data scenario Fix #2 was meant to prevent. Realistic
on a daemon with concurrent brv search / brv query / dream-scan
tasks.

Fix per reviewer's accepted shape: refreshIndex() now AWAITS any
in-flight buildingPromise before clearing cached state. By the time
the await returns, the in-flight build has either published its
result (which is then cleared) or rejected (swallowed — callers don't
care about that build's outcome, they just want a clean slate). No
orphan publisher can write back after invalidation because the
publishing point is now in the past relative to the clear.

New test file search-knowledge-service-refresh.test.ts covers:
- the contract under test (refreshIndex MUST NOT resolve while a
  build is in flight)
- idempotent behavior on cold state (no in-flight build)
- graceful clear when the in-flight build rejects

Not a follow-up sub-agent regression replay: the race manifests only
under concurrent operations on a single SearchKnowledgeService
instance, which the 12-scenario test matrix (per-scenario daemons,
serial workflow) does not exercise. The unit test at the contract
boundary is the right pin.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

Claude finished @RyanNg1403's task in 6m 38s —— View job


Claude PR Review — ENG-2884 (synchronize)

  • Sync local with origin/refactor/ENG-2884 and diff vs origin/proj/byterover-tool-mode
  • Check what changed since the previous (09:23Z) review
  • Verify prior-review follow-ups (SSOT in schemas.ts, integration test in dream.test.ts)
  • Audit new commits (refreshIndex race, combineWith, bit-exact undo, .html idempotence, v1-stub disclosure, SKILL.md §7)
  • Post inline comments + summary

Summary

Score: 4.5 / 5 — Five additional non-merge commits since the previous review (7a397ba8c7c1cc) move beyond "address review feedback" and into real follow-up hardening: the orphan-builder race in SearchKnowledgeService.refreshIndex, bit-exact dream undo (mtime + sidecar signals), idempotent .html path stripping in the writer, and an honest v1-stub disclosure on dream sessions / dream cancel. All four come with focused, contract-pinning unit tests. SKILL.md §7 expands the agent-facing surface (sample envelopes, prune thresholds, worked example) and the prune-candidates.ts lockstep comment ties the threshold defaults to the doc — small but right.

Status of prior-review nits

# Item Status
1 Wire DREAM_REMOVED_FLAGS into Dream.run() ✅ (closed in 7d77a8e)
2 Drop 'dream' from TaskTypeSchema + postWorkRegistry gate ✅ (closed in 7d77a8e)
3 review.ts stale brv dream needsReview bullet ✅ (closed in 7d77a8e)
4 Stale dream-executor comments ✅ (closed in 7d77a8e)
5 Topic-root exit code ✅ deliberate exit-0 with rationale (closed in 7d77a8e)
6 task-router.test.ts type: 'dream' fixtures ✅ (closed in 7d77a8e)
7 schemas.ts:446 SSOT drift (inline z.enum vs TaskTypeSchema) ⚠️ still open — see inline comment
8 dream.test.ts flag-rejection coverage gap ⚠️ still open — see inline comment

New observations from this round (all non-blocking — full detail inline)

  • Praise: refreshIndex() race fix is correct and the 3-case test file pins it well (await contract, cold-state idempotence, in-flight-rejection). Small reasoning note on the doc comment inline.
  • Praise: capturing previousMtimes + previousSignals before the rename in finalizeDreamSession is exactly the right ordering — after-rename would be lossy because the sidecar has been deleted. Schema fields are optional, so old log entries still undo.
  • Praise: topicPathToFilePath's .replace(/\.html$/i, '') closes a silent-data-loss class for the dream→curate merge handoff. Both-direction tests (x/y then x/y.html, and reverse) lock the symmetry in.
  • nit (cosmetic): agent-process.ts:866 uses previousTexts[path] ?? '' while the new previousMtimes / previousSignals lookups have no fallback. Inconsistent defensiveness; the ?? '' is actually subtly harmful because if it ever fires, the corresponding mtime/signals would be undefined and fail Zod validation on log re-read. Suggested dropping the ?? '' for uniformity.

Verification stance — grep across the repo: no orphaned dream-executor / dream-trigger / dream-response-schemas / parse-dream-response / dream/operations/ / timeout-deprecation imports remain. 'agent-idle' is correctly kept on the read-side of dream-log-schema.ts:88 for back-compat with on-disk entries. related-ref-warner.ts appearing under the writer directory is from base (PR #690) — not in scope for this PR. Did not run npm test locally (no node_modules in fresh checkout), trusting the PR-description test counts.
· branch refactor/ENG-2884

Comment thread test/commands/dream.test.ts
Comment thread src/server/core/domain/transport/schemas.ts Outdated
Comment thread src/agent/infra/tools/implementations/search-knowledge-service.ts
Comment thread src/server/infra/dream/tool-mode/dream-session.ts
Comment thread src/server/infra/daemon/agent-process.ts Outdated
Comment thread src/server/infra/render/writer/html-writer.ts
Five items from the latest codex review batch on PR #689.

- agent-process.ts: drop the dead-code `?? ''` fallback on
  previousTexts. archived[] paths are invariant-guaranteed to have
  all three fields (text + mtime + signals) populated together, so
  the fallback was already unreachable — and if it ever fired, the
  resulting `{[path]: undefined}` would violate the new PRUNE
  schema's z.record(z.string(), z.number()) for previousMtimes. All
  three lookups now agree: trust the invariant uniformly.

- search-knowledge-service.ts: extend refreshIndex docstring with a
  maintainer note explaining that the explicit `state.buildingPromise
  = undefined` after the await is defensive (acquireIndex's finally
  block already nulls it). Also documents the benign double-build
  inefficiency when a fresh acquireIndex() races into the gap between
  await-resolve and clear — not a correctness issue, just non-obvious.

- SKILL.md §7: soften the merge bullet's path-attribute warning. The
  prior "drop the trailing .html — the writer always appends it"
  instruction was load-bearing before the path-doubling fix shipped;
  now that the writer normalizes idempotently (both bare and
  .html-suffixed forms resolve to the same on-disk file), the
  instruction reduces to agent-facing cognitive load. Updated to
  "either form works; the writer normalizes" so the agent doesn't
  have to track which form scan emitted.

- dream.test.ts: add two regression tests for the DREAM_REMOVED_FLAGS
  wire-up in dream.ts's run() body. (1) text mode: --timeout 30
  rejects with this.error throwing before this.log runs; (2) json
  mode: --timeout 30 --format json emits a JSON envelope with
  success:false / data.status:'error' / data.error containing
  '--timeout'. Pins the early-exit ordering so a future refactor
  that drops the findRemovedFlagMessage(this.argv, ...) call fails
  the suite instead of silently regressing to pre-removal state.

- schemas.ts: hoist TaskTypeSchema declaration above
  TaskExecuteSchema (the schema previously sat ~100 lines below) and
  replace TaskExecuteSchema.type's inline z.enum(...) with a
  reference to TaskTypeSchema. The doc block at the original
  declaration site claimed it was the single source of truth, but
  the inline enum on TaskExecuteSchema.type silently violated that
  — the only outlier; TaskCreatedSchema.type (now line 605) and
  TaskCreateRequestSchema.type already referenced TaskTypeSchema.
  Hoist needed to avoid a Zod TDZ at module load. No behavioral
  change; schemas now share one literal enum source.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

Claude finished @RyanNg1403's task in 2m 59s —— View job


Claude PR Review — ENG-2884 (synchronize)

  • Sync local with origin/refactor/ENG-2884 and diff vs origin/proj/byterover-tool-mode
  • Check what changed since the previous (15:14Z) review
  • Verify prior-review nits (SSOT in schemas.ts, --timeout rejection test in dream.test.ts, ?? '' defensiveness, search-service docstring, SKILL.md path-attr instruction)
  • Audit new commit e3f96e8
  • Post summary

Summary

Score: 5 / 5 — One new follow-up commit since the 15:14Z review (e3f96e8 fix: [ENG-2884] address PR #689 codex review) cleanly closes every open nit from the prior round with thoughtful execution. Nothing new to flag.

Status of prior-review items

# Item Status
1 agent-process.ts:866 — drop ?? '' defensiveness on previousTexts so all three lookups agree ✅ closed — invariant at dream-session.ts:263-267 confirms archived[] paths populate text/mtime/signals together; ?? '' was already dead, and as the prior review noted, harmful if it ever fired ({[path]: undefined} would violate z.record(z.string(), z.number()) for previousMtimes)
2 search-knowledge-service.ts refreshIndex docstring — record the maintainer reasoning note about the defensive state.buildingPromise = undefined after the await and the benign double-build race ✅ closed at lines 1107-1130 — exactly the right walkthrough (acquireIndex's finally already nulls it; explicit clear is defensive; documents the benign racing-acquireIndex double-build scenario)
3 SKILL.md §7 merge bullet — soften "drop the trailing .html" instruction now that the writer normalizes idempotently ✅ closed — rewritten as "writer normalizes idempotently — both path="auth/jwt" and path="auth/jwt.html" resolve to the same on-disk file." Reduces agent-facing cognitive load now that the path-doubling fix shipped
4 test/commands/dream.test.ts — add a regression test for the DREAM_REMOVED_FLAGS wire-up so a future refactor that drops findRemovedFlagMessage(this.argv, …) can't silently regress ✅ closed at lines 48-101 — two tests added: (a) text mode asserts this.error throws with a message containing --timeout before any log call (pins early-exit ordering); (b) JSON mode asserts the JSON envelope hits stdout with success: false / status: 'error' / error containing --timeout AND log stub stays empty (pins the writeJsonResponse branch). Lock-in is bidirectional
5 schemas.ts SSOT drift — replace inline z.enum on TaskExecuteSchema.type with reference to TaskTypeSchema ✅ closed — TaskTypeSchema hoisted from line 547 → line 427 (above TaskExecuteSchema), with a doc comment explaining the hoist avoids a Zod TDZ at module load. Now three call sites (TaskExecuteSchema.type, TaskCreatedSchema.type, TaskCreateRequestSchema.type) all reference the single source. The SSOT doc comment now matches reality

Praise

  • this.argv choice in the test setup (new Dream(['--timeout', '30'], config)) sidesteps process.argv monkey-patching cleanly; the JSON-mode test additionally stubs process.stdout.write to capture the envelope without console.log pollution. Both are solid testability decisions.
  • The hoist-with-comment in schemas.ts:418-427 turns what could've been an unexplained re-org into a deliberate, signposted move — future readers won't be tempted to move it back.
  • The maintainer note on refreshIndex is admirably honest: it spells out the benign-but-non-obvious double-build edge case without claiming it's a problem, leaving future maintainers a paper trail if they ever decide it IS worth closing.

Tiny non-issue worth mentioning in passingremoved-flags.ts:111-112 doc comment says "let[s] us delete the dead timeout-deprecation.ts helper", which references a file this PR deletes. The comment will read awkwardly post-merge ("delete" tense vs. already-deleted file). Not worth a follow-up commit; will self-resolve next time the comment is touched.

Verification stance — grep confirms no orphaned imports of dream-executor / dream-trigger / dream-response-schemas / parse-dream-response / dream/operations/ / timeout-deprecation remain in src/ or test/. The lone surviving mention of timeout-deprecation.ts is the historical doc comment above. Did not run npm test locally (no node_modules in fresh checkout), trusting the PR-description test counts. No further inline comments — e3f96e8 resolves the only pending ones from the 15:14Z review.

PR ready to merge from my read.

· branch refactor/ENG-2884 ·

@danhdoan danhdoan merged commit 8edf54a into proj/byterover-tool-mode May 22, 2026
7 checks passed
@danhdoan danhdoan deleted the refactor/ENG-2884 branch May 22, 2026 09:24
@github-actions github-actions Bot mentioned this pull request May 22, 2026
8 tasks
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.

3 participants