Skip to content

feat: [ENG-1593] add brv review --disable / --enable toggle#563

Open
RyanNg1403 wants to merge 3 commits intomainfrom
feat/ENG-1593
Open

feat: [ENG-1593] add brv review --disable / --enable toggle#563
RyanNg1403 wants to merge 3 commits intomainfrom
feat/ENG-1593

Conversation

@RyanNg1403
Copy link
Copy Markdown
Collaborator

Summary

  • Problem: Users had no way to opt out of the HITL review log per project. brv curate always emitted the "X operations require review" prompt, dream surfaced its own needsReview ops, and curate-log entries always carried review markers — even for projects/workflows where review is unwanted.
  • Why it matters: Review pending entries and review-backups are persistent state. Without a toggle, projects that don't want HITL review accumulated dead entries. There was also a cross-process snapshot race: a user toggling mid-curate could leave orphan backups (no review entry references them) or missing backups (review entry exists but reject can't restore).
  • What changed: Added a project-scoped reviewDisabled flag on BrvConfig. New top-level brv review command exposes --enable / --disable flags and a bare-form status read; both routed through new daemon transport events (review:getDisabled, review:setDisabled). The daemon snapshots reviewDisabled once at the task-create boundary and forwards it through TaskInfo (daemon-side, used by CurateLogHandler) and TaskExecuteSchema (wire format, opened as an AsyncLocalStorage scope around the agent-process task body so any descendant async callsite — direct curate-tool invocation OR sandbox tools.curate(...) via CurateService — observes the same value). Dream-side review entry creation and review-backup writing both honor the same snapshot.
  • What did NOT change (scope boundary): Existing brv review pending / approve / reject subcommands and their wiring are untouched. brv init flow is untouched — reviewDisabled is optional and defaults to enabled (fail-open). No global / machine-wide override; the toggle is strictly project-level.

Type of change

  • New feature

Scope (select all touched areas)

  • Agent / Tools
  • Server / Daemon
  • Shared (constants, types, transport events)
  • CLI Commands (oclif)

Linked issues

  • Closes ENG-1593
  • Related: HITL for Cipher Agent Curation project (backlog ticket; project branch was deleted, hence PR targets main)

Root cause (bug fixes only, otherwise write N/A)

N/A — feature add. (Mid-task snapshot race surfaced during the work; closed by the AsyncLocalStorage design from day one of the implementation, no separate fix.)

Test plan

  • Coverage added:
    • Unit test
    • Integration test (interactive CLI, see Evidence below)
  • Test file(s):
    • test/commands/review-toggle.test.ts (new) — top-level command surface (10 tests, transport-stubbed, mirrors peer test/commands/review.test.ts)
    • test/unit/infra/transport/handlers/review-handler.test.ts (new) — handleGetDisabled + handleSetDisabled (7 tests including "preserves other config fields")
    • test/unit/agent/tools/curate-tool.test.ts — added 4 ALS-path tests (scope=true overrides config=false; scope=false overrides config=true; ALS via executeCurate with no _context.taskId — the regression that the prior Map-based registry missed; outside-scope falls back to file)
    • test/unit/infra/process/curate-log-handler.test.ts — rewritten around task.reviewDisabled (snapshot-at-create test included)
    • test/unit/infra/process/task-router.test.ts — added describe('reviewDisabled stamping') (4 tests)
    • test/unit/infra/executor/dream-executor.test.ts — 6 dream-side tests including snapshot consistency between runOperations and createReviewEntries (prototype-patched)
    • test/unit/core/domain/entities/brv-config.test.tsreviewDisabled field round-trip
  • Key scenario(s) covered:
    • Curate enabled/disabled, single-op and multi-op batch, new file vs UPSERT existing vs DELETE
    • Mid-task toggle disabled→enabled (snapshot held disabled → no orphan backup)
    • Mid-task toggle enabled→disabled (snapshot held enabled → backup created, pending entry created)
    • Toggle storm (alternating disable/enable across 4 sequential ops)
    • Idempotent --enable / --disable (writing the same value succeeds silently)
    • Dream enabled/disabled and dream-with-mid-toggle (review entries surface only when daemon snapshot was enabled at dream-create)

