Skip to content
Back to skills

Sap Check Abap

ASecurity

Validates ABAP source (report / program / FM / class / include) before deployment — one skill, dimension-dispatched: naming (variable conventions), type (DDIC type validity), unused (dead variables), sql (SELECT/UPDATE/DELETE fields vs table defs), contract (offline generation rules), spec (offline: code covers every dependency / message / text / selection field in the design spec), fm (CALL FUNCTION params / sections / types vs the live FM over RFC — absorbed from the former sap-check-fm), s...

  • 7 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 27, 2026
toolsgoshellbashsqlapifrontendsecurity

Works with

  • claude code
  • cli
  • api

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned September 5, 2026

npx -y skills add sapdev-ai/sap-dev --skill sap-check-abap --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Sap Check Abap?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Sap Check Abap
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/sapdev-ai-sap-check-abap/badge)](https://www.skillsdirectory.com/skills/sapdev-ai-sap-check-abap)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: sap-check-abap
description: |
  Validates ABAP source (report / program / FM / class / include) before
  deployment — one skill, dimension-dispatched: naming (variable conventions),
  type (DDIC type validity), unused (dead variables), sql (SELECT/UPDATE/DELETE
  fields vs table defs), contract (offline generation rules), spec (offline: code
  covers every dependency / message / text / selection field in the design spec),
  fm (CALL FUNCTION params / sections / types vs the live FM over RFC — absorbed
  from the former sap-check-fm), syntax (compiler-level EDITOR_SYNTAX_CHECK via
  RFC, catching real errors offline before any GUI upload). Writes tab-delimited
  result files with fix advice; feed findings to /sap-fix-abap. SAP connection
  optional — offline skips type/sql/fm/syntax; live dimensions that can't run
  report COULD_NOT_CHECK, never a silent pass.
  Prerequisites: SAP GUI (SAP.Functions COM); SAP NCo 3.1 (32-bit) + the dev-init
  wrapper Z_GENERIC_RFC_WRAPPER_TBL for the syntax dimension.
argument-hint: "<path-to-abap-source-file> [--dimensions naming,type,sql,contract,spec,conv,fm,syntax] [--offline] [--gate]"
---

# SAP Check ABAP Skill

You validate ABAP source code quality before deployment across several **dimensions**, then write findings that `/sap-fix-abap` can consume. This one skill supersedes the former `sap-check-abap` + `sap-check-fm` (now merged here, in sap-dev-core).

Task: $ARGUMENTS

---

## Dimensions — what to run

The skill runs a set of **dimensions**. By default it runs every dimension that
applies to the source and the connection state; narrow with
`--dimensions a,b,…`, force offline with `--offline`, or use the fast pre-deploy
preset `--gate` (= `syntax,fm`).

| Dimension | What it checks | Live? | Step | Output file |
|---|---|---|---|---|
| `naming` | variable + top-level object names | offline | 1.5 + 3 (Phase 5b naming) | `<file>.check.tsv` |
| `type` | data-type validity via DDIC | RFC | 3 | `<file>.check.tsv` |
| `sql` | SELECT/UPDATE/DELETE field validity | RFC | 3 | `<file>.check.tsv` |
| `unused` | declared-but-unused variables | offline | 3 | `<file>.check.tsv` |
| `contract` | generation-contract rules (§ codes) | offline | 3 | `<file>.check.tsv` |
| `spec` | spec-coverage (deps/messages/texts/selection) | offline | 3.5 / 3.6 / 3.7 | `<file>.check.tsv` |
| `conv` | CONVEXIT + CURR/QUAN reference | offline | 3.7 | `<file>.check.tsv` |
| **`fm`** | **CALL FUNCTION signatures vs live FM defs** | **RFC** | **3.8** | `<file>.check_fm.tsv` |
| **`syntax`** | **compiler-level ABAP syntax (all errors + warnings)** | **RFC + wrapper** | **3.9** | `<file>.syntax.tsv` |

**Rules:**
- The `naming`/`type`/`sql`/`unused`/`contract`/`spec`/`conv` dimensions all run in
  the one Step 3 VBS + the Step 3.5–3.7 sidecars (they share `<file>.check.tsv`).
  The new `fm` and `syntax` dimensions run their own steps and write their own
  result files; **Step 4 reads all three** and presents them together.
- **`syntax` applicability:** self-contained `REPORT`/`PROGRAM` sources compile
  standalone (`-Subc 1`). A **function-module `FUNCTION…ENDFUNCTION` fragment** or a
  **class / interface pool** is not standalone-compilable, so the dimension runs the
  engine's **`-Wrap` mode** (Strategy A, proven live S4D 2026-07-04): the fragment is
  re-presented as a self-contained program so its **body** is syntax-checked
  **pre-insert** with zero writes to SAP, and findings are line-mapped back to the
  original file. A signature too complex to model — or a bare **include** with no
  compilation unit — degrades to `SYNTAX_COULD_NOT_CHECK` (never a false-fail),
  deferring to the deploy skill's authoritative in-context Ctrl+F2 after an inactive
  insert (`/sap-se37`, `/sap-se24`). Every other dimension still runs.
- Any live dimension that cannot reach SAP reports `COULD_NOT_CHECK` (honest
  tri-state — never rendered as a pass).

---

## Step 0 — Resolve Work Directory

**Resolve `work_dir` via the env-aware helper** — do NOT take `work_dir` from a direct `settings.json` read (that ignores the `SAPDEV_AI_WORK_DIR` env var and `userconfig.json`). Use the `WORK_DIR=` value printed by:

```bash
powershell -NoProfile -ExecutionPolicy Bypass -Command ". '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_settings_lib.ps1'; . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1'; Write-Output ('WORK_DIR=' + (Get-SapWorkDir))"
```

The settings note below still applies to the OTHER keys.

**Settings reads/writes follow `<SAP_DEV_CORE_SHARED_DIR>/rules/settings_lookup.md`** — merge per-key on the `.value` field (env var → `settings.local.json` → `userconfig.json` → `settings.json`); non-per-connection writes go to `userconfig.json`. Resolve cross-plugin paths: 3 levels up from `<SKILL_DIR>`, then into `sap-dev-core\settings.json` and (if present) `sap-dev-core\settings.local.json`. Read `custom_url`.

| Setting | Default if blank |
|---|---|
| `work_dir` | `C:\sap_dev_work` |
| `custom_url` | `{work_dir}\custom` |

Set `{WORK_TEMP}` = `{work_dir}\temp`

Ensure the temp directory exists:
```bash
cmd /c if not exist "{WORK_TEMP}" mkdir "{WORK_TEMP}"
```

Set `{RUN_TEMP}` = the per-run scratch dir (`Get-SapRunTemp` mints + creates `{work_dir}\temp\run_<id>`):
```bash
powershell -NoProfile -ExecutionPolicy Bypass -Command ". '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1'; Write-Output ('RUN_TEMP=' + (Get-SapRunTemp))"
```
Per the CLAUDE.md "Two-bucket temp model" write this skill's generated scratch (`*_run.ps1` / `*_run.vbs` and the `_run.json` state) under `{RUN_TEMP}`; keep `{WORK_TEMP}` (base) only for `Get-SapCurrentSessionPath -WorkTemp`.

---

## Step 0.5 — Start Logging

Start a structured log run. State file: `{RUN_TEMP}\sap_check_abap_run.json`. Best-effort.

```bash
powershell -ExecutionPolicy Bypass -File "<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_log_helper.ps1" -Action start -StateFile "{RUN_TEMP}\sap_check_abap_run.json" -Skill sap-check-abap -ParamsJson "{\"abap_file\":\"<ABAP_FILE>\"}"
```

---

## Step 1 — Parse Arguments

Extract from `$ARGUMENTS`:
- **ABAP source file path** — required. If not provided, ask for it before continuing.

Verify the source file exists:
```bash
powershell -Command "if (Test-Path 'THE_FILE_PATH') { 'EXISTS' } else { 'NOT FOUND' }"
```

If the file does not exist, tell the user and stop.

Set these paths:
- **RESULT_FILE**: Same directory as the ABAP file, with `.check.tsv` extension (e.g. `{WORK_TEMP}\ztest.abap` → `{WORK_TEMP}\ztest.abap.check.tsv`)
- **NAMING_RULES**: First check if `{custom_url}\abap_naming_rules.tsv` exists. If yes, use it. Otherwise fall back to `<SAP_DEV_CORE_SHARED_DIR>\tables\abap_naming_rules.tsv` — resolve `<SAP_DEV_CORE_SHARED_DIR>` by going 3 levels up from `<SKILL_DIR>` (skill → skills/ → plugin dir → plugins root), then into `sap-dev-core\shared`
- **OBJECT_RULES**: First check `{custom_url}\sap_object_naming_rules.tsv`. If yes, use it. Otherwise fall back to `<SAP_DEV_CORE_SHARED_DIR>\tables\sap_object_naming_rules.tsv`. Used by Step 1.5 to validate program / FORM / class / method names.

Check custom overrides:
```bash
powershell -Command "if (Test-Path '{custom_url}\abap_naming_rules.tsv') { 'CUSTOM' } else { 'DEFAULT' }"
powershell -Command "if (Test-Path '{custom_url}\sap_object_naming_rules.tsv') { 'CUSTOM' } else { 'DEFAULT' }"
```

---

## Step 1.5 — Validate Top-Level Object Names

Parse the ABAP source for top-level object declarations and validate each name
against `sap_object_naming_rules.tsv` via the shared validator
`<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_check_object_name.ps1`.

Patterns to scan (case-insensitive, regex):

| ABAP construct | OBJECT_TYPE for validator |
|---|---|
| `^\s*REPORT\s+(\w+)` or `^\s*PROGRAM\s+(\w+)` | `PROGRAM` |
| `^\s*FORM\s+(\w+)` | `SUBROUTINE` |
| `^\s*CLASS\s+(\w+)\s+DEFINITION\b` — classify by shape: a name matching `^(lcl_\|ltcl_\|ltc_\|lif_)` (case-insensitive), or a definition WITHOUT the `PUBLIC` addition (the addition may sit on a continuation line before the statement's closing period), is an in-source **local** class / test class; only a definition WITH the `PUBLIC` addition is a global class. Skip `DEFINITION DEFERRED` and `DEFINITION LOCAL FRIENDS` lines entirely. | `GLOBAL_CLASS` (PUBLIC definitions) / `LOCAL_CLASS` (in-source local classes) |
| `^\s*(?:METHODS|CLASS-METHODS)\s+(\w+)` (inside global CLASS DEFINITION blocks only) | `METHOD` |

> The shipped `sap_object_naming_rules.tsv` deliberately has NO `LOCAL_CLASS`
> row: the validator returns exit code `2` (UNKNOWN_TYPE) for local classes,
> which per the exit-code rule below is logged once and skipped — so `lcl_*` /
> `ltcl_*` classes are never checked against the `^ZCL_` global-class pattern.
> Shops that want to enforce a local-class convention add a `LOCAL_CLASS` row
> to `{custom_url}\sap_object_naming_rules.tsv`.

For each match, call:

```bash
powershell -ExecutionPolicy Bypass -File "<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_check_object_name.ps1" -ObjectType <TYPE> -ObjectName <NAME> -CustomUrl "{custom_url}"
```

Exit code `1` = VIOLATION → append a row to **RESULT_FILE** with:
- `Code`: `OBJECT_NAMING`
- `Severity`: `WARNING`
- `Variable`: the offending name
- `Detail`: the validator's stdout line
- `Fix Advice`: "Rename to follow `<expected pattern>` (e.g. `<example>`), or override the rule in `{custom_url}\sap_object_naming_rules.tsv`."

Exit code `0` = OK (no row written). Exit code `2` = UNKNOWN_TYPE / RULES_NOT_FOUND → log a single INFO note and skip (do not block).

> Note: variable-level naming inside FORM/METHOD/FUNCTION bodies is still
> handled by the existing VBScript checker (Step 3). Step 1.5 only covers the
> *top-level* object declarations.

---

## Shared Resources

| File | Token | Purpose |
|---|---|---|
| `<SAP_DEV_CORE_SHARED_DIR>/rules/skill_operating_rules.md` | *(rule)* | Mandatory operating rules |
| `<SAP_DEV_CORE_SHARED_DIR>/rules/language_independence_rules.md` | *(rule)* | GUI-scripting language independence — offline checker, but rule applies to downstream deploy skills the checked source feeds |
| `sap-dev-core/settings.json` | *(config)* | SAP connection parameters |
| `sap-dev-core/shared/tables/abap_naming_rules.tsv` | `%%NAMING_RULES%%` | ABAP variable naming prefix conventions |
| `sap-dev-core/shared/tables/sap_object_naming_rules.tsv` | *(read by helper)* | Top-level SAP object naming patterns (program / class / method / FORM). Custom override: `{custom_url}\sap_object_naming_rules.tsv` |
| `sap-dev-core/shared/scripts/sap_check_object_name.ps1` | *(helper)* | Shared validator invoked in Step 1.5 |
| `<SKILL_DIR>/references/sap_check_signatures.ps1` | *(helper, skill-local)* | Step 3.5 signature validator — struct-field + AUTHORITY-CHECK shape vs the live-SAP caches |
| `<SKILL_DIR>/references/sap_check_spec_coverage.ps1` | *(helper, skill-local)* | Step 3.6 spec-coverage validator — confirms the generated manifests cover the spec's deps / messages / text elements / selection fields |
| `<SKILL_DIR>/references/sap_check_conversion.ps1` | *(helper, skill-local)* | Step 3.7 conversion validator — flags CURR/QUAN file columns mapped without their currency/unit reference, and `CURRENCY_AMOUNT_DISPLAY_TO_SAP` feeding a BAPI amount type (double-shift). Reads `*_file_mapping_*.txt` + `_struct_signatures.txt` (§28). |
| `<SKILL_DIR>/references/sap_check_fm.vbs` | *(helper, skill-local)* | Step 3.8 (`fm` dimension) — `CALL FUNCTION` signature checker, absorbed from the former sap-check-fm. Delegates RFC to the two lookup helpers below. |
| `<SAP_DEV_CORE_SHARED_DIR>/scripts/sap_rfc_lookup_fm.ps1` | *(helper)* | Step 3.8 — FM signature fetch (`RPY_FUNCTIONMODULE_READ_NEW`, per-system disk cache). |
| `<SAP_DEV_CORE_SHARED_DIR>/scripts/sap_rfc_lookup_ddic.ps1` | *(helper)* | Step 3 + 3.8 — DDIC type resolution (`DDIF_FIELDINFO_GET` / `DDIF_DTEL_GET`). |
| `<SAP_DEV_CORE_SHARED_DIR>/scripts/sap_rfc_syntax_check.ps1` | *(helper)* | Step 3.9 (`syntax` dimension) — headless `EDITOR_SYNTAX_CHECK` via the dev-init wrapper `Z_GENERIC_RFC_WRAPPER_TBL`. Programs (`-Subc 1`) check directly; FM (`-Subc F -Wrap`) and class/interface (`-Subc K -Wrap`) fragments get a pre-insert **body** check (Strategy A) with findings line-mapped to the original file; signatures too complex to model degrade to COULD_NOT_CHECK. |
| `sap-dev-core/shared/rules/abap_code_quality_rules.md` | *(rule)* | **Mandatory** ABAP code-quality rules. The VBS engine emits the offline-checkable rules in two phases. **Phase 5f** (per-line): `LITERAL_MESSAGE` + `MESSAGE_NUM_UNDECLARED` (§20), `TEXT_NNN_ASSIGN` + `TEXT_SYMBOL_UNDECLARED` (§21), `CLASS_DEF_AFTER_EVENT` (§10), `LOOP_WHERE_EXIT` (§19), `SPLIT_INTO_NUMERIC` (§25), `SELECT_STAR` (§12), `LINE_TOO_LONG`/`LINE_HARD_LIMIT`, plus `SQL_STRICT_COMMA` (§9). **Phase 5g** (scope-tracked): `MESSAGE_E_IN_METHOD` (§11), `SELECT_IN_LOOP` + `FOR_ALL_ENTRIES_NO_GUARD` (§12), `METHOD_TOO_LONG` (§18). These mirror the CI gate `scripts/lint-abap-contract.mjs` (same contract, two engines). The remaining heuristic rules — `MISSING_AT_HOST_VAR`/`STRING_CONCAT_SQL` (§13), `MISSING_AUTHZ_CHECK` (§14) — carry a higher false-positive risk and are reviewed by the orchestrator against this rule file rather than emitted by the engine. |
| `sap-dev-core/shared/templates/customer_brief.md` | *(config)* | Project Profile — used to set the quality bar (e.g. method length limit, modern-ABAP required, ATC priority gating). Resolved language-aware, first hit wins: `{custom_url}\customer_brief_<LANG>.md` → `{custom_url}\customer_brief.md` → shared `customer_brief_<LANG>.md` → this default (`<LANG>`: `userConfig.template_language`, else `userConfig.sap_language`, else `EN` — `EN` skips the `_<LANG>` probes). |

---

## Step 2 — Read SAP Connection Parameters

Read SAP connection parameters from the merged sap-dev-core settings (per `<SAP_DEV_CORE_SHARED_DIR>/rules/settings_lookup.md`). The `sap_password` value typically comes from `settings.local.json` and is a `dpapi:...` blob — decrypt via `sap_dpapi.ps1` before use.
Resolve path: go 3 levels up from `<SKILL_DIR>` (skill → skills/ → plugin dir → plugins root),
then into `sap-dev-core\settings.json`.

| Setting key | Maps to | Example |
|---|---|---|
| `sap_application_server` | `%%SAP_SERVER%%` | `10.0.0.1` |
| `sap_system_number` | `%%SAP_SYSNR%%` | `00` |
| `sap_client` | `%%SAP_CLIENT%%` | `100` |
| `sap_user` | `%%SAP_USER%%` | `DEVELOPER` |
| `sap_password` | `%%SAP_PASSWORD%%` | *(masked)* |
| `sap_language` | `%%SAP_LANGUAGE%%` | `EN` |

**If `sap_application_server` is not configured**, ask the user:
> "Do you want to configure SAP connection in sap-dev-core settings.json for type validation,
> or run in offline mode (naming + unused checks only)?"

If offline mode, set `%%SAP_SERVER%%` to empty string and skip to Step 3.

---

## Step 3 — Generate and Run the Check VBScript

The VBScript template is at `./references/sap_check_abap.vbs` (relative to this skill directory).
RFC type lookups are delegated to the sidecar PowerShell helper at
`<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_rfc_lookup_ddic.ps1` (uses SAP NCo 3.1).

### 3a. Generate the filled DDIC helper PS1

The helper template has tokens: `%%SAP_*%%`, `%%REQUEST_FILE%%`, `%%RESULT_FILE%%`.
Token-substitute and write to `{RUN_TEMP}\sap_checkabap_ddic_helper.ps1`:

```powershell
$h = Get-Content '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_rfc_lookup_ddic.ps1' -Raw
$h = $h -replace '%%SAP_SERVER%%',   ''
$h = $h -replace '%%SAP_SYSNR%%',    ''
$h = $h -replace '%%SAP_CLIENT%%',   ''
$h = $h -replace '%%SAP_USER%%',     ''
$h = $h -replace '%%SAP_PASSWORD%%', ''
$h = $h -replace '%%SAP_LANGUAGE%%', ''
$h = $h -replace '%%RFC_LIB_PS1%%',  '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_rfc_lib.ps1'
$h = $h -replace '%%REQUEST_FILE%%', '{RUN_TEMP}\sap_checkabap_ddic_request.txt'
$h = $h -replace '%%RESULT_FILE%%',  '{RUN_TEMP}\sap_checkabap_ddic_result.tsv'
Set-Content '{RUN_TEMP}\sap_checkabap_ddic_helper.ps1' $h -Encoding UTF8
```

### 3b. Generate the filled VBScript

Write `{RUN_TEMP}\sap_checkabap_run.ps1`:
```powershell
$content = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\sap_check_abap.vbs', [System.Text.Encoding]::UTF8)
$content = $content -replace '%%SAP_SERVER%%',         'THE_SERVER'
$content = $content -replace '%%SAP_SYSNR%%',          'THE_SYSNR'
$content = $content -replace '%%SAP_CLIENT%%',         'THE_CLIENT'
$content = $content -replace '%%SAP_USER%%',           'THE_USER'
$content = $content -replace '%%SAP_PASSWORD%%',       'THE_PASSWORD'
$content = $content -replace '%%SAP_LANGUAGE%%',       'THE_LANGUAGE'
$content = $content -replace '%%ABAP_FILE%%',          'THE_ABAP_FILE'
$content = $content -replace '%%RESULT_FILE%%',        'THE_RESULT_FILE'
$content = $content -replace '%%NAMING_RULES%%',       'THE_NAMING_RULES_PATH'
$content = $content -replace '%%DDIC_HELPER_PS1%%',    '{RUN_TEMP}\sap_checkabap_ddic_helper.ps1'
$content = $content -replace '%%DDIC_REQUEST_FILE%%',  '{RUN_TEMP}\sap_checkabap_ddic_request.txt'
$content = $content -replace '%%DDIC_RESULT_FILE%%',   '{RUN_TEMP}\sap_checkabap_ddic_result.tsv'
$content = $content -replace '%%MAX_METHOD_LINES%%',   'THE_MAX_METHOD_LINES'
[System.IO.File]::WriteAllText('{RUN_TEMP}\sap_checkabap_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true))
Write-Host 'Done'
```
Replace all `THE_*` placeholders and `<SKILL_DIR>` / `<SAP_DEV_CORE_SHARED_DIR>` with absolute paths.
`THE_MAX_METHOD_LINES` = the customer brief's "Maximum method length" (`MODE_MAX_METHOD_LINES`); use `50` if the brief doesn't set it. The VBS defaults to 50 if the token is left unsubstituted or blank, so this is safe to omit.

For **offline mode**, set `THE_SERVER` to empty string (`''`); the VBS will skip the helper invocation entirely.

Run:
```bash
powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_checkabap_run.ps1"
```

### 3c. Execute the VBScript

Run via the 32-bit cscript host (the VBS itself is COM-free and the helper PS1 runs 32-bit PowerShell internally, but spelling the SysWOW64 host is the repo-wide standard for every VBS invocation):
```bash
C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_checkabap_run.vbs
```

Show the full script output as it runs. Then read the result file.

Delete the filled scripts (they contain plaintext credentials):
```bash
cmd /c del {RUN_TEMP}\sap_checkabap_run.vbs
cmd /c del {RUN_TEMP}\sap_checkabap_ddic_helper.ps1
```

---

## Step 3.5 — Validate against live-SAP signature caches (struct + AUTHX)

**Why:** the offline parser in Step 3 can't catch two error classes that show
up at SE38 upload / ATC time:

- **STRUCT field references** like `ls_clientdata-gross_wt = …` where
  `gross_wt` doesn't exist on `BAPI_MARA` on this S/4HANA build (was the 8
  syntax errors of round 5).
- **AUTHORITY-CHECK shape mismatches** vs the live SU21 field list (was the
  P2=10 SLIN storm of round 6).

`/sap-gen-abap` already populates two cache files in the work folder when
RFC is available:

- `<work_folder>/_struct_signatures.txt` (from Step 1.5e — DDIF_FIELDINFO_GET)
- `<work_folder>/_authz_signatures.txt`  (from Step 1.5b' — RFC_READ_TABLE on AUTHX)

This step runs a shared post-parser validator that reads those caches and
appends new finding rows to the same `THE_RESULT_FILE` the Step 3 VBS wrote.
The combined report at Step 4 then shows naming/type/SQL findings AND
struct-field/authz findings in one place.

### 3.5a — Locate signature cache files

The ABAP source lives in `<work_folder>/<NAME>.abap`. Both signature files
are emitted alongside it by `/sap-gen-abap`. Compute:

```
STRUCT_SIG = <directory of ABAP_FILE>\_struct_signatures.txt
AUTHZ_SIG  = <directory of ABAP_FILE>\_authz_signatures.txt
```

If neither file exists (the upstream gen didn't run RFC, or the ABAP is
hand-written and gen never ran), the validator cannot run — **that is a
coverage gap, not a clean pass**. Do all three, then continue:

1. Tell the user explicitly: `INFO: struct/authz signature caches not found
   — STRUCT_FIELD_MISSING and AUTHORITY-CHECK shape checks SKIPPED (reduced
   coverage). They require /sap-gen-abap Step 1.5 (RFC) to have populated
   _struct_signatures.txt / _authz_signatures.txt in this work folder.`
2. Append one honesty row to `THE_RESULT_FILE` so the written report carries
   the reduced-coverage marker (same 5-column schema as every other row):
   `Code=SIGNATURE_CHECKS_SKIPPED`, `Severity=WARNING`, `Variable=-`,
   `Detail=struct+authz signature caches absent; struct-field and
   AUTHORITY-CHECK shape validation not performed`,
   `Fix Advice=run /sap-gen-abap Step 1.5 (RFC) on this work folder, or
   accept the reduced check scope for hand-written code`.
3. Skip 3.5b/3.5c. The validator is purely additive — its absence doesn't
   break the existing checks, but the verdict line must count this WARNING
   like any other (a PASS with this row present is a *qualified* pass).

### 3.5b — Token-replace and run the validator

Template at `<SKILL_DIR>\references\sap_check_signatures.ps1`.

```powershell
$content = Get-Content '<SKILL_DIR>\references\sap_check_signatures.ps1' -Raw
$content = $content.Replace('%%ABAP_FILE%%',       'THE_ABAP_FILE')
$content = $content.Replace('%%STRUCT_SIG_FILE%%', 'THE_STRUCT_SIG')   # may be '' if absent
$content = $content.Replace('%%AUTHZ_SIG_FILE%%',  'THE_AUTHZ_SIG')    # may be '' if absent
$content = $content.Replace('%%RESULT_FILE%%',     'THE_RESULT_FILE')  # SAME file Step 3 wrote
Set-Content '{RUN_TEMP}\sap_checkabap_signatures.ps1' $content -Encoding UTF8
```

Run via standard PowerShell (no SAP / no NCo — pure file I/O over cached
TSVs):

```bash
powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_checkabap_signatures.ps1"
```

Expected stdout (when caches are present):
```
INFO: Struct cache loaded — 7 struct(s) with field lists, 0 NOT_FOUND.
INFO: AUTHX cache loaded — 3 object(s), 0 NOT_FOUND.
INFO: Checked 142 struct field reference(s); 8 error(s) found.
INFO: Checked 3 AUTHORITY-CHECK statement(s); 1 error(s) found.
INFO: Appended 9 finding(s) to THE_RESULT_FILE
```

### 3.5c — New finding classes added to the report

These are emitted by the signature validator (in addition to the codes Step 3 produces):

| Code | Severity | Meaning |
|---|---|---|
| `STRUCT_FIELD_MISSING` | ERROR | `<var>-<field>` references a field that doesn't exist on `<var>`'s structure type (per live DDIF_FIELDINFO_GET). Fix advice points at SE11 lookup or the correct BAPI parameter (e.g. `marmdata` for MARM-resident fields per §22). |
| `STRUCT_TYPE_MISSING` | ERROR | The struct type itself is in the cache with `NOT_FOUND` — typo or removed-in-this-release. |
| `AUTHZ_OBJECT_MISSING` | ERROR | `AUTHORITY-CHECK OBJECT '<X>'` references an object the SU21 lookup returned `NOT_FOUND` for. |
| `AUTHZ_FIELD_COUNT` | ERROR | The source's ID-clause count doesn't match the SU21 field count. SLIN would flag this as Priority 2 post-deploy; this catches it offline. |
| `AUTHZ_FIELD_NAME` | ERROR | ID-clause names don't match SU21 (missing fields, extra fields, or wrong names). |

Clean up:
```bash
cmd /c del {RUN_TEMP}\sap_checkabap_signatures.ps1
```

---

## Step 3.6 — Spec-coverage check (did the generated code cover the spec?)

**Why:** Steps 3 / 3.5 check that the code is internally well-formed. They do
NOT check that it COVERS the spec it was generated from — a dropped dependency,
a missing error message, or a selection field that never reached the screen all
pass those steps. This step derives the EXPECTED coverage from the spec
extraction files (`/sap-docs-extract` output) and confirms the generator's own
manifest siblings honoured it. It is the user-facing twin of the CI regression
net `scripts/diff-abap-skeleton.mjs`.

It compares (all in the work folder next to the `.abap`):

| Spec file (`<doc>_` prefix) | Generated manifest (`<stem>.` prefix) | Coverage |
|---|---|---|
| `<doc>_deps.txt` | `<stem>.deps.txt` | every spec dependency present |
| `<doc>_errorMsgs.txt` | `<stem>.messages.txt` | every spec message id emitted |
| `<doc>_textElements.txt` | `<stem>.text_elements.txt` `[TEXT_SYMBOLS]` | every spec text symbol present |
| `<doc>_selection_definition.txt` | `[SELECTION_TEXTS]` | field count matches |
| — | `<stem>.traceability.txt` | category rollup (informational) |

### 3.6a — Token-replace and run

Template at `<SKILL_DIR>\references\sap_check_spec_coverage.ps1`.

```powershell
$content = Get-Content '<SKILL_DIR>\references\sap_check_spec_coverage.ps1' -Raw
$content = $content.Replace('%%ABAP_FILE%%',   'THE_ABAP_FILE')
$content = $content.Replace('%%RESULT_FILE%%', 'THE_RESULT_FILE')   # SAME file Step 3 wrote
Set-Content '{RUN_TEMP}\sap_checkabap_speccov.ps1' $content -Encoding UTF8
```

Run via standard PowerShell (no SAP — pure file I/O over the work folder):

```bash
powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_checkabap_speccov.ps1"
```

If the work folder has no `*_deps.txt` / `*_errorMsgs.txt` / `*_textElements.txt`
/ `*_selection_definition.txt` (e.g. hand-written ABAP, or the spec lives
elsewhere), the script prints a single INFO line and appends nothing — the
check is purely additive.

### 3.6b — New finding classes

| Code | Severity | Meaning |
|---|---|---|
| `SPEC_DEP_MISSING` | WARNING | A dependency declared in the spec `_deps.txt` is absent from the generated `.deps.txt`. Best-effort name match. |
| `SPEC_MESSAGE_MISSING` | WARNING | A spec error-message id (`_errorMsgs.txt`) was not emitted in `.messages.txt`. |
| `SPEC_TEXTSYM_MISSING` | WARNING | A spec text element (`_textElements.txt`) is absent from `[TEXT_SYMBOLS]`. |
| `SPEC_SELECTION_COUNT` | WARNING | The spec selection-field count differs from `[SELECTION_TEXTS]`. |
| `SPEC_TRACEABILITY_INFO` | INFO | Category rollup of `.traceability.txt` entries (validation / processing / file-mapping). |

> **Boundary:** this is a STRUCTURAL coverage check (presence + counts). It does
> not verify the logic INSIDE a validation (right field, right operator) — that
> is the live ABAP Unit run (`/sap-run-abap-unit`) on the `_golden.txt` rows.

Clean up:
```bash
cmd /c del {RUN_TEMP}\sap_checkabap_speccov.ps1
```

---

## Step 3.7 — Internal/external conversion checks (CONVEXIT + CURR/QUAN)

**Why:** amounts / quantities and external-key fields convert between internal and
external representation at file boundaries (`GUI_UPLOAD` / `GUI_DOWNLOAD`,
`READ` / `TRANSFER DATASET`). Getting it wrong survives syntax + activation + ATC
and only shows as wrong data at runtime — the class `abap_code_quality_rules.md`
§28 governs. This step flags two such defects offline.

Template at `<SKILL_DIR>\references\sap_check_conversion.ps1`.

```powershell
$content = Get-Content '<SKILL_DIR>\references\sap_check_conversion.ps1' -Raw
$content = $content.Replace('%%ABAP_FILE%%',   'THE_ABAP_FILE')
$content = $content.Replace('%%RESULT_FILE%%', 'THE_RESULT_FILE')   # SAME file Step 3 wrote
Set-Content '{RUN_TEMP}\sap_checkabap_conv.ps1' $content -Encoding UTF8
```

Run via standard PowerShell (no SAP — pure file I/O over the work folder):

```bash
powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_checkabap_conv.ps1"
```

It cross-checks the spec `*_file_mapping_in.txt` / `*_file_mapping_out.txt`
(`/sap-docs-extract`) against `_struct_signatures.txt`. With the 13-column struct
signatures (`DATATYPE` / `CONVEXIT` / `REFTABLE` / `REFFIELD` — present after an RFC
run) the reference check is PRECISE (names the exact missing currency / unit
field); without them it falls back to a coarse program-level check. If neither the
mapping nor the struct cache is present it prints one INFO line and appends
nothing — purely additive.

### 3.7a — New finding classes

| Code | Severity | Meaning |
|---|---|---|
| `CONV_CURR_MISSING_REF` | WARNING | A `CURR` / `QUAN` file column is mapped to a SAP field but its reference currency (`CUKY`) / unit (`UNIT`) field is not — the amount's decimal count is undefined (TCURX-CURRDEC / T006-DECAN). e.g. `VBAP-NETPR` needs `VBAP-WAERK`. |
| `CONV_CURR_DISPLAY_TO_BAPI` | WARNING | `CURRENCY_AMOUNT_DISPLAY_TO_SAP` co-occurs with a BAPI amount type (`BAPICURR` / `BAPICUREXT` / `BAPICURR_D`) — verify the converted *internal* amount is not fed into a BAPI amount field (double-shift, e.g. 100× for JPY). |

Clean up:
```bash
cmd /c del {RUN_TEMP}\sap_checkabap_conv.ps1
```

---

## Step 3.8 — FM Call Validation (`fm` dimension)

**Skip when** the `fm` dimension is disabled (`--dimensions` without `fm`, or
`--offline`), or when SAP is unreachable → append one `FM_COULD_NOT_CHECK`
WARNING row to the report and continue.

Validates every `CALL FUNCTION` in the source against the live FM definition
(parameter names / sections / mandatory / types). Absorbed verbatim from the
former `sap-check-fm`; the checker VBS ships at
`<SKILL_DIR>\references\sap_check_fm.vbs` and delegates RFC to the shared
`sap_rfc_lookup_fm.ps1` (FM signatures, per-system cached) +
`sap_rfc_lookup_ddic.ps1` (DDIC types). Writes findings to a **separate**
`FM_RESULT_FILE` = `<abap-file>.check_fm.tsv` (kept distinct from `.check.tsv`
so parallel runs never collide and Step 4 can present both).

Resolve `{FM_CACHE_DIR}` (`userConfig.fm_cache_dir` or `{work_dir}\cache\fm_signatures`)
and `{SYSTEM_ID}` (`{server}_{sysnr}_{client}`), then generate + run:

```powershell
# FM-signature helper
$h = Get-Content '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_rfc_lookup_fm.ps1' -Raw
$h = $h -replace '%%SAP_SERVER%%','' -replace '%%SAP_SYSNR%%','' -replace '%%SAP_CLIENT%%','' -replace '%%SAP_USER%%','' -replace '%%SAP_PASSWORD%%','' -replace '%%SAP_LANGUAGE%%',''
$h = $h -replace '%%RFC_LIB_PS1%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_rfc_lib.ps1'
$h = $h -replace '%%REQUEST_FILE%%','{RUN_TEMP}\sap_checkfm_fm_names.txt' -replace '%%RESULT_FILE%%','{RUN_TEMP}\sap_checkfm_fm_result.tsv'
$h = $h -replace '%%CACHE_DIR%%','{FM_CACHE_DIR}' -replace '%%SYSTEM_ID%%','{SYSTEM_ID}' -replace '%%TTL_STD_DAYS%%','30' -replace '%%TTL_Z_DAYS%%','1' -replace '%%REFRESH_CACHE%%','false'
Set-Content '{RUN_TEMP}\sap_checkfm_fm_helper.ps1' $h -Encoding UTF8
# DDIC helper
$d = Get-Content '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_rfc_lookup_ddic.ps1' -Raw
$d = $d -replace '%%SAP_SERVER%%','' -replace '%%SAP_SYSNR%%','' -replace '%%SAP_CLIENT%%','' -replace '%%SAP_USER%%','' -replace '%%SAP_PASSWORD%%','' -replace '%%SAP_LANGUAGE%%',''
$d = $d -replace '%%RFC_LIB_PS1%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_rfc_lib.ps1'
$d = $d -replace '%%REQUEST_FILE%%','{RUN_TEMP}\sap_checkfm_ddic_request.txt' -replace '%%RESULT_FILE%%','{RUN_TEMP}\sap_checkfm_ddic_result.tsv'
Set-Content '{RUN_TEMP}\sap_checkfm_ddic_helper.ps1' $d -Encoding UTF8
# Checker VBS
$content = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\sap_check_fm.vbs', [System.Text.Encoding]::UTF8)
$content = $content -replace '%%SAP_SERVER%%','THE_SERVER' -replace '%%SAP_SYSNR%%','THE_SYSNR' -replace '%%SAP_CLIENT%%','THE_CLIENT' -replace '%%SAP_USER%%','THE_USER' -replace '%%SAP_PASSWORD%%','THE_PASSWORD' -replace '%%SAP_LANGUAGE%%','THE_LANGUAGE'
$content = $content -replace '%%ABAP_FILE%%','THE_ABAP_FILE' -replace '%%RESULT_FILE%%','THE_FM_RESULT_FILE'
$content = $content -replace '%%FM_HELPER_PS1%%','{RUN_TEMP}\sap_checkfm_fm_helper.ps1' -replace '%%FM_NAMES_FILE%%','{RUN_TEMP}\sap_checkfm_fm_names.txt' -replace '%%FM_RESULT_FILE%%','{RUN_TEMP}\sap_checkfm_fm_result.tsv'
$content = $content -replace '%%DDIC_HELPER_PS1%%','{RUN_TEMP}\sap_checkfm_ddic_helper.ps1' -replace '%%DDIC_REQUEST_FILE%%','{RUN_TEMP}\sap_checkfm_ddic_request.txt' -replace '%%DDIC_RESULT_FILE%%','{RUN_TEMP}\sap_checkfm_ddic_result.tsv'
[System.IO.File]::WriteAllText('{RUN_TEMP}\sap_checkfm_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true))
```

Run `C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_checkfm_run.vbs`, then
delete the credential-bearing generated scripts. Finding codes (`UNKNOWN_PARAM`,
`WRONG_SECTION`, `MISSING_MANDATORY`, `TYPE_*`, `FM_NOT_FOUND`) are documented in
the Step 4 table.

---

## Step 3.9 — Compiler Syntax Check (`syntax` dimension)

**Skip when** the `syntax` dimension is disabled or `--offline`. This is the
headless equivalent of Ctrl+F2 — it catches real ABAP syntax errors (undeclared
fields, missing periods, typos) that the offline dimensions cannot.

**Applicability gate + mode.** Inspect the source's first non-comment statement and
pick the engine mode:
- `REPORT` / `PROGRAM` → self-contained → `-Subc 1` (no `-Wrap`).
- `FUNCTION` … `ENDFUNCTION` fragment → `-Subc F -Wrap` — best-effort pre-insert
  **body** check; the engine wraps it as a self-contained program and line-maps
  findings back to the original file.
- `CLASS` / `INTERFACE` pool → `-Subc K -Wrap`.
- A bare **include** with no determinable compilation unit → do **not** run; append
  one `SYNTAX_COULD_NOT_CHECK` INFO row (checked in-context by the deploy skill) and
  continue.

Run the shared engine (32-bit PS; writes `<abap-file>.syntax.tsv`). Set `-Subc` per
the gate and add `-Wrap` for the `F`/`K` cases:

```bash
# REPORT/PROGRAM: -Subc 1  (omit -Wrap)  |  FM: -Subc F -Wrap  |  class/interface: -Subc K -Wrap
C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -File "<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_rfc_syntax_check.ps1" -SourceFile "THE_ABAP_FILE" -ProgramName "THE_PROGRAM_NAME" -Subc "<1|F|K>" -Wrap -OutTsv "THE_ABAP_FILE.syntax.tsv"
```

Parse the `STATUS:` line:
- `STATUS: CLEAN` → no syntax findings.
- `STATUS: FINDINGS errors=<e> warnings=<w>` → each `SYNTAX: ERROR/WARN LINE=.. COL=.. MSG=..`
  is a finding (`SYNTAX_ERROR` = ERROR, `SYNTAX_WARNING` = WARNING). Under `-Wrap` the
  `LINE=` values are already mapped to the **original** file — pass them straight to the fix loop.
- `STATUS: COULD_NOT_CHECK <reason>` → the fragment's signature was too complex to model
  faithfully (e.g. a generic/untyped param, or a type the wrap scaffold couldn't compile).
  Append one `SYNTAX_COULD_NOT_CHECK` INFO row with the reason and continue — the deploy
  skill's in-context Ctrl+F2 after an inactive insert is the authoritative check, not a fail.
- `STATUS: RFC_ERROR …` / wrapper not found → append one `SYNTAX_COULD_NOT_CHECK`
  WARNING row (RFC off or wrapper not deployed via `/sap-dev-init`) and continue.

---

## Step 4 — Interpret and Report Results

Read **all** result files produced by the dimensions that ran, and present them
as one report:
- `<abap-file>.check.tsv` — the naming/type/sql/unused/contract/spec/conv dimensions (below).
- `<abap-file>.check_fm.tsv` — the `fm` dimension (Step 3.8), if it ran.
- `<abap-file>.syntax.tsv` — the `syntax` dimension (Step 3.9), if it ran.

The `.check.tsv` file has a header section (STATUS, ABAP_FILE, NAMING_RULES, TIMESTAMP, TOTAL_DECLARATIONS, TOTAL_SQL_STATEMENTS, TOTAL_ISSUES) followed by a column header row and tab-delimited findings.

### Summary table:

| Check Type | Count | Severity |
|---|---|---|
| NAMING | N | WARNING |
| TYPE_NOT_FOUND | N | ERROR |
| TYPE_RESOLVED | N | INFO |
| UNUSED | N | WARNING |
| SQL_TABLE_NOT_FOUND | N | ERROR |
| SQL_FIELD_NOT_FOUND | N | ERROR |

### Finding codes and their meaning:

| Code | Severity | Meaning |
|---|---|---|
| `NAMING` | WARNING | Variable name doesn't follow naming convention — wrong prefix |
| `TYPE_NOT_FOUND` | ERROR | Data type not found in local TYPES or SAP Dictionary |
| `TYPE_RESOLVED` | INFO | Data type successfully resolved (structure or data element) |
| `UNUSED` | WARNING | Variable declared but never referenced in the source |
| `SQL_TABLE_NOT_FOUND` | ERROR | SQL table not found in SAP Dictionary |
| `SQL_FIELD_NOT_FOUND` | ERROR | SQL field not found in the referenced table |
| `SQL_STRICT_COMMA` | ERROR | (Phase 5f) Strict-mode SQL (`@` host variables) with a space-separated field list — the SELECT list must be comma-separated (§9) |
| `METHOD_PARAM_NOT_FOUND` | ERROR | Named parameter in a method call does not exist in the local class's method signature |
| `SELECT_IN_LOOP` | ERROR | `SELECT` inside `LOOP AT itab` — pre-select instead (quality rule §12) |
| `FOR_ALL_ENTRIES_NO_GUARD` | ERROR | `FOR ALL ENTRIES` without `IF lt_keys IS NOT INITIAL` — empty driver reads ALL rows (§12) |
| `MESSAGE_E_IN_METHOD` | ERROR | `MESSAGE e/a/x` inside CLASS method — causes UNCAUGHT_EXCEPTION short dump (§11) |
| `MISSING_AT_HOST_VAR` | WARNING | Open-SQL host variable used without `@` prefix on release ≥ 7.50 (§13) |
| `STRING_CONCAT_SQL` | ERROR | Dynamic SQL via string concatenation without whitelist (§13) |
| `MISSING_AUTHZ_CHECK` | WARNING | Persistence (`UPDATE`/`INSERT`/`MODIFY`/`DELETE`/BAPI write) without preceding `AUTHORITY-CHECK` (§14) |
| `METHOD_TOO_LONG` | WARNING | Method exceeds `MODE_MAX_METHOD_LINES` from brief (default 50) (§18) |
| `OBJECT_NAMING` | WARNING | Top-level object name (program / FORM / global class / method) does not match `sap_object_naming_rules.tsv` |
| `LITERAL_MESSAGE` | ERROR | (Phase 5f) Hard-coded `MESSAGE '...'` / `MESSAGE \|...\|` text — route via a message class (§20). |
| `TEXT_NNN_ASSIGN` | ERROR | (Phase 5f) Assignment to a read-only `TEXT-NNN` symbol; populate via `.text_elements.txt` (§21). |
| `SELECT_STAR` | WARNING | (Phase 5f) `SELECT *` — list only the columns you use (§12). |
| `CLASS_DEF_AFTER_EVENT` | ERROR | (Phase 5f) Global `CLASS … DEFINITION` after an event block — the DECL_ORDER activation bug; move global decls before the event blocks (§10). |
| `LOOP_WHERE_EXIT` | WARNING | (Phase 5f) `LOOP AT … WHERE … EXIT` first-match; use `READ TABLE … TRANSPORTING NO FIELDS` (§19). |
| `SPLIT_INTO_NUMERIC` | ERROR | (Phase 5f) A `SPLIT … INTO` receiver is an elementary numeric variable; text-parse targets must be character-type (§25). |
| `LINE_TOO_LONG` / `LINE_HARD_LIMIT` | WARNING / ERROR | (Phase 5f) Source line > 72 cols (generation style rule) / > 255 cols (hard ABAP limit). |
| `TEXT_SYMBOL_UNDECLARED` | ERROR | (Phase 5f) `TEXT-NNN` referenced but absent from `[TEXT_SYMBOLS]` in the `.text_elements.txt` sibling (§21). Only when the sibling exists. |
| `MESSAGE_NUM_UNDECLARED` | ERROR | (Phase 5f) `MESSAGE eNNN(class)` referenced but absent from the `.messages.txt` sibling (§20). Only when the sibling exists. |
| `SPEC_DEP_MISSING` / `SPEC_MESSAGE_MISSING` / `SPEC_TEXTSYM_MISSING` / `SPEC_SELECTION_COUNT` | WARNING | (Step 3.6) Spec-coverage gaps — a spec dependency / message / text element / selection field is not covered by the generated manifests. |
| `SPEC_TRACEABILITY_INFO` | INFO | (Step 3.6) Category rollup of `.traceability.txt` entries. |
| `STRUCT_FIELD_MISSING` | ERROR | (Step 3.5) `<var>-<field>` references a field not on the variable's structure type, per `_struct_signatures.txt`. Catches BAPI_MARA-style typos before deploy. |
| `STRUCT_TYPE_MISSING` | ERROR | (Step 3.5) Struct type marked `NOT_FOUND` in cache — typo or release-removed. |
| `AUTHZ_OBJECT_MISSING` | ERROR | (Step 3.5) AUTHORITY-CHECK OBJECT name `NOT_FOUND` in SU21 cache. |
| `AUTHZ_FIELD_COUNT` | ERROR | (Step 3.5) AUTHORITY-CHECK ID-clause count ≠ SU21 field count. Pre-empts SLIN P2 "Wrong number of authorization fields". |
| `AUTHZ_FIELD_NAME` | ERROR | (Step 3.5) AUTHORITY-CHECK ID-clause names don't match SU21. Pre-empts SLIN P2 "Authorization field missing". |
| `CONV_CURR_MISSING_REF` | WARNING | (Step 3.7) A CURR/QUAN file column is mapped without its currency (CUKY) / unit (UNIT) reference — decimal count undefined (TCURX-CURRDEC / T006-DECAN) (§28). |
| `CONV_CURR_DISPLAY_TO_BAPI` | WARNING | (Step 3.7) `CURRENCY_AMOUNT_DISPLAY_TO_SAP` co-occurs with a BAPI amount type — double-shift smell; pass external amounts to BAPI fields (§28). |
| `UNKNOWN_PARAM` / `WRONG_SECTION` / `MISSING_MANDATORY` | ERROR | (Step 3.8 `fm`, in `.check_fm.tsv`) `CALL FUNCTION` parameter name unknown / placed under the wrong keyword / mandatory parameter not passed, vs the live FM definition. |
| `TYPE_MATCH` / `TYPE_COMPATIBLE` / `TYPE_WARNING` / `TYPE_INCOMPATIBLE` / `TYPE_UNKNOWN` | INFO / WARNING / ERROR | (Step 3.8 `fm`) Passed-variable type vs FM parameter type (exact / compatible / risky / incompatible / unresolved). |
| `FM_NOT_FOUND` | ERROR | (Step 3.8 `fm`) The called FM does not exist / RFC read failed. |
| `SYNTAX_ERROR` / `SYNTAX_WARNING` | ERROR / WARNING | (Step 3.9 `syntax`, in `.syntax.tsv`) A compiler syntax error / warning at `line:col` from `EDITOR_SYNTAX_CHECK`. |
| `SYNTAX_COULD_NOT_CHECK` | INFO | (Step 3.9) The `syntax` dimension could not produce a reliable verdict — a bare include (no compilation unit), or an FM/class fragment whose signature the `-Wrap` scaffold could not model (generic/untyped param, or a type that would not compile). Deferred to the deploy skill's in-context Ctrl+F2 after an inactive insert — **not** a pass, **not** a fail. |
| `FM_COULD_NOT_CHECK` / `SYNTAX_COULD_NOT_CHECK` (RFC-off) | WARNING | (Step 3.8 / 3.9) The `fm` / `syntax` dimension could not reach SAP (RFC off, or the dev-init wrapper is absent). Honest COULD_NOT_CHECK — not a pass. |

### Detail — show all WARNING and ERROR findings:

For each finding, show: Line, Variable, Detail, Fix Advice.

**On `STATUS: SUCCESS`** — all declarations pass all checks. Congratulate the user.

**On `STATUS: SUCCESS_WITH_ISSUES`** — show findings grouped by severity (ERROR first, then WARNING). Suggest:
> "Run `/sap-fix-abap <file> <result-file>` to apply automatic fixes."

**On `STATUS: ERROR`:**

| Error message | Cause | Fix |
|---|---|---|
| `Cannot create SAP.Functions` | SAP GUI not installed or OCX not registered | Install SAP GUI; verify `wdtfuncs.ocx` |
| `RFC logon failed` | Wrong server/credentials | Verify connection parameters |
| `ABAP file not found` | Wrong path | Verify the file path |
| `Naming rules file not found` | Missing `sap-dev-core/shared/tables/abap_naming_rules.tsv` | Verify sap-dev-core plugin is installed |

---

## Step 5 — Clean Up

```bash
cmd /c del {RUN_TEMP}\sap_checkabap_run.ps1
cmd /c del {RUN_TEMP}\sap_checkabap_conv.ps1
```

Keep the result TSV so the user can review it or pass it to `sap-fix-abap`. To remove:
```bash
cmd /c del "THE_RESULT_FILE"
```

---

## Final — Log End

Log the run-end record. Best-effort.

On success:

```bash
powershell -ExecutionPolicy Bypass -File "<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_log_helper.ps1" -Action end -StateFile "{RUN_TEMP}\sap_check_abap_run.json" -Status SUCCESS -ExitCode 0 -MetricsJson '{"gate":"CHECK","verdict":"PASS","errors":0,"warnings":0}'
```

**Build-KPI enrichment (best-effort).** Add `-MetricsJson` populated from the
`.check.tsv` summary: `verdict` is `PASS` when the ERROR-severity count is 0
else `FAIL`; `errors`/`warnings` are the severity counts. The offline aggregator
(`shared/rules/build_metrics.md`) reads it for `gen_first_pass_pct` and derives
the per-build fix-iteration count by counting how many CHECK end-records appear
in the build cluster — so you do not report iterations here. Best-effort: omit
if you cannot read the counts.

On failure:

```bash
powershell -ExecutionPolicy Bypass -File "<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_log_helper.ps1" -Action end -StateFile "{RUN_TEMP}\sap_check_abap_run.json" -Status FAILED -ExitCode 1 -ErrorClass <CLASS> -ErrorMsg "<short>"
```

Suggested `<CLASS>`: `CHECK_ABAP_FAILED`, `RFC_LOGON_FAILED`.

---

## Security Note

The generated `.vbs` file contains the SAP password in plain text and is deleted automatically after execution.
Connection parameters are stored in sap-dev-core settings.json. The password field is marked as
`sensitive` and masked in the Claude Code UI.

---

## ABAP Parsing Limitations

- **Local classes**: Variables declared inside `CLASS ... IMPLEMENTATION` / `ENDCLASS` blocks are **not currently validated** for naming or type checks. This is a known gap — code generated by sap-gen-abap that uses local classes should be manually reviewed for type correctness before deployment.
- **Scope tracking**: FORM/ENDFORM, METHOD/ENDMETHOD, FUNCTION/ENDFUNCTION only. Nested scopes (IF/LOOP etc.) do not affect scope detection.
- **Chain declarations**: Handled for DATA, TYPES, CONSTANTS, FIELD-SYMBOLS.
- **FORM parameters**: Parsed from the FORM line only. Multi-line FORM signatures may miss parameters after the first continuation line.
- **Structure vs. scalar**: Without SAP connection, cannot distinguish STRUCTURE from VARIABLE for SAP-standard types. Local TYPES (BEGIN OF / TYPES tt_x TYPE TABLE OF ...) are tracked and propagated to variables typed by them, so naming-rule checks honour the right kind even offline.
- **TYPES declarations**: Local TYPES are tracked as valid types but are not checked for naming conventions by default. Users can add TYPES rules to the naming file.
- **Selection screen**: PARAMETERS and SELECT-OPTIONS are detected. SELECTION-SCREEN BEGIN OF BLOCK, etc. are ignored.
- **Inline DATA()**: Inline declarations like `DATA(lv_x) = ...`, `READ TABLE … INTO DATA(ls_row)`, `LOOP AT … INTO DATA(ls_row)`, `SELECT … INTO TABLE @DATA(lt_data)` are scanned and added to the declaration table with type unknown. They are NOT subject to TYPE_NOT_FOUND (no type to check) and NOT subject to UNUSED (the inline-declaration line IS the first-use line). Naming-rule checks still apply.
- **Table-field type syntax**: `TYPE mara-matnr`, `TYPE rlgrap-filename`, etc. are accepted silently — the type validator skips them rather than emitting a false TYPE_NOT_FOUND. A future enhancement may split on `-` and validate the field via DD03L.
- **DDIC table types / domains / classes**: `DATA lt_x TYPE <ddic_table_type>.` — e.g. `TYPE filetable`, the standard table type used with `CL_GUI_FRONTEND_SERVICES=>gui_upload` — resolves correctly in online mode. The sidecar helper (`sap_rfc_lookup_ddic.ps1`) classifies the name via DD40L and returns kind `TTYP`; the checker treats it as a known type (no `TYPE_NOT_FOUND`) and reclassifies the variable to an internal table, so `lt_`/`gt_` is the expected naming prefix (not `lv_`). Domains (DD01L → `DOMAIN`) and global classes/interfaces (SEOCLASS → `CLASS`, via `TYPE REF TO`) resolve the same way. **Regression fixed 2026-07-03** (class 4 of the known false-positive set): the helper already queried DD40L, but the VBS consumer mapped only `STRUCT`/`DTEL` and folded every other resolved kind to `UNKNOWN`, producing a false `TYPE_NOT_FOUND` ERROR plus a false `NAMING` WARNING. Repro that must stay clean: `DATA lt_files TYPE filetable.`. Note: this resolution needs an SAP connection — in **offline mode** Phase 5b (type validity) is skipped entirely, so the false positive never manifested offline.
- **SQL validation**: SELECT, UPDATE, DELETE statements are parsed. JOINs with aliases are supported. Aggregate functions (COUNT, SUM, etc.) skip field checking. Dynamic SQL, CDS views, subqueries, UNION, GROUP BY/ORDER BY/HAVING field validation are not supported. Only field existence is checked, not INTO-variable type compatibility. Requires SAP connection (skipped in offline mode).
- **Custom Z-tables in SQL**: If the ABAP code references custom Z-tables (e.g., `ZHKFIXEDVALS`), the SQL check will report `SQL_TABLE_NOT_FOUND` until the table is created in SAP. This is expected behavior — create the table via sap-se11 first, then re-run the check.
- **MESSAGE e/a/x in local class methods**: `MESSAGE` types E, A, X inside `CLASS ... IMPLEMENTATION` methods will cause `UNCAUGHT_EXCEPTION` at runtime. Replace with exception classes or result table pattern. Flag these as potential short dump causes during static analysis.
- **Custom types in BAPI TABLES parameters**: When a `CALL FUNCTION` for a BAPI uses a local table typed differently from the BAPI's expected structure, flag as potential `CALL_FUNCTION_CONFLICT_LENG`. Example: `DATA lt TYPE TABLE OF ty_custom` passed to `materialdescription` which expects `bapi_makt`. Always use the exact BAPI structure type for TABLES and EXPORTING/IMPORTING parameters.
- **GUI_UPLOAD `has_field_separator` with flat char tables**: `has_field_separator = 'X'` with `TABLE OF charNNNN` (single-field flat table) strips tab delimiters and only reads the first column. Remove `has_field_separator` when using flat char tables and parse tab separators manually with `SPLIT`.

---

## Pipeline Integration

This skill is part of the ABAP quality pipeline:

1. **sap-gen-abap** — generates ABAP source code from design documents
2. **sap-check-abap** ← you are here — offline + RFC validation (naming, types, SQL fields, basic patterns)
3. **sap-atc** — in-system SCI / ATC run, priority-gated via the customer brief's `MAX_PRIORITY`
4. **sap-fix-abap** — applies automatic fixes from check results
5. **abap-deploy** — deploys to SAP system (sap-se38 / sap-se37 / sap-se24 / sap-se11 / sap-se91)

Files in this skill

  • README.md1.9 KB
  • SKILL.md22.8 KB
  • references/sap_check_abap.vbs81.3 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.