关于 dx-apexguru-scan
面向 Salesforce Apex 项目的技能,通过 ApexGuru SFAP Scan API 扫描性能反模式,并将违规及建议修复整理成报告。
主要功能
- 01打包项目中的 Apex 文件
- 02提交扫描并轮询状态
- 03解码并呈现违规报告
- 04按规则筛选现有结果
适用场景
- 01检查 SOQL 或 DML 循环
- 02查找缺少 WHERE 或 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 性能扫描技能
严重:必须使用脚本
每个步骤——令牌解析、压缩、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
底层的三个脚本仍然存在,且 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/——它会相对于用户的当前工作目录解析,而不是技能目录。
任何筛选/排序/分组问题(“哪个文件的问题最多?”、“仅显示循环中的 SOQL”、“按严重性细分”)都应通过使用标志重新运行 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.
(在报告前添加由 Agent 编写的归属说明行——脚本自己的汇总行已说明模式;这条重复内容会导致输出不确定,还会提及脚本无法访问的组织)
正确做法——始终这样做:
粘贴 decode-report.js --present 的完整标准输出——包括每个 ### Issue N 卡片和结尾的 ## Summary 表格;不得编辑,并在一条回复中呈现。
概览
ApexGuru 可检测 Apex 中的性能反模式(循环中的 SOQL/DML、Schema.getGlobalDescribe()、没有 WHERE/LIMIT 的 SOQL、未使用的 SOQL 字段)。
此技能负责调用 ApexGuru SFAP Scan API:将用户的 Apex 代码(项目根目录下的所有 .cls/.trigger 文件,无论目录布局如何)打包成 zip,提交扫描任务,轮询直到扫描完成,解码 base64 编码的报告,并按规则分组展示违规项,包括严重性、file:line 和建议的修复方法。
必须注明归属。 API 会返回 analysisMode:
static→ 仅分析源代码 → 将结果标记为 “仅静态分析”。full→ 结合已接入 ApexGuru 的组织提供的运行时指标进行分析 → 将结果标记为 “生产环境洞察”。
decode-report.js --present 已会在摘要行和标题(“仅静态分析”/“生产环境洞察”)中显示此归属信息——这满足强制注明归属的要求。请将该行作为确切输出打印;不要自行撰写归属说明,也不要提及组织名称。如果用户预期得到 full 结果,却得到 static,脚本的静态模式提示行已说明该组织尚未接入 ApexGuru——请引导用户查看该提示,而不要重复说明(参见错误处理)。
**范围内:**打包项目中的 Apex、提交并轮询扫描任务、解码并展示违规项、筛选/分组现有结果、排查 API 错误。
**不在范围内:**通用静态分析/安全/lint(→ dx-code-analyzer-run,该工具将 ApexGuru 列为一个引擎)、对代码应用修复、将组织接入 ApexGuru、生成 SFAP 令牌。
前提条件
- 已通过身份验证的
sfCLI 组织(sf org login web ...)。resolve-token.sh会通过<instanceUrl>/ide/auth从中生成 SFAP JWT——这是常规的 IDE 会话方式。或者,设置APEXGURU_SFAP_TOKEN/APEXGURU_SFAP_TOKEN_FILE直接提供 JWT(适用于 CI/无头环境)。组织信息取自令牌的tnk声明——无需传入组织 ID。传入--org <alias>可指定组织。 参见<skill_dir>/references/authentication.md。如果无法解析令牌,脚本会返回清晰的错误提示。 - PATH 中需包含
sf、bash、curl、zip、jq、node(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 字段),请转达提示并停止。
步骤 3:提交并轮询
bash "<skill_dir>/scripts/run-scan.sh" "./apexguru-${TS}.zip" "./apexguru-raw-${TS}.json"
- 如果用户希望运行更快/成本更低,请添加
--fast(跳过耗费大量 LLM 资源的修复生成)。 - 端点遵循令牌的环境——基础 URL 根据令牌的
tnk声明派生:生产组织访问api.salesforce.com,内部 stage/dev 组织访问stage./dev.api.salesforce.com。客户使用生产组织进行身份验证,因此始终访问生产环境;无需额外标志或配置。 --org <alias>用于选择从哪个已通过身份验证的sf组织派生 JWT(省略时使用 CLI 的默认组织)。- 进度(
QUEUED → RUNNING → SUCCEEDED)会输出到 stderr;脚本大约每 15 秒轮询一次。默认上限为 10 min(使用--max-polls、--interval调整)。 - 成功时,stdout 会输出一行 JSON 摘要,并将完整原始响应正文写入
apexguru-raw-${TS}.json。失败时,stdout 为{error, httpStatus, status, hint}——转达其中的提示。有关状态码的详细信息,请参阅<skill_dir>/references/error-handling.md。 - 仅限前台运行。 不要将其放到后台;必须观察轮询输出。
- 扫描状态为 SUCCEEDED 并不代表流程结束。 原始结果是 base64 数据块,不是面向用户的答案。扫描成功后不要停止或报告“已完成”——必须继续执行步骤 4,解码并呈现报告。在步骤 3 结束本轮对话,用户将得不到任何可读内容。
步骤 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 热点”排名表中,而不是为每个方法重复显示近似相同的卡片。请原样向用户输出此内容——立即呈现,不要暂停询问,也不要将其重新总结成更短的表格。
对于步骤 5 的深入分析(筛选/分组现有结果),可以使用不带 --present 的 JSON 格式——请参阅下方的读取规则,这些规则适用于每次在不带 --present 的情况下运行脚本。
不要: 编造脚本代码、使用裸的 ./scripts/... 路径、内联解码 base64、对 report 字段使用 jq,或直接读取原始文件。
阅读裸(非 --present)decode-report.js 输出的说明
该命令会向 stdout 输出一个 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构建“按规则分类的违规项”表,每个条目占一行: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字段(修复代码)并非对每条规则都有填充(特别是ExpensiveMethods,这是一项 CPU 排名,不存在单行修复);不要说“没有可用的修复方法”,只需省略该列。- 使用
--full时,每个组还会包含一个items数组(该规则的所有违规项,而不只是 3 项sample)——用户需要某条规则的完整列表时(例如“显示所有 SOQL 未使用字段的违规项”),使用items而不是sample。
演示模板(备用 — 仅在不使用 --present 时)
按照上文步骤 4,--present(默认选项)已渲染出“读取裸输出说明”部分所述的完整输出,包括严重性图例、问题卡片和汇总表 — 只需原样打印其标准输出。仅当确实无法使用 --present 时(例如没有 Markdown 渲染器的脚本/CI 环境),才根据裸 JSON 手动构建表格:
填写 <Static only | Production insights> 标题占位符: 根据 attribution 字段(而非仅根据 analysisMode)推导标签 — 该字段已编码这三种状态:
- “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 → 一张表;11+ → 严重性计数 + 按规则分类的表 + 前 10 条。以原始结果路径结尾。不要附加自己的后续帮助提议(例如“我可以不重新扫描就深入分析……”,或“按规则筛选 / 按文件分组 / 解释违规”的选项菜单)— --present 已打印脚本的“显示全部”页脚;这是完整且获准的结尾语句,添加自己的内容会导致输出不确定。
规则目录详情:<skill_dir>/references/violation-catalog.md。
步骤 5:深入查看结果(不重新扫描)
使用以下标志,针对同一个原始文件重新运行 decode-report.js:
| 用户说 | 标志 |
|---|---|
| “只显示循环中的 SOQL” | --rule SOQL_IN_LOOP --full |
| “只看严重的” | --severity 1 |
| “AccountService.cls 中有什么?” | --file AccountService.cls --full |
| “按文件分组” / “哪个文件问题最严重?” | --group file --top 5 |
| “按严重性细分” | --group severity |
| “全部显示” | --present(或使用 --full 输出裸 JSON) |
限制与注意事项
| 项目 | 原因 / 解决方法 |
|---|---|
使用绝对 <skill_dir> 路径运行脚本 | ./scripts/ 会相对于用户的当前工作目录解析,而非 skill 目录 |
| 任意项目布局均可 | API 会遍历整个压缩包查找 Apex;build-zip.sh 会收集根目录下的所有 .cls/.trigger,不要求存在 force-app/ |
切勿内联解码 report | 它是一个很大的 base64 数据块 — 始终使用 decode-report.js |
初始解码时使用 --present | 它隐含 --full(不会静默截断),并直接渲染可直接展示的 Markdown — 包含严重性图例、逐项问题卡片和结尾汇总表,呈现密度与参考 MCP 工具一致 |
| 切勿重新扫描以筛选 | 步骤 5 会即时重新解码现有原始文件 |
| 归属信息已预先渲染 | --present 已打印模式行(“Static only” / “Production insights”)— 按原样输出;切勿自行撰写归属说明句或提及组织名称 |
预期为 full,结果却是 static | 组织尚未接入 ApexGuru — 告知用户,不要将其视为错误 |
| 401 / 403 / 404 / 400 | 令牌 / 组织所有权 / scanId / zip 问题 — 请参阅 references/error-handling.md |
| 仅在前台运行,轮询间隔约 15 秒 | 切换到后台会丢失进度;扫描可能需要几分钟 |
| 令牌是机密信息 | resolve-token.sh 不会回显令牌;不要打印令牌,也不要将其写入结果文件 |
| 不是安全/代码检查扫描器 | 对于 PMD/ESLint/安全扫描,请使用 dx-code-analyzer-run |
参考资料与脚本索引
脚本(使用绝对路径前缀 <skill_dir>/,通过 bash/node 执行,切勿使用 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 响应示例和一个解码后的摘要示例。