abapgit-agent 1.8.2 → 1.8.3

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
@@ -258,8 +258,189 @@ abapgit-agent unit --files src/zcl_test1.clas.testclasses.abap,src/zcl_test2.cla
258
258
 
259
259
  ## Development Workflow
260
260
 
261
+ This project's workflow mode is configured in `.abapGitAgent` under `workflow.mode`.
262
+
263
+ ### Workflow Modes
264
+
265
+ | Mode | Branch Strategy | Rebase Before Pull | Create PR |
266
+ |------|----------------|-------------------|-----------|
267
+ | `"branch"` | Feature branches | ✓ Always | ✓ Yes (squash merge) |
268
+ | `"trunk"` | Direct to default branch | ✗ No | ✗ No |
269
+ | (not set) | Direct to default branch | ✗ No | ✗ No |
270
+
271
+ **Default branch** (main/master/develop) is **auto-detected** from your git repository.
272
+
273
+ ### Branch Workflow (`"mode": "branch"`)
274
+
275
+ **IMPORTANT**: Always work on feature branches, never commit directly to the default branch.
276
+
277
+ #### Starting a New Feature
278
+
279
+ ```bash
280
+ # 1. Create and switch to feature branch from default branch
281
+ git checkout main # or master/develop (auto-detected)
282
+ git pull origin main
283
+ git checkout -b feature/user-authentication
284
+
285
+ # 2. Make your changes
286
+ edit src/zcl_auth_handler.clas.abap
287
+
288
+ # 3. Check syntax (CLAS/INTF/PROG/DDLS only, if independent)
289
+ abapgit-agent syntax --files src/zcl_auth_handler.clas.abap
290
+
291
+ # 4. Commit
292
+ git add src/zcl_auth_handler.clas.abap
293
+ git commit -m "feat: add authentication handler"
294
+
295
+ # 5. Push feature branch
296
+ git push origin feature/user-authentication
297
+
298
+ # 6. **CRITICAL**: Rebase before pull
299
+ git fetch origin main
300
+ git rebase origin/main
301
+ git push origin feature/user-authentication --force-with-lease
302
+
303
+ # 7. Pull to ABAP system
304
+ abapgit-agent pull --files src/zcl_auth_handler.clas.abap
305
+ ```
306
+
307
+ #### During Development: Always Rebase Before Pull
308
+
309
+ **CRITICAL**: Before every `pull` command, rebase to default branch to avoid activating outdated code.
310
+
311
+ ```bash
312
+ # Before EVERY pull, always do this:
313
+ git fetch origin main # main/master/develop (auto-detected)
314
+ git rebase origin/main
315
+
316
+ # If no conflicts:
317
+ git push origin feature/user-authentication --force-with-lease
318
+ abapgit-agent pull --files src/zcl_auth_handler.clas.abap
319
+
320
+ # If conflicts:
321
+ # 1. Fix conflicts in files
322
+ # 2. git add <resolved-files>
323
+ # 3. git rebase --continue
324
+ # 4. git push origin feature/user-authentication --force-with-lease
325
+ # 5. abapgit-agent pull --files ...
326
+ ```
327
+
328
+ #### Completing the Feature
329
+
330
+ ```bash
331
+ # 1. Final rebase and push
332
+ git fetch origin main
333
+ git rebase origin/main
334
+ git push origin feature/user-authentication --force-with-lease
335
+
336
+ # 2. Final activation and test
337
+ abapgit-agent pull --files src/zcl_auth_handler.clas.abap
338
+ abapgit-agent unit --files src/zcl_auth_handler.clas.testclasses.abap
339
+
340
+ # 3. Create PR (squash merge enabled on GitHub/GitLab)
341
+ # Go to GitHub and create PR from feature/user-authentication to main
342
+ # Select "Squash and merge" option to combine all commits into one
343
+ ```
344
+
345
+ #### Why Rebase Before Pull?
346
+
347
+ ABAP is a **centralized system**. Multiple developers may modify the same files:
348
+
349
+ | Without Rebase | With Rebase |
350
+ |----------------|-------------|
351
+ | ✗ Your branch is based on old main | ✓ Your branch includes latest changes |
352
+ | ✗ Activate outdated code in ABAP | ✓ Activate current code |
353
+ | ✗ May overwrite others' work | ✓ Conflicts caught before activation |
354
+ | ✗ Hard to debug issues | ✓ Clear what changed |
355
+
356
+ **Example Scenario:**
357
+
358
+ ```
359
+ Situation:
360
+ - You: working on feature/auth (based on main commit A)
361
+ - Colleague: pushed to main (now at commit B)
362
+ - Both modified: src/zcl_auth_handler.clas.abap
363
+
364
+ Without rebase:
365
+ feature/auth pull → activates version from commit A ✗
366
+
367
+ With rebase:
368
+ git rebase origin/main → either:
369
+ - No conflict: includes colleague's changes ✓
370
+ - Conflict: you see it and resolve ✓
371
+ ```
372
+
373
+ #### Complete Example Workflow (Branch Mode)
374
+
375
+ ```bash
376
+ # Day 1: Start feature
377
+ git checkout main && git pull origin main
378
+ git checkout -b feature/user-authentication
379
+ edit src/zcl_auth_handler.clas.abap
380
+ abapgit-agent syntax --files src/zcl_auth_handler.clas.abap
381
+ git add . && git commit -m "wip: add basic auth logic"
382
+ git push origin feature/user-authentication
383
+ git fetch origin main && git rebase origin/main
384
+ git push origin feature/user-authentication --force-with-lease
385
+ abapgit-agent pull --files src/zcl_auth_handler.clas.abap
386
+
387
+ # Day 2: Continue (colleague pushed to main overnight)
388
+ git fetch origin main && git rebase origin/main
389
+ # If conflicts: resolve, git add, git rebase --continue
390
+ git push origin feature/user-authentication --force-with-lease
391
+ edit src/zcl_auth_handler.clas.abap
392
+ git add . && git commit -m "feat: complete auth logic"
393
+ git push origin feature/user-authentication
394
+ git fetch origin main && git rebase origin/main
395
+ git push origin feature/user-authentication --force-with-lease
396
+ abapgit-agent pull --files src/zcl_auth_handler.clas.abap
397
+
398
+ # Day 3: Finish feature
399
+ abapgit-agent unit --files src/zcl_auth_handler.clas.testclasses.abap
400
+ git fetch origin main && git rebase origin/main
401
+ git push origin feature/user-authentication --force-with-lease
402
+ # Create PR on GitHub/GitLab (squash 3 commits into 1)
403
+ ```
404
+
405
+ ### Trunk Workflow (`"mode": "trunk"`)
406
+
407
+ If workflow mode is `"trunk"` or not set, commit directly to the default branch:
408
+
409
+ ```bash
410
+ git checkout main # or master/develop (auto-detected)
411
+ git pull origin main
412
+ edit src/zcl_auth_handler.clas.abap
413
+ abapgit-agent syntax --files src/zcl_auth_handler.clas.abap
414
+ git add . && git commit -m "feat: add authentication handler"
415
+ git push origin main
416
+ abapgit-agent pull --files src/zcl_auth_handler.clas.abap
417
+ ```
418
+
419
+ ### AI Tool Guidelines
420
+
421
+ **Read `.abapGitAgent` to determine workflow mode:**
422
+
423
+ **When `workflow.mode = "branch"`:**
424
+ 1. ✓ Auto-detect default branch (main/master/develop)
425
+ 2. ✓ Create feature branches (naming: `feature/description`)
426
+ 3. ✓ Always `git fetch origin <default> && git rebase origin/<default>` before `pull` command
427
+ 4. ✓ Use `--force-with-lease` after rebase (never `--force`)
428
+ 5. ✓ Create PR with squash merge when feature complete
429
+ 6. ✗ Never commit directly to default branch
430
+ 7. ✗ Never use `git push --force` (always use `--force-with-lease`)
431
+
432
+ **When `workflow.mode = "trunk"` or not set:**
433
+ 1. ✓ Commit directly to default branch
434
+ 2. ✓ Keep commits clean and atomic
435
+ 3. ✓ `git pull origin <default>` before push
436
+ 4. ✗ Don't create feature branches
437
+
438
+ ---
439
+
440
+ ## Development Workflow (Detailed)
441
+
261
442
  ```
262
- 1. Read .abapGitAgent → get folder value
443
+ 1. Read .abapGitAgent → get folder value AND workflow.mode
263
444
 
264
445
 
265
446
  2. Research → use ref command for unfamiliar topics
@@ -11,12 +11,13 @@ grand_parent: ABAP Development
11
11
  **Searchable keywords**: unit test, AUnit, test class, cl_abap_unit_assert, FOR TESTING, setup, teardown, RISK LEVEL, DURATION, CDS test double, CL_CDS_TEST_ENVIRONMENT
12
12
 
13
13
  ## TOPICS IN THIS FILE
14
- 1. Local Test Classes - line 3
15
- 2. File Structure - line 5
16
- 3. Required Elements - line 16
17
- 4. Naming Conventions - line 48
18
- 5. CDS Test Doubles - line 94
19
- 6. CDS with Aggregations - line 178
14
+ 1. Local Test Classes - line 22
15
+ 2. File Structure - line 24
16
+ 3. Required Elements - line 35
17
+ 4. Naming Conventions - line 67
18
+ 5. Common Mistake: DDLS Testing - line 133
19
+ 6. CDS Test Doubles - line 163
20
+ 7. CDS with Aggregations - line 247
20
21
 
21
22
  ## Unit Testing with Local Test Classes
22
23
 
@@ -122,6 +123,44 @@ cl_abap_unit_assert=>assert_true( act = lv_bool msg = 'Should be true' ).
122
123
  - ❌ Don't reference `<TESTCLASS>` in XML - abapGit auto-detects `.testclasses.abap`
123
124
  - ❌ Don't use nested local classes inside the main class definition
124
125
 
126
+ ---
127
+
128
+ ### ⚠️ Common Mistake: CDS Views Don't Have `.testclasses.abap` Files
129
+
130
+ **WRONG - Creating test file for DDLS**:
131
+ ```
132
+ zc_my_view.ddls.asddls
133
+ zc_my_view.ddls.testclasses.abap ❌ This doesn't work!
134
+ zc_my_view.ddls.xml
135
+ ```
136
+
137
+ **Error you'll see**:
138
+ ```
139
+ The REPORT/PROGRAM statement is missing, or the program type is INCLUDE.
140
+ ```
141
+
142
+ **CORRECT - Test CDS views using separate CLAS test classes**:
143
+ ```
144
+ zc_flight_revenue.ddls.asddls ← CDS view definition
145
+ zc_flight_revenue.ddls.xml ← CDS metadata
146
+
147
+ zcl_test_flight_revenue.clas.abap ← Test class definition
148
+ zcl_test_flight_revenue.clas.testclasses.abap ← Test implementation
149
+ zcl_test_flight_revenue.clas.xml ← Class metadata (WITH_UNIT_TESTS=X)
150
+ ```
151
+
152
+ **Why**: Each ABAP object type has its own testing pattern:
153
+ - **CLAS** (classes): Use `.clas.testclasses.abap` for the same class
154
+ - **DDLS** (CDS views): Use separate CLAS test class with CDS Test Double Framework
155
+ - **FUGR** (function groups): Use `.fugr.testclasses.abap`
156
+ - **PROG** (programs): Use `.prog.testclasses.abap`
157
+
158
+ **Don't assume patterns from one object type apply to another!**
159
+
160
+ See "Unit Testing CDS Views" section below for the correct CDS testing approach.
161
+
162
+ ---
163
+
125
164
  ### Running Tests
126
165
 
127
166
  In ABAP: SE24 → Test → Execute Unit Tests
@@ -11,11 +11,15 @@ grand_parent: ABAP Development
11
11
  **Searchable keywords**: CDS, DDL, DDLS, CDS view, @AbapCatalog, @AccessControl, association, projection, consumption
12
12
 
13
13
  ## TOPICS IN THIS FILE
14
- 1. File Naming - line 7
15
- 2. DDL Source (.ddls.asddls) - line 18
16
- 3. Annotations - line 50
17
- 4. Associations - line 75
18
- 5. CDS Test Doubles - see 03_testing.md
14
+ 1. File Naming - line 96
15
+ 2. DDL Source (.ddls.asddls) - line 107
16
+ 3. Annotations - line 141
17
+ 4. Associations - line 164
18
+ 5. CDS Best Practices - line 194
19
+ - Key Field Ordering (STRICT RULE) - line 198
20
+ - Currency/Amount Field Aggregation - line 230
21
+ - Choosing Currency Fields for Aggregation - line 255
22
+ 6. CDS Test Doubles - see 03_testing.md
19
23
 
20
24
  ## Creating CDS Views (DDLS)
21
25
 
@@ -188,6 +192,110 @@ where devclass not like '$%'
188
192
  3. **Expose associations**: Add the association name at the end of the SELECT to expose it for OData navigation
189
193
  4. **Activation warnings**: Search help warnings are informational and don't block activation
190
194
 
195
+ ---
196
+
197
+ ## CDS Best Practices and Common Patterns
198
+
199
+ ### Key Field Ordering (STRICT RULE)
200
+
201
+ CDS views enforce strict key field ordering that differs from regular SQL:
202
+
203
+ ❌ **WRONG - Non-key field between keys**:
204
+ ```abap
205
+ {
206
+ key Flight.carrid as Carrid,
207
+ Airline.carrname as Carrname, // ← breaks contiguity!
208
+ key Flight.connid as Connid,
209
+ key Flight.fldate as Fldate,
210
+ ...
211
+ }
212
+ ```
213
+
214
+ ✅ **CORRECT - All keys first, then non-keys**:
215
+ ```abap
216
+ {
217
+ key Flight.carrid as Carrid,
218
+ key Flight.connid as Connid,
219
+ key Flight.fldate as Fldate,
220
+ Airline.carrname as Carrname, // ← after all keys
221
+ ...
222
+ }
223
+ ```
224
+
225
+ **Rules:**
226
+ 1. All key fields MUST be at the beginning of the field list
227
+ 2. Key fields MUST be contiguous (no non-key fields in between)
228
+ 3. Key fields must be declared before any non-key fields
229
+
230
+ **Error message**: "Key must be contiguous and start at the first position"
231
+
232
+ **Why this rule exists**: CDS views are not just SQL - they represent data models with strict structural requirements for consistency across the system.
233
+
234
+ ---
235
+
236
+ ### Currency/Amount Field Aggregation
237
+
238
+ When aggregating currency or amount fields in CDS views, use semantic annotations instead of complex casting:
239
+
240
+ ❌ **WRONG - Complex casting (will fail)**:
241
+ ```abap
242
+ cast(coalesce(sum(Booking.loccuram), 0) as abap.curr(15,2))
243
+ // Error: Data type CURR is not supported at this position
244
+ ```
245
+
246
+ ✅ **CORRECT - Semantic annotation + simple aggregation**:
247
+ ```abap
248
+ @Semantics.amount.currencyCode: 'Currency'
249
+ sum(Booking.loccuram) as TotalRevenue,
250
+ currency as Currency
251
+ ```
252
+
253
+ **Key points:**
254
+ - Use `@Semantics.amount.currencyCode` to link amount to currency field
255
+ - Let the framework handle data typing automatically
256
+ - Don't over-engineer with casts or type conversions
257
+ - Keep it simple: annotation + aggregation function
258
+
259
+ **Reference**:
260
+ ```bash
261
+ abapgit-agent ref "CDS aggregation"
262
+ # Check: zdemo_abap_cds_ve_agg_exp.ddls.asddls for working examples
263
+ ```
264
+
265
+ ---
266
+
267
+ ### Choosing Currency Fields for Aggregation
268
+
269
+ **Understand your data model before aggregating currency fields** - not all currency fields can be safely summed:
270
+
271
+ ❌ **Dangerous - Foreign currency (different keys per row)**:
272
+ ```abap
273
+ sum(Booking.forcuram) // FORCURAM has different FORCURKEY per booking!
274
+ // Problem: Can't safely sum USD + EUR + GBP without conversion
275
+ ```
276
+
277
+ ✅ **Safe - Local currency (shared key per group)**:
278
+ ```abap
279
+ sum(Booking.loccuram) // LOCCURAM shares LOCCURKEY per airline
280
+ // Safe: All bookings for one airline use the same currency
281
+ ```
282
+
283
+ **Data Model Example (SBOOK table)**:
284
+ ```
285
+ FORCURAM + FORCURKEY = Payment currency (USD, EUR, GBP - different per booking)
286
+ LOCCURAM + LOCCURKEY = Airline currency (one currency per airline)
287
+ ```
288
+
289
+ **Analysis Steps:**
290
+ 1. Identify all currency fields in source tables
291
+ 2. Check which currency code field each amount uses
292
+ 3. Verify currency code is constant within your aggregation groups
293
+ 4. Choose the field with consistent currency per group
294
+
295
+ **Rule**: Only aggregate amounts that share the same currency code within your grouping (GROUP BY).
296
+
297
+ ---
298
+
191
299
  ## CDS Syntax Reference
192
300
 
193
301
  When working with CDS view syntax (arithmetic, built-in functions, aggregations, etc.):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abapgit-agent",
3
- "version": "1.8.2",
3
+ "version": "1.8.3",
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 @@
34
34
  "test:cmd:preview": "node tests/run-all.js --cmd --command=preview",
35
35
  "test:cmd:tree": "node tests/run-all.js --cmd --command=tree",
36
36
  "test:lifecycle": "node tests/run-all.js --lifecycle",
37
+ "test:pull": "node tests/run-all.js --pull",
37
38
  "pull": "node bin/abapgit-agent",
38
39
  "release": "node scripts/release.js",
39
40
  "unrelease": "node scripts/unrelease.js"
@@ -172,39 +172,100 @@ module.exports = {
172
172
  }
173
173
  console.log(`📌 Git remote: ${gitUrl}`);
174
174
 
175
- // Check if .abapGitAgent already exists
175
+ // Check if .abapGitAgent already exists - merge if it does
176
176
  const configPath = pathModule.join(process.cwd(), '.abapGitAgent');
177
+ let config = null;
178
+ let isUpdate = false;
179
+
177
180
  if (fs.existsSync(configPath)) {
178
- console.error('Error: .abapGitAgent already exists.');
179
- console.error('To reinitialize, delete the existing file first.');
180
- process.exit(1);
181
- }
181
+ isUpdate = true;
182
+ try {
183
+ // Read existing configuration
184
+ const currentConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
185
+
186
+ console.log('📝 Updating existing .abapGitAgent configuration');
187
+ console.log('');
188
+ console.log('Current values:');
189
+ console.log(` Package: ${currentConfig.package || '(not set)'}`);
190
+ console.log(` Folder: ${currentConfig.folder || '(not set)'}`);
191
+ console.log(` Host: ${currentConfig.host || '(not set)'}`);
192
+ console.log('');
193
+
194
+ // Merge: keep existing values, update package and folder
195
+ config = currentConfig;
196
+
197
+ // Track what changed
198
+ const changes = [];
199
+
200
+ if (packageName && packageName !== currentConfig.package) {
201
+ const oldValue = currentConfig.package || '(not set)';
202
+ config.package = packageName;
203
+ changes.push(`package: ${oldValue} → ${packageName}`);
204
+ }
182
205
 
183
- // Copy .abapGitAgent.example to .abapGitAgent
184
- const samplePath = pathModule.join(__dirname, '..', '..', '.abapGitAgent.example');
185
- if (!fs.existsSync(samplePath)) {
186
- console.error('Error: .abapGitAgent.example not found.');
187
- process.exit(1);
206
+ if (folder && folder !== currentConfig.folder) {
207
+ const oldValue = currentConfig.folder || '(not set)';
208
+ config.folder = folder;
209
+ changes.push(`folder: ${oldValue} ${folder}`);
210
+ }
211
+
212
+ if (changes.length === 0) {
213
+ console.log('⚠️ No changes needed - package and folder are already set correctly');
214
+ console.log('');
215
+ console.log('To change other settings, edit .abapGitAgent manually');
216
+ // Don't exit - continue with other setup tasks (CLAUDE.md, guidelines, etc.)
217
+ } else {
218
+ console.log('Changes to be made:');
219
+ changes.forEach(change => console.log(` ${change}`));
220
+ console.log('');
221
+ console.log('✅ Keeping all other settings (host, credentials, workflow, etc.)');
222
+ console.log('');
223
+ }
224
+ } catch (error) {
225
+ console.error('Error: .abapGitAgent exists but could not read it:');
226
+ console.error(` ${error.message}`);
227
+ console.error('');
228
+ console.error('To fix:');
229
+ console.error(' 1. Check if .abapGitAgent contains valid JSON');
230
+ console.error(' 2. Or delete it: rm .abapGitAgent');
231
+ process.exit(1);
232
+ }
233
+ } else {
234
+ // Create new config from template
235
+ const samplePath = pathModule.join(__dirname, '..', '..', '.abapGitAgent.example');
236
+ if (!fs.existsSync(samplePath)) {
237
+ console.error('Error: .abapGitAgent.example not found.');
238
+ process.exit(1);
239
+ }
240
+
241
+ try {
242
+ // Read sample and update with package/folder
243
+ const sampleContent = fs.readFileSync(samplePath, 'utf8');
244
+ config = JSON.parse(sampleContent);
245
+ config.package = packageName;
246
+ config.folder = folder;
247
+ } catch (error) {
248
+ console.error(`Error reading .abapGitAgent.example: ${error.message}`);
249
+ process.exit(1);
250
+ }
188
251
  }
189
252
 
253
+ // Write the config (either new or updated)
190
254
  try {
191
- // Read sample and update with package/folder
192
- const sampleContent = fs.readFileSync(samplePath, 'utf8');
193
- const config = JSON.parse(sampleContent);
194
- config.package = packageName;
195
- config.folder = folder;
196
-
197
- // Write updated config
198
255
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
199
- console.log(`✅ Created .abapGitAgent`);
256
+ if (isUpdate) {
257
+ console.log(`✅ Updated .abapGitAgent`);
258
+ } else {
259
+ console.log(`✅ Created .abapGitAgent`);
260
+ }
200
261
  } catch (error) {
201
- console.error(`Error creating .abapGitAgent: ${error.message}`);
262
+ console.error(`Error writing .abapGitAgent: ${error.message}`);
202
263
  process.exit(1);
203
264
  }
204
265
 
205
266
  // Update .gitignore
206
267
  const gitignorePath = pathModule.join(process.cwd(), '.gitignore');
207
- const ignoreEntries = ['.abapGitAgent', '.abapgit_agent_cookies.txt'];
268
+ const ignoreEntries = ['.abapGitAgent'];
208
269
  let existingIgnore = '';
209
270
 
210
271
  if (fs.existsSync(gitignorePath)) {
@@ -63,7 +63,7 @@ module.exports = {
63
63
  }
64
64
 
65
65
  // Pattern search (default)
66
- const patternIndex = args.findIndex((arg, idx) => idx > 0 && !arg.startsWith('--'));
66
+ const patternIndex = args.findIndex(arg => !arg.startsWith('--'));
67
67
  if (patternIndex === -1) {
68
68
  console.error('Error: No pattern specified');
69
69
  console.error('');
package/src/config.js CHANGED
@@ -74,10 +74,25 @@ function isAbapIntegrationEnabled() {
74
74
  return fs.existsSync(repoConfigPath);
75
75
  }
76
76
 
77
+ /**
78
+ * Get workflow configuration
79
+ * @returns {Object} Workflow config with mode ('branch' or 'trunk') and optional defaultBranch
80
+ */
81
+ function getWorkflowConfig() {
82
+ const cfg = loadConfig();
83
+
84
+ // Default to trunk mode if not specified (backward compatible)
85
+ return {
86
+ mode: cfg.workflow?.mode || 'trunk',
87
+ defaultBranch: cfg.workflow?.defaultBranch || null // null means auto-detect
88
+ };
89
+ }
90
+
77
91
  module.exports = {
78
92
  loadConfig,
79
93
  getAbapConfig,
80
94
  getAgentConfig,
81
95
  getTransport,
82
- isAbapIntegrationEnabled
96
+ isAbapIntegrationEnabled,
97
+ getWorkflowConfig
83
98
  };
@@ -3,6 +3,7 @@
3
3
  */
4
4
  const pathModule = require('path');
5
5
  const fs = require('fs');
6
+ const { execSync } = require('child_process');
6
7
 
7
8
  /**
8
9
  * Get git remote URL from .git/config
@@ -51,8 +52,55 @@ function isGitRepo() {
51
52
  return fs.existsSync(gitPath);
52
53
  }
53
54
 
55
+ /**
56
+ * Get the default/trunk branch name (main, master, develop, etc.)
57
+ * @param {string|null} configuredBranch - Branch name from config (overrides auto-detection)
58
+ * @returns {string} Default branch name
59
+ */
60
+ function getDefaultBranch(configuredBranch = null) {
61
+ // If explicitly configured, use that
62
+ if (configuredBranch) {
63
+ return configuredBranch;
64
+ }
65
+
66
+ // Method 1: Check remote HEAD (most accurate)
67
+ try {
68
+ const result = execSync('git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null', {
69
+ cwd: process.cwd(),
70
+ encoding: 'utf8'
71
+ }).trim();
72
+
73
+ // Result looks like: refs/remotes/origin/main
74
+ const match = result.match(/refs\/remotes\/origin\/(.+)/);
75
+ if (match && match[1]) {
76
+ return match[1];
77
+ }
78
+ } catch (e) {
79
+ // Ignore error, try next method
80
+ }
81
+
82
+ // Method 2: Check common branch names in remote
83
+ try {
84
+ const branches = execSync('git branch -r 2>/dev/null', {
85
+ cwd: process.cwd(),
86
+ encoding: 'utf8'
87
+ });
88
+
89
+ // Check in order of preference: main > master > develop
90
+ if (branches.includes('origin/main')) return 'main';
91
+ if (branches.includes('origin/master')) return 'master';
92
+ if (branches.includes('origin/develop')) return 'develop';
93
+ } catch (e) {
94
+ // Ignore error, use fallback
95
+ }
96
+
97
+ // Method 3: Fallback to 'main' (modern default)
98
+ return 'main';
99
+ }
100
+
54
101
  module.exports = {
55
102
  getRemoteUrl,
56
103
  getBranch,
57
- isGitRepo
104
+ isGitRepo,
105
+ getDefaultBranch
58
106
  };