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
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
"""0001: record the schema version.
|
|
2
|
-
|
|
3
|
-
A project scaffolded before schema versions existed has .ai/ without .ai/VERSION,
|
|
4
|
-
which is schema 0. This migration has no operations: the .ai/VERSION write that
|
|
5
|
-
closes every migration run is its whole effect.
|
|
6
|
-
"""
|
|
7
|
-
VERSION = 1
|
|
8
|
-
TITLE = "record the schema version in .ai/VERSION"
|
|
9
|
-
MOVES = []
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def plan(ctx): # pylint: disable=unused-argument # no operations; update.py writes .ai/VERSION
|
|
13
|
-
"""Nothing to do."""
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
"""0002: the event journal, and the state keys a task in flight needs for it.
|
|
2
|
-
|
|
3
|
-
Schema 1 recorded a task's history inside the state file and nowhere else. A
|
|
4
|
-
history entry has three fields and no types, so a reader cannot ask it which
|
|
5
|
-
tier a task was raised from, which step a scope change touched, or how an
|
|
6
|
-
approval was given. The journal answers those, one line per change, append-only,
|
|
7
|
-
beside the audit trail it belongs to.
|
|
8
|
-
|
|
9
|
-
Two operations, both idempotent:
|
|
10
|
-
|
|
11
|
-
* the new state keys, added only where they are absent, so a re-run records
|
|
12
|
-
nothing and a task at any stage keeps working;
|
|
13
|
-
* a journal backfilled from `history[]`, written once. Every line it produces
|
|
14
|
-
carries `backfilled: true` and the legacy event name it came from, because a
|
|
15
|
-
line reconstructed after the fact is not the same evidence as one written at
|
|
16
|
-
the moment it happened — and the values that were never recorded (the tier a
|
|
17
|
-
task was raised *from*, who granted an approval and how) stay missing rather
|
|
18
|
-
than being invented.
|
|
19
|
-
|
|
20
|
-
A run interrupted between the two leaves .ai/VERSION at 1, and the next --apply
|
|
21
|
-
completes both.
|
|
22
|
-
"""
|
|
23
|
-
import json
|
|
24
|
-
|
|
25
|
-
VERSION = 2
|
|
26
|
-
TITLE = "add the task journal and the schema-2 state keys"
|
|
27
|
-
MOVES = []
|
|
28
|
-
|
|
29
|
-
# history[] event name -> the journal type it becomes. Anything not named here
|
|
30
|
-
# keeps its name in data.legacy_event and is recorded as "legacy": a type this
|
|
31
|
-
# file does not know is still a fact that happened.
|
|
32
|
-
LEGACY_TYPES = {
|
|
33
|
-
"task_started": "task_started",
|
|
34
|
-
"stage": "stage_started",
|
|
35
|
-
"risk_classified": "tier_set",
|
|
36
|
-
"plan_approved": "plan_registered",
|
|
37
|
-
"step_started": "step_started",
|
|
38
|
-
"step_completed": "step_done",
|
|
39
|
-
"remediation": "step_started",
|
|
40
|
-
"set": "field_set",
|
|
41
|
-
"risk_added": "field_set",
|
|
42
|
-
"risks_cleared": "field_set",
|
|
43
|
-
"modules": "field_set",
|
|
44
|
-
"human_approval": "gate_approved",
|
|
45
|
-
"task_closed": "task_closed",
|
|
46
|
-
"schema_migrated": "schema_migrated",
|
|
47
|
-
}
|
|
48
|
-
STAGES_IN_DETAIL = " -> "
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def _add_defaults(state):
|
|
52
|
-
"""The schema-2 keys, added only where they are absent."""
|
|
53
|
-
task_id = state.get("task_id") or ""
|
|
54
|
-
state.setdefault("owner_runtime", None)
|
|
55
|
-
state.setdefault("resume_point", None)
|
|
56
|
-
state.setdefault("questions",
|
|
57
|
-
{"file": ".ai/reports/%s/questions.md" % task_id, "pending": []})
|
|
58
|
-
state.setdefault("handoff",
|
|
59
|
-
{"file": ".ai/state/handoff.md", "written_at": None, "reason": None})
|
|
60
|
-
approval = state.setdefault("human_approval", {})
|
|
61
|
-
if isinstance(approval, dict):
|
|
62
|
-
for key, value in (("requested_at", None), ("gate_id", None),
|
|
63
|
-
("requested_session", None), ("rejected_at", None),
|
|
64
|
-
("via", None), ("unattended", False)):
|
|
65
|
-
approval.setdefault(key, value)
|
|
66
|
-
return state
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def _stage_of(detail, current):
|
|
70
|
-
"""The stage a history entry left the task in, read out of its own text."""
|
|
71
|
-
if STAGES_IN_DETAIL not in detail:
|
|
72
|
-
return current
|
|
73
|
-
return detail.split(STAGES_IN_DETAIL)[-1].split(":")[0].strip() or current
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def _line(entry, stage, task_id):
|
|
77
|
-
legacy = entry.get("event") or "legacy"
|
|
78
|
-
detail = entry.get("detail") or ""
|
|
79
|
-
data = {"backfilled": True, "legacy_event": legacy}
|
|
80
|
-
if legacy == "stage" and STAGES_IN_DETAIL in detail:
|
|
81
|
-
data["from"] = detail.split(STAGES_IN_DETAIL)[0].strip()
|
|
82
|
-
data["to"] = stage
|
|
83
|
-
elif legacy == "risk_classified":
|
|
84
|
-
data["tier"] = detail.split(":")[0].strip()
|
|
85
|
-
data["from"] = None
|
|
86
|
-
elif legacy in ("step_completed", "step_started", "remediation"):
|
|
87
|
-
data["step_id"] = detail.split(":")[0].strip()
|
|
88
|
-
if legacy == "remediation":
|
|
89
|
-
data["kind"] = "remediation"
|
|
90
|
-
elif legacy == "human_approval":
|
|
91
|
-
data["via"] = "legacy"
|
|
92
|
-
elif legacy in ("set", "risk_added", "risks_cleared", "modules"):
|
|
93
|
-
data["field"] = detail.split(" = ")[0].strip() if " = " in detail else legacy
|
|
94
|
-
return {
|
|
95
|
-
"ts": entry.get("at") or "",
|
|
96
|
-
"task": task_id,
|
|
97
|
-
"event": LEGACY_TYPES.get(legacy, "legacy"),
|
|
98
|
-
"actor": "migration",
|
|
99
|
-
"runtime": "unknown",
|
|
100
|
-
"stage": stage,
|
|
101
|
-
"detail": detail[:500],
|
|
102
|
-
"data": data,
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def _backfill(state):
|
|
107
|
-
"""One line per history entry, in order, with the stage each one left."""
|
|
108
|
-
task_id = state.get("task_id") or "unknown"
|
|
109
|
-
stage, out = "", []
|
|
110
|
-
for entry in state.get("history") or []:
|
|
111
|
-
if not isinstance(entry, dict):
|
|
112
|
-
continue
|
|
113
|
-
stage = _stage_of(entry.get("detail") or "", stage)
|
|
114
|
-
out.append(json.dumps(_line(entry, stage, task_id), ensure_ascii=False))
|
|
115
|
-
return ("\n".join(out) + "\n").encode("utf-8") if out else b""
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def plan(ctx):
|
|
119
|
-
ctx.patch_state(_add_defaults)
|
|
120
|
-
state = ctx.state
|
|
121
|
-
if not state or not state.get("task_id"):
|
|
122
|
-
return # no task in flight: the journal starts empty
|
|
123
|
-
ctx.create(".ai/reports/%s/events.jsonl" % state["task_id"], _backfill(state))
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
"""0003: take the pipeline text out of the root instruction files.
|
|
2
|
-
|
|
3
|
-
Every root instruction file used to carry a `## SDLC workflow` section that
|
|
4
|
-
repeated, in prose, what the managed block now routes to. Two copies of the same
|
|
5
|
-
rule is one copy too many: the section is loaded into every session, it drifts
|
|
6
|
-
away from the block the moment either one is edited, and it is the part of the
|
|
7
|
-
file a human never wrote.
|
|
8
|
-
|
|
9
|
-
The section is removed only when the bytes in the project's file are bytes this
|
|
10
|
-
plugin shipped — matched against a version in the template history, never
|
|
11
|
-
against a pattern over the human's own prose. Anything else is a hint:
|
|
12
|
-
|
|
13
|
-
* a section that was edited is the human's text now, and only they can merge it;
|
|
14
|
-
* a managed block that was edited is never written over, here or anywhere else.
|
|
15
|
-
|
|
16
|
-
The file is the project's, not the plugin's, so `apply` keeps the original under
|
|
17
|
-
`.ai/reports/project-update-<date>/original/` before the rewrite.
|
|
18
|
-
|
|
19
|
-
Nothing here is a conflict and nothing is a deletion, so this migration never
|
|
20
|
-
holds `.ai/VERSION`: a project whose sections were all edited still reaches the
|
|
21
|
-
current schema, carrying two hints.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
VERSION = 3
|
|
25
|
-
TITLE = "move the pipeline text out of the instruction files (context diet)"
|
|
26
|
-
MOVES = []
|
|
27
|
-
|
|
28
|
-
HEADING = b"\n## SDLC workflow"
|
|
29
|
-
NEXT_SECTION = b"\n## "
|
|
30
|
-
NOTE = "SDLC workflow section removed (now in the managed block)"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def _section(blob):
|
|
34
|
-
"""The `## SDLC workflow` section of one shipped template, or None: from the
|
|
35
|
-
newline before its heading to the newline before the next heading, so cutting
|
|
36
|
-
it out of a file leaves the surrounding blank lines as they were."""
|
|
37
|
-
start = blob.find(HEADING)
|
|
38
|
-
if start < 0:
|
|
39
|
-
return None
|
|
40
|
-
end = blob.find(NEXT_SECTION, start + 1)
|
|
41
|
-
return blob[start:] if end < 0 else blob[start:end]
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def _shipped_section(text, shipped):
|
|
45
|
-
"""The one shipped section this file carries verbatim, or None."""
|
|
46
|
-
for blob in shipped:
|
|
47
|
-
section = _section(blob)
|
|
48
|
-
if section and section in text:
|
|
49
|
-
return section
|
|
50
|
-
return None
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def plan(ctx):
|
|
54
|
-
for path in ctx.instruction_files():
|
|
55
|
-
text = ctx.read(path)
|
|
56
|
-
section = _shipped_section(text, ctx.shipped(path))
|
|
57
|
-
if section:
|
|
58
|
-
ctx.edit_text(path, lambda t, s=section: t.replace(s, b"", 1), NOTE)
|
|
59
|
-
elif HEADING in text:
|
|
60
|
-
ctx.hint("%s: the SDLC workflow section was edited, so it is yours to keep — "
|
|
61
|
-
"the managed block below it now says the same thing" % path)
|
|
62
|
-
if ctx.block_status(path) == "edited":
|
|
63
|
-
ctx.hint("%s: the managed block was edited here, so the plugin's stub cannot "
|
|
64
|
-
"replace it — move your lines below the block and run /rr-setup --update "
|
|
65
|
-
"again" % path)
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
"""0004: the state keys the deterministic gates measure into.
|
|
2
|
-
|
|
3
|
-
Schema 3 recorded what a task decided; it recorded nothing about what the task
|
|
4
|
-
actually changed. `state.py step-done` now measures the step's diff between two
|
|
5
|
-
tree objects, re-scores the tier from it, and the review gate reads a sensor
|
|
6
|
-
report — all of which need somewhere to live in the state file.
|
|
7
|
-
|
|
8
|
-
One operation, and it adds keys only where they are absent, so a re-run records
|
|
9
|
-
nothing and a task at any stage keeps working. Nothing is backfilled and
|
|
10
|
-
nothing is invented: a task that started before this schema has no base tree,
|
|
11
|
-
and there is no honest way to reconstruct one after the fact. Its measurements
|
|
12
|
-
read `unavailable`, which keeps every review its tier asks for — the safe
|
|
13
|
-
reading, and the true one.
|
|
14
|
-
|
|
15
|
-
The policy side of this change (the diff budget, the path scopes, the sensor
|
|
16
|
-
set in `risk-tiers.json`) arrives through the normal template walk, which
|
|
17
|
-
merges a JSON policy per key and re-hashes a mirror that was in step. A
|
|
18
|
-
migration that rewrote the same file would ask the human to confirm the same
|
|
19
|
-
change twice.
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
VERSION = 4
|
|
23
|
-
TITLE = "add the diff, sensor and test-run keys a task in flight needs"
|
|
24
|
-
MOVES = []
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def _add_defaults(state):
|
|
28
|
-
"""The schema-4 keys, added only where they are absent."""
|
|
29
|
-
task_id = state.get("task_id") or ""
|
|
30
|
-
diff = state.setdefault("diff", {})
|
|
31
|
-
diff.setdefault("base_commit", None)
|
|
32
|
-
diff.setdefault("base_tree", None)
|
|
33
|
-
diff.setdefault("task", {"files": 0, "added": 0, "deleted": 0, "lines": 0,
|
|
34
|
-
"excluded_lines": 0, "unbudgeted_lines": 0, "tree": None,
|
|
35
|
-
"measured_at": None, "status": "not_measured"})
|
|
36
|
-
diff.setdefault("rescored_tier", None)
|
|
37
|
-
diff.setdefault("rescore_reasons", [])
|
|
38
|
-
diff.setdefault("over_budget", False)
|
|
39
|
-
state.setdefault("sensors", {"file": ".ai/reports/%s/sensors.json" % task_id,
|
|
40
|
-
"tree": None, "verdict": None, "checked_at": None})
|
|
41
|
-
state.setdefault("tests", {"runs": [], "suite_runs": 0})
|
|
42
|
-
state.setdefault("risk_tier_lowered", {"by": None, "at": None, "from": None})
|
|
43
|
-
for step in ((state.get("approved_plan") or {}).get("steps") or []):
|
|
44
|
-
if not isinstance(step, dict):
|
|
45
|
-
continue
|
|
46
|
-
# A step's kind is what the must-bite check reads: a remediation step
|
|
47
|
-
# proves nothing on its own, and the R-prefix is how they were named
|
|
48
|
-
# before there was a field to say so.
|
|
49
|
-
step.setdefault("kind", "remediation"
|
|
50
|
-
if str(step.get("step_id", "")).startswith("R") else "implementation")
|
|
51
|
-
step.setdefault("tree_before", None)
|
|
52
|
-
step.setdefault("diff", None)
|
|
53
|
-
return state
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def plan(ctx):
|
|
57
|
-
ctx.patch_state(_add_defaults)
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
"""0005: the state keys a task needs to move between runtimes on purpose.
|
|
2
|
-
|
|
3
|
-
Schema 4 already recorded which runtime owns a task and where it resumes, but
|
|
4
|
-
a move was only ever noticed after the fact, by the next command that ran
|
|
5
|
-
somewhere else. `state.py handoff --to` now hands a task over deliberately and
|
|
6
|
-
waits for the receiving side to pick it up; a T4+ review can be asked of the
|
|
7
|
-
other side. Both need a place in the state file.
|
|
8
|
-
|
|
9
|
-
One operation, adding keys only where they are absent, so a re-run records
|
|
10
|
-
nothing and a task at any stage keeps working. `state.py` fills the same
|
|
11
|
-
defaults in memory, so a task in flight works whether or not this has run; the
|
|
12
|
-
migration exists so the project reports itself behind until it has, and so
|
|
13
|
-
`schema_migrated` lands in the journal. Nothing is backfilled: a task that was
|
|
14
|
-
never handed over has nothing pending, and no review was ever asked of anyone.
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
VERSION = 5
|
|
18
|
-
TITLE = "add the pending-handoff and cross-vendor-review keys a task in flight needs"
|
|
19
|
-
MOVES = []
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def _add_defaults(state):
|
|
23
|
-
"""The schema-5 keys, added only where they are absent."""
|
|
24
|
-
handoff = state.get("handoff")
|
|
25
|
-
if not isinstance(handoff, dict):
|
|
26
|
-
handoff = state["handoff"] = {"file": ".ai/state/handoff.md", "written_at": None,
|
|
27
|
-
"reason": None}
|
|
28
|
-
handoff.setdefault("pending_to", None)
|
|
29
|
-
handoff.setdefault("pending_since", None)
|
|
30
|
-
state.setdefault("cross_vendor_review", None)
|
|
31
|
-
return state
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def plan(ctx):
|
|
35
|
-
ctx.patch_state(_add_defaults)
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
"""0006: the shared policies, agent contracts, workflows and templates are read
|
|
2
|
-
from the plugin, not from a copy in every project.
|
|
3
|
-
|
|
4
|
-
Twenty-nine files under `.ai/` were the same in every project that never edited
|
|
5
|
-
them, and each one was merged on every update. They now ship once, in the
|
|
6
|
-
plugin's shared tree, and a project reads them from there; a file at the same
|
|
7
|
-
path under `.ai/` still wins, as an override. The project keeps what is its own:
|
|
8
|
-
`project/`, `rules/`, `state/`, `reports/`, `VERSION`, and the policies
|
|
9
|
-
risk-tiers.json, risk-tiers.md, path-guard.json, testing.md, coding.md,
|
|
10
|
-
safety.md and security.md. Those two stay project-owned, so an instruction file
|
|
11
|
-
that names `.ai/policies/safety.md` keeps pointing at a file that exists.
|
|
12
|
-
|
|
13
|
-
The router goes first: before any copy is proposed for deletion, `.ai/AGENTS.md`
|
|
14
|
-
says where the shared files now live. `apply` stops at the first item that
|
|
15
|
-
fails, so no deletion lands while the router is unwritten.
|
|
16
|
-
|
|
17
|
-
* no router: the shipped one is created;
|
|
18
|
-
* a router that already names the shared tree: nothing;
|
|
19
|
-
* a router the plugin shipped, unedited: replaced by the current one;
|
|
20
|
-
* an edited router: the section is inserted before the heading that follows
|
|
21
|
-
it in the shipped router, or appended when that heading was renamed. The
|
|
22
|
-
human's lines are kept as they are.
|
|
23
|
-
|
|
24
|
-
Then each shared copy the project has:
|
|
25
|
-
|
|
26
|
-
* reached through a symlink, or resolving outside the project: a hint, never
|
|
27
|
-
deleted;
|
|
28
|
-
* edited: a hint that it is now an override and no longer receives changes;
|
|
29
|
-
* byte-identical to a version the plugin shipped (CRLF counts): proposed for
|
|
30
|
-
deletion, once every installed home has the shared file it falls back to.
|
|
31
|
-
|
|
32
|
-
A deletion waits for --apply --confirm-delete NAME, and `.ai/VERSION` stays
|
|
33
|
-
where it was until it is settled. The original of every deleted file is kept
|
|
34
|
-
under `.ai/reports/project-update-<date>/original/`; git is the rollback.
|
|
35
|
-
"""
|
|
36
|
-
import re
|
|
37
|
-
|
|
38
|
-
from migrations import SchemaError
|
|
39
|
-
|
|
40
|
-
VERSION = 6
|
|
41
|
-
TITLE = "read the shared policies, agent contracts, workflows and templates from the plugin"
|
|
42
|
-
MOVES = []
|
|
43
|
-
|
|
44
|
-
ROUTER = ".ai/AGENTS.md"
|
|
45
|
-
HEADING = b"\n## Shared files"
|
|
46
|
-
NEXT_SECTION = b"\n## "
|
|
47
|
-
HAS_SECTION = re.compile(rb"(?m)^## Shared files[ \t]*\r?$")
|
|
48
|
-
|
|
49
|
-
# The shared set, literally: the plugin's tree must equal it (tested). A file
|
|
50
|
-
# that projects once held a copy of is added by a new migration, which plans
|
|
51
|
-
# that copy's deletion; one no project ever held needs only its name here.
|
|
52
|
-
SHARED = (
|
|
53
|
-
".ai/policies/production.md",
|
|
54
|
-
".ai/policies/database.md",
|
|
55
|
-
".ai/policies/git.md",
|
|
56
|
-
".ai/policies/release.md",
|
|
57
|
-
".ai/policies/context-management.md",
|
|
58
|
-
".ai/policies/tooling.md",
|
|
59
|
-
".ai/policies/model-routing.md",
|
|
60
|
-
".ai/policies/review-economy.md",
|
|
61
|
-
".ai/agents/context.md",
|
|
62
|
-
".ai/agents/discovery.md",
|
|
63
|
-
".ai/agents/implementer.md",
|
|
64
|
-
".ai/agents/manager.md",
|
|
65
|
-
".ai/agents/planner.md",
|
|
66
|
-
".ai/agents/release.md",
|
|
67
|
-
".ai/agents/reviewer.md",
|
|
68
|
-
".ai/agents/risk.md",
|
|
69
|
-
".ai/agents/security.md",
|
|
70
|
-
".ai/agents/tester.md",
|
|
71
|
-
".ai/workflows/feature.md",
|
|
72
|
-
".ai/workflows/bugfix.md",
|
|
73
|
-
".ai/workflows/hotfix.md",
|
|
74
|
-
".ai/workflows/investigation.md",
|
|
75
|
-
".ai/workflows/refactoring.md",
|
|
76
|
-
".ai/templates/task-context.md",
|
|
77
|
-
".ai/templates/impact-report.md",
|
|
78
|
-
".ai/templates/review-report.md",
|
|
79
|
-
".ai/templates/release-report.md",
|
|
80
|
-
".ai/templates/architecture-decision.md",
|
|
81
|
-
".ai/templates/implementation-plan.md",
|
|
82
|
-
# Added after 0006 shipped (redesign 2.3), so declared_shared() knows them.
|
|
83
|
-
# They were never copied into a project, so a schema < 6 project has
|
|
84
|
-
# neither and plan() skips both.
|
|
85
|
-
".ai/using-riskrails.md",
|
|
86
|
-
".ai/workflows.md",
|
|
87
|
-
)
|
|
88
|
-
REASON = "identical to a version the plugin shipped; its shared copy applies"
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def _section(blob):
|
|
92
|
-
"""The `## Shared files` section of the shipped router and the line that
|
|
93
|
-
follows it: from the newline before its heading to the newline before the
|
|
94
|
-
next heading, so inserting it leaves the blank lines as they were."""
|
|
95
|
-
start = blob.find(HEADING)
|
|
96
|
-
if start < 0:
|
|
97
|
-
return None, None
|
|
98
|
-
end = blob.find(NEXT_SECTION, start + 1)
|
|
99
|
-
if end < 0:
|
|
100
|
-
return blob[start:], None
|
|
101
|
-
anchor_end = blob.find(b"\n", end + 1)
|
|
102
|
-
return blob[start:end], blob[end:] if anchor_end < 0 else blob[end:anchor_end]
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def _add_section(text, section, anchor):
|
|
106
|
-
"""The router with the section in it, its own lines untouched."""
|
|
107
|
-
crlf = b"\r\n" in text
|
|
108
|
-
if anchor:
|
|
109
|
-
at = text.find(anchor + (b"\r\n" if crlf else b"\n"))
|
|
110
|
-
if at < 0 and text.endswith(anchor):
|
|
111
|
-
at = len(text) - len(anchor)
|
|
112
|
-
if at >= 0:
|
|
113
|
-
if crlf: # inserted after the \r of the line before the anchor
|
|
114
|
-
section = section.replace(b"\n", b"\r\n")[1:] + b"\r"
|
|
115
|
-
return text[:at] + section + text[at:]
|
|
116
|
-
eol = b"\r\n" if crlf else b"\n"
|
|
117
|
-
return text.rstrip(b"\r\n") + eol + section.rstrip(b"\n").replace(b"\n", eol) + eol
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def _router(ctx):
|
|
121
|
-
shipped = ctx.template(ROUTER)
|
|
122
|
-
text = ctx.read(ROUTER)
|
|
123
|
-
if text is None:
|
|
124
|
-
ctx.create(ROUTER, shipped)
|
|
125
|
-
elif HAS_SECTION.search(text):
|
|
126
|
-
return
|
|
127
|
-
elif ctx.is_shipped(ROUTER, text):
|
|
128
|
-
ctx.edit_text(ROUTER, lambda _t: shipped, "unchanged since it was installed; now names the shared files")
|
|
129
|
-
else:
|
|
130
|
-
section, anchor = _section(shipped)
|
|
131
|
-
if section is None:
|
|
132
|
-
raise SchemaError("%04d: the shipped router has no Shared files section" % VERSION)
|
|
133
|
-
ctx.edit_text(ROUTER, lambda t: _add_section(t, section, anchor),
|
|
134
|
-
"Shared files section added; your router edits kept")
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def plan(ctx):
|
|
138
|
-
_router(ctx)
|
|
139
|
-
candidates = []
|
|
140
|
-
for path in SHARED:
|
|
141
|
-
if not ctx.exists(path):
|
|
142
|
-
continue
|
|
143
|
-
why = ctx.unsafe_target(path)
|
|
144
|
-
if why:
|
|
145
|
-
ctx.hint("%s: %s, so this update never deletes it — check by hand whether it is "
|
|
146
|
-
"still needed" % (path, why))
|
|
147
|
-
elif not ctx.is_shipped(path, ctx.read(path)):
|
|
148
|
-
ctx.hint(ctx.override_note(path))
|
|
149
|
-
else:
|
|
150
|
-
candidates.append(path)
|
|
151
|
-
if candidates:
|
|
152
|
-
ctx.require_shared(candidates)
|
|
153
|
-
for path in candidates:
|
|
154
|
-
ctx.delete(path, REASON)
|