@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,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: close-session
|
|
3
|
+
description: >-
|
|
4
|
+
Close out a working session: write the dated archive note, reset the live handoff document to a
|
|
5
|
+
cold-readable resume point, and record what became an active constraint. Use when asked to
|
|
6
|
+
"close the session", "wrap up", "archive this session", "hand off", "write the handoff", or at
|
|
7
|
+
the end of a long stretch of work before context is lost. Recording an out-of-scope observation
|
|
8
|
+
is /record-finding; finishing a tracked item is /work-item.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Close a session
|
|
12
|
+
|
|
13
|
+
The test for everything below: **could a session starting cold tomorrow, with no memory of this
|
|
14
|
+
conversation, pick up from the document alone?** If not, it is not closed.
|
|
15
|
+
|
|
16
|
+
## 1. Write the archive note
|
|
17
|
+
|
|
18
|
+
`{{archive}}/YYYY-MM-DD_session-NN_<what-closed>-and-<what-is-next>.md`. The filename carries both
|
|
19
|
+
halves so the directory is scannable without opening anything.
|
|
20
|
+
|
|
21
|
+
Contents:
|
|
22
|
+
|
|
23
|
+
- **Delivered** — what actually landed, with ids.
|
|
24
|
+
- **Not delivered** — what was planned and did not happen, **and why**. This is the half people
|
|
25
|
+
skip, and it is the half that stops the next session re-planning the same thing.
|
|
26
|
+
- **Decisions taken**, with reasons. Flag any that should become an active constraint.
|
|
27
|
+
- **What turned out to be wrong** during the session, and what replaced it.
|
|
28
|
+
|
|
29
|
+
Write what was believed at close. **Never revise an archived note later** — its value is that it
|
|
30
|
+
records what was known then.
|
|
31
|
+
|
|
32
|
+
## 2. Rewrite the live document
|
|
33
|
+
|
|
34
|
+
`{{path}}` is **rewritten, not appended to.** Delete what is no longer true rather than layering
|
|
35
|
+
qualifications on it.
|
|
36
|
+
|
|
37
|
+
- **Resume from** — one concrete next action. A path, a command, an id. Not "continue the
|
|
38
|
+
refactor"; something that can be executed.
|
|
39
|
+
- **Active constraints** — move any decision here that a fresh session would otherwise relitigate,
|
|
40
|
+
each with why it is closed. Be specific: *"the storage boundary is fixed by ADR-0007, do not
|
|
41
|
+
re-scope it"* beats *"architecture is settled"*. **This section is what pays for the document.**
|
|
42
|
+
- **Working assumptions** — what is believed but unverified. Being wrong here is expected; the
|
|
43
|
+
value is telling the next session which claims to distrust first.
|
|
44
|
+
- **Open questions** — mark which block progress. "None blocking" is worth stating.
|
|
45
|
+
|
|
46
|
+
## 3. Check it against the archive
|
|
47
|
+
|
|
48
|
+
The live document should now say nothing the archive note repeats. If it does, one of them is
|
|
49
|
+
wrong later: **the live document is present tense, the archive is past tense**, and a fact in both
|
|
50
|
+
will drift in one.
|
|
51
|
+
|
|
52
|
+
## 4. What not to do
|
|
53
|
+
|
|
54
|
+
- **Do not close a session to avoid finishing something.** If work is half-done, say so in
|
|
55
|
+
`In progress` with what remains — a handoff that hides a loose end is worse than no handoff.
|
|
56
|
+
- **Do not summarise the conversation.** The archive records outcomes and decisions, not a
|
|
57
|
+
narrative. Nobody reads a transcript.
|
|
58
|
+
- **Do not promote an assumption to a constraint** because it went unchallenged. A constraint is
|
|
59
|
+
something decided; an assumption is something believed.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<!-- rungs:begin skills@1.0.0 -->
|
|
2
|
+
## Skills
|
|
3
|
+
|
|
4
|
+
Multi-step procedures live in [`{{dir}}/`]({{dir}}/) and load only when used. Authoring rules are
|
|
5
|
+
in [`.ai/rules/`](.ai/rules/README.md): six spec frontmatter fields and no more, a description that
|
|
6
|
+
lists the phrases people actually say, and **every skill names its neighbours** — the failure past a
|
|
7
|
+
dozen skills is the wrong one firing and running to completion.
|
|
8
|
+
<!-- rungs:end skills -->
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Gate table for the `skills` module.
|
|
2
|
+
#
|
|
3
|
+
# Moved here from `gates/structural.toml`: what a SKILL.md may contain is knowledge about skills,
|
|
4
|
+
# not a repo-agnostic structural fact. `gates` contributes the frontmatter-schema engine.
|
|
5
|
+
|
|
6
|
+
# ── skills-spec-pure ──────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
[[frontmatter_schema]]
|
|
9
|
+
id = "skills"
|
|
10
|
+
scan = ["{{dir}}/**/SKILL.md"]
|
|
11
|
+
required = ["name", "description"]
|
|
12
|
+
allowed = ["name", "description", "license", "compatibility", "metadata", "allowed-tools"]
|
|
13
|
+
# Extensions are legal only when the owning module opted in. The gate reads that manifest rather
|
|
14
|
+
# than a repo-local allowlist, so the portability cost stays attached to the decision.
|
|
15
|
+
extensions_allowed_from = "module.toml:skills.<name>.extensions"
|
|
16
|
+
message = """
|
|
17
|
+
`{file}` uses non-spec frontmatter key `{key}`. claude.ai uploads and the Skills API reject unknown
|
|
18
|
+
keys with a hard error rather than ignoring them. If this is deliberate, opt in from the owning
|
|
19
|
+
module's manifest, where the portability cost is recorded next to the choice.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# ── skills-description-routes ─────────────────────────────────────────────────
|
|
23
|
+
#
|
|
24
|
+
# Structure, not quality. A gate cannot judge whether a description is *good*; it can tell that it
|
|
25
|
+
# is a single clause with no trigger vocabulary, which is the shape that never fires.
|
|
26
|
+
|
|
27
|
+
[[frontmatter_schema.field_shape]]
|
|
28
|
+
field = "description"
|
|
29
|
+
min_words = 20
|
|
30
|
+
requires_any = ["Use when", "Use for", "Use whenever", "Trigger"]
|
|
31
|
+
message = """
|
|
32
|
+
`{file}`'s description does not say when to use it. At startup an agent loads only the name and
|
|
33
|
+
description, so this is the entire routing surface — lead with the use case and list the phrases
|
|
34
|
+
people actually say.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# ── skills-names-neighbours ───────────────────────────────────────────────────
|
|
38
|
+
#
|
|
39
|
+
# Only fires past a threshold: in a repo with three skills there is nothing to disambiguate, and
|
|
40
|
+
# demanding cross-references there is the checklist-as-noise failure.
|
|
41
|
+
|
|
42
|
+
[cross_reference]
|
|
43
|
+
scan = ["{{dir}}/**/SKILL.md"]
|
|
44
|
+
field = "description"
|
|
45
|
+
min_skills = 6
|
|
46
|
+
requires_mention_of_peer = true
|
|
47
|
+
exempt_marker = "no-neighbours-ok:" # ignored unless it states a reason
|
|
48
|
+
message = """
|
|
49
|
+
`{file}` names no neighbouring skill. With {count} skills in this repo, the failure mode is no
|
|
50
|
+
longer "no skill fires" but "a plausible-but-wrong skill fires and runs to completion". Name the
|
|
51
|
+
adjacent skills and the boundary, in one clause each.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
# ── self-tests ────────────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
[[self_test]]
|
|
57
|
+
gate = "skills-spec-pure"
|
|
58
|
+
expect = "fail"
|
|
59
|
+
fixture = { frontmatter = { name = "x", description = "d", "argument-hint" = "y" }, opted_in = [] }
|
|
60
|
+
|
|
61
|
+
[[self_test]]
|
|
62
|
+
gate = "skills-spec-pure"
|
|
63
|
+
expect = "pass"
|
|
64
|
+
fixture = { frontmatter = { name = "x", description = "d", "disable-model-invocation" = true }, opted_in = ["disable-model-invocation"] }
|
|
65
|
+
|
|
66
|
+
[[self_test]]
|
|
67
|
+
gate = "skills-spec-pure"
|
|
68
|
+
expect = "fail"
|
|
69
|
+
fixture = { frontmatter = { name = "x", description = "d", "disable-model-invocation" = true }, opted_in = [] }
|
|
70
|
+
|
|
71
|
+
[[self_test]]
|
|
72
|
+
gate = "skills-description-routes"
|
|
73
|
+
expect = "fail"
|
|
74
|
+
fixture = { description = "Executes the release process." }
|
|
75
|
+
|
|
76
|
+
[[self_test]]
|
|
77
|
+
gate = "skills-description-routes"
|
|
78
|
+
expect = "pass"
|
|
79
|
+
fixture = { description = "Cut, tag and ship a release from the active candidate. Use when asked to cut the release, tag a version, hotfix production, or roll back a deploy." }
|
|
80
|
+
|
|
81
|
+
[[self_test]]
|
|
82
|
+
gate = "skills-names-neighbours"
|
|
83
|
+
expect = "pass"
|
|
84
|
+
fixture = { skill_count = 3, mentions_peer = false }
|
|
85
|
+
|
|
86
|
+
[[self_test]]
|
|
87
|
+
gate = "skills-names-neighbours"
|
|
88
|
+
expect = "fail"
|
|
89
|
+
fixture = { skill_count = 9, mentions_peer = false }
|
|
90
|
+
|
|
91
|
+
[[self_test]]
|
|
92
|
+
gate = "skills-names-neighbours"
|
|
93
|
+
expect = "pass"
|
|
94
|
+
fixture = { skill_count = 9, mentions_peer = false, exempt = "no-neighbours-ok: the only skill touching deployment" }
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
[module]
|
|
2
|
+
name = "skills"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
rung = 2
|
|
5
|
+
summary = "Skill-authoring discipline and the gates that hold it: spec-pure frontmatter, descriptions that route, neighbours named, and output that lands somewhere."
|
|
6
|
+
|
|
7
|
+
[requires]
|
|
8
|
+
modules = ["instructions"]
|
|
9
|
+
|
|
10
|
+
[conflicts]
|
|
11
|
+
modules = []
|
|
12
|
+
|
|
13
|
+
# This is not "the ability to have skills" — every module ships skills through
|
|
14
|
+
# its own skills/ directory. This module ships the discipline for writing good
|
|
15
|
+
# ones, and it is where the SKILL.md frontmatter schema lives, because knowing
|
|
16
|
+
# what a skill may contain is knowledge about skills rather than a repo-agnostic
|
|
17
|
+
# structural fact.
|
|
18
|
+
|
|
19
|
+
[params.dir]
|
|
20
|
+
description = """
|
|
21
|
+
Where skills live. Resolved from the harness set (ADR-0001): `.claude/skills/` reaches Claude Code,
|
|
22
|
+
Copilot and Cursor today; `.agents/skills/` is the vendor-neutral direction and is not read by
|
|
23
|
+
Claude Code.
|
|
24
|
+
"""
|
|
25
|
+
default = ".claude/skills"
|
|
26
|
+
allowed = [".claude/skills", ".agents/skills"]
|
|
27
|
+
consumed_by = "render"
|
|
28
|
+
|
|
29
|
+
[[gates]]
|
|
30
|
+
id = "skills-spec-pure"
|
|
31
|
+
kind = "declared"
|
|
32
|
+
engine = "frontmatter-schema"
|
|
33
|
+
table = "gates/skills.toml"
|
|
34
|
+
tier = "fast"
|
|
35
|
+
why = """
|
|
36
|
+
The Agent Skills spec allows six frontmatter fields. Anything else is a harness extension, and
|
|
37
|
+
claude.ai uploads and the Skills API reject unknown keys with a hard error rather than ignoring
|
|
38
|
+
them. An extension must be opted into in the owning module's manifest, where its portability cost
|
|
39
|
+
is stated at the point of the choice.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
[[gates]]
|
|
43
|
+
id = "skills-description-routes"
|
|
44
|
+
kind = "declared"
|
|
45
|
+
engine = "frontmatter-schema"
|
|
46
|
+
table = "gates/skills.toml"
|
|
47
|
+
tier = "fast"
|
|
48
|
+
why = """
|
|
49
|
+
A description is the entire routing surface — at startup an agent loads only the name and
|
|
50
|
+
description, so a vague one is the single most common reason a skill never fires. It has to name
|
|
51
|
+
what the skill does and the phrases people actually say.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
[[gates]]
|
|
55
|
+
id = "skills-names-neighbours"
|
|
56
|
+
kind = "declared"
|
|
57
|
+
engine = "cross-reference"
|
|
58
|
+
table = "gates/skills.toml"
|
|
59
|
+
tier = "fast"
|
|
60
|
+
why = """
|
|
61
|
+
Past about a dozen skills with overlapping vocabulary the failure stops being "no skill fires" and
|
|
62
|
+
becomes "a plausible-but-wrong skill fires and runs to completion". Every skill naming its
|
|
63
|
+
neighbours makes each one a correction surface.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
[detect]
|
|
67
|
+
paths = [".claude/skills/**/SKILL.md", ".agents/skills/**/SKILL.md", ".github/prompts/*.prompt.md", ".ai/prompts/*.md"]
|
|
68
|
+
markers = ["rungs:begin skills"]
|
|
69
|
+
|
|
70
|
+
[[detect.adopt_as]]
|
|
71
|
+
kind = "prompt-library"
|
|
72
|
+
paths = [".github/prompts/*.prompt.md", ".ai/prompts/*.md"]
|
|
73
|
+
note = """
|
|
74
|
+
A markdown prompt library is a skill set without an invocation surface. Adopting means proposing a
|
|
75
|
+
conversion — frontmatter added, one directory per skill — and it is worth doing: the format is now
|
|
76
|
+
an open standard, so the portability objection that justified staying with plain prompts is gone.
|
|
77
|
+
`add` proposes; it never rewrites a prompt in place.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
[provenance]
|
|
81
|
+
sources = ["rift-forge", "axiom-mesh", "hexguard"]
|
|
82
|
+
patterns = ["invocable-procedure", "skill-neighbours", "prompt-writes-artifact", "operating-skills", "prompt-index-routing"]
|
|
83
|
+
incident = """
|
|
84
|
+
axiom-mesh wrote 21 prompt playbooks, several with no equivalent anywhere else, and gave them no
|
|
85
|
+
invocation surface at all — they must be found, opened and pasted. hexguard-templates shipped the
|
|
86
|
+
best decision procedure in the corpus behind a placeholder table. rift-forge, at 13 skills, had to
|
|
87
|
+
invent the neighbour-naming convention because descriptions alone had stopped disambiguating
|
|
88
|
+
/curate-mechanic from /curate-champion from /patch-ingest.
|
|
89
|
+
"""
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: >-
|
|
3
|
+
How to write a skill that fires when it should, does not fire when it should not, and leaves
|
|
4
|
+
something behind. Loads when creating or editing any SKILL.md.
|
|
5
|
+
paths:
|
|
6
|
+
- "{{dir}}/**/SKILL.md"
|
|
7
|
+
enforcement: gated
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Writing a skill
|
|
11
|
+
|
|
12
|
+
## What earns a skill
|
|
13
|
+
|
|
14
|
+
A **multi-step procedure with a beginning and an end**. Not a fact — that is the entry document.
|
|
15
|
+
Not a rule about a surface — that is a path-scoped rule. A skill's body loads only when it is used,
|
|
16
|
+
so long reference material costs nothing until needed; a fact that belongs in every session costs
|
|
17
|
+
the full context every session and belongs elsewhere.
|
|
18
|
+
|
|
19
|
+
## Frontmatter: six fields, and that is the list
|
|
20
|
+
|
|
21
|
+
`name` · `description` · `license` · `compatibility` · `metadata` · `allowed-tools`.
|
|
22
|
+
|
|
23
|
+
Anything else is a harness extension. claude.ai uploads and the Skills API **reject** unknown keys
|
|
24
|
+
with a hard error rather than ignoring them, so an extension is opted into in the owning module's
|
|
25
|
+
manifest, where the cost is stated at the point of the choice — not added here quietly.
|
|
26
|
+
|
|
27
|
+
The two that matter:
|
|
28
|
+
|
|
29
|
+
- **`disable-model-invocation: true`** for anything with side effects — releases, deploys, sends,
|
|
30
|
+
merges. You do not want a model deciding that now is the moment to ship.
|
|
31
|
+
- **`user-invocable: false`** for background knowledge that is not an action anyone would type.
|
|
32
|
+
|
|
33
|
+
## The description is the whole routing surface
|
|
34
|
+
|
|
35
|
+
At startup an agent loads **only the name and description**. A vague description is the single most
|
|
36
|
+
common reason a skill never fires.
|
|
37
|
+
|
|
38
|
+
Write it in the third person. Lead with the use case, then **list the phrases people actually
|
|
39
|
+
say** — the exact words, not a paraphrase of the capability.
|
|
40
|
+
|
|
41
|
+
> *Execute a backlog work item end to end… Use when asked to "work on / tackle / start / ship
|
|
42
|
+
> WI-###", to pick up the next item, or to work an epic's sub-items.*
|
|
43
|
+
|
|
44
|
+
## Name your neighbours
|
|
45
|
+
|
|
46
|
+
**Every skill names the adjacent skills and the boundary between them**, in the description, in one
|
|
47
|
+
clause each.
|
|
48
|
+
|
|
49
|
+
The failure this fixes is not "no skill fires". It is **a plausible-but-wrong skill firing and
|
|
50
|
+
running to completion** — which is worse, because it produces confident output against the wrong
|
|
51
|
+
procedure. Naming neighbours makes every skill a correction surface:
|
|
52
|
+
|
|
53
|
+
> *One mechanic across many subjects is /curate-mechanic; a whole new dataset is /patch-ingest;
|
|
54
|
+
> this skill is one subject, whole kit.*
|
|
55
|
+
|
|
56
|
+
## Land something durable
|
|
57
|
+
|
|
58
|
+
A skill that only produces conversation evaporates. **Say in the body where the output goes** — a
|
|
59
|
+
register row, a document at a known path, a status field. This is what makes a skill set compound
|
|
60
|
+
instead of being re-derived every session.
|
|
61
|
+
|
|
62
|
+
## Write standing instructions, not one-time steps
|
|
63
|
+
|
|
64
|
+
The rendered body enters the conversation once and stays for the session; it is not re-read on
|
|
65
|
+
later turns. Guidance that should apply throughout a task must read as a standing rule, not as
|
|
66
|
+
step 4.
|
|
67
|
+
|
|
68
|
+
## Say what the skill cannot do
|
|
69
|
+
|
|
70
|
+
Near the end, briefly. The limits are what stop it being used for the adjacent problem it almost
|
|
71
|
+
fits — which is the same failure as the wrong skill firing, arriving one step later.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
**Authoritative for:** spec conventions: ids, per-story status, the mandatory scope section, and how to propose new cross-cutting scope.
|
|
2
|
+
**Not authoritative for:** the behaviour of any surface, which each spec owns, and what is currently being built, which is the backlog.
|
|
3
|
+
|
|
4
|
+
# Specs
|
|
5
|
+
|
|
6
|
+
What each surface is supposed to do. **Specs describe behaviour; they do not track work** — if you
|
|
7
|
+
need to know what is happening right now, that is the backlog, not here.
|
|
8
|
+
|
|
9
|
+
## Index
|
|
10
|
+
|
|
11
|
+
<!-- rungs:begin specs-index -->
|
|
12
|
+
<!-- Generated by `rungs render`. -->
|
|
13
|
+
|
|
14
|
+
| Spec | Scope | Status | File |
|
|
15
|
+
| --- | --- | --- | --- |
|
|
16
|
+
| — | | | |
|
|
17
|
+
|
|
18
|
+
<!-- rungs:end specs-index -->
|
|
19
|
+
|
|
20
|
+
## Conventions
|
|
21
|
+
|
|
22
|
+
- **Frontmatter on every spec:** `id`, `type: spec`, `scope`, `status`, `created`, `updated`.
|
|
23
|
+
- **Stable ids** per feature (`{{feature_format}}`) and per story (`{{story_format}}`). Cite these
|
|
24
|
+
from commits, pull requests and work items so work traces back to the spec that justified it.
|
|
25
|
+
- **Status is per feature and story, not per file:** ✅ implemented · 🟡 partial · ⬜ planned.
|
|
26
|
+
Fine-grained integration detail belongs in the linked plan or work item; the spec only has to be
|
|
27
|
+
accurate at the feature level.
|
|
28
|
+
- **The Scope section is mandatory**, and its out-of-scope half is the point: it is what stops an
|
|
29
|
+
agent silently expanding a surface while implementing an unrelated story.
|
|
30
|
+
- **One file per surface.** Split into `spec.md` + `stories/` only past ~{{split_lines}} lines —
|
|
31
|
+
do not pre-split. Structure created ahead of content is structure nobody fills.
|
|
32
|
+
|
|
33
|
+
## Demo is not done
|
|
34
|
+
|
|
35
|
+
An endpoint, screen or flow that exists to **showcase** a capability in isolation is 🟡 with an
|
|
36
|
+
explicit note. **Never ✅.**
|
|
37
|
+
|
|
38
|
+
A spec that overclaims integration is worse than no spec, because it is read as verified — by the
|
|
39
|
+
next contributor, by a plan that cites it, and by an agent deciding a story is already handled.
|
|
40
|
+
`rungs check` refuses a ✅ story that does not name what closed it.
|
|
41
|
+
|
|
42
|
+
## Proposing new cross-cutting scope
|
|
43
|
+
|
|
44
|
+
Written down so a fresh contributor does not have to reverse-engineer it from examples:
|
|
45
|
+
|
|
46
|
+
1. **Find the surface that owns it.** If two surfaces both plausibly own it, that is the thing to
|
|
47
|
+
resolve first — not by picking one, but by naming the boundary.
|
|
48
|
+
2. **Claim the next feature id** under that surface's prefix.
|
|
49
|
+
3. **State the mechanism once**, in the owning spec, and have other specs reference it. A
|
|
50
|
+
mechanism described in two specs will diverge, and the second copy always reads as authoritative
|
|
51
|
+
to whoever finds it first.
|
|
52
|
+
4. **Name a reference consumer.** A cross-cutting feature with no consumer is a design, not a
|
|
53
|
+
requirement.
|
|
54
|
+
5. **Fill Non-goals and Open questions.** An empty Open questions section on a new proposal is
|
|
55
|
+
almost always untrue.
|
|
56
|
+
|
|
57
|
+
## Status legend
|
|
58
|
+
|
|
59
|
+
| | |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| ✅ | Implemented and integrated. Names the commit, pull request or work item that closed it |
|
|
62
|
+
| 🟡 | Partial. **Carries a note saying what is missing** — a bare 🟡 is a guess |
|
|
63
|
+
| ⬜ | Planned. Specified, not built |
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: <PREFIX>
|
|
3
|
+
type: spec
|
|
4
|
+
scope: <what surface this covers>
|
|
5
|
+
status: draft # draft | stable | superseded
|
|
6
|
+
created: YYYY-MM-DD
|
|
7
|
+
updated: YYYY-MM-DD
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# <Surface> — specification
|
|
11
|
+
|
|
12
|
+
## Purpose
|
|
13
|
+
|
|
14
|
+
What this surface is for, in two or three sentences. The property that must not break.
|
|
15
|
+
|
|
16
|
+
## Scope
|
|
17
|
+
|
|
18
|
+
**In scope**
|
|
19
|
+
|
|
20
|
+
-
|
|
21
|
+
|
|
22
|
+
**Out of scope**
|
|
23
|
+
|
|
24
|
+
- **Required — answer, do not delete.** What this surface explicitly does *not* cover, and where
|
|
25
|
+
that lives instead if it lives anywhere.
|
|
26
|
+
>
|
|
27
|
+
> This half is why the section is mandatory. Without it, "the spec did not say I could not" is a
|
|
28
|
+
> defensible reading, and surfaces grow sideways during unrelated work.
|
|
29
|
+
|
|
30
|
+
## Reference implementation
|
|
31
|
+
|
|
32
|
+
<!-- The directory that demonstrates the correct pattern. Cheaper than prose and it cannot drift
|
|
33
|
+
from the code, because it *is* the code. -->
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
### <PREFIX>-F01 — <name>
|
|
38
|
+
|
|
39
|
+
**Status:** ⬜
|
|
40
|
+
|
|
41
|
+
<What it does. The mechanism, stated once — other specs reference this rather than restating it.>
|
|
42
|
+
|
|
43
|
+
#### Stories
|
|
44
|
+
|
|
45
|
+
| Id | Story | Status | Closed by |
|
|
46
|
+
| --- | --- | --- | --- |
|
|
47
|
+
| `<PREFIX>-US-001` | As a …, I can … | ⬜ | |
|
|
48
|
+
|
|
49
|
+
**Acceptance criteria**
|
|
50
|
+
|
|
51
|
+
- Checkable statements. "Given X, the endpoint returns 422 with a typed error", not "handles
|
|
52
|
+
errors properly".
|
|
53
|
+
|
|
54
|
+
## Non-goals
|
|
55
|
+
|
|
56
|
+
Things a reader might reasonably expect here and will not find, **with the reason**. A non-goal
|
|
57
|
+
without a reason reads as an oversight and gets proposed again.
|
|
58
|
+
|
|
59
|
+
## Open questions
|
|
60
|
+
|
|
61
|
+
Unresolved, with who or what would resolve them. An empty section on a new spec is almost always
|
|
62
|
+
untrue — say "none known" deliberately if you mean it.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<!-- rungs:begin specs@1.0.0 -->
|
|
2
|
+
## Specs
|
|
3
|
+
|
|
4
|
+
Behaviour is specified in [`{{path}}/`]({{path}}/README.md) with stable feature and story ids —
|
|
5
|
+
cite them from commits and work items. Specs say what is *in scope*; they do not track what is
|
|
6
|
+
happening. **Demo is not done**: a showcase is 🟡 with a note, never ✅, and a ✅ story must name
|
|
7
|
+
what closed it.
|
|
8
|
+
<!-- rungs:end specs -->
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Gate table for the `specs` module.
|
|
2
|
+
|
|
3
|
+
# ── specs-status-evidence ─────────────────────────────────────────────────────
|
|
4
|
+
#
|
|
5
|
+
# The "demo is not done" gate. A story claiming completion must name what closed it; a partial
|
|
6
|
+
# story must say what is missing. Presence, never quality — a gate cannot judge whether the named
|
|
7
|
+
# commit actually implemented the story, and one that tried would be confidently wrong.
|
|
8
|
+
|
|
9
|
+
[register_schema]
|
|
10
|
+
scan = ["{{path}}/**/*.md"]
|
|
11
|
+
table_columns = ["Id", "Story", "Status", "Closed by"]
|
|
12
|
+
enum = { Status = ["✅", "🟡", "⬜"] }
|
|
13
|
+
|
|
14
|
+
[[register_schema.conditional]]
|
|
15
|
+
when = { Status = "✅" }
|
|
16
|
+
non_empty = ["Closed by"]
|
|
17
|
+
message = """
|
|
18
|
+
Story {id} is marked ✅ with nothing in `Closed by`. A spec that overclaims integration is read as
|
|
19
|
+
verified — by the next contributor, by a plan citing it, and by an agent deciding the story is
|
|
20
|
+
already handled. Name the commit, pull request or work item, or mark it 🟡 with what is missing.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
[[register_schema.conditional]]
|
|
24
|
+
when = { Status = "🟡" }
|
|
25
|
+
requires_note = true
|
|
26
|
+
message = "Story {id} is 🟡 with no note. A bare partial is a guess; say what is missing."
|
|
27
|
+
|
|
28
|
+
# ── specs-scope-section ───────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
[sections]
|
|
31
|
+
scan = ["{{path}}/**/*.md"]
|
|
32
|
+
exclude = ["{{path}}/README.md", "{{path}}/TEMPLATE.md"]
|
|
33
|
+
required = ["Purpose", "Scope"]
|
|
34
|
+
non_empty = true
|
|
35
|
+
# The out-of-scope half specifically, not just the heading.
|
|
36
|
+
required_subsections = { Scope = ["Out of scope"] }
|
|
37
|
+
message = """
|
|
38
|
+
{file} has no non-empty `Out of scope`. Without it, "the spec did not say I could not" is a
|
|
39
|
+
defensible reading, and the surface grows sideways during unrelated work.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
# ── specs-ids-unique ──────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
[kinds.feature]
|
|
45
|
+
format = "[A-Z]{2,8}-F\\d{2}"
|
|
46
|
+
sources = ["{{path}}/**/*.md"]
|
|
47
|
+
|
|
48
|
+
[kinds.story]
|
|
49
|
+
format = "[A-Z]{2,8}-US-\\d{1,4}"
|
|
50
|
+
sources = ["{{path}}/**/*.md"]
|
|
51
|
+
|
|
52
|
+
[citations]
|
|
53
|
+
roots = ["docs", ".ai", "AGENTS.md"]
|
|
54
|
+
extensions = [".md"]
|
|
55
|
+
|
|
56
|
+
# ── specs-split-threshold ─────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
[file_budget]
|
|
59
|
+
scan = ["{{path}}/**/*.md"]
|
|
60
|
+
exclude = ["{{path}}/README.md"]
|
|
61
|
+
max_lines = {{split_lines}}
|
|
62
|
+
warn_at = 0.85
|
|
63
|
+
message = """
|
|
64
|
+
{file} is past {{split_lines}} lines. Split it into spec.md + stories/ — a spec nobody reads to
|
|
65
|
+
the end is one whose later sections collect unverified claims. Do not pre-split anything else.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
# ── self-tests ────────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
[[self_test]]
|
|
71
|
+
gate = "specs-status-evidence"
|
|
72
|
+
expect = "fail"
|
|
73
|
+
fixture = { row = { Id = "X-US-001", Status = "✅", "Closed by" = "" } }
|
|
74
|
+
|
|
75
|
+
[[self_test]]
|
|
76
|
+
gate = "specs-status-evidence"
|
|
77
|
+
expect = "pass"
|
|
78
|
+
fixture = { row = { Id = "X-US-001", Status = "✅", "Closed by" = "WI-042" } }
|
|
79
|
+
|
|
80
|
+
[[self_test]]
|
|
81
|
+
gate = "specs-status-evidence"
|
|
82
|
+
expect = "fail"
|
|
83
|
+
fixture = { row = { Id = "X-US-002", Status = "🟡" }, note = "" }
|
|
84
|
+
|
|
85
|
+
[[self_test]]
|
|
86
|
+
gate = "specs-scope-section"
|
|
87
|
+
expect = "fail"
|
|
88
|
+
fixture = { sections = ["Purpose", "Scope"], subsections = { Scope = ["In scope"] } }
|
|
89
|
+
|
|
90
|
+
[[self_test]]
|
|
91
|
+
gate = "specs-scope-section"
|
|
92
|
+
expect = "pass"
|
|
93
|
+
fixture = { sections = ["Purpose", "Scope"], subsections = { Scope = ["In scope", "Out of scope"] } }
|
|
94
|
+
|
|
95
|
+
[[self_test]]
|
|
96
|
+
gate = "specs-split-threshold"
|
|
97
|
+
expect = "fail"
|
|
98
|
+
fixture = { file = "{{path}}/a/spec.md", lines = 900 }
|
|
99
|
+
|
|
100
|
+
[[self_test]]
|
|
101
|
+
gate = "specs-split-threshold"
|
|
102
|
+
expect = "pass"
|
|
103
|
+
fixture = { file = "{{path}}/a/spec.md", lines = 400 }
|
|
104
|
+
|
|
105
|
+
# ── specs-ids-unique self-tests ───────────────────────────────────────────────
|
|
106
|
+
#
|
|
107
|
+
# Story ids are cited from commits, plans and work items, so a duplicate makes
|
|
108
|
+
# every one of those citations ambiguous — and the citation still resolves,
|
|
109
|
+
# which is why this is checked rather than left to be noticed.
|
|
110
|
+
|
|
111
|
+
[[self_test]]
|
|
112
|
+
gate = "specs-ids-unique"
|
|
113
|
+
expect = "fail"
|
|
114
|
+
fixture = { stories = ["FOUND-US-001", "FOUND-US-001"] }
|
|
115
|
+
|
|
116
|
+
[[self_test]]
|
|
117
|
+
gate = "specs-ids-unique"
|
|
118
|
+
expect = "pass"
|
|
119
|
+
fixture = { stories = ["FOUND-US-001", "AUTH-US-001"] }
|