@skitterbyte/skitterspec 16.5.0 → 16.6.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.
@@ -0,0 +1,69 @@
1
+ # Checks That Accuse
2
+
3
+ A check **accuses** when being wrong costs something: it deletes, it exits
4
+ non-zero, or it tells the user their code is broken. Those checks earn the four
5
+ rules below. An ordinary conditional does not — this is about the ones that act
6
+ on what they conclude.
7
+
8
+ Almost every accusation begins as an **absence**: a name not in a list, a
9
+ directory not on disk, a version not in a response. An absence is evidence only
10
+ once you have established that the lookup could have seen the thing. Three
11
+ times in one day, in unrelated code, we established nothing and acted anyway:
12
+
13
+ | Absence observed | Concluded | What had blinded the lookup |
14
+ |------------------|-----------|-----------------------------|
15
+ | ref not in the issue list | "it does not exist" | the query excluded archived issues, and capped at 250 |
16
+ | version not in the registry's list | "the publish failed" | the registry is eventually consistent |
17
+ | lifecycle folder not on disk | "half-installed" | git does not store an empty directory |
18
+
19
+ The bills: 146 healthy refs accused, a valid release tag deleted, a non-zero
20
+ exit on a healthy repo. Each blind spot was knowable in advance.
21
+
22
+ ## 1. Prefer a positive signal to an absence
23
+
24
+ Assert something that must be **present**, not something that must not be
25
+ missing. A positive signal fails loudly when you are wrong about it; an absence
26
+ fails silently whenever the lookup was narrower than you assumed.
27
+
28
+ The scaffold check above stopped asking "is the lifecycle folder there?" — a
29
+ folder git drops as soon as it empties — and started asking whether the config
30
+ folder the installer always writes into is there. Same intent, and the new
31
+ question has an answer.
32
+
33
+ Where no positive signal exists, widen the lookup until absence means
34
+ something (include the archived rows, ask the API for the one id rather than
35
+ scanning a page) — or do not conclude.
36
+
37
+ ## 2. Name the blind spot beside the check
38
+
39
+ A comment naming what could make this lookup lie is what makes the next reader
40
+ check it. Not what the code does — what would fool it:
41
+
42
+ ```js
43
+ // A LIFECYCLE FOLDER IS NOT CHECKED, deliberately. git does not track empty
44
+ // directories, so it disappears whenever the bucket empties and returns the
45
+ // moment something lands in it.
46
+ ```
47
+
48
+ Write it when you write the check, while you still know why it is safe.
49
+
50
+ ## 3. Pair every accusation with a stays-silent test
51
+
52
+ For each accusing check, a test that feeds it a **healthy but unusual** input
53
+ and asserts it says nothing: the empty bucket, the archived record, the
54
+ just-published version, the file the user edited on purpose. The positive test
55
+ proves the check can fire; only this one proves it does not fire at everyone
56
+ else. All three incidents would have been caught by it, and prose alone had
57
+ already failed to prevent them.
58
+
59
+ ## 4. Bias the unknown case toward inaction
60
+
61
+ Three states, not two: yes, no, and *cannot tell*. Route the third to the
62
+ harmless branch — skip, warn, keep, retry — never to the destructive one.
63
+
64
+ The install manifest classifies a file whose hash it does not recognise as
65
+ `customized` rather than stale, so a resync **keeps** it (`managedState`,
66
+ `packages/common/src/init.js`). An unrecognised hash could mean a user's edit or
67
+ a lost manifest; only one of those readings is safe to act on, so it takes that
68
+ one. Being wrong there costs a redundant file on disk. The opposite default
69
+ costs the user their work.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skitterbyte/skitterspec",
3
- "version": "16.5.0",
3
+ "version": "16.6.0",
4
4
  "description": "Spec-driven development for Claude Code — a tracker-free filesystem workflow: lifecycle skills and per-spec isolation. For Linear sync, install @skitterbyte/skitterspec-linear instead.",
5
5
  "keywords": [
6
6
  "claude",
package/src/init.js CHANGED
@@ -406,6 +406,11 @@ function installClaudeMd(dir, { mode }) {
406
406
 
407
407
  // True when the repo looks already set up: any managed file present, any spec
408
408
  // lifecycle folder, or the CLAUDE.md spec marker (Decision 1 — detect eagerly).
409
+ // Is skitterspec already installed here? Matched on what we ACTUALLY install —
410
+ // our managed files, our lifecycle folders, our CLAUDE.md marker — never on
411
+ // `.claude/` merely existing: someone else's skills are not evidence of ours,
412
+ // and reading them as ours would treat every Claude Code project as a
413
+ // half-finished install.
409
414
  function isExistingSetup(dir) {
410
415
  if (managedTargets(dir).some((t) => fs.existsSync(t.abs))) return true
411
416
  if (SPEC_FOLDERS.some((f) => fs.existsSync(path.join(dir, 'specs', f)))) return true
@@ -562,11 +567,21 @@ function printReport(dir, mode, { diff = false } = {}) {
562
567
  // ships none. Discovering it from what was actually installed keeps this file
563
568
  // tracker-free — it never has to know which tracker (if any) is in the box.
564
569
  const setupSkill = SKILLS.find((s) => /^spec-.+-setup$/.test(s))
565
- const trackerNote = setupSkill
566
- ? `Tracker sync is opt-in: run /${setupSkill} to configure it` +
567
- ' (it discovers your workspace and writes the config), or see' +
568
- ' specs/.core/SETUP.md.\n'
569
- : ''
570
+ const provider = setupSkill ? /^spec-(.+)-setup$/.exec(setupSkill)[1] : null
571
+ // …and the same derivation gives the provider's config filename, so this can
572
+ // report tracker sync the way it reports isolation above from what is
573
+ // actually on disk. It used to say "opt-in: run /…-setup" even on a repo that
574
+ // had already configured it, telling you to set up what was already set up.
575
+ const trackerOn =
576
+ provider && fs.existsSync(path.join(dir, 'specs', '.core', `${provider}.config.json`))
577
+ const trackerNote = !setupSkill
578
+ ? ''
579
+ : trackerOn
580
+ ? `Tracker sync is ON: ${provider} — the repo stays the source of truth;` +
581
+ ' /spec-push mirrors a spec up and /spec-status reports drift.\n'
582
+ : `Tracker sync is opt-in: run /${setupSkill} to configure it` +
583
+ ' (it discovers your workspace and writes the config), or see' +
584
+ ' specs/.core/SETUP.md.\n'
570
585
  process.stdout.write(
571
586
  '\nDone. Skills resolve as /spec, /spec-go, /spec-complete, /spec-cancel,' +
572
587
  ' /spec-bug, /spec-review, /spec-init, /spec-connect.\n' +