@ethlete/agent-rules 0.1.0-next.14 → 0.1.0-next.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +36 -2
  3. package/THIRD-PARTY-LICENSES.md +35 -0
  4. package/content/hooks/context-warning.py +450 -112
  5. package/content/hooks/subagent-model-policy.py +185 -0
  6. package/content/rules/subagent-models.md +29 -0
  7. package/content/skills/codex-subagent/SKILL.md +90 -0
  8. package/content/skills/codex-subagent/codex-agent.mjs +229 -0
  9. package/content/skills/design-exploration/SKILL.md +203 -0
  10. package/content/skills/design-exploration/check-story.mjs +139 -0
  11. package/content/skills/design-exploration/shoot-template.mjs +61 -0
  12. package/content/skills/domain-modeling/ADR-FORMAT.md +47 -0
  13. package/content/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
  14. package/content/skills/domain-modeling/SKILL.md +80 -0
  15. package/content/skills/grill-with-docs/SKILL.md +13 -0
  16. package/content/skills/grilling/SKILL.md +34 -0
  17. package/content/skills/handoff/SKILL.md +69 -4
  18. package/content/skills/sdk-update/SKILL.md +2 -1
  19. package/content/skills/timetrack/SKILL.md +190 -10
  20. package/package.json +1 -1
  21. package/src/index.js +2 -1
  22. package/src/index.js.map +1 -1
  23. package/src/lib/frontmatter.d.ts +2 -0
  24. package/src/lib/frontmatter.js +2 -1
  25. package/src/lib/frontmatter.js.map +1 -1
  26. package/src/lib/git-flow/config.js +1 -1
  27. package/src/lib/git-flow/config.js.map +1 -1
  28. package/src/lib/git.d.ts +37 -0
  29. package/src/lib/git.js +77 -1
  30. package/src/lib/git.js.map +1 -1
  31. package/src/lib/index.d.ts +1 -0
  32. package/src/lib/index.js +1 -0
  33. package/src/lib/index.js.map +1 -1
  34. package/src/lib/plain-text.d.ts +9 -0
  35. package/src/lib/plain-text.js +22 -0
  36. package/src/lib/plain-text.js.map +1 -0
  37. package/src/lib/targets/claude-hooks.js +2 -1
  38. package/src/lib/targets/claude-hooks.js.map +1 -1
  39. package/src/lib/targets/codex-hooks.js +2 -1
  40. package/src/lib/targets/codex-hooks.js.map +1 -1
  41. package/src/lib/targets/hooks-shared.d.ts +22 -1
  42. package/src/lib/targets/hooks-shared.js +42 -9
  43. package/src/lib/targets/hooks-shared.js.map +1 -1
  44. package/src/lib/targets/shared.js +1 -0
  45. package/src/lib/targets/shared.js.map +1 -1
  46. package/src/lib/timetrack-command.js +449 -20
  47. package/src/lib/timetrack-command.js.map +1 -1
  48. package/src/lib/timetrack.d.ts +241 -0
  49. package/src/lib/timetrack.js +83 -2
  50. package/src/lib/timetrack.js.map +1 -1
