claude-autopm 1.30.0 → 1.31.0
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/autopm/.claude/scripts/github/dependency-tracker.js +554 -0
- package/autopm/.claude/scripts/github/dependency-validator.js +545 -0
- package/autopm/.claude/scripts/github/dependency-visualizer.js +477 -0
- package/autopm/.claude/scripts/pm/lib/epic-discovery.js +119 -0
- package/autopm/.claude/scripts/pm/next.js +56 -58
- package/autopm/.claude/scripts/pm/prd-new.js +33 -6
- package/autopm/.claude/scripts/pm/template-list.js +25 -3
- package/autopm/.claude/scripts/pm/template-new.js +25 -3
- package/autopm/lib/README-FILTER-SEARCH.md +285 -0
- package/autopm/lib/analytics-engine.js +689 -0
- package/autopm/lib/batch-processor-integration.js +366 -0
- package/autopm/lib/batch-processor.js +278 -0
- package/autopm/lib/burndown-chart.js +415 -0
- package/autopm/lib/conflict-history.js +316 -0
- package/autopm/lib/conflict-resolver.js +330 -0
- package/autopm/lib/dependency-analyzer.js +466 -0
- package/autopm/lib/filter-engine.js +414 -0
- package/autopm/lib/guide/interactive-guide.js +756 -0
- package/autopm/lib/guide/manager.js +663 -0
- package/autopm/lib/query-parser.js +322 -0
- package/autopm/lib/template-engine.js +347 -0
- package/autopm/lib/visual-diff.js +297 -0
- package/bin/autopm-poc.js +216 -0
- package/install/install.js +2 -1
- package/lib/ai-providers/ClaudeProvider.js +112 -0
- package/lib/ai-providers/base-provider.js +110 -0
- package/lib/services/PRDService.js +178 -0
- package/package.json +5 -2
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visual Diff Renderer
|
|
3
|
+
*
|
|
4
|
+
* Renders ASCII side-by-side comparisons of text differences.
|
|
5
|
+
* Highlights conflicting sections and provides context lines.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const VisualDiff = require('./lib/visual-diff');
|
|
9
|
+
*
|
|
10
|
+
* const diff = new VisualDiff({
|
|
11
|
+
* columnWidth: 80
|
|
12
|
+
* });
|
|
13
|
+
*
|
|
14
|
+
* // Render side-by-side comparison
|
|
15
|
+
* const rendered = diff.sideBySide(localContent, remoteContent);
|
|
16
|
+
* console.log(rendered);
|
|
17
|
+
*
|
|
18
|
+
* // Highlight conflicts
|
|
19
|
+
* const highlighted = diff.highlightConflicts(text, conflicts);
|
|
20
|
+
*
|
|
21
|
+
* // Render with context
|
|
22
|
+
* const context = diff.renderContext(text, [5, 10], 3);
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
class VisualDiff {
|
|
26
|
+
/**
|
|
27
|
+
* Create a new VisualDiff instance
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} options - Configuration options
|
|
30
|
+
* @param {number} options.columnWidth - Width of each column in side-by-side view (default: 60)
|
|
31
|
+
* @param {boolean} options.showLineNumbers - Show line numbers (default: true)
|
|
32
|
+
* @param {string} options.separator - Column separator (default: ' | ')
|
|
33
|
+
*/
|
|
34
|
+
constructor(options = {}) {
|
|
35
|
+
// Validate configuration
|
|
36
|
+
if (options.columnWidth !== undefined) {
|
|
37
|
+
if (typeof options.columnWidth !== 'number' || options.columnWidth <= 0) {
|
|
38
|
+
throw new Error('columnWidth must be a positive number');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.options = {
|
|
43
|
+
columnWidth: options.columnWidth || 60,
|
|
44
|
+
showLineNumbers: options.showLineNumbers !== false,
|
|
45
|
+
separator: options.separator || ' | '
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Render side-by-side comparison of two text versions
|
|
51
|
+
*
|
|
52
|
+
* @param {string} left - Left side content (local)
|
|
53
|
+
* @param {string} right - Right side content (remote)
|
|
54
|
+
* @param {Object} options - Rendering options
|
|
55
|
+
* @returns {string} ASCII side-by-side comparison
|
|
56
|
+
*/
|
|
57
|
+
sideBySide(left, right, options = {}) {
|
|
58
|
+
const leftLines = left.split('\n');
|
|
59
|
+
const rightLines = right.split('\n');
|
|
60
|
+
|
|
61
|
+
const maxLines = Math.max(leftLines.length, rightLines.length);
|
|
62
|
+
|
|
63
|
+
const output = [];
|
|
64
|
+
|
|
65
|
+
// Header
|
|
66
|
+
const colWidth = this.options.columnWidth;
|
|
67
|
+
const header = this._padRight('LOCAL', colWidth) +
|
|
68
|
+
this.options.separator +
|
|
69
|
+
this._padRight('REMOTE', colWidth);
|
|
70
|
+
output.push(header);
|
|
71
|
+
output.push('='.repeat(header.length));
|
|
72
|
+
|
|
73
|
+
// Content lines
|
|
74
|
+
for (let i = 0; i < maxLines; i++) {
|
|
75
|
+
const leftLine = leftLines[i] || '';
|
|
76
|
+
const rightLine = rightLines[i] || '';
|
|
77
|
+
|
|
78
|
+
const lineNum = this.options.showLineNumbers ? `${(i + 1).toString().padStart(4)} ` : '';
|
|
79
|
+
|
|
80
|
+
const leftContent = this._truncate(leftLine, colWidth - lineNum.length);
|
|
81
|
+
const rightContent = this._truncate(rightLine, colWidth - lineNum.length);
|
|
82
|
+
|
|
83
|
+
// Mark differences
|
|
84
|
+
const marker = leftLine !== rightLine ? '*' : ' ';
|
|
85
|
+
|
|
86
|
+
const line = marker + lineNum + this._padRight(leftContent, colWidth - lineNum.length - 1) +
|
|
87
|
+
this.options.separator +
|
|
88
|
+
marker + lineNum + this._padRight(rightContent, colWidth - lineNum.length - 1);
|
|
89
|
+
|
|
90
|
+
output.push(line);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return output.join('\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Highlight conflicts in text with conflict markers
|
|
98
|
+
*
|
|
99
|
+
* @param {string} text - Original text
|
|
100
|
+
* @param {Array<Object>} conflicts - Array of conflict objects
|
|
101
|
+
* @returns {string} Text with conflict markers inserted
|
|
102
|
+
*/
|
|
103
|
+
highlightConflicts(text, conflicts) {
|
|
104
|
+
const lines = text.split('\n');
|
|
105
|
+
|
|
106
|
+
// Sort conflicts by line number (ascending)
|
|
107
|
+
const sortedConflicts = [...conflicts].sort((a, b) => a.line - b.line);
|
|
108
|
+
|
|
109
|
+
// Build result in a single pass
|
|
110
|
+
const result = [];
|
|
111
|
+
let conflictIdx = 0;
|
|
112
|
+
for (let i = 0; i < lines.length; i++) {
|
|
113
|
+
// Check if current line matches the next conflict
|
|
114
|
+
if (
|
|
115
|
+
conflictIdx < sortedConflicts.length &&
|
|
116
|
+
sortedConflicts[conflictIdx].line - 1 === i
|
|
117
|
+
) {
|
|
118
|
+
const conflict = sortedConflicts[conflictIdx];
|
|
119
|
+
result.push('<<<<<<< LOCAL');
|
|
120
|
+
result.push(conflict.localContent);
|
|
121
|
+
result.push('=======');
|
|
122
|
+
result.push(conflict.remoteContent);
|
|
123
|
+
result.push('>>>>>>> REMOTE');
|
|
124
|
+
conflictIdx++;
|
|
125
|
+
} else {
|
|
126
|
+
result.push(lines[i]);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return result.join('\n');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Render context lines around specified line numbers
|
|
135
|
+
*
|
|
136
|
+
* @param {string} text - Full text content
|
|
137
|
+
* @param {Array<number>} lineNumbers - Line numbers to show context for
|
|
138
|
+
* @param {number} contextLines - Number of context lines before and after (default: 3)
|
|
139
|
+
* @returns {string} Text with only context lines
|
|
140
|
+
*/
|
|
141
|
+
renderContext(text, lineNumbers, contextLines = 3) {
|
|
142
|
+
const lines = text.split('\n');
|
|
143
|
+
const lineSet = new Set();
|
|
144
|
+
|
|
145
|
+
// Add context lines for each specified line number
|
|
146
|
+
for (const lineNum of lineNumbers) {
|
|
147
|
+
const lineIndex = lineNum - 1; // Convert to 0-based
|
|
148
|
+
|
|
149
|
+
// Add lines in context range
|
|
150
|
+
const start = Math.max(0, lineIndex - contextLines);
|
|
151
|
+
const end = Math.min(lines.length - 1, lineIndex + contextLines);
|
|
152
|
+
|
|
153
|
+
for (let i = start; i <= end; i++) {
|
|
154
|
+
lineSet.add(i);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Convert to sorted array
|
|
159
|
+
const includedLines = Array.from(lineSet).sort((a, b) => a - b);
|
|
160
|
+
|
|
161
|
+
// Build output with ellipsis for gaps
|
|
162
|
+
const output = [];
|
|
163
|
+
let lastLine = -2;
|
|
164
|
+
|
|
165
|
+
for (const lineIndex of includedLines) {
|
|
166
|
+
// Add ellipsis if there's a gap
|
|
167
|
+
if (lineIndex > lastLine + 1) {
|
|
168
|
+
output.push('...');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Add the line with line number
|
|
172
|
+
const lineNum = (lineIndex + 1).toString().padStart(4);
|
|
173
|
+
output.push(`${lineNum} | ${lines[lineIndex]}`);
|
|
174
|
+
|
|
175
|
+
lastLine = lineIndex;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return output.join('\n');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Calculate diff statistics
|
|
183
|
+
*
|
|
184
|
+
* @param {string} left - Left side content
|
|
185
|
+
* @param {string} right - Right side content
|
|
186
|
+
* @returns {Object} Diff statistics
|
|
187
|
+
*/
|
|
188
|
+
getStats(left, right) {
|
|
189
|
+
const leftLines = left.split('\n');
|
|
190
|
+
const rightLines = right.split('\n');
|
|
191
|
+
|
|
192
|
+
let added = 0;
|
|
193
|
+
let removed = 0;
|
|
194
|
+
let modified = 0;
|
|
195
|
+
let unchanged = 0;
|
|
196
|
+
|
|
197
|
+
const maxLines = Math.max(leftLines.length, rightLines.length);
|
|
198
|
+
|
|
199
|
+
for (let i = 0; i < maxLines; i++) {
|
|
200
|
+
const leftLine = leftLines[i];
|
|
201
|
+
const rightLine = rightLines[i];
|
|
202
|
+
|
|
203
|
+
if (leftLine === undefined && rightLine !== undefined) {
|
|
204
|
+
added++;
|
|
205
|
+
} else if (leftLine !== undefined && rightLine === undefined) {
|
|
206
|
+
removed++;
|
|
207
|
+
} else if (leftLine !== rightLine) {
|
|
208
|
+
modified++;
|
|
209
|
+
} else {
|
|
210
|
+
unchanged++;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
added,
|
|
216
|
+
removed,
|
|
217
|
+
modified,
|
|
218
|
+
unchanged,
|
|
219
|
+
total: maxLines
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Truncate text to fit within column width
|
|
225
|
+
*
|
|
226
|
+
* @private
|
|
227
|
+
* @param {string} text - Text to truncate
|
|
228
|
+
* @param {number} maxWidth - Maximum width
|
|
229
|
+
* @returns {string} Truncated text
|
|
230
|
+
*/
|
|
231
|
+
_truncate(text, maxWidth) {
|
|
232
|
+
if (text.length <= maxWidth) {
|
|
233
|
+
return text;
|
|
234
|
+
}
|
|
235
|
+
// If maxWidth is less than 3, we can't fit the ellipsis, so just truncate to maxWidth
|
|
236
|
+
if (maxWidth < 3) {
|
|
237
|
+
return text.substring(0, maxWidth);
|
|
238
|
+
}
|
|
239
|
+
return text.substring(0, maxWidth - 3) + '...';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Pad text to the right with spaces
|
|
244
|
+
*
|
|
245
|
+
* @private
|
|
246
|
+
* @param {string} text - Text to pad
|
|
247
|
+
* @param {number} width - Target width
|
|
248
|
+
* @returns {string} Padded text
|
|
249
|
+
*/
|
|
250
|
+
_padRight(text, width) {
|
|
251
|
+
if (text.length >= width) {
|
|
252
|
+
return text.substring(0, width);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return text + ' '.repeat(width - text.length);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Render unified diff format (similar to git diff)
|
|
260
|
+
*
|
|
261
|
+
* @param {string} left - Left side content
|
|
262
|
+
* @param {string} right - Right side content
|
|
263
|
+
* @param {Object} options - Rendering options
|
|
264
|
+
* @returns {string} Unified diff output
|
|
265
|
+
*/
|
|
266
|
+
unified(left, right, options = {}) {
|
|
267
|
+
const leftLines = left.split('\n');
|
|
268
|
+
const rightLines = right.split('\n');
|
|
269
|
+
|
|
270
|
+
const output = [];
|
|
271
|
+
output.push('--- LOCAL');
|
|
272
|
+
output.push('+++ REMOTE');
|
|
273
|
+
output.push(`@@ -1,${leftLines.length} +1,${rightLines.length} @@`);
|
|
274
|
+
|
|
275
|
+
const maxLines = Math.max(leftLines.length, rightLines.length);
|
|
276
|
+
|
|
277
|
+
for (let i = 0; i < maxLines; i++) {
|
|
278
|
+
const leftLine = leftLines[i];
|
|
279
|
+
const rightLine = rightLines[i];
|
|
280
|
+
|
|
281
|
+
if (leftLine === rightLine) {
|
|
282
|
+
output.push(` ${leftLine || ''}`);
|
|
283
|
+
} else if (leftLine === undefined) {
|
|
284
|
+
output.push(`+${rightLine}`);
|
|
285
|
+
} else if (rightLine === undefined) {
|
|
286
|
+
output.push(`-${leftLine}`);
|
|
287
|
+
} else {
|
|
288
|
+
output.push(`-${leftLine}`);
|
|
289
|
+
output.push(`+${rightLine}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return output.join('\n');
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
module.exports = VisualDiff;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AutoPM POC - CLI for testing Claude API integration
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* autopm-poc parse <prd-file> - Parse PRD with streaming output
|
|
7
|
+
* autopm-poc parse <prd-file> --json - Parse PRD and output JSON
|
|
8
|
+
* autopm-poc summarize <prd-file> - Get one-paragraph summary
|
|
9
|
+
* autopm-poc test - Test API connection
|
|
10
|
+
*
|
|
11
|
+
* Environment:
|
|
12
|
+
* ANTHROPIC_API_KEY - Required for all operations
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const ClaudeProvider = require('../lib/ai-providers/ClaudeProvider');
|
|
18
|
+
const PRDService = require('../lib/services/PRDService');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Print usage information
|
|
22
|
+
*/
|
|
23
|
+
function printUsage() {
|
|
24
|
+
console.log(`
|
|
25
|
+
AutoPM POC - Claude API Integration Demo
|
|
26
|
+
=========================================
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
autopm-poc parse <prd-file> Parse PRD with streaming output
|
|
30
|
+
autopm-poc parse <prd-file> --json Parse PRD and extract epics as JSON
|
|
31
|
+
autopm-poc summarize <prd-file> Get one-paragraph summary
|
|
32
|
+
autopm-poc test Test API connection
|
|
33
|
+
autopm-poc help Show this help message
|
|
34
|
+
|
|
35
|
+
Environment Variables:
|
|
36
|
+
ANTHROPIC_API_KEY Required - Your Anthropic API key
|
|
37
|
+
|
|
38
|
+
Examples:
|
|
39
|
+
export ANTHROPIC_API_KEY="sk-ant-..."
|
|
40
|
+
autopm-poc parse examples/sample-prd.md
|
|
41
|
+
autopm-poc summarize examples/sample-prd.md
|
|
42
|
+
autopm-poc parse examples/sample-prd.md --json
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Test API connection
|
|
48
|
+
*/
|
|
49
|
+
async function testConnection(provider) {
|
|
50
|
+
console.log('🔍 Testing API connection...\n');
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const result = await provider.complete('Say "Connection successful!"', {
|
|
54
|
+
maxTokens: 50
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
console.log('✅ API Connection Test: SUCCESS');
|
|
58
|
+
console.log(`📝 Response: ${result}\n`);
|
|
59
|
+
return true;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error('❌ API Connection Test: FAILED');
|
|
62
|
+
console.error(` Error: ${error.message}\n`);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Parse PRD with streaming output
|
|
69
|
+
*/
|
|
70
|
+
async function parsePRDStream(service, content) {
|
|
71
|
+
console.log('🔍 Analyzing PRD with Claude AI...\n');
|
|
72
|
+
console.log('📝 Streaming response:\n');
|
|
73
|
+
console.log('─'.repeat(60));
|
|
74
|
+
|
|
75
|
+
let fullResponse = '';
|
|
76
|
+
try {
|
|
77
|
+
for await (const chunk of service.parseStream(content)) {
|
|
78
|
+
process.stdout.write(chunk);
|
|
79
|
+
fullResponse += chunk;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log('\n' + '─'.repeat(60));
|
|
83
|
+
console.log('\n✅ Analysis complete!');
|
|
84
|
+
console.log(`📊 Total response length: ${fullResponse.length} characters\n`);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error('\n❌ Error during parsing:', error.message);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Parse PRD and extract epics as JSON
|
|
93
|
+
*/
|
|
94
|
+
async function parsePRDJson(service, content) {
|
|
95
|
+
console.log('🔍 Extracting epics from PRD...\n');
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const epics = await service.extractEpics(content);
|
|
99
|
+
|
|
100
|
+
console.log('✅ Extraction complete!\n');
|
|
101
|
+
console.log('📋 Extracted Epics:\n');
|
|
102
|
+
console.log(JSON.stringify(epics, null, 2));
|
|
103
|
+
console.log(`\n📊 Found ${Array.isArray(epics) ? epics.length : 0} epic(s)\n`);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error('❌ Error during JSON extraction:', error.message);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Summarize PRD
|
|
112
|
+
*/
|
|
113
|
+
async function summarizePRD(service, content) {
|
|
114
|
+
console.log('🔍 Summarizing PRD...\n');
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const summary = await service.summarize(content);
|
|
118
|
+
|
|
119
|
+
console.log('✅ Summary complete!\n');
|
|
120
|
+
console.log('📝 Summary:\n');
|
|
121
|
+
console.log(summary);
|
|
122
|
+
console.log();
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error('❌ Error during summarization:', error.message);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Main entry point
|
|
131
|
+
*/
|
|
132
|
+
async function main() {
|
|
133
|
+
const args = process.argv.slice(2);
|
|
134
|
+
const command = args[0];
|
|
135
|
+
|
|
136
|
+
// Handle help
|
|
137
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
138
|
+
printUsage();
|
|
139
|
+
process.exit(0);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Check for API key
|
|
143
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
144
|
+
if (!apiKey) {
|
|
145
|
+
console.error('❌ Error: ANTHROPIC_API_KEY environment variable required\n');
|
|
146
|
+
console.error('Set it with:');
|
|
147
|
+
console.error(' export ANTHROPIC_API_KEY="sk-ant-..."\n');
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Initialize provider and service
|
|
152
|
+
const provider = new ClaudeProvider(apiKey);
|
|
153
|
+
const service = new PRDService(provider);
|
|
154
|
+
|
|
155
|
+
// Handle test command
|
|
156
|
+
if (command === 'test') {
|
|
157
|
+
const success = await testConnection(provider);
|
|
158
|
+
process.exit(success ? 0 : 1);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Handle parse and summarize commands
|
|
162
|
+
if (command === 'parse' || command === 'summarize') {
|
|
163
|
+
const file = args[1];
|
|
164
|
+
|
|
165
|
+
if (!file) {
|
|
166
|
+
console.error(`❌ Error: PRD file required for '${command}' command\n`);
|
|
167
|
+
console.error(`Usage: autopm-poc ${command} <prd-file>\n`);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Check if file exists
|
|
172
|
+
if (!fs.existsSync(file)) {
|
|
173
|
+
console.error(`❌ Error: File not found: ${file}\n`);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Read file content
|
|
178
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
179
|
+
|
|
180
|
+
if (!content.trim()) {
|
|
181
|
+
console.error('❌ Error: PRD file is empty\n');
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
console.log(`📄 Reading PRD from: ${file}`);
|
|
186
|
+
console.log(`📏 File size: ${content.length} characters\n`);
|
|
187
|
+
|
|
188
|
+
// Execute command
|
|
189
|
+
if (command === 'parse') {
|
|
190
|
+
const jsonFlag = args[2] === '--json';
|
|
191
|
+
|
|
192
|
+
if (jsonFlag) {
|
|
193
|
+
await parsePRDJson(service, content);
|
|
194
|
+
} else {
|
|
195
|
+
await parsePRDStream(service, content);
|
|
196
|
+
}
|
|
197
|
+
} else if (command === 'summarize') {
|
|
198
|
+
await summarizePRD(service, content);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
process.exit(0);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Unknown command
|
|
205
|
+
console.error(`❌ Error: Unknown command: ${command}\n`);
|
|
206
|
+
printUsage();
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Run main with error handling
|
|
211
|
+
main().catch(err => {
|
|
212
|
+
console.error('❌ Fatal error:', err.message);
|
|
213
|
+
console.error('\nStack trace:');
|
|
214
|
+
console.error(err.stack);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
});
|
package/install/install.js
CHANGED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClaudeProvider - Anthropic Claude API integration
|
|
3
|
+
*
|
|
4
|
+
* Provides both synchronous completion and streaming capabilities
|
|
5
|
+
* for Claude AI interactions.
|
|
6
|
+
*
|
|
7
|
+
* Documentation Queries:
|
|
8
|
+
* - mcp://context7/anthropic/sdk - Anthropic SDK patterns
|
|
9
|
+
* - mcp://context7/anthropic/streaming - Streaming best practices
|
|
10
|
+
* - mcp://context7/nodejs/async-generators - Async generator patterns
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const Anthropic = require('@anthropic-ai/sdk');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* ClaudeProvider class for Anthropic Claude API integration
|
|
17
|
+
*/
|
|
18
|
+
class ClaudeProvider {
|
|
19
|
+
/**
|
|
20
|
+
* Create a new ClaudeProvider instance
|
|
21
|
+
* @param {string} apiKey - Anthropic API key
|
|
22
|
+
* @throws {Error} If API key is not provided
|
|
23
|
+
*/
|
|
24
|
+
constructor(apiKey) {
|
|
25
|
+
if (!apiKey) {
|
|
26
|
+
throw new Error('API key is required for ClaudeProvider');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.apiKey = apiKey;
|
|
30
|
+
this.client = new Anthropic({ apiKey });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Complete a prompt synchronously (wait for full response)
|
|
35
|
+
* @param {string} prompt - The prompt to complete
|
|
36
|
+
* @param {Object} options - Optional configuration
|
|
37
|
+
* @param {string} options.model - Model to use (default: claude-sonnet-4-20250514)
|
|
38
|
+
* @param {number} options.maxTokens - Maximum tokens to generate (default: 4096)
|
|
39
|
+
* @returns {Promise<string>} The completed text
|
|
40
|
+
*/
|
|
41
|
+
async complete(prompt, options = {}) {
|
|
42
|
+
try {
|
|
43
|
+
const response = await this.client.messages.create({
|
|
44
|
+
model: options.model || 'claude-sonnet-4-20250514',
|
|
45
|
+
max_tokens: options.maxTokens || 4096,
|
|
46
|
+
messages: [{ role: 'user', content: prompt }]
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Extract text from response
|
|
50
|
+
if (response.content && response.content.length > 0) {
|
|
51
|
+
return response.content[0].text;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return '';
|
|
55
|
+
} catch (error) {
|
|
56
|
+
throw new Error(`Claude API error: ${error.message}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Stream a prompt response (async generator for real-time feedback)
|
|
62
|
+
* @param {string} prompt - The prompt to complete
|
|
63
|
+
* @param {Object} options - Optional configuration
|
|
64
|
+
* @param {string} options.model - Model to use (default: claude-sonnet-4-20250514)
|
|
65
|
+
* @param {number} options.maxTokens - Maximum tokens to generate (default: 4096)
|
|
66
|
+
* @yields {string} Text chunks as they arrive
|
|
67
|
+
*/
|
|
68
|
+
async *stream(prompt, options = {}) {
|
|
69
|
+
try {
|
|
70
|
+
const stream = await this.client.messages.create({
|
|
71
|
+
model: options.model || 'claude-sonnet-4-20250514',
|
|
72
|
+
max_tokens: options.maxTokens || 4096,
|
|
73
|
+
stream: true,
|
|
74
|
+
messages: [{ role: 'user', content: prompt }]
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Yield text deltas as they arrive
|
|
78
|
+
for await (const event of stream) {
|
|
79
|
+
if (event.type === 'content_block_delta' &&
|
|
80
|
+
event.delta &&
|
|
81
|
+
event.delta.type === 'text_delta') {
|
|
82
|
+
yield event.delta.text;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw new Error(`Claude API streaming error: ${error.message}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get the current model being used
|
|
92
|
+
* @returns {string} The default model name
|
|
93
|
+
*/
|
|
94
|
+
getModel() {
|
|
95
|
+
return 'claude-sonnet-4-20250514';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Test the API connection
|
|
100
|
+
* @returns {Promise<boolean>} True if connection is successful
|
|
101
|
+
*/
|
|
102
|
+
async testConnection() {
|
|
103
|
+
try {
|
|
104
|
+
await this.complete('Hello');
|
|
105
|
+
return true;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = ClaudeProvider;
|