dx-apexguru-scan について
Salesforce Apex プロジェクト向けのスキルです。ApexGuru SFAP Scan API で性能上のアンチパターンを検査し、違反と修正案をレポートにまとめます。
主な機能
- 01プロジェクト内の Apex ファイルをパッケージ化
- 02スキャンの送信と状態のポーリング
- 03違反レポートのデコードと表示
- 04既存結果をルールで絞り込み
活用シーン
- 01SOQL や DML のループ内実行を確認
- 02WHERE または LIMIT のない SOQL を検出
- 03重大度でスキャン結果を絞り込み
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ forcedotcom/ sf-skills --skill dx-apexguru-scanインストール後、Claude Code で/dx-apexguru-scanを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
examples/README.mdsample-decoded-summary.jsonsample-full-no-runtime-response.jsonsample-succeeded-response.jsonreferences/api-reference.mdauthentication.mderror-handling.mdviolation-catalog.mdscripts/build-zip.shdecode-report.jsresolve-token.shrun-scan.shscan.shvalidate-token.js詳細
ApexGuru パフォーマンススキャンスキル
重大:スクリプトの使用は必須です
トークンの解決、ZIP 化、API 呼び出し、レポートのデコードなど、すべての手順で <skill_dir>/scripts/ に含まれるスクリプトを必ず使用してください。例外はありません。
誤り — 絶対に行わないでください:
# WRONG: hand-rolled curl to the API
curl -X POST https://api.salesforce.com/... -F [email protected]
# WRONG: inline base64 + jq to read the report
cat raw.json | jq -r .report | base64 -d | jq '.[]'
# WRONG: reading the raw result file directly (report is a large base64 blob)
Read tool → apexguru-raw-*.json
# WRONG: inline node/python to parse violations
node -e "const r = require('./raw.json'); ..."
正しい方法 — 常にこのようにしてください:
# PREFERRED — one command runs all three steps (package → submit+poll →
# decode+present) and prints the ready-to-show report as its final stdout.
# Use this for every initial scan: it cannot be left half-finished.
bash "<skill_dir>/scripts/scan.sh" "<project-root>"
# Optionally persist the presented markdown to a file as well:
bash "<skill_dir>/scripts/scan.sh" "<project-root>" --out ./apexguru-report.md
基盤となる3つのスクリプトは引き続き利用でき、scan.sh が順番に呼び出します。
個別に実行するのは、すでにスキャン済みの結果を詳しく調べる場合(ステップ5)、または中間生成物を意図的に確認する必要がある場合に限ります。
# Equivalent manual chain (scan.sh runs exactly these, in this order):
bash "<skill_dir>/scripts/build-zip.sh" "<project-root>" "./apexguru-<TS>.zip"
bash "<skill_dir>/scripts/run-scan.sh" "./apexguru-<TS>.zip" "./apexguru-raw-<TS>.json"
node "<skill_dir>/scripts/decode-report.js" "./apexguru-raw-<TS>.json" --present
# Drill into a subset WITHOUT re-scanning (reuse the raw file scan.sh left, or
# pass --raw to scan.sh to keep it at a known path):
node "<skill_dir>/scripts/decode-report.js" "./apexguru-raw-<TS>.json" --rule SOQL_IN_LOOP --full
node "<skill_dir>/scripts/decode-report.js" "./apexguru-raw-<TS>.json" --group file --top 5
<skill_dir> は、この SKILL.md を含むディレクトリの絶対パスです。
絶対に ./scripts/ を使用しないでください。これはスキルのディレクトリではなく、ユーザーの CWD を基準に解決されます。
フィルター、ランク付け、グループ化に関する質問(「問題が最も多いファイルはどれですか?」「SOQL-in-loop のみを表示」「重大度別に分類」など)には、同じ生の結果ファイルを指定してフラグ付きで decode-report.js を再実行し、回答してください。再スキャンしたり、JSON を手作業で解析したりしてはいけません。
重大:--present の出力は一字一句そのまま提示してください — 要約してはいけません
decode-report.js --present(ステップ4)は、すぐに提示できる最終的な Markdown をすでに生成します。重大度の凡例、違反ごとの詳細カード(メッセージ、コード、修正方法、リソースリンク)、最後のサマリーテーブルが含まれます。この標準出力がそのまま回答です。
出力されたとおりにユーザーに提示してください。より短い表に書き換えたり、問題ごとのカードを省いてサマリーテーブルだけにしたり、「違反について説明して」とユーザーに求められるまでメッセージ、修正方法、リソースを記載しなかったりしてはいけません。要約すると、--present の目的そのものが損なわれます。
帰属情報はすでに標準出力に含まれています。サマリー行には、モードを示す正確な出力(例:「ApexGuru (static analysis) is active. To unlock runtime intelligence…」)が記載されています。独自の帰属説明文を前後に追加してはいけません(「帰属情報:analysisMode: static…」のような記述、組織名の記載、「静的解析のみの検出結果」の言い換えなど)。スクリプトの行が、承認済みの完全な表現です。独自の文言を追加すると、出力が非決定的になり、意図から外れます。
誤り — 絶対に行わないでください:
Top Issues (worst first)
# Severity Rule Method Line
1 Major UsingTheTestMethodKeyword legacy... 136
...
Key Antipatterns Detected:
- SOQL/DML in loops (3 violations)
(メッセージ、コード、修正方法をすべて省いた手作りの要約 — それらを含む違反についても同様です)
Attribution: analysisMode: static — source-only analysis. The scanned org
(ag-skills-org) is not onboarded to ApexGuru's full runtime metrics, so
these are static-only findings.
(レポートの前に、エージェントが作成した帰属情報の行を追加 — スクリプト自身のサマリー行にすでにモードが記載されており、この重複は非決定的で、スクリプトがアクセスできなかった組織名まで記載しています)
正しい方法 — 常にこのようにしてください:
decode-report.js --present の標準出力全体を貼り付けてください。すべての ### Issue N カードと最後の ## Summary テーブルを含め、編集せずに1つの回答で提示します。
概要
ApexGuru は、Apex におけるパフォーマンスのアンチパターン(ループ内の SOQL/DML、
Schema.getGlobalDescribe()、WHERE/LIMIT のない SOQL、未使用の SOQL フィールド)を検出します。
このスキルは ApexGuru SFAP Scan API を呼び出します。ユーザーの Apex(プロジェクトルート以下の
すべての .cls/.trigger ファイル。配置場所は問いません)を zip にまとめて送信し、スキャンが
完了するまでポーリングし、base64 エンコードされたレポートをデコードして、違反をルール別に
重大度、file:line、修正案とともに提示します。
帰属情報の表示は必須です。 API は analysisMode を返します。
static→ ソースのみの分析 → 結果に 「Static only」 と表示します。full→ ApexGuru にオンボーディング済みの組織から取得したランタイムメトリクスで拡充 → 結果に 「Production insights」 と表示します。
decode-report.js --present は、この帰属情報をサマリー行とタイトル(「Static only」/「Production insights」)に
すでに表示しており、必須の帰属情報表示要件を満たしています。この行を正確な出力として表示してください。
独自の帰属情報に関する文を作成したり、組織名を挙げたりしないでください。ユーザーが full を想定していたのに
static になった場合、スクリプトの static モードの行に組織がオンボーディングされていないことがすでに説明されています。
この説明を繰り返すのではなく、その行を参照するよう案内してください(エラー処理を参照)。
対象範囲: プロジェクトの Apex の zip 化、スキャンの送信/ポーリング、違反のデコードと提示、 既存結果のフィルタリング/グループ化、API エラーのトラブルシューティング。
対象外: 一般的な静的解析/セキュリティ/Lint(ApexGuru をエンジンの一つとして列挙する
dx-code-analyzer-run を使用してください)、コードへの修正の適用、組織の ApexGuru へのオンボーディング、
SFAP トークンの発行。
前提条件
- 認証済みの
sfCLI 組織(sf org login web ...)。resolve-token.shは<instanceUrl>/ide/authを通じてそこから SFAP JWT を取得します。これは通常の IDE セッションでの方法です。代わりに、JWT を直接指定する場合(CI/ヘッドレス)はAPEXGURU_SFAP_TOKEN/APEXGURU_SFAP_TOKEN_FILEを設定してください。組織はトークンのtnkclaim から特定されます。組織 ID は渡しません。特定の組織を選ぶには--org <alias>を渡してください。<skill_dir>/references/authentication.mdを参照してください。トークンを解決できない場合、 スクリプトはヒント付きの明確なエラーを返します。 sf、bash、curl、zip、jq、nodeが PATH 上にあること(macOS/Linux の開発環境では標準的です)。- Apex を含むフォルダー — sfdx プロジェクト、
force-app/サブツリー、または.cls/.triggerファイルを含む任意のフォルダーを指定できます。build-zip.shは配置場所にかかわらず その下にあるすべての Apex を収集し、API はアーカイブ全体を検索します。
ワークフロー
ステップ 1:プロジェクトルートを特定する
プロジェクトルートは、その下のどこかにApex を含む任意のフォルダーです
(通常は sfdx-project.json と同じ階層にある sfdx プロジェクトのルートですが、force-app/
サブツリーや、.cls ファイルだけを含むフォルダーでもかまいません)。ユーザーがパスを指定した場合は
それを使用し、指定がない場合は現在の作業ディレクトリを使用します。build-zip.sh はその下にある
すべての .cls/.trigger ファイル(配置場所は問いません)を収集し、該当するファイルが一つもなければ
明確なエラーを返します。
ステップ 2:プロジェクトをパッケージ化する
TS=$(date +%Y%m%d-%H%M%S)
bash "<skill_dir>/scripts/build-zip.sh" "<project-root>" "./apexguru-${TS}.zip"
出力 JSON には zip、bytes、humanSize、apexFileCount、scanRoot が含まれます。スクリプトは
圧縮後 200MB の上限を適用し、超過した場合はすぐにエラーを返します。エラー時(error/hint
フィールドがある場合)は、そのヒントを伝えて終了します。
Step 3: 送信してポーリングする
bash "<skill_dir>/scripts/run-scan.sh" "./apexguru-${TS}.zip" "./apexguru-raw-${TS}.json"
- より高速で低コストの実行を希望する場合は、
--fastを追加します(LLM を多用する修正案の生成をスキップします)。 - エンドポイントはトークンの環境に従います。ベース URL はトークンの
tnkクレームから導出されます。prod 組織はapi.salesforce.comに接続し、内部の stage/dev 組織はstage./dev.api.salesforce.comに接続します。お客様は prod 組織で認証するため、常に prod に接続され、追加のフラグや設定は不要です。 --org <alias>を指定すると、JWT の導出元となる認証済みsf組織を選択できます(省略すると、CLI のデフォルト組織を使用します)。- 進行状況(
QUEUED → RUNNING → SUCCEEDED)は stderr に出力されます。スクリプトは約 15 秒ごとにポーリングします。デフォルトの上限は 10 分です(--max-polls、--intervalで調整できます)。 - 成功すると、stdout に 1 行の JSON サマリーが出力され、完全な生データの本文が
apexguru-raw-${TS}.jsonに書き込まれます。失敗すると、stdout は{error, httpStatus, status, hint}となります。hint を伝えてください。ステータスコードの詳細については、<skill_dir>/references/error-handling.mdを参照してください。 - フォアグラウンドでのみ実行してください。 バックグラウンドで実行してはいけません。ポーリングの出力を確認する必要があります。
- スキャンが SUCCEEDED になっても、まだ完了ではありません。 生の結果は Base64 の塊であり、ユーザーに提示できる回答ではありません。スキャンが成功した時点で停止したり、「完了」と報告したりせず、レポートをデコードして提示するため、必ずステップ 4 に進んでください。ステップ 3 でターンを終えると、ユーザーには読める内容が何も残りません。
Step 4: デコードして提示する
node "<skill_dir>/scripts/decode-report.js" "./apexguru-raw-${TS}.json" --present
提示用にデコードする際は、--present をデフォルトで使用します。これは --full を含み(暗黙の上限なし)、そのまま提示できる Markdown を出力します。内容は、深刻度の凡例(Minor/Major/Critical。analysisMode: full で本番メトリクスに基づいて深刻度が補足される場合は Tip マーカーも表示)、ホットスポット以外のルールに関する違反ごとの ### Issue N カード(メッセージ、現在のコード、推奨される修正、ヘルプドキュメントへのリンク。上限は --top で指定し、デフォルトは 10 件。深刻度の高い順に表示し、上限は見出しに明記)、およびカードの上限にかかわらずすべての違反を一覧表示する最後の ## Summary テーブルです。ExpensiveMethods(full モードでのメソッドごとの CPU ホットスポットランキングであり、行単位のアンチパターンではありません)は、メソッドごとにほぼ同じカードを繰り返すのではなく、独立した「CPU Hotspots」ランキングテーブルにまとめられます。この出力はそのままユーザーに提示してください。すぐに提示し、いったん止まって確認を求めたり、より短いテーブルに再要約したりしてはいけません。
ステップ 5 での詳細確認(既存の結果のフィルタリング/グループ化)では、JSON のみを出力する形式(--present なし)で問題ありません。スクリプトを --present なしで実行する場合は、以下の読み取りルールを参照してください。
禁止事項: スクリプトコードをでっち上げること、./scripts/... のようなパスをそのまま使うこと、Base64 をインラインでデコードすること、report フィールドを jq で処理すること、生ファイルを直接 Read すること。
--present を指定しない場合の decode-report.js 出力の読み方
このコマンドは、1 つの JSON オブジェクトを標準出力に出力します。何かを提示する前に、各フィールドを順に確認してください。一部だけをざっと見て、全体を確認したつもりにならないでください。
- 何よりも先に
truncatedを確認してください。trueの場合、groupsは上位--top(デフォルトは 10)件のルールに、各グループのsampleは 3 件に、topViolationsは--top件に制限されています。truncated:trueの結果を全体像として提示してはいけません。 同じコマンドに--fullを追加して再実行し、その出力を使用してください。ユーザーが簡易的な結果や一部の結果を明示的に求めた場合に限り、これを省略できます。 analysisMode/attributionに基づく帰属を明記してください —static/「静的のみ」またはfull/「本番環境のインサイト」です。上記の「帰属の明記は必須」に従い、毎回必ず記載してください。serverViolationBreakdownは、API 内部のルールコード(例:SOQL_IN_LOOP_1HOP、GGD)の集計値です。整合性確認用の合計値(violationCountと合致します)であり、表示名ではありません。これらのコードをユーザーに見せてはいけません。代わりに、人が読めるgroups[].keyの名前(例:SoqlInALoopOneHop、SchemaGetGlobalDescribeNotEfficient)を使用してください。- トップレベルの
severityCountsは、すべての違反の重大度の内訳です。概要テーブルにはこれを使用してください。各groups[]エントリにも、そのルールだけを対象とする独自のseverityCountsがあります。 - 「ルール別の違反」テーブルは
groupsから作成し、各エントリにつき 1 行を設けてください。key→ ルール、count→ 件数、severityCounts→ 重大度、sample[0](または--fullの場合はitems[0])→ 例(file:line)とします。 - 「主な問題」テーブルは
topViolationsから作成してください。 これは重大度の高い順に並んでいます。rule、severity、file:line、およびfixesが空でなければその最初の項目を修正案として使用してください。fixesが空の場合は、修正案の欄に内容を作り足さず、空欄にしてください。 - ユーザーが特定の違反について説明を求めた場合(「これはどういう意味ですか」「なぜフラグが立っていますか」など)、その違反の
message(平易な言葉で説明した理由)とresources[0](ヘルプドキュメントの URL)を、どちらもそのまま提示してください。これらはすべての違反オブジェクトに含まれていますが、一覧性を保つため、手順 5/6 の概要テーブルには含めていません。messageが空の場合に限り、references/violation-catalog.mdを参照してください。 fixesが[]でも問題ではありません。API のsuggestionsフィールド(修正コード)は、すべてのルールに設定されているわけではありません(特に、1 行で修正できない CPU ランキングのExpensiveMethods)。「修正方法がありません」とは言わず、修正案の欄を空欄にしてください。--fullを指定すると、各グループにはitems配列(そのルールに該当するすべての違反。3 件のsampleだけではありません)も含まれます。ユーザーが特定ルールの該当項目すべてを求めている場合(「未使用フィールドの SOQL をすべて見せて」など)は、sampleではなくitemsを使用してください。
プレゼンテーションテンプレート(フォールバック — --present を使用しない場合のみ)
--present(上記のステップ 4 に従い、デフォルト)は、「未加工の出力を読むための手順」セクションで説明されている、重大度の凡例+問題カード+サマリーテーブルを含む完全な出力をすでにレンダリングします。標準出力をそのまま表示してください。未加工の JSON から手作業でテーブルを作成するのは、--present を実際に使用できない場合(例:Markdown レンダラーのないスクリプト/CI 環境)に限ってください。
<Static only | Production insights> タイトルのプレースホルダーの設定: ラベルは analysisMode だけでなく、attribution フィールドから導き出してください。そこには 3 つの状態がすでに反映されています。
- 「Production insights」(
analysisMode: fullかつ ランタイムメトリクスあり)— 本番環境のランタイムメトリクスで情報が補完されています。 - 「Static only」+
analysisMode: full(ランタイムメトリクスなし)— 組織はオンボーディング済みですが、このコードのランタイムデータはまだありません。Scale Center でランタイムレポートを生成してください。 - 「Static only」+
analysisMode: static— ソースコードのみです。本番環境のインサイトを得るには、組織を ApexGuru にオンボーディングしてください。
以下のコードフェンスは、レンダリングされる出力そのものです。実際の値に置き換えて表示してください。上記のガイダンスは出力しないでください。
## ApexGuru Scan Complete — <Static only | Production insights>
**Found X performance violations** across Y files.
| Severity | Count |
|----------|-------|
| Critical (1) | X |
| High (2) | X |
| Moderate (3) | X |
### Violations by Rule
| Rule | Count | Severity | Example |
|------|-------|----------|---------|
| SOQL_IN_LOOP | 15 | High (2) | AccountService.cls:42 |
| DML_IN_LOOP | 8 | Critical (1) | AccountService.cls:60 |
| GGD | 2 | Moderate (3) | Utils.cls:12 |
### Top Issues
| # | Rule | Sev | File:Line | Suggested Fix |
|---|------|-----|-----------|---------------|
| 1 | DML_IN_LOOP | 1 | AccountService.cls:60 | Collect records; DML once after the loop |
| ... up to 10 |
Raw result: `./apexguru-raw-<TS>.json`
結果のサイズに応じて表示を調整してください。0 件なら「パフォーマンスのアンチパターンは見つかりませんでした」、1~10 件なら 1 つのテーブル、11 件以上なら重大度別の件数+ルール別テーブル+上位 10 件を表示します。最後に未加工の結果ファイルのパスを記載してください。独自の追加案内(「再スキャンせずに詳しく調べられます……」、または「ルールで絞り込む/ファイルでグループ化する/違反内容を説明する」といったメニュー)を追加しないでください。--present はスクリプトの「すべて表示」フッターをすでに出力しています。これが承認済みの完全な締めくくりの行であり、独自の文言を加えると出力が非決定的になります。
ルールカタログの詳細:<skill_dir>/references/violation-catalog.md。
ステップ 5:結果を詳しく調べる(再スキャンなし)
同じ未加工ファイルに対して decode-report.js を次のフラグで再実行します。
| ユーザーの指定 | フラグ |
|---|---|
| 「SOQL-in-loop だけを表示して」 | --rule SOQL_IN_LOOP --full |
| 「重大なものだけ」 | --severity 1 |
| 「AccountService.cls の内容は?」 | --file AccountService.cls --full |
| 「ファイル別にグループ化して」/「最も問題の多いファイルは?」 | --group file --top 5 |
| 「重大度別に内訳を表示して」 | --group severity |
| 「すべて表示して」 | --present(または未加工の JSON を表示するには --full) |
制約事項と注意点
| 項目 | 理由/対処方法 |
|---|---|
スクリプトは絶対パスの <skill_dir> を使って実行する | ./scripts/ はスキルのディレクトリではなく、ユーザーの CWD を基準に解決されます |
| プロジェクトのレイアウトは問いません | API はアーカイブ全体を走査して Apex を調べます。build-zip.sh はルート以下のすべての .cls/.trigger を収集するため、force-app/ は不要です |
report をインラインでデコードしない | 大きな base64 blob です。必ず decode-report.js を使用してください |
最初のデコードには --present を使用する | --full(暗黙の上限なし)を含み、表示用の Markdown をそのままレンダリングします。重大度の凡例、問題ごとのカード、締めくくりのサマリーテーブルが含まれ、参照 MCP ツールの表示密度を再現します |
| 絞り込みのために再スキャンしない | ステップ 5 では、既存の未加工ファイルを瞬時に再デコードします |
| Attribution はレンダリング済み | --present はモード行(「Static only」/「Production insights」)をすでに出力しています。出力をそのまま表示してください。独自の attribution 文を作成したり、組織名を記載したりしないでください |
full が想定されるところで static が返る | 組織が ApexGuru にオンボーディングされていません。エラーとして扱わず、ユーザーに伝えてください |
| 401/403/404/400 | トークン/組織の所有権/scanId/zip の問題です。references/error-handling.md を参照してください |
| フォアグラウンドのみ、ポーリング間隔は約 15 秒 | バックグラウンドで実行すると進捗が失われます。スキャンには数分かかることがあります |
| トークンは秘密情報 | resolve-token.sh はトークンを出力しません。トークンを表示したり、結果ファイルに書き込んだりしないでください |
| セキュリティ/lint スキャナーではない | PMD/ESLint/セキュリティのチェックには dx-code-analyzer-run を使用してください |
リファレンスとスクリプト一覧
スクリプト(bash/nodeで、絶対パスの<skill_dir>/を先頭に付けて実行します。Readは使用しないでください):
| ファイル | 使用するタイミング |
|---|---|
<skill_dir>/scripts/resolve-token.sh | SFAP JWTとベースURLの解決(run-scan.shから呼び出されます) |
<skill_dir>/scripts/validate-token.js | ローカルでのJWT事前チェック(ネットワーク不要):環境変数/スコープ/有効期限(resolve-token.shから呼び出されます) |
<skill_dir>/scripts/build-zip.sh | ステップ2 — プロジェクトのApexを集め、サイズをチェックしたzipを作成 |
<skill_dir>/scripts/run-scan.sh | ステップ3 — 完了まで送信とポーリングを実行 |
<skill_dir>/scripts/decode-report.js | ステップ4~5 — base64レポートをデコードし、違反をグループ化/フィルタリング |
リファレンス(必要に応じて参照):
| ファイル | 参照するタイミング |
|---|---|
references/authentication.md | SFAP JWTの取得元、環境変数/ファイルの設定 |
references/api-reference.md | エンドポイントの契約、リクエスト/レスポンスの形式、制限 |
references/violation-catalog.md | ApexGuruルールの意味と一般的な修正方法 |
references/error-handling.md | 400/401/403/404、FAILED、タイムアウト、静的解析とフル解析の診断 |
examples/には、SUCCEEDEDレスポンスのサンプルとデコード済みサマリーのサンプルが含まれています。