arc-1 0.6.8 → 0.6.10

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 (113) hide show
  1. package/README.md +21 -15
  2. package/dist/adt/abapgit.d.ts +39 -0
  3. package/dist/adt/abapgit.d.ts.map +1 -0
  4. package/dist/adt/abapgit.js +333 -0
  5. package/dist/adt/abapgit.js.map +1 -0
  6. package/dist/adt/cds-impact.d.ts +25 -0
  7. package/dist/adt/cds-impact.d.ts.map +1 -0
  8. package/dist/adt/cds-impact.js +91 -0
  9. package/dist/adt/cds-impact.js.map +1 -0
  10. package/dist/adt/client.d.ts +15 -1
  11. package/dist/adt/client.d.ts.map +1 -1
  12. package/dist/adt/client.js +84 -1
  13. package/dist/adt/client.js.map +1 -1
  14. package/dist/adt/codeintel.d.ts +8 -0
  15. package/dist/adt/codeintel.d.ts.map +1 -1
  16. package/dist/adt/codeintel.js +33 -0
  17. package/dist/adt/codeintel.js.map +1 -1
  18. package/dist/adt/config.d.ts +3 -0
  19. package/dist/adt/config.d.ts.map +1 -1
  20. package/dist/adt/config.js +1 -0
  21. package/dist/adt/config.js.map +1 -1
  22. package/dist/adt/cookies.d.ts +5 -0
  23. package/dist/adt/cookies.d.ts.map +1 -1
  24. package/dist/adt/cookies.js +14 -0
  25. package/dist/adt/cookies.js.map +1 -1
  26. package/dist/adt/crud.d.ts.map +1 -1
  27. package/dist/adt/crud.js +49 -3
  28. package/dist/adt/crud.js.map +1 -1
  29. package/dist/adt/devtools.d.ts +11 -0
  30. package/dist/adt/devtools.d.ts.map +1 -1
  31. package/dist/adt/devtools.js +33 -0
  32. package/dist/adt/devtools.js.map +1 -1
  33. package/dist/adt/errors.d.ts +22 -1
  34. package/dist/adt/errors.d.ts.map +1 -1
  35. package/dist/adt/errors.js +71 -2
  36. package/dist/adt/errors.js.map +1 -1
  37. package/dist/adt/features.d.ts.map +1 -1
  38. package/dist/adt/features.js +3 -0
  39. package/dist/adt/features.js.map +1 -1
  40. package/dist/adt/gcts.d.ts +68 -0
  41. package/dist/adt/gcts.d.ts.map +1 -0
  42. package/dist/adt/gcts.js +239 -0
  43. package/dist/adt/gcts.js.map +1 -0
  44. package/dist/adt/http.d.ts +2 -0
  45. package/dist/adt/http.d.ts.map +1 -1
  46. package/dist/adt/http.js +33 -1
  47. package/dist/adt/http.js.map +1 -1
  48. package/dist/adt/safety.d.ts +3 -0
  49. package/dist/adt/safety.d.ts.map +1 -1
  50. package/dist/adt/safety.js +9 -0
  51. package/dist/adt/safety.js.map +1 -1
  52. package/dist/adt/transport.d.ts +26 -0
  53. package/dist/adt/transport.d.ts.map +1 -1
  54. package/dist/adt/transport.js +64 -1
  55. package/dist/adt/transport.js.map +1 -1
  56. package/dist/adt/types.d.ts +226 -0
  57. package/dist/adt/types.d.ts.map +1 -1
  58. package/dist/adt/xml-parser.d.ts +21 -1
  59. package/dist/adt/xml-parser.d.ts.map +1 -1
  60. package/dist/adt/xml-parser.js +175 -0
  61. package/dist/adt/xml-parser.js.map +1 -1
  62. package/dist/cli.js +5 -0
  63. package/dist/cli.js.map +1 -1
  64. package/dist/handlers/intent.d.ts.map +1 -1
  65. package/dist/handlers/intent.js +439 -10
  66. package/dist/handlers/intent.js.map +1 -1
  67. package/dist/handlers/schemas.d.ts +104 -38
  68. package/dist/handlers/schemas.d.ts.map +1 -1
  69. package/dist/handlers/schemas.js +98 -22
  70. package/dist/handlers/schemas.js.map +1 -1
  71. package/dist/handlers/tools.d.ts +4 -3
  72. package/dist/handlers/tools.d.ts.map +1 -1
  73. package/dist/handlers/tools.js +195 -46
  74. package/dist/handlers/tools.js.map +1 -1
  75. package/dist/probe/catalog.d.ts +30 -0
  76. package/dist/probe/catalog.d.ts.map +1 -0
  77. package/dist/probe/catalog.js +196 -0
  78. package/dist/probe/catalog.js.map +1 -0
  79. package/dist/probe/fixtures.d.ts +54 -0
  80. package/dist/probe/fixtures.d.ts.map +1 -0
  81. package/dist/probe/fixtures.js +94 -0
  82. package/dist/probe/fixtures.js.map +1 -0
  83. package/dist/probe/format.d.ts +10 -0
  84. package/dist/probe/format.d.ts.map +1 -0
  85. package/dist/probe/format.js +114 -0
  86. package/dist/probe/format.js.map +1 -0
  87. package/dist/probe/quality.d.ts +13 -0
  88. package/dist/probe/quality.d.ts.map +1 -0
  89. package/dist/probe/quality.js +50 -0
  90. package/dist/probe/quality.js.map +1 -0
  91. package/dist/probe/runner.d.ts +48 -0
  92. package/dist/probe/runner.d.ts.map +1 -0
  93. package/dist/probe/runner.js +211 -0
  94. package/dist/probe/runner.js.map +1 -0
  95. package/dist/probe/types.d.ts +159 -0
  96. package/dist/probe/types.d.ts.map +1 -0
  97. package/dist/probe/types.js +11 -0
  98. package/dist/probe/types.js.map +1 -0
  99. package/dist/server/audit.d.ts.map +1 -1
  100. package/dist/server/audit.js +12 -1
  101. package/dist/server/audit.js.map +1 -1
  102. package/dist/server/config.d.ts.map +1 -1
  103. package/dist/server/config.js +33 -2
  104. package/dist/server/config.js.map +1 -1
  105. package/dist/server/server.d.ts +8 -2
  106. package/dist/server/server.d.ts.map +1 -1
  107. package/dist/server/server.js +66 -8
  108. package/dist/server/server.js.map +1 -1
  109. package/dist/server/types.d.ts +7 -1
  110. package/dist/server/types.d.ts.map +1 -1
  111. package/dist/server/types.js +4 -0
  112. package/dist/server/types.js.map +1 -1
  113. package/package.json +7 -2
