@skitterbyte/skitterspec-linear 13.0.0 → 15.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.
@@ -473,7 +473,7 @@ sub-issue jump from Backlog straight to Done, with nothing visible in between.
473
473
  - **Say what happened** in the skill's report: mirror updated, skipped as
474
474
  unlinked, or failed with the reason.
475
475
 
476
- ## 6b. Render the page then offer the review, never write it
476
+ ## 6b. Render the page, arm the gate, then wait for the verdict
477
477
 
478
478
  **Only when the project has per-spec isolation** (`specs/.core/env.config.json`
479
479
  present). Without it there is no worktree to read and this step does not exist —
@@ -489,28 +489,33 @@ skitterspec spec-env review <spec>
489
489
  **This is free.** The engine reads git and splices the patches into a template;
490
490
  the diff never passes through you, so a 266KB patch costs nothing.
491
491
 
492
- **Then offer `/spec-diff`. Do not run it.** The written review is the part that
493
- costs — roughly **700 output tokens**, because writing it means reading the diff
494
- — and that spend is the operator's call, not a default.
492
+ **Then arm the gate**, so the fix now owes a verdict:
495
493
 
496
- **The offer is the `Review` row of step 6's block** — the counts, the page link
497
- and a question, in one row:
494
+ ```
495
+ skitterspec spec-env review arm <spec>
496
+ ```
498
497
 
499
- | **Review** | <N> files, +<a> −<d> · [open the page](<the `open:` URL>) want a written review before you commit? |
498
+ **Then wait**, and `/spec-next` §5 owns the sequencefollow it there rather
499
+ than reading a second copy here: note the moment you start waiting, watch the
500
+ pending store, end your turn, and let `--claim-since` pick the one pass that
501
+ arrived inside the window. The banner it describes is what this skill emits in
502
+ place of a `Review` row, and the routing on the verdict is `/spec-diff` §2 and
503
+ §4, as it is everywhere.
500
504
 
501
- **It ends in a question, addressed to someone.** It was once a fenced block of
502
- engine output, and it fired on every phase and was never once taken: two quoted
503
- lines under the test counts, addressed to nobody, with the report then closing
504
- on *"commit this first"* the last instruction the reader got was to move on,
505
- so they did. A row in a labelled table is findable; a question in it is
506
- answerable. Both halves are load-bearing.
505
+ **Why this skill arms as well as waits.** Waiting is what any offer does;
506
+ **arming** asserts an obligation that outlives the turn, and belongs only to
507
+ work that is finished. A hotfix is a completed unit red→green against a
508
+ released tag so it qualifies, and a wait with nothing owed behind it is a
509
+ suggestion rather than a gate.
507
510
 
508
- **Never bury it and never split it.** It sits above `Follow-ups` and `Next`, and
509
- the page and the question stay in the same row: two adjacent rows about one page
510
- make the reader resolve a distinction before acting on either. A later edit that
511
- moves it out of the block, or separates the link from the question, undoes this
512
- and should be read as a regression rather than tidying.
511
+ **A hotfix is the case where reading it matters most.** This change is about to
512
+ be tagged and shipped to production from a release line, not merged into a
513
+ branch someone else will read first. The verdict is the only review it gets.
513
514
 
515
+ **It is user-visible, and that is deliberate.** Once armed, a `git commit` in
516
+ this worktree is refused until a verdict is sent or
517
+ `skitterspec spec-env review skip "<reason>"` records the decision to move on.
518
+ The exit is always one command, and one of them is *"I am moving on"*.
514
519
 
515
520
  Relay the **`open:`** line rather than the bare path: a path is not clickable in
516
521
  any terminal, and a page nobody can open is a page nobody reads.
@@ -578,10 +583,16 @@ the shape; this section carries only what is specific here.
578
583
  **Fields:** `Tracker` · `Branch` · `Spec` · `Cause` · `Built` · `Tests` ·
579
584
  `Review` · `Follow-ups` · `Next`
580
585
 
586
+ **`Review` is emitted only where step 6b did not run** — a project with no
587
+ isolation, or a render that failed. Where the run is waiting, the banner carries
588
+ the whole subject and the row is dropped, per `.claude/rules/spec-reports.md`.
589
+
581
590
  **The base tag goes in the verdict clause** — `✅ /spec-hotfix · hotfix-foo ·
582
591
  green on v2.3.1`. Which released version this was fixed against is the first
583
592
  thing anyone needs, and it is not a field: the clause is where the run says
