@rungs/cli 0.3.0 → 0.4.0

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 (74) hide show
  1. package/README.md +6 -6
  2. package/dist/cli.js +2194 -488
  3. package/dist/cli.js.map +4 -4
  4. package/modules/README.md +25 -3
  5. package/modules/adr/files/{{path}}/README.md +1 -1
  6. package/modules/adr/gates/adr.toml +1 -1
  7. package/modules/adr/module.toml +1 -1
  8. package/modules/audit/fragments/AGENTS.md +2 -2
  9. package/modules/audit/module.toml +1 -1
  10. package/modules/audit/skills/assess/SKILL.md +1 -1
  11. package/modules/backlog/files/docs/{{root}}/BACKLOG.md +1 -1
  12. package/modules/backlog/files/docs/{{root}}/README.md +2 -2
  13. package/modules/backlog/files/docs/{{root}}/archive/README.md +1 -1
  14. package/modules/backlog/files/docs/{{root}}/items/README.md +1 -1
  15. package/modules/backlog/fragments/AGENTS.md +2 -2
  16. package/modules/backlog/module.toml +1 -1
  17. package/modules/backlog/skills/work-item/SKILL.md +1 -1
  18. package/modules/ci/files/{{workflow_path}} +3 -3
  19. package/modules/ci/module.toml +1 -1
  20. package/modules/concurrency/files/docs/concurrent-sessions.md +66 -18
  21. package/modules/concurrency/fragments/AGENTS.md +5 -4
  22. package/modules/concurrency/fragments/gitattributes +2 -2
  23. package/modules/concurrency/gates/concurrency.toml +3 -3
  24. package/modules/concurrency/module.toml +1 -1
  25. package/modules/doc-authority/files/{{registry_path}} +1 -1
  26. package/modules/doc-authority/module.toml +1 -1
  27. package/modules/findings/files/docs/{{backlog.root}}/FINDINGS.md +1 -1
  28. package/modules/findings/gates/findings.toml +5 -0
  29. package/modules/findings/module.toml +1 -1
  30. package/modules/findings/skills/record-finding/SKILL.md +1 -1
  31. package/modules/gates/files/.ai/gates.toml +1 -1
  32. package/modules/gates/fragments/AGENTS.md +6 -5
  33. package/modules/gates/module.toml +1 -1
  34. package/modules/instructions/files/.ai/rules/README.md +2 -2
  35. package/modules/instructions/files/.ai/rungs.mjs +52 -0
  36. package/modules/instructions/files/AGENTS.md +4 -2
  37. package/modules/instructions/files/CLAUDE.md +1 -1
  38. package/modules/instructions/fragments/AGENTS.md +2 -2
  39. package/modules/instructions/gates/core.toml +2 -2
  40. package/modules/instructions/module.toml +1 -1
  41. package/modules/release/files/{{changelog_dir}}/CONSUMED_THROUGH +1 -0
  42. package/modules/release/gates/release.toml +169 -17
  43. package/modules/release/module.toml +9 -5
  44. package/modules/release/skills/cut-release/SKILL.md +43 -15
  45. package/modules/session/files/{{archive}}/README.md +1 -1
  46. package/modules/session/files/{{path}} +2 -2
  47. package/modules/session/module.toml +1 -1
  48. package/modules/specs/files/{{path}}/README.md +2 -2
  49. package/modules/specs/module.toml +1 -1
  50. package/modules/workflows/module.toml +1 -1
  51. package/modules/workflows/rules/planning-tiers.md +1 -1
  52. package/package.json +3 -2
  53. package/src/add.ts +204 -48
  54. package/src/backlog.ts +354 -48
  55. package/src/check.ts +54 -33
  56. package/src/cli.ts +196 -69
  57. package/src/concurrency.ts +628 -42
  58. package/src/detect.ts +11 -3
  59. package/src/emitted-path.ts +274 -0
  60. package/src/engine-table.ts +66 -0
  61. package/src/engines.ts +40 -32
  62. package/src/engines2.ts +424 -29
  63. package/src/engines3.ts +115 -23
  64. package/src/explain.ts +3 -7
  65. package/src/help.ts +43 -0
  66. package/src/lifecycle.ts +95 -31
  67. package/src/manifest.ts +41 -5
  68. package/src/render.ts +106 -21
  69. package/src/selftest.ts +87 -10
  70. package/src/storage-key.ts +20 -0
  71. package/src/substitute.ts +47 -5
  72. package/src/text.ts +11 -0
  73. package/src/types.ts +16 -3
  74. package/src/version-source.ts +144 -0
