Skip to content

Add optional CLOJURE_MCP_TRAINING_DIR training-log emit - #164

Open
theselbalancingscooter wants to merge 3 commits into
bhauman:mainfrom
theselbalancingscooter:path-b/training-log-emit-hook
Open

Add optional CLOJURE_MCP_TRAINING_DIR training-log emit#164
theselbalancingscooter wants to merge 3 commits into
bhauman:mainfrom
theselbalancingscooter:path-b/training-log-emit-hook

Conversation

@theselbalancingscooter

@theselbalancingscooter theselbalancingscooter commented Aug 1, 2026

Copy link
Copy Markdown

Summary

Adds an opt-in JSONL emit hook to clojure-mcp so users building
LLM training-corpus pipelines can capture their tool-call
trajectories without asking downstream users to run a fork.

Off by default. Enabled by setting the CLOJURE_MCP_TRAINING_DIR
environment variable to a writable directory before starting the
server. Zero effect on existing users — the interceptor no-ops when
the env var is unset.

How it works

Every tool call already flows through clojure-mcp.core/create-async-tool's
clj-result-k continuation. This PR adds ONE line inside that
continuation to call training-log/record-tool-call! after the
tool has produced its result but before the response goes back to the
client. Each call appends one line to session-<uuid>-turns.jsonl;
a session-<uuid>-summary.edn snapshots on JVM shutdown.

Emitted line shape (JSON, one per tool call):

{"session-id":"…uuid…",
 "turn-index":0,
 "model":"unknown",
 "tool-calls":[{"tool":"clojure_eval",
                 "input":{"code":"(+ 1 2)"},
                 "output":"3",
                 "status":"ok",
                 "ms":null}],
 "outcome":"verified",
 "timestamp":"2026-08-02T…Z"}

Design constraints

  • Zero effect on tool behaviour. Any exception in the emit path
    is WARN-logged and swallowed. A bug in the training-log ns CANNOT
    break a tool response. Test asserts this invariant.
  • No new dependencies. Uses clojure.data.json + taoensso.timbre
    (both already transitive on org.clojure/data.json and
    com.taoensso/timbre).
  • Sensitive by design. Traces contain file contents, shell
    commands, and eval expressions. README section calls this out and
    suggests treating the training dir as private.

Non-goals

  • No network I/O. This PR writes local files only; any dispatch to a
    server is out of scope.
  • No PII scrubbing at emit. A downstream ingest is expected to redact
    before publishing.
  • Not a new tool. This is a passive interceptor on the existing tool
    dispatch path.

Changes

  • src/clojure_mcp/training_log.clj — new (90 LOC)
  • src/clojure_mcp/core.clj — +2 lines (require + one-line interceptor)
  • test/clojure_mcp/training_log_test.clj — new (~100 LOC, hermetic)
  • README.md — new '## 📊 Optional: Training-log emit' section

Test plan

  • clojure -M:test -n clojure-mcp.training-log-test — 5 tests,
    11 assertions, 0 failures (covers happy path, disabled no-op,
    error flag, monotonic turn-index, emit-never-throws invariant)
  • clojure -M:test -n clojure-mcp.core-test — 6 tests, 17
    assertions, 0 failures (existing tests unchanged)

Reference

The emitted shape is designed to be consumed by any pipeline that
reads the documented JSONL. The pipeline that motivated this PR is
described in a sibling repo's docs/PRD-clojure-mcp-corpus.md — no
hard coupling; any consumer reading this shape works.

Summary by CodeRabbit

  • New Features

    • Added optional per-session training logs for tool calls.
    • When CLOJURE_MCP_TRAINING_DIR is configured, the server records JSONL traces and an EDN session summary containing inputs, outputs, statuses, and timestamps.
    • Logging occurs after tool completion and does not affect tool responses if recording fails.
  • Documentation

    • Documented configuration and session-log behavior.
    • Added a warning that logs may contain sensitive inputs and outputs.

Off by default. When the env var is set to a writable directory,
every tool call gets appended to session-<uuid>-turns.jsonl and a
session summary is snapshotted on JVM shutdown. The emitted shape
matches the schema used by an external training-corpus pipeline for
LLM fine-tuning, so downstream ingests can read the files directly.

Why: LLM users of clojure-mcp produce high-signal training data
(REPL-verified structural editing) that's hard to capture from
other IDEs. Making the emit an opt-in env var means anyone building
a training pipeline can wire this up without asking users to run
a fork.

