sapbuddy-dsh 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +34 -122
  3. package/defaults/AGENTS.md +114 -114
  4. package/defaults/connections.example.json +21 -21
  5. package/defaults/prompts/Memory.md +21 -21
  6. package/defaults/prompts/SYSTEM.md +110 -110
  7. package/defaults/settings.example.json +3 -3
  8. package/dist/sap-tools/adtManager.d.ts +11 -1
  9. package/dist/sap-tools/adtManager.js +176 -9
  10. package/dist/sap-tools/connections-store.js +4 -1
  11. package/dist/sap-tools/execution.js +1 -1
  12. package/dist/sap-tools/tools/activationResult.d.ts +4 -2
  13. package/dist/sap-tools/tools/activationResult.js +33 -4
  14. package/dist/sap-tools/tools/writeTools.js +11 -9
  15. package/dist/tools.js +5 -3
  16. package/package.json +5 -5
  17. package/skills/abap-code-review/SKILL.md +75 -75
  18. package/skills/abap-code-review/assets/report-template.html +142 -142
  19. package/skills/abap-code-review/references/checklist.md +114 -114
  20. package/skills/abap-explain/SKILL.md +126 -126
  21. package/skills/abap-templates/SKILL.md +764 -764
  22. package/skills/abap-wiki/SKILL.md +104 -104
  23. package/skills/clean-abap/SKILL.md +91 -91
  24. package/skills/clean-abap/references/CleanABAP_zh.md +4309 -4309
  25. package/skills/dev-write/SKILL.md +137 -137
  26. package/skills/sap-abap-skills/README.md +205 -205
  27. package/skills/sap-abap-skills/SKILL.md +498 -498
  28. package/skills/sap-abap-skills/references/abap-dictionary.md +211 -211
  29. package/skills/sap-abap-skills/references/abap-sql.md +588 -588
  30. package/skills/sap-abap-skills/references/amdp.md +279 -279
  31. package/skills/sap-abap-skills/references/authorization.md +149 -149
  32. package/skills/sap-abap-skills/references/bits-bytes.md +92 -92
  33. package/skills/sap-abap-skills/references/builtin-functions.md +373 -373
  34. package/skills/sap-abap-skills/references/cds-views.md +412 -412
  35. package/skills/sap-abap-skills/references/cloud-development.md +299 -299
  36. package/skills/sap-abap-skills/references/constructor-expressions.md +458 -458
  37. package/skills/sap-abap-skills/references/date-time.md +415 -415
  38. package/skills/sap-abap-skills/references/design-patterns.md +292 -292
  39. package/skills/sap-abap-skills/references/dynamic-programming.md +426 -426
  40. package/skills/sap-abap-skills/references/exceptions.md +432 -432
  41. package/skills/sap-abap-skills/references/generative-ai.md +180 -180
  42. package/skills/sap-abap-skills/references/internal-tables.md +578 -578
  43. package/skills/sap-abap-skills/references/numeric-operations.md +219 -219
  44. package/skills/sap-abap-skills/references/object-orientation.md +518 -518
  45. package/skills/sap-abap-skills/references/performance.md +428 -428
  46. package/skills/sap-abap-skills/references/program-flow.md +302 -302
  47. package/skills/sap-abap-skills/references/rap-eml.md +549 -549
  48. package/skills/sap-abap-skills/references/released-classes.md +308 -308
  49. package/skills/sap-abap-skills/references/sap-luw.md +203 -203
  50. package/skills/sap-abap-skills/references/skill-reference-guide.md +98 -98
  51. package/skills/sap-abap-skills/references/sql-hierarchies.md +180 -180
  52. package/skills/sap-abap-skills/references/string-processing.md +488 -488
  53. package/skills/sap-abap-skills/references/table-grouping.md +150 -150
  54. package/skills/sap-abap-skills/references/tool-guide.md +285 -285
  55. package/skills/sap-abap-skills/references/unit-testing.md +512 -512
  56. package/skills/sap-abap-skills/references/where-conditions.md +223 -223
  57. package/skills/sap-abap-skills/references/xml-json.md +315 -315
  58. package/skills/translate-workflow/SKILL.md +69 -69
