edsger 0.26.0 → 0.26.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 (129) hide show
  1. package/.claude/settings.local.json +28 -0
  2. package/.env.local +12 -0
  3. package/dist/api/features/__tests__/regression-prevention.test.d.ts +5 -0
  4. package/dist/api/features/__tests__/regression-prevention.test.js +338 -0
  5. package/dist/api/features/__tests__/status-updater.integration.test.d.ts +5 -0
  6. package/dist/api/features/__tests__/status-updater.integration.test.js +497 -0
  7. package/dist/commands/workflow/config/phase-configs.js +5 -0
  8. package/dist/commands/workflow/executors/phase-executor.d.ts +2 -2
  9. package/dist/commands/workflow/executors/phase-executor.js +2 -2
  10. package/dist/commands/workflow/feature-coordinator.js +3 -1
  11. package/dist/commands/workflow/phase-orchestrator.js +66 -3
  12. package/dist/commands/workflow/pipeline-runner.d.ts +17 -0
  13. package/dist/commands/workflow/pipeline-runner.js +393 -0
  14. package/dist/commands/workflow/runner.d.ts +26 -0
  15. package/dist/commands/workflow/runner.js +119 -0
  16. package/dist/commands/workflow/workflow-runner.d.ts +26 -0
  17. package/dist/commands/workflow/workflow-runner.js +119 -0
  18. package/dist/config/feature-status.js +3 -0
  19. package/dist/index.js +0 -0
  20. package/dist/phases/code-implementation/analyzer-helpers.d.ts +28 -0
  21. package/dist/phases/code-implementation/analyzer-helpers.js +177 -0
  22. package/dist/phases/code-implementation/analyzer.d.ts +32 -0
  23. package/dist/phases/code-implementation/analyzer.js +629 -0
  24. package/dist/phases/code-implementation/context-fetcher.d.ts +17 -0
  25. package/dist/phases/code-implementation/context-fetcher.js +86 -0
  26. package/dist/phases/code-implementation/mcp-server.d.ts +1 -0
  27. package/dist/phases/code-implementation/mcp-server.js +93 -0
  28. package/dist/phases/code-implementation/prompts-improvement.d.ts +5 -0
  29. package/dist/phases/code-implementation/prompts-improvement.js +108 -0
  30. package/dist/phases/code-implementation-verification/verifier.d.ts +31 -0
  31. package/dist/phases/code-implementation-verification/verifier.js +196 -0
  32. package/dist/phases/code-refine/analyzer.d.ts +41 -0
  33. package/dist/phases/code-refine/analyzer.js +561 -0
  34. package/dist/phases/code-refine/context-fetcher.d.ts +94 -0
  35. package/dist/phases/code-refine/context-fetcher.js +423 -0
  36. package/dist/phases/code-refine-verification/analysis/llm-analyzer.d.ts +22 -0
  37. package/dist/phases/code-refine-verification/analysis/llm-analyzer.js +134 -0
  38. package/dist/phases/code-refine-verification/verifier.d.ts +47 -0
  39. package/dist/phases/code-refine-verification/verifier.js +597 -0
  40. package/dist/phases/code-review/analyzer.d.ts +29 -0
  41. package/dist/phases/code-review/analyzer.js +363 -0
  42. package/dist/phases/code-review/context-fetcher.d.ts +92 -0
  43. package/dist/phases/code-review/context-fetcher.js +296 -0
  44. package/dist/phases/feature-analysis/analyzer-helpers.d.ts +10 -0
  45. package/dist/phases/feature-analysis/analyzer-helpers.js +47 -0
  46. package/dist/phases/feature-analysis/analyzer.d.ts +11 -0
  47. package/dist/phases/feature-analysis/analyzer.js +208 -0
  48. package/dist/phases/feature-analysis/context-fetcher.d.ts +26 -0
  49. package/dist/phases/feature-analysis/context-fetcher.js +134 -0
  50. package/dist/phases/feature-analysis/http-fallback.d.ts +20 -0
  51. package/dist/phases/feature-analysis/http-fallback.js +95 -0
  52. package/dist/phases/feature-analysis/mcp-server.d.ts +1 -0
  53. package/dist/phases/feature-analysis/mcp-server.js +144 -0
  54. package/dist/phases/feature-analysis/prompts-improvement.d.ts +8 -0
  55. package/dist/phases/feature-analysis/prompts-improvement.js +109 -0
  56. package/dist/phases/feature-analysis-verification/verifier.d.ts +37 -0
  57. package/dist/phases/feature-analysis-verification/verifier.js +147 -0
  58. package/dist/phases/pr-execution/context.d.ts +26 -0
  59. package/dist/phases/pr-execution/context.js +156 -0
  60. package/dist/phases/pr-execution/index.d.ts +20 -0
  61. package/dist/phases/pr-execution/index.js +287 -0
  62. package/dist/phases/pr-execution/outcome.d.ts +26 -0
  63. package/dist/phases/pr-execution/outcome.js +34 -0
  64. package/dist/phases/pr-execution/pr-executor.d.ts +28 -0
  65. package/dist/phases/pr-execution/pr-executor.js +152 -0
  66. package/dist/phases/pr-execution/prompts.d.ts +17 -0
  67. package/dist/phases/pr-execution/prompts.js +208 -0
  68. package/dist/phases/pr-splitting/context.d.ts +16 -2
  69. package/dist/phases/pr-splitting/context.js +127 -4
  70. package/dist/phases/pr-splitting/index.d.ts +7 -0
  71. package/dist/phases/pr-splitting/index.js +58 -52
  72. package/dist/phases/pr-splitting/prompts.d.ts +4 -4
  73. package/dist/phases/pr-splitting/prompts.js +42 -30
  74. package/dist/phases/technical-design/analyzer-helpers.d.ts +25 -0
  75. package/dist/phases/technical-design/analyzer-helpers.js +39 -0
  76. package/dist/phases/technical-design/analyzer.d.ts +21 -0
  77. package/dist/phases/technical-design/analyzer.js +461 -0
  78. package/dist/phases/technical-design/context-fetcher.d.ts +12 -0
  79. package/dist/phases/technical-design/context-fetcher.js +39 -0
  80. package/dist/phases/technical-design/http-fallback.d.ts +17 -0
  81. package/dist/phases/technical-design/http-fallback.js +151 -0
  82. package/dist/phases/technical-design/mcp-server.d.ts +1 -0
  83. package/dist/phases/technical-design/mcp-server.js +157 -0
  84. package/dist/phases/technical-design/prompts-improvement.d.ts +5 -0
  85. package/dist/phases/technical-design/prompts-improvement.js +93 -0
  86. package/dist/phases/technical-design-verification/verifier.d.ts +53 -0
  87. package/dist/phases/technical-design-verification/verifier.js +170 -0
  88. package/dist/services/audit-logs.d.ts +2 -2
  89. package/dist/services/feature-branches.d.ts +77 -0
  90. package/dist/services/feature-branches.js +205 -0
  91. package/dist/types/index.d.ts +1 -1
  92. package/dist/types/pipeline.d.ts +1 -1
  93. package/dist/utils/github-repo-info.d.ts +14 -0
  94. package/dist/utils/github-repo-info.js +19 -0
  95. package/dist/workflow-runner/config/phase-configs.d.ts +5 -0
  96. package/dist/workflow-runner/config/phase-configs.js +120 -0
  97. package/dist/workflow-runner/core/feature-filter.d.ts +16 -0
  98. package/dist/workflow-runner/core/feature-filter.js +46 -0
  99. package/dist/workflow-runner/core/index.d.ts +8 -0
  100. package/dist/workflow-runner/core/index.js +12 -0
  101. package/dist/workflow-runner/core/pipeline-evaluator.d.ts +24 -0
  102. package/dist/workflow-runner/core/pipeline-evaluator.js +32 -0
  103. package/dist/workflow-runner/core/state-manager.d.ts +24 -0
  104. package/dist/workflow-runner/core/state-manager.js +42 -0
  105. package/dist/workflow-runner/core/workflow-logger.d.ts +20 -0
  106. package/dist/workflow-runner/core/workflow-logger.js +65 -0
  107. package/dist/workflow-runner/executors/phase-executor.d.ts +8 -0
  108. package/dist/workflow-runner/executors/phase-executor.js +248 -0
  109. package/dist/workflow-runner/feature-workflow-runner.d.ts +26 -0
  110. package/dist/workflow-runner/feature-workflow-runner.js +119 -0
  111. package/dist/workflow-runner/index.d.ts +2 -0
  112. package/dist/workflow-runner/index.js +2 -0
  113. package/dist/workflow-runner/pipeline-runner.d.ts +17 -0
  114. package/dist/workflow-runner/pipeline-runner.js +393 -0
  115. package/dist/workflow-runner/workflow-processor.d.ts +54 -0
  116. package/dist/workflow-runner/workflow-processor.js +170 -0
  117. package/package.json +1 -1
  118. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +0 -4
  119. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +0 -133
  120. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +0 -4
  121. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +0 -336
  122. package/dist/services/lifecycle-agent/index.d.ts +0 -24
  123. package/dist/services/lifecycle-agent/index.js +0 -25
  124. package/dist/services/lifecycle-agent/phase-criteria.d.ts +0 -57
  125. package/dist/services/lifecycle-agent/phase-criteria.js +0 -335
  126. package/dist/services/lifecycle-agent/transition-rules.d.ts +0 -60
  127. package/dist/services/lifecycle-agent/transition-rules.js +0 -184
  128. package/dist/services/lifecycle-agent/types.d.ts +0 -190
  129. package/dist/services/lifecycle-agent/types.js +0 -12
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Build a successful execution result
3
+ */
4
+ export function buildExecutionSuccessResult(featureId, executionSummary, summary) {
5
+ const details = executionSummary.branchesUpdated > 0
6
+ ? `${executionSummary.branchesUpdated} branches updated, ${executionSummary.prsUpdated} PRs updated`
7
+ : `${executionSummary.branchesCreated} branches created, ${executionSummary.prsCreated} PRs created`;
8
+ return {
9
+ featureId,
10
+ status: 'success',
11
+ summary: `${summary} (${details})`,
12
+ executionSummary,
13
+ };
14
+ }
15
+ /**
16
+ * Build an error result
17
+ */
18
+ export function buildExecutionErrorResult(featureId, errorMessage) {
19
+ return {
20
+ featureId,
21
+ status: 'error',
22
+ summary: `PR execution failed: ${errorMessage}`,
23
+ };
24
+ }
25
+ /**
26
+ * Build a no-change result
27
+ */
28
+ export function buildNoChangeResult(featureId, prCount) {
29
+ return {
30
+ featureId,
31
+ status: 'success',
32
+ summary: `All ${prCount} PR branches are already synced to latest commit. No changes needed.`,
33
+ };
34
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * PR Executor for pushing branches and creating GitHub pull requests
3
+ * Handles fork-aware PR creation
4
+ */
5
+ import type { RepoForkInfo } from '../../utils/github-repo-info.js';
6
+ export interface PRExecutionConfig {
7
+ githubToken: string;
8
+ owner: string;
9
+ repo: string;
10
+ forkInfo: RepoForkInfo;
11
+ verbose?: boolean;
12
+ }
13
+ export interface PRExecutionResult {
14
+ branchName: string;
15
+ prUrl?: string;
16
+ prNumber?: number;
17
+ headSha: string;
18
+ success: boolean;
19
+ error?: string;
20
+ }
21
+ /**
22
+ * Push a branch and create a GitHub PR (fork-aware)
23
+ */
24
+ export declare function pushAndCreateGitHubPR(config: PRExecutionConfig, branchName: string, title: string, description: string): Promise<PRExecutionResult>;
25
+ /**
26
+ * Update a PR record in the database with GitHub PR info and sync commit
27
+ */
28
+ export declare function updatePRDatabaseRecord(prId: string, prUrl: string, prNumber: number, headSha: string, verbose?: boolean): Promise<void>;
@@ -0,0 +1,152 @@
1
+ /**
2
+ * PR Executor for pushing branches and creating GitHub pull requests
3
+ * Handles fork-aware PR creation
4
+ */
5
+ import { Octokit } from '@octokit/rest';
6
+ import { execSync } from 'child_process';
7
+ import { logInfo, logError } from '../../utils/logger.js';
8
+ import { updatePullRequest } from '../../services/pull-requests.js';
9
+ /**
10
+ * Push a branch to remote with force-with-lease fallback
11
+ */
12
+ function pushBranch(branchName, verbose) {
13
+ try {
14
+ if (verbose) {
15
+ logInfo(`📤 Pushing branch ${branchName} to remote...`);
16
+ }
17
+ try {
18
+ execSync(`git push -u origin ${branchName}`, {
19
+ encoding: 'utf-8',
20
+ stdio: verbose ? 'inherit' : 'pipe',
21
+ });
22
+ }
23
+ catch {
24
+ try {
25
+ execSync(`git push origin ${branchName}`, {
26
+ encoding: 'utf-8',
27
+ stdio: verbose ? 'inherit' : 'pipe',
28
+ });
29
+ }
30
+ catch {
31
+ if (verbose) {
32
+ logInfo(`⚠️ Push rejected, attempting force push with lease...`);
33
+ }
34
+ execSync(`git push --force-with-lease origin ${branchName}`, {
35
+ encoding: 'utf-8',
36
+ stdio: verbose ? 'inherit' : 'pipe',
37
+ });
38
+ if (verbose) {
39
+ logInfo(`✅ Successfully force pushed ${branchName}`);
40
+ }
41
+ }
42
+ }
43
+ }
44
+ catch (error) {
45
+ throw new Error(`Failed to push branch ${branchName}: ${error instanceof Error ? error.message : String(error)}`);
46
+ }
47
+ }
48
+ /**
49
+ * Get the HEAD SHA of a local branch
50
+ */
51
+ function getLocalBranchSha(branchName) {
52
+ return execSync(`git rev-parse ${branchName}`, {
53
+ encoding: 'utf-8',
54
+ }).trim();
55
+ }
56
+ /**
57
+ * Push a branch and create a GitHub PR (fork-aware)
58
+ */
59
+ export async function pushAndCreateGitHubPR(config, branchName, title, description) {
60
+ const { githubToken, owner, repo, forkInfo, verbose } = config;
61
+ const headSha = getLocalBranchSha(branchName);
62
+ try {
63
+ // Push the branch to origin
64
+ pushBranch(branchName, verbose);
65
+ const octokit = new Octokit({ auth: githubToken });
66
+ // Determine PR target based on fork status
67
+ let prOwner;
68
+ let prRepo;
69
+ let prHead;
70
+ const prBase = 'main';
71
+ if (forkInfo.isFork && forkInfo.upstream) {
72
+ prOwner = forkInfo.upstream.owner;
73
+ prRepo = forkInfo.upstream.repo;
74
+ prHead = `${owner}:${branchName}`;
75
+ if (verbose) {
76
+ logInfo(`📝 Creating PR on upstream ${prOwner}/${prRepo}: ${prHead} → ${prBase}`);
77
+ }
78
+ }
79
+ else {
80
+ prOwner = owner;
81
+ prRepo = repo;
82
+ prHead = branchName;
83
+ if (verbose) {
84
+ logInfo(`📝 Creating PR on ${prOwner}/${prRepo}: ${prHead} → ${prBase}`);
85
+ }
86
+ }
87
+ // Check for existing open PR
88
+ const { data: existingPRs } = await octokit.pulls.list({
89
+ owner: prOwner,
90
+ repo: prRepo,
91
+ head: forkInfo.isFork ? prHead : `${prOwner}:${prHead}`,
92
+ base: prBase,
93
+ state: 'open',
94
+ });
95
+ if (existingPRs.length > 0) {
96
+ const existingPR = existingPRs[0];
97
+ if (verbose) {
98
+ logInfo(`ℹ️ Found existing PR #${existingPR.number}: ${existingPR.html_url}`);
99
+ }
100
+ return {
101
+ branchName,
102
+ prUrl: existingPR.html_url,
103
+ prNumber: existingPR.number,
104
+ headSha,
105
+ success: true,
106
+ };
107
+ }
108
+ // Create new PR
109
+ const { data: newPR } = await octokit.pulls.create({
110
+ owner: prOwner,
111
+ repo: prRepo,
112
+ title,
113
+ body: description,
114
+ head: prHead,
115
+ base: prBase,
116
+ draft: false,
117
+ });
118
+ if (verbose) {
119
+ logInfo(`✅ Pull request created: ${newPR.html_url}`);
120
+ }
121
+ return {
122
+ branchName,
123
+ prUrl: newPR.html_url,
124
+ prNumber: newPR.number,
125
+ headSha,
126
+ success: true,
127
+ };
128
+ }
129
+ catch (error) {
130
+ const errorMessage = error instanceof Error ? error.message : String(error);
131
+ if (verbose) {
132
+ logError(`❌ Failed to create PR for ${branchName}: ${errorMessage}`);
133
+ }
134
+ return {
135
+ branchName,
136
+ headSha,
137
+ success: false,
138
+ error: errorMessage,
139
+ };
140
+ }
141
+ }
142
+ /**
143
+ * Update a PR record in the database with GitHub PR info and sync commit
144
+ */
145
+ export async function updatePRDatabaseRecord(prId, prUrl, prNumber, headSha, verbose) {
146
+ await updatePullRequest(prId, {
147
+ pull_request_url: prUrl,
148
+ pull_request_number: prNumber,
149
+ status: 'pr_opened',
150
+ last_synced_commit: headSha,
151
+ }, verbose);
152
+ }
@@ -0,0 +1,17 @@
1
+ import type { PullRequest } from '../../services/pull-requests.js';
2
+ /**
3
+ * Create the system prompt for branch creation and code splitting
4
+ */
5
+ export declare function createPRExecutionSystemPrompt(featureId: string, devBranchName: string): string;
6
+ /**
7
+ * Create the system prompt for incremental sync (re-runs)
8
+ */
9
+ export declare function createIncrementalSyncSystemPrompt(featureId: string, devBranchName: string): string;
10
+ /**
11
+ * Create the user prompt for first-time branch creation
12
+ */
13
+ export declare function createPRExecutionPrompt(featureId: string, devBranchName: string, pullRequests: PullRequest[]): string;
14
+ /**
15
+ * Create the user prompt for incremental sync
16
+ */
17
+ export declare function createIncrementalSyncPrompt(featureId: string, devBranchName: string, pullRequests: PullRequest[], lastSyncedCommit: string, diffStat: string, changedFiles: string[]): string;
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Create the system prompt for branch creation and code splitting
3
+ */
4
+ export function createPRExecutionSystemPrompt(featureId, devBranchName) {
5
+ return `You are a git operations expert. Your task is to create PR branches and move the right code changes to each branch.
6
+
7
+ ## Task
8
+
9
+ You will receive a PR plan with file assignments. For each PR in sequence order, you must:
10
+
11
+ 1. Switch to \`main\` branch
12
+ 2. Create a new branch \`pr/${featureId}/N-description\` from \`main\`
13
+ 3. Apply ONLY the relevant file changes from the \`${devBranchName}\` branch to this new branch
14
+ 4. Commit the changes with a meaningful commit message
15
+
16
+ ## Strategy for Moving Code
17
+
18
+ Choose the best approach for each PR:
19
+
20
+ ### Option A: Selective file checkout (recommended for most cases)
21
+ \`\`\`bash
22
+ git checkout main
23
+ git checkout -b pr/${featureId}/1-description
24
+ git checkout ${devBranchName} -- path/to/file1 path/to/file2
25
+ git commit -m "feat: descriptive message"
26
+ \`\`\`
27
+
28
+ ### Option B: Using git diff + apply (for partial file changes)
29
+ \`\`\`bash
30
+ git checkout main
31
+ git checkout -b pr/${featureId}/1-description
32
+ git diff main...${devBranchName} -- path/to/file | git apply
33
+ git add -A
34
+ git commit -m "feat: descriptive message"
35
+ \`\`\`
36
+
37
+ ## Rules
38
+
39
+ 1. **Always start each PR branch from \`main\`** — never from another PR branch
40
+ 2. **Each branch must be independent** — it should compile/build on its own when merged to main
41
+ 3. **Do NOT push branches** — just create them locally. Pushing is handled separately.
42
+ 4. **Verify after each branch**: Run \`git diff --stat main...pr/${featureId}/N-description\` to confirm only the expected files changed
43
+ 5. **Commit all changes** before switching branches
44
+ 6. **Handle new files**: For newly added files, use \`git checkout ${devBranchName} -- path/to/new/file\`
45
+ 7. **Handle deleted files**: For deleted files, use \`git rm path/to/deleted/file\`
46
+ 8. **After all branches are created**, switch back to \`main\`
47
+
48
+ ## Output
49
+
50
+ After creating all branches, respond with:
51
+
52
+ \`\`\`json
53
+ {
54
+ "execution_result": {
55
+ "status": "success",
56
+ "branches_created": ["pr/${featureId}/1-desc", "pr/${featureId}/2-desc"],
57
+ "summary": "Brief description of what was done"
58
+ }
59
+ }
60
+ \`\`\`
61
+
62
+ If any branch fails, include the error but continue with remaining branches:
63
+
64
+ \`\`\`json
65
+ {
66
+ "execution_result": {
67
+ "status": "partial",
68
+ "branches_created": ["pr/${featureId}/1-desc"],
69
+ "branches_failed": [{"branch": "pr/${featureId}/2-desc", "error": "..."}],
70
+ "summary": "..."
71
+ }
72
+ }
73
+ \`\`\``;
74
+ }
75
+ /**
76
+ * Create the system prompt for incremental sync (re-runs)
77
+ */
78
+ export function createIncrementalSyncSystemPrompt(featureId, devBranchName) {
79
+ return `You are a git operations expert. Your task is to update existing PR branches with new changes from the dev branch.
80
+
81
+ ## Task
82
+
83
+ The PR branches already exist from a previous run. New changes have been made on \`${devBranchName}\` since the last sync. You need to update each PR branch with the relevant new changes.
84
+
85
+ ## Strategy for Updating Branches
86
+
87
+ For each existing PR branch:
88
+
89
+ 1. Switch to the PR branch: \`git checkout pr/${featureId}/N-description\`
90
+ 2. Apply the relevant NEW changes from \`${devBranchName}\`
91
+ 3. Commit the changes
92
+
93
+ ### Option A: Selective file checkout (for files entirely owned by this PR)
94
+ \`\`\`bash
95
+ git checkout pr/${featureId}/1-description
96
+ git checkout ${devBranchName} -- path/to/file1 path/to/file2
97
+ git commit -m "sync: update with latest changes from dev"
98
+ \`\`\`
99
+
100
+ ### Option B: Using diff + apply (for partial changes)
101
+ \`\`\`bash
102
+ git checkout pr/${featureId}/1-description
103
+ git diff <last_synced_commit>...${devBranchName} -- path/to/file | git apply
104
+ git add -A
105
+ git commit -m "sync: update with latest changes from dev"
106
+ \`\`\`
107
+
108
+ ## Rules
109
+
110
+ 1. **Only update files that belong to each PR** according to the file assignments
111
+ 2. **Do NOT push branches** — just update them locally. Pushing is handled separately.
112
+ 3. **If a branch doesn't exist locally**, check remote and create a tracking branch
113
+ 4. **Verify after each update**: Check that the branch has the expected changes
114
+ 5. **Commit all changes** before switching branches
115
+ 6. **After all branches are updated**, switch back to \`main\`
116
+
117
+ ## Output
118
+
119
+ After updating all branches, respond with:
120
+
121
+ \`\`\`json
122
+ {
123
+ "execution_result": {
124
+ "status": "success",
125
+ "branches_updated": ["pr/${featureId}/1-desc", "pr/${featureId}/2-desc"],
126
+ "summary": "Brief description of what was done"
127
+ }
128
+ }
129
+ \`\`\``;
130
+ }
131
+ /**
132
+ * Create the user prompt for first-time branch creation
133
+ */
134
+ export function createPRExecutionPrompt(featureId, devBranchName, pullRequests) {
135
+ const prList = pullRequests
136
+ .map((pr) => {
137
+ const files = pr.files
138
+ ? pr.files.map((f) => ` - ${f.path} (${f.change_type})`).join('\n')
139
+ : ' (no files specified)';
140
+ return `### PR ${pr.sequence}: ${pr.name}
141
+ - Branch: \`${pr.branch_name}\`
142
+ - Description: ${pr.description}
143
+ - Files:
144
+ ${files}`;
145
+ })
146
+ .join('\n\n');
147
+ return `# Create PR Branches
148
+
149
+ Create the following PR branches from \`main\`, applying the relevant changes from \`${devBranchName}\`:
150
+
151
+ ${prList}
152
+
153
+ ## Instructions
154
+
155
+ For each PR above (in sequence order):
156
+ 1. \`git checkout main\`
157
+ 2. Create the branch: \`git checkout -b <branch_name>\`
158
+ 3. Apply only the listed files from \`${devBranchName}\`
159
+ 4. Commit with a descriptive message
160
+ 5. Verify with \`git diff --stat main...<branch_name>\`
161
+
162
+ After all branches are created, switch back to \`main\` and provide the execution result JSON.`;
163
+ }
164
+ /**
165
+ * Create the user prompt for incremental sync
166
+ */
167
+ export function createIncrementalSyncPrompt(featureId, devBranchName, pullRequests, lastSyncedCommit, diffStat, changedFiles) {
168
+ const prList = pullRequests
169
+ .map((pr) => {
170
+ const files = pr.files
171
+ ? pr.files.map((f) => ` - ${f.path} (${f.change_type})`).join('\n')
172
+ : ' (no files specified)';
173
+ return `### PR ${pr.sequence}: ${pr.name}
174
+ - Branch: \`${pr.branch_name}\`
175
+ - Files:
176
+ ${files}`;
177
+ })
178
+ .join('\n\n');
179
+ return `# Sync PR Branches with Latest Changes
180
+
181
+ New changes have been made on \`${devBranchName}\` since the last sync at commit \`${lastSyncedCommit}\`.
182
+
183
+ ## New Changes Since Last Sync
184
+
185
+ ### Diff Stat
186
+ \`\`\`
187
+ ${diffStat || 'No changes detected'}
188
+ \`\`\`
189
+
190
+ ### Changed Files (${changedFiles.length} files)
191
+ ${changedFiles.map((f) => `- ${f}`).join('\n') || 'No files changed'}
192
+
193
+ ## Existing PR Branches
194
+
195
+ ${prList}
196
+
197
+ ## Instructions
198
+
199
+ For each PR branch that has files affected by the new changes:
200
+ 1. Switch to the PR branch
201
+ 2. Apply the relevant new changes from \`${devBranchName}\`
202
+ 3. Commit with a sync message
203
+ 4. Verify the branch state
204
+
205
+ If a PR branch has no files affected by the new changes, skip it.
206
+
207
+ After all branches are updated, switch back to \`main\` and provide the execution result JSON.`;
208
+ }
@@ -2,13 +2,27 @@ import type { FeatureInfo } from '../../types/features.js';
2
2
  import { type ProductInfo } from '../../api/products.js';
3
3
  import { type Branch } from '../../services/branches.js';
4
4
  import { type PullRequest } from '../../services/pull-requests.js';
5
+ import { type RepoForkInfo } from '../../utils/github-repo-info.js';
6
+ export interface GitHubConfigInfo {
7
+ configured: boolean;
8
+ token?: string;
9
+ owner?: string;
10
+ repo?: string;
11
+ message?: string;
12
+ }
5
13
  export interface PRSplittingContext {
6
14
  feature: FeatureInfo;
7
15
  product: ProductInfo;
8
16
  existing_branches: Branch[];
9
17
  existing_pull_requests: PullRequest[];
18
+ diffStat: string;
19
+ changedFiles: string[];
20
+ devBranchName: string;
21
+ devBranchHeadSha: string;
22
+ githubConfig: GitHubConfigInfo;
23
+ forkInfo: RepoForkInfo;
10
24
  }
11
25
  /**
12
- * Fetch all context information needed for PR splitting via MCP endpoints
26
+ * Fetch all context information needed for PR splitting
13
27
  */
14
- export declare function fetchPRSplittingContext(featureId: string, verbose?: boolean): Promise<PRSplittingContext>;
28
+ export declare function fetchPRSplittingContext(featureId: string, verbose?: boolean, replaceExisting?: boolean): Promise<PRSplittingContext>;
@@ -1,36 +1,159 @@
1
+ import { execSync } from 'child_process';
1
2
  import { logInfo, logError } from '../../utils/logger.js';
2
3
  import { getFeature } from '../../api/features/index.js';
3
4
  import { getProduct } from '../../api/products.js';
4
5
  import { getBranches } from '../../services/branches.js';
5
6
  import { getPullRequests, } from '../../services/pull-requests.js';
7
+ import { getGitHubConfig } from '../../api/github.js';
8
+ import { getRepoForkInfo, } from '../../utils/github-repo-info.js';
9
+ import { branchExists, remoteBranchExists, } from '../../utils/git-branch-manager.js';
6
10
  /**
7
- * Fetch all context information needed for PR splitting via MCP endpoints
11
+ * Get the dev branch name for a feature
8
12
  */
9
- export async function fetchPRSplittingContext(featureId, verbose) {
13
+ function getDevBranchName(featureId) {
14
+ return `dev/${featureId}`;
15
+ }
16
+ /**
17
+ * Get the HEAD SHA of a branch
18
+ */
19
+ function getBranchHeadSha(branchName) {
20
+ return execSync(`git rev-parse ${branchName}`, { encoding: 'utf-8' }).trim();
21
+ }
22
+ /**
23
+ * Get diff stat between two refs
24
+ */
25
+ function getDiffStat(baseRef, headRef) {
26
+ try {
27
+ return execSync(`git diff --stat ${baseRef}...${headRef}`, {
28
+ encoding: 'utf-8',
29
+ }).trim();
30
+ }
31
+ catch {
32
+ return '';
33
+ }
34
+ }
35
+ /**
36
+ * Get list of changed files between two refs
37
+ */
38
+ function getChangedFiles(baseRef, headRef) {
39
+ try {
40
+ const output = execSync(`git diff --name-only ${baseRef}...${headRef}`, { encoding: 'utf-8' }).trim();
41
+ if (!output)
42
+ return [];
43
+ return output.split('\n').filter((f) => f.length > 0);
44
+ }
45
+ catch {
46
+ return [];
47
+ }
48
+ }
49
+ /**
50
+ * Determine the diff base ref for incremental re-runs
51
+ * If existing PRs have last_synced_commit, use the earliest one
52
+ * Otherwise use main
53
+ */
54
+ function determineDiffBaseRef(existingPRs, replaceExisting) {
55
+ if (replaceExisting || existingPRs.length === 0) {
56
+ return 'main';
57
+ }
58
+ // Find the minimum last_synced_commit (earliest sync point)
59
+ const syncedCommits = existingPRs
60
+ .map((pr) => pr.last_synced_commit)
61
+ .filter((c) => c !== null);
62
+ if (syncedCommits.length === 0) {
63
+ return 'main';
64
+ }
65
+ // All PRs should have been synced to the same commit
66
+ // Use the first one (they should all be equal after a successful sync)
67
+ return syncedCommits[0];
68
+ }
69
+ /**
70
+ * Fetch all context information needed for PR splitting
71
+ */
72
+ export async function fetchPRSplittingContext(featureId, verbose, replaceExisting) {
10
73
  try {
11
74
  if (verbose) {
12
75
  logInfo(`Fetching PR splitting context for feature: ${featureId}`);
13
76
  }
14
- // Fetch all required data in parallel for better performance
77
+ const devBranchName = getDevBranchName(featureId);
78
+ // Verify dev branch exists
79
+ const localExists = branchExists(devBranchName);
80
+ const remoteExists = !localExists && remoteBranchExists(devBranchName);
81
+ if (!localExists && !remoteExists) {
82
+ throw new Error(`Development branch '${devBranchName}' does not exist. ` +
83
+ `The feature must have code on the dev branch before PR splitting.`);
84
+ }
85
+ // If branch only exists on remote, fetch it
86
+ if (!localExists && remoteExists) {
87
+ if (verbose) {
88
+ logInfo(`Fetching remote branch ${devBranchName}...`);
89
+ }
90
+ execSync(`git fetch origin ${devBranchName}`, {
91
+ encoding: 'utf-8',
92
+ stdio: 'pipe',
93
+ });
94
+ }
95
+ // Fetch database data in parallel
15
96
  const [feature, existing_branches, existing_pull_requests] = await Promise.all([
16
97
  getFeature(featureId, verbose),
17
98
  getBranches({ featureId, verbose }).catch(() => []),
18
99
  getPullRequests({ featureId, verbose }).catch(() => []),
19
100
  ]);
20
101
  const product = await getProduct(feature.product_id, verbose);
102
+ // Fetch GitHub config
103
+ const githubConfig = await getGitHubConfig(featureId, verbose);
104
+ // Detect fork status
105
+ let forkInfo = { isFork: false };
106
+ if (githubConfig.configured && githubConfig.token && githubConfig.owner && githubConfig.repo) {
107
+ try {
108
+ forkInfo = await getRepoForkInfo(githubConfig.token, githubConfig.owner, githubConfig.repo);
109
+ if (verbose) {
110
+ logInfo(forkInfo.isFork
111
+ ? `📌 Repository is a fork of ${forkInfo.upstream?.owner}/${forkInfo.upstream?.repo}`
112
+ : `📌 Repository is not a fork`);
113
+ }
114
+ }
115
+ catch (error) {
116
+ if (verbose) {
117
+ logError(`Failed to detect fork status: ${error instanceof Error ? error.message : String(error)}`);
118
+ }
119
+ }
120
+ }
121
+ // Determine diff range
122
+ const devRef = localExists ? devBranchName : `origin/${devBranchName}`;
123
+ const baseRef = determineDiffBaseRef(existing_pull_requests, replaceExisting);
124
+ const devBranchHeadSha = getBranchHeadSha(devRef);
125
+ // Check if there are new changes since last sync
126
+ if (baseRef !== 'main' && baseRef === devBranchHeadSha) {
127
+ if (verbose) {
128
+ logInfo(`No new changes since last sync (HEAD: ${devBranchHeadSha})`);
129
+ }
130
+ }
131
+ // Get diff information
132
+ const diffStat = getDiffStat(baseRef, devRef);
133
+ const changedFiles = getChangedFiles(baseRef, devRef);
21
134
  if (verbose) {
22
135
  logInfo(`✅ PR splitting context fetched successfully:`);
23
136
  logInfo(` Feature: ${feature.name}`);
24
137
  logInfo(` Product: ${product.name}`);
25
- logInfo(` Technical Design: ${feature.technical_design ? 'Yes' : 'No'}`);
138
+ logInfo(` Dev Branch: ${devBranchName}`);
139
+ logInfo(` Dev Branch HEAD: ${devBranchHeadSha}`);
140
+ logInfo(` Diff Base: ${baseRef}`);
141
+ logInfo(` Changed Files: ${changedFiles.length}`);
26
142
  logInfo(` Existing Branches: ${existing_branches.length}`);
27
143
  logInfo(` Existing Pull Requests: ${existing_pull_requests.length}`);
144
+ logInfo(` GitHub: ${githubConfig.configured ? 'Configured' : 'Not configured'}`);
28
145
  }
29
146
  return {
30
147
  feature,
31
148
  product,
32
149
  existing_branches,
33
150
  existing_pull_requests,
151
+ diffStat,
152
+ changedFiles,
153
+ devBranchName,
154
+ devBranchHeadSha,
155
+ githubConfig,
156
+ forkInfo,
34
157
  };
35
158
  }
36
159
  catch (error) {
@@ -24,6 +24,13 @@ export interface PRSplittingResult {
24
24
  summary: string;
25
25
  rationale?: string;
26
26
  }
27
+ /**
28
+ * PR Splitting Phase: Analyze diff and create PR split plan
29
+ *
30
+ * This phase analyzes the actual code diff between dev/{featureId} and main,
31
+ * then uses AI to produce a PR split plan saved to the database.
32
+ * Human review is expected before running the pr-execution phase.
33
+ */
27
34
  export declare const splitFeatureIntoPRs: (options: PRSplittingOptions, config: EdsgerConfig) => Promise<PRSplittingResult>;
28
35
  export { fetchPRSplittingContext } from './context.js';
29
36
  export type { PRSplittingContext } from './context.js';