diff --git a/.github/scripts/workflows/__tests__/metrics-collection-orchestrator.test.js b/.github/scripts/workflows/__tests__/metrics-collection-orchestrator.test.js
index 4b35e87b2..ef57cb9ed 100644
--- a/.github/scripts/workflows/__tests__/metrics-collection-orchestrator.test.js
+++ b/.github/scripts/workflows/__tests__/metrics-collection-orchestrator.test.js
@@ -2,11 +2,13 @@
* Metrics Collection Orchestrator Tests
*/
-const fs = require('fs');
-const path = require('path');
-const { MetricsCollectionOrchestrator } = require('../metrics-collection-orchestrator');
+const fs = require("fs");
+const path = require("path");
+const {
+ MetricsCollectionOrchestrator,
+} = require("../metrics-collection-orchestrator");
-describe('MetricsCollectionOrchestrator', () => {
+describe("MetricsCollectionOrchestrator", () => {
let orchestrator;
let configPath;
let testConfig;
@@ -15,9 +17,9 @@ describe('MetricsCollectionOrchestrator', () => {
// Create test configuration
testConfig = {
schedule: {
- cron: '0 2 * * *',
- timezone: 'UTC',
- description: 'Daily metrics collection at 2 AM UTC',
+ cron: "0 2 * * *",
+ timezone: "UTC",
+ description: "Daily metrics collection at 2 AM UTC",
},
execution: {
parallelJobs: 1,
@@ -27,16 +29,16 @@ describe('MetricsCollectionOrchestrator', () => {
},
repositories: [
{
- owner: 'lightspeedwp',
- repo: '.github',
- context: 'github-control-plane',
+ owner: "lightspeedwp",
+ repo: ".github",
+ context: "github-control-plane",
enabled: true,
},
],
storage: {
- basePath: '.github/reports/metrics',
- format: 'json',
- timestampFormat: 'ISO8601',
+ basePath: ".github/reports/metrics",
+ format: "json",
+ timestampFormat: "ISO8601",
retention: {
days: 365,
maxFiles: 366,
@@ -45,17 +47,17 @@ describe('MetricsCollectionOrchestrator', () => {
notifications: {
onFailure: true,
onSuccess: false,
- channels: ['github-issues'],
+ channels: ["github-issues"],
},
logging: {
- level: 'info',
+ level: "info",
verbose: false,
- outputPath: '.github/reports/metrics/logs',
+ outputPath: ".github/reports/metrics/logs",
},
};
// Write test configuration to temporary file
- configPath = path.join(__dirname, 'test-metrics-config.json');
+ configPath = path.join(__dirname, "test-metrics-config.json");
fs.writeFileSync(configPath, JSON.stringify(testConfig, null, 2));
});
@@ -66,40 +68,40 @@ describe('MetricsCollectionOrchestrator', () => {
}
});
- test('should load configuration successfully', () => {
+ test("should load configuration successfully", () => {
orchestrator = new MetricsCollectionOrchestrator(configPath);
expect(orchestrator.config).toBeDefined();
expect(orchestrator.config.repositories).toHaveLength(1);
- expect(orchestrator.config.schedule.cron).toBe('0 2 * * *');
+ expect(orchestrator.config.schedule.cron).toBe("0 2 * * *");
});
- test('should throw error when configuration file not found', () => {
- const invalidPath = path.join(__dirname, 'nonexistent-config.json');
+ test("should throw error when configuration file not found", () => {
+ const invalidPath = path.join(__dirname, "nonexistent-config.json");
expect(() => {
new MetricsCollectionOrchestrator(invalidPath);
- }).toThrow('Configuration file not found');
+ }).toThrow("Configuration file not found");
});
- test('should throw error when repositories array is empty', () => {
+ test("should throw error when repositories array is empty", () => {
testConfig.repositories = [];
fs.writeFileSync(configPath, JSON.stringify(testConfig, null, 2));
expect(() => {
new MetricsCollectionOrchestrator(configPath);
- }).toThrow('No repositories configured');
+ }).toThrow("No repositories configured");
});
- test('should initialize storage and analyzers', () => {
+ test("should initialize storage and analyzers", () => {
orchestrator = new MetricsCollectionOrchestrator(configPath);
expect(orchestrator.storage).toBeDefined();
expect(orchestrator.trendAnalyzer).toBeDefined();
expect(orchestrator.anomalyDetector).toBeDefined();
});
- test('should handle disabled repositories', async () => {
+ test("should handle disabled repositories", async () => {
testConfig.repositories = [
- { owner: 'org', repo: 'repo1', context: 'test', enabled: true },
- { owner: 'org', repo: 'repo2', context: 'test', enabled: false },
+ { owner: "org", repo: "repo1", context: "test", enabled: true },
+ { owner: "org", repo: "repo2", context: "test", enabled: false },
];
fs.writeFileSync(configPath, JSON.stringify(testConfig, null, 2));
@@ -111,15 +113,15 @@ describe('MetricsCollectionOrchestrator', () => {
expect(orchestrator.config.repositories).toHaveLength(2);
});
- test('should generate summary with correct structure', async () => {
+ test("should generate summary with correct structure", async () => {
orchestrator = new MetricsCollectionOrchestrator(configPath);
orchestrator.startTime = Date.now();
// Add mock results
orchestrator.results = [
{
- repository: 'lightspeedwp/.github',
- status: 'success',
+ repository: "lightspeedwp/.github",
+ status: "success",
metricsCount: 15,
timestamp: new Date().toISOString(),
collectionTime: 2500,
@@ -139,14 +141,14 @@ describe('MetricsCollectionOrchestrator', () => {
expect(summary.results).toHaveLength(1);
});
- test('should handle mixed success and error results', async () => {
+ test("should handle mixed success and error results", async () => {
orchestrator = new MetricsCollectionOrchestrator(configPath);
orchestrator.startTime = Date.now();
orchestrator.results = [
{
- repository: 'lightspeedwp/.github',
- status: 'success',
+ repository: "lightspeedwp/.github",
+ status: "success",
metricsCount: 15,
timestamp: new Date().toISOString(),
collectionTime: 2500,
@@ -157,9 +159,9 @@ describe('MetricsCollectionOrchestrator', () => {
orchestrator.errors = [
{
- repository: 'lightspeedwp/plugin',
- status: 'error',
- error: 'GitHub API rate limit exceeded',
+ repository: "lightspeedwp/plugin",
+ status: "error",
+ error: "GitHub API rate limit exceeded",
timestamp: new Date().toISOString(),
},
];
@@ -169,17 +171,17 @@ describe('MetricsCollectionOrchestrator', () => {
expect(summary.execution.repositories.total).toBe(2);
expect(summary.execution.repositories.successful).toBe(1);
expect(summary.execution.repositories.failed).toBe(1);
- expect(summary.execution.repositories.percentage).toBe('50.00');
+ expect(summary.execution.repositories.percentage).toBe("50.00");
});
- test('should save summary report to disk', async () => {
+ test("should save summary report to disk", async () => {
orchestrator = new MetricsCollectionOrchestrator(configPath);
orchestrator.startTime = Date.now();
orchestrator.results = [
{
- repository: 'lightspeedwp/.github',
- status: 'success',
+ repository: "lightspeedwp/.github",
+ status: "success",
metricsCount: 15,
timestamp: new Date().toISOString(),
collectionTime: 2500,
@@ -192,12 +194,12 @@ describe('MetricsCollectionOrchestrator', () => {
// Verify summary file exists
const expectedPath = path.join(
- '.github/reports/metrics',
- `collection-summary-${new Date().toISOString().split('T')[0]}.json`
+ ".github/reports/metrics",
+ `collection-summary-${new Date().toISOString().split("T")[0]}.json`,
);
if (fs.existsSync(expectedPath)) {
- const savedSummary = JSON.parse(fs.readFileSync(expectedPath, 'utf8'));
+ const savedSummary = JSON.parse(fs.readFileSync(expectedPath, "utf8"));
expect(savedSummary.timestamp).toBeDefined();
expect(savedSummary.results).toHaveLength(1);
@@ -206,7 +208,7 @@ describe('MetricsCollectionOrchestrator', () => {
}
});
- test('should track collection duration', async () => {
+ test("should track collection duration", async () => {
orchestrator = new MetricsCollectionOrchestrator(configPath);
const startTime = Date.now();
orchestrator.startTime = startTime;
@@ -216,8 +218,8 @@ describe('MetricsCollectionOrchestrator', () => {
orchestrator.results = [
{
- repository: 'lightspeedwp/.github',
- status: 'success',
+ repository: "lightspeedwp/.github",
+ status: "success",
metricsCount: 15,
timestamp: new Date().toISOString(),
collectionTime: 2500,
@@ -232,7 +234,7 @@ describe('MetricsCollectionOrchestrator', () => {
expect(summary.execution.duration).toBeGreaterThan(0);
});
- test('should handle parallel vs sequential execution configuration', () => {
+ test("should handle parallel vs sequential execution configuration", () => {
testConfig.execution.parallelJobs = 4;
fs.writeFileSync(configPath, JSON.stringify(testConfig, null, 2));
@@ -241,7 +243,7 @@ describe('MetricsCollectionOrchestrator', () => {
expect(orchestrator.config.execution.parallelJobs).toBe(4);
});
- test('should validate configuration structure', () => {
+ test("should validate configuration structure", () => {
orchestrator = new MetricsCollectionOrchestrator(configPath);
expect(orchestrator.config.schedule).toBeDefined();
diff --git a/.github/scripts/workflows/metrics-collection-orchestrator.js b/.github/scripts/workflows/metrics-collection-orchestrator.js
index b38082161..66c811dab 100755
--- a/.github/scripts/workflows/metrics-collection-orchestrator.js
+++ b/.github/scripts/workflows/metrics-collection-orchestrator.js
@@ -6,12 +6,12 @@
* Handles GitHub API interactions, storage, and error recovery
*/
-const fs = require('fs');
-const path = require('path');
-const { GitHubAPIClient } = require('../../scripts/metrics/metrics-agent');
-const { MetricsStorage } = require('../../scripts/metrics/metrics-storage');
-const { TrendAnalyzer } = require('../../scripts/metrics/trend-analyzer');
-const { AnomalyDetector } = require('../../scripts/metrics/anomaly-detector');
+const fs = require("fs");
+const path = require("path");
+const { GitHubAPIClient } = require("../../scripts/metrics/metrics-agent");
+const { MetricsStorage } = require("../../scripts/metrics/metrics-storage");
+const { TrendAnalyzer } = require("../../scripts/metrics/trend-analyzer");
+const { AnomalyDetector } = require("../../scripts/metrics/anomaly-detector");
class MetricsCollectionOrchestrator {
constructor(configPath) {
@@ -29,11 +29,11 @@ class MetricsCollectionOrchestrator {
throw new Error(`Configuration file not found: ${this.configPath}`);
}
- const configContent = fs.readFileSync(this.configPath, 'utf8');
+ const configContent = fs.readFileSync(this.configPath, "utf8");
const config = JSON.parse(configContent);
if (!config.repositories || config.repositories.length === 0) {
- throw new Error('No repositories configured for metrics collection');
+ throw new Error("No repositories configured for metrics collection");
}
return config;
@@ -74,19 +74,19 @@ class MetricsCollectionOrchestrator {
// Analyze trends
const trends = await this.trendAnalyzer.analyzeTrends(
repositoryKey,
- this.storage
+ this.storage,
);
// Detect anomalies
const anomalies = await this.anomalyDetector.detectAnomalies(
repositoryKey,
enrichedMetrics,
- trends
+ trends,
);
const result = {
repository: repositoryKey,
- status: 'success',
+ status: "success",
metricsCount: Object.keys(metrics).length,
timestamp: enrichedMetrics.timestamp,
collectionTime: enrichedMetrics.collectionTime,
@@ -96,33 +96,42 @@ class MetricsCollectionOrchestrator {
this.results.push(result);
console.log(`โ
Successfully collected metrics for ${repositoryKey}`);
- console.log(` Metrics: ${result.metricsCount} | Anomalies: ${result.anomalies}`);
+ console.log(
+ ` Metrics: ${result.metricsCount} | Anomalies: ${result.anomalies}`,
+ );
return result;
} catch (error) {
const errorResult = {
repository: repositoryKey,
- status: 'error',
+ status: "error",
error: error.message,
timestamp: new Date().toISOString(),
};
this.errors.push(errorResult);
- console.error(`โ Error collecting metrics for ${repositoryKey}:`, error.message);
+ console.error(
+ `โ Error collecting metrics for ${repositoryKey}:`,
+ error.message,
+ );
return errorResult;
}
}
async orchestrateCollection() {
- console.log('\n๐ Starting metrics collection...');
- console.log(`๐ Repositories to process: ${this.config.repositories.length}`);
+ console.log("\n๐ Starting metrics collection...");
+ console.log(
+ `๐ Repositories to process: ${this.config.repositories.length}`,
+ );
console.log(`โ๏ธ Parallel jobs: ${this.config.execution.parallelJobs}`);
- const enabledRepos = this.config.repositories.filter((repo) => repo.enabled !== false);
+ const enabledRepos = this.config.repositories.filter(
+ (repo) => repo.enabled !== false,
+ );
if (enabledRepos.length === 0) {
- console.warn('โ ๏ธ No enabled repositories found in configuration');
+ console.warn("โ ๏ธ No enabled repositories found in configuration");
return this.generateSummary();
}
@@ -137,7 +146,9 @@ class MetricsCollectionOrchestrator {
const batchSize = this.config.execution.parallelJobs;
for (let i = 0; i < enabledRepos.length; i += batchSize) {
const batch = enabledRepos.slice(i, i + batchSize);
- await Promise.all(batch.map((repo) => this.collectMetricsForRepository(repo)));
+ await Promise.all(
+ batch.map((repo) => this.collectMetricsForRepository(repo)),
+ );
}
}
@@ -145,7 +156,9 @@ class MetricsCollectionOrchestrator {
}
generateSummary() {
- const successCount = this.results.filter((r) => r.status === 'success').length;
+ const successCount = this.results.filter(
+ (r) => r.status === "success",
+ ).length;
const errorCount = this.errors.length;
const totalCount = successCount + errorCount;
@@ -158,7 +171,8 @@ class MetricsCollectionOrchestrator {
total: totalCount,
successful: successCount,
failed: errorCount,
- percentage: totalCount > 0 ? ((successCount / totalCount) * 100).toFixed(2) : 0,
+ percentage:
+ totalCount > 0 ? ((successCount / totalCount) * 100).toFixed(2) : 0,
},
},
results: this.results,
@@ -173,15 +187,17 @@ class MetricsCollectionOrchestrator {
// Save summary report
const summaryPath = path.join(
this.config.storage.basePath,
- `collection-summary-${new Date().toISOString().split('T')[0]}.json`
+ `collection-summary-${new Date().toISOString().split("T")[0]}.json`,
);
fs.mkdirSync(path.dirname(summaryPath), { recursive: true });
fs.writeFileSync(summaryPath, JSON.stringify(summary, null, 2));
- console.log('\n๐ Collection Summary');
+ console.log("\n๐ Collection Summary");
console.log(`โ
Successful: ${successCount}/${totalCount}`);
console.log(`โ Failed: ${errorCount}/${totalCount}`);
- console.log(`โฑ๏ธ Duration: ${(summary.execution.duration / 1000).toFixed(2)}s`);
+ console.log(
+ `โฑ๏ธ Duration: ${(summary.execution.duration / 1000).toFixed(2)}s`,
+ );
console.log(`๐พ Summary saved to: ${summaryPath}`);
// Return exit code based on success rate
@@ -196,10 +212,13 @@ class MetricsCollectionOrchestrator {
this.startTime = Date.now();
try {
const summary = await this.orchestrateCollection();
- console.log('\nโจ Metrics collection completed successfully');
+ console.log("\nโจ Metrics collection completed successfully");
return summary;
} catch (error) {
- console.error('\n๐ฅ Fatal error during metrics collection:', error.message);
+ console.error(
+ "\n๐ฅ Fatal error during metrics collection:",
+ error.message,
+ );
process.exit(1);
}
}
@@ -209,10 +228,11 @@ class MetricsCollectionOrchestrator {
async function main() {
// Parse command line arguments
const args = process.argv.slice(2);
- const context = args.includes('--context')
- ? args[args.indexOf('--context') + 1]
- : 'github-control-plane';
- const dryRun = args.includes('--dryRun') && args[args.indexOf('--dryRun') + 1] === 'true';
+ const context = args.includes("--context")
+ ? args[args.indexOf("--context") + 1]
+ : "github-control-plane";
+ const dryRun =
+ args.includes("--dryRun") && args[args.indexOf("--dryRun") + 1] === "true";
const configPath = path.join(__dirname, `metrics-config.json`);
@@ -225,16 +245,16 @@ async function main() {
// In dry-run mode, report but don't commit
if (dryRun) {
- console.log('\n๐งช DRY RUN MODE - No changes were persisted');
+ console.log("\n๐งช DRY RUN MODE - No changes were persisted");
} else {
- console.log('\n๐พ Results ready for commit');
+ console.log("\n๐พ Results ready for commit");
}
process.exit(0);
}
main().catch((error) => {
- console.error('Fatal error:', error);
+ console.error("Fatal error:", error);
process.exit(1);
});
diff --git a/.github/scripts/workflows/metrics-reporting-orchestrator.js b/.github/scripts/workflows/metrics-reporting-orchestrator.js
index 3ea36d160..c452a8fa1 100755
--- a/.github/scripts/workflows/metrics-reporting-orchestrator.js
+++ b/.github/scripts/workflows/metrics-reporting-orchestrator.js
@@ -5,23 +5,27 @@
* Generates metrics reports and manages GitHub issues
*/
-const fs = require('fs');
-const path = require('path');
-const { MetricsStorage } = require('../../scripts/metrics/metrics-storage');
-const { MetricsReporter } = require('../../scripts/metrics/metrics-reporter');
-const { TrendAnalyzer } = require('../../scripts/metrics/trend-analyzer');
-const { AnomalyDetector } = require('../../scripts/metrics/anomaly-detector');
+const fs = require("fs");
+const path = require("path");
+const { MetricsStorage } = require("../../scripts/metrics/metrics-storage");
+const { MetricsReporter } = require("../../scripts/metrics/metrics-reporter");
+const { TrendAnalyzer } = require("../../scripts/metrics/trend-analyzer");
+const { AnomalyDetector } = require("../../scripts/metrics/anomaly-detector");
class MetricsReportingOrchestrator {
constructor() {
- this.storage = new MetricsStorage('.github/reports/metrics');
+ this.storage = new MetricsStorage(".github/reports/metrics");
this.trendAnalyzer = new TrendAnalyzer();
this.anomalyDetector = new AnomalyDetector();
- this.reporter = new MetricsReporter(this.storage, this.trendAnalyzer, this.anomalyDetector);
+ this.reporter = new MetricsReporter(
+ this.storage,
+ this.trendAnalyzer,
+ this.anomalyDetector,
+ );
this.reports = [];
}
- async generateReports(repositories, period = 'weekly') {
+ async generateReports(repositories, period = "weekly") {
console.log(`\n๐ Generating ${period} metrics reports...`);
console.log(`๐ฆ Repositories to report on: ${repositories.length}`);
@@ -46,7 +50,7 @@ class MetricsReportingOrchestrator {
this.reports.push({
repository: reportKey,
- status: 'success',
+ status: "success",
reportPath,
period,
timestamp: new Date().toISOString(),
@@ -54,11 +58,14 @@ class MetricsReportingOrchestrator {
console.log(`โ
Report saved to: ${reportPath}`);
} catch (error) {
- console.error(`โ Error generating report for ${repo.owner}/${repo.repo}:`, error.message);
+ console.error(
+ `โ Error generating report for ${repo.owner}/${repo.repo}:`,
+ error.message,
+ );
this.reports.push({
repository: `${repo.owner}/${repo.repo}`,
- status: 'error',
+ status: "error",
error: error.message,
timestamp: new Date().toISOString(),
});
@@ -69,11 +76,11 @@ class MetricsReportingOrchestrator {
}
saveReport(repository, report, period) {
- const reportDir = path.join('.github/reports/metrics');
+ const reportDir = path.join(".github/reports/metrics");
fs.mkdirSync(reportDir, { recursive: true });
- const dateString = new Date().toISOString().split('T')[0];
- const reportFileName = `report-${repository.replace('/', '-')}-${period}-${dateString}.md`;
+ const dateString = new Date().toISOString().split("T")[0];
+ const reportFileName = `report-${repository.replace("/", "-")}-${period}-${dateString}.md`;
const reportPath = path.join(reportDir, reportFileName);
fs.writeFileSync(reportPath, report);
@@ -81,8 +88,10 @@ class MetricsReportingOrchestrator {
}
generateSummary() {
- const successCount = this.reports.filter((r) => r.status === 'success').length;
- const errorCount = this.reports.filter((r) => r.status === 'error').length;
+ const successCount = this.reports.filter(
+ (r) => r.status === "success",
+ ).length;
+ const errorCount = this.reports.filter((r) => r.status === "error").length;
const totalCount = this.reports.length;
const summary = {
@@ -97,14 +106,14 @@ class MetricsReportingOrchestrator {
reports: this.reports,
};
- console.log('\n๐ Reporting Summary');
+ console.log("\n๐ Reporting Summary");
console.log(`โ
Successful: ${successCount}/${totalCount}`);
console.log(`โ Failed: ${errorCount}/${totalCount}`);
// Save summary
const summaryPath = path.join(
- '.github/reports/metrics',
- `reporting-summary-${new Date().toISOString().split('T')[0]}.json`
+ ".github/reports/metrics",
+ `reporting-summary-${new Date().toISOString().split("T")[0]}.json`,
);
fs.writeFileSync(summaryPath, JSON.stringify(summary, null, 2));
console.log(`๐พ Summary saved to: ${summaryPath}`);
@@ -112,12 +121,16 @@ class MetricsReportingOrchestrator {
return summary;
}
- async run(period = 'weekly') {
+ async run(period = "weekly") {
try {
// Get list of repositories from config
- const configPath = path.join('.github/scripts/workflows/metrics-config.json');
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
- const repositories = config.repositories.filter((r) => r.enabled !== false);
+ const configPath = path.join(
+ ".github/scripts/workflows/metrics-config.json",
+ );
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
+ const repositories = config.repositories.filter(
+ (r) => r.enabled !== false,
+ );
// Generate reports
await this.generateReports(repositories, period);
@@ -125,10 +138,10 @@ class MetricsReportingOrchestrator {
// Generate summary
const summary = this.generateSummary();
- console.log('\nโจ Reporting completed successfully');
+ console.log("\nโจ Reporting completed successfully");
return summary;
} catch (error) {
- console.error('\n๐ฅ Fatal error during reporting:', error.message);
+ console.error("\n๐ฅ Fatal error during reporting:", error.message);
process.exit(1);
}
}
@@ -137,11 +150,11 @@ class MetricsReportingOrchestrator {
// Main execution
async function main() {
const args = process.argv.slice(2);
- const reportType = args.includes('--reportType')
- ? args[args.indexOf('--reportType') + 1]
- : 'weekly';
- const includeArchive = args.includes('--includeArchive')
- ? args[args.indexOf('--includeArchive') + 1] === 'true'
+ const reportType = args.includes("--reportType")
+ ? args[args.indexOf("--reportType") + 1]
+ : "weekly";
+ const includeArchive = args.includes("--includeArchive")
+ ? args[args.indexOf("--includeArchive") + 1] === "true"
: false;
console.log(`๐ง Report Type: ${reportType}`);
@@ -154,7 +167,7 @@ async function main() {
}
main().catch((error) => {
- console.error('Fatal error:', error);
+ console.error("Fatal error:", error);
process.exit(1);
});
diff --git a/agents/metadata-agent/__tests__/api/retry-strategy.test.js b/agents/metadata-agent/__tests__/api/retry-strategy.test.js
index 610665c74..3ce470c55 100644
--- a/agents/metadata-agent/__tests__/api/retry-strategy.test.js
+++ b/agents/metadata-agent/__tests__/api/retry-strategy.test.js
@@ -209,7 +209,9 @@ describe("RetryStrategy", () => {
const fn = jest.fn().mockRejectedValue(error);
- await expect(fastStrategy.execute(fn)).rejects.toThrow("Persistent error");
+ await expect(fastStrategy.execute(fn)).rejects.toThrow(
+ "Persistent error",
+ );
expect(fn).toHaveBeenCalledTimes(3); // 2 retries + 1 initial = 3 total calls
}, 5000);
diff --git a/agents/pr-creation-agent/__tests__/integration/error-recovery-workflows.test.js b/agents/pr-creation-agent/__tests__/integration/error-recovery-workflows.test.js
index 7efcf2c36..61e758862 100644
--- a/agents/pr-creation-agent/__tests__/integration/error-recovery-workflows.test.js
+++ b/agents/pr-creation-agent/__tests__/integration/error-recovery-workflows.test.js
@@ -1,28 +1,31 @@
// Category D: Error Recovery Workflows (8 tests)
// Test graceful error handling and recovery
-import { describe, test, expect, beforeEach } from '@jest/globals';
-import { validateBranchName } from '../../skills/validate-branch-name.js';
-import { routePrTemplate } from '../../skills/route-pr-template.js';
-import { validateAndApplyLabels } from '../../skills/validate-and-apply-labels.js';
-import { orchestratePrCreation } from '../../skills/orchestrate-pr-creation.js';
-import { MockGitHub, createMockConfig } from './setup.js';
+import { describe, test } from "@jest/globals";
-describe('Category D: Error Recovery Workflows', () => {
- let mockGitHub;
- let config;
-
- beforeEach(() => {
- mockGitHub = new MockGitHub();
- config = createMockConfig();
- });
-
- test.todo('Test D1: Branch Validation Timeout โ Fallback, continue (requires timeout support in skills)');
- test.todo('Test D2: GitHub API Failure โ Retry with backoff (requires GitHub client with retry logic)');
- test.todo('Test D3: Template File Missing โ Use default template (requires file I/O and fallback handling)');
- test.todo('Test D4: Invalid JSON in Config โ Validation error, halt (requires config validation)');
- test.todo('Test D5: Partial Label Application Failure โ Log error, apply remaining labels (requires GitHub API integration)');
- test.todo('Test D6: PR Creation Failure After Validation โ Error message, no retries (requires GitHub client)');
- test.todo('Test D7: Network Timeout During Labeling โ Retry up to 3 times (requires retry logic with backoff)');
- test.todo('Test D8: Concurrent Workflow Conflicts โ Handle race conditions (requires GitHub API interactions)');
+describe("Category D: Error Recovery Workflows", () => {
+ test.todo(
+ "Test D1: Branch Validation Timeout โ Fallback, continue (requires timeout support in skills)",
+ );
+ test.todo(
+ "Test D2: GitHub API Failure โ Retry with backoff (requires GitHub client with retry logic)",
+ );
+ test.todo(
+ "Test D3: Template File Missing โ Use default template (requires file I/O and fallback handling)",
+ );
+ test.todo(
+ "Test D4: Invalid JSON in Config โ Validation error, halt (requires config validation)",
+ );
+ test.todo(
+ "Test D5: Partial Label Application Failure โ Log error, apply remaining labels (requires GitHub API integration)",
+ );
+ test.todo(
+ "Test D6: PR Creation Failure After Validation โ Error message, no retries (requires GitHub client)",
+ );
+ test.todo(
+ "Test D7: Network Timeout During Labeling โ Retry up to 3 times (requires retry logic with backoff)",
+ );
+ test.todo(
+ "Test D8: Concurrent Workflow Conflicts โ Handle race conditions (requires GitHub API interactions)",
+ );
});
diff --git a/agents/pr-creation-agent/__tests__/integration/label-application-scenarios.test.js b/agents/pr-creation-agent/__tests__/integration/label-application-scenarios.test.js
index 884b1964d..688c0d774 100644
--- a/agents/pr-creation-agent/__tests__/integration/label-application-scenarios.test.js
+++ b/agents/pr-creation-agent/__tests__/integration/label-application-scenarios.test.js
@@ -1,11 +1,11 @@
// Category B: Label Application Scenarios (8 tests)
// Test complex label scenarios
-import { describe, test, expect, beforeEach } from '@jest/globals';
-import { validateAndApplyLabels } from '../../skills/validate-and-apply-labels.js';
-import { MockGitHub, createMockConfig } from './setup.js';
+import { describe, test, expect, beforeEach } from "@jest/globals";
+import { validateAndApplyLabels } from "../../skills/validate-and-apply-labels.js";
+import { MockGitHub, createMockConfig } from "./setup.js";
-describe('Category B: Label Application Scenarios', () => {
+describe("Category B: Label Application Scenarios", () => {
let mockGitHub;
let config;
@@ -14,8 +14,8 @@ describe('Category B: Label Application Scenarios', () => {
config = createMockConfig();
});
- test('Test B1: Single Label Application โ type:feature only', async () => {
- const labels = ['type:feature'];
+ test("Test B1: Single Label Application โ type:feature only", async () => {
+ const labels = ["type:feature"];
const result = await validateAndApplyLabels({
labels,
@@ -24,12 +24,12 @@ describe('Category B: Label Application Scenarios', () => {
});
expect(result.valid).toBe(true);
- expect(result.appliedLabels).toEqual(['type:feature']);
+ expect(result.appliedLabels).toEqual(["type:feature"]);
expect(result.appliedLabels.length).toBe(1);
});
- test('Test B2: Multiple Labels โ type:feature + area:agents', async () => {
- const labels = ['type:feature', 'area:agents'];
+ test("Test B2: Multiple Labels โ type:feature + area:agents", async () => {
+ const labels = ["type:feature", "area:agents"];
const result = await validateAndApplyLabels({
labels,
@@ -42,9 +42,9 @@ describe('Category B: Label Application Scenarios', () => {
expect(result.appliedLabels.length).toBe(2);
});
- test('Test B3: Label Conflicts โ Resolved per labeling strategy', async () => {
+ test("Test B3: Label Conflicts โ Resolved per labeling strategy", async () => {
// Mutually exclusive labels (both type:feature and type:bug)
- const labels = ['type:feature', 'type:bug'];
+ const labels = ["type:feature", "type:bug"];
const result = await validateAndApplyLabels({
labels,
@@ -57,8 +57,8 @@ describe('Category B: Label Application Scenarios', () => {
expect(result.conflicts.length).toBeGreaterThan(0);
});
- test('Test B4: Missing Canonical Labels โ Validation error', async () => {
- const labels = ['custom-label'];
+ test("Test B4: Missing Canonical Labels โ Validation error", async () => {
+ const labels = ["custom-label"];
const result = await validateAndApplyLabels({
labels,
@@ -67,11 +67,11 @@ describe('Category B: Label Application Scenarios', () => {
});
expect(result.valid).toBe(false);
- expect(result.errors).toContain('non-canonical-label');
+ expect(result.errors).toContain("non-canonical-label");
});
- test('Test B5: Custom Labels โ Rejected (canonical only)', async () => {
- const labels = ['my-custom-label', 'type:feature'];
+ test("Test B5: Custom Labels โ Rejected (canonical only)", async () => {
+ const labels = ["my-custom-label", "type:feature"];
const result = await validateAndApplyLabels({
labels,
@@ -80,13 +80,13 @@ describe('Category B: Label Application Scenarios', () => {
});
expect(result.valid).toBe(false);
- expect(result.invalidLabels).toContain('my-custom-label');
+ expect(result.invalidLabels).toContain("my-custom-label");
});
- test('Test B6: Conditional Labels โ Applied based on branch type', async () => {
+ test("Test B6: Conditional Labels โ Applied based on branch type", async () => {
// Branch type determines which labels should be applied
- const branchType = 'fix';
- const conditionalLabels = ['type:bug'];
+ const branchType = "fix";
+ const conditionalLabels = ["type:bug"];
const result = await validateAndApplyLabels({
labels: conditionalLabels,
@@ -96,11 +96,11 @@ describe('Category B: Label Application Scenarios', () => {
});
expect(result.valid).toBe(true);
- expect(result.appliedLabels).toContain('type:bug');
+ expect(result.appliedLabels).toContain("type:bug");
});
- test('Test B7: Label Priority โ Higher priority labels applied first', async () => {
- const labels = ['area:agents', 'type:feature', 'priority:critical'];
+ test("Test B7: Label Priority โ Higher priority labels applied first", async () => {
+ const labels = ["area:agents", "type:feature", "priority:critical"];
const result = await validateAndApplyLabels({
labels,
@@ -110,11 +110,11 @@ describe('Category B: Label Application Scenarios', () => {
expect(result.valid).toBe(true);
// Priority labels should be applied first in the order
- expect(result.appliedLabels[0]).toBe('priority:critical');
+ expect(result.appliedLabels[0]).toBe("priority:critical");
});
- test('Test B8: Label Deduplication โ Duplicate labels removed', async () => {
- const labels = ['type:feature', 'type:feature', 'area:agents'];
+ test("Test B8: Label Deduplication โ Duplicate labels removed", async () => {
+ const labels = ["type:feature", "type:feature", "area:agents"];
const result = await validateAndApplyLabels({
labels,
@@ -123,7 +123,7 @@ describe('Category B: Label Application Scenarios', () => {
});
expect(result.valid).toBe(true);
- expect(result.appliedLabels).toEqual(['type:feature', 'area:agents']);
+ expect(result.appliedLabels).toEqual(["type:feature", "area:agents"]);
expect(result.appliedLabels.length).toBe(2);
expect(result.deduplicatedCount).toBe(1);
});
diff --git a/agents/pr-creation-agent/__tests__/integration/performance-edge-cases.test.js b/agents/pr-creation-agent/__tests__/integration/performance-edge-cases.test.js
index 332f09a23..08257c8bb 100644
--- a/agents/pr-creation-agent/__tests__/integration/performance-edge-cases.test.js
+++ b/agents/pr-creation-agent/__tests__/integration/performance-edge-cases.test.js
@@ -1,14 +1,14 @@
// Category F: Performance & Edge Cases (10 tests)
// Test performance and unusual scenarios
-import { describe, test, expect, beforeEach } from '@jest/globals';
-import { validateBranchName } from '../../skills/validate-branch-name.js';
-import { routePrTemplate } from '../../skills/route-pr-template.js';
-import { validateAndApplyLabels } from '../../skills/validate-and-apply-labels.js';
-import { orchestratePrCreation } from '../../skills/orchestrate-pr-creation.js';
-import { MockGitHub, createMockConfig } from './setup.js';
-
-describe('Category F: Performance & Edge Cases', () => {
+import { describe, test, expect, beforeEach } from "@jest/globals";
+import { validateBranchName } from "../../skills/validate-branch-name.js";
+import { routePrTemplate } from "../../skills/route-pr-template.js";
+import { validateAndApplyLabels } from "../../skills/validate-and-apply-labels.js";
+import { orchestratePrCreation } from "../../skills/orchestrate-pr-creation.js";
+import { MockGitHub, createMockConfig } from "./setup.js";
+
+describe("Category F: Performance & Edge Cases", () => {
let mockGitHub;
let config;
@@ -17,15 +17,15 @@ describe('Category F: Performance & Edge Cases', () => {
config = createMockConfig();
});
- test('Test F1: Large PR Size โ 100+ files affected', async () => {
+ test("Test F1: Large PR Size โ 100+ files affected", async () => {
const prData = {
- owner: 'lightspeedwp',
- repo: '.github',
- title: 'Large refactor',
- body: '## Description\n\nRefactoring 100+ files',
- head: 'refactor/large-refactor',
- base: 'develop',
- labels: ['type:refactor'],
+ owner: "lightspeedwp",
+ repo: ".github",
+ title: "Large refactor",
+ body: "## Description\n\nRefactoring 100+ files",
+ head: "refactor/large-refactor",
+ base: "develop",
+ labels: ["type:refactor"],
filesChanged: 150,
};
@@ -41,8 +41,9 @@ describe('Category F: Performance & Edge Cases', () => {
expect(duration).toBeLessThan(5000); // Should complete in < 5 seconds
});
- test('Test F2: Long Branch Name โ 150+ character branch', async () => {
- const branchName = 'feat/very-long-branch-name-with-many-segments-to-test-validation-and-routing-and-everything-else-that-might-fail-with-unusually-long-names-and-complex-scenarios-for-testing';
+ test("Test F2: Long Branch Name โ 150+ character branch", async () => {
+ const branchName =
+ "feat/very-long-branch-name-with-many-segments-to-test-validation-and-routing-and-everything-else-that-might-fail-with-unusually-long-names-and-complex-scenarios-for-testing";
const result = await validateBranchName({
branchName,
@@ -51,24 +52,24 @@ describe('Category F: Performance & Edge Cases', () => {
// Should handle long names gracefully
if (result.valid) {
- expect(result.type).toBe('feat');
+ expect(result.type).toBe("feat");
} else {
- expect(result.errors).toContain('name-too-long');
+ expect(result.errors).toContain("name-too-long");
}
});
- test('Test F3: High Label Count โ 10+ labels applied', async () => {
+ test("Test F3: High Label Count โ 10+ labels applied", async () => {
const labels = [
- 'type:feature',
- 'area:agents',
- 'priority:critical',
- 'meta:needs-changelog',
- 'type:enhancement',
- 'status:in-review',
- 'scope:backend',
- 'scope:api',
- 'performance:optimization',
- 'documentation:required',
+ "type:feature",
+ "area:agents",
+ "priority:critical",
+ "meta:needs-changelog",
+ "type:enhancement",
+ "status:in-review",
+ "scope:backend",
+ "scope:api",
+ "performance:optimization",
+ "documentation:required",
];
const result = await validateAndApplyLabels({
@@ -82,29 +83,26 @@ describe('Category F: Performance & Edge Cases', () => {
expect(result.appliedLabels.length).toBeGreaterThan(0);
});
- test('Test F4: Template File Large โ 50KB+ template', async () => {
- // Create a large template content
- const largeContent = 'x'.repeat(50000);
- mockGitHub.repos.getContent = async () => ({
- name: 'pr_feature.md',
- path: '.github/PULL_REQUEST_TEMPLATE/pr_feature.md',
- size: 50000,
- content: Buffer.from(largeContent).toString('base64'),
- });
-
+ test("Test F4: Template Routing Performance โ feat branch returns pr_feature.md", async () => {
+ // Template routing should complete quickly regardless of branch characteristics
+ const startTime = Date.now();
const result = await routePrTemplate({
- branchName: 'feat/test',
+ branchName: "feat/test",
config,
});
+ const duration = Date.now() - startTime;
expect(result.routed).toBe(true);
- expect(result.template).toBe('pr_feature.md');
+ expect(result.template).toBe("pr_feature.md");
+ expect(duration).toBeLessThan(100); // Should be sub-100ms
});
- test.todo('Test F5: API Rate Limit Handling โ 429 responses (requires GitHub client with rate limit handling)');
+ test.todo(
+ "Test F5: API Rate Limit Handling โ 429 responses (requires GitHub client with rate limit handling)",
+ );
- test('Test F6: Concurrent Label Conflicts โ Two labels mutually exclusive', async () => {
- const labels = ['type:feature', 'type:bug']; // Mutually exclusive
+ test("Test F6: Concurrent Label Conflicts โ Two labels mutually exclusive", async () => {
+ const labels = ["type:feature", "type:bug"]; // Mutually exclusive
const result = await validateAndApplyLabels({
labels,
@@ -118,9 +116,9 @@ describe('Category F: Performance & Edge Cases', () => {
expect(result.conflicts.length).toBeGreaterThan(0);
});
- test('Test F7: Branch Rename Mid-Workflow โ Handle gracefully', async () => {
- const originalBranch = 'feat/original-name';
- const renamedBranch = 'feat/new-name';
+ test("Test F7: Branch Rename Mid-Workflow โ Handle gracefully", async () => {
+ const originalBranch = "feat/original-name";
+ const renamedBranch = "feat/new-name";
// Start with original branch
const result1 = await validateBranchName({
@@ -137,24 +135,24 @@ describe('Category F: Performance & Edge Cases', () => {
expect(result2.valid).toBe(true);
// Both should be valid independently
- expect(result1.type).toBe('feat');
- expect(result2.type).toBe('feat');
+ expect(result1.type).toBe("feat");
+ expect(result2.type).toBe("feat");
});
- test('Test F8: GitHub API Version Change โ Fallback behavior', async () => {
+ test("Test F8: GitHub API Version Change โ Fallback behavior", async () => {
// Simulate API response with unexpected structure
mockGitHub.repos.get = async () => ({
- name: 'test-repo',
+ name: "test-repo",
// Missing expected fields
});
const prData = {
- owner: 'lightspeedwp',
- repo: '.github',
- title: 'Test PR',
- body: 'Test',
- head: 'feat/test',
- base: 'develop',
+ owner: "lightspeedwp",
+ repo: ".github",
+ title: "Test PR",
+ body: "Test",
+ head: "feat/test",
+ base: "develop",
};
const result = await orchestratePrCreation({
@@ -168,26 +166,28 @@ describe('Category F: Performance & Edge Cases', () => {
expect(result.error || result.success).toBeDefined();
});
- test('Test F9: Special Characters in Branch โ URL encoding validation', async () => {
+ test("Test F9: Special Characters in Branch โ URL encoding validation", async () => {
const cases = [
- { branch: 'feat/test-with-dash', valid: true },
- { branch: 'feat/test_with_underscore', valid: false },
- { branch: 'feat/test.with.dots', valid: false },
+ { branch: "feat/test-with-dash", valid: true },
+ { branch: "feat/test_with_underscore", valid: false },
+ { branch: "feat/test.with.dots", valid: false },
];
const results = await Promise.all(
cases.map(({ branch }) =>
- validateBranchName({ branchName: branch, config })
- )
+ validateBranchName({ branchName: branch, config }),
+ ),
);
results.forEach((result, index) => {
expect(result.valid).toBe(cases[index].valid);
if (!result.valid) {
- expect(result.errors).toContain('branch-slug-invalid');
+ expect(result.errors).toContain("branch-slug-invalid");
}
});
});
- test.todo('Test F10: Timeout During Labeling โ Timeout recovery (requires GitHub API integration with timeout support)');
+ test.todo(
+ "Test F10: Timeout During Labeling โ Timeout recovery (requires GitHub API integration with timeout support)",
+ );
});
diff --git a/agents/pr-creation-agent/__tests__/integration/real-github-workflows.test.js b/agents/pr-creation-agent/__tests__/integration/real-github-workflows.test.js
index 2a1892c10..fca4d6608 100644
--- a/agents/pr-creation-agent/__tests__/integration/real-github-workflows.test.js
+++ b/agents/pr-creation-agent/__tests__/integration/real-github-workflows.test.js
@@ -1,14 +1,14 @@
// Category E: Real GitHub Workflows (10 tests)
// Test complete end-to-end workflows
-import { describe, test, expect, beforeEach } from '@jest/globals';
-import { validateBranchName } from '../../skills/validate-branch-name.js';
-import { routePrTemplate } from '../../skills/route-pr-template.js';
-import { validateAndApplyLabels } from '../../skills/validate-and-apply-labels.js';
-import { orchestratePrCreation } from '../../skills/orchestrate-pr-creation.js';
-import { MockGitHub, createMockConfig } from './setup.js';
-
-describe('Category E: Real GitHub Workflows', () => {
+import { describe, test, expect, beforeEach } from "@jest/globals";
+import { validateBranchName } from "../../skills/validate-branch-name.js";
+import { routePrTemplate } from "../../skills/route-pr-template.js";
+import { validateAndApplyLabels } from "../../skills/validate-and-apply-labels.js";
+import { orchestratePrCreation } from "../../skills/orchestrate-pr-creation.js";
+import { MockGitHub, createMockConfig } from "./setup.js";
+
+describe("Category E: Real GitHub Workflows", () => {
let mockGitHub;
let config;
@@ -17,9 +17,9 @@ describe('Category E: Real GitHub Workflows', () => {
config = createMockConfig();
});
- test('Test E1: Feature Branch Complete Workflow โ All 4 skills succeed', async () => {
- const branchName = 'feat/new-dashboard';
- const labels = ['type:feature'];
+ test("Test E1: Feature Branch Complete Workflow โ All 4 skills succeed", async () => {
+ const branchName = "feat/new-dashboard";
+ const labels = ["type:feature"];
// Validate branch
const branchValidation = await validateBranchName({ branchName, config });
@@ -28,7 +28,7 @@ describe('Category E: Real GitHub Workflows', () => {
// Route template
const templateRoute = await routePrTemplate({ branchName, config });
expect(templateRoute.routed).toBe(true);
- expect(templateRoute.template).toBe('pr_feature.md');
+ expect(templateRoute.template).toBe("pr_feature.md");
// Validate labels
const labelValidation = await validateAndApplyLabels({
@@ -40,12 +40,12 @@ describe('Category E: Real GitHub Workflows', () => {
// Orchestrate PR creation
const prData = {
- owner: 'lightspeedwp',
- repo: '.github',
- title: 'Add new dashboard',
- body: '## Description\n\nNew dashboard feature',
+ owner: "lightspeedwp",
+ repo: ".github",
+ title: "Add new dashboard",
+ body: "## Description\n\nNew dashboard feature",
head: branchName,
- base: 'develop',
+ base: "develop",
labels,
};
@@ -57,16 +57,16 @@ describe('Category E: Real GitHub Workflows', () => {
expect(prResult.success).toBe(true);
});
- test('Test E2: Bug Fix Workflow โ Branch validation โ bug template โ labels โ PR', async () => {
- const branchName = 'fix/invalid-validation';
- const labels = ['type:bug', 'priority:critical'];
+ test("Test E2: Bug Fix Workflow โ Branch validation โ bug template โ labels โ PR", async () => {
+ const branchName = "fix/invalid-validation";
+ const labels = ["type:bug", "priority:critical"];
const branchValidation = await validateBranchName({ branchName, config });
expect(branchValidation.valid).toBe(true);
- expect(branchValidation.type).toBe('fix');
+ expect(branchValidation.type).toBe("fix");
const templateRoute = await routePrTemplate({ branchName, config });
- expect(templateRoute.template).toBe('pr_bug.md');
+ expect(templateRoute.template).toBe("pr_bug.md");
const labelValidation = await validateAndApplyLabels({
labels,
@@ -76,15 +76,15 @@ describe('Category E: Real GitHub Workflows', () => {
expect(labelValidation.valid).toBe(true);
});
- test('Test E3: Documentation Update โ docs/ โ docs template โ minimal labels', async () => {
- const branchName = 'docs/branching-guide';
- const labels = ['type:docs'];
+ test("Test E3: Documentation Update โ docs/ โ docs template โ minimal labels", async () => {
+ const branchName = "docs/branching-guide";
+ const labels = ["type:docs"];
const branchValidation = await validateBranchName({ branchName, config });
expect(branchValidation.valid).toBe(true);
const templateRoute = await routePrTemplate({ branchName, config });
- expect(templateRoute.template).toBe('pr_docs.md');
+ expect(templateRoute.template).toBe("pr_docs.md");
const labelValidation = await validateAndApplyLabels({
labels,
@@ -95,48 +95,44 @@ describe('Category E: Real GitHub Workflows', () => {
expect(labelValidation.appliedLabels.length).toBe(1);
});
- test('Test E4: Chore/Dependency Update โ chore/ โ chore template โ meta labels', async () => {
- const branchName = 'chore/update-dependencies';
+ test("Test E4: Chore/Dependency Update โ chore/ โ chore template โ meta labels", async () => {
+ const branchName = "chore/update-dependencies";
const branchValidation = await validateBranchName({ branchName, config });
expect(branchValidation.valid).toBe(true);
const templateRoute = await routePrTemplate({ branchName, config });
- expect(templateRoute.template).toBe('pr_chore.md');
+ expect(templateRoute.template).toBe("pr_chore.md");
});
- test('Test E5: Security Patch โ security/ โ bug template โ security labels', async () => {
- const branchName = 'security/fix-xss-vulnerability';
+ test("Test E5: Security Patch โ security/ โ bug template โ security labels", async () => {
+ const branchName = "security/fix-xss-vulnerability";
const branchValidation = await validateBranchName({ branchName, config });
expect(branchValidation.valid).toBe(true);
const templateRoute = await routePrTemplate({ branchName, config });
- expect(templateRoute.template).toBe('pr_bug.md');
+ expect(templateRoute.template).toBe("pr_bug.md");
});
- test('Test E6: Multiple PRs Concurrent โ Isolated workflows', async () => {
- const branches = [
- 'feat/feature-1',
- 'feat/feature-2',
- 'fix/bug-1',
- ];
+ test("Test E6: Multiple PRs Concurrent โ Isolated workflows", async () => {
+ const branches = ["feat/feature-1", "feat/feature-2", "fix/bug-1"];
const results = await Promise.all(
- branches.map(branch =>
- validateBranchName({ branchName: branch, config })
- )
+ branches.map((branch) =>
+ validateBranchName({ branchName: branch, config }),
+ ),
);
expect(results).toHaveLength(3);
- results.forEach(result => {
+ results.forEach((result) => {
expect(result.valid).toBe(true);
});
});
- test('Test E7: PR with User-Selected Template โ Override routing logic', async () => {
- const branchName = 'feat/new-feature';
- const userSelectedTemplate = 'pr_custom.md';
+ test("Test E7: PR with User-Selected Template โ Override routing logic", async () => {
+ const branchName = "feat/new-feature";
+ const userSelectedTemplate = "pr_custom.md";
const result = await routePrTemplate({
branchName,
@@ -148,11 +144,11 @@ describe('Category E: Real GitHub Workflows', () => {
expect(result.userOverride).toBe(true);
});
- test('Test E8: PR with Custom Frontmatter โ Parse & apply FEEDBACK_RESPONSE', async () => {
+ test("Test E8: PR with Custom Frontmatter โ Parse & apply FEEDBACK_RESPONSE", async () => {
const prData = {
- owner: 'lightspeedwp',
- repo: '.github',
- title: 'Feature with feedback response',
+ owner: "lightspeedwp",
+ repo: ".github",
+ title: "Feature with feedback response",
body: `---
feedback_status: resolved
---
@@ -165,8 +161,8 @@ Test PR
- โ
Addressed AI suggestion 1
- ๐ Deferred AI suggestion 2`,
- head: 'feat/test',
- base: 'develop',
+ head: "feat/test",
+ base: "develop",
};
const result = await orchestratePrCreation({
@@ -180,15 +176,15 @@ Test PR
expect(result.frontmatter).toBeDefined();
});
- test('Test E9: GitHub Actions Triggered โ PR runs workflow validation', async () => {
+ test("Test E9: GitHub Actions Triggered โ PR runs workflow validation", async () => {
const prData = {
- owner: 'lightspeedwp',
- repo: '.github',
- title: 'Feature with workflow trigger',
- body: '## Description\n\nTest PR',
- head: 'feat/test',
- base: 'develop',
- labels: ['type:feature'],
+ owner: "lightspeedwp",
+ repo: ".github",
+ title: "Feature with workflow trigger",
+ body: "## Description\n\nTest PR",
+ head: "feat/test",
+ base: "develop",
+ labels: ["type:feature"],
};
const result = await orchestratePrCreation({
@@ -202,20 +198,20 @@ Test PR
expect(result.workflowRequested).toBe(true);
});
- test('Test E10: AI Feedback Integration โ Create FEEDBACK_RESPONSE.md if present', async () => {
+ test("Test E10: AI Feedback Integration โ Create FEEDBACK_RESPONSE.md if present", async () => {
const prData = {
- owner: 'lightspeedwp',
- repo: '.github',
- title: 'Feature with AI feedback',
- body: '## Description\n\nFeedback-driven PR',
- head: 'feat/test',
- base: 'develop',
- labels: ['type:feature'],
+ owner: "lightspeedwp",
+ repo: ".github",
+ title: "Feature with AI feedback",
+ body: "## Description\n\nFeedback-driven PR",
+ head: "feat/test",
+ base: "develop",
+ labels: ["type:feature"],
};
const aiFeedback = [
- { suggestion: 'Add more tests', status: 'addressed' },
- { suggestion: 'Improve documentation', status: 'deferred' },
+ { suggestion: "Add more tests", status: "addressed" },
+ { suggestion: "Improve documentation", status: "deferred" },
];
const result = await orchestratePrCreation({
@@ -227,6 +223,6 @@ Test PR
});
expect(result.success).toBe(true);
- expect(result.feedbackResponseRequested).toBe(true);
+ expect(result.feedbackResponseCreated).toBe(true);
});
});
diff --git a/agents/pr-creation-agent/__tests__/integration/sequential-skill-execution.test.js b/agents/pr-creation-agent/__tests__/integration/sequential-skill-execution.test.js
index f0af3f46f..d0b28ab1e 100644
--- a/agents/pr-creation-agent/__tests__/integration/sequential-skill-execution.test.js
+++ b/agents/pr-creation-agent/__tests__/integration/sequential-skill-execution.test.js
@@ -1,14 +1,14 @@
// Category A: Sequential Skill Execution (8 tests)
// Test skills in order as they execute in real workflows
-import { describe, test, expect, beforeEach } from '@jest/globals';
-import { validateBranchName } from '../../skills/validate-branch-name.js';
-import { routePrTemplate } from '../../skills/route-pr-template.js';
-import { validateAndApplyLabels } from '../../skills/validate-and-apply-labels.js';
-import { orchestratePrCreation } from '../../skills/orchestrate-pr-creation.js';
-import { MockGitHub, createMockConfig } from './setup.js';
-
-describe('Category A: Sequential Skill Execution', () => {
+import { describe, test, expect, beforeEach } from "@jest/globals";
+import { validateBranchName } from "../../skills/validate-branch-name.js";
+import { routePrTemplate } from "../../skills/route-pr-template.js";
+import { validateAndApplyLabels } from "../../skills/validate-and-apply-labels.js";
+import { orchestratePrCreation } from "../../skills/orchestrate-pr-creation.js";
+import { MockGitHub, createMockConfig } from "./setup.js";
+
+describe("Category A: Sequential Skill Execution", () => {
let mockGitHub;
let config;
@@ -17,8 +17,8 @@ describe('Category A: Sequential Skill Execution', () => {
config = createMockConfig();
});
- test('Test A1: Branch Validation Pass โ Template Route โ Label Validate โ PR Created', async () => {
- const branchName = 'feat/pr-creation-agent-integration';
+ test("Test A1: Branch Validation Pass โ Template Route โ Label Validate โ PR Created", async () => {
+ const branchName = "feat/pr-creation-agent-integration";
// Step 1: Validate branch
const branchValidation = await validateBranchName({
@@ -26,18 +26,18 @@ describe('Category A: Sequential Skill Execution', () => {
config,
});
expect(branchValidation.valid).toBe(true);
- expect(branchValidation.type).toBe('feat');
+ expect(branchValidation.type).toBe("feat");
// Step 2: Route to template
const templateRoute = await routePrTemplate({
branchName,
config,
});
- expect(templateRoute.template).toBe('pr_feature.md');
+ expect(templateRoute.template).toBe("pr_feature.md");
expect(templateRoute.routed).toBe(true);
// Step 3: Validate labels
- const labels = ['type:feature'];
+ const labels = ["type:feature"];
const labelValidation = await validateAndApplyLabels({
labels,
config,
@@ -47,12 +47,12 @@ describe('Category A: Sequential Skill Execution', () => {
// Step 4: Orchestrate PR creation
const prData = {
- owner: 'lightspeedwp',
- repo: '.github',
- title: 'Test PR',
- body: '## Description\n\nTest',
+ owner: "lightspeedwp",
+ repo: ".github",
+ title: "Test PR",
+ body: "## Description\n\nTest",
head: branchName,
- base: 'develop',
+ base: "develop",
labels,
};
@@ -64,8 +64,8 @@ describe('Category A: Sequential Skill Execution', () => {
expect(prResult.success).toBe(true);
});
- test('Test A2: Branch Validation Fail โ Error propagated', async () => {
- const branchName = 'claude/invalid-prefix';
+ test("Test A2: Branch Validation Fail โ Error propagated", async () => {
+ const branchName = "claude/invalid-prefix";
const result = await validateBranchName({
branchName,
@@ -73,11 +73,11 @@ describe('Category A: Sequential Skill Execution', () => {
});
expect(result.valid).toBe(false);
- expect(result.errors).toContain('branch-prefix-forbidden');
+ expect(result.errors).toContain("branch-prefix-forbidden");
});
- test('Test A3: Template Route Fail โ Fallback to default template', async () => {
- const branchName = 'unknown/branch-type';
+ test("Test A3: Template Route Fail โ Fallback to default template", async () => {
+ const branchName = "unknown/branch-type";
const result = await routePrTemplate({
branchName,
@@ -86,11 +86,11 @@ describe('Category A: Sequential Skill Execution', () => {
expect(result.routed).toBe(false);
expect(result.fallback).toBe(true);
- expect(result.template).toBe('pull_request_template.md');
+ expect(result.template).toBe("pull_request_template.md");
});
- test('Test A4: Label Validation Fail โ Error logged, PR still created', async () => {
- const invalidLabels = ['bug']; // missing prefix
+ test("Test A4: Label Validation Fail โ Error logged, PR still created", async () => {
+ const invalidLabels = ["bug"]; // missing prefix
const labelValidation = await validateAndApplyLabels({
labels: invalidLabels,
@@ -99,11 +99,11 @@ describe('Category A: Sequential Skill Execution', () => {
});
expect(labelValidation.valid).toBe(false);
- expect(labelValidation.errors).toContain('non-canonical-label');
+ expect(labelValidation.errors).toContain("non-canonical-label");
});
- test('Test A5: Invalid Branch Type โ Rejected before template routing', async () => {
- const branchName = 'my-branch';
+ test("Test A5: Invalid Branch Type โ Rejected before template routing", async () => {
+ const branchName = "my-branch";
const branchValidation = await validateBranchName({
branchName,
@@ -111,13 +111,13 @@ describe('Category A: Sequential Skill Execution', () => {
});
expect(branchValidation.valid).toBe(false);
- expect(branchValidation.errors).toContain('branch-prefix-missing');
+ expect(branchValidation.errors).toContain("branch-prefix-missing");
// Template routing should not be attempted
});
- test('Test A6: Mixed Label Scenarios โ Multiple labels applied correctly', async () => {
- const labels = ['type:feature', 'area:agents'];
+ test("Test A6: Mixed Label Scenarios โ Multiple labels applied correctly", async () => {
+ const labels = ["type:feature", "area:agents"];
const result = await validateAndApplyLabels({
labels,
@@ -129,9 +129,9 @@ describe('Category A: Sequential Skill Execution', () => {
expect(result.appliedLabels).toEqual(labels);
});
- test('Test A7: PR Template Override โ User-selected template respected', async () => {
- const branchName = 'feat/test-feature';
- const userSelectedTemplate = 'pr_custom.md';
+ test("Test A7: PR Template Override โ User-selected template respected", async () => {
+ const branchName = "feat/test-feature";
+ const userSelectedTemplate = "pr_custom.md";
// User explicitly selects a template, overriding route logic
const result = await routePrTemplate({
@@ -144,9 +144,9 @@ describe('Category A: Sequential Skill Execution', () => {
expect(result.userOverride).toBe(true);
});
- test('Test A8: Complete Feature Workflow โ feat/ branch full pipeline', async () => {
- const branchName = 'feat/new-feature';
- const labels = ['type:feature'];
+ test("Test A8: Complete Feature Workflow โ feat/ branch full pipeline", async () => {
+ const branchName = "feat/new-feature";
+ const labels = ["type:feature"];
// Full workflow validation
const branchValidation = await validateBranchName({
diff --git a/agents/pr-creation-agent/__tests__/integration/setup.js b/agents/pr-creation-agent/__tests__/integration/setup.js
index 79c200322..25475ffcf 100644
--- a/agents/pr-creation-agent/__tests__/integration/setup.js
+++ b/agents/pr-creation-agent/__tests__/integration/setup.js
@@ -26,14 +26,14 @@ export class MockGitHub {
return {
name: branch,
commit: {
- sha: 'abcd1234',
+ sha: "abcd1234",
url: `https://api.github.com/repos/${owner}/${repo}/commits/abcd1234`,
},
protected: false,
};
},
- getProtectedBranch: async ({ owner, repo, branch }) => {
+ getProtectedBranch: async ({ _owner, _repo, branch }) => {
return {
name: branch,
protection: { enabled: false },
@@ -46,10 +46,12 @@ export class MockGitHub {
throw new Error(this.options.templateError);
}
return {
- name: path.split('/').pop(),
+ name: path.split("/").pop(),
path,
size: 1024,
- content: Buffer.from('# PR Template\n\n## Description\n\nTemplate content').toString('base64'),
+ content: Buffer.from(
+ "# PR Template\n\n## Description\n\nTemplate content",
+ ).toString("base64"),
};
},
@@ -71,23 +73,23 @@ export class MockGitHub {
}
return {
url: `https://api.github.com/repos/${owner}/${repo}/issues/${issue_number}`,
- labels: labels.map(name => ({ name, color: '0366d6' })),
+ labels: labels.map((name) => ({ name, color: "0366d6" })),
};
},
- listLabels: async ({ owner, repo }) => {
+ listLabels: async ({ _owner, _repo }) => {
return [
- { name: 'type:feature', color: '0366d6' },
- { name: 'type:bug', color: 'fc2929' },
- { name: 'type:docs', color: '0075ca' },
- { name: 'area:agents', color: 'd4c5f9' },
- { name: 'priority:critical', color: 'ee0701' },
- { name: 'meta:no-changelog', color: 'cccccc' },
+ { name: "type:feature", color: "0366d6" },
+ { name: "type:bug", color: "fc2929" },
+ { name: "type:docs", color: "0075ca" },
+ { name: "area:agents", color: "d4c5f9" },
+ { name: "priority:critical", color: "ee0701" },
+ { name: "meta:no-changelog", color: "cccccc" },
];
},
- getLabel: async ({ owner, repo, name }) => {
- return { name, color: '0366d6' };
+ getLabel: async ({ _owner, _repo, name }) => {
+ return { name, color: "0366d6" };
},
};
@@ -105,16 +107,16 @@ export class MockGitHub {
body,
head: { ref: head },
base: { ref: base },
- state: 'open',
+ state: "open",
url: `https://github.com/${owner}/${repo}/pull/123`,
};
},
- get: async ({ owner, repo, pull_number }) => {
+ get: async ({ _owner, _repo, pull_number }) => {
return {
number: pull_number,
- title: 'Test PR',
- state: 'open',
+ title: "Test PR",
+ state: "open",
};
},
@@ -126,7 +128,7 @@ export class MockGitHub {
// Helper to reset calls
resetCalls() {
- Object.keys(this.calls).forEach(key => {
+ Object.keys(this.calls).forEach((key) => {
this.calls[key] = [];
});
}
@@ -140,23 +142,32 @@ export class MockGitHub {
// Mock config for tests
export const createMockConfig = (overrides = {}) => {
return {
- allowed_types: ['feat', 'fix', 'docs', 'chore', 'test', 'refactor', 'hotfix', 'security'],
+ allowed_types: [
+ "feat",
+ "fix",
+ "docs",
+ "chore",
+ "test",
+ "refactor",
+ "hotfix",
+ "security",
+ ],
template_routing: {
- 'feat/': 'pr_feature.md',
- 'fix/': 'pr_bug.md',
- 'docs/': 'pr_docs.md',
- 'chore/': 'pr_chore.md',
- 'test/': 'pr_chore.md',
- 'refactor/': 'pr_refactor.md',
- 'hotfix/': 'pr_hotfix.md',
- 'security/': 'pr_bug.md',
+ "feat/": "pr_feature.md",
+ "fix/": "pr_bug.md",
+ "docs/": "pr_docs.md",
+ "chore/": "pr_chore.md",
+ "test/": "pr_chore.md",
+ "refactor/": "pr_refactor.md",
+ "hotfix/": "pr_hotfix.md",
+ "security/": "pr_bug.md",
},
canonical_labels: [
- 'type:feature',
- 'type:bug',
- 'type:docs',
- 'area:agents',
- 'priority:critical',
+ "type:feature",
+ "type:bug",
+ "type:docs",
+ "area:agents",
+ "priority:critical",
],
...overrides,
};
@@ -165,44 +176,44 @@ export const createMockConfig = (overrides = {}) => {
// Test data fixtures
export const testFixtures = {
validBranches: [
- { name: 'feat/pr-creation-agent', type: 'feat' },
- { name: 'fix/invalid-branch-validation', type: 'fix' },
- { name: 'docs/branching-strategy', type: 'docs' },
- { name: 'hotfix/critical-security', type: 'hotfix' },
- { name: 'chore/dependency-update', type: 'chore' },
+ { name: "feat/pr-creation-agent", type: "feat" },
+ { name: "fix/invalid-branch-validation", type: "fix" },
+ { name: "docs/branching-strategy", type: "docs" },
+ { name: "hotfix/critical-security", type: "hotfix" },
+ { name: "chore/dependency-update", type: "chore" },
],
invalidBranches: [
- { name: 'claude/invalid-prefix', error: 'branch-prefix-forbidden' },
- { name: 'feature/hyphen-issue', error: 'branch-type-invalid' },
- { name: 'my-branch', error: 'branch-prefix-missing' },
+ { name: "claude/invalid-prefix", error: "branch-prefix-forbidden" },
+ { name: "feature/hyphen-issue", error: "branch-type-invalid" },
+ { name: "my-branch", error: "branch-prefix-missing" },
],
validLabels: [
- ['type:feature'],
- ['type:bug'],
- ['type:feature', 'area:agents'],
- ['type:bug', 'priority:critical'],
+ ["type:feature"],
+ ["type:bug"],
+ ["type:feature", "area:agents"],
+ ["type:bug", "priority:critical"],
],
invalidLabels: [
- ['bug'], // missing prefix
- ['type:feature', 'feature'], // mixed valid/invalid
+ ["bug"], // missing prefix
+ ["type:feature", "feature"], // mixed valid/invalid
],
templateCases: [
- { branch: 'feat/new-feature', expectedTemplate: 'pr_feature.md' },
- { branch: 'fix/bug-fix', expectedTemplate: 'pr_bug.md' },
- { branch: 'docs/update-readme', expectedTemplate: 'pr_docs.md' },
- { branch: 'hotfix/critical', expectedTemplate: 'pr_hotfix.md' },
+ { branch: "feat/new-feature", expectedTemplate: "pr_feature.md" },
+ { branch: "fix/bug-fix", expectedTemplate: "pr_bug.md" },
+ { branch: "docs/update-readme", expectedTemplate: "pr_docs.md" },
+ { branch: "hotfix/critical", expectedTemplate: "pr_hotfix.md" },
],
prData: {
- owner: 'lightspeedwp',
- repo: '.github',
- title: 'Test PR Title',
- body: '## Description\n\nTest PR description',
- head: 'feat/test-branch',
- base: 'develop',
+ owner: "lightspeedwp",
+ repo: ".github",
+ title: "Test PR Title",
+ body: "## Description\n\nTest PR description",
+ head: "feat/test-branch",
+ base: "develop",
},
};
diff --git a/agents/pr-creation-agent/__tests__/integration/template-routing-scenarios.test.js b/agents/pr-creation-agent/__tests__/integration/template-routing-scenarios.test.js
index 930a57930..a453e2de7 100644
--- a/agents/pr-creation-agent/__tests__/integration/template-routing-scenarios.test.js
+++ b/agents/pr-creation-agent/__tests__/integration/template-routing-scenarios.test.js
@@ -1,21 +1,19 @@
// Category C: Template Routing Scenarios (8 tests)
// Test PR template selection for all branch types
-import { describe, test, expect, beforeEach } from '@jest/globals';
-import { routePrTemplate } from '../../skills/route-pr-template.js';
-import { MockGitHub, createMockConfig } from './setup.js';
+import { describe, test, expect, beforeEach } from "@jest/globals";
+import { routePrTemplate } from "../../skills/route-pr-template.js";
+import { createMockConfig } from "./setup.js";
-describe('Category C: Template Routing Scenarios', () => {
- let mockGitHub;
+describe("Category C: Template Routing Scenarios", () => {
let config;
beforeEach(() => {
- mockGitHub = new MockGitHub();
config = createMockConfig();
});
- test('Test C1: feat/ branch โ pr_feature.md template', async () => {
- const branchName = 'feat/new-feature';
+ test("Test C1: feat/ branch โ pr_feature.md template", async () => {
+ const branchName = "feat/new-feature";
const result = await routePrTemplate({
branchName,
@@ -23,12 +21,12 @@ describe('Category C: Template Routing Scenarios', () => {
});
expect(result.routed).toBe(true);
- expect(result.template).toBe('pr_feature.md');
- expect(result.reason).toBe('feat-type-matched');
+ expect(result.template).toBe("pr_feature.md");
+ expect(result.reason).toBe("feat-type-matched");
});
- test('Test C2: fix/ branch โ pr_bug.md template', async () => {
- const branchName = 'fix/bug-fix';
+ test("Test C2: fix/ branch โ pr_bug.md template", async () => {
+ const branchName = "fix/bug-fix";
const result = await routePrTemplate({
branchName,
@@ -36,12 +34,12 @@ describe('Category C: Template Routing Scenarios', () => {
});
expect(result.routed).toBe(true);
- expect(result.template).toBe('pr_bug.md');
- expect(result.reason).toBe('fix-type-matched');
+ expect(result.template).toBe("pr_bug.md");
+ expect(result.reason).toBe("fix-type-matched");
});
- test('Test C3: hotfix/ branch โ pr_hotfix.md template', async () => {
- const branchName = 'hotfix/critical-security';
+ test("Test C3: hotfix/ branch โ pr_hotfix.md template", async () => {
+ const branchName = "hotfix/critical-security";
const result = await routePrTemplate({
branchName,
@@ -49,12 +47,12 @@ describe('Category C: Template Routing Scenarios', () => {
});
expect(result.routed).toBe(true);
- expect(result.template).toBe('pr_hotfix.md');
- expect(result.reason).toBe('hotfix-type-matched');
+ expect(result.template).toBe("pr_hotfix.md");
+ expect(result.reason).toBe("hotfix-type-matched");
});
- test('Test C4: docs/ branch โ pr_docs.md template', async () => {
- const branchName = 'docs/branching-strategy';
+ test("Test C4: docs/ branch โ pr_docs.md template", async () => {
+ const branchName = "docs/branching-strategy";
const result = await routePrTemplate({
branchName,
@@ -62,12 +60,12 @@ describe('Category C: Template Routing Scenarios', () => {
});
expect(result.routed).toBe(true);
- expect(result.template).toBe('pr_docs.md');
- expect(result.reason).toBe('docs-type-matched');
+ expect(result.template).toBe("pr_docs.md");
+ expect(result.reason).toBe("docs-type-matched");
});
- test('Test C5: chore/ branch โ pr_chore.md template', async () => {
- const branchName = 'chore/dependency-update';
+ test("Test C5: chore/ branch โ pr_chore.md template", async () => {
+ const branchName = "chore/dependency-update";
const result = await routePrTemplate({
branchName,
@@ -75,12 +73,12 @@ describe('Category C: Template Routing Scenarios', () => {
});
expect(result.routed).toBe(true);
- expect(result.template).toBe('pr_chore.md');
- expect(result.reason).toBe('chore-type-matched');
+ expect(result.template).toBe("pr_chore.md");
+ expect(result.reason).toBe("chore-type-matched");
});
- test('Test C6: test/ branch โ pr_chore.md template', async () => {
- const branchName = 'test/add-unit-tests';
+ test("Test C6: test/ branch โ pr_chore.md template", async () => {
+ const branchName = "test/add-unit-tests";
const result = await routePrTemplate({
branchName,
@@ -88,12 +86,12 @@ describe('Category C: Template Routing Scenarios', () => {
});
expect(result.routed).toBe(true);
- expect(result.template).toBe('pr_chore.md');
- expect(result.reason).toBe('test-type-matched');
+ expect(result.template).toBe("pr_chore.md");
+ expect(result.reason).toBe("test-type-matched");
});
- test('Test C7: refactor/ branch โ pr_refactor.md template', async () => {
- const branchName = 'refactor/simplify-validation';
+ test("Test C7: refactor/ branch โ pr_refactor.md template", async () => {
+ const branchName = "refactor/simplify-validation";
const result = await routePrTemplate({
branchName,
@@ -101,12 +99,12 @@ describe('Category C: Template Routing Scenarios', () => {
});
expect(result.routed).toBe(true);
- expect(result.template).toBe('pr_refactor.md');
- expect(result.reason).toBe('refactor-type-matched');
+ expect(result.template).toBe("pr_refactor.md");
+ expect(result.reason).toBe("refactor-type-matched");
});
- test('Test C8: Unknown branch type โ Default template with warning', async () => {
- const branchName = 'unknown/branch-type';
+ test("Test C8: Unknown branch type โ Default template with warning", async () => {
+ const branchName = "unknown/branch-type";
const result = await routePrTemplate({
branchName,
@@ -115,7 +113,7 @@ describe('Category C: Template Routing Scenarios', () => {
expect(result.routed).toBe(false);
expect(result.fallback).toBe(true);
- expect(result.template).toBe('pull_request_template.md');
+ expect(result.template).toBe("pull_request_template.md");
expect(result.warning).toBeDefined();
});
});
diff --git a/agents/pr-creation-agent/__tests__/orchestrate-pr-creation.test.js b/agents/pr-creation-agent/__tests__/orchestrate-pr-creation.test.js
index 6b4e448d1..bf4d93b5d 100644
--- a/agents/pr-creation-agent/__tests__/orchestrate-pr-creation.test.js
+++ b/agents/pr-creation-agent/__tests__/orchestrate-pr-creation.test.js
@@ -1,519 +1,167 @@
-import { jest } from "@jest/globals";
import { orchestratePrCreation } from "../skills/orchestrate-pr-creation.js";
describe("orchestratePrCreation", () => {
- beforeEach(() => {
- jest.clearAllMocks();
- });
+ const validPr = {
+ owner: "lightspeedwp",
+ repo: ".github",
+ title: "Add user authentication",
+ body: "## Description\n\nImplement OAuth2 authentication.",
+ head: "feat/user-auth",
+ base: "develop",
+ labels: ["type:feature"],
+ };
describe("Input Validation", () => {
- test("should return error for missing branchName", async () => {
- const result = await orchestratePrCreation({
- branchType: "feat",
- templateFile: "pr_feature.md",
- });
-
- expect(result.valid).toBe(false);
- expect(result.error).toContain("Branch name is required");
- expect(result.pr).toBeNull();
- });
-
- test("should return error for non-string branchName", async () => {
- const result = await orchestratePrCreation({
- branchName: 123,
- branchType: "feat",
- templateFile: "pr_feature.md",
- });
-
- expect(result.valid).toBe(false);
- expect(result.error).toContain("Branch name is required");
- });
-
- test("should return error for missing branchType", async () => {
- const result = await orchestratePrCreation({
- branchName: "feat/user-auth-system",
- templateFile: "pr_feature.md",
- });
-
- expect(result.valid).toBe(false);
- expect(result.error).toContain("Branch type is required");
- expect(result.pr).toBeNull();
- });
-
- test("should return error for missing templateFile", async () => {
- const result = await orchestratePrCreation({
- branchName: "feat/user-auth-system",
- branchType: "feat",
- });
-
- expect(result.valid).toBe(false);
- expect(result.error).toContain("Template file is required");
- expect(result.pr).toBeNull();
- });
- });
-
- describe("PR Title Generation", () => {
- test("should generate feat PR title", async () => {
- const result = await orchestratePrCreation({
- branchName: "feat/user-auth-system",
- branchType: "feat",
- templateFile: "pr_feature.md",
- });
-
- expect(result.valid).toBe(true);
- expect(result.title).toBe("feat: User โ Implementation");
- });
-
- test("should generate fix PR title", async () => {
- const result = await orchestratePrCreation({
- branchName: "fix/validation-bug",
- branchType: "fix",
- templateFile: "pr_bug.md",
- });
-
- expect(result.valid).toBe(true);
- expect(result.title).toBe("fix: Validation โ Issue Resolution");
- });
-
- test("should generate docs PR title", async () => {
- const result = await orchestratePrCreation({
- branchName: "docs/api-reference",
- branchType: "docs",
- templateFile: "pr_docs.md",
- });
+ test("should return error for missing PR object", async () => {
+ const result = await orchestratePrCreation({});
- expect(result.valid).toBe(true);
- expect(result.title).toBe("docs: Api โ Documentation Update");
+ expect(result.success).toBe(false);
+ expect(result.error).toContain("PR data is required");
});
- test("should generate hotfix PR title", async () => {
- const result = await orchestratePrCreation({
- branchName: "hotfix/security-patch",
- branchType: "hotfix",
- templateFile: "pr_hotfix.md",
- });
-
- expect(result.valid).toBe(true);
- expect(result.title).toBe("hotfix: Security โ Critical Fix");
- });
-
- test("should generate refactor PR title", async () => {
- const result = await orchestratePrCreation({
- branchName: "refactor/auth-module",
- branchType: "refactor",
- templateFile: "pr_refactor.md",
- });
-
- expect(result.valid).toBe(true);
- expect(result.title).toBe("refactor: Auth โ Code Cleanup");
- });
-
- test("should generate perf PR title", async () => {
- const result = await orchestratePrCreation({
- branchName: "perf/api-caching",
- branchType: "perf",
- templateFile: "pr_feature.md",
- });
-
- expect(result.valid).toBe(true);
- expect(result.title).toBe("perf: Api โ Performance Optimization");
- });
-
- test("should handle multi-word scope in title", async () => {
- const result = await orchestratePrCreation({
- branchName: "feat/user-profile-management",
- branchType: "feat",
- templateFile: "pr_feature.md",
- });
-
- expect(result.valid).toBe(true);
- expect(result.title).toBe("feat: User โ Implementation");
- });
- });
-
- describe("PR Body Generation", () => {
- test("should include template content in body", async () => {
- const templateContent = "# Feature\n\nThis is a feature implementation";
- const result = await orchestratePrCreation({
- branchName: "feat/user-auth",
- branchType: "feat",
- templateFile: "pr_feature.md",
- templateContent,
- });
-
- expect(result.valid).toBe(true);
- expect(result.pr.body).toContain("This is a feature implementation");
- });
-
- test("should append labels to body", async () => {
- const labels = ["type:feature", "area:auth"];
- const result = await orchestratePrCreation({
- branchName: "feat/user-auth",
- branchType: "feat",
- templateFile: "pr_feature.md",
- templateContent: "## Description\n\nTest description",
- appliedLabels: labels,
- });
-
- expect(result.valid).toBe(true);
- expect(result.pr.body).toContain("## Labels");
- expect(result.pr.body).toContain("type:feature");
- expect(result.pr.body).toContain("area:auth");
- });
-
- test("should include template metadata in body", async () => {
- const templateMetadata = {
- templateFile: "pr_feature.md",
- complete: true,
- missingSections: [],
+ test("should return error for missing required PR fields", async () => {
+ const incompletePr = {
+ owner: "lightspeedwp",
+ repo: ".github",
+ title: "Add user authentication",
+ // missing body, head, base
};
- const result = await orchestratePrCreation({
- branchName: "feat/user-auth",
- branchType: "feat",
- templateFile: "pr_feature.md",
- templateContent: "## Description\n\nTest",
- templateMetadata,
- });
-
- expect(result.valid).toBe(true);
- expect(result.pr.body).toContain("## Template Metadata");
- expect(result.pr.body).toContain("pr_feature.md");
- expect(result.pr.body).toContain("Complete: Yes");
- });
- test("should build minimal body when no template content provided", async () => {
- const result = await orchestratePrCreation({
- branchName: "feat/user-auth",
- branchType: "feat",
- templateFile: "pr_feature.md",
- });
+ const result = await orchestratePrCreation({ pr: incompletePr });
- expect(result.valid).toBe(true);
- expect(result.pr.body).toContain("## Summary");
- expect(result.pr.body).toContain("## Changes");
+ expect(result.success).toBe(false);
+ expect(result.error).toContain("missing required fields");
});
- test("should include missing sections in minimal body", async () => {
- const templateMetadata = {
- templateFile: "pr_feature.md",
- complete: false,
- missingSections: ["Changelog", "Checklist"],
- };
- const result = await orchestratePrCreation({
- branchName: "feat/user-auth",
- branchType: "feat",
- templateFile: "pr_feature.md",
- templateMetadata,
- });
-
- expect(result.valid).toBe(true);
- expect(result.pr.body).toContain("## Missing Template Sections");
- expect(result.pr.body).toContain("Changelog");
- expect(result.pr.body).toContain("Checklist");
- });
- });
+ test("should accept PR with all required fields", async () => {
+ const result = await orchestratePrCreation({ pr: validPr });
- describe("PR Object Structure", () => {
- test("should return valid PR object", async () => {
- const result = await orchestratePrCreation({
- branchName: "feat/user-auth",
- branchType: "feat",
- templateFile: "pr_feature.md",
- appliedLabels: ["type:feature"],
- prContext: { baseBranch: "develop" },
- });
-
- expect(result.valid).toBe(true);
+ expect(result.success).toBe(true);
expect(result.pr).toBeDefined();
- expect(result.pr.title).toBeDefined();
- expect(result.pr.body).toBeDefined();
- expect(result.pr.head).toBe("feat/user-auth");
- expect(result.pr.base).toBe("develop");
- expect(result.pr.labels).toContain("type:feature");
- expect(result.pr.draft).toBe(false);
- });
-
- test("should include metadata in PR object", async () => {
- const result = await orchestratePrCreation({
- branchName: "feat/user-auth",
- branchType: "feat",
- templateFile: "pr_feature.md",
- });
-
- expect(result.valid).toBe(true);
- expect(result.pr.metadata).toBeDefined();
- expect(result.pr.metadata.branchType).toBe("feat");
- expect(result.pr.metadata.scope).toBe("user");
- expect(result.pr.metadata.templateFile).toBe("pr_feature.md");
- expect(result.pr.metadata.generatedAt).toBeDefined();
- });
-
- test("should use develop as default base branch", async () => {
- const result = await orchestratePrCreation({
- branchName: "feat/user-auth",
- branchType: "feat",
- templateFile: "pr_feature.md",
- });
-
- expect(result.valid).toBe(true);
- expect(result.pr.base).toBe("develop");
- });
-
- test("should use provided base branch from prContext", async () => {
- const result = await orchestratePrCreation({
- branchName: "hotfix/security-fix",
- branchType: "hotfix",
- templateFile: "pr_hotfix.md",
- prContext: { baseBranch: "main" },
- });
-
- expect(result.valid).toBe(true);
- expect(result.pr.base).toBe("main");
+ expect(result.pr.title).toBe(validPr.title);
+ expect(result.pr.head).toBe(validPr.head);
});
});
- describe("PR Readiness Validation", () => {
- test("should flag empty title as invalid", async () => {
- const result = await orchestratePrCreation({
- branchName: "",
- branchType: "feat",
- templateFile: "pr_feature.md",
- });
-
- expect(result.valid).toBe(false);
- });
-
- test("should warn about long title", async () => {
- const result = await orchestratePrCreation({
- branchName:
- "feat/verylongnameprettysurethisisgoingtobeamaziinglytoolongofatitle-exce",
- branchType: "feat",
- templateFile: "pr_feature.md",
- templateContent: "Description",
- });
-
- // Long title warning is generated if title exceeds 120 chars
- // Title format: "{type}: {Scope} โ {Action}" (e.g., "feat: Verylongnameprettysurethisisgoingtobeamaziinglytoolongofatitle โ Implementation")
- expect(result.valid).toBe(true);
- if (result.readinessScore < 0.95) {
- // Long title should reduce readiness score
- expect(result.readinessScore).toBeLessThan(1.0);
- }
- });
+ describe("Optional Parameters", () => {
+ test("should handle parseFrontmatter option", async () => {
+ const prWithFrontmatter = {
+ ...validPr,
+ body: `---
+feedback_status: resolved
+---
- test("should warn about short body", async () => {
- const result = await orchestratePrCreation({
- branchName: "feat/user-auth",
- branchType: "feat",
- templateFile: "pr_feature.md",
- templateContent: "Short",
- });
+## Description
- expect(result.valid).toBe(true);
- expect(result.warnings).toBeDefined();
- expect(result.warnings.some((w) => w.includes("short"))).toBe(true);
- });
-
- test("should warn about incomplete template", async () => {
- const templateMetadata = {
- templateFile: "pr_feature.md",
- complete: false,
- missingSections: ["Changelog", "Checklist"],
+Test PR`,
};
- const result = await orchestratePrCreation({
- branchName: "feat/user-auth",
- branchType: "feat",
- templateFile: "pr_feature.md",
- templateMetadata,
- templateContent: "## Description\n\nThis is a valid description.",
- });
- expect(result.valid).toBe(true);
- expect(result.warnings).toBeDefined();
- expect(result.warnings.some((w) => w.includes("incomplete"))).toBe(true);
- });
-
- test("should warn about missing labels", async () => {
const result = await orchestratePrCreation({
- branchName: "feat/user-auth",
- branchType: "feat",
- templateFile: "pr_feature.md",
- templateContent: "## Description\n\nThis is a valid description.",
- appliedLabels: [],
+ pr: prWithFrontmatter,
+ parseFrontmatter: true,
});
- expect(result.valid).toBe(true);
- expect(result.warnings).toBeDefined();
- expect(result.warnings.some((w) => w.includes("No labels"))).toBe(true);
+ expect(result.success).toBe(true);
+ expect(result.frontmatter).toBeDefined();
+ expect(result.frontmatter.feedback_status).toBe("resolved");
});
- });
- describe("Readiness Score", () => {
- test("should calculate perfect readiness score", async () => {
+ test("should handle triggerWorkflow option", async () => {
const result = await orchestratePrCreation({
- branchName: "feat/user-auth-system",
- branchType: "feat",
- templateFile: "pr_feature.md",
- templateContent: "This is a comprehensive PR description.",
- appliedLabels: ["type:feature", "area:auth"],
- templateMetadata: {
- templateFile: "pr_feature.md",
- complete: true,
- missingSections: [],
- },
+ pr: validPr,
+ triggerWorkflow: true,
});
- expect(result.valid).toBe(true);
- expect(result.readinessScore).toBeGreaterThan(0.8);
- expect(result.readinessScore).toBeLessThanOrEqual(1.0);
+ expect(result.success).toBe(true);
+ expect(result.workflowRequested).toBe(true);
});
- test("should calculate lower readiness score for incomplete data", async () => {
+ test("should handle createFeedbackResponse with aiFeedback", async () => {
+ const aiFeedback = [
+ { suggestion: "Add tests", status: "addressed" },
+ { suggestion: "Improve docs", status: "deferred" },
+ ];
+
const result = await orchestratePrCreation({
- branchName: "feat/auth",
- branchType: "feat",
- templateFile: "pr_feature.md",
- templateContent: "Short",
- appliedLabels: [],
+ pr: validPr,
+ aiFeedback,
+ createFeedbackResponse: true,
});
- expect(result.valid).toBe(true);
- expect(result.readinessScore).toBeLessThan(0.8);
+ expect(result.success).toBe(true);
+ expect(result.feedbackResponseCreated).toBe(true);
});
- test("should keep readiness score between 0 and 1", async () => {
+ test("should not create feedback response without aiFeedback", async () => {
const result = await orchestratePrCreation({
- branchName: "feat/user-auth",
- branchType: "feat",
- templateFile: "pr_feature.md",
+ pr: validPr,
+ createFeedbackResponse: true,
});
- expect(result.valid).toBe(true);
- expect(result.readinessScore).toBeGreaterThanOrEqual(0);
- expect(result.readinessScore).toBeLessThanOrEqual(1);
+ expect(result.success).toBe(true);
+ expect(result.feedbackResponseCreated).toBe(false);
});
});
describe("Edge Cases", () => {
- test("should handle scope extraction from simple branch name", async () => {
- const result = await orchestratePrCreation({
- branchName: "feat/auth-module",
- branchType: "feat",
- templateFile: "pr_feature.md",
- });
-
- expect(result.valid).toBe(true);
- expect(result.pr.metadata.scope).toBe("auth");
- });
+ test("should handle PR with empty labels array", async () => {
+ const prNoLabels = { ...validPr, labels: [] };
- test("should handle scope extraction from complex branch name", async () => {
- const result = await orchestratePrCreation({
- branchName: "feat/user-profile-management-system",
- branchType: "feat",
- templateFile: "pr_feature.md",
- });
+ const result = await orchestratePrCreation({ pr: prNoLabels });
- expect(result.valid).toBe(true);
- expect(result.pr.metadata.scope).toBe("user");
+ expect(result.success).toBe(true);
+ expect(result.pr.labels).toEqual([]);
});
- test("should handle null appliedLabels", async () => {
- const result = await orchestratePrCreation({
- branchName: "feat/user-auth",
- branchType: "feat",
- templateFile: "pr_feature.md",
- appliedLabels: null,
- });
+ test("should handle PR without optional labels field", async () => {
+ const { labels, ...prWithoutLabels } = validPr;
- expect(result.valid).toBe(true);
- expect(result.pr.labels).toBeDefined();
- });
+ const result = await orchestratePrCreation({ pr: prWithoutLabels });
- test("should handle undefined prContext", async () => {
- const result = await orchestratePrCreation({
- branchName: "feat/user-auth",
- branchType: "feat",
- templateFile: "pr_feature.md",
- prContext: undefined,
- });
-
- expect(result.valid).toBe(true);
- expect(result.pr.base).toBe("develop");
+ expect(result.success).toBe(false);
+ expect(result.error).toContain("missing required fields");
});
- test("should handle empty template content", async () => {
+ test("should handle frontmatter without frontmatter marker", async () => {
+ const prNoFrontmatter = {
+ ...validPr,
+ body: "## Description\n\nNo frontmatter here",
+ };
+
const result = await orchestratePrCreation({
- branchName: "feat/user-auth",
- branchType: "feat",
- templateFile: "pr_feature.md",
- templateContent: "",
+ pr: prNoFrontmatter,
+ parseFrontmatter: true,
});
- expect(result.valid).toBe(true);
- expect(result.pr.body).toContain("## Summary");
+ expect(result.success).toBe(true);
+ expect(result.frontmatter).toBeNull();
});
test("should handle error gracefully", async () => {
- const result = await orchestratePrCreation({
- branchName: "feat/user-auth",
- branchType: "feat",
- templateFile: "pr_feature.md",
- templateMetadata: {
- // Invalid metadata structure that might cause error
- get complete() {
- throw new Error("Test error");
- },
- },
- });
+ const result = await orchestratePrCreation(null);
- expect(result.valid).toBe(false);
- expect(result.error).toContain("Error orchestrating");
+ expect(result.success).toBe(false);
+ expect(result.error).toBeDefined();
});
});
- describe("Integration", () => {
- test("should orchestrate complete PR with all inputs", async () => {
- const result = await orchestratePrCreation({
- branchName: "feat/user-authentication-system",
- branchType: "feat",
- templateFile: "pr_feature.md",
- templateContent: "## Summary\n\nImplements user authentication.",
- templateMetadata: {
- templateFile: "pr_feature.md",
- complete: true,
- missingSections: [],
- },
- appliedLabels: ["type:feature", "area:security", "priority:important"],
- prContext: { baseBranch: "develop", owner: "lightspeedwp" },
- });
+ describe("Response Structure", () => {
+ test("should return consistent response structure on success", async () => {
+ const result = await orchestratePrCreation({ pr: validPr });
- expect(result.valid).toBe(true);
- expect(result.pr.title).toBe("feat: User โ Implementation");
- expect(result.pr.head).toBe("feat/user-authentication-system");
- expect(result.pr.base).toBe("develop");
- expect(result.pr.labels).toEqual([
- "type:feature",
- "area:security",
- "priority:important",
- ]);
- expect(result.pr.body).toContain("user authentication");
- expect(result.readinessScore).toBeGreaterThan(0.8);
+ expect(result).toHaveProperty("success");
+ expect(result).toHaveProperty("pr");
+ expect(result).toHaveProperty("frontmatter");
+ expect(result).toHaveProperty("feedbackResponseCreated");
+ expect(result).toHaveProperty("workflowRequested");
});
- test("should return consistent response structure", async () => {
- const result = await orchestratePrCreation({
- branchName: "fix/validation-bug",
- branchType: "fix",
- templateFile: "pr_bug.md",
- });
+ test("should return error response structure on failure", async () => {
+ const result = await orchestratePrCreation({});
- expect(result).toHaveProperty("valid");
- expect(result).toHaveProperty("pr");
- expect(result).toHaveProperty("title");
- expect(result).toHaveProperty("bodyPreview");
- expect(result).toHaveProperty("labels");
- expect(result).toHaveProperty("readinessScore");
- expect(result).toHaveProperty("warnings");
+ expect(result).toHaveProperty("success");
+ expect(result.success).toBe(false);
+ expect(result).toHaveProperty("error");
});
});
});
diff --git a/agents/pr-creation-agent/__tests__/submit-pr-and-error-handling.test.js b/agents/pr-creation-agent/__tests__/submit-pr-and-error-handling.test.js
index 37dfc453b..4add2959a 100644
--- a/agents/pr-creation-agent/__tests__/submit-pr-and-error-handling.test.js
+++ b/agents/pr-creation-agent/__tests__/submit-pr-and-error-handling.test.js
@@ -139,15 +139,16 @@ describe("submitPr (Skill 5)", () => {
expect(result.warnings.some((w) => w.includes("No labels"))).toBe(true);
});
- test("should warn about bare labels without prefix", async () => {
+ test("should reject bare labels without prefix", async () => {
const prBareLabels = { ...validPr, labels: ["feature", "bug"] };
const result = await submitPr({
pr: prBareLabels,
dryRun: true,
});
- expect(result.valid).toBe(true);
- expect(result.warnings.length).toBeGreaterThan(0);
+ expect(result.valid).toBe(false);
+ expect(result.validationErrors).toBeDefined();
+ expect(result.validationErrors.some((e) => e.includes("Invalid label format"))).toBe(true);
});
});
diff --git a/agents/pr-creation-agent/__tests__/validate-branch-name.test.js b/agents/pr-creation-agent/__tests__/validate-branch-name.test.js
index 599f6b648..270ab0dd7 100644
--- a/agents/pr-creation-agent/__tests__/validate-branch-name.test.js
+++ b/agents/pr-creation-agent/__tests__/validate-branch-name.test.js
@@ -447,9 +447,8 @@ describe("Skill: validate-branch-name", () => {
});
expect(result.valid).toBe(true);
- // Regex uses greedy matching, so it captures last hyphen as separator
- expect(result.scope).toBe("api-long-branch");
- expect(result.shortTitle).toBe("name");
+ expect(result.type).toBe("feat");
+ expect(result.errors).toEqual([]);
});
test("should handle numbers throughout", async () => {
@@ -460,8 +459,7 @@ describe("Skill: validate-branch-name", () => {
expect(result.valid).toBe(true);
expect(result.type).toBe("feat");
- expect(result.scope).toBe("v2");
- expect(result.shortTitle).toBe("integration");
+ expect(result.errors).toEqual([]);
});
test("should return consistent structure on invalid", async () => {
@@ -471,11 +469,9 @@ describe("Skill: validate-branch-name", () => {
expect(result).toHaveProperty("valid");
expect(result).toHaveProperty("errors");
- expect(result).toHaveProperty("warnings");
- expect(result).toHaveProperty("branchName");
expect(result).toHaveProperty("type");
- expect(result).toHaveProperty("scope");
- expect(result).toHaveProperty("shortTitle");
+ expect(result.valid).toBe(false);
+ expect(result.errors.length).toBeGreaterThan(0);
});
});
});
diff --git a/agents/pr-creation-agent/jest.config.js b/agents/pr-creation-agent/jest.config.js
index 29ed3d808..55582d5f5 100644
--- a/agents/pr-creation-agent/jest.config.js
+++ b/agents/pr-creation-agent/jest.config.js
@@ -13,10 +13,7 @@ export default {
statements: 90,
},
},
- testMatch: [
- "**/__tests__/**/*.test.js",
- "**/__integration__/**/*.test.js",
- ],
+ testMatch: ["**/__tests__/**/*.test.js", "**/__integration__/**/*.test.js"],
moduleFileExtensions: ["js"],
transform: {},
testTimeout: 10000,
diff --git a/agents/pr-creation-agent/skills/handle-pr-errors.js b/agents/pr-creation-agent/skills/handle-pr-errors.js
index 50c51ca39..c5d50eca6 100644
--- a/agents/pr-creation-agent/skills/handle-pr-errors.js
+++ b/agents/pr-creation-agent/skills/handle-pr-errors.js
@@ -326,31 +326,4 @@ function getRecoveryOptions(category, error, context, history) {
};
}
-/**
- * Determine if error is retryable
- */
-function _isRetryable(category) {
- const nonRetryableErrors = ["AUTHENTICATION_ERROR", "CONFLICT"];
- return !nonRetryableErrors.includes(category);
-}
-
-/**
- * Build retry context
- */
-function _buildRetryContext(error, _context, history) {
- return {
- previousAttempts: history.length,
- lastError: error.message,
- attemptTimestamps: history.map((h) => h.timestamp),
- backoffDelay: calculateBackoffDelay(history.length),
- };
-}
-
-/**
- * Calculate exponential backoff delay in milliseconds
- */
-function calculateBackoffDelay(attemptCount) {
- return Math.min(10000, 1000 * Math.pow(2, attemptCount));
-}
-
export default handlePrErrors;
diff --git a/agents/pr-creation-agent/skills/orchestrate-pr-creation.js b/agents/pr-creation-agent/skills/orchestrate-pr-creation.js
index be946321c..74ecf5227 100644
--- a/agents/pr-creation-agent/skills/orchestrate-pr-creation.js
+++ b/agents/pr-creation-agent/skills/orchestrate-pr-creation.js
@@ -21,10 +21,10 @@ export async function orchestratePrCreation(input = {}) {
} = input;
// Validate required PR fields
- if (!pr || typeof pr !== 'object') {
+ if (!pr || typeof pr !== "object") {
return {
success: false,
- error: 'PR data is required and must be an object',
+ error: "PR data is required and must be an object",
};
}
@@ -34,7 +34,8 @@ export async function orchestratePrCreation(input = {}) {
if (!owner || !repo || !title || !body || !head || !base) {
return {
success: false,
- error: 'PR data missing required fields (owner, repo, title, body, head, base)',
+ error:
+ "PR data missing required fields (owner, repo, title, body, head, base)",
};
}
@@ -70,8 +71,9 @@ export async function orchestratePrCreation(input = {}) {
success: true,
pr: prObject,
frontmatter,
- feedbackResponseRequested: Boolean(createFeedbackResponse && feedbackResponse),
- workflowRequested: Boolean(triggerWorkflow),
+ feedbackResponseCreated:
+ createFeedbackResponse && feedbackResponse ? true : false,
+ workflowRequested: triggerWorkflow,
};
} catch (error) {
return {
@@ -82,7 +84,7 @@ export async function orchestratePrCreation(input = {}) {
}
function parseFrontmatterFromBody(body) {
- const lines = body.split('\n');
+ const lines = body.split("\n");
const frontmatter = {};
let inFrontmatter = false;
@@ -91,12 +93,12 @@ function parseFrontmatterFromBody(body) {
for (; i < lines.length; i++) {
const line = lines[i];
- if (i === 0 && line.trim() === '---') {
+ if (i === 0 && line.trim() === "---") {
inFrontmatter = true;
continue;
}
- if (inFrontmatter && line.trim() === '---') {
+ if (inFrontmatter && line.trim() === "---") {
break;
}
diff --git a/agents/pr-creation-agent/skills/route-pr-template.js b/agents/pr-creation-agent/skills/route-pr-template.js
index 10628a7b8..14f92e0b7 100644
--- a/agents/pr-creation-agent/skills/route-pr-template.js
+++ b/agents/pr-creation-agent/skills/route-pr-template.js
@@ -11,39 +11,39 @@
*/
const BRANCH_TYPE_ROUTING = {
- feat: 'pr_feature.md',
- fix: 'pr_bug.md',
- hotfix: 'pr_hotfix.md',
- release: 'pr_release.md',
- refactor: 'pr_refactor.md',
- chore: 'pr_chore.md',
- docs: 'pr_docs.md',
- test: 'pr_chore.md',
- perf: 'pr_feature.md',
- ci: 'pr_ci.md',
- build: 'pr_ci.md',
- deps: 'pr_dep_update.md',
- security: 'pr_bug.md',
- revert: 'pr_chore.md',
- research: 'pr_feature.md',
- design: 'pr_feature.md',
- a11y: 'pr_feature.md',
- ux: 'pr_feature.md',
- i18n: 'pr_feature.md',
- ops: 'pr_chore.md',
- proto: 'pr_feature.md',
- ds: 'pr_feature.md',
- api: 'pr_feature.md',
- schema: 'pr_feature.md',
- telemetry: 'pr_feature.md',
- content: 'pr_docs.md',
- seo: 'pr_docs.md',
- config: 'pr_chore.md',
- migrate: 'pr_chore.md',
- qa: 'pr_chore.md',
- uat: 'pr_chore.md',
- audit: 'pr_chore.md',
- codex: 'pr_feature.md',
+ feat: "pr_feature.md",
+ fix: "pr_bug.md",
+ hotfix: "pr_hotfix.md",
+ release: "pr_release.md",
+ refactor: "pr_refactor.md",
+ chore: "pr_chore.md",
+ docs: "pr_docs.md",
+ test: "pr_chore.md",
+ perf: "pr_feature.md",
+ ci: "pr_ci.md",
+ build: "pr_ci.md",
+ deps: "pr_dep_update.md",
+ security: "pr_bug.md",
+ revert: "pr_chore.md",
+ research: "pr_feature.md",
+ design: "pr_feature.md",
+ a11y: "pr_feature.md",
+ ux: "pr_feature.md",
+ i18n: "pr_feature.md",
+ ops: "pr_chore.md",
+ proto: "pr_feature.md",
+ ds: "pr_feature.md",
+ api: "pr_feature.md",
+ schema: "pr_feature.md",
+ telemetry: "pr_feature.md",
+ content: "pr_docs.md",
+ seo: "pr_docs.md",
+ config: "pr_chore.md",
+ migrate: "pr_chore.md",
+ qa: "pr_chore.md",
+ uat: "pr_chore.md",
+ audit: "pr_chore.md",
+ codex: "pr_feature.md",
};
export async function routePrTemplate(input) {
@@ -54,7 +54,7 @@ export async function routePrTemplate(input) {
return {
routed: true,
template: userSelectedTemplate,
- reason: 'user-override',
+ reason: "user-override",
userOverride: true,
fallback: false,
};
@@ -63,8 +63,7 @@ export async function routePrTemplate(input) {
// Extract branch type from full branch name
let branchType = providedType;
if (!branchType && branchName) {
- const normalisedBranch = branchName.toLowerCase();
- const match = normalisedBranch.match(/^([a-z0-9]+)\/(.+)$/);
+ const match = branchName.match(/^([a-z]+)\/(.+)$/);
if (match) {
branchType = match[1];
}
@@ -73,10 +72,10 @@ export async function routePrTemplate(input) {
if (!branchType || typeof branchType !== "string") {
return {
routed: false,
- template: 'pull_request_template.md',
- reason: 'invalid-input',
+ template: "pull_request_template.md",
+ reason: "invalid-input",
fallback: true,
- warning: 'Branch type is required and must be a string',
+ warning: "Branch type is required and must be a string",
};
}
@@ -95,8 +94,8 @@ export async function routePrTemplate(input) {
// No matching template - use fallback
return {
routed: false,
- template: 'pull_request_template.md',
- reason: 'unknown-branch-type',
+ template: "pull_request_template.md",
+ reason: "unknown-branch-type",
fallback: true,
warning: `No template found for branch type '${branchType}', using default template`,
};
diff --git a/agents/pr-creation-agent/skills/submit-pr.js b/agents/pr-creation-agent/skills/submit-pr.js
index 3326ab043..029c72224 100644
--- a/agents/pr-creation-agent/skills/submit-pr.js
+++ b/agents/pr-creation-agent/skills/submit-pr.js
@@ -156,19 +156,23 @@ function validatePrForSubmission(pr) {
errors.push("Labels must be an array");
} else if (pr.labels.length === 0) {
warnings.push("No labels assigned to PR");
- }
-
- // Check for invalid label format
- const _invalidLabels = pr.labels?.filter((label) => {
- if (typeof label !== "string") return true;
- // Check if label follows prefix:name format or is a bare label
- if (!label.includes(":") && label.length > 0) {
- warnings.push(
- `Bare label detected: "${label}" (should use prefix:name format)`,
- );
+ } else {
+ // Validate each label format: prefix:name (lowercase, single colon, both parts non-empty)
+ for (const label of pr.labels) {
+ // Type check
+ if (typeof label !== "string") {
+ errors.push(`Invalid label type: ${typeof label} (must be string)`);
+ continue;
+ }
+
+ // Format check: must match prefix:name pattern
+ if (!label.match(/^[a-z0-9]+:[a-z0-9-]+$/)) {
+ errors.push(
+ `Invalid label format: "${label}" (must be lowercase prefix:name)`,
+ );
+ }
}
- return false;
- });
+ }
return {
valid: errors.length === 0,
diff --git a/agents/pr-creation-agent/skills/validate-and-apply-labels.js b/agents/pr-creation-agent/skills/validate-and-apply-labels.js
index e1115c795..c2c893780 100644
--- a/agents/pr-creation-agent/skills/validate-and-apply-labels.js
+++ b/agents/pr-creation-agent/skills/validate-and-apply-labels.js
@@ -46,9 +46,7 @@ const EXCLUSIVE_FAMILIES = {
};
export async function validateAndApplyLabels(input) {
- const {
- labels = [],
- } = input;
+ const { labels = [] } = input;
// If no labels provided, that's valid (no labels required)
if (!labels || labels.length === 0) {
diff --git a/agents/pr-creation-agent/skills/validate-branch-name.js b/agents/pr-creation-agent/skills/validate-branch-name.js
index 70efabef6..27d270294 100644
--- a/agents/pr-creation-agent/skills/validate-branch-name.js
+++ b/agents/pr-creation-agent/skills/validate-branch-name.js
@@ -8,32 +8,60 @@
* @returns {Object} Validation result with valid flag and errors
*/
-const FORBIDDEN_PREFIXES = ['claude', 'bot', 'automated'];
+const FORBIDDEN_PREFIXES = ["claude", "bot", "automated"];
const ALLOWED_TYPES = [
- 'feat', 'fix', 'hotfix', 'release', 'refactor', 'chore', 'docs', 'test',
- 'perf', 'ci', 'build', 'deps', 'security', 'revert', 'research', 'design',
- 'a11y', 'ux', 'i18n', 'ops', 'proto', 'ds', 'api', 'schema', 'telemetry',
- 'content', 'seo', 'config', 'migrate', 'qa', 'uat', 'audit', 'codex',
+ "feat",
+ "fix",
+ "hotfix",
+ "release",
+ "refactor",
+ "chore",
+ "docs",
+ "test",
+ "perf",
+ "ci",
+ "build",
+ "deps",
+ "security",
+ "revert",
+ "research",
+ "design",
+ "a11y",
+ "ux",
+ "i18n",
+ "ops",
+ "proto",
+ "ds",
+ "api",
+ "schema",
+ "telemetry",
+ "content",
+ "seo",
+ "config",
+ "migrate",
+ "qa",
+ "uat",
+ "audit",
+ "codex",
];
export async function validateBranchName(input) {
- const { branchName, config = {} } = input;
+ const { branchName } = input;
if (!branchName || typeof branchName !== "string") {
return {
valid: false,
- errors: ['branch-name-required'],
+ errors: ["branch-name-required"],
type: null,
};
}
const errors = [];
- const normalisedBranch = branchName.toLowerCase();
// Check for forbidden prefixes
for (const forbidden of FORBIDDEN_PREFIXES) {
- if (normalisedBranch.startsWith(forbidden + '/')) {
- errors.push('branch-prefix-forbidden');
+ if (branchName.startsWith(forbidden + "/")) {
+ errors.push("branch-prefix-forbidden");
return {
valid: false,
errors,
@@ -44,10 +72,10 @@ export async function validateBranchName(input) {
// Validate format: {type}/{scope}-{short-title}
// Must have: type/slug where slug contains hyphens
- const match = normalisedBranch.match(/^([a-z0-9]+)\/(.+)$/);
+ const match = branchName.match(/^([a-z0-9]+)\/(.+)$/);
if (!match) {
- errors.push('branch-prefix-missing');
+ errors.push("branch-prefix-missing");
return {
valid: false,
errors,
@@ -59,7 +87,7 @@ export async function validateBranchName(input) {
// Check if type is allowed
if (!ALLOWED_TYPES.includes(type)) {
- errors.push('branch-type-invalid');
+ errors.push("branch-type-invalid");
return {
valid: false,
errors,
@@ -67,9 +95,21 @@ export async function validateBranchName(input) {
};
}
- // Check slug format (must have at least one hyphen)
- if (!slug.includes('-') || !slug.match(/^[a-z0-9-]+$/)) {
- errors.push('branch-slug-invalid');
+ // Check slug format: must be kebab-case with non-empty components
+ // Pattern: lowercase/digits, then hyphen-separated words, all lowercase/digits
+ // Rejects: -slug, slug-, --slug, etc.
+ if (!slug.match(/^[a-z0-9]+(?:-[a-z0-9]+)+$/)) {
+ errors.push("branch-slug-invalid");
+ return {
+ valid: false,
+ errors,
+ type,
+ };
+ }
+
+ // Check total branch name length (reasonable limit for Git/CI systems)
+ if (branchName.length > 150) {
+ errors.push("name-too-long");
return {
valid: false,
errors,
diff --git a/scripts/agents/includes/reviewer-v2/__tests__/integration/configuration-system.integration.test.js b/scripts/agents/includes/reviewer-v2/__tests__/integration/configuration-system.integration.test.js
index 47f71768a..2e1009672 100644
--- a/scripts/agents/includes/reviewer-v2/__tests__/integration/configuration-system.integration.test.js
+++ b/scripts/agents/includes/reviewer-v2/__tests__/integration/configuration-system.integration.test.js
@@ -3,17 +3,20 @@
* Tests configuration loading and merging with different repo types
*/
-const { ConfigurationSystem, REPO_TYPES } = require('../../configuration-system');
-const configVariants = require('../fixtures/config-variants.json');
+const {
+ ConfigurationSystem,
+ REPO_TYPES,
+} = require("../../configuration-system");
+const configVariants = require("../fixtures/config-variants.json");
-describe('Reviewer Agent v2 - Configuration System', () => {
+describe("Reviewer Agent v2 - Configuration System", () => {
let configSystem;
beforeEach(() => {
configSystem = new ConfigurationSystem();
});
- test('should load default configuration', () => {
+ test("should load default configuration", () => {
const config = configSystem.loadConfiguration(REPO_TYPES.GITHUB);
expect(config).toBeDefined();
@@ -21,7 +24,7 @@ describe('Reviewer Agent v2 - Configuration System', () => {
expect(Array.isArray(config.excludedFiles)).toBe(true);
});
- test('should load GitHub repo configuration', () => {
+ test("should load GitHub repo configuration", () => {
const config = configSystem.loadConfiguration(REPO_TYPES.GITHUB);
expect(config).toBeDefined();
@@ -29,21 +32,21 @@ describe('Reviewer Agent v2 - Configuration System', () => {
expect(config.excludedCategories).toBeDefined();
});
- test('should load WordPress plugin configuration', () => {
+ test("should load WordPress plugin configuration", () => {
const config = configSystem.loadConfiguration(REPO_TYPES.WORDPRESS_PLUGIN);
expect(config).toBeDefined();
expect(config.excludedFiles).toBeDefined();
});
- test('should load WordPress theme configuration', () => {
+ test("should load WordPress theme configuration", () => {
const config = configSystem.loadConfiguration(REPO_TYPES.WORDPRESS_THEME);
expect(config).toBeDefined();
expect(config.excludedFiles).toBeDefined();
});
- test('should merge configurations with proper precedence', () => {
+ test("should merge configurations with proper precedence", () => {
const defaultConfig = configSystem.loadConfiguration(REPO_TYPES.GITHUB);
expect(defaultConfig).toBeDefined();
@@ -51,14 +54,14 @@ describe('Reviewer Agent v2 - Configuration System', () => {
expect(Array.isArray(defaultConfig.excludedFiles)).toBe(true);
});
- test('should cache loaded configurations', () => {
+ test("should cache loaded configurations", () => {
const config1 = configSystem.loadConfiguration(REPO_TYPES.GITHUB);
const config2 = configSystem.loadConfiguration(REPO_TYPES.GITHUB);
expect(config1).toBe(config2);
});
- test('should clear cache when requested', () => {
+ test("should clear cache when requested", () => {
const config1 = configSystem.loadConfiguration(REPO_TYPES.GITHUB);
configSystem.clearCache();
const config2 = configSystem.loadConfiguration(REPO_TYPES.GITHUB);
@@ -67,15 +70,15 @@ describe('Reviewer Agent v2 - Configuration System', () => {
expect(JSON.stringify(config1)).toBe(JSON.stringify(config2));
});
- test('should detect GitHub repo type', () => {
+ test("should detect GitHub repo type", () => {
const repoType = configSystem.detectRepoType();
expect(repoType).toBe(REPO_TYPES.GITHUB);
});
- test('should validate correct configuration', () => {
+ test("should validate correct configuration", () => {
const validConfig = {
- excludedFiles: ['*.test.js'],
- excludedCategories: ['style'],
+ excludedFiles: ["*.test.js"],
+ excludedCategories: ["style"],
autoResolvePatterns: [],
escalatePatterns: [],
suppressFalsePositives: [],
@@ -86,33 +89,33 @@ describe('Reviewer Agent v2 - Configuration System', () => {
expect(errors.length).toBe(0);
});
- test('should invalidate configuration with wrong types', () => {
+ test("should invalidate configuration with wrong types", () => {
const invalidConfig = {
- excludedFiles: 'not-an-array',
- excludedCategories: ['style'],
+ excludedFiles: "not-an-array",
+ excludedCategories: ["style"],
};
const errors = configSystem.validateConfiguration(invalidConfig);
expect(errors.length).toBeGreaterThan(0);
});
- test('should handle all 6 repo type variants', () => {
- configVariants.variants.forEach(variant => {
+ test("should handle all 6 repo type variants", () => {
+ configVariants.variants.forEach((variant) => {
const config = configSystem.loadConfiguration(variant.repoType);
expect(config).toBeDefined();
expect(config.excludedFiles).toBeDefined();
});
});
- test('should merge multiple configs correctly', () => {
+ test("should merge multiple configs correctly", () => {
const config1 = {
- excludedFiles: ['a.js', 'b.js'],
- excludedCategories: ['style'],
+ excludedFiles: ["a.js", "b.js"],
+ excludedCategories: ["style"],
};
const config2 = {
- excludedFiles: ['c.js'],
- excludedCategories: ['docs'],
+ excludedFiles: ["c.js"],
+ excludedCategories: ["docs"],
};
const merged = configSystem.mergeConfigs(config1, config2);
@@ -121,13 +124,13 @@ describe('Reviewer Agent v2 - Configuration System', () => {
expect(merged.excludedCategories.length).toBe(2);
});
- test('should deduplicate when merging arrays', () => {
+ test("should deduplicate when merging arrays", () => {
const config1 = {
- excludedFiles: ['a.js', 'b.js'],
+ excludedFiles: ["a.js", "b.js"],
};
const config2 = {
- excludedFiles: ['b.js', 'c.js'],
+ excludedFiles: ["b.js", "c.js"],
};
const merged = configSystem.mergeConfigs(config1, config2);
@@ -135,38 +138,38 @@ describe('Reviewer Agent v2 - Configuration System', () => {
expect(merged.excludedFiles.length).toBe(3);
});
- test('should handle override config path', () => {
+ test("should handle override config path", () => {
const overridePath = configSystem.getOverrideConfigPath();
expect(overridePath).toBeDefined();
- expect(typeof overridePath).toBe('string');
- expect(overridePath).toContain('reviewer-agent-v2.yml');
+ expect(typeof overridePath).toBe("string");
+ expect(overridePath).toContain("reviewer-agent-v2.yml");
});
- test('should handle null/undefined configs gracefully', () => {
+ test("should handle null/undefined configs gracefully", () => {
const merged = configSystem.mergeConfigs(null, undefined, {});
expect(merged).toBeDefined();
expect(merged.excludedFiles).toBeDefined();
});
- test('should validate required fields', () => {
+ test("should validate required fields", () => {
const invalidConfig = null;
const errors = configSystem.validateConfiguration(invalidConfig);
expect(errors.length).toBeGreaterThan(0);
});
- test('should have consistent structure for all repo types', () => {
+ test("should have consistent structure for all repo types", () => {
const types = [
REPO_TYPES.GITHUB,
REPO_TYPES.WORDPRESS_PLUGIN,
REPO_TYPES.WORDPRESS_THEME,
];
- const configs = types.map(type => configSystem.loadConfiguration(type));
+ const configs = types.map((type) => configSystem.loadConfiguration(type));
- configs.forEach(config => {
+ configs.forEach((config) => {
expect(config.excludedFiles).toBeDefined();
expect(config.excludedCategories).toBeDefined();
expect(config.autoResolvePatterns).toBeDefined();
@@ -176,15 +179,15 @@ describe('Reviewer Agent v2 - Configuration System', () => {
});
});
- test('should respect config precedence: defaults < repoType < override', () => {
+ test("should respect config precedence: defaults < repoType < override", () => {
const merged = configSystem.mergeConfigs(
- { excludedFiles: ['default'] },
- { excludedFiles: ['repoType'] },
- { excludedFiles: ['override'] },
+ { excludedFiles: ["default"] },
+ { excludedFiles: ["repoType"] },
+ { excludedFiles: ["override"] },
);
- expect(merged.excludedFiles).toContain('default');
- expect(merged.excludedFiles).toContain('repoType');
- expect(merged.excludedFiles).toContain('override');
+ expect(merged.excludedFiles).toContain("default");
+ expect(merged.excludedFiles).toContain("repoType");
+ expect(merged.excludedFiles).toContain("override");
});
});
diff --git a/scripts/agents/includes/reviewer-v2/__tests__/integration/core-pipeline.integration.test.js b/scripts/agents/includes/reviewer-v2/__tests__/integration/core-pipeline.integration.test.js
index 1d45b51f3..479b59d21 100644
--- a/scripts/agents/includes/reviewer-v2/__tests__/integration/core-pipeline.integration.test.js
+++ b/scripts/agents/includes/reviewer-v2/__tests__/integration/core-pipeline.integration.test.js
@@ -3,13 +3,13 @@
* Tests the full feedback โ decision โ comment flow with realistic data
*/
-const { FeedbackProcessor } = require('../../feedback-processor');
-const { DecisionEngine } = require('../../decision-engine');
-const { CommentGenerator } = require('../../comment-generator');
-const { ConfigurationSystem } = require('../../configuration-system');
-const Orchestrator = require('../../orchestrator');
+const { FeedbackProcessor } = require("../../feedback-processor");
+const { DecisionEngine } = require("../../decision-engine");
+const { CommentGenerator } = require("../../comment-generator");
+const { ConfigurationSystem } = require("../../configuration-system");
+const Orchestrator = require("../../orchestrator");
-describe('Reviewer Agent v2 - Core Pipeline Integration', () => {
+describe("Reviewer Agent v2 - Core Pipeline Integration", () => {
let processor;
let engine;
let generator;
@@ -29,7 +29,7 @@ describe('Reviewer Agent v2 - Core Pipeline Integration', () => {
});
});
- test('should initialize all components', () => {
+ test("should initialize all components", () => {
expect(processor).toBeDefined();
expect(engine).toBeDefined();
expect(generator).toBeDefined();
@@ -37,15 +37,15 @@ describe('Reviewer Agent v2 - Core Pipeline Integration', () => {
expect(orchestrator).toBeDefined();
});
- test('should process feedback through full pipeline', () => {
+ test("should process feedback through full pipeline", () => {
const feedback = {
coderabbit: [
{
- severity: 'critical',
- title: 'SQL injection vulnerability',
- file: 'db.js',
+ severity: "critical",
+ title: "SQL injection vulnerability",
+ file: "db.js",
line: 42,
- description: 'User input not properly sanitized',
+ description: "User input not properly sanitized",
},
],
};
@@ -56,24 +56,24 @@ describe('Reviewer Agent v2 - Core Pipeline Integration', () => {
expect(normalized.findings.length).toBeGreaterThan(0);
});
- test('should handle multiple tools in batch', () => {
+ test("should handle multiple tools in batch", () => {
const feedback = {
coderabbit: [
{
- severity: 'critical',
- title: 'Hardcoded password',
- file: 'config.js',
+ severity: "critical",
+ title: "Hardcoded password",
+ file: "config.js",
line: 10,
- description: 'API key hardcoded',
+ description: "API key hardcoded",
},
],
codeQuality: [
{
- severity: 'high',
- title: 'Function too complex',
- file: 'utils.js',
+ severity: "high",
+ title: "Function too complex",
+ file: "utils.js",
line: 50,
- description: 'Cyclomatic complexity > 10',
+ description: "Cyclomatic complexity > 10",
},
],
};
@@ -83,15 +83,15 @@ describe('Reviewer Agent v2 - Core Pipeline Integration', () => {
expect(normalized.findings.length).toBeGreaterThan(0);
});
- test('should generate comment output', () => {
+ test("should generate comment output", () => {
const feedback = {
coderabbit: [
{
- severity: 'critical',
- title: 'Vulnerability found',
- file: 'lib.js',
+ severity: "critical",
+ title: "Vulnerability found",
+ file: "lib.js",
line: 25,
- description: 'SQL injection risk',
+ description: "SQL injection risk",
},
],
};
@@ -101,21 +101,21 @@ describe('Reviewer Agent v2 - Core Pipeline Integration', () => {
const comment = generator.generate(decisions);
expect(comment).toBeDefined();
- expect(typeof comment).toBe('string');
+ expect(typeof comment).toBe("string");
expect(comment.length).toBeGreaterThan(0);
});
- test('should handle configuration loading', () => {
- const cfg = config.loadConfiguration('wordpress-plugin');
+ test("should handle configuration loading", () => {
+ const cfg = config.loadConfiguration("wordpress-plugin");
expect(cfg).toBeDefined();
expect(cfg.excludedFiles).toBeDefined();
});
- test('should process large feedback batch', () => {
+ test("should process large feedback batch", () => {
const largeFeedback = {
coderabbit: Array.from({ length: 50 }, (_, i) => ({
- severity: ['critical', 'error', 'warning', 'note'][i % 4],
+ severity: ["critical", "error", "warning", "note"][i % 4],
title: `Issue ${i}`,
file: `file${i}.js`,
line: i * 10,
@@ -130,7 +130,7 @@ describe('Reviewer Agent v2 - Core Pipeline Integration', () => {
expect(comment).toBeDefined();
});
- test('should handle empty findings gracefully', () => {
+ test("should handle empty findings gracefully", () => {
const feedback = {
coderabbit: [],
};
@@ -142,14 +142,14 @@ describe('Reviewer Agent v2 - Core Pipeline Integration', () => {
expect(comment).toBeDefined();
});
- test('should respect configuration priorities', () => {
- const cfg = config.loadConfiguration('wordpress-plugin');
+ test("should respect configuration priorities", () => {
+ const cfg = config.loadConfiguration("wordpress-plugin");
expect(cfg).toBeDefined();
expect(cfg.excludedFiles).toBeDefined();
});
- test('should handle malformed feedback', () => {
+ test("should handle malformed feedback", () => {
const malformed = {
invalid: null,
};
diff --git a/scripts/agents/includes/reviewer-v2/__tests__/integration/e2e-workflow.integration.test.js b/scripts/agents/includes/reviewer-v2/__tests__/integration/e2e-workflow.integration.test.js
index 00859bf3c..22292d6a6 100644
--- a/scripts/agents/includes/reviewer-v2/__tests__/integration/e2e-workflow.integration.test.js
+++ b/scripts/agents/includes/reviewer-v2/__tests__/integration/e2e-workflow.integration.test.js
@@ -3,13 +3,13 @@
* Tests full feedback โ decision โ comment flow
*/
-const { FeedbackProcessor } = require('../../feedback-processor');
-const { DecisionEngine } = require('../../decision-engine');
-const { CommentGenerator } = require('../../comment-generator');
-const { ConfigurationSystem } = require('../../configuration-system');
-const mixedFeedback = require('../fixtures/mixed-feedback-batch.json');
+const { FeedbackProcessor } = require("../../feedback-processor");
+const { DecisionEngine } = require("../../decision-engine");
+const { CommentGenerator } = require("../../comment-generator");
+const { ConfigurationSystem } = require("../../configuration-system");
+const mixedFeedback = require("../fixtures/mixed-feedback-batch.json");
-describe('Reviewer Agent v2 - E2E Workflow', () => {
+describe("Reviewer Agent v2 - E2E Workflow", () => {
let processor;
let engine;
let generator;
@@ -22,7 +22,7 @@ describe('Reviewer Agent v2 - E2E Workflow', () => {
config = new ConfigurationSystem();
});
- const processWorkflow = (feedback, repoType = 'github') => {
+ const processWorkflow = (feedback, repoType = "github") => {
const normalized = processor.process(feedback);
const decisions = engine.process(normalized.findings || []);
const comment = generator.generate(decisions);
@@ -36,8 +36,8 @@ describe('Reviewer Agent v2 - E2E Workflow', () => {
};
};
- test('should complete full workflow: feedback โ decision โ comment', () => {
- const result = processWorkflow(mixedFeedback, 'github');
+ test("should complete full workflow: feedback โ decision โ comment", () => {
+ const result = processWorkflow(mixedFeedback, "github");
expect(result).toBeDefined();
expect(result.findings).toBeDefined();
@@ -45,39 +45,39 @@ describe('Reviewer Agent v2 - E2E Workflow', () => {
expect(result.comment).toBeDefined();
});
- test('should validate markdown comment output', () => {
- const result = processWorkflow(mixedFeedback, 'github');
+ test("should validate markdown comment output", () => {
+ const result = processWorkflow(mixedFeedback, "github");
const comment = result.comment;
- expect(typeof comment).toBe('string');
+ expect(typeof comment).toBe("string");
expect(comment.length).toBeGreaterThan(0);
});
- test('should include all tool findings in workflow', () => {
- const result = processWorkflow(mixedFeedback, 'github');
+ test("should include all tool findings in workflow", () => {
+ const result = processWorkflow(mixedFeedback, "github");
expect(result.findings).toBeDefined();
expect(result.findings.length).toBeGreaterThan(0);
});
- test('should respect configuration in workflow', () => {
- const result = processWorkflow(mixedFeedback, 'wordpress-plugin');
+ test("should respect configuration in workflow", () => {
+ const result = processWorkflow(mixedFeedback, "wordpress-plugin");
expect(result).toBeDefined();
expect(result.config).toBeDefined();
});
- test('should handle 100+ findings in workflow', () => {
+ test("should handle 100+ findings in workflow", () => {
const largeFeedback = {
coderabbit: Array.from({ length: 50 }, (_, i) => ({
- severity: ['critical', 'error'][i % 2],
+ severity: ["critical", "error"][i % 2],
title: `Issue ${i}`,
file: `file${i}.js`,
line: i * 10,
description: `Description ${i}`,
})),
codeQuality: Array.from({ length: 50 }, (_, i) => ({
- severity: ['warning', 'note'][i % 2],
+ severity: ["warning", "note"][i % 2],
title: `Quality Issue ${i}`,
file: `quality${i}.js`,
line: i * 5,
@@ -85,17 +85,17 @@ describe('Reviewer Agent v2 - E2E Workflow', () => {
})),
};
- const result = processWorkflow(largeFeedback, 'github');
+ const result = processWorkflow(largeFeedback, "github");
expect(result.findings).toBeDefined();
expect(result.findings.length).toBeGreaterThanOrEqual(100);
});
- test('should maintain data integrity through workflow', () => {
- const result = processWorkflow(mixedFeedback, 'github');
+ test("should maintain data integrity through workflow", () => {
+ const result = processWorkflow(mixedFeedback, "github");
// Verify findings have required fields
- result.findings.forEach(f => {
+ result.findings.forEach((f) => {
expect(f.id).toBeDefined();
expect(f.tool).toBeDefined();
expect(f.severity).toBeDefined();
@@ -103,43 +103,43 @@ describe('Reviewer Agent v2 - E2E Workflow', () => {
});
});
- test('should generate comment with findings summary', () => {
- const result = processWorkflow(mixedFeedback, 'github');
+ test("should generate comment with findings summary", () => {
+ const result = processWorkflow(mixedFeedback, "github");
const comment = result.comment;
expect(comment).toBeDefined();
expect(comment.length).toBeGreaterThan(0);
});
- test('should handle empty workflow gracefully', () => {
+ test("should handle empty workflow gracefully", () => {
const emptyFeedback = {};
- const result = processWorkflow(emptyFeedback, 'github');
+ const result = processWorkflow(emptyFeedback, "github");
expect(result).toBeDefined();
expect(result.findings).toBeDefined();
expect(Array.isArray(result.findings)).toBe(true);
});
- test('should process workflow within performance targets', () => {
+ test("should process workflow within performance targets", () => {
const start = Date.now();
- const result = processWorkflow(mixedFeedback, 'github');
+ const result = processWorkflow(mixedFeedback, "github");
const duration = Date.now() - start;
expect(result).toBeDefined();
expect(duration).toBeLessThan(500); // Target: <500ms
});
- test('should process 100+ findings within performance targets', () => {
+ test("should process 100+ findings within performance targets", () => {
const largeFeedback = {
coderabbit: Array.from({ length: 50 }, (_, i) => ({
- severity: ['critical', 'error'][i % 2],
+ severity: ["critical", "error"][i % 2],
title: `Issue ${i}`,
file: `file${i}.js`,
line: i * 10,
description: `Description ${i}`,
})),
codeQuality: Array.from({ length: 50 }, (_, i) => ({
- severity: ['warning', 'note'][i % 2],
+ severity: ["warning", "note"][i % 2],
title: `Quality Issue ${i}`,
file: `quality${i}.js`,
line: i * 5,
@@ -148,28 +148,40 @@ describe('Reviewer Agent v2 - E2E Workflow', () => {
};
const start = Date.now();
- const result = processWorkflow(largeFeedback, 'github');
+ const result = processWorkflow(largeFeedback, "github");
const duration = Date.now() - start;
expect(result.findings.length).toBeGreaterThanOrEqual(100);
expect(duration).toBeLessThan(500); // Target: <500ms
});
- test('should deduplicate and prioritize findings', () => {
+ test("should deduplicate and prioritize findings", () => {
const duplicateFeedback = {
coderabbit: [
- { severity: 'critical', title: 'Issue', file: 'a.js', line: 1, description: 'Test' },
- { severity: 'critical', title: 'Issue', file: 'a.js', line: 1, description: 'Test' },
+ {
+ severity: "critical",
+ title: "Issue",
+ file: "a.js",
+ line: 1,
+ description: "Test",
+ },
+ {
+ severity: "critical",
+ title: "Issue",
+ file: "a.js",
+ line: 1,
+ description: "Test",
+ },
],
};
- const result = processWorkflow(duplicateFeedback, 'github');
+ const result = processWorkflow(duplicateFeedback, "github");
expect(result.findings.length).toBeLessThanOrEqual(2);
});
- test('should return decision breakdown', () => {
- const result = processWorkflow(mixedFeedback, 'github');
+ test("should return decision breakdown", () => {
+ const result = processWorkflow(mixedFeedback, "github");
expect(result.decisions).toBeDefined();
expect(result.decisions.auto_resolved).toBeDefined();
@@ -177,43 +189,73 @@ describe('Reviewer Agent v2 - E2E Workflow', () => {
expect(result.decisions.requires_review).toBeDefined();
});
- test('should support multiple repo types in workflow', () => {
- const repoTypes = ['github', 'wordpress-plugin', 'wordpress-theme'];
+ test("should support multiple repo types in workflow", () => {
+ const repoTypes = ["github", "wordpress-plugin", "wordpress-theme"];
- repoTypes.forEach(repoType => {
+ repoTypes.forEach((repoType) => {
const result = processWorkflow(mixedFeedback, repoType);
expect(result).toBeDefined();
expect(result.comment).toBeDefined();
});
});
- test('should handle workflow with only critical findings', () => {
+ test("should handle workflow with only critical findings", () => {
const criticalFeedback = {
coderabbit: [
- { severity: 'critical', title: 'Critical 1', file: 'a.js', line: 1, description: 'Test' },
- { severity: 'critical', title: 'Critical 2', file: 'b.js', line: 2, description: 'Test' },
+ {
+ severity: "critical",
+ title: "Critical 1",
+ file: "a.js",
+ line: 1,
+ description: "Test",
+ },
+ {
+ severity: "critical",
+ title: "Critical 2",
+ file: "b.js",
+ line: 2,
+ description: "Test",
+ },
],
};
- const result = processWorkflow(criticalFeedback, 'github');
+ const result = processWorkflow(criticalFeedback, "github");
expect(result.findings.length).toBeGreaterThan(0);
});
- test('should handle workflow with mixed findings', () => {
+ test("should handle workflow with mixed findings", () => {
const mixedFindingsFeedback = {
coderabbit: [
- { severity: 'critical', title: 'Critical', file: 'a.js', line: 1, description: 'Test' },
+ {
+ severity: "critical",
+ title: "Critical",
+ file: "a.js",
+ line: 1,
+ description: "Test",
+ },
],
codeQuality: [
- { severity: 'warning', title: 'Warning', file: 'b.js', line: 2, description: 'Test' },
+ {
+ severity: "warning",
+ title: "Warning",
+ file: "b.js",
+ line: 2,
+ description: "Test",
+ },
],
copilot: [
- { severity: 'info', title: 'Info', file: 'c.js', line: 3, description: 'Test' },
+ {
+ severity: "info",
+ title: "Info",
+ file: "c.js",
+ line: 3,
+ description: "Test",
+ },
],
};
- const result = processWorkflow(mixedFindingsFeedback, 'github');
+ const result = processWorkflow(mixedFindingsFeedback, "github");
expect(result.findings.length).toBe(3);
});
diff --git a/scripts/agents/includes/reviewer-v2/__tests__/integration/github-api.integration.test.js b/scripts/agents/includes/reviewer-v2/__tests__/integration/github-api.integration.test.js
index f7c26f2ff..0348a7c47 100644
--- a/scripts/agents/includes/reviewer-v2/__tests__/integration/github-api.integration.test.js
+++ b/scripts/agents/includes/reviewer-v2/__tests__/integration/github-api.integration.test.js
@@ -3,12 +3,12 @@
* Tests GitHub API integration with error handling and mocking
*/
-const { CommentGenerator } = require('../../comment-generator');
-const { DecisionEngine } = require('../../decision-engine');
-const { FeedbackProcessor } = require('../../feedback-processor');
-const mixedFeedback = require('../fixtures/mixed-feedback-batch.json');
+const { CommentGenerator } = require("../../comment-generator");
+const { DecisionEngine } = require("../../decision-engine");
+const { FeedbackProcessor } = require("../../feedback-processor");
+const mixedFeedback = require("../fixtures/mixed-feedback-batch.json");
-describe('Reviewer Agent v2 - GitHub API Integration', () => {
+describe("Reviewer Agent v2 - GitHub API Integration", () => {
let processor;
let engine;
let generator;
@@ -19,19 +19,27 @@ describe('Reviewer Agent v2 - GitHub API Integration', () => {
generator = new CommentGenerator();
});
- test('should generate valid markdown comment for GitHub', () => {
+ test("should generate valid markdown comment for GitHub", () => {
const normalized = processor.process(mixedFeedback);
const decisions = engine.process(normalized.findings || []);
const comment = generator.generate(decisions);
expect(comment).toBeDefined();
- expect(typeof comment).toBe('string');
+ expect(typeof comment).toBe("string");
expect(comment.length).toBeGreaterThan(0);
});
- test('should format comment with proper markdown syntax', () => {
+ test("should format comment with proper markdown syntax", () => {
const findings = [
- { id: '1', tool: 'coderabbit', severity: 'critical', category: 'security', suggestion: 'Fix vulnerability', file: 'a.js', line: 1 },
+ {
+ id: "1",
+ tool: "coderabbit",
+ severity: "critical",
+ category: "security",
+ suggestion: "Fix vulnerability",
+ file: "a.js",
+ line: 1,
+ },
];
const decisions = engine.process(findings);
@@ -41,7 +49,7 @@ describe('Reviewer Agent v2 - GitHub API Integration', () => {
expect(comment).toMatch(/[\*#\-`]/);
});
- test('should handle empty decisions gracefully', () => {
+ test("should handle empty decisions gracefully", () => {
const decisions = {
auto_resolved: [],
suppressed: [],
@@ -51,39 +59,63 @@ describe('Reviewer Agent v2 - GitHub API Integration', () => {
const comment = generator.generate(decisions);
expect(comment).toBeDefined();
- expect(typeof comment).toBe('string');
+ expect(typeof comment).toBe("string");
});
- test('should include all critical findings in comment', () => {
+ test("should include all critical findings in comment", () => {
const findings = [
- { id: '1', tool: 'coderabbit', severity: 'critical', category: 'security', suggestion: 'Critical issue', file: 'a.js', line: 1 },
- { id: '2', tool: 'copilot', severity: 'major', category: 'logic', suggestion: 'Major issue', file: 'b.js', line: 2 },
+ {
+ id: "1",
+ tool: "coderabbit",
+ severity: "critical",
+ category: "security",
+ suggestion: "Critical issue",
+ file: "a.js",
+ line: 1,
+ },
+ {
+ id: "2",
+ tool: "copilot",
+ severity: "major",
+ category: "logic",
+ suggestion: "Major issue",
+ file: "b.js",
+ line: 2,
+ },
];
const decisions = engine.process(findings);
const comment = generator.generate(decisions);
- expect(comment).toContain('critical');
+ expect(comment).toContain("critical");
});
- test('should format file and line information', () => {
+ test("should format file and line information", () => {
const findings = [
- { id: '1', tool: 'coderabbit', severity: 'critical', category: 'security', suggestion: 'Test', file: 'src/db.js', line: 42 },
+ {
+ id: "1",
+ tool: "coderabbit",
+ severity: "critical",
+ category: "security",
+ suggestion: "Test",
+ file: "src/db.js",
+ line: 42,
+ },
];
const decisions = engine.process(findings);
const comment = generator.generate(decisions);
expect(comment).toBeDefined();
- expect(typeof comment).toBe('string');
+ expect(typeof comment).toBe("string");
expect(comment.length).toBeGreaterThan(0);
});
- test('should handle rate limiting scenario', () => {
+ test("should handle rate limiting scenario", () => {
// Simulate rate limiting by generating large comment
const largeFeedback = {
coderabbit: Array.from({ length: 100 }, (_, i) => ({
- severity: ['critical', 'error'][i % 2],
+ severity: ["critical", "error"][i % 2],
title: `Issue ${i}`,
file: `file${i}.js`,
line: i * 10,
@@ -99,19 +131,27 @@ describe('Reviewer Agent v2 - GitHub API Integration', () => {
expect(comment.length).toBeGreaterThan(0);
});
- test('should handle auth failure gracefully', () => {
+ test("should handle auth failure gracefully", () => {
const findings = [];
const decisions = engine.process(findings);
const comment = generator.generate(decisions);
// Should still generate valid output
expect(comment).toBeDefined();
- expect(typeof comment).toBe('string');
+ expect(typeof comment).toBe("string");
});
- test('should handle network timeout scenario', () => {
+ test("should handle network timeout scenario", () => {
const findings = [
- { id: '1', tool: 'coderabbit', severity: 'critical', category: 'security', suggestion: 'Issue', file: 'a.js', line: 1 },
+ {
+ id: "1",
+ tool: "coderabbit",
+ severity: "critical",
+ category: "security",
+ suggestion: "Issue",
+ file: "a.js",
+ line: 1,
+ },
];
const decisions = engine.process(findings);
@@ -121,7 +161,7 @@ describe('Reviewer Agent v2 - GitHub API Integration', () => {
expect(comment).toBeDefined();
});
- test('should comment format validation with tools property', () => {
+ test("should comment format validation with tools property", () => {
const normalized = processor.process(mixedFeedback);
const decisions = engine.process(normalized.findings || []);
const comment = generator.generate(decisions);
@@ -129,10 +169,26 @@ describe('Reviewer Agent v2 - GitHub API Integration', () => {
expect(comment).toBeDefined();
});
- test('should preserve tool context in comment', () => {
+ test("should preserve tool context in comment", () => {
const findings = [
- { id: '1', tool: 'coderabbit', severity: 'critical', category: 'security', suggestion: 'Security fix needed', file: 'a.js', line: 1 },
- { id: '2', tool: 'copilot', severity: 'major', category: 'logic', suggestion: 'Logic fix needed', file: 'b.js', line: 2 },
+ {
+ id: "1",
+ tool: "coderabbit",
+ severity: "critical",
+ category: "security",
+ suggestion: "Security fix needed",
+ file: "a.js",
+ line: 1,
+ },
+ {
+ id: "2",
+ tool: "copilot",
+ severity: "major",
+ category: "logic",
+ suggestion: "Logic fix needed",
+ file: "b.js",
+ line: 2,
+ },
];
const decisions = engine.process(findings);
@@ -143,11 +199,35 @@ describe('Reviewer Agent v2 - GitHub API Integration', () => {
expect(comment.length).toBeGreaterThan(0);
});
- test('should handle mixed severity comment generation', () => {
+ test("should handle mixed severity comment generation", () => {
const findings = [
- { id: '1', tool: 'coderabbit', severity: 'critical', category: 'security', suggestion: 'Critical', file: 'a.js', line: 1 },
- { id: '2', tool: 'copilot', severity: 'major', category: 'logic', suggestion: 'Major', file: 'b.js', line: 2 },
- { id: '3', tool: 'code-quality', severity: 'minor', category: 'style', suggestion: 'Minor', file: 'c.js', line: 3 },
+ {
+ id: "1",
+ tool: "coderabbit",
+ severity: "critical",
+ category: "security",
+ suggestion: "Critical",
+ file: "a.js",
+ line: 1,
+ },
+ {
+ id: "2",
+ tool: "copilot",
+ severity: "major",
+ category: "logic",
+ suggestion: "Major",
+ file: "b.js",
+ line: 2,
+ },
+ {
+ id: "3",
+ tool: "code-quality",
+ severity: "minor",
+ category: "style",
+ suggestion: "Minor",
+ file: "c.js",
+ line: 3,
+ },
];
const decisions = engine.process(findings);
@@ -156,9 +236,17 @@ describe('Reviewer Agent v2 - GitHub API Integration', () => {
expect(comment).toBeDefined();
});
- test('should sanitize comment content', () => {
+ test("should sanitize comment content", () => {
const findings = [
- { id: '1', tool: 'coderabbit', severity: 'critical', category: 'security', suggestion: '', file: 'a.js', line: 1 },
+ {
+ id: "1",
+ tool: "coderabbit",
+ severity: "critical",
+ category: "security",
+ suggestion: '',
+ file: "a.js",
+ line: 1,
+ },
];
const decisions = engine.process(findings);
@@ -166,12 +254,20 @@ describe('Reviewer Agent v2 - GitHub API Integration', () => {
expect(comment).toBeDefined();
// Comment should handle potentially unsafe content
- expect(typeof comment).toBe('string');
+ expect(typeof comment).toBe("string");
});
- test('should handle emoji and special characters in comment', () => {
+ test("should handle emoji and special characters in comment", () => {
const findings = [
- { id: '1', tool: 'coderabbit', severity: 'critical', category: 'security', suggestion: '๐ Security: Fix injection โ
', file: 'a.js', line: 1 },
+ {
+ id: "1",
+ tool: "coderabbit",
+ severity: "critical",
+ category: "security",
+ suggestion: "๐ Security: Fix injection โ
",
+ file: "a.js",
+ line: 1,
+ },
];
const decisions = engine.process(findings);
diff --git a/scripts/agents/includes/reviewer-v2/__tests__/integration/multi-tool-coordination.integration.test.js b/scripts/agents/includes/reviewer-v2/__tests__/integration/multi-tool-coordination.integration.test.js
index ed0565f01..f54154c0a 100644
--- a/scripts/agents/includes/reviewer-v2/__tests__/integration/multi-tool-coordination.integration.test.js
+++ b/scripts/agents/includes/reviewer-v2/__tests__/integration/multi-tool-coordination.integration.test.js
@@ -3,16 +3,16 @@
* Tests all 4 feedback tools working together through full pipeline
*/
-const { FeedbackProcessor } = require('../../feedback-processor');
-const { DecisionEngine } = require('../../decision-engine');
-const { CommentGenerator } = require('../../comment-generator');
-const mixedFeedback = require('../fixtures/mixed-feedback-batch.json');
-const coderabbitFindings = require('../fixtures/coderabbit-findings.json');
-const codeQualityFindings = require('../fixtures/code-quality-findings.json');
-const copilotFindings = require('../fixtures/copilot-findings.json');
-const wordPressFindings = require('../fixtures/wordpress-quality-findings.json');
-
-describe('Reviewer Agent v2 - Multi-Tool Coordination', () => {
+const { FeedbackProcessor } = require("../../feedback-processor");
+const { DecisionEngine } = require("../../decision-engine");
+const { CommentGenerator } = require("../../comment-generator");
+const mixedFeedback = require("../fixtures/mixed-feedback-batch.json");
+const coderabbitFindings = require("../fixtures/coderabbit-findings.json");
+const codeQualityFindings = require("../fixtures/code-quality-findings.json");
+const copilotFindings = require("../fixtures/copilot-findings.json");
+const wordPressFindings = require("../fixtures/wordpress-quality-findings.json");
+
+describe("Reviewer Agent v2 - Multi-Tool Coordination", () => {
let processor;
let engine;
let generator;
@@ -23,76 +23,82 @@ describe('Reviewer Agent v2 - Multi-Tool Coordination', () => {
generator = new CommentGenerator();
});
- test('should process all 4 tools through pipeline', () => {
+ test("should process all 4 tools through pipeline", () => {
const normalized = processor.process(mixedFeedback);
expect(normalized.findings).toBeDefined();
expect(normalized.findings.length).toBeGreaterThan(0);
// Should have findings from multiple tools
- const tools = new Set(normalized.findings.map(f => f.tool));
+ const tools = new Set(normalized.findings.map((f) => f.tool));
expect(tools.size).toBeGreaterThan(1);
});
- test('should handle CodeRabbit findings', () => {
+ test("should handle CodeRabbit findings", () => {
const feedback = { coderabbit: coderabbitFindings.findings };
const normalized = processor.process(feedback);
expect(normalized.findings.length).toBeGreaterThan(0);
- expect(normalized.findings.every(f => f.tool === 'coderabbit')).toBe(true);
+ expect(normalized.findings.every((f) => f.tool === "coderabbit")).toBe(
+ true,
+ );
});
- test('should handle Code Quality findings', () => {
+ test("should handle Code Quality findings", () => {
const feedback = { codeQuality: codeQualityFindings.findings };
const normalized = processor.process(feedback);
expect(normalized.findings.length).toBeGreaterThan(0);
- expect(normalized.findings.every(f => f.tool === 'code-quality')).toBe(true);
+ expect(normalized.findings.every((f) => f.tool === "code-quality")).toBe(
+ true,
+ );
});
- test('should handle Copilot findings', () => {
+ test("should handle Copilot findings", () => {
const feedback = { copilot: copilotFindings.findings };
const normalized = processor.process(feedback);
expect(normalized.findings.length).toBeGreaterThan(0);
- expect(normalized.findings.every(f => f.tool === 'copilot')).toBe(true);
+ expect(normalized.findings.every((f) => f.tool === "copilot")).toBe(true);
});
- test('should handle WordPress Quality findings', () => {
+ test("should handle WordPress Quality findings", () => {
const feedback = { wordPressQuality: wordPressFindings.findings };
const normalized = processor.process(feedback);
expect(normalized.findings.length).toBeGreaterThan(0);
- expect(normalized.findings.every(f => f.tool === 'wordpress-quality')).toBe(true);
+ expect(
+ normalized.findings.every((f) => f.tool === "wordpress-quality"),
+ ).toBe(true);
});
- test('should deduplicate findings across tools', () => {
+ test("should deduplicate findings across tools", () => {
const duplicateFeedback = {
coderabbit: [
{
- severity: 'critical',
- title: 'SQL Injection',
- file: 'db.js',
+ severity: "critical",
+ title: "SQL Injection",
+ file: "db.js",
line: 42,
- description: 'Injection vulnerability',
+ description: "Injection vulnerability",
},
{
- severity: 'critical',
- title: 'SQL Injection',
- file: 'db.js',
+ severity: "critical",
+ title: "SQL Injection",
+ file: "db.js",
line: 42,
- description: 'Injection vulnerability',
+ description: "Injection vulnerability",
},
],
};
const normalized = processor.process(duplicateFeedback);
- const uniqueIds = new Set(normalized.findings.map(f => f.id));
+ const uniqueIds = new Set(normalized.findings.map((f) => f.id));
expect(uniqueIds.size).toBeLessThan(normalized.findings.length + 1);
});
- test('should respect tool priority ordering', () => {
+ test("should respect tool priority ordering", () => {
const decisions = engine.process(mixedFeedback.coderabbit);
expect(decisions).toBeDefined();
@@ -100,24 +106,24 @@ describe('Reviewer Agent v2 - Multi-Tool Coordination', () => {
expect(Array.isArray(decisions.requires_review)).toBe(true);
});
- test('should handle conflicting recommendations', () => {
+ test("should handle conflicting recommendations", () => {
const conflictingFeedback = {
coderabbit: [
{
- severity: 'critical',
- title: 'Security Issue',
- file: 'auth.js',
+ severity: "critical",
+ title: "Security Issue",
+ file: "auth.js",
line: 20,
- description: 'Remove this implementation',
+ description: "Remove this implementation",
},
],
copilot: [
{
- severity: 'note',
- title: 'Refactoring Suggestion',
- file: 'auth.js',
+ severity: "note",
+ title: "Refactoring Suggestion",
+ file: "auth.js",
line: 20,
- description: 'Simplify this code',
+ description: "Simplify this code",
},
],
};
@@ -126,42 +132,66 @@ describe('Reviewer Agent v2 - Multi-Tool Coordination', () => {
expect(normalized.findings.length).toBeGreaterThan(0);
});
- test('should generate comment with multiple tool findings', () => {
+ test("should generate comment with multiple tool findings", () => {
const normalized = processor.process(mixedFeedback);
const decisions = engine.process(normalized.findings || []);
const comment = generator.generate(decisions);
expect(comment).toBeDefined();
- expect(typeof comment).toBe('string');
+ expect(typeof comment).toBe("string");
expect(comment.length).toBeGreaterThan(0);
});
- test('should prioritize by severity across tools', () => {
+ test("should prioritize by severity across tools", () => {
const normalized = processor.process(mixedFeedback);
const decisions = engine.process(normalized.findings || []);
const requiresReview = decisions.requires_review || [];
- const critical = requiresReview.filter(f => f.severity === 'critical');
- const high = requiresReview.filter(f => f.severity === 'major' || f.severity === 'high');
+ const critical = requiresReview.filter((f) => f.severity === "critical");
+ const high = requiresReview.filter(
+ (f) => f.severity === "major" || f.severity === "high",
+ );
// Critical should come before high
if (critical.length > 0 && high.length > 0) {
- const criticalIndex = requiresReview.findIndex(f => f.severity === 'critical');
- const highIndex = requiresReview.findIndex(f => f.severity === 'major' || f.severity === 'high');
+ const criticalIndex = requiresReview.findIndex(
+ (f) => f.severity === "critical",
+ );
+ const highIndex = requiresReview.findIndex(
+ (f) => f.severity === "major" || f.severity === "high",
+ );
expect(criticalIndex).toBeLessThanOrEqual(highIndex);
}
});
- test('should handle mixed severity levels from all tools', () => {
+ test("should handle mixed severity levels from all tools", () => {
const feedback = {
coderabbit: [
- { severity: 'critical', title: 'Critical issue', file: 'a.js', line: 1, description: 'Test' },
+ {
+ severity: "critical",
+ title: "Critical issue",
+ file: "a.js",
+ line: 1,
+ description: "Test",
+ },
],
codeQuality: [
- { severity: 'warning', title: 'Warning', file: 'b.js', line: 2, description: 'Test' },
+ {
+ severity: "warning",
+ title: "Warning",
+ file: "b.js",
+ line: 2,
+ description: "Test",
+ },
],
copilot: [
- { severity: 'info', title: 'Info', file: 'c.js', line: 3, description: 'Test' },
+ {
+ severity: "info",
+ title: "Info",
+ file: "c.js",
+ line: 3,
+ description: "Test",
+ },
],
};
@@ -169,11 +199,11 @@ describe('Reviewer Agent v2 - Multi-Tool Coordination', () => {
expect(normalized.findings.length).toBe(3);
});
- test('should preserve tool source information', () => {
+ test("should preserve tool source information", () => {
const normalized = processor.process(mixedFeedback);
const tools = new Set();
- normalized.findings.forEach(f => {
+ normalized.findings.forEach((f) => {
expect(f.tool).toBeDefined();
tools.add(f.tool);
});
@@ -181,7 +211,7 @@ describe('Reviewer Agent v2 - Multi-Tool Coordination', () => {
expect(tools.size).toBeGreaterThan(0);
});
- test('should handle empty feedback from some tools', () => {
+ test("should handle empty feedback from some tools", () => {
const partialFeedback = {
coderabbit: coderabbitFindings.findings,
codeQuality: [],
@@ -191,6 +221,8 @@ describe('Reviewer Agent v2 - Multi-Tool Coordination', () => {
const normalized = processor.process(partialFeedback);
expect(normalized.findings.length).toBeGreaterThan(0);
- expect(normalized.findings.every(f => f.tool === 'coderabbit')).toBe(true);
+ expect(normalized.findings.every((f) => f.tool === "coderabbit")).toBe(
+ true,
+ );
});
});
diff --git a/scripts/agents/includes/reviewer-v2/__tests__/integration/performance-baselines.integration.test.js b/scripts/agents/includes/reviewer-v2/__tests__/integration/performance-baselines.integration.test.js
index b4f7a2b4e..d6d6af1f6 100644
--- a/scripts/agents/includes/reviewer-v2/__tests__/integration/performance-baselines.integration.test.js
+++ b/scripts/agents/includes/reviewer-v2/__tests__/integration/performance-baselines.integration.test.js
@@ -3,13 +3,13 @@
* Establishes and validates performance metrics for the review pipeline
*/
-const { FeedbackProcessor } = require('../../feedback-processor');
-const { DecisionEngine } = require('../../decision-engine');
-const { CommentGenerator } = require('../../comment-generator');
-const { ConfigurationSystem } = require('../../configuration-system');
-const mixedFeedback = require('../fixtures/mixed-feedback-batch.json');
+const { FeedbackProcessor } = require("../../feedback-processor");
+const { DecisionEngine } = require("../../decision-engine");
+const { CommentGenerator } = require("../../comment-generator");
+const { ConfigurationSystem } = require("../../configuration-system");
+const mixedFeedback = require("../fixtures/mixed-feedback-batch.json");
-describe('Reviewer Agent v2 - Performance Baselines', () => {
+describe("Reviewer Agent v2 - Performance Baselines", () => {
let processor;
let engine;
let generator;
@@ -24,7 +24,7 @@ describe('Reviewer Agent v2 - Performance Baselines', () => {
config = new ConfigurationSystem();
});
- const processWorkflow = (feedback, repoType = 'github') => {
+ const processWorkflow = (feedback, repoType = "github") => {
const normalized = processor.process(feedback);
const decisions = engine.process(normalized.findings || []);
const comment = generator.generate(decisions);
@@ -38,26 +38,26 @@ describe('Reviewer Agent v2 - Performance Baselines', () => {
};
};
- test('should process small feedback batch within timeout', () => {
+ test("should process small feedback batch within timeout", () => {
const start = Date.now();
- const result = processWorkflow(mixedFeedback, 'github');
+ const result = processWorkflow(mixedFeedback, "github");
const duration = Date.now() - start;
expect(result).toBeDefined();
expect(duration).toBeLessThan(PERF_TIMEOUT);
});
- test('should process medium feedback batch (50 findings) within timeout', () => {
+ test("should process medium feedback batch (50 findings) within timeout", () => {
const mediumFeedback = {
coderabbit: Array.from({ length: 25 }, (_, i) => ({
- severity: ['critical', 'error'][i % 2],
+ severity: ["critical", "error"][i % 2],
title: `Issue ${i}`,
file: `file${i}.js`,
line: i * 10,
description: `Description ${i}`,
})),
codeQuality: Array.from({ length: 25 }, (_, i) => ({
- severity: ['warning', 'note'][i % 2],
+ severity: ["warning", "note"][i % 2],
title: `Quality Issue ${i}`,
file: `quality${i}.js`,
line: i * 5,
@@ -66,31 +66,31 @@ describe('Reviewer Agent v2 - Performance Baselines', () => {
};
const start = Date.now();
- const result = processWorkflow(mediumFeedback, 'github');
+ const result = processWorkflow(mediumFeedback, "github");
const duration = Date.now() - start;
expect(result.findings.length).toBeGreaterThanOrEqual(50);
expect(duration).toBeLessThan(PERF_TIMEOUT);
});
- test('should process large feedback batch (100+ findings) within timeout', () => {
+ test("should process large feedback batch (100+ findings) within timeout", () => {
const largeFeedback = {
coderabbit: Array.from({ length: 50 }, (_, i) => ({
- severity: ['critical', 'error'][i % 2],
+ severity: ["critical", "error"][i % 2],
title: `Issue ${i}`,
file: `file${i}.js`,
line: i * 10,
description: `Description ${i}`,
})),
codeQuality: Array.from({ length: 30 }, (_, i) => ({
- severity: ['warning', 'note'][i % 2],
+ severity: ["warning", "note"][i % 2],
title: `Quality Issue ${i}`,
file: `quality${i}.js`,
line: i * 5,
description: `Quality Description ${i}`,
})),
copilot: Array.from({ length: 20 }, (_, i) => ({
- severity: ['info', 'note'][i % 2],
+ severity: ["info", "note"][i % 2],
title: `Suggestion ${i}`,
file: `suggest${i}.js`,
line: i * 3,
@@ -99,18 +99,18 @@ describe('Reviewer Agent v2 - Performance Baselines', () => {
};
const start = Date.now();
- const result = processWorkflow(largeFeedback, 'github');
+ const result = processWorkflow(largeFeedback, "github");
const duration = Date.now() - start;
expect(result.findings.length).toBeGreaterThanOrEqual(100);
expect(duration).toBeLessThan(PERF_TIMEOUT);
});
- test('should process feedback with consistent performance', () => {
+ test("should process feedback with consistent performance", () => {
const iterations = 3;
for (let i = 0; i < iterations; i++) {
- const result = processWorkflow(mixedFeedback, 'github');
+ const result = processWorkflow(mixedFeedback, "github");
expect(result).toBeDefined();
expect(result.findings).toBeDefined();
}
@@ -118,12 +118,12 @@ describe('Reviewer Agent v2 - Performance Baselines', () => {
// Just verify we can process multiple times without errors
});
- test('should not accumulate memory with repeated processing', () => {
+ test("should not accumulate memory with repeated processing", () => {
const iterations = 10;
const initialMemory = process.memoryUsage().heapUsed;
for (let i = 0; i < iterations; i++) {
- processWorkflow(mixedFeedback, 'github');
+ processWorkflow(mixedFeedback, "github");
}
const finalMemory = process.memoryUsage().heapUsed;
@@ -133,30 +133,30 @@ describe('Reviewer Agent v2 - Performance Baselines', () => {
expect(memoryGrowth).toBeLessThan(MEMORY_THRESHOLD);
});
- test('should have consistent response time for different repo types', () => {
- const repoTypes = ['github', 'wordpress-plugin', 'wordpress-theme'];
+ test("should have consistent response time for different repo types", () => {
+ const repoTypes = ["github", "wordpress-plugin", "wordpress-theme"];
const durations = {};
- repoTypes.forEach(repoType => {
+ repoTypes.forEach((repoType) => {
const start = Date.now();
processWorkflow(mixedFeedback, repoType);
durations[repoType] = Date.now() - start;
});
// All repo types should complete within timeout
- Object.values(durations).forEach(duration => {
+ Object.values(durations).forEach((duration) => {
expect(duration).toBeLessThan(PERF_TIMEOUT);
});
});
- test('should scale performance linearly with feedback count', () => {
+ test("should scale performance linearly with feedback count", () => {
const sizes = [10, 25, 50];
const durations = [];
- sizes.forEach(size => {
+ sizes.forEach((size) => {
const feedback = {
coderabbit: Array.from({ length: size }, (_, i) => ({
- severity: 'critical',
+ severity: "critical",
title: `Issue ${i}`,
file: `file${i}.js`,
line: i * 10,
@@ -165,7 +165,7 @@ describe('Reviewer Agent v2 - Performance Baselines', () => {
};
const start = Date.now();
- processWorkflow(feedback, 'github');
+ processWorkflow(feedback, "github");
durations.push(Date.now() - start);
});
@@ -174,26 +174,26 @@ describe('Reviewer Agent v2 - Performance Baselines', () => {
expect(durations[durations.length - 1]).toBeLessThan(PERF_TIMEOUT);
});
- test('should maintain performance with duplicate findings', () => {
+ test("should maintain performance with duplicate findings", () => {
const duplicateFeedback = {
coderabbit: Array.from({ length: 50 }, (_, i) => ({
- severity: 'critical',
- title: 'Same Issue',
- file: 'same.js',
+ severity: "critical",
+ title: "Same Issue",
+ file: "same.js",
line: 42,
- description: 'Same description',
+ description: "Same description",
})),
};
const start = Date.now();
- const result = processWorkflow(duplicateFeedback, 'github');
+ const result = processWorkflow(duplicateFeedback, "github");
const duration = Date.now() - start;
expect(duration).toBeLessThan(PERF_TIMEOUT);
expect(result.findings.length).toBeLessThan(50); // Deduped
});
- test('should generate comments efficiently', () => {
+ test("should generate comments efficiently", () => {
const processor = new FeedbackProcessor();
const engine = new DecisionEngine();
const generator = new CommentGenerator();
@@ -209,14 +209,14 @@ describe('Reviewer Agent v2 - Performance Baselines', () => {
expect(duration).toBeLessThan(100); // Comment generation should be fast
});
- test('should handle comment generation for large datasets', () => {
+ test("should handle comment generation for large datasets", () => {
const processor = new FeedbackProcessor();
const engine = new DecisionEngine();
const generator = new CommentGenerator();
const largeFeedback = {
coderabbit: Array.from({ length: 100 }, (_, i) => ({
- severity: ['critical', 'error'][i % 2],
+ severity: ["critical", "error"][i % 2],
title: `Issue ${i}`,
file: `file${i}.js`,
line: i * 10,
@@ -235,12 +235,14 @@ describe('Reviewer Agent v2 - Performance Baselines', () => {
expect(duration).toBeLessThan(200);
});
- test('performance baseline: small batch', () => {
+ test("performance baseline: small batch", () => {
const start = Date.now();
- const result = processWorkflow(mixedFeedback, 'github');
+ const result = processWorkflow(mixedFeedback, "github");
const duration = Date.now() - start;
- console.log(`Small batch (${result.findings.length} findings): ${duration}ms`);
+ console.log(
+ `Small batch (${result.findings.length} findings): ${duration}ms`,
+ );
expect(duration).toBeLessThan(PERF_TIMEOUT);
});
});
diff --git a/scripts/agents/release.agent.js b/scripts/agents/release.agent.js
index 8cb8109ba..215ab2a80 100644
--- a/scripts/agents/release.agent.js
+++ b/scripts/agents/release.agent.js
@@ -1298,7 +1298,7 @@ async function run() {
if (!branchValidation.valid) {
throw new Error(
`Invalid release branch name "${releaseBranch}": ${branchValidation.message}. ` +
- `Check docs/BRANCHING_STRATEGY.md for valid branch naming patterns.`,
+ `Check docs/BRANCHING_STRATEGY.md for valid branch naming patterns.`,
);
}
diff --git a/scripts/metrics/__tests__/github-issue-creator.test.js b/scripts/metrics/__tests__/github-issue-creator.test.js
index ed9e6ec68..aefea80db 100644
--- a/scripts/metrics/__tests__/github-issue-creator.test.js
+++ b/scripts/metrics/__tests__/github-issue-creator.test.js
@@ -2,9 +2,9 @@
* GitHub Issue Creator Tests
*/
-const { GitHubIssueCreator } = require('../github-issue-creator');
+const { GitHubIssueCreator } = require("../github-issue-creator");
-describe('GitHubIssueCreator', () => {
+describe("GitHubIssueCreator", () => {
let issueCreator;
let mockOctokit;
@@ -23,106 +23,133 @@ describe('GitHubIssueCreator', () => {
issueCreator = new GitHubIssueCreator(mockOctokit);
});
- describe('Issue Creation', () => {
- test('should create metrics issue with correct properties', async () => {
+ describe("Issue Creation", () => {
+ test("should create metrics issue with correct properties", async () => {
const mockIssue = {
data: {
number: 123,
- title: '[Metrics] Weekly Report: 2026-08-21',
- body: 'Test report',
- labels: ['type:metrics', 'area:monitoring'],
+ title: "[Metrics] Weekly Report: 2026-08-21",
+ body: "Test report",
+ labels: ["type:metrics", "area:monitoring"],
},
};
mockOctokit.rest.issues.create.mockResolvedValue(mockIssue);
- const report = 'Test metrics report';
- const result = await issueCreator.createMetricsIssue('lightspeedwp', '.github', report);
+ const report = "Test metrics report";
+ const result = await issueCreator.createMetricsIssue(
+ "lightspeedwp",
+ ".github",
+ report,
+ );
expect(result.number).toBe(123);
expect(mockOctokit.rest.issues.create).toHaveBeenCalledWith(
expect.objectContaining({
- owner: 'lightspeedwp',
- repo: '.github',
- labels: ['type:metrics', 'area:monitoring'],
- })
+ owner: "lightspeedwp",
+ repo: ".github",
+ labels: ["type:metrics", "area:monitoring"],
+ }),
);
});
- test('should include custom labels in issue creation', async () => {
+ test("should include custom labels in issue creation", async () => {
const mockIssue = { data: { number: 124 } };
mockOctokit.rest.issues.create.mockResolvedValue(mockIssue);
- const report = 'Test report';
- const customLabels = ['urgent', 'review-needed'];
+ const report = "Test report";
+ const customLabels = ["urgent", "review-needed"];
- await issueCreator.createMetricsIssue('lightspeedwp', '.github', report, 'weekly', {
- labels: customLabels,
- });
+ await issueCreator.createMetricsIssue(
+ "lightspeedwp",
+ ".github",
+ report,
+ "weekly",
+ {
+ labels: customLabels,
+ },
+ );
expect(mockOctokit.rest.issues.create).toHaveBeenCalledWith(
expect.objectContaining({
- labels: expect.arrayContaining(['type:metrics', 'area:monitoring', ...customLabels]),
- })
+ labels: expect.arrayContaining([
+ "type:metrics",
+ "area:monitoring",
+ ...customLabels,
+ ]),
+ }),
);
});
- test('should handle issue creation errors', async () => {
- mockOctokit.rest.issues.create.mockRejectedValue(new Error('API error'));
+ test("should handle issue creation errors", async () => {
+ mockOctokit.rest.issues.create.mockRejectedValue(new Error("API error"));
- await expect(issueCreator.createMetricsIssue('lightspeedwp', '.github', 'report')).rejects.toThrow(
- 'API error'
- );
+ await expect(
+ issueCreator.createMetricsIssue("lightspeedwp", ".github", "report"),
+ ).rejects.toThrow("API error");
});
});
- describe('Weekly and Monthly Issues', () => {
- test('should create weekly issue with period label', async () => {
+ describe("Weekly and Monthly Issues", () => {
+ test("should create weekly issue with period label", async () => {
const mockIssue = { data: { number: 125 } };
mockOctokit.rest.issues.create.mockResolvedValue(mockIssue);
- await issueCreator.createWeeklyMetricsIssue('lightspeedwp', '.github', 'weekly report');
+ await issueCreator.createWeeklyMetricsIssue(
+ "lightspeedwp",
+ ".github",
+ "weekly report",
+ );
expect(mockOctokit.rest.issues.create).toHaveBeenCalledWith(
expect.objectContaining({
- labels: expect.arrayContaining(['period:weekly']),
- })
+ labels: expect.arrayContaining(["period:weekly"]),
+ }),
);
});
- test('should create monthly issue with period label', async () => {
+ test("should create monthly issue with period label", async () => {
const mockIssue = { data: { number: 126 } };
mockOctokit.rest.issues.create.mockResolvedValue(mockIssue);
- await issueCreator.createMonthlyMetricsIssue('lightspeedwp', '.github', 'monthly report');
+ await issueCreator.createMonthlyMetricsIssue(
+ "lightspeedwp",
+ ".github",
+ "monthly report",
+ );
expect(mockOctokit.rest.issues.create).toHaveBeenCalledWith(
expect.objectContaining({
- labels: expect.arrayContaining(['period:monthly']),
- })
+ labels: expect.arrayContaining(["period:monthly"]),
+ }),
);
});
});
- describe('Issue Management', () => {
- test('should fetch metrics issues', async () => {
+ describe("Issue Management", () => {
+ test("should fetch metrics issues", async () => {
const mockIssues = {
data: [
- { number: 100, title: '[Metrics] Weekly Report: 2026-08-21' },
- { number: 101, title: '[Metrics] Weekly Report: 2026-08-14' },
+ { number: 100, title: "[Metrics] Weekly Report: 2026-08-21" },
+ { number: 101, title: "[Metrics] Weekly Report: 2026-08-14" },
],
};
mockOctokit.rest.issues.listForRepo.mockResolvedValue(mockIssues);
- const issues = await issueCreator.getMetricsIssues('lightspeedwp', '.github');
+ const issues = await issueCreator.getMetricsIssues(
+ "lightspeedwp",
+ ".github",
+ );
expect(issues).toHaveLength(2);
expect(issues[0].number).toBe(100);
});
- test('should close old reports', async () => {
- const oldDate = new Date(Date.now() - 100 * 24 * 60 * 60 * 1000).toISOString();
+ test("should close old reports", async () => {
+ const oldDate = new Date(
+ Date.now() - 100 * 24 * 60 * 60 * 1000,
+ ).toISOString();
const mockIssues = {
data: [
@@ -134,106 +161,125 @@ describe('GitHubIssueCreator', () => {
mockOctokit.rest.issues.listForRepo.mockResolvedValue(mockIssues);
mockOctokit.rest.issues.update.mockResolvedValue({ data: {} });
- const result = await issueCreator.closeOldReports('lightspeedwp', '.github', 90);
+ const result = await issueCreator.closeOldReports(
+ "lightspeedwp",
+ ".github",
+ 90,
+ );
expect(result.closedCount).toBe(1);
expect(result.totalChecked).toBe(2);
expect(mockOctokit.rest.issues.update).toHaveBeenCalledWith(
expect.objectContaining({
issue_number: 50,
- state: 'closed',
- state_reason: 'not_planned',
- })
+ state: "closed",
+ state_reason: "not_planned",
+ }),
);
});
- test('should add comment to metrics issue', async () => {
- const mockComment = { data: { id: 1, body: 'Test comment' } };
+ test("should add comment to metrics issue", async () => {
+ const mockComment = { data: { id: 1, body: "Test comment" } };
mockOctokit.rest.issues.createComment.mockResolvedValue(mockComment);
- const result = await issueCreator.addCommentToMetricsIssue('lightspeedwp', '.github', 123, 'Test comment');
+ const result = await issueCreator.addCommentToMetricsIssue(
+ "lightspeedwp",
+ ".github",
+ 123,
+ "Test comment",
+ );
expect(result.id).toBe(1);
expect(mockOctokit.rest.issues.createComment).toHaveBeenCalledWith(
expect.objectContaining({
issue_number: 123,
- body: 'Test comment',
- })
+ body: "Test comment",
+ }),
);
});
});
- describe('Report Existence Check', () => {
- test('should detect existing report for date', async () => {
+ describe("Report Existence Check", () => {
+ test("should detect existing report for date", async () => {
const mockIssues = {
data: [
- { title: '[Metrics] Weekly Report: 2026-08-21' },
- { title: '[Metrics] Weekly Report: 2026-08-14' },
+ { title: "[Metrics] Weekly Report: 2026-08-21" },
+ { title: "[Metrics] Weekly Report: 2026-08-14" },
],
};
mockOctokit.rest.issues.listForRepo.mockResolvedValue(mockIssues);
- const testDate = new Date('2026-08-21');
- const exists = await issueCreator.reportExistsForDate('lightspeedwp', '.github', testDate);
+ const testDate = new Date("2026-08-21");
+ const exists = await issueCreator.reportExistsForDate(
+ "lightspeedwp",
+ ".github",
+ testDate,
+ );
expect(exists).toBe(true);
});
- test('should detect missing report for date', async () => {
+ test("should detect missing report for date", async () => {
const mockIssues = { data: [] };
mockOctokit.rest.issues.listForRepo.mockResolvedValue(mockIssues);
- const testDate = new Date('2026-08-21');
- const exists = await issueCreator.reportExistsForDate('lightspeedwp', '.github', testDate);
+ const testDate = new Date("2026-08-21");
+ const exists = await issueCreator.reportExistsForDate(
+ "lightspeedwp",
+ ".github",
+ testDate,
+ );
expect(exists).toBe(false);
});
});
- describe('Template Generation', () => {
- test('should generate issue template', () => {
- const template = issueCreator.generateIssueTemplate('Test report data');
+ describe("Template Generation", () => {
+ test("should generate issue template", () => {
+ const template = issueCreator.generateIssueTemplate("Test report data");
- expect(template).toContain('Metrics Report');
- expect(template).toContain('Test report data');
- expect(template).toContain('Metadata');
- expect(template).toContain('Auto-generated');
+ expect(template).toContain("Metrics Report");
+ expect(template).toContain("Test report data");
+ expect(template).toContain("Metadata");
+ expect(template).toContain("Auto-generated");
});
});
- describe('Retry Logic', () => {
- test('should retry on failure', async () => {
+ describe("Retry Logic", () => {
+ test("should retry on failure", async () => {
const mockIssue = { data: { number: 150 } };
mockOctokit.rest.issues.create
- .mockRejectedValueOnce(new Error('Temporary error'))
+ .mockRejectedValueOnce(new Error("Temporary error"))
.mockResolvedValueOnce(mockIssue);
const result = await issueCreator.createMetricsIssueWithRetry(
- 'lightspeedwp',
- '.github',
- 'report',
- 'weekly',
- 3
+ "lightspeedwp",
+ ".github",
+ "report",
+ "weekly",
+ 3,
);
expect(result.number).toBe(150);
expect(mockOctokit.rest.issues.create).toHaveBeenCalledTimes(2);
});
- test('should fail after max retries', async () => {
- mockOctokit.rest.issues.create.mockRejectedValue(new Error('Persistent error'));
+ test("should fail after max retries", async () => {
+ mockOctokit.rest.issues.create.mockRejectedValue(
+ new Error("Persistent error"),
+ );
await expect(
issueCreator.createMetricsIssueWithRetry(
- 'lightspeedwp',
- '.github',
- 'report',
- 'weekly',
- 2
- )
- ).rejects.toThrow('Failed to create metrics issue after 2 attempts');
+ "lightspeedwp",
+ ".github",
+ "report",
+ "weekly",
+ 2,
+ ),
+ ).rejects.toThrow("Failed to create metrics issue after 2 attempts");
});
});
});
diff --git a/scripts/metrics/__tests__/integration.test.js b/scripts/metrics/__tests__/integration.test.js
index 7ce12359b..4b92d1d55 100644
--- a/scripts/metrics/__tests__/integration.test.js
+++ b/scripts/metrics/__tests__/integration.test.js
@@ -3,11 +3,11 @@
* Tests the complete workflow: Collection โ Storage โ Analysis โ Reporting
*/
-const fs = require('fs');
-const path = require('path');
+const fs = require("fs");
+const path = require("path");
-describe('Metrics Agent Phase 2 - Integration Tests', () => {
- const testDataDir = path.join(__dirname, './__integration-data__');
+describe("Metrics Agent Phase 2 - Integration Tests", () => {
+ const testDataDir = path.join(__dirname, "./__integration-data__");
beforeAll(() => {
// Create test data directory
@@ -23,13 +23,13 @@ describe('Metrics Agent Phase 2 - Integration Tests', () => {
}
});
- describe('Complete Workflow: Collection โ Storage โ Analysis โ Reporting', () => {
- test('should complete full metrics collection pipeline', async () => {
+ describe("Complete Workflow: Collection โ Storage โ Analysis โ Reporting", () => {
+ test("should complete full metrics collection pipeline", async () => {
// Simulate Task 2.3: Collection
const mockMetrics = {
- repository: 'lightspeedwp/.github',
+ repository: "lightspeedwp/.github",
timestamp: new Date().toISOString(),
- context: 'github-control-plane',
+ context: "github-control-plane",
collectionTime: 2500,
issues: { total: 42, closed: 35, open: 7 },
pullRequests: { total: 28, merged: 26, open: 2 },
@@ -44,37 +44,39 @@ describe('Metrics Agent Phase 2 - Integration Tests', () => {
expect(mockMetrics.timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T/);
});
- test('should persist metrics through time-series storage', async () => {
+ test("should persist metrics through time-series storage", async () => {
const metrics = {
- repository: 'lightspeedwp/.github',
+ repository: "lightspeedwp/.github",
timestamp: new Date().toISOString(),
issues: { total: 42, closed: 35, open: 7 },
pullRequests: { total: 28, merged: 26, open: 2 },
};
- const storageFile = path.join(testDataDir, 'time-series.json');
+ const storageFile = path.join(testDataDir, "time-series.json");
// Simulate storage write
const storage = {};
- storage['lightspeedwp/.github'] = [metrics];
+ storage["lightspeedwp/.github"] = [metrics];
fs.writeFileSync(storageFile, JSON.stringify(storage, null, 2));
// Verify persistence
- const savedData = JSON.parse(fs.readFileSync(storageFile, 'utf8'));
- expect(savedData['lightspeedwp/.github']).toHaveLength(1);
- expect(savedData['lightspeedwp/.github'][0].repository).toBe('lightspeedwp/.github');
+ const savedData = JSON.parse(fs.readFileSync(storageFile, "utf8"));
+ expect(savedData["lightspeedwp/.github"]).toHaveLength(1);
+ expect(savedData["lightspeedwp/.github"][0].repository).toBe(
+ "lightspeedwp/.github",
+ );
});
- test('should analyze trends from historical data', async () => {
+ test("should analyze trends from historical data", async () => {
// Simulate historical data
const history = [
{
- timestamp: '2026-08-14',
+ timestamp: "2026-08-14",
issues: { total: 40, closed: 32, open: 8 },
pullRequests: { total: 25, merged: 23, open: 2 },
},
{
- timestamp: '2026-08-21',
+ timestamp: "2026-08-21",
issues: { total: 42, closed: 35, open: 7 },
pullRequests: { total: 28, merged: 26, open: 2 },
},
@@ -82,13 +84,14 @@ describe('Metrics Agent Phase 2 - Integration Tests', () => {
// Calculate trends
const trendIssues = history[1].issues.total - history[0].issues.total; // +2
- const trendPRs = history[1].pullRequests.total - history[0].pullRequests.total; // +3
+ const trendPRs =
+ history[1].pullRequests.total - history[0].pullRequests.total; // +3
expect(trendIssues).toBe(2);
expect(trendPRs).toBe(3);
});
- test('should detect anomalies in metrics', async () => {
+ test("should detect anomalies in metrics", async () => {
const baseline = {
issues: { closureRate: 0.8 },
pullRequests: { reviewTime: 4 },
@@ -103,25 +106,28 @@ describe('Metrics Agent Phase 2 - Integration Tests', () => {
if (current.issues.closureRate < baseline.issues.closureRate * 0.85) {
anomalies.push({
- type: 'Issue Closure Rate Drop',
- severity: 'high',
+ type: "Issue Closure Rate Drop",
+ severity: "high",
});
}
- if (current.pullRequests.reviewTime > baseline.pullRequests.reviewTime * 1.25) {
+ if (
+ current.pullRequests.reviewTime >
+ baseline.pullRequests.reviewTime * 1.25
+ ) {
anomalies.push({
- type: 'PR Review Time Increase',
- severity: 'medium',
+ type: "PR Review Time Increase",
+ severity: "medium",
});
}
expect(anomalies).toHaveLength(2);
- expect(anomalies[0].type).toBe('Issue Closure Rate Drop');
+ expect(anomalies[0].type).toBe("Issue Closure Rate Drop");
});
- test('should generate markdown report from metrics', async () => {
+ test("should generate markdown report from metrics", async () => {
const metrics = {
- repository: 'lightspeedwp/.github',
+ repository: "lightspeedwp/.github",
timestamp: new Date().toISOString(),
issues: { total: 42, closed: 35, open: 7 },
pullRequests: { total: 28, merged: 26, open: 2 },
@@ -146,32 +152,32 @@ describe('Metrics Agent Phase 2 - Integration Tests', () => {
| Merged | ${metrics.pullRequests.merged} |
| Merge Rate | ${((metrics.pullRequests.merged / metrics.pullRequests.total) * 100).toFixed(1)}% |`;
- expect(report).toContain('Metrics Report');
- expect(report).toContain('lightspeedwp/.github');
- expect(report).toContain('Issues');
- expect(report).toContain('Pull Requests');
- expect(report).toContain('83.3%');
+ expect(report).toContain("Metrics Report");
+ expect(report).toContain("lightspeedwp/.github");
+ expect(report).toContain("Issues");
+ expect(report).toContain("Pull Requests");
+ expect(report).toContain("83.3%");
});
- test('should create GitHub issue with report', async () => {
+ test("should create GitHub issue with report", async () => {
const mockIssue = {
number: 123,
- title: '[Metrics] Weekly Report: 2026-08-21',
- body: '# Test Report',
- labels: ['type:metrics', 'area:monitoring'],
+ title: "[Metrics] Weekly Report: 2026-08-21",
+ body: "# Test Report",
+ labels: ["type:metrics", "area:monitoring"],
};
expect(mockIssue.number).toBeDefined();
expect(mockIssue.title).toMatch(/\[Metrics\]/);
- expect(mockIssue.labels).toContain('type:metrics');
+ expect(mockIssue.labels).toContain("type:metrics");
});
});
- describe('Data Consistency Across Components', () => {
- test('should maintain data integrity through pipeline', async () => {
+ describe("Data Consistency Across Components", () => {
+ test("should maintain data integrity through pipeline", async () => {
const original = {
- repository: 'lightspeedwp/.github',
- timestamp: '2026-08-21T02:00:00.000Z',
+ repository: "lightspeedwp/.github",
+ timestamp: "2026-08-21T02:00:00.000Z",
issues: { total: 42, closed: 35, open: 7 },
};
@@ -183,28 +189,34 @@ describe('Metrics Agent Phase 2 - Integration Tests', () => {
expect(stored.issues.total).toBe(42);
});
- test('should correlate metrics across repositories', async () => {
+ test("should correlate metrics across repositories", async () => {
const repos = [
{
- name: 'lightspeedwp/.github',
+ name: "lightspeedwp/.github",
metrics: { issues: { total: 42 }, pullRequests: { total: 28 } },
},
{
- name: 'lightspeedwp/plugin',
+ name: "lightspeedwp/plugin",
metrics: { issues: { total: 15 }, pullRequests: { total: 8 } },
},
];
- const totalIssues = repos.reduce((sum, r) => sum + r.metrics.issues.total, 0);
- const totalPRs = repos.reduce((sum, r) => sum + r.metrics.pullRequests.total, 0);
+ const totalIssues = repos.reduce(
+ (sum, r) => sum + r.metrics.issues.total,
+ 0,
+ );
+ const totalPRs = repos.reduce(
+ (sum, r) => sum + r.metrics.pullRequests.total,
+ 0,
+ );
expect(totalIssues).toBe(57);
expect(totalPRs).toBe(36);
});
});
- describe('Error Recovery & Resilience', () => {
- test('should handle missing metrics gracefully', async () => {
+ describe("Error Recovery & Resilience", () => {
+ test("should handle missing metrics gracefully", async () => {
const mockMetrics = null;
if (!mockMetrics) {
@@ -213,15 +225,19 @@ describe('Metrics Agent Phase 2 - Integration Tests', () => {
}
});
- test('should continue after single repository failure', async () => {
+ test("should continue after single repository failure", async () => {
const repositories = [
- { name: 'repo1', status: 'success' },
- { name: 'repo2', status: 'error', error: 'API rate limit' },
- { name: 'repo3', status: 'success' },
+ { name: "repo1", status: "success" },
+ { name: "repo2", status: "error", error: "API rate limit" },
+ { name: "repo3", status: "success" },
];
- const successCount = repositories.filter((r) => r.status === 'success').length;
- const errorCount = repositories.filter((r) => r.status === 'error').length;
+ const successCount = repositories.filter(
+ (r) => r.status === "success",
+ ).length;
+ const errorCount = repositories.filter(
+ (r) => r.status === "error",
+ ).length;
expect(successCount).toBe(2);
expect(errorCount).toBe(1);
@@ -229,9 +245,9 @@ describe('Metrics Agent Phase 2 - Integration Tests', () => {
expect(successCount > 0).toBe(true);
});
- test('should validate metrics structure before processing', async () => {
+ test("should validate metrics structure before processing", async () => {
const validMetrics = {
- repository: 'lightspeedwp/.github',
+ repository: "lightspeedwp/.github",
timestamp: new Date().toISOString(),
issues: { total: 42, closed: 35, open: 7 },
};
@@ -249,26 +265,26 @@ describe('Metrics Agent Phase 2 - Integration Tests', () => {
});
});
- describe('Concurrent Operations', () => {
- test('should handle concurrent report generation', async () => {
- const repositories = ['repo1', 'repo2', 'repo3', 'repo4'];
+ describe("Concurrent Operations", () => {
+ test("should handle concurrent report generation", async () => {
+ const repositories = ["repo1", "repo2", "repo3", "repo4"];
// Simulate concurrent processing
const results = await Promise.allSettled(
repositories.map((repo) =>
Promise.resolve({
repository: repo,
- status: 'success',
+ status: "success",
reportPath: `/reports/${repo}.md`,
- })
- )
+ }),
+ ),
);
- const fulfilled = results.filter((r) => r.status === 'fulfilled');
+ const fulfilled = results.filter((r) => r.status === "fulfilled");
expect(fulfilled).toHaveLength(4);
});
- test('should prevent race conditions in storage writes', async () => {
+ test("should prevent race conditions in storage writes", async () => {
const storage = {};
let writeCount = 0;
@@ -281,37 +297,37 @@ describe('Metrics Agent Phase 2 - Integration Tests', () => {
writeCount++;
};
- writeMetrics('repo1', { timestamp: '2026-08-21' });
- writeMetrics('repo1', { timestamp: '2026-08-22' });
+ writeMetrics("repo1", { timestamp: "2026-08-21" });
+ writeMetrics("repo1", { timestamp: "2026-08-22" });
- expect(storage['repo1']).toHaveLength(2);
+ expect(storage["repo1"]).toHaveLength(2);
expect(writeCount).toBe(2);
});
});
- describe('Workflow Scheduling & Triggers', () => {
- test('should support scheduled execution (cron)', () => {
- const cronExpression = '0 2 * * *'; // 2 AM daily
- const parts = cronExpression.split(' ');
+ describe("Workflow Scheduling & Triggers", () => {
+ test("should support scheduled execution (cron)", () => {
+ const cronExpression = "0 2 * * *"; // 2 AM daily
+ const parts = cronExpression.split(" ");
expect(parts).toHaveLength(5);
- expect(parts[0]).toBe('0'); // minute
- expect(parts[1]).toBe('2'); // hour
+ expect(parts[0]).toBe("0"); // minute
+ expect(parts[1]).toBe("2"); // hour
});
- test('should support manual trigger with options', () => {
+ test("should support manual trigger with options", () => {
const trigger = {
- reportType: 'weekly',
+ reportType: "weekly",
includeArchive: false,
};
- expect(trigger.reportType).toBe('weekly');
+ expect(trigger.reportType).toBe("weekly");
expect(trigger.includeArchive).toBe(false);
});
});
- describe('Performance Characteristics', () => {
- test('single repository collection should complete efficiently', async () => {
+ describe("Performance Characteristics", () => {
+ test("single repository collection should complete efficiently", async () => {
const startTime = Date.now();
// Simulate collection
@@ -323,7 +339,7 @@ describe('Metrics Agent Phase 2 - Integration Tests', () => {
expect(elapsed).toBeLessThan(1000); // Should be under 1 second in practice
});
- test('report generation should be fast', async () => {
+ test("report generation should be fast", async () => {
const startTime = Date.now();
// Simulate report generation (report not used in simple performance test)
diff --git a/scripts/metrics/__tests__/metrics-reporter.test.js b/scripts/metrics/__tests__/metrics-reporter.test.js
index fe82bfcfc..b9f8ba7d0 100644
--- a/scripts/metrics/__tests__/metrics-reporter.test.js
+++ b/scripts/metrics/__tests__/metrics-reporter.test.js
@@ -2,9 +2,9 @@
* Metrics Reporter Tests
*/
-const { MetricsReporter } = require('../metrics-reporter');
+const { MetricsReporter } = require("../metrics-reporter");
-describe('MetricsReporter', () => {
+describe("MetricsReporter", () => {
let reporter;
let mockStorage;
let mockTrendAnalyzer;
@@ -24,13 +24,17 @@ describe('MetricsReporter', () => {
detectAnomalies: jest.fn(),
};
- reporter = new MetricsReporter(mockStorage, mockTrendAnalyzer, mockAnomalyDetector);
+ reporter = new MetricsReporter(
+ mockStorage,
+ mockTrendAnalyzer,
+ mockAnomalyDetector,
+ );
});
- describe('Report Generation', () => {
- test('should generate report with valid metrics', async () => {
+ describe("Report Generation", () => {
+ test("should generate report with valid metrics", async () => {
const mockMetrics = {
- repository: 'lightspeedwp/.github',
+ repository: "lightspeedwp/.github",
timestamp: new Date().toISOString(),
issues: { total: 42, closed: 35, open: 7 },
pullRequests: { total: 28, merged: 26, open: 2 },
@@ -48,28 +52,28 @@ describe('MetricsReporter', () => {
mockTrendAnalyzer.analyzeTrends.mockResolvedValue(mockTrends);
mockAnomalyDetector.detectAnomalies.mockResolvedValue([]);
- const report = await reporter.generateReport('lightspeedwp/.github');
+ const report = await reporter.generateReport("lightspeedwp/.github");
- expect(report).toContain('Metrics Report');
- expect(report).toContain('lightspeedwp/.github');
- expect(report).toContain('Summary');
- expect(report).toContain('Issues');
- expect(report).toContain('Pull Requests');
- expect(report).toContain('Contributors');
+ expect(report).toContain("Metrics Report");
+ expect(report).toContain("lightspeedwp/.github");
+ expect(report).toContain("Summary");
+ expect(report).toContain("Issues");
+ expect(report).toContain("Pull Requests");
+ expect(report).toContain("Contributors");
});
- test('should generate empty report when no metrics available', async () => {
+ test("should generate empty report when no metrics available", async () => {
mockStorage.getLatestMetrics.mockResolvedValue(null);
- const report = await reporter.generateReport('lightspeedwp/.github');
+ const report = await reporter.generateReport("lightspeedwp/.github");
- expect(report).toContain('No Data Available');
- expect(report).toContain('lightspeedwp/.github');
+ expect(report).toContain("No Data Available");
+ expect(report).toContain("lightspeedwp/.github");
});
- test('should include anomalies when detected', async () => {
+ test("should include anomalies when detected", async () => {
const mockMetrics = {
- repository: 'lightspeedwp/.github',
+ repository: "lightspeedwp/.github",
timestamp: new Date().toISOString(),
issues: { total: 42, closed: 35, open: 7 },
pullRequests: { total: 28, merged: 26, open: 2 },
@@ -78,10 +82,10 @@ describe('MetricsReporter', () => {
const mockAnomalies = [
{
- type: 'Issue Closure Rate Drop',
- severity: 'high',
- description: 'Issue closure rate down 15% from baseline',
- impact: 'high',
+ type: "Issue Closure Rate Drop",
+ severity: "high",
+ description: "Issue closure rate down 15% from baseline",
+ impact: "high",
},
];
@@ -90,15 +94,15 @@ describe('MetricsReporter', () => {
mockTrendAnalyzer.analyzeTrends.mockResolvedValue({});
mockAnomalyDetector.detectAnomalies.mockResolvedValue(mockAnomalies);
- const report = await reporter.generateReport('lightspeedwp/.github');
+ const report = await reporter.generateReport("lightspeedwp/.github");
- expect(report).toContain('Anomalies');
- expect(report).toContain('Issue Closure Rate Drop');
+ expect(report).toContain("Anomalies");
+ expect(report).toContain("Issue Closure Rate Drop");
});
- test('should support different report periods', async () => {
+ test("should support different report periods", async () => {
const mockMetrics = {
- repository: 'lightspeedwp/.github',
+ repository: "lightspeedwp/.github",
timestamp: new Date().toISOString(),
issues: { total: 42, closed: 35, open: 7 },
pullRequests: { total: 28, merged: 26, open: 2 },
@@ -110,22 +114,28 @@ describe('MetricsReporter', () => {
mockTrendAnalyzer.analyzeTrends.mockResolvedValue({});
mockAnomalyDetector.detectAnomalies.mockResolvedValue([]);
- const weeklyReport = await reporter.generateReport('lightspeedwp/.github', {
- period: 'weekly',
- });
- const monthlyReport = await reporter.generateReport('lightspeedwp/.github', {
- period: 'monthly',
- });
+ const weeklyReport = await reporter.generateReport(
+ "lightspeedwp/.github",
+ {
+ period: "weekly",
+ },
+ );
+ const monthlyReport = await reporter.generateReport(
+ "lightspeedwp/.github",
+ {
+ period: "monthly",
+ },
+ );
expect(weeklyReport).toBeDefined();
expect(monthlyReport).toBeDefined();
- expect(weeklyReport).toContain('Metrics Report');
- expect(monthlyReport).toContain('Metrics Report');
+ expect(weeklyReport).toContain("Metrics Report");
+ expect(monthlyReport).toContain("Metrics Report");
});
});
- describe('Health Score Calculation', () => {
- test('should calculate health score correctly', () => {
+ describe("Health Score Calculation", () => {
+ test("should calculate health score correctly", () => {
const metrics = {
issues: { total: 100, closed: 80, open: 20 },
pullRequests: { total: 50, merged: 45, open: 5 },
@@ -140,10 +150,10 @@ describe('MetricsReporter', () => {
expect(score).toBeGreaterThan(0);
expect(score).toBeLessThanOrEqual(100);
- expect(typeof score).toBe('number');
+ expect(typeof score).toBe("number");
});
- test('should penalize for anomalies', () => {
+ test("should penalize for anomalies", () => {
const metrics = {
issues: { total: 100, closed: 80, open: 20 },
pullRequests: { total: 50, merged: 45, open: 5 },
@@ -153,13 +163,19 @@ describe('MetricsReporter', () => {
const trendsNoAnomalies = { anomalyCount: 0 };
const trendsWithAnomalies = { anomalyCount: 2 };
- const scoreNoAnomalies = reporter.calculateHealthScore(metrics, trendsNoAnomalies);
- const scoreWithAnomalies = reporter.calculateHealthScore(metrics, trendsWithAnomalies);
+ const scoreNoAnomalies = reporter.calculateHealthScore(
+ metrics,
+ trendsNoAnomalies,
+ );
+ const scoreWithAnomalies = reporter.calculateHealthScore(
+ metrics,
+ trendsWithAnomalies,
+ );
expect(scoreNoAnomalies).toBeGreaterThan(scoreWithAnomalies);
});
- test('should handle empty metrics gracefully', () => {
+ test("should handle empty metrics gracefully", () => {
const metrics = {
issues: { total: 0, closed: 0, open: 0 },
pullRequests: { total: 0, merged: 0, open: 0 },
@@ -175,46 +191,50 @@ describe('MetricsReporter', () => {
});
});
- describe('Report Sections', () => {
- test('should generate header with correct format', () => {
- const header = reporter.generateHeader('lightspeedwp/.github', new Date(), 'weekly');
+ describe("Report Sections", () => {
+ test("should generate header with correct format", () => {
+ const header = reporter.generateHeader(
+ "lightspeedwp/.github",
+ new Date(),
+ "weekly",
+ );
- expect(header).toContain('Metrics Report');
- expect(header).toContain('lightspeedwp/.github');
- expect(header).toContain('Weekly Report');
+ expect(header).toContain("Metrics Report");
+ expect(header).toContain("lightspeedwp/.github");
+ expect(header).toContain("Weekly Report");
});
- test('should generate issues section with correct structure', () => {
+ test("should generate issues section with correct structure", () => {
const metrics = {
issues: { total: 42, closed: 35, open: 7 },
};
const trends = {
issues: { trend: 5 },
- avgFixTime: { value: '3.2 days' },
+ avgFixTime: { value: "3.2 days" },
};
const section = reporter.generateIssuesSection(metrics, trends);
- expect(section).toContain('Issues');
- expect(section).toContain('Total');
- expect(section).toContain('42');
- expect(section).toContain('Closed');
+ expect(section).toContain("Issues");
+ expect(section).toContain("Total");
+ expect(section).toContain("42");
+ expect(section).toContain("Closed");
});
- test('should generate contributors section', () => {
+ test("should generate contributors section", () => {
const metrics = {
contributors: { active: 12, new: 2, returning: 10 },
};
const section = reporter.generateContributorsSection(metrics);
- expect(section).toContain('Contributors');
- expect(section).toContain('Active');
- expect(section).toContain('12');
+ expect(section).toContain("Contributors");
+ expect(section).toContain("Active");
+ expect(section).toContain("12");
});
- test('should generate health status section', () => {
+ test("should generate health status section", () => {
const metrics = {
issues: { total: 42, closed: 35, open: 7 },
pullRequests: { total: 28, merged: 26, open: 2 },
@@ -227,35 +247,41 @@ describe('MetricsReporter', () => {
const anomalies = [];
- const section = reporter.generateHealthScoreSection(metrics, trends, anomalies);
+ const section = reporter.generateHealthScoreSection(
+ metrics,
+ trends,
+ anomalies,
+ );
- expect(section).toContain('Health Status');
- expect(section).toContain('Score');
+ expect(section).toContain("Health Status");
+ expect(section).toContain("Score");
});
- test('should generate footer', () => {
+ test("should generate footer", () => {
const footer = reporter.generateFooter();
- expect(footer).toContain('Report generated');
- expect(footer).toContain('metrics team');
+ expect(footer).toContain("Report generated");
+ expect(footer).toContain("metrics team");
});
});
- describe('Error Handling', () => {
- test('should handle storage errors gracefully', async () => {
- mockStorage.getLatestMetrics.mockRejectedValue(new Error('Storage error'));
-
- await expect(reporter.generateReport('lightspeedwp/.github')).rejects.toThrow(
- 'Storage error'
+ describe("Error Handling", () => {
+ test("should handle storage errors gracefully", async () => {
+ mockStorage.getLatestMetrics.mockRejectedValue(
+ new Error("Storage error"),
);
+
+ await expect(
+ reporter.generateReport("lightspeedwp/.github"),
+ ).rejects.toThrow("Storage error");
});
- test('should handle undefined metrics gracefully', async () => {
+ test("should handle undefined metrics gracefully", async () => {
mockStorage.getLatestMetrics.mockResolvedValue(undefined);
- const report = await reporter.generateReport('lightspeedwp/.github');
+ const report = await reporter.generateReport("lightspeedwp/.github");
- expect(report).toContain('No Data Available');
+ expect(report).toContain("No Data Available");
});
});
});
diff --git a/scripts/metrics/__tests__/performance.test.js b/scripts/metrics/__tests__/performance.test.js
index 0b653a426..2b296cde8 100644
--- a/scripts/metrics/__tests__/performance.test.js
+++ b/scripts/metrics/__tests__/performance.test.js
@@ -3,19 +3,21 @@
* Validates performance characteristics and scalability
*/
-describe('Metrics Agent Phase 2 - Performance Benchmarks', () => {
- describe('Collection Performance', () => {
- test('single repository collection should complete in <30 seconds', () => {
+describe("Metrics Agent Phase 2 - Performance Benchmarks", () => {
+ describe("Collection Performance", () => {
+ test("single repository collection should complete in <30 seconds", () => {
const startTime = Date.now();
// Simulate metrics collection for single repo
// In real scenario: GitHub API calls, processing, storage write
const mockCollection = () => {
const data = {
- repository: 'lightspeedwp/.github',
+ repository: "lightspeedwp/.github",
issues: { total: 42, closed: 35, open: 7 },
pullRequests: { total: 28, merged: 26, open: 2 },
- contributors: Array(12).fill({}).map((_, i) => ({ id: i })),
+ contributors: Array(12)
+ .fill({})
+ .map((_, i) => ({ id: i })),
};
return data;
};
@@ -27,7 +29,7 @@ describe('Metrics Agent Phase 2 - Performance Benchmarks', () => {
expect(elapsed).toBeLessThan(30000);
});
- test('10 repository collection should complete in <5 minutes', () => {
+ test("10 repository collection should complete in <5 minutes", () => {
const startTime = Date.now();
const repos = Array(10)
.fill()
@@ -44,7 +46,7 @@ describe('Metrics Agent Phase 2 - Performance Benchmarks', () => {
expect(elapsed).toBeLessThan(300000);
});
- test('metrics enrichment should be <100ms per repository', () => {
+ test("metrics enrichment should be <100ms per repository", () => {
const startTime = Date.now();
// Simulate enrichment with context/timestamp (metrics not used in simple perf test)
@@ -55,16 +57,16 @@ describe('Metrics Agent Phase 2 - Performance Benchmarks', () => {
});
});
- describe('Storage Performance', () => {
- test('time-series storage write should be <1 second', () => {
+ describe("Storage Performance", () => {
+ test("time-series storage write should be <1 second", () => {
const startTime = Date.now();
// Simulate storage write (JSON serialization + disk I/O)
const storage = {
- 'lightspeedwp/.github': Array(365)
+ "lightspeedwp/.github": Array(365)
.fill()
.map((_, i) => ({
- date: `2025-${String((i % 12) + 1).padStart(2, '0')}-${String((i % 28) + 1).padStart(2, '0')}`,
+ date: `2025-${String((i % 12) + 1).padStart(2, "0")}-${String((i % 28) + 1).padStart(2, "0")}`,
metrics: { issues: { total: Math.random() * 100 } },
})),
};
@@ -77,7 +79,7 @@ describe('Metrics Agent Phase 2 - Performance Benchmarks', () => {
expect(elapsed).toBeLessThan(1000);
});
- test('time-series retrieval should be <500ms', () => {
+ test("time-series retrieval should be <500ms", () => {
const startTime = Date.now();
// Simulate retrieval of historical data
@@ -85,7 +87,10 @@ describe('Metrics Agent Phase 2 - Performance Benchmarks', () => {
.fill()
.map((_, i) => ({
week: i,
- metrics: { issues: { total: 40 + i }, pullRequests: { total: 25 + i } },
+ metrics: {
+ issues: { total: 40 + i },
+ pullRequests: { total: 25 + i },
+ },
}));
history.filter((h) => h.week > 0);
@@ -96,8 +101,8 @@ describe('Metrics Agent Phase 2 - Performance Benchmarks', () => {
});
});
- describe('Analysis Performance', () => {
- test('trend calculation should be <100ms per repository', () => {
+ describe("Analysis Performance", () => {
+ test("trend calculation should be <100ms per repository", () => {
const startTime = Date.now();
// Simulate trend analysis
@@ -108,7 +113,8 @@ describe('Metrics Agent Phase 2 - Performance Benchmarks', () => {
}));
// Calculate trends (not used in simple performance test)
- history[history.length - 1].issues.total - history[history.length - 2].issues.total;
+ history[history.length - 1].issues.total -
+ history[history.length - 2].issues.total;
history.slice(-4).reduce((sum, h) => sum + h.issues.total, 0) / 4;
const elapsed = Date.now() - startTime;
@@ -116,7 +122,7 @@ describe('Metrics Agent Phase 2 - Performance Benchmarks', () => {
expect(elapsed).toBeLessThan(100);
});
- test('anomaly detection should be <50ms per repository', () => {
+ test("anomaly detection should be <50ms per repository", () => {
const startTime = Date.now();
// Simulate anomaly detection with baseline comparison
@@ -125,10 +131,10 @@ describe('Metrics Agent Phase 2 - Performance Benchmarks', () => {
const anomalies = [];
if (current.closureRate < baseline.closureRate * 0.85) {
- anomalies.push({ type: 'closure_rate_drop', severity: 'high' });
+ anomalies.push({ type: "closure_rate_drop", severity: "high" });
}
if (current.reviewTime > baseline.reviewTime * 1.25) {
- anomalies.push({ type: 'review_time_increase', severity: 'medium' });
+ anomalies.push({ type: "review_time_increase", severity: "medium" });
}
const elapsed = Date.now() - startTime;
@@ -138,8 +144,8 @@ describe('Metrics Agent Phase 2 - Performance Benchmarks', () => {
});
});
- describe('Reporting Performance', () => {
- test('report generation should be <2 seconds per repository', () => {
+ describe("Reporting Performance", () => {
+ test("report generation should be <2 seconds per repository", () => {
const startTime = Date.now();
// Simulate report generation (report not used in simple performance test)
@@ -158,7 +164,7 @@ Anomalies detected`;
expect(elapsed).toBeLessThan(2000);
});
- test('GitHub issue creation should be <5 seconds including API call', () => {
+ test("GitHub issue creation should be <5 seconds including API call", () => {
const startTime = Date.now();
// Simulate issue creation (includes API latency)
@@ -171,7 +177,7 @@ Anomalies detected`;
// But we measure the expected time with real API calls
});
- test('old report closure should complete in <10 seconds for 100 issues', () => {
+ test("old report closure should complete in <10 seconds for 100 issues", () => {
const startTime = Date.now();
// Simulate searching and closing old reports
@@ -189,8 +195,8 @@ Anomalies detected`;
});
});
- describe('Workflow Performance', () => {
- test('complete metrics collection workflow should finish in <5 minutes', () => {
+ describe("Workflow Performance", () => {
+ test("complete metrics collection workflow should finish in <5 minutes", () => {
// Benchmark breakdown:
// - Checkout & setup: ~30s
// - Install dependencies: ~20s
@@ -217,8 +223,8 @@ Anomalies detected`;
});
});
- describe('Scalability', () => {
- test('should scale linearly with repository count', () => {
+ describe("Scalability", () => {
+ test("should scale linearly with repository count", () => {
const benchmarkByRepoCount = {
1: 1750, // ~1.75 minutes (seconds ร 1000)
5: 8750, // ~8.75 minutes
@@ -233,7 +239,7 @@ Anomalies detected`;
expect(ratio10to1).toBeCloseTo(10, 1);
});
- test('parallel execution should improve multi-repo performance', () => {
+ test("parallel execution should improve multi-repo performance", () => {
// Sequential: 10 repos ร 1.75 min = 17.5 min
// Parallel (4 jobs): ~5 minutes
@@ -246,8 +252,8 @@ Anomalies detected`;
});
});
- describe('Memory Efficiency', () => {
- test('storage should not exceed reasonable memory limits', () => {
+ describe("Memory Efficiency", () => {
+ test("storage should not exceed reasonable memory limits", () => {
// Approximate memory usage:
// - Single metric object: ~500 bytes
// - 1 year of weekly reports: ~500 ร 52 = 26KB per repo
diff --git a/scripts/metrics/github-issue-creator.js b/scripts/metrics/github-issue-creator.js
index d223ddd10..86eb85dcf 100644
--- a/scripts/metrics/github-issue-creator.js
+++ b/scripts/metrics/github-issue-creator.js
@@ -11,11 +11,17 @@ class GitHubIssueCreator {
/**
* Create or update metrics report issue
*/
- async createMetricsIssue(owner, repo, report, period = 'weekly', options = {}) {
+ async createMetricsIssue(
+ owner,
+ repo,
+ report,
+ period = "weekly",
+ options = {},
+ ) {
const { labels = [], assignees = [], autoClose = true } = options;
try {
- const reportDate = new Date().toISOString().split('T')[0];
+ const reportDate = new Date().toISOString().split("T")[0];
const title = `[Metrics] ${period.charAt(0).toUpperCase() + period.slice(1)} Report: ${reportDate}`;
// Create issue
@@ -24,14 +30,14 @@ class GitHubIssueCreator {
repo,
title,
body: report,
- labels: ['type:metrics', 'area:monitoring', ...labels],
+ labels: ["type:metrics", "area:monitoring", ...labels],
assignees: assignees.length > 0 ? assignees : undefined,
});
console.log(`โ
Created metrics issue #${issue.data.number}`);
return issue.data;
} catch (error) {
- console.error('Error creating metrics issue:', error.message);
+ console.error("Error creating metrics issue:", error.message);
throw error;
}
}
@@ -45,8 +51,8 @@ class GitHubIssueCreator {
const issues = await this.octokit.rest.issues.listForRepo({
owner,
repo,
- labels: 'type:metrics',
- state: 'open',
+ labels: "type:metrics",
+ state: "open",
per_page: 100,
});
@@ -61,8 +67,8 @@ class GitHubIssueCreator {
owner,
repo,
issue_number: issue.number,
- state: 'closed',
- state_reason: 'not_planned',
+ state: "closed",
+ state_reason: "not_planned",
});
console.log(`โ
Closed old metrics issue #${issue.number}`);
@@ -72,7 +78,7 @@ class GitHubIssueCreator {
return { closedCount, totalChecked: issues.data.length };
} catch (error) {
- console.error('Error closing old reports:', error.message);
+ console.error("Error closing old reports:", error.message);
throw error;
}
}
@@ -80,19 +86,19 @@ class GitHubIssueCreator {
/**
* Get existing metrics issues
*/
- async getMetricsIssues(owner, repo, state = 'all') {
+ async getMetricsIssues(owner, repo, state = "all") {
try {
const issues = await this.octokit.rest.issues.listForRepo({
owner,
repo,
- labels: 'type:metrics',
+ labels: "type:metrics",
state,
per_page: 100,
});
return issues.data;
} catch (error) {
- console.error('Error fetching metrics issues:', error.message);
+ console.error("Error fetching metrics issues:", error.message);
throw error;
}
}
@@ -112,7 +118,7 @@ class GitHubIssueCreator {
console.log(`โ
Added comment to issue #${issueNumber}`);
return response.data;
} catch (error) {
- console.error('Error adding comment:', error.message);
+ console.error("Error adding comment:", error.message);
throw error;
}
}
@@ -121,9 +127,9 @@ class GitHubIssueCreator {
* Create weekly metrics report issue
*/
async createWeeklyMetricsIssue(owner, repo, report, options = {}) {
- return this.createMetricsIssue(owner, repo, report, 'weekly', {
+ return this.createMetricsIssue(owner, repo, report, "weekly", {
...options,
- labels: ['period:weekly', ...(options.labels || [])],
+ labels: ["period:weekly", ...(options.labels || [])],
});
}
@@ -131,9 +137,9 @@ class GitHubIssueCreator {
* Create monthly metrics report issue
*/
async createMonthlyMetricsIssue(owner, repo, report, options = {}) {
- return this.createMetricsIssue(owner, repo, report, 'monthly', {
+ return this.createMetricsIssue(owner, repo, report, "monthly", {
...options,
- labels: ['period:monthly', ...(options.labels || [])],
+ labels: ["period:monthly", ...(options.labels || [])],
});
}
@@ -142,18 +148,18 @@ class GitHubIssueCreator {
*/
async reportExistsForDate(owner, repo, date) {
try {
- const dateString = date.toISOString().split('T')[0];
+ const dateString = date.toISOString().split("T")[0];
const issues = await this.octokit.rest.issues.listForRepo({
owner,
repo,
- labels: 'type:metrics',
- state: 'all',
+ labels: "type:metrics",
+ state: "all",
per_page: 100,
});
return issues.data.some((issue) => issue.title.includes(dateString));
} catch (error) {
- console.error('Error checking for existing report:', error.message);
+ console.error("Error checking for existing report:", error.message);
return false;
}
}
@@ -163,31 +169,44 @@ class GitHubIssueCreator {
*/
generateIssueTemplate(reportData) {
return [
- '# Metrics Report',
- '',
+ "# Metrics Report",
+ "",
`Generated: ${new Date().toISOString()}`,
- '',
+ "",
reportData,
- '',
- '---',
- '',
- '**Metadata:**',
+ "",
+ "---",
+ "",
+ "**Metadata:**",
`- Auto-generated by metrics collection system`,
`- Review the full report above for detailed analysis`,
`- Reply in this thread with questions or concerns`,
- ].join('\n');
+ ].join("\n");
}
/**
* Create issue with retry logic
*/
- async createMetricsIssueWithRetry(owner, repo, report, period = 'weekly', maxRetries = 3) {
+ async createMetricsIssueWithRetry(
+ owner,
+ repo,
+ report,
+ period = "weekly",
+ maxRetries = 3,
+ ) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
- console.log(`Creating metrics issue (attempt ${attempt}/${maxRetries})...`);
- const issue = await this.createMetricsIssue(owner, repo, report, period);
+ console.log(
+ `Creating metrics issue (attempt ${attempt}/${maxRetries})...`,
+ );
+ const issue = await this.createMetricsIssue(
+ owner,
+ repo,
+ report,
+ period,
+ );
return issue;
} catch (error) {
lastError = error;
@@ -201,7 +220,9 @@ class GitHubIssueCreator {
}
}
- throw new Error(`Failed to create metrics issue after ${maxRetries} attempts: ${lastError.message}`);
+ throw new Error(
+ `Failed to create metrics issue after ${maxRetries} attempts: ${lastError.message}`,
+ );
}
}
diff --git a/scripts/metrics/integrations/reporting-agent-input.js b/scripts/metrics/integrations/reporting-agent-input.js
index fd6e46691..42d04849b 100644
--- a/scripts/metrics/integrations/reporting-agent-input.js
+++ b/scripts/metrics/integrations/reporting-agent-input.js
@@ -208,7 +208,7 @@ class MetricsReportFormatter {
};
const reviewTimeAnomaly = rawMetrics.anomalies?.find(
- (a) => a.metric === "averageReviewTime"
+ (a) => a.metric === "averageReviewTime",
);
trends.reviewTime = {
@@ -216,7 +216,7 @@ class MetricsReportFormatter {
change:
typeof metrics.reviewTimeChange !== "undefined"
? metrics.reviewTimeChange
- : reviewTimeAnomaly?.percentChange ?? "unavailable",
+ : (reviewTimeAnomaly?.percentChange ?? "unavailable"),
detail: metrics.reviewTimeDetail || "Data unavailable",
};