@@ -55,17 +55,46 @@ export async function verifyActivation(client, obj, activate) {
55
55
  return failedStage("激活前语法检查", `无法确认待激活源码或语法结果,未执行激活:${sanitizeErrMsg(err)}`);
56
56
  }
57
57
  const expected = sourceHash(source);
58
- const result = mergeActivationResults([syntax, await activate()], []);
59
- if (!result.success)
60
- return { ...result, sourceHash: expected };
58
+ let activation;
59
+ try {
60
+ activation = await activate();
61
+ }
62
+ catch (err) {
63
+ activation = failedStage("激活请求", sanitizeErrMsg(err));
64
+ }
65
+ const result = mergeActivationResults([syntax, activation], []);
61
66
  try {
62
67
  const active = await client.getObjectSource(uri, { version: "active" });
63
68
  if (sourceHash(active) !== expected) {
64
69
  return { ...mergeActivationResults([result, failedStage("激活版本核验", "active 源码与激活前核对的源码不一致,本次修改未确认生效。")], []), verification: "mismatch", sourceHash: expected };
65
70
  }
71
+ if (!result.success) {
72
+ // An unchanged main source alone cannot prove its INCLUDE changes took effect.
73
+ // Re-read the live inactive list; the activation response may describe an earlier stage.
74
+ const related = [obj, ...result.inactive.map(r => r.object).filter(Boolean)];
75
+ for (const line of source.split("\n")) {
76
+ const match = /^\s*INCLUDE\s+([A-Za-z/][A-Za-z0-9_/]*)\s*\./i.exec(line);
77
+ if (match)
78
+ related.push({ "adtcore:name": match[1], "adtcore:type": "PROG/I" });
79
+ }
80
+ const normalizeUri = value => String(value ?? "").split(/[?#]/)[0].replace(/\/source\/main\/?$/i, "").replace(/\/$/, "").toLowerCase();
81
+ const pending = await client.inactiveObjects();
82
+ if (!Array.isArray(pending))
83
+ throw new Error("未激活对象清单无效");
84
+ const remaining = pending.filter(r => r.object && related.some(o =>
85
+ (o["adtcore:uri"] && normalizeUri(o["adtcore:uri"]) === normalizeUri(r.object["adtcore:uri"])) ||
86
+ (o["adtcore:name"] && o["adtcore:name"].toUpperCase() === String(r.object["adtcore:name"] ?? "").toUpperCase())));
87
+ if (remaining.length) {
88
+ return { ...mergeActivationResults([result, failedStage("激活状态核验", "目标或相关 INCLUDE 仍存在未激活版本,保留失败计数。")], []), inactive: remaining, verification: "pending", sourceHash: expected };
89
+ }
90
+ return {
91
+ ...result, success: true, inactive: [], verification: "matched", recovered: true, sourceHash: expected,
92
+ stages: [...result.stages, { name: "激活后核验(源码一致且无相关未激活对象)", success: true, messages: [] }],
93
+ };
94
+ }
66
95
  return { ...result, verification: "matched", sourceHash: expected };
67
96
  }
68
97
  catch (err) {
69
- return { ...mergeActivationResults([result, failedStage("激活版本核验", `激活请求已返回,但无法读取 active 源码,结果未确认:${sanitizeErrMsg(err)}`)], []), verification: "unavailable", sourceHash: expected };
98
+ return { ...mergeActivationResults([result, failedStage("激活版本核验", `激活请求已返回,但无法完成激活状态核对,结果未确认:${sanitizeErrMsg(err)}`)], []), verification: "unavailable", sourceHash: expected };
70
99
  }
71
100
  }
@@ -313,7 +313,7 @@ async function activateFunctionGroupFormIncludes(client, fgName) {
313
313
  catch (err) {
314
314
  results.push({
315
315
  success: false,
316
- messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText: toToolError(err) }],
316
+ messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText: sanitizeErrMsg(err) }],
317
317
  inactive: [],
318
318
  });
