riskrails 3.2.2 → 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.
Files changed (141) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/README.md +40 -83
  5. package/agents/ai-reviewer.md.tmpl +1 -1
  6. package/ai/using-riskrails.md +0 -3
  7. package/bin/riskrails.js +13 -59
  8. package/hooks/ai-path-guard-defaults.json +1 -1
  9. package/hooks/context-guard.py +2 -4
  10. package/hooks/guard/paths.py +3 -6
  11. package/hooks/guard/scope.py +1 -3
  12. package/hooks/runtime-gate.py +18 -58
  13. package/instructions/routing.md +2 -2
  14. package/package.json +1 -1
  15. package/scripts/ai-path +1 -1
  16. package/scripts/resolve-profile.py +0 -3
  17. package/skills/ai-audit/templates/ai-sdlc-adoption-plan.md +1 -1
  18. package/skills/ai-audit/templates/play-scoring-rubric.md +1 -1
  19. package/skills/ai-init/templates/.ai/AGENTS.md +6 -6
  20. package/skills/ai-init/templates/.ai/VERSION +1 -1
  21. package/skills/ai-init/templates/.ai/policies/security.md +1 -2
  22. package/skills/ai-init/templates/.ai/policies/testing.md +5 -12
  23. package/skills/ai-init/templates/.ai/policies/verification.md +18 -0
  24. package/skills/ai-init/templates/.ai/policies/weight.json +0 -1
  25. package/skills/ai-init/templates/.ai/policies/weight.md +1 -2
  26. package/skills/ai-init/templates/.ai/policies/weight.project.json +4 -0
  27. package/skills/ai-init/templates/.ai/project/architecture.md +1 -1
  28. package/skills/ai-init/templates/.ai/project/initial-assessment.md +2 -2
  29. package/skills/ai-init/templates/.ai/project/overview.md +1 -1
  30. package/skills/ai-init/templates/.ai/rules/README.md +2 -2
  31. package/skills/ai-init/templates/.ai/state/README.md +1 -1
  32. package/skills/ai-task/memory_store.py +8 -9
  33. package/skills/ai-task/rr_state/cli.py +1 -1
  34. package/skills/ai-task/rr_state/dialog.py +2 -4
  35. package/skills/ai-task/rr_state/fs.py +5 -13
  36. package/skills/ai-task/rr_state/profile.py +2 -4
  37. package/skills/ai-task/rr_state/questions_cmd.py +1 -1
  38. package/skills/ai-task/rr_state/report.py +3 -4
  39. package/skills/ai-task/rr_state/runtime.py +1 -1
  40. package/skills/ai-task/rr_state/start.py +7 -7
  41. package/skills/ai-task/sensors.py +10 -11
  42. package/skills/ai-task/state.py +4 -24
  43. package/skills/ai-task/weight.py +17 -17
  44. package/skills/ai-task/weight_overlay.py +38 -0
  45. package/skills/project-init/templates/constitution.md +2 -2
  46. package/skills/project-update/history/blobs/00f5e27ce2d66e3a1d8b222b013ae8fa1fed32612d91a551e0c661cee3ef9c2d +34 -0
  47. package/skills/project-update/history/blobs/0a6b1b6dd06a35582b2e9989a1f41c40abc29254b8e482714fcc6f0a7b16c406 +32 -0
  48. package/skills/project-update/history/blobs/0aeb04b04262683cac6513545683486a73dc473b447bba43b5aaab18403957a6 +34 -0
  49. package/skills/project-update/history/blobs/0ce913a28996f546f72d570b0e5a376362e993ce4d805bc4aae57d38caa44212 +41 -0
  50. package/skills/project-update/history/blobs/0ea9e36446b624b73b2a821231b2ee66a357b43d584a5b23da48b3c800855e65 +41 -0
  51. package/skills/project-update/history/blobs/0fb1d68678a1349195b2e79433bb20174d25874e3bfd0b70410bb4e428b4e4b8 +35 -0
  52. package/skills/project-update/history/blobs/119b2b9d0f9c647a243540724edac6b29afa020c69aa205797bcfa9eb06bc021 +35 -0
  53. package/skills/project-update/history/blobs/1d0fe3ee5fb8260ba610942d4bbdfff7410fbe80ed826ac07f32985031dc2b3f +34 -0
  54. package/skills/project-update/history/blobs/1de9a37ed6402893dcab6e61ddbf15ffd35f15a6d84b18e675a0c5bf4e981f85 +42 -0
  55. package/skills/project-update/history/blobs/1f44e1a236233dcbab58064f39b69f798b35d1d9a29f8c615b5b12854ce633cb +15 -0
  56. package/skills/project-update/history/blobs/1f824645299579cbdd8141642ddbb4bca9fd5fe9d3112f036d8221f0811e0abf +40 -0
  57. package/skills/project-update/history/blobs/235fb88393cfe20dd58b35fe9785cc45baf5ffe21a13d926652d76ec68cea8b7 +35 -0
  58. package/skills/project-update/history/blobs/3918b4ee011d9477b9d2779e2893ab599749c406d4b09439a9748b64fbff58f3 +18 -0
  59. package/skills/project-update/history/blobs/449273200dfec2a56885c881ff34d40e8ecc00da53c24c2d09b7fbeb1004d255 +40 -0
  60. package/skills/project-update/history/blobs/4c45aee9b9fc5f6b4e1d9739e434c4094975a5a8dec6b9703fb25f3080680c77 +124 -0
  61. package/skills/project-update/history/blobs/4dd3fa12bc45acc8c836ab65d790248ca7504a021bca6677499d17bb3d798ed9 +64 -0
  62. package/skills/project-update/history/blobs/51e3f15095d7123ea8566b512479ee10a6cb2f336b7352a259a083a887bd7116 +33 -0
  63. package/skills/project-update/history/blobs/5497a204967bea7dab29079a6e1dfa3d8713b2d2cb294c2779378039f03a55fc +36 -0
  64. package/skills/project-update/history/blobs/60c4d7e7fde7049d92d84295fc13ca9a91eb2de423be08f093b3ee5d5e817ff8 +64 -0
  65. package/skills/project-update/history/blobs/6221b7c14cc04634e485e562d464c6860d45666a7f63e293c06bb2944ff556cc +27 -0
  66. package/skills/project-update/history/blobs/637971cfb844ef4285e2886a4b1c834fb2ed5c46e773adb88c2c147fea626ad3 +33 -0
  67. package/skills/project-update/history/blobs/65af2282eea2a51fcc27a4acf2c26c727b0961c30e1be13176369200c460c219 +34 -0
  68. package/skills/project-update/history/blobs/66ecd73e303e7bbc1aa8ee72435d0d10812ff7b2eff0adac59bbf0a303e2cc01 +41 -0
  69. package/skills/project-update/history/blobs/6a43bf32461b22b87fafe2865529dc04fa66cd00182a2a7598aa071667ca255a +35 -0
  70. package/skills/project-update/history/blobs/6d6dee8634abf9ac057bb206828b83a893a2afcf4f86bbe69e839a3b47d0adde +48 -0
  71. package/skills/project-update/history/blobs/7224ede803ea81e65a456e21b6028a0e848e8a880456c6503eebe7534ca4f6aa +74 -0
  72. package/skills/project-update/history/blobs/765a39a202617096c232017822ae738d37e4405430c71700dc7bc14bf1ba794e +33 -0
  73. package/skills/project-update/history/blobs/77577440a933d1cabd8c6f4a6401cf361a784aa09a25500a6f7b44ead8ab5e55 +41 -0
  74. package/skills/project-update/history/blobs/7bae90789625d6fb9071ecb412d14a8d1eb0bc5659d8d284c4eaf213e1b5de56 +67 -0
  75. package/skills/project-update/history/blobs/7bcdf9a0e6b85277b9b60669150224b9414d2e32821ff06f4d148eb1d18785bb +42 -0
  76. package/skills/project-update/history/blobs/7f834103e9d97aea44e76717bcdcbbe349c560e5b81a42ce85a1cec341041281 +4 -0
  77. package/skills/project-update/history/blobs/7fb3acb0211818efebc2234863db0b8c846022a7280a6d2d69f267dbf63db48c +34 -0
  78. package/skills/project-update/history/blobs/87f0855859ff68cc6c988cc0b631ee7f56af7fbba062172bf80e01f4982cc054 +35 -0
  79. package/skills/project-update/history/blobs/8abab74759766d02b5cb7b0c3e59a3d440b25a454216cd8fea325c23fd822d36 +33 -0
  80. package/skills/project-update/history/blobs/a1fb50e6c86fae1679ef3351296fd6713411a08cf8dd1790a4fd05fae8688164 +1 -0
  81. package/skills/project-update/history/blobs/a3df7268feb0780be0d2cf9acced6ed81780a7790f36fe024b6ea0b1bd6eb7a6 +35 -0
  82. package/skills/project-update/history/blobs/b28721136482dafb9fd2801af340364cb97e9f6eaa987908606c786045b8d334 +28 -0
  83. package/skills/project-update/history/blobs/b3726422d767f9219fea633bea77f0c99ad9af843c591e6e928e1ccd2a47f374 +27 -0
  84. package/skills/project-update/history/blobs/b3839425f9d0ab2efa42a6f5e9488ec2d26ca5fb8dcd694b8ab098e10743ca46 +32 -0
  85. package/skills/project-update/history/blobs/ba14a457e83f9dde65e4190fdfab83b912f7398c1442a2a1cb3d89bfac741cf3 +8 -0
  86. package/skills/project-update/history/blobs/bceb7189444bb6291b262ca08e34db01010dda2f6e64d696231ec3c343c95c66 +34 -0
  87. package/skills/project-update/history/blobs/bf82a2bb4fce1bd349ff66118930a01d61a7af8e2bd8d200c721d43afc58c713 +34 -0
  88. package/skills/project-update/history/blobs/c4dbeb180292a6199ad73a3be861ece07c1f8bf6bf0cc7ca204412bc8e1bb1d8 +31 -0
  89. package/skills/project-update/history/blobs/c644838866df2001a42501a988dd7b77aacf862bf09d22b465f3cd8a22c6b6eb +33 -0
  90. package/skills/project-update/history/blobs/c980d74f76a4bdfb38c94b573ac10a54eb50b850b052e15fa6f2442889b36ccb +32 -0
  91. package/skills/project-update/history/blobs/ceed2f3bd937f862d0e6ca59ce1b1e3603e19a0d7f85d90d6dfeb7bf690153be +29 -0
  92. package/skills/project-update/history/blobs/d51ea2e893482a20c5882056cf8043327ca1dc1c957927f6746929a47ee8368e +45 -0
  93. package/skills/project-update/history/blobs/d78f5e0bb1380259fce2cce009785a9a11f6de8b87a160080b9872c059f95060 +34 -0
  94. package/skills/project-update/history/blobs/d97f46a2cca020406252b1f7baa4184617fa71faca3885e2379c44ae4e1d9327 +67 -0
  95. package/skills/project-update/history/blobs/da3ecd4c4c414c93d04488685931df39a56bf959bfeb9c45cb1aea2c13bd991b +38 -0
  96. package/skills/project-update/history/blobs/e129bf71a5a922e24b2c1bc8daa8a5d2b5589b0069770e17ce17e7db5ed4e7a4 +27 -0
  97. package/skills/project-update/history/blobs/e504529519a371892838725f8b89b5d32e3300e345bcbf2643a3a1a48434b3dd +24 -0
  98. package/skills/project-update/history/blobs/e9e62d03ca70b3dffccef8dceeb214173352cfef005145e519f0e037993f1c03 +36 -0
  99. package/skills/project-update/history/blobs/effefbaaf4beb8f1ba55081d6b7e74bf25ebb91045dd10613418f17b95fa7182 +33 -0
  100. package/skills/project-update/history/blobs/f6af4581bafbf62b257d57e06c3ae2c5998aeca608ee7df037fcc3c3e4b563a9 +34 -0
  101. package/skills/project-update/history/blobs/fc35045777d29630303430752a651b3e553580a581036a02cc103eb96a46f4b0 +34 -0
  102. package/skills/project-update/history/blobs/fdc21345c77f92a42d244aa104f393f439a0ff9981a9fd462bd837e8a47931bb +48 -0
  103. package/skills/project-update/history/index.json +32 -13
  104. package/skills/project-update/render_instructions.py +12 -45
  105. package/skills/project-update/update.py +757 -1172
  106. package/skills/rr-setup/SKILL.md +42 -81
  107. package/skills/rr-setup/scaffold_ai.py +7 -7
  108. package/skills/rr-setup/setup.py +96 -164
  109. package/skills/rr-status/status.py +7 -10
  110. package/skills/usage-report/usage-report.py +4 -5
  111. package/skills/ai-audit/SKILL.md +0 -9
  112. package/skills/ai-init/SKILL.md +0 -9
  113. package/skills/ai-status/SKILL.md +0 -9
  114. package/skills/ai-task/SKILL.md +0 -9
  115. package/skills/project-init/SKILL.md +0 -9
  116. package/skills/project-update/SKILL.md +0 -9
  117. package/skills/project-update/adopt-map.json +0 -91
  118. package/skills/project-update/adopt.py +0 -2225
  119. package/skills/project-update/migrations/0001_schema_version.py +0 -13
  120. package/skills/project-update/migrations/0002_task_journal.py +0 -123
  121. package/skills/project-update/migrations/0003_context_diet.py +0 -65
  122. package/skills/project-update/migrations/0004_deterministic_gates.py +0 -57
  123. package/skills/project-update/migrations/0005_runtimes_and_plans.py +0 -35
  124. package/skills/project-update/migrations/0006_shared_files.py +0 -154
  125. package/skills/project-update/migrations/0007_weight_model.py +0 -295
  126. package/skills/project-update/migrations/0008_renames.py +0 -43
  127. package/skills/project-update/migrations/0009_state_file.py +0 -25
  128. package/skills/project-update/migrations/0010_dialog_switch.py +0 -30
  129. package/skills/project-update/migrations/0011_retire_risk_tiers.py +0 -37
  130. package/skills/project-update/migrations/README.md +0 -76
  131. package/skills/project-update/migrations/__init__.py +0 -186
  132. package/skills/sdlc-intent/SKILL.md +0 -9
  133. package/skills/sdlc-plan/SKILL.md +0 -9
  134. package/skills/sdlc-spec/SKILL.md +0 -9
  135. package/skills/usage-report/SKILL.md +0 -10
  136. /package/{ai/workflows/refactoring.md → skills/project-update/history/blobs/36cb593293496fbcf38df828a9b9b2bc0d5a53333f584a5319ebc3d799016e24} +0 -0
  137. /package/{ai/workflows/feature.md → skills/project-update/history/blobs/56e16072c2f3edc6c8897c690d09a11d12fc414c3e3f361f359f7dc609a21282} +0 -0
  138. /package/{ai/workflows/hotfix.md → skills/project-update/history/blobs/8cbefbf1aa412bae3e5019acd40b8fe0598fceba6c781906b3bc6dbd014f6b23} +0 -0
  139. /package/{ai/workflows/bugfix.md → skills/project-update/history/blobs/af56dc16e44ae1d8c3dac4c3ca82e2163b719553c02ea455e77430bab9a14089} +0 -0
  140. /package/{ai/workflows/investigation.md → skills/project-update/history/blobs/b9e09cb24958cd6831589997558974bd6356305306e8f9685e226bcdc30d60ac} +0 -0
  141. /package/{ai/agents/manager.md → skills/project-update/history/blobs/d0cf6388f35a92a0cf607d33909278382f6d7b6a4c32e281b9f9e0ea652c7b91} +0 -0
