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.
- package/.claude/settings.local.json +28 -0
- package/.env.local +12 -0
- package/dist/api/features/__tests__/regression-prevention.test.d.ts +5 -0
- package/dist/api/features/__tests__/regression-prevention.test.js +338 -0
- package/dist/api/features/__tests__/status-updater.integration.test.d.ts +5 -0
- package/dist/api/features/__tests__/status-updater.integration.test.js +497 -0
- package/dist/commands/workflow/config/phase-configs.js +5 -0
- package/dist/commands/workflow/executors/phase-executor.d.ts +2 -2
- package/dist/commands/workflow/executors/phase-executor.js +2 -2
- package/dist/commands/workflow/feature-coordinator.js +3 -1
- package/dist/commands/workflow/phase-orchestrator.js +66 -3
- package/dist/commands/workflow/pipeline-runner.d.ts +17 -0
- package/dist/commands/workflow/pipeline-runner.js +393 -0
- package/dist/commands/workflow/runner.d.ts +26 -0
- package/dist/commands/workflow/runner.js +119 -0
- package/dist/commands/workflow/workflow-runner.d.ts +26 -0
- package/dist/commands/workflow/workflow-runner.js +119 -0
- package/dist/config/feature-status.js +3 -0
- package/dist/index.js +0 -0
- package/dist/phases/code-implementation/analyzer-helpers.d.ts +28 -0
- package/dist/phases/code-implementation/analyzer-helpers.js +177 -0
- package/dist/phases/code-implementation/analyzer.d.ts +32 -0
- package/dist/phases/code-implementation/analyzer.js +629 -0
- package/dist/phases/code-implementation/context-fetcher.d.ts +17 -0
- package/dist/phases/code-implementation/context-fetcher.js +86 -0
- package/dist/phases/code-implementation/mcp-server.d.ts +1 -0
- package/dist/phases/code-implementation/mcp-server.js +93 -0
- package/dist/phases/code-implementation/prompts-improvement.d.ts +5 -0
- package/dist/phases/code-implementation/prompts-improvement.js +108 -0
- package/dist/phases/code-implementation-verification/verifier.d.ts +31 -0
- package/dist/phases/code-implementation-verification/verifier.js +196 -0
- package/dist/phases/code-refine/analyzer.d.ts +41 -0
- package/dist/phases/code-refine/analyzer.js +561 -0
- package/dist/phases/code-refine/context-fetcher.d.ts +94 -0
- package/dist/phases/code-refine/context-fetcher.js +423 -0
- package/dist/phases/code-refine-verification/analysis/llm-analyzer.d.ts +22 -0
- package/dist/phases/code-refine-verification/analysis/llm-analyzer.js +134 -0
- package/dist/phases/code-refine-verification/verifier.d.ts +47 -0
- package/dist/phases/code-refine-verification/verifier.js +597 -0
- package/dist/phases/code-review/analyzer.d.ts +29 -0
- package/dist/phases/code-review/analyzer.js +363 -0
- package/dist/phases/code-review/context-fetcher.d.ts +92 -0
- package/dist/phases/code-review/context-fetcher.js +296 -0
- package/dist/phases/feature-analysis/analyzer-helpers.d.ts +10 -0
- package/dist/phases/feature-analysis/analyzer-helpers.js +47 -0
- package/dist/phases/feature-analysis/analyzer.d.ts +11 -0
- package/dist/phases/feature-analysis/analyzer.js +208 -0
- package/dist/phases/feature-analysis/context-fetcher.d.ts +26 -0
- package/dist/phases/feature-analysis/context-fetcher.js +134 -0
- package/dist/phases/feature-analysis/http-fallback.d.ts +20 -0
- package/dist/phases/feature-analysis/http-fallback.js +95 -0
- package/dist/phases/feature-analysis/mcp-server.d.ts +1 -0
- package/dist/phases/feature-analysis/mcp-server.js +144 -0
- package/dist/phases/feature-analysis/prompts-improvement.d.ts +8 -0
- package/dist/phases/feature-analysis/prompts-improvement.js +109 -0
- package/dist/phases/feature-analysis-verification/verifier.d.ts +37 -0
- package/dist/phases/feature-analysis-verification/verifier.js +147 -0
- package/dist/phases/pr-execution/context.d.ts +26 -0
- package/dist/phases/pr-execution/context.js +156 -0
- package/dist/phases/pr-execution/index.d.ts +20 -0
- package/dist/phases/pr-execution/index.js +287 -0
- package/dist/phases/pr-execution/outcome.d.ts +26 -0
- package/dist/phases/pr-execution/outcome.js +34 -0
- package/dist/phases/pr-execution/pr-executor.d.ts +28 -0
- package/dist/phases/pr-execution/pr-executor.js +152 -0
- package/dist/phases/pr-execution/prompts.d.ts +17 -0
- package/dist/phases/pr-execution/prompts.js +208 -0
- package/dist/phases/pr-splitting/context.d.ts +16 -2
- package/dist/phases/pr-splitting/context.js +127 -4
- package/dist/phases/pr-splitting/index.d.ts +7 -0
- package/dist/phases/pr-splitting/index.js +58 -52
- package/dist/phases/pr-splitting/prompts.d.ts +4 -4
- package/dist/phases/pr-splitting/prompts.js +42 -30
- package/dist/phases/technical-design/analyzer-helpers.d.ts +25 -0
- package/dist/phases/technical-design/analyzer-helpers.js +39 -0
- package/dist/phases/technical-design/analyzer.d.ts +21 -0
- package/dist/phases/technical-design/analyzer.js +461 -0
- package/dist/phases/technical-design/context-fetcher.d.ts +12 -0
- package/dist/phases/technical-design/context-fetcher.js +39 -0
- package/dist/phases/technical-design/http-fallback.d.ts +17 -0
- package/dist/phases/technical-design/http-fallback.js +151 -0
- package/dist/phases/technical-design/mcp-server.d.ts +1 -0
- package/dist/phases/technical-design/mcp-server.js +157 -0
- package/dist/phases/technical-design/prompts-improvement.d.ts +5 -0
- package/dist/phases/technical-design/prompts-improvement.js +93 -0
- package/dist/phases/technical-design-verification/verifier.d.ts +53 -0
- package/dist/phases/technical-design-verification/verifier.js +170 -0
- package/dist/services/audit-logs.d.ts +2 -2
- package/dist/services/feature-branches.d.ts +77 -0
- package/dist/services/feature-branches.js +205 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/pipeline.d.ts +1 -1
- package/dist/utils/github-repo-info.d.ts +14 -0
- package/dist/utils/github-repo-info.js +19 -0
- package/dist/workflow-runner/config/phase-configs.d.ts +5 -0
- package/dist/workflow-runner/config/phase-configs.js +120 -0
- package/dist/workflow-runner/core/feature-filter.d.ts +16 -0
- package/dist/workflow-runner/core/feature-filter.js +46 -0
- package/dist/workflow-runner/core/index.d.ts +8 -0
- package/dist/workflow-runner/core/index.js +12 -0
- package/dist/workflow-runner/core/pipeline-evaluator.d.ts +24 -0
- package/dist/workflow-runner/core/pipeline-evaluator.js +32 -0
- package/dist/workflow-runner/core/state-manager.d.ts +24 -0
- package/dist/workflow-runner/core/state-manager.js +42 -0
- package/dist/workflow-runner/core/workflow-logger.d.ts +20 -0
- package/dist/workflow-runner/core/workflow-logger.js +65 -0
- package/dist/workflow-runner/executors/phase-executor.d.ts +8 -0
- package/dist/workflow-runner/executors/phase-executor.js +248 -0
- package/dist/workflow-runner/feature-workflow-runner.d.ts +26 -0
- package/dist/workflow-runner/feature-workflow-runner.js +119 -0
- package/dist/workflow-runner/index.d.ts +2 -0
- package/dist/workflow-runner/index.js +2 -0
- package/dist/workflow-runner/pipeline-runner.d.ts +17 -0
- package/dist/workflow-runner/pipeline-runner.js +393 -0
- package/dist/workflow-runner/workflow-processor.d.ts +54 -0
- package/dist/workflow-runner/workflow-processor.js +170 -0
- package/package.json +1 -1
- package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +0 -4
- package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +0 -133
- package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +0 -4
- package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +0 -336
- package/dist/services/lifecycle-agent/index.d.ts +0 -24
- package/dist/services/lifecycle-agent/index.js +0 -25
- package/dist/services/lifecycle-agent/phase-criteria.d.ts +0 -57
- package/dist/services/lifecycle-agent/phase-criteria.js +0 -335
- package/dist/services/lifecycle-agent/transition-rules.d.ts +0 -60
- package/dist/services/lifecycle-agent/transition-rules.js +0 -184
- package/dist/services/lifecycle-agent/types.d.ts +0 -190
- 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
|
|
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
|
-
*
|
|
11
|
+
* Get the dev branch name for a feature
|
|
8
12
|
*/
|
|
9
|
-
|
|
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
|
-
|
|
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(`
|
|
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';
|