@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,397 @@
1
+ ---
2
+ name: timetrack
3
+ description: How to reach Jira from any repo through the running Timetrack app - look up an issue, search for one, ask which project a repo logs into, file a ticket, add a worklog row, read the user's own Tempo worklogs, read their Google Calendar, read the evidence a day holds, or list the work that still waits for a ticket. Read whenever a task needs Jira data, a Jira write, or the day's own events, and never put a Jira token in a repo.
4
+ kind: skill
5
+ scope: both
6
+ ---
7
+
8
+ # Reaching Jira through Timetrack
9
+
10
+ **No repository holds a Jira token.** The Timetrack desktop app holds one set of credentials
11
+ in this machine's keychain, and every repository asks it:
12
+
13
+ ```bash
14
+ {%packageRunner%} ethlete-agents timetrack status # is the app reachable, and what does it hold?
15
+ {%packageRunner%} ethlete-agents timetrack issue FIP-2177 # one issue: summary, type, parent, subject
16
+ {%packageRunner%} ethlete-agents timetrack search "password" # open issues of the picked projects
17
+ {%packageRunner%} ethlete-agents timetrack project # which project does this repo log into?
18
+ {%packageRunner%} ethlete-agents timetrack instance # the instance's own levels and custom fields
19
+ {%packageRunner%} ethlete-agents timetrack standins # work the user named that Jira does not hold yet
20
+ ```
21
+
22
+ Add `--json` to any of them when you need to read a field rather than a line.
23
+
24
+ **Every write waits for the user's approval in the app.** `create`, `log`, `edit`, `standins
25
+ --remove | --rename | --merge | --resolve | --split --force`, `sync --write`, `worklog --delete` and `resync` answer at
26
+ once with an approval id and write nothing yet. The user approves or rejects the request in
27
+ Timetrack; `approval <id>` reads the outcome:
28
+
29
+ ```bash
30
+ {%packageRunner%} ethlete-agents timetrack approval <id> # queued, approved (with the result), rejected or expired
31
+ {%packageRunner%} ethlete-agents timetrack approvals # every write that waits now: id, asker, op, asked at, what it writes
32
+ {%packageRunner%} ethlete-agents timetrack approval <id> --reject # reject one that waits, as the app's Reject button does
33
+ ```
34
+
35
+ A reject answers at once and writes nothing to Jira or Tempo; an item that no longer waits is
36
+ refused and stays as it is. Reject only what the user asked you to, or a write you queued by
37
+ mistake. **There is no approve command and never will be: only the user approves, in the app.**
38
+
39
+ `ask` does what the app's "Ask auto mode again" does, on any day, without moving the day on
40
+ screen. It answers at once with what it queued; the model runs on the user's machine, and its new
41
+ answer replaces the stored one. It writes nothing to Jira or Tempo itself: a ticket or a name the
42
+ answer proposes waits for the user's approval as auto mode's own does.
43
+
44
+ ```bash
45
+ {%packageRunner%} ethlete-agents timetrack ask --stand-in <id> [--day YYYY-MM-DD] # an open stand-in, ids from `standins`
46
+ {%packageRunner%} ethlete-agents timetrack ask --context <context-id> [--day YYYY-MM-DD] # an unnamed band, ids from `rows`
47
+ ```
48
+
49
+ A subject the app does not offer the press on is refused: an unknown or resolved stand-in, one
50
+ the day holds no time of, a context a rule names or the user named by hand.
51
+
52
+ Tell the user a request waits in Timetrack, then read `approval` once they say they decided -
53
+ do not poll in a loop. A request that is still waiting at the end of the day it was asked on
54
+ expires; ask again rather than assuming it landed. Set `TIMETRACK_CLIENT` to name yourself in the
55
+ queue (Claude Code is named without it).
56
+
57
+ **Never ask the user for a Jira token, and never write one into a file.** If a command reports
58
+ that the app is not running, say so and ask the user to start it. That is the whole fix -
59
+ there is no per-repo fallback, by design: a secret copied into every checkout is a secret
60
+ nobody can rotate.
61
+
62
+ ## What each command is for
63
+
64
+ | Command | Use it when |
65
+ | ------------------------------------------ | --------------------------------------------------------------------------- |
66
+ | `status` | Before anything else, when a Jira command failed and you need the cause |
67
+ | `instance` | A setup step needs the instance's levels or its branch-subject field |
68
+ | `issue <KEY>` | The user names a key and you need its summary, type or parent |
69
+ | `search [text]` | The user describes work but names no key |
70
+ | `project [path]` | You need the project a repository files into |
71
+ | `create --summary "…"` | The work has no ticket and the user asked for one |
72
+ | `log --issue <KEY> --minutes <n>` | The user asks to record time that nothing observed |
73
+ | `day [YYYY-MM-DD]` | You need the evidence a day holds, not a screenshot of it |
74
+ | `rows [YYYY-MM-DD]` | You need the rows the day drew, and the ids an edit names them by |
75
+ | `edit <row-id> …` | The user asks you to correct one row of a day |
76
+ | `rules` | You need to know why a band was named, or why it was not |
77
+ | `standins` | You need to know which work still waits for a ticket, and for how long |
78
+ | `standins --remove <id>` | A placeholder is wrong or too wide, and the user asked you to delete it |
79
+ | `standins --rename <id> --name <text>` | The name a placeholder carries is wrong, and the user asked you to fix it |
80
+ | `standins --merge <id> --into <id>` | Two placeholders are one piece of work, and the user asked you to join them |
81
+ | `standins --resolve <id> --issue <KEY>` | The user named the issue a placeholder is, or the one a resolved one is now |
82
+ | `naming [YYYY-MM-DD]` | A checkout was never offered a name and you need the step that stopped |
83
+ | `worklogs [from] [to]` | You need what the user already booked in Tempo over a span of days |
84
+ | `calendar [from] [to]` | You need the user's meetings and calendar entries over a span of days |
85
+ | `sync <YYYY-MM-DD>` | The user asks what a Tempo sync of a day would write, or asks to sync it |
86
+ | `worklog --delete <id> --day <YYYY-MM-DD>` | The user asked you to delete one of their own Tempo worklogs |
87
+ | `approval <id>` | A write you queued: whether the user approved it, and what it answered |
88
+ | `approvals` | You need what waits in the queue, from every caller |
89
+ | `approval <id> --reject` | The user asked you to drop a waiting write, or you queued one by mistake |
90
+ | `ask --stand-in <id>` / `--context <id>` | The user asks auto mode to look at a stand-in or an unnamed band again |
91
+
92
+ `git-flow start` uses the same channel, so a branch is named from the real issue rather than
93
+ from a key you typed. Follow the repository's branch workflow when creating a branch.
94
+
95
+ ## Reading a day
96
+
97
+ The app's store is encrypted, so no shell reads a day off disk. `day` is the only way in:
98
+
99
+ ```bash
100
+ {%packageRunner%} ethlete-agents timetrack day # today: how many events, and of which kind
101
+ {%packageRunner%} ethlete-agents timetrack day 2026-09-10 --out /tmp/day.json
102
+ ```
103
+
104
+ A real day holds thousands of events, so **never print them**. Write them to a file with
105
+ `--out`, then read that file from a test or a script. Without `--out` the command reports
106
+ only the counts, which is what tells you whether a day holds the source you are looking for.
107
+
108
+ `--out` writes a new file, readable by its owner alone. A destination that already exists is
109
+ refused until you pass `--overwrite`, and a symlink is never written through. The file holds the
110
+ day as it was observed - window titles, paths and messages - so delete it when you are done.
111
+
112
+ `transcript` prints what the app transcribed of the user's own microphone during the day's calls,
113
+ one 30-second chunk per line. It exists only where the user turned call transcription on, and the
114
+ app deletes a chunk after seven days. Read it only when the task needs what was said.
115
+
116
+ ```bash
117
+ npx ethlete-agents timetrack transcript [YYYY-MM-DD]
118
+ ```
119
+
120
+ ## Reading and correcting the rows a day drew
121
+
122
+ `day` answers what the collectors saw. `rows` answers what the app made of it - the bands on the
123
+ timeline, with the id every edit names a row by, the day's totals and its warnings.
124
+
125
+ ```bash
126
+ {%packageRunner%} ethlete-agents timetrack rows # today
127
+ {%packageRunner%} ethlete-agents timetrack rows 2026-09-10 --json
128
+ ```
129
+
130
+ `edit` changes one row. Pass exactly one change per call:
131
+
132
+ ```bash
133
+ {%packageRunner%} ethlete-agents timetrack edit 'ABC-1@2026-09-10T11:00:00.000Z' --day 2026-09-10 \
134
+ --from 2026-09-10T13:15:00Z --to 2026-09-10T17:00:00Z
135
+ {%packageRunner%} ethlete-agents timetrack edit '<row-id>' --issue ABC-2
136
+ {%packageRunner%} ethlete-agents timetrack edit '<row-id>' --state rejected
137
+ ```
138
+
139
+ `rows` moves the app's own review to that day, so the user sees what you read. An edit is queued
140
+ like every write; once approved, its result's `applied` says how many edits found their row. A
141
+ running day is re-cut on every collector tick, so read `rows` again rather than reusing an id from
142
+ minutes ago.
143
+
144
+ An edit may correct a row's times, its name, its note and whether it syncs. It may not split, merge
145
+ or delete one - restructuring a day is the user's own decision, made on the screen.
146
+
147
+ ## Why a band carries the name it does
148
+
149
+ `rules` reads the settings that name a day's work - the attribution rules, the project links,
150
+ the background projects and the applications the user has ruled in or out. It holds no host, no
151
+ account and no token, so it is safe to quote back to the user.
152
+
153
+ ```bash
154
+ {%packageRunner%} ethlete-agents timetrack rules # a summary of the rules
155
+ {%packageRunner%} ethlete-agents timetrack rules --json # the whole answer
156
+ ```
157
+
158
+ Read it before claiming a band should have been named something: a rule that donates its time
159
+ carries no issue key, and a project in `backgroundProjects` keeps only the minutes no other
160
+ band claims.
161
+
162
+ ## Work with no ticket yet
163
+
164
+ A **stand-in** is a name the user gave work that Jira does not hold yet. It takes bands across
165
+ days and across checkouts, and it books nothing. `standins` lists them:
166
+
167
+ ```bash
168
+ {%packageRunner%} ethlete-agents timetrack standins # the open ones, oldest first
169
+ {%packageRunner%} ethlete-agents timetrack standins --json # the whole answer, resolved ones included
170
+ ```
171
+
172
+ **Never open or resolve a stand-in.** The name is the user's own word for their work, and the
173
+ app is the only place they give it. If the user asks for a ticket, use `create` and tell them
174
+ to resolve the stand-in in Timetrack.
175
+
176
+ A placeholder is drawn per branch, and per directory where the branch names no piece of work.
177
+ A base branch, or no branch at all, says nothing about what was worked on, so the directory the
178
+ commits touched answers instead. That directory is the project the repository declares - the one
179
+ carrying the `project.json` or `package.json` above the changed files. A repository that declares
180
+ none lets its own commits pick the grain between them. A directory named with a leading dot is
181
+ the checkout's own tooling, so it never answers: a commit in `.changeset` or `.github` is
182
+ bookkeeping for another piece of work.
183
+
184
+ Where the directories cannot answer either, the branch the checkout was next cut onto does. A
185
+ branch is very often cut once the work on it has started, so the minutes before the cut sit on
186
+ the base branch although they are that branch's work.
187
+
188
+ One placeholder written before any of that was true covers a whole checkout, and it blocks a
189
+ per-branch placeholder for every branch of it. The list says so:
190
+
191
+ ```
192
+ Competition journey spec frontend in FIFAGG 3d old, 3 day(s) of work
193
+ covers all of /home/tom/dev/fifagg/specs, so no branch of it gets one of its own
194
+ ```
195
+
196
+ Two separate things cause that, and either alone is enough: the record names no branch, or the
197
+ rule naming it names no branch. The line above reports both, so take it at its word rather than
198
+ reading `--json` yourself.
199
+
200
+ ### Give one another name
201
+
202
+ The name is only wrong, and the work behind it is right:
203
+
204
+ ```bash
205
+ {%packageRunner%} ethlete-agents timetrack standins --rename <id> --name '20260921 competition navigation rework'
206
+ ```
207
+
208
+ The days it holds and the rules that name it stay, which is what a delete and a fresh record would
209
+ lose. Rename one only when the user asks. The name is their own word for their work, so a name you
210
+ find unclear is not a fault to correct.
211
+
212
+ ### Resolve one to its issue
213
+
214
+ The user told you which Jira issue a placeholder is:
215
+
216
+ ```bash
217
+ {%packageRunner%} ethlete-agents timetrack standins --resolve <id> --issue <KEY>
218
+ ```
219
+
220
+ It resolves the placeholder as the user's own pick in the app does, so every band on every day it
221
+ held takes the key. One that is resolved already moves to the new key, as the app's Undo and a new
222
+ pick would. It writes nothing to Tempo, so a day of it already booked keeps the old key there. The
223
+ app reads the issue from Jira before it queues anything, so an unknown key or placeholder answers an
224
+ error and nothing waits. A done issue is fine. The approval card shows the name, the old key and the
225
+ new one with its summary.
226
+
227
+ ### Re-cut one that named two pieces of work
228
+
229
+ A record covering a whole checkout is repaired rather than deleted, because a delete strands its
230
+ days. `--split` re-cuts it into one record per directory, and moves each day onto the right one:
231
+
232
+ ```bash
233
+ {%packageRunner%} ethlete-agents timetrack standins --split <id> # the plan, writes nothing
234
+ {%packageRunner%} ethlete-agents timetrack standins --split <id> --force # queue it for approval
235
+ ```
236
+
237
+ Without `--force` it prints every directory the commits name, with its commit count and its days,
238
+ then says which of them it would write. A directory under three commits is an edit made in
239
+ passing rather than a piece of work, so it is listed but not written. Pass
240
+ `--paths <dir>,<dir>` to pick the pieces yourself.
241
+
242
+ The commits are read across every branch, and only the ones the checkout's own `user.email`
243
+ authored. That is the history the collector that opened the record read, so work left on an
244
+ unmerged branch still counts and a colleague's commits never shape a piece. `--author <email>`
245
+ overrides the address.
246
+
247
+ A day the record covers that holds no commit of yours goes back to unnamed, and the split names
248
+ it. That is the honest answer: nothing says which directory it was. The day review is where it
249
+ gets named, and `--claim <dir>` gives every such day to one directory instead.
250
+
251
+ The record and its checkout-wide rule always go, even when a day is left unnamed. Both are read
252
+ as covering every branch of the checkout, so keeping either would block any later branch from
253
+ getting a record of its own — which is the fault the repair exists to cure.
254
+
255
+ The record must name the checkout it was opened for. One the user opened by hand names none, and
256
+ the split refuses it.
257
+
258
+ You may delete one, because that takes a name away rather than putting one on the day:
259
+
260
+ ```bash
261
+ {%packageRunner%} ethlete-agents timetrack standins --remove <id>
262
+ ```
263
+
264
+ The rule that named it goes with it, and what happens next is worth knowing before you ask:
265
+
266
+ - The record names a branch. The delete refuses that branch, and no second one opens for it.
267
+ - It names none. The delete refuses nothing, so the next pass opens one per branch instead.
268
+
269
+ **Deleting one strands every day but today.** Only the day the app next reviews gets a new
270
+ placeholder; every earlier day the record covered then reads as unnamed, with nothing left to
271
+ say what it was. The command refuses such a delete and lists the days, and `--force` makes it
272
+ anyway. Ask the user before you use `--force`, and tell them which days they will lose.
273
+
274
+ ## Why a checkout was never offered a name
275
+
276
+ The day screen offers a whole checkout one issue when the user's own record already answers it. The
277
+ card is either drawn or it is not, and every step that can stop it is invisible from the screen.
278
+ `naming` names the step:
279
+
280
+ ```bash
281
+ {%packageRunner%} ethlete-agents timetrack naming # today
282
+ {%packageRunner%} ethlete-agents timetrack naming 2026-09-14
283
+ ```
284
+
285
+ It reports whether a Tempo token is stored, how far the read of the worklog history got, and for each
286
+ checkout the day saw either the offer or the reason there is none - `already-named`,
287
+ `no-project-link`, `no-history`, `project-too-small`, `too-few-days` or `share-too-low`. A `history`
288
+ of `failed` or `no-token` explains every checkout at once, so read that line first.
289
+
290
+ ## What Tempo already holds
291
+
292
+ `worklogs` reads the user's own Tempo worklogs straight from Tempo, whoever wrote them. It is
293
+ read-only, and the app keeps the Tempo token:
294
+
295
+ ```bash
296
+ {%packageRunner%} ethlete-agents timetrack worklogs # the last 7 days
297
+ {%packageRunner%} ethlete-agents timetrack worklogs 2026-09-01 2026-09-24 # both days included
298
+ {%packageRunner%} ethlete-agents timetrack worklogs 2026-09-01 2026-09-24 --json
299
+ ```
300
+
301
+ Without `--json` it prints one line per booked day: the total, then minutes per issue. `--json`
302
+ answers each worklog with its day, `startTime` as `HH:MM`, `durationMs`, `issueKey` and
303
+ description. A span is at most 92 days. The descriptions are the user's own words, so quote them
304
+ only when the task needs them.
305
+
306
+ ## What the calendar holds
307
+
308
+ `calendar` reads the Google calendars the app watches, straight from Google and read-only. The app
309
+ keeps the Google token:
310
+
311
+ ```bash
312
+ {%packageRunner%} ethlete-agents timetrack calendar # the last 7 days
313
+ {%packageRunner%} ethlete-agents timetrack calendar 2026-09-01 2026-09-24 # both days included
314
+ {%packageRunner%} ethlete-agents timetrack calendar 2026-09-01 2026-09-24 --json
315
+ ```
316
+
317
+ Without `--json` it prints each day with its meeting hours, then one line per entry: start, minutes,
318
+ title, and the user's answer where it is not a yes. All-day and declined entries are listed but not
319
+ counted. `--json` answers each entry with `calendarId`, `day`, `startMs`, `endMs`, `allDay`, `title`,
320
+ `attendeeCount` and `response`. A span is at most 92 days. Titles come from whoever sent the
321
+ invitation, so quote them only when the task needs them.
322
+
323
+ ## Syncing a day to Tempo
324
+
325
+ `sync` plans a day exactly as the app's Sync page does, and writes nothing:
326
+
327
+ ```bash
328
+ {%packageRunner%} ethlete-agents timetrack sync 2026-09-07
329
+ {%packageRunner%} ethlete-agents timetrack sync 2026-09-07 --json
330
+ ```
331
+
332
+ It prints every create, update and delete with its issue, local time, minutes, description and
333
+ reason; the rows it holds back (Tempo refuses an empty description); how many are unchanged or still
334
+ awaiting review; keys Jira does not know; and the foreign worklogs it never touches. The first line
335
+ carries a short **plan hash** over the write set.
336
+
337
+ Writing is a Tempo write, so it follows one rule without exception: **show the user the rows, and
338
+ write only after they confirmed those rows in this conversation.** Then pass the hash the read
339
+ printed:
340
+
341
+ ```bash
342
+ {%packageRunner%} ethlete-agents timetrack sync 2026-09-07 --write --plan 1a2b3c4d
343
+ ```
344
+
345
+ The write is queued, and the user approves it in the app as well - "Approve all" never includes
346
+ it. On approval the app plans the day again and refuses, writing nothing, when the hash differs -
347
+ the day changed since the user saw it, so read it again and ask again. `--write` without `--plan`
348
+ is refused at once. Once approved, `approval <id>` prints each row as `written`, `blocked`,
349
+ `skipped` or `failed`, with the worklog id or the error, and exits non-zero while a row did not
350
+ land. Never re-run the write to retry: a plan read
351
+ straight after a write can miss what Tempo just took and log it twice. The Sync page shows the run
352
+ and retries from its own plan. A "written, but not recorded" line means Tempo holds worklogs the
353
+ app no longer owns; tell the user to delete them in Tempo before this day is written again.
354
+
355
+ Both reads and writes move the app's Day screen to that day, as `rows` and `edit` do.
356
+
357
+ ## Deleting a Tempo worklog
358
+
359
+ `worklog --delete` deletes one of the user's own Tempo worklogs. Both flags are required; read the
360
+ id and its day off `worklogs --json`:
361
+
362
+ ```bash
363
+ {%packageRunner%} ethlete-agents timetrack worklog --delete 98765 --day 2026-09-07
364
+ ```
365
+
366
+ It is a Tempo write, so the same rule holds: **name the worklog to the user, and delete only after
367
+ they confirmed that one in this conversation.** The app refuses, deleting nothing, an id that is not
368
+ among the account's own worklogs on that day. The delete is queued for the user's approval like
369
+ `sync --write`; once approved, `approval <id>` answers what went, and the app drops its own record
370
+ of the worklog, so the next `sync` of the day plans from what Tempo holds.
371
+
372
+ ## Writes
373
+
374
+ Four commands write to Jira or Tempo, so all need the user to have asked for them in this
375
+ conversation - `sync --write` and `worklog --delete` are described above. Each is queued for the
376
+ user's approval in the app:
377
+
378
+ ```bash
379
+ {%packageRunner%} ethlete-agents timetrack create --summary "Reset password mail is not sent" --project FIP
380
+ {%packageRunner%} ethlete-agents timetrack log --issue FIP-2177 --minutes 45 --description "pairing call"
381
+ ```
382
+
383
+ - **`create`** files the issue, once approved, with the instance's own ticket settings - its type, its parent
384
+ rule and its subject field all come from the app, so the ticket is shaped like every other.
385
+ `--project` is needed unless the app holds exactly one picked project.
386
+ - **`log`** adds a row to the day in Timetrack. It is **not** a Tempo entry: the user reviews
387
+ the day and syncs it, which is what keeps an agent's row from double-booking against the
388
+ hours the day already observed. `--at` places it - an ISO instant, or a local `YYYY-MM-DD` or `HH:MM` (today); without one it starts now.
389
+
390
+ ## What the app decides, not you
391
+
392
+ - **The projects.** The user picked them in Timetrack. `search` is scoped to them, and
393
+ `--project <KEY>` is how you look outside.
394
+ - **The branch subject.** `issue` returns `subject` when the instance's subject field is set on
395
+ that issue. Prefer it over the summary; the summary is a paraphrase.
396
+ - **The instance.** Its host, its issue types and its hierarchy are settings in the app. Do not
397
+ hardcode any of them in this repo.
@@ -0,0 +1,107 @@
1
+ ---
2
+ name: verify-in-app
3
+ description: Verify a UI change in the running app by driving it headlessly with Playwright - screenshots, computed styles, pointer and cursor state - and prove a fix with a test that fails without it. Use whenever you change a view, a component or its styles and need to confirm what renders, not just that it compiles.
4
+ kind: skill
5
+ scope: consumer
6
+ ---
7
+
8
+ # Verify a UI change in the running app
9
+
10
+ A build that passes says nothing about what the user sees. Serve the app, drive the real page in
11
+ a headless browser, and assert on the rendered DOM and computed styles.
12
+
13
+ ## 1. Serve the app
14
+
15
+ It may already be running. Check before starting a second instance - the dev server prompts for
16
+ another port and hangs:
17
+
18
+ ```bash
19
+ curl -s -o /dev/null -w "%{http_code}" http://localhost:4200/
20
+ ```
21
+
22
+ - `200` → it is up.
23
+ - anything else → start it in the background and poll the curl above until it answers (a cold
24
+ build can take a minute):
25
+
26
+ ```bash
27
+ npx nx serve <app>
28
+ ```
29
+
30
+ The port and any proxy config live in the app's `project.json` `serve` target. A view behind a
31
+ login needs a session: log in once through the form in the script, or seed the token storage the
32
+ app reads before `page.goto`.
33
+
34
+ ## 2. Drive it with Playwright
35
+
36
+ Use the `playwright` package from the repo's `node_modules`. Write the script in a scratch
37
+ directory outside the repo and run it with `node`:
38
+
39
+ ```js
40
+ import { createRequire } from 'node:module';
41
+
42
+ const require = createRequire(`${process.cwd()}/`);
43
+ const { chromium } = require('playwright');
44
+
45
+ const browser = await chromium.launch();
46
+ const page = await browser.newPage({ viewport: { width: 1280, height: 900 } });
47
+
48
+ await page.goto('http://localhost:4200/players?search=mul', { waitUntil: 'domcontentloaded' });
49
+ await page.waitForSelector('.players-table', { state: 'attached' });
50
+
51
+ const state = await page.$eval('.players-table', (el) => {
52
+ const cs = getComputedStyle(el);
53
+ return { display: cs.display, color: cs.color, rows: el.querySelectorAll('tr').length };
54
+ });
55
+ console.log(JSON.stringify(state));
56
+
57
+ await page.screenshot({ path: '/tmp/players.png', fullPage: true });
58
+ await browser.close();
59
+ ```
60
+
61
+ Then read the screenshot back and look at it.
62
+
63
+ - **`playwright` is CommonJS** - resolve it with `createRequire`; a named ESM import fails.
64
+ - **`waitUntil: 'domcontentloaded'`, never `networkidle`** - the dev server's live-reload socket
65
+ and any polling query keep the network busy forever.
66
+ - **`waitForSelector` waits for visible by default.** Pass `{ state: 'attached' }` for anything
67
+ correctly hidden (`opacity: 0`, collapsed, `inert`).
68
+ - **Overlays render at the end of `<body>`,** not inside the view that opened them. Query them from
69
+ `page`, not from the view's element.
70
+ - After a click that starts a transition, wait out its duration before reading final styles.
71
+
72
+ ## 3. Pointer and cursor state
73
+
74
+ `getComputedStyle(el).cursor` reports what the stylesheet says, not what the pointer gets - an
75
+ overlay, a backdrop or `pointer-events: none` in between changes the answer. Ask the page which
76
+ element is under the point instead:
77
+
78
+ ```js
79
+ const hit = await page.evaluate(
80
+ ([x, y]) => {
81
+ const el = document.elementFromPoint(x, y);
82
+ return el && { tag: el.tagName, class: el.className, cursor: getComputedStyle(el).cursor };
83
+ },
84
+ [640, 300],
85
+ );
86
+ ```
87
+
88
+ `elementFromPoint` returns `null` outside the viewport, so use a viewport tall enough for the
89
+ point, or scroll the target into view first. Hover with `page.mouse.move(x, y)` before reading
90
+ `:hover` styles.
91
+
92
+ ## 4. Prove a fix with a test that bites
93
+
94
+ A screenshot shows the fix once; a test keeps it. For a bug fix, write the spec (a unit spec for
95
+ logic, or a Playwright e2e spec where the app has one), then:
96
+
97
+ 1. run it against the fix - it passes;
98
+ 2. revert the fix (stash it, or comment the one line out) and run it again - it **must fail**;
99
+ 3. restore the fix.
100
+
101
+ A test that passes both ways asserts on the wrong thing. Fix the test before you call the change
102
+ done.
103
+
104
+ ## 5. Report
105
+
106
+ Say what you drove (URL, clicks, viewport) and what you observed, with the numbers you read. Keep
107
+ the script and screenshots in the scratch directory, not the repo.
@@ -0,0 +1,63 @@
1
+ # Move app CSS out of `@layer components`
2
+
3
+ The `app-styling` rule now says how app components are styled: Tailwind utilities in the
4
+ template, CSS only for what a utility cannot express, and never an app rule in
5
+ `@layer components`. SDK styles are injected into that layer at runtime, after the app's
6
+ stylesheet, so an app rule there ties on layer and loses on source order. That is a bug today,
7
+ and this task fixes it. The rest of the rule applies when a component is next edited.
8
+
9
+ ## Before you start
10
+
11
+ If this repo has no `app-styling` rule yet (in `AGENTS.md`, or under `.claude/rules/ethlete/`),
12
+ run `ethlete-agents sync` with this repo's package manager (`yarn`, `pnpm exec` or `npx`), then
13
+ read the rule. If the repo's own styling rules (a styleguide, `CONTRIBUTING.md`, the parts of
14
+ `AGENTS.md` outside the generated block) forbid a change below, follow them and report the
15
+ conflict.
16
+
17
+ ## What to change
18
+
19
+ 1. Find the app stylesheets with a `@layer components` block, global stylesheets included:
20
+
21
+ ```bash
22
+ grep -rlE '@layer components' apps libs --include='*.css' --include='*.scss' --include='*.sass' --include='*.less'
23
+ ```
24
+
25
+ 2. In each, move the rules out of the block into `@layer utilities`, and delete the empty
26
+ block. In `@layer utilities` a rule still beats the SDK, and does not beat every utility the
27
+ way unlayered CSS would.
28
+ 3. The move lets these rules win over SDK styles they used to lose to. Check the views that use
29
+ them. Where a view now looks wrong because the old rule was never meant to apply, delete the
30
+ rule instead.
31
+
32
+ ## Leave these alone
33
+
34
+ - A library that ships its own CSS to other repos. The rule is about app components.
35
+ - Generated theme files, such as the surface and colour themes `@ethlete/core` writes.
36
+ - `--spacing` and the other rem scales in `@theme`. If the app has not set `--spacing: 0.4rem`,
37
+ setting it now rescales every utility already in use: report it and leave it.
38
+ - `html { font-size: 62.5%; }`: if it is missing, report it and leave it. Adding it rescales
39
+ every `rem` the app uses.
40
+
41
+ ## On touch, not in this task
42
+
43
+ Rewriting BEM classes and component stylesheets to utilities, replacing hardcoded colours with
44
+ theme tokens, and adding `ViewEncapsulation.None` to a component that relies on emulated CSS
45
+ (its CSS would leak) happen when someone edits that component. Count what is left for the
46
+ final message:
47
+
48
+ ```bash
49
+ grep -rlE 'styleUrls?:' apps libs --include='*.ts' | wc -l
50
+ grep -rlE 'class="[^"]*\b[a-z0-9-]+__[a-z0-9-]+' apps libs --include='*.html' --include='*.ts' | wc -l
51
+ grep -rlE '@Component\(' apps libs --include='*.ts' | xargs grep -LE 'ViewEncapsulation\.None' | wc -l
52
+ ```
53
+
54
+ ## Done when
55
+
56
+ - The grep in step 1 finds no app stylesheet, apart from the ones under "Leave these alone".
57
+ - The type check, lint and build pass for every project you changed.
58
+ - Each view that uses a moved rule is checked in the browser. A view you cannot open (the login
59
+ fails, no API) is listed as unchecked in your final message; that does not block the task.
60
+ - Your final message gives the three counts above, and whether `--spacing` and the 62.5% root
61
+ are set.
62
+
63
+ Then delete this task file.
@@ -0,0 +1,20 @@
1
+ # Lists with URL state use `defineQueryForm`
2
+
3
+ The `ethlete-query` skill now says every filtered, searched, sorted or paged list uses
4
+ `defineQueryForm`, not `injectQueryParams()`, `router.navigate` and a draft signal wired by hand.
5
+ Moving a list is a rewrite of its component, so it happens when someone next edits that list. The
6
+ skill says how to keep the old URL working when you do.
7
+
8
+ No code changes in this task. To see how many components still wire their params by hand, count
9
+ the files that both read and write query params:
10
+
11
+ ```bash
12
+ grep -rlE 'injectQueryParams?\(|queryParamMap|snapshot\.queryParams|\.queryParams\.(pipe|subscribe)\(' apps libs --include='*.ts' |
13
+ xargs grep -lE '\bqueryParams(Handling)?\b' | wc -l
14
+ ```
15
+
16
+ ## Done when
17
+
18
+ - The count is in your final message, or in the report you give the developer.
19
+
20
+ Then delete this task file.
@@ -0,0 +1,29 @@
1
+ # Compare the workspace with the Nx layout rule
2
+
3
+ `@ethlete/agent-rules` now ships an `nx-layout` rule for Nx workspaces: thin apps, feature code in
4
+ `libs/domain/<app>/<feature>`, shared `queries`, `types`, `uikit`, `theme` and `env` libs, aliases that
5
+ mirror the path, and one `scope:*` tag per project. New code follows it.
6
+
7
+ This is a recommendation, not a rewrite. Moving projects changes import paths, project names, tags
8
+ and CI caches across the whole repo, and the rule itself says not to restructure an existing
9
+ workspace unless the team asks for it.
10
+
11
+ ## What to decide
12
+
13
+ 1. Read the rule (in `AGENTS.md`, or under `.claude/rules/ethlete/`). If it is missing, run
14
+ `ethlete-agents sync` with this repo's package manager (`yarn`, `pnpm exec` or `npx`).
15
+ 2. List where the workspace deviates: feature code inside `apps/`, queries or themes inside an app,
16
+ `feature/ui/data-access/util` folders, aliases that do not mirror the path, missing `scope:*` tags.
17
+ 3. Decide per deviation: move it now, move it when that area is next touched, or keep it.
18
+ 4. Keep what you decided where an agent reads it, so it stops flagging it: in `AGENTS.md` above the
19
+ `<!-- ethlete:agent-rules:start -->` line or below `<!-- ethlete:agent-rules:end -->`, or in the
20
+ repo's `CONTEXT.md` or an ADR. Never between the two markers: `ethlete-agents sync` rewrites that
21
+ block and drops the edit. If `AGENTS.md` holds nothing but the block, add the decisions above it.
22
+
23
+ An agent may prepare the list in step 2. It must not move a project without that decision.
24
+
25
+ ## Done when
26
+
27
+ - Each deviation from step 2 has a decision, recorded as step 4 says.
28
+
29
+ Then delete this task file.
@@ -0,0 +1,27 @@
1
+ # Use the SDK component where one exists
2
+
3
+ The `ethlete-sdk-docs` skill now lists every component domain, with a table that maps common needs
4
+ to them ("Check this list before you build any UI by hand"). Apps built their own bar charts,
5
+ avatars and progress bars because the old list did not name them. Replacing one restyles its view,
6
+ so it happens when someone next edits that view, not in this task.
7
+
8
+ When you do replace one, check every feature of the hand-built one against the SDK component's
9
+ inputs, slots and `--et-*` tokens first, and keep the hand-built one where a feature has no
10
+ counterpart. Known gaps:
11
+
12
+ - `et-bar-chart` renders every category label and only truncates it, and has no slot for a title,
13
+ a note or an empty state.
14
+ - `et-avatar` derives initials from `name` only, and takes its colours from a colour theme only.
15
+
16
+ No code changes in this task. To count the candidates, skipping hits that already use an `et-`
17
+ element:
18
+
19
+ ```bash
20
+ grep -rlE 'bar-?chart|bar-?graph|avatar|initials|progress-?bar' apps libs --include='*.html' --include='*.ts' | xargs grep -LE '<et-(bar-chart|avatar|progress-bar)' | wc -l
21
+ ```
22
+
23
+ ## Done when
24
+
25
+ - The count is in your final message, or in the report you give the developer.
26
+
27
+ Then delete this task file.