@@ -1,6 +1,6 @@
1
1
  [module]
2
2
  name = "instructions"
3
- version = "1.1.0"
3
+ version = "1.2.0"
4
4
  rung = 0
5
5
  summary = "The agent entry point, the harness bridge, path-scoped rule sources, and the render pipeline. The one module every repo installs."
6
6
 
@@ -6,11 +6,12 @@
6
6
  # tests and tooling changes do not need release notes, and a gate that demands one for a typo fix
7
7
  # is a gate people learn to bypass.
8
8
 
9
- [file_population]
9
+ [change_requires_file]
10
+ base_branch = "{{backlog.integration_branch}}"
10
11
  require_when_changed = ["src/**", "lib/**", "app/**", "server/**", "web/**", "packages/**"]
11
12
  requires_one_of = ["{{changelog_dir}}/*.md"]
12
13
  ignore_when_only = ["docs/**", "**/*.test.*", "**/*.spec.*", ".github/**", "*.md"]
13
- exempt_marker = "changelog-ok:" # ignored unless it states a reason
14
+ exempt_marker = "changelog-ok:" # ignored unless followed by substantive text on that line
14
15
  message = """
15
16
  This branch changes shipping code and adds no fragment in {{changelog_dir}}/. Add one, or state why
16
17
  not with `changelog-ok: <reason>` — internal refactors with no user-visible effect are a legitimate
@@ -37,7 +38,7 @@ rule = "all-agree"
37
38
  # version is a judgement about *your* repo, and the engine guessing it is how a healthy layout gets
38
39
  # a red gate. rungs' own docs site is the case that found this — a private, never-published package
39
40
  # sitting at its own version beside the CLI (F-023).
40
- exclude = []
41
+ exclude = ["{{version_exclude}}"]
41
42
 
42
43
  # No `autofix` key. It named a "rungs release sync-version" command that does not exist and never
43
44
  # has, so the message told whoever hit this gate to run something that would fail. The engine now
@@ -63,6 +64,11 @@ note = "A green check means these files agree, not that the version is correct e
63
64
  # The other half of the fragment discipline. `release-changelog-fragment` catches a release-worthy
64
65
  # change that added no fragment; this catches a fragment that was consumed and never deleted.
65
66
  #
67
+ # `CONSUMED_THROUGH` is the offline assertion that separates a fragment being prepared from an
68
+ # equal-version fragment already assembled into a release. It is consumer-maintained state: the
69
+ # module emits `UNINITIALIZED`, and the owner explicitly chooses `none` or the last consumed
70
+ # version. A concrete value equals the package version in a steady tree.
71
+ #
66
72
  # Only filenames that parse as a version are judged. A repo whose fragments are named per-change
67
73
  # (`42.feature.md`) states no version, so there is nothing to compare and nothing is claimed — a
68
74
  # gate that refused a naming convention it was not asked about is one people turn off.
@@ -70,11 +76,17 @@ note = "A green check means these files agree, not that the version is correct e
70
76
  [[changelog_freshness]]
71
77
  id = "release-fragment-current"
72
78
  fragments = ["{{changelog_dir}}/*.md"]
73
- version = { file = "package.json", path = "version" }
79
+ versions = [
80
+ { file = "package.json", path = "version" },
81
+ { file = "Directory.Build.props", xpath = "//Version" },
82
+ { file = "pyproject.toml", path = "project.version" },
83
+ { file = "*/package.json", path = "version" },
84
+ ]
85
+ consumed_through = "{{changelog_dir}}/CONSUMED_THROUGH"
74
86
  message = """
