Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 50 additions & 5 deletions issue-lifecycle/.claude/skills/issue-lifecycle/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,57 @@ swamp model output search issue-<N> --json

## Resuming a Session

If the human comes back to an in-progress issue, check the current state:
If the human comes back to an in-progress issue, check the current phase:

```
swamp model get issue-<N> --json
swamp data get issue-<N> state-main --json
```

Then read the reference file for whatever phase the state shows and pick up from
there.
Read the `phase` field from the response. **Do NOT call `start` to resume** —
`start` unconditionally resets the phase to `triaging`, destroying progress.

Use this table to determine what to do next:

| Phase | Action |
| ---------------- | ------------------------------------------------------------------------- |
| `triaging` | Read [references/triage.md](references/triage.md) |
| `classified` | Read [references/planning.md](references/planning.md) |
| `plan_generated` | Read [references/adversarial-review.md](references/adversarial-review.md) |
| `approved` | Read [references/implementation.md](references/implementation.md) |
| `implementing` | Link a PR with `link_pr` or call `complete` |
| `pr_open` | Wait 3 min, then check PR: `pr_merged` if merged, `pr_failed` if failed |
| `pr_failed` | Fix the issue, then `link_pr` (new PR) or `implement` (major rework) |
| `releasing` | Check release build: `ship` when done, or `complete` as fallback |
| `done` | Nothing to do — lifecycle is complete |

The canonical phase list lives in the `TRANSITIONS` constant in
`extensions/models/_lib/schemas.ts`.

## Closing Out a Shipped Issue

When a PR has already merged and the lifecycle just needs to be marked done:

1. Check the current phase:
```
swamp data get issue-<N> state-main --json
```
2. If the phase is `implementing`, link the PR first:
```
swamp model method run issue-<N> link_pr --input url=<PR URL>
```
3. If the phase is `pr_open`, record the merge:
```
swamp model method run issue-<N> pr_merged
```
4. If the phase is `releasing`, ship it:
```
swamp model method run issue-<N> ship
```
5. For quick close-out, `complete` still works from `implementing`, `pr_open`,
or `releasing`:
```
swamp model method run issue-<N> complete
```

## Key Rules

Expand All @@ -130,7 +173,9 @@ there.
reference specific files, functions, and test paths.
6. **Follow the planning conventions for this repository.** Read
`agent-constraints/planning-conventions.md` if it exists.
7. **File unrelated issues immediately.** If you discover a bug, code smell, or
7. **Never open a PR without asking first.** Present the changes summary and
wait for the human to confirm before creating the pull request.
8. **File unrelated issues immediately.** If you discover a bug, code smell, or
problem during investigation that is NOT related to the current issue, file
it as a new swamp-club issue. Do not try to fix it in the current work span —
keep the scope focused.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ Report verification results to the human before creating the PR:

## 4. Create a PR

**Always ask the human before opening a PR.** Do not create the PR automatically
— present a summary of the changes and ask if they are ready to open it. Only
proceed after explicit confirmation.

Use the repository's normal PR tooling. Read
`agent-constraints/implementation-conventions.md` for repo-specific PR
conventions.
Expand Down Expand Up @@ -69,13 +73,56 @@ Calling `link_pr` is **encouraged but not enforced** — `complete` still accept
records. Prefer the `implementing → link_pr → complete` flow for all new work so
the lifecycle record carries the PR link.

## 5. Complete the Issue
## 4b. Wait for CI and Check PR Status

After linking the PR, wait at least 3 minutes for CI to run. The model enforces
a `pr-cooldown` check — calling `pr_merged` or `pr_failed` within 3 minutes of
`link_pr` will be rejected.

Check the PR status externally (e.g.,
`gh pr view <url> --json state,statusCheckRollup`):

- **PR merged**: call `pr_merged` to transition to `releasing`
```
swamp model method run issue-<N> pr_merged
```
- **PR failed** (CI red, changes requested): call `pr_failed` with the reason
```
swamp model method run issue-<N> pr_failed --input reason="CI failed: type check errors"
```
- **PR still open and passing**: wait and check again later.

## 4c. Handle PR Failure

When in `pr_failed`, diagnose and fix the issue. Then either:

- Push fixes and call `link_pr` again (same or new PR URL) to return to
`pr_open`:
```
swamp model method run issue-<N> link_pr --input url=<PR URL>
```
- Call `implement` to go back to the implementing phase for major rework:
```
swamp model method run issue-<N> implement
```

Once the PR has merged and the work is done, call `complete`:
## 5. Ship the Release

After `pr_merged` transitions to `releasing`, wait for the release build to
complete. Once the release is out, call `ship`:

```
swamp model method run issue-<N> ship
```

Optionally pass release metadata:

```
swamp model method run issue-<N> complete
swamp model method run issue-<N> ship --input releaseUrl=<URL> --input releaseNotes="Bug fix release"
```

