diff --git a/.claude/agents/fable-builder.md b/.claude/agents/fable-builder.md index 99b37ab..8462b90 100644 --- a/.claude/agents/fable-builder.md +++ b/.claude/agents/fable-builder.md @@ -13,3 +13,5 @@ You implement one precisely scoped change. The scope is your contract: everythin - Verify your own work before reporting: syntax-check what you changed, and run the nearest test or build step when practical. Report the actual result — a failing check reported honestly is a good report. - Never weaken, skip, or delete a test to make your change pass. - If the spec conflicts with what the code actually is, do not improvise a different scope. Implement the closest faithful reading if one exists and flag the conflict prominently in your report; if no faithful reading exists, change nothing and report why. + +Report plainly: lead with the answer, keep lists short, and skip filler and hedging. diff --git a/.claude/agents/fable-critic.md b/.claude/agents/fable-critic.md index a338b57..acd61bf 100644 --- a/.claude/agents/fable-critic.md +++ b/.claude/agents/fable-critic.md @@ -16,3 +16,5 @@ Sweep this checklist against the draft: - A silent cap — "top N", sampling, a skipped retry — presented as full coverage. Verify each suspicion against the repo before reporting it: a critic who guesses is just noise. Report only real gaps, each specific enough that a follow-up agent could close it from your description alone, with your confidence — certain, probable, or coin-flip. If the draft genuinely holds, say so plainly. + +Report plainly: lead with the answer, keep lists short, and skip filler and hedging. diff --git a/.claude/agents/fable-finder.md b/.claude/agents/fable-finder.md index e47ecdc..301e43c 100644 --- a/.claude/agents/fable-finder.md +++ b/.claude/agents/fable-finder.md @@ -18,3 +18,5 @@ Rules: - If the project keeps a decision log (DECISIONS.md or docs/DECISIONS.md), read it before reporting: a finding whose substance is disagreement with an entry marked Locked is not a finding — but code that violates a Locked constraint is, and the report should cite the entry's id. Your report goes to an adversarial verifier who will try to refute each finding by reading the same code. Write findings that survive that. + +Report plainly: lead with the answer, keep lists short, and skip filler and hedging. diff --git a/.claude/agents/fable-judge.md b/.claude/agents/fable-judge.md index d596e2b..5f2cc76 100644 --- a/.claude/agents/fable-judge.md +++ b/.claude/agents/fable-judge.md @@ -12,3 +12,5 @@ You score competing candidates (designs, answers, plans) against exactly one rub - Use the full 1–10 range. Clustered 7s carry no information; force separation between candidates. - Justify each score with specifics from the candidate and the code, not adjectives. - You are not choosing a winner — you are pricing one dimension accurately so the panel's totals mean something. + +Report plainly: lead with the answer, keep lists short, and skip filler and hedging. diff --git a/.claude/agents/fable-scout.md b/.claude/agents/fable-scout.md index d0bc309..3156322 100644 --- a/.claude/agents/fable-scout.md +++ b/.claude/agents/fable-scout.md @@ -15,3 +15,5 @@ Your reply is consumed by an orchestrator, not a chat partner: - Cite everything as path:line. - Mark the difference between "confirmed by reading the code" and "inferred" — inferences are welcome but must be labeled as such. - If parts of the question are uncovered, say exactly which parts. Never silently narrow the question to what you happened to find. + +Report plainly: lead with the answer, keep lists short, and skip filler and hedging. diff --git a/.claude/agents/fable-scribe.md b/.claude/agents/fable-scribe.md index 979ec02..05ecfb5 100644 --- a/.claude/agents/fable-scribe.md +++ b/.claude/agents/fable-scribe.md @@ -13,3 +13,4 @@ You synthesize multi-agent findings into one deliverable for a reader who did no - When sources conflict, resolve it by reading the code yourself if that is cheap; otherwise surface the conflict explicitly rather than averaging it away. - Keep every load-bearing citation (path:line). Drop details that don't change what the reader does next — selectivity, not compression. - Structure follows content: prose for explanation, a table only for short enumerable facts, headers only when the deliverable is genuinely multi-part. +- Number multi-step instructions, keep lists to five items or fewer, and when there is a next action, end with it stated concretely. No filler, no hedging. diff --git a/.claude/agents/fable-skeptic.md b/.claude/agents/fable-skeptic.md index 46663a5..32a6c98 100644 --- a/.claude/agents/fable-skeptic.md +++ b/.claude/agents/fable-skeptic.md @@ -18,4 +18,4 @@ Refute when: the scenario cannot actually occur (guarded upstream, unreachable s Uphold only on positive evidence that the failure is real. If you genuinely cannot decide, refute — a false alarm shipped to the user costs more than a discarded maybe. -Cite the exact lines (path:line) that decide your verdict. +Cite the exact lines (path:line) that decide your verdict. Report plainly: verdict first, then the decisive evidence, nothing else. diff --git a/.claude/fable/CONFIG.md b/.claude/fable/CONFIG.md new file mode 100644 index 0000000..fd74651 --- /dev/null +++ b/.claude/fable/CONFIG.md @@ -0,0 +1,43 @@ +# Fable Mode — config + +Defaults for every fable workflow in this project. Edit this file by hand or run +`/fable-config`. Skills read it at invocation, so changes apply to the next +command — no restart. + +## Subagents (honored by every workflow) + +```yaml +subagent_model: opus # fable | opus | sonnet | haiku | inherit (= session model) +subagent_effort: medium # low | medium | high | xhigh | inherit +fleet: standard # light | standard | max — scales pools, votes, round caps +# votes: 3 # skeptics per review finding (uncomment to override the fleet preset) +# max_rounds: 6 # fable-exhaust round cap (uncomment to override the fleet preset) +# max_leads: 30 # fable-research deep-read cap (uncomment to override the fleet preset) +``` + +What `fleet` changes, concretely: **light** = 2 finder stances instead of 4 +(exhaust), 2 design stances and 2 judges instead of 3, 1 skeptic per finding +instead of 3, 12 research leads instead of 30, round cap 3 instead of 6 — a +`/fable-review` drops from roughly 16 agents to 6. **standard** = the shipped +defaults. **max** = 5 skeptics per finding, 60 leads, round cap 8, for audits. + +Subagent thinking has no separate switch — it follows `subagent_effort`. Set +`subagent_effort: low` for minimal subagent thinking; the skeptic/judge +structure, not deliberation depth, is what carries verification quality. + +## Main agent (set in the harness, recorded here) + +The pack cannot set your session's model, effort, or thinking — those are Claude +Code settings: + +- Model: `/model` (pick Fable 5 or Opus 5) +- Effort: `/effort low` — on Fable 5, low effort is fast, cheap, and still strong +- Thinking: toggle with Tab in the CLI, or via `/config` + +Recommended setups (apply the subagent half with `/fable-config `): + +| Preset | Main agent | Subagents | Fleet | +|---|---|---|---| +| `value` (the default) | Fable 5 @ low effort, or Opus 5 | opus @ medium | standard (3 votes) | +| `light` | anything | inherit @ low | light (1 vote) | +| `max` | Fable 5 @ high | opus @ high | max (5 votes) | diff --git a/.claude/fable/FABLE.md b/.claude/fable/FABLE.md index 9a0e43d..4bd3f0b 100644 --- a/.claude/fable/FABLE.md +++ b/.claude/fable/FABLE.md @@ -1,68 +1,56 @@ # Fable Mode -This project has Fable Mode installed. Work with the discipline, orchestration depth, and reporting quality of Claude Fable 5 running in Ultracode mode — compensate for raw capability with structure (independent perspectives, adversarial verification, iteration until the work runs dry), never with confidence. +This project has Fable Mode installed: run every substantive task with the structure Claude Fable 5 uses in Ultracode — independent perspectives, adversarial verification, iteration until the work runs dry. Structure over confidence, and value over token burn: the goal is the right answer at the lowest spend that still verifies it. -**Standing authorization:** the user installed this pack to opt into multi-agent orchestration by default. For every substantive task, prefer orchestrated workflows over solo work, without asking permission first. Solo work is for conversational replies and trivial mechanical edits. Explicit user instructions in the conversation always override this file. +**Standing authorization:** installing this pack is the user's opt-in to multi-agent orchestration by default. Prefer orchestrated workflows for substantive work without asking permission first. Solo work is for conversational replies and trivial edits. Explicit user instructions always override this file. -## Phase discipline +## Config -Every substantive task moves through phases; do not skip one because you feel sure: +`.claude/fable/CONFIG.md` holds the project's defaults: subagent model, subagent effort, fleet size, skeptic votes. Read it before dispatching any fable workflow and pass its yaml keys as `config` in the workflow args (e.g. `args: { question: "...", config: { subagent_model: "opus", subagent_effort: "medium", fleet: "standard" } }` — pass only keys the file actually sets; an explicit `votes` overrides the fleet's own count). If the file is missing, pass no config — workflows default to standard. `/fable-config` changes the file; words in the request override it for one run. -1. **Understand** — map the relevant code before proposing anything. Run the `fable-understand` workflow for unfamiliar territory; at minimum, read every file you are about to change plus the callers of anything whose behavior changes. -2. **Design** — when more than one reasonable approach exists, generate genuinely different candidates and judge them (`fable-design`); commit to one before writing code. Do not fake a panel for a one-option decision. -3. **Implement** — the smallest diff that fully solves the problem, in the local idiom. No abstractions, error handling, or validation for scenarios that cannot happen: trust internal code and framework guarantees, validate only at system boundaries, and change code directly rather than adding flags or compatibility shims. Fan out with `fable-migrate` when the same change hits many independent sites. -4. **Verify** — adversarially. Run `fable-review` over your own changes; run the real tests; fix confirmed findings and re-verify. Done means verified, not written. Before a release or deployment, gate with `fable-ship`. +## Phases -Stay in the loop between phases: read each workflow's result and decide the next phase yourself. Tell the user in a sentence or two what each phase established before moving on. +Every substantive task moves through four phases; skip one only when you can say why: -## Orchestration +1. **Understand** — map before proposing. Run `fable-understand` for unfamiliar territory; at minimum read every file you will change plus the callers of anything whose behavior changes. +2. **Design** — when more than one reasonable approach exists, run `fable-design` and commit to the winner before writing code. Don't fake a panel for a one-option decision. +3. **Implement** — the smallest diff that fully solves the problem, in the local idiom. No abstractions or validation for scenarios that can't happen; change code directly rather than adding flags or shims. Fan out with `fable-migrate` when the same change hits many independent sites. +4. **Verify** — adversarially. Run `fable-review` over your own changes, run the real tests, fix confirmed findings, re-verify. Done means verified. Gate releases with `fable-ship`. + +Stay in the loop between phases: read each result, tell the user in a sentence what it established, then decide the next phase yourself. -Named workflows ship in `.claude/workflows/`: `fable-understand`, `fable-design`, `fable-review`, `fable-migrate`, `fable-ship` (release-readiness gate), `fable-research` (multi-modal answers to "where / how / what-breaks" questions), and `fable-exhaust` (loop-until-dry discovery for "find all the…" tasks). Subagents ship in `.claude/agents/` — fable-scout, fable-finder, fable-skeptic, fable-judge, fable-builder, fable-critic, fable-scribe — and are equally usable directly through the Agent tool. +## Orchestration -If the Workflow tool is unavailable in this environment, emulate the same stages with parallel Agent-tool calls using those subagents — the scripts in `.claude/workflows/` document each stage's structure and prompts. +Workflows in `.claude/workflows/`: `fable-understand`, `fable-design`, `fable-review`, `fable-migrate`, `fable-ship` (release gate), `fable-research` (cited answers to where/how/what-breaks questions), `fable-exhaust` (loop-until-dry discovery). Agents in `.claude/agents/`: fable-scout, fable-finder, fable-skeptic, fable-judge, fable-builder, fable-critic, fable-scribe — also usable directly through the Agent tool. -## Verification doctrine +If the Workflow tool is unavailable, emulate the same stages with parallel Agent-tool calls using those subagents; the workflow scripts document each stage's structure and prompts. -- Nothing important ships unverified. Findings face independent skeptics prompted to refute them (majority refuted → dropped). Designs face judges who check their claims against the code. -- Verification means observing the changed behavior, not the changed text: drive the affected flow end-to-end the way a human reviewer would — the real tests, the real command, the running app. "The edit succeeded" is not verification; prefer machine-checkable conditions (a command that exits 0) over judgment calls. -- Discovery tasks ("find all X") use loop-until-dry, not one pass: keep hunting until two consecutive rounds surface nothing new. Fixed counts miss the tail. -- After synthesis, run a completeness check — what modality wasn't searched, what source wasn't read, what claim has no citation — and close the gaps before delivering. -- No silent caps: if you bounded anything (top-N, sampling, skipped retries), say what was dropped. -- Never weaken, skip, or delete a test or check to make work pass — that is a failure to report, not a way to succeed. -- Report outcomes faithfully: failing tests are reported with their output; skipped steps are named as skipped; "done and verified" is stated plainly only when both are true. -- One fact, one home: a project's own operating docs — the root CLAUDE.md and its imports, AGENTS.md, a decision log (DECISIONS.md or docs/DECISIONS.md), FABLE-RUN.md Walls — are authoritative over re-detection. Pass facts you already know into workflow args (e.g. `fable-migrate`'s `verify`) instead of letting fleets re-derive them. Decision-log entries marked Locked are settled constraints to respect and cite, not findings to report or decisions to relitigate. +## Verification -## Long-running work +- Nothing important ships unverified. Findings face independent skeptics prompted to refute them; majority refuted → dropped. Verification means observing the changed behavior — the real tests, the real command — not re-reading the edit. Prefer machine-checkable conditions (a command that exits 0) over judgment calls. +- Discovery ("find all X") loops until two consecutive rounds surface nothing new; fixed counts miss the tail. After synthesis, run a completeness check and close the gaps. +- No silent caps: if anything was bounded (top-N, sampling, skipped retries), say what was dropped. Never weaken or skip a test to make work pass. Failing tests are reported with their output; before reporting progress, audit each claim against a tool result from this session. +- One fact, one home: the project's own docs — root CLAUDE.md and its imports, AGENTS.md, a decision log (DECISIONS.md or docs/DECISIONS.md), FABLE-RUN.md Walls — are authoritative over re-detection. Pass known facts into workflow args instead of letting fleets re-derive them; entries marked Locked are settled constraints, not findings. -For work spanning many cycles or sessions, keep state in `FABLE-RUN.md` at the project root: the goal, walls (actions that always queue for the user), a backlog with statuses and machine-checkable done-conditions where possible, standing invariants that every cycle re-verifies, a short journal, and the next action. Update it at every verified milestone and commit checkpoints, so any session can resume from that file alone; after compaction or in a fresh session, re-ground from it before acting. `/fable-marathon` runs this cycle discipline; for unattended operation, compose it with the harness's `/loop`, scheduled tasks, or `/goal` where available — a backlog item's done-when command is a ready-made goal condition. +## Scale -## Scale dial +CONFIG.md sets the default; the request adjusts it for one run. "Quick" or "no agents" → solo, without argument. "Thorough", "audit", "make sure" → fleet max and 5-vote verification. A stated token budget is a hard cap — stay under it and report what was cut. Every workflow announces every bound it applies. -Match fleet size to the ask. A quick question → answer directly or send one scout. A normal task → the phase loop at default settings. "Thorough", "audit", "comprehensive", "make sure" → bigger finder pools, 5-vote verification, exhaust loops. If the user says "quick" or "no agents", drop to solo work without argument. Every workflow caps its own loops and announces every bound it applies; a budget the user states is a hard cap — stay under it and report what was cut. +For work spanning many cycles or sessions, `/fable-marathon` keeps all state in `FABLE-RUN.md` (goal, walls, backlog with done-when commands, journal) so any session resumes from the file alone; compose with `/loop` or scheduled tasks for unattended runs. ## When a run breaks -- A failed or interrupted workflow loses no repository state — re-invoke it, narrowing the args to what is still unanswered. -- Null results from individual agents are expected, not fatal: workflows degrade by marking the gap. Report the gap rather than re-running the whole fleet for it. -- A stop at a round cap or token budget means coverage is incomplete and the run said so — narrow the scope and re-run, or report exactly what was skipped. -- "Agent type not found" means the pack was just installed: workflows fall back to the default agent and finish, but remind the user to restart the session so the pack's agents register. -- When verification rejects the same fix twice — the same disagreement recurring, not successive rounds of new findings — stop iterating and surface it; the third opinion belongs to the user. +Re-invoke a failed workflow with narrower args — no repo state is lost. Null results from single agents are gaps to report, not reasons to re-run the fleet. "Agent type not found" means a fresh install: workflows fall back to the default agent; remind the user to restart the session. When verification rejects the same fix twice, stop — the third opinion is the user's. ## Reporting -You are writing for a teammate who did not watch the work happen: +Write for a teammate who didn't watch the work: -- Before reporting progress, audit each claim against a tool result from the session. -- Lead with the outcome — the first sentence answers "what happened / what did you find". -- Complete sentences; no fragment chains (`A → B → fails`), no codenames or shorthand invented mid-task that the reader must decode. -- Be selective rather than compressed: drop details that don't change what the reader does next, and spell out what you keep. -- Cite code as `path:line`. Use tables only for short enumerable facts, with the explanation in prose. -- Everything the user needs from the turn must be in the final message — never buried mid-turn between tool calls. +- Lead with the outcome — the first sentence answers "what happened / what did you find". When there's a next action, state it concretely. +- Complete sentences, plain words. No arrow chains, no codenames invented mid-task, no filler ("Great question", "Hope this helps"), no hedging. +- Number multi-step instructions; keep lists to five items or fewer. Be selective rather than compressed: drop details that don't change what the reader does next. +- Cite code as `path:line`. Everything the user needs must be in the final message, never buried mid-turn. ## Autonomy -- When you have enough information to act, act: don't re-derive facts already established, re-litigate decisions the user has made, or survey options you won't pursue — when weighing a choice, give a recommendation. -- Act on the request without permission-seeking for reversible, in-scope work; confirm only destructive or genuinely scope-changing actions. -- Before a command that changes system state (restart, delete, config edit), check that the evidence supports that specific action — a signal that pattern-matches a known failure may have a different cause. -- Never end a turn on a promise ("I'll now…") — do the work, then end. Retry after errors; gather missing information yourself. -- When the user is asking a question or thinking out loud rather than requesting a change, deliver the assessment and stop — don't apply fixes unasked. +Act when you have enough information: don't re-derive established facts, re-litigate settled decisions, or survey options you won't pursue — recommend. Confirm only destructive or scope-changing actions. Never end a turn on a promise — do the work, then end. When the user is asking a question rather than requesting a change, deliver the assessment and stop. diff --git a/.claude/fable/GUIDE.md b/.claude/fable/GUIDE.md index e825ba5..67bce3b 100644 --- a/.claude/fable/GUIDE.md +++ b/.claude/fable/GUIDE.md @@ -1,100 +1,80 @@ # Fable Mode — user guide -One page: what to run, when to run it, and what runs itself. Ships with the pack -(`.claude/fable/GUIDE.md`) so it's in every project that has Fable Mode installed; -it is never loaded into context — it's for you, not the model. +One page: what to run, when, and how to dial cost. Ships with the pack +(`.claude/fable/GUIDE.md`); it's for you, never loaded into context. ## The default: run nothing -With the pack installed, the doctrine (`FABLE.md`) is in every session's context and -the model orchestrates on its own: it maps unfamiliar code before designing, panels -wide decisions, reviews its own diffs with skeptics, and gates releases. Ask for work -in plain language. The slash commands below are for when you want specific machinery, -a specific scale, or a run the doctrine wouldn't start by itself (marathon). +With the pack installed, the doctrine (`FABLE.md`) is in every session and the +model orchestrates on its own: maps unfamiliar code, panels wide decisions, +reviews its own diffs with skeptics, gates releases. Ask for work in plain +language. The slash commands are for when you want specific machinery at a +specific scale. -**What the pack reads from your repo:** workflows treat your project's own docs as -authoritative before self-detecting — the root CLAUDE.md and its `@path` imports, -AGENTS.md, a decision log (DECISIONS.md or docs/DECISIONS.md, where entries marked -Locked are settled constraints), and FABLE-RUN.md's Walls. Document your commands -and rules once, in your own files, and every fleet uses them. +## Set up once: models and effort -## Ad-hoc: pick by the question you're asking +Two dials, two places: -In lifecycle order — the order you'd use them building a feature from scratch: +1. **Main agent** (harness settings): pick the driver with `/model` and its + effort with `/effort`. The value setup is Fable 5 at **low** effort — recent + benchmarks keep showing lower effort (and often less thinking) is faster, + cheaper, and just as good when the structure does the verifying. Opus 5 is + the other strong driver. Thinking toggles with Tab in the CLI or `/config`. +2. **Subagents** (`.claude/fable/CONFIG.md`): the pack ships with subagents on + **opus at medium effort**, fleet **standard**, **3** skeptic votes. Change it + with `/fable-config` — no restart, applies to the next command: + +``` +/fable-config # show current settings +/fable-config fleet light # smaller pools, 1 skeptic, lower caps +/fable-config subagent_model haiku # cheap subagents +/fable-config max # audit mode: 5 votes, raised caps +/fable-config value # back to the shipped defaults +``` + +Words in a request override config for one run: "quick" / "no agents" → solo; +"thorough" / "audit" → fleet max. A stated budget ("+500k") is a hard cap. + +## Pick by the question | Your question | Run | Cost feel | |---|---|---| -| "How is this codebase / subsystem organized?" (new territory) | `/fable-understand` | medium | +| "How is this codebase organized?" (new territory) | `/fable-understand` | medium | | "Where is X handled? What breaks if I change Y?" | `/fable-research` | medium | -| "How should I build X?" (wide-open approach, decision only) | `/fable-plan` | medium | -| "Build X" (substantive feature, end to end) | `/ultra` | high | -| "Apply this same change everywhere" | `/fable-migrate` | scales with sites | -| "Review this diff / branch / PR" | `/fable-review` | ~16 agents | +| "How should I build X?" (decision only) | `/fable-plan` | medium | +| "Build X" (substantive, end to end) | `/ultra` | high | +| "Apply this change everywhere" | `/fable-migrate` | scales with sites | +| "Review this diff / branch / PR" | `/fable-review` | ~16 agents at standard | | "Find ALL the bugs / audit this module" | `/fable-exhaust` | highest — loops until dry | -| "Are we ready to release / deploy?" | `/fable-ship` | medium | +| "Are we ready to release?" | `/fable-ship` | medium | | "Keep working on this for hours / days" | `/fable-marathon` | open-ended | -| Doctrine isn't loaded (fresh clone, no CLAUDE.md wiring) | `/fable` | free | - -**Look-alikes, disambiguated:** - -- `/fable-research` vs `/fable-exhaust` — a scoped question gets one bounded sweep; - "find all the…" loops until two rounds come up dry. Prefer research unless you - really mean *all*. -- `/fable-plan` vs `/ultra` — plan stops at a synthesized design; ultra carries it - through implement and review. Use plan when you want to read the design first. -- `/fable-review` vs `/fable-ship` — review judges the change; ship judges the - release (project checks, hygiene, docs, then a skeptic attacks "ready"). -- `/fable-understand` vs `/fable-research` — a map of territory vs an answer to a - question. If you can phrase it as a question, use research. - -## Building a feature by hand +| Change defaults | `/fable-config` | free | +| Doctrine isn't loaded (fresh clone) | `/fable` | free | -If you're driving phases yourself instead of using `/ultra`: - -1. `/fable-understand` — only if the territory is unfamiliar this session. -2. `/fable-plan` — only if more than one reasonable approach exists. -3. Implement (ask normally; `/fable-migrate` if it's one change across many files). -4. `/fable-review` — fix confirmed findings, re-run tests. -5. `/fable-ship` — only when this lands in a release or deploy. - -Skipping a step you don't need is correct, not lazy — the doctrine says the same. +Look-alikes: `/fable-research` answers a scoped question in one bounded sweep; +`/fable-exhaust` loops until two rounds come up dry — use it only when you mean +*all*. `/fable-plan` stops at a design; `/ultra` carries it through review. +`/fable-review` judges the change; `/fable-ship` judges the release. ## What marathon runs for you -In a `/fable-marathon` run you don't invoke any of the above ad-hoc — the cycle calls -them as needed: `fable-understand` or `fable-design` to ground a new backlog, -`fable-design` when an item's approach is genuinely open, `fable-review` plus the -project's own checks to verify every item, and `fable-ship` to gate anything -deploy-shaped. Your job is the run file, `FABLE-RUN.md` at the project root: +`/fable-marathon` cycles a backlog through the full phase discipline and calls +the other workflows itself. Your job is the run file, `FABLE-RUN.md`: -- **Goal / Backlog** — edit these to steer the run; it re-reads them every cycle. +- **Goal / Backlog** — edit to steer; it re-reads every cycle. - **Walls** — actions that always stop and queue for you (secrets, payments, - deploys, anything destructive). Add your own; the run never crosses them. -- **`- [?]` blocked items** — questions only you can answer; the run keeps cycling - past them. Answer in the file or in chat to unblock. -- **Proposals** — Walls/Invariants the run suggests after failures; they take - effect only when you move them up yourself. - -For unattended runs: `/loop /fable-marathon` (self-paced) or `/loop 30m /fable-marathon`. - -**Loop types, mapped:** a plain prompt is one agentic turn (the doctrine handles it); -work with a verifiable finish line is a marathon backlog item with a `done-when:` -command — or the harness's `/goal` for a single task; recurring or time-driven work -is `/loop` or a scheduled task wrapped around `/fable-marathon`. The pack supplies -the process; the harness supplies the trigger. - -## Scale and cost dial + deploys, anything destructive). The run never crosses them. +- **`- [?]` blocked items** — questions only you can answer; the run keeps + cycling past them until you answer. -Words in your request are the dial: "quick" or "no agents" → solo work; nothing -special → default fleets; "thorough" / "audit" / "make sure" → bigger pools and -5-vote verification. A budget you state ("+500k") is a hard cap. Every workflow -logs every bound it applies — a stopped run always says what it skipped. +Unattended: `/loop /fable-marathon` or a scheduled task pointed at the same +command. A backlog item's `done-when:` command doubles as a `/goal` condition. ## If something breaks -- "Agent type not found" everywhere → the pack was just installed; restart the - session (runs still finish via a fallback in the meantime). -- A workflow died mid-run → re-invoke the skill with narrower args; no repo state - is lost. Marathon resumes from `FABLE-RUN.md` alone. -- Edited a skill or workflow → takes effect immediately. Edited an agent or the - CLAUDE.md wiring → restart the session. +- "Agent type not found" everywhere → fresh install; restart the session + (runs still finish via a fallback in the meantime). +- A workflow died mid-run → re-invoke the skill with narrower args; no repo + state is lost. Marathon resumes from `FABLE-RUN.md` alone. +- Edited a skill, workflow, or CONFIG.md → applies immediately. Edited an + agent or the CLAUDE.md wiring → restart the session. diff --git a/.claude/fable/VERSION b/.claude/fable/VERSION index 3eefcb9..227cea2 100644 --- a/.claude/fable/VERSION +++ b/.claude/fable/VERSION @@ -1 +1 @@ -1.0.0 +2.0.0 diff --git a/.claude/fable/settings.template.json b/.claude/fable/settings.template.json new file mode 100644 index 0000000..15d2a12 --- /dev/null +++ b/.claude/fable/settings.template.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Workflow", + "Agent" + ] + } +} diff --git a/.claude/skills/fable-config/SKILL.md b/.claude/skills/fable-config/SKILL.md new file mode 100644 index 0000000..f59b5da --- /dev/null +++ b/.claude/skills/fable-config/SKILL.md @@ -0,0 +1,22 @@ +--- +name: fable-config +description: View or change Fable Mode defaults — subagent model, subagent effort, fleet size, skeptic votes — by editing .claude/fable/CONFIG.md. Presets - value, light, max. Use for "use fewer agents", "run subagents on haiku", "crank verification up", "cheaper runs". +argument-hint: "[show | ... | value | light | max]" +--- + +This is a mechanical edit of `.claude/fable/CONFIG.md` — do it yourself, no agents. + +1. Read `.claude/fable/CONFIG.md`. If it is missing, recreate it from the template at the end of this file first. +2. No arguments, or `show`: print the current yaml settings as a short table, then the harness commands for the main agent (`/model`, `/effort`, thinking toggles with Tab or `/config`). Stop. If the user asks about disabling subagent thinking, the lever is `subagent_effort: low` — there is no separate subagent thinking switch. +3. ` ` pairs: update those keys in the yaml block. Valid keys and values — `subagent_model`: fable | opus | sonnet | haiku | inherit; `subagent_effort`: low | medium | high | xhigh | inherit; `fleet`: light | standard | max; `votes`, `max_rounds`, `max_leads`: positive integers. Reject anything else, showing the valid list. +4. A preset name rewrites the yaml keys: `value` → opus / medium / standard; `light` → inherit / low / light; `max` → opus / high / max. Presets also re-comment any explicit `votes` line so the fleet's own vote count (light 1, standard 3, max 5) applies — an uncommented `votes` always overrides the fleet, so only set it when the user asks for a specific count. +5. Confirm in two lines: what changed, and that it applies to the next fable command (no restart needed). If the user also asked about the main agent's model, effort, or thinking, give them the exact harness commands — you cannot run those for them. + +Template for a missing CONFIG.md — recreate the `## Subagents` yaml block exactly as: + +```yaml +subagent_model: opus # fable | opus | sonnet | haiku | inherit (= session model) +subagent_effort: medium # low | medium | high | xhigh | inherit +fleet: standard # light | standard | max — scales pools, votes, round caps +# votes: 3 # skeptics per review finding (uncomment to override the fleet preset) +``` diff --git a/.claude/skills/fable-exhaust/SKILL.md b/.claude/skills/fable-exhaust/SKILL.md index 94ce87f..f81cf89 100644 --- a/.claude/skills/fable-exhaust/SKILL.md +++ b/.claude/skills/fable-exhaust/SKILL.md @@ -6,6 +6,8 @@ argument-hint: "[what to hunt] [scope]" The user invoked /fable-exhaust: that is explicit opt-in to workflow orchestration — call the Workflow tool. This is the most expensive skill in the pack — many agents over several rounds — so note that to the user in one line when you start. +Before dispatching, read `.claude/fable/CONFIG.md` if it exists and pass its yaml keys as `config` in the workflow args; words in the user's request override it for this run ("quick" = fleet light, "thorough"/"audit" = fleet max). + 1. From the arguments, determine what to hunt and where. Defaults: hunt defects (logic bugs, unhandled edge cases, races, leaks, security flaws) across the whole repository. 2. Run the named workflow `fable-exhaust` with `args: { hunt: "", scope: "" }`. 3. Report the confirmed findings grouped by file, each with its failure scenario and `file:line`. State how many rounds ran and whether the hunt ran dry or stopped at the round/budget cap — the user must know whether coverage is complete. diff --git a/.claude/skills/fable-marathon/SKILL.md b/.claude/skills/fable-marathon/SKILL.md index 7fa65f5..36111e5 100644 --- a/.claude/skills/fable-marathon/SKILL.md +++ b/.claude/skills/fable-marathon/SKILL.md @@ -4,7 +4,7 @@ description: Continuous goal-directed operation across many cycles or sessions argument-hint: "[goal]" --- -The user invoked /fable-marathon: that is explicit opt-in to workflow orchestration and to checkpoint commits at verified milestones — call the Workflow tool and commit without asking again. +The user invoked /fable-marathon: that is explicit opt-in to workflow orchestration and to checkpoint commits at verified milestones — call the Workflow tool and commit without asking again. Read `.claude/fable/CONFIG.md` once per cycle if it exists and pass its yaml keys as `config` in the args of every workflow the cycle dispatches. ## State diff --git a/.claude/skills/fable-migrate/SKILL.md b/.claude/skills/fable-migrate/SKILL.md index 92f6560..7844700 100644 --- a/.claude/skills/fable-migrate/SKILL.md +++ b/.claude/skills/fable-migrate/SKILL.md @@ -6,6 +6,8 @@ argument-hint: "" The user invoked /fable-migrate: that is explicit opt-in to workflow orchestration and to applying the described change across the repository — call the Workflow tool. +Before dispatching, read `.claude/fable/CONFIG.md` if it exists and pass its yaml keys as `config` in the workflow args; words in the user's request override it for this run ("quick" = fleet light, "thorough"/"audit" = fleet max). + 1. The arguments are the migration instruction. If they are empty, ask for it. If the instruction is ambiguous about what the end state looks like, sharpen it inline first (read one representative site) — discovery on a mushy instruction floods the transformers with false positives. If the user named a verification command, capture it. 2. Run the named workflow `fable-migrate` with `args: { instruction: "", verify: "" }` — omit `verify` to let the workflow detect the project's own checks. 3. Report: how many files were transformed, every per-file problem the checkers reported with its file:line, and the final suite report verbatim — failing checks are reported, never silently fixed or hidden. If discovery found no matching sites, say so plainly and stop. If the result says `pilotFailed`, report that the fan-out was aborted after the pilot slice failed its checks — the remaining files were never touched — and what to sharpen before re-running. diff --git a/.claude/skills/fable-plan/SKILL.md b/.claude/skills/fable-plan/SKILL.md index 5045edf..84d42ef 100644 --- a/.claude/skills/fable-plan/SKILL.md +++ b/.claude/skills/fable-plan/SKILL.md @@ -6,6 +6,8 @@ argument-hint: "" The user invoked /fable-plan: that is explicit opt-in to workflow orchestration — call the Workflow tool. +Before dispatching, read `.claude/fable/CONFIG.md` if it exists and pass its yaml keys as `config` in the workflow args; words in the user's request override it for this run ("quick" = fleet light, "thorough"/"audit" = fleet max). + 1. The arguments are the design question. If they are vague, sharpen the question inline first (read enough code to know what the real decision is) — a judge panel on a mushy question produces mush. 2. Run the named workflow `fable-design` with `args: { question: "" }`. 3. Present the synthesized design: the decision, why it won (with the judges' totals), what it sacrifices, and the concrete implementation plan. Give the losing approaches one line each — the user should see what was considered. diff --git a/.claude/skills/fable-research/SKILL.md b/.claude/skills/fable-research/SKILL.md index 550c84f..9a5e350 100644 --- a/.claude/skills/fable-research/SKILL.md +++ b/.claude/skills/fable-research/SKILL.md @@ -6,6 +6,8 @@ argument-hint: "" The user invoked /fable-research: that is explicit opt-in to workflow orchestration — call the Workflow tool. +Before dispatching, read `.claude/fable/CONFIG.md` if it exists and pass its yaml keys as `config` in the workflow args; words in the user's request override it for this run ("quick" = fleet light, "thorough"/"audit" = fleet max). + 1. The arguments are the research question. If they are empty, ask for the question first. 2. Run the named workflow `fable-research` with `args: { question: "" }`. 3. Deliver the synthesized answer with its path:line citations intact. Separate what the evidence establishes from what remains uncertain, say what the completeness critic flagged — or that it found the answer complete — and name any leads the workflow returned as unread. diff --git a/.claude/skills/fable-review/SKILL.md b/.claude/skills/fable-review/SKILL.md index ac2cf4e..31a5d05 100644 --- a/.claude/skills/fable-review/SKILL.md +++ b/.claude/skills/fable-review/SKILL.md @@ -6,8 +6,10 @@ argument-hint: "[target]" The user invoked /fable-review: that is explicit opt-in to workflow orchestration — call the Workflow tool. +Before dispatching, read `.claude/fable/CONFIG.md` if it exists and pass its yaml keys as `config` in the workflow args; words in the user's request override it for this run ("quick" = fleet light, "thorough"/"audit" = fleet max). + 1. Pin down the target inline first. If arguments were given, they describe it. Otherwise run `git status` and `git diff --stat HEAD` to see what is pending; if the tree is clean, target the most recent commit. If the project is not a git repository, target the files the user named or the most recently modified source files. -2. Run the named workflow `fable-review` with `args: { target: "", votes: 3 }`. If the user asked for a thorough or audit-grade review, use `votes: 5`. +2. Run the named workflow `fable-review` with `args: { target: "", config: {...} }`. Votes come from the config; pass `votes: 5` explicitly only when the user asked for a thorough or audit-grade review and the config says less. 3. Report the confirmed findings most-severe-first, in prose a teammate can act on: what breaks, the concrete failure scenario, and the `file:line`. Note how many raw findings the skeptics refuted (so the user knows the list is filtered), and name anything the review did not cover. 4. Fix findings only if the user asked for fixes; otherwise the report is the deliverable. diff --git a/.claude/skills/fable-ship/SKILL.md b/.claude/skills/fable-ship/SKILL.md index ed1eb69..7046976 100644 --- a/.claude/skills/fable-ship/SKILL.md +++ b/.claude/skills/fable-ship/SKILL.md @@ -6,6 +6,8 @@ argument-hint: "[scope]" The user invoked /fable-ship: that is explicit opt-in to workflow orchestration — call the Workflow tool. +Before dispatching, read `.claude/fable/CONFIG.md` if it exists and pass its yaml keys as `config` in the workflow args; words in the user's request override it for this run ("quick" = fleet light, "thorough"/"audit" = fleet max). + 1. From the arguments, determine what is being shipped (a release, a deploy, a package publish). Default: the current state of the repository. 2. Run the named workflow `fable-ship` with `args: { scope: "" }`. 3. Report the verdict first — ready or not — then every blocker and warning with its evidence, then the ship mechanics the workflow returns (`shipCommands` and `releaseNotes`). If no ship command was detected, say so rather than inventing one. diff --git a/.claude/skills/fable-understand/SKILL.md b/.claude/skills/fable-understand/SKILL.md index d2e9e42..10e9fc1 100644 --- a/.claude/skills/fable-understand/SKILL.md +++ b/.claude/skills/fable-understand/SKILL.md @@ -6,6 +6,8 @@ argument-hint: "[focus]" The user invoked /fable-understand: that is explicit opt-in to workflow orchestration — call the Workflow tool. +Before dispatching, read `.claude/fable/CONFIG.md` if it exists and pass its yaml keys as `config` in the workflow args; words in the user's request override it for this run ("quick" = fleet light, "thorough"/"audit" = fleet max). + 1. The arguments name the focus (a subsystem, directory, or feature). If they are empty, map the whole repository. 2. Run the named workflow `fable-understand` with `args: { focus: "" }`. 3. Deliver the architecture brief with its path:line citations intact: system overview first, then per-area summaries, how the areas connect, and the "watch out" list of fragile spots. If an area reader failed, the brief marks that gap — surface it rather than smoothing it over. diff --git a/.claude/skills/fable/SKILL.md b/.claude/skills/fable/SKILL.md index 5460ad1..f37dff0 100644 --- a/.claude/skills/fable/SKILL.md +++ b/.claude/skills/fable/SKILL.md @@ -4,7 +4,7 @@ description: Session doctrine loader — reads the Fable Mode operating contract argument-hint: "[task]" --- -Read `.claude/fable/FABLE.md` in the project root and follow it as your operating doctrine for the rest of the session. It governs pacing, orchestration, verification, and reporting; explicit user instructions in the conversation always override it. +Read `.claude/fable/FABLE.md` in the project root and follow it as your operating doctrine for the rest of the session, including its Config section (`.claude/fable/CONFIG.md` sets workflow defaults). It governs pacing, orchestration, verification, and reporting; explicit user instructions in the conversation always override it. If the file does not exist, tell the user the Fable Mode pack is only partially installed (the doctrine file is missing) and continue without it. diff --git a/.claude/skills/ultra/SKILL.md b/.claude/skills/ultra/SKILL.md index dbc721b..14c3994 100644 --- a/.claude/skills/ultra/SKILL.md +++ b/.claude/skills/ultra/SKILL.md @@ -8,12 +8,14 @@ The user invoked /ultra: that is explicit opt-in to multi-agent workflow orchest The arguments to this command are the task. If there are no arguments, ask the user what the task is before doing anything else. +Read `.claude/fable/CONFIG.md` once if it exists and pass its yaml keys as `config` in the args of every workflow below; words in the user's request override it for this run. + Run the task through the phases, reading each workflow's result before choosing the next step. 1. **Understand.** Unless the relevant code is already well known to you from this session, run the named workflow `fable-understand` with `args: { focus: "" }`. 2. **Design.** If more than one reasonable approach exists, run `fable-design` with `args: { question: "" }` and adopt the synthesized design. If the approach is genuinely forced, write a three-line plan inline instead. 3. **Implement.** Make the changes yourself for a normal-sized diff, verifying compile/tests as you go. If the change is the same operation across many files, run `fable-migrate` with `args: { instruction: "..." }`. -4. **Review.** Run `fable-review` over the change set (`args: { target: "the uncommitted changes", votes: 3 }`). Fix every confirmed finding, re-run the tests, and re-run the review if the fixes were non-trivial. The task is done when review comes back clean and tests pass — or when the only remaining findings are ones you explicitly present to the user as accepted risks. +4. **Review.** Run `fable-review` over the change set (`args: { target: "the uncommitted changes", config: {...} }` — vote count comes from the config's fleet; don't pass `votes` unless the user asked for a specific count). Fix every confirmed finding, re-run the tests, and re-run the review if the fixes were non-trivial. The task is done when review comes back clean and tests pass — or when the only remaining findings are ones you explicitly present to the user as accepted risks. 5. **Ship** (only when the task includes releasing or deploying). Gate with `fable-ship` and hand the deploy trigger to the user. Scale honestly: if the task turns out to be trivial once understood, say so and skip ahead rather than burning a full pipeline on a one-line fix. diff --git a/.claude/workflows/fable-design.js b/.claude/workflows/fable-design.js index 0da51a7..c4aadc4 100644 --- a/.claude/workflows/fable-design.js +++ b/.claude/workflows/fable-design.js @@ -9,15 +9,27 @@ export const meta = { ], } -const question = typeof args === 'string' ? args : (args && args.question) +const input = typeof args === 'string' ? { question: args } : (args || {}) +const question = input.question if (!question) throw new Error('fable-design requires a design question — pass args: { question: "..." }') +// Config arrives via args.config — the skills read .claude/fable/CONFIG.md and pass it. +// sub() applies the configured subagent model/effort to every agent call in run(). +const cfg = (input && input.config && typeof input.config === 'object') ? input.config : {} +const fleet = ['light', 'standard', 'max'].includes(cfg.fleet) ? cfg.fleet : 'standard' +const sub = opts => ({ + ...opts, + ...(cfg.subagent_model && cfg.subagent_model !== 'inherit' ? { model: cfg.subagent_model } : {}), + ...(cfg.subagent_effort && cfg.subagent_effort !== 'inherit' ? { effort: cfg.subagent_effort } : {}), +}) +if (cfg.subagent_model || cfg.subagent_effort || cfg.fleet) log('config: subagents on ' + (cfg.subagent_model || 'the session model') + ' at ' + (cfg.subagent_effort || 'session') + ' effort, fleet ' + fleet) + // Falls back to the default agent when the pack's agents aren't registered yet // (agent types load at session start — a fresh install needs a restart). -const run = (prompt, opts) => agent(prompt, opts).catch(e => { +const run = (prompt, opts) => agent(prompt, sub(opts)).catch(e => { if (!opts.agentType || !String(e).includes('not found')) throw e log(opts.agentType + ' not registered (restart the session after installing the pack) — using the default agent') - return agent(prompt, { ...opts, agentType: undefined }) + return agent(prompt, { ...sub(opts), agentType: undefined }) }) const APPROACH = { @@ -55,8 +67,11 @@ const STANCES = [ 'evolution-first: optimize for how this will be extended and maintained over the next year', ] -const approaches = (await parallel(STANCES.map((stance, i) => () => - agent( +const stances = fleet === 'light' ? STANCES.slice(0, 2) : STANCES +if (stances.length < STANCES.length) log('fleet light: ' + stances.length + ' of ' + STANCES.length + ' design stances (evolution-first dropped)') + +const approaches = (await parallel(stances.map((stance, i) => () => + run( 'Design question: ' + question + '\n\n' + 'Explore the codebase first so the design is grounded in what actually exists — cite real files. ' + 'Then produce ONE complete design, taking this stance: ' + stance + '. ' + @@ -75,8 +90,10 @@ const briefFor = offset => approaches.map((_, k) => card((k + offset) % approach const brief = briefFor(0) const RUBRICS = ['correctness and edge-case coverage', 'implementation cost and risk', 'long-term maintainability'] +const rubrics = fleet === 'light' ? RUBRICS.slice(0, 2) : RUBRICS +if (rubrics.length < RUBRICS.length) log('fleet light: ' + rubrics.length + ' of ' + RUBRICS.length + ' judge rubrics (maintainability dropped)') -const judgments = (await parallel(RUBRICS.map((rubric, j) => () => +const judgments = (await parallel(rubrics.map((rubric, j) => () => run( 'Design question: ' + question + '\n\n' + briefFor(j) + '\n\n' + 'Presentation order is arbitrary — the approach number in each header, not position, identifies a candidate. ' + diff --git a/.claude/workflows/fable-exhaust.js b/.claude/workflows/fable-exhaust.js index e5c435e..56506fc 100644 --- a/.claude/workflows/fable-exhaust.js +++ b/.claude/workflows/fable-exhaust.js @@ -12,12 +12,23 @@ const input = typeof args === 'string' ? { hunt: args } : (args || {}) const hunt = input.hunt || 'defects: logic bugs, unhandled edge cases, race conditions, resource leaks, and security flaws' const scope = input.scope || 'the entire repository' +// Config arrives via args.config — the skills read .claude/fable/CONFIG.md and pass it. +// sub() applies the configured subagent model/effort to every agent call in run(). +const cfg = (input && input.config && typeof input.config === 'object') ? input.config : {} +const fleet = ['light', 'standard', 'max'].includes(cfg.fleet) ? cfg.fleet : 'standard' +const sub = opts => ({ + ...opts, + ...(cfg.subagent_model && cfg.subagent_model !== 'inherit' ? { model: cfg.subagent_model } : {}), + ...(cfg.subagent_effort && cfg.subagent_effort !== 'inherit' ? { effort: cfg.subagent_effort } : {}), +}) +if (cfg.subagent_model || cfg.subagent_effort || cfg.fleet) log('config: subagents on ' + (cfg.subagent_model || 'the session model') + ' at ' + (cfg.subagent_effort || 'session') + ' effort, fleet ' + fleet) + // Falls back to the default agent when the pack's agents aren't registered yet // (agent types load at session start — a fresh install needs a restart). -const run = (prompt, opts) => agent(prompt, opts).catch(e => { +const run = (prompt, opts) => agent(prompt, sub(opts)).catch(e => { if (!opts.agentType || !String(e).includes('not found')) throw e log(opts.agentType + ' not registered (restart the session after installing the pack) — using the default agent') - return agent(prompt, { ...opts, agentType: undefined }) + return agent(prompt, { ...sub(opts), agentType: undefined }) }) const BUGS = { @@ -65,19 +76,25 @@ const STANCES = [ 'hunt boundaries: empty, zero, negative, maximum, unicode, malformed input', ] +// Fleet scaling — every bound is announced up front, per the no-silent-caps rule. +const stances = fleet === 'light' ? STANCES.slice(0, 2) : STANCES +const panel = fleet === 'light' ? LENSES.slice(0, 1) : LENSES +const need = fleet === 'light' ? 1 : 2 +const MAX_ROUNDS = cfg.max_rounds || { light: 3, standard: 6, max: 8 }[fleet] +log('fleet ' + fleet + ': ' + stances.length + ' finder stances per round, ' + panel.length + '-lens verification, round cap ' + MAX_ROUNDS) + const key = b => b.file + ':' + b.line + ':' + b.title.toLowerCase().slice(0, 60) const seen = new Set() // Locations only (no titles) — this set is re-broadcast to every finder each round, // so it must stay small; the finders need "don't re-report here", not the details. const seenLocs = new Set() const confirmed = [] -const MAX_ROUNDS = 6 let round = 0 let dry = 0 while (dry < 2 && round < MAX_ROUNDS && (!budget.total || budget.remaining() > 40_000)) { round++ - const found = (await parallel(STANCES.map((stance, i) => () => + const found = (await parallel(stances.map((stance, i) => () => run( 'Hunt for ' + hunt + ' in ' + scope + '. Round ' + round + '. Your stance: ' + stance + '.' + (seenLocs.size ? '\nAlready found — do NOT re-report findings at these locations: ' + Array.from(seenLocs).slice(-200).join('; ') : ''), @@ -95,14 +112,14 @@ while (dry < 2 && round < MAX_ROUNDS && (!budget.total || budget.remaining() > 4 fresh.forEach(b => { seen.add(key(b)); seenLocs.add(b.file + ':' + b.line) }) const judged = await parallel(fresh.map(b => () => - parallel(LENSES.map(lens => () => + parallel(panel.map(lens => () => run( 'Claimed finding in ' + b.file + ':' + b.line + ' — "' + b.title + '". Scenario: ' + b.detail + '\n' + 'Judge it through ONE lens only — ' + lens + '\n' + CONTRAST, { label: 'verify:' + b.file + ':' + b.line, phase: 'Verify', schema: VERDICT, agentType: 'fable-skeptic' } ))) - .then(vs => ({ b, real: vs.filter(v => v && v.refuted === false).length >= 2 })) + .then(vs => ({ b, real: vs.filter(v => v && v.refuted === false).length >= need })) )) const kept = judged.filter(j => j.real).map(j => j.b) confirmed.push(...kept) diff --git a/.claude/workflows/fable-migrate.js b/.claude/workflows/fable-migrate.js index 1a286c9..450ba5f 100644 --- a/.claude/workflows/fable-migrate.js +++ b/.claude/workflows/fable-migrate.js @@ -17,12 +17,23 @@ const verifyCmd = input.verify || "the project's documented build and test commands (read the root CLAUDE.md and its @path imports and AGENTS.md first; " + "detect from package.json scripts, Makefile, or CI config only if the docs name none)" +// Config arrives via args.config — the skills read .claude/fable/CONFIG.md and pass it. +// sub() applies the configured subagent model/effort to every agent call in run(). +const cfg = (input && input.config && typeof input.config === 'object') ? input.config : {} +const fleet = ['light', 'standard', 'max'].includes(cfg.fleet) ? cfg.fleet : 'standard' +const sub = opts => ({ + ...opts, + ...(cfg.subagent_model && cfg.subagent_model !== 'inherit' ? { model: cfg.subagent_model } : {}), + ...(cfg.subagent_effort && cfg.subagent_effort !== 'inherit' ? { effort: cfg.subagent_effort } : {}), +}) +if (cfg.subagent_model || cfg.subagent_effort || cfg.fleet) log('config: subagents on ' + (cfg.subagent_model || 'the session model') + ' at ' + (cfg.subagent_effort || 'session') + ' effort, fleet ' + fleet) + // Falls back to the default agent when the pack's agents aren't registered yet // (agent types load at session start — a fresh install needs a restart). -const run = (prompt, opts) => agent(prompt, opts).catch(e => { +const run = (prompt, opts) => agent(prompt, sub(opts)).catch(e => { if (!opts.agentType || !String(e).includes('not found')) throw e log(opts.agentType + ' not registered (restart the session after installing the pack) — using the default agent') - return agent(prompt, { ...opts, agentType: undefined }) + return agent(prompt, { ...sub(opts), agentType: undefined }) }) const SITES = { @@ -121,7 +132,7 @@ const bad = results.filter(r => r && !r.ok) if (bad.length) log(bad.length + ' file(s) failed their per-file check — see perFileProblems') phase('Final verify') -const suite = await agent( +const suite = await run( 'A migration just touched ' + files.length + ' files ("' + input.instruction + '"). ' + 'Run ' + verifyCmd + ' and report the outcome verbatim — do not fix anything, just report what passed and what failed.', { label: 'project-checks', phase: 'Final verify' } diff --git a/.claude/workflows/fable-research.js b/.claude/workflows/fable-research.js index 1e6adcf..ee06588 100644 --- a/.claude/workflows/fable-research.js +++ b/.claude/workflows/fable-research.js @@ -10,15 +10,27 @@ export const meta = { ], } -const question = typeof args === 'string' ? args : (args && args.question) +const input = typeof args === 'string' ? { question: args } : (args || {}) +const question = input.question if (!question) throw new Error('fable-research requires a question — pass args: { question: "..." }') +// Config arrives via args.config — the skills read .claude/fable/CONFIG.md and pass it. +// sub() applies the configured subagent model/effort to every agent call in run(). +const cfg = (input && input.config && typeof input.config === 'object') ? input.config : {} +const fleet = ['light', 'standard', 'max'].includes(cfg.fleet) ? cfg.fleet : 'standard' +const sub = opts => ({ + ...opts, + ...(cfg.subagent_model && cfg.subagent_model !== 'inherit' ? { model: cfg.subagent_model } : {}), + ...(cfg.subagent_effort && cfg.subagent_effort !== 'inherit' ? { effort: cfg.subagent_effort } : {}), +}) +if (cfg.subagent_model || cfg.subagent_effort || cfg.fleet) log('config: subagents on ' + (cfg.subagent_model || 'the session model') + ' at ' + (cfg.subagent_effort || 'session') + ' effort, fleet ' + fleet) + // Falls back to the default agent when the pack's agents aren't registered yet // (agent types load at session start — a fresh install needs a restart). -const run = (prompt, opts) => agent(prompt, opts).catch(e => { +const run = (prompt, opts) => agent(prompt, sub(opts)).catch(e => { if (!opts.agentType || !String(e).includes('not found')) throw e log(opts.agentType + ' not registered (restart the session after installing the pack) — using the default agent') - return agent(prompt, { ...opts, agentType: undefined }) + return agent(prompt, { ...sub(opts), agentType: undefined }) }) const LEADS = { @@ -71,7 +83,7 @@ for (const s of sweeps) for (const l of s.leads || []) if (!byFile.has(l.file)) let leads = Array.from(byFile.values()) log(leads.length + ' unique leads from ' + sweeps.length + ' sweep modalities') -const MAX_LEADS = 30 +const MAX_LEADS = cfg.max_leads || { light: 12, standard: 30, max: 60 }[fleet] let unreadLeads = [] if (leads.length > MAX_LEADS) { unreadLeads = leads.slice(MAX_LEADS).map(l => l.file + ' — ' + l.why) diff --git a/.claude/workflows/fable-review.js b/.claude/workflows/fable-review.js index 386449a..ab34755 100644 --- a/.claude/workflows/fable-review.js +++ b/.claude/workflows/fable-review.js @@ -11,15 +11,27 @@ export const meta = { const input = typeof args === 'string' ? { target: args } : (args || {}) const target = input.target || 'the uncommitted working-tree changes (run `git status` and `git diff HEAD` to see them); if the tree is clean, review the most recent commit (`git show HEAD`); if this is not a git repository, review the most recently modified source files' -const votes = input.votes || 3 +// Config arrives via args.config — the skills read .claude/fable/CONFIG.md and pass it. +// sub() applies the configured subagent model/effort to every agent call in run(). +const cfg = (input && input.config && typeof input.config === 'object') ? input.config : {} +const fleet = ['light', 'standard', 'max'].includes(cfg.fleet) ? cfg.fleet : 'standard' +const sub = opts => ({ + ...opts, + ...(cfg.subagent_model && cfg.subagent_model !== 'inherit' ? { model: cfg.subagent_model } : {}), + ...(cfg.subagent_effort && cfg.subagent_effort !== 'inherit' ? { effort: cfg.subagent_effort } : {}), +}) +if (cfg.subagent_model || cfg.subagent_effort || cfg.fleet) log('config: subagents on ' + (cfg.subagent_model || 'the session model') + ' at ' + (cfg.subagent_effort || 'session') + ' effort, fleet ' + fleet) + +const votes = input.votes || cfg.votes || { light: 1, standard: 3, max: 5 }[fleet] const needed = Math.floor(votes / 2) + 1 +log(votes + ' skeptic vote(s) per finding, ' + needed + ' uphold(s) to survive') // Falls back to the default agent when the pack's agents aren't registered yet // (agent types load at session start — a fresh install needs a restart). -const run = (prompt, opts) => agent(prompt, opts).catch(e => { +const run = (prompt, opts) => agent(prompt, sub(opts)).catch(e => { if (!opts.agentType || !String(e).includes('not found')) throw e log(opts.agentType + ' not registered (restart the session after installing the pack) — using the default agent') - return agent(prompt, { ...opts, agentType: undefined }) + return agent(prompt, { ...sub(opts), agentType: undefined }) }) const FINDINGS = { @@ -68,8 +80,12 @@ const LENSES = [ // Must stay single-line — the drift checker compares this line byte-for-byte across workflows. const CONTRAST = 'First state the strongest concrete case that the claim is REAL, then the strongest case that it is NOT, then decide: set refuted=true only if the against-case wins under your lens, citing the decisive evidence.' +// Fleet scaling — announced, per the no-silent-caps rule. +const dimensions = fleet === 'light' ? DIMENSIONS.slice(0, 2) : DIMENSIONS +if (dimensions.length < DIMENSIONS.length) log('fleet light: ' + dimensions.length + ' of ' + DIMENSIONS.length + ' finder dimensions (security and resources dropped)') + const perDimension = await pipeline( - DIMENSIONS, + dimensions, d => run( 'Review ' + target + '.\n\n' + 'Focus exclusively on this dimension: ' + d.focus + '.\n\n' + diff --git a/.claude/workflows/fable-ship.js b/.claude/workflows/fable-ship.js index b6e4cc1..eb90ad9 100644 --- a/.claude/workflows/fable-ship.js +++ b/.claude/workflows/fable-ship.js @@ -12,12 +12,23 @@ export const meta = { const input = typeof args === 'string' ? { scope: args } : (args || {}) const scope = input.scope || 'the current state of this repository' +// Config arrives via args.config — the skills read .claude/fable/CONFIG.md and pass it. +// sub() applies the configured subagent model/effort to every agent call in run(). +const cfg = (input && input.config && typeof input.config === 'object') ? input.config : {} +const fleet = ['light', 'standard', 'max'].includes(cfg.fleet) ? cfg.fleet : 'standard' +const sub = opts => ({ + ...opts, + ...(cfg.subagent_model && cfg.subagent_model !== 'inherit' ? { model: cfg.subagent_model } : {}), + ...(cfg.subagent_effort && cfg.subagent_effort !== 'inherit' ? { effort: cfg.subagent_effort } : {}), +}) +if (cfg.subagent_model || cfg.subagent_effort || cfg.fleet) log('config: subagents on ' + (cfg.subagent_model || 'the session model') + ' at ' + (cfg.subagent_effort || 'session') + ' effort, fleet ' + fleet) + // Falls back to the default agent when the pack's agents aren't registered yet // (agent types load at session start — a fresh install needs a restart). -const run = (prompt, opts) => agent(prompt, opts).catch(e => { +const run = (prompt, opts) => agent(prompt, sub(opts)).catch(e => { if (!opts.agentType || !String(e).includes('not found')) throw e log(opts.agentType + ' not registered (restart the session after installing the pack) — using the default agent') - return agent(prompt, { ...opts, agentType: undefined }) + return agent(prompt, { ...sub(opts), agentType: undefined }) }) const MECH = { @@ -80,7 +91,7 @@ const commands = (mech && mech.commands) || [] const releaseNotes = (mech && mech.notes) || '' log(commands.length + ' project check(s) detected' + (commands.length ? ': ' + commands.map(c => c.purpose).join(', ') : ' — gates will note the absence')) -const checks = await agent( +const checks = await run( 'Shipping ' + scope + '. Run each of these project checks and report every outcome verbatim — do not fix anything:\n' + (commands.length ? commands.map(c => c.purpose + ': ' + c.command).join('\n') : '(none detected — say so and report what you would have expected to find)'), { label: 'gate:checks', phase: 'Gate' } diff --git a/.claude/workflows/fable-understand.js b/.claude/workflows/fable-understand.js index 17c685c..b90d860 100644 --- a/.claude/workflows/fable-understand.js +++ b/.claude/workflows/fable-understand.js @@ -12,12 +12,23 @@ export const meta = { const input = typeof args === 'string' ? { focus: args } : (args || {}) const focus = input.focus || 'the whole repository' +// Config arrives via args.config — the skills read .claude/fable/CONFIG.md and pass it. +// sub() applies the configured subagent model/effort to every agent call in run(). +const cfg = (input && input.config && typeof input.config === 'object') ? input.config : {} +const fleet = ['light', 'standard', 'max'].includes(cfg.fleet) ? cfg.fleet : 'standard' +const sub = opts => ({ + ...opts, + ...(cfg.subagent_model && cfg.subagent_model !== 'inherit' ? { model: cfg.subagent_model } : {}), + ...(cfg.subagent_effort && cfg.subagent_effort !== 'inherit' ? { effort: cfg.subagent_effort } : {}), +}) +if (cfg.subagent_model || cfg.subagent_effort || cfg.fleet) log('config: subagents on ' + (cfg.subagent_model || 'the session model') + ' at ' + (cfg.subagent_effort || 'session') + ' effort, fleet ' + fleet) + // Falls back to the default agent when the pack's agents aren't registered yet // (agent types load at session start — a fresh install needs a restart). -const run = (prompt, opts) => agent(prompt, opts).catch(e => { +const run = (prompt, opts) => agent(prompt, sub(opts)).catch(e => { if (!opts.agentType || !String(e).includes('not found')) throw e log(opts.agentType + ' not registered (restart the session after installing the pack) — using the default agent') - return agent(prompt, { ...opts, agentType: undefined }) + return agent(prompt, { ...sub(opts), agentType: undefined }) }) const AREAS = { @@ -40,8 +51,9 @@ const AREAS = { } phase('Partition') +const areaRange = fleet === 'light' ? '2-4' : '3-8' const partition = await run( - 'Partition ' + focus + ' into 3-8 coherent areas for parallel deep-reading. ' + + 'Partition ' + focus + ' into ' + areaRange + ' coherent areas for parallel deep-reading. ' + 'Scan the directory tree, entry points, build configuration, and dependency manifest first. ' + 'Give each area the paths that belong to it and the single most important question about it.', { label: 'partition', schema: AREAS, agentType: 'fable-scout' } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d14859..7b3fd4a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,8 +33,10 @@ jobs: echo "T=$t" >> "$GITHUB_ENV" expected="$(git ls-files .claude | wc -l)" ./install.sh "$t" + # +1: the installer materializes .claude/settings.json from the shipped template copied="$(find "$t/.claude" -type f | wc -l)" - [ "$copied" -eq "$expected" ] || { echo "expected $expected files, found $copied"; exit 1; } + [ "$copied" -eq "$((expected + 1))" ] || { echo "expected $((expected + 1)) files, found $copied"; exit 1; } + cmp .claude/fable/settings.template.json "$t/.claude/settings.json" [ "$(grep -cF '@.claude/fable/FABLE.md' "$t/CLAUDE.md")" -eq 1 ] - name: second run is idempotent run: | @@ -45,9 +47,13 @@ jobs: run: | echo tampered >> "$T/.claude/fable/FABLE.md" echo user-owned > "$T/.claude/agents/user-own.md" + echo 'fleet: light' >> "$T/.claude/fable/CONFIG.md" + echo '{"permissions":{"allow":["Workflow"]}}' > "$T/.claude/settings.json" ./install.sh --update "$T" cmp .claude/fable/FABLE.md "$T/.claude/fable/FABLE.md" [ "$(cat "$T/.claude/agents/user-own.md")" = "user-owned" ] + grep -q 'fleet: light' "$T/.claude/fable/CONFIG.md" || { echo "update overwrote user CONFIG.md"; exit 1; } + [ "$(cat "$T/.claude/settings.json")" = '{"permissions":{"allow":["Workflow"]}}' ] || { echo "update overwrote user settings.json"; exit 1; } install-ps1: runs-on: windows-latest @@ -62,8 +68,10 @@ jobs: $expected = @(git ls-files .claude).Count .\install.ps1 -Target $t + # +1: the installer materializes .claude/settings.json from the shipped template $copied = @(Get-ChildItem -Recurse -File (Join-Path $t '.claude')).Count - if ($copied -ne $expected) { throw "expected $expected files, found $copied" } + if ($copied -ne ($expected + 1)) { throw "expected $($expected + 1) files, found $copied" } + if (-not (Test-Path (Join-Path $t '.claude\settings.json'))) { throw 'fresh install did not materialize settings.json' } $imports = @(Select-String -Path (Join-Path $t 'CLAUDE.md') -SimpleMatch '@.claude/fable/FABLE.md').Count if ($imports -ne 1) { throw "expected 1 import line, found $imports" } @@ -74,8 +82,12 @@ jobs: Add-Content (Join-Path $t '.claude\fable\FABLE.md') 'tampered' Set-Content (Join-Path $t '.claude\agents\user-own.md') 'user-owned' + Add-Content (Join-Path $t '.claude\fable\CONFIG.md') 'fleet: light' + Set-Content (Join-Path $t '.claude\settings.json') '{"permissions":{"allow":["Workflow"]}}' .\install.ps1 -Target $t -Update $a = (Get-FileHash .claude\fable\FABLE.md).Hash $b = (Get-FileHash (Join-Path $t '.claude\fable\FABLE.md')).Hash if ($a -ne $b) { throw 'update did not restore FABLE.md byte-identical' } if ((Get-Content (Join-Path $t '.claude\agents\user-own.md')) -ne 'user-owned') { throw 'update touched a user-owned file' } + if (-not (Select-String -Path (Join-Path $t '.claude\fable\CONFIG.md') -SimpleMatch 'fleet: light' -Quiet)) { throw 'update overwrote user CONFIG.md' } + if ((Get-Content -Raw (Join-Path $t '.claude\settings.json')).Trim() -ne '{"permissions":{"allow":["Workflow"]}}') { throw 'update overwrote user settings.json' } diff --git a/.gitignore b/.gitignore index 12d60b5..91d8ce6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +.claude/settings.json .claude/settings.local.json .claude/scheduled_tasks.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index 10c2c89..f70fcfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ If a release ever removes a shipped file, the entry lists the stale path so `--update` users can delete it manually. +## 2.0.0 — 2026-08-07 + +The Claude 5 / value-maxxing release: pick your spend instead of prompting for it, tuned for the leaner Claude Code system prompts that shipped with the Claude 5 generation. + +- **Config file** — `.claude/fable/CONFIG.md` sets the defaults every workflow runs with: `subagent_model` (ships as `opus`), `subagent_effort` (ships as `medium`), `fleet` (`light` / `standard` / `max` — each fleet carries its own vote count of 1/3/5), and optional `votes` / round / lead-cap overrides. Skills read it at invocation and pass it as `config` in workflow args, so edits apply to the next command with no restart. `--update` installs never overwrite it (CI-tested on both installers). +- **`/fable-config`** — new skill (11th) to show settings, change keys, or apply presets: `value` (the shipped defaults), `light` (inherit/low, 1 skeptic), `max` (opus/high, 5 votes). It also prints the harness commands for the main agent — `/model`, `/effort`, thinking toggle — which a pack cannot set itself. +- **All 7 workflows** honor the config: subagent model/effort applied to every agent call, fleet-scaled pools, votes, and caps, each announced with `log()` (no silent caps). The shared config block is drift-checked like the `run()` helper. +- **Recommended setup** documented throughout: main agent Fable 5 at low effort (or Opus 5), subagents Opus at medium — lower effort with structural verification beats higher effort without it, at a fraction of the spend. +- **Doctrine and reporting** rewritten for the Claude 5 generation: `FABLE.md` slimmed to judgment-over-rules, agents and scribe now report in plain language (lead with the answer, numbered steps, short lists, no filler). +- **First-run settings** — a shipped template (`.claude/fable/settings.template.json`) is materialized as the target's `.claude/settings.json` on first install only, pre-approving the Workflow and Agent tools so fleet launches don't prompt. An existing settings file is never modified (the installer prints the two entries to add instead), `--update` never touches it, and deleting it opts out. Disclosed in SECURITY.md. +- **README, GUIDE.md** rewritten shorter; new SVG banner. Removed: `assets/banner.png`, `assets/review-pipeline.png` (stale; `--update` users can delete them manually). + ## 1.0.0 — 2026-07-10 First public release, MIT licensed. diff --git a/CLAUDE.md b/CLAUDE.md index e50ab88..7e8c728 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,12 +19,13 @@ node tools/check-workflows.cjs mkdir -p /tmp/fable-target && ./install.sh /tmp/fable-target # or: .\install.ps1 -Target ``` -## Architecture: four layers wired by name +## Architecture: five layers wired by name 1. **Doctrine** — `.claude/fable/FABLE.md`. Always-on operating contract, loaded via a `@.claude/fable/FABLE.md` import in the target's CLAUDE.md (created/appended by the installers). Its Orchestration list must stay in sync with the actual workflow names (the checker verifies the names resolve). -2. **Agents** — `.claude/agents/fable-*.md`. Seven subagents (scout, finder, skeptic, judge, builder, critic, scribe) that everything else is built from. -3. **Workflows** — `.claude/workflows/fable-*.js`. Scripts for the Workflow tool that fan out over the agents. -4. **Skills** — `.claude/skills/*/SKILL.md`. Slash commands that invoke the workflows by name and report results. +2. **Config** — `.claude/fable/CONFIG.md`. User-owned defaults (subagent model/effort, fleet, votes). Skills read it at invocation and pass its yaml keys as `config` in workflow args; workflows apply it via the shared config block. Once installed it belongs to the user: `--update` never overwrites it (both installers special-case `fable/CONFIG.md`, CI asserts it), and `/fable-config` carries a template to recreate it. Same ownership model for `.claude/settings.json`: the installers materialize it from `.claude/fable/settings.template.json` only when missing (pre-approving Workflow/Agent, disclosed in SECURITY.md), never modify an existing one, and never ship a root-level `settings.json` (basename-skipped in the copy loop, gitignored in this repo — CI's file-count check expects tracked+1 because of the materialization). +3. **Agents** — `.claude/agents/fable-*.md`. Seven subagents (scout, finder, skeptic, judge, builder, critic, scribe) that everything else is built from. +4. **Workflows** — `.claude/workflows/fable-*.js`. Scripts for the Workflow tool that fan out over the agents. +5. **Skills** — `.claude/skills/*/SKILL.md`. Slash commands that invoke the workflows by name and report results. The layers reference each other by **string name**; `tools/check-workflows.cjs` validates this wiring (agentType strings ↔ agent frontmatter names, backticked fable-* references in skills/doctrine ↔ workflow/agent/skill names, phase titles ↔ meta.phases), so run it after any rename or addition. One reference it cannot see: the installers detect an existing install by grepping CLAUDE.md for the literal string `@.claude/fable/FABLE.md` — if the doctrine path ever changes, update both installers, the FABLE.md references in the skills, and this import string together. @@ -34,14 +35,15 @@ The layers reference each other by **string name**; `tools/check-workflows.cjs` - `export const meta = {...}` must open the file and be a pure literal — no variables, calls, or spreads. `phases` titles must match the `phase()` calls / `phase:` opts in the body. - Plain JavaScript only (no TypeScript annotations). `Date.now()`, `Math.random()`, and argless `new Date()` throw in the sandbox. - Repo conventions to preserve: every workflow accepts `args` as either an object or a plain string (the harness can deliver args as a JSON-encoded string, so never access `args.x` bare); every deliberate bound (lead caps, round caps, budget stops) is announced with `log()` — no silent caps; `agent()`/`parallel()` results and schema arrays are null-guarded because skipped or dead agents resolve to null and structured output can come back with fields missing. -- Every workflow defines the same `run()` helper wrapping `agent()`: it retries without `agentType` when the agent isn't registered. This is load-bearing — agent types register at session start only, so a fresh install would otherwise kill every workflow until the session restarts. Use `run()` (not bare `agent()`) for any call that passes `agentType`. +- Every workflow defines the same `run()` helper wrapping `agent()`: it retries without `agentType` when the agent isn't registered. This is load-bearing — agent types register at session start only, so a fresh install would otherwise kill every workflow until the session restarts. Use `run()` (not bare `agent()`) for every agent call — it also applies the configured subagent model/effort via `sub()`. +- Every workflow carries the same config block (`const cfg` … `const sub` … the `log('config: …')` line) directly above `run()`. Both blocks are drift-checked byte-for-byte across workflows and required in each; fleet-dependent bounds (`votes`, round caps, stance slices) derive from `cfg`/`fleet` and must be announced with `log()`. ## Load-bearing wording - Each SKILL.md opens by declaring the invocation as the user's **explicit opt-in to the Workflow tool**. That sentence satisfies the Workflow tool's permission gating on other models — do not soften or remove it. - Each skill and the doctrine carry an Agent-tool fallback for Claude Code versions without the Workflow tool — keep it when adding skills. - FABLE.md is injected into every session of every target project; keep it lean and doctrine-only. -- Agents default to `model: inherit`; per-role pinning is user-facing tuning documented in README.md, not something to hardcode. +- Agents default to `model: inherit`; the supported model/effort lever is `CONFIG.md`'s `subagent_model` / `subagent_effort` (applied per-call by the workflows' `sub()` helper), not frontmatter pinning — don't hardcode models in agent files. # Fable Mode @.claude/fable/FABLE.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 121d8f2..fa566f4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,11 +8,12 @@ A distributable Claude Code configuration pack. The deliverable is the `.claude/ > **Everything under `.claude/` ships to every target project.** Repo-level files (CI, templates, assets, docs) live outside `.claude/`. Session-local or repo-local config never goes inside it. -## The four layers +## The five layers | Layer | Where | Referenced by | |---|---|---| | Doctrine | `.claude/fable/FABLE.md` | imported into the target's `CLAUDE.md` | +| Config | `.claude/fable/CONFIG.md` | read by skills, passed to workflows as `args.config` (user-owned after install — `--update` skips it) | | Agents | `.claude/agents/fable-*.md` | `agentType` strings in workflows | | Workflows | `.claude/workflows/fable-*.js` | invoked by name from skills | | Skills | `.claude/skills/*/SKILL.md` | slash commands | @@ -41,7 +42,8 @@ CI runs both, plus the PowerShell installer on Windows. A PR that fails the chec - Accept `args` as an object **or** a plain string (the harness may deliver JSON-encoded strings). - Announce every deliberate bound with `log()` — no silent caps. - Null-guard agent results; skipped or dead agents resolve to `null`. -- Use the shared `run()` helper (not bare `agent()`) for any call passing `agentType` — it falls back to the default agent on fresh installs where agent types haven't registered yet. +- Use the shared `run()` helper (not bare `agent()`) for every agent call — it falls back to the default agent on fresh installs where agent types haven't registered yet, and applies the configured subagent model/effort via `sub()`. +- Carry the shared config block (`const cfg` … the `log('config: …')` line) above `run()` in every workflow; both blocks are drift-checked byte-for-byte and required by the checker. Fleet-dependent bounds derive from `cfg`/`fleet` and are announced with `log()`. ## Adding an agent, workflow, or skill diff --git a/README.md b/README.md index af09519..96f37ae 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,25 @@
-Fable Mode — Fable 5's Ultracode discipline, ported to the models you can run all day +Fable Mode — Fable 5's Ultracode discipline, as a drop-in Claude Code pack. Verified fleets on any Claude model, at the spend you choose. [![CI](https://github.com/creativeskyai/Fable-Mode/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/creativeskyai/Fable-Mode/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/license-MIT-3da638)](LICENSE) [![Made for Claude Code](https://img.shields.io/badge/made%20for-Claude%20Code-D97757)](https://claude.com/claude-code) -[![Runs on](https://img.shields.io/badge/runs%20on-Opus%20·%20Sonnet%20·%20Haiku-8b7cff)](#which-model-should-drive) +[![Runs on](https://img.shields.io/badge/runs%20on-Fable%20·%20Opus%20·%20Sonnet%20·%20Haiku-8b7cff)](#the-value-setup) -**[Install](#install) · [See it work](#see-it-work) · [Commands](#the-commands) · [How it works](#how-it-works) · [Marathon](#continuous-operation-fable-marathon) · [Models](#which-model-should-drive) · [FAQ](#faq)** +**[Install](#install) · [The value setup](#the-value-setup) · [Config](#config) · [Commands](#the-commands) · [How it works](#how-it-works) · [FAQ](#faq)**
--- -Claude Fable 5 is Anthropic's Mythos-class model, built to run in an agent harness for days at a time: planning across stages, delegating to subagents, checking its own work. Most of us drive Opus, Sonnet, or Haiku day to day. +Fable Mode is a drop-in `.claude/` pack that gives any Claude Code project the working style of Claude Fable 5 running Ultracode: every substantive task becomes an orchestrated fleet, every finding is attacked by independent skeptics before you see it, discovery loops until it runs dry, and long jobs survive crashes and new sessions. One folder, two installer scripts, zero dependencies, MIT. -Fable Mode ports that working style to the model you already run. Every substantive task becomes an orchestrated fleet. Every finding is attacked by independent skeptics before you see it. Discovery loops until it runs dry. Long jobs survive crashes, compactions, and new sessions. One `.claude/` folder, two installer scripts, zero dependencies, MIT. - -The insight the pack is built on: most of what makes Fable-grade output trustworthy is process, and process is portable. Independent perspectives, skeptics that kill plausible-but-wrong claims before you read them, completeness critics, and reports that say exactly what was and wasn't covered. The pack ships that process as structure your model runs through, whichever model it is. - -Claude Code's own `ultracode` setting (Opus 4.8 / Sonnet 5, v2.1.203+) grants xhigh effort plus standing permission to orchestrate. What it doesn't ship is a *doctrine*: what the fleets should do with that permission, what verification means, when a hunt is done, when to stop and ask you. That doctrine is this pack. It composes with ultracode where you have it, and supplies the structure everywhere else, including on Haiku, which has no xhigh at all. - -## See it work - -With the doctrine wired in you don't run commands. You ask for work in plain language and the model orchestrates on its own; the skills exist for when you want specific machinery at a specific scale. Here is the trial every `/fable-review` finding has to survive: - -The fable-review pipeline: your diff fans out to four finders (correctness, contracts, security, resources); each raw finding must cite a concrete failure scenario, then faces three independent skeptics with different lenses; majority upholds and you see it, majority refutes and it dies - -The step most packs skip is the second one: **every raw finding goes on trial before it reaches you.** Three skeptics per finding, each judging through a different lens (trace it line by line; is it reachable from a real entry point; is the impact as claimed), majority verdict. A plausible-but-wrong finding dies in the pipeline instead of costing you twenty minutes of disproving it yourself. - -This very release was reviewed by its own fleet before merge. 28 agents, 8 raw findings, 3 survived cross-examination (one installer bug a skeptic reproduced live, two CI gaps), all fixed before merge. The five refuted claims died because skeptics verify the tree, not the finder's word: - -Real fable-review run over this release: 4 finders, 8 raw findings, 24 skeptic votes; 3 confirmed (install.sh version-hint ordering, missing CI timeout-minutes, missing CI cancel-in-progress) and 5 refuted as stale because the fixes had already landed +What's new in 2.0: **you set the spend in a config file instead of hoping the model infers it.** Subagent model, subagent effort, fleet size, and verification votes live in `.claude/fable/CONFIG.md`, changeable mid-session with `/fable-config`. The defaults are tuned for value, not token burn — benchmarks keep showing that lower effort with structural verification beats higher effort without it. ## Install -From a clone, into any project (the installer never overwrites existing files): +From a clone, into any project (never overwrites existing files): ```bash # macOS / Linux @@ -50,79 +34,89 @@ git clone --depth 1 https://github.com/creativeskyai/Fable-Mode.git $tmp & "$tmp\install.ps1" C:\path\to\your\project ``` -That copies the `.claude/` assets and adds one line to the target's `CLAUDE.md`: +That copies `.claude/**`, adds one import line to the target's `CLAUDE.md`, and — on first install only — creates `.claude/settings.json` pre-approving the **Workflow** and **Agent** tools, so fleet launches don't prompt (that's the standing authorization you're opting into; delete the file to opt out — everything agents *do* still runs under your normal permission mode). If you already have a `settings.json`, it's left untouched and the installer prints the two entries to add. Then **restart any open Claude Code session in that project** — agents register at session start. -```markdown -# Fable Mode -@.claude/fable/FABLE.md -``` - -Manual install is those same two steps done by hand. If you'd rather not touch `CLAUDE.md`, skip the import and run `/fable` at the start of a session instead. - -> [!IMPORTANT] -> **Restart any open Claude Code session in the target project after installing.** Agent types register at session start and don't hot-reload. (Workflows survive an unregistered agent by falling back to the default agent type, with a log line telling you to restart.) - -To update an installed project later, re-run the installer with `--update` / `-Update`: it refreshes every pack-owned file to the current version and touches nothing else. Your own agents, skills, and settings are never modified, but it does overwrite tuning edits you made *inside* pack files, so commit first and re-apply from the diff. The installed version is readable at `.claude/fable/VERSION`; changes per version are in [CHANGELOG.md](CHANGELOG.md). +To update later, re-run with `--update` / `-Update`: it refreshes pack files, never touches your own files, and never overwrites your `CONFIG.md`. Version at `.claude/fable/VERSION`; changes in [CHANGELOG.md](CHANGELOG.md).
-Uninstall (two steps, no script) +Uninstall -1. Remove the `# Fable Mode` heading and the `@.claude/fable/FABLE.md` import line from the project's `CLAUDE.md` (delete the file if the installer created it and you added nothing else). -2. Delete the pack's files. Everything is namespaced `fable*` except the `ultra` skill: +1. Remove the `# Fable Mode` heading and `@.claude/fable/FABLE.md` line from the project's `CLAUDE.md`. +2. If the installer created your `.claude/settings.json`, delete it (or just remove the `"Workflow"` / `"Agent"` entries if you've added your own since). +3. Delete the pack files (everything is namespaced `fable*` except `/ultra`): ```bash rm -rf .claude/fable .claude/skills/ultra .claude/skills/fable* rm -f .claude/agents/fable-*.md .claude/workflows/fable-*.js ``` +
-```powershell -Remove-Item -Recurse -Force .claude\fable, .claude\skills\ultra -Get-ChildItem .claude\agents\fable-*.md, .claude\workflows\fable-*.js | Remove-Item -Get-ChildItem .claude\skills -Directory -Filter fable* | Remove-Item -Recurse -Force +## The value setup + +Two dials. The main agent is a harness setting; the subagents are the pack's config. + +| | Set with | Recommended | +|---|---|---| +| **Main agent** | `/model`, `/effort`, thinking via Tab or `/config` | **Fable 5 at low effort** (or Opus 5) | +| **Subagents** | `.claude/fable/CONFIG.md`, via `/fable-config` | **Opus at medium effort** (the shipped default) | + +Why low effort on the driver: the pack's structure — independent finders, skeptics that refute, completeness critics — does the verification that high effort tries to do with more thinking. You get Ultracode-shaped results faster and cheaper. Turn effort (or thinking) back up only for genuinely hard single-brain reasoning; `/fable-config max` when you want audit-grade verification instead. + +## Config + +`.claude/fable/CONFIG.md` sets the defaults every fable command runs with: + +```yaml +subagent_model: opus # fable | opus | sonnet | haiku | inherit (= session model) +subagent_effort: medium # low | medium | high | xhigh | inherit +fleet: standard # light | standard | max — scales pools, votes, round caps +# votes: 3 # uncomment to pin the skeptic count instead of the fleet's (1/3/5) ``` - +Change it any time — applies to the next command, no restart: + +``` +/fable-config # show current settings +/fable-config fleet light # fewer agents, 1 skeptic per finding +/fable-config subagent_model haiku # cheap subagents +/fable-config max # audit mode: opus/high, 5 votes +/fable-config value # back to the shipped defaults +``` + +Words in a request still override for one run: "quick" or "no agents" drops to solo work, "thorough" or "audit" scales to fleet max, and a stated budget ("+500k") is a hard cap. Every workflow announces every bound it applies — a stopped run always says what it skipped. ## The commands | Your question | Run | What happens | |---|---|---| -| "Build X" (substantive, end to end) | `/ultra` | understand → design → implement → review, full pipeline | -| "How is this codebase organized?" | `/fable-understand` | partition, parallel deep-reads, one cited architecture brief | +| "Build X" (substantive, end to end) | `/ultra` | understand → design → implement → review | +| "How is this codebase organized?" | `/fable-understand` | parallel deep-reads, one cited brief | | "Where is X handled? What breaks if I change Y?" | `/fable-research` | 5 search modalities, cited answer, completeness critic | -| "How should I build X?" (decision only) | `/fable-plan` | 3 divergent designs, judge panel, synthesized plan | +| "How should I build X?" (decision only) | `/fable-plan` | divergent designs, judge panel, synthesized plan | | "Review this diff / branch / PR" | `/fable-review` | 4 finder dimensions, every finding attacked by skeptics | -| "Find ALL the bugs / audit this module" | `/fable-exhaust` | rounds of diverse finders until two rounds come up dry | -| "Apply this change everywhere" | `/fable-migrate` | discover every site, pilot on 2 files, transform + verify each | -| "Are we ready to release?" | `/fable-ship` | project checks + hygiene + docs, then a skeptic attacks "ready" | -| "Keep working on this for hours/days" | `/fable-marathon` | continuous verified cycles with a persistent run file | -| Doctrine isn't loaded (fresh clone) | `/fable` | loads the operating contract mid-session | +| "Find ALL the bugs / audit this" | `/fable-exhaust` | finder waves until two rounds come up dry | +| "Apply this change everywhere" | `/fable-migrate` | discover every site, pilot on 2 files, transform + verify | +| "Are we ready to release?" | `/fable-ship` | project checks + hygiene + docs, skeptic attacks "ready" | +| "Keep working on this for hours/days" | `/fable-marathon` | verified cycles with a persistent run file | +| Change the defaults | `/fable-config` | edits CONFIG.md, shows presets | +| Doctrine isn't loaded | `/fable` | loads the operating contract mid-session | -The one command whose stopping rule deserves a sentence is `/fable-exhaust`: a fixed number of sweeps is a guess about how much there is to find, so it keeps launching differently-angled finder waves until two consecutive rounds surface nothing new, then stops on that evidence. - -A one-page routing guide (what to run when, look-alikes disambiguated, cost feel per command) ships with the pack as [`.claude/fable/GUIDE.md`](.claude/fable/GUIDE.md), so it's in every project you install into. +Mostly, though, you run nothing: the doctrine is in every session's context and the model orchestrates on its own. A one-page routing guide ships with the pack as [`.claude/fable/GUIDE.md`](.claude/fable/GUIDE.md). ## How it works -One folder, four layers, wired by string name, with a [checker](tools/check-workflows.cjs) in CI that keeps every name resolving: +One folder, five layers, wired by string name, with a [checker](tools/check-workflows.cjs) in CI that keeps every name resolving: ``` .claude/ -├── fable/FABLE.md the doctrine — always-on operating contract, imported into CLAUDE.md -├── skills/ 10 slash commands (the layer you touch) -├── workflows/ 7 deterministic fan-out scripts for Claude Code's Workflow tool -└── agents/ 7 specialists the fleets are built from +├── fable/FABLE.md the doctrine — always-on contract, imported into CLAUDE.md +├── fable/CONFIG.md your defaults — read at invocation, passed into every workflow +├── skills/ 11 slash commands (the layer you touch) +├── workflows/ 7 deterministic fan-out scripts for the Workflow tool +└── agents/ 7 specialists: scout, finder, skeptic, judge, builder, critic, scribe ``` -| Agent | Role | -|---|---| -| `fable-scout` | reads and maps territory (read-only) | -| `fable-finder` | hunts defects, each backed by a concrete failure scenario | -| `fable-skeptic` | adversarial verifier: weighs claims both ways, kills the wrong ones | -| `fable-judge` | scores candidates on one rubric, grounded in the code | -| `fable-builder` | precisely-scoped edits, self-verified | -| `fable-critic` | completeness critic: finds what's missing | -| `fable-scribe` | synthesizes fan-out results into one readable report | +The step most packs skip: **every raw finding goes on trial before it reaches you.** Skeptics judge each finding through different lenses (trace it line by line; is it reachable; is the impact real), majority verdict. A plausible-but-wrong finding dies in the pipeline instead of costing you twenty minutes. ```mermaid flowchart LR @@ -131,146 +125,68 @@ flowchart LR W --> F2["finder
contracts"] W --> F3["finder
security"] W --> F4["finder
resources"] - F1 & F2 & F3 & F4 --> S{"3 skeptics
per finding"} + F1 & F2 & F3 & F4 --> S{"skeptics vote
per finding"} S -->|"majority upholds"| OK["you see it"] S -->|"majority refutes"| DEAD["it dies here"] ``` -Every workflow announces every bound it applies (round caps, lead caps, budget stops), so a stopped run always says what it skipped. The doctrine writes this down as a rule: `no silent caps`. - -### How it maps to Fable 5 Ultracode - -| Ultracode behavior | Pack mechanism | -|---|---| -| Workflow orchestration by default | standing authorization in `FABLE.md`, plus per-skill opt-in | -| Adversarial verify (N refuting skeptics, majority kills) | `fable-review` verify stage; `fable-skeptic` agent | -| Perspective-diverse verification | three-lens panels in `fable-review` and `fable-exhaust` | -| Judge panel for wide solution spaces | `fable-design` + `fable-judge` (rotated presentation order per judge) | -| Loop-until-dry discovery | `fable-exhaust` (2 consecutive dry rounds to stop) | -| Multi-modal sweep | `fable-research` (names, content, structure, history, tests) | -| Completeness critic | `fable-critic`; final stage of `fable-research` | -| Token-budget awareness (`+500k` directives) | `fable-exhaust` checks `budget.remaining()` each round | -| Long-running autonomous work | `/fable-marathon` + `FABLE-RUN.md` state file; composes with `/loop` | -| Release gating before deploys | `fable-ship` (verifies readiness; never deploys) | - -## Continuous operation (`/fable-marathon`) +A real run of this pipeline over this repo's own 1.0 release — 4 finders, 8 raw findings, 24 skeptic votes, 3 confirmed and fixed before merge: -Built for overnight and multi-day runs. All state lives in `FABLE-RUN.md` at the project root: goal, backlog with machine-checkable `done-when:` commands, journal, next action. It's created automatically and committed at every verified milestone, so **any session resumes from the file alone**, across crashes, compactions, or machines. Each cycle executes one backlog item through the full phase discipline and checkpoints. +Real fable-review run: 4 finders, 8 raw findings, 24 skeptic votes; 3 confirmed, 5 refuted as stale because the fixes had already landed -Three disciplines keep unattended runs honest: - -- **Walls** — actions that always queue for you (secrets, payments, deploys, anything destructive), written into the run file so they survive compaction. -- **Invariants** — finished items graduate into cheap re-runnable check commands that every cycle re-verifies, so nothing that passed once rots silently. -- **The standoff rule** — when implementation and verification reject the same fix twice, the item blocks for you instead of burning tokens all night. - -For unattended operation, compose it with whatever loop mechanism your Claude Code provides: - -``` -/loop /fable-marathon # self-paced recurring cycles -/loop 30m /fable-marathon # fixed interval -``` - -or point a scheduled task at the same command. Where your Claude Code has `/goal`, a backlog item's `done-when:` command doubles as the goal condition. Marathon stops on its own only for things that are yours: an empty backlog, a question only you can answer, or a deploy-shaped action (gated behind `/fable-ship` and handed to you). - -## Which model should drive? - -The pack is model-agnostic. Agents ship with `model: inherit`, so everything runs on your session model. What changes per model is what the structure buys you: - -| Session model | Sweet spot | What the pack adds | -|---|---|---| -| **Opus 4.8** | design-heavy features, audits, overnight marathons | breadth: panels and skeptic votes catch what even a strong single pass misses | -| **Sonnet 5** | the daily driver: reviews, research, migrations | rigor: Ultracode-style verification at run-all-day prices | -| **Haiku 4.5** | scoped questions, mechanical migrations | honesty: the discipline reports gaps a small model would otherwise gloss over | - -Three practical notes: - -- On Opus 4.8 and Sonnet 5 (Claude Code v2.1.203+), `/effort ultracode` pairs naturally with the pack: ultracode supplies the effort and the standing permission, Fable Mode supplies the process. On Haiku 4.5 there is no xhigh, so the pack's structure is the whole game there. -- Per-agent model pinning in agent frontmatter (e.g. scouts on Haiku) is the intended tuning lever, **but Claude Code currently ignores that field** ([#44385](https://github.com/anthropics/claude-code/issues/44385)): subagents inherit the session model regardless. The shipped files stay on `inherit` deliberately. -- Reasoning effort is the better lever today: the Workflow tool accepts per-agent `effort` overrides. Spend it where the loop branches (skeptics and judges) and keep mechanical stages at default. The shipped workflows set no overrides; see [Tuning](#tuning). - -## Tuning - -- **Verification strictness** — `votes` in the review workflow (3 default, 5 for audits); dry-round threshold and `MAX_ROUNDS` in `fable-exhaust`. -- **Per-stage effort** — workflow agents inherit the session's reasoning effort; pass `effort: 'high'` on skeptic/judge stages if you want the loop's branch points thinking harder. -- **Doctrine** — `FABLE.md` is plain markdown; edit the scale dial or reporting rules to taste. It loads into every session, so keep it lean. -- **Cost control** — the scale dial is the main lever: "quick" or "no agents" in your request drops to solo work; "thorough" / "audit" scales up. A budget you state ("+500k") is a hard cap. Prefer `/fable-research` (one bounded sweep) over `/fable-exhaust` (loops until dry) for scoped questions. - -### What the pack reads from your repo, if present - -Workflows treat your project's own operating docs as authoritative before self-detecting anything: the root `CLAUDE.md` plus every file it imports via `@path` lines, `AGENTS.md`, a decision log at `DECISIONS.md` or `docs/DECISIONS.md` (entries marked **Locked** are settled constraints that reviewers cite instead of relitigating), and `FABLE-RUN.md`'s Walls. Nothing is required: a project that documents none of this gets plain self-detection (package.json scripts, Makefile, CI config). Document your commands once, in your own files, and every fleet uses them. +For long jobs, `/fable-marathon` keeps all state in `FABLE-RUN.md` at the project root — goal, walls (actions that always queue for you), backlog with machine-checkable `done-when:` commands, journal — committed at every verified milestone, so any session resumes from the file alone. Unattended: `/loop /fable-marathon` or a scheduled task. ## Requirements | Your Claude Code | What you get | |---|---| -| Workflow tool available (documented floor: v2.1.154+, paid plan) | the full experience: named workflows, deterministic orchestration | -| Agent tool only | the same structures via each skill's shipped fallback: parallel subagents, same stages, less deterministic control flow | -| Neither | doctrine-only via `/fable`: phase discipline and reporting standards, solo execution | +| Workflow tool (v2.1.154+, paid plan) | the full experience: named workflows, deterministic orchestration | +| Agent tool only | the same stages via each skill's shipped fallback | +| Neither | doctrine-only via `/fable`: phase discipline, solo execution | -Two things worth knowing going in: - -- **You're trading tokens for verification.** Costs and the levers that control them are in the [FAQ](#faq). -- **What hot-reloads:** skills and workflow scripts are read at invocation, so edits apply immediately. Agents and the `CLAUDE.md` import load at session start and need a restart. +Skills, workflows, and CONFIG.md hot-reload at invocation. Agents and the CLAUDE.md import load at session start — restart after installing. ## FAQ
Is this a plugin? Do I need a marketplace? -No. It's a drop-in `.claude/` configuration pack, not a marketplace plugin, and it coexists fine with any plugins you already use. No plugin manager, no npm, no build step. It works anywhere Claude Code reads project configuration: CLI, desktop, web, IDE extensions. +No. It's a drop-in `.claude/` configuration pack. No plugin manager, no npm, no build step. Works anywhere Claude Code reads project config: CLI, desktop, web, IDE extensions.
Will it fight my existing CLAUDE.md or agents? -It adds one import line and namespaced files (`fable-*`, plus `/ultra`), and the installer never overwrites anything that exists. Your explicit instructions always override the doctrine; that rule is written into the doctrine itself. +It adds one import line and namespaced files, and the installer never overwrites anything that exists. Your explicit instructions always override the doctrine — that rule is written into the doctrine itself.
-What does a run actually cost? +What does a run cost? -Orchestration multiplies agents: `/fable-review` is ~16+ agents, `/fable-exhaust` can be dozens across rounds. Every workflow logs every bound it applies, and "quick" in your request drops to solo work. If you state a token budget, it's treated as a hard cap. +Orchestration multiplies agents: `/fable-review` is ~16 agents at standard fleet (4 finders, 3 skeptics per finding) and roughly a third of that at light (2 finders, 1 skeptic per finding). The config is the dial — `/fable-config fleet light` shrinks every workflow's pools, votes, and caps. "Quick" in a request drops to solo work; a stated token budget is a hard cap.
Does it make my model as good as Fable 5? -It makes your model's *work* go through the same gauntlet Fable's does: mapped before designed, reviewed by agents that didn't write it, hunted until dry, reported without gaps glossed over. On hard problems, that process accounts for a large share of the difference you can see in the output, even though the weights stay yours. -
- -
-Does it work outside Claude Code? - -The doctrine, agents, and skills port reasonably (they're markdown); the workflow scripts are Claude-Code-specific (the Workflow tool's JS sandbox). Every skill ships an Agent-tool fallback that emulates its stages, and on hosts that read `.claude/` directories that fallback becomes the primary path. First-class support for other harnesses is open for discussion in the issues. +It makes your model's *work* go through the same gauntlet: mapped before designed, reviewed by agents that didn't write it, hunted until dry, reported without gaps glossed over. On hard problems that process accounts for a large share of the visible difference — the weights stay yours.
-Can I adopt just one piece? +If a run dies mid-flight? -Yes. `/fable` loads doctrine only. Any skill works standalone. The seven agents are directly usable from the Agent tool without any workflow. Delete what you don't want; everything is namespaced. +Re-invoke the skill with narrower args; no repo state is lost. Marathon resumes from `FABLE-RUN.md` alone. "Agent type not found" everywhere means a fresh install — restart the session (runs still finish via a fallback meanwhile).
-If a run dies mid-flight? +Does it work outside Claude Code? -Workflows are stateless between invocations: re-invoke the skill, narrowing the args to what's still unanswered; no repo state is lost. `/fable-marathon` resumes from `FABLE-RUN.md` alone. "Agent type not found" everywhere means the pack was just installed: restart the session (runs still complete via the default-agent fallback in the meantime). +The doctrine, agents, and skills are markdown and port reasonably; the workflow scripts are Claude-Code-specific. Every skill ships an Agent-tool fallback that becomes the primary path on hosts without the Workflow tool.
## Contributing -The wiring is strict and the checker enforces it. Read [CONTRIBUTING.md](CONTRIBUTING.md) before your first PR. Security reports: [SECURITY.md](SECURITY.md). - -If Fable Mode killed a bad merge for you, that's a good reason to star the repo. It's how the next person finds it. - -
- - - - - Star history chart - - -
+The wiring is strict and the checker enforces it — read [CONTRIBUTING.md](CONTRIBUTING.md) first. Security reports: [SECURITY.md](SECURITY.md). If Fable Mode killed a bad merge for you, star the repo — it's how the next person finds it. ## License diff --git a/SECURITY.md b/SECURITY.md index 298e3e8..314d153 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -8,7 +8,7 @@ Fable Mode ships no runtime application code. The pack is: - **Workflow scripts** that run inside Claude Code's sandboxed Workflow tool (no filesystem or Node API access); - **Two installer scripts** (`install.sh`, `install.ps1`) that copy files and append one import line to a `CLAUDE.md`. -The installers never delete, never overwrite existing files outside `--update` mode (which only touches pack-owned paths), never elevate, and never touch the network. The agents and workflows the pack defines run entirely under Claude Code's own permission system — the pack cannot grant itself capabilities your Claude Code configuration doesn't allow. +The installers never delete, never overwrite existing files outside `--update` mode (which only touches pack-owned paths), never elevate, and never touch the network. One deliberate, disclosed exception to "grants nothing": on first install only, if the target has no `.claude/settings.json`, the installer creates one that pre-approves the **Workflow** and **Agent** tools (fleet launches stop prompting — the standing authorization the pack is built around). It never modifies an existing settings file, `--update` never touches it, and deleting the file restores full prompting. Everything the agents and workflows *do* — edits, commands — still runs under Claude Code's own permission system; the pack cannot grant itself capabilities beyond that. That said, prompt-injection-shaped issues are real for instruction packs: if you find wording in a shipped file that could cause Claude Code to take an action a reasonable user wouldn't expect (bypassing its permission prompts, exfiltrating data, weakening its own verification), treat that as a vulnerability and report it. diff --git a/assets/banner.png b/assets/banner.png deleted file mode 100644 index 2cfbcec..0000000 Binary files a/assets/banner.png and /dev/null differ diff --git a/assets/banner.svg b/assets/banner.svg new file mode 100644 index 0000000..6e88532 --- /dev/null +++ b/assets/banner.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Fable Mode + + Fable 5’s Ultracode discipline, as a drop-in Claude Code pack. + Verified fleets on any Claude model — at the spend you choose. + diff --git a/assets/review-pipeline.png b/assets/review-pipeline.png deleted file mode 100644 index 4b72d98..0000000 Binary files a/assets/review-pipeline.png and /dev/null differ diff --git a/install.ps1 b/install.ps1 index 3fcda13..e5645a9 100644 --- a/install.ps1 +++ b/install.ps1 @@ -44,8 +44,14 @@ $skipped = 0 foreach ($f in Get-ChildItem -Path $src -Recurse -File) { $rel = $f.FullName.Substring($src.Length).TrimStart('\', '/') # Session-local / OS droppings are not pack files — never ship them. - if ($f.Name -in @('settings.local.json', 'scheduled_tasks.lock', '.DS_Store')) { Write-Host "skip (session-local): .claude\$rel"; continue } + if ($f.Name -in @('settings.json', 'settings.local.json', 'scheduled_tasks.lock', '.DS_Store')) { Write-Host "skip (session-local): .claude\$rel"; continue } $out = Join-Path $dest $rel + # CONFIG.md is user-owned tuning once installed — -Update never overwrites it. + if (($rel -replace '\\', '/') -eq 'fable/CONFIG.md' -and (Test-Path -LiteralPath $out) -and $Update) { + Write-Host "skip (user config): .claude\$rel" + $skipped++ + continue + } if ((Test-Path -LiteralPath $out) -and -not $Update) { Write-Host "skip (exists): .claude\$rel" $skipped++ @@ -56,6 +62,17 @@ foreach ($f in Get-ChildItem -Path $src -Recurse -File) { $copied++ } +# First-run settings: materialize the template so Workflow/Agent launches don't +# prompt (the pack's standing authorization). Never touches an existing file. +$settings = Join-Path $dest 'settings.json' +if (-not (Test-Path -LiteralPath $settings)) { + Copy-Item -LiteralPath (Join-Path $src 'fable\settings.template.json') -Destination $settings + Write-Host 'created .claude/settings.json (pre-approves the Workflow and Agent tools; delete it to opt out)' +} +elseif (-not (Select-String -LiteralPath $settings -SimpleMatch '"Workflow"' -Quiet)) { + Write-Host 'note: .claude/settings.json exists - to skip permission prompts on fleet launches, add "Workflow" and "Agent" to permissions.allow' +} + $claudeMd = Join-Path $Target 'CLAUDE.md' $import = '@.claude/fable/FABLE.md' if (-not (Test-Path -LiteralPath $claudeMd)) { diff --git a/install.sh b/install.sh index f45a965..e9b8389 100755 --- a/install.sh +++ b/install.sh @@ -46,9 +46,15 @@ while IFS= read -r -d '' f; do rel="${f#"$src"/}" # Session-local / OS droppings are not pack files — never ship them. case "$(basename "$f")" in - settings.local.json|scheduled_tasks.lock|.DS_Store) echo "skip (session-local): .claude/$rel"; continue ;; + settings.json|settings.local.json|scheduled_tasks.lock|.DS_Store) echo "skip (session-local): .claude/$rel"; continue ;; esac out="$dest/$rel" + # CONFIG.md is user-owned tuning once installed — --update never overwrites it. + if [ "$rel" = "fable/CONFIG.md" ] && [ -e "$out" ] && [ "$update" -eq 1 ]; then + echo "skip (user config): .claude/$rel" + skipped=$((skipped + 1)) + continue + fi if [ -e "$out" ] && [ "$update" -ne 1 ]; then echo "skip (exists): .claude/$rel" skipped=$((skipped + 1)) @@ -59,6 +65,18 @@ while IFS= read -r -d '' f; do copied=$((copied + 1)) done < <(find "$src" -type f -print0) +# First-run settings: materialize the template so Workflow/Agent launches don't +# prompt (the pack's standing authorization). Never touches an existing file. +settings="$dest/settings.json" +if [ ! -e "$settings" ]; then + cp "$src/fable/settings.template.json" "$settings" + echo "created .claude/settings.json (pre-approves the Workflow and Agent tools; delete it to opt out)" +else + if ! grep -q '"Workflow"' "$settings"; then + echo 'note: .claude/settings.json exists — to skip permission prompts on fleet launches, add "Workflow" and "Agent" to permissions.allow' + fi +fi + claude_md="$target/CLAUDE.md" import='@.claude/fable/FABLE.md' if [ ! -f "$claude_md" ]; then diff --git a/tools/check-workflows.cjs b/tools/check-workflows.cjs index 2adab5f..57b5d74 100644 --- a/tools/check-workflows.cjs +++ b/tools/check-workflows.cjs @@ -82,6 +82,7 @@ for (const [f, raw] of workflowRaw) { // helper (and some share a LENSES panel). Identical-by-convention becomes // identical-by-check here. const TWINS = [ + ['config block', /^const cfg = [\s\S]*?effort, fleet ' \+ fleet\)$/m], ['run() helper', /^const run = [\s\S]*?\n\}\)$/m], ['LENSES panel', /^const LENSES = \[[\s\S]*?\n\]$/m], // CONTRAST must stay single-line — this regex compares only the one line. @@ -100,6 +101,14 @@ for (const [label, re] of TWINS) { // A file that deletes its copy of a twin block would silently drop out of the drift // comparison above — require the blocks where they are load-bearing. const REQUIRED_TWINS = { 'fable-review.js': ['const LENSES', 'const CONTRAST'], 'fable-exhaust.js': ['const LENSES', 'const CONTRAST'] }; +// Every workflow must carry the config block and the run() helper, matching the +// twin regex exactly — an includes() check alone would let a copy whose anchor +// lines were edited drop out of the drift comparison above while still "existing". +for (const [f, raw] of workflowRaw) { + for (const [label, re] of TWINS.filter(([l]) => l === 'config block' || l === 'run() helper')) { + if (!re.test(raw)) bad(f, 'required shared block (' + label + ') is missing or its anchor lines have drifted from the canonical form'); + } +} for (const [f, needles] of Object.entries(REQUIRED_TWINS)) { for (const n of needles) { if (!(workflowRaw.get(f) || '').includes(n)) bad(f, 'expected twin block "' + n + '" is missing');