@@ -9,20 +9,23 @@
9
9
  * responses. Internal details (stack traces, SAP XML) are NOT
10
10
  * leaked to the LLM — only user-friendly error messages.
11
11
  */
12
+ import { checkRepo as abapGitCheckRepo, createBranch as abapGitCreateBranch, createRepo as abapGitCreateRepo, getExternalInfo as abapGitGetExternalInfo, listRepos as abapGitListRepos, pullRepo as abapGitPullRepo, pushRepo as abapGitPushRepo, stageRepo as abapGitStageRepo, switchBranch as abapGitSwitchBranch, unlinkRepo as abapGitUnlinkRepo, } from '../adt/abapgit.js';
13
+ import { classifyCdsImpact } from '../adt/cds-impact.js';
12
14
  import { findDefinition, findReferences, findWhereUsed, getCompletion, } from '../adt/codeintel.js';
13
15
  import { createObject, deleteObject, lockObject, safeUpdateObject, safeUpdateSource, unlockObject, updateObject, } from '../adt/crud.js';
14
16
  import { buildDataElementXml, buildDomainXml, buildMessageClassXml, buildPackageXml, buildServiceBindingXml, decodeKtdText, rewriteKtdText, } from '../adt/ddic-xml.js';
15
- import { activate, activateBatch, applyFixProposal, getFixProposals, publishServiceBinding, runAtcCheck, runUnitTests, syntaxCheck, unpublishServiceBinding, } from '../adt/devtools.js';
17
+ import { activate, activateBatch, applyFixProposal, getFixProposals, getPrettyPrinterSettings, prettyPrint, publishServiceBinding, runAtcCheck, runUnitTests, setPrettyPrinterSettings, syntaxCheck, unpublishServiceBinding, } from '../adt/devtools.js';
16
18
  import { getDump, getTraceDbAccesses, getTraceHitlist, getTraceStatements, listDumps, listTraces, } from '../adt/diagnostics.js';
17
19
  import { AdtApiError, AdtNetworkError, AdtSafetyError, classifySapDomainError, isNotFoundError, } from '../adt/errors.js';
18
20
  import { classifyTextSearchError, mapSapReleaseToAbaplintVersion, probeFeatures } from '../adt/features.js';
19
21
  import { addTileToGroup, createCatalog, createGroup, createTile, deleteCatalog, listCatalogs, listGroups, listTiles, } from '../adt/flp.js';
22
+ import { cloneRepo as gctsCloneRepo, commitRepo as gctsCommitRepo, createBranch as gctsCreateBranch, deleteRepo as gctsDeleteRepo, getCommitHistory as gctsGetCommitHistory, getConfig as gctsGetConfig, getUserInfo as gctsGetUserInfo, listBranches as gctsListBranches, listRepoObjects as gctsListRepoObjects, listRepos as gctsListRepos, pullRepo as gctsPullRepo, switchBranch as gctsSwitchBranch, } from '../adt/gcts.js';
20
23
  import { changePackage } from '../adt/refactoring.js';
