edsger 0.29.1 → 0.29.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/dist/commands/agent-workflow/processor.js +38 -10
  2. package/dist/commands/workflow/feature-coordinator.js +5 -3
  3. package/dist/index.js +0 -0
  4. package/dist/phases/chat-processor/tools.js +35 -5
  5. package/dist/phases/feature-analysis/index.js +6 -6
  6. package/dist/phases/feature-analysis/outcome.d.ts +1 -1
  7. package/dist/phases/feature-analysis/outcome.js +8 -8
  8. package/dist/phases/test-cases-analysis/index.js +5 -5
  9. package/dist/phases/test-cases-analysis/outcome.js +3 -3
  10. package/dist/phases/user-stories-analysis/index.js +5 -5
  11. package/dist/phases/user-stories-analysis/outcome.js +4 -4
  12. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +4 -0
  13. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +133 -0
  14. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +4 -0
  15. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +336 -0
  16. package/dist/services/lifecycle-agent/index.d.ts +24 -0
  17. package/dist/services/lifecycle-agent/index.js +25 -0
  18. package/dist/services/lifecycle-agent/phase-criteria.d.ts +57 -0
  19. package/dist/services/lifecycle-agent/phase-criteria.js +335 -0
  20. package/dist/services/lifecycle-agent/transition-rules.d.ts +60 -0
  21. package/dist/services/lifecycle-agent/transition-rules.js +184 -0
  22. package/dist/services/lifecycle-agent/types.d.ts +190 -0
  23. package/dist/services/lifecycle-agent/types.js +12 -0
  24. package/dist/types/features.d.ts +2 -2
  25. package/dist/types/index.d.ts +2 -2
  26. package/package.json +1 -1
  27. package/.claude/settings.local.json +0 -28
  28. package/.env.local +0 -12
  29. package/dist/api/features/__tests__/regression-prevention.test.d.ts +0 -5
  30. package/dist/api/features/__tests__/regression-prevention.test.js +0 -338
  31. package/dist/api/features/__tests__/status-updater.integration.test.d.ts +0 -5
  32. package/dist/api/features/__tests__/status-updater.integration.test.js +0 -497
  33. package/dist/commands/workflow/pipeline-runner.d.ts +0 -17
  34. package/dist/commands/workflow/pipeline-runner.js +0 -393
  35. package/dist/commands/workflow/runner.d.ts +0 -26
  36. package/dist/commands/workflow/runner.js +0 -119
  37. package/dist/commands/workflow/workflow-runner.d.ts +0 -26
  38. package/dist/commands/workflow/workflow-runner.js +0 -119
  39. package/dist/phases/code-implementation/analyzer-helpers.d.ts +0 -28
  40. package/dist/phases/code-implementation/analyzer-helpers.js +0 -177
  41. package/dist/phases/code-implementation/analyzer.d.ts +0 -32
  42. package/dist/phases/code-implementation/analyzer.js +0 -629
  43. package/dist/phases/code-implementation/context-fetcher.d.ts +0 -17
  44. package/dist/phases/code-implementation/context-fetcher.js +0 -86
  45. package/dist/phases/code-implementation/mcp-server.d.ts +0 -1
  46. package/dist/phases/code-implementation/mcp-server.js +0 -93
  47. package/dist/phases/code-implementation/prompts-improvement.d.ts +0 -5
  48. package/dist/phases/code-implementation/prompts-improvement.js +0 -108
  49. package/dist/phases/code-implementation-verification/verifier.d.ts +0 -31
  50. package/dist/phases/code-implementation-verification/verifier.js +0 -196
  51. package/dist/phases/code-refine/analyzer.d.ts +0 -41
  52. package/dist/phases/code-refine/analyzer.js +0 -561
  53. package/dist/phases/code-refine/context-fetcher.d.ts +0 -94
  54. package/dist/phases/code-refine/context-fetcher.js +0 -423
  55. package/dist/phases/code-refine-verification/analysis/llm-analyzer.d.ts +0 -22
  56. package/dist/phases/code-refine-verification/analysis/llm-analyzer.js +0 -134
  57. package/dist/phases/code-refine-verification/verifier.d.ts +0 -47
  58. package/dist/phases/code-refine-verification/verifier.js +0 -597
  59. package/dist/phases/code-review/analyzer.d.ts +0 -29
  60. package/dist/phases/code-review/analyzer.js +0 -363
  61. package/dist/phases/code-review/context-fetcher.d.ts +0 -92
  62. package/dist/phases/code-review/context-fetcher.js +0 -296
  63. package/dist/phases/feature-analysis/analyzer-helpers.d.ts +0 -10
  64. package/dist/phases/feature-analysis/analyzer-helpers.js +0 -47
  65. package/dist/phases/feature-analysis/analyzer.d.ts +0 -11
  66. package/dist/phases/feature-analysis/analyzer.js +0 -208
  67. package/dist/phases/feature-analysis/context-fetcher.d.ts +0 -26
  68. package/dist/phases/feature-analysis/context-fetcher.js +0 -134
  69. package/dist/phases/feature-analysis/http-fallback.d.ts +0 -20
  70. package/dist/phases/feature-analysis/http-fallback.js +0 -95
  71. package/dist/phases/feature-analysis/mcp-server.d.ts +0 -1
  72. package/dist/phases/feature-analysis/mcp-server.js +0 -144
  73. package/dist/phases/feature-analysis/prompts-improvement.d.ts +0 -8
  74. package/dist/phases/feature-analysis/prompts-improvement.js +0 -109
  75. package/dist/phases/feature-analysis-verification/verifier.d.ts +0 -37
  76. package/dist/phases/feature-analysis-verification/verifier.js +0 -147
  77. package/dist/phases/technical-design/analyzer-helpers.d.ts +0 -25
  78. package/dist/phases/technical-design/analyzer-helpers.js +0 -39
  79. package/dist/phases/technical-design/analyzer.d.ts +0 -21
  80. package/dist/phases/technical-design/analyzer.js +0 -461
  81. package/dist/phases/technical-design/context-fetcher.d.ts +0 -12
  82. package/dist/phases/technical-design/context-fetcher.js +0 -39
  83. package/dist/phases/technical-design/http-fallback.d.ts +0 -17
  84. package/dist/phases/technical-design/http-fallback.js +0 -151
  85. package/dist/phases/technical-design/mcp-server.d.ts +0 -1
  86. package/dist/phases/technical-design/mcp-server.js +0 -157
  87. package/dist/phases/technical-design/prompts-improvement.d.ts +0 -5
  88. package/dist/phases/technical-design/prompts-improvement.js +0 -93
  89. package/dist/phases/technical-design-verification/verifier.d.ts +0 -53
  90. package/dist/phases/technical-design-verification/verifier.js +0 -170
  91. package/dist/services/feature-branches.d.ts +0 -77
  92. package/dist/services/feature-branches.js +0 -205
  93. package/dist/workflow-runner/config/phase-configs.d.ts +0 -5
  94. package/dist/workflow-runner/config/phase-configs.js +0 -120
  95. package/dist/workflow-runner/core/feature-filter.d.ts +0 -16
  96. package/dist/workflow-runner/core/feature-filter.js +0 -46
  97. package/dist/workflow-runner/core/index.d.ts +0 -8
  98. package/dist/workflow-runner/core/index.js +0 -12
  99. package/dist/workflow-runner/core/pipeline-evaluator.d.ts +0 -24
  100. package/dist/workflow-runner/core/pipeline-evaluator.js +0 -32
  101. package/dist/workflow-runner/core/state-manager.d.ts +0 -24
  102. package/dist/workflow-runner/core/state-manager.js +0 -42
  103. package/dist/workflow-runner/core/workflow-logger.d.ts +0 -20
  104. package/dist/workflow-runner/core/workflow-logger.js +0 -65
  105. package/dist/workflow-runner/executors/phase-executor.d.ts +0 -8
  106. package/dist/workflow-runner/executors/phase-executor.js +0 -248
  107. package/dist/workflow-runner/feature-workflow-runner.d.ts +0 -26
  108. package/dist/workflow-runner/feature-workflow-runner.js +0 -119
  109. package/dist/workflow-runner/index.d.ts +0 -2
  110. package/dist/workflow-runner/index.js +0 -2
  111. package/dist/workflow-runner/pipeline-runner.d.ts +0 -17
  112. package/dist/workflow-runner/pipeline-runner.js +0 -393
  113. package/dist/workflow-runner/workflow-processor.d.ts +0 -54
  114. package/dist/workflow-runner/workflow-processor.js +0 -170
