edsger 0.25.0 → 0.26.0

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 (128) hide show
  1. package/dist/commands/workflow/config/phase-configs.js +5 -0
  2. package/dist/commands/workflow/executors/phase-executor.d.ts +2 -2
  3. package/dist/commands/workflow/executors/phase-executor.js +2 -2
  4. package/dist/commands/workflow/phase-orchestrator.js +58 -1
  5. package/dist/config/feature-status.js +5 -0
  6. package/dist/index.js +0 -0
  7. package/dist/phases/pr-splitting/context.d.ts +14 -0
  8. package/dist/phases/pr-splitting/context.js +41 -0
  9. package/dist/phases/pr-splitting/index.d.ts +29 -0
  10. package/dist/phases/pr-splitting/index.js +361 -0
  11. package/dist/phases/pr-splitting/outcome.d.ts +24 -0
  12. package/dist/phases/pr-splitting/outcome.js +113 -0
  13. package/dist/phases/pr-splitting/prompts.d.ts +43 -0
  14. package/dist/phases/pr-splitting/prompts.js +209 -0
  15. package/dist/phases/test-cases-analysis/context.d.ts +1 -0
  16. package/dist/phases/test-cases-analysis/context.js +11 -3
  17. package/dist/phases/test-cases-analysis/prompts.d.ts +1 -0
  18. package/dist/phases/test-cases-analysis/prompts.js +32 -0
  19. package/dist/phases/user-stories-analysis/context.d.ts +1 -0
  20. package/dist/phases/user-stories-analysis/context.js +11 -3
  21. package/dist/phases/user-stories-analysis/prompts.d.ts +1 -0
  22. package/dist/phases/user-stories-analysis/prompts.js +32 -0
  23. package/dist/services/audit-logs.d.ts +2 -2
  24. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +4 -0
  25. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +133 -0
  26. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +4 -0
  27. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +336 -0
  28. package/dist/services/lifecycle-agent/index.d.ts +24 -0
  29. package/dist/services/lifecycle-agent/index.js +25 -0
  30. package/dist/services/lifecycle-agent/phase-criteria.d.ts +57 -0
  31. package/dist/services/lifecycle-agent/phase-criteria.js +335 -0
  32. package/dist/services/lifecycle-agent/transition-rules.d.ts +60 -0
  33. package/dist/services/lifecycle-agent/transition-rules.js +184 -0
  34. package/dist/services/lifecycle-agent/types.d.ts +190 -0
  35. package/dist/services/lifecycle-agent/types.js +12 -0
  36. package/dist/services/pull-requests.d.ts +56 -0
  37. package/dist/services/pull-requests.js +125 -0
  38. package/dist/types/index.d.ts +1 -1
  39. package/dist/types/pipeline.d.ts +1 -1
  40. package/package.json +1 -1
  41. package/.claude/settings.local.json +0 -28
  42. package/.env.local +0 -12
  43. package/dist/api/features/__tests__/regression-prevention.test.d.ts +0 -5
  44. package/dist/api/features/__tests__/regression-prevention.test.js +0 -338
  45. package/dist/api/features/__tests__/status-updater.integration.test.d.ts +0 -5
  46. package/dist/api/features/__tests__/status-updater.integration.test.js +0 -497
  47. package/dist/commands/workflow/pipeline-runner.d.ts +0 -17
  48. package/dist/commands/workflow/pipeline-runner.js +0 -393
  49. package/dist/commands/workflow/runner.d.ts +0 -26
  50. package/dist/commands/workflow/runner.js +0 -119
  51. package/dist/commands/workflow/workflow-runner.d.ts +0 -26
  52. package/dist/commands/workflow/workflow-runner.js +0 -119
  53. package/dist/phases/code-implementation/analyzer-helpers.d.ts +0 -28
  54. package/dist/phases/code-implementation/analyzer-helpers.js +0 -177
  55. package/dist/phases/code-implementation/analyzer.d.ts +0 -32
  56. package/dist/phases/code-implementation/analyzer.js +0 -629
  57. package/dist/phases/code-implementation/context-fetcher.d.ts +0 -17
  58. package/dist/phases/code-implementation/context-fetcher.js +0 -86
  59. package/dist/phases/code-implementation/mcp-server.d.ts +0 -1
  60. package/dist/phases/code-implementation/mcp-server.js +0 -93
  61. package/dist/phases/code-implementation/prompts-improvement.d.ts +0 -5
  62. package/dist/phases/code-implementation/prompts-improvement.js +0 -108
  63. package/dist/phases/code-implementation-verification/verifier.d.ts +0 -31
  64. package/dist/phases/code-implementation-verification/verifier.js +0 -196
  65. package/dist/phases/code-refine/analyzer.d.ts +0 -41
  66. package/dist/phases/code-refine/analyzer.js +0 -561
  67. package/dist/phases/code-refine/context-fetcher.d.ts +0 -94
  68. package/dist/phases/code-refine/context-fetcher.js +0 -423
  69. package/dist/phases/code-refine-verification/analysis/llm-analyzer.d.ts +0 -22
  70. package/dist/phases/code-refine-verification/analysis/llm-analyzer.js +0 -134
  71. package/dist/phases/code-refine-verification/verifier.d.ts +0 -47
  72. package/dist/phases/code-refine-verification/verifier.js +0 -597
  73. package/dist/phases/code-review/analyzer.d.ts +0 -29
  74. package/dist/phases/code-review/analyzer.js +0 -363
  75. package/dist/phases/code-review/context-fetcher.d.ts +0 -92
  76. package/dist/phases/code-review/context-fetcher.js +0 -296
  77. package/dist/phases/feature-analysis/analyzer-helpers.d.ts +0 -10
  78. package/dist/phases/feature-analysis/analyzer-helpers.js +0 -47
  79. package/dist/phases/feature-analysis/analyzer.d.ts +0 -11
  80. package/dist/phases/feature-analysis/analyzer.js +0 -208
  81. package/dist/phases/feature-analysis/context-fetcher.d.ts +0 -26
  82. package/dist/phases/feature-analysis/context-fetcher.js +0 -134
  83. package/dist/phases/feature-analysis/http-fallback.d.ts +0 -20
  84. package/dist/phases/feature-analysis/http-fallback.js +0 -95
  85. package/dist/phases/feature-analysis/mcp-server.d.ts +0 -1
  86. package/dist/phases/feature-analysis/mcp-server.js +0 -144
  87. package/dist/phases/feature-analysis/prompts-improvement.d.ts +0 -8
  88. package/dist/phases/feature-analysis/prompts-improvement.js +0 -109
  89. package/dist/phases/feature-analysis-verification/verifier.d.ts +0 -37
  90. package/dist/phases/feature-analysis-verification/verifier.js +0 -147
  91. package/dist/phases/technical-design/analyzer-helpers.d.ts +0 -25
  92. package/dist/phases/technical-design/analyzer-helpers.js +0 -39
  93. package/dist/phases/technical-design/analyzer.d.ts +0 -21
  94. package/dist/phases/technical-design/analyzer.js +0 -461
  95. package/dist/phases/technical-design/context-fetcher.d.ts +0 -12
  96. package/dist/phases/technical-design/context-fetcher.js +0 -39
  97. package/dist/phases/technical-design/http-fallback.d.ts +0 -17
  98. package/dist/phases/technical-design/http-fallback.js +0 -151
  99. package/dist/phases/technical-design/mcp-server.d.ts +0 -1
  100. package/dist/phases/technical-design/mcp-server.js +0 -157
  101. package/dist/phases/technical-design/prompts-improvement.d.ts +0 -5
  102. package/dist/phases/technical-design/prompts-improvement.js +0 -93
  103. package/dist/phases/technical-design-verification/verifier.d.ts +0 -53
  104. package/dist/phases/technical-design-verification/verifier.js +0 -170
  105. package/dist/services/feature-branches.d.ts +0 -77
  106. package/dist/services/feature-branches.js +0 -205
  107. package/dist/workflow-runner/config/phase-configs.d.ts +0 -5
  108. package/dist/workflow-runner/config/phase-configs.js +0 -120
  109. package/dist/workflow-runner/core/feature-filter.d.ts +0 -16
  110. package/dist/workflow-runner/core/feature-filter.js +0 -46
  111. package/dist/workflow-runner/core/index.d.ts +0 -8
  112. package/dist/workflow-runner/core/index.js +0 -12
  113. package/dist/workflow-runner/core/pipeline-evaluator.d.ts +0 -24
  114. package/dist/workflow-runner/core/pipeline-evaluator.js +0 -32
  115. package/dist/workflow-runner/core/state-manager.d.ts +0 -24
  116. package/dist/workflow-runner/core/state-manager.js +0 -42
  117. package/dist/workflow-runner/core/workflow-logger.d.ts +0 -20
  118. package/dist/workflow-runner/core/workflow-logger.js +0 -65
  119. package/dist/workflow-runner/executors/phase-executor.d.ts +0 -8
  120. package/dist/workflow-runner/executors/phase-executor.js +0 -248
  121. package/dist/workflow-runner/feature-workflow-runner.d.ts +0 -26
  122. package/dist/workflow-runner/feature-workflow-runner.js +0 -119
  123. package/dist/workflow-runner/index.d.ts +0 -2
  124. package/dist/workflow-runner/index.js +0 -2
  125. package/dist/workflow-runner/pipeline-runner.d.ts +0 -17
  126. package/dist/workflow-runner/pipeline-runner.js +0 -393
  127. package/dist/workflow-runner/workflow-processor.d.ts +0 -54
  128. package/dist/workflow-runner/workflow-processor.js +0 -170
