Claude Media
Claude CodeでSalesforce Code Analyzerの指摘を直す・抑える・保留に振り分ける

Claude CodeでSalesforce Code Analyzerの指摘を直す・抑える・保留に振り分ける

sf code-analyzer runのJSON出力をclaude -pに読ませ、修正・抑制・要判断の3分類にする手順。jqでの絞り込みと--json-schemaの固定、抑制マーカーの落とし穴までまとめます。

Salesforce Code Analyzerを既存のorgのソースに初めて当てると、指摘は数百件単位で並びます。全部を直す必要はありません。直すもの、理由を残して抑えるもの、人が決めるものに分けて、初めて次の作業が決まります。

この仕分けは、sf code-analyzer runのJSON出力をclaude -pに読ませれば自動化できます。ただし一括で「直して」と頼むと、抑制マーカーが大量に付くか、判断が要る指摘まで書き換えられます。そこで、仕分けと適用を2回の呼び出しに分けます。1回目は分類だけを返させ、人が確認してから2回目で手を入れる流れです。

先に結論: 分類と適用を別の呼び出しにする

流れは4段です。

手順

指摘を仕分けて直すまでの流れ

  1. 1

    JSONで結果を出す

    sf code-analyzer run --output-file results.jsonで、結果をJSONファイルに書き出します。

  2. 2

    jqで必要な項目だけに絞る

    違反1件ごとの規則名・重大度・場所・メッセージ・自動修正の有無だけを残し、番号を振ります。

  3. 3

    claude -pに分類だけをさせる

    --json-schemaで出力の形を固定し、番号ごとに修正・抑制・要判断のどれかを返させます。この段では何も書き換えません。

  4. 4

    人が確認してから適用する

    修正と抑制の対象を別々に渡し、再度sf code-analyzer runで件数が減ったことを確かめます。

分類を構造化出力で返させる理由は、結果を機械で扱えるからです。「要判断」の件数だけ数える、修正対象だけを次の呼び出しに渡す、といった後続の処理が、本文の文章を読み解かずに書けます。

Code AnalyzerのJSONに入っている項目

sf code-analyzer runは、既定ではRecommendedのルールで、カレントフォルダ全体を対象にします。結果をファイルにするには--output-fileを使い、拡張子で形式が決まります。.jsonのほか、.csv、.html、.sarif、.xmlが指定できます。出力先のフォルダが無いとエラーになり、同名のファイルは確認なしで上書きされます。

sf code-analyzer run \
  --rule-selector Recommended \
  --workspace . \
  --include-fixes \
  --output-file results.json

--include-fixesを付けると、自動修正が用意されている違反に、置換する場所とコードが付きます。この有無が、後で「直す」に倒す根拠の1つになります。--include-suggestionsは、修正の方向を文章で示す提案を足すフラグです。どちらも、付けると解析時間が延びる場合があります。

JSONの中身は、コアのソースにある型定義で確認できます。トップレベルはrunDir、violationCounts、versions、violationsです。violationsの1件が持つ項目は次のとおりです。

項目内容
rule / engine内容規則名と、それを実行したエンジン名
severity内容1(Critical)から5(Info)までの数値
tags内容規則に付いたタグ(Recommendedなど)
locations / primaryLocationIndex内容違反の位置の配列と、主たる位置の添字
message / resources内容違反メッセージと、関連するURLの配列
fixes / suggestions内容自動修正と提案(出力に含めた場合のみ)

位置のfileはrunDirからの相対パスです。violationCountsには重大度ごとの件数が入るので、まずjq '.violationCounts' results.jsonで全体の規模を見ておくと、次の絞り込みの判断がしやすくなります。

jqで入力を絞り、番号を振る

JSONの全文をそのまま渡すと、メッセージ以外の項目も大量に載り、claude -pが受け取れる入力の上限にも近づきます。標準入力で渡せるのは10MBまでで、超えるとエラー終了します。上限の扱いはclaude -pにログを渡すと10MB上限で止まるにまとめています。

絞り込みでは、番号を振ることが肝心です。分類の結果を元の違反に結び付けるキーが無いと、同じ規則名の指摘が複数ある場面で、どれのことか分からなくなります。

jq '[.violations | to_entries[] | {
  id: .key,
  rule: .value.rule,
  engine: .value.engine,
  severity: .value.severity,
  tags: .value.tags,
  file: .value.locations[.value.primaryLocationIndex].file,
  line: .value.locations[.value.primaryLocationIndex].startLine,
  message: .value.message,
  hasFix: ((.value.fixes // []) | length > 0)
}]' results.json > violations-slim.json

