From 33da330dd2610103c3feed0aa71bf8a86b3f8a8b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 24 Sep 2026 15:03:10 +0000 Subject: [PATCH 1/5] test: check skill models against the AGENTS.md Skills table in CI Red: no skill declares a model and the table has no Model or Forked column yet. Refs #109. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WwCFb6cH1WSzCS8iob6A7e --- .github/workflows/lint.yaml | 4 ++ package.json | 1 + scripts/check-skill-models.mjs | 81 ++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 scripts/check-skill-models.mjs diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 640aa813..3ce2ccbb 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -51,6 +51,10 @@ jobs: - name: Check Markdown links # Relative links and heading anchors only, no network — see scripts/check-md-links.mjs. run: pnpm check:links + - name: Check skill models + # Each skill's model: and context: against the Skills table in AGENTS.md — see + # scripts/check-skill-models.mjs. + run: pnpm check:skills typecheck: runs-on: ubuntu-latest diff --git a/package.json b/package.json index db4a64cf..39659cc3 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "scripts": { "build": "turbo run build", "check:links": "node scripts/check-md-links.mjs", + "check:skills": "node scripts/check-skill-models.mjs", "clean": "turbo run clean", "lint": "turbo run lint", "test": "turbo run test", diff --git a/scripts/check-skill-models.mjs b/scripts/check-skill-models.mjs new file mode 100644 index 00000000..52007adf --- /dev/null +++ b/scripts/check-skill-models.mjs @@ -0,0 +1,81 @@ +#!/usr/bin/env node +/** + * Skill model check — the Skills table in AGENTS.md against each skill's frontmatter, no network. + * + * Fails when: + * - a skill in `.claude/skills/` has no row in the table; + * - a row names a skill found in neither `.claude/skills/` nor `skills/`; + * - a row's Model column differs from the skill's `model:` (`none` when it has none); + * - a row's Forked column is `yes` and the skill lacks `context: fork`, or the reverse. + * + * Usage: node scripts/check-skill-models.mjs (always checks this repository) + * Exit codes: 0 = table and frontmatter agree, 1 = at least one mismatch. + */ + +import { existsSync, readdirSync, readFileSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const ROOT = resolve(join(dirname(fileURLToPath(import.meta.url)), "..")); +const SKILL_DIRS = [".claude/skills", "skills"]; + +/** The rows of the table under `## Skills` in AGENTS.md, as cells keyed by header. */ +function tableRows() { + const agents = readFileSync(join(ROOT, "AGENTS.md"), "utf8"); + const section = agents.split(/^## Skills$/m)[1]?.split(/^## /m)[0] ?? ""; + const lines = section.split("\n").filter((line) => line.startsWith("|")); + const cells = (line) => line.split("|").slice(1, -1).map((cell) => cell.trim()); + const [header, , ...body] = lines.map(cells); + if (!header) return []; + return body.map((row) => Object.fromEntries(header.map((name, i) => [name, row[i] ?? ""]))); +} + +/** `model` and `context` from a SKILL.md's frontmatter, or undefined when a key is absent. */ +function frontmatter(path) { + const block = readFileSync(path, "utf8").match(/^---\n([\s\S]*?)\n---/)?.[1] ?? ""; + const value = (key) => block.match(new RegExp(`^${key}:\\s*(.+)$`, "m"))?.[1].trim(); + return { model: value("model"), context: value("context") }; +} + +const errors = []; +const rows = tableRows(); +const listed = new Set(); + +for (const row of rows) { + const name = row.Skill?.match(/`([^`]+)`/)?.[1]; + if (!name) { + errors.push(`AGENTS.md: a Skills row has no skill name in backticks: ${JSON.stringify(row)}`); + continue; + } + listed.add(name); + const dir = SKILL_DIRS.find((d) => existsSync(join(ROOT, d, name, "SKILL.md"))); + if (!dir) { + errors.push(`AGENTS.md: \`${name}\` is in the Skills table but not in ${SKILL_DIRS.join(" or ")}`); + continue; + } + const path = `${dir}/${name}/SKILL.md`; + const { model, context } = frontmatter(join(ROOT, path)); + const declaredModel = model ?? "none"; + if (row.Model !== declaredModel) { + errors.push(`${path}: model is ${declaredModel}, AGENTS.md says ${row.Model || "nothing"}`); + } + const forked = context === "fork" ? "yes" : "no"; + if (row.Forked !== forked) { + errors.push( + `${path}: context is ${context ?? "none"} (Forked ${forked}), AGENTS.md says ${row.Forked || "nothing"}`, + ); + } +} + +for (const entry of readdirSync(join(ROOT, ".claude/skills"), { withFileTypes: true })) { + if (entry.isDirectory() && !listed.has(entry.name)) { + errors.push(`.claude/skills/${entry.name}: missing from the Skills table in AGENTS.md`); + } +} + +if (errors.length > 0) { + console.error(errors.join("\n")); + console.error(`\n${errors.length} skill model mismatch(es).`); + process.exit(1); +} +console.log(`Skill models agree with AGENTS.md (${rows.length} rows).`); From d4a46521815e36c592ee13388c52e538b4473db2 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 24 Sep 2026 15:04:56 +0000 Subject: [PATCH 2/5] chore: declare a model for each agent skill stage Review, triage, design and memory stages fork on opus; implement and E2E fork on sonnet; file-issue and cut-release declare sonnet inline. work-issue invokes each stage through the Skill tool with arguments instead of a general-purpose agent. AGENTS.md gains Model and Forked columns. Closes #109. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WwCFb6cH1WSzCS8iob6A7e --- .claude/skills/cut-release/SKILL.md | 1 + .claude/skills/design-feature/SKILL.md | 2 ++ .claude/skills/e2e-device/SKILL.md | 2 ++ .claude/skills/file-issue/SKILL.md | 1 + .claude/skills/implement-issue/SKILL.md | 2 ++ .claude/skills/review-memory/SKILL.md | 2 ++ .claude/skills/review-pr/SKILL.md | 2 ++ .claude/skills/triage-issue/SKILL.md | 2 ++ .claude/skills/work-issue/SKILL.md | 27 +++++++++++++------- AGENTS.md | 33 ++++++++++++++----------- 10 files changed, 50 insertions(+), 24 deletions(-) diff --git a/.claude/skills/cut-release/SKILL.md b/.claude/skills/cut-release/SKILL.md index 6cced024..9208ab5c 100644 --- a/.claude/skills/cut-release/SKILL.md +++ b/.claude/skills/cut-release/SKILL.md @@ -1,6 +1,7 @@ --- name: cut-release description: Cut a release of the three npm packages - propose the version from the Unreleased changelog, bump versions in lockstep, open the release PR, and after it merges create the GitHub release that triggers publishing, only on an explicit yes. Use when asked to release, cut a version, tag or publish. +model: sonnet --- # Cut a release diff --git a/.claude/skills/design-feature/SKILL.md b/.claude/skills/design-feature/SKILL.md index ca41fc54..6324481f 100644 --- a/.claude/skills/design-feature/SKILL.md +++ b/.claude/skills/design-feature/SKILL.md @@ -1,6 +1,8 @@ --- name: design-feature description: Design a feature that is too big for one PR - verify the issue's claims against the code, choose the shape (modules, public API, calls vs events, ports), cut it into ordered slices each with its own acceptance criteria, and after a human approves, file one child issue per slice. Use on issues labelled status:needs-design, or when asked to design, shape or break down a feature. +model: opus +context: fork --- # Design a feature diff --git a/.claude/skills/e2e-device/SKILL.md b/.claude/skills/e2e-device/SKILL.md index 89b9b58b..566824d5 100644 --- a/.claude/skills/e2e-device/SKILL.md +++ b/.claude/skills/e2e-device/SKILL.md @@ -1,6 +1,8 @@ --- name: e2e-device description: Build and run a playground app on an iOS simulator or Android emulator, connect it to this repo's Appduct daemon and drive it through the CLI - a smoke pass over the five demo tools plus the calls that prove a specific change works. Use when a PR needs device E2E evidence, when asked to test on a simulator, or when a work-issue orchestrator delegates E2E. +model: sonnet +context: fork --- # E2E on a device diff --git a/.claude/skills/file-issue/SKILL.md b/.claude/skills/file-issue/SKILL.md index 4c0d396b..0ee9f0fe 100644 --- a/.claude/skills/file-issue/SKILL.md +++ b/.claude/skills/file-issue/SKILL.md @@ -1,6 +1,7 @@ --- name: file-issue description: Turn a request or a discovered bug into a GitHub issue in this repo's format - interview the person until the intent is well defined, check for duplicates, then file. Use when asked to file, open, create or write up an issue, when someone describes a feature they want, or when you hit a bug you should not fix in the current change. +model: sonnet --- # File an issue diff --git a/.claude/skills/implement-issue/SKILL.md b/.claude/skills/implement-issue/SKILL.md index f75d12d9..0d9fc62d 100644 --- a/.claude/skills/implement-issue/SKILL.md +++ b/.claude/skills/implement-issue/SKILL.md @@ -1,6 +1,8 @@ --- name: implement-issue description: Implement a GitHub issue tests-first - take the acceptance criteria from the issue, write red tests against the public API, commit them, open a draft PR, then implement in checkpoint commits until green, with user-facing docs as one of the criteria. Use when asked to implement, build or fix something tracked as an issue, or when a work-issue orchestrator delegates implementation. +model: sonnet +context: fork --- # Implement an issue diff --git a/.claude/skills/review-memory/SKILL.md b/.claude/skills/review-memory/SKILL.md index a76fea81..bf5ae211 100644 --- a/.claude/skills/review-memory/SKILL.md +++ b/.claude/skills/review-memory/SKILL.md @@ -1,6 +1,8 @@ --- name: review-memory description: Curate agent memory - read the lessons inbox and the curated lessons file, promote what repeats, prune what is stale, open a memory-only PR and merge it. Use weekly, when the inbox has notes, when a lessons section is over its cap, or when asked to review, consolidate or dream over memory. +model: opus +context: fork --- # Review memory diff --git a/.claude/skills/review-pr/SKILL.md b/.claude/skills/review-pr/SKILL.md index e6279c7f..d534ce90 100644 --- a/.claude/skills/review-pr/SKILL.md +++ b/.claude/skills/review-pr/SKILL.md @@ -1,6 +1,8 @@ --- name: review-pr description: Adversarial code review of a PR or branch - hunt for concrete failures, drop low-ROI comments, verify every finding, post inline comments and a verdict through gh. Use when asked to review a PR, a branch or the current diff, or when a work-issue orchestrator delegates review. +model: opus +context: fork --- # Review a PR diff --git a/.claude/skills/triage-issue/SKILL.md b/.claude/skills/triage-issue/SKILL.md index 5f86f153..00ebc64c 100644 --- a/.claude/skills/triage-issue/SKILL.md +++ b/.claude/skills/triage-issue/SKILL.md @@ -1,6 +1,8 @@ --- name: triage-issue description: Triage a bug report - rank hypotheses, verify the top three by static analysis, name the root cause with path and line, and propose a module-level fix. Sets the status label. Use when asked to triage, investigate or diagnose a bug, or when a type:bug issue carries status:needs-triage. Features are not triaged; they go through file-issue and design-feature. +model: opus +context: fork --- # Triage a bug diff --git a/.claude/skills/work-issue/SKILL.md b/.claude/skills/work-issue/SKILL.md index e4ad1813..8dadde47 100644 --- a/.claude/skills/work-issue/SKILL.md +++ b/.claude/skills/work-issue/SKILL.md @@ -1,17 +1,21 @@ --- name: work-issue -description: Orchestrate one GitHub issue from status:ready to a reviewed, device-tested PR by delegating implementation, review and E2E to subagents and reasoning only over their reports. Use when asked to work, deliver, take or drive an issue end to end. +description: Orchestrate one GitHub issue from status:ready to a reviewed, device-tested PR by delegating implementation, review and E2E to their forked skills and reasoning only over their reports. Use when asked to work, deliver, take or drive an issue end to end. --- # Work an issue You are the chief of staff for one issue. You never write code, run builds or post review -comments yourself. You read the issue, decide the next step, delegate it to a subagent with the -matching skill, and reason over the report that comes back. That keeps your context about the -issue, not about file contents. +comments yourself. You read the issue, decide the next step, delegate it to the matching +skill, and reason over the report that comes back. That keeps your context about the issue, +not about file contents. -Subagents: in Claude Code use the Agent tool with a fresh general-purpose agent per step. In -OpenCode use the task tool. Either way the prompt is short and names the skill to load. +Delegating: in Claude Code, invoke the stage's skill with the Skill tool and pass the task as +its arguments. Each stage skill is forked (`context: fork` in `AGENTS.md`'s Skills table), so it +runs in its own subagent on its own model and hands back only its report. Never start an agent +that then loads the skill, and never pass a model: either one overrides the skill's model. In +OpenCode, use the task tool with a prompt that names the skill to load; there every stage runs +on the session model. Read the `work-issue` section of `.agents/memory/LESSONS.md` before starting, plus General. @@ -35,8 +39,11 @@ phase to resume at. ## 2. Delegate, in order -Each prompt has the same shape: the issue number, the branch, the one thing to do, "load the -`` skill and follow it", and "end with that skill's report and nothing else". +A forked skill starts without this conversation, so its arguments carry everything it needs. +Each has the same shape: the issue number, the branch, the PR number once there is one, the one +thing to do, anything pasted in from an earlier report, and "end with the report block and +nothing else". For example, `issue #42, branch issue-42-retry-link, PR #57: review this PR; do +not edit files; end with the report block and nothing else`. 1. **Implement.** `implement-issue`. Expect the draft PR and a criteria count. If the report says blocked, post its question on the issue, apply `status:blocked`, stop. @@ -76,7 +83,9 @@ Implement: done (5/5 green) Review: round 2, approve E2E: pass (iOS) Ready: y Each skill ends with a fixed block (`implement-issue`, `review-pr`, `e2e-device`, `triage-issue`, `design-feature`). Reason only over those and the issue. If a report is -missing the block or adds narration, ask that subagent for the block, do not guess from prose. +missing the block or adds narration, do not guess from prose: invoke the skill again with the +same arguments plus "the last run ended without its report block; report on the work already +done". ## Your own report diff --git a/AGENTS.md b/AGENTS.md index 73066eec..8a7cae84 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -69,19 +69,22 @@ no restating the question. Commit subjects: conventional prefix, imperative, und ## Skills Load the skill before starting the matching task. They live in `.claude/skills/`. +Model and Forked mirror each skill's `model:` and `context: fork` frontmatter, and +`pnpm check:skills` fails CI when they drift. A forked skill runs in its own subagent on its +model however it is started, and sees only the arguments it was invoked with. -| Task | Skill | -| --- | --- | -| Designing or writing any non-trivial code | `architecture` | -| Implementing an issue (tests first, draft PR, checkpoints) | `implement-issue` | -| Reviewing a PR or branch | `review-pr` | -| Investigating a bug report | `triage-issue` | -| Designing a feature too big for one PR, or sizing one | `design-feature` | -| Turning a request or a found bug into an issue (interviews first) | `file-issue` | -| Running the app on a simulator and driving it through the CLI | `e2e-device` | -| Writing or editing anything an Appduct user reads: READMEs, `docs/`, website, the shipped skill, CLI help, error messages | `writing-user-docs` | -| Adding, amending or reviewing an entry in `CHANGELOG.md` | `writing-changelog` | -| Cutting a release | `cut-release` | -| Curating agent memory (weekly, or when the inbox has notes) | `review-memory` | -| Taking an issue from `status:ready` to a reviewed, tested PR | `work-issue` (orchestrator) | -| Driving an Appduct-enabled app as a user of Appduct | `appduct` (in `skills/`) | +| Task | Skill | Model | Forked | +| --- | --- | --- | --- | +| Designing or writing any non-trivial code | `architecture` | none | no | +| Implementing an issue (tests first, draft PR, checkpoints) | `implement-issue` | sonnet | yes | +| Reviewing a PR or branch | `review-pr` | opus | yes | +| Investigating a bug report | `triage-issue` | opus | yes | +| Designing a feature too big for one PR, or sizing one | `design-feature` | opus | yes | +| Turning a request or a found bug into an issue (interviews first) | `file-issue` | sonnet | no | +| Running the app on a simulator and driving it through the CLI | `e2e-device` | sonnet | yes | +| Writing or editing anything an Appduct user reads: READMEs, `docs/`, website, the shipped skill, CLI help, error messages | `writing-user-docs` | none | no | +| Adding, amending or reviewing an entry in `CHANGELOG.md` | `writing-changelog` | none | no | +| Cutting a release | `cut-release` | sonnet | no | +| Curating agent memory (weekly, or when the inbox has notes) | `review-memory` | opus | yes | +| Taking an issue from `status:ready` to a reviewed, tested PR | `work-issue` (orchestrator) | none | no | +| Driving an Appduct-enabled app as a user of Appduct | `appduct` (in `skills/`) | none | no | From 20fb22ab284dac9dddb112b534f44382be11fa8d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 24 Sep 2026 15:05:26 +0000 Subject: [PATCH 3/5] test: compare each skill's effort with the AGENTS.md Skills table Red: no skill declares effort and the table has no Effort column yet. Refs #109. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WwCFb6cH1WSzCS8iob6A7e --- .github/workflows/lint.yaml | 2 +- scripts/check-skill-models.mjs | 24 +++++++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 3ce2ccbb..a455d048 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -52,7 +52,7 @@ jobs: # Relative links and heading anchors only, no network — see scripts/check-md-links.mjs. run: pnpm check:links - name: Check skill models - # Each skill's model: and context: against the Skills table in AGENTS.md — see + # Each skill's model:, effort: and context: against the Skills table in AGENTS.md — see # scripts/check-skill-models.mjs. run: pnpm check:skills diff --git a/scripts/check-skill-models.mjs b/scripts/check-skill-models.mjs index 52007adf..cb63873f 100644 --- a/scripts/check-skill-models.mjs +++ b/scripts/check-skill-models.mjs @@ -5,7 +5,8 @@ * Fails when: * - a skill in `.claude/skills/` has no row in the table; * - a row names a skill found in neither `.claude/skills/` nor `skills/`; - * - a row's Model column differs from the skill's `model:` (`none` when it has none); + * - a row's Model or Effort column differs from the skill's `model:` or `effort:` (`none` + * when the key is absent); * - a row's Forked column is `yes` and the skill lacks `context: fork`, or the reverse. * * Usage: node scripts/check-skill-models.mjs (always checks this repository) @@ -30,11 +31,11 @@ function tableRows() { return body.map((row) => Object.fromEntries(header.map((name, i) => [name, row[i] ?? ""]))); } -/** `model` and `context` from a SKILL.md's frontmatter, or undefined when a key is absent. */ +/** `model`, `effort` and `context` from a SKILL.md's frontmatter, undefined when a key is absent. */ function frontmatter(path) { const block = readFileSync(path, "utf8").match(/^---\n([\s\S]*?)\n---/)?.[1] ?? ""; const value = (key) => block.match(new RegExp(`^${key}:\\s*(.+)$`, "m"))?.[1].trim(); - return { model: value("model"), context: value("context") }; + return { model: value("model"), effort: value("effort"), context: value("context") }; } const errors = []; @@ -54,15 +55,20 @@ for (const row of rows) { continue; } const path = `${dir}/${name}/SKILL.md`; - const { model, context } = frontmatter(join(ROOT, path)); - const declaredModel = model ?? "none"; - if (row.Model !== declaredModel) { - errors.push(`${path}: model is ${declaredModel}, AGENTS.md says ${row.Model || "nothing"}`); + const declared = frontmatter(join(ROOT, path)); + for (const [key, column] of [ + ["model", "Model"], + ["effort", "Effort"], + ]) { + const value = declared[key] ?? "none"; + if (row[column] !== value) { + errors.push(`${path}: ${key} is ${value}, AGENTS.md says ${row[column] || "nothing"}`); + } } - const forked = context === "fork" ? "yes" : "no"; + const forked = declared.context === "fork" ? "yes" : "no"; if (row.Forked !== forked) { errors.push( - `${path}: context is ${context ?? "none"} (Forked ${forked}), AGENTS.md says ${row.Forked || "nothing"}`, + `${path}: context is ${declared.context ?? "none"} (Forked ${forked}), AGENTS.md says ${row.Forked || "nothing"}`, ); } } From 22f1012e8420fcc9bd0c9c9b9f37f37d8d16010c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 24 Sep 2026 15:06:06 +0000 Subject: [PATCH 4/5] chore: declare reasoning effort for each agent skill stage Review, triage, design and memory run at high; implement and file-issue at medium; E2E and cut-release at low. AGENTS.md gains an Effort column. Closes #109. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WwCFb6cH1WSzCS8iob6A7e --- .claude/skills/cut-release/SKILL.md | 1 + .claude/skills/design-feature/SKILL.md | 1 + .claude/skills/e2e-device/SKILL.md | 1 + .claude/skills/file-issue/SKILL.md | 1 + .claude/skills/implement-issue/SKILL.md | 1 + .claude/skills/review-memory/SKILL.md | 1 + .claude/skills/review-pr/SKILL.md | 1 + .claude/skills/triage-issue/SKILL.md | 1 + .claude/skills/work-issue/SKILL.md | 5 ++-- AGENTS.md | 37 +++++++++++++------------ 10 files changed, 30 insertions(+), 20 deletions(-) diff --git a/.claude/skills/cut-release/SKILL.md b/.claude/skills/cut-release/SKILL.md index 9208ab5c..aea14e54 100644 --- a/.claude/skills/cut-release/SKILL.md +++ b/.claude/skills/cut-release/SKILL.md @@ -2,6 +2,7 @@ name: cut-release description: Cut a release of the three npm packages - propose the version from the Unreleased changelog, bump versions in lockstep, open the release PR, and after it merges create the GitHub release that triggers publishing, only on an explicit yes. Use when asked to release, cut a version, tag or publish. model: sonnet +effort: low --- # Cut a release diff --git a/.claude/skills/design-feature/SKILL.md b/.claude/skills/design-feature/SKILL.md index 6324481f..96cfca24 100644 --- a/.claude/skills/design-feature/SKILL.md +++ b/.claude/skills/design-feature/SKILL.md @@ -2,6 +2,7 @@ name: design-feature description: Design a feature that is too big for one PR - verify the issue's claims against the code, choose the shape (modules, public API, calls vs events, ports), cut it into ordered slices each with its own acceptance criteria, and after a human approves, file one child issue per slice. Use on issues labelled status:needs-design, or when asked to design, shape or break down a feature. model: opus +effort: high context: fork --- diff --git a/.claude/skills/e2e-device/SKILL.md b/.claude/skills/e2e-device/SKILL.md index 566824d5..d1efeea9 100644 --- a/.claude/skills/e2e-device/SKILL.md +++ b/.claude/skills/e2e-device/SKILL.md @@ -2,6 +2,7 @@ name: e2e-device description: Build and run a playground app on an iOS simulator or Android emulator, connect it to this repo's Appduct daemon and drive it through the CLI - a smoke pass over the five demo tools plus the calls that prove a specific change works. Use when a PR needs device E2E evidence, when asked to test on a simulator, or when a work-issue orchestrator delegates E2E. model: sonnet +effort: low context: fork --- diff --git a/.claude/skills/file-issue/SKILL.md b/.claude/skills/file-issue/SKILL.md index 0ee9f0fe..6f641bd4 100644 --- a/.claude/skills/file-issue/SKILL.md +++ b/.claude/skills/file-issue/SKILL.md @@ -2,6 +2,7 @@ name: file-issue description: Turn a request or a discovered bug into a GitHub issue in this repo's format - interview the person until the intent is well defined, check for duplicates, then file. Use when asked to file, open, create or write up an issue, when someone describes a feature they want, or when you hit a bug you should not fix in the current change. model: sonnet +effort: medium --- # File an issue diff --git a/.claude/skills/implement-issue/SKILL.md b/.claude/skills/implement-issue/SKILL.md index 0d9fc62d..f20bdbd5 100644 --- a/.claude/skills/implement-issue/SKILL.md +++ b/.claude/skills/implement-issue/SKILL.md @@ -2,6 +2,7 @@ name: implement-issue description: Implement a GitHub issue tests-first - take the acceptance criteria from the issue, write red tests against the public API, commit them, open a draft PR, then implement in checkpoint commits until green, with user-facing docs as one of the criteria. Use when asked to implement, build or fix something tracked as an issue, or when a work-issue orchestrator delegates implementation. model: sonnet +effort: medium context: fork --- diff --git a/.claude/skills/review-memory/SKILL.md b/.claude/skills/review-memory/SKILL.md index bf5ae211..5f557926 100644 --- a/.claude/skills/review-memory/SKILL.md +++ b/.claude/skills/review-memory/SKILL.md @@ -2,6 +2,7 @@ name: review-memory description: Curate agent memory - read the lessons inbox and the curated lessons file, promote what repeats, prune what is stale, open a memory-only PR and merge it. Use weekly, when the inbox has notes, when a lessons section is over its cap, or when asked to review, consolidate or dream over memory. model: opus +effort: high context: fork --- diff --git a/.claude/skills/review-pr/SKILL.md b/.claude/skills/review-pr/SKILL.md index d534ce90..a04d9a2a 100644 --- a/.claude/skills/review-pr/SKILL.md +++ b/.claude/skills/review-pr/SKILL.md @@ -2,6 +2,7 @@ name: review-pr description: Adversarial code review of a PR or branch - hunt for concrete failures, drop low-ROI comments, verify every finding, post inline comments and a verdict through gh. Use when asked to review a PR, a branch or the current diff, or when a work-issue orchestrator delegates review. model: opus +effort: high context: fork --- diff --git a/.claude/skills/triage-issue/SKILL.md b/.claude/skills/triage-issue/SKILL.md index 00ebc64c..51243081 100644 --- a/.claude/skills/triage-issue/SKILL.md +++ b/.claude/skills/triage-issue/SKILL.md @@ -2,6 +2,7 @@ name: triage-issue description: Triage a bug report - rank hypotheses, verify the top three by static analysis, name the root cause with path and line, and propose a module-level fix. Sets the status label. Use when asked to triage, investigate or diagnose a bug, or when a type:bug issue carries status:needs-triage. Features are not triaged; they go through file-issue and design-feature. model: opus +effort: high context: fork --- diff --git a/.claude/skills/work-issue/SKILL.md b/.claude/skills/work-issue/SKILL.md index 8dadde47..d0c0b11e 100644 --- a/.claude/skills/work-issue/SKILL.md +++ b/.claude/skills/work-issue/SKILL.md @@ -12,8 +12,9 @@ not about file contents. Delegating: in Claude Code, invoke the stage's skill with the Skill tool and pass the task as its arguments. Each stage skill is forked (`context: fork` in `AGENTS.md`'s Skills table), so it -runs in its own subagent on its own model and hands back only its report. Never start an agent -that then loads the skill, and never pass a model: either one overrides the skill's model. In +runs in its own subagent on its own model and effort and hands back only its report. Never +start an agent that then loads the skill, and never pass a model: either one overrides the +skill's frontmatter. In OpenCode, use the task tool with a prompt that names the skill to load; there every stage runs on the session model. diff --git a/AGENTS.md b/AGENTS.md index 8a7cae84..64fceddc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -69,22 +69,23 @@ no restating the question. Commit subjects: conventional prefix, imperative, und ## Skills Load the skill before starting the matching task. They live in `.claude/skills/`. -Model and Forked mirror each skill's `model:` and `context: fork` frontmatter, and -`pnpm check:skills` fails CI when they drift. A forked skill runs in its own subagent on its -model however it is started, and sees only the arguments it was invoked with. +Model, Effort and Forked mirror each skill's `model:`, `effort:` and `context: fork` +frontmatter, and `pnpm check:skills` fails CI when they drift. A forked skill runs in its own +subagent on its model and effort however it is started, and sees only the arguments it was +invoked with. -| Task | Skill | Model | Forked | -| --- | --- | --- | --- | -| Designing or writing any non-trivial code | `architecture` | none | no | -| Implementing an issue (tests first, draft PR, checkpoints) | `implement-issue` | sonnet | yes | -| Reviewing a PR or branch | `review-pr` | opus | yes | -| Investigating a bug report | `triage-issue` | opus | yes | -| Designing a feature too big for one PR, or sizing one | `design-feature` | opus | yes | -| Turning a request or a found bug into an issue (interviews first) | `file-issue` | sonnet | no | -| Running the app on a simulator and driving it through the CLI | `e2e-device` | sonnet | yes | -| Writing or editing anything an Appduct user reads: READMEs, `docs/`, website, the shipped skill, CLI help, error messages | `writing-user-docs` | none | no | -| Adding, amending or reviewing an entry in `CHANGELOG.md` | `writing-changelog` | none | no | -| Cutting a release | `cut-release` | sonnet | no | -| Curating agent memory (weekly, or when the inbox has notes) | `review-memory` | opus | yes | -| Taking an issue from `status:ready` to a reviewed, tested PR | `work-issue` (orchestrator) | none | no | -| Driving an Appduct-enabled app as a user of Appduct | `appduct` (in `skills/`) | none | no | +| Task | Skill | Model | Effort | Forked | +| --- | --- | --- | --- | --- | +| Designing or writing any non-trivial code | `architecture` | none | none | no | +| Implementing an issue (tests first, draft PR, checkpoints) | `implement-issue` | sonnet | medium | yes | +| Reviewing a PR or branch | `review-pr` | opus | high | yes | +| Investigating a bug report | `triage-issue` | opus | high | yes | +| Designing a feature too big for one PR, or sizing one | `design-feature` | opus | high | yes | +| Turning a request or a found bug into an issue (interviews first) | `file-issue` | sonnet | medium | no | +| Running the app on a simulator and driving it through the CLI | `e2e-device` | sonnet | low | yes | +| Writing or editing anything an Appduct user reads: READMEs, `docs/`, website, the shipped skill, CLI help, error messages | `writing-user-docs` | none | none | no | +| Adding, amending or reviewing an entry in `CHANGELOG.md` | `writing-changelog` | none | none | no | +| Cutting a release | `cut-release` | sonnet | low | no | +| Curating agent memory (weekly, or when the inbox has notes) | `review-memory` | opus | high | yes | +| Taking an issue from `status:ready` to a reviewed, tested PR | `work-issue` (orchestrator) | none | none | no | +| Driving an Appduct-enabled app as a user of Appduct | `appduct` (in `skills/`) | none | none | no | From 2bea0253f570771222a06b0d72ed024011b865d9 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 24 Sep 2026 15:10:04 +0000 Subject: [PATCH 5/5] chore: drop the skill model CI check The frontmatter and the AGENTS.md table change together by hand; a drift between them cannot break anything silently. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WwCFb6cH1WSzCS8iob6A7e --- .github/workflows/lint.yaml | 4 -- AGENTS.md | 5 +- package.json | 1 - scripts/check-skill-models.mjs | 87 ---------------------------------- 4 files changed, 2 insertions(+), 95 deletions(-) delete mode 100644 scripts/check-skill-models.mjs diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index a455d048..640aa813 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -51,10 +51,6 @@ jobs: - name: Check Markdown links # Relative links and heading anchors only, no network — see scripts/check-md-links.mjs. run: pnpm check:links - - name: Check skill models - # Each skill's model:, effort: and context: against the Skills table in AGENTS.md — see - # scripts/check-skill-models.mjs. - run: pnpm check:skills typecheck: runs-on: ubuntu-latest diff --git a/AGENTS.md b/AGENTS.md index 64fceddc..efe24a65 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -70,9 +70,8 @@ no restating the question. Commit subjects: conventional prefix, imperative, und Load the skill before starting the matching task. They live in `.claude/skills/`. Model, Effort and Forked mirror each skill's `model:`, `effort:` and `context: fork` -frontmatter, and `pnpm check:skills` fails CI when they drift. A forked skill runs in its own -subagent on its model and effort however it is started, and sees only the arguments it was -invoked with. +frontmatter; change both together. A forked skill runs in its own subagent on its model and +effort however it is started, and sees only the arguments it was invoked with. | Task | Skill | Model | Effort | Forked | | --- | --- | --- | --- | --- | diff --git a/package.json b/package.json index 39659cc3..db4a64cf 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,6 @@ "scripts": { "build": "turbo run build", "check:links": "node scripts/check-md-links.mjs", - "check:skills": "node scripts/check-skill-models.mjs", "clean": "turbo run clean", "lint": "turbo run lint", "test": "turbo run test", diff --git a/scripts/check-skill-models.mjs b/scripts/check-skill-models.mjs deleted file mode 100644 index cb63873f..00000000 --- a/scripts/check-skill-models.mjs +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env node -/** - * Skill model check — the Skills table in AGENTS.md against each skill's frontmatter, no network. - * - * Fails when: - * - a skill in `.claude/skills/` has no row in the table; - * - a row names a skill found in neither `.claude/skills/` nor `skills/`; - * - a row's Model or Effort column differs from the skill's `model:` or `effort:` (`none` - * when the key is absent); - * - a row's Forked column is `yes` and the skill lacks `context: fork`, or the reverse. - * - * Usage: node scripts/check-skill-models.mjs (always checks this repository) - * Exit codes: 0 = table and frontmatter agree, 1 = at least one mismatch. - */ - -import { existsSync, readdirSync, readFileSync } from "node:fs"; -import { dirname, join, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -const ROOT = resolve(join(dirname(fileURLToPath(import.meta.url)), "..")); -const SKILL_DIRS = [".claude/skills", "skills"]; - -/** The rows of the table under `## Skills` in AGENTS.md, as cells keyed by header. */ -function tableRows() { - const agents = readFileSync(join(ROOT, "AGENTS.md"), "utf8"); - const section = agents.split(/^## Skills$/m)[1]?.split(/^## /m)[0] ?? ""; - const lines = section.split("\n").filter((line) => line.startsWith("|")); - const cells = (line) => line.split("|").slice(1, -1).map((cell) => cell.trim()); - const [header, , ...body] = lines.map(cells); - if (!header) return []; - return body.map((row) => Object.fromEntries(header.map((name, i) => [name, row[i] ?? ""]))); -} - -/** `model`, `effort` and `context` from a SKILL.md's frontmatter, undefined when a key is absent. */ -function frontmatter(path) { - const block = readFileSync(path, "utf8").match(/^---\n([\s\S]*?)\n---/)?.[1] ?? ""; - const value = (key) => block.match(new RegExp(`^${key}:\\s*(.+)$`, "m"))?.[1].trim(); - return { model: value("model"), effort: value("effort"), context: value("context") }; -} - -const errors = []; -const rows = tableRows(); -const listed = new Set(); - -for (const row of rows) { - const name = row.Skill?.match(/`([^`]+)`/)?.[1]; - if (!name) { - errors.push(`AGENTS.md: a Skills row has no skill name in backticks: ${JSON.stringify(row)}`); - continue; - } - listed.add(name); - const dir = SKILL_DIRS.find((d) => existsSync(join(ROOT, d, name, "SKILL.md"))); - if (!dir) { - errors.push(`AGENTS.md: \`${name}\` is in the Skills table but not in ${SKILL_DIRS.join(" or ")}`); - continue; - } - const path = `${dir}/${name}/SKILL.md`; - const declared = frontmatter(join(ROOT, path)); - for (const [key, column] of [ - ["model", "Model"], - ["effort", "Effort"], - ]) { - const value = declared[key] ?? "none"; - if (row[column] !== value) { - errors.push(`${path}: ${key} is ${value}, AGENTS.md says ${row[column] || "nothing"}`); - } - } - const forked = declared.context === "fork" ? "yes" : "no"; - if (row.Forked !== forked) { - errors.push( - `${path}: context is ${declared.context ?? "none"} (Forked ${forked}), AGENTS.md says ${row.Forked || "nothing"}`, - ); - } -} - -for (const entry of readdirSync(join(ROOT, ".claude/skills"), { withFileTypes: true })) { - if (entry.isDirectory() && !listed.has(entry.name)) { - errors.push(`.claude/skills/${entry.name}: missing from the Skills table in AGENTS.md`); - } -} - -if (errors.length > 0) { - console.error(errors.join("\n")); - console.error(`\n${errors.length} skill model mismatch(es).`); - process.exit(1); -} -console.log(`Skill models agree with AGENTS.md (${rows.length} rows).`);