@ethlete/agent-rules 0.1.0-next.2 → 0.1.0-next.21

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 (178) hide show
  1. package/CHANGELOG.md +275 -0
  2. package/README.md +350 -29
  3. package/THIRD-PARTY-LICENSES.md +35 -0
  4. package/content/git-hooks/post-checkout.sh +10 -0
  5. package/content/git-hooks/pre-push.sh +5 -0
  6. package/content/hooks/context-warning.py +766 -108
  7. package/content/hooks/subagent-model-policy.py +193 -0
  8. package/content/output-styles/ste-clarity.md +131 -0
  9. package/content/rules/app-styling.md +59 -0
  10. package/content/rules/comments.md +53 -20
  11. package/content/rules/lint-and-format.md +6 -5
  12. package/content/rules/nx-layout.md +47 -0
  13. package/content/rules/reactive-state.md +1 -1
  14. package/content/rules/styling.md +9 -7
  15. package/content/rules/subagent-models.md +29 -0
  16. package/content/skills/angular-patterns/SKILL.md +30 -7
  17. package/content/skills/api-source/SKILL.md +130 -0
  18. package/content/skills/app-testing/SKILL.md +141 -0
  19. package/content/skills/codex-subagent/SKILL.md +90 -0
  20. package/content/skills/codex-subagent/codex-agent.mjs +229 -0
  21. package/content/skills/design-exploration/SKILL.md +205 -0
  22. package/content/skills/design-exploration/check-story.mjs +139 -0
  23. package/content/skills/design-exploration/shoot-template.mjs +61 -0
  24. package/content/skills/domain-modeling/ADR-FORMAT.md +47 -0
  25. package/content/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
  26. package/content/skills/domain-modeling/SKILL.md +80 -0
  27. package/content/skills/figma-export/SKILL.md +191 -0
  28. package/content/skills/figma-export/dump-figma-layers.py +83 -0
  29. package/content/skills/figma-export/dump-figma-svg.py +235 -0
  30. package/content/skills/figma-export/measure-template.mjs +87 -0
  31. package/content/skills/git-commit/SKILL.md +6 -7
  32. package/content/skills/git-flow/SKILL.md +87 -0
  33. package/content/skills/grill-with-docs/SKILL.md +13 -0
  34. package/content/skills/grilling/SKILL.md +34 -0
  35. package/content/skills/handoff/SKILL.md +74 -4
  36. package/content/skills/query/SKILL.md +165 -64
  37. package/content/skills/rxjs-signals/SKILL.md +31 -4
  38. package/content/skills/sdk-docs/SKILL.md +50 -14
  39. package/content/skills/sdk-local-build/SKILL.md +30 -10
  40. package/content/skills/sdk-local-build/sdk-local-baseline.mjs +64 -0
  41. package/content/skills/sdk-source/SKILL.md +49 -21
  42. package/content/skills/sdk-update/SKILL.md +96 -0
  43. package/content/skills/story-styling/SKILL.md +10 -8
  44. package/content/skills/styleguide/SKILL.md +12 -7
  45. package/content/skills/styleguide/assets.md +54 -0
  46. package/content/skills/styleguide/changesets.md +17 -0
  47. package/content/skills/styleguide/file-structure.md +73 -0
  48. package/content/skills/styleguide/lint-rule-lookup.md +51 -0
  49. package/content/skills/styleguide/storybook-structure.md +25 -0
  50. package/content/skills/theming/SKILL.md +41 -28
  51. package/content/skills/timetrack/SKILL.md +397 -0
  52. package/content/skills/verify-in-app/SKILL.md +107 -0
  53. package/migrations/app-styling-utilities.md +63 -0
  54. package/migrations/list-state-query-form.md +20 -0
  55. package/migrations/nx-layout.md +29 -0
  56. package/migrations/sdk-components-over-hand-built-ui.md +27 -0
  57. package/migrations/search-query-field.md +52 -0
  58. package/migrations.json +48 -0
  59. package/package.json +16 -2
  60. package/src/index.js +63 -28
  61. package/src/index.js.map +1 -1
  62. package/src/lib/cli-args.d.ts +9 -0
  63. package/src/lib/cli-args.js +48 -0
  64. package/src/lib/cli-args.js.map +1 -0
  65. package/src/lib/commitlint.d.ts +10 -0
  66. package/src/lib/commitlint.js +51 -0
  67. package/src/lib/commitlint.js.map +1 -0
  68. package/src/lib/config.d.ts +38 -4
  69. package/src/lib/config.js +84 -8
  70. package/src/lib/config.js.map +1 -1
  71. package/src/lib/filter.d.ts +2 -0
  72. package/src/lib/filter.js +4 -4
  73. package/src/lib/filter.js.map +1 -1
  74. package/src/lib/frontmatter.d.ts +2 -0
  75. package/src/lib/frontmatter.js +33 -5
  76. package/src/lib/frontmatter.js.map +1 -1
  77. package/src/lib/git-flow/build.d.ts +35 -0
  78. package/src/lib/git-flow/build.js +24 -0
  79. package/src/lib/git-flow/build.js.map +1 -0
  80. package/src/lib/git-flow/config.d.ts +59 -0
  81. package/src/lib/git-flow/config.js +50 -0
  82. package/src/lib/git-flow/config.js.map +1 -0
  83. package/src/lib/git-flow/index.d.ts +6 -0
  84. package/src/lib/git-flow/index.js +10 -0
  85. package/src/lib/git-flow/index.js.map +1 -0
  86. package/src/lib/git-flow/parse.d.ts +49 -0
  87. package/src/lib/git-flow/parse.js +300 -0
  88. package/src/lib/git-flow/parse.js.map +1 -0
  89. package/src/lib/git-flow/rename.d.ts +24 -0
  90. package/src/lib/git-flow/rename.js +70 -0
  91. package/src/lib/git-flow/rename.js.map +1 -0
  92. package/src/lib/git-flow/start.d.ts +49 -0
  93. package/src/lib/git-flow/start.js +57 -0
  94. package/src/lib/git-flow/start.js.map +1 -0
  95. package/src/lib/git-flow/validate.d.ts +34 -0
  96. package/src/lib/git-flow/validate.js +72 -0
  97. package/src/lib/git-flow/validate.js.map +1 -0
  98. package/src/lib/git-flow-command.d.ts +4 -0
  99. package/src/lib/git-flow-command.js +157 -0
  100. package/src/lib/git-flow-command.js.map +1 -0
  101. package/src/lib/git-flow-repair.d.ts +17 -0
  102. package/src/lib/git-flow-repair.js +157 -0
  103. package/src/lib/git-flow-repair.js.map +1 -0
  104. package/src/lib/git-flow-start.d.ts +20 -0
  105. package/src/lib/git-flow-start.js +132 -0
  106. package/src/lib/git-flow-start.js.map +1 -0
  107. package/src/lib/git.d.ts +64 -0
  108. package/src/lib/git.js +131 -0
  109. package/src/lib/git.js.map +1 -0
  110. package/src/lib/gitlab.d.ts +35 -0
  111. package/src/lib/gitlab.js +108 -0
  112. package/src/lib/gitlab.js.map +1 -0
  113. package/src/lib/index.d.ts +2 -0
  114. package/src/lib/index.js +2 -0
  115. package/src/lib/index.js.map +1 -1
  116. package/src/lib/migrate.js +1 -1
  117. package/src/lib/migrate.js.map +1 -1
  118. package/src/lib/output-style-command.d.ts +3 -0
  119. package/src/lib/output-style-command.js +69 -0
  120. package/src/lib/output-style-command.js.map +1 -0
  121. package/src/lib/output-style.d.ts +38 -0
  122. package/src/lib/output-style.js +134 -0
  123. package/src/lib/output-style.js.map +1 -0
  124. package/src/lib/owned-paths.js +27 -3
  125. package/src/lib/owned-paths.js.map +1 -1
  126. package/src/lib/package-runner.d.ts +3 -0
  127. package/src/lib/package-runner.js +69 -0
  128. package/src/lib/package-runner.js.map +1 -0
  129. package/src/lib/plain-text.d.ts +9 -0
  130. package/src/lib/plain-text.js +22 -0
  131. package/src/lib/plain-text.js.map +1 -0
  132. package/src/lib/plan.d.ts +6 -6
  133. package/src/lib/plan.js +147 -40
  134. package/src/lib/plan.js.map +1 -1
  135. package/src/lib/prompt.d.ts +8 -0
  136. package/src/lib/prompt.js +27 -0
  137. package/src/lib/prompt.js.map +1 -0
  138. package/src/lib/render.d.ts +24 -2
  139. package/src/lib/render.js +39 -10
  140. package/src/lib/render.js.map +1 -1
  141. package/src/lib/sync.d.ts +0 -1
  142. package/src/lib/sync.js +23 -15
  143. package/src/lib/sync.js.map +1 -1
  144. package/src/lib/targets/agents-skills.js +1 -1
  145. package/src/lib/targets/agents-skills.js.map +1 -1
  146. package/src/lib/targets/claude-hooks.d.ts +1 -23
  147. package/src/lib/targets/claude-hooks.js +17 -85
  148. package/src/lib/targets/claude-hooks.js.map +1 -1
  149. package/src/lib/targets/claude.js +1 -2
  150. package/src/lib/targets/claude.js.map +1 -1
  151. package/src/lib/targets/codex-hooks.d.ts +12 -0
  152. package/src/lib/targets/codex-hooks.js +34 -0
  153. package/src/lib/targets/codex-hooks.js.map +1 -0
  154. package/src/lib/targets/codex.js +4 -4
  155. package/src/lib/targets/codex.js.map +1 -1
  156. package/src/lib/targets/copilot.js +3 -3
  157. package/src/lib/targets/copilot.js.map +1 -1
  158. package/src/lib/targets/cursor.js +2 -2
  159. package/src/lib/targets/cursor.js.map +1 -1
  160. package/src/lib/targets/git-hooks.d.ts +24 -0
  161. package/src/lib/targets/git-hooks.js +71 -0
  162. package/src/lib/targets/git-hooks.js.map +1 -0
  163. package/src/lib/targets/hooks-shared.d.ts +58 -0
  164. package/src/lib/targets/hooks-shared.js +128 -0
  165. package/src/lib/targets/hooks-shared.js.map +1 -0
  166. package/src/lib/targets/shared.d.ts +1 -9
  167. package/src/lib/targets/shared.js +4 -8
  168. package/src/lib/targets/shared.js.map +1 -1
  169. package/src/lib/timetrack-command.d.ts +11 -0
  170. package/src/lib/timetrack-command.js +1055 -0
  171. package/src/lib/timetrack-command.js.map +1 -0
  172. package/src/lib/timetrack-snapshot.d.ts +55 -0
  173. package/src/lib/timetrack-snapshot.js +120 -0
  174. package/src/lib/timetrack-snapshot.js.map +1 -0
  175. package/src/lib/timetrack.d.ts +561 -0
  176. package/src/lib/timetrack.js +271 -0
  177. package/src/lib/timetrack.js.map +1 -0
  178. package/content/skills/styleguide/STYLEGUIDE.md +0 -520