584
593
  where it got to.
585
594
 
586
- Step 6b's offer is the `Review` row, not a paragraph after the block — the
587
- counts, the link and the question in one row. Nothing follows the block.
595
+ Step 6b ends in the **banner**, not a paragraph and not a row — the run is
596
+ waiting on a verdict, and the banner is the shape that says so. Nothing follows
597
+ it. Where the run is not waiting it asks nothing at all: a `Review` row carries
598
+ the counts and the link, and no question (*asking implies waiting*).
@@ -105,6 +105,40 @@ Ensure it exists. If missing, create it documenting:
105
105
  Read a sibling spec skill (e.g. `spec`, `spec-next`) for the canonical shapes
106
106
  rather than inventing them. If the rule already exists, leave it unless stale.
107
107
 
108
+ ## 4a. The review-gate hook (`.claude/hooks/review-gate.cjs`)
109
+
110
+ `skitterspec init` **and `skitterspec update`** install the hook script and
111
+ register it in the project's **committed** `.claude/settings.json` as a
112
+ `PreToolUse` hook on `Bash`. It runs one engine call per Bash tool call and
113
+ refuses a `git commit` in a worktree whose phase is still awaiting a verdict.
114
+
115
+ **Both commands, and that is not a detail.** Copying the script and registering
116
+ it are one operation, and they were once split across two code paths — so
117
+ `update` landed the file, reported `created:`, and wired nothing, on every
118
+ project that upgraded. Landing the script is not installing the hook.
119
+
120
+ **`.cjs`, and the extension is load-bearing.** The script lands inside the
121
+ target project, where *that* project's `package.json` decides how node parses a
122
+ `.js` — so CommonJS shipped as `.js` crashes in any `"type": "module"` project,
123
+ on every Bash tool call. `.cjs` settles it at the file. A registration left over
124
+ from the release that named `.js` is rewritten in place, keeping any wrapping
125
+ the operator added, rather than gaining a second entry beside it.
126
+
127
+ **Committed, not machine-local**, and the difference is the point: the trusted
128
+ worktree root is one machine's absolute path, while *a phase that ended owes an
129
+ answer* is the project's policy and should reach everyone who clones it. The
130
+ command is written with `${CLAUDE_PROJECT_DIR}`, so it holds in worktrees too.
131
+
132
+ **Say it is there, once.** A hook that blocks a commit with the operator not
133
+ knowing a hook exists reads as a broken git, so name it in the report when it is
134
+ newly registered — and say what turns it off (`review.required: false` in
135
+ `env.config.json`, which the hook defers to entirely).
136
+
137
+ **Never fatal, and never rewritten.** A settings file that is not parseable JSON
138
+ is reported and left exactly as it is — it is the operator's config, and
139
+ everything else in it would be lost. A settings file that already names this
140
+ script, however it was wrapped, is left alone rather than gaining a second copy.
141
+
108
142
  ## 5. CLAUDE.md
109
143
 
110
144
  Ensure a `## Spec workflow` section exists. If absent, add one with the
@@ -116,7 +116,7 @@ A **name argument** is accepted, but it must *match* the spec in flight — it
116
116
  narrows a re-run, it does not select a different spec. A mismatch refuses,
117
117
  naming both.
118
118
 
119
- ## 2. Pre-flight — commit prior work
119
+ ## 2. Pre-flight — the last phase is committed, and was answered
120
120
 
121
121
  Before writing any code for this phase, get the workspace clean:
122
122
 
