sapbuddy-dsh 0.0.6 → 0.0.8

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 (57) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +25 -4
  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/client.js +20 -5
  9. package/dist/index.js +85 -11
  10. package/dist/sap-tools/adtManager.d.ts +11 -1
  11. package/dist/sap-tools/adtManager.js +176 -9
  12. package/dist/sap-tools/connections-store.js +16 -3
  13. package/dist/sap-tools/execution.js +1 -1
  14. package/dist/tools.js +5 -3
  15. package/package.json +8 -3
  16. package/skills/abap-code-review/SKILL.md +75 -75
  17. package/skills/abap-code-review/assets/report-template.html +142 -142
  18. package/skills/abap-code-review/references/checklist.md +114 -114
  19. package/skills/abap-explain/SKILL.md +126 -126
  20. package/skills/abap-templates/SKILL.md +764 -764
  21. package/skills/abap-wiki/SKILL.md +104 -104
  22. package/skills/clean-abap/SKILL.md +91 -91
  23. package/skills/clean-abap/references/CleanABAP_zh.md +4309 -4309
  24. package/skills/dev-write/SKILL.md +137 -137
  25. package/skills/sap-abap-skills/README.md +205 -205
  26. package/skills/sap-abap-skills/SKILL.md +498 -498
  27. package/skills/sap-abap-skills/references/abap-dictionary.md +211 -211
  28. package/skills/sap-abap-skills/references/abap-sql.md +588 -588
  29. package/skills/sap-abap-skills/references/amdp.md +279 -279
  30. package/skills/sap-abap-skills/references/authorization.md +149 -149
  31. package/skills/sap-abap-skills/references/bits-bytes.md +92 -92
  32. package/skills/sap-abap-skills/references/builtin-functions.md +373 -373
  33. package/skills/sap-abap-skills/references/cds-views.md +412 -412
  34. package/skills/sap-abap-skills/references/cloud-development.md +299 -299
  35. package/skills/sap-abap-skills/references/constructor-expressions.md +458 -458
  36. package/skills/sap-abap-skills/references/date-time.md +415 -415
  37. package/skills/sap-abap-skills/references/design-patterns.md +292 -292
  38. package/skills/sap-abap-skills/references/dynamic-programming.md +426 -426
  39. package/skills/sap-abap-skills/references/exceptions.md +432 -432
  40. package/skills/sap-abap-skills/references/generative-ai.md +180 -180
  41. package/skills/sap-abap-skills/references/internal-tables.md +578 -578
  42. package/skills/sap-abap-skills/references/numeric-operations.md +219 -219
  43. package/skills/sap-abap-skills/references/object-orientation.md +518 -518
  44. package/skills/sap-abap-skills/references/performance.md +428 -428
  45. package/skills/sap-abap-skills/references/program-flow.md +302 -302
  46. package/skills/sap-abap-skills/references/rap-eml.md +549 -549
  47. package/skills/sap-abap-skills/references/released-classes.md +308 -308
  48. package/skills/sap-abap-skills/references/sap-luw.md +203 -203
  49. package/skills/sap-abap-skills/references/skill-reference-guide.md +98 -98
  50. package/skills/sap-abap-skills/references/sql-hierarchies.md +180 -180
  51. package/skills/sap-abap-skills/references/string-processing.md +488 -488
  52. package/skills/sap-abap-skills/references/table-grouping.md +150 -150
  53. package/skills/sap-abap-skills/references/tool-guide.md +285 -285
  54. package/skills/sap-abap-skills/references/unit-testing.md +512 -512
  55. package/skills/sap-abap-skills/references/where-conditions.md +223 -223
  56. package/skills/sap-abap-skills/references/xml-json.md +315 -315
  57. package/skills/translate-workflow/SKILL.md +69 -69
@@ -1,9 +1,21 @@
1
1
  import { executionSignal, checkCancelled, runCleanup, waitForReady } from "./execution.js";
2
2
  import { createManagedAdtClient } from "./http-client.js";
