@ethlete/agent-rules 0.1.0-next.4 → 0.1.0-next.6

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 (76) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +192 -11
  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 +259 -97
  6. package/content/skills/figma-export/SKILL.md +193 -0
  7. package/content/skills/figma-export/dump-figma-layers.py +83 -0
  8. package/content/skills/figma-export/dump-figma-svg.py +235 -0
  9. package/content/skills/figma-export/measure-template.mjs +87 -0
  10. package/content/skills/git-flow/SKILL.md +83 -0
  11. package/content/skills/handoff/SKILL.md +4 -0
  12. package/content/skills/query/SKILL.md +23 -13
  13. package/package.json +12 -1
  14. package/src/index.js +10 -5
  15. package/src/index.js.map +1 -1
  16. package/src/lib/config.d.ts +30 -2
  17. package/src/lib/config.js +20 -3
  18. package/src/lib/config.js.map +1 -1
  19. package/src/lib/git-flow/build.d.ts +35 -0
  20. package/src/lib/git-flow/build.js +24 -0
  21. package/src/lib/git-flow/build.js.map +1 -0
  22. package/src/lib/git-flow/config.d.ts +59 -0
  23. package/src/lib/git-flow/config.js +50 -0
  24. package/src/lib/git-flow/config.js.map +1 -0
  25. package/src/lib/git-flow/index.d.ts +5 -0
  26. package/src/lib/git-flow/index.js +9 -0
  27. package/src/lib/git-flow/index.js.map +1 -0
  28. package/src/lib/git-flow/parse.d.ts +49 -0
  29. package/src/lib/git-flow/parse.js +274 -0
  30. package/src/lib/git-flow/parse.js.map +1 -0
  31. package/src/lib/git-flow/start.d.ts +22 -0
  32. package/src/lib/git-flow/start.js +24 -0
  33. package/src/lib/git-flow/start.js.map +1 -0
  34. package/src/lib/git-flow/validate.d.ts +34 -0
  35. package/src/lib/git-flow/validate.js +72 -0
  36. package/src/lib/git-flow/validate.js.map +1 -0
  37. package/src/lib/git-flow-command.d.ts +4 -0
  38. package/src/lib/git-flow-command.js +157 -0
  39. package/src/lib/git-flow-command.js.map +1 -0
  40. package/src/lib/git-flow-repair.d.ts +17 -0
  41. package/src/lib/git-flow-repair.js +150 -0
  42. package/src/lib/git-flow-repair.js.map +1 -0
  43. package/src/lib/git-flow-start.d.ts +20 -0
  44. package/src/lib/git-flow-start.js +147 -0
  45. package/src/lib/git-flow-start.js.map +1 -0
  46. package/src/lib/git.d.ts +27 -0
  47. package/src/lib/git.js +49 -0
  48. package/src/lib/git.js.map +1 -0
  49. package/src/lib/gitlab.d.ts +35 -0
  50. package/src/lib/gitlab.js +98 -0
  51. package/src/lib/gitlab.js.map +1 -0
  52. package/src/lib/jira.d.ts +28 -0
  53. package/src/lib/jira.js +83 -0
  54. package/src/lib/jira.js.map +1 -0
  55. package/src/lib/owned-paths.js +19 -1
  56. package/src/lib/owned-paths.js.map +1 -1
  57. package/src/lib/plan.js +29 -5
  58. package/src/lib/plan.js.map +1 -1
  59. package/src/lib/prompt.d.ts +8 -0
  60. package/src/lib/prompt.js +27 -0
  61. package/src/lib/prompt.js.map +1 -0
  62. package/src/lib/render.d.ts +12 -1
  63. package/src/lib/render.js +23 -6
  64. package/src/lib/render.js.map +1 -1
  65. package/src/lib/targets/claude-hooks.d.ts +1 -23
  66. package/src/lib/targets/claude-hooks.js +15 -84
  67. package/src/lib/targets/claude-hooks.js.map +1 -1
  68. package/src/lib/targets/codex-hooks.d.ts +12 -0
  69. package/src/lib/targets/codex-hooks.js +33 -0
  70. package/src/lib/targets/codex-hooks.js.map +1 -0
  71. package/src/lib/targets/git-hooks.d.ts +25 -0
  72. package/src/lib/targets/git-hooks.js +70 -0
  73. package/src/lib/targets/git-hooks.js.map +1 -0
  74. package/src/lib/targets/hooks-shared.d.ts +38 -0
  75. package/src/lib/targets/hooks-shared.js +95 -0
  76. package/src/lib/targets/hooks-shared.js.map +1 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # @ethlete/agent-rules