@@ -128,6 +128,32 @@ Before writing any code for this phase, get the workspace clean:
128
128
  next phase on top of an uncommitted one. (Skip if this is the first phase —
129
129
  there's nothing prior to commit.)
130
130
 
131
+ - **Confirm the last phase's review was answered.** Ask the engine, never the
132
+ sidecar:
133
+
134
+ ```
135
+ skitterspec spec-env review gate <spec> --json
136
+ ```
137
+
138
+ `state: "armed"` means a phase ended, its page was rendered, and nobody has
139
+ said what they concluded. **Refuse, with the `⏸` block**, and name the three
140
+ ways out in the `Next` row: read the page and send a verdict, type
141
+ `/spec-reviewed` if one is already waiting, or
142
+ `skitterspec spec-env review skip "<reason>"` to move on with the reason on
143
+ the record.
144
+
145
+ `state: "clear"` carries on.
146
+ **`state: "unknown"` also carries on, in silence** — it is the project opting out, a sidecar that could not be read, or
147
+ a spec the engine could not resolve, and none of those is evidence that
148
+ anything is owed (`.claude/rules/negative-checks.md`). Do not mention it: a
149
+ line about a gate nobody armed is an accusation against a healthy repo.
150
+
151
+ **This refusal counts nothing.** It is not a tally of ticked boxes — those
152
+ still gate nothing and still are not counted. It asserts one thing: a phase
153
+ that ended has an answer. And the exit is always **one command**, one of them
154
+ being *"I am moving on"*, which is what keeps this a push rather than a wall —
155
+ a gate with no exit gets switched off wholesale instead of answered.
156
+
131
157
  ## 3. Implement the phase
132
158
 
133
159
  Identify the **first unfinished phase** from the `00-overview.md` phase index,
@@ -280,7 +306,7 @@ skitterspec spec-env resolve <spec> --assert-primary-clean
280
306
  - **"cannot tell"** — no baseline, or one from another spec. It exits 0 and
281
307
  claims nothing; say so in one line and carry on. An absence is not evidence.
282
308
 
283
- ## 5. Render the page then offer the review, never write it
309
+ ## 5. Render the page, arm the gate, then wait for the verdict
284
310
 
285
311
  **Only when the project has per-spec isolation** (`specs/.core/env.config.json`
286
312
  present). Without it there is no worktree to read and this step does not exist.
@@ -297,31 +323,137 @@ skitterspec spec-env review <spec>
297
323
  the diff never passes through you, so a 266KB patch costs nothing. Report the
298
324
  path it prints and move on.
299
325
 
326
+ **This render takes the committing button set**, which is the default — so
327
+ `--buttons` is not passed. That is a statement about the work, not about the
328
+ gate: the phase is finished, so `Commit` and `Commit & Continue` are the right
329
+ verbs for it. A render part-way through a run takes `--buttons midrun` and
330
+ offers `Continue` instead; `/spec-diff` owns that case.
331
+
332
+ **Then arm the gate**, so the phase now owes a verdict:
333
+
334
+ ```
335
+ skitterspec spec-env review arm <spec> --phase <n>
336
+ ```
337
+
338
+ It is idempotent within a phase, so a re-render does not restart the clock, and
339
+ it is **never fatal**: a project that opted out, or an engine that could not
340
+ resolve the spec, says so and the phase is still built. Nothing here counts
341
+ anything — the gate asserts that a phase which ended has an answer, and that is
342
+ all it asserts.
343
+
300
344
  **Then offer `/spec-diff`. Do not run it.** The written review is the part that
301
345
  costs — roughly **700 output tokens**, because writing it means reading the diff
302
346
  — and that spend is the operator's call, not a default.
303
347
 
304
- **The offer is the `Review` row of step 6's block** the counts, the page link
305
- and a question, in one row:
348
+ **Where you are going to wait, the offer is the banner after the block**
349
+ defined in `.claude/rules/spec-reports.md`, and **the `Review` row is dropped**
350
+ so one subject lives in one place:
306
351
 
307
- | **Review** | <N> files, +<a> −<d> · [open the page](<the `open:` URL>) — want a written review before you commit? |
352
+ ---
353
+
354
+ ## ⏸ Review ready — <N> files, +<a> −<d>
355
+
356
+ **[Open the page](<the `open:` URL>)** · I'm holding here until you send a verdict.
308
357
 
309
- **It ends in a question, addressed to someone.** It was once a fenced block of
310
- engine output, and it fired on every phase and was never once taken: two quoted
311
- lines under the test counts, addressed to nobody, with the report then closing
312
- on *"commit this first"* — the last instruction the reader got was to move on,
313
- so they did. A row in a labelled table is findable; a question in it is
314
- answerable. Both halves are load-bearing.
358
+ `/spec-reviewed` picks it up · `spec-env review skip "<reason>"` moves on
315
359
 
316
- **Never bury it and never split it.** It sits above `Follow-ups` and `Next`, and
317
- the page and the question stay in the same row: two adjacent rows about one page
318
- make the reader resolve a distinction before acting on either. A later edit that
319
- moves it out of the block, or separates the link from the question, undoes this
320
- and should be read as a regression rather than tidying.
360
+ **One link, and the engine has already chosen it.** Where it served, the
361
+ banner carries the served URL and the wait is real. Where it could not serve —
362
+ a busy port, a machine with no network address that is the case publishing
363
+ exists for, and then the banner carries the published URL with what is true of
364
+ it: *press a verdict, then type `/spec-reviewed`*, because nothing pushes from
365
+ the artifact store into this conversation.
366
+
367
+ **Never offer both.** Publishing while the server is reachable adds a second
368
+ door the reader cannot tell apart from the first, and the wait only stands
369
+ behind one of them — that was done, and three verdicts were pressed on the
370
+ published page while each sat unread under a line saying I was holding. The
371
+ published page is for the reader the server cannot reach, and for nobody
372
+ else.
373
+
374
+ ---
321
375
 
322
- **Non-blocking, deliberately.** Do not end your turn waiting on the answer.
323
- Phases get chained — `/commit && /spec-next` typed as one line — and a question
324
- that stops the run taxes every phase to fix a problem the row already fixes.
376
+ **Where you are not waiting, it stays the `Review` row** — the counts and the
377
+ page link, and **no question**:
378
+
379
+ | **Review** | <N> files, +<a> −<d> · [open the page](<the `open:` URL>) |
380
+
381
+ At the end of a phase you are always waiting, so this shape belongs to the
382
+ renders that are not this step: a mid-phase `/spec-diff`, a page produced
383
+ alongside other work. A row cannot be waited on, so a question in one is
384
+ unanswerable however findable it is.
385
+
386
+ **Both shapes are addressed to someone, and that is the constraint.** The offer
387
+ was once a fenced block of engine output: two quoted lines under the test
388
+ counts, addressed to nobody, with the report then closing on *"commit this
389
+ first"* — the last instruction the reader got was to move on, so they did. A row
390
+ in a labelled table is findable; a banner says the work has stopped. What must
391
+ never come back is something unaddressed, unfindable, or fenced.
392
+
393
+ **The row asks nothing, and that is not a weakening of the above.** A row cannot
394
+ be waited on, so a question in one is unanswerable however findable it is —
395
+ which is the failure `spec-reports.md` records under *asking implies waiting*.
396
+ The row names the page; the banner is what asks, because the banner is the shape
397
+ the run is standing behind.
398
+
399
+ **Never bury it and never split it.** The row sits above the last two rows of
400
+ the block, and the page and its counts stay in the same row; the banner
401
+ replaces the row rather than joining it. Two places naming one page make the reader
402
+ resolve a distinction before acting on either — which is the same failure
403
+ whether the two places are adjacent rows or a row and a banner.
404
+
405
+ ### Then wait for the verdict, where the harness can
406
+
407
+ The row is findable, but a row cannot make the continuation follow from the
408
+ reading — and that is the gap the whole gate exists to close.
409
+ **Where this harness can watch a file and wake the session on a change, use it.** The pass
410
+ arrives at the engine's holding area, the watch fires, and the verdict the
411
+ reader pressed is what carries the work on.
412
+
413
+ 1. **Note the moment you start waiting**, as an ISO timestamp. That instant is
414
+ the whole scope of what you may claim.
415
+ 2. **Watch the pending store** for the spec —
416
+ `.spec-env/reviews/<spec>.pending.json` in the primary checkout — and
417
+ **end your turn**. Do not poll, and do not hold the turn open: the point is that
418
+ the reader has the terminal back while they read.
419
+ 3. **On waking, let the engine pick**:
420
+
421
+ ```
422
+ skitterspec spec-env review <spec> --claim-since <the timestamp> --json
423
+ ```
424
+
425
+ It claims the one pass that arrived inside the window, and acts on nothing
426
+ at all when none did (the watch can fire on a write that was not a pass) or
427
+ when two did (two sittings, or two people — the operator has the codes).
428
+ 4. **Route on the verdict** exactly as `/spec-diff` §2 and §4 describe. Do not
429
+ restate that routing here.
430
+
431
+ **WHY THIS IS SAFE, AND WHAT IT COSTS.** It was once true that a device
432
+ reaching your page could not reach your conversation, and that fact was the
433
+ whole guard: a pass sat in the holding area until a person typed
434
+ `/spec-reviewed`. This replaces that guard rather than weakening it by
435
+ accident, and the replacement is two things together — **the serve token**,
436
+ 48 unguessable bits minted per server, which is what decides who can POST at
437
+ all; and **the window**, which is what decides which pass is yours. A pass
438
+ already waiting when the wait began is never claimed by it, which is exactly
439
+ the stranger's pass the old rule was written about. What is genuinely given up
440
+ is that the page can now act, so the token has become a credential rather than
441
+ a convenience — and `--claim-since` refusing to choose between two passes is
442
+ what stops a race becoming a wrong commit.
443
+
444
+ **Where the harness cannot watch a file, you still wait** — the turn ending is
445
+ the wait. Say the page is rendered and that you are holding for the pass, then end
446
+ your turn; the reader's next message is what carries it, and `/spec-diff` picks
447
+ it up from the paste exactly as it always has. The gate holds either way: it is
448
+ the engine's, not the watch's.
449
+
450
+ This once read *"change nothing"* — keep the row, keep the question, do not
451
+ wait — and that exemption is the hatch a whole class of unanswerable questions
452
+ came through. Every harness can end a turn.
453
+
454
+ **It still does not break a chained run.** `/commit && /spec-next` is typed as
455
+ one line; by the time this step is reached the chain has finished, so waiting
456
+ here stops nothing that was still going to happen.
325
457
 
326
458
  Relay the **`open:`** line the engine prints, not the bare path: a path is not
327
459
  clickable in any terminal, and a page nobody can open is a page nobody reads.
@@ -350,7 +482,8 @@ drift.
350
482
 
351
483
  **Serving is the engine's to do; publishing is never.** A `remote` reader
352
484
  authorises a local server — one process, ended by one flag, leaving nothing
353
- behind — and authorises nothing else. Publishing leaves a page this tooling
485
+ behind — and authorises nothing else. A reader the server CAN reach is not a
486
+ reason to publish as well: the page they can already open is the page to name. Publishing leaves a page this tooling
354
487
  cannot remove, so it stays an ask in every case, always. If the engine could not
355
488
  serve (a busy port, a machine with no network address) it falls back to the
356
489
  `file://` URL with its marker, and that is when publishing is worth naming.
@@ -375,7 +508,7 @@ the shape; this section carries only what is specific here.
375
508
  - `❌` — the phase's tests are red, or it stopped part-way. Quote the failure.
376
509
  - `⏸` — no spec in flight, or the name given does not match the one that is.
377
510
 
378
- **Fields:** `Tracker` · `Branch` · `Built` · `Tests` · `Snags` · `Review` ·
511
+ **Fields:** `Tracker` · `Branch` · `Built` · `Tests` · `Notes` · `Review` ·
379
512
  `Follow-ups` · `Next`
380
513
 
381
514
  ## 6a. End in a picker
@@ -384,6 +517,12 @@ The block says what happened; this is what to do about it. Offer the same four
384
517
  endings the review page carries, so a review finishes the same way wherever the
385
518
  reader is standing — the page, a pasted code, or here.
386
519
 
520
+ **Not when you are waiting.** Where §5 set a watch and ended the turn, the
521
+ verdict is coming from the page and the picker would be a second way to answer
522
+ a question already asked — so the block ends on `Next`, which names the page.
523
+ The picker is for the run that did not wait: no watch available, or a render
524
+ the reader is expected to come back to in their own time.
525
+
387
526
  | Option | Does |
388
527
  |--------|------|
389
528
  | `Reviewed` | Claims the waiting pass and routes on its verdict |
@@ -427,8 +566,10 @@ top of an uncommitted one — so a `Next` that names only `/spec-next` sends the
427
566
  reader straight into that refusal. The two halves are four hundred lines apart,
428
567
  which is exactly how they drifted.
429
568
 
430
- **Step 5's offer is the `Review` row.** It is not a paragraph after the block,
431
- because nothing is after the block: the counts, the page link and the question
432
- go in one row, above `Follow-ups` and `Next`. Step 5 renders before the commit
433
- and this step is where its offer lands, so the two must not disagree about where
434
- it goes.
569
+ **Step 5's offer lands in one of two shapes, and never both.** Waiting on a
570
+ verdict the **banner** after the block, and no `Review` row. Not waiting the
571
+ `Review` row, and no banner. Neither is a paragraph: the ban on prose after the
572
+ block is untouched, and the banner is a control the contract names
573
+ (`.claude/rules/spec-reports.md`). Step 5 renders before the commit and this
574
+ step is where its offer lands, so the two must not disagree about which shape it
575
+ takes.
@@ -37,16 +37,25 @@ of `/spec-diff` step 0's central rule:
37
37
  **never claim a pass you were not asked to claim**.
38
38
 
39
39
  That rule exists because a review pass can be POSTed by anything that reaches
40
- the page, and what it cannot reach is **this conversation**. So a stranger's pass
41
- sits in the holding area forever unless an agent goes and fetches it, which is
42
- exactly what happened once: an agent found a waiting approval, read its code off
43
- disk, claimed it, and reported the round-trip working.
40
+ the page. So a stranger's pass sits in the holding area unless an agent goes
41
+ and fetches it, which is exactly what happened once: an agent found a waiting
42
+ approval, read its code off disk, claimed it, and reported the round-trip
43
+ working.
44
44
 
45
45
  Prose alone did not prevent that. The harness does —
46
- **the model cannot invoke this skill** — so a pass is only ever picked up
47
- because a person typed the command. Typing it *is* the human signal. A later edit that makes this skill
48
- model-invocable does not make it more convenient — it removes the only thing
49
- standing between a stray approval and someone's repo.
46
+ **the model cannot invoke this skill** — so a pass picked up *this way* is only
47
+ ever picked up because a person typed the command. Typing it *is* the human
48
+ signal. A later edit that makes this skill model-invocable does not make it more
49
+ convenient — it removes the only thing standing between a stray approval and
50
+ someone's repo.
51
+
52
+ **There is exactly one other way in, and it is bounded rather than trusting.**
53
+ A phase that ends waits on its page, and a pass arriving *during that wait* is
54
+ claimed by the engine (`--claim-since`, `/spec-diff` §4b): scoped to the window,
55
+ acting on nothing when no pass arrived, refusing when two did. A pass already
56
+ sitting there when the wait began is never swept up by it — which is the
57
+ stranger's pass this whole rule was written about. Everything outside that
58
+ window still comes through here.
50
59
 
51
60
  ## 1. Resolve the spec
52
61
 
@@ -141,10 +150,17 @@ everything a decision needs, so there is nothing to go looking for, and going
141
150
  looking is the bypass step 0 forbids. The file is right there and readable; that
142
151
  is precisely why the rule is written down rather than assumed.
143
152
 
144
- **Nothing waiting is an ordinary answer.** Say so and stop. Do not hunt through
145
- other specs, and do not treat an empty holding area as a problem a `file://`
146
- page copies to the clipboard instead of sending, so mention that the pass may be
147
- on their clipboard waiting to be pasted, and finish.
153
+ **Check the store before concluding nothing is waiting.** Only when this spec
154
+ has a published page (`--json` reports a `url`): a reader off the LAN sent their
155
+ pass to the artifact's own `passes` collection rather than to the engine, so an
156
+ empty holding area is not an empty answer yet. Read it with the Artifact tool's
157
+ `read_db`, merge the `blob` through `--notes`, and **delete the document** —
158
+ `/spec-diff` §6 owns the whole sequence, including that a claim consumes.
159
+
160
+ **Nothing waiting in either is an ordinary answer.** Say so and stop. Do not hunt
161
+ through other specs, and do not treat an empty holding area as a problem — a
162
+ `file://` page copies to the clipboard instead of sending, so mention that the
163
+ pass may be on their clipboard waiting to be pasted, and finish.
148
164
 
149
165
  ## 3. One pass waiting? Act on it
150
166
 
@@ -155,10 +171,11 @@ manufactured, because the model cannot invoke it.
155
171
 
156
172
  **The code was never an authorisation.** A later edit must not restore it as
157
173
  one. It earned its keep when the *page* pushed: the agent went looking, found
158
- a pass, and had to prove which one it had. That channel is gone. What stops a
159
- stranger's approval reaching your repo is that a device on your network can
160
- reach your page and cannot reach this conversation the confirmation step
161
- added nothing to that, and cost a round-trip on every review.
174
+ a pass, and had to prove which one it had. What stops a stranger's approval
175
+ reaching your repo is not those six digits it is that this command cannot be
176
+ typed by anything but a person, and that the one automatic path (`--claim-since`)
177
+ is bounded by a window a stranger's pass falls outside of. The confirmation step
178
+ added nothing to either, and cost a round-trip on every review.
162
179
 
163
180
  So the code has exactly one job left: **telling two passes apart**. That is
164
181
  disambiguation, not a gate.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skitterbyte/skitterspec-linear",
3
- "version": "13.0.0",
3
+ "version": "15.0.0",
4
4
  "description": "Spec-driven development for Claude Code, with one-way Linear sync \u2014 a superset of @skitterbyte/skitterspec: the base filesystem workflow plus /spec-status \u00b7 /spec-push and the spec-sync CLI. The repo is canonical; Linear is a generated mirror. Install this OR the base, not both.",
5
5
  "keywords": [
6
6
  "claude",