edsger 0.4.8 → 0.4.10

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/cli/index.js CHANGED
@@ -6,12 +6,8 @@ import { fileURLToPath } from 'url';
6
6
  import { config as dotenvConfig } from 'dotenv';
7
7
  import { logError } from '../utils/logger.js';
8
8
  import { runWorkflow } from './commands/workflow-command.js';
9
- import { runFeatureAnalysis } from './commands/feature-analysis-command.js';
10
- import { runTechnicalDesign } from './commands/technical-design-command.js';
11
- import { runCodeImplementation } from './commands/code-implementation-command.js';
12
- import { runFunctionalTestingCommand } from './commands/functional-testing-command.js';
13
- import { runCodeReview } from './commands/code-review-command.js';
14
- import { runRefactor } from './commands/refactor-command.js';
9
+ import { runCodeReview } from '../commands/code-review/index.js';
10
+ import { runRefactor } from '../commands/refactor/refactor.js';
15
11
  // Get package.json version dynamically
16
12
  const __filename = fileURLToPath(import.meta.url);
17
13
  const __dirname = dirname(__filename);
@@ -30,10 +26,6 @@ program
30
26
  .option('-s, --staged', 'Review only staged changes')
31
27
  .option('-f, --files <patterns...>', 'Review specific file patterns')
32
28
  .option('--refactor', 'Refactor code in current directory')
33
- .option('--feature-analysis <featureId>', 'Analyze feature and generate user stories/test cases')
34
- .option('--technical-design <featureId>', 'Generate technical design for a feature')
35
- .option('--implement <featureId>', 'Implement code for a feature based on its specifications')
36
- .option('--test <featureId>', 'Run functional tests for a feature using Playwright')
37
29
  .option('--workflow', 'Run continuous workflow processor to handle ready_for_dev features')
38
30
  .option('-c, --config <path>', 'Path to config file')
39
31
  .option('-v, --verbose', 'Verbose output');
@@ -52,26 +44,6 @@ export const runEdsger = async (options) => {
52
44
  await runWorkflow(options);
53
45
  return;
54
46
  }
55
- // Handle feature analysis mode
56
- if (options.featureAnalysis) {
57
- await runFeatureAnalysis(options);
58
- return;
59
- }
60
- // Handle technical design mode
61
- if (options.technicalDesign) {
62
- await runTechnicalDesign(options);
63
- return;
64
- }
65
- // Handle code implementation mode
66
- if (options.implement) {
67
- await runCodeImplementation(options);
68
- return;
69
- }
70
- // Handle functional testing mode
71
- if (options.test) {
72
- await runFunctionalTestingCommand(options);
73
- return;
74
- }
75
47
  // Handle refactor mode