@@ -6,6 +6,7 @@ import { analyseUserStories } from '../../../phases/user-stories-analysis/index.
6
6
  import { analyseTestCases } from '../../../phases/test-cases-analysis/index.js';
7
7
  import { generateTechnicalDesign } from '../../../phases/technical-design/index.js';
8
8
  import { planFeatureBranches } from '../../../phases/branch-planning/index.js';
9
+ import { splitFeatureIntoPRs } from '../../../phases/pr-splitting/index.js';
9
10
  import { implementFeatureCode } from '../../../phases/code-implementation/index.js';
10
11
  import { runFunctionalTesting } from '../../../phases/functional-testing/analyzer.js';
11
12
  import { writeCodeTests } from '../../../phases/code-testing/analyzer.js';
@@ -95,6 +96,10 @@ export const phaseConfigs = [
95
96
  name: 'code-implementation',
96
97
  execute: implementFeatureCode,
97
98
  },
99
+ {
100
+ name: 'pr-splitting',
101
+ execute: splitFeatureIntoPRs,
102
+ },
98
103
  {
99
104
  name: 'functional-testing',
100
105
  execute: runFunctionalTesting,
@@ -4,5 +4,5 @@
4
4
  import { EdsgerConfig } from '../../../types/index.js';
5
5
  import { PipelinePhaseOptions, PipelineResult, PhaseConfig } from '../../../types/pipeline.js';
6
6
  export declare const createPhaseRunner: (phaseConfig: PhaseConfig) => (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>;
7
- declare const runFeatureAnalysisPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runUserStoriesAnalysisPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runTestCasesAnalysisPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runTechnicalDesignPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runBranchPlanningPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeImplementationPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runFunctionalTestingPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeTestingPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeRefinePhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeRefineVerificationPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeReviewPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runAutonomousPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>;
8
- export { runFeatureAnalysisPhase, runUserStoriesAnalysisPhase, runTestCasesAnalysisPhase, runTechnicalDesignPhase, runBranchPlanningPhase, runCodeImplementationPhase, runFunctionalTestingPhase, runCodeTestingPhase, runCodeRefinePhase, runCodeRefineVerificationPhase, runCodeReviewPhase, runAutonomousPhase, };
7
+ declare const runFeatureAnalysisPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runUserStoriesAnalysisPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runTestCasesAnalysisPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runTechnicalDesignPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runBranchPlanningPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeImplementationPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runPRSplittingPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runFunctionalTestingPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeTestingPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeRefinePhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeRefineVerificationPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeReviewPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runAutonomousPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>;
8
+ export { runFeatureAnalysisPhase, runUserStoriesAnalysisPhase, runTestCasesAnalysisPhase, runTechnicalDesignPhase, runBranchPlanningPhase, runCodeImplementationPhase, runPRSplittingPhase, runFunctionalTestingPhase, runCodeTestingPhase, runCodeRefinePhase, runCodeRefineVerificationPhase, runCodeReviewPhase, runAutonomousPhase, };
@@ -262,6 +262,6 @@ export const createPhaseRunner = (phaseConfig) => async (options, config) => {
262
262
  }
263
263
  };
264
264
  // Create phase runners using the configuration
265
- const [runFeatureAnalysisPhase, runUserStoriesAnalysisPhase, runTestCasesAnalysisPhase, runTechnicalDesignPhase, runBranchPlanningPhase, runCodeImplementationPhase, runFunctionalTestingPhase, runCodeTestingPhase, runCodeRefinePhase, runCodeRefineVerificationPhase, runCodeReviewPhase, runAutonomousPhase,] = phaseConfigs.map(createPhaseRunner);
265
+ const [runFeatureAnalysisPhase, runUserStoriesAnalysisPhase, runTestCasesAnalysisPhase, runTechnicalDesignPhase, runBranchPlanningPhase, runCodeImplementationPhase, runPRSplittingPhase, runFunctionalTestingPhase, runCodeTestingPhase, runCodeRefinePhase, runCodeRefineVerificationPhase, runCodeReviewPhase, runAutonomousPhase,] = phaseConfigs.map(createPhaseRunner);
266
266
  // Export individual phase runners for granular control
267
- export { runFeatureAnalysisPhase, runUserStoriesAnalysisPhase, runTestCasesAnalysisPhase, runTechnicalDesignPhase, runBranchPlanningPhase, runCodeImplementationPhase, runFunctionalTestingPhase, runCodeTestingPhase, runCodeRefinePhase, runCodeRefineVerificationPhase, runCodeReviewPhase, runAutonomousPhase, };
267
+ export { runFeatureAnalysisPhase, runUserStoriesAnalysisPhase, runTestCasesAnalysisPhase, runTechnicalDesignPhase, runBranchPlanningPhase, runCodeImplementationPhase, runPRSplittingPhase, runFunctionalTestingPhase, runCodeTestingPhase, runCodeRefinePhase, runCodeRefineVerificationPhase, runCodeReviewPhase, runAutonomousPhase, };
@@ -14,7 +14,7 @@
14
14
  * Uses functional programming principles for composability
15
15
  */
16
16
  import { updateFeatureStatusForPhase, markWorkflowPhaseCompleted, } from '../../api/features/index.js';
17
- import { runFeatureAnalysisPhase, runUserStoriesAnalysisPhase, runTestCasesAnalysisPhase, runTechnicalDesignPhase, runCodeImplementationPhase, runFunctionalTestingPhase, runCodeTestingPhase, runCodeReviewPhase, } from './executors/phase-executor.js';
17
+ import { runFeatureAnalysisPhase, runUserStoriesAnalysisPhase, runTestCasesAnalysisPhase, runTechnicalDesignPhase, runCodeImplementationPhase, runPRSplittingPhase, runFunctionalTestingPhase, runCodeTestingPhase, runCodeReviewPhase, } from './executors/phase-executor.js';
18
18
  import { logPipelineStart, logPhaseResult, logPipelineComplete, shouldContinuePipeline, } from '../../utils/pipeline-logger.js';
19
19
  import { handleTestFailuresWithRetry } from '../../phases/functional-testing/test-retry-handler.js';
20
20
  import { handleCodeRefineWithRetry } from '../../phases/code-refine/retry-handler.js';
@@ -78,6 +78,11 @@ export const runPipelineByMode = async (options, config, executionMode) => {
78
78
  return await runOnlyCodeImplementation(options, config);
79
79
  case 'from_code_implementation':
80
80
  return await runFromCodeImplementation(options, config);
81
+ // PR Splitting
82
+ case 'only_pr_splitting':
83
+ return await runOnlyPRSplitting(options, config);
84
+ case 'from_pr_splitting':
85
+ return await runFromPRSplitting(options, config);
81
86
  // Functional Testing
82
87
  case 'only_functional_testing':
83
88
  return await runOnlyFunctionalTesting(options, config);
@@ -310,6 +315,58 @@ const runOnlyCodeImplementation = async (options, config) => {
310
315
  }
311
316
  return finalizePipelineExecution(options, results, verbose);
312
317
  };
318
+ /**
319
+ * Run only PR splitting phase
320
+ */
321
+ const runOnlyPRSplitting = async (options, config) => {
322
+ const { featureId, verbose } = options;
323
+ logPipelineStart(featureId, verbose);
324
+ const results = [];
325
+ const prSplittingResult = await runPRSplittingPhase(options, config);
326
+ results.push(await logAndMarkPhaseCompleted(prSplittingResult, verbose));
327
+ return finalizePipelineExecution(options, results, verbose);
328
+ };
329
+ /**
330
+ * Run from PR splitting to end
331
+ * Flow: pr-splitting → functional-testing → ... → code-review → code-refine
332
+ */
333
+ const runFromPRSplitting = async (options, config) => {
334
+ const { featureId, verbose } = options;
335
+ logPipelineStart(featureId, verbose);
336
+ const results = [];
337
+ // 1. PR Splitting
338
+ const prSplittingResult = await runPRSplittingPhase(options, config);
339
+ results.push(await logAndMarkPhaseCompleted(prSplittingResult, verbose));
340
+ if (!shouldContinuePipeline(results)) {
341
+ return finalizePipelineExecution(options, results, verbose);
342
+ }
343
+ // 2. Functional Testing with retry loop for bug fixes
344
+ const testingResult = await handleTestFailuresWithRetry({
345
+ options,
346
+ config,
347
+ results,
348
+ verbose,
349
+ });
350
+ const finalStatus = testingResult.status === 'success' ? 'testing_passed' : 'testing_failed';
351
+ await updateFeatureStatusForPhase(featureId, finalStatus === 'testing_passed' ? 'testing-passed' : 'testing-failed', verbose);
352
+ if (testingResult.status === 'success') {
353
+ const prCreated = await handlePullRequestCreation({
354
+ featureId,
355
+ results,
356
+ testingResult,
357
+ verbose,
358
+ });
359
+ if (prCreated) {
360
+ await continueWithCodeReviewAndRefine(options, config, results, verbose);
361
+ }
362
+ else {
363
+ if (verbose) {
364
+ logInfo('āš ļø Skipping code review and refine workflow: pull request creation failed');
365
+ }
366
+ }
367
+ }
368
+ return finalizePipelineExecution(options, results, verbose);
369
+ };
313
370
  /**
314
371
  * Run only functional testing phase
315
372
  */
@@ -36,6 +36,8 @@ export const STATUS_PROGRESSION_ORDER = [
36
36
  'branch_planning_verification',
37
37
  'code_implementation',
38
38
  'code_implementation_verification',
39
+ 'pr_splitting',
40
+ 'pr_splitting_verification',
39
41
  'code_refine',
40
42
  'code_refine_verification',
41
43
  'bug_fixing',
@@ -73,6 +75,8 @@ export const PHASE_STATUS_MAP = {
73
75
  'branch-planning-verification': 'branch_planning_verification',
74
76
  'code-implementation': 'code_implementation',
75
77
  'code-implementation-verification': 'code_implementation_verification',
78
+ 'pr-splitting': 'pr_splitting',
79
+ 'pr-splitting-verification': 'pr_splitting_verification',
76
80
  'code-refine': 'code_refine',
77
81
  'code-refine-verification': 'code_refine_verification',
78
82
  'bug-fixing': 'bug_fixing',
@@ -105,6 +109,7 @@ export const HUMAN_SELECTABLE_STATUSES = [
105
109
  'technical_design',
106
110
  'branch_planning',
107
111
  'code_implementation',
112
+ 'pr_splitting',
108
113
  'code_refine',
109
114
  'bug_fixing',
110
115
  'code_review',
package/dist/index.js CHANGED
File without changes
@@ -0,0 +1,14 @@
1
+ import type { FeatureInfo } from '../../types/features.js';
2
+ import { type ProductInfo } from '../../api/products.js';
3
+ import { type Branch } from '../../services/branches.js';
4
+ import { type PullRequest } from '../../services/pull-requests.js';
5
+ export interface PRSplittingContext {
6
+ feature: FeatureInfo;
7
+ product: ProductInfo;
8
+ existing_branches: Branch[];
9
+ existing_pull_requests: PullRequest[];
10
+ }
11
+ /**
12
+ * Fetch all context information needed for PR splitting via MCP endpoints
13
+ */
14
+ export declare function fetchPRSplittingContext(featureId: string, verbose?: boolean): Promise<PRSplittingContext>;
@@ -0,0 +1,41 @@
1
+ import { logInfo, logError } from '../../utils/logger.js';
2
+ import { getFeature } from '../../api/features/index.js';
3
+ import { getProduct } from '../../api/products.js';
4
+ import { getBranches } from '../../services/branches.js';
5
+ import { getPullRequests, } from '../../services/pull-requests.js';
6
+ /**
7
+ * Fetch all context information needed for PR splitting via MCP endpoints
8
+ */
9
+ export async function fetchPRSplittingContext(featureId, verbose) {
10
+ try {
11
+ if (verbose) {
12
+ logInfo(`Fetching PR splitting context for feature: ${featureId}`);
13
+ }
14
+ // Fetch all required data in parallel for better performance
15
+ const [feature, existing_branches, existing_pull_requests] = await Promise.all([
16
+ getFeature(featureId, verbose),
17
+ getBranches({ featureId, verbose }).catch(() => []),
18
+ getPullRequests({ featureId, verbose }).catch(() => []),
19
+ ]);
20
+ const product = await getProduct(feature.product_id, verbose);
21
+ if (verbose) {
22
+ logInfo(`āœ… PR splitting context fetched successfully:`);
23
+ logInfo(` Feature: ${feature.name}`);
24
+ logInfo(` Product: ${product.name}`);
25
+ logInfo(` Technical Design: ${feature.technical_design ? 'Yes' : 'No'}`);
26
+ logInfo(` Existing Branches: ${existing_branches.length}`);
27
+ logInfo(` Existing Pull Requests: ${existing_pull_requests.length}`);
28
+ }
29
+ return {
30
+ feature,
31
+ product,
32
+ existing_branches,
33
+ existing_pull_requests,
34
+ };
35
+ }
36
+ catch (error) {
37
+ const errorMessage = error instanceof Error ? error.message : String(error);
38
+ logError(`Failed to fetch PR splitting context: ${errorMessage}`);
39
+ throw new Error(`Context fetch failed: ${errorMessage}`);
40
+ }
41
+ }
@@ -0,0 +1,29 @@
1
+ import { EdsgerConfig } from '../../types/index.js';
2
+ export interface PRSplittingOptions {
3
+ featureId: string;
4
+ verbose?: boolean;
5
+ replaceExisting?: boolean;
6
+ }
7
+ export interface PlannedPullRequest {
8
+ sequence: number;
9
+ name: string;
10
+ description: string;
11
+ branch_name?: string;
12
+ depends_on_branch_name?: string | null;
13
+ files?: Array<{
14
+ path: string;
15
+ change_type: string;
16
+ }>;
17
+ scope?: string[];
18
+ acceptance_criteria?: string[];
19
+ }
20
+ export interface PRSplittingResult {
21
+ featureId: string;
22
+ pullRequests: PlannedPullRequest[];
23
+ status: 'success' | 'error';
24
+ summary: string;
25
+ rationale?: string;
26
+ }
27
+ export declare const splitFeatureIntoPRs: (options: PRSplittingOptions, config: EdsgerConfig) => Promise<PRSplittingResult>;
28
+ export { fetchPRSplittingContext } from './context.js';
29
+ export type { PRSplittingContext } from './context.js';
@@ -0,0 +1,361 @@
1
+ import { query } from '@anthropic-ai/claude-agent-sdk';
2
+ import { DEFAULT_MODEL } from '../../constants.js';
3
+ import { logInfo, logError } from '../../utils/logger.js';
4
+ import { fetchPRSplittingContext } from './context.js';
5
+ import { createPRSplittingSystemPrompt, createPRSplittingPromptWithContext, createImprovementPrompt, formatContextForPrompt, formatExistingPRsForPrompt, } from './prompts.js';
6
+ import { buildSuccessResult, buildErrorResult, buildNoChangeResult, validatePlannedPRs, sortPRsByDependency, } from './outcome.js';
7
+ import { createPullRequests, clearPullRequests, } from '../../services/pull-requests.js';
8
+ import { logFeaturePhaseEvent } from '../../services/audit-logs.js';
9
+ import { getFeedbacksForPhase, formatFeedbacksForContext, } from '../../services/feedbacks.js';
10
+ function userMessage(content) {
11
+ return {
12
+ type: 'user',
13
+ message: { role: 'user', content: content },
14
+ };
15
+ }
16
+ async function* prompt(analysisPrompt) {
17
+ yield userMessage(analysisPrompt);
18
+ await new Promise((res) => setTimeout(res, 10000));
19
+ }
20
+ export const splitFeatureIntoPRs = async (options, config) => {
21
+ const { featureId, verbose, replaceExisting } = options;
22
+ if (verbose) {
23
+ logInfo(`Starting PR splitting for feature ID: ${featureId}`);
24
+ }
25
+ try {
26
+ // Log phase start
27
+ await logFeaturePhaseEvent({
28
+ featureId,
29
+ eventType: 'phase_started',
30
+ phase: 'pr_splitting',
31
+ result: 'info',
32
+ metadata: {
33
+ replace_existing: replaceExisting || false,
34
+ timestamp: new Date().toISOString(),
35
+ },
36
+ }, verbose);
37
+ // Fetch context
38
+ const context = await fetchPRSplittingContext(featureId, verbose);
39
+ // Fetch feedbacks for pr-splitting phase
40
+ let feedbacksContext = null;
41
+ let hasFeedbacks = false;
42
+ try {
43
+ feedbacksContext = await getFeedbacksForPhase({ featureId, verbose }, 'pr-splitting');
44
+ hasFeedbacks = feedbacksContext.feedbacks.length > 0;
45
+ if (verbose) {
46
+ logInfo(`šŸ“‹ Feedbacks fetched: ${feedbacksContext.feedbacks.length} feedbacks found`);
47
+ }
48
+ }
49
+ catch (error) {
50
+ if (verbose) {
51
+ logError(`Failed to fetch feedbacks: ${error instanceof Error ? error.message : String(error)}`);
52
+ }
53
+ }
54
+ // Determine operation mode
55
+ const hasExistingPRs = context.existing_pull_requests.length > 0;
56
+ const isIncrementalUpdate = hasExistingPRs && hasFeedbacks && !replaceExisting;
57
+ if (verbose) {
58
+ logInfo('\nšŸ“‹ PR Splitting Mode Decision:');
59
+ logInfo(` - Has existing PRs: ${hasExistingPRs} (${context.existing_pull_requests.length} PRs)`);
60
+ logInfo(` - Has feedbacks: ${hasFeedbacks} (${feedbacksContext?.feedbacks.length || 0} feedbacks)`);
61
+ logInfo(` - Replace existing: ${replaceExisting || false}`);
62
+ logInfo(` - Mode: ${isIncrementalUpdate ? 'šŸ”„ Incremental Update' : hasExistingPRs && !replaceExisting ? 'āœ… No Change Needed' : '✨ New Planning'}`);
63
+ }
64
+ // Check if there are existing PRs and no feedbacks - no change needed
65
+ if (hasExistingPRs && !hasFeedbacks && !replaceExisting) {
66
+ if (verbose) {
67
+ logInfo(`Feature already has ${context.existing_pull_requests.length} PRs planned and no feedbacks to address.`);
68
+ logInfo('Use replaceExisting: true to re-plan.');
69
+ }
70
+ return buildNoChangeResult(featureId, context.existing_pull_requests.length);
71
+ }
72
+ // Clear existing PRs if requested (full re-plan)
73
+ if (replaceExisting && hasExistingPRs) {
74
+ if (verbose) {
75
+ logInfo(`Clearing ${context.existing_pull_requests.length} existing PRs for full re-plan...`);
76
+ }
77
+ await clearPullRequests({ featureId, verbose }, true);
78
+ }
79
+ // Format context for prompt
80
+ const contextInfo = formatContextForPrompt(context.feature, context.product, context.existing_branches);
81
+ const existingPRsInfo = formatExistingPRsForPrompt(context.existing_pull_requests);
82
+ // Create prompts based on mode
83
+ const systemPrompt = createPRSplittingSystemPrompt(config, featureId);
84
+ let userPrompt;
85
+ if (isIncrementalUpdate && feedbacksContext) {
86
+ if (verbose) {
87
+ logInfo('\nšŸŽÆ Feedbacks that will be addressed:');
88
+ feedbacksContext.feedbacks.forEach((fb, idx) => {
89
+ logInfo(` ${idx + 1}. [${fb.feedback_type}] ${fb.title}`);
90
+ logInfo(` ${fb.content.substring(0, 100)}${fb.content.length > 100 ? '...' : ''}`);
91
+ });
92
+ }
93
+ const feedbacksInfo = await formatFeedbacksForContext(feedbacksContext);
94
+ const currentPlanInfo = existingPRsInfo || 'No existing PR plan documented.';
95
+ userPrompt = createImprovementPrompt(feedbacksInfo, currentPlanInfo);
96
+ userPrompt = `${userPrompt}\n\n## Feature Context\n\n${contextInfo}`;
97
+ }
98
+ else {
99
+ userPrompt = createPRSplittingPromptWithContext(featureId, contextInfo, existingPRsInfo);
100
+ }
101
+ if (verbose) {
102
+ logInfo('Starting Claude Code query for PR splitting...');
103
+ }
104
+ // Execute query
105
+ const result = await executePRSplittingQuery(userPrompt, systemPrompt, config, verbose);
106
+ if (!result ||
107
+ !result.pullRequests ||
108
+ result.pullRequests.length === 0) {
109
+ const errorMsg = 'No pull requests were generated';
110
+ logError(errorMsg);
111
+ return buildErrorResult(featureId, errorMsg);
112
+ }
113
+ // Validate PRs
114
+ const validation = validatePlannedPRs(result.pullRequests);
115
+ if (!validation.valid) {
116
+ const errorMsg = `Invalid PR plan: ${validation.errors.join(', ')}`;
117
+ logError(errorMsg);
118
+ return buildErrorResult(featureId, errorMsg);
119
+ }
120
+ // Sort PRs by dependency
121
+ const sortedPRs = sortPRsByDependency(result.pullRequests);
122
+ // For incremental update mode, clear existing PRs before creating new ones
123
+ if (isIncrementalUpdate && hasExistingPRs) {
124
+ if (verbose) {
125
+ logInfo(`šŸ”„ Clearing ${context.existing_pull_requests.length} existing PRs for incremental update...`);
126
+ }
127
+ await clearPullRequests({ featureId, verbose: false }, true);
128
+ }
129
+ // Create PRs in database with proper dependencies
130
+ const prBranchNameToId = new Map();
131
+ const prInputs = [];
132
+ for (const pr of sortedPRs) {
133
+ let basePrId;
134
+ if (pr.depends_on_branch_name) {
135
+ basePrId = prBranchNameToId.get(pr.depends_on_branch_name);
136
+ }
137
+ prInputs.push({
138
+ sequence: pr.sequence,
139
+ name: pr.name,
140
+ description: pr.description,
141
+ branch_name: pr.branch_name,
142
+ base_pr_id: basePrId,
143
+ files: pr.files,
144
+ });
145
+ }
146
+ const created = await createPullRequests({ featureId, verbose: false }, prInputs);
147
+ // Build the branch name to ID mapping from created PRs
148
+ created.forEach((createdPR) => {
149
+ if (createdPR.branch_name) {
150
+ prBranchNameToId.set(createdPR.branch_name, createdPR.id);
151
+ }
152
+ });
153
+ if (verbose) {
154
+ const modeLabel = isIncrementalUpdate ? 'šŸ”„ Updated' : 'āœ… Created';
155
+ logInfo(`${modeLabel} ${sortedPRs.length} pull requests`);
156
+ sortedPRs.forEach((pr) => {
157
+ logInfo(` ${pr.sequence}. ${pr.name}`);
158
+ if (pr.files) {
159
+ logInfo(` Files: ${pr.files.map((f) => f.path).join(', ')}`);
160
+ }
161
+ });
162
+ }
163
+ // Log phase completion
164
+ await logFeaturePhaseEvent({
165
+ featureId,
166
+ eventType: 'phase_completed',
167
+ phase: 'pr_splitting',
168
+ result: 'success',
169
+ metadata: {
170
+ prs_created: sortedPRs.length,
171
+ pr_names: sortedPRs.map((pr) => pr.name),
172
+ mode: isIncrementalUpdate ? 'incremental_update' : 'new_planning',
173
+ feedbacks_addressed: feedbacksContext?.feedbacks.length || 0,
174
+ timestamp: new Date().toISOString(),
175
+ },
176
+ }, verbose);
177
+ const summaryPrefix = isIncrementalUpdate
178
+ ? `Updated PR plan based on ${feedbacksContext?.feedbacks.length || 0} feedbacks.`
179
+ : '';
180
+ return buildSuccessResult(featureId, sortedPRs, result.summary ||
181
+ `${summaryPrefix} ${sortedPRs.length} pull requests for incremental review`.trim(), result.rationale);
182
+ }
183
+ catch (error) {
184
+ const errorMessage = error instanceof Error ? error.message : String(error);
185
+ logError(`PR splitting failed: ${errorMessage}`);
186
+ // Log phase failure
187
+ await logFeaturePhaseEvent({
188
+ featureId,
189
+ eventType: 'phase_failed',
190
+ phase: 'pr_splitting',
191
+ result: 'error',
192
+ metadata: {
193
+ error: errorMessage,
194
+ timestamp: new Date().toISOString(),
195
+ },
196
+ }, verbose);
197
+ return buildErrorResult(featureId, errorMessage);
198
+ }
199
+ };
200
+ /**
201
+ * Execute the PR splitting query and parse the result
202
+ */
203
+ async function executePRSplittingQuery(userPrompt, systemPrompt, config, verbose) {
204
+ let lastAssistantResponse = '';
205
+ let structuredResult = null;
206
+ if (verbose) {
207
+ logInfo('šŸ¤– Starting PR splitting agent query...');
208
+ }
209
+ for await (const message of query({
210
+ prompt: prompt(userPrompt),
211
+ options: {
212
+ systemPrompt: {
213
+ type: 'preset',
214
+ preset: 'claude_code',
215
+ append: systemPrompt,
216
+ },
217
+ model: DEFAULT_MODEL,
218
+ maxTurns: 500,
219
+ permissionMode: 'bypassPermissions',
220
+ },
221
+ })) {
222
+ if (verbose) {
223
+ logInfo(` Received message type: ${message.type}`);
224
+ }
225
+ if (message.type === 'assistant' && message.message?.content) {
226
+ for (const content of message.message.content) {
227
+ if (content.type === 'text') {
228
+ lastAssistantResponse += content.text + '\n';
229
+ if (verbose) {
230
+ console.log(`\nšŸ¤– ${content.text}`);
231
+ }
232
+ }
233
+ else if (content.type === 'tool_use') {
234
+ if (verbose) {
235
+ console.log(`\nšŸ”§ ${content.name}: ${content.input.description || 'Running...'}`);
236
+ }
237
+ }
238
+ }
239
+ }
240
+ if (message.type === 'result') {
241
+ if (message.subtype === 'success') {
242
+ logInfo('\nšŸ“‹ PR splitting completed, parsing results...');
243
+ try {
244
+ const responseText = message.result || lastAssistantResponse;
245
+ structuredResult = parseJsonResponse(responseText, verbose);
246
+ }
247
+ catch (error) {
248
+ logError(`Failed to parse PR splitting result: ${error}`);
249
+ }
250
+ }
251
+ else {
252
+ logError(`\nāš ļø PR splitting incomplete: ${message.subtype}`);
253
+ }
254
+ }
255
+ }
256
+ if (!structuredResult) {
257
+ return null;
258
+ }
259
+ return {
260
+ pullRequests: structuredResult.pullRequests || [],
261
+ summary: structuredResult.summary,
262
+ rationale: structuredResult.rationale,
263
+ };
264
+ }
265
+ /**
266
+ * Parse JSON response from Claude
267
+ */
268
+ function parseJsonResponse(responseText, verbose) {
269
+ // Try to extract JSON from markdown code block
270
+ const jsonBlockMatch = responseText.match(/```json\s*\n([\s\S]*?)\n\s*```/);
271
+ if (jsonBlockMatch) {
272
+ try {
273
+ const parsed = JSON.parse(jsonBlockMatch[1]);
274
+ if (parsed.pr_splitting_result) {
275
+ return {
276
+ pullRequests: parsed.pr_splitting_result.pull_requests || [],
277
+ summary: parsed.pr_splitting_result.summary,
278
+ rationale: parsed.pr_splitting_result.rationale,
279
+ };
280
+ }
281
+ if (parsed.pull_requests) {
282
+ return {
283
+ pullRequests: parsed.pull_requests,
284
+ summary: parsed.summary,
285
+ rationale: parsed.rationale,
286
+ };
287
+ }
288
+ return parsed;
289
+ }
290
+ catch (e) {
291
+ if (verbose) {
292
+ logError(`Failed to parse JSON from code block: ${e}`);
293
+ }
294
+ }
295
+ }
296
+ // Try to find JSON object containing "pr_splitting_result"
297
+ const resultMatch = responseText.match(/\{[\s\S]*"pr_splitting_result"[\s\S]*\}/);
298
+ if (resultMatch) {
299
+ try {
300
+ let braceCount = 0;
301
+ let startIndex = -1;
302
+ let endIndex = -1;
303
+ for (let i = 0; i < responseText.length; i++) {
304
+ if (responseText[i] === '{') {
305
+ if (startIndex === -1)
306
+ startIndex = i;
307
+ braceCount++;
308
+ }
309
+ else if (responseText[i] === '}') {
310
+ braceCount--;
311
+ if (braceCount === 0 && startIndex !== -1) {
312
+ endIndex = i;
313
+ break;
314
+ }
315
+ }
316
+ }
317
+ if (startIndex !== -1 && endIndex !== -1) {
318
+ const jsonStr = responseText.substring(startIndex, endIndex + 1);
319
+ const parsed = JSON.parse(jsonStr);
320
+ if (parsed.pr_splitting_result) {
321
+ return {
322
+ pullRequests: parsed.pr_splitting_result.pull_requests || [],
323
+ summary: parsed.pr_splitting_result.summary,
324
+ rationale: parsed.pr_splitting_result.rationale,
325
+ };
326
+ }
327
+ }
328
+ }
329
+ catch (e) {
330
+ if (verbose) {
331
+ logError(`Failed to extract JSON from response: ${e}`);
332
+ }
333
+ }
334
+ }
335
+ // Try to parse the entire response as JSON
336
+ try {
337
+ const parsed = JSON.parse(responseText);
338
+ if (parsed.pr_splitting_result) {
339
+ return {
340
+ pullRequests: parsed.pr_splitting_result.pull_requests || [],
341
+ summary: parsed.pr_splitting_result.summary,
342
+ rationale: parsed.pr_splitting_result.rationale,
343
+ };
344
+ }
345
+ if (parsed.pull_requests) {
346
+ return {
347
+ pullRequests: parsed.pull_requests,
348
+ summary: parsed.summary,
349
+ rationale: parsed.rationale,
350
+ };
351
+ }
352
+ }
353
+ catch (e) {
354
+ if (verbose) {
355
+ logError(`Failed to parse response as JSON: ${e}`);
356
+ }
357
+ }
358
+ return null;
359
+ }
360
+ // Re-export types and functions for external use
361
+ export { fetchPRSplittingContext } from './context.js';
@@ -0,0 +1,24 @@
1
+ import { PRSplittingResult, PlannedPullRequest } from './index.js';
2
+ /**
3
+ * Build a successful PR splitting result
4
+ */
5
+ export declare function buildSuccessResult(featureId: string, pullRequests: PlannedPullRequest[], summary: string, rationale?: string): PRSplittingResult;
6
+ /**
7
+ * Build an error result
8
+ */
9
+ export declare function buildErrorResult(featureId: string, errorMessage: string): PRSplittingResult;
10
+ /**
11
+ * Build a result when no changes are needed
12
+ */
13
+ export declare function buildNoChangeResult(featureId: string, existingPRCount: number): PRSplittingResult;
14
+ /**
15
+ * Validate planned pull requests
16
+ */
17
+ export declare function validatePlannedPRs(prs: PlannedPullRequest[]): {
18
+ valid: boolean;
19
+ errors: string[];
20
+ };
21
+ /**
22
+ * Sort pull requests by dependency order (topological sort based on depends_on_branch_name)
23
+ */
24
+ export declare function sortPRsByDependency(prs: PlannedPullRequest[]): PlannedPullRequest[];