Design:
- New ns clojure_mcp.training_log — pure emit, no network I/O.
- One-line change to `create-async-tool`'s continuation to call
  training-log/record-tool-call! after the tool's clj-result-k fires.
- Zero effect on tool behaviour: emit runs post-response, and any
  exception in the emit path is WARN-logged + swallowed so a
  training-log bug cannot break tool responses.
- No new dependencies — uses clojure.data.json + timbre, both
  already transitive.

Sensitive files: traces contain tool arguments (file contents,
shell commands, eval expressions) and results. README section calls
this out and suggests treating the training dir as private.

Tests: hermetic (tmp-dir per test). Covers happy path, disabled
no-op, error flag, monotonic turn-index, and the invariant that
emit never throws on I/O failure.

Reference PRD: this shape matches agent/docs/PRD-clojure-mcp-corpus.md
in a sibling repo, which describes the external ingest expectations.
No hard coupling: any consumer that reads the documented JSONL shape
works.
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The server adds opt-in per-session training logs for completed MCP tool calls. It writes JSONL records, creates an EDN shutdown summary, isolates logging failures, documents sensitive data capture, and adds hermetic tests.

Changes

Training logging

Layer / File(s) Summary
Logger state and session paths
src/clojure_mcp/training_log.clj
Defines environment-based enablement, session metadata, turn counters, tool tracking, output paths, serialized writing, and failure tracking.
Tool-call emission and shutdown summary
src/clojure_mcp/training_log.clj
Provides non-blocking recording, handles rejected writes, drains queued tasks, and writes an EDN summary at JVM shutdown.
Server wiring and validation
src/clojure_mcp/core.clj, test/clojure_mcp/training_log_test.clj, README.md
Records calls after MCP responses are sent. Tests cover enabled and disabled logging, errors, turn indexes, concurrency, failed writes, shutdown flushing, and I/O failures. Documentation describes configuration and captured data.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MCPToolInvocation
  participant training_log
  participant TrainingLogFiles
  MCPToolInvocation->>training_log: record-tool-call!(tool-name, arg-map, result-strs, error?)
  training_log->>TrainingLogFiles: append JSONL record
  training_log->>training_log: update session metadata
  training_log->>TrainingLogFiles: write EDN summary on JVM shutdown
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: optional training-log emission controlled by CLOJURE_MCP_TRAINING_DIR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/clojure_mcp/core.clj`:
- Around line 119-122: Update the callback flow around
training-log/record-tool-call! and mono-fill-k so mono-fill-k signals the
adapted tool result before training-log I/O runs. Dispatch the training record
through a bounded asynchronous writer, preserving the existing opt-in and
non-throwing behavior while preventing filesystem delays or exceptions from
blocking the MCP response.

In `@src/clojure_mcp/training_log.clj`:
- Around line 98-100: In src/clojure_mcp/training_log.clj lines 98-100, replace
the broad Throwable catch around the training emit with specific expected
exception handling, and increment the atom-based failure counter when an emit
fails. Apply the same specific catch and failure-state update to the
summary-write handling at lines 123-125, reusing the shared atom and preserving
the existing warning context.
- Around line 75-97: The turn-index reservation and JSONL append in the
training-log emission flow must be atomic and serialized. Update the enclosing
function around session-state, turn, and line construction so concurrent
callbacks reserve unique sequential :turn-index values and append records in
that same order through one lock or serialized writer; add a concurrent emission
test covering uniqueness and ordering.

In `@test/clojure_mcp/training_log_test.clj`:
- Around line 87-92: Update emit-never-throws-on-io-failure-test to use a
temporary failure fixture: create a regular file within fresh-dir, configure
CLOJURE_MCP_TRAINING_DIR to that file path, invoke tl/record-tool-call!, and
assert that no turn file is created. Remove the non-portable /proc dependency
while preserving the no-exception assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0ec5a810-5380-4653-94fc-8aaa392a1cfe

📥 Commits

Reviewing files that changed from the base of the PR and between d625610 and 5de8626.

📒 Files selected for processing (4)
  • README.md
  • src/clojure_mcp/core.clj
  • src/clojure_mcp/training_log.clj
  • test/clojure_mcp/training_log_test.clj

Comment thread src/clojure_mcp/core.clj Outdated
Comment thread src/clojure_mcp/training_log.clj Outdated
Comment thread src/clojure_mcp/training_log.clj Outdated
Comment thread test/clojure_mcp/training_log_test.clj Outdated
Four review findings, all addressed:

1. Don't block MCP response on emit I/O (core.clj + training_log.clj)
   - core.clj: call mono-fill-k BEFORE record-tool-call! so the
     client response goes out first even if the ordering ever mattered.
   - training_log.clj: record-tool-call! now submits to a
     single-thread ExecutorService (writer-executor) and returns
     immediately. Zero response latency from disk I/O.

2. Atomic turn-index reservation + concurrent test (training_log.clj)
   - record-tool-call-sync! (runs on the writer thread) uses
     swap-vals! for a single-CAS reserve+increment. Reading the old
     value and swapping are one atomic operation.
   - Single-thread executor guarantees append order matches submission
     order — no reordering across the append.
   - New test: concurrent-emit-preserves-unique-indexes-test hammers
     50 submissions across 8 threads, asserts no duplicate indexes
     and full 0..N-1 range.

3. Catch Exception not Throwable + failure counter (training_log.clj)
   - Both catches (per-turn record and shutdown summary write) now
     catch Exception, letting fatal Error subclasses (OOM, ThreadDeath)
     propagate as they should.
   - New public `failure-count` atom is bumped on every swallowed
     exception. Ops can graph training-log emit health without
     scraping logs. Matches the CLAUDE.md guideline "atom for
     tracking errors".

4. Portable I/O-failure test fixture (training_log_test.clj)
   - Replaced /proc/1/... with a portable fixture: create a regular
     file, point CLOJURE_MCP_TRAINING_DIR at that file path, invoke
     record-tool-call!, assert no exception + failure-count bumped +
     no JSONL file created (proves the failure path actually ran).

Also: shutdown hook now drains the writer-executor before writing the
summary, so the turn-count in summary.edn matches what actually landed
in the JSONL.

Tests: 6 tests / 16 assertions (was 5/11). Core suite unchanged
(6/17). All hermetic.
@theselbalancingscooter

Copy link
Copy Markdown
Author

Addressed all 4 CodeRabbit findings in 8f7c393:

  1. Response-blocking I/O (core.clj)mono-fill-k now signals before record-tool-call!, and the record path itself is non-blocking (submits to a single-thread executor and returns). Client latency is fully decoupled from disk I/O.
  2. Atomic turn-index reservation (training_log.clj)record-tool-call-sync! (writer-thread) uses swap-vals! for a single-CAS reserve+increment. Single-thread executor guarantees append order matches submission order. New test concurrent-emit-preserves-unique-indexes-test hammers 50 submissions across 8 threads and asserts unique 0..N-1 indexes.
  3. Specific catches + failure counter (training_log.clj) — both catches now Exception (fatal Error subclasses propagate). New public failure-count atom is bumped on every swallowed exception so ops can graph emit health without log scraping. Matches CLAUDE.md's "atom for tracking errors" guideline.
  4. Portable I/O-failure fixture (training_log_test.clj) — replaced /proc/1/... with a regular-file-as-directory trick, asserting no-exception + failure-count bumped + no JSONL created (proves the failure path actually ran).

Bonus: shutdown hook now drains the writer-executor before writing summary, so turn-count in summary.edn matches what actually landed in the JSONL.

Tests: 6/16 (was 5/11), core suite unchanged (6/17). All hermetic.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
test/clojure_mcp/training_log_test.clj (1)

1-145: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No dedicated test for shutdown draining / summary accuracy.

The PR description lists "shutdown draining" among the tested behaviors, but none of the six deftest blocks here exercise flush-and-write-summary! or assert that the EDN summary's counts match the JSONL content after a drain. Consider adding a test that calls #'tl/flush-and-write-summary! directly and asserts the summary file's :turn-count matches the number of persisted JSONL lines.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/clojure_mcp/training_log_test.clj` around lines 1 - 145, Add a dedicated
deftest for shutdown draining around tl/flush-and-write-summary! that records
multiple tool calls, waits for queued writes, invokes the summary flush, and
reads the generated EDN summary. Assert the summary’s :turn-count equals the
number of persisted JSONL lines, covering both drain completion and summary
accuracy.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/clojure_mcp/training_log.clj`:
- Around line 14-17: Make the background writer executor used by
record-tool-call! bounded, replacing the default unbounded queue created by
Executors/newSingleThreadExecutor with an explicitly capacity-limited executor
configuration. Preserve immediate non-blocking submission and the existing
RejectedExecutionException handling, ensuring the docstring’s “bounded” behavior
matches the implementation.
- Around line 175-182: Update flush-and-write-summary! to capture the boolean
result of .awaitTermination and log a warning when it returns false, indicating
the 5-second drain timed out before all writer tasks completed. Preserve the
existing InterruptedException handling and write-summary! flow.
- Around line 90-126: Separate persisted-record accounting from the reservation
state in record-tool-call-sync!: do not advance the summary-facing :turn-count
or :tools-used before io/make-parents and spit succeed. Keep turn-index
reservation available for record ordering, then update the successful-write
counters only after the JSONL append completes, and ensure write-summary!
reports those persisted counters so failed writes are excluded.

---

Nitpick comments:
In `@test/clojure_mcp/training_log_test.clj`:
- Around line 1-145: Add a dedicated deftest for shutdown draining around
tl/flush-and-write-summary! that records multiple tool calls, waits for queued
writes, invokes the summary flush, and reads the generated EDN summary. Assert
the summary’s :turn-count equals the number of persisted JSONL lines, covering
both drain completion and summary accuracy.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 806984a7-b34a-48fc-b942-4dc3446ac961

📥 Commits

Reviewing files that changed from the base of the PR and between 5de8626 and 8f7c393.

📒 Files selected for processing (3)
  • src/clojure_mcp/core.clj
  • src/clojure_mcp/training_log.clj
  • test/clojure_mcp/training_log_test.clj
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/clojure_mcp/core.clj

Comment thread src/clojure_mcp/training_log.clj Outdated
Comment on lines +14 to +17
Zero effect on tool behaviour:
- The MCP response is NOT blocked on emit I/O. The interceptor
hands the (name, args, result) triple to a bounded background
writer and returns immediately.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Docstring claims a "bounded" writer, but the executor queue is unbounded.

Line 16 says the interceptor "hands the (name, args, result) triple to a bounded background writer." Executors/newSingleThreadExecutor (Line 71) wraps an unbounded LinkedBlockingQueue. Under sustained disk I/O backpressure, the queue can grow without limit and risk memory exhaustion. The existing RejectedExecutionException catch in record-tool-call! (Lines 142-146) only fires after .shutdown, so it never protects against queue growth today.

Bound the queue so the documented behavior matches the implementation, or update the docstring to say "unbounded" if that is the intended tradeoff.

🔧 Proposed fix to bound the writer queue
 (defonce ^:private ^ExecutorService writer-executor
   (let [thread-factory
         (reify java.util.concurrent.ThreadFactory
           (newThread [_ r]
             (doto (Thread. r "clojure-mcp-training-log")
               (.setDaemon true))))]
-    (Executors/newSingleThreadExecutor thread-factory)))
+    (java.util.concurrent.ThreadPoolExecutor.
+      1 1 0 TimeUnit/MILLISECONDS
+      (java.util.concurrent.LinkedBlockingQueue. 1000)
+      thread-factory)))

Also applies to: 62-71

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/clojure_mcp/training_log.clj` around lines 14 - 17, Make the background
writer executor used by record-tool-call! bounded, replacing the default
unbounded queue created by Executors/newSingleThreadExecutor with an explicitly
capacity-limited executor configuration. Preserve immediate non-blocking
submission and the existing RejectedExecutionException handling, ensuring the
docstring’s “bounded” behavior matches the implementation.

