@abapify/adt-atc 0.4.20 → 0.4.21

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 CHANGED
@@ -82,6 +82,9 @@ const result: AtcResult = {
82
82
  objectType: 'CLAS',
83
83
  objectName: 'ZCL_MY_CLASS',
84
84
  location: '/sap/bc/adt/oo/classes/zcl_my_class/source/main#start=10,0',
85
+ // Optional: resolved by the atc command from the run's check variant
86
+ // form template (check categories are best-effort enrichment)
87
+ checkCategory: 'CI_PERFORMANCE',
85
88
  },
86
89
  ],
87
90
  };
@@ -90,6 +93,40 @@ await outputSarifReport(result, 'atc-results.sarif');
90
93
  await outputGitLabCodeQuality(result, 'gl-code-quality.json');
91
94
  ```
92
95
 
96
+ ## Check Categories
97
+
98
+ Each finding can carry a `checkCategory` (e.g., `CI_PERFORMANCE`,
99
+ `CI_SECURITY`): the name of the ATC check category its check is assigned
100
+ to. The `atc` command resolves it automatically from the run's check
101
+ variant form template (`GET /sap/bc/adt/atc/checkvariants/formtemplate`)
102
+ by mapping the finding's `checkId` (the check's implementing class name)
103
+ to the category that contains it. The enrichment is best-effort: when
104
+ the form template is unavailable, or the check is outside its catalog,
105
+ findings simply carry no category and the run still succeeds.
106
+
107
+ The enrichment helpers are exported for reuse:
108
+
109
+ ```typescript
110
+ import {
111
+ parseCheckCategoryIndex,
112
+ fetchCheckCategoryIndex,
113
+ enrichResultWithCategories,
114
+ } from '@abapify/adt-atc';
115
+
116
+ // Build the checkId -> category name index from a parsed form template
117
+ const index = parseCheckCategoryIndex(formTemplateDocument);
118
+
119
+ // Or fetch it through an ADT client (never throws)
120
+ const fetched = await fetchCheckCategoryIndex(client, 'DEFAULT');
121
+
122
+ // Enrich a result in place
123
+ enrichResultWithCategories(result, index);
124
+ ```
125
+
126
+ The GitLab Code Quality formatter emits the category as `check_category`
127
+ per finding; the SARIF formatter sets it on each rule's
128
+ `properties.category`.
129
+
93
130
  ## Output Formats
94
131
 
95
132
  ### Console (default)
@@ -15,7 +15,8 @@ async function outputSarifReport(result, outputFile, _targetName) {
15
15
  defaultConfiguration: { level: f.priority === 1 ? "error" : f.priority === 2 ? "warning" : "note" },
16
16
  properties: {
17
17
  tags: ["abap", "code-quality"],
18
- precision: "high"
18
+ precision: "high",
19
+ ...f.checkCategory && { category: f.checkCategory }
19
20
  }
20
21
  }])).values());
21
22
  const results = result.findings.map((finding) => {
@@ -125,6 +126,7 @@ async function outputGitLabCodeQuality(result, outputFile, options) {
125
126
  return {
126
127
  description: finding.messageText,
127
128
  check_name: finding.checkTitle || finding.checkId,
129
+ check_id: finding.checkId || void 0,
128
130
  fingerprint,
129
131
  severity,
130
132
  location: {
@@ -135,13 +137,167 @@ async function outputGitLabCodeQuality(result, outputFile, options) {
135
137
  }
136
138
  },
137
139
  ...methodName && { method: methodName },
138
- ...finding.location && { atc_location: finding.location }
140
+ ...finding.location && { atc_location: finding.location },
141
+ ...finding.checkCategory && { check_category: finding.checkCategory }
139
142
  };
140
143
  }));
141
144
  await writeFile(outputFile, JSON.stringify(gitlabReport, null, 2));
142
145
  console.log(`\n📄 GitLab Code Quality report written to: ${outputFile}`);
143
146
  console.log(`📊 ${result.totalFindings} issues exported`);
144
147
  }
148
+ // src/check-categories.ts
149
+ /**
150
+ * Build a checkId -> check category name index from a parsed check variant
151
+ * form template document.
152
+ *
153
+ * The checkId key is the implementing class name; the value is the category
154
+ * object name (a stable key like 'CI_PERFORMANCE', not the display title).
155
+ *
156
+ * @param doc - Parsed form template document (nullable for convenience)
157
+ * @returns Map of checkId -> category name; empty when the document is
158
+ * missing or malformed
159
+ */
160
+ function parseCheckCategoryIndex(doc) {
161
+ const index = /* @__PURE__ */ new Map();
162
+ const categories = doc?.formTemplate?.availableCheckCategories?.availableCheckCategory ?? [];
163
+ for (const category of categories) collectCategoryChecks(category, index);
164
+ return index;
165
+ }
166
+ /** Index every check of one category under the category's name. */
167
+ function collectCategoryChecks(category, index) {
168
+ const categoryName = category.checkCategoryObject?.name;
169
+ if (!categoryName) return;
170
+ const checkIds = (category.check ?? []).map((check) => check.checkObject?.content?.implementingClass?.name).filter((checkId) => !!checkId);
171
+ for (const checkId of checkIds) index.set(checkId, categoryName);
172
+ }
173
+ /**
174
+ * Fetch the check variant form template and build the checkId -> category
175
+ * name index for a run.
176
+ *
177
+ * Best-effort: any error (network, 404, malformed document) is swallowed
178
+ * and reported through the optional logger so a failed enrichment never
179
+ * fails the ATC run itself.
180
+ *
181
+ * @param client - ADT client with the checkvariants.formtemplate surface
182
+ * @param checkVariant - The check variant name of the run (e.g., 'DEFAULT')
183
+ * @param logger - Optional CLI logger for diagnostics
184
+ * @returns checkId -> category name index; empty when unavailable
185
+ */
186
+ async function fetchCheckCategoryIndex(client, checkVariant, logger) {
187
+ try {
188
+ return parseCheckCategoryIndex(await client.adt.atc.checkvariants.formtemplate({ chkvName: checkVariant }));
189
+ } catch (error) {
190
+ logger?.warn?.(`⚠️ Check category enrichment unavailable: ${error instanceof Error ? error.message : String(error)}`);
191
+ return /* @__PURE__ */ new Map();
192
+ }
193
+ }
194
+ /**
195
+ * Enrich findings with their check category in place.
196
+ *
197
+ * Generic over any finding shape carrying a checkId (and an optional
198
+ * checkCategory field), so both the CLI plugin's AtcFinding and other
199
+ * canonical finding types can be enriched with the same index.
200
+ *
201
+ * @param findings - Findings to enrich (mutated in place)
202
+ * @param index - checkId -> category name index
203
+ * @returns Number of findings that received a category
204
+ */
205
+ function enrichFindingsWithCategories(findings, index) {
206
+ let enriched = 0;
207
+ for (const finding of findings) {
208
+ const checkCategory = index.get(finding.checkId);
209
+ if (checkCategory) {
210
+ finding.checkCategory = checkCategory;
211
+ enriched++;
212
+ }
213
+ }
214
+ return enriched;
215
+ }
216
+ /**
217
+ * Enrich an ATC result with check categories in place.
218
+ *
219
+ * @param result - The run result (findings mutated in place)
220
+ * @param index - checkId -> category name index
221
+ * @returns Number of findings that received a category
222
+ */
223
+ function enrichResultWithCategories(result, index) {
224
+ return enrichFindingsWithCategories(result.findings ?? [], index);
225
+ }
226
+ // src/documentation-bridge.ts
227
+ /**
228
+ * Parse the implementing check class from an ATC finding documentation page.
229
+ *
230
+ * Live shape (2026-10-22): the page opens with
231
+ * "Appl. Comp. Check / Check Class / Message Code" followed by
232
+ * `<p>BC-ABA-LA / CL_CI_TEST_SELECT_TAW_A / 0501</p>`. The check class in
233
+ * that line is the implementing class the check variant form template lists
234
+ * — the bridge from the finding's GUID checkId to the check catalog.
235
+ *
236
+ * The match is bounded (no unbounded quantifiers on page input) and accepts
237
+ * an optional ABAP namespace prefix (e.g. `/NSP/CL_EXAMPLE`).
238
+ *
239
+ * @param html - The documentation page HTML
240
+ * @returns The check class name, or undefined when the page does not carry it
241
+ */
242
+ function parseCheckClassFromDocumentation(html) {
243
+ return html.match(/Check\s*Class[^<]{0,80}<\/h3>\s*<p>\s*[^/<]{0,120}\/\s*((?:\/[A-Za-z0-9_]{1,8}\/)?[A-Za-z][A-Za-z0-9_]*)\s*\//u)?.[1];
244
+ }
245
+ /**
246
+ * Read one documentation page and resolve its check class, with a shared
247
+ * per-URI cache so repeated findings never re-fetch the same page.
248
+ */
249
+ async function resolveCheckClassForUri(documentationUri, context) {
250
+ if (context.classByDocumentationUri.has(documentationUri)) return;
251
+ try {
252
+ const html = await context.read(documentationUri, context.maxBytes, { headers: { Accept: "application/vnd.sap.adt.docu.v1+html" } });
253
+ context.classByDocumentationUri.set(documentationUri, parseCheckClassFromDocumentation(html));
254
+ } catch (error) {
255
+ context.logger?.warn?.(`⚠️ ATC documentation read failed for ${documentationUri}: ${error instanceof Error ? error.message : String(error)}`);
256
+ context.classByDocumentationUri.set(documentationUri, void 0);
257
+ }
258
+ }
259
+ /**
260
+ * Bridge finding categories through the ATC documentation page.
261
+ *
262
+ * After the direct form-template index pass (which resolves findings whose
263
+ * checkId already matches a catalog implementing class), this resolves the
264
+ * GUID-checkId findings: each finding's documentation page names the check
265
+ * class, which resolves through the same index.
266
+ *
267
+ * Best-effort: per-documentation-URI fetch errors are reported through the
268
+ * optional logger and skipped; a finding keeps no category when its page
269
+ * cannot be fetched or parsed. Reads are deduplicated per URI.
270
+ *
271
+ * @param client - ADT client with an optional bounded text read
272
+ * @param findings - Findings to enrich in place
273
+ * @param classToCategory - checkId/class -> category name index (the
274
+ * form-template catalog: parseCheckCategoryIndex output)
275
+ * @param options - maxBytes (default 64 KiB) and an optional logger
276
+ * @returns Number of findings that received a category
277
+ */
278
+ async function resolveCategoriesViaDocumentation(client, findings, classToCategory, options = {}) {
279
+ const read = client.readTextBounded;
280
+ if (!read || classToCategory.size === 0) return 0;
281
+ const pending = findings.filter((finding) => !finding.checkCategory && finding.documentationUri);
282
+ if (pending.length === 0) return 0;
283
+ const context = {
284
+ read,
285
+ maxBytes: options.maxBytes ?? 65536,
286
+ classByDocumentationUri: /* @__PURE__ */ new Map(),
287
+ logger: options.logger
288
+ };
289
+ let enriched = 0;
290
+ for (const finding of pending) {
291
+ await resolveCheckClassForUri(finding.documentationUri, context);
292
+ const checkClass = context.classByDocumentationUri.get(finding.documentationUri);
293
+ const checkCategory = checkClass ? classToCategory.get(checkClass) : void 0;
294
+ if (checkCategory) {
295
+ finding.checkCategory = checkCategory;
296
+ enriched++;
297
+ }
298
+ }
299
+ return enriched;
300
+ }
145
301
  // src/commands/atc.ts
146
302
  /**
147
303
  * Create OSC 8 hyperlink for terminal
@@ -161,6 +317,16 @@ function adtLink(name, uri, systemName) {
161
317
  return hyperlink(chalk.cyan(name), url);
162
318
  }
163
319
  /**
320
+ * The trusted documentation link of one worklist finding.
321
+ *
322
+ * Live shape (2026-10-22): <atom:link rel="http://www.sap.com/adt/
323
+ * relations/documentation" href="/sap/bc/adt/documentation/atc/documents/
324
+ * itemid/{guid}/index/{n}"/> inside the atcfinding:finding element.
325
+ */
326
+ function documentationUriFromFinding(finding) {
327
+ return finding.link?.find((link) => link.rel === "http://www.sap.com/adt/relations/documentation")?.href;
328
+ }
329
+ /**
164
330
  * Convert worklist response to AtcResult
165
331
  */
166
332
  function convertWorklistToResult(worklistResponse, checkVariant) {
@@ -179,7 +345,8 @@ function convertWorklistToResult(worklistResponse, checkVariant) {
179
345
  objectType: obj.type || "",
180
346
  objectName: obj.name || "",
181
347
  location: finding.location,
182
- findingUri: finding.uri
348
+ findingUri: finding.uri,
349
+ documentationUri: documentationUriFromFinding(finding)
183
350
  });
184
351
  }
185
352
  }
@@ -353,6 +520,11 @@ const atcCommand = {
353
520
  await client.adt.atc.runs.post({ worklistId }, runData);
354
521
  ctx.logger.info("📊 Fetching results...");
355
522
  const result = convertWorklistToResult(await client.adt.atc.worklists.get(worklistId, { includeExemptedFindings: "false" }), checkVariant);
523
+ const categoryIndex = await fetchCheckCategoryIndex(client, checkVariant, ctx.logger);
524
+ const enriched = enrichResultWithCategories(result, categoryIndex);
525
+ if (enriched > 0) ctx.logger.info(`🏷️ Enriched ${enriched}/${result.totalFindings} finding(s) with check categories`);
526
+ const bridged = await resolveCategoriesViaDocumentation(client, result.findings, categoryIndex, { logger: ctx.logger });
527
+ if (bridged > 0) ctx.logger.info(`🏷 Bridged ${bridged} finding(s) to check categories via ATC documentation`);
356
528
  let resolver;
357
529
  if (options.resolver) {
358
530
  const name = options.resolver;
@@ -375,6 +547,6 @@ const atcCommand = {
375
547
  if (result.errorCount > 0) process.exit(1);
376
548
  }
377
549
  };
378
- export { outputGitLabCodeQuality as n, outputSarifReport as r, atcCommand as t };
550
+ export { enrichResultWithCategories as a, outputGitLabCodeQuality as c, enrichFindingsWithCategories as i, outputSarifReport as l, parseCheckClassFromDocumentation as n, fetchCheckCategoryIndex as o, resolveCategoriesViaDocumentation as r, parseCheckCategoryIndex as s, atcCommand as t };
379
551
 
380
- //# sourceMappingURL=atc-BEoE4xIY.mjs.map
552
+ //# sourceMappingURL=atc-6cFtFkir.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atc-6cFtFkir.mjs","names":[],"sources":["../src/formatters/sarif.ts","../src/formatters/gitlab.ts","../src/check-categories.ts","../src/documentation-bridge.ts","../src/commands/atc.ts"],"sourcesContent":["/**\n * SARIF Formatter\n *\n * Outputs ATC findings in SARIF format for GitHub Code Scanning.\n */\n\nimport { writeFile } from 'node:fs/promises';\nimport type { AtcResult, AtcFinding } from '../types';\n\nexport async function outputSarifReport(\n result: AtcResult,\n outputFile: string,\n _targetName?: string,\n): Promise<void> {\n // Transform ATC findings to SARIF format\n const rules = Array.from(\n new Map(\n result.findings.map((f) => [\n f.checkId,\n {\n id: f.checkId,\n name: f.checkTitle || f.checkId,\n shortDescription: { text: f.checkTitle || f.checkId },\n fullDescription: { text: f.checkTitle || f.checkId },\n defaultConfiguration: {\n level:\n f.priority === 1\n ? 'error'\n : f.priority === 2\n ? 'warning'\n : 'note',\n },\n properties: {\n tags: ['abap', 'code-quality'],\n precision: 'high',\n ...(f.checkCategory && { category: f.checkCategory }),\n },\n },\n ]),\n ).values(),\n );\n\n const results = result.findings.map((finding: AtcFinding) => {\n const filePath = `src/${finding.objectType.toLowerCase()}/${finding.objectName.toLowerCase()}.${finding.objectType.toLowerCase()}`;\n\n // Parse line number from location if available\n const lineMatch = finding.location?.match(/start=(\\d+)/);\n const line = lineMatch ? Number.parseInt(lineMatch[1], 10) : 1;\n\n return {\n ruleId: finding.checkId,\n level:\n finding.priority === 1\n ? 'error'\n : finding.priority === 2\n ? 'warning'\n : 'note',\n message: { text: finding.messageText },\n locations: [\n {\n physicalLocation: {\n artifactLocation: { uri: filePath },\n region: {\n startLine: line,\n startColumn: 1,\n },\n },\n },\n ],\n partialFingerprints: {\n primaryLocationLineHash: `${finding.checkId}-${finding.objectName}-${line}`,\n },\n };\n });\n\n const sarifReport = {\n $schema: 'https://json.schemastore.org/sarif-2.1.0.json',\n version: '2.1.0',\n runs: [\n {\n tool: {\n driver: {\n name: 'ABAP Test Cockpit',\n version: '1.0.0',\n informationUri:\n 'https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/4ec5711c6e391014adc9fffe4e204223.html',\n rules: Array.from(rules),\n },\n },\n results,\n },\n ],\n };\n\n await writeFile(outputFile, JSON.stringify(sarifReport, null, 2));\n console.log(`\\n📄 SARIF report written to: ${outputFile}`);\n console.log(\n `📊 ${result.totalFindings} issues exported for GitHub Code Scanning`,\n );\n}\n","/**\n * GitLab Code Quality Formatter\n *\n * Outputs ATC findings in GitLab Code Quality format.\n *\n * This formatter is a pure output formatter — it maps ATC findings to the\n * GitLab Code Quality JSON structure. Path resolution and line number\n * conversion are delegated to an optional FindingResolver.\n *\n * Without a resolver, the formatter produces PREFIX-style paths and raw\n * ATC (method-relative) line numbers. With a resolver (e.g., from\n * @abapify/adt-plugin-abapgit), it produces correct git paths and\n * file-relative line numbers.\n *\n * @example\n * ```typescript\n * // Without resolver (standalone CLI usage)\n * await outputGitLabCodeQuality(result, 'report.json');\n *\n * // With resolver (inside an abapgit repo)\n * import { createFindingResolver } from '@abapify/adt-plugin-abapgit';\n * const resolver = await createFindingResolver();\n * await outputGitLabCodeQuality(result, 'report.json', { resolver });\n * ```\n */\n\nimport { writeFile } from 'node:fs/promises';\nimport type { AtcResult, AtcFinding, FindingResolver } from '../types';\n\n/**\n * Options for the GitLab Code Quality formatter\n */\nexport interface GitLabFormatterOptions {\n /** Optional resolver for path/line resolution (e.g., from abapgit plugin) */\n resolver?: FindingResolver;\n}\n\n// ── Extract method name from ATC location URI ───────────────────────────\n// Format 1: /sap/bc/adt/oo/classes/zcl_foo/methods/my_method#start=21,0\n// Format 2: ...#type=CLAS%2FOM;name=MY_METHOD;start=21\n\nfunction extractMethodName(location?: string): string | undefined {\n if (!location) return undefined;\n const match =\n location.match(/\\/methods\\/(\\w+)/i) || location.match(/[;?&]name=(\\w+)/i);\n return match ? match[1].toLowerCase() : undefined;\n}\n\n// ── Construct PREFIX-style fallback path ─────────────────────────────────\n\nfunction prefixPath(finding: AtcFinding): string {\n return `src/${finding.objectType.toLowerCase()}/${finding.objectName.toLowerCase()}.${finding.objectType.toLowerCase()}.abap`;\n}\n\n// ── Main formatter ──────────────────────────────────────────────────────\n\nexport async function outputGitLabCodeQuality(\n result: AtcResult,\n outputFile: string,\n options?: GitLabFormatterOptions,\n): Promise<void> {\n const resolver = options?.resolver;\n\n // Transform ATC findings to GitLab Code Quality format\n const gitlabReport = await Promise.all(\n result.findings.map(async (finding: AtcFinding) => {\n // Map ATC priority to GitLab severity\n let severity: string;\n switch (finding.priority) {\n case 1:\n severity = 'blocker';\n break;\n case 2:\n severity = 'major';\n break;\n case 3:\n severity = 'minor';\n break;\n default:\n severity = 'info';\n break;\n }\n\n // Parse ATC line number from location\n const lineMatch = finding.location?.match(/start=(\\d+)/);\n const atcLine = lineMatch ? Number.parseInt(lineMatch[1], 10) : 1;\n\n // Extract method name from ATC location URI\n const methodName = extractMethodName(finding.location);\n\n // Resolve path and line via plugin, or fall back to PREFIX-style\n let filePath: string;\n let fileLine: number;\n\n if (resolver) {\n const resolved = await resolver.resolve(\n finding.objectType,\n finding.objectName,\n atcLine,\n methodName,\n finding.location,\n );\n if (resolved) {\n filePath = resolved.path;\n fileLine = resolved.line;\n } else {\n filePath = prefixPath(finding);\n fileLine = atcLine;\n }\n } else {\n filePath = prefixPath(finding);\n fileLine = atcLine;\n }\n\n // Create unique fingerprint\n const fingerprint = `${finding.checkId}-${finding.objectName}-${fileLine}`;\n\n return {\n description: finding.messageText,\n check_name: finding.checkTitle || finding.checkId,\n // The worklist checkId as SAP reports it. On live systems this is a\n // per-instance GUID (stable for the finding, but not a catalog\n // identity); checkTitle is a display name that SAP can rename, so\n // downstream consumers should key on this field rather than the\n // title.\n check_id: finding.checkId || undefined,\n fingerprint,\n severity,\n location: {\n path: filePath,\n lines: {\n begin: fileLine,\n end: fileLine,\n },\n },\n // Extra fields for downstream processing\n ...(methodName && { method: methodName }),\n ...(finding.location && { atc_location: finding.location }),\n ...(finding.checkCategory && {\n check_category: finding.checkCategory,\n }),\n };\n }),\n );\n\n await writeFile(outputFile, JSON.stringify(gitlabReport, null, 2));\n console.log(`\\n📄 GitLab Code Quality report written to: ${outputFile}`);\n console.log(`📊 ${result.totalFindings} issues exported`);\n}\n","/**\n * Check Category Enrichment\n *\n * Resolves ATC findings to their check category (e.g., 'CI_PERFORMANCE',\n * 'CI_SECURITY') through the check variant form template\n * (GET /sap/bc/adt/atc/checkvariants/formtemplate?chkvName={name}).\n *\n * The form template is the check catalog of the run's check variant: every\n * available check category lists its checks, and each check carries its\n * implementing class. When a finding's checkId matches a catalog\n * implementing class, the index resolves it directly. Live worklist\n * checkIds are check-instance GUIDs (observed on a live S/4 system,\n * 2026-10-22); those resolve through the documentation bridge in\n * ./documentation-bridge.ts.\n *\n * Enrichment is best-effort by design: without the form template, or for a\n * checkId outside the catalog, findings keep no category and the run still\n * succeeds.\n */\n\n/**\n * Structural shape of the parsed check variant form template document\n * (GET /sap/bc/adt/atc/checkvariants/formtemplate).\n *\n * Only the fields needed for the checkId -> category index are declared.\n */\nexport interface FormTemplateDocument {\n formTemplate?: {\n availableCheckCategories?: {\n availableCheckCategory?: FormTemplateCategoryEntry[];\n };\n };\n}\n\n/** One available check category with the checks it contains. */\nexport interface FormTemplateCategoryEntry {\n checkCategoryObject?: {\n /** Category name (the enrichment key, e.g., 'CI_PERFORMANCE') */\n name?: string;\n /** Category display title (e.g., 'Performance') */\n description?: string;\n };\n check?: FormTemplateCheckEntry[];\n}\n\n/** One check entry of a category. */\nexport interface FormTemplateCheckEntry {\n checkObject?: {\n /** Check name (e.g., 'CI_ANALYZE_WHERE') */\n name?: string;\n content?: {\n /** The check's category assignment (the category name) */\n category?: { name?: string };\n /**\n * Implementing class of the check (e.g., 'CL_CI_TEST_SELECT_TAW_A').\n * Worklist findings carry a check-instance GUID instead; the\n * documentation bridge resolves that GUID to this class name.\n */\n implementingClass?: { name?: string };\n };\n };\n}\n\n/**\n * Build a checkId -> check category name index from a parsed check variant\n * form template document.\n *\n * The checkId key is the implementing class name; the value is the category\n * object name (a stable key like 'CI_PERFORMANCE', not the display title).\n *\n * @param doc - Parsed form template document (nullable for convenience)\n * @returns Map of checkId -> category name; empty when the document is\n * missing or malformed\n */\nexport function parseCheckCategoryIndex(\n doc: FormTemplateDocument | null | undefined,\n): Map<string, string> {\n const index = new Map<string, string>();\n const categories =\n doc?.formTemplate?.availableCheckCategories?.availableCheckCategory ?? [];\n for (const category of categories) {\n collectCategoryChecks(category, index);\n }\n return index;\n}\n\n/** Index every check of one category under the category's name. */\nfunction collectCategoryChecks(\n category: FormTemplateCategoryEntry,\n index: Map<string, string>,\n): void {\n const categoryName = category.checkCategoryObject?.name;\n if (!categoryName) return;\n const checkIds = (category.check ?? [])\n .map((check) => check.checkObject?.content?.implementingClass?.name)\n .filter((checkId): checkId is string => !!checkId);\n for (const checkId of checkIds) {\n index.set(checkId, categoryName);\n }\n}\n\n/** Structural client surface needed to fetch a form template. */\nexport interface FormTemplateClient {\n adt: {\n atc: {\n checkvariants: {\n /** GET /sap/bc/adt/atc/checkvariants/formtemplate{?chkvName} */\n formtemplate: (params?: {\n chkvName?: string;\n }) => Promise<FormTemplateDocument>;\n };\n };\n };\n}\n\n/**\n * Fetch the check variant form template and build the checkId -> category\n * name index for a run.\n *\n * Best-effort: any error (network, 404, malformed document) is swallowed\n * and reported through the optional logger so a failed enrichment never\n * fails the ATC run itself.\n *\n * @param client - ADT client with the checkvariants.formtemplate surface\n * @param checkVariant - The check variant name of the run (e.g., 'DEFAULT')\n * @param logger - Optional CLI logger for diagnostics\n * @returns checkId -> category name index; empty when unavailable\n */\nexport async function fetchCheckCategoryIndex(\n client: FormTemplateClient,\n checkVariant: string,\n logger?: {\n warn: (message: string) => void;\n info?: (message: string) => void;\n },\n): Promise<Map<string, string>> {\n try {\n const doc = await client.adt.atc.checkvariants.formtemplate({\n chkvName: checkVariant,\n });\n return parseCheckCategoryIndex(doc);\n } catch (error) {\n logger?.warn?.(\n `⚠️ Check category enrichment unavailable: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n return new Map();\n }\n}\n\n/**\n * Enrich findings with their check category in place.\n *\n * Generic over any finding shape carrying a checkId (and an optional\n * checkCategory field), so both the CLI plugin's AtcFinding and other\n * canonical finding types can be enriched with the same index.\n *\n * @param findings - Findings to enrich (mutated in place)\n * @param index - checkId -> category name index\n * @returns Number of findings that received a category\n */\nexport function enrichFindingsWithCategories<\n T extends { checkId: string; checkCategory?: string },\n>(findings: T[], index: Map<string, string>): number {\n let enriched = 0;\n for (const finding of findings) {\n const checkCategory = index.get(finding.checkId);\n if (checkCategory) {\n finding.checkCategory = checkCategory;\n enriched++;\n }\n }\n return enriched;\n}\n\n/**\n * Enrich an ATC result with check categories in place.\n *\n * @param result - The run result (findings mutated in place)\n * @param index - checkId -> category name index\n * @returns Number of findings that received a category\n */\nexport function enrichResultWithCategories<\n T extends { checkId: string; checkCategory?: string },\n>(result: { findings?: T[] }, index: Map<string, string>): number {\n return enrichFindingsWithCategories(result.findings ?? [], index);\n}\n","/**\n * Documentation Bridge for Check Category Enrichment\n *\n * ATC worklist findings carry a check-instance GUID as their checkId — not\n * the implementing class name the check variant form template catalog lists\n * (observed on a live S/4 system, 2026-10-22). The finding's documentation\n * page (the worklist atom:link) names the check class in its header line,\n * which resolves through the form-template category index.\n *\n * Everything here is best-effort: fetch errors are reported through the\n * optional logger and skipped; findings keep no category when their page\n * cannot be fetched or parsed.\n */\n\n/** Structural surface for a bounded documentation read. The real ADT client\n * provides readTextBounded; mocks without it simply skip the bridge. */\nexport interface DocumentationReadingClient {\n readTextBounded?: (\n uri: string,\n maxBytes: number,\n options?: { headers?: Record<string, string> },\n ) => Promise<string>;\n}\n\n/**\n * Parse the implementing check class from an ATC finding documentation page.\n *\n * Live shape (2026-10-22): the page opens with\n * \"Appl. Comp. Check / Check Class / Message Code\" followed by\n * `<p>BC-ABA-LA / CL_CI_TEST_SELECT_TAW_A / 0501</p>`. The check class in\n * that line is the implementing class the check variant form template lists\n * — the bridge from the finding's GUID checkId to the check catalog.\n *\n * The match is bounded (no unbounded quantifiers on page input) and accepts\n * an optional ABAP namespace prefix (e.g. `/NSP/CL_EXAMPLE`).\n *\n * @param html - The documentation page HTML\n * @returns The check class name, or undefined when the page does not carry it\n */\nexport function parseCheckClassFromDocumentation(\n html: string,\n): string | undefined {\n const match = html.match(\n /Check\\s*Class[^<]{0,80}<\\/h3>\\s*<p>\\s*[^/<]{0,120}\\/\\s*((?:\\/[A-Za-z0-9_]{1,8}\\/)?[A-Za-z][A-Za-z0-9_]*)\\s*\\//u,\n );\n return match?.[1];\n}\n\n/** Finding shape the bridge enriches in place. */\nexport interface DocumentationBridgeFinding {\n checkId: string;\n checkCategory?: string;\n documentationUri?: string;\n}\n\n/** Shared state for the per-URI documentation fetch. */\ninterface DocumentationFetchContext {\n read: NonNullable<DocumentationReadingClient['readTextBounded']>;\n maxBytes: number;\n classByDocumentationUri: Map<string, string | undefined>;\n logger?: { warn: (message: string) => void };\n}\n\n/**\n * Read one documentation page and resolve its check class, with a shared\n * per-URI cache so repeated findings never re-fetch the same page.\n */\nasync function resolveCheckClassForUri(\n documentationUri: string,\n context: DocumentationFetchContext,\n): Promise<void> {\n if (context.classByDocumentationUri.has(documentationUri)) return;\n try {\n const html = await context.read(documentationUri, context.maxBytes, {\n headers: { Accept: 'application/vnd.sap.adt.docu.v1+html' },\n });\n context.classByDocumentationUri.set(\n documentationUri,\n parseCheckClassFromDocumentation(html),\n );\n } catch (error) {\n context.logger?.warn?.(\n `⚠️ ATC documentation read failed for ${documentationUri}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n context.classByDocumentationUri.set(documentationUri, undefined);\n }\n}\n\n/**\n * Bridge finding categories through the ATC documentation page.\n *\n * After the direct form-template index pass (which resolves findings whose\n * checkId already matches a catalog implementing class), this resolves the\n * GUID-checkId findings: each finding's documentation page names the check\n * class, which resolves through the same index.\n *\n * Best-effort: per-documentation-URI fetch errors are reported through the\n * optional logger and skipped; a finding keeps no category when its page\n * cannot be fetched or parsed. Reads are deduplicated per URI.\n *\n * @param client - ADT client with an optional bounded text read\n * @param findings - Findings to enrich in place\n * @param classToCategory - checkId/class -> category name index (the\n * form-template catalog: parseCheckCategoryIndex output)\n * @param options - maxBytes (default 64 KiB) and an optional logger\n * @returns Number of findings that received a category\n */\nexport async function resolveCategoriesViaDocumentation(\n client: DocumentationReadingClient,\n findings: DocumentationBridgeFinding[],\n classToCategory: ReadonlyMap<string, string>,\n options: {\n maxBytes?: number;\n logger?: { warn: (message: string) => void };\n } = {},\n): Promise<number> {\n const read = client.readTextBounded;\n if (!read || classToCategory.size === 0) return 0;\n\n const pending = findings.filter(\n (finding) => !finding.checkCategory && finding.documentationUri,\n );\n if (pending.length === 0) return 0;\n\n const context: DocumentationFetchContext = {\n read,\n maxBytes: options.maxBytes ?? 64 * 1024,\n classByDocumentationUri: new Map<string, string | undefined>(),\n logger: options.logger,\n };\n let enriched = 0;\n for (const finding of pending) {\n await resolveCheckClassForUri(finding.documentationUri!, context);\n const checkClass = context.classByDocumentationUri.get(\n finding.documentationUri!,\n );\n const checkCategory = checkClass\n ? classToCategory.get(checkClass)\n : undefined;\n if (checkCategory) {\n finding.checkCategory = checkCategory;\n enriched++;\n }\n }\n return enriched;\n}\n","/**\n * ATC Command Plugin\n *\n * CLI-agnostic command for running ABAP Test Cockpit checks.\n * Uses the CliContext.getAdtClient() factory for API access.\n */\n\nimport type { CliCommandPlugin, CliContext } from '@abapify/adt-plugin';\nimport chalk from 'chalk';\nimport { outputSarifReport, outputGitLabCodeQuality } from '../formatters';\nimport {\n fetchCheckCategoryIndex,\n enrichResultWithCategories,\n} from '../check-categories';\nimport { resolveCategoriesViaDocumentation } from '../documentation-bridge';\nimport type { AtcResult, AtcFinding, OutputFormat } from '../types';\n\n// Client type - the plugin receives a typed client from context\n// The client.adt.atc namespace provides all ATC operations\ninterface AdtClient {\n adt: {\n atc: {\n customizing: {\n get: () => Promise<{\n customizing: {\n properties: { property?: Array<{ name: string; value?: string }> };\n };\n }>;\n };\n worklists: {\n create: (params: {\n checkVariant: string;\n }) => Promise<\n | string\n | { worklistRun?: { worklistId: string }; worklist?: { id: string } }\n >;\n get: (\n id: string,\n params: { includeExemptedFindings: string },\n ) => Promise<WorklistResponse>;\n };\n checkvariants: {\n formtemplate: (params?: {\n chkvName?: string;\n }) => Promise<import('../check-categories').FormTemplateDocument>;\n };\n runs: {\n post: (\n params: { worklistId: string },\n body: {\n run: {\n maximumVerdicts: number;\n objectSets: {\n objectSet: Array<{\n kind: string;\n objectReferences: {\n objectReference: Array<{ uri: string }>;\n };\n }>;\n };\n };\n },\n ) => Promise<void>;\n };\n };\n };\n /**\n * Bounded documentation text read (the real ADT client provides it).\n * Optional: without it the documentation category bridge is skipped.\n */\n readTextBounded?: (\n uri: string,\n maxBytes: number,\n options?: { headers?: Record<string, string> },\n ) => Promise<string>;\n}\n\n// Worklist response type\ninterface WorklistResponse {\n worklist?: {\n id: string;\n timestamp: string;\n objects: {\n object?: Array<{\n uri?: string;\n type?: string;\n name?: string;\n findings: {\n finding?: Array<{\n uri?: string;\n location?: string;\n priority?: string;\n checkId?: string;\n checkTitle?: string;\n messageId?: string;\n messageTitle?: string;\n /** Documentation link: the page that names the check class. */\n link?: Array<{ href?: string; rel?: string }>;\n }>;\n };\n }>;\n };\n };\n worklistRun?: { worklistId: string };\n}\n\n/**\n * Create OSC 8 hyperlink for terminal\n */\nfunction hyperlink(text: string, url: string): string {\n const OSC = '\\x1b]';\n const BEL = '\\x07';\n const SEP = ';';\n return `${OSC}8${SEP}${SEP}${url}${BEL}${text}${OSC}8${SEP}${SEP}${BEL}`;\n}\n\n/**\n * Create ADT Eclipse link\n */\nfunction adtLink(name: string, uri: string, systemName?: string): string {\n if (!systemName || !name || !uri) {\n return name ? chalk.cyan(name) : '';\n }\n const path = uri.startsWith('/sap/bc/adt') ? uri : `/sap/bc/adt${uri}`;\n const url = `adt://${systemName}${path}`;\n return hyperlink(chalk.cyan(name), url);\n}\n\n/**\n * The trusted documentation link of one worklist finding.\n *\n * Live shape (2026-10-22): <atom:link rel=\"http://www.sap.com/adt/\n * relations/documentation\" href=\"/sap/bc/adt/documentation/atc/documents/\n * itemid/{guid}/index/{n}\"/> inside the atcfinding:finding element.\n */\nfunction documentationUriFromFinding(\n finding: NonNullable<\n NonNullable<\n NonNullable<WorklistResponse['worklist']>['objects']['object']\n >[number]['findings']['finding']\n >[number],\n): string | undefined {\n return finding.link?.find(\n (link) => link.rel === 'http://www.sap.com/adt/relations/documentation',\n )?.href;\n}\n\n/**\n * Convert worklist response to AtcResult\n */\nfunction convertWorklistToResult(\n worklistResponse: WorklistResponse,\n checkVariant: string,\n): AtcResult {\n const findings: AtcFinding[] = [];\n\n if (worklistResponse.worklist) {\n const objects = worklistResponse.worklist.objects.object || [];\n\n for (const obj of objects) {\n const objFindings = obj.findings.finding || [];\n for (const finding of objFindings) {\n findings.push({\n checkId: finding.checkId || '',\n checkTitle: finding.checkTitle || '',\n messageId: finding.messageId || '',\n priority: Number.parseInt(finding.priority || '3', 10),\n messageText: finding.messageTitle || '',\n objectUri: obj.uri || '',\n objectType: obj.type || '',\n objectName: obj.name || '',\n location: finding.location,\n findingUri: finding.uri,\n documentationUri: documentationUriFromFinding(finding),\n });\n }\n }\n }\n\n const errorCount = findings.filter((f) => f.priority === 1).length;\n const warningCount = findings.filter((f) => f.priority === 2).length;\n const infoCount = findings.filter((f) => f.priority >= 3).length;\n\n return {\n checkVariant,\n totalFindings: findings.length,\n errorCount,\n warningCount,\n infoCount,\n findings,\n };\n}\n\n/**\n * Display ATC results in console\n */\nfunction displayAtcResults(result: AtcResult, systemName?: string): void {\n if (result.totalFindings === 0) {\n console.log(`\\n✅ ATC check passed - No issues found!`);\n console.log(` 🎯 Variant: ${result.checkVariant}`);\n return;\n }\n\n console.log(`\\n📊 ATC Results Summary:`);\n console.log(` 🎯 Variant: ${result.checkVariant}`);\n if (result.errorCount > 0) console.log(` ❌ Errors: ${result.errorCount}`);\n if (result.warningCount > 0)\n console.log(` ⚠️ Warnings: ${result.warningCount}`);\n if (result.infoCount > 0) console.log(` ℹ️ Info: ${result.infoCount}`);\n console.log(` 📋 Total Issues: ${result.totalFindings}`);\n\n if (result.findings && result.findings.length > 0) {\n console.log(`\\n📄 Findings:`);\n result.findings.slice(0, 5).forEach((finding) => {\n const priorityIcon =\n finding.priority === 1 ? '❌' : finding.priority === 2 ? '⚠️' : 'ℹ️';\n const objectLink = finding.location\n ? adtLink(finding.objectName, finding.location, systemName)\n : finding.objectName;\n console.log(\n ` ${priorityIcon} ${finding.checkTitle || finding.checkId}`,\n );\n console.log(` ${finding.messageText}`);\n console.log(` Object: ${objectLink} (${finding.objectType})`);\n });\n\n if (result.findings.length > 5) {\n console.log(` ... (${result.findings.length - 5} more findings)`);\n }\n }\n\n console.log(`\\n💡 Use --debug for detailed results`);\n}\n\n/**\n * ATC Command Plugin\n */\nexport const atcCommand: CliCommandPlugin = {\n name: 'atc',\n description: 'Run ABAP Test Cockpit (ATC) checks',\n\n options: [\n { flags: '-p, --package <package>', description: 'Run ATC on package' },\n {\n flags: '-o, --object <uri>',\n description:\n 'Run ATC on specific object (e.g., /sap/bc/adt/oo/classes/zcl_my_class)',\n },\n {\n flags: '-t, --transport <transport>',\n description: 'Run ATC on transport request (e.g., TRLK942970)',\n },\n {\n flags: '-f, --from-file <file>',\n description: 'Run ATC on objects listed in file (one URI per line)',\n },\n {\n flags: '--variant <variant>',\n description: 'ATC check variant (default: from system customizing)',\n },\n {\n flags: '--max-results <number>',\n description: 'Maximum number of results',\n default: '100',\n },\n {\n flags: '--format <format>',\n description: 'Output format: console, json, gitlab, sarif',\n default: 'console',\n },\n {\n flags: '--output <file>',\n description: 'Output file (required for gitlab/sarif format)',\n },\n {\n flags: '--resolver <name>',\n description:\n 'Finding resolver plugin for path/line resolution (e.g., abapgit)',\n },\n ],\n\n async execute(args, ctx: CliContext) {\n const options = args as {\n package?: string;\n object?: string;\n transport?: string;\n fromFile?: string;\n variant?: string;\n maxResults?: string;\n format?: OutputFormat;\n output?: string;\n resolver?: string;\n };\n\n // Validate mutually exclusive options\n const targetCount = [\n options.package,\n options.object,\n options.transport,\n options.fromFile,\n ].filter(Boolean).length;\n\n if (targetCount === 0) {\n ctx.logger.error(\n '❌ One of --package, --object, --transport, or --from-file is required',\n );\n process.exit(1);\n }\n\n if (targetCount > 1) {\n ctx.logger.error(\n '❌ Only one of --package, --object, --transport, or --from-file can be specified',\n );\n process.exit(1);\n }\n\n // Validate output file for gitlab/sarif formats\n if (\n (options.format === 'gitlab' || options.format === 'sarif') &&\n !options.output\n ) {\n ctx.logger.error(`❌ --output is required for ${options.format} format`);\n process.exit(1);\n }\n\n // Get ADT client from context\n if (!ctx.getAdtClient) {\n ctx.logger.error(\n '❌ ADT client not available. This command requires authentication.',\n );\n ctx.logger.error(' Run: adt auth login');\n process.exit(1);\n }\n\n const client = (await ctx.getAdtClient()) as AdtClient;\n\n ctx.logger.info('🔍 Running ABAP Test Cockpit checks...');\n\n // Determine target(s)\n let targetUris: string[];\n let targetName: string;\n\n if (options.fromFile) {\n // Read URIs from file\n const { readFileSync } = await import('fs');\n const fileContent = readFileSync(options.fromFile, 'utf-8');\n targetUris = fileContent\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line && !line.startsWith('#')); // Skip empty lines and comments\n\n if (targetUris.length === 0) {\n ctx.logger.error(`❌ No objects found in ${options.fromFile}`);\n process.exit(1);\n }\n targetName = `${targetUris.length} objects from ${options.fromFile}`;\n ctx.logger.info(`📄 Target: ${targetName}`);\n } else if (options.transport) {\n targetUris = [`/sap/bc/adt/cts/transportrequests/${options.transport}`];\n targetName = `Transport ${options.transport}`;\n ctx.logger.info(`🚚 Target: ${targetName}`);\n } else if (options.package) {\n targetUris = [`/sap/bc/adt/packages/${options.package.toUpperCase()}`];\n targetName = `Package ${options.package.toUpperCase()}`;\n ctx.logger.info(`📦 Target: ${targetName}`);\n } else {\n targetUris = [options.object!];\n targetName = options.object!;\n ctx.logger.info(`📄 Target: ${targetName}`);\n }\n\n // Step 1: Get ATC customizing to find default check variant\n ctx.logger.info('📋 Reading ATC customizing...');\n const customizing = await client.adt.atc.customizing.get();\n\n // Find systemCheckVariant in properties\n let checkVariant = options.variant;\n if (!checkVariant) {\n const variantProp = customizing.customizing.properties.property?.find(\n (p) => p.name === 'systemCheckVariant',\n );\n if (variantProp?.value) {\n checkVariant = variantProp.value;\n } else {\n checkVariant = 'DEFAULT';\n }\n }\n ctx.logger.info(`🎯 Check variant: ${checkVariant}`);\n\n // Step 2: Create worklist (just with checkVariant, no objects)\n ctx.logger.info('📋 Creating ATC worklist...');\n const worklistCreateResponse = await client.adt.atc.worklists.create({\n checkVariant,\n });\n\n // Extract worklist ID - SAP can return plain string or object\n let worklistId: string;\n if (typeof worklistCreateResponse === 'string') {\n // Plain string response - extract ID from XML or use as-is\n const idMatch = worklistCreateResponse.match(/id=\"([^\"]+)\"/);\n worklistId = idMatch ? idMatch[1] : worklistCreateResponse.trim();\n } else if (\n 'worklist' in worklistCreateResponse &&\n worklistCreateResponse.worklist?.id\n ) {\n worklistId = worklistCreateResponse.worklist.id;\n } else if (\n 'worklistRun' in worklistCreateResponse &&\n worklistCreateResponse.worklistRun?.worklistId\n ) {\n worklistId = worklistCreateResponse.worklistRun.worklistId;\n } else {\n ctx.logger.error('❌ Failed to get worklist ID from response');\n process.exit(1);\n }\n ctx.logger.info(`📋 Worklist created: ${worklistId}`);\n\n // Step 3: Run ATC checks with objects\n ctx.logger.info('⏳ Running ATC analysis...');\n const maxResults = Number.parseInt(options.maxResults || '100', 10);\n const runData = {\n run: {\n maximumVerdicts: maxResults,\n objectSets: {\n objectSet: [\n {\n kind: 'inclusive',\n objectReferences: {\n objectReference: targetUris.map((uri) => ({ uri })),\n },\n },\n ],\n },\n },\n };\n await client.adt.atc.runs.post({ worklistId }, runData);\n\n // Step 4: Get results\n ctx.logger.info('📊 Fetching results...');\n const worklistResult = await client.adt.atc.worklists.get(worklistId, {\n includeExemptedFindings: 'false',\n });\n\n // Convert to AtcResult\n const result = convertWorklistToResult(\n worklistResult as WorklistResponse,\n checkVariant,\n );\n\n // Enrich findings with check categories from the run's check variant\n // form template (best-effort: never fails the run itself)\n const categoryIndex = await fetchCheckCategoryIndex(\n client,\n checkVariant,\n ctx.logger,\n );\n const enriched = enrichResultWithCategories(result, categoryIndex);\n if (enriched > 0) {\n ctx.logger.info(\n `🏷️ Enriched ${enriched}/${result.totalFindings} finding(s) with check categories`,\n );\n }\n\n // Bridge remaining findings through their ATC documentation page: the\n // worklist checkId is a check-instance GUID, not the implementing class\n // the form-template catalog lists (live evidence, 2026-10-22), so\n // the direct index match cannot resolve them. The documentation page\n // names the check class, which resolves through the same index.\n // Best-effort: never fails the run.\n const bridged = await resolveCategoriesViaDocumentation(\n client,\n result.findings,\n categoryIndex,\n { logger: ctx.logger },\n );\n if (bridged > 0) {\n ctx.logger.info(\n `🏷 Bridged ${bridged} finding(s) to check categories via ATC documentation`,\n );\n }\n\n // Load finding resolver if requested\n let resolver: import('../types').FindingResolver | undefined;\n if (options.resolver) {\n const name = options.resolver;\n if (name === 'abapgit') {\n // Built-in abapgit resolver — no external dependencies\n const { createAbapGitResolver } = await import('../resolvers/abapgit');\n resolver = createAbapGitResolver();\n } else {\n // External resolver — try dynamic import\n try {\n ctx.logger.info(`📂 Loading external resolver: ${name}`);\n const mod = await import(name);\n if (typeof mod.createFindingResolver === 'function') {\n resolver = mod.createFindingResolver();\n } else {\n ctx.logger.warn(\n `⚠️ Module ${name} does not export createFindingResolver()`,\n );\n }\n } catch (e) {\n ctx.logger.warn(\n `⚠️ Failed to load resolver \"${name}\": ${e instanceof Error ? e.message : e}`,\n );\n }\n }\n }\n\n // Output based on format\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else if (options.format === 'gitlab' && options.output) {\n await outputGitLabCodeQuality(result, options.output, { resolver });\n } else if (options.format === 'sarif' && options.output) {\n await outputSarifReport(result, options.output, targetName);\n } else {\n displayAtcResults(result, ctx.adtSystemName);\n }\n\n // Exit with error code if there are errors\n if (result.errorCount > 0) {\n process.exit(1);\n }\n },\n};\n\nexport default atcCommand;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"atc.d.mts","names":[],"sources":["../../src/commands/atc.ts"],"mappings":";;;;cAoMa,YAAY"}
1
+ {"version":3,"file":"atc.d.mts","names":[],"sources":["../../src/commands/atc.ts"],"mappings":";;;;cA6Oa,YAAY"}
@@ -1 +1 @@
1
- export { t as atcCommand, t as default } from "../atc-BEoE4xIY.mjs";
1
+ export { t as atcCommand, t as default } from "../atc-6cFtFkir.mjs";
package/dist/index.d.mts CHANGED
@@ -20,6 +20,20 @@ interface AtcFinding {
20
20
  objectName: string;
21
21
  location?: string;
22
22
  findingUri?: string;
23
+ /**
24
+ * Trusted ATC documentation link for this occurrence (the worklist
25
+ * atom:link). The documentation page names the check class
26
+ * ("Appl. Comp. Check / Check Class / Message Code" header line), which
27
+ * is the bridge from the finding's GUID checkId to the check catalog.
28
+ */
29
+ documentationUri?: string;
30
+ /**
31
+ * Check category name (e.g., 'CI_PERFORMANCE') the finding's check is
32
+ * assigned to, resolved from the run's check variant form template.
33
+ * Optional: enrichment is best-effort and absent when the form template
34
+ * is unavailable or the checkId is not in its catalog.
35
+ */
36
+ checkCategory?: string;
23
37
  }
