riskrails 3.2.1 → 4.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 +1 -1
- package/README.md +40 -83
- package/agents/ai-reviewer.md.tmpl +1 -1
- package/ai/using-riskrails.md +0 -3
- package/bin/riskrails.js +13 -59
- package/hooks/ai-path-guard-defaults.json +1 -1
- package/hooks/context-guard.py +2 -4
- package/hooks/guard/paths.py +3 -6
- package/hooks/guard/scope.py +1 -3
- package/hooks/runtime-gate.py +18 -58
- package/instructions/routing.md +2 -2
- package/package.json +1 -1
- package/scripts/ai-path +1 -1
- package/scripts/resolve-profile.py +0 -3
- package/skills/ai-audit/templates/ai-sdlc-adoption-plan.md +1 -1
- package/skills/ai-audit/templates/play-scoring-rubric.md +1 -1
- package/skills/ai-init/templates/.ai/AGENTS.md +6 -6
- package/skills/ai-init/templates/.ai/VERSION +1 -1
- package/skills/ai-init/templates/.ai/policies/security.md +1 -2
- package/skills/ai-init/templates/.ai/policies/testing.md +5 -12
- package/skills/ai-init/templates/.ai/policies/verification.md +18 -0
- package/skills/ai-init/templates/.ai/policies/weight.json +0 -1
- package/skills/ai-init/templates/.ai/policies/weight.md +1 -2
- package/skills/ai-init/templates/.ai/policies/weight.project.json +4 -0
- package/skills/ai-init/templates/.ai/project/architecture.md +1 -1
- package/skills/ai-init/templates/.ai/project/initial-assessment.md +2 -2
- package/skills/ai-init/templates/.ai/project/overview.md +1 -1
- package/skills/ai-init/templates/.ai/rules/README.md +2 -2
- package/skills/ai-init/templates/.ai/state/README.md +1 -1
- package/skills/ai-task/memory_store.py +8 -9
- package/skills/ai-task/rr_state/cli.py +1 -1
- package/skills/ai-task/rr_state/dialog.py +2 -4
- package/skills/ai-task/rr_state/fs.py +5 -13
- package/skills/ai-task/rr_state/profile.py +2 -4
- package/skills/ai-task/rr_state/questions_cmd.py +1 -1
- package/skills/ai-task/rr_state/report.py +3 -4
- package/skills/ai-task/rr_state/runtime.py +1 -1
- package/skills/ai-task/rr_state/start.py +7 -7
- package/skills/ai-task/sensors.py +10 -11
- package/skills/ai-task/state.py +4 -24
- package/skills/ai-task/weight.py +17 -17
- package/skills/ai-task/weight_overlay.py +38 -0
- package/skills/project-init/templates/constitution.md +2 -2
- package/skills/project-update/history/blobs/00f5e27ce2d66e3a1d8b222b013ae8fa1fed32612d91a551e0c661cee3ef9c2d +34 -0
- package/skills/project-update/history/blobs/0a6b1b6dd06a35582b2e9989a1f41c40abc29254b8e482714fcc6f0a7b16c406 +32 -0
- package/skills/project-update/history/blobs/0aeb04b04262683cac6513545683486a73dc473b447bba43b5aaab18403957a6 +34 -0
- package/skills/project-update/history/blobs/0ce913a28996f546f72d570b0e5a376362e993ce4d805bc4aae57d38caa44212 +41 -0
- package/skills/project-update/history/blobs/0ea9e36446b624b73b2a821231b2ee66a357b43d584a5b23da48b3c800855e65 +41 -0
- package/skills/project-update/history/blobs/0fb1d68678a1349195b2e79433bb20174d25874e3bfd0b70410bb4e428b4e4b8 +35 -0
- package/skills/project-update/history/blobs/119b2b9d0f9c647a243540724edac6b29afa020c69aa205797bcfa9eb06bc021 +35 -0
- package/skills/project-update/history/blobs/1d0fe3ee5fb8260ba610942d4bbdfff7410fbe80ed826ac07f32985031dc2b3f +34 -0
- package/skills/project-update/history/blobs/1de9a37ed6402893dcab6e61ddbf15ffd35f15a6d84b18e675a0c5bf4e981f85 +42 -0
- package/skills/project-update/history/blobs/1f44e1a236233dcbab58064f39b69f798b35d1d9a29f8c615b5b12854ce633cb +15 -0
- package/skills/project-update/history/blobs/1f824645299579cbdd8141642ddbb4bca9fd5fe9d3112f036d8221f0811e0abf +40 -0
- package/skills/project-update/history/blobs/235fb88393cfe20dd58b35fe9785cc45baf5ffe21a13d926652d76ec68cea8b7 +35 -0
- package/skills/project-update/history/blobs/3918b4ee011d9477b9d2779e2893ab599749c406d4b09439a9748b64fbff58f3 +18 -0
- package/skills/project-update/history/blobs/449273200dfec2a56885c881ff34d40e8ecc00da53c24c2d09b7fbeb1004d255 +40 -0
- package/skills/project-update/history/blobs/4c45aee9b9fc5f6b4e1d9739e434c4094975a5a8dec6b9703fb25f3080680c77 +124 -0
- package/skills/project-update/history/blobs/4dd3fa12bc45acc8c836ab65d790248ca7504a021bca6677499d17bb3d798ed9 +64 -0
- package/skills/project-update/history/blobs/51e3f15095d7123ea8566b512479ee10a6cb2f336b7352a259a083a887bd7116 +33 -0
- package/skills/project-update/history/blobs/5497a204967bea7dab29079a6e1dfa3d8713b2d2cb294c2779378039f03a55fc +36 -0
- package/skills/project-update/history/blobs/60c4d7e7fde7049d92d84295fc13ca9a91eb2de423be08f093b3ee5d5e817ff8 +64 -0
- package/skills/project-update/history/blobs/6221b7c14cc04634e485e562d464c6860d45666a7f63e293c06bb2944ff556cc +27 -0
- package/skills/project-update/history/blobs/637971cfb844ef4285e2886a4b1c834fb2ed5c46e773adb88c2c147fea626ad3 +33 -0
- package/skills/project-update/history/blobs/65af2282eea2a51fcc27a4acf2c26c727b0961c30e1be13176369200c460c219 +34 -0
- package/skills/project-update/history/blobs/66ecd73e303e7bbc1aa8ee72435d0d10812ff7b2eff0adac59bbf0a303e2cc01 +41 -0
- package/skills/project-update/history/blobs/6a43bf32461b22b87fafe2865529dc04fa66cd00182a2a7598aa071667ca255a +35 -0
- package/skills/project-update/history/blobs/6d6dee8634abf9ac057bb206828b83a893a2afcf4f86bbe69e839a3b47d0adde +48 -0
- package/skills/project-update/history/blobs/7224ede803ea81e65a456e21b6028a0e848e8a880456c6503eebe7534ca4f6aa +74 -0
- package/skills/project-update/history/blobs/765a39a202617096c232017822ae738d37e4405430c71700dc7bc14bf1ba794e +33 -0
- package/skills/project-update/history/blobs/77577440a933d1cabd8c6f4a6401cf361a784aa09a25500a6f7b44ead8ab5e55 +41 -0
- package/skills/project-update/history/blobs/7bae90789625d6fb9071ecb412d14a8d1eb0bc5659d8d284c4eaf213e1b5de56 +67 -0
- package/skills/project-update/history/blobs/7bcdf9a0e6b85277b9b60669150224b9414d2e32821ff06f4d148eb1d18785bb +42 -0
- package/skills/project-update/history/blobs/7f834103e9d97aea44e76717bcdcbbe349c560e5b81a42ce85a1cec341041281 +4 -0
- package/skills/project-update/history/blobs/7fb3acb0211818efebc2234863db0b8c846022a7280a6d2d69f267dbf63db48c +34 -0
- package/skills/project-update/history/blobs/87f0855859ff68cc6c988cc0b631ee7f56af7fbba062172bf80e01f4982cc054 +35 -0
- package/skills/project-update/history/blobs/8abab74759766d02b5cb7b0c3e59a3d440b25a454216cd8fea325c23fd822d36 +33 -0
- package/skills/project-update/history/blobs/a1fb50e6c86fae1679ef3351296fd6713411a08cf8dd1790a4fd05fae8688164 +1 -0
- package/skills/project-update/history/blobs/a3df7268feb0780be0d2cf9acced6ed81780a7790f36fe024b6ea0b1bd6eb7a6 +35 -0
- package/skills/project-update/history/blobs/b28721136482dafb9fd2801af340364cb97e9f6eaa987908606c786045b8d334 +28 -0
- package/skills/project-update/history/blobs/b3726422d767f9219fea633bea77f0c99ad9af843c591e6e928e1ccd2a47f374 +27 -0
- package/skills/project-update/history/blobs/b3839425f9d0ab2efa42a6f5e9488ec2d26ca5fb8dcd694b8ab098e10743ca46 +32 -0
- package/skills/project-update/history/blobs/ba14a457e83f9dde65e4190fdfab83b912f7398c1442a2a1cb3d89bfac741cf3 +8 -0
- package/skills/project-update/history/blobs/bceb7189444bb6291b262ca08e34db01010dda2f6e64d696231ec3c343c95c66 +34 -0
- package/skills/project-update/history/blobs/bf82a2bb4fce1bd349ff66118930a01d61a7af8e2bd8d200c721d43afc58c713 +34 -0
- package/skills/project-update/history/blobs/c4dbeb180292a6199ad73a3be861ece07c1f8bf6bf0cc7ca204412bc8e1bb1d8 +31 -0
- package/skills/project-update/history/blobs/c644838866df2001a42501a988dd7b77aacf862bf09d22b465f3cd8a22c6b6eb +33 -0
- package/skills/project-update/history/blobs/c980d74f76a4bdfb38c94b573ac10a54eb50b850b052e15fa6f2442889b36ccb +32 -0
- package/skills/project-update/history/blobs/ceed2f3bd937f862d0e6ca59ce1b1e3603e19a0d7f85d90d6dfeb7bf690153be +29 -0
- package/skills/project-update/history/blobs/d51ea2e893482a20c5882056cf8043327ca1dc1c957927f6746929a47ee8368e +45 -0
- package/skills/project-update/history/blobs/d78f5e0bb1380259fce2cce009785a9a11f6de8b87a160080b9872c059f95060 +34 -0
- package/skills/project-update/history/blobs/d97f46a2cca020406252b1f7baa4184617fa71faca3885e2379c44ae4e1d9327 +67 -0
- package/skills/project-update/history/blobs/da3ecd4c4c414c93d04488685931df39a56bf959bfeb9c45cb1aea2c13bd991b +38 -0
- package/skills/project-update/history/blobs/e129bf71a5a922e24b2c1bc8daa8a5d2b5589b0069770e17ce17e7db5ed4e7a4 +27 -0
- package/skills/project-update/history/blobs/e504529519a371892838725f8b89b5d32e3300e345bcbf2643a3a1a48434b3dd +24 -0
- package/skills/project-update/history/blobs/e9e62d03ca70b3dffccef8dceeb214173352cfef005145e519f0e037993f1c03 +36 -0
- package/skills/project-update/history/blobs/effefbaaf4beb8f1ba55081d6b7e74bf25ebb91045dd10613418f17b95fa7182 +33 -0
- package/skills/project-update/history/blobs/f6af4581bafbf62b257d57e06c3ae2c5998aeca608ee7df037fcc3c3e4b563a9 +34 -0
- package/skills/project-update/history/blobs/fc35045777d29630303430752a651b3e553580a581036a02cc103eb96a46f4b0 +34 -0
- package/skills/project-update/history/blobs/fdc21345c77f92a42d244aa104f393f439a0ff9981a9fd462bd837e8a47931bb +48 -0
- package/skills/project-update/history/index.json +32 -13
- package/skills/project-update/render_instructions.py +12 -45
- package/skills/project-update/update.py +757 -1172
- package/skills/rr-setup/SKILL.md +42 -81
- package/skills/rr-setup/scaffold_ai.py +7 -7
- package/skills/rr-setup/setup.py +96 -164
- package/skills/rr-status/status.py +7 -10
- package/skills/usage-report/usage-report.py +4 -5
- package/skills/ai-audit/SKILL.md +0 -9
- package/skills/ai-init/SKILL.md +0 -9
- package/skills/ai-status/SKILL.md +0 -9
- package/skills/ai-task/SKILL.md +0 -9
- package/skills/project-init/SKILL.md +0 -9
- package/skills/project-update/SKILL.md +0 -9
- package/skills/project-update/adopt-map.json +0 -91
- package/skills/project-update/adopt.py +0 -2225
- package/skills/project-update/migrations/0001_schema_version.py +0 -13
- package/skills/project-update/migrations/0002_task_journal.py +0 -123
- package/skills/project-update/migrations/0003_context_diet.py +0 -65
- package/skills/project-update/migrations/0004_deterministic_gates.py +0 -57
- package/skills/project-update/migrations/0005_runtimes_and_plans.py +0 -35
- package/skills/project-update/migrations/0006_shared_files.py +0 -154
- package/skills/project-update/migrations/0007_weight_model.py +0 -295
- package/skills/project-update/migrations/0008_renames.py +0 -43
- package/skills/project-update/migrations/0009_state_file.py +0 -25
- package/skills/project-update/migrations/0010_dialog_switch.py +0 -30
- package/skills/project-update/migrations/0011_retire_risk_tiers.py +0 -37
- package/skills/project-update/migrations/README.md +0 -76
- package/skills/project-update/migrations/__init__.py +0 -186
- package/skills/sdlc-intent/SKILL.md +0 -9
- package/skills/sdlc-plan/SKILL.md +0 -9
- package/skills/sdlc-spec/SKILL.md +0 -9
- package/skills/usage-report/SKILL.md +0 -10
- /package/{ai/workflows/refactoring.md → skills/project-update/history/blobs/36cb593293496fbcf38df828a9b9b2bc0d5a53333f584a5319ebc3d799016e24} +0 -0
- /package/{ai/workflows/feature.md → skills/project-update/history/blobs/56e16072c2f3edc6c8897c690d09a11d12fc414c3e3f361f359f7dc609a21282} +0 -0
- /package/{ai/workflows/hotfix.md → skills/project-update/history/blobs/8cbefbf1aa412bae3e5019acd40b8fe0598fceba6c781906b3bc6dbd014f6b23} +0 -0
- /package/{ai/workflows/bugfix.md → skills/project-update/history/blobs/af56dc16e44ae1d8c3dac4c3ca82e2163b719553c02ea455e77430bab9a14089} +0 -0
- /package/{ai/workflows/investigation.md → skills/project-update/history/blobs/b9e09cb24958cd6831589997558974bd6356305306e8f9685e226bcdc30d60ac} +0 -0
- /package/{ai/agents/manager.md → skills/project-update/history/blobs/d0cf6388f35a92a0cf607d33909278382f6d7b6a4c32e281b9f9e0ea652c7b91} +0 -0
package/skills/rr-setup/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: rr-setup
|
|
3
|
-
description: Set up and maintain the .ai/ layer of an existing production codebase. With no flag, scaffold docs/sdlc/, the runtime directory and .ai/, survey the code into .ai/project/ (light or full), write the Verification commands, the weight table's project entries and the constitution — read-only towards application code. --update
|
|
4
|
-
argument-hint: [--survey light|full] | --update [--apply] | --
|
|
3
|
+
description: Set up and maintain the .ai/ layer of an existing production codebase. With no flag, scaffold docs/sdlc/, the runtime directory and .ai/, survey the code into .ai/project/ (light or full), write the Verification commands, the weight table's project entries and the constitution — read-only towards application code. --update regenerates a project's files from the installed plugin (a copy that is a shipped version is replaced, an edited one waits for the human, the project's own files are kept); run it right after installing a new riskrails; --audit scores the repository against the twelve plays and writes docs/ai-sdlc-adoption-plan.md. Use on "/rr-setup", "set up this project", "analyse this project for AI work", "update the rules", "обнови правилата", "audit our AI setup", or when /rr-status says the project is behind.
|
|
4
|
+
argument-hint: [--survey light|full] | --update [--apply] | --audit [context]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# /rr-setup $ARGUMENTS
|
|
@@ -17,7 +17,7 @@ UPDATE="python3 $AI_HOME/skills/project-update/update.py $PWD"
|
|
|
17
17
|
SETUP="python3 $AI_HOME/skills/rr-setup/setup.py"
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
`--update
|
|
20
|
+
`--update` and `--audit` go to their sections below; anything else
|
|
21
21
|
is **Setup**. In every mode: no application code changes — not a rename, not a
|
|
22
22
|
formatting fix. Problems you find are written down, never fixed. Never read
|
|
23
23
|
`.env`, secrets, dumps or production logs. Do not commit; suggest the `git add`.
|
|
@@ -90,13 +90,13 @@ formatting fix. Problems you find are written down, never fixed. Never read
|
|
|
90
90
|
that `.claude/settings.json` keeps `"enableAllProjectMcpServers": false` and
|
|
91
91
|
that each server in a checked-in `.mcp.json` sits in `enabledMcpjsonServers`
|
|
92
92
|
only if it earned it, else in `disabledMcpjsonServers`; say what you found.
|
|
93
|
-
6. **Weight table.**
|
|
94
|
-
`.ai/policies/weight.json
|
|
95
|
-
data), `domain.customer_visible`, `scope.hot_paths`,
|
|
93
|
+
6. **Weight table.** The plugin ships the table; this codebase's own paths go in
|
|
94
|
+
`.ai/policies/weight.project.json`, laid over it: `domain.modules` (money,
|
|
95
|
+
identity, personal data), `domain.customer_visible`, `scope.hot_paths`,
|
|
96
96
|
`reversibility.needs_step` and `irreversible`, and one or two `examples` per
|
|
97
|
-
mode. The JSON is the human's to edit (the path guard refuses yours);
|
|
98
|
-
|
|
99
|
-
are not this skill's to move.
|
|
97
|
+
mode. The JSON is the human's to edit (the path guard refuses yours);
|
|
98
|
+
`--update` renders `weight.md` from the table and the overlay. The
|
|
99
|
+
thresholds are not this skill's to move.
|
|
100
100
|
7. **Constitution.** Fill `docs/sdlc/constitution.md`: C1–C5 ship prefilled;
|
|
101
101
|
C6 onwards are one line each, drawn from what the code and tests already
|
|
102
102
|
enforce. Leave a slot empty rather than write a platitude; at most fifteen.
|
|
@@ -108,7 +108,7 @@ formatting fix. Problems you find are written down, never fixed. Never read
|
|
|
108
108
|
security observations, missing documentation, areas agents must not touch
|
|
109
109
|
casually, the weight-table entries you chose, recommended next improvements —
|
|
110
110
|
recommendations only, none implemented. When the survey found a toolbox,
|
|
111
|
-
|
|
111
|
+
name each toolbox item the project keeps using; they stay where they are.
|
|
112
112
|
9. **Report.** Files created (a tree from the scaffold output), surprising
|
|
113
113
|
findings, the modes and what puts a change in each, the safety restrictions
|
|
114
114
|
in force (hooks versus policy), the tooling the workflows reuse, assumptions,
|
|
@@ -120,27 +120,39 @@ formatting fix. Problems you find are written down, never fixed. Never read
|
|
|
120
120
|
|
|
121
121
|
## --update
|
|
122
122
|
|
|
123
|
-
The script
|
|
124
|
-
|
|
123
|
+
The script regenerates; you show it, confirm what is owed, and merge what it
|
|
124
|
+
cannot. It converts nothing but the one split. A tree that still holds an old
|
|
125
|
+
shape it detects (the pre-3.4b task state without `state.json`, the risk-tier
|
|
126
|
+
policy without a weight table, a pre-3.4b block marker) is refused with `KEPT_UNREADABLE` (exit 6); a
|
|
127
|
+
project last updated before 3.1.0 goes through `npx riskrails@3.1.0` first. Run it right after a new
|
|
128
|
+
riskrails is installed: until then the project's old shapes are not read.
|
|
125
129
|
|
|
126
130
|
1. `$STATE status`: with a task in flight, say that updating policies mid-task
|
|
127
131
|
can change what the rest of it needs, and ask whether to finish it first.
|
|
128
132
|
2. `$UPDATE` — the dry run; show it as is. Up to date and no hint: say so, stop.
|
|
129
|
-
`
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
A `regenerate` is a file that matches a version the plugin once shipped and is
|
|
134
|
+
replaced; a `regenerate?` or `delete?` is a proposal for the human (step 4); a
|
|
135
|
+
`create` is a file the project lacks. A project that still has the mixed
|
|
136
|
+
`testing.md` and `weight.json` gets `verification.md` and `weight.project.json`
|
|
137
|
+
split out of them once.
|
|
138
|
+
3. A `split` row writes `verification.md` or `weight.project.json`, which humans
|
|
139
|
+
own from then on: say in one plain sentence what it carries out of
|
|
140
|
+
`testing.md` or `weight.json`, and ask, unless `$ARGUMENTS` holds `--apply`.
|
|
134
141
|
4. `$UPDATE --apply`. Exit 3 stopped on a changed, non-regular or unwritable
|
|
135
142
|
file and names it; nothing after it was written — run the dry run again.
|
|
136
|
-
|
|
143
|
+
Exit 6 (`KEPT_UNREADABLE`): a file the update keeps cannot be read; nothing was
|
|
144
|
+
written — name it, the human fixes or removes it, then run again.
|
|
145
|
+
**A `regenerate?` and a `delete?` are the human's.** Never pass `--confirm-delete` or `--confirm-cleanup`, not even with
|
|
137
146
|
`--apply`. Give them, with `$AI_HOME` and `$PWD` substituted, for their own
|
|
138
147
|
terminal (the `!` prefix is refused):
|
|
139
148
|
`python3 "<home>/skills/project-update/update.py" "<project>" --apply --confirm-delete "<your name>"`.
|
|
140
|
-
|
|
141
|
-
file
|
|
142
|
-
|
|
143
|
-
|
|
149
|
+
Exit 5 (`CONFIRM_REFUSED`) means an agent session ran it; it wrote nothing.
|
|
150
|
+
Every file replaced or deleted that way is first copied under
|
|
151
|
+
`.ai/reports/project-update-<date>/original/`. Two kinds are housekeeping:
|
|
152
|
+
`[rules]` — every rule source `.ai/rules/<slug>.md` (an update does not keep
|
|
153
|
+
them; confirming deletes the source and its rendered blocks with it) and a
|
|
154
|
+
rendered rule file whose source is gone or no longer sets `paths:`, both
|
|
155
|
+
settled by `--confirm-delete`; `[cleanup]` —
|
|
144
156
|
the `*.log` files of closed tasks, and the report and log files of an
|
|
145
157
|
abandoned task (its `state.json` stays) or of one unclosed for more than seven
|
|
146
158
|
days (never the task in flight; and none when `.ai/state/state.json` is unreadable).
|
|
@@ -151,16 +163,15 @@ it cannot.
|
|
|
151
163
|
commit `.ai/` first (the hint says so), and an `original/` copy is kept as for any
|
|
152
164
|
other deletion. They have their own command, the human's too, never passed by the agent:
|
|
153
165
|
`python3 "<home>/skills/project-update/update.py" "<project>" --apply --confirm-cleanup "<your name>"`.
|
|
154
|
-
Show both groups as printed.
|
|
155
|
-
`install.sh` for every runtime used, commit `.ai/`, read the `delete?` list,
|
|
156
|
-
then the command with no task in flight. Rollback is git:
|
|
166
|
+
Show both groups as printed. Rollback is git:
|
|
157
167
|
`git restore --source=<commit before> --staged --worktree -- .ai`, before
|
|
158
168
|
the plugin itself is rolled back.
|
|
159
|
-
5. A `
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
169
|
+
5. A `regenerate?` row: the project edited a file the plugin generates. Read
|
|
170
|
+
its reason; anything project-specific in it belongs in a file the update keeps
|
|
171
|
+
(`.ai/project/`, `weight.project.json`, `verification.md`, the instruction
|
|
172
|
+
file outside the managed block — not `.ai/rules/`, whose sources are `[rules]`
|
|
173
|
+
rows too) — say where, the human moves it,
|
|
174
|
+
then confirms. Never edit the generated file to dodge the row.
|
|
164
175
|
6. Hints. `verify_command is empty`: show `$SETUP survey`'s proposal, ask for
|
|
165
176
|
the one command and what a green run ends with, write it with
|
|
166
177
|
`$SETUP verify --set`, and fill the rest of the Verification block. `the managed block is N B`:
|
|
@@ -169,8 +180,7 @@ it cannot.
|
|
|
169
180
|
fifteen principles or 4096 B: offer to move policy into `.ai/policies/`.
|
|
170
181
|
`that rule is not rendered`: name the `.ai/rules/` file and what it lacks
|
|
171
182
|
(`paths:` and a body). `overrides the plugin's shared copy`: offer to diff
|
|
172
|
-
each and delete those that add nothing. `
|
|
173
|
-
**--adopt**. `tracked by git but ignored`: show the line as printed and give
|
|
183
|
+
each and delete those that add nothing. `tracked by git but ignored`: show the line as printed and give
|
|
174
184
|
the human the command it prints, exactly as printed, plus a commit, for their
|
|
175
185
|
own terminal (never rebuilt from the path; only the xargs form untracks every
|
|
176
186
|
such file under `.ai/state`, a printed `rm --cached -- <path>` one file; a
|
|
@@ -180,55 +190,6 @@ it cannot.
|
|
|
180
190
|
and what is the human's. Suggest `git add -A .ai docs/sdlc .gitignore` plus
|
|
181
191
|
the runtime directories and instruction files.
|
|
182
192
|
|
|
183
|
-
## --adopt
|
|
184
|
-
|
|
185
|
-
**A project's own toolbox** — its `.claude/` skills, commands, rules, agents
|
|
186
|
-
and hooks, `.codex/*`, `bin/*.sh`, Makefile targets — is production behaviour
|
|
187
|
-
(C1), not a foreign structure: it is inventoried in place, never copied or
|
|
188
|
-
moved. `$SETUP adopt` shows `.ai/policies/toolbox.md` (one line per item: link,
|
|
189
|
-
purpose, KNOWN FACT `file:line` or UNKNOWN) and the router rows it appends to
|
|
190
|
-
`.ai/AGENTS.md` — one per skill or command, naming its job and its `/<name>`. A
|
|
191
|
-
project hook doing a plugin guard's job is kept and noted; a guard with none is
|
|
192
|
-
reported. Ask "apply?", then `$SETUP adopt --apply`; it is idempotent. Exit 3:
|
|
193
|
-
`toolbox.md` differs from the inventory (a human edited it) — show both, never
|
|
194
|
-
overwrite. A foreign tool's structure, below, can be present as well.
|
|
195
|
-
|
|
196
|
-
Every command is `$UPDATE --adopt …`, with `--mode coexist` on each when the team
|
|
197
|
-
still uses the other tool (router rows only, nothing moves). Exit 5
|
|
198
|
-
`ADOPT_REFUSED`: show the first line and stop. Exit 2: the routing table or a
|
|
199
|
-
migration could not be read (reason on stderr, prefixed `project-update:`),
|
|
200
|
-
nothing written. Exit 4 `ADOPT_INCOMPLETE`:
|
|
201
|
-
`unmapped` files and a `split?` are yours to settle, then dry-run again; a
|
|
202
|
-
`conflict` or a check `FAIL` left after that is the human's — never edit a
|
|
203
|
-
destination or a source to make it pass.
|
|
204
|
-
|
|
205
|
-
1. Preconditions: no task in flight (`$STATE status`), `git status` clean,
|
|
206
|
-
`$UPDATE --check` exits 0. Otherwise stop and say which failed.
|
|
207
|
-
2. `$UPDATE --adopt`; show it as is.
|
|
208
|
-
3. Each `unmapped` file: ask the human (drop with a reason, or copy to a named
|
|
209
|
-
destination) and write the answers to `.ai/reports/adopt-<date>/decisions.json`
|
|
210
|
-
as `{"version": 1, "unmapped": {"<path>": {"action": "drop", "why": "…"}}}`.
|
|
211
|
-
A glob key settles a directory (`drop` or `ignore` only); an exact path wins
|
|
212
|
-
over a glob, a longer glob over a shorter one.
|
|
213
|
-
4. A `split?` (an instruction file too large to keep): on a `pro` or `plus` plan
|
|
214
|
-
(`jq -r .plan "$AI_HOME/riskrails/profile.json"`), when that fails, or
|
|
215
|
-
under `AI_UNATTENDED`, use `--split fallback` — every line outside the managed
|
|
216
|
-
block moves verbatim to `.ai/policies/adopted/`. Otherwise run
|
|
217
|
-
`--split-request` and write the proposal yourself, once, to the `proposal`
|
|
218
|
-
path the request names: line ranges, destinations from `allowed_dest`,
|
|
219
|
-
headings from its `outline` — never text (the tool refuses that). A proposal
|
|
220
|
-
for the same file sha is reused.
|
|
221
|
-
5. `$UPDATE --adopt --diff`; show it and ask "apply?".
|
|
222
|
-
6. `$UPDATE --adopt --apply` (with `--split fallback` if step 4 chose it); show
|
|
223
|
-
the two `check` lines and read out each `no-dangling` warning. On a FAIL read
|
|
224
|
-
`dropped.jsonl`, amend the proposal or decisions, run again.
|
|
225
|
-
7. Point at `.ai/reports/adopt-<date>/report.md`; suggest `git add` of the new
|
|
226
|
-
structure and the report directory, and a commit.
|
|
227
|
-
8. Cleanup is the human's: show `$UPDATE --adopt --cleanup`, then give the full
|
|
228
|
-
command with both paths absolute for their own terminal —
|
|
229
|
-
`python3 "<home>/skills/project-update/update.py" "<project>" --adopt --cleanup --apply --confirm-delete "<your name>"`.
|
|
230
|
-
Setting `AI_UNATTENDED` to pass it is the same act as typing their name.
|
|
231
|
-
|
|
232
193
|
## --audit
|
|
233
194
|
|
|
234
195
|
The only file written is `docs/ai-sdlc-adoption-plan.md`; everything else is
|
|
@@ -27,8 +27,9 @@ import sys
|
|
|
27
27
|
NAME = "scaffold-ai"
|
|
28
28
|
# The directory the shell script ran from: templates and the runtime it was installed for.
|
|
29
29
|
HERE = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "ai-init"))
|
|
30
|
+
SHIPPED_WEIGHT = ".ai/policies/weight.json"
|
|
30
31
|
RUNTIMES = ("claude", "codex", "gemini", "junie")
|
|
31
|
-
BLOCK = re.compile(rb"<!--
|
|
32
|
+
BLOCK = re.compile(rb"<!-- riskrails:start -->")
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
class Exit(Exception):
|
|
@@ -212,15 +213,14 @@ def main(argv):
|
|
|
212
213
|
root = stdin_cwd()
|
|
213
214
|
root = logical_cd(root or logical_pwd())
|
|
214
215
|
project = os.fsencode("/" if root == "/" else os.path.basename(root))
|
|
215
|
-
tpl = env("RISKRAILS_TEMPLATES") or
|
|
216
|
+
tpl = env("RISKRAILS_TEMPLATES") or os.path.join(HERE, "templates")
|
|
216
217
|
if not os.path.isdir(os.path.join(tpl, ".ai")):
|
|
217
218
|
raise Exit(1, "%s: templates not found at %s (run riskrails/install.sh)" % (NAME, tpl))
|
|
218
219
|
on = runtimes(runtime, root)
|
|
219
220
|
created = []
|
|
220
221
|
|
|
221
222
|
# A project that already has .ai/ carries a schema version this scaffold must not
|
|
222
|
-
# invent:
|
|
223
|
-
# has already run. Only a fresh tree starts at the shipped schema.
|
|
223
|
+
# invent: only a fresh tree starts at the shipped schema.
|
|
224
224
|
had_ai = os.path.isdir(os.path.join(root, ".ai"))
|
|
225
225
|
|
|
226
226
|
# Copy the .ai/ tree, file by file, skipping anything that already exists.
|
|
@@ -231,6 +231,8 @@ def main(argv):
|
|
|
231
231
|
dst = os.path.join(root, rel)
|
|
232
232
|
if os.path.exists(dst) or (rel == ".ai/VERSION" and had_ai):
|
|
233
233
|
continue
|
|
234
|
+
if rel == SHIPPED_WEIGHT: # A2: the plugin's table is read from the plugin, never copied
|
|
235
|
+
continue
|
|
234
236
|
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
|
235
237
|
# cp: the source's permission bits, less the umask.
|
|
236
238
|
shutil.copyfile(src, dst)
|
|
@@ -253,9 +255,7 @@ def main(argv):
|
|
|
253
255
|
created.append(".gitignore (appended)")
|
|
254
256
|
|
|
255
257
|
# The instruction file: create a minimal one if absent, then append the managed
|
|
256
|
-
# block once. An existing file is never replaced — only appended to.
|
|
257
|
-
# Codex-agentic pair is the block under its old name: nothing is appended beside
|
|
258
|
-
# it; /project-update renames it.
|
|
258
|
+
# block once. An existing file is never replaced — only appended to.
|
|
259
259
|
def instruction_file(name, minimal, block):
|
|
260
260
|
md = os.path.join(root, name)
|
|
261
261
|
os.makedirs(os.path.dirname(md), exist_ok=True)
|
package/skills/rr-setup/setup.py
CHANGED
|
@@ -3,19 +3,17 @@
|
|
|
3
3
|
and whether superpowers is on (spec R9, R28, SQ5, SQ12 variant B).
|
|
4
4
|
|
|
5
5
|
setup.py survey [--root DIR] the toolbox, the proposed verify_command, the superpowers line
|
|
6
|
-
setup.py check [--root DIR] exit 1 VERIFY_COMMAND_MISSING unless
|
|
7
|
-
setup.py
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
never copied or moved; what to inventory comes from the `own` section of
|
|
13
|
-
skills/project-update/adopt-map.json, which update.py --adopt does not read.
|
|
6
|
+
setup.py check [--root DIR] exit 1 VERIFY_COMMAND_MISSING unless verification.md names one
|
|
7
|
+
setup.py verify [--root DIR] --set CMD write verify_command into verification.md (after the human confirmed)
|
|
8
|
+
|
|
9
|
+
survey and check write nothing. The toolbox is production behaviour (C1): it is
|
|
10
|
+
inventoried in place, never copied or moved; what to inventory is the constants
|
|
11
|
+
FOREIGN_ROOTS and OWN_* below.
|
|
14
12
|
The enabledPlugins line is printed, never written: .claude/settings.json is the
|
|
15
13
|
human's.
|
|
16
14
|
|
|
17
|
-
Exit codes: 0 done; 1 VERIFY_COMMAND_MISSING; 2 usage
|
|
18
|
-
3 refused (
|
|
15
|
+
Exit codes: 0 done; 1 VERIFY_COMMAND_MISSING; 2 usage;
|
|
16
|
+
3 refused (the line is already set).
|
|
19
17
|
"""
|
|
20
18
|
import argparse
|
|
21
19
|
import json
|
|
@@ -25,10 +23,7 @@ import shutil
|
|
|
25
23
|
import sys
|
|
26
24
|
|
|
27
25
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
28
|
-
|
|
29
|
-
TESTING = os.path.join(".ai", "policies", "testing.md")
|
|
30
|
-
TOOLBOX = os.path.join(".ai", "policies", "toolbox.md")
|
|
31
|
-
ROUTER_FILE = os.path.join(".ai", "AGENTS.md")
|
|
26
|
+
VERIFICATION = os.path.join(".ai", "policies", "verification.md")
|
|
32
27
|
SUPERPOWERS_KEY = "superpowers@superpowers-marketplace"
|
|
33
28
|
SKIP_DIRS = {".git", ".ai", "vendor", "node_modules", "__pycache__", ".venv"}
|
|
34
29
|
PURPOSE_MAX = 120
|
|
@@ -36,6 +31,72 @@ UNKNOWN = "UNKNOWN — no description and no leading comment"
|
|
|
36
31
|
VERIFY_RE = re.compile(r"(?m)^verify_command:[ \t]*(.*)$")
|
|
37
32
|
MAKE_TARGET_RE = re.compile(r"^([A-Za-z0-9][A-Za-z0-9_. \t-]*?)\s*:(?![=:])(.*)$")
|
|
38
33
|
BLOCK_SCALAR_RE = re.compile(r"^[|>][-+]?$")
|
|
34
|
+
# Paths a foreign AI tool owns (Spec Kit, Kiro, AI-DLC, Cursor, Copilot): no part of the project's own toolbox.
|
|
35
|
+
FOREIGN_ROOTS = [
|
|
36
|
+
".specify/",
|
|
37
|
+
"specs/",
|
|
38
|
+
".claude/commands/speckit*",
|
|
39
|
+
".claude/skills/speckit-*",
|
|
40
|
+
".github/prompts/speckit*",
|
|
41
|
+
".github/skills/speckit-*",
|
|
42
|
+
".github/agents/speckit*",
|
|
43
|
+
".codex/prompts/speckit*",
|
|
44
|
+
".agents/skills/speckit-*",
|
|
45
|
+
".gemini/commands/speckit*",
|
|
46
|
+
".cursor/commands/speckit*",
|
|
47
|
+
".cursor/skills/speckit-*",
|
|
48
|
+
".kiro/",
|
|
49
|
+
"aidlc-docs/",
|
|
50
|
+
".amazonq/rules/aws-aidlc-rules/",
|
|
51
|
+
".amazonq/aws-aidlc-rule-details/",
|
|
52
|
+
".cursorrules",
|
|
53
|
+
".cursor/",
|
|
54
|
+
".github/copilot-instructions.md",
|
|
55
|
+
".github/instructions/",
|
|
56
|
+
]
|
|
57
|
+
# What `survey` inventories in place (SQ5): globs by kind, the guards' name words, the verify-command sources.
|
|
58
|
+
OWN_INVENTORY = [
|
|
59
|
+
{"glob": ".claude/skills/*/SKILL.md", "kind": "skill", "router": "skill"},
|
|
60
|
+
{"glob": ".claude/commands/**/*.md", "kind": "command", "router": "command"},
|
|
61
|
+
{"glob": ".claude/rules/**/*.md", "kind": "rule"},
|
|
62
|
+
{"glob": ".claude/agents/*.md", "kind": "agent"},
|
|
63
|
+
{"glob": ".claude/hooks/*", "kind": "hook"},
|
|
64
|
+
{"glob": ".agents/skills/*/SKILL.md", "kind": "skill", "router": "skill"},
|
|
65
|
+
{"glob": ".codex/skills/*/SKILL.md", "kind": "skill", "router": "skill"},
|
|
66
|
+
{"glob": ".codex/prompts/*.md", "kind": "command", "router": "command"},
|
|
67
|
+
{"glob": ".codex/agents/*", "kind": "agent"},
|
|
68
|
+
{"glob": ".codex/hooks/*", "kind": "hook"},
|
|
69
|
+
{"glob": "bin/*.sh", "kind": "script"},
|
|
70
|
+
{"glob": "Makefile", "kind": "make"},
|
|
71
|
+
]
|
|
72
|
+
OWN_GUARDS = {
|
|
73
|
+
"ai-git-guard": [
|
|
74
|
+
"git",
|
|
75
|
+
"commit",
|
|
76
|
+
"push",
|
|
77
|
+
"merge"
|
|
78
|
+
],
|
|
79
|
+
"ai-path-guard": [
|
|
80
|
+
"path",
|
|
81
|
+
"protect",
|
|
82
|
+
"protected",
|
|
83
|
+
"secret",
|
|
84
|
+
"secrets",
|
|
85
|
+
"dotenv"
|
|
86
|
+
],
|
|
87
|
+
"ai-scope-guard": [
|
|
88
|
+
"scope"
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
OWN_VERIFY = [
|
|
92
|
+
{"file": "Makefile", "make_target": ["test", "tests", "check", "qa"], "command": "make {name}"},
|
|
93
|
+
{"file": "composer.json", "script": ["test", "tests", "qa", "check"], "command": "composer {name}"},
|
|
94
|
+
{"file": "package.json", "script": ["test"], "command": "npm test"},
|
|
95
|
+
{"file": "bin/*.sh", "stem": ["test", "tests", "run-tests", "check"], "command": "bin/{name}.sh"},
|
|
96
|
+
{"file": "phpunit.xml.dist", "command": "vendor/bin/phpunit"},
|
|
97
|
+
{"file": "phpunit.xml", "command": "vendor/bin/phpunit"},
|
|
98
|
+
{"file": "pytest.ini", "command": "python3 -m pytest"},
|
|
99
|
+
]
|
|
39
100
|
|
|
40
101
|
|
|
41
102
|
def die(message, code=2):
|
|
@@ -60,20 +121,8 @@ def is_crlf(path):
|
|
|
60
121
|
return False
|
|
61
122
|
|
|
62
123
|
|
|
63
|
-
def load_map():
|
|
64
|
-
try:
|
|
65
|
-
with open(MAP_FILE, encoding="utf-8") as fh:
|
|
66
|
-
table = json.load(fh)
|
|
67
|
-
except (OSError, ValueError) as exc:
|
|
68
|
-
die("%s: %s" % (MAP_FILE, exc))
|
|
69
|
-
own = table.get("own")
|
|
70
|
-
if not isinstance(own, dict) or not isinstance(own.get("inventory"), list):
|
|
71
|
-
die("%s: needs an own section with an inventory list" % MAP_FILE)
|
|
72
|
-
return table
|
|
73
|
-
|
|
74
|
-
|
|
75
124
|
def glob_re(pattern):
|
|
76
|
-
"""The
|
|
125
|
+
"""The glob dialect: `*` stops at `/`, `**` crosses it, `**/` may match nothing."""
|
|
77
126
|
out, i = [], 0
|
|
78
127
|
while i < len(pattern):
|
|
79
128
|
if pattern.startswith("**/", i):
|
|
@@ -91,12 +140,6 @@ def glob_re(pattern):
|
|
|
91
140
|
return re.compile("".join(out) + r"\Z")
|
|
92
141
|
|
|
93
142
|
|
|
94
|
-
def foreign_roots(table):
|
|
95
|
-
"""Paths a foreign tool owns (Spec Kit skills under .claude/ and the like):
|
|
96
|
-
update.py --adopt handles those, so they are no part of the own toolbox."""
|
|
97
|
-
return [r for spec in (table.get("tools") or {}).values() for r in spec.get("roots") or []]
|
|
98
|
-
|
|
99
|
-
|
|
100
143
|
def under_root(path, root):
|
|
101
144
|
if root.endswith("/"):
|
|
102
145
|
return path.startswith(root)
|
|
@@ -218,13 +261,11 @@ def make_targets(root, rel="Makefile"):
|
|
|
218
261
|
|
|
219
262
|
# ---------------------------------------------------------------- inventory
|
|
220
263
|
|
|
221
|
-
def inventory(root
|
|
264
|
+
def inventory(root):
|
|
222
265
|
"""[{path, kind, line, purpose, name, router}] in table order, then path order."""
|
|
223
|
-
|
|
224
|
-
skip = foreign_roots(table)
|
|
225
|
-
paths = [p for p in walk(root) if not any(under_root(p, r) for r in skip)]
|
|
266
|
+
paths = [p for p in walk(root) if not any(under_root(p, r) for r in FOREIGN_ROOTS)]
|
|
226
267
|
items, seen = [], set()
|
|
227
|
-
for row in
|
|
268
|
+
for row in OWN_INVENTORY:
|
|
228
269
|
pattern = glob_re(row["glob"])
|
|
229
270
|
for rel in paths:
|
|
230
271
|
if rel in seen or not pattern.match(rel):
|
|
@@ -255,15 +296,14 @@ def command_name(rel, row):
|
|
|
255
296
|
return os.path.basename(rel)
|
|
256
297
|
|
|
257
298
|
|
|
258
|
-
def guard_overlap(items
|
|
299
|
+
def guard_overlap(items):
|
|
259
300
|
"""{guard: [hook paths doing its job]} by the hook file name's words."""
|
|
260
|
-
|
|
261
|
-
found = {g: [] for g in guards}
|
|
301
|
+
found = {g: [] for g in OWN_GUARDS}
|
|
262
302
|
for item in items:
|
|
263
303
|
if item["kind"] != "hook":
|
|
264
304
|
continue
|
|
265
305
|
words = set(re.split(r"[^a-z0-9]+", os.path.basename(item["path"]).lower()))
|
|
266
|
-
for guard, keys in
|
|
306
|
+
for guard, keys in OWN_GUARDS.items():
|
|
267
307
|
if words & set(keys):
|
|
268
308
|
found[guard].append(item["path"])
|
|
269
309
|
return found
|
|
@@ -271,10 +311,10 @@ def guard_overlap(items, table):
|
|
|
271
311
|
|
|
272
312
|
# ------------------------------------------------------------------- verify
|
|
273
313
|
|
|
274
|
-
def verify_candidates(root
|
|
314
|
+
def verify_candidates(root):
|
|
275
315
|
"""[(command, source)] in table order; the first is the proposal. Nothing is run."""
|
|
276
316
|
out = []
|
|
277
|
-
for row in
|
|
317
|
+
for row in OWN_VERIFY:
|
|
278
318
|
pattern = row["file"]
|
|
279
319
|
if "*" in pattern:
|
|
280
320
|
regex = glob_re(pattern)
|
|
@@ -305,9 +345,9 @@ def verify_candidates(root, table):
|
|
|
305
345
|
|
|
306
346
|
|
|
307
347
|
def verify_value(root):
|
|
308
|
-
"""None when
|
|
348
|
+
"""None when verification.md is missing or has no verify_command line; else the
|
|
309
349
|
value, '' for the template's comment-only line (sensors.read_commands' rule)."""
|
|
310
|
-
text = read_text(os.path.join(root,
|
|
350
|
+
text = read_text(os.path.join(root, VERIFICATION))
|
|
311
351
|
if text is None:
|
|
312
352
|
return None
|
|
313
353
|
match = VERIFY_RE.search(text)
|
|
@@ -344,76 +384,6 @@ def superpowers_line(root):
|
|
|
344
384
|
"\"enabledPlugins\": {\"%s\": true}" % (" (disabled in %s)" % where if where else "", SUPERPOWERS_KEY))
|
|
345
385
|
|
|
346
386
|
|
|
347
|
-
# ----------------------------------------------------------------- rendering
|
|
348
|
-
|
|
349
|
-
def toolbox_text(items, overlap):
|
|
350
|
-
lines = ["# Toolbox — this project's own skills, rules, agents, hooks and scripts", "",
|
|
351
|
-
"<!-- Written by `setup.py adopt` (/rr-setup --adopt). An inventory, not copies: each line links",
|
|
352
|
-
" the file in place, which stays the source of truth (C1). Edit freely; adopt never",
|
|
353
|
-
" overwrites a toolbox.md that differs from what it would write. -->", ""]
|
|
354
|
-
kinds = [("skill", "Skills"), ("command", "Commands"), ("rule", "Rules"), ("agent", "Agents"),
|
|
355
|
-
("hook", "Hooks"), ("script", "Scripts"), ("make", "Make targets")]
|
|
356
|
-
for kind, title in kinds:
|
|
357
|
-
rows = [i for i in items if i["kind"] == kind]
|
|
358
|
-
if not rows:
|
|
359
|
-
continue
|
|
360
|
-
lines += ["## %s" % title, ""]
|
|
361
|
-
for item in rows:
|
|
362
|
-
label = item["name"] if kind in ("skill", "command", "make") else item["path"]
|
|
363
|
-
if kind in ("skill", "command"):
|
|
364
|
-
label = "/" + label
|
|
365
|
-
elif kind == "make":
|
|
366
|
-
label = "make " + label
|
|
367
|
-
purpose = ("%s — KNOWN FACT `%s:%d`" % (clip(item["purpose"]), item["path"], item["line"])
|
|
368
|
-
if item["purpose"] else UNKNOWN)
|
|
369
|
-
lines.append("- [%s](../../%s) — %s" % (label, item["path"], purpose))
|
|
370
|
-
lines.append("")
|
|
371
|
-
if overlap:
|
|
372
|
-
lines += ["## Guards", ""]
|
|
373
|
-
for guard, paths in overlap.items():
|
|
374
|
-
if paths:
|
|
375
|
-
lines.append("- `%s`: this project's %s does the same job — kept; the plugin's guard is not "
|
|
376
|
-
"installed into the project" % (guard, ", ".join("`%s`" % p for p in paths)))
|
|
377
|
-
else:
|
|
378
|
-
lines.append("- `%s`: no project hook does its job — the plugin's guard applies" % guard)
|
|
379
|
-
lines.append("")
|
|
380
|
-
if not items:
|
|
381
|
-
lines += ["No skills, rules, agents, hooks, scripts or Make targets found.", ""]
|
|
382
|
-
return "\n".join(lines)
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
def router_rows(items):
|
|
386
|
-
"""One row per `/name`: a skill mirrored under .claude/ and .agents/ or
|
|
387
|
-
.codex/, or a command of the same name, is one route (the first found)."""
|
|
388
|
-
rows = ["| this project's own skills, hooks, agents and scripts | policies/toolbox.md |"]
|
|
389
|
-
named = set()
|
|
390
|
-
for item in items:
|
|
391
|
-
if not item["router"] or item["name"] in named:
|
|
392
|
-
continue
|
|
393
|
-
named.add(item["name"])
|
|
394
|
-
job = clip(item["purpose"] or "the project's %s" % item["name"]).replace("|", "\\|")
|
|
395
|
-
rows.append("| %s | `/%s` |" % (job, item["name"]))
|
|
396
|
-
return rows
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
def last_cell(row):
|
|
400
|
-
cells = row.rstrip().rstrip("|").rsplit("|", 1)
|
|
401
|
-
return cells[-1].strip() if len(cells) == 2 else None
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
def add_rows(text, rows):
|
|
405
|
-
"""Rows appended after the last table row; a row whose target cell is
|
|
406
|
-
already some table row's last cell is skipped, so a human's rewording of the
|
|
407
|
-
job survives and prose that mentions `/<name>` does not count."""
|
|
408
|
-
lines = text.split("\n")
|
|
409
|
-
last = max((i for i, l in enumerate(lines) if l.startswith("|")), default=None)
|
|
410
|
-
if last is None:
|
|
411
|
-
return None, []
|
|
412
|
-
targets = {last_cell(l) for l in lines if l.startswith("|")}
|
|
413
|
-
new = [r for r in rows if last_cell(r) not in targets]
|
|
414
|
-
return "\n".join(lines[:last + 1] + new + lines[last + 1:]), new
|
|
415
|
-
|
|
416
|
-
|
|
417
387
|
def write(path, text):
|
|
418
388
|
"""In place through a symlink, keeping the file's mode and a CRLF file's
|
|
419
389
|
line ends; a failed write leaves no temporary file behind."""
|
|
@@ -435,19 +405,18 @@ def write(path, text):
|
|
|
435
405
|
# ----------------------------------------------------------------- commands
|
|
436
406
|
|
|
437
407
|
def cmd_survey(root):
|
|
438
|
-
|
|
439
|
-
items = inventory(root, table)
|
|
408
|
+
items = inventory(root)
|
|
440
409
|
print("toolbox: %d item(s)" % len(items))
|
|
441
410
|
for item in items:
|
|
442
411
|
where = "%s:%d" % (item["path"], item["line"]) if item["line"] else item["path"]
|
|
443
412
|
name = " %s" % item["name"] if item["kind"] == "make" else ""
|
|
444
413
|
print(" %-8s %s%s — %s" % (item["kind"], where, name, clip(item["purpose"]) if item["purpose"] else UNKNOWN))
|
|
445
|
-
for guard, paths in guard_overlap(items
|
|
414
|
+
for guard, paths in guard_overlap(items).items():
|
|
446
415
|
print(" guard %s: %s" % (guard, ", ".join(paths) + " (same job, kept)" if paths else "no project hook"))
|
|
447
416
|
current = verify_value(root)
|
|
448
|
-
candidates = verify_candidates(root
|
|
417
|
+
candidates = verify_candidates(root)
|
|
449
418
|
if current:
|
|
450
|
-
print("verify_command: %s (
|
|
419
|
+
print("verify_command: %s (verification.md)" % current)
|
|
451
420
|
elif candidates:
|
|
452
421
|
print("verify_command: proposed `%s` (from %s) — write it with `setup.py verify --set` once the human "
|
|
453
422
|
"confirms" % candidates[0])
|
|
@@ -463,71 +432,36 @@ def cmd_check(root):
|
|
|
463
432
|
value = verify_value(root)
|
|
464
433
|
if not value or value.lower() == "none":
|
|
465
434
|
print("VERIFY_COMMAND_MISSING: %s names no verify_command — propose one with `setup.py survey`, "
|
|
466
|
-
"write it with `setup.py verify --set \"<command>\"` after the human confirms" %
|
|
435
|
+
"write it with `setup.py verify --set \"<command>\"` after the human confirms" % VERIFICATION)
|
|
467
436
|
return 1
|
|
468
437
|
print("verify_command: %s" % value)
|
|
469
438
|
return 0
|
|
470
439
|
|
|
471
440
|
|
|
472
|
-
def cmd_adopt(root, apply):
|
|
473
|
-
table = load_map()
|
|
474
|
-
router = os.path.join(root, ROUTER_FILE)
|
|
475
|
-
router_text = read_text(router)
|
|
476
|
-
if router_text is None:
|
|
477
|
-
die("%s not found — scaffold .ai/ first (/rr-setup)" % ROUTER_FILE)
|
|
478
|
-
items = inventory(root, table)
|
|
479
|
-
text = toolbox_text(items, guard_overlap(items, table))
|
|
480
|
-
toolbox = os.path.join(root, TOOLBOX)
|
|
481
|
-
existing = read_text(toolbox)
|
|
482
|
-
new_router, added = add_rows(router_text, router_rows(items))
|
|
483
|
-
if new_router is None:
|
|
484
|
-
die("%s has no table to add router rows to" % ROUTER_FILE)
|
|
485
|
-
if existing is not None and existing != text:
|
|
486
|
-
print("refused: %s exists and differs from the inventory — compare by hand, or delete it and run "
|
|
487
|
-
"again" % TOOLBOX)
|
|
488
|
-
return 3
|
|
489
|
-
print("%s %s (%d item(s))" % ("unchanged" if existing == text else "write", TOOLBOX, len(items)))
|
|
490
|
-
for row in added:
|
|
491
|
-
print("router %s" % row)
|
|
492
|
-
if not added:
|
|
493
|
-
print("router no new row")
|
|
494
|
-
if not apply:
|
|
495
|
-
print("dry run — --apply writes it")
|
|
496
|
-
return 0
|
|
497
|
-
if existing != text:
|
|
498
|
-
write(toolbox, text)
|
|
499
|
-
if added:
|
|
500
|
-
write(router, new_router)
|
|
501
|
-
print("applied")
|
|
502
|
-
return 0
|
|
503
|
-
|
|
504
|
-
|
|
505
441
|
def cmd_verify(root, command):
|
|
506
442
|
command = (command or "").strip()
|
|
507
443
|
if not command or "\n" in command or command.startswith("#"):
|
|
508
444
|
die("--set needs one command on one line")
|
|
509
|
-
path = os.path.join(root,
|
|
445
|
+
path = os.path.join(root, VERIFICATION)
|
|
510
446
|
text = read_text(path)
|
|
511
447
|
if text is None or not VERIFY_RE.search(text):
|
|
512
|
-
die("%s has no verify_command line — run /rr-setup --update first" %
|
|
448
|
+
die("%s has no verify_command line — run /rr-setup --update first" % VERIFICATION)
|
|
513
449
|
current = verify_value(root)
|
|
514
450
|
if current and current.lower() != "none":
|
|
515
|
-
print("refused: verify_command is already `%s` — the human edits %s" % (current,
|
|
451
|
+
print("refused: verify_command is already `%s` — the human edits %s" % (current, VERIFICATION))
|
|
516
452
|
return 3
|
|
517
453
|
match = VERIFY_RE.search(text)
|
|
518
454
|
write(path, text[:match.start()] + "verify_command: " + command + text[match.end():])
|
|
519
|
-
print("verify_command: %s (written to %s)" % (command,
|
|
455
|
+
print("verify_command: %s (written to %s)" % (command, VERIFICATION))
|
|
520
456
|
return 0
|
|
521
457
|
|
|
522
458
|
|
|
523
459
|
def main(argv=None):
|
|
524
460
|
parser = argparse.ArgumentParser(prog="setup.py", description=__doc__.split("\n\n", 1)[0])
|
|
525
461
|
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
526
|
-
for name in ("survey", "check", "
|
|
462
|
+
for name in ("survey", "check", "verify"):
|
|
527
463
|
p = sub.add_parser(name)
|
|
528
464
|
p.add_argument("--root", default=".")
|
|
529
|
-
if name == "adopt":
|
|
530
|
-
p.add_argument("--apply", action="store_true")
|
|
531
465
|
if name == "verify":
|
|
532
466
|
p.add_argument("--set", dest="command", required=True)
|
|
533
467
|
args = parser.parse_args(argv)
|
|
@@ -538,8 +472,6 @@ def main(argv=None):
|
|
|
538
472
|
return cmd_survey(root)
|
|
539
473
|
if args.cmd == "check":
|
|
540
474
|
return cmd_check(root)
|
|
541
|
-
if args.cmd == "adopt":
|
|
542
|
-
return cmd_adopt(root, args.apply)
|
|
543
475
|
return cmd_verify(root, args.command)
|
|
544
476
|
|
|
545
477
|
|