@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,48 @@
1
+ # Gate table for the `audit` module.
2
+
3
+ # ── audit-output-is-rows ──────────────────────────────────────────────────────
4
+ #
5
+ # Refuses a per-subject audit document tree from re-forming. Deliberately does not fire on a small
6
+ # number: one or two written-up assessments are a legitimate thing to have. It fires on the shape
7
+ # that scales — a document per subject, accumulating.
8
+
9
+ [file_population]
10
+ scan = ["docs/**/audits/**/*.md", "docs/**/*-audit-*.md", "docs/**/*-readiness-*.md"]
11
+ exclude = ["docs/**/archive/**"]
12
+ warn_at = 6
13
+ fail_at = 15
14
+ exempt_marker = "audit-doc-ok:" # ignored unless it states a reason
15
+ message = """
16
+ {count} per-subject audit documents. This is the shape that reached 268 files in one repo, where
17
+ there is still no way to say which findings are open — the prompt was good enough to run 268
18
+ times, the output form was the defect. Audit output belongs in the findings register as rows.
19
+ Existing documents are history and can stay; mark them with `<!-- audit-doc-ok: <reason> -->` or
20
+ move them to an archive.
21
+ """
22
+
23
+ # ── self-tests ────────────────────────────────────────────────────────────────
24
+
25
+ [[self_test]]
26
+ gate = "audit-output-is-rows"
27
+ expect = "pass"
28
+ fixture = { matching_files = 3 }
29
+
30
+ [[self_test]]
31
+ gate = "audit-output-is-rows"
32
+ expect = "fail"
33
+ fixture = { matching_files = 40 }
34
+
35
+ [[self_test]]
36
+ gate = "audit-output-is-rows"
37
+ expect = "pass"
38
+ fixture = { matching_files = 40, location = "docs/archive/audits/" }
39
+
40
+ [[self_test]]
41
+ gate = "audit-output-is-rows"
42
+ expect = "pass"
43
+ fixture = { matching_files = 40, exempt = "audit-doc-ok: regulatory evidence, retained per policy" }
44
+
45
+ [[self_test]]
46
+ gate = "audit-output-is-rows"
47
+ expect = "fail"
48
+ fixture = { matching_files = 40, exempt = "audit-doc-ok:" }
@@ -0,0 +1,72 @@
1
+ [module]
2
+ name = "audit"
3
+ version = "1.0.0"
4
+ rung = 2
5
+ summary = "A repeatable assessment whose output is rows in the findings register, never a document per subject."
6
+
7
+ [requires]
8
+ modules = ["findings"]
9
+
10
+ [conflicts]
11
+ modules = []
12
+
13
+ # The dependency is the whole point, and it is declared because a repo violated
14
+ # it: an audit with nowhere to land produces unactioned prose.
15
+
16
+ [params.subject]
17
+ description = "What gets audited, one at a time — package, service, surface, endpoint…"
18
+ default = "package"
19
+
20
+ [params.criteria_path]
21
+ description = """
22
+ The criteria list, as a file the repo edits rather than a parameter. A list substituted into a
23
+ skill body is a list nobody maintains, and criteria are the part that changes most.
24
+ """
25
+ default = "docs/audit-criteria.md"
26
+
27
+ [[gates]]
28
+ id = "audit-output-is-rows"
29
+ kind = "declared"
30
+ engine = "file-population"
31
+ table = "gates/audit.toml"
32
+ tier = "fast"
33
+ why = """
34
+ hexguard produced 268 per-subject audit documents and there is still no way to say which findings
35
+ are open. The audit prompt was good enough to run 268 times; the output form was the defect. This
36
+ gate refuses the document tree from re-forming.
37
+ """
38
+
39
+ [skills.assess]
40
+ # No extensions. Read-only, and useful for a model to reach for when it notices
41
+ # a subject has never been assessed.
42
+
43
+ [detect]
44
+ # A dedicated audits directory only. The first draft also matched
45
+ # `docs/**/*-audit-*.md` and `*-readiness-*` anywhere, and on a real repo it hit
46
+ # `WI-108-lol-mechanics-fidelity-audit-epic.md` and a launch-readiness epic —
47
+ # work items, not audit documents. Under-detection is the correct bias: a repo
48
+ # with scattered audit files gets a second directory it can see, while a false
49
+ # positive would have the CLI believe it owns somebody's backlog.
50
+ paths = ["docs/**/audits/**/*.md", "docs/**/audit/**/*.md"]
51
+ markers = ["rungs:begin audit"]
52
+
53
+ [[detect.adopt_as]]
54
+ kind = "audit-documents"
55
+ note = """
56
+ An existing per-subject audit tree is the failure this module exists to prevent, already present.
57
+ Adopt means: leave the documents alone as history, register the criteria, and report how many
58
+ subjects have documents with no corresponding open finding — which is the number that says how much
59
+ of that work is currently actionable. Never bulk-convert; that would fabricate findings nobody
60
+ verified.
61
+ """
62
+
63
+ [provenance]
64
+ sources = ["hexguard", "axiom-mesh"]
65
+ patterns = ["audit-to-register", "lifecycle-verbs"]
66
+ incident = """
67
+ hexguard ran /assess-package-readiness across its catalogue twice, producing 268 dated documents
68
+ plus a global audit, a triage doc and a methodology doc — and no aggregate state. To know the
69
+ current condition of the catalogue you read 268 files. There was also no work-item object for a
70
+ finding to become, so the assessments could not close. axiom-mesh got this right by logging every
71
+ confirmed defect as a stable AD-### row before any remediation was deferred.
72
+ """
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: assess
3
+ description: >-
4
+ Assess one subject — a package, service, surface or endpoint — against this repo's audit criteria,
5
+ and record each failure as a row in the findings register. Use when asked to "audit", "assess",
6
+ "review the readiness of", "check whether X is production-ready", or before promoting, releasing
7
+ or publicising something. Also use to work through a backlog of unassessed subjects, one at a
8
+ time. It records observations; it does not fix them and does not open work items — that is
9
+ /record-finding's triage and /work-item.
10
+ ---
11
+
12
+ # Assess a subject
13
+
14
+ Read the criteria file first. It is repo-owned and changes; the version in your memory does not
15
+ count.
16
+
17
+ ## 1. One subject at a time
18
+
19
+ Assessing several at once produces a comparison, which is a different and less useful artifact.
20
+ Name the subject explicitly before starting, and its boundary — what is in it and what is merely
21
+ adjacent.
22
+
23
+ ## 2. Check each criterion against the artifact
24
+
25
+ **Read the thing, not the documentation about the thing.** A README claiming the errors are typed
26
+ is a claim; the code is the fact. This is the failure that makes an audit worthless: an assessment
27
+ built from the subject's own self-description confirms it.
28
+
29
+ For each criterion, reach one of three verdicts:
30
+
31
+ - **passes** — record nothing
32
+ - **fails** — one row in the findings register
33
+ - **cannot tell** — say so, and say what would settle it. A "cannot tell" recorded honestly is
34
+ more useful than a pass you did not earn
35
+
36
+ ## 3. Write rows, not a document
37
+
38
+ Each failure is one row: severity from the criteria table, what specifically fails, **evidence**
39
+ (a path, a symbol, a command), when to act, how to fix.
40
+
41
+ **Do not produce an audit document.** One repo in the corpus generated 268 of them and still cannot
42
+ say which findings are open — the prompt was fine, the output form was the defect. `rungs check`
43
+ refuses the document tree from re-forming.
44
+
45
+ ## 4. Report the run itself
46
+
47
+ Say how many criteria were checked, how many subjects, and **how many files you actually read**.
48
+
49
+ An audit that found nothing and an audit that read nothing produce the same output, and only one of
50
+ them is good news. A report that cannot fail loudly is not a measurement.
51
+
52
+ ## 5. Stop
53
+
54
+ Do not fix what you found. Do not open work items. Do not rank. Those are decisions made against
55
+ the register later, deliberately, by someone weighing them against everything else — which is the
56
+ whole reason the register exists.
57
+
58
+ ## What this cannot tell you
59
+
60
+ - **That a subject is good.** It passed the criteria that were written down.
61
+ - **That the criteria are right.** They are this repo's opinion, in a file you can edit — and a
62
+ criterion nothing has ever failed is worth suspecting.
@@ -0,0 +1,52 @@
1
+ **Authoritative for:** which items exist and what state each is in, and the next free id.
2
+ **Not authoritative for:** why any item exists or how to do it — that is the item file.
3
+
4
+ # Backlog
5
+
6
+ The board. One row per live work item, grouped by status. Items live in
7
+ [`items/`](items/); finished work moves to [`archive/`](archive/).
8
+
9
+ <!-- NEXT-ID: {{id_prefix}}-001 -->
10
+ <!-- Claim from this marker and bump it on your own branch. `rungs check` refuses a duplicate. -->
11
+
12
+ ## In progress
13
+
14
+ | Id | Title | Type | Branch |
15
+ | --- | --- | --- | --- |
16
+ | — | *nothing in flight* | | |
17
+
18
+ ## Review
19
+
20
+ | Id | Title | Type | Branch |
21
+ | --- | --- | --- | --- |
22
+ | — | | | |
23
+
24
+ ## Planned
25
+
26
+ | Id | Title | Type |
27
+ | --- | --- | --- |
28
+ | — | | |
29
+
30
+ ## Accepted
31
+
32
+ | Id | Title | Type |
33
+ | --- | --- | --- |
34
+ | — | | |
35
+
36
+ ## Proposed
37
+
38
+ | Id | Title | Type |
39
+ | --- | --- | --- |
40
+ | — | | |
41
+
42
+ ## Deferred
43
+
44
+ | Id | Title | Revisit when |
45
+ | --- | --- | --- |
46
+ | — | | |
47
+
48
+ ---
49
+
50
+ Rejected and completed items keep their files — see [`archive/`](archive/). A rejection is part of
51
+ the record, with its reason; deleting one loses the answer to "why not?" and invites the same
52
+ proposal again.
@@ -0,0 +1,110 @@
1
+ **Authoritative for:** how work is proposed, decided, planned, executed and reviewed, and the meaning of each status.
2
+ **Not authoritative for:** what any individual item contains, and what the code should look like — see the engineering guides.
3
+
4
+ # Delivery methodology & backlog
5
+
6
+ How work is proposed, decided, planned, executed, and reviewed here — and where it is tracked.
7
+ **Mandatory for all contributors, including AI agents.**
8
+
9
+ This tracks **units of work and their state**. It does not describe behaviour (that is a spec) or
10
+ record decisions (that is an ADR).
11
+
12
+ ---
13
+
14
+ ## 1. Lifecycle
15
+
16
+ Every unit of work is a **work item** with a stable id (`{{id_prefix}}-###`) and a status:
17
+
18
+ ```mermaid
19
+ stateDiagram-v2
20
+ [*] --> proposed
21
+ proposed --> accepted: decision
22
+ proposed --> rejected: decision
23
+ accepted --> planned: requirements + impacts
24
+ accepted --> deferred: not now
25
+ deferred --> planned: revisited
26
+ planned --> in_progress: branch + build
27
+ in_progress --> review: executed + tested
28
+ review --> planned: changes requested
29
+ review --> done: approved + merged
30
+ done --> [*]
31
+ rejected --> [*]
32
+ ```
33
+
34
+ | Status | Meaning |
35
+ | --- | --- |
36
+ | `proposed` | An idea captured with **rationale**; awaiting a decision |
37
+ | `accepted` | Approved to pursue; not yet planned in detail |
38
+ | `rejected` | Declined; kept for the record **with the reason** |
39
+ | `deferred` | Accepted in principle but intentionally **not now**, with a revisit trigger |
40
+ | `planned` | Requirements, impacts, approach and acceptance criteria written; ready to build |
41
+ | `in_progress` | Being executed on its branch — **the "current" work** |
42
+ | `review` | Executed and tested; under review against the acceptance criteria |
43
+ | `done` | Approved, merged to `{{integration_branch}}`, acceptance criteria met |
44
+
45
+ ## 2. Workflow
46
+
47
+ 1. **Propose** — open an item (`proposed`) stating the problem and **why it matters**. Anyone may
48
+ propose.
49
+ 2. **Decide** — accept, reject, or defer. Record the decision **and its reason** in the item. A
50
+ rejection is final unless re-proposed with new information.
51
+ 3. **Plan** — for an `accepted` item write **requirements**, **impacts**, **approach** (with
52
+ options and trade-offs where relevant), **acceptance criteria**, and **out of scope**.
53
+ Significant design choices get an ADR. Status → `planned`.
54
+ 4. **Execute + test** — branch, build, test. Status → `in_progress`, then `review`.
55
+ 5. **Review** — verify against the acceptance criteria, reconcile any specs or ADRs, merge.
56
+ Status → `done`. Changes requested → back to `planned` / `in_progress`.
57
+
58
+ **Planning artifacts ride `{{integration_branch}}`.** Proposals, decisions and plans precede
59
+ execution and do not need a branch; only *code* does.
60
+
61
+ ## 3. Ids
62
+
63
+ Ids are permanent and never reused, including after archiving. `BACKLOG.md` carries the
64
+ `NEXT-ID` marker; claim from it and bump it **on your own branch**.
65
+
66
+ An id spent in a branch name or a commit subject without bumping the marker is invisible to the
67
+ check, which reads the working tree. Bump first.
68
+
69
+ ## 4. Branches
70
+
71
+ `{{branch_prefix}}/{{id_prefix}}-###-slug`, cut from `{{integration_branch}}` and merged back on
72
+ review. Use `chore/`, `docs/` and `spike/` for non-feature work. **Delete the branch on merge.**
73
+
74
+ ## 5. Definition of done
75
+
76
+ - Acceptance criteria met and demonstrated
77
+ - Tests written and passing; existing tests not weakened
78
+ - Docs updated where public behaviour or developer workflow changed
79
+ - Status is `done` **and the branch is merged** — the two must agree
80
+
81
+ ## 6. Scope discipline
82
+
83
+ **Never scope-creep an item.** Work discovered mid-flight becomes a *new* item, or a finding if it
84
+ is only an observation. An item that grew a second purpose should have been two.
85
+
86
+ ## 7. Epics and sub-items
87
+
88
+ An epic (`type: epic`) has no branch of its own. Membership is a **two-way link**: the epic lists
89
+ `children`, each child names its `epic`. Both directions or neither.
90
+
91
+ ## 8. Archiving
92
+
93
+ `items/` should hold only work that can still change. Move finished items to `archive/` with
94
+ `rungs backlog archive` — it recomputes every link repo-wide, so archived ids still resolve and
95
+ stay permanently spent.
96
+
97
+ **Never edit an archived item.** If archived work turns out to be wrong, that is a *new* item.
98
+
99
+ ## 9. What keeps this true
100
+
101
+ Three gates, run by `rungs check`:
102
+
103
+ | Gate | Refuses |
104
+ | --- | --- |
105
+ | `backlog-ids` | A duplicate id, or a citation to an id that does not exist |
106
+ | `backlog-stale-blocker` | A document claiming it is blocked on work that has finished — it reads as a live constraint and the next session plans around a wall that came down months ago |
107
+ | `backlog-merged-status` | An item whose branch is merged but whose status is still pre-review. One-directional: a merged branch with a pre-review status is always wrong; an unmerged one is not. A genuinely phased item says why with `branch-merged-ok: <reason>` |
108
+
109
+ The `status` field is typed by a person and "did it land" is a fact in git. When they disagree the
110
+ typed field is what every board and plan reads, so the gate exists to keep it honest.
@@ -0,0 +1,56 @@
1
+ ---
2
+ id: {{id_prefix}}-000
3
+ title: Short imperative title
4
+ type: feature # feature | chore | docs | spike | epic
5
+ status: proposed # proposed | accepted | rejected | deferred | planned | in_progress | review | done
6
+ branch: # {{branch_prefix}}/{{id_prefix}}-000-slug — set when execution starts; epics have none
7
+ created: YYYY-MM-DD
8
+ updated: YYYY-MM-DD
9
+ related: [] # {{id_prefix}}-###, ADR-####, spec ids
10
+ epic: # parent {{id_prefix}}-### — sub-items only
11
+ children: [] # child {{id_prefix}}-### — epics only
12
+ ---
13
+
14
+ ## Proposal (rationale)
15
+
16
+ The problem, and why it matters. Expected value, or the cost of not doing it.
17
+
18
+ ## Decision
19
+
20
+ `accepted` | `rejected` | `deferred` — date, who, and the reason. Empty until decided.
21
+
22
+ ## Plan
23
+
24
+ > Filled once `accepted`. Significant design choices get an ADR — link it.
25
+
26
+ ### Requirements
27
+
28
+ - Explicit and testable.
29
+
30
+ ### Impacts
31
+
32
+ - Specs and ADRs touched, code areas, data and contracts, migration or ops, risks.
33
+
34
+ ### Approach
35
+
36
+ - The chosen approach; options considered and their trade-offs; decisions still open.
37
+
38
+ ### Acceptance criteria / tests
39
+
40
+ - How we will know it is done and correct.
41
+
42
+ ### Out of scope
43
+
44
+ - **Required — answer, do not delete.** What this item explicitly does *not* cover, plus the
45
+ follow-up item id if there is one, or an explicit `nothing deferred`.
46
+ >
47
+ > A blank line here is an unfinished plan, exactly like blank acceptance criteria. This section is
48
+ > the one that prevents an item quietly growing a second purpose.
49
+
50
+ ## Execution
51
+
52
+ Branch, key commits, notes, and any deviation from the plan **with its reason**.
53
+
54
+ ## Review
55
+
56
+ Verification against each acceptance criterion. What was checked, and how.
@@ -0,0 +1,13 @@
1
+ **Authoritative for:** how finished items are archived and why they are never edited afterwards.
2
+ **Not authoritative for:** the content of any archived item, which is fixed at the moment it was archived.
3
+
4
+ # Archive
5
+
6
+ Finished work items, moved here so [`../items/`](../items/README.md) holds only work that can still
7
+ change.
8
+
9
+ Move them with `rungs backlog archive`, never by hand — it recomputes every link repo-wide, and
10
+ archived ids stay indexed so citations to them keep resolving and the ids stay permanently spent.
11
+
12
+ **Never edit an archived item.** If archived work turns out to be wrong, that is a new item. The
13
+ value of an archived record is that it says what was decided at the time.
@@ -0,0 +1,13 @@
1
+ **Authoritative for:** what lives in this directory and when an item leaves it.
2
+ **Not authoritative for:** the lifecycle itself, which is the methodology one level up.
3
+
4
+ # Items
5
+
6
+ One file per work item: `{{id_prefix}}-###-slug.md`, from
7
+ [`../TEMPLATE.md`](../TEMPLATE.md).
8
+
9
+ This directory holds **only work that can still change.** Finished items move to
10
+ [`../archive/`](../archive/) with `rungs backlog archive`, which recomputes every link repo-wide so
11
+ archived ids still resolve and stay permanently spent.
12
+
13
+ Never edit an archived item. If archived work turns out to be wrong, that is a new item.
@@ -0,0 +1,9 @@
1
+ <!-- rungs:begin backlog@1.0.0 -->
2
+ ## Work tracking
3
+
4
+ Non-trivial work is a **work item** (`{{id_prefix}}-###`) under
5
+ [`docs/{{root}}/`](docs/{{root}}/README.md) — that file is the methodology and is mandatory reading
6
+ before your first item. Branch `{{branch_prefix}}/{{id_prefix}}-###-slug` off
7
+ `{{integration_branch}}`; claim ids from `NEXT-ID` on your own branch; never scope-creep an item.
8
+ Status must agree with git, and `rungs check` enforces it. Execute one with **`/work-item`**.
9
+ <!-- rungs:end backlog -->
@@ -0,0 +1,106 @@
1
+ # Gate table for the `backlog` module.
2
+ #
3
+ # Consumed by two engines the CLI provides — `id-integrity` and `git-status-reconcile`. No script
4
+ # is written into the repo (ADR-0002). Everything here is data; anything that would need an
5
+ # expression belongs in a `command` gate instead (ADR-0003 revisit trigger 3).
6
+
7
+ # ── backlog-ids ───────────────────────────────────────────────────────────────
8
+
9
+ [kinds.item]
10
+ format = "{{id_prefix}}-\\d{1,4}"
11
+ marker = { file = "docs/{{root}}/BACKLOG.md", pattern = "NEXT-ID: ({{id_prefix}}-\\d{1,4})" }
12
+ sources = ["docs/{{root}}/items/**/*.md", "docs/{{root}}/archive/**/*.md"]
13
+ # The archive is indexed so archived ids still resolve and stay permanently spent.
14
+
15
+ [citations]
16
+ # Where a reference to an id may appear, and must resolve.
17
+ roots = ["docs", "AGENTS.md", "CLAUDE.md", ".ai"]
18
+ extensions = [".md", ".toml"]
19
+
20
+ # ── backlog-stale-blocker ─────────────────────────────────────────────────────
21
+ #
22
+ # Refuses a document claiming it waits on work that has finished.
23
+ #
24
+ # The vocabulary is deliberately narrow, and that was measured: rift-forge's first draft matched
25
+ # `until {{id_prefix}}-###` and hit 29 lines that were almost all true history in the repo's own
26
+ # voice. `until` and `once … lands` were dropped, and past tense is a record rather than a claim.
27
+
28
+ [stale_blocker]
29
+ phrases = [
30
+ "blocked on",
31
+ "blocked by",
32
+ "waiting on",
33
+ "waiting for",
34
+ "pending",
35
+ ]
36
+ # Checked against a preceding-context window, so a sentence explaining a retired claim is not
37
+ # itself refused — a guard that also refuses its own fix is one people disable.
38
+ negation_window = 60
39
+ past_tense_ok = ["was blocked on", "were blocked on", "had been blocked on", "used to be blocked"]
40
+ exempt_marker = "owner-ok:" # ignored unless it states a reason
41
+ scope_exclude = ["docs/{{root}}/archive/**"]
42
+
43
+ # ── backlog-merged-status ─────────────────────────────────────────────────────
44
+ #
45
+ # One-directional by design: a merged branch with a pre-review status is always wrong; an unmerged
46
+ # branch at any status is not. The gate can only see items whose branch still exists, so deleting a
47
+ # branch immediately after merge costs you the check — which is why the methodology says to delete
48
+ # it *on* merge, after the status moves.
49
+
50
+ [merged_status]
51
+ branch_field = "branch"
52
+ status_field = "status"
53
+ pre_review_statuses = ["proposed", "accepted", "planned", "in_progress"]
54
+ integration_branch = "{{integration_branch}}"
55
+ exempt_marker = "branch-merged-ok:" # ignored unless it states a reason
56
+
57
+ # ── self-test ─────────────────────────────────────────────────────────────────
58
+ #
59
+ # A gate whose rules are all currently satisfied is indistinguishable from a gate that matches
60
+ # nothing, so each engine runs these fixtures and asserts BOTH directions.
61
+
62
+ [[self_test]]
63
+ gate = "backlog-stale-blocker"
64
+ expect = "fail"
65
+ input = "The mapper is blocked on {{id_prefix}}-001, which is done."
66
+
67
+ [[self_test]]
68
+ gate = "backlog-stale-blocker"
69
+ expect = "pass"
70
+ input = "The mapper was blocked on {{id_prefix}}-001 until it landed."
71
+
72
+ [[self_test]]
73
+ gate = "backlog-stale-blocker"
74
+ expect = "pass"
75
+ input = "Do not write that a doc is blocked on finished work. <!-- owner-ok: quotes the retired rule to explain it -->"
76
+
77
+ [[self_test]]
78
+ gate = "backlog-merged-status"
79
+ expect = "fail"
80
+ input = "status: in_progress, branch merged into {{integration_branch}}"
81
+
82
+ [[self_test]]
83
+ gate = "backlog-merged-status"
84
+ expect = "pass"
85
+ input = "status: in_progress, branch not merged"
86
+
87
+ # ── backlog-ids self-tests ────────────────────────────────────────────────────
88
+ #
89
+ # The marker case is the one worth pinning: a NEXT-ID pointing at an id that is
90
+ # already spent hands the next session a collision, and the archive is indexed
91
+ # precisely so an archived id still counts as taken.
92
+
93
+ [[self_test]]
94
+ gate = "backlog-ids"
95
+ expect = "fail"
96
+ fixture = { items = ["{{id_prefix}}-001-a.md", "{{id_prefix}}-001-b.md"] }
97
+
98
+ [[self_test]]
99
+ gate = "backlog-ids"
100
+ expect = "pass"
101
+ fixture = { items = ["{{id_prefix}}-001-a.md", "{{id_prefix}}-002-b.md"] }
102
+
103
+ [[self_test]]
104
+ gate = "backlog-ids"
105
+ expect = "fail"
106
+ fixture = { items = ["{{id_prefix}}-001-a.md"], marker = "{{id_prefix}}-001" }