abapgit-agent 1.4.0 → 1.5.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.
@@ -0,0 +1,289 @@
1
+ # list Command Requirements
2
+
3
+ ## Overview
4
+
5
+ List ABAP objects in a package with filtering and pagination capabilities. This command provides a flat list of objects with support for filtering by type, name pattern, and pagination.
6
+
7
+ ## Command
8
+
9
+ ```bash
10
+ # List all objects in a package
11
+ abapgit-agent list --package $ZMY_PACKAGE
12
+
13
+ # Filter by object type
14
+ abapgit-agent list --package $ZMY_PACKAGE --type CLAS,INTF
15
+
16
+ # Filter by name pattern
17
+ abapgit-agent list --package $ZMY_PACKAGE --name ZCL_*
18
+
19
+ # Limit results
20
+ abapgit-agent list --package $ZMY_PACKAGE --limit 50
21
+
22
+ # Paginate results
23
+ abapgit-agent list --package $ZMY_PACKAGE --offset 100 --limit 50
24
+
25
+ # JSON output for scripting
26
+ abapgit-agent list --package $ZMY_PACKAGE --json
27
+ ```
28
+
29
+ ## Prerequisite
30
+
31
+ - `.abapGitAgent` exists with valid credentials
32
+ - Package must exist in the ABAP system
33
+
34
+ ## Parameters
35
+
36
+ | Parameter | Required | Default | Description |
37
+ |-----------|----------|---------|-------------|
38
+ | `--package` | Yes | - | Package name (e.g., `$ZMY_PACKAGE`, `ZMY_PACKAGE`) |
39
+ | `--type` | No | All types | Comma-separated object types (e.g., `CLAS,INTF,PROG`) |
40
+ | `--name` | No | - | Name pattern with wildcard support (e.g., `ZCL_*`) |
41
+ | `--limit` | No | 100 | Maximum objects to return (max: 1000) |
42
+ | `--offset` | No | 0 | Number of objects to skip |
43
+ | `--json` | No | false | Output raw JSON only |
44
+
45
+ ---
46
+
47
+ ## Tasks
48
+
49
+ ### 1. Validate Parameters
50
+
51
+ - `--package` must be specified
52
+ - Package name must be valid (1-30 characters)
53
+ - `--limit` must be between 1 and 1000
54
+
55
+ ### 2. Load Configuration
56
+
57
+ Read `.abapGitAgent` for credentials
58
+
59
+ ### 3. Fetch CSRF Token
60
+
61
+ ```bash
62
+ GET /health (with X-CSRF-Token: fetch)
63
+ ```
64
+
65
+ ### 4. Make List Request
66
+
67
+ **Endpoint:** `POST /list`
68
+
69
+ **Request Body:**
70
+ ```json
71
+ {
72
+ "package": "$ZMY_PACKAGE",
73
+ "type": "CLAS,INTF",
74
+ "name": "ZCL_*",
75
+ "limit": 100,
76
+ "offset": 0
77
+ }
78
+ ```
79
+
80
+ ### 5. Display Results
81
+
82
+ ---
83
+
84
+ ## Output
85
+
86
+ ### Human-Readable Output
87
+
88
+ ```
89
+ 📋 Objects in $ZMY_PACKAGE (Total: 15)
90
+
91
+ CLAS (5)
92
+ ZCL_CLASS1
93
+ ZCL_CLASS2
94
+ ZCL_CLASS3
95
+ ZCL_CLASS4
96
+ ZCL_CLASS5
97
+
98
+ INTF (2)
99
+ ZIF_INTERFACE1
100
+ ZIF_INTERFACE2
101
+
102
+ PROG (3)
103
+ ZPROG1
104
+ ZPROG2
105
+ ZPROG3
106
+
107
+ TABL (5)
108
+ ZTABLE1
109
+ ZTABLE2
110
+ ZTABLE3
111
+ ZTABLE4
112
+ ZTABLE5
113
+ ```
114
+
115
+ ### With Type Filter
116
+
117
+ ```
118
+ 📋 Objects in $ZMY_PACKAGE (CLAS only, Total: 5)
119
+
120
+ CLAS (5)
121
+ ZCL_CLASS1
122
+ ZCL_CLASS2
123
+ ZCL_CLASS3
124
+ ZCL_CLASS4
125
+ ZCL_CLASS5
126
+ ```
127
+
128
+ ### JSON Output
129
+
130
+ ```json
131
+ {
132
+ "SUCCESS": true,
133
+ "COMMAND": "LIST",
134
+ "PACKAGE": "$ZMY_PACKAGE",
135
+ "TOTAL": 15,
136
+ "LIMIT": 100,
137
+ "OFFSET": 0,
138
+ "OBJECTS": [
139
+ { "TYPE": "CLAS", "NAME": "ZCL_CLASS1" },
140
+ { "TYPE": "CLAS", "NAME": "ZCL_CLASS2" },
141
+ { "TYPE": "CLAS", "NAME": "ZCL_CLASS3" },
142
+ { "TYPE": "CLAS", "NAME": "ZCL_CLASS4" },
143
+ { "TYPE": "CLAS", "NAME": "ZCL_CLASS5" },
144
+ { "TYPE": "INTF", "NAME": "ZIF_INTERFACE1" },
145
+ { "TYPE": "INTF", "NAME": "ZIF_INTERFACE2" },
146
+ { "TYPE": "PROG", "NAME": "ZPROG1" },
147
+ { "TYPE": "PROG", "NAME": "ZPROG2" },
148
+ { "TYPE": "PROG", "NAME": "ZPROG3" },
149
+ { "TYPE": "TABL", "NAME": "ZTABLE1" },
150
+ { "TYPE": "TABL", "NAME": "ZTABLE2" },
151
+ { "TYPE": "TABL", "NAME": "ZTABLE3" },
152
+ { "TYPE": "TABL", "NAME": "ZTABLE4" },
153
+ { "TYPE": "TABL", "NAME": "ZTABLE5" }
154
+ ],
155
+ "BY_TYPE": [
156
+ { "TYPE": "CLAS", "COUNT": 5 },
157
+ { "TYPE": "INTF", "COUNT": 2 },
158
+ { "TYPE": "PROG", "COUNT": 3 },
159
+ { "TYPE": "TABL", "COUNT": 5 }
160
+ ],
161
+ "ERROR": ""
162
+ }
163
+ ```
164
+
165
+ **Response Fields:**
166
+
167
+ | Field | Type | Description |
168
+ |-------|------|-------------|
169
+ | `SUCCESS` | boolean | Whether the request succeeded |
170
+ | `COMMAND` | string | Command name ("LIST") |
171
+ | `PACKAGE` | string | Package name |
172
+ | `TOTAL` | number | Total objects matching filter |
173
+ | `LIMIT` | number | Requested limit |
174
+ | `OFFSET` | number | Requested offset |
175
+ | `OBJECTS` | array | List of objects [{TYPE, NAME}] |
176
+ | `BY_TYPE` | array | Object counts by type [{TYPE, COUNT}] |
177
+ | `ERROR` | string | Error message (empty if success) |
178
+
179
+ ---
180
+
181
+ ## Error Handling
182
+
183
+ | Error | Message |
184
+ |-------|---------|
185
+ | Package not specified | `Package parameter is required` |
186
+ | Package not found | `Package <name> does not exist` |
187
+ | Invalid type | `Invalid object type: <type>` |
188
+ | Limit too high | `Limit value too high (max: 1000)` |
189
+
190
+ ### Error Output
191
+
192
+ ```
193
+ ❌ Package not found: $ZNONEXISTENT
194
+
195
+ Error: Package $ZNONEXISTENT does not exist in the system.
196
+ ```
197
+
198
+ ---
199
+
200
+ ## Example
201
+
202
+ ```bash
203
+ # List all objects
204
+ abapgit-agent list --package $ZMY_PACKAGE
205
+
206
+ # Filter by type
207
+ abapgit-agent list --package $ZMY_PACKAGE --type CLAS,INTF
208
+
209
+ # Filter by name pattern
210
+ abapgit-agent list --package $ZMY_PACKAGE --name ZCL_*
211
+
212
+ # Paginate
213
+ abapgit-agent list --package $ZMY_PACKAGE --limit 50 --offset 50
214
+
215
+ # JSON for scripting
216
+ abapgit-agent list --package $ZMY_PACKAGE --json > objects.json
217
+
218
+ # CI/CD: Count classes
219
+ CLASS_COUNT=$(abapgit-agent list --package $ZMY_PACKAGE --type CLAS --json | jq '.TOTAL')
220
+ echo "Package has $CLASS_COUNT classes"
221
+ ```
222
+
223
+ ---
224
+
225
+ ## Implementation
226
+
227
+ ### ABAP Tables Used
228
+
229
+ | Table | Purpose |
230
+ |-------|---------|
231
+ | **TDEVC** | Package definitions (validate package exists) |
232
+ | **TADIR** | Object directory (fetch objects in package) |
233
+
234
+ ### Supported Object Types
235
+
236
+ | Type | Description |
237
+ |------|-------------|
238
+ | CLAS | Class |
239
+ | INTF | Interface |
240
+ | PROG | Program |
241
+ | FUGR | Function Group |
242
+ | TABL | Table |
243
+ | STRU | Structure |
244
+ | DTEL | Data Element |
245
+ | TTYP | Table Type |
246
+ | DDLS | CDS View |
247
+ | DDLX | CDS View Entity |
248
+
249
+ ### Query Logic
250
+
251
+ ```abap
252
+ " Validate package exists
253
+ SELECT SINGLE devclass FROM tdevc
254
+ INTO lv_package
255
+ WHERE devclass = iv_package.
256
+
257
+ " Get objects with filters
258
+ SELECT object obj_name FROM tadir
259
+ INTO TABLE lt_objects
260
+ WHERE devclass = iv_package
261
+ AND object IN lt_types
262
+ AND obj_name LIKE lv_name_pattern
263
+ ORDER BY object obj_name
264
+ LIMIT iv_limit
265
+ OFFSET iv_offset.
266
+
267
+ " Get counts by type (for summary)
268
+ SELECT object COUNT(*) AS count FROM tadir
269
+ INTO TABLE lt_counts
270
+ WHERE devclass = iv_package
271
+ AND object IN lt_types
272
+ GROUP BY object.
273
+ ```
274
+
275
+ ### Files to Create
276
+
277
+ | File | Description |
278
+ |------|-------------|
279
+ | `zcl_abgagt_command_list.clas.abap` | Command implementation |
280
+ | `zcl_abgagt_command_list.clas.xml` | Class metadata |
281
+ | `zcl_abgagt_resource_list.clas.abap` | REST resource handler |
282
+ | `zcl_abgagt_resource_list.clas.xml` | Resource metadata |
283
+
284
+ ### Files to Modify
285
+
286
+ | File | Description |
287
+ |------|-------------|
288
+ | `zif_abgagt_command.intf.abap` | Add LIST constant |
289
+ | `zcl_abgagt_cmd_factory.clas.abap` | Add LIST command mapping |
@@ -24,6 +24,9 @@ abapgit-agent pull --transport DEVK900001
24
24
 
