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.
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,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)