@skitterbyte/skitterspec-jira 1.0.0 → 2.0.0
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/MIGRATION.md +178 -1
- package/README.md +3 -1
- package/assets/core/SETUP.md +1 -1
- package/assets/core/env.config.md +54 -2
- package/assets/core/jira.config.md +33 -6
- package/assets/hooks/main-guard.cjs +1 -1
- package/assets/hooks/review-gate.cjs +1 -1
- package/assets/rules/spec-planning.md +13 -3
- package/assets/rules/spec-reports.md +19 -4
- package/assets/skills/no-spec/SKILL.md +6 -3
- package/assets/skills/spec-bug/SKILL.md +3 -1
- package/assets/skills/spec-complete/SKILL.md +6 -3
- package/assets/skills/spec-hotfix/SKILL.md +3 -1
- package/assets/skills/spec-jira-setup/SKILL.md +2 -1
- package/assets/skills/spec-next/SKILL.md +3 -1
- package/assets/skills/spec-status/SKILL.md +27 -25
- package/assets/skills/spec-sync/SKILL.md +6 -4
- package/assets/skills/spec-to-main/SKILL.md +5 -3
- package/bin/skitterspec-jira.js +7 -0
- package/package.json +1 -1
- package/src/cli.js +82 -16
- package/src/env/config.js +111 -14
- package/src/env/glob.js +34 -0
- package/src/env/hotfix.js +33 -5
- package/src/env/integrate.js +52 -11
- package/src/env/live.js +1 -26
- package/src/env/proxy.js +33 -0
- package/src/env/review.js +47 -2
- package/src/init.js +3 -3
- package/src/vendor/jira/cli-sync.js +5 -9
- package/src/vendor/jira/config.js +5 -15
- package/src/vendor/provider-kit/src/apply.js +10 -0
- package/src/vendor/provider-kit/src/assignment.js +277 -0
- package/src/vendor/provider-kit/src/config.js +101 -48
- package/src/vendor/provider-kit/src/http.js +30 -11
- package/src/vendor/provider-kit/src/list-assignee.js +71 -0
- package/src/vendor/provider-kit/src/list-rows.js +35 -0
- package/src/vendor/provider-kit/src/released.js +80 -87
- package/src/vendor/provider-kit/src/specs.js +89 -7
- package/src/vendor/sync-core/index.js +5 -5
- package/src/vendor/sync-core/src/compare.js +38 -6
- package/src/vendor/sync-core/src/normalize.js +40 -58
package/MIGRATION.md
CHANGED
|
@@ -1,5 +1,104 @@
|
|
|
1
1
|
# Migration guide
|
|
2
2
|
|
|
3
|
+
## `@skitterbyte/skitterspec-github` v1 → v2 (tell, don't track)
|
|
4
|
+
|
|
5
|
+
Skitterspec now tells GitHub about **its own** transitions and nothing
|
|
6
|
+
else. It writes nothing about releases or deployments, and it never reads a
|
|
7
|
+
ticket's state back.
|
|
8
|
+
|
|
9
|
+
### Breaking change 1 — The `release create|deployed|complete|reconcile` verbs are removed
|
|
10
|
+
|
|
11
|
+
They exit 1 with one line naming `spec-sync released <range> --json`. A pipeline
|
|
12
|
+
still calling them goes red rather than silently believing it mirrors
|
|
13
|
+
anything. **To migrate**, replace the step with your own script over the
|
|
14
|
+
release's ticket list:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
skitterspec-github spec-sync released "<previous-tag>..<tag>" --json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Each ticket row now carries `spec` and `url` (both `null` for a ref no spec has
|
|
21
|
+
linked), so the script needs no second lookup. Set whatever GitHub field,
|
|
22
|
+
state or release object your process uses from that list. The checkout needs
|
|
23
|
+
`fetch-depth: 0` so the previous tag is visible.
|
|
24
|
+
|
|
25
|
+
### Breaking change 2 — The `releases` block (`deployedField`, `series`) and `release.stages` are retired
|
|
26
|
+
|
|
27
|
+
The keys are ignored, like any unknown key, so a config carrying them still
|
|
28
|
+
loads. `spec-sync doctor` names them as unused config (a `warn`, never a
|
|
29
|
+
failure). Delete them when you remove the CI step.
|
|
30
|
+
|
|
31
|
+
### Breaking change 3 — no state drift
|
|
32
|
+
|
|
33
|
+
`spec-sync status --remote` and `/spec-status` no longer compare the tracker's
|
|
34
|
+
state with the spec's. A ticket someone moved on GitHub is not reported, and
|
|
35
|
+
push still resends state only when the spec itself moves. The edited-description
|
|
36
|
+
warning is unchanged.
|
|
37
|
+
|
|
38
|
+
## `@skitterbyte/skitterspec-azure-boards` v1 → v2 (tell, don't track)
|
|
39
|
+
|
|
40
|
+
Skitterspec now tells Azure Boards about **its own** transitions and nothing
|
|
41
|
+
else. It writes nothing about releases or deployments, and it never reads a
|
|
42
|
+
ticket's state back.
|
|
43
|
+
|
|
44
|
+
### Breaking change 1 — The `release create|deployed|complete` verbs are removed
|
|
45
|
+
|
|
46
|
+
They exit 1 with one line naming `spec-sync released <range> --json`. A pipeline
|
|
47
|
+
still calling them goes red rather than silently believing it mirrors
|
|
48
|
+
anything. **To migrate**, replace the step with your own script over the
|
|
49
|
+
release's ticket list:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
skitterspec-azure-boards spec-sync released "<previous-tag>..<tag>" --json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Each ticket row now carries `spec` and `url` (both `null` for a ref no spec has
|
|
56
|
+
linked), so the script needs no second lookup. Set whatever Azure Boards field,
|
|
57
|
+
state or release object your process uses from that list. The checkout needs
|
|
58
|
+
`fetch-depth: 0` so the previous tag is visible.
|
|
59
|
+
|
|
60
|
+
### Breaking change 2 — The `releases` block (`workItemType`, `series`, `ticketTag`, `deployedField`) and `release.stages` are retired
|
|
61
|
+
|
|
62
|
+
The keys are ignored, like any unknown key, so a config carrying them still
|
|
63
|
+
loads. `spec-sync doctor` names them as unused config (a `warn`, never a
|
|
64
|
+
failure). Delete them when you remove the CI step.
|
|
65
|
+
|
|
66
|
+
### Breaking change 3 — no state drift
|
|
67
|
+
|
|
68
|
+
`spec-sync status --remote` and `/spec-status` no longer compare the tracker's
|
|
69
|
+
state with the spec's. A ticket someone moved on Azure Boards is not reported, and
|
|
70
|
+
push still resends state only when the spec itself moves. The edited-description
|
|
71
|
+
warning is unchanged.
|
|
72
|
+
|
|
73
|
+
## `@skitterbyte/skitterspec-jira` v1 → v2 (tell, don't track)
|
|
74
|
+
|
|
75
|
+
Skitterspec now tells Jira about **its own** transitions and nothing else. It
|
|
76
|
+
writes nothing about releases or deployments, and it never reads an issue's
|
|
77
|
+
status back.
|
|
78
|
+
|
|
79
|
+
### Breaking change 1 — no status drift
|
|
80
|
+
|
|
81
|
+
`spec-sync status --remote` and `/spec-status` no longer compare Jira's status
|
|
82
|
+
with the spec's. An issue someone moved on Jira is not reported, and push still
|
|
83
|
+
transitions it only when the spec itself moves. The edited-description warning
|
|
84
|
+
is unchanged.
|
|
85
|
+
|
|
86
|
+
### Breaking change 2 — `release.stages` retired
|
|
87
|
+
|
|
88
|
+
It was accepted and validated before, and never acted on. It is now ignored
|
|
89
|
+
like any unknown key, and `spec-sync doctor` names it as unused config (a
|
|
90
|
+
`warn`, never a failure). Delete it.
|
|
91
|
+
|
|
92
|
+
### Also — `released --json` names the spec
|
|
93
|
+
|
|
94
|
+
Each ticket row now carries `spec` and `url` (both `null` for a ref no spec has
|
|
95
|
+
linked), so CI can record a release, for example as a fix version, without a
|
|
96
|
+
second lookup:
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
skitterspec-jira spec-sync released "<previous-tag>..<tag>" --json
|
|
100
|
+
```
|
|
101
|
+
|
|
3
102
|
## `@skitterbyte/skitterspec` v23 → v24 (hotfix land runs the release itself)
|
|
4
103
|
|
|
5
104
|
### Breaking change 1 — `spec-env hotfix land` runs its steps, and holds before the carry
|
|
@@ -100,6 +199,43 @@ Nothing in `env.config.json` changes.
|
|
|
100
199
|
**If you added a secrets-hook exception for the printed seed commands**, you
|
|
101
200
|
can remove it now. Nothing shipped prints them any more.
|
|
102
201
|
|
|
202
|
+
## `@skitterbyte/skitterspec-azure-boards` v0 → v1 (first release)
|
|
203
|
+
|
|
204
|
+
Nothing to migrate: this is the distribution's first release. It is a superset
|
|
205
|
+
of `@skitterbyte/skitterspec` — install exactly one of the base or one
|
|
206
|
+
superset. Coming from the base, run `npx @skitterbyte/skitterspec-azure-boards
|
|
207
|
+
update` and run `/spec-azure-setup`
|
|
208
|
+
(or `skitterspec-azure-boards spec-sync init-config --org-url … --project …`).
|
|
209
|
+
|
|
210
|
+
**Coming from `@skitterbyte/skitterspec-linear` or `-jira`** — a repo moving
|
|
211
|
+
trackers: swap the distribution, configure Azure as above, set
|
|
212
|
+
`refs.foreignPatterns` to the old team key (e.g. `"ERQ-\\d+"`), then push each
|
|
213
|
+
backlog and in-progress spec (`/spec-push`, or `spec-sync apply --all
|
|
214
|
+
in-progress` and `--all backlog`) to link it to a new work item. Finished specs
|
|
215
|
+
keep their old stamps (`linear_identifier`, …) as history; nothing rewrites
|
|
216
|
+
them. Old `Refs: ERQ-12` trailers stay in the log, and `spec-sync released`
|
|
217
|
+
counts them as `historical` rather than as missing a ref. New commits carry
|
|
218
|
+
`Refs: AB#n`. Delete the old `linear.config.json` / `jira.config.json` once the
|
|
219
|
+
open specs are linked.
|
|
220
|
+
|
|
221
|
+
## `@skitterbyte/skitterspec-github` v0 → v1 (first release)
|
|
222
|
+
|
|
223
|
+
Nothing to migrate: this is the distribution's first release. It is a superset
|
|
224
|
+
of `@skitterbyte/skitterspec` — install exactly one of the base or one
|
|
225
|
+
superset. Coming from the base, run `npx @skitterbyte/skitterspec-github
|
|
226
|
+
update`, make a Projects v2 board for the repo, and run `/spec-github-setup`
|
|
227
|
+
(or `skitterspec-github spec-sync init-config --owner … --repo … --project N`).
|
|
228
|
+
|
|
229
|
+
**Coming from `@skitterbyte/skitterspec-linear` or `-jira`** — a repo moving
|
|
230
|
+
trackers: swap the distribution, configure GitHub as above, then push each
|
|
231
|
+
backlog and in-progress spec (`/spec-push`, or `spec-sync apply --all
|
|
232
|
+
in-progress` and `--all backlog`) to link it to a new issue. Finished specs keep
|
|
233
|
+
their old stamps (`linear_identifier`, …) as history; nothing rewrites them.
|
|
234
|
+
Old `Refs: SKS-12` trailers stay in the log, and `spec-sync released` counts
|
|
235
|
+
them as `historical` rather than as missing a ref. New commits carry
|
|
236
|
+
`Refs: #N`. Delete the old `linear.config.json` / `jira.config.json` once the
|
|
237
|
+
open specs are linked.
|
|
238
|
+
|
|
103
239
|
## `@skitterbyte/skitterspec-jira` v0 → v1 (first release)
|
|
104
240
|
|
|
105
241
|
Nothing to migrate: this is the distribution's first release. It is a superset
|
|
@@ -238,7 +374,48 @@ can run it.
|
|
|
238
374
|
committed file, so it changes for everyone who pulls and leaves that tree dirty.
|
|
239
375
|
The engine says so when it does.
|
|
240
376
|
|
|
241
|
-
## `@skitterbyte/skitterspec-linear` v18 → v19 (hotfix land runs the release itself)
|
|
377
|
+
## `@skitterbyte/skitterspec-linear` v18 → v19 (tell, don't track; hotfix land runs the release itself)
|
|
378
|
+
|
|
379
|
+
Skitterspec now tells Linear about **its own** transitions and nothing
|
|
380
|
+
else. It writes nothing about releases or deployments, and it never reads a
|
|
381
|
+
ticket's state back.
|
|
382
|
+
|
|
383
|
+
### Breaking change 1 — The `spec-sync stage` verb is removed
|
|
384
|
+
|
|
385
|
+
It exits 1 with one line naming `spec-sync released <range> --json`. A pipeline
|
|
386
|
+
still calling it goes red rather than silently believing it mirrors
|
|
387
|
+
anything. **To migrate**, replace the step with your own script over the
|
|
388
|
+
release's ticket list:
|
|
389
|
+
|
|
390
|
+
```sh
|
|
391
|
+
skitterspec-linear spec-sync released "<previous-tag>..<tag>" --json
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Each ticket row now carries `spec` and `url` (both `null` for a ref no spec has
|
|
395
|
+
linked), so the script needs no second lookup. Set whatever Linear field,
|
|
396
|
+
state or release object your process uses from that list. The checkout needs
|
|
397
|
+
`fetch-depth: 0` so the previous tag is visible.
|
|
398
|
+
|
|
399
|
+
### Breaking change 2 — `release.stages` (the deployment ladder) is retired
|
|
400
|
+
|
|
401
|
+
The keys are ignored, like any unknown key, so a config carrying them still
|
|
402
|
+
loads. `spec-sync doctor` names them as unused config (a `warn`, never a
|
|
403
|
+
failure). Delete them when you remove the CI step.
|
|
404
|
+
|
|
405
|
+
### Breaking change 3 — no state drift
|
|
406
|
+
|
|
407
|
+
`spec-sync status --remote` and `/spec-status` no longer compare the tracker's
|
|
408
|
+
state with the spec's. A ticket someone moved on Linear is not reported, and
|
|
409
|
+
push still resends state only when the spec itself moves. The edited-description
|
|
410
|
+
warning is unchanged.
|
|
411
|
+
|
|
412
|
+
**A ladder rung no longer reads as `complete`.** Where a state was a
|
|
413
|
+
`release.stages` rung, the live `spec-sync list` now shows it as unmapped. To
|
|
414
|
+
keep it reading as finished, list it as an alias:
|
|
415
|
+
`"complete": ["Done", "On Test"]`. `init-config --stage` is gone too, and is
|
|
416
|
+
refused as an unknown flag.
|
|
417
|
+
|
|
418
|
+
### Also — hotfix land runs the release itself
|
|
242
419
|
|
|
243
420
|
The same change as `@skitterbyte/skitterspec` v23 → v24 above — this
|
|
244
421
|
distribution composes the same engine. Read that entry; nothing here is
|
package/README.md
CHANGED
|
@@ -37,7 +37,9 @@ skitterspec-jira spec-sync credentials set
|
|
|
37
37
|
every layer.
|
|
38
38
|
|
|
39
39
|
Not yet shipped for Jira (the Linear superset has them): assignment/`/spec-claim`,
|
|
40
|
-
|
|
40
|
+
`retarget`, `reattach`, `spec-sanitise`. Releases and deploys are the host
|
|
41
|
+
project's to record on every provider: `spec-sync released <range> --json`
|
|
42
|
+
lists a range's tickets for its CI to act on.
|
|
41
43
|
|
|
42
44
|
This package's `bin/`, `src/` and `assets/` are composed at build time from the
|
|
43
45
|
skitterspec monorepo — see the repo for sources and development.
|
package/assets/core/SETUP.md
CHANGED
|
@@ -114,7 +114,7 @@ every configured status exists in the project's workflows.
|
|
|
114
114
|
`/spec-push <spec>` computes the plan, applies it, stamps the returned keys
|
|
115
115
|
into the spec's frontmatter (`jira_identifier` / `jira_url` /
|
|
116
116
|
`jira_issue_id`), verifies the round-trip by words, and records the snapshot
|
|
117
|
-
later
|
|
117
|
+
later pushes diff against. From then on the lifecycle skills keep the
|
|
118
118
|
mirror fresh on their own.
|
|
119
119
|
|
|
120
120
|
---
|
|
@@ -143,6 +143,10 @@ through to a documented conservative default; see the field notes below.
|
|
|
143
143
|
// exits non-zero; [] = none. Each string is a shell command; {slug}/{branch}/{worktreePath}/{projectName}/{portOffset}
|
|
144
144
|
// expand (the cwd is already the worktree, so {worktreePath} is usually
|
|
145
145
|
// redundant). Example: ["pnpm install --frozen-lockfile"].
|
|
146
|
+
// An entry may also be { "run": "<cmd>", "when": ["<glob>", ...] }. `when` is
|
|
147
|
+
// IGNORED here — a fresh worktree always runs every setup step — and exists
|
|
148
|
+
// so `afterLand` can re-run that step after a land that changes its inputs
|
|
149
|
+
// (`{ "setup": true }`, below).
|
|
146
150
|
"setup": [],
|
|
147
151
|
|
|
148
152
|
// Commands to run in the PRIMARY checkout after a land — the counterpart of
|
|
@@ -160,6 +164,51 @@ through to a documented conservative default; see the field notes below.
|
|
|
160
164
|
// failure, report it as a caveat, and never undo the land or block
|
|
161
165
|
// teardown. Literal strings — tokens are NOT expanded, because they run on
|
|
162
166
|
// the base branch where a spec's {slug} or {portOffset} mean nothing.
|
|
167
|
+
//
|
|
168
|
+
// CONDITIONAL ENTRIES. A plain string always runs. An entry may instead be
|
|
169
|
+
// { "run": "<cmd>", "when": ["<glob>", ...] } (a single string `when` is
|
|
170
|
+
// fine), which runs only when the land changes a file one of its globs
|
|
171
|
+
// matches — so a generate or an install costs nothing on a land that does
|
|
172
|
+
// not need it, and is not forgotten on one that does:
|
|
173
|
+
//
|
|
174
|
+
// "afterLand": [
|
|
175
|
+
// { "run": "pnpm install --frozen-lockfile", "when": ["pnpm-lock.yaml", "**/package.json"] },
|
|
176
|
+
// { "run": "pnpm run db:generate", "when": ["prisma/schema.prisma"] },
|
|
177
|
+
// "pnpm db:data-migration"
|
|
178
|
+
// ]
|
|
179
|
+
//
|
|
180
|
+
// Globs are repo-relative: `*` stays within a directory, `**` crosses them
|
|
181
|
+
// (`**/package.json` matches the root one too), `?` is one character. An
|
|
182
|
+
// entry whose `when` holds no usable glob is DROPPED, never widened to
|
|
183
|
+
// always-run.
|
|
184
|
+
// The changed files are what the land brings into the base branch:
|
|
185
|
+
// `base...branch` for `integrate` (read before the rebase, so it is the
|
|
186
|
+
// branch's own changes), and the carried fix commits — minus the release's
|
|
187
|
+
// version commit — for `hotfix land`.
|
|
188
|
+
// When that cannot be known, the conditional entries RUN. That is the
|
|
189
|
+
// "already landed" plan (the land happened elsewhere, so its range is not
|
|
190
|
+
// here) and any diff git would not answer. A redundant generate costs
|
|
191
|
+
// seconds; a skipped one leaves the base red with nothing pointing at why.
|
|
192
|
+
// A land that changed no files is known, not unknown, and skips them.
|
|
193
|
+
// The plan says why for every entry: a matched one ends in
|
|
194
|
+
// `# <path> changed`, an unknown one in `# range unknown` with a line saying
|
|
195
|
+
// so, and the rest are listed under `skipped (nothing in the land matched):`
|
|
196
|
+
// with their globs. The reason is a shell comment, so a line run as printed
|
|
197
|
+
// stays one command. Skipped lines are information — run only the list
|
|
198
|
+
// under `then, … run`.
|
|
199
|
+
//
|
|
200
|
+
// REUSING A SETUP STEP. `{ "setup": true }` expands, in place, to every
|
|
201
|
+
// `setup` entry that declares `when`, so the trigger lives in one list:
|
|
202
|
+
//
|
|
203
|
+
// "setup": [
|
|
204
|
+
// { "run": "pnpm install --frozen-lockfile", "when": ["pnpm-lock.yaml", "**/package.json"] },
|
|
205
|
+
// { "run": "pnpm run db:generate", "when": ["prisma/schema.prisma"] }
|
|
206
|
+
// ],
|
|
207
|
+
// "afterLand": [{ "setup": true }, "pnpm db:data-migration"]
|
|
208
|
+
//
|
|
209
|
+
// Plain setup strings carry no trigger and are never pulled in. The command
|
|
210
|
+
// is copied literally — afterLand expands no tokens — so a setup step that
|
|
211
|
+
// relies on {slug}-style tokens is a poor one to reuse.
|
|
163
212
|
"afterLand": [],
|
|
164
213
|
|
|
165
214
|
// Host dev servers `spec-env dev up <spec>` starts on the spec's port block
|
|
@@ -292,8 +341,11 @@ through to a documented conservative default; see the field notes below.
|
|
|
292
341
|
// one. Empty (the default) = the built-in tagger.
|
|
293
342
|
//
|
|
294
343
|
// `bump` is the built-in tagger's strategy: "patch" (v33.16.4 -> v33.16.5) or
|
|
295
|
-
// "hotfix-suffix" (v33.16.4 -> v33.16.4-hotfix.1, then -hotfix.2,
|
|
296
|
-
// that is already a hotfix tag continues its line
|
|
344
|
+
// "hotfix-suffix" (v33.16.4 -> v33.16.4-hotfix.1, then -hotfix.2, ...). Under
|
|
345
|
+
// either, a base that is already a hotfix tag continues its line
|
|
346
|
+
// (v36.2.3-hotfix.4 -> v36.2.3-hotfix.5), so "patch" never takes a number the
|
|
347
|
+
// mainline owns. When the patch tag already exists, the refusal names the
|
|
348
|
+
// free hotfix tag that "hotfix-suffix" would cut. Any other value is refused
|
|
297
349
|
// when `hotfix land` runs. Tags order X.Y.Z-rc.N < X.Y.Z < X.Y.Z-hotfix.N <
|
|
298
350
|
// X.Y.(Z+1), the same as skittership.
|
|
299
351
|
//
|
|
@@ -16,7 +16,7 @@ NAMES — never a credential. Credentials live in the user-level store
|
|
|
16
16
|
| `apply.transport` | `api` \| `mcp`, or empty for "api when credentials resolve, mcp when they don't". | `""` |
|
|
17
17
|
| `sync.baseDir` | Where last-pushed snapshots live. | `specs/.core/jira-base` |
|
|
18
18
|
| `sync.fieldOwnership` | Which projection fields push (`description`, `subIssues`, `workflowState`, `assignee`; `"none"` declines one). | all `push` |
|
|
19
|
-
| `release.
|
|
19
|
+
| `release.ignorePaths` | Bookkeeping path prefixes `released` ignores, exactly as the Linear provider defines them. | `specs/`, changelogs |
|
|
20
20
|
|
|
21
21
|
Descriptions are stored as ADF (Atlassian Document Format): the adapter
|
|
22
22
|
renders markdown to ADF on push and reduces ADF back to text on read-back, so
|
|
@@ -24,6 +24,31 @@ verification compares words, not bytes. Phase checklists render as live ADF
|
|
|
24
24
|
task lists — still a read-only mirror: a box ticked in Jira is overwritten on
|
|
25
25
|
the next push.
|
|
26
26
|
|
|
27
|
+
## More than one name per bucket — read-only aliases
|
|
28
|
+
|
|
29
|
+
A board usually has more states than the four buckets: "Blocked", "Next
|
|
30
|
+
Sprint", "Released". A bucket's value may be an **array** instead of a string:
|
|
31
|
+
|
|
32
|
+
```jsonc
|
|
33
|
+
"states": {
|
|
34
|
+
"in-progress": ["Active", "Blocked"],
|
|
35
|
+
"complete": ["Closed", "Released"]
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- **The first name is the only one push writes**, and only when the spec
|
|
40
|
+
itself moves. The rest are never written.
|
|
41
|
+
- **The rest are read as that bucket** wherever a listing interprets a status.
|
|
42
|
+
The array is parsed and validated like every other provider's, so one config
|
|
43
|
+
shape works across all of them. `spec-sync status` never reads a ticket's
|
|
44
|
+
status at all: skitterspec tells Jira about its own transitions and nothing
|
|
45
|
+
else.
|
|
46
|
+
- **Nothing is pulled.** An alias changes what the report *says* about a
|
|
47
|
+
ticket, never the spec.
|
|
48
|
+
- A plain string works exactly as before. An alias that also names another
|
|
49
|
+
bucket is refused at load. An alias the tracker lacks is reported by
|
|
50
|
+
`spec-sync states` but never blocks a push, because it is never written.
|
|
51
|
+
|
|
27
52
|
## Choosing your status map
|
|
28
53
|
|
|
29
54
|
Map the four buckets to **work** statuses, never to deployment stages. The
|
|
@@ -44,8 +69,9 @@ the map honest:
|
|
|
44
69
|
type keeps the pipeline — subtasks genuinely have no delivery lifecycle, and
|
|
45
70
|
Jira lets you say so structurally.
|
|
46
71
|
- **Track "which release contains this" with fix versions** — the Releases
|
|
47
|
-
hub
|
|
48
|
-
|
|
72
|
+
hub. Your CI can fill it from `spec-sync released "<prev>..<tag>" --json`,
|
|
73
|
+
which lists the tickets a range contains from their `Refs:` trailers, each
|
|
74
|
+
with its spec and url. (Available on every plan; in the current "Spaces" UI it is not a
|
|
49
75
|
Features toggle but a **tab** — add it with `+` in the space's tab bar.)
|
|
50
76
|
- **Let CI/CD integrations stamp environment position** (the deployments
|
|
51
77
|
panel), so it never needs to be a status at all.
|
|
@@ -56,6 +82,7 @@ status's board category, the closest match when one is missing, and the
|
|
|
56
82
|
one-line remap. Run it whenever the wiring feels uncertain; it changes
|
|
57
83
|
nothing.
|
|
58
84
|
|
|
59
|
-
Walking the parent through pipeline stages at *release* time is the
|
|
60
|
-
|
|
61
|
-
|
|
85
|
+
Walking the parent through pipeline stages at *release* time is the host
|
|
86
|
+
project's job, not skitterspec's: it writes nothing about releases or
|
|
87
|
+
deployments on any provider. A leftover `release.stages` is flagged by
|
|
88
|
+
`spec-sync doctor` as unused config.
|
|
@@ -85,7 +85,7 @@ function findEngine(projectDir) {
|
|
|
85
85
|
if (process.env.SKITTERSPEC_BIN) return process.env.SKITTERSPEC_BIN
|
|
86
86
|
let at = projectDir
|
|
87
87
|
for (let i = 0; i < 6 && at; i++) {
|
|
88
|
-
for (const name of ['skitterspec', 'skitterspec-linear', 'skitterspec-jira']) {
|
|
88
|
+
for (const name of ['skitterspec', 'skitterspec-linear', 'skitterspec-jira', 'skitterspec-github', 'skitterspec-azure-boards']) {
|
|
89
89
|
const candidate = path.join(at, 'node_modules', '.bin', name)
|
|
90
90
|
if (fs.existsSync(candidate)) return candidate
|
|
91
91
|
}
|
|
@@ -82,7 +82,7 @@ function findEngine(projectDir) {
|
|
|
82
82
|
if (process.env.SKITTERSPEC_BIN) return process.env.SKITTERSPEC_BIN
|
|
83
83
|
let at = projectDir
|
|
84
84
|
for (let i = 0; i < 6 && at; i++) {
|
|
85
|
-
for (const name of ['skitterspec', 'skitterspec-linear', 'skitterspec-jira']) {
|
|
85
|
+
for (const name of ['skitterspec', 'skitterspec-linear', 'skitterspec-jira', 'skitterspec-github', 'skitterspec-azure-boards']) {
|
|
86
86
|
const candidate = path.join(at, 'node_modules', '.bin', name)
|
|
87
87
|
if (fs.existsSync(candidate)) return candidate
|
|
88
88
|
}
|
|
@@ -492,9 +492,19 @@ missing alias.
|
|
|
492
492
|
Sync is **one-way**: the repo is the source of truth and the tracker is a
|
|
493
493
|
**generated mirror**. It ships `/spec-push` (repo→tracker; computes a create/update plan
|
|
494
494
|
against a committed last-pushed snapshot and applies it) and `/spec-status`
|
|
495
|
-
(read-only
|
|
496
|
-
|
|
497
|
-
|
|
495
|
+
(read-only — what would push, and whether someone edited a description there),
|
|
496
|
+
backed by a `spec-sync` CLI. There is no content pull — the tracker is never
|
|
497
|
+
read back or merged.
|
|
498
|
+
|
|
499
|
+
**It tells, it doesn't track.** Skitterspec sends the tracker its own
|
|
500
|
+
transitions — a spec starting, a phase finishing, a spec completing or being
|
|
501
|
+
cancelled — and nothing else. A status someone changes on the tracker is
|
|
502
|
+
theirs to have changed: it is never read back, never reported, and only
|
|
503
|
+
overwritten when the spec itself next moves. Releases and deployments are the
|
|
504
|
+
host project's to record. The one release question skitterspec answers is which
|
|
505
|
+
tickets a commit range contains (`spec-sync released <range> --json`, each row
|
|
506
|
+
with its spec and url), because only the `Refs:` trailers know. What the
|
|
507
|
+
project's CI does with that list is its own business.
|
|
498
508
|
|
|
499
509
|
**One-way means the repo wins, and it now wins *loudly*.** Those are different
|
|
500
510
|
claims and only the first was ever in doubt. A ticket's description is generated
|
|
@@ -36,7 +36,7 @@ A verdict sentence, a blank line, then a two-column table:
|
|
|
36
36
|
| **Branch** | `spec/feat-foo` · 3 commits, clean |
|
|
37
37
|
| **Built** | POST /orders handler, orders schema |
|
|
38
38
|
| **Tests** | 128 passed · dotnet test |
|
|
39
|
-
| **Review** | 7 files, +212 −18 · **local** [http://127.0.0.1:7760/…](http://127.0.0.1:7760/…) · **network** [http://192.168.0.136:7760/…](http://192.168.0.136:7760/…) · **remote** off |
|
|
39
|
+
| **Review** | 7 files, +212 −18 · **local** [http://127.0.0.1:7760/…](http://127.0.0.1:7760/…) · **network** [http://192.168.0.136:7760/…](http://192.168.0.136:7760/…) also [http://100.126.111.34:7760/…](http://100.126.111.34:7760/…) (tailnet) · **remote** off |
|
|
40
40
|
| **Follow-ups** | none |
|
|
41
41
|
| **Next** | `/spec-next` → phase 3 (Auth) |
|
|
42
42
|
|
|
@@ -89,6 +89,7 @@ the options may be offered after the table.
|
|
|
89
89
|
|
|
90
90
|
- **local** — [http://127.0.0.1:7760/7e9e123e7540/feat-orders](http://127.0.0.1:7760/7e9e123e7540/feat-orders)
|
|
91
91
|
- **network** — [http://192.168.0.136:7760/7e9e123e7540/feat-orders](http://192.168.0.136:7760/7e9e123e7540/feat-orders)
|
|
92
|
+
- also — [http://100.126.111.34:7760/7e9e123e7540/feat-orders](http://100.126.111.34:7760/7e9e123e7540/feat-orders) (tailnet)
|
|
92
93
|
- **remote** — off · `/spec-remote-review` turns it on
|
|
93
94
|
|
|
94
95
|
live: off — /spec-live to put it live
|
|
@@ -156,6 +157,17 @@ on. Dropping the line is what left a reader who had walked out of the house with
|
|
|
156
157
|
nothing on screen to tell them a remote page was even possible — they could not
|
|
157
158
|
ask for what they could not see.
|
|
158
159
|
|
|
160
|
+
**The `network` tier may carry alternates, and they are part of it — not a fourth tier.**
|
|
161
|
+
The engine offers the other addresses this machine has (a
|
|
162
|
+
tailnet one, for a reader off the wifi) as `also:` lines under `network`, each
|
|
163
|
+
labelled; `--json` carries them as that tier's `alternates`, labelled the same
|
|
164
|
+
way. Copy them as indented sub-bullets under the network bullet, verbatim,
|
|
165
|
+
labels included. This is not an amendment to the tier rule: the stack is the
|
|
166
|
+
engine's, copied rather than composed, and the engine was already printing
|
|
167
|
+
these — a contract that made the skill edit them out was the composing this
|
|
168
|
+
section forbids. A machine with no alternates renders the banner exactly as
|
|
169
|
+
above, minus the sub-bullet.
|
|
170
|
+
|
|
159
171
|
**Only promise a wait the transport can deliver.** The wait itself is real in
|
|
160
172
|
every case; what varies is what carries it back, not whether the run is
|
|
161
173
|
stopped. Three transports, three true sentences:
|
|
@@ -298,8 +310,11 @@ behind, and they cannot.
|
|
|
298
310
|
`fetch(location.pathname, …)`, so a page opened at `127.0.0.1:7760/…` and one
|
|
299
311
|
opened at `192.168.0.136:7760/…` reach the same server process and the same
|
|
300
312
|
pending store. One wait covers both, and a verdict pressed on either wakes the
|
|
301
|
-
same run.
|
|
302
|
-
|
|
313
|
+
same run. A `network` alternate — the `also:` tailnet address — is one more
|
|
314
|
+
door into the same room: the same server answers on it, so the wait-coverage
|
|
315
|
+
line stays true with nothing added to it. Between all of those the objection
|
|
316
|
+
does not apply — which is why they are offered, and why offering them gives
|
|
317
|
+
nothing up.
|
|
303
318
|
|
|
304
319
|
**So two things are required, and the second is what the old rule lacked.**
|
|
305
320
|
Every link is **labelled** with its tier, and one line says
|
|
@@ -410,7 +425,7 @@ behind it — say nothing rather than reporting that there was nothing to report
|
|
|
410
425
|
| `Landed` | A fast-forward, a tag, a cherry-pick. |
|
|
411
426
|
| `Worktree` | A worktree provisioned, entered, or torn down. |
|
|
412
427
|
| `Untouched` | Uncommitted work the run deliberately left alone — whose, and how much. |
|
|
413
|
-
| `Review` | The rendered diff page: files, `+`/`−`, and the stack — the same tiers the banner lists, labelled, run together with `·` because a row is one cell, plus `live` where the engine reported one. **No question** — a row cannot be waited on, so a question in one is unanswerable by construction (see *asking implies waiting*). **Omitted entirely when the run is waiting on a verdict**: the banner after the block carries it instead, and a row saying the same thing beside it splits the reader's attention across two places. |
|
|
428
|
+
| `Review` | The rendered diff page: files, `+`/`−`, and the stack — the same tiers the banner lists, labelled, run together with `·` because a row is one cell, with any `network` alternates inline after the network URL (`also <url> (tailnet)`), plus `live` where the engine reported one. **No question** — a row cannot be waited on, so a question in one is unanswerable by construction (see *asking implies waiting*). **Omitted entirely when the run is waiting on a verdict**: the banner after the block carries it instead, and a row saying the same thing beside it splits the reader's attention across two places. |
|
|
414
429
|
| `Follow-ups` | **Always.** `none`, or one line each. |
|
|
415
430
|
| `Next` | **Last.** The single next action for this work — runnable from the state the run leaves behind. |
|
|
416
431
|
|
|
@@ -174,7 +174,10 @@ three, in that order — never the bare `page:` path.
|
|
|
174
174
|
**Between the two, run the after-land commands.** Where the `integrate` plan
|
|
175
175
|
ends with a `then, in the primary checkout (<path>), run:` section — the
|
|
176
176
|
project's `afterLand` — run those commands there, in order, once the
|
|
177
|
-
fast-forward has succeeded, and on an already-landed no-op too.
|
|
177
|
+
fast-forward has succeeded, and on an already-landed no-op too. Run only the
|
|
178
|
+
lines under that heading; a `skipped (nothing in the land matched):` block
|
|
179
|
+
lists conditional entries this land did not need, and is information, never
|
|
180
|
+
commands. A lockfile
|
|
178
181
|
refresh is exactly the land that needs them.
|
|
179
182
|
**Stop at the first non-zero exit**: the run becomes `⚠️`, naming the command that failed and the ones that
|
|
180
183
|
did not run, and it **never undoes the land and never stops the `down`**.
|
|
@@ -205,8 +208,8 @@ the shape; this section carries only what is specific here.
|
|
|
205
208
|
|
|
206
209
|
- `✅` — the work is done, the tests are green, and the verdict was acted on.
|
|
207
210
|
- `⚠️` — done and green, with something worth knowing — an after-land command
|
|
208
|
-
that failed, for one. `Notes` carries what the after-land commands ran
|
|
209
|
-
where they stopped; nothing about them when none are configured.
|
|
211
|
+
that failed, for one. `Notes` carries what the after-land commands ran and
|
|
212
|
+
skipped, or where they stopped; nothing about them when none are configured.
|
|
210
213
|
- `❌` — the tests are red, or the land failed part-way. Quote the failure and
|
|
211
214
|
say what is standing.
|
|
212
215
|
- `⏸` — refused before acting: no name given, the name is already a spec, the
|
|
@@ -527,7 +527,9 @@ underneath a reader when detection flipped mid-session.
|
|
|
527
527
|
|
|
528
528
|
So: **relay all three tier lines, every time.** Any `also:` lines sit under
|
|
529
529
|
`network` — the other addresses this machine has, offered because the
|
|
530
|
-
best-guess one can be wrong — so pass them on rather than editing them out
|
|
530
|
+
best-guess one can be wrong — so pass them on rather than editing them out: in
|
|
531
|
+
the banner they are indented sub-bullets under **network**, labels
|
|
532
|
+
(`(tailnet)`) included, exactly as `.claude/rules/spec-reports.md` shows.
|
|
531
533
|
|
|
532
534
|
**Never read an environment variable to decide anything about the offer** — not
|
|
533
535
|
`SSH_CONNECTION`, not `CLAUDE_CODE_*`, not a tty check. There is nothing left
|
|
@@ -169,8 +169,8 @@ the shape; this section carries only what is specific here.
|
|
|
169
169
|
**Fields:** `Tracker` · `Spec` · `Tests` · `Notes` · `Landed` · `Worktree` ·
|
|
170
170
|
`Follow-ups` · `Next`
|
|
171
171
|
|
|
172
|
-
`Notes` carries the after-land commands step 6 ran, and where
|
|
173
|
-
failure, the command that failed and the ones that did not run. A project with
|
|
172
|
+
`Notes` carries the after-land commands step 6 ran, and where, and any it
|
|
173
|
+
skipped because the land matched no `when` — or, on a failure, the command that failed and the ones that did not run. A project with
|
|
174
174
|
no `afterLand` says nothing about it.
|
|
175
175
|
|
|
176
176
|
**Report this spec and no other.** A completion is the moment it is most
|
|
@@ -287,7 +287,10 @@ is finished — e.g. to run a later phase in CI or a shared test env? Use
|
|
|
287
287
|
3. **Run the after-land commands.** Where the plan ends with a
|
|
288
288
|
`then, in the primary checkout (<path>), run:` section — the project's
|
|
289
289
|
`afterLand`, such as a data migration — run those commands in that
|
|
290
|
-
directory, in order, after the fast-forward succeeds.
|
|
290
|
+
directory, in order, after the fast-forward succeeds. Run only the lines under that
|
|
291
|
+
heading; a `skipped (nothing in the land matched):` block lists conditional
|
|
292
|
+
entries this land did not need, and is information, never commands.
|
|
293
|
+
**Also on the no-op**:
|
|
291
294
|
the engine prints them there deliberately, because a land made on another
|
|
292
295
|
machine leaves them pending here. **Stop at the first non-zero exit** — a
|
|
293
296
|
later command may depend on it — and carry on with the rest of the skill:
|
|
@@ -538,7 +538,9 @@ underneath a reader when detection flipped mid-session.
|
|
|
538
538
|
|
|
539
539
|
So: **relay all three tier lines, every time.** Any `also:` lines sit under
|
|
540
540
|
`network` — the other addresses this machine has, offered because the
|
|
541
|
-
best-guess one can be wrong — so pass them on rather than editing them out
|
|
541
|
+
best-guess one can be wrong — so pass them on rather than editing them out: in
|
|
542
|
+
the banner they are indented sub-bullets under **network**, labels
|
|
543
|
+
(`(tailnet)`) included, exactly as `.claude/rules/spec-reports.md` shows.
|
|
542
544
|
|
|
543
545
|
**Never read an environment variable to decide anything about the offer** — not
|
|
544
546
|
`SSH_CONNECTION`, not `CLAUDE_CODE_*`, not a tty check. There is nothing left
|
|
@@ -189,7 +189,8 @@ Then name the next step:
|
|
|
189
189
|
|
|
190
190
|
- `/spec` — write a spec; with Jira configured it creates the linked issue in
|
|
191
191
|
the configured project and stamps the key.
|
|
192
|
-
- `/spec-status` — read-only
|
|
192
|
+
- `/spec-status` — read-only report of what would push, the safe way to prove
|
|
193
|
+
the link works.
|
|
193
194
|
- `/spec-push` — send a spec up.
|
|
194
195
|
|
|
195
196
|
Mention what setup did **not** configure, so the defaults aren't mistaken for
|
|
@@ -574,7 +574,9 @@ underneath a reader when detection flipped mid-session.
|
|
|
574
574
|
|
|
575
575
|
So: **relay all three tier lines, every time.** Any `also:` lines sit under
|
|
576
576
|
`network` — the other addresses this machine has, offered because the
|
|
577
|
-
best-guess one can be wrong — so pass them on rather than editing them out
|
|
577
|
+
best-guess one can be wrong — so pass them on rather than editing them out: in
|
|
578
|
+
the banner they are indented sub-bullets under **network**, labels
|
|
579
|
+
(`(tailnet)`) included, exactly as `.claude/rules/spec-reports.md` shows.
|
|
578
580
|
|
|
579
581
|
**Never read an environment variable to decide anything about the offer** — not
|
|
580
582
|
`SSH_CONNECTION`, not `CLAUDE_CODE_*`, not a tty check. There is nothing left
|