abapgit-agent 1.16.3 → 1.17.1

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/abap/CLAUDE.md CHANGED
@@ -123,6 +123,7 @@ Use the object name from `objects.local.md` (or `objects.md` as fallback) in pla
123
123
  | CDS View (DDLS) | `<name>.ddls.asddls` | `<name>.ddls.xml` |
124
124
  | CDS Access Control (DCLS) | `<name>.dcls.asdcls` | `<name>.dcls.xml` |
125
125
  | Function Group (FUGR) | `<name>.fugr.abap` + includes | `<name>.fugr.xml` |
126
+ | Enhancement (ENHO) | `<name>.enho.<hash>.abap` (one per hook) | `<name>.enho.xml` |
126
127
  | Table (TABL) | *(none)* | `<name>.tabl.xml` |
127
128
  | Structure (STRU) | *(none)* | `<name>.tabl.xml` ⚠️ NOT `.stru.xml` |
128
129
  | Data Element (DTEL) | *(none)* | `<name>.dtel.xml` |
@@ -786,6 +787,11 @@ Modified ABAP files?
786
787
  ├─ DCLS (CDS access control)?
787
788
  │ └─ ✅ Use: skip syntax → [abaplint] → commit → push → pull --files abap/zc_view.dcls.xml --sync-xml
788
789
  │ ⚠️ Pass the .xml file — pull --files does NOT accept .asdcls extensions
790
+ ├─ ENHO (Enhancement)?
791
+ │ └─ ✅ Use: syntax (optional) → [abaplint] → commit → push → pull --files abap/<name>.enho.xml --sync-xml
792
+ │ ⚠️ syntax checks basic errors in the hook body; semantic checks require pull
793
+ │ ⚠️ Pass the .enho.xml file to pull — hash .abap files also work but xml is preferred
794
+ │ → see: abapgit-agent ref --topic enho
789
795
  └─ Other complex objects?
790
796
  └─ ✅ Use: skip syntax → [abaplint] → commit → push → pull --sync-xml → (if errors: inspect)
791
797
 
@@ -818,6 +824,7 @@ Modified ABAP files?
818
824
  | `ref --topic abapgit` | abapGit XML Metadata — **use for CDS/DDLS XML**, also CLAS, INTF, PROG, DCLS, FUGR |
819
825
  | `ref --topic abapgit-xml-only` | abapGit XML Metadata — XML-only objects (TABL, STRU, DTEL, TTYP, DOMA, MSAG) |
820
826
  | `ref --topic abapgit-fugr` | abapGit XML Metadata — Function Group (FUGR) details |
827
+ | `ref --topic enho` | Enhancement Objects (ENHO) — workflow, hash algorithm, creation guide |
821
828
  | `ref --topic abaplint` | abaplint Rule Guidelines (prefer_inline trap, safe patterns) |
822
829
  | `ref --topic debug-session` | Debug Session Guide |
823
830
  | `ref --topic debug-dump` | Dump Analysis Guide |
@@ -42,3 +42,7 @@ The `ref` command uses bundled guidelines automatically — no local `guidelines
42
42
  ## Project-Specific Naming Conventions
43
43
 
44
44
  See `guidelines/objects.local.md` for this project's naming convention overrides.
45
+
46
+ ## CLI Commands
47
+
48
+ Run `abapgit-agent --help` to see all available commands (pull, customize, transport, preview, and more).
@@ -22,7 +22,8 @@ Interface | zif_*.intf.abap | zif_*.intf.xml
22
22
  Program | z*.prog.abap | z*.prog.xml
23
23
  CDS View (DDLS) | zc_*.ddls.asddls | zc_*.ddls.xml
24
24
  CDS Access Control | zc_*.dcls.asdcls | zc_*.dcls.xml
25
- Function Group | z*fugr*.abap (6 files) | z*.fugr.xml
25
+ Function Group | z*fugr*.abap (1 per FM + includes) | z*.fugr.xml
26
+ Enhancement (ENHO) | z*.enho.<hash>.abap (N files)| z*.enho.xml
26
27
  ```
27
28
 
28
29
  > **XML-only objects** (TABL, STRU, DTEL, TTYP, DOMA, MSAG) have **no ABAP source file** and are not covered here.
