@epicparty/skills 0.0.0-stage → 0.1.1
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/NOTICE +27 -0
- package/README.md +110 -2
- package/bin/cli.mjs +177 -0
- package/lib/transform.mjs +219 -0
- package/package.json +37 -4
- package/skills/architect/DESIGN-IT-TWICE.md +46 -0
- package/skills/architect/HTML-REPORT.md +126 -0
- package/skills/architect/SKILL.md +101 -0
- package/skills/babysit/SKILL.md +164 -0
- package/skills/code-review/SKILL.md +111 -0
- package/skills/copy/COPY.md +142 -0
- package/skills/copy/SKILL.md +87 -0
- package/skills/deliver/SKILL.md +259 -0
- package/skills/diagnose/SKILL.md +114 -0
- package/skills/diagnose/scripts/hitl-loop.template.sh +44 -0
- package/skills/explore/SKILL.md +78 -0
- package/skills/grill/SKILL.md +75 -0
- package/skills/implement/SKILL.md +132 -0
- package/skills/implement/TDD.md +131 -0
- package/skills/implement/TEST-VALUE.md +114 -0
- package/skills/outdated/SKILL.md +125 -0
- package/skills/research/SKILL.md +115 -0
- package/skills/router/BOARD.md +157 -0
- package/skills/router/DECISIONS.md +71 -0
- package/skills/router/GIT.md +70 -0
- package/skills/router/GLOSSARY-FORMAT.md +48 -0
- package/skills/router/SKILL.md +251 -0
- package/skills/setup/SKILL.md +99 -0
- package/skills/ship/SKILL.md +132 -0
- package/skills/to-pr/SKILL.md +88 -0
- package/skills/to-story/SEAMS.md +51 -0
- package/skills/to-story/SKILL.md +79 -0
- package/skills/to-story/SPLITTING.md +53 -0
- package/skills/to-story/STORIES.md +179 -0
- package/skills/triage/SKILL.md +179 -0
- package/skills/upgrade/SKILL.md +139 -0
- package/skills/writing-for-agents/SKILL-MECHANICS.md +24 -0
- package/skills/writing-for-agents/SKILL.md +88 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: outdated
|
|
3
|
+
description: Reports which dependencies of a pnpm workspace have newer releases past the release quarantine, and what every changelog in between means for this codebase. Use when asked what is outdated, what a new release brings, or what an upgrade would involve.
|
|
4
|
+
metadata:
|
|
5
|
+
pack: epicparty/skills
|
|
6
|
+
version: 0.1.0
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Outdated
|
|
10
|
+
|
|
11
|
+
Packages in, one report out: each scoped dependency's newest version past the **quarantine**, and what every release in between means for this codebase. The report changes nothing. [`/ep-upgrade`](../upgrade/SKILL.md) applies it.
|
|
12
|
+
|
|
13
|
+
The **quarantine** is the age a release must reach before the workspace takes it. It is the age the user gave, or else `minimumReleaseAge` in `pnpm-workspace.yaml` — in minutes there — and it skips each package in `minimumReleaseAgeExclude`. With neither, ask the user for this run's quarantine, and recommend `minimumReleaseAge`: pnpm enforces it at install, so the next run reads it too.
|
|
14
|
+
|
|
15
|
+
The skill reads the registry and the tree and runs no product, so it works in any checkout as it is.
|
|
16
|
+
|
|
17
|
+
## 1. Pin the scope
|
|
18
|
+
|
|
19
|
+
- **The packages** — the ones the user named: by name, by catalog ("the tanstack catalog") or by product. With none named, every npm dependency in the workspace: each catalog entry, each direct range in a `package.json`, and the root `packageManager`.
|
|
20
|
+
- **The families** — packages released or used together form one family, and a named package brings its family in. Read them off the workspace: packages published from one source repository (`pnpm view <pkg> repository.url`), a package with its `@types/*`, and the packages of a catalog named for one ecosystem — a `convex` or `sentry` catalog is a family, a catch-all `tooling` catalog is not.
|
|
21
|
+
- **The couplings** — a comment beside a pin or an override in `pnpm-workspace.yaml` is a constraint, and it joins the packages it names into one family. A comment saying `vitest` follows the Vitest that `vite-plus` bundles moves `vitest` only to that version.
|
|
22
|
+
- **The quarantine**, in days.
|
|
23
|
+
|
|
24
|
+
Done when you can name the packages, their families, and the quarantine in days.
|
|
25
|
+
|
|
26
|
+
## 2. Find the targets
|
|
27
|
+
|
|
28
|
+
[`taze`](https://github.com/antfu-collective/taze) reads the catalogs and every manifest in one pass:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
pnpx taze latest -r -l --no-github-actions --no-node-version --include <a>,<b> --json
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- `latest` crosses majors. Leave out `--include` for the whole workspace.
|
|
35
|
+
- `-l` is required. Without it taze skips exact pins, and almost every catalog entry is one.
|
|
36
|
+
- `--no-github-actions --no-node-version` keep the run to packages. Action pins and the Node version are outside this report.
|
|
37
|
+
- taze reads `minimumReleaseAge` and `minimumReleaseAgeExclude` itself. Add `--maturity-period <days>` only for a quarantine the user gave; it replaces the repo's.
|
|
38
|
+
- taze writes the `packageManager` target with a caret. The field takes an exact version, so report the exact one.
|
|
39
|
+
|
|
40
|
+
taze drops a package without a word when its newest release is still in quarantine and a release on another line — a `v9` maintenance tag, a canary — is the newest one past it. The package then has no row at all, or a row with `"diff": "error"`, and reads as current. So check every scoped package taze did not report: its target is the highest version in `pnpm view <pkg> versions --json` that is newer than the current one and past the quarantine in `pnpm view <pkg> time --json`. Take the versions from `versions`, because `time` also keeps unpublished ones.
|
|
41
|
+
|
|
42
|
+
Confirm the quarantine held on one target that has a younger release: `pnpm view <pkg> time --json` shows the target past the quarantine and the newer release skipped.
|
|
43
|
+
|
|
44
|
+
Done when every scoped package has a target with its publish date, or reads as current.
|
|
45
|
+
|
|
46
|
+
## 3. Read the changelogs
|
|
47
|
+
|
|
48
|
+
Read every release between the current version and the target. A breaking change ships in a middle release as often as in the last one. Sources, in order: the GitHub releases (`gh release list -R <owner>/<repo>`, the repository from `pnpm view <pkg> repository.url`), the `CHANGELOG.md`, then the migration guide of each major.
|
|
49
|
+
|
|
50
|
+
Fan the reading out: subagents in parallel, one per large family and one per handful of small ones, each given its packages and their version spans. Each returns, per package:
|
|
51
|
+
|
|
52
|
+
- **Breaking** — each change that touches our code, with the files it hits (`rg` the imports).
|
|
53
|
+
- **Deprecated** — each API we call that the span deprecates, with its replacement and the files that call it.
|
|
54
|
+
- **Improves us** — each new feature that replaces or simplifies code we have: a workaround, an override, a patch, a hand-rolled helper, a comment that names an upstream issue. With the files.
|
|
55
|
+
- **New** — each feature worth knowing that no code of ours needs yet.
|
|
56
|
+
- **Security** — each advisory the span fixes, with its id.
|
|
57
|
+
- **Defaults** — each changed default that alters what the product collects, stores or shows, with what our config sets today.
|
|
58
|
+
- **Peers** — the target's `peerDependencies` and `engines` (`pnpm view <pkg>@<version> peerDependencies engines`).
|
|
59
|
+
|
|
60
|
+
A package with no changelog to find says so, and the gate becomes its only check.
|
|
61
|
+
|
|
62
|
+
Done when every target has its items, or reads as nothing that touches us.
|
|
63
|
+
|
|
64
|
+
## 4. Check the targets fit
|
|
65
|
+
|
|
66
|
+
Check each target's peer ranges against the other targets and against the couplings, and its `engines` against the project's Node version — `.node-version`, `.nvmrc`, or the root `engines.node`. A **Breaking** item can hold a package with no range to show it — a plugin we keep that misbehaves under the target. A package in `minimumReleaseAgeExclude` skips the quarantine, but its dependencies do not. When it pins one exactly, check that dependency's publish date too (`pnpm view <pkg>@<version> dependencies`). A dependency still in quarantine holds the package at its newest version whose dependencies have all passed. A conflict holds the package at the newest version that fits (`pnpm view <pkg> versions --json`), or at its current one.
|
|
67
|
+
|
|
68
|
+
Done when no target's peer range excludes another target, no `engines` range excludes the project's Node version, and each held package has its reason.
|
|
69
|
+
|
|
70
|
+
## 5. Find the unused
|
|
71
|
+
|
|
72
|
+
A scoped dependency is **unused** when nothing reaches it: no import (`rg` the package name across the workspace), no binary in a script, no reference in a config file, and no peer range of another installed package it satisfies. Check each manifest's entries and each catalog entry that no manifest references. The check is workspace-wide: a package that any workspace package reaches is used, whichever manifest declares it.
|
|
73
|
+
|
|
74
|
+
Done when each scoped dependency is used, or listed as unused with where it is declared.
|
|
75
|
+
|
|
76
|
+
## 6. Write the report
|
|
77
|
+
|
|
78
|
+
The report lives in the reply only — a later `/ep-upgrade` in the same thread reads it from there, and another thread runs this skill again. It is read top-down: the highlights first, the detail by family after them. Every line holds one fact, and every version is exact, because `/ep-upgrade` takes the report as its plan.
|
|
79
|
+
|
|
80
|
+
```markdown
|
|
81
|
+
**Quarantine:** 3 days, from `minimumReleaseAge`. **Scope:** the whole workspace — 80 of 112 packages behind, 12 families.
|
|
82
|
+
|
|
83
|
+
## Highlights
|
|
84
|
+
|
|
85
|
+
1. **Sentry 10.65.0 → 11.8.0** — breaking: `beforeSend` receives … Touches `src/lib/sentry/server.ts`.
|
|
86
|
+
2. **TanStack Router 1.170.17 → 1.170.45** — improves us: typed search defaults remove the casts in `src/routes/…`.
|
|
87
|
+
|
|
88
|
+
## By family
|
|
89
|
+
|
|
90
|
+
### vite — `vite-plus`, `@voidzero-dev/vite-plus-core`, `vitest`, `@vitest/coverage-v8`
|
|
91
|
+
|
|
92
|
+
`vite-plus` and `vite-plus-core` 1.0.0 → 1.3.0 (minor) · `vitest` and `@vitest/coverage-v8` 5.0.1 → 5.0.5 (patch)
|
|
93
|
+
|
|
94
|
+
- **Breaking:** none that touches us.
|
|
95
|
+
- **Improves us:** `vp test --changed` … could replace … in `package.json`.
|
|
96
|
+
- **New:** …
|
|
97
|
+
- **Peers:** fit.
|
|
98
|
+
|
|
99
|
+
### Nothing that touches us
|
|
100
|
+
|
|
101
|
+
- **tailwind** (4 packages) — 4.3.2 → 4.3.3, patch. Bug fixes in …
|
|
102
|
+
|
|
103
|
+
## Held
|
|
104
|
+
|
|
105
|
+
- `posthog-js` 1.438.13 — under the quarantine until 2026-10-23. Takes 1.438.12.
|
|
106
|
+
- `better-auth` 1.7.10 — `@convex-dev/better-auth` 0.12.5 peers on `<1.7.0`. Takes 1.6.33.
|
|
107
|
+
- `jsdom` 30.1.3 — its `engines` excludes Node 24.14.0, the project's Node version. Stays on 29.1.1.
|
|
108
|
+
|
|
109
|
+
## Open questions
|
|
110
|
+
|
|
111
|
+
1. **Sentry `dataCollection`** — v11 removes `sendDefaultPii` and collects request bodies, headers and IPs by default, because … Our events then carry … (`src/lib/sentry/…`). Options: keep v10's set — …; take the new default — …. Recommended: keep v10's set, because ….
|
|
112
|
+
|
|
113
|
+
## Unused
|
|
114
|
+
|
|
115
|
+
- `@tailwindcss/cli` — in the `styling` catalog. No manifest references it.
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
- **Highlights** — the items that change what the user decides, ranked: **Security** for an advisory our current version has — in shipped code before a dev tool — then **Breaking** and **Defaults**, then **Improves us**, then a **New** feature with real weight. Each names the family, the version jump, the item and its files.
|
|
119
|
+
- **A family block** for each family with at least one item, majors first, then by item count. The version line names every package that moves. The bullets keep the order of step 3; **Breaking** is always there, as "none that touches us" when empty, so the reader knows it was checked. Any other label with nothing is left out.
|
|
120
|
+
- **Nothing that touches us** — one line per remaining family: the span, the kind, and what the releases did in a few words.
|
|
121
|
+
- **Held** — every release a package does not take, its reason, and the version it takes instead. A held package that still moves also keeps its line in its family block.
|
|
122
|
+
- **Open questions** — each choice the report leaves to the user: a major to take or hold, a held package with another way out, a default that changes what the product collects or shows, an optional adoption. Each carries what [`/ep-upgrade`](../upgrade/SKILL.md) needs for its briefing: what changed and why, what it touches here, the options with their consequences, and a recommendation with its reason.
|
|
123
|
+
- **Unused** — last, each unused dependency with where it is declared. Leave the heading out when there is none.
|
|
124
|
+
|
|
125
|
+
Done when every scoped package that moves appears once — in a family block or under **Nothing that touches us** — every release it does not take is under **Held**, every package that neither moves nor is held is in the count of current packages, and every unused one is under **Unused**.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: research
|
|
3
|
+
description: Investigates a question against primary sources and reports the findings, publishing a large one where the project keeps its research. Use when a question needs facts from outside this repo, or the user asks for a topic researched. A failure in this code goes to /ep-diagnose instead.
|
|
4
|
+
metadata:
|
|
5
|
+
pack: epicparty/skills
|
|
6
|
+
version: 0.1.0
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Research
|
|
10
|
+
|
|
11
|
+
A question about facts outside this repo — a platform's behaviour, a library's contract, a spec. Research ends in **findings the user can decide from**, and in a **published note** when the findings are worth going back to.
|
|
12
|
+
|
|
13
|
+
The decision belongs on the issue or in the ADR. The evidence belongs in the note, at the project's **Destination** setting. A note is evidence and never a rule: an agent that finds research beside the standards follows the evidence instead of the decision, so a note that lands in the repo says so on its first line.
|
|
14
|
+
|
|
15
|
+
## 1. Investigate
|
|
16
|
+
|
|
17
|
+
Spin up a **background agent** so you keep working while it reads. Its brief: primary sources first — official docs, source code, specs, first-party help centres — and a secondary source only where no primary exists, labelled as such. Every claim carries its source. A page that blocks fetching is cited from its search snippet and marked `(snippet)`; a claim with no source at all is marked `(unverified)`.
|
|
18
|
+
|
|
19
|
+
Done when every claim in the findings has a source or a mark.
|
|
20
|
+
|
|
21
|
+
## 2. Report
|
|
22
|
+
|
|
23
|
+
The reply is the findings' default home, sized to the question: an answer with its sources, or a comparison table with a synthesis. Lead with what the user asked, then what it means for the decision in hand.
|
|
24
|
+
|
|
25
|
+
Done when the user can decide from the reply alone.
|
|
26
|
+
|
|
27
|
+
## 3. Decide the home
|
|
28
|
+
|
|
29
|
+
A research earns a published note when both hold:
|
|
30
|
+
|
|
31
|
+
- **It is big.** A comparison across many sources, or more than a reply carries without burying the answer.
|
|
32
|
+
- **Something will cite it.** A story, an ADR or a PR will point back at it, or the user says they want to go back to it.
|
|
33
|
+
|
|
34
|
+
Both hold: publish (§ 4). Only the first holds: ask the user whether to keep it. Neither: the reply was the whole deliverable, and the agent's working notes are discarded with it.
|
|
35
|
+
|
|
36
|
+
Done when the home is named: the reply, or a published note.
|
|
37
|
+
|
|
38
|
+
## 4. Publish
|
|
39
|
+
|
|
40
|
+
Read the **Destination** setting now, per [Settings](#settings). This is the one step that needs it, so a research that ended in the reply never asks.
|
|
41
|
+
|
|
42
|
+
Every destination takes the same note:
|
|
43
|
+
|
|
44
|
+
- **Title**: `Research: <the question>`.
|
|
45
|
+
- **Body**: the date, the question, the method and its limits, the findings with inline `[n]` citations, the synthesis, and the `Sources` list. The whole note, so it stands without the chat.
|
|
46
|
+
|
|
47
|
+
Then publish it the way the setting says.
|
|
48
|
+
|
|
49
|
+
**GitHub Discussion** — one Discussion in the setting's category, resolved by name, never by id:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
publish_research() { # publish_research <category> <title> <body-file>
|
|
53
|
+
# Braced on purpose: the harness substitutes skill arguments into a bare $N.
|
|
54
|
+
local category="${1}" title="${2}" body_file="${3}"
|
|
55
|
+
local owner name repo_id category_id
|
|
56
|
+
read -r owner name < <(gh repo view --json owner,name --jq '"\(.owner.login) \(.name)"')
|
|
57
|
+
read -r repo_id category_id < <(gh api graphql -f query='
|
|
58
|
+
query($o:String!,$r:String!){ repository(owner:$o,name:$r){ id
|
|
59
|
+
discussionCategories(first:20){ nodes{ id name } } } }' \
|
|
60
|
+
-f o="$owner" -f r="$name" \
|
|
61
|
+
--jq ".data.repository | \"\(.id) \(.discussionCategories.nodes[] | select(.name==\"$category\") | .id)\"")
|
|
62
|
+
[[ -z "$category_id" ]] && { echo "no $category category: a maintainer creates it under Settings → Discussions" >&2; return 1; }
|
|
63
|
+
gh api graphql -f query='
|
|
64
|
+
mutation($r:ID!,$c:ID!,$t:String!,$b:String!){
|
|
65
|
+
createDiscussion(input:{repositoryId:$r,categoryId:$c,title:$t,body:$b}){ discussion{ url } } }' \
|
|
66
|
+
-f r="$repo_id" -f c="$category_id" -f t="$title" -f b="$(cat "$body_file")" \
|
|
67
|
+
--jq '.data.createDiscussion.discussion.url'
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
A missing category stops the publish: say so, because only a human can create one.
|
|
72
|
+
|
|
73
|
+
**A file in this repo** — `<directory>/<YYYY-MM-DD>-<slug>.md`, its first line `> Research evidence, not a rule. The decision lives in the issue or ADR that cites this note.` Write it to the working tree and name the path; it is committed when the user asks, or with the work that cites it.
|
|
74
|
+
|
|
75
|
+
**Somewhere else** — follow the setting's instructions as written. A tool they name that this session cannot reach stops the publish: say which one.
|
|
76
|
+
|
|
77
|
+
Done when the reply carries the note's URL or path.
|
|
78
|
+
|
|
79
|
+
## 5. Cite
|
|
80
|
+
|
|
81
|
+
Whatever the research fed — the story, the ADR, the PR body — links the note and states the conclusion in one line beside the link. A reader of the issue learns what was decided without opening the note, and opens it only to see why.
|
|
82
|
+
|
|
83
|
+
Done when the citing document names the decision and the link.
|
|
84
|
+
|
|
85
|
+
## Settings
|
|
86
|
+
|
|
87
|
+
This skill reads one **setting**, **Destination**, at § 4. Each `###` heading below is one setting: its topic, its question, and its options. `/ep-setup` reads the same headings to ask every setting up front.
|
|
88
|
+
|
|
89
|
+
1. **The settings home.** The `## Agent skills` block of the root `AGENTS.md` — or `CLAUDE.md`, whichever the project has — names where `ep-*` settings live and in what form. With neither file present, first ask which to create, recommending `AGENTS.md`, which most agents read. With no such line, ask the user where to keep them, the default first:
|
|
90
|
+
- **`docs/agents/<topic>.md`** (default) — one Markdown file per topic, one `##` heading per setting, named as the setting. Other headings in the file are the project's own notes.
|
|
91
|
+
- **One file** — at a path the user names.
|
|
92
|
+
- **Another form** — as the user describes it.
|
|
93
|
+
|
|
94
|
+
Write their answer as one line in that block, creating the block when it is missing — for the default, `` `ep-*` settings: `docs/agents/<topic>.md`, one heading per setting. ``
|
|
95
|
+
2. **The setting.** Read it at the settings home. Present: use it, and ask nothing.
|
|
96
|
+
3. **Missing.** Ask its question below, recommended answer first, with the follow-up its option needs. Write the answer at the settings home, tell the user where it went and that they can edit it there or through `/ep-setup`, then continue.
|
|
97
|
+
|
|
98
|
+
### Destination
|
|
99
|
+
|
|
100
|
+
Topic `research`. Question: *Where should research worth keeping be published?*
|
|
101
|
+
|
|
102
|
+
| Option | Follow-up | Recommend when |
|
|
103
|
+
| --- | --- | --- |
|
|
104
|
+
| **GitHub Discussion** | The category name, default `Research` | The repo has Discussions on (`gh repo view --json hasDiscussionsEnabled`) |
|
|
105
|
+
| **A file in this repo** | The directory, default `docs/research/` | Discussions are off |
|
|
106
|
+
| **Somewhere else** | The instructions, in the user's words: the tool, the place, the format | — |
|
|
107
|
+
| **Nowhere** | — | — |
|
|
108
|
+
|
|
109
|
+
**Nowhere** makes the reply every research's home: § 3 then names the reply and § 4 never runs.
|
|
110
|
+
|
|
111
|
+
## Guardrails
|
|
112
|
+
|
|
113
|
+
- **Findings are facts, decisions are the user's.** Report what the sources say and what it implies for the choice in hand; put the choice to the user.
|
|
114
|
+
- **One note per question.** A follow-up on the same question extends that note — a comment on the Discussion, a dated section in the file — rather than starting a second one.
|
|
115
|
+
- **A note is evidence, never a rule.** Nothing in `AGENTS.md`, a standard or a skill points at one as the thing to follow; they point at the ADR or the story that decided. A note directory stays out of every standards route.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Board
|
|
2
|
+
|
|
3
|
+
How the `ep-*` skills keep a project board, where the **Tracker** setting (`/ep` `## Settings`) names one. Without a board, every rule here is skipped: the labels, the branches and the PRs carry the state. The project's own rules add to these and win where they differ.
|
|
4
|
+
|
|
5
|
+
## Stages
|
|
6
|
+
|
|
7
|
+
The skills name a board's statuses by **stage**. The **Tracker** setting maps each stage to the board's own status name.
|
|
8
|
+
|
|
9
|
+
| Stage | Meaning | Who writes it |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| **Ideas** | A draft idea, not wanted yet; never a real issue | A skill, on a draft it creates |
|
|
12
|
+
| **Triage** | Not evaluated, or waiting on the reporter or on a decision | The board's automation on add, then projected from the labels |
|
|
13
|
+
| **Backlog** | Prioritised, not started | Projected from the labels |
|
|
14
|
+
| **Ready** | Fully specified | Projected from the labels |
|
|
15
|
+
| **Queued** | Wanted now: a worker may claim it. Only where the **Ready signal** is this board column (`/ep-deliver`) | **The user**, by hand. A skill only returns a story there (see Rules) |
|
|
16
|
+
| **Building** | Being built, validated and self-reviewed on a branch | `/ep-implement` § 1, or the `/ep-deliver` claim |
|
|
17
|
+
| **Review** | PR open, awaiting CI and review | `/ep-to-pr` § 5, and the board's automation where it watches linked PRs |
|
|
18
|
+
| **Shipping** | Merged; deploy running, awaiting its receipt | `/ep-ship` § 1 |
|
|
19
|
+
| **Parked** | Optional. A worker stopped on a question; its branch or PR holds the work | `/ep-deliver`, at the park |
|
|
20
|
+
| **Done** | Closed | The board's automation on close |
|
|
21
|
+
|
|
22
|
+
A board without some of these stages skips them, and the skills write nothing for a stage it lacks. Without **Parked**, a parked story keeps the stage it had, and its question label and comment are what mark it.
|
|
23
|
+
|
|
24
|
+
## Labels drive the status, until the work starts
|
|
25
|
+
|
|
26
|
+
Up to **Ready**, the status is a **projection** of the labels, not a second source of truth: set it in the same pass you set the labels, or the two drift and the board stops being worth reading. From **Queued** — or, without that stage, from the claim — the labels stop driving it and the board **is** the state.
|
|
27
|
+
|
|
28
|
+
Up to **Ready**, read this table top-down and take the **first** row that matches; the rows overlap in practice, so order is the rule. Labels are named by role (the **Labels** setting).
|
|
29
|
+
|
|
30
|
+
| Labels on the issue | Stage |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| The triage label, or an axis still unset | Triage |
|
|
33
|
+
| The info label | Triage — looked at, but waiting on the reporter |
|
|
34
|
+
| The discovery label | Triage — looked at, but waiting on a decision |
|
|
35
|
+
| A ready label | Ready |
|
|
36
|
+
| Every axis set | Backlog |
|
|
37
|
+
|
|
38
|
+
The triage label outranks a priority deliberately. The two co-occur on real issues: a priority applied at filing time is a guess, and the triage label says a maintainer has still not evaluated the issue. Whoever triages drops it, and only then does the backlog row take effect.
|
|
39
|
+
|
|
40
|
+
**Queued is intent; a ready label is a property.** A ready label says the story is specified well enough to build. Queued says the user wants it started now. Keeping them apart is what lets a story be honestly ready and still not be started.
|
|
41
|
+
|
|
42
|
+
## Rules
|
|
43
|
+
|
|
44
|
+
- **Never add an issue to the board by hand where an automation adds it.** Your job is only the status of an item that is already there.
|
|
45
|
+
- **When creating an issue, leave the status alone.** The automation adds the item asynchronously, so anything set in the same breath as the create races it and silently does nothing. Get the labels right instead; the board catches up on its own.
|
|
46
|
+
- **When filing an idea, create a draft on the board, never an issue.** A draft is the one item you add by hand, because no automation can. An add automation may still write Triage on it, asynchronously, so `board_idea` below sets Ideas and re-reads until it holds.
|
|
47
|
+
- **When labels move an issue along**, set the new stage in the same pass. By then the item exists.
|
|
48
|
+
- **Only the user queues a story.** No skill puts an issue in Queued that the user did not put there. A user's explicit yes to a skill's offer — `/ep-triage` offers it on a ready story — is the user queuing it. An agent that queues its own work has removed the control the gate exists to give. The one write a skill makes is a **return**: a story the user queued, whose claim produced no branch and no PR, goes back to Queued (`/ep-deliver` § 1). The user wanted it started, and a worker that died does not cancel that.
|
|
49
|
+
- **A claim is one write, then a read.** Write Building **before** cutting the branch, then re-read the item. A worker that does not see itself as the writer lost the race. This is the only thing stopping two workers building one story.
|
|
50
|
+
- **Review when the PR opens**, not after CI goes green: Review covers "PR open, awaiting CI and review", so an amber build belongs there too. The write may agree with an automation that already made it; it costs one call and covers a PR opened without a link.
|
|
51
|
+
- **Shipping immediately *before* the merge**, not after. The merge closes the issue, and a close automation then writes Done; a later Shipping write would land on top of Done and stick, because that trigger is spent.
|
|
52
|
+
- **Never set Done by hand**, except when reconciling: moving an issue you have just read as **closed** confirms rather than asserts. Setting Done on anything else only masks whether the issue actually closed.
|
|
53
|
+
- **Done means closed, not shipped.** A close automation fires on any close, including *not planned*. The merged PR is what says the work landed.
|
|
54
|
+
- **Never hardcode the project, field or option ids.** They are regenerated whenever the board is recreated, so resolve them every time.
|
|
55
|
+
|
|
56
|
+
## When the user asks
|
|
57
|
+
|
|
58
|
+
| The user says | It means |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| "the board" | The board the **Tracker** setting names |
|
|
61
|
+
| "save it as an idea", "add it to the board as an idea", "a draft issue" | A draft in Ideas, with `board_idea`. Never a real issue |
|
|
62
|
+
| "check the board", "update the statuses" | [Checking the board](#checking-the-board), with every card you moved named in the reply |
|
|
63
|
+
|
|
64
|
+
## Checking the board
|
|
65
|
+
|
|
66
|
+
Read every card and move each one whose stage no longer matches what exists. Take the first rule that applies:
|
|
67
|
+
|
|
68
|
+
1. **The issue is closed**: Done.
|
|
69
|
+
2. **Building, Review, Shipping or Parked**: the in-flight table in [`/ep-deliver`](../deliver/SKILL.md) `### 1. Reconcile what is in flight` — a branch, a PR, a merge.
|
|
70
|
+
3. **Triage, Backlog or Ready**: the label table above.
|
|
71
|
+
4. **Queued, or a draft in Ideas**: leave it. Both are the user's own moves.
|
|
72
|
+
|
|
73
|
+
Never queue a story the user did not queue: the only Queued write is the return that step 2 makes. Name every card you moved, from which stage to which, and why. A card moved in silence is a card the user can no longer trust.
|
|
74
|
+
|
|
75
|
+
## GitHub Projects commands
|
|
76
|
+
|
|
77
|
+
For a GitHub Project linked to the repository, with a single-select `Status` field. `board` finds the one open board linked to the repo — by that link, not by its title, so renaming it changes nothing. `board_status` sets the status of an issue already on it; `board_idea` saves an idea as a draft and prints the draft's id. Pass the board's own status names, from the **Tracker** setting. An empty item id in `board_status` means the automation has not added the issue yet; leave it, do not add it yourself.
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
repo() { # prints "<owner> <name>"
|
|
81
|
+
gh repo view --json owner,name --jq '"\(.owner.login) \(.name)"'
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
board() { # prints "<number> <project-id>": the one open board linked to this repo
|
|
85
|
+
local owner name
|
|
86
|
+
read -r owner name < <(repo)
|
|
87
|
+
gh api graphql -f query='
|
|
88
|
+
query($o:String!,$r:String!){ repository(owner:$o,name:$r){
|
|
89
|
+
projectsV2(first:10){ nodes{ number id closed } } } }' -f o="$owner" -f r="$name" \
|
|
90
|
+
--jq '[.data.repository.projectsV2.nodes[] | select(.closed==false)][0] | "\(.number) \(.id)"'
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
board_option() { # board_option <project-number> <status-name>: prints "<field-id> <option-id>"
|
|
94
|
+
local owner name
|
|
95
|
+
read -r owner name < <(repo)
|
|
96
|
+
gh project field-list "$1" --owner "$owner" --format json \
|
|
97
|
+
--jq ".fields[] | select(.name==\"Status\") | \"\(.id) \(.options[] | select(.name==\"$2\") | .id)\""
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
board_set() { # board_set <project-id> <item-id> <field-id> <option-id>
|
|
101
|
+
gh api graphql -f query='
|
|
102
|
+
mutation($p:ID!,$i:ID!,$f:ID!,$o:String!){
|
|
103
|
+
updateProjectV2ItemFieldValue(input:{
|
|
104
|
+
projectId:$p, itemId:$i, fieldId:$f, value:{singleSelectOptionId:$o}
|
|
105
|
+
}){ projectV2Item{ id } }
|
|
106
|
+
}' -f p="$1" -f i="$2" -f f="$3" -f o="$4" >/dev/null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
board_item_status() { # board_item_status <item-id>
|
|
110
|
+
gh api graphql -f query='
|
|
111
|
+
query($i:ID!){ node(id:$i){ ... on ProjectV2Item {
|
|
112
|
+
fieldValueByName(name:"Status"){ ... on ProjectV2ItemFieldSingleSelectValue { name } } } } }' \
|
|
113
|
+
-f i="$1" --jq '.data.node.fieldValueByName.name'
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
board_status() { # board_status <issue-number> <status-name>
|
|
117
|
+
local issue="$1" target="$2" # not `status`: zsh reserves that name
|
|
118
|
+
local owner name number project_id field_id option_id item_id
|
|
119
|
+
read -r owner name < <(repo)
|
|
120
|
+
read -r number project_id < <(board)
|
|
121
|
+
read -r field_id option_id < <(board_option "$number" "$target")
|
|
122
|
+
|
|
123
|
+
item_id=$(gh api graphql -f query='
|
|
124
|
+
query($o:String!,$r:String!,$n:Int!){
|
|
125
|
+
repository(owner:$o,name:$r){ issue(number:$n){
|
|
126
|
+
projectItems(first:10){ nodes{ id project{ id } } } } }
|
|
127
|
+
}' -f o="$owner" -f r="$name" -F n="$issue" \
|
|
128
|
+
--jq ".data.repository.issue.projectItems.nodes[] | select(.project.id==\"$project_id\") | .id") ||
|
|
129
|
+
{ echo "issue #$issue does not exist" >&2; return 1; }
|
|
130
|
+
|
|
131
|
+
[[ -z "$item_id" ]] && { echo "issue #$issue is not on the board yet" >&2; return 1; }
|
|
132
|
+
|
|
133
|
+
board_set "$project_id" "$item_id" "$field_id" "$option_id"
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
board_idea() { # board_idea <title> <body> <ideas-status-name>: prints the draft's item id
|
|
137
|
+
local owner name number project_id field_id option_id item_id
|
|
138
|
+
read -r owner name < <(repo)
|
|
139
|
+
read -r number project_id < <(board)
|
|
140
|
+
read -r field_id option_id < <(board_option "$number" "$3")
|
|
141
|
+
item_id=$(gh project item-create "$number" --owner "$owner" \
|
|
142
|
+
--title "$1" --body "$2" --format json --jq '.id')
|
|
143
|
+
|
|
144
|
+
# An add automation may write Triage on the draft, asynchronously:
|
|
145
|
+
# set Ideas until a re-read shows it held.
|
|
146
|
+
local attempt
|
|
147
|
+
for attempt in 1 2 3; do
|
|
148
|
+
board_set "$project_id" "$item_id" "$field_id" "$option_id"
|
|
149
|
+
sleep 5
|
|
150
|
+
[[ "$(board_item_status "$item_id")" == "$3" ]] && { echo "$item_id"; return 0; }
|
|
151
|
+
done
|
|
152
|
+
echo "draft $item_id did not stay in $3" >&2
|
|
153
|
+
return 1
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Writing to a project needs a token carrying the `project` scope; the repo scope alone is not enough, and the default `GITHUB_TOKEN` cannot write an organisation's projects at all. Locally: `gh auth refresh -s project,read:project`.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Decisions
|
|
2
|
+
|
|
3
|
+
Where a decision the work makes is written down, and how the `ep-*` skills read the project's domain documents. The project's own rules — its domain doc, its ADR layout — add to these and win where they differ.
|
|
4
|
+
|
|
5
|
+
## Before exploring, read these
|
|
6
|
+
|
|
7
|
+
- **The glossary** — `GLOSSARY.md` at the root, or a `GLOSSARY-MAP.md` that points at one glossary per context. A project that already names them `CONTEXT.md` and `CONTEXT-MAP.md` keeps those names. Read each one relevant to the topic.
|
|
8
|
+
- **The ADRs** — `docs/adr/`, and in a multi-context project the context's own ADR directory beside its glossary. Read the ones that touch the area you are about to work in.
|
|
9
|
+
|
|
10
|
+
Where any of these does not exist, **proceed silently**. Their absence is not a finding, and creating them upfront is work nobody asked for: they are created lazily, when a term or a decision actually gets resolved — [`/ep-grill`](../grill/SKILL.md) writes them as a session settles them, in the formats below.
|
|
11
|
+
|
|
12
|
+
## Where a decision goes
|
|
13
|
+
|
|
14
|
+
Three homes, widening in scope. Pick by **how long the decision binds**, not by how much detail it carries.
|
|
15
|
+
|
|
16
|
+
| The decision | Home |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| Shapes the increment being built, and nothing beyond it | The story's `## Implementation Decisions` |
|
|
19
|
+
| Binds code outside its own story, in one area | An ADR |
|
|
20
|
+
| Binds more than one area, or is applied repeatedly | A project standard |
|
|
21
|
+
|
|
22
|
+
**The story is the default.** Most decisions made while building are answers to "how do we do this one", and they belong in the issue that asked for the work. Writing an ADR for one of those duplicates the story into a file nobody reads.
|
|
23
|
+
|
|
24
|
+
**An ADR earns its file when someone will need the decision while working on something else.** The cheap proxy: would a code comment cite it? A function that refuses to do the obvious thing needs a reason with a stable address, and `// (ADR 0006)` is that address.
|
|
25
|
+
|
|
26
|
+
The reason a live constraint cannot stay in the story is lifetime. **A story is frozen once its work lands on the default branch** — later changes become new issues, so a feature that keeps moving leaves a chain of closed stories, and a constraint parked in one of them can only be reconstructed by reading the whole chain and guessing which parts still hold. **An ADR is live**: it is superseded in place by a named successor, so there is exactly one current answer.
|
|
27
|
+
|
|
28
|
+
**A standard is an ADR that outgrew its context.** When a second area needs the same rule, the ADR stays as decision history and the rule moves to the project's standards, which become binding.
|
|
29
|
+
|
|
30
|
+
Three things that are **not** ADRs: a decision that only shapes its own increment (the story), a restatement of an existing standard (point at the standard), and a plan for work not yet done (an issue).
|
|
31
|
+
|
|
32
|
+
## Writing the glossary
|
|
33
|
+
|
|
34
|
+
When a term is resolved, write it into the glossary right there — never batched for later — in the shape of [`GLOSSARY-FORMAT.md`](./GLOSSARY-FORMAT.md).
|
|
35
|
+
|
|
36
|
+
## Writing an ADR
|
|
37
|
+
|
|
38
|
+
<!-- upstream: mattpocock/skills skills/engineering/domain-modeling/ADR-FORMAT.md -->
|
|
39
|
+
|
|
40
|
+
**Offer an ADR only when all three hold:** the decision is **hard to reverse**, it is **surprising without context** — a reader of the code would wonder why — and it is **the result of a real trade-off**, with genuine alternatives. Missing any one, skip it: an easy decision is reversed, an obvious one is never wondered about, and one with no alternative records nothing.
|
|
41
|
+
|
|
42
|
+
What qualifies: the architecture's shape; how contexts integrate; a technology choice with lock-in — not every library, only the ones that take a quarter to swap; a boundary or scope decision, the explicit noes included; a deliberate deviation from the obvious path; a constraint the code cannot show, such as a compliance rule or a partner's response-time contract; and a rejected alternative whose rejection is not obvious, so nobody proposes it again in six months.
|
|
43
|
+
|
|
44
|
+
ADRs live in `docs/adr/` as `0001-slug.md`, `0002-slug.md`, and so on — the next number is the highest in the directory plus one. Create the directory with the first ADR.
|
|
45
|
+
|
|
46
|
+
```md
|
|
47
|
+
# {The decision, stated as a claim}
|
|
48
|
+
|
|
49
|
+
{One to three sentences: the context, what was decided, and why.}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
That can be the whole ADR. Keep it short: one to three paragraphs.
|
|
53
|
+
|
|
54
|
+
- **The title states the decision as a claim**, present tense — "Blocking severs relationships and never confirms itself", not "Blocking behaviour". The title is what a reader scans a directory for.
|
|
55
|
+
- **The body gives the decision, what it rules out, and the cost accepted.** The cost is the part that stops the decision being reopened every six months.
|
|
56
|
+
- **`## Considered options` only when a rejected option will be proposed again.** Most decisions do not need it. `## Consequences` only when a downstream effect is not obvious, and a `Status` line (proposed, accepted, deprecated, superseded) only where decisions get revisited.
|
|
57
|
+
- **An ADR number is its identity.** Numbers run per directory and are never reused, so a new ADR takes the next free number in the directory it lands in.
|
|
58
|
+
- **Supersession is stated by the successor**, naming what it replaces and what still stands. Add a line to the superseded ADR pointing forward; leave its body intact as history.
|
|
59
|
+
- **Qualify a citation that crosses directories.** Where several ADR directories share one numbering space, `ADR 0006` names several decisions. Inside a context's own tree a bare number reads as that context's ADR; a citation from outside it takes the context's prefix.
|
|
60
|
+
|
|
61
|
+
## Use the glossary's vocabulary
|
|
62
|
+
|
|
63
|
+
When your output names a domain concept — an issue title, a refactor proposal, a hypothesis, a test name — use the term as the glossary defines it. Do not drift to synonyms the glossary avoids.
|
|
64
|
+
|
|
65
|
+
A concept the glossary lacks is a signal: either you are inventing language the project does not use (reconsider), or there is a real gap: add the term when the user settles it, or note it in your report when they are not there to.
|
|
66
|
+
|
|
67
|
+
## Flag ADR conflicts
|
|
68
|
+
|
|
69
|
+
When your output contradicts an existing ADR, surface it rather than silently overriding it:
|
|
70
|
+
|
|
71
|
+
> _Contradicts ADR 0007 (match-scoped disqualification) — but worth reopening because…_
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Git
|
|
2
|
+
|
|
3
|
+
How the `ep-*` skills name, commit, merge and catch up work. The project's own git rules — in its agent files, or the document they route to — add to these and win where they differ.
|
|
4
|
+
|
|
5
|
+
## Commits
|
|
6
|
+
|
|
7
|
+
Follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/): `type(scope): description`, the type from the set `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `ci`, `style`, `perf`, `build`, `revert`.
|
|
8
|
+
|
|
9
|
+
- **Each commit stands alone**: its own subject, its own scope, and a body wherever the change needs explaining. Under a rebase merge every commit reaches the default branch as itself, and it is written for the reader who finds it there.
|
|
10
|
+
- Move and rename files with `git mv`, so history follows them.
|
|
11
|
+
- Validate before committing.
|
|
12
|
+
- Stage only your own changes. Work in the tree you did not write is somebody's, and it stays out of your commits.
|
|
13
|
+
|
|
14
|
+
## Naming an issue, a PR and a branch
|
|
15
|
+
|
|
16
|
+
One **scope** identifies a piece of work everywhere it appears — the issue, the PR and the branch — taken from the **Scopes** setting (`/ep` `## Settings`). Read it once and you know which part of the project is moving.
|
|
17
|
+
|
|
18
|
+
| Artifact | Shape | Example |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| Issue title | `<scope>: description` | `billing/invoices: send a reminder before an invoice falls due` |
|
|
21
|
+
| PR title | `type(<scope>): description` | `feat(billing/invoices): send a reminder before an invoice falls due` |
|
|
22
|
+
| Branch | `type/<scope-with-dashes>-short-slug` | `feat/billing-invoices-due-reminder` |
|
|
23
|
+
|
|
24
|
+
- **The scope is the same string on all three**, cased the way the project's rules case it.
|
|
25
|
+
- **The issue carries no type.** One issue can legitimately produce a `feat` PR and a `fix` PR, and a title that names a type would be wrong for one of them. The type lives on a label, where the project has a type axis.
|
|
26
|
+
- **The PR always carries a type**, the branch's dominant change.
|
|
27
|
+
- **The description is a lowercase imperative phrase** with no trailing period, and it reads the same on the issue and the PR. It does not restate the scope.
|
|
28
|
+
- **Commits inside the PR keep their own narrower scopes.** A `feat(billing/invoices)` PR may hold `feat(billing/api)` and `feat(billing/web)` commits: the PR names the feature, each commit names the layer it touches.
|
|
29
|
+
|
|
30
|
+
Without scopes, every shape drops its scope: `description`, `type: description`, `type/short-slug`.
|
|
31
|
+
|
|
32
|
+
## Merging
|
|
33
|
+
|
|
34
|
+
Merge the way the **Merge strategy** setting says (`/ep` `## Settings`):
|
|
35
|
+
|
|
36
|
+
- **Rebase** — every commit lands on the default branch as itself, so the branch's history is the record. The PR title and body reach the default branch in no form at all: anything a reader of `git log` needs is in a commit message already. A review fix is a commit like any other, and is written as one.
|
|
37
|
+
- **Squash** — the PR becomes one commit, its subject the PR title and its body the PR body. The PR body is part of the record, and is written for the `git log` reader as well as the reviewer.
|
|
38
|
+
- **Merge commit** — the branch's commits land as themselves under a merge commit titled by the PR.
|
|
39
|
+
|
|
40
|
+
## Catching up with the default branch
|
|
41
|
+
|
|
42
|
+
**A branch catches up by a rebase onto `origin/<default>`.** A conflict with the default branch is the usual reason. Never merge the default branch into the branch: under a rebase merge the merge commit is dropped and the conflict comes back, and under any strategy it buries the branch's own changes in a merge nobody reviews.
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
git fetch origin <default>
|
|
46
|
+
git rev-parse HEAD # the old head SHA: it must match the PR's head
|
|
47
|
+
git rebase origin/<default>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Resolve each conflicting commit as the rebase stops on it, and continue until every commit is replayed. A resolution belongs to the commit that conflicted, so each commit still stands alone. Keep the rebase to conflict resolution: a review fix is its own commit, made on top of the rebased branch.
|
|
51
|
+
|
|
52
|
+
<!-- upstream: mattpocock/skills skills/engineering/resolving-merge-conflicts/SKILL.md (removed upstream at daa01d8) -->
|
|
53
|
+
|
|
54
|
+
Each stop, in order:
|
|
55
|
+
|
|
56
|
+
1. **Read the state** — `git status`, the conflicting files, and the commit being replayed.
|
|
57
|
+
2. **Find why each side changed.** Read the commit messages, then the PRs and issues behind them. A hunk resolved without knowing either intent is a guess.
|
|
58
|
+
3. **Resolve each hunk.** Keep both intents where they fit together. Where they cannot, keep the one that matches the default branch's later decision, and name the trade-off in your report. Add no behaviour neither side had.
|
|
59
|
+
4. **Run the checks** the change touches — `/ep-implement`'s **Scoped check** setting where it is set, or the project's typecheck and tests — and fix what the resolution broke.
|
|
60
|
+
5. **Stage and continue** — `git add` the resolved files, then `git rebase --continue`.
|
|
61
|
+
|
|
62
|
+
Abort the rebase only when the user asks: a conflict is resolved, not walked away from.
|
|
63
|
+
|
|
64
|
+
The rebased head is a commit nobody has tested, so the project's full suite runs on the head you push. Push with a lease pinned to the old head SHA:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
git push --force-with-lease=<branch>:<old-head-sha>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The pinned lease refuses the push when somebody pushed to the branch after you read it. A bare `--force-with-lease` checks against the remote-tracking ref, and any fetch since your read — yours, or an editor's in the background — may have moved that ref over a commit you never saw.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Glossary format
|
|
2
|
+
|
|
3
|
+
<!-- upstream: mattpocock/skills skills/engineering/domain-modeling/GLOSSARY-FORMAT.md -->
|
|
4
|
+
|
|
5
|
+
How the glossary is written, once a term is resolved. Where it lives is [`DECISIONS.md`](./DECISIONS.md) `## Before exploring, read these`; the examples below use `GLOSSARY.md`, and a project that keeps `CONTEXT.md` writes the same thing there.
|
|
6
|
+
|
|
7
|
+
## One glossary
|
|
8
|
+
|
|
9
|
+
```md
|
|
10
|
+
# {Context name}
|
|
11
|
+
|
|
12
|
+
{One or two sentences: what this context is and why it exists.}
|
|
13
|
+
|
|
14
|
+
## Language
|
|
15
|
+
|
|
16
|
+
**Order**:
|
|
17
|
+
A customer's request to buy one or more products.
|
|
18
|
+
_Avoid_: Purchase, transaction
|
|
19
|
+
|
|
20
|
+
**Invoice**:
|
|
21
|
+
A request for payment sent to a customer after delivery.
|
|
22
|
+
_Avoid_: Bill, payment request
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- **Be opinionated.** Where several words name one concept, pick the best and list the others under `_Avoid_`.
|
|
26
|
+
- **Keep definitions tight.** One or two sentences: what the term _is_, not what it does.
|
|
27
|
+
- **Only the project's own concepts.** A general programming concept — a timeout, an error type, a utility pattern — stays out, however much the project uses it.
|
|
28
|
+
- **Group terms under subheadings** when natural clusters emerge. One cohesive area is a flat list.
|
|
29
|
+
- **No implementation details.** The glossary is not a spec, a scratch pad or a home for implementation decisions. It is a glossary and nothing else.
|
|
30
|
+
|
|
31
|
+
## Several contexts
|
|
32
|
+
|
|
33
|
+
A map at the root lists the contexts, where each glossary lives, and how they relate:
|
|
34
|
+
|
|
35
|
+
```md
|
|
36
|
+
# Glossary map
|
|
37
|
+
|
|
38
|
+
## Contexts
|
|
39
|
+
|
|
40
|
+
- [Ordering](./src/ordering/GLOSSARY.md): receives and tracks customer orders
|
|
41
|
+
- [Billing](./src/billing/GLOSSARY.md): generates invoices and processes payments
|
|
42
|
+
|
|
43
|
+
## Relationships
|
|
44
|
+
|
|
45
|
+
- **Ordering → Billing**: Ordering emits `OrderPlaced`; Billing consumes it to generate an invoice
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
With a map, infer which context the term belongs to, and ask when it is unclear. With neither file, create a root glossary when the first term is resolved — never before.
|