24
38
  /**
25
39
  * ATC Result summary
@@ -86,6 +100,206 @@ interface GitLabFormatterOptions {
86
100
  resolver?: FindingResolver;
87
101
  }
88
102
  declare function outputGitLabCodeQuality(result: AtcResult, outputFile: string, options?: GitLabFormatterOptions): Promise<void>;
103
+ /**
104
+ * Check Category Enrichment
105
+ *
106
+ * Resolves ATC findings to their check category (e.g., 'CI_PERFORMANCE',
107
+ * 'CI_SECURITY') through the check variant form template
108
+ * (GET /sap/bc/adt/atc/checkvariants/formtemplate?chkvName={name}).
109
+ *
110
+ * The form template is the check catalog of the run's check variant: every
111
+ * available check category lists its checks, and each check carries its
112
+ * implementing class. When a finding's checkId matches a catalog
113
+ * implementing class, the index resolves it directly. Live worklist
114
+ * checkIds are check-instance GUIDs (observed on a live S/4 system,
115
+ * 2026-10-22); those resolve through the documentation bridge in
116
+ * ./documentation-bridge.ts.
117
+ *
118
+ * Enrichment is best-effort by design: without the form template, or for a
119
+ * checkId outside the catalog, findings keep no category and the run still
120
+ * succeeds.
121
+ */
122
+ /**
123
+ * Structural shape of the parsed check variant form template document
124
+ * (GET /sap/bc/adt/atc/checkvariants/formtemplate).
125
+ *
126
+ * Only the fields needed for the checkId -> category index are declared.
127
+ */
128
+ interface FormTemplateDocument {
129
+ formTemplate?: {
130
+ availableCheckCategories?: {
131
+ availableCheckCategory?: FormTemplateCategoryEntry[];
132
+ };
133
+ };
134
+ }
135
+ /** One available check category with the checks it contains. */
136
+ interface FormTemplateCategoryEntry {
137
+ checkCategoryObject?: {
138
+ /** Category name (the enrichment key, e.g., 'CI_PERFORMANCE') */
139
+ name?: string;
140
+ /** Category display title (e.g., 'Performance') */
141
+ description?: string;
142
+ };
143
+ check?: FormTemplateCheckEntry[];
144
+ }
145
+ /** One check entry of a category. */
146
+ interface FormTemplateCheckEntry {
147
+ checkObject?: {
148
+ /** Check name (e.g., 'CI_ANALYZE_WHERE') */
149
+ name?: string;
150
+ content?: {
151
+ /** The check's category assignment (the category name) */
152
+ category?: {
153
+ name?: string;
154
+ };
155
+ /**
156
+ * Implementing class of the check (e.g., 'CL_CI_TEST_SELECT_TAW_A').
157
+ * Worklist findings carry a check-instance GUID instead; the
158
+ * documentation bridge resolves that GUID to this class name.
159
+ */
160
+ implementingClass?: {
161
+ name?: string;
162
+ };
163
+ };
164
+ };
165
+ }
166
+ /**
167
+ * Build a checkId -> check category name index from a parsed check variant
168
+ * form template document.
169
+ *
170
+ * The checkId key is the implementing class name; the value is the category
171
+ * object name (a stable key like 'CI_PERFORMANCE', not the display title).
172
+ *
173
+ * @param doc - Parsed form template document (nullable for convenience)
174
+ * @returns Map of checkId -> category name; empty when the document is
175
+ * missing or malformed
176
+ */
177
+ declare function parseCheckCategoryIndex(doc: FormTemplateDocument | null | undefined): Map<string, string>;
178
+ /** Structural client surface needed to fetch a form template. */
179
+ interface FormTemplateClient {
180
+ adt: {
181
+ atc: {
182
+ checkvariants: {
183
+ /** GET /sap/bc/adt/atc/checkvariants/formtemplate{?chkvName} */
184
+ formtemplate: (params?: {
185
+ chkvName?: string;
186
+ }) => Promise<FormTemplateDocument>;
187
+ };
188
+ };
189
+ };
190
+ }
191
+ /**
192
+ * Fetch the check variant form template and build the checkId -> category
193
+ * name index for a run.
194
+ *
195
+ * Best-effort: any error (network, 404, malformed document) is swallowed
196
+ * and reported through the optional logger so a failed enrichment never
197
+ * fails the ATC run itself.
198
+ *
199
+ * @param client - ADT client with the checkvariants.formtemplate surface
200
+ * @param checkVariant - The check variant name of the run (e.g., 'DEFAULT')
201
+ * @param logger - Optional CLI logger for diagnostics
202
+ * @returns checkId -> category name index; empty when unavailable
203
+ */
204
+ declare function fetchCheckCategoryIndex(client: FormTemplateClient, checkVariant: string, logger?: {
205
+ warn: (message: string) => void;
206
+ info?: (message: string) => void;
207
+ }): Promise<Map<string, string>>;
208
+ /**
209
+ * Enrich findings with their check category in place.
210
+ *
211
+ * Generic over any finding shape carrying a checkId (and an optional
212
+ * checkCategory field), so both the CLI plugin's AtcFinding and other
213
+ * canonical finding types can be enriched with the same index.
214
+ *
215
+ * @param findings - Findings to enrich (mutated in place)
216
+ * @param index - checkId -> category name index
217
+ * @returns Number of findings that received a category
218
+ */
219
+ declare function enrichFindingsWithCategories<T extends {
220
+ checkId: string;
221
+ checkCategory?: string;
222
+ }>(findings: T[], index: Map<string, string>): number;
223
+ /**
224
+ * Enrich an ATC result with check categories in place.
225
+ *
226
+ * @param result - The run result (findings mutated in place)
227
+ * @param index - checkId -> category name index
228
+ * @returns Number of findings that received a category
229
+ */
230
+ declare function enrichResultWithCategories<T extends {
231
+ checkId: string;
232
+ checkCategory?: string;
233
+ }>(result: {
234
+ findings?: T[];
235
+ }, index: Map<string, string>): number;
236
+ /**
237
+ * Documentation Bridge for Check Category Enrichment
238
+ *
239
+ * ATC worklist findings carry a check-instance GUID as their checkId — not
240
+ * the implementing class name the check variant form template catalog lists
241
+ * (observed on a live S/4 system, 2026-10-22). The finding's documentation
242
+ * page (the worklist atom:link) names the check class in its header line,
243
+ * which resolves through the form-template category index.
244
+ *
245
+ * Everything here is best-effort: fetch errors are reported through the
246
+ * optional logger and skipped; findings keep no category when their page
247
+ * cannot be fetched or parsed.
248
+ */
249
+ /** Structural surface for a bounded documentation read. The real ADT client
250
+ * provides readTextBounded; mocks without it simply skip the bridge. */
251
+ interface DocumentationReadingClient {
252
+ readTextBounded?: (uri: string, maxBytes: number, options?: {
253
+ headers?: Record<string, string>;
254
+ }) => Promise<string>;
255
+ }
256
+ /**
257
+ * Parse the implementing check class from an ATC finding documentation page.
258
+ *
259
+ * Live shape (2026-10-22): the page opens with
260
+ * "Appl. Comp. Check / Check Class / Message Code" followed by
261
+ * `<p>BC-ABA-LA / CL_CI_TEST_SELECT_TAW_A / 0501</p>`. The check class in
262
+ * that line is the implementing class the check variant form template lists
263
+ * — the bridge from the finding's GUID checkId to the check catalog.
264
+ *
265
+ * The match is bounded (no unbounded quantifiers on page input) and accepts
266
+ * an optional ABAP namespace prefix (e.g. `/NSP/CL_EXAMPLE`).
267
+ *
268
+ * @param html - The documentation page HTML
269
+ * @returns The check class name, or undefined when the page does not carry it
270
+ */
271
+ declare function parseCheckClassFromDocumentation(html: string): string | undefined;
272
+ /** Finding shape the bridge enriches in place. */
273
+ interface DocumentationBridgeFinding {
274
+ checkId: string;
275
+ checkCategory?: string;
276
+ documentationUri?: string;
277
+ }
278
+ /**
279
+ * Bridge finding categories through the ATC documentation page.
280
+ *
281
+ * After the direct form-template index pass (which resolves findings whose
282
+ * checkId already matches a catalog implementing class), this resolves the
283
+ * GUID-checkId findings: each finding's documentation page names the check
284
+ * class, which resolves through the same index.
285
+ *
286
+ * Best-effort: per-documentation-URI fetch errors are reported through the
287
+ * optional logger and skipped; a finding keeps no category when its page
288
+ * cannot be fetched or parsed. Reads are deduplicated per URI.
289
+ *
290
+ * @param client - ADT client with an optional bounded text read
291
+ * @param findings - Findings to enrich in place
292
+ * @param classToCategory - checkId/class -> category name index (the
293
+ * form-template catalog: parseCheckCategoryIndex output)
294
+ * @param options - maxBytes (default 64 KiB) and an optional logger
295
+ * @returns Number of findings that received a category
296
+ */
297
+ declare function resolveCategoriesViaDocumentation(client: DocumentationReadingClient, findings: DocumentationBridgeFinding[], classToCategory: ReadonlyMap<string, string>, options?: {
298
+ maxBytes?: number;
299
+ logger?: {
300
+ warn: (message: string) => void;
301
+ };
302
+ }): Promise<number>;
89
303
  /**
90
304
  * Create a built-in abapGit finding resolver.
91
305
  *
@@ -109,5 +323,5 @@ declare function createAbapGitResolver(srcRoot?: string): FindingResolver;
109
323
  * callers resolve that prefix against the repository's actual source tree.
110
324
  */