75
- This fragment names a version below the one being prepared, so it belongs to a release that already
76
- shipped. Delete it — fragments are consumed, not archived, and one left behind is republished as
77
- though it were new work.
87
+ This fragment names an already-consumed version or falls below the package version being prepared.
88
+ Delete it — fragments are consumed, not archived, and one left behind is republished as though it
89
+ were new work.
78
90
  """
79
91
 
80
92
  # ── self-tests ────────────────────────────────────────────────────────────────
@@ -104,29 +116,117 @@ gate = "release-changelog-fragment"
104
116
  expect = "fail"
105
117
  fixture = { changed = ["src/a.ts"], fragments = [], exempt = "changelog-ok:" }
106
118
 
119
+ # The tracked consumption marker is state, not release-note content. Its extensionless name must
120
+ # not satisfy the changed-fragment obligation.
121
+ [[self_test]]
122
+ gate = "release-changelog-fragment"
123
+ expect = "fail"
124
+ fixture = { changed = ["src/a.ts"], fragments = ["{{changelog_dir}}/CONSUMED_THROUGH"] }
125
+
126
+ [[self_test]]
127
+ gate = "release-changelog-fragment"
128
+ expect = "fail"
129
+ fixture = { changed = ["src/a.ts"], fragments = [], inherited_exempt = "changelog-ok: historical internal rename" }
130
+
131
+ [[self_test]]
132
+ gate = "release-changelog-fragment"
133
+ expect = "pass"
134
+ fixture = { changed = ["src/a.ts"], fragments = [], inherited_exempt = "changelog-ok: historical internal rename", exempt = "changelog-ok: new cache path; output is unchanged" }
135
+
136
+ [[self_test]]
137
+ gate = "release-fragment-current"
138
+ expect = "fail"
139
+ fixture = { dir = "changelog.d", version = "0.2.0", fragments = [] }
140
+
107
141
  [[self_test]]
108
142
  gate = "release-fragment-current"
109
143
  expect = "fail"
110
- fixture = { dir = "changelog.d", version = "0.2.0", fragments = ["0.1.1.md"] }
144
+ fixture = { dir = "changelog.d", version = "0.2.0", consumed_through = "UNINITIALIZED", fragments = [] }
145
+
146
+ [[self_test]]
147
+ gate = "release-fragment-current"
148
+ expect = "fail"
149
+ fixture = { dir = "changelog.d", version = "0.2.0", consumed_through = "", fragments = [] }
150
+
151
+ [[self_test]]
152
+ gate = "release-fragment-current"
153
+ expect = "fail"
154
+ fixture = { dir = "changelog.d", version = "0.2.0", consumed_through = "v0.2.0", fragments = [] }
155
+
156
+ [[self_test]]
157
+ gate = "release-fragment-current"
158
+ expect = "fail"
159
+ fixture = { dir = "changelog.d", version = "0.2.0", consumed_through = " 0.2.0 ", fragments = [] }
160
+
161
+ [[self_test]]
162
+ gate = "release-fragment-current"
163
+ expect = "fail"
164
+ fixture = { dir = "changelog.d", version = "0.2.0", consumed_through = "\uFEFF0.2.0", fragments = [] }
165
+
166
+ [[self_test]]
167
+ gate = "release-fragment-current"
168
+ expect = "fail"
169
+ fixture = { dir = "changelog.d", version = "0.2.0", consumed_through = "0.2.0\n", fragments = [] }
170
+
171
+ [[self_test]]
172
+ gate = "release-fragment-current"
173
+ expect = "fail"
174
+ fixture = { dir = "changelog.d", version = "0.2.0", consumed_through = "0.1.0", fragments = [] }
175
+
176
+ [[self_test]]
177
+ gate = "release-fragment-current"
178
+ expect = "fail"
179
+ fixture = { dir = "changelog.d", version = "0.2.0", consumed_through = "0.3.0", fragments = [] }
180
+
181
+ # `none` is the explicit first-release exception. The equal fragment is still being prepared.
182
+ [[self_test]]
183
+ gate = "release-fragment-current"
184
+ expect = "pass"
185
+ fixture = { dir = "changelog.d", version = "0.2.0", consumed_through = "none", fragments = ["0.2.0.md"] }
111
186
 
112
187
  [[self_test]]
113
188
  gate = "release-fragment-current"
114
189
  expect = "pass"
115
- fixture = { dir = "changelog.d", version = "0.2.0", fragments = ["0.2.0.md"] }
190
+ fixture = { dir = "changelog.d", version = "0.2.0", consumed_through = "0.2.0", fragments = [] }
191
+
192
+ # F-025: after a cut, the forgotten fragment and package version are equal. The concrete
193
+ # consumption boundary is what makes that shape fail rather than look like preparation.
194
+ [[self_test]]
195
+ gate = "release-fragment-current"
196
+ expect = "fail"
197
+ fixture = { dir = "changelog.d", version = "0.2.0", consumed_through = "0.2.0", fragments = ["0.2.0.md"] }
198
+
199
+ [[self_test]]
200
+ gate = "release-fragment-current"
201
+ expect = "fail"
202
+ fixture = { dir = "changelog.d", version = "0.2.0", consumed_through = "0.2.0", fragments = ["0.1.1.md"] }
116
203
 
117
204
  # A fragment for a version *above* the one being prepared is not stale — it is a branch that
118
205
  # already decided it is a minor while the package has not been bumped yet.
119
206
  [[self_test]]
120
207
  gate = "release-fragment-current"
121
208
  expect = "pass"
122
- fixture = { dir = "changelog.d", version = "0.2.0", fragments = ["0.3.0.md"] }
209
+ fixture = { dir = "changelog.d", version = "0.2.0", consumed_through = "0.2.0", fragments = ["0.3.0.md"] }
123
210
 
124
211
  # Per-change fragment naming states no version, so there is nothing to compare and nothing is
125
212
  # claimed. The gate must stay silent rather than refuse a convention it was not asked about.
126
213
  [[self_test]]
127
214
  gate = "release-fragment-current"
128
215
  expect = "pass"
129
- fixture = { dir = "changelog.d", version = "0.2.0", fragments = ["42.feature.md"] }
216
+ fixture = { dir = "changelog.d", version = "0.2.0", consumed_through = "0.2.0", fragments = ["42.feature.md"] }
217
+
218
+ # The release module's version gate supports Node, .NET and Python repositories. The consumption
219
+ # boundary must use the same ecosystem-neutral source set rather than making the latter two
220
+ # permanently red after their first release.
221
+ [[self_test]]
222
+ gate = "release-fragment-current"
223
+ expect = "pass"
224
+ fixture = { dir = "changelog.d", version = "0.2.0", version_file = "Directory.Build.props", consumed_through = "0.2.0", fragments = [] }
225
+
226
+ [[self_test]]
227
+ gate = "release-fragment-current"
228
+ expect = "pass"
229
+ fixture = { dir = "changelog.d", version = "0.2.0", version_file = "pyproject.toml", consumed_through = "0.2.0", fragments = [] }
130
230
 
131
231
  [[self_test]]
132
232
  gate = "release-version-consistent"
@@ -138,16 +238,68 @@ gate = "release-version-consistent"
138
238
  expect = "pass"
139
239
  fixture = { packages = { "package.json" = "1.2.0", "web/package.json" = "1.2.0" } }
140
240
 
141
- # The F-023 case: a sibling deliberately versioned on its own is excluded and the gate stays green,
142
- # rather than reporting a healthy layout as a disagreement.
241
+ # All three advertised ecosystems contribute to one comparison. Equal values pass; changing only
242
+ # Python fails and the engine's finding names every contributing file beside its value.
243
+ [[self_test]]
244
+ gate = "release-version-consistent"
245
+ expect = "pass"
246
+ fixture = { versions = { "package.json" = "1.2.0", "pyproject.toml" = "1.2.0", "Directory.Build.props" = "1.2.0" } }
247
+
248
+ [[self_test]]
249
+ gate = "release-version-consistent"
250
+ expect = "fail"
251
+ fixture = { versions = { "package.json" = "1.2.0", "pyproject.toml" = "2.0.0", "Directory.Build.props" = "1.2.0" } }
252
+
253
+ # A matched source is evidence even when it is broken: malformed documents, missing configured
254
+ # locations and unusable values all fail rather than disappearing from the comparison.
255
+ [[self_test]]
256
+ gate = "release-version-consistent"
257
+ expect = "fail"
258
+ fixture = { version_files = { "package.json" = "{" } }
259
+
260
+ [[self_test]]
261
+ gate = "release-version-consistent"
262
+ expect = "fail"
263
+ fixture = { version_files = { "pyproject.toml" = "[project" } }
264
+
265
+ [[self_test]]
266
+ gate = "release-version-consistent"
267
+ expect = "fail"
268
+ fixture = { version_files = { "package.json" = "{\"name\":\"fixture\"}" } }
269
+
270
+ [[self_test]]
271
+ gate = "release-version-consistent"
272
+ expect = "fail"
273
+ fixture = { version_files = { "pyproject.toml" = "[project]\nname = \"fixture\"\n" } }
274
+
275
+ [[self_test]]
276
+ gate = "release-version-consistent"
277
+ expect = "fail"
278
+ fixture = { version_files = { "Directory.Build.props" = "<Project><PropertyGroup><VersionPrefix>1.2.0</VersionPrefix></PropertyGroup></Project>" } }
279
+
280
+ [[self_test]]
281
+ gate = "release-version-consistent"
282
+ expect = "fail"
283
+ fixture = { version_files = { "Directory.Build.props" = "<Project><Version>1.2.0</Version>" } }
284
+
285
+ [[self_test]]
286
+ gate = "release-version-consistent"
287
+ expect = "fail"
288
+ fixture = { version_files = { "Directory.Build.props" = "<Project><![CDATA[<Version>1.2.0</Version>]]></Project>" } }
289
+
290
+ [[self_test]]
291
+ gate = "release-version-consistent"
292
+ expect = "fail"
293
+ fixture = { version_files = { "package.json" = "{\"version\":{}}" } }
294
+
295
+ # Unmatched sources are optional when one valid source participates. When none of the configured
296
+ # globs contributes a file, the gate refuses to manufacture a green result from zero evidence.
143
297
  [[self_test]]
144
298
  gate = "release-version-consistent"
145
299
  expect = "pass"
146
- fixture = { packages = { "package.json" = "1.2.0", "web/package.json" = "0.0.1" }, exclude = ["web/package.json"] }
300
+ fixture = { versions = { "package.json" = "1.2.0" } }
147
301
 
148
- # …and excluding one sibling must not blind the gate to a different one. An `exclude` that
149
- # silently widened would be worse than no gate, because it would still report green.
150
302
  [[self_test]]
151
303
  gate = "release-version-consistent"
152
304
  expect = "fail"
153
- fixture = { packages = { "package.json" = "1.2.0", "web/package.json" = "0.0.1", "api/package.json" = "9.9.9" }, exclude = ["web/package.json"] }
305
+ fixture = { version_files = { "README.md" = "# no version source\n" } }
@@ -1,6 +1,6 @@
1
1
  [module]
2
2
  name = "release"
3
- version = "1.2.0"
3
+ version = "1.6.0"
4
4
  rung = 3
5
5
  summary = "Candidate, stable and deploy branch lines; one procedure for cutting a release, hotfixing and rolling back; changelog fragments instead of a shared file."
6
6
 
@@ -42,10 +42,14 @@ description = "semver | calver"
42
42
  default = "semver"
43
43
  consumed_by = "render"
44
44
 
45
+ [params.version_exclude]
46
+ description = "One glob for files versioned independently of this release. Use a brace group for several paths; empty excludes nothing."
47
+ default = ""
48
+
45
49
  [[gates]]
46
50
  id = "release-changelog-fragment"
47
51
  kind = "declared"
48
- engine = "file-population"
52
+ engine = "change-requires-file"
49
53
  applicability = "repo-content"
50
54
  table = "gates/release.toml"
51
55
  tier = "fast"
@@ -59,13 +63,13 @@ the change.
59
63
  id = "release-fragment-current"
60
64
  kind = "declared"
61
65
  engine = "changelog-freshness"
62
- applicability = "repo-content"
66
+ applicability = "our-schema"
63
67
  table = "gates/release.toml"
64
68
  tier = "fast"
65
69
  why = """
