Skip to content
4 changes: 2 additions & 2 deletions agents/pr-agent/AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ Create and update GitHub pull requests correctly and consistently: deriving PR c
## Implementation Reference

- **Folder:** `agents/pr-agent/`
- **Entry points:** each skill's own `skills/<name>/<name>.js`
- **Tests:** `__tests__/` (unit), `__tests__/integration/` (cross-skill)
- **Entry points:** each skill's own `skills/<name>/scripts/<name>.js`
- **Tests:** `skills/<name>/scripts/__tests__/<name>.test.js` (unit), `__tests__/integration/` (cross-skill)

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Test complex label scenarios

import { describe, test, expect, beforeEach } from "@jest/globals";
import { validateAndApplyLabels } from "../../skills/validate-and-apply-labels/validate-and-apply-labels.js";
import { validateAndApplyLabels } from "../../skills/validate-and-apply-labels/scripts/validate-and-apply-labels.js";
import { MockGitHub, createMockConfig } from "./setup.js";

describe("Category B: Label Application Scenarios", () => {
Expand All @@ -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: Label Application → type and changelog decision", async () => {
const labels = ["type:feature", "meta:no-changelog"];

const result = await validateAndApplyLabels({
labels,
Expand All @@ -24,12 +24,12 @@ describe("Category B: Label Application Scenarios", () => {
});

expect(result.valid).toBe(true);
expect(result.appliedLabels).toEqual(["type:feature"]);
expect(result.appliedLabels.length).toBe(1);
expect(result.appliedLabels).toEqual(labels);
expect(result.appliedLabels.length).toBe(2);
});

test("Test B2: Multiple Labels → type:feature + area:agents", async () => {
const labels = ["type:feature", "area:agents"];
const labels = ["type:feature", "area:agents", "meta:no-changelog"];

const result = await validateAndApplyLabels({
labels,
Expand All @@ -39,7 +39,7 @@ describe("Category B: Label Application Scenarios", () => {

expect(result.valid).toBe(true);
expect(result.appliedLabels).toEqual(labels);
expect(result.appliedLabels.length).toBe(2);
expect(result.appliedLabels.length).toBe(3);
});

test("Test B3: Label Conflicts → Resolved per labeling strategy", async () => {
Expand Down Expand Up @@ -86,7 +86,7 @@ describe("Category B: Label Application Scenarios", () => {
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 conditionalLabels = ["type:bug", "meta:no-changelog"];

const result = await validateAndApplyLabels({
labels: conditionalLabels,
Expand All @@ -101,7 +101,12 @@ describe("Category B: Label Application Scenarios", () => {
});

test("Test B7: Label Priority → Higher priority labels applied first", async () => {
const labels = ["area:agents", "type:feature", "priority:critical"];
const labels = [
"area:agents",
"type:feature",
"priority:critical",
"meta:no-changelog",
];

const result = await validateAndApplyLabels({
labels,
Expand All @@ -115,7 +120,12 @@ describe("Category B: Label Application Scenarios", () => {
});

test("Test B8: Label Deduplication → Duplicate labels removed", async () => {
const labels = ["type:feature", "type:feature", "area:agents"];
const labels = [
"type:feature",
"type:feature",
"area:agents",
"meta:no-changelog",
];

const result = await validateAndApplyLabels({
labels,
Expand All @@ -124,8 +134,12 @@ describe("Category B: Label Application Scenarios", () => {
});

expect(result.valid).toBe(true);
expect(result.appliedLabels).toEqual(["type:feature", "area:agents"]);
expect(result.appliedLabels.length).toBe(2);
expect(result.appliedLabels).toEqual([
"type:feature",
"area:agents",
"meta:no-changelog",
]);
expect(result.appliedLabels.length).toBe(3);
expect(result.deduplicatedCount).toBe(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
// Test performance and unusual scenarios

import { describe, test, expect, beforeEach } from "@jest/globals";
import { validateBranchName } from "../../skills/validate-branch-name/validate-branch-name.js";
import { routePrTemplate } from "../../skills/route-pr-template/route-pr-template.js";
import { validateAndApplyLabels } from "../../skills/validate-and-apply-labels/validate-and-apply-labels.js";
import { orchestratePrCreation } from "../../skills/orchestrate-pr-creation/orchestrate-pr-creation.js";
import { validateBranchName } from "../../skills/validate-branch-name/scripts/validate-branch-name.js";
import { routePrTemplate } from "../../skills/route-pr-template/scripts/route-pr-template.js";
import { validateAndApplyLabels } from "../../skills/validate-and-apply-labels/scripts/validate-and-apply-labels.js";
import { orchestratePrCreation } from "../../skills/orchestrate-pr-creation/scripts/orchestrate-pr-creation.js";
import { MockGitHub, createMockConfig } from "./setup.js";

describe("Category F: Performance & Edge Cases", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
// Test complete end-to-end workflows

import { describe, test, expect, beforeEach } from "@jest/globals";
import { validateBranchName } from "../../skills/validate-branch-name/validate-branch-name.js";
import { routePrTemplate } from "../../skills/route-pr-template/route-pr-template.js";
import { validateAndApplyLabels } from "../../skills/validate-and-apply-labels/validate-and-apply-labels.js";
import { orchestratePrCreation } from "../../skills/orchestrate-pr-creation/orchestrate-pr-creation.js";
import { validateBranchName } from "../../skills/validate-branch-name/scripts/validate-branch-name.js";
import { routePrTemplate } from "../../skills/route-pr-template/scripts/route-pr-template.js";
import { validateAndApplyLabels } from "../../skills/validate-and-apply-labels/scripts/validate-and-apply-labels.js";
import { orchestratePrCreation } from "../../skills/orchestrate-pr-creation/scripts/orchestrate-pr-creation.js";
import { MockGitHub, createMockConfig } from "./setup.js";

describe("Category E: Real GitHub Workflows", () => {
Expand All @@ -19,7 +19,7 @@ describe("Category E: Real GitHub Workflows", () => {

test("Test E1: Feature Branch Complete Workflow → All 4 skills succeed", async () => {
const branchName = "feat/new-dashboard";
const labels = ["type:feature"];
const labels = ["type:feature", "meta:no-changelog"];

// Validate branch
const branchValidation = await validateBranchName({ branchName, config });
Expand Down Expand Up @@ -59,7 +59,7 @@ describe("Category E: Real GitHub Workflows", () => {

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 labels = ["type:bug", "priority:critical", "meta:needs-changelog"];

const branchValidation = await validateBranchName({ branchName, config });
expect(branchValidation.valid).toBe(true);
Expand All @@ -78,7 +78,7 @@ describe("Category E: Real GitHub Workflows", () => {

test("Test E3: Documentation Update → docs/ → docs template → minimal labels", async () => {
const branchName = "docs/branching-guide";
const labels = ["type:docs"];
const labels = ["type:docs", "meta:no-changelog"];

const branchValidation = await validateBranchName({ branchName, config });
expect(branchValidation.valid).toBe(true);
Expand All @@ -92,7 +92,7 @@ describe("Category E: Real GitHub Workflows", () => {
mockGitHub: mockGitHub.issues,
});
expect(labelValidation.valid).toBe(true);
expect(labelValidation.appliedLabels.length).toBe(1);
expect(labelValidation.appliedLabels.length).toBe(2);
});

test("Test E4: Chore/Dependency Update → chore/ → chore template → meta labels", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
// 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/validate-branch-name.js";
import { routePrTemplate } from "../../skills/route-pr-template/route-pr-template.js";
import { validateAndApplyLabels } from "../../skills/validate-and-apply-labels/validate-and-apply-labels.js";
import { orchestratePrCreation } from "../../skills/orchestrate-pr-creation/orchestrate-pr-creation.js";
import { validateBranchName } from "../../skills/validate-branch-name/scripts/validate-branch-name.js";
import { routePrTemplate } from "../../skills/route-pr-template/scripts/route-pr-template.js";
import { validateAndApplyLabels } from "../../skills/validate-and-apply-labels/scripts/validate-and-apply-labels.js";
import { orchestratePrCreation } from "../../skills/orchestrate-pr-creation/scripts/orchestrate-pr-creation.js";
import { MockGitHub, createMockConfig } from "./setup.js";

describe("Category A: Sequential Skill Execution", () => {
Expand Down Expand Up @@ -37,7 +37,7 @@ describe("Category A: Sequential Skill Execution", () => {
expect(templateRoute.routed).toBe(true);

// Step 3: Validate labels
const labels = ["type:feature"];
const labels = ["type:feature", "meta:no-changelog"];
const labelValidation = await validateAndApplyLabels({
labels,
config,
Expand Down Expand Up @@ -117,7 +117,7 @@ describe("Category A: Sequential Skill Execution", () => {
});

test("Test A6: Mixed Label Scenarios → Multiple labels applied correctly", async () => {
const labels = ["type:feature", "area:agents"];
const labels = ["type:feature", "area:agents", "meta:no-changelog"];

const result = await validateAndApplyLabels({
labels,
Expand Down Expand Up @@ -146,7 +146,7 @@ describe("Category A: Sequential Skill Execution", () => {

test("Test A8: Complete Feature Workflow → feat/ branch full pipeline", async () => {
const branchName = "feat/new-feature";
const labels = ["type:feature"];
const labels = ["type:feature", "meta:no-changelog"];

// Full workflow validation
const branchValidation = await validateBranchName({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Test PR template selection for all branch types

import { describe, test, expect, beforeEach } from "@jest/globals";
import { routePrTemplate } from "../../skills/route-pr-template/route-pr-template.js";
import { routePrTemplate } from "../../skills/route-pr-template/scripts/route-pr-template.js";
import { createMockConfig } from "./setup.js";

describe("Category C: Template Routing Scenarios", () => {
Expand Down
66 changes: 62 additions & 4 deletions agents/pr-agent/skills/handle-pr-errors/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,66 @@
---
name: template-skill
description: Replace with description of the skill and when Claude should use it.
name: handle-pr-errors
description: Catches an error from any stage of the PR workflow, categorizes it, assigns a severity, and suggests recovery options and next steps. Use whenever any other pr-agent skill returns or throws an error, to turn it into an actionable recovery response instead of a bare failure.
---

# Insert instructions below
# handle-pr-errors

*Have questions? Ping us on GitHub! 🐙 Made with 💚 by LightSpeedWP*
Handles errors and provides recovery mechanisms for the PR creation workflow.

## When to use this skill

Whenever any other skill in this agent (`validate-branch-name`, `route-pr-template`, `orchestrate-pr-creation`, `validate-and-apply-labels`, `submit-pr`) returns or throws an error — to categorise it and suggest a concrete recovery path rather than surfacing a bare failure.

## Input

```js
{
error: object, // { type, message, stage }
context: object, // optional: context from the failed operation
history: array, // optional: prior attempts, for retry logic
}
```

## Behaviour

- Categorises the error (input validation, branch name, template, GitHub API, authentication, rate limit, unknown).
- Assigns a severity (LOW/MEDIUM/HIGH/CRITICAL).
- Determines whether the error is retryable, capping retries at 3 attempts.
- Provides recovery options and ordered next steps.

## Output

Handled errors include their classification and recovery plan:

```js
{
handled: true,
errorCategory: string,
severity: string,
originalError: string,
recoveryOptions: string[],
recommendedAction: string,
nextSteps: string[],
retryable: boolean,
retryCount: number,
maxRetries: 3,
}
```

Invalid input and failures caught while handling an error return the same failure shape:

```js
{
handled: false,
error: string,
recoveryOptions: [],
}
```

## Usage

```js
import { handlePrErrors } from './scripts/handle-pr-errors.js';

const result = await handlePrErrors({ error: { message: 'Rate limit exceeded' } });
```
Loading
Loading