@techgoblin/gobstack 0.4.4-beta.7 → 0.4.4-beta.8

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/README.md CHANGED
@@ -50,14 +50,17 @@ After installing, in this order:
50
50
 
51
51
  cd <target> && git add -A && git commit # the install is a change like any other
52
52
  gob verify # or .goblin/bin/goblin-verify, inside the target
53
- hermes skills trust <target> # one-time, Hermes users, so project-tier skills load
53
+ gob emit --platform <p> # optional, per platform: the agent skills are an opt-in
54
54
  gob audit # once, deliberately: the ONLY network step (SC-07)
55
55
 
56
- **A class-A install verifies green — `43 passed, 0 failed, 11 advisory, 28 skipped`, exit 0 — once
56
+ **A default class-A install (no agent skills — those are `gob emit`'s job) verifies green —
57
+ `38 passed, 0 failed, 11 advisory, 33 skipped`, exit 0 — once
57
58
  `HANDOFF.md` names a commit that exists. Before that edit the scaffold's `0000000` placeholder is
58
- the one expected red: `42 passed, 1 failed`, `HP-05`. Both numbers measured 2026-09-25; the run and
59
- the fix are step 2 of `docs/GUIDE.md`.**
60
- Twenty-eight rows skip (`HS-02` has no pinned pre-change commit yet, so the REPLAY is not provable;
59
+ the one expected red: `37 passed, 1 failed`, `HP-05`. Both numbers measured at W6 (neutral-first);
60
+ the run and the fix are step 2 of `docs/GUIDE.md`.**
61
+ Thirty-three rows skip — the five skill rows (`SK-01`..`SK-04`, `AU-04`) skip on the `playbooks`
62
+ opt-out a skills-free install records, then the not-yet rows (`HS-02` has no pinned pre-change
63
+ commit yet, so the REPLAY is not provable;
61
64
  `AU-02` and `AU-03` have no report to audit; `SC-06`, `SC-07` and `SC-08` have no dependency
62
65
  manifest, no lockfile and no audit record to read; `PF-01` has no measured perf baseline;
63
66
  `BN-01`/`BN-02`/`BN-05` have no `src/` for a ban to read, and `BN-03` plus the four electron bans
@@ -83,7 +86,7 @@ The measurement and the vacuous-pass reading are in `docs/CONTRACTS.md`.
83
86
  | `gob verify` | run the rule matrix against the current repo — `PASS`/`FAIL`/`SKIP` per row, exit 0 pass · 1 a check failed · 2 could not run · 3 the manifest is broken |
84
87
  | `gob bans` | run the ban list (per-pattern red lines over the source tree) |
85
88
  | `gob audit` | check recorded dependency claims against live advisory feeds — the only command that touches the network |
86
- | `gob install` | install the manifest, skills and verifier into a target repo |
89
+ | `gob install` | install the harness into a target repo: manifest, verifier, gates, HANDOFF — no agent skills (those are an opt-in: `gob emit --platform <p>`, or `--skills yes`) |
87
90
  | `gob uninstall` | remove everything an install wrote, byte-exactly (`gob install --target <dir> --uninstall` is the same job) |
88
91
  | `gob upgrade` | migrate a repo to the shared global engine at `~/.goblin/engine` — 8 steps, two commits, one report |
89
92
  | `gob doctor` | one run across the platforms below: DETECTED / NOT-DETECTED / DRIFT per platform |
@@ -111,6 +114,13 @@ One run of `gob emit --platform <p> --scope project` writes the skills and the c
111
114
  session of that platform reads; `--scope global` writes to the machine-level anchor. `--dry-run`
112
115
  prints the full write plan first.
113
116
 
117
+ **Agent skills are opt-in.** A `gob install` writes the neutral harness only — `.goblin/`,
118
+ `HANDOFF.md`, `AGENTS.md`, the checks and the `.gitignore` block; no skills directory, and no
119
+ files belonging to any coding agent. The guided path is `gob init`'s emit screen; the one-shot
120
+ path is `gob emit --platform <p>` after installing. Repos whose install predates the opt-in
121
+ default keep their skills through `gob upgrade` (the install record names them; only an explicit
122
+ `--skills no`, or `--uninstall`, removes them).
123
+
114
124
  ## What it is not
115
125
 
116
126
  Not a rules document (every rule carries a runnable check or is explicitly counted as
package/bin/goblin-init CHANGED
@@ -375,29 +375,37 @@ if [ -n "$EMIT" ]; then
375
375
  [ -n "$ok" ] || { g_err "--emit: unknown platform '$w' — I ship $PLA, hermes, copilot, cursor, opencode, codex, $GEM"; exit 2; }
376
376
  done
377
377
  else
378
+ # neutral-first: a flags-only run with no --emit emits NOTHING. The detected rows are
379
+ # shown on the emit screen as pre-ticked SUGGESTIONS for the interactive path; in
380
+ # non-interactive mode silence means the neutral harness only.
378
381
  EMIT_SELECTED=""
379
- while IFS=$'\t' read -r p _proj _glob _hit; do
380
- [ -n "$p" ] || continue
381
- EMIT_SELECTED="$EMIT_SELECTED $p"
382
- done <<< "$DET_ROWS"
382
+ if [ "$YES" -eq 0 ] && [ "$TTY_IN" -eq 1 ]; then
383
+ while IFS=$'\t' read -r p _proj _glob _hit; do
384
+ [ -n "$p" ] || continue
385
+ EMIT_SELECTED="$EMIT_SELECTED $p"
386
+ done <<< "$DET_ROWS"
387
+ fi
383
388
  fi
384
389
  EMIT_N=$(printf '%s' "$EMIT_SELECTED" | awk 'NF' | wc -l | tr -d '[:space:]')
385
390
 
386
391
  if [ "$TTY_IN" -eq 1 ] && [ -z "$EMIT" ] && [ "$YES" -eq 0 ]; then
387
392
  rail_say ""
388
- rail_say " ${C_MUTED}emit now — the skills go into:$C_RESET"
393
+ rail_say " ${C_MUTED}agent skills are opt-in, per platform — emit them into:$C_RESET"
389
394
  for p in $PLATFORMS; do
390
395
  hit=""
391
396
  while IFS=$' ' read -r dp _a _b _c; do [ "$dp" = "$p" ] && hit=1; done <<< "$DET_ROWS"
392
397
  [ -n "$hit" ] || continue
393
398
  rail_say " ${C_GREEN}✔$C_RESET $C_TEXT$p$C_RESET"
394
399
  done
395
- rail_say " ${C_MUTED}Enter = these; or type platform names to override$C_RESET"
400
+ rail_say " ${C_MUTED}the install itself writes none — the harness stays neutral$C_RESET"
401
+ rail_say " ${C_MUTED}Enter = these; or type platform names to override (none: type x)$C_RESET"
396
402
  printf ' '
397
403
  read -r REPLY_EMIT
398
404
  ASKED_ANY=1
399
405
  rail_say ""
400
- if [ -n "$REPLY_EMIT" ]; then
406
+ if [ "$REPLY_EMIT" = "x" ] || [ "$REPLY_EMIT" = "none" ]; then
407
+ EMIT_SELECTED=""
408
+ elif [ -n "$REPLY_EMIT" ]; then
401
409
  EMIT_SELECTED=""
402
410
  for w in $REPLY_EMIT; do
403
411
  ok=""
@@ -456,7 +464,7 @@ if [ "$TTY_OUT" -eq 1 ]; then
456
464
  else
457
465
  printf 'gob init [run] goblin-install --target %s --class %s\n' "$TARGET" "$CLASS"
458
466
  fi
459
- bash "$SRC/bin/goblin-install" --target "$TARGET" --class "$CLASS" --yes
467
+ bash "$SRC/bin/goblin-install" --target "$TARGET" --class "$CLASS" --skills no --yes
460
468
  EXIT_INSTALL=$?
461
469
  if [ "$EXIT_INSTALL" -ne 0 ]; then
462
470
  g_err "install failed (exit $EXIT_INSTALL) — the refusals above name the path and the fix"
@@ -546,7 +554,11 @@ if [ "$DRYRUN" -eq 0 ]; then
546
554
  printf ' 1. %sthe %s skipped checks ARE your checklist — gob verify names each one%s\n' "$C_TEXT" "$SKIPPED" "$C_RESET"
547
555
  printf ' 2. %sreplace the placeholder gate in .goblin/goblin.yaml with your real commands (P8 step 3)%s\n' "$C_TEXT" "$C_RESET"
548
556
  printf ' 3. %snot emitted here: gob emit --platform <p> --scope global%s\n' "$C_TEXT" "$C_RESET"
549
- printf ' 4. %shermes skills trust %s # one-time, so the project-tier skills load%s\n' "$C_TEXT" "$TARGET" "$C_RESET"
557
+ if [ "$EMIT_N" -gt 0 ]; then
558
+ printf ' 4. %shermes skills trust %s # one-time, so the project-tier skills load%s\n' "$C_TEXT" "$TARGET" "$C_RESET"
559
+ else
560
+ printf ' 4. %sagent skills are opt-in per platform: gob emit --platform <p>%s\n' "$C_TEXT" "$C_RESET"
561
+ fi
550
562
  printf '\n'
551
563
  if [ "$VERIFY_RC" -eq 0 ]; then
552
564
  printf ' %s▙ the goblin sees you. keep the gate green.%s\n' "$C_ACCENT" "$C_RESET"
@@ -8,7 +8,8 @@
8
8
  # --practice <path> the referenced standard (default: $GOBLIN_PRACTICE -> ~/projects/PROJECT-PRACTICE.md); a named path that is absent is reported, never silently dropped
9
9
  # --parts <list> comma list to install; default = every part the class requires
10
10
  # --archive mark the project archive: verify requires no HANDOFF and no gates
11
- # --skills yes|no install .hermes/skills (default yes; needs the one-time skills trust step)
11
+ # --skills yes|no install agent skills under .hermes/skills (default no; opt in per
12
+ # platform with: gob emit --platform <p>)
12
13
  # --dry-run print the plan; write nothing
13
14
  # --upgrade re-install at the current version; report created/updated/unchanged/skipped
14
15
  # --opt-out <part> record the part in disabled: so its required checks are skipped
@@ -40,7 +41,16 @@ PRACTICE="${GOBLIN_PRACTICE:-$HOME/projects/PROJECT-PRACTICE.md}"
40
41
  PRACTICE_ARG=""
41
42
  PARTS=""
42
43
  ARCHIVE="false"
43
- SKILLS="yes"
44
+ # W6 neutral-first: a default install is a NEUTRAL harness (manifest + verify + HANDOFF +
45
+ # .goblin) with NO agent skills. Agent skills are an explicit opt-in (--skills yes, or the
46
+ # per-platform gob emit path the wizard's emit screen drives). The old default (yes) was the
47
+ # house's own setup, not a product default: a non-agent user got a dead .hermes/ tree, and a
48
+ # user of another agent harness got nothing at all.
49
+ SKILLS="no"
50
+ # Whether --skills was named on the command line: an OMITTED flag on a re-install means
51
+ # "whatever the record says" (the migration contract below); a NAMED --skills no is an
52
+ # explicit opt-out and is honoured verbatim.
53
+ SKILLS_NAMED=0
44
54
  DRY_RUN=0
45
55
  UPGRADE=0
46
56
  UNINSTALL=0
@@ -63,7 +73,7 @@ while [ $# -gt 0 ]; do
63
73
  --practice) PRACTICE="${2:-}"; PRACTICE_ARG="$PRACTICE"; shift 2 ;;
64
74
  --parts) PARTS="${2:-}"; shift 2 ;;
65
75
  --archive) ARCHIVE="true"; shift ;;
66
- --skills) SKILLS="${2:-}"; shift 2 ;;
76
+ --skills) SKILLS="${2:-}"; SKILLS_NAMED=1; shift 2 ;;
67
77
  --dry-run) DRY_RUN=1; shift ;;
