@ethlete/agent-rules 0.1.0-next.1 → 0.1.0-next.10

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 (121) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/README.md +296 -21
  3. package/content/git-hooks/post-checkout.sh +10 -0
  4. package/content/git-hooks/pre-push.sh +5 -0
  5. package/content/hooks/context-warning.py +284 -69
  6. package/content/output-styles/ste-clarity.md +131 -0
  7. package/content/rules/comments.md +50 -20
  8. package/content/skills/angular-patterns/SKILL.md +1 -1
  9. package/content/skills/api-source/SKILL.md +118 -0
  10. package/content/skills/figma-export/SKILL.md +193 -0
  11. package/content/skills/figma-export/dump-figma-layers.py +83 -0
  12. package/content/skills/figma-export/dump-figma-svg.py +235 -0
  13. package/content/skills/figma-export/measure-template.mjs +87 -0
  14. package/content/skills/git-commit/SKILL.md +6 -7
  15. package/content/skills/git-flow/SKILL.md +87 -0
  16. package/content/skills/handoff/SKILL.md +4 -0
  17. package/content/skills/query/SKILL.md +23 -13
  18. package/content/skills/rxjs-signals/SKILL.md +1 -1
  19. package/content/skills/sdk-docs/SKILL.md +10 -2
  20. package/content/skills/sdk-local-build/SKILL.md +115 -0
  21. package/content/skills/sdk-source/SKILL.md +133 -0
  22. package/content/skills/styleguide/STYLEGUIDE.md +2 -2
  23. package/content/skills/theming/SKILL.md +1 -1
  24. package/content/skills/timetrack/SKILL.md +66 -0
  25. package/package.json +12 -1
  26. package/src/index.js +23 -10
  27. package/src/index.js.map +1 -1
  28. package/src/lib/commitlint.d.ts +10 -0
  29. package/src/lib/commitlint.js +51 -0
  30. package/src/lib/commitlint.js.map +1 -0
  31. package/src/lib/config.d.ts +35 -6
  32. package/src/lib/config.js +26 -3
  33. package/src/lib/config.js.map +1 -1
  34. package/src/lib/git-flow/build.d.ts +35 -0
  35. package/src/lib/git-flow/build.js +24 -0
  36. package/src/lib/git-flow/build.js.map +1 -0
  37. package/src/lib/git-flow/config.d.ts +59 -0
  38. package/src/lib/git-flow/config.js +50 -0
  39. package/src/lib/git-flow/config.js.map +1 -0
  40. package/src/lib/git-flow/index.d.ts +6 -0
  41. package/src/lib/git-flow/index.js +10 -0
  42. package/src/lib/git-flow/index.js.map +1 -0
  43. package/src/lib/git-flow/parse.d.ts +49 -0
  44. package/src/lib/git-flow/parse.js +274 -0
  45. package/src/lib/git-flow/parse.js.map +1 -0
  46. package/src/lib/git-flow/rename.d.ts +24 -0
  47. package/src/lib/git-flow/rename.js +70 -0
  48. package/src/lib/git-flow/rename.js.map +1 -0
  49. package/src/lib/git-flow/start.d.ts +49 -0
  50. package/src/lib/git-flow/start.js +57 -0
  51. package/src/lib/git-flow/start.js.map +1 -0
  52. package/src/lib/git-flow/validate.d.ts +34 -0
  53. package/src/lib/git-flow/validate.js +72 -0
  54. package/src/lib/git-flow/validate.js.map +1 -0
  55. package/src/lib/git-flow-command.d.ts +4 -0
  56. package/src/lib/git-flow-command.js +157 -0
  57. package/src/lib/git-flow-command.js.map +1 -0
  58. package/src/lib/git-flow-repair.d.ts +17 -0
  59. package/src/lib/git-flow-repair.js +146 -0
  60. package/src/lib/git-flow-repair.js.map +1 -0
  61. package/src/lib/git-flow-start.d.ts +20 -0
  62. package/src/lib/git-flow-start.js +132 -0
  63. package/src/lib/git-flow-start.js.map +1 -0
  64. package/src/lib/git.d.ts +27 -0
  65. package/src/lib/git.js +49 -0
  66. package/src/lib/git.js.map +1 -0
  67. package/src/lib/gitlab.d.ts +35 -0
  68. package/src/lib/gitlab.js +98 -0
  69. package/src/lib/gitlab.js.map +1 -0
  70. package/src/lib/index.d.ts +1 -0
  71. package/src/lib/index.js +1 -0
  72. package/src/lib/index.js.map +1 -1
  73. package/src/lib/output-style-command.d.ts +3 -0
  74. package/src/lib/output-style-command.js +69 -0
  75. package/src/lib/output-style-command.js.map +1 -0
  76. package/src/lib/output-style.d.ts +38 -0
  77. package/src/lib/output-style.js +126 -0
  78. package/src/lib/output-style.js.map +1 -0
  79. package/src/lib/owned-paths.js +19 -1
  80. package/src/lib/owned-paths.js.map +1 -1
  81. package/src/lib/plan.d.ts +0 -1
  82. package/src/lib/plan.js +83 -9
  83. package/src/lib/plan.js.map +1 -1
  84. package/src/lib/prompt.d.ts +8 -0
  85. package/src/lib/prompt.js +27 -0
  86. package/src/lib/prompt.js.map +1 -0
  87. package/src/lib/render.d.ts +20 -2
  88. package/src/lib/render.js +31 -8
  89. package/src/lib/render.js.map +1 -1
  90. package/src/lib/sync.d.ts +0 -1
  91. package/src/lib/sync.js +2 -2
  92. package/src/lib/sync.js.map +1 -1
  93. package/src/lib/targets/claude-hooks.d.ts +1 -23
  94. package/src/lib/targets/claude-hooks.js +15 -84
  95. package/src/lib/targets/claude-hooks.js.map +1 -1
  96. package/src/lib/targets/claude.js +1 -1
  97. package/src/lib/targets/claude.js.map +1 -1
  98. package/src/lib/targets/codex-hooks.d.ts +12 -0
  99. package/src/lib/targets/codex-hooks.js +33 -0
  100. package/src/lib/targets/codex-hooks.js.map +1 -0
  101. package/src/lib/targets/codex.js +1 -1
  102. package/src/lib/targets/codex.js.map +1 -1
  103. package/src/lib/targets/copilot.js +1 -1
  104. package/src/lib/targets/copilot.js.map +1 -1
  105. package/src/lib/targets/cursor.js +1 -1
  106. package/src/lib/targets/cursor.js.map +1 -1
  107. package/src/lib/targets/git-hooks.d.ts +24 -0
  108. package/src/lib/targets/git-hooks.js +70 -0
  109. package/src/lib/targets/git-hooks.js.map +1 -0
  110. package/src/lib/targets/hooks-shared.d.ts +37 -0
  111. package/src/lib/targets/hooks-shared.js +95 -0
  112. package/src/lib/targets/hooks-shared.js.map +1 -0
  113. package/src/lib/targets/shared.d.ts +0 -1
  114. package/src/lib/targets/shared.js +1 -1
  115. package/src/lib/targets/shared.js.map +1 -1
  116. package/src/lib/timetrack-command.d.ts +11 -0
  117. package/src/lib/timetrack-command.js +199 -0
  118. package/src/lib/timetrack-command.js.map +1 -0
  119. package/src/lib/timetrack.d.ts +86 -0
  120. package/src/lib/timetrack.js +112 -0
  121. package/src/lib/timetrack.js.map +1 -0
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Measure real rendered geometry against the numbers in a Figma export.
3
+ *
4
+ * Copy into a scratch directory next to a `styles.css` copied out of a production build,
5
+ * replace MARKUP / WIDTHS / DESIGN / probe(), then `node measure-template.mjs`.
6
+ */
7
+ import { createRequire } from 'node:module';
8
+ import { writeFileSync } from 'node:fs';
9
+
10
+ // This runs from a scratch directory, so Playwright cannot be resolved by name — point
11
+ // createRequire at the repo root. Playwright is CommonJS; a named import fails.
12
+ const REPO_ROOT = '/absolute/path/to/repo'; // <-- change me
13
+ const { chromium } = createRequire(`${REPO_ROOT}/`)('playwright');
14
+
15
+ /** One instance of the thing under test. Keep the real component classes verbatim. */
16
+ const MARKUP = (id) => `
17
+ <div id="${id}-card" class="…">
18
+ <h4 id="${id}-title" class="…">Some realistically long label</h4>
19
+ </div>`;
20
+
21
+ /** Container widths to probe, taken from the export's frames — not from viewport breakpoints. */
22
+ const WIDTHS = [956, 640];
23
+
24
+ /** What the export says each width should produce. */
25
+ const DESIGN = {
26
+ 956: { cols: 4, cardW: 219, cardH: 60 },
27
+ 640: { cols: 3, cardW: 192, cardH: 60 },
28
+ };
29
+
30
+ // Column, never row: in a flex row the harness items shrink, and container queries then
31
+ // report results for a width the component would never actually see.
32
+ const page = (blocks) => `<!doctype html><html class="et-surface--dark"><head><meta charset="utf-8">
33
+ <link rel="stylesheet" href="./styles.css"></head>
34
+ <body class="et-surface--dark" style="margin:0;padding:24px;display:flex;flex-direction:column;gap:24px;align-items:flex-start">
35
+ ${blocks}
36
+ </body></html>`;
37
+
38
+ const block = (width) => `<div id="w${width}" class="@container" style="width:${width}px">
39
+ <div class="…">${MARKUP(`w${width}`)}</div>
40
+ </div>`;
41
+
42
+ // page.setContent() renders on about:blank, which blocks file:// subresources — the
43
+ // stylesheet would silently never load. Write a real file and navigate to it.
44
+ writeFileSync('harness.html', page(WIDTHS.map(block).join('\n')));
45
+
46
+ const browser = await chromium.launch();
47
+ const tab = await browser.newPage({ viewport: { width: 1200, height: 900 }, deviceScaleFactor: 2 });
48
+
49
+ await tab.goto(`file://${process.cwd()}/harness.html`);
50
+ await tab.waitForTimeout(300);
51
+
52
+ const measured = await tab.evaluate((widths) => {
53
+ const round = (value) => Math.round(value * 100) / 100;
54
+ const box = (id) => document.getElementById(id).getBoundingClientRect();
55
+ const font = (id) => getComputedStyle(document.getElementById(id));
56
+
57
+ return widths.map((width) => {
58
+ const card = box(`w${width}-card`);
59
+ const title = font(`w${width}-title`);
60
+
61
+ return {
62
+ width,
63
+ cardW: round(card.width),
64
+ cardH: round(card.height),
65
+ titleInset: round(box(`w${width}-title`).left - card.left),
66
+ fontSize: title.fontSize,
67
+ lineHeight: title.lineHeight,
68
+ letterSpacing: title.letterSpacing,
69
+ };
70
+ });
71
+ }, WIDTHS);
72
+
73
+ let failures = 0;
74
+
75
+ for (const row of measured) {
76
+ const want = DESIGN[row.width];
77
+ const ok = Math.abs(row.cardW - want.cardW) < 1 && Math.abs(row.cardH - want.cardH) < 0.5;
78
+
79
+ if (!ok) failures++;
80
+
81
+ console.log(`${String(row.width).padStart(4)}px | ${ok ? 'OK ' : 'BAD'} |`, row);
82
+ }
83
+
84
+ console.log(failures === 0 ? '\nALL GEOMETRY MATCHES' : `\n${failures} MISMATCHES`);
85
+
86
+ await tab.screenshot({ path: 'verify.png', fullPage: true });
87
+ await browser.close();
@@ -1,23 +1,22 @@
1
1
  ---
