@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 `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 -->
|