68
78
  --upgrade) UPGRADE=1; shift ;;
69
79
  --uninstall) UNINSTALL=1; shift ;;
@@ -233,6 +243,20 @@ fi
233
243
  case "$CLASS" in A|B|C|D|E|F) ;; *) g_err "--class must be one of A B C D E F, got '$CLASS'"; exit 2 ;; esac
234
244
  case "$SKILLS" in yes|no) ;; *) g_err "--skills must be yes or no"; exit 2 ;; esac
235
245
 
246
+ # W6 migration safety: on a --upgrade (or any re-install) of a repo whose record shows skills
247
+ # were installed, an OMITTED --skills flag must READ that state, not silently strip it. The
248
+ # idempotence contract (created/updated/unchanged) holds only if the effective skills choice
249
+ # for a flag-less re-install is `the record's choice`, so previously-installed skills survive
250
+ # every upgrade unchanged. An EXPLICIT --skills no is honoured verbatim (the switch stays
251
+ # real), and --uninstall still removes exactly what the record lists.
252
+ if [ -f "$INSTALLED" ] && [ "$SKILLS_NAMED" -eq 0 ]; then
253
+ PREV_SKILLS=$(g_installed_scalar_options "$INSTALLED" skills)
254
+ if [ "$PREV_SKILLS" = "yes" ]; then
255
+ SKILLS="yes"
256
+ g_info "skills: this repo's install record has agent skills installed - they are kept (remove them with an explicit --skills no, or --uninstall)"
257
+ fi
258
+ fi
259
+
236
260
  PRESET=$(ls "$SRC"/presets/"$CLASS"-*.yaml 2>/dev/null | head -n 1)
