@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
@@ -0,0 +1,127 @@
1
+ # Gate table for the `doc-authority` module.
2
+ #
3
+ # Every gate here is vocabulary-based, which is a real ceiling: it catches a surface that dropped
4
+ # the current phrasing, not one that kept the phrasing and changed meaning. That limit is pinned in
5
+ # each message rather than hidden, so green never reads as "verified".
6
+
7
+ # โ”€โ”€ docauth-ownership-respected โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
8
+
9
+ [term_ownership]
10
+ registry = "{{registry_path}}"
11
+ columns = { topic = "Topic", owner = "Owner", forbidden = "Must NOT appear in" }
12
+ # A topic is "engaged" by a document that carries several of its terms in one section โ€” not by a
13
+ # passing mention. Under-detection is the correct bias: a false refusal on a cross-reference is a
14
+ # gate people delete.
15
+ engage_min_terms = 3
16
+ engage_window = "section"
17
+ exempt_marker = "ownership-ok:" # ignored unless it states a reason
18
+ message = """
19
+ {file} restates the topic "{topic}", which is owned by {owner}. Reference it with a link instead โ€”
20
+ a paraphrase looks maintained while it decays, and when {owner} changes this becomes wrong while
21
+ still reading as deliberate.
22
+ """
23
+
24
+ [term_ownership.pins]
25
+ note = "Detects restatement by vocabulary. It cannot detect a restatement that uses different words, nor one that is subtly wrong while using the right words."
26
+
27
+ # โ”€โ”€ docauth-scope-headers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
28
+
29
+ [[sections]]
30
+ id = "scope-headers"
31
+ scan = ["docs/**/*.md"]
32
+ # A template is not a document. A scope header inside `TEMPLATE.md` would be
33
+ # copied into every item made from it, which is both wrong and self-defeating โ€”
34
+ # the copies would each claim authority over the same topic. The root README is
35
+ # exempt for axiom-mesh's original reason: it is a navigation page, and a scope
36
+ # header there makes a worse landing page for no gain.
37
+ exclude = ["README.md", "docs/**/archive/**", "**/TEMPLATE.md", "**/*-TEMPLATE.md"]
38
+ requires_opening = ["**Authoritative for:**", "**Not authoritative for:**"]
39
+ enabled_by = "{{scope_headers}}"
40
+ message = "{file} does not open with both scope blocks. A reader who opened the wrong document should learn it in one line."
41
+
42
+ # The stub rule: a file carrying only scope headers may hold nothing but cross-references.
43
+ [sections.stub_rule]
44
+ when = "only_scope_headers"
45
+ allows_only = ["links", "headings"]
46
+ message = "{file} is a stub and has grown content. Promote it deliberately or move the content to its owner โ€” this is how a placeholder quietly acquires a topic it does not own."
47
+
48
+ # โ”€โ”€ docauth-working-rules โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
49
+
50
+ [rule_propagation]
51
+ registry = "{{rules_path}}"
52
+ columns = { rule = "Rule", authority = "Authority", surfaces = "Surfaces that restate it", retired = "Retired wording" }
53
+ # Read the negation before the token: a `retired` phrase inside "do NOT <retired>" is the fix, not
54
+ # the violation. A guard that also refuses its own fix is one people disable.
55
+ negation_window = 60
56
+ exempt_marker = "working-rule-ok:" # ignored unless it states a reason
57
+ message = """
58
+ {surface} engages "{rule}" but carries the retired wording, or is missing the current vocabulary.
59
+ Fix the authority first, then the citers โ€” a citer corrected against a stale authority is a second
60
+ wrong statement, and the next reader cannot tell which one won.
61
+ """
62
+
63
+ # โ”€โ”€ docauth-no-redirect-stubs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
64
+
65
+ [file_population]
66
+ id = "redirect-stubs"
67
+ scan = ["docs/**/*.md"]
68
+ detect = "body_is_only_a_pointer"
69
+ max_body_words = 40
70
+ fail_at = 1
71
+ message = """
72
+ {file} exists only to point elsewhere. It will be cited, and the citation will look authoritative.
73
+ Delete it and repoint the citers โ€” one repo needed four separate warnings in its instruction file
74
+ because it kept one of these.
75
+ """
76
+
77
+ # โ”€โ”€ self-tests โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
78
+
79
+ [[self_test]]
80
+ gate = "docauth-working-rules"
81
+ expect = "fail"
82
+ input = "Claim the id on the integration branch before starting."
83
+
84
+ [[self_test]]
85
+ gate = "docauth-working-rules"
86
+ expect = "pass"
87
+ input = "Do not claim the id on the integration branch; claim it on your own."
88
+
89
+ [[self_test]]
90
+ gate = "docauth-working-rules"
91
+ expect = "pass"
92
+ input = "We used to claim on the integration branch. <!-- working-rule-ok: id-claiming โ€” quotes the retired wording to explain the change -->"
93
+
94
+ [[self_test]]
95
+ gate = "docauth-working-rules"
96
+ expect = "fail"
97
+ input = "We used to claim on the integration branch. <!-- working-rule-ok: -->"
98
+
99
+ [[self_test]]
100
+ gate = "docauth-scope-headers"
101
+ expect = "fail"
102
+ fixture = { file = "docs/a.md", opening = "# A" }
103
+
104
+ [[self_test]]
105
+ gate = "docauth-scope-headers"
106
+ expect = "pass"
107
+ fixture = { file = "README.md", opening = "# Project" }
108
+
109
+ [[self_test]]
110
+ gate = "docauth-ownership-respected"
111
+ expect = "pass"
112
+ fixture = { terms_matched = 1 }
113
+
114
+ [[self_test]]
115
+ gate = "docauth-ownership-respected"
116
+ expect = "fail"
117
+ fixture = { terms_matched = 5, file = "docs/other.md", owner = "AGENTS.md" }
118
+
119
+ [[self_test]]
120
+ gate = "docauth-no-redirect-stubs"
121
+ expect = "fail"
122
+ fixture = { body_words = 12, links = 1 }
123
+
124
+ [[self_test]]
125
+ gate = "docauth-no-redirect-stubs"
126
+ expect = "pass"
127
+ fixture = { body_words = 300, links = 4 }
@@ -0,0 +1,111 @@
1
+ [module]
2
+ name = "doc-authority"
3
+ version = "1.0.0"
4
+ rung = 4
5
+ summary = "One owner per topic, declared in a registry and checked โ€” plus a propagation gate for rules that live in more surfaces than their authority."
6
+
7
+ [requires]
8
+ modules = ["gates"]
9
+
10
+ [conflicts]
11
+ modules = []
12
+
13
+ # Rung 4: for repos where several documentation surfaces make claims about the
14
+ # same things. Below that it is ceremony โ€” a repo with six docs does not need an
15
+ # ownership registry, it needs six good docs.
16
+ #
17
+ # This module is the corpus's most valuable combination and no source repo has
18
+ # it: axiom-mesh wrote the best ownership model and checked none of it;
19
+ # rift-forge built the propagation gate and has no registry.
20
+
21
+ [params.registry_path]
22
+ description = "The ownership registry โ€” topic, owner, and where the topic must not appear"
23
+ default = "docs/doc-ownership.md"
24
+
25
+ [params.rules_path]
26
+ description = "Cross-cutting working rules and the surfaces that restate each one"
27
+ default = "docs/working-rules.md"
28
+
29
+ [params.scope_headers]
30
+ description = """
31
+ Require every document to open with `Authoritative for:` / `Not authoritative for:`. The root
32
+ README is always exempt โ€” it is a navigation page, and demanding a scope header there produces a
33
+ worse landing page for no gain.
34
+ """
35
+ default = true
36
+
37
+ [[gates]]
38
+ id = "docauth-ownership-respected"
39
+ kind = "declared"
40
+ engine = "term-ownership"
41
+ table = "gates/authority.toml"
42
+ tier = "fast"
43
+ why = """
44
+ The registry's third column โ€” where a topic must NOT appear โ€” is what turns "one source of truth"
45
+ from a principle into a lookup. Checked by term, which is approximate; approximate is enough,
46
+ because the failure it catches is a whole section restated in the wrong file, not a passing mention.
47
+ """
48
+
49
+ [[gates]]
50
+ id = "docauth-scope-headers"
51
+ kind = "declared"
52
+ engine = "sections"
53
+ table = "gates/authority.toml"
54
+ tier = "fast"
55
+ why = "A mandatory rule with a zero-cost check has no excuse to be manual, and this one was manual in the repo that called it non-negotiable."
56
+
57
+ [[gates]]
58
+ id = "docauth-working-rules"
59
+ kind = "declared"
60
+ engine = "rule-propagation"
61
+ table = "gates/authority.toml"
62
+ tier = "fast"
63
+ why = """
64
+ A working rule lives in more places than its authority: the authority document, the always-on
65
+ document, the skills that execute it, the tool that automates it. Fixing the authority does not
66
+ reach the rest, and a citation is not propagation โ€” "see ยง3" ages into a false claim the moment ยง3
67
+ changes, and reads as verified precisely because it names a source.
68
+ """
69
+
70
+ [[gates]]
71
+ id = "docauth-no-redirect-stubs"
72
+ kind = "declared"
73
+ engine = "file-population"
74
+ table = "gates/authority.toml"
75
+ tier = "fast"
76
+ why = "A file that exists only to say 'go elsewhere' is a bug: it will be cited, and the citation will look authoritative."
77
+
78
+ [detect]
79
+ paths = ["**/doc-ownership.md", ".ai/context/doc_ownership.md", "docs/**/working-rules.md", ".github/scripts/check-working-rules.*"]
80
+ markers = ["rungs:begin doc-authority"]
81
+
82
+ [[detect.adopt_as]]
83
+ kind = "registry-without-gates"
84
+ paths = [".ai/context/doc_ownership.md", "**/doc-ownership.md"]
85
+ note = """
86
+ A registry with no checker is the axiom-mesh case exactly, and it is the highest-value adoption in
87
+ the catalogue: the hard part โ€” deciding who owns what โ€” is already done, and what is missing is the
88
+ enforcement. Adopt the registry as authored, install only the gates, and report how many rows the
89
+ first run finds violated.
90
+ """
91
+
92
+ [provenance]
93
+ sources = ["axiom-mesh", "rift-forge"]
94
+ patterns = [
95
+ "doc-ownership-registry",
96
+ "scope-headers",
97
+ "stub-rule",
98
+ "no-paraphrase",
99
+ "no-redirect-stubs",
100
+ "working-rule-propagation",
101
+ "defect-register-split",
102
+ "inline-gap-callout",
103
+ ]
104
+ incident = """
105
+ axiom-mesh declared "no fact is ever stated in two docs" and "drift is a bug" as non-negotiable,
106
+ enforced by review only โ€” and its own 350-line instruction file had to warn four times that one of
107
+ its routing targets was a dead redirect stub. hexguard-templates forbids restating scope and its
108
+ entry document's repo map grew five duplicated entries. rift-forge measured the other half:
109
+ five repo-level rules had changed and none had reached the files that teach them, with four skills
110
+ citing as authority the very section that had reversed them.
111
+ """
@@ -0,0 +1,56 @@
1
+ **Authoritative for:** what has been noticed and not yet decided, and the disposition of everything closed.
2
+ **Not authoritative for:** what to do about any of it โ€” a finding becomes a work item before it becomes work.
3
+
4
+ # Findings
5
+
6
+ Things noticed while doing something else. **A finding is the observation; a work item is the
7
+ decision.** Recording one must cost almost nothing, or it will not happen โ€” so a finding is a
8
+ **row**, not a file. Items are files; findings are rows. The asymmetry is deliberate.
9
+
10
+ <!-- NEXT-ID: {{id_prefix}}-001 -->
11
+
12
+ ## Open
13
+
14
+ | Id | Sev | Pri | What | Evidence | When to act | How to fix |
15
+ | --- | --- | --- | --- | --- | --- | --- |
16
+ | โ€” | | | *nothing open* | | | |
17
+
18
+ ## Closed
19
+
20
+ | Id | What | Disposition | Reason |
21
+ | --- | --- | --- | --- |
22
+ | โ€” | | | |
23
+
24
+ ---
25
+
26
+ ## Recording one
27
+
28
+ Use `/record-finding`, or add a row directly. Required:
29
+
30
+ - **Sev** โ€” `high` (wrong output, data loss, security) ยท `medium` (wrong behaviour, contained) ยท
31
+ `low` (cost, clarity, tidiness)
32
+ - **Pri** โ€” `now` ยท `next` ยท `someday`. Severity is about the problem; priority is about us.
33
+ - **Evidence** โ€” a path, a command, a count. **A finding with no evidence is a hunch**, and the
34
+ next reader cannot tell the difference. If you cannot produce evidence, say so in the row.
35
+ - **When to act** โ€” the trigger, not a date. *"Before the next release"*, *"if this recurs"*.
36
+ - **How to fix** โ€” enough that someone else could, or an explicit "unknown".
37
+
38
+ ## Closing one
39
+
40
+ Every finding leaves the Open table by one of three dispositions, and **each carries a written
41
+ reason**:
42
+
43
+ | Disposition | Means | Reason must say |
44
+ | --- | --- | --- |
45
+ | **promoted** | It became a work item | Which item, and what scope it took |
46
+ | **fixed** | It was resolved directly | What changed, and where |
47
+ | **dismissed** | It is not a problem, or not one worth solving | *Why not* โ€” this is the one people skip, and it is the one that stops the same observation being recorded again next month |
48
+
49
+ `rungs check` refuses a closed finding with no reason.
50
+
51
+ ## What this register does not do
52
+
53
+ - It does not prioritise. A `now` priority is a claim by whoever typed it.
54
+ - It does not prove anything is fixed. `fixed` means someone said so.
55
+ - It counts what was **recorded**, not what was noticed. A quiet register and an unobserved repo
56
+ look identical from here.
@@ -0,0 +1,7 @@
1
+ <!-- rungs:begin findings@1.0.0 -->
2
+ ## Findings
3
+
4
+ Noticed something out of scope? Record it in [`{{path}}`]({{path}}) via **`/record-finding`** โ€”
5
+ rows, not files, because recording one must cost almost nothing. A finding is the observation; a
6
+ work item is the decision. Closing one always carries a written reason, dismissals included.
7
+ <!-- rungs:end findings -->
@@ -0,0 +1,68 @@
1
+ # Gate table for the `findings` module.
2
+
3
+ # โ”€โ”€ findings-ids โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
4
+
5
+ [kinds.finding]
6
+ format = "{{id_prefix}}-\\d{1,4}"
7
+ marker = { file = "{{path}}", pattern = "NEXT-ID: ({{id_prefix}}-\\d{1,4})" }
8
+ sources = ["{{path}}"]
9
+
10
+ [citations]
11
+ roots = ["docs", "AGENTS.md", "CLAUDE.md", ".ai"]
12
+ extensions = [".md", ".toml"]
13
+
14
+ # โ”€โ”€ findings-disposition-has-reason โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
15
+
16
+ [register_schema]
17
+ file = "{{path}}"
18
+ table = "Closed"
19
+ required_cols = ["Id", "What", "Disposition", "Reason"]
20
+ enum = { Disposition = ["promoted", "fixed", "dismissed"] }
21
+ non_empty = ["Reason"]
22
+ min_words = { Reason = 4 }
23
+ message = """
24
+ Finding {id} is closed as `{disposition}` with no reason. A dismissal without a reason is the
25
+ observation being recorded again next month by someone who cannot tell it was already considered.
26
+ """
27
+
28
+ # The Open table's evidence column is checked for presence, not for quality โ€” a gate cannot judge
29
+ # whether evidence is good, and one that tried would be the confidently-wrong probe.
30
+ [register_schema.open]
31
+ table = "Open"
32
+ non_empty = ["Sev", "Pri", "What", "Evidence"]
33
+ enum = { Sev = ["high", "medium", "low"], Pri = ["now", "next", "someday"] }
34
+ placeholder_ok = ["none โ€” hunch", "unknown"]
35
+
36
+ # โ”€โ”€ self-tests โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
37
+
38
+ [[self_test]]
39
+ gate = "findings-disposition-has-reason"
40
+ expect = "fail"
41
+ fixture = { table = "Closed", row = { Id = "{{id_prefix}}-001", What = "x", Disposition = "dismissed", Reason = "" } }
42
+
43
+ [[self_test]]
44
+ gate = "findings-disposition-has-reason"
45
+ expect = "pass"
46
+ fixture = { table = "Closed", row = { Id = "{{id_prefix}}-001", What = "x", Disposition = "dismissed", Reason = "the mapper already handles this case" } }
47
+
48
+ [[self_test]]
49
+ gate = "findings-disposition-has-reason"
50
+ expect = "fail"
51
+ fixture = { table = "Open", row = { Id = "{{id_prefix}}-002", Sev = "critical", Pri = "now", What = "x", Evidence = "y" } }
52
+
53
+ [[self_test]]
54
+ gate = "findings-disposition-has-reason"
55
+ expect = "pass"
56
+ fixture = { table = "Open", row = { Id = "{{id_prefix}}-002", Sev = "high", Pri = "now", What = "x", Evidence = "none โ€” hunch" } }
57
+
58
+ # โ”€โ”€ findings-ids self-tests โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
59
+
60
+ [[self_test]]
61
+ gate = "findings-ids"
62
+ expect = "fail"
63
+ fixture = { register_rows = ["{{id_prefix}}-001", "{{id_prefix}}-001"] }
64
+
65
+ [[self_test]]
66
+ gate = "findings-ids"
67
+ expect = "pass"
68
+ fixture = { register_rows = ["{{id_prefix}}-001", "{{id_prefix}}-002"] }
@@ -0,0 +1,100 @@
1
+ [module]
2
+ name = "findings"
3
+ version = "1.0.0"
4
+ rung = 1
5
+ summary = "A register for what is noticed before it is decided, with a triage path into work items."
6
+
7
+ [requires]
8
+ modules = ["backlog"]
9
+
10
+ [conflicts]
11
+ modules = []
12
+
13
+ # โ”€โ”€ parameters โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
14
+
15
+ [params.id_prefix]
16
+ description = "Prefix for finding ids, e.g. F-012"
17
+ default = "F"
18
+ pattern = "^[A-Z]{1,6}$"
19
+
20
+ [params.path]
21
+ description = """
22
+ Where the register lives. Defaults next to the backlog it feeds โ€” a module may reference a
23
+ declared dependency's parameters as {{<module>.<param>}}.
24
+ """
25
+ default = "docs/{{backlog.root}}/FINDINGS.md"
26
+
27
+ # โ”€โ”€ gates โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
28
+
29
+ [[gates]]
30
+ id = "findings-ids"
31
+ kind = "declared"
32
+ engine = "id-integrity"
33
+ table = "gates/findings.toml"
34
+ tier = "fast"
35
+ why = "Two findings sharing an id are one finding as far as every citation is concerned."
36
+
37
+ [[gates]]
38
+ id = "findings-disposition-has-reason"
39
+ kind = "declared"
40
+ engine = "register-schema"
41
+ table = "gates/findings.toml"
42
+ tier = "fast"
43
+ why = """
44
+ A finding closed without a written reason is indistinguishable from one nobody looked at, and it
45
+ is the same observation the next session will record again. The reason is what the register is for.
46
+ """
47
+
48
+ # โ”€โ”€ skills โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
49
+
50
+ [skills.record-finding]
51
+ # No extensions. Recording an out-of-scope observation mid-task is exactly what a
52
+ # model should reach for on its own โ€” the whole value is that it costs nothing.
53
+
54
+ # โ”€โ”€ detection (ADR-0004) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
55
+
56
+ [detect]
57
+ paths = [
58
+ "docs/**/FINDINGS.md",
59
+ "docs/**/findings/**/*.md",
60
+ "docs/governance/implementation_defects.md",
61
+ "docs/governance/design_flaws.md",
62
+ ]
63
+ markers = ["rungs:begin findings"]
64
+
65
+ # Two registers split by kind, rather than one with a `kind` column. Not a
66
+ # different paradigm โ€” the same function, more ceremony โ€” so it adopts, and
67
+ # `add` says which register it mapped to what.
68
+ [[detect.adopt_as]]
69
+ kind = "split-registers"
70
+ note = """
71
+ axiom-mesh separates AD-### (code diverges from the standard) from DF-### (the standard itself is
72
+ missing or contradictory). Different resolvers, and the split is defensible at scale. Adopt both;
73
+ do not merge them, and do not add a third.
74
+ """
75
+
76
+ [[detect.infer]]
77
+ param = "id_prefix"
78
+ # The register's own marker settles this. Counting raw ids proposed the
79
+ # *backlog's* prefix on a real repo, because a findings register cites work
80
+ # items far more often than it defines its own ids โ€” 787 to 722 in one case.
81
+ # Matches both `NEXT-ID: F-303` and the per-kind form `NEXT-F: F-303` that a
82
+ # real repo turned out to use โ€” assuming one marker spelling is how an anchor
83
+ # silently falls back to the frequency count it was written to override.
84
+ anchor = "NEXT-(?:ID|[A-Z]{1,6}):\\s*([A-Z]{1,6})-\\d"
85
+ anchor_name = "the register's NEXT-* marker"
86
+ pattern = "^\\|\\s*\\[?([A-Z]{1,6})-\\d{1,4}"
87
+ min = 10
88
+ scope = ["docs/**/FINDINGS.md", "docs/governance/*.md"]
89
+
90
+ # โ”€โ”€ provenance (required, validated) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
91
+
92
+ [provenance]
93
+ sources = ["rift-forge", "axiom-mesh"]
94
+ patterns = ["findings-log", "record-without-derailing", "finding-promotion", "audit-to-register", "inline-gap-callout"]
95
+ incident = """
96
+ hexguard ran a good audit prompt 268 times and produced 268 documents, because no register existed
97
+ to hold rows and no work-item object existed to close them into. There is still no way to say which
98
+ of those findings are open. rift-forge, which has the register, reached 91 findings precisely
99
+ because recording one costs almost nothing โ€” that cost is the design, not a side effect.
100
+ """
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: record-finding
3
+ description: >-
4
+ Record something noticed while doing other work into the findings register โ€” classified by
5
+ severity and priority, with evidence, when to act, and how to fix โ€” so it is not lost and does
6
+ not derail the current task. Use when asked to "record / log / note a finding", "capture this for
7
+ later", "don't lose this", "add that to the findings", or whenever work surfaces a real problem
8
+ that is out of scope right now. Also use to triage the register: promote a finding to a work
9
+ item, mark it fixed, or dismiss it with a reason. Executing tracked work is /work-item; changing
10
+ an instruction so a mistake cannot recur is /harden-rule.
11
+ ---
12
+
13
+ # Record a finding
14
+
15
+ **A finding is the observation; a work item is the decision.** This must cost almost nothing, or
16
+ you will keep working instead of recording โ€” which is how the observation is lost.
17
+
18
+ ## Recording
19
+
20
+ Add a row to the **Open** table in the register. Then **return to what you were doing.** Do not fix
21
+ it, do not scope it, do not open an item. Those are separate decisions made later, deliberately.
22
+
23
+ Fill every column:
24
+
25
+ - **Sev** โ€” `high` (wrong output, data loss, security) ยท `medium` (wrong behaviour, contained) ยท
26
+ `low` (cost, clarity, tidiness). Severity is about the problem.
27
+ - **Pri** โ€” `now` ยท `next` ยท `someday`. Priority is about us. They are independent: a high-severity
28
+ problem in code nobody runs can be `someday`.
29
+ - **What** โ€” one sentence, specific enough to act on without this conversation.
30
+ - **Evidence** โ€” a path, a command, a count. **If you have none, write `none โ€” hunch`.** A finding
31
+ that looks evidenced and is not is worse than one that admits it, because the next reader spends
32
+ an hour looking for the evidence.
33
+ - **When to act** โ€” a trigger, not a date. *"Before the next release"*, *"if this recurs"*.
34
+ - **How to fix** โ€” enough that someone else could, or `unknown`.
35
+
36
+ ## What is not a finding
37
+
38
+ - **Something you are about to fix** โ€” just fix it.
39
+ - **A rule that was broken** โ€” that is `/harden-rule`. Recording it and leaving the instruction
40
+ unchanged is how the same lesson gets re-learned by the next session.
41
+ - **Scope you decided against** โ€” that belongs in the item's `Out of scope`.
42
+ - **A vague unease.** If you cannot say what is wrong specifically enough for someone else to
43
+ check it, you have a feeling. Say so in the row or leave it out.
44
+
45
+ ## Triaging
46
+
47
+ Every finding leaves **Open** by exactly one disposition, each with a written reason:
48
+
49
+ | Disposition | Do |
50
+ | --- | --- |
51
+ | **promoted** | Open a work item, link both ways, move the row to Closed naming the item and the scope it took |
52
+ | **fixed** | Resolve it, move the row naming what changed and where |
53
+ | **dismissed** | Move the row saying **why not** |
54
+
55
+ **Dismissal is the one that matters.** It is the disposition people skip, and its reason is what
56
+ stops the same observation being recorded again next month by someone who cannot tell it was
57
+ already considered. `rungs check` refuses a closed finding with no reason.
58
+
59
+ ## Before triaging, re-check
60
+
61
+ A finding has a shelf life. Re-derive it against the repo as it is now โ€” a register row is a claim
62
+ about the past, and work has landed since. Findings that dissolve on contact get dismissed with
63
+ "already resolved by โ€ฆ", which is a perfectly good reason and a useful record.
@@ -0,0 +1,49 @@
1
+ # Gate registry.
2
+ #
3
+ # Every gate this repo runs is declared here, and `rungs check` runs them. Modules add their own
4
+ # entries on install โ€” those live inside marked blocks and are regenerated; anything outside a
5
+ # block is yours and is never touched.
6
+
7
+ [runner]
8
+ tiers = {{tiers}}
9
+ ledger = {{ledger}} # .ai/.gate-ledger.jsonl โ€” gitignored, local, no network
10
+ fast_budget_ms = {{fast_budget_ms}}
11
+
12
+ # โ”€โ”€ your gates โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
13
+ #
14
+ # A `command` gate is any shell string. The runner records its exit status and wall-clock the same
15
+ # way it records a declared gate's, whatever language it is written in โ€” which is how an existing
16
+ # validator gets the ledger without being rewritten.
17
+ #
18
+ # [[gates]]
19
+ # id = "build"
20
+ # kind = "command"
21
+ # tier = "full"
22
+ # command = "dotnet build MySolution.slnx -c Release"
23
+ # why = "One sentence. `doctor` quotes this back when the gate has never fired."
24
+ #
25
+ # [[gates]]
26
+ # id = "doc-links"
27
+ # kind = "command"
28
+ # tier = "fast"
29
+ # command = "pwsh ./scripts/validate-doc-links.ps1"
30
+ # why = "Adopted 2026-01-01; predates rungs and is owned by this repo."
31
+
32
+ # โ”€โ”€ module gates โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
33
+ # Generated. Edit the owning module's parameters, not these entries.
34
+
35
+ # rungs:begin gates
36
+ # rungs:end gates
37
+
38
+ # โ”€โ”€ notes on writing a gate โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
39
+ #
40
+ # 1. A gate exits 0 or 1 and prints why. It does not write to the ledger โ€” the runner does.
41
+ # A gate that records its own runs can lie about them.
42
+ # 2. Every declared gate needs self-tests asserting BOTH directions. A gate whose rules are
43
+ # currently satisfied looks exactly like a gate that matches nothing.
44
+ # 3. An exemption marker is ignored unless it states a reason. An escape hatch nobody has to
45
+ # justify is not an escape hatch, it is an off switch.
46
+ # 4. Match against a preceding-context negation window before refusing a phrase, or the gate will
47
+ # refuse the sentence that documents the fix. A guard that refuses its own fix gets disabled.
48
+ # 5. Prefer a gate that recomputes a number over one that compares a typed number. A probe
49
+ # encoding a guess is a gate that is confidently wrong, which is worse than the typed value.
@@ -0,0 +1,8 @@
1
+ <!-- rungs:begin gates@1.0.0 -->
2
+ ## Gates
3
+
4
+ `rungs check` runs everything in [`.ai/gates.toml`](.ai/gates.toml) โ€” fast tier constantly, full
5
+ tier at a boundary. **Never weaken a gate to make a change pass**; if a gate is wrong that is its
6
+ own work. Every rule you add declares `gated` or `review-only` โ€” there is no third option. Broke a
7
+ rule that already existed? Do not restate it, make it mechanical: **`/harden-rule`**.
8
+ <!-- rungs:end gates -->
@@ -0,0 +1,4 @@
1
+ # rungs:begin gates@1.0.0
2
+ # Gate run ledger โ€” local, machine-specific, never transmitted (ADR-0005).
3
+ .ai/.gate-ledger.jsonl
4
+ # rungs:end gates