Comment thread src/clojure_mcp/training_log.clj
Comment thread src/clojure_mcp/training_log.clj
1. Docstring "bounded" -> "serialised" + unbounded-queue rationale
   (training_log.clj:14-24). newSingleThreadExecutor's queue IS
   unbounded — that's intentional for this opt-in dev tool:
   silently dropping training records to protect memory is worse
   than the records themselves, and unbounded queue growth only
   happens when disk I/O is stalled indefinitely (a system-wide
   problem the operator is already seeing).

2. Two-stage turn accounting fixes summary-vs-JSONL drift
   (training_log.clj record-tool-call-sync!). Previous impl bumped
   :turn-count + :tools-used before the spit — if the write threw,
   the summary reported N while the JSONL had M<N.
   Now: reservation atomically bumps :turn-index only. :turn-count
   and :tools-used advance ONLY after a successful spit. Summary
   counts match what actually landed. Failed writes leave an honest
   gap in the index sequence (a curator UI can detect it).
   Regression test: write-failure-does-not-drift-turn-count-test —
   with an I/O failure fixture, asserts turn-count stays 0 and
   failure-count bumps twice.

3. Checked .awaitTermination result (training_log.clj
   flush-and-write-summary!). Discarded false let write-summary!
   run against unsettled state. Now: WARN-log + bump failure-count
   on drain timeout so operators can detect incomplete flushes.

