@rungs/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +196 -0
  3. package/modules/README.md +116 -0
  4. package/modules/adr/files/{{path}}/README.md +52 -0
  5. package/modules/adr/files/{{path}}/TEMPLATE.md +55 -0
  6. package/modules/adr/fragments/AGENTS.md +8 -0
  7. package/modules/adr/gates/adr.toml +65 -0
  8. package/modules/adr/module.toml +67 -0
  9. package/modules/audit/files/{{criteria_path}} +43 -0
  10. package/modules/audit/fragments/AGENTS.md +8 -0
  11. package/modules/audit/gates/audit.toml +48 -0
  12. package/modules/audit/module.toml +72 -0
  13. package/modules/audit/skills/assess/SKILL.md +62 -0
  14. package/modules/backlog/files/docs/{{root}}/BACKLOG.md +52 -0
  15. package/modules/backlog/files/docs/{{root}}/README.md +110 -0
  16. package/modules/backlog/files/docs/{{root}}/TEMPLATE.md +56 -0
  17. package/modules/backlog/files/docs/{{root}}/archive/README.md +13 -0
  18. package/modules/backlog/files/docs/{{root}}/items/README.md +13 -0
  19. package/modules/backlog/fragments/AGENTS.md +9 -0
  20. package/modules/backlog/gates/ids.toml +106 -0
  21. package/modules/backlog/module.toml +150 -0
  22. package/modules/backlog/rules/work-items.md +44 -0
  23. package/modules/backlog/skills/backlog-summary/SKILL.md +65 -0
  24. package/modules/backlog/skills/work-item/SKILL.md +96 -0
  25. package/modules/ci/files/{{workflow_path}} +43 -0
  26. package/modules/ci/gates/ci.toml +44 -0
  27. package/modules/ci/module.toml +74 -0
  28. package/modules/concurrency/files/docs/concurrent-sessions.md +87 -0
  29. package/modules/concurrency/fragments/AGENTS.md +10 -0
  30. package/modules/concurrency/fragments/gitattributes +15 -0
  31. package/modules/concurrency/gates/concurrency.toml +54 -0
  32. package/modules/concurrency/module.toml +113 -0
  33. package/modules/design-sync/fragments/AGENTS.md +8 -0
  34. package/modules/design-sync/gates/design.toml +70 -0
  35. package/modules/design-sync/module.toml +82 -0
  36. package/modules/design-sync/skills/design-align/SKILL.md +51 -0
  37. package/modules/design-sync/skills/design-pull/SKILL.md +50 -0
  38. package/modules/doc-authority/files/{{registry_path}} +59 -0
  39. package/modules/doc-authority/files/{{rules_path}} +51 -0
  40. package/modules/doc-authority/fragments/AGENTS.md +9 -0
  41. package/modules/doc-authority/gates/authority.toml +127 -0
  42. package/modules/doc-authority/module.toml +111 -0
  43. package/modules/findings/files/docs/{{backlog.root}}/FINDINGS.md +56 -0
  44. package/modules/findings/fragments/AGENTS.md +7 -0
  45. package/modules/findings/gates/findings.toml +68 -0
  46. package/modules/findings/module.toml +100 -0
  47. package/modules/findings/skills/record-finding/SKILL.md +63 -0
  48. package/modules/gates/files/.ai/gates.toml +49 -0
  49. package/modules/gates/fragments/AGENTS.md +8 -0
  50. package/modules/gates/fragments/gitignore +4 -0
  51. package/modules/gates/gates/structural.toml +127 -0
  52. package/modules/gates/module.toml +143 -0
  53. package/modules/gates/skills/harden-rule/SKILL.md +82 -0
  54. package/modules/instructions/files/.ai/rules/README.md +53 -0
  55. package/modules/instructions/files/AGENTS.md +104 -0
  56. package/modules/instructions/files/CLAUDE.md +11 -0
  57. package/modules/instructions/gates/core.toml +114 -0
  58. package/modules/instructions/module.toml +127 -0
  59. package/modules/release/fragments/AGENTS.md +8 -0
  60. package/modules/release/gates/release.toml +85 -0
  61. package/modules/release/module.toml +97 -0
  62. package/modules/release/skills/cut-release/SKILL.md +81 -0
  63. package/modules/session/files/{{archive}}/README.md +28 -0
  64. package/modules/session/files/{{path}} +56 -0
  65. package/modules/session/fragments/AGENTS.md +6 -0
  66. package/modules/session/gates/session.toml +63 -0
  67. package/modules/session/module.toml +72 -0
  68. package/modules/session/skills/close-session/SKILL.md +59 -0
  69. package/modules/skills/fragments/AGENTS.md +8 -0
  70. package/modules/skills/gates/skills.toml +94 -0
  71. package/modules/skills/module.toml +89 -0
  72. package/modules/skills/rules/skill-authoring.md +71 -0
  73. package/modules/specs/files/{{path}}/README.md +63 -0
  74. package/modules/specs/files/{{path}}/TEMPLATE.md +62 -0
  75. package/modules/specs/fragments/AGENTS.md +8 -0
  76. package/modules/specs/gates/specs.toml +119 -0
  77. package/modules/specs/module.toml +107 -0
  78. package/modules/workflows/fragments/AGENTS.md +8 -0
  79. package/modules/workflows/gates/workflows.toml +65 -0
  80. package/modules/workflows/module.toml +75 -0
  81. package/modules/workflows/rules/planning-tiers.md +46 -0
  82. package/modules/workflows/rules/reuse-decision.md +50 -0
  83. package/modules/workflows/skills/decompose/SKILL.md +75 -0
  84. package/package.json +42 -0
  85. package/src/add.ts +331 -0
  86. package/src/check.ts +216 -0
  87. package/src/cli.ts +411 -0
  88. package/src/detect.ts +246 -0
  89. package/src/engines.ts +260 -0
  90. package/src/engines2.ts +297 -0
  91. package/src/engines3.ts +194 -0
  92. package/src/glob.ts +101 -0
  93. package/src/lifecycle.ts +268 -0
  94. package/src/manifest.ts +108 -0
  95. package/src/render.ts +222 -0
  96. package/src/substitute.ts +78 -0
  97. package/src/types.ts +110 -0