@@ -0,0 +1,205 @@
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. A variant wins or loses only against the other variants of its call.
16
+
17
+ ## The rules
18
+
19
+ 1. **One open call at a time.** Take one question. Draw its variants. Stop.
20
+ 2. **Put the variants in the call, then name your pick.** Two to four variants, 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 variants, 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 a variant 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 variants 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 variant 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 variants and the round prose
58
+ fixture.ts the data every variant shares
59
+ variant-a.ts one default-exported drawing per variant
60
+ variant-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:check`. There, the user reads the
71
+ calls in Ethlete Studio, started with `yarn studio`, and Studio serves the page itself. Do not
72
+ start `yarn design` next to it.
73
+
74
+ Ethlete Studio carries its own copy of the tool, so it draws a checkout that installs nothing.
75
+ The machine still needs Node, and the render stage still needs `playwright` where the copy in
76
+ use can reach it.
77
+
78
+ `call.ts` calls `defineCall` from `@design-explore`. Each variant carries a `key`, a `name`,
79
+ a `claim`, a `cost`, an optional `verdict` of `chosen` or `rejected`, and a `load` that
80
+ imports its own module. The host greys a rejected variant and shows it on hover.
81
+
82
+ In a call that runs past about a dozen variants, every variant names the pass that drew it with
83
+ `round: 'r3'`. **That tag is the only thing that makes a round exist.** The host reads the
84
+ variants, draws one band per round in the order the variants introduce them, and lists those
85
+ same bands in the sidebar, so a new variant can never leave the menu stale. A round whose
86
+ variants all carry a verdict is **settled**: the page folds it down to its winner plus one row
87
+ per rejected variant, and a link opens it again.
88
+
89
+ A call whose rounds have all ruled is **resolved**. Its winners usually form a chain, each one
90
+ the last plus a change, so the page stops drawing them: it leads with a **Result** band holding
91
+ the final winner, and prints the chain as a row of keys above it. Every round below folds to
92
+ rows. A key in the chain opens the round that drew it.
93
+
94
+ `rounds` is prose only. Each entry gives a `key`, a `title` and a `note` saying what came out
95
+ of that pass, which is how the intro stays short and each pass reads as a reply to the one
96
+ before. An untagged round still draws and still gets a menu row - it says the bare key until
97
+ somebody writes the entry. A `rounds` entry that no variant names is stale prose, and the host
98
+ and the check both report it. Write the note in the same pass that sets the verdicts, never
99
+ before the user rules.
100
+
101
+ A call with one variant and no `claim` is a **view**: one reference picture, drawn full
102
+ width with no verdict tag. Use it for a picture that answers no question.
103
+
104
+ Two views, and a way to compare:
105
+
106
+ - **rounds** is the default. Round headings, the result band, and the folding above.
107
+ - **all N** is a contact sheet: every variant in the call at about a third size, in one grid, so
108
+ a call of two dozen fits on a screen or two. Use it to find the variants worth a close look.
109
+ - Clicking a variant's name anywhere - a heading, a folded row, a thumbnail - puts it in the
110
+ **compare overlay** at the top of the page. Two drawings that differ by a few pixels can only
111
+ be told apart in one place, so the overlay stacks every pick in one box at full size and the
112
+ reader switches between them: click the box or press space to blink, and with two picks the
113
+ arrow keys wipe a seam across. Nothing is scaled and nothing moves. The picks live in the URL
114
+ under `pick`, so a comparison is a link you can send.
115
+
116
+ Three constraints the tool puts on a variant file:
117
+
118
+ - **Never import a package barrel.** The libraries resolve to source, so one barrel makes
119
+ the browser request every module in the library, and the requests fail with
120
+ `ERR_INSUFFICIENT_RESOURCES`. Import the one file you need.
121
+ - **The fixture is shared, and a variant may not change it.** Variants drawn at three
122
+ geometries cannot be compared.
123
+ - **One file per variant**, so several agents can draw at once, and a broken variant breaks
124
+ its own frame only.
125
+
126
+ **A repository with a sketch Storybook** draws every variant in the **same** story, side by
127
+ side, under the real geometry the thing ships in. The default Storybook is
128
+ {%storybookUrl%}; an app with its own sketch Storybook names its port in the plan file.
129
+
130
+ Either way:
131
+
132
+ - Sketches take inputs only and stay out of the application's build.
133
+ - Prototype, never refactor. Do not touch the shipped component until the treatment is
134
+ settled.
135
+ - Keep the rejected variants drawn, marked as rejected.
136
+
137
+ ## Check before you look
138
+
139
+ A broken build draws an overlay, so a screenshot of it looks like a design. **One check
140
+ covers that, and it is the whole gate.** Name the files you changed, never a whole project,
141
+ which is far slower and can fight the user's own editor.
142
+
143
+ With `et design`, run the checker from the checkout it draws:
144
+
145
+ ```bash
146
+ et design check --lint <changed files> --tsconfig
147
+ ```
148
+
149
+ `--tsconfig` with no path types every call of the checkout. `--checkout <path>` names another
150
+ one.
151
+
152
+ With a Storybook, copy {%resource:check-story.mjs%} to the **repository root** first:
153
+
154
+ ```bash
155
+ node check-story.mjs --lint <changed files> --tsconfig apps/<app>/.storybook/tsconfig.json
156
+ ```
157
+
158
+ Both take about four seconds and need no dev server. Then stop and hand the slug to the
159
+ user. Do not also open the page in a browser: it costs a browser launch, and both tools
160
+ transpile without type checking, so the render reports `ok` on a file that does not
161
+ compile. Open it only when the check passes and the drawing still does not appear:
162
+
163
+ ```bash
164
+ et design check --call <slug> # every variant of the call
165
+ et design check --call <slug> --variant b # one of them
166
+ node check-story.mjs --tsconfig <path> --story <story-id>
167
+ ```
168
+
169
+ ## Delegating a call
170
+
171
+ A call has one job per variant, so it fans out. Every brief names `model: opus`.
172
+
173
+ 1. **One agent per variant.** Give it the call folder, the variant key, the fixture it must
174
+ not change, and the one claim its drawing has to support. Tell it to write its own
175
+ variant file and nothing else, so two agents never touch one file.
176
+ 2. **One check-and-fix agent, after the drawing agents return.** Give it the changed files
177
+ and the call slug. It runs the check above, fixes what it reports, and repeats until the
178
+ check says `ok`. It may not change what a variant draws, only what stops it rendering.
179
+ 3. **One write-up agent, once the user settles the call.** Give it the verdict in the
180
+ user's own words and the plan file. It records what won, what lost and why, it sets each
181
+ variant's `verdict` in `call.ts`, and it writes that round's `note`. It runs while you open
182
+ the next call.
183
+
184
+ ## Screenshots
185
+
186
+ **Off by default.** The user has the page open and sends you a picture when something looks
187
+ wrong. Take one only when the code cannot tell you whether two variants really differ, and
188
+ never to put in front of the user. Copy {%resource:shoot-template.mjs%} to the repository
189
+ root.
190
+
191
+ ```bash
192
+ node shoot.mjs <story-id> 1100 760 out.png
193
+ ```
194
+
195
+ - `waitUntil: 'networkidle'` never settles on an iframe that holds a live dev server
196
+ connection. Use `'domcontentloaded'`.
197
+ - `:hover`, `:focus-visible` and `:active` need a CDP session and `CSS.forcePseudoState`.
198
+ Several hold open at once, so one image shows them all. The template does this.
199
+
200
+ ## Writing it down
201
+
202
+ Every settled call goes in the exploration's plan file: what won, what lost, and why. Keep
203
+ an **Open** list for the calls not yet made. A rejected variant written down stops the next
204
+ session from drawing it again. After a clear rejection, use that record to frame and draw the
205
+ 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).