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
@@ -16,12 +16,19 @@ Exactly five keys are managed:
16
16
  agents.default_subagent_reasoning_effort
17
17
  agents.max_concurrent_threads_per_session
18
18
 
19
+ and, with ``--status-context yes``, ``tui.status_line``: ``model-with-reasoning``
20
+ and ``context-used`` are appended to a list the user set, when missing; the user's
21
+ items are never removed or reordered. An unset key (Codex's own default items) and
22
+ an empty list are left alone, with a note: writing ours would replace either.
23
+ ``--status-context no`` (the default) leaves the list alone.
24
+
19
25
  Safety contract: the file is parsed before and after the edit, and the write is
20
26
  abandoned unless the *only* difference between the two parses is inside that
21
27
  managed set. A prose key name inside a multi-line string cannot slip through
22
28
  that check, because it would show up as an unexpected difference.
23
29
 
24
30
  merge-codex-config.py <config.toml> --profile <codex-plus.json|codex-pro.json> [--dry-run] [--no-backup]
31
+ [--status-context yes|no]
25
32
  """
26
33
  import argparse
27
34
  import json
@@ -37,6 +44,7 @@ MANAGED_AGENTS = (
37
44
  "default_subagent_reasoning_effort",
38
45
  "max_concurrent_threads_per_session",
39
46
  )
47
+ STATUS_ITEMS = ("model-with-reasoning", "context-used") # Codex 0.165 [tui] status_line ids
40
48
  TABLE_HEADER = re.compile(r"^\s*\[")
41
49
 
42
50
 
@@ -46,6 +54,8 @@ def die(message):
46
54
 
47
55
 
48
56
  def toml_value(value):
57
+ if isinstance(value, list):
58
+ return "[%s]" % ", ".join(toml_value(v) for v in value)
49
59
  if isinstance(value, bool):
50
60
  return "true" if value else "false"
51
61
  if isinstance(value, (int, float)):
@@ -100,6 +110,121 @@ def set_in_span(lines, start, end, key, value, changes):
100
110
  return end + 1
101
111
 
102
112
 
113
+ def value_end(lines, at):
114
+ """One past the last line of the value assigned on line `at` (an array may span lines)."""
115
+ chunk = lines[at].split("=", 1)[1]
116
+ for j in range(at, len(lines)):
117
+ if j > at:
118
+ chunk += "\n" + lines[j]
119
+ try:
120
+ tomllib.loads("v = " + chunk)
121
+ return j + 1
122
+ except tomllib.TOMLDecodeError:
123
+ continue
124
+ return None
125
+
126
+
127
+ def status_items(lines, before, changes, notes):
128
+ """Append STATUS_ITEMS to the user's [tui] status_line where missing; the user's
129
+ items keep their order. Returns the wanted list, or None when nothing is to change,
130
+ the key is unset or empty, or it lives somewhere this edit does not reach."""
131
+ current = before.get("tui.status_line")
132
+ if current is None or current == []:
133
+ # Unset is Codex's own default set, and [] a deliberate empty line: writing
134
+ # our two items would replace either.
135
+ notes.append("left [tui] status_line alone (%s; add %s to show them)"
136
+ % ("not set: Codex's default items" if current is None else "empty on purpose",
137
+ " and ".join(STATUS_ITEMS)))
138
+ return None
139
+ if not isinstance(current, list):
140
+ return None
141
+ missing = [item for item in STATUS_ITEMS if item not in current]
142
+ if not missing:
143
+ return None
144
+ value = current + missing
145
+ rendered = "status_line = %s" % toml_value(value)
146
+ span = table_span(lines, "tui")
147
+ at = key_line(lines, span[0] + 1, span[1], "status_line") if span else None
148
+ if at is None:
149
+ notes.append("left [tui] status_line alone (not a status_line key in a [tui] table)")
150
+ return None
151
+ end = value_end(lines, at)
152
+ if end is None:
153
+ return None
154
+ was = " ".join(line.strip() for line in lines[at:end])
155
+ kept = append_items(lines[at:end], missing)
156
+ try:
157
+ ok = kept is not None and tomllib.loads("\n".join(kept))["status_line"] == value
158
+ except (tomllib.TOMLDecodeError, KeyError):
159
+ ok = False
160
+ # The user's own lines and comments stay; one rendered line only when they cannot.
161
+ lines[at:end] = kept if ok else [rendered]
162
+ changes.append("set [tui] %s (was: %s)" % (rendered, was))
163
+ return value
164
+
165
+
166
+ def array_marks(block):
167
+ """(line, col) of the closing ] of the array assigned on block[0], and of the last
168
+ character before it outside strings, comments and blanks; None for an array this
169
+ scan does not read (nested, or a multi-line string)."""
170
+ row, col = 0, block[0].index("=") + 1
171
+ depth, last, quote = 0, None, None
172
+ while row < len(block):
173
+ line = block[row]
174
+ while col < len(line):
175
+ ch = line[col]
176
+ if quote:
177
+ if quote == '"' and ch == "\\":
178
+ col += 2
179
+ continue
180
+ if ch == quote:
181
+ quote = None
182
+ last = (row, col)
183
+ elif ch in "\"'":
184
+ if line.startswith(ch * 3, col):
185
+ return None
186
+ quote = ch
187
+ elif ch == "#":
188
+ break
189
+ elif ch == "[":
190
+ depth += 1
191
+ if depth > 1:
192
+ return None
193
+ last = (row, col)
194
+ elif ch == "]":
195
+ return (row, col), last
196
+ elif not ch.isspace():
197
+ last = (row, col)
198
+ col += 1
199
+ if quote:
200
+ return None
201
+ row, col = row + 1, 0
202
+ return None
203
+
204
+
205
+ def append_items(block, items):
206
+ """The lines of `block` (one `status_line = [...]` assignment) with `items` added
207
+ before its closing ], comments and layout kept; None when the scan cannot place them."""
208
+ marks = array_marks(block)
209
+ if marks is None or marks[1] is None:
210
+ return None
211
+ (row, col), (lrow, lcol) = marks
212
+ out = list(block)
213
+ comma = block[lrow][lcol] not in ",["
214
+ if block[row][:col].strip():
215
+ # ] shares its line with an item: `["a"] # c` → `["a", "x", "y"] # c`.
216
+ joined = ", ".join(toml_value(item) for item in items)
217
+ lead = ", " if comma else ("" if block[lrow][lcol] == "[" else " ")
218
+ out[row] = out[row][:col] + lead + joined + out[row][col:]
219
+ else:
220
+ # ] on its own line: one `"item",` line each, at the last item's indentation.
221
+ indent = re.match(r"\s*", block[lrow]).group(0) if lrow > 0 else re.match(r"\s*", block[row]).group(0) + " "
222
+ out[row:row] = ["%s%s," % (indent, toml_value(item)) for item in items]
223
+ if comma:
224
+ out[lrow] = out[lrow][:lcol + 1] + "," + out[lrow][lcol + 1:]
225
+ return out
226
+
227
+
103
228
  def flatten(data, prefix=""):
104
229
  out = {}
105
230
  for key, value in data.items():
@@ -117,6 +242,7 @@ def main():
117
242
  parser.add_argument("--profile", required=True)
118
243
  parser.add_argument("--dry-run", action="store_true")
119
244
  parser.add_argument("--no-backup", action="store_true")
245
+ parser.add_argument("--status-context", choices=("yes", "no"), default="no")
120
246
  args = parser.parse_args()
121
247
 
122
248
  try:
@@ -182,6 +308,12 @@ def main():
182
308
  continue
183
309
  stop = set_in_span(lines, start + 1, stop, key, wanted["agents.%s" % key], changes)
184
310
 
311
+ notes = []
312
+ if args.status_context == "yes":
313
+ status = status_items(lines, before, changes, notes)
314
+ if status is not None:
315
+ wanted["tui.status_line"] = status
316
+
185
317
  text = "\n".join(lines)
186
318
  if trailing_newline or text:
187
319
  text = text.rstrip("\n") + "\n"
@@ -201,6 +333,8 @@ def main():
201
333
  if after.get(key) != value:
202
334
  die(f"refusing to write: {key} did not end up as {value!r}")
203
335
 
336
+ for note in notes:
337
+ print("codex config: %s" % note)
204
338
  if not changes:
205
339
  print("codex config: already set (model=%s, effort=%s)" % (wanted["model"], wanted["model_reasoning_effort"]))
206
340
  return 0
@@ -21,9 +21,14 @@ Resolution:
21
21
  ``pro.json``, ``team-max`` resolves ``max.json``).
22
22
  * ``--fable yes|no`` records ``fable`` and drops ``architect_model_with_fable``
23
23
  when Fable is off, so a reader never has to know the flag.
24
+ * ``--context default|economy`` (default ``default``) records ``context: {mode,
25
+ window}``. ``economy`` copies ``riskrails.economy.autoCompactWindow`` to the
26
+ settings' top level and into ``window``; ``default`` leaves the runtime its own
27
+ compaction, so ``window`` is null and settings carry no ``autoCompactWindow``.
24
28
 
25
29
  resolve-profile.py <name|path> [--src DIR] [--plan P] [--label L]
26
- [--fable yes|no] [--print full|settings|agentic]
30
+ [--fable yes|no] [--context default|economy]
31
+ [--print full|settings|agentic]
27
32
 
28
33
  ``settings`` prints the profile without ``riskrails``; ``agentic`` prints
29
34
  the resolved ``riskrails`` object; ``full`` (the default) prints both.
@@ -79,7 +84,7 @@ def codex_tiers(data):
79
84
  return tiers
80
85
 
81
86
 
82
- def resolve(src, name, plan=None, label=None, fable=None):
87
+ def resolve(src, name, plan=None, label=None, fable=None, context="default"):
83
88
  data = load(src, name)
84
89
  agentic = data.get(KEY)
85
90
  if not isinstance(agentic, dict):
@@ -94,6 +99,12 @@ def resolve(src, name, plan=None, label=None, fable=None):
94
99
  agentic["fable"] = fable
95
100
  if not fable:
96
101
  agentic.get("tiers", {}).get("EXPERT", {}).pop("architect_model_with_fable", None)
102
+ window = None
103
+ if context == "economy":
104
+ window = (agentic.get("economy") or {}).get("autoCompactWindow")
105
+ if window:
106
+ data["autoCompactWindow"] = window
107
+ agentic["context"] = {"mode": context, "window": window}
97
108
  return data
98
109
 
99
110
 
@@ -105,12 +116,13 @@ def main(argv=None):
105
116
  ap.add_argument("--plan")
106
117
  ap.add_argument("--label")
107
118
  ap.add_argument("--fable", choices=("yes", "no"))
119
+ ap.add_argument("--context", choices=("default", "economy"), default="default")
108
120
  ap.add_argument("--print", dest="what", choices=("full", "settings", "agentic"), default="full")
109
121
  args = ap.parse_args(argv)
110
122
 
111
123
  fable = None if args.fable is None else args.fable == "yes"
112
124
  try:
113
- data = resolve(args.src, args.profile, args.plan, args.label, fable)
125
+ data = resolve(args.src, args.profile, args.plan, args.label, fable, args.context)
114
126
  except (OSError, ValueError) as exc:
115
127
  print(f"resolve-profile: {exc}", file=sys.stderr)
116
128
  return 2
@@ -26,13 +26,13 @@
26
26
  ]
27
27
  },
28
28
  {
29
- "matcher": "Read|Edit|Write|MultiEdit|NotebookEdit|Bash",
29
+ "matcher": "*",
30
30
  "hooks": [
31
31
  {
32
32
  "type": "command",
33
33
  "command": "python3 -I -S \"$HOME/.claude/hooks/ai-guard.py\"",
34
34
  "timeout": 15,
35
- "statusMessage": "Checking git, paths and scope"
35
+ "statusMessage": "Checking git, paths, scope and open decisions"
36
36
  }
37
37
  ]
38
38
  }
@@ -84,6 +84,43 @@
84
84
  }
85
85
  ]
86
86
  }
87
+ ],
88
+ "Notification": [
89
+ {
90
+ "matcher": "auth_success",
91
+ "hooks": [
92
+ {
93
+ "type": "command",
94
+ "command": "\"$HOME/.claude/hooks/context-guard.py\"",
95
+ "timeout": 15
96
+ }
97
+ ]
98
+ }
99
+ ],
100
+ "PostToolUse": [
101
+ {
102
+ "matcher": "AskUserQuestion",
103
+ "hooks": [
104
+ {
105
+ "type": "command",
106
+ "command": "python3 -I -S \"$HOME/.claude/hooks/dialog-gate.py\"",
107
+ "timeout": 5,
108
+ "statusMessage": "Recording the dialog's answer"
109
+ }
110
+ ]
111
+ }
112
+ ],
113
+ "Stop": [
114
+ {
115
+ "hooks": [
116
+ {
117
+ "type": "command",
118
+ "command": "python3 -I -S \"$HOME/.claude/hooks/dialog-gate.py\"",
119
+ "timeout": 5,
120
+ "statusMessage": "Checking for an open decision"
121
+ }
122
+ ]
123
+ }
87
124
  ]
88
125
  }
89
126
  }
package/skills/AGENTS.md CHANGED
@@ -1,6 +1,6 @@
1
- <!-- claude-agentic:rule:skills:start -->
1
+ <!-- riskrails:rule:skills:start -->
2
2
  # Skills rules
3
3
  - Weigh a change by what it does to a user project's decisions, never by the skill being a process (constitution C6). The mode is what W gives (`state.py weight`); the rows below set the behaviour axis.
4
4
  - behaviour 0: skill or agent text, `usage-report`, no `state.py` refusal moves. behaviour 1: adds a `state.py` check, a sensor or a refusal. behaviour 2: weakens a refusal, changes the approval path (`approve`, `questions --sync`, `AI_UNATTENDED`, the review verdict transitions) or changes install and migration (the `project-update` merge; a migration also raises reversibility through its hint) — declare it, `--declare behaviour=2:"<the effect>"`. Start such a task with `start --mode full --by <name>`: its weight lands in guarded, and these effects keep a STRONG review and the gate.
5
5
  - A change to a shipped template or to `ai/` runs `tools/build-template-history.py` in the same task, or `tests/test-project-update.sh` goes red.
6
- <!-- claude-agentic:rule:skills:end -->
6
+ <!-- riskrails:rule:skills:end -->
package/skills/CLAUDE.md CHANGED
@@ -1,6 +1,6 @@
1
- <!-- claude-agentic:rule:skills:start -->
1
+ <!-- riskrails:rule:skills:start -->
2
2
  # Skills rules
3
3
  - Weigh a change by what it does to a user project's decisions, never by the skill being a process (constitution C6). The mode is what W gives (`state.py weight`); the rows below set the behaviour axis.
4
4
  - behaviour 0: skill or agent text, `usage-report`, no `state.py` refusal moves. behaviour 1: adds a `state.py` check, a sensor or a refusal. behaviour 2: weakens a refusal, changes the approval path (`approve`, `questions --sync`, `AI_UNATTENDED`, the review verdict transitions) or changes install and migration (the `project-update` merge; a migration also raises reversibility through its hint) — declare it, `--declare behaviour=2:"<the effect>"`. Start such a task with `start --mode full --by <name>`: its weight lands in guarded, and these effects keep a STRONG review and the gate.
5
5
  - A change to a shipped template or to `ai/` runs `tools/build-template-history.py` in the same task, or `tests/test-project-update.sh` goes red.
6
- <!-- claude-agentic:rule:skills:end -->
6
+ <!-- riskrails:rule:skills:end -->
@@ -13,8 +13,8 @@ Everything else loads on demand; nothing here is worth reading twice.
13
13
  The task names the files it may touch; an edit outside them is refused —
14
14
  answer `SCOPE_CHANGE_REQUIRED` and widen the task in place with
15
15
  `state.py start --amend --files "+<path>" --why "…"`, never around the guard.
16
- 3. No agent commits, pushes, merges or deploys. Approval is given by a human,
17
- outside the agent.
16
+ 3. No agent merges or deploys; commit, push, approval only as the dialog's picked
17
+ command, or the human runs them outside the agent.
18
18
  4. Verify before reporting done: the verification command once, to the end,
19
19
  every failure fixed as one batch, the output shown. Run it through
20
20
  `state.py verify --scope all` — it caps the runs, keeps the log out of the
@@ -1 +1 @@
1
- 8
1
+ 10
@@ -0,0 +1 @@
1
+ { "version": 1, "enabled": true, "window_seconds": 120, "stop_blocks_max": 2 }
@@ -23,8 +23,15 @@ is evidence; your intuition about how the code "should" look is not.
23
23
  - Read production secrets or customer data into the model context.
24
24
 
25
25
  If you find a problem outside the current task: **write it down, do not fix it.**
26
- Unrelated findings go to `.ai/project/known-risks.md` or into the report's
27
- "observations" section, and the human decides whether they become their own task.
26
+ Unrelated findings are recorded with `state.py memory risk add` (in files mode it
27
+ writes `.ai/project/known-risks.md`) or go into the report's "observations"
28
+ section, and the human decides whether they become their own task.
29
+
30
+ The compact record is the human's to switch: `state.py memory enable`, `disable`,
31
+ `cleanup`, `pack`, `redact` and `migrate --move-logs` take the human route, as
32
+ `approve` does, and `.ai/memory/` and `.ai/local/memory/` are written by
33
+ `state.py` alone — an agent never edits them (`state.py memory --help`; the
34
+ plugin's `docs/local-memory.md`).
28
35
 
29
36
  ## Scope
30
37
 
@@ -43,6 +50,16 @@ Codex has no hookable read tool, so the large-read cap is Claude-only there —
43
50
  an agent that behaves correctly only because a hook is watching is not behaving
44
51
  correctly. The rules above hold whether or not a hook enforces them.
45
52
 
53
+ **The guards' threat model.** The guards defend against an agent's mistakes — a
54
+ careless `rm`, a write to a frozen policy during a task, a commit with a
55
+ secret — not against an agent that constructs a command to exhaust them. A
56
+ command built to time the guard out is visible in the transcript and in the
57
+ journal, and the human gate in full and full_irreversible mode is the boundary
58
+ for anything that matters.
59
+ Approval rests on a guard plus a ledger: a command the agent constructs to pass
60
+ for the human's pick is outside the threat model, and `state.py consent audit`
61
+ detects it afterwards.
62
+
46
63
  ## Evidence labels
47
64
 
48
65
  Anything an agent writes into `.ai/project/` carries one of four labels:
@@ -62,3 +79,6 @@ evidence for each, and let the human decide which is the bug.
62
79
 
63
80
  Stop and ask. An agent that pauses costs a message; an agent that guesses on a
64
81
  payment path costs an incident.
82
+
83
+ Doubt on reversibility or domain is a blocker; other doubt is written to
84
+ `task.md` and the work continues.
@@ -40,6 +40,7 @@
40
40
  "review_rounds_max": 2,
41
41
  "remediation_rounds": 2,
42
42
  "tests": {"max_suite_runs": 2, "timeout_seconds": 1800, "env_retries": 1, "log_max_bytes": 2097152},
43
+ "batch": {"default": false, "review_rounds_max": 3, "max_suite_runs": 8, "parallel": 3, "tier": "BALANCED"},
43
44
  "examples": {
44
45
  "light": ["a font or copy change in one template", "a test-only change", "a message-text change"],
45
46
  "guarded": ["a regex fix in a hot guard script", "a dev-environment manifest", "a feature with a new screen and an additive migration"],
@@ -1,6 +1,6 @@
1
1
  # Weight table
2
2
 
3
- <!-- generated by `state.py weight --mirror` from weight.json; edit the JSON and regenerate. sha256: 7f3859c34035fe32abf937eebdd7240ac72ab32d254286cff7d939cf0cf6eaef -->
3
+ <!-- generated by `state.py weight --mirror` from weight.json; edit the JSON and regenerate. sha256: 5353ca529c46f2443e12947ec205a6887fb594a4de1f87f0eb1c0e6c0f826133 -->
4
4
 
5
5
  Six axes, 0–2 each; W is their sum. Why each rule is what it is: the plugin's `docs/weight.md`.
6
6
 
@@ -29,4 +29,4 @@ Six axes, 0–2 each; W is their sum. Why each rule is what it is: the plugin's
29
29
 
30
30
  Rules: {"unknown_scores": 1, "hint_raises_by": 1, "hint_raises_once_per_axis": true, "rescore_axes": ["reversibility", "domain"], "rescore_direction": "up", "lower_by": "human", "steps_advice_over": 8}
31
31
 
32
- Budgets: review_rounds_max 2 · remediation_rounds 2 · tests {"max_suite_runs": 2, "timeout_seconds": 1800, "env_retries": 1, "log_max_bytes": 2097152}
32
+ Budgets: review_rounds_max 2 · remediation_rounds 2 · tests {"max_suite_runs": 2, "timeout_seconds": 1800, "env_retries": 1, "log_max_bytes": 2097152} · batch {"default": false, "review_rounds_max": 3, "max_suite_runs": 8, "parallel": 3, "tier": "BALANCED"}
@@ -1,6 +1,6 @@
1
1
  # Task state
2
2
 
3
- `current.json` is the machine-readable record of the task in flight. It is
3
+ `state.json` is the machine-readable record of the task in flight. It is
4
4
  written by `skills/ai-task/state.py` (atomically), read by `/ai-status`, and
5
5
  enforced by `ai-scope-guard`, which derives the files the current step may touch
6
6
  from it.
@@ -10,15 +10,18 @@ Two other writers exist, and only these two. A schema migration in
10
10
  same atomic write and a `schema_migrated` history entry — that is how a task in
11
11
  flight survives a change to the shape of this file. And `hooks/context-guard.py`
12
12
  writes `session.json`, the sidecar beside this file, and nothing else: it never
13
- touches `current.json`. Nothing else writes any of it — an agent never edits
13
+ touches `state.json`. Nothing else writes any of it — an agent never edits
14
14
  them by hand, and the path guard refuses when one tries.
15
15
 
16
16
  It is **not committed**: it describes a session, not the repository. The
17
17
  `.gitignore` entry added by `/ai-init` keeps it out of git.
18
18
 
19
- When a task closes, `state.py archive` moves it to
20
- `.ai/reports/<task-id>/state.json`, which **is** committed — that is the audit
21
- trail: which stages ran, in what order, who approved what, and when.
19
+ When a task closes, `state.py close` writes the final state to
20
+ `.ai/state/logs/<task-id>/state.json`, beside the task's verify logs — local,
21
+ **not** committed. What is committed is `.ai/reports/<task-id>/events.jsonl`
22
+ and `task.md`, and in packages mode the task's compact record under
23
+ `.ai/memory/` — that is the audit trail: which stages ran, in what order, who
24
+ approved what, and when (the plugin's `docs/local-memory.md`).
22
25
 
23
26
  It holds facts, never transcripts:
24
27
 
@@ -42,7 +45,7 @@ defaults.
42
45
 
43
46
  | File | Written by | Committed |
44
47
  |---|---|---|
45
- | `current.json` | `state.py`, and `update.py` during a migration | no |
48
+ | `state.json` (legacy `current.json`, still read) | `state.py`, and `update.py` during a migration | no |
46
49
  | `session.json` | `context-guard.py`, on SessionStart and each prompt | no |
47
50
  | `handoff.md` | `state.py handoff` — never a model | no |
48
51
  | `../reports/<task-id>/task.md` §Questions | `state.py ask/answer/questions` | yes |
@@ -8,6 +8,6 @@ This repository runs an agentic pipeline under `.ai/`. Read `.ai/AGENTS.md` firs
8
8
  - A change runs through `/rr-build <request>`; `/rr-status` shows where it stands. Each step names the files it may touch — an edit outside them is refused: answer `SCOPE_CHANGE_REQUIRED`. One `apply_patch` is checked file by file: a patch that reaches outside the step is refused whole.
9
9
 
10
10
  - Verify before reporting done: `verify_command` from `.ai/policies/testing.md` once, to the end, then `e2e_command` once; every failure fixed as one batch; show the output.
11
- - No agent commits, merges or deploys; approval is given by a human outside the agent. When a review flags the same mistake twice, the correction goes into this file.
11
+ - No agent merges or deploys; commit, push, approval only as the dialog picks, else a human outside. When a review flags the same mistake twice, the correction goes into this file.
12
12
  - Files, not chat, carry decisions: `.ai/reports/<task-id>/task.md` (answer by filling `[Answer]:`), `.ai/state/handoff.md` (read first when resuming), `docs/sdlc/constitution.md` (this project's principles).
13
13
  <!-- riskrails:end -->
@@ -8,6 +8,6 @@ This repository runs an agentic pipeline under `.ai/`. Read `.ai/AGENTS.md` firs
8
8
  - A change runs through `/rr-build <request>`; `/rr-status` shows where it stands. Each step names the files it may touch — an edit outside them is refused: answer `SCOPE_CHANGE_REQUIRED`.
9
9
 
10
10
  - Verify before reporting done: `verify_command` from `.ai/policies/testing.md` once, to the end, then `e2e_command` once; every failure fixed as one batch; show the output.
11
- - No agent commits, merges or deploys; approval is given by a human outside the agent. When a review flags the same mistake twice, the correction goes into this file.
11
+ - No agent merges or deploys; commit, push, approval only as the dialog picks, else a human outside. When a review flags the same mistake twice, the correction goes into this file.
12
12
  - Files, not chat, carry decisions: `.ai/reports/<task-id>/task.md` (answer by filling `[Answer]:`), `.ai/state/handoff.md` (read first when resuming), `docs/sdlc/constitution.md` (this project's principles).
13
13
  <!-- riskrails:end -->
@@ -8,6 +8,6 @@ This repository runs an agentic pipeline under `.ai/`. Read `.ai/AGENTS.md` firs
8
8
  - Changes run through the pipeline (`/rr-build` in Claude Code or Codex); without it, follow `.ai/policies/safety.md`, touch only the files the current task names, and answer questions in prose or by filling the file.
9
9
 
10
10
  - Verify before reporting done: `verify_command` from `.ai/policies/testing.md` once, to the end, then `e2e_command` once; every failure fixed as one batch; show the output.
11
- - No agent commits, merges or deploys; approval is given by a human outside the agent. When a review flags the same mistake twice, the correction goes into this file.
11
+ - No agent merges or deploys; commit, push, approval only as the dialog picks, else a human outside. When a review flags the same mistake twice, the correction goes into this file.
12
12
  - Files, not chat, carry decisions: `.ai/reports/<task-id>/task.md` (answer by filling `[Answer]:`), `.ai/state/handoff.md` (read first when resuming), `docs/sdlc/constitution.md` (this project's principles).
13
13
  <!-- riskrails:end -->
@@ -8,6 +8,6 @@ This repository runs an agentic pipeline under `.ai/`. Read `.ai/AGENTS.md` firs
8
8
  - Changes run through the pipeline (`/rr-build` in Claude Code or Codex); without it, follow `.ai/policies/safety.md`, touch only the files the current task names, and answer questions in prose or by filling the file.
9
9
 
10
10
  - Verify before reporting done: `verify_command` from `.ai/policies/testing.md` once, to the end, then `e2e_command` once; every failure fixed as one batch; show the output.
11
- - No agent commits, merges or deploys; approval is given by a human outside the agent. When a review flags the same mistake twice, the correction goes into this file.
11
+ - No agent merges or deploys; commit, push, approval only as the dialog picks, else a human outside. When a review flags the same mistake twice, the correction goes into this file.
12
12
  - Files, not chat, carry decisions: `.ai/reports/<task-id>/task.md` (answer by filling `[Answer]:`), `.ai/state/handoff.md` (read first when resuming), `docs/sdlc/constitution.md` (this project's principles).
13
13
  <!-- riskrails:end -->