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,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checklist verification agent for feature analysis
|
|
3
|
+
* This agent independently reviews checklist compliance to ensure objectivity
|
|
4
|
+
*/
|
|
5
|
+
import { query } from '@anthropic-ai/claude-code';
|
|
6
|
+
import { logInfo, logError } from '../../utils/logger.js';
|
|
7
|
+
import { createChecklistVerificationPrompt, createChecklistVerificationSystemPrompt, } from './prompts.js';
|
|
8
|
+
function userMessage(content) {
|
|
9
|
+
return {
|
|
10
|
+
type: 'user',
|
|
11
|
+
message: { role: 'user', content: content },
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
async function* prompt(verificationPrompt) {
|
|
15
|
+
yield userMessage(verificationPrompt);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Verify checklist compliance using an independent AI agent
|
|
19
|
+
* This agent acts as a "challenger" to validate the claims made by the analysis agent
|
|
20
|
+
*/
|
|
21
|
+
export async function verifyChecklistCompliance(options, config) {
|
|
22
|
+
const { checklistContext, analysisContext, createdUserStories, createdTestCases, verbose, } = options;
|
|
23
|
+
const totalChecklistItems = checklistContext.checklists.reduce((sum, checklist) => sum + checklist.items.length, 0);
|
|
24
|
+
if (verbose) {
|
|
25
|
+
logInfo('š Starting checklist verification...');
|
|
26
|
+
logInfo(` Verifying ${totalChecklistItems} checklist items`);
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const systemPrompt = createChecklistVerificationSystemPrompt(config);
|
|
30
|
+
const verificationPrompt = createChecklistVerificationPrompt({
|
|
31
|
+
checklistContext,
|
|
32
|
+
analysisContext,
|
|
33
|
+
createdUserStories,
|
|
34
|
+
createdTestCases,
|
|
35
|
+
});
|
|
36
|
+
let lastAssistantResponse = '';
|
|
37
|
+
let verificationResult = null;
|
|
38
|
+
if (verbose) {
|
|
39
|
+
logInfo('š¤ Starting verification agent query...');
|
|
40
|
+
}
|
|
41
|
+
// Use Claude Code SDK for verification
|
|
42
|
+
for await (const message of query({
|
|
43
|
+
prompt: prompt(verificationPrompt),
|
|
44
|
+
options: {
|
|
45
|
+
appendSystemPrompt: systemPrompt,
|
|
46
|
+
model: config.claude.model || 'sonnet',
|
|
47
|
+
maxTurns: 100,
|
|
48
|
+
permissionMode: 'bypassPermissions',
|
|
49
|
+
},
|
|
50
|
+
})) {
|
|
51
|
+
if (verbose) {
|
|
52
|
+
logInfo(` Received message type: ${message.type}`);
|
|
53
|
+
}
|
|
54
|
+
// Capture assistant responses
|
|
55
|
+
if (message.type === 'assistant' && message.message?.content) {
|
|
56
|
+
for (const content of message.message.content) {
|
|
57
|
+
if (content.type === 'text') {
|
|
58
|
+
lastAssistantResponse += content.text + '\n';
|
|
59
|
+
if (verbose) {
|
|
60
|
+
console.log(`\nš ${content.text}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (message.type === 'result') {
|
|
66
|
+
if (message.subtype === 'success') {
|
|
67
|
+
logInfo('\nā
Verification completed, parsing results...');
|
|
68
|
+
try {
|
|
69
|
+
const responseText = message.result || lastAssistantResponse;
|
|
70
|
+
// Try to extract JSON from markdown code block
|
|
71
|
+
const jsonBlockMatch = responseText.match(/```json\s*\n([\s\S]*?)\n\s*```/);
|
|
72
|
+
let jsonResult = null;
|
|
73
|
+
if (jsonBlockMatch) {
|
|
74
|
+
jsonResult = JSON.parse(jsonBlockMatch[1]);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
jsonResult = JSON.parse(responseText);
|
|
78
|
+
}
|
|
79
|
+
if (jsonResult && jsonResult.verification) {
|
|
80
|
+
verificationResult = jsonResult.verification;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
throw new Error('Invalid verification JSON structure');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
logError(`Failed to parse verification result: ${error}`);
|
|
88
|
+
// Return default "uncertain" result
|
|
89
|
+
verificationResult = createUncertainVerificationResult(checklistContext, 'Failed to parse verification response');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
logError(`\nā ļø Verification incomplete: ${message.subtype}`);
|
|
94
|
+
verificationResult = createUncertainVerificationResult(checklistContext, `Verification incomplete: ${message.subtype}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!verificationResult) {
|
|
99
|
+
verificationResult = createUncertainVerificationResult(checklistContext, 'No verification result received');
|
|
100
|
+
}
|
|
101
|
+
if (verbose) {
|
|
102
|
+
logInfo('\nš Verification Summary:');
|
|
103
|
+
logInfo(` Total items: ${verificationResult.total_items}`);
|
|
104
|
+
logInfo(` ā
Confirmed: ${verificationResult.confirmed_count}`);
|
|
105
|
+
logInfo(` ā Rejected: ${verificationResult.rejected_count}`);
|
|
106
|
+
logInfo(` ā ļø Uncertain: ${verificationResult.uncertain_count}`);
|
|
107
|
+
logInfo(` Summary: ${verificationResult.summary}`);
|
|
108
|
+
if (verificationResult.rejected_count > 0) {
|
|
109
|
+
logInfo('\nā Rejected items:');
|
|
110
|
+
verificationResult.item_verifications
|
|
111
|
+
.filter((v) => v.verification_status === 'rejected')
|
|
112
|
+
.forEach((v) => {
|
|
113
|
+
logInfo(` - ${v.checklist_item_id}`);
|
|
114
|
+
logInfo(` Reason: ${v.verification_reason}`);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return verificationResult;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
logError(`Checklist verification failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
122
|
+
return createUncertainVerificationResult(checklistContext, `Verification error: ${error instanceof Error ? error.message : String(error)}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Create a default "uncertain" verification result when verification fails
|
|
127
|
+
*/
|
|
128
|
+
function createUncertainVerificationResult(checklistContext, reason) {
|
|
129
|
+
// Get all checklist items
|
|
130
|
+
const allItems = checklistContext.checklists.flatMap((checklist) => checklist.items.map((item) => ({
|
|
131
|
+
checklist_item_id: item.id,
|
|
132
|
+
})));
|
|
133
|
+
return {
|
|
134
|
+
all_verified: false,
|
|
135
|
+
total_items: allItems.length,
|
|
136
|
+
confirmed_count: 0,
|
|
137
|
+
rejected_count: 0,
|
|
138
|
+
uncertain_count: allItems.length,
|
|
139
|
+
item_verifications: allItems.map((item) => ({
|
|
140
|
+
checklist_item_id: item.checklist_item_id,
|
|
141
|
+
is_satisfied: false,
|
|
142
|
+
verification_status: 'uncertain',
|
|
143
|
+
verification_reason: reason,
|
|
144
|
+
})),
|
|
145
|
+
summary: `Verification could not be completed: ${reason}`,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { FeatureInfo } from '../../types/features.js';
|
|
2
|
+
import { type PullRequest } from '../../services/pull-requests.js';
|
|
3
|
+
import { type RepoForkInfo } from '../../utils/github-repo-info.js';
|
|
4
|
+
export interface GitHubConfigInfo {
|
|
5
|
+
configured: boolean;
|
|
6
|
+
token?: string;
|
|
7
|
+
owner?: string;
|
|
8
|
+
repo?: string;
|
|
9
|
+
message?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface PRExecutionContext {
|
|
12
|
+
feature: FeatureInfo;
|
|
13
|
+
pullRequests: PullRequest[];
|
|
14
|
+
devBranchName: string;
|
|
15
|
+
devBranchHeadSha: string;
|
|
16
|
+
githubConfig: GitHubConfigInfo;
|
|
17
|
+
forkInfo: RepoForkInfo;
|
|
18
|
+
isIncrementalSync: boolean;
|
|
19
|
+
lastSyncedCommit: string | null;
|
|
20
|
+
diffStat: string;
|
|
21
|
+
changedFiles: string[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Fetch context for PR execution phase
|
|
25
|
+
*/
|
|
26
|
+
export declare function fetchPRExecutionContext(featureId: string, verbose?: boolean): Promise<PRExecutionContext>;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { logInfo, logError } from '../../utils/logger.js';
|
|
3
|
+
import { getFeature } from '../../api/features/index.js';
|
|
4
|
+
import { getPullRequests, } from '../../services/pull-requests.js';
|
|
5
|
+
import { getGitHubConfig } from '../../api/github.js';
|
|
6
|
+
import { getRepoForkInfo, } from '../../utils/github-repo-info.js';
|
|
7
|
+
import { branchExists, remoteBranchExists, } from '../../utils/git-branch-manager.js';
|
|
8
|
+
/**
|
|
9
|
+
* Get the dev branch name for a feature
|
|
10
|
+
*/
|
|
11
|
+
function getDevBranchName(featureId) {
|
|
12
|
+
return `dev/${featureId}`;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get the HEAD SHA of a branch
|
|
16
|
+
*/
|
|
17
|
+
function getBranchHeadSha(branchName) {
|
|
18
|
+
return execSync(`git rev-parse ${branchName}`, { encoding: 'utf-8' }).trim();
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get diff stat between two refs
|
|
22
|
+
*/
|
|
23
|
+
function getDiffStat(baseRef, headRef) {
|
|
24
|
+
try {
|
|
25
|
+
return execSync(`git diff --stat ${baseRef}...${headRef}`, {
|
|
26
|
+
encoding: 'utf-8',
|
|
27
|
+
}).trim();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get list of changed files between two refs
|
|
35
|
+
*/
|
|
36
|
+
function getChangedFiles(baseRef, headRef) {
|
|
37
|
+
try {
|
|
38
|
+
const output = execSync(`git diff --name-only ${baseRef}...${headRef}`, { encoding: 'utf-8' }).trim();
|
|
39
|
+
if (!output)
|
|
40
|
+
return [];
|
|
41
|
+
return output.split('\n').filter((f) => f.length > 0);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Determine the common last_synced_commit from existing PRs
|
|
49
|
+
* Returns null if any PR hasn't been synced yet (first run)
|
|
50
|
+
*/
|
|
51
|
+
function getLastSyncedCommit(pullRequests) {
|
|
52
|
+
if (pullRequests.length === 0)
|
|
53
|
+
return null;
|
|
54
|
+
const syncedCommits = pullRequests
|
|
55
|
+
.map((pr) => pr.last_synced_commit)
|
|
56
|
+
.filter((c) => c !== null);
|
|
57
|
+
// If not all PRs have been synced, this is a first-time execution
|
|
58
|
+
if (syncedCommits.length !== pullRequests.length)
|
|
59
|
+
return null;
|
|
60
|
+
// All should be the same after a successful sync, use the first
|
|
61
|
+
return syncedCommits[0];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Fetch context for PR execution phase
|
|
65
|
+
*/
|
|
66
|
+
export async function fetchPRExecutionContext(featureId, verbose) {
|
|
67
|
+
try {
|
|
68
|
+
if (verbose) {
|
|
69
|
+
logInfo(`Fetching PR execution context for feature: ${featureId}`);
|
|
70
|
+
}
|
|
71
|
+
const devBranchName = getDevBranchName(featureId);
|
|
72
|
+
// Verify dev branch exists
|
|
73
|
+
const localExists = branchExists(devBranchName);
|
|
74
|
+
const remoteExists = !localExists && remoteBranchExists(devBranchName);
|
|
75
|
+
if (!localExists && !remoteExists) {
|
|
76
|
+
throw new Error(`Development branch '${devBranchName}' does not exist. ` +
|
|
77
|
+
`The feature must have code on the dev branch before PR execution.`);
|
|
78
|
+
}
|
|
79
|
+
// If branch only exists on remote, fetch it
|
|
80
|
+
if (!localExists && remoteExists) {
|
|
81
|
+
if (verbose) {
|
|
82
|
+
logInfo(`Fetching remote branch ${devBranchName}...`);
|
|
83
|
+
}
|
|
84
|
+
execSync(`git fetch origin ${devBranchName}`, {
|
|
85
|
+
encoding: 'utf-8',
|
|
86
|
+
stdio: 'pipe',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// Fetch data in parallel
|
|
90
|
+
const [feature, pullRequests] = await Promise.all([
|
|
91
|
+
getFeature(featureId, verbose),
|
|
92
|
+
getPullRequests({ featureId, verbose }),
|
|
93
|
+
]);
|
|
94
|
+
if (pullRequests.length === 0) {
|
|
95
|
+
throw new Error('No PR plan found. Run the pr-splitting phase first to create a PR plan.');
|
|
96
|
+
}
|
|
97
|
+
// Fetch GitHub config
|
|
98
|
+
const githubConfig = await getGitHubConfig(featureId, verbose);
|
|
99
|
+
if (!githubConfig.configured) {
|
|
100
|
+
throw new Error(`GitHub is not configured. ${githubConfig.message || 'Please configure GitHub integration.'}`);
|
|
101
|
+
}
|
|
102
|
+
// Detect fork status
|
|
103
|
+
let forkInfo = { isFork: false };
|
|
104
|
+
if (githubConfig.token && githubConfig.owner && githubConfig.repo) {
|
|
105
|
+
try {
|
|
106
|
+
forkInfo = await getRepoForkInfo(githubConfig.token, githubConfig.owner, githubConfig.repo);
|
|
107
|
+
if (verbose) {
|
|
108
|
+
logInfo(forkInfo.isFork
|
|
109
|
+
? `š Repository is a fork of ${forkInfo.upstream?.owner}/${forkInfo.upstream?.repo}`
|
|
110
|
+
: `š Repository is not a fork`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (verbose) {
|
|
115
|
+
logError(`Failed to detect fork status: ${error instanceof Error ? error.message : String(error)}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Determine sync mode
|
|
120
|
+
const devRef = localExists ? devBranchName : `origin/${devBranchName}`;
|
|
121
|
+
const devBranchHeadSha = getBranchHeadSha(devRef);
|
|
122
|
+
const lastSyncedCommit = getLastSyncedCommit(pullRequests);
|
|
123
|
+
const isIncrementalSync = lastSyncedCommit !== null;
|
|
124
|
+
// Get diff info: for incremental, diff from last sync; for first run, diff from main
|
|
125
|
+
const diffBase = isIncrementalSync ? lastSyncedCommit : 'main';
|
|
126
|
+
const diffStat = getDiffStat(diffBase, devRef);
|
|
127
|
+
const changedFiles = getChangedFiles(diffBase, devRef);
|
|
128
|
+
if (verbose) {
|
|
129
|
+
logInfo(`ā
PR execution context fetched:`);
|
|
130
|
+
logInfo(` Feature: ${feature.name}`);
|
|
131
|
+
logInfo(` Dev Branch: ${devBranchName} (HEAD: ${devBranchHeadSha})`);
|
|
132
|
+
logInfo(` PR Records: ${pullRequests.length}`);
|
|
133
|
+
logInfo(` Mode: ${isIncrementalSync ? 'š Incremental sync' : '⨠First execution'}`);
|
|
134
|
+
logInfo(` Diff Base: ${diffBase}`);
|
|
135
|
+
logInfo(` Changed Files: ${changedFiles.length}`);
|
|
136
|
+
logInfo(` GitHub: ${githubConfig.configured ? 'Configured' : 'Not configured'}`);
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
feature,
|
|
140
|
+
pullRequests,
|
|
141
|
+
devBranchName,
|
|
142
|
+
devBranchHeadSha,
|
|
143
|
+
githubConfig,
|
|
144
|
+
forkInfo,
|
|
145
|
+
isIncrementalSync,
|
|
146
|
+
lastSyncedCommit,
|
|
147
|
+
diffStat,
|
|
148
|
+
changedFiles,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
153
|
+
logError(`Failed to fetch PR execution context: ${errorMessage}`);
|
|
154
|
+
throw new Error(`Context fetch failed: ${errorMessage}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { EdsgerConfig } from '../../types/index.js';
|
|
2
|
+
export interface PRExecutionOptions {
|
|
3
|
+
featureId: string;
|
|
4
|
+
verbose?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* PR Execution Phase: Create git branches, push code, and create GitHub PRs
|
|
8
|
+
*
|
|
9
|
+
* Prerequisites: pr-splitting phase must have been run and human-approved.
|
|
10
|
+
* PR plan records must exist in the database.
|
|
11
|
+
*
|
|
12
|
+
* Supports incremental re-sync: if PR branches already exist and dev branch
|
|
13
|
+
* has new changes, only the new changes are synced to existing branches.
|
|
14
|
+
*/
|
|
15
|
+
export declare const executeFeaturePRs: (options: PRExecutionOptions, config: EdsgerConfig) => Promise<{
|
|
16
|
+
status: "success" | "error";
|
|
17
|
+
message?: string;
|
|
18
|
+
summary?: string;
|
|
19
|
+
[key: string]: any;
|
|
20
|
+
}>;
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { DEFAULT_MODEL } from '../../constants.js';
|
|
4
|
+
import { logInfo, logError } from '../../utils/logger.js';
|
|
5
|
+
import { fetchPRExecutionContext } from './context.js';
|
|
6
|
+
import { createPRExecutionSystemPrompt, createPRExecutionPrompt, createIncrementalSyncSystemPrompt, createIncrementalSyncPrompt, } from './prompts.js';
|
|
7
|
+
import { buildExecutionSuccessResult, buildExecutionErrorResult, buildNoChangeResult, } from './outcome.js';
|
|
8
|
+
import { logFeaturePhaseEvent } from '../../services/audit-logs.js';
|
|
9
|
+
import { pushAndCreateGitHubPR, updatePRDatabaseRecord, } from './pr-executor.js';
|
|
10
|
+
import { getCurrentBranch, returnToMainBranch, } from '../../utils/git-branch-manager.js';
|
|
11
|
+
function userMessage(content) {
|
|
12
|
+
return {
|
|
13
|
+
type: 'user',
|
|
14
|
+
message: { role: 'user', content: content },
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
async function* prompt(executionPrompt) {
|
|
18
|
+
yield userMessage(executionPrompt);
|
|
19
|
+
await new Promise((res) => setTimeout(res, 10000));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* PR Execution Phase: Create git branches, push code, and create GitHub PRs
|
|
23
|
+
*
|
|
24
|
+
* Prerequisites: pr-splitting phase must have been run and human-approved.
|
|
25
|
+
* PR plan records must exist in the database.
|
|
26
|
+
*
|
|
27
|
+
* Supports incremental re-sync: if PR branches already exist and dev branch
|
|
28
|
+
* has new changes, only the new changes are synced to existing branches.
|
|
29
|
+
*/
|
|
30
|
+
export const executeFeaturePRs = async (options, config) => {
|
|
31
|
+
const { featureId, verbose } = options;
|
|
32
|
+
if (verbose) {
|
|
33
|
+
logInfo(`Starting PR execution for feature ID: ${featureId}`);
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
// Log phase start
|
|
37
|
+
await logFeaturePhaseEvent({
|
|
38
|
+
featureId,
|
|
39
|
+
eventType: 'phase_started',
|
|
40
|
+
phase: 'pr_execution',
|
|
41
|
+
result: 'info',
|
|
42
|
+
metadata: {
|
|
43
|
+
timestamp: new Date().toISOString(),
|
|
44
|
+
},
|
|
45
|
+
}, verbose);
|
|
46
|
+
// Fetch context
|
|
47
|
+
const context = await fetchPRExecutionContext(featureId, verbose);
|
|
48
|
+
// Check if already fully synced
|
|
49
|
+
if (context.isIncrementalSync &&
|
|
50
|
+
context.lastSyncedCommit === context.devBranchHeadSha) {
|
|
51
|
+
if (verbose) {
|
|
52
|
+
logInfo('All PR branches are already synced to latest commit.');
|
|
53
|
+
}
|
|
54
|
+
return buildNoChangeResult(featureId, context.pullRequests.length);
|
|
55
|
+
}
|
|
56
|
+
// Check if there are any changed files (for incremental sync)
|
|
57
|
+
if (context.isIncrementalSync && context.changedFiles.length === 0) {
|
|
58
|
+
if (verbose) {
|
|
59
|
+
logInfo('No new changes since last sync.');
|
|
60
|
+
}
|
|
61
|
+
return buildNoChangeResult(featureId, context.pullRequests.length);
|
|
62
|
+
}
|
|
63
|
+
// ======================================
|
|
64
|
+
// Agent: Create/Update branches
|
|
65
|
+
// ======================================
|
|
66
|
+
if (verbose) {
|
|
67
|
+
logInfo(context.isIncrementalSync
|
|
68
|
+
? '\nš Syncing PR branches with latest changes...'
|
|
69
|
+
: '\nš§ Creating PR branches and moving code...');
|
|
70
|
+
}
|
|
71
|
+
// Ensure we're on main before the agent starts
|
|
72
|
+
const currentBranch = getCurrentBranch();
|
|
73
|
+
if (currentBranch !== 'main') {
|
|
74
|
+
if (verbose) {
|
|
75
|
+
logInfo(`Switching from ${currentBranch} to main...`);
|
|
76
|
+
}
|
|
77
|
+
execSync('git checkout main', { encoding: 'utf-8', stdio: 'pipe' });
|
|
78
|
+
}
|
|
79
|
+
let systemPrompt;
|
|
80
|
+
let userPrompt;
|
|
81
|
+
if (context.isIncrementalSync && context.lastSyncedCommit) {
|
|
82
|
+
systemPrompt = createIncrementalSyncSystemPrompt(featureId, context.devBranchName);
|
|
83
|
+
userPrompt = createIncrementalSyncPrompt(featureId, context.devBranchName, context.pullRequests, context.lastSyncedCommit, context.diffStat, context.changedFiles);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
systemPrompt = createPRExecutionSystemPrompt(featureId, context.devBranchName);
|
|
87
|
+
userPrompt = createPRExecutionPrompt(featureId, context.devBranchName, context.pullRequests);
|
|
88
|
+
}
|
|
89
|
+
// Execute agent query
|
|
90
|
+
await executeAgentQuery(userPrompt, systemPrompt, config, verbose);
|
|
91
|
+
// ================================================
|
|
92
|
+
// Push & Create GitHub PRs (Programmatic)
|
|
93
|
+
// ================================================
|
|
94
|
+
if (verbose) {
|
|
95
|
+
logInfo('\nš Pushing branches and creating GitHub PRs...');
|
|
96
|
+
}
|
|
97
|
+
// Ensure we're on main before pushing
|
|
98
|
+
try {
|
|
99
|
+
execSync('git checkout main', { encoding: 'utf-8', stdio: 'pipe' });
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Ignore if already on main
|
|
103
|
+
}
|
|
104
|
+
const executionConfig = {
|
|
105
|
+
githubToken: context.githubConfig.token,
|
|
106
|
+
owner: context.githubConfig.owner,
|
|
107
|
+
repo: context.githubConfig.repo,
|
|
108
|
+
forkInfo: context.forkInfo,
|
|
109
|
+
verbose,
|
|
110
|
+
};
|
|
111
|
+
const executionSummary = {
|
|
112
|
+
branchesCreated: 0,
|
|
113
|
+
branchesUpdated: 0,
|
|
114
|
+
prsCreated: 0,
|
|
115
|
+
prsUpdated: 0,
|
|
116
|
+
failedBranches: [],
|
|
117
|
+
prUrls: [],
|
|
118
|
+
};
|
|
119
|
+
for (const pr of context.pullRequests) {
|
|
120
|
+
if (!pr.branch_name) {
|
|
121
|
+
if (verbose) {
|
|
122
|
+
logError(`PR "${pr.name}" has no branch name, skipping`);
|
|
123
|
+
}
|
|
124
|
+
executionSummary.failedBranches.push(pr.name);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
// Verify the branch exists locally
|
|
128
|
+
try {
|
|
129
|
+
execSync(`git rev-parse --verify ${pr.branch_name}`, {
|
|
130
|
+
encoding: 'utf-8',
|
|
131
|
+
stdio: 'pipe',
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
if (verbose) {
|
|
136
|
+
logError(`Branch ${pr.branch_name} does not exist locally, skipping`);
|
|
137
|
+
}
|
|
138
|
+
executionSummary.failedBranches.push(pr.branch_name);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (context.isIncrementalSync) {
|
|
142
|
+
executionSummary.branchesUpdated++;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
executionSummary.branchesCreated++;
|
|
146
|
+
}
|
|
147
|
+
// Push and create/update GitHub PR
|
|
148
|
+
const prTitle = `feat: ${pr.name.toLowerCase()}`;
|
|
149
|
+
const prDescription = `## ${pr.name}\n\n${pr.description || ''}\n\n---\n**Branch**: \`${pr.branch_name}\`\n**Created by**: Automated PR Splitting`;
|
|
150
|
+
const result = await pushAndCreateGitHubPR(executionConfig, pr.branch_name, prTitle, prDescription);
|
|
151
|
+
if (result.success && result.prUrl && result.prNumber) {
|
|
152
|
+
if (pr.pull_request_url) {
|
|
153
|
+
executionSummary.prsUpdated++;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
executionSummary.prsCreated++;
|
|
157
|
+
}
|
|
158
|
+
executionSummary.prUrls.push(result.prUrl);
|
|
159
|
+
// Update database record with PR info and sync commit
|
|
160
|
+
try {
|
|
161
|
+
await updatePRDatabaseRecord(pr.id, result.prUrl, result.prNumber, context.devBranchHeadSha, verbose);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (verbose) {
|
|
165
|
+
logError(`Failed to update PR record: ${error instanceof Error ? error.message : String(error)}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
executionSummary.failedBranches.push(pr.branch_name);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Return to main branch
|
|
174
|
+
try {
|
|
175
|
+
returnToMainBranch('main', verbose);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
// Best effort
|
|
179
|
+
}
|
|
180
|
+
if (verbose) {
|
|
181
|
+
logInfo(`\nš PR execution complete:`);
|
|
182
|
+
if (context.isIncrementalSync) {
|
|
183
|
+
logInfo(` Branches updated: ${executionSummary.branchesUpdated}`);
|
|
184
|
+
logInfo(` PRs updated: ${executionSummary.prsUpdated}`);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
logInfo(` Branches created: ${executionSummary.branchesCreated}`);
|
|
188
|
+
logInfo(` PRs created: ${executionSummary.prsCreated}`);
|
|
189
|
+
}
|
|
190
|
+
logInfo(` Failed: ${executionSummary.failedBranches.length}`);
|
|
191
|
+
executionSummary.prUrls.forEach((url) => {
|
|
192
|
+
logInfo(` š ${url}`);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
// Log phase completion
|
|
196
|
+
await logFeaturePhaseEvent({
|
|
197
|
+
featureId,
|
|
198
|
+
eventType: 'phase_completed',
|
|
199
|
+
phase: 'pr_execution',
|
|
200
|
+
result: 'success',
|
|
201
|
+
metadata: {
|
|
202
|
+
branches_created: executionSummary.branchesCreated,
|
|
203
|
+
branches_updated: executionSummary.branchesUpdated,
|
|
204
|
+
prs_created: executionSummary.prsCreated,
|
|
205
|
+
prs_updated: executionSummary.prsUpdated,
|
|
206
|
+
failed_branches: executionSummary.failedBranches,
|
|
207
|
+
pr_urls: executionSummary.prUrls,
|
|
208
|
+
last_synced_commit: context.devBranchHeadSha,
|
|
209
|
+
incremental_sync: context.isIncrementalSync,
|
|
210
|
+
timestamp: new Date().toISOString(),
|
|
211
|
+
},
|
|
212
|
+
}, verbose);
|
|
213
|
+
const summary = context.isIncrementalSync
|
|
214
|
+
? `Synced PR branches with latest changes from ${context.devBranchName}`
|
|
215
|
+
: `Created ${executionSummary.branchesCreated} PR branches and ${executionSummary.prsCreated} GitHub PRs`;
|
|
216
|
+
return buildExecutionSuccessResult(featureId, executionSummary, summary);
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
220
|
+
logError(`PR execution failed: ${errorMessage}`);
|
|
221
|
+
// Try to return to main branch
|
|
222
|
+
try {
|
|
223
|
+
returnToMainBranch('main', false);
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
// Best effort
|
|
227
|
+
}
|
|
228
|
+
await logFeaturePhaseEvent({
|
|
229
|
+
featureId,
|
|
230
|
+
eventType: 'phase_failed',
|
|
231
|
+
phase: 'pr_execution',
|
|
232
|
+
result: 'error',
|
|
233
|
+
metadata: {
|
|
234
|
+
error: errorMessage,
|
|
235
|
+
timestamp: new Date().toISOString(),
|
|
236
|
+
},
|
|
237
|
+
}, verbose);
|
|
238
|
+
return buildExecutionErrorResult(featureId, errorMessage);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* Execute an agent query for branch creation/update
|
|
243
|
+
*/
|
|
244
|
+
async function executeAgentQuery(userPrompt, systemPrompt, config, verbose) {
|
|
245
|
+
if (verbose) {
|
|
246
|
+
logInfo('š¤ Starting PR execution agent query...');
|
|
247
|
+
}
|
|
248
|
+
for await (const message of query({
|
|
249
|
+
prompt: prompt(userPrompt),
|
|
250
|
+
options: {
|
|
251
|
+
systemPrompt: {
|
|
252
|
+
type: 'preset',
|
|
253
|
+
preset: 'claude_code',
|
|
254
|
+
append: systemPrompt,
|
|
255
|
+
},
|
|
256
|
+
model: DEFAULT_MODEL,
|
|
257
|
+
maxTurns: 500,
|
|
258
|
+
permissionMode: 'bypassPermissions',
|
|
259
|
+
},
|
|
260
|
+
})) {
|
|
261
|
+
if (verbose) {
|
|
262
|
+
logInfo(` Received message type: ${message.type}`);
|
|
263
|
+
}
|
|
264
|
+
if (message.type === 'assistant' && message.message?.content) {
|
|
265
|
+
for (const content of message.message.content) {
|
|
266
|
+
if (content.type === 'text') {
|
|
267
|
+
if (verbose) {
|
|
268
|
+
console.log(`\nš¤ ${content.text}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
else if (content.type === 'tool_use') {
|
|
272
|
+
if (verbose) {
|
|
273
|
+
console.log(`\nš§ ${content.name}: ${content.input.description || 'Running...'}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (message.type === 'result') {
|
|
279
|
+
if (message.subtype === 'success') {
|
|
280
|
+
logInfo('\nā
Branch operations completed.');
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
logError(`\nā ļø Branch operations incomplete: ${message.subtype}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface PRExecutionSummary {
|
|
2
|
+
branchesCreated: number;
|
|
3
|
+
branchesUpdated: number;
|
|
4
|
+
prsCreated: number;
|
|
5
|
+
prsUpdated: number;
|
|
6
|
+
failedBranches: string[];
|
|
7
|
+
prUrls: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface PRExecutionPhaseResult {
|
|
10
|
+
featureId: string;
|
|
11
|
+
status: 'success' | 'error';
|
|
12
|
+
summary: string;
|
|
13
|
+
executionSummary?: PRExecutionSummary;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build a successful execution result
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildExecutionSuccessResult(featureId: string, executionSummary: PRExecutionSummary, summary: string): PRExecutionPhaseResult;
|
|
19
|
+
/**
|
|
20
|
+
* Build an error result
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildExecutionErrorResult(featureId: string, errorMessage: string): PRExecutionPhaseResult;
|
|
23
|
+
/**
|
|
24
|
+
* Build a no-change result
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildNoChangeResult(featureId: string, prCount: number): PRExecutionPhaseResult;
|