@ethlete/agent-rules 0.1.0-next.17 → 0.1.0-next.19

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 (55) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +14 -2
  3. package/content/hooks/subagent-model-policy.py +5 -3
  4. package/content/rules/app-styling.md +6 -1
  5. package/content/rules/subagent-models.md +7 -7
  6. package/content/skills/design-exploration/SKILL.md +3 -1
  7. package/content/skills/query/SKILL.md +10 -2
  8. package/content/skills/sdk-update/SKILL.md +5 -3
  9. package/content/skills/timetrack/SKILL.md +97 -27
  10. package/migrations/app-styling-utilities.md +44 -95
  11. package/migrations/list-state-query-form.md +12 -95
  12. package/migrations/nx-layout.md +6 -0
  13. package/migrations/sdk-components-over-hand-built-ui.md +16 -58
  14. package/migrations/search-query-field.md +12 -5
  15. package/migrations.json +5 -5
  16. package/package.json +1 -1
  17. package/src/lib/frontmatter.js +32 -5
  18. package/src/lib/frontmatter.js.map +1 -1
  19. package/src/lib/git-flow/parse.js +38 -12
  20. package/src/lib/git-flow/parse.js.map +1 -1
  21. package/src/lib/git-flow-repair.js +12 -1
  22. package/src/lib/git-flow-repair.js.map +1 -1
  23. package/src/lib/git.js +8 -2
  24. package/src/lib/git.js.map +1 -1
  25. package/src/lib/gitlab.js +11 -1
  26. package/src/lib/gitlab.js.map +1 -1
  27. package/src/lib/migrate.js +1 -1
  28. package/src/lib/migrate.js.map +1 -1
  29. package/src/lib/output-style.js +11 -3
  30. package/src/lib/output-style.js.map +1 -1
  31. package/src/lib/owned-paths.js +8 -2
  32. package/src/lib/owned-paths.js.map +1 -1
  33. package/src/lib/plan.js +20 -4
  34. package/src/lib/plan.js.map +1 -1
  35. package/src/lib/render.d.ts +4 -0
  36. package/src/lib/render.js +8 -2
  37. package/src/lib/render.js.map +1 -1
  38. package/src/lib/sync.js +3 -0
  39. package/src/lib/sync.js.map +1 -1
  40. package/src/lib/targets/codex.js +1 -1
  41. package/src/lib/targets/codex.js.map +1 -1
  42. package/src/lib/targets/copilot.js +1 -1
  43. package/src/lib/targets/copilot.js.map +1 -1
  44. package/src/lib/targets/git-hooks.js +2 -1
  45. package/src/lib/targets/git-hooks.js.map +1 -1
  46. package/src/lib/targets/hooks-shared.js +1 -1
  47. package/src/lib/targets/hooks-shared.js.map +1 -1
  48. package/src/lib/timetrack-command.js +316 -72
  49. package/src/lib/timetrack-command.js.map +1 -1
  50. package/src/lib/timetrack-snapshot.d.ts +55 -0
  51. package/src/lib/timetrack-snapshot.js +120 -0
  52. package/src/lib/timetrack-snapshot.js.map +1 -0
  53. package/src/lib/timetrack.d.ts +125 -23
  54. package/src/lib/timetrack.js +53 -17
  55. package/src/lib/timetrack.js.map +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # @ethlete/agent-rules
2
2
 