21
24
  import { checkOperation, checkPackage, isOperationAllowed, OperationType } from '../adt/safety.js';
22
- import { createTransport, deleteTransport, getTransport, getTransportInfo, listTransports, reassignTransport, releaseTransport, releaseTransportRecursive, } from '../adt/transport.js';
25
+ import { createTransport, deleteTransport, getObjectTransports, getTransport, getTransportInfo, listTransports, reassignTransport, releaseTransport, releaseTransportRecursive, } from '../adt/transport.js';
23
26
  import { getAppInfo } from '../adt/ui5-repository.js';
24
27
  import { validateAffHeader } from '../aff/validator.js';
25
- import { extractCdsElements } from '../context/cds-deps.js';
28
+ import { extractCdsDependencies, extractCdsElements } from '../context/cds-deps.js';
26
29
  import { compressCdsContext, compressContext } from '../context/compressor.js';
27
30
  import { extractMethod, formatMethodListing, listMethods, spliceMethod } from '../context/method-surgery.js';
28
31
  import { buildLintConfig, listRulesFromConfig, } from '../lint/config-builder.js';
@@ -47,6 +50,7 @@ export const TOOL_SCOPES = {
47
50
  SAPRead: 'read',
48
51
  SAPSearch: 'read',
49
52
  SAPQuery: 'sql',
53
+ SAPGit: 'read',
50
54
  SAPNavigate: 'read',
51
55
  SAPContext: 'read',
52
56
  SAPLint: 'read',
@@ -56,6 +60,24 @@ export const TOOL_SCOPES = {
56
60
  SAPManage: 'write',
57
61
  SAPTransport: 'write',
58
62
  };
63
+ const SAPGIT_ACTION_SCOPES = {
64
+ list_repos: 'read',
65
+ whoami: 'read',
66
+ config: 'read',
67
+ branches: 'read',
68
+ external_info: 'read',
69
+ history: 'read',
70
+ objects: 'read',
71
+ check: 'read',
72
+ clone: 'write',
73
+ pull: 'write',
74
+ push: 'write',
75
+ commit: 'write',
76
+ stage: 'write',
77
+ switch_branch: 'write',
78
+ create_branch: 'write',
79
+ unlink: 'write',
80
+ };
59
81
  /**
60
82
  * Check if authInfo has the required scope, respecting implied scopes:
61
83
  * - `write` implies `read`
@@ -365,6 +387,9 @@ export async function handleToolCall(client, config, toolName, args, authInfo, _
365
387
  case 'SAPTransport':
366
388
  result = await handleSAPTransport(client, args);
367
389
  break;
390
+ case 'SAPGit':
391
+ result = await handleSAPGit(client, args, authInfo);
392
+ break;
368
393
  case 'SAPContext':
369
394
  result = await handleSAPContext(client, args, cachingLayer);
370
395
  break;
@@ -627,6 +652,58 @@ async function handleSAPRead(client, args, cachingLayer) {
627
652
  const dtel = await client.getDataElement(name);
628
653
  return textResult(JSON.stringify(dtel, null, 2));
629
654
  }
655
+ case 'AUTH': {
656
+ const authField = await client.getAuthorizationField(name);
657
+ return textResult(JSON.stringify(authField, null, 2));
658
+ }
659
+ case 'FTG2': {
660
+ const toggle = await client.getFeatureToggle(name);
661
+ return textResult(JSON.stringify(toggle, null, 2));
662
+ }
663
+ case 'ENHO': {
664
+ const enhancement = await client.getEnhancementImplementation(name);
665
+ return textResult(JSON.stringify(enhancement, null, 2));
666
+ }
667
+ case 'VERSIONS': {
668
+ const include = typeof args.include === 'string' ? args.include : undefined;
669
+ let group = typeof args.group === 'string' ? args.group : undefined;
670
+ const objectType = normalizeObjectType(String(args.objectType ?? '')) || inferObjectType(name) || 'PROG';
671
+ if (objectType === 'FUNC' && !group) {
672
+ const resolved = cachingLayer
673
+ ? await cachingLayer.resolveFuncGroup(client, name)
674
+ : await client.resolveFunctionGroup(name);
675
+ if (!resolved) {
676
+ return errorResult(`Cannot resolve function group for "${name}". Provide the group parameter explicitly, or use SAPSearch("${name}") to find the function group.`);
677
+ }
678
+ group = resolved;
679
+ }
680
+ try {
681
+ const revisions = await client.getRevisions(objectType, name, { include, group });
682
+ return textResult(JSON.stringify(revisions, null, 2));
683
+ }
684
+ catch (err) {
685
+ if (isNotFoundError(err)) {
686
+ return textResult(`No version history available for ${objectType} "${name}" on this SAP system. ` +
687
+ `This usually means the object does not exist, or the ADT versions endpoint is not supported for ${objectType} on this backend release.`);
688
+ }
689
+ throw err;
690
+ }
691
+ }
692
+ case 'VERSION_SOURCE': {
693
+ const versionUri = String(args.versionUri ?? '');
694
+ if (!versionUri) {
695
+ return errorResult('VERSION_SOURCE requires a versionUri parameter. Get it from SAPRead(type="VERSIONS", name="...") response (.revisions[].uri).');
696
+ }
697
+ try {
698
+ return textResult(await client.getRevisionSource(versionUri));
699
+ }
700
+ catch (err) {
701
+ if (isNotFoundError(err)) {
702
+ return errorResult(`Revision at URI "${versionUri}" was not found. The revision may have been removed, or the URI is malformed. Fetch a fresh list via SAPRead(type="VERSIONS", name="...").`);
703
+ }
704
+ throw err;
705
+ }
706
+ }
630
707
  case 'TRAN': {
631
708
  const tran = await client.getTransaction(name);
632
709
  // Enrich with program name via SQL — only if free SQL is allowed by safety config
@@ -763,7 +840,7 @@ async function handleSAPRead(client, args, cachingLayer) {
763
840
  }
764
841
  }
765
842
  default:
766
- return errorResult(`Unknown SAPRead type: "${type}". Supported types: PROG, CLAS, INTF, FUNC, FUGR, INCL, DDLS, DCLS, DDLX, BDEF, SRVD, SRVB, SKTD, TABL, VIEW, STRU, DOMA, DTEL, TRAN, TABLE_CONTENTS, DEVC, SOBJ, SYSTEM, COMPONENTS, MESSAGES, TEXT_ELEMENTS, VARIANTS, BSP, BSP_DEPLOY, API_STATE, INACTIVE_OBJECTS. ` +
843
+ return errorResult(`Unknown SAPRead type: "${type}". Supported types: PROG, CLAS, INTF, FUNC, FUGR, INCL, DDLS, DCLS, DDLX, BDEF, SRVD, SRVB, SKTD, TABL, VIEW, STRU, DOMA, DTEL, AUTH, FTG2, ENHO, VERSIONS, VERSION_SOURCE, TRAN, TABLE_CONTENTS, DEVC, SOBJ, SYSTEM, COMPONENTS, MESSAGES, TEXT_ELEMENTS, VARIANTS, BSP, BSP_DEPLOY, API_STATE, INACTIVE_OBJECTS. ` +
767
844
  'Tip: Type aliases are auto-normalized (e.g., DDLS/DF → DDLS, DCLS/DL → DCLS, CLAS/OC → CLAS, PROG/P → PROG). ' +
768
845
  'Do not pass a URI — use the "type" and "name" parameters instead.');
769
846
  }
@@ -851,9 +928,8 @@ async function handleSAPQuery(client, args) {
851
928
  throw err;
852
929
  }
853
930
  }
854
- // _client unused: SAPLint runs offline via @abaplint/core (no SAP round-trip).
855
- // Signature matches other handlers for consistency with handleToolCall dispatch.
856
- async function handleSAPLint(_client, args, config) {
931
+ // Some SAPLint actions run offline (@abaplint/core), others call SAP ADT formatter APIs.
932
+ async function handleSAPLint(client, args, config) {
857
933
  const action = String(args.action ?? '');
858
934
  const ruleOverrides = args.rules;
859
935
  const configOptions = buildLintConfigOptions(config, ruleOverrides);
@@ -892,8 +968,33 @@ async function handleSAPLint(_client, args, config) {
892
968
  disabledRuleNames: disabled.map((r) => r.rule),
893
969
  }, null, 2));
894
970
  }
971
+ case 'format': {
972
+ const source = String(args.source ?? '');
973
+ if (!source)
974
+ return errorResult('"source" is required for format action.');
975
+ const formatted = await prettyPrint(client.http, client.safety, source);
976
+ return textResult(formatted);
977
+ }
978
+ case 'get_formatter_settings': {
979
+ const settings = await getPrettyPrinterSettings(client.http, client.safety);
980
+ return textResult(JSON.stringify(settings, null, 2));
981
+ }
982
+ case 'set_formatter_settings': {
983
+ const indentation = args.indentation;
984
+ const style = args.style;
985
+ if (indentation === undefined && style === undefined) {
986
+ return errorResult('At least one of "indentation" or "style" is required for set_formatter_settings.');
987
+ }
988
+ const current = await getPrettyPrinterSettings(client.http, client.safety);
989
+ const next = {
990
+ indentation: indentation ?? current.indentation,
991
+ style: style ?? current.style,
992
+ };
993
+ await setPrettyPrinterSettings(client.http, client.safety, next);
994
+ return textResult(JSON.stringify(next, null, 2));
995
+ }
895
996
  default:
896
- return errorResult(`Unknown SAPLint action: "${action}". Supported: lint, lint_and_fix, list_rules. For atc/syntax/unittest, use SAPDiagnose instead.`);
997
+ return errorResult(`Unknown SAPLint action: "${action}". Supported: lint, lint_and_fix, list_rules, format, get_formatter_settings, set_formatter_settings. For atc/syntax/unittest, use SAPDiagnose instead.`);
897
998
  }
898
999
  }
899
1000
  /**
@@ -2492,6 +2593,217 @@ async function handleSAPDiagnose(client, args) {
2492
2593
  return errorResult(`Unknown SAPDiagnose action: ${action}. Supported: syntax, unittest, atc, quickfix, apply_quickfix, dumps, traces`);
2493
2594
  }
2494
2595
  }
2596
+ function resolveSapGitBackend(args) {
2597
+ const forced = args.backend;
2598
+ const hasGcts = Boolean(cachedFeatures?.gcts?.available);
2599
+ const hasAbapGit = Boolean(cachedFeatures?.abapGit?.available);
2600
+ if (!hasGcts && !hasAbapGit) {
2601
+ return {
2602
+ error: 'Neither gCTS nor abapGit is available on this SAP system. Run SAPManage(action="probe") to refresh feature detection.',
2603
+ };
2604
+ }
2605
+ if (forced) {
2606
+ if (forced === 'gcts' && !hasGcts)
2607
+ return { error: 'gCTS backend is not available on this SAP system.' };
2608
+ if (forced === 'abapgit' && !hasAbapGit)
2609
+ return { error: 'abapGit backend is not available on this SAP system.' };
2610
+ return { backend: forced };
2611
+ }
2612
+ return { backend: hasGcts ? 'gcts' : 'abapgit' };
2613
+ }
2614
+ async function loadAbapGitRepo(client, repoId) {
2615
+ const repos = await abapGitListRepos(client.http, client.safety);
2616
+ const repo = repos.find((candidate) => candidate.key === repoId);
2617
+ if (!repo) {
2618
+ throw new Error(`abapGit repository "${repoId}" was not found. Run SAPGit(action="list_repos", backend="abapgit").`);
2619
+ }
2620
+ return repo;
2621
+ }
2622
+ async function handleSAPGit(client, args, authInfo) {
2623
+ const action = String(args.action ?? '');
2624
+ const scope = SAPGIT_ACTION_SCOPES[action];
2625
+ if (!scope) {
2626
+ return errorResult(`Unknown SAPGit action: ${action}`);
2627
+ }
2628
+ if (authInfo && !hasRequiredScope(authInfo, scope)) {
2629
+ return errorResult(`Insufficient scope: '${scope}' required for SAPGit(action="${action}"). Your scopes: [${authInfo.scopes.join(', ')}]`);
2630
+ }
2631
+ const resolved = resolveSapGitBackend(args);
2632
+ if (!resolved.backend) {
2633
+ return errorResult(resolved.error ?? 'Unable to resolve SAPGit backend.');
2634
+ }
2635
+ const backend = resolved.backend;
2636
+ const repoId = String(args.repoId ?? '').trim();
2637
+ const url = String(args.url ?? '').trim();
2638
+ const branch = String(args.branch ?? '').trim();
2639
+ const packageName = String(args.package ?? '').trim();
2640
+ const user = String(args.user ?? '').trim() || undefined;
2641
+ const password = String(args.password ?? '').trim() || undefined;
2642
+ const token = String(args.token ?? '').trim() || undefined;
2643
+ const limit = Number(args.limit ?? 20);
2644
+ const gctsOnlyActions = new Set(['whoami', 'config', 'branches', 'history', 'objects', 'commit']);
2645
+ const abapGitOnlyActions = new Set(['external_info', 'check', 'stage', 'push']);
2646
+ if (backend === 'abapgit' && gctsOnlyActions.has(action)) {
2647
+ return errorResult(`Action '${action}' is only supported by gCTS; this system uses abapGit.`);
2648
+ }
2649
+ if (backend === 'gcts' && abapGitOnlyActions.has(action)) {
2650
+ return errorResult(`Action '${action}' is only supported by abapGit; this system uses gCTS.`);
2651
+ }
2652
+ let result;
2653
+ switch (action) {
2654
+ case 'list_repos':
2655
+ result =
2656
+ backend === 'gcts'
2657
+ ? await gctsListRepos(client.http, client.safety)
2658
+ : await abapGitListRepos(client.http, client.safety);
2659
+ break;
2660
+ case 'whoami':
2661
+ result = await gctsGetUserInfo(client.http, client.safety);
2662
+ break;
2663
+ case 'config':
2664
+ result = await gctsGetConfig(client.http, client.safety, repoId || undefined);
2665
+ break;
2666
+ case 'branches':
2667
+ if (!repoId)
2668
+ return errorResult('SAPGit(action="branches") requires repoId.');
2669
+ result = await gctsListBranches(client.http, client.safety, repoId);
2670
+ break;
2671
+ case 'external_info':
2672
+ if (!url)
2673
+ return errorResult('SAPGit(action="external_info") requires url.');
2674
+ result = await abapGitGetExternalInfo(client.http, client.safety, url, user, password);
2675
+ break;
2676
+ case 'history':
2677
+ if (!repoId)
2678
+ return errorResult('SAPGit(action="history") requires repoId.');
2679
+ result = await gctsGetCommitHistory(client.http, client.safety, repoId, Number.isFinite(limit) ? limit : 20);
2680
+ break;
2681
+ case 'objects':
2682
+ if (!repoId)
2683
+ return errorResult('SAPGit(action="objects") requires repoId.');
2684
+ result = await gctsListRepoObjects(client.http, client.safety, repoId);
2685
+ break;
2686
+ case 'check': {
2687
+ if (!repoId)
2688
+ return errorResult('SAPGit(action="check") requires repoId.');
2689
+ const repo = await loadAbapGitRepo(client, repoId);
2690
+ result = await abapGitCheckRepo(client.http, client.safety, repo);
2691
+ break;
2692
+ }
2693
+ case 'stage': {
2694
+ if (!repoId)
2695
+ return errorResult('SAPGit(action="stage") requires repoId.');
2696
+ const repo = await loadAbapGitRepo(client, repoId);
2697
+ result = await abapGitStageRepo(client.http, client.safety, repo);
2698
+ break;
2699
+ }
2700
+ case 'clone':
2701
+ if (!url)
2702
+ return errorResult('SAPGit(action="clone") requires url.');
2703
+ if (backend === 'gcts') {
2704
+ const params = {
2705
+ rid: repoId || undefined,
2706
+ name: repoId || undefined,
2707
+ url,
2708
+ ...(packageName ? { package: packageName } : {}),
2709
+ user,
2710
+ password,
2711
+ token,
2712
+ };
2713
+ result = await gctsCloneRepo(client.http, client.safety, params);
2714
+ }
2715
+ else {
2716
+ if (!packageName)
2717
+ return errorResult('SAPGit(action="clone", backend="abapgit") requires package.');
2718
+ result = await abapGitCreateRepo(client.http, client.safety, {
2719
+ package: packageName,
2720
+ url,
2721
+ branchName: branch || undefined,
2722
+ transportRequest: String(args.transport ?? '').trim() || undefined,
2723
+ user,
2724
+ password,
2725
+ });
2726
+ }
2727
+ break;
2728
+ case 'pull':
2729
+ if (!repoId)
2730
+ return errorResult('SAPGit(action="pull") requires repoId.');
2731
+ if (backend === 'gcts') {
2732
+ result = await gctsPullRepo(client.http, client.safety, repoId, String(args.commit ?? '').trim() || undefined);
2733
+ }
2734
+ else {
2735
+ result = await abapGitPullRepo(client.http, client.safety, repoId, {
2736
+ ...(packageName ? { package: packageName } : {}),
2737
+ ...(url ? { url } : {}),
2738
+ ...(branch ? { branchName: branch } : {}),
2739
+ transportRequest: String(args.transport ?? '').trim() || undefined,
2740
+ user,
2741
+ password,
2742
+ });
2743
+ }
2744
+ break;
2745
+ case 'push': {
2746
+ if (!repoId)
2747
+ return errorResult('SAPGit(action="push") requires repoId.');
2748
+ const repo = await loadAbapGitRepo(client, repoId);
2749
+ const staging = Array.isArray(args.objects) && args.objects.length > 0
2750
+ ? { repoKey: repo.key, branchName: repo.branchName, objects: args.objects }
2751
+ : await abapGitStageRepo(client.http, client.safety, repo);
2752
+ await abapGitPushRepo(client.http, client.safety, repo, staging);
2753
+ result = { ok: true };
2754
+ break;
2755
+ }
2756
+ case 'commit':
2757
+ if (!repoId)
2758
+ return errorResult('SAPGit(action="commit") requires repoId.');
2759
+ result = await gctsCommitRepo(client.http, client.safety, repoId, {
2760
+ message: String(args.message ?? '').trim() || undefined,
2761
+ description: String(args.description ?? '').trim() || undefined,
2762
+ objects: Array.isArray(args.objects) ? args.objects : undefined,
2763
+ });
2764
+ break;
2765
+ case 'switch_branch':
2766
+ if (!repoId || !branch)
2767
+ return errorResult('SAPGit(action="switch_branch") requires repoId and branch.');
2768
+ if (backend === 'gcts') {
2769
+ result = await gctsSwitchBranch(client.http, client.safety, repoId, branch);
2770
+ }
2771
+ else {
2772
+ await abapGitSwitchBranch(client.http, client.safety, repoId, branch, false);
2773
+ result = { ok: true };
2774
+ }
2775
+ break;
2776
+ case 'create_branch':
2777
+ if (!repoId || !branch)
2778
+ return errorResult('SAPGit(action="create_branch") requires repoId and branch.');
2779
+ if (backend === 'gcts') {
2780
+ result = await gctsCreateBranch(client.http, client.safety, repoId, {
2781
+ branch,
2782
+ ...(packageName ? { package: packageName } : {}),
2783
+ });
2784
+ }
2785
+ else {
2786
+ await abapGitCreateBranch(client.http, client.safety, repoId, branch);
2787
+ result = { ok: true };
2788
+ }
2789
+ break;
2790
+ case 'unlink':
2791
+ if (!repoId)
2792
+ return errorResult('SAPGit(action="unlink") requires repoId.');
2793
+ if (backend === 'gcts') {
2794
+ await gctsDeleteRepo(client.http, client.safety, repoId);
2795
+ }
2796
+ else {
2797
+ await abapGitUnlinkRepo(client.http, client.safety, repoId);
2798
+ }
2799
+ result = { ok: true };
2800
+ break;
2801
+ default:
2802
+ return errorResult(`Unknown SAPGit action: ${action}`);
2803
+ }
2804
+ const payload = backend === 'gcts' || backend === 'abapgit' ? { backend, result } : result;
2805
+ return textResult(JSON.stringify(payload, null, 2));
2806
+ }
2495
2807
  // ─── SAPTransport Handler ────────────────────────────────────────────
2496
2808
  async function handleSAPTransport(client, args) {
2497
2809
  const action = String(args.action ?? '');
@@ -2581,14 +2893,56 @@ async function handleSAPTransport(client, args) {
2581
2893
  summary,
2582
2894
  }, null, 2));
2583
2895
  }
2896
+ case 'history': {
2897
+ const objectType = String(args.type ?? '');
2898
+ const objectName = String(args.name ?? '');
2899
+ if (!objectType || !objectName) {
2900
+ return errorResult('"type" and "name" are required for "history" action.');
2901
+ }
2902
+ const objectUrl = objectUrlForType(objectType, objectName);
2903
+ const primary = await getObjectTransports(client.http, client.safety, objectUrl);
2904
+ let candidateTransports = primary.candidateTransports;
2905
+ // Fallback: if per-object transport lookup is empty, derive the package via
2906
+ // the object metadata endpoint and ask transportchecks for candidate transports.
2907
+ if (primary.relatedTransports.length === 0 && candidateTransports.length === 0) {
2908
+ try {
2909
+ const pkg = await client.resolveObjectPackage(objectUrl);
2910
+ if (pkg && pkg !== '$TMP') {
2911
+ const info = await getTransportInfo(client.http, client.safety, objectUrl, pkg, '');
2912
+ candidateTransports = info.existingTransports;
2913
+ }
2914
+ }
2915
+ catch {
2916
+ // best-effort-fallback
2917
+ }
2918
+ }
2919
+ const lockOwner = primary.relatedTransports[0]?.owner;
2920
+ const summary = primary.lockedTransport
2921
+ ? `Object ${objectName} is locked in transport ${primary.lockedTransport}${lockOwner ? ` by ${lockOwner}` : ''}.`
2922
+ : candidateTransports.length > 0
2923
+ ? `Object ${objectName} has no active lock; ${candidateTransports.length} transport(s) available for assignment.`
2924
+ : `Object ${objectName} has no related or candidate transports (likely $TMP / local object).`;
2925
+ const history = {
2926
+ object: { type: objectType, name: objectName, uri: objectUrl },
2927
+ ...(primary.lockedTransport ? { lockedTransport: primary.lockedTransport } : {}),
2928
+ relatedTransports: primary.relatedTransports,
2929
+ candidateTransports,
2930
+ summary,
2931
+ };
2932
+ return textResult(JSON.stringify(history, null, 2));
2933
+ }
2584
2934
  default:
2585
- return errorResult(`Unknown SAPTransport action: ${action}. Supported: list, get, create, release, delete, reassign, release_recursive, check`);
2935
+ return errorResult(`Unknown SAPTransport action: ${action}. Supported: list, get, create, release, delete, reassign, release_recursive, check, history`);
2586
2936
  }
2587
2937
  }
2588
2938
  // ─── SAPContext Handler ───────────────────────────────────────────────
2589
2939
  async function handleSAPContext(client, args, cachingLayer) {
2590
2940
  const action = String(args.action ?? '');
2591
- const type = normalizeObjectType(String(args.type ?? ''));
2941
+ // action="impact" is DDLS-only on the server side — default the type so LLMs
2942
+ // don't have to supply it redundantly (and don't get a validation retry when
2943
+ // they don't). Any non-DDLS value still fails the guardrail below.
2944
+ const rawType = String(args.type ?? '');
2945
+ const type = normalizeObjectType(rawType || (action === 'impact' ? 'DDLS' : ''));
2592
2946
  const name = String(args.name ?? '');
2593
2947
  const maxDeps = Number(args.maxDeps ?? 20);
2594
2948
  const depth = Math.min(Math.max(Number(args.depth ?? 1), 1), 3);
@@ -2624,6 +2978,42 @@ async function handleSAPContext(client, args, cachingLayer) {
2624
2978
  const { source } = await cachingLayer.getSource(objType, objName, fetcher);
2625
2979
  return source;
2626
2980
  };
2981
+ if (action === 'impact') {
2982
+ if (type !== 'DDLS') {
2983
+ return errorResult('SAPContext(action="impact") supports DDLS only. For non-CDS objects, use SAPNavigate(action="references").');
2984
+ }
2985
+ const ddlSource = await cachedGet('DDLS', name, () => client.getDdls(name));
2986
+ const upstream = buildCdsUpstream(extractCdsDependencies(ddlSource));
2987
+ const includeIndirect = args.includeIndirect === true;
2988
+ let downstream = classifyCdsImpact([], { includeIndirect });
2989
+ const warnings = [];
2990
+ try {
2991
+ const whereUsed = await findWhereUsed(client.http, client.safety, objectUrlForType('DDLS', name));
2992
+ downstream = classifyCdsImpact(whereUsed, { includeIndirect });
2993
+ }
2994
+ catch (err) {
2995
+ if (err instanceof AdtApiError && [404, 405, 415, 501].includes(err.statusCode)) {
2996
+ warnings.push('Where-used endpoint not available on this system');
2997
+ }
2998
+ else {
2999
+ throw err;
3000
+ }
3001
+ }
3002
+ const upstreamCount = upstream.tables.length + upstream.views.length + upstream.associations.length + upstream.compositions.length;
3003
+ const response = {
3004
+ name,
3005
+ type: 'DDLS',
3006
+ upstream,
3007
+ downstream,
3008
+ summary: {
3009
+ upstreamCount,
3010
+ downstreamTotal: downstream.summary.total,
3011
+ downstreamDirect: downstream.summary.direct,
3012
+ },
3013
+ ...(warnings.length > 0 ? { warnings } : {}),
3014
+ };
3015
+ return textResult(JSON.stringify(response, null, 2));
3016
+ }
2627
3017
  // Get source — either provided or fetched from SAP
2628
3018
  let source;
2629
3019
  if (args.source) {
@@ -2692,6 +3082,45 @@ async function handleSAPContext(client, args, cachingLayer) {
2692
3082
  const result = await compressContext(client, source, name, type, maxDeps, depth, abaplintVersion, cachingLayer);
2693
3083
  return textResult(result.output);
2694
3084
  }
3085
+ function buildCdsUpstream(deps) {
3086
+ const tableNames = new Set();
3087
+ const viewNames = new Set();
3088
+ const associationNames = new Set();
3089
+ const compositionNames = new Set();
3090
+ for (const dep of deps) {
3091
+ const upperName = dep.name.toUpperCase();
3092
+ if (dep.kind === 'association') {
3093
+ associationNames.add(upperName);
3094
+ continue;
3095
+ }
3096
+ if (dep.kind === 'composition') {
3097
+ compositionNames.add(upperName);
3098
+ continue;
3099
+ }
3100
+ if (dep.kind === 'projection_base') {
3101
+ viewNames.add(upperName);
3102
+ continue;
3103
+ }
3104
+ if (isLikelyCdsViewName(upperName)) {
3105
+ viewNames.add(upperName);
3106
+ }
3107
+ else {
3108
+ tableNames.add(upperName);
3109
+ }
3110
+ }
3111
+ return {
3112
+ tables: [...tableNames].sort().map((name) => ({ name })),
3113
+ views: [...viewNames].sort().map((name) => ({ name })),
3114
+ associations: [...associationNames].sort().map((name) => ({ name })),
3115
+ compositions: [...compositionNames].sort().map((name) => ({ name })),
3116
+ };
3117
+ }
3118
+ function isLikelyCdsViewName(name) {
3119
+ if (name.startsWith('/')) {
3120
+ return /\/[ICRPAZ][A-Z0-9_]*_/.test(name);
3121
+ }
3122
+ return /^(ZI_|ZC_|ZR_|ZP_|I_|C_|R_|P_)/.test(name);
3123
+ }
2695
3124
  // ─── SAPManage Handler ────────────────────────────────────────────────
2696
3125
  /** Cached feature status — populated on first probe */
2697
3126
  let cachedFeatures;