@ethlete/agent-rules 0.1.0-next.3 → 0.1.0-next.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +28 -0
- package/CHANGELOG.md +480 -0
- package/CLAUDE.md +1 -0
- package/README.md +362 -29
- package/THIRD-PARTY-LICENSES.md +35 -0
- package/content/defaults.json +1 -1
- package/content/git-hooks/post-checkout.sh +10 -0
- package/content/git-hooks/pre-push.sh +5 -0
- package/content/hooks/context-warning.py +872 -110
- package/content/hooks/subagent-model-policy.py +194 -0
- package/content/mods/.claude-plugin/marketplace.json +11 -0
- package/content/mods/plugins/e2e-progress/.claude-plugin/plugin.json +6 -0
- package/content/mods/plugins/e2e-progress/hooks/hooks.json +1 -0
- package/content/mods/plugins/e2e-progress/hooks/register.tsx +442 -0
- package/content/mods/plugins/e2e-progress/tsconfig.json +3 -0
- package/content/mods/plugins/e2e-progress/types/index.d.ts +23 -0
- package/content/output-styles/ste-clarity.md +131 -0
- package/content/rules/agent-sessions-wake-up.md +11 -0
- package/content/rules/app-styling.md +66 -0
- package/content/rules/comments.md +4 -1
- package/content/rules/lint-and-format.md +7 -5
- package/content/rules/nx-layout.md +50 -0
- package/content/rules/reactive-state.md +1 -1
- package/content/rules/styling.md +9 -7
- package/content/rules/subagent-models.md +39 -0
- package/content/skills/agent-sessions/SKILL.md +81 -0
- package/content/skills/agent-sessions/agent-sessions.mjs +498 -0
- package/content/skills/angular-patterns/SKILL.md +30 -7
- package/content/skills/api-source/SKILL.md +156 -0
- package/content/skills/app-testing/SKILL.md +141 -0
- package/content/skills/codex-subagent/SKILL.md +90 -0
- package/content/skills/codex-subagent/codex-agent.mjs +229 -0
- package/content/skills/design-exploration/SKILL.md +687 -0
- package/content/skills/design-exploration/check-story.mjs +139 -0
- package/content/skills/design-exploration/shoot-template.mjs +61 -0
- package/content/skills/domain-modeling/ADR-FORMAT.md +47 -0
- package/content/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/content/skills/domain-modeling/SKILL.md +80 -0
- package/content/skills/e2e-triage/SKILL.md +80 -0
- package/content/skills/figma-export/SKILL.md +191 -0
- package/content/skills/figma-export/dump-figma-layers.py +83 -0
- package/content/skills/figma-export/dump-figma-svg.py +235 -0
- package/content/skills/figma-export/measure-template.mjs +87 -0
- package/content/skills/git-commit/SKILL.md +11 -7
- package/content/skills/git-flow/SKILL.md +87 -0
- package/content/skills/grill-with-docs/SKILL.md +13 -0
- package/content/skills/grilling/SKILL.md +51 -0
- package/content/skills/handoff/SKILL.md +125 -5
- package/content/skills/query/SKILL.md +165 -64
- package/content/skills/rxjs-signals/SKILL.md +33 -4
- package/content/skills/sdk-docs/SKILL.md +50 -14
- package/content/skills/sdk-local-build/SKILL.md +26 -8
- package/content/skills/sdk-local-build/sdk-local-baseline.mjs +64 -0
- package/content/skills/sdk-source/SKILL.md +37 -34
- package/content/skills/sdk-update/SKILL.md +103 -0
- package/content/skills/story-styling/SKILL.md +10 -8
- package/content/skills/styleguide/SKILL.md +12 -7
- package/content/skills/styleguide/assets.md +54 -0
- package/content/skills/styleguide/changesets.md +17 -0
- package/content/skills/styleguide/file-structure.md +73 -0
- package/content/skills/styleguide/lint-rule-lookup.md +51 -0
- package/content/skills/styleguide/storybook-structure.md +25 -0
- package/content/skills/theming/SKILL.md +50 -28
- package/content/skills/timetrack/SKILL.md +397 -0
- package/content/skills/verify-in-app/SKILL.md +107 -0
- package/migrations/app-styling-utilities.md +63 -0
- package/migrations/list-state-query-form.md +20 -0
- package/migrations/nx-layout.md +29 -0
- package/migrations/sdk-components-over-hand-built-ui.md +27 -0
- package/migrations/search-query-field.md +52 -0
- package/migrations.json +48 -0
- package/package.json +19 -3
- package/src/index.js +85 -30
- package/src/index.js.map +1 -1
- package/src/lib/cli-args.d.ts +9 -0
- package/src/lib/cli-args.js +48 -0
- package/src/lib/cli-args.js.map +1 -0
- package/src/lib/commitlint.d.ts +10 -0
- package/src/lib/commitlint.js +51 -0
- package/src/lib/commitlint.js.map +1 -0
- package/src/lib/config.d.ts +41 -5
- package/src/lib/config.js +117 -10
- package/src/lib/config.js.map +1 -1
- package/src/lib/filter.d.ts +2 -0
- package/src/lib/filter.js +4 -4
- package/src/lib/filter.js.map +1 -1
- package/src/lib/frontmatter.d.ts +2 -0
- package/src/lib/frontmatter.js +33 -5
- package/src/lib/frontmatter.js.map +1 -1
- package/src/lib/git-flow/build.d.ts +35 -0
- package/src/lib/git-flow/build.js +24 -0
- package/src/lib/git-flow/build.js.map +1 -0
- package/src/lib/git-flow/config.d.ts +59 -0
- package/src/lib/git-flow/config.js +50 -0
- package/src/lib/git-flow/config.js.map +1 -0
- package/src/lib/git-flow/index.d.ts +6 -0
- package/src/lib/git-flow/index.js +23 -0
- package/src/lib/git-flow/index.js.map +1 -0
- package/src/lib/git-flow/parse.d.ts +49 -0
- package/src/lib/git-flow/parse.js +300 -0
- package/src/lib/git-flow/parse.js.map +1 -0
- package/src/lib/git-flow/rename.d.ts +24 -0
- package/src/lib/git-flow/rename.js +70 -0
- package/src/lib/git-flow/rename.js.map +1 -0
- package/src/lib/git-flow/start.d.ts +49 -0
- package/src/lib/git-flow/start.js +57 -0
- package/src/lib/git-flow/start.js.map +1 -0
- package/src/lib/git-flow/validate.d.ts +34 -0
- package/src/lib/git-flow/validate.js +72 -0
- package/src/lib/git-flow/validate.js.map +1 -0
- package/src/lib/git-flow-command.d.ts +4 -0
- package/src/lib/git-flow-command.js +165 -0
- package/src/lib/git-flow-command.js.map +1 -0
- package/src/lib/git-flow-repair.d.ts +17 -0
- package/src/lib/git-flow-repair.js +165 -0
- package/src/lib/git-flow-repair.js.map +1 -0
- package/src/lib/git-flow-start.d.ts +20 -0
- package/src/lib/git-flow-start.js +140 -0
- package/src/lib/git-flow-start.js.map +1 -0
- package/src/lib/git.d.ts +64 -0
- package/src/lib/git.js +131 -0
- package/src/lib/git.js.map +1 -0
- package/src/lib/gitlab.d.ts +35 -0
- package/src/lib/gitlab.js +116 -0
- package/src/lib/gitlab.js.map +1 -0
- package/src/lib/index.d.ts +2 -0
- package/src/lib/index.js +26 -11
- package/src/lib/index.js.map +1 -1
- package/src/lib/migrate.js +6 -1
- package/src/lib/migrate.js.map +1 -1
- package/src/lib/output-style-command.d.ts +3 -0
- package/src/lib/output-style-command.js +69 -0
- package/src/lib/output-style-command.js.map +1 -0
- package/src/lib/output-style.d.ts +38 -0
- package/src/lib/output-style.js +134 -0
- package/src/lib/output-style.js.map +1 -0
- package/src/lib/owned-paths.js +27 -3
- package/src/lib/owned-paths.js.map +1 -1
- package/src/lib/package-runner.d.ts +4 -0
- package/src/lib/package-runner.js +86 -0
- package/src/lib/package-runner.js.map +1 -0
- package/src/lib/plain-text.d.ts +9 -0
- package/src/lib/plain-text.js +22 -0
- package/src/lib/plain-text.js.map +1 -0
- package/src/lib/plan.d.ts +6 -6
- package/src/lib/plan.js +164 -40
- package/src/lib/plan.js.map +1 -1
- package/src/lib/prompt.d.ts +8 -0
- package/src/lib/prompt.js +35 -0
- package/src/lib/prompt.js.map +1 -0
- package/src/lib/render.d.ts +25 -2
- package/src/lib/render.js +43 -10
- package/src/lib/render.js.map +1 -1
- package/src/lib/sessions-command.d.ts +4 -0
- package/src/lib/sessions-command.js +16 -0
- package/src/lib/sessions-command.js.map +1 -0
- package/src/lib/sync.d.ts +0 -1
- package/src/lib/sync.js +30 -17
- package/src/lib/sync.js.map +1 -1
- package/src/lib/targets/agents-skills.js +1 -1
- package/src/lib/targets/agents-skills.js.map +1 -1
- package/src/lib/targets/claude-hooks.d.ts +3 -23
- package/src/lib/targets/claude-hooks.js +22 -86
- package/src/lib/targets/claude-hooks.js.map +1 -1
- package/src/lib/targets/claude-mods.d.ts +20 -0
- package/src/lib/targets/claude-mods.js +47 -0
- package/src/lib/targets/claude-mods.js.map +1 -0
- package/src/lib/targets/claude.js +2 -3
- package/src/lib/targets/claude.js.map +1 -1
- package/src/lib/targets/codex-hooks.d.ts +12 -0
- package/src/lib/targets/codex-hooks.js +35 -0
- package/src/lib/targets/codex-hooks.js.map +1 -0
- package/src/lib/targets/codex.js +4 -4
- package/src/lib/targets/codex.js.map +1 -1
- package/src/lib/targets/copilot.js +4 -4
- package/src/lib/targets/copilot.js.map +1 -1
- package/src/lib/targets/cursor.js +3 -3
- package/src/lib/targets/cursor.js.map +1 -1
- package/src/lib/targets/git-hooks.d.ts +25 -0
- package/src/lib/targets/git-hooks.js +71 -0
- package/src/lib/targets/git-hooks.js.map +1 -0
- package/src/lib/targets/hooks-shared.d.ts +60 -0
- package/src/lib/targets/hooks-shared.js +130 -0
- package/src/lib/targets/hooks-shared.js.map +1 -0
- package/src/lib/targets/mcp.d.ts +16 -0
- package/src/lib/targets/mcp.js +68 -0
- package/src/lib/targets/mcp.js.map +1 -0
- package/src/lib/targets/shared.d.ts +1 -9
- package/src/lib/targets/shared.js +4 -8
- package/src/lib/targets/shared.js.map +1 -1
- package/src/lib/timetrack-command.d.ts +11 -0
- package/src/lib/timetrack-command.js +1063 -0
- package/src/lib/timetrack-command.js.map +1 -0
- package/src/lib/timetrack-snapshot.d.ts +55 -0
- package/src/lib/timetrack-snapshot.js +120 -0
- package/src/lib/timetrack-snapshot.js.map +1 -0
- package/src/lib/timetrack.d.ts +561 -0
- package/src/lib/timetrack.js +279 -0
- package/src/lib/timetrack.js.map +1 -0
- package/content/skills/styleguide/STYLEGUIDE.md +0 -520
package/README.md
CHANGED
|
@@ -15,6 +15,17 @@ npx ethlete-agents init # writes ethlete-agents.config.json
|
|
|
15
15
|
npx ethlete-agents sync # writes the generated rules and skills
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
With Yarn 4.18+ and `npmMinimalAgeGate`, a freshly published release is blocked until it ages. Add
|
|
19
|
+
`npmPreapprovedPackages: ["@ethlete/*"]` to `.yarnrc.yml` to install new `@ethlete/*` versions right away.
|
|
20
|
+
|
|
21
|
+
`init` pre-fills `vars` with the lint, format and Storybook commands for your repo, using its package manager; check them before
|
|
22
|
+
the first sync. `sync`, `check`, `init` and `migrate` reject a flag they do not know and print the usage
|
|
23
|
+
instead of running, and a config file that does not parse is reported by name.
|
|
24
|
+
|
|
25
|
+
Adopting the rules in an existing app includes a styling audit: count the global CSS files,
|
|
26
|
+
classes and hardcoded colours that break the `app-styling` rule, tell the user the numbers, and
|
|
27
|
+
record the conversion as owed work.
|
|
28
|
+
|
|
18
29
|
Commit the generated files, and add a drift check to CI:
|
|
19
30
|
|
|
20
31
|
```bash
|
|
@@ -38,6 +49,11 @@ Marker-block files (`AGENTS.md`, `.github/copilot-instructions.md`) are only rew
|
|
|
38
49
|
between `<!-- ethlete:agent-rules:start -->` and `:end` - everything you wrote around
|
|
39
50
|
them survives.
|
|
40
51
|
|
|
52
|
+
When the repo has `@ethlete/cli` installed, `sync` also registers its design MCP server
|
|
53
|
+
(`et design mcp` through the `packageRunner`, for example `yarn et design mcp`) as `ethlete-design`: in `.mcp.json` for Claude Code and in
|
|
54
|
+
`.codex/config.toml` for Codex. Only that entry is written; other servers and tables
|
|
55
|
+
stay as they are.
|
|
56
|
+
|
|
41
57
|
## Migrating a repo to the AGENTS.md layout
|
|
42
58
|
|
|
43
59
|
`AGENTS.md` is the cross-tool standard, and Claude Code officially supports reading it
|
|
@@ -99,12 +115,26 @@ Prettier rewrites them and `check` then reports drift on every run:
|
|
|
99
115
|
`.agents/skills/` is the cross-tool baseline) and adds `claude`, `cursor` or `copilot`
|
|
100
116
|
when their directory exists; or list an explicit subset.
|
|
101
117
|
- **`profile`** - `"consumer"` (default) emits `scope: consumer` and `scope: both`
|
|
102
|
-
content. `"sdk"` emits
|
|
103
|
-
authoring-side guides are not overwritten by the consumer-side versions.
|
|
118
|
+
content. `"sdk"` emits `scope: sdk` and `scope: both`; the SDK repo uses it so its own
|
|
119
|
+
hand-written, authoring-side guides are not overwritten by the consumer-side versions.
|
|
104
120
|
- **`vars`** - values for the template tokens a guide declares. Defaults live in
|
|
105
121
|
`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.
|
|
107
|
-
|
|
122
|
+
skipped with a warning rather than emitted with a dangling placeholder. Some are
|
|
123
|
+
derived from the repo instead of defaulted - the `gitFlow*` ones from the `gitFlow`
|
|
124
|
+
block, `packageRunner` (`yarn`, `pnpm exec`, `bunx` or `npx`) from the root
|
|
125
|
+
`package.json`'s `packageManager` or the lockfile, and `commitRuleSource` / `commitValidation` from whether a commitlint config
|
|
126
|
+
exists (`commitlint.config.*`, `.commitlintrc*`, or a `commitlint` key in
|
|
127
|
+
`package.json`). Without one, the git-commit guide presents the format as the repo's
|
|
128
|
+
convention and never mentions a `commitlint` run - an agent that goes looking for a
|
|
129
|
+
promised validator and finds nothing reports the discrepancy instead of just
|
|
130
|
+
committing. `lintCommand` / `lintFixCommand` run the repo's `lint` script, else
|
|
131
|
+
`nx lint <project>` when Nx is installed, and `storybookStartCommand` runs its `storybook`
|
|
132
|
+
script; `sync` warns when a lint var names a script `package.json` does not have. Setting
|
|
133
|
+
any derived var in `vars` overrides the detection.
|
|
134
|
+
- **`exclude`** - rule or skill names to skip entirely for every configured agent and
|
|
135
|
+
developer. For example, `"exclude": ["git-flow", "handoff"]` prevents those skills
|
|
136
|
+
from being generated; the next `sync` also removes copies generated previously. Unknown
|
|
137
|
+
names produce a warning so a typo cannot silently leave a skill enabled.
|
|
108
138
|
- **`claudeMdImportsAgentsMd`** - set (usually by `migrate`) when `CLAUDE.md` is an
|
|
109
139
|
`@AGENTS.md` import or symlink; the claude target then skips `.claude/rules/ethlete/`
|
|
110
140
|
so the rules don't load twice. `sync` warns when the flag is set but the import is
|
|
@@ -113,56 +143,346 @@ Prettier rewrites them and `check` then reports drift on every run:
|
|
|
113
143
|
Content that declares `requires` is only emitted when those packages are installed, so
|
|
114
144
|
a repo without `@ethlete/query` never sees the query guide.
|
|
115
145
|
|
|
146
|
+
## Git flow
|
|
147
|
+
|
|
148
|
+
The branch convention lives in the same config, as one machine-readable grammar that the
|
|
149
|
+
CLI, a git hook, a CI job and `@ethlete/timetrack` all read:
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"gitFlow": {
|
|
154
|
+
"keyPrefixes": ["FIP"],
|
|
155
|
+
"baseBranches": { "development": "next", "production": "main" }
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npx ethlete-agents git-flow start FIP-2177 # name it and branch off the right base
|
|
162
|
+
npx ethlete-agents git-flow check # the current branch
|
|
163
|
+
npx ethlete-agents git-flow check "$SOURCE" --target "$TARGET"
|
|
164
|
+
npx ethlete-agents git-flow check --all # adoption report
|
|
165
|
+
npx ethlete-agents git-flow repair dev-game-codes --key FIP-2900
|
|
166
|
+
npx ethlete-agents git-flow explain feat/FIP-2177-user-management
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The shapes:
|
|
170
|
+
|
|
171
|
+
| Shape | Branch from | Merges into |
|
|
172
|
+
| ------------------------------------------ | ----------------------- | ------------------------------ |
|
|
173
|
+
| `feat/<KEY>-<subject>` | development | development |
|
|
174
|
+
| `sub/feat/<KEY>-<subject>/<KEY>-<subject>` | the main feature branch | the main feature branch |
|
|
175
|
+
| `release/<YYYY.MM.DD>` | development | development **and** production |
|
|
176
|
+
| `sub/release/<YYYY.MM.DD>/<KEY>-<subject>` | the release branch | the release branch |
|
|
177
|
+
| `hotfix/<KEY>-<subject>` | production | production |
|
|
178
|
+
|
|
179
|
+
**Why nested branches carry a `sub/` prefix.** Git refuses a ref that is both a branch and
|
|
180
|
+
a directory of branches, so `feat/FIP-2177-user-management/FIP-2178-reset` cannot exist
|
|
181
|
+
while `feat/FIP-2177-user-management` does - the push is rejected with `refname conflict`.
|
|
182
|
+
The prefix moves the nested tree out of the way while keeping the parent's full path inside
|
|
183
|
+
the child's name, so the merge request target is still derivable from the name alone. The
|
|
184
|
+
unprefixed spelling still parses, reports why it cannot exist, and `repair` moves it.
|
|
185
|
+
Configurable as `subPrefix`.
|
|
186
|
+
|
|
187
|
+
- **`enforcement`** - `"advisory"` (default) reports everything and blocks nothing, so a
|
|
188
|
+
repo can adopt the convention before it gates on it. `"gated"` applies each rule's
|
|
189
|
+
`severity`. A direct push to a base branch is blocked in both modes, and
|
|
190
|
+
`wrong-mr-target` can be raised to `"error"` on its own without ending the naming
|
|
191
|
+
grace period.
|
|
192
|
+
- **`keyPrefixes`** - the project's issue prefixes. Leave it empty and anything shaped
|
|
193
|
+
like `keyPattern` counts, which reads `chore/angular-22` as issue `ANGULAR-22`.
|
|
194
|
+
- **`severity`** - per rule: `unknown-type`, `missing-key`, `key-case`,
|
|
195
|
+
`missing-subject`, `type-alias`, `deprecated-prefix`, `release-date`,
|
|
196
|
+
`wrong-mr-target`, `protected-push`.
|
|
197
|
+
- **`deprecatedShapes`** - legacy spellings that still classify correctly and only earn a
|
|
198
|
+
rename suggestion. `dev-*` ships as the old spelling of a main feature branch.
|
|
199
|
+
|
|
200
|
+
The grammar is also importable on its own - `@ethlete/agent-rules/git-flow` has no
|
|
201
|
+
dependencies and touches no Node built-ins, so it runs in a browser:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { parseBranch, planStart, resolveGitFlowConfig } from '@ethlete/agent-rules/git-flow';
|
|
205
|
+
|
|
206
|
+
const { storyKey, taskKey, findings } = parseBranch({ branch, config: resolveGitFlowConfig() });
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### `start` - the prospective flow
|
|
210
|
+
|
|
211
|
+
`git-flow start <KEY>` reads the issue from Jira, computes the name from the grammar and
|
|
212
|
+
creates the branch off the correct base. It prints the plan first and asks before writing;
|
|
213
|
+
`--dry-run` stops after the plan and `--yes` skips the question. It refuses on a dirty
|
|
214
|
+
working tree, when the branch already exists, and when the base branch is nowhere to be
|
|
215
|
+
found.
|
|
216
|
+
|
|
217
|
+
A Task with a parent Story nests under that Story's feature branch, which therefore has to
|
|
218
|
+
exist already - `start` says so rather than inventing a parent. `--of <branch>` picks the
|
|
219
|
+
parent explicitly, `--hotfix` branches off production, `--release <date>` makes a release
|
|
220
|
+
branch, and `--subject <text>` skips Jira entirely.
|
|
221
|
+
|
|
222
|
+
The issue is read through the Timetrack app - see [Jira, through Timetrack](#jira-through-timetrack).
|
|
223
|
+
No repository holds a Jira credential, so the only thing the committed config still says about
|
|
224
|
+
Jira is how an issue type becomes a branch type:
|
|
225
|
+
|
|
226
|
+
```json
|
|
227
|
+
{
|
|
228
|
+
"jira": {
|
|
229
|
+
"typeByIssueType": { "Bug": "fix" }
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
- **`typeByIssueType`** - the branch type per Jira issue type; anything unlisted becomes
|
|
235
|
+
`feat`. `--type` overrides it per call.
|
|
236
|
+
|
|
237
|
+
The branch subject comes from the instance's own subject field, which Timetrack resolves
|
|
238
|
+
because the field id is a property of the instance rather than of this repo. An issue that
|
|
239
|
+
sets no subject falls back to its summary, and the printed plan says which of the two it used.
|
|
240
|
+
|
|
241
|
+
### `repair` - renaming a branch that does not conform
|
|
242
|
+
|
|
243
|
+
`git-flow repair [ref]` derives the conforming name (`--key FIP-2900` when the old name
|
|
244
|
+
carries no issue key, `--to <branch>` to override), renames the branch locally and on the
|
|
245
|
+
remote, and retargets the open merge requests aimed at it through the GitLab API.
|
|
246
|
+
`GITLAB_TOKEN` needs the `api` scope, and is sent only to the hosts named in `GITLAB_HOST`
|
|
247
|
+
(comma-separated) or, in a GitLab CI job, `CI_SERVER_HOST`.
|
|
248
|
+
|
|
249
|
+
Everything is checked before the first mutation, and it refuses rather than half-finishing:
|
|
250
|
+
|
|
251
|
+
- An open merge request whose **source** is the branch blocks the repair. GitLab cannot
|
|
252
|
+
move a merge request to another source branch, and closing it would lose its discussion -
|
|
253
|
+
merge or close it first.
|
|
254
|
+
- A branch that is pushed but whose merge requests cannot be listed (no token, or a remote
|
|
255
|
+
that is not a configured GitLab host) blocks too. `--no-mr-check` asserts that none point at it.
|
|
256
|
+
- If a retarget fails halfway, the old branch is still there and the recovery commands are
|
|
257
|
+
printed.
|
|
258
|
+
|
|
259
|
+
## Jira, through Timetrack
|
|
260
|
+
|
|
261
|
+
A Jira token in every repository is a secret nobody can rotate. There is one on this machine
|
|
262
|
+
instead, in the Timetrack app's keychain entry, and every repository asks the running app:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
npx ethlete-agents timetrack status # is it reachable, and what does it hold?
|
|
266
|
+
npx ethlete-agents timetrack issue FIP-2177 # summary, type, parent, branch subject
|
|
267
|
+
npx ethlete-agents timetrack search "password" # open issues of the picked projects
|
|
268
|
+
npx ethlete-agents timetrack project # which project does this repo log into?
|
|
269
|
+
npx ethlete-agents timetrack create --summary "…" # file a ticket with the instance's settings
|
|
270
|
+
npx ethlete-agents timetrack log --issue FIP-2177 --minutes 45
|
|
271
|
+
npx ethlete-agents timetrack approval <id> # where a queued write stands
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
`timetrack snapshot [from] [to] [--out <file>]` records the rows of real days (default: the last 7) with
|
|
275
|
+
the git HEAD and uncommitted files of the current repo; without `--out` the file lands in the app's data
|
|
276
|
+
directory under `snapshots/`, never in a repo. `timetrack snapshot --compare <file>` reads the same days
|
|
277
|
+
again and prints every row gone, new or changed (`--json` for a script); it exits 1 when anything changed.
|
|
278
|
+
|
|
279
|
+
`--json` prints the raw answer instead of lines. `git-flow start` uses the same channel.
|
|
280
|
+
|
|
281
|
+
**Every write waits for the user.** `create`, `log` and every other write answer at once with an
|
|
282
|
+
approval id and write nothing; the user approves or rejects the request in the app, and
|
|
283
|
+
`approval <id>` reads the outcome. A request still waiting at the end of its day expires.
|
|
284
|
+
`TIMETRACK_CLIENT` names the caller in the queue.
|
|
285
|
+
|
|
286
|
+
**How it connects.** The app binds a loopback socket and writes its port and a fresh token
|
|
287
|
+
into `agent.json` in its own data directory (`~/.local/share/io.ethlete.timetrack/` on Linux,
|
|
288
|
+
`~/Library/Application Support/…` on macOS, `%APPDATA%\…` on Windows), readable by its owner
|
|
289
|
+
alone. The token lives no longer than the run, so a caller left over from an earlier one is
|
|
290
|
+
refused rather than trusted, and a request carrying an `Origin` header is refused outright -
|
|
291
|
+
a page the user happens to have open must not reach Jira through a port it guessed.
|
|
292
|
+
`TIMETRACK_AGENT_DISCOVERY` names the file for a machine that keeps it elsewhere.
|
|
293
|
+
|
|
294
|
+
**What the app answers, not this package.** The instance, the credentials, the picked
|
|
295
|
+
projects, the subject field and the ticket shape are all settings there. That is what makes
|
|
296
|
+
`create` file a ticket indistinguishable from one the app filed, and it is why none of them
|
|
297
|
+
appear in a repo's config any more.
|
|
298
|
+
|
|
299
|
+
**`log` writes to the day, not to Tempo.** It adds the same row the app's own timeline draws
|
|
300
|
+
for work nothing observed, and the user reviews the day before syncing it. A worklog posted
|
|
301
|
+
behind the review would double-book against whatever the evidence already proposed for that
|
|
302
|
+
hour.
|
|
303
|
+
|
|
304
|
+
**When the app is not running** every command says so and exits non-zero. There is no
|
|
305
|
+
fallback to an environment variable on purpose - a fallback is how the per-repo secret comes
|
|
306
|
+
back.
|
|
307
|
+
|
|
116
308
|
## Hooks (opt-in)
|
|
117
309
|
|
|
118
|
-
|
|
119
|
-
|
|
310
|
+
Hooks run commands on the developer's machine, so none are emitted by default - opt in
|
|
311
|
+
per hook in the config:
|
|
120
312
|
|
|
121
313
|
```json
|
|
122
314
|
{
|
|
123
|
-
"hooks": ["context-warning"]
|
|
315
|
+
"hooks": ["context-warning", "subagent-model-policy"]
|
|
124
316
|
}
|
|
125
317
|
```
|
|
126
318
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
319
|
+
They are emitted for whichever of the `claude` and `codex` targets is enabled:
|
|
320
|
+
|
|
321
|
+
| Target | Script | Registered in |
|
|
322
|
+
| -------- | ------------------------ | ----------------------- |
|
|
323
|
+
| `claude` | `.claude/hooks/ethlete/` | `.claude/settings.json` |
|
|
324
|
+
| `codex` | `.codex/hooks/ethlete/` | `.codex/hooks.json` |
|
|
325
|
+
|
|
326
|
+
Your own entries in those files are left untouched; removing the name from `hooks`
|
|
327
|
+
unregisters and deletes the script again. Codex only loads project-local hooks once the
|
|
328
|
+
`.codex/` layer is trusted, and honours `[features] hooks = false`.
|
|
130
329
|
|
|
131
330
|
Available hooks:
|
|
132
331
|
|
|
133
|
-
- **`context-warning`** - warns once per tier (and instructs
|
|
134
|
-
context crosses 70% / 85% of the token budget, recommending
|
|
135
|
-
capped at the 200k long-context pricing boundary: on 1M-window models every
|
|
136
|
-
past 200k input tokens bills the whole context at a premium rate, so the
|
|
137
|
-
fire at ~140k/~170k instead of deep into the expensive range.
|
|
332
|
+
- **`context-warning`** - warns once per tier (and instructs the agent) when the session
|
|
333
|
+
context crosses 70% / 85% / 95% of the token budget, recommending a handoff. Under Claude the
|
|
334
|
+
budget is capped at the 200k long-context pricing boundary: on 1M-window models every
|
|
335
|
+
request past 200k input tokens bills the whole context at a premium rate, so the
|
|
336
|
+
warnings fire at ~140k/~170k instead of deep into the expensive range. Codex uses
|
|
337
|
+
the model-specific 272k pricing boundary for GPT-5.6, GPT-5.5 and GPT-5.4, and the
|
|
338
|
+
rollout's reported context window for models without that pricing rule.
|
|
339
|
+
|
|
340
|
+
The 85% tier asks the agent to choose - finish the task, or hand off - because a session
|
|
341
|
+
is often a step or two from done when it fires. The 95% tier takes that choice away. Every
|
|
342
|
+
prompt after the first warning carries a short reminder instead of a new warning: the
|
|
343
|
+
remaining budget, and how to work so that either ending stays cheap. The `handoff` skill
|
|
344
|
+
calls this handoff mode.
|
|
345
|
+
|
|
346
|
+
Under Claude the hook runs on four events, because a warning that only a user prompt can
|
|
347
|
+
trigger never reaches a long autonomous run. `SessionStart` names the budget before the
|
|
348
|
+
first request. `UserPromptSubmit` carries the tiered warning and the reminder.
|
|
349
|
+
`PostToolBatch` fires between the agent's own model requests, so it learns the count while
|
|
350
|
+
it works. `Stop` fires as a turn ends over 85% and the turn then continues, so the agent
|
|
351
|
+
must take one of three and say which: finish, hand off, or cross the boundary on purpose
|
|
352
|
+
for a reason it states. Each event speaks once per tier. Codex has none of the three, so
|
|
353
|
+
its registration stays on `UserPromptSubmit`.
|
|
354
|
+
|
|
355
|
+
Two things are Claude-only: the separate user-facing line (Codex documents only
|
|
356
|
+
`additionalContext`, so there the warning is folded into the text the model is told to
|
|
357
|
+
relay), and the auto-mode escalation that writes the handoff file unprompted - Codex's
|
|
358
|
+
`permission_mode` values are undocumented, so no value enables it.
|
|
359
|
+
|
|
360
|
+
Codex sub-agents are tracked by their own thread ids even though they share the root
|
|
361
|
+
session id. A sub-agent warning tells that child to report its state to the parent; it
|
|
362
|
+
never tells the user or the main agent to hand off the root session.
|
|
363
|
+
|
|
364
|
+
- **`subagent-model-policy`** (Claude Code only) - gates every call to the subagent tool.
|
|
365
|
+
A call that names no `model` is denied and the model table is returned instead: haiku for
|
|
366
|
+
a lookup, opus for real work, sonnet in between, fable for judgment-heavy work. Without
|
|
367
|
+
it a subagent inherits the model leading the session, which is how an expensive model
|
|
368
|
+
ends up doing every grep it delegates; with it, that mistake costs one tool call.
|
|
369
|
+
|
|
370
|
+
A call that names `fable` asks the user - it is the most expensive option, and only the
|
|
371
|
+
user knows whether the task is judgment-heavy. A fork and an agent type whose own
|
|
372
|
+
definition sets a model both pass untouched, because `model` would change nothing there.
|
|
138
373
|
|
|
139
374
|
Hooks can be turned off per machine - see the local config below.
|
|
140
375
|
|
|
376
|
+
## Git hooks (opt-in)
|
|
377
|
+
|
|
378
|
+
Separate from the agent hooks above, and opt-in for the same reason - a generated block
|
|
379
|
+
that can reject a push is a higher-stakes artifact than a markdown one:
|
|
380
|
+
|
|
381
|
+
```json
|
|
382
|
+
{
|
|
383
|
+
"gitHooks": ["pre-push", "post-checkout"]
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
Each one is written as an `# ethlete:git-flow:start` … `end` block **appended** to your
|
|
388
|
+
`.husky/<name>`, so an existing hook there (a git-lfs hook, typically) keeps working and
|
|
389
|
+
keeps reading stdin first - which is why the block never reads stdin itself. Removing the
|
|
390
|
+
name from `gitHooks` takes the block back out and leaves the rest of the file alone.
|
|
391
|
+
|
|
392
|
+
- **`pre-push`** - runs `git-flow check --push` on the current branch. In `advisory` mode
|
|
393
|
+
only a direct push to a base branch can actually stop it.
|
|
394
|
+
- **`post-checkout`** - reports a non-conforming name on a branch that is on no remote yet,
|
|
395
|
+
which is the whole window in which renaming it is free.
|
|
396
|
+
|
|
397
|
+
Only `.husky/` is written, never `.git/hooks/`: the generated files are committed and CI's
|
|
398
|
+
`check` diffs them, so a hook outside the working tree could never be in sync. Without a
|
|
399
|
+
`.husky/` directory `sync` warns and writes nothing. The block calls
|
|
400
|
+
`node_modules/.bin/ethlete-agents` directly rather than through `npx`, so a repo where the
|
|
401
|
+
package is missing gets silence instead of a registry lookup that would fail the push.
|
|
402
|
+
`ETHLETE_GIT_FLOW_SKIP=1` silences both hooks on one machine.
|
|
403
|
+
|
|
404
|
+
## CI job
|
|
405
|
+
|
|
406
|
+
On GitLab, the merge request target is the half no local hook can see. The job needs no
|
|
407
|
+
configuration beyond the predefined variables:
|
|
408
|
+
|
|
409
|
+
```yaml
|
|
410
|
+
Git Flow:
|
|
411
|
+
stage: Checks
|
|
412
|
+
rules:
|
|
413
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
414
|
+
allow_failure: true
|
|
415
|
+
script:
|
|
416
|
+
- >
|
|
417
|
+
npx ethlete-agents git-flow check "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME"
|
|
418
|
+
--target "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME"
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
`allow_failure: true` on top of `advisory` mode is deliberate belt and braces: the job
|
|
422
|
+
reports for a whole grace period before it can ever be the reason a merge request is red.
|
|
423
|
+
|
|
424
|
+
## Output styles (Claude Code)
|
|
425
|
+
|
|
426
|
+
An output style replaces Claude Code's own answer style for a whole session. This package
|
|
427
|
+
ships one - **`ste-clarity`**, which writes every answer in ASD-STE100 Simplified Technical
|
|
428
|
+
English - and installs it into the machine's Claude config rather than into a repo, because
|
|
429
|
+
that is where Claude Code reads styles from:
|
|
430
|
+
|
|
431
|
+
```bash
|
|
432
|
+
npx ethlete-agents output-style # install ste-clarity and switch to it
|
|
433
|
+
npx ethlete-agents output-style --dry-run # print what would change
|
|
434
|
+
npx ethlete-agents output-style --remove # take it back out
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Two files, both under `~/.claude` (or `$CLAUDE_CONFIG_DIR`, or `--config-dir <path>`):
|
|
438
|
+
|
|
439
|
+
| File | What changes |
|
|
440
|
+
| ------------------------------ | ------------------------------------------------------------------ |
|
|
441
|
+
| `output-styles/ste-clarity.md` | The style itself, written whole |
|
|
442
|
+
| `settings.json` | `"outputStyle": "ste-clarity"` - every other setting is left alone |
|
|
443
|
+
|
|
444
|
+
- **`--no-activate`** writes the style file but not the setting, so switching to it is then
|
|
445
|
+
`/output-style ste-clarity` inside a session.
|
|
446
|
+
- A style file this command did not write is never overwritten or deleted - it says so and
|
|
447
|
+
stops, and `--force` is the way through. A copy of the same style that differs only in
|
|
448
|
+
layout still counts as its own.
|
|
449
|
+
- Nothing here is repo-local, so `sync` and `check` neither write nor diff it. `--remove`
|
|
450
|
+
clears `outputStyle` only while it still points at that style.
|
|
451
|
+
|
|
452
|
+
**Claude Code only.** Codex has no output-style mechanism - everything it is told comes
|
|
453
|
+
from `AGENTS.md`, which `sync` already writes.
|
|
454
|
+
|
|
141
455
|
## Per-machine local config
|
|
142
456
|
|
|
143
|
-
A gitignored `ethlete-agents.config.local.json` at the repo root holds the
|
|
144
|
-
|
|
457
|
+
A gitignored `ethlete-agents.config.local.json` at the repo root holds the agent behaviour
|
|
458
|
+
that differs per developer, without touching any committed file:
|
|
145
459
|
|
|
146
460
|
```json
|
|
147
461
|
{
|
|
148
462
|
"disableHooks": true,
|
|
149
|
-
"
|
|
463
|
+
"disableAutoHandoffSave": true
|
|
150
464
|
}
|
|
151
465
|
```
|
|
152
466
|
|
|
153
467
|
- **`disableHooks`** - `true` disables every generated hook; an array
|
|
154
468
|
(`["context-warning"]`) just the named ones. The hook scripts read the file at
|
|
155
469
|
runtime, so toggling takes effect on the next prompt - no `sync` needed.
|
|
156
|
-
- **`
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
resolved from the repo root.
|
|
470
|
+
- **`disableAutoHandoffSave`** - keeps the `context-warning` hook's tiered warnings but
|
|
471
|
+
drops the auto-mode escalation: at the critical tier it recommends `/ethlete-handoff`
|
|
472
|
+
instead of saving the handoff file itself.
|
|
160
473
|
|
|
161
474
|
Everything in this file is read at runtime, never by `sync`: the generated files stay
|
|
162
475
|
identical on every machine and in CI, which is what lets `check` diff them. That is also
|
|
163
|
-
why the file takes nothing beyond these keys - `sync`/`check` warn about unknown keys
|
|
164
|
-
|
|
165
|
-
|
|
476
|
+
why the file takes nothing beyond these keys - `sync`/`check` warn about unknown keys. Add
|
|
477
|
+
the filename to your repo's `.gitignore`.
|
|
478
|
+
|
|
479
|
+
### Where the sibling checkouts live
|
|
480
|
+
|
|
481
|
+
`sdkSourcePath`, `apiRepoPaths` and `apiRepoBranches` used to live in the file above. They
|
|
482
|
+
moved to `ethlete.config.local.json`, which [`@ethlete/cli`](../cli/README.md) owns, because
|
|
483
|
+
`et api` needs the same values the skills do. `sync`/`check` still read that file and warn
|
|
484
|
+
about a path that is missing or is not the checkout it claims to be, and they tell you to
|
|
485
|
+
move any of the three keys still found in the agents file.
|
|
166
486
|
|
|
167
487
|
## Authoring content
|
|
168
488
|
|
|
@@ -178,11 +498,24 @@ scope: consumer # consumer | sdk | both
|
|
|
178
498
|
requires: ['@ethlete/core'] # optional
|
|
179
499
|
paths: ['**/*.css'] # optional; becomes Claude `paths` and Cursor `globs` on rules
|
|
180
500
|
vars: [docsBaseUrl] # optional
|
|
501
|
+
modelInvocation: false # optional; a skill only the user may start
|
|
181
502
|
---
|
|
182
503
|
```
|
|
183
504
|
|
|
505
|
+
A content file vendored from another repository keeps its upstream wording apart from the
|
|
506
|
+
link syntax, and names its source in the body. `THIRD-PARTY-LICENSES.md` lists each one,
|
|
507
|
+
its upstream commit and its license.
|
|
508
|
+
|
|
509
|
+
`content/output-styles/<name>.md` is a third kind, and the only one this package does not
|
|
510
|
+
compile: it is a Claude Code output style verbatim, so its frontmatter is Claude's
|
|
511
|
+
(`name`, `description`, `keep-coding-instructions`) and `output-style` installs the file
|
|
512
|
+
as it is, apart from a marker line that records where it came from.
|
|
513
|
+
|
|
184
514
|
In a body, `{% varName %}` substitutes a variable, `{% skill:other-name %}` links to
|
|
185
|
-
another
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
515
|
+
another emitted package guide, and `{% resource:file.mjs %}` links to a bundled file.
|
|
516
|
+
Skill links are required dependencies: after scope, package, variable, and exclusion
|
|
517
|
+
filtering, `sync` and `check` fail with the source and missing target instead of emitting
|
|
518
|
+
a dangling name. Optional guidance must be self-contained when its guide is absent. Use
|
|
519
|
+
the structured marker instead of a plain `` `name` skill`` reference so validation can
|
|
520
|
+
see it. The delimiter is `{% … %}`, not `{{ … }}`, so Angular templates in examples pass
|
|
521
|
+
through untouched. Resource files get variable substitution too, but no links.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Third-party content
|
|
2
|
+
|
|
3
|
+
Content in this package that comes from another repository, with the license it keeps.
|
|
4
|
+
To update one, copy it again from upstream and record the new commit here.
|
|
5
|
+
|
|
6
|
+
## [mattpocock/skills](https://github.com/mattpocock/skills)
|
|
7
|
+
|
|
8
|
+
Commit `3cca18b` (2026-09-04). Vendored skills, kept as written upstream apart from the
|
|
9
|
+
generator's own link syntax:
|
|
10
|
+
|
|
11
|
+
- `content/skills/grill-with-docs` - the entry point; calls the other two.
|
|
12
|
+
- `content/skills/grilling` - plus our own section "Local addition" after the upstream footer.
|
|
13
|
+
- `content/skills/domain-modeling`
|
|
14
|
+
|
|
15
|
+
MIT License
|
|
16
|
+
|
|
17
|
+
Copyright (c) 2026 Matt Pocock
|
|
18
|
+
|
|
19
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
20
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
21
|
+
in the Software without restriction, including without limitation the rights
|
|
22
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
23
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
24
|
+
furnished to do so, subject to the following conditions:
|
|
25
|
+
|
|
26
|
+
The above copyright notice and this permission notice shall be included in all
|
|
27
|
+
copies or substantial portions of the Software.
|
|
28
|
+
|
|
29
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
30
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
31
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
32
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
33
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
34
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
35
|
+
SOFTWARE.
|
package/content/defaults.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"formatCommand": "npx prettier --write <files>",
|
|
5
5
|
"storybookUrl": "http://localhost:6006",
|
|
6
6
|
"storybookStartCommand": "npm run storybook",
|
|
7
|
-
"docsBaseUrl": "https://ethlete-sdk-docs.web.app",
|
|
7
|
+
"docsBaseUrl": "https://ethlete-sdk-docs-next.web.app",
|
|
8
8
|
"sdkStorybookUrl": "https://ethlete-sdk.web.app",
|
|
9
9
|
"handoffDir": ".agents/handoffs"
|
|
10
10
|
}
|
|
@@ -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
|