arc-1 0.6.4 → 0.6.5

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 (51) hide show
  1. package/dist/adt/crud.d.ts +1 -1
  2. package/dist/adt/crud.d.ts.map +1 -1
  3. package/dist/adt/crud.js +9 -2
  4. package/dist/adt/crud.js.map +1 -1
  5. package/dist/adt/ddic-xml.d.ts +17 -0
  6. package/dist/adt/ddic-xml.d.ts.map +1 -1
  7. package/dist/adt/ddic-xml.js +38 -3
  8. package/dist/adt/ddic-xml.js.map +1 -1
  9. package/dist/adt/devtools.d.ts +10 -2
  10. package/dist/adt/devtools.d.ts.map +1 -1
  11. package/dist/adt/devtools.js +169 -2
  12. package/dist/adt/devtools.js.map +1 -1
  13. package/dist/adt/errors.d.ts +19 -0
  14. package/dist/adt/errors.d.ts.map +1 -1
  15. package/dist/adt/errors.js +80 -0
  16. package/dist/adt/errors.js.map +1 -1
  17. package/dist/adt/http.d.ts.map +1 -1
  18. package/dist/adt/http.js +7 -0
  19. package/dist/adt/http.js.map +1 -1
  20. package/dist/adt/types.d.ts +30 -0
  21. package/dist/adt/types.d.ts.map +1 -1
  22. package/dist/handlers/intent.d.ts +2 -0
  23. package/dist/handlers/intent.d.ts.map +1 -1
  24. package/dist/handlers/intent.js +253 -33
  25. package/dist/handlers/intent.js.map +1 -1
  26. package/dist/handlers/schemas.d.ts +25 -0
  27. package/dist/handlers/schemas.d.ts.map +1 -1
  28. package/dist/handlers/schemas.js +12 -1
  29. package/dist/handlers/schemas.js.map +1 -1
  30. package/dist/handlers/tools.d.ts.map +1 -1
  31. package/dist/handlers/tools.js +65 -17
  32. package/dist/handlers/tools.js.map +1 -1
  33. package/dist/lint/config-builder.d.ts.map +1 -1
  34. package/dist/lint/config-builder.js +2 -0
  35. package/dist/lint/config-builder.js.map +1 -1
  36. package/dist/lint/lint.d.ts.map +1 -1
  37. package/dist/lint/lint.js +30 -9
  38. package/dist/lint/lint.js.map +1 -1
  39. package/dist/lint/presets/cloud.d.ts.map +1 -1
  40. package/dist/lint/presets/cloud.js +9 -0
  41. package/dist/lint/presets/cloud.js.map +1 -1
  42. package/dist/lint/presets/onprem.d.ts.map +1 -1
  43. package/dist/lint/presets/onprem.js +9 -0
  44. package/dist/lint/presets/onprem.js.map +1 -1
  45. package/dist/server/server.d.ts +1 -1
  46. package/dist/server/server.js +1 -1
  47. package/dist/server/xsuaa.d.ts +13 -8
  48. package/dist/server/xsuaa.d.ts.map +1 -1
  49. package/dist/server/xsuaa.js +51 -12
  50. package/dist/server/xsuaa.js.map +1 -1
  51. package/package.json +1 -1
@@ -12,7 +12,7 @@
12
12
  import { findDefinition, findReferences, findWhereUsed, getCompletion, } from '../adt/codeintel.js';
13
13
  import { createObject, deleteObject, lockObject, safeUpdateObject, safeUpdateSource, unlockObject, updateObject, } from '../adt/crud.js';
14
14
  import { buildDataElementXml, buildDomainXml, buildMessageClassXml, buildPackageXml, buildServiceBindingXml, } from '../adt/ddic-xml.js';
15
- import { activate, activateBatch, publishServiceBinding, runAtcCheck, runUnitTests, syntaxCheck, unpublishServiceBinding, } from '../adt/devtools.js';
15
+ import { activate, activateBatch, applyFixProposal, getFixProposals, publishServiceBinding, runAtcCheck, runUnitTests, syntaxCheck, unpublishServiceBinding, } from '../adt/devtools.js';
16
16
  import { getDump, getTraceDbAccesses, getTraceHitlist, getTraceStatements, listDumps, listTraces, } from '../adt/diagnostics.js';
17
17
  import { AdtApiError, AdtNetworkError, AdtSafetyError, isNotFoundError } from '../adt/errors.js';
18
18
  import { classifyTextSearchError, mapSapReleaseToAbaplintVersion, probeFeatures } from '../adt/features.js';