319
319
  }
@@ -321,7 +321,7 @@ async function activateFunctionGroupFormIncludes(client, fgName) {
321
321
  }
322
322
  catch (err) {
323
323
  names.push("读取函数组 include 清单");
324
- results.push({ success: false, inactive: [], messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText: toToolError(err) }] });
324
+ results.push({ success: false, inactive: [], messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText: sanitizeErrMsg(err) }] });
325
325
  }
326
326
  return mergeActivationResults(results, names);
327
327
  }
@@ -351,7 +351,7 @@ async function activateWithIncludes(client, obj) {
351
351
  catch (err) {
352
352
  results.push({
353
353
  success: false,
354
- messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText: toToolError(err) }],
354
+ messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText: sanitizeErrMsg(err) }],
355
355
  inactive: [],
356
356
  });
357
357
  }
@@ -361,7 +361,7 @@ async function activateWithIncludes(client, obj) {
361
361
  catch (err) {
362
362
  results.push({
363
363
  success: false,
364
- messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText: toToolError(err) }],
364
+ messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText: sanitizeErrMsg(err) }],
365
365
  inactive: [],
366
366
  });
367
367
  }
@@ -371,7 +371,7 @@ async function activateWithIncludes(client, obj) {
371
371
  catch (err) {
372
372
  results.push({
373
373
  success: false,
374
- messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText: toToolError(err) }],
374
+ messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText: sanitizeErrMsg(err) }],
375
375
  inactive: [],
376
376
  });
377
377
  }
@@ -433,7 +433,7 @@ async function activateWithIncludes(client, obj) {
433
433
  catch (err) {
434
434
  results.push({
435
435
  success: false,
436
- messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText: toToolError(err) }],
436
+ messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText: sanitizeErrMsg(err) }],
437
437
  inactive: [],
438
438
  });
439
439
  }
@@ -445,7 +445,7 @@ async function activateWithIncludes(client, obj) {
445
445
  catch (err) {
446
446
  results.push({
447
447
  success: false,
448
- messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText: toToolError(err) }],
448
+ messages: [{ objDescr: "", type: "E", line: 0, href: "", forceSupported: false, shortText: sanitizeErrMsg(err) }],
449
449
  inactive: [],
450
450
  });
451
451
  }