to_entriesで配列の添字をidとして取り出し、位置は主たる位置の1か所だけに畳んでいます。fixesが無い違反は// []で空配列に置き換えているので、hasFixは真偽値で揃います。

3分類の基準を先に決める

分類の基準は、Claudeに推測させず、先に文章で決めます。次はその一例です。組織のリスクの取り方に合わせて書き換えてください。

修正(fix):
  - hasFixがtrueの指摘
  - 違反メッセージだけで直し方が一意に決まり、動作が変わらない指摘
抑制(suppress):
  - テストクラスや生成コードなど、指摘の前提が当てはまらない場所の指摘
  - 意図した実装であることがコードから読み取れる指摘
要判断(review):
  - 権限・共有・入力検証など、セキュリティに関わる指摘
  - 直すと挙動やデータの扱いが変わりうる指摘
  - 上のどれにも確信が持てない指摘

最後の行が要です。迷ったものを「要判断」に倒す規則を明示しておくと、確信のない抑制が混ざりにくくなります。「抑制」も「修正」も、人が見たときに理由を説明できる指摘に限ります。

claude -pで分類だけを返させる

分類の出力は、--json-schemaで形を固定します。--output-format jsonと組み合わせると、応答のメタ情報とは別に、スキーマに沿った結果がstructured_outputに入ります。スキーマに合う出力が得られなかった場合、このフィールドは付きません。後続の処理は、存在を確かめてから使います。

次のスキーマは、各指摘にdecisionとreasonを返させる例です。decisionは列挙に限定しています。

{
  "type": "object",
  "properties": {
    "items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "decision": { "type": "string", "enum": ["fix", "suppress", "review"] },
          "reason": { "type": "string" }
        },
        "required": ["id", "decision", "reason"]
      }
    }
  },
  "required": ["items"]
}

これをtriage-schema.jsonとして保存し、絞り込んだJSONを標準入力から渡します。基準の文章はtriage-rules.txtに置いた想定です。

cat violations-slim.json | claude -p \
  "$(cat triage-rules.txt)
 
上のJSONは静的解析の指摘です。基準に従い、全件をfix・suppress・reviewに分類してください。ファイルは変更しないでください。" \
  --allowedTools "Read" \
  --output-format json \
  --json-schema "$(cat triage-schema.json)" \
  | jq '.structured_output' > triage.json

許可するツールはReadだけにしています。分類の段階では、ソースを読んで根拠を確かめられれば足り、書き換えは要りません。権限の待ちで止まらないように、使わせるツールを--allowedToolsで先に決めておくのが、非対話実行の基本です。

スクリプトやCIに組み込むなら、--bareの追加が推奨されています。フックやプラグイン、CLAUDE.mdを読まないので、どのマシンでも同じ結果になりやすくなります。その代わり、サブスクリプションのログインは使われず、ANTHROPIC_API_KEYが必要です。基準がCLAUDE.mdに頼らずtriage-rules.txtに入っていれば、この構成でも動きます。

分類が返ったら、件数を数えます。

jq '.items | group_by(.decision) | map({decision: .[0].decision, count: length})' triage.json

「要判断」が多すぎれば基準が厳しすぎ、「抑制」が過半ならば基準が緩いというのが、見直しの目安です。ここで基準を直し、分類をやり直すのは安く済みます。ソースには一切触れていないからです。

抑制は範囲と理由に気をつける

「抑制」と判断した指摘は、ソースにマーカーを書くか、設定ファイルにまとめて書くかのどちらかで抑えます。どちらも、Claudeに任せる前に挙動を知っておく必要があります。

行内マーカーは、閉じるまで効く

ソースのコメントにcode-analyzer-suppressを書くと、そのコメントから後の違反が抑制されます。括弧でルールを指定でき、eslint:no-consoleのように、エンジンと規則名をコロンでつなぎます。括弧を省いた形も書けますが、その場合は全ルールが対象です。

// code-analyzer-suppress(pmd:RuleName)
// ...抑制したい範囲...
// code-analyzer-unsuppress(pmd:RuleName)

コアのソースを読むと、code-analyzer-unsuppressが現れなかった場合、抑制の範囲はファイルの末尾まで続きます。1行だけのつもりで書いたマーカーが、その後ろの同じ規則の違反を、すべて隠してしまう形です。

