edsger 0.41.3 → 0.42.1

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 (117) hide show
  1. package/.claude/settings.local.json +3 -23
  2. package/dist/commands/pr-resolve/index.d.ts +1 -0
  3. package/dist/commands/pr-resolve/index.js +1 -0
  4. package/dist/index.js +1 -0
  5. package/dist/phases/pr-resolve/__tests__/checklist-learner.test.d.ts +1 -0
  6. package/dist/phases/pr-resolve/__tests__/checklist-learner.test.js +157 -0
  7. package/dist/phases/pr-resolve/__tests__/types.test.d.ts +1 -0
  8. package/dist/phases/pr-resolve/__tests__/types.test.js +43 -0
  9. package/dist/phases/pr-resolve/checklist-learner.d.ts +28 -0
  10. package/dist/phases/pr-resolve/checklist-learner.js +128 -0
  11. package/dist/phases/pr-resolve/index.d.ts +4 -0
  12. package/dist/phases/pr-resolve/index.js +42 -7
  13. package/dist/phases/pr-resolve/types.d.ts +18 -0
  14. package/dist/phases/pr-resolve/types.js +14 -0
  15. package/dist/phases/pr-resolve/workspace.d.ts +7 -1
  16. package/dist/phases/pr-resolve/workspace.js +38 -11
  17. package/dist/phases/pr-splitting/context.js +20 -15
  18. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +4 -0
  19. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +133 -0
  20. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +4 -0
  21. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +336 -0
  22. package/dist/services/lifecycle-agent/index.d.ts +24 -0
  23. package/dist/services/lifecycle-agent/index.js +25 -0
  24. package/dist/services/lifecycle-agent/phase-criteria.d.ts +57 -0
  25. package/dist/services/lifecycle-agent/phase-criteria.js +335 -0
  26. package/dist/services/lifecycle-agent/transition-rules.d.ts +60 -0
  27. package/dist/services/lifecycle-agent/transition-rules.js +184 -0
  28. package/dist/services/lifecycle-agent/types.d.ts +190 -0
  29. package/dist/services/lifecycle-agent/types.js +12 -0
  30. package/package.json +1 -1
  31. package/.env.local +0 -12
  32. package/dist/api/features/__tests__/regression-prevention.test.d.ts +0 -5
  33. package/dist/api/features/__tests__/regression-prevention.test.js +0 -338
  34. package/dist/api/features/__tests__/status-updater.integration.test.d.ts +0 -5
  35. package/dist/api/features/__tests__/status-updater.integration.test.js +0 -497
  36. package/dist/commands/workflow/pipeline-runner.d.ts +0 -17
  37. package/dist/commands/workflow/pipeline-runner.js +0 -393
  38. package/dist/commands/workflow/runner.d.ts +0 -26
  39. package/dist/commands/workflow/runner.js +0 -119
  40. package/dist/commands/workflow/workflow-runner.d.ts +0 -26
  41. package/dist/commands/workflow/workflow-runner.js +0 -119
  42. package/dist/phases/code-implementation/analyzer-helpers.d.ts +0 -28
  43. package/dist/phases/code-implementation/analyzer-helpers.js +0 -177
  44. package/dist/phases/code-implementation/analyzer.d.ts +0 -32
  45. package/dist/phases/code-implementation/analyzer.js +0 -629
  46. package/dist/phases/code-implementation/context-fetcher.d.ts +0 -17
  47. package/dist/phases/code-implementation/context-fetcher.js +0 -86
  48. package/dist/phases/code-implementation/mcp-server.d.ts +0 -1
  49. package/dist/phases/code-implementation/mcp-server.js +0 -93
  50. package/dist/phases/code-implementation/prompts-improvement.d.ts +0 -5
  51. package/dist/phases/code-implementation/prompts-improvement.js +0 -108
  52. package/dist/phases/code-implementation-verification/verifier.d.ts +0 -31
  53. package/dist/phases/code-implementation-verification/verifier.js +0 -196
  54. package/dist/phases/code-refine/analyzer.d.ts +0 -41
  55. package/dist/phases/code-refine/analyzer.js +0 -561
  56. package/dist/phases/code-refine/context-fetcher.d.ts +0 -94
  57. package/dist/phases/code-refine/context-fetcher.js +0 -423
  58. package/dist/phases/code-refine-verification/analysis/llm-analyzer.d.ts +0 -22
  59. package/dist/phases/code-refine-verification/analysis/llm-analyzer.js +0 -134
  60. package/dist/phases/code-refine-verification/verifier.d.ts +0 -47
  61. package/dist/phases/code-refine-verification/verifier.js +0 -597
  62. package/dist/phases/code-review/analyzer.d.ts +0 -29
  63. package/dist/phases/code-review/analyzer.js +0 -363
  64. package/dist/phases/code-review/context-fetcher.d.ts +0 -92
  65. package/dist/phases/code-review/context-fetcher.js +0 -296
  66. package/dist/phases/feature-analysis/analyzer-helpers.d.ts +0 -10
  67. package/dist/phases/feature-analysis/analyzer-helpers.js +0 -47
  68. package/dist/phases/feature-analysis/analyzer.d.ts +0 -11
  69. package/dist/phases/feature-analysis/analyzer.js +0 -208
  70. package/dist/phases/feature-analysis/context-fetcher.d.ts +0 -26
  71. package/dist/phases/feature-analysis/context-fetcher.js +0 -134
  72. package/dist/phases/feature-analysis/http-fallback.d.ts +0 -20
  73. package/dist/phases/feature-analysis/http-fallback.js +0 -95
  74. package/dist/phases/feature-analysis/mcp-server.d.ts +0 -1
  75. package/dist/phases/feature-analysis/mcp-server.js +0 -144
  76. package/dist/phases/feature-analysis/prompts-improvement.d.ts +0 -8
  77. package/dist/phases/feature-analysis/prompts-improvement.js +0 -109
  78. package/dist/phases/feature-analysis-verification/verifier.d.ts +0 -37
  79. package/dist/phases/feature-analysis-verification/verifier.js +0 -147
  80. package/dist/phases/technical-design/analyzer-helpers.d.ts +0 -25
  81. package/dist/phases/technical-design/analyzer-helpers.js +0 -39
  82. package/dist/phases/technical-design/analyzer.d.ts +0 -21
  83. package/dist/phases/technical-design/analyzer.js +0 -461
  84. package/dist/phases/technical-design/context-fetcher.d.ts +0 -12
  85. package/dist/phases/technical-design/context-fetcher.js +0 -39
  86. package/dist/phases/technical-design/http-fallback.d.ts +0 -17
  87. package/dist/phases/technical-design/http-fallback.js +0 -151
  88. package/dist/phases/technical-design/mcp-server.d.ts +0 -1
  89. package/dist/phases/technical-design/mcp-server.js +0 -157
  90. package/dist/phases/technical-design/prompts-improvement.d.ts +0 -5
  91. package/dist/phases/technical-design/prompts-improvement.js +0 -93
  92. package/dist/phases/technical-design-verification/verifier.d.ts +0 -53
  93. package/dist/phases/technical-design-verification/verifier.js +0 -170
  94. package/dist/services/feature-branches.d.ts +0 -77
  95. package/dist/services/feature-branches.js +0 -205
  96. package/dist/workflow-runner/config/phase-configs.d.ts +0 -5
  97. package/dist/workflow-runner/config/phase-configs.js +0 -120
  98. package/dist/workflow-runner/core/feature-filter.d.ts +0 -16
  99. package/dist/workflow-runner/core/feature-filter.js +0 -46
  100. package/dist/workflow-runner/core/index.d.ts +0 -8
  101. package/dist/workflow-runner/core/index.js +0 -12
  102. package/dist/workflow-runner/core/pipeline-evaluator.d.ts +0 -24
  103. package/dist/workflow-runner/core/pipeline-evaluator.js +0 -32
  104. package/dist/workflow-runner/core/state-manager.d.ts +0 -24
  105. package/dist/workflow-runner/core/state-manager.js +0 -42
  106. package/dist/workflow-runner/core/workflow-logger.d.ts +0 -20
  107. package/dist/workflow-runner/core/workflow-logger.js +0 -65
  108. package/dist/workflow-runner/executors/phase-executor.d.ts +0 -8
  109. package/dist/workflow-runner/executors/phase-executor.js +0 -248
  110. package/dist/workflow-runner/feature-workflow-runner.d.ts +0 -26
  111. package/dist/workflow-runner/feature-workflow-runner.js +0 -119
  112. package/dist/workflow-runner/index.d.ts +0 -2
  113. package/dist/workflow-runner/index.js +0 -2
  114. package/dist/workflow-runner/pipeline-runner.d.ts +0 -17
  115. package/dist/workflow-runner/pipeline-runner.js +0 -393
  116. package/dist/workflow-runner/workflow-processor.d.ts +0 -54
  117. package/dist/workflow-runner/workflow-processor.js +0 -170
