claude-autopm 3.0.3 ā 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.
- package/autopm/lib/batch-processor-integration.js +1 -1
- package/lib/batch-processor-integration.js +1 -1
- package/lib/services/AgentService.js +1 -1
- package/lib/template-engine.js +2 -2
- package/package.json +1 -1
- package/packages/plugin-pm/providers/azure/issue-start.js +1 -0
- package/packages/plugin-pm/scripts/pm/issue-start.cjs +538 -0
- package/scripts/benchmarks/azure-issue-list.bench.js +1 -1
- package/scripts/benchmarks/provider-router.bench.js +1 -1
- package/scripts/validate-framework-paths.sh +64 -17
- package/packages/plugin-pm/scripts/pm/issue-start.js +0 -181
|
@@ -38,7 +38,7 @@ class AgentService {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
this.aiProvider = aiProvider;
|
|
41
|
-
this.agentsBaseDir = path.join(process.cwd(), '
|
|
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)
|
package/lib/template-engine.js
CHANGED
|
@@ -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
|
|
65
|
-
this.builtInDir = builtInDir || path.join(__dirname, '..', '
|
|
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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
throw new Error("Provider script error");
|
|
@@ -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('
|
|
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('
|
|
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
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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;
|