From 66aac3d2c02b99f3ede10e34e336820223aa992f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 9 Jun 2026 01:04:20 +0000 Subject: [PATCH 1/3] feat(loop): add scheduled triage automation (the loop heartbeat) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the missing "heartbeat" pillar of a loop-engineered workflow on top of the existing Ralph loop, sub-agents, skills, and connectors: - .github/workflows/triage.yml — weekday cron that runs the triage skill on Sonnet, discovers work, and files actionable items (discovery only, never fixes). - .claude/skills/triage/SKILL.md — the loop's brain: reads recent commits, latest CI, and open issues; records findings; files triage-labeled issues. - docs/loop/state.md — durable memory that survives between runs. - docs/loop/README.md — how the loop fits together and how to steer/disable it. https://claude.ai/code/session_016JcSoq1xtvEVV5sMZC5oHH --- .claude/skills/triage/SKILL.md | 97 ++++++++++++++++++++++++++++++++++ .github/workflows/triage.yml | 63 ++++++++++++++++++++++ docs/loop/README.md | 57 ++++++++++++++++++++ docs/loop/state.md | 21 ++++++++ 4 files changed, 238 insertions(+) create mode 100644 .claude/skills/triage/SKILL.md create mode 100644 .github/workflows/triage.yml create mode 100644 docs/loop/README.md create mode 100644 docs/loop/state.md diff --git a/.claude/skills/triage/SKILL.md b/.claude/skills/triage/SKILL.md new file mode 100644 index 0000000..95b393c --- /dev/null +++ b/.claude/skills/triage/SKILL.md @@ -0,0 +1,97 @@ +--- +name: triage +description: Run autonomous discovery + triage on the Iridium repo. Use when asked to "triage", run the morning loop, scan for regressions/bugs/stale work, summarize CI failures or recent commits, or refresh the loop state. Reads recent commits, open issues, and the latest CI run, then records findings in docs/loop/state.md and files actionable items as GitHub issues. Invoked on a schedule by .github/workflows/triage.yml and runnable by hand in any session. +--- + +# Triage loop + +You are the discovery + triage stage of a [loop-engineered](../../../docs/loop/README.md) +workflow. You run unattended on a schedule. Your job is **not** to fix things — +it is to find what is worth doing, write it down where the next run can see it, +and file the clearly-actionable items. Execution happens elsewhere (Ralph, a +human, or a follow-up agent). + +The repo is your memory. You forget everything between runs. `docs/loop/state.md` +does not. **Read it first, write it last.** + +## Procedure + +1. **Load memory.** Read `docs/loop/state.md`. Note the `Operator notes` (human + steering — honor it), the last-run timestamp, and every entry already under + `Open findings`. You will not re-file anything already listed there. + +2. **Gather signal** (cheap reads first, stop when you have enough — be frugal + with tokens): + - **Recent commits:** `git log --oneline --no-merges -20`. What changed since + the last run timestamp? + - **Latest CI:** check the most recent run of the `CI` workflow on the default + branch. If a job failed, pull just the failing step's log — do not download + whole logs. Use `gh run list --workflow=ci.yml --branch=main -L 5` and + `gh run view --log-failed` when `gh` is available, otherwise the GitHub + MCP tools. + - **Open issues:** `gh issue list --state open -L 30` (or GitHub MCP). Which + are stale, duplicated, or already fixed by a recent commit? + - **Cheap health probes** (only if commits touched the relevant area): + `bun run typecheck`, `bun run lint`. Do not run the full e2e suite here — + that is CI's job, not triage's. + +3. **Decide.** For each candidate finding classify it: + - **actionable** — a concrete, scoped change with a clear done-condition + (a failing test, a type error, a dead route, a missing validation). File it. + - **watch** — real but not yet ripe (a flaky test seen once, a TODO). Record + under `Open findings`, do not file. + - **noise** — already tracked, already fixed, or out of scope. Skip silently. + +4. **File actionable items** as GitHub issues, labeled `triage`: + - Title: imperative and specific (`Fix type error in app/models/thread.server.ts`). + - Body: the evidence (log excerpt, commit SHA, file:line), a proposed + done-condition, and a backlink noting it was filed by the triage loop. + - Before filing, search open issues for a near-duplicate. If one exists, add a + comment with the new evidence instead of opening a second issue. + - `gh issue create --label triage --title "..." --body "..."` (create the + `triage` label first if missing), or the GitHub MCP `issue_write` tool. + +5. **Write memory.** Update `docs/loop/state.md` (see format below): refresh + `Last run`, move resolved items to `Resolved / archived`, and list every + current finding with its issue link. Keep it tight — this file is read in full + every run, so prune aggressively. + +6. **Persist.** Commit only `docs/loop/state.md` with message + `chore(loop): triage [skip ci]` and push to the default branch. + Never commit anything else from a triage run. + +## state.md format + +```markdown +# Loop State + +## Operator notes + +- ... + +## Last run +- Timestamp: 2026-06-09T13:00Z +- Commits since prior run: 4 +- Summary: one or two sentences. + +## Open findings +- [actionable] Fix type error in thread.server.ts — #42 — filed 2026-06-09 +- [watch] auth.spec.ts flaked once on CI run 1817 — seen 2026-06-09 + +## Resolved / archived +- [actionable] Dead /api/legacy route — #38 — closed 2026-06-07 +``` + +## Discipline (this is the part that keeps the loop honest) + +- **One source of truth.** A finding lives in `Open findings` until its issue is + closed, then it moves to `Resolved / archived`. Never duplicate. +- **Evidence or it didn't happen.** Every finding cites a commit SHA, a CI run, + or a `file:line`. No vibes-based issues. +- **Token frugality.** Prefer `--log-failed` over full logs, `git log` over + reading diffs, targeted `Grep` over reading whole files. A triage run that + reads the entire codebase is a bug. +- **Stay in your lane.** You discover and record. You do not refactor, you do not + fix, you do not open PRs. Hand execution to the next stage. +- **Quiet on nothing.** If there is nothing actionable, still update `Last run` + (so the chain stays unbroken) but file no issues and add no noise. diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml new file mode 100644 index 0000000..f4b24ac --- /dev/null +++ b/.github/workflows/triage.yml @@ -0,0 +1,63 @@ +name: Triage Loop + +# The "heartbeat" of the loop-engineered workflow (see docs/loop/README.md). +# Runs the `triage` skill on a schedule: it discovers work, records findings in +# docs/loop/state.md, and files actionable items as `triage`-labeled issues. +# It does NOT fix anything — execution is a separate stage. + +on: + schedule: + # 13:00 UTC on weekdays (~8am ET). Adjust or delete to change cadence. + - cron: '0 13 * * 1-5' + workflow_dispatch: + +# Only one triage run at a time; a new run cancels nothing but waits its turn. +concurrency: + group: triage-loop + cancel-in-progress: false + +permissions: + contents: write # commit docs/loop/state.md back to the repo + issues: write # file actionable findings + actions: read # read CI run results + pull-requests: read + +jobs: + triage: + name: Discover + triage + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 50 # enough history for `git log` since last run + + - uses: oven-sh/setup-bun@v2 + + # Install so the skill can run cheap health probes (typecheck/lint) + # when a finding needs confirming. e2e is left to the CI workflow. + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Generate Prisma client + run: bunx --bun prisma generate + + - name: Configure git identity + run: | + git config user.name "iridium-triage-bot" + git config user.email "triage-bot@users.noreply.github.com" + + - name: Run triage loop + uses: anthropics/claude-code-action@v1 + env: + # gh CLI (used by the skill) authenticates via this token. + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + github_token: ${{ secrets.GITHUB_TOKEN }} + # Sonnet keeps the recurring token cost low; triage is a + # reading/judgement task, not heavy generation. + prompt: '/triage' + claude_args: | + --model claude-sonnet-4-6 + --allowedTools "Bash,Read,Write,Edit,Glob,Grep,mcp__github" diff --git a/docs/loop/README.md b/docs/loop/README.md new file mode 100644 index 0000000..56b90ed --- /dev/null +++ b/docs/loop/README.md @@ -0,0 +1,57 @@ +# The Iridium loop + +This repo is set up for **loop engineering**: instead of prompting an agent turn +by turn, you run small systems that prompt the agents for you. The pieces below +already exist in the repo; this directory adds the one that was missing — the +scheduled **heartbeat** — and the durable **memory** the loop runs on. + +## The pieces (and where they live) + +| Loop pillar | In this repo | +| -------------- | ------------------------------------------------------------------------- | +| **Heartbeat** | `.github/workflows/triage.yml` — scheduled discovery + triage | +| **Memory** | `docs/loop/state.md` — survives between runs; the agent forgets, it doesn't | +| **Skills** | `.claude/skills/` — incl. `triage` (the loop's brain) and `iridium-form` | +| **Sub-agents** | `.claude/agents/` — `staff-engineer`, `security-auditor`, `prisma`, … | +| **Worktrees** | `scripts/ralph/ralph.sh` (run in one) + subagent `isolation: worktree` | +| **Connectors** | GitHub, Linear, context7 MCP servers | +| **Execution** | `scripts/ralph/` — the autonomous loop that *does* the work | + +## How the heartbeat works + +``` +schedule (weekday mornings) + │ + ▼ +triage.yml ──runs──▶ /triage skill + │ + reads: recent commits · latest CI run · open issues + │ + writes: docs/loop/state.md (durable memory) + files: GitHub issues labeled `triage` (actionable work) +``` + +Triage **discovers and records only** — it never fixes code or opens PRs. +Execution is a deliberately separate stage so the maker is never the checker. + +## Operating it + +- **Run it now:** Actions tab → *Triage Loop* → *Run workflow* (`workflow_dispatch`). +- **Run it locally / in a session:** invoke the `triage` skill (`/triage`). +- **Steer it:** edit the `Operator notes` section of `docs/loop/state.md`. The + loop reads and obeys it (e.g. "ignore the legacy/ dir", "focus on auth"). +- **Change cadence:** edit the `cron` in `triage.yml`. +- **Turn it off:** delete `triage.yml`, or disable *Triage Loop* in the Actions tab. + +## Required setup + +The workflow needs an `ANTHROPIC_API_KEY` repository secret. The built-in +`GITHUB_TOKEN` covers issue filing and committing `state.md`. + +## A note on cost and quality + +Scheduled agents spend tokens whether or not they find anything, so this loop +runs on Sonnet, prefers cheap reads (`git log`, `--log-failed`) over reading the +whole tree, and is capped at 15 minutes per run. It also does not replace you: +every issue it files is a *claim*, and every fix that follows still needs a human +review before it ships. Build the loop — stay the engineer. diff --git a/docs/loop/state.md b/docs/loop/state.md new file mode 100644 index 0000000..398da85 --- /dev/null +++ b/docs/loop/state.md @@ -0,0 +1,21 @@ +# Loop State + +> Durable memory for the triage loop. The agent forgets everything between runs; +> this file does not. Maintained automatically by `.github/workflows/triage.yml` +> via the `triage` skill. Humans edit only the **Operator notes** section. + +## Operator notes + +- (none yet) + +## Last run +- Timestamp: (never) +- Commits since prior run: — +- Summary: Initial state. The triage loop has not run yet. + +## Open findings + +- (none yet) + +## Resolved / archived +- (none yet) From 542077859b88322794bc228f9fdc52b43c900d7e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 9 Jun 2026 01:39:12 +0000 Subject: [PATCH 2/3] refactor(loop): use GitHub Issues as triage memory, drop state.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A markdown state file isn't queryable, merge-conflicts under parallel runs, and forces the agent to rewrite it whole each run. GitHub Issues is already queryable, merge-safe, human-visible, and wired up — and makes the loop naturally idempotent (it dedups by searching open issues before filing). - Delete docs/loop/state.md. - Rewrite the triage skill around GitHub Issues: dedup against open `triage` issues, file/comment/close via gh, read operator steering from a `triage-meta` issue instead of a file section. Run produces zero file changes. - triage.yml: drop the state-file commit machinery (git identity step, contents:write → contents:read). - README: explain why Issues over a file, with a note that beads is the upgrade path if autonomous multi-agent execution is turned on later. https://claude.ai/code/session_016JcSoq1xtvEVV5sMZC5oHH --- .claude/skills/triage/SKILL.md | 145 +++++++++++++++------------------ .github/workflows/triage.yml | 18 ++-- docs/loop/README.md | 27 ++++-- docs/loop/state.md | 21 ----- 4 files changed, 92 insertions(+), 119 deletions(-) delete mode 100644 docs/loop/state.md diff --git a/.claude/skills/triage/SKILL.md b/.claude/skills/triage/SKILL.md index 95b393c..8319baf 100644 --- a/.claude/skills/triage/SKILL.md +++ b/.claude/skills/triage/SKILL.md @@ -1,97 +1,84 @@ --- name: triage -description: Run autonomous discovery + triage on the Iridium repo. Use when asked to "triage", run the morning loop, scan for regressions/bugs/stale work, summarize CI failures or recent commits, or refresh the loop state. Reads recent commits, open issues, and the latest CI run, then records findings in docs/loop/state.md and files actionable items as GitHub issues. Invoked on a schedule by .github/workflows/triage.yml and runnable by hand in any session. +description: Run autonomous discovery + triage on the Iridium repo. Use when asked to "triage", run the morning loop, scan for regressions/bugs/stale work, or summarize CI failures or recent commits. Reads recent commits, open issues, and the latest CI run, then files actionable findings as `triage`-labeled GitHub issues (deduping against existing ones). GitHub Issues is the loop's memory — there is no state file. Invoked on a schedule by .github/workflows/triage.yml and runnable by hand in any session. --- # Triage loop You are the discovery + triage stage of a [loop-engineered](../../../docs/loop/README.md) workflow. You run unattended on a schedule. Your job is **not** to fix things — -it is to find what is worth doing, write it down where the next run can see it, -and file the clearly-actionable items. Execution happens elsewhere (Ralph, a -human, or a follow-up agent). +it is to find what is worth doing and record it where the next run, a human, or +Ralph can pick it up. Execution happens elsewhere. -The repo is your memory. You forget everything between runs. `docs/loop/state.md` -does not. **Read it first, write it last.** +**GitHub Issues is your memory.** There is no state file. You forget everything +between runs; the issue tracker does not. This makes you naturally idempotent: +before filing anything you search existing issues, so re-running never +duplicates. Read the tracker first, write to it last. + +Use the `gh` CLI when available (it is, in CI, authenticated via `GH_TOKEN`), +otherwise the GitHub MCP tools. ## Procedure -1. **Load memory.** Read `docs/loop/state.md`. Note the `Operator notes` (human - steering — honor it), the last-run timestamp, and every entry already under - `Open findings`. You will not re-file anything already listed there. +1. **Load memory + steering.** + - Open findings: `gh issue list --label triage --state open -L 50 --json number,title,body,labels`. + This is the list you dedup against — never re-file anything already here. + - Operator steering: look for an open issue labeled `triage-meta` titled + "Triage loop — operator notes". If it exists, read it and obey it (focus + areas, paths to ignore). It is the human's steering wheel; honor it over + your own judgement. + +2. **Establish the window.** Find the previous run so you only look at what is + new: `gh run list --workflow=triage.yml --status success -L 2 --json createdAt` + → use the prior run's `createdAt` as your "since" time. If there is no prior + run, use the last 24h. -2. **Gather signal** (cheap reads first, stop when you have enough — be frugal +3. **Gather signal** (cheap reads first; stop when you have enough — be frugal with tokens): - - **Recent commits:** `git log --oneline --no-merges -20`. What changed since - the last run timestamp? - - **Latest CI:** check the most recent run of the `CI` workflow on the default - branch. If a job failed, pull just the failing step's log — do not download - whole logs. Use `gh run list --workflow=ci.yml --branch=main -L 5` and - `gh run view --log-failed` when `gh` is available, otherwise the GitHub - MCP tools. - - **Open issues:** `gh issue list --state open -L 30` (or GitHub MCP). Which - are stale, duplicated, or already fixed by a recent commit? + - **Commits since the window:** `git log --oneline --no-merges --since=""`. + - **Latest CI:** `gh run list --workflow=ci.yml --branch=main -L 5`. For any + failure, pull only the failing step: `gh run view --log-failed`. Never + download whole logs. + - **Stale/duplicate issues:** from the list in step 1, flag issues a recent + commit already resolved, or obvious duplicates. - **Cheap health probes** (only if commits touched the relevant area): - `bun run typecheck`, `bun run lint`. Do not run the full e2e suite here — - that is CI's job, not triage's. + `bun run typecheck`, `bun run lint`. Do **not** run e2e here — that is CI's job. -3. **Decide.** For each candidate finding classify it: - - **actionable** — a concrete, scoped change with a clear done-condition - (a failing test, a type error, a dead route, a missing validation). File it. - - **watch** — real but not yet ripe (a flaky test seen once, a TODO). Record - under `Open findings`, do not file. +4. **Classify each candidate.** + - **actionable** — concrete, scoped, with a clear done-condition (a failing + test, a type error, a dead route, a missing validation). File it. + - **watch** — real but not ripe (a test that flaked once, a TODO). File only + if it recurs; a single flake is not yet an issue. - **noise** — already tracked, already fixed, or out of scope. Skip silently. -4. **File actionable items** as GitHub issues, labeled `triage`: - - Title: imperative and specific (`Fix type error in app/models/thread.server.ts`). - - Body: the evidence (log excerpt, commit SHA, file:line), a proposed - done-condition, and a backlink noting it was filed by the triage loop. - - Before filing, search open issues for a near-duplicate. If one exists, add a - comment with the new evidence instead of opening a second issue. - - `gh issue create --label triage --title "..." --body "..."` (create the - `triage` label first if missing), or the GitHub MCP `issue_write` tool. - -5. **Write memory.** Update `docs/loop/state.md` (see format below): refresh - `Last run`, move resolved items to `Resolved / archived`, and list every - current finding with its issue link. Keep it tight — this file is read in full - every run, so prune aggressively. - -6. **Persist.** Commit only `docs/loop/state.md` with message - `chore(loop): triage [skip ci]` and push to the default branch. - Never commit anything else from a triage run. - -## state.md format - -```markdown -# Loop State - -## Operator notes - -- ... - -## Last run -- Timestamp: 2026-06-09T13:00Z -- Commits since prior run: 4 -- Summary: one or two sentences. - -## Open findings -- [actionable] Fix type error in thread.server.ts — #42 — filed 2026-06-09 -- [watch] auth.spec.ts flaked once on CI run 1817 — seen 2026-06-09 - -## Resolved / archived -- [actionable] Dead /api/legacy route — #38 — closed 2026-06-07 -``` - -## Discipline (this is the part that keeps the loop honest) - -- **One source of truth.** A finding lives in `Open findings` until its issue is - closed, then it moves to `Resolved / archived`. Never duplicate. -- **Evidence or it didn't happen.** Every finding cites a commit SHA, a CI run, - or a `file:line`. No vibes-based issues. -- **Token frugality.** Prefer `--log-failed` over full logs, `git log` over - reading diffs, targeted `Grep` over reading whole files. A triage run that - reads the entire codebase is a bug. -- **Stay in your lane.** You discover and record. You do not refactor, you do not - fix, you do not open PRs. Hand execution to the next stage. -- **Quiet on nothing.** If there is nothing actionable, still update `Last run` - (so the chain stays unbroken) but file no issues and add no noise. +5. **File actionable findings** as issues, labeled `triage` (create the label + first if missing: `gh label create triage --color FBCA04 --description "Filed by the triage loop"`): + - **Dedup first.** Compare against the open `triage` issues from step 1 and + `gh issue list --search "" --state open`. If a near-duplicate + exists, add a comment with the new evidence instead of opening a second issue. + - Title: imperative and specific — `Fix type error in app/models/thread.server.ts`. + - Body: the evidence (log excerpt, commit SHA, `file:line`), a proposed + done-condition, and a line noting it was filed by the triage loop. + - `gh issue create --label triage --title "..." --body "..."`. + +6. **Close the loop on resolved items.** If a `triage` issue is clearly fixed by + a commit in the window, close it with a reference: + `gh issue close --comment "Resolved by ."`. + +7. **Report, don't persist.** Print a one-paragraph summary of the run (window, + commits scanned, issues filed/closed). Commit nothing — the tracker is the + record, not the repo. A triage run produces zero file changes. + +## Discipline (this is what keeps the loop honest) + +- **One source of truth.** A finding is an open `triage` issue until it's closed. + Never mirror it into a file or a second issue. +- **Evidence or it didn't happen.** Every issue cites a commit SHA, a CI run, or + a `file:line`. No vibes-based issues. +- **Token frugality.** `--log-failed` over full logs, `git log` over reading + diffs, targeted `Grep` over whole files. A run that reads the whole codebase + is a bug. +- **Stay in your lane.** You discover and record. You do not refactor, fix, or + open PRs. Hand execution to the next stage. +- **Quiet on nothing.** If nothing is actionable, file nothing and say so. An + empty run is a successful run. diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml index f4b24ac..72cd9d2 100644 --- a/.github/workflows/triage.yml +++ b/.github/workflows/triage.yml @@ -1,9 +1,10 @@ name: Triage Loop # The "heartbeat" of the loop-engineered workflow (see docs/loop/README.md). -# Runs the `triage` skill on a schedule: it discovers work, records findings in -# docs/loop/state.md, and files actionable items as `triage`-labeled issues. -# It does NOT fix anything — execution is a separate stage. +# Runs the `triage` skill on a schedule: it discovers work and files actionable +# items as `triage`-labeled GitHub issues — the issue tracker is the loop's +# memory, so this job writes nothing back to the repo. It does NOT fix anything; +# execution is a separate stage. on: schedule: @@ -17,9 +18,9 @@ concurrency: cancel-in-progress: false permissions: - contents: write # commit docs/loop/state.md back to the repo - issues: write # file actionable findings - actions: read # read CI run results + contents: read # checkout + git log only; the loop commits nothing + issues: write # file actionable findings (the loop's memory) + actions: read # read CI run results + prior triage run timestamps pull-requests: read jobs: @@ -42,11 +43,6 @@ jobs: - name: Generate Prisma client run: bunx --bun prisma generate - - name: Configure git identity - run: | - git config user.name "iridium-triage-bot" - git config user.email "triage-bot@users.noreply.github.com" - - name: Run triage loop uses: anthropics/claude-code-action@v1 env: diff --git a/docs/loop/README.md b/docs/loop/README.md index 56b90ed..03da6ad 100644 --- a/docs/loop/README.md +++ b/docs/loop/README.md @@ -3,20 +3,29 @@ This repo is set up for **loop engineering**: instead of prompting an agent turn by turn, you run small systems that prompt the agents for you. The pieces below already exist in the repo; this directory adds the one that was missing — the -scheduled **heartbeat** — and the durable **memory** the loop runs on. +scheduled **heartbeat**. ## The pieces (and where they live) | Loop pillar | In this repo | | -------------- | ------------------------------------------------------------------------- | | **Heartbeat** | `.github/workflows/triage.yml` — scheduled discovery + triage | -| **Memory** | `docs/loop/state.md` — survives between runs; the agent forgets, it doesn't | +| **Memory** | **GitHub Issues** (label `triage`) — queryable, merge-safe, outlives any run | | **Skills** | `.claude/skills/` — incl. `triage` (the loop's brain) and `iridium-form` | | **Sub-agents** | `.claude/agents/` — `staff-engineer`, `security-auditor`, `prisma`, … | | **Worktrees** | `scripts/ralph/ralph.sh` (run in one) + subagent `isolation: worktree` | | **Connectors** | GitHub, Linear, context7 MCP servers | | **Execution** | `scripts/ralph/` — the autonomous loop that *does* the work | +**Why Issues and not a state file?** The loop's memory has to be queryable, +merge-safe under parallel runs, and visible to humans. A markdown file is none of +those — the agent would rewrite it whole every run and two runs would collide. +GitHub Issues already gives us all three for free, and the loop is naturally +idempotent because it dedups by *searching open issues* before filing. (If you +later turn on heavy autonomous multi-agent execution and want a dependency +"ready-queue" to feed Ralph, [`beads`](https://github.com/steveyegge/beads) is +the purpose-built upgrade — but Issues is the right call until then.) + ## How the heartbeat works ``` @@ -25,10 +34,10 @@ schedule (weekday mornings) ▼ triage.yml ──runs──▶ /triage skill │ - reads: recent commits · latest CI run · open issues + reads: recent commits · latest CI run · open `triage` issues │ - writes: docs/loop/state.md (durable memory) - files: GitHub issues labeled `triage` (actionable work) + dedups → files / comments / closes GitHub issues (label `triage`) + writes nothing back to the repo ``` Triage **discovers and records only** — it never fixes code or opens PRs. @@ -38,15 +47,17 @@ Execution is a deliberately separate stage so the maker is never the checker. - **Run it now:** Actions tab → *Triage Loop* → *Run workflow* (`workflow_dispatch`). - **Run it locally / in a session:** invoke the `triage` skill (`/triage`). -- **Steer it:** edit the `Operator notes` section of `docs/loop/state.md`. The - loop reads and obeys it (e.g. "ignore the legacy/ dir", "focus on auth"). +- **See its memory:** the open issues labeled `triage` _are_ the loop's state. +- **Steer it:** open an issue labeled `triage-meta` titled + "Triage loop — operator notes". The loop reads and obeys it (e.g. "ignore the + legacy/ dir", "focus on auth"). It is the human steering wheel. - **Change cadence:** edit the `cron` in `triage.yml`. - **Turn it off:** delete `triage.yml`, or disable *Triage Loop* in the Actions tab. ## Required setup The workflow needs an `ANTHROPIC_API_KEY` repository secret. The built-in -`GITHUB_TOKEN` covers issue filing and committing `state.md`. +`GITHUB_TOKEN` covers reading CI and filing/closing issues. ## A note on cost and quality diff --git a/docs/loop/state.md b/docs/loop/state.md deleted file mode 100644 index 398da85..0000000 --- a/docs/loop/state.md +++ /dev/null @@ -1,21 +0,0 @@ -# Loop State - -> Durable memory for the triage loop. The agent forgets everything between runs; -> this file does not. Maintained automatically by `.github/workflows/triage.yml` -> via the `triage` skill. Humans edit only the **Operator notes** section. - -## Operator notes - -- (none yet) - -## Last run -- Timestamp: (never) -- Commits since prior run: — -- Summary: Initial state. The triage loop has not run yet. - -## Open findings - -- (none yet) - -## Resolved / archived -- (none yet) From 496226bb3f5ab2aa772051bd70227c2ca4b46d62 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 14 Jun 2026 14:17:14 +0000 Subject: [PATCH 3/3] fix(ci): add .prettierignore for vendored tooling, format project docs format:check ran `prettier --check .` over the whole tree with no ignore file, so ~120 vendored agent/spec-kit scaffolding files (.agents, .claude, .specify, .github/agents, skills-lock.json) failed the check and broke CI. Ignore those tool-generated trees and format the genuinely project-owned docs that remained (CLAUDE.md, docs/loop/README.md, scripts/ralph/*). https://claude.ai/code/session_01Kn2TP2U1WdkfWAi3YXidBz --- .prettierignore | 18 +++++ CLAUDE.md | 2 + docs/loop/README.md | 24 +++--- scripts/ralph/CLAUDE.md | 45 +++++------ scripts/ralph/prd.json.example | 132 ++++++++++++++++----------------- 5 files changed, 122 insertions(+), 99 deletions(-) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..a646994 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,18 @@ +# Build artifacts & generated output +/node_modules/ +/.react-router/ +/build/ +app/generated/prisma +/test-results/ +/playwright-report/ +/blob-report/ + +# Lockfiles +bun.lock +skills-lock.json + +# Vendored / tool-generated agent & spec-kit scaffolding (not project source) +.agents/ +.claude/ +.specify/ +.github/agents/ diff --git a/CLAUDE.md b/CLAUDE.md index 67d24ff..6e9ae82 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -164,6 +164,8 @@ Shared chrome is extracted into `SiteHeader` and `SiteFooter` (`app/components/` Prettier with: 80 char width, 4-space indentation, single quotes, semicolons, tailwindcss plugin for class sorting. ESLint with typescript-eslint and react-hooks plugin. + For additional context about technologies to be used, project structure, shell commands, and other important information, read the current plan + diff --git a/docs/loop/README.md b/docs/loop/README.md index 03da6ad..b2dbec7 100644 --- a/docs/loop/README.md +++ b/docs/loop/README.md @@ -7,21 +7,21 @@ scheduled **heartbeat**. ## The pieces (and where they live) -| Loop pillar | In this repo | -| -------------- | ------------------------------------------------------------------------- | -| **Heartbeat** | `.github/workflows/triage.yml` — scheduled discovery + triage | +| Loop pillar | In this repo | +| -------------- | ---------------------------------------------------------------------------- | +| **Heartbeat** | `.github/workflows/triage.yml` — scheduled discovery + triage | | **Memory** | **GitHub Issues** (label `triage`) — queryable, merge-safe, outlives any run | -| **Skills** | `.claude/skills/` — incl. `triage` (the loop's brain) and `iridium-form` | -| **Sub-agents** | `.claude/agents/` — `staff-engineer`, `security-auditor`, `prisma`, … | -| **Worktrees** | `scripts/ralph/ralph.sh` (run in one) + subagent `isolation: worktree` | -| **Connectors** | GitHub, Linear, context7 MCP servers | -| **Execution** | `scripts/ralph/` — the autonomous loop that *does* the work | +| **Skills** | `.claude/skills/` — incl. `triage` (the loop's brain) and `iridium-form` | +| **Sub-agents** | `.claude/agents/` — `staff-engineer`, `security-auditor`, `prisma`, … | +| **Worktrees** | `scripts/ralph/ralph.sh` (run in one) + subagent `isolation: worktree` | +| **Connectors** | GitHub, Linear, context7 MCP servers | +| **Execution** | `scripts/ralph/` — the autonomous loop that _does_ the work | **Why Issues and not a state file?** The loop's memory has to be queryable, merge-safe under parallel runs, and visible to humans. A markdown file is none of those — the agent would rewrite it whole every run and two runs would collide. GitHub Issues already gives us all three for free, and the loop is naturally -idempotent because it dedups by *searching open issues* before filing. (If you +idempotent because it dedups by _searching open issues_ before filing. (If you later turn on heavy autonomous multi-agent execution and want a dependency "ready-queue" to feed Ralph, [`beads`](https://github.com/steveyegge/beads) is the purpose-built upgrade — but Issues is the right call until then.) @@ -45,14 +45,14 @@ Execution is a deliberately separate stage so the maker is never the checker. ## Operating it -- **Run it now:** Actions tab → *Triage Loop* → *Run workflow* (`workflow_dispatch`). +- **Run it now:** Actions tab → _Triage Loop_ → _Run workflow_ (`workflow_dispatch`). - **Run it locally / in a session:** invoke the `triage` skill (`/triage`). - **See its memory:** the open issues labeled `triage` _are_ the loop's state. - **Steer it:** open an issue labeled `triage-meta` titled "Triage loop — operator notes". The loop reads and obeys it (e.g. "ignore the legacy/ dir", "focus on auth"). It is the human steering wheel. - **Change cadence:** edit the `cron` in `triage.yml`. -- **Turn it off:** delete `triage.yml`, or disable *Triage Loop* in the Actions tab. +- **Turn it off:** delete `triage.yml`, or disable _Triage Loop_ in the Actions tab. ## Required setup @@ -64,5 +64,5 @@ The workflow needs an `ANTHROPIC_API_KEY` repository secret. The built-in Scheduled agents spend tokens whether or not they find anything, so this loop runs on Sonnet, prefers cheap reads (`git log`, `--log-failed`) over reading the whole tree, and is capped at 15 minutes per run. It also does not replace you: -every issue it files is a *claim*, and every fix that follows still needs a human +every issue it files is a _claim_, and every fix that follows still needs a human review before it ships. Build the loop — stay the engineer. diff --git a/scripts/ralph/CLAUDE.md b/scripts/ralph/CLAUDE.md index 932764c..e806f48 100644 --- a/scripts/ralph/CLAUDE.md +++ b/scripts/ralph/CLAUDE.md @@ -25,21 +25,24 @@ You are an autonomous coding agent working on a software project. ```json { - "project": "MyApp", - "branchName": "ralph/feature-name", - "description": "One-line summary of the feature", - "userStories": [ - { - "id": "US-001", - "title": "Story title", - "description": "As a ..., I want ... so that ...", - "acceptanceCriteria": ["...", "Typecheck passes (`bun run typecheck`)"], - "order": 1, - "status": "pending", - "blockedReason": "", - "notes": "" - } - ] + "project": "MyApp", + "branchName": "ralph/feature-name", + "description": "One-line summary of the feature", + "userStories": [ + { + "id": "US-001", + "title": "Story title", + "description": "As a ..., I want ... so that ...", + "acceptanceCriteria": [ + "...", + "Typecheck passes (`bun run typecheck`)" + ], + "order": 1, + "status": "pending", + "blockedReason": "", + "notes": "" + } + ] } ``` @@ -68,7 +71,7 @@ When all stories in `prd.json` have `status` of `"done"`, write a file at `.ralph-status.json` (sibling of this CLAUDE.md) containing: ```json -{"status": "complete"} +{ "status": "complete" } ``` Do not rely on free-form text in your reply — the ralph.sh loop looks at the @@ -78,7 +81,7 @@ normally. If you cannot proceed and want the loop to stop instead of retrying, write: ```json -{"status": "blocked", "reason": "short human-readable explanation"} +{ "status": "blocked", "reason": "short human-readable explanation" } ``` ## Progress Report Format @@ -118,10 +121,10 @@ Before committing, check whether nearby agent-instruction files should learn fro 1. Identify directories with edited files. 2. Look for an existing `CLAUDE.md` or `AGENTS.md` in those directories or their parents. 3. Add genuinely reusable knowledge: - - API patterns or conventions specific to that module - - Non-obvious requirements or gotchas - - Cross-file dependencies - - Testing setup specific to that area + - API patterns or conventions specific to that module + - Non-obvious requirements or gotchas + - Cross-file dependencies + - Testing setup specific to that area Do not add story-specific implementation details, temporary debugging notes, or anything already in `progress.txt`. diff --git a/scripts/ralph/prd.json.example b/scripts/ralph/prd.json.example index 1188a81..bf8610d 100644 --- a/scripts/ralph/prd.json.example +++ b/scripts/ralph/prd.json.example @@ -1,68 +1,68 @@ { - "project": "MyApp", - "branchName": "ralph/task-priority", - "description": "Task Priority System - Add priority levels to tasks", - "userStories": [ - { - "id": "US-001", - "title": "Add priority field to database", - "description": "As a developer, I need to store task priority so it persists across sessions.", - "acceptanceCriteria": [ - "Add priority column to tasks table: 'high' | 'medium' | 'low' (default 'medium')", - "Generate and run migration successfully", - "Typecheck passes (`bun run typecheck`)" - ], - "order": 1, - "status": "pending", - "blockedReason": "", - "notes": "" - }, - { - "id": "US-002", - "title": "Display priority indicator on task cards", - "description": "As a user, I want to see task priority at a glance.", - "acceptanceCriteria": [ - "Each task card shows colored priority badge (red=high, yellow=medium, gray=low)", - "Priority visible without hovering or clicking", - "Typecheck passes (`bun run typecheck`)", - "Verify in browser using the agent-browser skill" - ], - "order": 2, - "status": "pending", - "blockedReason": "", - "notes": "" - }, - { - "id": "US-003", - "title": "Add priority selector to task edit", - "description": "As a user, I want to change a task's priority when editing it.", - "acceptanceCriteria": [ - "Priority dropdown in task edit modal", - "Shows current priority as selected", - "Saves immediately on selection change", - "Typecheck passes (`bun run typecheck`)", - "Verify in browser using the agent-browser skill" - ], - "order": 3, - "status": "pending", - "blockedReason": "", - "notes": "" - }, - { - "id": "US-004", - "title": "Filter tasks by priority", - "description": "As a user, I want to filter the task list to see only high-priority items.", - "acceptanceCriteria": [ - "Filter dropdown with options: All | High | Medium | Low", - "Filter persists in URL params", - "Empty state message when no tasks match filter", - "Typecheck passes (`bun run typecheck`)", - "Verify in browser using the agent-browser skill" - ], - "order": 4, - "status": "pending", - "blockedReason": "", - "notes": "" - } - ] + "project": "MyApp", + "branchName": "ralph/task-priority", + "description": "Task Priority System - Add priority levels to tasks", + "userStories": [ + { + "id": "US-001", + "title": "Add priority field to database", + "description": "As a developer, I need to store task priority so it persists across sessions.", + "acceptanceCriteria": [ + "Add priority column to tasks table: 'high' | 'medium' | 'low' (default 'medium')", + "Generate and run migration successfully", + "Typecheck passes (`bun run typecheck`)" + ], + "order": 1, + "status": "pending", + "blockedReason": "", + "notes": "" + }, + { + "id": "US-002", + "title": "Display priority indicator on task cards", + "description": "As a user, I want to see task priority at a glance.", + "acceptanceCriteria": [ + "Each task card shows colored priority badge (red=high, yellow=medium, gray=low)", + "Priority visible without hovering or clicking", + "Typecheck passes (`bun run typecheck`)", + "Verify in browser using the agent-browser skill" + ], + "order": 2, + "status": "pending", + "blockedReason": "", + "notes": "" + }, + { + "id": "US-003", + "title": "Add priority selector to task edit", + "description": "As a user, I want to change a task's priority when editing it.", + "acceptanceCriteria": [ + "Priority dropdown in task edit modal", + "Shows current priority as selected", + "Saves immediately on selection change", + "Typecheck passes (`bun run typecheck`)", + "Verify in browser using the agent-browser skill" + ], + "order": 3, + "status": "pending", + "blockedReason": "", + "notes": "" + }, + { + "id": "US-004", + "title": "Filter tasks by priority", + "description": "As a user, I want to filter the task list to see only high-priority items.", + "acceptanceCriteria": [ + "Filter dropdown with options: All | High | Medium | Low", + "Filter persists in URL params", + "Empty state message when no tasks match filter", + "Typecheck passes (`bun run typecheck`)", + "Verify in browser using the agent-browser skill" + ], + "order": 4, + "status": "pending", + "blockedReason": "", + "notes": "" + } + ] }