3
- import { createSSLConfig } from "abap-adt-api";
3
+ import { createSSLConfig, session_types } from "abap-adt-api";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
5
  import { getConfig } from "./config.js";
6
6
  const pool = new Map();
7
+ /** 会话"新鲜度":该连接最近一次确认可用(login 成功)的时间戳。
8
+ * 写操作前据此决定是否重新登录——见 ensureFreshSession。 */
9
+ const sessionOkAt = new Map();
10
+ /** 写操作复用同一会话的时间窗。超过它就重登录,换一份新的 cookie + CSRF 令牌。
11
+ * SAP 侧会话空闲超时(ICM/ADT 会话)后 cookie 与令牌都会失效,而 stateful 写请求
12
+ * 不会被 SDK 自动重建,于是出现"读正常、写一律 CSRF 令牌验证失败"的僵局。
13
+ * 5 分钟足以覆盖一轮连续写(create→replace→activate),又不至于拿着可能过期的会话去写。 */
14
+ const SESSION_REUSE_MS = 5 * 60_000;
15
+ /** 刷新会话(重新登录)的单独时限:写序列在独占锁内,登录不能吃掉整段写的时间预算 */
16
+ const SESSION_LOGIN_TIMEOUT_MS = 30_000;
17
+ /** 连接配置版本号:resetClients() 递增,写序列据此判断"重试期间用户是否切了系统" */
18
+ let configEpoch = 0;
7
19
  /**
8
20
  * 每连接读写门(reader-writer gate):
9
21
  * - 读(stateless 查询,如 searchObject/getObjectSource/runQuery)可并发,但每个连接最多
@@ -167,17 +179,44 @@ export function withAllConnMutex(fn) {
167
179
  }
168
180
  /**
169
181
  * 标记连接为不健康:从池中移除缓存(客户端 + 类别),下次调用自动重新 login。
170
- * 会话自愈的核心——连接级故障(401/5xx/网络错误)不保留坏会话,宁可重连。
182
+ * 会话自愈的核心——连接级故障(401/5xx/网络错误/CSRF 令牌失效)不保留坏会话,宁可重连。
171
183
  */
172
184
  export function markConnectionUnhealthy(connId) {
173
185
  const id = connId.toLowerCase();
174
186
  pool.delete(id);
175
187
  clientCategoryCache.delete(id);
188
+ sessionOkAt.delete(id);
176
189
  }