User-visible changes

  • New top-level command:
    • brv review (no flags) — prints "Review log is enabled." or "Review log is disabled." (or {"reviewDisabled":<bool>} with --format json)
    • brv review --disable — disables for the current project; prints "Review log disabled. To re-enable: brv review --enable"
    • brv review --enable — re-enables; prints "Review log enabled. To disable: brv review --disable"
  • Existing brv review pending / approve / reject subcommands continue to work (no change)
  • New project-config field: reviewDisabled?: boolean in <project_root>/.brv/config.json. Defaults to enabled (omitted = enabled). Per-project; not global, not per-worktree (worktrees inherit parent project's setting via the existing pointer model).

Evidence

  • Interactive CLI test pass artifacts:
    • notes/eng-1593-review-disable/round3-cli-suite/PLAN.md — matrix
    • notes/eng-1593-review-disable/round3-cli-suite/RESULTS.md — 24 curates + 6 dreams; every mid-toggle observed the daemon snapshot, not the post-toggle config
    • notes/eng-1593-review-disable/IMPLEMENTATION_REPORT.md — full report including Implementation Decisions section per CLAUDE.md
  • Test totals: 6920 passing, 16 pending (pre-existing), 0 failing
  • Round-3 D3 / D4 mid-toggle scenarios verified end-to-end with debug-traced daemon snapshots (debug logs were removed before commit; git diff is clean of eng-1593-debug strings)

Checklist

  • Tests added or updated and passing (npm test — 6920 passing)
  • Lint passes (npm run lint — clean on changed files; pre-existing submodule lint error in packages/byterover-packages/ is unrelated and tracked separately)
  • Type check passes (npm run typecheck)
  • Build succeeds (npm run build)
  • Commits follow Conventional Commits format (feat: [ENG-1593] ..., no Co-Authored-By)
  • No breaking changes (additive only; existing review commands untouched; reviewDisabled defaults to enabled / undefined)
  • Branch is up to date with main (rebased to 3c7f749c before commit)

Risks and mitigations

  • Risk: AsyncLocalStorage propagation depends on the LLM streaming pipeline + sandbox staying in-process and same-thread. If the sandbox is ever moved to a worker thread or separate process, the ALS scope silently stops working.
    • Mitigation: Same constraint already applies to the existing CurateResultCollector (cited as precedent), and that has shipped reliably. Documented in the JSDoc of curate-tool-task-context.ts so future refactors don't break it silently.
  • Risk: Idempotent --disable / --enable (running the same flag twice in a row) doesn't differentiate between "actually toggled" and "already in that state" — both produce the same confirmation text.
    • Mitigation: Discussed; intentionally kept simple — the daemon write is a boolean overwrite, no efficiency concern. If UX wants distinguished messages later, the daemon handler can return a changed boolean for the CLI to branch on.
  • Risk: Fail-open on missing/corrupt .brv/config.json — undefined reviewDisabled is treated as enabled rather than disabled.
    • Mitigation: Backups exist to support rejection; failing closed (= disabled) would silently break the rejection path. Documented in snapshotReviewDisabled JSDoc and isReviewDisabledForBrvDir fallback.

Adds a project-scoped HITL review toggle. When disabled:
- `brv curate` no longer prints the "X operations require review" prompt
- `--detach` curate-log entries omit the per-op review marker
- `brv dream` no longer surfaces its own needsReview ops as pending reviews
- `brv review pending` lists nothing new until re-enabled
- Review backups (the rollback substrate) are not written

Single source of truth: the daemon snapshots `reviewDisabled` once at the
task-create boundary and forwards it through TaskInfo (daemon-side, used
by CurateLogHandler) and TaskExecuteSchema (wire format, opened as an
AsyncLocalStorage scope around the agent-process task body so any
descendant async callsite — direct curate-tool invocation OR sandbox
`tools.curate(...)` via CurateService — observes the same value even if
the user toggles mid-task). Same propagation pattern as
CurateResultCollector.

Mid-task toggle race coverage:
- Started disabled, toggle enable → daemon snapshot held disabled →
  no orphan backup, no pending entry
- Started enabled, toggle disable → daemon snapshot held enabled →
  backup created, pending entry created
- Verified across 24 curates + 6 dreams in interactive testing.

Top-level `brv review` command (and the new `--enable` / `--disable`
flags) routes through the daemon transport via two new events
(`review:getDisabled`, `review:setDisabled`) — same wiring pattern as
`brv review pending` / `approve` / `reject`. Existing review
subcommands are untouched.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 27, 2026

Claude finished @RyanNg1403's task in 5m 35s —— View job


PR Review: feat: [ENG-1593] add brv review --disable / --enable toggle

Tasks

  • Gather context and understand the request
  • Read changed source files
  • Read changed test files
  • Analyze for correctness, security, and code quality issues
  • Post inline comments
  • Post summary with overall score

Summary

The design is solid. The snapshot-at-task-create approach (one ALS scope per task:execute, covering direct curate-tool invocations and sandbox tools.curate(...) paths) is the right solution to the mid-task toggle race. The reviewDisabled flag flows cleanly from daemon config → TaskInfoTaskExecute wire format → ALS scope in the agent process → DreamExecutor options. Test coverage is comprehensive across all mentioned scenarios.

One correctness bug found. Three style/standards comments posted inline.


Issues Found

Bug — idle-triggered dreams ignore the toggle

Inline comment on brv-server.ts lines 391-393.

The isReviewDisabled resolver is wired only into TransportHandlersTaskRouter.handleTaskCreate. But idle dreams (agent-idle timeout, around line 310) call agentPool.submitTask(...) directly, bypassing handleTaskCreate entirely. task.reviewDisabled arrives as undefined in the agent process; the ALS scope is never opened; and DreamExecutor defaults to options.reviewDisabled ?? false — review always enabled for idle dreams regardless of the project setting.

Fix: stamp curateConfigStore.read(projectPath)?.reviewDisabled === true on the idle-dream submitTask call — curateConfigStore is already in scope.

This gap isn't covered by the existing test suite: dream-executor.test.ts injects reviewDisabled directly into executeWithAgent options, which bypasses the stamp path entirely.

Standards violation — as Type assertion

Inline comment on curate-tool.ts line 505. The JSON.parse(raw) as {reviewDisabled?: unknown} assertion violates CLAUDE.md ("Avoid as Type assertions — use type guards or proper typing"). A proper type guard is straightforward here. The immediate === true narrowing makes this safe in practice, but it should still be brought into compliance.

Nitpick — onAfterWrite annotation style

Inline comment on curate-tool.ts line 1532. Two minor items: undefined | WriteCallback ordering, and the braced single-expression arrow body that may trip arrow-body-style.

Suggestion — withReviewDisabled field enumeration

Inline comment on brv-config.ts lines 246-262. Manually enumerating all 12 BrvConfigParams fields in every withXxx method is a maintenance trap; {...this, reviewDisabled} is safer. Pre-existing pattern across all withXxx methods, so a follow-up refactor rather than a blocker.


What Works Well

  • ALS propagation design: correctly handles the sandbox tools.curate(...) path where _context.taskId is not available — the precedent from CurateResultCollector is well-cited.
  • Fail-open semantics: undefined → enabled consistently throughout the stack (resolver throws → undefined, no resolver wired → undefined, missing config → false, ?? false in DreamExecutor). Correct: silent breakage of the reject-restore path would be worse than spurious review entries.
  • Transport events: review:getDisabled / review:setDisabled cleanly separated, idempotent writes, exclusive flag on CLI flags, error path reported correctly.
  • Test coverage: ALS regression tests (scope=true overrides config=false, vice versa, outside-scope fallback), snapshot-consistency tests for mid-toggle dreams, round-trip reviewDisabled in config, 6920 passing.
  • handleSetDisabled returns the request value: this is correct because projectConfigStore.write throws on failure; no silent divergence possible.

Overall Score: 4 / 5

Solid implementation. The idle-dream bug should be fixed before merging — it creates a gap between user expectation ("I disabled review") and actual behavior (idle dream still generates review entries). The other comments are non-blocking.

Comment thread src/agent/infra/tools/implementations/curate-tool.ts Outdated
Comment thread src/server/infra/daemon/brv-server.ts Outdated
Comment thread src/agent/infra/tools/implementations/curate-tool.ts Outdated
Comment thread src/server/core/domain/entities/brv-config.ts
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 96cdaf680a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/server/infra/executor/dream-executor.ts
Comment thread src/server/infra/process/task-router.ts Outdated
…ping and curate-tool style

Fix #1 (blocker — brv-server.ts): the idle-trigger dream-dispatch path
called `agentPool.submitTask(...)` directly, bypassing
`TaskRouter.handleTaskCreate` and therefore the daemon's
reviewDisabled snapshot. Without the stamp, `task.reviewDisabled`
arrived undefined in the agent process, the AsyncLocalStorage scope
was never opened, and `DreamExecutor` defaulted to review-enabled —
so idle dreams ignored the project's `reviewDisabled` setting
regardless of `brv review --disable`.

Hoisted the `ProjectConfigStore` declaration up the file and added a
shared `resolveReviewDisabled` resolver that both the idle-dream
dispatch and the existing `TransportHandlers.isReviewDisabled` reuse.
The two paths now stamp identical values, so review semantics are
consistent across CLI dispatch and idle-trigger dispatch.

Fix #2 (curate-tool.ts): replaced `JSON.parse(raw) as
{reviewDisabled?: unknown}` in `isReviewDisabledForBrvDir` with a
proper `hasReviewDisabledField` type guard. CLAUDE.md forbids
`as Type` assertions; the new guard narrows safely without an
escape-hatch cast.

Fix #3 (curate-tool.ts): dropped redundant braces around the
single-expression arrow body in the `onAfterWrite` definition. The
union-type ordering nit (`undefined | WriteCallback` vs reverse) was
left as-is — `perfectionist/sort-union-types` requires the existing
order, so the reviewer's suggested swap would actually have
introduced a lint error.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 27, 2026

PR Review: feat: [ENG-1593] add brv review --disable / --enable toggle

Tasks

  • Gather context and understand the request
  • Read changed source files
  • Read changed test files
  • Analyze for correctness, security, and code quality issues
  • Post inline comments
  • Post summary with overall score

Working…

View job run

P2 fix (task-router.ts): `snapshotReviewDisabled` returned `undefined`
when the resolver threw, which re-introduced the exact daemon/agent
divergence the snapshot is meant to prevent — daemon stamps no field
→ `CurateLogHandler` treats it as enabled (`?? false`), while the
agent process opens no ALS scope and may observe a different value
from `.brv/config.json` in the curate-tool fallback. Now stamps an
explicit `false` (fail-open: review enabled) on resolver throw, so
both sides observe a single concrete value. Aligns with the
agent-side `isReviewDisabledForBrvDir` which also fails open.

`undefined` is still returned when no resolver is wired or no
projectPath was resolved — those are legitimate "not configured"
cases that downstream consumers handle correctly.

Adds a regression test pinning the new behavior in
`reviewDisabled stamping` describe block.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 27, 2026

Claude finished @RyanNg1403's task in 5m 42s —— View job


PR Review: feat: [ENG-1593] add brv review --disable / --enable toggle

Tasks

  • Gather context and understand the request
  • Read changed source files
  • Read changed test files
  • Analyze for correctness, security, and code quality issues
  • Post inline comments
  • Post summary with overall score

Feedback Addressed Since Round 1

All four issues from the previous review pass are resolved in commits 283fb0c and a89b50d:

Prior finding Status
Bug (blocker) — idle-dream bypass of handleTaskCreate never stamped reviewDisabled ✅ Fixed: resolveReviewDisabled lifted to module scope, shared by both idle-trigger and TransportHandlers.isReviewDisabled. agentPool.submitTask(...) now includes reviewDisabled at line 325 of brv-server.ts.
Standards violationas {reviewDisabled?: unknown} assertion in isReviewDisabledForBrvDir ✅ Fixed: proper hasReviewDisabledField type guard at curate-tool.ts:502.
NitpickonAfterWrite arrow body redundant braces ✅ Fixed: implicit-return form used. Ordering kept as undefined | WriteCallback per perfectionist/sort-union-types (author confirmed swap would introduce lint error).
Codex P2 bugsnapshotReviewDisabled returned undefined on resolver throw, re-introducing daemon/agent divergence ✅ Fixed: now returns explicit false (fail-open, review enabled) at task-router.ts:1038. Pinned by new test at line 273 in task-router.test.ts.

The withReviewDisabled field enumeration suggestion was acknowledged and correctly declined — sweeping all three withXxx methods at once belongs in a follow-up PR.


New Inline Comments

Five inline comments posted (see diff):

  • brv-server.ts:325 — praise for the idle-dream fix design
  • curate-tool.ts:503 — praise for the type guard
  • task-router.ts:1038 — praise for the fail-open stamping fix
  • curate-log-handler.ts:266 — nitpick on guard ordering (no-op, fine as-is)
  • oclif/commands/review.ts:57 — nitpick: flags.disable === true can be flags.disable (no functional impact)

What Works Well

  • All previous blockers closed. No correctness gaps remain in the stamping pipeline (CLI dispatch → TaskRouter, idle-trigger → brv-server, resolver-throw fail-open → explicit false).
  • ALS propagation is complete. The conditional scope at agent-process.ts:657 covers every task type uniformly; legacy clients without a stamp preserve prior behavior.
  • Fail-open semantics are consistent end-to-end: resolver throws → false, missing config → false, undefined → false. No path silently disables review where it shouldn't.
  • curate-log-handler.ts:266 correctly gates reviewStatus = 'pending' behind !state.reviewDisabled, so the onPendingReviews callback is naturally suppressed when review is disabled (pendingCount stays 0).
  • Test coverage is thorough: resolver-throw regression test (task-router), idle-dream snapshot coverage (dream-executor), ALS path tests (curate-tool), curate-log snapshot test, config round-trip.

Overall Score: 5 / 5

The implementation is ready to merge. All blocker and standards issues from round 1 are resolved. The design is sound, the code is clean, and the tests cover the critical paths. The two remaining nitpicks (flags.disable === true and withXxx enumeration) are non-blocking and the latter is intentionally deferred.

Comment thread src/server/infra/daemon/brv-server.ts
Comment thread src/agent/infra/tools/implementations/curate-tool.ts
Comment thread src/server/infra/process/task-router.ts
Comment thread src/server/infra/process/curate-log-handler.ts
Comment thread src/server/infra/daemon/agent-process.ts
Comment thread src/oclif/commands/review.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant