claude-autopm 1.17.0 → 1.20.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.
Files changed (76) hide show
  1. package/README.md +159 -0
  2. package/autopm/.claude/agents/core/mcp-manager.md +1 -1
  3. package/autopm/.claude/commands/pm/context.md +11 -0
  4. package/autopm/.claude/commands/pm/epic-decompose.md +25 -2
  5. package/autopm/.claude/commands/pm/epic-oneshot.md +13 -0
  6. package/autopm/.claude/commands/pm/epic-start.md +19 -0
  7. package/autopm/.claude/commands/pm/epic-sync-modular.md +10 -10
  8. package/autopm/.claude/commands/pm/epic-sync.md +14 -14
  9. package/autopm/.claude/commands/pm/issue-start.md +50 -5
  10. package/autopm/.claude/commands/pm/issue-sync.md +15 -15
  11. package/autopm/.claude/commands/pm/what-next.md +11 -0
  12. package/autopm/.claude/mcp/MCP-REGISTRY.md +1 -1
  13. package/autopm/.claude/scripts/azure/active-work.js +2 -2
  14. package/autopm/.claude/scripts/azure/blocked.js +13 -13
  15. package/autopm/.claude/scripts/azure/daily.js +1 -1
  16. package/autopm/.claude/scripts/azure/dashboard.js +1 -1
  17. package/autopm/.claude/scripts/azure/feature-list.js +2 -2
  18. package/autopm/.claude/scripts/azure/feature-status.js +1 -1
  19. package/autopm/.claude/scripts/azure/next-task.js +1 -1
  20. package/autopm/.claude/scripts/azure/search.js +1 -1
  21. package/autopm/.claude/scripts/azure/setup.js +15 -15
  22. package/autopm/.claude/scripts/azure/sprint-report.js +2 -2
  23. package/autopm/.claude/scripts/azure/sync.js +1 -1
  24. package/autopm/.claude/scripts/azure/us-list.js +1 -1
  25. package/autopm/.claude/scripts/azure/us-status.js +1 -1
  26. package/autopm/.claude/scripts/azure/validate.js +13 -13
  27. package/autopm/.claude/scripts/lib/frontmatter-utils.sh +42 -7
  28. package/autopm/.claude/scripts/lib/logging-utils.sh +20 -16
  29. package/autopm/.claude/scripts/lib/validation-utils.sh +1 -1
  30. package/autopm/.claude/scripts/pm/context.js +338 -0
  31. package/autopm/.claude/scripts/pm/issue-sync/format-comment.sh +3 -3
  32. package/autopm/.claude/scripts/pm/lib/README.md +85 -0
  33. package/autopm/.claude/scripts/pm/lib/logger.js +78 -0
  34. package/autopm/.claude/scripts/pm/next.js +25 -1
  35. package/autopm/.claude/scripts/pm/what-next.js +660 -0
  36. package/bin/autopm.js +25 -0
  37. package/bin/commands/team.js +86 -0
  38. package/package.json +1 -1
  39. package/lib/agentExecutor.js.deprecated +0 -101
  40. package/lib/azure/cache.js +0 -80
  41. package/lib/azure/client.js +0 -77
  42. package/lib/azure/formatter.js +0 -177
  43. package/lib/commandHelpers.js +0 -177
  44. package/lib/context/manager.js +0 -290
  45. package/lib/documentation/manager.js +0 -528
  46. package/lib/github/workflow-manager.js +0 -546
  47. package/lib/helpers/azure-batch-api.js +0 -133
  48. package/lib/helpers/azure-cache-manager.js +0 -287
  49. package/lib/helpers/azure-parallel-processor.js +0 -158
  50. package/lib/helpers/azure-work-item-create.js +0 -278
  51. package/lib/helpers/gh-issue-create.js +0 -250
  52. package/lib/helpers/interactive-prompt.js +0 -336
  53. package/lib/helpers/output-manager.js +0 -335
  54. package/lib/helpers/progress-indicator.js +0 -258
  55. package/lib/performance/benchmarker.js +0 -429
  56. package/lib/pm/epic-decomposer.js +0 -273
  57. package/lib/pm/epic-syncer.js +0 -221
  58. package/lib/prdMetadata.js +0 -270
  59. package/lib/providers/azure/index.js +0 -234
  60. package/lib/providers/factory.js +0 -87
  61. package/lib/providers/github/index.js +0 -204
  62. package/lib/providers/interface.js +0 -73
  63. package/lib/python/scaffold-manager.js +0 -576
  64. package/lib/react/scaffold-manager.js +0 -745
  65. package/lib/regression/analyzer.js +0 -578
  66. package/lib/release/manager.js +0 -324
  67. package/lib/tailwind/manager.js +0 -486
  68. package/lib/traefik/manager.js +0 -484
  69. package/lib/utils/colors.js +0 -126
  70. package/lib/utils/config.js +0 -317
  71. package/lib/utils/filesystem.js +0 -316
  72. package/lib/utils/logger.js +0 -135
  73. package/lib/utils/prompts.js +0 -294
  74. package/lib/utils/shell.js +0 -237
  75. package/lib/validators/email-validator.js +0 -337
  76. package/lib/workflow/manager.js +0 -449
@@ -1,578 +0,0 @@
1
- /**
2
- * Regression Test Analyzer
3
- * Centralized regression testing and analysis functionality
4
- */
5
-
6
- const fs = require('fs').promises;
7
- const path = require('path');
8
- const { exec } = require('child_process');
9
- const { promisify } = require('util');
10
- const execAsync = promisify(exec);
11
-
12
- /**
13
- * Configuration
14
- */
15
- const CONFIG = {
16
- directories: {
17
- regression: '.claude/regression'
18
- },
19
- defaults: {
20
- coverageThreshold: 80,
21
- slowTestThreshold: 1000, // ms
22
- maxHistorySize: 100
23
- },
24
- filePatterns: {
25
- baseline: 'baseline.json',
26
- history: 'history.json',
27
- report: 'report-{timestamp}.json'
28
- }
29
- };
30
-
31
- class RegressionAnalyzer {
32
- constructor(projectRoot = process.cwd()) {
33
- this.projectRoot = projectRoot;
34
- this.regressionDir = path.join(projectRoot, CONFIG.directories.regression);
35
- }
36
-
37
- /**
38
- * Runs the test suite
39
- */
40
- async runTests() {
41
- const packagePath = path.join(this.projectRoot, 'package.json');
42
-
43
- try {
44
- // Check if package.json exists
45
- const packageJson = JSON.parse(await fs.readFile(packagePath, 'utf8'));
46
-
47
- // Try to run npm test
48
- if (packageJson.scripts && packageJson.scripts.test) {
49
- const startTime = Date.now();
50
-
51
- try {
52
- const { stdout, stderr } = await execAsync('npm test', {
53
- cwd: this.projectRoot,
54
- env: { ...process.env, CI: 'true' }
55
- });
56
- const duration = Date.now() - startTime;
57
-
58
- return this.parseTestResults(stdout, duration, true);
59
- } catch (error) {
60
- const duration = Date.now() - startTime;
61
- return this.parseTestResults(error.stdout || error.message, duration, false);
62
- }
63
- }
64
- } catch (error) {
65
- // No package.json or no test script
66
- }
67
-
68
- // Fallback: look for test files
69
- const testFiles = await this.findTestFiles();
70
- const results = {
71
- success: true,
72
- tests: { total: testFiles.length, passed: 0, failed: 0 },
73
- timestamp: new Date().toISOString(),
74
- duration: 0
75
- };
76
-
77
- const startTime = Date.now();
78
- for (const testFile of testFiles) {
79
- try {
80
- await execAsync(`node ${testFile}`, { cwd: this.projectRoot });
81
- results.tests.passed++;
82
- } catch (error) {
83
- results.tests.failed++;
84
- results.success = false;
85
- }
86
- }
87
- results.duration = Date.now() - startTime;
88
-
89
- return results;
90
- }
91
-
92
- /**
93
- * Parses test results from output
94
- */
95
- parseTestResults(output, duration, success) {
96
- const results = {
97
- success,
98
- duration,
99
- output: output.substring(0, 1000), // Limit output size
100
- timestamp: new Date().toISOString()
101
- };
102
-
103
- // Try to parse different test output formats
104
- const patterns = [
105
- /(\d+)\s+(pass|passed|passing)/i,
106
- /(\d+)\s+(fail|failed|failing)/i,
107
- /(\d+)\s+tests?/i,
108
- /✓\s+(\d+)/,
109
- /✗\s+(\d+)/
110
- ];
111
-
112
- const passMatch = output.match(patterns[0]);
113
- const failMatch = output.match(patterns[1]);
114
- const totalMatch = output.match(patterns[2]);
115
-
116
- if (passMatch || failMatch || totalMatch) {
117
- results.tests = {
118
- total: parseInt(totalMatch?.[1] || 0) ||
119
- (parseInt(passMatch?.[1] || 0) + parseInt(failMatch?.[1] || 0)),
120
- passed: parseInt(passMatch?.[1] || 0),
121
- failed: parseInt(failMatch?.[1] || 0)
122
- };
123
- }
124
-
125
- return results;
126
- }
127
-
128
- /**
129
- * Finds test files in the project
130
- */
131
- async findTestFiles() {
132
- const testFiles = [];
133
- const testDirs = ['test', 'tests', '__tests__', 'spec'];
134
-
135
- for (const dir of testDirs) {
136
- const testDir = path.join(this.projectRoot, dir);
137
-
138
- try {
139
- await this.findTestFilesInDir(testDir, testFiles);
140
- } catch (error) {
141
- // Directory doesn't exist
142
- }
143
- }
144
-
145
- return testFiles;
146
- }
147
-
148
- /**
149
- * Recursively finds test files in a directory
150
- */
151
- async findTestFilesInDir(dir, testFiles) {
152
- const entries = await fs.readdir(dir, { withFileTypes: true });
153
-
154
- for (const entry of entries) {
155
- const fullPath = path.join(dir, entry.name);
156
-
157
- if (entry.isDirectory() && !entry.name.startsWith('.')) {
158
- await this.findTestFilesInDir(fullPath, testFiles);
159
- } else if (entry.isFile()) {
160
- if (entry.name.match(/\.(test|spec)\.(js|ts|jsx|tsx)$/)) {
161
- testFiles.push(fullPath);
162
- }
163
- }
164
- }
165
- }
166
-
167
- /**
168
- * Gets coverage information
169
- */
170
- async getCoverage() {
171
- // Try to get coverage from common locations
172
- const coveragePaths = [
173
- path.join(this.projectRoot, 'coverage', 'coverage-summary.json'),
174
- path.join(this.projectRoot, '.nyc_output', 'coverage-summary.json'),
175
- path.join(this.projectRoot, 'coverage', 'coverage-final.json'),
176
- path.join(this.projectRoot, 'coverage.json')
177
- ];
178
-
179
- for (const coveragePath of coveragePaths) {
180
- try {
181
- const content = await fs.readFile(coveragePath, 'utf8');
182
- const coverage = JSON.parse(content);
183
-
184
- // Extract coverage metrics
185
- if (coverage.total) {
186
- return {
187
- lines: Math.round(coverage.total.lines?.pct || 0),
188
- branches: Math.round(coverage.total.branches?.pct || 0),
189
- functions: Math.round(coverage.total.functions?.pct || 0),
190
- statements: Math.round(coverage.total.statements?.pct || 0)
191
- };
192
- }
193
-
194
- // Try aggregate format
195
- if (coverage.aggregate) {
196
- return {
197
- lines: Math.round(coverage.aggregate.line?.pct || 0),
198
- branches: Math.round(coverage.aggregate.branch?.pct || 0),
199
- functions: Math.round(coverage.aggregate.function?.pct || 0),
200
- statements: Math.round(coverage.aggregate.statement?.pct || 0)
201
- };
202
- }
203
- } catch (error) {
204
- // Coverage file not found or invalid
205
- }
206
- }
207
-
208
- // Fallback: simulated coverage for testing
209
- return {
210
- lines: 75 + Math.floor(Math.random() * 20),
211
- branches: 70 + Math.floor(Math.random() * 20),
212
- functions: 80 + Math.floor(Math.random() * 15),
213
- statements: 75 + Math.floor(Math.random() * 20)
214
- };
215
- }
216
-
217
- /**
218
- * Captures baseline for regression testing
219
- */
220
- async captureBaseline() {
221
- // Run tests to get baseline
222
- const testResults = await this.runTests();
223
-
224
- // Get coverage if available
225
- const coverage = await this.getCoverage();
226
-
227
- const baseline = {
228
- timestamp: new Date().toISOString(),
229
- tests: testResults.tests || { total: 0, passed: 0, failed: 0 },
230
- coverage: coverage,
231
- performance: {
232
- duration: testResults.duration || 0
233
- },
234
- environment: {
235
- node: process.version,
236
- platform: process.platform,
237
- arch: process.arch
238
- }
239
- };
240
-
241
- // Save baseline
242
- await fs.mkdir(this.regressionDir, { recursive: true });
243
- const baselinePath = path.join(this.regressionDir, CONFIG.filePatterns.baseline);
244
- await fs.writeFile(baselinePath, JSON.stringify(baseline, null, 2));
245
-
246
- return { baseline, path: baselinePath };
247
- }
248
-
249
- /**
250
- * Loads baseline
251
- */
252
- async loadBaseline() {
253
- const baselinePath = path.join(this.regressionDir, CONFIG.filePatterns.baseline);
254
-
255
- try {
256
- const content = await fs.readFile(baselinePath, 'utf8');
257
- return JSON.parse(content);
258
- } catch (error) {
259
- return null;
260
- }
261
- }
262
-
263
- /**
264
- * Compares current state with baseline
265
- */
266
- async compareWithBaseline() {
267
- const baseline = await this.loadBaseline();
268
-
269
- if (!baseline) {
270
- throw new Error('No baseline found. Run "regression:suite baseline" first.');
271
- }
272
-
273
- // Run current tests
274
- const currentResults = await this.runTests();
275
- const currentCoverage = await this.getCoverage();
276
-
277
- const comparison = {
278
- baseline: {
279
- timestamp: baseline.timestamp,
280
- tests: baseline.tests,
281
- coverage: baseline.coverage,
282
- performance: baseline.performance
283
- },
284
- current: {
285
- timestamp: new Date().toISOString(),
286
- tests: currentResults.tests || { total: 0, passed: 0, failed: 0 },
287
- coverage: currentCoverage,
288
- performance: { duration: currentResults.duration || 0 }
289
- },
290
- regressions: [],
291
- improvements: []
292
- };
293
-
294
- // Compare tests
295
- if (baseline.tests && currentResults.tests) {
296
- const testDiff = currentResults.tests.passed - baseline.tests.passed;
297
- if (testDiff < 0) {
298
- comparison.regressions.push({
299
- type: 'tests',
300
- message: `${Math.abs(testDiff)} fewer tests passing`
301
- });
302
- } else if (testDiff > 0) {
303
- comparison.improvements.push({
304
- type: 'tests',
305
- message: `${testDiff} more tests passing`
306
- });
307
- }
308
- }
309
-
310
- // Compare coverage
311
- if (baseline.coverage && currentCoverage) {
312
- const coverageDiff = currentCoverage.lines - baseline.coverage.lines;
313
- if (coverageDiff < -5) {
314
- comparison.regressions.push({
315
- type: 'coverage',
316
- message: `${Math.abs(coverageDiff)}% coverage decrease`
317
- });
318
- } else if (coverageDiff > 5) {
319
- comparison.improvements.push({
320
- type: 'coverage',
321
- message: `${coverageDiff}% coverage increase`
322
- });
323
- }
324
- }
325
-
326
- // Compare performance
327
- if (baseline.performance && currentResults.duration) {
328
- const perfChange = ((currentResults.duration - baseline.performance.duration) /
329
- baseline.performance.duration) * 100;
330
- if (perfChange > 20) {
331
- comparison.regressions.push({
332
- type: 'performance',
333
- message: `${Math.round(perfChange)}% slower`
334
- });
335
- } else if (perfChange < -10) {
336
- comparison.improvements.push({
337
- type: 'performance',
338
- message: `${Math.abs(Math.round(perfChange))}% faster`
339
- });
340
- }
341
- }
342
-
343
- return comparison;
344
- }
345
-
346
- /**
347
- * Analyzes test history for patterns
348
- */
349
- async analyzePatterns(options = {}) {
350
- const historyPath = path.join(this.regressionDir, CONFIG.filePatterns.history);
351
-
352
- // Load history
353
- let history = [];
354
- try {
355
- const content = await fs.readFile(historyPath, 'utf8');
356
- history = JSON.parse(content);
357
- } catch (error) {
358
- // No history yet
359
- }
360
-
361
- const analysis = {
362
- flaky: [],
363
- slow: [],
364
- trends: {}
365
- };
366
-
367
- if (options.flaky) {
368
- // Analyze for flaky tests
369
- const testRuns = {};
370
- for (const run of history) {
371
- if (run.test) {
372
- if (!testRuns[run.test]) {
373
- testRuns[run.test] = [];
374
- }
375
- testRuns[run.test].push(run.passed);
376
- }
377
- }
378
-
379
- for (const [test, results] of Object.entries(testRuns)) {
380
- if (results.length < 3) continue;
381
-
382
- const passCount = results.filter(r => r).length;
383
- const failCount = results.filter(r => !r).length;
384
-
385
- if (passCount > 0 && failCount > 0) {
386
- const passRate = (passCount / results.length) * 100;
387
- if (passRate < 95 && passRate > 5) {
388
- analysis.flaky.push({
389
- test,
390
- passRate: Math.round(passRate),
391
- runs: results.length
392
- });
393
- }
394
- }
395
- }
396
- }
397
-
398
- if (options.slow) {
399
- // Identify slow tests (simulated)
400
- const threshold = options.slowThreshold || CONFIG.defaults.slowTestThreshold;
401
- analysis.slow = [
402
- { name: 'integration test', duration: 2500 },
403
- { name: 'database test', duration: 1800 }
404
- ].filter(t => t.duration > threshold);
405
- }
406
-
407
- return analysis;
408
- }
409
-
410
- /**
411
- * Adds entry to history
412
- */
413
- async addToHistory(entry) {
414
- const historyPath = path.join(this.regressionDir, CONFIG.filePatterns.history);
415
-
416
- let history = [];
417
- try {
418
- const content = await fs.readFile(historyPath, 'utf8');
419
- history = JSON.parse(content);
420
- } catch (error) {
421
- // No history yet
422
- }
423
-
424
- history.push(entry);
425
-
426
- // Limit history size
427
- if (history.length > CONFIG.defaults.maxHistorySize) {
428
- history = history.slice(-CONFIG.defaults.maxHistorySize);
429
- }
430
-
431
- await fs.mkdir(this.regressionDir, { recursive: true });
432
- await fs.writeFile(historyPath, JSON.stringify(history, null, 2));
433
-
434
- return history;
435
- }
436
-
437
- /**
438
- * Gets test trends from history
439
- */
440
- async getTrends(limit = 10) {
441
- const historyPath = path.join(this.regressionDir, CONFIG.filePatterns.history);
442
-
443
- let history = [];
444
- try {
445
- const content = await fs.readFile(historyPath, 'utf8');
446
- history = JSON.parse(content);
447
- } catch (error) {
448
- return { history: [], statistics: {} };
449
- }
450
-
451
- const recentRuns = history.slice(-limit);
452
-
453
- // Calculate statistics
454
- const statistics = {
455
- totalRuns: history.length,
456
- avgSuccessRate: 0,
457
- avgDuration: 0,
458
- avgCoverage: 0
459
- };
460
-
461
- let successCount = 0;
462
- let durationCount = 0;
463
- let coverageCount = 0;
464
-
465
- for (const run of history) {
466
- if (run.tests && run.tests.total > 0) {
467
- statistics.avgSuccessRate += (run.tests.passed / run.tests.total);
468
- successCount++;
469
- }
470
- if (run.duration) {
471
- statistics.avgDuration += run.duration;
472
- durationCount++;
473
- }
474
- if (run.coverage && run.coverage.lines) {
475
- statistics.avgCoverage += run.coverage.lines;
476
- coverageCount++;
477
- }
478
- }
479
-
480
- if (successCount > 0) {
481
- statistics.avgSuccessRate = Math.round((statistics.avgSuccessRate / successCount) * 100);
482
- }
483
- if (durationCount > 0) {
484
- statistics.avgDuration = Math.round(statistics.avgDuration / durationCount);
485
- }
486
- if (coverageCount > 0) {
487
- statistics.avgCoverage = Math.round(statistics.avgCoverage / coverageCount);
488
- }
489
-
490
- return { history: recentRuns, statistics };
491
- }
492
-
493
- /**
494
- * Generates comprehensive report
495
- */
496
- async generateReport() {
497
- // Gather all data
498
- const testResults = await this.runTests();
499
- const coverage = await this.getCoverage();
500
- const baseline = await this.loadBaseline();
501
- const { history, statistics } = await this.getTrends();
502
-
503
- // Create report
504
- const report = {
505
- timestamp: new Date().toISOString(),
506
- summary: {
507
- tests: testResults.tests || { total: 0, passed: 0, failed: 0 },
508
- coverage: coverage,
509
- duration: testResults.duration,
510
- success: testResults.success
511
- },
512
- baseline: baseline ? {
513
- timestamp: baseline.timestamp,
514
- comparison: await this.compareWithBaseline()
515
- } : null,
516
- trends: statistics,
517
- environment: {
518
- node: process.version,
519
- platform: process.platform,
520
- arch: process.arch,
521
- cwd: this.projectRoot
522
- }
523
- };
524
-
525
- // Save report
526
- await fs.mkdir(this.regressionDir, { recursive: true });
527
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
528
- const reportPath = path.join(
529
- this.regressionDir,
530
- CONFIG.filePatterns.report.replace('{timestamp}', timestamp)
531
- );
532
-
533
- await fs.writeFile(reportPath, JSON.stringify(report, null, 2));
534
-
535
- // Add to history
536
- await this.addToHistory({
537
- timestamp: report.timestamp,
538
- tests: report.summary.tests,
539
- coverage: report.summary.coverage,
540
- duration: report.summary.duration
541
- });
542
-
543
- return { report, path: reportPath };
544
- }
545
-
546
- /**
547
- * Checks coverage against threshold
548
- */
549
- checkCoverageThreshold(coverage, threshold) {
550
- const failures = [];
551
-
552
- if (coverage.lines < threshold) {
553
- failures.push({
554
- metric: 'lines',
555
- actual: coverage.lines,
556
- threshold
557
- });
558
- }
559
- if (coverage.branches < threshold) {
560
- failures.push({
561
- metric: 'branches',
562
- actual: coverage.branches,
563
- threshold
564
- });
565
- }
566
- if (coverage.functions < threshold) {
567
- failures.push({
568
- metric: 'functions',
569
- actual: coverage.functions,
570
- threshold
571
- });
572
- }
573
-
574
- return failures;
575
- }
576
- }
577
-
578
- module.exports = RegressionAnalyzer;