@@ -0,0 +1,127 @@
1
+ [module]
2
+ name = "instructions"
3
+ version = "1.0.0"
4
+ rung = 0
5
+ summary = "The agent entry point, the harness bridge, path-scoped rule sources, and the render pipeline. The one module every repo installs."
6
+
7
+ [requires]
8
+ modules = []
9
+
10
+ [conflicts]
11
+ modules = []
12
+
13
+ # ── parameters ────────────────────────────────────────────────────────────────
14
+
15
+ [params.project_name]
16
+ description = "Name used in the entry document's first line"
17
+ default = "" # inferred from the repo directory when blank
18
+
19
+ [params.harnesses]
20
+ description = "Which coding agents to emit for. Drives the ADR-0001 render matrix."
21
+ default = ["claude", "agents-md"]
22
+ allowed = ["claude", "copilot", "cursor", "agents-md"]
23
+ consumed_by = "render"
24
+ # `consumed_by` marks a *behavioural* parameter: it changes what the CLI does rather than being
25
+ # substituted into a template, so it will never appear as {{harnesses}} anywhere. Without this a
26
+ # dead-parameter lint would report it, and the obvious "fix" would be to delete the parameter that
27
+ # decides which harnesses exist.
28
+
29
+ [params.core_budget]
30
+ description = """
31
+ Maximum lines in AGENTS.md before `instructions-core-size` fails. 200 is Anthropic's published
32
+ guidance: "Longer files consume more context and reduce adherence." Fragments merged by other
33
+ modules count against it, which is deliberate — the budget is what forces content into
34
+ path-scoped rules instead.
35
+ """
36
+ default = 200
37
+
38
+ # ── gates ─────────────────────────────────────────────────────────────────────
39
+
40
+ [[gates]]
41
+ id = "instructions-core-size"
42
+ kind = "declared"
43
+ engine = "file-budget"
44
+ table = "gates/core.toml"
45
+ tier = "fast"
46
+ why = "rift-forge's entry document reached 1513 lines and every session read all of it, in the one repo that otherwise measured everything."
47
+
48
+ [[gates]]
49
+ id = "instructions-render-current"
50
+ kind = "declared"
51
+ engine = "render-freshness"
52
+ table = "gates/core.toml"
53
+ tier = "fast"
54
+ why = "A rendered rule that no longer matches its source is a rule the agent is not reading. A green check means 'not yet regenerated', never 'current'."
55
+
56
+ [[gates]]
57
+ id = "instructions-repo-map-current"
58
+ kind = "declared"
59
+ engine = "render-freshness"
60
+ table = "gates/core.toml"
61
+ tier = "fast"
62
+ why = "Every source repo hand-maintained a repo map and one of them grew five duplicated entries — in the file every session reads first."
63
+
64
+ # A hook is a gate with a lifecycle trigger rather than a runner trigger. It is
65
+ # registered into the harness that supports it and reported as degraded for those
66
+ # that do not, exactly like any other ADR-0001 target.
67
+ [[gates]]
68
+ id = "instructions-shell-backticks"
69
+ kind = "declared"
70
+ engine = "shell-safety"
71
+ table = "gates/core.toml"
72
+ trigger = "pre-tool-use"
73
+ matcher = "Bash|PowerShell"
74
+ why = """
75
+ Inside a double-quoted shell string, backticks are command substitution, so a `node -e "…"` that
76
+ writes documentation deletes the backticked words and leaves grammatical text behind. It exits 0.
77
+ rift-forge measured six occurrences and six repair passes in one session, documented it, and was
78
+ broken three more times before making it a hook — which is the whole argument for the escalation
79
+ ladder this module ships.
80
+ """
81
+
82
+ # ── detection (ADR-0004) ──────────────────────────────────────────────────────
83
+
84
+ [detect]
85
+ paths = [
86
+ "AGENTS.md",
87
+ "CLAUDE.md",
88
+ ".github/copilot-instructions.md",
89
+ ".cursorrules",
90
+ ".ai/instructions.md",
91
+ ]
92
+ markers = ["rungs:begin instructions"]
93
+
94
+ # No `detect.paradigm`: there is no competing paradigm for "the file the agent
95
+ # reads first". Every repo either has one or does not.
96
+
97
+ [[detect.infer]]
98
+ param = "harnesses"
99
+ # Presence of a harness's own directory proposes adding it to the matrix.
100
+ # Proposes only — a stale `.cursorrules` is not consent to emit Cursor rules.
101
+ paths = { claude = ".claude/", copilot = ".github/instructions/", cursor = ".cursor/" }
102
+
103
+ # ── provenance (required, validated) ──────────────────────────────────────────
104
+
105
+ [provenance]
106
+ sources = ["axiom-mesh", "hexguard", "hexguard-templates", "rift-forge"]
107
+ patterns = [
108
+ "entry-point",
109
+ "agents-md-bridge",
110
+ "scoped-instructions",
111
+ "core-size-budget",
112
+ "validation-matrix",
113
+ "repo-map",
114
+ "narrowest-anchor-loop",
115
+ "negative-conventions",
116
+ "comms-style",
117
+ "shell-editing-rules",
118
+ "instruction-hardening",
119
+ ]
120
+ incident = """
121
+ All four source repos converged on a single canonical entry document, and all four put content in
122
+ it that decayed. axiom-mesh's 350-line file had to warn four times that one of its own routing
123
+ targets was a dead redirect. hexguard-templates' repo map grew five duplicated entries in the file
124
+ every session reads first, in the repo whose conventions forbid restating scope. rift-forge reached
125
+ 1513 lines with no path scoping at all, while hexguard had solved that with applyTo globs a month
126
+ earlier. Nobody had both completeness and scope.
127
+ """
@@ -0,0 +1,8 @@
1
+ <!-- rungs:begin release@1.0.0 -->
2
+ ## Releases
3
+
4
+ `{{candidate_prefix}}<version>` integrates the next release, `{{stable_branch}}` is the released
5
+ line, and `{{deploy_branch_prefix}}<version>` is what you deploy from **and roll back to**. Ship
6
+ with **`/cut-release`** — never by hand. Changed shipping code? Add a fragment in
7
+ `{{changelog_dir}}/` on the same branch.
8
+ <!-- rungs:end release -->
@@ -0,0 +1,85 @@
1
+ # Gate table for the `release` module.
2
+
3
+ # ── release-changelog-fragment ────────────────────────────────────────────────
4
+ #
5
+ # Fires on a branch that changed shipping code and added no fragment. Deliberately narrow: docs,
6
+ # tests and tooling changes do not need release notes, and a gate that demands one for a typo fix
7
+ # is a gate people learn to bypass.
8
+
9
+ [file_population]
10
+ require_when_changed = ["src/**", "lib/**", "app/**", "server/**", "web/**", "packages/**"]
11
+ requires_one_of = ["{{changelog_dir}}/*.md"]
12
+ ignore_when_only = ["docs/**", "**/*.test.*", "**/*.spec.*", ".github/**", "*.md"]
13
+ exempt_marker = "changelog-ok:" # ignored unless it states a reason
14
+ message = """
15
+ This branch changes shipping code and adds no fragment in {{changelog_dir}}/. Add one, or state why
16
+ not with `changelog-ok: <reason>` — internal refactors with no user-visible effect are a legitimate
17
+ reason, and saying so takes less time than reconstructing it at cut time.
18
+ """
19
+
20
+ # ── release-version-consistent ────────────────────────────────────────────────
21
+ #
22
+ # The version is derivable, so it is computed rather than compared against a typed value. Probing
23
+ # only what the data settles without judgement: every listed location must agree with every other,
24
+ # which needs no opinion about which is right.
25
+
26
+ [[computed_claim]]
27
+ id = "version"
28
+ sources = [
29
+ { file = "package.json", path = "version" },
30
+ { file = "*/package.json", path = "version" },
31
+ { file = "Directory.Build.props", xpath = "//Version" },
32
+ { file = "pyproject.toml", path = "project.version" },
33
+ ]
34
+ rule = "all-agree"
35
+ autofix = "rungs release sync-version"
36
+ message = """
37
+ Version disagrees across {count} locations: {values}. One of them was bumped and the others were
38
+ not, which is discovered by a user rather than by us. Do not hand-edit — run the autofix.
39
+ """
40
+
41
+ # What this does not cover is pinned, so green never reads as "verified":
42
+ [computed_claim.pins]
43
+ uncovered = [
44
+ "version strings embedded in source constants",
45
+ "versions in documentation prose",
46
+ "container image tags",
47
+ ]
48
+ note = "A green check means these files agree, not that the version is correct everywhere."
49
+
50
+ # ── self-tests ────────────────────────────────────────────────────────────────
51
+
52
+ [[self_test]]
53
+ gate = "release-changelog-fragment"
54
+ expect = "fail"
55
+ fixture = { changed = ["src/a.ts"], fragments = [] }
56
+
57
+ [[self_test]]
58
+ gate = "release-changelog-fragment"
59
+ expect = "pass"
60
+ fixture = { changed = ["src/a.ts"], fragments = ["{{changelog_dir}}/42.feature.md"] }
61
+
62
+ [[self_test]]
63
+ gate = "release-changelog-fragment"
64
+ expect = "pass"
65
+ fixture = { changed = ["docs/a.md"], fragments = [] }
66
+
67
+ [[self_test]]
68
+ gate = "release-changelog-fragment"
69
+ expect = "pass"
70
+ fixture = { changed = ["src/a.ts"], fragments = [], exempt = "changelog-ok: internal rename, no user-visible effect" }
71
+
72
+ [[self_test]]
73
+ gate = "release-changelog-fragment"
74
+ expect = "fail"
75
+ fixture = { changed = ["src/a.ts"], fragments = [], exempt = "changelog-ok:" }
76
+
77
+ [[self_test]]
78
+ gate = "release-version-consistent"
79
+ expect = "fail"
80
+ fixture = { values = ["1.2.0", "1.1.0"] }
81
+
82
+ [[self_test]]
83
+ gate = "release-version-consistent"
84
+ expect = "pass"
85
+ fixture = { values = ["1.2.0", "1.2.0"] }
@@ -0,0 +1,97 @@
1
+ [module]
2
+ name = "release"
3
+ version = "1.0.0"
4
+ rung = 3
5
+ summary = "Candidate, stable and deploy branch lines; one procedure for cutting a release, hotfixing and rolling back; changelog fragments instead of a shared file."
6
+
7
+ [requires]
8
+ modules = ["backlog", "ci"]
9
+
10
+ [conflicts]
11
+ modules = []
12
+
13
+ [params.candidate_prefix]
14
+ description = """
15
+ Prefix for the branch integrating the next release — `candidate/0.2.0`. A prefix, not a full name:
16
+ the version is decided when a release is cut, not when the module is installed, so a parameter
17
+ holding the whole branch name would be stale before it was ever used.
18
+ """
19
+ default = "candidate/"
20
+
21
+ [params.stable_branch]
22
+ description = "The released line. Tagged, never developed on directly."
23
+ default = "main"
24
+
25
+ [params.deploy_branch_prefix]
26
+ description = """
27
+ Long-lived branch cut per release to deploy from — and to roll back to. A rollback that means
28
+ reverting commits on the stable line is a rollback nobody performs under pressure.
29
+ """
30
+ default = "release/"
31
+
32
+ [params.changelog_dir]
33
+ description = """
34
+ One file per change instead of a shared CHANGELOG. A shared changelog is a guaranteed conflict on
35
+ every concurrent branch, and the conflict is always resolved by keeping both — which is how
36
+ duplicate entries ship.
37
+ """
38
+ default = "changelog.d"
39
+
40
+ [params.version_scheme]
41
+ description = "semver | calver"
42
+ default = "semver"
43
+ consumed_by = "render"
44
+
45
+ [[gates]]
46
+ id = "release-changelog-fragment"
47
+ kind = "declared"
48
+ engine = "file-population"
49
+ table = "gates/release.toml"
50
+ tier = "fast"
51
+ why = """
52
+ A change with no changelog fragment is a change the release notes will not mention, discovered at
53
+ cut time when nobody remembers what it did. Cheaper to require the fragment on the branch that made
54
+ the change.
55
+ """
56
+
57
+ [[gates]]
58
+ id = "release-version-consistent"
59
+ kind = "declared"
60
+ engine = "computed-claim"
61
+ table = "gates/release.toml"
62
+ tier = "fast"
63
+ why = "A version appears in several files and drifts in exactly one of them. It is derivable, so it is computed rather than typed."
64
+
65
+ [skills.cut-release]
66
+ extensions = { disable-model-invocation = true }
67
+ extension_note = """
68
+ Cutting a release tags, merges and moves a deploy branch. A model must never decide that now is the
69
+ moment to ship. This costs packaging for claude.ai uploads and the Skills API; it stays portable to
70
+ every skills-compatible CLI.
71
+ """
72
+
73
+ [detect]
74
+ paths = ["CHANGELOG.md", "changelog.d/**", ".github/workflows/release*.yml", "docs/**/release-workflow.md"]
75
+ markers = ["rungs:begin release"]
76
+
77
+ [[detect.paradigm]]
78
+ id = "trunk-tag-only"
79
+ paths = [".github/workflows/release*.yml"]
80
+ compare = "docs/research/synthesis.md#5-the-maturity-ladder"
81
+ note = """
82
+ Tagging directly on the trunk is a legitimate, cheaper model, and most repos should stay there. The
83
+ candidate/stable/deploy split earns its cost when several sessions integrate into one release and
84
+ a rollback has to be possible without reverting commits. Report the difference; do not convert.
85
+ """
86
+
87
+ [provenance]
88
+ sources = ["rift-forge"]
89
+ patterns = ["candidate-and-release-lines", "release-skill"]
90
+ incident = """
91
+ rift-forge separates the candidate that integrates the next release, the stable line that is tagged,
92
+ and a long-lived deploy branch cut per release — because a rollback that means reverting commits on
93
+ the stable line is a rollback nobody performs correctly under pressure. Its changelog is fragment
94
+ files rather than one document, for the same reason its id ledgers needed a merge driver: a shared
95
+ file touched by every concurrent branch conflicts every time, and the conflict is always resolved
96
+ by keeping both.
97
+ """
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: cut-release
3
+ description: >-
4
+ Cut, tag and ship a release from the active candidate — gates, version bump, changelog assembly,
5
+ annotated tag, the long-lived deploy branch, merge to the stable line, and opening the next
6
+ candidate — plus the hotfix and rollback flows. Use when asked to "cut / ship / tag the release",
7
+ "release v1.2.0", "bump the version", "hotfix production", "roll back the deploy", or "open the
8
+ next candidate". Executing one tracked item is /work-item; assessing readiness is /assess.
9
+ ---
10
+
11
+ # Cut a release
12
+
13
+ **Every step below is reversible except the tag and the deploy.** Know which one you are on.
14
+
15
+ ## 1. Decide the version
16
+
17
+ From the changelog fragments in `{{changelog_dir}}/`, not from memory or from what the last release
18
+ was. A breaking change in any fragment decides the major; a feature decides the minor. If the
19
+ fragments do not support the version you were asked for, **say so before continuing** — that
20
+ mismatch is usually a fragment somebody skipped, not a versioning disagreement.
21
+
22
+ ## 2. Gate
23
+
24
+ ```bash
25
+ rungs check --tier full
26
+ ```
27
+
28
+ **Do not proceed on a red gate**, and do not weaken one to get through. A release is exactly the
29
+ moment the temptation is highest and the cost of yielding is highest.
30
+
31
+ If a gate is red for reasons that predate this work, say so explicitly and get a decision. Shipping
32
+ past a known-red gate is a choice someone should make on purpose.
33
+
34
+ ## 3. Assemble the changelog
35
+
36
+ Combine the fragments into the release section, then **delete the fragments**. They are consumed,
37
+ not archived — a fragment left behind appears in the next release too.
38
+
39
+ ## 4. Bump the version
40
+
41
+ In every place it appears. `release-version-consistent` computes this rather than trusting you;
42
+ run it before tagging, because a version that disagrees with itself is discovered by a user.
43
+
44
+ ## 5. Tag and merge
45
+
46
+ - Annotated tag on the candidate, message naming the release.
47
+ - Merge the candidate into `{{stable_branch}}`.
48
+ - Cut `{{deploy_branch_prefix}}<version>` from the tag. **This is what you deploy from and what you
49
+ roll back to** — a rollback that means reverting commits on the stable line is a rollback nobody
50
+ performs correctly under pressure.
51
+
52
+ ## 6. Open the next candidate
53
+
54
+ Cut a new `{{candidate_prefix}}<next>` immediately. A period with no open candidate is a period
55
+ where work lands somewhere improvised.
56
+
57
+ ---
58
+
59
+ ## Hotfix
60
+
61
+ Branch **from the deploy branch, never from the candidate** — the candidate contains unreleased
62
+ work, and shipping it as a hotfix is how an unrelated feature reaches production during an
63
+ incident.
64
+
65
+ Fix → gate → tag a patch version → merge into `{{stable_branch}}` **and forward into the active
66
+ candidate**. The forward-merge is the step people skip, and skipping it means the next release
67
+ silently reverts the hotfix.
68
+
69
+ ## Rollback
70
+
71
+ Point the deploy at the previous `{{deploy_branch_prefix}}<version>`. Do not revert commits.
72
+
73
+ **Then record why**, as a finding at minimum. A rollback with no written cause is one the team
74
+ repeats, and the pressure of the moment is exactly why it will not be remembered otherwise.
75
+
76
+ ## What this skill will not do
77
+
78
+ - Decide *whether* to release. That is a judgement about readiness, not a procedure.
79
+ - Proceed past a red gate without an explicit decision from a person.
80
+ - Deploy. Tagging and cutting the branch is where this stops; what consumes them is your
81
+ infrastructure.
@@ -0,0 +1,28 @@
1
+ # Session archive
2
+
3
+ Closed sessions, one file each, newest first in the index generated into
4
+ [`{{path}}`](../session.md).
5
+
6
+ ## Naming
7
+
8
+ `YYYY-MM-DD_session-NN_<what-closed>-and-<what-is-next>.md`
9
+
10
+ The filename does the work. Someone scanning this directory should be able to find the session
11
+ that closed a given piece of work without opening anything — which is why the name carries both
12
+ halves, not just a number. `rungs check` refuses a name that does not.
13
+
14
+ ## What an archive note holds
15
+
16
+ - What was delivered, and what was **not** delivered that was planned
17
+ - Decisions taken, with their reasons — especially ones that became active constraints
18
+ - The handoff state at close: what the next session was told to do
19
+ - Anything that turned out to be wrong during the session, and what replaced it
20
+
21
+ ## What it is not
22
+
23
+ **An archive note is a record, not a source of truth.** It says what was believed at a moment. If
24
+ it disagrees with a spec, a decision record, or the code, those win — and the disagreement is
25
+ worth a finding, because it means something changed without its authority being updated.
26
+
27
+ **Never edit an archived note** to reflect what was learned later. Its value is that it is what
28
+ was known then.
@@ -0,0 +1,56 @@
1
+ # Session state
2
+
3
+ The live handoff. **Rewritten as work moves, not appended to.** Everything here is present tense;
4
+ history belongs in [`{{archive}}/`](archive/README.md).
5
+
6
+ Keep it short. This document is read at the start of every session, and a long one is skimmed.
7
+
8
+ ## Current objective
9
+
10
+ <!-- One or two sentences. What is being pursued right now, not the roadmap. -->
11
+
12
+ ## In progress
13
+
14
+ <!-- What is actually being worked on, with its id. Empty is a valid and honest answer. -->
15
+
16
+ ## Resume from
17
+
18
+ <!-- The single next concrete action. A path, a command, a work-item id — something a cold session
19
+ can act on without reconstructing context. This is the section that earns the document. -->
20
+
21
+ ## Up next
22
+
23
+ 1.
24
+
25
+ ## Active constraints — do not reopen
26
+
27
+ <!-- Decisions already settled that a fresh session would otherwise relitigate, each with why it
28
+ is closed. Name them specifically: "the storage boundary is fixed by ADR-0007, do not
29
+ re-scope it" beats "architecture is settled".
30
+
31
+ This is the section that pays for the document. Without it every session re-derives, and
32
+ re-derivation reaches a different answer often enough to matter. -->
33
+
34
+ -
35
+
36
+ ## Working assumptions
37
+
38
+ <!-- Things believed true and not verified this session. Being wrong here is normal; the value is
39
+ that the next session knows which claims to distrust first. -->
40
+
41
+ -
42
+
43
+ ## Open questions
44
+
45
+ <!-- Blocking and non-blocking, marked. "None blocking" is a useful thing to state. -->
46
+
47
+ -
48
+
49
+ ## Archive
50
+
51
+ <!-- rungs:begin session-archive -->
52
+ <!-- Generated by `rungs render` from {{archive}}/. The link above is relative and assumes
53
+ the archive sits beside this file, which is the default. A relative link between two
54
+ parameterised paths cannot be computed without logic, and ADR-0003 has none — so moving
55
+ one without the other is a divergence `rungs check` will report as a broken link. -->
56
+ <!-- rungs:end session-archive -->
@@ -0,0 +1,6 @@
1
+ <!-- rungs:begin session@1.0.0 -->
2
+ ## Session state
3
+
4
+ Read [`{{path}}`]({{path}}) first. Treat **Active constraints — do not reopen** as binding:
5
+ settled decisions, not suggestions. Close a session with **`/close-session`**.
6
+ <!-- rungs:end session -->
@@ -0,0 +1,63 @@
1
+ # Gate table for the `session` module.
2
+
3
+ # ── session-sections-present ──────────────────────────────────────────────────
4
+ #
5
+ # Presence, not content. A gate cannot judge whether a resume point is useful, and one that tried
6
+ # would be the confidently-wrong probe. An empty section under its heading is allowed — an honest
7
+ # "nothing in progress" is a real answer, and demanding prose would produce filler.
8
+
9
+ [sections]
10
+ file = "{{path}}"
11
+ required = [
12
+ "Current objective",
13
+ "In progress",
14
+ "Resume from",
15
+ "Up next",
16
+ "Active constraints — do not reopen",
17
+ "Working assumptions",
18
+ "Open questions",
19
+ ]
20
+ non_empty = false
21
+ message = """
22
+ {file} is missing `{section}`. The fixed section list is the point: a handoff that drops
23
+ "Active constraints" is one where the next session reopens a settled question.
24
+ """
25
+
26
+ # ── session-archive-named ─────────────────────────────────────────────────────
27
+
28
+ [filename_schema]
29
+ scan = ["{{archive}}/*.md"]
30
+ exclude = ["{{archive}}/README.md"]
31
+ pattern = "^\\d{4}-\\d{2}-\\d{2}_session-\\d+_[a-z0-9-]+\\.md$"
32
+ message = """
33
+ `{file}` does not name what closed and what came next. Use
34
+ YYYY-MM-DD_session-NN_<what-closed>-and-<what-is-next>.md — the filename is what makes the
35
+ directory scannable without opening anything.
36
+ """
37
+
38
+ # ── self-tests ────────────────────────────────────────────────────────────────
39
+
40
+ [[self_test]]
41
+ gate = "session-sections-present"
42
+ expect = "fail"
43
+ fixture = { sections = ["Current objective", "In progress", "Resume from"] }
44
+
45
+ [[self_test]]
46
+ gate = "session-sections-present"
47
+ expect = "pass"
48
+ fixture = { sections = ["Current objective", "In progress", "Resume from", "Up next", "Active constraints — do not reopen", "Working assumptions", "Open questions"] }
49
+
50
+ [[self_test]]
51
+ gate = "session-archive-named"
52
+ expect = "fail"
53
+ input = "{{archive}}/session-4.md"
54
+
55
+ [[self_test]]
56
+ gate = "session-archive-named"
57
+ expect = "pass"
58
+ input = "{{archive}}/2026-08-14_session-04_storage-closeout-and-api-handoff.md"
59
+
60
+ [[self_test]]
61
+ gate = "session-archive-named"
62
+ expect = "pass"
63
+ input = "{{archive}}/README.md"
@@ -0,0 +1,72 @@
1
+ [module]
2
+ name = "session"
3
+ version = "1.0.0"
4
+ rung = 1
5
+ summary = "A handoff document carrying state between sessions: resume point, active constraints, and a dated archive."
6
+
7
+ [requires]
8
+ modules = []
9
+
10
+ [conflicts]
11
+ modules = []
12
+
13
+ # A `mode = file | board` parameter was specified in the catalog and dropped on
14
+ # authoring. "Board mode" meant deriving session state from the backlog and
15
+ # creating no file — which is not a mode, it is **not installing this module**.
16
+ # A parameter whose value is "do nothing" is the absence of the module, and
17
+ # substitution-only templating cannot express it anyway.
18
+
19
+ [params.path]
20
+ description = "The live handoff document"
21
+ default = ".ai/session.md"
22
+
23
+ [params.archive]
24
+ description = "Where closed sessions are kept"
25
+ default = ".ai/archive"
26
+
27
+ [[gates]]
28
+ id = "session-sections-present"
29
+ kind = "declared"
30
+ engine = "frontmatter-schema"
31
+ table = "gates/session.toml"
32
+ tier = "fast"
33
+ why = """
34
+ The sections are the value. A handoff missing "Active constraints" is one where the next session
35
+ reopens a settled question, which is the specific failure this document exists to prevent.
36
+ """
37
+
38
+ [[gates]]
39
+ id = "session-archive-named"
40
+ kind = "declared"
41
+ engine = "filename-schema"
42
+ table = "gates/session.toml"
43
+ tier = "fast"
44
+ why = "An archive whose filenames do not say what closed and what came next is a folder nobody opens."
45
+
46
+ [skills.close-session]
47
+ extensions = { disable-model-invocation = true }
48
+ extension_note = """
49
+ Closing a session archives the live document and rewrites it. A model should not decide the session
50
+ is over. Opting in costs packaging for claude.ai uploads and the Skills API; it stays portable to
51
+ every skills-compatible CLI.
52
+ """
53
+
54
+ [detect]
55
+ paths = [
56
+ ".ai/session.md",
57
+ ".ai/context/session.md",
58
+ "docs/**/session.md",
59
+ ".ai/context/archive/**/*.md",
60
+ ]
61
+ markers = ["rungs:begin session"]
62
+
63
+ [provenance]
64
+ sources = ["axiom-mesh"]
65
+ patterns = ["session-handoff", "settled-decisions-lock", "dated-session-archive"]
66
+ incident = """
67
+ axiom-mesh kept 21 dated handoff notes and a live session document with fixed sections, and its
68
+ resume state is genuinely readable cold — the only repo of the four where a new session can start
69
+ without reconstructing context from git. The section that earns its keep is "Active constraints /
70
+ decisions since last archive", whose wording is a standing instruction not to reopen settled
71
+ questions: "do not reopen helper ownership, topology, host-boundary … unless explicitly re-planned."
72
+ """