@@ -185,11 +185,36 @@ export class AgentWorkflowProcessor {
185
185
  }
186
186
  return;
187
187
  }
188
- // Filter out features already being processed
189
- const newFeatures = features.filter((f) => !this.activeWorkers.has(f.id));
188
+ // Filter out features already being processed or recently failed
189
+ const COOLDOWN_MS = 5 * 60 * 1000; // 5 minutes between retries
190
+ const newFeatures = features.filter((f) => {
191
+ if (this.activeWorkers.has(f.id))
192
+ return false;
193
+ const state = this.processedFeatures.get(f.id);
194
+ if (state?.status === 'failed') {
195
+ // Allow retry if feature was updated after our last attempt (user intervention)
196
+ if (f.updated_at && new Date(f.updated_at) > state.lastAttempt)
197
+ return true;
198
+ // Skip if max retries exceeded
199
+ if (state.retryCount >= this.options.maxRetries)
200
+ return false;
201
+ // Skip if within cooldown period
202
+ if (Date.now() - state.lastAttempt.getTime() < COOLDOWN_MS)
203
+ return false;
204
+ }
205
+ return true;
206
+ });
190
207
  if (newFeatures.length === 0) {
208
+ // Log skipped features for debugging
209
+ const skippedCount = features.filter((f) => {
210
+ const state = this.processedFeatures.get(f.id);
211
+ return state?.status === 'failed' && state.retryCount >= this.options.maxRetries;
212
+ }).length;
213
+ if (skippedCount > 0) {
214
+ logInfo(`${skippedCount} feature(s) skipped (max retries exceeded)`);
215
+ }
191
216
  if (this.options.verbose) {
192
- logInfo('All available features already being processed');
217
+ logInfo('No features available for processing');
193
218
  }
194
219
  return;
195
220
  }