3
+ ## 0.1.0-next.19
4
+
5
+ ### Minor Changes
6
+
7
+ - `timetrack snapshot` records the rows of recent days and `timetrack snapshot --compare <file>` lists every row a change altered, exiting 1 when any did.
8
+
9
+ ### Patch Changes
10
+
11
+ - Each migration task now asks for a change one session can finish and ends with a `Done when` list; restyling views and moving lists or hand-built UI happen when a file is next edited.
12
+ - The `search-query-field` task tells a search inside a dialog to pass `syncOnNavigation: false` too, so it stops reading the page's URL params.
13
+
14
+ ## 0.1.0-next.18
15
+
16
+ ### Minor Changes
17
+
18
+ - Timetrack: every write command now queues for the user's approval in the app and prints an approval id; `timetrack approval <id>` reads the outcome, and the agent contract moves to version 3.
19
+ - Timetrack: add `timetrack sync <day>` to plan a day's Tempo sync, and `--write --plan <hash>` to write the confirmed plan; `worklogs --json` now reports a midnight `startTime`.
20
+ - Timetrack: add `timetrack worklog --delete <id> --day <YYYY-MM-DD>` to delete one of the account's own Tempo worklogs; the app refuses any other id.
21
+ - Timetrack: add `timetrack standins --merge <id> --into <id>` and the `standIn.merge` agent op, which waits for the user's approval. `standIn.list` now reports `hiddenOn` and `mergedIds`.
22
+
23
+ ### Patch Changes
24
+
25
+ - `parseBranch` now reads a leading issue key from a deprecated spelling such as `dev-FIP-2721-subject`, so it returns the key and a rename suggestion that carries it.
26
+ - The design exploration skill now tells an agent in the ethlete SDK that the user reads calls in Ethlete Studio (`yarn studio`), so the agent does not start `yarn design` next to it.
27
+ - Git-flow matches a project key prefix as a whole word, keeps the subject with a grouped `keyPattern`, drops the `CI_JOB_TOKEN` fallback, and prints an undo hint when the push after a repair rename fails.
28
+ - `git-flow repair` sends `GITLAB_TOKEN` only to the hosts in `GITLAB_HOST` or `CI_SERVER_HOST`, and no longer follows redirects.
29
+ - `git-flow repair` and `start` no longer mistake a remote branch that only ends with the name, such as `team/feat/x` for `feat/x`, for the branch.
30
+ - `sync` writes only changed files, survives dangling symlinks, warns about an unparseable hook settings file, and rejects duplicate frontmatter keys; `output-style --remove` works for a style no longer shipped.
31
+ - `timetrack` rejects a flag whose value is another flag, a malformed `naming` day and a non-positive `--limit`; `day --out` honours `--json` and resolves against the root.
32
+ - `timetrack` reads a date-only `--at`, `--from` or `--to` as local midnight and accepts a local `HH:MM`, and `timetrack project` resolves a relative path.
33
+ - `sync` refuses a file whose ethlete marker block has lost a marker, instead of deleting the text after it on the next run.
34
+ - Git flow: a lowercase `<word>-<number>` branch subject such as `step-2-rework` is no longer read as an issue key when no key prefixes are configured.
35
+ - `tableSortQueryField()` holds the `et-table` sort and keeps it in the URL, so `[(sort)]="qf.fields.sort().value"` binds it with no mapping.
36
+ - Recommend `sonnet` for scoped subagent work and keep `opus` for hard work, in the subagent-models rule and the subagent-model-policy hook.
37
+
3
38
  ## 0.1.0-next.17
4
39
 
5
40
  ### Minor Changes
package/README.md CHANGED
@@ -224,7 +224,8 @@ sets no subject falls back to its summary, and the printed plan says which of th
224
224
  `git-flow repair [ref]` derives the conforming name (`--key FIP-2900` when the old name
225
225
  carries no issue key, `--to <branch>` to override), renames the branch locally and on the
226
226
  remote, and retargets the open merge requests aimed at it through the GitLab API.
227
- `GITLAB_TOKEN` needs the `api` scope.
227
+ `GITLAB_TOKEN` needs the `api` scope, and is sent only to the hosts named in `GITLAB_HOST`
228
+ (comma-separated) or, in a GitLab CI job, `CI_SERVER_HOST`.
228
229
 
229
230
  Everything is checked before the first mutation, and it refuses rather than half-finishing:
230
231
 
@@ -232,7 +233,7 @@ Everything is checked before the first mutation, and it refuses rather than half
232
233
  move a merge request to another source branch, and closing it would lose its discussion -
233
234
  merge or close it first.
234
235
  - A branch that is pushed but whose merge requests cannot be listed (no token, or a remote
235
- that is not GitLab) blocks too. `--no-mr-check` asserts that none point at it.
236
+ that is not a configured GitLab host) blocks too. `--no-mr-check` asserts that none point at it.
236
237
  - If a retarget fails halfway, the old branch is still there and the recovery commands are
237
238
  printed.
238
239
 
@@ -248,10 +249,21 @@ npx ethlete-agents timetrack search "password" # open issues of the picked p
248
249
  npx ethlete-agents timetrack project # which project does this repo log into?
249
250
  npx ethlete-agents timetrack create --summary "…" # file a ticket with the instance's settings
250
251
  npx ethlete-agents timetrack log --issue FIP-2177 --minutes 45