@@ -302,6 +303,16 @@ FUGR serializes as multiple files (main XML + TOP include + SAPL include + one s
302
303
 
303
304
  ---
304
305
 
306
+ ### Enhancement (ENHO)
307
+
308
+ **Files**: `src/<name>.enho.xml` + `src/<name>.enho.<hash>.abap` (one per hook)
309
+
310
+ ENHO is a rare, specialized object type. For the full XML template, hash algorithm, and workflow:
311
+
312
+ → `abapgit-agent ref --topic enho`
313
+
314
+ ---
315
+
305
316
  ## Important Notes
306
317
 
307
318
  1. **ALWAYS push to git BEFORE running pull** - abapGit reads from git
@@ -42,6 +42,27 @@ The output varies by section type:
42
42
 
43
43
  The hint already points to the first **executable** line, skipping `METHOD`, blank lines, comments, and all declaration forms (`DATA`, `DATA:`, `DATA(`).
44
44
 
45
+ **Classes with active ENHO hook enhancements** — `view --full --lines` injects the ENHO hook lines into the CM section so you can see the runtime code layout. The `G` numbers for injected lines are shown for reference, but the BP hint uses **base-source coordinates** (subtracting the injected ENHO lines) because ADT validates breakpoints against the non-injected `/source/main`:
46
+
47
+ ```
48
+ * ---- Method: COMPUTE (CM001) — breakpoint: debug set --objects ZCL_CAIS_DBG_TRIGGER:33 ----
49
+ 29 [ 1] METHOD compute.
50
+ 30 [ 2] *"* ENHO: ZCAIS_DBG_ENHO (BEGIN)
51
+ 31 [ 3] DATA lv_enho_marker TYPE i.
52
+ 32 [ 4] lv_enho_marker = iv_a * iv_b.
53
+ 33 [ 5] *"* ENHO END
54
+ 34 [ 6] " compute: add two integers...
55
+ 35 [ 7] DATA lv_sum TYPE i.
56
+ 36 [ 8]
57
+ 37 [ 9] lv_sum = iv_a + iv_b. ← lives at G=37 in runtime layout
58
+ 38 [ 10] rv_result = lv_sum.
59
+ 39 [ 11] ENDMETHOD.
60
+ ```
61
+
62
+ The first executable original line is `lv_sum = iv_a + iv_b.` at runtime G=37, but its position in ADT's base source (without the 4 injected ENHO lines) is G=33 (37 − 4). The hint says `:33`, not `:37`. ADT accepts `:33` and the breakpoint fires correctly inside the ENHO-affected method.
63
+
64
+ > **ENHO body lines (G 30–32 in the example) cannot be used as breakpoints.** Setting a BP at those G numbers returns "Not registered on server" — ADT's base source has no ENHO lines. The BP hint already points past the ENHO block to the first original executable line.
65
+
45
66
  **Two scenarios for CM* methods:**
46
67
 
47
68
  | Scenario | Command |
@@ -0,0 +1,193 @@
1
+ ---
2
+ layout: default
3
+ title: Enhancement Objects (ENHO)
4
+ nav_order: 15
5
+ parent: ABAP Coding Guidelines
6
+ grand_parent: ABAP Development
7
+ ---
8
+
9
+ # Enhancement Objects (ENHO) — Workflow Guide
10
+
11
+ ENHO (Hook Enhancements) let you inject ABAP code into specific spots of existing methods without modifying the original class. Use them sparingly — only when you cannot subclass or use dependency injection.
12
+
13
+ **Searchable keywords**: enho, enhancement, hook, hook implementation, enhancements, hook_impl
14
+
15
+ ---
16
+
17
+ ## Creating Brand-New ENHOs
18
+
19
+ **abapGit pull can create brand-new ENHOs headlessly** — write the XML and ABAP files, push, and pull as normal.
20
+
21
+ This works because abapGit's ENHO hook deserializer passes `dark = abap_true` to `cl_enh_factory=>create_enhancement`, which suppresses the Modification Assistant dialog (`SAPLSTRD 0353`) that would otherwise require a GUI session. The `save()` call also uses `run_dark = abap_true`.
22
+
23
+ ### Workflow for new ENHOs
24
+
25
+ 1. Write `<name>.enho.xml` (see XML Template section below)
26
+ 2. For each hook: compute `hash = SHA1(full_name)[0:8]` → write `<name>.enho.<hash>.abap`
27
+ 3. Commit, push, then pull:
28
+ ```bash
29
+ abapgit-agent pull --files src/<name>.enho.xml --sync-xml
30
+ ```
31
+
32
+ The `--sync-xml` flag is important for new objects — abapGit's serializer may rewrite the XML during the first pull.
33
+
34
+ Once the ENHO exists in the system, abapGit's full-replace deserialization (delete + recreate) works correctly via REST.
35
+
36
+ ---
37
+
38
+ ## File Naming Pattern
39
+
40
+ An ENHO object produces **multiple** files:
41
+
42
+ ```
43
+ <name>.enho.xml ← object metadata (describes all hooks)
44
+ <name>.enho.<hash>.abap ← hook source code (one file per hook)
45
+ <name>.enho.<hash2>.abap ← second hook source (if multiple hooks)
46
+ ```
47
+
48
+ ### Hash Computation
49
+
50
+ The `<hash>` is the first 8 hex characters of SHA1(`full_name`):
51
+
52
+ ```js
53
+ // Node.js
54
+ const crypto = require('crypto');
55
+ const fullName = '\\TY:ZCL_TARGET_CLASS\\ME:GET_VALUE\\SE:BEGIN\\EI';
56
+ const hash = crypto.createHash('sha1').update(fullName).digest('hex').substring(0, 8);
57
+ // → 'd639f45c'
58
+ ```
59
+
60
+ ### `full_name` Format
61
+
62
+ ```
63
+ \TY:<CLASS>\ME:<METHOD>\SE:<SECTION>\EI
64
+ ```
65
+
66
+ | Part | Value | Meaning |
67
+ |------|-------|---------|
68
+ | `\TY:` | class name (uppercase) | Target class |
69
+ | `\IN:` | interface name (uppercase) | Interface (only when method is from an interface) |
70
+ | `\ME:` | method name (uppercase) | Target method |
71
+ | `\SE:` | `BEGIN` or `END` | Hook point |
72
+ | `\EI` | *(fixed suffix)* | End of identifier |
73
+
74
+ Examples:
75
+ ```
76
+ \TY:ZCL_TARGET\ME:GET_VALUE\SE:BEGIN\EI
77
+ \TY:CL_MPC_EXT\IN:/IWBEP/IF_MGW_APPL_SRV_RUNTIME\ME:GET_ENTITYSET\SE:BEGIN\EI
78
+ ```
79
+
80
+ The `full_name` and its hash are stored in the XML `<FILES>/<item>/<FILE>` element — read them directly from the `.enho.xml` file when the ENHO already exists in git.
81
+
82
+ ---
83
+
84
+ ## Pull Behavior
85
+
86
+ ENHO deserialization is **full replace**: abapGit deletes the entire ENHO object and recreates all its hooks from git. This is the same as for classes and interfaces.
87
+
88
+ - Passing the `.enho.xml` file targets the whole ENHO (preferred)
89
+ - Passing any `.enho.<hash>.abap` file also targets the whole ENHO (resolves to same object)
90
+ - **Other ENHO objects are NOT affected** — the object filter is at the ENHO name level
91
+
92
+ ```bash
93
+ # Both commands activate the whole ZFOO_ENH (all hooks)
94
+ abapgit-agent pull --files src/zfoo_enh.enho.xml
95
+ abapgit-agent pull --files src/zfoo_enh.enho.d639f45c.abap
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Workflow — Editing an Existing ENHO
101
+
102
+ When an ENHO already exists in both git and the ABAP system:
103
+
104
+ ```bash
105
+ # Edit the hook source
106
+ vi src/zfoo_enh.enho.d639f45c.abap
107
+
108
+ # Syntax check (optional — catches basic errors before commit)
109
+ abapgit-agent syntax --files src/zfoo_enh.enho.d639f45c.abap
110
+
111
+ # Commit, push, pull
112
+ git add src/zfoo_enh.enho.d639f45c.abap
113
+ git commit -m "fix: update enhancement hook"
114
+ git push
115
+ abapgit-agent pull --files src/zfoo_enh.enho.xml --sync-xml
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Note on Syntax Command
121
+
122
+ The `syntax` command supports ENHO `.enho.<hash>.abap` files. It extracts the code between `ENHANCEMENT`/`ENDENHANCEMENT`, wraps it in a minimal program skeleton, and runs `SYNTAX-CHECK` with the target class pool's uccheck/unicode settings.
123
+
124
+ ```bash
125
+ abapgit-agent syntax --files src/zfoo_enh.enho.d639f45c.abap
126
+ ```
127
+
128
+ **Limitation**: Because the code is checked outside the real class pool context, instance variables and class-specific types from the target class are not visible. The check catches basic syntax errors (undefined keywords, missing periods, etc.) but not semantic errors like unknown field names from the enhanced class.
129
+
130
+ ---
131
+
132
+ ## XML Template
133
+
134
+ This is the format abapGit actually serializes. Use a real abapGit export as the source of truth — copy from an existing ENHO in your project.
135
+
136
+ ```xml
137
+ <?xml version="1.0" encoding="utf-8"?>
138
+ <abapGit version="v1.0.0" serializer="LCL_OBJECT_ENHO" serializer_version="v1.0.0">
139
+ <asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
140
+ <asx:values>
141
+ <TOOL>HOOK_IMPL</TOOL>
142
+ <SHORTTEXT>My enhancement description</SHORTTEXT>
143
+ <ORIGINAL_OBJECT>
144
+ <PGMID>R3TR</PGMID>
145
+ <ORG_OBJ_TYPE>CLAS</ORG_OBJ_TYPE>
146
+ <ORG_OBJ_NAME>ZCL_TARGET_CLASS</ORG_OBJ_NAME>
147
+ <ORG_MAIN_TYPE>CLAS</ORG_MAIN_TYPE>
148
+ <ORG_MAIN_NAME>ZCL_TARGET_CLASS</ORG_MAIN_NAME>
149
+ <PROGRAMNAME>ZCL_TARGET_CLASS================CP</PROGRAMNAME>
150
+ </ORIGINAL_OBJECT>
151
+ <ENHANCEMENTS>
152
+ <ENH_HOOK_IMPL>
153
+ <PROGRAMNAME>ZCL_TARGET_CLASS================CP</PROGRAMNAME>
154
+ <ENHMODE>D</ENHMODE>
155
+ <FULL_NAME>\TY:ZCL_TARGET_CLASS\ME:GET_VALUE\SE:BEGIN\EI</FULL_NAME>
156
+ </ENH_HOOK_IMPL>
157
+ </ENHANCEMENTS>
158
+ <FILES>
159
+ <item>
160
+ <NAME>\TY:ZCL_TARGET_CLASS\ME:GET_VALUE\SE:BEGIN\EI</NAME>
161
+ <FILE>d639f45c</FILE>
162
+ </item>
163
+ </FILES>
164
+ </asx:values>
165
+ </asx:abap>
166
+ </abapGit>
167
+ ```
168
+
169
+ Key elements:
170
+ - `<TOOL>HOOK_IMPL</TOOL>` — always `HOOK_IMPL` for hook enhancements
171
+ - `<SHORTTEXT>` — short description text
172
+ - `<ORIGINAL_OBJECT>` — the class being enhanced
173
+ - `<PROGRAMNAME>` — class pool name: classname padded to 30 chars with `=`, then `CP` (total 32 chars)
174
+ - Example: `ZCL_MY_CLASS` (12 chars) → `ZCL_MY_CLASS==================CP`
175
+ - `<ENHANCEMENTS>/<ENH_HOOK_IMPL>/<FULL_NAME>` — the full_name identifier
176
+ - `<ENHMODE>D` — Draft mode (standard value)
177
+ - `<FILES>/<item>/<NAME>` — same full_name as in ENHANCEMENTS
178
+ - `<FILES>/<item>/<FILE>` — first 8 hex chars of SHA1(full_name)
179
+
180
+ For multiple hooks: add additional `<ENH_HOOK_IMPL>` blocks in `<ENHANCEMENTS>` and additional `<item>` blocks in `<FILES>`.
181
+
182
+ ### Hash file format (`.enho.<hash>.abap`)
183
+
184
+ ```abap
185
+ "Name: \TY:ZCL_TARGET_CLASS\ME:GET_VALUE\SE:BEGIN\EI
186
+ ENHANCEMENT 0 ZFOO_ENH.
187
+ " Your ABAP code goes here
188
+ DATA lv_result TYPE string.
189
+ lv_result = 'modified'.
190
+ ENDENHANCEMENT.
191
+ ```
192
+
193
+ The first line is a comment with the `full_name`. `ENHANCEMENT 0 <ENHNAME>.` and `ENDENHANCEMENT.` are the wrapper statements — the actual code goes between them.
@@ -22,7 +22,11 @@ This folder contains detailed ABAP coding guidelines that can be searched using
22
22
  | `objects.md` | Object Naming Conventions |
23
23
  | `naming-limits.md` | Naming Length Limits (30/16/40 char rules per type) |
24
24
  | `json.md` | JSON Handling |
25
- | `abapgit.md` | abapGit XML Metadata Templates |
25
+ | `string-template.md` | String Templates syntax, escaping `\{` `\}`, JSON payloads |
26
+ | `abapgit.md` | abapGit XML Metadata — CLAS, INTF, PROG, CDS/DDLS, DCLS, FUGR |
27
+ | `abapgit-xml-only.md` | abapGit XML Metadata — XML-only objects (TABL, STRU, DTEL, TTYP, DOMA, MSAG) |
28
+ | `abapgit-fugr.md` | abapGit XML Metadata — Function Group (FUGR) details |
29
+ | `enho.md` | Enhancement Objects (ENHO) — workflow, hash algorithm, creation guide |
26
30
  | `unit-testable-code.md` | Unit Testable Code Guidelines (Dependency Injection) |
27
31
  | `common-errors.md` | Common ABAP Errors - Quick Fixes |
28
32
  | `debug-session.md` | Debug Session Guide |
@@ -33,6 +37,8 @@ This folder contains detailed ABAP coding guidelines that can be searched using
33
37
  | `cds-testing.md` | CDS Testing (Test Double Framework) |
34
38
  | `abaplint.md` | abaplint Rule Guidelines (prefer_inline trap, safe patterns) |
35
39
  | `comments.md` | Documentation Comments (ABAP DOC, shorttext, @parameter, CDS //) |
40
+ | `run-probe-classes.md` | run Command — AI Guidelines (probe classes, scratchWorkspace) |
41
+ | `probe-poc.md` | Probe and PoC — Full Decision Flow |
36
42
 
37
43
  ## Usage
38
44
 
@@ -23,6 +23,7 @@ Replace `<name>` with the actual object name from this project's naming conventi
23
23
  | CDS View Entity (DDLS) | `<name>.ddls.asddls` | `<name>.ddls.xml` | **Use by default** — `ref --topic abapgit` |
24
24
  | CDS Access Control (DCLS) | `<name>.dcls.asdcls` | `<name>.dcls.xml` | `ref --topic abapgit` |
25
25
  | Function Group (FUGR) | `<name>.fugr.abap` + includes | `<name>.fugr.xml` | `ref --topic abapgit` |
26
+ | Enhancement (ENHO) | `<name>.enho.<hash>.abap` (one per hook) | `<name>.enho.xml` | `ref --topic enho` |
26
27
  | Table (TABL) | *(none — XML-only)* | `<name>.tabl.xml` | `ref --topic abapgit-xml-only` |
27
28
  | Structure (STRU) | *(none — XML-only)* | `<name>.tabl.xml` ⚠️ NOT `.stru.xml` | `ref --topic abapgit-xml-only` |
28
29
  | Data Element (DTEL) | *(none — XML-only)* | `<name>.dtel.xml` | `ref --topic abapgit-xml-only` |
@@ -18,9 +18,9 @@ grand_parent: ABAP Development
18
18
  3. Write code → place in correct folder (e.g., src/zcl_*.clas.abap)
19
19
 
20
20
 
21
- 4. Syntax check (for CLAS, INTF, PROG, DDLS only)
21
+ 4. Syntax check (for CLAS, INTF, PROG, DDLS, ENHO only)
22
22
 
23
- ├─► CLAS/INTF/PROG/DDLS → abapgit-agent syntax --files <file>
23
+ ├─► CLAS/INTF/PROG/DDLS/ENHO → abapgit-agent syntax --files <file>
24
24
  │ │
25
25
  │ ├─► Errors? → Fix locally (no commit needed), re-run syntax
26
26
  │ │
@@ -31,7 +31,7 @@ grand_parent: ABAP Development
31
31
 
32
32
  4b. abaplint (OPTIONAL — only if .abaplint.json exists in repo root)
33
33
 
34
- ├─► .abaplint.json exists → npx @abaplint/cli .abaplint.json
34
+ ├─► .abaplint.json exists → abapgit-agent lint
35
35
  │ │
36
36
  │ ├─► Issues? → Fix locally, re-run abaplint
37
37
  │ │ ⚠️ Before applying any quickfix: run abapgit-agent ref --topic abaplint
@@ -45,7 +45,7 @@ grand_parent: ABAP Development
45
45
  5. Commit and push → git add . && git commit && git push
46
46
 
47
47
 
48
- 6. Activate → abapgit-agent pull --files src/file.clas.abap
48
+ 6. Activate → abapgit-agent pull --files src/file.clas.abap --sync-xml
49
49
  │ (behaviour depends on .abapgit-agent.json — see AI Tool Guidelines)
50
50
 
51
51
 
@@ -67,6 +67,7 @@ grand_parent: ABAP Development
67
67
  | INTF (interfaces) | ✅ Supported | Run `syntax` before commit |
68
68
  | PROG (programs) | ✅ Supported | Run `syntax` before commit |
69
69
  | DDLS (CDS views) | ✅ Supported | Run `syntax` before commit (requires annotations) |
70
+ | ENHO (enhancements) | ✅ Supported | Run `syntax` before commit (basic errors only — no target class context) |
70
71
  | FUGR (function groups) | ❌ Not supported | Skip syntax, use `pull` then `inspect` |
71
72
  | TABL/DTEL/DOMA/MSAG/SHLP | ❌ Not supported | Skip syntax, just `pull` |
72
73
  | All other types | ❌ Not supported | Skip syntax, just `pull` |
@@ -90,7 +91,7 @@ abapgit-agent syntax --files src/zif_my_interface.intf.abap # ✅ Works (no dep
90
91
  git add src/zif_my_interface.intf.abap src/zif_my_interface.intf.xml
91
92
  git commit -m "feat: add interface"
92
93
  git push
93
- abapgit-agent pull --files src/zif_my_interface.intf.abap # Interface now activated
94
+ abapgit-agent pull --files src/zif_my_interface.intf.abap --sync-xml # Interface now activated
94
95
 
95
96
  # Step 2: Create class, syntax check, commit, activate
96
97
  vim src/zcl_my_class.clas.abap
@@ -98,7 +99,7 @@ abapgit-agent syntax --files src/zcl_my_class.clas.abap # ✅ Works (interfa
98
99
  git add src/zcl_my_class.clas.abap src/zcl_my_class.clas.xml
99
100
  git commit -m "feat: add class implementing interface"
100
101
  git push
101
- abapgit-agent pull --files src/zcl_my_class.clas.abap
102
+ abapgit-agent pull --files src/zcl_my_class.clas.abap --sync-xml
102
103
  ```
103
104
 
104
105
  **Benefits:**
@@ -123,7 +124,7 @@ git commit -m "feat: add interface and implementing class"
123
124
  git push
124
125
 
125
126
  # Pull both together
126
- abapgit-agent pull --files src/zif_my_interface.intf.abap,src/zcl_my_class.clas.abap
127
+ abapgit-agent pull --files src/zif_my_interface.intf.abap,src/zcl_my_class.clas.abap --sync-xml
127
128
 
128
129
  # Use inspect if errors occur
129
130
  abapgit-agent inspect --files src/zcl_my_class.clas.abap
@@ -150,7 +151,7 @@ git commit -m "feat: add class and CDS view"
150
151
  git push
151
152
 
152
153
  # Pull all files together
153
- abapgit-agent pull --files src/zcl_my_class.clas.abap,src/zc_my_view.ddls.asddls
154
+ abapgit-agent pull --files src/zcl_my_class.clas.abap,src/zc_my_view.ddls.asddls --sync-xml
154
155
  ```
155
156
 
156
157
  **When to use syntax vs abaplint vs inspect vs view**:
@@ -169,6 +170,7 @@ abapgit-agent pull --files src/zcl_my_class.clas.abap,src/zc_my_view.ddls.asddls
169
170
  ├─ .intf.abap → INTF ✅ syntax supported
170
171
  ├─ .prog.abap → PROG ✅ syntax supported
171
172
  ├─ .ddls.asddls → DDLS ✅ syntax supported (requires proper annotations)
173
+ ├─ .enho.<hash>.abap → ENHO ✅ syntax supported (basic errors; no target class context)
172
174
  └─ All other extensions → ❌ syntax not supported
173
175
 
174
176
  2. Check for dependencies:
@@ -177,25 +179,25 @@ abapgit-agent pull --files src/zcl_my_class.clas.abap,src/zc_my_view.ddls.asddls
177
179
  ├─ CDS view uses table? → INDEPENDENT (table already exists)
178
180
  └─ Unrelated bug fixes across files? → INDEPENDENT
179
181
 
180
- 3. For SUPPORTED types (CLAS/INTF/PROG/DDLS):
181
- ├─ INDEPENDENT files → Run syntax → [abaplint if enabled] → Fix errors → Commit → Push → Pull
182
+ 3. For SUPPORTED types (CLAS/INTF/PROG/DDLS/ENHO):
183
+ ├─ INDEPENDENT files → Run syntax → [abaplint if enabled] → Fix errors → Commit → Push → Pull --sync-xml
182
184
 
183
185
  └─ DEPENDENT files (NEW objects):
184
186
  ├─ RECOMMENDED: Create underlying object first (interface, base class, etc.)
185
- │ 1. Create underlying object → Syntax → [abaplint] → Commit → Push → Pull
186
- │ 2. Create dependent object → Syntax (works!) → [abaplint] → Commit → Push → Pull
187
+ │ 1. Create underlying object → Syntax → [abaplint] → Commit → Push → Pull --sync-xml
188
+ │ 2. Create dependent object → Syntax (works!) → [abaplint] → Commit → Push → Pull --sync-xml
187
189
  │ ✅ Benefits: Both syntax checks work, cleaner workflow
188
190
 
189
191
  └─ ALTERNATIVE: If interface design uncertain, commit both together
190
- → Skip syntax → [abaplint] → Commit both → Push → Pull → (if errors: inspect)
192
+ → Skip syntax → [abaplint] → Commit both → Push → Pull --sync-xml → (if errors: inspect)
191
193
 
192
194
  4. For UNSUPPORTED types (FUGR, TABL, etc.):
193
- Write code → Skip syntax → [abaplint] → Commit → Push → Pull → (if errors: inspect)
195
+ Write code → Skip syntax → [abaplint] → Commit → Push → Pull --sync-xml → (if errors: inspect)
194
196
 
195
197
  5. For MIXED types (some supported + some unsupported):
196
- Write all code → Run syntax on independent supported files ONLY → [abaplint] → Commit ALL → Push → Pull ALL
198
+ Write all code → Run syntax on independent supported files ONLY → [abaplint] → Commit ALL → Push → Pull ALL --sync-xml
197
199
 
198
- [abaplint] = run npx @abaplint/cli .abaplint.json only if .abaplint.json exists in repo root
200
+ [abaplint] = run abapgit-agent lint only if .abaplint.json exists in repo root
199
201
  before applying any quickfix: run abapgit-agent ref --topic abaplint
200
202
  ```
201
203
 
@@ -207,13 +209,13 @@ abapgit-agent pull --files src/zcl_my_class.clas.abap,src/zc_my_view.ddls.asddls
207
209
  vim src/zif_calculator.intf.abap
208
210
  abapgit-agent syntax --files src/zif_calculator.intf.abap # ✅ Works
209
211
  git commit -am "feat: add calculator interface" && git push
210
- abapgit-agent pull --files src/zif_calculator.intf.abap # Interface activated
212
+ abapgit-agent pull --files src/zif_calculator.intf.abap --sync-xml # Interface activated
211
213
 
212
214
  # Step 2: Class next
213
215
  vim src/zcl_calculator.clas.abap
214
216
  abapgit-agent syntax --files src/zcl_calculator.clas.abap # ✅ Works (interface exists!)
215
217
  git commit -am "feat: implement calculator" && git push
216
- abapgit-agent pull --files src/zcl_calculator.clas.abap
218
+ abapgit-agent pull --files src/zcl_calculator.clas.abap --sync-xml
217
219
  ```
218
220
 
219
221
  **Scenario 2: Multiple independent classes**
@@ -222,7 +224,7 @@ abapgit-agent pull --files src/zcl_calculator.clas.abap
222
224
  vim src/zcl_class1.clas.abap src/zcl_class2.clas.abap
223
225
  abapgit-agent syntax --files src/zcl_class1.clas.abap,src/zcl_class2.clas.abap
224
226
  git commit -am "feat: add utility classes" && git push
225
- abapgit-agent pull --files src/zcl_class1.clas.abap,src/zcl_class2.clas.abap
227
+ abapgit-agent pull --files src/zcl_class1.clas.abap,src/zcl_class2.clas.abap --sync-xml
226
228
  ```
227
229
 
228
230
  **Error indicators after pull:**
@@ -240,10 +242,10 @@ abapgit-agent syntax --files src/zc_my_view.ddls.asddls
240
242
  abapgit-agent syntax --files src/zcl_class1.clas.abap,src/zif_intf1.intf.abap,src/zc_view.ddls.asddls
241
243
 
242
244
  # 2. Pull/activate AFTER pushing to git
243
- abapgit-agent pull --files src/zcl_class1.clas.abap,src/zcl_class2.clas.abap
245
+ abapgit-agent pull --files src/zcl_class1.clas.abap,src/zcl_class2.clas.abap --sync-xml
244
246
 
245
247
  # Override conflict detection for a single pull (e.g. deliberate branch switch)
246
- abapgit-agent pull --files src/zcl_class1.clas.abap --conflict-mode ignore
248
+ abapgit-agent pull --files src/zcl_class1.clas.abap --conflict-mode ignore --sync-xml
247
249
 
248
250
  # 3. Inspect AFTER pull (for errors or unsupported types)
249
251
  abapgit-agent inspect --files src/zcl_class1.clas.abap
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abapgit-agent",
3
- "version": "1.16.3",
3
+ "version": "1.17.1",
4
4
  "description": "ABAP Git Agent - Pull and activate ABAP code via abapGit from any git repository",
5
5
  "files": [
6
6
  "bin/",
@@ -37,6 +37,7 @@
37
37
  "test:cmd:tree": "node tests/run-all.js --cmd --command=tree",
38
38
  "test:cmd:dump": "node tests/run-all.js --cmd --command=dump",
39
39
  "test:drop": "node tests/run-all.js --drop",
40
+ "test:customize": "node tests/run-all.js --customize",
40
41
  "test:cmd:debug": "node tests/run-all.js --cmd --command=debug",
41
42
  "test:debug": "node tests/run-all.js --debug",
42
43
  "test:debug:scenarios": "bash tests/integration/debug-scenarios.sh",
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Customize command — write a single row to a SAP customizing table (delivery class C/E).
3
+ *
4
+ * Usage:
5
+ * abapgit-agent customize <table> --set <field=value> [<field=value>...]
6
+ * [--transport <TRKORR>] [--no-transport] [--json]
7
+ */
8
+
9
+ const { printHttpError } = require('../utils/format-error');
10
+
11
+ module.exports = {
12
+ name: 'customize',
13
+ description: 'Write a row to a SAP customizing table (delivery class C/E)',
14
+ requiresAbapConfig: true,
15
+ requiresVersionCheck: false,
16
+
17
+ async execute(args, context) {
18
+ const { loadConfig, AbapHttp, getTransport, getTransportSettings } = context;
19
+
20
+ const jsonOutput = args.includes('--json');
21
+ const verbose = args.includes('--verbose');
22
+ const noTransport = args.includes('--no-transport');
23
+
24
+ // First positional arg (not starting with '-' and not following --set/--transport) = table name
25
+ const skipNext = new Set();
26
+ for (let i = 0; i < args.length; i++) {
27
+ if (args[i] === '--transport' || args[i] === '-t') {
28
+ if (i + 1 < args.length) skipNext.add(i + 1);
29
+ } else if (args[i] === '--set') {
30
+ let j = i + 1;
31
+ while (j < args.length && !args[j].startsWith('-')) {
32
+ skipNext.add(j);
33
+ j++;
34
+ }
35
+ }
36
+ }
37
+ const tableName = args.find((a, idx) => !a.startsWith('-') && !skipNext.has(idx));
38
+ if (!tableName) {
39
+ console.error('❌ Error: table name is required');
40
+ console.error(' Usage: abapgit-agent customize <table> --set <field=value> [...]');
41
+ process.exit(1);
42
+ }
43
+
44
+ // Collect all --set values (one or more field=value pairs after the flag)
45
+ const fieldValues = [];
46
+ for (let i = 0; i < args.length; i++) {
47
+ if (args[i] === '--set') {
48
+ // Consume all following positional tokens as field=value pairs
49
+ let j = i + 1;
50
+ while (j < args.length && !args[j].startsWith('-')) {
51
+ const pair = args[j];
52
+ const eqIdx = pair.indexOf('=');
53
+ if (eqIdx === -1) {
54
+ console.error(`❌ Error: --set value '${pair}' must be in field=value format`);
55
+ process.exit(1);
56
+ }
57
+ fieldValues.push({
58
+ field: pair.substring(0, eqIdx).toUpperCase(),
59
+ value: pair.substring(eqIdx + 1)
60
+ });
61
+ j++;
62
+ }
63
+ }
64
+ }
65
+
66
+ if (fieldValues.length === 0) {
67
+ console.error('❌ Error: at least one --set field=value pair is required');
68
+ console.error(' Example: abapgit-agent customize ZTABLE_CONFIG --set KEY=APP VALUE=active');
69
+ process.exit(1);
70
+ }
71
+
72
+ // Transport resolution: CLI --transport > config/env > auto-selection
73
+ let transportRequest = null;
74
+ const transportArgIndex = args.indexOf('--transport');
75
+ if (transportArgIndex !== -1 && transportArgIndex + 1 < args.length) {
76
+ transportRequest = args[transportArgIndex + 1];
77
+ } else if (!noTransport) {
78
+ transportRequest = getTransport ? getTransport() : null;
79
+ }
80
+
81
+ // When no transport is determined yet and we are not in JSON mode, use the
82
+ // selector (hook / interactive picker) with type='customizing'
83
+ if (!transportRequest && !noTransport && !jsonOutput) {
84
+ const { selectTransport, isNonInteractive, _getTransportHookConfig } = require('../utils/transport-selector');
85
+
86
+ const config = loadConfig();
87
+ const http = new AbapHttp(config);
88
+ transportRequest = await selectTransport(config, http, loadConfig, AbapHttp, getTransportSettings, 'customizing');
89
+
90
+ if (transportRequest === null) {
91
+ const hookConfig = _getTransportHookConfig();
92
+ if (hookConfig && hookConfig.hook) {
93
+ if (isNonInteractive()) {
94
+ console.error('❌ Error: transport hook returned no transport request.');
95
+ console.error(` Hook: ${hookConfig.hook}`);
96
+ if (hookConfig.description) console.error(` ${hookConfig.description}`);
97
+ process.exit(1);
98
+ } else {
99
+ process.stderr.write(`⚠️ Transport hook returned no transport request (${hookConfig.hook}).\n`);
100
+ process.stderr.write(' Please select one manually:\n');
101
+ const { interactivePicker } = require('../utils/transport-selector');
102
+ transportRequest = await interactivePicker(http, 'customizing');
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ const config = loadConfig();
109
+ const http = new AbapHttp(config);
110
+
111
+ try {
112
+ const csrfToken = await http.fetchCsrfToken();
113
+ const payload = {
114
+ table_name: tableName.toUpperCase(),
115
+ field_values: fieldValues,
116
+ transport: transportRequest || '',
117
+ no_transport: noTransport ? 'X' : ''
118
+ };
119
+
120
+ const result = await http.post(
121
+ '/sap/bc/z_abapgit_agent/customize',
122
+ payload,
123
+ { csrfToken }
124
+ );
125
+
126
+ if (jsonOutput) {
127
+ console.log(JSON.stringify(result, null, 2));
128
+ return;
129
+ }
130
+
131
+ const success = result.SUCCESS === true || result.success === true ||
132
+ result.SUCCESS === 'X' || result.success === 'X';
133
+ const error = result.ERROR || result.error || '';
134
+ const message = result.MESSAGE || result.message || '';
135
+ const action = result.ACTION || result.action || '';
136
+ const transport = result.TRANSPORT || result.transport || '';
137
+ const deliveryCls = result.DELIVERY_CLASS || result.delivery_class || '';
138
+
139
+ if (!success) {
140
+ console.error(`❌ Error: ${error || message || 'customize command failed'}`);
141
+ process.exit(1);
142
+ }
143
+
144
+ console.log(`\n✅ Customizing entry written to ${tableName.toUpperCase()}`);
145
+ if (action) console.log(` Action: ${action}`);
146
+ if (transport) console.log(` Transport: ${transport} (recorded)`);
147
+ else if (deliveryCls) console.log(` Transport: none (delivery class ${deliveryCls})`);
148
+ else console.log(` Transport: none`);
149
+ if (message) console.log(` ${message}`);
150
+ console.log('');
151
+
152
+ } catch (error) {
153
+ printHttpError(error, { verbose });
154
+ process.exit(1);
155
+ }
156
+ }
157
+ };
@@ -8,7 +8,7 @@ const { printHttpError } = require('../utils/format-error');
8
8
  const EXT_TO_TYPE = {
9
9
  clas: 'CLAS', intf: 'INTF', prog: 'PROG', fugr: 'FUGR',
10
10
  tabl: 'TABL', dtel: 'DTEL', ttyp: 'TTYP', doma: 'DOMA',
11
- ddls: 'DDLS', dcls: 'DCLS', msag: 'MSAG', stru: 'STRU'
11
+ ddls: 'DDLS', dcls: 'DCLS', msag: 'MSAG', stru: 'STRU', enho: 'ENHO'
12
12
  };
13
13
 
14
14
  /**
@@ -66,6 +66,9 @@ Commands:
66
66
  List and manage SAP transport requests.
67
67
  Subcommands: list, create, release
68
68
 
69
+ customize <table> --set <field>=<value> [<field>=<value> ...] [--transport <TRKORR>] [--no-transport] [--json]
70
+ Write a row (insert-or-update) into a SAP customizing table (delivery class C or E).
71
+
69
72
  tree --package <package> [--depth <n>] [--include-types] [--json]
70
73
  Display package hierarchy tree from ABAP system
71
74
 
@@ -147,6 +150,7 @@ Examples:
147
150
  abapgit-agent view --objects ZCL_MY_CLASS # View class definition
148
151
  abapgit-agent preview --objects SFLIGHT # Preview table data
149
152
  abapgit-agent where --objects ZCL_MY_CLASS # Find where class is used
153
+ abapgit-agent customize ZAPP_CONFIG --set KEY=X VALUE=Y --no-transport # Write customizing entry
150
154
  abapgit-agent dump --date TODAY # Recent short dumps
151
155
  abapgit-agent dump --user DEVELOPER --detail 1 # Full detail of first result
152
156
  abapgit-agent ref "CORRESPONDING" # Search for pattern
@@ -31,5 +31,6 @@ module.exports = {
31
31
  upgrade: require('./upgrade'),
32
32
  transport: require('./transport'),
33
33
  lint: require('./lint'),
34
- drop: require('./drop')
34
+ drop: require('./drop'),
35
+ customize: require('./customize')
35
36
  };
@@ -153,8 +153,9 @@ Examples:
153
153
  console.error('❌ Error: --files only accepts ABAP source files (.abap, .asddls) or XML-only object files (name.type.xml).');
154
154
  console.error(' The following file(s) are not recognised:');
155
155
  nonSourceFiles.forEach(f => console.error(` ${f}`));
156
- console.error(' Tip: for source objects pass the .abap file; for XML-only objects (TABL, STRU, DTEL, TTYP)');
157
- console.error(' pass the .xml file, e.g. --files abap/ztable.tabl.xml');
156
+ console.error(' Tip: for source objects pass the .abap file (e.g. zcl_foo.clas.abap or zcl_foo.enho.28bbfe2f.abap);');
157
+ console.error(' for XML-only objects (TABL, STRU, DTEL, TTYP) or enhancement metadata,');
158
+ console.error(' pass the .xml file, e.g. --files abap/ztable.tabl.xml or abap/zfoo.enho.xml');
158
159
  process.exit(1);
159
160
  }
160
161
 
@@ -24,7 +24,7 @@ Description:
24
24
  Reads source from local files and checks directly in the ABAP system.
25
25
 
26
26
  Parameters:
27
- --files <file1,...> Comma-separated ABAP source files (required). Accepts CLAS, INTF, PROG.
27
+ --files <file1,...> Comma-separated ABAP source files (required). Accepts CLAS, INTF, PROG, ENHO.
28
28
  --cloud Use ABAP Cloud (BTP) stricter syntax check.
29
29
  --json Output as JSON.
30
30
 
@@ -103,6 +103,9 @@ Examples:
103
103
  } else if (baseName.includes('.prog.')) {
104
104
  objType = 'PROG';
105
105
  objName = baseName.split('.')[0].toUpperCase();
106
+ } else if (/\.enho\.[0-9a-f]{8}\.abap$/i.test(baseName)) {
107
+ objType = 'ENHO';
108
+ objName = baseName.split('.')[0].toUpperCase();
106
109
  } else if (baseName.includes('.ddls.asddls')) {
107
110
  objType = 'DDLS';
108
111
  objName = baseName.split('.')[0].toUpperCase();
@@ -81,7 +81,16 @@ module.exports = {
81
81
  process.exit(1);
82
82
  }
83
83
 
84
- const result = await http.get(`/sap/bc/z_abapgit_agent/transport?scope=${scope}`);
84
+ const typeIdx = args.indexOf('--type');
85
+ const type = typeIdx !== -1 ? args[typeIdx + 1] : null;
86
+
87
+ if (type && !VALID_TYPES.includes(type)) {
88
+ console.error(`❌ Error: Invalid type '${type}'. Valid values: ${VALID_TYPES.join(', ')}`);
89
+ process.exit(1);
90
+ }
91
+
92
+ const typeParam = type ? `&type=${type}` : '';
93
+ const result = await http.get(`/sap/bc/z_abapgit_agent/transport?scope=${scope}${typeParam}`);
85
94
 
86
95
  if (jsonOutput) {
87
96
  console.log(JSON.stringify(result, null, 2));
@@ -90,8 +99,9 @@ module.exports = {
90
99
 
91
100
  const transports = result.TRANSPORTS || result.transports || [];
92
101
  const scopeLabel = { mine: 'mine', task: 'task — where I own or have a task', all: 'all' }[scope];
102
+ const typeLabel = type ? ` (${type})` : '';
93
103
 
94
- console.log(`\n📋 Open Transport Requests (${scopeLabel})\n`);
104
+ console.log(`\n📋 Open Transport Requests (${scopeLabel}${typeLabel})\n`);
95
105
 
96
106
  if (transports.length === 0) {
97
107
  console.log(' No open transport requests found.');
@@ -156,9 +156,21 @@ function findFirstExecutableLine(lines) {
156
156
  const commentPattern = /^\s*[*"]/;
157
157
  // Program-level header/declaration keywords that are not executable statements
158
158
  const progDeclPattern = /^\s*(report|program|parameters|tables|selection-screen|select-options|class-pool|function-pool|interface-pool|type-pool|include)\b/i;
159
+ // ENHO injected block marker: *"* ENHO: ... through *"* ENHO END
160
+ const enhoStartPattern = /^\s*\*"\* ENHO:/;
161
+ const enhoEndPattern = /^\s*\*"\* ENHO END/;
159
162
  let inDeclBlock = false; // true while inside a multi-line DATA:/TYPES:/PARAMETERS: block
163
+ let inEnhoBlock = false; // true while inside an injected ENHO block
160
164
  for (let i = 0; i < lines.length; i++) {
161
165
  const trimmed = lines[i].trim();
166
+ if (inEnhoBlock) {
167
+ if (enhoEndPattern.test(trimmed)) inEnhoBlock = false;
168
+ continue;
169
+ }
170
+ if (enhoStartPattern.test(trimmed)) {
171
+ inEnhoBlock = true;
172
+ continue;
173
+ }
162
174
  if (inDeclBlock) {
163
175
  // continuation line — skip until the block closes with a period
164
176
  if (trimmed.endsWith('.')) inDeclBlock = false;
@@ -182,6 +194,34 @@ function findFirstExecutableLine(lines) {
182
194
  return 0;
183
195
  }
184
196
 
197
+ /**
198
+ * Count the number of injected ENHO lines in `lines[0..upToIndex-1]`.
199
+ * An ENHO block spans from a `*"* ENHO:` marker line through (and including) the
200
+ * `*"* ENHO END` marker line. All lines in these blocks are injected by the ABAP
201
+ * backend and do not exist in the base class source that ADT uses for BP validation.
202
+ *
203
+ * Subtracting this count from the execOffset returned by findFirstExecutableLine
204
+ * converts the injected-array index back to the base-source line offset, which is
205
+ * the coordinate system ADT accepts for breakpoints.
206
+ */
207
+ function countEnhoLines(lines, upToIndex) {
208
+ const enhoStartPattern = /^\s*\*"\* ENHO:/;
209
+ const enhoEndPattern = /^\s*\*"\* ENHO END/;
210
+ let count = 0;
211
+ let inEnhoBlock = false;
212
+ for (let i = 0; i < upToIndex && i < lines.length; i++) {
213
+ const trimmed = lines[i].trim();
214
+ if (inEnhoBlock) {
215
+ count++;
216
+ if (enhoEndPattern.test(trimmed)) inEnhoBlock = false;
217
+ } else if (enhoStartPattern.test(trimmed)) {
218
+ inEnhoBlock = true;
219
+ count++;
220
+ }
221
+ }
222
+ return count;
223
+ }
224
+
185
225
  module.exports = {
186
226
  name: 'view',
187
227
  description: 'View ABAP object definitions from ABAP system',
@@ -189,6 +229,7 @@ module.exports = {
189
229
  requiresVersionCheck: true,
190
230
  _buildMethodLineMap: buildMethodLineMap,
191
231
  _findFirstExecutableLine: findFirstExecutableLine,
232
+ _countEnhoLines: countEnhoLines,
192
233
 
193
234
  async execute(args, context) {
194
235
  const { loadConfig, AbapHttp } = context;
@@ -339,7 +380,8 @@ Examples:
339
380
  const file = section.FILE || section.file || '';
340
381
  const lines = section.LINES || section.lines || [];
341
382
  const isCmSection = suffix.startsWith('CM') && methodName;
342
- const isFugrFmSection = !isCmSection && !!methodName;
383
+ const isEnhoSection = /^[0-9a-f]{8}$/i.test(suffix);
384
+ const isFugrFmSection = !isCmSection && !isEnhoSection && !!methodName;
343
385
 
344
386
  if (linesMode) {
345
387
  // --full --lines: dual line numbers (G [N]) for debugging
@@ -359,7 +401,10 @@ Examples:
359
401
  let bpHint;
360
402
  if (globalStart) {
361
403
  const execOffset = findFirstExecutableLine(lines);
362
- const execLine = globalStart + execOffset;
404
+ // Subtract injected ENHO lines that appear before execOffset so
405
+ // the BP hint uses base-source coordinates (what ADT validates against)
406
+ const enhoCount = countEnhoLines(lines, execOffset);
407
+ const execLine = globalStart + execOffset - enhoCount;
363
408
  bpHint = `debug set --objects ${objName}:${execLine}`;
364
409
  } else {
365
410
  bpHint = `debug set --objects ${objName}:<global_line>`;
@@ -390,6 +435,10 @@ Examples:
390
435
  }
391
436
  const bpHint = `debug set --objects ${suffix}:${firstExecLine}`;
392
437
  console.log(` * ---- FM: ${methodName} (${suffix}) — breakpoint: ${bpHint} ----`);
438
+ } else if (isEnhoSection) {
439
+ const desc = section.DESCRIPTION || section.description || '';
440
+ console.log(` * ---- Hook: ${methodName} (${suffix}) ----`);
441
+ if (desc && desc !== methodName) console.log(` * full_name: ${desc}`);
393
442
  } else if (file) {
394
443
  console.log(` * ---- Section: ${section.DESCRIPTION || section.description} (from .clas.${file}.abap) ----`);
395
444
  } else if (suffix) {
@@ -453,6 +502,10 @@ Examples:
453
502
  console.log(` * ---- Method: ${methodName} (${suffix}) ----`);
454
503
  } else if (isFugrFmSection) {
455
504
  console.log(` * ---- FM: ${methodName} (${suffix}) ----`);
505
+ } else if (isEnhoSection) {
506
+ const desc = section.DESCRIPTION || section.description || '';
507
+ console.log(` * ---- Hook: ${methodName} (${suffix}) ----`);
508
+ if (desc && desc !== methodName) console.log(` * full_name: ${desc}`);
456
509
  } else if (file) {
457
510
  console.log(` * ---- Section: ${section.DESCRIPTION || section.description} (from .clas.${file}.abap) ----`);
458
511
  } else if (suffix) {
@@ -38,11 +38,13 @@ async function runHook(hookPath, context) {
38
38
  *
39
39
  * @param {object} http
40
40
  * @param {string} scope - 'mine' | 'task' | 'all'
41
+ * @param {string} [type] - 'workbench' (default) | 'customizing'
41
42
  * @returns {Promise<Array>}
42
43
  */
43
- async function fetchTransports(http, scope = 'mine') {
44
+ async function fetchTransports(http, scope = 'mine', type = 'workbench') {
44
45
  try {
45
- const result = await http.get(`/sap/bc/z_abapgit_agent/transport?scope=${scope}`);
46
+ const typeParam = type === 'customizing' ? '&type=customizing' : '';
47
+ const result = await http.get(`/sap/bc/z_abapgit_agent/transport?scope=${scope}${typeParam}`);
46
48
  const raw = result.TRANSPORTS || result.transports || [];
47
49
  return raw.map(t => ({
48
50
  number: t.NUMBER || t.number || '',
@@ -59,13 +61,14 @@ async function fetchTransports(http, scope = 'mine') {
59
61
  * Create a new transport request.
60
62
  * @param {object} http
61
63
  * @param {string} description
64
+ * @param {string} [type] - 'workbench' (default) | 'customizing'
62
65
  * @returns {Promise<string|null>} transport number or null
63
66
  */
64
- async function createTransport(http, description) {
67
+ async function createTransport(http, description, type = 'workbench') {
65
68
  try {
66
69
  const result = await http.post(
67
70
  '/sap/bc/z_abapgit_agent/transport',
68
- { action: 'CREATE', description: description || '' },
71
+ { action: 'CREATE', description: description || '', type },
69
72
  {}
70
73
  );
71
74
  return result.NUMBER || result.number || null;
@@ -79,9 +82,10 @@ async function createTransport(http, description) {
79
82
  * Presents a numbered menu; supports scope switching, create, and skip.
80
83
  *
81
84
  * @param {object} http
85
+ * @param {string} [type] - 'workbench' (default) | 'customizing'
82
86
  * @returns {Promise<string|null>}
83
87
  */
84
- async function interactivePicker(http) {
88
+ async function interactivePicker(http, type = 'workbench') {
85
89
  const readline = require('readline');
86
90
 
87
91
  let scope = 'mine';
@@ -92,11 +96,12 @@ async function interactivePicker(http) {
92
96
  const ask = (q) => new Promise(resolve => rl.question(q, resolve));
93
97
 
94
98
  const fetchAndDisplay = async () => {
95
- transports = await fetchTransports(http, scope);
99
+ transports = await fetchTransports(http, scope, type);
96
100
  fetchError = transports.length === 0;
97
101
 
98
102
  const scopeLabel = { mine: 'my transports', task: 'transports where I have a task', all: 'all open transports' }[scope];
99
- process.stderr.write(`\nSelect a transport request (showing: ${scopeLabel}):\n\n`);
103
+ const typeLabel = type === 'customizing' ? ' (customizing)' : '';
104
+ process.stderr.write(`\nSelect a transport request (showing: ${scopeLabel}${typeLabel}):\n\n`);
100
105
 
101
106
  if (transports.length > 0) {
102
107
  transports.forEach((t, i) => {
@@ -144,7 +149,7 @@ async function interactivePicker(http) {
144
149
  if (answer === 'c') {
145
150
  const desc = (await ask('Description: ')).trim();
146
151
  rl.close();
147
- const number = await createTransport(http, desc);
152
+ const number = await createTransport(http, desc, type);
148
153
  if (number) {
149
154
  process.stderr.write(`\n✅ Created transport ${number}\n`);
150
155
  return number;
@@ -173,18 +178,30 @@ async function interactivePicker(http) {
173
178
  *
174
179
  * Splits on whitespace, forces --json, captures output, returns parsed JSON.
175
180
  *
181
+ * When `type` is set, `transport list` calls automatically get `--type <type>`
182
+ * injected (unless the hook already passes --type explicitly). This means hooks
183
+ * written generically as `run('transport list ...')` automatically see only the
184
+ * right kind of transport (workbench or customizing) without any hook changes.
185
+ *
176
186
  * @param {object} config - Loaded ABAP config
177
187
  * @param {object} http - Pre-built AbapHttp instance
178
188
  * @param {Function} loadConfig - Config factory (from pull context)
179
189
  * @param {Function} AbapHttp - AbapHttp constructor (from pull context)
180
190
  * @param {Function} getTransportSettings - Transport settings getter (from pull context)
191
+ * @param {string} [type] - 'workbench' (default) | 'customizing'
181
192
  * @returns {Function|undefined} - The run helper, or undefined if factories are missing
182
193
  */
183
- function buildRun(config, http, loadConfig, AbapHttp, getTransportSettings) {
194
+ function buildRun(config, http, loadConfig, AbapHttp, getTransportSettings, type = 'workbench') {
184
195
  if (!loadConfig || !AbapHttp) return undefined;
185
196
 
186
197
  return async function run(command) {
187
- const [commandName, ...args] = command.trim().split(/\s+/);
198
+ let [commandName, ...args] = command.trim().split(/\s+/);
199
+
200
+ // Auto-inject --type for "transport list" so the hook gets the right request type
201
+ // without needing to know about the transport type explicitly.
202
+ if (commandName === 'transport' && args[0] === 'list' && !args.includes('--type') && type) {
203
+ args = [...args, '--type', type];
204
+ }
188
205
  const cmdModule = require(`../commands/${commandName}`);
189
206
 
190
207
  // Always force --json so output is parseable
@@ -220,7 +237,7 @@ function buildRun(config, http, loadConfig, AbapHttp, getTransportSettings) {
220
237
  }
221
238
 
222
239
  /**
223
- * Main export — selects a transport request for use in the pull command.
240
+ * Main export — selects a transport request for use in the pull/customize command.
224
241
  * Returns the transport number, or null to proceed without one.
225
242
  *
226
243
  * @param {object} config - Loaded ABAP config
@@ -228,16 +245,21 @@ function buildRun(config, http, loadConfig, AbapHttp, getTransportSettings) {
228
245
  * @param {Function} [loadConfig] - Config factory (enables run helper in hook context)
229
246
  * @param {Function} [AbapHttp] - AbapHttp constructor (enables run helper in hook context)
230
247
  * @param {Function} [getTransportSettings] - Transport settings getter
248
+ * @param {string} [type] - 'workbench' (default) | 'customizing'
249
+ * Passed to hook as context.type and auto-injected into
250
+ * run('transport list ...') calls so hooks work correctly
251
+ * for both ABAP objects and customizing entries without
252
+ * needing to handle type explicitly.
231
253
  * @returns {Promise<string|null>}
232
254
  */
233
- async function selectTransport(config, http, loadConfig, AbapHttp, getTransportSettings) {
255
+ async function selectTransport(config, http, loadConfig, AbapHttp, getTransportSettings, type = 'workbench') {
234
256
  // Hook takes precedence over the interactive picker — runs in both TTY and non-TTY mode
235
257
  const hookConfig = module.exports._getTransportHookConfig();
236
258
  if (hookConfig && hookConfig.hook) {
237
259
  const hookPath = path.resolve(process.cwd(), hookConfig.hook);
238
- const run = buildRun(config, http, loadConfig, AbapHttp, getTransportSettings);
260
+ const run = buildRun(config, http, loadConfig, AbapHttp, getTransportSettings, type);
239
261
  try {
240
- return await module.exports.runHook(hookPath, { config, http, run });
262
+ return await module.exports.runHook(hookPath, { config, http, run, type });
241
263
  } catch {
242
264
  return null;
243
265
  }
@@ -249,7 +271,7 @@ async function selectTransport(config, http, loadConfig, AbapHttp, getTransportS
249
271
  }
250
272
 
251
273
  // Manual mode: interactive picker
252
- return interactivePicker(http);
274
+ return interactivePicker(http, type);
253
275
  }
254
276
 
255
277
  /**