arc-1 0.6.7 → 0.6.9
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 +15 -11
- 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 +17 -1
- package/dist/adt/client.d.ts.map +1 -1
- package/dist/adt/client.js +91 -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 +2 -0
- package/dist/adt/config.d.ts.map +1 -1
- 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 +7 -0
- package/dist/adt/crud.js.map +1 -1
- package/dist/adt/ddic-xml.d.ts +16 -0
- package/dist/adt/ddic-xml.d.ts.map +1 -1
- package/dist/adt/ddic-xml.js +79 -0
- package/dist/adt/ddic-xml.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/http.d.ts +2 -0
- package/dist/adt/http.d.ts.map +1 -1
- package/dist/adt/http.js +54 -5
- package/dist/adt/http.js.map +1 -1
- package/dist/adt/refactoring.d.ts +60 -0
- package/dist/adt/refactoring.d.ts.map +1 -0
- package/dist/adt/refactoring.js +117 -0
- package/dist/adt/refactoring.js.map +1 -0
- package/dist/adt/transport.d.ts +26 -0
- package/dist/adt/transport.d.ts.map +1 -1
- package/dist/adt/transport.js +60 -0
- package/dist/adt/transport.js.map +1 -1
- package/dist/adt/types.d.ts +89 -0
- package/dist/adt/types.d.ts.map +1 -1
- package/dist/adt/xml-parser.d.ts +19 -1
- package/dist/adt/xml-parser.d.ts.map +1 -1
- package/dist/adt/xml-parser.js +173 -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 +352 -15
- package/dist/handlers/intent.js.map +1 -1
- package/dist/handlers/schemas.d.ts +75 -35
- package/dist/handlers/schemas.d.ts.map +1 -1
- package/dist/handlers/schemas.js +68 -22
- package/dist/handlers/schemas.js.map +1 -1
- package/dist/handlers/tools.d.ts.map +1 -1
- package/dist/handlers/tools.js +122 -47
- package/dist/handlers/tools.js.map +1 -1
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js +18 -0
- package/dist/server/config.js.map +1 -1
- package/dist/server/server.d.ts +7 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +62 -6
- package/dist/server/server.js.map +1 -1
- package/dist/server/types.d.ts +4 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js +2 -0
- package/dist/server/types.js.map +1 -1
- package/package.json +8 -4
package/dist/handlers/intent.js
CHANGED
|
@@ -9,19 +9,21 @@
|
|
|
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 { classifyCdsImpact } from '../adt/cds-impact.js';
|
|
12
13
|
import { findDefinition, findReferences, findWhereUsed, getCompletion, } from '../adt/codeintel.js';
|
|
13
14
|
import { createObject, deleteObject, lockObject, safeUpdateObject, safeUpdateSource, unlockObject, updateObject, } from '../adt/crud.js';
|
|
14
|
-
import { buildDataElementXml, buildDomainXml, buildMessageClassXml, buildPackageXml, buildServiceBindingXml, } from '../adt/ddic-xml.js';
|
|
15
|
-
import { activate, activateBatch, applyFixProposal, getFixProposals, publishServiceBinding, runAtcCheck, runUnitTests, syntaxCheck, unpublishServiceBinding, } from '../adt/devtools.js';
|
|
15
|
+
import { buildDataElementXml, buildDomainXml, buildMessageClassXml, buildPackageXml, buildServiceBindingXml, decodeKtdText, rewriteKtdText, } from '../adt/ddic-xml.js';
|
|
16
|
+
import { activate, activateBatch, applyFixProposal, getFixProposals, getPrettyPrinterSettings, prettyPrint, publishServiceBinding, runAtcCheck, runUnitTests, setPrettyPrinterSettings, syntaxCheck, unpublishServiceBinding, } from '../adt/devtools.js';
|
|
16
17
|
import { getDump, getTraceDbAccesses, getTraceHitlist, getTraceStatements, listDumps, listTraces, } from '../adt/diagnostics.js';
|
|
17
18
|
import { AdtApiError, AdtNetworkError, AdtSafetyError, classifySapDomainError, isNotFoundError, } from '../adt/errors.js';
|
|
18
19
|
import { classifyTextSearchError, mapSapReleaseToAbaplintVersion, probeFeatures } from '../adt/features.js';
|
|
19
20
|
import { addTileToGroup, createCatalog, createGroup, createTile, deleteCatalog, listCatalogs, listGroups, listTiles, } from '../adt/flp.js';
|
|
21
|
+
import { changePackage } from '../adt/refactoring.js';
|
|
20
22
|
import { checkOperation, checkPackage, isOperationAllowed, OperationType } from '../adt/safety.js';
|
|
21
|
-
import { createTransport, deleteTransport, getTransport, getTransportInfo, listTransports, reassignTransport, releaseTransport, releaseTransportRecursive, } from '../adt/transport.js';
|
|
23
|
+
import { createTransport, deleteTransport, getObjectTransports, getTransport, getTransportInfo, listTransports, reassignTransport, releaseTransport, releaseTransportRecursive, } from '../adt/transport.js';
|
|
22
24
|
import { getAppInfo } from '../adt/ui5-repository.js';
|
|
23
25
|
import { validateAffHeader } from '../aff/validator.js';
|
|
24
|
-
import { extractCdsElements } from '../context/cds-deps.js';
|
|
26
|
+
import { extractCdsDependencies, extractCdsElements } from '../context/cds-deps.js';
|
|
25
27
|
import { compressCdsContext, compressContext } from '../context/compressor.js';
|
|
26
28
|
import { extractMethod, formatMethodListing, listMethods, spliceMethod } from '../context/method-surgery.js';
|
|
27
29
|
import { buildLintConfig, listRulesFromConfig, } from '../lint/config-builder.js';
|
|
@@ -591,6 +593,21 @@ async function handleSAPRead(client, args, cachingLayer) {
|
|
|
591
593
|
const { source, cacheHit } = await cachedGet('SRVB', name, () => client.getSrvb(name));
|
|
592
594
|
return cachedTextResult(source, cacheHit);
|
|
593
595
|
}
|
|
596
|
+
case 'SKTD': {
|
|
597
|
+
try {
|
|
598
|
+
// ADT returns a <sktd:docu> XML envelope with the Markdown body base64-encoded
|
|
599
|
+
// inside <sktd:text>. Cache the raw envelope (update flow re-uses it) and
|
|
600
|
+
// return the decoded Markdown to the LLM.
|
|
601
|
+
const { source, cacheHit } = await cachedGet('SKTD', name, () => client.getKtd(name));
|
|
602
|
+
return cachedTextResult(decodeKtdText(source), cacheHit);
|
|
603
|
+
}
|
|
604
|
+
catch (err) {
|
|
605
|
+
if (isNotFoundError(err)) {
|
|
606
|
+
return textResult(`No Knowledge Transfer Document (SKTD) found for "${name}". KTD docs are optional Markdown documentation attached to ABAP objects — either one was never created for "${name}", or the name is wrong.`);
|
|
607
|
+
}
|
|
608
|
+
throw err;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
594
611
|
case 'TABL': {
|
|
595
612
|
const { source, cacheHit } = await cachedGet('TABL', name, () => client.getTable(name));
|
|
596
613
|
return cachedTextResult(source, cacheHit);
|
|
@@ -611,6 +628,58 @@ async function handleSAPRead(client, args, cachingLayer) {
|
|
|
611
628
|
const dtel = await client.getDataElement(name);
|
|
612
629
|
return textResult(JSON.stringify(dtel, null, 2));
|
|
613
630
|
}
|
|
631
|
+
case 'AUTH': {
|
|
632
|
+
const authField = await client.getAuthorizationField(name);
|
|
633
|
+
return textResult(JSON.stringify(authField, null, 2));
|
|
634
|
+
}
|
|
635
|
+
case 'FTG2': {
|
|
636
|
+
const toggle = await client.getFeatureToggle(name);
|
|
637
|
+
return textResult(JSON.stringify(toggle, null, 2));
|
|
638
|
+
}
|
|
639
|
+
case 'ENHO': {
|
|
640
|
+
const enhancement = await client.getEnhancementImplementation(name);
|
|
641
|
+
return textResult(JSON.stringify(enhancement, null, 2));
|
|
642
|
+
}
|
|
643
|
+
case 'VERSIONS': {
|
|
644
|
+
const include = typeof args.include === 'string' ? args.include : undefined;
|
|
645
|
+
let group = typeof args.group === 'string' ? args.group : undefined;
|
|
646
|
+
const objectType = normalizeObjectType(String(args.objectType ?? '')) || inferObjectType(name) || 'PROG';
|
|
647
|
+
if (objectType === 'FUNC' && !group) {
|
|
648
|
+
const resolved = cachingLayer
|
|
649
|
+
? await cachingLayer.resolveFuncGroup(client, name)
|
|
650
|
+
: await client.resolveFunctionGroup(name);
|
|
651
|
+
if (!resolved) {
|
|
652
|
+
return errorResult(`Cannot resolve function group for "${name}". Provide the group parameter explicitly, or use SAPSearch("${name}") to find the function group.`);
|
|
653
|
+
}
|
|
654
|
+
group = resolved;
|
|
655
|
+
}
|
|
656
|
+
try {
|
|
657
|
+
const revisions = await client.getRevisions(objectType, name, { include, group });
|
|
658
|
+
return textResult(JSON.stringify(revisions, null, 2));
|
|
659
|
+
}
|
|
660
|
+
catch (err) {
|
|
661
|
+
if (isNotFoundError(err)) {
|
|
662
|
+
return textResult(`No version history available for ${objectType} "${name}" on this SAP system. ` +
|
|
663
|
+
`This usually means the object does not exist, or the ADT versions endpoint is not supported for ${objectType} on this backend release.`);
|
|
664
|
+
}
|
|
665
|
+
throw err;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
case 'VERSION_SOURCE': {
|
|
669
|
+
const versionUri = String(args.versionUri ?? '');
|
|
670
|
+
if (!versionUri) {
|
|
671
|
+
return errorResult('VERSION_SOURCE requires a versionUri parameter. Get it from SAPRead(type="VERSIONS", name="...") response (.revisions[].uri).');
|
|
672
|
+
}
|
|
673
|
+
try {
|
|
674
|
+
return textResult(await client.getRevisionSource(versionUri));
|
|
675
|
+
}
|
|
676
|
+
catch (err) {
|
|
677
|
+
if (isNotFoundError(err)) {
|
|
678
|
+
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="...").`);
|
|
679
|
+
}
|
|
680
|
+
throw err;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
614
683
|
case 'TRAN': {
|
|
615
684
|
const tran = await client.getTransaction(name);
|
|
616
685
|
// Enrich with program name via SQL — only if free SQL is allowed by safety config
|
|
@@ -747,7 +816,7 @@ async function handleSAPRead(client, args, cachingLayer) {
|
|
|
747
816
|
}
|
|
748
817
|
}
|
|
749
818
|
default:
|
|
750
|
-
return errorResult(`Unknown SAPRead type: "${type}". Supported types: PROG, CLAS, INTF, FUNC, FUGR, INCL, DDLS, DCLS, DDLX, BDEF, SRVD, SRVB, TABL, VIEW, STRU, DOMA, DTEL, TRAN, TABLE_CONTENTS, DEVC, SOBJ, SYSTEM, COMPONENTS, MESSAGES, TEXT_ELEMENTS, VARIANTS, BSP, BSP_DEPLOY, API_STATE, INACTIVE_OBJECTS. ` +
|
|
819
|
+
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. ` +
|
|
751
820
|
'Tip: Type aliases are auto-normalized (e.g., DDLS/DF → DDLS, DCLS/DL → DCLS, CLAS/OC → CLAS, PROG/P → PROG). ' +
|
|
752
821
|
'Do not pass a URI — use the "type" and "name" parameters instead.');
|
|
753
822
|
}
|
|
@@ -835,9 +904,8 @@ async function handleSAPQuery(client, args) {
|
|
|
835
904
|
throw err;
|
|
836
905
|
}
|
|
837
906
|
}
|
|
838
|
-
//
|
|
839
|
-
|
|
840
|
-
async function handleSAPLint(_client, args, config) {
|
|
907
|
+
// Some SAPLint actions run offline (@abaplint/core), others call SAP ADT formatter APIs.
|
|
908
|
+
async function handleSAPLint(client, args, config) {
|
|
841
909
|
const action = String(args.action ?? '');
|
|
842
910
|
const ruleOverrides = args.rules;
|
|
843
911
|
const configOptions = buildLintConfigOptions(config, ruleOverrides);
|
|
@@ -876,8 +944,33 @@ async function handleSAPLint(_client, args, config) {
|
|
|
876
944
|
disabledRuleNames: disabled.map((r) => r.rule),
|
|
877
945
|
}, null, 2));
|
|
878
946
|
}
|
|
947
|
+
case 'format': {
|
|
948
|
+
const source = String(args.source ?? '');
|
|
949
|
+
if (!source)
|
|
950
|
+
return errorResult('"source" is required for format action.');
|
|
951
|
+
const formatted = await prettyPrint(client.http, client.safety, source);
|
|
952
|
+
return textResult(formatted);
|
|
953
|
+
}
|
|
954
|
+
case 'get_formatter_settings': {
|
|
955
|
+
const settings = await getPrettyPrinterSettings(client.http, client.safety);
|
|
956
|
+
return textResult(JSON.stringify(settings, null, 2));
|
|
957
|
+
}
|
|
958
|
+
case 'set_formatter_settings': {
|
|
959
|
+
const indentation = args.indentation;
|
|
960
|
+
const style = args.style;
|
|
961
|
+
if (indentation === undefined && style === undefined) {
|
|
962
|
+
return errorResult('At least one of "indentation" or "style" is required for set_formatter_settings.');
|
|
963
|
+
}
|
|
964
|
+
const current = await getPrettyPrinterSettings(client.http, client.safety);
|
|
965
|
+
const next = {
|
|
966
|
+
indentation: indentation ?? current.indentation,
|
|
967
|
+
style: style ?? current.style,
|
|
968
|
+
};
|
|
969
|
+
await setPrettyPrinterSettings(client.http, client.safety, next);
|
|
970
|
+
return textResult(JSON.stringify(next, null, 2));
|
|
971
|
+
}
|
|
879
972
|
default:
|
|
880
|
-
return errorResult(`Unknown SAPLint action: "${action}". Supported: lint, lint_and_fix, list_rules. For atc/syntax/unittest, use SAPDiagnose instead.`);
|
|
973
|
+
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.`);
|
|
881
974
|
}
|
|
882
975
|
}
|
|
883
976
|
/**
|
|
@@ -904,12 +997,13 @@ const DATAELEMENT_V2_CONTENT_TYPE = 'application/vnd.sap.adt.dataelements.v2+xml
|
|
|
904
997
|
const SERVICEBINDING_V2_CONTENT_TYPE = 'application/vnd.sap.adt.businessservices.servicebinding.v2+xml; charset=utf-8';
|
|
905
998
|
const BDEF_CONTENT_TYPE = 'application/vnd.sap.adt.blues.v1+xml';
|
|
906
999
|
const MESSAGECLASS_CONTENT_TYPE = 'application/vnd.sap.adt.mc.messageclass+xml';
|
|
1000
|
+
const SKTD_V2_CONTENT_TYPE = 'application/vnd.sap.adt.sktdv2+xml';
|
|
907
1001
|
function isMetadataWriteType(type) {
|
|
908
1002
|
return type === 'DOMA' || type === 'DTEL' || type === 'MSAG' || type === 'SRVB';
|
|
909
1003
|
}
|
|
910
1004
|
/** Types that require a specific vendor content type for creation (not application/*) */
|
|
911
1005
|
function needsVendorContentType(type) {
|
|
912
|
-
return type === 'DOMA' || type === 'DTEL' || type === 'BDEF' || type === 'MSAG';
|
|
1006
|
+
return type === 'DOMA' || type === 'DTEL' || type === 'BDEF' || type === 'MSAG' || type === 'SKTD';
|
|
913
1007
|
}
|
|
914
1008
|
/** Content type used for create POST */
|
|
915
1009
|
function createContentTypeForType(type) {
|
|
@@ -946,6 +1040,8 @@ function vendorContentTypeForType(type) {
|
|
|
946
1040
|
return BDEF_CONTENT_TYPE;
|
|
947
1041
|
case 'MSAG':
|
|
948
1042
|
return MESSAGECLASS_CONTENT_TYPE;
|
|
1043
|
+
case 'SKTD':
|
|
1044
|
+
return SKTD_V2_CONTENT_TYPE;
|
|
949
1045
|
default:
|
|
950
1046
|
// Wildcard lets the SAP server resolve the correct handler.
|
|
951
1047
|
// Sending 'application/xml' causes 415 on DDL-based endpoints
|
|
@@ -1435,6 +1531,7 @@ const SLASH_TYPE_MAP = {
|
|
|
1435
1531
|
'DEVC/K': 'DEVC',
|
|
1436
1532
|
'TRAN/O': 'TRAN',
|
|
1437
1533
|
'VIEW/V': 'VIEW',
|
|
1534
|
+
'SKTD/TYP': 'SKTD',
|
|
1438
1535
|
};
|
|
1439
1536
|
/** Normalize ADT type codes and aliases to ARC-1 canonical short types. */
|
|
1440
1537
|
export function normalizeObjectType(type) {
|
|
@@ -1545,13 +1642,17 @@ function objectBasePath(type) {
|
|
|
1545
1642
|
return '/sap/bc/adt/packages/';
|
|
1546
1643
|
case 'TRAN':
|
|
1547
1644
|
return '/sap/bc/adt/vit/wb/object_type/trant/object_name/';
|
|
1645
|
+
case 'SKTD':
|
|
1646
|
+
return '/sap/bc/adt/documentation/ktd/documents/';
|
|
1548
1647
|
default:
|
|
1549
1648
|
return '/sap/bc/adt/programs/programs/';
|
|
1550
1649
|
}
|
|
1551
1650
|
}
|
|
1552
1651
|
/** Map object type + name to the ADT object URL used by CRUD/DevTools/etc. Name is URI-encoded. */
|
|
1553
1652
|
function objectUrlForType(type, name) {
|
|
1554
|
-
|
|
1653
|
+
// KTD endpoints require lowercase object names in the URL path (confirmed via Eclipse ADT trace).
|
|
1654
|
+
const effectiveName = type === 'SKTD' ? name.toLowerCase() : name;
|
|
1655
|
+
return `${objectBasePath(type)}${encodeURIComponent(effectiveName)}`;
|
|
1555
1656
|
}
|
|
1556
1657
|
/** Infer SAP object type from naming conventions. Returns empty string if type cannot be determined. */
|
|
1557
1658
|
function inferObjectType(name) {
|
|
@@ -1569,7 +1670,8 @@ function inferObjectType(name) {
|
|
|
1569
1670
|
* Used for API release state where the full URI is encoded as a single path segment by the caller.
|
|
1570
1671
|
*/
|
|
1571
1672
|
function objectUrlForTypeRaw(type, name) {
|
|
1572
|
-
|
|
1673
|
+
const effectiveName = type === 'SKTD' ? name.toLowerCase() : name;
|
|
1674
|
+
return `${objectBasePath(type)}${effectiveName}`;
|
|
1573
1675
|
}
|
|
1574
1676
|
/** Get the source URL for an object (appends /source/main) */
|
|
1575
1677
|
function sourceUrlForType(type, name) {
|
|
@@ -1602,6 +1704,19 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
|
|
|
1602
1704
|
switch (action) {
|
|
1603
1705
|
case 'update': {
|
|
1604
1706
|
const existingPackage = await enforcePackageForExistingObject();
|
|
1707
|
+
if (type === 'SKTD') {
|
|
1708
|
+
// KTD update requires the full <sktd:docu> XML envelope with the Markdown
|
|
1709
|
+
// body base64-encoded inside <sktd:text>, PUT with
|
|
1710
|
+
// `application/vnd.sap.adt.sktdv2+xml`. PUTting raw text/plain silently
|
|
1711
|
+
// no-ops (or 415s on strict systems). Fetch the current envelope,
|
|
1712
|
+
// replace only the <sktd:text> body, and PUT it back — preserves
|
|
1713
|
+
// responsible/masterLanguage/packageRef/refObject metadata.
|
|
1714
|
+
const currentEnvelope = await client.getKtd(name);
|
|
1715
|
+
const body = rewriteKtdText(currentEnvelope, source);
|
|
1716
|
+
await safeUpdateObject(client.http, client.safety, objectUrl, body, SKTD_V2_CONTENT_TYPE, transport);
|
|
1717
|
+
cachingLayer?.invalidate(type, name);
|
|
1718
|
+
return textResult(`Successfully updated ${type} ${name}.`);
|
|
1719
|
+
}
|
|
1605
1720
|
if (isMetadataWriteType(type)) {
|
|
1606
1721
|
// Metadata updates are full-XML-replace — we must fetch existing metadata
|
|
1607
1722
|
// and merge with provided fields so omitted fields keep their current values.
|
|
@@ -1675,6 +1790,45 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
|
|
|
1675
1790
|
if (!affResult.valid) {
|
|
1676
1791
|
return errorResult(`AFF metadata validation failed for ${type} ${name}:\n- ${(affResult.errors ?? []).join('\n- ')}\n\nFix the metadata and retry.`);
|
|
1677
1792
|
}
|
|
1793
|
+
if (type === 'SKTD') {
|
|
1794
|
+
// A KTD is not a standalone object — it documents a parent object (e.g., a DDLS view or a CLAS).
|
|
1795
|
+
// The create POST goes to the collection URL with a sktd:docu XML body that references the parent.
|
|
1796
|
+
const refType = String(args.refObjectType ?? '');
|
|
1797
|
+
if (!refType) {
|
|
1798
|
+
return errorResult('"refObjectType" is required for SKTD create — the ADT type+subtype of the parent object being documented (e.g., "DDLS/DF", "CLAS/OC", "PROG/P", "INTF/OI", "BDEF/BDO", "SRVD/SRV").');
|
|
1799
|
+
}
|
|
1800
|
+
const refName = String(args.refObjectName ?? name);
|
|
1801
|
+
// SAP rule: a KTD's own name must equal the parent object's name (one KTD per object).
|
|
1802
|
+
// Creating a KTD named differently from its parent fails server-side with a cryptic
|
|
1803
|
+
// "Check of condition failed" — fail fast with a clear message instead.
|
|
1804
|
+
if (refName.toUpperCase() !== name.toUpperCase()) {
|
|
1805
|
+
return errorResult(`SKTD name "${name}" must match refObjectName "${refName}" — a Knowledge Transfer Document inherits the name of the ABAP object it documents (one KTD per object). To document "${refName}", call SAPWrite(action="create", type="SKTD", name="${refName}", refObjectType="${refType}", ...).`);
|
|
1806
|
+
}
|
|
1807
|
+
const refDescription = String(args.refObjectDescription ?? '');
|
|
1808
|
+
// Build the parent URI. ADT URIs use lowercase names by convention (matches the Eclipse trace).
|
|
1809
|
+
const refParentType = refType.split('/')[0] ?? '';
|
|
1810
|
+
const refUri = `${objectBasePath(refParentType)}${encodeURIComponent(refName.toLowerCase())}`;
|
|
1811
|
+
const ktdBody = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1812
|
+
<sktd:docu xmlns:sktd="http://www.sap.com/wbobj/texts/sktd" xmlns:adtcore="http://www.sap.com/adt/core" adtcore:language="EN" adtcore:name="${escapeXml(name)}" adtcore:type="SKTD/TYP" adtcore:masterLanguage="EN">
|
|
1813
|
+
<adtcore:packageRef adtcore:name="${escapeXml(pkg)}"/>
|
|
1814
|
+
<sktd:refObject adtcore:description="${escapeXml(refDescription)}" adtcore:name="${escapeXml(refName)}" adtcore:type="${escapeXml(refType)}" adtcore:uri="${escapeXml(refUri)}"/>
|
|
1815
|
+
</sktd:docu>`;
|
|
1816
|
+
const ktdCreateUrl = '/sap/bc/adt/documentation/ktd/documents';
|
|
1817
|
+
const ktdResult = await createObject(client.http, client.safety, ktdCreateUrl, ktdBody, SKTD_V2_CONTENT_TYPE, effectiveTransport);
|
|
1818
|
+
// If initial Markdown was provided, follow up with an update PUT to write it.
|
|
1819
|
+
// Same envelope contract as the update path: fetch-then-rewrite ensures we
|
|
1820
|
+
// PUT back exactly the shape SAP gave us (with all the server-assigned
|
|
1821
|
+
// metadata), only swapping <sktd:text>.
|
|
1822
|
+
if (source) {
|
|
1823
|
+
const currentEnvelope = await client.getKtd(name);
|
|
1824
|
+
const body = rewriteKtdText(currentEnvelope, source);
|
|
1825
|
+
await safeUpdateObject(client.http, client.safety, objectUrl, body, SKTD_V2_CONTENT_TYPE, effectiveTransport);
|
|
1826
|
+
cachingLayer?.invalidate(type, name);
|
|
1827
|
+
return textResult(`Created SKTD ${name} in package ${pkg} and wrote Markdown content.\nNext step: SAPActivate(type="SKTD", name="${name}").\n${ktdResult}`);
|
|
1828
|
+
}
|
|
1829
|
+
cachingLayer?.invalidate(type, name);
|
|
1830
|
+
return textResult(`Created SKTD ${name} in package ${pkg} (no Markdown content written — pass "source" to write the body).\nNext step: SAPActivate(type="SKTD", name="${name}").\n${ktdResult}`);
|
|
1831
|
+
}
|
|
1678
1832
|
// Build type-specific creation XML body.
|
|
1679
1833
|
// SAP ADT requires the root element to match the object type —
|
|
1680
1834
|
// a generic objectReferences body returns 400 "System expected the element ...".
|
|
@@ -1785,7 +1939,7 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
|
|
|
1785
1939
|
}
|
|
1786
1940
|
case 'delete': {
|
|
1787
1941
|
await enforcePackageForExistingObject();
|
|
1788
|
-
// Lock, delete, unlock pattern — auto-propagate lock corrNr if no explicit transport
|
|
1942
|
+
// Lock, delete, unlock pattern (works for all types including SKTD) — auto-propagate lock corrNr if no explicit transport
|
|
1789
1943
|
await client.http.withStatefulSession(async (session) => {
|
|
1790
1944
|
const lock = await lockObject(session, client.safety, objectUrl);
|
|
1791
1945
|
const effectiveTransport = transport ?? (lock.corrNr || undefined);
|
|
@@ -2504,14 +2658,56 @@ async function handleSAPTransport(client, args) {
|
|
|
2504
2658
|
summary,
|
|
2505
2659
|
}, null, 2));
|
|
2506
2660
|
}
|
|
2661
|
+
case 'history': {
|
|
2662
|
+
const objectType = String(args.type ?? '');
|
|
2663
|
+
const objectName = String(args.name ?? '');
|
|
2664
|
+
if (!objectType || !objectName) {
|
|
2665
|
+
return errorResult('"type" and "name" are required for "history" action.');
|
|
2666
|
+
}
|
|
2667
|
+
const objectUrl = objectUrlForType(objectType, objectName);
|
|
2668
|
+
const primary = await getObjectTransports(client.http, client.safety, objectUrl);
|
|
2669
|
+
let candidateTransports = primary.candidateTransports;
|
|
2670
|
+
// Fallback: if per-object transport lookup is empty, derive the package via
|
|
2671
|
+
// the object metadata endpoint and ask transportchecks for candidate transports.
|
|
2672
|
+
if (primary.relatedTransports.length === 0 && candidateTransports.length === 0) {
|
|
2673
|
+
try {
|
|
2674
|
+
const pkg = await client.resolveObjectPackage(objectUrl);
|
|
2675
|
+
if (pkg && pkg !== '$TMP') {
|
|
2676
|
+
const info = await getTransportInfo(client.http, client.safety, objectUrl, pkg, '');
|
|
2677
|
+
candidateTransports = info.existingTransports;
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
catch {
|
|
2681
|
+
// best-effort-fallback
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
const lockOwner = primary.relatedTransports[0]?.owner;
|
|
2685
|
+
const summary = primary.lockedTransport
|
|
2686
|
+
? `Object ${objectName} is locked in transport ${primary.lockedTransport}${lockOwner ? ` by ${lockOwner}` : ''}.`
|
|
2687
|
+
: candidateTransports.length > 0
|
|
2688
|
+
? `Object ${objectName} has no active lock; ${candidateTransports.length} transport(s) available for assignment.`
|
|
2689
|
+
: `Object ${objectName} has no related or candidate transports (likely $TMP / local object).`;
|
|
2690
|
+
const history = {
|
|
2691
|
+
object: { type: objectType, name: objectName, uri: objectUrl },
|
|
2692
|
+
...(primary.lockedTransport ? { lockedTransport: primary.lockedTransport } : {}),
|
|
2693
|
+
relatedTransports: primary.relatedTransports,
|
|
2694
|
+
candidateTransports,
|
|
2695
|
+
summary,
|
|
2696
|
+
};
|
|
2697
|
+
return textResult(JSON.stringify(history, null, 2));
|
|
2698
|
+
}
|
|
2507
2699
|
default:
|
|
2508
|
-
return errorResult(`Unknown SAPTransport action: ${action}. Supported: list, get, create, release, delete, reassign, release_recursive, check`);
|
|
2700
|
+
return errorResult(`Unknown SAPTransport action: ${action}. Supported: list, get, create, release, delete, reassign, release_recursive, check, history`);
|
|
2509
2701
|
}
|
|
2510
2702
|
}
|
|
2511
2703
|
// ─── SAPContext Handler ───────────────────────────────────────────────
|
|
2512
2704
|
async function handleSAPContext(client, args, cachingLayer) {
|
|
2513
2705
|
const action = String(args.action ?? '');
|
|
2514
|
-
|
|
2706
|
+
// action="impact" is DDLS-only on the server side — default the type so LLMs
|
|
2707
|
+
// don't have to supply it redundantly (and don't get a validation retry when
|
|
2708
|
+
// they don't). Any non-DDLS value still fails the guardrail below.
|
|
2709
|
+
const rawType = String(args.type ?? '');
|
|
2710
|
+
const type = normalizeObjectType(rawType || (action === 'impact' ? 'DDLS' : ''));
|
|
2515
2711
|
const name = String(args.name ?? '');
|
|
2516
2712
|
const maxDeps = Number(args.maxDeps ?? 20);
|
|
2517
2713
|
const depth = Math.min(Math.max(Number(args.depth ?? 1), 1), 3);
|
|
@@ -2547,6 +2743,42 @@ async function handleSAPContext(client, args, cachingLayer) {
|
|
|
2547
2743
|
const { source } = await cachingLayer.getSource(objType, objName, fetcher);
|
|
2548
2744
|
return source;
|
|
2549
2745
|
};
|
|
2746
|
+
if (action === 'impact') {
|
|
2747
|
+
if (type !== 'DDLS') {
|
|
2748
|
+
return errorResult('SAPContext(action="impact") supports DDLS only. For non-CDS objects, use SAPNavigate(action="references").');
|
|
2749
|
+
}
|
|
2750
|
+
const ddlSource = await cachedGet('DDLS', name, () => client.getDdls(name));
|
|
2751
|
+
const upstream = buildCdsUpstream(extractCdsDependencies(ddlSource));
|
|
2752
|
+
const includeIndirect = args.includeIndirect === true;
|
|
2753
|
+
let downstream = classifyCdsImpact([], { includeIndirect });
|
|
2754
|
+
const warnings = [];
|
|
2755
|
+
try {
|
|
2756
|
+
const whereUsed = await findWhereUsed(client.http, client.safety, objectUrlForType('DDLS', name));
|
|
2757
|
+
downstream = classifyCdsImpact(whereUsed, { includeIndirect });
|
|
2758
|
+
}
|
|
2759
|
+
catch (err) {
|
|
2760
|
+
if (err instanceof AdtApiError && [404, 405, 415, 501].includes(err.statusCode)) {
|
|
2761
|
+
warnings.push('Where-used endpoint not available on this system');
|
|
2762
|
+
}
|
|
2763
|
+
else {
|
|
2764
|
+
throw err;
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
const upstreamCount = upstream.tables.length + upstream.views.length + upstream.associations.length + upstream.compositions.length;
|
|
2768
|
+
const response = {
|
|
2769
|
+
name,
|
|
2770
|
+
type: 'DDLS',
|
|
2771
|
+
upstream,
|
|
2772
|
+
downstream,
|
|
2773
|
+
summary: {
|
|
2774
|
+
upstreamCount,
|
|
2775
|
+
downstreamTotal: downstream.summary.total,
|
|
2776
|
+
downstreamDirect: downstream.summary.direct,
|
|
2777
|
+
},
|
|
2778
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
2779
|
+
};
|
|
2780
|
+
return textResult(JSON.stringify(response, null, 2));
|
|
2781
|
+
}
|
|
2550
2782
|
// Get source — either provided or fetched from SAP
|
|
2551
2783
|
let source;
|
|
2552
2784
|
if (args.source) {
|
|
@@ -2615,6 +2847,45 @@ async function handleSAPContext(client, args, cachingLayer) {
|
|
|
2615
2847
|
const result = await compressContext(client, source, name, type, maxDeps, depth, abaplintVersion, cachingLayer);
|
|
2616
2848
|
return textResult(result.output);
|
|
2617
2849
|
}
|
|
2850
|
+
function buildCdsUpstream(deps) {
|
|
2851
|
+
const tableNames = new Set();
|
|
2852
|
+
const viewNames = new Set();
|
|
2853
|
+
const associationNames = new Set();
|
|
2854
|
+
const compositionNames = new Set();
|
|
2855
|
+
for (const dep of deps) {
|
|
2856
|
+
const upperName = dep.name.toUpperCase();
|
|
2857
|
+
if (dep.kind === 'association') {
|
|
2858
|
+
associationNames.add(upperName);
|
|
2859
|
+
continue;
|
|
2860
|
+
}
|
|
2861
|
+
if (dep.kind === 'composition') {
|
|
2862
|
+
compositionNames.add(upperName);
|
|
2863
|
+
continue;
|
|
2864
|
+
}
|
|
2865
|
+
if (dep.kind === 'projection_base') {
|
|
2866
|
+
viewNames.add(upperName);
|
|
2867
|
+
continue;
|
|
2868
|
+
}
|
|
2869
|
+
if (isLikelyCdsViewName(upperName)) {
|
|
2870
|
+
viewNames.add(upperName);
|
|
2871
|
+
}
|
|
2872
|
+
else {
|
|
2873
|
+
tableNames.add(upperName);
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
return {
|
|
2877
|
+
tables: [...tableNames].sort().map((name) => ({ name })),
|
|
2878
|
+
views: [...viewNames].sort().map((name) => ({ name })),
|
|
2879
|
+
associations: [...associationNames].sort().map((name) => ({ name })),
|
|
2880
|
+
compositions: [...compositionNames].sort().map((name) => ({ name })),
|
|
2881
|
+
};
|
|
2882
|
+
}
|
|
2883
|
+
function isLikelyCdsViewName(name) {
|
|
2884
|
+
if (name.startsWith('/')) {
|
|
2885
|
+
return /\/[ICRPAZ][A-Z0-9_]*_/.test(name);
|
|
2886
|
+
}
|
|
2887
|
+
return /^(ZI_|ZC_|ZR_|ZP_|I_|C_|R_|P_)/.test(name);
|
|
2888
|
+
}
|
|
2618
2889
|
// ─── SAPManage Handler ────────────────────────────────────────────────
|
|
2619
2890
|
/** Cached feature status — populated on first probe */
|
|
2620
2891
|
let cachedFeatures;
|
|
@@ -2713,6 +2984,72 @@ async function handleSAPManage(client, config, args, cachingLayer, isPerUserClie
|
|
|
2713
2984
|
});
|
|
2714
2985
|
return textResult(`Deleted package ${name}.`);
|
|
2715
2986
|
}
|
|
2987
|
+
case 'change_package': {
|
|
2988
|
+
const objectName = String(args.objectName ?? '').trim();
|
|
2989
|
+
const objectType = String(args.objectType ?? '').trim();
|
|
2990
|
+
const oldPackage = String(args.oldPackage ?? '').trim();
|
|
2991
|
+
const newPackage = String(args.newPackage ?? '').trim();
|
|
2992
|
+
const transport = String(args.transport ?? '').trim();
|
|
2993
|
+
let objectUri = String(args.objectUri ?? '').trim();
|
|
2994
|
+
if (!objectName)
|
|
2995
|
+
return errorResult('"objectName" is required for change_package action.');
|
|
2996
|
+
if (!objectType)
|
|
2997
|
+
return errorResult('"objectType" is required for change_package action.');
|
|
2998
|
+
if (!oldPackage)
|
|
2999
|
+
return errorResult('"oldPackage" is required for change_package action.');
|
|
3000
|
+
if (!newPackage)
|
|
3001
|
+
return errorResult('"newPackage" is required for change_package action.');
|
|
3002
|
+
checkOperation(client.safety, OperationType.Update, 'ChangePackage');
|
|
3003
|
+
checkPackage(client.safety, oldPackage);
|
|
3004
|
+
checkPackage(client.safety, newPackage);
|
|
3005
|
+
// Resolve object URI via search if not provided
|
|
3006
|
+
if (!objectUri) {
|
|
3007
|
+
const searchResp = await client.http.get(`/sap/bc/adt/repository/informationsystem/search?operation=quickSearch&query=${encodeURIComponent(objectName)}&maxResults=10`);
|
|
3008
|
+
const uriMatch = searchResp.body.match(new RegExp(`adtcore:uri="([^"]*)"[^>]*adtcore:type="${objectType.replace('/', '\\/')}"`, 'i'));
|
|
3009
|
+
if (!uriMatch?.[1]) {
|
|
3010
|
+
return errorResult(`Could not find object "${objectName}" with type "${objectType}" via ADT search. ` +
|
|
3011
|
+
`Verify the object exists and the type is correct (e.g., CLAS/OC, DDLS/DF, PROG/P).`);
|
|
3012
|
+
}
|
|
3013
|
+
objectUri = uriMatch[1];
|
|
3014
|
+
}
|
|
3015
|
+
// Transport pre-flight for non-local target packages
|
|
3016
|
+
let effectiveTransport = transport || undefined;
|
|
3017
|
+
if (!effectiveTransport && newPackage.toUpperCase() !== '$TMP') {
|
|
3018
|
+
try {
|
|
3019
|
+
const transportInfo = await getTransportInfo(client.http, client.safety, objectUri, newPackage, 'I');
|
|
3020
|
+
if (transportInfo.lockedTransport) {
|
|
3021
|
+
effectiveTransport = transportInfo.lockedTransport;
|
|
3022
|
+
}
|
|
3023
|
+
else if (!transportInfo.isLocal && transportInfo.recording) {
|
|
3024
|
+
const existingList = transportInfo.existingTransports.length > 0
|
|
3025
|
+
? `\n\nExisting transports for this package:\n${transportInfo.existingTransports
|
|
3026
|
+
.slice(0, 10)
|
|
3027
|
+
.map((t) => ` - ${t.id}: ${t.description} (${t.owner})`)
|
|
3028
|
+
.join('\n')}`
|
|
3029
|
+
: '';
|
|
3030
|
+
return errorResult(`Package "${newPackage}" requires a transport number for change_package, but none was provided.\n\n` +
|
|
3031
|
+
`To fix this, either:\n` +
|
|
3032
|
+
`1. Use SAPTransport(action="list") to find an existing modifiable transport\n` +
|
|
3033
|
+
`2. Use SAPTransport(action="create", description="...") to create a new one\n` +
|
|
3034
|
+
`3. Then retry SAPManage(action="change_package", ..., transport="<transport_id>")` +
|
|
3035
|
+
existingList);
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
catch {
|
|
3039
|
+
// Graceful fallback: let SAP enforce transport requirements if the pre-check fails.
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
const result = await changePackage(client.http, client.safety, {
|
|
3043
|
+
objectUri,
|
|
3044
|
+
objectType,
|
|
3045
|
+
objectName,
|
|
3046
|
+
oldPackage,
|
|
3047
|
+
newPackage,
|
|
3048
|
+
transport: effectiveTransport,
|
|
3049
|
+
});
|
|
3050
|
+
const transportNote = result.transport ? ` (transport: ${result.transport})` : '';
|
|
3051
|
+
return textResult(`Moved ${objectName} from package ${oldPackage} to ${newPackage}${transportNote}.`);
|
|
3052
|
+
}
|
|
2716
3053
|
case 'flp_list_catalogs': {
|
|
2717
3054
|
const catalogs = await listCatalogs(client.http, client.safety);
|
|
2718
3055
|
const customCount = catalogs.filter((c) => /^(Z|Y)/i.test(c.domainId)).length;
|