@epicparty/skills 0.0.0-stage → 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/NOTICE +27 -0
- package/README.md +109 -2
- package/bin/cli.mjs +175 -0
- package/lib/transform.mjs +185 -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,51 @@
|
|
|
1
|
+
# Seams
|
|
2
|
+
|
|
3
|
+
<!-- upstream: mattpocock/skills skills/engineering/codebase-design/SKILL.md -->
|
|
4
|
+
|
|
5
|
+
The vocabulary for deciding where a seam goes and what sits behind it. [`/ep-to-story` § 3](./SKILL.md#3-settle-the-seams) settles the seams with it, [`/ep-implement`](../implement/TDD.md) tests at them, [`/ep-diagnose`](../diagnose/SKILL.md) looks for one to hold a regression test, and [`/ep-architect`](../architect/SKILL.md) designs with it and hunts the shallow modules it describes.
|
|
6
|
+
|
|
7
|
+
Design **deep modules**: a lot of behaviour behind a small interface, placed at a clean seam, testable through that interface. The aim is leverage for callers, locality for maintainers, and testability for everyone.
|
|
8
|
+
|
|
9
|
+
## Glossary
|
|
10
|
+
|
|
11
|
+
Use these terms exactly — not "component", "service", "API" or "boundary". Consistent language is the whole point.
|
|
12
|
+
|
|
13
|
+
- **Module** — anything with an interface and an implementation. Deliberately scale-agnostic: a function, a class, a package, a slice across tiers. _Avoid_: unit, component, service.
|
|
14
|
+
- **Interface** — everything a caller must know to use the module correctly: the type signature, and also invariants, ordering constraints, error modes, required configuration and performance characteristics. _Avoid_: API, signature — both name only the type-level surface.
|
|
15
|
+
- **Implementation** — what is inside a module, its body of code. Distinct from **adapter**: a thing can be a small adapter with a large implementation (a Postgres repository) or a large adapter with a small implementation (an in-memory fake). Say "adapter" when the seam is the topic, "implementation" otherwise.
|
|
16
|
+
- **Depth** — leverage at the interface: how much behaviour a caller, or a test, can exercise per unit of interface they have to learn. A module is **deep** when a lot of behaviour sits behind a small interface, **shallow** when the interface is nearly as complex as the implementation.
|
|
17
|
+
- **Seam** _(Michael Feathers)_ — a place where you can alter behaviour without editing in that place: the _location_ of a module's interface. Where the seam goes is its own decision, distinct from what goes behind it. _Avoid_: boundary — overloaded with the domain's bounded context.
|
|
18
|
+
- **Port** — an interface defined at a seam for a dependency the module does not own outright, so production and tests plug in different adapters.
|
|
19
|
+
- **Adapter** — a concrete thing that satisfies an interface at a seam. It names a _role_, the slot it fills, not what is inside.
|
|
20
|
+
- **Leverage** — what callers get from depth: more capability per unit of interface learned. One implementation pays back across every call site and every test.
|
|
21
|
+
- **Locality** — what maintainers get from depth: change, bugs, knowledge and verification concentrate in one place instead of spreading across callers. Fixed once, fixed everywhere.
|
|
22
|
+
|
|
23
|
+
## Principles
|
|
24
|
+
|
|
25
|
+
- **Depth is a property of the interface, not the implementation.** A deep module can be built from small, mockable, swappable parts; they are just not part of its interface. A module has **internal seams**, private to its implementation and used by its own tests, as well as the **external seam** at its interface.
|
|
26
|
+
- **The deletion test.** Imagine deleting the module. If the complexity vanishes, it was a pass-through. If it reappears across its callers, it was earning its keep.
|
|
27
|
+
- **The interface is the test surface.** Callers and tests cross the same seam. Wanting to test _past_ the interface says the module is the wrong shape.
|
|
28
|
+
- **One adapter means a hypothetical seam. Two adapters mean a real one.** Introduce a seam only where something actually varies across it — typically production and test.
|
|
29
|
+
|
|
30
|
+
When designing an interface, ask: can it have fewer methods, simpler parameters, more complexity hidden inside? Good interfaces make testing natural — they **accept their dependencies** rather than creating them, **return results** rather than producing side effects, and keep a **small surface**: fewer methods need fewer tests, and fewer parameters need less setup.
|
|
31
|
+
|
|
32
|
+
## Dependencies decide how a seam is tested
|
|
33
|
+
|
|
34
|
+
<!-- upstream: mattpocock/skills skills/engineering/codebase-design/DEEPENING.md -->
|
|
35
|
+
|
|
36
|
+
Classify what the module depends on. The category says how the module is tested across its seam.
|
|
37
|
+
|
|
38
|
+
| Dependency | Example | Tested with |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| **In-process** | Pure computation, in-memory state, no I/O | The real thing, through the interface. No adapter. |
|
|
41
|
+
| **Local stand-in** | PGLite for Postgres, an in-memory file system, a fake browser storage | The stand-in, running in the suite — injected at the seam only where the test environment cannot supply it. |
|
|
42
|
+
| **Remote but owned** | The project's own services across a network | A **port** at the seam: a network adapter in production, an in-memory adapter in tests. |
|
|
43
|
+
| **Truly external** | Stripe, Twilio — services you do not control | An injected port, and a mock adapter in tests. |
|
|
44
|
+
|
|
45
|
+
## Replace tests, do not layer them
|
|
46
|
+
|
|
47
|
+
When shallow modules merge into a deeper one, the tests move to its interface:
|
|
48
|
+
|
|
49
|
+
- Old tests on the shallow modules become waste once tests at the new interface exist. Delete them.
|
|
50
|
+
- New tests assert on observable outcomes through the interface, never on internal state.
|
|
51
|
+
- A test that has to change when the implementation changes is testing past the interface.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: to-story
|
|
3
|
+
description: Writes the conversation up as one user story on the project's tracker. Use when work should become a tracked issue.
|
|
4
|
+
metadata:
|
|
5
|
+
pack: epicparty/skills
|
|
6
|
+
version: 0.1.0
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# To Story
|
|
10
|
+
|
|
11
|
+
Synthesis, not interview. This skill takes what the conversation and the codebase already settled and writes it up as **one user story** on the project's tracker, governed by [`STORIES.md`](./STORIES.md).
|
|
12
|
+
|
|
13
|
+
**One goal, one story, one PR.** The goal is the outcome the user asked for, and the story carries everything reaching it takes — every layer, every edge, the tests and the translations. A large story reads as one piece of work on the tracker; four fragments of it read as noise around it.
|
|
14
|
+
|
|
15
|
+
A question you still need answered means the story is not ready to be written. Ask it, the way `/ep` `## Working rules` says — one question at a time, each with your recommendation — and write once it is answered. Decisions that turn on each other go to [`/ep-grill`](../grill/SKILL.md) first.
|
|
16
|
+
|
|
17
|
+
## 1. Gather what is settled
|
|
18
|
+
|
|
19
|
+
Work from the conversation. Where `/ep-explore` produced a brief, that brief is the material: the provenance, the seams, the prior art, the decisions the user has since made.
|
|
20
|
+
|
|
21
|
+
Without one, run `/ep-explore` now — the story names interfaces, test levels and prior art, and each of those is a fact about the codebase rather than a matter of taste. Its § 2 searches the tracker first: an issue that already describes the work is refined, never filed twice.
|
|
22
|
+
|
|
23
|
+
Check the request's premise too: the product, the area and the terms it names, against the glossary. A request that names the wrong one is corrected before anything is written (`/ep` `## Working rules`).
|
|
24
|
+
|
|
25
|
+
Use the domain's own vocabulary throughout, from the project's glossary — `/ep`'s [`DECISIONS.md`](../router/DECISIONS.md) says where it lives — and respect the ADRs in the area you are touching.
|
|
26
|
+
|
|
27
|
+
Done when every decision the conversation reached is listed in one place — the explore brief, or a summary you write in the chat now — and none is only implied.
|
|
28
|
+
|
|
29
|
+
## 2. Hold it to one goal
|
|
30
|
+
|
|
31
|
+
Name the goal in one sentence: the outcome the user asked for. Then hold everything the conversation settled against it.
|
|
32
|
+
|
|
33
|
+
- It all serves that goal: one story, however large. Go to step 3.
|
|
34
|
+
- Something serves a second goal — an outcome that would be asked for on its own, and worth shipping if the first never existed: that is a second story. Read [`SPLITTING.md`](./SPLITTING.md) before writing anything, because a set of stories has its own wiring.
|
|
35
|
+
|
|
36
|
+
Size, layers and readiness leave a story whole ([`STORIES.md`](./STORIES.md) `## Splitting`). A part still waiting on a decision holds the story until it is decided.
|
|
37
|
+
|
|
38
|
+
Done when the goal is named in one sentence, and every settled decision sits inside it or is named as a second goal.
|
|
39
|
+
|
|
40
|
+
## 3. Settle the seams
|
|
41
|
+
|
|
42
|
+
Sketch the seams the feature will be tested at, in the vocabulary of [`SEAMS.md`](./SEAMS.md). Prefer an existing seam to a new one, and the highest seam to a lower one. The fewer seams across the codebase, the better — one is the ideal.
|
|
43
|
+
|
|
44
|
+
Put them to the user and wait. A seam is an architectural decision that outlives the feature, so it is theirs. A seam the user already agreed — in their answers, a grilling, on the explore brief, or from [`/ep-architect`](../architect/SKILL.md) — is named as agreed, not asked again. Where the seams are not obvious — new modules, several places they could go — `/ep-architect` recommends them before you ask.
|
|
45
|
+
|
|
46
|
+
Done when the user has agreed the seams, or named different ones.
|
|
47
|
+
|
|
48
|
+
## 4. Write it
|
|
49
|
+
|
|
50
|
+
Write the body to [`STORIES.md`](./STORIES.md) `## The story body` — or `## Bugs` when the work is a bug. It is the **story** shape; epics and discovery tickets are written by hand, because no skill can ask its way to either.
|
|
51
|
+
|
|
52
|
+
Name each behaviour's test level from the **Test levels** setting when the settings home has one; without it, name the tool that runs the test. A level the project has no tool for yet is named anyway, and setting the tool up becomes part of the story.
|
|
53
|
+
|
|
54
|
+
Done when every accepted requirement appears in a user story, an implementation decision, a testing decision, or an out-of-scope note — anything appearing nowhere is an unrecorded decision, and it will be re-litigated in review.
|
|
55
|
+
|
|
56
|
+
## 5. Publish
|
|
57
|
+
|
|
58
|
+
Read the **Tracker**, **Labels** and **Scopes** settings now, per [Settings](#settings).
|
|
59
|
+
|
|
60
|
+
With **Tracker** at **None**, create nothing: print the story in the chat — its title and its body — for the user to keep, and say no issue was created. Skip **Labels** and the board, and name a blocking story in `## Blocked by` by its title.
|
|
61
|
+
|
|
62
|
+
- **Title**: the project's own title rule where its agent files or the **Scopes** document state one. Otherwise `<scope>: <description>`, the description a lowercase imperative phrase with no type in it — or the description alone, without scopes. Take the scope from the vocabulary, never from a guess: a scope missing from it is a review finding.
|
|
63
|
+
- **Labels**: the triage label, and stop there. Triage sets the label axes, and a ready label asserts a bar this skill cannot check, so it is triage's to apply. A label the tracker lacks is named in the report and left off.
|
|
64
|
+
- **Board**: where the project keeps one, leave its status alone. The tracker adds the item asynchronously, so anything set in the same breath races it. Getting the labels right is the whole job.
|
|
65
|
+
|
|
66
|
+
On GitHub, `gh issue create`, with a heredoc for the body. On another tracker, the way the **Tracker** setting describes.
|
|
67
|
+
|
|
68
|
+
**Refining an issue that already exists** — the one § 1 found, or a discovery body whose questions were just answered — rewrites its body in place instead (`gh issue edit <n> --body-file -`) and keeps its labels, unless the story's work is already on the default branch: that is frozen, and the change is a new issue ([`STORIES.md`](./STORIES.md) `## Frozen once live`).
|
|
69
|
+
|
|
70
|
+
Report the issue and its URL, then hand to [`/ep-triage`](../triage/SKILL.md): it owns the axes and the ready label, and a story written to the bar passes it in one quick pass. `/ep-implement` follows its **Ready** verdict.
|
|
71
|
+
|
|
72
|
+
Done when the issue exists with its title and the triage label — or, with **Tracker** at **None**, the story is printed with its title — and reads as something triage can mark ready with no further conversation.
|
|
73
|
+
|
|
74
|
+
## Settings
|
|
75
|
+
|
|
76
|
+
This skill reads four settings, all defined in `/ep` `## Settings` together with the steps that find them:
|
|
77
|
+
|
|
78
|
+
- **Tracker**, **Labels** and **Scopes** at § 5. This skill writes the issue, its labels and its title, so it asks for each one that is missing.
|
|
79
|
+
- **Test levels** at § 4, and only when it is set: without it, the story names tools.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Splitting
|
|
2
|
+
|
|
3
|
+
Reached from [`/ep-to-story`](./SKILL.md) step 2, when the conversation settled more than one **goal**. The bar is [`STORIES.md`](./STORIES.md) `## Splitting`; this file is how a set of stories is cut and wired.
|
|
4
|
+
|
|
5
|
+
Every issue carries fixed overhead — a branch, a PR, a suite run, a review, a merge — and every issue is one more line the tracker's readers have to place. A set is therefore as small as its goals: two goals, two stories. Bigger means more behaviour per story, never vaguer.
|
|
6
|
+
|
|
7
|
+
## Cut by goal
|
|
8
|
+
|
|
9
|
+
One story per goal, each carrying its goal whole: every layer, every edge, its tests and its translations. A finished story is demoable on its own, and would be worth shipping if its siblings never existed. Two stories that could only ever ship together are one story.
|
|
10
|
+
|
|
11
|
+
A set needs no parent. Two sibling stories, one blocking the other, are wired by the blocking edge alone; an epic exists only when the user wants the outcome tracked as one. A prerequisite nobody asked for that would not be asked for on its own is part of the story that needs it, not a second goal.
|
|
12
|
+
|
|
13
|
+
Where the goals depend on each other, give each story its **blocking edges** — the siblings that must close before it can start. Prefactoring that several goals share goes first: make the change easy, then make the easy change.
|
|
14
|
+
|
|
15
|
+
## Check it with the user
|
|
16
|
+
|
|
17
|
+
Present the set as a numbered list before publishing anything. Per story: the title it will carry, the goal it reaches, and what blocks it.
|
|
18
|
+
|
|
19
|
+
Then ask:
|
|
20
|
+
|
|
21
|
+
- Which goal does each story reach, and would it be asked for on its own?
|
|
22
|
+
- Should any two be one story?
|
|
23
|
+
- Does each depend only on stories that genuinely gate it?
|
|
24
|
+
|
|
25
|
+
Iterate until the user approves. A split nobody agreed to is a split that gets re-argued in review.
|
|
26
|
+
|
|
27
|
+
## Publish
|
|
28
|
+
|
|
29
|
+
In dependency order — blockers first, so each story's edges can name a real issue number.
|
|
30
|
+
|
|
31
|
+
- **Parent**, where the set has one: promote it to an epic and let its `## Stories` checklist be the children, or keep it a story with the children linked under it. Either way it stays open and otherwise untouched.
|
|
32
|
+
- **Without a parent**, each story takes the full story body from [`STORIES.md`](./STORIES.md).
|
|
33
|
+
- **Child body**: `## Parent` · `## What to build` · `## Acceptance criteria` · `## Testing Decisions` · `## Blocked by`. A child inherits its parent's problem and solution and does not repeat them — that is the one dependency `STORIES.md` permits, and it is safe only because the parent is still open.
|
|
34
|
+
- **Title**: the rule in [`/ep-to-story`](./SKILL.md) § 5.
|
|
35
|
+
- **Labels**: the triage label, plus the parent's axis labels where they are obviously the same.
|
|
36
|
+
- **Parent link**: attach each child as a sub-issue (below). Where sub-issues are unavailable, put `Part of #<parent>` at the top of the child body and add it to the parent's `## Stories` checklist.
|
|
37
|
+
- **Blocking edges**: the tracker's native issue dependencies (below), matching each child's `## Blocked by`.
|
|
38
|
+
- **Board**: where the project keeps one, leave its status alone; the tracker adds the item.
|
|
39
|
+
|
|
40
|
+
On another tracker, each of these is done the way the **Tracker** setting describes.
|
|
41
|
+
|
|
42
|
+
**Completion criterion:** every child carries its title, the triage label, a parent link, native blocking edges matching its `## Blocked by`, and acceptance criteria someone could hold a running build against. Every goal the parent named has exactly one story, and no story in the set reaches less than a goal.
|
|
43
|
+
|
|
44
|
+
Then work the frontier one story at a time with [`/ep-implement`](../implement/SKILL.md), clearing context between them.
|
|
45
|
+
|
|
46
|
+
## GitHub mechanics
|
|
47
|
+
|
|
48
|
+
These are reached rarely, so read them rather than recall them.
|
|
49
|
+
|
|
50
|
+
- **Sub-issue**: attach a child to its parent through GitHub's sub-issues endpoint (`gh api`).
|
|
51
|
+
- **Blocking edge**: `gh api --method POST repos/<owner>/<repo>/issues/<child>/dependencies/blocked_by -F issue_id=<blocker-db-id>`, where `<blocker-db-id>` is the blocker's numeric **database id** (`gh api repos/<owner>/<repo>/issues/<n> --jq .id`) — _not_ the `#number` or the `node_id`. GitHub reports `issue_dependencies_summary.blocked_by`, which counts open blockers only: that is the live gate. Where dependencies are unavailable, fall back to the `## Blocked by` list in the child body.
|
|
52
|
+
- **Reading the edges**: `gh api repos/<owner>/<repo>/issues/<n>/dependencies/blocked_by` lists what blocks an issue, and `…/dependencies/blocking` what it blocks.
|
|
53
|
+
- **Frontier**: the children with no open blocker. List the parent's open children and drop any with `issue_dependencies_summary.blocked_by > 0`.
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Stories
|
|
2
|
+
|
|
3
|
+
Every issue the `ep-*` skills write or read is a **user story**: one user-visible outcome, written from the side of whoever benefits, with enough decided that someone can build it and someone else can tell whether it happened.
|
|
4
|
+
|
|
5
|
+
This file says what goes *in* an issue. The project's own rules add to it — its title shape, its label axes, its domain roles — and win where they differ.
|
|
6
|
+
|
|
7
|
+
Treat the backlog as a production backlog. An issue is read months later by someone who was not in the conversation that produced it — often an agent with no context beyond the text. Write for that reader.
|
|
8
|
+
|
|
9
|
+
Before writing or editing one, read the neighbouring issues in the same area. The backlog's existing shape is the pattern to match, and it carries decisions this file cannot.
|
|
10
|
+
|
|
11
|
+
The label names below — the triage, discovery, epic and ready labels — are roles. The **Labels** setting (`/ep` `## Settings`) maps each role to the project's own label.
|
|
12
|
+
|
|
13
|
+
## The unit
|
|
14
|
+
|
|
15
|
+
One issue is **one goal**: the outcome someone asked for, carried whole — backend, frontend, tests and translations, every layer and every edge that reaching it takes. Shippable on its own and worth shipping on its own. A story is as large as its goal and never smaller: a large story reads as one piece of work on the tracker, where its fragments read as noise around the work that matters.
|
|
16
|
+
|
|
17
|
+
- **Written from the beneficiary's side.** A story names who gets something and what they get. The implementation is how, and it comes later in the body.
|
|
18
|
+
- **In domain vocabulary.** Use the terms from the project's glossary — `/ep`'s [`DECISIONS.md`](../router/DECISIONS.md) says where it lives — capitalised as the domain capitalises them. An issue that invents its own words for existing concepts is a rename waiting to happen.
|
|
19
|
+
- **Not a task list.** Tasks belong to the PR that delivers the story. An issue whose body is a checklist of files to touch has skipped the part that makes it a story.
|
|
20
|
+
- **Independent, or explicitly blocked.** If it needs another issue first, say so in `## Blocked by` and wire the tracker's native dependency.
|
|
21
|
+
|
|
22
|
+
## Four shapes
|
|
23
|
+
|
|
24
|
+
Which shape an issue takes depends on how settled it is, not on who is writing it.
|
|
25
|
+
|
|
26
|
+
| Shape | Marked by | When | Body |
|
|
27
|
+
| --- | --- | --- | --- |
|
|
28
|
+
| **Epic** | the epic label | An outcome made of several goals, whose stories aren't known yet | `## Outcome` · `## Problem` · `## Stories` (checklist, each a future issue) · `## Success signals` · `## Out of scope` · `## Open questions` |
|
|
29
|
+
| **Story** | the default | The work is understood well enough to build | [The story body](#the-story-body) |
|
|
30
|
+
| **Child story** | a parent link | One goal of an epic or story that is still open | `## Parent` · `## What to build` · `## Acceptance criteria` · `## Testing Decisions` · `## Blocked by` |
|
|
31
|
+
| **Discovery** | the discovery label | The outcome is wanted but the decisions aren't made | `## Context` · `## Scope (to be decided)` · `## Acceptance (draft)`, and the open decisions listed as questions |
|
|
32
|
+
|
|
33
|
+
A discovery ticket is a story that admits it isn't ready. It becomes a story when its decisions are answered — that rewrite is expected, and legitimate right up until the work ships.
|
|
34
|
+
|
|
35
|
+
A **child story** is a full story in every way that matters: its own title, its own labels, its own PR, demoable on its own. What it does not do is repeat its parent. The problem and the solution are stated once, on the parent, and the child links to them. That is the one place an issue may depend on another issue for its meaning, and it is safe precisely because the parent is open: both are live, and a reader following the link finds a document that is still being maintained.
|
|
36
|
+
|
|
37
|
+
**This is not the refinement case.** A refinement of a *shipped* story restates it in full, because the original is frozen and can no longer be corrected. A child of an *open* parent links instead. The distinguishing question is the same one everything else here turns on: has the work landed on the default branch?
|
|
38
|
+
|
|
39
|
+
Epics and discovery tickets are written by hand. No form or skill can ask its way to either shape.
|
|
40
|
+
|
|
41
|
+
## The story body
|
|
42
|
+
|
|
43
|
+
The default shape, in this order:
|
|
44
|
+
|
|
45
|
+
<story-template>
|
|
46
|
+
|
|
47
|
+
## Problem Statement
|
|
48
|
+
|
|
49
|
+
The problem the user faces, from the user's perspective: what someone cannot do today, or what goes wrong when they try. Cite the code that makes it true (`path/file.ts:42`), so a reader can check the premise instead of trusting it.
|
|
50
|
+
|
|
51
|
+
## Solution
|
|
52
|
+
|
|
53
|
+
The solution to that problem, from the user's perspective. Still no implementation.
|
|
54
|
+
|
|
55
|
+
## User Stories
|
|
56
|
+
|
|
57
|
+
A LONG, numbered list. Each one:
|
|
58
|
+
|
|
59
|
+
1. As a <Role>, I <capability>, so that <outcome>
|
|
60
|
+
|
|
61
|
+
<user-story-example>
|
|
62
|
+
1. As a Shop Owner, I see which orders are still unpaid, so that I know whom to remind before I ship
|
|
63
|
+
</user-story-example>
|
|
64
|
+
|
|
65
|
+
Cover the whole surface of the change, edges included: what someone without permission sees, what happens when the thing is absent, what an existing user experiences after it ships. Roles are domain roles.
|
|
66
|
+
|
|
67
|
+
## Implementation Decisions
|
|
68
|
+
|
|
69
|
+
The durable decisions:
|
|
70
|
+
|
|
71
|
+
- The modules built or modified, and the interfaces that change
|
|
72
|
+
- Schema changes and API contracts
|
|
73
|
+
- Architectural choices, and the alternatives rejected with the reason
|
|
74
|
+
- Technical clarifications the developer gave
|
|
75
|
+
|
|
76
|
+
Keep file paths and code snippets out — they go stale fastest. Exception: where a prototype produced a snippet that encodes a decision more precisely than prose can — a state machine, a reducer, a schema, a type shape — inline it, trimmed to the decision-rich part, and note that it came from a prototype.
|
|
77
|
+
|
|
78
|
+
## Testing Decisions
|
|
79
|
+
|
|
80
|
+
- The externally observable behaviour to test
|
|
81
|
+
- The level each behaviour is tested at, from the **Test levels** setting where it is set, and in which module
|
|
82
|
+
- The prior art to copy from: the tests in this area that already work
|
|
83
|
+
|
|
84
|
+
## Out of Scope
|
|
85
|
+
|
|
86
|
+
Each boundary, and why it sits outside. This section is what stops scope creep from being argued in a PR review.
|
|
87
|
+
|
|
88
|
+
## Blocked by
|
|
89
|
+
|
|
90
|
+
Issue references this story waits on, matching the tracker's native dependencies. Omit the section when there are none.
|
|
91
|
+
|
|
92
|
+
</story-template>
|
|
93
|
+
|
|
94
|
+
Add `## Further Notes` after these only when something genuinely fits nowhere above.
|
|
95
|
+
|
|
96
|
+
**Completion criterion:** every accepted requirement appears in a user story, an implementation decision, a testing decision, or an out-of-scope note. Anything that appears nowhere is an unrecorded decision, and it will be re-litigated in review.
|
|
97
|
+
|
|
98
|
+
## Writing the story sentence
|
|
99
|
+
|
|
100
|
+
`As a <Role>, I <capability>, so that <outcome>.`
|
|
101
|
+
|
|
102
|
+
- **Role is a domain role**, not "a user" as a catch-all. If every story starts `As a User`, the roles haven't been thought about.
|
|
103
|
+
- **Capability is what the person does**, present tense, concrete enough to demo.
|
|
104
|
+
- **`so that` is the value, and never a restatement of the capability.** "so that I can upload an avatar" after "I upload an avatar" says nothing; "so that people recognise me in a list" is the reason the work is worth doing — and the thing to check the built feature against.
|
|
105
|
+
|
|
106
|
+
Each story is a sentence someone can hold a running build against and answer yes or no. In the **story** shape that is the acceptance criteria — it plus `## Testing Decisions`, with no separate section. A **child story** is the exception, and carries `## Acceptance criteria` explicitly: it has no `## User Stories` section for the criteria to be implied by, and it is the unit somebody picks up and builds.
|
|
107
|
+
|
|
108
|
+
Cover the edges as their own stories, not as caveats: what the person without permission sees, what happens when the thing is absent, what an existing user experiences after the change ships.
|
|
109
|
+
|
|
110
|
+
## Bugs
|
|
111
|
+
|
|
112
|
+
A bug is a story about behaviour that already exists and is wrong. Same title rule, the bug type, and a body that gives:
|
|
113
|
+
|
|
114
|
+
- **Observed** — what happens, on which surface, for which role.
|
|
115
|
+
- **Expected** — the behaviour the product promised. If a shipped issue promised it, link it.
|
|
116
|
+
- **Reproduction** — steps, the failing test, or a link to the error or the session that shows it.
|
|
117
|
+
- **Scope** — which surfaces are affected.
|
|
118
|
+
|
|
119
|
+
## Enabler stories
|
|
120
|
+
|
|
121
|
+
Chores, refactors and infrastructure are still stories — the beneficiary is a developer, an operator, or the business, and the value goes in the `so that`. Write `As a developer, I ..., so that ...`, and don't contort a dependency bump into a customer-facing sentence. What is not acceptable is an enabler with no stated value: if the `so that` can't be written, the work needs justifying before it needs building.
|
|
122
|
+
|
|
123
|
+
## Ready
|
|
124
|
+
|
|
125
|
+
An issue earns a ready label — for an agent, or for a human — when all of this is true; nothing here is a formality:
|
|
126
|
+
|
|
127
|
+
- The title carries a scope from the **Scopes** vocabulary, where the project uses scopes.
|
|
128
|
+
- Every label axis the project defines is set, and the triage label is gone.
|
|
129
|
+
- The problem is stated with evidence, not asserted.
|
|
130
|
+
- Every part of the surface is covered by a numbered story.
|
|
131
|
+
- Testing decisions name a level and prior art.
|
|
132
|
+
- Out of scope is explicit.
|
|
133
|
+
- Blockers are linked and the open ones are known.
|
|
134
|
+
- **No decision is left implicit.** A question the body raises and does not answer means the issue needs info or is a discovery ticket, not ready.
|
|
135
|
+
|
|
136
|
+
Ready for an agent specifically means an agent can deliver it with no further conversation. If you'd expect a question back, it isn't ready.
|
|
137
|
+
|
|
138
|
+
## Done
|
|
139
|
+
|
|
140
|
+
The story is done when the change is on the default branch, every numbered story reads true against the shipped product, and the tests named in `## Testing Decisions` exist and run in the project's gate. The merged PR closes the issue.
|
|
141
|
+
|
|
142
|
+
## Frozen once live
|
|
143
|
+
|
|
144
|
+
An issue records what was asked and what shipped for it. That record is only worth reading if it is not rewritten afterwards.
|
|
145
|
+
|
|
146
|
+
**Before the work is live** — nothing merged to the default branch for it — the body is working material. Sharpen it, split it, rescope it, rewrite the acceptance. Refining a story nobody has built is the point of having it written down.
|
|
147
|
+
|
|
148
|
+
**Once the delivering change lands on the default branch, the body is frozen.** Do not edit a shipped issue to reflect what changed later. Each of these is a **new issue**:
|
|
149
|
+
|
|
150
|
+
| What happened after it went live | What you do |
|
|
151
|
+
| --- | --- |
|
|
152
|
+
| Scope grew or shrank | New issue |
|
|
153
|
+
| Core logic changed | New issue |
|
|
154
|
+
| Real acceptance turned out different from what shipped | New issue |
|
|
155
|
+
| Polish, follow-up, or a bug found later | New issue |
|
|
156
|
+
|
|
157
|
+
Editing a shipped story rewrites history. The issue stops describing what was actually built and reviewed, the PR that closed it no longer matches its own issue, and the trail from "why does the product behave like this" back to "who asked for it, when, and against what acceptance" breaks. Two stories in sequence are readable; one story silently overwritten is not.
|
|
158
|
+
|
|
159
|
+
Still fine on a closed issue: labels, board status, a title scope that was wrong, links, and comments — metadata about the story, not the story. If a shipped body is factually wrong about what shipped, **correct it in a comment**, never in the body.
|
|
160
|
+
|
|
161
|
+
### The refinement issue
|
|
162
|
+
|
|
163
|
+
- A normal issue with its own labels — an improvement when it moves the behaviour further, a bug when live behaviour doesn't match the story that shipped.
|
|
164
|
+
- **Restates the story in full.** Never a diff against the original; a reader must not need the old issue to understand what is being asked now.
|
|
165
|
+
- Names its origin in the body: `Refines #<n>`.
|
|
166
|
+
- Gets a pointer back: comment `Refined by #<m>` on the original, so the trail reads in both directions.
|
|
167
|
+
|
|
168
|
+
A feature that keeps moving therefore has a chain of closed stories, one per delivered increment, and that chain is the history of the feature.
|
|
169
|
+
|
|
170
|
+
## Splitting
|
|
171
|
+
|
|
172
|
+
**One goal, one story, one PR.** A story splits on one condition: it carries a second goal — an outcome that would be asked for on its own, and would be worth shipping if the first never existed. Nothing else splits it. The layers stay together, because neither half of "backend for X" and "frontend for X" is worth shipping alone. The size of the diff stays together, because bigger means more behaviour per issue, never vaguer. A part still waiting on a decision holds the story until it is decided, because the decided half shipped alone is a fragment. A surface the numbered stories missed becomes one more numbered story on the same issue.
|
|
173
|
+
|
|
174
|
+
A split makes siblings, one goal each, or promotes the parent to an epic whose `## Stories` checklist names them. Every child carries its parent link and its native dependencies, so the set still reads as one outcome. [`SPLITTING.md`](./SPLITTING.md) is the mechanics.
|
|
175
|
+
|
|
176
|
+
Two shapes look like a split and are not:
|
|
177
|
+
|
|
178
|
+
- **A landing in stages.** A schema change on live data widens, backfills and narrows, and the narrow can only deploy after the widen has. That is one story with a PR per stage: `Part of #<n>` on every stage but the last, `Closes #<n>` on the last. The story stays open between stages, and the next stage starts from `/ep-implement` on the same issue.
|
|
179
|
+
- **A refactor with a wide blast radius.** Expand, migrate and contract are the commits of one PR, not a child issue each. Under a rebase or merge-commit merge each stage reaches the default branch as its own commit, so the stages read there without a child issue each; under a squash, the PR body names them in the commit it becomes (**Merge strategy**, `/ep` `## Settings`).
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: triage
|
|
3
|
+
description: Judges one existing issue — still viable, decided enough, labelled right — and ends in one verdict. Use when asked whether a story is still worth building, is decided enough to build, or is mislabelled.
|
|
4
|
+
metadata:
|
|
5
|
+
pack: epicparty/skills
|
|
6
|
+
version: 0.1.0
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Triage
|
|
10
|
+
|
|
11
|
+
One issue, one **verdict**. Triage reads a story the way the next agent will — text and comments only, no memory of the conversation that produced it — then tests that text against the default branch as it is today, and says what should happen to it next.
|
|
12
|
+
|
|
13
|
+
It answers three questions, in order: is the outcome still wanted and still true, are the decisions in the body decided or merely written down, and do the labels say what the evidence says. The verdict follows from the answers, and every line of it cites something.
|
|
14
|
+
|
|
15
|
+
Triage reads until § 7. The labels and the body are the user's to change, and the verdict is a recommendation until they take it.
|
|
16
|
+
|
|
17
|
+
Labels are named here by **role** — the triage, info, discovery, ready and won't-fix labels — and the **Labels** setting maps each role to the project's own label and defines its axes.
|
|
18
|
+
|
|
19
|
+
## 1. Read the record
|
|
20
|
+
|
|
21
|
+
Read the **Tracker** setting now, per [Settings](#settings): every triage ends in a change to the issue, so a missing one is asked for here. With **None**, stop: triage judges an issue, and the project keeps none. Say so.
|
|
22
|
+
|
|
23
|
+
Read the issue with its comments, labels, assignees, dependency edges and linked PRs — on GitHub, `gh issue view <n> --comments`, with the edges from [`SPLITTING.md`](../to-story/SPLITTING.md) `## GitHub mechanics`. Where the project keeps a board, read the issue's status on it too. Note who filed it and when: age is evidence in § 2.
|
|
24
|
+
|
|
25
|
+
Then restate the outcome in one or two sentences, in the domain's own words from the project's glossary — `/ep`'s [`DECISIONS.md`](../router/DECISIONS.md) says where it lives. A story you cannot restate is one you cannot judge.
|
|
26
|
+
|
|
27
|
+
Done when you hold the body, every comment, the labels, the board status where there is a board, and the dependency edges, and the user would recognise their ask in your restatement.
|
|
28
|
+
|
|
29
|
+
## 2. Test the premise
|
|
30
|
+
|
|
31
|
+
The body asserts a problem. Check it against the default branch today, on three fronts:
|
|
32
|
+
|
|
33
|
+
- **Still true.** Read the code the `## Problem Statement` cites; where it cites none, find the code that makes the problem real. A premise the codebase no longer bears out is the most common way an old story dies.
|
|
34
|
+
- **Not overtaken.** Search closed issues and merged PRs for the same work, with the story's own nouns — on GitHub, `gh issue list --search "<keywords>" --state all` and `gh pr list --search "<keywords>" --state merged`. A closed hit that delivered the outcome closes this one; an adjacent hit is context.
|
|
35
|
+
- **Still consistent.** The ADRs in the area, the glossary's vocabulary, and any story shipped since. A story that contradicts a later decision is superseded, however good it looked when filed.
|
|
36
|
+
|
|
37
|
+
Read the blockers too. A blocker closed as *not planned* takes its dependants with it unless something says otherwise.
|
|
38
|
+
|
|
39
|
+
Done when each front has a finding with a citation — a path, an issue, an ADR — and you can say *viable*, *overtaken* or *superseded* with the evidence beside it.
|
|
40
|
+
|
|
41
|
+
## 3. Grade the decisions
|
|
42
|
+
|
|
43
|
+
Walk the decisions the body's shape carries ([`STORIES.md`](../to-story/STORIES.md) `## Four shapes`): in a story, `## Solution`, `## Implementation Decisions`, `## Testing Decisions` and `## Out of Scope`; in a discovery body, each open question, missing until it is answered; in a bug, the `## Bugs` fields — observed, expected, reproduction — each present or missing. Grade every decision:
|
|
44
|
+
|
|
45
|
+
| Grade | Meaning |
|
|
46
|
+
| --- | --- |
|
|
47
|
+
| **held** | Carries its reason, or names the alternative it rejected. |
|
|
48
|
+
| **thin** | Asserted with no reason, or with a reason the default branch has since falsified. The decision taken without much thought. |
|
|
49
|
+
| **missing** | A question the body raises and does not answer, or a surface no numbered story covers — the person without permission, the thing that is absent, the existing user after the change ships. |
|
|
50
|
+
|
|
51
|
+
Then hold the whole against the **Ready bar** in [`STORIES.md`](../to-story/STORIES.md) `## Ready`, and the **one-goal** test in [`/ep-to-story` § 2](../to-story/SKILL.md#2-hold-it-to-one-goal) — a second outcome that would be asked for on its own.
|
|
52
|
+
|
|
53
|
+
A question the code, an ADR or a shipped story already answers is a fact, not a decision: cite the answer and grade it **held**.
|
|
54
|
+
|
|
55
|
+
A thin decision is not yours to re-make. Put a recommendation beside it, with its reason, and leave the choice where it belongs.
|
|
56
|
+
|
|
57
|
+
Done when every decision in the body carries a grade, every gap against the Ready bar is named, and the size question has an answer.
|
|
58
|
+
|
|
59
|
+
## 4. Check the labels
|
|
60
|
+
|
|
61
|
+
Read the **Labels** setting now, per [Settings](#settings), and **Scopes** when it is set. Then check each axis the **Labels** setting defines, current against what the evidence says:
|
|
62
|
+
|
|
63
|
+
- **The title** carries a scope from the **Scopes** vocabulary, where the project uses scopes. An axis that names the same thing as the scope — a product, a layer — agrees with it.
|
|
64
|
+
- **A priority axis** reads off what the setting says each value means, and off the neighbours: what else in this area sits at that priority, and what is blocked on this one. A low priority that a higher one waits on is mislabelled.
|
|
65
|
+
- **A type axis** matches what the body actually changes.
|
|
66
|
+
- **The process label** matches the verdict you are about to give. Where the project keeps a board whose status follows the labels, the status matches them too.
|
|
67
|
+
|
|
68
|
+
A story still carrying the triage label gets every axis proposed here, and dropping the triage label is part of the delta. A label the verdict needs and the tracker lacks is named in the report, never created here — only the **Labels** setting's own step offers to create labels, when it is first answered. With the **Labels** setting at **None**, this step checks the title alone.
|
|
69
|
+
|
|
70
|
+
Done when each axis has a current value, a proposed value and a reason — or reads *unchanged*.
|
|
71
|
+
|
|
72
|
+
## 5. Give the verdict
|
|
73
|
+
|
|
74
|
+
Take the first row that matches:
|
|
75
|
+
|
|
76
|
+
| Verdict | When | It leads to |
|
|
77
|
+
| --- | --- | --- |
|
|
78
|
+
| **Close** | Overtaken, superseded, or the premise is gone | Close the issue with the reason; the won't-fix label where it was declined rather than done |
|
|
79
|
+
| **Needs info** | A fact only the reporter holds, and nothing can be judged without it | The info label, and the question as a comment |
|
|
80
|
+
| **Split** | Carries a second goal | [`SPLITTING.md`](../to-story/SPLITTING.md), then triage each child |
|
|
81
|
+
| **Grill** | Decisions that turn on each other, so tightly that no list of questions can be written up front | The discovery label; the decisions in § 6 seed [`/ep-grill`](../grill/SKILL.md), on the user's word |
|
|
82
|
+
| **Settle** | Thin or missing decisions, each standing alone | The questions, listed in the report with your recommendation beside each one you can defend, then asked one at a time (`/ep` `## Working rules`); on their answers, the body is edited to carry them, and the ready label follows when the bar then passes |
|
|
83
|
+
| **Ready** | Passes the Ready bar | The ready label for an agent or for a human; the labels are the whole change |
|
|
84
|
+
|
|
85
|
+
The rows sit in this order because each makes the rows below it moot: nothing on a dead story is worth grilling, and a story that is really two cannot be graded as one.
|
|
86
|
+
|
|
87
|
+
Done when one verdict is named, and each row above it has been ruled out in a sentence.
|
|
88
|
+
|
|
89
|
+
## 6. Report
|
|
90
|
+
|
|
91
|
+
The report, and the note § 7 posts, name issues with the prefix the **Tracker** setting gives.
|
|
92
|
+
|
|
93
|
+
One shape, every time, so a verdict reads the same on any story. Details first and the ask last, because the user reads the chat from the bottom. In this sample the project's prefix is `SHOP-`, its axes are `priority` and `type`, and its board has a `Status`:
|
|
94
|
+
|
|
95
|
+
```md
|
|
96
|
+
## [SHOP-184](…/issues/184) **"billing/invoices: retire the PDF export"**
|
|
97
|
+
|
|
98
|
+
Filed 2026-06-02 by jdoe · open · Backlog · `priority:p2` `type:refactor`
|
|
99
|
+
Blocked by — · Blocks SHOP-190 · PRs —
|
|
100
|
+
|
|
101
|
+
**Asks:** Shop Owners lose the legacy PDF export, and the invoice page's print view is the only printable invoice.
|
|
102
|
+
|
|
103
|
+
### Premise — viable
|
|
104
|
+
- Still true: `src/billing/export.ts:88` serves the legacy export today.
|
|
105
|
+
- Not overtaken: SHOP-172 (closed) removed the export *button*, not the endpoint. No merged PR touches it.
|
|
106
|
+
- Consistent: ADR 0009 names the print view as the invoice's one printable form.
|
|
107
|
+
|
|
108
|
+
### Decisions
|
|
109
|
+
| Decision | Grade | Note |
|
|
110
|
+
| --- | --- | --- |
|
|
111
|
+
| Drop the stored PDFs in the same PR | thin | No reason given. Recommend a later stage of this story: removing live data lands after the code that stops reading it has deployed. |
|
|
112
|
+
| A journey test covers the print view | held | Prior art named. |
|
|
113
|
+
| What a Shop Owner with a bookmarked export link sees after the change | missing | No numbered story covers it. |
|
|
114
|
+
|
|
115
|
+
### Labels
|
|
116
|
+
| Axis | Now | Proposed | Why |
|
|
117
|
+
| --- | --- | --- | --- |
|
|
118
|
+
| `priority` | `p2` | `p1` | SHOP-190 is `p1` and blocked on this. |
|
|
119
|
+
| process | — | `ready-for-agent` | Once both answers are in the body, it passes the bar. |
|
|
120
|
+
| `Status` | Backlog | Ready | Follows the process label. |
|
|
121
|
+
|
|
122
|
+
### Verdict — Settle
|
|
123
|
+
Close ruled out: the premise holds. Needs info and Split ruled out: nothing only the reporter knows, one goal. Grill ruled out: the two decisions stand alone.
|
|
124
|
+
|
|
125
|
+
1. **The bookmarked export link** — what does a Shop Owner who follows one see after the change?
|
|
126
|
+
➡️ A redirect to that invoice's print view: ADR 0009 makes it the one printable form, and the bookmark still lands somewhere useful.
|
|
127
|
+
2. **The stored PDFs** — are they deleted in this PR?
|
|
128
|
+
➡️ No, in a later stage of this story: removing live data lands after the code that stops reading it has deployed.
|
|
129
|
+
|
|
130
|
+
### Your turn
|
|
131
|
+
1. Answer the two questions — I ask them one at a time, next — and I write the answers into the body, set the labels and status above, and post this note.
|
|
132
|
+
2. Say **later** and I post this note with the questions, and add the discovery label until they are answered.
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
A run that stops before § 5 — no tracker, an issue it cannot read — keeps the header with what it could read, says under `### Verdict — none` where it stopped and why, and ends with **Your turn**.
|
|
136
|
+
|
|
137
|
+
Under a **Ready** verdict — or a **Settle** that leaves the story ready — **Your turn** also offers to queue it, where `/ep-deliver`'s **Ready signal** is set to a board column or a label: *"Say **queue** and I move it to `Queued` so `/ep-deliver` picks it up."* Without that setting, or with it at **None**, offer nothing.
|
|
138
|
+
|
|
139
|
+
Sections earn their size from the story. A `Premise — viable` line with three citations is a complete section, and an empty `Decisions` table under a `Ready` verdict says the body has nothing left to grade.
|
|
140
|
+
|
|
141
|
+
Done when the user can act on the report without opening the issue.
|
|
142
|
+
|
|
143
|
+
## 7. Apply, on the user's word
|
|
144
|
+
|
|
145
|
+
Nothing above wrote. When the user takes the verdict:
|
|
146
|
+
|
|
147
|
+
- **Labels and board status in one pass**, where the project keeps a board whose status follows the labels — per `/ep`'s [`BOARD.md`](../router/BOARD.md). Set separately, the two drift.
|
|
148
|
+
- **Settle** on a discovery body sends the answers to [`/ep-to-story`](../to-story/SKILL.md), which rewrites it as a story. On a story, it edits the body: each recommendation lands in `## Implementation Decisions` or `## Testing Decisions` as a decision with its reason, and a surface the stories missed becomes a numbered story. The body is working material until the work is on the default branch ([`STORIES.md`](../to-story/STORIES.md) `## Frozen once live`); an open story whose work is already live is a **Close**, never an edit.
|
|
149
|
+
- **Settle** left unanswered — the user said **later** — posts the note with the questions, and adds the discovery label.
|
|
150
|
+
- **Grill**, on the user's word, continues in this window — [`/ep-grill`](../grill/SKILL.md) on the seed decisions, then [`/ep-to-story`](../to-story/SKILL.md) rewrites the body from the same context, per [`/ep` § 4](../router/SKILL.md#4-walk-it).
|
|
151
|
+
- **Close** carries the reason and the issue or PR that made it moot.
|
|
152
|
+
- **Needs info** posts the question to the reporter as the comment, and adds the info label.
|
|
153
|
+
- **Split** publishes the set through [`SPLITTING.md`](../to-story/SPLITTING.md), once the user approved it, then triages each new story.
|
|
154
|
+
- **Ready** changes the labels and nothing else.
|
|
155
|
+
- **A title change** § 4 proposed: on GitHub, `gh issue edit <n> --title`.
|
|
156
|
+
- **Queue**, only on the user's explicit yes to that offer: set the **Ready signal** — move the card to its column per `/ep`'s [`BOARD.md`](../router/BOARD.md), or add its label. Their yes is them queuing it; never queue on a bare **apply**.
|
|
157
|
+
- **The triage note goes on the issue as a comment** — the verdict, the premise findings and the label delta — so the next agent reading the comments finds the judgement where the story lives.
|
|
158
|
+
|
|
159
|
+
On GitHub, `gh issue edit <n> --add-label … --remove-label …`, `gh issue comment` and `gh issue close --comment`. On another tracker, the way the **Tracker** setting describes.
|
|
160
|
+
|
|
161
|
+
Done when the issue carries the labels, status, body and comment the verdict named, and the report says which.
|
|
162
|
+
|
|
163
|
+
## Guardrails
|
|
164
|
+
|
|
165
|
+
- **Triage reads.** Every write to the issue waits for § 7 and the user's word. A verdict is a recommendation; the labels are theirs. A missing setting is the one earlier write: it is written when the user answers it.
|
|
166
|
+
- **Every row cites.** A premise finding names a path, an issue or an ADR. A label change names the neighbour or the rule that moved it. A grade with no note is an opinion.
|
|
167
|
+
- **Ready is a real verdict.** A story with nothing left to decide gets a ready label, not a manufactured question. A grilling nobody needed costs the user an hour.
|
|
168
|
+
- **Facts are yours, decisions are the user's.** A thin decision gets a recommendation beside it and stays open until they close it.
|
|
169
|
+
- **Depth follows stakes.** A low-priority copy tweak earns a two-minute premise check; a story on a critical path earns every step in full. Say which you did.
|
|
170
|
+
|
|
171
|
+
## Settings
|
|
172
|
+
|
|
173
|
+
This skill reads three settings, all defined in `/ep` `## Settings` together with the steps that find them. Triage ends in a change to the issue, so it asks for the two it writes with when they are missing:
|
|
174
|
+
|
|
175
|
+
- **Tracker** at § 1, with its issue prefix.
|
|
176
|
+
- **Labels** at § 4.
|
|
177
|
+
- **Scopes** at § 4, read when set: without it, the title check is skipped.
|
|
178
|
+
|
|
179
|
+
It also reads `/ep-deliver`'s **Ready signal** at § 6 when it is set, to offer queuing a ready story, and never asks for it.
|