Skip to content

refactor: extract CooldownDiaryWriter from CooldownSession (Wave 2B, #375)#399

Merged
cmbays merged 4 commits intomainfrom
wave-2b-cooldown-diary-writer
Mar 22, 2026
Merged

refactor: extract CooldownDiaryWriter from CooldownSession (Wave 2B, #375)#399
cmbays merged 4 commits intomainfrom
wave-2b-cooldown-diary-writer

Conversation

@cmbays
Copy link
Copy Markdown
Owner

@cmbays cmbays commented Mar 22, 2026

Summary

  • Extract diary writing + dojo session generation into CooldownDiaryWriter (191L)
  • CooldownSession drops from 1,098 → 965 lines (-133L)
  • 8 private methods moved: writeRunDiary, writeCompleteDiary, writeDiaryEntry, enrichBetOutcomesWithDescriptions, writeOptionalDojoSession, writeDojoSession, buildDojoSessionRequest, gatherDojoSessionData
  • Public API unchanged — CooldownSession.run(), .prepare(), .complete() delegate to diaryWriter

Quality Loop

Gate Result
BDD scenarios 14 scenarios (CPO + CQO audited)
Unit tests 16 tests
Acceptance tests 93/93 passing
Unit tests 3,469/3,469 passing
CRAP 0 functions above threshold — PASS
ArchUnit 14/15 (pre-existing CycleManager LCOM4)
Mutation 86.67% covered (2 catch-block equivalent mutants)
Typecheck Clean

Test plan

  • npm run typecheck — clean
  • npm run test:unit — 3,469 passing
  • npm run test:acceptance — 93 passing (13 new diary writer scenarios)
  • npm run test:crap — PASS
  • npm run test:arch:unit — 14/15 (pre-existing)
  • Stryker mutation — 86.67% covered, above 70% threshold

Part of #375 — CooldownSession decomposition Wave 2B

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added cooldown diary writing capability with support for one-shot and two-phase cooldown flows.
    • Implemented optional dojo session generation during cooldown completion.
    • Enhanced diary entries to include bet outcome descriptions, human and agent perspectives, and synthesis summaries.
  • Refactor

    • Improved code organization by extracting diary writing logic into a dedicated component.

…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>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 22, 2026

Warning

Rate limit exceeded

@cmbays has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 26 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 94e7231f-152c-4665-92b4-913d0494af25

📥 Commits

Reviewing files that changed from the base of the PR and between 38b5b8e and d6fc38a.

📒 Files selected for processing (4)
  • src/features/cycle-management/cooldown-diary-writer.steps.ts
  • src/features/cycle-management/cooldown-diary-writer.test.ts
  • src/features/cycle-management/cooldown-diary-writer.ts
  • src/features/cycle-management/cooldown-session.ts
📝 Walkthrough

Walkthrough

Introduces a new CooldownDiaryWriter class that encapsulates cooldown diary persistence and dojo session generation, extracted from inline implementations in CooldownSession. Adds comprehensive test coverage (feature file, BDD steps, unit tests) and updates the session to delegate diary and session writing to the new writer.

Changes

Cohort / File(s) Summary
New CooldownDiaryWriter Implementation
src/features/cycle-management/cooldown-diary-writer.ts
Introduced CooldownDiaryWriter class with methods writeForRun(), writeForComplete(), enrichBetOutcomesWithDescriptions(), and writeDojoSession(). Handles diary entry persistence, bet outcome enrichment, human/agent perspective forwarding, and conditional dojo session generation with error logging.
CooldownDiaryWriter Test Coverage
src/features/cycle-management/cooldown-diary-writer.feature, src/features/cycle-management/cooldown-diary-writer.steps.ts, src/features/cycle-management/cooldown-diary-writer.test.ts
Added Gherkin feature file defining end-to-end behavior for run/complete diary writing, error handling, and bet outcome enrichment. Implemented BDD step definitions with mocked dependencies and state management. Added Vitest unit tests validating enrichment logic, perspective forwarding, bet outcome filtering, dojo session generation guards, and failure logging.
CooldownSession Refactoring
src/features/cycle-management/cooldown-session.ts
Removed inline diary writing, dojo session generation, and bet outcome enrichment implementations. Replaced with delegation to injected CooldownDiaryWriter instance. Net reduction of 133 lines via removal of writeRunDiary(), writeCompleteDiary(), writeOptionalDojoSession(), and related helpers.
Configuration & Setup Updates
src/acceptance/setup.ts, stryker.config.mjs
Added side-effect import for cooldown-diary-writer.steps.js to acceptance setup. Extended Stryker mutate configuration to include new cooldown-diary-writer.ts.
Test Hook Modernization
src/features/cycle-management/cooldown-follow-up-runner.steps.ts
Made cleanup hook async to support potential asynchronous cleanup patterns.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 A diary writer hops into place,
CooldownSession finds breathing space,
Bet outcomes enriched with care,
Dojo sessions float through air,
Logic delegated, tests everywhere! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main refactoring: extracting CooldownDiaryWriter from CooldownSession. It references the issue and wave number, making it precise and actionable.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch wave-2b-cooldown-diary-writer

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot]
coderabbitai bot previously requested changes Mar 22, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between d8701e2 and 38b5b8e.

📒 Files selected for processing (8)
  • src/acceptance/setup.ts
  • src/features/cycle-management/cooldown-diary-writer.feature
  • src/features/cycle-management/cooldown-diary-writer.steps.ts
  • src/features/cycle-management/cooldown-diary-writer.test.ts
  • src/features/cycle-management/cooldown-diary-writer.ts
  • src/features/cycle-management/cooldown-follow-up-runner.steps.ts
  • src/features/cycle-management/cooldown-session.ts
  • stryker.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 .js extensions on all internal imports. Project must have "type": "module" in package.json.

Files:

  • src/features/cycle-management/cooldown-follow-up-runner.steps.ts
  • src/acceptance/setup.ts
  • src/features/cycle-management/cooldown-session.ts
  • src/features/cycle-management/cooldown-diary-writer.test.ts
  • src/features/cycle-management/cooldown-diary-writer.steps.ts
  • src/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 and src/cli/index.ts.

Files:

  • src/features/cycle-management/cooldown-follow-up-runner.steps.ts
  • src/acceptance/setup.ts
  • src/features/cycle-management/cooldown-session.ts
  • src/features/cycle-management/cooldown-diary-writer.test.ts
  • src/features/cycle-management/cooldown-diary-writer.steps.ts
  • src/features/cycle-management/cooldown-diary-writer.ts
src/acceptance/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Register step files for .feature files through src/acceptance/setup.ts

Files:

  • src/acceptance/setup.ts
**/*.feature

📄 CodeRabbit inference engine (AGENTS.md)

**/*.feature: Co-locate .feature files 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.ts with 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.ts
  • stryker.config.mjs
  • src/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.mjs
  • src/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.mjs
  • src/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.mjs
  • src/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 CooldownSession focused on orchestration while preserving the existing run() and complete() flow.

Also applies to: 495-506, 594-603

Comment on lines +238 to +250
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'));
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

cmbays and others added 3 commits March 22, 2026 17:22
- 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>
@cmbays cmbays dismissed coderabbitai[bot]’s stale review March 22, 2026 21:28

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

@cmbays cmbays merged commit afe211b into main Mar 22, 2026
1 of 2 checks passed
@cmbays cmbays deleted the wave-2b-cooldown-diary-writer branch March 22, 2026 21:31
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