4. New test flush-and-summary-drain-writes-accurate-counts-test.
   Drives 3 record submissions, calls flush-and-write-summary!,
   asserts JSONL has 3 lines AND summary EDN's :turn-count = 3.
   Regression fixture for the "summary reports N while JSONL has
   M<N" class of bug the review flagged.

Test-fixture helper (test file): ensure-writer-executor! rebuilds
the executor if a prior test shut it down. Without this the drain
test would break every test that runs after it.

Tests: 8 tests, 23 assertions, 0 failures (was 6/16). Core suite
unchanged.
@theselbalancingscooter

Copy link
Copy Markdown
Author

Addressed CodeRabbit round-2 findings in df3282d:

  1. Docstring/executor "bounded" claim — Kept the unbounded newSingleThreadExecutor queue but rewrote the docstring to say "serialised" and explain why unbounded is intentional for this opt-in dev tool: silently dropping training records to protect memory is worse than the records themselves, and unbounded queue growth only happens when disk I/O is stalled indefinitely (a system-wide problem the operator is already seeing). If bounded-with-drop is preferred here, happy to swap to ThreadPoolExecutor with a bounded queue + a discard policy — flag your preference.

  2. Turn-count drift on write failure — Two-stage accounting now: reservation atomically bumps :turn-index only. :turn-count + :tools-used advance ONLY after a successful spit. Summary counts now match what actually landed. Failed writes leave an honest gap in the index sequence (a curator UI can detect). Regression test write-failure-does-not-drift-turn-count-test uses an I/O failure fixture and asserts turn-count stays 0 while failure-count bumps twice.

  3. Unchecked .awaitTermination — Now checked. WARN-log + bump failure-count on drain timeout so operators detect incomplete flushes.

  4. No drain/summary test — Added flush-and-summary-drain-writes-accurate-counts-test: drives 3 record submissions, calls flush-and-write-summary!, asserts JSONL has 3 lines AND summary EDN's :turn-count = 3. Regression fixture for the "summary reports N while JSONL has M<N" class of bug the review flagged.