25
25
  # Combined options
26
26
  abapgit-agent pull --branch develop --files src/zcl_my_class.clas.abap --transport DEVK900001
27
+
28
+ # Using transport from config/environment (no --transport flag needed)
29
+ abapgit-agent pull
27
30
  ```
28
31
 
29
32
  ## Prerequisite
@@ -39,7 +42,18 @@ abapgit-agent pull --branch develop --files src/zcl_my_class.clas.abap --transpo
39
42
  | `--url` | No | Git repository URL (auto-detected if not specified) |
40
43
  | `--branch` | No | Branch name (default: current branch) |
41
44
  | `--files` | No | Comma-separated list of files to pull |
42
- | `--transport` | No | Transport request for activation |
45
+ | `--transport` | No | Transport request (config/env takes priority if not specified) |
46
+
47
+ ## Transport Request Precedence
48
+
49
+ The transport request is determined in this order:
50
+
51
+ | Priority | Source | Example |
52
+ |----------|--------|---------|
53
+ | 1 | CLI `--transport` argument | `--transport DEVK900001` |
54
+ | 2 | Config file `transport` | `"transport": "DEVK900001"` in `.abapGitAgent` |
55
+ | 3 | Environment variable `ABAP_TRANSPORT` | `export ABAP_TRANSPORT="DEVK900001"` |
56
+ | 4 (default) | Not set | abapGit creates/uses default |
43
57
 
44
58
  ---
45
59
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abapgit-agent",
3
- "version": "1.4.0",
3
+ "version": "1.5.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
  "bin": {
@@ -10,7 +10,9 @@
10
10
  "start": "node src/server.js",
11
11
  "dev": "nodemon src/server.js",
12
12
  "test": "jest",
13
- "pull": "node bin/abapgit-agent"
13
+ "pull": "node bin/abapgit-agent",
14
+ "release": "node scripts/release.js",
15
+ "unrelease": "node scripts/unrelease.js"
14
16
  },
15
17
  "dependencies": {
16
18
  "cors": "^2.8.5",
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Release script - Creates release for github.com
3
+ *
4
+ * Usage: npm run release [--dry-run]
5
+ *
6
+ * Options:
7
+ * --dry-run Test the release flow without pushing to github.com
8
+ *
9
+ * This script:
10
+ * 1. Reads version from package.json
11
+ * 2. Updates the ABAP health resource with the new version
12
+ * 3. Uses Claude CLI to generate release notes from commits
13
+ * 4. Updates RELEASE_NOTES.md with new version notes
14
+ * 5. Pushes to github.com to trigger GitHub Actions (unless --dry-run)
15
+ * 6. GitHub Actions will publish to npm and create GitHub release
16
+ */
17
+
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+ const { execSync } = require('child_process');
21
+
22
+ const packageJsonPath = path.join(__dirname, '..', 'package.json');
23
+ const abapHealthPath = path.join(__dirname, '..', 'abap', 'zcl_abgagt_resource_health.clas.abap');
24
+ const releaseNotesPath = path.join(__dirname, '..', 'RELEASE_NOTES.md');
25
+ const repoRoot = path.join(__dirname, '..');
26
+
27
+ // Check for --dry-run flag
28
+ const args = process.argv.slice(2);
29
+ const dryRun = args.includes('--dry-run');
30
+
31
+ if (dryRun) {
32
+ console.log('🔹 DRY RUN MODE - No actual release will be created\n');
33
+ }
34
+
35
+ // Read version from package.json
36
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
37
+ const version = pkg.version;
38
+
39
+ console.log(`Current version: ${version}`);
40
+ console.log('');
41
+
42
+ // Check if version has been bumped - version must be greater than or equal to latest tag
43
+ const allTags = execSync('git tag --sort=-v:refname', { cwd: repoRoot, encoding: 'utf8' });
44
+ const tagList = allTags.trim().split('\n').filter(t => t.startsWith('v'));
45
+ const latestTag = tagList[0] ? tagList[0].replace('v', '') : '';
46
+
47
+ const versionTag = `v${version}`;
48
+
49
+ if (version === latestTag) {
50
+ // Check if the tag points to HEAD (new release we just created)
51
+ try {
52
+ const tagRef = execSync(`git rev-parse ${versionTag}^{commit}`, { cwd: repoRoot, encoding: 'utf8' }).trim();
53
+ const headRef = execSync('git rev-parse HEAD', { cwd: repoRoot, encoding: 'utf8' }).trim();
54
+
55
+ if (tagRef !== headRef) {
56
+ console.log(`Version ${version} has already been released (tag v${version} exists on different commit)`);
57
+ console.log('');
58
+ console.log('To bump version, run one of:');
59
+ console.log(' npm version patch # e.g., 1.4.0 -> 1.4.1');
60
+ console.log(' npm version minor # e.g., 1.4.0 -> 1.5.0');
61
+ console.log(' npm version major # e.g., 1.4.0 -> 2.0.0');
62
+ console.log('');
63
+ process.exit(1);
64
+ }
65
+ // If tag points to HEAD, this is the new release we just created - continue
66
+ } catch (e) {
67
+ // Tag doesn't exist - this is a new version
68
+ }
69
+ }
70
+
71
+ console.log(`Current version: ${version} (tag: ${versionTag})`);
72
+ console.log('');
73
+
74
+ // Check if there's a remote for github.com
75
+ let remoteName = 'origin';
76
+ try {
77
+ const remotes = execSync('git remote -v', { cwd: repoRoot, encoding: 'utf8' });
78
+ if (remotes.includes('github.com') && !remotes.includes('github.tools.sap')) {
79
+ remoteName = 'origin';
80
+ } else if (remotes.includes('public') && remotes.includes('github.com')) {
81
+ remoteName = 'public';
82
+ }
83
+ console.log(`Using remote: ${remoteName}`);
84
+ } catch (e) {
85
+ console.log('Could not determine remote, using origin');
86
+ }
87
+
88
+ console.log('');
89
+
90
+ // Read ABAP health resource file
91
+ let abapContent = fs.readFileSync(abapHealthPath, 'utf8');
92
+
93
+ // Update version in ABAP file (replace existing version)
94
+ const oldVersionMatch = abapContent.match(/version":"(\d+\.\d+\.\d+)"/);
95
+ if (oldVersionMatch) {
96
+ const oldVersion = oldVersionMatch[1];
97
+ abapContent = abapContent.replace(
98
+ `version":"${oldVersion}"`,
99
+ `version":"${version}"`
100
+ );
101
+
102
+ // Write updated content
103
+ fs.writeFileSync(abapHealthPath, abapContent);
104
+ console.log(`📦 ABAP version: ${oldVersion} -> ${version}`);
105
+ if (dryRun) {
106
+ console.log(' (file modified, not committed)');
107
+ }
108
+ console.log('');
109
+ } else {
110
+ console.error('Could not find version in ABAP file');
111
+ process.exit(1);
112
+ }
113
+
114
+ // Get commits since last tag for release notes
115
+ console.log('Generating release notes with Claude...');
116
+ console.log('');
117
+
118
+ let releaseNotesContent = '';
119
+
120
+ try {
121
+ // Find previous tag
122
+ const allTags = execSync('git tag --sort=-v:refname', { cwd: repoRoot, encoding: 'utf8' });
123
+ const tagList = allTags.trim().split('\n').filter(t => t.startsWith('v'));
124
+ const previousTag = tagList[1] || 'HEAD~10';
125
+
126
+ // Get commits since last release
127
+ const commits = execSync(`git log ${previousTag}..HEAD --oneline`, { cwd: repoRoot, encoding: 'utf8' });
128
+
129
+ if (commits.trim()) {
130
+ console.log(`Found ${commits.trim().split('\n').length} commits since last release`);
131
+ console.log('');
132
+
133
+ // Create Claude prompt - escape for shell
134
+ const commitsEscaped = commits.replace(/"/g, '\\"').replace(/\n/g, '\\n');
135
+ const prompt = `Generate concise release notes for version ${version} of a Node.js CLI tool called abapgit-agent.
136
+
137
+ Commits since last release:
138
+ ${commitsEscaped}
139
+
140
+ Instructions:
141
+ 1. IGNORE commits that revert, undo, or remove previous changes
142
+ 2. IGNORE commits that fix/improve the release process itself
143
+ 3. Focus on actual USER-FACING features and fixes
144
+ 4. Use 2-4 bullet points MAX per category
145
+ 5. Keep each bullet brief (under 10 words)
146
+ 6. START your response with "## v${version}" and END with "---"
147
+ 7. OUTPUT ONLY the release notes - do NOT add any intro text, explanation, or commentary
148
+ 8. Use this exact format with blank lines between categories:
149
+ ## v${version}
150
+
151
+ ### New Features
152
+
153
+ - Brief feature description
154
+
155
+ ### Bug Fixes
156
+
157
+ - Brief fix description
158
+
159
+ ### Improvements
160
+
161
+ - Brief improvement
162
+
163
+ ### Documentation
164
+
165
+ - Brief doc update
166
+
167
+ ---
168
+
169
+ OMIT any category that has no items.`;
170
+
171
+ // Call Claude CLI
172
+ try {
173
+ releaseNotesContent = execSync(`claude --print "${prompt}"`, { cwd: repoRoot, encoding: 'utf8', timeout: 60000 });
174
+ releaseNotesContent = releaseNotesContent.trim();
175
+ console.log('Generated release notes:');
176
+ console.log(releaseNotesContent);
177
+ console.log('');
178
+ } catch (e) {
179
+ console.log('Claude CLI not available, using fallback');
180
+ releaseNotesContent = `## v${version}\n\nSee commit history for changes.`;
181
+ }
182
+ } else {
183
+ console.log('No commits since last release');
184
+ releaseNotesContent = `## v${version}\n\nSee commit history for changes.`;
185
+ }
186
+ } catch (e) {
187
+ console.log('Could not generate release notes:', e.message);
188
+ releaseNotesContent = `## v${version}\n\nSee commit history for changes.`;
189
+ }
190
+
191
+ // Show release notes in dry-run mode
192
+ if (dryRun) {
193
+ console.log('📝 Generated Release Notes:');
194
+ console.log('─'.repeat(50));
195
+ console.log(releaseNotesContent);
196
+ console.log('─'.repeat(50));
197
+ console.log('');
198
+ }
199
+
200
+ // Update RELEASE_NOTES.md
201
+ if (fs.existsSync(releaseNotesPath)) {
202
+ const existingContent = fs.readFileSync(releaseNotesPath, 'utf8');
203
+
204
+ // Check if version already exists
205
+ if (existingContent.includes(`## v${version}`)) {
206
+ console.log(`Release notes for v${version} already exist`);
207
+ } else {
208
+ let newContent;
209
+
210
+ // Check if there's a "# Release Notes" header - insert after it if present
211
+ if (existingContent.startsWith('# Release Notes')) {
212
+ // Find the position after "# Release Notes" and any following content
213
+ const lines = existingContent.split('\n');
214
+ let insertIndex = 0;
215
+ for (let i = 0; i < lines.length; i++) {
216
+ if (lines[i].match(/^## v\d+\.\d+\.\d+/)) {
217
+ insertIndex = i;
218
+ break;
219
+ }
220
+ }
221
+ // Insert new content before the first version header
222
+ const before = lines.slice(0, insertIndex).join('\n');
223
+ const after = lines.slice(insertIndex).join('\n');
224
+ newContent = before + '\n\n' + releaseNotesContent + '\n\n---\n\n' + after;
225
+ } else {
226
+ // Add new version at the top
227
+ newContent = releaseNotesContent + '\n\n---\n\n' + existingContent;
228
+ }
229
+
230
+ fs.writeFileSync(releaseNotesPath, newContent);
231
+ if (dryRun) {
232
+ console.log('📄 RELEASE_NOTES.md: would add new version at top');
233
+ } else {
234
+ console.log(`Updated RELEASE_NOTES.md with v${version}`);
235
+ }
236
+ }
237
+ } else {
238
+ // Create new RELEASE_NOTES.md
239
+ fs.writeFileSync(releaseNotesPath, releaseNotesContent);
240
+ console.log(`Created RELEASE_NOTES.md with v${version}`);
241
+ }
242
+ console.log('');
243
+
244
+ // Check git status and commit (skip in dry-run)
245
+ const status = execSync('git status --porcelain', { cwd: repoRoot, encoding: 'utf8' });
246
+
247
+ if (status.trim()) {
248
+ if (dryRun) {
249
+ console.log('🔹 DRY RUN - Would create commit with changes:');
250
+ console.log(status);
251
+ console.log('');
252
+ } else {
253
+ // Stage and commit
254
+ try {
255
+ execSync('git add abap/zcl_abgagt_resource_health.clas.abap package.json RELEASE_NOTES.md', { cwd: repoRoot });
256
+ execSync(`git commit -m "chore: release v${version}"`, { cwd: repoRoot });
257
+ console.log('Created git commit for version update');
258
+ console.log('');
259
+ } catch (e) {
260
+ console.log('No changes to commit or commit failed');
261
+ }
262
+ }
263
+ } else {
264
+ console.log('No changes to commit (version already up to date)');
265
+ console.log('');
266
+ }
267
+
268
+ // Push to trigger GitHub Actions
269
+ if (dryRun) {
270
+ console.log('🔹 DRY RUN - Skipping push to github.com');
271
+ console.log('');
272
+ console.log('To actually release, run:');
273
+ console.log(` git push ${remoteName} master --follow-tags`);
274
+ console.log('');
275
+ } else {
276
+ console.log('Pushing to github.com to trigger release...');
277
+ console.log('');
278
+
279
+ try {
280
+ // Push master and tags to github.com
281
+ execSync(`git push ${remoteName} master --follow-tags`, { cwd: repoRoot });
282
+ console.log('Pushed to github.com successfully!');
283
+ console.log('');
284
+ } catch (e) {
285
+ console.log('Push failed, please push manually');
286
+ console.log('');
287
+ }
288
+
289
+ console.log('Release workflow triggered!');
290
+ console.log('');
291
+ console.log('The GitHub Actions workflow will:');
292
+ console.log('1. Run tests');
293
+ console.log('2. Publish to npm');
294
+ console.log('3. Create GitHub release with Claude-generated notes');
295
+ console.log('');
296
+ console.log('Next step for ABAP system:');
297
+ console.log(' abapgit-agent pull --files abap/zcl_abgagt_resource_health.clas.abap');
298
+ }