@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,142 @@
|
|
|
1
|
+
# Copy
|
|
2
|
+
|
|
3
|
+
Rules for every word a person reads in a product: labels, descriptions, buttons, errors, empty states, banners, hero text. The project's own copy rules — its reader, its protected terms, its languages, how a string reaches the product — are the **Copy standard** setting, and they add to these and win where they differ.
|
|
4
|
+
|
|
5
|
+
## Write for the reader, not the technician
|
|
6
|
+
|
|
7
|
+
The reader is the person using the product, as the **Copy standard** describes them. By default they are distracted, they read once, and they did not build the product. Every line is judged by that reader: would they know what it means, and what to do, on one pass?
|
|
8
|
+
|
|
9
|
+
The **technician** is the failure mode. Technician copy names the mechanism; reader copy names the outcome for the reader. Rewrite any line that describes the system to the reader:
|
|
10
|
+
|
|
11
|
+
| Technician | Reader |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| Manage your availability timeslots | Pick the times that work for you |
|
|
14
|
+
| Session token expired | You were signed out. Open your link again to get back in. |
|
|
15
|
+
| Username is your unique public identifier | Your username is how friends find you |
|
|
16
|
+
| Enable public reservation name visibility | Show guests' names on the shared page |
|
|
17
|
+
|
|
18
|
+
Three checks catch technician copy:
|
|
19
|
+
|
|
20
|
+
- **A domain term is a name, not a definition.** The project's glossary defines terms so the people building it agree; it does not license them in copy. Some terms are user-facing, and most internal ones are not. When a glossary term carries a user-facing rendering, use it. When it does not, describe what the reader does or gets.
|
|
21
|
+
- **A benefit before a feature.** Hero text, descriptions, banners and field descriptions open with what changes for the reader. The feature name, if it is needed at all, comes second.
|
|
22
|
+
- **A verb the reader performs.** "Configure", "manage", "enable", "generate", "validate" describe the system. "Pick", "share", "invite", "play", "show" describe the reader.
|
|
23
|
+
|
|
24
|
+
## Protected terms and product names
|
|
25
|
+
|
|
26
|
+
- A **protected term** keeps its exact spelling and capitals, untranslated, everywhere. The **Copy standard** lists them.
|
|
27
|
+
- Product names are proper nouns in copy, written the way the brand writes them — never an internal code form.
|
|
28
|
+
- Every other glossary term is a plain word in copy. The glossary's capitals do not reach the screen.
|
|
29
|
+
- Same term for the same thing everywhere. If it is "Archive" in the menu, it is not "Move to storage" in the toast.
|
|
30
|
+
|
|
31
|
+
A deliberate brand voice is not a defect. Raise a departure from plain language only when it creates ambiguity, inconsistency, translation risk, or a tone the stakes do not support.
|
|
32
|
+
|
|
33
|
+
## One voice, tone by stakes
|
|
34
|
+
|
|
35
|
+
| Context | Tone |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| Hero, success, onboarding, empty states, descriptions | Warm, can be light |
|
|
38
|
+
| Routine actions, settings, field descriptions | Neutral, minimal |
|
|
39
|
+
| Errors, destructive confirmations | Calm, plain, zero playfulness |
|
|
40
|
+
| Data loss, security, account deletion | Serious, explicit |
|
|
41
|
+
|
|
42
|
+
## Speak to "you"
|
|
43
|
+
|
|
44
|
+
Instructional copy says "you", never "the user", "the guest", "the admin". A description says what the field does for you, not who the field is for.
|
|
45
|
+
|
|
46
|
+
"We" is for warm, low-stakes copy where the product is talking: a hero, a welcome. In an error it reads as deflection. Prefer "Unable to save" to "We're having trouble saving".
|
|
47
|
+
|
|
48
|
+
Possessives are rare in labels and headings: "Favorites", not "Your favorites". In a sentence that names the reader's own things — "Your cart is empty" — one reads naturally. One perspective per flow.
|
|
49
|
+
|
|
50
|
+
## Plain words, short lines
|
|
51
|
+
|
|
52
|
+
- Choose words a twelve-year-old gets on the first pass. Delete every word that does no work.
|
|
53
|
+
- One idea per sentence, under 20 words. A description is one or two sentences; a hero subtitle is one.
|
|
54
|
+
- Use everyday contractions: "you're", "it's", "we'll". Spell out "cannot" and "do not" in errors and destructive confirmations, where a misread negative costs the most.
|
|
55
|
+
- No idioms, no puns, no humour that will not survive translation. Translations run longer — German and Italian up to 40% over English — so a string that fills its box in English breaks in the longest language the product ships.
|
|
56
|
+
- Humour is seasoning: one light touch per surface at most, in the warm tone row only. Never in an error, a confirmation or a notification.
|
|
57
|
+
- Exclamation marks are a budget: one per screen, only for a real celebration. None in an error, a setting or a serious moment.
|
|
58
|
+
- No unnecessary gender: "Guests can pick a time", not "each guest picks his or her time".
|
|
59
|
+
- Match the device: "tap" on touch, "click" with a pointer, "select" when either is possible.
|
|
60
|
+
- A number or a name goes in a placeholder, never spliced from fragments. Word order changes per language.
|
|
61
|
+
|
|
62
|
+
## Buttons start with the verb
|
|
63
|
+
|
|
64
|
+
A button names the action: "Send", "Save changes", "Create project". Never "OK", "Yes", "Let's go". One to three words, under 20 characters, no punctuation.
|
|
65
|
+
|
|
66
|
+
A confirmation button repeats the consequence so the dialog is answerable without its body: "Delete this project?" offers `Delete project` and `Cancel`. The body states what cannot be undone, in one line. The title is the question, never "Are you sure?".
|
|
67
|
+
|
|
68
|
+
A multi-step flow keeps one vocabulary: "Get started" to enter, "Continue" to advance, "Done" to finish. Pick one advance word and keep it.
|
|
69
|
+
|
|
70
|
+
## Links say where they go
|
|
71
|
+
|
|
72
|
+
Link text stands alone, because screen readers list links out of context. "Read the billing docs", never "click here". Two "Learn more" on one page collide; suffix each one: "Learn more about invites".
|
|
73
|
+
|
|
74
|
+
Link straight to a referenced setting. "Notification settings" as a link, not "go to Settings, then Notifications".
|
|
75
|
+
|
|
76
|
+
## Fields: label, description, example, fix
|
|
77
|
+
|
|
78
|
+
Each part of a field does one job:
|
|
79
|
+
|
|
80
|
+
- The **label** names the thing in the reader's words. "Username", "Project name", "Your times".
|
|
81
|
+
- The **description** says what it is for and the one rule the reader will hit. It is not the validation policy. "Your username is how friends find you. Letters, numbers, dots and underscores." The rest of the policy lives in the error, when it is needed.
|
|
82
|
+
- The **placeholder** is an example of the expected format: `name@example.com`, `alex.smith`. It vanishes on input, so a visible label stays.
|
|
83
|
+
- The **error** is an instruction, beside the field that failed, phrased as the fix: "Use at least 3 characters", not "Too short". Positive over negative: "Use only letters", not "No numbers or symbols".
|
|
84
|
+
|
|
85
|
+
A toggle describes the ON state: "Show guests' names", never "Don't hide names".
|
|
86
|
+
|
|
87
|
+
## Errors say how to fix it
|
|
88
|
+
|
|
89
|
+
Structure: what happened, then how to fix it. Add why only when the reader can act on it.
|
|
90
|
+
|
|
91
|
+
| Weak | Strong |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| Oops! Something went wrong. | Unable to save. Refresh the page and try again. |
|
|
94
|
+
| Invalid username | Use lowercase letters, numbers, dots or underscores |
|
|
95
|
+
| Payment failed | Your bank declined the card. Try another card or contact your bank. |
|
|
96
|
+
|
|
97
|
+
**Name a cause the reader can act on, and only a true one.** "Check your connection" is right only when the app knows it is offline; a failure the server answered is not a lost network, and the line sends the reader after a cause that is not there.
|
|
98
|
+
|
|
99
|
+
- A refusal the server names gets its own message, phrased as what happened: "This invitation is no longer open."
|
|
100
|
+
- Any other failure names what did not happen and the one fix that works for a stale page: "Could not join. Refresh the page and try again."
|
|
101
|
+
- A form, an upload, or any action on a screen that holds an unsaved draft keeps what the reader entered. A reload would throw it away, so its failure says "Try again." instead.
|
|
102
|
+
- A failure screen with its own retry button says to try again first, then to refresh: "Try again. If it still does not load, refresh the page."
|
|
103
|
+
|
|
104
|
+
No blame, no exclamation marks. Words that never appear in an error: "error", "invalid", "failed", "forbidden", "oops", "sorry", "please". Each one either adds nothing or implies a choice the reader does not have. An error that keeps firing is an interaction to redesign, not a sentence to reword.
|
|
105
|
+
|
|
106
|
+
A success toast is under 30 characters: "Changes saved". An error toast names what did not happen in as few words, and may add the fix as one short sentence. A toast is only for an action with no form or dialog to hold an alert, such as a one-click button, a menu item or a copy; a form or a confirmation dialog shows its failure in its own alert.
|
|
107
|
+
|
|
108
|
+
## Empty states point forward
|
|
109
|
+
|
|
110
|
+
An empty state says what this place is, why it is empty, and the one thing to do next:
|
|
111
|
+
|
|
112
|
+
- Weak: "No results."
|
|
113
|
+
- Strong: "No projects yet. A project is where your team keeps its work." with `Create a project`.
|
|
114
|
+
|
|
115
|
+
The title states what is missing, with "yet". The button matches the title: `Create a project`, not `Get started`. A routine action is not a success to celebrate.
|
|
116
|
+
|
|
117
|
+
A search or filter empty state names the query and offers an exit: "No results for 'quarterly'. Clear filters". Persistent information never lives in an empty state; it disappears the moment content exists.
|
|
118
|
+
|
|
119
|
+
## Sentence case
|
|
120
|
+
|
|
121
|
+
Sentence case for every element type: labels, buttons, headings, menu items. It is calmer, has no per-word rules and localises cleanly. Proper nouns and protected terms keep their capitals.
|
|
122
|
+
|
|
123
|
+
## Long-form copy: hero, descriptions, banners
|
|
124
|
+
|
|
125
|
+
Marketing and descriptive copy follows the same reader rule at a larger size:
|
|
126
|
+
|
|
127
|
+
- The **hero title** is the promise, in the reader's life, in under ten words. The subtitle says how, in one sentence. The button says the first step.
|
|
128
|
+
- An **item description** — a product, a plan, a template — opens on what you do with it, with verbs the reader performs. Two sentences. Facts like counts and lengths belong in the card's metadata, not the prose.
|
|
129
|
+
- A **feature banner** opens with what the reader can now do, then one sentence of how, then one verb-first button. The feature's internal name is optional.
|
|
130
|
+
- A **form intro** says what the form is for and what happens after submit, in one or two sentences.
|
|
131
|
+
|
|
132
|
+
## Docs for practitioners
|
|
133
|
+
|
|
134
|
+
A design system or developer documentation page has a different reader: a designer or developer at a desk, looking up a token, a component or a rule, and coming back to it. The technician check inverts there: the word the reader implements is the right word.
|
|
135
|
+
|
|
136
|
+
- **Name the thing by its system name.** Token names, scales, palettes, states and formats are the copy: `surface-200`, OKLCH, hover, focus-visible.
|
|
137
|
+
- **Define, then prescribe.** A section opens with what the thing is, in one sentence, then says when to use it and when not: "Surface 200 is the hover or selected fill. Use it on a panel under the pointer, never as a page background." A fragment ("Dividers.") names without teaching.
|
|
138
|
+
- **A rule carries its number.** A contrast rule names the ratio and the standard it meets. A spacing rule names the base unit. A number the reader has to match is prose here, not card metadata.
|
|
139
|
+
- **Imperative, not "you".** "Use the muted scale for secondary copy", not "You can use the muted scale".
|
|
140
|
+
- Everything else holds: the neutral tone row, plain words, short lines, sentence case.
|
|
141
|
+
|
|
142
|
+
**Completion criterion:** every line in scope passes the reader check on one read, every button starts with its verb, every error states its fix, every protected term keeps its rendering, and every changed string reaches the product the way the **Copy standard** says.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: copy
|
|
3
|
+
description: Writes or reviews user-facing copy — labels, field descriptions, buttons, errors, empty states, hero and banner text, design system pages. Use when wording a screen, a field or a message, or when asked to review copy.
|
|
4
|
+
metadata:
|
|
5
|
+
pack: epicparty/skills
|
|
6
|
+
version: 0.1.0
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Copy
|
|
10
|
+
|
|
11
|
+
Write or review the words a person reads in the product.
|
|
12
|
+
|
|
13
|
+
Read [`COPY.md`](./COPY.md) and follow it, with the project's own rules from the **Copy standard** setting on top ([Settings](#settings)). They own the rules; this skill owns the steps. The one rule that decides everything: **write for the reader, not the technician**. On a documentation page for practitioners, the reader is the person who builds with the system, and `COPY.md` `## Docs for practitioners` owns that case.
|
|
14
|
+
|
|
15
|
+
## 1. Recon
|
|
16
|
+
|
|
17
|
+
Read before writing one line, then read the **Copy standard** setting — when it is missing, the recon below is what its proposed facts come from:
|
|
18
|
+
|
|
19
|
+
- The copy around the target: the same page, the sibling fields, the flow's other steps. That copy sets the voice, and a local edit does not invent a new one.
|
|
20
|
+
- The glossary for the terms the target touches — `/ep`'s [`DECISIONS.md`](../router/DECISIONS.md) says where it lives — and the protected terms the **Copy standard** lists. On a documentation page, the terms are the tokens and props it documents, read from their source.
|
|
21
|
+
- The strings already shipped, for the terms already in use — in the translation catalogs where the project has them, or in the source.
|
|
22
|
+
|
|
23
|
+
A file under review includes the copy of the project components it renders, unless the user narrows the scope. Say which you included.
|
|
24
|
+
|
|
25
|
+
Done when you can name the surface, the voice its neighbours set, and the protected terms the target touches.
|
|
26
|
+
|
|
27
|
+
## 2. Establish the reader
|
|
28
|
+
|
|
29
|
+
Settle four facts, from the code and the request, and ask only for the ones you cannot find:
|
|
30
|
+
|
|
31
|
+
- **Surface**: which screen, which element, which part of the product.
|
|
32
|
+
- **Reader state**: what they are trying to do, and how they feel while doing it — starting from the reader the **Copy standard** describes.
|
|
33
|
+
- **Stakes**: routine, error, destructive, data loss. The stakes set the tone row in `COPY.md`.
|
|
34
|
+
- **Limits**: characters, layout width, and the longest translation that has to fit the same box.
|
|
35
|
+
|
|
36
|
+
Done when the tone row is chosen and the reader's next action is named.
|
|
37
|
+
|
|
38
|
+
## 3. Write
|
|
39
|
+
|
|
40
|
+
Apply the rules to every string in scope. When the request is a rewrite, keep the current string beside the new one. When the request leaves room for choice, offer at most three alternatives, each with the tone it takes and when to prefer it.
|
|
41
|
+
|
|
42
|
+
Done when every string in scope reads on one pass to a distracted reader who did not build the product.
|
|
43
|
+
|
|
44
|
+
## 4. Land it
|
|
45
|
+
|
|
46
|
+
- The string reaches the product the way the **Copy standard** says: through the project's translation library and its extract step where it has one, with a placeholder for every value.
|
|
47
|
+
- Every locale the project ships is complete before the change is done, where it ships more than one. A translation skill the **Copy standard** names fills what extract reports missing.
|
|
48
|
+
- A test that asserts on the old string changes with it: `rg -F '<old string>'` across the tests.
|
|
49
|
+
- Copy the **Copy standard** routes elsewhere — legal notices, documentation kept in one language — follows the rule it names.
|
|
50
|
+
|
|
51
|
+
## 5. Report
|
|
52
|
+
|
|
53
|
+
A rewrite reports one table, before and after, one row per string, with the rule each change applies.
|
|
54
|
+
|
|
55
|
+
A review reports findings by severity. `HIGH` misleads the reader, leaves them unable to act — a button that names no action, invokes nothing, or invokes another action than the one it names — names a system term loosely on a documentation page, or hides how to recover from an error. `MEDIUM` breaks a `COPY.md` rule without misleading: a technician word where a plain one exists, voice, terminology or capitalisation. `LOW` is isolated polish. Source is enough to verify: check every button against the action it invokes, every error for a stated fix, every description for the reader rule, and every term against the copy and glossary around it.
|
|
56
|
+
|
|
57
|
+
| Severity | Location | Before | After | Why |
|
|
58
|
+
| --- | --- | --- | --- | --- |
|
|
59
|
+
|
|
60
|
+
One row per root cause, listing every location it appears in. `After` holds the recommended string; other alternatives go in `Why`. `Location` is `path/to/file:line`. `Why` names the rule and the reader impact. End with `Block` when any `HIGH` remains, `Approve` otherwise. Never `Approve` coverage you did not inspect. With nothing to report, say "No actionable copy findings".
|
|
61
|
+
|
|
62
|
+
Completion criterion: `COPY.md`'s completion criterion is met for every string in scope, every test that named an old string names the new one, and a changed string is in every locale the project ships. A review meets it for the strings it proposes; the tests and locales follow when the change lands.
|
|
63
|
+
|
|
64
|
+
## Settings
|
|
65
|
+
|
|
66
|
+
This skill reads one **setting**, **Copy standard**, at § 1. Writing copy asks for it when it is missing; a review reads it when set and asks nothing. 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.
|
|
67
|
+
|
|
68
|
+
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:
|
|
69
|
+
- **`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.
|
|
70
|
+
- **One file** — at a path the user names.
|
|
71
|
+
- **Another form** — as the user describes it.
|
|
72
|
+
|
|
73
|
+
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. ``
|
|
74
|
+
2. **The setting.** Read it at the settings home. Present: use it, and ask nothing.
|
|
75
|
+
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.
|
|
76
|
+
|
|
77
|
+
### Copy standard
|
|
78
|
+
|
|
79
|
+
Topic `copy`. Question: *Who reads the product, which terms keep their exact form, which languages does it ship, and how does a string reach it?*
|
|
80
|
+
|
|
81
|
+
| Option | Follow-up | Recommend when |
|
|
82
|
+
| --- | --- | --- |
|
|
83
|
+
| **A document** | The file and heading that hold the project's copy rules | The agent files route copy to a standard already |
|
|
84
|
+
| **These facts** | The reader in one sentence; the protected terms; the languages and the longest one; how a string reaches the product — the translation library, its extract command, the skill that fills missing translations; and any copy routed elsewhere | The project has no copy document, and has protected terms, more than one language, or a translation library |
|
|
85
|
+
| **None** | — | One language, no protected terms: `COPY.md` alone, strings written in the source |
|
|
86
|
+
|
|
87
|
+
Propose the facts from the code: the i18n library in `package.json`, the catalog files and their locales, the extract script, and the brand names the existing copy keeps capitalised.
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deliver
|
|
3
|
+
description: Delivers the stories you marked to start — in sequence in this thread, or across parallel workers — and prints where each one stands. Run it bare to coordinate the queue, or with one issue to deliver that story.
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
metadata:
|
|
6
|
+
pack: epicparty/skills
|
|
7
|
+
version: 0.1.0
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Deliver
|
|
11
|
+
|
|
12
|
+
Takes stories from ready to deployed with nobody driving each step. It has two roles, chosen by how it is run:
|
|
13
|
+
|
|
14
|
+
| Run | Role |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| `/ep-deliver` | **Coordinator** over the stories the **Ready signal** marks |
|
|
17
|
+
| `/ep-deliver SHOP-1 SHOP-7` — two or more issues | **Coordinator** over exactly those. Naming a story on the run is the intent, so the **Ready signal** is skipped |
|
|
18
|
+
| `/ep-deliver SHOP-1` — one issue | **Worker** on that story, in this checkout. It is what the coordinator starts in each thread, and what you run by hand |
|
|
19
|
+
|
|
20
|
+
The coordinator reconciles what is in flight, reads the queue for questions before work starts, delivers the free stories the way the **Orchestration** setting says, and prints one table. A **worker** claims one story and walks [`/ep-implement`](../implement/SKILL.md) → [`/ep-to-pr`](../to-pr/SKILL.md) → [`/ep-ship`](../ship/SKILL.md).
|
|
21
|
+
|
|
22
|
+
Two properties hold it together:
|
|
23
|
+
|
|
24
|
+
- **The tracker is the coordination.** Workers never talk to each other. Everything one needs to know about the others is on the tracker: a claim, a status, a branch, a PR, a comment.
|
|
25
|
+
- **A claim is a race you can lose.** Two workers reading the queue at the same moment see the same story. Losing the race is normal and costs nothing.
|
|
26
|
+
|
|
27
|
+
Read the **Tracker**, **Ready signal**, **Orchestration** and **Checkout** settings when the run starts, in that order, per [Settings](#settings). The coordinator claims stories and starts work, so it asks for each one that is missing; a worker run by hand asks only **Tracker** and **Checkout**. With **Tracker** at **None**, both roles stop before asking the others, and say so: the queue, the claim and the table all live on the tracker. Route a story given in the chat through `/ep-implement` instead.
|
|
28
|
+
|
|
29
|
+
## The coordinator
|
|
30
|
+
|
|
31
|
+
One invocation is one **beat**. Under **In sequence**, the beat lasts until the queue runs dry. Under either parallel option, the beat starts what it may, prints the table and ends, and the cadence is yours:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
/loop 10m /ep-deliver
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
A beat carries nothing between beats. Every beat re-derives its picture from the tracker, the branches and the open PRs, so ten beats or one, a crash between them or a week, the answer is the same.
|
|
38
|
+
|
|
39
|
+
### 1. Reconcile what is in flight
|
|
40
|
+
|
|
41
|
+
A story is **in flight** from its claim to its deploy, in one of three stages: **building** (claimed, no PR), **review** (PR open) and **shipping** (merged, deploy unfinished). Where the project keeps a board, each stage is a status on it, and the status is a **claim to be checked**: a worker that died mid-story leaves a card reading building forever, and a stale card reads as work in progress.
|
|
42
|
+
|
|
43
|
+
In flight is every story at a building, review or shipping stage where the project keeps a board. Without one — or to catch what a board missed — it is every open issue a claim assigned, with a branch linked to it, or with an open PR that closes it, plus every open issue whose comments carry the `**A worker needs a decision**` line: on GitHub, `gh search issues --repo <owner>/<repo> --state open --match comments '"A worker needs a decision"'`.
|
|
44
|
+
|
|
45
|
+
For every in-flight story, take the first row that matches:
|
|
46
|
+
|
|
47
|
+
| What exists | Reading |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| **The issue is closed** | It is finished, whatever the board says. Move it to done. |
|
|
50
|
+
| An unanswered `**A worker needs a decision**` comment, on the issue or its PR | Parked. Resume nothing; list it under Parked. Where the board has a Parked stage, the card reads Parked. |
|
|
51
|
+
| A merged PR, the deploy unfinished | Shipping |
|
|
52
|
+
| An open PR | Review |
|
|
53
|
+
| A pushed branch linked to the issue (on GitHub, `gh issue develop --list <n>`), no PR | Building — it rejoins at `/ep-to-pr` |
|
|
54
|
+
| No branch and no PR | Nothing was built. Remove your claim, and return it to the queue: restore the Ready signal the claim took off, where it took one. A story named on the run only loses your assignment. That return is the one Ready signal write a skill makes. |
|
|
55
|
+
|
|
56
|
+
The closed row moves a card to done by hand, where a board would usually do it on the close. That is safe here because the closed state **is** the confirmation, read before the write: a card closed before the board's automation existed, or written over afterwards, has no second chance to correct itself.
|
|
57
|
+
|
|
58
|
+
Say what you changed and why. A card you silently moved is a card the user cannot trust.
|
|
59
|
+
|
|
60
|
+
Done when every in-flight story matches something that actually exists, and every correction is named.
|
|
61
|
+
|
|
62
|
+
### 2. Pre-flight the queue
|
|
63
|
+
|
|
64
|
+
The queue is the stories named on the run, or else every story the **Ready signal** marks. Under **None** with no story named, there is no queue: say so, and stop. Read each **once, before anything starts**, and ask everything it needs in one round — each question as a comment on its issue that opens with the `**A worker needs a decision**` line, so § 6 sees the reply, and listed in the report. A question asked here is answered while workers run; the same question found mid-build parks a story and costs a day.
|
|
65
|
+
|
|
66
|
+
Check each against two things:
|
|
67
|
+
|
|
68
|
+
- **The Ready bar** in [`STORIES.md`](../to-story/STORIES.md) `## Ready`. A story marked to start without a ready label is the common case, not an error: the signal is intent, and the bar is a property.
|
|
69
|
+
- **The irreversible steps** in [What earns a go/no-go](#what-earns-a-gono-go). A destructive migration step, a production write, a credential rotation and a one-way third-party call are all readable from a story's text.
|
|
70
|
+
|
|
71
|
+
**Sort, never block.** A story carrying a question stays in the queue, shows under **Parked** until the answer comes, and is not claimed; the rest are free to claim. One vague story must not stall three good ones.
|
|
72
|
+
|
|
73
|
+
What this cannot catch is what the code turns out to look like. Say that rather than implying the queue is now safe.
|
|
74
|
+
|
|
75
|
+
Done when every queued story is either free to claim or named with the question it needs answered.
|
|
76
|
+
|
|
77
|
+
### 3. Find the free stories
|
|
78
|
+
|
|
79
|
+
Take the queue in priority order — the priority axis where the **Labels** setting defines one, the board's order otherwise — and hold back each story a signal below names:
|
|
80
|
+
|
|
81
|
+
| Signal | Reading |
|
|
82
|
+
| --- | --- |
|
|
83
|
+
| An open blocking edge ([`SPLITTING.md`](../to-story/SPLITTING.md)) | Not free. The blocker ships first. |
|
|
84
|
+
| Someone other than this run's own claim is assigned | Somebody holds it. Not free. |
|
|
85
|
+
| Its scope matches a story in flight | Assume the same files. Not free. |
|
|
86
|
+
| A scope that every other part depends on — a shared layer, root files — while anything at all is in flight | It fans out to every gate. Not free. |
|
|
87
|
+
|
|
88
|
+
A blocking edge is a hard order you cannot overrule; a scope collision is a heuristic you can. Name which held each story.
|
|
89
|
+
|
|
90
|
+
Done when every queued story is free, or named with the signal that held it.
|
|
91
|
+
|
|
92
|
+
### 4. Deliver, per Orchestration
|
|
93
|
+
|
|
94
|
+
- **In sequence, in this thread** — before the first claim, run the worker's [§ 1 check](#1-check-this-checkout) on this checkout: a dirty checkout stops the beat before a claim, not after. Then take the first free story, run the [worker](#the-worker) on it here, then come back to step 3. Stop when nothing is free.
|
|
95
|
+
- **Parallel threads it starts** — count the stories in flight. For each free story, up to the setting's most workers at once: [claim it](#2-claim-it), then start one thread with the harness's thread tool, running `/ep-deliver <issue>` in a checkout of the kind the **Checkout** setting gives a new story. Name each thread after its story, so the user can find it.
|
|
96
|
+
- **Parallel, started by you** — start nothing. Name each free story beside a free checkout of a kind the **Checkout** setting lists, so the user can run `/ep-deliver <issue>` there.
|
|
97
|
+
|
|
98
|
+
Under **Parallel threads it starts**, a harness without a thread tool falls back to **Parallel, started by you** for this beat, and says so.
|
|
99
|
+
|
|
100
|
+
Done when every free story is delivered, started, or named for the user — up to the width the setting allows.
|
|
101
|
+
|
|
102
|
+
### 5. Print the table
|
|
103
|
+
|
|
104
|
+
One row per story in flight, then the tails. The table **is** the beat's report — there is no prose summary above it. Under **In sequence**, the run ends in § 7's report, with this table as its details.
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
Issue Story Stage For PR Waiting on
|
|
108
|
+
SHOP-184 billing/invoices: retire the PDF export Building 22m — —
|
|
109
|
+
SHOP-191 accounts/profile: warm the account Review 6m #203 CI: accounts gate
|
|
110
|
+
SHOP-177 shared/platform: port registry Shipping 3m #198 deploy run 4471
|
|
111
|
+
|
|
112
|
+
Queued (3): SHOP-205 · SHOP-206 (held — blocked by SHOP-205) · SHOP-211 (held — collides with SHOP-205, shared scope)
|
|
113
|
+
Parked (1): SHOP-199 — worker question, unanswered 40m
|
|
114
|
+
Not ready (1): SHOP-188 — fails the Ready bar: acceptance criteria name no test level
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Every column comes from the tracker and `git`, with issues named by the **Tracker** setting's prefix. The table is fixed-width and already carries the title, so it skips the bold title a first mention would otherwise get.
|
|
118
|
+
|
|
119
|
+
- **For** — time in the current stage, from the branch's last commit and the PR's last update. This is the column that exposes a stuck worker, so it earns its place over anything prettier.
|
|
120
|
+
- **Waiting on** — the concrete blocker: a named failing check, an unresolved review thread, a deploy run id. `—` means nothing is blocking and the worker is simply working.
|
|
121
|
+
- **Queued** — in claim order, and every held story names its cause.
|
|
122
|
+
- **Parked** and **Not ready** — the stories waiting on you, and why.
|
|
123
|
+
- **Ready, not queued** — when the queue is empty, the stories at the Ready stage, so you can see what you could queue next.
|
|
124
|
+
|
|
125
|
+
Recompute the ordering every beat. Stories join the queue while workers run, so an order computed once is wrong by the next beat.
|
|
126
|
+
|
|
127
|
+
Done when every in-flight story has a row whose stage, age and blocker match the PR, and every queued story is either claimable or carries its hold.
|
|
128
|
+
|
|
129
|
+
### 6. Relay what the user said
|
|
130
|
+
|
|
131
|
+
Steering is a **comment**. It survives a crashed session, it works from every tool, and it lands where the decision belongs.
|
|
132
|
+
|
|
133
|
+
Each beat, read new comments on the in-flight issues and their PRs. A comment that changes what a worker should do gets relayed to the worker holding that story. A question counts as answered once a later comment exists that is not a worker's own — every worker comment opens with a bold marker line. An answer moves that story on: drop the label the question added, where it added one, move a Parked card to the stage its branch or PR reads in § 1, and say so in the report. The next free worker resumes it from there.
|
|
134
|
+
|
|
135
|
+
Where a worker is reachable through the harness — a thread this coordinator started — relay immediately and say you did. Where it is not, the comment stands on the issue and the worker picks it up at its next stage boundary. Say that too, because the difference is whether your instruction lands in seconds or in twenty minutes.
|
|
136
|
+
|
|
137
|
+
Done when every new comment on an in-flight story is relayed, answered or explicitly noted as needing nothing.
|
|
138
|
+
|
|
139
|
+
### 7. End the beat
|
|
140
|
+
|
|
141
|
+
Under either parallel option, print the loop line and stop: a beat that keeps running is a beat whose table is already out of date. Under **In sequence**, end with `/ep-ship`'s report shape for the run as a whole, the table in its details.
|
|
142
|
+
|
|
143
|
+
Stop when the queue has nothing free, and say which: empty, everything blocked, or everything colliding with work in flight. **Do not idle waiting for a story.** You are a session the user started; ending cleanly costs them nothing, and a spinning coordinator costs them a checkout.
|
|
144
|
+
|
|
145
|
+
## The worker
|
|
146
|
+
|
|
147
|
+
One story, in this checkout. Started by the coordinator, in a thread or in sequence, or by hand with one issue named.
|
|
148
|
+
|
|
149
|
+
### 1. Check this checkout
|
|
150
|
+
|
|
151
|
+
`git status --porcelain` empty, no merge or rebase in progress, and at `origin/<default>` — on the default branch, or on a fresh branch with no commits past it, as some checkout kinds start — the local default branch where the repo has no remote. A dirty checkout is **somebody's work** — stop, name the files, and change nothing (`/ep` `## Working rules`).
|
|
152
|
+
|
|
153
|
+
Then find this checkout's kind in the **Checkout** setting, and confirm its setup has run. A checkout whose kind lists a setup step that has not run is setup you own: run it, or — when it needs the user — stop and say which checkout needs what. The worker never reaches into a sibling checkout, and never creates one.
|
|
154
|
+
|
|
155
|
+
Done when the checkout is clean, on the default branch, and set up for its kind — or you have stopped and named what is wrong with it.
|
|
156
|
+
|
|
157
|
+
### 2. Claim it
|
|
158
|
+
|
|
159
|
+
Skip this when the coordinator already claimed the story for this worker.
|
|
160
|
+
|
|
161
|
+
Claim it in one write, and read it back:
|
|
162
|
+
|
|
163
|
+
- **A board column** — move the story from that column to the Building stage (`/ep`'s [`BOARD.md`](../router/BOARD.md)), then re-read the item. If it does not read Building with you as the writer, you lost the race.
|
|
164
|
+
- **A label** — assign yourself, then re-read the issue. If someone else is assigned too, you lost the race: unassign yourself. If you are the only one, remove the label.
|
|
165
|
+
- **Named on the run** — assign yourself, and move it to the Building stage where the project keeps a board.
|
|
166
|
+
|
|
167
|
+
This single write is the only thing stopping two workers building one story, so it happens before the branch, not after. A worker that lost the race stops, and says which story it lost.
|
|
168
|
+
|
|
169
|
+
A story that fails the Ready bar in [`STORIES.md`](../to-story/STORIES.md) `## Ready` is not yours to build or to grill, whatever its labels say. Started by the coordinator, trust its pre-flight: it checked the bar. Started by hand, check the bar yourself. Report a story that fails it and stop; the coordinator's pre-flight is what asks about it.
|
|
170
|
+
|
|
171
|
+
Done when you hold the claim, or have stopped and said why.
|
|
172
|
+
|
|
173
|
+
### 3. Walk the route
|
|
174
|
+
|
|
175
|
+
`/ep-implement`, then `/ep-to-pr`, then `/ep-ship`. They move the story through building, review and shipping as they cross each boundary, which is what gives the coordinator's table something true to render.
|
|
176
|
+
|
|
177
|
+
Read each skill and follow it. Where one stops on a condition it cannot clear, that is a **park**, not a failure — see [Escalation](#escalation).
|
|
178
|
+
|
|
179
|
+
Done when the story is deployed and the checkout is clean, or the story is parked with the branch pushed.
|
|
180
|
+
|
|
181
|
+
## Escalation
|
|
182
|
+
|
|
183
|
+
Autonomy is the default and the bar to break it is high. Before asking anything, **exhaust the harness**: read the routed standard, read the neighbouring implementation, run the dry run, take the reversible path. A question you could have answered by looking costs a day of wall-clock and buys nothing.
|
|
184
|
+
|
|
185
|
+
Two things earn a question, and each has exactly one surface.
|
|
186
|
+
|
|
187
|
+
**A question about the spec** → the **issue**. The story is ambiguous, contradicts itself, or never decided something the build turns on. That is a defect in the spec, so the answer belongs where the spec lives. Add the discovery label for an unmade decision or the info label for a missing fact. Never open a PR to carry a spec question: a PR is where work is reviewed, never where it is proposed.
|
|
188
|
+
|
|
189
|
+
**A go/no-go on the work** → the **PR**. Take the branch to a normal reviewable PR through `/ep-to-pr`, then ask in a comment on it. Nobody can answer *is this safe to run* without the diff in front of them.
|
|
190
|
+
|
|
191
|
+
Either way, one comment opening with the literal line `**A worker needs a decision**`, then the single question, the options, your recommendation, and what you will do if no answer comes. That first line is how the coordinator finds the question and tells a reply from silence.
|
|
192
|
+
|
|
193
|
+
Then park the story: remove your claim, and move the card to the board's Parked stage where it has one ([`BOARD.md`](../router/BOARD.md)). Without that stage, the card keeps the stage it had. Never return a parked story to the queue: its branch or PR holds the state, and the next worker resumes from there.
|
|
194
|
+
|
|
195
|
+
### What earns a go/no-go
|
|
196
|
+
|
|
197
|
+
Almost nothing here is one-way. A bad commit is a revert, and a failed deploy is fixed by the next PR ([`/ep-ship`](../ship/SKILL.md) `## 2`). Build it, run it, and let the review catch it.
|
|
198
|
+
|
|
199
|
+
Escalate on one test: **the step cannot be undone from this repo.**
|
|
200
|
+
|
|
201
|
+
- The destructive step of a migration — the one that drops the old shape. Widening a schema and backfilling are reversible and they are yours: write them, run them the way the project's migration standard says, open the PR, and ask before the drop lands.
|
|
202
|
+
- A production write that what remains cannot rebuild.
|
|
203
|
+
- Rotating or revoking a credential already in use.
|
|
204
|
+
- A one-way third-party action — a billing call, mail to real users, deleting a deployment.
|
|
205
|
+
|
|
206
|
+
Everything else you decide, do, and record in the PR body as a decision you made.
|
|
207
|
+
|
|
208
|
+
### Park, do not wait
|
|
209
|
+
|
|
210
|
+
**The branch is the parking place.** Commit what exists and push it, because work that lives only in a checkout is one reset from gone — `git stash` puts it exactly where the next reset destroys it. A worker started for one story then stops; under **In sequence**, the coordinator goes back to step 3 and takes another.
|
|
211
|
+
|
|
212
|
+
A parked story is resumed by whichever worker next finds it: an issue with a pushed branch rejoins at `/ep-to-pr`, one with an open PR rejoins at `/ep-ship`. Ten minutes or a week later costs the same, because the branch and the PR carry the state.
|
|
213
|
+
|
|
214
|
+
## Guardrails
|
|
215
|
+
|
|
216
|
+
- **A worker decides how, never what.** Every implementation call is yours to make and record. What the product should do was decided in the story.
|
|
217
|
+
- **You never set the Ready signal on a story the user did not mark.** That gate is the user's, and a skill that queues its own work has removed the only control the gate exists to give. The one write is the return in § 1: a story the user marked, whose claim produced nothing.
|
|
218
|
+
- **The tracker is the state.** Anything that exists only in this context is gone when this session ends — a decision, a park, a failure reason all get written to the issue or the PR.
|
|
219
|
+
- **A dirty checkout is evidence, not a chore.** It is the one thing here you never fix.
|
|
220
|
+
- **Cut from `origin/<default>` every time.** Parallel workers all aim at one moving default branch. A branch that falls into conflict with it is rebased by the `/ep-babysit` sweeps that `/ep-ship` runs.
|
|
221
|
+
- **An empty tail has to mean empty.** `Parked (0)` says every story is moving. A reader who cannot tell an empty list from a forgotten one has to go and check for themselves.
|
|
222
|
+
- **Report what you cannot see.** A beat that could not reach something — a deploy run, a check, a thread — says so on the row rather than printing a `—` that reads like "nothing wrong".
|
|
223
|
+
|
|
224
|
+
## Settings
|
|
225
|
+
|
|
226
|
+
This skill reads two settings `/ep` `## Settings` defines — **Tracker** and **Checkout** — and two of its own, **Ready signal** and **Orchestration**. It claims stories and starts work, so it asks for each one that is missing. It reads **Labels** and **Scopes** at the coordinator's § 3 when they are set, and asks for **Labels** only when it is about to write a label — the discovery or info label at an escalation. 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.
|
|
227
|
+
|
|
228
|
+
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:
|
|
229
|
+
- **`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.
|
|
230
|
+
- **One file** — at a path the user names.
|
|
231
|
+
- **Another form** — as the user describes it.
|
|
232
|
+
|
|
233
|
+
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. ``
|
|
234
|
+
2. **The setting.** Read it at the settings home. Present: use it, and ask nothing.
|
|
235
|
+
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.
|
|
236
|
+
|
|
237
|
+
### Ready signal
|
|
238
|
+
|
|
239
|
+
Topic `delivery`. Question: *How do you mark a story you want started now?*
|
|
240
|
+
|
|
241
|
+
| Option | Follow-up | Recommend when |
|
|
242
|
+
| --- | --- | --- |
|
|
243
|
+
| **A board column** | The column's name, such as `Queued` | The project keeps a board |
|
|
244
|
+
| **A label** | The label's name, such as `agent-queue` | The project has no board |
|
|
245
|
+
| **None** | — | Stories are named on the run, every time |
|
|
246
|
+
|
|
247
|
+
The signal says **you want this started now**, which is intent. A ready label says the story is specified well enough to build, which is a property of the story. Keep them apart, so a story can be honestly ready and still not wanted yet: a ready label is never the signal. Only a human sets the signal — a skill sets it only on their explicit yes (`/ep-triage` offers it on a ready story), or returns it (§ 1) — and naming stories on the run skips it.
|
|
248
|
+
|
|
249
|
+
### Orchestration
|
|
250
|
+
|
|
251
|
+
Topic `delivery`. Question: *How should the coordinator deliver the stories it may start?*
|
|
252
|
+
|
|
253
|
+
| Option | Follow-up | Recommend when |
|
|
254
|
+
| --- | --- | --- |
|
|
255
|
+
| **In sequence, in this thread** | — | Default: one checkout, or work you want to watch as it happens |
|
|
256
|
+
| **Parallel threads it starts** | The most workers at once, and the harness tool that starts a thread — such as T3 Code's `create_threads` | A thread tool is among this session's tools, and the **Checkout** setting has a kind a new thread can set up alone |
|
|
257
|
+
| **Parallel, started by you** | — | Several prepared checkouts, and no thread tool |
|
|
258
|
+
|
|
259
|
+
Every worker takes its own checkout, so the width is bounded by the machine: recommend a width from what one checkout's setup and full suite cost to run.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: diagnose
|
|
3
|
+
description: Diagnoses a failure in this codebase — a bug, an error, a failing or flaky test, a slowdown — with a loop that goes red on it before any theory, then fixes it under a regression test. Use when something is broken, failing or slow, or the user asks to debug or investigate why.
|
|
4
|
+
metadata:
|
|
5
|
+
pack: epicparty/skills
|
|
6
|
+
version: 0.1.0
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Diagnose
|
|
10
|
+
|
|
11
|
+
<!-- upstream: mattpocock/skills skills/engineering/diagnosing-bugs/SKILL.md -->
|
|
12
|
+
|
|
13
|
+
A discipline for hard bugs. Skip a phase only with a stated reason.
|
|
14
|
+
|
|
15
|
+
Where the failure has an issue — named by the user, or found by searching the tracker with the symptom's own nouns, the way `/ep` § 1 searches — read it with its comments — the tracker is the **Tracker** setting when set, or GitHub when `gh repo view` finds the repo — and claim it the way [`/ep-implement` § 1](../implement/SKILL.md#1-load-the-story) does. Read the glossary and the ADRs in the area, where `/ep`'s [`DECISIONS.md`](../router/DECISIONS.md) says they live, for a clear model of the modules involved.
|
|
16
|
+
|
|
17
|
+
**Redact every secret** in what you show — commands, outputs, captured artifacts: write `<REDACTED>` in its place. Build loops against environment variables, so the credential stays in the environment. Captured artifacts carry auth headers: quote only the lines that carry the signal. If the redacted output is not enough to diagnose, say so and ask the user.
|
|
18
|
+
|
|
19
|
+
## 1. Build a feedback loop
|
|
20
|
+
|
|
21
|
+
**This is the skill.** With a **tight** pass/fail signal that goes **red** on _this_ bug, you will find the cause — bisection, hypotheses and instrumentation all just consume it. Without one, no amount of reading code will save you. Spend disproportionate effort here, and refuse to give up.
|
|
22
|
+
|
|
23
|
+
Ways to build one, roughly in this order:
|
|
24
|
+
|
|
25
|
+
1. **A failing test** at whatever seam reaches the bug — unit, integration, end-to-end.
|
|
26
|
+
2. **A curl or HTTP script** against a running dev server.
|
|
27
|
+
3. **A CLI run** with a fixture input, diffing its output against a known-good snapshot.
|
|
28
|
+
4. **A headless browser script** that drives the UI and asserts on the DOM, the console or the network.
|
|
29
|
+
5. **A replayed trace** — a real request, payload or event log saved to disk and replayed through the code path alone.
|
|
30
|
+
6. **A throwaway harness** — the smallest subset of the system that reaches the bug's path in one call.
|
|
31
|
+
7. **A property or fuzz loop** — for "sometimes wrong", a thousand random inputs, looking for the failure.
|
|
32
|
+
8. **A bisection harness** — when the bug appeared between two known states, automate "boot at state X, check" and `git bisect run` it.
|
|
33
|
+
9. **A differential loop** — the same input through the old and the new version, or two configs, with the outputs diffed.
|
|
34
|
+
10. **A human in the loop**, last — when a person must click, drive _them_ with [`scripts/hitl-loop.template.sh`](./scripts/hitl-loop.template.sh), so the loop stays structured and its captured output feeds back to you.
|
|
35
|
+
|
|
36
|
+
**A slowdown's loop is a measurement** — a timing harness, a profiler, a query plan — against a threshold the user agrees: red is slower than it. A regression with a known fast state bisects to it (8); a slowness with none is a profiling job, and the threshold is still its red.
|
|
37
|
+
|
|
38
|
+
Then **tighten** it, treating the loop as a product: faster (cache setup, skip unrelated start-up, narrow the test scope), sharper (assert the specific symptom, not "did not crash"), more deterministic (pin time, seed randomness, isolate the file system, freeze the network). A thirty-second flaky loop is barely better than none.
|
|
39
|
+
|
|
40
|
+
**A non-deterministic bug** needs a higher reproduction rate, not a clean repro: loop the trigger a hundred times, parallelise, add stress, narrow timing windows. A 50 % flake is debuggable; 1 % is not, so keep raising the rate.
|
|
41
|
+
|
|
42
|
+
**A loop blocked only by a permission or a missing install** is not "no loop": ask for the approval or the install, and wait.
|
|
43
|
+
|
|
44
|
+
**No loop possible?** Stop and say so, with what you tried. Ask the user for access to an environment that reproduces it, a redacted captured artifact (a HAR file, a log dump, a screen recording with timestamps), or permission for temporary production instrumentation. Do not hypothesise without a loop.
|
|
45
|
+
|
|
46
|
+
Done when you can name **one command** — a script, a test invocation, a curl — that you have already run at least once, with its redacted invocation and output shown, and that is:
|
|
47
|
+
|
|
48
|
+
- [ ] **Red-capable** — it drives the bug's actual code path and asserts the **user's exact symptom**, so it goes red on this bug and green once fixed.
|
|
49
|
+
- [ ] **Deterministic** — the same verdict every run, or a pinned high reproduction rate for a flaky bug.
|
|
50
|
+
- [ ] **Fast** — seconds, not minutes.
|
|
51
|
+
- [ ] **Agent-runnable** — it runs unattended, with a human only through the HITL script.
|
|
52
|
+
|
|
53
|
+
Reading code and config to build the loop is the work of this step; reading it to build a theory before that command exists is the exact failure this skill prevents. No red loop, no § 2.
|
|
54
|
+
|
|
55
|
+
## 2. Reproduce and minimise
|
|
56
|
+
|
|
57
|
+
Run the loop and watch it go red. Confirm it shows the failure the **user** described — a nearby failure is the wrong bug, and gets the wrong fix — that it reproduces across runs, and that you hold the exact symptom (the error, the wrong output, the timing) to check the fix against.
|
|
58
|
+
|
|
59
|
+
Then shrink it to the **smallest scenario that still goes red**: cut inputs, callers, config, data and steps one at a time, re-running after each cut. A minimal repro shrinks the hypothesis space and becomes the clean regression test.
|
|
60
|
+
|
|
61
|
+
Done when every remaining element is load-bearing — removing any one turns the loop green.
|
|
62
|
+
|
|
63
|
+
## 3. Hypothesise
|
|
64
|
+
|
|
65
|
+
Write **three to five ranked hypotheses** before testing any: one hypothesis anchors on the first plausible idea. Each states the prediction that would falsify it — "If X is the cause, changing Y makes the bug disappear, and changing Z makes it worse." A hypothesis with no prediction is a vibe: sharpen it or drop it.
|
|
66
|
+
|
|
67
|
+
Show the ranked list to the user before testing. They often re-rank it at once ("we just deployed a change to 3"), or have ruled some out already. When they are not there, proceed on your ranking.
|
|
68
|
+
|
|
69
|
+
Done when the list is ranked, each item carries its prediction, and the user has seen it or is away.
|
|
70
|
+
|
|
71
|
+
## 4. Instrument
|
|
72
|
+
|
|
73
|
+
Each probe maps to one prediction from § 3. Change one variable at a time.
|
|
74
|
+
|
|
75
|
+
Prefer a debugger or REPL where the environment has one — one breakpoint beats ten logs — then targeted logs at the boundaries that tell hypotheses apart. **Tag every debug log** with a unique prefix, such as `[DEBUG-a4f2]`, so the cleanup is one search.
|
|
76
|
+
|
|
77
|
+
**A slowdown** takes measurement, not logs: probe with the § 1 measurement, one change at a time.
|
|
78
|
+
|
|
79
|
+
Done when one hypothesis survives with the evidence that killed the others.
|
|
80
|
+
|
|
81
|
+
## 5. Fix under a regression test
|
|
82
|
+
|
|
83
|
+
Cut a branch for the fix as [`/ep-implement` § 2](../implement/SKILL.md#2-cut-the-branch) does — with no issue, a plain `git switch -c <branch>` off the default branch — unless you are already on the work's branch.
|
|
84
|
+
|
|
85
|
+
Write the regression test **before the fix**, at a **correct seam**: one where the test exercises the real bug pattern as it happens at the call site. [`TDD.md`](../implement/TDD.md) says what a good test is, and [`SEAMS.md`](../to-story/SEAMS.md) gives the seam vocabulary. The correct seam is the agreement here: a regression test needs no story to name it. A seam too shallow to replicate the chain that caused the bug — one caller where the bug needs two, a unit test that cannot reach it — gives false confidence. **No correct seam is itself the finding**: the code's shape keeps the bug from being locked down, and that goes under **Found in passing** (`/ep` `## Vocabulary`), where [`/ep-architect`](../architect/SKILL.md) can take it up.
|
|
86
|
+
|
|
87
|
+
A slowdown's regression test is a budget the suite can hold — a measured timeout or a benchmark threshold, by the project's own rules for slow tests. Where none fits, the threshold and its measurement go in the PR body.
|
|
88
|
+
|
|
89
|
+
With a correct seam:
|
|
90
|
+
|
|
91
|
+
1. Turn the minimised repro into a failing test there.
|
|
92
|
+
2. Watch it fail. Where you forced the red by changing code or a fixture, diff against a clean copy to prove the change landed before you trust it.
|
|
93
|
+
3. Apply the fix.
|
|
94
|
+
4. Watch it pass.
|
|
95
|
+
5. Re-run the § 1 loop against the original, unminimised scenario.
|
|
96
|
+
|
|
97
|
+
Then validate with `/ep-implement`'s **Scoped check** where it is set, or the project's own typecheck and test scripts, the way [its § 6](../implement/SKILL.md#6-validate) does, and review the diff as its § 7 does.
|
|
98
|
+
|
|
99
|
+
Done when the test is green, the original loop is green, and the check passes.
|
|
100
|
+
|
|
101
|
+
## 6. Clean up and hand off
|
|
102
|
+
|
|
103
|
+
- [ ] The original repro no longer reproduces.
|
|
104
|
+
- [ ] The regression test passes, or the missing seam is written down.
|
|
105
|
+
- [ ] No `[DEBUG-…]` line is left — search for the prefix.
|
|
106
|
+
- [ ] Throwaway harnesses are deleted, or moved to a clearly marked debug location.
|
|
107
|
+
|
|
108
|
+
Commit per `/ep`'s [`GIT.md`](../router/GIT.md) `## Commits`, and state the hypothesis that proved correct in the commit body, so the next debugger learns from it. Then [`/ep-to-pr`](../to-pr/SKILL.md) opens the pull request.
|
|
109
|
+
|
|
110
|
+
Report the symptom, the loop, the hypothesis that held and the ones ruled out, the fix, and every finding and **open question** you are leaving.
|
|
111
|
+
|
|
112
|
+
## Settings
|
|
113
|
+
|
|
114
|
+
This skill asks for nothing. It reads, when they are set, the settings `/ep-implement` reads for the branch, the tests and the check — **Tracker**, **Checkout**, **Labels** and **Scopes** from `/ep` `## Settings`, **Test levels**, and `/ep-implement`'s **Scoped check**. Without them it uses GitHub where `gh repo view` finds the repo, and the project's own typecheck and test scripts.
|