feat: [ENG-2070] Dream undo — revert last dream from previousTexts#392
feat: [ENG-2070] Dream undo — revert last dream from previousTexts#392danhdoan merged 2 commits intoproj/dreamingfrom
Conversation
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.
|
Claude finished @RyanNg1403's task in 4m 6s —— View job PR Review: feat: [ENG-2070] Dream undoTasks
Overall Assessment — Score: 3 / 5The feature is well-scoped, the happy-path logic is sound, and the test coverage hits the most important cases (precondition guards, each operation type, partial failures, state rewind). That said, there are a security issue and a data-loss case that need fixing before merge. Critical
Major
Minor
Tests
What's good: The dependency-injection design on |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7a3e8f0fbc
ℹ️ 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".
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
|
Claude finished @RyanNg1403's task in 2m 22s —— View job PR Review: feat: [ENG-2070] Dream undo (re-review after feedback)Tasks
Re-review: feat: [ENG-2070] Dream undo — Score: 4 / 5Good progress since the first pass. Both critical issues are resolved and most majors were cleaned up. Three items still need attention before merge. Still OpenBug (was Codex P2):
|
| # | Location | Issue |
|---|---|---|
| 1 | dream-undo.ts:123 |
as NodeJS.ErrnoException in unlinkSafe is the same as Type pattern that was correctly removed elsewhere. Use a type guard: const err = error as {code?: string}; if (err.code !== 'ENOENT') throw error — or the standard instanceof Error && 'code' in error pattern. |
| 2 | dream.ts:136 |
resolveProject()?.projectRoot ?? process.cwd() still silently falls back to cwd when not in a BRV project — command runs against arbitrary directory without warning. Consider logging a warning at minimum. |
Fixed Since Last Review
| # | Issue | Status |
|---|---|---|
| C1 | Path traversal via previousTexts keys |
✅ Fixed — safePath() uses resolve() + startsWith check |
| C2 | SYNTHESIZE/UPDATE deletes pre-existing file | ✅ Fixed — throws Cannot undo SYNTHESIZE/UPDATE |
| M1 | as number cast on log.completedAt |
✅ Fixed — plain log.completedAt |
| M2 | Silent manifest rebuild failure | ✅ Fixed — adds to result.errors |
| m1 | .catch(() => {}) swallows non-ENOENT errors |
✅ Fixed — unlinkSafe() helper rethrows non-ENOENT |
| T1 | No test for SYNTHESIZE/UPDATE | ✅ Fixed — test at line 268 |
The safePath implementation is correct: resolve() is the right function (not join) because it handles .. segments in absolute path context; the startsWith guard covers both the path-traversal case and absolute-path injection.
Summary
brv dream --undoCLI flag anddream-undo.tsreversal logicType of change
Scope (select all touched areas)
Linked issues
Root cause (bug fixes only, otherwise write
N/A)N/A
Test plan
test/unit/infra/dream/dream-undo.test.tsUser-visible changes
brv dream --undoflag reverts the last dreamUndone dream drm-XXXXwith file counts{ status: "undone", dreamId, restoredFiles, deletedFiles, restoredArchives, errors }Evidence
Interactive testing verified:
lastDreamAt: null,totalDreamsdecrementedlastDreamAtreset works)Checklist
npm test)npm run lint)npm run typecheck)npm run build)Risks and mitigations
resolveProject()(same utility daemon uses) for project resolution. Pure file I/O with no agent/LLM needed.