2
2
  name: git-commit
3
- description: How to write git commits in this repo - commitlint format (type(scope): Subject), lean messages, no trailers. Read before committing anything (e.g. the user says "commit this").
3
+ description: How to write git commits in this repo - conventional format (type(scope): Subject), lean messages, no trailers. Read before committing anything (e.g. the user says "commit this").
4
4
  kind: skill
5
5
  scope: both
6
- vars: [commitScopes]
6
+ vars: [commitScopes, commitRuleSource, commitValidation]
7
7
  ---
8
8
 
9
9
  # Git commits
10
10
 
11
- Commits are **lean** and follow the **commitlint rules** in `commitlint.config.js`
12
- (conventional commits with a required scope):
11
+ Commits are **lean** and follow {%commitRuleSource%}:
13
12
 
14
- - **Format: `type(scope): Subject`** - one line. All three parts are enforced:
13
+ - **Format: `type(scope): Subject`** - one line, all three parts required:
15
14
  - `type` ∈ `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`,
16
15
  `build`, `ci`, `chore`, `revert`
17
- - `scope` is **required**, ∈ {%commitScopes%}
16
+ - `scope` ∈ {%commitScopes%}
18
17
  - Subject is **sentence-case** ("Add the search filter", not
19
18
  "add the search filter")