66
70
  Fragments are consumed at release time, not archived — one left behind appears in the next release
67
- too, where it reads as unreleased work. `cut-release` §3 has said so in prose since it was written,
68
- and prose did not hold it: this repo's own `0.1.1` fragment survived two releases (F-022).
71
+ too, where it reads as unreleased work. The tracked consumption boundary distinguishes a fragment
72
+ being prepared from the equal-version fragment this repo left behind after v0.2.0 (F-025).
69
73
  """
70
74
 
71
75
  [[gates]]
@@ -12,17 +12,28 @@ description: >-
12
12
 
13
13
  **Every step below is reversible except the tag and the deploy.** Know which one you are on.
14
14
 
15
- ## 1. Decide the version
15
+ ## 1. Initialise the consumption boundary
16
+
17
+ Open `{{changelog_dir}}/CONSUMED_THROUGH`. If it says `UNINITIALIZED`, replace it with `none` only
18
+ when no release has ever consumed fragments; otherwise write the exact last version whose fragments
19
+ were assembled. **Do not infer this from a package version, tag or registry.** Those say what was
20
+ versioned or published, not whether its fragments were actually consumed.
21
+
22
+ This is a changelog-consumption claim, not a publication claim. In a steady tree its concrete value
23
+ equals the package version. During the reversible preparation below it briefly moves ahead, then the
24
+ version bump restores equality before the final gate.
25
+
26
+ ## 2. Decide the version
16
27
 
17
28
  From the changelog fragments in `{{changelog_dir}}/`, not from memory or from what the last release
18
29
  was. A breaking change in any fragment decides the major; a feature decides the minor. If the
19
30
  fragments do not support the version you were asked for, **say so before continuing** — that
20
31
  mismatch is usually a fragment somebody skipped, not a versioning disagreement.
21
32
 
22
- ## 2. Gate
33
+ ## 3. Gate the current tree
23
34
 
24
35
  ```bash
25
- rungs check
36
+ node .ai/rungs.mjs check
26
37
  ```
27
38
 
28
39
  Run **every** registered gate. This step used to name a "--tier full" flag that the CLI does not
@@ -30,7 +41,8 @@ accept, so both words were parsed as positionals and the run checked a directory
30
41
  exist — reporting no gates rather than a pass. A release step that gates on nothing while looking
31
42
  green is worse than one that is skipped, because nobody goes back to check it.
32
43
 
33
- Narrow to a tier (`rungs check --fast`) only when you know the tier holds what you meant to run.
44
+ Narrow to a tier (`node .ai/rungs.mjs check fast`) only when you know the tier holds what you meant
45
+ to run.
34
46
 
35
47
  **Do not proceed on a red gate**, and do not weaken one to get through. A release is exactly the
36
48
  moment the temptation is highest and the cost of yielding is highest.
@@ -38,17 +50,31 @@ moment the temptation is highest and the cost of yielding is highest.
38
50
  If a gate is red for reasons that predate this work, say so explicitly and get a decision. Shipping
39
51
  past a known-red gate is a choice someone should make on purpose.
40
52
 
41
- ## 3. Assemble the changelog
53
+ ## 4. Assemble the changelog and advance the boundary
54
+
55
+ As one reversible preparation, combine the fragments into the release section, **delete the
56
+ fragments**, and set `{{changelog_dir}}/CONSUMED_THROUGH` to the version being prepared. Fragments
57
+ are consumed, not archived — a fragment left behind appears in the next release too. Advancing the
58
+ marker in the same change makes a retained equal-version fragment mechanically red (F-025).
59
+
60
+ ## 5. Bump the version
42
61
 
43
- Combine the fragments into the release section, then **delete the fragments**. They are consumed,
44
- not archived a fragment left behind appears in the next release too.
62
+ In every place it appears. The bump restores equality with the consumption boundary;
63
+ `release-version-consistent` computes agreement across version surfaces rather than trusting you.
45
64
 
46
- ## 4. Bump the version
65
+ ## 6. Gate the prepared tree
66
+
67
+ Run **every** registered gate again. The preliminary gate proved the starting point; this one proves
68
+ the exact tree that will be tagged. It must see the package version and concrete consumption
69
+ boundary agree, with no fragment at or below that boundary.
70
+
71
+ ```bash
72
+ node .ai/rungs.mjs check
73
+ ```
47
74
 
48
- In every place it appears. `release-version-consistent` computes this rather than trusting you;
49
- run it before tagging, because a version that disagrees with itself is discovered by a user.
75
+ Do not tag on red, and require CI to pass at the exact prepared commit.
50
76
 
51
- ## 5. Tag and merge
77
+ ## 7. Tag and merge
52
78
 
53
79
  - Annotated tag on the candidate, message naming the release.
54
80
  - Merge the candidate into `{{stable_branch}}`.
@@ -56,7 +82,7 @@ run it before tagging, because a version that disagrees with itself is discovere
56
82
  roll back to** — a rollback that means reverting commits on the stable line is a rollback nobody
57
83
  performs correctly under pressure.
58
84
 
59
- ## 6. Open the next candidate
85
+ ## 8. Open the next candidate
60
86
 
61
87
  Cut a new `{{candidate_prefix}}<next>` immediately. A period with no open candidate is a period
62
88
  where work lands somewhere improvised.
@@ -69,9 +95,11 @@ Branch **from the deploy branch, never from the candidate** — the candidate co
69
95
  work, and shipping it as a hotfix is how an unrelated feature reaches production during an
70
96
  incident.
71
97
 
72
- Fix gate tag a patch version merge into `{{stable_branch}}` **and forward into the active
73
- candidate**. The forward-merge is the step people skip, and skipping it means the next release
74
- silently reverts the hotfix.
98
+ Fix and add its patch fragment, then use the same reversible preparation as a normal cut: assemble
99
+ the fragment, delete it, advance `{{changelog_dir}}/CONSUMED_THROUGH`, bump every version surface,
100
+ and run every gate against the exact prepared commit. Only then tag the patch, merge it into
101
+ `{{stable_branch}}` **and forward into the active candidate**. The forward-merge is the step people
102
+ skip, and skipping it means the next release silently reverts the hotfix.
75
103
 
76
104
  ## Rollback
77
105
 
@@ -9,7 +9,7 @@ Closed sessions, one file each, newest first in the index generated into
9
9
 
10
10
  The filename does the work. Someone scanning this directory should be able to find the session
11
11
  that closed a given piece of work without opening anything — which is why the name carries both
12
- halves, not just a number. `rungs check` refuses a name that does not.
12
+ halves, not just a number. `node .ai/rungs.mjs check` refuses a name that does not.
13
13
 
14
14
  ## What an archive note holds
15
15
 
@@ -50,8 +50,8 @@ Keep it short. This document is read at the start of every session, and a long o
50
50
  ## Archive
51
51
 
52
52
  <!-- rungs:begin session-archive -->
53
- <!-- Generated by `rungs render` from {{archive}}/. The link above is relative and assumes
53
+ <!-- Generated by `node .ai/rungs.mjs render` from {{archive}}/. The link above is relative and assumes
54
54
  the archive sits beside this file, which is the default. A relative link between two
55
55
  parameterised paths cannot be computed without logic, and ADR-0003 has none — so moving
56
- one without the other is a divergence `rungs check` will report as a broken link. -->
56
+ one without the other is a divergence `node .ai/rungs.mjs check` will report as a broken link. -->
57
57
  <!-- rungs:end session-archive -->
@@ -1,6 +1,6 @@
1
1
  [module]
2
2
  name = "session"
3
- version = "1.2.0"
3
+ version = "1.3.0"
4
4
  rung = 1
5
5
  summary = "A handoff document carrying state between sessions: resume point, active constraints, and a dated archive."
6
6
 
@@ -9,7 +9,7 @@ need to know what is happening right now, that is the backlog, not here.
9
9
  ## Index
10
10
 
11
11
  <!-- rungs:begin specs-index -->
12
- <!-- Generated by `rungs render`. -->
12
+ <!-- Generated by `node .ai/rungs.mjs render`. -->
13
13
 
14
14
  | Spec | Scope | Status | File |
15
15
  | --- | --- | --- | --- |
@@ -37,7 +37,7 @@ explicit note. **Never ✅.**
37
37
 
38
38
  A spec that overclaims integration is worse than no spec, because it is read as verified — by the
39
39
  next contributor, by a plan that cites it, and by an agent deciding a story is already handled.
40
- `rungs check` refuses a ✅ story that does not name what closed it.
40
+ `node .ai/rungs.mjs check` refuses a ✅ story that does not name what closed it.
41
41
 
42
42
  ## Proposing new cross-cutting scope
43
43
 
@@ -1,6 +1,6 @@
1
1
  [module]
2
2
  name = "specs"
3
- version = "1.0.0"
3
+ version = "1.1.0"
4
4
  rung = 2
5
5
  summary = "Behaviour specifications with stable feature and story ids, per-story status, a mandatory scope section, and a gate against overclaiming."
6
6
 
@@ -1,6 +1,6 @@
1
1
  [module]
2
2
  name = "workflows"
3
- version = "1.1.0"
3
+ version = "1.2.0"
4
4
  rung = 2
5
5
  summary = "Decision procedures: how to decompose work, whether to reuse or build, and how much planning a piece of work earns."
6
6
 
@@ -35,7 +35,7 @@ Any one of these is enough:
35
35
 
36
36
  ## Tier is declared, not inferred
37
37
 
38
- Every plan carries `tier:` in its frontmatter. `rungs check` reads it and checks that tier's
38
+ Every plan carries `tier:` in its frontmatter. `node .ai/rungs.mjs check` reads it and checks that tier's
39
39
  required sections are present and non-empty. A plan with no tier cannot be checked, and tiering
40
40
  degrades back into "write whatever felt right" — which is the judgement call this table replaces.
41
41
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rungs/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Installs and maintains a repository's agentic development system, composed from modules.",
5
5
  "author": "Antoine Dancre",
6
6
  "repository": {
@@ -43,7 +43,8 @@
43
43
  ],
44
44
  "license": "MIT",
45
45
  "dependencies": {
46
- "smol-toml": "^1.8.0"
46
+ "saxes": "6.0.0",
47
+ "smol-toml": "1.8.0"
47
48
  },
48
49
  "devDependencies": {
49
50
  "esbuild": "^0.25.12"