@skitterbyte/skitterspec-linear 15.0.0 → 17.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.
Files changed (44) hide show
  1. package/MIGRATION.md +218 -0
  2. package/README.md +34 -1
  3. package/assets/claude-md-section.md +29 -18
  4. package/assets/commands/spec-remote-review.md +22 -0
  5. package/assets/core/SETUP.md +10 -6
  6. package/assets/core/env.config.json.example +4 -2
  7. package/assets/core/env.config.md +100 -23
  8. package/assets/core/linear.config.json.example +2 -1
  9. package/assets/core/linear.config.md +49 -22
  10. package/assets/review/page.html +1044 -101
  11. package/assets/rules/spec-planning.md +35 -3
  12. package/assets/rules/spec-reports.md +131 -20
  13. package/assets/skills/spec/SKILL.md +161 -4
  14. package/assets/skills/spec-bug/SKILL.md +68 -30
  15. package/assets/skills/spec-cancel/SKILL.md +2 -2
  16. package/assets/skills/spec-claim/SKILL.md +12 -4
  17. package/assets/skills/spec-complete/SKILL.md +2 -2
  18. package/assets/skills/spec-diff/SKILL.md +131 -36
  19. package/assets/skills/spec-hotfix/SKILL.md +61 -25
  20. package/assets/skills/spec-linear-setup/SKILL.md +19 -11
  21. package/assets/skills/spec-next/SKILL.md +121 -58
  22. package/assets/skills/spec-push/SKILL.md +45 -0
  23. package/assets/skills/spec-review/SKILL.md +89 -2
  24. package/assets/skills/spec-reviewed/SKILL.md +31 -5
  25. package/assets/skills/spec-start/SKILL.md +26 -3
  26. package/assets/skills/spec-status/SKILL.md +20 -6
  27. package/assets/skills/spec-sync/SKILL.md +1 -0
  28. package/package.json +2 -2
  29. package/src/cli.js +913 -116
  30. package/src/env/classify.js +87 -2
  31. package/src/env/config.js +214 -17
  32. package/src/env/live.js +94 -0
  33. package/src/env/resolve.js +36 -2
  34. package/src/env/review.js +542 -21
  35. package/src/env/serve.js +298 -19
  36. package/src/env/supervise.js +8 -1
  37. package/src/init.js +60 -9
  38. package/src/vendor/linear/api.js +111 -2
  39. package/src/vendor/linear/cli-sync.js +661 -11
  40. package/src/vendor/linear/config.js +41 -13
  41. package/src/vendor/linear/doctor.js +6 -5
  42. package/src/vendor/sync-core/index.js +11 -3
  43. package/src/vendor/sync-core/src/compare.js +65 -0
  44. package/src/vendor/sync-core/src/normalize.js +26 -0
@@ -19,7 +19,9 @@ your team ID (and an optional grouping project).
19
19
  The loader (`src/sync/config.js` → `loadLinearConfig`) merges your file over the
20
20
  frozen defaults below and returns `{ config, present }`; `present:false` means no
21
21
  live `linear.config.json` was found (the opt-in gate — it never throws on
22
- absence). A `sync.fieldOwnership` value outside `both|pull|push` is a hard error.
22
+ absence). A `sync.fieldOwnership` value outside `both|pull|push|none` is a hard error.
23
+ `none` is how a repo **declines** a field the defaults own — see
24
+ **Assignment** below, which is the field that needs it.
23
25
 
24
26
  ## Fields
25
27
 
@@ -40,7 +42,10 @@ absence). A `sync.fieldOwnership` value outside `both|pull|push` is a hard error
40
42
  "intake": {
41
43
  "label": "", // inbox filter — the label the web app files under
42
44
  "bugLabels": [], // e.g. ["bug"] — these route to /spec-bug instead
43
- "hotfixLabels": [] // e.g. ["production"] — these route to /spec-hotfix
45
+ "hotfixLabels": [], // e.g. ["production"] — these route to /spec-hotfix
46
+ "preserveOriginal": true // keep the reporter's description as a comment
47
+ // before the linking push replaces it. ON by
48
+ // default; false declines.
44
49
  },
45
50
 
46
51
  // How a spec's parts map onto Linear objects: a spec is an Issue, each phase a
@@ -119,7 +124,8 @@ absence). A `sync.fieldOwnership` value outside `both|pull|push` is a hard error
119
124
  "fieldOwnership": {
120
125
  "description": "push",
121
126
  "subIssues": "push",
122
- "workflowState": "push"
127
+ "workflowState": "push",
128
+ "assignee": "push"
123
129
  },