Tests: 8 / 23 (was 6/16). Core suite unchanged.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/clojure_mcp/training_log_test.clj (1)

42-55: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Restore all modified training-log state.

with-training-dir* resets tl/session-state and tl/failure-count, but the finally block does not restore their original values. Later tests can observe state from this test namespace.

Proposed fix
 (defn- with-training-dir* [dir f]
-  (let [original @#'tl/training-dir]
+  (let [original @#'tl/training-dir
+        original-enabled? @#'tl/enabled?
+        original-session-state @@#'tl/session-state
+        original-failure-count `@tl/failure-count`]
     (try
       ...
       (finally
         (alter-var-root #'tl/training-dir (constantly original))
-        (alter-var-root #'tl/enabled? (constantly (some? original)))))))
+        (alter-var-root #'tl/enabled? (constantly original-enabled?))
+        (reset! @#'tl/session-state original-session-state)
+        (reset! tl/failure-count original-failure-count)))))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/clojure_mcp/training_log_test.clj` around lines 42 - 55, Update
with-training-dir* to capture the original values of tl/session-state and
tl/failure-count before resetting them, then restore both atoms in the finally
block alongside training-dir and enabled?.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@test/clojure_mcp/training_log_test.clj`:
- Around line 42-55: Update with-training-dir* to capture the original values of
tl/session-state and tl/failure-count before resetting them, then restore both
atoms in the finally block alongside training-dir and enabled?.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f178e31c-4297-4b01-bb62-264c165013b3

📥 Commits

Reviewing files that changed from the base of the PR and between 8f7c393 and df3282d.

📒 Files selected for processing (2)
  • src/clojure_mcp/training_log.clj
  • test/clojure_mcp/training_log_test.clj
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/clojure_mcp/training_log.clj

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.

2 participants