76
48
  if (options.refactor) {
77
49
  await runRefactor(options);
package/dist/cli.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- export * from './cli/index.js';
3
- import './cli/index.js';
2
+ import { CliOptions } from './types/index.js';
3
+ export declare const runEdsger: (options: CliOptions) => Promise<void>;
package/dist/cli.js CHANGED
@@ -1,5 +1,78 @@
1
1
  #!/usr/bin/env node
2
- // Re-export the CLI module
3
- export * from './cli/index.js';
4
- // Import and trigger the CLI parsing if this file is run directly
5
- import './cli/index.js';
2
+ import { Command } from 'commander';
3
+ import { readFileSync } from 'fs';
4
+ import { join, dirname } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { config as dotenvConfig } from 'dotenv';
7
+ import { logError } from './utils/logger.js';
8
+ import { runWorkflow } from './commands/workflow/index.js';
9
+ import { runCodeReview } from './commands/code-review/index.js';
10
+ import { runRefactor } from './commands/refactor/refactor.js';
11
+ // Get package.json version dynamically
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+ const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
15
+ const version = packageJson.version;
16
+ // Load environment variables from .env file
17
+ // Load from current working directory (where the command is run)
18
+ dotenvConfig({ path: join(process.cwd(), '.env') });
19
+ const program = new Command();
20
+ program
21
+ .name('edsger')
22
+ .description('AI-powered code review CLI tool using Claude Code SDK')
23
+ .version(version);
24
+ program
25
+ .option('-r, --review', 'Review code changes')
26
+ .option('-s, --staged', 'Review only staged changes')
27
+ .option('-f, --files <patterns...>', 'Review specific file patterns')
28
+ .option('--refactor', 'Refactor code in current directory')
29
+ .option('--workflow', 'Run continuous workflow processor to handle ready_for_dev features')
30
+ .option('-c, --config <path>', 'Path to config file')
31
+ .option('-v, --verbose', 'Verbose output');
32
+ program.action(async (options) => {
33
+ try {
34
+ await runEdsger(options);
35
+ }
36
+ catch (error) {
37
+ logError(error instanceof Error ? error.message : String(error));
38
+ process.exit(1);
39
+ }
40
+ });
41
+ export const runEdsger = async (options) => {
42
+ // Handle workflow mode
43
+ if (options.workflow) {
44
+ await runWorkflow(options);
45
+ return;
46
+ }
47
+ // Handle refactor mode
48
+ if (options.refactor) {
49
+ await runRefactor(options);
50
+ return;
51
+ }
52
+ // Handle code review mode (explicit --review flag or as fallback)
53
+ if (options.review || options.staged || options.files) {
54
+ await runCodeReview(options);
55
+ return;
56
+ }
57
+ // Default to workflow mode if environment is configured, otherwise code review
58
+ const productId = process.env.EDSGER_PRODUCT_ID;
59
+ const mcpToken = process.env.EDSGER_MCP_TOKEN;
60
+ if (productId && mcpToken) {
61
+ // Environment is configured for workflow, use that as default
62
+ options.workflow = true;
63
+ await runWorkflow(options);
64
+ }
65
+ else {
66
+ // Default to code review mode
67
+ await runCodeReview(options);
68
+ }
69
+ };
70
+ // Only parse when this file is run directly (not imported as module)
71
+ // Check if this is the main module being executed
72
+ const isMainModule = import.meta.url === `file://${process.argv[1]}` ||
73
+ process.argv[1]?.endsWith('/edsger') ||
74
+ process.argv[1]?.endsWith('\\edsger') ||
75
+ process.argv[1]?.endsWith('cli.js');
76
+ if (isMainModule) {
77
+ program.parse();
78
+ }
@@ -0,0 +1,2 @@
1
+ import { CliOptions } from '../../types/index.js';
2
+ export declare const runCodeReview: (options: CliOptions) => Promise<void>;
@@ -0,0 +1,39 @@
1
+ import { reviewWithClaudeSDK, checkClaudeCodeSDK } from './reviewer.js';
2
+ import { logInfo, logError, logSuccess, logResults, } from '../../utils/logger.js';
3
+ import { validateConfiguration, validateRequirements, } from '../../utils/validation.js';
4
+ export const runCodeReview = async (options) => {
5
+ // Check Claude Code SDK availability
6
+ await validateRequirements(checkClaudeCodeSDK, 'Claude Code SDK not found. Install with: npm install @anthropic-ai/claude-code');
7
+ // Load and validate configuration
8
+ const config = validateConfiguration(options);
9
+ if (options.verbose) {
10
+ logInfo(`Using Claude Code SDK for ${options.staged ? 'staged' : 'all'} changes`);
11
+ }
12
+ logInfo('Starting Claude Code review...');
13
+ try {
14
+ // Use Claude Code SDK to review changes
15
+ const results = await reviewWithClaudeSDK(config, options.staged || false, options.files);
16
+ // Display results
17
+ logResults(results, options.verbose);
18
+ // Determine exit code
19
+ const hasBlockingIssues = results.some((r) => r.status === 'BLOCK');
20
+ const hasWarnings = results.some((r) => r.status === 'WARN');
21
+ const exitCode = hasBlockingIssues || (hasWarnings && config.severity === 'error') ? 1 : 0;
22
+ if (exitCode === 0) {
23
+ logSuccess('Review completed successfully');
24
+ }
25
+ else {
26
+ if (hasBlockingIssues) {
27
+ logError('Review failed with blocking issues');
28
+ }
29
+ else if (hasWarnings) {
30
+ logError('Review failed with warnings');
31
+ }
32
+ }
33
+ process.exit(exitCode);
34
+ }
35
+ catch (error) {
36
+ logError(`Review failed: ${error instanceof Error ? error.message : String(error)}`);
37
+ process.exit(1);
38
+ }
39
+ };
@@ -0,0 +1,3 @@
1
+ import { EdsgerConfig, ReviewResult } from '../../types/index.js';
2
+ export declare const reviewWithClaudeSDK: (config: EdsgerConfig, staged?: boolean, filePatterns?: string[]) => Promise<ReviewResult[]>;
3
+ export declare const checkClaudeCodeSDK: () => Promise<boolean>;
@@ -0,0 +1,192 @@
1
+ import { query } from '@anthropic-ai/claude-code';
2
+ export const reviewWithClaudeSDK = async (config, staged = false, filePatterns) => {
3
+ const systemPrompt = createSystemPrompt(config);
4
+ const reviewPrompt = createReviewPrompt(staged, filePatterns);
5
+ try {
6
+ const results = [];
7
+ for await (const message of query({
8
+ prompt: reviewPrompt,
9
+ options: {
10
+ appendSystemPrompt: systemPrompt,
11
+ model: config.claude.model || 'sonnet',
12
+ },
13
+ })) {
14
+ // Stream the review process
15
+ if (message.type === 'assistant' && message.message?.content) {
16
+ for (const content of message.message.content) {
17
+ if (content.type === 'text') {
18
+ console.log(`\n🤖 ${content.text}`);
19
+ }
20
+ else if (content.type === 'tool_use') {
21
+ console.log(`\n🔧 ${content.name}: ${content.input.description || content.input.command || 'Running...'}`);
22
+ }
23
+ }
24
+ }
25
+ if (message.type === 'result') {
26
+ if (message.subtype === 'success') {
27
+ console.log('\n📊 Review completed, parsing results...');
28
+ const parsedResults = parseSDKResponse(message.result);
29
+ results.push(...parsedResults);
30
+ }
31
+ else {
32
+ console.log(`\n⚠️ Review incomplete: ${message.subtype}`);
33
+ if (message.subtype === 'error_max_turns') {
34
+ console.log('💡 Try reducing maxFiles in config or increase timeout');
35
+ }
36
+ }
37
+ }
38
+ }
39
+ return results.length > 0
40
+ ? results
41
+ : [
42
+ {
43
+ file: 'No Changes',
44
+ status: 'OK',
45
+ message: 'No files to review or no changes detected',
46
+ details: undefined,
47
+ },
48
+ ];
49
+ }
50
+ catch (error) {
51
+ throw new Error(`Claude Code SDK review failed: ${error instanceof Error ? error.message : String(error)}`);
52
+ }
53
+ };
54
+ const createSystemPrompt = (config) => {
55
+ return `You are an expert code reviewer using Claude Code. Your task is to review git repository changes.
56
+
57
+ IMPORTANT INSTRUCTIONS:
58
+ 1. Use the Bash tool to check git status and find changed files
59
+ 2. Use the Read tool to examine file contents
60
+ 3. Use the Grep tool to search for patterns or related code
61
+ 4. Analyze code quality, bugs, security issues, and best practices
62
+
63
+ REVIEW CRITERIA:
64
+ - Severity level: ${config.severity}
65
+ - Max files to review: ${config.maxFiles}
66
+ - Include patterns: ${config.patterns.join(', ')}
67
+ - Exclude patterns: ${config.exclude.join(', ')}
68
+
69
+ RESPONSE FORMAT:
70
+ You MUST end your response with a JSON object containing the review results. Use this EXACT format:
71
+
72
+ {
73
+ "reviews": [
74
+ {
75
+ "file": "path/to/file.js",
76
+ "status": "OK|WARN|BLOCK",
77
+ "message": "Brief description",
78
+ "details": "Detailed explanation if needed"
79
+ }
80
+ ]
81
+ }
82
+
83
+ STATUS GUIDELINES:
84
+ - OK: Code looks good, no issues
85
+ - WARN: Minor issues, suggestions for improvement
86
+ - BLOCK: Serious issues that should prevent commit (use only when severity is "error")
87
+
88
+ IMPORTANT: After your analysis, you MUST provide the JSON results at the end of your response. Start by examining the repository state and then review the appropriate files.`;
89
+ };
90
+ const createReviewPrompt = (staged, filePatterns) => {
91
+ const changeType = staged ? 'staged changes' : 'uncommitted changes';
92
+ const patternsText = filePatterns?.length
93
+ ? ` matching patterns: ${filePatterns.join(', ')}`
94
+ : '';
95
+ const gitCommand = staged
96
+ ? 'git diff --cached --name-only'
97
+ : 'git status --porcelain';
98
+ return `Please review the ${changeType} in this git repository${patternsText}.
99
+
100
+ IMPORTANT: Start by running "${gitCommand}" to see what files have changed. Then read and analyze each changed file.
101
+
102
+ Steps to follow:
103
+ 1. Run "${gitCommand}" to identify changed files
104
+ 2. For each changed file, use the Read tool to examine its contents
105
+ 3. Analyze code quality, bugs, security issues, and best practices
106
+ 4. Provide structured feedback using the required format
107
+
108
+ If you find files that have changed, review them. If no files are found, clearly state "No changes detected".
109
+
110
+ Focus on:
111
+ - Code quality and maintainability
112
+ - Potential bugs or security vulnerabilities
113
+ - Performance issues
114
+ - Adherence to best practices
115
+ - Breaking changes
116
+ - Missing error handling
117
+
118
+ Please start by running the git command to check for changes.`;
119
+ };
120
+ const parseSDKResponse = (response) => {
121
+ console.log('Parsing JSON response...');
122
+ try {
123
+ // Try to find JSON in the response
124
+ const jsonMatch = response.match(/```json\s*([\s\S]*?)\s*```/) ||
125
+ response.match(/\{[\s\S]*"reviews"[\s\S]*\}/);
126
+ if (jsonMatch) {
127
+ const jsonStr = jsonMatch[1] || jsonMatch[0];
128
+ console.log('Found JSON:', jsonStr.substring(0, 200) + '...');
129
+ const parsed = JSON.parse(jsonStr);
130
+ if (parsed.reviews && Array.isArray(parsed.reviews)) {
131
+ const results = parsed.reviews.map((review) => ({
132
+ file: review.file || 'Unknown file',
133
+ status: review.status || 'OK',
134
+ message: review.message || 'No message provided',
135
+ details: review.details || undefined,
136
+ }));
137
+ console.log(`Parsed ${results.length} results from JSON`);
138
+ results.forEach((r) => console.log(`- ${r.file}: ${r.status}`));
139
+ return results;
140
+ }
141
+ }
142
+ // Fallback: look for JSON without markdown
143
+ const lines = response.split('\n');
144
+ for (let i = 0; i < lines.length; i++) {
145
+ const line = lines[i].trim();
146
+ if (line.startsWith('{') && line.includes('"reviews"')) {
147
+ // Try to parse from this line to the end
148
+ const potentialJson = lines.slice(i).join('\n').trim();
149
+ try {
150
+ const parsed = JSON.parse(potentialJson);
151
+ if (parsed.reviews) {
152
+ const results = parsed.reviews.map((review) => ({
153
+ file: review.file || 'Unknown file',
154
+ status: review.status || 'OK',
155
+ message: review.message || 'No message provided',
156
+ details: review.details || undefined,
157
+ }));
158
+ console.log(`Parsed ${results.length} results from fallback JSON`);
159
+ return results;
160
+ }
161
+ }
162
+ catch {
163
+ continue;
164
+ }
165
+ }
166
+ }
167
+ }
168
+ catch (error) {
169
+ console.log('JSON parsing failed:', error);
170
+ }
171
+ // Ultimate fallback: return a generic result
172
+ console.log('Using fallback parser due to JSON parse failure');
173
+ return [
174
+ {
175
+ file: 'Parse Error',
176
+ status: 'WARN',
177
+ message: 'Failed to parse review results from Claude response',
178
+ details: 'The response was not in the expected JSON format',
179
+ },
180
+ ];
181
+ };
182
+ export const checkClaudeCodeSDK = async () => {
183
+ try {
184
+ // Try to import and use the SDK
185
+ const claudeCode = await import('@anthropic-ai/claude-code');
186
+ return claudeCode && typeof claudeCode.query === 'function';
187
+ }
188
+ catch (error) {
189
+ console.log('SDK check failed:', error instanceof Error ? error.message : error);
190
+ return false;
191
+ }
192
+ };
@@ -0,0 +1,2 @@
1
+ import { CliOptions } from '../../types/index.js';
2
+ export declare const runRefactor: (options: CliOptions) => Promise<void>;
@@ -0,0 +1,123 @@
1
+ import { query } from '@anthropic-ai/claude-code';
2
+ import { logInfo, logError, logSuccess } from '../../utils/logger.js';
3
+ import { validateConfiguration } from '../../utils/validation.js';
4
+ export const runRefactor = async (options) => {
5
+ // Load and validate configuration
6
+ const config = validateConfiguration(options);
7
+ if (options.verbose) {
8
+ logInfo('Starting automatic code refactoring for current directory...');
9
+ }
10
+ logInfo('Analyzing and refactoring code...');
11
+ try {
12
+ const refactorPrompt = createRefactorPrompt(config);
13
+ const systemPrompt = createSystemPrompt();
14
+ let hasCompleted = false;
15
+ for await (const message of query({
16
+ prompt: refactorPrompt,
17
+ options: {
18
+ appendSystemPrompt: systemPrompt,
19
+ model: config.claude.model || 'sonnet',
20
+ permissionMode: 'bypassPermissions',
21
+ },
22
+ })) {
23
+ // Stream the refactoring process
24
+ if (message.type === 'assistant' && message.message?.content) {
25
+ for (const content of message.message.content) {
26
+ if (content.type === 'text') {
27
+ console.log(`\n🤖 ${content.text}`);
28
+ }
29
+ else if (content.type === 'tool_use') {
30
+ console.log(`\n🔧 ${content.name}: ${content.input.description || content.input.command || 'Running...'}`);
31
+ }
32
+ }
33
+ }
34
+ if (message.type === 'result') {
35
+ hasCompleted = true;
36
+ if (message.subtype === 'success') {
37
+ logSuccess('Code refactoring completed successfully');
38
+ process.exit(0);
39
+ }
40
+ else {
41
+ logError(`Refactoring incomplete: ${message.subtype}`);
42
+ if (message.subtype === 'error_max_turns') {
43
+ console.log('💡 Try simplifying the codebase or increase timeout');
44
+ }
45
+ process.exit(1);
46
+ }
47
+ }
48
+ }
49
+ if (!hasCompleted) {
50
+ logError('Refactoring process did not complete');
51
+ process.exit(1);
52
+ }
53
+ }
54
+ catch (error) {
55
+ logError(`Refactoring failed: ${error instanceof Error ? error.message : String(error)}`);
56
+ process.exit(1);
57
+ }
58
+ };
59
+ const createSystemPrompt = () => {
60
+ return `You are an expert code refactoring assistant using Claude Code. Your task is to analyze and AUTOMATICALLY REFACTOR code in the current directory to improve quality and maintainability.
61
+
62
+ IMPORTANT INSTRUCTIONS:
63
+ 1. Use the Bash tool to explore the project structure (ls, find, etc.)
64
+ 2. Use the Read tool to examine file contents
65
+ 3. Use the Grep tool to search for patterns, code smells, or duplications
66
+ 4. Use the Edit tool to DIRECTLY apply refactoring changes
67
+ 5. You have FULL PERMISSION to make refactoring changes without asking for confirmation
68
+
69
+ REFACTORING FOCUS AREAS (prioritized):
70
+ 1. Code duplication and DRY violations (HIGHEST PRIORITY)
71
+ 2. Complex functions that need simplification
72
+ 3. Poor naming conventions
73
+ 4. Missing type annotations (TypeScript)
74
+ 5. Inefficient algorithms or data structures
75
+ 6. Code organization and structure
76
+ 7. Missing or inadequate error handling
77
+ 8. Opportunities for better abstraction
78
+ 9. Performance improvements
79
+
80
+ WORKFLOW:
81
+ 1. First, explore the project structure to understand the codebase
82
+ 2. Identify files that would benefit from refactoring
83
+ 3. Analyze specific issues and patterns (focus on high-impact issues first)
84
+ 4. For each issue found:
85
+ - Explain what you're refactoring and why
86
+ - Apply the refactoring using the Edit tool
87
+ - Verify the change is correct
88
+ 5. Continue until all high-priority refactorings are complete or you run out of turns
89
+
90
+ SAFETY GUIDELINES:
91
+ - Start with safest refactorings (extract utilities, remove duplication)
92
+ - Preserve existing functionality - only improve structure/quality
93
+ - Focus on non-breaking changes
94
+ - Make atomic commits for each logical refactoring
95
+
96
+ IMPORTANT: You should DIRECTLY perform refactorings without asking for permission. The user has granted you full bypass authority to improve the code.`;
97
+ };
98
+ const createRefactorPrompt = (config) => {
99
+ return `Please analyze and REFACTOR the code in the current directory. You have full permission to make improvements directly.
100
+
101
+ TASK: Automatically identify and fix code quality issues
102
+
103
+ Steps to follow:
104
+ 1. Start by running "ls -la" to see the project structure
105
+ 2. Identify key source files (focus on patterns: ${config.patterns.join(', ')})
106
+ 3. Read and analyze relevant source files
107
+ 4. Look for HIGH-PRIORITY issues:
108
+ - Code duplication (MOST IMPORTANT - fix first)
109
+ - Complex or long functions
110
+ - Poor naming
111
+ - Missing types or documentation
112
+ - Error handling issues
113
+ - Performance bottlenecks
114
+ 5. For EACH issue found, IMMEDIATELY apply the fix:
115
+ - Use the Edit tool to make the change
116
+ - Explain what you changed and why
117
+ - Move to the next issue
118
+ 6. Continue refactoring until all high-priority issues are addressed
119
+
120
+ IMPORTANT: Do NOT ask for permission. You have full bypass authority to improve the code directly. Start refactoring immediately after analysis.
121
+
122
+ Start by exploring the current directory structure and begin refactoring.`;
123
+ };
@@ -0,0 +1,2 @@
1
+ import { CliOptions } from '../../types/index.js';
2
+ export declare const runWorkflow: (options: CliOptions) => Promise<void>;
@@ -0,0 +1,34 @@
1
+ import { startWorkflowProcessor } from '../../workflow-runner/workflow-processor.js';
2
+ import { logInfo, logError, logSuccess } from '../../utils/logger.js';
3
+ import { setupWorkflowProcessor } from '../../utils/workflow-utils.js';
4
+ export const runWorkflow = async (options) => {
5
+ const { config, params } = setupWorkflowProcessor(options);
6
+ try {
7
+ // Start workflow processor using functional composition
8
+ const startProcessor = startWorkflowProcessor(params, config);
9
+ const processor = await startProcessor();
10
+ // Handle graceful shutdown
11
+ const cleanup = () => {
12
+ logInfo('\n⏹️ Received shutdown signal, stopping workflow processor...');
13
+ processor.stop();
14
+ // Show final stats
15
+ const stats = processor.getStats();
16
+ logInfo('📊 Final Statistics:');
17
+ logInfo(` Total processed: ${stats.totalProcessed}`);
18
+ logInfo(` Completed: ${stats.completed}`);
19
+ logInfo(` Failed: ${stats.failed}`);
20
+ logInfo(` Processing: ${stats.processing}`);
21
+ logSuccess('Workflow processor stopped gracefully');
22
+ process.exit(0);
23
+ };
24
+ // Set up signal handlers for graceful shutdown
25
+ process.on('SIGINT', cleanup);
26
+ process.on('SIGTERM', cleanup);
27
+ // Keep the process running
28
+ process.stdin.resume();
29
+ }
30
+ catch (error) {
31
+ logError(`Workflow processor failed: ${error instanceof Error ? error.message : String(error)}`);
32
+ process.exit(1);
33
+ }
34
+ };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { runEdsger } from './cli/index.js';
1
+ export { runEdsger } from './cli.js';
2
2
  export { loadConfig, validateConfig } from './config.js';
3
- export { reviewWithClaudeSDK, checkClaudeCodeSDK, } from './phases/code-review/reviewer.js';
3
+ export { reviewWithClaudeSDK, checkClaudeCodeSDK, } from './commands/code-review/reviewer.js';
4
4
  export type { EdsgerConfig, ReviewResult, CliOptions } from './types/index.js';
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export { runEdsger } from './cli/index.js';
1
+ export { runEdsger } from './cli.js';
2
2
  export { loadConfig, validateConfig } from './config.js';
3
- export { reviewWithClaudeSDK, checkClaudeCodeSDK, } from './phases/code-review/reviewer.js';
3
+ export { reviewWithClaudeSDK, checkClaudeCodeSDK, } from './commands/code-review/reviewer.js';
@@ -9,29 +9,76 @@ import { logInfo, logError } from '../../utils/logger.js';
9
9
  import { parsePullRequestUrl, fetchPRReviews, } from '../code-refine/context-fetcher.js';
10
10
  import { getFeature } from '../../api/features/get-feature.js';
11
11
  /**
12
- * Fetch PR file changes (diff information)
12
+ * Fetch complete file content from a specific ref (branch/commit)
13
+ */
14
+ async function fetchFileContent(octokit, owner, repo, ref, path, verbose) {
15
+ try {
16
+ const { data } = await octokit.repos.getContent({
17
+ owner,
18
+ repo,
19
+ ref,
20
+ path,
21
+ });
22
+ // Handle file content (not directory)
23
+ if ('content' in data && !Array.isArray(data)) {
24
+ // Content is base64 encoded
25
+ return Buffer.from(data.content, 'base64').toString('utf-8');
26
+ }
27
+ return undefined;
28
+ }
29
+ catch (error) {
30
+ if (verbose) {
31
+ logError(`Failed to fetch content for ${path} at ref ${ref}: ${error}`);
32
+ }
33
+ return undefined;
34
+ }
35
+ }
36
+ /**
37
+ * Fetch PR file changes (diff information and full content)
13
38
  */
14
39
  async function fetchPRFileChanges(octokit, owner, repo, prNumber, verbose) {
15
40
  try {
16
41
  if (verbose) {
17
- logInfo('📂 Fetching PR file changes...');
42
+ logInfo('📂 Fetching PR file changes and content...');
18
43
  }
44
+ // Get PR details to get the head ref
45
+ const { data: pr } = await octokit.pulls.get({
46
+ owner,
47
+ repo,
48
+ pull_number: prNumber,
49
+ });
50
+ const headRef = pr.head.sha;
51
+ // Get list of changed files
19
52
  const { data: files } = await octokit.pulls.listFiles({
20
53
  owner,
21
54
  repo,
22
55
  pull_number: prNumber,
23
56
  per_page: 100,
24
57
  });
25
- const fileChanges = files.map((file) => ({
26
- filename: file.filename,
27
- status: file.status,
28
- additions: file.additions,
29
- deletions: file.deletions,
30
- changes: file.changes,
31
- patch: file.patch,
58
+ if (verbose) {
59
+ logInfo(`✅ Found ${files.length} changed files`);
60
+ logInfo(`📥 Fetching full content for changed files from ${headRef.substring(0, 7)}...`);
61
+ }
62
+ // Fetch full content for each file in parallel
63
+ const fileChanges = await Promise.all(files.map(async (file) => {
64
+ let fullContent;
65
+ // Only fetch content for files that were not deleted
66
+ if (file.status !== 'removed') {
67
+ fullContent = await fetchFileContent(octokit, owner, repo, headRef, file.filename, verbose);
68
+ }
69
+ return {
70
+ filename: file.filename,
71
+ status: file.status,
72
+ additions: file.additions,
73
+ deletions: file.deletions,
74
+ changes: file.changes,
75
+ patch: file.patch,
76
+ fullContent,
77
+ };
32
78
  }));
33
79
  if (verbose) {
34
- logInfo(`✅ Found ${fileChanges.length} changed files`);
80
+ const filesWithContent = fileChanges.filter((f) => f.fullContent).length;
81
+ logInfo(`✅ Retrieved full content for ${filesWithContent}/${fileChanges.length} files`);
35
82
  }
36
83
  return fileChanges;
37
84
  }
@@ -221,6 +268,35 @@ function createThreadAnalysisPrompt(thread, fileChange, firstComment) {
221
268
  const allComments = thread.comments.nodes
222
269
  .map((c, idx) => `Comment ${idx + 1} by @${c.author.login}:\n${c.body}`)
223
270
  .join('\n\n');
271
+ // Build the code context section
272
+ let codeContext = '';
273
+ // Include diff (what changed)
274
+ if (fileChange.patch) {
275
+ codeContext += `**Code Changes (Diff):**
276
+ \`\`\`diff
277
+ ${fileChange.patch}
278
+ \`\`\`
279
+
280
+ `;
281
+ }
282
+ // Include full file content for better context
283
+ if (fileChange.fullContent) {
284
+ // Truncate very large files to avoid context limits
285
+ const maxLength = 10000;
286
+ const content = fileChange.fullContent.length > maxLength
287
+ ? fileChange.fullContent.substring(0, maxLength) +
288
+ '\n\n... (file truncated, showing first 10000 characters)'
289
+ : fileChange.fullContent;
290
+ codeContext += `**Complete File Content (After Changes):**
291
+ \`\`\`
292
+ ${content}
293
+ \`\`\`
294
+
295
+ `;
296
+ }
297
+ if (!codeContext) {
298
+ codeContext = '(No code information available)\n\n';
299
+ }
224
300
  return `You are analyzing whether a code review comment has been addressed by subsequent code changes.
225
301
 
226
302
  **Review Thread Information:**
@@ -231,18 +307,24 @@ function createThreadAnalysisPrompt(thread, fileChange, firstComment) {
231
307
  **Review Comments:**
232
308
  ${allComments}
233
309
 
234
- **Code Changes in This File:**
235
- \`\`\`diff
236
- ${fileChange.patch || '(No patch available)'}
237
- \`\`\`
310
+ ${codeContext}
238
311
 
239
312
  **Your Task:**
240
- Analyze whether the code changes adequately address the feedback in the review comments. Consider:
313
+ Analyze whether the code changes adequately address the feedback in the review comments. You have access to:
314
+ 1. **The diff** showing what was changed
315
+ 2. **The complete file content** after changes for full context
316
+
317
+ Consider:
241
318
  1. Does the feedback request a specific code change?
242
319
  2. Have those changes (or equivalent changes) been made?
243
- 3. If the feedback points to a specific line, have related areas of code been modified to address the concern?
320
+ 3. If the feedback points to a specific line, use the full file content to understand the broader context
321
+ 4. Check if related areas of code were modified to address the concern
322
+ 5. Use the complete file to verify the fix is properly integrated
244
323
 
245
- **Important:** The comment may point to a specific line, but the fix might be in nearby code. Focus on whether the **underlying issue** was addressed, not just whether that exact line was modified.
324
+ **Important:**
325
+ - The comment may point to a specific line, but the fix might be in nearby code
326
+ - Focus on whether the **underlying issue** was addressed, not just whether that exact line was modified
327
+ - Use the full file content to understand the complete context and verify the implementation
246
328
 
247
329
  Return your analysis in this JSON format:
248
330
  \`\`\`json
@@ -253,9 +335,10 @@ Return your analysis in this JSON format:
253
335
  \`\`\`
254
336
 
255
337
  Example responses:
256
- - If a comment says "add null check" and the diff shows a null check was added nearby: {"isAddressed": true, "reason": "Null check added in related code"}
257
- - If a comment suggests a refactor but no relevant changes are visible: {"isAddressed": false, "reason": "No refactoring changes visible in diff"}
338
+ - If a comment says "add null check" and the code shows a null check was added: {"isAddressed": true, "reason": "Null check added in the updated code"}
339
+ - If a comment suggests a refactor but no relevant changes are visible: {"isAddressed": false, "reason": "No refactoring changes visible in diff or file content"}
258
340
  - If code was modified in related areas addressing the concern: {"isAddressed": true, "reason": "Related code modified to address the concern"}
341
+ - If the full context shows the issue is resolved differently: {"isAddressed": true, "reason": "Issue resolved through alternative implementation visible in full file"}
259
342
  `;
260
343
  }
261
344
  /**
@@ -25,10 +25,6 @@ export interface CliOptions {
25
25
  files?: string[];
26
26
  config?: string;
27
27
  verbose?: boolean;
28
- featureAnalysis?: string;
29
- technicalDesign?: string;
30
- implement?: string;
31
- test?: string;
32
28
  workflow?: boolean;
33
29
  refactor?: boolean;
34
30
  }
@@ -0,0 +1,25 @@
1
+ import { CliOptions } from '../types/index.js';
2
+ export interface ValidationResult {
3
+ config: any;
4
+ mcpServerUrl: string;
5
+ mcpToken: string;
6
+ }
7
+ /**
8
+ * Common configuration validation for all CLI commands
9
+ */
10
+ export declare const validateConfiguration: (options: CliOptions) => any;
11
+ /**
12
+ * Validate MCP environment variables and URL format
13
+ */
14
+ export declare const validateMCPEnvironment: () => {
15
+ mcpServerUrl: string;
16
+ mcpToken: string;
17
+ };
18
+ /**
19
+ * Combined validation for commands that need both config and MCP setup
20
+ */
21
+ export declare const validateCommandEnvironment: (options: CliOptions) => ValidationResult;
22
+ /**
23
+ * Check if required dependencies are installed
24
+ */
25
+ export declare const validateRequirements: (checkFunction: () => Promise<boolean>, errorMessage: string) => Promise<void>;
@@ -0,0 +1,58 @@
1
+ import { loadConfig, validateConfig } from '../config.js';
2
+ import { logInfo } from './logger.js';
3
+ /**
4
+ * Common configuration validation for all CLI commands
5
+ */
6
+ export const validateConfiguration = (options) => {
7
+ const config = loadConfig(options.config);
8
+ const configErrors = validateConfig(config);
9
+ if (configErrors.length > 0) {
10
+ throw new Error(`Configuration errors:\n${configErrors.map((e) => ` - ${e}`).join('\n')}`);
11
+ }
12
+ if (options.verbose) {
13
+ logInfo('Configuration loaded successfully');
14
+ }
15
+ return config;
16
+ };
17
+ /**
18
+ * Validate MCP environment variables and URL format
19
+ */
20
+ export const validateMCPEnvironment = () => {
21
+ const mcpServerUrl = process.env.EDSGER_MCP_SERVER_URL;
22
+ const mcpToken = process.env.EDSGER_MCP_TOKEN;
23
+ if (!mcpServerUrl) {
24
+ throw new Error('MCP server URL is required. Set EDSGER_MCP_SERVER_URL environment variable.');
25
+ }
26
+ if (!mcpToken) {
27
+ throw new Error('MCP token is required. Set EDSGER_MCP_TOKEN environment variable.');
28
+ }
29
+ // Validate MCP server URL format
30
+ try {
31
+ new URL(mcpServerUrl);
32
+ }
33
+ catch {
34
+ throw new Error('Invalid MCP server URL format. Use http:// or https://');
35
+ }
36
+ return { mcpServerUrl, mcpToken };
37
+ };
38
+ /**
39
+ * Combined validation for commands that need both config and MCP setup
40
+ */
41
+ export const validateCommandEnvironment = (options) => {
42
+ const config = validateConfiguration(options);
43
+ const { mcpServerUrl, mcpToken } = validateMCPEnvironment();
44
+ return {
45
+ config,
46
+ mcpServerUrl,
47
+ mcpToken,
48
+ };
49
+ };
50
+ /**
51
+ * Check if required dependencies are installed
52
+ */
53
+ export const validateRequirements = async (checkFunction, errorMessage) => {
54
+ const hasRequirements = await checkFunction();
55
+ if (!hasRequirements) {
56
+ throw new Error(errorMessage);
57
+ }
58
+ };
@@ -0,0 +1,21 @@
1
+ import { CliOptions } from '../types/index.js';
2
+ /**
3
+ * Validate workflow-specific environment variables
4
+ */
5
+ export declare const validateWorkflowEnvironment: () => {
6
+ productId: string;
7
+ mcpServerUrl: string;
8
+ mcpToken: string;
9
+ };
10
+ /**
11
+ * Setup workflow processor parameters
12
+ */
13
+ export declare const setupWorkflowProcessor: (options: CliOptions) => {
14
+ config: any;
15
+ params: {
16
+ productId: string;
17
+ mcpServerUrl: string;
18
+ mcpToken: string;
19
+ verbose?: boolean;
20
+ };
21
+ };
@@ -0,0 +1,47 @@
1
+ import { validateConfiguration } from './validation.js';
2
+ import { logInfo } from './logger.js';
3
+ /**
4
+ * Validate workflow-specific environment variables
5
+ */
6
+ export const validateWorkflowEnvironment = () => {
7
+ const productId = process.env.EDSGER_PRODUCT_ID;
8
+ const mcpServerUrl = process.env.EDSGER_MCP_SERVER_URL;
9
+ const mcpToken = process.env.EDSGER_MCP_TOKEN;
10
+ if (!productId) {
11
+ throw new Error('Product ID is required. Set EDSGER_PRODUCT_ID environment variable.');
12
+ }
13
+ if (!mcpServerUrl) {
14
+ throw new Error('MCP server URL is required. Set EDSGER_MCP_SERVER_URL environment variable.');
15
+ }
16
+ if (!mcpToken) {
17
+ throw new Error('MCP token is required. Set EDSGER_MCP_TOKEN environment variable.');
18
+ }
19
+ // Validate MCP server URL format
20
+ try {
21
+ new URL(mcpServerUrl);
22
+ }
23
+ catch {
24
+ throw new Error('Invalid MCP server URL format. Use http:// or https://');
25
+ }
26
+ return { productId, mcpServerUrl, mcpToken };
27
+ };
28
+ /**
29
+ * Setup workflow processor parameters
30
+ */
31
+ export const setupWorkflowProcessor = (options) => {
32
+ const config = validateConfiguration(options);
33
+ if (options.verbose) {
34
+ logInfo('Starting workflow processor mode...');
35
+ }
36
+ const { productId, mcpServerUrl, mcpToken } = validateWorkflowEnvironment();
37
+ logInfo('🚀 Starting workflow processor...');
38
+ return {
39
+ config,
40
+ params: {
41
+ productId,
42
+ mcpServerUrl,
43
+ mcpToken,
44
+ verbose: options.verbose,
45
+ },
46
+ };
47
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edsger",
3
- "version": "0.4.8",
3
+ "version": "0.4.10",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {