@skitterbyte/skitterspec-linear 11.0.0 → 13.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 +260 -10
- package/README.md +32 -2
- package/assets/claude-md-section.md +48 -2
- package/assets/commands/spec-connect.md +2 -2
- package/assets/commands/spec-live.md +2 -2
- package/assets/core/SETUP.md +21 -3
- package/assets/core/env.config.json.example +9 -3
- package/assets/core/env.config.md +102 -30
- package/assets/core/gating.config.json.example +4 -0
- package/assets/core/gating.config.md +81 -0
- package/assets/core/linear.config.md +67 -8
- package/assets/review/page.html +1501 -0
- package/assets/rules/spec-planning.md +224 -15
- package/assets/rules/spec-reports.md +269 -0
- package/assets/skills/spec/SKILL.md +64 -13
- package/assets/skills/spec-bug/SKILL.md +193 -27
- package/assets/skills/spec-cancel/SKILL.md +99 -8
- package/assets/skills/spec-claim/SKILL.md +114 -0
- package/assets/skills/spec-complete/SKILL.md +123 -22
- package/assets/skills/spec-diff/SKILL.md +564 -0
- package/assets/skills/spec-hotfix/SKILL.md +202 -22
- package/assets/skills/spec-init/SKILL.md +49 -9
- package/assets/skills/spec-linear-setup/SKILL.md +86 -7
- package/assets/skills/spec-list/SKILL.md +218 -0
- package/assets/skills/spec-next/SKILL.md +300 -7
- package/assets/skills/spec-push/SKILL.md +45 -22
- package/assets/skills/spec-review/SKILL.md +59 -11
- package/assets/skills/spec-reviewed/SKILL.md +241 -0
- package/assets/skills/spec-start/SKILL.md +426 -66
- package/assets/skills/spec-status/SKILL.md +24 -2
- package/assets/skills/spec-sync/SKILL.md +47 -11
- package/assets/skills/spec-to-main/SKILL.md +42 -20
- package/package.json +11 -7
- package/src/cli.js +1710 -80
- package/src/env/building.js +143 -0
- package/src/env/classify.js +91 -0
- package/src/env/config.js +57 -9
- package/src/env/provision.js +192 -19
- package/src/env/proxy.js +34 -1
- package/src/env/render.js +3 -12
- package/src/env/resolve.js +296 -9
- package/src/env/review.js +1329 -0
- package/src/env/serve.js +549 -0
- package/src/env/teardown.js +13 -6
- package/src/gating.js +155 -0
- package/src/init.js +124 -2
- package/src/prompts.js +10 -1
- package/src/vendor/linear/api.js +104 -1
- package/src/vendor/linear/cli-sync.js +874 -17
- package/src/vendor/linear/config.js +8 -0
- package/src/vendor/linear/credentials.js +94 -0
- package/src/vendor/linear/doctor.js +35 -0
- package/src/vendor/linear/identity.js +105 -0
- package/src/vendor/linear/mcp.js +26 -0
- package/src/vendor/sync-core/index.js +6 -2
- package/src/vendor/sync-core/src/compare.js +74 -5
- package/src/vendor/sync-core/src/normalize.js +30 -0
- package/src/vendor/sync-core/src/push.js +11 -1
- package/src/vendor/sync-core/src/write.js +38 -0
- package/LICENSE +0 -21
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
# `env.config.json` — per-spec isolation config
|
|
2
2
|
|
|
3
3
|
Opt-in config for per-spec isolation (git worktree + optional namespaced Docker
|
|
4
|
-
stack + host dev servers + a front-door proxy
|
|
5
|
-
|
|
6
|
-
`/spec-
|
|
7
|
-
`skitterspec spec-env
|
|
8
|
-
|
|
4
|
+
stack + host dev servers + a front-door proxy per in-progress spec).
|
|
5
|
+
Provisioning is folded into `/spec-start`, teardown into `/spec-complete` ·
|
|
6
|
+
`/spec-cancel`, traffic diversion is `/spec-connect`, and reading a spec's diff
|
|
7
|
+
is `/spec-diff`; the `skitterspec spec-env
|
|
8
|
+
<up|down|prune|dev|connect|integrate|hotfix|live|review|stage|status|resolve>` CLI is
|
|
9
|
+
the engine beneath them.
|
|
9
10
|
|
|
10
11
|
**Once this file is present, isolation is the default policy:** `/spec-start` gives
|
|
11
|
-
**every** in-progress spec its own git worktree automatically. Docker is a
|
|
12
|
-
spec escalation** — a spec brings up a stack only when its
|
|
13
|
-
is `worktree + docker` (set at `/spec` when it touches
|
|
14
|
-
services). A `worktree`-only spec takes no registry slot, no
|
|
15
|
-
`.env`.
|
|
12
|
+
**every** in-progress spec its own git worktree automatically. Docker is a
|
|
13
|
+
**per- spec escalation** — a spec brings up a stack only when its
|
|
14
|
+
`> **Stack:**` header is `worktree + docker` (set at `/spec` when it touches
|
|
15
|
+
the DB / stateful services). A `worktree`-only spec takes no registry slot, no
|
|
16
|
+
port block, and no `.env`.
|
|
16
17
|
|
|
17
18
|
**Adopt it** with `skitterspec init --isolation` (or copy
|
|
18
19
|
`env.config.json.example` → `env.config.json` here) and edit the values. While
|
|
@@ -31,11 +32,12 @@ no live `env.config.json` was found.
|
|
|
31
32
|
// Where a spec's branch gets built.
|
|
32
33
|
//
|
|
33
34
|
// "worktree" (default) — every spec gets its own git worktree. Several
|
|
34
|
-
// specs run side by side and `main` stays free
|
|
35
|
-
//
|
|
35
|
+
// specs run side by side and `main` stays free. `/spec-start`
|
|
36
|
+
// moves your session into the spec's worktree with a `cd`, so
|
|
37
|
+
// the terminal you are already in follows the work.
|
|
36
38
|
// "checkout" — the branch is built in the primary checkout instead. One
|
|
37
|
-
// spec at a time,
|
|
38
|
-
// terminal
|
|
39
|
+
// spec at a time, and nowhere else to stand: the work comes to
|
|
40
|
+
// your terminal rather than your terminal going to it.
|
|
39
41
|
//
|
|
40
42
|
// Pick it for how you work, not for what this repo contains — a project with
|
|
41
43
|
// no dev servers may still want several specs in flight. An unrecognised
|
|
@@ -124,19 +126,6 @@ no live `env.config.json` was found.
|
|
|
124
126
|
"host": "127.0.0.1" // bind host for the canonical ports
|
|
125
127
|
},
|
|
126
128
|
|
|
127
|
-
// Optional, editor/terminal-agnostic opener. `/spec-start` RUNS it when it hands
|
|
128
|
-
// you into a new worktree — after provisioning and bootstrap, so the session
|
|
129
|
-
// opens onto a tree that is ready to work in. The template is expanded with
|
|
130
|
-
// {worktreePath}, {slug}, {branch}, {projectName}, {portOffset}.
|
|
131
|
-
// Empty = nothing is opened (the path is just printed), which is how you turn
|
|
132
|
-
// the auto-open off. A non-interactive run skips it either way — an opened
|
|
133
|
-
// window nobody is sitting at helps no one.
|
|
134
|
-
// Examples: "code {worktreePath}", "tmux new-window -c {worktreePath}",
|
|
135
|
-
// or a "warp://..." deeplink for Warp users.
|
|
136
|
-
"open": {
|
|
137
|
-
"command": ""
|
|
138
|
-
},
|
|
139
|
-
|
|
140
129
|
// Machine-local slot registry (spec → slot index). Resolved against the
|
|
141
130
|
// primary checkout root, shared by all worktrees, gitignored.
|
|
142
131
|
"registry": ".spec-env/registry.json",
|
|
@@ -153,6 +142,26 @@ no live `env.config.json` was found.
|
|
|
153
142
|
"identifierField": ""
|
|
154
143
|
},
|
|
155
144
|
|
|
145
|
+
// Paths that belong to a spec ALONGSIDE its own `specs/<bucket>/<name>/`
|
|
146
|
+
// folder. `/spec-start` uses this to tell "the spec you just wrote, not yet
|
|
147
|
+
// committed" apart from someone else's uncommitted work: if every dirty path
|
|
148
|
+
// belongs to the spec being started it is committed for you, and if a single
|
|
149
|
+
// path does not, the start is refused as before.
|
|
150
|
+
//
|
|
151
|
+
// Provider-neutral by design — the base engine must not know that any
|
|
152
|
+
// particular tracker exists — so you declare the shape here. `{slug}` and
|
|
153
|
+
// `{identifier}` expand exactly as in `branch.pattern` above, `{identifier}`
|
|
154
|
+
// via `branch.identifierField`.
|
|
155
|
+
//
|
|
156
|
+
// A pattern using {identifier} matches NOTHING when no identifier resolves
|
|
157
|
+
// (no `identifierField` set, or a spec never pushed to a tracker). That is
|
|
158
|
+
// deliberate: the file it names then belongs to some other spec, and the safe
|
|
159
|
+
// failure is a refusal you clear with /commit, not a stranger's file swept
|
|
160
|
+
// into your commit. Default: none — a spec owns only its own folder.
|
|
161
|
+
"spec": {
|
|
162
|
+
"companionPaths": []
|
|
163
|
+
},
|
|
164
|
+
|
|
156
165
|
// Integration base branch — the branch specs fork from and land back onto
|
|
157
166
|
// (used by the teardown "merged?" guard and, later, the integrate step).
|
|
158
167
|
// Empty = auto-detect: origin/HEAD → main → master. Set it when your default
|
|
@@ -168,9 +177,10 @@ no live `env.config.json` was found.
|
|
|
168
177
|
"refuseTeardownIfUnpushed": true
|
|
169
178
|
},
|
|
170
179
|
|
|
171
|
-
// What teardown cleans up beyond this machine.
|
|
172
|
-
// branch
|
|
173
|
-
// branch on the remote
|
|
180
|
+
// What teardown cleans up beyond this machine. Nothing publishes a spec
|
|
181
|
+
// branch for you, so a remote copy exists only because you pushed it by hand —
|
|
182
|
+
// and without this a completed spec leaves that merged branch on the remote
|
|
183
|
+
// forever. `deleteRemoteBranch`:
|
|
174
184
|
// "prompt" (default) — plan `git push <remote> --delete <branch>` in its own
|
|
175
185
|
// "confirm with the user first" section; /spec-complete and
|
|
176
186
|
// /spec-cancel ask before running it.
|
|
@@ -207,6 +217,68 @@ no live `env.config.json` was found.
|
|
|
207
217
|
"bump": "patch",
|
|
208
218
|
"cherryPickMain": true,
|
|
209
219
|
"targets": []
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
// Reading a spec's diff (`spec-env review`, `/spec-diff`).
|
|
223
|
+
//
|
|
224
|
+
// `reader` decides how the page's LOCATION IS WORDED, and — through
|
|
225
|
+
// `serveOnRemote` below — whether the engine stands its local server up so a
|
|
226
|
+
// remote reader gets a link that opens. It never decides to PUBLISH. Three
|
|
227
|
+
// values:
|
|
228
|
+
// "local" — you are at the machine holding the page; a file:// URL opens.
|
|
229
|
+
// "remote" — you are not; it does not, so the page is served instead.
|
|
230
|
+
// "detect" — work it out (the default).
|
|
231
|
+
// An explicit "local"/"remote" is BELIEVED WITHOUT SNIFFING: you know where
|
|
232
|
+
// you are reading, and no signal outranks being told. Detection is only the
|
|
233
|
+
// default, and it has three outcomes rather than two — local, remote, and
|
|
234
|
+
// unknown. Unknown behaves exactly as the tool did before any of this existed
|
|
235
|
+
// (the file:// URL, no warning), because a wrong "local" prints a dead link
|
|
236
|
+
// and a wrong "remote" warns at someone whose link works fine. An
|
|
237
|
+
// unrecognised value falls through to "detect", so a typo cannot become a
|
|
238
|
+
// confident answer. Default: detect.
|
|
239
|
+
//
|
|
240
|
+
// `servePort` is the default port for `spec-env review serve`, which renders
|
|
241
|
+
// every spec's diff per request on one local server. `--port` overrides it per
|
|
242
|
+
// run. The server binds 127.0.0.1 unless `--host 0.0.0.0` is passed, which
|
|
243
|
+
// mints an unguessable path token and prints the LAN URL including it —
|
|
244
|
+
// anyone holding that URL can read every spec's diff while it runs.
|
|
245
|
+
// Default: 7777.
|
|
246
|
+
//
|
|
247
|
+
// `serveOnRemote` is whether a "remote" reader may have that server started
|
|
248
|
+
// FOR them. On (the default) the engine brings it up, binds 0.0.0.0, and puts
|
|
249
|
+
// the served URL on the `open:` line — best-guess network address first, the
|
|
250
|
+
// rest listed under it, because the guess reads interface names and a VPN or
|
|
251
|
+
// an unusual adapter will fool it. Off, you get the file:// URL with its
|
|
252
|
+
// "will not open where you are reading" marker and the command to type.
|
|
253
|
+
// Either way NOTHING IS PUBLISHED on a detection: a server is one process
|
|
254
|
+
// ended by one flag, while a published page is one this tooling cannot
|
|
255
|
+
// remove, so that half stays an explicit ask. Teardown names a server that
|
|
256
|
+
// served the last spec, and `spec-env prune` reaps a pidfile whose process is
|
|
257
|
+
// gone. Default: true.
|
|
258
|
+
//
|
|
259
|
+
// `commitWith` names the skill a COMMITTING verdict hands off to. A review
|
|
260
|
+
// page ends in a verdict — commit, commit & continue, request changes,
|
|
261
|
+
// discuss — and the point of the first two is that the commit follows from
|
|
262
|
+
// the reading rather than costing a separate decision. Skitterspec never
|
|
263
|
+
// commits through a skill it vendored: `/commit` ships with skittership, a
|
|
264
|
+
// different package, and a copy living here would fork it. Two shapes:
|
|
265
|
+
// "/commit" — the default; hand off to skittership's commit skill.
|
|
266
|
+
// "<name>" — any other skill your project installs.
|
|
267
|
+
// With the named skill unavailable, the committing branch commits directly —
|
|
268
|
+
// stage, typecheck, test, conventional message — and SAYS it took that path,
|
|
269
|
+
// because a commit made under rules nobody configured must not read as one
|
|
270
|
+
// made under /commit.
|
|
271
|
+
//
|
|
272
|
+
// THERE IS NO OFF SWITCH. `"none"` existed and was removed: it produced a
|
|
273
|
+
// verdict that records itself and does nothing, which is the one thing a
|
|
274
|
+
// review page must not offer — a review is the guard in front of an action.
|
|
275
|
+
// Recording an approval for SOMEONE ELSE to act on is a separate mechanism,
|
|
276
|
+
// not a value of this key. Default: "/commit".
|
|
277
|
+
"review": {
|
|
278
|
+
"reader": "detect",
|
|
279
|
+
"servePort": 7777,
|
|
280
|
+
"serveOnRemote": true,
|
|
281
|
+
"commitWith": "/commit"
|
|
210
282
|
}
|
|
211
283
|
}
|
|
212
284
|
```
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# `gating.config.json` — release-gating config
|
|
2
|
+
|
|
3
|
+
Opt-in config for **release gating**: whether each spec records a decision about
|
|
4
|
+
shipping behind a feature flag.
|
|
5
|
+
|
|
6
|
+
The problem it solves is not "we forgot to use a flag" — it is that
|
|
7
|
+
**nobody can tell whether the question was asked**. A spec can go from `/spec`
|
|
8
|
+
through
|
|
9
|
+
implementation to `/spec-complete` with no flag and no mention of one, and that
|
|
10
|
+
is indistinguishable from "we considered it and decided against". Prose guidance
|
|
11
|
+
in a project rule has already been shown not to close that gap; a required header
|
|
12
|
+
does, because its absence is visible.
|
|
13
|
+
|
|
14
|
+
**Skitterspec bakes in the offer, never the mechanism.** How your project does
|
|
15
|
+
flags — a resolver, an admin toggle, env precedence, a vendor SDK — is none of
|
|
16
|
+
its business, and it never reads your flag code. It asks the question, cites your
|
|
17
|
+
own documentation, and records the answer.
|
|
18
|
+
|
|
19
|
+
**Adopt it** by copying `gating.config.json.example` → `gating.config.json` here
|
|
20
|
+
(or `skitterspec init --gating`). While this file is absent the feature is
|
|
21
|
+
entirely unused: no grill question, no header, nothing to check — which is read
|
|
22
|
+
as "this project does not use feature flags".
|
|
23
|
+
|
|
24
|
+
## Fields
|
|
25
|
+
|
|
26
|
+
```jsonc
|
|
27
|
+
{
|
|
28
|
+
// Where THIS project documents how its flags work — a repo-relative path,
|
|
29
|
+
// cited when the question is asked so the answer is an informed one.
|
|
30
|
+
// Skitterspec never reads the file; it only names it. Empty = say nothing.
|
|
31
|
+
"guidance": ".claude/rules/feature-flags.md",
|
|
32
|
+
|
|
33
|
+
// The value written when the user declines a flag, so a project can
|
|
34
|
+
// standardise its wording. Must keep the `none: <reason>` shape — the reason
|
|
35
|
+
// half is the whole point (see below). Empty falls back to `none: <reason>`.
|
|
36
|
+
"default": "none: <reason>"
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## The header it drives
|
|
41
|
+
|
|
42
|
+
With this file present, every spec `/spec`, `/spec-bug` and `/spec-hotfix` write
|
|
43
|
+
carries a `Gating:` field beside `Stack:` in `00-overview.md`:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
> **Gating:** search-ranking-v2
|
|
47
|
+
> **Gating:** none: additive, nothing to revert
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Two valid shapes, and one that is not:
|
|
51
|
+
|
|
52
|
+
| Value | Meaning |
|
|
53
|
+
|-------|---------|
|
|
54
|
+
| a flag name | ships behind that flag |
|
|
55
|
+
| `none: <reason>` | deliberately not flagged, and why |
|
|
56
|
+
| *missing, empty, or a bare `none`* | **not a decision** — nobody answered |
|
|
57
|
+
|
|
58
|
+
The reason half is load-bearing. `none: additive, nothing to revert` is a
|
|
59
|
+
decision a reviewer can disagree with; a bare `none` is a shrug, and a missing
|
|
60
|
+
line is an oversight. Distinguishing those three is the entire feature.
|
|
61
|
+
|
|
62
|
+
## What it never does
|
|
63
|
+
|
|
64
|
+
- **It never blocks.** `skitterspec gating check` reports and exits 0;
|
|
65
|
+
`/spec-start` and `/spec-complete` mention a missing header and carry on.
|
|
66
|
+
Nothing here can stop a spec being started, completed or landed.
|
|
67
|
+
- **It never accuses an old spec.** Only `specs/backlog/` and
|
|
68
|
+
`specs/in-progress/` are read. Specs finished or abandoned before you adopted
|
|
69
|
+
gating are out of range by construction, not by a filter someone has to
|
|
70
|
+
remember.
|
|
71
|
+
- **It never learns your flag system.** `guidance` is a path it prints, nothing
|
|
72
|
+
more.
|
|
73
|
+
|
|
74
|
+
## Checking
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
skitterspec gating check # the spec in flight here
|
|
78
|
+
skitterspec gating check <spec> # one named spec
|
|
79
|
+
skitterspec gating check --all # every active spec
|
|
80
|
+
skitterspec gating check --json # for tooling
|
|
81
|
+
```
|
|
@@ -133,6 +133,64 @@ absence). A `sync.fieldOwnership` value outside `both|pull|push` is a hard error
|
|
|
133
133
|
}
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
+
## Assignment (`sync.fieldOwnership.assignee`)
|
|
137
|
+
|
|
138
|
+
Off by default. Add one key and the spec issue is assigned to whoever is
|
|
139
|
+
building it:
|
|
140
|
+
|
|
141
|
+
```jsonc
|
|
142
|
+
"fieldOwnership": {
|
|
143
|
+
"description": "push",
|
|
144
|
+
"subIssues": "push",
|
|
145
|
+
"workflowState": "push",
|
|
146
|
+
"assignee": "push" // <- the whole opt-in
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
It is deliberately **not** a config key of its own. `fieldOwnership` is already
|
|
151
|
+
the documented extension point — "any key you add joins the pushed projection" —
|
|
152
|
+
and assignment is exactly one more field the repo owns.
|
|
153
|
+
|
|
154
|
+
- **Absent = inert.** No writes, no prompts, no drift line, and no assignee hash
|
|
155
|
+
in any snapshot. A project that never opts in cannot tell the feature exists,
|
|
156
|
+
which is why it is missing from `linear.config.json.example`: that file is
|
|
157
|
+
copied verbatim into new projects, and shipping the key there would opt
|
|
158
|
+
everyone in by default.
|
|
159
|
+
- **The bucket decides.** The spec's `linear_assignee_id` is pushed while the
|
|
160
|
+
spec is live (`backlog`, `in-progress`) and cleared once it reaches `complete`
|
|
161
|
+
or `cancelled` — so finishing a spec hands the issue back with no unassign
|
|
162
|
+
step for anyone to remember. The stamp stays in the file, and so does
|
|
163
|
+
`> **Developer:**`: they record who *actioned* the work, which outlives who is
|
|
164
|
+
holding it.
|
|
165
|
+
- **Unset means don't touch.** A spec that records nobody sends no assignee at
|
|
166
|
+
all, so an issue a PM assigned in Linear is never overwritten. Only an assignee
|
|
167
|
+
the repo itself pushed is ever cleared — and a snapshot written before you
|
|
168
|
+
opted in counts as "never pushed", not as "was nobody".
|
|
169
|
+
- **Only the spec issue.** Phase sub-issues are never assigned: one person builds
|
|
170
|
+
a spec, and N assigned sub-issues is N notifications for one piece of work.
|
|
171
|
+
They stay independently assignable in Linear.
|
|
172
|
+
|
|
173
|
+
**Who you are** is not configured here, and cannot be — this file is committed,
|
|
174
|
+
so a user id in it would follow the repo to every teammate who clones it. It is
|
|
175
|
+
derived from your own API key (`viewer`) and cached per machine in
|
|
176
|
+
`~/.config/skitterspec/credentials.json` beside the key. `spec-sync whoami`
|
|
177
|
+
shows it, `--set` overrides it when the key is shared or a bot's, and
|
|
178
|
+
`spec-sync users` looks somebody up by name or email.
|
|
179
|
+
|
|
180
|
+
Ownership moves with **`/spec-claim`** — take it, `--release` it, or `--to` a
|
|
181
|
+
teammate.
|
|
182
|
+
|
|
183
|
+
**Reading it back** is **`/spec-list`** (engine: `spec-sync list`), the one
|
|
184
|
+
command that asks Linear what exists instead of the repo. It lists parentless
|
|
185
|
+
issues — a phase sub-issue carries a parent, a spec issue does not — joined to
|
|
186
|
+
the local spec folder that owns each one, so every row carries the name you
|
|
187
|
+
paste into `/spec-start`. Scope is the live states by default
|
|
188
|
+
(`--state`/`--all`/`--in-progress` to change it, `--next N` for the top of the
|
|
189
|
+
backlog in Linear's own order), and `--mine`/`--by <user>` filter by assignee,
|
|
190
|
+
reusing the identity above rather than a second copy of it. It writes nothing,
|
|
191
|
+
and it says what it did not show — the count, the archived exclusion, and any
|
|
192
|
+
cap — rather than implying a completeness it never checked.
|
|
193
|
+
|
|
136
194
|
## The deployment ladder (`release.stages`)
|
|
137
195
|
|
|
138
196
|
A spec's lifecycle stops at `complete`. Where a ticket goes **after** that —
|
|
@@ -366,9 +424,10 @@ default for anyone who never sets one.
|
|
|
366
424
|
- `"deferred"` — only once the work starts. A spec sitting in `specs/backlog/`
|
|
367
425
|
mirrors as **the issue alone**; its sub-issues are created by the push that
|
|
368
426
|
follows `/spec-start`.
|
|
369
|
-
- `"inline"` — never. Each phase becomes a
|
|
370
|
-
description**, with its full task list, and
|
|
371
|
-
table of contents. One issue per spec,
|
|
427
|
+
- `"inline"` — never. Each phase becomes a
|
|
428
|
+
**section of the spec issue's own description**, with its full task list, and
|
|
429
|
+
the `## Phases` index stays as the table of contents. One issue per spec,
|
|
430
|
+
however many phases it has.
|
|
372
431
|
|
|
373
432
|
### One mode, or one per bucket
|
|
374
433
|
|
|
@@ -405,11 +464,11 @@ that made the choice worth it.
|
|
|
405
464
|
|
|
406
465
|
### Switching modes is non-destructive
|
|
407
466
|
|
|
408
|
-
**A phase already carrying a `linear_issue_id` keeps its sub-issue in every
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
467
|
+
**A phase already carrying a `linear_issue_id` keeps its sub-issue in every mode**,
|
|
468
|
+
and is never *also* inlined. One-way sync has no delete op, so withholding a
|
|
469
|
+
live sub-issue would not remove it from Linear — it would freeze it there,
|
|
470
|
+
never updated again. So changing `mapping.phases` only ever changes what has
|
|
471
|
+
yet to be minted, and a spec part-way through keeps a coherent mirror.
|
|
413
472
|
|
|
414
473
|
**Adopting on an established repo:** set `complete: "inline"` (and `"deferred"`
|
|
415
474
|
or `"inline"` for `backlog`) **before** the first backfill push. Finished specs
|