riskrails 3.0.1 → 3.2.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 +1 -1
- package/README.md +29 -15
- package/ai/policies/model-routing.md +7 -7
- package/ai/policies/production.md +3 -3
- package/ai/policies/release.md +2 -2
- package/ai/policies/review-economy.md +27 -30
- package/bin/riskrails.js +114 -23
- package/hooks/ai-path-guard-defaults.json +3 -2
- package/hooks/context-guard.py +15 -12
- package/hooks/dialog-gate.py +1 -1
- package/hooks/guard/common.py +2 -0
- package/hooks/guard/dialog.py +107 -27
- package/hooks/guard/git.py +12 -3
- package/hooks/guard/paths.py +150 -37
- package/hooks/project-scaffold.py +3 -6
- package/hooks/runtime-gate.py +134 -33
- package/hooks/update_notice.py +22 -16
- package/instructions/routing.md +1 -1
- package/instructions/stub.md +3 -3
- package/package.json +1 -1
- package/skills/AGENTS.md +1 -1
- package/skills/CLAUDE.md +1 -1
- package/skills/ai-init/templates/.ai/AGENTS.md +1 -1
- package/skills/ai-init/templates/.ai/VERSION +1 -1
- package/skills/ai-init/templates/.ai/policies/security.md +7 -1
- package/skills/ai-init/templates/.ai/policies/testing.md +19 -6
- package/skills/ai-init/templates/.ai/state/README.md +4 -4
- package/skills/ai-init/templates/AGENTS.block.md +2 -2
- package/skills/ai-init/templates/CLAUDE.block.md +2 -2
- package/skills/ai-init/templates/GEMINI.block.md +2 -2
- package/skills/ai-init/templates/gitignore.snippet +1 -0
- package/skills/ai-init/templates/junie-guidelines.block.md +2 -2
- package/skills/ai-task/rr_state/__init__.py +1 -0
- package/skills/ai-task/rr_state/cli.py +210 -0
- package/skills/ai-task/rr_state/close.py +171 -0
- package/skills/ai-task/rr_state/dialog.py +541 -0
- package/skills/ai-task/rr_state/dialog_cmd.py +164 -0
- package/skills/ai-task/rr_state/fs.py +220 -0
- package/skills/ai-task/rr_state/gate.py +125 -0
- package/skills/ai-task/rr_state/gate_cmd.py +128 -0
- package/skills/ai-task/rr_state/handoff.py +226 -0
- package/skills/ai-task/rr_state/journal.py +126 -0
- package/skills/ai-task/rr_state/labels.py +99 -0
- package/skills/ai-task/rr_state/ledger.py +145 -0
- package/skills/ai-task/rr_state/locks.py +84 -0
- package/skills/ai-task/rr_state/memory_cmd.py +310 -0
- package/skills/ai-task/rr_state/policy.py +378 -0
- package/skills/ai-task/rr_state/profile.py +373 -0
- package/skills/ai-task/rr_state/questions.py +336 -0
- package/skills/ai-task/rr_state/questions_cmd.py +321 -0
- package/skills/ai-task/rr_state/report.py +271 -0
- package/skills/ai-task/rr_state/report_cmd.py +230 -0
- package/skills/ai-task/rr_state/review.py +165 -0
- package/skills/ai-task/rr_state/runtime.py +80 -0
- package/skills/ai-task/rr_state/start.py +257 -0
- package/skills/ai-task/rr_state/steps.py +131 -0
- package/skills/ai-task/rr_state/task.py +396 -0
- package/skills/ai-task/rr_state/verify.py +231 -0
- package/skills/ai-task/sensors.py +37 -4
- package/skills/ai-task/state.py +24 -5138
- package/skills/project-init/templates/constitution.md +1 -1
- package/skills/project-init/templates/sdlc-README.md +20 -14
- package/skills/project-update/adopt.py +38 -24
- package/skills/project-update/history/blobs/00289f98434fed230eebe768dfb3425f83d737866a3aebc97aa95167c4fa0c6e +131 -0
- package/skills/project-update/history/blobs/079a91f976ad69b5cf49ccb9a28849576e4735ddd9485fc26599e528a69fd2f4 +30 -0
- package/skills/project-update/history/blobs/07ff7fa6f93d471ff533f553b4e523e4105ccde1f3dab70132c5237728f68422 +13 -0
- package/skills/project-update/history/blobs/0ef4a360c76da290173dea0f8073949cb0f8113710a38df95acae5d96bdd1286 +123 -0
- package/skills/project-update/history/blobs/18c7a2753ce9cb6f6d3d1e9d5d1125d75950b879d56b7325225b200869631b29 +15 -0
- package/skills/project-update/history/blobs/25d4f2a86deb5e2574bb3210b67bb24fcc4afb19f93a7b65a057daa874a9d18e +1 -0
- package/skills/project-update/history/blobs/48d9950f16e31d55475f566a80dc95f64b35f1c804d2f51729e313a4f29bac8d +13 -0
- package/skills/project-update/history/blobs/5a93f78b4ed4d416c171139b2ce610a117f9c1dd46fbb56ca86729cb4c53d10c +6 -0
- package/skills/project-update/history/blobs/6a3be0e624e5ad91e44d08857b7183ee53b7903e1e8d56ff196dbc9dde271e4a +75 -0
- package/skills/project-update/history/blobs/7170553410bba904ffb7edb4b231e3b3579b85542d84de717299980938f82c9d +13 -0
- package/skills/project-update/history/blobs/774b1e3a04ef0d49d33c44a2cfecd92eb6f6b738a12b6a681103ba7fc13e7b86 +67 -0
- package/skills/project-update/history/blobs/7f063e79195a5c85c37b7260dd20c184d3b9bea85e37202e0bbca73ad84bba49 +13 -0
- package/skills/project-update/history/blobs/a51517e13e57420d2d88a6bcfb4055d762d4a0d10a8f88450929c5c4572213a9 +67 -0
- package/skills/project-update/history/blobs/c8573ca1f810ce6c4ac49456a4c1725b93e0d3b74c54ce95d46d8747777895f0 +13 -0
- package/skills/project-update/history/blobs/d6e5b34d475e7187d2a8cb2aac7e7f1a427142baf6461bda387d70ceb4d8a8c1 +30 -0
- package/skills/project-update/history/blobs/e120ff4e32b2e950fc94ada4e787d57dc9c790da37223e5a202d74f7e3aadf80 +13 -0
- package/skills/project-update/history/index.json +38 -16
- package/skills/project-update/migrations/0003_context_diet.py +1 -1
- package/skills/project-update/migrations/0007_weight_model.py +130 -23
- package/skills/project-update/migrations/0011_retire_risk_tiers.py +37 -0
- package/skills/project-update/migrations/README.md +16 -1
- package/skills/project-update/migrations/__init__.py +6 -1
- package/skills/project-update/render_instructions.py +1 -1
- package/skills/project-update/update.py +569 -106
- package/skills/rr-build-all/SKILL.md +2 -2
- package/skills/rr-plan/SKILL.md +3 -1
- package/skills/rr-review/SKILL.md +13 -8
- package/skills/rr-setup/SKILL.md +24 -4
- package/skills/rr-setup/setup.py +7 -1
- package/skills/rr-spec/SKILL.md +4 -2
- package/skills/rr-status/status.py +36 -9
- package/skills/usage-report/usage-report.py +4 -2
- package/skills/ai-init/templates/.ai/policies/risk-tiers.json +0 -628
- package/skills/ai-init/templates/.ai/policies/risk-tiers.md +0 -180
package/hooks/update_notice.py
CHANGED
|
@@ -28,6 +28,7 @@ import urllib.request
|
|
|
28
28
|
REGISTRY_URL = "https://registry.npmjs.org/riskrails/latest"
|
|
29
29
|
MAX_AGE = 24 * 3600
|
|
30
30
|
TIMEOUT = 3
|
|
31
|
+
MAX_BODY = 65536
|
|
31
32
|
COMMAND = "npx -y riskrails@latest --yes"
|
|
32
33
|
VERSION_RE = re.compile(r"^(\d+)\.(\d+)\.(\d+)$") # no prerelease, no build metadata
|
|
33
34
|
|
|
@@ -50,7 +51,7 @@ def installed_version(runtime, plugin_root, home=None):
|
|
|
50
51
|
try:
|
|
51
52
|
with open(path, encoding="utf-8") as fh:
|
|
52
53
|
version = json.load(fh).get(key)
|
|
53
|
-
except (OSError, ValueError, AttributeError):
|
|
54
|
+
except (OSError, ValueError, AttributeError, RecursionError):
|
|
54
55
|
continue
|
|
55
56
|
if parse(version):
|
|
56
57
|
return version
|
|
@@ -62,7 +63,7 @@ def read_cache(path):
|
|
|
62
63
|
with open(path, encoding="utf-8") as fh:
|
|
63
64
|
data = json.load(fh)
|
|
64
65
|
return data if isinstance(data, dict) else {}
|
|
65
|
-
except (OSError, ValueError):
|
|
66
|
+
except (OSError, ValueError, RecursionError):
|
|
66
67
|
return {}
|
|
67
68
|
|
|
68
69
|
|
|
@@ -103,20 +104,7 @@ def notice(runtime, home, plugin_root, now):
|
|
|
103
104
|
return message, context
|
|
104
105
|
|
|
105
106
|
|
|
106
|
-
def
|
|
107
|
-
"""Ask the registry and write the cache; on failure only move checked_at."""
|
|
108
|
-
latest = None
|
|
109
|
-
try:
|
|
110
|
-
url = os.environ.get("RISKRAILS_REGISTRY_URL") or REGISTRY_URL
|
|
111
|
-
with urllib.request.urlopen(url, timeout=TIMEOUT) as resp: # nosec: the registry, or a test's file URL
|
|
112
|
-
version = json.loads(resp.read().decode("utf-8")).get("version")
|
|
113
|
-
if parse(version):
|
|
114
|
-
latest = version
|
|
115
|
-
except (OSError, ValueError, AttributeError):
|
|
116
|
-
pass
|
|
117
|
-
if latest is None:
|
|
118
|
-
previous = read_cache(path).get("latest")
|
|
119
|
-
latest = previous if isinstance(previous, str) else None
|
|
107
|
+
def write_cache(path, latest):
|
|
120
108
|
tmp = f"{path}.{os.getpid()}.tmp"
|
|
121
109
|
try:
|
|
122
110
|
with open(tmp, "w", encoding="utf-8") as fh:
|
|
@@ -129,6 +117,24 @@ def refresh(path):
|
|
|
129
117
|
pass
|
|
130
118
|
|
|
131
119
|
|
|
120
|
+
def refresh(path):
|
|
121
|
+
"""Ask the registry and write the cache; checked_at moves first, so a fetch that hangs
|
|
122
|
+
or is killed still costs one spawn a day, and a failed fetch keeps the previous latest."""
|
|
123
|
+
previous = read_cache(path).get("latest")
|
|
124
|
+
previous = previous if isinstance(previous, str) else None
|
|
125
|
+
write_cache(path, previous)
|
|
126
|
+
latest = None
|
|
127
|
+
try:
|
|
128
|
+
url = os.environ.get("RISKRAILS_REGISTRY_URL") or REGISTRY_URL
|
|
129
|
+
with urllib.request.urlopen(url, timeout=TIMEOUT) as resp: # nosec: the registry, or a test's file URL
|
|
130
|
+
version = json.loads(resp.read(MAX_BODY).decode("utf-8")).get("version")
|
|
131
|
+
if parse(version):
|
|
132
|
+
latest = version
|
|
133
|
+
except (OSError, ValueError, AttributeError, RecursionError):
|
|
134
|
+
pass
|
|
135
|
+
write_cache(path, latest if latest is not None else previous)
|
|
136
|
+
|
|
137
|
+
|
|
132
138
|
if __name__ == "__main__":
|
|
133
139
|
if len(sys.argv) == 3 and sys.argv[1] == "--refresh" and not disabled():
|
|
134
140
|
refresh(sys.argv[2])
|
package/instructions/routing.md
CHANGED
|
@@ -46,7 +46,7 @@ Facts are collected cheaply; thinking is paid for. Session model {{SESSION_MODEL
|
|
|
46
46
|
# Guards
|
|
47
47
|
|
|
48
48
|
<!-- stub: runtime=claude -->
|
|
49
|
-
- `ai-git-guard` runs in every repository: no force push, history rewrite, push or merge to a protected branch, `--no-verify`, staged secret or production deploy. `ai-path-guard` and `ai-scope-guard` arm only where `.ai/` exists. No agent merges or deploys; commit, push and approval run only as the command the human picked in the dialog — verbatim, once — or the human runs them outside the agent.
|
|
49
|
+
- `ai-git-guard` runs in every repository: no force push, history rewrite, push or merge to a protected branch, `--no-verify`, staged secret or production deploy. `ai-path-guard` and `ai-scope-guard` arm only where `.ai/` exists. No agent merges or deploys; commit, push and approval run only as the command the human picked in the dialog, as the harness recorded — verbatim, once — or the human runs them outside the agent.
|
|
50
50
|
- `context-guard` measures the context on every prompt and holds a prompt back once at the block threshold above, when there is one; its "Session state before compaction" block and `.ai/state/handoff.md` are what a resumed session trusts over the summary.
|
|
51
51
|
|
|
52
52
|
<!-- stub: runtime=codex -->
|
package/instructions/stub.md
CHANGED
|
@@ -24,7 +24,7 @@ Rules for every repository; everything else loads on demand. `{{HOME}}/riskrails
|
|
|
24
24
|
- With `.ai/`: read `.ai/AGENTS.md` first, run every change through `/rr-build <request>`, treat `.ai/policies/` as binding; `/rr-setup --update` after a plugin reinstall. Without `.ai/`: `/rr-setup` first.
|
|
25
25
|
- Production behaviour is the source of truth: document problems outside the task, do not fix them. Never mix a refactoring with a feature change.
|
|
26
26
|
- Each step names the files it may touch; an edit outside them is refused — answer `SCOPE_CHANGE_REQUIRED`, do not widen the step.{{MULTI_EDIT}}
|
|
27
|
-
- No agent merges or deploys. Commit, push and approval run only as the command the human picked in the dialog — verbatim, once — or the human runs them outside the agent. A guard's refusal is not a hint to reword.{{TRUST}}
|
|
27
|
+
- No agent merges or deploys. Commit, push and approval run only as the command the human picked in the dialog, as the harness recorded — verbatim, once — or the human runs them outside the agent. A guard's refusal is not a hint to reword.{{TRUST}}
|
|
28
28
|
- Verify before reporting done: the verification command once, to the end, every failure fixed as one batch, output shown.
|
|
29
29
|
- Context: `grep -n`, then read the hits; never `cat` logs, lockfiles or generated code; a file over ~{{READ_LINES}} lines is read by a FAST reader (`Explore`, `log-reader`) that returns `file:line` excerpts, never the file. Deterministic tools first. {{NEW_CONTEXT}} between tasks. `/rr-status --cost` shows where tokens went.
|
|
30
30
|
- Models are tiers, not names: runners and verbatim extraction on FAST at `low`, readers that judge on BALANCED; the session implements; STRONG and EXPERT only on a named trigger, one agent at a time, never a fleet on the top tier; a failed thinking task is never retried on a cheaper model.
|
|
@@ -47,8 +47,8 @@ This repository runs an agentic pipeline under `.ai/`. Read `.ai/AGENTS.md` firs
|
|
|
47
47
|
|
|
48
48
|
<!-- stub: scope=project runtime=* -->
|
|
49
49
|
- 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.
|
|
50
|
-
- 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.
|
|
51
|
-
- 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
|
|
50
|
+
- No agent merges or deploys; commit, push, approval only as the dialog picks and the harness records, else a human outside. When a review flags the same mistake twice, the correction goes into this file.
|
|
51
|
+
- 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`.
|
|
52
52
|
|
|
53
53
|
<!-- stub: scope=skeleton,skeleton-sdlc runtime=* -->
|
|
54
54
|
# {{PROJECT}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "riskrails",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.1",
|
|
4
4
|
"description": "RiskRails: a weight-based engineering pipeline for Claude Code and Codex — scoped steps, enforcement hooks, adversarial review, model routing.",
|
|
5
5
|
"keywords": ["claude-code", "codex", "sdlc", "agents", "guardrails", "code-review"],
|
|
6
6
|
"homepage": "https://github.com/vanssata/claude-agentic-sdlc-module#readme",
|
package/skills/AGENTS.md
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
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
|
-
- A change to a shipped template or to `ai/` runs `tools/build-template-history.py` in the same task, or `tests/
|
|
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.py` (`test_history_covers_every_template`) goes red.
|
|
6
6
|
<!-- riskrails:rule:skills:end -->
|
package/skills/CLAUDE.md
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
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
|
-
- A change to a shipped template or to `ai/` runs `tools/build-template-history.py` in the same task, or `tests/
|
|
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.py` (`test_history_covers_every_template`) goes red.
|
|
6
6
|
<!-- riskrails:rule:skills:end -->
|
|
@@ -31,7 +31,7 @@ Everything else loads on demand; nothing here is worth reading twice.
|
|
|
31
31
|
## Shared files
|
|
32
32
|
|
|
33
33
|
This `.ai/` holds `project/`, `rules/`, `state/`, `reports/`, `VERSION` and, in
|
|
34
|
-
`policies/`, `
|
|
34
|
+
`policies/`, `weight.json`, `weight.md`, `path-guard.json`, `testing.md`,
|
|
35
35
|
`coding.md`, `safety.md` and `security.md`. Every other path below ships with the
|
|
36
36
|
plugin at `<home>/riskrails/ai/<path>`; `<home>` is `~/.claude`, or
|
|
37
37
|
`$CODEX_HOME` (default `~/.codex`). A file at the same path here overrides it and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
11
|
|
@@ -57,12 +57,18 @@ it, add its pattern to `deny_patterns` (a `dependency_instruction_patterns`
|
|
|
57
57
|
list from an older `path-guard.json` is read as `deny_patterns`).
|
|
58
58
|
|
|
59
59
|
Always read-only for an agent: the task's state under `.ai/state/`, its
|
|
60
|
-
`task.md` and `events.jsonl`, `.ai/policies/*.json`,
|
|
60
|
+
`task.md` and `events.jsonl`, `.ai/policies/*.json`, the compact
|
|
61
|
+
record under `.ai/memory/` and `.ai/local/memory/` (written by `state.py` alone),
|
|
62
|
+
and the installed guards —
|
|
61
63
|
their hooks, engine and `skills/ai-task/` under `.claude/` and `.codex/`. While a
|
|
62
64
|
task is in flight the runtime's settings and this project's `.ai/policies/` are
|
|
63
65
|
read-only too. Changing the rules a run is judged by, inside that run, is not a
|
|
64
66
|
change an agent may make on its own.
|
|
65
67
|
|
|
68
|
+
A review's evidence for a closed task is its record — `state.py memory show <id>`
|
|
69
|
+
and `recall` — once cleanup has moved the report directory; an approval in the
|
|
70
|
+
record authorises nothing.
|
|
71
|
+
|
|
66
72
|
Project-specific additions go in `.ai/policies/path-guard.json`
|
|
67
73
|
(`deny_patterns`, `allow_patterns`, `protected_config_patterns`,
|
|
68
74
|
`task_protected_patterns`; allow wins over all of them). When a deny is wrong, widen the allow list in that file — do not
|
|
@@ -47,13 +47,15 @@ else runs in between.
|
|
|
47
47
|
which, in one line; silence is not a verdict.
|
|
48
48
|
- A failure in a step's own tests is fixed in that step, before `step-done`;
|
|
49
49
|
the batch rule below is for the two end-of-task runs.
|
|
50
|
+
- After a green suite, a documents-only edit runs `doc_tests` (step scope), not the suite.
|
|
50
51
|
|
|
51
52
|
## Reading a failure
|
|
52
53
|
|
|
53
54
|
**Run to the end, then fix as one batch.** The verification command runs with
|
|
54
55
|
no fail-fast flag and is never interrupted at the first failure. Collect every
|
|
55
|
-
failure, classify each one, fix all the new regressions in
|
|
56
|
-
|
|
56
|
+
failure, classify each one, fix all the new regressions in one batch
|
|
57
|
+
(`state.py start --amend` widens the task when a fix needs more files), and run the
|
|
58
|
+
command once more. One failure never
|
|
57
59
|
sends the task back to the start: the plan, the tier and the finished steps
|
|
58
60
|
stand. At most two remediation rounds; a third means the human decides.
|
|
59
61
|
|
|
@@ -66,6 +68,9 @@ Classify before fixing. The four answers are:
|
|
|
66
68
|
is missing.
|
|
67
69
|
- **UNKNOWN** — you could not tell. Say what you tried.
|
|
68
70
|
|
|
71
|
+
A review's MEDIUM and LOW findings are recorded with `state.py memory risk add`, not
|
|
72
|
+
fixed in the task; only a BLOCKER, a HIGH or a regression buys an `--after-fix` run.
|
|
73
|
+
|
|
69
74
|
**Never edit a test to make it pass when production behaviour changed
|
|
70
75
|
unexpectedly.** A test that suddenly disagrees with the code is evidence, and
|
|
71
76
|
deleting evidence is the worst available option.
|
|
@@ -88,6 +93,7 @@ verify_command: # the full fast suite, e2e excluded — e.g. make test |
|
|
|
88
93
|
step_test_command: # the same runner, scoped to a path or filter — e.g. vendor/bin/phpunit {files} | pytest {files}
|
|
89
94
|
e2e_command: # the end-to-end suite, run once at the end of a task — e.g. make e2e | vendor/bin/behat | npx playwright test
|
|
90
95
|
lint_command: # e.g. vendor/bin/php-cs-fixer fix --dry-run | ruff check . | npm run lint — write `none` if the project has none
|
|
96
|
+
doc_tests: # the suites a documents-only edit runs after a green suite — e.g. tests/test_docs.py; `none` if the project has none
|
|
91
97
|
typecheck_command: # e.g. vendor/bin/phpstan analyse --no-progress | mypy . | npx tsc --noEmit — `none` if the project has none
|
|
92
98
|
healthy_output: # two or three lines of what a green run ends with
|
|
93
99
|
runtime: # roughly, for each command, so a hang is recognisable
|
|
@@ -99,12 +105,19 @@ step names; without it the paths are appended. The placeholder is what lets
|
|
|
99
105
|
`sensors.py` run a step's own tests for the "test must bite" check, so a runner
|
|
100
106
|
that only takes a `--filter` gets `bite: unavailable` rather than a wrong answer.
|
|
101
107
|
|
|
108
|
+
`bite_codes` (optional) lists the exit codes that mean "a test failed" when a
|
|
109
|
+
test is shown failing before its fix; the default is `1`. Runners that exit
|
|
110
|
+
differently say so here — `bite_codes: 1,2` for pytest (2 is a collection
|
|
111
|
+
error, such as a missing import), `1,2` for phpunit, `101` for cargo. Any other
|
|
112
|
+
code is the runner's own usage error and does not count as a bite.
|
|
113
|
+
|
|
102
114
|
`lint_command` and `typecheck_command` are read by the deterministic sensors,
|
|
103
|
-
never by a model. They decide, together with the test run, whether a
|
|
104
|
-
can skip its model review (`
|
|
115
|
+
never by a model. They decide, together with the test run, whether a light-mode change
|
|
116
|
+
can skip its model review (`state.py close` measures them). A **missing** line is
|
|
105
117
|
`unavailable` and keeps the review; only the literal `none`, written by a human,
|
|
106
|
-
says the project has no such tool.
|
|
107
|
-
|
|
118
|
+
says the project has no such tool. Read the exact line from the project's
|
|
119
|
+
config files (`package.json`, `composer.json`, `pyproject.toml`, `Makefile`), and
|
|
120
|
+
never run a command just to find it out.
|
|
108
121
|
|
|
109
122
|
If the project has no e2e suite, write `e2e_command: none` and say so — an
|
|
110
123
|
empty line is read as "not written down yet" and the next task will go looking
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Task state
|
|
2
2
|
|
|
3
3
|
`state.json` is the machine-readable record of the task in flight. It is
|
|
4
|
-
written by `skills/ai-task/state.py` (atomically), read by `/
|
|
4
|
+
written by `skills/ai-task/state.py` (atomically), read by `/rr-status`, and
|
|
5
5
|
enforced by `ai-scope-guard`, which derives the files the current step may touch
|
|
6
6
|
from it.
|
|
7
7
|
|
|
8
8
|
Two other writers exist, and only these two. A schema migration in
|
|
9
|
-
`/
|
|
9
|
+
`/rr-setup --update` may change the state through `ctx.patch_state()`, with the
|
|
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
|
|
@@ -14,7 +14,7 @@ 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
|
-
`.gitignore` entry added by `/
|
|
17
|
+
`.gitignore` entry added by `/rr-setup` keeps it out of git.
|
|
18
18
|
|
|
19
19
|
When a task closes, `state.py close` writes the final state to
|
|
20
20
|
`.ai/state/logs/<task-id>/state.json`, beside the task's verify logs — local,
|
|
@@ -37,7 +37,7 @@ created_at, updated_at, history[]
|
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
`state.py` applies the schema-2 keys in memory as it loads, so a project that has
|
|
40
|
-
not run `/
|
|
40
|
+
not run `/rr-setup --update` yet still answers every command; `save()` then writes
|
|
41
41
|
them. A reader that opens this file directly sees what is on disk, not those
|
|
42
42
|
defaults.
|
|
43
43
|
|
|
@@ -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 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
|
-
- 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
|
|
11
|
+
- No agent merges or deploys; commit, push, approval only as the dialog picks and the harness records, else a human outside. When a review flags the same mistake twice, the correction goes into this file.
|
|
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`.
|
|
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 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
|
-
- 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
|
|
11
|
+
- No agent merges or deploys; commit, push, approval only as the dialog picks and the harness records, else a human outside. When a review flags the same mistake twice, the correction goes into this file.
|
|
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`.
|
|
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 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
|
-
- 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
|
|
11
|
+
- No agent merges or deploys; commit, push, approval only as the dialog picks and the harness records, else a human outside. When a review flags the same mistake twice, the correction goes into this file.
|
|
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`.
|
|
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 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
|
-
- 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
|
|
11
|
+
- No agent merges or deploys; commit, push, approval only as the dialog picks and the harness records, else a human outside. When a review flags the same mistake twice, the correction goes into this file.
|
|
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`.
|
|
13
13
|
<!-- riskrails:end -->
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""rr_state: the task state machine behind skills/ai-task/state.py."""
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""cli: the argument parser and the dispatch -- the one place that resolves the runtime and builds the Runtime every command receives.
|
|
2
|
+
|
|
3
|
+
shell: BLOCKING_COMMANDS, MUTATING_COMMANDS, DIALOG_SYNC_COMMANDS, memory_parsers, main
|
|
4
|
+
"""
|
|
5
|
+
import argparse
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
from rr_state import close, dialog, dialog_cmd, fs, gate, gate_cmd, memory_cmd, policy, questions_cmd
|
|
9
|
+
from rr_state import report_cmd, review, start, steps, task, verify
|
|
10
|
+
from rr_state import runtime as rr_runtime
|
|
11
|
+
|
|
12
|
+
# A pending question stops a stage from moving; it never stops a command that
|
|
13
|
+
# records, reads or answers (R3). close --abandon is exempt: abandoning a task is
|
|
14
|
+
# how an unanswerable question is closed.
|
|
15
|
+
BLOCKING_COMMANDS = {"step-done", "approve", "close", "verify"}
|
|
16
|
+
|
|
17
|
+
# The commands that change the task. Only these take ownership of it: reading a
|
|
18
|
+
# task from the other runtime, or writing a note about it, is not a handoff.
|
|
19
|
+
MUTATING_COMMANDS = {"step-done", "ask", "answer", "close", "start", "verify", "review", "batch"}
|
|
20
|
+
# What prunes decision.json and mirrors consent.jsonl first (R36.14, R36.24):
|
|
21
|
+
# every command that changes something; status and consent stay read-only.
|
|
22
|
+
DIALOG_SYNC_COMMANDS = MUTATING_COMMANDS | {"approve", "reject", "decision"}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def memory_parsers(sub):
|
|
26
|
+
p = sub.add_parser("recall", help="what the record holds about these files, within a token budget")
|
|
27
|
+
p.add_argument("--files", default=""); p.add_argument("--budget", type=int)
|
|
28
|
+
p.add_argument("--kinds", default=""); p.add_argument("--all-risks", dest="all_risks", action="store_true")
|
|
29
|
+
p.add_argument("--offset", type=int, default=0); p.add_argument("--json", action="store_true")
|
|
30
|
+
p.set_defaults(func=memory_cmd.cmd_recall)
|
|
31
|
+
p = sub.add_parser("memory", help="the compact project record (docs/local-memory.md)")
|
|
32
|
+
p.set_defaults(func=memory_cmd.cmd_memory)
|
|
33
|
+
msub = p.add_subparsers(dest="memory_action", required=True)
|
|
34
|
+
m = msub.add_parser("status"); m.add_argument("--json", action="store_true")
|
|
35
|
+
m = msub.add_parser("enable"); m.add_argument("--author"); m.add_argument("--period", choices=["month", "quarter", "year"])
|
|
36
|
+
m.add_argument("--index", choices=["none", "sqlite"])
|
|
37
|
+
msub.add_parser("disable")
|
|
38
|
+
m = msub.add_parser("export"); m.add_argument("ids", nargs="*"); m.add_argument("--pending", action="store_true")
|
|
39
|
+
m = msub.add_parser("verify"); m.add_argument("ids", nargs="*"); m.add_argument("--all", action="store_true")
|
|
40
|
+
m.add_argument("--range"); m.add_argument("--fresh-clone", dest="fresh_clone", action="store_true")
|
|
41
|
+
m.add_argument("--json", action="store_true")
|
|
42
|
+
m = msub.add_parser("show"); m.add_argument("id"); m.add_argument("--md", action="store_true")
|
|
43
|
+
m.add_argument("--json", action="store_true"); m.add_argument("--entry")
|
|
44
|
+
m = msub.add_parser("search"); m.add_argument("query", nargs="?", default="")
|
|
45
|
+
for flag in ("--kind", "--by", "--file", "--task", "--since", "--until"):
|
|
46
|
+
m.add_argument(flag)
|
|
47
|
+
m.add_argument("--limit", type=int, default=20); m.add_argument("--offset", type=int, default=0)
|
|
48
|
+
m.add_argument("--json", action="store_true")
|
|
49
|
+
m = msub.add_parser("risk"); rsub = m.add_subparsers(dest="risk_action", required=True)
|
|
50
|
+
r = rsub.add_parser("add"); r.add_argument("--title", required=True); r.add_argument("--where", default="")
|
|
51
|
+
r.add_argument("--label", required=True); r.add_argument("--what", required=True)
|
|
52
|
+
r.add_argument("--likely"); r.add_argument("--fix"); r.add_argument("--status", default="open")
|
|
53
|
+
r = rsub.add_parser("close"); r.add_argument("rkey"); r.add_argument("--status", required=True)
|
|
54
|
+
r.add_argument("--why", required=True)
|
|
55
|
+
m = msub.add_parser("risks"); m.add_argument("--status", choices=["open", "ticketed", "accepted", "fixed", "all"],
|
|
56
|
+
default="open")
|
|
57
|
+
m.add_argument("--md", action="store_true"); m.add_argument("--json", action="store_true")
|
|
58
|
+
m = msub.add_parser("migrate"); g = m.add_mutually_exclusive_group(required=True)
|
|
59
|
+
g.add_argument("--report", action="store_true"); g.add_argument("--apply", action="store_true")
|
|
60
|
+
g.add_argument("--move-logs", dest="move_logs", action="store_true")
|
|
61
|
+
m.add_argument("--risks", action="store_true"); m.add_argument("--confirm-delete", dest="confirm_delete")
|
|
62
|
+
msub.add_parser("rebuild")
|
|
63
|
+
m = msub.add_parser("cleanup"); m.add_argument("ids", nargs="*"); m.add_argument("--all", action="store_true")
|
|
64
|
+
m.add_argument("--confirm-delete", dest="confirm_delete")
|
|
65
|
+
m = msub.add_parser("restore"); m.add_argument("ids", nargs="*"); m.add_argument("--all", action="store_true")
|
|
66
|
+
m = msub.add_parser("pack"); m.add_argument("--period", required=True)
|
|
67
|
+
m.add_argument("--confirm-delete", dest="confirm_delete")
|
|
68
|
+
m = msub.add_parser("redact"); m.add_argument("rid"); m.add_argument("--field", required=True)
|
|
69
|
+
m.add_argument("--why", required=True)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def main():
|
|
73
|
+
# description: the first line of state.py's docstring, which argparse printed as __doc__ before the split
|
|
74
|
+
parser = argparse.ArgumentParser(
|
|
75
|
+
description="Task state for /ai-task — the small, machine-readable record of one task.")
|
|
76
|
+
parser.add_argument("--root", default=".", help="project directory (default: search upwards for .ai/)")
|
|
77
|
+
parser.add_argument("--runtime", choices=rr_runtime.RUNTIMES,
|
|
78
|
+
help="overrides AI_RUNTIME and detection for this call")
|
|
79
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
80
|
+
|
|
81
|
+
p = sub.add_parser("step-done"); p.add_argument("step_id")
|
|
82
|
+
p.add_argument("--force", action="store_true",
|
|
83
|
+
help="accepted for older callers; it forces nothing")
|
|
84
|
+
p.set_defaults(func=steps.cmd_step_done_v7)
|
|
85
|
+
|
|
86
|
+
who = os.environ.get("USER") or "unknown"
|
|
87
|
+
p = sub.add_parser("ask"); p.add_argument("question", nargs="?")
|
|
88
|
+
p.add_argument("--option", action="append", default=[]); p.add_argument("--recommend")
|
|
89
|
+
p.add_argument("--context", default=""); p.add_argument("--gate")
|
|
90
|
+
p.add_argument("--batch"); p.add_argument("--topic"); p.add_argument("--by", default="")
|
|
91
|
+
p.set_defaults(func=questions_cmd.cmd_ask)
|
|
92
|
+
|
|
93
|
+
p = sub.add_parser("answer"); p.add_argument("assignment", nargs="*")
|
|
94
|
+
p.add_argument("--prose"); p.add_argument("--by", default=who)
|
|
95
|
+
# `file` is not offered: it is what --sync writes, and it is the one route
|
|
96
|
+
# that is recorded as a human's.
|
|
97
|
+
p.add_argument("--via", choices=["picker", "prose"], default="prose")
|
|
98
|
+
p.add_argument("--topic"); p.set_defaults(func=questions_cmd.cmd_answer)
|
|
99
|
+
|
|
100
|
+
p = sub.add_parser("questions"); p.add_argument("--pending", action="store_true")
|
|
101
|
+
p.add_argument("--sync", action="store_true"); p.add_argument("--topic")
|
|
102
|
+
p.add_argument("--by", default=who)
|
|
103
|
+
p.add_argument("--format", choices=["md", "prose", "json"], default="prose")
|
|
104
|
+
p.set_defaults(func=questions_cmd.cmd_questions)
|
|
105
|
+
|
|
106
|
+
p = sub.add_parser("handoff")
|
|
107
|
+
p.add_argument("--print", dest="print_it", action="store_true")
|
|
108
|
+
p.add_argument("--reason", choices=["stage", "precompact", "manual", "session-start"],
|
|
109
|
+
default="manual")
|
|
110
|
+
p.add_argument("--to", choices=rr_runtime.RUNTIMES)
|
|
111
|
+
p.add_argument("--for", dest="for_", choices=["continue", "review"], default="continue")
|
|
112
|
+
p.add_argument("--why", default="")
|
|
113
|
+
p.set_defaults(func=report_cmd.cmd_handoff)
|
|
114
|
+
|
|
115
|
+
p = sub.add_parser("profile"); p.add_argument("action", nargs="?", choices=["detect", "set", "ack"])
|
|
116
|
+
p.add_argument("plan", nargs="?")
|
|
117
|
+
p.add_argument("--field")
|
|
118
|
+
p.add_argument("--runtime", dest="detect_runtime", choices=rr_runtime.RUNTIMES)
|
|
119
|
+
p.add_argument("--open", action="store_true")
|
|
120
|
+
p.add_argument("--tier", choices=["FAST", "BALANCED", "STRONG", "EXPERT"])
|
|
121
|
+
p.add_argument("--other", action="store_true"); p.add_argument("--json", action="store_true")
|
|
122
|
+
p.set_defaults(func=report_cmd.cmd_profile)
|
|
123
|
+
|
|
124
|
+
p = sub.add_parser("weight"); p.add_argument("--files", default="")
|
|
125
|
+
p.add_argument("--goal", default=""); p.add_argument("--declare", action="append", default=[])
|
|
126
|
+
out = p.add_mutually_exclusive_group()
|
|
127
|
+
out.add_argument("--json", action="store_true"); out.add_argument("--long", action="store_true")
|
|
128
|
+
out.add_argument("--mirror", action="store_true"); p.set_defaults(func=start.cmd_weight)
|
|
129
|
+
|
|
130
|
+
p = sub.add_parser("event"); p.add_argument("type"); p.add_argument("--detail", default="")
|
|
131
|
+
p.add_argument("--data"); p.set_defaults(func=report_cmd.cmd_event)
|
|
132
|
+
|
|
133
|
+
p = sub.add_parser("approve"); p.add_argument("--by", required=True)
|
|
134
|
+
p.add_argument("--note", default="")
|
|
135
|
+
p.add_argument("--confirm", default="", help="the operation a full_irreversible task performs")
|
|
136
|
+
p.set_defaults(func=gate_cmd.cmd_approve_v7)
|
|
137
|
+
|
|
138
|
+
p = sub.add_parser("reject"); p.add_argument("--by", required=True)
|
|
139
|
+
p.add_argument("--why", required=True); p.set_defaults(func=gate_cmd.cmd_reject)
|
|
140
|
+
|
|
141
|
+
p = sub.add_parser("close")
|
|
142
|
+
p.add_argument("--abandon", action="store_true", help="archive without the checks")
|
|
143
|
+
p.set_defaults(func=close.cmd_close_v7)
|
|
144
|
+
|
|
145
|
+
p = sub.add_parser("start"); p.add_argument("--goal", default="")
|
|
146
|
+
p.add_argument("--workflow", default=""); p.add_argument("--files", default="")
|
|
147
|
+
p.add_argument("--tests", default=""); p.add_argument("--declare", action="append", default=[])
|
|
148
|
+
p.add_argument("--mode"); p.add_argument("--by", default=""); p.add_argument("--why", default="")
|
|
149
|
+
p.add_argument("--steps", default=""); p.add_argument("--plan-file", dest="plan_file")
|
|
150
|
+
p.add_argument("--task-id", help="an id of your own; a legacy T-YYYY-MM-DD-NNN id is still accepted")
|
|
151
|
+
p.add_argument("--slug", help="3-24 characters of [a-z0-9] words joined by '-': the id becomes "
|
|
152
|
+
"T-YYYY-MM-DD-HHMM-<slug> (UTC). Default: derived from --goal")
|
|
153
|
+
p.add_argument("--amend", action="store_true")
|
|
154
|
+
p.add_argument("--batch", action="store_true", help="the batch loop of /rr-build-all (needs --by)")
|
|
155
|
+
p.add_argument("--no-batch", dest="no_batch", action="store_true", help="opt out of weight.json batch.default")
|
|
156
|
+
p.set_defaults(func=start.cmd_start)
|
|
157
|
+
|
|
158
|
+
p = sub.add_parser("steps"); p.add_argument("--ready", action="store_true")
|
|
159
|
+
p.add_argument("--json", action="store_true"); p.set_defaults(func=steps.cmd_steps)
|
|
160
|
+
|
|
161
|
+
p = sub.add_parser("batch"); p.add_argument("action", choices=["on", "off"])
|
|
162
|
+
p.add_argument("--by", default=""); p.add_argument("--why", default="")
|
|
163
|
+
p.set_defaults(func=steps.cmd_batch)
|
|
164
|
+
|
|
165
|
+
p = sub.add_parser("verify")
|
|
166
|
+
p.add_argument("--scope", required=True, choices=["step", "suite", "e2e", "all"])
|
|
167
|
+
p.add_argument("--files", default=""); p.add_argument("--expect", choices=["fail"])
|
|
168
|
+
p.add_argument("--e2e", nargs=2, metavar=("not-applicable", "WHY"))
|
|
169
|
+
p.add_argument("--after-fix", dest="after_fix", default="")
|
|
170
|
+
p.add_argument("--by", default=""); p.set_defaults(func=verify.cmd_verify)
|
|
171
|
+
|
|
172
|
+
p = sub.add_parser("review"); p.add_argument("--verdict", choices=sorted(policy.VERDICT_STATUS))
|
|
173
|
+
p.add_argument("--tier", choices=review.REVIEW_TIERS); p.add_argument("--focus", choices=["security"])
|
|
174
|
+
p.add_argument("--report", default=""); p.add_argument("--closes", default="")
|
|
175
|
+
p.add_argument("--by", default=""); p.set_defaults(func=review.cmd_review)
|
|
176
|
+
|
|
177
|
+
p = sub.add_parser("status"); p.add_argument("--json", action="store_true")
|
|
178
|
+
p.set_defaults(func=report_cmd.cmd_status)
|
|
179
|
+
|
|
180
|
+
p = sub.add_parser("decision"); p.add_argument("action", choices=["show", "open", "settle", "fallback"])
|
|
181
|
+
p.add_argument("--id"); p.add_argument("--kind"); p.add_argument("--command", dest="commands", action="append", default=[])
|
|
182
|
+
p.add_argument("--pick", type=int); p.add_argument("--why", default="")
|
|
183
|
+
p.add_argument("--json", action="store_true"); p.set_defaults(func=dialog_cmd.cmd_decision)
|
|
184
|
+
|
|
185
|
+
p = sub.add_parser("consent"); p.add_argument("action", nargs="?", choices=["audit"])
|
|
186
|
+
p.add_argument("--json", action="store_true"); p.set_defaults(func=dialog_cmd.cmd_consent)
|
|
187
|
+
|
|
188
|
+
memory_parsers(sub)
|
|
189
|
+
|
|
190
|
+
args = parser.parse_args()
|
|
191
|
+
if getattr(args, "topic", None):
|
|
192
|
+
# Topic mode never touches .ai/: it must work where there is none (I11).
|
|
193
|
+
args.func(args, fs.find_docs_root(args.root), rt=rr_runtime.Runtime())
|
|
194
|
+
return
|
|
195
|
+
if args.command == "profile":
|
|
196
|
+
# Read-only and task-free: it answers where there is no .ai/ at all.
|
|
197
|
+
name = args.runtime or os.environ.get("AI_RUNTIME") or \
|
|
198
|
+
("claude" if os.environ.get("CLAUDECODE") else "unknown")
|
|
199
|
+
args.func(args, None, rt=rr_runtime.Runtime(name=name))
|
|
200
|
+
return
|
|
201
|
+
root = fs.find_root(args.root)
|
|
202
|
+
rt = rr_runtime.Runtime(name=rr_runtime.detect_runtime(args.runtime, root),
|
|
203
|
+
mutating=args.command in MUTATING_COMMANDS)
|
|
204
|
+
task.route_surface(root, args, rt=rt)
|
|
205
|
+
if args.command in BLOCKING_COMMANDS and not getattr(args, "abandon", False):
|
|
206
|
+
gate.guard_pending(root, args.command, rt=rt)
|
|
207
|
+
# After the guard: an exit-4 refusal leaves every file as it was.
|
|
208
|
+
if args.command in DIALOG_SYNC_COMMANDS:
|
|
209
|
+
dialog.sync_dialog(root, rt=rt)
|
|
210
|
+
args.func(args, root, rt=rt)
|