20
- - When unsure a message passes, check it: `echo "<msg>" | npx commitlint`.
19
+ - {%commitValidation%}
21
20
  - Add a short body only when the change genuinely needs context that the diff
22
21
  can't convey.
23
22
  - **No trailers.** Never append `Co-Authored-By`, `Claude-Session`, or similar
@@ -0,0 +1,87 @@
1
+ ---
2
+ name: git-flow
3
+ description: How to name and base a branch in this repo, and what its merge request must target - feature, sub-feature, release, release fix and hotfix. Read BEFORE creating a branch, opening a merge request, or choosing what to branch from (e.g. the user says "start work on FIP-2177" or "open an MR").
4
+ kind: skill
5
+ scope: both
6
+ vars: [gitFlowDevelopmentBranch, gitFlowProductionBranch, gitFlowTypes, gitFlowEnforcement, gitFlowSubPrefix]
7
+ ---
8
+
9
+ # Git flow
10
+
11
+ Work is organised in two levels: a **main feature branch** per Jira Story, into which
12
+ **sub-feature branches** (one per Task) are merged after review. The main feature branch is
13
+ what gets deployed to a test environment and, once accepted, merged into
14
+ `{%gitFlowDevelopmentBranch%}`.
15
+
16
+ **Never name a branch by hand - `start` does it from the grammar:**
17
+
18
+ ```bash
19
+ npx ethlete-agents git-flow start FIP-2178 # reads the issue, names it, branches off the right base
20
+ npx ethlete-agents git-flow check # is the current branch conforming?
21
+ npx ethlete-agents git-flow explain <branch> # what the parser sees, and what it expects
22
+ npx ethlete-agents git-flow repair <branch> # rename a non-conforming one, retarget its MRs
23
+ ```
24
+
25
+ `start` prints its plan (branch, base, MR target) and asks before writing anything; add
26
+ `--dry-run` to see the plan alone. It refuses on a dirty working tree, and a Task nests under
27
+ its parent Story's branch, so that branch has to exist first.
28
+
29
+ It reads the issue through the running Timetrack app, which holds this machine's only Jira
30
+ credentials - see the `timetrack` skill. If it reports that the app is not running, ask the
31
+ user to start it. `--subject <text>` names a branch without reading Jira at all.
32
+
33
+ ## The five shapes
34
+
35
+ | Shape | Issue | Branch from | MR targets |
36
+ | --------------------------------------------------------------------------------- | ----- | ------------------------------ | ---------------------------------------------------------------- |
37
+ | `feat/FIP-2177-user-management` | Story | `{%gitFlowDevelopmentBranch%}` | `{%gitFlowDevelopmentBranch%}` |
38
+ | `{%gitFlowSubPrefix%}/feat/FIP-2177-user-management/FIP-2178-user-password-reset` | Task | the main feature branch | the main feature branch |
39
+ | `release/2026.04.28` | - | `{%gitFlowDevelopmentBranch%}` | `{%gitFlowDevelopmentBranch%}` and `{%gitFlowProductionBranch%}` |
40
+ | `{%gitFlowSubPrefix%}/release/2026.04.28/FIP-2222-button-not-visible` | Bug | the release branch | the release branch |
41
+ | `hotfix/FIP-2799-password-recovery-broken` | Bug | `{%gitFlowProductionBranch%}` | `{%gitFlowProductionBranch%}` |
42
+
43
+ - The **type** is one of {%gitFlowTypes%}, and a nested branch carries its parent's **full**
44
+ name - that path is what makes the parent machine-readable.
45
+ - The **key** is the Jira issue, uppercase, immediately after the type. The **subject** is the
46
+ Story's subject meta field in kebab-case, not a paraphrase of the summary.
47
+ - A branch with no key still works, but nothing can attribute it to an issue. Add the key.
48
+
49
+ ### Why a nested branch starts with `{%gitFlowSubPrefix%}/`
50
+
51
+ Git refuses a ref that is both a branch and a directory of branches. So
52
+ `feat/FIP-2177-user-management/FIP-2178-user-password-reset` **cannot exist** while
53
+ `feat/FIP-2177-user-management` does - git rejects it locally and the push comes back as
54
+ `refname conflict`. The prefix moves the nested tree out of the way and keeps the parent's
55
+ full path inside the child's name, so the MR target is still readable off the name.
56
+
57
+ Do not "fix" a name by dropping the prefix; the branch it produces cannot be created.
58
+
59
+ ## Rules that are not about naming
60
+
61
+ - **Never rebase a shared branch.** A main feature branch is published and other people's
62
+ sub-features are based on it - bring it up to date by **merging**
63
+ `{%gitFlowDevelopmentBranch%}` into it. Rebase only a local branch you have not pushed.
64
+ - **A sub-feature merges into its parent, never straight into
65
+ `{%gitFlowDevelopmentBranch%}`** - that is the whole point of the two levels, since the
66
+ parent is what gets tested as a unit.
67
+ - **Delete the source branch on merge** (the checkbox in the merge request) for sub-features,
68
+ release fixes and merged main features.
69
+ - **A hotfix branches off `{%gitFlowProductionBranch%}`** and, after rollout, that branch is
70
+ merged back into `{%gitFlowDevelopmentBranch%}` so the two do not drift.
71
+ - **Never push directly to `{%gitFlowDevelopmentBranch%}` or
72
+ `{%gitFlowProductionBranch%}`.** Open a merge request; it needs another developer's review.
73
+
74
+ ## Enforcement is `{%gitFlowEnforcement%}`
75
+
76
+ In `advisory` mode every rule reports and nothing blocks - the older naming shapes (`feature/`
77
+ instead of `feat/`, a lowercase key, no key at all, a `dev-*` integration branch) are accepted
78
+ on purpose while the team adapts. Report the suggestion, do not "fix" someone's existing
79
+ branch unasked - `repair` is how a rename happens, and only when asked for.
80
+
81
+ `dev-*` is the **old spelling of a main feature branch**, not a stray name: sub-features
82
+ legitimately target it. Leave a live one alone.
83
+
84
+ ## Commit messages are a separate thing
85
+
86
+ Commits stay conventional (`feat(platform): Prefer a player's common name`) and carry **no
87
+ issue key** - the branch already has it. See {%skill:git-commit%}.
@@ -15,6 +15,10 @@ continue in a fresh session. Two modes:
15
15
  - **save** ("handoff", "wrap up") - write a handoff file.