@@ -77,6 +77,8 @@ function textResult(text) {
77
77
  function errorResult(message) {
78
78
  return { content: [{ type: 'text', text: message }], isError: true };
79
79
  }
80
+ const DDIC_SAVE_HINT_TYPES = new Set(['TABL', 'DDLS', 'BDEF', 'SRVD', 'SRVB', 'DDLX', 'DOMA', 'DTEL']);
81
+ const DDIC_POST_SAVE_CHECK_TYPES = new Set(['TABL', 'DDLS', 'BDEF', 'SRVD', 'SRVB', 'DDLX']);
80
82
  // ─── Search Helpers ─────────────────────────────────────────────────
81
83
  /**
82
84
  * Transliterate non-ASCII characters in search queries.
@@ -122,6 +124,7 @@ function formatErrorForLLM(err, message, _tool, args) {
122
124
  if (err instanceof AdtApiError) {
123
125
  // Append additional SAP messages (line numbers, secondary errors) if available
124
126
  const enriched = enrichWithSapDetails(err, message);
127
+ const argType = String(args.type ?? '').toUpperCase();
125
128
  if (err.isNotFound) {
126
129
  const name = String(args.name ?? '');
127
130
  const type = String(args.type ?? '');
@@ -135,6 +138,10 @@ function formatErrorForLLM(err, message, _tool, args) {
135
138
  if (transportHint) {
136
139
  return `${enriched}\n\nHint: ${transportHint}`;
137
140
  }
141
+ if ((err.statusCode === 400 || err.statusCode === 409) && DDIC_SAVE_HINT_TYPES.has(argType)) {
142
+ return (`${enriched}\n\nHint: DDIC save failed. Check the diagnostic details above for specific field or annotation errors. ` +
143
+ 'Common fixes: add missing @AbapCatalog annotations, fix field type names, check key field definitions.');
144
+ }
138
145
  // Server errors (500, 502, 503, etc.)
139
146
  if (err.isServerError) {
140
147
  return `${enriched}\n\nHint: SAP application server error (${err.statusCode}). This is often transient — wait 10-30 seconds and retry. If the error persists, check SAPDiagnose(action="dumps") for short dumps, or verify the SAP system is responding via SAPRead(type="SYSTEM").`;
@@ -156,18 +163,49 @@ function enrichWithSapDetails(err, message) {
156
163
  if (extraMessages.length > 0) {
157
164
  parts.push(`\nAdditional detail:\n${extraMessages.map((m) => ` - ${m}`).join('\n')}`);
158
165
  }
159
- // Surface line/column info from properties if present
160
- const lineInfo = props.LINE || props['T100KEY-NO'];
161
- if (lineInfo || Object.keys(props).length > 0) {
162
- const propStr = Object.entries(props)
163
- .slice(0, 5) // Limit to avoid overwhelming output
164
- .map(([k, v]) => `${k}=${v}`)
165
- .join(', ');
166
- if (propStr)
167
- parts.push(`Properties: ${propStr}`);
166
+ const ddicDiagnostics = AdtApiError.formatDdicDiagnostics(err.responseBody);
167
+ if (ddicDiagnostics) {
168
+ parts.push(ddicDiagnostics);
169
+ // Skip raw Properties dump — DDIC diagnostics already include the structured
170
+ // T100KEY details (message ID, number, variables, line). Showing both would
171
+ // triplicate the same information.
172
+ }
173
+ else {
174
+ // Surface line/column info from properties if present (non-DDIC errors only)
175
+ const lineInfo = props.LINE || props['T100KEY-NO'];
176
+ if (lineInfo || Object.keys(props).length > 0) {
177
+ const propStr = Object.entries(props)
178
+ .slice(0, 5) // Limit to avoid overwhelming output
179
+ .map(([k, v]) => `${k}=${v}`)
180
+ .join(', ');
181
+ if (propStr)
182
+ parts.push(`Properties: ${propStr}`);
183
+ }
168
184
  }
169
185
  return parts.join('\n');
170
186
  }
187
+ async function tryPostSaveSyntaxCheck(client, type, name) {
188
+ if (!DDIC_POST_SAVE_CHECK_TYPES.has(type.toUpperCase()))
189
+ return '';
190
+ try {
191
+ const checkResult = await syntaxCheck(client.http, client.safety, objectUrlForType(type, name), {
192
+ version: 'inactive',
193
+ });
194
+ if (!checkResult.hasErrors)
195
+ return '';
196
+ const errors = checkResult.messages.filter((msg) => msg.severity === 'error');
197
+ if (errors.length === 0)
198
+ return '';
199
+ const lines = errors.map((msg) => {
200
+ const line = msg.line ? `Line ${msg.line}` : 'Line ?';
201
+ return ` - ${line}: ${msg.text}`;
202
+ });
203
+ return `\nServer syntax check (inactive):\n${lines.join('\n')}`;
204
+ }
205
+ catch {
206
+ return '';
207
+ }
208
+ }
171
209
  /** Detect transport/corrNr failure signatures and return a remediation hint, or undefined if not transport-related. */
172
210
  function getTransportHint(err) {
173
211
  const body = (err.responseBody ?? '').toLowerCase();
@@ -263,6 +301,7 @@ export async function handleToolCall(client, config, toolName, args, authInfo, _
263
301
  // and returns a proper error message with the probe reason when source_code search is unavailable
264
302
  const schema = getToolSchema(toolName, isBtp);
265
303
  if (schema) {
304
+ args = normalizeTypeArgsForValidation(toolName, args);
266
305
  const parsed = schema.safeParse(args);
267
306
  if (!parsed.success) {
268
307
  const validationError = formatZodError(parsed.error, toolName);
@@ -397,7 +436,7 @@ const BTP_HINTS = {
397
436
  TRAN: 'Transaction codes (TRAN) are not available on BTP ABAP Environment. Use SAPSearch to find apps and services instead.',
398
437
  };
399
438
  async function handleSAPRead(client, args, cachingLayer) {
400
- const type = String(args.type ?? '');
439
+ const type = normalizeObjectType(String(args.type ?? ''));
401
440
  const name = String(args.name ?? '');
402
441
  // BTP: return helpful error for unavailable types
403
442
  if (isBtpSystem() && BTP_HINTS[type]) {
@@ -574,7 +613,7 @@ async function handleSAPRead(client, args, cachingLayer) {
574
613
  }
575
614
  case 'API_STATE': {
576
615
  // Determine object type for URL construction — use explicit objectType, infer from name, or error
577
- const explicitType = String(args.objectType ?? '').toUpperCase();
616
+ const explicitType = normalizeObjectType(String(args.objectType ?? ''));
578
617
  const inferredType = explicitType || inferObjectType(name);
579
618
  if (!inferredType) {
580
619
  return errorResult(`Cannot infer object type from name "${name}". Please specify objectType explicitly (e.g., objectType="CLAS", "INTF", "PROG", "TABL", "DDLS", "FUGR", "DOMA", "DTEL", "SRVD", "SRVB", "BDEF").`);
@@ -692,7 +731,7 @@ async function handleSAPRead(client, args, cachingLayer) {
692
731
  }
693
732
  default:
694
733
  return errorResult(`Unknown SAPRead type: "${type}". Supported types: PROG, CLAS, INTF, FUNC, FUGR, INCL, DDLS, 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. ` +
695
- 'Tip: Map objectType from SAPSearch results by dropping the slash suffix (e.g., DDLS/DF → type="DDLS", CLAS/OC → type="CLAS", PROG/P → type="PROG"). ' +
734
+ 'Tip: Type aliases are auto-normalized (e.g., DDLS/DF → DDLS, CLAS/OC → CLAS, PROG/P → PROG). ' +
696
735
  'Do not pass a URI — use the "type" and "name" parameters instead.');
697
736
  }
698
737
  }
@@ -706,7 +745,7 @@ async function handleSAPSearch(client, args) {
706
745
  return errorResult(`Source code search is not available on this SAP system. ${cachedFeatures.textSearch.reason ?? ''}` +
707
746
  `\nUse SAPSearch with searchType="object" to search by object name instead, or use SAPQuery to search metadata tables.`);
708
747
  }
709
- const objectType = args.objectType;
748
+ const objectType = args.objectType ? normalizeObjectType(String(args.objectType)) : undefined;
710
749
  const packageName = args.packageName;
711
750
  try {
712
751
  const results = await client.searchSource(rawQuery, maxResults, objectType, packageName);
@@ -939,6 +978,7 @@ function getMetadataWriteProperties(input) {
939
978
  bindingType: input.bindingType,
940
979
  category: input.category,
941
980
  version: input.version,
981
+ odataVersion: input.odataVersion,
942
982
  };
943
983
  return props;
944
984
  }
@@ -1016,6 +1056,7 @@ async function mergeMetadataWriteProperties(client, type, name, provided) {
1016
1056
  bindingType: provided.bindingType ?? existing.bindingType,
1017
1057
  category: provided.category ?? normalizeSrvbCategory(existing.bindingCategory),
1018
1058
  version: provided.version ?? existing.serviceVersion,
1059
+ odataVersion: provided.odataVersion ?? existing.odataVersion,
1019
1060
  };
1020
1061
  }
1021
1062
  }
@@ -1233,7 +1274,7 @@ export function buildCreateXml(type, name, pkg, description, properties) {
1233
1274
  throw new Error('SRVB create/update requires "serviceDefinition" (referenced SRVD name).');
1234
1275
  }
1235
1276
  const categoryRaw = properties?.category;
1236
- const category = categoryRaw === '1' || categoryRaw === 1 ? '1' : '0';
1277
+ const category = categoryRaw === '1' || categoryRaw === 1 ? '1' : categoryRaw === '0' || categoryRaw === 0 ? '0' : undefined;
1237
1278
  const params = {
1238
1279
  name,
1239
1280
  description,
@@ -1242,6 +1283,7 @@ export function buildCreateXml(type, name, pkg, description, properties) {
1242
1283
  bindingType: properties?.bindingType ? String(properties.bindingType) : undefined,
1243
1284
  category,
1244
1285
  version: properties?.version ? String(properties.version) : undefined,
1286
+ odataVersion: properties?.odataVersion ? String(properties.odataVersion) : undefined,
1245
1287
  };
1246
1288
  return buildServiceBindingXml(params);
1247
1289
  }
@@ -1341,6 +1383,97 @@ function escapeXml(s) {
1341
1383
  .replace(/>/g, '>');
1342
1384
  }
1343
1385
  // ─── Object URL Mapping ──────────────────────────────────────────────
1386
+ const SLASH_TYPE_MAP = {
1387
+ 'PROG/P': 'PROG',
1388
+ 'PROG/I': 'INCL',
1389
+ 'CLAS/OC': 'CLAS',
1390
+ 'CLAS/LI': 'CLAS',
1391
+ 'INTF/OI': 'INTF',
1392
+ 'FUNC/FM': 'FUNC',
1393
+ 'FUGR/F': 'FUGR',
1394
+ 'FUGR/FF': 'FUGR',
1395
+ 'DDLS/DF': 'DDLS',
1396
+ 'BDEF/BDO': 'BDEF',
1397
+ 'SRVD/SRV': 'SRVD',
1398
+ 'SRVB/SVB': 'SRVB',
1399
+ 'DDLX/EX': 'DDLX',
1400
+ 'TABL/DT': 'TABL',
1401
+ 'STRU/DS': 'STRU',
1402
+ 'DOMA/DD': 'DOMA',
1403
+ 'DTEL/DE': 'DTEL',
1404
+ 'MSAG/N': 'MSAG',
1405
+ 'DEVC/K': 'DEVC',
1406
+ 'TRAN/O': 'TRAN',
1407
+ 'VIEW/V': 'VIEW',
1408
+ };
1409
+ /** Normalize ADT type codes and aliases to ARC-1 canonical short types. */
1410
+ export function normalizeObjectType(type) {
1411
+ const normalized = String(type).trim().toUpperCase();
1412
+ if (!normalized)
1413
+ return '';
1414
+ return SLASH_TYPE_MAP[normalized] ?? normalized;
1415
+ }
1416
+ /** Normalize type fields before schema validation so slash/case aliases are accepted. */
1417
+ function normalizeTypeArgsForValidation(toolName, args) {
1418
+ switch (toolName) {
1419
+ case 'SAPRead':
1420
+ return {
1421
+ ...args,
1422
+ type: normalizeObjectType(String(args.type ?? '')),
1423
+ objectType: args.objectType === undefined ? undefined : normalizeObjectType(String(args.objectType ?? '')),
1424
+ };
1425
+ case 'SAPWrite':
1426
+ return {
1427
+ ...args,
1428
+ type: args.type === undefined ? undefined : normalizeObjectType(String(args.type ?? '')),
1429
+ objects: Array.isArray(args.objects)
1430
+ ? args.objects.map((obj) => typeof obj === 'object' && obj !== null
1431
+ ? {
1432
+ ...obj,
1433
+ type: normalizeObjectType(String(obj.type ?? '')),
1434
+ }
1435
+ : obj)
1436
+ : args.objects,
1437
+ };
1438
+ case 'SAPActivate':
1439
+ return {
1440
+ ...args,
1441
+ type: args.type === undefined ? undefined : normalizeObjectType(String(args.type ?? '')),
1442
+ objects: Array.isArray(args.objects)
1443
+ ? args.objects.map((obj) => typeof obj === 'object' && obj !== null
1444
+ ? {
1445
+ ...obj,
1446
+ type: normalizeObjectType(String(obj.type ?? '')),
1447
+ }
1448
+ : obj)
1449
+ : args.objects,
1450
+ };
1451
+ case 'SAPSearch':
1452
+ return {
1453
+ ...args,
1454
+ objectType: args.objectType === undefined ? undefined : normalizeObjectType(String(args.objectType ?? '')),
1455
+ };
1456
+ case 'SAPNavigate':
1457
+ // Only normalize `type` (for URL building). `objectType` is passed to SAP's
1458
+ // where-used scope API in slash format (e.g., CLAS/OC) — normalizing it would break the filter.
1459
+ return {
1460
+ ...args,
1461
+ type: args.type === undefined ? undefined : normalizeObjectType(String(args.type ?? '')),
1462
+ };
1463
+ case 'SAPDiagnose':
1464
+ return {
1465
+ ...args,
1466
+ type: args.type === undefined ? undefined : normalizeObjectType(String(args.type ?? '')),
1467
+ };
1468
+ case 'SAPContext':
1469
+ return {
1470
+ ...args,
1471
+ type: args.type === undefined ? undefined : normalizeObjectType(String(args.type ?? '')),
1472
+ };
1473
+ default:
1474
+ return args;
1475
+ }
1476
+ }
1344
1477
  /** Base path for an object type. Returns path prefix without trailing name segment. */
1345
1478
  function objectBasePath(type) {
1346
1479
  switch (type) {
@@ -1413,10 +1546,11 @@ function sourceUrlForType(type, name) {
1413
1546
  // ─── SAPWrite Handler ────────────────────────────────────────────────
1414
1547
  async function handleSAPWrite(client, args, config, cachingLayer) {
1415
1548
  const action = String(args.action ?? '');
1416
- const type = String(args.type ?? '');
1549
+ const type = normalizeObjectType(String(args.type ?? ''));
1417
1550
  const name = String(args.name ?? '');
1418
1551
  const source = String(args.source ?? '');
1419
1552
  const transport = args.transport;
1553
+ const lintOverride = args.lintBeforeWrite;
1420
1554
  // type and name are required for all actions except batch_create
1421
1555
  if (action !== 'batch_create' && (!type || !name)) {
1422
1556
  return errorResult('"type" and "name" are required for this action.');
@@ -1454,7 +1588,7 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
1454
1588
  if (cdsGuardUpdate)
1455
1589
  return cdsGuardUpdate;
1456
1590
  // Pre-write lint validation
1457
- const lintWarnings = runPreWriteLint(source, type, name, config);
1591
+ const lintWarnings = runPreWriteLint(source, type, name, config, lintOverride);
1458
1592
  if (lintWarnings.blocked)
1459
1593
  return lintWarnings.result;
1460
1594
  await safeUpdateSource(client.http, client.safety, objectUrl, srcUrl, source, transport);
@@ -1520,7 +1654,20 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
1520
1654
  // 'application/*' — the wildcard lets the SAP server resolve the correct
1521
1655
  // handler (matching how ADT Eclipse and abap-adt-api send requests).
1522
1656
  const contentType = createContentTypeForType(type);
1523
- const result = await createObject(client.http, client.safety, createUrl, body, contentType, effectiveTransport);
1657
+ const needsPackageParam = type === 'BDEF' || type === 'TABL';
1658
+ let result;
1659
+ try {
1660
+ result = await createObject(client.http, client.safety, createUrl, body, contentType, effectiveTransport, needsPackageParam ? pkg : undefined);
1661
+ }
1662
+ catch (createErr) {
1663
+ if (createErr instanceof AdtApiError && (createErr.statusCode === 400 || createErr.statusCode === 409)) {
1664
+ const syntaxDetail = await tryPostSaveSyntaxCheck(client, type, name);
1665
+ if (syntaxDetail) {
1666
+ createErr.message += syntaxDetail;
1667
+ }
1668
+ }
1669
+ throw createErr;
1670
+ }
1524
1671
  if (isMetadataWriteType(type)) {
1525
1672
  // SAP's DTEL POST ignores labels, searchHelp, etc. — they require a follow-up PUT.
1526
1673
  // Use withStatefulSession directly (not safeUpdateObject) to keep the lock cycle
@@ -1561,7 +1708,7 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
1561
1708
  // Step 2: Write source code if provided
1562
1709
  if (source) {
1563
1710
  // Pre-write lint validation
1564
- const lintWarnings = runPreWriteLint(source, type, name, config);
1711
+ const lintWarnings = runPreWriteLint(source, type, name, config, lintOverride);
1565
1712
  if (lintWarnings.blocked) {
1566
1713
  return textResult(`Created ${type} ${name} in package ${pkg}, but source was rejected by lint:\n${lintWarnings.result.content[0].text}`);
1567
1714
  }
@@ -1595,7 +1742,7 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
1595
1742
  return errorResult(spliced.error ?? `Failed to splice method "${method}" in ${name}.`);
1596
1743
  }
1597
1744
  // Pre-write lint validation on the full spliced source
1598
- const lintWarnings = runPreWriteLint(spliced.newSource, type, name, config);
1745
+ const lintWarnings = runPreWriteLint(spliced.newSource, type, name, config, lintOverride);
1599
1746
  if (lintWarnings.blocked)
1600
1747
  return lintWarnings.result;
1601
1748
  // Write the full source back (existing lock/modify/unlock flow)
@@ -1639,7 +1786,8 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
1639
1786
  try {
1640
1787
  // Use first object's URL for the transport check
1641
1788
  const firstObj = objects[0];
1642
- const firstUrl = objectUrlForType(String(firstObj.type ?? ''), String(firstObj.name ?? ''));
1789
+ const firstType = normalizeObjectType(String(firstObj?.type ?? ''));
1790
+ const firstUrl = objectUrlForType(firstType, String(firstObj?.name ?? ''));
1643
1791
  const transportInfo = await getTransportInfo(client.http, client.safety, firstUrl, pkg, 'I');
1644
1792
  if (transportInfo.lockedTransport) {
1645
1793
  batchTransport = transportInfo.lockedTransport;
@@ -1665,7 +1813,7 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
1665
1813
  }
1666
1814
  const results = [];
1667
1815
  for (const obj of objects) {
1668
- const objType = String(obj.type ?? '');
1816
+ const objType = normalizeObjectType(String(obj.type ?? ''));
1669
1817
  const objName = String(obj.name ?? '');
1670
1818
  const metadataObject = isMetadataWriteType(objType);
1671
1819
  const objSource = obj.source ? String(obj.source) : undefined;
@@ -1685,7 +1833,7 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
1685
1833
  // Pre-validate source with lint BEFORE creating the object to avoid orphaned objects.
1686
1834
  // Metadata objects (DOMA/DTEL) are XML-only and intentionally skip source lint.
1687
1835
  if (!metadataObject && objSource) {
1688
- const lintWarnings = runPreWriteLint(objSource, objType, objName, config);
1836
+ const lintWarnings = runPreWriteLint(objSource, objType, objName, config, lintOverride);
1689
1837
  if (lintWarnings.blocked) {
1690
1838
  results.push({
1691
1839
  type: objType,
@@ -1702,7 +1850,19 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
1702
1850
  const objMetadataProps = getMetadataWriteProperties(obj);
1703
1851
  const body = buildCreateXml(objType, objName, pkg, objDescription, objMetadataProps);
1704
1852
  const contentType = createContentTypeForType(objType);
1705
- await createObject(client.http, client.safety, createUrl, body, contentType, batchTransport);
1853
+ const needsPackageParam = objType === 'BDEF' || objType === 'TABL';
1854
+ try {
1855
+ await createObject(client.http, client.safety, createUrl, body, contentType, batchTransport, needsPackageParam ? pkg : undefined);
1856
+ }
1857
+ catch (createErr) {
1858
+ if (createErr instanceof AdtApiError && (createErr.statusCode === 400 || createErr.statusCode === 409)) {
1859
+ const syntaxDetail = await tryPostSaveSyntaxCheck(client, objType, objName);
1860
+ if (syntaxDetail) {
1861
+ createErr.message += syntaxDetail;
1862
+ }
1863
+ }
1864
+ throw createErr;
1865
+ }
1706
1866
  // Step 1b: DTEL POST ignores labels — follow up with PUT on main session
1707
1867
  if (objType === 'DTEL' && dtelNeedsPostCreateUpdate(objMetadataProps)) {
1708
1868
  await client.http.withStatefulSession(async (session) => {
@@ -1749,7 +1909,7 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
1749
1909
  for (let i = results.length; i < objects.length; i++) {
1750
1910
  const skipped = objects[i];
1751
1911
  results.push({
1752
- type: String(skipped.type ?? ''),
1912
+ type: normalizeObjectType(String(skipped?.type ?? '')),
1753
1913
  name: String(skipped.name ?? ''),
1754
1914
  status: 'failed',
1755
1915
  error: 'skipped — stopped after previous failure',
@@ -1786,8 +1946,20 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
1786
1946
  * If lint itself throws (e.g., abaplint bug on unusual syntax), we don't block
1787
1947
  * the write — we let the SAP server-side syntax check handle it instead.
1788
1948
  */
1789
- function runPreWriteLint(source, type, name, config) {
1790
- if (!config.lintBeforeWrite || !source) {
1949
+ function runPreWriteLint(source, type, name, config, perCallOverride) {
1950
+ // Per-call override takes precedence over server config
1951
+ const enabled = perCallOverride ?? config.lintBeforeWrite;
1952
+ if (!enabled || !source) {
1953
+ return { blocked: false };
1954
+ }
1955
+ // abaplint supports ABAP source (PROG/CLAS/INTF/FUNC/INCL) and CDS views (DDLS) via
1956
+ // its CDS parser. DDLS lint catches syntax errors (cds_parser_error) like missing commas,
1957
+ // wrong keywords, and invalid DDL constructs. BDEF/SRVD/SRVB/DDLX are silently ignored
1958
+ // by abaplint (no parser for those types — garbage passes without errors). TABL (define
1959
+ // table syntax) is not supported by the CDS parser and produces false cds_parser_error.
1960
+ // For unsupported types, SAP server-side compilation handles validation.
1961
+ const LINTABLE_TYPES = new Set(['PROG', 'CLAS', 'INTF', 'FUNC', 'INCL', 'DDLS']);
1962
+ if (!LINTABLE_TYPES.has(type)) {
1791
1963
  return { blocked: false };
1792
1964
  }
1793
1965
  try {
@@ -1894,12 +2066,12 @@ async function handleSAPActivate(client, args) {
1894
2066
  return textResult(`Successfully unpublished service binding ${name}.${srvbInfo ? `\n\n${srvbInfo}` : ''}`);
1895
2067
  }
1896
2068
  // Batch activation: multiple objects at once (for RAP stacks etc.)
1897
- const type = String(args.type ?? '');
2069
+ const type = normalizeObjectType(String(args.type ?? ''));
1898
2070
  const preaudit = args.preaudit !== undefined ? Boolean(args.preaudit) : undefined;
1899
2071
  const activateOpts = preaudit !== undefined ? { preaudit } : undefined;
1900
2072
  if (args.objects && Array.isArray(args.objects)) {
1901
2073
  const objects = args.objects.map((o) => {
1902
- const objType = String(o.type ?? type);
2074
+ const objType = normalizeObjectType(String(o.type ?? type));
1903
2075
  const objName = String(o.name ?? '');
1904
2076
  return { url: objectUrlForType(objType, objName), name: objName };
1905
2077
  });
@@ -1955,7 +2127,7 @@ async function handleSAPNavigate(client, args) {
1955
2127
  const source = String(args.source ?? '');
1956
2128
  // Allow symbolic type+name as alternative to uri for references
1957
2129
  if (!uri && args.type && args.name) {
1958
- const symType = String(args.type);
2130
+ const symType = normalizeObjectType(String(args.type));
1959
2131
  const symName = String(args.name);
1960
2132
  if (symType === 'FUNC') {
1961
2133
  // FUNC needs group to build URL — auto-resolve it
@@ -1986,6 +2158,8 @@ async function handleSAPNavigate(client, args) {
1986
2158
  if (!uri) {
1987
2159
  return errorResult('Provide uri or type+name to find references.');
1988
2160
  }
2161
+ // objectType is passed to SAP's where-used scope API which expects slash format (CLAS/OC, PROG/P).
2162
+ // Do NOT normalize it — the slash suffix is semantically meaningful for the SAP filter.
1989
2163
  const objectType = args.objectType ? String(args.objectType) : undefined;
1990
2164
  let results;
1991
2165
  try {
@@ -2083,7 +2257,7 @@ async function handleSAPNavigate(client, args) {
2083
2257
  async function handleSAPDiagnose(client, args) {
2084
2258
  const action = String(args.action ?? '');
2085
2259
  const name = String(args.name ?? '');
2086
- const type = String(args.type ?? '');
2260
+ const type = normalizeObjectType(String(args.type ?? ''));
2087
2261
  switch (action) {
2088
2262
  case 'syntax': {
2089
2263
  const objectUrl = objectUrlForType(type, name);
@@ -2101,6 +2275,52 @@ async function handleSAPDiagnose(client, args) {
2101
2275
  const result = await runAtcCheck(client.http, client.safety, objectUrl, variant);
2102
2276
  return textResult(JSON.stringify(result, null, 2));
2103
2277
  }
2278
+ case 'quickfix': {
2279
+ const source = args.source;
2280
+ if (!name || !type)
2281
+ return errorResult('"name" and "type" are required for "quickfix" action.');
2282
+ if (!source)
2283
+ return errorResult('"source" is required for "quickfix" action.');
2284
+ if (args.line == null)
2285
+ return errorResult('"line" is required for "quickfix" action.');
2286
+ const line = Number(args.line);
2287
+ const column = Number(args.column ?? 0);
2288
+ if (!Number.isFinite(line))
2289
+ return errorResult('"line" must be a number for "quickfix" action.');
2290
+ if (!Number.isFinite(column))
2291
+ return errorResult('"column" must be a number for "quickfix" action.');
2292
+ const proposals = await getFixProposals(client.http, client.safety, sourceUrlForType(type, name), source, line, column);
2293
+ return textResult(JSON.stringify(proposals, null, 2));
2294
+ }
2295
+ case 'apply_quickfix': {
2296
+ const source = args.source;
2297
+ const proposalUri = args.proposalUri;
2298
+ const proposalUserContent = args.proposalUserContent;
2299
+ if (!name || !type)
2300
+ return errorResult('"name" and "type" are required for "apply_quickfix" action.');
2301
+ if (!source)
2302
+ return errorResult('"source" is required for "apply_quickfix" action.');
2303
+ if (args.line == null)
2304
+ return errorResult('"line" is required for "apply_quickfix" action.');
2305
+ if (!proposalUri)
2306
+ return errorResult('"proposalUri" is required for "apply_quickfix" action.');
2307
+ if (!proposalUserContent)
2308
+ return errorResult('"proposalUserContent" is required for "apply_quickfix" action.');
2309
+ const line = Number(args.line);
2310
+ const column = Number(args.column ?? 0);
2311
+ if (!Number.isFinite(line))
2312
+ return errorResult('"line" must be a number for "apply_quickfix" action.');
2313
+ if (!Number.isFinite(column))
2314
+ return errorResult('"column" must be a number for "apply_quickfix" action.');
2315
+ const deltas = await applyFixProposal(client.http, client.safety, {
2316
+ uri: proposalUri,
2317
+ type: 'quickfix/proposal',
2318
+ name: '',
2319
+ description: '',
2320
+ userContent: proposalUserContent,
2321
+ }, sourceUrlForType(type, name), source, line, column);
2322
+ return textResult(JSON.stringify(deltas, null, 2));
2323
+ }
2104
2324
  case 'dumps': {
2105
2325
  const id = args.id;
2106
2326
  if (id) {
@@ -2141,7 +2361,7 @@ async function handleSAPDiagnose(client, args) {
2141
2361
  return textResult(JSON.stringify(traces, null, 2));
2142
2362
  }
2143
2363
  default:
2144
- return errorResult(`Unknown SAPDiagnose action: ${action}. Supported: syntax, unittest, atc, dumps, traces`);
2364
+ return errorResult(`Unknown SAPDiagnose action: ${action}. Supported: syntax, unittest, atc, quickfix, apply_quickfix, dumps, traces`);
2145
2365
  }
2146
2366
  }
2147
2367
  // ─── SAPTransport Handler ────────────────────────────────────────────
@@ -2240,7 +2460,7 @@ async function handleSAPTransport(client, args) {
2240
2460
  // ─── SAPContext Handler ───────────────────────────────────────────────
2241
2461
  async function handleSAPContext(client, args, cachingLayer) {
2242
2462
  const action = String(args.action ?? '');
2243
- const type = String(args.type ?? '');
2463
+ const type = normalizeObjectType(String(args.type ?? ''));
2244
2464
  const name = String(args.name ?? '');
2245
2465
  const maxDeps = Number(args.maxDeps ?? 20);
2246
2466
  const depth = Math.min(Math.max(Number(args.depth ?? 1), 1), 3);