237
261
  [ -n "$PRESET" ] || { g_err "no preset for class $CLASS"; exit 2; }
238
262
 
@@ -572,6 +596,30 @@ if [ "$SKILLS" = "yes" ] && part_wanted playbooks; then
572
596
  done
573
597
  fi
574
598
 
599
+ # W6 neutral-first: an EXPLICIT --skills no re-install of a repo that HAS recorded skills
600
+ # removes them — the switch stays real in both directions. The paths come from the previous
601
+ # record, so exactly what was installed is what goes; a skill file the project edited by hand
602
+ # is refused by put's own contract (never clobbered silently), reported in the summary.
603
+ if [ "$SKILLS_NAMED" -eq 1 ] && [ "$SKILLS" = "no" ] && [ -n "$PREV_FILES" ]; then
604
+ while IFS= read -r rel; do
605
+ case "$rel" in .hermes/skills/*) ;; *) continue ;; esac
606
+ dst="$TARGET/$rel"
607
+ [ -f "$dst" ] || { rm -f "$TARGET/.goblin/$(basename "$rel")" 2>/dev/null; continue; }
608
+ if [ "$DRY_RUN" -eq 1 ]; then
609
+ g_info "plan: remove $rel (explicit --skills no)"
610
+ else
611
+ rm -f "$dst"
612
+ g_info "removed $rel (explicit --skills no)"
613
+ fi
614
+ done <<EOF
615
+ $PREV_FILES
616
+ EOF
617
+ # the emptied .hermes/skills/*/<name> and .hermes/ directories go too (deepest first)
618
+ if [ "$DRY_RUN" -eq 0 ]; then
619
+ find "$TARGET/.hermes" -depth -type d -exec rmdir {} + 2>/dev/null
620
+ fi
621
+ fi
622
+
575
623
  # The automation producers. They land in the target so the target is self-contained and
