@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.
- package/CHANGELOG.md +35 -0
- package/README.md +14 -2
- package/content/hooks/subagent-model-policy.py +5 -3
- package/content/rules/app-styling.md +6 -1
- package/content/rules/subagent-models.md +7 -7
- package/content/skills/design-exploration/SKILL.md +3 -1
- package/content/skills/query/SKILL.md +10 -2
- package/content/skills/sdk-update/SKILL.md +5 -3
- package/content/skills/timetrack/SKILL.md +97 -27
- package/migrations/app-styling-utilities.md +44 -95
- package/migrations/list-state-query-form.md +12 -95
- package/migrations/nx-layout.md +6 -0
- package/migrations/sdk-components-over-hand-built-ui.md +16 -58
- package/migrations/search-query-field.md +12 -5
- package/migrations.json +5 -5
- package/package.json +1 -1
- package/src/lib/frontmatter.js +32 -5
- package/src/lib/frontmatter.js.map +1 -1
- package/src/lib/git-flow/parse.js +38 -12
- package/src/lib/git-flow/parse.js.map +1 -1
- package/src/lib/git-flow-repair.js +12 -1
- package/src/lib/git-flow-repair.js.map +1 -1
- package/src/lib/git.js +8 -2
- package/src/lib/git.js.map +1 -1
- package/src/lib/gitlab.js +11 -1
- package/src/lib/gitlab.js.map +1 -1
- package/src/lib/migrate.js +1 -1
- package/src/lib/migrate.js.map +1 -1
- package/src/lib/output-style.js +11 -3
- package/src/lib/output-style.js.map +1 -1
- package/src/lib/owned-paths.js +8 -2
- package/src/lib/owned-paths.js.map +1 -1
- package/src/lib/plan.js +20 -4
- package/src/lib/plan.js.map +1 -1
- package/src/lib/render.d.ts +4 -0
- package/src/lib/render.js +8 -2
- package/src/lib/render.js.map +1 -1
- package/src/lib/sync.js +3 -0
- package/src/lib/sync.js.map +1 -1
- package/src/lib/targets/codex.js +1 -1
- package/src/lib/targets/codex.js.map +1 -1
- package/src/lib/targets/copilot.js +1 -1
- package/src/lib/targets/copilot.js.map +1 -1
- package/src/lib/targets/git-hooks.js +2 -1
- package/src/lib/targets/git-hooks.js.map +1 -1
- package/src/lib/targets/hooks-shared.js +1 -1
- package/src/lib/targets/hooks-shared.js.map +1 -1
- package/src/lib/timetrack-command.js +316 -72
- package/src/lib/timetrack-command.js.map +1 -1
- package/src/lib/timetrack-snapshot.d.ts +55 -0
- package/src/lib/timetrack-snapshot.js +120 -0
- package/src/lib/timetrack-snapshot.js.map +1 -0
- package/src/lib/timetrack.d.ts +125 -23
- package/src/lib/timetrack.js +53 -17
- 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
|
-
- `
|
|
47
|
-
|
|
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.
|
|
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` |
|
|
18
|
-
| `opus` |
|
|
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
|
|
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
|
|
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"
|
|
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.
|
|
73
|
-
whole file before the first edit, then
|
|
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
|
|
32
|
-
|
|
|
33
|
-
| `status`
|
|
34
|
-
| `instance`
|
|
35
|
-
| `issue <KEY>`
|
|
36
|
-
| `search [text]`
|
|
37
|
-
| `project [path]`
|
|
38
|
-
| `create --summary "…"`
|
|
39
|
-
| `log --issue <KEY> --minutes <n>`
|
|
40
|
-
| `day [YYYY-MM-DD]`
|
|
41
|
-
| `rows [YYYY-MM-DD]`
|
|
42
|
-
| `edit <row-id> …`
|
|
43
|
-
| `rules`
|
|
44
|
-
| `standins`
|
|
45
|
-
| `standins --remove <id>`
|
|
46
|
-
| `standins --rename <id> --name <text>`
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
tick, so read `rows` again rather than reusing an id from
|
|
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 #
|
|
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`
|
|
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
|
-
|
|
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
|
|
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
|
-
#
|
|
1
|
+
# Move app CSS out of `@layer components`
|
|
2
2
|
|
|
3
|
-
The
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
11
|
-
`
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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.
|
|
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 -
|
|
22
|
+
grep -rlE '@layer components' apps libs --include='*.css' --include='*.scss' --include='*.sass' --include='*.less'
|
|
77
23
|
```
|
|
78
24
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
-
|
|
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
|
-
##
|
|
54
|
+
## Done when
|
|
108
55
|
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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.
|