@@ -0,0 +1,203 @@
1
+ ---
2
+ name: design-exploration
3
+ description: How a visual design exploration runs - the step size, what to put in front of the user at each stop, and when you may commit. Read BEFORE any visual design work on a sketch, a prototype, a theme, a palette, or the look of a component. The user is the designer; you find and frame, and you never pick the fix alone.
4
+ kind: skill
5
+ scope: both
6
+ vars: [storybookUrl]
7
+ ---
8
+
9
+ # Design exploration
10
+
11
+ A design exploration is a dialog. **The user is the designer. You find and frame. The user
12
+ chooses.** A finding is not a licence to pick the fix.
13
+
14
+ The unit of work is a **call**: one open question, with every answer drawn side by side at
15
+ the same geometry. An option wins or loses only against the other options of its call.
16
+
17
+ ## The rules
18
+
19
+ 1. **One open call at a time.** Take one question. Draw its options. Stop.
20
+ 2. **Put the options in the call, then name your pick.** Two to four options, drawn for
21
+ real, side by side, labelled, with what each one costs. Your pick is a proposal.
22
+ 3. **Commit only when the user says commit.**
23
+ 4. **A clear rejection starts the next call.** Settle and record the rejected call, then draw
24
+ the next focused question automatically, within the user's original scope. Stop only when the
25
+ user asks to pause or end the exploration, or when their feedback leaves no grounded next
26
+ question.
27
+ 5. **Keep the answer short.** 120 words at most.
28
+
29
+ Confirm the step size once, at the start. Do not ask again at every stop. A task is not
30
+ finished while the user is still talking about it.
31
+
32
+ ## What one stop looks like
33
+
34
+ - The question, in one sentence.
35
+ - The call slug to look at.
36
+ - The options, labelled A, B, C, one line each.
37
+ - Your pick, one line on why.
38
+ - Nothing else. No next step, no second finding.
39
+
40
+ **Do not send a screenshot.** The user keeps the page open. Screenshot only for your own
41
+ check that an option renders.
42
+
43
+ If a second defect appears, add it to the open list and give it one line. Do not draw it.
44
+
45
+ ## Where the options are drawn
46
+
47
+ The exploration's plan file names the tool. Two shapes exist.
48
+
49
+ **A repository with a `.ethlete/design` folder** draws a call per page, each option in its own
50
+ iframe. The tool is `et design`, from `@ethlete/cli`, so the repository itself needs no design
51
+ tooling. A call argues in the repository it is about:
52
+
53
+ ```
54
+ .ethlete/design/
55
+ config.json the port, the default call, and one entry per project
56
+ calls/<project>/<group>/<slug>/
57
+ call.ts the eyebrow, the headline, the intro, frameWidth, the options and the round prose
58
+ fixture.ts the data every option shares
59
+ option-a.ts one default-exported drawing per option
60
+ option-b.ts
61
+ ```
62
+
63
+ The first segment of a slug is the **project**. `config.json` gives each project its own
64
+ `styles` and its own `head`, so one project's type scale and fonts never reach another's
65
+ drawings. A project the config names not draws bare.
66
+
67
+ Start the page with `et design [checkout]`, which serves the port `config.json` names. The
68
+ checkout defaults to the working directory, and `DE_PORT` overrules the config, so two
69
+ checkouts can be drawn at once. A repository that builds the tool rather than installing it
70
+ wraps this in a script - in the ethlete SDK, `yarn design` and `yarn design:check`.
71
+
72
+ Ethlete Studio carries its own copy of the tool, so it draws a checkout that installs nothing.
73
+ The machine still needs Node, and the render stage still needs `playwright` where the copy in
74
+ use can reach it.
75
+
76
+ `call.ts` calls `defineCall` from `@design-explore`. Each option carries a `key`, a `name`,
77
+ a `claim`, a `cost`, an optional `verdict` of `chosen` or `rejected`, and a `load` that
78
+ imports its own module. The host greys a rejected option and shows it on hover.
79
+
80
+ In a call that runs past about a dozen options, every option names the pass that drew it with
81
+ `round: 'r3'`. **That tag is the only thing that makes a round exist.** The host reads the
82
+ options, draws one band per round in the order the options introduce them, and lists those
83
+ same bands in the sidebar, so a new option can never leave the menu stale. A round whose
84
+ options all carry a verdict is **settled**: the page folds it down to its winner plus one row
85
+ per rejected option, and a link opens it again.
86
+
87
+ A call whose rounds have all ruled is **resolved**. Its winners usually form a chain, each one
88
+ the last plus a change, so the page stops drawing them: it leads with a **Result** band holding
89
+ the final winner, and prints the chain as a row of keys above it. Every round below folds to
90
+ rows. A key in the chain opens the round that drew it.
91
+
92
+ `rounds` is prose only. Each entry gives a `key`, a `title` and a `note` saying what came out
93
+ of that pass, which is how the intro stays short and each pass reads as a reply to the one
94
+ before. An untagged round still draws and still gets a menu row - it says the bare key until
95
+ somebody writes the entry. A `rounds` entry that no option names is stale prose, and the host
96
+ and the check both report it. Write the note in the same pass that sets the verdicts, never
97
+ before the user rules.
98
+
99
+ A call with one option and no `claim` is a **view**: one reference picture, drawn full
100
+ width with no verdict tag. Use it for a picture that answers no question.
101
+
102
+ Two views, and a way to compare:
103
+
104
+ - **rounds** is the default. Round headings, the result band, and the folding above.
105
+ - **all N** is a contact sheet: every option in the call at about a third size, in one grid, so
106
+ a call of two dozen fits on a screen or two. Use it to find the options worth a close look.
107
+ - Clicking an option's name anywhere - a heading, a folded row, a thumbnail - puts it in the
108
+ **compare overlay** at the top of the page. Two drawings that differ by a few pixels can only
109
+ be told apart in one place, so the overlay stacks every pick in one box at full size and the
110
+ reader switches between them: click the box or press space to blink, and with two picks the
111
+ arrow keys wipe a seam across. Nothing is scaled and nothing moves. The picks live in the URL
112
+ under `pick`, so a comparison is a link you can send.
113
+
114
+ Three constraints the tool puts on an option file:
115
+
116
+ - **Never import a package barrel.** The libraries resolve to source, so one barrel makes
117
+ the browser request every module in the library, and the requests fail with
118
+ `ERR_INSUFFICIENT_RESOURCES`. Import the one file you need.
119
+ - **The fixture is shared, and an option may not change it.** Options drawn at three
120
+ geometries cannot be compared.
121
+ - **One file per option**, so several agents can draw at once, and a broken option breaks
122
+ its own frame only.
123
+
124
+ **A repository with a sketch Storybook** draws every option in the **same** story, side by
125
+ side, under the real geometry the thing ships in. The default Storybook is
126
+ {%storybookUrl%}; an app with its own sketch Storybook names its port in the plan file.
127
+
128
+ Either way:
129
+
130
+ - Sketches take inputs only and stay out of the application's build.
131
+ - Prototype, never refactor. Do not touch the shipped component until the treatment is
132
+ settled.
133
+ - Keep the rejected options drawn, marked as rejected.
134
+
135
+ ## Check before you look
136
+
137
+ A broken build draws an overlay, so a screenshot of it looks like a design. **One check
138
+ covers that, and it is the whole gate.** Name the files you changed, never a whole project,
139
+ which is far slower and can fight the user's own editor.
140
+
141
+ With `et design`, run the checker from the checkout it draws:
142
+
143
+ ```bash
144
+ et design check --lint <changed files> --tsconfig
145
+ ```
146
+
147
+ `--tsconfig` with no path types every call of the checkout. `--checkout <path>` names another
148
+ one.
149
+
150
+ With a Storybook, copy {%resource:check-story.mjs%} to the **repository root** first:
151
+
152
+ ```bash
153
+ node check-story.mjs --lint <changed files> --tsconfig apps/<app>/.storybook/tsconfig.json
154
+ ```
155
+
156
+ Both take about four seconds and need no dev server. Then stop and hand the slug to the
157
+ user. Do not also open the page in a browser: it costs a browser launch, and both tools
158
+ transpile without type checking, so the render reports `ok` on a file that does not
159
+ compile. Open it only when the check passes and the drawing still does not appear:
160
+
161
+ ```bash
162
+ et design check --call <slug> # every option of the call
163
+ et design check --call <slug> --option b # one of them
164
+ node check-story.mjs --tsconfig <path> --story <story-id>
165
+ ```
166
+
167
+ ## Delegating a call
168
+
169
+ A call has one job per option, so it fans out. Every brief names `model: opus`.
170
+
171
+ 1. **One agent per option.** Give it the call folder, the option key, the fixture it must
172
+ not change, and the one claim its drawing has to support. Tell it to write its own
173
+ option file and nothing else, so two agents never touch one file.
174
+ 2. **One check-and-fix agent, after the drawing agents return.** Give it the changed files
175
+ and the call slug. It runs the check above, fixes what it reports, and repeats until the
176
+ check says `ok`. It may not change what an option draws, only what stops it rendering.
177
+ 3. **One write-up agent, once the user settles the call.** Give it the verdict in the
178
+ user's own words and the plan file. It records what won, what lost and why, it sets each
179
+ option's `verdict` in `call.ts`, and it writes that round's `note`. It runs while you open
180
+ the next call.
181
+
182
+ ## Screenshots
183
+
184
+ **Off by default.** The user has the page open and sends you a picture when something looks
185
+ wrong. Take one only when the code cannot tell you whether two options really differ, and
186
+ never to put in front of the user. Copy {%resource:shoot-template.mjs%} to the repository
187
+ root.
188
+
189
+ ```bash
190
+ node shoot.mjs <story-id> 1100 760 out.png
191
+ ```
192
+
193
+ - `waitUntil: 'networkidle'` never settles on an iframe that holds a live dev server
194
+ connection. Use `'domcontentloaded'`.
195
+ - `:hover`, `:focus-visible` and `:active` need a CDP session and `CSS.forcePseudoState`.
196
+ Several hold open at once, so one image shows them all. The template does this.
197
+
198
+ ## Writing it down
199
+
200
+ Every settled call goes in the exploration's plan file: what won, what lost, and why. Keep
201
+ an **Open** list for the calls not yet made. A rejected option written down stops the next
202
+ session from drawing it again. After a clear rejection, use that record to frame and draw the
203
+ next call; do not wait for the user to type “continue”.
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Find out why a story does not render, in the cheapest order.
3
+ *
4
+ * A compile error renders as an overlay on top of the story, so a screenshot of it looks
5
+ * like a design. Run this first, and never screenshot before it says `ok`.
6
+ *
7
+ * Copy to the repository root - Node resolves `playwright` from the working directory.
8
+ *
9
+ * node check-story.mjs --lint <file>… --tsconfig <path> # ~4s, no browser
10
+ * node check-story.mjs --tsconfig <path> --story <story-id> # + the render
11
+ * SB_URL=http://localhost:4401 node check-story.mjs --story <story-id>
12
+ *
13
+ * Three stages, because they cost three different amounts:
14
+ *
15
+ * 1. `eslint` on the files you changed, about a second. Name the files - never a whole
16
+ * project, which is far slower and can fight the user's own editor.
17
+ * 2. `tsc --noEmit` reads the source. It needs no dev server, waits for no rebuild, and
18
+ * catches every syntax and type error in about three seconds. Almost everything you
19
+ * break is caught here.
20
+ * 3. The browser catches what the source cannot: an Angular template error, a runtime
21
+ * throw, a missing story id. It costs a browser launch, and it can only see a build the
22
+ * dev server has already finished, so a fresh edit needs a few seconds first.
23
+ *
24
+ * A type error does not stop this repo's Storybook - it transpiles without checking - so
25
+ * stage 2 alone will report `ok` on a file that does not compile. That is why stage 1 exists.
26
+ */
27
+ import { execFileSync } from 'node:child_process';
28
+
29
+ const BASE = process.env.SB_URL ?? 'http://localhost:4400';
30
+
31
+ /**
32
+ * The type checker to run. `tsc` resolves the one the repository installed. Set
33
+ * `TSC=@typescript/native-preview` for the Go port, which is about a third faster - but it
34
+ * rejects a `baseUrl` in the config, so a repository that still uses one cannot run it.
35
+ */
36
+ const TSC = process.env.TSC ?? null;
37
+
38
+ const flags = { '--lint': [], '--tsconfig': [], '--story': [] };
39
+ let current = null;
40
+
41
+ for (const arg of process.argv.slice(2)) {
42
+ if (arg in flags) current = arg;
43
+ else if (current) flags[current].push(arg);
44
+ }
45
+
46
+ const lintFiles = flags['--lint'];
47
+ const [tsconfig] = flags['--tsconfig'];
48
+ const [id] = flags['--story'];
49
+
50
+ if (lintFiles.length === 0 && !tsconfig && !id) {
51
+ console.error('usage: node check-story.mjs [--lint <file>…] [--tsconfig <path>] [--story <id>]');
52
+ process.exit(2);
53
+ }
54
+
55
+ /** A bundler stack runs to hundreds of frames and says nothing. Keep the message. */
56
+ const trim = (text) =>
57
+ [...new Set(text.split('\n').filter((line) => !/^\s+at /.test(line) && line.trim() !== ''))].slice(0, 12).join('\n');
58
+
59
+ if (lintFiles.length > 0) {
60
+ try {
61
+ execFileSync('npx', ['eslint', ...lintFiles], { encoding: 'utf8', stdio: 'pipe' });
62
+ } catch (error) {
63
+ console.error(`LINT\n${trim(`${error.stdout ?? ''}${error.stderr ?? ''}`)}`);
64
+ process.exit(1);
65
+ }
66
+ }
67
+
68
+ if (tsconfig) {
69
+ try {
70
+ const cmd = TSC ? ['-y', TSC] : ['tsc'];
71
+ execFileSync('npx', [...cmd, '--noEmit', '-p', tsconfig], { encoding: 'utf8', stdio: 'pipe' });
72
+ } catch (error) {
73
+ console.error(`TYPE ERROR\n${trim(`${error.stdout ?? ''}${error.stderr ?? ''}`)}`);
74
+ process.exit(1);
75
+ }
76
+ }
77
+
78
+ if (!id) {
79
+ console.log('ok');
80
+ process.exit(0);
81
+ }
82
+
83
+ const { chromium } = await import('playwright');
84
+
85
+ const browser = await chromium.launch();
86
+ const page = await browser.newPage({ viewport: { width: 1400, height: 900 } });
87
+
88
+ /** Angular throws at render time; the overlay never shows those, so read the console too. */
89
+ const consoleErrors = [];
90
+ page.on('console', (m) => m.type() === 'error' && consoleErrors.push(m.text()));
91
+ page.on('pageerror', (e) => consoleErrors.push(e.message));
92
+
93
+ await page.goto(`${BASE}/iframe.html?id=${id}&viewMode=story`, { waitUntil: 'domcontentloaded' });
94
+
95
+ const rendered = await page
96
+ .locator('#storybook-root > *')
97
+ .first()
98
+ .waitFor({ timeout: 15000 })
99
+ .then(
100
+ () => true,
101
+ () => false,
102
+ );
103
+
104
+ /** The webpack overlay is an iframe, so its text is only reachable through a frame locator. */
105
+ const webpackOverlayText =
106
+ (await page.locator('#webpack-dev-server-client-overlay').count()) > 0
107
+ ? await page
108
+ .frameLocator('#webpack-dev-server-client-overlay')
109
+ .locator('body')
110
+ .innerText()
111
+ .catch(() => '(overlay present, text unreadable)')
112
+ : '';
113
+ /** The Vite overlay keeps its text in a shadow root. */
114
+ const viteOverlayText = await page.evaluate(() => {
115
+ const overlay = document.querySelector('vite-error-overlay');
116
+ if (!overlay) return '';
117
+ return overlay.shadowRoot?.textContent?.trim() || '(overlay present, text unreadable)';
118
+ });
119
+ const overlayText = webpackOverlayText || viteOverlayText;
120
+
121
+ await browser.close();
122
+
123
+ if (overlayText) {
124
+ console.error(`COMPILE ERROR on ${id}\n${trim(overlayText)}`);
125
+ process.exit(1);
126
+ }
127
+
128
+ if (!rendered) {
129
+ console.error(`EMPTY on ${id} - #storybook-root never got a child.`);
130
+ if (consoleErrors.length > 0) console.error(trim(consoleErrors.join('\n')));
131
+ process.exit(1);
132
+ }
133
+
134
+ if (consoleErrors.length > 0) {
135
+ console.error(`RENDERED WITH ERRORS on ${id}\n${trim(consoleErrors.join('\n'))}`);
136
+ process.exit(1);
137
+ }
138
+
139
+ console.log(`ok ${id}`);
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Screenshot one Storybook story, with optional forced pseudo-states.
3
+ *
4
+ * Copy to the repository root - Node resolves `playwright` from the working directory.
5
+ *
6
+ * node shoot.mjs <story-id> [width] [height] [out.png]
7
+ *
8
+ * To hold :hover / :focus-visible / :active open at the same time, fill FORCED below with
9
+ * one entry per element. A CSS selector is matched inside the story's document.
10
+ */
11
+ import { chromium } from 'playwright';
12
+
13
+ const BASE = process.env.SB_URL ?? 'http://localhost:4400';
14
+
15
+ /** @type {{ selector: string, states: ('hover'|'focus'|'focus-visible'|'active')[] }[]} */
16
+ const FORCED = [
17
+ // { selector: '[data-state="hover"] .the-band', states: ['hover'] },
18
+ ];
19
+
20
+ const [id, width = '1100', height = '760', out = 'shot.png'] = process.argv.slice(2);
21
+
22
+ if (!id) {
23
+ console.error('usage: node shoot.mjs <story-id> [width] [height] [out.png]');
24
+ process.exit(1);
25
+ }
26
+
27
+ const browser = await chromium.launch();
28
+ const page = await browser.newPage({
29
+ viewport: { width: Number(width), height: Number(height) },
30
+ deviceScaleFactor: 2,
31
+ });
32
+
33
+ await page.goto(`${BASE}/iframe.html?id=${id}&viewMode=story`, { waitUntil: 'domcontentloaded' });
34
+ await page.waitForSelector('#storybook-root > *', { timeout: 15000 });
35
+
36
+ const overlays = await page.locator('#webpack-dev-server-client-overlay, vite-error-overlay').count();
37
+ if (overlays > 0) {
38
+ console.error('compile error overlay is present - the image would lie');
39
+ await browser.close();
40
+ process.exit(1);
41
+ }
42
+
43
+ if (FORCED.length > 0) {
44
+ const cdp = await page.context().newCDPSession(page);
45
+ await cdp.send('DOM.enable');
46
+ await cdp.send('CSS.enable');
47
+ const { root } = await cdp.send('DOM.getDocument', { depth: -1 });
48
+
49
+ for (const { selector, states } of FORCED) {
50
+ const { nodeIds } = await cdp.send('DOM.querySelectorAll', { nodeId: root.nodeId, selector });
51
+ for (const nodeId of nodeIds) {
52
+ await cdp.send('CSS.forcePseudoState', { nodeId, forcedPseudoClasses: states });
53
+ }
54
+ }
55
+ }
56
+
57
+ await page.waitForTimeout(400);
58
+ await page.screenshot({ path: out });
59
+ await browser.close();
60
+
61
+ console.log(out);
@@ -0,0 +1,47 @@
1
+ # ADR Format
2
+
3
+ ADRs live in `docs/adr/` and use sequential numbering: `0001-slug.md`, `0002-slug.md`, etc.
4
+
5
+ Create the `docs/adr/` directory lazily: only when the first ADR is needed.
6
+
7
+ ## Template
8
+
9
+ ```md
10
+ # {Short title of the decision}
11
+
12
+ {1-3 sentences: what's the context, what did we decide, and why.}
13
+ ```
14
+
15
+ That's it. An ADR can be a single paragraph. The value is in recording _that_ a decision was made and _why_, not in filling out sections.
16
+
17
+ ## Optional sections
18
+
19
+ Only include these when they add genuine value. Most ADRs won't need them.
20
+
21
+ - **Status** frontmatter (`proposed | accepted | deprecated | superseded by ADR-NNNN`): useful when decisions are revisited
22
+ - **Considered Options**: only when the rejected alternatives are worth remembering
23
+ - **Consequences**: only when non-obvious downstream effects need to be called out
24
+
25
+ ## Numbering
26
+
27
+ Scan `docs/adr/` for the highest existing number and increment by one.
28
+
29
+ ## When to offer an ADR
30
+
31
+ All three of these must be true:
32
+
33
+ 1. **Hard to reverse**: the cost of changing your mind later is meaningful
34
+ 2. **Surprising without context**: a future reader will look at the code and wonder "why on earth did they do it this way?"
35
+ 3. **The result of a real trade-off**: there were genuine alternatives and you picked one for specific reasons
36
+
37
+ If a decision is easy to reverse, skip it: you'll just reverse it. If it's not surprising, nobody will wonder why. If there was no real alternative, there's nothing to record beyond "we did the obvious thing."
38
+
39
+ ### What qualifies
40
+
41
+ - **Architectural shape.** "We're using a monorepo." "The write model is event-sourced, the read model is projected into Postgres."
42
+ - **Integration patterns between contexts.** "Ordering and Billing communicate via domain events, not synchronous HTTP."
43
+ - **Technology choices that carry lock-in.** Database, message bus, auth provider, deployment target. Not every library: just the ones that would take a quarter to swap out.
44
+ - **Boundary and scope decisions.** "Customer data is owned by the Customer context; other contexts reference it by ID only." The explicit no-s are as valuable as the yes-s.
45
+ - **Deliberate deviations from the obvious path.** "We're using manual SQL instead of an ORM because X." Anything where a reasonable reader would assume the opposite. These stop the next engineer from "fixing" something that was deliberate.
46
+ - **Constraints not visible in the code.** "We can't use AWS because of compliance requirements." "Response times must be under 200ms because of the partner API contract."
47
+ - **Rejected alternatives when the rejection is non-obvious.** If you considered GraphQL and picked REST for subtle reasons, record it; otherwise someone will suggest GraphQL again in six months.
@@ -0,0 +1,60 @@
1
+ # CONTEXT.md Format
2
+
3
+ ## Structure
4
+
5
+ ```md
6
+ # {Context Name}
7
+
8
+ {One or two sentence description of what this context is and why it exists.}
9
+
10
+ ## Language
11
+
12
+ **Order**:
13
+ {A one or two sentence description of the term}
14
+ _Avoid_: Purchase, transaction
15
+
16
+ **Invoice**:
17
+ A request for payment sent to a customer after delivery.
18
+ _Avoid_: Bill, payment request
19
+
20
+ **Customer**:
21
+ A person or organization that places orders.
22
+ _Avoid_: Client, buyer, account
23
+ ```
24
+
25
+ ## Rules
26
+
27
+ - **Be opinionated.** When multiple words exist for the same concept, pick the best one and list the others under `_Avoid_`.
28
+ - **Keep definitions tight.** One or two sentences max. Define what it IS, not what it does.
29
+ - **Only include terms specific to this project's context.** General programming concepts (timeouts, error types, utility patterns) don't belong even if the project uses them extensively. Before adding a term, ask: is this a concept unique to this context, or a general programming concept? Only the former belongs.
30
+ - **Group terms under subheadings** when natural clusters emerge. If all terms belong to a single cohesive area, a flat list is fine.
31
+
32
+ ## Single vs multi-context repos
33
+
34
+ **Single context (most repos):** One `CONTEXT.md` at the repo root.
35
+
36
+ **Multiple contexts:** A `CONTEXT-MAP.md` at the repo root lists the contexts, where they live, and how they relate to each other:
37
+
38
+ ```md
39
+ # Context Map
40
+
41
+ ## Contexts
42
+
43
+ - [Ordering](./src/ordering/CONTEXT.md): receives and tracks customer orders
44
+ - [Billing](./src/billing/CONTEXT.md): generates invoices and processes payments
45
+ - [Fulfillment](./src/fulfillment/CONTEXT.md): manages warehouse picking and shipping
46
+
47
+ ## Relationships
48
+
49
+ - **Ordering → Fulfillment**: Ordering emits `OrderPlaced` events; Fulfillment consumes them to start picking
50
+ - **Fulfillment → Billing**: Fulfillment emits `ShipmentDispatched` events; Billing consumes them to generate invoices
51
+ - **Ordering ↔ Billing**: Shared types for `CustomerId` and `Money`
52
+ ```
53
+
54
+ The skill infers which structure applies:
55
+
56
+ - If `CONTEXT-MAP.md` exists, read it to find contexts
57
+ - If only a root `CONTEXT.md` exists, single context
58
+ - If neither exists, create a root `CONTEXT.md` lazily when the first term is resolved
59
+
60
+ When multiple contexts exist, infer which one the current topic relates to. If unclear, ask.
@@ -0,0 +1,80 @@
1
+ ---
2
+ name: domain-modeling
3
+ description: Build and sharpen a project's domain model. Use when discussing codebase terminology, writing or editing a CONTEXT.md, or recording or editing an ADR.
4
+ kind: skill
5
+ scope: both
6
+ ---
7
+
8
+ # Domain Modeling
9
+
10
+ Actively build and sharpen the project's domain model as you design. This is the _active_ discipline: challenging terms, inventing edge-case scenarios, and writing the glossary and decisions down the moment they crystallise. (Merely _reading_ `CONTEXT.md` for vocabulary is not this skill: that's a one-line habit any skill can do. This skill is for when you're changing the model, not just consuming it.)
11
+
12
+ ## File structure
13
+
14
+ Most repos have a single context:
15
+
16
+ ```
17
+ /
18
+ ├── CONTEXT.md
19
+ ├── docs/
20
+ │ └── adr/
21
+ │ ├── 0001-event-sourced-orders.md
22
+ │ └── 0002-postgres-for-write-model.md
23
+ └── src/
24
+ ```
25
+
26
+ If a `CONTEXT-MAP.md` exists at the root, the repo has multiple contexts. The map points to where each one lives:
27
+
28
+ ```
29
+ /
30
+ ├── CONTEXT-MAP.md
31
+ ├── docs/
32
+ │ └── adr/ ← system-wide decisions
33
+ ├── src/
34
+ │ ├── ordering/
35
+ │ │ ├── CONTEXT.md
36
+ │ │ └── docs/adr/ ← context-specific decisions
37
+ │ └── billing/
38
+ │ ├── CONTEXT.md
39
+ │ └── docs/adr/
40
+ ```
41
+
42
+ Create files lazily: only when you have something to write. If no `CONTEXT.md` exists, create one when the first term is resolved. If no `docs/adr/` exists, create it when the first ADR is needed.
43
+
44
+ ## During the session
45
+
46
+ ### Challenge against the glossary
47
+
48
+ When the user uses a term that conflicts with the existing language in `CONTEXT.md`, call it out immediately. "Your glossary defines 'cancellation' as X, but you seem to mean Y. Which is it?"
49
+
50
+ ### Sharpen fuzzy language
51
+
52
+ When the user uses vague or overloaded terms, propose a precise canonical term. "You're saying 'account': do you mean the Customer or the User? Those are different things."
53
+
54
+ ### Discuss concrete scenarios
55
+
56
+ When domain relationships are being discussed, stress-test them with specific scenarios. Invent scenarios that probe edge cases and force the user to be precise about the boundaries between concepts.
57
+
58
+ ### Cross-reference with code
59
+
60
+ When the user states how something works, check whether the code agrees. If you find a contradiction, surface it: "Your code cancels entire Orders, but you just said partial cancellation is possible. Which is right?"
61
+
62
+ ### Update CONTEXT.md inline
63
+
64
+ When a term is resolved, update `CONTEXT.md` right there. Don't batch these up: capture them as they happen. Use the format in {%resource:CONTEXT-FORMAT.md%}.
65
+
66
+ `CONTEXT.md` should be totally devoid of implementation details. Do not treat `CONTEXT.md` as a spec, a scratch pad, or a repository for implementation decisions. It is a glossary and nothing else.
67
+
68
+ ### Offer ADRs sparingly
69
+
70
+ Only offer to create an ADR when all three are true:
71
+
72
+ 1. **Hard to reverse**: the cost of changing your mind later is meaningful
73
+ 2. **Surprising without context**: a future reader will wonder "why did they do it this way?"
74
+ 3. **The result of a real trade-off**: there were genuine alternatives and you picked one for specific reasons
75
+
76
+ If any of the three is missing, skip the ADR. Use the format in {%resource:ADR-FORMAT.md%}.
77
+
78
+ ---
79
+
80
+ Vendored from [mattpocock/skills](https://github.com/mattpocock/skills) at `3cca18b`, MIT (Copyright (c) 2026 Matt Pocock).
@@ -0,0 +1,13 @@
1
+ ---
2
+ name: grill-with-docs
3
+ description: A relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go.
4
+ kind: skill
5
+ scope: both
6
+ modelInvocation: false
7
+ ---
8
+
9
+ Call the Skill tool twice, for {%skill:grilling%} and {%skill:domain-modeling%}.
10
+
11
+ ---
12
+
13
+ Vendored from [mattpocock/skills](https://github.com/mattpocock/skills) at `3cca18b`, MIT (Copyright (c) 2026 Matt Pocock).
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: grilling
3
+ description: Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases.
4
+ kind: skill
5
+ scope: both
6
+ ---
7
+
8
+ Interview the user relentlessly until you reach a shared understanding. Map this as a **design tree**: every decision branches into the decisions that hang off it.
9
+
10
+ Work the tree in **rounds**. The **frontier** is every decision whose prerequisites are already settled: the questions you can ask _now_ without guessing at answers you haven't heard yet. Ask the whole frontier in one round: number each question and give your recommended answer. Then wait for the user's answers before the next round.
11
+
12
+ Format a round like so:
13
+
14
+ ```
15
+ ❓ **Q1** - **<question title>**: <question body, might be multiple paragraphs, including multiple choices>
16
+
17
+ ➡️ <your recommended answer>
18
+
19
+ ---
20
+
21
+ ❓ **Q2** - **<question title>**: <question body, might be multiple paragraphs, including multiple choices>
22
+
23
+ ➡️ <your recommended answer>
24
+ ```
25
+
26
+ Each round the user answers reshapes the tree: settled decisions push the frontier outward and unblock questions that depended on them. Recompute the frontier and ask the next round. A question whose answer depends on another question still open in this round belongs to a _later_ round, not this one.
27
+
28
+ Finding _facts_ is your job, never the user's. When a frontier question needs a fact from the environment (filesystem, tools, etc.), dispatch a sub-agent to find it; don't ask the user for anything you could look up yourself. Don't block on it: a running exploration is an unsettled prerequisite, so only the questions downstream of it wait for the sub-agent to report; ask the rest of the frontier now. The _decisions_ are the user's: put each to them and wait.
29
+
30
+ The session is done when the frontier is empty: every branch of the design tree visited, nothing left silently assumed. Do not act on it until the user confirms you have reached a shared understanding.
31
+
32
+ ---
33
+
34
+ Vendored from [mattpocock/skills](https://github.com/mattpocock/skills) at `3cca18b`, MIT (Copyright (c) 2026 Matt Pocock).