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,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;