@ethlete/agent-rules 0.1.0-next.3 → 0.1.0-next.30

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 (200) hide show
  1. package/AGENTS.md +28 -0
  2. package/CHANGELOG.md +463 -0
  3. package/CLAUDE.md +1 -0
  4. package/README.md +362 -29
  5. package/THIRD-PARTY-LICENSES.md +35 -0
  6. package/content/defaults.json +1 -1
  7. package/content/git-hooks/post-checkout.sh +10 -0
  8. package/content/git-hooks/pre-push.sh +5 -0
  9. package/content/hooks/context-warning.py +871 -110
  10. package/content/hooks/subagent-model-policy.py +194 -0
  11. package/content/mods/.claude-plugin/marketplace.json +11 -0
  12. package/content/mods/plugins/e2e-progress/.claude-plugin/plugin.json +6 -0
  13. package/content/mods/plugins/e2e-progress/hooks/hooks.json +1 -0
  14. package/content/mods/plugins/e2e-progress/hooks/register.tsx +442 -0
  15. package/content/mods/plugins/e2e-progress/tsconfig.json +3 -0
  16. package/content/mods/plugins/e2e-progress/types/index.d.ts +23 -0
  17. package/content/output-styles/ste-clarity.md +131 -0
  18. package/content/rules/agent-sessions-wake-up.md +11 -0
  19. package/content/rules/app-styling.md +66 -0
  20. package/content/rules/comments.md +4 -1
  21. package/content/rules/lint-and-format.md +7 -5
  22. package/content/rules/nx-layout.md +50 -0
  23. package/content/rules/reactive-state.md +1 -1
  24. package/content/rules/styling.md +9 -7
  25. package/content/rules/subagent-models.md +39 -0
  26. package/content/skills/agent-sessions/SKILL.md +81 -0
  27. package/content/skills/agent-sessions/agent-sessions.mjs +492 -0
  28. package/content/skills/angular-patterns/SKILL.md +30 -7
  29. package/content/skills/api-source/SKILL.md +156 -0
  30. package/content/skills/app-testing/SKILL.md +141 -0
  31. package/content/skills/codex-subagent/SKILL.md +90 -0
  32. package/content/skills/codex-subagent/codex-agent.mjs +229 -0
  33. package/content/skills/design-exploration/SKILL.md +657 -0
  34. package/content/skills/design-exploration/check-story.mjs +139 -0
  35. package/content/skills/design-exploration/shoot-template.mjs +61 -0
  36. package/content/skills/domain-modeling/ADR-FORMAT.md +47 -0
  37. package/content/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
  38. package/content/skills/domain-modeling/SKILL.md +80 -0
  39. package/content/skills/e2e-triage/SKILL.md +80 -0
  40. package/content/skills/figma-export/SKILL.md +191 -0
  41. package/content/skills/figma-export/dump-figma-layers.py +83 -0
  42. package/content/skills/figma-export/dump-figma-svg.py +235 -0
  43. package/content/skills/figma-export/measure-template.mjs +87 -0
  44. package/content/skills/git-commit/SKILL.md +11 -7
  45. package/content/skills/git-flow/SKILL.md +87 -0
  46. package/content/skills/grill-with-docs/SKILL.md +13 -0
  47. package/content/skills/grilling/SKILL.md +51 -0
  48. package/content/skills/handoff/SKILL.md +125 -5
  49. package/content/skills/query/SKILL.md +165 -64
  50. package/content/skills/rxjs-signals/SKILL.md +33 -4
  51. package/content/skills/sdk-docs/SKILL.md +50 -14
  52. package/content/skills/sdk-local-build/SKILL.md +26 -8
  53. package/content/skills/sdk-local-build/sdk-local-baseline.mjs +64 -0
  54. package/content/skills/sdk-source/SKILL.md +37 -34
  55. package/content/skills/sdk-update/SKILL.md +103 -0
  56. package/content/skills/story-styling/SKILL.md +10 -8
  57. package/content/skills/styleguide/SKILL.md +12 -7
  58. package/content/skills/styleguide/assets.md +54 -0
  59. package/content/skills/styleguide/changesets.md +17 -0
  60. package/content/skills/styleguide/file-structure.md +73 -0
  61. package/content/skills/styleguide/lint-rule-lookup.md +51 -0
  62. package/content/skills/styleguide/storybook-structure.md +25 -0
  63. package/content/skills/theming/SKILL.md +50 -28
  64. package/content/skills/timetrack/SKILL.md +397 -0
  65. package/content/skills/verify-in-app/SKILL.md +107 -0
  66. package/migrations/app-styling-utilities.md +63 -0
  67. package/migrations/list-state-query-form.md +20 -0
  68. package/migrations/nx-layout.md +29 -0
  69. package/migrations/sdk-components-over-hand-built-ui.md +27 -0
  70. package/migrations/search-query-field.md +52 -0
  71. package/migrations.json +48 -0
  72. package/package.json +19 -3
  73. package/src/index.js +85 -30
  74. package/src/index.js.map +1 -1
  75. package/src/lib/cli-args.d.ts +9 -0
  76. package/src/lib/cli-args.js +48 -0
  77. package/src/lib/cli-args.js.map +1 -0
  78. package/src/lib/commitlint.d.ts +10 -0
  79. package/src/lib/commitlint.js +51 -0
  80. package/src/lib/commitlint.js.map +1 -0
  81. package/src/lib/config.d.ts +41 -5
  82. package/src/lib/config.js +117 -10
  83. package/src/lib/config.js.map +1 -1
  84. package/src/lib/filter.d.ts +2 -0
  85. package/src/lib/filter.js +4 -4
  86. package/src/lib/filter.js.map +1 -1
  87. package/src/lib/frontmatter.d.ts +2 -0
  88. package/src/lib/frontmatter.js +33 -5
  89. package/src/lib/frontmatter.js.map +1 -1
  90. package/src/lib/git-flow/build.d.ts +35 -0
  91. package/src/lib/git-flow/build.js +24 -0
  92. package/src/lib/git-flow/build.js.map +1 -0
  93. package/src/lib/git-flow/config.d.ts +59 -0
  94. package/src/lib/git-flow/config.js +50 -0
  95. package/src/lib/git-flow/config.js.map +1 -0
  96. package/src/lib/git-flow/index.d.ts +6 -0
  97. package/src/lib/git-flow/index.js +23 -0
  98. package/src/lib/git-flow/index.js.map +1 -0
  99. package/src/lib/git-flow/parse.d.ts +49 -0
  100. package/src/lib/git-flow/parse.js +300 -0
  101. package/src/lib/git-flow/parse.js.map +1 -0
  102. package/src/lib/git-flow/rename.d.ts +24 -0
  103. package/src/lib/git-flow/rename.js +70 -0
  104. package/src/lib/git-flow/rename.js.map +1 -0
  105. package/src/lib/git-flow/start.d.ts +49 -0
  106. package/src/lib/git-flow/start.js +57 -0
  107. package/src/lib/git-flow/start.js.map +1 -0
  108. package/src/lib/git-flow/validate.d.ts +34 -0
  109. package/src/lib/git-flow/validate.js +72 -0
  110. package/src/lib/git-flow/validate.js.map +1 -0
  111. package/src/lib/git-flow-command.d.ts +4 -0
  112. package/src/lib/git-flow-command.js +165 -0
  113. package/src/lib/git-flow-command.js.map +1 -0
  114. package/src/lib/git-flow-repair.d.ts +17 -0
  115. package/src/lib/git-flow-repair.js +165 -0
  116. package/src/lib/git-flow-repair.js.map +1 -0
  117. package/src/lib/git-flow-start.d.ts +20 -0
  118. package/src/lib/git-flow-start.js +140 -0
  119. package/src/lib/git-flow-start.js.map +1 -0
  120. package/src/lib/git.d.ts +64 -0
  121. package/src/lib/git.js +131 -0
  122. package/src/lib/git.js.map +1 -0
  123. package/src/lib/gitlab.d.ts +35 -0
  124. package/src/lib/gitlab.js +116 -0
  125. package/src/lib/gitlab.js.map +1 -0
  126. package/src/lib/index.d.ts +2 -0
  127. package/src/lib/index.js +26 -11
  128. package/src/lib/index.js.map +1 -1
  129. package/src/lib/migrate.js +6 -1
  130. package/src/lib/migrate.js.map +1 -1
  131. package/src/lib/output-style-command.d.ts +3 -0
  132. package/src/lib/output-style-command.js +69 -0
  133. package/src/lib/output-style-command.js.map +1 -0
  134. package/src/lib/output-style.d.ts +38 -0
  135. package/src/lib/output-style.js +134 -0
  136. package/src/lib/output-style.js.map +1 -0
  137. package/src/lib/owned-paths.js +27 -3
  138. package/src/lib/owned-paths.js.map +1 -1
  139. package/src/lib/package-runner.d.ts +4 -0
  140. package/src/lib/package-runner.js +86 -0
  141. package/src/lib/package-runner.js.map +1 -0
  142. package/src/lib/plain-text.d.ts +9 -0
  143. package/src/lib/plain-text.js +22 -0
  144. package/src/lib/plain-text.js.map +1 -0
  145. package/src/lib/plan.d.ts +6 -6
  146. package/src/lib/plan.js +164 -40
  147. package/src/lib/plan.js.map +1 -1
  148. package/src/lib/prompt.d.ts +8 -0
  149. package/src/lib/prompt.js +35 -0
  150. package/src/lib/prompt.js.map +1 -0
  151. package/src/lib/render.d.ts +25 -2
  152. package/src/lib/render.js +43 -10
  153. package/src/lib/render.js.map +1 -1
  154. package/src/lib/sessions-command.d.ts +4 -0
  155. package/src/lib/sessions-command.js +16 -0
  156. package/src/lib/sessions-command.js.map +1 -0
  157. package/src/lib/sync.d.ts +0 -1
  158. package/src/lib/sync.js +30 -17
  159. package/src/lib/sync.js.map +1 -1
  160. package/src/lib/targets/agents-skills.js +1 -1
  161. package/src/lib/targets/agents-skills.js.map +1 -1
  162. package/src/lib/targets/claude-hooks.d.ts +3 -23
  163. package/src/lib/targets/claude-hooks.js +22 -86
  164. package/src/lib/targets/claude-hooks.js.map +1 -1
  165. package/src/lib/targets/claude-mods.d.ts +20 -0
  166. package/src/lib/targets/claude-mods.js +47 -0
  167. package/src/lib/targets/claude-mods.js.map +1 -0
  168. package/src/lib/targets/claude.js +2 -3
  169. package/src/lib/targets/claude.js.map +1 -1
  170. package/src/lib/targets/codex-hooks.d.ts +12 -0
  171. package/src/lib/targets/codex-hooks.js +35 -0
  172. package/src/lib/targets/codex-hooks.js.map +1 -0
  173. package/src/lib/targets/codex.js +4 -4
  174. package/src/lib/targets/codex.js.map +1 -1
  175. package/src/lib/targets/copilot.js +4 -4
  176. package/src/lib/targets/copilot.js.map +1 -1
  177. package/src/lib/targets/cursor.js +3 -3
  178. package/src/lib/targets/cursor.js.map +1 -1
  179. package/src/lib/targets/git-hooks.d.ts +25 -0
  180. package/src/lib/targets/git-hooks.js +71 -0
  181. package/src/lib/targets/git-hooks.js.map +1 -0
  182. package/src/lib/targets/hooks-shared.d.ts +60 -0
  183. package/src/lib/targets/hooks-shared.js +130 -0
  184. package/src/lib/targets/hooks-shared.js.map +1 -0
  185. package/src/lib/targets/mcp.d.ts +16 -0
  186. package/src/lib/targets/mcp.js +68 -0
  187. package/src/lib/targets/mcp.js.map +1 -0
  188. package/src/lib/targets/shared.d.ts +1 -9
  189. package/src/lib/targets/shared.js +4 -8
  190. package/src/lib/targets/shared.js.map +1 -1
  191. package/src/lib/timetrack-command.d.ts +11 -0
  192. package/src/lib/timetrack-command.js +1063 -0
  193. package/src/lib/timetrack-command.js.map +1 -0
  194. package/src/lib/timetrack-snapshot.d.ts +55 -0
  195. package/src/lib/timetrack-snapshot.js +120 -0
  196. package/src/lib/timetrack-snapshot.js.map +1 -0
  197. package/src/lib/timetrack.d.ts +561 -0
  198. package/src/lib/timetrack.js +279 -0
  199. package/src/lib/timetrack.js.map +1 -0
  200. package/content/skills/styleguide/STYLEGUIDE.md +0 -520
