@ethlete/agent-rules 0.1.0-next.5 → 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.
- package/CHANGELOG.md +10 -0
- package/README.md +167 -3
- package/content/git-hooks/post-checkout.sh +10 -0
- package/content/git-hooks/pre-push.sh +5 -0
- package/content/hooks/context-warning.py +39 -11
- package/content/skills/git-flow/SKILL.md +83 -0
- package/content/skills/handoff/SKILL.md +4 -0
- package/content/skills/query/SKILL.md +17 -13
- package/package.json +12 -1
- package/src/index.js +10 -5
- package/src/index.js.map +1 -1
- package/src/lib/config.d.ts +28 -0
- package/src/lib/config.js +20 -3
- package/src/lib/config.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 +5 -0
- package/src/lib/git-flow/index.js +9 -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 +274 -0
- package/src/lib/git-flow/parse.js.map +1 -0
- package/src/lib/git-flow/start.d.ts +22 -0
- package/src/lib/git-flow/start.js +24 -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 +157 -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 +150 -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 +147 -0
- package/src/lib/git-flow-start.js.map +1 -0
- package/src/lib/git.d.ts +27 -0
- package/src/lib/git.js +49 -0
- package/src/lib/git.js.map +1 -0
- package/src/lib/gitlab.d.ts +35 -0
- package/src/lib/gitlab.js +98 -0
- package/src/lib/gitlab.js.map +1 -0
- package/src/lib/jira.d.ts +28 -0
- package/src/lib/jira.js +83 -0
- package/src/lib/jira.js.map +1 -0
- package/src/lib/plan.js +20 -2
- package/src/lib/plan.js.map +1 -1
- package/src/lib/prompt.d.ts +8 -0
- package/src/lib/prompt.js +27 -0
- package/src/lib/prompt.js.map +1 -0
- package/src/lib/render.d.ts +12 -1
- package/src/lib/render.js +23 -6
- package/src/lib/render.js.map +1 -1
- package/src/lib/targets/git-hooks.d.ts +25 -0
- package/src/lib/targets/git-hooks.js +70 -0
- package/src/lib/targets/git-hooks.js.map +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
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
|
+
|
|
3
13
|
## 0.1.0-next.5
|
|
4
14
|
|
|
5
15
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -113,6 +113,118 @@ 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
230
|
Hooks run commands on the developer's machine, so none are emitted by default - opt in
|
|
@@ -152,6 +264,54 @@ Available hooks:
|
|
|
152
264
|
|
|
153
265
|
Hooks can be turned off per machine - see the local config below.
|
|
154
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
|
+
|
|
155
315
|
## Per-machine local config
|
|
156
316
|
|
|
157
317
|
A gitignored `ethlete-agents.config.local.json` at the repo root holds the values that
|
|
@@ -160,7 +320,8 @@ differ per developer, without touching any committed file:
|
|
|
160
320
|
```json
|
|
161
321
|
{
|
|
162
322
|
"disableHooks": true,
|
|
163
|
-
"sdkSourcePath": "/absolute/path/to/ethlete-sdk"
|
|
323
|
+
"sdkSourcePath": "/absolute/path/to/ethlete-sdk",
|
|
324
|
+
"jira": { "email": "you@example.com", "token": "…" }
|
|
164
325
|
}
|
|
165
326
|
```
|
|
166
327
|
|
|
@@ -168,8 +329,11 @@ differ per developer, without touching any committed file:
|
|
|
168
329
|
(`["context-warning"]`) just the named ones. The hook scripts read the file at
|
|
169
330
|
runtime, so toggling takes effect on the next prompt - no `sync` needed.
|
|
170
331
|
- **`disableAutoHandoffSave`** - keeps the `context-warning` hook's tiered warnings but
|
|
171
|
-
drops the auto-mode escalation: at the critical tier it recommends `/handoff`
|
|
172
|
-
of saving the handoff file itself.
|
|
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.
|
|
173
337
|
- **`sdkSourcePath`** - a local `ethlete-sdk` checkout. The `sdk-source` and
|
|
174
338
|
`sdk-local-build` skills read it when the agent needs the SDK's own sources, or has to
|
|
175
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
|
|
@@ -21,8 +21,10 @@ that matter here, all captured in AGENT_PROFILES:
|
|
|
21
21
|
bills the entire context at the premium rate, which costs far more than
|
|
22
22
|
handing off into a fresh session ever would. Codex has no such boundary, so
|
|
23
23
|
its budget is just the window.
|
|
24
|
-
* How a handoff is invoked. Claude has
|
|
25
|
-
and reads the skill from disk.
|
|
24
|
+
* How a handoff is invoked. Claude has a slash command and /clear; Codex has
|
|
25
|
+
neither and reads the skill from disk. The skill's own name differs per repo
|
|
26
|
+
(`ethlete-handoff` where the generator installed it, `handoff` where the repo
|
|
27
|
+
ships its own copy), so it is resolved at runtime — see handoff_skill().
|
|
26
28
|
|
|
27
29
|
At the critical tier, if the session's permission_mode is "auto", the
|
|
28
30
|
instruction escalates from "recommend" to "just do it": auto mode already
|
|
@@ -85,14 +87,14 @@ AGENT_PROFILES = {
|
|
|
85
87
|
"default_window": None,
|
|
86
88
|
"auto_modes": ("auto",),
|
|
87
89
|
"emits_system_message": True,
|
|
88
|
-
"act_now": "Run /handoff now and continue in a fresh session.",
|
|
89
|
-
"act_later": "consider /handoff to continue in a fresh session",
|
|
90
|
-
"recommend": "recommend the user run /handoff to save state and start a fresh session",
|
|
91
|
-
"suggest": "suggest the user run /handoff to save state and start a fresh session",
|
|
90
|
+
"act_now": "Run /{handoff} now and continue in a fresh session.",
|
|
91
|
+
"act_later": "consider /{handoff} to continue in a fresh session",
|
|
92
|
+
"recommend": "recommend the user run /{handoff} to save state and start a fresh session",
|
|
93
|
+
"suggest": "suggest the user run /{handoff} to save state and start a fresh session",
|
|
92
94
|
"save_now": (
|
|
93
95
|
"run the handoff skill's save mode right now (finish only an in-flight atomic "
|
|
94
96
|
"edit first, nothing new). Then tell the user exactly which handoff file was "
|
|
95
|
-
"written and that they should run /clear, then '/handoff resume <slug>', to "
|
|
97
|
+
"written and that they should run /clear, then '/{handoff} resume <slug>', to "
|
|
96
98
|
"continue — clearing and resuming can't be done programmatically, so this is "
|
|
97
99
|
"the one step still on them."
|
|
98
100
|
),
|
|
@@ -112,15 +114,15 @@ AGENT_PROFILES = {
|
|
|
112
114
|
"act_later": "consider saving a handoff and continuing in a fresh session",
|
|
113
115
|
"recommend": (
|
|
114
116
|
"tell the user you are near the context limit, then follow "
|
|
115
|
-
".agents/skills/
|
|
117
|
+
".agents/skills/{handoff}/SKILL.md to save a handoff and have them start a "
|
|
116
118
|
"fresh session"
|
|
117
119
|
),
|
|
118
120
|
"suggest": (
|
|
119
|
-
"suggest saving a handoff via .agents/skills/
|
|
121
|
+
"suggest saving a handoff via .agents/skills/{handoff}/SKILL.md and "
|
|
120
122
|
"continuing in a fresh session"
|
|
121
123
|
),
|
|
122
124
|
"save_now": (
|
|
123
|
-
"follow .agents/skills/
|
|
125
|
+
"follow .agents/skills/{handoff}/SKILL.md and save a handoff right now "
|
|
124
126
|
"(finish only an in-flight atomic edit first, nothing new). Then tell the user "
|
|
125
127
|
"exactly which handoff file was written and that they should start a fresh "
|
|
126
128
|
"codex session and resume from it."
|
|
@@ -129,6 +131,29 @@ AGENT_PROFILES = {
|
|
|
129
131
|
}
|
|
130
132
|
|
|
131
133
|
|
|
134
|
+
def handoff_skill(root):
|
|
135
|
+
"""Name of the handoff skill installed in this repo.
|
|
136
|
+
|
|
137
|
+
The generator prefixes every skill it writes, so the command is /ethlete-handoff in a
|
|
138
|
+
consumer repo — but a repo that excludes the generated copy and ships its own (the SDK
|
|
139
|
+
itself) has a plain /handoff instead. Naming the wrong one sends the user to a slash
|
|
140
|
+
command that does not exist, so it is read off disk rather than assumed.
|
|
141
|
+
"""
|
|
142
|
+
for name in ("ethlete-handoff", "handoff"):
|
|
143
|
+
for skills_dir in (".claude/skills", ".agents/skills"):
|
|
144
|
+
if os.path.isdir(os.path.join(root or ".", skills_dir, name)):
|
|
145
|
+
return name
|
|
146
|
+
return "ethlete-handoff"
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def resolve_profile(profile, skill):
|
|
150
|
+
"""Fills the installed handoff skill's name into a profile's message fragments."""
|
|
151
|
+
return {
|
|
152
|
+
key: value.replace("{handoff}", skill) if isinstance(value, str) else value
|
|
153
|
+
for key, value in profile.items()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
132
157
|
def agent_name(argv):
|
|
133
158
|
"""The --agent value, defaulting to claude — older registrations pass no flag."""
|
|
134
159
|
for index, arg in enumerate(argv):
|
|
@@ -336,10 +361,13 @@ def main():
|
|
|
336
361
|
return
|
|
337
362
|
|
|
338
363
|
data = json.load(sys.stdin)
|
|
339
|
-
|
|
364
|
+
root = repo_root(data)
|
|
365
|
+
local_config = load_local_config(root)
|
|
340
366
|
if disabled_locally(local_config):
|
|
341
367
|
return
|
|
342
368
|
|
|
369
|
+
profile = resolve_profile(profile, handoff_skill(root))
|
|
370
|
+
|
|
343
371
|
transcript_path = data.get("transcript_path")
|
|
344
372
|
session_id = data.get("session_id", "unknown")
|
|
345
373
|
auto_mode = data.get("permission_mode") in profile["auto_modes"] and not auto_handoff_save_disabled(local_config)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: git-flow
|
|
3
|
+
description: How to name and base a branch in this repo, and what its merge request must target - feature, sub-feature, release, release fix and hotfix. Read BEFORE creating a branch, opening a merge request, or choosing what to branch from (e.g. the user says "start work on FIP-2177" or "open an MR").
|
|
4
|
+
kind: skill
|
|
5
|
+
scope: both
|
|
6
|
+
vars: [gitFlowDevelopmentBranch, gitFlowProductionBranch, gitFlowTypes, gitFlowEnforcement, gitFlowSubPrefix]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Git flow
|
|
10
|
+
|
|
11
|
+
Work is organised in two levels: a **main feature branch** per Jira Story, into which
|
|
12
|
+
**sub-feature branches** (one per Task) are merged after review. The main feature branch is
|
|
13
|
+
what gets deployed to a test environment and, once accepted, merged into
|
|
14
|
+
`{%gitFlowDevelopmentBranch%}`.
|
|
15
|
+
|
|
16
|
+
**Never name a branch by hand - `start` does it from the grammar:**
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx ethlete-agents git-flow start FIP-2178 # reads the issue, names it, branches off the right base
|
|
20
|
+
npx ethlete-agents git-flow check # is the current branch conforming?
|
|
21
|
+
npx ethlete-agents git-flow explain <branch> # what the parser sees, and what it expects
|
|
22
|
+
npx ethlete-agents git-flow repair <branch> # rename a non-conforming one, retarget its MRs
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`start` prints its plan (branch, base, MR target) and asks before writing anything; add
|
|
26
|
+
`--dry-run` to see the plan alone. It refuses on a dirty working tree, and a Task nests under
|
|
27
|
+
its parent Story's branch, so that branch has to exist first.
|
|
28
|
+
|
|
29
|
+
## The five shapes
|
|
30
|
+
|
|
31
|
+
| Shape | Issue | Branch from | MR targets |
|
|
32
|
+
| --------------------------------------------------------------------------------- | ----- | ------------------------------ | ---------------------------------------------------------------- |
|
|
33
|
+
| `feat/FIP-2177-user-management` | Story | `{%gitFlowDevelopmentBranch%}` | `{%gitFlowDevelopmentBranch%}` |
|
|
34
|
+
| `{%gitFlowSubPrefix%}/feat/FIP-2177-user-management/FIP-2178-user-password-reset` | Task | the main feature branch | the main feature branch |
|
|
35
|
+
| `release/2026.04.28` | - | `{%gitFlowDevelopmentBranch%}` | `{%gitFlowDevelopmentBranch%}` and `{%gitFlowProductionBranch%}` |
|
|
36
|
+
| `{%gitFlowSubPrefix%}/release/2026.04.28/FIP-2222-button-not-visible` | Bug | the release branch | the release branch |
|
|
37
|
+
| `hotfix/FIP-2799-password-recovery-broken` | Bug | `{%gitFlowProductionBranch%}` | `{%gitFlowProductionBranch%}` |
|
|
38
|
+
|
|
39
|
+
- The **type** is one of {%gitFlowTypes%}, and a nested branch carries its parent's **full**
|
|
40
|
+
name - that path is what makes the parent machine-readable.
|
|
41
|
+
- The **key** is the Jira issue, uppercase, immediately after the type. The **subject** is the
|
|
42
|
+
Story's subject meta field in kebab-case, not a paraphrase of the summary.
|
|
43
|
+
- A branch with no key still works, but nothing can attribute it to an issue. Add the key.
|
|
44
|
+
|
|
45
|
+
### Why a nested branch starts with `{%gitFlowSubPrefix%}/`
|
|
46
|
+
|
|
47
|
+
Git refuses a ref that is both a branch and a directory of branches. So
|
|
48
|
+
`feat/FIP-2177-user-management/FIP-2178-user-password-reset` **cannot exist** while
|
|
49
|
+
`feat/FIP-2177-user-management` does - git rejects it locally and the push comes back as
|
|
50
|
+
`refname conflict`. The prefix moves the nested tree out of the way and keeps the parent's
|
|
51
|
+
full path inside the child's name, so the MR target is still readable off the name.
|
|
52
|
+
|
|
53
|
+
Do not "fix" a name by dropping the prefix; the branch it produces cannot be created.
|
|
54
|
+
|
|
55
|
+
## Rules that are not about naming
|
|
56
|
+
|
|
57
|
+
- **Never rebase a shared branch.** A main feature branch is published and other people's
|
|
58
|
+
sub-features are based on it - bring it up to date by **merging**
|
|
59
|
+
`{%gitFlowDevelopmentBranch%}` into it. Rebase only a local branch you have not pushed.
|
|
60
|
+
- **A sub-feature merges into its parent, never straight into
|
|
61
|
+
`{%gitFlowDevelopmentBranch%}`** - that is the whole point of the two levels, since the
|
|
62
|
+
parent is what gets tested as a unit.
|
|
63
|
+
- **Delete the source branch on merge** (the checkbox in the merge request) for sub-features,
|
|
64
|
+
release fixes and merged main features.
|
|
65
|
+
- **A hotfix branches off `{%gitFlowProductionBranch%}`** and, after rollout, that branch is
|
|
66
|
+
merged back into `{%gitFlowDevelopmentBranch%}` so the two do not drift.
|
|
67
|
+
- **Never push directly to `{%gitFlowDevelopmentBranch%}` or
|
|
68
|
+
`{%gitFlowProductionBranch%}`.** Open a merge request; it needs another developer's review.
|
|
69
|
+
|
|
70
|
+
## Enforcement is `{%gitFlowEnforcement%}`
|
|
71
|
+
|
|
72
|
+
In `advisory` mode every rule reports and nothing blocks - the older naming shapes (`feature/`
|
|
73
|
+
instead of `feat/`, a lowercase key, no key at all, a `dev-*` integration branch) are accepted
|
|
74
|
+
on purpose while the team adapts. Report the suggestion, do not "fix" someone's existing
|
|
75
|
+
branch unasked - `repair` is how a rename happens, and only when asked for.
|
|
76
|
+
|
|
77
|
+
`dev-*` is the **old spelling of a main feature branch**, not a stray name: sub-features
|
|
78
|
+
legitimately target it. Leave a live one alone.
|
|
79
|
+
|
|
80
|
+
## Commit messages are a separate thing
|
|
81
|
+
|
|
82
|
+
Commits stay conventional (`feat(platform): Prefer a player's common name`) and carry **no
|
|
83
|
+
issue key** - the branch already has it. See {%skill:git-commit%}.
|
|
@@ -15,6 +15,10 @@ continue in a fresh session. Two modes:
|
|
|
15
15
|
- **save** ("handoff", "wrap up") - write a handoff file.
|
|
16
16
|
- **resume** ("continue from the handoff") - read one and continue the work.
|
|
17
17
|
|
|
18
|
+
This skill is installed as `ethlete-handoff`, so under Claude Code the commands
|
|
19
|
+
are `/ethlete-handoff` and `/ethlete-handoff resume [name]` - there is no
|
|
20
|
+
`/handoff`. Name them that way whenever you tell the user what to run.
|
|
21
|
+
|
|
18
22
|
Handoff files live in `{%handoffDir%}/` (gitignored - they are personal,
|
|
19
23
|
ephemeral working state, not team docs).
|
|
20
24
|
|
|
@@ -16,17 +16,17 @@ paged queries, bearer auth, GraphQL, and a socket.io realtime client.
|
|
|
16
16
|
non-trivial query work.** This guide is the index plus the load-bearing facts, so you
|
|
17
17
|
don't re-derive them from source.
|
|
18
18
|
|
|
19
|
-
| Page | Covers
|
|
20
|
-
| ---------------------------------------------------------------------- |
|
|
21
|
-
| {%docsBaseUrl%}/query/ | Overview + the two-generations note
|
|
22
|
-
| {%docsBaseUrl%}/query/queries | **Start here** - client, creators, the query object's signals, auto-execution
|
|
23
|
-
| {%docsBaseUrl%}/query/features | `withArgs`, `withPolling`, `withAutoRefresh`, side-effect handlers
|
|
24
|
-
| {%docsBaseUrl%}/query/http | REST creators, typing requests, response transforms, upload progress
|
|
25
|
-
| {%docsBaseUrl%}/query/auth | Bearer auth: login/refresh, auto token refresh, multi-tab sync
|
|
26
|
-
| {%docsBaseUrl%}/query/caching · `/stacks` · `/errors` · `/gql` · `/ws` | Caching/dedup, pagination, error/retry, GraphQL, WebSockets
|
|
27
|
-
| {%docsBaseUrl%}/query/multi-tab | Opt-in cross-tab sync: shared responses, per-key polling election, mutation fan-out
|
|
28
|
-
| {%docsBaseUrl%}/query/query-forms | Router-synced filter/search forms
|
|
29
|
-
| {%docsBaseUrl%}/query/legacy | The maintenance-mode `V2QueryClient`
|
|
19
|
+
| Page | Covers |
|
|
20
|
+
| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
|
21
|
+
| {%docsBaseUrl%}/query/ | Overview + the two-generations note |
|
|
22
|
+
| {%docsBaseUrl%}/query/queries | **Start here** - client, creators, the query object's signals, auto-execution |
|
|
23
|
+
| {%docsBaseUrl%}/query/features | `withArgs`, `withPolling`, `withLongPolling`, `withAutoRefresh`, side-effect handlers |
|
|
24
|
+
| {%docsBaseUrl%}/query/http | REST creators, typing requests, response transforms, upload progress |
|
|
25
|
+
| {%docsBaseUrl%}/query/auth | Bearer auth: login/refresh, auto token refresh, multi-tab sync |
|
|
26
|
+
| {%docsBaseUrl%}/query/caching · `/stacks` · `/errors` · `/gql` · `/ws` | Caching/dedup, pagination, error/retry, GraphQL, WebSockets |
|
|
27
|
+
| {%docsBaseUrl%}/query/multi-tab | Opt-in cross-tab sync: shared responses, per-key polling election, mutation fan-out |
|
|
28
|
+
| {%docsBaseUrl%}/query/query-forms | Router-synced filter/search forms |
|
|
29
|
+
| {%docsBaseUrl%}/query/legacy | The maintenance-mode `V2QueryClient` |
|
|
30
30
|
|
|
31
31
|
## Two generations - use the current one
|
|
32
32
|
|
|
@@ -77,8 +77,8 @@ raw `toObservable`). It emits `null` first - `pipe(filter(r => r !== null))`.
|
|
|
77
77
|
- **`withArgs(() => ({ pathParams, queryParams, body }))`** - runs like a `computed`;
|
|
78
78
|
re-runs when a signal it reads changes and re-executes the query. This is how you
|
|
79
79
|
drive **search-as-you-type**: back it with a search signal
|
|
80
|
-
(`withArgs(() => ({ queryParams: { search: this.search() } }))`). Return
|
|
81
|
-
|
|
80
|
+
(`withArgs(() => ({ queryParams: { search: this.search() } }))`). Return `null`
|
|
81
|
+
to park the query - args reset to `null`, pausing polling/auto-refresh.
|
|
82
82
|
- **Prefer `withArgs` over passing `args` to `execute()`.** Args declared on the query
|
|
83
83
|
stay reactive: a `GET` re-executes itself when they change, and `withPolling` /
|
|
84
84
|
`withAutoRefresh` restart off the same signal - none of which happens for args handed
|
|
@@ -86,6 +86,10 @@ raw `toObservable`). It emits `null` first - `pipe(filter(r => r !== null))`.
|
|
|
86
86
|
place a mutation is just `.execute()`, which reuses the current `args()`. Reserve
|
|
87
87
|
`execute({ args })` for a one-off payload no signal holds (a form submit).
|
|
88
88
|
- `withPolling({ interval })`, `withAutoRefresh({ onSignalChanges: [...] })`.
|
|
89
|
+
- **`withLongPolling({ nextArgs })`** for a completion-driven chain instead of an interval: each
|
|
90
|
+
round starts once the previous settled, with args (a cursor) derived from its response. `nextArgs`
|
|
91
|
+
returning `null` ends the chain. Not `withPolling` with a small interval - and the two throw when
|
|
92
|
+
combined.
|
|
89
93
|
- Side-effects: `withSuccessHandling`, `withErrorHandling`, `withLogging`.
|
|
90
94
|
|
|
91
95
|
There is no built-in debounce operator - dedup/caching handles repeated identical
|
package/package.json
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ethlete/agent-rules",
|
|
3
|
-
"version": "0.1.0-next.
|
|
3
|
+
"version": "0.1.0-next.6",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "commonjs",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./src/index.d.ts",
|
|
9
|
+
"default": "./src/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./git-flow": {
|
|
12
|
+
"types": "./src/lib/git-flow/index.d.ts",
|
|
13
|
+
"default": "./src/lib/git-flow/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
6
17
|
"main": "./src/index.js",
|
|
7
18
|
"typings": "./src/index.d.ts",
|
|
8
19
|
"bin": {
|
package/src/index.js
CHANGED
|
@@ -5,6 +5,7 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
const fs_1 = require("fs");
|
|
6
6
|
const path_1 = require("path");
|
|
7
7
|
const config_1 = require("./lib/config");
|
|
8
|
+
const git_flow_command_1 = require("./lib/git-flow-command");
|
|
8
9
|
const migrate_1 = require("./lib/migrate");
|
|
9
10
|
const sync_1 = require("./lib/sync");
|
|
10
11
|
const USAGE = `ethlete-agents — compile @ethlete agent rules and skills into your repo
|
|
@@ -12,6 +13,8 @@ const USAGE = `ethlete-agents — compile @ethlete agent rules and skills into y
|
|
|
12
13
|
ethlete-agents sync Write the generated rules/skills for every detected agent
|
|
13
14
|
ethlete-agents check Exit non-zero when the generated files are out of date (for CI)
|
|
14
15
|
ethlete-agents init Write a starter ${config_1.CONFIG_FILE_NAME}
|
|
16
|
+
ethlete-agents git-flow Name, check and repair branches against the repo's git flow
|
|
17
|
+
(start, check, repair, explain)
|
|
15
18
|
ethlete-agents migrate Convert the repo to the AGENTS.md + .agents/skills layout:
|
|
16
19
|
CLAUDE.md content moves into AGENTS.md (CLAUDE.md becomes an
|
|
17
20
|
@AGENTS.md import), hand-written .claude/skills move to
|
|
@@ -66,6 +69,8 @@ const run = (argv) => {
|
|
|
66
69
|
return (0, sync_1.check)(options);
|
|
67
70
|
case 'init':
|
|
68
71
|
return init(root);
|
|
72
|
+
case 'git-flow':
|
|
73
|
+
return (0, git_flow_command_1.gitFlowCommand)({ root, argv: argv.slice(1) });
|
|
69
74
|
case 'migrate':
|
|
70
75
|
return (0, migrate_1.migrate)(Object.assign(Object.assign({}, options), { dryRun: argv.includes('--dry-run') }));
|
|
71
76
|
default:
|
|
@@ -76,12 +81,12 @@ const run = (argv) => {
|
|
|
76
81
|
tslib_1.__exportStar(require("./lib"), exports);
|
|
77
82
|
// Guarded so the package can also be imported as a library without running the CLI.
|
|
78
83
|
if (require.main === module) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
Promise.resolve()
|
|
85
|
+
.then(() => run(process.argv.slice(2)))
|
|
86
|
+
.then((code) => process.exit(code))
|
|
87
|
+
.catch((error) => {
|
|
83
88
|
console.error(error instanceof Error ? error.message : error);
|
|
84
89
|
process.exit(1);
|
|
85
|
-
}
|
|
90
|
+
});
|
|
86
91
|
}
|
|
87
92
|
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/agent-rules/src/index.ts"],"names":[],"mappings":";;;;AACA,2BAA6D;AAC7D,+BAA4B;AAC5B,yCAA2F;AAC3F,2CAAwC;AACxC,qCAAyC;AAEzC,MAAM,KAAK,GAAG;;;;6CAI+B,yBAAgB
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/agent-rules/src/index.ts"],"names":[],"mappings":"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"}
|
package/src/lib/config.d.ts
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
import { ContentScope } from './frontmatter';
|
|
2
|
+
import { GitFlowConfig } from './git-flow';
|
|
2
3
|
export declare const AGENT_TARGETS: readonly ["claude", "codex", "cursor", "copilot"];
|
|
3
4
|
export type AgentTarget = (typeof AGENT_TARGETS)[number];
|
|
4
5
|
export declare const CONFIG_FILE_NAME = "ethlete-agents.config.json";
|
|
5
6
|
export declare const LOCAL_CONFIG_FILE_NAME = "ethlete-agents.config.local.json";
|
|
7
|
+
/**
|
|
8
|
+
* Non-secret Jira wiring, committed with the repo — `git-flow start` resolves an issue key through
|
|
9
|
+
* it. Credentials never live here; they come from the environment or the gitignored local config.
|
|
10
|
+
*/
|
|
11
|
+
export type JiraSettings = {
|
|
12
|
+
host?: string;
|
|
13
|
+
/**
|
|
14
|
+
* The instance's field holding a Story's branch subject (`customfield_10050`). Without it the
|
|
15
|
+
* summary is slugified instead, which is a paraphrase rather than the agreed subject.
|
|
16
|
+
*/
|
|
17
|
+
subjectField?: string;
|
|
18
|
+
/** Branch type per Jira issue type, e.g. `{ "Bug": "fix" }`. Anything unlisted becomes `feat`. */
|
|
19
|
+
typeByIssueType?: Record<string, string>;
|
|
20
|
+
};
|
|
6
21
|
export type SyncConfig = {
|
|
7
22
|
root: string;
|
|
8
23
|
targets: AgentTarget[];
|
|
@@ -18,6 +33,11 @@ export type SyncConfig = {
|
|
|
18
33
|
claudeMdImportsAgentsMd: boolean;
|
|
19
34
|
/** Opt-in agent hooks (they run commands on the developer's machine, so never default). */
|
|
20
35
|
hooks: string[];
|
|
36
|
+
/** Opt-in git hooks, appended to the repo's own husky hooks. Same reason they never default. */
|
|
37
|
+
gitHooks: string[];
|
|
38
|
+
/** The branch grammar, resolved against its defaults — see `@ethlete/agent-rules/git-flow`. */
|
|
39
|
+
gitFlow: GitFlowConfig;
|
|
40
|
+
jira: JiraSettings;
|
|
21
41
|
};
|
|
22
42
|
/**
|
|
23
43
|
* The gitignored local config holds per-machine values, read at runtime — by the generated hook
|
|
@@ -31,11 +51,19 @@ export type SyncConfig = {
|
|
|
31
51
|
* a handoff instead of writing the handoff file automatically.
|
|
32
52
|
* - `sdkSourcePath` points at a local `ethlete-sdk` checkout, which the SDK source and local-build
|
|
33
53
|
* skills read when they need the SDK's own sources instead of the published package.
|
|
54
|
+
* - `jira` holds the per-user Jira credentials `git-flow start` needs. This is the one place in the
|
|
55
|
+
* repo a secret may sit, and only because the file is gitignored; `JIRA_EMAIL`/`JIRA_API_TOKEN`
|
|
56
|
+
* in the environment are the alternative.
|
|
34
57
|
*/
|
|
35
58
|
export type LocalConfig = {
|
|
36
59
|
disableHooks?: boolean | string[];
|
|
37
60
|
disableAutoHandoffSave?: boolean;
|
|
38
61
|
sdkSourcePath?: string;
|
|
62
|
+
jira?: {
|
|
63
|
+
host?: string;
|
|
64
|
+
email?: string;
|
|
65
|
+
token?: string;
|
|
66
|
+
};
|
|
39
67
|
};
|
|
40
68
|
export type LocalConfigState = {
|
|
41
69
|
exists: false;
|