576
624
  # IN-02/SK-02 hash them like every other installed file; the cron copy step then reads from
577
625
  # here (see the "next:" block and automations/README.md).
@@ -667,12 +715,20 @@ fi
667
715
  } > "$TMP/installed.json"
668
716
 
669
717
  PREV_VERSION=""
670
- if [ -f "$INSTALLED" ]; then PREV_VERSION=$(g_installed_scalar "$INSTALLED" version); fi
718
+ RECORD_SKILLS_EFFECTIVE=""
719
+ if [ -f "$INSTALLED" ]; then
720
+ PREV_VERSION=$(g_installed_scalar "$INSTALLED" version)
721
+ # The record's own skills choice (after the migration read above): a flip of the effective
722
+ # choice IS a change even when no file hash moved — the explicit --skills no re-install must
723
+ # rewrite the record (and drop the files), never report no-op over a state change.
724
+ RECORD_SKILLS_EFFECTIVE=$(g_installed_scalar_options "$INSTALLED" skills)
725
+ fi
671
726
 
672
727
 
673
728
  if [ "$DRY_RUN" -eq 0 ]; then
674
729
  mkdir -p "$TARGET/.goblin"
675
- if [ "$CREATED" -eq 0 ] && [ "$UPDATED" -eq 0 ] && [ "$PREV_VERSION" = "$VERSION" ]; then
730
+ if [ "$CREATED" -eq 0 ] && [ "$UPDATED" -eq 0 ] && [ "$PREV_VERSION" = "$VERSION" ] \
731
+ && [ "$(printf '%s' "$RECORD_SKILLS_EFFECTIVE" )" = "$(printf '%s' "$SKILLS")" ]; then
676
732
  g_info "no-op: $UNCHANGED files unchanged (v$VERSION already installed)"
677
733
  else
678
734
  cp "$TMP/installed.json" "$INSTALLED"
@@ -699,6 +755,8 @@ if [ "$DRY_RUN" -eq 0 ]; then
699
755
  g_info " 3. edit .goblin/goblin.yaml: replace the default gate with your real commands (P8 step 3)"
700
756
  if [ "$SKILLS" = "yes" ]; then
701
757
  g_info " 4. hermes skills trust $TARGET # one-time, so the project-tier skills load"
758
+ else
759
+ g_info " 4. agent skills are opt-in: gob emit --platform <p> # run gob doctor for the platform list"
702
760
  fi
703
761
  g_info ""
704
762
  g_info "automations (optional; neither writes outside this repo, and A-02 has no agent in it):"
package/bin/goblin-lib.sh CHANGED
@@ -216,6 +216,37 @@ g_installed_scalar() {
216
216
  sed -n "s/^[[:space:]]*\"$2\"[[:space:]]*:[[:space:]]*\"\{0,1\}\([^\",]*\)\"\{0,1\},\{0,1\}$/\1/p" "$1" | head -n 1
217
217
  }
218
218
 
