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

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 +101 -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 +14 -7
  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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,106 @@
1
1
  # @ethlete/agent-rules
2
2
 
3
+ ## 0.1.0-next.11
4
+
5
+ ### Patch Changes
6
+
7
+ - [`16ae17e`](https://github.com/ethlete-io/ethdk/commit/16ae17e30238ef4539f3e168ca8299a4546ac292) Thanks [@TomTomB](https://github.com/TomTomB)! - The theming skill now states that a component which tints with `--et-theme-color-primary-opacity`
8
+ must compose the color itself, and that an `@property` `initial-value` cannot use a font-relative or
9
+ container unit.
10
+
11
+ ## 0.1.0-next.10
12
+
13
+ ### Minor Changes
14
+
15
+ - [#3067](https://github.com/ethlete-io/ethdk/pull/3067) [`226fd3d`](https://github.com/ethlete-io/ethdk/commit/226fd3d0b52f1a131ccbaa417ffe96682752d3f8) Thanks [@github-actions](https://github.com/apps/github-actions)! - New `ethlete-agents output-style` command: it installs the `ste-clarity` ASD-STE100 output style into Claude Code's user config and switches to it. Claude Code only.
16
+
17
+ - [#3067](https://github.com/ethlete-io/ethdk/pull/3067) [`3657156`](https://github.com/ethlete-io/ethdk/commit/36571560c755468459a87da9d9ec5764d976ee96) Thanks [@github-actions](https://github.com/apps/github-actions)! - `ethlete-agents timetrack instance` reports the Jira instance's own levels and the custom fields a
18
+ branch subject could go in, so a setup step reads the answer instead of guessing it.
19
+
20
+ ## 0.1.0-next.9
21
+
22
+ ### Minor Changes
23
+
24
+ - [`ae165cc`](https://github.com/ethlete-io/ethdk/commit/ae165cc4123e3f2abaa88c5cfd8262b13aac81d1) Thanks [@TomTomB](https://github.com/TomTomB)! - New `api-source` skill reads the API repo of the app you are in, from the local config's new `apiRepoPaths` map (`{ "hub": "../fut-hub-backend" }`).
25
+
26
+ - [`f132d0b`](https://github.com/ethlete-io/ethdk/commit/f132d0b64e322c5823c9f50adeedecf388c5aa65) Thanks [@TomTomB](https://github.com/TomTomB)! - `ethlete-agents timetrack` reaches Jira through the running Timetrack app, so no repository holds a
27
+ token any more — the `jira` credentials in the local config and the `JIRA_*` variables are gone.
28
+
29
+ - [#3066](https://github.com/ethlete-io/ethdk/pull/3066) [`fef4586`](https://github.com/ethlete-io/ethdk/commit/fef45868b10b0a0f01efff741958d65d9e405a31) Thanks [@github-actions](https://github.com/apps/github-actions)! - `git-flow`: add `conformingNameFor()`, which names what a non-conforming branch should be renamed to, and `git-flow repair` now handles a keyless branch it previously refused.
30
+
31
+ - [#3066](https://github.com/ethlete-io/ethdk/pull/3066) [`fef4586`](https://github.com/ethlete-io/ethdk/commit/fef45868b10b0a0f01efff741958d65d9e405a31) Thanks [@github-actions](https://github.com/apps/github-actions)! - `git-flow`: export `featureBranchesFor()` and `nestedSpecFor()`, so a host can plan a nested branch without running the CLI.
32
+
33
+ ## 0.1.0-next.8
34
+
35
+ ### Patch Changes
36
+
37
+ - [#3058](https://github.com/ethlete-io/ethdk/pull/3058) [`7074ffc`](https://github.com/ethlete-io/ethdk/commit/7074ffcb99812faed82a4e32fa3f8a1a43fcaf9d) Thanks [@github-actions](https://github.com/apps/github-actions)! - The `sdk-docs` skill points agents at the docs site's `llms.txt` index and the `.md` suffix on any page URL, so a page can be found and read without guessing a URL from the hardcoded domain list.
38
+
39
+ - [#3058](https://github.com/ethlete-io/ethdk/pull/3058) [`86fce5b`](https://github.com/ethlete-io/ethdk/commit/86fce5b87770ef4d5b069e59fec745eae93fda7b) Thanks [@github-actions](https://github.com/apps/github-actions)! - The `sdk-source` skill points at `apps/storybook/`, the SDK repo's renamed Storybook host.
40
+
41
+ ## 0.1.0-next.7
42
+
43
+ ### Minor Changes
44
+
45
+ - [#3056](https://github.com/ethlete-io/ethdk/pull/3056) [`8aec5ff`](https://github.com/ethlete-io/ethdk/commit/8aec5ff1a3ea6dd459d0e00bc691468019dcaca6) Thanks [@github-actions](https://github.com/apps/github-actions)! - Generated files no longer stamp the package version into their banner, so a release bump alone no longer reports every repo as out of sync - only real content changes do.
46
+
47
+ ### Patch Changes
48
+
49
+ - [#3056](https://github.com/ethlete-io/ethdk/pull/3056) [`b5f72d6`](https://github.com/ethlete-io/ethdk/commit/b5f72d6bf35dd0e49cf14588b651012fb5d6a7c5) Thanks [@github-actions](https://github.com/apps/github-actions)! - The git-commit guide now presents its format as the repo's own convention unless a commitlint config is actually there, instead of pointing every repo at a `commitlint.config.js` it may not have.
50
+
51
+ ## 0.1.0-next.6
52
+
53
+ ### Minor Changes
54
+
55
+ - [#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`.
56
+
57
+ ### Patch Changes
58
+
59
+ - [#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.
60
+
61
+ ## 0.1.0-next.5
62
+
63
+ ### Minor Changes
64
+
65
+ - [#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
66
+ `.codex/hooks.json` whenever the `codex` target is on.
67
+
68
+ - [#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.
69
+
70
+ ### Patch Changes
71
+
72
+ - [#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.
73
+
74
+ - [#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`
75
+ that prints its box tree and measures the auto-layout gaps.
76
+
77
+ ## 0.1.0-next.4
78
+
79
+ ### Minor Changes
80
+
81
+ - [#3045](https://github.com/ethlete-io/ethdk/pull/3045) [`fcd14f0`](https://github.com/ethlete-io/ethdk/commit/fcd14f09b4b09f81b5bde9f128a0fac4b0e2245c) Thanks [@github-actions](https://github.com/apps/github-actions)! - Add `disableAutoHandoffSave` to `ethlete-agents.config.local.json`, to opt out of the context-warning hook's auto-mode auto-save at the critical tier while keeping its normal warnings.
82
+
83
+ ### Patch Changes
84
+
85
+ - [#3045](https://github.com/ethlete-io/ethdk/pull/3045) [`eb1b0a9`](https://github.com/ethlete-io/ethdk/commit/eb1b0a9cfb4e868b13a2b8eb6fd13221a6e0a654) Thanks [@github-actions](https://github.com/apps/github-actions)! - `context-warning` hook: in auto mode, the critical-tier warning now saves a `/handoff` immediately instead of just recommending it.
86
+
87
+ ## 0.1.0-next.3
88
+
89
+ ### Patch Changes
90
+
91
+ - [`ca0bf2f`](https://github.com/ethlete-io/ethdk/commit/ca0bf2f09cd5bd925da52bdb17c93bb62bda8735) Thanks [@TomTomB](https://github.com/TomTomB)! - The `comments` rule is now an allowlist: four kinds of comment are allowed and everything else gets deleted.
92
+
93
+ ## 0.1.0-next.2
94
+
95
+ ### Minor Changes
96
+
97
+ - [#3043](https://github.com/ethlete-io/ethdk/pull/3043) [`a311f80`](https://github.com/ethlete-io/ethdk/commit/a311f80455bc9cc9a925fe8f72ac945a1b057315) Thanks [@github-actions](https://github.com/apps/github-actions)! - New `sdk-source` and `sdk-local-build` skills let an agent read the SDK's own sources and test an unreleased build via `file:`, from the checkout named by the local config's new `sdkSourcePath`.
98
+
99
+ ### Patch Changes
100
+
101
+ - [#3043](https://github.com/ethlete-io/ethdk/pull/3043) [`9627646`](https://github.com/ethlete-io/ethdk/commit/96276462e1c2ecde5394b8b1eafcebbb9f56a973) Thanks [@github-actions](https://github.com/apps/github-actions)! - Styleguide: changeset notes are now capped at one to two sentences, with mechanism and API inventories
102
+ explicitly sent to the docs instead.
103
+
3
104
  ## 0.1.0-next.1
4
105
 
5
106
  ### Minor Changes
package/README.md CHANGED
@@ -103,7 +103,14 @@ Prettier rewrites them and `check` then reports drift on every run:
103
103
  authoring-side guides are not overwritten by the consumer-side versions.
104
104
  - **`vars`** - values for the template tokens a guide declares. Defaults live in
105
105
  `content/defaults.json`; a guide whose variable has no default and no value is
106
- skipped with a warning rather than emitted with a dangling placeholder.
106
+ skipped with a warning rather than emitted with a dangling placeholder. Some are
107
+ derived from the repo instead of defaulted - the `gitFlow*` ones from the `gitFlow`
108
+ block, and `commitRuleSource` / `commitValidation` from whether a commitlint config
109
+ exists (`commitlint.config.*`, `.commitlintrc*`, or a `commitlint` key in
110
+ `package.json`). Without one, the git-commit guide presents the format as the repo's
111
+ convention and never mentions a `commitlint` run - an agent that goes looking for a
112
+ promised validator and finds nothing reports the discrepancy instead of just
113
+ committing. Setting either one in `vars` overrides the detection.
107
114
  - **`exclude`** - content names to skip entirely.
108
115
  - **`claudeMdImportsAgentsMd`** - set (usually by `migrate`) when `CLAUDE.md` is an
109
116
  `@AGENTS.md` import or symlink; the claude target then skips `.claude/rules/ethlete/`
@@ -113,10 +120,160 @@ Prettier rewrites them and `check` then reports drift on every run:
113
120
  Content that declares `requires` is only emitted when those packages are installed, so
114
121
  a repo without `@ethlete/query` never sees the query guide.
115
122
 
123
+ ## Git flow
124
+
125
+ The branch convention lives in the same config, as one machine-readable grammar that the
126
+ CLI, a git hook, a CI job and `@ethlete/timetrack` all read:
127
+
128
+ ```json
129
+ {
130
+ "gitFlow": {
131
+ "keyPrefixes": ["FIP"],
132
+ "baseBranches": { "development": "next", "production": "main" }
133
+ }
134
+ }
135
+ ```
136
+
137
+ ```bash
138
+ npx ethlete-agents git-flow start FIP-2177 # name it and branch off the right base
139
+ npx ethlete-agents git-flow check # the current branch
140
+ npx ethlete-agents git-flow check "$SOURCE" --target "$TARGET"
141
+ npx ethlete-agents git-flow check --all # adoption report
142
+ npx ethlete-agents git-flow repair dev-game-codes --key FIP-2900
143
+ npx ethlete-agents git-flow explain feat/FIP-2177-user-management
144
+ ```
145
+
146
+ The shapes:
147
+
148
+ | Shape | Branch from | Merges into |
149
+ | ------------------------------------------ | ----------------------- | ------------------------------ |
150
+ | `feat/<KEY>-<subject>` | development | development |
151
+ | `sub/feat/<KEY>-<subject>/<KEY>-<subject>` | the main feature branch | the main feature branch |
152
+ | `release/<YYYY.MM.DD>` | development | development **and** production |
153
+ | `sub/release/<YYYY.MM.DD>/<KEY>-<subject>` | the release branch | the release branch |
154
+ | `hotfix/<KEY>-<subject>` | production | production |
155
+
156
+ **Why nested branches carry a `sub/` prefix.** Git refuses a ref that is both a branch and
157
+ a directory of branches, so `feat/FIP-2177-user-management/FIP-2178-reset` cannot exist
158
+ while `feat/FIP-2177-user-management` does - the push is rejected with `refname conflict`.
159
+ The prefix moves the nested tree out of the way while keeping the parent's full path inside
160
+ the child's name, so the merge request target is still derivable from the name alone. The
161
+ unprefixed spelling still parses, reports why it cannot exist, and `repair` moves it.
162
+ Configurable as `subPrefix`.
163
+
164
+ - **`enforcement`** - `"advisory"` (default) reports everything and blocks nothing, so a
165
+ repo can adopt the convention before it gates on it. `"gated"` applies each rule's
166
+ `severity`. A direct push to a base branch is blocked in both modes, and
167
+ `wrong-mr-target` can be raised to `"error"` on its own without ending the naming
168
+ grace period.
169
+ - **`keyPrefixes`** - the project's issue prefixes. Leave it empty and anything shaped
170
+ like `keyPattern` counts, which reads `chore/angular-22` as issue `ANGULAR-22`.
171
+ - **`severity`** - per rule: `unknown-type`, `missing-key`, `key-case`,
172
+ `missing-subject`, `type-alias`, `deprecated-prefix`, `release-date`,
173
+ `wrong-mr-target`, `protected-push`.
174
+ - **`deprecatedShapes`** - legacy spellings that still classify correctly and only earn a
175
+ rename suggestion. `dev-*` ships as the old spelling of a main feature branch.
176
+
177
+ The grammar is also importable on its own - `@ethlete/agent-rules/git-flow` has no
178
+ dependencies and touches no Node built-ins, so it runs in a browser:
179
+
180
+ ```ts
181
+ import { parseBranch, planStart, resolveGitFlowConfig } from '@ethlete/agent-rules/git-flow';
182
+
183
+ const { storyKey, taskKey, findings } = parseBranch({ branch, config: resolveGitFlowConfig() });
184
+ ```
185
+
186
+ ### `start` - the prospective flow
187
+
188
+ `git-flow start <KEY>` reads the issue from Jira, computes the name from the grammar and
189
+ creates the branch off the correct base. It prints the plan first and asks before writing;
190
+ `--dry-run` stops after the plan and `--yes` skips the question. It refuses on a dirty
191
+ working tree, when the branch already exists, and when the base branch is nowhere to be
192
+ found.
193
+
194
+ A Task with a parent Story nests under that Story's feature branch, which therefore has to
195
+ exist already - `start` says so rather than inventing a parent. `--of <branch>` picks the
196
+ parent explicitly, `--hotfix` branches off production, `--release <date>` makes a release
197
+ branch, and `--subject <text>` skips Jira entirely.
198
+
199
+ The issue is read through the Timetrack app - see [Jira, through Timetrack](#jira-through-timetrack).
200
+ No repository holds a Jira credential, so the only thing the committed config still says about
201
+ Jira is how an issue type becomes a branch type:
202
+
203
+ ```json
204
+ {
205
+ "jira": {
206
+ "typeByIssueType": { "Bug": "fix" }
207
+ }
208
+ }
209
+ ```
210
+
211
+ - **`typeByIssueType`** - the branch type per Jira issue type; anything unlisted becomes
212
+ `feat`. `--type` overrides it per call.
213
+
214
+ The branch subject comes from the instance's own subject field, which Timetrack resolves
215
+ because the field id is a property of the instance rather than of this repo. An issue that
216
+ sets no subject falls back to its summary, and the printed plan says which of the two it used.
217
+
218
+ ### `repair` - renaming a branch that does not conform
219
+
220
+ `git-flow repair [ref]` derives the conforming name (`--key FIP-2900` when the old name
221
+ carries no issue key, `--to <branch>` to override), renames the branch locally and on the
222
+ remote, and retargets the open merge requests aimed at it through the GitLab API.
223
+ `GITLAB_TOKEN` needs the `api` scope.
224
+
225
+ Everything is checked before the first mutation, and it refuses rather than half-finishing:
226
+
227
+ - An open merge request whose **source** is the branch blocks the repair. GitLab cannot
228
+ move a merge request to another source branch, and closing it would lose its discussion -
229
+ merge or close it first.
230
+ - A branch that is pushed but whose merge requests cannot be listed (no token, or a remote
231
+ that is not GitLab) blocks too. `--no-mr-check` asserts that none point at it.
232
+ - If a retarget fails halfway, the old branch is still there and the recovery commands are
233
+ printed.
234
+
235
+ ## Jira, through Timetrack
236
+
237
+ A Jira token in every repository is a secret nobody can rotate. There is one on this machine
238
+ instead, in the Timetrack app's keychain entry, and every repository asks the running app:
239
+
240
+ ```bash
241
+ npx ethlete-agents timetrack status # is it reachable, and what does it hold?
242
+ npx ethlete-agents timetrack issue FIP-2177 # summary, type, parent, branch subject
243
+ npx ethlete-agents timetrack search "password" # open issues of the picked projects
244
+ npx ethlete-agents timetrack project # which project does this repo log into?
245
+ npx ethlete-agents timetrack create --summary "…" # file a ticket with the instance's settings
246
+ npx ethlete-agents timetrack log --issue FIP-2177 --minutes 45
247
+ ```
248
+
249
+ `--json` prints the raw answer instead of lines. `git-flow start` uses the same channel.
250
+
251
+ **How it connects.** The app binds a loopback socket and writes its port and a fresh token
252
+ into `agent.json` in its own data directory (`~/.local/share/io.ethlete.timetrack/` on Linux,
253
+ `~/Library/Application Support/…` on macOS, `%APPDATA%\…` on Windows), readable by its owner
254
+ alone. The token lives no longer than the run, so a caller left over from an earlier one is
255
+ refused rather than trusted, and a request carrying an `Origin` header is refused outright -
256
+ a page the user happens to have open must not reach Jira through a port it guessed.
257
+ `TIMETRACK_AGENT_DISCOVERY` names the file for a machine that keeps it elsewhere.
258
+
259
+ **What the app answers, not this package.** The instance, the credentials, the picked
260
+ projects, the subject field and the ticket shape are all settings there. That is what makes
261
+ `create` file a ticket indistinguishable from one the app filed, and it is why none of them
262
+ appear in a repo's config any more.
263
+
264
+ **`log` writes to the day, not to Tempo.** It adds the same row the app's own timeline draws
265
+ for work nothing observed, and the user reviews the day before syncing it. A worklog posted
266
+ behind the review would double-book against whatever the evidence already proposed for that
267
+ hour.
268
+
269
+ **When the app is not running** every command says so and exits non-zero. There is no
270
+ fallback to an environment variable on purpose - a fallback is how the per-repo secret comes
271
+ back.
272
+
116
273
  ## Hooks (opt-in)
117
274
 
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:
275
+ Hooks run commands on the developer's machine, so none are emitted by default - opt in
276
+ per hook in the config:
120
277
 
121
278
  ```json
122
279
  {
@@ -124,35 +281,148 @@ default - opt in per hook in the config:
124
281
  }
125
282
  ```
126
283
 
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.
284
+ They are emitted for whichever of the `claude` and `codex` targets is enabled:
285
+
286
+ | Target | Script | Registered in |
287
+ | -------- | ------------------------ | ----------------------- |
288
+ | `claude` | `.claude/hooks/ethlete/` | `.claude/settings.json` |
289
+ | `codex` | `.codex/hooks/ethlete/` | `.codex/hooks.json` |
290
+
291
+ Your own entries in those files are left untouched; removing the name from `hooks`
292
+ unregisters and deletes the script again. Codex only loads project-local hooks once the
293
+ `.codex/` layer is trusted, and honours `[features] hooks = false`.
130
294
 
131
295
  Available hooks:
132
296
 
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.
297
+ - **`context-warning`** - warns once per tier (and instructs the agent) when the session
298
+ context crosses 70% / 85% of the token budget, recommending a handoff. Under Claude the
299
+ budget is capped at the 200k long-context pricing boundary: on 1M-window models every
300
+ request past 200k input tokens bills the whole context at a premium rate, so the
301
+ warnings fire at ~140k/~170k instead of deep into the expensive range. Codex has no
302
+ such boundary, so its budget is the model's own reported context window and the
303
+ warnings are pure occupancy.
138
304
 
139
- ### Disabling hooks per machine
305
+ Two things are Claude-only: the separate user-facing line (Codex documents only
306
+ `additionalContext`, so there the warning is folded into the text the model is told to
307
+ relay), and the auto-mode escalation that writes the handoff file unprompted - Codex's
308
+ `permission_mode` values are undocumented, so no value enables it.
140
309
 
141
- A gitignored `ethlete-agents.config.local.json` at the repo root turns generated hooks
142
- off for one developer without touching any committed file:
310
+ Hooks can be turned off per machine - see the local config below.
311
+
312
+ ## Git hooks (opt-in)
313
+
314
+ Separate from the agent hooks above, and opt-in for the same reason - a generated block
315
+ that can reject a push is a higher-stakes artifact than a markdown one:
143
316
 
144
317
  ```json
145
318
  {
146
- "disableHooks": true
319
+ "gitHooks": ["pre-push", "post-checkout"]
147
320
  }
148
321
  ```
149
322
 
150
- `true` disables every generated hook; an array (`["context-warning"]`) just the named
151
- ones. The hook scripts read this file at runtime, so toggling takes effect on the next
152
- prompt - no `sync` needed - and the generated files stay identical on every machine and
153
- in CI. That is also why the local file supports nothing else: sync output must never
154
- depend on it, and `sync`/`check` warn if it contains other keys. Add the filename to
155
- your repo's `.gitignore`.
323
+ Each one is written as an `# ethlete:git-flow:start` … `end` block **appended** to your
324
+ `.husky/<name>`, so an existing hook there (a git-lfs hook, typically) keeps working and
325
+ keeps reading stdin first - which is why the block never reads stdin itself. Removing the
326
+ name from `gitHooks` takes the block back out and leaves the rest of the file alone.
327
+
328
+ - **`pre-push`** - runs `git-flow check --push` on the current branch. In `advisory` mode
329
+ only a direct push to a base branch can actually stop it.
330
+ - **`post-checkout`** - reports a non-conforming name on a branch that is on no remote yet,
331
+ which is the whole window in which renaming it is free.
332
+
333
+ Only `.husky/` is written, never `.git/hooks/`: the generated files are committed and CI's
334
+ `check` diffs them, so a hook outside the working tree could never be in sync. Without a
335
+ `.husky/` directory `sync` warns and writes nothing. The block calls
336
+ `node_modules/.bin/ethlete-agents` directly rather than through `npx`, so a repo where the
337
+ package is missing gets silence instead of a registry lookup that would fail the push.
338
+ `ETHLETE_GIT_FLOW_SKIP=1` silences both hooks on one machine.
339
+
340
+ ## CI job
341
+
342
+ On GitLab, the merge request target is the half no local hook can see. The job needs no
343
+ configuration beyond the predefined variables:
344
+
345
+ ```yaml
346
+ Git Flow:
347
+ stage: Checks
348
+ rules:
349
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event"
350
+ allow_failure: true
351
+ script:
352
+ - >
353
+ npx ethlete-agents git-flow check "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME"
354
+ --target "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME"
355
+ ```
356
+
357
+ `allow_failure: true` on top of `advisory` mode is deliberate belt and braces: the job
358
+ reports for a whole grace period before it can ever be the reason a merge request is red.
359
+
360
+ ## Output styles (Claude Code)
361
+
362
+ An output style replaces Claude Code's own answer style for a whole session. This package
363
+ ships one - **`ste-clarity`**, which writes every answer in ASD-STE100 Simplified Technical
364
+ English - and installs it into the machine's Claude config rather than into a repo, because
365
+ that is where Claude Code reads styles from:
366
+
367
+ ```bash
368
+ npx ethlete-agents output-style # install ste-clarity and switch to it
369
+ npx ethlete-agents output-style --dry-run # print what would change
370
+ npx ethlete-agents output-style --remove # take it back out
371
+ ```
372
+
373
+ Two files, both under `~/.claude` (or `$CLAUDE_CONFIG_DIR`, or `--config-dir <path>`):
374
+
375
+ | File | What changes |
376
+ | ------------------------------ | ------------------------------------------------------------------ |
377
+ | `output-styles/ste-clarity.md` | The style itself, written whole |
378
+ | `settings.json` | `"outputStyle": "ste-clarity"` - every other setting is left alone |
379
+
380
+ - **`--no-activate`** writes the style file but not the setting, so switching to it is then
381
+ `/output-style ste-clarity` inside a session.
382
+ - A style file this command did not write is never overwritten or deleted - it says so and
383
+ stops, and `--force` is the way through. A copy of the same style that differs only in
384
+ layout still counts as its own.
385
+ - Nothing here is repo-local, so `sync` and `check` neither write nor diff it. `--remove`
386
+ clears `outputStyle` only while it still points at that style.
387
+
388
+ **Claude Code only.** Codex has no output-style mechanism - everything it is told comes
389
+ from `AGENTS.md`, which `sync` already writes.
390
+
391
+ ## Per-machine local config
392
+
393
+ A gitignored `ethlete-agents.config.local.json` at the repo root holds the values that
394
+ differ per developer, without touching any committed file:
395
+
396
+ ```json
397
+ {
398
+ "disableHooks": true,
399
+ "sdkSourcePath": "/absolute/path/to/ethlete-sdk",
400
+ "apiRepoPaths": { "hub": "../fut-hub-backend" }
401
+ }
402
+ ```
403
+
404
+ - **`disableHooks`** - `true` disables every generated hook; an array
405
+ (`["context-warning"]`) just the named ones. The hook scripts read the file at
406
+ runtime, so toggling takes effect on the next prompt - no `sync` needed.
407
+ - **`disableAutoHandoffSave`** - keeps the `context-warning` hook's tiered warnings but
408
+ drops the auto-mode escalation: at the critical tier it recommends `/ethlete-handoff`
409
+ instead of saving the handoff file itself.
410
+ - **`sdkSourcePath`** - a local `ethlete-sdk` checkout. The `sdk-source` and
411
+ `sdk-local-build` skills read it when the agent needs the SDK's own sources, or has to
412
+ build the SDK and install it here through a `file:` dependency. A relative path is
413
+ resolved from the repo root.
414
+ - **`apiRepoPaths`** - one checkout per app, keyed by the app's project name
415
+ (`{ "hub": "../fut-hub-backend" }`). The `api-source` skill reads it to confirm a
416
+ response shape, a status code or an enum in the API's own source instead of guessing it
417
+ from the client. Relative paths resolve from the repo root; a map with a single entry is
418
+ used for whatever app is in play.
419
+
420
+ Everything in this file is read at runtime, never by `sync`: the generated files stay
421
+ identical on every machine and in CI, which is what lets `check` diff them. That is also
422
+ why the file takes nothing beyond these keys - `sync`/`check` warn about unknown keys,
423
+ about an `sdkSourcePath` that is missing or is not an SDK checkout, and about an
424
+ `apiRepoPaths` entry that is not a directory. Add the filename to your repo's
425
+ `.gitignore`.
156
426
 
157
427
  ## Authoring content
158
428
 
@@ -171,6 +441,11 @@ vars: [docsBaseUrl] # optional
171
441
  ---
172
442
  ```
173
443
 
444
+ `content/output-styles/<name>.md` is a third kind, and the only one this package does not
445
+ compile: it is a Claude Code output style verbatim, so its frontmatter is Claude's
446
+ (`name`, `description`, `keep-coding-instructions`) and `output-style` installs the file
447
+ as it is, apart from a marker line that records where it came from.
448
+
174
449
  In a body, `{% varName %}` substitutes a variable, `{% skill:other-name %}` links to
175
450
  another guide the way the current target expects, and `{% resource:file.mjs %}` links to
176
451
  a bundled file. The delimiter is `{% … %}`, not `{{ … }}`, so Angular templates in
@@ -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