claude-autopm 3.0.2 → 3.1.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.
@@ -26,7 +26,7 @@ const {
26
26
  syncTaskToGitHub,
27
27
  loadSyncMap,
28
28
  saveSyncMap
29
- } = require('../autopm/.claude/scripts/pm-sync-upload-local');
29
+ } = require('../.claude/scripts/pm-sync-upload-local');
30
30
 
31
31
  /**
32
32
  * Batch sync PRDs to GitHub
@@ -26,7 +26,7 @@ const {
26
26
  syncTaskToGitHub,
27
27
  loadSyncMap,
28
28
  saveSyncMap
29
- } = require('../autopm/.claude/scripts/pm-sync-upload-local');
29
+ } = require('../.claude/scripts/pm-sync-upload-local');
30
30
 
31
31
  /**
32
32
  * Batch sync PRDs to GitHub
@@ -38,7 +38,7 @@ class AgentService {
38
38
  }
39
39
 
40
40
  this.aiProvider = aiProvider;
41
- this.agentsBaseDir = path.join(process.cwd(), 'autopm/.claude/agents');
41
+ this.agentsBaseDir = path.join(process.cwd(), '.claude/agents');
42
42
  this.conversationHistories = new Map();
43
43
 
44
44
  // Cache for loaded agents (performance optimization)
@@ -61,8 +61,8 @@ const path = require('path');
61
61
  class TemplateEngine {
62
62
  constructor(builtInDir, userDir) {
63
63
  // For testing, allow custom built-in directory and user directory
64
- // In production, use autopm/.claude/templates and .claude/templates
65
- this.builtInDir = builtInDir || path.join(__dirname, '..', 'autopm', '.claude', 'templates');
64
+ // In production, use .claude/templates (framework templates are copied during installation)
65
+ this.builtInDir = builtInDir || path.join(__dirname, '..', '.claude', 'templates');
66
66
  this.userDir = userDir || path.join('.claude', 'templates');
67
67
  }
68
68
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-autopm",
3
- "version": "3.0.2",
3
+ "version": "3.1.0",
4
4
  "description": "Autonomous Project Management Framework for Claude Code - Advanced AI-powered development automation",
5
5
  "main": "bin/autopm.js",
6
6
  "workspaces": [
@@ -0,0 +1 @@
1
+ throw new Error("Provider script error");
@@ -67,7 +67,7 @@ epic_number=$(gh issue create \
67
67
  - Created: $(date -u +"%Y-%m-%dT%H:%M:%SZ")
68
68
  " \
69
69
  --label "$labels" \
70
- 2>&1 | grep -oP '(?<=#)[0-9]+' | head -1)
70
+ 2>&1 | grep -o '#[0-9]\+' | head -1 | sed 's/#//')
71
71
 
72
72
  if [[ -z "$epic_number" ]]; then
73
73
  echo "❌ Error: Failed to create epic issue"
@@ -66,7 +66,7 @@ for task_file in $task_files; do
66
66
  - Created: $(date -u +"%Y-%m-%dT%H:%M:%SZ")
67
67
  " \
68
68
  --label "task,epic:$EPIC_NAME" \
69
- 2>&1 | grep -oP '(?<=#)[0-9]+' | head -1)
69
+ 2>&1 | grep -o '#[0-9]\+' | head -1 | sed 's/#//')
70
70
 
71
71
  if [[ -n "$issue_number" ]]; then
72
72
  echo "#$issue_number ✓"
@@ -0,0 +1,538 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Issue Start - Start work on an issue
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const { execSync } = require('child_process');
9
+
10
+ class IssueStarter {
11
+ constructor() {
12
+ this.providersDir = path.join(__dirname, '..', '..', 'providers');
13
+ this.issueDir = path.join('.claude', 'issues');
14
+ this.activeWorkFile = path.join('.claude', 'active-work.json');
15
+ }
16
+
17
+ detectProvider() {
18
+ // Check for Azure DevOps
19
+ if (fs.existsSync('.azure') || process.env.AZURE_DEVOPS_ORG) {
20
+ return 'azure';
21
+ }
22
+
23
+ // Check for GitHub
24
+ if (fs.existsSync('.github') || fs.existsSync('.git')) {
25
+ try {
26
+ const remoteUrl = execSync('git remote get-url origin 2>/dev/null', { encoding: 'utf8' });
27
+ if (remoteUrl.includes('github.com')) {
28
+ return 'github';
29
+ }
30
+ } catch {}
31
+ }
32
+
33
+ return 'local';
34
+ }
35
+
36
+ loadActiveWork() {
37
+ if (!fs.existsSync(this.activeWorkFile)) {
38
+ return { issues: [], epics: [] };
39
+ }
40
+ try {
41
+ return JSON.parse(fs.readFileSync(this.activeWorkFile, 'utf8'));
42
+ } catch {
43
+ return { issues: [], epics: [] };
44
+ }
45
+ }
46
+
47
+ saveActiveWork(activeWork) {
48
+ const dir = path.dirname(this.activeWorkFile);
49
+ if (!fs.existsSync(dir)) {
50
+ fs.mkdirSync(dir, { recursive: true });
51
+ }
52
+ fs.writeFileSync(this.activeWorkFile, JSON.stringify(activeWork, null, 2));
53
+ }
54
+
55
+ async startIssue(issueId, options = {}) {
56
+ const provider = options.provider || this.detectProvider();
57
+ console.log(`🚀 Starting work on issue: ${issueId}`);
58
+ console.log(`📦 Provider: ${provider}\n`);
59
+
60
+ // Update active work tracking
61
+ const activeWork = this.loadActiveWork();
62
+ const issueEntry = {
63
+ id: issueId,
64
+ provider: provider,
65
+ startedAt: new Date().toISOString(),
66
+ status: 'in-progress'
67
+ };
68
+
69
+ // Remove if already exists and add to beginning
70
+ activeWork.issues = activeWork.issues.filter(i => i.id !== issueId);
71
+ activeWork.issues.unshift(issueEntry);
72
+
73
+ // Keep only last 10 active issues
74
+ if (activeWork.issues.length > 10) {
75
+ activeWork.issues = activeWork.issues.slice(0, 10);
76
+ }
77
+
78
+ this.saveActiveWork(activeWork);
79
+
80
+ // Try to use provider-specific start command
81
+ const providerScript = path.join(this.providersDir, provider, 'issue-start.js');
82
+ if (fs.existsSync(providerScript)) {
83
+ console.log(`Using ${provider} provider to start issue...`);
84
+ try {
85
+ require(providerScript);
86
+ return;
87
+ } catch (error) {
88
+ console.log(`⚠️ Provider script failed, using local tracking`);
89
+ }
90
+ }
91
+
92
+ // Local tracking fallback
93
+ console.log('📝 Creating local issue tracking...');
94
+
95
+ // Create issue file if it doesn't exist
96
+ if (!fs.existsSync(this.issueDir)) {
97
+ fs.mkdirSync(this.issueDir, { recursive: true });
98
+ }
99
+
100
+ const issueFile = path.join(this.issueDir, `${issueId}.md`);
101
+ if (!fs.existsSync(issueFile)) {
102
+ const template = `# Issue ${issueId}
103
+
104
+ ## Status
105
+ - **State**: In Progress
106
+ - **Started**: ${new Date().toISOString()}
107
+ - **Assigned**: ${process.env.USER || 'current-user'}
108
+
109
+ ## Description
110
+ [Add issue description here]
111
+
112
+ ## Tasks
113
+ - [ ] Task 1
114
+ - [ ] Task 2
115
+ - [ ] Task 3
116
+
117
+ ## Notes
118
+ - Started work on ${new Date().toLocaleDateString()}
119
+
120
+ ## Updates
121
+ - ${new Date().toISOString()}: Issue started
122
+ `;
123
+ fs.writeFileSync(issueFile, template);
124
+ console.log(`✅ Created issue file: ${issueFile}`);
125
+ }
126
+
127
+ // Display status
128
+ console.log('\n📊 Issue Status:');
129
+ console.log(` • ID: ${issueId}`);
130
+ console.log(` • Status: In Progress`);
131
+ console.log(` • Started: ${new Date().toLocaleString()}`);
132
+
133
+ // Show next steps
134
+ console.log('\n💡 Next steps:');
135
+ console.log(` • View issue: pm issue-show ${issueId}`);
136
+ console.log(` • Update status: pm issue-edit ${issueId}`);
137
+ console.log(` • Close issue: pm issue-close ${issueId}`);
138
+ console.log(` • View all active: pm in-progress`);
139
+ }
140
+
141
+ /**
142
+ * Find task file for an issue by ID
143
+ * Supports both new naming (123.md) and old naming (feature-name.md with github URL in frontmatter)
144
+ */
145
+ findTaskFile(issueId) {
146
+ const epicsDir = path.join('.claude', 'epics');
147
+
148
+ if (!fs.existsSync(epicsDir)) {
149
+ return null;
150
+ }
151
+
152
+ // Search all epic directories
153
+ const epicDirs = fs.readdirSync(epicsDir).filter(name => {
154
+ const epicPath = path.join(epicsDir, name);
155
+ return fs.statSync(epicPath).isDirectory();
156
+ });
157
+
158
+ for (const epicDir of epicDirs) {
159
+ const epicPath = path.join(epicsDir, epicDir);
160
+
161
+ // Try new naming convention first: {issueId}.md
162
+ const newStylePath = path.join(epicPath, `${issueId}.md`);
163
+ if (fs.existsSync(newStylePath)) {
164
+ return newStylePath;
165
+ }
166
+
167
+ // Try old naming convention: search files for github URL
168
+ const files = fs.readdirSync(epicPath).filter(f => f.endsWith('.md'));
169
+ for (const file of files) {
170
+ const filePath = path.join(epicPath, file);
171
+ const content = fs.readFileSync(filePath, 'utf8');
172
+
173
+ // Check frontmatter for github URL with this issue number
174
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
175
+ if (frontmatterMatch) {
176
+ const frontmatter = frontmatterMatch[1];
177
+ const githubMatch = frontmatter.match(/github:\s*.*\/issues\/(\d+)/);
178
+ if (githubMatch && githubMatch[1] === issueId) {
179
+ return filePath;
180
+ }
181
+ }
182
+ }
183
+ }
184
+
185
+ return null;
186
+ }
187
+
188
+ /**
189
+ * Extract epic name from task file path
190
+ */
191
+ getEpicFromTaskFile(taskFilePath) {
192
+ const parts = taskFilePath.split(path.sep);
193
+ const epicsIndex = parts.indexOf('epics');
194
+ if (epicsIndex >= 0 && epicsIndex < parts.length - 1) {
195
+ return parts[epicsIndex + 1];
196
+ }
197
+ return null;
198
+ }
199
+
200
+ /**
201
+ * Analyze issue to identify parallel work streams
202
+ */
203
+ async analyzeIssue(issueId, options = {}) {
204
+ console.log(`🔍 Analyzing issue #${issueId} for parallel work streams...`);
205
+
206
+ // Find task file
207
+ const taskFile = this.findTaskFile(issueId);
208
+ if (!taskFile) {
209
+ throw new Error(`No task file found for issue #${issueId}`);
210
+ }
211
+
212
+ const epicName = this.getEpicFromTaskFile(taskFile);
213
+ if (!epicName) {
214
+ throw new Error('Could not determine epic name from task file');
215
+ }
216
+
217
+ // Check if analysis already exists
218
+ const analysisFile = path.join('.claude', 'epics', epicName, `${issueId}-analysis.md`);
219
+ if (fs.existsSync(analysisFile) && !options.force) {
220
+ console.log('⚠️ Analysis already exists. Use --force to overwrite.');
221
+ return;
222
+ }
223
+
224
+ // Read task file
225
+ const taskContent = fs.readFileSync(taskFile, 'utf8');
226
+
227
+ // Get issue from GitHub if available
228
+ let issueData = null;
229
+ try {
230
+ const result = execSync(`gh issue view ${issueId} --json title,body,labels`, { encoding: 'utf8' });
231
+ issueData = JSON.parse(result);
232
+ } catch (error) {
233
+ console.log('⚠️ Could not fetch issue from GitHub, using local task file only');
234
+ }
235
+
236
+ // Create analysis content
237
+ const timestamp = new Date().toISOString();
238
+ const analysisContent = this.generateAnalysisContent(issueId, taskContent, issueData, timestamp);
239
+
240
+ // Write analysis file
241
+ fs.writeFileSync(analysisFile, analysisContent);
242
+
243
+ console.log(`✅ Analysis complete: ${analysisFile}`);
244
+ console.log(` Next: Run /pm:issue-start ${issueId} to begin parallel work`);
245
+ }
246
+
247
+ /**
248
+ * Generate analysis file content (simplified version - full analysis done by Claude)
249
+ */
250
+ generateAnalysisContent(issueId, taskContent, issueData, timestamp) {
251
+ const title = issueData?.title || 'Issue #' + issueId;
252
+
253
+ // This is a placeholder - in real workflow, Claude would create full analysis
254
+ return `---
255
+ issue: ${issueId}
256
+ title: ${title}
257
+ analyzed: ${timestamp}
258
+ estimated_hours: 8
259
+ parallelization_factor: 2.0
260
+ ---
261
+
262
+ # Parallel Work Analysis: Issue #${issueId}
263
+
264
+ ## Overview
265
+ ${issueData?.body || 'Analysis pending - Claude will provide detailed breakdown'}
266
+
267
+ ## Parallel Streams
268
+
269
+ ### Stream A: Primary Implementation
270
+ **Scope**: Main implementation work
271
+ **Files**:
272
+ - TBD (Claude will analyze)
273
+ **Agent Type**: general-purpose
274
+ **Can Start**: immediately
275
+ **Estimated Hours**: 4
276
+ **Dependencies**: none
277
+
278
+ ### Stream B: Tests
279
+ **Scope**: Test implementation
280
+ **Files**:
281
+ - test/**/*.test.js
282
+ **Agent Type**: test-runner
283
+ **Can Start**: immediately
284
+ **Estimated Hours**: 4
285
+ **Dependencies**: none
286
+
287
+ ## Coordination Points
288
+
289
+ ### Shared Files
290
+ To be determined by Claude during analysis.
291
+
292
+ ### Sequential Requirements
293
+ 1. Analysis complete
294
+ 2. Implementation and tests can run in parallel
295
+
296
+ ## Conflict Risk Assessment
297
+ - **Low Risk**: Expected - tests and implementation typically work in separate files
298
+
299
+ ## Parallelization Strategy
300
+
301
+ **Recommended Approach**: parallel
302
+
303
+ Launch Streams A and B simultaneously for maximum efficiency.
304
+
305
+ ## Expected Timeline
306
+
307
+ With parallel execution:
308
+ - Wall time: 4 hours
309
+ - Total work: 8 hours
310
+ - Efficiency gain: 50%
311
+
312
+ ## Notes
313
+ This is an automated placeholder. Claude will provide detailed analysis when running this command.
314
+ `;
315
+ }
316
+
317
+ /**
318
+ * Parse analysis file to extract parallel streams
319
+ */
320
+ parseAnalysisStreams(analysisFilePath) {
321
+ const content = fs.readFileSync(analysisFilePath, 'utf8');
322
+ const streams = [];
323
+
324
+ // Extract stream sections using regex
325
+ const streamRegex = /### Stream ([A-Z]):\s*(.+?)\n\*\*Scope\*\*:\s*(.+?)\n\*\*Files\*\*:\n((?:- .+\n)+)\*\*Agent Type\*\*:\s*(.+?)\n\*\*Can Start\*\*:\s*(.+?)\n\*\*Estimated Hours\*\*:\s*(\d+)/g;
326
+
327
+ let match;
328
+ while ((match = streamRegex.exec(content)) !== null) {
329
+ const [, letter, name, scope, filesText, agentType, canStart, hours] = match;
330
+
331
+ // Parse files list
332
+ const files = filesText.trim().split('\n').map(line =>
333
+ line.replace(/^- /, '').trim()
334
+ );
335
+
336
+ streams.push({
337
+ letter,
338
+ name: name.trim(),
339
+ scope: scope.trim(),
340
+ files,
341
+ agentType: agentType.trim(),
342
+ canStart: canStart.trim(),
343
+ estimatedHours: parseInt(hours, 10)
344
+ });
345
+ }
346
+
347
+ return streams;
348
+ }
349
+
350
+ /**
351
+ * Create workspace structure for issue tracking
352
+ */
353
+ async createWorkspace(issueId, epicName) {
354
+ const workspaceDir = path.join('.claude', 'epics', epicName, 'updates', issueId);
355
+
356
+ if (!fs.existsSync(workspaceDir)) {
357
+ fs.mkdirSync(workspaceDir, { recursive: true });
358
+ }
359
+
360
+ return workspaceDir;
361
+ }
362
+
363
+ /**
364
+ * Create stream tracking file
365
+ */
366
+ async createStreamFile(issueId, epicName, stream, streamNumber) {
367
+ const workspaceDir = await this.createWorkspace(issueId, epicName);
368
+ const streamFile = path.join(workspaceDir, `stream-${streamNumber}.md`);
369
+ const timestamp = new Date().toISOString();
370
+
371
+ const content = `---
372
+ issue: ${issueId}
373
+ stream: ${stream.name}
374
+ agent: ${stream.agentType}
375
+ started: ${timestamp}
376
+ status: in_progress
377
+ ---
378
+
379
+ # Stream ${streamNumber}: ${stream.name}
380
+
381
+ ## Scope
382
+ ${stream.scope}
383
+
384
+ ## Files
385
+ ${stream.files.map(f => `- ${f}`).join('\n')}
386
+
387
+ ## Progress
388
+ - Starting implementation
389
+ `;
390
+
391
+ fs.writeFileSync(streamFile, content);
392
+ return streamFile;
393
+ }
394
+
395
+ /**
396
+ * Start issue with parallel work streams based on analysis
397
+ */
398
+ async startIssueWithAnalysis(issueId, options = {}) {
399
+ console.log(`🚀 Starting parallel work on issue #${issueId}...\n`);
400
+
401
+ // Find task file and epic
402
+ const taskFile = this.findTaskFile(issueId);
403
+ if (!taskFile) {
404
+ throw new Error(`No task file found for issue #${issueId}`);
405
+ }
406
+
407
+ const epicName = this.getEpicFromTaskFile(taskFile);
408
+ const analysisFile = path.join('.claude', 'epics', epicName, `${issueId}-analysis.md`);
409
+
410
+ if (!fs.existsSync(analysisFile)) {
411
+ throw new Error(`No analysis found for issue #${issueId}. Run with --analyze first.`);
412
+ }
413
+
414
+ // Parse streams from analysis
415
+ const streams = this.parseAnalysisStreams(analysisFile);
416
+
417
+ if (streams.length === 0) {
418
+ console.log('⚠️ No parallel streams found in analysis. Starting regular workflow.');
419
+ return this.startIssue(issueId, options);
420
+ }
421
+
422
+ // Create workspace
423
+ await this.createWorkspace(issueId, epicName);
424
+
425
+ // Update active work
426
+ const activeWork = this.loadActiveWork();
427
+ const issueEntry = {
428
+ id: issueId,
429
+ provider: options.provider || this.detectProvider(),
430
+ startedAt: new Date().toISOString(),
431
+ status: 'in-progress',
432
+ parallelStreams: streams.length
433
+ };
434
+
435
+ activeWork.issues = activeWork.issues.filter(i => i.id !== issueId);
436
+ activeWork.issues.unshift(issueEntry);
437
+
438
+ if (activeWork.issues.length > 10) {
439
+ activeWork.issues = activeWork.issues.slice(0, 10);
440
+ }
441
+
442
+ this.saveActiveWork(activeWork);
443
+
444
+ // Create stream files for immediate streams
445
+ const immediateStreams = streams.filter(s => s.canStart === 'immediately');
446
+ let streamNumber = 1;
447
+
448
+ console.log(`📋 Epic: ${epicName}`);
449
+ console.log(`🔀 Launching ${immediateStreams.length} parallel streams:\n`);
450
+
451
+ for (const stream of immediateStreams) {
452
+ await this.createStreamFile(issueId, epicName, stream, streamNumber);
453
+ console.log(` ✓ Stream ${streamNumber}: ${stream.name} (${stream.agentType})`);
454
+ streamNumber++;
455
+ }
456
+
457
+ // List dependent streams
458
+ const dependentStreams = streams.filter(s => s.canStart !== 'immediately');
459
+ if (dependentStreams.length > 0) {
460
+ console.log(`\n⏳ Waiting streams (will start after dependencies):`);
461
+ dependentStreams.forEach(stream => {
462
+ console.log(` ○ ${stream.name} (${stream.canStart})`);
463
+ });
464
+ }
465
+
466
+ console.log(`\n📁 Progress tracking:`);
467
+ console.log(` .claude/epics/${epicName}/updates/${issueId}/\n`);
468
+
469
+ console.log(`⚠️ TDD REMINDER - All agents MUST follow:`);
470
+ console.log(` 1. ❌ RED: Write failing test`);
471
+ console.log(` 2. ✅ GREEN: Make test pass (minimal code)`);
472
+ console.log(` 3. 🔄 REFACTOR: Clean up code\n`);
473
+
474
+ // Assign issue on GitHub
475
+ try {
476
+ execSync(`gh issue edit ${issueId} --add-assignee @me --add-label "in-progress"`, { stdio: 'ignore' });
477
+ console.log(`✓ Issue assigned on GitHub`);
478
+ } catch (error) {
479
+ console.log(`⚠️ Could not assign issue on GitHub (not critical)`);
480
+ }
481
+
482
+ console.log(`\n💡 Next steps:`);
483
+ console.log(` • Monitor: /pm:epic-status ${epicName}`);
484
+ console.log(` • Sync updates: /pm:issue-sync ${issueId}`);
485
+ }
486
+
487
+ async run(args) {
488
+ const issueId = args[0];
489
+
490
+ if (!issueId) {
491
+ console.error('❌ Error: Issue ID required');
492
+ console.error('Usage: pm issue-start <issue-id> [--provider=azure|github] [--analyze]');
493
+
494
+ // Show active work if any
495
+ const activeWork = this.loadActiveWork();
496
+ if (activeWork.issues.length > 0) {
497
+ console.log('\n📋 Currently active issues:');
498
+ activeWork.issues.slice(0, 5).forEach(issue => {
499
+ const date = new Date(issue.startedAt).toLocaleDateString();
500
+ console.log(` • ${issue.id} (${issue.provider}) - started ${date}`);
501
+ });
502
+ }
503
+
504
+ process.exit(1);
505
+ }
506
+
507
+ const options = {};
508
+ let shouldAnalyze = false;
509
+
510
+ args.slice(1).forEach(arg => {
511
+ if (arg.startsWith('--provider=')) {
512
+ options.provider = arg.split('=')[1];
513
+ } else if (arg === '--analyze') {
514
+ shouldAnalyze = true;
515
+ }
516
+ });
517
+
518
+ if (shouldAnalyze) {
519
+ // Analyze first, then start with analysis
520
+ await this.analyzeIssue(issueId, options);
521
+ await this.startIssueWithAnalysis(issueId, options);
522
+ } else {
523
+ // Regular start without analysis
524
+ await this.startIssue(issueId, options);
525
+ }
526
+ }
527
+ }
528
+
529
+ // Main execution
530
+ if (require.main === module) {
531
+ const starter = new IssueStarter();
532
+ starter.run(process.argv.slice(2)).catch(error => {
533
+ console.error('❌ Error:', error.message);
534
+ process.exit(1);
535
+ });
536
+ }
537
+
538
+ module.exports = IssueStarter;
@@ -72,7 +72,7 @@ async function benchmarkIssueList(iterations = 10) {
72
72
  };