219
+ # g_installed_scalar_options <file> <key> — the value of one key inside the "options" object
220
+ # of an install record (e.g. skills). The installer writes options on ONE line
221
+ # (`"options": {"skills": "yes", ...}`), so the awk reads that line's shape directly; a
222
+ # multi-line variant is read the same way the other installed_* readers read their block.
223
+ g_installed_scalar_options() {
224
+ awk -v k="$2" '
225
+ /^[[:space:]]*"options"[[:space:]]*:[[:space:]]*\{/ && index($0, "\"" k "\"") {
226
+ # single-line form: pull the value out between the key and the next , or }
227
+ line = $0
228
+ sub(/^[^{]*\{/, "", line) # everything up to and including the opening brace
229
+ n = split(line, pair, ",")
230
+ for (i = 1; i <= n; i++) {
231
+ p = pair[i]
232
+ sub(/^[[:space:]]*/, "", p); sub(/[[:space:]]*$/, "", p)
233
+ key = p; sub(/[[:space:]]*:.*/, "", key); gsub(/"/, "", key)
234
+ if (key == k) {
235
+ v = p; sub(/^[^:]*:[[:space:]]*/, "", v); gsub(/"/, "", v); sub(/\}[[:space:]]*$/, "", v)
236
+ print v; exit
237
+ }
238
+ }
239
+ exit
240
+ }
241
+ /^[[:space:]]*"options"[[:space:]]*:[[:space:]]*\{/ { inf = 1; next }
242
+ inf && /^[[:space:]]*\}/ { inf = 0 }
243
+ inf && index($0, "\"" k "\"") {
244
+ v = $0; sub(/^[^:]*:[[:space:]]*/, "", v); sub(/,?[[:space:]]*$/, "", v); gsub(/"/, "", v)
245
+ print v; exit
246
+ }
247
+ ' "$1"
248
+ }
249
+
219
250
  # ------------------------------------------------------------- self-test -----
220
251
  # Proves the parser actually parses. Every assertion is a real comparison against a
221
252
  # value written to a temp file in this function — blank the awk in g_yaml_scalar and
package/bin/goblin-verify CHANGED
@@ -536,6 +536,13 @@ check_in_02() {
536
536
  [ -e "$ROOT/$p" ] || continue ;;
537
537
  esac
538
538
  fi
539
+ # W6 neutral-first: an EXPLICIT --skills no re-install removes the recorded skill files
540
+ # by design (the record follows the switch). Their absence on a record whose options now
541
+ # say skills=no is the documented opt-out, not drift - the same clause W4a wrote for the
542
+ # global tier, scoped to the record's own declared choice.
543
+ case "$p" in
544
+ .hermes/skills/*) [ -e "$ROOT/$p" ] || [ "$(g_installed_scalar_options "$INSTALLED" skills)" = "yes" ] || continue ;;
545
+ esac
539
546
  n=$((n + 1))
540
547
  cur=$(g_sha256_file "$ROOT/$p")
541
548
  if [ "$cur" != "$h" ]; then
@@ -2661,6 +2668,21 @@ while IFS=$'\t' read -r id scope rule enforced_by artifact check why; do
2661
2668
  only_selected "$id" || continue
2662
2669
 
2663
2670
  part=$(row_part "$id")
2671
+ # W6 neutral-first: in global engine mode AU-01's subject is the PRODUCER glob, which
2672
+ # global mode reads from the engine dir — the row must run there even when the repo's
2673
+ # skills opt-out (playbooks) is set, or the W1 control below it is skip-masked. This
2674
+ # clause precedes the opt-out branch on purpose. SK-01/SK-02/SK-04 and AU-02..AU-04 keep
2675
+ # their opt-out semantics.
2676
+ if [ "$id" = "AU-01" ] && [ "$ENGINE_MODE" = "global" ]; then
2677
+ out=$(check_au_01 2>&1); rc=$?
2678
+ case "$rc" in
2679
+ 0) PASS=$((PASS + 1)); emit "$id" PASS "$out" ;;
2680
+ 1) FAIL=$((FAIL + 1)); emit "$id" FAIL "$out" ;;
2681
+ 2) ADV=$((ADV + 1)); emit "$id" ADV "$out" ;;
2682
+ 3) SKIP=$((SKIP + 1)); emit "$id" SKIP "$out" ;;
2683
+ esac
2684
+ continue
2685
+ fi
2664
2686
  if [ -n "$part" ] && g_part_disabled "$CONFIG" "$part"; then
2665
2687
  SKIP=$((SKIP + 1)); emit "$id" SKIP "$rule (opt-out: $part)"; continue
2666
2688
  fi
package/docs/ADOPTION.md CHANGED
@@ -102,13 +102,16 @@ Each step is independently useful and the later ones build on the earlier:
102
102
  Then, in order:
103
103
 
104
104
  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
105
+ .goblin/bin/goblin-verify # 37 passed, 1 failed - HP-05, until HANDOFF names a commit
106
+ gob emit --platform <p> # optional, per platform: the agent skills are an opt-in
107
107
 
108
- A class-A install is **green** — `43 passed, 0 failed, 11 advisory, 28 skipped`, exit 0 — once
108
+ A default class-A install (no agent skills) is **green** — `38 passed, 0 failed, 11 advisory,
109
+ 33 skipped`, exit 0 — once
109
110
  `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
111
+ the one expected red (`37 passed, 1 failed`). Both numbers are measured, not assumed
112
+ (`docs/CONTRACTS.md`; step 2 of `docs/GUIDE.md`). Thirty-three rows skip with a reason: the
113
+ five skill rows (`SK-01`..`SK-04`, `AU-04`) skip on the `playbooks` opt-out a skills-free
114
+ install records, plus the not-yet rows: `HS-02` (no
112
115
  pinned pre-change commit yet), `AU-02`/`AU-03` (no report has been filed, so there is nothing to
113
116
  dedup and no reporter run to audit), `SC-06`/`SC-07`/`SC-08` (no dependency manifest, no lockfile,
114
117
  no audit record), `PF-01` (no measured perf baseline), `BN-01`/`BN-02`/`BN-05` (no `src/` for a ban
@@ -160,7 +163,7 @@ The remedy is a reconciliation. The project's file stays the file of record; not
160
163
  git add -A && git commit
161
164
  .goblin/bin/goblin-verify # HP-02, HP-03, HP-05 go green
162
165
 
163
- Success is the class's full green path (`43 passed, 0 failed, 11 advisory, 28 skipped`, exit 0 for
166
+ Success is the class's full green path (`38 passed, 0 failed, 11 advisory, 33 skipped`, exit 0 for
164
167
  class A) with `git status --short` empty.
165
168
 
166
169
  The edit is additive and small — measured on the model repo (§1's exemplar, 2450 lines): three
package/docs/CONTRACTS.md CHANGED
@@ -14,7 +14,10 @@ same way the fleet's own tool reads it. Everything else is line-oriented shell.
14
14
  --practice <path> the referenced standard (default: $GOBLIN_PRACTICE -> ~/projects/PROJECT-PRACTICE.md)
15
15
  --parts <list> comma list to install; default = every part the class requires
16
16
  --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)
17
+ --skills yes|no install agent skills under .hermes/skills (default no — the harness is
18
+ neutral; opt in per platform with: gob emit --platform <p>). On a repo whose
19
+ record already has skills installed, an OMITTED flag keeps them; an explicit
20
+ --skills no removes them.
18
21
  --dry-run print the plan; write nothing
19
22
  --upgrade re-install at the current version; report created/updated/unchanged/skipped
20
23
  --opt-out <part> record the part in disabled: so its required checks are skipped
@@ -37,7 +40,7 @@ files whose hash changed and prints `created C · updated U · unchanged N · sk
37
40
 
38
41
  | kind | recorded as | overwritten? | hash-checked? | removed by `--uninstall`? |
39
42
  |---|---|---|---|---|
40
- | installed artifact (bin, manifest, roles, skills, harness scaffold) | `files` | yes, on upgrade | yes — IN-02, SK-02 | yes |
43
+ | installed artifact (bin, manifest, roles, opt-in skills, harness scaffold) | `files` | yes, on upgrade | yes — IN-02, SK-02 | yes |
41
44
  | 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
45
  | pre-existing, left alone | `refused` | never | no — IN-04 only proves it was not taken over | no |
43
46
 
@@ -112,9 +115,9 @@ Output is one line per executed row, in manifest order, plus a summary line at t
112
115
  ADV MD-02 code lane and review lane both resolve to the same family
113
116
  SKIP HS-02 no pinned pre-change commit yet - REPLAY not provable
114
117
 
115
- Those four lines are one row of each marking. The summary line of a green class-A run is:
118
+ Those four lines are one row of each marking. The summary line of a green default class-A run is:
116
119
 
117
- 43 passed, 0 failed, 11 advisory, 28 skipped
120
+ 38 passed, 0 failed, 11 advisory, 33 skipped
118
121
 
119
122
  **Exit codes:** `0` every executed check passed (advisories and skips do not fail the run) ·
120
123
  `1` at least one check FAILED · `2` verify could not run (not installed, a missing dependency,
@@ -135,8 +138,10 @@ settings that make a workflow a **gate** are written down.
135
138
 
136
139
  ### A fresh install verifies green
137
140
 
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
141
+ Measured on a fresh DEFAULT class-A install (skills opt-in, W6 neutral-first), committed with no
142
+ hand edit: **`38 passed, 0 failed, 11 advisory, 33 skipped`, exit 0.** Thirty-three rows skip with
143
+ a reason — the same not-yet rows as before, plus the five skill rows (`SK-01`..`SK-04`,
144
+ `AU-04`) that skip on the `playbooks` opt-out a skills-free install records: `HS-02` — no pre-change commit
140
145
  is pinned yet, so the REPLAY is not provable (`docs/LIMITS.md` #11) — `AU-02` and `AU-03`, which
141
146
  have no report to audit in a repo where no reporter has run — `SC-06`, `SC-07` and `SC-08`, which
142
147
  have no dependency manifest, no lockfile and no audit record to read yet — `PF-01`, which has
@@ -173,13 +178,20 @@ verifier is reporting FAILs.
173
178
  - **Per part:** `--opt-out <part>` records the part in `disabled:`. `goblin-verify` then reports
174
179
  the part's rows as `SKIP (opt-out)` in the summary, so the opt-out is **visible rather than
175
180
  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).
181
+ - **The opt-out numbers are pinned (V3-3).** A class-A install with an explicit `--skills no`
182
+ verifies `38 passed, 0 failed, 11 advisory, 33 skipped`, exit 0, and
183
+ `tests/t-install-off-switch.sh` asserts that line: a silent drift in the opt-out path is caught
184
+ rather than left as a number nobody wrote down (the `--skills no` count moved from `37/0/9/11`
185
+ at v0.2 when the ban rows landed, **15 → 18 on 2026-09-25 (G1)** — the feature-map rows,
186
+ **18 → 24 on 2026-09-25 (W3)** — the judge/loop rows, and **to `38/0/11/33` at W6
187
+ (neutral-first)**, when this opt-out shape BECAME the default and the two lines converged:
188
+ the old default install measured `43/0/11/28`).
189
+ - **Skills, W6 neutral-first.** A default install ships no agent skills. A repo whose record has
190
+ `skills: yes` keeps them through every flag-less re-install and `--upgrade` (the installer
191
+ reads the record's choice and says so out loud); an explicit `--skills no` removes exactly the
192
+ recorded skill files; `--uninstall` removes everything recorded, as always.
193
+ `tests/t-install-off-switch.sh` walks that migration: install `--skills yes`, upgrade flag-less,
194
+ the skills survive byte-identical; uninstall, and they are all gone.
183
195
  - **Whole harness:** `--uninstall` deletes the `files` list plus `.goblin/goblin.yaml`, removes
184
196
  every directory that leaves empty (deepest first, after `installed.json` itself is gone — the
185
197
  order that used to leave `.goblin/` and the sixteen `.hermes/skills/*` directories behind),
package/docs/GUIDE.md CHANGED
@@ -106,18 +106,20 @@ or the plain installer this wizard drives, if you prefer the one-shot shape:
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
 
@@ -305,9 +307,9 @@ honest entry, and the harness treats it as one.
305
307
  > **Prove it was broken first.**
306
308
 
307
309
  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
310
+ it go green. Break it on a row this walkthrough can actually break: `IN-02` hashes the **16 files it
309
311
  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`.
312
+ `.goblin/installed.json`; edit one of the 16 — the exercise below uses `.goblin/bans/README.md`.
311
313
 
312
314
  # REPLAY-BEGIN (this exact block is run by tests/t-doc-guide.sh - keep the two copies identical)
313
315
  .goblin/bin/goblin-verify --only IN-02 # expect PASS
@@ -393,13 +395,13 @@ the next session.
393
395
  A class-A install lands on a specific shape. The scaffold ships one deliberate red — `HP-05`, the
394
396
  `0000000` placeholder in `HANDOFF.md` (§4) — so a literal first run prints:
395
397
 
396
- 42 passed, 1 failed, 11 advisory, 28 skipped (the one FAIL is HP-05)
398
+ 37 passed, 1 failed, 11 advisory, 33 skipped (the one FAIL is HP-05)
397
399
 
398
400
  Name a real commit in `HANDOFF.md` and commit, and it is green:
399
401
 
400
- 43 passed, 0 failed, 11 advisory, 28 skipped (on a real project; your numbers will differ)
402
+ 38 passed, 0 failed, 11 advisory, 33 skipped (on a real project; your numbers will differ)
401
403
 
402
- **Twenty-eight rows skipping is correct**, and each skip prints its reason. In plain terms: the
404
+ **Thirty-three rows skipping is correct**, and each skip prints its reason. In plain terms: the
403
405
  harness is telling you which of its rules have nothing to read yet. It is a checklist, not a
404
406
  scolding.
405
407
 
@@ -579,7 +581,7 @@ with *"prove it was broken first"* — it is the one practice that survives cont
579
581
  # 1. try it somewhere disposable
580
582
  mkdir -p /tmp/gs-try && cd /tmp/gs-try
581
583
  git init -b main
582
- gob install --target . --class A # expect: created 50
584
+ gob install --target . --class A # expect: created 25 (no skills — those are gob emit)
583
585
 
584
586
  # 2. commit and check
585
587
  git add -A && git commit -m "chore: install gobstack"
package/docs/LIMITS.md CHANGED
@@ -111,7 +111,7 @@ deliberate trade or an unfilled gap.
111
111
  drift check — `IN-02`, `SK-02`, and `HS-01`'s hash of the harness dir — reads its expected
112
112
  hash out of that one file, and that file is the one file no check protects. Measured: append a
113
113
  byte to `.goblin/bin/goblin-verify`, rewrite its recorded hash in `installed.json`, commit, and
114
- the run is **fully GREEN** (`43 passed, 0 failed`, exit 0). One edit defeats three rows at
114
+ the run is **fully GREEN** (`38 passed, 0 failed`, exit 0). One edit defeats three rows at
115
115
  once, and it is the cheapest way to fake a green run. Doing better needs an anchor the target
116
116
  cannot edit — a signature, or a hash held outside the repo — and goblin-stack has no such
117
117
  trust root: the source checkout is not guaranteed to exist at verify time, and any value
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techgoblin/gobstack",
3
- "version": "0.4.4-beta.7",
3
+ "version": "0.4.4-beta.8",
4
4
  "description": "Agent-discipline toolkit: one verify command, an enforcement matrix, and LIMITS. bash engine, npm shim.",
5
5
  "bin": {
6
6
  "goblin": "bin/goblin.js",
@@ -9,11 +9,16 @@ Use when adopting goblin-stack in a repo, or starting one.
9
9
 
10
10
  1. **Classify the project A-F.** The class selects which parts are required, optional or off;
11
11
  it is not a stringency level. F is a desktop shell: it adds the electron bans and a host gate.
12
- 2. **`goblin-install --target <dir> --class <x>`**
13
- 3. **`goblin-verify`** — a class-A install verifies green: `43 passed, 0 failed, 11
14
- advisory, 28 skipped`, exit 0, once `HANDOFF.md` names a commit that exists; before that edit the
15
- scaffold's `0000000` placeholder is `HP-05`'s one expected day-one red (`42 passed, 1 failed`).
16
- Twenty-eight rows skip with a reason, and the reason matters: `HS-02`
12
+ 2. **`goblin-install --target <dir> --class <x>`** — the default install is a NEUTRAL harness:
13
+ no agent skills. Opt in per platform afterwards with `gob emit --platform <p>` (or vendor the
14
+ Hermes project tier with `--skills yes`).
15
+ 3. **`goblin-verify`** — a default class-A install (no agent skills) verifies green:
16
+ `38 passed, 0 failed, 11 advisory, 33 skipped`, exit 0, once `HANDOFF.md` names a commit that
17
+ exists; before that edit the
18
+ scaffold's `0000000` placeholder is `HP-05`'s one expected day-one red (`37 passed, 1 failed`).
19
+ Thirty-three rows skip with a reason, and the reason matters: the five skill rows
20
+ (`SK-01`..`SK-04`, `AU-04`) skip on the `playbooks` opt-out a skills-free install records,
21
+ then `HS-02`
17
22
  (no pinned pre-change commit yet, so the REPLAY is not provable), `AU-02`/`AU-03` (no report
18
23
  has been filed in this repo), `SC-06`/`SC-07`/`SC-08` (no dependency manifest, no lockfile, no
19
24
  audit record), `PF-01` (no perf baseline measured yet), `BN-01`/`BN-02`/`BN-05` (the ban table is
@@ -1,23 +1,32 @@
1
1
  # AGENTS.md
2
2
 
3
- This repository uses **goblin-stack** (class `{{CLASS}}`, installed {{DATE}}). It is a pointer,
3
+ This repository uses **gobstack** (class `{{CLASS}}`, installed {{DATE}}). It is a pointer,
4
4
  not a rule dump — the rules live in one executable place, and facts beat requirements.
5
5
 
6
+ - **Harness entry point:** `.goblin/bin/goblin-verify` — exit 0 pass, 1 a check failed,
7
+ 2 could not run, 3 the manifest is broken. Run it before you commit; the gate is the
8
+ same run.
6
9
  - **Rules and their checks:** `.goblin/manifest/enforcement.tsv` (one row per rule; every row
7
10
  carries a runnable check or the literal `advisory`).
8
11
  - **Forbidden code (the ban list):** `.goblin/bin/goblin-bans` runs the bans named by `bans:` in
9
12
  `.goblin/goblin.yaml`; each ban's mechanism lives in `.goblin/manifest/bans.tsv`. Run it before
10
13
  you write the line, not after — a ban is a gate, and `goblin-verify` only reports it once the
11
14
  code exists.
12
- - **Verify:** `.goblin/bin/goblin-verify` — exit 0 pass, 1 a check failed, 2 could not run,
13
- 3 the manifest is broken.
14
- - **Procedures:** the skills installed at `.hermes/skills/` (Hermes project tier — highest
15
- precedence, repo-owned). Start with `goblin-mode`, which routes a request to a playbook.
16
15
  - **Session state:** `HANDOFF.md` at the repo root. Read it before touching anything.
17
16
  - **Project config:** `.goblin/goblin.yaml` (class, branch, gates, ratchet, runtime data,
18
17
  replay, opt-outs). Edit it in place; the installer never overwrites it.
19
18
  - **The referenced standard**, if configured, is named by `practice:` in `.goblin/goblin.yaml`.
20
19
  Read it before starting work; this repo carries no copy of it.
21
20
 
21
+ **Agent skills are opt-in, per platform.** A default install ships none — this is a neutral
22
+ harness on purpose, so nothing here assumes which coding agent you use. To install the
23
+ procedure skills for your tool:
24
+
25
+ gob emit --platform <p>
26
+
27
+ (`--scope project` writes them inside this repo; `--scope global` writes them for your user.)
28
+ Run `gob emit` with no platform to see the list. If skills were installed here, they live
29
+ under the platform's own directory, and `gob verify` hashes them.
30
+
22
31
  Two things this repo does not do: it does not choose models (a role resolves through the
23
32
  mapping file named by `models_file:`), and it does not write outside its own tree.