16
16
  - **resume** ("continue from the handoff") - read one and continue the work.
17
17
 
18
+ This skill is installed as `ethlete-handoff`, so under Claude Code the commands
19
+ are `/ethlete-handoff` and `/ethlete-handoff resume [name]` - there is no
20
+ `/handoff`. Name them that way whenever you tell the user what to run.
21
+
18
22
  Handoff files live in `{%handoffDir%}/` (gitignored - they are personal,
19
23
  ephemeral working state, not team docs).
20
24
 
@@ -16,17 +16,17 @@ paged queries, bearer auth, GraphQL, and a socket.io realtime client.
16
16
  non-trivial query work.** This guide is the index plus the load-bearing facts, so you
17
17
  don't re-derive them from source.
18
18
 
19
- | Page | Covers |
20
- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
21
- | {%docsBaseUrl%}/query/ | Overview + the two-generations note |
22
- | {%docsBaseUrl%}/query/queries | **Start here** - client, creators, the query object's signals, auto-execution |
23
- | {%docsBaseUrl%}/query/features | `withArgs`, `withPolling`, `withAutoRefresh`, side-effect handlers |
24
- | {%docsBaseUrl%}/query/http | REST creators, typing requests, response transforms, upload progress |
25
- | {%docsBaseUrl%}/query/auth | Bearer auth: login/refresh, auto token refresh, multi-tab sync |
26
- | {%docsBaseUrl%}/query/caching · `/stacks` · `/errors` · `/gql` · `/ws` | Caching/dedup, pagination, error/retry, GraphQL, WebSockets |
27
- | {%docsBaseUrl%}/query/multi-tab | Opt-in cross-tab sync: shared responses, per-key polling election, mutation fan-out |
28
- | {%docsBaseUrl%}/query/query-forms | Router-synced filter/search forms |
29
- | {%docsBaseUrl%}/query/legacy | The maintenance-mode `V2QueryClient` |
19
+ | Page | Covers |
20
+ | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
21
+ | {%docsBaseUrl%}/query/ | Overview + the two-generations note |
22
+ | {%docsBaseUrl%}/query/queries | **Start here** - client, creators, the query object's signals, auto-execution |
23
+ | {%docsBaseUrl%}/query/features | `withArgs`, `withPolling`, `withLongPolling`, `withAutoRefresh`, side-effect handlers |
24
+ | {%docsBaseUrl%}/query/http | REST creators, typing requests, response transforms, upload progress |
25
+ | {%docsBaseUrl%}/query/auth | Bearer auth: login/refresh, auto token refresh, multi-tab sync |
26
+ | {%docsBaseUrl%}/query/caching · `/stacks` · `/errors` · `/gql` · `/ws` | Caching/dedup, pagination, error/retry, GraphQL, WebSockets |
27
+ | {%docsBaseUrl%}/query/multi-tab | Opt-in cross-tab sync: shared responses, per-key polling election, mutation fan-out |
28
+ | {%docsBaseUrl%}/query/query-forms | Router-synced filter/search forms |
29
+ | {%docsBaseUrl%}/query/legacy | The maintenance-mode `V2QueryClient` |
30
30
 
