edsger 0.13.2 → 0.13.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.
@@ -8,7 +8,9 @@ import { execSync } from 'child_process';
8
8
  import { fetchCodeRefineContext, } from './context.js';
9
9
  import { getFeedbacksForPhase, formatFeedbacksForContext, } from '../../services/feedbacks.js';
10
10
  import { createSystemPrompt, createCodeRefinePrompt } from './prompts.js';
11
- import { preparePhaseGitEnvironment, hasUncommittedChanges, getUncommittedFiles, } from '../../utils/git-branch-manager.js';
11
+ import { preparePhaseGitEnvironment, hasUncommittedChanges, getUncommittedFiles, syncFeatBranchWithMain, } from '../../utils/git-branch-manager.js';
12
+ import { getFeature } from '../../api/features/get-feature.js';
13
+ import { parsePullRequestUrl } from './context.js';
12
14
  function userMessage(content) {
13
15
  return {
14
16
  type: 'user',
@@ -62,6 +64,23 @@ export const refineCodeFromPRFeedback = async (options, config) => {
62
64
  if (verbose) {
63
65
  logInfo(`Starting code refine for feature ID: ${featureId}`);
64
66
  }
67
+ // Sync feat branch with main before preparing git environment
68
+ // This prevents extra commits from appearing in PR when dev branch is rebased
69
+ try {
70
+ const feature = await getFeature(featureId, verbose);
71
+ if (feature.pull_request_url) {
72
+ const prInfo = parsePullRequestUrl(feature.pull_request_url);
73
+ if (prInfo) {
74
+ await syncFeatBranchWithMain(featureId, githubToken, prInfo.owner, prInfo.repo, 'main', verbose);
75
+ }
76
+ }
77
+ }
78
+ catch (error) {
79
+ if (verbose) {
80
+ logInfo(`⚠️ Could not sync feat branch: ${error instanceof Error ? error.message : String(error)}`);
81
+ }
82
+ // Continue even if sync fails - it's not critical
83
+ }
65
84
  // Prepare git environment: switch to feature branch and rebase with main
66
85
  const cleanupGit = preparePhaseGitEnvironment(featureId, 'main', verbose);
67
86
  try {
@@ -71,3 +71,16 @@ export declare function returnToMainBranch(baseBranch?: string, verbose?: boolea
71
71
  * @returns Cleanup function that will return to main branch
72
72
  */
73
73
  export declare function preparePhaseGitEnvironment(featureId: string, baseBranch?: string, verbose?: boolean): () => void;
74
+ /**
75
+ * Sync feat branch with main using GitHub API
76
+ * This ensures the feat branch (PR base) is up to date with main,
77
+ * preventing extra commits from appearing in PRs when dev branch is rebased.
78
+ *
79
+ * @param featureId - The feature ID (will be used to construct branch name "feat/{featureId}")
80
+ * @param githubToken - GitHub personal access token or app token
81
+ * @param owner - Repository owner
82
+ * @param repo - Repository name
83
+ * @param baseBranch - The base branch to sync from (default: "main")
84
+ * @param verbose - Whether to log verbose output
85
+ */
86
+ export declare function syncFeatBranchWithMain(featureId: string, githubToken: string, owner: string, repo: string, baseBranch?: string, verbose?: boolean): Promise<boolean>;
@@ -3,6 +3,7 @@
3
3
  * Shared utilities for consistent git branch management across all phases
4
4
  */
5
5
  import { execSync } from 'child_process';
6
+ import { Octokit } from '@octokit/rest';
6
7
  import { logInfo, logError } from './logger.js';
7
8
  /**
8
9
  * Get current Git branch name
@@ -358,3 +359,100 @@ export function preparePhaseGitEnvironment(featureId, baseBranch = 'main', verbo
358
359
  // Return cleanup function
359
360
  return cleanup;
360
361
  }
362
+ /**
363
+ * Sync feat branch with main using GitHub API
364
+ * This ensures the feat branch (PR base) is up to date with main,
365
+ * preventing extra commits from appearing in PRs when dev branch is rebased.
366
+ *
367
+ * @param featureId - The feature ID (will be used to construct branch name "feat/{featureId}")
368
+ * @param githubToken - GitHub personal access token or app token
369
+ * @param owner - Repository owner
370
+ * @param repo - Repository name
371
+ * @param baseBranch - The base branch to sync from (default: "main")
372
+ * @param verbose - Whether to log verbose output
373
+ */
374
+ export async function syncFeatBranchWithMain(featureId, githubToken, owner, repo, baseBranch = 'main', verbose) {
375
+ const featBranch = `feat/${featureId}`;
376
+ try {
377
+ const octokit = new Octokit({ auth: githubToken });
378
+ // Check if feat branch exists
379
+ if (verbose) {
380
+ logInfo(`🔍 Checking if ${featBranch} branch exists...`);
381
+ }
382
+ try {
383
+ await octokit.repos.getBranch({
384
+ owner,
385
+ repo,
386
+ branch: featBranch,
387
+ });
388
+ }
389
+ catch (error) {
390
+ if (error.status === 404) {
391
+ if (verbose) {
392
+ logInfo(`ℹ️ ${featBranch} branch does not exist, skipping sync`);
393
+ }
394
+ return true; // Not an error, just no feat branch yet
395
+ }
396
+ throw error;
397
+ }
398
+ // Get the latest SHA of the base branch
399
+ const { data: baseBranchData } = await octokit.repos.getBranch({
400
+ owner,
401
+ repo,
402
+ branch: baseBranch,
403
+ });
404
+ const mainSha = baseBranchData.commit.sha;
405
+ // Get the current SHA of the feat branch
406
+ const { data: featBranchData } = await octokit.repos.getBranch({
407
+ owner,
408
+ repo,
409
+ branch: featBranch,
410
+ });
411
+ const featSha = featBranchData.commit.sha;
412
+ // Check if feat branch is already up to date (same as main or ahead)
413
+ // We need to merge main into feat to keep it updated
414
+ if (verbose) {
415
+ logInfo(`📥 Syncing ${featBranch} with ${baseBranch}...`);
416
+ logInfo(` ${baseBranch} SHA: ${mainSha.substring(0, 7)}`);
417
+ logInfo(` ${featBranch} SHA: ${featSha.substring(0, 7)}`);
418
+ }
419
+ // Use GitHub merge API to merge main into feat branch
420
+ try {
421
+ await octokit.repos.merge({
422
+ owner,
423
+ repo,
424
+ base: featBranch,
425
+ head: baseBranch,
426
+ commit_message: `chore: sync ${featBranch} with ${baseBranch}`,
427
+ });
428
+ if (verbose) {
429
+ logInfo(`✅ Successfully synced ${featBranch} with ${baseBranch}`);
430
+ }
431
+ }
432
+ catch (mergeError) {
433
+ // 409 means nothing to merge (already up to date)
434
+ if (mergeError.status === 409) {
435
+ if (verbose) {
436
+ logInfo(`ℹ️ ${featBranch} is already up to date with ${baseBranch}`);
437
+ }
438
+ return true;
439
+ }
440
+ // 404 means branch doesn't exist (shouldn't happen since we checked above)
441
+ if (mergeError.status === 404) {
442
+ if (verbose) {
443
+ logInfo(`ℹ️ ${featBranch} branch not found, skipping sync`);
444
+ }
445
+ return true;
446
+ }
447
+ throw mergeError;
448
+ }
449
+ return true;
450
+ }
451
+ catch (error) {
452
+ if (verbose) {
453
+ logError(`⚠️ Failed to sync ${featBranch} with ${baseBranch}: ${error instanceof Error ? error.message : String(error)}`);
454
+ }
455
+ // Don't fail the whole process if sync fails, just log warning
456
+ return false;
457
+ }
458
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edsger",
3
- "version": "0.13.2",
3
+ "version": "0.13.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "edsger": "dist/index.js"
@@ -53,4 +53,4 @@
53
53
  "optional": false
54
54
  }
55
55
  }
56
- }
56
+ }