mcpField
C
clj-debug

clj-debug

github.com/humorless開発4460 回インストール22 週間前に更新
リポジトリ

clj-debug について

clj-debug は、Clojure 開発者向けの REPL デバッグスキルです。ソースファイルを変更する前に nrepl で値を調べて修正を試す方法と、変更した Integrant コンポーネントのリセットが必要な場合を説明します。

主な機能

  1. 01nrepl で値を捕捉して確認
  2. 02ソースファイルを編集せず修正を検証
  3. 03エラーとスタックトレースから問題箇所を特定
  4. 04必要に応じた Integrant コンポーネントのリセット
  5. 05コミット前の一時的な def の削除

活用シーン

  1. 01関数の引数や呼び出し規約の誤りの診断
  2. 02ソースに反映する前の修正の検証
  3. 03古い動作が残るコンポーネントの調査

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/humorless/clj-native-agent --skill clj-debug

インストール後、Claude Code で/clj-debugを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

CHANGELOG.md
IMPROVEMENT_SUMMARY.md
evals/
evals.json
iteration-1-grading.md
SIDE_BY_SIDE_COMPARISON.md

詳細

ClojureでREPLベースのデバッグを行う理由

ログを追加してテストを再実行すると、フィードバックループが遅くなります。ClojureのREPLを使えば、失敗した箇所で値を直接調べ、修正をすぐにテストし、ソースファイルに手を加える前に修正内容を検証できます。これは、printlnや正式なデバッガーを使うよりも高速で安全です。

基本パターン:ログを追加する前に立ち止まる

危険信号: デバッグのためにprintln、tap>、(log ...)を追加しようとしている、またはソースコードを変更しようとしている。

代わりに: nreplでインラインのdefを使います。ファイルを変更せずに変更後のコードを直接評価し、値を捕捉して調べ、修正をテストしてから、自信を持ってコミットします。

インラインdefパターン:捕捉して調べる

関数が失敗したら、関数内に一時的な(def variable-name variable-name)を追加し、変更後の関数をnreplで評価します。これにより、ソースファイルを変更せずに値を捕捉できます。

例:呼び出し規約のバグを見つける

関数が失敗します:

(defn foo [& [{:keys [a b c] :as m}]]
  (+ a b c))

(foo :a 1 :b 2 :c 3)
;; => java.lang.NullPointerException

nreplで、引数を捕捉するためにインラインdefを加えた関数を書きます:

;; Evaluate this in nrepl (don't modify the source file):
(defn foo [& [{:keys [a b c] :as m}]]
  (def m m) ;; TODO: remove before commit
  (+ a b c))

;; Now call it:
(foo :a 1 :b 2 :c 3)
;; => java.lang.NullPointerException

;; Inspect what was captured:
m
;; => :a
;;    Aha! m is :a, not a map. The caller is passing wrong format.

これでバグが分かりました。この関数が想定しているのはキーワード引数ではなく、マップ{:a 1 :b 2 :c 3}です。

例:コミット前にnreplで修正をテストする

問題を理解したら、nreplで修正版をテストします:

;; Test 1: Fix the calling convention
(foo {:a 1 :b 2 :c 3})
;; => 6   ✓ Works!

;; Test 2: Or fix the function to accept varargs correctly
(defn foo [& [{:keys [a b c] :as m}]]
  ;; No def here — we understand the bug now
  (+ a b c))

(foo {:a 1 :b 2 :c 3})
;; => 6   ✓ Verified!

;; Or change the function signature:
(defn foo [& args]
  (def args args) ;; Capture to verify varargs format
  (let [{:keys [a b c]} (first args)]
    (+ a b c)))

(foo {:a 1 :b 2 :c 3})
;; args => ({:a 1, :b 2, :c 3})   ✓ Correct format now

nreplで修正が機能したら、自信を持ってソースファイルを更新します。コミットする前に、(def ...)の行を削除してください。

発見と修正のワークフロー

これが一連の完全なサイクルです。各ステップの評価には/breplを使います:

digraph debugging_workflow {
    rankdir=TB;
    node [shape=box, style=rounded];
    edge [fontsize=10];
    
    "Test Fails or\nError Occurs" -> "Read Error\nMessage &\nStack Trace";
    
    "Read Error\nMessage &\nStack Trace" -> "Identify Where\nto Inspect\n(Which function/\nwhich args?)";
    
    "Identify Where\nto Inspect\n(Which function/\nwhich args?)" -> "Write Function\nwith inline def\nin nrepl\n(def var var)";
    
    "Write Function\nwith inline def\nin nrepl\n(def var var)" -> "Evaluate Modified\nFunction in nrepl\n(defn foo ...)";
    
    "Evaluate Modified\nFunction in nrepl\n(defn foo ...)" -> "Call Function\nwith Test Args\n(foo ...)";
    
    "Call Function\nwith Test Args\n(foo ...)" -> "Inspect Captured\nValue at REPL\n(var / keys / type)";
    
    "Inspect Captured\nValue at REPL\n(var / keys / type)" -> "Form Hypothesis\n(Why is this\nwrong?)";
    
    "Form Hypothesis\n(Why is this\nwrong?)" -> "Test Corrected Code\nin nrepl\n(defn foo ...)";
    
    "Test Corrected Code\nin nrepl\n(defn foo ...)" -> "Verify Fix Works\n(Call & verify\nresults)";
    
    "Verify Fix Works\n(Call & verify\nresults)" -> "Update Source File\n(Remove inline def,\napply fix)";
    
    "Update Source File\n(Remove inline def,\napply fix)" -> "Run Test Suite\n(Confirm fix\nsolves problem)";
}

Integrantコンポーネントの操作

Integrantコンポーネントを使うコードの場合、次の点を理解しておいてください:**コンポーネントのコードを変更してnreplで評価しても、実行中のコンポーネントに変更が反映されないことがあります。**新しいコードが読み込まれていても、古い動作が確認されることがあります。

コンポーネントをリセットするタイミング

次の判断フローを使います:

digraph integrant_decision {
    rankdir=TB;
    node [shape=diamond, style=rounded];
    
    start [label="Modified code that\nthe component\nuses?", shape=diamond];
    no_reset [label="No reset needed\n(Only inspecting\nwith def or\ntesting pure\nfunctions)", shape=box, style=rounded];
    do_reset [label="Reset component\nto pick up\nchanges", shape=box, style=rounded];
    
    start -> no_reset [label="No"];
    start -> do_reset [label="Yes"];
}

リセット方法:

;; After evaluating your code change in nrepl:
(require '[your.component :as comp] :reload)

;; Reset the component to pick up the new code:
(ig/halt! @system :component-key)
(alter-var-root #'@system (fn [s] (ig/init (:system/config @system) :component-key)))

;; Now test again — the component reflects your change

クイックチェックリスト

  • エラーメッセージとスタックトレースを読みましたか?
  • 調べる関数と引数を特定しましたか?
  • nreplで(def var var)を含む関数を書きましたか?
  • 変更後の関数定義を評価しましたか?
  • 関数を呼び出して値を捕捉しましたか?
  • 捕捉した変数(キー、型、値)を調べましたか?
  • 何が問題かについて仮説を立てましたか?
  • まずnreplで修正後のコードをテストしましたか?
  • ソースファイルを変更する前に、修正が機能することを確認しましたか?
  • コミット前にdef行を削除しましたか?
  • コンポーネントのライフサイクル(Integrantの状態)を考慮しましたか?

コメント