@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,150 @@
|
|
|
1
|
+
[module]
|
|
2
|
+
name = "backlog"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
rung = 1
|
|
5
|
+
summary = "Work items with stable ids, an eight-status lifecycle, a board, and the three gates that keep the bookkeeping honest."
|
|
6
|
+
|
|
7
|
+
[requires]
|
|
8
|
+
modules = ["instructions"]
|
|
9
|
+
|
|
10
|
+
[conflicts]
|
|
11
|
+
modules = []
|
|
12
|
+
|
|
13
|
+
# ── parameters ────────────────────────────────────────────────────────────────
|
|
14
|
+
# Only things that substitute cleanly into prose and paths are parameters.
|
|
15
|
+
# The eight-status lifecycle is deliberately NOT one: a status set cannot be
|
|
16
|
+
# substituted into a table, a state diagram and a gate without a template
|
|
17
|
+
# language, and ADR-0003 chose not to have one. A repo that wants different
|
|
18
|
+
# statuses edits the file, and `add` records the divergence (ADR-0004 state 3).
|
|
19
|
+
|
|
20
|
+
[params.id_prefix]
|
|
21
|
+
description = "Prefix for work-item ids, e.g. WI-042"
|
|
22
|
+
default = "WI"
|
|
23
|
+
pattern = "^[A-Z]{1,6}$"
|
|
24
|
+
|
|
25
|
+
[params.root]
|
|
26
|
+
description = "Directory under docs/ that holds the backlog"
|
|
27
|
+
default = "backlog"
|
|
28
|
+
|
|
29
|
+
[params.integration_branch]
|
|
30
|
+
description = "Branch that item branches are cut from and merged back into"
|
|
31
|
+
default = "main"
|
|
32
|
+
|
|
33
|
+
[params.branch_prefix]
|
|
34
|
+
description = "Prefix for a feature item's branch"
|
|
35
|
+
default = "feature"
|
|
36
|
+
|
|
37
|
+
# ── gates ─────────────────────────────────────────────────────────────────────
|
|
38
|
+
# All three are `declared`: a CLI engine plus the table below. No script is
|
|
39
|
+
# written into the repo (ADR-0002).
|
|
40
|
+
|
|
41
|
+
[[gates]]
|
|
42
|
+
id = "backlog-ids"
|
|
43
|
+
kind = "declared"
|
|
44
|
+
engine = "id-integrity"
|
|
45
|
+
table = "gates/ids.toml"
|
|
46
|
+
tier = "fast"
|
|
47
|
+
why = "An id claimed twice is two items the board shows as one."
|
|
48
|
+
|
|
49
|
+
[[gates]]
|
|
50
|
+
id = "backlog-stale-blocker"
|
|
51
|
+
kind = "declared"
|
|
52
|
+
engine = "id-integrity"
|
|
53
|
+
table = "gates/ids.toml"
|
|
54
|
+
tier = "fast"
|
|
55
|
+
why = """
|
|
56
|
+
A document saying it is blocked on finished work reads as a live constraint, and the next session
|
|
57
|
+
plans around a wall that came down months ago. rift-forge measured 95% of 474 rows naming a
|
|
58
|
+
finished item as next owner, then found those routes quoted in a code comment as evidence.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
[[gates]]
|
|
62
|
+
id = "backlog-merged-status"
|
|
63
|
+
kind = "declared"
|
|
64
|
+
engine = "git-status-reconcile"
|
|
65
|
+
table = "gates/ids.toml"
|
|
66
|
+
tier = "fast"
|
|
67
|
+
why = """
|
|
68
|
+
An item whose branch is merged cannot still be on its way to review. Measured 2026-08-13 in
|
|
69
|
+
rift-forge: 37 items sat at `review` with their code already landed, and one sat `in_progress`
|
|
70
|
+
for eight days after its own merge.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
# ── skills ────────────────────────────────────────────────────────────────────
|
|
74
|
+
# SKILL.md files stay restricted to the six Agent Skills spec fields (ADR-0001).
|
|
75
|
+
# Claude Code extensions are opted into here, per skill, with the cost stated.
|
|
76
|
+
|
|
77
|
+
[skills.work-item]
|
|
78
|
+
extensions = { disable-model-invocation = true }
|
|
79
|
+
extension_note = """
|
|
80
|
+
`work-item` creates branches and merges. A model should not decide on its own that now is the
|
|
81
|
+
moment. Opting in means this skill no longer packages for claude.ai uploads or the Skills API,
|
|
82
|
+
which reject non-spec keys with a hard error; it remains portable to every skills-compatible CLI.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
[skills.backlog-summary]
|
|
86
|
+
# No extensions. Read-only, and useful for a model to reach for unprompted.
|
|
87
|
+
|
|
88
|
+
# ── detection (ADR-0004) ──────────────────────────────────────────────────────
|
|
89
|
+
# Presence is decided by `paths` and `markers` only. Biased toward false
|
|
90
|
+
# negatives: when uncertain, fail to match.
|
|
91
|
+
|
|
92
|
+
[detect]
|
|
93
|
+
paths = [
|
|
94
|
+
"docs/*/items/**/*.md",
|
|
95
|
+
"docs/*/BACKLOG.md",
|
|
96
|
+
"docs/*/TEMPLATE.md",
|
|
97
|
+
"docs/.ai/backlog/**/*.md",
|
|
98
|
+
]
|
|
99
|
+
markers = ["rungs:begin backlog"]
|
|
100
|
+
|
|
101
|
+
# A different system serving the same function. Never auto-adopted; `add` prints
|
|
102
|
+
# the comparison and stops (ADR-0004 state 5).
|
|
103
|
+
[[detect.paradigm]]
|
|
104
|
+
id = "milestones"
|
|
105
|
+
paths = ["docs/**/milestones/index.md", ".ai/context/session.md"]
|
|
106
|
+
compare = "docs/research/synthesis.md#33-unit-of-work"
|
|
107
|
+
note = """
|
|
108
|
+
Milestones (`M##-T#`) suit long sequenced programmes; work items suit continuous flow. They can
|
|
109
|
+
coexist — a milestone overlay sits on top of items. Three real options: adopt milestones as the
|
|
110
|
+
unit and install only the missing gates, install items alongside, or skip.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
# Proposes a parameter once presence is already established. Never concludes
|
|
114
|
+
# presence — hexguard-templates has 207 well-formed `FOUND-US-###` matches and
|
|
115
|
+
# no backlog; they are spec story ids.
|
|
116
|
+
[[detect.infer]]
|
|
117
|
+
param = "id_prefix"
|
|
118
|
+
# The lookbehind is load-bearing and was added after a real false positive:
|
|
119
|
+
# `\b([A-Z]{1,6})-\d` matches `US-110` inside `FOUND-US-110`, because `-` to `U`
|
|
120
|
+
# is a word boundary. On hexguard-templates that proposed `US` — a spec story
|
|
121
|
+
# id — as the backlog prefix, which is precisely the failure ADR-0004 was
|
|
122
|
+
# written about, arriving through the inference path instead of the presence one.
|
|
123
|
+
pattern = "(?<![A-Za-z0-9-])([A-Z]{1,6})-\\d{1,4}\\b"
|
|
124
|
+
min = 20
|
|
125
|
+
scope = ["docs/**/*.md"]
|
|
126
|
+
exclude = ["docs/specs/**", "docs/decisions/**", "docs/adr/**"]
|
|
127
|
+
# Encodings, hashes and standards share the id shape. On hexguard the top
|
|
128
|
+
# proposal was `UTF` at 32 matches, from `UTF-8`.
|
|
129
|
+
exclude_values = ["UTF", "SHA", "ISO", "RFC", "MD", "AES", "RSA", "HTTP", "IPV", "X"]
|
|
130
|
+
|
|
131
|
+
# ── provenance (required, validated) ──────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
[provenance]
|
|
134
|
+
sources = ["rift-forge", "axiom-mesh"]
|
|
135
|
+
patterns = [
|
|
136
|
+
"work-item-lifecycle",
|
|
137
|
+
"item-template-required-fields",
|
|
138
|
+
"branch-per-item",
|
|
139
|
+
"planning-rides-trunk",
|
|
140
|
+
"scope-discipline",
|
|
141
|
+
"epics-and-subitems",
|
|
142
|
+
"bookkeeping-gates",
|
|
143
|
+
]
|
|
144
|
+
incident = """
|
|
145
|
+
Two of the four source repos had no unit of work at all, and both show the same consequence:
|
|
146
|
+
hexguard produced 268 audit reports that could not become anything, because no object existed for
|
|
147
|
+
a finding to turn into; hexguard-templates cannot say what is in flight. rift-forge, which has the
|
|
148
|
+
object, needed two gates to keep its status fields honest — 37 items were found claiming `review`
|
|
149
|
+
with their code already on the integration branch.
|
|
150
|
+
"""
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: >-
|
|
3
|
+
How to read and change a work item — required sections, status honesty, and the scope rule.
|
|
4
|
+
Loads when an agent touches the backlog.
|
|
5
|
+
paths:
|
|
6
|
+
- "docs/{{root}}/**/*.md"
|
|
7
|
+
enforcement: gated
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Working on a work item
|
|
11
|
+
|
|
12
|
+
**Authority:** [`docs/{{root}}/README.md`](../../docs/{{root}}/README.md). This rule is the
|
|
13
|
+
short form an agent meets while editing; the reasoning lives there.
|
|
14
|
+
|
|
15
|
+
## Before changing an item
|
|
16
|
+
|
|
17
|
+
- **Re-derive the state; do not quote the board.** A status line, a board row or a triage is
|
|
18
|
+
bookkeeping *about* the work, not the work. Check the artifact the claim is about — the branch,
|
|
19
|
+
the tests, the file — before acting on what a field says.
|
|
20
|
+
- **The template's sections are required, not suggested.** `Out of scope` and
|
|
21
|
+
`Acceptance criteria` may be answered with an explicit "none, because…" but may not be left
|
|
22
|
+
blank or deleted. A blank one is an unfinished plan.
|
|
23
|
+
|
|
24
|
+
## While executing
|
|
25
|
+
|
|
26
|
+
- **Never scope-creep.** Work discovered mid-flight becomes a **new item**; a mere observation
|
|
27
|
+
becomes a **finding**. An item that grew a second purpose should have been two.
|
|
28
|
+
- **Deviations from the plan go in `## Execution`, with the reason.** A plan that silently stopped
|
|
29
|
+
matching what was built is worse than no plan, because it reads as verified.
|
|
30
|
+
|
|
31
|
+
## When it lands
|
|
32
|
+
|
|
33
|
+
- **Move the status in the same change that merges the branch.** `backlog-merged-status` refuses a
|
|
34
|
+
merged branch sitting at a pre-review status — measured elsewhere at 37 items, one of them eight
|
|
35
|
+
days stale.
|
|
36
|
+
- **Never write that something is blocked on finished work.** `backlog-stale-blocker` refuses it:
|
|
37
|
+
it reads as a live constraint and the next session plans around a wall that came down months ago.
|
|
38
|
+
Past tense ("was blocked on") is a record and is allowed.
|
|
39
|
+
|
|
40
|
+
## Escape hatches
|
|
41
|
+
|
|
42
|
+
Both gates take an exemption **that must state a reason** — `branch-merged-ok: <why>` and
|
|
43
|
+
`owner-ok: <why>`. A marker with no reason is ignored: an escape hatch nobody has to justify is not
|
|
44
|
+
an escape hatch, it is an off switch.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: backlog-summary
|
|
3
|
+
description: >-
|
|
4
|
+
Summarize the backlog: open work items grouped by category, the highest-value items with the
|
|
5
|
+
reasoning, stale or contradictory bookkeeping, and workflow-health observations with concrete
|
|
6
|
+
suggestions. Use whenever asked to "summarize the backlog", "what's open", "backlog status /
|
|
7
|
+
report / health", "rank the backlog", "what should we do next", or for any prioritization of
|
|
8
|
+
open work — even when the word "backlog" is not used but the question is about what to build
|
|
9
|
+
next. Read-only: it reports, it never changes an item. Executing one item is /work-item.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Summarize the backlog
|
|
13
|
+
|
|
14
|
+
Read-only. **This skill never edits an item, a status, or the board.**
|
|
15
|
+
|
|
16
|
+
## 1. Read from the current tip, not a copy
|
|
17
|
+
|
|
18
|
+
Run against `{{integration_branch}}` as it is now. A worktree copy or a cached report goes stale,
|
|
19
|
+
and a summary built on one is confidently wrong about what is open.
|
|
20
|
+
|
|
21
|
+
## 2. Gather
|
|
22
|
+
|
|
23
|
+
Read every file in `docs/{{root}}/items/`, plus `BACKLOG.md`. Do **not** trust the board's grouping
|
|
24
|
+
— derive status from the item files and note any disagreement, which is itself a finding worth
|
|
25
|
+
reporting.
|
|
26
|
+
|
|
27
|
+
## 3. Report
|
|
28
|
+
|
|
29
|
+
### Open work, by category
|
|
30
|
+
|
|
31
|
+
Group by the natural categories of this repo (its subsystems, surfaces, or domains — not by
|
|
32
|
+
status). For each: count, and the items with a one-line summary each.
|
|
33
|
+
|
|
34
|
+
### Highest-value items, with the reasoning
|
|
35
|
+
|
|
36
|
+
Rank the top few, and **state why each ranks where it does** — value, cost, what it unblocks, what
|
|
37
|
+
it stops costing. A ranking without reasoning is an opinion wearing a number.
|
|
38
|
+
|
|
39
|
+
### Bookkeeping that disagrees with reality
|
|
40
|
+
|
|
41
|
+
Report, do not fix:
|
|
42
|
+
|
|
43
|
+
- items whose `status` and merged branch disagree
|
|
44
|
+
- items `in_progress` with no branch, or with a branch that no longer exists
|
|
45
|
+
- documents claiming they wait on work that has finished
|
|
46
|
+
- epics whose `children` links are not reciprocated
|
|
47
|
+
|
|
48
|
+
### Workflow health
|
|
49
|
+
|
|
50
|
+
Observations about the *process*, with a concrete suggestion each:
|
|
51
|
+
|
|
52
|
+
- items sitting in one status for a long time
|
|
53
|
+
- items whose acceptance criteria are blank while their status is past `planned`
|
|
54
|
+
- how much work arrives as items versus as unrecorded changes
|
|
55
|
+
- whether `items/` has outgrown readability and wants archiving
|
|
56
|
+
|
|
57
|
+
## 4. What this cannot tell you
|
|
58
|
+
|
|
59
|
+
State these limits in the output rather than leaving the reader to infer them:
|
|
60
|
+
|
|
61
|
+
- it measures **what is recorded**, not what is happening — work done without an item is invisible
|
|
62
|
+
- a ranking is a judgement, and the reasoning is there so it can be argued with
|
|
63
|
+
- an empty result and an unread backlog produce the same summary, so say how many files were read
|
|
64
|
+
|
|
65
|
+
A report that cannot fail loudly is not a measurement.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: work-item
|
|
3
|
+
description: >-
|
|
4
|
+
Execute a backlog work item ({{id_prefix}}-###) end to end through its lifecycle: claim or open
|
|
5
|
+
the item, branch, plan, implement, test, review, merge, and move the status. Use when asked to
|
|
6
|
+
"work on / tackle / start / do / ship {{id_prefix}}-###", to pick up the next item from the
|
|
7
|
+
backlog, or to work an epic's sub-items. Also use to open a new item from a request that turns
|
|
8
|
+
out to be real work rather than a quick fix. An out-of-scope observation is a finding, not an
|
|
9
|
+
item — that is /record-finding. Shipping a version is /cut-release. This skill is one item,
|
|
10
|
+
start to finish.
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Execute a work item
|
|
14
|
+
|
|
15
|
+
**Authority:** [`docs/{{root}}/README.md`](../../../docs/{{root}}/README.md). Read it once per
|
|
16
|
+
session before the first item; this skill is the execution form, not the reasoning.
|
|
17
|
+
|
|
18
|
+
## 0. Establish the item
|
|
19
|
+
|
|
20
|
+
- Given an id → open `docs/{{root}}/items/{{id_prefix}}-###-*.md`.
|
|
21
|
+
- Given a request with no id → decide first **whether this is an item at all**: a one-line fix with
|
|
22
|
+
no design choice is just a change; an observation you are not acting on now is a **finding**. If
|
|
23
|
+
it is an item, claim the next id from the `NEXT-ID` marker in `BACKLOG.md` and **bump the marker
|
|
24
|
+
on your own branch**, then copy `TEMPLATE.md`.
|
|
25
|
+
- Given "the next item" → read the board, but **re-derive before committing to it**. A board row is
|
|
26
|
+
bookkeeping about the work, not the work; scoping from a stale row is how work that is already
|
|
27
|
+
done gets started again.
|
|
28
|
+
|
|
29
|
+
## 1. Check the status is real
|
|
30
|
+
|
|
31
|
+
Before doing anything, confirm the item's `status` and `branch` agree with git. If the branch is
|
|
32
|
+
merged and the status is pre-review, the work landed and the field lied — fix the field, say so in
|
|
33
|
+
`## Execution`, and stop. That is the whole task.
|
|
34
|
+
|
|
35
|
+
## 2. Plan, if it is not planned
|
|
36
|
+
|
|
37
|
+
An `accepted` item needs its plan filled before code: **requirements · impacts · approach ·
|
|
38
|
+
acceptance criteria · out of scope**. None may be blank.
|
|
39
|
+
|
|
40
|
+
- Acceptance criteria must be **checkable**. "Works correctly" is not a criterion; "given X, the
|
|
41
|
+
endpoint returns 422 with a typed error" is.
|
|
42
|
+
- `Out of scope` is required — answer it or write `nothing deferred`. It is the section that stops
|
|
43
|
+
an item quietly growing a second purpose.
|
|
44
|
+
- A significant design choice gets an ADR. Link it; do not restate it here.
|
|
45
|
+
|
|
46
|
+
Status → `planned`.
|
|
47
|
+
|
|
48
|
+
## 3. Branch and execute
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
git switch -c {{branch_prefix}}/{{id_prefix}}-###-slug {{integration_branch}}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Status → `in_progress`.
|
|
55
|
+
|
|
56
|
+
- **Follow the repo's engineering guides**, not this skill, for how the code should look.
|
|
57
|
+
- **Work discovered mid-flight does not join this item.** A new item, or a finding. Record it and
|
|
58
|
+
carry on — do not widen the branch.
|
|
59
|
+
- Deviations from the plan go in `## Execution` **with the reason**, as you make them. A plan that
|
|
60
|
+
silently stopped matching what was built reads as verified and is not.
|
|
61
|
+
|
|
62
|
+
## 4. Test
|
|
63
|
+
|
|
64
|
+
Run the narrowest validation that covers what you touched, then the repo's standard gate set:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
rungs check
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Existing tests are not weakened to make a change pass. If a test is genuinely wrong, that is its
|
|
71
|
+
own item with its own reasoning.
|
|
72
|
+
|
|
73
|
+
## 5. Review
|
|
74
|
+
|
|
75
|
+
Walk **each** acceptance criterion and write what you checked in `## Review`. A criterion you
|
|
76
|
+
cannot demonstrate is not met — leave it open and say so rather than marking the item done.
|
|
77
|
+
|
|
78
|
+
Status → `review`.
|
|
79
|
+
|
|
80
|
+
## 6. Land
|
|
81
|
+
|
|
82
|
+
Merge into `{{integration_branch}}`, **move the status to `done` in the same change**, then delete
|
|
83
|
+
the branch. In that order: the merged-status gate can only see items whose branch still exists, so
|
|
84
|
+
deleting first costs you the check.
|
|
85
|
+
|
|
86
|
+
Update the board: remove the row from **In progress**, and archive the item if the repo archives on
|
|
87
|
+
completion.
|
|
88
|
+
|
|
89
|
+
## When to stop and ask
|
|
90
|
+
|
|
91
|
+
- The plan cannot be written because the requirement is ambiguous in a way that changes the work.
|
|
92
|
+
- Execution reveals the approach is wrong — reopen the plan rather than improvising past it.
|
|
93
|
+
- Acceptance criteria conflict with something already shipped.
|
|
94
|
+
|
|
95
|
+
In each case, write what you found in the item first. The next session reads the item, not this
|
|
96
|
+
conversation.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Gate run. Generated by `rungs`; the trigger block is regenerated from the `trigger` parameter.
|
|
2
|
+
name: checks
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
# rungs:begin ci-triggers
|
|
6
|
+
push:
|
|
7
|
+
branches: ['**']
|
|
8
|
+
pull_request:
|
|
9
|
+
# rungs:end ci-triggers
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
# One run per ref. A superseded run tells you about a tree nobody has any more.
|
|
13
|
+
group: checks-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
gates:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
with:
|
|
22
|
+
# Gates that compare against the merge base need history. Cheaper than
|
|
23
|
+
# discovering mid-run that a gate cannot see what it is attributing against.
|
|
24
|
+
fetch-depth: 0
|
|
25
|
+
|
|
26
|
+
- uses: actions/setup-node@v4
|
|
27
|
+
with:
|
|
28
|
+
node-version: '22'
|
|
29
|
+
|
|
30
|
+
# Runs every gate in .ai/gates.toml — the same set, in the same order, as
|
|
31
|
+
# `rungs check` locally. A CI-only gate is one nobody can reproduce.
|
|
32
|
+
- name: Run gates
|
|
33
|
+
run: npx rungs check --tier full --reporter github
|
|
34
|
+
|
|
35
|
+
# Add a matrix job per package here rather than a workflow per package.
|
|
36
|
+
# A checklist step that creates a file creates N files:
|
|
37
|
+
#
|
|
38
|
+
# strategy:
|
|
39
|
+
# matrix:
|
|
40
|
+
# package: [a, b, c]
|
|
41
|
+
#
|
|
42
|
+
# `rungs check` reports the workflow count and fails past the threshold in
|
|
43
|
+
# .ai/gates.toml, for exactly that reason.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Gate table for the `ci` module.
|
|
2
|
+
|
|
3
|
+
# ── ci-workflow-proliferation ─────────────────────────────────────────────────
|
|
4
|
+
#
|
|
5
|
+
# Counts near-identical workflow files. Similarity is structural — job names, step commands, and
|
|
6
|
+
# `uses:` refs — not textual, because near-identical files differ exactly in the package name.
|
|
7
|
+
|
|
8
|
+
[file_population]
|
|
9
|
+
scan = [".github/workflows/*.yml", ".github/workflows/*.yaml"]
|
|
10
|
+
similarity_on = ["job_ids", "step_uses", "step_run_shape"]
|
|
11
|
+
similarity_min = 0.85
|
|
12
|
+
warn_at = 4
|
|
13
|
+
fail_at = 8
|
|
14
|
+
exempt_marker = "proliferation-ok:" # ignored unless it states a reason
|
|
15
|
+
message = """
|
|
16
|
+
{count} workflows share ~{similarity} of their structure. This is the shape that reached 98 files
|
|
17
|
+
in one repo, where changing release policy became a 98-file edit. Replace them with one workflow
|
|
18
|
+
and a `strategy.matrix`, or state why not with `# proliferation-ok: <reason>`.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# The threshold is deliberately low. A repo with 8 genuinely different workflows can exempt them
|
|
22
|
+
# once; a repo generating one per package hits it at the point the pattern is still cheap to undo.
|
|
23
|
+
|
|
24
|
+
# ── self-tests ────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
[[self_test]]
|
|
27
|
+
gate = "ci-workflow-proliferation"
|
|
28
|
+
expect = "fail"
|
|
29
|
+
fixture = { workflows = 12, similarity = 0.97 }
|
|
30
|
+
|
|
31
|
+
[[self_test]]
|
|
32
|
+
gate = "ci-workflow-proliferation"
|
|
33
|
+
expect = "pass"
|
|
34
|
+
fixture = { workflows = 12, similarity = 0.30 }
|
|
35
|
+
|
|
36
|
+
[[self_test]]
|
|
37
|
+
gate = "ci-workflow-proliferation"
|
|
38
|
+
expect = "pass"
|
|
39
|
+
fixture = { workflows = 12, similarity = 0.97, exempt = "proliferation-ok: per-runtime, they differ in the container image" }
|
|
40
|
+
|
|
41
|
+
[[self_test]]
|
|
42
|
+
gate = "ci-workflow-proliferation"
|
|
43
|
+
expect = "fail"
|
|
44
|
+
fixture = { workflows = 12, similarity = 0.97, exempt = "proliferation-ok:" }
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
[module]
|
|
2
|
+
name = "ci"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
rung = 1
|
|
5
|
+
summary = "One workflow that runs the gate registry, a matrix instead of a file per item, and a check that keeps it that way."
|
|
6
|
+
|
|
7
|
+
[requires]
|
|
8
|
+
modules = ["gates"]
|
|
9
|
+
|
|
10
|
+
[conflicts]
|
|
11
|
+
modules = []
|
|
12
|
+
|
|
13
|
+
# Rung 1, but bundled in the `disciplined` profile rather than `tracked`: rung is
|
|
14
|
+
# how mature the practice is, a profile is which problem you have. A solo repo
|
|
15
|
+
# can be at rung 1 without needing CI at all.
|
|
16
|
+
|
|
17
|
+
[params.provider]
|
|
18
|
+
description = "CI provider. Only `github` is implemented."
|
|
19
|
+
default = "github"
|
|
20
|
+
allowed = ["github"]
|
|
21
|
+
consumed_by = "render"
|
|
22
|
+
|
|
23
|
+
[params.trigger]
|
|
24
|
+
description = """
|
|
25
|
+
When the gate run fires. `push` runs on every push to any branch. `land` runs only on the
|
|
26
|
+
integration branch and on merge refs, which is what a repo with billed minutes and many concurrent
|
|
27
|
+
sessions wants — rift-forge measured ~19 billed minutes per run, and one run per push across ~15
|
|
28
|
+
sessions is 400-1000 minutes a day.
|
|
29
|
+
"""
|
|
30
|
+
default = "push"
|
|
31
|
+
allowed = ["push", "land"]
|
|
32
|
+
consumed_by = "render"
|
|
33
|
+
# Reaches file content through the `ci-triggers` managed block, not through a
|
|
34
|
+
# conditional. That is the general mechanism: a behavioural parameter changes
|
|
35
|
+
# what the renderer writes into a block, and substitution-only stays intact.
|
|
36
|
+
|
|
37
|
+
[params.workflow_path]
|
|
38
|
+
description = "Where the workflow file lands"
|
|
39
|
+
default = ".github/workflows/checks.yml"
|
|
40
|
+
|
|
41
|
+
[[gates]]
|
|
42
|
+
id = "ci-workflow-proliferation"
|
|
43
|
+
kind = "declared"
|
|
44
|
+
engine = "file-population"
|
|
45
|
+
table = "gates/ci.toml"
|
|
46
|
+
tier = "fast"
|
|
47
|
+
why = """
|
|
48
|
+
hexguard has 98 near-identical per-package release workflows because a phase checklist told the
|
|
49
|
+
agent to add one each time. Changing release policy there is a 98-file edit and the workflow
|
|
50
|
+
directory is unreadable. A checklist step that creates a file creates N files, and nobody notices
|
|
51
|
+
until N is large — so the count is checked.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
[detect]
|
|
55
|
+
paths = [".github/workflows/*.yml", ".github/workflows/*.yaml", ".gitlab-ci.yml", "azure-pipelines.yml"]
|
|
56
|
+
markers = ["rungs:begin ci"]
|
|
57
|
+
|
|
58
|
+
[[detect.adopt_as]]
|
|
59
|
+
kind = "existing-workflows"
|
|
60
|
+
note = """
|
|
61
|
+
A repo with workflows has CI; it does not have this registry-driven one. Adopt means: leave every
|
|
62
|
+
existing workflow alone, add one that runs `rungs check`, and report the proliferation count so the
|
|
63
|
+
owner can see what they have. Never rewrite or delete a workflow — that is somebody's deploy.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
[provenance]
|
|
67
|
+
sources = ["hexguard", "rift-forge"]
|
|
68
|
+
patterns = ["matrix-not-per-item-ci", "workflow-proliferation-check", "ci-at-land-time"]
|
|
69
|
+
incident = """
|
|
70
|
+
hexguard's .github/workflows/ holds 99 files: one CI workflow and 98 per-package release
|
|
71
|
+
workflows, near-identical, hand-maintained, emitted one at a time by Phase 3 of a mandatory
|
|
72
|
+
checklist. rift-forge went the other way and made CI cost-aware — item branches are deliberately
|
|
73
|
+
not triggers, because the repository is private and one run is ~19 billed minutes.
|
|
74
|
+
"""
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
**Authoritative for:** the loop many sessions share: where to branch from, when to verify, how to land, and which conflicts are the tooling’s problem.
|
|
2
|
+
**Not authoritative for:** the backlog lifecycle those branches carry, and the gates the loop runs.
|
|
3
|
+
|
|
4
|
+
# Concurrent sessions
|
|
5
|
+
|
|
6
|
+
**Read the threshold first.** Below roughly five sessions working at once on one integration
|
|
7
|
+
branch, everything here costs more than it returns. It is a real tooling surface with its own
|
|
8
|
+
failure modes.
|
|
9
|
+
|
|
10
|
+
Every rule assumes one thing: **many sessions work at once, in separate worktrees, off one shared
|
|
11
|
+
branch, and they cannot see each other's work.**
|
|
12
|
+
|
|
13
|
+
## The loop
|
|
14
|
+
|
|
15
|
+
| | |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `rungs session start <branch>` | Cuts from `{{green_prefix}}<branch>` — the last merge that was actually verified — not the tip. Falls back to the tip and **says so** |
|
|
18
|
+
| `rungs check` | The fast tier. Run it constantly |
|
|
19
|
+
| `rungs preflight` | The integration branch moved: did it change files *you* changed? That, not the commit count, predicts a conflict |
|
|
20
|
+
| `rungs land <branch>` | merge → verify **the merged tree** → fast-forward → move the green ref |
|
|
21
|
+
| `rungs worktrees` | What is finished and prunable. **Reports only** — removing someone else's worktree is not a script's call |
|
|
22
|
+
|
|
23
|
+
## Do not run the full tier before landing
|
|
24
|
+
|
|
25
|
+
`land` runs it on the merged tree itself. A separate full run beforehand proves nothing land will
|
|
26
|
+
not prove — and it is not merely wasted time: **the integration branch moves while it runs**, so
|
|
27
|
+
the pre-verify is what widens the window the merge then conflicts in.
|
|
28
|
+
|
|
29
|
+
Measured in one session: three of five land attempts refused, every one after a 4–12 minute
|
|
30
|
+
pre-verify, every one on the same generated artifacts. The attempt that landed did merge → resolve
|
|
31
|
+
→ regenerate → land with nothing in between.
|
|
32
|
+
|
|
33
|
+
So: **fast tier constantly, full tier at the boundary.** Reach for it early only to answer a
|
|
34
|
+
deliberate question, never as a ritual.
|
|
35
|
+
|
|
36
|
+
## A failure is attributed, never just counted
|
|
37
|
+
|
|
38
|
+
`check` re-runs each failing gate against the merge base in a throwaway worktree and reports it as
|
|
39
|
+
**inherited** (already red before you started — stated, never blocking) or **INTRODUCED** (yours —
|
|
40
|
+
blocks). Anything it cannot attribute also blocks: **we do not land on an unknown.**
|
|
41
|
+
|
|
42
|
+
This is not politeness about blame. *A gate that is red for reasons you did not cause and cannot
|
|
43
|
+
fix is a gate you learn to bypass, and a bypassed gate reports nothing.*
|
|
44
|
+
|
|
45
|
+
> **Attribution makes a red gate survivable, which also removes the pressure to fix it.** One repo
|
|
46
|
+
> ran 11 of its last 15 CI runs red on two permanently-broken jobs. The ledger's ageing signal
|
|
47
|
+
> exists for exactly this: fix them or delete them.
|
|
48
|
+
|
|
49
|
+
## Land, then move the tip — in that order
|
|
50
|
+
|
|
51
|
+
`land` merges into a scratch `{{integ_prefix}}…` ref, verifies **that** tree, and only then
|
|
52
|
+
fast-forwards with a compare-and-swap. Two things follow:
|
|
53
|
+
|
|
54
|
+
- **The integration branch cannot go red from a merge nobody verified** — the ref update is
|
|
55
|
+
unreachable otherwise, and a refusal leaves it bit-for-bit unchanged with the merged tree parked
|
|
56
|
+
for you to fix.
|
|
57
|
+
- **Concurrent landing is refused, not silently merged.** A real lock names its holder and start
|
|
58
|
+
time and is taken over if the holder died.
|
|
59
|
+
|
|
60
|
+
**Nothing keeps the integration branch checked out.** If something needs it checked out, that is a
|
|
61
|
+
bug in whatever it is doing.
|
|
62
|
+
|
|
63
|
+
## Three things to know when a land refuses
|
|
64
|
+
|
|
65
|
+
- **Your worktree is left detached.** Your branch and commits are safe; the files in front of you
|
|
66
|
+
are simply not yours. Switch back and carry on. Do not "restore" anything.
|
|
67
|
+
- **Check the exit code of `land` itself.** Piping it through `tail` or `grep` reports *that*
|
|
68
|
+
command's status, so a refused land reads as success.
|
|
69
|
+
- **Reconcile generated artifacts by regenerating, never by merging text.** Take one side, re-run
|
|
70
|
+
the producer, and re-pin what moved with the reason at the pin.
|
|
71
|
+
|
|
72
|
+
## What conflicts, and which of it is the tooling's problem
|
|
73
|
+
|
|
74
|
+
| Class | Handling |
|
|
75
|
+
| --- | --- |
|
|
76
|
+
| **Id ledgers** — boards, registers, indexes | The driver takes the higher `NEXT-ID` and keeps both claim comments. **It resolves nothing else** — a row that moved section is handed back, because a union merge would keep both copies of it |
|
|
77
|
+
| **Generated artifacts** | The driver **always refuses** and prints the regenerate command |
|
|
78
|
+
| **Shared code** | **Not a tooling problem — a scheduling one.** Give a hot surface one owner at a time and batch same-surface work behind one session. `preflight` tells you when you are about to collide |
|
|
79
|
+
|
|
80
|
+
That third row is the honest one: it names the class this will not automate and prescribes
|
|
81
|
+
scheduling instead.
|
|
82
|
+
|
|
83
|
+
## Worktree lifetime is item lifetime
|
|
84
|
+
|
|
85
|
+
Delete the branch when it lands. `rungs worktrees` measures whether that happened and flags
|
|
86
|
+
worktrees that are merged and clean — and, more usefully, ones that are merged **and dirty**, where
|
|
87
|
+
uncommitted work is sitting in a branch that has already landed.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<!-- rungs:begin concurrency@1.0.0 -->
|
|
2
|
+
## Concurrent sessions
|
|
3
|
+
|
|
4
|
+
Many sessions share `{{integration_branch}}` and cannot see each other — see
|
|
5
|
+
[`docs/concurrent-sessions.md`](docs/concurrent-sessions.md). Cut with `rungs session start`
|
|
6
|
+
(from the last **verified** merge, not the tip), run the fast tier constantly, and land with
|
|
7
|
+
`rungs land` — **never `git merge` by hand**. Do not run the full tier before landing: it widens
|
|
8
|
+
the window the merge then conflicts in. Reconcile generated artifacts by **regenerating**, never
|
|
9
|
+
by merging text.
|
|
10
|
+
<!-- rungs:end concurrency -->
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# rungs:begin concurrency@1.0.0
|
|
2
|
+
# Merge drivers per conflict class. INERT until `rungs setup git` installs them — the driver names
|
|
3
|
+
# below mean nothing to git on a fresh clone, and merges quietly fall back to the default. For a
|
|
4
|
+
# generated artifact that default is a clean text merge of a file that is a fixed point of nothing.
|
|
5
|
+
|
|
6
|
+
# Id ledgers: take the higher NEXT-ID counter, keep both claim comments, resolve nothing else.
|
|
7
|
+
docs/**/BACKLOG.md merge=rungs-ledger
|
|
8
|
+
docs/**/FINDINGS.md merge=rungs-ledger
|
|
9
|
+
|
|
10
|
+
# Generated artifacts: always refuse, and print the regenerate command.
|
|
11
|
+
.claude/rules/** merge=rungs-generated
|
|
12
|
+
.github/instructions/** merge=rungs-generated
|
|
13
|
+
.cursor/rules/** merge=rungs-generated
|
|
14
|
+
.ai/render-report.md merge=rungs-generated
|
|
15
|
+
# rungs:end concurrency
|