riskrails 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +2 -2
  4. package/README.md +246 -812
  5. package/agents/ai-reviewer.md.tmpl +6 -4
  6. package/ai/policies/review-economy.md +7 -2
  7. package/ai/using-riskrails.md +15 -0
  8. package/bin/claude-1m +15 -14
  9. package/bin/riskrails.js +108 -32
  10. package/codex/hooks.json +25 -2
  11. package/hooks/AGENTS.md +2 -2
  12. package/hooks/CLAUDE.md +2 -2
  13. package/hooks/ai-guard.py +29 -8
  14. package/hooks/ai-path-guard-defaults.json +3 -1
  15. package/hooks/codex-hooks.json +25 -2
  16. package/hooks/context-guard.py +251 -35
  17. package/hooks/dialog-gate.py +105 -0
  18. package/hooks/guard/common.py +5 -0
  19. package/hooks/guard/decision.py +123 -0
  20. package/hooks/guard/dialog.py +598 -0
  21. package/hooks/guard/git.py +49 -3
  22. package/hooks/guard/paths.py +62 -3
  23. package/hooks/hooks.json +37 -2
  24. package/hooks/memory-pre-push.py +28 -0
  25. package/hooks/runtime-gate.py +59 -12
  26. package/hooks/update_notice.py +135 -0
  27. package/instructions/routing.md +4 -4
  28. package/instructions/stub.md +2 -2
  29. package/package.json +1 -1
  30. package/profiles/max.json +3 -1
  31. package/profiles/pro.json +3 -1
  32. package/scripts/merge-codex-config.py +134 -0
  33. package/scripts/resolve-profile.py +15 -3
  34. package/settings.common.json +39 -2
  35. package/skills/AGENTS.md +2 -2
  36. package/skills/CLAUDE.md +2 -2
  37. package/skills/ai-init/templates/.ai/AGENTS.md +2 -2
  38. package/skills/ai-init/templates/.ai/VERSION +1 -1
  39. package/skills/ai-init/templates/.ai/policies/dialog.json +1 -0
  40. package/skills/ai-init/templates/.ai/policies/safety.md +22 -2
  41. package/skills/ai-init/templates/.ai/policies/weight.json +1 -0
  42. package/skills/ai-init/templates/.ai/policies/weight.md +2 -2
  43. package/skills/ai-init/templates/.ai/state/README.md +9 -6
  44. package/skills/ai-init/templates/AGENTS.block.md +1 -1
  45. package/skills/ai-init/templates/CLAUDE.block.md +1 -1
  46. package/skills/ai-init/templates/GEMINI.block.md +1 -1
  47. package/skills/ai-init/templates/junie-guidelines.block.md +1 -1
  48. package/skills/ai-task/memory_compact.py +968 -0
  49. package/skills/ai-task/memory_format.py +775 -0
  50. package/skills/ai-task/memory_store.py +528 -0
  51. package/skills/ai-task/memory_sync.py +1571 -0
  52. package/skills/ai-task/sensors.py +2 -2
  53. package/skills/ai-task/state.py +967 -70
  54. package/skills/ai-task/weight.py +3 -3
  55. package/skills/project-init/templates/constitution.md +1 -1
  56. package/skills/project-update/history/blobs/164cd34c182de88c33de7df35beb29200d792419e1c13a5f0b2104f944e57be7 +1 -0
  57. package/skills/project-update/history/blobs/1d1d907449ec74594d0ee923ac142ae746b9aedf0a1eda9469f075a44d23e3d7 +13 -0
  58. package/skills/project-update/history/blobs/23a42febbacaa7b52a802d428033a99c0694391c81d51fe3b6e1dff608a641d5 +67 -0
  59. package/skills/project-update/history/blobs/5353ca529c46f2443e12947ec205a6887fb594a4de1f87f0eb1c0e6c0f826133 +49 -0
  60. package/skills/project-update/history/blobs/5f65bc7ab3fc3c4e75ae05d49eaf4331fafc398c661abe33c69f7380a720344c +32 -0
  61. package/skills/project-update/history/blobs/7be527cd648b27cca6e267de30f8e33a2ce17d5631a82694f23ae5c0623efe1f +15 -0
  62. package/skills/project-update/history/blobs/7ee99edafe7cec45ff577376c0d0178fdb2690134d4656488f46e129aba3cc5a +67 -0
  63. package/skills/project-update/history/blobs/8b3ef9586491a426767c01da5d01bdd6b0655c576c68c4cc6dfb093367817dc0 +67 -0
  64. package/skills/project-update/history/blobs/917df3320d778ddbaa5c5c7742bc4046bf803c36ed2b050f30844ed206783469 +1 -0
  65. package/skills/project-update/history/blobs/935af1019931058ec3a0b834fe230e127ec914f75edcc4a6c589cfe733b0b979 +13 -0
  66. package/skills/project-update/history/blobs/ae5ce131d6bae401199ff32d8c3dc4d8aec188010a9111624ac64506e2116ff4 +84 -0
  67. package/skills/project-update/history/blobs/ca4499d829268c8766fb3b825a515e88dc55c237e9d202816b50959b4dac031f +13 -0
  68. package/skills/project-update/history/blobs/e9adb7a9905eb719ac78ab96671f055da53e5001810f5ad6d6c94306b0323fe7 +74 -0
  69. package/skills/project-update/history/blobs/fae3064e648cdc55be4aeb1177525b302a5e34ebd752e4a904f47dc0f4557b96 +71 -0
  70. package/skills/project-update/history/index.json +32 -13
  71. package/skills/project-update/migrations/0009_state_file.py +25 -0
  72. package/skills/project-update/migrations/0010_dialog_switch.py +30 -0
  73. package/skills/project-update/migrations/README.md +2 -1
  74. package/skills/project-update/update.py +12 -3
  75. package/skills/rr-build/SKILL.md +14 -5
  76. package/skills/rr-build-all/SKILL.md +190 -0
  77. package/skills/rr-plan/SKILL.md +13 -3
  78. package/skills/rr-review/SKILL.md +18 -8
  79. package/skills/rr-ship/SKILL.md +16 -8
  80. package/skills/rr-status/SKILL.md +2 -2
  81. package/skills/usage-report/usage-report.py +41 -8
package/README.md CHANGED
@@ -1,869 +1,303 @@
1
1
  # RiskRails
2
2
 