177
- /** 判断是否连接级故障(会话失效/网络断了),而非业务错误 */
190
+ /** abap-adt-api 的 AdtCsrfException 只带一个 Symbol typeID,没有 status/code */
191
+ const CSRF_TYPE_ID = Symbol.for("BAD CSRF");
192
+ /**
193
+ * 是否 CSRF 令牌校验失败。
194
+ * 注意:SDK 的 AdtCsrfException 是裸 Error(没有 status),所以 isConnectionError
195
+ * 靠 status/网络关键字判断时会漏掉它——这正是"会话坏了却永远不重连"的根源。
196
+ */
197
+ export function isCsrfError(err) {
198
+ if (!err || typeof err !== "object")
199
+ return false;
200
+ try {
201
+ if (err.typeID === CSRF_TYPE_ID)
202
+ return true;
203
+ }
204
+ catch { /* getter 抛错按非 CSRF 处理 */ }
205
+ const msg = `${err.message ?? ""} ${err.parent?.message ?? ""}`;
206
+ return /CSRF/i.test(msg);
207
+ }
208
+ /** 写工具把 SAP 错误文本当返回值(不抛异常)时,从文本里识别 CSRF 令牌校验失败。
209
+ * 只认令牌校验:这类请求在 SAP 侧一定没有执行,重放才是安全的;
210
+ * "会话已失效/会话已过期"这类说法不保证请求未执行,不能据此重放写操作。 */
211
+ function isCsrfFailureText(text) {
212
+ return /CSRF|令牌验证/i.test(String(text).slice(0, 800));
213
+ }
214
+ /** 判断是否连接级故障(会话失效/网络断了/CSRF 令牌失效),而非业务错误 */
178
215
  export function isConnectionError(err) {
179
216
  if (!(err instanceof Error))
180
217
  return false;
218
+ if (isCsrfError(err))
219
+ return true;
181
220
  const status = err.status;
182
221
  if (typeof status === "number")
183
222
  return status === 401 || status === 403 || status >= 500;
@@ -267,6 +306,23 @@ const READ_METHODS = new Set([
267
306
  "abapDocumentation",
268
307
  "objectEnhancements",
269
308
  ]);
309
+ /** 记录"当前写序列里已经成功执行过会改数据的方法",用于判断 CSRF 重试是否安全。
310
+ * CSRF 校验失败的请求在 SAP 侧没有执行,但只要本序列已经成功跑过 lock/setObjectSource
311
+ * 这类会落库的调用,重试就可能重复写入或留下编辑锁,此时绝不重试。 */
312
+ const writeProgress = new AsyncLocalStorage();
313
+ /** 不属于"会改数据"的会话维护方法:它们成功不代表 SAP 侧已有改动 */
314
+ const SESSION_ONLY_METHODS = new Set(["login", "logout", "dropSession", "keepAlive"]);
315
+ function noteCallSuccess(methodName) {
316
+ if (READ_METHODS.has(methodName) || SESSION_ONLY_METHODS.has(methodName))
317
+ return;
318
+ const store = writeProgress.getStore();
319
+ if (store)
320
+ store.modifying++;
321
+ }
322
+ /** 返回值里出现这些标记,说明本次写已经落库,绝不能再重放 */
323
+ function looksApplied(text) {
324
+ return /✅|已保存|已写入|已创建|已激活/.test(String(text).slice(0, 800));
325
+ }
270
326
  function wrapSelfHeal(client, connId) {
271
327
  return new Proxy(client, {
272
328
  get(target, prop, receiver) {
@@ -280,12 +336,13 @@ function wrapSelfHeal(client, connId) {
280
336
  try {
281
337
  const result = value.apply(target, args);
282
338
  if (result && typeof result.then === "function") {
283
- return result.catch((err) => {
339
+ return result.then((ok) => { noteCallSuccess(m); return ok; }, (err) => {
284
340
  if (isConnectionError(err))
285
341
  markConnectionUnhealthy(connId);
286
342
  throw err;
287
343
  });
288
344
  }
345
+ noteCallSuccess(m);
289
346
  return result;
290
347
  }
291
348
  catch (err) {
@@ -380,6 +437,10 @@ let clientFactoryOverride = null;
380
437
  export function __setClientFactoryForTest(fn) {
381
438
  clientFactoryOverride = fn;
382
439
  }
440
+ /** 测试专用:伪造连接会话"最近确认可用"的时间戳(验证会话保鲜窗口,生产勿调) */
441
+ export function __setSessionFreshAtForTest(connId, timestamp) {
442
+ sessionOkAt.set(connId.toLowerCase(), timestamp);
443
+ }
383
444
  function createAdtClient(conf) {
384
445
  if (clientFactoryOverride)
385
446
  return clientFactoryOverride(conf);
@@ -426,17 +487,121 @@ export function getClient(connId) {
426
487
  let managed = pool.get(conf.id);
427
488
  if (!managed) {
428
489
  const client = createAdtClient(conf);
429
- const ready = client.login().catch((err) => {
430
- // 登录失败时移除缓存,允许下次重试
431
- pool.delete(conf.id);
490
+ const entry = { client, ready: undefined };
491
+ // 登录失败时移除缓存,允许下次重试;只清理"自己这一条",避免并发登录失败把新建的健康条目挤掉
492
+ entry.ready = client.login().then(() => { sessionOkAt.set(conf.id, Date.now()); }).catch((err) => {
493
+ if (pool.get(conf.id) === entry) {
494
+ pool.delete(conf.id);
495
+ sessionOkAt.delete(conf.id);
496
+ }
432
497
  throw err;
433
498
  });
434
- managed = { client, ready };
435
- pool.set(conf.id, managed);
499
+ pool.set(conf.id, entry);
500
+ managed = entry;
436
501
  }
437
502
  // 包自愈代理:连接级故障自动标记不健康 → 下次调用重新 login
438
503
  return waitForReady(managed.ready).then(() => { checkCancelled(); return wrapSelfHeal(managed.client, conf.id); });
439
504
  }
505
+ /**
506
+ * 写序列前把会话切换到"确定新鲜"的状态,返回可直接使用的客户端。
507
+ *
508
+ * 为什么必须显式做:
509
+ * - 池里的客户端跨系统切换/长时间空闲后仍在复用,其 SAP 会话(cookie + CSRF 令牌)
510
+ * 已经失效;读请求之所以看起来正常,是 abap-adt-api 在 stateless 模式下捕获
511
+ * 401/CSRF 后自动 login 再重试了一次。
512
+ * - 写请求(lock→setObjectSource→unLock)必须 stateful,而 SDK 的自动重建条件里
513
+ * 有 `!this.isStateful`——stateful 下它不会重登录,于是同一个坏令牌让每次写都报
514
+ * "CSRF 令牌验证失败",读却一直正常。
515
+ *
516
+ * 做法:先把残留的 stateful 会话压回 stateless(否则 SDK 连自动重试都不会做),
517
+ * 会话超过 maxAgeMs 没被确认可用就重新 login(清空 cookie + 换新 CSRF 令牌)。
518
+ */
519
+ export async function ensureFreshSession(connId, maxAgeMs = SESSION_REUSE_MS) {
520
+ const id = connId.toLowerCase();
521
+ const client = await getClient(id);
522
+ checkCancelled();
523
+ try {
524
+ if (client.stateful === session_types.stateful)
525
+ client.stateful = session_types.stateless;
526
+ }
527
+ catch { /* 克隆客户端禁止切换 stateful,忽略即可 */ }
528
+ const last = sessionOkAt.get(id) ?? 0;
529
+ if (Date.now() - last <= maxAgeMs)
530
+ return client;
531
+ let login;
532
+ let timer;
533
+ try {
534
+ login = client.login();
535
+ await Promise.race([
536
+ login,
537
+ new Promise((_, reject) => {
538
+ timer = setTimeout(() => reject(new Error(`刷新 SAP 会话超时(${Math.round(SESSION_LOGIN_TIMEOUT_MS / 1000)}s 未完成登录)`)), SESSION_LOGIN_TIMEOUT_MS);
539
+ }),
540
+ ]);
541
+ sessionOkAt.set(id, Date.now());
542
+ }
543
+ catch (err) {
544
+ login?.catch(() => undefined); // 超时后原登录可能继续,别留下未处理的 rejection
545
+ // 登录失败不保留坏会话,下次调用重新走一遍
546
+ markConnectionUnhealthy(id);
547
+ throw err;
548
+ }
549
+ finally {
550
+ clearTimeout(timer);
551
+ }
552
+ return client;
553
+ }
554
+ /**
555
+ * 连接配置变更(切换系统/改地址/改账号)后立刻失效所有池化会话,不发起网络请求。
556
+ * 不清池的后果:新连接复用旧系统的 cookie/CSRF 令牌,读写都会打到旧系统或直接被 SAP 拒绝。
557
+ */
558
+ export function resetClients() {
559
+ pool.clear();
560
+ clientCategoryCache.clear();
561
+ sessionOkAt.clear();
562
+ configEpoch++;
563
+ }
564
+ /**
565
+ * 写操作统一入口:独占锁 + 会话保鲜 + CSRF/会话失效时自动重建一次。
566
+ * CSRF 校验失败的请求在 SAP 侧根本没有执行,所以重建连接后整体重试一次是安全的;
567
+ * 这样"切换系统后第一次写"不会再把错误直接抛给用户。
568
+ */
569
+ export async function runWriteWithRecovery(connId, run) {
570
+ const id = connId.toLowerCase();
571
+ // 配置在本序列期间被改动(用户切了系统)时不能重试:重试会拿旧 payload 打到新系统上
572
+ const epoch = configEpoch;
573
+ let progress;
574
+ const attempt = () => {
575
+ progress = { modifying: 0 };
576
+ return withConnMutex(id, () => writeProgress.run(progress, async () => {
577
+ // 持锁后再确认一次连接配置没被换掉:重试绝不能把旧 payload 打到新系统上
578
+ if (configEpoch !== epoch)
579
+ throw new Error("SAP 连接配置已变更,已放弃本次写操作;请先调用 get_connected_systems 确认当前连接后再重试。");
580
+ await ensureFreshSession(id);
581
+ return run();
582
+ }));
583
+ };
584
+ /** 只有"SAP 侧还没落过任何改动"且连接配置没变,才允许重建连接重试一次 */
585
+ const canRetry = () => (progress?.modifying ?? 0) === 0 && configEpoch === epoch;
586
+ let text;
587
+ try {
588
+ text = await attempt();
589
+ }
590
+ catch (err) {
591
+ if (!isCsrfError(err) || !canRetry())
592
+ throw err;
593
+ markConnectionUnhealthy(id);
594
+ return attempt();
595
+ }
596
+ if (typeof text === "string" && isCsrfFailureText(text) && !looksApplied(text)) {
597
+ // 写工具多数把 SAP 错误转成返回值(不抛异常),这里同样识别一次
598
+ if (!canRetry())
599
+ return text;
600
+ markConnectionUnhealthy(id);
601
+ return attempt();
602
+ }
603
+ return text;
604
+ }
440
605
  /** 释放指定连接(用于测试/连接重置) */
441
606
  export async function dropClient(connId) {
442
607
  const managed = pool.get(connId.toLowerCase());
@@ -445,11 +610,13 @@ export async function dropClient(connId) {
445
610
  await managed.client.dropSession().catch(() => undefined);
446
611
  pool.delete(connId.toLowerCase());
447
612
  clientCategoryCache.delete(connId.toLowerCase());
613
+ sessionOkAt.delete(connId.toLowerCase());
448
614
  }
449
615
  /** 释放所有连接 */
450
616
  export async function dropAllClients() {
451
617
  await Promise.all([...pool.keys()].map(dropClient));
452
618
  clientCategoryCache.clear();
619
+ sessionOkAt.clear();
453
620
  }
454
621
  // ── 连接变更后的强制重确认 ──
455
622
  // 连接配置被保存(/api/sap-config)后置脏。除 get_connected_systems 外所有 SAP 工具
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
12
12
  import { sapbuddyPath } from "./sapbuddy-home.js";
13
- import { markConnectionDirty } from "./adtManager.js";
13
+ import { markConnectionDirty, resetClients } from "./adtManager.js";
14
14
  import { reloadConfig } from "./config.js";
15
15
  const PASSWORD_MASK = "***";
16
16
  function readRaw() {
@@ -37,6 +37,9 @@ function writeRaw(file) {
37
37
  }
38
38
  catch { /* 无连接或文件刚建,忽略 */ }
39
39
  markConnectionDirty();
40
+ // 池化的 ADT 客户端必须整体作废:切换系统/改地址/改账号后继续复用旧客户端,
41
+ // 会带着上一个系统的 cookie 与 CSRF 令牌去写当前系统(SAP 直接拒绝 CSRF 校验)。
42
+ resetClients();
40
43
  }
41
44
  /** 全部连接(脱敏视图) */
42
45
  export function listConnections() {
@@ -52,10 +55,19 @@ function validate(input) {
52
55
  return "连接 ID 不能为空";
53
56
  if (!/^[A-Za-z0-9_-]+$/.test(id))
54
57
  return "连接 ID 只能包含字母/数字/下划线/连字符";
55
- if (!String(input.url ?? "").trim())
58
+ const url = String(input.url ?? "").trim();
59
+ if (!url)
56
60
  return "连接地址(url)不能为空";
61
+ // 协议必须显式写:SAP 的 ICM HTTP 端口(常见 8000)是 http,HTTPS 端口(常见 44300)才是 https。
62
+ // 不写协议时真实工具与探测都会拿不到明确结论,干脆在保存时就拦下。
63
+ if (!/^https?:\/\//i.test(url))
64
+ return "连接地址要以 http:// 或 https:// 开头(ICM HTTP 端口常见 8000 用 http://,HTTPS 端口常见 44300 用 https://)";
57
65
  if (!String(input.username ?? "").trim())
58
66
  return "用户名不能为空";
67
+ // 客户端号就是登录时的 sap-client,填错(比如系统是 300 却填 200)会直接 401
68
+ const client = String(input.client ?? "").trim();
69
+ if (client && !/^\d{1,3}$/.test(client))
70
+ return "客户端号应为 1-3 位数字(如 100)";
59
71
  return undefined;
60
72
  }
61
73
  /** 新建或更新连接;更新时密码为空/'***' 保持原密码。返回 {ok} 或 {ok:false,error} */
@@ -69,12 +81,13 @@ export function saveConnection(input) {
69
81
  const password = input.password === undefined || input.password === "" || input.password === PASSWORD_MASK
70
82
  ? existing?.password ?? ""
71
83
  : input.password;
84
+ const rawClient = String(input.client ?? existing?.client ?? "").trim();
72
85
  const next = {
73
86
  ...existing,
74
87
  ...input,
75
88
  id,
76
89
  password,
77
- client: String(input.client ?? existing?.client ?? "").trim(),
90
+ client: rawClient ? rawClient.padStart(3, "0") : rawClient,
78
91
  language: input.language ?? existing?.language ?? "ZH",
79
92
  authMethod: input.authMethod ?? existing?.authMethod ?? "basic",
80
93
  };
@@ -21,7 +21,7 @@ export function classifyFailure(error) {
21
21
  : /timeout|timedout|超时/i.test(message) ? "TIMEOUT"
22
22
  : status === 401 || status === 403 || /无权限|permission|unauthorized|forbidden/i.test(message) ? "PERMISSION"
23
23
  : status === 404 || /未找到|不存在|not found/i.test(message) ? "NOT_FOUND"
24
- : /ECONN|ENOTFOUND|EPIPE|socket|network|fetch failed|连接失败|TLS|CERT_/i.test(message) || status >= 500 ? "CONNECTION"
24
+ : /ECONN|ENOTFOUND|EPIPE|socket|network|fetch failed|连接失败|TLS|CERT_|CSRF|令牌验证/i.test(message) || status >= 500 ? "CONNECTION"
25
25
  : /参数|invalid|无效/i.test(message) ? "INVALID_ARGUMENT" : "TOOL_ERROR";
26
26
  return { code, retryable: code === "CONNECTION" || code === "TIMEOUT", message: message.replace(/<stack>[\s\S]*?<\/stack>/gi, "[堆栈已省略]").slice(0, 600) };
27
27
  }
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.6",
3
+ "version": "0.0.8",
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,8 +77,13 @@
77
77
  "typescript": "^5.6.0"
78
78
  },
79
79
  "scripts": {
80
- "test": "npm run test:activation",
81
- "test:activation": "node --experimental-vm-modules --test --test-isolation=none test/activation.test.mjs"
80
+ "test": "npm run test:activation && npm run test:session && npm run test:store && npm run test:probe && npm run test:route && npm run test:bundle",
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
+ "test:probe": "node --test --test-isolation=none test/connection-probe.test.mjs",
85
+ "test:route": "node --test --test-isolation=none test/connection-test-route.test.mjs",
86
+ "test:bundle": "node --test --test-isolation=none test/client-bundle.test.mjs"
82
87
  },
83
88
  "publishConfig": {
84
89
  "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`。