@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.
- package/LICENSE +21 -0
- package/README.md +196 -0
- package/modules/README.md +116 -0
- package/modules/adr/files/{{path}}/README.md +52 -0
- package/modules/adr/files/{{path}}/TEMPLATE.md +55 -0
- package/modules/adr/fragments/AGENTS.md +8 -0
- package/modules/adr/gates/adr.toml +65 -0
- package/modules/adr/module.toml +67 -0
- package/modules/audit/files/{{criteria_path}} +43 -0
- package/modules/audit/fragments/AGENTS.md +8 -0
- package/modules/audit/gates/audit.toml +48 -0
- package/modules/audit/module.toml +72 -0
- package/modules/audit/skills/assess/SKILL.md +62 -0
- package/modules/backlog/files/docs/{{root}}/BACKLOG.md +52 -0
- package/modules/backlog/files/docs/{{root}}/README.md +110 -0
- package/modules/backlog/files/docs/{{root}}/TEMPLATE.md +56 -0
- package/modules/backlog/files/docs/{{root}}/archive/README.md +13 -0
- package/modules/backlog/files/docs/{{root}}/items/README.md +13 -0
- package/modules/backlog/fragments/AGENTS.md +9 -0
- package/modules/backlog/gates/ids.toml +106 -0
- package/modules/backlog/module.toml +150 -0
- package/modules/backlog/rules/work-items.md +44 -0
- package/modules/backlog/skills/backlog-summary/SKILL.md +65 -0
- package/modules/backlog/skills/work-item/SKILL.md +96 -0
- package/modules/ci/files/{{workflow_path}} +43 -0
- package/modules/ci/gates/ci.toml +44 -0
- package/modules/ci/module.toml +74 -0
- package/modules/concurrency/files/docs/concurrent-sessions.md +87 -0
- package/modules/concurrency/fragments/AGENTS.md +10 -0
- package/modules/concurrency/fragments/gitattributes +15 -0
- package/modules/concurrency/gates/concurrency.toml +54 -0
- package/modules/concurrency/module.toml +113 -0
- package/modules/design-sync/fragments/AGENTS.md +8 -0
- package/modules/design-sync/gates/design.toml +70 -0
- package/modules/design-sync/module.toml +82 -0
- package/modules/design-sync/skills/design-align/SKILL.md +51 -0
- package/modules/design-sync/skills/design-pull/SKILL.md +50 -0
- package/modules/doc-authority/files/{{registry_path}} +59 -0
- package/modules/doc-authority/files/{{rules_path}} +51 -0
- package/modules/doc-authority/fragments/AGENTS.md +9 -0
- package/modules/doc-authority/gates/authority.toml +127 -0
- package/modules/doc-authority/module.toml +111 -0
- package/modules/findings/files/docs/{{backlog.root}}/FINDINGS.md +56 -0
- package/modules/findings/fragments/AGENTS.md +7 -0
- package/modules/findings/gates/findings.toml +68 -0
- package/modules/findings/module.toml +100 -0
- package/modules/findings/skills/record-finding/SKILL.md +63 -0
- package/modules/gates/files/.ai/gates.toml +49 -0
- package/modules/gates/fragments/AGENTS.md +8 -0
- package/modules/gates/fragments/gitignore +4 -0
- package/modules/gates/gates/structural.toml +127 -0
- package/modules/gates/module.toml +143 -0
- package/modules/gates/skills/harden-rule/SKILL.md +82 -0
- package/modules/instructions/files/.ai/rules/README.md +53 -0
- package/modules/instructions/files/AGENTS.md +104 -0
- package/modules/instructions/files/CLAUDE.md +11 -0
- package/modules/instructions/gates/core.toml +114 -0
- package/modules/instructions/module.toml +127 -0
- package/modules/release/fragments/AGENTS.md +8 -0
- package/modules/release/gates/release.toml +85 -0
- package/modules/release/module.toml +97 -0
- package/modules/release/skills/cut-release/SKILL.md +81 -0
- package/modules/session/files/{{archive}}/README.md +28 -0
- package/modules/session/files/{{path}} +56 -0
- package/modules/session/fragments/AGENTS.md +6 -0
- package/modules/session/gates/session.toml +63 -0
- package/modules/session/module.toml +72 -0
- package/modules/session/skills/close-session/SKILL.md +59 -0
- package/modules/skills/fragments/AGENTS.md +8 -0
- package/modules/skills/gates/skills.toml +94 -0
- package/modules/skills/module.toml +89 -0
- package/modules/skills/rules/skill-authoring.md +71 -0
- package/modules/specs/files/{{path}}/README.md +63 -0
- package/modules/specs/files/{{path}}/TEMPLATE.md +62 -0
- package/modules/specs/fragments/AGENTS.md +8 -0
- package/modules/specs/gates/specs.toml +119 -0
- package/modules/specs/module.toml +107 -0
- package/modules/workflows/fragments/AGENTS.md +8 -0
- package/modules/workflows/gates/workflows.toml +65 -0
- package/modules/workflows/module.toml +75 -0
- package/modules/workflows/rules/planning-tiers.md +46 -0
- package/modules/workflows/rules/reuse-decision.md +50 -0
- package/modules/workflows/skills/decompose/SKILL.md +75 -0
- package/package.json +42 -0
- package/src/add.ts +331 -0
- package/src/check.ts +216 -0
- package/src/cli.ts +411 -0
- package/src/detect.ts +246 -0
- package/src/engines.ts +260 -0
- package/src/engines2.ts +297 -0
- package/src/engines3.ts +194 -0
- package/src/glob.ts +101 -0
- package/src/lifecycle.ts +268 -0
- package/src/manifest.ts +108 -0
- package/src/render.ts +222 -0
- package/src/substitute.ts +78 -0
- 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 -->
|