@@ -53,18 +53,18 @@ function getChangedFiles(baseRef, headRef) {
53
53
  /**
54
54
  * Determine the diff base ref for incremental re-runs
55
55
  * If existing PRs have last_synced_commit, use the earliest one
56
- * Otherwise use main
56
+ * Otherwise use origin/main (remote-tracking ref, always up-to-date after fetch)
57
57
  */
58
58
  function determineDiffBaseRef(existingPRs, replaceExisting) {
59
59
  if (replaceExisting || existingPRs.length === 0) {
60
- return 'main';
60
+ return 'origin/main';
61
61
  }
62
62
  // Find the minimum last_synced_commit (earliest sync point)
63
63
  const syncedCommits = existingPRs
64
64
  .map((pr) => pr.last_synced_commit)
65
65
  .filter((c) => c !== null);
66
66
  if (syncedCommits.length === 0) {
67
- return 'main';
67
+ return 'origin/main';
68
68
  }
69
69
  // All PRs should have been synced to the same commit
70
70
  // Use the first one (they should all be equal after a successful sync)
@@ -87,6 +87,22 @@ export async function fetchPRSplittingContext(featureId, verbose, replaceExistin
87
87
  getPullRequests({ featureId, verbose }).catch(() => []),
88
88
  getGitHubConfig(featureId, verbose),
89
89
  ]);
90
+ // Fetch latest remote refs (updates origin/main and all remote-tracking branches)
91
+ try {
92
+ const credArgs = buildCredentialArgs(githubConfig.token);
93
+ execFileSync('git', [...credArgs, 'fetch', 'origin'], {
94
+ encoding: 'utf-8',
95
+ stdio: 'pipe',
96
+ });
97
+ if (verbose) {
98
+ logInfo('✅ Fetched latest remote refs');
99
+ }
100
+ }
101
+ catch (error) {
102
+ if (verbose) {
103
+ logInfo(`⚠️ Could not fetch from origin: ${error instanceof Error ? error.message : String(error)}`);
104
+ }
105
+ }
90
106
  // Verify dev branch exists
91
107
  const localExists = branchExists(devBranchName);
92
108
  const remoteExists = !localExists && remoteBranchExists(devBranchName, githubConfig.token);
@@ -94,17 +110,6 @@ export async function fetchPRSplittingContext(featureId, verbose, replaceExistin
94
110
  throw new Error(`Development branch '${devBranchName}' does not exist. ` +
95
111
  `The feature must have code on the dev branch before PR splitting.`);
96
112
  }
97
- // If branch only exists on remote, fetch it (using credential helper)
98
- if (!localExists && remoteExists) {
99
- if (verbose) {
100
- logInfo(`Fetching remote branch ${devBranchName}...`);
101
- }
102
- const credArgs = buildCredentialArgs(githubConfig.token);
103
- execFileSync('git', [...credArgs, 'fetch', 'origin', devBranchName], {
104
- encoding: 'utf-8',
105
- stdio: 'pipe',
106
- });
107
- }
108
113
  const product = await getProduct(feature.product_id, verbose);
109
114
  // Detect fork status
110
115
  let forkInfo = { isFork: false };
@@ -131,7 +136,7 @@ export async function fetchPRSplittingContext(featureId, verbose, replaceExistin
131
136
  const baseRef = determineDiffBaseRef(existingPullRequests, replaceExisting);
132
137
  const devBranchHeadSha = getBranchHeadSha(devRef);
133
138
  // Check if there are new changes since last sync
134
- if (baseRef !== 'main' && baseRef === devBranchHeadSha) {
139
+ if (baseRef !== 'origin/main' && baseRef === devBranchHeadSha) {
135
140
  if (verbose) {
136
141
  logInfo(`No new changes since last sync (HEAD: ${devBranchHeadSha})`);
137
142
  }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Unit tests for phase quality criteria definitions
3
+ */
4
+ export {};
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Unit tests for phase quality criteria definitions
3
+ */
4
+ import { describe, it } from 'node:test';
5
+ import assert from 'node:assert';
6
+ import { DEFAULT_PHASE_CRITERIA, USER_STORIES_ANALYSIS_CRITERIA, TEST_CASES_ANALYSIS_CRITERIA, TECHNICAL_DESIGN_CRITERIA, BRANCH_PLANNING_CRITERIA, CODE_IMPLEMENTATION_CRITERIA, FUNCTIONAL_TESTING_CRITERIA, CODE_REVIEW_CRITERIA, getPhaseQualityCriteria, } from '../phase-criteria.js';
7
+ describe('Phase Quality Criteria', () => {
8
+ describe('DEFAULT_PHASE_CRITERIA', () => {
9
+ it('should cover all evaluable phases', () => {
10
+ const expectedPhases = [
11
+ 'user_stories_analysis',
12
+ 'test_cases_analysis',
13
+ 'technical_design',
14
+ 'branch_planning',
15
+ 'code_implementation',
16
+ 'functional_testing',
17
+ 'code_review',
18
+ ];
19
+ for (const phase of expectedPhases) {
20
+ assert.ok(phase in DEFAULT_PHASE_CRITERIA, `Should have criteria for phase: ${phase}`);
21
+ }
22
+ });
23
+ it('should have consistent phase names in criteria objects', () => {
24
+ for (const [key, criteria] of Object.entries(DEFAULT_PHASE_CRITERIA)) {
25
+ assert.strictEqual(criteria.phase, key, `Criteria for ${key} should have matching phase name`);
26
+ }
27
+ });
28
+ });
29
+ describe('Criteria Structural Validity', () => {
30
+ const allCriteria = [
31
+ USER_STORIES_ANALYSIS_CRITERIA,
32
+ TEST_CASES_ANALYSIS_CRITERIA,
33
+ TECHNICAL_DESIGN_CRITERIA,
34
+ BRANCH_PLANNING_CRITERIA,
35
+ CODE_IMPLEMENTATION_CRITERIA,
36
+ FUNCTIONAL_TESTING_CRITERIA,
37
+ CODE_REVIEW_CRITERIA,
38
+ ];
39
+ for (const phaseCriteria of allCriteria) {
40
+ describe(phaseCriteria.phase, () => {
41
+ it('should have advanceThreshold > escalateThreshold', () => {
42
+ assert.ok(phaseCriteria.advanceThreshold > phaseCriteria.escalateThreshold, `advanceThreshold (${phaseCriteria.advanceThreshold}) should be > escalateThreshold (${phaseCriteria.escalateThreshold})`);
43
+ });
44
+ it('should have thresholds in valid range (0-100)', () => {
45
+ assert.ok(phaseCriteria.advanceThreshold >= 0);
46
+ assert.ok(phaseCriteria.advanceThreshold <= 100);
47
+ assert.ok(phaseCriteria.escalateThreshold >= 0);
48
+ assert.ok(phaseCriteria.escalateThreshold <= 100);
49
+ });
50
+ it('should have maxAutoRetries >= 1', () => {
51
+ assert.ok(phaseCriteria.maxAutoRetries >= 1, `maxAutoRetries should be >= 1, got ${phaseCriteria.maxAutoRetries}`);
52
+ });
53
+ it('should have at least one criterion', () => {
54
+ assert.ok(phaseCriteria.criteria.length > 0, 'Should have at least one criterion');
55
+ });
56
+ it('should have criteria weights that approximately sum to 1', () => {
57
+ const totalWeight = phaseCriteria.criteria.reduce((sum, c) => sum + c.weight, 0);
58
+ assert.ok(Math.abs(totalWeight - 1.0) < 0.01, `Weights should sum to ~1.0, got ${totalWeight}`);
59
+ });
60
+ it('should have unique criterion IDs', () => {
61
+ const ids = phaseCriteria.criteria.map((c) => c.id);
62
+ const uniqueIds = new Set(ids);
63
+ assert.strictEqual(ids.length, uniqueIds.size, 'Criterion IDs should be unique');
64
+ });
65
+ it('should have valid criterion weights (0 < weight <= 1)', () => {
66
+ for (const criterion of phaseCriteria.criteria) {
67
+ assert.ok(criterion.weight > 0 && criterion.weight <= 1, `Weight for ${criterion.id} should be between 0 and 1, got ${criterion.weight}`);
68
+ }
69
+ });
70
+ it('should have valid minimum scores (0-100)', () => {
71
+ for (const criterion of phaseCriteria.criteria) {
72
+ assert.ok(criterion.minimumScore >= 0 && criterion.minimumScore <= 100, `minimumScore for ${criterion.id} should be 0-100, got ${criterion.minimumScore}`);
73
+ }
74
+ });
75
+ it('should have non-empty evaluation guidance', () => {
76
+ for (const criterion of phaseCriteria.criteria) {
77
+ assert.ok(criterion.evaluationGuidance.length > 0, `Criterion ${criterion.id} should have evaluation guidance`);
78
+ }
79
+ });
80
+ });
81
+ }
82
+ });
83
+ describe('getPhaseQualityCriteria', () => {
84
+ it('should return default criteria for known phases', () => {
85
+ const criteria = getPhaseQualityCriteria('user_stories_analysis');
86
+ assert.ok(criteria);
87
+ assert.strictEqual(criteria.phase, 'user_stories_analysis');
88
+ assert.strictEqual(criteria.advanceThreshold, USER_STORIES_ANALYSIS_CRITERIA.advanceThreshold);
89
+ });
90
+ it('should return null for unknown phases', () => {
91
+ const criteria = getPhaseQualityCriteria('nonexistent_phase');
92
+ assert.strictEqual(criteria, null);
93
+ });
94
+ it('should apply overrides when provided', () => {
95
+ const criteria = getPhaseQualityCriteria('user_stories_analysis', {
96
+ user_stories_analysis: {
97
+ advanceThreshold: 90,
98
+ maxAutoRetries: 5,
99
+ },
100
+ });
101
+ assert.ok(criteria);
102
+ assert.strictEqual(criteria.advanceThreshold, 90);
103
+ assert.strictEqual(criteria.maxAutoRetries, 5);
104
+ // Non-overridden values should remain from defaults
105
+ assert.strictEqual(criteria.escalateThreshold, USER_STORIES_ANALYSIS_CRITERIA.escalateThreshold);
106
+ });
107
+ it('should return defaults when override map does not include the phase', () => {
108
+ const criteria = getPhaseQualityCriteria('technical_design', {
109
+ user_stories_analysis: { advanceThreshold: 90 },
110
+ });
111
+ assert.ok(criteria);
112
+ assert.strictEqual(criteria.advanceThreshold, TECHNICAL_DESIGN_CRITERIA.advanceThreshold);
113
+ });
114
+ it('should override criteria array when provided', () => {
115
+ const customCriteria = [
116
+ {
117
+ id: 'custom_1',
118
+ name: 'Custom',
119
+ description: 'A custom criterion',
120
+ weight: 1.0,
121
+ minimumScore: 50,
122
+ evaluationGuidance: 'Custom guidance',
123
+ },
124
+ ];
125
+ const criteria = getPhaseQualityCriteria('technical_design', {
126
+ technical_design: { criteria: customCriteria },
127
+ });
128
+ assert.ok(criteria);
129
+ assert.strictEqual(criteria.criteria.length, 1);
130
+ assert.strictEqual(criteria.criteria[0].id, 'custom_1');
131
+ });
132
+ });
133
+ });
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Unit tests for lifecycle agent transition rules and decision logic
3
+ */
4
+ export {};
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Unit tests for lifecycle agent transition rules and decision logic
3
+ */
4
+ import { describe, it } from 'node:test';
5
+ import assert from 'node:assert';
6
+ import { DEFAULT_TRANSITION_RULES, getTransitionRule, getRerunExecutionMode, getVerificationStatus, calculateQualityScore, determineDecision, buildDecisionResult, DEFAULT_LIFECYCLE_AGENT_CONFIG, } from '../transition-rules.js';
7
+ // Helpers to create test fixtures
8
+ function makeCriteria(overrides) {
9
+ return {
10
+ phase: 'test_phase',
11
+ advanceThreshold: 75,
12
+ escalateThreshold: 40,
13
+ maxAutoRetries: 2,
14
+ criteria: [
15
+ {
16
+ id: 'c1',
17
+ name: 'Criterion 1',
18
+ description: 'Test criterion',
19
+ weight: 0.6,
20
+ minimumScore: 50,
21
+ evaluationGuidance: 'Evaluate this.',
22
+ },
23
+ {
24
+ id: 'c2',
25
+ name: 'Criterion 2',
26
+ description: 'Another test criterion',
27
+ weight: 0.4,
28
+ minimumScore: 50,
29
+ evaluationGuidance: 'Evaluate this too.',
30
+ },
31
+ ],
32
+ ...overrides,
33
+ };
34
+ }
35
+ function makeConfig(overrides) {
36
+ return {
37
+ ...DEFAULT_LIFECYCLE_AGENT_CONFIG,
38
+ ...overrides,
39
+ };
40
+ }
41
+ function makeEvaluation(overrides) {
42
+ return {
43
+ phase: 'user_stories_analysis',
44
+ featureId: 'feat-123',
45
+ qualityScore: 80,
46
+ decision: 'advance',
47
+ confidence: 85,
48
+ criteria: [],
49
+ reasoning: 'Test evaluation',
50
+ rerunCount: 0,
51
+ evaluatedAt: new Date().toISOString(),
52
+ ...overrides,
53
+ };
54
+ }
55
+ describe('Transition Rules', () => {
56
+ describe('DEFAULT_TRANSITION_RULES', () => {
57
+ it('should cover all major phases', () => {
58
+ const phases = DEFAULT_TRANSITION_RULES.map((r) => r.fromPhase);
59
+ const expectedPhases = [
60
+ 'user_stories_analysis',
61
+ 'test_cases_analysis',
62
+ 'technical_design',
63
+ 'branch_planning',
64
+ 'code_implementation',
65
+ 'functional_testing',
66
+ 'code_review',
67
+ ];
68
+ for (const phase of expectedPhases) {
69
+ assert.ok(phases.includes(phase), `Should have transition rule for phase: ${phase}`);
70
+ }
71
+ });
72
+ it('should form a connected chain from user_stories_analysis to code_refine', () => {
73
+ let current = 'user_stories_analysis';
74
+ const visited = [current];
75
+ while (true) {
76
+ const rule = DEFAULT_TRANSITION_RULES.find((r) => r.fromPhase === current);
77
+ if (!rule)
78
+ break;
79
+ current = rule.toPhase;
80
+ visited.push(current);
81
+ }
82
+ assert.ok(visited.length > 1, 'Should form a chain of transitions');
83
+ assert.strictEqual(visited[0], 'user_stories_analysis', 'Chain should start at user_stories_analysis');
84
+ assert.strictEqual(visited[visited.length - 1], 'code_refine', 'Chain should end at code_refine');
85
+ });
86
+ it('should have valid execution modes', () => {
87
+ for (const rule of DEFAULT_TRANSITION_RULES) {
88
+ assert.ok(typeof rule.executionMode === 'string', `Rule for ${rule.fromPhase} should have a string executionMode`);
89
+ assert.ok(rule.executionMode.length > 0, `Rule for ${rule.fromPhase} should have non-empty executionMode`);
90
+ }
91
+ });
92
+ });
93
+ describe('getTransitionRule', () => {
94
+ it('should return the correct rule for known phases', () => {
95
+ const rule = getTransitionRule('user_stories_analysis');
96
+ assert.ok(rule);
97
+ assert.strictEqual(rule.toPhase, 'test_cases_analysis');
98
+ assert.strictEqual(rule.executionMode, 'only_test_cases_analysis');
99
+ });
100
+ it('should return undefined for unknown phases', () => {
101
+ const rule = getTransitionRule('nonexistent_phase');
102
+ assert.strictEqual(rule, undefined);
103
+ });
104
+ it('should return correct transition for each phase', () => {
105
+ const expectations = {
106
+ user_stories_analysis: 'test_cases_analysis',
107
+ test_cases_analysis: 'technical_design',
108
+ technical_design: 'branch_planning',
109
+ branch_planning: 'code_implementation',
110
+ code_implementation: 'functional_testing',
111
+ functional_testing: 'code_review',
112
+ code_review: 'code_refine',
113
+ };
114
+ for (const [from, expectedTo] of Object.entries(expectations)) {
115
+ const rule = getTransitionRule(from);
116
+ assert.ok(rule, `Should have rule for ${from}`);
117
+ assert.strictEqual(rule.toPhase, expectedTo, `${from} should transition to ${expectedTo}`);
118
+ }
119
+ });
120
+ });
121
+ describe('getRerunExecutionMode', () => {
122
+ it('should return only_* mode for known phases', () => {
123
+ assert.strictEqual(getRerunExecutionMode('user_stories_analysis'), 'only_user_stories_analysis');
124
+ assert.strictEqual(getRerunExecutionMode('technical_design'), 'only_technical_design');
125
+ assert.strictEqual(getRerunExecutionMode('code_implementation'), 'only_code_implementation');
126
+ });
127
+ it('should return full_pipeline for unknown phases', () => {
128
+ assert.strictEqual(getRerunExecutionMode('nonexistent'), 'full_pipeline');
129
+ });
130
+ });
131
+ describe('getVerificationStatus', () => {
132
+ it('should return verification status for phases that have one', () => {
133
+ assert.strictEqual(getVerificationStatus('user_stories_analysis'), 'user_stories_analysis_verification');
134
+ assert.strictEqual(getVerificationStatus('technical_design'), 'technical_design_verification');
135
+ assert.strictEqual(getVerificationStatus('code_implementation'), 'code_implementation_verification');
136
+ });
137
+ it('should return undefined for phases without verification', () => {
138
+ assert.strictEqual(getVerificationStatus('functional_testing'), undefined);
139
+ assert.strictEqual(getVerificationStatus('code_review'), undefined);
140
+ });
141
+ });
142
+ });
143
+ describe('Quality Score Calculation', () => {
144
+ describe('calculateQualityScore', () => {
145
+ const criteria = makeCriteria();
146
+ it('should calculate weighted average correctly', () => {
147
+ const evaluations = [
148
+ {
149
+ criterionId: 'c1',
150
+ score: 80,
151
+ passed: true,
152
+ reasoning: 'Good',
153
+ },
154
+ {
155
+ criterionId: 'c2',
156
+ score: 60,
157
+ passed: true,
158
+ reasoning: 'Okay',
159
+ },
160
+ ];
161
+ const score = calculateQualityScore(evaluations, criteria);
162
+ // (80 * 0.6 + 60 * 0.4) / (0.6 + 0.4) = (48 + 24) / 1 = 72
163
+ assert.strictEqual(score, 72);
164
+ });
165
+ it('should return 0 for empty evaluations', () => {
166
+ const score = calculateQualityScore([], criteria);
167
+ assert.strictEqual(score, 0);
168
+ });
169
+ it('should ignore evaluations for unknown criteria', () => {
170
+ const evaluations = [
171
+ {
172
+ criterionId: 'c1',
173
+ score: 80,
174
+ passed: true,
175
+ reasoning: 'Good',
176
+ },
177
+ {
178
+ criterionId: 'unknown',
179
+ score: 10,
180
+ passed: false,
181
+ reasoning: 'Unknown',
182
+ },
183
+ ];
184
+ const score = calculateQualityScore(evaluations, criteria);
185
+ // Only c1 is counted: (80 * 0.6) / 0.6 = 80
186
+ assert.strictEqual(score, 80);
187
+ });
188
+ it('should handle perfect scores', () => {
189
+ const evaluations = [
190
+ {
191
+ criterionId: 'c1',
192
+ score: 100,
193
+ passed: true,
194
+ reasoning: 'Perfect',
195
+ },
196
+ {
197
+ criterionId: 'c2',
198
+ score: 100,
199
+ passed: true,
200
+ reasoning: 'Perfect',
201
+ },
202
+ ];
203
+ const score = calculateQualityScore(evaluations, criteria);
204
+ assert.strictEqual(score, 100);
205
+ });
206
+ it('should handle zero scores', () => {
207
+ const evaluations = [
208
+ {
209
+ criterionId: 'c1',
210
+ score: 0,
211
+ passed: false,
212
+ reasoning: 'Failed',
213
+ },
214
+ {
215
+ criterionId: 'c2',
216
+ score: 0,
217
+ passed: false,
218
+ reasoning: 'Failed',
219
+ },
220
+ ];
221
+ const score = calculateQualityScore(evaluations, criteria);
222
+ assert.strictEqual(score, 0);
223
+ });
224
+ });
225
+ });
226
+ describe('Decision Logic', () => {
227
+ describe('determineDecision', () => {
228
+ const criteria = makeCriteria();
229
+ const config = makeConfig();
230
+ it('should return advance when quality meets threshold', () => {
231
+ const decision = determineDecision(80, 85, 0, criteria, config);
232
+ assert.strictEqual(decision, 'advance');
233
+ });
234
+ it('should return escalate when quality is below escalate threshold', () => {
235
+ const decision = determineDecision(30, 85, 0, criteria, config);
236
+ assert.strictEqual(decision, 'escalate');
237
+ });
238
+ it('should return rerun when quality is between thresholds', () => {
239
+ const decision = determineDecision(55, 85, 0, criteria, config);
240
+ assert.strictEqual(decision, 'rerun');
241
+ });
242
+ it('should escalate when confidence is below threshold', () => {
243
+ const decision = determineDecision(80, 40, 0, criteria, config);
244
+ assert.strictEqual(decision, 'escalate');
245
+ });
246
+ it('should escalate when max retries exhausted', () => {
247
+ const decision = determineDecision(55, 85, 2, criteria, config);
248
+ assert.strictEqual(decision, 'escalate');
249
+ });
250
+ it('should use the lower of phase and config max retries', () => {
251
+ const strictConfig = makeConfig({ maxAutoRetries: 1 });
252
+ const decision = determineDecision(55, 85, 1, criteria, strictConfig);
253
+ assert.strictEqual(decision, 'escalate');
254
+ });
255
+ it('should advance at exactly the advance threshold', () => {
256
+ const decision = determineDecision(75, 85, 0, criteria, config);
257
+ assert.strictEqual(decision, 'advance');
258
+ });
259
+ it('should escalate at exactly the escalate threshold', () => {
260
+ // At exactly 40 (escalateThreshold), the score is NOT below it,
261
+ // so it falls to the rerun case
262
+ const decision = determineDecision(40, 85, 0, criteria, config);
263
+ assert.strictEqual(decision, 'rerun');
264
+ });
265
+ it('should escalate just below the escalate threshold', () => {
266
+ const decision = determineDecision(39, 85, 0, criteria, config);
267
+ assert.strictEqual(decision, 'escalate');
268
+ });
269
+ });
270
+ describe('buildDecisionResult', () => {
271
+ it('should include transition rule when advancing', () => {
272
+ const evaluation = makeEvaluation({
273
+ phase: 'user_stories_analysis',
274
+ decision: 'advance',
275
+ });
276
+ const result = buildDecisionResult(evaluation);
277
+ assert.ok(result.transition);
278
+ assert.strictEqual(result.transition.toPhase, 'test_cases_analysis');
279
+ assert.strictEqual(result.executionMode, 'only_test_cases_analysis');
280
+ assert.strictEqual(result.targetStatus, 'test_cases_analysis');
281
+ });
282
+ it('should include rerun execution mode when rerunning', () => {
283
+ const evaluation = makeEvaluation({
284
+ phase: 'technical_design',
285
+ decision: 'rerun',
286
+ });
287
+ const result = buildDecisionResult(evaluation);
288
+ assert.strictEqual(result.transition, undefined);
289
+ assert.strictEqual(result.executionMode, 'only_technical_design');
290
+ assert.strictEqual(result.targetStatus, undefined);
291
+ });
292
+ it('should not include execution mode when escalating', () => {
293
+ const evaluation = makeEvaluation({
294
+ phase: 'code_implementation',
295
+ decision: 'escalate',
296
+ });
297
+ const result = buildDecisionResult(evaluation);
298
+ assert.strictEqual(result.transition, undefined);
299
+ assert.strictEqual(result.executionMode, undefined);
300
+ assert.strictEqual(result.targetStatus, undefined);
301
+ });
302
+ it('should preserve the evaluation in the result', () => {
303
+ const evaluation = makeEvaluation({
304
+ phase: 'test_cases_analysis',
305
+ qualityScore: 60,
306
+ decision: 'rerun',
307
+ reasoning: 'Needs improvement',
308
+ });
309
+ const result = buildDecisionResult(evaluation);
310
+ assert.strictEqual(result.evaluation, evaluation);
311
+ assert.strictEqual(result.evaluation.qualityScore, 60);
312
+ assert.strictEqual(result.evaluation.reasoning, 'Needs improvement');
313
+ });
314
+ });
315
+ });
316
+ describe('Default Configuration', () => {
317
+ describe('DEFAULT_LIFECYCLE_AGENT_CONFIG', () => {
318
+ it('should be disabled by default', () => {
319
+ assert.strictEqual(DEFAULT_LIFECYCLE_AGENT_CONFIG.enabled, false);
320
+ });
321
+ it('should disable auto-advance by default', () => {
322
+ assert.strictEqual(DEFAULT_LIFECYCLE_AGENT_CONFIG.autoAdvanceEnabled, false);
323
+ });
324
+ it('should enable auto-rerun by default', () => {
325
+ assert.strictEqual(DEFAULT_LIFECYCLE_AGENT_CONFIG.autoRerunEnabled, true);
326
+ });
327
+ it('should have a reasonable confidence threshold', () => {
328
+ assert.ok(DEFAULT_LIFECYCLE_AGENT_CONFIG.confidenceThreshold >= 50);
329
+ assert.ok(DEFAULT_LIFECYCLE_AGENT_CONFIG.confidenceThreshold <= 80);
330
+ });
331
+ it('should have a reasonable max retries', () => {
332
+ assert.ok(DEFAULT_LIFECYCLE_AGENT_CONFIG.maxAutoRetries >= 1);
333
+ assert.ok(DEFAULT_LIFECYCLE_AGENT_CONFIG.maxAutoRetries <= 5);
334
+ });
335
+ });
336
+ });
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Lifecycle Agent Module
3
+ *
4
+ * AI-powered lifecycle management for product features.
5
+ * Evaluates phase outputs against quality criteria and determines
6
+ * whether to advance, re-run, or escalate to a human.
7
+ *
8
+ * This module provides:
9
+ * - Type definitions for lifecycle decisions
10
+ * - Quality criteria per phase
11
+ * - Transition rules engine
12
+ * - Core decision-making logic
13
+ *
14
+ * Usage:
15
+ * import {
16
+ * getPhaseQualityCriteria,
17
+ * determineDecision,
18
+ * buildDecisionResult,
19
+ * DEFAULT_LIFECYCLE_AGENT_CONFIG,
20
+ * } from './services/lifecycle-agent/index.js'
21
+ */
22
+ export type { LifecycleDecision, CriterionEvaluation, PhaseEvaluationResult, SuggestedFeedback, CriterionDefinition, PhaseQualityCriteria, TransitionRule, LifecycleAgentConfig, LifecycleAgentState, PhaseEvaluationInput, LifecycleDecisionResult, } from './types.js';
23
+ export { DEFAULT_PHASE_CRITERIA, USER_STORIES_ANALYSIS_CRITERIA, TEST_CASES_ANALYSIS_CRITERIA, TECHNICAL_DESIGN_CRITERIA, BRANCH_PLANNING_CRITERIA, CODE_IMPLEMENTATION_CRITERIA, FUNCTIONAL_TESTING_CRITERIA, CODE_REVIEW_CRITERIA, getPhaseQualityCriteria, } from './phase-criteria.js';
24
+ export { DEFAULT_TRANSITION_RULES, DEFAULT_LIFECYCLE_AGENT_CONFIG, getTransitionRule, getRerunExecutionMode, getVerificationStatus, calculateQualityScore, determineDecision, buildDecisionResult, } from './transition-rules.js';
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Lifecycle Agent Module
3
+ *
4
+ * AI-powered lifecycle management for product features.
5
+ * Evaluates phase outputs against quality criteria and determines
6
+ * whether to advance, re-run, or escalate to a human.
7
+ *
8
+ * This module provides:
9
+ * - Type definitions for lifecycle decisions
10
+ * - Quality criteria per phase
11
+ * - Transition rules engine
12
+ * - Core decision-making logic
13
+ *
14
+ * Usage:
15
+ * import {
16
+ * getPhaseQualityCriteria,
17
+ * determineDecision,
18
+ * buildDecisionResult,
19
+ * DEFAULT_LIFECYCLE_AGENT_CONFIG,
20
+ * } from './services/lifecycle-agent/index.js'
21
+ */
22
+ // Phase criteria
23
+ export { DEFAULT_PHASE_CRITERIA, USER_STORIES_ANALYSIS_CRITERIA, TEST_CASES_ANALYSIS_CRITERIA, TECHNICAL_DESIGN_CRITERIA, BRANCH_PLANNING_CRITERIA, CODE_IMPLEMENTATION_CRITERIA, FUNCTIONAL_TESTING_CRITERIA, CODE_REVIEW_CRITERIA, getPhaseQualityCriteria, } from './phase-criteria.js';
24
+ // Transition rules and decision logic
25
+ export { DEFAULT_TRANSITION_RULES, DEFAULT_LIFECYCLE_AGENT_CONFIG, getTransitionRule, getRerunExecutionMode, getVerificationStatus, calculateQualityScore, determineDecision, buildDecisionResult, } from './transition-rules.js';
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Phase Quality Criteria Definitions
3
+ *
4
+ * Defines the quality criteria the lifecycle agent uses to evaluate
5
+ * each phase's outputs. These criteria determine whether a phase
6
+ * should advance, be re-run, or be escalated to a human.
7
+ *
8
+ * Each phase has:
9
+ * - An advance threshold: minimum score to auto-advance
10
+ * - An escalate threshold: score below which to escalate to human
11
+ * - A set of weighted criteria for evaluation
12
+ * - A max retry count before escalation
13
+ */
14
+ import type { PhaseQualityCriteria } from './types.js';
15
+ /**
16
+ * User Stories Analysis quality criteria
17
+ * Evaluates whether generated user stories adequately capture the feature requirements
18
+ */
19
+ export declare const USER_STORIES_ANALYSIS_CRITERIA: PhaseQualityCriteria;
20
+ /**
21
+ * Test Cases Analysis quality criteria
22
+ * Evaluates whether generated test cases provide adequate coverage
23
+ */
24
+ export declare const TEST_CASES_ANALYSIS_CRITERIA: PhaseQualityCriteria;
25
+ /**
26
+ * Technical Design quality criteria
27
+ * Evaluates the technical design document quality
28
+ */
29
+ export declare const TECHNICAL_DESIGN_CRITERIA: PhaseQualityCriteria;
30
+ /**
31
+ * Branch Planning quality criteria
32
+ * Evaluates whether the feature is well-split into branches
33
+ */
34
+ export declare const BRANCH_PLANNING_CRITERIA: PhaseQualityCriteria;
35
+ /**
36
+ * Code Implementation quality criteria
37
+ * Evaluates the code output after implementation
38
+ */
39
+ export declare const CODE_IMPLEMENTATION_CRITERIA: PhaseQualityCriteria;
40
+ /**
41
+ * Functional Testing quality criteria
42
+ * Evaluates test execution results
43
+ */
44
+ export declare const FUNCTIONAL_TESTING_CRITERIA: PhaseQualityCriteria;
45
+ /**
46
+ * Code Review quality criteria
47
+ * Evaluates the AI code review output
48
+ */
49
+ export declare const CODE_REVIEW_CRITERIA: PhaseQualityCriteria;
50
+ /**
51
+ * All default phase criteria indexed by phase name
52
+ */
53
+ export declare const DEFAULT_PHASE_CRITERIA: Readonly<Record<string, PhaseQualityCriteria>>;
54
+ /**
55
+ * Get quality criteria for a phase, with optional overrides from config
56
+ */
57
+ export declare function getPhaseQualityCriteria(phase: string, overrides?: Partial<Record<string, Partial<PhaseQualityCriteria>>>): PhaseQualityCriteria | null;