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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/README.md +246 -812
- package/agents/ai-reviewer.md.tmpl +6 -4
- package/ai/policies/review-economy.md +7 -2
- package/ai/using-riskrails.md +15 -0
- package/bin/claude-1m +15 -14
- package/bin/riskrails.js +108 -32
- package/codex/hooks.json +25 -2
- package/hooks/AGENTS.md +2 -2
- package/hooks/CLAUDE.md +2 -2
- package/hooks/ai-guard.py +29 -8
- package/hooks/ai-path-guard-defaults.json +3 -1
- package/hooks/codex-hooks.json +25 -2
- package/hooks/context-guard.py +251 -35
- package/hooks/dialog-gate.py +105 -0
- package/hooks/guard/common.py +5 -0
- package/hooks/guard/decision.py +123 -0
- package/hooks/guard/dialog.py +598 -0
- package/hooks/guard/git.py +49 -3
- package/hooks/guard/paths.py +62 -3
- package/hooks/hooks.json +37 -2
- package/hooks/memory-pre-push.py +28 -0
- package/hooks/runtime-gate.py +59 -12
- package/hooks/update_notice.py +135 -0
- package/instructions/routing.md +4 -4
- package/instructions/stub.md +2 -2
- package/package.json +1 -1
- package/profiles/max.json +3 -1
- package/profiles/pro.json +3 -1
- package/scripts/merge-codex-config.py +134 -0
- package/scripts/resolve-profile.py +15 -3
- package/settings.common.json +39 -2
- package/skills/AGENTS.md +2 -2
- package/skills/CLAUDE.md +2 -2
- package/skills/ai-init/templates/.ai/AGENTS.md +2 -2
- package/skills/ai-init/templates/.ai/VERSION +1 -1
- package/skills/ai-init/templates/.ai/policies/dialog.json +1 -0
- package/skills/ai-init/templates/.ai/policies/safety.md +22 -2
- package/skills/ai-init/templates/.ai/policies/weight.json +1 -0
- package/skills/ai-init/templates/.ai/policies/weight.md +2 -2
- package/skills/ai-init/templates/.ai/state/README.md +9 -6
- package/skills/ai-init/templates/AGENTS.block.md +1 -1
- package/skills/ai-init/templates/CLAUDE.block.md +1 -1
- package/skills/ai-init/templates/GEMINI.block.md +1 -1
- package/skills/ai-init/templates/junie-guidelines.block.md +1 -1
- package/skills/ai-task/memory_compact.py +968 -0
- package/skills/ai-task/memory_format.py +775 -0
- package/skills/ai-task/memory_store.py +528 -0
- package/skills/ai-task/memory_sync.py +1571 -0
- package/skills/ai-task/sensors.py +2 -2
- package/skills/ai-task/state.py +967 -70
- package/skills/ai-task/weight.py +3 -3
- package/skills/project-init/templates/constitution.md +1 -1
- package/skills/project-update/history/blobs/164cd34c182de88c33de7df35beb29200d792419e1c13a5f0b2104f944e57be7 +1 -0
- package/skills/project-update/history/blobs/1d1d907449ec74594d0ee923ac142ae746b9aedf0a1eda9469f075a44d23e3d7 +13 -0
- package/skills/project-update/history/blobs/23a42febbacaa7b52a802d428033a99c0694391c81d51fe3b6e1dff608a641d5 +67 -0
- package/skills/project-update/history/blobs/5353ca529c46f2443e12947ec205a6887fb594a4de1f87f0eb1c0e6c0f826133 +49 -0
- package/skills/project-update/history/blobs/5f65bc7ab3fc3c4e75ae05d49eaf4331fafc398c661abe33c69f7380a720344c +32 -0
- package/skills/project-update/history/blobs/7be527cd648b27cca6e267de30f8e33a2ce17d5631a82694f23ae5c0623efe1f +15 -0
- package/skills/project-update/history/blobs/7ee99edafe7cec45ff577376c0d0178fdb2690134d4656488f46e129aba3cc5a +67 -0
- package/skills/project-update/history/blobs/8b3ef9586491a426767c01da5d01bdd6b0655c576c68c4cc6dfb093367817dc0 +67 -0
- package/skills/project-update/history/blobs/917df3320d778ddbaa5c5c7742bc4046bf803c36ed2b050f30844ed206783469 +1 -0
- package/skills/project-update/history/blobs/935af1019931058ec3a0b834fe230e127ec914f75edcc4a6c589cfe733b0b979 +13 -0
- package/skills/project-update/history/blobs/ae5ce131d6bae401199ff32d8c3dc4d8aec188010a9111624ac64506e2116ff4 +84 -0
- package/skills/project-update/history/blobs/ca4499d829268c8766fb3b825a515e88dc55c237e9d202816b50959b4dac031f +13 -0
- package/skills/project-update/history/blobs/e9adb7a9905eb719ac78ab96671f055da53e5001810f5ad6d6c94306b0323fe7 +74 -0
- package/skills/project-update/history/blobs/fae3064e648cdc55be4aeb1177525b302a5e34ebd752e4a904f47dc0f4557b96 +71 -0
- package/skills/project-update/history/index.json +32 -13
- package/skills/project-update/migrations/0009_state_file.py +25 -0
- package/skills/project-update/migrations/0010_dialog_switch.py +30 -0
- package/skills/project-update/migrations/README.md +2 -1
- package/skills/project-update/update.py +12 -3
- package/skills/rr-build/SKILL.md +14 -5
- package/skills/rr-build-all/SKILL.md +190 -0
- package/skills/rr-plan/SKILL.md +13 -3
- package/skills/rr-review/SKILL.md +18 -8
- package/skills/rr-ship/SKILL.md +16 -8
- package/skills/rr-status/SKILL.md +2 -2
- 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] [--
|
|
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
|
package/settings.common.json
CHANGED
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
]
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
|
-
"matcher": "
|
|
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
|
|
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
|
-
<!--
|
|
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
|
-
<!--
|
|
6
|
+
<!-- riskrails:rule:skills:end -->
|
package/skills/CLAUDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
<!--
|
|
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
|
-
<!--
|
|
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
|
|
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
|
-
|
|
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
|
|
27
|
-
|
|
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:
|
|
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
|
-
`
|
|
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 `
|
|
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
|
|
20
|
-
`.ai/
|
|
21
|
-
|
|
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
|
-
| `
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 -->
|