@techgoblin/gobstack 0.4.4-beta.7 → 0.5.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/goblin-verify CHANGED
@@ -19,7 +19,7 @@
19
19
 
20
20
  set -uo pipefail
21
21
 
22
- GOBLIN_VERIFY_VERSION="0.4.4"
22
+ GOBLIN_VERIFY_VERSION="0.5.0"
23
23
  SELF_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
24
24
  # shellcheck source=goblin-lib.sh
25
25
  . "$SELF_DIR/goblin-lib.sh"
@@ -261,15 +261,17 @@ if [ -n "$ONLY" ]; then
261
261
  fi
262
262
 
263
263
  if [ ! -f "$CONFIG" ]; then
264
- g_err "not installed: $CONFIG is absent. Run: goblin-install --target $ROOT --class <A|B|C|D|E>"
264
+ g_err "not installed: $CONFIG is absent. Run: goblin-install --target $ROOT --class <software|service|game|research|fleet>"
265
265
  exit 2
266
266
  fi
267
267
 
268
- CLASS=$(g_yaml_scalar "$CONFIG" class)
269
- case "$CLASS" in
270
- A|B|C|D|E|F) ;;
271
- *) g_err "unknown class '${CLASS:-<empty>}' in $CONFIG (expected A|B|C|D|E|F)"; exit 2 ;;
272
- esac
268
+ # The class is read-time resolved: goblin.yaml records the canonical name on new installs, but
269
+ # every repo installed before the 5-class merge records a letter (A..F) or an old name
270
+ # (app/agent/desktop). g_class_canon maps them all to the five canonical names; the tsv carries
271
+ # the canonical names, so nothing else in this file has to know about letters. `desktop`/F -> software.
272
+ CLASS_RAW=$(g_yaml_scalar "$CONFIG" class)
273
+ CLASS=$(g_class_canon "$CLASS_RAW") \
274
+ || { g_err "unknown class '${CLASS_RAW:-<empty>}' in $CONFIG (expected software|service|game|research|fleet, or A-E / app / agent / desktop)"; exit 2; }
273
275
 
274
276
  # Root vs git toplevel. find_installed_root resolves the CONFIG lookup correctly, but the
275
277
  # rows that shell out to git run in $ROOT and git resolves that to the ENCLOSING repo when
