claude-autopm 1.30.1 → 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.
@@ -1,6 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { logError } = require('./lib/logger');
4
+ const { findAllEpicDirs } = require('./lib/epic-discovery');
4
5
 
5
6
  /**
6
7
  * PM Next Script (Node.js version)
@@ -106,69 +107,66 @@ function displayTddReminder(addMessage) {
106
107
  async function findAvailableTasks() {
107
108
  const availableTasks = [];
108
109
 
109
- if (!fs.existsSync('.claude/epics')) {
110
- return availableTasks;
111
- }
110
+ // Use shared epic discovery utility
111
+ const epicDirs = findAllEpicDirs();
112
112
 
113
- try {
114
- const epicDirs = fs.readdirSync('.claude/epics', { withFileTypes: true })
115
- .filter(dirent => dirent.isDirectory())
116
- .map(dirent => dirent.name);
117
-
118
- for (const epicName of epicDirs) {
119
- const epicPath = path.join('.claude/epics', epicName);
120
-
121
- try {
122
- const taskFiles = fs.readdirSync(epicPath)
123
- .filter(file => /^[0-9].*\.md$/.test(file))
124
- .sort();
125
-
126
- for (const taskFile of taskFiles) {
127
- const taskPath = path.join(epicPath, taskFile);
128
-
129
- try {
130
- const content = fs.readFileSync(taskPath, 'utf8');
131
-
132
- // Check if task is open
133
- const statusMatch = content.match(/^status:\s*(.+)$/m);
134
- const status = statusMatch ? statusMatch[1].trim() : '';
135
-
136
- // Skip non-open tasks (only open tasks or tasks without status are available)
137
- if (status !== 'open' && status !== '') {
138
- continue;
139
- }
140
-
141
- // Check dependencies
142
- const depsMatch = content.match(/^depends_on:\s*\[(.*?)\]/m);
143
- const depsStr = depsMatch ? depsMatch[1].trim() : '';
144
-
145
- // If no dependencies or empty dependencies, task is available
146
- if (!depsStr || depsStr === '') {
147
- const nameMatch = content.match(/^name:\s*(.+)$/m);
148
- const name = nameMatch ? nameMatch[1].trim() : 'Unnamed Task';
149
-
150
- const parallelMatch = content.match(/^parallel:\s*(.+)$/m);
151
- const parallel = parallelMatch ? parallelMatch[1].trim() === 'true' : false;
152
-
153
- const taskNum = path.basename(taskFile, '.md');
154
-
155
- availableTasks.push({
156
- taskNum,
157
- name,
158
- epicName,
159
- parallel
160
- });
161
- }
162
- } catch (err) {
163
- // Skip files we can't read
113
+ for (const epicDir of epicDirs) {
114
+ const { name: epicName, path: epicPath } = epicDir;
115
+
116
+ try {
117
+ const taskFiles = fs.readdirSync(epicPath)
118
+ .filter(file => /^\d+.*\.md$/.test(file))
119
+ .sort();
120
+
121
+ for (const taskFile of taskFiles) {
122
+ const taskPath = path.join(epicPath, taskFile);
123
+
124
+ try {
125
+ const content = fs.readFileSync(taskPath, 'utf8');
126
+
127
+ // Check if task is open (case-insensitive)
128
+ const statusMatch = content.match(/^status:\s*(.+)$/m);
129
+ const status = statusMatch ? statusMatch[1].trim().toLowerCase() : '';
130
+
131
+ // Skip non-open tasks (only open tasks or tasks without status are available)
132
+ if (status !== 'open' && status !== '') {
133
+ continue;
134
+ }
135
+
136
+ // Check dependencies
137
+ const depsMatch = content.match(/^depends_on:\s*\[(.*?)\]/m);
138
+ const depsStr = depsMatch ? depsMatch[1].trim() : '';
139
+
140
+ // If no dependencies or empty dependencies, task is available
141
+ if (!depsStr || depsStr === '') {
142
+ const nameMatch = content.match(/^name:\s*(.+)$/m);
143
+ const name = nameMatch ? nameMatch[1].trim() : 'Unnamed Task';
144
+
145
+ const parallelMatch = content.match(/^parallel:\s*(.+)$/m);
146
+ const parallel = parallelMatch ? parallelMatch[1].trim() === 'true' : false;
147
+
148
+ const taskNum = path.basename(taskFile, '.md');
149
+
150
+ availableTasks.push({
151
+ taskNum,
152
+ name,
153
+ epicName,
154
+ parallel
155
+ });
156
+ }
157
+ } catch (err) {
158
+ // Log file read errors in DEBUG mode
159
+ if (process.env.DEBUG) {
160
+ console.error(`Error reading task file ${taskPath}:`, err.message);
164
161
  }
165
162
  }
166
- } catch (err) {
167
- // Skip directories we can't read
163
+ }
164
+ } catch (err) {
165
+ // Log directory read errors in DEBUG mode
166
+ if (process.env.DEBUG) {
167
+ console.error(`Error reading epic directory ${epicPath}:`, err.message);
168
168
  }
169
169
  }
170
- } catch (err) {
171
- // Silently handle errors
172
170
  }
173
171
 
174
172
  return availableTasks;
@@ -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
+ });
@@ -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;
@@ -0,0 +1,178 @@
1
+ /**
2
+ * PRDService - Product Requirements Document parsing with AI
3
+ *
4
+ * Analyzes PRD documents and extracts structured information
5
+ * including epics, features, dependencies, and estimates.
6
+ *
7
+ * Documentation Queries:
8
+ * - mcp://context7/agile/prd-analysis - PRD analysis best practices
9
+ * - mcp://context7/agile/epic-breakdown - Epic decomposition patterns
10
+ * - mcp://context7/project-management/estimation - Estimation techniques
11
+ */
12
+
13
+ /**
14
+ * PRDService class for analyzing Product Requirements Documents
15
+ */
16
+ class PRDService {
17
+ /**
18
+ * Create a new PRDService instance
19
+ * @param {Object} aiProvider - AI provider instance (e.g., ClaudeProvider)
20
+ */
21
+ constructor(aiProvider) {
22
+ if (!aiProvider) {
23
+ throw new Error('AI provider is required for PRDService');
24
+ }
25
+ this.ai = aiProvider;
26
+ }
27
+
28
+ /**
29
+ * Build a comprehensive prompt for PRD analysis
30
+ * @param {string} prdContent - The PRD content to analyze
31
+ * @returns {string} Structured prompt for AI
32
+ * @private
33
+ */
34
+ _buildPrompt(prdContent) {
35
+ return `You are an expert product manager and technical analyst. Analyze the following Product Requirements Document (PRD) and extract structured information.
36
+
37
+ PRD Content:
38
+ ${prdContent}
39
+
40
+ Please analyze and provide:
41
+
42
+ 1. **Project Overview**
43
+ - Project name
44
+ - Brief description
45
+ - Goals and objectives
46
+
47
+ 2. **Epics/Features** (Main features or user stories)
48
+ - Name of each epic
49
+ - Description
50
+ - Rough estimate (if mentioned)
51
+ - Priority (High/Medium/Low)
52
+
53
+ 3. **Technical Requirements**
54
+ - Technologies mentioned
55
+ - Infrastructure needs
56
+ - Integration points
57
+
58
+ 4. **Dependencies**
59
+ - Dependencies between features
60
+ - External dependencies
61
+ - Blockers or constraints
62
+
63
+ 5. **Timeline/Phases** (if mentioned)
64
+ - Phases or milestones
65
+ - Estimated durations
66
+
67
+ Please structure your response clearly with headers and bullet points. Focus on actionable insights that would help with project planning and task breakdown.`;
68
+ }
69
+
70
+ /**
71
+ * Build a simpler prompt for streaming analysis
72
+ * @param {string} prdContent - The PRD content to analyze
73
+ * @returns {string} Prompt for streaming
74
+ * @private
75
+ */
76
+ _buildStreamPrompt(prdContent) {
77
+ return `Analyze this PRD and extract the key epics, features, and technical requirements:
78
+
79
+ ${prdContent}
80
+
81
+ Provide a clear, structured breakdown of:
82
+ 1. Main features/epics
83
+ 2. Technical requirements
84
+ 3. Dependencies
85
+ 4. Estimated timeline (if mentioned)`;
86
+ }
87
+
88
+ /**
89
+ * Parse PRD synchronously (wait for complete analysis)
90
+ * @param {string} prdContent - The PRD content to analyze
91
+ * @param {Object} options - Optional configuration
92
+ * @returns {Promise<string>} Structured analysis of the PRD
93
+ */
94
+ async parse(prdContent, options = {}) {
95
+ if (!prdContent) {
96
+ return 'No PRD content provided. Please provide a PRD document to analyze.';
97
+ }
98
+
99
+ const prompt = this._buildPrompt(prdContent);
100
+
101
+ try {
102
+ return await this.ai.complete(prompt, options);
103
+ } catch (error) {
104
+ throw new Error(`PRD parsing error: ${error.message}`);
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Parse PRD with streaming (async generator for real-time feedback)
110
+ * @param {string} prdContent - The PRD content to analyze
111
+ * @param {Object} options - Optional configuration
112
+ * @yields {string} Analysis chunks as they arrive
113
+ */
114
+ async *parseStream(prdContent, options = {}) {
115
+ if (!prdContent) {
116
+ yield 'No PRD content provided. Please provide a PRD document to analyze.';
117
+ return;
118
+ }
119
+
120
+ const prompt = this._buildStreamPrompt(prdContent);
121
+
122
+ try {
123
+ for await (const chunk of this.ai.stream(prompt, options)) {
124
+ yield chunk;
125
+ }
126
+ } catch (error) {
127
+ throw new Error(`PRD streaming error: ${error.message}`);
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Extract epics from PRD (simplified extraction)
133
+ * @param {string} prdContent - The PRD content
134
+ * @returns {Promise<Array>} Array of epic objects
135
+ */
136
+ async extractEpics(prdContent) {
137
+ const prompt = `Extract all epics/features from this PRD and return them as a JSON array:
138
+
139
+ ${prdContent}
140
+
141
+ Format: [{"name": "Epic Name", "description": "Brief description", "estimate": "time estimate if available"}]
142
+
143
+ Return ONLY the JSON array, no other text.`;
144
+
145
+ try {
146
+ const response = await this.ai.complete(prompt, { maxTokens: 2048 });
147
+
148
+ // Try to parse as JSON
149
+ try {
150
+ return JSON.parse(response);
151
+ } catch (parseError) {
152
+ // If parsing fails, return raw response wrapped in array
153
+ return [{ raw: response }];
154
+ }
155
+ } catch (error) {
156
+ throw new Error(`Epic extraction error: ${error.message}`);
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Summarize PRD in one paragraph
162
+ * @param {string} prdContent - The PRD content
163
+ * @returns {Promise<string>} One-paragraph summary
164
+ */
165
+ async summarize(prdContent) {
166
+ const prompt = `Summarize this PRD in one concise paragraph (2-3 sentences):
167
+
168
+ ${prdContent}`;
169
+
170
+ try {
171
+ return await this.ai.complete(prompt, { maxTokens: 256 });
172
+ } catch (error) {
173
+ throw new Error(`PRD summarization error: ${error.message}`);
174
+ }
175
+ }
176
+ }
177
+
178
+ module.exports = PRDService;
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "claude-autopm",
3
- "version": "1.30.1",
3
+ "version": "1.31.0",
4
4
  "description": "Autonomous Project Management Framework for Claude Code - Advanced AI-powered development automation",
5
5
  "main": "bin/autopm.js",
6
6
  "bin": {
7
- "autopm": "./bin/autopm.js"
7
+ "autopm": "./bin/autopm.js",
8
+ "autopm-poc": "./bin/autopm-poc.js"
8
9
  },
9
10
  "scripts": {
10
11
  "postinstall": "echo '🎉 ClaudeAutoPM installed! Run: autopm --help'",
@@ -52,6 +53,7 @@
52
53
  "test:unit:coverage": "c8 node --test $(find test/unit -name '*.test.js' ! -name '*-jest.test.js')",
53
54
  "test:e2e": "jest test/e2e",
54
55
  "test:e2e:legacy": "node --test test/e2e/*.test.js",
56
+ "test:poc": "jest --config jest.config.poc.js",
55
57
  "setup:hooks": "bash scripts/setup-hooks.sh",
56
58
  "lint": "markdownlint *.md .claude/**/*.md install/*.md",
57
59
  "lint:fix": "markdownlint --fix *.md .claude/**/*.md install/*.md",
@@ -114,6 +116,7 @@
114
116
  "README.md"
115
117
  ],
116
118
  "dependencies": {
119
+ "@anthropic-ai/sdk": "^0.32.1",
117
120
  "@octokit/rest": "^22.0.0",
118
121
  "azure-devops-node-api": "^15.1.1",
119
122
  "chalk": "^5.3.0",