edsger 0.30.0 → 0.30.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/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/index.js +0 -0
- package/dist/phases/autonomous/index.js +9 -56
- 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/branch-pr-creator.js +2 -58
- 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/index.js +9 -67
- 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/index.js +6 -37
- 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/code-review/index.js +1 -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/index.js +1 -0
- package/dist/phases/pr-execution/pr-executor.d.ts +2 -1
- package/dist/phases/pr-execution/pr-executor.js +9 -38
- package/dist/phases/pull-request/creator.js +9 -35
- 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/feature-branches.d.ts +77 -0
- package/dist/services/feature-branches.js +205 -0
- package/dist/utils/git-branch-manager.d.ts +2 -0
- package/dist/utils/git-branch-manager.js +8 -35
- package/dist/utils/git-push.d.ts +43 -0
- package/dist/utils/git-push.js +127 -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/dist/workspace/workspace-manager.js +5 -3
- 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,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature Branches service for pipeline integration
|
|
3
|
+
* Allows phases to manage feature branches via MCP
|
|
4
|
+
*/
|
|
5
|
+
import { callMcpEndpoint } from '../api/mcp-client.js';
|
|
6
|
+
/**
|
|
7
|
+
* List all branches for a feature
|
|
8
|
+
*/
|
|
9
|
+
export async function getFeatureBranches(options) {
|
|
10
|
+
const { featureId, verbose } = options;
|
|
11
|
+
if (verbose) {
|
|
12
|
+
console.log(`📋 Fetching feature branches for feature: ${featureId}`);
|
|
13
|
+
}
|
|
14
|
+
const result = (await callMcpEndpoint('feature_branches/list', {
|
|
15
|
+
feature_id: featureId,
|
|
16
|
+
}));
|
|
17
|
+
const branches = result?.feature_branches || [];
|
|
18
|
+
if (verbose) {
|
|
19
|
+
console.log(`✅ Found ${branches.length} feature branches`);
|
|
20
|
+
}
|
|
21
|
+
return branches;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get the current active branch for a feature
|
|
25
|
+
*/
|
|
26
|
+
export async function getCurrentBranch(options) {
|
|
27
|
+
const { featureId, verbose } = options;
|
|
28
|
+
if (verbose) {
|
|
29
|
+
console.log(`📋 Getting current branch for feature: ${featureId}`);
|
|
30
|
+
}
|
|
31
|
+
const result = (await callMcpEndpoint('feature_branches/current', {
|
|
32
|
+
feature_id: featureId,
|
|
33
|
+
}));
|
|
34
|
+
return result?.current_branch || null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Create feature branches
|
|
38
|
+
*/
|
|
39
|
+
export async function createFeatureBranches(options, branches) {
|
|
40
|
+
const { featureId, verbose } = options;
|
|
41
|
+
if (verbose) {
|
|
42
|
+
console.log(`📋 Creating ${branches.length} feature branches for feature: ${featureId}`);
|
|
43
|
+
}
|
|
44
|
+
const result = (await callMcpEndpoint('feature_branches/create', {
|
|
45
|
+
feature_id: featureId,
|
|
46
|
+
branches: branches,
|
|
47
|
+
}));
|
|
48
|
+
const createdBranches = result?.created_branches || [];
|
|
49
|
+
if (verbose) {
|
|
50
|
+
console.log(`✅ Created ${createdBranches.length} feature branches`);
|
|
51
|
+
createdBranches.forEach((b, idx) => {
|
|
52
|
+
console.log(` ${idx + 1}. ${b.name} (status: ${b.status})`);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return createdBranches;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Update a feature branch
|
|
59
|
+
*/
|
|
60
|
+
export async function updateFeatureBranch(branchId, updates, verbose) {
|
|
61
|
+
if (verbose) {
|
|
62
|
+
console.log(`📋 Updating feature branch: ${branchId}`);
|
|
63
|
+
}
|
|
64
|
+
const result = (await callMcpEndpoint('feature_branches/update', {
|
|
65
|
+
branch_id: branchId,
|
|
66
|
+
...updates,
|
|
67
|
+
}));
|
|
68
|
+
if (verbose) {
|
|
69
|
+
console.log(`✅ Feature branch updated successfully`);
|
|
70
|
+
}
|
|
71
|
+
return result?.feature_branch;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Clear all branches for a feature (used before re-planning)
|
|
75
|
+
*/
|
|
76
|
+
export async function clearFeatureBranches(options, force = false) {
|
|
77
|
+
const { featureId, verbose } = options;
|
|
78
|
+
if (verbose) {
|
|
79
|
+
console.log(`📋 Clearing feature branches for feature: ${featureId}`);
|
|
80
|
+
}
|
|
81
|
+
const result = (await callMcpEndpoint('feature_branches/clear', {
|
|
82
|
+
feature_id: featureId,
|
|
83
|
+
force: force,
|
|
84
|
+
}));
|
|
85
|
+
const deletedCount = result?.deleted_count || 0;
|
|
86
|
+
if (verbose) {
|
|
87
|
+
console.log(`✅ Cleared ${deletedCount} feature branches`);
|
|
88
|
+
}
|
|
89
|
+
return deletedCount;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Format feature branches for context (to include in prompts)
|
|
93
|
+
*/
|
|
94
|
+
export function formatBranchesForContext(branches) {
|
|
95
|
+
if (!branches || branches.length === 0) {
|
|
96
|
+
return 'No feature branches defined yet.';
|
|
97
|
+
}
|
|
98
|
+
const branchList = branches
|
|
99
|
+
.map((b, idx) => {
|
|
100
|
+
const statusEmoji = b.status === 'merged'
|
|
101
|
+
? '✅'
|
|
102
|
+
: b.status === 'in_progress'
|
|
103
|
+
? '🔄'
|
|
104
|
+
: b.status === 'ready_for_review'
|
|
105
|
+
? '👀'
|
|
106
|
+
: b.status === 'closed'
|
|
107
|
+
? '❌'
|
|
108
|
+
: '⏳';
|
|
109
|
+
return `${idx + 1}. **${b.name}** ${statusEmoji}
|
|
110
|
+
- Status: ${b.status}
|
|
111
|
+
- Branch: ${b.branch_name || 'Not created'}
|
|
112
|
+
- PR: ${b.pull_request_url || 'Not created'}
|
|
113
|
+
- Description: ${b.description || 'No description'}`;
|
|
114
|
+
})
|
|
115
|
+
.join('\n\n');
|
|
116
|
+
return `# Feature Branches
|
|
117
|
+
|
|
118
|
+
${branchList}
|
|
119
|
+
|
|
120
|
+
Total: ${branches.length} branches
|
|
121
|
+
Merged: ${branches.filter((b) => b.status === 'merged').length}
|
|
122
|
+
In Progress: ${branches.filter((b) => b.status === 'in_progress').length}
|
|
123
|
+
Pending: ${branches.filter((b) => b.status === 'pending').length}`;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Check if feature has multiple branches planned
|
|
127
|
+
*/
|
|
128
|
+
export async function hasMultipleBranches(options) {
|
|
129
|
+
const branches = await getFeatureBranches(options);
|
|
130
|
+
return branches.length > 1;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get next pending branch to work on
|
|
134
|
+
*/
|
|
135
|
+
export async function getNextPendingBranch(options) {
|
|
136
|
+
const branches = await getFeatureBranches(options);
|
|
137
|
+
return branches.find((b) => b.status === 'pending') || null;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Check if all branches are completed
|
|
141
|
+
*/
|
|
142
|
+
export async function allBranchesCompleted(options) {
|
|
143
|
+
const branches = await getFeatureBranches(options);
|
|
144
|
+
if (branches.length === 0)
|
|
145
|
+
return true;
|
|
146
|
+
return branches.every((b) => b.status === 'merged' || b.status === 'closed');
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get the base branch information for a feature branch.
|
|
150
|
+
* Returns:
|
|
151
|
+
* - If base_branch_id is set and that branch is merged: use main
|
|
152
|
+
* - If base_branch_id is set and that branch is not merged: use that branch's branch_name
|
|
153
|
+
* - If base_branch_id is null: use main
|
|
154
|
+
*/
|
|
155
|
+
export async function getBaseBranchInfo(branch, allBranches, mainBranch = 'main') {
|
|
156
|
+
// No base branch - start from main
|
|
157
|
+
if (!branch.base_branch_id) {
|
|
158
|
+
return {
|
|
159
|
+
baseBranch: mainBranch,
|
|
160
|
+
needsRebase: false,
|
|
161
|
+
baseBranchMerged: true,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// Find the base branch
|
|
165
|
+
const baseBranch = allBranches.find((b) => b.id === branch.base_branch_id);
|
|
166
|
+
if (!baseBranch) {
|
|
167
|
+
// Base branch not found - fall back to main
|
|
168
|
+
return {
|
|
169
|
+
baseBranch: mainBranch,
|
|
170
|
+
needsRebase: false,
|
|
171
|
+
baseBranchMerged: true,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
// Check if base branch is merged
|
|
175
|
+
if (baseBranch.status === 'merged') {
|
|
176
|
+
// Base branch is merged to main - we should base on main and rebase if needed
|
|
177
|
+
return { baseBranch: mainBranch, needsRebase: true, baseBranchMerged: true };
|
|
178
|
+
}
|
|
179
|
+
// Base branch is not merged - we should base on that branch
|
|
180
|
+
if (!baseBranch.branch_name) {
|
|
181
|
+
// Base branch doesn't have a git branch yet - fall back to main
|
|
182
|
+
return {
|
|
183
|
+
baseBranch: mainBranch,
|
|
184
|
+
needsRebase: false,
|
|
185
|
+
baseBranchMerged: false,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
baseBranch: baseBranch.branch_name,
|
|
190
|
+
needsRebase: false,
|
|
191
|
+
baseBranchMerged: false,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Get branch by ID
|
|
196
|
+
*/
|
|
197
|
+
export async function getBranchById(branchId, verbose) {
|
|
198
|
+
if (verbose) {
|
|
199
|
+
console.log(`📋 Fetching feature branch: ${branchId}`);
|
|
200
|
+
}
|
|
201
|
+
const result = (await callMcpEndpoint('feature_branches/get', {
|
|
202
|
+
branch_id: branchId,
|
|
203
|
+
}));
|
|
204
|
+
return result?.feature_branch || null;
|
|
205
|
+
}
|
|
@@ -212,6 +212,8 @@ export interface RebaseWithConflictResolutionOptions {
|
|
|
212
212
|
* GitHub to recalculate the PR diff.
|
|
213
213
|
*/
|
|
214
214
|
forcePushAfterRebase?: boolean;
|
|
215
|
+
/** GitHub App installation token for authenticated push */
|
|
216
|
+
githubToken?: string;
|
|
215
217
|
}
|
|
216
218
|
/**
|
|
217
219
|
* Switch to feature branch and rebase with base branch, with optional automatic conflict resolution
|
|
@@ -7,6 +7,7 @@ import * as fs from 'fs';
|
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import { Octokit } from '@octokit/rest';
|
|
9
9
|
import { logInfo, logError } from './logger.js';
|
|
10
|
+
import { gitForcePush } from './git-push.js';
|
|
10
11
|
/**
|
|
11
12
|
* Get current Git branch name
|
|
12
13
|
*/
|
|
@@ -689,7 +690,7 @@ export async function syncFeatBranchWithMain(featBranch, githubToken, owner, rep
|
|
|
689
690
|
* @returns Object containing the previous branch name and conflict resolution result
|
|
690
691
|
*/
|
|
691
692
|
export async function switchToFeatureBranchAndRebaseAsync(options) {
|
|
692
|
-
const { featureBranch, baseBranch = 'main', rebaseTargetBranch, originalBaseBranch, verbose, resolveConflicts = false, conflictResolverConfig, forcePushAfterRebase = false, baseBranchCompleted = false, } = options;
|
|
693
|
+
const { featureBranch, baseBranch = 'main', rebaseTargetBranch, originalBaseBranch, verbose, resolveConflicts = false, conflictResolverConfig, forcePushAfterRebase = false, baseBranchCompleted = false, githubToken, } = options;
|
|
693
694
|
// Determine the actual target branch for rebase
|
|
694
695
|
// If rebaseTargetBranch is set (e.g., main), use it; otherwise use baseBranch
|
|
695
696
|
const actualRebaseTarget = rebaseTargetBranch || baseBranch;
|
|
@@ -864,23 +865,9 @@ export async function switchToFeatureBranchAndRebaseAsync(options) {
|
|
|
864
865
|
}
|
|
865
866
|
// Force push after rebase to trigger GitHub to recalculate PR diff
|
|
866
867
|
if (forcePushAfterRebase) {
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
}
|
|
871
|
-
execSync(`git push --force-with-lease origin ${featureBranch}`, {
|
|
872
|
-
encoding: 'utf-8',
|
|
873
|
-
stdio: verbose ? 'inherit' : 'pipe',
|
|
874
|
-
});
|
|
875
|
-
if (verbose) {
|
|
876
|
-
logInfo(`✅ Successfully force pushed ${featureBranch}`);
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
catch (pushError) {
|
|
880
|
-
if (verbose) {
|
|
881
|
-
logInfo(`⚠️ Could not force push ${featureBranch}: ${pushError instanceof Error ? pushError.message : String(pushError)}`);
|
|
882
|
-
}
|
|
883
|
-
// Don't fail the whole process if push fails
|
|
868
|
+
const pushResult = gitForcePush({ branchName: featureBranch, token: githubToken, verbose });
|
|
869
|
+
if (!pushResult.success && verbose) {
|
|
870
|
+
logInfo(`⚠️ Could not force push ${featureBranch}: ${pushResult.error}`);
|
|
884
871
|
}
|
|
885
872
|
}
|
|
886
873
|
return { previousBranch };
|
|
@@ -908,23 +895,9 @@ export async function switchToFeatureBranchAndRebaseAsync(options) {
|
|
|
908
895
|
}
|
|
909
896
|
// Force push after rebase to trigger GitHub to recalculate PR diff
|
|
910
897
|
if (forcePushAfterRebase) {
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
}
|
|
915
|
-
execSync(`git push --force-with-lease origin ${featureBranch}`, {
|
|
916
|
-
encoding: 'utf-8',
|
|
917
|
-
stdio: verbose ? 'inherit' : 'pipe',
|
|
918
|
-
});
|
|
919
|
-
if (verbose) {
|
|
920
|
-
logInfo(`✅ Successfully force pushed ${featureBranch}`);
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
catch (pushError) {
|
|
924
|
-
if (verbose) {
|
|
925
|
-
logInfo(`⚠️ Could not force push ${featureBranch}: ${pushError instanceof Error ? pushError.message : String(pushError)}`);
|
|
926
|
-
}
|
|
927
|
-
// Don't fail the whole process if push fails
|
|
898
|
+
const pushResult = gitForcePush({ branchName: featureBranch, token: githubToken, verbose });
|
|
899
|
+
if (!pushResult.success && verbose) {
|
|
900
|
+
logInfo(`⚠️ Could not force push ${featureBranch}: ${pushResult.error}`);
|
|
928
901
|
}
|
|
929
902
|
}
|
|
930
903
|
return {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared git push utilities with GitHub App credential helper support.
|
|
3
|
+
*
|
|
4
|
+
* Uses `git -c credential.helper=...` to pass the token securely per-command,
|
|
5
|
+
* matching the pattern established in workspace-manager.ts for clone/fetch.
|
|
6
|
+
* Uses `execFileSync` instead of `execSync` to avoid shell metacharacter injection.
|
|
7
|
+
*/
|
|
8
|
+
export interface GitPushOptions {
|
|
9
|
+
branchName: string;
|
|
10
|
+
token?: string;
|
|
11
|
+
verbose?: boolean;
|
|
12
|
+
cwd?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface GitPushResult {
|
|
15
|
+
success: boolean;
|
|
16
|
+
error?: string;
|
|
17
|
+
method?: 'push-u' | 'push' | 'force-with-lease';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Build git args with optional credential helper prefix.
|
|
21
|
+
* When a token is provided, prepends `-c credential.helper=...` so git
|
|
22
|
+
* authenticates with the GitHub App installation token.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildCredentialArgs(token?: string): string[];
|
|
25
|
+
/**
|
|
26
|
+
* Push a branch to remote with three-level fallback:
|
|
27
|
+
* 1. git push -u origin <branch>
|
|
28
|
+
* 2. git push origin <branch>
|
|
29
|
+
* 3. git push --force-with-lease origin <branch>
|
|
30
|
+
*
|
|
31
|
+
* When `token` is provided, each command uses a credential helper for authentication.
|
|
32
|
+
*/
|
|
33
|
+
export declare function gitPush(options: GitPushOptions): GitPushResult;
|
|
34
|
+
/**
|
|
35
|
+
* Force push a branch using --force-with-lease.
|
|
36
|
+
* Used after rebase operations to update the remote branch.
|
|
37
|
+
*/
|
|
38
|
+
export declare function gitForcePush(options: GitPushOptions): GitPushResult;
|
|
39
|
+
/**
|
|
40
|
+
* Push the current branch to remote.
|
|
41
|
+
* Resolves the current branch name first, then delegates to gitPush.
|
|
42
|
+
*/
|
|
43
|
+
export declare function gitPushCurrentBranch(options: Omit<GitPushOptions, 'branchName'>): GitPushResult;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared git push utilities with GitHub App credential helper support.
|
|
3
|
+
*
|
|
4
|
+
* Uses `git -c credential.helper=...` to pass the token securely per-command,
|
|
5
|
+
* matching the pattern established in workspace-manager.ts for clone/fetch.
|
|
6
|
+
* Uses `execFileSync` instead of `execSync` to avoid shell metacharacter injection.
|
|
7
|
+
*/
|
|
8
|
+
import { execFileSync } from 'child_process';
|
|
9
|
+
import { logInfo } from './logger.js';
|
|
10
|
+
/**
|
|
11
|
+
* Build git args with optional credential helper prefix.
|
|
12
|
+
* When a token is provided, prepends `-c credential.helper=...` so git
|
|
13
|
+
* authenticates with the GitHub App installation token.
|
|
14
|
+
*/
|
|
15
|
+
export function buildCredentialArgs(token) {
|
|
16
|
+
if (!token)
|
|
17
|
+
return [];
|
|
18
|
+
// First clear any existing credential helpers (e.g. osxkeychain) with an empty value,
|
|
19
|
+
// then set our custom helper. This ensures the token is used instead of stale keychain creds.
|
|
20
|
+
const credentialHelper = `!f() { echo "username=x-access-token"; echo "password=${token}"; }; f`;
|
|
21
|
+
return ['-c', 'credential.helper=', '-c', `credential.helper=${credentialHelper}`];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Push a branch to remote with three-level fallback:
|
|
25
|
+
* 1. git push -u origin <branch>
|
|
26
|
+
* 2. git push origin <branch>
|
|
27
|
+
* 3. git push --force-with-lease origin <branch>
|
|
28
|
+
*
|
|
29
|
+
* When `token` is provided, each command uses a credential helper for authentication.
|
|
30
|
+
*/
|
|
31
|
+
export function gitPush(options) {
|
|
32
|
+
const { branchName, token, verbose, cwd } = options;
|
|
33
|
+
const credArgs = buildCredentialArgs(token);
|
|
34
|
+
const execOpts = {
|
|
35
|
+
cwd,
|
|
36
|
+
stdio: (verbose ? 'inherit' : 'pipe'),
|
|
37
|
+
};
|
|
38
|
+
if (verbose) {
|
|
39
|
+
logInfo(`📤 Pushing branch ${branchName} to remote...`);
|
|
40
|
+
}
|
|
41
|
+
// Try 1: push -u (set upstream tracking)
|
|
42
|
+
try {
|
|
43
|
+
execFileSync('git', [...credArgs, 'push', '-u', 'origin', branchName], execOpts);
|
|
44
|
+
return { success: true, method: 'push-u' };
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// fall through
|
|
48
|
+
}
|
|
49
|
+
// Try 2: plain push
|
|
50
|
+
try {
|
|
51
|
+
execFileSync('git', [...credArgs, 'push', 'origin', branchName], execOpts);
|
|
52
|
+
return { success: true, method: 'push' };
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// fall through
|
|
56
|
+
}
|
|
57
|
+
// Try 3: force push with lease
|
|
58
|
+
if (verbose) {
|
|
59
|
+
logInfo(`⚠️ Push rejected, attempting force push with lease...`);
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
execFileSync('git', [...credArgs, 'push', '--force-with-lease', 'origin', branchName], execOpts);
|
|
63
|
+
if (verbose) {
|
|
64
|
+
logInfo(`✅ Successfully force pushed ${branchName}`);
|
|
65
|
+
}
|
|
66
|
+
return { success: true, method: 'force-with-lease' };
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
return {
|
|
70
|
+
success: false,
|
|
71
|
+
error: error instanceof Error ? error.message : String(error),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Force push a branch using --force-with-lease.
|
|
77
|
+
* Used after rebase operations to update the remote branch.
|
|
78
|
+
*/
|
|
79
|
+
export function gitForcePush(options) {
|
|
80
|
+
const { branchName, token, verbose, cwd } = options;
|
|
81
|
+
const credArgs = buildCredentialArgs(token);
|
|
82
|
+
const execOpts = {
|
|
83
|
+
cwd,
|
|
84
|
+
stdio: (verbose ? 'inherit' : 'pipe'),
|
|
85
|
+
};
|
|
86
|
+
if (verbose) {
|
|
87
|
+
logInfo(`📤 Force pushing ${branchName} to remote...`);
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
execFileSync('git', [...credArgs, 'push', '--force-with-lease', 'origin', branchName], execOpts);
|
|
91
|
+
if (verbose) {
|
|
92
|
+
logInfo(`✅ Successfully force pushed ${branchName}`);
|
|
93
|
+
}
|
|
94
|
+
return { success: true, method: 'force-with-lease' };
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
return {
|
|
98
|
+
success: false,
|
|
99
|
+
error: error instanceof Error ? error.message : String(error),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Push the current branch to remote.
|
|
105
|
+
* Resolves the current branch name first, then delegates to gitPush.
|
|
106
|
+
*/
|
|
107
|
+
export function gitPushCurrentBranch(options) {
|
|
108
|
+
const { token, verbose, cwd } = options;
|
|
109
|
+
let branchName;
|
|
110
|
+
try {
|
|
111
|
+
branchName = execFileSync('git', ['branch', '--show-current'], {
|
|
112
|
+
encoding: 'utf-8',
|
|
113
|
+
cwd,
|
|
114
|
+
stdio: 'pipe',
|
|
115
|
+
}).trim();
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
return {
|
|
119
|
+
success: false,
|
|
120
|
+
error: `Failed to resolve current branch: ${error instanceof Error ? error.message : String(error)}`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (!branchName) {
|
|
124
|
+
return { success: false, error: 'Could not determine current branch (detached HEAD?)' };
|
|
125
|
+
}
|
|
126
|
+
return gitPush({ branchName, token, verbose, cwd });
|
|
127
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase configurations for the pipeline runner
|
|
3
|
+
*/
|
|
4
|
+
import { analyzeFeatureWithMCP, checkFeatureAnalysisRequirements, } from '../../phases/feature-analysis/analyzer.js';
|
|
5
|
+
import { generateTechnicalDesign, checkTechnicalDesignRequirements, } from '../../phases/technical-design/analyzer.js';
|
|
6
|
+
import { implementFeatureCode, checkCodeImplementationRequirements, } from '../../phases/code-implementation/analyzer.js';
|
|
7
|
+
import { runFunctionalTesting, checkFunctionalTestingRequirements, } from '../../phases/functional-testing/analyzer.js';
|
|
8
|
+
import { writeCodeTests, checkCodeTestingRequirements, } from '../../phases/code-testing/analyzer.js';
|
|
9
|
+
import { refineCodeFromPRFeedback, checkCodeRefineRequirements, } from '../../phases/code-refine/analyzer.js';
|
|
10
|
+
import { verifyAndResolveComments, checkCodeRefineVerificationRequirements, } from '../../phases/code-refine-verification/verifier.js';
|
|
11
|
+
import { reviewPullRequest, checkCodeReviewRequirements, } from '../../phases/code-review/analyzer.js';
|
|
12
|
+
/**
|
|
13
|
+
* Wrapper for code-refine phase to inject GitHub token
|
|
14
|
+
*/
|
|
15
|
+
const executeCodeRefine = async (options, config) => {
|
|
16
|
+
const githubToken = process.env.GITHUB_TOKEN || process.env.GITHUB_ACCESS_TOKEN;
|
|
17
|
+
if (!githubToken) {
|
|
18
|
+
return {
|
|
19
|
+
status: 'error',
|
|
20
|
+
message: 'GitHub token not found. Set GITHUB_TOKEN or GITHUB_ACCESS_TOKEN environment variable.',
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return refineCodeFromPRFeedback({
|
|
24
|
+
featureId: options.featureId,
|
|
25
|
+
mcpServerUrl: options.mcpServerUrl,
|
|
26
|
+
mcpToken: options.mcpToken,
|
|
27
|
+
githubToken,
|
|
28
|
+
verbose: options.verbose,
|
|
29
|
+
}, config);
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Wrapper for code-refine-verification phase to inject GitHub token
|
|
33
|
+
*/
|
|
34
|
+
const executeCodeRefineVerification = async (options, config) => {
|
|
35
|
+
const githubToken = process.env.GITHUB_TOKEN || process.env.GITHUB_ACCESS_TOKEN;
|
|
36
|
+
if (!githubToken) {
|
|
37
|
+
return {
|
|
38
|
+
status: 'error',
|
|
39
|
+
message: 'GitHub token not found. Set GITHUB_TOKEN or GITHUB_ACCESS_TOKEN environment variable.',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return verifyAndResolveComments({
|
|
43
|
+
featureId: options.featureId,
|
|
44
|
+
mcpServerUrl: options.mcpServerUrl,
|
|
45
|
+
mcpToken: options.mcpToken,
|
|
46
|
+
githubToken,
|
|
47
|
+
config, // Add config parameter for LLM analysis
|
|
48
|
+
verbose: options.verbose,
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Wrapper for code-review phase to inject GitHub token
|
|
53
|
+
*/
|
|
54
|
+
const executeCodeReview = async (options, config) => {
|
|
55
|
+
const githubToken = process.env.GITHUB_TOKEN || process.env.GITHUB_ACCESS_TOKEN;
|
|
56
|
+
if (!githubToken) {
|
|
57
|
+
return {
|
|
58
|
+
status: 'error',
|
|
59
|
+
message: 'GitHub token not found. Set GITHUB_TOKEN or GITHUB_ACCESS_TOKEN environment variable.',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return reviewPullRequest({
|
|
63
|
+
featureId: options.featureId,
|
|
64
|
+
mcpServerUrl: options.mcpServerUrl,
|
|
65
|
+
mcpToken: options.mcpToken,
|
|
66
|
+
githubToken,
|
|
67
|
+
verbose: options.verbose,
|
|
68
|
+
}, config);
|
|
69
|
+
};
|
|
70
|
+
// Pipeline phase configurations
|
|
71
|
+
export const phaseConfigs = [
|
|
72
|
+
{
|
|
73
|
+
name: 'feature-analysis',
|
|
74
|
+
checkRequirements: checkFeatureAnalysisRequirements,
|
|
75
|
+
execute: analyzeFeatureWithMCP,
|
|
76
|
+
requirementsError: 'Feature analysis requirements not met. Install with: npm install @anthropic-ai/claude-code zod',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'technical-design',
|
|
80
|
+
checkRequirements: checkTechnicalDesignRequirements,
|
|
81
|
+
execute: generateTechnicalDesign,
|
|
82
|
+
requirementsError: 'Technical design requirements not met. Install with: npm install @anthropic-ai/claude-code zod',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'code-implementation',
|
|
86
|
+
checkRequirements: checkCodeImplementationRequirements,
|
|
87
|
+
execute: implementFeatureCode,
|
|
88
|
+
requirementsError: 'Code implementation requirements not met. Install with: npm install @anthropic-ai/claude-code zod',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'functional-testing',
|
|
92
|
+
checkRequirements: checkFunctionalTestingRequirements,
|
|
93
|
+
execute: runFunctionalTesting,
|
|
94
|
+
requirementsError: 'Functional testing requirements not met. Install with: npm install @anthropic-ai/claude-code zod',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'code-testing',
|
|
98
|
+
checkRequirements: checkCodeTestingRequirements,
|
|
99
|
+
execute: writeCodeTests,
|
|
100
|
+
requirementsError: 'Code testing requirements not met. Install with: npm install @anthropic-ai/claude-code zod',
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'code-refine',
|
|
104
|
+
checkRequirements: checkCodeRefineRequirements,
|
|
105
|
+
execute: executeCodeRefine,
|
|
106
|
+
requirementsError: 'Code refine requirements not met. Install with: npm install @anthropic-ai/claude-code @octokit/rest',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'code-refine-verification',
|
|
110
|
+
checkRequirements: checkCodeRefineVerificationRequirements,
|
|
111
|
+
execute: executeCodeRefineVerification,
|
|
112
|
+
requirementsError: 'Code refine verification requirements not met. Install with: npm install @octokit/rest',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'code-review',
|
|
116
|
+
checkRequirements: checkCodeReviewRequirements,
|
|
117
|
+
execute: executeCodeReview,
|
|
118
|
+
requirementsError: 'Code review requirements not met. Install with: npm install @anthropic-ai/claude-code @octokit/rest',
|
|
119
|
+
},
|
|
120
|
+
];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature filtering utilities for workflow processor
|
|
3
|
+
* Pure functions for filtering and selecting features for processing
|
|
4
|
+
*/
|
|
5
|
+
import type { FeatureInfo } from '../../types/features.js';
|
|
6
|
+
import type { FeatureProcessingState } from './state-manager.js';
|
|
7
|
+
export declare const shouldProcessFeature: (maxRetries: number) => (feature: FeatureInfo, states: Map<string, FeatureProcessingState>) => boolean;
|
|
8
|
+
export declare const findNextFeature: (features: readonly FeatureInfo[], states: Map<string, FeatureProcessingState>, maxRetries: number) => FeatureInfo | undefined;
|
|
9
|
+
export declare const isFeatureProcessing: (featureId: string, states: Map<string, FeatureProcessingState>) => boolean;
|
|
10
|
+
export declare const isFeatureCompleted: (featureId: string, states: Map<string, FeatureProcessingState>) => boolean;
|
|
11
|
+
export declare const isFeatureFailed: (featureId: string, states: Map<string, FeatureProcessingState>) => boolean;
|
|
12
|
+
export declare const hasReachedMaxRetries: (featureId: string, states: Map<string, FeatureProcessingState>, maxRetries: number) => boolean;
|
|
13
|
+
export declare const filterProcessingFeatures: (features: readonly FeatureInfo[], states: Map<string, FeatureProcessingState>) => FeatureInfo[];
|
|
14
|
+
export declare const filterCompletedFeatures: (features: readonly FeatureInfo[], states: Map<string, FeatureProcessingState>) => FeatureInfo[];
|
|
15
|
+
export declare const filterFailedFeatures: (features: readonly FeatureInfo[], states: Map<string, FeatureProcessingState>) => FeatureInfo[];
|
|
16
|
+
export declare const filterAvailableFeatures: (features: readonly FeatureInfo[], states: Map<string, FeatureProcessingState>, maxRetries: number) => FeatureInfo[];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature filtering utilities for workflow processor
|
|
3
|
+
* Pure functions for filtering and selecting features for processing
|
|
4
|
+
*/
|
|
5
|
+
// Feature filtering functions (pure)
|
|
6
|
+
export const shouldProcessFeature = (maxRetries) => (feature, states) => {
|
|
7
|
+
const state = states.get(feature.id);
|
|
8
|
+
// If never processed, should process
|
|
9
|
+
if (!state)
|
|
10
|
+
return true;
|
|
11
|
+
// If feature was updated after last processing attempt, should reprocess
|
|
12
|
+
// This handles cases where user manually changes status back to ready_for_dev
|
|
13
|
+
if (feature.updated_at) {
|
|
14
|
+
const featureUpdatedTime = new Date(feature.updated_at).getTime();
|
|
15
|
+
const lastAttemptTime = state.lastAttempt.getTime();
|
|
16
|
+
if (featureUpdatedTime > lastAttemptTime) {
|
|
17
|
+
// Feature has been updated since last processing, reprocess it
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// If failed and haven't exceeded retry limit, should retry
|
|
22
|
+
return state.status === 'failed' && state.retryCount < maxRetries;
|
|
23
|
+
};
|
|
24
|
+
export const findNextFeature = (features, states, maxRetries) => features.find((feature) => shouldProcessFeature(maxRetries)(feature, states));
|
|
25
|
+
// Feature status checking functions (pure)
|
|
26
|
+
export const isFeatureProcessing = (featureId, states) => {
|
|
27
|
+
const state = states.get(featureId);
|
|
28
|
+
return state?.status === 'processing';
|
|
29
|
+
};
|
|
30
|
+
export const isFeatureCompleted = (featureId, states) => {
|
|
31
|
+
const state = states.get(featureId);
|
|
32
|
+
return state?.status === 'completed';
|
|
33
|
+
};
|
|
34
|
+
export const isFeatureFailed = (featureId, states) => {
|
|
35
|
+
const state = states.get(featureId);
|
|
36
|
+
return state?.status === 'failed';
|
|
37
|
+
};
|
|
38
|
+
export const hasReachedMaxRetries = (featureId, states, maxRetries) => {
|
|
39
|
+
const state = states.get(featureId);
|
|
40
|
+
return state ? state.retryCount >= maxRetries : false;
|
|
41
|
+
};
|
|
42
|
+
// Feature collection filtering functions (pure)
|
|
43
|
+
export const filterProcessingFeatures = (features, states) => features.filter((feature) => isFeatureProcessing(feature.id, states));
|
|
44
|
+
export const filterCompletedFeatures = (features, states) => features.filter((feature) => isFeatureCompleted(feature.id, states));
|
|
45
|
+
export const filterFailedFeatures = (features, states) => features.filter((feature) => isFeatureFailed(feature.id, states));
|
|
46
|
+
export const filterAvailableFeatures = (features, states, maxRetries) => features.filter((feature) => shouldProcessFeature(maxRetries)(feature, states));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core workflow processor modules
|
|
3
|
+
* Centralized exports for all core functionality
|
|
4
|
+
*/
|
|
5
|
+
// State management
|
|
6
|
+
export * from './state-manager.js';
|
|
7
|
+
// Feature filtering
|
|
8
|
+
export * from './feature-filter.js';
|
|
9
|
+
// Pipeline evaluation
|
|
10
|
+
export * from './pipeline-evaluator.js';
|
|
11
|
+
// Workflow logging
|
|
12
|
+
export * from './workflow-logger.js';
|