111
325
  declare function adtUriToAbapGitPath(uri: string): string | null;
112
- export { type AtcFinding, type AtcResult, type FindingResolver, type OutputFormat, type ResolvedLocation, adtUriToAbapGitPath, atcCommand, atcCustomizingCommand, createAbapGitResolver, outputGitLabCodeQuality, outputSarifReport };
326
+ export { type AtcFinding, type AtcResult, type DocumentationBridgeFinding, type DocumentationReadingClient, type FindingResolver, type FormTemplateCategoryEntry, type FormTemplateCheckEntry, type FormTemplateClient, type FormTemplateDocument, type OutputFormat, type ResolvedLocation, adtUriToAbapGitPath, atcCommand, atcCustomizingCommand, createAbapGitResolver, enrichFindingsWithCategories, enrichResultWithCategories, fetchCheckCategoryIndex, outputGitLabCodeQuality, outputSarifReport, parseCheckCategoryIndex, parseCheckClassFromDocumentation, resolveCategoriesViaDocumentation };
113
327
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/commands/atc-customizing.ts","../src/types.ts","../src/formatters/sarif.ts","../src/formatters/gitlab.ts","../src/resolvers/abapgit.ts","../src/resolvers/adt-uri-to-abapgit-path.ts"],"mappings":";;cAiCa,uBAAuB;;;;;;;;;UCxBnB;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;UAMe;EACf;EACA;EACA;EACA;EACA;EACA,UAAU;;;;;KAMA;;;;UAKK;;EAEf;;EAEA;;;;;;;;;;;;;;;;;;;;;UAsBe;;;;;;;;;;;EAWf,QACE,oBACA,oBACA,iBACA,qBACA,uBACC,QAAQ;;iBC5ES,kBACpB,QAAQ,WACR,oBACA,uBACC;;;;UCmBc;;EAEf,WAAW;;iBAsBS,wBACpB,QAAQ,WACR,oBACA,UAAU,yBACT;;;;;;;;;iBCmOa,sBAAsB,mBAAmB;;;;;;;;;;;;;;iBCzHzC,oBAAoB"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/commands/atc-customizing.ts","../src/types.ts","../src/formatters/sarif.ts","../src/formatters/gitlab.ts","../src/check-categories.ts","../src/documentation-bridge.ts","../src/resolvers/abapgit.ts","../src/resolvers/adt-uri-to-abapgit-path.ts"],"mappings":";;cAiCa,uBAAuB;;;;;;;;;UCxBnB;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;EAOA;;;;;;;EAOA;;;;;UAMe;EACf;EACA;EACA;EACA;EACA;EACA,UAAU;;;;;KAMA;;;;UAKK;;EAEf;;EAEA;;;;;;;;;;;;;;;;;;;;;UAsBe;;;;;;;;;;;EAWf,QACE,oBACA,oBACA,iBACA,qBACA,uBACC,QAAQ;;iBC1FS,kBACpB,QAAQ,WACR,oBACA,uBACC;;;;UCmBc;;EAEf,WAAW;;iBAsBS,wBACpB,QAAQ,WACR,oBACA,UAAU,yBACT;;;;;;;;;;;;;;;;;;;;;;;;;;UClCc;EACf;IACE;MACE,yBAAyB;;;;;UAMd;EACf;;IAEE;;IAEA;;EAEF,QAAQ;;;UAIO;EACf;;IAEE;IACA;;MAEE;QAAa;;;;;;;MAMb;QAAsB;;;;;;;;;;;;;;;;iBAgBZ,wBACd,KAAK,0CACJ;;UA0Bc;EACf;IACE;MACE;;QAEE,eAAe;UACb;cACI,QAAQ;;;;;;;;;;;;;;;;;;iBAmBA,wBACpB,QAAQ,oBACR,sBACA;EACE,OAAO;EACP,QAAQ;IAET,QAAQ;;;;;;;;;;;;iBA2BK,6BACd;EAAY;EAAiB;GAC7B,UAAU,KAAK,OAAO;;;;;;;;iBAmBR,2BACd;EAAY;EAAiB;GAC7B;EAAU,WAAW;GAAO,OAAO;;;;;;;;;;;;;;;;UCzKpB;EACf,mBACE,aACA,kBACA;IAAY,UAAU;QACnB;;;;;;;;;;;;;;;;;iBAkBS,iCACd;;UASe;EACf;EACA;EACA;;;;;;;;;;;;;;;;;;;;;iBAyDoB,kCACpB,QAAQ,4BACR,UAAU,8BACV,iBAAiB,6BACjB;EACE;EACA;IAAW,OAAO;;IAEnB;;;;;;;;;iBC0Ka,sBAAsB,mBAAmB;;;;;;;;;;;;;;iBCzHzC,oBAAoB"}
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { n as createAbapGitResolver, r as adtUriToAbapGitPath } from "./abapgit-B2E5I-u0.mjs";
2
- import { n as outputGitLabCodeQuality, r as outputSarifReport, t as atcCommand } from "./atc-BEoE4xIY.mjs";
2
+ import { a as enrichResultWithCategories, c as outputGitLabCodeQuality, i as enrichFindingsWithCategories, l as outputSarifReport, n as parseCheckClassFromDocumentation, o as fetchCheckCategoryIndex, r as resolveCategoriesViaDocumentation, s as parseCheckCategoryIndex, t as atcCommand } from "./atc-6cFtFkir.mjs";
3
3
  // src/commands/atc-customizing.ts