2
2
 
3
+ ## 0.1.0-next.6
4
+
5
+ ### Minor Changes
6
+
7
+ - [#3055](https://github.com/ethlete-io/ethdk/pull/3055) [`86cd6e9`](https://github.com/ethlete-io/ethdk/commit/86cd6e97072d38436650ddc94a15527da34fa946) Thanks [@github-actions](https://github.com/apps/github-actions)! - Add the git-flow branch convention: a `git-flow` skill, `ethlete-agents git-flow start|check|repair|explain`, opt-in git hooks, and the parser at `@ethlete/agent-rules/git-flow`.
8
+
9
+ ### Patch Changes
10
+
11
+ - [#3055](https://github.com/ethlete-io/ethdk/pull/3055) [`01b0797`](https://github.com/ethlete-io/ethdk/commit/01b0797d80df17e740419402034bc3eec739daaf) Thanks [@github-actions](https://github.com/apps/github-actions)! - The context-warning hook now points at `/ethlete-handoff`, the name the generated skill actually has, instead of a `/handoff` command that does not exist in a consumer repo.
12
+
13
+ ## 0.1.0-next.5
14
+
15
+ ### Minor Changes
16
+
17
+ - [#3048](https://github.com/ethlete-io/ethdk/pull/3048) [`6e19999`](https://github.com/ethlete-io/ethdk/commit/6e199997f51b88aaa1860a56a6b96be057ba1205) Thanks [@github-actions](https://github.com/apps/github-actions)! - The `context-warning` hook now runs under Codex as well as Claude Code, registered in
18
+ `.codex/hooks.json` whenever the `codex` target is on.
19
+
20
+ - [#3049](https://github.com/ethlete-io/ethdk/pull/3049) [`a606dda`](https://github.com/ethlete-io/ethdk/commit/a606dda0695ac8cc4370816bf3ff1c0814436091) Thanks [@TomTomB](https://github.com/TomTomB)! - Add the `figma-export` skill, guiding agents through reconciling a component against a Figma "copy as CSS" export.
21
+
22
+ ### Patch Changes
23
+
24
+ - [#3048](https://github.com/ethlete-io/ethdk/pull/3048) [`fc56189`](https://github.com/ethlete-io/ethdk/commit/fc56189450a45f2b5819d40945a71205a6d67ba0) Thanks [@github-actions](https://github.com/apps/github-actions)! - The `query` skill now says to prefer `withArgs` over passing `args` to `execute()`, and when the imperative form is still right.
25
+
26
+ - [#3048](https://github.com/ethlete-io/ethdk/pull/3048) [`a16cc69`](https://github.com/ethlete-io/ethdk/commit/a16cc698a3cd3f14d0419d8f9710929fb41b4711) Thanks [@github-actions](https://github.com/apps/github-actions)! - Figma export skill: read an SVG frame as well as the CSS dump, with a `dump-figma-svg.py`
27
+ that prints its box tree and measures the auto-layout gaps.
28
+
3
29
  ## 0.1.0-next.4
4
30
 
5
31
  ### Minor Changes
package/README.md CHANGED
@@ -113,10 +113,122 @@ Prettier rewrites them and `check` then reports drift on every run:
113
113
  Content that declares `requires` is only emitted when those packages are installed, so
114
114
  a repo without `@ethlete/query` never sees the query guide.
115
115
 
116
+ ## Git flow
117
+
118
+ The branch convention lives in the same config, as one machine-readable grammar that the
119
+ CLI, a git hook, a CI job and `@ethlete/timetrack` all read:
120
+
121
+ ```json
122
+ {
123
+ "gitFlow": {
124
+ "keyPrefixes": ["FIP"],
125
+ "baseBranches": { "development": "next", "production": "main" }
126
+ }
127
+ }
128
+ ```
129
+
130
+ ```bash
131
+ npx ethlete-agents git-flow start FIP-2177 # name it and branch off the right base
132
+ npx ethlete-agents git-flow check # the current branch
133
+ npx ethlete-agents git-flow check "$SOURCE" --target "$TARGET"
134
+ npx ethlete-agents git-flow check --all # adoption report
135
+ npx ethlete-agents git-flow repair dev-game-codes --key FIP-2900
136
+ npx ethlete-agents git-flow explain feat/FIP-2177-user-management
137
+ ```
138
+
139
+ The shapes:
140
+
141
+ | Shape | Branch from | Merges into |
142
+ | ------------------------------------------ | ----------------------- | ------------------------------ |
143
+ | `feat/<KEY>-<subject>` | development | development |
144
+ | `sub/feat/<KEY>-<subject>/<KEY>-<subject>` | the main feature branch | the main feature branch |
145
+ | `release/<YYYY.MM.DD>` | development | development **and** production |
146
+ | `sub/release/<YYYY.MM.DD>/<KEY>-<subject>` | the release branch | the release branch |
147
+ | `hotfix/<KEY>-<subject>` | production | production |
148
+
149
+ **Why nested branches carry a `sub/` prefix.** Git refuses a ref that is both a branch and
150
+ a directory of branches, so `feat/FIP-2177-user-management/FIP-2178-reset` cannot exist
151
+ while `feat/FIP-2177-user-management` does - the push is rejected with `refname conflict`.
152
+ The prefix moves the nested tree out of the way while keeping the parent's full path inside
153
+ the child's name, so the merge request target is still derivable from the name alone. The
154
+ unprefixed spelling still parses, reports why it cannot exist, and `repair` moves it.
155
+ Configurable as `subPrefix`.
156
+
157
+ - **`enforcement`** - `"advisory"` (default) reports everything and blocks nothing, so a
158
+ repo can adopt the convention before it gates on it. `"gated"` applies each rule's
159
+ `severity`. A direct push to a base branch is blocked in both modes, and
160
+ `wrong-mr-target` can be raised to `"error"` on its own without ending the naming
161
+ grace period.
162
+ - **`keyPrefixes`** - the project's issue prefixes. Leave it empty and anything shaped
163
+ like `keyPattern` counts, which reads `chore/angular-22` as issue `ANGULAR-22`.
164
+ - **`severity`** - per rule: `unknown-type`, `missing-key`, `key-case`,
165
+ `missing-subject`, `type-alias`, `deprecated-prefix`, `release-date`,
166
+ `wrong-mr-target`, `protected-push`.
167
+ - **`deprecatedShapes`** - legacy spellings that still classify correctly and only earn a
168
+ rename suggestion. `dev-*` ships as the old spelling of a main feature branch.
169
+
170
+ The grammar is also importable on its own - `@ethlete/agent-rules/git-flow` has no
171
+ dependencies and touches no Node built-ins, so it runs in a browser:
172
+
173
+ ```ts
174
+ import { parseBranch, planStart, resolveGitFlowConfig } from '@ethlete/agent-rules/git-flow';
175
+
176
+ const { storyKey, taskKey, findings } = parseBranch({ branch, config: resolveGitFlowConfig() });
177
+ ```
178
+
179
+ ### `start` - the prospective flow
180
+
181
+ `git-flow start <KEY>` reads the issue from Jira, computes the name from the grammar and
182
+ creates the branch off the correct base. It prints the plan first and asks before writing;
183
+ `--dry-run` stops after the plan and `--yes` skips the question. It refuses on a dirty
184
+ working tree, when the branch already exists, and when the base branch is nowhere to be
185
+ found.
186
+
187
+ A Task with a parent Story nests under that Story's feature branch, which therefore has to
188
+ exist already - `start` says so rather than inventing a parent. `--of <branch>` picks the
189
+ parent explicitly, `--hotfix` branches off production, `--release <date>` makes a release
190
+ branch, and `--subject <text>` skips Jira entirely.
191
+
192
+ Jira needs a host, an email and an API token. Only the host belongs in the committed
193
+ config; the two secrets come from `JIRA_EMAIL` / `JIRA_API_TOKEN` or from the gitignored
194
+ local config.
195
+
196
+ ```json
197
+ {
198
+ "jira": {
199
+ "host": "https://your-team.atlassian.net",
200
+ "subjectField": "customfield_10050",
201
+ "typeByIssueType": { "Bug": "fix" }
202
+ }
203
+ }
204
+ ```
205
+
206
+ - **`subjectField`** - the field holding a Story's branch subject. Without it the summary
207
+ is slugified, which is a paraphrase rather than the agreed subject.
208
+ - **`typeByIssueType`** - the branch type per Jira issue type; anything unlisted becomes
209
+ `feat`. `--type` overrides it per call.
210
+
211
+ ### `repair` - renaming a branch that does not conform
212
+
213
+ `git-flow repair [ref]` derives the conforming name (`--key FIP-2900` when the old name
214
+ carries no issue key, `--to <branch>` to override), renames the branch locally and on the
215
+ remote, and retargets the open merge requests aimed at it through the GitLab API.
216
+ `GITLAB_TOKEN` needs the `api` scope.
217
+
218
+ Everything is checked before the first mutation, and it refuses rather than half-finishing:
219
+
220
+ - An open merge request whose **source** is the branch blocks the repair. GitLab cannot
221
+ move a merge request to another source branch, and closing it would lose its discussion -
222
+ merge or close it first.
223
+ - A branch that is pushed but whose merge requests cannot be listed (no token, or a remote
224
+ that is not GitLab) blocks too. `--no-mr-check` asserts that none point at it.
225
+ - If a retarget fails halfway, the old branch is still there and the recovery commands are
226
+ printed.
227
+
116
228
  ## Hooks (opt-in)
117
229
 
118
- Claude Code hooks run commands on the developer's machine, so none are emitted by
119
- default - opt in per hook in the config:
230
+ Hooks run commands on the developer's machine, so none are emitted by default - opt in
231
+ per hook in the config:
120
232
 
121
233
  ```json
122
234
  {
@@ -124,20 +236,82 @@ default - opt in per hook in the config:
124
236
  }
125
237
  ```
126
238
 
127
- `sync` writes the script to `.claude/hooks/ethlete/` and registers it in
128
- `.claude/settings.json` (your own entries are left untouched); removing the name from
129
- `hooks` unregisters and deletes it again.
239
+ They are emitted for whichever of the `claude` and `codex` targets is enabled:
240
+
241
+ | Target | Script | Registered in |
242
+ | -------- | ------------------------ | ----------------------- |
243
+ | `claude` | `.claude/hooks/ethlete/` | `.claude/settings.json` |
244
+ | `codex` | `.codex/hooks/ethlete/` | `.codex/hooks.json` |
245
+
246
+ Your own entries in those files are left untouched; removing the name from `hooks`
247
+ unregisters and deletes the script again. Codex only loads project-local hooks once the
248
+ `.codex/` layer is trusted, and honours `[features] hooks = false`.
130
249
 
131
250
  Available hooks:
132
251
 
133
- - **`context-warning`** - warns once per tier (and instructs Claude) when the session
134
- context crosses 70% / 85% of the token budget, recommending `/handoff`. The budget is
135
- capped at the 200k long-context pricing boundary: on 1M-window models every request
136
- past 200k input tokens bills the whole context at a premium rate, so the warnings
137
- fire at ~140k/~170k instead of deep into the expensive range.
252
+ - **`context-warning`** - warns once per tier (and instructs the agent) when the session
253
+ context crosses 70% / 85% of the token budget, recommending a handoff. Under Claude the
254
+ budget is capped at the 200k long-context pricing boundary: on 1M-window models every
255
+ request past 200k input tokens bills the whole context at a premium rate, so the
256
+ warnings fire at ~140k/~170k instead of deep into the expensive range. Codex has no
257
+ such boundary, so its budget is the model's own reported context window and the
258
+ warnings are pure occupancy.
259
+
260
+ Two things are Claude-only: the separate user-facing line (Codex documents only
261
+ `additionalContext`, so there the warning is folded into the text the model is told to
262
+ relay), and the auto-mode escalation that writes the handoff file unprompted - Codex's
263
+ `permission_mode` values are undocumented, so no value enables it.
138
264
 
139
265
  Hooks can be turned off per machine - see the local config below.
140
266
 
267
+ ## Git hooks (opt-in)
268
+
269
+ Separate from the agent hooks above, and opt-in for the same reason - a generated block
270
+ that can reject a push is a higher-stakes artifact than a markdown one:
271
+
272
+ ```json
273
+ {
274
+ "gitHooks": ["pre-push", "post-checkout"]
275
+ }
276
+ ```
277
+
278
+ Each one is written as an `# ethlete:git-flow:start` … `end` block **appended** to your
279
+ `.husky/<name>`, so an existing hook there (a git-lfs hook, typically) keeps working and
280
+ keeps reading stdin first - which is why the block never reads stdin itself. Removing the
281
+ name from `gitHooks` takes the block back out and leaves the rest of the file alone.
282
+
283
+ - **`pre-push`** - runs `git-flow check --push` on the current branch. In `advisory` mode
284
+ only a direct push to a base branch can actually stop it.
285
+ - **`post-checkout`** - reports a non-conforming name on a branch that is on no remote yet,
286
+ which is the whole window in which renaming it is free.
287
+
288
+ Only `.husky/` is written, never `.git/hooks/`: the generated files are committed and CI's
289
+ `check` diffs them, so a hook outside the working tree could never be in sync. Without a
290
+ `.husky/` directory `sync` warns and writes nothing. The block calls
291
+ `node_modules/.bin/ethlete-agents` directly rather than through `npx`, so a repo where the
292
+ package is missing gets silence instead of a registry lookup that would fail the push.
293
+ `ETHLETE_GIT_FLOW_SKIP=1` silences both hooks on one machine.
294
+
295
+ ## CI job
296
+
297
+ On GitLab, the merge request target is the half no local hook can see. The job needs no
298
+ configuration beyond the predefined variables:
299
+
300
+ ```yaml
301
+ Git Flow:
302
+ stage: Checks
303
+ rules:
304
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event"
305
+ allow_failure: true
306
+ script:
307
+ - >
308
+ npx ethlete-agents git-flow check "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME"
309
+ --target "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME"
310
+ ```
311
+
312
+ `allow_failure: true` on top of `advisory` mode is deliberate belt and braces: the job
313
+ reports for a whole grace period before it can ever be the reason a merge request is red.
314
+
141
315
  ## Per-machine local config
142
316
 
143
317
  A gitignored `ethlete-agents.config.local.json` at the repo root holds the values that
@@ -146,13 +320,20 @@ differ per developer, without touching any committed file:
146
320
  ```json
147
321
  {
148
322
  "disableHooks": true,
149
- "sdkSourcePath": "/absolute/path/to/ethlete-sdk"
323
+ "sdkSourcePath": "/absolute/path/to/ethlete-sdk",
324
+ "jira": { "email": "you@example.com", "token": "…" }
150
325
  }
151
326
  ```
152
327
 
153
328
  - **`disableHooks`** - `true` disables every generated hook; an array
154
329
  (`["context-warning"]`) just the named ones. The hook scripts read the file at
155
330
  runtime, so toggling takes effect on the next prompt - no `sync` needed.
331
+ - **`disableAutoHandoffSave`** - keeps the `context-warning` hook's tiered warnings but
332
+ drops the auto-mode escalation: at the critical tier it recommends `/ethlete-handoff`
333
+ instead of saving the handoff file itself.
334
+ - **`jira`** - the credentials `git-flow start` needs (`host`, `email`, `token`). This is
335
+ the one place in a repo a secret may sit, and only because the file is gitignored;
336
+ `JIRA_EMAIL` / `JIRA_API_TOKEN` in the environment are the alternative and win over it.
156
337
  - **`sdkSourcePath`** - a local `ethlete-sdk` checkout. The `sdk-source` and
157
338
  `sdk-local-build` skills read it when the agent needs the SDK's own sources, or has to
158
339
  build the SDK and install it here through a `file:` dependency. A relative path is
@@ -0,0 +1,10 @@
1
+ if [ "$3" = "1" ] && [ -z "$ETHLETE_GIT_FLOW_SKIP" ] && [ -x node_modules/.bin/ethlete-agents ]; then
2
+ ethlete_branch=$(git rev-parse --abbrev-ref HEAD)
3
+
4
+ # Only while the branch is on no remote: that is the whole window in which renaming it is free.
5
+ if [ -z "$(git for-each-ref --format='%(refname)' "refs/remotes/*/$ethlete_branch")" ]; then
6
+ node_modules/.bin/ethlete-agents git-flow check || true
7
+ fi
8
+
9
+ unset ethlete_branch
10
+ fi
@@ -0,0 +1,5 @@
1
+ # Never `npx`: it reaches the registry when the package is absent, and a network error here would
2
+ # reject the push. A missing binary must make this block do nothing at all.
3
+ if [ -z "$ETHLETE_GIT_FLOW_SKIP" ] && [ -x node_modules/.bin/ethlete-agents ]; then
4
+ node_modules/.bin/ethlete-agents git-flow check --push || exit 1
5
+ fi