@@ -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 brings a project's files up to the installed plugin (migrations, three-way merges, the managed block); --adopt moves a foreign AI-tool structure (Spec Kit, Kiro, Cursor, Copilot, AI-DLC, an oversized CLAUDE.md or AGENTS.md) into this layout with nothing lost; --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] | --adopt [--mode coexist] | --audit [context]
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`, `--adopt` and `--audit` go to their sections below; anything else
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.** Propose this codebase's own paths for
94
- `.ai/policies/weight.json`: `domain.modules` (money, identity, personal
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); once
98
- they have, `$STATE weight --mirror` regenerates `weight.md`. The thresholds
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
- offer **--adopt**'s own-toolbox path.
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 does the update; you show it, confirm what is owed, and merge what
124
- it cannot.
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
- `schema N -> M` means migrations run first; `[NNNN]` marks their lines; a
130
- `move` carries edits to the new path and keeps the original under
131
- `.ai/reports/project-update-<date>/`; `delete?` is a proposal (step 4).
132
- 3. Lines marked `policy` change `.ai/policies/*.json`, which humans own: explain
133
- each in one plain sentence and ask, unless `$ARGUMENTS` holds `--apply`.
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
- **A `delete?` is the human's.** Never pass `--confirm-delete` or `--confirm-cleanup`, not even with
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
- The schema stays put until it is settled — that command, or deleting the
141
- file by hand; there is no third way. Two kinds are housekeeping, not
142
- migrations: `[sdlc]` — an unedited `docs/sdlc/*/TEMPLATE.md`, which the plugin
143
- now supplies (an edited one stays as the project's override); `[cleanup]` —
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. For 0006 (shared files) the order is:
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 `conflict` line: the project file was edited here and changed upstream.
160
- Markdown, or the managed block (inside the markers only): read both, keep
161
- everything project-specific, take the plugin's rules and structure, edit the
162
- project file, delete the copy. A JSON policy: show both values for each
163
- listed path and say which the plugin expects and why; the human edits it.
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. `foreign structure detected`: offer
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"<!-- (riskrails|claude-agentic|[Cc]odex-agentic):start -->")
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 env("CLAUDE_AGENTIC_TEMPLATES") or os.path.join(HERE, "templates")
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: writing .ai/VERSION here would tell /project-update that every migration
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. A legacy
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)
@@ -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 testing.md names one
7
- setup.py adopt [--root DIR] [--apply] .ai/policies/toolbox.md and router rows in .ai/AGENTS.md
8
- setup.py verify [--root DIR] --set CMD write verify_command into testing.md (after the human confirmed)
9
-
10
- survey and check write nothing. adopt is a dry run without --apply. The
11
- toolbox is production behaviour (C1): it is inventoried in place, with links,
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 or an unreadable table;
18
- 3 refused (a file exists and differs, or the line is already set).
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
- MAP_FILE = os.path.join(os.path.dirname(HERE), "project-update", "adopt-map.json")
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 adopt.py dialect: `*` stops at `/`, `**` crosses it, `**/` may match nothing."""
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, table):
264
+ def inventory(root):
222
265
  """[{path, kind, line, purpose, name, router}] in table order, then path order."""
223
- own = table["own"]
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 own["inventory"]:
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, table):
299
+ def guard_overlap(items):
259
300
  """{guard: [hook paths doing its job]} by the hook file name's words."""
260
- guards = table["own"].get("guards") or {}
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 guards.items():
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, table):
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 table["own"].get("verify") or []:
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 testing.md is missing or has no verify_command line; else the
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, TESTING))
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
- table = load_map()
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, table).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, table)
417
+ candidates = verify_candidates(root)
449
418
  if current:
450
- print("verify_command: %s (testing.md)" % current)
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" % TESTING)
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, TESTING)
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" % TESTING)
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, TESTING))
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, TESTING))
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", "adopt", "verify"):
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