--no-suppressionsの説明には、code-analyzer-suppress-lineとcode-analyzer-suppress-next-lineという名前も載っています。行単位で閉じる形式があるなら、そちらを使わせるのが安全です。お使いの版のドキュメントで書式を確かめ、使う形を指示に書いておきます。

一括抑制は、設定ファイルに理由と上限を書く

レガシーなフォルダ全体の指摘は、code-analyzer.ymlのsuppressionsに書けます。キーはファイルやフォルダのパスで、規則の選び方、抑制する件数の上限、理由を並べます。

suppressions:
  disable_suppressions: false
  "force-app/legacy":
    - rule_selector: "pmd:RuleName"
      max_suppressed_violations: 5
      reason: "旧システム連携のため。移行完了後に外す"

上限を超えた分は抑制されず、指摘として残ります。「5件までは許す」という線引きができるので、同じ規則の指摘が増えたときに気づけます。

抑制した件数を、あとから見られるようにする

--no-suppressionsを付けて実行すると、ソースのマーカーも設定ファイルの抑制も無視した結果が得られます。通常の結果と件数を比べれば、抑制がどれだけ効いているかが分かります。四半期ごとに一度回して、抑制の棚卸しをする運用に向きます。

修正と抑制を、別々に適用する

人がtriage.jsonを確認したら、適用です。ここでは対象を分けて、2回に分けて呼びます。

jq '[.items[] | select(.decision == "fix") | .id]' triage.json > fix-ids.json

修正の呼び出しでは、Editを許可し、渡すのはfixの違反だけにします。

jq --slurpfile ids fix-ids.json \
  '[.[] | select(.id as $i | $ids[0] | index($i))]' \
  violations-slim.json \
  | claude -p "この指摘を1件ずつ修正してください。指摘の範囲外のコードは変更せず、終わったらgit diffの要約だけを返してください。" \
    --allowedTools "Read,Edit"

抑制の呼び出しも同じ形ですが、指示に「マーカーは必ず対になるcode-analyzer-unsuppressで閉じる」「理由をコメントに1行添える」を入れます。reasonの文面をtriage.jsonから引いて渡せば、分類で書かせた根拠がそのままコードに残ります。

最後に、同じ条件でsf code-analyzer runを再実行します。--severity-threshold 3を付けると、重大度が3(Moderate)以上の違反が残っているときに、終了コードが0以外になります。数値はHighのような文字列でも指定できます。CIでの合否判定にもそのまま使えます。

つまずきやすい場面

落とし穴

分類の根拠は、実行するまで確かめられない

reasonは、モデルが書いた説明です。正しいかどうかは、ソースを見た人が判断するしかありません。特に「抑制」と書かれた指摘は、理由が自然に読めても、サンプルを抜き出して確認してから適用します。

  • リモート接続が要るエンジンがある: --target-orgは、ApexGuruのような、組織への接続が必要なエンジン向けのフラグです。認証済みのorgを指定するので、Claudeのsf実行権限の切り方はClaude CodeでSalesforce CLIの権限を切るを先に読んでおくと安全です。
  • 件数が多すぎる: 数千件になると、分類の呼び出し1回では入力が収まらないことがあります。jqで規則名ごとにグループ化し、規則ごとの代表例と件数だけを渡す分類から始めると、基準の見直しも速く済みます。
  • 同じ流れの別ツール: 修正・抑制・保留への振り分けは、他の静的解析にもそのまま使えます。PlatformIOの例はpio checkの指摘を修正と抑制に仕分けるにあります。
  • テストの修正まで任せない: 指摘を直した結果、Apexのテストが落ちることがあります。修正のあとにテストを回して失敗を直す手順は、CIのテスト失敗を自動修正に分けて書いています。

まとめ

Code Analyzerの指摘が多いときは、一括修正に飛びつかず、まず分類だけをclaude -pに出させます。JSONへの番号付与と--json-schemaによる形の固定で、結果は機械で数えられ、人が確認しやすくなります。抑制マーカーは閉じ忘れると範囲がファイル末尾まで伸びるので、閉じる形を指示に含めます。迷ったものを「要判断」に倒す一行が、仕分け全体の安全弁です。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/headless
  2. github.com/forcedotcom/code-analyzer
  3. github.com/forcedotcom/code-analyzer-core
この記事を共有:XはてブLinkedIn