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.
- package/README.md +21 -15
- package/dist/adt/abapgit.d.ts +39 -0
- package/dist/adt/abapgit.d.ts.map +1 -0
- package/dist/adt/abapgit.js +333 -0
- package/dist/adt/abapgit.js.map +1 -0
- package/dist/adt/cds-impact.d.ts +25 -0
- package/dist/adt/cds-impact.d.ts.map +1 -0
- package/dist/adt/cds-impact.js +91 -0
- package/dist/adt/cds-impact.js.map +1 -0
- package/dist/adt/client.d.ts +15 -1
- package/dist/adt/client.d.ts.map +1 -1
- package/dist/adt/client.js +84 -1
- package/dist/adt/client.js.map +1 -1
- package/dist/adt/codeintel.d.ts +8 -0
- package/dist/adt/codeintel.d.ts.map +1 -1
- package/dist/adt/codeintel.js +33 -0
- package/dist/adt/codeintel.js.map +1 -1
- package/dist/adt/config.d.ts +3 -0
- package/dist/adt/config.d.ts.map +1 -1
- package/dist/adt/config.js +1 -0
- package/dist/adt/config.js.map +1 -1
- package/dist/adt/cookies.d.ts +5 -0
- package/dist/adt/cookies.d.ts.map +1 -1
- package/dist/adt/cookies.js +14 -0
- package/dist/adt/cookies.js.map +1 -1
- package/dist/adt/crud.d.ts.map +1 -1
- package/dist/adt/crud.js +49 -3
- package/dist/adt/crud.js.map +1 -1
- package/dist/adt/devtools.d.ts +11 -0
- package/dist/adt/devtools.d.ts.map +1 -1
- package/dist/adt/devtools.js +33 -0
- package/dist/adt/devtools.js.map +1 -1
- package/dist/adt/errors.d.ts +22 -1
- package/dist/adt/errors.d.ts.map +1 -1
- package/dist/adt/errors.js +71 -2
- package/dist/adt/errors.js.map +1 -1
- package/dist/adt/features.d.ts.map +1 -1
- package/dist/adt/features.js +3 -0
- package/dist/adt/features.js.map +1 -1
- package/dist/adt/gcts.d.ts +68 -0
- package/dist/adt/gcts.d.ts.map +1 -0
- package/dist/adt/gcts.js +239 -0
- package/dist/adt/gcts.js.map +1 -0
- package/dist/adt/http.d.ts +2 -0
- package/dist/adt/http.d.ts.map +1 -1
- package/dist/adt/http.js +33 -1
- package/dist/adt/http.js.map +1 -1
- package/dist/adt/safety.d.ts +3 -0
- package/dist/adt/safety.d.ts.map +1 -1
- package/dist/adt/safety.js +9 -0
- package/dist/adt/safety.js.map +1 -1
- package/dist/adt/transport.d.ts +26 -0
- package/dist/adt/transport.d.ts.map +1 -1
- package/dist/adt/transport.js +64 -1
- package/dist/adt/transport.js.map +1 -1
- package/dist/adt/types.d.ts +226 -0
- package/dist/adt/types.d.ts.map +1 -1
- package/dist/adt/xml-parser.d.ts +21 -1
- package/dist/adt/xml-parser.d.ts.map +1 -1
- package/dist/adt/xml-parser.js +175 -0
- package/dist/adt/xml-parser.js.map +1 -1
- package/dist/cli.js +5 -0
- package/dist/cli.js.map +1 -1
- package/dist/handlers/intent.d.ts.map +1 -1
- package/dist/handlers/intent.js +439 -10
- package/dist/handlers/intent.js.map +1 -1
- package/dist/handlers/schemas.d.ts +104 -38
- package/dist/handlers/schemas.d.ts.map +1 -1
- package/dist/handlers/schemas.js +98 -22
- package/dist/handlers/schemas.js.map +1 -1
- package/dist/handlers/tools.d.ts +4 -3
- package/dist/handlers/tools.d.ts.map +1 -1
- package/dist/handlers/tools.js +195 -46
- package/dist/handlers/tools.js.map +1 -1
- package/dist/probe/catalog.d.ts +30 -0
- package/dist/probe/catalog.d.ts.map +1 -0
- package/dist/probe/catalog.js +196 -0
- package/dist/probe/catalog.js.map +1 -0
- package/dist/probe/fixtures.d.ts +54 -0
- package/dist/probe/fixtures.d.ts.map +1 -0
- package/dist/probe/fixtures.js +94 -0
- package/dist/probe/fixtures.js.map +1 -0
- package/dist/probe/format.d.ts +10 -0
- package/dist/probe/format.d.ts.map +1 -0
- package/dist/probe/format.js +114 -0
- package/dist/probe/format.js.map +1 -0
- package/dist/probe/quality.d.ts +13 -0
- package/dist/probe/quality.d.ts.map +1 -0
- package/dist/probe/quality.js +50 -0
- package/dist/probe/quality.js.map +1 -0
- package/dist/probe/runner.d.ts +48 -0
- package/dist/probe/runner.d.ts.map +1 -0
- package/dist/probe/runner.js +211 -0
- package/dist/probe/runner.js.map +1 -0
- package/dist/probe/types.d.ts +159 -0
- package/dist/probe/types.d.ts.map +1 -0
- package/dist/probe/types.js +11 -0
- package/dist/probe/types.js.map +1 -0
- package/dist/server/audit.d.ts.map +1 -1
- package/dist/server/audit.js +12 -1
- package/dist/server/audit.js.map +1 -1
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js +33 -2
- package/dist/server/config.js.map +1 -1
- package/dist/server/server.d.ts +8 -2
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +66 -8
- package/dist/server/server.js.map +1 -1
- package/dist/server/types.d.ts +7 -1
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js +4 -0
- package/dist/server/types.js.map +1 -1
- package/package.json +7 -2
package/dist/handlers/intent.js
CHANGED
|
@@ -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
|
-
//
|
|
855
|
-
|
|
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
|
-
|
|
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;
|