diff --git a/.claude/skills/triage/SKILL.md b/.claude/skills/triage/SKILL.md new file mode 100644 index 0000000..8319baf --- /dev/null +++ b/.claude/skills/triage/SKILL.md @@ -0,0 +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, 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 and record it where the next run, a human, or +Ralph can pick it up. Execution happens elsewhere. + +**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 + 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. + +3. **Gather signal** (cheap reads first; stop when you have enough — be frugal + with tokens): + - **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 e2e here — that is CI's job. + +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. + +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 new file mode 100644 index 0000000..72cd9d2 --- /dev/null +++ b/.github/workflows/triage.yml @@ -0,0 +1,59 @@ +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 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: + # 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: 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: + 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: 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/.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 new file mode 100644 index 0000000..b2dbec7 --- /dev/null +++ b/docs/loop/README.md @@ -0,0 +1,68 @@ +# 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**. + +## The pieces (and where they live) + +| 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 | + +**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 + +``` +schedule (weekday mornings) + │ + ▼ +triage.yml ──runs──▶ /triage skill + │ + reads: recent commits · latest CI run · open `triage` issues + │ + 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. +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`). +- **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 reading CI and filing/closing issues. + +## 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/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": "" + } + ] }