edsger 0.2.1 → 0.2.3
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/api/features/feature-utils.d.ts +13 -0
- package/dist/api/features/feature-utils.js +46 -0
- package/dist/api/features/get-feature.d.ts +5 -0
- package/dist/api/features/get-feature.js +19 -0
- package/dist/api/features/index.d.ts +7 -0
- package/dist/api/features/index.js +9 -0
- package/dist/api/features/status-updater.d.ts +27 -0
- package/dist/api/features/status-updater.js +64 -0
- package/dist/api/features/test-cases.d.ts +21 -0
- package/dist/api/features/test-cases.js +63 -0
- package/dist/api/features/update-feature.d.ts +13 -0
- package/dist/api/features/update-feature.js +31 -0
- package/dist/api/features/user-stories.d.ts +21 -0
- package/dist/api/features/user-stories.js +63 -0
- package/dist/api/features.d.ts +100 -0
- package/dist/api/features.js +219 -0
- package/dist/api/mcp-client.d.ts +18 -0
- package/dist/api/mcp-client.js +58 -0
- package/dist/api/products.d.ts +10 -0
- package/dist/api/products.js +22 -0
- package/dist/api/test-reports.d.ts +9 -0
- package/dist/api/test-reports.js +25 -0
- package/dist/cli/commands/code-implementation-command.d.ts +2 -0
- package/dist/cli/commands/code-implementation-command.js +36 -0
- package/dist/cli/commands/code-review-command.d.ts +2 -0
- package/dist/cli/commands/code-review-command.js +39 -0
- package/dist/cli/commands/feature-analysis-command.d.ts +2 -0
- package/dist/cli/commands/feature-analysis-command.js +36 -0
- package/dist/cli/commands/functional-testing-command.d.ts +2 -0
- package/dist/cli/commands/functional-testing-command.js +36 -0
- package/dist/cli/commands/technical-design-command.d.ts +2 -0
- package/dist/cli/commands/technical-design-command.js +36 -0
- package/dist/cli/commands/workflow-command.d.ts +2 -0
- package/dist/cli/commands/workflow-command.js +34 -0
- package/dist/cli/formatters/code-implementation-formatter.d.ts +9 -0
- package/dist/cli/formatters/code-implementation-formatter.js +27 -0
- package/dist/cli/formatters/feature-analysis-formatter.d.ts +2 -0
- package/dist/cli/formatters/feature-analysis-formatter.js +27 -0
- package/dist/cli/formatters/functional-testing-formatter.d.ts +15 -0
- package/dist/cli/formatters/functional-testing-formatter.js +37 -0
- package/dist/cli/formatters/technical-design-formatter.d.ts +7 -0
- package/dist/cli/formatters/technical-design-formatter.js +30 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +99 -0
- package/dist/cli/utils/validation.d.ts +25 -0
- package/dist/cli/utils/validation.js +58 -0
- package/dist/cli/utils/workflow-utils.d.ts +21 -0
- package/dist/cli/utils/workflow-utils.js +47 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +11 -466
- package/dist/config.d.ts +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/{bug-fixing → phases/bug-fixing}/analyzer.d.ts +1 -1
- package/dist/{bug-fixing → phases/bug-fixing}/analyzer.js +1 -1
- package/dist/{bug-fixing → phases/bug-fixing}/context-fetcher.d.ts +4 -22
- package/dist/{bug-fixing → phases/bug-fixing}/context-fetcher.js +14 -58
- package/dist/{bug-fixing → phases/bug-fixing}/mcp-server.js +1 -30
- package/dist/phases/code-implementation/analyzer.d.ts +33 -0
- package/dist/{code-implementation → phases/code-implementation}/analyzer.js +178 -19
- package/dist/phases/code-implementation/context-fetcher.d.ts +17 -0
- package/dist/phases/code-implementation/context-fetcher.js +86 -0
- package/dist/{code-implementation → phases/code-implementation}/mcp-server.js +1 -30
- package/dist/{code-review → phases/code-review}/reviewer.d.ts +1 -1
- package/dist/{feature-analysis → phases/feature-analysis}/analyzer.d.ts +3 -2
- package/dist/{feature-analysis → phases/feature-analysis}/analyzer.js +35 -127
- package/dist/phases/feature-analysis/context-fetcher.d.ts +18 -0
- package/dist/phases/feature-analysis/context-fetcher.js +86 -0
- package/dist/{feature-analysis → phases/feature-analysis}/http-fallback.js +1 -1
- package/dist/{feature-analysis → phases/feature-analysis}/mcp-server.js +1 -24
- package/dist/{functional-testing → phases/functional-testing}/analyzer.d.ts +17 -2
- package/dist/{functional-testing → phases/functional-testing}/analyzer.js +225 -31
- package/dist/phases/functional-testing/context-fetcher.d.ts +16 -0
- package/dist/phases/functional-testing/context-fetcher.js +81 -0
- package/dist/{functional-testing → phases/functional-testing}/http-fallback.js +1 -1
- package/dist/{functional-testing → phases/functional-testing}/index.d.ts +1 -1
- package/dist/{functional-testing → phases/functional-testing}/index.js +1 -1
- package/dist/{functional-testing → phases/functional-testing}/mcp-server.js +1 -30
- package/dist/{functional-testing → phases/functional-testing}/test-report-creator.js +1 -1
- package/dist/phases/functional-testing/test-retry-handler.d.ts +16 -0
- package/dist/phases/functional-testing/test-retry-handler.js +75 -0
- package/dist/{pull-request → phases/pull-request}/creator.js +47 -6
- package/dist/phases/pull-request/handler.d.ts +16 -0
- package/dist/phases/pull-request/handler.js +60 -0
- package/dist/{technical-design → phases/technical-design}/analyzer.d.ts +7 -2
- package/dist/phases/technical-design/analyzer.js +424 -0
- package/dist/phases/technical-design/context-fetcher.d.ts +12 -0
- package/dist/phases/technical-design/context-fetcher.js +39 -0
- package/dist/{technical-design → phases/technical-design}/http-fallback.js +1 -1
- package/dist/{technical-design → phases/technical-design}/mcp-server.js +1 -30
- package/dist/prompts/bug-fixing.d.ts +2 -0
- package/dist/prompts/bug-fixing.js +63 -0
- package/dist/prompts/code-implementation.d.ts +3 -0
- package/dist/prompts/code-implementation.js +132 -0
- package/dist/prompts/feature-analysis.d.ts +3 -0
- package/dist/prompts/feature-analysis.js +149 -0
- package/dist/prompts/formatters.d.ts +42 -0
- package/dist/prompts/formatters.js +168 -0
- package/dist/prompts/functional-testing.d.ts +3 -0
- package/dist/prompts/functional-testing.js +126 -0
- package/dist/prompts/index.d.ts +6 -0
- package/dist/prompts/index.js +7 -0
- package/dist/prompts/technical-design.d.ts +3 -0
- package/dist/prompts/technical-design.js +130 -0
- package/dist/services/checklist.d.ts +99 -0
- package/dist/services/checklist.js +337 -0
- package/dist/types/features.d.ts +29 -0
- package/dist/types/features.js +1 -0
- package/dist/types/index.d.ts +112 -0
- package/dist/types/index.js +1 -0
- package/dist/types/pipeline.d.ts +25 -0
- package/dist/types/pipeline.js +4 -0
- package/dist/utils/image-downloader.d.ts +32 -0
- package/dist/utils/image-downloader.js +144 -0
- package/dist/utils/image-processor.d.ts +5 -0
- package/dist/utils/image-processor.js +55 -0
- package/dist/utils/logger.d.ts +19 -0
- package/dist/utils/logger.js +52 -0
- package/dist/utils/pipeline-logger.d.ts +8 -0
- package/dist/utils/pipeline-logger.js +35 -0
- package/dist/workflow-runner/config/phase-configs.d.ts +5 -0
- package/dist/workflow-runner/config/phase-configs.js +34 -0
- package/dist/workflow-runner/config/stage-configs.d.ts +5 -0
- package/dist/workflow-runner/config/stage-configs.js +34 -0
- package/dist/workflow-runner/core/feature-filter.d.ts +16 -0
- package/dist/workflow-runner/core/feature-filter.js +46 -0
- package/dist/workflow-runner/core/feature-filter.test.d.ts +4 -0
- package/dist/workflow-runner/core/feature-filter.test.js +127 -0
- package/dist/workflow-runner/core/index.d.ts +8 -0
- package/dist/workflow-runner/core/index.js +12 -0
- package/dist/workflow-runner/core/pipeline-evaluator.d.ts +24 -0
- package/dist/workflow-runner/core/pipeline-evaluator.js +32 -0
- package/dist/workflow-runner/core/state-manager.d.ts +24 -0
- package/dist/workflow-runner/core/state-manager.js +42 -0
- package/dist/workflow-runner/core/workflow-logger.d.ts +20 -0
- package/dist/workflow-runner/core/workflow-logger.js +65 -0
- package/dist/workflow-runner/executors/phase-executor.d.ts +8 -0
- package/dist/workflow-runner/executors/phase-executor.js +183 -0
- package/dist/workflow-runner/executors/stage-executor.d.ts +8 -0
- package/dist/workflow-runner/executors/stage-executor.js +49 -0
- package/dist/workflow-runner/feature-service.d.ts +17 -0
- package/dist/workflow-runner/feature-service.js +60 -0
- package/dist/workflow-runner/feature-workflow-runner.d.ts +26 -0
- package/dist/workflow-runner/feature-workflow-runner.js +113 -0
- package/dist/workflow-runner/index.d.ts +0 -1
- package/dist/workflow-runner/index.js +0 -1
- package/dist/workflow-runner/pipeline-runner.d.ts +9 -19
- package/dist/workflow-runner/pipeline-runner.js +247 -256
- package/dist/workflow-runner/pipeline.d.ts +18 -0
- package/dist/workflow-runner/pipeline.js +197 -0
- package/dist/workflow-runner/processor.d.ts +40 -0
- package/dist/workflow-runner/processor.js +191 -0
- package/dist/workflow-runner/types.d.ts +48 -0
- package/dist/workflow-runner/types.js +4 -0
- package/dist/workflow-runner/workflow-processor.d.ts +6 -23
- package/dist/workflow-runner/workflow-processor.js +38 -100
- package/package.json +1 -1
- package/dist/code-implementation/analyzer.d.ts +0 -19
- package/dist/code-implementation/context-fetcher.d.ts +0 -38
- package/dist/code-implementation/context-fetcher.js +0 -147
- package/dist/feature-analysis/context-fetcher.d.ts +0 -54
- package/dist/feature-analysis/context-fetcher.js +0 -193
- package/dist/functional-testing/context-fetcher.d.ts +0 -47
- package/dist/functional-testing/context-fetcher.js +0 -192
- package/dist/technical-design/analyzer.js +0 -338
- package/dist/technical-design/context-fetcher.d.ts +0 -42
- package/dist/technical-design/context-fetcher.js +0 -170
- /package/dist/{bug-fixing → phases/bug-fixing}/index.d.ts +0 -0
- /package/dist/{bug-fixing → phases/bug-fixing}/index.js +0 -0
- /package/dist/{bug-fixing → phases/bug-fixing}/mcp-server.d.ts +0 -0
- /package/dist/{code-implementation → phases/code-implementation}/mcp-server.d.ts +0 -0
- /package/dist/{code-review → phases/code-review}/reviewer.js +0 -0
- /package/dist/{feature-analysis → phases/feature-analysis}/http-fallback.d.ts +0 -0
- /package/dist/{feature-analysis → phases/feature-analysis}/index.d.ts +0 -0
- /package/dist/{feature-analysis → phases/feature-analysis}/index.js +0 -0
- /package/dist/{feature-analysis → phases/feature-analysis}/mcp-server.d.ts +0 -0
- /package/dist/{functional-testing → phases/functional-testing}/http-fallback.d.ts +0 -0
- /package/dist/{functional-testing → phases/functional-testing}/mcp-server.d.ts +0 -0
- /package/dist/{functional-testing → phases/functional-testing}/test-report-creator.d.ts +0 -0
- /package/dist/{pull-request → phases/pull-request}/creator.d.ts +0 -0
- /package/dist/{technical-design → phases/technical-design}/http-fallback.d.ts +0 -0
- /package/dist/{technical-design → phases/technical-design}/mcp-server.d.ts +0 -0
|
@@ -91,7 +91,7 @@ export async function createPullRequest(config, feature) {
|
|
|
91
91
|
const octokit = new Octokit({
|
|
92
92
|
auth: githubToken,
|
|
93
93
|
});
|
|
94
|
-
// Get current branch
|
|
94
|
+
// Get current branch (should be dev/feature-id)
|
|
95
95
|
const currentBranch = getCurrentBranch();
|
|
96
96
|
if (verbose) {
|
|
97
97
|
console.log(`🔍 Current branch: ${currentBranch}`);
|
|
@@ -103,22 +103,63 @@ export async function createPullRequest(config, feature) {
|
|
|
103
103
|
error: `Cannot create PR: already on ${baseBranch} branch`,
|
|
104
104
|
};
|
|
105
105
|
}
|
|
106
|
-
//
|
|
107
|
-
|
|
106
|
+
// Extract feature ID from current branch (dev/feature-id)
|
|
107
|
+
let targetBranch = baseBranch;
|
|
108
|
+
if (currentBranch.startsWith('dev/')) {
|
|
109
|
+
const featureId = currentBranch.replace('dev/', '');
|
|
110
|
+
targetBranch = `feat/${featureId}`;
|
|
111
|
+
// Create the feat/ branch from base branch (main)
|
|
112
|
+
if (verbose) {
|
|
113
|
+
console.log(`📝 Creating target branch: ${targetBranch} from ${baseBranch}`);
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
// Push current branch to remote first
|
|
117
|
+
pushBranch(currentBranch, verbose);
|
|
118
|
+
// Get the base branch (main) SHA to create feat/ branch from
|
|
119
|
+
const { data: baseBranchData } = await octokit.repos.getBranch({
|
|
120
|
+
owner,
|
|
121
|
+
repo,
|
|
122
|
+
branch: baseBranch,
|
|
123
|
+
});
|
|
124
|
+
// Create the feat/ branch from main
|
|
125
|
+
await octokit.git.createRef({
|
|
126
|
+
owner,
|
|
127
|
+
repo,
|
|
128
|
+
ref: `refs/heads/${targetBranch}`,
|
|
129
|
+
sha: baseBranchData.commit.sha,
|
|
130
|
+
});
|
|
131
|
+
if (verbose) {
|
|
132
|
+
console.log(`✅ Created target branch: ${targetBranch} from ${baseBranch}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
// If branch already exists, that's fine
|
|
137
|
+
if (!error.message?.includes('Reference already exists')) {
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
if (verbose) {
|
|
141
|
+
console.log(`ℹ️ Target branch ${targetBranch} already exists`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// If not a dev/ branch, push it normally
|
|
147
|
+
pushBranch(currentBranch, verbose);
|
|
148
|
+
}
|
|
108
149
|
// Generate PR title and body
|
|
109
150
|
const title = generatePRTitle(feature.name);
|
|
110
151
|
const body = generatePRBody(feature);
|
|
111
152
|
if (verbose) {
|
|
112
|
-
console.log(`📝 Creating pull request: ${title}`);
|
|
153
|
+
console.log(`📝 Creating pull request: ${title} from ${currentBranch} to ${targetBranch}`);
|
|
113
154
|
}
|
|
114
|
-
// Create the pull request
|
|
155
|
+
// Create the pull request from dev/ to feat/
|
|
115
156
|
const { data: pullRequest } = await octokit.pulls.create({
|
|
116
157
|
owner,
|
|
117
158
|
repo,
|
|
118
159
|
title,
|
|
119
160
|
body,
|
|
120
161
|
head: currentBranch,
|
|
121
|
-
base:
|
|
162
|
+
base: targetBranch,
|
|
122
163
|
draft: false,
|
|
123
164
|
});
|
|
124
165
|
if (verbose) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pull request creation handler for pipeline execution
|
|
3
|
+
*/
|
|
4
|
+
import { PipelineResult } from '../../types/pipeline.js';
|
|
5
|
+
export interface PullRequestHandlerOptions {
|
|
6
|
+
featureId: string;
|
|
7
|
+
mcpServerUrl: string;
|
|
8
|
+
mcpToken: string;
|
|
9
|
+
results: readonly PipelineResult[];
|
|
10
|
+
testingResult: PipelineResult;
|
|
11
|
+
verbose?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create pull request for successful feature implementation
|
|
15
|
+
*/
|
|
16
|
+
export declare function handlePullRequestCreation({ featureId, mcpServerUrl, mcpToken, results, testingResult, verbose, }: PullRequestHandlerOptions): Promise<void>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pull request creation handler for pipeline execution
|
|
3
|
+
*/
|
|
4
|
+
import { getFeature } from '../../api/features/index.js';
|
|
5
|
+
import { createPullRequest, updateFeatureWithPullRequest } from './creator.js';
|
|
6
|
+
/**
|
|
7
|
+
* Create pull request for successful feature implementation
|
|
8
|
+
*/
|
|
9
|
+
export async function handlePullRequestCreation({ featureId, mcpServerUrl, mcpToken, results, testingResult, verbose, }) {
|
|
10
|
+
if (verbose) {
|
|
11
|
+
console.log('🔄 Creating pull request for successful feature...');
|
|
12
|
+
}
|
|
13
|
+
// Get feature details for PR creation
|
|
14
|
+
const feature = await getFeature(mcpServerUrl, mcpToken, featureId, verbose);
|
|
15
|
+
if (!feature) {
|
|
16
|
+
if (verbose) {
|
|
17
|
+
console.log('⚠️ Could not fetch feature details for pull request creation');
|
|
18
|
+
}
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// Get GitHub configuration from environment
|
|
22
|
+
const githubToken = process.env.GITHUB_TOKEN || process.env.GITHUB_ACCESS_TOKEN;
|
|
23
|
+
const githubOwner = process.env.GITHUB_OWNER;
|
|
24
|
+
const githubRepo = process.env.GITHUB_REPO;
|
|
25
|
+
if (!githubToken || !githubOwner || !githubRepo) {
|
|
26
|
+
if (verbose) {
|
|
27
|
+
console.log('⚠️ GitHub configuration not found. Skipping pull request creation.');
|
|
28
|
+
console.log(' Set GITHUB_TOKEN, GITHUB_OWNER, and GITHUB_REPO environment variables.');
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const prResult = await createPullRequest({
|
|
33
|
+
githubToken,
|
|
34
|
+
owner: githubOwner,
|
|
35
|
+
repo: githubRepo,
|
|
36
|
+
baseBranch: 'main',
|
|
37
|
+
verbose,
|
|
38
|
+
}, {
|
|
39
|
+
id: feature.id,
|
|
40
|
+
name: feature.name,
|
|
41
|
+
description: feature.description || '',
|
|
42
|
+
productId: feature.product_id,
|
|
43
|
+
// Get technical design and test result from previous phases
|
|
44
|
+
technicalDesign: results.find((r) => r.phase === 'technical-design')?.data?.technicalDesign,
|
|
45
|
+
testResult: testingResult.data?.testResult,
|
|
46
|
+
testReportUrl: testingResult.data?.testReport?.testReportUrl,
|
|
47
|
+
});
|
|
48
|
+
if (prResult.success && prResult.pullRequestUrl) {
|
|
49
|
+
// Update feature with PR URL and status
|
|
50
|
+
await updateFeatureWithPullRequest(mcpServerUrl, mcpToken, featureId, prResult.pullRequestUrl, verbose);
|
|
51
|
+
if (verbose) {
|
|
52
|
+
console.log(`✅ Pull request created and feature updated to ready_for_review`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
if (verbose) {
|
|
57
|
+
console.log(`⚠️ Pull request creation failed: ${prResult.error}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { EdsgerConfig } from '
|
|
1
|
+
import { EdsgerConfig } from '../../types/index.js';
|
|
2
|
+
import { ChecklistPhaseContext } from '../../services/checklist.js';
|
|
2
3
|
export interface TechnicalDesignOptions {
|
|
3
4
|
featureId: string;
|
|
4
5
|
mcpServerUrl: string;
|
|
@@ -11,6 +12,10 @@ export interface TechnicalDesignResult {
|
|
|
11
12
|
status: 'success' | 'error';
|
|
12
13
|
summary: string;
|
|
13
14
|
savedViaHttp?: boolean;
|
|
15
|
+
data?: {
|
|
16
|
+
checklist_item_results?: any[];
|
|
17
|
+
[key: string]: any;
|
|
18
|
+
};
|
|
14
19
|
}
|
|
15
|
-
export declare const generateTechnicalDesign: (options: TechnicalDesignOptions, config: EdsgerConfig) => Promise<TechnicalDesignResult>;
|
|
20
|
+
export declare const generateTechnicalDesign: (options: TechnicalDesignOptions, config: EdsgerConfig, checklistContext?: ChecklistPhaseContext | null) => Promise<TechnicalDesignResult>;
|
|
16
21
|
export declare const checkTechnicalDesignRequirements: () => Promise<boolean>;
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { query } from '@anthropic-ai/claude-code';
|
|
2
|
+
import { logInfo, logError } from '../../utils/logger.js';
|
|
3
|
+
import { saveTechnicalDesignViaHttp } from './http-fallback.js';
|
|
4
|
+
import { fetchTechnicalDesignContext, } from './context-fetcher.js';
|
|
5
|
+
import { updateTechnicalDesign } from '../../api/features/index.js';
|
|
6
|
+
import { formatTechnicalDesignContext } from '../../prompts/formatters.js';
|
|
7
|
+
import { createTechnicalDesignSystemPrompt, createTechnicalDesignPromptWithContext, } from '../../prompts/technical-design.js';
|
|
8
|
+
import { formatChecklistsForContext, } from '../../services/checklist.js';
|
|
9
|
+
function userMessage(content) {
|
|
10
|
+
return {
|
|
11
|
+
type: 'user',
|
|
12
|
+
message: { role: 'user', content: content },
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
async function* prompt(analysisPrompt) {
|
|
16
|
+
yield userMessage(analysisPrompt);
|
|
17
|
+
await new Promise((res) => setTimeout(res, 10000));
|
|
18
|
+
}
|
|
19
|
+
export const generateTechnicalDesign = async (options, config, checklistContext) => {
|
|
20
|
+
const { featureId, mcpServerUrl, mcpToken, verbose } = options;
|
|
21
|
+
if (verbose) {
|
|
22
|
+
logInfo(`Starting technical design generation for feature ID: ${featureId}`);
|
|
23
|
+
logInfo(`Using MCP server: ${mcpServerUrl}`);
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
// Fetch all required context information via MCP endpoints
|
|
27
|
+
if (verbose) {
|
|
28
|
+
logInfo('Fetching technical design context via MCP endpoints...');
|
|
29
|
+
}
|
|
30
|
+
const context = await fetchTechnicalDesignContext(mcpServerUrl, mcpToken, featureId, verbose);
|
|
31
|
+
const systemPrompt = createTechnicalDesignSystemPrompt(config, mcpServerUrl, mcpToken, featureId);
|
|
32
|
+
const { content: contextInfo, downloadedImages } = await formatTechnicalDesignContext(context);
|
|
33
|
+
if (verbose && downloadedImages.length > 0) {
|
|
34
|
+
logInfo(`Downloaded ${downloadedImages.length} images for Claude Code:`);
|
|
35
|
+
downloadedImages.forEach((img) => {
|
|
36
|
+
logInfo(` - ${img.url} -> ${img.localPath}`);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// Add checklist context to the design prompt
|
|
40
|
+
let finalContextInfo = contextInfo;
|
|
41
|
+
if (checklistContext && checklistContext.checklists.length > 0) {
|
|
42
|
+
const checklistInfo = formatChecklistsForContext(checklistContext);
|
|
43
|
+
finalContextInfo = contextInfo + '\n\n' + checklistInfo;
|
|
44
|
+
if (verbose) {
|
|
45
|
+
logInfo(`Added ${checklistContext.checklists.length} checklists to design context`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const designPrompt = createTechnicalDesignPromptWithContext(featureId, finalContextInfo);
|
|
49
|
+
let lastAssistantResponse = '';
|
|
50
|
+
let structuredDesignResult = null;
|
|
51
|
+
if (verbose) {
|
|
52
|
+
logInfo('Starting Claude Code query with pre-fetched information...');
|
|
53
|
+
}
|
|
54
|
+
// Use Claude Code SDK without MCP servers - all info is pre-fetched
|
|
55
|
+
for await (const message of query({
|
|
56
|
+
prompt: prompt(designPrompt),
|
|
57
|
+
options: {
|
|
58
|
+
appendSystemPrompt: systemPrompt,
|
|
59
|
+
model: config.claude.model || 'sonnet',
|
|
60
|
+
maxTurns: 1000,
|
|
61
|
+
permissionMode: 'bypassPermissions',
|
|
62
|
+
},
|
|
63
|
+
})) {
|
|
64
|
+
if (verbose) {
|
|
65
|
+
logInfo(`Received message type: ${message.type}`);
|
|
66
|
+
}
|
|
67
|
+
// Stream the technical design generation process
|
|
68
|
+
if (message.type === 'assistant' && message.message?.content) {
|
|
69
|
+
for (const content of message.message.content) {
|
|
70
|
+
if (content.type === 'text') {
|
|
71
|
+
lastAssistantResponse += content.text + '\n';
|
|
72
|
+
if (verbose) {
|
|
73
|
+
console.log(`\n🤖 ${content.text}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else if (content.type === 'tool_use') {
|
|
77
|
+
if (verbose) {
|
|
78
|
+
console.log(`\n🔧 ${content.name}: ${content.input.description || 'Running...'}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (message.type === 'result') {
|
|
84
|
+
if (message.subtype === 'success') {
|
|
85
|
+
logInfo('\n🎨 Technical design generation completed, parsing results...');
|
|
86
|
+
try {
|
|
87
|
+
// Try to extract JSON from markdown code block or parse directly
|
|
88
|
+
const responseText = message.result || lastAssistantResponse;
|
|
89
|
+
let jsonResult = null;
|
|
90
|
+
// DEBUG: Log response text details
|
|
91
|
+
console.log('=== DEBUG: Response Text Analysis ===');
|
|
92
|
+
console.log('message.result exists:', !!message.result);
|
|
93
|
+
console.log('lastAssistantResponse exists:', !!lastAssistantResponse);
|
|
94
|
+
console.log('responseText length:', responseText.length);
|
|
95
|
+
console.log('responseText first 200 chars:', JSON.stringify(responseText.substring(0, 200)));
|
|
96
|
+
console.log('responseText last 200 chars:', JSON.stringify(responseText.substring(responseText.length - 200)));
|
|
97
|
+
console.log('Contains ```json:', responseText.includes('```json'));
|
|
98
|
+
console.log('Contains technical_design_result:', responseText.includes('"technical_design_result"'));
|
|
99
|
+
// First try to extract JSON from markdown code block
|
|
100
|
+
const jsonBlockMatch = responseText.match(/```json\s*\n([\s\S]*?)\n\s*```/);
|
|
101
|
+
console.log('=== DEBUG: Markdown Block Match ===');
|
|
102
|
+
console.log('jsonBlockMatch found:', !!jsonBlockMatch);
|
|
103
|
+
if (jsonBlockMatch) {
|
|
104
|
+
console.log('Matched JSON length:', jsonBlockMatch[1].length);
|
|
105
|
+
console.log('Matched JSON first 100 chars:', JSON.stringify(jsonBlockMatch[1].substring(0, 100)));
|
|
106
|
+
jsonResult = JSON.parse(jsonBlockMatch[1]);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
console.log('=== DEBUG: JSON Object Search ===');
|
|
110
|
+
// Try to find JSON object containing "technical_design_result"
|
|
111
|
+
const lines = responseText.split('\n');
|
|
112
|
+
let jsonStartIndex = -1;
|
|
113
|
+
console.log('Total lines:', lines.length);
|
|
114
|
+
console.log('Lines preview:', lines
|
|
115
|
+
.slice(0, 10)
|
|
116
|
+
.map((line, i) => `${i}: ${JSON.stringify(line.substring(0, 50))}`));
|
|
117
|
+
// Find the line that contains the start of a JSON object with "technical_design_result"
|
|
118
|
+
for (let i = 0; i < lines.length; i++) {
|
|
119
|
+
const line = lines[i].trim();
|
|
120
|
+
if (line.startsWith('{') &&
|
|
121
|
+
responseText.includes('"technical_design_result"')) {
|
|
122
|
+
console.log(`Found { at line ${i}: ${JSON.stringify(line)}`);
|
|
123
|
+
const fromThisLine = lines.slice(i).join('\n');
|
|
124
|
+
if (fromThisLine.includes('"technical_design_result"')) {
|
|
125
|
+
jsonStartIndex = responseText.indexOf(lines[i]);
|
|
126
|
+
console.log(`JSON start found at line ${i}, responseText index: ${jsonStartIndex}`);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (jsonStartIndex !== -1) {
|
|
132
|
+
// Find the complete JSON object starting from this position
|
|
133
|
+
let braceCount = 0;
|
|
134
|
+
let endIndex = jsonStartIndex;
|
|
135
|
+
let inString = false;
|
|
136
|
+
let escapeNext = false;
|
|
137
|
+
for (let i = jsonStartIndex; i < responseText.length; i++) {
|
|
138
|
+
const char = responseText[i];
|
|
139
|
+
if (escapeNext) {
|
|
140
|
+
escapeNext = false;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (char === '\\' && inString) {
|
|
144
|
+
escapeNext = true;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (char === '"') {
|
|
148
|
+
inString = !inString;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (!inString) {
|
|
152
|
+
if (char === '{') {
|
|
153
|
+
braceCount++;
|
|
154
|
+
}
|
|
155
|
+
else if (char === '}') {
|
|
156
|
+
braceCount--;
|
|
157
|
+
if (braceCount === 0) {
|
|
158
|
+
endIndex = i;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const jsonStr = responseText.substring(jsonStartIndex, endIndex + 1);
|
|
165
|
+
console.log('=== DEBUG: JSON Extraction ===');
|
|
166
|
+
console.log('JSON start index:', jsonStartIndex);
|
|
167
|
+
console.log('JSON end index:', endIndex);
|
|
168
|
+
console.log('Extracted JSON length:', jsonStr.length);
|
|
169
|
+
console.log('Extracted JSON first 100 chars:', JSON.stringify(jsonStr.substring(0, 100)));
|
|
170
|
+
console.log('Extracted JSON last 100 chars:', JSON.stringify(jsonStr.substring(jsonStr.length - 100)));
|
|
171
|
+
try {
|
|
172
|
+
jsonResult = JSON.parse(jsonStr);
|
|
173
|
+
console.log('JSON parsing succeeded!');
|
|
174
|
+
}
|
|
175
|
+
catch (parseError) {
|
|
176
|
+
console.log('Failed to parse extracted JSON:', parseError);
|
|
177
|
+
console.log('Extracted JSON string:', jsonStr.substring(0, 200) + '...');
|
|
178
|
+
console.log('=== DEBUG: Fallback to full response ===');
|
|
179
|
+
console.log('Attempting to parse entire responseText as JSON');
|
|
180
|
+
// Try to parse the entire response as JSON as fallback
|
|
181
|
+
jsonResult = JSON.parse(responseText);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
console.log('=== DEBUG: No JSON start found ===');
|
|
186
|
+
console.log('Falling back to parse entire responseText as JSON');
|
|
187
|
+
// Try to parse the entire response as JSON
|
|
188
|
+
jsonResult = JSON.parse(responseText);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
console.log('=== DEBUG: Final Result Processing ===');
|
|
192
|
+
console.log('jsonResult exists:', !!jsonResult);
|
|
193
|
+
console.log('jsonResult has technical_design_result:', !!(jsonResult && jsonResult.technical_design_result));
|
|
194
|
+
if (jsonResult && jsonResult.technical_design_result) {
|
|
195
|
+
console.log('checklist_item_results in parsed JSON:', !!jsonResult.technical_design_result.checklist_item_results);
|
|
196
|
+
if (jsonResult.technical_design_result.checklist_item_results) {
|
|
197
|
+
console.log('checklist_item_results length:', jsonResult.technical_design_result.checklist_item_results
|
|
198
|
+
.length);
|
|
199
|
+
}
|
|
200
|
+
structuredDesignResult = jsonResult.technical_design_result;
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
throw new Error('Invalid JSON structure');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
logError(`Failed to parse structured design result: ${error}`);
|
|
208
|
+
// Extract technical design from response if JSON parsing fails
|
|
209
|
+
const extractedDesign = extractTechnicalDesignFromResponse(message.result || lastAssistantResponse);
|
|
210
|
+
structuredDesignResult = {
|
|
211
|
+
status: extractedDesign ? 'success' : 'error',
|
|
212
|
+
technical_design: extractedDesign,
|
|
213
|
+
summary: extractedDesign
|
|
214
|
+
? 'Technical design generated successfully'
|
|
215
|
+
: 'Failed to generate technical design',
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
logError(`\n⚠️ Technical design generation incomplete: ${message.subtype}`);
|
|
221
|
+
if (message.subtype === 'error_max_turns') {
|
|
222
|
+
logError('💡 Try increasing timeout or reducing complexity');
|
|
223
|
+
}
|
|
224
|
+
// Try to parse results from the last assistant response
|
|
225
|
+
if (lastAssistantResponse) {
|
|
226
|
+
try {
|
|
227
|
+
const responseText = lastAssistantResponse;
|
|
228
|
+
let jsonResult = null;
|
|
229
|
+
const jsonBlockMatch = responseText.match(/```json\s*\n([\s\S]*?)\n\s*```/);
|
|
230
|
+
if (jsonBlockMatch) {
|
|
231
|
+
jsonResult = JSON.parse(jsonBlockMatch[1]);
|
|
232
|
+
if (jsonResult && jsonResult.technical_design_result) {
|
|
233
|
+
structuredDesignResult = jsonResult.technical_design_result;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
// Try to find JSON object containing "technical_design_result"
|
|
238
|
+
const lines = responseText.split('\n');
|
|
239
|
+
let jsonStartIndex = -1;
|
|
240
|
+
// Find the line that contains the start of a JSON object with "technical_design_result"
|
|
241
|
+
for (let i = 0; i < lines.length; i++) {
|
|
242
|
+
const line = lines[i].trim();
|
|
243
|
+
if (line.startsWith('{') &&
|
|
244
|
+
responseText.includes('"technical_design_result"')) {
|
|
245
|
+
const fromThisLine = lines.slice(i).join('\n');
|
|
246
|
+
if (fromThisLine.includes('"technical_design_result"')) {
|
|
247
|
+
jsonStartIndex = responseText.indexOf(lines[i]);
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (jsonStartIndex !== -1) {
|
|
253
|
+
// Find the complete JSON object starting from this position
|
|
254
|
+
let braceCount = 0;
|
|
255
|
+
let endIndex = jsonStartIndex;
|
|
256
|
+
let inString = false;
|
|
257
|
+
let escapeNext = false;
|
|
258
|
+
for (let i = jsonStartIndex; i < responseText.length; i++) {
|
|
259
|
+
const char = responseText[i];
|
|
260
|
+
if (escapeNext) {
|
|
261
|
+
escapeNext = false;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (char === '\\' && inString) {
|
|
265
|
+
escapeNext = true;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (char === '"') {
|
|
269
|
+
inString = !inString;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (!inString) {
|
|
273
|
+
if (char === '{') {
|
|
274
|
+
braceCount++;
|
|
275
|
+
}
|
|
276
|
+
else if (char === '}') {
|
|
277
|
+
braceCount--;
|
|
278
|
+
if (braceCount === 0) {
|
|
279
|
+
endIndex = i;
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const jsonStr = responseText.substring(jsonStartIndex, endIndex + 1);
|
|
286
|
+
try {
|
|
287
|
+
jsonResult = JSON.parse(jsonStr);
|
|
288
|
+
if (jsonResult && jsonResult.technical_design_result) {
|
|
289
|
+
structuredDesignResult =
|
|
290
|
+
jsonResult.technical_design_result;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
catch (parseError) {
|
|
294
|
+
logError(`Failed to parse extracted JSON: ${parseError}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (!structuredDesignResult) {
|
|
298
|
+
const extractedDesign = extractTechnicalDesignFromResponse(lastAssistantResponse);
|
|
299
|
+
if (extractedDesign) {
|
|
300
|
+
structuredDesignResult = {
|
|
301
|
+
status: 'success',
|
|
302
|
+
technical_design: extractedDesign,
|
|
303
|
+
summary: 'Technical design generated successfully',
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
logError(`Failed to parse assistant response: ${error}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// Save the technical design if we have it
|
|
317
|
+
if (structuredDesignResult?.technical_design) {
|
|
318
|
+
if (verbose) {
|
|
319
|
+
logInfo('Saving technical design...');
|
|
320
|
+
}
|
|
321
|
+
const designSaved = await updateTechnicalDesign(mcpServerUrl, mcpToken, featureId, structuredDesignResult.technical_design, verbose);
|
|
322
|
+
// Try HTTP fallback if direct update failed
|
|
323
|
+
if (!designSaved) {
|
|
324
|
+
if (verbose) {
|
|
325
|
+
logInfo('Direct update failed, trying HTTP fallback...');
|
|
326
|
+
}
|
|
327
|
+
const fallbackSaved = await saveTechnicalDesignViaHttp({
|
|
328
|
+
mcpServerUrl,
|
|
329
|
+
mcpToken,
|
|
330
|
+
featureId,
|
|
331
|
+
technicalDesign: structuredDesignResult.technical_design,
|
|
332
|
+
verbose,
|
|
333
|
+
});
|
|
334
|
+
if (!fallbackSaved && verbose) {
|
|
335
|
+
logError('⚠️ Both direct update and HTTP fallback failed');
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
featureId,
|
|
339
|
+
technicalDesign: structuredDesignResult.technical_design,
|
|
340
|
+
status: fallbackSaved ? 'success' : 'error',
|
|
341
|
+
summary: fallbackSaved
|
|
342
|
+
? 'Technical design generated and saved via HTTP fallback'
|
|
343
|
+
: 'Technical design generated but failed to save',
|
|
344
|
+
savedViaHttp: fallbackSaved,
|
|
345
|
+
data: {
|
|
346
|
+
checklist_item_results: structuredDesignResult.checklist_item_results || [],
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
featureId,
|
|
352
|
+
technicalDesign: structuredDesignResult.technical_design,
|
|
353
|
+
status: 'success',
|
|
354
|
+
summary: 'Technical design generated and saved successfully',
|
|
355
|
+
data: {
|
|
356
|
+
checklist_item_results: structuredDesignResult.checklist_item_results || [],
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
featureId,
|
|
362
|
+
technicalDesign: null,
|
|
363
|
+
status: 'error',
|
|
364
|
+
summary: 'No technical design was generated',
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
logError(`Technical design generation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
369
|
+
return {
|
|
370
|
+
featureId,
|
|
371
|
+
technicalDesign: null,
|
|
372
|
+
status: 'error',
|
|
373
|
+
summary: `Generation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
const extractTechnicalDesignFromResponse = (response) => {
|
|
378
|
+
// Try to extract technical design content from the response
|
|
379
|
+
// Look for markdown sections that contain technical design
|
|
380
|
+
const lines = response.split('\n');
|
|
381
|
+
let inTechnicalDesign = false;
|
|
382
|
+
const technicalDesignLines = [];
|
|
383
|
+
for (let i = 0; i < lines.length; i++) {
|
|
384
|
+
const line = lines[i].toLowerCase();
|
|
385
|
+
// Look for technical design section headers
|
|
386
|
+
if (line.includes('technical design') ||
|
|
387
|
+
line.includes('architecture') ||
|
|
388
|
+
line.includes('# design') ||
|
|
389
|
+
line.includes('## technical')) {
|
|
390
|
+
inTechnicalDesign = true;
|
|
391
|
+
technicalDesignLines.push(lines[i]);
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
if (inTechnicalDesign) {
|
|
395
|
+
// Continue until we hit another major section or JSON
|
|
396
|
+
if (line.startsWith('{') && line.includes('"technical_design_result"')) {
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
technicalDesignLines.push(lines[i]);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (technicalDesignLines.length > 0) {
|
|
403
|
+
return technicalDesignLines.join('\n').trim();
|
|
404
|
+
}
|
|
405
|
+
// Ultimate fallback: return the whole response if it seems to contain design content
|
|
406
|
+
if (response.length > 100 &&
|
|
407
|
+
(response.toLowerCase().includes('architecture') ||
|
|
408
|
+
response.toLowerCase().includes('component') ||
|
|
409
|
+
response.toLowerCase().includes('database'))) {
|
|
410
|
+
return response.trim();
|
|
411
|
+
}
|
|
412
|
+
return null;
|
|
413
|
+
};
|
|
414
|
+
export const checkTechnicalDesignRequirements = async () => {
|
|
415
|
+
try {
|
|
416
|
+
// Check if Claude Code SDK is available
|
|
417
|
+
const claudeCode = await import('@anthropic-ai/claude-code');
|
|
418
|
+
return claudeCode && typeof claudeCode.query === 'function';
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
console.log('Technical design requirements check failed:', error instanceof Error ? error.message : error);
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FeatureInfo, UserStory, TestCase } from '../../types/features.js';
|
|
2
|
+
import { type ProductInfo } from '../../api/products.js';
|
|
3
|
+
export interface TechnicalDesignContext {
|
|
4
|
+
feature: FeatureInfo;
|
|
5
|
+
product: ProductInfo;
|
|
6
|
+
user_stories: UserStory[];
|
|
7
|
+
test_cases: TestCase[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Fetch all technical design context information via MCP endpoints
|
|
11
|
+
*/
|
|
12
|
+
export declare function fetchTechnicalDesignContext(mcpServerUrl: string, mcpToken: string, featureId: string, verbose?: boolean): Promise<TechnicalDesignContext>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { logInfo, logError } from '../../utils/logger.js';
|
|
2
|
+
import { getFeature, getUserStories, getTestCases, } from '../../api/features/index.js';
|
|
3
|
+
import { getProduct } from '../../api/products.js';
|
|
4
|
+
/**
|
|
5
|
+
* Fetch all technical design context information via MCP endpoints
|
|
6
|
+
*/
|
|
7
|
+
export async function fetchTechnicalDesignContext(mcpServerUrl, mcpToken, featureId, verbose) {
|
|
8
|
+
try {
|
|
9
|
+
if (verbose) {
|
|
10
|
+
logInfo(`Fetching complete technical design context for feature: ${featureId}`);
|
|
11
|
+
}
|
|
12
|
+
// Fetch all required data in parallel for better performance
|
|
13
|
+
const [feature, user_stories, test_cases] = await Promise.all([
|
|
14
|
+
getFeature(mcpServerUrl, mcpToken, featureId, verbose),
|
|
15
|
+
getUserStories(mcpServerUrl, mcpToken, featureId, verbose),
|
|
16
|
+
getTestCases(mcpServerUrl, mcpToken, featureId, verbose),
|
|
17
|
+
]);
|
|
18
|
+
const product = await getProduct(mcpServerUrl, mcpToken, feature.product_id, verbose);
|
|
19
|
+
if (verbose) {
|
|
20
|
+
logInfo(`✅ Technical design context fetched successfully:`);
|
|
21
|
+
logInfo(` Feature: ${feature.name}`);
|
|
22
|
+
logInfo(` Product: ${product.name}`);
|
|
23
|
+
logInfo(` User Stories: ${user_stories.length}`);
|
|
24
|
+
logInfo(` Test Cases: ${test_cases.length} (${test_cases.filter((tc) => tc.is_critical).length} critical)`);
|
|
25
|
+
logInfo(` Existing Technical Design: ${feature.technical_design ? 'Yes' : 'No'}`);
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
feature,
|
|
29
|
+
product,
|
|
30
|
+
user_stories,
|
|
31
|
+
test_cases,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
36
|
+
logError(`Failed to fetch technical design context: ${errorMessage}`);
|
|
37
|
+
throw new Error(`Context fetch failed: ${errorMessage}`);
|
|
38
|
+
}
|
|
39
|
+
}
|