4
4
  const atcCustomizingCommand = {
5
5
  name: "atc-customizing",
@@ -41,6 +41,6 @@ const atcCustomizingCommand = {
41
41
  }
42
42
  }
43
43
  };
44
- export { adtUriToAbapGitPath, atcCommand, atcCustomizingCommand, createAbapGitResolver, outputGitLabCodeQuality, outputSarifReport };
44
+ export { adtUriToAbapGitPath, atcCommand, atcCustomizingCommand, createAbapGitResolver, enrichFindingsWithCategories, enrichResultWithCategories, fetchCheckCategoryIndex, outputGitLabCodeQuality, outputSarifReport, parseCheckCategoryIndex, parseCheckClassFromDocumentation, resolveCategoriesViaDocumentation };
45
45
 
46
46
  //# sourceMappingURL=index.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abapify/adt-atc",
3
- "version": "0.4.20",
3
+ "version": "0.4.21",
4
4
  "description": "ABAP Test Cockpit (ATC) CLI plugin for adt-cli",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -28,7 +28,7 @@
28
28
  "gitlab"
29
29
  ],
30
30
  "dependencies": {
31
- "@abapify/adt-plugin": "0.4.20",
31
+ "@abapify/adt-plugin": "0.4.21",
32
32
  "chalk": "^5.3.0"
33
33
  },