@@ -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,80 @@
1
+ ---
2
+ name: e2e-triage
3
+ description: Work through a failed Playwright e2e run - read the failures file the @ethlete/cli failure reporter writes, group the failures by error, fix the largest group first, and re-run only the specs that failed. Read when an e2e run fails with more than a few tests, when an `e2e-failures.jsonl` file is present, or before you start a full e2e run after a change.
4
+ kind: skill
5
+ scope: consumer
6
+ requires: ['@ethlete/cli']
7
+ vars: [docsBaseUrl]
8
+ ---
9
+
10
+ # Triage a failed e2e run
11
+
12
+ Many failed tests rarely mean many bugs. In one real run, a single change in a shared pipe failed 61
13
+ tests. After the fix, 158 of the first 159 retried tests passed. So find the shared cause first, and
14
+ never fix failures one test at a time from the top of the log.
15
+
16
+ ## Before the run
17
+
18
+ 1. Check that `playwright.config.ts` lists `@ethlete/cli/playwright/failure-reporter` in `reporter`.
19
+ If not, add it next to the reporter that is there: `[['list'], ['@ethlete/cli/playwright/failure-reporter']]`.
20
+ The reporter writes one JSON line per failed attempt while the run goes on. The guide is
21
+ {%docsBaseUrl%}/cli/e2e.
22
+ 2. Run the specs of the files you changed first. Start the full suite only when they pass. A full
23
+ run takes long, and one broken shared file fails it everywhere.
24
+
25
+ ## Read the failures
26
+
27
+ The file is `e2e-failures.jsonl` next to `playwright.config.ts`, unless `E2E_FAILURES_FILE` or the
28
+ `outputFile` option of the reporter names another path. You can read it while the run goes on.
29
+
30
+ Each line has `test`, `location`, `project`, `status`, `retry`, `seconds`, `error`, `detail` and `context`.
31
+ `error` holds the first lines of the message. `detail` holds the first later line with a URL, for errors whose first lines do not name it, or `null`. `context` is the path of Playwright's
32
+ `error-context.md`, which holds the page snapshot at the failure.
33
+
34
+ Do not parse the console log of the run. It mixes the output of parallel workers, and it has no end
35
+ line if the run stops.
36
+
37
+ ## Group, then fix the largest group
38
+
39
+ 1. Normalize the `error` text of each line. Remove line and column numbers, timeouts in ms, ids,
40
+ hashes and dates. Keep the matcher, the locator and the expected value.
41
+ 2. Group the lines by the normalized text. Count the distinct spec files per group, not only the lines.
42
+ A group that spans many spec files points at shared code: a component, a pipe, a fixture or a
43
+ test helper.
44
+ 3. Take the largest group. Read one `context` file of it, then the code the error points at. Find
45
+ one cause that explains the whole group.
46
+ 4. Fix that cause. Then re-run only the spec files of that group.
47
+
48
+ A group whose errors are a connection refused (`ERR_CONNECTION_REFUSED`, `ECONNREFUSED`) on a local mock or
49
+ `webServer` port is an infrastructure group, not a test verdict. Check that the server is still up,
50
+ restart it, and re-run those tests before you read them as failures.
51
+
52
+ Small groups that differ only in a locator often have the same cause. Check them after the large group
53
+ is fixed, because a fix often clears them too.
54
+
55
+ ## Re-run only what failed
56
+
57
+ Take the spec file from `location`, without the `:line`. Collect each file once, then run:
58
+
59
+ ```bash
60
+ npx playwright test <file> <file> --project=<project> --no-deps
61
+ ```
62
+
63
+ - Pass each file as its own argument. In zsh, an unquoted `$files` is one argument, and Playwright
64
+ then finds no tests.
65
+ - `--no-deps` skips the setup projects. Leave it out if the failure is in a setup project, or if the
66
+ setup state is gone.
67
+ - `--project` comes from the `project` field. Run one call per project.
68
+
69
+ ## Retries and flaky tests
70
+
71
+ With `retries` on, a test that fails each attempt has one line per attempt (`retry` 0, 1, and so on).
72
+ A test with a line for `retry` 0 and no line for the last retry passed on a later attempt: it is flaky.
73
+ Report a flaky test, but fix the stable failures first.
74
+
75
+ ## Rules
76
+
77
+ - Never skip a failed test, or mark it with `test.fail()` or `test.fixme()`, to make a run pass.
78
+ - Never change an assertion to match the wrong value the app shows now. A failed e2e test is a
79
+ signal first. If you think the test is wrong, say why, and ask before you change it.
80
+ - Report per group: the cause, the fix, the spec files you re-ran, and what still fails.
@@ -0,0 +1,191 @@
1
+ ---
2
+ name: figma-export
3
+ description: Reconcile a component against a Figma export - which exports to ask the designer for (an .svg frame and its "copy as CSS" dump), how to dump the geometry out of each, which of their numbers are authoritative, and how to measure the real rendered result against them headlessly. Use whenever a design export is dropped next to a component and the component has to be matched to it.
4
+ kind: skill
5
+ scope: both
6
+ ---
7
+
8
+ # Reconcile a component against a Figma export
9
+
10
+ Three things arrive from Figma, and each answers a different question:
11
+
12
+ | Export | Gives you | Cannot tell you |
13
+ | ------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------- |
14
+ | **`.svg`** (Export frame) | Exact geometry with nesting, exact fills, and a picture once you rasterise it | Layer names, auto-layout intent, font sizes |
15
+ | **`.css`** (Copy as CSS) | Named layers, auto-layout properties, typography metrics, design-token names | Any hierarchy at all — the dump is flat |
16
+ | **`.png`** (a screenshot) | Figma's own blue measurement overlays, and what the designer chose to frame | Nothing machine-readable |
17
+
18
+ **Ask for the `.svg` and the `.css` together.** That pair is the preferred complete input:
19
+ the SVG is the export you can look at and measure, and the CSS names layers and records
20
+ type. A PNG earns its place only when it is a _screenshot_ carrying dev-mode annotations.
21
+ Exports expose rendered color values, but they do not identify the authoritative semantic
22
+ theme token.
23
+
24
+ If one artifact is missing, inspect the existing code and the artifact you do have. List
25
+ the exact facts that remain unknown and do not invent their measurements. Ask before a
26
+ structural choice or an untraceable numeric value, not before useful read-only inspection.
27
+
28
+ ## 1. Read the export before touching code
29
+
30
+ ### From an `.svg` — look at it, then measure it
31
+
32
+ Rasterise it and actually open the image. Figma outlines text on export, so this needs no
33
+ webfonts and matches the frame exactly:
34
+
35
+ ```bash
36
+ magick -density 144 <export.svg> <scratch>/frame.png # then read frame.png
37
+ ```
38
+
39
+ (If the render looks wrong, ImageMagick fell back to its own SVG renderer — check for
40
+ `RSVG` in `magick -list format | grep SVG`, or screenshot the file in Playwright instead.)
41
+
42
+ Then dump the geometry with {%resource:dump-figma-svg.py%} — every shape in document order,
43
+ indented by group nesting, with absolute coordinates:
44
+
45
+ ```bash
46
+ python3 dump-figma-svg.py <export.svg>
47
+ ```
48
+
49
+ It closes with two summaries worth reading first: repeated rect sizes, which say _one
50
+ component rendered N times_ where the CSS dump would show N unrelated frames; and the measured
51
+ gaps between rects that share a top edge, which is the auto-layout gap without having to
52
+ believe a label.
53
+
54
+ What the SVG does **not** carry:
55
+
56
+ - **Layer names.** Nothing is called `Card` or `Badge`; you match shapes to the picture.
57
+ - **Auto-layout intent.** `Hug` vs `Fixed` vs `Fill` is gone, so a width you read off is the
58
+ width _at this one size_, not a rule. Derive padding and gaps from the coordinates, and
59
+ treat a child width as content-driven until the picture or the CSS dump says otherwise.
60
+ - **Typography.** Outlined text is a `<path>`; the dumper labels the wide ones `text?` and
61
+ boxes them, which places a text run but tells you nothing about `font-size` or
62
+ `line-height`. If the designer exported with _Outline Text_ off, `<text>` nodes survive and
63
+ the dumper prints their font metrics and strings — take them when you get them.
64
+
65
+ Two numbers to read carefully: a **stroke is centred**, so a 32px circle with a 1px border
66
+ exports as `31 × 31` at `.5` offsets — add the stroke width back before comparing. And an
67
+ **outlined glyph box is the ink**, not the line box: a 17px/20px title measures ~12px tall,
68
+ the same cap-trim the CSS dump reports, so never match a text node's height.
69
+
70
+ ### From a `.css` dump — names, intent and type metrics
71
+
72
+ Never grep the raw file — its layout defeats naive parsing (see the traps below). Run
73
+ {%resource:dump-figma-layers.py%}:
74
+
75
+ ```bash
76
+ python3 dump-figma-layers.py <export.css> # every layer, artwork dropped
77
+ python3 dump-figma-layers.py <export.css> '^(Widget|Frame|Card)' # only what you name
78
+ ```
79
+
80
+ Then work out the frame's box model top-down — outer frame width and padding, then each
81
+ child's `width`/`gap`/`flex-grow` — and write the ladder down before you start editing, so you
82
+ can tell a deliberate design decision from a Figma artefact.
83
+
84
+ ### Whatever you have, look at the picture before you decide what to build
85
+
86
+ The CSS dump is a **flat** sequence of layers with no nesting whatsoever, so the tree is not in
87
+ it; the SVG has the tree but no names. The image is what disambiguates:
88
+
89
+ - **Hierarchy and reading order** — which layer contains which. The only other clue in the CSS
90
+ is `order:` inside a sibling run, which does not cross frames.
91
+ - **Repetition vs distinct layers.** Six blocks with identical declarations are one component
92
+ rendered six times. The CSS looks like six unrelated frames.
93
+ - **Multiple states in one board.** Frames are routinely laid out side by side as
94
+ default / hover / selected / empty, with a pink cursor marking the interaction. The CSS
95
+ gives you every state flattened together with nothing saying which is which, so a number
96
+ read blind may belong to a hover state you are not building.
97
+ - **Figma's own measurement overlays.** Blue annotations like `396 × 401 Hug` or `347 × 23`
98
+ are authoritative and frequently _absent_ from the CSS — `Hug` in particular tells you a
99
+ dimension is content-driven, which no declaration in the export records. These live only in
100
+ a canvas _screenshot_; a frame export of any format drops them.
101
+ - **What is decoration.** Cursors, callout arrows and section captions painted on the board
102
+ are not part of the component, but they do emit layers.
103
+
104
+ ### Traps in the `.css` export itself
105
+
106
+ - **Sub-comments carry the real properties.** Figma writes `/* Auto layout */`,
107
+ `/* Inside auto layout */`, `/* or 16px */` and token names like
108
+ `/* Brand/Chalk White/500 */` _between_ a layer's declarations. A parser that starts a new
109
+ layer at every comment reports frames with **zero properties** and swallows the geometry.
110
+ The dumper folds them in — it decides by the blank line that follows a real layer name, not
111
+ by the name, so component layers called `Profile/Badge` survive.
112
+ - **Frame labels lie.** A frame _named_ "Widget 636px" routinely has `width: 640px`, and a
113
+ label like "8 rows = 564px" often does not reconcile with the grid it sits in. Trust the
114
+ declarations, never the label.
115
+ - **Text heights are cap-trimmed.** With `leading-trim: both; text-edge: cap`, a 17px/20px
116
+ title reports `height: 12px`. Compare `font-size`, `line-height` and `letter-spacing`;
117
+ never compare a text node's height.
118
+ - **Absolute `left`/`top`** are canvas coordinates of the whole board. Only the _differences_
119
+ within one frame mean anything.
120
+
121
+ ## 2. Decide what the export is allowed to dictate
122
+
123
+ - **Authoritative:** geometry and typography metrics — widths, padding, gaps, `flex-grow`,
124
+ border radius, font size / weight / line-height / letter-spacing, and the breakpoints at
125
+ which the layout changes.
126
+ - **Never authoritative: semantic colour choice.** Backgrounds, text, borders and
127
+ interaction states resolve from the repository's theme tokens. A hex in the
128
+ export is information about the _designer's_ palette, not a value to paste. Where the
129
+ export's colour and the token disagree, keep the token and note the delta for the design
130
+ review; the export can be wrong about contrast in a way the tokens are not. This holds
131
+ hardest for an SVG, whose fills are exact and therefore tempting: an exact wrong answer is
132
+ still wrong.
133
+ - **Radius and spacing snap to the scale.** Round to the nearest token rather than emitting an
134
+ arbitrary value, and say so if the export's number is more than a step away.
135
+
136
+ ## 3. Ask before making a structural choice
137
+
138
+ Matching numbers is mechanical; the choices around them are not. Stop and ask when the export
139
+ implies:
140
+
141
+ - a **breakpoint strategy** — container queries with an exact ladder vs a retuned
142
+ `auto-fill`/`auto-fit` grid;
143
+ - **shared-component churn** — a new variant on a component other screens already use, vs a
144
+ local override;
145
+ - a **scroll region** — which part scrolls and what stays pinned;
146
+ - **fixed sizing** where the component is currently fluid, or the reverse;
147
+ - anything the export shows that the API does not yet return.
148
+
149
+ Colour deltas are informational — report them, do not block on them.
150
+
151
+ ## 4. Measure the real thing, don't eyeball it
152
+
153
+ A build passing proves nothing about geometry. Render the component's real markup against the
154
+ **production stylesheet** and read the computed boxes back. {%resource:measure-template.mjs%}
155
+ is a working starting point; copy it into a scratch directory with the compiled stylesheet
156
+ beside it:
157
+
158
+ ```bash
159
+ # whatever your build emits — the point is a real, fully compiled stylesheet
160
+ cp dist/apps/<app>/browser/styles-*.css <scratch>/styles.css
161
+ node <scratch>/measure-template.mjs
162
+ ```
163
+
164
+ Confirm the arbitrary utilities you wrote actually compiled — a typo in
165
+ `@min-[392px]` fails silently:
166
+
167
+ ```bash
168
+ grep -oE '@container \(width >= [0-9]+px\)|\.(h-15|rounded-sm)\{[^}]*\}' dist/apps/<app>/browser/styles-*.css | sort -u
169
+ ```
170
+
171
+ Harness gotchas, each of which will cost you an hour:
172
+
173
+ - **`page.setContent()` renders on `about:blank`, which blocks `file://` subresources** — the
174
+ stylesheet silently never loads. Write a real HTML file and `page.goto('file://…')`.
175
+ - **Never lay the probes out in a flex _row_.** They shrink, and container queries then report
176
+ results for a width the component would never see. Stack them in a column at explicit widths.
177
+ - **The harness has no webfonts** unless you copy the `@font-face` sources too. Text runs
178
+ ~1px wide of reality, so a label that wraps in the harness may well fit in the app. Check
179
+ before calling a wrap a defect.
180
+ - Playwright is CommonJS and unresolvable from a scratch directory — `createRequire` against
181
+ the repo root, as the template does. When driving a story, follow the repository's
182
+ installed Storybook verification guidance if present.
183
+
184
+ ## 5. Close the loop
185
+
186
+ Report the measured numbers next to the export's, per width — not "matches the design". Say
187
+ explicitly which parts of the export you deliberately did **not** implement and why (colour
188
+ kept as tokens, a label the product decided never to render, a field the API lacks). Then
189
+ report that the export files are no longer needed. Delete only artifacts this workflow
190
+ created and only when the workflow or user explicitly authorizes cleanup; otherwise let the
191
+ user decide whether supplied exports remain useful records.