124
130
 
125
131
  // Markdown sections of 00-overview.md that are local-only scaffolding and
@@ -135,37 +141,41 @@ absence). A `sync.fieldOwnership` value outside `both|pull|push` is a hard error
135
141
 
136
142
  ## Assignment (`sync.fieldOwnership.assignee`)
137
143
 
138
- Off by default. Add one key and the spec issue is assigned to whoever is
139
- building it:
144
+ **On by default since v16.** The spec issue is assigned to whoever is building
145
+ it: `/spec-start` records them, and the issue is released when the spec
146
+ completes. To decline it, say so:
140
147
 
141
148
  ```jsonc
142
149
  "fieldOwnership": {
143
- "description": "push",
144
- "subIssues": "push",
145
- "workflowState": "push",
146
- "assignee": "push" // <- the whole opt-in
150
+ "assignee": "none" // <- the whole opt-out
147
151
  }
148
152
  ```
149
153
 
150
154
  It is deliberately **not** a config key of its own. `fieldOwnership` is already
151
155
  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.
156
+ and assignment is exactly one more field the repo owns. `none` is a value rather
157
+ than an omitted key because the map merges **per key** onto the defaults: once a
158
+ field is owned by default there is nothing an absent key can subtract.
159
+
160
+ - **`none` = inert.** No writes, no prompts, no drift line, and no assignee hash
161
+ in any snapshot identical in every respect to a field the config never
162
+ listed. A repo that declines cannot tell the feature exists.
163
+ - **It was opt-in until v16**, and the cost of that was silent in the wrong
164
+ direction: a repo that never added the line looked exactly like one that had
165
+ decided against it, so the only signal was noticing an unassigned issue weeks
166
+ later. Declining is now a decision on the record; forgetting is not a state.
159
167
  - **The bucket decides.** The spec's `linear_assignee_id` is pushed while the
160
168
  spec is live (`backlog`, `in-progress`) and cleared once it reaches `complete`
161
169
  or `cancelled` — so finishing a spec hands the issue back with no unassign
162
170
  step for anyone to remember. The stamp stays in the file, and so does
163
171
  `> **Developer:**`: they record who *actioned* the work, which outlives who is
164
172
  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".
173
+ - **Unset means don't touch, and this is what makes on-by-default safe.** A
174
+ spec that records nobody sends no assignee at all, so an issue a PM assigned in
175
+ Linear is never overwritten. Only an assignee the repo itself pushed is ever
176
+ cleared and a snapshot written before the field was owned counts as "never
177
+ pushed", not as "was nobody". Turning the default on widens who gets
178
+ **assigned**; it cannot widen who gets **unassigned**.
169
179
  - **Only the spec issue.** Phase sub-issues are never assigned: one person builds
170
180
  a spec, and N assigned sub-issues is N notifications for one piece of work.
171
181
  They stay independently assignable in Linear.
@@ -605,8 +615,25 @@ The issue **becomes** the spec's issue: its identifier is stamped as
605
615
  `linear_identifier`, phases become its sub-issues, and the **linking push**
606
616
  — which runs as the spec is created — replaces its description with the spec. The
607
617
  reporter's comments, links and subscribers stay on the one issue everyone is
608
- already watching; their original words are carried into the spec's **Problem**
609
- (or **Symptom**) section, and Linear keeps the original in the issue's history.
618
+ already watching.
619
+
620
+ **What happens to what they wrote.** Two things, and neither is the issue's
621
+ history:
622
+
623
+ - **Adoption posts it back as a comment**, verbatim, before the push replaces
624
+ the description (`skitterspec spec-sync preserve <spec>`, which the adoption
625
+ skills run for you). A comment is outside `sync.fieldOwnership` by design —
626
+ Linear-native triage that one-way sync never touches — which is exactly what
627
+ makes it safe: no later push can clobber it. It carries a marker so a second
628
+ run posts nothing, and it never fails the adoption: an unreachable Linear, an
629
+ issue with no description or `intake.preserveOriginal: false` all exit 0 and
630
+ say so. Set that key to `false` to turn the whole thing off.
631
+ - **The spec quotes them** in its **Problem** (or **Symptom**) section, which
632
+ *is* pushed as part of the generated description — so the reporter's words are
633
+ in the description too, and they round-trip like everything else.
634
+
635
+ The issue's history does technically hold the pre-adoption text, and it is not
636
+ the answer: a diff viewer is not quotable and nobody opens one.
610
637
 
611
638
  An issue already stamped on a spec can't be adopted twice — `skitterspec spec-sync
612
639
  linked` is the list that's checked.