@ethlete/agent-rules 0.1.0-next.7 → 0.1.0-next.9
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 +21 -0
- package/README.md +54 -13
- package/content/skills/api-source/SKILL.md +118 -0
- package/content/skills/git-flow/SKILL.md +4 -0
- package/content/skills/sdk-docs/SKILL.md +9 -2
- package/content/skills/sdk-source/SKILL.md +1 -1
- package/content/skills/styleguide/STYLEGUIDE.md +1 -1
- package/content/skills/timetrack/SKILL.md +64 -0
- package/package.json +1 -1
- package/src/index.js +6 -0
- package/src/index.js.map +1 -1
- package/src/lib/config.d.ts +9 -16
- package/src/lib/config.js +6 -1
- package/src/lib/config.js.map +1 -1
- package/src/lib/git-flow/index.d.ts +1 -0
- package/src/lib/git-flow/index.js +1 -0
- package/src/lib/git-flow/index.js.map +1 -1
- 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 +27 -0
- package/src/lib/git-flow/start.js +34 -1
- package/src/lib/git-flow/start.js.map +1 -1
- package/src/lib/git-flow-repair.js +7 -11
- package/src/lib/git-flow-repair.js.map +1 -1
- package/src/lib/git-flow-start.js +14 -29
- package/src/lib/git-flow-start.js.map +1 -1
- package/src/lib/plan.js +31 -3
- package/src/lib/plan.js.map +1 -1
- package/src/lib/timetrack-command.d.ts +11 -0
- package/src/lib/timetrack-command.js +187 -0
- package/src/lib/timetrack-command.js.map +1 -0
- package/src/lib/timetrack.d.ts +74 -0
- package/src/lib/timetrack.js +110 -0
- package/src/lib/timetrack.js.map +1 -0
- package/src/lib/jira.d.ts +0 -28
- package/src/lib/jira.js +0 -83
- package/src/lib/jira.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @ethlete/agent-rules
|
|
2
2
|
|
|
3
|
+
## 0.1.0-next.9
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`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" }`).
|
|
8
|
+
|
|
9
|
+
- [`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
|
|
10
|
+
token any more — the `jira` credentials in the local config and the `JIRA_*` variables are gone.
|
|
11
|
+
|
|
12
|
+
- [#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.
|
|
13
|
+
|
|
14
|
+
- [#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.
|
|
15
|
+
|
|
16
|
+
## 0.1.0-next.8
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- [#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.
|
|
21
|
+
|
|
22
|
+
- [#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.
|
|
23
|
+
|
|
3
24
|
## 0.1.0-next.7
|
|
4
25
|
|
|
5
26
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -196,25 +196,25 @@ exist already - `start` says so rather than inventing a parent. `--of <branch>`
|
|
|
196
196
|
parent explicitly, `--hotfix` branches off production, `--release <date>` makes a release
|
|
197
197
|
branch, and `--subject <text>` skips Jira entirely.
|
|
198
198
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
202
|
|
|
203
203
|
```json
|
|
204
204
|
{
|
|
205
205
|
"jira": {
|
|
206
|
-
"host": "https://your-team.atlassian.net",
|
|
207
|
-
"subjectField": "customfield_10050",
|
|
208
206
|
"typeByIssueType": { "Bug": "fix" }
|
|
209
207
|
}
|
|
210
208
|
}
|
|
211
209
|
```
|
|
212
210
|
|
|
213
|
-
- **`subjectField`** - the field holding a Story's branch subject. Without it the summary
|
|
214
|
-
is slugified, which is a paraphrase rather than the agreed subject.
|
|
215
211
|
- **`typeByIssueType`** - the branch type per Jira issue type; anything unlisted becomes
|
|
216
212
|
`feat`. `--type` overrides it per call.
|
|
217
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
218
|
### `repair` - renaming a branch that does not conform
|
|
219
219
|
|
|
220
220
|
`git-flow repair [ref]` derives the conforming name (`--key FIP-2900` when the old name
|
|
@@ -232,6 +232,44 @@ Everything is checked before the first mutation, and it refuses rather than half
|
|
|
232
232
|
- If a retarget fails halfway, the old branch is still there and the recovery commands are
|
|
233
233
|
printed.
|
|
234
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
|
+
|
|
235
273
|
## Hooks (opt-in)
|
|
236
274
|
|
|
237
275
|
Hooks run commands on the developer's machine, so none are emitted by default - opt in
|
|
@@ -328,7 +366,7 @@ differ per developer, without touching any committed file:
|
|
|
328
366
|
{
|
|
329
367
|
"disableHooks": true,
|
|
330
368
|
"sdkSourcePath": "/absolute/path/to/ethlete-sdk",
|
|
331
|
-
"
|
|
369
|
+
"apiRepoPaths": { "hub": "../fut-hub-backend" }
|
|
332
370
|
}
|
|
333
371
|
```
|
|
334
372
|
|
|
@@ -338,19 +376,22 @@ differ per developer, without touching any committed file:
|
|
|
338
376
|
- **`disableAutoHandoffSave`** - keeps the `context-warning` hook's tiered warnings but
|
|
339
377
|
drops the auto-mode escalation: at the critical tier it recommends `/ethlete-handoff`
|
|
340
378
|
instead of saving the handoff file itself.
|
|
341
|
-
- **`jira`** - the credentials `git-flow start` needs (`host`, `email`, `token`). This is
|
|
342
|
-
the one place in a repo a secret may sit, and only because the file is gitignored;
|
|
343
|
-
`JIRA_EMAIL` / `JIRA_API_TOKEN` in the environment are the alternative and win over it.
|
|
344
379
|
- **`sdkSourcePath`** - a local `ethlete-sdk` checkout. The `sdk-source` and
|
|
345
380
|
`sdk-local-build` skills read it when the agent needs the SDK's own sources, or has to
|
|
346
381
|
build the SDK and install it here through a `file:` dependency. A relative path is
|
|
347
382
|
resolved from the repo root.
|
|
383
|
+
- **`apiRepoPaths`** - one checkout per app, keyed by the app's project name
|
|
384
|
+
(`{ "hub": "../fut-hub-backend" }`). The `api-source` skill reads it to confirm a
|
|
385
|
+
response shape, a status code or an enum in the API's own source instead of guessing it
|
|
386
|
+
from the client. Relative paths resolve from the repo root; a map with a single entry is
|
|
387
|
+
used for whatever app is in play.
|
|
348
388
|
|
|
349
389
|
Everything in this file is read at runtime, never by `sync`: the generated files stay
|
|
350
390
|
identical on every machine and in CI, which is what lets `check` diff them. That is also
|
|
351
391
|
why the file takes nothing beyond these keys - `sync`/`check` warn about unknown keys,
|
|
352
|
-
|
|
353
|
-
to your repo's
|
|
392
|
+
about an `sdkSourcePath` that is missing or is not an SDK checkout, and about an
|
|
393
|
+
`apiRepoPaths` entry that is not a directory. Add the filename to your repo's
|
|
394
|
+
`.gitignore`.
|
|
354
395
|
|
|
355
396
|
## Authoring content
|
|
356
397
|
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: api-source
|
|
3
|
+
description: How to read the source of the API an app in this repo talks to, from a local backend checkout named in ethlete-agents.config.local.json. Read when a response shape, a status code, an error body, an enum or an auth rule has to be confirmed rather than guessed - and never edit that checkout as part of work in this repo.
|
|
4
|
+
kind: skill
|
|
5
|
+
scope: consumer
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Reading the API source
|
|
9
|
+
|
|
10
|
+
The client's types describe what the frontend _expects_. When the two disagree, the
|
|
11
|
+
server is right, so a question about the contract is answered in the API repository -
|
|
12
|
+
not by reading the frontend's own models harder.
|
|
13
|
+
|
|
14
|
+
Reach for the checkout when:
|
|
15
|
+
|
|
16
|
+
- a response arrives with a field, a shape or a `null` the frontend types do not allow
|
|
17
|
+
- you need the exact status code, error body or validation message for a failure path
|
|
18
|
+
- an enum, a permission or a filter parameter has to match the server's list exactly
|
|
19
|
+
- a request fails and you cannot tell whether the client or the server is wrong
|
|
20
|
+
- you are about to report or fix something in the API itself
|
|
21
|
+
|
|
22
|
+
Do not read it to design frontend code that the API does not serve yet. An endpoint in
|
|
23
|
+
the checkout is not deployed until the API team ships it.
|
|
24
|
+
|
|
25
|
+
## 1. Resolve the checkout for your app
|
|
26
|
+
|
|
27
|
+
One repository can hold several apps, each with its own API, so the paths are a map from
|
|
28
|
+
app to checkout. They are per machine, so the map lives in the gitignored
|
|
29
|
+
`ethlete-agents.config.local.json` at the repo root:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"apiRepoPaths": {
|
|
34
|
+
"hub": "../fut-hub-backend"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Read that file before searching anywhere. The rules:
|
|
40
|
+
|
|
41
|
+
- **The key is the app** as this repo names it - the workspace project name, which is
|
|
42
|
+
normally also the folder under `apps/`. Match the app you are working in.
|
|
43
|
+
- **A relative path resolves from the repo root**, not from the app folder.
|
|
44
|
+
- **One entry means one API.** If the map holds a single entry, use it whatever it is
|
|
45
|
+
called.
|
|
46
|
+
- **No matching entry - stop and ask.** Do not guess a sibling folder and do not clone
|
|
47
|
+
the repository. Say which app you needed the API for and offer the snippet above; the
|
|
48
|
+
file is gitignored, so adding it changes nothing for anyone else.
|
|
49
|
+
|
|
50
|
+
Without a checkout, fall back to what the running API tells you: the generated API
|
|
51
|
+
description if the project serves one (`/openapi.json`, `/swagger`, `/api/doc`), and the
|
|
52
|
+
real response body of the call you are debugging.
|
|
53
|
+
|
|
54
|
+
## 2. Check the branch before you read anything
|
|
55
|
+
|
|
56
|
+
A checkout sits on whatever branch its developer left it on, and the code you would
|
|
57
|
+
quote must be the code that serves this app:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git -C <apiRepoPath> fetch --quiet # read-only, safe
|
|
61
|
+
git -C <apiRepoPath> status -sb # branch, ahead/behind, dirty files
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**The state to expect is the API's own development branch, up to date with its remote.**
|
|
65
|
+
Anything else, and you are describing a different API than the one the app calls.
|
|
66
|
+
|
|
67
|
+
When it is not in that state, **say so and ask** - never switch, pull, stash or reset it
|
|
68
|
+
yourself:
|
|
69
|
+
|
|
70
|
+
- **On another branch** - name it and ask. A feature branch may be exactly the endpoint
|
|
71
|
+
you were sent to look at, but it is not what the app talks to today.
|
|
72
|
+
- **Behind its remote** - report how far. The behaviour you are about to call a bug may
|
|
73
|
+
already be fixed upstream.
|
|
74
|
+
- **Dirty** - it holds someone's work in progress. Say so rather than quoting it as API
|
|
75
|
+
behaviour.
|
|
76
|
+
|
|
77
|
+
## 3. The checkout is not the environment the app calls
|
|
78
|
+
|
|
79
|
+
Even a clean, current checkout is only the source. The app talks to a deployed
|
|
80
|
+
environment, which can lag it:
|
|
81
|
+
|
|
82
|
+
- Read the app's API base URL from its environment config, and say which environment
|
|
83
|
+
your answer is about.
|
|
84
|
+
- When the source and the observed response disagree, **the observed response wins** for
|
|
85
|
+
what the app has to handle today. Report the difference instead of writing client code
|
|
86
|
+
against the newer source.
|
|
87
|
+
|
|
88
|
+
## 4. Search it, don't read it whole
|
|
89
|
+
|
|
90
|
+
The stack is whatever the API team chose, so search by the thing you already know - the
|
|
91
|
+
path, the field name, the error message - rather than by an expected file layout:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
rg -n "api/v1/matches" <apiRepoPath> --glob '!*test*' # the route
|
|
95
|
+
rg -n "kickoffAt" <apiRepoPath> # a field of the payload
|
|
96
|
+
rg -n "MATCH_NOT_FOUND" <apiRepoPath> # an error code you received
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Two things to find first, because they answer most questions on their own:
|
|
100
|
+
|
|
101
|
+
- **The API description** the project generates or checks in (OpenAPI, Swagger, GraphQL
|
|
102
|
+
schema, `.http` files). It is the contract, and it is cheaper to read than the code.
|
|
103
|
+
- **The serializer, resource or DTO** for the entity - the field list the client sees,
|
|
104
|
+
which is usually a subset of the database model, and the place where a name is
|
|
105
|
+
rewritten between the two.
|
|
106
|
+
|
|
107
|
+
The API's own tests describe intended behaviour more directly than the implementation:
|
|
108
|
+
they name the status code and the body for each case.
|
|
109
|
+
|
|
110
|
+
## 5. The checkout is read-only from here
|
|
111
|
+
|
|
112
|
+
It is a different repository with its own branch, review and release process. Never edit
|
|
113
|
+
it while working on a task in this repo, and never change its git state without being
|
|
114
|
+
asked (`fetch` is fine).
|
|
115
|
+
|
|
116
|
+
When the fix belongs in the API, say so precisely: the endpoint, the field, and the
|
|
117
|
+
behaviour it should have. Then handle the API as it is today - a client workaround for a
|
|
118
|
+
server bug is a decision for the user, and it needs a comment naming what it waits on.
|
|
@@ -26,6 +26,10 @@ npx ethlete-agents git-flow repair <branch> # rename a non-conforming one,
|
|
|
26
26
|
`--dry-run` to see the plan alone. It refuses on a dirty working tree, and a Task nests under
|
|
27
27
|
its parent Story's branch, so that branch has to exist first.
|
|
28
28
|
|
|
29
|
+
It reads the issue through the running Timetrack app, which holds this machine's only Jira
|
|
30
|
+
credentials - see the `timetrack` skill. If it reports that the app is not running, ask the
|
|
31
|
+
user to start it. `--subject <text>` names a branch without reading Jira at all.
|
|
32
|
+
|
|
29
33
|
## The five shapes
|
|
30
34
|
|
|
31
35
|
| Shape | Issue | Branch from | MR targets |
|
|
@@ -43,8 +43,15 @@ Component domains under `/components/`:
|
|
|
43
43
|
`tabs` `text-inputs` `time-picker` `toggletip` `tooltip`
|
|
44
44
|
|
|
45
45
|
So the table guide is `{%docsBaseUrl%}/components/table`, the menu guide
|
|
46
|
-
`{%docsBaseUrl%}/components/menu`, and so on.
|
|
47
|
-
|
|
46
|
+
`{%docsBaseUrl%}/components/menu`, and so on.
|
|
47
|
+
|
|
48
|
+
That list is a snapshot. The site itself is machine-readable, so fetch rather than guess:
|
|
49
|
+
|
|
50
|
+
- `{%docsBaseUrl%}/llms.txt` - every page's title and path as of the last deploy. Use it
|
|
51
|
+
when a name isn't in the list above, or to check the list hasn't drifted.
|
|
52
|
+
- Append `.md` to any page URL - `{%docsBaseUrl%}/components/button.md` - to get raw
|
|
53
|
+
markdown instead of the rendered page. Prefer this over `/llms-full.txt`, which is the
|
|
54
|
+
entire site in one ~1 MB file.
|
|
48
55
|
|
|
49
56
|
## How to use them
|
|
50
57
|
|
|
@@ -94,7 +94,7 @@ Paths are relative to the checkout root:
|
|
|
94
94
|
| `libs/cdk/` | The predecessor UI toolkit, maintenance mode - only for code still on it |
|
|
95
95
|
| `libs/eslint-plugin/src/` | The lint rules, including the message text explaining each one |
|
|
96
96
|
| `apps/docs/` | The markdown behind {%docsBaseUrl%} |
|
|
97
|
-
| `apps/
|
|
97
|
+
| `apps/storybook/` | The Storybook app - stories also live next to each component |
|
|
98
98
|
|
|
99
99
|
Inside a component domain: `<name>.component.ts` with its `.css` next to it,
|
|
100
100
|
`<name>.imports.ts` (the imports array to spread into a consumer component),
|
|
@@ -37,7 +37,7 @@ Run your lint task with `--fix` - the rules below are enforced (and mostly auto-
|
|
|
37
37
|
| Max two function parameters | `max-params` |
|
|
38
38
|
| No `import type` / inline `type` specifiers | `ethlete/no-type-only-import` |
|
|
39
39
|
| Generic params `T`-prefixed (`TValue`), never bare `T` | `@typescript-eslint/naming-convention` |
|
|
40
|
-
| No `async`/`await` - use RxJS | `no-
|
|
40
|
+
| No `async`/`await` - use RxJS | `ethlete/no-async-await` |
|
|
41
41
|
| Arrow fns standalone; methods in classes; no arrow-fn class props; no `function` keyword | `no-restricted-syntax` |
|
|
42
42
|
| Blank line before `return` in multi-line guard clauses | `ethlete/guard-return-newline` |
|
|
43
43
|
| No trivially-inferable explicit return types | `ethlete/no-trivial-return-type` |
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: timetrack
|
|
3
|
+
description: How to reach Jira from any repo through the running Timetrack app - look up an issue, search for one, ask which project a repo logs into, file a ticket, or add a worklog row. Read whenever a task needs Jira data or a Jira write, and never put a Jira token in a repo.
|
|
4
|
+
kind: skill
|
|
5
|
+
scope: both
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Reaching Jira through Timetrack
|
|
9
|
+
|
|
10
|
+
**No repository holds a Jira token.** The Timetrack desktop app holds one set of credentials
|
|
11
|
+
in this machine's keychain, and every repository asks it:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx ethlete-agents timetrack status # is the app reachable, and what does it hold?
|
|
15
|
+
npx ethlete-agents timetrack issue FIP-2177 # one issue: summary, type, parent, subject
|
|
16
|
+
npx ethlete-agents timetrack search "password" # open issues of the picked projects
|
|
17
|
+
npx ethlete-agents timetrack project # which project does this repo log into?
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Add `--json` to any of them when you need to read a field rather than a line.
|
|
21
|
+
|
|
22
|
+
**Never ask the user for a Jira token, and never write one into a file.** If a command reports
|
|
23
|
+
that the app is not running, say so and ask the user to start it. That is the whole fix -
|
|
24
|
+
there is no per-repo fallback, by design: a secret copied into every checkout is a secret
|
|
25
|
+
nobody can rotate.
|
|
26
|
+
|
|
27
|
+
## What each command is for
|
|
28
|
+
|
|
29
|
+
| Command | Use it when |
|
|
30
|
+
| --------------------------------- | ----------------------------------------------------------------------- |
|
|
31
|
+
| `status` | Before anything else, when a Jira command failed and you need the cause |
|
|
32
|
+
| `issue <KEY>` | The user names a key and you need its summary, type or parent |
|
|
33
|
+
| `search [text]` | The user describes work but names no key |
|
|
34
|
+
| `project [path]` | You need the project a repository files into |
|
|
35
|
+
| `create --summary "…"` | The work has no ticket and the user asked for one |
|
|
36
|
+
| `log --issue <KEY> --minutes <n>` | The user asks to record time that nothing observed |
|
|
37
|
+
|
|
38
|
+
`git-flow start` uses the same channel, so a branch is named from the real issue rather than
|
|
39
|
+
from a key you typed - see the `git-flow` skill.
|
|
40
|
+
|
|
41
|
+
## Writes
|
|
42
|
+
|
|
43
|
+
Two commands write, so both need the user to have asked for them in this conversation:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx ethlete-agents timetrack create --summary "Reset password mail is not sent" --project FIP
|
|
47
|
+
npx ethlete-agents timetrack log --issue FIP-2177 --minutes 45 --description "pairing call"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- **`create`** files the issue with the instance's own ticket settings - its type, its parent
|
|
51
|
+
rule and its subject field all come from the app, so the ticket is shaped like every other.
|
|
52
|
+
`--project` is needed unless the app holds exactly one picked project.
|
|
53
|
+
- **`log`** adds a row to the day in Timetrack. It is **not** a Tempo entry: the user reviews
|
|
54
|
+
the day and syncs it, which is what keeps an agent's row from double-booking against the
|
|
55
|
+
hours the day already observed. `--at <date>` places it; without one it starts now.
|
|
56
|
+
|
|
57
|
+
## What the app decides, not you
|
|
58
|
+
|
|
59
|
+
- **The projects.** The user picked them in Timetrack. `search` is scoped to them, and
|
|
60
|
+
`--project <KEY>` is how you look outside.
|
|
61
|
+
- **The branch subject.** `issue` returns `subject` when the instance's subject field is set on
|
|
62
|
+
that issue. Prefer it over the summary; the summary is a paraphrase.
|
|
63
|
+
- **The instance.** Its host, its issue types and its hierarchy are settings in the app. Do not
|
|
64
|
+
hardcode any of them in this repo.
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -8,6 +8,7 @@ const config_1 = require("./lib/config");
|
|
|
8
8
|
const git_flow_command_1 = require("./lib/git-flow-command");
|
|
9
9
|
const migrate_1 = require("./lib/migrate");
|
|
10
10
|
const sync_1 = require("./lib/sync");
|
|
11
|
+
const timetrack_command_1 = require("./lib/timetrack-command");
|
|
11
12
|
const USAGE = `ethlete-agents — compile @ethlete agent rules and skills into your repo
|
|
12
13
|
|
|
13
14
|
ethlete-agents sync Write the generated rules/skills for every detected agent
|
|
@@ -15,6 +16,9 @@ const USAGE = `ethlete-agents — compile @ethlete agent rules and skills into y
|
|
|
15
16
|
ethlete-agents init Write a starter ${config_1.CONFIG_FILE_NAME}
|
|
16
17
|
ethlete-agents git-flow Name, check and repair branches against the repo's git flow
|
|
17
18
|
(start, check, repair, explain)
|
|
19
|
+
ethlete-agents timetrack Ask the running Timetrack app about Jira - it holds this
|
|
20
|
+
machine's credentials, so no repo needs a token
|
|
21
|
+
(status, issue, search, project, create, log)
|
|
18
22
|
ethlete-agents migrate Convert the repo to the AGENTS.md + .agents/skills layout:
|
|
19
23
|
CLAUDE.md content moves into AGENTS.md (CLAUDE.md becomes an
|
|
20
24
|
@AGENTS.md import), hand-written .claude/skills move to
|
|
@@ -67,6 +71,8 @@ const run = (argv) => {
|
|
|
67
71
|
return init(root);
|
|
68
72
|
case 'git-flow':
|
|
69
73
|
return (0, git_flow_command_1.gitFlowCommand)({ root, argv: argv.slice(1) });
|
|
74
|
+
case 'timetrack':
|
|
75
|
+
return (0, timetrack_command_1.timetrackCommand)({ root, argv: argv.slice(1) });
|
|
70
76
|
case 'migrate':
|
|
71
77
|
return (0, migrate_1.migrate)(Object.assign(Object.assign({}, options), { dryRun: argv.includes('--dry-run') }));
|
|
72
78
|
default:
|
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,2BAA+C;AAC/C,+BAA4B;AAC5B,yCAA2F;AAC3F,6DAAwD;AACxD,2CAAwC;AACxC,qCAAyC;
|
|
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
|
@@ -5,16 +5,10 @@ export type AgentTarget = (typeof AGENT_TARGETS)[number];
|
|
|
5
5
|
export declare const CONFIG_FILE_NAME = "ethlete-agents.config.json";
|
|
6
6
|
export declare const LOCAL_CONFIG_FILE_NAME = "ethlete-agents.config.local.json";
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* How this repo reads a Jira issue into a branch name. The instance, the credentials and the subject
|
|
9
|
+
* field are not here and cannot be: the Timetrack app holds them, and `git-flow start` asks it.
|
|
10
10
|
*/
|
|
11
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
12
|
/** Branch type per Jira issue type, e.g. `{ "Bug": "fix" }`. Anything unlisted becomes `feat`. */
|
|
19
13
|
typeByIssueType?: Record<string, string>;
|
|
20
14
|
};
|
|
@@ -51,19 +45,18 @@ export type SyncConfig = {
|
|
|
51
45
|
* a handoff instead of writing the handoff file automatically.
|
|
52
46
|
* - `sdkSourcePath` points at a local `ethlete-sdk` checkout, which the SDK source and local-build
|
|
53
47
|
* skills read when they need the SDK's own sources instead of the published package.
|
|
54
|
-
* - `
|
|
55
|
-
*
|
|
56
|
-
*
|
|
48
|
+
* - `apiRepoPaths` maps an app in this repo to the checkout of the API it talks to (`{ "hub":
|
|
49
|
+
* "../fut-hub-backend" }`), which the api-source skill reads to answer a question about the
|
|
50
|
+
* server instead of guessing it from the client.
|
|
51
|
+
*
|
|
52
|
+
* No secret belongs here any more. Jira is reached through the Timetrack app, which holds the token
|
|
53
|
+
* in the machine's keychain — one secret per machine rather than one per checkout.
|
|
57
54
|
*/
|
|
58
55
|
export type LocalConfig = {
|
|
59
56
|
disableHooks?: boolean | string[];
|
|
60
57
|
disableAutoHandoffSave?: boolean;
|
|
61
58
|
sdkSourcePath?: string;
|
|
62
|
-
|
|
63
|
-
host?: string;
|
|
64
|
-
email?: string;
|
|
65
|
-
token?: string;
|
|
66
|
-
};
|
|
59
|
+
apiRepoPaths?: Record<string, string>;
|
|
67
60
|
};
|
|
68
61
|
export type LocalConfigState = {
|
|
69
62
|
exists: false;
|
package/src/lib/config.js
CHANGED
|
@@ -15,7 +15,12 @@ const readRawConfig = (root) => {
|
|
|
15
15
|
return {};
|
|
16
16
|
return JSON.parse((0, fs_1.readFileSync)(path, 'utf8'));
|
|
17
17
|
};
|
|
18
|
-
const LOCAL_CONFIG_KEYS = [
|
|
18
|
+
const LOCAL_CONFIG_KEYS = [
|
|
19
|
+
'disableHooks',
|
|
20
|
+
'disableAutoHandoffSave',
|
|
21
|
+
'sdkSourcePath',
|
|
22
|
+
'apiRepoPaths',
|
|
23
|
+
];
|
|
19
24
|
const readLocalConfig = (root) => {
|
|
20
25
|
const path = (0, path_1.join)(root, exports.LOCAL_CONFIG_FILE_NAME);
|
|
21
26
|
if (!(0, fs_1.existsSync)(path))
|
package/src/lib/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../../../libs/agent-rules/src/lib/config.ts"],"names":[],"mappings":";;;AAAA,2BAA8C;AAC9C,+BAA4B;AAC5B,6CAAuE;AAEvE,yCAAmF;AACnF,iDAAiD;AAEpC,QAAA,aAAa,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAU,CAAC;AAIlE,QAAA,gBAAgB,GAAG,4BAA4B,CAAC;AAEhD,QAAA,sBAAsB,GAAG,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../../../libs/agent-rules/src/lib/config.ts"],"names":[],"mappings":";;;AAAA,2BAA8C;AAC9C,+BAA4B;AAC5B,6CAAuE;AAEvE,yCAAmF;AACnF,iDAAiD;AAEpC,QAAA,aAAa,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAU,CAAC;AAIlE,QAAA,gBAAgB,GAAG,4BAA4B,CAAC;AAEhD,QAAA,sBAAsB,GAAG,kCAAkC,CAAC;AA6CzE,MAAM,aAAa,GAAG,CAAC,IAAY,EAAE,EAAE;IACrC,MAAM,IAAI,GAAG,IAAA,WAAI,EAAC,IAAI,EAAE,wBAAgB,CAAC,CAAC;IAE1C,IAAI,CAAC,IAAA,eAAU,EAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAEjC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAA,iBAAY,EAAC,IAAI,EAAE,MAAM,CAAC,CAAc,CAAC;AAC7D,CAAC,CAAC;AAiCF,MAAM,iBAAiB,GAA0B;IAC/C,cAAc;IACd,wBAAwB;IACxB,eAAe;IACf,cAAc;CACf,CAAC;AAEK,MAAM,eAAe,GAAG,CAAC,IAAY,EAAoB,EAAE;IAChE,MAAM,IAAI,GAAG,IAAA,WAAI,EAAC,IAAI,EAAE,8BAAsB,CAAC,CAAC;IAEhD,IAAI,CAAC,IAAA,eAAU,EAAC,IAAI,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAEhD,IAAI,MAAe,CAAC;IAEpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAA,iBAAY,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAClD,CAAC;IAAC,WAAM,CAAC;QACP,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACxC,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAElH,MAAM,MAAM,GAAG,MAAqB,CAAC;IACrC,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,iBAAiB,CAAC,QAAQ,CAAC,GAAwB,CAAC,CAAC,CAAC;IAE/G,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;AAC5D,CAAC,CAAC;AAnBW,QAAA,eAAe,mBAmB1B;AAEF;;;;;GAKG;AACI,MAAM,aAAa,GAAG,CAAC,IAAY,EAAiB,EAAE;IAC3D,MAAM,QAAQ,GAAkB,CAAC,OAAO,CAAC,CAAC;IAE1C,IAAI,IAAA,eAAU,EAAC,IAAA,WAAI,EAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAAE,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC/D,IAAI,IAAA,eAAU,EAAC,IAAA,WAAI,EAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAAE,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC/D,IAAI,IAAA,eAAU,EAAC,IAAA,WAAI,EAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAEhE,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAC;AARW,QAAA,aAAa,iBAQxB;AAEF,MAAM,kBAAkB,GAAG,CAAC,OAAsB,EAAE,EAAE;IACpD,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,qBAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAE5E,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,sBAAsB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB,qBAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3G,CAAC;AACH,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,GAAG,CAAC,OAAsB,EAAqC,EAAE,CAAC,CAAC;IAClF,wBAAwB,EAAE,OAAO,CAAC,YAAY,CAAC,WAAW;IAC1D,uBAAuB,EAAE,OAAO,CAAC,YAAY,CAAC,UAAU;IACxD,YAAY,EAAE,OAAO,CAAC,KAAK;IAC3B,kBAAkB,EAAE,OAAO,CAAC,WAAW;IACvC,gBAAgB,EAAE,OAAO,CAAC,SAAS;CACpC,CAAC,CAAC;AAEI,MAAM,UAAU,GAAG,CAAC,OAAyD,EAAc,EAAE;;IAClG,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC;IACzC,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IACnF,MAAM,OAAO,GAAG,MAAA,cAAc,aAAd,cAAc,cAAd,cAAc,GAAI,UAAU,mCAAI,IAAA,qBAAa,EAAC,IAAI,CAAC,CAAC;IACpE,MAAM,OAAO,GAAG,IAAA,+BAAoB,EAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAElD,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAE5B,OAAO;QACL,IAAI;QACJ,OAAO;QACP,MAAM,EAAE,GAAG,CAAC,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,MAAM,CAAC;QAC/D,IAAI,8DACC,IAAA,8BAAe,GAAE,GACjB,WAAW,CAAC,OAAO,CAAC,GACpB,IAAA,8BAAiB,EAAC,IAAA,iCAAoB,EAAC,IAAI,CAAC,CAAC,GAC7C,CAAC,MAAA,GAAG,CAAC,IAAI,mCAAI,EAAE,CAAC,CACpB;QACD,OAAO,EAAE,MAAA,GAAG,CAAC,OAAO,mCAAI,EAAE;QAC1B,uBAAuB,EAAE,MAAA,GAAG,CAAC,uBAAuB,mCAAI,KAAK;QAC7D,KAAK,EAAE,MAAA,GAAG,CAAC,KAAK,mCAAI,EAAE;QACtB,QAAQ,EAAE,MAAA,GAAG,CAAC,QAAQ,mCAAI,EAAE;QAC5B,OAAO;QACP,IAAI,EAAE,MAAA,GAAG,CAAC,IAAI,mCAAI,EAAE;KACrB,CAAC;AACJ,CAAC,CAAC;AA1BW,QAAA,UAAU,cA0BrB"}
|
|
@@ -4,6 +4,7 @@ const tslib_1 = require("tslib");
|
|
|
4
4
|
tslib_1.__exportStar(require("./build"), exports);
|
|
5
5
|
tslib_1.__exportStar(require("./config"), exports);
|
|
6
6
|
tslib_1.__exportStar(require("./parse"), exports);
|
|
7
|
+
tslib_1.__exportStar(require("./rename"), exports);
|
|
7
8
|
tslib_1.__exportStar(require("./start"), exports);
|
|
8
9
|
tslib_1.__exportStar(require("./validate"), exports);
|
|
9
10
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../libs/agent-rules/src/lib/git-flow/index.ts"],"names":[],"mappings":";;;AAAA,kDAAwB;AACxB,mDAAyB;AACzB,kDAAwB;AACxB,kDAAwB;AACxB,qDAA2B"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../libs/agent-rules/src/lib/git-flow/index.ts"],"names":[],"mappings":";;;AAAA,kDAAwB;AACxB,mDAAyB;AACzB,kDAAwB;AACxB,mDAAyB;AACzB,kDAAwB;AACxB,qDAA2B"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { GitFlowConfig } from './config';
|
|
2
|
+
export type ConformingName = {
|
|
3
|
+
ok: true;
|
|
4
|
+
name: string;
|
|
5
|
+
} | {
|
|
6
|
+
ok: false;
|
|
7
|
+
/**
|
|
8
|
+
* `needs-key` is the only reason a caller can act on: file the issue, then ask again with the
|
|
9
|
+
* key it returned.
|
|
10
|
+
*/
|
|
11
|
+
reason: 'already-conforms' | 'needs-key' | 'no-shape';
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* The conforming name a non-conforming branch should take, once an issue key exists for it.
|
|
15
|
+
*
|
|
16
|
+
* A candidate is returned only after it parses as fully conforming, so a rename always lands on a
|
|
17
|
+
* name the grammar accepts — `parseBranch`'s own `suggestedName` does not guarantee that for a
|
|
18
|
+
* nested branch whose leaf names no issue, and half a rename is worse than none.
|
|
19
|
+
*/
|
|
20
|
+
export declare const conformingNameFor: (options: {
|
|
21
|
+
branch: string;
|
|
22
|
+
key?: string;
|
|
23
|
+
config: GitFlowConfig;
|
|
24
|
+
}) => ConformingName;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.conformingNameFor = void 0;
|
|
4
|
+
const build_1 = require("./build");
|
|
5
|
+
const parse_1 = require("./parse");
|
|
6
|
+
const KEY_PLACEHOLDER = '<KEY>';
|
|
7
|
+
/**
|
|
8
|
+
* The spec that rebuilds a branch whose leaf carries no key at all, which is why `parseBranch` left
|
|
9
|
+
* `suggestedName` unset. Only the kinds the grammar can spell are here — an unrecognised name names
|
|
10
|
+
* no kind, and inventing one would rename the branch to something the user never chose.
|
|
11
|
+
*/
|
|
12
|
+
const specFor = (options) => {
|
|
13
|
+
var _a;
|
|
14
|
+
const { parse, key } = options;
|
|
15
|
+
const subject = (_a = parse.subject) !== null && _a !== void 0 ? _a : '';
|
|
16
|
+
switch (parse.kind) {
|
|
17
|
+
case 'main-feature':
|
|
18
|
+
return { kind: 'main-feature', type: parse.type, key, subject };
|
|
19
|
+
case 'hotfix':
|
|
20
|
+
return { kind: 'hotfix', key, subject };
|
|
21
|
+
case 'sub-feature':
|
|
22
|
+
return parse.parent ? { kind: 'sub-feature', parent: parse.parent, key, subject } : undefined;
|
|
23
|
+
case 'release-fix':
|
|
24
|
+
return parse.parent ? { kind: 'release-fix', parent: parse.parent, key, subject } : undefined;
|
|
25
|
+
default:
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const candidatesFor = (options) => {
|
|
30
|
+
const { parse, key, config } = options;
|
|
31
|
+
const suggested = parse.suggestedName;
|
|
32
|
+
const candidates = [];
|
|
33
|
+
if (suggested && !suggested.includes(KEY_PLACEHOLDER))
|
|
34
|
+
candidates.push(suggested);
|
|
35
|
+
if (key) {
|
|
36
|
+
if (suggested === null || suggested === void 0 ? void 0 : suggested.includes(KEY_PLACEHOLDER))
|
|
37
|
+
candidates.push(suggested.replace(/<KEY>/g, key.toUpperCase()));
|
|
38
|
+
const spec = specFor({ parse, key: key.toUpperCase() });
|
|
39
|
+
if (spec)
|
|
40
|
+
candidates.push((0, build_1.buildBranchName)({ spec, config }));
|
|
41
|
+
}
|
|
42
|
+
return candidates;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* The conforming name a non-conforming branch should take, once an issue key exists for it.
|
|
46
|
+
*
|
|
47
|
+
* A candidate is returned only after it parses as fully conforming, so a rename always lands on a
|
|
48
|
+
* name the grammar accepts — `parseBranch`'s own `suggestedName` does not guarantee that for a
|
|
49
|
+
* nested branch whose leaf names no issue, and half a rename is worse than none.
|
|
50
|
+
*/
|
|
51
|
+
const conformingNameFor = (options) => {
|
|
52
|
+
var _a;
|
|
53
|
+
const { key, config } = options;
|
|
54
|
+
const parse = (0, parse_1.parseBranch)({ branch: options.branch, config });
|
|
55
|
+
if (parse.ok)
|
|
56
|
+
return { ok: false, reason: 'already-conforms' };
|
|
57
|
+
for (const name of candidatesFor({ parse, key, config })) {
|
|
58
|
+
if (name !== parse.branch && (0, parse_1.parseBranch)({ branch: name, config }).ok)
|
|
59
|
+
return { ok: true, name };
|
|
60
|
+
}
|
|
61
|
+
if (key)
|
|
62
|
+
return { ok: false, reason: 'no-shape' };
|
|
63
|
+
// Whether a key would help is answered by trying one, using a prefix this repo accepts so the
|
|
64
|
+
// probe is not rejected by `keyPrefixes` for a reason the real key would not hit.
|
|
65
|
+
const probe = candidatesFor({ parse, key: `${(_a = config.keyPrefixes[0]) !== null && _a !== void 0 ? _a : 'KEY'}-1`, config });
|
|
66
|
+
const rebuildable = probe.some((name) => (0, parse_1.parseBranch)({ branch: name, config }).ok);
|
|
67
|
+
return { ok: false, reason: rebuildable ? 'needs-key' : 'no-shape' };
|
|
68
|
+
};
|
|
69
|
+
exports.conformingNameFor = conformingNameFor;
|
|
70
|
+
//# sourceMappingURL=rename.js.map
|