@rungs/cli 0.3.1 → 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.
- package/README.md +6 -6
- package/dist/cli.js +2184 -478
- package/dist/cli.js.map +4 -4
- package/modules/README.md +25 -3
- package/modules/adr/files/{{path}}/README.md +1 -1
- package/modules/adr/gates/adr.toml +1 -1
- package/modules/adr/module.toml +1 -1
- package/modules/audit/fragments/AGENTS.md +2 -2
- package/modules/audit/module.toml +1 -1
- package/modules/audit/skills/assess/SKILL.md +1 -1
- package/modules/backlog/files/docs/{{root}}/BACKLOG.md +1 -1
- package/modules/backlog/files/docs/{{root}}/README.md +2 -2
- package/modules/backlog/files/docs/{{root}}/archive/README.md +1 -1
- package/modules/backlog/files/docs/{{root}}/items/README.md +1 -1
- package/modules/backlog/fragments/AGENTS.md +2 -2
- package/modules/backlog/module.toml +1 -1
- package/modules/backlog/skills/work-item/SKILL.md +1 -1
- package/modules/ci/files/{{workflow_path}} +3 -3
- package/modules/ci/module.toml +1 -1
- package/modules/concurrency/files/docs/concurrent-sessions.md +66 -18
- package/modules/concurrency/fragments/AGENTS.md +5 -4
- package/modules/concurrency/fragments/gitattributes +2 -2
- package/modules/concurrency/gates/concurrency.toml +3 -3
- package/modules/concurrency/module.toml +1 -1
- package/modules/doc-authority/files/{{registry_path}} +1 -1
- package/modules/doc-authority/module.toml +1 -1
- package/modules/findings/files/docs/{{backlog.root}}/FINDINGS.md +1 -1
- package/modules/findings/gates/findings.toml +5 -0
- package/modules/findings/module.toml +1 -1
- package/modules/findings/skills/record-finding/SKILL.md +1 -1
- package/modules/gates/files/.ai/gates.toml +1 -1
- package/modules/gates/fragments/AGENTS.md +6 -5
- package/modules/gates/module.toml +1 -1
- package/modules/instructions/files/.ai/rules/README.md +2 -2
- package/modules/instructions/files/.ai/rungs.mjs +52 -0
- package/modules/instructions/files/AGENTS.md +4 -2
- package/modules/instructions/files/CLAUDE.md +1 -1
- package/modules/instructions/fragments/AGENTS.md +2 -2
- package/modules/instructions/gates/core.toml +2 -2
- package/modules/instructions/module.toml +1 -1
- package/modules/release/files/{{changelog_dir}}/CONSUMED_THROUGH +1 -0
- package/modules/release/gates/release.toml +169 -17
- package/modules/release/module.toml +9 -5
- package/modules/release/skills/cut-release/SKILL.md +43 -15
- package/modules/session/files/{{archive}}/README.md +1 -1
- package/modules/session/files/{{path}} +2 -2
- package/modules/session/module.toml +1 -1
- package/modules/specs/files/{{path}}/README.md +2 -2
- package/modules/specs/module.toml +1 -1
- package/modules/workflows/module.toml +1 -1
- package/modules/workflows/rules/planning-tiers.md +1 -1
- package/package.json +3 -2
- package/src/add.ts +204 -48
- package/src/backlog.ts +354 -48
- package/src/check.ts +54 -33
- package/src/cli.ts +196 -69
- package/src/concurrency.ts +628 -42
- package/src/detect.ts +11 -3
- package/src/emitted-path.ts +274 -0
- package/src/engine-table.ts +66 -0
- package/src/engines.ts +18 -29
- package/src/engines2.ts +403 -20
- package/src/engines3.ts +111 -20
- package/src/explain.ts +3 -7
- package/src/help.ts +43 -0
- package/src/lifecycle.ts +86 -27
- package/src/manifest.ts +41 -5
- package/src/render.ts +106 -21
- package/src/selftest.ts +87 -10
- package/src/storage-key.ts +20 -0
- package/src/substitute.ts +47 -5
- package/src/text.ts +11 -0
- package/src/types.ts +16 -3
- package/src/version-source.ts +144 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
UNINITIALIZED
|
|
@@ -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
|
-
[
|
|
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
|
|
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
|
-
|
|
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
|
|
76
|
-
|
|
77
|
-
|
|
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 = [
|
|
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",
|
|
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
|
-
#
|
|
142
|
-
#
|
|
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 = {
|
|
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 = {
|
|
305
|
+
fixture = { version_files = { "README.md" = "# no version source\n" } }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[module]
|
|
2
2
|
name = "release"
|
|
3
|
-
version = "1.
|
|
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
|
|
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 = "
|
|
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.
|
|
68
|
-
|
|
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.
|
|
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
|
-
##
|
|
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
|
|
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
|
-
##
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
|
73
|
-
|
|
74
|
-
|
|
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 -->
|
|
@@ -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
|
|
|
@@ -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
|
+
"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
|
-
"
|
|
46
|
+
"saxes": "6.0.0",
|
|
47
|
+
"smol-toml": "1.8.0"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
49
50
|
"esbuild": "^0.25.12"
|