@@ -329,13 +354,16 @@ export class AgentWorkflowProcessor {
329
354
  else {
330
355
  this.processedFeatures = updateFeatureState(this.processedFeatures, featureId, (currentState) => createFailedState(featureId, currentState));
331
356
  logError(`Feature failed: ${featureName}${error ? ` - ${error}` : ''}`);
332
- // Notify chat worker about the failure
333
- this.notifyChatWorker({
334
- type: 'event:phase_failed',
335
- featureId,
336
- phase: 'workflow',
337
- error: error || 'Unknown error',
338
- });
357
+ // Only notify chat on first failure to avoid flooding with duplicate messages
358
+ const failedState = this.processedFeatures.get(featureId);
359
+ if (failedState && failedState.retryCount <= 1) {
360
+ this.notifyChatWorker({
361
+ type: 'event:phase_failed',
362
+ featureId,
363
+ phase: 'workflow',
364
+ error: error || 'Unknown error',
365
+ });
366
+ }
339
367
  }
340
368
  // Clear heartbeat feature info
341
369
  sendHeartbeat().catch(() => { });
@@ -47,7 +47,9 @@ function getPendingPhases(workflow) {
47
47
  */
48
48
  async function executePhase(phaseName, options, config) {
49
49
  const { featureId, verbose } = options;
50
- const phaseRunner = PHASE_RUNNERS[phaseName];
50
+ // Normalize phase name: kebab-case → snake_case (e.g., code-implementation → code_implementation)
51
+ const normalizedName = phaseName.replace(/-/g, '_');
52
+ const phaseRunner = PHASE_RUNNERS[normalizedName];
51
53
  if (!phaseRunner) {
52
54
  logWarning(`Unknown workflow phase: ${phaseName}, skipping`);
53
55
  return {
@@ -58,7 +60,7 @@ async function executePhase(phaseName, options, config) {
58
60
  };
59
61
  }
60
62
  if (verbose) {
61
- logInfo(`\n🎯 Executing phase: ${phaseName}`);
63
+ logInfo(`\n🎯 Executing phase: ${normalizedName}`);
62
64
  }
63
65
  // Execute the phase
64
66
  const result = await phaseRunner(options, config);
@@ -66,7 +68,7 @@ async function executePhase(phaseName, options, config) {
66
68
  logPhaseResult(result, verbose);
67
69
  // Mark workflow phase as completed if successful
68
70
  if (result.status === 'success') {
69
- await markWorkflowPhaseCompleted(featureId, phaseName, verbose);
71
+ await markWorkflowPhaseCompleted(featureId, normalizedName, verbose);
70
72
  }
71
73
  return result;
72
74
  }
package/dist/index.js CHANGED
File without changes
@@ -32,7 +32,32 @@ export function createChatMcpServer() {
32
32
  }),
33
33
  tool('update_execution_mode', 'Change the feature execution mode (e.g., from_user_stories_analysis, from_code_implementation).', {
34
34
  feature_id: z.string().describe('Feature ID'),
35
- execution_mode: z.string().describe('New execution mode'),
35
+ execution_mode: z.enum([
36
+ 'full_pipeline',
37
+ 'only_feature_analysis',
38
+ 'only_user_stories_analysis',
39
+ 'only_test_cases_analysis',
40
+ 'only_technical_design',
41
+ 'only_branch_planning',
42
+ 'only_code_implementation',
43
+ 'only_pr_splitting',
44
+ 'only_pr_execution',
45
+ 'only_functional_testing',
46
+ 'only_code_refine',
47
+ 'only_code_review',
48
+ 'from_feature_analysis',
49
+ 'from_user_stories_analysis',
50
+ 'from_test_cases_analysis',
51
+ 'from_technical_design',
52
+ 'from_branch_planning',
53
+ 'from_code_implementation',
54
+ 'from_pr_splitting',
55
+ 'from_pr_execution',
56
+ 'from_functional_testing',
57
+ 'from_code_review',
58
+ 'custom',
59
+ 'autonomous',
60
+ ]).describe('New execution mode'),
36
61
  }, async (args) => {
37
62
  const result = await callMcpEndpoint('features/update', {
38
63
  feature_id: args.feature_id,
@@ -42,18 +67,23 @@ export function createChatMcpServer() {
42
67
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
43
68
  };
44
69
  }),
45
- tool('update_workflow', 'Modify workflow phases — reset phases to pending, skip phases, or reorder.', {
70
+ tool('update_workflow', 'Modify workflow phases — reset phases to pending, skip phases, or reorder. Phase names must use snake_case (e.g., code_implementation, NOT code-implementation).', {
46
71
  feature_id: z.string().describe('Feature ID'),
47
72
  workflow: z
48
73
  .array(z.object({
49
- phase: z.string(),
74
+ phase: z.string().describe('Phase name in snake_case. Valid phases: feature_analysis, user_stories_analysis, test_cases_analysis, technical_design, branch_planning, code_implementation, pr_splitting, pr_execution, functional_testing, code_review, code_refine, autonomous'),
50
75
  status: z.enum(['pending', 'completed', 'skipped']),
51
76
  }))
52
77
  .describe('Updated workflow array'),
53
78
  }, async (args) => {
79
+ // Normalize phase names: kebab-case → snake_case
80
+ const normalizedWorkflow = args.workflow.map((p) => ({
81
+ ...p,
82
+ phase: p.phase.replace(/-/g, '_'),
83
+ }));
54
84
  const result = await callMcpEndpoint('features/update', {
55
85
  feature_id: args.feature_id,
56
- workflow: args.workflow,
86
+ workflow: normalizedWorkflow,
57
87
  });
58
88
  return {
59
89
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
@@ -72,7 +102,7 @@ export function createChatMcpServer() {
72
102
  .describe('Required for update/delete'),
73
103
  title: z.string().optional(),
74
104
  description: z.string().optional(),
75
- status: z.enum(['draft', 'ready', 'approved']).optional(),
105
+ status: z.enum(['draft', 'pending_approval', 'approved']).optional(),
76
106
  })),
77
107
  }, async (args) => {
78
108
  const results = [];
@@ -11,7 +11,7 @@ export const analyseFeature = async (options, config, checklistContext) => {
11
11
  logInfo(`Starting feature analysis for feature ID: ${featureId}`);
12
12
  }
13
13
  try {
14
- // Reset ready artifacts to draft so AI can manage them on re-run
14
+ // Reset pending_approval artifacts to draft so AI can manage them on re-run
15
15
  const resetResult = await resetReadyArtifactsToDraft(featureId, verbose);
16
16
  if (verbose && (resetResult.resetUserStories > 0 || resetResult.resetTestCases > 0)) {
17
17
  logInfo(`✅ Reset ${resetResult.resetUserStories} user stories and ${resetResult.resetTestCases} test cases to draft for re-analysis`);
@@ -86,10 +86,10 @@ export const analyseFeature = async (options, config, checklistContext) => {
86
86
  // Perform verification cycle
87
87
  const verificationCycle = await performVerificationCycle(structuredAnalysisResult, checklistContext || null, context.featureContext, config, currentIteration, maxIterations, featureId, verbose);
88
88
  verificationResult = verificationCycle.verificationResult;
89
- // If verification passed, update ALL remaining draft artifacts to ready and exit
89
+ // If verification passed, update ALL remaining draft artifacts to pending_approval and exit
90
90
  if (verificationCycle.passed) {
91
91
  if (verbose) {
92
- logInfo('✅ Verification passed! Updating all draft artifacts to ready status...');
92
+ logInfo('✅ Verification passed! Updating all draft artifacts to pending_approval status...');
93
93
  }
94
94
  const allDrafts = await getAllDraftArtifactIds(featureId, verbose);
95
95
  await updateArtifactsToReady(allDrafts.userStoryIds, allDrafts.testCaseIds, verbose);
@@ -118,7 +118,7 @@ export const analyseFeature = async (options, config, checklistContext) => {
118
118
  if (!structuredAnalysisResult) {
119
119
  throw new Error('No analysis results received');
120
120
  }
121
- // If no checklist was used, update all draft artifacts to ready now
121
+ // If no checklist was used, update all draft artifacts to pending_approval now
122
122
  if (!checklistContext ||
123
123
  checklistContext.checklists.length === 0 ||
124
124
  !verificationResult) {
@@ -126,7 +126,7 @@ export const analyseFeature = async (options, config, checklistContext) => {
126
126
  if (allDrafts.userStoryIds.length > 0 ||
127
127
  allDrafts.testCaseIds.length > 0) {
128
128
  if (verbose) {
129
- logInfo('✅ No checklist verification needed. Updating all draft artifacts to ready status...');
129
+ logInfo('✅ No checklist verification needed. Updating all draft artifacts to pending_approval status...');
130
130
  }
131
131
  await updateArtifactsToReady(allDrafts.userStoryIds, allDrafts.testCaseIds, verbose);
132
132
  }
@@ -143,7 +143,7 @@ export const analyseFeature = async (options, config, checklistContext) => {
143
143
  throw new Error(`Checklist verification failed after ${currentIteration} iterations`);
144
144
  }
145
145
  // Return success result
146
- // Note: Artifacts have already been saved and updated to 'ready' status (if verification passed)
146
+ // Note: Artifacts have already been saved and updated to 'pending_approval' status (if verification passed)
147
147
  // or remain as draft (if verification failed)
148
148
  return buildAnalysisResult(featureId, context.featureContext, structuredAnalysisResult, currentIteration);
149
149
  }
@@ -24,7 +24,7 @@ export declare function deleteArtifacts(userStoryIds: string[], testCaseIds: str
24
24
  */
25
25
  export declare function deleteSpecificArtifacts(featureId: string, deletedUserStoryIds: string[], deletedTestCaseIds: string[], deletionReasons: Record<string, string>, verbose?: boolean): Promise<void>;
26
26
  /**
27
- * Update artifacts to ready status after successful verification
27
+ * Update artifacts to pending_approval status after successful verification
28
28
  */
29
29
  export declare function updateArtifactsToReady(userStoryIds: string[], testCaseIds: string[], verbose?: boolean): Promise<void>;
30
30
  /**
@@ -10,10 +10,10 @@ export async function resetReadyArtifactsToDraft(featureId, verbose) {
10
10
  getTestCases(featureId, false),
11
11
  ]);
12
12
  const readyStoryIds = stories
13
- .filter((s) => s.status === 'ready')
13
+ .filter((s) => s.status === 'pending_approval')
14
14
  .map((s) => s.id);
15
15
  const readyTestCaseIds = testCases
16
- .filter((tc) => tc.status === 'ready')
16
+ .filter((tc) => tc.status === 'pending_approval')
17
17
  .map((tc) => tc.id);
18
18
  if (readyStoryIds.length > 0) {
19
19
  if (verbose) {
@@ -144,20 +144,20 @@ export async function deleteSpecificArtifacts(featureId, deletedUserStoryIds, de
144
144
  }
145
145
  }
146
146
  /**
147
- * Update artifacts to ready status after successful verification
147
+ * Update artifacts to pending_approval status after successful verification
148
148
  */
149
149
  export async function updateArtifactsToReady(userStoryIds, testCaseIds, verbose) {
150
150
  if (userStoryIds.length > 0) {
151
151
  if (verbose) {
152
- logInfo(`Updating ${userStoryIds.length} user stories from draft to ready...`);
152
+ logInfo(`Updating ${userStoryIds.length} user stories from draft to pending_approval...`);
153
153
  }
154
- await batchUpdateUserStoryStatus(userStoryIds, 'ready', verbose);
154
+ await batchUpdateUserStoryStatus(userStoryIds, 'pending_approval', verbose);
155
155
  }
156
156
  if (testCaseIds.length > 0) {
157
157
  if (verbose) {
158
- logInfo(`Updating ${testCaseIds.length} test cases from draft to ready...`);
158
+ logInfo(`Updating ${testCaseIds.length} test cases from draft to pending_approval...`);
159
159
  }
160
- await batchUpdateTestCaseStatus(testCaseIds, 'ready', verbose);
160
+ await batchUpdateTestCaseStatus(testCaseIds, 'pending_approval', verbose);
161
161
  }
162
162
  }
163
163
  /**
@@ -230,7 +230,7 @@ export function buildAnalysisResult(featureId, context, structuredAnalysisResult
230
230
  featureInfo: context.feature,
231
231
  existingUserStories: context.existing_user_stories.map((story) => ({
232
232
  ...story,
233
- status: (story.status === 'ready' ? 'ready' : story.status === 'approved' ? 'approved' : 'draft'),
233
+ status: (story.status === 'pending_approval' ? 'pending_approval' : story.status === 'approved' ? 'approved' : 'draft'),
234
234
  created_at: story.created_at || new Date().toISOString(),
235
235
  updated_at: story.updated_at || new Date().toISOString(),
236
236
  })),
@@ -11,10 +11,10 @@ export const analyseTestCases = async (options, config, checklistContext) => {
11
11
  logInfo(`Starting test cases analysis for feature ID: ${featureId}`);
12
12
  }
13
13
  try {
14
- // Reset ready test cases to draft so AI can manage them on re-run
14
+ // Reset pending_approval test cases to draft so AI can manage them on re-run
15
15
  const resetCount = await resetReadyTestCasesToDraft(featureId, verbose);
16
16
  if (verbose && resetCount > 0) {
17
- logInfo(`✅ Reset ${resetCount} ready test cases to draft for re-analysis`);
17
+ logInfo(`✅ Reset ${resetCount} pending_approval test cases to draft for re-analysis`);
18
18
  }
19
19
  const context = await prepareTestCasesAnalysisContext(featureId, checklistContext, verbose);
20
20
  const systemPrompt = createTestCasesAnalysisSystemPrompt();
@@ -83,7 +83,7 @@ export const analyseTestCases = async (options, config, checklistContext) => {
83
83
  verificationResult = verificationCycle.verificationResult;
84
84
  if (verificationCycle.passed) {
85
85
  if (verbose) {
86
- logInfo('✅ Verification passed! Updating all draft test cases to ready status...');
86
+ logInfo('✅ Verification passed! Updating all draft test cases to pending_approval status...');
87
87
  }
88
88
  // Update ALL remaining draft test cases (both kept old ones and newly created)
89
89
  const allDraftIds = await getAllDraftTestCaseIds(featureId, verbose);
@@ -107,14 +107,14 @@ export const analyseTestCases = async (options, config, checklistContext) => {
107
107
  if (!structuredAnalysisResult) {
108
108
  throw new Error('No analysis results received');
109
109
  }
110
- // If no checklist was used, update all draft artifacts to ready
110
+ // If no checklist was used, update all draft artifacts to pending_approval
111
111
  if (!checklistContext ||
112
112
  checklistContext.checklists.length === 0 ||
113
113
  !verificationResult) {
114
114
  const allDraftIds = await getAllDraftTestCaseIds(featureId, verbose);
115
115
  if (allDraftIds.length > 0) {
116
116
  if (verbose) {
117
- logInfo('✅ No checklist verification needed. Updating all draft test cases to ready status...');
117
+ logInfo('✅ No checklist verification needed. Updating all draft test cases to pending_approval status...');
118
118
  }
119
119
  await updateTestCasesToReady(allDraftIds, verbose);
120
120
  }
@@ -4,7 +4,7 @@ import { callMcpEndpoint } from '../../api/mcp-client.js';
4
4
  export async function resetReadyTestCasesToDraft(featureId, verbose) {
5
5
  const testCases = await getTestCases(featureId, false);
6
6
  const readyIds = testCases
7
- .filter((tc) => tc.status === 'ready')
7
+ .filter((tc) => tc.status === 'pending_approval')
8
8
  .map((tc) => tc.id);
9
9
  if (readyIds.length === 0) {
10
10
  return 0;
@@ -70,9 +70,9 @@ export async function deleteSpecificTestCases(featureId, deletedTestCaseIds, del
70
70
  export async function updateTestCasesToReady(testCaseIds, verbose) {
71
71
  if (testCaseIds.length > 0) {
72
72
  if (verbose) {
73
- logInfo(`Updating ${testCaseIds.length} test cases from draft to ready...`);
73
+ logInfo(`Updating ${testCaseIds.length} test cases from draft to pending_approval...`);
74
74
  }
75
- await batchUpdateTestCaseStatus(testCaseIds, 'ready', verbose);
75
+ await batchUpdateTestCaseStatus(testCaseIds, 'pending_approval', verbose);
76
76
  }
77
77
  }
78
78
  export async function saveTestCasesAsDraft(featureId, created_test_cases, verbose) {
@@ -11,10 +11,10 @@ export const analyseUserStories = async (options, config, checklistContext) => {
11
11
  logInfo(`Starting user stories analysis for feature ID: ${featureId}`);
12
12
  }
13
13
  try {
14
- // Reset ready user stories to draft so AI can manage them on re-run
14
+ // Reset pending_approval user stories to draft so AI can manage them on re-run
15
15
  const resetCount = await resetReadyUserStoriesToDraft(featureId, verbose);
16
16
  if (verbose && resetCount > 0) {
17
- logInfo(`✅ Reset ${resetCount} ready user stories to draft for re-analysis`);
17
+ logInfo(`✅ Reset ${resetCount} pending_approval user stories to draft for re-analysis`);
18
18
  }
19
19
  const context = await prepareUserStoriesAnalysisContext(featureId, checklistContext, verbose);
20
20
  const systemPrompt = createUserStoriesAnalysisSystemPrompt();
@@ -83,7 +83,7 @@ export const analyseUserStories = async (options, config, checklistContext) => {
83
83
  verificationResult = verificationCycle.verificationResult;
84
84
  if (verificationCycle.passed) {
85
85
  if (verbose) {
86
- logInfo('✅ Verification passed! Updating all draft user stories to ready status...');
86
+ logInfo('✅ Verification passed! Updating all draft user stories to pending_approval status...');
87
87
  }
88
88
  // Update ALL remaining draft stories (both kept old ones and newly created)
89
89
  const allDraftIds = await getAllDraftUserStoryIds(featureId, verbose);
@@ -107,14 +107,14 @@ export const analyseUserStories = async (options, config, checklistContext) => {
107
107
  if (!structuredAnalysisResult) {
108
108
  throw new Error('No analysis results received');
109
109
  }
110
- // If no checklist was used, update all draft artifacts to ready
110
+ // If no checklist was used, update all draft artifacts to pending_approval
111
111
  if (!checklistContext ||
112
112
  checklistContext.checklists.length === 0 ||
113
113
  !verificationResult) {
114
114
  const allDraftIds = await getAllDraftUserStoryIds(featureId, verbose);
115
115
  if (allDraftIds.length > 0) {
116
116
  if (verbose) {
117
- logInfo('✅ No checklist verification needed. Updating all draft user stories to ready status...');
117
+ logInfo('✅ No checklist verification needed. Updating all draft user stories to pending_approval status...');
118
118
  }
119
119
  await updateUserStoriesToReady(allDraftIds, verbose);
120
120
  }
@@ -4,7 +4,7 @@ import { callMcpEndpoint } from '../../api/mcp-client.js';
4
4
  export async function resetReadyUserStoriesToDraft(featureId, verbose) {
5
5
  const stories = await getUserStories(featureId, false);
6
6
  const readyStoryIds = stories
7
- .filter((story) => story.status === 'ready')
7
+ .filter((story) => story.status === 'pending_approval')
8
8
  .map((story) => story.id);
9
9
  if (readyStoryIds.length === 0) {
10
10
  return 0;
@@ -70,9 +70,9 @@ export async function deleteSpecificUserStories(featureId, deletedUserStoryIds,
70
70
  export async function updateUserStoriesToReady(userStoryIds, verbose) {
71
71
  if (userStoryIds.length > 0) {
72
72
  if (verbose) {
73
- logInfo(`Updating ${userStoryIds.length} user stories from draft to ready...`);
73
+ logInfo(`Updating ${userStoryIds.length} user stories from draft to pending_approval...`);
74
74
  }
75
- await batchUpdateUserStoryStatus(userStoryIds, 'ready', verbose);
75
+ await batchUpdateUserStoryStatus(userStoryIds, 'pending_approval', verbose);
76
76
  }
77
77
  }
78
78
  export async function saveUserStoriesAsDraft(featureId, created_user_stories, verbose) {
@@ -111,7 +111,7 @@ export function buildUserStoriesAnalysisResult(featureId, context, structuredAna
111
111
  featureInfo: context.feature,
112
112
  existingUserStories: context.existing_user_stories.map((story) => ({
113
113
  ...story,
114
- status: (story.status === 'ready' ? 'ready' : 'draft'),
114
+ status: (story.status === 'pending_approval' ? 'pending_approval' : 'draft'),
115
115
  created_at: story.created_at || new Date().toISOString(),
116
116
  updated_at: story.updated_at || new Date().toISOString(),
117
117
  })),
@@ -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 {};