abapgit-agent 1.11.0 → 1.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/.abapGitAgent.example +1 -0
  2. package/abap/.github/copilot-instructions.md +9 -9
  3. package/abap/CLAUDE.md +48 -539
  4. package/abap/guidelines/{08_abapgit.md → abapgit.md} +1 -1
  5. package/abap/guidelines/branch-workflow.md +137 -0
  6. package/abap/guidelines/cds-testing.md +25 -0
  7. package/abap/guidelines/{04_cds.md → cds.md} +4 -4
  8. package/abap/guidelines/{10_common_errors.md → common-errors.md} +3 -3
  9. package/abap/guidelines/debug-dump.md +33 -0
  10. package/abap/guidelines/debug-session.md +280 -0
  11. package/abap/guidelines/index.md +50 -0
  12. package/abap/guidelines/object-creation.md +51 -0
  13. package/abap/guidelines/{06_objects.md → objects.md} +2 -2
  14. package/abap/guidelines/{01_sql.md → sql.md} +2 -2
  15. package/abap/guidelines/{03_testing.md → testing.md} +3 -3
  16. package/abap/guidelines/workflow-detailed.md +255 -0
  17. package/package.json +1 -1
  18. package/src/commands/debug.js +54 -20
  19. package/src/commands/inspect.js +5 -3
  20. package/src/commands/pull.js +4 -1
  21. package/src/commands/transport.js +3 -1
  22. package/src/commands/unit.js +10 -10
  23. package/src/commands/view.js +238 -1
  24. package/src/config.js +4 -2
  25. package/src/utils/abap-http.js +11 -6
  26. package/src/utils/abap-reference.js +4 -4
  27. package/src/utils/adt-http.js +13 -8
  28. package/src/utils/format-error.js +89 -0
  29. package/src/utils/version-check.js +4 -3
  30. package/abap/guidelines/00_index.md +0 -44
  31. /package/abap/guidelines/{05_classes.md → classes.md} +0 -0
  32. /package/abap/guidelines/{02_exceptions.md → exceptions.md} +0 -0
  33. /package/abap/guidelines/{07_json.md → json.md} +0 -0
  34. /package/abap/guidelines/{09_unit_testable_code.md → unit-testable-code.md} +0 -0
