edsger 0.29.0 → 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.
- package/dist/commands/agent-workflow/processor.js +38 -10
- package/dist/commands/workflow/feature-coordinator.js +5 -3
- package/dist/index.js +0 -0
- package/dist/phases/chat-processor/prompts.d.ts +1 -1
- package/dist/phases/chat-processor/prompts.js +24 -1
- package/dist/phases/chat-processor/tools.js +40 -5
- package/dist/phases/feature-analysis/index.js +6 -6
- package/dist/phases/feature-analysis/outcome.d.ts +1 -1
- package/dist/phases/feature-analysis/outcome.js +8 -8
- package/dist/phases/test-cases-analysis/index.js +5 -5
- package/dist/phases/test-cases-analysis/outcome.js +3 -3
- package/dist/phases/user-stories-analysis/index.js +5 -5
- package/dist/phases/user-stories-analysis/outcome.js +4 -4
- package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +4 -0
- package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +133 -0
- package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +4 -0
- package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +336 -0
- package/dist/services/lifecycle-agent/index.d.ts +24 -0
- package/dist/services/lifecycle-agent/index.js +25 -0
- package/dist/services/lifecycle-agent/phase-criteria.d.ts +57 -0
- package/dist/services/lifecycle-agent/phase-criteria.js +335 -0
- package/dist/services/lifecycle-agent/transition-rules.d.ts +60 -0
- package/dist/services/lifecycle-agent/transition-rules.js +184 -0
- package/dist/services/lifecycle-agent/types.d.ts +190 -0
- package/dist/services/lifecycle-agent/types.js +12 -0
- package/dist/types/features.d.ts +2 -2
- package/dist/types/index.d.ts +2 -2
- package/package.json +1 -1
- package/.claude/settings.local.json +0 -28
- package/.env.local +0 -12
- package/dist/api/features/__tests__/regression-prevention.test.d.ts +0 -5
- package/dist/api/features/__tests__/regression-prevention.test.js +0 -338
- package/dist/api/features/__tests__/status-updater.integration.test.d.ts +0 -5
- package/dist/api/features/__tests__/status-updater.integration.test.js +0 -497
- package/dist/commands/workflow/pipeline-runner.d.ts +0 -17
- package/dist/commands/workflow/pipeline-runner.js +0 -393
- package/dist/commands/workflow/runner.d.ts +0 -26
- package/dist/commands/workflow/runner.js +0 -119
- package/dist/commands/workflow/workflow-runner.d.ts +0 -26
- package/dist/commands/workflow/workflow-runner.js +0 -119
- package/dist/phases/code-implementation/analyzer-helpers.d.ts +0 -28
- package/dist/phases/code-implementation/analyzer-helpers.js +0 -177
- package/dist/phases/code-implementation/analyzer.d.ts +0 -32
- package/dist/phases/code-implementation/analyzer.js +0 -629
- package/dist/phases/code-implementation/context-fetcher.d.ts +0 -17
- package/dist/phases/code-implementation/context-fetcher.js +0 -86
- package/dist/phases/code-implementation/mcp-server.d.ts +0 -1
- package/dist/phases/code-implementation/mcp-server.js +0 -93
- package/dist/phases/code-implementation/prompts-improvement.d.ts +0 -5
- package/dist/phases/code-implementation/prompts-improvement.js +0 -108
- package/dist/phases/code-implementation-verification/verifier.d.ts +0 -31
- package/dist/phases/code-implementation-verification/verifier.js +0 -196
- package/dist/phases/code-refine/analyzer.d.ts +0 -41
- package/dist/phases/code-refine/analyzer.js +0 -561
- package/dist/phases/code-refine/context-fetcher.d.ts +0 -94
- package/dist/phases/code-refine/context-fetcher.js +0 -423
- package/dist/phases/code-refine-verification/analysis/llm-analyzer.d.ts +0 -22
- package/dist/phases/code-refine-verification/analysis/llm-analyzer.js +0 -134
- package/dist/phases/code-refine-verification/verifier.d.ts +0 -47
- package/dist/phases/code-refine-verification/verifier.js +0 -597
- package/dist/phases/code-review/analyzer.d.ts +0 -29
- package/dist/phases/code-review/analyzer.js +0 -363
- package/dist/phases/code-review/context-fetcher.d.ts +0 -92
- package/dist/phases/code-review/context-fetcher.js +0 -296
- package/dist/phases/feature-analysis/analyzer-helpers.d.ts +0 -10
- package/dist/phases/feature-analysis/analyzer-helpers.js +0 -47
- package/dist/phases/feature-analysis/analyzer.d.ts +0 -11
- package/dist/phases/feature-analysis/analyzer.js +0 -208
- package/dist/phases/feature-analysis/context-fetcher.d.ts +0 -26
- package/dist/phases/feature-analysis/context-fetcher.js +0 -134
- package/dist/phases/feature-analysis/http-fallback.d.ts +0 -20
- package/dist/phases/feature-analysis/http-fallback.js +0 -95
- package/dist/phases/feature-analysis/mcp-server.d.ts +0 -1
- package/dist/phases/feature-analysis/mcp-server.js +0 -144
- package/dist/phases/feature-analysis/prompts-improvement.d.ts +0 -8
- package/dist/phases/feature-analysis/prompts-improvement.js +0 -109
- package/dist/phases/feature-analysis-verification/verifier.d.ts +0 -37
- package/dist/phases/feature-analysis-verification/verifier.js +0 -147
- package/dist/phases/technical-design/analyzer-helpers.d.ts +0 -25
- package/dist/phases/technical-design/analyzer-helpers.js +0 -39
- package/dist/phases/technical-design/analyzer.d.ts +0 -21
- package/dist/phases/technical-design/analyzer.js +0 -461
- package/dist/phases/technical-design/context-fetcher.d.ts +0 -12
- package/dist/phases/technical-design/context-fetcher.js +0 -39
- package/dist/phases/technical-design/http-fallback.d.ts +0 -17
- package/dist/phases/technical-design/http-fallback.js +0 -151
- package/dist/phases/technical-design/mcp-server.d.ts +0 -1
- package/dist/phases/technical-design/mcp-server.js +0 -157
- package/dist/phases/technical-design/prompts-improvement.d.ts +0 -5
- package/dist/phases/technical-design/prompts-improvement.js +0 -93
- package/dist/phases/technical-design-verification/verifier.d.ts +0 -53
- package/dist/phases/technical-design-verification/verifier.js +0 -170
- package/dist/services/feature-branches.d.ts +0 -77
- package/dist/services/feature-branches.js +0 -205
- package/dist/workflow-runner/config/phase-configs.d.ts +0 -5
- package/dist/workflow-runner/config/phase-configs.js +0 -120
- package/dist/workflow-runner/core/feature-filter.d.ts +0 -16
- package/dist/workflow-runner/core/feature-filter.js +0 -46
- package/dist/workflow-runner/core/index.d.ts +0 -8
- package/dist/workflow-runner/core/index.js +0 -12
- package/dist/workflow-runner/core/pipeline-evaluator.d.ts +0 -24
- package/dist/workflow-runner/core/pipeline-evaluator.js +0 -32
- package/dist/workflow-runner/core/state-manager.d.ts +0 -24
- package/dist/workflow-runner/core/state-manager.js +0 -42
- package/dist/workflow-runner/core/workflow-logger.d.ts +0 -20
- package/dist/workflow-runner/core/workflow-logger.js +0 -65
- package/dist/workflow-runner/executors/phase-executor.d.ts +0 -8
- package/dist/workflow-runner/executors/phase-executor.js +0 -248
- package/dist/workflow-runner/feature-workflow-runner.d.ts +0 -26
- package/dist/workflow-runner/feature-workflow-runner.js +0 -119
- package/dist/workflow-runner/index.d.ts +0 -2
- package/dist/workflow-runner/index.js +0 -2
- package/dist/workflow-runner/pipeline-runner.d.ts +0 -17
- package/dist/workflow-runner/pipeline-runner.js +0 -393
- package/dist/workflow-runner/workflow-processor.d.ts +0 -54
- 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
|
|
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('
|
|
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
|
-
//
|
|
333
|
-
this.
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
-
|
|
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: ${
|
|
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,
|
|
71
|
+
await markWorkflowPhaseCompleted(featureId, normalizedName, verbose);
|
|
70
72
|
}
|
|
71
73
|
return result;
|
|
72
74
|
}
|
package/dist/index.js
CHANGED
|
File without changes
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 1. CHAT_RESPONSE_PROMPT — responding to human messages
|
|
6
6
|
* 2. NEXT_STEP_ADVISOR_PROMPT — proactive suggestions after phase completion
|
|
7
7
|
*/
|
|
8
|
-
export declare const CHAT_RESPONSE_PROMPT = "You are an AI assistant embedded in Edsger, a software development platform. You are helping a team develop a feature.\n\n## Your Capabilities\nYou can see the feature's current state, user stories, test cases, workflow phases, and code. You have tools to:\n- Modify feature status, execution mode, and workflow phases\n- Create/update user stories and test cases\n- Read and search source code files\n- Send follow-up messages and present options to the user\n- Trigger phase reruns\n- Create tasks for team members (human) or AI\n- Look up product team members by name\n\n## How to Respond\n1. **Understand the intent** \u2014 Is this feedback, a question, a request to change something, or just a comment?\n2. **Take action if needed** \u2014 Use the appropriate tools to make changes\n3. **Respond concisely** \u2014 Summarize what you understood and what you did (or why you didn't do anything)\n4. **Ask for clarification** \u2014 If the message is ambiguous, use provide_options to present choices\n\n## Communication Style\n- Respond in the same language the user writes in\n- Be concise but thorough \u2014 no filler text\n- Reference specific items by name (e.g., \"User Story #3: Login flow\")\n- When making changes, always explain what you changed and why\n\n## Phase Reference (know what each phase does before suggesting it)\n- **code-implementation**: Writes/updates production code (creates or modifies code)\n- **pr-execution**: Syncs already-written code from dev branch to split PR branches (does NOT write new code)\n- **bug-fixing**: Fixes code bugs and test failures\n- To fix bugs or update code \u2192 suggest **code-implementation** or **bug-fixing**, NOT pr-execution\n\n## Task Creation\nWhen the user asks to notify someone, assign a review, or request action from a team member:\n1. Use list_product_members to find the person by name\n2. Use create_task with executor=\"human\"
|
|
8
|
+
export declare const CHAT_RESPONSE_PROMPT = "You are an AI assistant embedded in Edsger, a software development platform. You are helping a team develop a feature.\n\n## Your Capabilities\nYou can see the feature's current state, user stories, test cases, workflow phases, and code. You have tools to:\n- Modify feature status, execution mode, and workflow phases\n- Create/update user stories and test cases\n- Read and search source code files\n- Send follow-up messages and present options to the user\n- Trigger phase reruns\n- Create tasks for team members (human) or AI\n- Look up product team members by name\n\n## How to Respond\n1. **Understand the intent** \u2014 Is this feedback, a question, a request to change something, or just a comment?\n2. **Take action if needed** \u2014 Use the appropriate tools to make changes\n3. **Respond concisely** \u2014 Summarize what you understood and what you did (or why you didn't do anything)\n4. **Ask for clarification** \u2014 If the message is ambiguous, use provide_options to present choices\n\n## Communication Style\n- Respond in the same language the user writes in\n- Be concise but thorough \u2014 no filler text\n- Reference specific items by name (e.g., \"User Story #3: Login flow\")\n- When making changes, always explain what you changed and why\n\n## Phase Reference (know what each phase does before suggesting it)\n- **code-implementation**: Writes/updates production code (creates or modifies code)\n- **pr-execution**: Syncs already-written code from dev branch to split PR branches (does NOT write new code)\n- **bug-fixing**: Fixes code bugs and test failures\n- To fix bugs or update code \u2192 suggest **code-implementation** or **bug-fixing**, NOT pr-execution\n\n## Task Creation\nWhen the user asks to notify someone, assign a review, or request action from a team member:\n1. Use list_product_members to find the person by name\n2. Use create_task with executor=\"human\", the resolved user ID, and the correct action_url\n3. Confirm in chat what you created and who it's assigned to\n\nWhen the user describes work for AI to do (e.g., \"implement X\", \"fix Y\"):\n1. Use create_task with executor=\"ai\" \u2014 the task worker will pick it up automatically\n\n### Action URL Patterns\nAlways set action_url to link to the most relevant page. Available patterns:\n- Product page: `/products/{product_id}`\n- Feature details: `/products/{product_id}/features/{feature_id}`\n- Feature tab (append ?tab=): `/products/{product_id}/features/{feature_id}?tab={tab}`\n\nAvailable feature tabs:\n- `stories` \u2014 User Stories (use for: review user stories, update stories)\n- `test-cases` \u2014 Test Cases (use for: review test cases, verify tests)\n- `technical-design` \u2014 Technical Design (use for: review design, architecture review)\n- `checklists` \u2014 Checklists (use for: review checklists, quality checks)\n- `branches` \u2014 Branches (use for: code review, branch management)\n- `pull-requests` \u2014 Pull Requests (use for: review PRs, merge requests)\n- `test-reports` \u2014 Test Reports (use for: review test results)\n- `feedbacks` \u2014 Feedbacks (use for: provide feedback)\n- `chat` \u2014 Chat (use for: discussion)\n- `details` \u2014 Feature Details (default)\n\nChoose the tab that best matches the task content. For example:\n- \"review user stories\" \u2192 `?tab=stories`\n- \"review technical design\" \u2192 `?tab=technical-design`\n- \"check test results\" \u2192 `?tab=test-reports`\n\n## Important Rules\n- Never make destructive changes without confirmation (deleting stories, resetting phases)\n- For ambiguous feedback, present options rather than guessing\n- If you can't do something, explain why clearly\n- When creating tasks for people, always confirm the person's identity if the name is ambiguous\n";
|
|
9
9
|
export declare const NEXT_STEP_ADVISOR_PROMPT = "You are an AI advisor in Edsger, a software development platform. A workflow phase just completed for a feature you're helping develop.\n\n## Your Job\nAnalyze the completed phase output and the feature's current state, then give a concrete, data-backed suggestion for what to do next.\n\n## Critical Rules\n1. **Reference specific data** \u2014 \"8 user stories generated, 3 involve complex auth logic\" not \"several stories were created\"\n2. **Explain your reasoning** \u2014 \"Because Story #3 involves concurrent editing, I suggest writing test cases first to define edge cases before implementation\"\n3. **Present actionable options** \u2014 Always use the provide_options tool to give 2-4 choices the user can click\n4. **Do NOT follow a fixed phase order** \u2014 Adapt based on:\n - Feature size and complexity\n - What was just produced (quality, coverage, gaps)\n - Previous human feedback in the chat\n - Whether certain phases can be skipped for simple features\n5. **Flag issues proactively** \u2014 If the phase output has gaps, incomplete coverage, or potential problems, call them out\n\n## Phase Reference (know what each phase does before suggesting it)\n- **code-implementation**: Writes/updates production code (the phase that creates or modifies code)\n- **pr-splitting**: Plans how to split code changes into reviewable PRs\n- **pr-execution**: Syncs already-written code from the dev branch to split PR branches (does NOT write new code)\n- **code-testing**: Writes automated tests for implemented code\n- **functional-testing**: Runs end-to-end tests with Playwright\n- **bug-fixing**: Fixes code bugs and test failures\n- **code-review**: Reviews PR code for issues\n- **code-refine**: Updates code based on PR review feedback\n\n**Important distinctions:**\n- To fix bugs or update code \u2192 use **code-implementation** or **bug-fixing**, NOT pr-execution\n- pr-execution only moves existing code to PR branches \u2014 it never creates or modifies implementation code\n\n## Context You Receive\n- Feature description, size, and current state\n- The completed phase name and its full output\n- Remaining workflow phases (with descriptions)\n- User story and test case counts\n- Code change scope (if applicable)\n- Recent chat history (human feedback)\n\n## Communication Style\n- Respond in the same language as recent chat messages (default to the feature's language context)\n- Be specific and data-driven\n- Structure: brief summary \u2192 reasoning \u2192 options\n";
|
|
10
10
|
/**
|
|
11
11
|
* Phase descriptions so the AI advisor understands what each phase does.
|
|
@@ -38,12 +38,35 @@ You can see the feature's current state, user stories, test cases, workflow phas
|
|
|
38
38
|
## Task Creation
|
|
39
39
|
When the user asks to notify someone, assign a review, or request action from a team member:
|
|
40
40
|
1. Use list_product_members to find the person by name
|
|
41
|
-
2. Use create_task with executor="human"
|
|
41
|
+
2. Use create_task with executor="human", the resolved user ID, and the correct action_url
|
|
42
42
|
3. Confirm in chat what you created and who it's assigned to
|
|
43
43
|
|
|
44
44
|
When the user describes work for AI to do (e.g., "implement X", "fix Y"):
|
|
45
45
|
1. Use create_task with executor="ai" — the task worker will pick it up automatically
|
|
46
46
|
|
|
47
|
+
### Action URL Patterns
|
|
48
|
+
Always set action_url to link to the most relevant page. Available patterns:
|
|
49
|
+
- Product page: \`/products/{product_id}\`
|
|
50
|
+
- Feature details: \`/products/{product_id}/features/{feature_id}\`
|
|
51
|
+
- Feature tab (append ?tab=): \`/products/{product_id}/features/{feature_id}?tab={tab}\`
|
|
52
|
+
|
|
53
|
+
Available feature tabs:
|
|
54
|
+
- \`stories\` — User Stories (use for: review user stories, update stories)
|
|
55
|
+
- \`test-cases\` — Test Cases (use for: review test cases, verify tests)
|
|
56
|
+
- \`technical-design\` — Technical Design (use for: review design, architecture review)
|
|
57
|
+
- \`checklists\` — Checklists (use for: review checklists, quality checks)
|
|
58
|
+
- \`branches\` — Branches (use for: code review, branch management)
|
|
59
|
+
- \`pull-requests\` — Pull Requests (use for: review PRs, merge requests)
|
|
60
|
+
- \`test-reports\` — Test Reports (use for: review test results)
|
|
61
|
+
- \`feedbacks\` — Feedbacks (use for: provide feedback)
|
|
62
|
+
- \`chat\` — Chat (use for: discussion)
|
|
63
|
+
- \`details\` — Feature Details (default)
|
|
64
|
+
|
|
65
|
+
Choose the tab that best matches the task content. For example:
|
|
66
|
+
- "review user stories" → \`?tab=stories\`
|
|
67
|
+
- "review technical design" → \`?tab=technical-design\`
|
|
68
|
+
- "check test results" → \`?tab=test-reports\`
|
|
69
|
+
|
|
47
70
|
## Important Rules
|
|
48
71
|
- Never make destructive changes without confirmation (deleting stories, resetting phases)
|
|
49
72
|
- For ambiguous feedback, present options rather than guessing
|
|
@@ -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.
|
|
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:
|
|
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', '
|
|
105
|
+
status: z.enum(['draft', 'pending_approval', 'approved']).optional(),
|
|
76
106
|
})),
|
|
77
107
|
}, async (args) => {
|
|
78
108
|
const results = [];
|
|
@@ -286,6 +316,7 @@ export function createChatMcpServer() {
|
|
|
286
316
|
executor: z.enum(['ai', 'human']).describe('Who should do this: "ai" for automated work, "human" for manual review/approval'),
|
|
287
317
|
assigned_to: z.string().optional().describe('User ID to assign to (use list_product_members to look up)'),
|
|
288
318
|
feature_id: z.string().optional().describe('Related feature ID'),
|
|
319
|
+
action_url: z.string().optional().describe('URL where the assignee should take action. See prompt for URL patterns.'),
|
|
289
320
|
priority: z.number().optional().describe('1=low, 2=medium, 3=high, 4=urgent'),
|
|
290
321
|
}, async (args) => {
|
|
291
322
|
// Get next sequence number
|
|
@@ -295,6 +326,9 @@ export function createChatMcpServer() {
|
|
|
295
326
|
const text = listResult?.content?.[0]?.text || '[]';
|
|
296
327
|
const existingTasks = JSON.parse(text);
|
|
297
328
|
const nextSequence = existingTasks.length + 1;
|
|
329
|
+
// Use explicit action_url if provided, otherwise auto-generate from feature_id
|
|
330
|
+
const actionUrl = args.action_url
|
|
331
|
+
|| (args.feature_id ? `/products/${args.product_id}/features/${args.feature_id}` : null);
|
|
298
332
|
const result = await callMcpEndpoint('tasks/create', {
|
|
299
333
|
product_id: args.product_id,
|
|
300
334
|
sequence: nextSequence,
|
|
@@ -304,6 +338,7 @@ export function createChatMcpServer() {
|
|
|
304
338
|
source: 'system',
|
|
305
339
|
assigned_to: args.assigned_to || null,
|
|
306
340
|
feature_id: args.feature_id || null,
|
|
341
|
+
action_url: actionUrl,
|
|
307
342
|
priority: args.priority || (args.executor === 'human' ? 3 : 2),
|
|
308
343
|
});
|
|
309
344
|
return {
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 '
|
|
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
|
|
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 === '
|
|
13
|
+
.filter((s) => s.status === 'pending_approval')
|
|
14
14
|
.map((s) => s.id);
|
|
15
15
|
const readyTestCaseIds = testCases
|
|
16
|
-
.filter((tc) => tc.status === '
|
|
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
|
|
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
|
|
152
|
+
logInfo(`Updating ${userStoryIds.length} user stories from draft to pending_approval...`);
|
|
153
153
|
}
|
|
154
|
-
await batchUpdateUserStoryStatus(userStoryIds, '
|
|
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
|
|
158
|
+
logInfo(`Updating ${testCaseIds.length} test cases from draft to pending_approval...`);
|
|
159
159
|
}
|
|
160
|
-
await batchUpdateTestCaseStatus(testCaseIds, '
|
|
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 === '
|
|
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
|
|
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}
|
|
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
|
|
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
|
|
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
|
|
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 === '
|
|
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
|
|
73
|
+
logInfo(`Updating ${testCaseIds.length} test cases from draft to pending_approval...`);
|
|
74
74
|
}
|
|
75
|
-
await batchUpdateTestCaseStatus(testCaseIds, '
|
|
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
|
|
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}
|
|
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
|
|
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
|
|
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
|
|
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 === '
|
|
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
|
|
73
|
+
logInfo(`Updating ${userStoryIds.length} user stories from draft to pending_approval...`);
|
|
74
74
|
}
|
|
75
|
-
await batchUpdateUserStoryStatus(userStoryIds, '
|
|
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 === '
|
|
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,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
|
+
});
|