@@ -374,8 +376,12 @@ PERF_BASELINE_COMMIT=$(g_unquote "$(g_yaml_block_scalar "$CONFIG" perf baseline_
374
376
  PERF_BASELINE_VALUE=$(g_yaml_block_scalar "$CONFIG" perf baseline_value)
375
377
  PERF_MEASURED=$(g_unquote "$(g_yaml_block_scalar "$CONFIG" perf measured)")
376
378
  PERF_HOST_GATE=$(g_yaml_block_scalar "$CONFIG" perf host_gate)
379
+ # The electron opt-in (software class): true means the electron ban set stays on regardless of a
380
+ # hand-edited bans: list, and a perf host gate must be declared (PF-01). Absent on a repo
381
+ # installed before the merge — read as off, so a legacy class-F repo verifies with no rewrite.
382
+ ELECTRON=$(g_yaml_scalar "$CONFIG" electron)
377
383
  export SEC_GITIGNORE_FAMILY SEC_BUILD_OUTPUT SEC_AUDIT_CMD SEC_AUDIT_MAX_AGE SEC_WAIVER_MAX_AGE
378
- export SEC_WRITE_ROUTES PERF_METRIC PERF_CMD PERF_BASELINE_VALUE PERF_HOST_GATE
384
+ export SEC_WRITE_ROUTES PERF_METRIC PERF_CMD PERF_BASELINE_VALUE PERF_HOST_GATE ELECTRON
379
385
 
380
386
  # Whole days between an ISO date and today, or empty when the date does not parse on this host
381
387
  # (`date -d` is GNU; the row FAILS rather than assuming, because an unreadable date is not a
@@ -536,6 +542,13 @@ check_in_02() {
536
542
  [ -e "$ROOT/$p" ] || continue ;;
537
543
  esac
538
544
  fi
545
+ # W6 neutral-first: an EXPLICIT --skills no re-install removes the recorded skill files
546
+ # by design (the record follows the switch). Their absence on a record whose options now
547
+ # say skills=no is the documented opt-out, not drift - the same clause W4a wrote for the
548
+ # global tier, scoped to the record's own declared choice.
549
+ case "$p" in
550
+ .hermes/skills/*) [ -e "$ROOT/$p" ] || [ "$(g_installed_scalar_options "$INSTALLED" skills)" = "yes" ] || continue ;;
551
+ esac
539
552
  n=$((n + 1))
540
553
  cur=$(g_sha256_file "$ROOT/$p")
541
554
  if [ "$cur" != "$h" ]; then
@@ -606,8 +619,14 @@ check_hp_03() {
606
619
  m = split(names, dn, " ")
607
620
  for (i = 1; i <= m; i++) if (dn[i] != "") decl[dn[i]] = 1
608
621
  }
609
- tolower($0) ~ /^#{2,3}[[:space:]]+[^[:alpha:]]*gates?([^[:alpha:]]|$)/ { g = 1; next }
610
- g && /^#{1,3}[[:space:]]/ { g = 0 }
622
+ # Portability (clean-room catch, 2026-10-05): the Debian default awk (mawk) has no
623
+ # interval expressions ({2,3}) — the header rule matched nothing and HP-03 went red on
624
+ # every fresh Debian install. The hash-header variants are spelled out as two pattern
625
+ # statements, and the date as repeated [0-9] groups. (No apostrophes here: this
626
+ # program is a single-quoted shell string.)
627
+ tolower($0) ~ /^###[[:space:]]+[^[:alpha:]]*gates?([^[:alpha:]]|$)/ { g = 1; next }
628
+ tolower($0) ~ /^##[[:space:]]+[^[:alpha:]]*gates?([^[:alpha:]]|$)/ { g = 1; next }
629
+ g && (/^###[[:space:]]/ || /^##[[:space:]]/ || /^#[[:space:]]/) { g = 0 }
611
630
  !g { next }
612
631
  tolower($0) ~ /example of the required form/ { next }
613
632
  $0 !~ /=/ { next }
@@ -617,7 +636,7 @@ check_hp_03() {
617
636
  if (!hit) for (k in decl) if (hasname($0, k)) { hit = 1; break }
618
637
  if (hit) {
619
638
  n++
620
- if ($0 !~ /measured [0-9]{4}-[0-9]{2}-[0-9]{2}/) {
639
+ if ($0 !~ /measured [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/) {
621
640
  print "a gate line carries no measured date: " $0
622
641
  bad = 1
623
642
  }
@@ -1587,6 +1606,13 @@ check_pf_01() {
1587
1606
  # to ignore it.
1588
1607
  local rname bad=0
1589
1608
  rname=$(g_yaml_block_scalar "$CONFIG" ratchet name)
1609
+ # The electron opt-in's done-definition: an Electron repo declares its host gate (the FPS /
1610
+ # main_thread_busy_pct number a hermetic rule cannot measure). `electron: true` with no host
1611
+ # gate is a repo missing half its definition — FAIL, before the baseline skip below can mask it.
1612
+ if [ "$ELECTRON" = "true" ] && [ -z "$PERF_HOST_GATE" ]; then
1613
+ printf 'electron: true requires a declared perf.host_gate — declare the host-gate probe (the number no hermetic rule can measure) or drop electron: true\n'
1614
+ return 1
1615
+ fi
1590
1616
  [ -n "$PERF_METRIC" ] || {
1591
1617
  printf 'this class declares no perf metric (perf.metric is empty). The ratchet still carries\n%s; declare metric/cmd here the day you pick one\n' "${rname:-nothing}"
1592
1618
  return 3
@@ -2661,6 +2687,21 @@ while IFS=$'\t' read -r id scope rule enforced_by artifact check why; do
2661
2687
  only_selected "$id" || continue
2662
2688
 
2663
2689
  part=$(row_part "$id")
2690
+ # W6 neutral-first: in global engine mode AU-01's subject is the PRODUCER glob, which
2691
+ # global mode reads from the engine dir — the row must run there even when the repo's
2692
+ # skills opt-out (playbooks) is set, or the W1 control below it is skip-masked. This
2693
+ # clause precedes the opt-out branch on purpose. SK-01/SK-02/SK-04 and AU-02..AU-04 keep
2694
+ # their opt-out semantics.
2695
+ if [ "$id" = "AU-01" ] && [ "$ENGINE_MODE" = "global" ]; then
2696
+ out=$(check_au_01 2>&1); rc=$?
2697
+ case "$rc" in
2698
+ 0) PASS=$((PASS + 1)); emit "$id" PASS "$out" ;;
2699
+ 1) FAIL=$((FAIL + 1)); emit "$id" FAIL "$out" ;;
2700
+ 2) ADV=$((ADV + 1)); emit "$id" ADV "$out" ;;
2701
+ 3) SKIP=$((SKIP + 1)); emit "$id" SKIP "$out" ;;
2702
+ esac
2703
+ continue
2704
+ fi
2664
2705
  if [ -n "$part" ] && g_part_disabled "$CONFIG" "$part"; then
2665
2706
  SKIP=$((SKIP + 1)); emit "$id" SKIP "$rule (opt-out: $part)"; continue
2666
2707
  fi
package/docs/ADOPTION.md CHANGED
@@ -1,26 +1,25 @@
1
1
  # Adoption — classes, presets, and the order
2
2
 
3
- ## The six classes
3
+ ## The five classes
4
4
 
5
5
  A class is **not** a stringency level. It selects which parts are required, optional or off, and
6
6
  it supplies the default gate and ratchet shape. The gate vocabulary differs by class; the
7
- harness does not.
7
+ harness does not. The names are words; the letters `A`..`E` are read-time aliases.
8
8
 
9
9
  | Class | What "done" means |
10
10
  |---|---|
11
- | **A. Shipped software** | a gate set reports measured numbers, a round lands, the artifact deploys or publishes |
12
- | **B. Service / configuration** | a contract (schema, route, API) is unchanged, or the change is intentional and migrated |
13
- | **C. Game** | a suite green in the Editor **and** a human feel verdict — the verdict is a first-class deliverable |
14
- | **D. Knowledge / research** | a question is answered with sources and the answer is findable |
15
- | **E. Agent-fleet config** | a config change is applied, verified against the **artifact**, and versioned |
16
- | **F. Desktop shell** | the renderer is isolated from Node, the main process is not busy, and the packaged bundle ships no dev dependency |
11
+ | **software** (A) | a gate set reports measured numbers, a round lands, the artifact deploys or publishes |
12
+ | **service** (B) | a contract (schema, route, API) is unchanged, or the change is intentional and migrated |
13
+ | **game** (C) | a suite green in the Editor **and** a human feel verdict — the verdict is a first-class deliverable |
14
+ | **research** (D) | a question is answered with sources and the answer is findable |
15
+ | **fleet** (E) | a config change is applied, verified against the **artifact**, and versioned |
17
16
 
18
17
  Two placements worth arguing about:
19
18
 
20
19
  - A repo whose code is small and lives elsewhere, while the repo holds *output*, belongs in
21
- **D**, not A — gating it like an application gates the wrong artifact; its gate is freshness,
22
- not compilation.
23
- - An input directory that is not a build target at all belongs in **D** with **`--archive`**.
20
+ **research**, not **software** — gating it like an application gates the wrong artifact; its gate
21
+ is freshness, not compilation.
22
+ - An input directory that is not a build target at all belongs in **research** with **`--archive`**.
24
23
  Without the flag the installer keeps producing HANDOFFs for a directory whose own design
25
24
  folders are empty.
26
25
 
@@ -29,25 +28,28 @@ Two placements worth arguing about:
29
28
  `R` = required · `O` = optional (installed, reported) · `—` = off. The same data is in
30
29
  `manifest/classes.tsv`, and `CL-01` checks it against the repo.
31
30
 
32
- | Part | A | B | C | D | E | F |
33
- |---|---|---|---|---|---|---|
34
- | HANDOFF | R | R | R | R | R | R |
35
- | SPEC before change | R | R | R | — | R | R |
36
- | Verification gate | R | R | R | O | R | R |
37
- | Pinned-commit REPLAY | R | — | R | — | O | R |
38
- | Ratchet | R | O | O | — | O | R |
39
- | PR gate | O | — | O | — | O | O |
40
- | Review panel | O | — | R | — | O | O |
41
- | Playbooks (the skills) | R | R | R | R | R | R |
42
- | Design tokens | O | — | — | — | — | O |
43
- | CI lane | R | — | O | — | O | R |
44
-
45
- **F is the desktop shell**, added at W4: it declares the Electron failure surface as bans
46
- (`BN-06`..`BN-09`) and declares its FPS number as a **host gate** rather than a ratchet, because
47
- the probe that measures it needs Playwright or Electron plus a display — neither of which a
48
- shipped rule may depend on. Its ratchet carries `app_bundle_bytes` instead. `docs/CI.md` §3 argues
49
- that in full, including why frame time is the wrong number (measured flat at 16.70 ms while the
50
- main thread went from 1.8 % to 54.5 % busy).
31
+ | Part | software | service | game | research | fleet |
32
+ |---|---|---|---|---|---|
33
+ | HANDOFF | R | R | R | R | R |
34
+ | SPEC before change | R | R | R | — | R |
35
+ | Verification gate | R | R | R | O | R |
36
+ | Pinned-commit REPLAY | R | — | R | — | O |
37
+ | Ratchet | R | O | O | — | O |
38
+ | PR gate | O | — | O | — | O |
39
+ | Review panel | O | — | R | — | O |
40
+ | Playbooks (the skills) | R | R | R | R | R |
41
+ | Design tokens | O | — | — | — | O |
42
+ | CI lane | R | — | O | — | O |
43
+
44
+ **The electron opt-in, not a class**, added at W4: an Electron app is the **software** class with
45
+ `electron: true`, which declares the Electron failure surface as bans (`BN-06`..`BN-09`) and
46
+ declares its FPS number as a **host gate** rather than a ratchet, because the probe that measures
47
+ it needs Playwright or Electron plus a display — neither of which a shipped rule may depend on. Its
48
+ ratchet carries `app_bundle_bytes` instead. The old sixth class was merged into `software` (its need
49
+ column measured identical on all ten parts), and the old `F` letter remains an install-time alias
50
+ that selects `software` **with** the opt-in. `docs/CI.md` §3 argues it in full, including why frame
51
+ time is the wrong number (measured flat at 16.70 ms while the main thread went from 1.8 % to 54.5 %
52
+ busy).
51
53
 
52
54
  **`—` is a real, enforced option.** The installer records every off part in `disabled:`, so its
53
55
  rows report `SKIP (opt-out)`; `CL-01` fails if a forbidden part's artifact exists. A repo with
@@ -102,13 +104,16 @@ Each step is independently useful and the later ones build on the earlier:
102
104
  Then, in order:
103
105
 
104
106
  git add -A && git commit # the install is a change like any other
105
- .goblin/bin/goblin-verify # 42 passed, 1 failed - HP-05, until HANDOFF names a commit
106
- hermes skills trust <target> # one-time, so the project-tier skills load
107
+ .goblin/bin/goblin-verify # 37 passed, 1 failed - HP-05, until HANDOFF names a commit
108
+ gob emit --platform <p> # optional, per platform: the agent skills are an opt-in
107
109
 
108
- A class-A install is **green** — `43 passed, 0 failed, 11 advisory, 28 skipped`, exit 0 — once
110
+ A default software-class install (no agent skills) is **green** — `38 passed, 0 failed, 11 advisory,
111
+ 33 skipped`, exit 0 — once
109
112
  `HANDOFF.md` names a commit that exists; before that edit the scaffold's `0000000` placeholder is
110
- the one expected red (`42 passed, 1 failed`). Both numbers are measured, not assumed
111
- (`docs/CONTRACTS.md`; step 2 of `docs/GUIDE.md`). Twenty-eight rows skip with a reason: `HS-02` (no
113
+ the one expected red (`37 passed, 1 failed`). Both numbers are measured, not assumed
114
+ (`docs/CONTRACTS.md`; step 2 of `docs/GUIDE.md`). Thirty-three rows skip with a reason: the
115
+ five skill rows (`SK-01`..`SK-04`, `AU-04`) skip on the `playbooks` opt-out a skills-free
116
+ install records, plus the not-yet rows: `HS-02` (no
112
117
  pinned pre-change commit yet), `AU-02`/`AU-03` (no report has been filed, so there is nothing to
113
118
  dedup and no reporter run to audit), `SC-06`/`SC-07`/`SC-08` (no dependency manifest, no lockfile,
114
119
  no audit record), `PF-01` (no measured perf baseline), `BN-01`/`BN-02`/`BN-05` (no `src/` for a ban
@@ -160,8 +165,8 @@ The remedy is a reconciliation. The project's file stays the file of record; not
160
165
  git add -A && git commit
161
166
  .goblin/bin/goblin-verify # HP-02, HP-03, HP-05 go green
162
167
 
163
- Success is the class's full green path (`43 passed, 0 failed, 11 advisory, 28 skipped`, exit 0 for
164
- class A) with `git status --short` empty.
168
+ Success is the class's full green path (`38 passed, 0 failed, 11 advisory, 33 skipped`, exit 0 for
169
+ the software class) with `git status --short` empty.
165
170
 
166
171
  The edit is additive and small — measured on the model repo (§1's exemplar, 2450 lines): three
167
172
  headings plus a `State` block, one dated gate line and a `Not verified` block, 15 lines, no line
package/docs/CI.md CHANGED
@@ -43,8 +43,8 @@ not the model.
43
43
 
44
44
  ## 2. What goblin-stack places, and what it checks
45
45
 
46
- The `ci-gate` part is `R` for classes A and F, `O` for C and E, and `-` for B and D
47
- (`manifest/classes.tsv`). When it is installed, the installer renders
46
+ The `ci-gate` part is `R` for **software**, `O` for **game** and **fleet**, and `-` for **service**
47
+ and **research** (`manifest/classes.tsv`). When it is installed, the installer renders
48
48
  `templates/ci/goblin-gate.yml.tmpl` into `.github/workflows/goblin-gate.yml` — one job, no `if:` at
49
49
  any level, whose only step runs `.goblin/bin/goblin-verify`. It is `owned`, so a second install is a
50
50
  no-op and a hand-edited copy is never overwritten.
@@ -70,11 +70,14 @@ comment and cannot count toward a pass.
70
70
 
71
71
  ---
72
72
 
73
- ## 3. The Electron / desktop-shell class (F)
73
+ ## 3. The Electron opt-in (software class)
74
74
 
75
- Class F exists because a desktop shell needs a part no other class has — a **host gate**, a number
76
- measured on a machine with a display — and forbids a thing the others allow: a **renderer that
77
- reaches Node or the filesystem directly**. That is a new row-set, not a flag on class A.
75
+ An Electron app is the **`software`** class with `electron: true`. The opt-in exists because such an
76
+ app needs a part no other shape has — a **host gate**, a number measured on a machine with a display
77
+ — and forbids a thing the plain software class allows: a **renderer that reaches Node or the
78
+ filesystem directly**. The merge measured the old sixth class's part needs identical to `software` on
79
+ all ten parts, so none of this is a sixth column in `manifest/classes.tsv`: it is the `bans:` list and
80
+ the `perf.host_gate:` key, rendered over the software preset by `presets/electron-overlay.yaml`.
78
81
 
79
82
  ### 3.1 The failure surface, and the check for each
80
83
 
@@ -94,14 +97,14 @@ reaches Node or the filesystem directly**. That is a new row-set, not a flag on
94
97
 
95
98
  ### 3.2 The perf lane: one ratchet, and a host gate beside it
96
99
 
97
- There is **one** mechanism, and class F reuses it unchanged: `ratchet: {name, cmd, ceiling}`,
100
+ There is **one** mechanism, and the opt-in reuses it unchanged: `ratchet: {name, cmd, ceiling}`,
98
101
  enforced by `GT-04`/`GT-05` and pinned to a commit by `PF-01`. A second perf mechanism is not
99
102
  introduced.
100
103
 
101
104
  What the ratchet measures here is `app_bundle_bytes` — the packaged bundle's byte count. It is
102
105
  hermetic, it needs no browser, no display and no dependency, and a fat bundle is a slow cold start on
103
106
  every machine. **The FPS number is declared as a host gate instead** (`perf_host_gate:` in
104
- `presets/F-electron.yaml`), and carried in the HANDOFF with the date it was measured.
107
+ `presets/electron-overlay.yaml`), and carried in the HANDOFF with the date it was measured.
105
108
 
106
109
  This is a **deviation from G6 §B.3**, which put `main_thread_busy_pct` in the ratchet, and the reason
107
110
  is measured: the instrument that produces it — CDP `Performance.getMetrics`, or
package/docs/CONTRACTS.md CHANGED
@@ -9,12 +9,18 @@ same way the fleet's own tool reads it. Everything else is line-oriented shell.
9
9
  goblin-install --target <dir> [options]
10
10
 
11
11
  --target <dir> required; the repo root to install into
12
- --class A|B|C|D|E|F required unless --uninstall or --re-pin
12
+ --class <class> required unless --uninstall or --re-pin. One of the five domain classes:
13
+ software · service · game · research · fleet. The letters A-E and the
14
+ older names app (software), agent (fleet) and desktop/F (software + the
15
+ electron opt-in) are read-time aliases.
13
16
  --models <path> model mapping file (default: $GOBLIN_MODELS -> ~/projects/fleet-model.yaml)
14
17
  --practice <path> the referenced standard (default: $GOBLIN_PRACTICE -> ~/projects/PROJECT-PRACTICE.md)
15
18
  --parts <list> comma list to install; default = every part the class requires
16
19
  --archive mark the project archive: verify requires no HANDOFF and no gates
17
- --skills yes|no install .hermes/skills (default yes; needs the one-time trust step)
20
+ --skills yes|no install agent skills under .hermes/skills (default no — the harness is
21
+ neutral; opt in per platform with: gob emit --platform <p>). On a repo whose
22
+ record already has skills installed, an OMITTED flag keeps them; an explicit
23
+ --skills no removes them.
18
24
  --dry-run print the plan; write nothing
19
25
  --upgrade re-install at the current version; report created/updated/unchanged/skipped
20
26
  --opt-out <part> record the part in disabled: so its required checks are skipped
@@ -37,7 +43,7 @@ files whose hash changed and prints `created C · updated U · unchanged N · sk
37
43
 
38
44
  | kind | recorded as | overwritten? | hash-checked? | removed by `--uninstall`? |
39
45
  |---|---|---|---|---|
40
- | installed artifact (bin, manifest, roles, skills, harness scaffold) | `files` | yes, on upgrade | yes — IN-02, SK-02 | yes |
46
+ | installed artifact (bin, manifest, roles, opt-in skills, harness scaffold) | `files` | yes, on upgrade | yes — IN-02, SK-02 | yes |
41
47
  | created once, then yours (`.goblin/goblin.yaml`, `HANDOFF.md`, `AGENTS.md`, `*-SPEC.md`, `reviews/.gitkeep`) | `owned` | never | no — you are meant to edit them | no, except the config |
42
48
  | pre-existing, left alone | `refused` | never | no — IN-04 only proves it was not taken over | no |
43
49
 
@@ -112,9 +118,9 @@ Output is one line per executed row, in manifest order, plus a summary line at t
112
118
  ADV MD-02 code lane and review lane both resolve to the same family
113
119
  SKIP HS-02 no pinned pre-change commit yet - REPLAY not provable
114
120
 
115
- Those four lines are one row of each marking. The summary line of a green class-A run is:
121
+ Those four lines are one row of each marking. The summary line of a green default software-class run is:
116
122
 
117
- 43 passed, 0 failed, 11 advisory, 28 skipped
123
+ 38 passed, 0 failed, 11 advisory, 33 skipped
118
124
 
119
125
  **Exit codes:** `0` every executed check passed (advisories and skips do not fail the run) ·
120
126
  `1` at least one check FAILED · `2` verify could not run (not installed, a missing dependency,
@@ -135,8 +141,10 @@ settings that make a workflow a **gate** are written down.
135
141
 
136
142
  ### A fresh install verifies green
137
143
 
138
- Measured on a fresh class-A install, committed with no hand edit: **`43 passed, 0 failed,
139
- 11 advisory, 28 skipped`, exit 0.** Twenty-eight rows skip with a reason: `HS-02` — no pre-change commit
144
+ Measured on a fresh DEFAULT software-class install (skills opt-in, W6 neutral-first), committed with no
145
+ hand edit: **`38 passed, 0 failed, 11 advisory, 33 skipped`, exit 0.** Thirty-three rows skip with
146
+ a reason — the same not-yet rows as before, plus the five skill rows (`SK-01`..`SK-04`,
147
+ `AU-04`) that skip on the `playbooks` opt-out a skills-free install records: `HS-02` — no pre-change commit
140
148
  is pinned yet, so the REPLAY is not provable (`docs/LIMITS.md` #11) — `AU-02` and `AU-03`, which
141
149
  have no report to audit in a repo where no reporter has run — `SC-06`, `SC-07` and `SC-08`, which
142
150
  have no dependency manifest, no lockfile and no audit record to read yet — `PF-01`, which has
@@ -173,13 +181,20 @@ verifier is reporting FAILs.
173
181
  - **Per part:** `--opt-out <part>` records the part in `disabled:`. `goblin-verify` then reports
174
182
  the part's rows as `SKIP (opt-out)` in the summary, so the opt-out is **visible rather than
175
183
  absent**. The same mechanism is what makes a class's `-` (off) real.
176
- - **The opt-out numbers are pinned (V3-3).** A fresh class-A install with `--skills no` verifies
177
- `39 passed, 0 failed, 11 advisory, 28 skipped`, exit 0, and `tests/t-install-off-switch.sh`
178
- asserts that line: a silent drift in the opt-out path is caught rather than left as a number
179
- nobody wrote down (the `--skills no` count moved from `37/0/9/11` at v0.2 to here when the ban
180
- rows landed, and no file recorded the shift; **the skipped count moved 15 → 18 on 2026-09-25
181
- (G1)** — the three new feature-map rows skip on the same path for the same reason, measured;
182
- **and 18 → 24 on 2026-09-25 (W3)** — the six judge/loop rows skip there too, measured).
184
+ - **The opt-out numbers are pinned (V3-3).** A software-class install with an explicit `--skills no`
185
+ verifies `38 passed, 0 failed, 11 advisory, 33 skipped`, exit 0, and
186
+ `tests/t-install-off-switch.sh` asserts that line: a silent drift in the opt-out path is caught
187
+ rather than left as a number nobody wrote down (the `--skills no` count moved from `37/0/9/11`
188
+ at v0.2 when the ban rows landed, **15 → 18 on 2026-09-25 (G1)** — the feature-map rows,
189
+ **18 → 24 on 2026-09-25 (W3)** — the judge/loop rows, and **to `38/0/11/33` at W6
190
+ (neutral-first)**, when this opt-out shape BECAME the default and the two lines converged:
191
+ the old default install measured `43/0/11/28`).
192
+ - **Skills, W6 neutral-first.** A default install ships no agent skills. A repo whose record has
193
+ `skills: yes` keeps them through every flag-less re-install and `--upgrade` (the installer
194
+ reads the record's choice and says so out loud); an explicit `--skills no` removes exactly the
195
+ recorded skill files; `--uninstall` removes everything recorded, as always.
196
+ `tests/t-install-off-switch.sh` walks that migration: install `--skills yes`, upgrade flag-less,
197
+ the skills survive byte-identical; uninstall, and they are all gone.
183
198
  - **Whole harness:** `--uninstall` deletes the `files` list plus `.goblin/goblin.yaml`, removes
184
199
  every directory that leaves empty (deepest first, after `installed.json` itself is gone — the
185
200
  order that used to leave `.goblin/` and the sixteen `.hermes/skills/*` directories behind),
@@ -189,7 +204,7 @@ verifier is reporting FAILs.
189
204
 
190
205
  ## The two commands, verbatim
191
206
 
192
- bash bin/goblin-install --target /path/to/repo --class A
207
+ bash bin/goblin-install --target /path/to/repo --class software
193
208
  .goblin/bin/goblin-verify
194
209
 
195
210
  From a checkout, without installing anything:
@@ -164,26 +164,30 @@ OR its `enforced_by` cell does.
164
164
  the installer records every `-` part in `disabled:`, so its rows report `SKIP (opt-out)`
165
165
  instead of silently passing, and `CL-01` fails if a forbidden part's artifact exists.
166
166
 
167
- | part | A | B | C | D | E | F |
168
- |---|---|---|---|---|---|---|
169
- | handoff | R | R | R | R | R | R |
170
- | spec | R | R | R | - | R | R |
171
- | gate | R | R | R | O | R | R |
172
- | replay | R | - | R | - | O | R |
173
- | ratchet | R | O | O | - | O | R |
174
- | pr-gate | O | - | O | - | O | O |
175
- | review-panel | O | - | R | - | O | O |
176
- | playbooks | R | R | R | R | R | R |
177
- | tokens | O | - | - | - | - | O |
178
- | ci-gate | R | - | O | - | O | R |
179
-
180
- Class **F** is the desktop shell: it needs a part no other class has — a **host gate**, a number
181
- measured on a machine with a display — and forbids nothing the others allow except a renderer that
182
- reaches Node directly, which its ban list catches. `ci-gate` is the one part added at W4: when it
183
- is required or optional the installer renders `templates/ci/goblin-gate.yml.tmpl` into
184
- `.github/workflows/goblin-gate.yml`, and when it is `-` the artifact must be **absent** (which is
185
- why `CL-01` keys off that exact path, not the `.github/` directory — a repo is still allowed CI of
186
- its own). `docs/CI.md` is the contract for what that file does and does not make true.
167
+ | part | software | service | game | research | fleet |
168
+ |---|---|---|---|---|---|
169
+ | handoff | R | R | R | R | R |
170
+ | spec | R | R | R | - | R |
171
+ | gate | R | R | R | O | R |
172
+ | replay | R | - | R | - | O |
173
+ | ratchet | R | O | O | - | O |
174
+ | pr-gate | O | - | O | - | O |
175
+ | review-panel | O | - | R | - | O |
176
+ | playbooks | R | R | R | R | R |
177
+ | tokens | O | - | - | - | O |
178
+ | ci-gate | R | - | O | - | O |
179
+
180
+ The five columns carry the domain names; the letters `A`..`E` and the older names `app` (software)
181
+ and `agent` (fleet) are read-time aliases. The old `F` class — the Electron shell — was merged into
182
+ `software`: the merge measured the two need columns identical on all ten parts, so what it added
183
+ lives in config, not in this table. The **electron opt-in** (`electron: true`) turns the electron
184
+ bans `BN-06`..`BN-09` on even when a hand-edited `bans:` list omits them, and requires a declared
185
+ **host gate** — a number measured on a machine with a display; `docs/CI.md` §3 is the contract for
186
+ it. `ci-gate` is the one part added at W4: when it is required or optional the installer renders
187
+ `templates/ci/goblin-gate.yml.tmpl` into `.github/workflows/goblin-gate.yml`, and when it is `-` the
188
+ artifact must be **absent** (which is why `CL-01` keys off that exact path, not the `.github/`
189
+ directory — a repo is still allowed CI of its own). `docs/CI.md` is the contract for what that file
190
+ does and does not make true.
187
191
 
188
192
  ## The ban list (G5)
189
193
 
package/docs/GUIDE.md CHANGED
@@ -3,7 +3,7 @@
3
3
  A step-by-step guide for your first week. **Read this before the README.** The README tells you
4
4
  what the pieces are; this tells you what to *do*, in order, and what you should see when it works.
5
5
 
6
- Version: `0.4.4` · Last measured: 2026-09-25 · Every command and every output below was run on a
6
+ Version: `0.5.0` · Last measured: 2026-10-05 · Every command and every output below was run on a
7
7
  real repository while writing this guide.
8
8
 
9
9
  ---
@@ -97,27 +97,29 @@ see the plan first:
97
97
  git config user.email "you@example.com"
98
98
  git config user.name "you"
99
99
 
100
- gob init --target . --class app --branch main --email "you@example.com" \
100
+ gob init --target . --class software --branch main --email "you@example.com" \
101
101
  --gate "bash tests/run-tests.sh" --yes
102
102
 
103
103
  or the plain installer this wizard drives, if you prefer the one-shot shape:
104
104
 
105
- gob install --target . --class A
105
+ gob install --target . --class software
106
106
 
107
107
  Expected output (this is a real transcript, trimmed):
108
108
 
109
- created 50 · updated 0 · unchanged 0 · skipped 0
109
+ created 25 · updated 0 · unchanged 0 · skipped 0
110
110
 
111
111
  next:
112
112
  1. cd /tmp/gs-try && git add -A && git commit # the install is a change like any other
113
113
  2. .goblin/bin/goblin-verify # or add .goblin/bin to PATH
114
114
  3. edit .goblin/goblin.yaml: replace the default gate with your real commands (P8 step 3)
115
- 4. hermes skills trust /tmp/gs-try # one-time, so the project-tier skills load
115
+ 4. agent skills are opt-in: gob emit --platform <p> # hermes, claude, copilot, cursor, opencode, codex, gemini
116
116
 
117
- **`created 50`** is the installer's count of the files it **tracks** — the 41 in its `files` map,
118
- the 8 it `owns`, and `.gitignore`. It writes **51**: the 51st is `.goblin/installed.json`, the
119
- record it keeps for itself, which it writes but does not count. It has written nothing outside this
120
- directory.
117
+ **`created 25`** is the installer's count of the files it **tracks** — the 16 in its `files`
118
+ map, the 8 it `owns`, and `.gitignore`. It writes **26**: the 26th is `.goblin/installed.json`,
119
+ the record it keeps for itself, which it writes but does not count. It has written nothing
120
+ outside this directory. The default install ships **no agent skills** — the harness is neutral,
121
+ and `gob emit --platform <p>` is the per-platform opt-in (the old `--skills yes` default is
122
+ still there for repos that want the Hermes project tier vendored).
121
123
 
122
124
  ### Why `git init -b main` matters
123
125
 
@@ -141,14 +143,14 @@ branch** (unless you want to).
141
143
  You will see one line per rule. The shape:
142
144
 
143
145
  PASS IN-01 (test -s .goblin/installed.json && grep -q '"version"' .goblin/installed.json)
144
- PASS IN-02 40 installed files hashed | practice pin ok
146
+ PASS IN-02 16 installed files hashed | practice pin ok
145
147
  FAIL HP-05 HANDOFF.md names no commit that exists in this repo
146
148
  SKIP HS-02 no pinned pre-change commit yet - the REPLAY is not provable
147
149
  ADV HP-04 A stale sentence is corrected in place... (advisory)
148
150
 
149
151
  and a summary line at the bottom:
150
152
 
151
- 42 passed, 1 failed, 11 advisory, 28 skipped # the one FAIL is HP-05, below
153
+ 37 passed, 1 failed, 11 advisory, 33 skipped # the one FAIL is HP-05, below
152
154
 
153
155
  ### How to read that output
154
156
 
@@ -185,7 +187,7 @@ against a declared expectation. That is exactly what you want it to do.
185
187
  `HEAD when this file was written: `0000000``, and `HP-05` **rejects that placeholder on purpose**.
186
188
  A file that names a commit which does not exist is worse than one that names none — it looks like a
187
189
  record. Commit first, then write the real short SHA in. Measured: with the placeholder left in,
188
- verify reports `42 passed, 1 failed`; with the real SHA, `43 passed, 0 failed`.
190
+ verify reports `37 passed, 1 failed`; with the real SHA, `38 passed, 0 failed`.
189
191
 
190
192
  ---
191
193
 
@@ -197,7 +199,7 @@ Everything you configure lives in **one file**, created once and then never over
197
199
 
198
200
  Open it. The keys that matter on day one:
199
201
 
200
- class: A # A|B|C|D|E|F - what kind of project this is (step 6)
202
+ class: software # software|service|game|research|fleet (A-E are aliases) - what kind of project this is (step 6)
201
203
  branch: main # DECLARED, never assumed
202
204
  owner_email: you@example.com # the commit identity this repo expects
203
205
  practice: /path/to/your-standard.md # optional: your own house rules, hash-pinned
@@ -243,19 +245,21 @@ supplies the default gate shape. Choose by asking *what does "done" mean here?*
243
245
 
244
246
  | Class | Choose it when | "Done" means |
245
247
  |---|---|---|
246
- | **A · Shipped software** | an app, library, or tool users run | a gate set reports measured numbers and a round lands |
247
- | **B · Service / config** | an API, schema, route, or deployment config | the contract is unchanged, or the change is deliberate and migrated |
248
- | **C · Game** | a game | a suite is green **and** a human feel verdict exists |
249
- | **D · Knowledge / research** | notes, a vault, a research directory | a question is answered with sources and is findable |
250
- | **E · Agent-fleet config** | your agent's own config (`~/.hermes`) | the change is applied, verified against the artifact, versioned |
251
- | **F · Desktop shell** | an Electron / desktop app | renderer isolated from Node, main process not busy, no dev dependency shipped |
248
+ | **software** (A) | an app, library, or tool users run | a gate set reports measured numbers and a round lands |
249
+ | **service** (B) | an API, schema, route, or deployment config | the contract is unchanged, or the change is deliberate and migrated |
250
+ | **game** (C) | a game | a suite is green **and** a human feel verdict exists |
251
+ | **research** (D) | notes, a vault, a research directory | a question is answered with sources and is findable |
252
+ | **fleet** (E) | your agent's own config (`~/.hermes`) | the change is applied, verified against the artifact, versioned |
253
+
254
+ An Electron app is **software** with `electron: true` — the opt-in adds the electron bans and a host
255
+ gate, not a sixth class. The old `F` letter still resolves there as an install alias.
252
256
 
253
257
  **Two placements people get wrong:**
254
258
 
255
- - A repo that holds *output* while the code lives elsewhere → **D**, not A. Gating it like an
256
- application gates the wrong artifact.
257
- - A plain input directory that is not a build target → **D** with `--archive`, which tells verify to
258
- expect no HANDOFF and no gates, and to say so.
259
+ - A repo that holds *output* while the code lives elsewhere → **research**, not **software**. Gating
260
+ it like an application gates the wrong artifact.
261
+ - A plain input directory that is not a build target → **research** with `--archive`, which tells
262
+ verify to expect no HANDOFF and no gates, and to say so.
259
263
 
260
264
  Switch class later by editing `class:` in the config and re-running install. The parts you no longer
261
265
  need are recorded as **disabled** and will report `SKIP (opt-out)` rather than failing.
@@ -305,9 +309,9 @@ honest entry, and the harness treats it as one.
305
309
  > **Prove it was broken first.**
306
310
 
307
311
  Before you trust a check, break the thing it checks and watch it go red — then put it back and watch
308
- it go green. Break it on a row this walkthrough can actually break: `IN-02` hashes the **41 files it
312
+ it go green. Break it on a row this walkthrough can actually break: `IN-02` hashes the **16 files it
309
313
  tracks** — not the 8 it `owns` (including `.goblin/goblin.yaml`, which §5 has you editing) and not
310
- `.goblin/installed.json`; edit one of the 41 — the exercise below uses `.goblin/bans/README.md`.
314
+ `.goblin/installed.json`; edit one of the 16 — the exercise below uses `.goblin/bans/README.md`.
311
315
 
312
316
  # REPLAY-BEGIN (this exact block is run by tests/t-doc-guide.sh - keep the two copies identical)
313
317
  .goblin/bin/goblin-verify --only IN-02 # expect PASS
@@ -390,16 +394,16 @@ the next session.
390
394
 
391
395
  ## 9. What to expect on day one (so you do not misread it)
392
396
 
393
- A class-A install lands on a specific shape. The scaffold ships one deliberate red — `HP-05`, the
397
+ A software-class install lands on a specific shape. The scaffold ships one deliberate red — `HP-05`, the
394
398
  `0000000` placeholder in `HANDOFF.md` (§4) — so a literal first run prints:
395
399
 
396
- 42 passed, 1 failed, 11 advisory, 28 skipped (the one FAIL is HP-05)
400
+ 37 passed, 1 failed, 11 advisory, 33 skipped (the one FAIL is HP-05)
397
401
 
398
402
  Name a real commit in `HANDOFF.md` and commit, and it is green:
399
403
 
400
- 43 passed, 0 failed, 11 advisory, 28 skipped (on a real project; your numbers will differ)
404
+ 38 passed, 0 failed, 11 advisory, 33 skipped (on a real project; your numbers will differ)
401
405
 
402
- **Twenty-eight rows skipping is correct**, and each skip prints its reason. In plain terms: the
406
+ **Thirty-three rows skipping is correct**, and each skip prints its reason. In plain terms: the
403
407
  harness is telling you which of its rules have nothing to read yet. It is a checklist, not a
404
408
  scolding.
405
409
 
@@ -407,7 +411,7 @@ Two readings that are easy to get wrong:
407
411
 
408
412
  - **Advisory rows are not passes.** Ten rules are labelled `advisory` — counted, not enforced, and
409
413
  nine of them carry no executable check at all. The count is capped by `advisory_ceiling: 10`, and
410
- a class-A install already sits at 10 of 10: adding another unenforceable rule fails verify until
414
+ a software-class install already sits at 10 of 10: adding another unenforceable rule fails verify until
411
415
  one is removed. That is intentional. (The summary line can print `11 advisory`: the eleventh ADV
412
416
  line is `JG-02`, a row with a real command of its own that reports ADV here because your model
413
417
  file declares no `judge:` lane — it prints the remedy rather than failing a repo for a fleet's
@@ -483,7 +487,7 @@ engine and silently de-migrate the record).
483
487
 
484
488
  ### Commands
485
489
 
486
- gob install --target <dir> --class A|B|C|D|E|F [options]
490
+ gob install --target <dir> --class <software|service|game|research|fleet> [options]
487
491
  gob install --target <dir> --uninstall
488
492
  gob install --target <dir> --re-pin
489
493
  gob install --target <dir> --upgrade
@@ -579,7 +583,7 @@ with *"prove it was broken first"* — it is the one practice that survives cont
579
583
  # 1. try it somewhere disposable
580
584
  mkdir -p /tmp/gs-try && cd /tmp/gs-try
581
585
  git init -b main
582
- gob install --target . --class A # expect: created 50
586
+ gob install --target . --class software # expect: created 25 (no skills — those are gob emit)
583
587
 
584
588
  # 2. commit and check
585
589
  git add -A && git commit -m "chore: install gobstack"
@@ -600,7 +604,7 @@ with *"prove it was broken first"* — it is the one practice that survives cont
600
604
 
601
605
  # 5. do it for real, in a repo you care about
602
606
  cd ~/projects/your-project
603
- gob install --target . --class A
607
+ gob install --target . --class software
604
608
  git add -A && git commit -m "chore: adopt gobstack"
605
609
  .goblin/bin/goblin-verify
606
610
  $EDITOR HANDOFF.md # state / gates (dated!) / next / NOT verified