31
31
  ## Two generations - use the current one
32
32
 
@@ -77,9 +77,19 @@ raw `toObservable`). It emits `null` first - `pipe(filter(r => r !== null))`.
77
77
  - **`withArgs(() => ({ pathParams, queryParams, body }))`** - runs like a `computed`;
78
78
  re-runs when a signal it reads changes and re-executes the query. This is how you
79
79
  drive **search-as-you-type**: back it with a search signal
80
- (`withArgs(() => ({ queryParams: { search: this.search() } }))`). Return
81
- `CLEAR_QUERY_ARGS` to reset args to `null` (pauses polling/auto-refresh).
80
+ (`withArgs(() => ({ queryParams: { search: this.search() } }))`). Return `null`
81
+ to park the query - args reset to `null`, pausing polling/auto-refresh.
82
+ - **Prefer `withArgs` over passing `args` to `execute()`.** Args declared on the query
83
+ stay reactive: a `GET` re-executes itself when they change, and `withPolling` /
84
+ `withAutoRefresh` restart off the same signal - none of which happens for args handed
85
+ to `execute()`. A function route additionally throws without it. With `withArgs` in
86
+ place a mutation is just `.execute()`, which reuses the current `args()`. Reserve
87
+ `execute({ args })` for a one-off payload no signal holds (a form submit).
82
88
  - `withPolling({ interval })`, `withAutoRefresh({ onSignalChanges: [...] })`.
89
+ - **`withLongPolling({ nextArgs })`** for a completion-driven chain instead of an interval: each
90
+ round starts once the previous settled, with args (a cursor) derived from its response. `nextArgs`
91
+ returning `null` ends the chain. Not `withPolling` with a small interval - and the two throw when
92
+ combined.
83
93
  - Side-effects: `withSuccessHandling`, `withErrorHandling`, `withLogging`.
84
94
 
85
95
  There is no built-in debounce operator - dedup/caching handles repeated identical
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: rxjs-signals
3
- description: How to choose between signals and RxJS, and use each correctly - synchronous state vs asynchronous work, unsubscribing, and avoiding RxJS inside effects/computeds. Read when adding reactive state, wiring up an observable, or deciding whether something should be a signal or a stream. Part of the Ethlete styleguide (judgment beyond what lint enforces).
3
+ description: How to choose between signals and RxJS, and use each correctly - synchronous state vs asynchronous work, unsubscribing, and avoiding RxJS inside effects/computeds. Read when adding reactive state, wiring up an observable, or deciding whether something should be a signal or a stream.
4
4
  kind: skill
5
5
  scope: both
6
6
  requires: ['@ethlete/core']
@@ -43,8 +43,15 @@ Component domains under `/components/`:
43
43
  `tabs` `text-inputs` `time-picker` `toggletip` `tooltip`
44
44
 
45
45
  So the table guide is `{%docsBaseUrl%}/components/table`, the menu guide
46
- `{%docsBaseUrl%}/components/menu`, and so on. When a name isn't in that list, start at
47
- `{%docsBaseUrl%}/components/` and follow the sidebar rather than guessing a URL.
46
+ `{%docsBaseUrl%}/components/menu`, and so on.
47
+
48
+ That list is a snapshot. The site itself is machine-readable, so fetch rather than guess:
49
+
50
+ - `{%docsBaseUrl%}/llms.txt` - every page's title and path as of the last deploy. Use it
51
+ when a name isn't in the list above, or to check the list hasn't drifted.
52
+ - Append `.md` to any page URL - `{%docsBaseUrl%}/components/button.md` - to get raw
53
+ markdown instead of the rendered page. Prefer this over `/llms-full.txt`, which is the
54
+ entire site in one ~1 MB file.
48
55
 