This transitions the phase to `done`, transitions the swamp-club status to
`shipped`, and posts a `complete` lifecycle entry.
`shipped`, and posts a `shipped` lifecycle entry.

For quick close-out (e.g., the PR merged and you just want to wrap up),
`complete` still works from `implementing`, `pr_open`, or `releasing`.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@
Steps 1–5 of the issue lifecycle. Read this when starting a new triage or
resuming an issue in the `triaging` phase.

## Before You Start

Check whether the model instance already exists and is past triage:

```
swamp data get issue-<N> state-main --json
```

- If this **returns data** and the `phase` is anything other than `created` or
`triaging`, the issue is already in flight. **Do NOT call `start`** — go to
the "Resuming a Session" section in SKILL.md and use the phase-to-action table
to pick up where the issue left off.
- If the command **fails** (no data found), the model instance hasn't been
created yet — proceed with step 1 below.

## 1. Create the Model Instance

The swamp-club issue must already exist — create it in the swamp-club UI first,
Expand All @@ -25,6 +40,10 @@ commands in this skill also need `--repo-dir`.
swamp model method run issue-<N> start
```

> **Warning:** `start` unconditionally resets the phase to `triaging`. Only call
> it once when beginning a new lifecycle. Never use it to resume an in-progress
> issue — it will destroy all progress (classification, plan, approvals).

This fetches the issue from swamp-club via `GET /api/v1/lab/issues/<N>` and
writes the title, body, type, status, and comments to the `context` resource. If
the issue doesn't exist in swamp-club, `start` fails loudly — create the issue
Expand Down
2 changes: 1 addition & 1 deletion issue-lifecycle/.swamp.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
swampVersion: 20260406.211743.0
initializedAt: '2026-04-07T17:13:59.002Z'
initializedAt: "2026-04-07T17:13:59.002Z"
repoId: 7fa9e15c-844d-48d8-a8a6-d0ac8c5f8695
tool: claude
gitignoreManaged: true
53 changes: 43 additions & 10 deletions issue-lifecycle/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,55 @@
<!-- BEGIN swamp managed section - DO NOT EDIT -->

# Project

This repository is managed with [swamp](https://github.com/systeminit/swamp).

## Rules

1. **Search before you build.** When automating AWS, APIs, or any external service: (a) search local types with `swamp model type search <query>`, (b) search community extensions with `swamp extension search <query>`, (c) if a community extension exists, install it with `swamp extension pull <package>` instead of building from scratch, (d) only create a custom extension model in `extensions/models/` if nothing exists. Use the `swamp-extension-model` skill for guidance. The `command/shell` model is ONLY for ad-hoc one-off shell commands, NEVER for wrapping CLI tools or building integrations.
2. **Extend, don't be clever.** When a model covers the domain but lacks the method you need, extend it with `export const extension` — don't bypass it with shell scripts, CLI tools, or multi-step hacks. One method, one purpose. Use `swamp model type describe <type> --json` to check available methods.
3. **Use the data model.** Once data exists in a model (via `lookup`, `start`, `sync`, etc.), reference it with CEL expressions. Don't re-fetch data that's already available.
4. **CEL expressions everywhere.** Wire models together with CEL expressions. Always prefer `data.latest("<name>", "<dataName>").attributes.<field>` over the deprecated `model.<name>.resource.<spec>.<instance>.attributes.<field>` pattern.
5. **Verify before destructive operations.** Always `swamp model get <name> --json` and verify resource IDs before running delete/stop/destroy methods.
6. **Prefer fan-out methods over loops.** When operating on multiple targets, use a single method that handles all targets internally (factory pattern) rather than looping N separate `swamp model method run` calls against the same model. Multiple parallel calls against the same model contend on the per-model lock, causing timeouts. A single fan-out method acquires the lock once and produces all outputs in one execution. Check `swamp model type describe` for methods that accept filters or produce multiple outputs.
7. **Extension npm deps are bundled, not lockfile-tracked.** Swamp's bundler inlines all npm packages (except zod) into extension bundles at bundle time. `deno.lock` and `package.json` do NOT cover extension model dependencies — this is by design. Always pin explicit versions in `npm:` import specifiers (e.g., `npm:lodash-es@4.17.21`).
8. **Reports for reusable data pipelines.** When the task involves building a repeatable pipeline to transform, aggregate, or analyze model output (security reports, cost analysis, compliance checks, summaries), create a report extension. Use the `swamp-report` skill for guidance.
1. **Search before you build.** When automating AWS, APIs, or any external
service: (a) search local types with `swamp model type search <query>`, (b)
search community extensions with `swamp extension search <query>`, (c) if a
community extension exists, install it with `swamp extension pull <package>`
instead of building from scratch, (d) only create a custom extension model in
`extensions/models/` if nothing exists. Use the `swamp-extension-model` skill
for guidance. The `command/shell` model is ONLY for ad-hoc one-off shell
commands, NEVER for wrapping CLI tools or building integrations.
2. **Extend, don't be clever.** When a model covers the domain but lacks the
method you need, extend it with `export const extension` — don't bypass it
with shell scripts, CLI tools, or multi-step hacks. One method, one purpose.
Use `swamp model type describe <type> --json` to check available methods.
3. **Use the data model.** Once data exists in a model (via `lookup`, `start`,
`sync`, etc.), reference it with CEL expressions. Don't re-fetch data that's
already available.
4. **CEL expressions everywhere.** Wire models together with CEL expressions.
Always prefer `data.latest("<name>", "<dataName>").attributes.<field>` over
the deprecated `model.<name>.resource.<spec>.<instance>.attributes.<field>`
pattern.
5. **Verify before destructive operations.** Always
`swamp model get <name> --json` and verify resource IDs before running
delete/stop/destroy methods.
6. **Prefer fan-out methods over loops.** When operating on multiple targets,
use a single method that handles all targets internally (factory pattern)
rather than looping N separate `swamp model method run` calls against the
same model. Multiple parallel calls against the same model contend on the
per-model lock, causing timeouts. A single fan-out method acquires the lock
once and produces all outputs in one execution. Check
`swamp model type describe` for methods that accept filters or produce
multiple outputs.
7. **Extension npm deps are bundled, not lockfile-tracked.** Swamp's bundler
inlines all npm packages (except zod) into extension bundles at bundle time.
`deno.lock` and `package.json` do NOT cover extension model dependencies —
this is by design. Always pin explicit versions in `npm:` import specifiers
(e.g., `npm:lodash-es@4.17.21`).
8. **Reports for reusable data pipelines.** When the task involves building a
repeatable pipeline to transform, aggregate, or analyze model output
(security reports, cost analysis, compliance checks, summaries), create a
report extension. Use the `swamp-report` skill for guidance.

## Skills

**IMPORTANT:** Always load swamp skills, even when in plan mode. The skills provide
essential context for working with this repository.
**IMPORTANT:** Always load swamp skills, even when in plan mode. The skills
provide essential context for working with this repository.

- `swamp-model` - Work with swamp models (creating, editing, validating)
- `swamp-workflow` - Work with workflows (creating, editing, running)
Expand All @@ -39,4 +71,5 @@ Always start by using the `swamp-model` skill to work with swamp models.
## Commands

Use `swamp --help` to see available commands.

<!-- END swamp managed section -->
29 changes: 26 additions & 3 deletions issue-lifecycle/extensions/models/_lib/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,16 @@ export const Phase = z.enum([
"approved",
"implementing",
"pr_open",
"pr_failed",
"releasing",
"done",
]);

export type Phase = z.infer<typeof Phase>;

/** Minimum time (ms) between link_pr and pr_merged/pr_failed to allow CI to run. */
export const PR_COOLDOWN_MS = 3 * 60 * 1000;

/** Valid transitions: method name → allowed source phases */
export const TRANSITIONS: Record<string, Phase[]> = {
start: [
Expand All @@ -59,16 +64,21 @@ export const TRANSITIONS: Record<string, Phase[]> = {
"approved",
"implementing",
"pr_open",
"pr_failed",
"releasing",
],
triage: ["triaging"],
plan: ["classified"],
iterate: ["plan_generated"],
approve: ["plan_generated"],
implement: ["approved"],
implement: ["approved", "pr_failed"],
adversarial_review: ["plan_generated"],
resolve_findings: ["plan_generated"],
link_pr: ["implementing", "pr_open"],
complete: ["implementing", "pr_open"],
link_pr: ["implementing", "pr_open", "pr_failed"],
pr_merged: ["pr_open"],
pr_failed: ["pr_open"],
ship: ["releasing"],
complete: ["implementing", "pr_open", "releasing"],
};

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -170,10 +180,23 @@ export const PullRequestSchema = z.object({
"Canonical URL of the pull request. Opaque to the model — the agent " +
"supplies whatever URL their git host produced.",
),
attempt: z.number().describe(
"Sequential attempt number. Starts at 1 on the first link_pr call, " +
"incremented on each subsequent link_pr call after a pr_failed cycle.",
),
linkedAt: z.string().describe(
"ISO-8601 timestamp of when link_pr was called. Updated on every " +
"subsequent link_pr call so the record reflects the latest link.",
),
mergedAt: z.string().optional().describe(
"ISO-8601 timestamp of when pr_merged was called. Set once.",
),
failedAt: z.string().optional().describe(
"ISO-8601 timestamp of when pr_failed was called. Cleared on next link_pr.",
),
failureReason: z.string().optional().describe(
"Why the PR failed (CI failure, review rejection, etc.). Cleared on next link_pr.",
),
});

export type PullRequestData = z.infer<typeof PullRequestSchema>;
Loading
Loading