73
73
 
74
74
  // Load the actual module (with mocked client)
75
- const AzureIssueList = require('../../autopm/.claude/providers/azure/issue-list');
75
+ const AzureIssueList = require('../../.claude/providers/azure/issue-list');
76
76
 
77
77
  for (let i = 0; i < iterations; i++) {
78
78
  // Create fresh instance
@@ -45,7 +45,7 @@ async function benchmarkProviderRouting(iterations = 20) {
45
45
 
46
46
  // Measure module loading time
47
47
  const loadStart = performance.now();
48
- const ProviderRouter = require('../../autopm/.claude/providers/router');
48
+ const ProviderRouter = require('../../.claude/providers/router');
49
49
  const loadEnd = performance.now();
50
50
 
51
51
  // Measure routing time
@@ -12,6 +12,15 @@ echo ""
12
12
  echo "🔍 Validating Framework Paths"
13
13
  echo "=============================="
14
14
  echo ""
15
+ echo "Checking directories:"
16
+ echo " • autopm/.claude/ (framework files)"
17
+ echo " • lib/ (shared libraries)"
18
+ echo " • scripts/ (utility scripts)"
19
+ echo " • bin/ (executables)"
20
+ echo " • packages/ (plugin packages)"
21
+ echo ""
22
+ echo "Excluding: test/, node_modules/, coverage/"
23
+ echo ""
15
24
 
16
25
  # Colors for output
17
26
  RED='\033[0;31m'
@@ -22,27 +31,65 @@ NC='\033[0m' # No Color
22
31
  # Track violations
23
32
  violations=0
24
33
 
34
+ # Directories to check (production code only, exclude tests)
35
+ SEARCH_DIRS=(
36
+ "$PROJECT_ROOT/autopm/.claude"
37
+ "$PROJECT_ROOT/lib"
38
+ "$PROJECT_ROOT/scripts"
39
+ "$PROJECT_ROOT/bin"
40
+ "$PROJECT_ROOT/packages"
41
+ )
42
+
25
43
  # Function to check for violations
26
44
  check_violations() {
27
45
  local pattern="$1"
28
46
  local description="$2"
29
- local files
30
-
31
- # Search for pattern, excluding:
32
- # - Comments in JS files (lines starting with * or //)
33
- # - Comments in shell scripts (lines starting with #)
34
- # - "Migrated from" comments
35
- # - The framework-path-rules.md file itself (contains examples)
36
- files=$(grep -r "$pattern" "$PROJECT_ROOT/autopm/.claude" \
37
- --include="*.md" \
38
- --include="*.sh" \
39
- --include="*.js" \
40
- --exclude="framework-path-rules.md" \
41
- 2>/dev/null | \
42
- grep -v "Migrated from" | \
43
- grep -v "^\s*\*" | \
44
- grep -v "^\s*//" | \
45
- grep -v "^\s*#" || true)
47
+ local files=""
48
+
49
+ # Search each directory
50
+ for search_dir in "${SEARCH_DIRS[@]}"; do
51
+ if [ -d "$search_dir" ]; then
52
+ # Search for pattern, excluding:
53
+ # - Comments in JS files (lines starting with * or //)
54
+ # - Comments in shell scripts (lines starting with #)
55
+ # - "Migrated from" comments
56
+ # - The framework-path-rules.md file itself (contains examples)
57
+ # - Test files and node_modules
58
+ local dir_results=$(grep -r "$pattern" "$search_dir" \
59
+ --include="*.md" \
60
+ --include="*.sh" \
61
+ --include="*.js" \
62
+ --include="*.cjs" \
63
+ --exclude="framework-path-rules.md" \
64
+ --exclude="validate-framework-paths.sh" \
65
+ --exclude="setup-azure-aliases.js" \
66
+ --exclude="migrate-commands.js" \
67
+ --exclude="migrate-all-commands.js" \
68
+ --exclude="add-context7-to-commands.js" \
69
+ --exclude="fix-command-instructions.js" \
70
+ --exclude="standardize-framework-agents.js" \
71
+ --exclude="self-maintenance.js" \
72
+ --exclude="verify-agents.js" \
73
+ --exclude-dir="node_modules" \
74
+ --exclude-dir="test" \
75
+ --exclude-dir="tests" \
76
+ --exclude-dir="__tests__" \
77
+ --exclude-dir="coverage" \
78
+ --exclude-dir="benchmarks" \
79
+ 2>/dev/null | \
80
+ grep -v "Migrated from" | \
81
+ grep -v "^\s*\*" | \
82
+ grep -v "^\s*//" | \
83
+ grep -v "^\s*#" || true)
84
+
85
+ if [ -n "$dir_results" ]; then
86
+ files="$files$dir_results"$'\n'
87
+ fi
88
+ fi
89
+ done
90
+
91
+ # Remove trailing newline
92
+ files=$(echo "$files" | sed '/^$/d')
46
93
 
47
94
  if [ -n "$files" ]; then
48
95
  echo -e "${RED}❌ Found violations: $description${NC}"
@@ -1,181 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Issue Start - Start work on an issue
4
- */
5
-
6
- const fs = require('fs');
7
- const path = require('path');
8
- const { execSync } = require('child_process');
9
-
10
- class IssueStarter {
11
- constructor() {
12
- this.providersDir = path.join(__dirname, '..', '..', 'providers');
13
- this.issueDir = path.join('.claude', 'issues');
14
- this.activeWorkFile = path.join('.claude', 'active-work.json');
15
- }
16
-
17
- detectProvider() {
18
- // Check for Azure DevOps
19
- if (fs.existsSync('.azure') || process.env.AZURE_DEVOPS_ORG) {
20
- return 'azure';
21
- }
22
-
23
- // Check for GitHub
24
- if (fs.existsSync('.github') || fs.existsSync('.git')) {
25
- try {
26
- const remoteUrl = execSync('git remote get-url origin 2>/dev/null', { encoding: 'utf8' });
27
- if (remoteUrl.includes('github.com')) {
28
- return 'github';
29
- }
30
- } catch {}
31
- }
32
-
33
- return 'local';
34
- }
35
-
36
- loadActiveWork() {
37
- if (!fs.existsSync(this.activeWorkFile)) {
38
- return { issues: [], epics: [] };
39
- }
40
- try {
41
- return JSON.parse(fs.readFileSync(this.activeWorkFile, 'utf8'));
42
- } catch {
43
- return { issues: [], epics: [] };
44
- }
45
- }
46
-
47
- saveActiveWork(activeWork) {
48
- const dir = path.dirname(this.activeWorkFile);
49
- if (!fs.existsSync(dir)) {
50
- fs.mkdirSync(dir, { recursive: true });
51
- }
52
- fs.writeFileSync(this.activeWorkFile, JSON.stringify(activeWork, null, 2));
53
- }
54
-
55
- async startIssue(issueId, options = {}) {
56
- const provider = options.provider || this.detectProvider();
57
- console.log(`🚀 Starting work on issue: ${issueId}`);
58
- console.log(`📦 Provider: ${provider}\n`);
59
-
60
- // Update active work tracking
61
- const activeWork = this.loadActiveWork();
62
- const issueEntry = {
63
- id: issueId,
64
- provider: provider,
65
- startedAt: new Date().toISOString(),
66
- status: 'in-progress'
67
- };
68
-
69
- // Remove if already exists and add to beginning
70
- activeWork.issues = activeWork.issues.filter(i => i.id !== issueId);
71
- activeWork.issues.unshift(issueEntry);
72
-
73
- // Keep only last 10 active issues
74
- if (activeWork.issues.length > 10) {
75
- activeWork.issues = activeWork.issues.slice(0, 10);
76
- }
77
-
78
- this.saveActiveWork(activeWork);
79
-
80
- // Try to use provider-specific start command
81
- const providerScript = path.join(this.providersDir, provider, 'issue-start.js');
82
- if (fs.existsSync(providerScript)) {
83
- console.log(`Using ${provider} provider to start issue...`);
84
- try {
85
- require(providerScript);
86
- return;
87
- } catch (error) {
88
- console.log(`⚠️ Provider script failed, using local tracking`);
89
- }
90
- }
91
-
92
- // Local tracking fallback
93
- console.log('📝 Creating local issue tracking...');
94
-
95
- // Create issue file if it doesn't exist
96
- if (!fs.existsSync(this.issueDir)) {
97
- fs.mkdirSync(this.issueDir, { recursive: true });
98
- }
99
-
100
- const issueFile = path.join(this.issueDir, `${issueId}.md`);
101
- if (!fs.existsSync(issueFile)) {
102
- const template = `# Issue ${issueId}
103
-
104
- ## Status
105
- - **State**: In Progress
106
- - **Started**: ${new Date().toISOString()}
107
- - **Assigned**: ${process.env.USER || 'current-user'}
108
-
109
- ## Description
110
- [Add issue description here]
111
-
112
- ## Tasks
113
- - [ ] Task 1
114
- - [ ] Task 2
115
- - [ ] Task 3
116
-
117
- ## Notes
118
- - Started work on ${new Date().toLocaleDateString()}
119
-
120
- ## Updates
121
- - ${new Date().toISOString()}: Issue started
122
- `;
123
- fs.writeFileSync(issueFile, template);
124
- console.log(`✅ Created issue file: ${issueFile}`);
125
- }
126
-
127
- // Display status
128
- console.log('\n📊 Issue Status:');
129
- console.log(` • ID: ${issueId}`);
130
- console.log(` • Status: In Progress`);
131
- console.log(` • Started: ${new Date().toLocaleString()}`);
132
-
133
- // Show next steps
134
- console.log('\n💡 Next steps:');
135
- console.log(` • View issue: pm issue-show ${issueId}`);
136
- console.log(` • Update status: pm issue-edit ${issueId}`);
137
- console.log(` • Close issue: pm issue-close ${issueId}`);
138
- console.log(` • View all active: pm in-progress`);
139
- }
140
-
141
- async run(args) {
142
- const issueId = args[0];
143
-
144
- if (!issueId) {
145
- console.error('❌ Error: Issue ID required');
146
- console.error('Usage: pm issue-start <issue-id> [--provider=azure|github]');
147
-
148
- // Show active work if any
149
- const activeWork = this.loadActiveWork();
150
- if (activeWork.issues.length > 0) {
151
- console.log('\n📋 Currently active issues:');
152
- activeWork.issues.slice(0, 5).forEach(issue => {
153
- const date = new Date(issue.startedAt).toLocaleDateString();
154
- console.log(` • ${issue.id} (${issue.provider}) - started ${date}`);
155
- });
156
- }
157
-
158
- process.exit(1);
159
- }
160
-
161
- const options = {};
162
- args.slice(1).forEach(arg => {
163
- if (arg.startsWith('--provider=')) {
164
- options.provider = arg.split('=')[1];
165
- }
166
- });
167
-
168
- await this.startIssue(issueId, options);
169
- }
170
- }
171
-
172
- // Main execution
173
- if (require.main === module) {
174
- const starter = new IssueStarter();
175
- starter.run(process.argv.slice(2)).catch(error => {
176
- console.error('❌ Error:', error.message);
177
- process.exit(1);
178
- });
179
- }
180
-
181
- module.exports = IssueStarter;