@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
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Gate table for the `gates` module — the repo-agnostic structural set.
|
|
2
|
+
#
|
|
3
|
+
# Nothing here knows what a document *means*. Anything that does belongs to the module that owns
|
|
4
|
+
# that document (ADR-0003's declared/command boundary).
|
|
5
|
+
|
|
6
|
+
# ── gates-links-resolve · gates-paths-exist ───────────────────────────────────
|
|
7
|
+
|
|
8
|
+
[link_integrity]
|
|
9
|
+
scan = ["**/*.md", ".ai/**/*.toml"]
|
|
10
|
+
# `modules/**` is a rungs module source tree, not repo content: a fragment's
|
|
11
|
+
# links are relative to the file it merges *into*, not to where it is authored,
|
|
12
|
+
# so checking them where they sit reports every one as broken. Costs a consumer
|
|
13
|
+
# repo nothing — it has no `modules/` — and was found by running this against
|
|
14
|
+
# the rungs repo itself.
|
|
15
|
+
exclude = ["node_modules/**", "**/archive/**", ".git/**", "modules/*/fragments/**", "modules/*/files/**"]
|
|
16
|
+
check = ["relative_markdown_links", "backticked_paths"]
|
|
17
|
+
# Backticked paths are checked only when they look like paths — containing a `/` or a known
|
|
18
|
+
# extension — because prose is full of `identifiers` that are not files. Under-detection is the
|
|
19
|
+
# correct bias: a missed stale path is a nuisance, a false refusal on every code span is a gate
|
|
20
|
+
# people delete.
|
|
21
|
+
path_hint = ["/", ".md", ".ts", ".cs", ".mjs", ".toml", ".json", ".ps1"]
|
|
22
|
+
exempt = "path-ok:" # ignored unless it states a reason
|
|
23
|
+
|
|
24
|
+
[link_integrity.notes]
|
|
25
|
+
external = "not fetched — a gate that needs the network is a gate that fails on a plane"
|
|
26
|
+
|
|
27
|
+
# ── gates-frontmatter-valid · gates-rules-declare-enforcement ─────────────────
|
|
28
|
+
|
|
29
|
+
[[frontmatter_schema]]
|
|
30
|
+
id = "rules"
|
|
31
|
+
scan = [".ai/rules/**/*.md"]
|
|
32
|
+
exclude = [".ai/rules/README.md"]
|
|
33
|
+
required = ["description", "enforcement"]
|
|
34
|
+
optional = ["paths"]
|
|
35
|
+
enum = { enforcement = ["gated", "review-only"] }
|
|
36
|
+
message = """
|
|
37
|
+
Every rule declares whether something enforces it. `review-only` is a legitimate answer and is
|
|
38
|
+
what most rules honestly are; the failure this catches is the silent third category, where a rule
|
|
39
|
+
reads as mandatory and nothing checks it.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
# The skill frontmatter schema lived here and moved to the `skills` module, for the same reason
|
|
43
|
+
# `ids-unique` and `generated-current` did: knowing what a SKILL.md may contain is knowledge about
|
|
44
|
+
# skills, not a repo-agnostic structural fact. What `gates` contributes is the `frontmatter-schema`
|
|
45
|
+
# engine that both tables run on.
|
|
46
|
+
|
|
47
|
+
# ── gates-self-tests-both-directions ──────────────────────────────────────────
|
|
48
|
+
#
|
|
49
|
+
# The meta-gate.
|
|
50
|
+
|
|
51
|
+
[gate_meta]
|
|
52
|
+
require_self_tests = true
|
|
53
|
+
require_directions = ["pass", "fail"]
|
|
54
|
+
applies_to = "declared" # a command gate is the repo's own; we do not audit its tests
|
|
55
|
+
message = """
|
|
56
|
+
Declared gate `{id}` has no self-test expecting `{missing}`. A gate whose rules are all currently
|
|
57
|
+
satisfied is indistinguishable from a gate that matches nothing, and the fail case is the half
|
|
58
|
+
that proves it can still fire.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
# ── self-tests ────────────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
[[self_test]]
|
|
64
|
+
gate = "gates-links-resolve"
|
|
65
|
+
expect = "fail"
|
|
66
|
+
input = "See [the plan](./does-not-exist.md)."
|
|
67
|
+
|
|
68
|
+
[[self_test]]
|
|
69
|
+
gate = "gates-links-resolve"
|
|
70
|
+
expect = "pass"
|
|
71
|
+
input = "See [this table](./structural.toml)."
|
|
72
|
+
|
|
73
|
+
[[self_test]]
|
|
74
|
+
gate = "gates-paths-exist"
|
|
75
|
+
expect = "pass"
|
|
76
|
+
input = "The `Result<T>` type wraps it." # a code span that is not a path
|
|
77
|
+
|
|
78
|
+
[[self_test]]
|
|
79
|
+
gate = "gates-rules-declare-enforcement"
|
|
80
|
+
expect = "fail"
|
|
81
|
+
fixture = { file = ".ai/rules/x.md", frontmatter = { description = "d", paths = ["**"] } }
|
|
82
|
+
|
|
83
|
+
[[self_test]]
|
|
84
|
+
gate = "gates-rules-declare-enforcement"
|
|
85
|
+
expect = "pass"
|
|
86
|
+
fixture = { file = ".ai/rules/x.md", frontmatter = { description = "d", enforcement = "review-only" } }
|
|
87
|
+
|
|
88
|
+
[[self_test]]
|
|
89
|
+
gate = "gates-frontmatter-valid"
|
|
90
|
+
expect = "fail"
|
|
91
|
+
fixture = { file = ".claude/skills/x/SKILL.md", frontmatter = { name = "x", description = "d", "argument-hint" = "y" } }
|
|
92
|
+
|
|
93
|
+
[[self_test]]
|
|
94
|
+
gate = "gates-self-tests-both-directions"
|
|
95
|
+
expect = "fail"
|
|
96
|
+
fixture = { gate = "example", self_tests = ["pass"] }
|
|
97
|
+
|
|
98
|
+
[[self_test]]
|
|
99
|
+
gate = "gates-self-tests-both-directions"
|
|
100
|
+
expect = "pass"
|
|
101
|
+
fixture = { gate = "example", self_tests = ["pass", "fail"] }
|
|
102
|
+
|
|
103
|
+
# `gates-paths-exist` and `gates-frontmatter-valid` share the tables above with
|
|
104
|
+
# their siblings, but the meta-gate keys self-tests by gate id — so a shared
|
|
105
|
+
# table still needs a pass and a fail per gate. That is correct: two gates
|
|
106
|
+
# running the same engine can still be scoped differently, and only their own
|
|
107
|
+
# fixtures prove each one fires.
|
|
108
|
+
|
|
109
|
+
[[self_test]]
|
|
110
|
+
gate = "gates-paths-exist"
|
|
111
|
+
expect = "fail"
|
|
112
|
+
input = "The mapper lives in [`src/gone.ts`](src/gone.ts)."
|
|
113
|
+
|
|
114
|
+
[[self_test]]
|
|
115
|
+
gate = "gates-paths-exist"
|
|
116
|
+
expect = "pass"
|
|
117
|
+
input = "The mapper lives in [`structural.toml`](./structural.toml)."
|
|
118
|
+
|
|
119
|
+
[[self_test]]
|
|
120
|
+
gate = "gates-frontmatter-valid"
|
|
121
|
+
expect = "pass"
|
|
122
|
+
fixture = { file = ".claude/skills/x/SKILL.md", frontmatter = { name = "x", description = "d" } }
|
|
123
|
+
|
|
124
|
+
[[self_test]]
|
|
125
|
+
gate = "gates-frontmatter-valid"
|
|
126
|
+
expect = "fail"
|
|
127
|
+
fixture = { file = ".claude/skills/x/SKILL.md", frontmatter = { description = "d" } }
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
[module]
|
|
2
|
+
name = "gates"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
rung = 1
|
|
5
|
+
summary = "The gate runner, the registry every other module registers into, the run ledger, and the generic structural gate set."
|
|
6
|
+
|
|
7
|
+
[requires]
|
|
8
|
+
modules = ["instructions"]
|
|
9
|
+
|
|
10
|
+
[conflicts]
|
|
11
|
+
modules = []
|
|
12
|
+
|
|
13
|
+
# ── parameters ────────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
[params.tiers]
|
|
16
|
+
description = "Ordered gate tiers. The first is the one meant to run constantly."
|
|
17
|
+
default = ["fast", "full"]
|
|
18
|
+
|
|
19
|
+
[params.ledger]
|
|
20
|
+
description = """
|
|
21
|
+
Record exit status and wall-clock per gate per run to a gitignored ledger (ADR-0005). Off means
|
|
22
|
+
`doctor` cannot answer which gates never fire, and declared durations stay hand-typed.
|
|
23
|
+
"""
|
|
24
|
+
default = true
|
|
25
|
+
|
|
26
|
+
[params.fast_budget_ms]
|
|
27
|
+
description = """
|
|
28
|
+
Wall-clock the fast tier is expected to stay inside. Compared against the ledger's observed
|
|
29
|
+
values, not against typed per-gate numbers — that comparison is the reason the ledger exists.
|
|
30
|
+
"""
|
|
31
|
+
default = 30000
|
|
32
|
+
|
|
33
|
+
# ── gates ─────────────────────────────────────────────────────────────────────
|
|
34
|
+
#
|
|
35
|
+
# Only genuinely repo-agnostic checks live here. Anything needing to know what a
|
|
36
|
+
# document means belongs to the module that owns that document.
|
|
37
|
+
|
|
38
|
+
[[gates]]
|
|
39
|
+
id = "gates-links-resolve"
|
|
40
|
+
kind = "declared"
|
|
41
|
+
engine = "link-integrity"
|
|
42
|
+
table = "gates/structural.toml"
|
|
43
|
+
tier = "fast"
|
|
44
|
+
why = "A relative link that stopped resolving is the cheapest possible signal that a document moved and its citers did not."
|
|
45
|
+
|
|
46
|
+
[[gates]]
|
|
47
|
+
id = "gates-paths-exist"
|
|
48
|
+
kind = "declared"
|
|
49
|
+
engine = "link-integrity"
|
|
50
|
+
table = "gates/structural.toml"
|
|
51
|
+
tier = "fast"
|
|
52
|
+
why = "Instruction files name paths. hexguard's seven instruction files describe workspace registration and CI wiring across 105 packages, and nothing detected a moved path."
|
|
53
|
+
|
|
54
|
+
[[gates]]
|
|
55
|
+
id = "gates-frontmatter-valid"
|
|
56
|
+
kind = "declared"
|
|
57
|
+
engine = "frontmatter-schema"
|
|
58
|
+
table = "gates/structural.toml"
|
|
59
|
+
tier = "fast"
|
|
60
|
+
why = "A skill with malformed frontmatter does not fail; it silently never loads."
|
|
61
|
+
|
|
62
|
+
[[gates]]
|
|
63
|
+
id = "gates-rules-declare-enforcement"
|
|
64
|
+
kind = "declared"
|
|
65
|
+
engine = "frontmatter-schema"
|
|
66
|
+
table = "gates/structural.toml"
|
|
67
|
+
tier = "fast"
|
|
68
|
+
why = """
|
|
69
|
+
Every rule declares `gated` or `review-only`, and a rule claiming MANDATORY with no gate behind it
|
|
70
|
+
is reported. This is failure mode F1 made detectable: all four source repos wrote rules they could
|
|
71
|
+
not check, and all four decayed — including inside the file that states the rule.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
[[gates]]
|
|
75
|
+
id = "gates-self-tests-both-directions"
|
|
76
|
+
kind = "declared"
|
|
77
|
+
engine = "gate-meta"
|
|
78
|
+
table = "gates/structural.toml"
|
|
79
|
+
tier = "fast"
|
|
80
|
+
why = """
|
|
81
|
+
The meta-gate. Every declared gate must carry at least one self-test expecting `fail` and one
|
|
82
|
+
expecting `pass`, because a gate whose rules are currently satisfied is indistinguishable from a
|
|
83
|
+
gate that matches nothing.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
# ── skills ────────────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
[skills.harden-rule]
|
|
89
|
+
# No extensions. Escalating a broken rule is exactly the kind of thing a model
|
|
90
|
+
# should be able to reach for on its own, mid-task, without being asked.
|
|
91
|
+
|
|
92
|
+
# ── detection (ADR-0004) ──────────────────────────────────────────────────────
|
|
93
|
+
#
|
|
94
|
+
# Deliberately narrow. A repo with a folder of validation scripts has gates; it
|
|
95
|
+
# does not have *this* registry, and conflating the two would let `add` believe
|
|
96
|
+
# it owns scripts it has never read.
|
|
97
|
+
|
|
98
|
+
[detect]
|
|
99
|
+
paths = [".ai/gates.toml"]
|
|
100
|
+
markers = ["rungs:begin gates"]
|
|
101
|
+
|
|
102
|
+
# Existing validators. Not a paradigm difference — a repo that has these has the
|
|
103
|
+
# thing this module wraps, and they adopt cleanly as `command` gates. `add`
|
|
104
|
+
# proposes registering them; it never rewrites one.
|
|
105
|
+
[[detect.adopt_as]]
|
|
106
|
+
kind = "command"
|
|
107
|
+
paths = [
|
|
108
|
+
".github/scripts/check-*.mjs",
|
|
109
|
+
".github/scripts/validate-*.mjs",
|
|
110
|
+
"scripts/validate-*.ps1",
|
|
111
|
+
"scripts/check-*.sh",
|
|
112
|
+
]
|
|
113
|
+
note = """
|
|
114
|
+
Registering an existing validator gives it the runner, the ledger and attribution without changing
|
|
115
|
+
a line of it. This is the axiom-mesh case (8 PowerShell validators, no root package.json) and the
|
|
116
|
+
rift-forge case (82 registry entries, no ledger) in one mechanism.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
# ── provenance (required, validated) ──────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
[provenance]
|
|
122
|
+
sources = ["rift-forge", "axiom-mesh"]
|
|
123
|
+
patterns = [
|
|
124
|
+
"structural-gates",
|
|
125
|
+
"gate-self-test",
|
|
126
|
+
"read-the-negation",
|
|
127
|
+
"reasoned-exemption",
|
|
128
|
+
"computed-claims",
|
|
129
|
+
"generate-derivable",
|
|
130
|
+
"enforcement-declaration",
|
|
131
|
+
"ageing-signal",
|
|
132
|
+
"tool-level-hook",
|
|
133
|
+
"instruction-hardening",
|
|
134
|
+
]
|
|
135
|
+
incident = """
|
|
136
|
+
rift-forge's verify.mjs carries 82 gates each with a hand-typed `ms` duration measured on a single
|
|
137
|
+
date, supporting a "~30s" fast-tier budget stated in an authority document. The fast tier's 73
|
|
138
|
+
typed values sum to 38.4s serial; it runs concurrently, so the claim is plausible and unverifiable
|
|
139
|
+
from the repo. The runner produces the true duration of all 82 on every run and discards it — the
|
|
140
|
+
computed-claims failure inside the runner that enforces the other gates. Separately, its CI was red
|
|
141
|
+
for 11 of 15 runs on two permanently-broken jobs, kept survivable by attribution, which is also
|
|
142
|
+
what removed the pressure to fix them.
|
|
143
|
+
"""
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: harden-rule
|
|
3
|
+
description: >-
|
|
4
|
+
Turn a mistake into a rule that cannot be made again, by picking the cheapest rung that actually
|
|
5
|
+
holds: a sentence at the point of use, a line in a skill, a path-scoped rule, or a gate or hook.
|
|
6
|
+
Use after getting something wrong that an instruction could have prevented, after the same
|
|
7
|
+
mistake happens twice, when a rule that already exists was broken anyway, or when asked to
|
|
8
|
+
"write that down", "make sure this doesn't happen again", or "add a check for this". Also use to
|
|
9
|
+
decide whether a rule is worth a gate at all. Recording the observation without changing the
|
|
10
|
+
instruction is /record-finding; this skill changes the instruction.
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Harden a rule
|
|
14
|
+
|
|
15
|
+
**A mistake an instruction could have prevented is a defect in the instruction.** Repairing it
|
|
16
|
+
belongs in the same change as repairing the mistake — nobody will ask, because a reviewer reads the
|
|
17
|
+
fix, not the counterfactual in which you had been told the right thing first.
|
|
18
|
+
|
|
19
|
+
Run this unprompted. It is not a request; it is part of finishing.
|
|
20
|
+
|
|
21
|
+
## 1. Name the shape, not the incident
|
|
22
|
+
|
|
23
|
+
Write the rule as something the *next* agent can recognise before making the mistake, not as a
|
|
24
|
+
description of what you did. The trigger has to be mechanical: you should not need to notice you
|
|
25
|
+
were careless, only to notice a shape.
|
|
26
|
+
|
|
27
|
+
Weak: *"be careful when editing the dataset."*
|
|
28
|
+
Strong: *"reconcile a generated artifact by regenerating it, never by merging text."*
|
|
29
|
+
|
|
30
|
+
If you cannot state the shape, you have a finding, not a rule. Stop here and record it.
|
|
31
|
+
|
|
32
|
+
## 2. Check whether the rule already exists
|
|
33
|
+
|
|
34
|
+
Search the entry document, the rules, and the skills for it.
|
|
35
|
+
|
|
36
|
+
**If it already existed and was broken anyway, skip to rung 3 or 4.** Do not restate it, do not
|
|
37
|
+
bold it, do not add an emphasis marker. A louder sentence in a file that was already read changes
|
|
38
|
+
nothing, and the restatement is itself evidence that prose has been tried.
|
|
39
|
+
|
|
40
|
+
## 3. Pick the cheapest rung that holds
|
|
41
|
+
|
|
42
|
+
| Rung | Use when | Cost |
|
|
43
|
+
| --- | --- | --- |
|
|
44
|
+
| **1 — a sentence at the point of use** | First occurrence, and the rule is local to one place | Minutes |
|
|
45
|
+
| **2 — a line in the relevant skill** | The mistake happens *during* a procedure that already has a skill | Minutes |
|
|
46
|
+
| **3 — a path-scoped rule** in `.ai/rules/` | It applies to a surface rather than a task, and would be noise in the always-on document | An hour |
|
|
47
|
+
| **4 — a gate or a hook** | **The rule has already been broken after being written down**, or the check is mechanical and cheap | Half a day, plus maintenance |
|
|
48
|
+
|
|
49
|
+
Rung 1 goes **where the mistake is made** — not in a preamble, not in a summary section. The rule
|
|
50
|
+
has to be in front of the person about to break it.
|
|
51
|
+
|
|
52
|
+
## 4. If it is rung 4, build it properly
|
|
53
|
+
|
|
54
|
+
Add the gate to the owning module's table, or as a `command` gate in `.ai/gates.toml`. Then, before
|
|
55
|
+
you call it done:
|
|
56
|
+
|
|
57
|
+
- **Self-tests, both directions.** One expecting `fail`, one expecting `pass`. A gate whose rules
|
|
58
|
+
are all currently satisfied is indistinguishable from a gate that matches nothing.
|
|
59
|
+
- **Read the negation before the token.** If the gate refuses a phrase, check a preceding-context
|
|
60
|
+
window for a negation cue first — otherwise it refuses the sentence that documents the fix, and a
|
|
61
|
+
guard that refuses its own fix is one people disable.
|
|
62
|
+
- **An exemption must carry a reason.** `<marker>: <why>`, ignored when the reason is missing.
|
|
63
|
+
- **Recompute rather than compare.** If the gate checks a number, derive the number. A probe
|
|
64
|
+
encoding a guess is confidently wrong, which is worse than a typed value nobody trusts.
|
|
65
|
+
- **Pin what it does not cover**, in its own message. Green must never read as "verified".
|
|
66
|
+
|
|
67
|
+
## 5. Write down where it is now true
|
|
68
|
+
|
|
69
|
+
A rule usually lives in more than one place: the authority document that explains *why*, the
|
|
70
|
+
always-on document that every session reads, and the skills that execute it.
|
|
71
|
+
|
|
72
|
+
**Fix the authority first, then the citers.** A citer corrected against a stale authority is a
|
|
73
|
+
second wrong statement, and the next reader cannot tell which one won.
|
|
74
|
+
|
|
75
|
+
**A citation is not propagation.** "See §3" ages into a false claim the moment §3 changes, and it
|
|
76
|
+
reads as verified precisely because it names a source.
|
|
77
|
+
|
|
78
|
+
## 6. Say what you did
|
|
79
|
+
|
|
80
|
+
In the same change: what went wrong, which rung you chose, and why the cheaper rungs were not
|
|
81
|
+
enough. That last part is what stops the next person re-litigating it — and if the honest answer is
|
|
82
|
+
"prose had already failed twice", write that.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Path-scoped rules
|
|
2
|
+
|
|
3
|
+
**These files are the source. The copies under `.claude/rules/`, `.github/instructions/` and
|
|
4
|
+
`.cursor/rules/` are generated — do not edit those.** Run `rungs render` after changing anything
|
|
5
|
+
here; `rungs check` refuses a stale rendering.
|
|
6
|
+
|
|
7
|
+
A rule here loads only when an agent touches a matching file, so it costs nothing until it is
|
|
8
|
+
relevant. That is what keeps [`../../AGENTS.md`](../../AGENTS.md) inside its line budget.
|
|
9
|
+
|
|
10
|
+
## When something belongs here
|
|
11
|
+
|
|
12
|
+
| Content | Goes |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| A fact every session needs | `AGENTS.md` |
|
|
15
|
+
| A rule for one part of the tree | **here** |
|
|
16
|
+
| A multi-step procedure with a beginning and an end | a skill |
|
|
17
|
+
|
|
18
|
+
If you are adding a section to `AGENTS.md` that begins "when working on X…", it belongs here.
|
|
19
|
+
|
|
20
|
+
## Format
|
|
21
|
+
|
|
22
|
+
```markdown
|
|
23
|
+
---
|
|
24
|
+
description: >-
|
|
25
|
+
What this covers and when it applies. One or two sentences — some harnesses use this to decide
|
|
26
|
+
whether to load the rule at all, so lead with the trigger.
|
|
27
|
+
paths:
|
|
28
|
+
- "src/api/**/*.ts"
|
|
29
|
+
- "tests/api/**"
|
|
30
|
+
enforcement: gated # gated | review-only
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
# API rules
|
|
34
|
+
|
|
35
|
+
- Concrete, checkable statements. "Use 2-space indentation", not "format properly".
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- **`paths`** — globs. Omit for a rule that should load every session, but prefer `AGENTS.md` for
|
|
39
|
+
that; a rule with no paths is a rule with no reason to be here.
|
|
40
|
+
- **`enforcement`** — required. `gated` means a gate enforces it; `review-only` means nothing does.
|
|
41
|
+
There is no silent third category, because a silent third category is what every repo this
|
|
42
|
+
content came from actually had.
|
|
43
|
+
|
|
44
|
+
## What does not survive rendering
|
|
45
|
+
|
|
46
|
+
Not every harness can express every field, and the render report names each loss:
|
|
47
|
+
|
|
48
|
+
| Field | Claude | Copilot | Cursor | AGENTS.md-only |
|
|
49
|
+
| --- | --- | --- | --- | --- |
|
|
50
|
+
| `paths` | ✅ | ✅ `applyTo` | ✅ `globs` | directory-level, or a routing line |
|
|
51
|
+
| `description` | dropped | ✅ | ✅ | — |
|
|
52
|
+
|
|
53
|
+
Read `.ai/render-report.md` after a render to see what your harness set actually received.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# AGENTS.md — {{project_name}}
|
|
2
|
+
|
|
3
|
+
Canonical agent policy for this repository. Read in full before making changes.
|
|
4
|
+
|
|
5
|
+
This is the **always-on** document: facts every session needs. Anything that applies to one part of
|
|
6
|
+
the tree belongs in [`.ai/rules/`](.ai/rules/README.md) instead, and anything that is a multi-step
|
|
7
|
+
procedure belongs in a skill. Both load only when relevant, and this file has a line budget that
|
|
8
|
+
`rungs check` enforces.
|
|
9
|
+
|
|
10
|
+
## What this is
|
|
11
|
+
|
|
12
|
+
<!-- One paragraph: what the project does, for whom, and the one property that must not break. -->
|
|
13
|
+
|
|
14
|
+
## Repo map
|
|
15
|
+
|
|
16
|
+
<!-- rungs:begin repo-map -->
|
|
17
|
+
<!-- Generated. Run `rungs render` after moving directories. -->
|
|
18
|
+
<!-- rungs:end repo-map -->
|
|
19
|
+
|
|
20
|
+
## Validation matrix
|
|
21
|
+
|
|
22
|
+
**Run the narrowest validation that covers what you touched**, then the standard gates. Running
|
|
23
|
+
everything is slow enough to get skipped; running the wrong subset proves nothing.
|
|
24
|
+
|
|
25
|
+
| Change surface | Run |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| *(fill in per surface)* | |
|
|
28
|
+
| anything | `rungs check` |
|
|
29
|
+
|
|
30
|
+
## Task loop
|
|
31
|
+
|
|
32
|
+
1. **Start from the narrowest concrete anchor** — a file, a symbol, a failing test, a route.
|
|
33
|
+
2. Read the scoped rule for that surface before editing broadly.
|
|
34
|
+
3. Make the smallest change that proves or disproves the current hypothesis.
|
|
35
|
+
4. Run the narrowest validation above.
|
|
36
|
+
5. Update docs when public behaviour or developer workflow changed.
|
|
37
|
+
|
|
38
|
+
Choosing the anchor **before** reading instructions is the point: which instructions apply is then a
|
|
39
|
+
consequence of scope rather than a guess.
|
|
40
|
+
|
|
41
|
+
## Non-negotiables
|
|
42
|
+
|
|
43
|
+
### When you get something wrong, harden the instruction — without being asked
|
|
44
|
+
|
|
45
|
+
**A mistake an instruction could have prevented is a defect in the instruction**, and repairing it
|
|
46
|
+
is part of repairing the mistake. Do it in the same change, unprompted: a reviewer reads the fix,
|
|
47
|
+
not the counterfactual in which you had been told the right thing first, so the repair that does not
|
|
48
|
+
happen here does not happen at all.
|
|
49
|
+
|
|
50
|
+
Pick the cheapest rung that actually holds:
|
|
51
|
+
|
|
52
|
+
1. **A sentence at the point of use** — where the mistake is made, not in a preamble.
|
|
53
|
+
2. **A line in the relevant skill**, where an agent meets the rule during execution.
|
|
54
|
+
3. **A path-scoped rule**, when it applies to a surface rather than a task.
|
|
55
|
+
4. **A gate or a hook**, when the rule has already been broken *after* being written down.
|
|
56
|
+
|
|
57
|
+
**If the rule already existed and you broke it anyway, do not restate it — make it mechanical.** A
|
|
58
|
+
louder sentence in a file you have already read changes nothing. `/harden-rule` walks the ladder.
|
|
59
|
+
|
|
60
|
+
### Editing files from the shell
|
|
61
|
+
|
|
62
|
+
**Never pipe a multi-line edit through `node -e "…"`, `python -c "…"`, or a shell-expanded
|
|
63
|
+
heredoc.** Write the script to a file and run it. Inside a double-quoted shell string backticks are
|
|
64
|
+
command substitution, so a `node -e` that writes documentation deletes the backticked words and
|
|
65
|
+
leaves grammatical text behind — and exits 0.
|
|
66
|
+
|
|
67
|
+
**Chain with `&&`, never `;`**, when a later step consumes an earlier one. A failed producer
|
|
68
|
+
followed by `;` yields an empty variable, and an empty variable written into a file is not a crash;
|
|
69
|
+
it is a wrong value that passes review.
|
|
70
|
+
|
|
71
|
+
### Claims and numbers
|
|
72
|
+
|
|
73
|
+
- **A number a machine can compute is never typed by a human.** If it can be derived, generate it
|
|
74
|
+
and gate it.
|
|
75
|
+
- **A control that cannot fail loudly is not a control.** Filtering a command's output through
|
|
76
|
+
`| tail` or `| grep` reports *that* command's exit status, not the one you care about.
|
|
77
|
+
- **Check the artifact, not the bookkeeping about it.** A status field, a board row, or a triage is
|
|
78
|
+
a claim about the work; the branch, the test, and the file are the work.
|
|
79
|
+
|
|
80
|
+
<!--
|
|
81
|
+
Optional — uncomment if you want it. Not shipped active because it is a preference, not a universal:
|
|
82
|
+
|
|
83
|
+
### Communication style
|
|
84
|
+
|
|
85
|
+
- Never tell me what I want to hear; prioritise truth over comfort.
|
|
86
|
+
- Contradict me when you disagree, and challenge assumptions.
|
|
87
|
+
- Be direct and concise. Skip validation and praise.
|
|
88
|
+
- If there is a better approach, recommend it even if I did not ask.
|
|
89
|
+
-->
|
|
90
|
+
|
|
91
|
+
## Conventions
|
|
92
|
+
|
|
93
|
+
<!-- Positive and negative both. A prohibition should state the evidence that would reverse it,
|
|
94
|
+
e.g. "no dedicated X package until two real consumers prove a stable API". -->
|
|
95
|
+
|
|
96
|
+
- Commit messages: conventional prefixes (`feat:`, `fix:`, `docs:`, `chore:`…).
|
|
97
|
+
|
|
98
|
+
## Routing
|
|
99
|
+
|
|
100
|
+
| If you need… | Go to |
|
|
101
|
+
| --- | --- |
|
|
102
|
+
| Rules for one part of the tree | [`.ai/rules/`](.ai/rules/README.md) |
|
|
103
|
+
| A multi-step procedure | the skills in this repo — invoke by name |
|
|
104
|
+
| Why a decision was made | *(add when `adr` is installed)* |
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
@AGENTS.md
|
|
2
|
+
|
|
3
|
+
<!--
|
|
4
|
+
Generated by `rungs`. Claude Code does not read AGENTS.md, so this file imports it — one source,
|
|
5
|
+
no second copy to drift (ADR-0001). The import is used rather than a symlink because a symlink
|
|
6
|
+
requires Administrator or Developer Mode on Windows.
|
|
7
|
+
|
|
8
|
+
Add Claude-specific content below this comment. It is yours; `rungs upgrade` will not touch it.
|
|
9
|
+
-->
|
|
10
|
+
|
|
11
|
+
## Claude Code
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Gate table for the `instructions` module. Data only — no script enters the repo (ADR-0002).
|
|
2
|
+
|
|
3
|
+
# ── instructions-core-size ────────────────────────────────────────────────────
|
|
4
|
+
#
|
|
5
|
+
# Counts what is actually loaded: HTML comments are stripped by at least one harness before
|
|
6
|
+
# injection, so they do not count, and neither does frontmatter.
|
|
7
|
+
|
|
8
|
+
[file_budget]
|
|
9
|
+
file = "AGENTS.md"
|
|
10
|
+
max_lines = {{core_budget}}
|
|
11
|
+
ignore = ["html_comments", "frontmatter"]
|
|
12
|
+
warn_at = 0.8
|
|
13
|
+
message = """
|
|
14
|
+
AGENTS.md is over its {{core_budget}}-line budget. Move the most path-specific section into
|
|
15
|
+
.ai/rules/ — that is what the budget is for. Merged module fragments count toward it, so a fragment
|
|
16
|
+
that has grown past ~15 lines is usually the one to look at first.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# ── instructions-render-current ───────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
[[render_freshness]]
|
|
22
|
+
id = "rules"
|
|
23
|
+
sources = [".ai/rules/**/*.md"]
|
|
24
|
+
exclude = [".ai/rules/README.md"] # the scaffold doc is not a rule
|
|
25
|
+
targets = [".claude/rules/**/*.md", ".github/instructions/**/*.instructions.md", ".cursor/rules/**/*.mdc"]
|
|
26
|
+
command = "rungs render"
|
|
27
|
+
# Targets are checked only for harnesses in the installed matrix; the rest are ignored rather
|
|
28
|
+
# than reported missing.
|
|
29
|
+
|
|
30
|
+
# ── instructions-repo-map-current ─────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
[[render_freshness]]
|
|
33
|
+
id = "repo-map"
|
|
34
|
+
block = { file = "AGENTS.md", marker = "repo-map" }
|
|
35
|
+
derives = "directory-tree"
|
|
36
|
+
depth = 2
|
|
37
|
+
command = "rungs render"
|
|
38
|
+
|
|
39
|
+
# ── instructions-shell-backticks (hook: pre-tool-use) ─────────────────────────
|
|
40
|
+
#
|
|
41
|
+
# Refuses a command that would let the shell expand backticks inside a double-quoted string.
|
|
42
|
+
# It permits every form the rule prescribes, and asserts BOTH directions below, because a guard
|
|
43
|
+
# that also refuses its own fix is one people disable.
|
|
44
|
+
|
|
45
|
+
[shell_safety]
|
|
46
|
+
refuse = [
|
|
47
|
+
{ pattern = "node\\s+-e\\s+\"[^\"]*`", why = "backtick inside a double-quoted -e string is command substitution" },
|
|
48
|
+
{ pattern = "python\\s+-c\\s+\"[^\"]*`", why = "same trap, Python" },
|
|
49
|
+
{ pattern = "<<\\s*EOF", why = "unquoted heredoc is shell-expanded; use <<'EOF'" },
|
|
50
|
+
]
|
|
51
|
+
permit = [
|
|
52
|
+
"script file invocation",
|
|
53
|
+
"single-quoted -e string",
|
|
54
|
+
"<<'EOF'",
|
|
55
|
+
"escaped backtick",
|
|
56
|
+
]
|
|
57
|
+
message = "Write the script to a file and run it. A file has no shell quoting layer, so backticks are just characters."
|
|
58
|
+
|
|
59
|
+
# ── self-tests ────────────────────────────────────────────────────────────────
|
|
60
|
+
#
|
|
61
|
+
# Both directions, always. A gate whose rules are currently satisfied is indistinguishable from a
|
|
62
|
+
# gate that matches nothing.
|
|
63
|
+
|
|
64
|
+
[[self_test]]
|
|
65
|
+
gate = "instructions-shell-backticks"
|
|
66
|
+
expect = "fail"
|
|
67
|
+
input = "node -e \"fs.writeFileSync('a.md', 'see `foo`')\""
|
|
68
|
+
|
|
69
|
+
[[self_test]]
|
|
70
|
+
gate = "instructions-shell-backticks"
|
|
71
|
+
expect = "pass"
|
|
72
|
+
input = "node scripts/edit.mjs"
|
|
73
|
+
|
|
74
|
+
[[self_test]]
|
|
75
|
+
gate = "instructions-shell-backticks"
|
|
76
|
+
expect = "pass"
|
|
77
|
+
input = "cat <<'EOF' > a.md"
|
|
78
|
+
|
|
79
|
+
[[self_test]]
|
|
80
|
+
gate = "instructions-shell-backticks"
|
|
81
|
+
expect = "fail"
|
|
82
|
+
input = "cat <<EOF > a.md"
|
|
83
|
+
|
|
84
|
+
[[self_test]]
|
|
85
|
+
gate = "instructions-core-size"
|
|
86
|
+
expect = "fail"
|
|
87
|
+
fixture = { file = "AGENTS.md", lines = 9999 }
|
|
88
|
+
|
|
89
|
+
[[self_test]]
|
|
90
|
+
gate = "instructions-core-size"
|
|
91
|
+
expect = "pass"
|
|
92
|
+
fixture = { file = "AGENTS.md", lines = 10 }
|
|
93
|
+
|
|
94
|
+
# ── render-freshness self-tests ───────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
[[self_test]]
|
|
97
|
+
gate = "instructions-render-current"
|
|
98
|
+
expect = "fail"
|
|
99
|
+
fixture = { sources = [".ai/rules/backend.md"], targets = [] }
|
|
100
|
+
|
|
101
|
+
[[self_test]]
|
|
102
|
+
gate = "instructions-render-current"
|
|
103
|
+
expect = "pass"
|
|
104
|
+
fixture = { sources = [".ai/rules/backend.md"], targets = [".claude/rules/backend.md"] }
|
|
105
|
+
|
|
106
|
+
[[self_test]]
|
|
107
|
+
gate = "instructions-repo-map-current"
|
|
108
|
+
expect = "fail"
|
|
109
|
+
fixture = { file = "AGENTS.md", blocks = [] }
|
|
110
|
+
|
|
111
|
+
[[self_test]]
|
|
112
|
+
gate = "instructions-repo-map-current"
|
|
113
|
+
expect = "pass"
|
|
114
|
+
fixture = { file = "AGENTS.md", blocks = ["repo-map"] }
|