@@ -454,7 +454,7 @@ async function activateWithIncludes(client, obj) {
454
454
  export const activateTool = {
455
455
  name: "abap_activate",
456
456
  title: "Activate ABAP Object",
457
- description: "激活 ABAP 对象。ABAP 源码对象在同一次调用内串行执行语法检查、激活、active 源码核验;任一步失败即未完成。返回分阶段结果,不能用对象元数据的 active 字段覆盖失败结论。写入后必须激活才能生效。\n" +
457
+ description: "激活 ABAP 对象。ABAP 源码对象会先检查语法,激活请求返回或报错后均核对 active 源码。阶段报错但源码一致且无相关未激活对象时,确认已激活并清零该对象连续失败计数;原始诊断保留。核验不一致或不可用则保留失败,不能仅凭元数据 active 判定成功。写入后必须激活才能生效。\n" +
458
458
  "⚠️ 函数组(FUGR)激活不会自动连带激活其内部的函数模块——函数模块需单独激活:objectName=函数模块名,objectType=FUGR/FF。激活函数模块时工具会自动先激活函数组的 FORM include(把未激活的 FORM 改动固化,否则报 FORM ... does not exist)、再激活主程序(SAPL<fg>,让主程序里新增的 INCLUDE 语句先固化生效)、最后激活函数模块本身,AI 无需手动排顺序。\n" +
459
459
  "⚠️ 拆 INCLUDE 的可执行程序(主程序 PROG/P + 其 INCLUDE PROG/I)无法逐个编译——单独激活某个 INCLUDE 会对着兄弟 INCLUDE 的旧版本编译,必报 \"Field/Type unknown\"。激活时工具会自动先批量激活其全部 INCLUDE、再单独激活主程序(两步);同一对象连续激活失败 3 次会被强制拦截,需停下向用户说明。",
460
460
  write: true,
@@ -492,13 +492,15 @@ export const activateTool = {
492
492
  lines.push(`✅ ${obj["adtcore:type"]} ${obj["adtcore:name"]} 激活成功`);
493
493
  if (result.verification === "matched")
494
494
  lines.push("已核对 active 源码与本次待激活源码一致。");
495
+ if (result.recovered)
496
+ lines.push("激活阶段曾报错,但核对确认已激活且无相关未激活对象;该对象连续失败计数已清零,本次不计失败。以下保留原始阶段诊断。");
495
497
  }
496
498
  else {
497
499
  lines.push(`❌ ${result.verification === "unavailable" ? "激活未确认" : "激活失败"}(${obj["adtcore:type"]} ${obj["adtcore:name"]}):`);
498
500
  lines.push("本次操作未确认完成;对象元数据的 active 状态不能替代本次激活结果。");
499
501
  }
500
502
  for (const stage of result.stages) {
501
- lines.push(`- ${stage.name}:${stage.success ? "通过" : "未完成"}`);
503
+ lines.push(`- ${stage.name}:${stage.success ? "通过" : result.recovered ? "阶段报错(最终核验已通过)" : "未完成"}`);
502
504
  for (const m of stage.messages.slice(0, 10))
503
505
  lines.push(` [${m.type}] 行 ${m.line}: ${m.shortText}`);
504
506
  if (stage.messages.length > 10)
package/dist/tools.js CHANGED
@@ -24,7 +24,7 @@ import { installWriteGate } from "./sap-tools/gate.js";
24
24
  import { installPromptFiles } from "./sap-tools/prompt-files.js";
25
25
  import { ensureSapbuddyHome } from "./sap-tools/sapbuddy-home.js";
26
26
  import { extractObjectNames } from "./sap-tools/register.js";
27
- import { isConnectionDirty, assertDevClient, withConnMutex } from "./sap-tools/adtManager.js";
27
+ import { isConnectionDirty, assertDevClient, runWriteWithRecovery } from "./sap-tools/adtManager.js";
28
28
  import { resolveConnectionId } from "./sap-tools/tools/shared.js";
29
29
  import { appendAudit } from "./sap-tools/auditLog.js";
30
30
  /** 写工具清单(供门禁与工具列表使用;与 pi 版 WRITE_TOOL_NAMES 同源) */
@@ -56,10 +56,12 @@ function registerOne(registry, t) {
56
56
  if (t.name !== "get_connected_systems" && isConnectionDirty()) {
57
57
  return "⛔ SAP 连接配置已变更,必须先调用 get_connected_systems 确认当前连接,然后再重试本操作。";
58
58
  }
59
- // 写工具在连接互斥锁内执行(lock→改→存→激活 原子);读工具直接执行
59
+ // 写工具在连接互斥锁内执行(lock→改→存→激活 原子),并在写前刷新会话:
60
+ // 连接切换/空闲过久后池里的 CSRF 令牌已失效,stateful 写不会被 SDK 自动重建,
61
+ // 必须在进入写序列前重登录(见 adtManager.runWriteWithRecovery)。
60
62
  const connId = await resolveConnectionId(p.connectionId);
61
63
  const text = t.write
62
- ? await withConnMutex(connId, async () => {
64
+ ? await runWriteWithRecovery(connId, async () => {
63
65
  // 写操作安全守卫:非开发客户端(T000.CCCATEGORY)拒绝一切代码修改
64
66
  await assertDevClient(connId);
65
67
  return t.execute(p);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sapbuddy-dsh",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "SapBuddy —— SAP ABAP AI 全能助手(DeepSeek Harness 插件):41 个 SAP 工具 + 8 个 ABAP 技能 + SapBuddy preset + 连接设置页。一个包名装完。",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -77,10 +77,10 @@
77
77
  "typescript": "^5.6.0"
78
78
  },
79
79
  "scripts": {
80
- "build": "node scripts/clean.mjs && tsc && node scripts/build-client.mjs",
81
- "test": "node scripts/clean.mjs && tsc && node --test",
82
- "check": "tsc --noEmit",
83
- "prepack": "node scripts/clean.mjs && tsc && node scripts/build-client.mjs"
80
+ "test": "npm run test:activation && npm run test:session && npm run test:store",
81
+ "test:activation": "node --experimental-vm-modules --test --test-isolation=none test/activation.test.mjs",
82
+ "test:session": "node --experimental-vm-modules --test --test-isolation=none test/session-recovery.test.mjs",
83
+ "test:store": "node --test --test-isolation=none test/connections-store.test.mjs"
84
84
  },
85
85
  "publishConfig": {
86
86
  "access": "public"
@@ -1,75 +1,75 @@
1
- ---
2
- name: abap-code-review
3
- description: >-
4
- 【最高优先级】用户说"审查/审计/code review XXX"时立即触发本技能,禁止走其他流程。
5
- 对从 SAP 系统读取的 ABAP 程序(报表、INCLUDE、函数)源码进行代码审查,
6
- 并产出「多页签切换」的专业 HTML 审查报告(共 4 个页签)。
7
- 报告在「一、程序功能与结构概览」页签下方嵌入「代码逻辑详细解析」:解释程序在做什么、怎么做;
8
- 若识别为 ALV 报表类程序,另附「字段取数逻辑」表(字段名 | 取数来源/逻辑 | 备注/计算公式)。
9
- 当用户要求审查、审计或做某个 ABAP 程序的代码审查(典型如 Z* 报表),
10
- 或要求生成 ABAP 审查报告(HTML 格式)时,应使用本技能。
11
- 触发关键词:审查、审计、code review、review、检查代码、看看代码问题。
12
- agent_created: true
13
- disable: false
14
- ---
15
-
16
- # ABAP 代码审查(基于内置 SAP 工具)
17
-
18
- 一套可复用的审计流程,面向「对接外部系统(CRM/HTTP 接口)」类的 ABAP 程序,
19
- 并产出固定格式的 HTML 审查报告。
20
-
21
- ## 何时使用
22
-
23
- - 用户要求对某个 ABAP 程序做「审查 / 审计 / 代码审查」,通常会给出程序名(如 `ZPPR085_NEW`)。
24
- - 用户希望以 HTML 形式交付 ABAP 代码审查报告。
25
- - 程序的数据流涉及 `ZCL_BC_API=>CALL_API`、`/UI2/CL_JSON`,或将数据在可编辑 ALV 中回写外部系统。
26
-
27
- ## 前置条件
28
-
29
- - SAP 连接已配置(`get_connected_systems` 确认可用连接)。
30
- - 源码通过**内置 SAP 工具**读取,不依赖任何 MCP 服务器。
31
- - 审查规范遵循 `clean-abap` 技能(SAP 官方 Clean ABAP);语法/版本参考 `sap-abap-skills` 互补。
32
-
33
- ## 执行步骤
34
-
35
- 【强制】以下步骤必须按顺序执行,不得插入其他工具调用。
36
-
37
- 1. **获取输入。** 从用户处确认程序/函数名称以及审查范围。
38
- 2. **读取源码(智能分步)**:
39
- - 对象类型:报表/程序=`PROG`、函数模块=`FUNC`、类=`CLAS`;对象名不确定先 `search_abap_objects`。
40
- - **先读头部 + 事件块清单**(前 100 行),用 `search_abap_object_lines` 定位 INITIALIZATION / START-OF-SELECTION / FORM / CALL FUNCTION 关键行。
41
- - **再按行区间精读关键段**;程序 > 2000 行必须分步读取,禁止一次全量拉取。
42
- 3. **存档源码(可选)。** 默认**不**在本地保存源码;仅当用户明确要求时才保存到 `output/<程序名>.abap`,便于对照。
43
- 4. **ATC 自动检查(客观补充)。** 调用内置 `run_atc_analysis` 获取代码质量门禁的客观发现
44
- (语法/性能/安全/废弃用法规则),记录为「ATC 自动发现」并入报告——与 AI 静态走查互补。
45
- 5. **收集关联对象。** 留意被 INCLUDE 的程序、配置表(`ZBCT_OUTF_CONFIG`)、
46
- 结构(`ZCRMS_*`、`ZBCS_*`)等引用对象,可继续用内置工具读取;用 `find_where_used` 查谁调用本程序(修改影响面)。
47
- 6. **静态走查。** 自顶向下逐段核对 `references/checklist.md` 的每一项:
48
- 选择屏幕 → `INITIALIZATION` → GET_DATA(接口调用 + JSON 反序列化 + 展平)
49
- → ALV 展示 → 可编辑处理 → 发送/更新命令。
50
- - **逻辑解析准备**:同时梳理「程序在做什么 / 怎么做」的脉络,作为「代码逻辑详细解析」子节(挂在「一、程序功能与结构概览」页签下方)的 `{{LOGIC_SECTION}}`。
51
- - **ALV 字段取数逻辑(仅 ALV 报表类程序)**:若程序输出为 ALV 报表,逐字段整理取数逻辑,
52
- 一并写入 `{{LOGIC_SECTION}}`——格式为含 `<h3>ALV 字段取数逻辑</h3>` 与
53
- `<table>`(列:字段名 | 取数来源/逻辑 | 备注/计算公式)的整块 HTML;
54
- 非 ALV 程序(函数 / 接口类)的 `{{LOGIC_SECTION}}` 仅含文字解析、不含取数表。
55
- 7. **问题定级。** 每条问题记录:`id`、严重度(高/中/低)、
56
- `位置`(FORM/方法/行号,可用 `search_abap_object_lines` 定位准确行)、`现象`、`建议修复`、`来源`(AI 走查 / ATC)。
57
- 严重度配色:红=高,黄=中,蓝=低,绿=OK。
58
- ⚠️ 报告篇幅控制:重点问题 ≤ 15 条、中危 ≤ 20 条、低危 ≤ 20 条(超出按影响度精选),避免报告过大。
59
- 8. **生成 HTML 报告(需用户确认)。** 先把审查结论摘要展示给用户(问题数量/严重度分布/关键风险),**明确请求确认**后再生成文件:
60
- 将 `assets/report-template.html` 复制到 `output/` 目录,
61
- 把每个 `{{TOKEN}}` 占位符替换为真实内容(保留现有 CSS,不要改样式),
62
- 保存为 `output/<程序名或函数名>_CodeReview.html`。
63
- ⚠️ 若 write 被拦截(提示"审查 HTML 报告需人工确认"):把结论摘要发给用户,等用户输入"确认"后重试生成。
64
- - 报告顶部元信息常驻;下方为「多页签」切换,**共 4 个页签**,章节顺序:
65
- **一、程序功能与结构概览(下方嵌入「代码逻辑详细解析」子节)→
66
- 二、审查发现汇总 → 三、问题详解(内含 重点问题详解 / 中危问题 / 低危与改进建议 三个子节)→
67
- 四、亮点与结论(内含 亮点 / 结论与修复优先级 两个子节)**。
68
- - 各页签占位符:`{{OVERVIEW_SECTION}}`(概览段落+列表)、`{{LOGIC_SECTION}}`(逻辑解析+ALV 取数表)、
69
- `{{SUMMARY_SECTION}}`(统计+汇总表)、`{{DETAIL_ITEMS}}`/`{{MEDIUM_ITEMS}}`/`{{LOW_ITEMS}}`(重点/中危/低危)、
70
- `{{POSITIVES}}`(亮点)、`{{CONCLUSION_SECTION}}`(结论表+脚注)。
71
- - 页签切换为纯原生 JS(模板自带 `showTab()`),无外部依赖,单文件可离线打开。
72
- 9. **交付结果。** 告知用户报告位置 `output/<程序名或函数名>_CodeReview.html`(Web 端产物面板可预览),并简要总结发现问题数量。
73
-
74
- 完整清单见 `references/checklist.md`,
75
- 交付模板见 `assets/report-template.html`。
1
+ ---
2
+ name: abap-code-review
3
+ description: >-
4
+ 【最高优先级】用户说"审查/审计/code review XXX"时立即触发本技能,禁止走其他流程。
5
+ 对从 SAP 系统读取的 ABAP 程序(报表、INCLUDE、函数)源码进行代码审查,
6
+ 并产出「多页签切换」的专业 HTML 审查报告(共 4 个页签)。
7
+ 报告在「一、程序功能与结构概览」页签下方嵌入「代码逻辑详细解析」:解释程序在做什么、怎么做;
8
+ 若识别为 ALV 报表类程序,另附「字段取数逻辑」表(字段名 | 取数来源/逻辑 | 备注/计算公式)。
9
+ 当用户要求审查、审计或做某个 ABAP 程序的代码审查(典型如 Z* 报表),
10
+ 或要求生成 ABAP 审查报告(HTML 格式)时,应使用本技能。
11
+ 触发关键词:审查、审计、code review、review、检查代码、看看代码问题。
12
+ agent_created: true
13
+ disable: false
14
+ ---
15
+
16
+ # ABAP 代码审查(基于内置 SAP 工具)
17
+
18
+ 一套可复用的审计流程,面向「对接外部系统(CRM/HTTP 接口)」类的 ABAP 程序,
19
+ 并产出固定格式的 HTML 审查报告。
20
+
21
+ ## 何时使用
22
+
23
+ - 用户要求对某个 ABAP 程序做「审查 / 审计 / 代码审查」,通常会给出程序名(如 `ZPPR085_NEW`)。
24
+ - 用户希望以 HTML 形式交付 ABAP 代码审查报告。
25
+ - 程序的数据流涉及 `ZCL_BC_API=>CALL_API`、`/UI2/CL_JSON`,或将数据在可编辑 ALV 中回写外部系统。
26
+
27
+ ## 前置条件
28
+
29
+ - SAP 连接已配置(`get_connected_systems` 确认可用连接)。
30
+ - 源码通过**内置 SAP 工具**读取,不依赖任何 MCP 服务器。
31
+ - 审查规范遵循 `clean-abap` 技能(SAP 官方 Clean ABAP);语法/版本参考 `sap-abap-skills` 互补。
32
+
33
+ ## 执行步骤
34
+
35
+ 【强制】以下步骤必须按顺序执行,不得插入其他工具调用。
36
+
37
+ 1. **获取输入。** 从用户处确认程序/函数名称以及审查范围。
38
+ 2. **读取源码(智能分步)**:
39
+ - 对象类型:报表/程序=`PROG`、函数模块=`FUNC`、类=`CLAS`;对象名不确定先 `search_abap_objects`。
40
+ - **先读头部 + 事件块清单**(前 100 行),用 `search_abap_object_lines` 定位 INITIALIZATION / START-OF-SELECTION / FORM / CALL FUNCTION 关键行。
41
+ - **再按行区间精读关键段**;程序 > 2000 行必须分步读取,禁止一次全量拉取。
42
+ 3. **存档源码(可选)。** 默认**不**在本地保存源码;仅当用户明确要求时才保存到 `output/<程序名>.abap`,便于对照。
43
+ 4. **ATC 自动检查(客观补充)。** 调用内置 `run_atc_analysis` 获取代码质量门禁的客观发现
44
+ (语法/性能/安全/废弃用法规则),记录为「ATC 自动发现」并入报告——与 AI 静态走查互补。
45
+ 5. **收集关联对象。** 留意被 INCLUDE 的程序、配置表(`ZBCT_OUTF_CONFIG`)、
46
+ 结构(`ZCRMS_*`、`ZBCS_*`)等引用对象,可继续用内置工具读取;用 `find_where_used` 查谁调用本程序(修改影响面)。
47
+ 6. **静态走查。** 自顶向下逐段核对 `references/checklist.md` 的每一项:
48
+ 选择屏幕 → `INITIALIZATION` → GET_DATA(接口调用 + JSON 反序列化 + 展平)
49
+ → ALV 展示 → 可编辑处理 → 发送/更新命令。
50
+ - **逻辑解析准备**:同时梳理「程序在做什么 / 怎么做」的脉络,作为「代码逻辑详细解析」子节(挂在「一、程序功能与结构概览」页签下方)的 `{{LOGIC_SECTION}}`。
51
+ - **ALV 字段取数逻辑(仅 ALV 报表类程序)**:若程序输出为 ALV 报表,逐字段整理取数逻辑,
52
+ 一并写入 `{{LOGIC_SECTION}}`——格式为含 `<h3>ALV 字段取数逻辑</h3>` 与
53
+ `<table>`(列:字段名 | 取数来源/逻辑 | 备注/计算公式)的整块 HTML;
54
+ 非 ALV 程序(函数 / 接口类)的 `{{LOGIC_SECTION}}` 仅含文字解析、不含取数表。
55
+ 7. **问题定级。** 每条问题记录:`id`、严重度(高/中/低)、
56
+ `位置`(FORM/方法/行号,可用 `search_abap_object_lines` 定位准确行)、`现象`、`建议修复`、`来源`(AI 走查 / ATC)。
57
+ 严重度配色:红=高,黄=中,蓝=低,绿=OK。
58
+ ⚠️ 报告篇幅控制:重点问题 ≤ 15 条、中危 ≤ 20 条、低危 ≤ 20 条(超出按影响度精选),避免报告过大。
59
+ 8. **生成 HTML 报告(需用户确认)。** 先把审查结论摘要展示给用户(问题数量/严重度分布/关键风险),**明确请求确认**后再生成文件:
60
+ 将 `assets/report-template.html` 复制到 `output/` 目录,
61
+ 把每个 `{{TOKEN}}` 占位符替换为真实内容(保留现有 CSS,不要改样式),
62
+ 保存为 `output/<程序名或函数名>_CodeReview.html`。
63
+ ⚠️ 若 write 被拦截(提示"审查 HTML 报告需人工确认"):把结论摘要发给用户,等用户输入"确认"后重试生成。
64
+ - 报告顶部元信息常驻;下方为「多页签」切换,**共 4 个页签**,章节顺序:
65
+ **一、程序功能与结构概览(下方嵌入「代码逻辑详细解析」子节)→
66
+ 二、审查发现汇总 → 三、问题详解(内含 重点问题详解 / 中危问题 / 低危与改进建议 三个子节)→
67
+ 四、亮点与结论(内含 亮点 / 结论与修复优先级 两个子节)**。
68
+ - 各页签占位符:`{{OVERVIEW_SECTION}}`(概览段落+列表)、`{{LOGIC_SECTION}}`(逻辑解析+ALV 取数表)、
69
+ `{{SUMMARY_SECTION}}`(统计+汇总表)、`{{DETAIL_ITEMS}}`/`{{MEDIUM_ITEMS}}`/`{{LOW_ITEMS}}`(重点/中危/低危)、
70
+ `{{POSITIVES}}`(亮点)、`{{CONCLUSION_SECTION}}`(结论表+脚注)。
71
+ - 页签切换为纯原生 JS(模板自带 `showTab()`),无外部依赖,单文件可离线打开。
72
+ 9. **交付结果。** 告知用户报告位置 `output/<程序名或函数名>_CodeReview.html`(Web 端产物面板可预览),并简要总结发现问题数量。
73
+
74
+ 完整清单见 `references/checklist.md`,
75
+ 交付模板见 `assets/report-template.html`。