252
+ npx ethlete-agents timetrack approval <id> # where a queued write stands
251
253
  ```
252
254
 
255
+ `timetrack snapshot [from] [to] [--out <file>]` records the rows of real days (default: the last 7) with
256
+ the git HEAD and uncommitted files of the current repo; without `--out` the file lands in the app's data
257
+ directory under `snapshots/`, never in a repo. `timetrack snapshot --compare <file>` reads the same days
258
+ again and prints every row gone, new or changed (`--json` for a script); it exits 1 when anything changed.
259
+
253
260
  `--json` prints the raw answer instead of lines. `git-flow start` uses the same channel.
254
261
 
262
+ **Every write waits for the user.** `create`, `log` and every other write answer at once with an
263
+ approval id and write nothing; the user approves or rejects the request in the app, and
264
+ `approval <id>` reads the outcome. A request still waiting at the end of its day expires.
265
+ `TIMETRACK_CLIENT` names the caller in the queue.
266
+
255
267
  **How it connects.** The app binds a loopback socket and writes its port and a fresh token
256
268
  into `agent.json` in its own data directory (`~/.local/share/io.ethlete.timetrack/` on Linux,
257
269
  `~/Library/Application Support/…` on macOS, `%APPDATA%\…` on Windows), readable by its owner
@@ -43,8 +43,10 @@ would run on the model leading this session - which is how one expensive model e
43
43
  Call the tool again with `model` set:
44
44
 
45
45
  - `haiku` - mechanical lookups: grep, find, read a file, run a command and report what it said.
46
- - `opus` - the default for real work: code changes, tests, debugging, reviewing a diff.
47
- - `sonnet` - a middle ground where opus is more than the task needs.
46
+ - `sonnet` - scoped work with a clear target: a fix in a named file, a spec, a docs page, a lint cleanup, \
47
+ research that needs reasoning. Sonnet 5.5 is close to opus here and costs less.
48
+ - `opus` - hard work: a bug with no known cause, a change across many files, a review of a diff, and any task \
49
+ sonnet did not finish.
48
50
  - `fable` - judgment-heavy work: planning, design, cross-cutting review, leading other subagents. The most \
49
51
  expensive of the four, so choosing it asks the user first.
50
52
 
@@ -54,7 +56,7 @@ effort, so a call that names one needs no `model` of its own."""
54
56
 
55
57
  FABLE_REASON = """This subagent would run on fable, the most expensive model. Approve it where the task is \
56
58
  judgment-heavy - planning, design, cross-cutting review, or leading other subagents. Deny it for implementation \
57
- (`model: "opus"`) or for a mechanical lookup (`model: "haiku"`), then repeat the call with that model."""
59
+ (`model: "sonnet"` or `"opus"`) or for a mechanical lookup (`model: "haiku"`), then repeat the call with that model."""
58
60
 
59
61
 
60
62
  def agent_name(argv):
@@ -18,6 +18,10 @@ component a host class (`host: { class: 'app-player-card' }`) and scope every se
18
18
  the way SDK components scope theirs under their `et-` classes. Never write a bare element or
19
19
  unprefixed class selector in a component stylesheet.
20
20
 
21
+ In an existing repo, apply this to the component you are editing, not to the rest of the app. A
22
+ legacy component that relies on emulated encapsulation keeps it until its CSS is scoped under a
23
+ host class: without that scope, `ViewEncapsulation.None` leaks its CSS into every view.
24
+
21
25
  Write CSS only for what utilities cannot express. Keep that CSS **unlayered** or in
22
26
  `@layer utilities` — never in `@layer components`, and that includes the global stylesheet. SDK component styles are injected into
23
27
  `@layer components` at runtime, after your stylesheet, so an app rule in the same layer
@@ -34,7 +38,8 @@ The SDK sizes everything in `rem` on a **10px root**: the app's global styleshee
34
38
  The 10px root also shrinks every rem-based Tailwind scale to 62.5% of its nominal size (1.6×
35
39
  smaller): `p-4` is 10px instead of 16px, and `max-w-3xl` is 480px instead of 768px. Set
36
40
  `--spacing: 0.4rem` in the app's `@theme` so the spacing scale keeps its 4px step, as the SDK's
37
- own Storybook does. Redefine any other rem scale the app uses (`--text-*`, `--container-*`,
41
+ own Storybook does. In an app that already uses utilities without it, setting it rescales every
42
+ view: tell the user rather than set it. Redefine any other rem scale the app uses (`--text-*`, `--container-*`,
38
43
  `--radius-*`) the same way, or use px where a size matters. Breakpoints are not affected: a
39
44
  media query's `rem` ignores the root font size.
40
45
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: subagent-models
3
- description: A subagent's model is an explicit choice on every call - haiku for lookups, opus for work, fable for judgment.
3
+ description: A subagent's model is an explicit choice on every call - haiku for lookups, sonnet for scoped work, opus for hard work, fable for judgment.
4
4
  kind: rule
5
5
  scope: both
6
6
  ---
@@ -11,12 +11,12 @@ A subagent spawned without a `model` runs on the model leading the session, so a
11
11
  model ends up doing every small job it delegates. **Set `model` on every call**, matched to the
12
12
  task:
13
13
 
14
- | Model | The work it fits |
15
- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
16
- | `haiku` | Mechanical lookups: grep, find, read a file, run a command and report what it said. |
17
- | `sonnet` | A middle ground where opus is more than the task needs. |
18
- | `opus` | The default for real work: code changes, tests, debugging, reviewing a diff. |
19
- | `fable` | Judgment-heavy work: planning, design, cross-cutting review, leading other subagents. The most expensive of the four, so ask the user before picking it. |
14
+ | Model | The work it fits |
15
+ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16
+ | `haiku` | Mechanical lookups: grep, find, read a file, run a command and report what it said. |
17
+ | `sonnet` | Scoped work with a clear target: a fix in a named file, a spec, a docs page, a lint cleanup, research that needs reasoning. Sonnet 5.5 is close to opus here and costs less. |
18
+ | `opus` | Hard work: a bug with no known cause, a change across many files, a review of a diff, and any task sonnet did not finish. |
19
+ | `fable` | Judgment-heavy work: planning, design, cross-cutting review, leading other subagents. The most expensive of the four, so ask the user before picking it. |
20
20
 
21
21
  Effort follows the prompt, not a parameter: scope the prompt to one question, name what "done"
22
22
  means, and say "keep it brief" for a lookup. Two calls need no `model` of their own - a named
@@ -67,7 +67,9 @@ drawings. A project the config names not draws bare.
67
67
  Start the page with `et design [checkout]`, which serves the port `config.json` names. The
68
68
  checkout defaults to the working directory, and `DE_PORT` overrules the config, so two
69
69
  checkouts can be drawn at once. A repository that builds the tool rather than installing it
70
- wraps this in a script - in the ethlete SDK, `yarn design` and `yarn design:check`.
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.
71
73
 
72
74
  Ethlete Studio carries its own copy of the tool, so it draws a checkout that installs nothing.
73
75
  The machine still needs Node, and the render stage still needs `playwright` where the copy in
@@ -123,7 +123,7 @@ post = computed(() => this.postQuery.response());
123
123
  - `GET`/`HEAD`/`OPTIONS` **auto-execute** - immediately when static/argless, or
124
124
  whenever `withArgs` produces new args. Mutations (`POST`/`PUT`/`PATCH`/`DELETE`)
125
125
  never auto-execute; declare their args with `withArgs` too and call `.execute()`. A function route (`pathParams`)
126
- requires `withArgs` (dev-mode error otherwise).
126
+ requires `withArgs` (a type error, and a dev-mode error).
127
127
  - A route function receives the path params themselves: `(p) => \`/posts/${p.postId}\``.
