@rungs/cli 0.1.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 (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +196 -0
  3. package/modules/README.md +116 -0
  4. package/modules/adr/files/{{path}}/README.md +52 -0
  5. package/modules/adr/files/{{path}}/TEMPLATE.md +55 -0
  6. package/modules/adr/fragments/AGENTS.md +8 -0
  7. package/modules/adr/gates/adr.toml +65 -0
  8. package/modules/adr/module.toml +67 -0
  9. package/modules/audit/files/{{criteria_path}} +43 -0
  10. package/modules/audit/fragments/AGENTS.md +8 -0
  11. package/modules/audit/gates/audit.toml +48 -0
  12. package/modules/audit/module.toml +72 -0
  13. package/modules/audit/skills/assess/SKILL.md +62 -0
  14. package/modules/backlog/files/docs/{{root}}/BACKLOG.md +52 -0
  15. package/modules/backlog/files/docs/{{root}}/README.md +110 -0
  16. package/modules/backlog/files/docs/{{root}}/TEMPLATE.md +56 -0
  17. package/modules/backlog/files/docs/{{root}}/archive/README.md +13 -0
  18. package/modules/backlog/files/docs/{{root}}/items/README.md +13 -0
  19. package/modules/backlog/fragments/AGENTS.md +9 -0
  20. package/modules/backlog/gates/ids.toml +106 -0
  21. package/modules/backlog/module.toml +150 -0
  22. package/modules/backlog/rules/work-items.md +44 -0
  23. package/modules/backlog/skills/backlog-summary/SKILL.md +65 -0
  24. package/modules/backlog/skills/work-item/SKILL.md +96 -0
  25. package/modules/ci/files/{{workflow_path}} +43 -0
  26. package/modules/ci/gates/ci.toml +44 -0
  27. package/modules/ci/module.toml +74 -0
  28. package/modules/concurrency/files/docs/concurrent-sessions.md +87 -0
  29. package/modules/concurrency/fragments/AGENTS.md +10 -0
  30. package/modules/concurrency/fragments/gitattributes +15 -0
  31. package/modules/concurrency/gates/concurrency.toml +54 -0
  32. package/modules/concurrency/module.toml +113 -0
  33. package/modules/design-sync/fragments/AGENTS.md +8 -0
  34. package/modules/design-sync/gates/design.toml +70 -0
  35. package/modules/design-sync/module.toml +82 -0
  36. package/modules/design-sync/skills/design-align/SKILL.md +51 -0
  37. package/modules/design-sync/skills/design-pull/SKILL.md +50 -0
  38. package/modules/doc-authority/files/{{registry_path}} +59 -0
  39. package/modules/doc-authority/files/{{rules_path}} +51 -0
  40. package/modules/doc-authority/fragments/AGENTS.md +9 -0
  41. package/modules/doc-authority/gates/authority.toml +127 -0
  42. package/modules/doc-authority/module.toml +111 -0
  43. package/modules/findings/files/docs/{{backlog.root}}/FINDINGS.md +56 -0
  44. package/modules/findings/fragments/AGENTS.md +7 -0
  45. package/modules/findings/gates/findings.toml +68 -0
  46. package/modules/findings/module.toml +100 -0
  47. package/modules/findings/skills/record-finding/SKILL.md +63 -0
  48. package/modules/gates/files/.ai/gates.toml +49 -0
  49. package/modules/gates/fragments/AGENTS.md +8 -0
  50. package/modules/gates/fragments/gitignore +4 -0
  51. package/modules/gates/gates/structural.toml +127 -0
  52. package/modules/gates/module.toml +143 -0
  53. package/modules/gates/skills/harden-rule/SKILL.md +82 -0
  54. package/modules/instructions/files/.ai/rules/README.md +53 -0
  55. package/modules/instructions/files/AGENTS.md +104 -0
  56. package/modules/instructions/files/CLAUDE.md +11 -0
  57. package/modules/instructions/gates/core.toml +114 -0
  58. package/modules/instructions/module.toml +127 -0
  59. package/modules/release/fragments/AGENTS.md +8 -0
  60. package/modules/release/gates/release.toml +85 -0
  61. package/modules/release/module.toml +97 -0
  62. package/modules/release/skills/cut-release/SKILL.md +81 -0
  63. package/modules/session/files/{{archive}}/README.md +28 -0
  64. package/modules/session/files/{{path}} +56 -0
  65. package/modules/session/fragments/AGENTS.md +6 -0
  66. package/modules/session/gates/session.toml +63 -0
  67. package/modules/session/module.toml +72 -0
  68. package/modules/session/skills/close-session/SKILL.md +59 -0
  69. package/modules/skills/fragments/AGENTS.md +8 -0
  70. package/modules/skills/gates/skills.toml +94 -0
  71. package/modules/skills/module.toml +89 -0
  72. package/modules/skills/rules/skill-authoring.md +71 -0
  73. package/modules/specs/files/{{path}}/README.md +63 -0
  74. package/modules/specs/files/{{path}}/TEMPLATE.md +62 -0
  75. package/modules/specs/fragments/AGENTS.md +8 -0
  76. package/modules/specs/gates/specs.toml +119 -0
  77. package/modules/specs/module.toml +107 -0
  78. package/modules/workflows/fragments/AGENTS.md +8 -0
  79. package/modules/workflows/gates/workflows.toml +65 -0
  80. package/modules/workflows/module.toml +75 -0
  81. package/modules/workflows/rules/planning-tiers.md +46 -0
  82. package/modules/workflows/rules/reuse-decision.md +50 -0
  83. package/modules/workflows/skills/decompose/SKILL.md +75 -0
  84. package/package.json +42 -0
  85. package/src/add.ts +331 -0
  86. package/src/check.ts +216 -0
  87. package/src/cli.ts +411 -0
  88. package/src/detect.ts +246 -0
  89. package/src/engines.ts +260 -0
  90. package/src/engines2.ts +297 -0
  91. package/src/engines3.ts +194 -0
  92. package/src/glob.ts +101 -0
  93. package/src/lifecycle.ts +268 -0
  94. package/src/manifest.ts +108 -0
  95. package/src/render.ts +222 -0
  96. package/src/substitute.ts +78 -0
  97. package/src/types.ts +110 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Antoine Dancre
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,196 @@
1
+ # rungs
2
+
3
+ **Installs and maintains a repository's agentic development system.**
4
+
5
+ Your agent has instructions. Do they have gates? Is your backlog's status field
6
+ telling the truth about what actually merged? That thing you noticed last
7
+ Tuesday — where did it go?
8
+
9
+ rungs scaffolds the parts of a working setup — agent instructions, skills, work
10
+ tracking, findings, decision records, validation gates — as **modules you pick**,
11
+ then keeps checking that they still say what they said.
12
+
13
+ ```console
14
+ $ rungs init . tracked
15
+ instructions 3 create
16
+ gates 1 create · 1 skill · 2 merge
17
+ backlog 6 create · 1 rule · 2 skill · 1 merge
18
+ findings 1 create · 1 skill · 1 merge
19
+ adr 2 create · 1 merge
20
+ session 2 create · 1 skill · 1 merge
21
+
22
+ registered 18 gates from 6 module(s)
23
+ rendered 2 file(s) · 0 degraded → .ai/render-report.md
24
+
25
+ $ rungs check
26
+ pass backlog-ids 2ms 2 examined
27
+ FAIL backlog-merged-status 68ms
28
+ docs/backlog/items/WI-014-parser.md: branch feature/wi-014 is merged
29
+ but status is 'in_progress'
30
+
31
+ 17 pass · 1 fail · 0 unimplemented · 0 error
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Why this exists
37
+
38
+ Every rule in here was **paid for once already**.
39
+
40
+ The content is extracted from four repositories built over six months in
41
+ different stacks — a .NET ingestion platform, a 105-package Angular monorepo, a
42
+ set of reference apps, and a full-stack product with 3,236 commits across 401
43
+ branches. Each solved part of this by hand. Each also failed in ways the others
44
+ did too.
45
+
46
+ So rungs does not ship a good idea about how to work. It ships **what four repos
47
+ learned, with the incident attached**. Every module declares its provenance, and
48
+ `doctor` quotes that incident back when a gate it installed has never fired:
49
+
50
+ > `check-findings-register` has run 340 times and never fired. It exists because
51
+ > one repo produced 268 audit reports with no register to close them into. Is
52
+ > that still a risk here — or is this gate scoped too narrowly?
53
+
54
+ The research is in [`docs/research/`](docs/research/README.md) and stands on its
55
+ own: four repo autopsies, the [eight failure modes all of them
56
+ hit](docs/research/synthesis.md), and a maturity ladder that prices each practice
57
+ so you don't install rung 5 at rung 1.
58
+
59
+ ## Install
60
+
61
+ Not published yet. For now:
62
+
63
+ ```bash
64
+ git clone <this repo> && cd rungs && npm install
65
+ node src/cli.ts --help
66
+ ```
67
+
68
+ Requires **Node 22.18+** — it runs TypeScript directly, no build step.
69
+
70
+ Published as **`@rungs/cli`** — the unscoped name `rungs` is unpublishable, being
71
+ one edit away from both `rung` and `runjs`, which npm's typosquat filter refuses.
72
+ The tool, the command and everything it writes are still `rungs`; only the
73
+ package identifier differs:
74
+
75
+ ```bash
76
+ npx @rungs/cli doctor
77
+ ```
78
+
79
+ ## What you get
80
+
81
+ `rungs init . tracked` writes:
82
+
83
+ ```text
84
+ AGENTS.md # what every session reads — with a line budget that is enforced
85
+ CLAUDE.md # a one-line bridge: @AGENTS.md. Not a second copy
86
+ .ai/
87
+ rules/ # path-scoped rules you author, rendered per harness
88
+ gates.toml # every gate this repo runs
89
+ rungs.toml # what is installed, and a hash of everything we wrote
90
+ .claude/skills/ # spec-compliant Agent Skills — portable to 45+ clients
91
+ docs/backlog/ # work items, a board, a findings register
92
+ docs/decisions/ # ADRs, with an admission rule that keeps the directory small
93
+ ```
94
+
95
+ **Nothing is overwritten, ever.** `add` on a repo that already has a backlog
96
+ keeps yours and installs only what is missing. Files you edit afterwards are
97
+ reported as diverged and left alone.
98
+
99
+ ## Commands
100
+
101
+ | | |
102
+ | --- | --- |
103
+ | `rungs init [path] [profile]` | Scaffold — `minimal` · `tracked` · `disciplined` · `hardened` · `fleet` |
104
+ | `rungs doctor [path]` | What does this repo already have? Works on repos that never installed anything |
105
+ | `rungs add <module…>` | Install one module, resolving dependencies and adopting what exists |
106
+ | `rungs check [path]` | Run the gates, record the ledger |
107
+ | `rungs render [path]` | Re-emit path-scoped rules for each harness |
108
+ | `rungs upgrade [path]` | Move to newer module versions, never touching what you edited |
109
+ | `rungs eject [path]` | Materialise the engines; stop depending on rungs |
110
+ | `rungs setup git [path]` | Install the merge drivers `.gitattributes` names |
111
+ | `rungs modules` | List the set and audit the manifests |
112
+
113
+ Add `--dry-run` to any write command.
114
+
115
+ ## Modules
116
+
117
+ Fifteen, each carrying a **rung** — how mature a practice is — so `add` can tell
118
+ you when you are installing above your level.
119
+
120
+ | Rung | Modules |
121
+ | --- | --- |
122
+ | **0** any repo with an agent | `instructions` |
123
+ | **1** more than one thing in flight | `gates` `backlog` `findings` `adr` `session` `ci` |
124
+ | **2** repeated work of the same shape | `specs` `workflows` `skills` `audit` |
125
+ | **3** shipping versions, external design | `release` `design-sync` |
126
+ | **4** docs that restate each other | `doc-authority` |
127
+ | **5** 5+ concurrent sessions | `concurrency` |
128
+
129
+ `concurrency` refuses to install without `--confirm-threshold`, because below
130
+ five simultaneous sessions every mechanism in it costs more than it returns.
131
+ Selling rung 5 to a rung-1 repo is the most likely way this tool does harm.
132
+
133
+ Full specification: [`docs/design/module-catalog.md`](docs/design/module-catalog.md).
134
+
135
+ ## Which agents
136
+
137
+ Skills are **spec-compliant Agent Skills**, portable to Claude Code, Codex,
138
+ Cursor, Copilot, Gemini CLI and 40+ others without translation.
139
+
140
+ Only *path-scoped rules* are genuinely fragmented across harnesses, so that is
141
+ the only thing rungs renders — into `.claude/rules/`, `.github/instructions/`
142
+ and `.cursor/rules/`. Anything a target cannot express is **reported, never
143
+ silently dropped**, in `.ai/render-report.md`.
144
+
145
+ Reasoning: [ADR-0001](docs/decisions/ADR-0001-multi-harness-rendering.md).
146
+
147
+ ## Design commitments
148
+
149
+ Four promises that shape everything else:
150
+
151
+ - **Your repo gains no new language runtime.** rungs writes no gate scripts.
152
+ Generic gates are declarations run by engines the CLI provides; anything else
153
+ is a shell command you already own. A .NET repo with no `package.json` stays
154
+ that way. ([ADR-0002](docs/decisions/ADR-0002-stack-and-runtime-footprint.md))
155
+ - **A gate with no engine blocks — it never reports green.** A registry passing
156
+ because most of its gates do nothing is the worst failure this tool could have.
157
+ - **`eject` is a promise, not a courtesy.** It materialises the engines into your
158
+ repo and rewrites the registry to plain commands. A tool whose checks vanish
159
+ when you uninstall it is one nobody should adopt.
160
+ - **Nothing is measured that needs judgement.** The gate ledger records exit
161
+ status and wall-clock. It never scores your workflow.
162
+ ([ADR-0005](docs/decisions/ADR-0005-self-instrumentation.md))
163
+
164
+ ## Status
165
+
166
+ **Pre-release, v0.1.0.** Everything above runs; nothing is published.
167
+
168
+ rungs is installed in its own repo and its gates run on every change — 20 pass,
169
+ 0 fail. Detection is [verified against all four source
170
+ repos](docs/design/detection-verification.md). Not yet done: publishing, and
171
+ installing into a source repo for real rather than in dry run.
172
+
173
+ Expect module *contents* to move. The command surface is settled.
174
+
175
+ ## Contributing
176
+
177
+ Modules are the product, and **a module is a directory that looks like what it
178
+ emits** — markdown and TOML, no code. If you can read the repo it produces, you
179
+ can write one: [`modules/README.md`](modules/README.md).
180
+
181
+ Every module must declare `[provenance]` with a real incident behind it. A module
182
+ nobody paid for does not ship.
183
+
184
+ ## Repository
185
+
186
+ | | |
187
+ | --- | --- |
188
+ | [`docs/research/`](docs/research/README.md) | The four repo autopsies, the synthesis, the pattern catalogue |
189
+ | [`docs/design/`](docs/design/README.md) | Product brief, module catalogue, verification |
190
+ | [`docs/decisions/`](docs/decisions/README.md) | ADRs |
191
+ | [`modules/`](modules/README.md) | The fifteen modules |
192
+ | [`src/`](src/) | The CLI, ~2,800 lines |
193
+
194
+ ## Licence
195
+
196
+ MIT.
@@ -0,0 +1,116 @@
1
+ # Modules
2
+
3
+ The product. Each directory here is one installable module, in the format set by
4
+ [ADR-0003](../docs/decisions/ADR-0003-module-definition-format.md); the set and its rungs are
5
+ specified in [module-catalog.md](../docs/design/module-catalog.md).
6
+
7
+ | Module | Rung | Status |
8
+ | --- | --- | --- |
9
+ | [`instructions`](instructions/) | 0 | **authored** — owns `AGENTS.md`, the bridge, `.ai/rules/` |
10
+ | [`gates`](gates/) | 1 | **authored** — owns the runner, registry and ledger |
11
+ | [`backlog`](backlog/) | 1 | **authored** — the format exemplar |
12
+ | [`findings`](findings/) | 1 | **authored** — completes `audit → findings → backlog` |
13
+ | [`adr`](adr/) | 1 | **authored** |
14
+ | [`session`](session/) | 1 | **authored** |
15
+ | [`ci`](ci/) | 1 | **authored** |
16
+ | [`specs`](specs/) | 2 | **authored** |
17
+ | [`workflows`](workflows/) | 2 | **authored** |
18
+ | [`skills`](skills/) | 2 | **authored** |
19
+ | [`audit`](audit/) | 2 | **authored** |
20
+ | [`release`](release/) | 3 | **authored** |
21
+ | [`design-sync`](design-sync/) | 3 | **authored** |
22
+ | [`doc-authority`](doc-authority/) | 4 | **authored** |
23
+ | [`concurrency`](concurrency/) | 5 | **authored** |
24
+
25
+ **All fifteen are authored.** Installing every one assembles an entry document of **165 of the
26
+ 200-line budget**, leaving 35 for the repo's own conventions — and no repo should install all
27
+ fifteen, since rung 3+ modules are for specific problems.
28
+
29
+ `concurrency` carries a **threshold** in its manifest (`minimum = 5` concurrent sessions,
30
+ `confirm = true`): `add` states it and requires explicit confirmation. Selling rung 5 to a rung-1
31
+ repo is the most likely way this tool does harm.
32
+
33
+ ## Anatomy
34
+
35
+ **A module is a directory that looks like what it emits.** Disposition is decided by which
36
+ subdirectory a file is in — there is no per-file configuration:
37
+
38
+ | Directory | Disposition |
39
+ | --- | --- |
40
+ | `files/` | **create** — written into the repo, with `{{param}}` substituted in contents *and paths* |
41
+ | `rules/` | **render** — path-scoped rule sources, emitted per harness ([ADR-0001](../docs/decisions/ADR-0001-multi-harness-rendering.md)) |
42
+ | `skills/` | **copy** — spec-pure `SKILL.md`, byte-for-byte after substitution |
43
+ | `fragments/` | **merge** — managed blocks inside a file another module owns |
44
+ | `gates/` | **declare** — engine tables; **no script is written into the repo** |
45
+ | `module.toml` | the manifest: identity · rung · deps · params · gates · detection · provenance |
46
+
47
+ **Hooks are not a sixth disposition.** A hook is a gate with a lifecycle trigger instead of a
48
+ runner trigger — `trigger = "pre-tool-use"` plus a `matcher` in the `[[gates]]` entry. It is
49
+ emitted into harnesses that support it and reported as degraded for those that do not, exactly like
50
+ any other render target. A useful consequence: the ledger covers hooks too, so a repo can find out
51
+ whether its guard has ever actually fired.
52
+
53
+ ## Authoring rules
54
+
55
+ 1. **Substitution only, no logic.** A module that needs a conditional is two modules, or a variant.
56
+ 1b. **`${{ … }}` is never substituted.** A `$` immediately before `{{` marks a passthrough, because
57
+ GitHub Actions expressions (`${{ github.ref }}`) share the delimiter. Without this rule the CI
58
+ module's own workflow file is silently corrupted at install — the kind of collision that
59
+ produces a broken file rather than an error.
60
+ 1c. **A behavioural parameter reaches file content through a managed block**, never a conditional.
61
+ `ci.trigger` regenerates the `ci-triggers` block inside the workflow rather than branching the
62
+ template. This is the general escape when substitution alone is not enough.
63
+ 2. **Not everything is a parameter.** If a value cannot substitute cleanly into prose, a table
64
+ *and* a gate, it is the module's opinion — ship it and let a repo diverge. `backlog`'s
65
+ eight-status lifecycle is the worked example.
66
+ 2b. **Two kinds of parameter, and only one is substituted.** A *substitution* parameter appears as
67
+ `{{name}}` in templates. A *behavioural* one changes what the CLI does and is marked
68
+ `consumed_by = "render"` — `instructions.harnesses` decides which harnesses exist and will never
69
+ appear in a template. A dead-parameter lint that does not know the difference reports the second
70
+ kind as unused, and the obvious "fix" deletes it.
71
+ 3. **`[provenance]` is required and validated.** Sources, patterns, and the incident. A module with
72
+ no traceable source is one somebody invented, and `doctor` cannot ask its questions without the
73
+ incident ([ADR-0005](../docs/decisions/ADR-0005-self-instrumentation.md)).
74
+ 4. **Skills stay spec-pure.** Six Agent Skills fields; Claude Code extensions are opted into in
75
+ `module.toml`, per skill, with the portability cost stated there.
76
+ 5. **Every gate declares a self-test asserting both directions.** A gate whose rules are currently
77
+ satisfied is indistinguishable from a gate that matches nothing.
78
+ 6. **`[detect]` must correctly classify all four source repos.** That is the Phase 6 acceptance
79
+ criterion, and it is why detection is biased toward false negatives.
80
+ 7. **A managed-block marker uses the target file's comment syntax** — `<!-- rungs:begin x -->` in
81
+ markdown, `# rungs:begin x` in TOML and `.gitignore`. Found by writing an HTML comment into a
82
+ TOML registry, where it is a syntax error rather than a marker.
83
+ 8. **A fragment is a routing stanza, not a summary — 4 to 8 lines.** The budget is shared, and it
84
+ is the binding constraint at profile scale, not per module. Measured while authoring the
85
+ `disciplined` profile: a 73-line skeleton plus ten fragments at ~12 lines each is 193 of the
86
+ 200-line budget with nothing left for the repo's own conventions or repo map. Rewritten as
87
+ routing stanzas the same profile assembles to **134**, leaving 66 for the repo. A fragment says
88
+ *what exists, where it lives, and which skill runs it*; the reasoning goes in the module's
89
+ authority document, and the surface-specific rules go in `.ai/rules/`.
90
+ 8b. **Not every module needs a fragment.** `ci` has none — nothing about it changes what an agent
91
+ should do, and the `gates` fragment already names `rungs check`. A fragment that restates a
92
+ neighbour's is spending shared budget on a duplicate.
93
+ 9b. **A parameter may reference a declared dependency's parameters** as `{{<module>.<param>}}` —
94
+ `findings` places its register at `docs/{{backlog.root}}/FINDINGS.md` so it lands next to the
95
+ backlog it feeds. Only declared dependencies; anything else is an undeclared coupling.
96
+ 9c. **A path parameter may contain separators**, so one parameter places a whole subtree —
97
+ `files/{{path}}/README.md` with `path = "docs/decisions"`. A second "leaf" parameter is never
98
+ needed, and adding one was caught and reverted during authoring.
99
+ 9d. **A parameter whose value means "do nothing" is the absence of the module.** `session` was
100
+ specified with `mode = file | board`, where `board` created no files at all. That is not a
101
+ mode; it is not installing `session`. Dropped.
102
+ 9e. **Audit parameters across modules, not within one.** `adr` declared `id_width` that nothing
103
+ consumed — a knob wired to nothing, invisible until every module was compared at once.
104
+ 9f. **A parameter never holds a value decided at runtime.** `release` first declared
105
+ `candidate_branch = "candidate/{{version}}"`, referencing a version that does not exist at
106
+ install time. It is a **prefix**; the version is chosen when a release is cut. A parameter
107
+ holding a runtime value is stale before it is ever used.
108
+ 9g. **A module may declare a `[threshold]`** with `confirm = true`, which makes `add` state the
109
+ cost and require explicit acknowledgement. Only `concurrency` uses it, and it exists because
110
+ the maturity ladder is advice until something enforces it.
111
+
112
+ 10. **Genuinely optional prose ships commented out, with the reason** — substitution-only templating
113
+ has no other way to offer a choice, and a commented block is a decision the installer makes once
114
+ in their editor. Claude Code strips HTML comments before injection, so an unaccepted block costs
115
+ the agent nothing while staying visible to the human. `instructions`' communication-style block
116
+ is the worked example.
@@ -0,0 +1,52 @@
1
+ **Authoritative for:** the admission rule, the record format, and the index of decisions.
2
+ **Not authoritative for:** the content of any decision — each record is authoritative for its own.
3
+
4
+ # Decisions
5
+
6
+ Architecture decision records. **Immutable**: a record is never edited to reflect a new decision —
7
+ it is superseded by a new record that names it.
8
+
9
+ ## Index
10
+
11
+ <!-- rungs:begin adr-index -->
12
+ <!-- Generated by `rungs render`. -->
13
+
14
+ | ADR | Title | Status | Date |
15
+ | --- | --- | --- | --- |
16
+ | — | *none yet* | | |
17
+
18
+ <!-- rungs:end adr-index -->
19
+
20
+ ## Admission rule
21
+
22
+ **Before creating a record, all five must be true.** If any is false, the content belongs in the
23
+ document that already owns the topic — a spec, a module doc, a rule — not here.
24
+
25
+ 1. The decision **constrains future work** rather than describing current work.
26
+ 2. A reasonable **alternative existed and was rejected**, for a reason that can be stated.
27
+ 3. **Reversing it later would cost meaningfully more** than making it now.
28
+ 4. It is **not already owned** by another document.
29
+ 5. It is **not an implementation detail** that the code states more precisely than prose can.
30
+
31
+ This rule is the whole reason a decision directory stays navigable. Without it, every choice
32
+ becomes a record, the index becomes a second documentation tree, and its entries start
33
+ contradicting the docs that actually own those topics.
34
+
35
+ ## Writing one
36
+
37
+ Copy [`TEMPLATE.md`](TEMPLATE.md). Required sections, and each is required for a reason:
38
+
39
+ | Section | Why it is required |
40
+ | --- | --- |
41
+ | **Context** | The forces. A record whose context is one sentence usually failed criterion 1 |
42
+ | **Decision** | Stated in the present tense, as a rule |
43
+ | **Consequences** | **Both** — what this makes easy *and* what it costs. A consequences section with no costs is advocacy |
44
+ | **Alternatives considered** | Each with the reason it lost. This is the section future readers actually come for, and its absence is what criterion 2 is guarding |
45
+ | **Revisit triggers** | Facts about the world that would reopen this — not a date. A record with no trigger is one nobody will ever know to revisit |
46
+
47
+ ## Statuses
48
+
49
+ `proposed` → `accepted` → `superseded by {{id_prefix}}-####` · or `rejected`.
50
+
51
+ **Never delete a record**, including a rejected one. The reason something was not done is the
52
+ answer to a question that will be asked again.
@@ -0,0 +1,55 @@
1
+ ---
2
+ id: {{id_prefix}}-0000
3
+ title: Short imperative statement of the decision
4
+ status: proposed # proposed | accepted | rejected | superseded
5
+ date: YYYY-MM-DD
6
+ supersedes: # {{id_prefix}}-#### — when this replaces an earlier record
7
+ superseded_by: # set when a later record replaces this one
8
+ ---
9
+
10
+ # {{id_prefix}}-0000 — Title
11
+
12
+ ## Context
13
+
14
+ The forces at work: what is true, what is constrained, what problem is being solved. Enough that
15
+ someone who was not here can tell why a decision was needed at all.
16
+
17
+ > If this is one sentence, check admission criterion 1 — you may be describing work rather than
18
+ > constraining it.
19
+
20
+ ## Decision
21
+
22
+ Stated in the present tense, as a rule: *"X is Y"*, not *"we will make X be Y"*.
23
+
24
+ ## Consequences
25
+
26
+ **Good**
27
+
28
+ - What this makes possible or cheap.
29
+
30
+ **Costs and risks**
31
+
32
+ - What it makes harder, and what could go wrong.
33
+
34
+ > **Both halves are required.** A consequences section with no costs is advocacy, and the next
35
+ > reader will assume the costs were never considered.
36
+
37
+ ## Alternatives considered
38
+
39
+ **\<Alternative\>** — what it was, and **the reason it lost**.
40
+
41
+ > This is the section future readers actually come for. "We considered other options" without
42
+ > naming them is the same as not having considered them.
43
+
44
+ ## Revisit triggers
45
+
46
+ Facts about the world that would reopen this — not a date. A record with no trigger is one nobody
47
+ will know to revisit.
48
+
49
+ 1. …
50
+
51
+ ## Admission check
52
+
53
+ Against [the rule](README.md): (1) constrains future work ✅ · (2) a real alternative was rejected
54
+ with a stated reason ✅ · (3) reversing later costs meaningfully more ✅ · (4) not owned by another
55
+ document ✅ · (5) not an implementation detail ✅.
@@ -0,0 +1,8 @@
1
+ <!-- rungs:begin adr@1.0.0 -->
2
+ ## Decisions
3
+
4
+ Significant decisions are `{{id_prefix}}-####` records in [`{{path}}/`]({{path}}/README.md).
5
+ **Check the admission rule before writing one** — five criteria, all must hold, or the content
6
+ belongs in the document that already owns the topic. Records are immutable: supersede, never edit,
7
+ never delete.
8
+ <!-- rungs:end adr -->
@@ -0,0 +1,65 @@
1
+ # Gate table for the `adr` module.
2
+
3
+ # ── adr-index-current ─────────────────────────────────────────────────────────
4
+
5
+ [[render_freshness]]
6
+ id = "adr-index"
7
+ block = { file = "{{path}}/README.md", marker = "adr-index" }
8
+ derives = "file-index"
9
+ sources = ["{{path}}/{{id_prefix}}-*.md"]
10
+ columns = ["id", "title", "status", "date"]
11
+ command = "rungs render"
12
+
13
+ # ── adr-required-fields ───────────────────────────────────────────────────────
14
+
15
+ [[frontmatter_schema]]
16
+ id = "adr"
17
+ scan = ["{{path}}/{{id_prefix}}-*.md"]
18
+ required = ["id", "title", "status", "date"]
19
+ optional = ["supersedes", "superseded_by"]
20
+ enum = { status = ["proposed", "accepted", "rejected", "superseded"] }
21
+
22
+ # A superseded record must name its successor, and the successor must name it back. A one-way
23
+ # supersession leaves a reader on the stale record with no way to find the live one.
24
+ [frontmatter_schema.reciprocal]
25
+ pairs = [{ from = "superseded_by", to = "supersedes" }]
26
+
27
+ # Sections, not prose quality. The gate can check that `Alternatives considered` exists and is not
28
+ # empty; it cannot judge whether the alternatives were real, and one that tried would be the
29
+ # confidently-wrong probe.
30
+ [sections]
31
+ scan = ["{{path}}/{{id_prefix}}-*.md"]
32
+ required = ["Context", "Decision", "Consequences", "Alternatives considered", "Revisit triggers"]
33
+ non_empty = true
34
+ min_words = { "Alternatives considered" = 15 }
35
+ message = """
36
+ {file} has no non-empty `{section}`. A record without alternatives is a description of what was
37
+ done rather than a decision — which is what admission criterion 2 is guarding against.
38
+ """
39
+
40
+ # ── self-tests ────────────────────────────────────────────────────────────────
41
+
42
+ [[self_test]]
43
+ gate = "adr-required-fields"
44
+ expect = "fail"
45
+ fixture = { file = "{{path}}/{{id_prefix}}-0001-x.md", sections = ["Context", "Decision", "Consequences"] }
46
+
47
+ [[self_test]]
48
+ gate = "adr-required-fields"
49
+ expect = "pass"
50
+ fixture = { file = "{{path}}/{{id_prefix}}-0001-x.md", sections = ["Context", "Decision", "Consequences", "Alternatives considered", "Revisit triggers"] }
51
+
52
+ [[self_test]]
53
+ gate = "adr-required-fields"
54
+ expect = "fail"
55
+ fixture = { frontmatter = { id = "{{id_prefix}}-0001", title = "x", status = "superseded", date = "2026-01-01" } }
56
+
57
+ [[self_test]]
58
+ gate = "adr-index-current"
59
+ expect = "fail"
60
+ fixture = { block_rows = 0, source_files = 3 }
61
+
62
+ [[self_test]]
63
+ gate = "adr-index-current"
64
+ expect = "pass"
65
+ fixture = { block_rows = 3, source_files = 3 }
@@ -0,0 +1,67 @@
1
+ [module]
2
+ name = "adr"
3
+ version = "1.0.0"
4
+ rung = 1
5
+ summary = "Numbered, immutable decision records, with an admission rule that keeps the directory from becoming a second documentation tree."
6
+
7
+ [requires]
8
+ modules = []
9
+
10
+ [conflicts]
11
+ modules = []
12
+
13
+ [params.path]
14
+ description = "Directory holding the records"
15
+ default = "docs/decisions"
16
+
17
+ [params.id_prefix]
18
+ description = "Record id prefix"
19
+ default = "ADR"
20
+
21
+ # An `id_width` parameter was declared here and removed by the cross-module audit: nothing consumed
22
+ # it. The template hardcodes the width in its example id and the index gate globs `{{id_prefix}}-*`,
23
+ # so it was a knob wired to nothing. It comes back when an id-claiming command needs it, marked
24
+ # `consumed_by`, and not before.
25
+
26
+ [[gates]]
27
+ id = "adr-index-current"
28
+ kind = "declared"
29
+ engine = "render-freshness"
30
+ table = "gates/adr.toml"
31
+ tier = "fast"
32
+ why = "An index that has stopped listing every record is a routing table that silently drops decisions."
33
+
34
+ [[gates]]
35
+ id = "adr-required-fields"
36
+ kind = "declared"
37
+ engine = "frontmatter-schema"
38
+ table = "gates/adr.toml"
39
+ tier = "fast"
40
+ why = """
41
+ A record with no alternatives section is a description of what was done, not a decision. The
42
+ admission rule exists to keep those out, and this gate is what makes the rule more than advice.
43
+ """
44
+
45
+ [detect]
46
+ # `docs/**/decisions/**` rather than `docs/decisions/**`: hexguard keeps four
47
+ # numbered workflow decisions in `docs/.ai/decisions/` and the narrower glob
48
+ # missed them entirely. Found by running this against the corpus, not by reading.
49
+ paths = ["docs/**/adr/**/*.md", "docs/**/decisions/**/*.md", "docs/**/ADR-*.md", "doc/adr/**/*.md"]
50
+ markers = ["rungs:begin adr"]
51
+
52
+ [[detect.infer]]
53
+ param = "id_prefix"
54
+ pattern = "\\b(ADR|DEC|RFC)-\\d{3,4}\\b"
55
+ min = 3
56
+ scope = ["docs/**/*.md"]
57
+
58
+ [provenance]
59
+ sources = ["axiom-mesh", "rift-forge", "hexguard"]
60
+ patterns = ["adr-record", "adr-admission-rule"]
61
+ incident = """
62
+ axiom-mesh reached 48 ADRs and they stayed navigable, because a five-criterion admission rule was
63
+ applied before creation and routed most candidate decisions into the authoritative doc that already
64
+ owned the topic. Without that gate an ADR directory becomes a second, unindexed documentation tree
65
+ whose entries contradict the first. hexguard has the opposite shape: four numbered workflow
66
+ decisions, no admission rule, and no way to tell what else should have been one.
67
+ """
@@ -0,0 +1,43 @@
1
+ **Authoritative for:** what an assessment checks, and the severity of each failing criterion.
2
+ **Not authoritative for:** whether any subject passes, which is a finding, and whether to act on it, which is a decision.
3
+
4
+ # Audit criteria
5
+
6
+ What `/assess` checks, one {{subject}} at a time. **Edit this file** — the criteria are the part
7
+ that changes most, and a list buried in a skill body is a list nobody maintains.
8
+
9
+ Each criterion needs a **verdict that is checkable** rather than a matter of taste. If two people
10
+ could assess the same subject and disagree, the criterion is not written tightly enough yet.
11
+
12
+ ## Criteria
13
+
14
+ | # | Criterion | Passes when | Severity if failing |
15
+ | --- | --- | --- | --- |
16
+ | 1 | Public surface is documented | Every exported entry point has a description and an example | medium |
17
+ | 2 | Tests cover the contract | The documented behaviour has a test, not just the implementation | high |
18
+ | 3 | Errors are modelled | Expected failures are typed and surfaced, not swallowed | high |
19
+ | 4 | Consumable from outside | A consumer can use it without reading its internals | medium |
20
+ | 5 | No undeclared coupling | Dependencies are declared, not reached through | high |
21
+ | 6 | Change is safe | A breaking change is detectable by something other than a human reading the diff | medium |
22
+
23
+ <!-- Add, remove and reword freely. Six is a starting point, not a standard. -->
24
+
25
+ ## How a criterion becomes a finding
26
+
27
+ A failing criterion becomes **one row** in the findings register:
28
+
29
+ - **Sev** — from the table above
30
+ - **What** — the criterion and the specific way this subject fails it
31
+ - **Evidence** — the path, symbol or command that shows it. **A criterion marked failing with no
32
+ evidence is an opinion**, and next month nobody can tell whether it was checked
33
+
34
+ Passing criteria produce **nothing**. An audit that records its passes is an audit that produces a
35
+ document, and a document per subject is the shape this module exists to prevent.
36
+
37
+ ## What an audit does not do
38
+
39
+ - It does not prioritise. Severity is about the problem; whether to act is a separate decision made
40
+ against the register.
41
+ - It does not fix anything, and it does not open work items. A finding is the observation; a work
42
+ item is the decision.
43
+ - **It does not prove a subject is good.** A subject passing six criteria has passed six criteria.
@@ -0,0 +1,8 @@
1
+ <!-- rungs:begin audit@1.0.0 -->
2
+ ## Assessments
3
+
4
+ **`/assess`** checks one {{subject}} against [`{{criteria_path}}`]({{criteria_path}}) and writes
5
+ each failure as a **row in the findings register**. Never a document per subject — that shape
6
+ reached 268 files in one repo with no way to say which findings were open, and `rungs check`
7
+ refuses it re-forming.
8
+ <!-- rungs:end audit -->