@skitterbyte/skitterspec-jira 0.0.0-stage → 1.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 (109) hide show
  1. package/MIGRATION.md +1089 -0
  2. package/README.md +42 -2
  3. package/assets/claude-md-section.md +85 -0
  4. package/assets/commands/allow-main.md +33 -0
  5. package/assets/commands/spec-connect.md +13 -0
  6. package/assets/commands/spec-live.md +14 -0
  7. package/assets/commands/spec-remote-review.md +37 -0
  8. package/assets/commands/spec-skip.md +24 -0
  9. package/assets/core/SETUP.md +192 -0
  10. package/assets/core/env.config.json.example +56 -0
  11. package/assets/core/env.config.md +554 -0
  12. package/assets/core/gating.config.json.example +4 -0
  13. package/assets/core/gating.config.md +81 -0
  14. package/assets/core/jira.config.json.example +18 -0
  15. package/assets/core/jira.config.md +61 -0
  16. package/assets/hooks/main-guard.cjs +162 -0
  17. package/assets/hooks/review-gate.cjs +146 -0
  18. package/assets/review/page.html +3384 -0
  19. package/assets/rules/commit-trailers.md +96 -0
  20. package/assets/rules/negative-checks.md +69 -0
  21. package/assets/rules/offered-unblocks.md +100 -0
  22. package/assets/rules/spec-planning.md +692 -0
  23. package/assets/rules/spec-reports.md +557 -0
  24. package/assets/skills/no-spec/SKILL.md +246 -0
  25. package/assets/skills/spec/SKILL.md +714 -0
  26. package/assets/skills/spec-bug/SKILL.md +583 -0
  27. package/assets/skills/spec-cancel/SKILL.md +261 -0
  28. package/assets/skills/spec-complete/SKILL.md +448 -0
  29. package/assets/skills/spec-diff/SKILL.md +870 -0
  30. package/assets/skills/spec-hotfix/SKILL.md +605 -0
  31. package/assets/skills/spec-init/SKILL.md +178 -0
  32. package/assets/skills/spec-jira-setup/SKILL.md +220 -0
  33. package/assets/skills/spec-list/SKILL.md +173 -0
  34. package/assets/skills/spec-next/SKILL.md +676 -0
  35. package/assets/skills/spec-push/SKILL.md +306 -0
  36. package/assets/skills/spec-review/SKILL.md +256 -0
  37. package/assets/skills/spec-reviewed/SKILL.md +284 -0
  38. package/assets/skills/spec-start/SKILL.md +496 -0
  39. package/assets/skills/spec-status/SKILL.md +112 -0
  40. package/assets/skills/spec-sync/SKILL.md +218 -0
  41. package/assets/skills/spec-to-main/SKILL.md +139 -0
  42. package/bin/skitterspec-jira.js +61 -0
  43. package/package.json +40 -4
  44. package/src/cli.js +5901 -0
  45. package/src/deprecate.js +138 -0
  46. package/src/env/bootstrap.js +188 -0
  47. package/src/env/building.js +143 -0
  48. package/src/env/classify.js +176 -0
  49. package/src/env/commitcmd.js +108 -0
  50. package/src/env/config.js +753 -0
  51. package/src/env/dev.js +63 -0
  52. package/src/env/hooks.js +226 -0
  53. package/src/env/hotfix.js +385 -0
  54. package/src/env/hotfixland.js +302 -0
  55. package/src/env/integrate.js +126 -0
  56. package/src/env/live.js +504 -0
  57. package/src/env/mainguard.js +299 -0
  58. package/src/env/provision.js +466 -0
  59. package/src/env/proxy.js +192 -0
  60. package/src/env/prune.js +119 -0
  61. package/src/env/registry.js +159 -0
  62. package/src/env/render.js +17 -0
  63. package/src/env/resolve.js +716 -0
  64. package/src/env/review.js +2577 -0
  65. package/src/env/reviewers/coderabbit.js +228 -0
  66. package/src/env/reviewers.js +517 -0
  67. package/src/env/serve.js +1044 -0
  68. package/src/env/supervise.js +157 -0
  69. package/src/env/teardown.js +248 -0
  70. package/src/env/trust.js +87 -0
  71. package/src/gating.js +155 -0
  72. package/src/init.js +1150 -0
  73. package/src/lines-diff.js +114 -0
  74. package/src/prompts.js +129 -0
  75. package/src/vendor/jira/adf.js +414 -0
  76. package/src/vendor/jira/api.js +295 -0
  77. package/src/vendor/jira/cli-sync.js +1256 -0
  78. package/src/vendor/jira/commands.js +44 -0
  79. package/src/vendor/jira/config.js +296 -0
  80. package/src/vendor/jira/credentials.js +108 -0
  81. package/src/vendor/jira/dialect.js +45 -0
  82. package/src/vendor/jira/mcp.js +129 -0
  83. package/src/vendor/provider-kit/index.js +41 -0
  84. package/src/vendor/provider-kit/src/adapter.js +23 -0
  85. package/src/vendor/provider-kit/src/apply.js +197 -0
  86. package/src/vendor/provider-kit/src/args.js +68 -0
  87. package/src/vendor/provider-kit/src/config.js +198 -0
  88. package/src/vendor/provider-kit/src/credentials.js +404 -0
  89. package/src/vendor/provider-kit/src/doctor.js +35 -0
  90. package/src/vendor/provider-kit/src/http.js +44 -0
  91. package/src/vendor/provider-kit/src/identity.js +93 -0
  92. package/src/vendor/provider-kit/src/md-compare.js +39 -0
  93. package/src/vendor/provider-kit/src/md-tables.js +94 -0
  94. package/src/vendor/provider-kit/src/prompt.js +50 -0
  95. package/src/vendor/provider-kit/src/released.js +226 -0
  96. package/src/vendor/provider-kit/src/specs.js +166 -0
  97. package/src/vendor/provider-kit/src/transport.js +22 -0
  98. package/src/vendor/sync-core/index.js +92 -0
  99. package/src/vendor/sync-core/src/base.js +81 -0
  100. package/src/vendor/sync-core/src/compare.js +306 -0
  101. package/src/vendor/sync-core/src/dialect.js +51 -0
  102. package/src/vendor/sync-core/src/fields.js +36 -0
  103. package/src/vendor/sync-core/src/legacy.js +100 -0
  104. package/src/vendor/sync-core/src/normalize.js +989 -0
  105. package/src/vendor/sync-core/src/push.js +85 -0
  106. package/src/vendor/sync-core/src/retarget.js +278 -0
  107. package/src/vendor/sync-core/src/task-block.js +308 -0
  108. package/src/vendor/sync-core/src/verify.js +67 -0
  109. package/src/vendor/sync-core/src/write.js +191 -0