34
34
  "module": "./dist/index.mjs",
@@ -1 +0,0 @@
1
- {"version":3,"file":"atc-BEoE4xIY.mjs","names":[],"sources":["../src/formatters/sarif.ts","../src/formatters/gitlab.ts","../src/commands/atc.ts"],"sourcesContent":["/**\n * SARIF Formatter\n *\n * Outputs ATC findings in SARIF format for GitHub Code Scanning.\n */\n\nimport { writeFile } from 'node:fs/promises';\nimport type { AtcResult, AtcFinding } from '../types';\n\nexport async function outputSarifReport(\n result: AtcResult,\n outputFile: string,\n _targetName?: string,\n): Promise<void> {\n // Transform ATC findings to SARIF format\n const rules = Array.from(\n new Map(\n result.findings.map((f) => [\n f.checkId,\n {\n id: f.checkId,\n name: f.checkTitle || f.checkId,\n shortDescription: { text: f.checkTitle || f.checkId },\n fullDescription: { text: f.checkTitle || f.checkId },\n defaultConfiguration: {\n level:\n f.priority === 1\n ? 'error'\n : f.priority === 2\n ? 'warning'\n : 'note',\n },\n properties: {\n tags: ['abap', 'code-quality'],\n precision: 'high',\n },\n },\n ]),\n ).values(),\n );\n\n const results = result.findings.map((finding: AtcFinding) => {\n const filePath = `src/${finding.objectType.toLowerCase()}/${finding.objectName.toLowerCase()}.${finding.objectType.toLowerCase()}`;\n\n // Parse line number from location if available\n const lineMatch = finding.location?.match(/start=(\\d+)/);\n const line = lineMatch ? Number.parseInt(lineMatch[1], 10) : 1;\n\n return {\n ruleId: finding.checkId,\n level:\n finding.priority === 1\n ? 'error'\n : finding.priority === 2\n ? 'warning'\n : 'note',\n message: { text: finding.messageText },\n locations: [\n {\n physicalLocation: {\n artifactLocation: { uri: filePath },\n region: {\n startLine: line,\n startColumn: 1,\n },\n },\n },\n ],\n partialFingerprints: {\n primaryLocationLineHash: `${finding.checkId}-${finding.objectName}-${line}`,\n },\n };\n });\n\n const sarifReport = {\n $schema: 'https://json.schemastore.org/sarif-2.1.0.json',\n version: '2.1.0',\n runs: [\n {\n tool: {\n driver: {\n name: 'ABAP Test Cockpit',\n version: '1.0.0',\n informationUri:\n 'https://help.sap.com/docs/ABAP_PLATFORM_NEW/c238d694b825421f940829321ffa326a/4ec5711c6e391014adc9fffe4e204223.html',\n rules: Array.from(rules),\n },\n },\n results,\n },\n ],\n };\n\n await writeFile(outputFile, JSON.stringify(sarifReport, null, 2));\n console.log(`\\n📄 SARIF report written to: ${outputFile}`);\n console.log(\n `📊 ${result.totalFindings} issues exported for GitHub Code Scanning`,\n );\n}\n","/**\n * GitLab Code Quality Formatter\n *\n * Outputs ATC findings in GitLab Code Quality format.\n *\n * This formatter is a pure output formatter — it maps ATC findings to the\n * GitLab Code Quality JSON structure. Path resolution and line number\n * conversion are delegated to an optional FindingResolver.\n *\n * Without a resolver, the formatter produces PREFIX-style paths and raw\n * ATC (method-relative) line numbers. With a resolver (e.g., from\n * @abapify/adt-plugin-abapgit), it produces correct git paths and\n * file-relative line numbers.\n *\n * @example\n * ```typescript\n * // Without resolver (standalone CLI usage)\n * await outputGitLabCodeQuality(result, 'report.json');\n *\n * // With resolver (inside an abapgit repo)\n * import { createFindingResolver } from '@abapify/adt-plugin-abapgit';\n * const resolver = await createFindingResolver();\n * await outputGitLabCodeQuality(result, 'report.json', { resolver });\n * ```\n */\n\nimport { writeFile } from 'node:fs/promises';\nimport type { AtcResult, AtcFinding, FindingResolver } from '../types';\n\n/**\n * Options for the GitLab Code Quality formatter\n */\nexport interface GitLabFormatterOptions {\n /** Optional resolver for path/line resolution (e.g., from abapgit plugin) */\n resolver?: FindingResolver;\n}\n\n// ── Extract method name from ATC location URI ───────────────────────────\n// Format 1: /sap/bc/adt/oo/classes/zcl_foo/methods/my_method#start=21,0\n// Format 2: ...#type=CLAS%2FOM;name=MY_METHOD;start=21\n\nfunction extractMethodName(location?: string): string | undefined {\n if (!location) return undefined;\n const match =\n location.match(/\\/methods\\/(\\w+)/i) || location.match(/[;?&]name=(\\w+)/i);\n return match ? match[1].toLowerCase() : undefined;\n}\n\n// ── Construct PREFIX-style fallback path ─────────────────────────────────\n\nfunction prefixPath(finding: AtcFinding): string {\n return `src/${finding.objectType.toLowerCase()}/${finding.objectName.toLowerCase()}.${finding.objectType.toLowerCase()}.abap`;\n}\n\n// ── Main formatter ──────────────────────────────────────────────────────\n\nexport async function outputGitLabCodeQuality(\n result: AtcResult,\n outputFile: string,\n options?: GitLabFormatterOptions,\n): Promise<void> {\n const resolver = options?.resolver;\n\n // Transform ATC findings to GitLab Code Quality format\n const gitlabReport = await Promise.all(\n result.findings.map(async (finding: AtcFinding) => {\n // Map ATC priority to GitLab severity\n let severity: string;\n switch (finding.priority) {\n case 1:\n severity = 'blocker';\n break;\n case 2:\n severity = 'major';\n break;\n case 3:\n severity = 'minor';\n break;\n default:\n severity = 'info';\n break;\n }\n\n // Parse ATC line number from location\n const lineMatch = finding.location?.match(/start=(\\d+)/);\n const atcLine = lineMatch ? Number.parseInt(lineMatch[1], 10) : 1;\n\n // Extract method name from ATC location URI\n const methodName = extractMethodName(finding.location);\n\n // Resolve path and line via plugin, or fall back to PREFIX-style\n let filePath: string;\n let fileLine: number;\n\n if (resolver) {\n const resolved = await resolver.resolve(\n finding.objectType,\n finding.objectName,\n atcLine,\n methodName,\n finding.location,\n );\n if (resolved) {\n filePath = resolved.path;\n fileLine = resolved.line;\n } else {\n filePath = prefixPath(finding);\n fileLine = atcLine;\n }\n } else {\n filePath = prefixPath(finding);\n fileLine = atcLine;\n }\n\n // Create unique fingerprint\n const fingerprint = `${finding.checkId}-${finding.objectName}-${fileLine}`;\n\n return {\n description: finding.messageText,\n check_name: finding.checkTitle || finding.checkId,\n fingerprint,\n severity,\n location: {\n path: filePath,\n lines: {\n begin: fileLine,\n end: fileLine,\n },\n },\n // Extra fields for downstream processing\n ...(methodName && { method: methodName }),\n ...(finding.location && { atc_location: finding.location }),\n };\n }),\n );\n\n await writeFile(outputFile, JSON.stringify(gitlabReport, null, 2));\n console.log(`\\n📄 GitLab Code Quality report written to: ${outputFile}`);\n console.log(`📊 ${result.totalFindings} issues exported`);\n}\n","/**\n * ATC Command Plugin\n *\n * CLI-agnostic command for running ABAP Test Cockpit checks.\n * Uses the CliContext.getAdtClient() factory for API access.\n */\n\nimport type { CliCommandPlugin, CliContext } from '@abapify/adt-plugin';\nimport chalk from 'chalk';\nimport { outputSarifReport, outputGitLabCodeQuality } from '../formatters';\nimport type { AtcResult, AtcFinding, OutputFormat } from '../types';\n\n// Client type - the plugin receives a typed client from context\n// The client.adt.atc namespace provides all ATC operations\ninterface AdtClient {\n adt: {\n atc: {\n customizing: {\n get: () => Promise<{\n customizing: {\n properties: { property?: Array<{ name: string; value?: string }> };\n };\n }>;\n };\n worklists: {\n create: (params: {\n checkVariant: string;\n }) => Promise<\n | string\n | { worklistRun?: { worklistId: string }; worklist?: { id: string } }\n >;\n get: (\n id: string,\n params: { includeExemptedFindings: string },\n ) => Promise<WorklistResponse>;\n };\n runs: {\n post: (\n params: { worklistId: string },\n body: {\n run: {\n maximumVerdicts: number;\n objectSets: {\n objectSet: Array<{\n kind: string;\n objectReferences: {\n objectReference: Array<{ uri: string }>;\n };\n }>;\n };\n };\n },\n ) => Promise<void>;\n };\n };\n };\n}\n\n// Worklist response type\ninterface WorklistResponse {\n worklist?: {\n id: string;\n timestamp: string;\n objects: {\n object?: Array<{\n uri?: string;\n type?: string;\n name?: string;\n findings: {\n finding?: Array<{\n uri?: string;\n location?: string;\n priority?: string;\n checkId?: string;\n checkTitle?: string;\n messageId?: string;\n messageTitle?: string;\n }>;\n };\n }>;\n };\n };\n worklistRun?: { worklistId: string };\n}\n\n/**\n * Create OSC 8 hyperlink for terminal\n */\nfunction hyperlink(text: string, url: string): string {\n const OSC = '\\x1b]';\n const BEL = '\\x07';\n const SEP = ';';\n return `${OSC}8${SEP}${SEP}${url}${BEL}${text}${OSC}8${SEP}${SEP}${BEL}`;\n}\n\n/**\n * Create ADT Eclipse link\n */\nfunction adtLink(name: string, uri: string, systemName?: string): string {\n if (!systemName || !name || !uri) {\n return name ? chalk.cyan(name) : '';\n }\n const path = uri.startsWith('/sap/bc/adt') ? uri : `/sap/bc/adt${uri}`;\n const url = `adt://${systemName}${path}`;\n return hyperlink(chalk.cyan(name), url);\n}\n\n/**\n * Convert worklist response to AtcResult\n */\nfunction convertWorklistToResult(\n worklistResponse: WorklistResponse,\n checkVariant: string,\n): AtcResult {\n const findings: AtcFinding[] = [];\n\n if (worklistResponse.worklist) {\n const objects = worklistResponse.worklist.objects.object || [];\n\n for (const obj of objects) {\n const objFindings = obj.findings.finding || [];\n for (const finding of objFindings) {\n findings.push({\n checkId: finding.checkId || '',\n checkTitle: finding.checkTitle || '',\n messageId: finding.messageId || '',\n priority: Number.parseInt(finding.priority || '3', 10),\n messageText: finding.messageTitle || '',\n objectUri: obj.uri || '',\n objectType: obj.type || '',\n objectName: obj.name || '',\n location: finding.location,\n findingUri: finding.uri,\n });\n }\n }\n }\n\n const errorCount = findings.filter((f) => f.priority === 1).length;\n const warningCount = findings.filter((f) => f.priority === 2).length;\n const infoCount = findings.filter((f) => f.priority >= 3).length;\n\n return {\n checkVariant,\n totalFindings: findings.length,\n errorCount,\n warningCount,\n infoCount,\n findings,\n };\n}\n\n/**\n * Display ATC results in console\n */\nfunction displayAtcResults(result: AtcResult, systemName?: string): void {\n if (result.totalFindings === 0) {\n console.log(`\\n✅ ATC check passed - No issues found!`);\n console.log(` 🎯 Variant: ${result.checkVariant}`);\n return;\n }\n\n console.log(`\\n📊 ATC Results Summary:`);\n console.log(` 🎯 Variant: ${result.checkVariant}`);\n if (result.errorCount > 0) console.log(` ❌ Errors: ${result.errorCount}`);\n if (result.warningCount > 0)\n console.log(` ⚠️ Warnings: ${result.warningCount}`);\n if (result.infoCount > 0) console.log(` ℹ️ Info: ${result.infoCount}`);\n console.log(` 📋 Total Issues: ${result.totalFindings}`);\n\n if (result.findings && result.findings.length > 0) {\n console.log(`\\n📄 Findings:`);\n result.findings.slice(0, 5).forEach((finding) => {\n const priorityIcon =\n finding.priority === 1 ? '❌' : finding.priority === 2 ? '⚠️' : 'ℹ️';\n const objectLink = finding.location\n ? adtLink(finding.objectName, finding.location, systemName)\n : finding.objectName;\n console.log(\n ` ${priorityIcon} ${finding.checkTitle || finding.checkId}`,\n );\n console.log(` ${finding.messageText}`);\n console.log(` Object: ${objectLink} (${finding.objectType})`);\n });\n\n if (result.findings.length > 5) {\n console.log(` ... (${result.findings.length - 5} more findings)`);\n }\n }\n\n console.log(`\\n💡 Use --debug for detailed results`);\n}\n\n/**\n * ATC Command Plugin\n */\nexport const atcCommand: CliCommandPlugin = {\n name: 'atc',\n description: 'Run ABAP Test Cockpit (ATC) checks',\n\n options: [\n { flags: '-p, --package <package>', description: 'Run ATC on package' },\n {\n flags: '-o, --object <uri>',\n description:\n 'Run ATC on specific object (e.g., /sap/bc/adt/oo/classes/zcl_my_class)',\n },\n {\n flags: '-t, --transport <transport>',\n description: 'Run ATC on transport request (e.g., TRLK942970)',\n },\n {\n flags: '-f, --from-file <file>',\n description: 'Run ATC on objects listed in file (one URI per line)',\n },\n {\n flags: '--variant <variant>',\n description: 'ATC check variant (default: from system customizing)',\n },\n {\n flags: '--max-results <number>',\n description: 'Maximum number of results',\n default: '100',\n },\n {\n flags: '--format <format>',\n description: 'Output format: console, json, gitlab, sarif',\n default: 'console',\n },\n {\n flags: '--output <file>',\n description: 'Output file (required for gitlab/sarif format)',\n },\n {\n flags: '--resolver <name>',\n description:\n 'Finding resolver plugin for path/line resolution (e.g., abapgit)',\n },\n ],\n\n async execute(args, ctx: CliContext) {\n const options = args as {\n package?: string;\n object?: string;\n transport?: string;\n fromFile?: string;\n variant?: string;\n maxResults?: string;\n format?: OutputFormat;\n output?: string;\n resolver?: string;\n };\n\n // Validate mutually exclusive options\n const targetCount = [\n options.package,\n options.object,\n options.transport,\n options.fromFile,\n ].filter(Boolean).length;\n\n if (targetCount === 0) {\n ctx.logger.error(\n '❌ One of --package, --object, --transport, or --from-file is required',\n );\n process.exit(1);\n }\n\n if (targetCount > 1) {\n ctx.logger.error(\n '❌ Only one of --package, --object, --transport, or --from-file can be specified',\n );\n process.exit(1);\n }\n\n // Validate output file for gitlab/sarif formats\n if (\n (options.format === 'gitlab' || options.format === 'sarif') &&\n !options.output\n ) {\n ctx.logger.error(`❌ --output is required for ${options.format} format`);\n process.exit(1);\n }\n\n // Get ADT client from context\n if (!ctx.getAdtClient) {\n ctx.logger.error(\n '❌ ADT client not available. This command requires authentication.',\n );\n ctx.logger.error(' Run: adt auth login');\n process.exit(1);\n }\n\n const client = (await ctx.getAdtClient()) as AdtClient;\n\n ctx.logger.info('🔍 Running ABAP Test Cockpit checks...');\n\n // Determine target(s)\n let targetUris: string[];\n let targetName: string;\n\n if (options.fromFile) {\n // Read URIs from file\n const { readFileSync } = await import('fs');\n const fileContent = readFileSync(options.fromFile, 'utf-8');\n targetUris = fileContent\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line && !line.startsWith('#')); // Skip empty lines and comments\n\n if (targetUris.length === 0) {\n ctx.logger.error(`❌ No objects found in ${options.fromFile}`);\n process.exit(1);\n }\n targetName = `${targetUris.length} objects from ${options.fromFile}`;\n ctx.logger.info(`📄 Target: ${targetName}`);\n } else if (options.transport) {\n targetUris = [`/sap/bc/adt/cts/transportrequests/${options.transport}`];\n targetName = `Transport ${options.transport}`;\n ctx.logger.info(`🚚 Target: ${targetName}`);\n } else if (options.package) {\n targetUris = [`/sap/bc/adt/packages/${options.package.toUpperCase()}`];\n targetName = `Package ${options.package.toUpperCase()}`;\n ctx.logger.info(`📦 Target: ${targetName}`);\n } else {\n targetUris = [options.object!];\n targetName = options.object!;\n ctx.logger.info(`📄 Target: ${targetName}`);\n }\n\n // Step 1: Get ATC customizing to find default check variant\n ctx.logger.info('📋 Reading ATC customizing...');\n const customizing = await client.adt.atc.customizing.get();\n\n // Find systemCheckVariant in properties\n let checkVariant = options.variant;\n if (!checkVariant) {\n const variantProp = customizing.customizing.properties.property?.find(\n (p) => p.name === 'systemCheckVariant',\n );\n if (variantProp?.value) {\n checkVariant = variantProp.value;\n } else {\n checkVariant = 'DEFAULT';\n }\n }\n ctx.logger.info(`🎯 Check variant: ${checkVariant}`);\n\n // Step 2: Create worklist (just with checkVariant, no objects)\n ctx.logger.info('📋 Creating ATC worklist...');\n const worklistCreateResponse = await client.adt.atc.worklists.create({\n checkVariant,\n });\n\n // Extract worklist ID - SAP can return plain string or object\n let worklistId: string;\n if (typeof worklistCreateResponse === 'string') {\n // Plain string response - extract ID from XML or use as-is\n const idMatch = worklistCreateResponse.match(/id=\"([^\"]+)\"/);\n worklistId = idMatch ? idMatch[1] : worklistCreateResponse.trim();\n } else if (\n 'worklist' in worklistCreateResponse &&\n worklistCreateResponse.worklist?.id\n ) {\n worklistId = worklistCreateResponse.worklist.id;\n } else if (\n 'worklistRun' in worklistCreateResponse &&\n worklistCreateResponse.worklistRun?.worklistId\n ) {\n worklistId = worklistCreateResponse.worklistRun.worklistId;\n } else {\n ctx.logger.error('❌ Failed to get worklist ID from response');\n process.exit(1);\n }\n ctx.logger.info(`📋 Worklist created: ${worklistId}`);\n\n // Step 3: Run ATC checks with objects\n ctx.logger.info('⏳ Running ATC analysis...');\n const maxResults = Number.parseInt(options.maxResults || '100', 10);\n const runData = {\n run: {\n maximumVerdicts: maxResults,\n objectSets: {\n objectSet: [\n {\n kind: 'inclusive',\n objectReferences: {\n objectReference: targetUris.map((uri) => ({ uri })),\n },\n },\n ],\n },\n },\n };\n await client.adt.atc.runs.post({ worklistId }, runData);\n\n // Step 4: Get results\n ctx.logger.info('📊 Fetching results...');\n const worklistResult = await client.adt.atc.worklists.get(worklistId, {\n includeExemptedFindings: 'false',\n });\n\n // Convert to AtcResult\n const result = convertWorklistToResult(\n worklistResult as WorklistResponse,\n checkVariant,\n );\n\n // Load finding resolver if requested\n let resolver: import('../types').FindingResolver | undefined;\n if (options.resolver) {\n const name = options.resolver;\n if (name === 'abapgit') {\n // Built-in abapgit resolver — no external dependencies\n const { createAbapGitResolver } = await import('../resolvers/abapgit');\n resolver = createAbapGitResolver();\n } else {\n // External resolver — try dynamic import\n try {\n ctx.logger.info(`📂 Loading external resolver: ${name}`);\n const mod = await import(name);\n if (typeof mod.createFindingResolver === 'function') {\n resolver = mod.createFindingResolver();\n } else {\n ctx.logger.warn(\n `⚠️ Module ${name} does not export createFindingResolver()`,\n );\n }\n } catch (e) {\n ctx.logger.warn(\n `⚠️ Failed to load resolver \"${name}\": ${e instanceof Error ? e.message : e}`,\n );\n }\n }\n }\n\n // Output based on format\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else if (options.format === 'gitlab' && options.output) {\n await outputGitLabCodeQuality(result, options.output, { resolver });\n } else if (options.format === 'sarif' && options.output) {\n await outputSarifReport(result, options.output, targetName);\n } else {\n displayAtcResults(result, ctx.adtSystemName);\n }\n\n // Exit with error code if there are errors\n if (result.errorCount > 0) {\n process.exit(1);\n }\n },\n};\n\nexport default atcCommand;\n"],"mappings":"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"}