@@ -0,0 +1,255 @@
1
+ ---
2
+ layout: default
3
+ title: Development Workflow (Detailed)
4
+ nav_order: 15
5
+ parent: ABAP Coding Guidelines
6
+ grand_parent: ABAP Development
7
+ ---
8
+
9
+ # Development Workflow (Detailed)
10
+
11
+ ```
12
+ 1. Read .abapGitAgent → get folder value AND workflow.mode
13
+
14
+
15
+ 2. Research → use ref command for unfamiliar topics
16
+
17
+
18
+ 3. Write code → place in correct folder (e.g., src/zcl_*.clas.abap)
19
+
20
+
21
+ 4. Syntax check (for CLAS, INTF, PROG, DDLS only)
22
+
23
+ ├─► CLAS/INTF/PROG/DDLS → abapgit-agent syntax --files <file>
24
+ │ │
25
+ │ ├─► Errors? → Fix locally (no commit needed), re-run syntax
26
+ │ │
27
+ │ └─► Clean ✅ → Proceed to commit
28
+
29
+ └─► Other types (FUGR, TABL, etc.) → Skip syntax, go to commit
30
+
31
+
32
+ 5. Commit and push → git add . && git commit && git push
33
+
34
+
35
+ 6. Activate → abapgit-agent pull --files src/file.clas.abap
36
+ │ (behaviour depends on .abapgit-agent.json — see AI Tool Guidelines)
37
+
38
+
39
+ 7. Verify → Check pull output
40
+ - **"Error updating where-used list"** → SYNTAX ERROR (use inspect for details)
41
+ - Objects in "Failed Objects Log" → Syntax error (use inspect)
42
+ - Objects NOT appearing at all → XML metadata issue (check abapgit.md)
43
+
44
+
45
+ 8. (Optional) Run unit tests → abapgit-agent unit --files <testclass> (AFTER successful pull)
46
+ ```
47
+
48
+ **Syntax Command - Supported Object Types:**
49
+
50
+ | Object Type | Syntax Command | What to Do |
51
+ |-------------|----------------|------------|
52
+ | CLAS (classes) | ✅ Supported | Run `syntax` before commit |
53
+ | CLAS (test classes: .testclasses.abap) | ✅ Supported | Run `syntax` before commit |
54
+ | INTF (interfaces) | ✅ Supported | Run `syntax` before commit |
55
+ | PROG (programs) | ✅ Supported | Run `syntax` before commit |
56
+ | DDLS (CDS views) | ✅ Supported | Run `syntax` before commit (requires annotations) |
57
+ | FUGR (function groups) | ❌ Not supported | Skip syntax, use `pull` then `inspect` |
58
+ | TABL/DTEL/DOMA/MSAG/SHLP | ❌ Not supported | Skip syntax, just `pull` |
59
+ | All other types | ❌ Not supported | Skip syntax, just `pull` |
60
+
61
+ **IMPORTANT**:
62
+ - **Use `syntax` BEFORE commit** for CLAS/INTF/PROG/DDLS - catches errors early, no git pollution
63
+ - **Syntax checks files INDEPENDENTLY** - syntax checker doesn't have access to uncommitted files
64
+ - **For dependent files** (interface + class): Create/activate underlying object FIRST, then dependent object (see workflow below)
65
+ - **DDLS requires proper annotations** - CDS views need `@AbapCatalog.sqlViewName`, view entities don't
66
+ - **ALWAYS push to git BEFORE running pull** - abapGit reads from git
67
+ - **Use `inspect` AFTER pull** for unsupported types or if pull fails
68
+
69
+ **Working with dependent objects (RECOMMENDED APPROACH):**
70
+
71
+ When creating objects with dependencies (e.g., interface → class), create and activate the underlying object FIRST:
72
+
73
+ ```bash
74
+ # Step 1: Create interface, syntax check, commit, activate
75
+ vim src/zif_my_interface.intf.abap
76
+ abapgit-agent syntax --files src/zif_my_interface.intf.abap # ✅ Works (no dependencies)
77
+ git add src/zif_my_interface.intf.abap src/zif_my_interface.intf.xml
78
+ git commit -m "feat: add interface"
79
+ git push
80
+ abapgit-agent pull --files src/zif_my_interface.intf.abap # Interface now activated
81
+
82
+ # Step 2: Create class, syntax check, commit, activate
83
+ vim src/zcl_my_class.clas.abap
84
+ abapgit-agent syntax --files src/zcl_my_class.clas.abap # ✅ Works (interface already activated)
85
+ git add src/zcl_my_class.clas.abap src/zcl_my_class.clas.xml
86
+ git commit -m "feat: add class implementing interface"
87
+ git push
88
+ abapgit-agent pull --files src/zcl_my_class.clas.abap
89
+ ```
90
+
91
+ **Benefits:**
92
+ - ✅ Syntax checking works for both objects
93
+ - ✅ Each step is validated independently
94
+ - ✅ Easier to debug if something fails
95
+ - ✅ Cleaner workflow
96
+
97
+ **Alternative approach (when interface design is uncertain):**
98
+
99
+ If the interface might need changes while implementing the class, commit both together:
100
+
101
+ ```bash
102
+ # Create both files
103
+ vim src/zif_my_interface.intf.abap
104
+ vim src/zcl_my_class.clas.abap
105
+
106
+ # Skip syntax (files depend on each other), commit together
107
+ git add src/zif_my_interface.intf.abap src/zif_my_interface.intf.xml
108
+ git add src/zcl_my_class.clas.abap src/zcl_my_class.clas.xml
109
+ git commit -m "feat: add interface and implementing class"
110
+ git push
111
+
112
+ # Pull both together
113
+ abapgit-agent pull --files src/zif_my_interface.intf.abap,src/zcl_my_class.clas.abap
114
+
115
+ # Use inspect if errors occur
116
+ abapgit-agent inspect --files src/zcl_my_class.clas.abap
117
+ ```
118
+
119
+ **Use this approach when:**
120
+ - ❌ Interface design is still evolving
121
+ - ❌ Multiple iterations expected
122
+
123
+ **Working with mixed file types:**
124
+ When modifying multiple files of different types (e.g., 1 class + 1 CDS view):
125
+ 1. Run `syntax` on independent supported files (CLAS, INTF, PROG, DDLS)
126
+ 2. Commit ALL files together (including unsupported types)
127
+ 3. Push and pull ALL files together
128
+
129
+ Example:
130
+ ```bash
131
+ # Check syntax on independent files only
132
+ abapgit-agent syntax --files src/zcl_my_class.clas.abap,src/zc_my_view.ddls.asddls
133
+
134
+ # Commit and push all files
135
+ git add src/zcl_my_class.clas.abap src/zc_my_view.ddls.asddls
136
+ git commit -m "feat: add class and CDS view"
137
+ git push
138
+
139
+ # Pull all files together
140
+ abapgit-agent pull --files src/zcl_my_class.clas.abap,src/zc_my_view.ddls.asddls
141
+ ```
142
+
143
+ **When to use syntax vs inspect vs view**:
144
+ - **syntax**: Check LOCAL code BEFORE commit (CLAS, INTF, PROG, DDLS)
145
+ - **inspect**: Check ACTIVATED code AFTER pull (all types, runs Code Inspector)
146
+ - **view**: Understand object STRUCTURE (not for debugging errors)
147
+
148
+ ### Quick Decision Tree for AI
149
+
150
+ **When user asks to modify/create ABAP code:**
151
+
152
+ ```
153
+ 1. Identify file extension(s) AND dependencies
154
+ ├─ .clas.abap or .clas.testclasses.abap → CLAS ✅ syntax supported
155
+ ├─ .intf.abap → INTF ✅ syntax supported
156
+ ├─ .prog.abap → PROG ✅ syntax supported
157
+ ├─ .ddls.asddls → DDLS ✅ syntax supported (requires proper annotations)
158
+ └─ All other extensions → ❌ syntax not supported
159
+
160
+ 2. Check for dependencies:
161
+ ├─ Interface + implementing class? → DEPENDENT (interface is underlying)
162
+ ├─ Class A uses class B? → DEPENDENT (class B is underlying)
163
+ ├─ CDS view uses table? → INDEPENDENT (table already exists)
164
+ └─ Unrelated bug fixes across files? → INDEPENDENT
165
+
166
+ 3. For SUPPORTED types (CLAS/INTF/PROG/DDLS):
167
+ ├─ INDEPENDENT files → Run syntax → Fix errors → Commit → Push → Pull
168
+
169
+ └─ DEPENDENT files (NEW objects):
170
+ ├─ RECOMMENDED: Create underlying object first (interface, base class, etc.)
171
+ │ 1. Create underlying object → Syntax → Commit → Push → Pull
172
+ │ 2. Create dependent object → Syntax (works!) → Commit → Push → Pull
173
+ │ ✅ Benefits: Both syntax checks work, cleaner workflow
174
+
175
+ └─ ALTERNATIVE: If interface design uncertain, commit both together
176
+ → Skip syntax → Commit both → Push → Pull → (if errors: inspect)
177
+
178
+ 4. For UNSUPPORTED types (FUGR, TABL, etc.):
179
+ Write code → Skip syntax → Commit → Push → Pull → (if errors: inspect)
180
+
181
+ 5. For MIXED types (some supported + some unsupported):
182
+ Write all code → Run syntax on independent supported files ONLY → Commit ALL → Push → Pull ALL
183
+ ```
184
+
185
+ **Example workflows:**
186
+
187
+ **Scenario 1: Interface + Class (RECOMMENDED)**
188
+ ```bash
189
+ # Step 1: Interface first
190
+ vim src/zif_calculator.intf.abap
191
+ abapgit-agent syntax --files src/zif_calculator.intf.abap # ✅ Works
192
+ git commit -am "feat: add calculator interface" && git push
193
+ abapgit-agent pull --files src/zif_calculator.intf.abap # Interface activated
194
+
195
+ # Step 2: Class next
196
+ vim src/zcl_calculator.clas.abap
197
+ abapgit-agent syntax --files src/zcl_calculator.clas.abap # ✅ Works (interface exists!)
198
+ git commit -am "feat: implement calculator" && git push
199
+ abapgit-agent pull --files src/zcl_calculator.clas.abap
200
+ ```
201
+
202
+ **Scenario 2: Multiple independent classes**
203
+ ```bash
204
+ # All syntax checks work (no dependencies)
205
+ vim src/zcl_class1.clas.abap src/zcl_class2.clas.abap
206
+ abapgit-agent syntax --files src/zcl_class1.clas.abap,src/zcl_class2.clas.abap
207
+ git commit -am "feat: add utility classes" && git push
208
+ abapgit-agent pull --files src/zcl_class1.clas.abap,src/zcl_class2.clas.abap
209
+ ```
210
+
211
+ **Error indicators after pull:**
212
+ - ❌ **"Error updating where-used list"** → SYNTAX ERROR - run `inspect` for details
213
+ - ❌ **Objects in "Failed Objects Log"** → SYNTAX ERROR - run `inspect`
214
+ - ❌ **Objects NOT appearing at all** → XML metadata issue (check `ref --topic abapgit`)
215
+ - ⚠️ **"Activated with warnings"** → Code Inspector warnings - run `inspect` to see details
216
+
217
+ ### Commands
218
+
219
+ ```bash
220
+ # 1. Syntax check LOCAL code BEFORE commit (CLAS, INTF, PROG, DDLS)
221
+ abapgit-agent syntax --files src/zcl_my_class.clas.abap
222
+ abapgit-agent syntax --files src/zc_my_view.ddls.asddls
223
+ abapgit-agent syntax --files src/zcl_class1.clas.abap,src/zif_intf1.intf.abap,src/zc_view.ddls.asddls
224
+
225
+ # 2. Pull/activate AFTER pushing to git
226
+ abapgit-agent pull --files src/zcl_class1.clas.abap,src/zcl_class2.clas.abap
227
+
228
+ # Override conflict detection for a single pull (e.g. deliberate branch switch)
229
+ abapgit-agent pull --files src/zcl_class1.clas.abap --conflict-mode ignore
230
+
231
+ # 3. Inspect AFTER pull (for errors or unsupported types)
232
+ abapgit-agent inspect --files src/zcl_class1.clas.abap
233
+
234
+ # Run unit tests (after successful pull)
235
+ abapgit-agent unit --files src/zcl_test1.clas.testclasses.abap,src/zcl_test2.clas.testclasses.abap
236
+
237
+ # View object definitions (multiple objects)
238
+ abapgit-agent view --objects ZCL_CLASS1,ZCL_CLASS2,ZIF_INTERFACE
239
+
240
+ # Preview table data (multiple tables/views)
241
+ abapgit-agent preview --objects ZTABLE1,ZTABLE2
242
+
243
+ # Explore table structures
244
+ abapgit-agent view --objects ZTABLE --type TABL
245
+
246
+ # Display package tree
247
+ abapgit-agent tree --package \$MY_PACKAGE
248
+
249
+ # Investigate runtime errors (ST22 short dumps)
250
+ abapgit-agent dump # Last 7 days
251
+ abapgit-agent dump --user DEVELOPER --date TODAY # Today's dumps for a user
252
+ abapgit-agent dump --program ZMY_PROGRAM # Dumps from a specific program
253
+ abapgit-agent dump --error TIME_OUT # Dumps by error type
254
+ abapgit-agent dump --user DEVELOPER --detail 1 # Full detail of first result
255
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abapgit-agent",
3
- "version": "1.11.0",
3
+ "version": "1.11.2",
4
4
  "description": "ABAP Git Agent - Pull and activate ABAP code via abapGit from any git repository",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -34,6 +34,7 @@ const { spawn } = require('child_process');
34
34
  const { AdtHttp } = require('../utils/adt-http');
35
35
  const { DebugSession } = require('../utils/debug-session');
36
36
  const debugStateModule = require('../utils/debug-state');
37
+ const { printHttpError } = require('../utils/format-error');
37
38
  const {
38
39
  saveActiveSession,
39
40
  loadActiveSession,
@@ -60,11 +61,16 @@ function hasFlag(args, flag) {
60
61
  }
61
62
 
62
63
  /**
63
- * Determine ADT object URI from object name (class/interface vs program).
64
+ * Determine ADT object URI from object name (class/interface vs program vs include).
64
65
  * Must use /source/main suffix for classes — verified by live testing: ADT
65
66
  * rejects breakpoints set on the class root URI but accepts /source/main.
66
67
  * classes → /sap/bc/adt/oo/classes/<name_lowercase>/source/main
68
+ * includes → /sap/bc/adt/programs/includes/<name_lowercase>
67
69
  * programs → /sap/bc/adt/programs/programs/<name_lowercase>
70
+ *
71
+ * Class method includes are named <ClassName padded to 30 chars with '='>CM<suffix>
72
+ * e.g. ZCL_ABGAGT_AGENT=============CM00D
73
+ * These must be routed to the programs/includes ADT endpoint.
68
74
  */
69
75
  function objectUri(name) {
70
76
  const upper = (name || '').toUpperCase();
@@ -325,17 +331,16 @@ async function cmdSet(args, config, adt) {
325
331
  return;
326
332
  }
327
333
 
328
- await adt.fetchCsrfToken();
329
- const body = buildBreakpointsXml(config.user, updated);
330
-
331
334
  let resp;
332
335
  try {
336
+ await adt.fetchCsrfToken();
337
+ const body = buildBreakpointsXml(config.user, updated);
333
338
  resp = await adt.post('/sap/bc/adt/debugger/breakpoints', body, {
334
339
  contentType: 'application/xml',
335
340
  headers: { Accept: 'application/xml' }
336
341
  });
337
342
  } catch (err) {
338
- console.error(`\n Error: ${err.message || JSON.stringify(err)}\n`);
343
+ printHttpError(err, { prefix: ' Error' });
339
344
  process.exit(1);
340
345
  }
341
346
 
@@ -357,6 +362,35 @@ async function cmdSet(args, config, adt) {
357
362
  });
358
363
  if (_saveBpState) _saveBpState(config, updatedWithServerIds);
359
364
 
365
+ // Immediately re-validate the newly added breakpoints: ADT accepts the POST
366
+ // with HTTP 200 even for invalid positions (e.g. comment lines), but then
367
+ // silently drops them. Re-validating here gives immediate feedback instead
368
+ // of only discovering the failure on the next "debug list".
369
+ const addedBps = updatedWithServerIds.filter(bp =>
370
+ added.some(a => a.uri === bp.uri && a.line === bp.line)
371
+ );
372
+ const { stale: newlyStale } = await refreshBreakpoints(config, adt, addedBps);
373
+ if (newlyStale.length > 0) {
374
+ // Remove stale from state
375
+ const stillValid = updatedWithServerIds.filter(bp =>
376
+ !newlyStale.some(s => s.uri === bp.uri && s.line === bp.line)
377
+ );
378
+ if (_saveBpState) _saveBpState(config, stillValid);
379
+
380
+ if (jsonOutput) {
381
+ console.log(JSON.stringify({ error: 'Breakpoint not accepted by server', stale: newlyStale.map(b => ({ object: b.object, line: b.line, error: b.error })) }));
382
+ } else {
383
+ newlyStale.forEach(({ object, line, error }) => {
384
+ console.error(`\n ❌ Breakpoint not accepted: ${object} line ${line}`);
385
+ console.error(` Reason: ${error || 'Not registered on server'}`);
386
+ console.error(` Tip: line must be an executable statement — not a comment, blank line,`);
387
+ console.error(` DATA declaration, or continuation line of a multi-line call.\n`);
388
+ });
389
+ process.exit(1);
390
+ }
391
+ return;
392
+ }
393
+
360
394
  if (jsonOutput) {
361
395
  const out = added.map(a => {
362
396
  const sr = serverResults.find(r => r.uri === a.uri && r.line === a.line);
@@ -461,7 +495,7 @@ async function cmdDelete(args, config, adt) {
461
495
  try {
462
496
  await adt.delete(`/sap/bc/adt/debugger/breakpoints/${encodeURIComponent(bpId)}`);
463
497
  } catch (err) {
464
- console.error(`\n Error: ${err.message || JSON.stringify(err)}\n`);
498
+ printHttpError(err, { prefix: ' Error' });
465
499
  process.exit(1);
466
500
  }
467
501
  if (jsonOutput) {
@@ -488,11 +522,11 @@ async function cmdDelete(args, config, adt) {
488
522
  try {
489
523
  await adt.delete(`/sap/bc/adt/debugger/breakpoints?clientId=${encodeURIComponent(ADT_CLIENT_ID)}`);
490
524
  } catch (err2) {
491
- console.error(`\n Error: ${err2.message || JSON.stringify(err2)}\n`);
525
+ printHttpError(err2, { prefix: ' Error' });
492
526
  process.exit(1);
493
527
  }
494
528
  } else {
495
- console.error(`\n Error: ${err.message || JSON.stringify(err)}\n`);
529
+ printHttpError(err, { prefix: ' Error' });
496
530
  process.exit(1);
497
531
  }
498
532
  }
@@ -622,7 +656,7 @@ async function cmdAttach(args, config, adt) {
622
656
  );
623
657
  process.exit(1);
624
658
  }
625
- console.error(`\n Error from ADT listener: ${err.message || JSON.stringify(err)}\n`);
659
+ printHttpError(err, { prefix: ' Error from ADT listener' });
626
660
  process.exit(1);
627
661
  }
628
662
 
@@ -708,7 +742,7 @@ async function cmdAttach(args, config, adt) {
708
742
  try {
709
743
  await session.attach(sessionId, (config.user || '').toUpperCase());
710
744
  } catch (e) {
711
- console.error(`\n Error during attach: ${e.message || JSON.stringify(e)}\n`);
745
+ printHttpError(e, { prefix: ' Error during attach' });
712
746
  if (e.body) console.error(' Response body:', e.body.substring(0, 400));
713
747
  process.exit(1);
714
748
  }
@@ -802,7 +836,7 @@ async function cmdStep(args, config, adt) {
802
836
  try {
803
837
  resp = await sendDaemonCommand(socketPath, { cmd: 'step', type }, 60000);
804
838
  } catch (err) {
805
- console.error(`\n Error: ${err.message}\n`);
839
+ printHttpError(err, { prefix: ' Error' });
806
840
  process.exit(1);
807
841
  }
808
842
  if (!resp.ok) {
@@ -846,7 +880,7 @@ async function cmdStep(args, config, adt) {
846
880
  );
847
881
  process.exit(1);
848
882
  }
849
- console.error(`\n Error: ${err.message || JSON.stringify(err)}${err.body ? '\n Body: ' + err.body.substring(0, 600) : ''}\n`);
883
+ printHttpError(err, { prefix: ' Error' });
850
884
  process.exit(1);
851
885
  }
852
886
 
@@ -892,7 +926,7 @@ async function cmdVars(args, config, adt) {
892
926
  try {
893
927
  resp = await sendDaemonCommand(socketPath, { cmd: 'vars', name: nameFilter }, 60000);
894
928
  } catch (err) {
895
- console.error(`\n Error: ${err.message}\n`);
929
+ printHttpError(err, { prefix: ' Error' });
896
930
  process.exit(1);
897
931
  }
898
932
  if (!resp.ok) {
@@ -923,7 +957,7 @@ async function cmdVars(args, config, adt) {
923
957
  );
924
958
  process.exit(1);
925
959
  }
926
- console.error(`\n Error: ${err.message || JSON.stringify(err)}\n`);
960
+ printHttpError(err, { prefix: ' Error' });
927
961
  process.exit(1);
928
962
  }
929
963
 
@@ -962,7 +996,7 @@ async function cmdExpand(expandName, sessionId, socketPath, config, adt, jsonOut
962
996
  try {
963
997
  result = await session.expandPath(pathParts);
964
998
  } catch (err) {
965
- console.error(`\n Error: ${err.message}\n`);
999
+ printHttpError(err, { prefix: ' Error' });
966
1000
  process.exit(1);
967
1001
  }
968
1002
  const { variable: target, children } = result;
@@ -977,7 +1011,7 @@ async function cmdExpand(expandName, sessionId, socketPath, config, adt, jsonOut
977
1011
  try {
978
1012
  resp = await sendDaemonCommand(socketPath, { cmd: 'vars', name: null }, 60000);
979
1013
  } catch (err) {
980
- console.error(`\n Error: ${err.message}\n`);
1014
+ printHttpError(err, { prefix: ' Error' });
981
1015
  process.exit(1);
982
1016
  }
983
1017
  if (!resp.ok) {
@@ -1009,7 +1043,7 @@ async function cmdExpand(expandName, sessionId, socketPath, config, adt, jsonOut
1009
1043
  try {
1010
1044
  resp = await sendDaemonCommand(socketPath, { cmd: 'expand', id: target.id, meta }, 60000);
1011
1045
  } catch (err) {
1012
- console.error(`\n Error: ${err.message}\n`);
1046
+ printHttpError(err, { prefix: ' Error' });
1013
1047
  process.exit(1);
1014
1048
  }
1015
1049
  if (!resp.ok) {
@@ -1070,7 +1104,7 @@ async function cmdStack(args, config, adt) {
1070
1104
  try {
1071
1105
  resp = await sendDaemonCommand(socketPath, { cmd: 'stack' }, 60000);
1072
1106
  } catch (err) {
1073
- console.error(`\n Error: ${err.message}\n`);
1107
+ printHttpError(err, { prefix: ' Error' });
1074
1108
  process.exit(1);
1075
1109
  }
1076
1110
  if (!resp.ok) {
@@ -1101,7 +1135,7 @@ async function cmdStack(args, config, adt) {
1101
1135
  );
1102
1136
  process.exit(1);
1103
1137
  }
1104
- console.error(`\n Error: ${err.message || JSON.stringify(err)}\n`);
1138
+ printHttpError(err, { prefix: ' Error' });
1105
1139
  process.exit(1);
1106
1140
  }
1107
1141
 
@@ -1164,7 +1198,7 @@ async function cmdTerminate(args, config, adt) {
1164
1198
  );
1165
1199
  process.exit(1);
1166
1200
  }
1167
- console.error(`\n Error: ${err.message || JSON.stringify(err)}\n`);
1201
+ printHttpError(err, { prefix: ' Error' });
1168
1202
  process.exit(1);
1169
1203
  }
1170
1204
 
@@ -3,11 +3,12 @@
3
3
  */
4
4
 
5
5
  const pathModule = require('path');
6
+ const { printHttpError } = require('../utils/format-error');
6
7
 
7
8
  /**
8
9
  * Inspect all files in one request
9
10
  */
10
- async function inspectAllFiles(files, csrfToken, config, variant, http) {
11
+ async function inspectAllFiles(files, csrfToken, config, variant, http, verbose = false) {
11
12
  // Convert files to uppercase names
12
13
  const fileNames = files.map(f => {
13
14
  const baseName = pathModule.basename(f).toUpperCase();
@@ -45,7 +46,7 @@ async function inspectAllFiles(files, csrfToken, config, variant, http) {
45
46
 
46
47
  return results;
47
48
  } catch (error) {
48
- console.error(`\n Error: ${error.message}`);
49
+ printHttpError(error, { verbose });
49
50
  process.exit(1);
50
51
  }
51
52
  }
@@ -149,6 +150,7 @@ module.exports = {
149
150
  const { loadConfig, AbapHttp } = context;
150
151
 
151
152
  const jsonOutput = args.includes('--json');
153
+ const verbose = args.includes('--verbose');
152
154
  const filesArgIndex = args.indexOf('--files');
153
155
  if (filesArgIndex === -1 || filesArgIndex + 1 >= args.length) {
154
156
  console.error('Error: --files parameter required');
@@ -178,7 +180,7 @@ module.exports = {
178
180
  const csrfToken = await http.fetchCsrfToken();
179
181
 
180
182
  // Send all files in one request
181
- const results = await inspectAllFiles(filesSyntaxCheck, csrfToken, config, variant, http);
183
+ const results = await inspectAllFiles(filesSyntaxCheck, csrfToken, config, variant, http, verbose);
182
184
 
183
185
  // JSON output mode
184
186
  if (jsonOutput) {
@@ -2,6 +2,8 @@
2
2
  * Pull command - Pull and activate ABAP objects from git repository
3
3
  */
4
4
 
5
+ const { printHttpError } = require('../utils/format-error');
6
+
5
7
  module.exports = {
6
8
  name: 'pull',
7
9
  description: 'Pull and activate ABAP objects from git repository',
@@ -10,6 +12,7 @@ module.exports = {
10
12
 
11
13
  async execute(args, context) {
12
14
  const { loadConfig, AbapHttp, gitUtils, getTransport, getSafeguards, getConflictSettings, getTransportSettings } = context;
15
+ const verbose = args.includes('--verbose');
13
16
 
14
17
  // Check project-level safeguards
15
18
  const safeguards = getSafeguards();
@@ -404,7 +407,7 @@ module.exports = {
404
407
  if (error._isPullError) {
405
408
  throw error;
406
409
  }
407
- console.error(`\n❌ Error: ${error.message}`);
410
+ printHttpError(error, { verbose });
408
411
  process.exit(1);
409
412
  }
410
413
  }
@@ -5,6 +5,7 @@
5
5
  const VALID_SCOPES = ['mine', 'task', 'all'];
6
6
  const VALID_SUBCOMMANDS = ['list', 'create', 'check', 'release'];
7
7
  const VALID_TYPES = ['workbench', 'customizing'];
8
+ const { printHttpError } = require('../utils/format-error');
8
9
 
9
10
  module.exports = {
10
11
  name: 'transport',
@@ -16,6 +17,7 @@ module.exports = {
16
17
  const { loadConfig, AbapHttp, getTransportSettings } = context;
17
18
 
18
19
  const jsonOutput = args.includes('--json');
20
+ const verbose = args.includes('--verbose');
19
21
 
20
22
  // Determine subcommand (first positional arg, default to 'list')
21
23
  const subcommand = args[0] && !args[0].startsWith('-') ? args[0] : 'list';
@@ -65,7 +67,7 @@ module.exports = {
65
67
  break;
66
68
  }
67
69
  } catch (error) {
68
- console.error(`❌ Error: ${error.message}`);
70
+ printHttpError(error, { verbose });
69
71
  process.exit(1);
70
72
  }
71
73
  },
@@ -4,11 +4,12 @@
4
4
 
5
5
  const pathModule = require('path');
6
6
  const fs = require('fs');
7
+ const { formatHttpError } = require('../utils/format-error');
7
8
 
8
9
  /**
9
10
  * Run unit test for a single file
10
11
  */
11
- async function runUnitTestForFile(sourceFile, csrfToken, config, coverage, http, jsonOutput = false) {
12
+ async function runUnitTestForFile(sourceFile, csrfToken, config, coverage, http, jsonOutput = false, verbose = false) {
12
13
  if (!jsonOutput) {
13
14
  console.log(` Running unit test for: ${sourceFile}`);
14
15
  }
@@ -123,14 +124,12 @@ async function runUnitTestForFile(sourceFile, csrfToken, config, coverage, http,
123
124
  }
124
125
 
125
126
  if (!jsonOutput) {
126
- console.error(`\n ❌ Error: ${error.message}`);
127
- if (error.statusCode) {
128
- console.error(` Status Code: ${error.statusCode}`);
129
- }
130
- if (error.body && typeof error.body === 'string') {
131
- // Show first 500 chars of error body
132
- const preview = error.body.substring(0, 500);
133
- console.error(` Response: ${preview}${error.body.length > 500 ? '...' : ''}`);
127
+ console.error(`\n ❌ Error: ${formatHttpError(error)}`);
128
+ if (verbose && error.body) {
129
+ console.error('\n--- Raw response body ---');
130
+ const raw = typeof error.body === 'object' ? JSON.stringify(error.body, null, 2) : String(error.body);
131
+ console.error(raw);
132
+ console.error('--- End of response body ---');
134
133
  }
135
134
  }
136
135
 
@@ -148,6 +147,7 @@ module.exports = {
148
147
  const { loadConfig, AbapHttp } = context;
149
148
 
150
149
  const jsonOutput = args.includes('--json');
150
+ const verbose = args.includes('--verbose');
151
151
  const filesArgIndex = args.indexOf('--files');
152
152
  if (filesArgIndex === -1 || filesArgIndex + 1 >= args.length) {
153
153
  console.error('Error: --files parameter required');
@@ -177,7 +177,7 @@ module.exports = {
177
177
  let hasErrors = false;
178
178
 
179
179
  for (const sourceFile of files) {
180
- const result = await runUnitTestForFile(sourceFile, csrfToken, config, coverage, http, jsonOutput);
180
+ const result = await runUnitTestForFile(sourceFile, csrfToken, config, coverage, http, jsonOutput, verbose);
181
181
  if (result) {
182
182
  results.push(result);
183
183