@@ -0,0 +1,692 @@
1
+ # Spec Planning
2
+
3
+ Spec-driven development is driven by ten lifecycle skills (plus the
4
+ `/spec-connect`, `/spec-live` and `/spec-remote-review` **commands** when isolation is on) — use them rather than hand-rolling specs so the structure
5
+ and lifecycle stay consistent. Each sets a status on the spec header
6
+ (`> **Status:** …`):
7
+
8
+ | Skill | Purpose | Status | Folder |
9
+ |-------|---------|--------|--------|
10
+ | `/spec` | (Feature) Grill to a clear shared understanding, write a groomed spec in its own worktree, then land it on the verdict | `Ready` (or `Draft`) | `specs/backlog/` (on the base branch, by a fast-forward) |
11
+ | `/spec-bug` | (Bug) Reproduce with a failing test, capture spec, drive red→green | `In Progress` | `specs/in-progress/` |
12
+ | `/spec-hotfix` | (Hotfix) Fork a worktree from a release tag, red→green, land by tag + cherry-pick | `In Progress` | `specs/in-progress/` |
13
+ | `/no-spec` | Work that genuinely has no spec — its own branch, worktree, page and land. Writes no spec and moves nothing through the lifecycle | `—` | (none) |
14
+ | `/spec-review` | Re-validate a spec against the codebase; refresh stale parts | `—` | (unchanged) |
15
+ | `/spec-start` | Put a spec in flight — provision its branch, then build phase 1 | `In Progress` | `specs/in-progress/` |
16
+ | `/spec-next` | Build the next phase of the spec this session is in (re-run per phase) | `In Progress` (unchanged) | (unchanged) |
17
+ | `/spec-reviewed` | Pick up a review approved on the page — offered by its code, claimed on your word | (unchanged) | (unchanged) |
18
+ | `/spec-to-main` | Land the branch on the base (rebase + ff) **without** finishing — for running the work in CI / a shared env mid-spec; repeatable | `In Progress` (unchanged) | (unchanged) |
19
+ | `/spec-complete` | Verify all phases done + tests green; land + tear down | `Complete` | `specs/complete/` |
20
+ | `/spec-cancel` | Record progress, stamp a reason on the header; tear down | `Cancelled` | `specs/cancelled/` |
21
+ | `/spec-init` | Bootstrap/repair this workflow in a project (idempotent) | — | — |
22
+
23
+ **Skills vs commands.** The table above lists **skills** — Claude reads them and
24
+ exercises judgment. `/spec-connect`, `/spec-live` and `/spec-remote-review` are
25
+ instead **slash commands**
26
+ (`.claude/commands/`): each pre-executes one `skitterspec spec-env` verb and
27
+ relays its output, so there is no judgment to apply and no model turn spent
28
+ finding one. They are marked `disable-model-invocation`, meaning
29
+ **only you can run them** — a skill that wants one will tell you to type it
30
+ rather than invoking it.
31
+
32
+ **`/spec-remote-review`** is the newest and the smallest: it toggles whether
33
+ **this spec** may publish, and takes `on`/`off` when you would rather say which.
34
+ The permission is per worktree, kept in the gitignored review store, so the base
35
+ branch is never dirtied and no other spec gains it; `review.allowRemote` in the
36
+ committed config is the project default beneath it. Turned on beside a page
37
+ that already exists, it is the ask to publish that page and re-show the stack.
38
+ It exists because a render's `remote:` line has to name something a person can
39
+ act on, and `skitterspec spec-env review allow remote` is not what anyone
40
+ reconstructs from a page they are reading on a phone. Making the line
41
+ *clickable* was considered and rejected — the only clickable thing markdown has
42
+ is a URL, and a URL that acts when it is **fetched** is one a link previewer or
43
+ a prefetcher fires with nobody involved. It **permits** publishing; it publishes
44
+ nothing.
45
+
46
+ `/spec-reviewed` is a **skill** and **user-only**, and there the marking is not
47
+ convenience — it is the enforcement of `/spec-diff` step 0's rule that a waiting
48
+ review pass is never claimed unasked. Because the model cannot invoke this
49
+ skill, a pass named this way is only ever picked up because a person typed the
50
+ command, and typing it **is** the human signal. Prose alone did not hold that
51
+ line once already.
52
+
53
+ It is **no longer the only way in**, and the difference is worth stating
54
+ precisely. A phase that ends now *waits* on its page, and a pass arriving inside
55
+ that wait is claimed by the engine (`--claim-since`) without anyone typing
56
+ anything — scoped to the window, refusing when two arrive. `/spec-reviewed` is
57
+ what answers everything outside it: a pass sent when nobody was waiting, two
58
+ passes to choose between, and every harness with no file-watch to wait with.
59
+
60
+ **`/no-spec` is the one row that writes no spec**, and it is in the table
61
+ because it is the same lane: a branch, a worktree, a review page, a verdict, a
62
+ land. What it leaves out is the document, because mechanical work — a version
63
+ bump, a lockfile refresh, a rename — genuinely has none, and a one-phase spec
64
+ written for it is a document nobody reads. What it does **not** leave out is the
65
+ branch. Doing that work in the primary checkout leaves the base branch dirty for
66
+ every in-flight spec to replay over, and renders no page, so it is the one kind
67
+ of change that reaches the base branch unreviewed. It is model-invocable, which
68
+ matters: it is where Claude is sent when it would otherwise write on the base
69
+ branch. Beneath it, `skitterspec spec-env nospec <name>` records the branch —
70
+ and that record is what makes a name with nothing under `specs/**` resolvable by
71
+ `review`, `integrate`, `down` and a bare `resolve`.
72
+
73
+ **`/allow-main` is the fourth command.** It is user-only for a different reason
74
+ from the other three: `/spec-connect`, `/spec-live` and `/spec-remote-review`
75
+ are marked that way because there is no judgment to apply. This one is marked that
76
+ way because **Claude must not be able to lift a guard aimed at Claude.**
77
+
78
+ The guard is `.claude/hooks/main-guard.cjs`, a `PreToolUse` hook on the write
79
+ tools, and it refuses an `Edit` or a `Write` in the **primary checkout** while
80
+ it is on the **base branch**. `main` is where work lands, not where it happens:
81
+ work done there leaves the base branch dirty for every in-flight spec to replay
82
+ over, and renders no review page at all, so it is the one kind of change that
83
+ arrives unreviewed. It refuses the FIRST write rather than the commit, because
84
+ nothing has been written yet and the fix is one command — where a commit-time
85
+ gate fires once the work already exists in a checkout that cannot `switch -c`
86
+ out from under the other worktrees.
87
+
88
+ It fires only on a **positive signal** — isolation configured, the guard not
89
+ switched off, this directory IS the primary checkout, HEAD IS the base branch —
90
+ and every cannot-tell allows the write in silence: no repo, no config, a
91
+ worktree, an unresolvable base, a missing or crashed engine
92
+ (`.claude/rules/negative-checks.md`). `skitterspec spec-env main
93
+ <check|allow|status>` is the engine, and `guards.mainIsLandingZone` in
94
+ `env.config.json` turns it off for a project. It is
95
+ **on by default wherever isolation is configured**, matching the review gate.
96
+
97
+ There is **no allowlist**, and it is empty by construction rather than by
98
+ omission: `/spec` authors into its own worktree and `/no-spec` catches ad-hoc
99
+ work, so no legitimate writer to the base branch is left. `/spec-init` looks
100
+ like the exception and is not one — it bootstraps a repo with no
101
+ `env.config.json` yet, so the guard never fires there.
102
+
103
+ **Two exits, and they are not equal.** `/no-spec <name>` and `/spec-start
104
+ <name>` move the work, and Claude takes those on its own; `/allow-main` lifts
105
+ the guard, and only a person can type it. That asymmetry is the same line
106
+ `/spec-reviewed` draws, and `spec-planning.md` already records that prose alone
107
+ did not hold it once.
108
+
109
+ **`/spec-skip` is the fifth command**, and it is user-only for `/allow-main`'s
110
+ reason rather than the mild one: it lifts the review gate, which is a guard
111
+ aimed at Claude. It pre-executes `skitterspec spec-env review skip "<reason>"`
112
+ and relays it — the same exit that always existed, at an address a person will
113
+ actually type. The long form stays exactly as valid and is what the engine
114
+ prints; what changed is that a refusal now names something reachable, because a
115
+ gate whose exit nobody can find gets switched off wholesale instead of answered.
116
+
117
+ **It is not a `--force`, and one was asked for.** A reasonless lift reopens
118
+ precisely what the reason requirement closed: `none: additive, nothing to
119
+ revert` is a decision a reviewer can argue with, and silence is an oversight.
120
+ So a bare `/spec-skip` refuses and asks for a reason, and the two things that
121
+ discharge a phase's obligation are still a committing verdict and a recorded
122
+ skip — never a flag. Putting the flag on `/commit` was the other suggestion and
123
+ is worse: `/commit` belongs to skittership, which is why the gate is enforced
124
+ by a hook rather than by editing that skill.
125
+
126
+ **A refusal may OFFER its own exit, and that is a different thing again.**
127
+ Where the engine declares one, a skill relays the refusal and **then** offers it
128
+ — once per obligation, with the choice recorded, and answered by the operator
129
+ rather than by Claude. `.claude/rules/offered-unblocks.md` is the whole
130
+ contract. The three properties named there are what let a Claude-raised bypass
131
+ survive the rule above, where a reasonless `--force` did not: the operator
132
+ answers, the choice is on the record, and it is offered once. Remove any one and
133
+ the objection returns.
134
+
135
+ `/spec-to-main`, `/spec-status` and `/spec-sync` stay **skills** — each carries
136
+ real judgment (green tests before a land; an MCP fetch and a team-key check; ten
137
+ subcommands) — but they are marked user-only too, since nobody reaches them
138
+ except by typing them. Everything else in the table above stays model-invocable,
139
+ which is what lets `/spec-next` hand off to `/spec-push` as work progresses.
140
+
141
+ Status flow: `Ready → In Progress → Complete` (or `Cancelled` from any state).
142
+ `/spec` grills to a **Ready** spec directly — there is no separate grooming
143
+ command; it writes `Draft` only when open questions are deliberately left.
144
+ `/spec-bug` and `/spec-hotfix` are test-first and start straight in `In Progress`
145
+ (work begins immediately), so they skip Draft/Ready. `/spec-to-main` does **not**
146
+ move the spec through the flow at all — it lands the branch on the base branch
147
+ mid-spec (so the work can run in CI / a shared test env) while the spec stays
148
+ `In Progress` in `specs/in-progress/`; it's the intermediate, repeatable half of
149
+ `/spec-complete`'s landing, without the finalise-and-tear-down.
150
+
151
+ **Two workspace modes.** `specs/.core/env.config.json` → `mode` decides where a
152
+ spec's branch is built. **`worktree`** (the default) gives each spec its own
153
+ checkout — several specs at once and `main` left free. `/spec-start` builds the
154
+ branch there, **moves your session into it** with a plain `cd`, and then asks:
155
+ build phase 1 now? Say yes and it carries on into a bare `/spec-next`; say no and
156
+ you are already standing in the worktree, so `/spec-next` typed later does the
157
+ same thing. The primary checkout stays on the base branch throughout — the
158
+ session followed the spec, nothing was checked out anywhere.
159
+ **`checkout`** builds the branch in the primary checkout instead: one spec at a
160
+ time, nowhere else to stand, and the build always carries straight on.
161
+ The difference is how many specs can be in flight at once.
162
+ Pick it for how you work rather than for what the project contains — a repo with
163
+ no dev servers may still want several specs in flight. In `checkout` mode
164
+ `/spec-connect` and `/spec-live` do not apply and say so: both exist to reach
165
+ work that lives elsewhere, which is the gap that mode removes.
166
+
167
+ **Per-spec isolation (opt-in to adopt, then the default policy).** When a project
168
+ adopts isolation (`skitterspec init --isolation`, or `specs/.core/env.config.json`
169
+ present), `/spec-start` gives **every** in-progress spec its own git worktree
170
+ automatically — several specs run side by side without stashing or clashing, and
171
+ `main` stays free. Docker is a **per-spec escalation**: `/spec` records
172
+ `> **Stack:** worktree` (default) or `worktree + docker` when the spec touches the
173
+ DB / stateful services, and `/spec-start` brings up a namespaced stack only for the
174
+ latter. `/spec-start` also starts the project's host **dev servers** (`env.config`
175
+ → `dev`) on the spec's ports; **`/spec-connect <name>`** then exposes that spec on
176
+ your canonical `localhost` ports so you can test it at the normal URL
177
+ (`/spec-connect main` hands them back). All housekeeping (the backlog→in-progress
178
+ move, header edits, the code) happens on the spec's branch in the worktree; `main`
179
+ changes only when it merges. Teardown is folded into `/spec-complete` ·
180
+ `/spec-cancel`. Beneath the skills, `skitterspec spec-env
181
+ <up|bootstrap|nospec|main|down|prune|dev|connect|integrate|hotfix|live|review|stage|status|resolve>` is the CLI
182
+ engine. `up` plans the fork;
183
+ **`bootstrap` then runs the worktree's own steps itself** — seeding the gitignored files, the env file, `setup`, the stack — so
184
+ the command a skill runs names no file, and a secrets hook that refuses any
185
+ `.env` path has nothing to refuse. **Omit the spec name anywhere and it uses the worktree you are standing in**,
186
+ else the sole provisioned spec — with no exceptions left: a bare
187
+ `/spec-connect` connects, and a bare `/spec-live` takes, where both once meant
188
+ `main`. Teardown drops
189
+ the finished spec's own test-DB volume; `spec-env prune` additionally reaps
190
+ **orphaned** volumes left by declined/aborted teardowns, so `/spec-complete` and
191
+ `/spec-cancel` also sweep orphans (confirm-first). A **hotfix** is the one
192
+ exception to "fork from `main`": `/spec-hotfix` forks the worktree from a release
193
+ tag and `/spec-complete` lands it via `spec-env hotfix land` (release, push on
194
+ consent, carry to `main` without the version commit), not a fast-forward; its
195
+ worktree stays until the deploy is out and a second `/spec-complete` runs
196
+ `spec-env hotfix finish`. Isolation is **orthogonal to lifecycle status** and inactive
197
+ when `env.config.json` is absent — every skill then behaves as it does today.
198
+
199
+ **Live overlay (`/spec-live`, a command) — the light way to test a spec.** `/spec-connect`
200
+ runs a spec's *own* dev stack and proxies the canonical ports to it (one stack per
201
+ spec). **Live overlay** instead reuses the one dev server you already have running:
202
+ `/spec-live <spec>` — or a bare `/spec-live`, which takes the spec you are
203
+ standing on — rebases the branch onto base, frees it from its worktree, and
204
+ checks it out **in the primary checkout**, so your running server hot-reloads the
205
+ feature at the normal URL — no second stack, no proxy. Bare only acts when there
206
+ is exactly one answer *and* the workbench is free; anything else prints the
207
+ status report rather than guessing. The branch checked out in
208
+ the primary checkout **is** the lock: exactly one spec is live at a time, and
209
+ `/spec-live main` hands the instance back (fixes you make while live commit
210
+ straight onto the branch; `/spec-complete` is live-aware and lands them). Rule of
211
+ thumb: **live overlay is the light default for *testing* a code-only spec** — it
212
+ is not how work gets started, and no lifecycle skill invokes it; it *refuses*
213
+ stateful ones (`Stack: worktree + docker`, or a branch touching migrations) — keep
214
+ `/spec-connect` + a Docker stack for those, and for genuinely parallel testing.
215
+ Beneath it, `skitterspec spec-env live <take|release|abort|status>` is the engine.
216
+
217
+ **Authoring a spec is work, so it happens off the base branch too (`/spec`).**
218
+ `main` is where work **lands**, not where it happens, and a spec document is no
219
+ exception. With isolation configured, `/spec` grills first — which puts nothing
220
+ on disk — then provisions the new spec's own worktree with
221
+ `spec-env up <name> --docs`, moves the session into it, and writes the spec
222
+ there. `--docs` skips the `setup` commands and Docker, so a tree to write
223
+ markdown in costs one `git worktree add`; `/spec-start` re-runs `up` without the
224
+ flag and the install happens then, at the first moment the tree holds code.
225
+
226
+ **The committing verdict is what puts it on the base branch.** Phase C2's page
227
+ offers `Commit & Start` and `Commit`, and **both** commit on the branch and then
228
+ `spec-env integrate` — rebase, fast-forward — so the base gains the spec as one
229
+ commit. That is not optional tidiness: `ls specs/backlog/` is how specs are
230
+ found, and a folder bucket is only the truth on the branch you are standing on,
231
+ so a spec left unlanded is invisible to everyone. They differ only in the
232
+ worktree: `Commit & Start` keeps it and carries into `/spec-start`, plain
233
+ `commit` tears it down, because a spec parked in the backlog should hold no
234
+ checkout.
235
+
236
+ **What this bought is the reason it changed.** The page waits for a verdict
237
+ without a timeout — deliberately, because a reader walking away from a diff is
238
+ the normal case — and while the spec was authored in the primary checkout that
239
+ wait held the base branch dirty for as long as it lasted, so a release could not
240
+ be cut through it. Now an unanswered spec sits on its own branch in its own
241
+ worktree, in nothing's way. Phase C2 still **arms nothing**: the gate asserts
242
+ that a phase which ended owes an answer, and a backlog spec owes no phase — and
243
+ walking away from it now costs nobody anything rather than leaving a mess
244
+ someone else has to work around.
245
+
246
+ With isolation **absent** none of this applies: there is no worktree to make and
247
+ no land to do, so `/spec` writes the folder where the session is standing,
248
+ exactly as it always did.
249
+
250
+ **Staging one spec and not another (`spec-env stage`).** A `git add specs/`
251
+ stages a *directory*, sweeping whatever else happens to be sitting in it into
252
+ this spec's commit, under this spec's ticket trailer.
253
+ `skitterspec spec-env stage [<spec>] [--json]` is how a skill asks instead of
254
+ guessing: it splits the uncommitted tree into the paths that are this spec's —
255
+ its folder in **any** bucket, so a tree mid-`git mv` is handled, plus each
256
+ `spec.companionPaths` entry the project declares — and the paths that are not.
257
+ It reads **the tree you are standing in**, which is the worktree inside one and
258
+ the primary checkout outside, and prints which. The `owned` half is the spec's
259
+ **documents**, never its code: a phase's own implementation is `foreign`, because
260
+ the commits this bounds are the lifecycle ones. Nothing here refuses, writes, or
261
+ exits non-zero — `foreign` is a list of paths to leave alone, not an accusation.
262
+
263
+ **And it stays load-bearing now that `/spec` authors in its own worktree**, even
264
+ though that tree holds one spec. It was once justified by several specs sharing
265
+ one `specs/` folder, and that justification is gone; what replaces it is smaller
266
+ and just as real. A project's `spec.companionPaths`, a hand-edited
267
+ `specs/.core/`, and a tracker snapshot all land in the same tree as the spec, and
268
+ only the spec's own documents belong under its authoring verdict. Where isolation
269
+ is absent the original reason applies unchanged.
270
+
271
+ **Naming the paths is necessary and not sufficient**, which is why every spec
272
+ commit is also pathspec-limited: `git add -- <paths>` **and**
273
+ `git commit -m "…" -- <paths>`. A checkout has one `.git/index` and every session
274
+ standing in it shares that index, so a bare `git commit` takes whatever another
275
+ session has already staged no matter how carefully this one staged its own. The
276
+ `add` is still needed — `git commit` cannot take a path git has never seen, which
277
+ is every brand-new spec folder — but it is the `--` on the **commit** that bounds
278
+ what lands. The two failures are different and the fix needs both halves.
279
+
280
+ **Reading a spec's diff (`/spec-diff`) — seeing the work, not running it.**
281
+ `/spec-connect` and `/spec-live` both exist to reach a *running app*.
282
+ **`/spec-diff`** answers the other question: what did this actually change? A
283
+ phase is built in its own worktree, so `git diff` in your terminal answers about
284
+ the base branch — and a 350-line diff read as terminal text is scrolling, not
285
+ review. The engine collects the worktree's changes with `git -C` and writes a
286
+ self-contained HTML page (`.spec-env/reviews/<spec>.html`, gitignored) with
287
+ whole-file context that folds away, a file tree, and new files included. You open
288
+ it locally, or publish it and read it on a phone.
289
+ **The diff never passes through the model**, so the page is free to produce
290
+ however large it is; the *written*
291
+ review — a short read plus `flag`/`confirm`/`good` notes — is the part that costs
292
+ tokens, and it is offered rather than assumed. It is a **skill**, model-invocable,
293
+ and it is gated on nothing: half a phase, a hand edit and a colleague's branch are
294
+ all ordinary inputs. `/spec-next` renders the page at the end of a phase and
295
+ offers the review; `skitterspec spec-env review <spec> [--branch]` is the engine.
296
+
297
+ **The page is not read-only — it takes a review pass back.** Tick `✓ accept` per
298
+ file as you read, write notes against a line or a whole file, answer the
299
+ questions a written review asked, then **end it in a decision** and hand it
300
+ back. Three buttons, each carrying its own verdict:
301
+ **`✓ Approve`** (commit it), **`↺ Request changes`** (work them now),
302
+ **`… Discuss first`** (report and talk). `/spec-diff` stores it (`--notes`),
303
+ plays back what it read, and routes on the verdict — `changes` **is** the
304
+ go-ahead, so it works the commented files immediately, while `discuss`, a
305
+ refused approval and a pass carrying no verdict at all report and wait, which
306
+ is what a bare paste has always done. Accepted files stay unopened either way.
307
+
308
+ **Two ways back, and both are ordinary.** A **served** page POSTs the pass to
309
+ its own URL; the engine holds it in a gitignored `.pending.json` beside the
310
+ notes sidecar and answers with a **six-digit code**. Read the code out and
311
+ `/spec-diff` claims it (`--claim`), merging exactly the pass that code names.
312
+ A `file://` page has no server to talk to — the File System Access API is
313
+ Chrome-only and absent on iOS Safari — so it copies to the clipboard and you
314
+ paste, exactly as before. The clipboard path is **not legacy**: it is the whole
315
+ story for a local reader.
316
+
317
+ The code is **not a secret** — it is printed on the page, and it cannot be a
318
+ gate against Claude either, because the store is a file Claude can read.
319
+
320
+ It was once true that a device reaching your page
321
+ **could not reach your conversation**, and that fact was the whole guard: a pass sat in the holding
322
+ area until you typed `/spec-reviewed`. It is no longer true, deliberately —
323
+ a phase that ends **waits** for its verdict, so the button you press on the page
324
+ is what carries the work on. What replaced the guard is two mechanisms and one
325
+ rule. The **serve token** — 48 bits of randomness in the URL path, minted per
326
+ server — decides who can POST at all. The **wait window** decides which pass may
327
+ be claimed without you naming it: the engine's `--claim-since` takes the one
328
+ pass that arrived *while this session was waiting*, acts on nothing when none
329
+ did, and refuses to choose when two did. And outside that window the rule stands
330
+ unchanged and absolute: **Claude never claims a pass it was not asked to**
331
+ (`/spec-diff` step 0, because nothing enforces it).
332
+
333
+ What you give up is real and worth naming: the page can now act. What you get is
334
+ the loop closing without anyone remembering a command — which is the failure the
335
+ old design traded it for.
336
+
337
+ What the code *does* is let you **tell two passes apart** — it is an address,
338
+ never a password. One waiting pass is claimed and acted on the moment you type
339
+ `/spec-reviewed`, with nothing read back: the round-trip verified *which* pass
340
+ in a design where the page pushed and the agent went looking, and that design is
341
+ gone. Two waiting is a refusal to guess — the one case where a stranger's pass
342
+ sits beside yours, and the one case where six digits are worth anyone's time.
343
+ Pasting the code (`/spec-reviewed 324199`) skips even that. A pass you disown is `--drop <code>`; left there it is reported on every
344
+ render until you stop reading the line.
345
+
346
+ A code that matches nothing refuses, **names nothing**, and never falls back to
347
+ "the only one" — that fallback is precisely what would let an unread pass
348
+ through. Claiming **consumes**: a code works once. A second pass from the same
349
+ page render supersedes the first, so the code on screen is always the pass on
350
+ screen; passes from different renders stand alongside each other.
351
+
352
+ **And a claimed pass never enters the model's context.** The engine holds it,
353
+ merges it, and reports the counts — six digits is what reaches Claude, where a
354
+ pasted blob costs context in proportion to how much you wrote. That is the same
355
+ rule the diff already follows, applied to the one place it used to break. What it did comes back as a **resolution** (`--resolve`),
356
+ so the next render shows each note struck through with a one-line account and you
357
+ verify the fix instead of trusting it. An accept is keyed to the file's
358
+ **content hash**, so it survives the commit that ends the phase and lapses by
359
+ itself when that file changes again — announced as `accepted earlier — changed since`, never
360
+ silently. All of it lives beside the page in gitignored `.spec-env/`, and
361
+ **the marks are information, never a gate**: nothing counts them and nothing
362
+ refuses on them.
363
+
364
+ **A second opinion, from something that did not write the code (`review.reviewers`).**
365
+ The page can also carry findings from code reviewers
366
+ that run from a CLI — CodeRabbit's has a free tier, PR-Agent and Kodus are
367
+ self-hosted — and they arrive as **checks**, the same shape a written review
368
+ uses. `/spec-next` runs them once per phase before the render, so the page is
369
+ populated when it is opened; `/spec-diff --reviewers` is the opt-in mid-phase
370
+ run, and a bare `/spec-diff` never spends one. Results are cached against a hash
371
+ of the diff, so re-reading unchanged work costs nothing.
372
+
373
+ **They inform; they never gate.** A check is not a comment, so a page carrying
374
+ twenty findings still offers `Commit` — and a reader who replies to one creates
375
+ a comment, which does gate, because then a person has asked for something. There
376
+ is deliberately no severity threshold that blocks: that is the counting gate
377
+ this whole design exists against, wearing a vendor's name.
378
+
379
+ **And a reviewer that could not run says so**, which is the one place
380
+ `.claude/rules/negative-checks.md` inverts. Silence is normally the safe branch;
381
+ here a rate-limited or unauthenticated reviewer would render identically to one
382
+ that read the diff and approved of it, on the page a commit decision is made
383
+ from. So each gets a line — `12 findings` · `clean` · `did not run — <why>` —
384
+ and none of it ever refuses a render, a verdict or a commit.
385
+
386
+ The list is **empty by default** and `init` never writes one: configuring a
387
+ hosted reviewer sends the worktree's diff to a third party, which is a real
388
+ change for a tool that otherwise touches nothing outside `.spec-env/`. Every
389
+ finding on the page is badged with the reviewer that produced it. `env.config.md`
390
+ carries the field reference.
391
+
392
+ **The verdict is chosen, never derived.** A verdict is one person's conclusion,
393
+ sent once per pass; a counting gate is a refusal computed from how many boxes
394
+ are ticked, and it stays forbidden. The one refusal here is not a count of
395
+ ticks: **committing** is unavailable while any **comment** is unresolved,
396
+ because you asked for something and it therefore cannot also be fine. Files you
397
+ never ticked block nothing — an unticked file is something you said nothing
398
+ about, and requiring every one of them would be the tally this design exists to
399
+ avoid.
400
+
401
+ **The gate — a phase that ended owes an answer.** That is the second refusal,
402
+ and it is a different kind: not a count, and not about the marks at all.
403
+ `/spec-next` **arms** it when a phase ends and renders its page, and exactly two
404
+ things clear it — a **committing verdict**, or
405
+ `skitterspec spec-env review skip "<reason>"`. Until one of them happens,
406
+ `/spec-next` refuses to build the next phase and (where the hook is installed)
407
+ `git commit` refuses in that worktree. `skitterspec spec-env review gate
408
+ [--check] [--json]` is what both ask.
409
+
410
+ **The hook is the half that cannot be talked past.** `skitterspec init` and
411
+ `skitterspec update` install `.claude/hooks/review-gate.cjs` and register it in
412
+ the project's committed `.claude/settings.json`, so a `git commit` is refused by
413
+ the harness rather than by prose — which is what covers a bare `git commit`, a chained command, and
414
+ skittership's own `/commit` without skitterspec ever editing it. It decides
415
+ nothing itself: it hands the command line to
416
+ `spec-env review gate --check --for-command` and turns one exit status into an
417
+ answer. It refuses only where the commit is running
418
+ **inside that spec's own worktree** — a commit on the base branch, or in
419
+ another spec's tree, is not this obligation's business — and it fails **open** on everything else: no engine, an
420
+ unreadable payload, a crash, a timeout, a repo with no isolation.
421
+
422
+ Three things keep it a push rather than a wall. It is armed
423
+ **only by a phase ending**, so reading your own half-finished work mid-phase
424
+ owes nothing.
425
+ It has an **exit that is always one command**, and one of them is
426
+ *"I am moving on"* — with a reason, because `none: additive, nothing to revert` is a decision
427
+ a reviewer can argue with while silence is an oversight. And it accuses
428
+ **only on a positive signal**: an unreadable sidecar, a project that set
429
+ `review.required: false`, a spec the engine could not resolve — every
430
+ cannot-tell exits 0 and says nothing (`.claude/rules/negative-checks.md`).
431
+
432
+ It is on by default wherever isolation is configured. That is the point: the
433
+ push toward reading the diff is the normal path, and stepping off it is the
434
+ thing you have to do deliberately.
435
+
436
+ **The verdict names the action**, and that is why it is `commit` rather than
437
+ `approve`: a review is the guard in front of an action, and an approval that
438
+ only recorded itself was the one control on the page that did not describe what
439
+ it does. Both committing verdicts hand off to the project's own commit skill
440
+ (`review.commitWith` in `env.config.json`, `/commit` by default) — skitterspec
441
+ never vendors one, because `/commit` belongs to skittership.
442
+ **There is no off switch**: `"none"` existed and was removed, since it produced
443
+ exactly the record-and-do-nothing verdict this design is against.
444
+ `Commit & Continue` then runs `/spec-next` and **stops there** — it never
445
+ completes, lands or tears anything down.
446
+
447
+ A verdict is **consumed**, not stored: a commit verdict is spent by the commit
448
+ and a changes one by the work, so neither can go stale and later act on
449
+ something nobody read. What survives is a one-line outcome log, shown on the
450
+ next render as history.
451
+
452
+ **One ending, every skill (`.claude/rules/spec-reports.md`).** Every skill in
453
+ the table above finishes with the same block — a verdict, then a table of only
454
+ the fields that skill declares — and says nothing while it runs beyond a
455
+ question it cannot answer itself or a failure at the moment it happens. The
456
+ shape is defined once in that rule; each skill's `## Report` section names only
457
+ its verdicts and its fields.
458
+
459
+ ✅ **Phase 2 built** — `feat-orders`, 2 of 4
460
+
461
+ | | |
462
+ |---|---|
463
+ | **Tracker** | [ABC-88](https://example.invalid/ABC-88) · `feat-orders` · phase 2 moved |
464
+ | **Branch** | `spec/feat-orders` · 3 commits, clean |
465
+ | **Built** | POST /orders handler, orders schema |
466
+ | **Tests** | 128 passed · dotnet test |
467
+ | **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 |
468
+ | **Follow-ups** | none |
469
+ | **Next** | `/spec-next` → phase 3 (Auth) |
470
+
471
+ Four verdicts, and the last two are different facts about the repo: `✅` done ·
472
+ `⚠️` done with caveats · `❌` failed part-way, so there is a mess to clear ·
473
+ `⏸` refused before acting, so nothing changed.
474
+ **A refusal emits the block too** — "nothing happened" is a reported outcome
475
+ rather than an absent one.
476
+ `Follow-ups` is always present, because a recorded `none` is a decision where a
477
+ missing line is an oversight. And the block covers **that run only**: what else
478
+ is in flight is another question, and volunteering it here leaves the reader
479
+ unable to tell what followed from the run they just watched.
480
+
481
+ **Ticketing-provider sync (opt-in, a separate package).** The base is
482
+ tracker-free: it knows nothing about any specific ticketing system. A
483
+ ticketing provider is installed as its own distribution that plugs into named
484
+ **seams** in the shared skills and fulfils a skill-name + CLI contract.
485
+
486
+ Part of that contract is the **binary name**: the shipped `/spec-connect` and
487
+ `/spec-live` commands invoke `skitterspec`, so a superset that replaces the base
488
+ must expose that name too, alongside whatever it calls itself — one entry point
489
+ under two names. A distribution shipping only its own name leaves those commands
490
+ failing with `command not found`, which reads as a broken install rather than a
491
+ missing alias.
492
+ Sync is **one-way**: the repo is the source of truth and the tracker is a
493
+ **generated mirror**. It ships `/spec-push` (repo→tracker; computes a create/update plan
494
+ against a committed last-pushed snapshot and applies it) and `/spec-status`
495
+ (read-only drift report — what would push, and whether the tracker's
496
+ workflow-state drifted), backed by a `spec-sync` CLI. There is no content pull —
497
+ the tracker is never read back or merged.
498
+
499
+ **One-way means the repo wins, and it now wins *loudly*.** Those are different
500
+ claims and only the first was ever in doubt. A ticket's description is generated
501
+ from the spec, so a person editing it there is writing something the next push
502
+ replaces — which is correct, and which used to happen in silence. The engine now
503
+ compares a hash of what the tracker holds against a hash of what the repo last
504
+ sent, and says *that* it changed and where to read it.
505
+ It **reports and never refuses**: only a person can tell a typo fix from a
506
+ paragraph worth keeping.
507
+
508
+ **This is still not a pull.** Nothing read is merged, written to a spec, or fed
509
+ into the projection, and the text never enters the conversation — the comparison
510
+ is hashes, and the output is one line for a human. It is the same thing
511
+ `compareStored` has always done for a different question.
512
+
513
+ **And adoption no longer costs the reporter their words.** When a provider
514
+ adopts an existing ticket — one a PM or colleague filed — the linking push
515
+ replaces its description with the spec. Before that happens the original is
516
+ posted back onto the ticket as a **comment**: a surface one-way sync never
517
+ touches, so no later push can clobber it. Where a provider supports it, this is
518
+ automatic and has an opt-out. See the provider's own docs.
519
+
520
+ A provider may also ship a **read-only listing** of what the tracker holds. That
521
+ is not a pull: nothing is merged back and no spec file is written. It exists
522
+ because the folder buckets are only the truth *on the branch you are standing
523
+ on* — `/spec-start` moves a spec to
524
+ `specs/in-progress/` on that spec's own branch, so on the base branch an
525
+ in-flight spec still reads `backlog` and a teammate's unlanded spec is not on
526
+ disk at all. The tracker knows both, and each row carries the spec's folder name
527
+ so `/spec-start <name>` is a copy-paste away. See the provider's own docs for
528
+ what it calls the command.
529
+
530
+ A provider may also mirror **who is building a spec**, as one more field the repo
531
+ owns. Where it does, `/spec-start` records the developer and the tracker's ticket
532
+ is assigned to them, the assignment is released when the spec completes, and
533
+ `/spec-claim` moves ownership mid-flight — take it, hand it back, or hand it to a
534
+ teammate. Like everything else here it is opt-in and one-way: the spec file is
535
+ the record, and the ticket is the mirror. See the provider's own docs for how to
536
+ turn it on.
537
+
538
+ **Every skill that moves a spec through the lifecycle carries a seam**, so the
539
+ mirror keeps up without anyone remembering to push: `/spec`, `/spec-bug` and
540
+ `/spec-hotfix` link the spec they create; `/spec-next` refreshes it as work starts;
541
+ `/spec-complete`, `/spec-cancel` and `/spec-review` refresh it after they change
542
+ it. `/spec-to-main` and `/spec-live` carry none — they change no status.
543
+ With no provider installed the seams are empty and every skill behaves as a plain
544
+ filesystem workflow. See the provider package's own docs for its config and field
545
+ reference.
546
+
547
+ **Release gating (opt-in, config-gated).** With
548
+ `specs/.core/gating.config.json` present, every spec `/spec`, `/spec-bug` and
549
+ `/spec-hotfix` write carries a `> **Gating:**` header recording one decision:
550
+ does this ship behind a feature flag, or land live? The value is a **flag name**,
551
+ or **`none: <one-line reason>`** — and the reason half is the load-bearing part,
552
+ because `none: additive, nothing to revert` is a decision a reviewer can argue
553
+ with while a bare `none` is a shrug and a missing line is an oversight.
554
+
555
+ Skitterspec bakes in **the offer, never the mechanism**: it asks, cites the
556
+ project's own doc (`guidance` in that config), and records the answer. It never
557
+ reads your flag code. `skitterspec gating check` reports specs with no decision
558
+ and **always exits 0** — it reads only `backlog/` and `in-progress/`, so specs
559
+ finished before you adopted gating are out of range by construction. With the
560
+ config absent nothing appears at all: no question, no header, no check.
561
+
562
+ ## Project conventions (fill this in)
563
+
564
+ The spec skills tell you to run "your project's typecheck and test commands" and
565
+ to "honour project conventions". Make those concrete here so specs stay
566
+ consistent with the codebase:
567
+
568
+ Skitterspec never runs these itself and knows nothing about what this project
569
+ is written in — it shells out to `git`, and to whatever you write below. The
570
+ examples span several stacks on purpose: pick the shape that fits, or write one
571
+ none of them describes.
572
+
573
+ - **Typecheck command:** `<e.g. npm run typecheck · dotnet build · mypy . · go vet ./...>`
574
+ - **Test command:** `<e.g. npm test · dotnet test · pytest · go test ./...>`
575
+ (single file/dir: `<e.g. npx vitest run path · dotnet test --filter · pytest path>`)
576
+ - **Lint/format:** `<e.g. npm run lint · dotnet format · ruff check · golangci-lint run>`
577
+ - **Other rules specs must honour:** link the relevant `.claude/rules/*.md`
578
+ (architecture, code style, testing, database, etc.) rather than restating them.
579
+
580
+ ## Spec types — Feature, Bug, Hotfix
581
+
582
+ Every spec is one of three types, recorded **both** in the header and the filename:
583
+
584
+ - **Header field:** `> **Type:** Feature`, `> **Type:** Bug`, or
585
+ `> **Type:** Hotfix` (authoritative, greppable:
586
+ `grep -rl 'Type:.*Hotfix' specs/`).
587
+ - **Filename prefix:** `feat-<name>` for features, `bug-<name>` for bugs,
588
+ `hotfix-<name>` for hotfixes (visible in listings; glob-safe — never use
589
+ `[BUG]`/`[FEATURE]` brackets).
590
+
591
+ A **Hotfix** is a Bug fixed against a **released tag** rather than `main`: it
592
+ carries an extra `> **Base version:** <tag>` header, forks its worktree from that
593
+ tag, and lands by tagging a new patch + cherry-picking onto `main` (never a
594
+ fast-forward merge). `/spec-live` refuses a hotfix — test it with `/spec-connect`.
595
+
596
+ All three types share the same lifecycle folders below — type is orthogonal to
597
+ status.
598
+
599
+ ## Header fields & State log (audit trail)
600
+
601
+ Every spec header carries:
602
+
603
+ - `> **Name:**` — the spec's folder name (`feat-`/`bug-`/`hotfix-<kebab-name>`).
604
+ It's the handle you pass to `/spec-start` and the other lifecycle skills, surfaced
605
+ in the header so it's copy-pasteable without digging for the folder name.
606
+ - `> **Author:**` — who created the spec (set at `/spec` / `/spec-bug`, defaults
607
+ to `git config user.name`).
608
+ - `> **Developer:**` — who implements it (`—` until `/spec-start` starts work, then
609
+ set to `git config user.name`; `/spec-bug` sets it immediately).
610
+ - `> **Gating:**` — the release-gating decision,
611
+ **only when `specs/.core/gating.config.json` exists**: a flag name, or
612
+ `none: <one-line reason>`. Absent entirely in a project that has not adopted
613
+ gating.
614
+
615
+ Every spec also has a **State log** table — the audit trail of folder/status
616
+ transitions. Each lifecycle skill appends exactly one row when it changes state:
617
+
618
+ ```
619
+ ## State log
620
+
621
+ | Date | Status | Folder | By |
622
+ |------|--------|--------|----|
623
+ | 2026-01-01 | Draft | backlog | Jane Dev |
624
+ | 2026-01-02 | In Progress | in-progress | Jane Dev |
625
+ ```
626
+
627
+ Keep the **State log** (state transitions) separate from the **Changelog**
628
+ (decisions and course-corrections) — state moves go in the table, not the
629
+ changelog.
630
+
631
+ When asked for a plan, implementation strategy, or feature breakdown:
632
+
633
+ 1. Create or update a spec under `specs/` — never plan only in chat.
634
+ 2. Reach a clear shared understanding of the requirement AND the proposed
635
+ solution before writing (the `/spec` skill grills for this).
636
+ 3. Use markdown checkboxes `- [ ]` for tasks, `- [x]` when done.
637
+ 4. Organise work into phased sections with short goal descriptions.
638
+ 5. Tasks must be granular enough to complete in one coding session.
639
+ 6. Every phase ends with creating and running tests — a phase is not done until
640
+ its tests are green (run the project's typecheck + test commands above).
641
+ 7. Keep specs **as concise as possible**.
642
+ 8. Record decisions and course-corrections in the spec's **Changelog** section.
643
+
644
+ ## Lifecycle folders
645
+
646
+ ```
647
+ specs/backlog/ Ready (or Draft) specs (/spec)
648
+ specs/in-progress/ under active implementation (/spec-start, /spec-bug)
649
+ specs/complete/ finished (/spec-complete)
650
+ specs/cancelled/ abandoned, with a reason on the header (/spec-cancel)
651
+ specs/.core/ project rules — ALWAYS APPLY, never moved
652
+ ```
653
+
654
+ Every spec is a **folder** `specs/<bucket>/<name>/` — never a bare file, even for
655
+ simple changes. Inside it:
656
+
657
+ - `00-overview.md` is the entry point / dashboard: header, Problem, Decisions,
658
+ Solution overview, the **Impact map** (a `Surface | Change | Detail` table
659
+ naming the concrete surfaces the spec touches — endpoints, schemas, DB tables,
660
+ domain objects, routes, business rules — as the scannable blast radius), the
661
+ **phase index** (a table linking to each phase file with its status), Open
662
+ questions, State log, Changelog. **No per-phase task lists live here.**
663
+ - **One file per phase** — `01-<phase-slug>.md`, `02-<phase-slug>.md`, … in
664
+ execution order. Each holds that phase's goal, its task checkboxes (tests
665
+ included), and any phase-specific notes. Even a single-phase spec gets `01-….md`
666
+ — so each phase is easy to open and work on its own.
667
+
668
+ Keep the index and the phase files in sync (`⬜`/`🔄`/`✅`). Legacy specs may be a
669
+ bare `<name>.md`, or a `00-overview.md` with inline phases — the skills read
670
+ those, but new specs always use the folder + phase-file form.
671
+
672
+ ## Finding specs
673
+
674
+ The **folder buckets are the source of truth** — a spec's bucket is its status.
675
+ To see the backlog, list `specs/backlog/`; for the latest completed specs, use
676
+ `git log`/mtime on `specs/complete/` or each spec's dated **State log**. Live
677
+ status also lives in the tracker when a ticketing provider is linked. (There are
678
+ no `00-index.md` summary files — the folder tree, headers, and State logs are
679
+ queried directly.)
680
+
681
+ ## Rules
682
+
683
+ - If a spec already exists, update it — don't rewrite from scratch.
684
+ - Preserve completed `[x]` tasks.
685
+ - Add new tasks to the appropriate phase.
686
+ - Never delete historical notes.
687
+ - The spec file is the single source of truth for implementation progress.
688
+ - Move specs between buckets with `git mv` to preserve history.
689
+ - Never let inline emphasis or a link cross a hard line break — keep a whole
690
+ `**bold**`, `*italic*`, or `[text](url)` on one line (let it overflow the wrap
691
+ column rather than splitting it). Many round-tripping editors mangle a
692
+ `**`/`*`/link span that straddles a newline, so clean source avoids the churn.