refactor: extract CooldownDiaryWriter from CooldownSession (Wave 2B, #375)#399
refactor: extract CooldownDiaryWriter from CooldownSession (Wave 2B, #375)#399
Conversation
…375) Extract diary writing and dojo session generation into a dedicated CooldownDiaryWriter class. CooldownSession drops from 1,098 to 965 lines. - CooldownDiaryWriter (191L): writeForRun, writeForComplete, enrichBetOutcomesWithDescriptions, writeDojoSession - 14 BDD scenarios (CPO + CQO audited), 16 unit tests - Quality loop: CRAP < 8, ArchUnit 14/15, mutation 86.67% covered - Fix: After hook guard for cross-world compatibility in quickpickle - Fix: async After hooks in follow-up-runner and diary-writer steps Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughIntroduces a new Changes
Sequence DiagramsequenceDiagram
participant CooldownSession
participant CooldownDiaryWriter
participant DiaryWriter
participant DiaryStore
participant DataAggregator
participant SessionBuilder
CooldownSession->>CooldownDiaryWriter: writeForRun(cycleId, cycle, betOutcomes, ...)
CooldownDiaryWriter->>CooldownDiaryWriter: enrichBetOutcomesWithDescriptions()
CooldownDiaryWriter->>DiaryWriter: write diary entry
DiaryWriter->>DiaryStore: persist entry
CooldownSession->>CooldownDiaryWriter: writeForComplete(cycleId, cycle, proposals, ...)
CooldownDiaryWriter->>CooldownDiaryWriter: build agentPerspective from proposals
CooldownDiaryWriter->>DiaryWriter: write completion diary
DiaryWriter->>DiaryStore: persist entry
CooldownSession->>CooldownDiaryWriter: writeDojoSession(cycleId)
CooldownDiaryWriter->>DataAggregator: gather dojo session data
CooldownDiaryWriter->>SessionBuilder: build dojo session
Note over CooldownDiaryWriter: Log warnings on failure, continue normally
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/features/cycle-management/cooldown-diary-writer.test.ts`:
- Around line 238-250: The test is passing before reaching
dojoSessionBuilder.build because DataAggregator lacks a knowledgeStore.stats()
mock and makeDeps leaves dojoDir as a bogus path; update the test setup so
CooldownDiaryWriter.writeDojoSession hits the builder-throw path by giving
makeDeps() a knowledgeStore with a stats() implementation (e.g., return expected
stats shape) and a valid/mockable dojoDir or mock file interactions so
aggregation doesn't fail early, then keep dojoSessionBuilder.build mocked to
throw to assert the warning; reference CooldownDiaryWriter, makeDeps,
DataAggregator / knowledgeStore.stats, dojoDir, and dojoSessionBuilder.build
when making these changes.
In `@src/features/cycle-management/cooldown-diary-writer.ts`:
- Around line 52-75: The call to writeDiaryEntry in writeForRun must not use the
separate optional cycleName param (two sources of truth); instead derive
cycleName from the provided cycle object (use input.cycle.name) when building
the diary payload so the source of truth is cycle.name; apply the same
normalization in the other public method that forwards to writeDiaryEntry (the
corresponding method around lines 82-102), and keep other fields (e.g.,
betOutcomes via enrichBetOutcomesWithDescriptions) unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 64cc281e-910e-43d0-85a3-3c8554361bb4
📒 Files selected for processing (8)
src/acceptance/setup.tssrc/features/cycle-management/cooldown-diary-writer.featuresrc/features/cycle-management/cooldown-diary-writer.steps.tssrc/features/cycle-management/cooldown-diary-writer.test.tssrc/features/cycle-management/cooldown-diary-writer.tssrc/features/cycle-management/cooldown-follow-up-runner.steps.tssrc/features/cycle-management/cooldown-session.tsstryker.config.mjs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Lint, Tests, Quality Loop & Build
🧰 Additional context used
📓 Path-based instructions (5)
src/**/*.{ts,js}
📄 CodeRabbit inference engine (CLAUDE.md)
Use ESM-only imports with
.jsextensions on all internal imports. Project must have"type": "module"in package.json.
Files:
src/features/cycle-management/cooldown-follow-up-runner.steps.tssrc/acceptance/setup.tssrc/features/cycle-management/cooldown-session.tssrc/features/cycle-management/cooldown-diary-writer.test.tssrc/features/cycle-management/cooldown-diary-writer.steps.tssrc/features/cycle-management/cooldown-diary-writer.ts
src/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.ts: Use path aliases for imports:@domain/*→src/domain/*,@infra/*→src/infrastructure/*,@features/*→src/features/*,@shared/*→src/shared/*,@cli/*→src/cli/*. Aliases must be defined in tsconfig.json and vitest.config.ts.
Dependency direction must be strictly enforced: domain → infrastructure → features → shared → cli. No reverse imports allowed.
Coverage thresholds must maintain 80% statements/functions/lines and 75% branches. Coverage excludes test files andsrc/cli/index.ts.
Files:
src/features/cycle-management/cooldown-follow-up-runner.steps.tssrc/acceptance/setup.tssrc/features/cycle-management/cooldown-session.tssrc/features/cycle-management/cooldown-diary-writer.test.tssrc/features/cycle-management/cooldown-diary-writer.steps.tssrc/features/cycle-management/cooldown-diary-writer.ts
src/acceptance/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Register step files for
.featurefiles throughsrc/acceptance/setup.ts
Files:
src/acceptance/setup.ts
**/*.feature
📄 CodeRabbit inference engine (AGENTS.md)
**/*.feature: Co-locate.featurefiles with the feature they specify
For new behavior or behavior clarification, acceptance scenarios must exist and fail before implementation
Files:
src/features/cycle-management/cooldown-diary-writer.feature
src/**/*.test.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Test files must be colocated next to source files as
*.test.tswith Vitest globals enabled (no need to import describe/it/expect).
Files:
src/features/cycle-management/cooldown-diary-writer.test.ts
🧠 Learnings (9)
📚 Learning: 2026-03-15T01:51:21.342Z
Learnt from: CR
Repo: cmbays/kata PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-15T01:51:21.342Z
Learning: Applies to src/acceptance/**/*.ts : Register step files for `.feature` files through `src/acceptance/setup.ts`
Applied to files:
src/acceptance/setup.tsstryker.config.mjssrc/features/cycle-management/cooldown-diary-writer.steps.ts
📚 Learning: 2026-03-15T01:51:21.342Z
Learnt from: CR
Repo: cmbays/kata PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-15T01:51:21.342Z
Learning: Applies to src/**/{cli,orchestrat}*.ts : Prefer extracting pure helpers from CLI/orchestration files before adding more direct tests to large side-effect-heavy modules
Applied to files:
stryker.config.mjssrc/features/cycle-management/cooldown-diary-writer.test.ts
📚 Learning: 2026-03-21T22:25:46.055Z
Learnt from: CR
Repo: cmbays/kata PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T22:25:46.055Z
Learning: Applies to src/**/*.ts : Dependency direction must be strictly enforced: domain → infrastructure → features → shared → cli. No reverse imports allowed.
Applied to files:
stryker.config.mjs
📚 Learning: 2026-03-21T22:25:46.055Z
Learnt from: CR
Repo: cmbays/kata PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T22:25:46.055Z
Learning: Applies to src/{cli,domain/types}/index.ts : Two entrypoints must be defined via tsup: `cli/index` (bin) and `domain/types/index` (library exports).
Applied to files:
stryker.config.mjs
📚 Learning: 2026-03-21T22:25:46.055Z
Learnt from: CR
Repo: cmbays/kata PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T22:25:46.055Z
Learning: Applies to src/**/*.ts : Coverage thresholds must maintain 80% statements/functions/lines and 75% branches. Coverage excludes test files and `src/cli/index.ts`.
Applied to files:
stryker.config.mjs
📚 Learning: 2026-03-15T01:51:21.342Z
Learnt from: CR
Repo: cmbays/kata PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-15T01:51:21.342Z
Learning: Applies to src/**/*.command.test.ts : Keep command-level tests focused on wiring and delegation; put parsing/formatting/selection behavior into direct unit tests
Applied to files:
stryker.config.mjssrc/features/cycle-management/cooldown-diary-writer.test.ts
📚 Learning: 2026-03-21T22:25:46.055Z
Learnt from: CR
Repo: cmbays/kata PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T22:25:46.055Z
Learning: Applies to src/**/*.ts : Use path aliases for imports: `domain/*` → `src/domain/*`, `infra/*` → `src/infrastructure/*`, `features/*` → `src/features/*`, `shared/*` → `src/shared/*`, `cli/*` → `src/cli/*`. Aliases must be defined in tsconfig.json and vitest.config.ts.
Applied to files:
stryker.config.mjs
📚 Learning: 2026-03-21T22:25:46.055Z
Learnt from: CR
Repo: cmbays/kata PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T22:25:46.055Z
Learning: Applies to src/**/*.test.ts : Test files must be colocated next to source files as `*.test.ts` with Vitest globals enabled (no need to import describe/it/expect).
Applied to files:
stryker.config.mjssrc/features/cycle-management/cooldown-diary-writer.test.ts
📚 Learning: 2026-03-15T01:51:21.342Z
Learnt from: CR
Repo: cmbays/kata PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-15T01:51:21.342Z
Learning: Applies to **/*.feature : For new behavior or behavior clarification, acceptance scenarios must exist and fail before implementation
Applied to files:
src/features/cycle-management/cooldown-diary-writer.feature
🔇 Additional comments (1)
src/features/cycle-management/cooldown-session.ts (1)
264-290: Nice responsibility split.The new collaborator keeps
CooldownSessionfocused on orchestration while preserving the existingrun()andcomplete()flow.Also applies to: 495-506, 594-603
| it('logs warning on failure', () => { | ||
| const writer = new CooldownDiaryWriter(makeDeps({ | ||
| dojoSessionBuilder: { | ||
| build: vi.fn().mockImplementation(() => { | ||
| throw new Error('build broke'); | ||
| }), | ||
| }, | ||
| })); | ||
|
|
||
| writer.writeDojoSession('cycle-1'); | ||
|
|
||
| expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('dojo session')); | ||
| }); |
There was a problem hiding this comment.
Make this hit the builder-throw path, not an earlier aggregation failure.
DataAggregator needs a knowledgeStore.stats() implementation, and makeDeps() also leaves dojoDir as a fake path. In this setup, writeDojoSession() can fail before dojoSessionBuilder.build() runs, so the test can pass without exercising the path its name describes.
🧪 Tighten the failure-path setup
- it('logs warning on failure', () => {
- const writer = new CooldownDiaryWriter(makeDeps({
- dojoSessionBuilder: {
- build: vi.fn().mockImplementation(() => {
- throw new Error('build broke');
- }),
- },
- }));
-
- writer.writeDojoSession('cycle-1');
-
- expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('dojo session'));
- });
+ it('logs warning when the builder throws', () => {
+ const tmpDojoDir = mkdtempSync(join(tmpdir(), 'dojo-test-'));
+ try {
+ const buildSpy = vi.fn().mockImplementation(() => {
+ throw new Error('build broke');
+ });
+ const writer = new CooldownDiaryWriter(makeDeps({
+ dojoDir: tmpDojoDir,
+ runsDir: '/tmp/runs',
+ knowledgeStore: {
+ query: vi.fn().mockReturnValue([]),
+ stats: vi.fn().mockReturnValue({ totalLearnings: 0, tiers: {} }),
+ } as unknown as CooldownDiaryDeps['knowledgeStore'],
+ dojoSessionBuilder: { build: buildSpy },
+ }));
+
+ writer.writeDojoSession('cycle-1');
+
+ expect(buildSpy).toHaveBeenCalled();
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('dojo session'));
+ } finally {
+ rmSync(tmpDojoDir, { recursive: true, force: true });
+ }
+ });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/features/cycle-management/cooldown-diary-writer.test.ts` around lines 238
- 250, The test is passing before reaching dojoSessionBuilder.build because
DataAggregator lacks a knowledgeStore.stats() mock and makeDeps leaves dojoDir
as a bogus path; update the test setup so CooldownDiaryWriter.writeDojoSession
hits the builder-throw path by giving makeDeps() a knowledgeStore with a stats()
implementation (e.g., return expected stats shape) and a valid/mockable dojoDir
or mock file interactions so aggregation doesn't fail early, then keep
dojoSessionBuilder.build mocked to throw to assert the warning; reference
CooldownDiaryWriter, makeDeps, DataAggregator / knowledgeStore.stats, dojoDir,
and dojoSessionBuilder.build when making these changes.
- Move diaryWriteFn into CooldownDiaryDeps (single-deps-object convention) - Extract DiaryEntryInput named type (replaces Record<string, unknown>) - Make enrichBetOutcomesWithDescriptions private (test via writeForRun) - Self-guard writeDiaryEntry (eliminates dojoDir! non-null assertion) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Silent failure review found enrichBetOutcomesWithDescriptions and buildDiaryBetOutcomesFromCycleBets executing outside try-catch, violating the "never abort cooldown" contract. Now writeForRun and writeForComplete each have their own try-catch wrapping the full body. Also adds cycleId to all warning messages for debuggability. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CodeRabbit correctly identified that cycleName was passed separately from cycle.name in both writeForRun and writeForComplete. Now derived from cycle.name directly inside the diary writer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both findings addressed: (1) test setup improved with knowledgeStore.stats mock + temp dir in follow-up commit, (2) cycleName dual source of truth eliminated — now derived from cycle.name
Summary
CooldownDiaryWriter(191L)writeRunDiary,writeCompleteDiary,writeDiaryEntry,enrichBetOutcomesWithDescriptions,writeOptionalDojoSession,writeDojoSession,buildDojoSessionRequest,gatherDojoSessionDataCooldownSession.run(),.prepare(),.complete()delegate todiaryWriterQuality Loop
Test plan
npm run typecheck— cleannpm run test:unit— 3,469 passingnpm run test:acceptance— 93 passing (13 new diary writer scenarios)npm run test:crap— PASSnpm run test:arch:unit— 14/15 (pre-existing)Part of #375 — CooldownSession decomposition Wave 2B
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Refactor