49
56
  ## How to use them
50
57
 
@@ -68,3 +75,4 @@ So the table guide is `{%docsBaseUrl%}/components/table`, the menu guide
68
75
 
69
76
  - Data fetching has its own guide: {%skill:query%}
70
77
  - Theming tokens and how to register themes: {%skill:theming%}
78
+ - When the docs cannot answer it, read the SDK source: {%skill:sdk-source%}
@@ -0,0 +1,115 @@
1
+ ---
2
+ name: sdk-local-build
3
+ description: Build the @ethlete SDK from a local ethlete-sdk checkout and install it into this repo through a `file:` dependency, so an unreleased SDK change can be tested against this app before it is published - and reverted cleanly afterwards. Read whenever an SDK-side fix needs verifying here, or when package.json already points at a local build.
4
+ kind: skill
5
+ scope: consumer
6
+ ---
7
+
8
+ # Testing an unreleased SDK build in this repo
9
+
10
+ Swapping an `@ethlete/*` dependency for a locally built one lets you verify an SDK
11
+ change against this app before it ships. It is a **temporary, local-only** state: the
12
+ `file:` specifier and the lockfile entry it produces must never be committed.
13
+
14
+ Prefer a published prerelease when one exists - installing `@ethlete/components@next`
15
+ is faster and reproducible for the whole team. Use a local build when the change is not
16
+ published yet, or when you are iterating on it.
17
+
18
+ ## 1. Prerequisites
19
+
20
+ - The checkout path comes from `sdkSourcePath` in `ethlete-agents.config.local.json`;
21
+ {%skill:sdk-source%} covers resolving it and what to check before trusting it.
22
+ - The checkout has its dependencies installed (`yarn install` in the checkout root - the
23
+ SDK repo is a Yarn 4 workspace).
24
+ - Check which branch it is on before building - unless the user said otherwise, that
25
+ should be `next`, up to date with `origin/next`. Building `main` when your app runs
26
+ `-next` prereleases swaps in a completely different API surface, and building a stale
27
+ `next` rebuilds a bug that is already fixed upstream. Ask before changing its branch.
28
+
29
+ ## 2. Build the libraries you changed
30
+
31
+ From the **checkout root**, one build per `@ethlete/*` package whose source you touched:
32
+
33
+ ```bash
34
+ npx nx build components # writes dist/libs/components
35
+ npx nx build query # writes dist/libs/query
36
+ ```
37
+
38
+ Each build also builds the libs it depends on (`types` → `core` → `query` →
39
+ `components`), so a single command is enough to produce a consistent set. Only the
40
+ packages you actually changed need to be installed here; leave the rest on their
41
+ published versions.
42
+
43
+ If a build stalls trying to reach Nx Cloud, re-run it with `NX_NO_CLOUD=true`.
44
+
45
+ ## 3. Point this repo at the build
46
+
47
+ Edit the version specifiers in `package.json` (the one declaring the dependency - in a
48
+ workspace that is the workspace package, not necessarily the root):
49
+
50
+ ```json
51
+ {
52
+ "dependencies": {
53
+ "@ethlete/components": "file:../ethlete-sdk/dist/libs/components"
54
+ }
55
+ }
56
+ ```
57
+
58
+ The path is resolved relative to that `package.json`; an absolute path works too. Then
59
+ install:
60
+
61
+ ```bash
62
+ yarn install
63
+ ```
64
+
65
+ Expect peer-dependency warnings - the built package pins peers to the SDK's own Angular
66
+ version. They are warnings, not failures; a real version conflict shows up as a build
67
+ error, and means the checkout is on the wrong branch.
68
+
69
+ ## 4. Confirm the local build is really what got installed
70
+
71
+ `yarn install` is the only thing that copies the build into `node_modules`, so verifying
72
+ is not optional - a stale package looks exactly like a change that did not work:
73
+
74
+ ```bash
75
+ grep -m1 '"version"' node_modules/@ethlete/components/package.json
76
+ rg -n "<a symbol from your change>" node_modules/@ethlete/components/fesm2022/
77
+ ```
78
+
79
+ Then **restart the dev server**. Bundlers pre-bundle dependencies and will keep serving
80
+ the old copy; if the change still does not show up, delete `.angular/cache` (and
81
+ `node_modules/.vite` if present) and start it again.
82
+
83
+ ## 5. Iterating
84
+
85
+ Every SDK edit needs the full loop - there is no watch mode across the boundary:
86
+
87
+ 1. rebuild in the checkout (`npx nx build <lib>`)
88
+ 2. `yarn install` here
89
+ 3. restart the dev server
90
+
91
+ Yarn 4 re-copies a `file:` dependency whenever its contents change, so step 2 does pick
92
+ up the rebuild. It also rewrites that package's `resolution` hash in `yarn.lock` on
93
+ every rebuild - which is one more reason the lockfile must not be committed in this
94
+ state. (With npm, `file:` symlinks instead of copying, so a rebuild is picked up without
95
+ reinstalling; the restart in step 3 is still required.)
96
+
97
+ ## 6. Clean up when you are done
98
+
99
+ Leaving a `file:` dependency behind breaks every other checkout and CI, because the path
100
+ does not exist there. Restore it as part of the same task, not later:
101
+
102
+ ```bash
103
+ git checkout package.json # or hand-restore the original version specifier
104
+ yarn install
105
+ git status --short # package.json and yarn.lock must both be clean
106
+ ```
107
+
108
+ Never commit a `file:` specifier or the lockfile it produced. If the verified fix is
109
+ still unreleased, say what has to be published (which lib, which version) instead of
110
+ shipping a local path.
111
+
112
+ ## Related
113
+
114
+ - Finding and reading the checkout: {%skill:sdk-source%}
115
+ - What the published packages document: {%skill:sdk-docs%}
@@ -0,0 +1,133 @@
1
+ ---
2
+ name: sdk-source
3
+ description: How to read the @ethlete SDK's own source from a local ethlete-sdk checkout when the docs and the installed types are not enough. Read when you need an implementation detail, the exact behaviour behind a bug, or an API the docs do not cover - and never edit that checkout as part of work in this repo.
4
+ kind: skill
5
+ scope: consumer
6
+ vars: [docsBaseUrl]
7
+ ---
8
+
9
+ # Reading the @ethlete SDK source
10
+
11
+ The SDK is developed in a separate repository (`ethlete-sdk`). Most questions are
12
+ answered faster and more reliably by the documentation - read {%skill:sdk-docs%}
13
+ first. Reach for the source when the docs genuinely cannot answer the question:
14
+
15
+ - a behaviour looks like an SDK bug and you need to see what the code actually does
16
+ - you need an implementation detail the guides omit (event order, internal defaults,
17
+ which host directive writes which attribute)
18
+ - the installed version is ahead of - or behind - the published docs and you have to
19
+ confirm what the code in _this_ version does
20
+ - you are about to report or fix something in the SDK itself
21
+
22
+ ## 1. Resolve the checkout
23
+
24
+ The path is per machine, so it lives in the gitignored `ethlete-agents.config.local.json`
25
+ at the repo root:
26
+
27
+ ```json
28
+ {
29
+ "sdkSourcePath": "/absolute/path/to/ethlete-sdk"
30
+ }
31
+ ```
32
+
33
+ Read that file before searching anywhere. A relative path is resolved from the repo root.
34
+
35
+ If the file or the key is missing, **do not guess a path** and do not clone the
36
+ repository. Fall back to the installed package - `node_modules/@ethlete/<lib>/types/`
37
+ holds the full `.d.ts` surface of exactly the version this repo runs - and to
38
+ {%docsBaseUrl%}. Then tell the user a local checkout would help, and offer the snippet
39
+ above (the file is gitignored, so adding it changes nothing for anyone else).
40
+
41
+ ## 2. Check the branch before you read anything
42
+
43
+ A checkout sits on whatever branch the developer left it on, so the first command you
44
+ run against it is the one that tells you what you are looking at:
45
+
46
+ ```bash
47
+ git -C <sdkSourcePath> fetch --quiet # read-only, safe
48
+ git -C <sdkSourcePath> status -sb # branch, ahead/behind, dirty files
49
+ ```
50
+
51
+ **Unless the user says otherwise, the expected state is `next`, up to date with
52
+ `origin/next`.** That is the branch the SDK develops on, and the one the `-next`
53
+ prereleases are cut from. Anything else and you are reading a different SDK than the
54
+ one you are about to describe.
55
+
56
+ When it is not in that state, **say so and ask** - never switch, pull, stash or reset
57
+ it yourself. It is someone's working tree, and the read-only rule below applies:
58
+
59
+ - **On another branch** - name it and ask whether to read it as-is or whether they want
60
+ `next`. A feature branch may be exactly what you were sent to look at.
61
+ - **Behind `origin/next`** - report how far. The code you would quote may already be
62
+ fixed upstream, so read the missing commits before calling anything a bug:
63
+ `git -C <sdkSourcePath> log --oneline HEAD..origin/next`.
64
+ - **Dirty** - it may contain someone's work in progress. Say so rather than quoting it
65
+ as SDK behaviour.
66
+
67
+ ## 3. Check the checkout matches what is installed
68
+
69
+ Even on a clean `next`, the checkout can be months of work ahead of the installed
70
+ package - or behind it:
71
+
72
+ ```bash
73
+ grep '"@ethlete/' package.json # what this repo runs
74
+ grep -m1 '"version"' <sdkSourcePath>/libs/<lib>/package.json # what the checkout is at
75
+ ```
76
+
77
+ Rules when they differ:
78
+
79
+ - **The installed package wins** for anything about how this repo behaves today. The
80
+ `.d.ts` in `node_modules` is the truth about the API you are calling.
81
+ - Source that is ahead describes an **unreleased** API. Never write consumer code
82
+ against it, and never assume it is available - say what release it needs.
83
+
84
+ ## 4. Where things live
85
+
86
+ Paths are relative to the checkout root:
87
+
88
+ | Path | What is in it |
89
+ | ----------------------------------- | --------------------------------------------------------------------------- |
90
+ | `libs/components/src/lib/<domain>/` | The active UI library, one folder per domain (`button`, `menu`, `table`, …) |
91
+ | `libs/core/src/lib/` | Framework primitives: directives, signal utils, overlay runtime, theming |
92
+ | `libs/query/src/lib/` | Data fetching: `http`, `gql`, `ws`, auth, query-form |
93
+ | `libs/types/src/lib/` | Shared types |
94
+ | `libs/cdk/` | The predecessor UI toolkit, maintenance mode - only for code still on it |
95
+ | `libs/eslint-plugin/src/` | The lint rules, including the message text explaining each one |
96
+ | `apps/docs/` | The markdown behind {%docsBaseUrl%} |
97
+ | `apps/storybook/` | The Storybook app - stories also live next to each component |
98
+
99
+ Inside a component domain: `<name>.component.ts` with its `.css` next to it,
100
+ `<name>.imports.ts` (the imports array to spread into a consumer component),
101
+ `headless/` for the unstyled directives, `stories/` for the Storybook stories, and
102
+ `index.ts` as the barrel. The lib's public surface is `libs/<lib>/src/index.ts` -
103
+ anything not re-exported from there is internal, whatever it looks like.
104
+
105
+ ## 5. Search it, don't read it whole
106
+
107
+ ```bash
108
+ rg -n "etButton" <sdkSourcePath>/libs/components/src --glob '!*.spec.ts' # a selector
109
+ rg -n "export const OVERLAY" <sdkSourcePath>/libs/core/src # an export
110
+ rg -n "menu" <sdkSourcePath>/apps/docs/components # the guide source
111
+ ```
112
+
113
+ Specs are the cheapest description of intended behaviour - `<name>.component.spec.ts`
114
+ next to a component usually answers "is this supposed to happen?" faster than the
115
+ implementation does.
116
+
117
+ ## 6. The checkout is read-only from here
118
+
119
+ It is a different repository with its own branch, lint, docs and changeset workflow.
120
+ Never edit it while working on a task in this repo - that covers its git state too, so
121
+ no `checkout`, `pull`, `stash` or `reset` without the user asking for it (`fetch` is
122
+ fine). Never copy its internals into consumer code either - a private helper is not a
123
+ supported API and disappears without a major version (the same goes for anything under
124
+ a `subtle` namespace).
125
+
126
+ When the fix belongs in the SDK, say so and describe it precisely: file, symbol, and
127
+ the behaviour it should have. If the user wants that fix verified against this app
128
+ before it ships, that is {%skill:sdk-local-build%}.
129
+
130
+ ## Related
131
+
132
+ - Docs and Storybook, which come first: {%skill:sdk-docs%}
133
+ - Testing an unreleased SDK build here: {%skill:sdk-local-build%}
@@ -37,7 +37,7 @@ Run your lint task with `--fix` - the rules below are enforced (and mostly auto-
37
37
  | Max two function parameters | `max-params` |
38
38
  | No `import type` / inline `type` specifiers | `ethlete/no-type-only-import` |
39
39
  | Generic params `T`-prefixed (`TValue`), never bare `T` | `@typescript-eslint/naming-convention` |
40
- | No `async`/`await` - use RxJS | `no-restricted-syntax` |
40
+ | No `async`/`await` - use RxJS | `ethlete/no-async-await` |
41
41
  | Arrow fns standalone; methods in classes; no arrow-fn class props; no `function` keyword | `no-restricted-syntax` |
42
42
  | Blank line before `return` in multi-line guard clauses | `ethlete/guard-return-newline` |
43
43
  | No trivially-inferable explicit return types | `ethlete/no-trivial-return-type` |
@@ -463,7 +463,7 @@ settings-form/
463
463
  - **Do not** create a changeset for irrelevant changes (e.g., formatting, comments, internal refactoring).
464
464
  - **Do not** create a changeset for fixes to features that have not yet been released.
465
465
  - **Do not** include multiple changes in a single changeset. Each changeset should contain only one change.
466
- - **Always** start a changeset with at least one sentence describing the change. Optional follow-up markdown can be added after the initial sentence.
466
+ - **A changeset note is a TL;DR: one sentence, two at most, under 40 words.** Never a second paragraph, no matter how much work the change took. It is the line a consumer skims to decide whether the release affects them - not a summary of your work. Mechanism, API inventories, caveats and rationale belong in the docs or the commit body, never here.
467
467
  - Create changesets for dependency updates if they are relevant to the project (e.g., major version updates).
468
468
  - Write changesets in the imperative mood. For example:
469
469
  - Add button component
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: theming
3
- description: The two runtime theming systems in @ethlete/core - surface theming (elevation-aware neutrals) and color theming (semantic accent palettes) - how to register them in an app and how components must consume them. Read BEFORE writing or reviewing CSS that involves any color, background, border, or interaction state, and when wiring theme context across overlay/portal boundaries.
3
+ description: The two runtime theming systems in @ethlete/core - surface theming (elevation-aware neutrals) and color theming (semantic accent palettes). Read BEFORE writing or reviewing CSS involving color, background, border, or interaction state, and when wiring theme context across overlay/portal boundaries.
4
4
  kind: skill
5
5
  scope: consumer
6
6
  requires: ['@ethlete/core']