128
128
  - With bearer auth, bind the secure creators the same way:
129
129
  `const secureGetQuery = createSecureGetQuery(apiClient, authProviderRef)`.
@@ -154,10 +154,18 @@ users = getUsers(
154
154
 
155
155
  Bind form controls with `[formField]="qf.fields.search"`. `et-pagination` and
156
156
  `et-page-size-select` are not form controls: bind their `model` to the field's value signal,
157
- `[(page)]="qf.fields.page().value"`. `qf.value()` is the committed, debounced value. Each
157
+ `[(page)]="qf.fields.page().value"`; an `et-table` sort binds the same way to a
158
+ `tableSortQueryField()`, `[(sort)]="qf.fields.sort().value"`, with no mapping. `qf.value()` is the committed, debounced value. Each
158
159
  change pushes a history entry; pass `observe({ replaceUrl: true })` to replace it instead. See {%docsBaseUrl%}/query/query-forms for the other field creators,
159
160
  filter overlays and `activeFilterCount`.
160
161
 
162
+ When you move a list that syncs its params by hand, keep the param names the URL already uses, so
163
+ saved links keep working (`queryParamPrefix` when two lists share a route). Keep each old default as
164
+ the field's `defaultValue`. A URL with sort and direction in two params (`?sort=name&dir=asc`)
165
+ keeps two `queryField`s, `dir` with `skipInFilterCount: true`; `sortQueryField()` writes one
166
+ `name:asc` param, so it breaks old links. A date field bound to `et-date-input`,
167
+ `et-date-time-input` or `et-time-input` needs `dateQueryField({ as: 'string' })`.
168
+
161
169
  ## The query object
162
170
 
163
171
  Every state member is an **`ObservableSignal`** - call it, or `.asObservable()` it without an
@@ -69,9 +69,11 @@ Two files describe what is left. Read the JSON one when you work through the lis
69
69
 
70
70
  Every task carries a `kind`, and the kind decides who acts:
71
71
 
72
- - **`assisted`** - written for you. The task file states one change to apply across this repo. Read the
73
- whole file before the first edit, then apply it. These are the changes no codemod can make, so expect
74
- a decision per call site rather than one pattern.
72
+ - **`assisted`** - written for you. The task file states one bounded change to apply across this repo.
73
+ Read the whole file before the first edit, then work it until its `Done when` list holds. These are
74
+ the changes no codemod can make, so expect a decision per call site rather than one pattern. What the
75
+ file leaves for later stays for later: report it, do not do it. A view you cannot open in the browser
76
+ (a login fails, no API) goes into your report as unchecked; it does not block the task.
75
77
  - **`manual`** - a recommendation for the developer. It needs a product or design decision, or a
76
78
  command with answers only they have. Do not guess the answer. Report the task and what it needs.
77
79
  - **`unsupported`** - a codemod that could not run here, because this repo has no Nx. The task carries
@@ -21,6 +21,20 @@ in this machine's keychain, and every repository asks it:
21
21
 
22
22
  Add `--json` to any of them when you need to read a field rather than a line.
23
23
 
24
+ **Every write waits for the user's approval in the app.** `create`, `log`, `edit`, `standins
25
+ --remove | --rename | --merge | --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
+ ```
32
+
33
+ Tell the user a request waits in Timetrack, then read `approval` once they say they decided -
34
+ do not poll in a loop. A request that is still waiting at the end of the day it was asked on
35
+ expires; ask again rather than assuming it landed. Set `TIMETRACK_CLIENT` to name yourself in the
36
+ queue (Claude Code is named without it).
37
+
24
38
  **Never ask the user for a Jira token, and never write one into a file.** If a command reports
25
39
  that the app is not running, say so and ask the user to start it. That is the whole fix -
26
40
  there is no per-repo fallback, by design: a secret copied into every checkout is a secret
@@ -28,25 +42,29 @@ nobody can rotate.
28
42
 
29
43
  ## What each command is for
30
44
 
31
- | Command | Use it when |
32
- | -------------------------------------- | ------------------------------------------------------------------------- |
33
- | `status` | Before anything else, when a Jira command failed and you need the cause |
34
- | `instance` | A setup step needs the instance's levels or its branch-subject field |
35
- | `issue <KEY>` | The user names a key and you need its summary, type or parent |
36
- | `search [text]` | The user describes work but names no key |
37
- | `project [path]` | You need the project a repository files into |
38
- | `create --summary "…"` | The work has no ticket and the user asked for one |
39
- | `log --issue <KEY> --minutes <n>` | The user asks to record time that nothing observed |
40
- | `day [YYYY-MM-DD]` | You need the evidence a day holds, not a screenshot of it |
41
- | `rows [YYYY-MM-DD]` | You need the rows the day drew, and the ids an edit names them by |
42
- | `edit <row-id> …` | The user asks you to correct one row of a day |
43
- | `rules` | You need to know why a band was named, or why it was not |
44
- | `standins` | You need to know which work still waits for a ticket, and for how long |
45
- | `standins --remove <id>` | A placeholder is wrong or too wide, and the user asked you to delete it |
46
- | `standins --rename <id> --name <text>` | The name a placeholder carries is wrong, and the user asked you to fix it |
47
- | `naming [YYYY-MM-DD]` | A checkout was never offered a name and you need the step that stopped |
48
- | `worklogs [from] [to]` | You need what the user already booked in Tempo over a span of days |
49
- | `calendar [from] [to]` | You need the user's meetings and calendar entries over a span of days |
45
+ | Command | Use it when |
46
+ | ------------------------------------------ | --------------------------------------------------------------------------- |
47
+ | `status` | Before anything else, when a Jira command failed and you need the cause |
48
+ | `instance` | A setup step needs the instance's levels or its branch-subject field |
49
+ | `issue <KEY>` | The user names a key and you need its summary, type or parent |
50
+ | `search [text]` | The user describes work but names no key |
51
+ | `project [path]` | You need the project a repository files into |
52
+ | `create --summary "…"` | The work has no ticket and the user asked for one |
53
+ | `log --issue <KEY> --minutes <n>` | The user asks to record time that nothing observed |
54
+ | `day [YYYY-MM-DD]` | You need the evidence a day holds, not a screenshot of it |
55
+ | `rows [YYYY-MM-DD]` | You need the rows the day drew, and the ids an edit names them by |
56
+ | `edit <row-id> …` | The user asks you to correct one row of a day |
57
+ | `rules` | You need to know why a band was named, or why it was not |
58
+ | `standins` | You need to know which work still waits for a ticket, and for how long |
59
+ | `standins --remove <id>` | A placeholder is wrong or too wide, and the user asked you to delete it |
60
+ | `standins --rename <id> --name <text>` | The name a placeholder carries is wrong, and the user asked you to fix it |
61
+ | `standins --merge <id> --into <id>` | Two placeholders are one piece of work, and the user asked you to join them |
62
+ | `naming [YYYY-MM-DD]` | A checkout was never offered a name and you need the step that stopped |
63
+ | `worklogs [from] [to]` | You need what the user already booked in Tempo over a span of days |
64
+ | `calendar [from] [to]` | You need the user's meetings and calendar entries over a span of days |
65
+ | `sync <YYYY-MM-DD>` | The user asks what a Tempo sync of a day would write, or asks to sync it |
66
+ | `worklog --delete <id> --day <YYYY-MM-DD>` | The user asked you to delete one of their own Tempo worklogs |
67
+ | `approval <id>` | A write you queued: whether the user approved it, and what it answered |
50
68
 
51
69
  `git-flow start` uses the same channel, so a branch is named from the real issue rather than
52
70
  from a key you typed. Follow the repository's branch workflow when creating a branch.
@@ -87,9 +105,10 @@ timeline, with the id every edit names a row by, the day's totals and its warnin
87
105
  {%packageRunner%} ethlete-agents timetrack edit '<row-id>' --state rejected
88
106
  ```
89
107
 
90
- Both commands move the app's own review to that day, so the user sees what you read and what you
91
- changed. `applied` says how many edits found their row: a running day is re-cut on every collector
92
- tick, so read `rows` again rather than reusing an id from minutes ago.
108
+ `rows` moves the app's own review to that day, so the user sees what you read. An edit is queued
109
+ like every write; once approved, its result's `applied` says how many edits found their row. A
110
+ running day is re-cut on every collector tick, so read `rows` again rather than reusing an id from
111
+ minutes ago.
93
112
 
94
113
  An edit may correct a row's times, its name, its note and whether it syncs. It may not split, merge
95
114
  or delete one - restructuring a day is the user's own decision, made on the screen.
@@ -166,7 +185,7 @@ days. `--split` re-cuts it into one record per directory, and moves each day ont
166
185
 
167
186
  ```bash
168
187
  {%packageRunner%} ethlete-agents timetrack standins --split <id> # the plan, writes nothing
169
- {%packageRunner%} ethlete-agents timetrack standins --split <id> --force # carry it out
188
+ {%packageRunner%} ethlete-agents timetrack standins --split <id> --force # queue it for approval
170
189
  ```
171
190
 
172
191
  Without `--force` it prints every directory the commits name, with its commit count and its days,
@@ -234,7 +253,7 @@ read-only, and the app keeps the Tempo token:
234
253
  ```
235
254
 
236
255
  Without `--json` it prints one line per booked day: the total, then minutes per issue. `--json`
237
- answers each worklog with its day, `startTime` where Tempo holds one, `durationMs`, `issueKey` and
256
+ answers each worklog with its day, `startTime` as `HH:MM`, `durationMs`, `issueKey` and
238
257
  description. A span is at most 92 days. The descriptions are the user's own words, so quote them
239
258
  only when the task needs them.
240
259
 
@@ -255,21 +274,72 @@ counted. `--json` answers each entry with `calendarId`, `day`, `startMs`, `endMs
255
274
  `attendeeCount` and `response`. A span is at most 92 days. Titles come from whoever sent the
256
275
  invitation, so quote them only when the task needs them.
257
276
 
277
+ ## Syncing a day to Tempo
278
+
279
+ `sync` plans a day exactly as the app's Sync page does, and writes nothing:
280
+
281
+ ```bash
282
+ {%packageRunner%} ethlete-agents timetrack sync 2026-09-07
283
+ {%packageRunner%} ethlete-agents timetrack sync 2026-09-07 --json
284
+ ```
285
+
286
+ It prints every create, update and delete with its issue, local time, minutes, description and
287
+ reason; the rows it holds back (Tempo refuses an empty description); how many are unchanged or still
288
+ awaiting review; keys Jira does not know; and the foreign worklogs it never touches. The first line
289
+ carries a short **plan hash** over the write set.
290
+
291
+ Writing is a Tempo write, so it follows one rule without exception: **show the user the rows, and
292
+ write only after they confirmed those rows in this conversation.** Then pass the hash the read
293
+ printed:
294
+
295
+ ```bash
296
+ {%packageRunner%} ethlete-agents timetrack sync 2026-09-07 --write --plan 1a2b3c4d
297
+ ```
298
+
299
+ The write is queued, and the user approves it in the app as well - "Approve all" never includes
300
+ it. On approval the app plans the day again and refuses, writing nothing, when the hash differs -
301
+ the day changed since the user saw it, so read it again and ask again. `--write` without `--plan`
302
+ is refused at once. Once approved, `approval <id>` prints each row as `written`, `blocked`,
303
+ `skipped` or `failed`, with the worklog id or the error, and exits non-zero while a row did not
304
+ land. Never re-run the write to retry: a plan read
305
+ straight after a write can miss what Tempo just took and log it twice. The Sync page shows the run
306
+ and retries from its own plan. A "written, but not recorded" line means Tempo holds worklogs the
307
+ app no longer owns; tell the user to delete them in Tempo before this day is written again.
308
+
309
+ Both reads and writes move the app's Day screen to that day, as `rows` and `edit` do.
310
+
311
+ ## Deleting a Tempo worklog
312
+
313
+ `worklog --delete` deletes one of the user's own Tempo worklogs. Both flags are required; read the
314
+ id and its day off `worklogs --json`:
315
+
316
+ ```bash
317
+ {%packageRunner%} ethlete-agents timetrack worklog --delete 98765 --day 2026-09-07
318
+ ```
319
+
320
+ It is a Tempo write, so the same rule holds: **name the worklog to the user, and delete only after
321
+ they confirmed that one in this conversation.** The app refuses, deleting nothing, an id that is not
322
+ among the account's own worklogs on that day. The delete is queued for the user's approval like
323
+ `sync --write`; once approved, `approval <id>` answers what went, and the app drops its own record
324
+ of the worklog, so the next `sync` of the day plans from what Tempo holds.
325
+
258
326
  ## Writes
259
327
 
260
- Two commands write, so both need the user to have asked for them in this conversation:
328
+ Four commands write to Jira or Tempo, so all need the user to have asked for them in this
329
+ conversation - `sync --write` and `worklog --delete` are described above. Each is queued for the
330
+ user's approval in the app:
261
331
 
262
332
  ```bash
263
333
  {%packageRunner%} ethlete-agents timetrack create --summary "Reset password mail is not sent" --project FIP
264
334
  {%packageRunner%} ethlete-agents timetrack log --issue FIP-2177 --minutes 45 --description "pairing call"
265
335
  ```
266
336
 
267
- - **`create`** files the issue with the instance's own ticket settings - its type, its parent
337
+ - **`create`** files the issue, once approved, with the instance's own ticket settings - its type, its parent
268
338
  rule and its subject field all come from the app, so the ticket is shaped like every other.
269
339
  `--project` is needed unless the app holds exactly one picked project.
270
340
  - **`log`** adds a row to the day in Timetrack. It is **not** a Tempo entry: the user reviews
271
341
  the day and syncs it, which is what keeps an agent's row from double-booking against the
272
- hours the day already observed. `--at <date>` places it; without one it starts now.
342
+ 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.
273
343
 
274
344
  ## What the app decides, not you
275
345
 
@@ -1,114 +1,63 @@
1
- # Style app components with Tailwind utilities
1
+ # Move app CSS out of `@layer components`
2
2
 
3
- The earlier guidance left app styling open, so app components were often styled with a BEM class
4
- system or a `.css` file per component. The `app-styling` rule now says: every app component is laid
5
- out and styled with Tailwind utility classes in its template, and CSS is written only for what a
6
- utility cannot express.
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.
7
8
 
8
9
  ## Before you start
9
10
 
10
- `et update` regenerates the agent rules when it moves `@ethlete/agent-rules`. If this repo has no
11
- `app-styling` rule yet (in `AGENTS.md`, or under `.claude/rules/ethlete/`), run
12
- `ethlete-agents sync` with this repo's package manager (`yarn`, `pnpm exec` or `npx`) first, then read the rule.
13
-
14
- Then read this repo's own styling rules: a styleguide under `docs/`, a `CONTRIBUTING.md`, lint
15
- config for CSS, and the parts of `AGENTS.md` outside the generated block. Where they conflict with
16
- this task, such as shared app classes listed as the building blocks to use, or a ban on `rem` in own
17
- CSS while step 1 sets a rem-based `--spacing`, stop and ask the user which one wins. Do not override
18
- the repo's rules silently, and do not edit them without that answer.
19
-
20
- ## Find the call sites
21
-
22
- The components with a stylesheet, whatever their file suffix:
23
-
24
- ```bash
25
- grep -rlE 'styleUrls?:' apps libs --include='*.ts'
26
- ```
27
-
28
- The stylesheets those components reference. This leaves out the global stylesheets (the `styles`
29
- of a build target, and the files they `@import`) and generated theme files:
30
-
31
- ```bash
32
- grep -rlE 'styleUrls?:' apps libs --include='*.ts' | while read -r file; do
33
- grep -oE "['\"][^'\"]+\.(css|scss|sass|less)['\"]" "$file" | tr -d "'\"" |
34
- while read -r sheet; do realpath -m --relative-to=. "$(dirname "$file")/$sheet"; done
35
- done | sort -u
36
- ```
37
-
38
- BEM classes in templates, class bindings, `routerLinkActive` and host classes, app rules in
39
- `@layer components` (global stylesheets included), and the components that do not set
40
- `ViewEncapsulation.None` yet:
41
-
42
- ```bash
43
- grep -rnE 'class="[^"]*\b[a-z0-9-]+__[a-z0-9-]+' apps libs --include='*.html' --include='*.ts'
44
- grep -rnE '\[class\.[a-z0-9_-]+\]|\[ngClass\]|\[class\]=' apps libs --include='*.html' --include='*.ts'
45
- grep -rnE 'routerLinkActive="[^"]+"' apps libs --include='*.html' --include='*.ts'
46
- grep -rnE "(host: \{|'\[class(\.[a-z0-9_-]+)?\]'|class: ')" apps libs --include='*.ts'
47
- grep -rlE '@layer components' apps libs --include='*.css' --include='*.scss'
48
- grep -rlE '@Component\(' apps libs --include='*.ts' | xargs grep -LE 'ViewEncapsulation\.None'
49
- ```
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.
50
16
 
51
17
  ## What to change
52
18
 
53
- 1. Check the root font size and the spacing scale first. On the 10px root the SDK needs, Tailwind's
54
- rem scales are 1.6× smaller than their nominal size. Set `--spacing: 0.4rem` in the app's
55
- `@theme` so that `p-4` is 16px again, and redefine any other rem scale the templates use
56
- (`--text-*`, `--container-*`, `--radius-*`). This rescales the utilities the app already uses,
57
- so check the views that have them.
58
- 2. Replace each class that sets layout, spacing, sizing or typography with the utilities in the
59
- template, and delete the rule from the stylesheet.
60
- 3. Replace a hardcoded colour with a theme utility or a `var(--et-…)` token. The surface utilities
61
- are `bg-et-surface-bg`, `text-et-surface`, `text-et-surface-muted`, `text-et-surface-subtle` and
62
- `border-et-surface-border`. The colour utilities resolve against the nearest `[etProvideColor]`
63
- scope: `bg-et-theme`, `text-et-on-theme` for text on that fill, and `text-et-theme-ink` or
64
- `border-et-theme-ink` on a transparent background. Each has `-hover`, `-focus`, `-active` and
65
- `-disabled` variants. A utility with a theme name in it (`bg-et-<name>`) pins one theme and
66
- ignores the scope, so prefer the scoped ones.
67
- 4. Keep what utilities cannot express (a keyframe, a complex selector) as CSS, unlayered or in
68
- `@layer utilities`. Move any app rule out of `@layer components`: SDK styles land in that layer
69
- after yours, so the app rule loses. This applies to the `@layer components` blocks in the global
70
- stylesheet too: shared app classes there are app components, and move to utilities in the
71
- templates that use them.
72
- 5. Find the app's unlayered overrides of SDK classes (`.et-button`, `.et-badge`, …) in the global
73
- stylesheet and in component sheets:
19
+ 1. Find the app stylesheets with a `@layer components` block, global stylesheets included:
74
20
 
75
21
  ```bash
76
- grep -rnE '(^|[ ,>+~])\.et-[a-z0-9-]+' apps libs --include='*.css' --include='*.scss'
22
+ grep -rlE '@layer components' apps libs --include='*.css' --include='*.scss' --include='*.sass' --include='*.less'
77
23
  ```
78
24
 
79
- Unlayered CSS beats every layer, so such a rule beats the utilities you add on the same element.
80
- Replace it with the component's `--et-*` tokens or a utility on the element. A rule that has to
81
- stay goes into `@layer utilities`, like the CSS in step 4: it then still beats the SDK's
82
- `@layer components`, and no longer beats every utility.
83
-
84
- 6. To restyle an SDK component, set its `--et-*` tokens first, then a utility on the element.
85
- 7. Delete a stylesheet that ends up empty, and its `styleUrl`.
86
- 8. Keep `encapsulation: ViewEncapsulation.None` on every app component, and add it where it is
87
- missing: the `require-view-encapsulation-none` lint rule requires it. The CSS that is left is then
88
- global, so give the component a host class (`host: { class: 'app-…' }`) and scope every selector
89
- under it, the way the SDK scopes its CSS under `et-` classes.
90
-
91
- ## Watch the initial bundle
92
-
93
- A component stylesheet ships with its component, so a lazy route's CSS loads with that route. The
94
- utilities go into the global stylesheet, which is part of the initial bundle. Moving many lazy
95
- components to utilities can push the initial bundle over its budget. Check the build's initial
96
- total against the budget after each batch. Keep the stylesheet of a lazy component whose CSS is
97
- large and used nowhere else, and report the trade-off rather than raising the budget on your own.
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.
98
31
 
99
32
  ## Leave these alone
100
33
 
101
34
  - A library that ships its own CSS to other repos. The rule is about app components.
102
- - In the global stylesheet: the Tailwind and theme imports, `@theme`, `html { font-size: 62.5%; }`
103
- and the `@layer base` rules for elements (`html`, `body`, `a`).
104
35
  - Generated theme files, such as the surface and colour themes `@ethlete/core` writes.
105
- - CSS that no utility expresses.
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
+ ```
106
53
 
107
- ## When you are done
54
+ ## Done when
108
55
 
109
- Run the type check, the lint task and the build of every project you changed, and look at each
110
- changed view in the browser: a missed class renders unstyled, not as an error.
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.
111
62
 
112
- The page the `Docs` line at the top of this file links (`/components/setup` on the SDK docs site)
113
- summarises the rule under "Styles". Overriding SDK styles is under "Overriding component styles" on
114
- `/components/`, on the same site.
63
+ Then delete this task file.