3
- [![Version](https://img.shields.io/badge/version-3.0.0-d2f878?labelColor=101210)](https://github.com/vanssata/claude-agentic-sdlc-module/releases/tag/v3.0.0)
4
- [![Runtimes](https://img.shields.io/badge/runtimes-Claude%20Code%20%7C%20Codex-d2f878?labelColor=101210)](#one-module-two-runtimes)
5
- [![Known risks](https://img.shields.io/badge/known%20risks-documented-d2f878?labelColor=101210)](#known-risks)
3
+ [![Version](https://img.shields.io/badge/version-3.0.1-d2f878?labelColor=101210)](https://github.com/vanssata/claude-agentic-sdlc-module/releases/tag/v3.0.1)
4
+ [![Runtimes](https://img.shields.io/badge/runtimes-Claude%20Code%20%7C%20Codex-d2f878?labelColor=101210)](docs/routing.md)
5
+ [![Known risks](https://img.shields.io/badge/known%20risks-documented-d2f878?labelColor=101210)](docs/known-risks.md)
6
6
  [![License: MIT](https://img.shields.io/badge/license-MIT-d2f878?labelColor=101210)](LICENSE)
7
7
 
8
8
  **Let an AI agent work on your legacy production code without the agent
9
9
  widening its own scope, reading a production secret, force-pushing or
10
10
  deploying.**
11
11
 
12
- **RiskRails** (`riskrails`) is a plugin for **Claude Code and Codex**, built for codebases
13
- with undocumented business rules, old workarounds and behaviour customers
14
- rely on today. Small, well-defined changes go through easily. Risky ones are
15
- stopped by a hook, not by a polite request in a prompt.
12
+ **RiskRails** (`riskrails`) is a plugin for **Claude Code and Codex**, built for
13
+ codebases with undocumented business rules, old workarounds and behaviour
14
+ customers rely on today. Each change is **weighed** on six axes and runs in one
15
+ of three modes — light, guarded, full — so a typo fix stays a one-line edit and
16
+ a payment change gets a written plan, an adversarial review and a human's
17
+ approval. Risky actions are stopped by a hook, not by a polite request in a
18
+ prompt.
16
19
 
17
20
  ```bash
18
- npx riskrails@3.0.0 # detects Claude Code, Codex or both (Node 18+, python3)
21
+ npx riskrails@3.0.1 # detects Claude Code, Codex or both (Node 18+, python3)
19
22
  ```
20
23
 
21
- Then, in any project: `/ai-init` once, and `/ai-task <what you want>` for every
22
- change.
23
-
24
- Upgrading from `claude-agentic`: `npx riskrails` moves `<home>/claude-agentic/` to
25
- `<home>/riskrails/` and leaves a symlink at the old path for one version, and
26
- `/rr-setup --update` renames each project's managed-block markers (migration
27
- 0008). The task state stays in `.ai/state/current.json`; a later release moves
28
- it to `state.json`, and this one already reads either.
24
+ Then, in any project: `/rr-setup` once, and `/rr-build <what you want>` for
25
+ every change.
29
26
 
30
27
  **What you get**
31
28
 
32
- - **Guards, not guidelines.** Hooks refuse edits outside the approved step,
33
- reads of production secrets, force-pushes and deploys. No agent commits,
34
- merges or deploys: every task ends at your approval.
35
- - **Risk-tiered pipeline.** Each change gets a tier from T0 to T6, by what it
36
- reaches, never by how many files it touches. A typo fix stays a one-line
37
- edit. A new resource with its screens gets the sensors. A payment change gets
38
- a plan, tests, an adversarial review and a security review.
39
- - **Cheap by default.** Routing puts readers on the cheapest model, keeps the
40
- session on the one that does the thinking, and uses the top tier only when a
41
- named trigger calls for it. `/usage-report` shows where the tokens went.
29
+ - **Guards, not guidelines.** One hook on every tool call refuses an edit
30
+ outside the task's files, a read of a production secret, a force push, a
31
+ push to a protected branch, a deploy command, `npm publish`. No agent merges
32
+ or deploys; a commit, a push or an approval runs only as the command you
33
+ picked in the runtime's own dialog, verbatim, once — or you run it yourself.
34
+ - **Weight, not ceremony.** Scope, reversibility and coverage are measured
35
+ from the tree; behaviour, domain and novelty are declared with evidence. The
36
+ sum picks the mode, the mode picks who plans, who reviews and whether you
37
+ sign off. The mode only rises during a task; lowering it is yours.
38
+ - **Deterministic gates.** `state.py` measures and refuses; no model decides
39
+ whether a step stayed in scope, whether the suite is green on this tree, or
40
+ whether a budget is spent. Every decision is in a journal a human can read.
41
+ - **Cheap by default.** Readers on the cheapest model, the session on the one
42
+ that thinks, the top tier only on a named trigger, one short always-loaded
43
+ file per runtime. `/rr-status --cost` shows where the tokens went, at zero
44
+ model cost.
42
45
  - **One `.ai/` tree for both runtimes.** Start a task in Claude Code and finish
43
- it in Codex; the knowledge base, policies and task state are shared.
44
-
45
- Tuned for **one developer who knows the codebase, on a Pro or Team Pro plan**,
46
- following the [AI-native SDLC playbook](https://claude.com/blog/the-ai-native-sdlc-playbook).
47
- A fully delegated `team` profile is one JSON key away.
48
-
49
- **Version 3.0.0.** The guards have limits: read [Known risks](#known-risks)
50
- before relying on them.
51
-
52
- > This plugin absorbs the former `claude-routing`. Installing it migrates that
53
- > plugin's managed `CLAUDE.md` block into its own, so the two never coexist; see
54
- > [Migrating from claude-routing](#migrating-from-claude-routing).
55
-
56
- ## One module, two runtimes
57
-
58
- `.ai/**`, the pipeline state machine, the risk tiers, the workflow contracts and
59
- the skill bodies are provider-neutral. The runtime-specific part is a thin
60
- adapter: where the files go, what an agent definition looks like, how hooks are
61
- registered, and which model each tier resolves to.
62
-
63
- | | Claude Code | Codex |
64
- |---|---|---|
65
- | install root | `~/.claude/` | `~/.codex/` |
66
- | instruction file | `CLAUDE.md` | `AGENTS.md` |
67
- | settings | `settings.json` (deep-merged) | `config.toml` (six managed keys) |
68
- | agents | `agents/*.md`, YAML frontmatter | `agents/*.toml`, rendered from the same prompts |
69
- | hooks | `settings.json` `hooks` block | `hooks.json` |
70
- | edits arrive as | `Edit` / `Write`, one file | `apply_patch`, possibly many files |
71
- | FAST | `haiku` | `gpt-5.6-terra` (Terra) |
72
- | BALANCED | `sonnet` | `gpt-5.6-terra` (Terra) |
73
- | STRONG | `opus` | `gpt-5.6-sol` (Sol) |
74
- | EXPERT | Fable 5.1, or Opus 5 with `--fable no` | `gpt-6-astra` (Astra) |
75
- | session | Opus 5.5 [1m] (`claude-opus-5-5[1m]`, 1M window) at `medium` by default on Max, `/model opus` for a 200k session (Sonnet on Pro) | Sol at `high` |
76
-
77
- A repository can carry both instruction files over one `.ai/` tree. The task
78
- state is `.ai/state/current.json` either way, so a task started in one runtime
79
- resumes in the other.
80
-
81
- Five files make that resume real, and every one of them is provider-neutral:
82
-
83
- | File | Written by | Holds |
84
- |---|---|---|
85
- | `.ai/state/current.json` | `state.py`, `update.py` | the task: stage, step, risk tier, approval |
86
- | `.ai/reports/<task-id>/events.jsonl` | `state.py` | the journal — one append-only line per event, with the runtime that emitted it |
87
- | `.ai/reports/<task-id>/task.md` §Questions | `state.py` | the open questions, each with an `[Answer]:` line a human fills in |
88
- | `.ai/state/handoff.md` | `state.py`, rendered | thirty lines a session reads first: where the task is, what it may touch, what is pending |
89
- | `.ai/reports/<task-id>/sensors.json` | `state.py review-gate` | what each deterministic sensor measured, and on which tree |
90
- | `.ai/reports/<task-id>/tests-<scope>-<n>.log` | `state.py test-run` | the full test output, so it never reaches a context window |
91
- | `.ai/state/session.json` | `context-guard.py` | which runtime is driving and when the human last took a turn |
92
-
93
- A task also moves on purpose: `state.py handoff --to codex` (or `--to claude`)
94
- hands it over, prints the command to resume it there and runs nothing; until the
95
- other runtime resumes it, a state change (and `init --force`) from anywhere else
96
- exits 7. `--for review` asks the other vendor for a T5+ review; its verdict never
97
- clears the owner's own `blockers_open`, and moving the task again cancels an
98
- open request. `init`, `quick` and `risk` print a
99
- `preferred runtime:` line when the plan's table (refactoring → Codex on Claude
100
- plans) or the quota rule points at the other runtime — advice only.
101
-
102
- A subagent that needs a decision **returns** `QUESTIONS_NEEDED` instead of
103
- asking; the manager writes the question into `task.md` §Questions, a human answers it
104
- there or in the session, and `state.py questions --sync` puts the answer back
105
- into the state. The journal is what `/ai-status` and `/usage-report` read, at
106
- zero model cost — no model is involved in writing or reading any of these files.
46
+ it in Codex; the knowledge base, the policies, the task state and the compact
47
+ record of every closed task are shared.
48
+
49
+ Tuned for **one developer who knows the codebase**, on a Claude Pro, Max or
50
+ Team plan, or a ChatGPT Plus or Pro plan, following the
51
+ [AI-native SDLC playbook](https://claude.com/blog/the-ai-native-sdlc-playbook).
52
+
53
+ **Version 3.0.1.** The guards have limits: read [Known risks](docs/known-risks.md)
54
+ before relying on them. What is decided but not built is in [ROADMAP.md](ROADMAP.md).
55
+
56
+ ## Upgrade to 3.0
57
+
58
+ 3.0 renames the plugin from `claude-agentic` to `riskrails`, replaces the risk
59
+ tiers with the weight and the three modes, replaces the `/ai-*` and `/sdlc-*`
60
+ commands with `/rr-*`, ports the guards to one Python process, and moves the
61
+ task state to `.ai/state/state.json`. The old names stay as aliases for this
62
+ version. Step by step, in this order:
63
+
64
+ 1. **Commit every project's `.ai/`** before anything else; the update tells you
65
+ when the tree is dirty, and `git restore` is the rollback.
66
+ 2. **Close or abandon tasks in flight.** A task started by 2.x is refused by
67
+ the new commands as `old surface`; finish it with the version that started
68
+ it, or drop it with `python3 ~/.claude/skills/ai-task/state.py close
69
+ --abandon`. Check with `/rr-status`.
70
+ 3. **Update the home** on every machine, for every runtime:
71
+
72
+ ```bash
73
+ npx riskrails@3.0.1 # or: npx -y riskrails@latest --yes
74
+ ```
75
+
76
+ It moves `<home>/claude-agentic/` to `<home>/riskrails/` and leaves a
77
+ symlink at the old path for one version, replaces the old hook entries with
78
+ `ai-guard.py` and `runtime-gate.py`, retires the old agents and shim files
79
+ to `backups/<date>/`, and rewrites the managed block. Restart the runtime.
80
+ In Codex, open `/hooks` and **trust the changed entries** — until you do,
81
+ nothing is enforced. `--dry-run` shows all of it first.
82
+ 4. **Update each project:**
83
+
84
+ ```
85
+ /rr-setup --update # dry run first; read it; then --apply
86
+ ```
87
+
88
+ Four migrations run inside the dry run, in order, before the merge:
89
+ `0007` creates `.ai/policies/weight.json`, carrying over the project's
90
+ edits to its risk-tier table; `0008` renames the managed-block markers and
91
+ the router's routes; `0009` moves `current.json` to `state.json`; `0010`
92
+ creates `.ai/policies/dialog.json` with the dialog on. Edited markdown is
93
+ three-way merged, JSON policies key by key with a confirmation per key;
94
+ `delete?` proposals are left for you to confirm. The schema becomes 10.
95
+ 5. **Decide two things the migrations cannot.** Keep the dialog (then amend
96
+ constitution C5 as `0010` hints) or set `"enabled": false`; and tune the
97
+ weight table — `domain.modules`, `customer_visible`, `hot_paths`,
98
+ `needs_step`, `irreversible` — then `state.py weight --mirror`.
99
+ 6. **Verify**: `/rr-status` shows schema 10 and no task in flight; a
100
+ `/rr-build` on a small change runs light and closes on the two facts.
101
+ Commit `.ai/`.
102
+
103
+ Every machine of a team upgrades the plugin before a project at schema 10 is
104
+ committed; an older plugin refuses such a project. Rollback and the full
105
+ migration table are in [docs/project-update.md](docs/project-update.md); the
106
+ home-side details in [docs/installation.md](docs/installation.md).
107
107
 
108
108
  ## Install
109
109
 
110
- Pin the version: `npx riskrails` alone runs whatever was published last, and the
111
- hooks it installs see every command an agent runs.
112
-
113
110
  ```bash
114
- npx riskrails@3.0.0 # detects Claude Code, Codex, or both, and updates each
115
- npx riskrails@3.0.0 --target codex # auto | claude | codex | both
116
- npx riskrails@3.0.0 --dry-run # print what would be written, write nothing
117
- npx riskrails@3.0.0 --home ~/alt-claude # another Claude home (--codex-home for Codex)
118
- # The installer does not read CLAUDE_CONFIG_DIR or CODEX_HOME: with a home other than
119
- # ~/.claude or ~/.codex, pass it with --home / --codex-home (or CLAUDE_DIR / CODEX_DIR).
120
-
121
- # Claude-side options. The plan is detected from ~/.claude.json (organizationRateLimitTier
122
- # tells Max 5x from 20x) and, on a terminal, proposed for you to confirm with Enter (--yes
123
- # takes the detection without asking):
124
- npx riskrails@3.0.0 --plan pro # Pro: opusplan session, opus pinned for EXPERT
125
- npx riskrails@3.0.0 --plan team-pro # Team, Standard seat: the pro profile with the Team label
126
- npx riskrails@3.0.0 --plan team-max # Team, Premium seat: the max profile, Fable on architect
127
- npx riskrails@3.0.0 --plan max # Max 5x: Opus 5.5 [1m] session (1M window by default), Fable only on architect
128
- npx riskrails@3.0.0 --plan max20 # Max 20x: max's settings, larger budgets (6 agents, EXPERT without asking)
129
- npx riskrails@3.0.0 --plan max --fable no # no Fable anywhere; architect inherits the Opus session
130
- npx riskrails@3.0.0 --plan balanced-max # BalancedMax: only the session is strong (Opus 5.5, 200k); readers on Haiku,
131
- # judgement on Sonnet, reviews on Opus, EXPERT at high, no Fable, no xhigh;
132
- # also picks the Codex balanced-max profile (Sol session, readers on Luna)
133
-
134
- # Codex-side options (the ChatGPT plan is auto-detected from ~/.codex/auth.json):
135
- npx riskrails@3.0.0 --codex-plan plus # Plus: Sol at medium, three agent threads, Astra at high, no xhigh
136
- npx riskrails@3.0.0 --codex-plan pro # Pro: Sol at high, six agent threads, Astra at xhigh for ai-expert
137
- npx riskrails@3.0.0 --codex-plan balanced-max # Pro's budgets, Sol session, FAST readers on Luna, Astra at high
111
+ npx riskrails@3.0.1 # detects Claude Code, Codex, or both; proposes the detected plan
112
+ npx riskrails@3.0.1 --target codex # auto | claude | codex | both
113
+ npx riskrails@3.0.1 --plan max --fable no # pro | team-pro | team-max | max | max20 | balanced-max
114
+ npx riskrails@3.0.1 --codex-plan plus # plus | pro | balanced-max
115
+ npx riskrails@3.0.1 --context economy # the plan's compaction window; the context guard also blocks
116
+ npx riskrails@3.0.1 --dry-run # print what would be written, write nothing
117
+ npx -y riskrails@latest --yes # update in place, keeping the recorded plan
138
118
  ```
139
119
 
140
- From a checkout, `node bin/riskrails.js` takes the same flags. `./install.sh` only
141
- prints the `npx` line now and exits 2; the 2.0.x installer is at the `v2.0.2` tag.
142
-
143
- **The plugin route.** `/plugin install riskrails` (Claude Code) or the Codex
144
- plugin directory installs the skills and the hooks. Run `npx riskrails@3.0.0`
145
- as well: the plugin format cannot carry the agents, the plan profile, the
146
- managed block or the settings. When the runtime records the plugin as
147
- installed and enabled (Claude Code: `installed_plugins.json` at user scope and
148
- `enabledPlugins`; Codex: `[plugins."riskrails@<marketplace>"] enabled = true`
149
- in `config.toml`), `npx` registers none of the plugin's hooks and installs the
150
- skills without their `SKILL.md`, so nothing loads or runs twice and the skills
151
- still find their scripts under `~/.claude/skills/`. A plugin cache folder by
152
- itself changes nothing: `npx` installs everything. With
153
- the plugin alone there is no `ai-reviewer` agent, so a task whose mode needs a
154
- review has nothing to run it with.
155
-
156
- `--target auto` installs for each runtime whose CLI is on `PATH`, and asks for
157
- that runtime's plan only. A `~/.claude` or `~/.codex` directory without its CLI
158
- is skipped with a one-line note — `--target both` includes it anyway, which is
159
- also the way in when the CLI exists but is not on this shell's `PATH` (an IDE
160
- extension's bundled binary, `sudo` without `~/.local/bin`). If it
161
- finds neither CLI it stops and tells you which `--target` to name; it never
162
- guesses one.
163
-
164
- Restart the runtime afterwards.
165
-
166
- - **Claude Code**: `/skills` should list `ai-init`, `ai-audit`, `ai-task` and
167
- `ai-status`; `/hooks` should list `ai-guard.py`, the one guard command.
168
- - **Codex**: `/hooks` shows the hooks — **and you must review and trust them
169
- there before they run.** Codex does not execute a non-managed hook until it has
170
- been trusted, so until you do, nothing is being enforced.
171
-
172
- Re-running the installer updates in place: it backs up what it replaces, never
173
- duplicates a hook entry, and never overwrites your edits to `ai-git-guard.json`.
174
-
175
- ### What it installs
176
-
177
- | Source | Claude target | Codex target |
178
- |---|---|---|
179
- | `profiles/{pro,max}.json` + `settings.common.json` | deep-merged into `settings.json` | — |
180
- | `profiles/codex-{plus,pro}.json` | — | six managed keys in `config.toml` |
181
- | `instructions/stub.md` | a managed block in `~/.claude/CLAUDE.md` | a managed block in `~/.codex/AGENTS.md` |
182
- | `instructions/routing.md` | `~/.claude/riskrails/routing.md`, read on demand | `~/.codex/riskrails/routing.md`, read on demand |
183
- | `agents/*.md.tmpl` | rendered to `~/.claude/agents/*.md`, each tier's model and effort from the plan (`ai-expert` and `architect` have their own model line) | rendered to `~/.codex/agents/*.toml`, the model pinned per role |
184
- | `profiles/*.json` | the plan's settings, and its tier and budget tables (`riskrails`), resolved by `scripts/resolve-profile.py` into `~/.claude/riskrails/profile.json` | the same, into `~/.codex/riskrails/profile.json` |
185
- | `hooks/*` | `~/.claude/hooks/` — six hooks, `runtime-gate` (the old `fable-gate`/`codex-model-gate` names are shims), the shared library and the guards' default config | `~/.codex/hooks/` + `codex/hooks.json` |
186
- | `skills/*/` | `~/.claude/skills/` | `~/.codex/skills/` |
187
-
188
- The skills are `/ai-init`, `/ai-audit`, `/ai-task`, `/ai-status`, `/project-init`,
189
- `/project-update`, `/sdlc-intent`, `/sdlc-spec`, `/sdlc-plan` and
190
- `/usage-report`, identical in both runtimes.
191
-
192
- The Codex install is a strict subset in one place: `cap-large-read.py` is not
193
- installed there, because Codex's read tool is not on the hook path. The read
194
- rule is still written into `AGENTS.md`; it is just not mechanical there.
195
- `context-guard.py` **is** installed on both — Codex has `UserPromptSubmit`,
196
- `PreCompact` and `SessionStart` too, so the handoff, the pending questions and
197
- `session.json` cross unchanged. What does not cross is the transcript-derived
198
- snapshot: it is built from a Claude transcript and stays Claude-only. One file
199
- serves both; it reads its own location to know which runtime it is in.
200
-
201
- `profiles/codex-{plus,pro}.json` is the machine-readable routing contract, one
202
- file per ChatGPT plan — session model and effort, thread cap,
203
- `[agents]` defaults, the four tiers and every role's tier and sandbox mode. Both
204
- the agent renderer and the config merge read it, so there is one place to change
205
- a Codex routing decision.
206
-
207
- ### What loads on every turn
208
-
209
- Context length is the running cost: an always-loaded instruction file is re-read
210
- on every turn of every task, whether it is relevant or not. So the plugin keeps
211
- exactly one always-loaded file per runtime, and it is short.
212
-
213
- | Where | What it is | Budget |
214
- |---|---|---|
215
- | `~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md` | the managed block: the rules an agent is unsafe without | 2 560 B |
216
- | a project's `CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, `.junie/guidelines.md` | the same block, project scope | 2 048 B |
217
- | `~/.claude/riskrails/routing.md` | the model ladder, the context-guard thresholds, the guard list — read when a routing question comes up | — |
218
- | a project's `.ai/AGENTS.md` | a router: one row per job, pointing at the policy that answers it | advisory 4 096 B |
219
- | `docs/sdlc/constitution.md` | the 10–15 principles the project does not negotiate, cited as `C<n>` by `/sdlc-spec` and the planner | 15 lines, 4 096 B |
220
- | `.ai/rules/<slug>.md` | a rule scoped to the directories it names, rendered into their instruction files by `/project-update` | — |
221
-
222
- All of the block text comes from one source, `instructions/stub.md`, rendered per
223
- scope and runtime by `skills/project-update/render_instructions.py`; the committed
224
- templates are build artefacts and `render_instructions.py build --check` fails the
225
- suite on drift. `npx riskrails` prints what the block costs after every install
226
- (`managed block 1 903 B (budget 2 560), file 8 457 B — the rest is yours`) and
227
- never touches the rest of the file; `/ai-status` prints the same line for a
228
- project. Downstream the budget is advisory — the plugin measures, it does not
229
- refuse a file it does not own.
230
-
231
- ### Editing `config.toml` safely
232
-
233
- There is no comment-preserving TOML writer in the standard library, so
234
- `scripts/merge-codex-config.py` edits the six managed keys line by line, then
235
- parses the file before and after and refuses to write unless the *only* keys that
236
- differ are the six it manages. Your comments, `[projects.*]`, `[mcp_servers.*]`,
237
- `[marketplaces.*]` and every other setting survive; a malformed file aborts the
238
- merge and is left untouched. The previous version is kept as `config.toml.bak`.
239
-
240
- **One accepted change to your defaults:** the Codex session is set to Sol at
241
- `high`, not Astra. Astra stays available as the EXPERT escalation. This is the
242
- whole point of the routing — the session model is the largest single cost, and
243
- it should not be the most expensive model by default.
244
-
245
- ### Limits it sets
246
-
247
- | Limit | Value | Default |
248
- |---|---|---|
249
- | `bashOutputMaxChars` (Claude) | 75 000 | 30 000 |
250
- | `taskOutputMaxChars` (Claude) | 80 000 | — |
251
- | `MAX_MCP_OUTPUT_TOKENS` (Claude) | 40 000 | 25 000 |
252
- | `cap-large-read.py` (Claude) | refuses an unbounded `Read` over 4 000 lines or 250 KB | no limit |
253
- | `autoCompactWindow` (Claude) | 800 000 on Max and Team Max, 300 000 on Pro and Team Pro. Claude Code caps it at the model's own window, so one setting means compaction near 167k on a 200k model and near 767k on a `[1m]` one; on Pro the cap decides, at 167k | the model window |
254
- | `claude-1m [opus\|fable]` (Claude, Max and Team Max) | pins one session to `opus[1m]`, or to `fable[1m]` (Fable 5.1), at launch. The large window comes from the per-model cap, not from the launcher, so `/model opus[1m]` reaches it too; `CLAUDE_1M_COMPACT_WINDOW` exports `CLAUDE_CODE_AUTO_COMPACT_WINDOW` for that process alone to compact *earlier* than 767k | `/model` only, and the same cap applies |
255
- | `context-guard.py` (both runtimes) | warns from 80% of the point where compaction fires and holds a prompt back once from 120% — 133k and 200k on a 200k Max session, 613k and 920k on a `[1m]` one; `AI_CONTEXT_WARN_TOKENS` / `AI_CONTEXT_BLOCK_TOKENS` set them in tokens, `0` turns one off | no guard |
256
- | `model` (Claude) | `opusplan` on Pro and Team Pro: Opus in plan mode, Sonnet when executing; `claude-opus-5-5[1m]` (Opus 5.5, 1M window) by default on Max, Max 20x and Team Max, `opus` (200k) per task through `/model`; `claude-opus-5-5` (200k) on BalancedMax | Sonnet 5 on Pro |
257
- | `effortLevel` (Claude) | `medium` on both plans; agents raise it per task | — |
258
- | `model` (Codex) | `gpt-5.6-sol` at `high` on Pro, `medium` on Plus; subagents `gpt-5.6-terra` at `medium` | — |
259
- | `max_concurrent_threads_per_session` (Codex) | 6 on Pro, 3 on Plus | runtime default |
260
- | `ai-expert` effort (Codex) | `xhigh` on Pro, `high` on Plus; `xhigh` is never used on Plus | — |
261
-
262
- The Read guard is a guardrail, not a cage: an explicit `limit` always goes
263
- through, so reading something large stays possible but has to be deliberate.
120
+ Pin the version: the hooks it installs see every command an agent runs. It
121
+ installs the managed block, the plan's settings and limits, the five agents
122
+ with their tiers baked in, the hooks, the skills, the shared tree at
123
+ `<home>/riskrails/ai/` and the resolved profile; re-running it updates in
124
+ place with dated backups. The plugin route (`/plugin install riskrails`)
125
+ carries the skills and hooks only — run `npx` as well. Every option, every
126
+ limit it sets and what loads on every turn: [docs/installation.md](docs/installation.md).
264
127
 
265
128
  ## Use
266
129
 
267
130
  The same commands in both runtimes:
268
131
 
269
- ```bash
270
- /ai-init # in a project: survey it, build .ai/ and docs/sdlc/
271
- /ai-task <what you want> # run one change through the pipeline
272
- /ai-status # where does the current task stand, on which models
273
- /ai-audit # score the repo against the twelve AI-SDLC plays
274
- /usage-report # what it cost, from local transcripts, at zero model cost
275
- ```
276
-
277
- ### Updating a project after the plugin changes
278
-
279
- `npx riskrails` updates `~/.claude/` (or `~/.codex/`), and with it the shared
280
- tree at `<home>/riskrails/ai/`: the 29 policies, agent contracts,
281
- workflows and templates that are the same in every project. A project does not
282
- copy them; it reads them from there, and a file at the same path under `.ai/`
283
- wins as an **override**. `<home>/riskrails/ai-path <path>` prints which one
284
- applies. What a project owns stays in the project — `.ai/project/`, `rules/`,
285
- `state/`, `reports/`, `VERSION`, `AGENTS.md`, and the policies `risk-tiers.json`,
286
- `risk-tiers.md`, `path-guard.json`, `testing.md`, `coding.md`, `safety.md` and
287
- `security.md` — together with `docs/sdlc/` and the `CLAUDE.md` block. Those are
288
- copies, and stay as they were. Pull the new ones in with:
289
-
290
- ```bash
291
- /project-update # dry run, confirm the policy changes, apply, merge conflicts
292
- ```
293
-
294
- Running `/ai-init` or `/project-init` again in an initialised project does the
295
- same. `/ai-status` says when a project is behind.
296
-
297
- The plugin ships every version of every project-owned template it has ever
298
- installed (`skills/project-update/history/`, built from git by
299
- `tools/build-template-history.py`; the shared files keep only their hashes,
300
- since nothing is merged into them any more). That is how the update tells files
301
- apart:
302
-
303
- | The project file is | What happens |
132
+ | You want to | Run |
304
133
  |---|---|
305
- | missing | created |
306
- | a version the plugin once shipped | replaced — nobody edited it |
307
- | edited, markdown | three-way merged against the closest shipped version; every edit kept |
308
- | edited, JSON policy | merged key by key; the project's value wins wherever both changed |
309
- | edited where the plugin changed the same lines | left alone; the plugin's version goes to `.ai/local/plugin-update/` for a merge |
310
- | `.ai/project/**`, `CLAUDE.md` outside the block, `.claude/settings.json` | never touched |
311
- | moved by a migration | moved, with your edits merged at the new path; the original kept under `.ai/reports/project-update-<date>/` |
312
- | proposed for deletion by a migration | listed as `delete?` and left alone until a human passes `--apply --confirm-delete NAME` |
313
- | a shared file the plugin once shipped, unedited | `delete?` (`[0006]` during the migration, `[shared]` afterwards) — the project reads the shared tree instead, once a human confirms |
314
- | a shared file the project edited | kept as an override, with a hint: it no longer receives the plugin's changes, and nothing tells you when it falls behind |
315
-
316
- The tree also carries a schema version in `.ai/VERSION`; the current one is
317
- **6** (the shared files: `.ai/AGENTS.md` gains a `## Shared files` section
318
- first, then every unedited copy of a shared file is proposed for deletion,
319
- edited ones are reported as overrides), and a project initialised before the
320
- version existed is schema 0. Migration 0006 deletes nothing while any installed
321
- home (`~/.claude`, `~/.codex`) lacks the shared file it would fall back to — it
322
- exits 2 and asks for `npx riskrails` — and holds `.ai/VERSION` at 5 until the
323
- deletion is confirmed. Every machine of a team upgrades the plugin before a
324
- project at schema 6 is committed; an older plugin refuses it. Migrations under
325
- `skills/project-update/migrations/` run in order before anything is merged —
326
- they move, add, edit and propose deletions, all inside the same dry run — and
327
- the version is written last, only once they are finished.
328
-
329
- Changes to `.ai/policies/*.json` are listed per key and need a confirmation
330
- before they are applied. A risk-tier mirror that was in sync stays in sync; one
331
- that was already stale stays flagged. Nothing is deleted, and no version is
332
- advanced, without the migration that asked for it finishing: a conflict *from a
333
- migration*, or a deletion it proposed and nobody confirmed, holds `.ai/VERSION`
334
- where it is, and `/ai-status` keeps reporting the project as behind. An ordinary
335
- merge conflict does not.
336
-
337
- Changed a template? Run `tools/build-template-history.py` and commit the result;
338
- the test suite fails until you do.
339
-
340
- **Rolling back 0006 is git first.** Commit `.ai/` before the run (the update
341
- says so when it is dirty), then:
342
-
343
- ```bash
344
- git restore --source=<commit before the migration> --staged --worktree -- .ai
345
- ```
346
-
347
- That also reverts everything else under `.ai/` committed since — reports,
348
- project notes. To keep them, restore only `.ai/VERSION`, `.ai/AGENTS.md` and the
349
- paths `migration.json` lists.
350
-
351
- and keep the project off `/project-update --apply` until the plugin is
352
- downgraded, because a new plugin plans 0006 again. Without a commit, the
353
- originals under `.ai/reports/project-update-<date>/original/` bring the deleted
354
- files back (`migration.json` lists them), and nothing else: write the schema
355
- back by hand with `printf '5\n' > .ai/VERSION`, or an older plugin refuses the
356
- project (exit 2). The router keeps its new section, which stays true while the
357
- copies exist. Roll projects back before installing a plugin older than the
358
- shared tree.
359
-
360
- A project that already carries another AI tool's structure — Spec Kit
361
- (`.specify/`, `specs/`), Kiro (`.kiro/`), Cursor (`.cursorrules`,
362
- `.cursor/rules/`), Copilot (`.github/copilot-instructions.md`), AI-DLC, or a
363
- `CLAUDE.md`/`AGENTS.md` too large to load on every turn — is adopted with
364
- `/project-update --adopt`. The dry run maps every file through
365
- `skills/project-update/adopt-map.json` and writes nothing; `--apply` moves the
366
- content into `docs/sdlc/` and `.ai/`, keeps each original under
367
- `.ai/reports/adopt-<date>/original/`, and passes two checks on disk: no line
368
- lost, no reference left pointing at an old path. A file no row maps stops the
369
- run until a human decides. `--mode coexist` leaves the files where they are and
370
- only routes to them. The foreign files are deleted by a separate
371
- `--adopt --cleanup`, on a committed tree, and only with `--apply
372
- --confirm-delete NAME` typed by a human in a terminal outside Claude Code or
373
- Codex. `/ai-status` says when a structure is
374
- waiting, or came back after an adopt.
375
-
376
- For work that needs a written intent and specification before any code:
134
+ | set up `.ai/` in a project, or bring it up to the installed plugin | `/rr-setup`, `/rr-setup --update` |
135
+ | move another AI tool's files, or the project's own toolbox, into this layout | `/rr-setup --adopt` |
136
+ | score the repository against the twelve plays | `/rr-setup --audit` |
137
+ | turn an idea into intent, requirements and design | `/rr-spec <idea>` |
138
+ | plan a spec into tasks, or one task into a mode and a start | `/rr-plan <spec or task>` |
139
+ | do the work, or resume it | `/rr-build <request>` (no task in flight: it runs `/rr-plan` first) |
140
+ | run a whole plan as one task | `/rr-build-all docs/sdlc/plans/<slug>.md` |
141
+ | review the change | `/rr-review` — guarded and full, or when `close` asks |
142
+ | close the task, get the human's approval | `/rr-ship` |
143
+ | see where the task stands, or what it cost | `/rr-status`, `/rr-status --cost` |
144
+
145
+ The old names — `/ai-init`, `/ai-task`, `/ai-status`, `/ai-audit`,
146
+ `/project-init`, `/project-update`, `/sdlc-intent`, `/sdlc-spec`,
147
+ `/sdlc-plan`, `/usage-report` — are aliases for one version.
148
+
149
+ ### Three modes
150
+
151
+ `state.py weight` scores six axes 0–2 — scope, behaviour, reversibility,
152
+ domain, novelty, coverage — and the sum W picks the mode. The table is the
153
+ project's `.ai/policies/weight.json`; reversibility or domain at 2 is full
154
+ whatever W is ([docs/weight.md](docs/weight.md)).
155
+
156
+ | Mode | When | Plan | Review | Human stops |
157
+ |---|---|---|---|---|
158
+ | **light** | W ≤ 2 | inline: files, test, rollback | only when a fact at close is red | none |
159
+ | **guarded** | W 3–7 | plan mode, which you accept | one pass on BALANCED | the plan |
160
+ | **full** | W ≥ 8, or reversibility or domain = 2 | written; `architect` on STRONG when the design is open | one pass on STRONG, security focus when domain = 2 | the plan, and the tree at close |
161
+
162
+ A `full_irreversible` task (both hard axes at 2, or `DROP`, `TRUNCATE`,
163
+ `DELETE FROM` in the diff) is approved with `approve --confirm <operation>` in
164
+ your own terminal. `verify` and `close` re-score the two hard axes from the
165
+ real diff; a mode never drops during a task.
166
+
167
+ ### The two facts
168
+
169
+ `close` measures, never asks: (a) a green whole-suite `verify` on the tree as
170
+ it is now; (b) every changed file inside the task's files. Green and green in
171
+ light mode closes the task. Tests run once, to the end, with the log under
172
+ `.ai/state/logs/`, and every failure is fixed as one batch; two whole-suite
173
+ runs are the budget, and the third is your decision.
174
+
175
+ ### The dialog
176
+
177
+ With `.ai/policies/dialog.json` on (the default), the approval at close, a
178
+ budget refusal and a guarded `git commit` or `git push` come to you as a
179
+ question in the runtime's own dialog. The pick is recorded by a hook and the
180
+ command runs once, verbatim; a command that is not the pick is refused as
181
+ `CONSENT_REQUIRED`. Off, the same decisions are commands you run in your own
182
+ terminal.
183
+
184
+ ### Batch — one task for a whole plan
185
+
186
+ `/rr-build-all docs/sdlc/plans/<slug>.md` runs every row of a programme plan as
187
+ the steps of one task: the ready rows go to parallel implementers on
188
+ BALANCED (a step's `after` serializes the dependent ones), the suite runs once,
189
+ every failure is fixed by parallel fixers as one batch, one review at the
190
+ mode's tier, its findings fixed as one batch, then tests and the review again
191
+ until it passes. After three failed review passes the next verdict is yours.
192
+
193
+ ### The record
194
+
195
+ A closed task's who, what, how and why travel as a compact record: `state.py
196
+ memory enable` (yours, in a terminal) switches a project to packages mode, in
197
+ which `close` appends the task's entries to a per-developer batch under
198
+ `.ai/memory/` and a pre-push hook validates them; `state.py recall --files …`
199
+ gives an agent what the record holds about the files it is about to touch,
200
+ within a token budget. Task ids are `T-YYYY-MM-DD-HHMM-<slug>`
201
+ ([docs/local-memory.md](docs/local-memory.md)).
377
202
 
378
- ```bash
379
- /sdlc-intent <topic> # docs/sdlc/intent/<slug>.md
380
- /sdlc-spec docs/sdlc/intent/<slug>.md # docs/sdlc/specs/<slug>.md
381
- /sdlc-plan docs/sdlc/specs/<slug>.md # docs/sdlc/plans/<slug>.md
382
- /ai-task build the plan in docs/sdlc/plans/<slug>.md
383
- ```
203
+ ## The guards
384
204
 
385
- `/project-init` scaffolds only the SDLC layout, for a repository that does not
386
- want the agentic pipeline. Both scaffolds take
387
- `--runtime auto|claude|codex|gemini|junie|both` — or a comma list — and default
388
- to what the project already declares: `CLAUDE.md`/`.claude/`, `AGENTS.md`/`.codex/`,
389
- `GEMINI.md`/`.gemini/`, `.junie/`. Gemini and Junie get the rendered project stub
390
- only; they are not full runtimes here.
391
-
392
- `/ai-init` reads the codebase and writes `.ai/`. It does not touch application
393
- code — not a rename, not a formatting fix. Problems it finds are documented in
394
- `.ai/project/known-risks.md`, not fixed. By default it runs a **light** survey:
395
- it asks you for what you know, confirms it with `grep`, and sends only two
396
- discovery agents to the places memory is least reliable — legacy and risks,
397
- tests and data. `/ai-init --survey full` fans out six agents for a codebase new
398
- to you. Either way it ends with the test commands written into
399
- `.ai/policies/testing.md` — the scoped one a step runs, the full verification
400
- command, and the e2e suite — which every later task runs before reporting
401
- done.
402
-
403
- The tooling policy, `policies/tooling.md` (shared; a project overrides it in
404
- `.ai/`), says which MCP servers this repository enables for every task and why, which stay off, and how a single task asks for
405
- one. The default is none. A connected server is in the system prompt of every
406
- turn whether a task calls it or not, so the scaffolded `.claude/settings.json`
407
- ships with `enableAllProjectMcpServers: false` and an empty
408
- `enabledMcpjsonServers`. The same file holds the reading rule: a file too large
409
- to open is read by the cheapest model — `Explore` for code, `log-reader` for
410
- logs — which hands back the matching ranges with `file:line`, never the file.
411
-
412
- ## Risk tiers
413
-
414
- Every task is classified before it is planned. The tier decides who plans it, who
415
- reviews it, and whether a human signs it off. It does not depend on the runtime,
416
- and it comes from the **scope** of the change — a process, a migration, money,
417
- identity, infrastructure — never from how many files or lines it touches.
418
-
419
- | Tier | Covers | Plan review | Adversarial review | Security review | Human approval |
420
- |---|---|---|---|---|---|
421
- | T0 | documentation, comments | no | no | no | no |
422
- | T1 | formatting, a label, a template tweak | no | no | no | no |
423
- | T2 | an isolated screen or form, a new field, a small feature that changes no process | no | the sensors; a model only when one is red | no | no |
424
- | T3 | CRUD and presentation: a new resource with its screens, a non-critical API extension | no | the sensors; a model only when one is red | no | no |
425
- | T4 | shared domain behaviour and processes: orders, workflows, async, integrations, an additive migration, config | the human, in plan mode | yes, STRONG | when auth or personal data | yes |
426
- | T5 | payments, accounting, tax, fiscal, auth, order state, customer data | yes | yes | yes | yes |
427
- | T6 | migration strategy, infrastructure, production architecture, destructive or data migrations | yes | yes | yes | yes |
428
-
429
- The machine-readable source of truth is `.ai/policies/risk-tiers.json`, which
430
- `/ai-init` copies into each project so the tiers can be tuned to that codebase.
431
-
432
- ### Who does each stage: the pipeline profile
433
-
434
- The tier decides *whether* a stage runs; `pipeline_profile` in the same file
435
- decides *who* runs it. The default is `solo`, which has two modes: **direct**
436
- for T0–T4 — no pipeline ceremony, cheap readers only, a `sonnet` review at T2
437
- or T3 only when a sensor is red, one `opus` review at T4 whose plan the human
438
- approves in plan mode — and **sdlc** for T5–T6, the full pipeline:
439
-
440
- | Tier | `solo` delegates to a subagent | `team` delegates |
205
+ | Hook | Where | Does |
441
206
  |---|---|---|
442
- | T0, T1 | nothing — direct mode, no state file: say which files, edit, verify (T1) | discovery, test |
443
- | T2, T3 | the adversarial review, on `sonnet`, only when `review-gate` finds a red sensor; the plan is a few lines in the conversation, recorded by one `state.py quick` call | everything except implementation |
444
- | T4 | the adversarial review, on `opus`; the plan is written in plan mode and approved by the human, recorded by one `state.py quick` call | everything except implementation |
445
- | T5 | plus the plan, its review, the security review and the release report | everything except implementation |
446
- | T6 | plus discovery and impact; the plan goes to `ai-expert` | everything except implementation |
447
-
448
- Before any of those reviews, the deterministic gates measure the change itself.
449
- `state.py step-done` counts what a step actually changed, between the tree the
450
- step began with and the tree now: outside the step's own files it exits 6 and
451
- asks for `state.py step-split`, never for the work to be abandoned; over the
452
- tier's diff budget it finishes the step and warns, naming `step-split` — the
453
- budget is advisory, and only a project that writes `diff_budget.enforce: true`
454
- makes it a gate again. The tier is then re-scored from the real diff by
455
- **scope**, never by size — a change that reached `**/Payment/**` is T5 from
456
- then on, and only a human, in their own terminal, ever lowers a tier.
457
- `state.py test-run` owns the test runs, keeps their output in a file and caps
458
- how many there may be. `state.py review-gate` runs the sensor set — the
459
- recorded suite result, the project's own linter and type checker, the diff, the
460
- re-score, step-to-test traceability, repeated blocks, and whether the named
461
- tests **fail without the change** — and up to T3, with every one of them
462
- green, records `review_status: skipped_green`: the sensors are the review. A
463
- tool nobody wrote down is `unavailable`, not green, and keeps the review; so
464
- does a task over its diff budget, and so does a change into `config/`, a
465
- dependency manifest or a migration, whose floor is T4. From T4 the review
466
- always runs, with the sensor rows already in the ledger so the reviewer does
467
- not re-derive them. A project still on the six-tier table is read on its own
468
- scale until `/project-update` renumbers it.
469
-
470
- In `solo`, discovery, context, impact and risk come from the request plus
471
- `grep -n`; up to T4 they are a few lines in the conversation, from T5 they are
472
- recorded stage by stage. Tests run in three scopes: a step runs **only the
473
- tests it names**, the verification command runs **once, after the last step, to
474
- the end** (no fail-fast flag; the failing test first for a bugfix), and the
475
- **e2e suite runs once after that**, at the end of the task — never per step.
476
- Every failure of the two end-of-task runs is then fixed as **one batch** in a
477
- `state.py remediate` step before one more run — at most two rounds, then the
478
- human decides. Review
479
- findings are handled the same way. Up to T4 the release report is the commit
480
- message. A subagent is still sent on a stated trigger: an unfamiliar
481
- area, an `UNKNOWN` the plan depends on, a non-obvious T4+ classification, or
482
- verification output too long to read inline. Switch to `team` by editing the
483
- key. No stage is ever skipped; the profile only changes who does it.
207
+ | `ai-guard.py` — **git** | every repository, both runtimes | refuses force push, remote branch delete, history rewrite, push or merge to a protected branch, `gh pr merge`, `--no-verify`, staging a secret, production deploy commands, `npm publish`; a `git commit` or `push` that is not the dialog's pick |
208
+ | `ai-guard.py` — **paths** | where `.ai/` exists | refuses reading or writing `.env`, `secrets/`, keys, dumps, production logs; writing the task state, the journal, `task.md`, the policies' JSON, the compact record, the installed guards; the human-only `state.py` and `update.py` commands from an agent |
209
+ | `ai-guard.py` — **scope** | during an implementation step | refuses editing a file the step does not name, with `SCOPE_CHANGE_REQUIRED` |
210
+ | `ai-guard.py` — **decision** | while a decision is open | holds every tool until the dialog is answered or settled in the terminal |
211
+ | `cap-large-read.py` | Claude Code | refuses an unbounded `Read` over 4 000 lines or 250 KB; a `limit` within the budget passes |
212
+ | `context-guard.py` | both | warns from 80% of the compaction point, snapshots before a compaction, injects the handoff at session start, checks plan drift |
213
+ | `runtime-gate.py` | both | sends the top model's launches one tier down during an outage or at 90% of the weekly limit; asks before an EXPERT launch past the budget |
214
+ | `dialog-gate.py` | both | records your picks from the runtime's dialog; holds the turn while a decision is open |
215
+
216
+ Every path of a Codex `apply_patch` is checked on its own. The guards are
217
+ regex over a command line or a path — a tripwire for the honest mistake, **not
218
+ a security boundary**; the real backstops are branch protection, secrets off
219
+ the developer machine and human review. Every rule, its configuration and
220
+ how a change is proved behaviour-preserving: [docs/hooks.md](docs/hooks.md).
484
221
 
485
222
  ## Model tiers
486
223
 
487
- Each plan is one file in `profiles/`: the runtime's own settings plus a
488
- `riskrails` object with the tier table (which model and effort each of FAST,
489
- BALANCED, STRONG and EXPERT is on that plan), the budgets (how many agents may run
490
- at once and how many of them on STRONG, how far direct mode reaches, whether EXPERT
491
- runs without asking, and a token figure per tier) and the preferred runtime per
492
- workflow. `max20.json` is `max.json` with larger budgets; `balanced-max.json` is `max.json`
493
- with the session pinned to Opus 5.5, no Fable and EXPERT at `high` — only the
494
- session runs the strong model, every agent keeps its cheap tier. The installer writes the
495
- resolved object to `<home>/riskrails/profile.json`; `runtime-gate` enforces the
496
- fan-out and EXPERT budgets by asking, `state.py profile --tier <TIER>` prints a
497
- tier's model, and `/usage-report --task <id>` compares a task's tokens with its
498
- plan's figure — reported, never enforced. The prompts themselves name tiers only;
499
- `tests/test_shared_prompts_model_free.py` keeps it that way.
224
+ The prompts name tiers; the installed profile says which model each tier is on
225
+ your plan and runtime (`state.py profile --tier STRONG`).
500
226
 
501
227
  | Tier | Claude Code | Codex | Does |
502
228
  |---|---|---|---|
503
- | FAST | `haiku`, low | Terra, low | reading and running: file search, inventories, logs, test output (`Explore`, `log-reader`, `ai-tester`, `ai-indexer`) |
504
- | BALANCED | `sonnet` | Terra, medium | discovery, context, planning up to T3, mechanical edits, release, the T2/T3 review (`ai-discovery`, `ai-context`, `ai-implementer`, `ai-release`) |
505
- | STRONG | `opus`, high | Sol, high | adversarial and security review (from T4), T5 risk and planning, root cause after a first diagnosis failed, reversible design (`ai-reviewer`, `ai-security`, `architect`) |
506
- | EXPERT | the session model (Opus 5) on Max, `opus` pinned on Pro; Fable 5.1 [1m] on `architect` only | Astra, `xhigh` | T6, irreversible design, what STRONG could not settle |
507
-
508
- The main session does the implementation itself. Every agent definition pins its
509
- own `model:` — most of them the BALANCED tier — and `CLAUDE_CODE_SUBAGENT_MODEL` is
510
- deliberately not set: before Claude Code v2.1.251 it overrides the frontmatter and
511
- the per-call model, which put every agent on Sonnet. A STRONG or EXPERT agent runs
512
- only when a named trigger fires; the triggers are listed in `policies/model-routing.md` of the shared tree.
513
-
514
- On a Max plan the session runs Opus 5.5 [1m] by default at `medium` effort,
515
- compacting near 767k tokens (`autoCompactWindow` 800 000, uncapped on a 1M model)
516
- — `/model opus` gives a 200k session that compacts near 167k, cheaper per turn for
517
- a small task — and escalates from there: `ai-expert` pins `opus` at `xhigh`, so a session switched to
518
- Sonnet (the JetBrains agent's Model setting, or `/model`) cannot weaken the last-resort tier. Fable 5.1 [1m] is pinned on `architect` alone, at
519
- `xhigh`, for design questions outside a task; nothing else ever runs on it, and
520
- `runtime-gate` sends it to Opus while Fable is rate-limited or its weekly limit is
521
- nearly used. `--fable no` leaves `architect` on the Opus session as well.
522
-
523
- Team accounts map by seat: a Standard seat installs as `team-pro` and gets the
524
- Pro profile, a Premium seat installs as `team-max` and gets the Max profile with
525
- Fable on `architect`. The installer reads the seat from `~/.claude.json` and
526
- falls back to `team-pro` when it cannot tell; `--plan team-max` overrides.
527
-
528
- On Pro and Team Pro the session model is `opusplan`:
529
- Opus 5 in plan mode, Sonnet 5 when executing. That is the playbook's "plan mode"
530
- play priced for a $20 plan — the expensive model is spent on the plan, and the
531
- cheaper one on typing it out. Because a subagent that omits `model:` would
532
- inherit Sonnet outside plan mode, the installer pins `model: opus` on
533
- `ai-expert` and `architect` for this plan. `log-reader`, `ai-tester` and
534
- `ai-release` run at `low` effort: they read and report, they do not think.
535
-
536
- Under Codex the ChatGPT plan chooses the profile. On Pro the session runs Sol
537
- at `high` with six agent threads and `ai-expert` runs Astra at `xhigh`. On Plus
538
- the usage window is a fraction of Pro's, so the session runs Sol at `medium`,
539
- three agent threads, and `ai-expert` runs Astra at `high` — `xhigh` stays off
540
- everywhere. The plan is read from `chatgpt_plan_type` in `~/.codex/auth.json`;
541
- `--codex-plan plus|pro` overrides it, and an install that cannot tell assumes
542
- Pro and says so.
543
-
544
- **One difference worth knowing.** Under Claude Code you escalate by spawning
545
- `ai-risk` or `ai-planner` with `model: opus`. Codex resolves an agent's own file
546
- *ahead* of the model asked for at spawn time, so that would silently be ignored —
547
- the Codex roster therefore carries dedicated `ai-risk-strong` and
548
- `ai-planner-strong` agents that pin Sol. Same tier, same trigger, different
549
- mechanism. `ai-expert-strong` is the third: the runtime gate reroutes an
550
- `ai-expert` launch to it while the EXPERT model is unavailable. Every rendered Codex agent writes an explicit `model` and
551
- `model_reasoning_effort` for the same reason: an omitted `model` falls back to the
552
- Terra `[agents]` default, which is not the tier a reviewer needs.
553
-
554
- Why route this way: in measured usage, over 80% of the cost was the main session
555
- re-reading its context (cache read and write), not output, so the session model is
556
- the lever that matters. `/usage-report` shows the split on your machine, across
557
- both runtimes.
558
-
559
- There is no LOCAL tier: neither runtime has a local-model backend. The work it
560
- would have done is done by deterministic tools and by `ai-indexer` on the cheapest
561
- model, and nothing in the design depends on a local model existing.
562
-
563
- ## The guards
229
+ | FAST | `haiku`, low | Terra, low | reading and running: `Explore`, `log-reader` |
230
+ | BALANCED | `sonnet` | Terra, medium | discovery with judgement (`ai-discovery`), the guarded review, batch implementers |
231
+ | STRONG | `opus`, high | Sol, high | the full review, security review, `architect` in `/rr-spec` |
232
+ | EXPERT | Fable 5.1 [1m] on `architect`; Opus without Fable | Astra | what STRONG could not settle |
564
233
 
565
- | Hook | Runtimes | Where | Does |
566
- |---|---|---|---|
567
- | `ai-git-guard` | both | **every repository** | refuses force push, remote branch delete, history rewrite, push or merge to a protected branch, `gh pr merge`, `--no-verify`, staging a secret, production deploy commands |
568
- | `ai-path-guard` | both | only where `.ai/` exists | refuses reading or writing `.env`, `secrets/`, keys, dumps, production logs; edits to the guards' own config, the task state, the journal, the questions file or the handoff; and `state.py approve` from an agent session — approval is a human's to give |
569
- | `ai-scope-guard` | both | only during an implementation step | refuses editing a file the approved step does not name, with the `SCOPE_CHANGE_REQUIRED` signal |
570
- | `cap-large-read.py` | Claude only | every session | refuses an unbounded `Read` of a large file; an explicit `limit` passes |
571
- | `project-scaffold.sh` | Claude only | `Setup:init` | creates the `docs/sdlc/` and runtime layout on `/init` |
572
- | `context-guard.py` | both | `UserPromptSubmit` / `PreCompact` / `SessionStart:startup\|resume\|clear\|compact` | reads the context size from the transcript: warns once per 10k from 80% of the compaction point, holds a prompt back once from 120% (the same prompt again passes). Before a compaction it writes a snapshot — edited files, latest instructions verbatim, todo list, git state, `.ai/` task state — and tells the summary what to keep; after it, the snapshot goes back into the context. On every session start it writes `.ai/state/session.json` and, with a task in flight, injects `handoff.md` and the pending questions. The snapshot is Claude-only; everything else crosses to Codex. Fails open |
573
- | `runtime-gate.py` | both, every plan | `PreToolUse`/`PostToolUse` on `Agent` (Codex: `spawn_agent`, or `collaborationspawn_agent` under multi-agent v2 — matcher `^Agent$\|spawn_agent$`), `SubagentStart`/`SubagentStop`, `StopFailure` (Fable installs), the statusline | sends the top model's launches one tier down while it is rate-limited or unreachable (Fable → Opus; on Codex `ai-expert` → `ai-expert-strong` by rewriting `agent_type`, since a role's pinned model beats the call's); asks before an EXPERT launch or a launch past the plan's fan-out (explains instead on Codex, which cannot ask); records the quota; journals `model_fallback`, and `missed_reroute` when an EXPERT child starts during an outage anyway. After an upgrade, re-trust the changed Codex entries in `/hooks`. `fable-gate.py` and `codex-model-gate.py` are shims that exec it |
574
-
575
- The three shared guards see Codex's `apply_patch` as well. One `apply_patch` can
576
- touch many files, so every `*** Add/Update/Delete File:` and `*** Move to:` path
577
- in the patch is checked separately — a single out-of-scope or protected file
578
- rejects the whole patch. Split the patch rather than widening the step.
579
-
580
- Codex has no `StopFailure` event, so on Codex `runtime-gate` attributes a failure at
581
- `SubagentStop` instead, and only when the evidence points at an EXPERT agent:
582
- the agent's name in the text, an explicit expert `model`, an agent file pinned to
583
- the expert model, or an expert launch inside the last five minutes.
584
-
585
- They are regex-based and run on every matching tool call. That makes them
586
- defence-in-depth against ordinary agent mistakes — **not a security boundary**. A
587
- determined process can still read a file through an interpreter, and a tool that
588
- is not on the hook path is not gated at all. The real backstops are human review
589
- and server-side branch protection.
234
+ The session implements — Opus 5.5 [1m] on Max, `opusplan` on Pro, Sol on Codex
235
+ — and the expensive tiers run one at a time on a named trigger. Plans,
236
+ budgets, the runtime gate and the Codex differences: [docs/routing.md](docs/routing.md).
590
237
 
591
238
  ## Known risks
592
239
 
593
- What version 3.0.0 knowingly does **not** protect against. Each entry says what
594
- can happen, why it was left that way, what limits the damage, and what you should
595
- do. None is a secret. Each was found in a review or a spec, then accepted or
596
- deferred with a written reason in the file named under **Source**.
597
-
598
- ### The guards are a tripwire, not a sandbox
599
-
600
- **Risk.** Every guard rule is a regex over a command line or a path. A process
601
- that wants to get past it can do so. It can read `.env` through a Python
602
- one-liner, build a command from variables, pipe base64 into a shell, or use a
603
- tool that is not on the hook path at all.
604
- **Why it stays.** A real boundary needs an OS-level sandbox. A hook that runs
605
- before a tool call cannot be one. The guards exist to stop the *honest mistake*,
606
- like reading `.env` to check a value or editing one file too many.
607
- **Limits.** A tripwire catches the common obfuscations (quoted flags,
608
- `--opt=value`, interpreter one-liners that mention `.ai/state/` or
609
- `.ai/reports/`).
610
- **What to do.** Keep production secrets off the developer machine, turn on
611
- server-side branch protection, and review every diff before merge. Those are the
612
- real backstops.
613
- **Source.** `docs/hooks.md`, "What these guards are, and are not".
614
-
615
- ### Human approval can be forged by a determined process
616
-
617
- **Risk.** `state.py approve` refuses to run without a terminal on stdin, and the
618
- path guard refuses it from an agent session. An obfuscated invocation that gets
619
- past the regex and also allocates a pseudo-terminal (a pty) can still grant the
620
- approval.
621
- **Why it stays.** This is the same regex limit as above. The check is there to
622
- make self-approval deliberate and visible, not impossible.
623
- **Limits.** `approve` also refuses when no gate was ever requested
624
- (`gate_requested` in the journal). Every approval is journalled with who, when
625
- and how (`via: terminal`).
626
- **What to do.** Read `state.py events` for the approval before you merge a
627
- T4–T6 change. An approval you do not remember giving is a finding.
628
- **Source.** WP2 security review. `hooks/guard/paths.py`, `docs/hooks.md`.
629
-
630
- ### The deletion gate stops an agent's command, not an agent's script
631
-
632
- **Risk.** `--confirm-delete` of `/project-update` (plain `--apply` and
633
- `--adopt --cleanup --apply`) is refused two ways: the path guard denies an agent
634
- Bash command that runs `update.py` with `--apply` and any prefix of
635
- `--confirm-delete`, and `adopt.py` refuses the flag whenever an agent-session
636
- marker (`CLAUDECODE`, or Codex's `CODEX_THREAD_ID`, `CODEX_SESSION_ID`,
637
- `CODEX_CI`, `CODEX_SANDBOX`, `CODEX_SANDBOX_NETWORK_DISABLED`,
638
- `CODEX_VERSION`) is in its
639
- environment, even with a terminal on stdin — an agent can open one with
640
- `script`. The hook matches spellings, so any spelling it does not recognise is
641
- past it: a script file written with the Write tool, arguments held in a
642
- variable, command substitution (`$(…)`, backticks), arguments fed through
643
- `xargs` or stdin, inline `-c` code that imports or runs `update.py`, a glob or a
644
- renamed copy of it — and a session whose working directory has no `.ai/` above
645
- it, where the path guard does not run at all. Each of those still needs the
646
- markers unset (plus a pty) or `AI_UNATTENDED=1` on the command itself to pass
647
- `adopt.py`, which is deliberate, not an honest mistake. A run with the markers
648
- stripped under a pty records `via: terminal`, and `adopt.json` /
649
- `migration.json` are not write-protected, so `via` is a record, not a proof.
650
- **Why it stays.** Same regex limit as above: the hook sees a command line, not
651
- what a file it runs will do.
652
- **Limits.** The record says why the gate passed: `adopt.json` `cleanup.via` and
653
- `migration.json` `deletions[].via` are `terminal` or `unattended`. A human
654
- typing through Claude Code's `!` prefix, or in a shell started from an agent
655
- session, is refused too — that shell carries the markers.
656
- **What to do.** Run `--confirm-delete` in a terminal of your own. A `via:
657
- unattended` on a run nobody launched unattended is a finding.
658
- **Source.** WP6 finding F3, `.ai/reports/T-2026-09-27-003/`.
659
- `hooks/guard/paths.py`, `skills/project-update/adopt.py` (`human_gate`).
660
-
661
- ### `AI_UNATTENDED=1` turns the human gates off
662
-
663
- **Risk.** With `AI_UNATTENDED=1` in its environment, any process passes the
664
- human-present check. That covers the approval gate and the deletion gates of
665
- `/project-update --confirm-delete` and `--adopt --cleanup`. Those can then
666
- approve a task or delete files with no human at the keyboard.
667
- **Why it stays.** CI jobs, cron and scripted batches have no human to take a
668
- turn. Without the variable they could not run at all.
669
- **Limits.** It never hides. The approval is recorded permanently as
670
- `unattended: true` in the state and the journal, `adopt.json` records
671
- `cleanup.unattended: true` and `cleanup.via: unattended` before the first file
672
- is removed, `migration.json` records `via: unattended` per deletion, the report prints
673
- `deleted unattended`, and `/ai-status` names every such event.
674
- **What to do.** Export it only in the environment of a launcher, never in an
675
- interactive shell. There it silently disables the gates for every session the
676
- shell starts. Check `/ai-status` after an unattended run.
677
- **Source.** `.ai/reports/T-2026-09-21-001/release.md`, "Residual accepted
678
- risk". `docs/hooks.md`.
679
-
680
- ### The routing hooks are less protected than the guards
681
-
682
- **Risk.** The path guard protects the three guards' own configuration and the
683
- shim `codex-model-gate.py` by name. It does **not** yet protect `runtime-gate.py`
684
- or `context-guard.py`. An agent could edit those two hooks, and so change the
685
- EXPERT reroute, the quota records or the session and compaction snapshot.
686
- **Why it stays.** WP5 froze the guard files, so that the characterization golden
687
- file proves no guard behaviour changed. Protection for both hooks was split off
688
- as its own additive task.
689
- **Limits.** The installed copies live under `~/.claude` or `~/.codex`, outside
690
- any project, and a change to them shows up in `npx riskrails --dry-run`. On Codex an
691
- edited hook entry loses its trust and stops running until you re-approve it.
692
- **What to do.** Treat an unexpected diff under `hooks/` like a change to CI
693
- configuration. Until the follow-up lands, review hook changes by hand.
694
- **Status.** Open, planned as a follow-up (WP5 OQ3).
695
- **Source.** `docs/sdlc/specs/adaptive-cross-runtime-sdlc-wp5-runtimes-and-plans.md`, concern 2 and OQ3.
696
-
697
- ### The compaction snapshot runs `git` in the working directory
698
-
699
- **Risk.** Before a compaction, `context-guard.py` runs `git branch --show-current`
700
- and `git status --short` in the session's directory. A hostile repository can
701
- make those calls run its own code through its local git configuration (for
702
- example `core.fsmonitor`).
703
- **Why it stays.** This predates the plugin's own work. It is the same exposure as
704
- any shell prompt that shows the git branch.
705
- **Limits.** It runs only under Claude Code, only on compaction, and with your
706
- own user rights. It gives no more than opening the repository in a git-aware
707
- shell already does.
708
- **What to do.** Do not run agent sessions inside repositories you do not trust.
709
- **Status.** Open.
710
- **Source.** WP2 security review. `hooks/context-guard.py` (`build_snapshot`).
711
-
712
- ### Codex runs only hooks you re-approved
713
-
714
- **Risk.** Codex keeps each hook approval as a hash of the whole `config.toml`
715
- entry. After an upgrade changes an entry, for example the gate's matcher
716
- `^Agent$|spawn_agent$`, Codex **skips that hook** until you approve it again in
717
- `/hooks`. Until then the scope, path and git guards, or the runtime gate, are
718
- simply not running under Codex, and nothing on screen says so.
719
- **Why it stays.** This is how Codex's trust model works. A plugin cannot and
720
- should not approve itself.
721
- **What to do.** After every `npx riskrails` that touches Codex, open `/hooks` in
722
- Codex and approve the changed entries. The installer ends with that reminder.
723
- **Source.** `docs/hooks.md`, runtime gate. `README.md`, "What it installs".
724
-
725
- ### Codex facts are observed, not guaranteed
726
-
727
- **Risk.** Codex's multi-agent v2 launches agents as `collaborationspawn_agent`.
728
- A role's pinned model beats a model given at launch, and a Codex hook cannot ask
729
- the user a question. All three were established by live probes and one
730
- documentation page, and Codex changes quickly. A future Codex release can make
731
- the reroute or the fan-out check silently miss.
732
- **Limits.** On Codex these rules degrade to "allow and explain" by design, never
733
- to a block. The `SubagentStart` backstop journals `missed_reroute` when an
734
- EXPERT agent starts during an outage anyway.
735
- **What to do.** After a Codex upgrade, run `state.py events --type
736
- missed_reroute`. Any line there means the reroute stopped matching.
737
- **Source.** WP5 spec, accepted concern 6. PR #26.
738
-
739
- ### Budgets and quota are advice, not limits
740
-
741
- **Risk.** Several numbers can be off, and none of them ever blocks work.
742
- - **Quota signals can be stale.** Codex quota comes only from the rollouts of a
743
- past or running session. Claude's comes only while a session with the
744
- statusline runs.
745
- - **The running-agent count can drift.** A `SubagentStop` lost to a crash leaves
746
- an entry behind for up to 30 minutes, which costs one extra question.
747
- - **The EXPERT launch count per task never shrinks.** Declined launches count
748
- too.
749
- - **On Pro/Plus, "strictly serial" is the plugin's rule, not Codex's.** Codex
750
- still allows 3 threads and can run a second agent after the gate has explained
751
- why it should not.
752
-
753
- **What to do.** `runtime-gate.py clear` empties the count after a crash. Read
754
- the quota with its `stale` and `resets_at` fields, not as a hard figure.
755
- **Source.** WP5 spec, accepted concerns 1, 3 and 4. `hooks/runtime-gate.py`.
756
-
757
- ### `--adopt` edge cases fail safe, not smoothly
758
-
759
- Three low-severity cases from the WP6 review were accepted. Each fails closed or
760
- safe rather than losing data.
761
- - An interrupted apply's `planned_writes` excuses those paths from the
762
- clean-tree check. It never decides what is written or deleted.
763
- - On resume, `original/<file>` is reused, so edits made between the crash and
764
- the resume are not in the saved original. Git still has the committed version.
765
- - In a monorepo subdirectory an interrupted apply cannot resume until you commit.
766
- It refuses rather than guesses.
767
-
768
- Two things have not been tried by hand yet: `--adopt` on a real Spec Kit or
769
- Cursor project (the tests use fixtures), and a cleanup run in a real terminal.
770
- **What to do.** Run `--adopt` on a clean, committed tree, read the dry run
771
- first, and keep `original/` until you have checked the result.
772
- **Source.** `.ai/reports/T-2026-09-21-001/review.md`, the LOW rows.
773
-
774
- ### The usage report's parse cache trusts mtime and size
775
-
776
- **Risk.** `usage-report.py` keeps each transcript's parse state on disk, keyed by
777
- path, mtime and size, and folds only the bytes past the offset it stopped at. A
778
- file that was rewritten rather than appended to — same path, larger, and the
779
- same first 256 bytes — would be resumed from a stale offset, and the report
780
- would count the old records together with the new ones.
781
- **Why it stays.** The alternative is hashing every transcript on every run,
782
- which is the work the cache exists to avoid. Transcripts are append-only in both
783
- runtimes; a rewrite that also preserves the opening bytes is not a case either
784
- runtime produces.
785
- **Limits.** A shrunk file, one whose first block changed, or one whose stored
786
- state is not the shape and contents this version folds, is parsed from zero.
787
- Same mtime and size means the file is not read again — unless its last line had
788
- no newline after it, which is re-read until it is complete — so an in-place
789
- rewrite of exactly the same length is the other side of the same assumption.
790
- The cache is a report's cache: nothing else reads it, and a wrong number is a
791
- wrong number in a report, not a wrong change to a repository.
792
- **What to do.** `--no-cache` reports without it, and tells you within one run
793
- whether a surprising number came from the cache. Deleting
794
- `~/.cache/riskrails/usage-report.json` rebuilds it from scratch.
795
- **Source.** This entry; `skills/usage-report/SKILL.md`, "The parse cache".
796
-
797
- ## Migrating from claude-routing
798
-
799
- This plugin absorbed `claude-routing`. Run `npx riskrails` once and it:
800
-
801
- - writes the routing settings itself, from the same `profiles/` — nothing about
802
- your model, effort or limits changes;
803
- - **removes the `<!-- claude-routing:start/end -->` block** from
804
- `~/.claude/CLAUDE.md` and writes one `riskrails` block carrying both sets
805
- of rules;
806
- - also removes an older unmarked `# Model allocation by task and scope` section,
807
- if one predates the plugins;
808
- - **retires `agents/reviewer.md`** to `reviewer.md.superseded`, because
809
- `ai-reviewer` replaces it — adversarial, tier-aware, and reading the project's
810
- own policies. If you edited that file, it is left alone and the conflict is
811
- reported instead;
812
- - keeps every other hook, agent and settings key you have, including ones no
813
- plugin owns.
814
-
815
- Where the two disagreed, the agentic layer wins:
816
-
817
- | Question | Answer |
818
- |---|---|
819
- | Who reviews a change before commit? | `ai-reviewer`, not `reviewer` |
820
- | Where does a change start? | `/ai-task`. The `/sdlc-*` chain is for work needing a written intent and spec first, then hands its plan to `/ai-task` |
821
- | Who designs? | `ai-planner` inside a task, `ai-expert` on escalation. `architect` is for design questions outside a task, or in a repository without `.ai/` |
822
- | Which init? | `/ai-init`, which runs the SDLC scaffold too. `/project-init` is the SDLC layout alone |
240
+ What 3.0 knowingly does not protect against, each with why it stays and what
241
+ to do: [docs/known-risks.md](docs/known-risks.md).
823
242
 
824
- The old `claude-routing` directory can be deleted once you have installed this
825
- one; nothing references it any more.
243
+ | Risk | In one line |
244
+ |---|---|
245
+ | The guards are a tripwire, not a sandbox | a determined process gets past a regex |
246
+ | Human approval can be forged by a determined process | a pty, or a written ledger, can approve; every approval is journalled |
247
+ | The deletion gate stops a command, not a script | run `--confirm-delete` in a terminal of your own |
248
+ | `AI_UNATTENDED=1` turns the human gates off | export it only in a launcher, never in a shell |
249
+ | Three hooks are less protected than the guards | review hook diffs by hand |
250
+ | The compaction snapshot runs `git` in the working directory | do not run agents in repositories you do not trust |
251
+ | Codex runs only hooks you re-approved | open `/hooks` after every upgrade |
252
+ | Codex facts are observed, not guaranteed | grep the journals for `missed_reroute` after a Codex upgrade |
253
+ | The dialog switch is read in two places | when the dialog and the terminal disagree, use the terminal |
254
+ | Budgets and quota are advice, not limits | read the quota with its `stale` field |
255
+ | `--adopt` edge cases fail safe, not smoothly | adopt on a committed tree, keep `original/` |
256
+ | The usage report's parse cache trusts mtime and size | `--no-cache` when a number surprises you |
826
257
 
827
258
  ## Tests
828
259
 
829
260
  ```bash
830
- bash tests/run-all.sh
261
+ python3 -I tests/run_suite.py --serial test_guard_characterization.TestTimed --serial test_dialog_gate.TestGateFastPath
262
+ python3 -I -m unittest discover -s tests -p 'test_*.py' # CI's serial oracle, same test ids
263
+ AI_E2E=1 python3 -I -m unittest discover -s tests -p test_end_to_end.py
831
264
  ```
832
265
 
833
- Twenty-seven suites, 2,904 assertions. The guards against JSON fixtures in both runtimes (including
834
- `apply_patch` payloads that touch several files at once); the state machine and
835
- its `triage` call; scaffold idempotency for one runtime, the other, and both
836
- over a single `.ai/` tree; installer rendering for every plan combination and
837
- every `--target`; the Codex agent renderer, asserting each role's *effective*
838
- model and effort; the `config.toml` merge against a fixture full of third-party
839
- settings, and its refusal on a malformed file; the migration off
840
- `claude-routing`; `/project-update` against a project scaffolded from the oldest
841
- shipped templates and edited by hand, for a Claude and for a Codex project; an
842
- end-to-end run that installs both runtimes into scratch directories, scaffolds a
843
- throwaway repository, drives a T5 task through the guards from Claude and then
844
- from Codex, and proves both see the same state; the usage report for both
845
- runtimes, including its incremental parse cache against the same run with the
846
- cache off; and both escalation gates through every event.
847
-
848
- No suite reads or writes the developer's real `~/.claude` or `~/.codex`.
266
+ Fifty-six suites, 979 tests, about 75 seconds in parallel. The guards against JSON fixtures for both runtimes and a
267
+ golden of 1 341 recorded decisions, byte for byte, with timed cases that hold
268
+ the hot path under its budget; the state machine, the weight, the batch loop,
269
+ the dialog contract and the documents rule; the compact record and its
270
+ pre-push validator; the installer for every plan and `--target`, the Codex
271
+ agent renderer and the `config.toml` merge against a fixture full of
272
+ third-party settings; `/rr-setup --update` against projects scaffolded from
273
+ the oldest shipped templates and edited by hand; `--adopt` against Spec Kit,
274
+ Kiro and Cursor fixtures; and an end-to-end run that installs both runtimes
275
+ into scratch directories, drives one task through the guards from Claude and
276
+ then from Codex, and proves both see the same state. No suite reads or writes
277
+ the developer's real `~/.claude` or `~/.codex`.
849
278
 
850
279
  ## Documentation
851
280
 
852
281
  | File | About |
853
282
  |---|---|
854
- | `docs/getting-started.md` | the first hour: install, `/ai-init`, a worked T1 and T4 task |
855
- | `docs/architecture.md` | how the pieces fit: `.ai/`, the pipeline, state, hooks, the two adapters |
856
- | `docs/agents.md` | the roster, contracts and when each agent runs |
857
- | `docs/hooks.md` | every rule the guards enforce, their configuration and limits |
858
- | `docs/hook-performance.md` | what the guards cost per tool call, the budget, and how a change is proved behaviour-preserving |
859
- | `docs/risk-tiers.md` | how classification works and how to tune it |
860
- | `docs/workflows.md` | the five workflows and how one is chosen |
861
- | `docs/faq.md` | why something was blocked, and how to change it |
862
- | `docs/sdlc/plans/dual-runtime-agentic-routing.md` | the approved plan this dual-runtime support was built from |
283
+ | [docs/getting-started.md](docs/getting-started.md) | the first hour: install, `/rr-setup`, a light and a full task, a batch build, the update, the bill |
284
+ | [docs/installation.md](docs/installation.md) | every installer option, what it installs per runtime, what loads on every turn, the limits it sets, the 2.x home-side upgrade |
285
+ | [docs/project-update.md](docs/project-update.md) | shared versus project-owned files, how a file is updated, the migrations and their rollback, `--adopt` |
286
+ | [docs/architecture.md](docs/architecture.md) | how the pieces fit: the installed home, `.ai/`, the pipeline, the state, the hooks, the two adapters |
287
+ | [docs/weight.md](docs/weight.md) | the six axes, the three modes, what is measured and what is declared, tuning the table |
288
+ | [docs/workflows.md](docs/workflows.md) | feature, bugfix, refactoring, hotfix, investigation |
289
+ | [docs/agents.md](docs/agents.md) | the five agents, their contracts, the escalation ladder |
290
+ | [docs/routing.md](docs/routing.md) | the tiers per plan and runtime, the runtime gate, the Codex differences |
291
+ | [docs/hooks.md](docs/hooks.md) | every rule the guards enforce, their configuration and limits |
292
+ | [docs/hook-performance.md](docs/hook-performance.md) | the hot path's budget and how a change is proved behaviour-preserving |
293
+ | [docs/local-memory.md](docs/local-memory.md) | the compact record: task ids, packages, recall, the pre-push check, migration |
294
+ | [docs/known-risks.md](docs/known-risks.md) | what the guards do not protect against, and what to do about it |
295
+ | [docs/faq.md](docs/faq.md) | why something was blocked, and how to change it |
296
+ | [docs/sdlc/README.md](docs/sdlc/README.md) | the spec → plan → build artefacts in a project |
297
+ | [ROADMAP.md](ROADMAP.md) | what is decided but not built; the open rows |
863
298
 
864
299
  ## Contributing, security and license
865
300
 
866
- - [CONTRIBUTING.md](CONTRIBUTING.md): setup, tests, and how a change gets merged
867
- - [SECURITY.md](SECURITY.md): what counts as a vulnerability and how to report
868
- one privately
301
+ - [CONTRIBUTING.md](CONTRIBUTING.md): setup, tests, how a guard change is proved, how a change gets merged
302
+ - [SECURITY.md](SECURITY.md): what counts as a vulnerability and how to report one privately
869
303
  - [LICENSE](LICENSE): MIT