abapgit-agent 1.12.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -133
- package/abap/.github/copilot-instructions.slim.md +114 -0
- package/abap/CLAUDE.md +90 -25
- package/abap/CLAUDE.slim.md +42 -0
- package/abap/guidelines/abapgit.md +9 -47
- package/abap/guidelines/cds-testing.md +4 -1
- package/abap/guidelines/cds.md +38 -10
- package/abap/guidelines/testing.md +46 -2
- package/bin/abapgit-agent +1 -0
- package/package.json +3 -1
- package/src/commands/guide.js +276 -0
- package/src/commands/help.js +15 -0
- package/src/commands/init.js +132 -65
- package/src/commands/pull.js +69 -20
- package/src/utils/abap-reference.js +254 -118
|
@@ -22,4 +22,7 @@ grand_parent: ABAP Development
|
|
|
22
22
|
- Testing specific scenarios that may not exist in production
|
|
23
23
|
- Fast, isolated tests that don't depend on database state
|
|
24
24
|
|
|
25
|
-
See `
|
|
25
|
+
See `abapgit-agent ref --topic testing` for full code examples including:
|
|
26
|
+
- Basic CDS test double setup
|
|
27
|
+
- Testing CDS views with aggregations (insert into base tables)
|
|
28
|
+
- Testing CDS views that select from another CDS view (`create_for_multiple_cds`)
|
package/abap/guidelines/cds.md
CHANGED
|
@@ -8,7 +8,7 @@ grand_parent: ABAP Development
|
|
|
8
8
|
|
|
9
9
|
# Creating CDS Views
|
|
10
10
|
|
|
11
|
-
**Searchable keywords**: CDS, DDL, DDLS, CDS view, @AbapCatalog, @AccessControl, association, projection, consumption
|
|
11
|
+
**Searchable keywords**: CDS, DDL, DDLS, CDS view, @AbapCatalog, @AccessControl, association, projection, consumption, GROUP BY, aggregation, subquery, JOIN
|
|
12
12
|
|
|
13
13
|
## TOPICS IN THIS FILE
|
|
14
14
|
1. File Naming - line 96
|
|
@@ -145,20 +145,13 @@ where devclass not like '$%'
|
|
|
145
145
|
|
|
146
146
|
1. **Avoid reserved words** - Field names like `PACKAGE`, `CLASS`, `INTERFACE` are reserved in CDS. Use alternatives like `PackageName`, `ClassName`.
|
|
147
147
|
|
|
148
|
-
2. **Workflow for creating CDS views**
|
|
149
|
-
- Independent CDS views: `syntax → commit → pull --files`
|
|
150
|
-
- Dependent CDS views (with associations to NEW views): Create underlying view first, then dependent view
|
|
151
|
-
- See CLAUDE.md section on "Working with dependent objects"
|
|
148
|
+
2. **Workflow for creating CDS views** — run: `abapgit-agent ref --topic workflow-detailed`
|
|
152
149
|
|
|
153
150
|
3. **System support** - CDS views require SAP systems with CDS capability (S/4HANA, SAP BW/4HANA, or ABAP 7.51+). Older systems will show error: "Object type DDLS is not supported by this system"
|
|
154
151
|
|
|
155
152
|
### Activating CDS Views
|
|
156
153
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
**CDS-specific notes:**
|
|
160
|
-
- Single independent DDLS file: `abapgit-agent pull --files src/zc_view.ddls.asddls`
|
|
161
|
-
- CDS views with associations to OTHER NEW views: Create target view first (see `../CLAUDE.md` for workflow)
|
|
154
|
+
→ See `abapgit-agent ref --topic workflow-detailed` for the complete workflow.
|
|
162
155
|
|
|
163
156
|
## CDS View Entity Features
|
|
164
157
|
|
|
@@ -196,6 +189,39 @@ where devclass not like '$%'
|
|
|
196
189
|
|
|
197
190
|
## CDS Best Practices and Common Patterns
|
|
198
191
|
|
|
192
|
+
### No Inline Subqueries — Use JOIN + GROUP BY Instead
|
|
193
|
+
|
|
194
|
+
**CDS view entities do NOT support inline subqueries in JOIN.** This is a hard syntax error:
|
|
195
|
+
|
|
196
|
+
❌ **WRONG — inline subquery (syntax error)**:
|
|
197
|
+
```abap
|
|
198
|
+
define view entity ZC_MyView as select from zheader as Header
|
|
199
|
+
inner join ( // ← NOT SUPPORTED
|
|
200
|
+
select docid, sum( amount ) as Total
|
|
201
|
+
from zitems
|
|
202
|
+
group by docid
|
|
203
|
+
) as Items on Items.docid = Header.docid ...
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
✅ **CORRECT — JOIN base tables directly, use GROUP BY on the outer view**:
|
|
207
|
+
```abap
|
|
208
|
+
define view entity ZC_MyView
|
|
209
|
+
as select from zheader as Header
|
|
210
|
+
inner join zitems as Item
|
|
211
|
+
on Item.docid = Header.docid
|
|
212
|
+
{
|
|
213
|
+
key Header.docid as DocId,
|
|
214
|
+
Header.description as Description,
|
|
215
|
+
count(*) as NumberOfItems,
|
|
216
|
+
sum(Item.amount) as TotalAmount
|
|
217
|
+
}
|
|
218
|
+
group by Header.docid, Header.description
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**This works.** CDS supports JOIN + GROUP BY + aggregation functions in a single view. Only inline subqueries in the FROM/JOIN clause are unsupported.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
199
225
|
### Key Field Ordering (STRICT RULE)
|
|
200
226
|
|
|
201
227
|
CDS views enforce strict key field ordering that differs from regular SQL:
|
|
@@ -235,6 +261,8 @@ CDS views enforce strict key field ordering that differs from regular SQL:
|
|
|
235
261
|
|
|
236
262
|
### Currency/Amount Field Aggregation
|
|
237
263
|
|
|
264
|
+
> **Design principle**: Prefer a **single CDS view** with JOIN + GROUP BY. Only split into multiple layered views when requirements specifically need reusable intermediate aggregations shared across different consumers.
|
|
265
|
+
|
|
238
266
|
When aggregating currency or amount fields in CDS views, use semantic annotations instead of complex casting:
|
|
239
267
|
|
|
240
268
|
❌ **WRONG - Complex casting (will fail)**:
|
|
@@ -290,6 +290,49 @@ METHOD test_aggregation.
|
|
|
290
290
|
ENDMETHOD.
|
|
291
291
|
```
|
|
292
292
|
|
|
293
|
+
### Testing CDS Views that Select from Another CDS View
|
|
294
|
+
|
|
295
|
+
> **Note:** This pattern applies when your design **already has** a CDS view that selects from another CDS view. It does NOT mean you should split a single view into two — use a single CDS view with GROUP BY / JOIN when the business logic fits.
|
|
296
|
+
|
|
297
|
+
When your CDS view selects from **another CDS view** (not a base table), `create` will raise `CX_CDS_FAILURE`. Use `create_for_multiple_cds` instead and list all CDS entities in the dependency chain.
|
|
298
|
+
|
|
299
|
+
```abap
|
|
300
|
+
METHOD class_setup.
|
|
301
|
+
" ZC_TopView selects from ZC_IntermediateView (another CDS view entity)
|
|
302
|
+
" → must use create_for_multiple_cds and list all CDS entities
|
|
303
|
+
mo_cds_env_static = cl_cds_test_environment=>create_for_multiple_cds(
|
|
304
|
+
i_for_entities = VALUE #(
|
|
305
|
+
( 'ZC_TOPVIEW' ) " the view under test
|
|
306
|
+
( 'ZC_INTERMEDIATEVIEW' ) " the CDS view it selects from
|
|
307
|
+
) ).
|
|
308
|
+
ENDMETHOD.
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Insert test data into the intermediate CDS view (not the base tables), since that is what the top-level view reads:
|
|
312
|
+
|
|
313
|
+
```abap
|
|
314
|
+
METHOD test_read.
|
|
315
|
+
DATA lt_source TYPE TABLE OF zc_intermediateview WITH EMPTY KEY.
|
|
316
|
+
lt_source = VALUE #(
|
|
317
|
+
( field1 = 'A' field2 = 100 )
|
|
318
|
+
( field1 = 'B' field2 = 200 ) ).
|
|
319
|
+
mo_cds_env->insert_test_data( i_data = lt_source ).
|
|
320
|
+
|
|
321
|
+
SELECT * FROM zc_topview INTO TABLE @DATA(lt_result).
|
|
322
|
+
|
|
323
|
+
cl_abap_unit_assert=>assert_equals(
|
|
324
|
+
exp = 2 act = lines( lt_result ) msg = 'Expected 2 rows' ).
|
|
325
|
+
ENDMETHOD.
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
**Rules:**
|
|
329
|
+
- List the view under test **and all CDS views it depends on** in `i_for_entities`
|
|
330
|
+
- Insert data into the **direct source** of the top-level view (the intermediate CDS view)
|
|
331
|
+
- Order in `i_for_entities` does not matter
|
|
332
|
+
- If you get `CX_CDS_FAILURE` when using `create`, switch to `create_for_multiple_cds`
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
293
336
|
### Key Classes for CDS Testing
|
|
294
337
|
|
|
295
338
|
| Item | Type/Usage |
|
|
@@ -304,7 +347,8 @@ ENDMETHOD.
|
|
|
304
347
|
|
|
305
348
|
| Method | Purpose |
|
|
306
349
|
|--------|---------|
|
|
307
|
-
| `CL_CDS_TEST_ENVIRONMENT=>create( i_for_entity = ... )` | Create test environment
|
|
350
|
+
| `CL_CDS_TEST_ENVIRONMENT=>create( i_for_entity = ... )` | Create test environment for a CDS view over base tables |
|
|
351
|
+
| `CL_CDS_TEST_ENVIRONMENT=>create_for_multiple_cds( i_for_entities = ... )` | Create test environment when the CDS view selects from another CDS view |
|
|
308
352
|
| `insert_test_data( i_data = ... )` | Insert test data into test doubles |
|
|
309
353
|
| `clear_doubles` | Clear test data before each test method |
|
|
310
354
|
| `destroy` | Clean up after test class |
|
|
@@ -314,7 +358,7 @@ ENDMETHOD.
|
|
|
314
358
|
1. **Use interface type**: `DATA mo_cds_env TYPE REF TO if_cds_test_environment` - the CREATE method returns an interface reference
|
|
315
359
|
2. **CLASS-METHODS required**: `class_setup` and `class_teardown` must be declared with `CLASS-METHODS` (not `METHODS`)
|
|
316
360
|
3. **Table type declaration**: Must declare `DATA lt_tab TYPE TABLE OF <type> WITH EMPTY KEY` before using `VALUE #()`
|
|
317
|
-
4. **Auto-created dependencies**: CDS framework auto-creates test doubles
|
|
361
|
+
4. **Auto-created dependencies**: When the CDS view selects only from **base tables**, the framework auto-creates test doubles — do not specify `i_dependency_list`. When the CDS view selects from **another CDS view**, use `create_for_multiple_cds` instead (see section below).
|
|
318
362
|
5. **Aggregations**: For CDS views with SUM/COUNT/GROUP BY, insert test data into base tables (SFLIGHT, SCARR, etc.)
|
|
319
363
|
6. **Clear doubles**: Call `clear_doubles` in `setup` method before each test
|
|
320
364
|
7. **Enable associations**: Set `test_associations = 'X'` only if testing CDS associations
|
package/bin/abapgit-agent
CHANGED
|
@@ -54,6 +54,7 @@ async function main() {
|
|
|
54
54
|
debug: require('../src/commands/debug'),
|
|
55
55
|
run: require('../src/commands/run'),
|
|
56
56
|
ref: require('../src/commands/ref'),
|
|
57
|
+
guide: require('../src/commands/guide'),
|
|
57
58
|
init: require('../src/commands/init'),
|
|
58
59
|
pull: require('../src/commands/pull'),
|
|
59
60
|
upgrade: require('../src/commands/upgrade'),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "abapgit-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
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": [
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"src/",
|
|
9
9
|
"abap/guidelines/",
|
|
10
10
|
"abap/CLAUDE.md",
|
|
11
|
+
"abap/CLAUDE.slim.md",
|
|
11
12
|
"abap/.github/copilot-instructions.md",
|
|
13
|
+
"abap/.github/copilot-instructions.slim.md",
|
|
12
14
|
".abapGitAgent.example"
|
|
13
15
|
],
|
|
14
16
|
"bin": {
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const readline = require('readline');
|
|
6
|
+
|
|
7
|
+
// Marker present in the old full-guide CLAUDE.md (copied by init before this feature)
|
|
8
|
+
const FULL_GUIDE_MARKER = 'Claude Code Instructions';
|
|
9
|
+
|
|
10
|
+
// Marker present in the slim stub (so we can detect it's already migrated)
|
|
11
|
+
const SLIM_STUB_MARKER = 'abapgit-agent guide';
|
|
12
|
+
|
|
13
|
+
// Marker present in the old full copilot-instructions.md
|
|
14
|
+
const COPILOT_FULL_MARKER = '# ABAP Development with abapGit';
|
|
15
|
+
|
|
16
|
+
// Marker present in the slim copilot stub
|
|
17
|
+
const COPILOT_SLIM_MARKER = 'abapgit-agent guide';
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
name: 'guide',
|
|
21
|
+
description: 'Show bundled ABAP development guide',
|
|
22
|
+
requiresAbapConfig: false,
|
|
23
|
+
|
|
24
|
+
_findBundledGuide() {
|
|
25
|
+
const candidates = [
|
|
26
|
+
path.join(__dirname, '..', '..', 'abap', 'CLAUDE.md'),
|
|
27
|
+
path.join(__dirname, '..', '..', '..', 'abap', 'CLAUDE.md')
|
|
28
|
+
];
|
|
29
|
+
return candidates.find(p => fs.existsSync(p)) || null;
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
_findSlimStub() {
|
|
33
|
+
const candidates = [
|
|
34
|
+
path.join(__dirname, '..', '..', 'abap', 'CLAUDE.slim.md'),
|
|
35
|
+
path.join(__dirname, '..', '..', '..', 'abap', 'CLAUDE.slim.md')
|
|
36
|
+
];
|
|
37
|
+
return candidates.find(p => fs.existsSync(p)) || null;
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
_findCopilotSlimStub() {
|
|
41
|
+
const candidates = [
|
|
42
|
+
path.join(__dirname, '..', '..', 'abap', '.github', 'copilot-instructions.slim.md'),
|
|
43
|
+
path.join(__dirname, '..', '..', '..', 'abap', '.github', 'copilot-instructions.slim.md')
|
|
44
|
+
];
|
|
45
|
+
return candidates.find(p => fs.existsSync(p)) || null;
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
_getBundledGuidelineNames() {
|
|
49
|
+
const candidates = [
|
|
50
|
+
path.join(__dirname, '..', '..', 'abap', 'guidelines'),
|
|
51
|
+
path.join(__dirname, '..', '..', '..', 'abap', 'guidelines')
|
|
52
|
+
];
|
|
53
|
+
const guidelinesDir = candidates.find(p => fs.existsSync(p));
|
|
54
|
+
if (!guidelinesDir) return new Set();
|
|
55
|
+
return new Set(
|
|
56
|
+
fs.readdirSync(guidelinesDir).filter(f => f.endsWith('.md'))
|
|
57
|
+
);
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
async _confirm(question) {
|
|
61
|
+
return new Promise((resolve) => {
|
|
62
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
63
|
+
rl.question(question, (answer) => {
|
|
64
|
+
rl.close();
|
|
65
|
+
const normalized = answer.trim().toLowerCase();
|
|
66
|
+
resolve(normalized === '' || normalized === 'y' || normalized === 'yes');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
async _runMigrate(args) {
|
|
72
|
+
const dryRun = args.includes('--dry-run');
|
|
73
|
+
const yes = args.includes('--yes') || args.includes('-y');
|
|
74
|
+
const cwd = process.cwd();
|
|
75
|
+
|
|
76
|
+
const bundledNames = this._getBundledGuidelineNames();
|
|
77
|
+
const slimStubPath = this._findSlimStub();
|
|
78
|
+
const copilotSlimStubPath = this._findCopilotSlimStub();
|
|
79
|
+
|
|
80
|
+
// --- Scan guidelines/ ---
|
|
81
|
+
const guidelinesDir = path.join(cwd, 'guidelines');
|
|
82
|
+
const toDelete = []; // standard files matching bundled names
|
|
83
|
+
const toKeep = []; // *.local.md or other non-standard files
|
|
84
|
+
|
|
85
|
+
if (fs.existsSync(guidelinesDir)) {
|
|
86
|
+
for (const name of fs.readdirSync(guidelinesDir)) {
|
|
87
|
+
if (!name.endsWith('.md')) continue;
|
|
88
|
+
if (bundledNames.has(name)) {
|
|
89
|
+
toDelete.push(path.join(guidelinesDir, name));
|
|
90
|
+
} else {
|
|
91
|
+
toKeep.push(name);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// --- Scan CLAUDE.md ---
|
|
97
|
+
const claudeMdPath = path.join(cwd, 'CLAUDE.md');
|
|
98
|
+
let claudeMdAction = 'none'; // 'replace' | 'already-slim' | 'custom' | 'missing'
|
|
99
|
+
if (fs.existsSync(claudeMdPath)) {
|
|
100
|
+
const content = fs.readFileSync(claudeMdPath, 'utf8');
|
|
101
|
+
if (content.includes(SLIM_STUB_MARKER)) {
|
|
102
|
+
claudeMdAction = 'already-slim';
|
|
103
|
+
} else if (content.includes(FULL_GUIDE_MARKER)) {
|
|
104
|
+
claudeMdAction = 'replace';
|
|
105
|
+
} else {
|
|
106
|
+
claudeMdAction = 'custom';
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
claudeMdAction = 'missing';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// --- Scan .github/copilot-instructions.md ---
|
|
113
|
+
const copilotMdPath = path.join(cwd, '.github', 'copilot-instructions.md');
|
|
114
|
+
let copilotAction = 'none'; // 'replace' | 'already-slim' | 'custom' | 'missing'
|
|
115
|
+
if (fs.existsSync(copilotMdPath)) {
|
|
116
|
+
const content = fs.readFileSync(copilotMdPath, 'utf8');
|
|
117
|
+
if (content.includes(COPILOT_SLIM_MARKER)) {
|
|
118
|
+
copilotAction = 'already-slim';
|
|
119
|
+
} else if (content.includes(COPILOT_FULL_MARKER)) {
|
|
120
|
+
copilotAction = 'replace';
|
|
121
|
+
} else {
|
|
122
|
+
copilotAction = 'custom';
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
copilotAction = 'missing';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// --- Nothing to do? ---
|
|
129
|
+
const nothingToDo = toDelete.length === 0 && claudeMdAction !== 'replace' && copilotAction !== 'replace';
|
|
130
|
+
if (nothingToDo) {
|
|
131
|
+
console.log('');
|
|
132
|
+
console.log('✅ Already clean — nothing to migrate.');
|
|
133
|
+
if (claudeMdAction === 'already-slim') {
|
|
134
|
+
console.log(' CLAUDE.md is already the slim stub.');
|
|
135
|
+
} else if (claudeMdAction === 'custom') {
|
|
136
|
+
console.log(' CLAUDE.md has custom content — left untouched.');
|
|
137
|
+
} else if (claudeMdAction === 'missing') {
|
|
138
|
+
console.log(' No CLAUDE.md found.');
|
|
139
|
+
}
|
|
140
|
+
if (copilotAction === 'already-slim') {
|
|
141
|
+
console.log(' .github/copilot-instructions.md is already the slim stub.');
|
|
142
|
+
} else if (copilotAction === 'custom') {
|
|
143
|
+
console.log(' .github/copilot-instructions.md has custom content — left untouched.');
|
|
144
|
+
}
|
|
145
|
+
if (toDelete.length === 0 && fs.existsSync(guidelinesDir)) {
|
|
146
|
+
console.log(' No standard guideline files found in guidelines/.');
|
|
147
|
+
}
|
|
148
|
+
console.log('');
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// --- Preview ---
|
|
153
|
+
console.log('');
|
|
154
|
+
console.log('🔄 guide --migrate: switch to bundled guidelines');
|
|
155
|
+
console.log('');
|
|
156
|
+
|
|
157
|
+
if (toDelete.length > 0) {
|
|
158
|
+
console.log(`Files to remove (${toDelete.length} standard guideline file${toDelete.length > 1 ? 's' : ''}):`);
|
|
159
|
+
toDelete.forEach(f => console.log(` ${path.relative(cwd, f)}`));
|
|
160
|
+
console.log('');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (toKeep.length > 0) {
|
|
164
|
+
console.log('Files to keep (project-specific):');
|
|
165
|
+
toKeep.forEach(f => console.log(` guidelines/${f}`));
|
|
166
|
+
console.log('');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (claudeMdAction === 'replace') {
|
|
170
|
+
if (slimStubPath) {
|
|
171
|
+
console.log('CLAUDE.md: detected as full guide → will replace with slim stub');
|
|
172
|
+
console.log(" (run 'abapgit-agent guide' to read the full guide on demand)");
|
|
173
|
+
} else {
|
|
174
|
+
console.log('CLAUDE.md: detected as full guide → ⚠️ slim stub not found, will skip');
|
|
175
|
+
}
|
|
176
|
+
console.log('');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (copilotAction === 'replace') {
|
|
180
|
+
if (copilotSlimStubPath) {
|
|
181
|
+
console.log('.github/copilot-instructions.md: detected as full guide → will replace with slim stub');
|
|
182
|
+
console.log(' (Copilot uses the slim stub; full guide available online)');
|
|
183
|
+
} else {
|
|
184
|
+
console.log('.github/copilot-instructions.md: detected as full guide → ⚠️ slim stub not found, will skip');
|
|
185
|
+
}
|
|
186
|
+
console.log('');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// After deletions, would guidelines/ be empty?
|
|
190
|
+
const dirWillBeEmpty = fs.existsSync(guidelinesDir) && toKeep.length === 0;
|
|
191
|
+
if (dirWillBeEmpty) {
|
|
192
|
+
console.log('guidelines/ will be removed (no project-specific files remain).');
|
|
193
|
+
console.log('');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (dryRun) {
|
|
197
|
+
console.log('ℹ️ Dry run — no changes made.');
|
|
198
|
+
console.log('');
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// --- Confirm ---
|
|
203
|
+
if (!yes) {
|
|
204
|
+
const proceed = await this._confirm('Proceed? [y/N] ');
|
|
205
|
+
if (!proceed) {
|
|
206
|
+
console.log('Migration cancelled.');
|
|
207
|
+
console.log('');
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// --- Execute ---
|
|
213
|
+
console.log('');
|
|
214
|
+
let deletedCount = 0;
|
|
215
|
+
|
|
216
|
+
for (const filePath of toDelete) {
|
|
217
|
+
fs.unlinkSync(filePath);
|
|
218
|
+
console.log(`🗑️ Removed ${path.relative(cwd, filePath)}`);
|
|
219
|
+
deletedCount++;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (dirWillBeEmpty) {
|
|
223
|
+
// Verify no other files remain before removing the directory
|
|
224
|
+
const remaining = fs.readdirSync(guidelinesDir);
|
|
225
|
+
if (remaining.length === 0) {
|
|
226
|
+
fs.rmdirSync(guidelinesDir);
|
|
227
|
+
console.log('🗑️ Removed guidelines/');
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (claudeMdAction === 'replace' && slimStubPath) {
|
|
232
|
+
const slimContent = fs.readFileSync(slimStubPath, 'utf8');
|
|
233
|
+
fs.writeFileSync(claudeMdPath, slimContent);
|
|
234
|
+
console.log('✅ Replaced CLAUDE.md with slim stub');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (copilotAction === 'replace' && copilotSlimStubPath) {
|
|
238
|
+
const slimContent = fs.readFileSync(copilotSlimStubPath, 'utf8');
|
|
239
|
+
fs.writeFileSync(copilotMdPath, slimContent);
|
|
240
|
+
console.log('✅ Replaced .github/copilot-instructions.md with slim stub');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
console.log('');
|
|
244
|
+
console.log('✅ Migration complete.');
|
|
245
|
+
console.log(' Standard guidelines are now read from the package automatically.');
|
|
246
|
+
console.log(" Run 'abapgit-agent ref \"<pattern>\"' or 'abapgit-agent ref --topic <topic>' to search them.");
|
|
247
|
+
console.log('');
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
async execute(args) {
|
|
251
|
+
if (args.includes('--migrate')) {
|
|
252
|
+
return this._runMigrate(args);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const filePath = this._findBundledGuide();
|
|
256
|
+
|
|
257
|
+
if (!filePath) {
|
|
258
|
+
console.error('❌ Bundled CLAUDE.md not found. Make sure abapgit-agent is properly installed.');
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (args.includes('--path')) {
|
|
263
|
+
console.log(filePath);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
268
|
+
|
|
269
|
+
if (args.includes('--json')) {
|
|
270
|
+
console.log(JSON.stringify({ path: filePath, content }));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
console.log(content);
|
|
275
|
+
}
|
|
276
|
+
};
|
package/src/commands/help.js
CHANGED
|
@@ -78,6 +78,19 @@ Commands:
|
|
|
78
78
|
- Use full URL: https://github.com/user/repo.git
|
|
79
79
|
- Or short name: user/repo or user/repo (assumes github.com)
|
|
80
80
|
|
|
81
|
+
guide [--path] [--json]
|
|
82
|
+
Show the full ABAP development guide bundled with the package.
|
|
83
|
+
Reads directly from the installed npm package — always up-to-date.
|
|
84
|
+
- Use --path to print only the file path (useful for pagers)
|
|
85
|
+
- Use --json for machine-readable output
|
|
86
|
+
|
|
87
|
+
guide --migrate [--dry-run] [--yes]
|
|
88
|
+
Migrate repo from locally-copied guidelines to bundled fallback.
|
|
89
|
+
Removes standard guideline files copied by old 'init', replaces full CLAUDE.md
|
|
90
|
+
with slim stub. Project-specific files (*.local.md) are never removed.
|
|
91
|
+
- Use --dry-run to preview changes without applying them
|
|
92
|
+
- Use --yes to skip confirmation prompt
|
|
93
|
+
|
|
81
94
|
health
|
|
82
95
|
Check if ABAP REST API is healthy
|
|
83
96
|
|
|
@@ -107,6 +120,8 @@ Examples:
|
|
|
107
120
|
abapgit-agent dump --user DEVELOPER --detail 1 # Full detail of first result
|
|
108
121
|
abapgit-agent ref "CORRESPONDING" # Search for pattern
|
|
109
122
|
abapgit-agent ref --topic exceptions # View exceptions topic
|
|
123
|
+
abapgit-agent guide # Full ABAP dev guide
|
|
124
|
+
abapgit-agent guide --path # Path to guide file
|
|
110
125
|
abapgit-agent health # Health check
|
|
111
126
|
abapgit-agent status # Configuration status
|
|
112
127
|
|