@techgoblin/gobstack 0.4.4-beta.4 → 0.4.4-beta.6
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 +29 -26
- package/bin/goblin +19 -18
- package/bin/goblin-doctor +3 -3
- package/bin/goblin-emit +3 -3
- package/bin/goblin-init +14 -13
- package/bin/goblin-install +1 -1
- package/bin/goblin-upgrade +5 -5
- package/bin/goblin-verify +1 -1
- package/bin/goblin.js +22 -22
- package/docs/GUIDE.md +26 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ and a rule that cannot be checked is counted rather than asserted.
|
|
|
7
7
|
|
|
8
8
|
Install it from npm — globally; it is a CLI, not a library:
|
|
9
9
|
|
|
10
|
-
npm install -g @techgoblin/gobstack@beta # gives you the `
|
|
10
|
+
npm install -g @techgoblin/gobstack@beta # gives you the `gob` CLI (`goblin` remains as a legacy alias)
|
|
11
11
|
npx @techgoblin/gobstack@beta init # or the one-shot: run the wizard, install nothing globally
|
|
12
12
|
|
|
13
13
|
## Install
|
|
@@ -27,8 +27,8 @@ never imports, and can fail resolution outright with `ERESOLVE` when the app's o
|
|
|
27
27
|
dependencies disagree with npm's. If you see `ERESOLVE` after a local install, remove the
|
|
28
28
|
dependency from `package.json` and install globally instead.
|
|
29
29
|
|
|
30
|
-
**The two-layer model.** The global install gives you the CLI only. `
|
|
31
|
-
`
|
|
30
|
+
**The two-layer model.** The global install gives you the CLI only. `gob init` (or
|
|
31
|
+
`gob install --target <dir> --class A`) then vendors a self-contained engine into the target
|
|
32
32
|
repo under `.goblin/` — verifier, manifest, ban probes, skills, all of it. That second layer is
|
|
33
33
|
why an initialized repo keeps working on machines with **no gobstack installed at all**: the
|
|
34
34
|
engine lives in the repo, not in your `node_modules`, and `bash .goblin/bin/goblin-verify` (or a
|
|
@@ -36,7 +36,7 @@ plain `git` + `bash` box) is the only runtime the repo's gate needs.
|
|
|
36
36
|
|
|
37
37
|
Then, from any project:
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
gob install --target /path/to/repo --class A
|
|
40
40
|
|
|
41
41
|
The installer writes only paths it records, hash-compares before writing, and prints `no-op` on a
|
|
42
42
|
second run with the same arguments. It never overwrites `HANDOFF.md`, `AGENTS.md`, a `*-SPEC.md`,
|
|
@@ -49,9 +49,9 @@ failure: reconcile the file rather than forcing over it — `docs/ADOPTION.md`.
|
|
|
49
49
|
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
53
|
hermes skills trust <target> # one-time, Hermes users, so project-tier skills load
|
|
54
|
-
|
|
54
|
+
gob audit # once, deliberately: the ONLY network step (SC-07)
|
|
55
55
|
|
|
56
56
|
**A class-A install verifies green — `43 passed, 0 failed, 11 advisory, 28 skipped`, exit 0 — once
|
|
57
57
|
`HANDOFF.md` names a commit that exists. Before that edit the scaffold's `0000000` placeholder is
|
|
@@ -76,23 +76,26 @@ that only a round can produce — a first review note, a gate that is not the sh
|
|
|
76
76
|
*vacuously* rather than failing, and `P8` (`goblin-bootstrap`) still walks them as work to do.
|
|
77
77
|
The measurement and the vacuous-pass reading are in `docs/CONTRACTS.md`.
|
|
78
78
|
|
|
79
|
-
## The `
|
|
79
|
+
## The `gob` CLI
|
|
80
80
|
|
|
81
81
|
| command | what it does |
|
|
82
82
|
|---|---|
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
83
|
+
| `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
|
+
| `gob bans` | run the ban list (per-pattern red lines over the source tree) |
|
|
85
|
+
| `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 |
|
|
87
|
+
| `gob uninstall` | remove everything an install wrote, byte-exactly (`gob install --target <dir> --uninstall` is the same job) |
|
|
88
|
+
| `gob upgrade` | migrate a repo to the shared global engine at `~/.goblin/engine` — 8 steps, two commits, one report |
|
|
89
|
+
| `gob doctor` | one run across the platforms below: DETECTED / NOT-DETECTED / DRIFT per platform |
|
|
90
|
+
| `gob emit` | write the skills + context block for one platform (`--scope project` or `global`); `--unshadow` removes a hermes project skill whose hash equals the source |
|
|
91
|
+
| `gob init` | the first-run wizard: detect → class → branch/email → first gate → emit → verify, one screen per question; every question has a flag (`--class app --branch main --email a@b.c --gate 'cmd' --emit hermes`), so CI runs it with zero prompts; `--dry-run` prints the plan and writes nothing |
|
|
92
|
+
|
|
93
|
+
`goblin` remains as a legacy alias for every command above — existing scripts keep working, but
|
|
94
|
+
new commands and docs use `gob`.
|
|
92
95
|
|
|
93
96
|
## Platforms
|
|
94
97
|
|
|
95
|
-
`
|
|
98
|
+
`gob emit` and `gob doctor` cover seven agent platforms, each detected via its own anchor:
|
|
96
99
|
|
|
97
100
|
| platform | what emit writes there |
|
|
98
101
|
|---|---|
|
|
@@ -104,7 +107,7 @@ The measurement and the vacuous-pass reading are in `docs/CONTRACTS.md`.
|
|
|
104
107
|
| `codex` | skills + context block under `~/.codex` (partial: some commands blocked, `docs/LIMITS.md` #47) |
|
|
105
108
|
| `gemini` | skills + context block under `~/.gemini` (partial: some commands blocked, `docs/LIMITS.md` #47) |
|
|
106
109
|
|
|
107
|
-
One run of `
|
|
110
|
+
One run of `gob emit --platform <p> --scope project` writes the skills and the context block a
|
|
108
111
|
session of that platform reads; `--scope global` writes to the machine-level anchor. `--dry-run`
|
|
109
112
|
prints the full write plan first.
|
|
110
113
|
|
|
@@ -143,7 +146,7 @@ vocabulary.
|
|
|
143
146
|
|
|
144
147
|
## Verify
|
|
145
148
|
|
|
146
|
-
|
|
149
|
+
gob verify [--only <id[,id...]>] [--json] [--list] [--source <path>]
|
|
147
150
|
|
|
148
151
|
Exit codes: `0` pass · `1` a check failed · `2` could not
|
|
149
152
|
run · `3` the manifest is broken. Every run prints what it cannot see.
|
|
@@ -166,7 +169,7 @@ touches the others.
|
|
|
166
169
|
|
|
167
170
|
npm uninstall -g @techgoblin/gobstack
|
|
168
171
|
|
|
169
|
-
This removes the `
|
|
172
|
+
This removes the `gob` (and legacy `goblin`) commands from the machine and nothing else: no project, no
|
|
170
173
|
repo, no `.goblin/` directory anywhere is touched. Repos you already initialized keep working
|
|
171
174
|
fully — the engine is vendored into each repo's `.goblin/`, so the CLI's absence removes no
|
|
172
175
|
capability (you lose the installer/upgrade/emit entry points, not the gate; see layer (c) for
|
|
@@ -174,10 +177,10 @@ the machine-level skills the CLI wrote).
|
|
|
174
177
|
|
|
175
178
|
**(b) A project's harness** — the `.goblin/` tree an install created in one repo:
|
|
176
179
|
|
|
177
|
-
|
|
180
|
+
gob uninstall --target .
|
|
178
181
|
|
|
179
|
-
(equivalently `
|
|
180
|
-
`gob
|
|
182
|
+
(equivalently `gob install --target . --uninstall` — through the legacy alias, spell it `goblin`
|
|
183
|
+
instead of `gob`). The uninstall is **byte-exact**: it removes exactly the files
|
|
181
184
|
`installed.json` records — hash-compared preimages, so a file you edited after install is
|
|
182
185
|
reported and kept, never clobbered — then every directory that leaves empty. After it, the repo
|
|
183
186
|
has zero goblin files; only the project's own record (`HANDOFF.md`, `AGENTS.md`, `reviews/`, the
|
|
@@ -188,19 +191,19 @@ self-contained until the moment you remove it.
|
|
|
188
191
|
**(c) Global agent skills** — the machine-level skills an `emit --scope global` wrote outside any
|
|
189
192
|
repo:
|
|
190
193
|
|
|
191
|
-
|
|
194
|
+
gob emit --undo --platform <p> --scope global
|
|
192
195
|
|
|
193
196
|
(`--undo` is the same byte-exact reversal as `--uninstall`, under its friendlier name). By hand,
|
|
194
197
|
the same job is deleting the platform's anchor entries: `~/.claude/skills/goblin-*` (and the
|
|
195
198
|
equivalents under `~/.hermes`, `~/.copilot`, `~/.cursor`, `~/.config/opencode`, `~/.codex`,
|
|
196
|
-
`~/.gemini` — `
|
|
199
|
+
`~/.gemini` — `gob doctor` lists which platforms were detected).
|
|
197
200
|
|
|
198
201
|
The short version, for a full removal from a machine and its repos: (c) first, then (b) in each
|
|
199
202
|
initialized repo, then (a).
|
|
200
203
|
|
|
201
204
|
## Re-pin the referenced standard
|
|
202
205
|
|
|
203
|
-
|
|
206
|
+
gob install --target <dir> --re-pin
|
|
204
207
|
|
|
205
208
|
`practice_sha256:` pins the referenced standard and `IN-02` re-checks it, so editing that standard
|
|
206
209
|
— a legitimate, intended edit — reds `IN-02` in every installed repo. `--re-pin` re-records that
|
package/bin/goblin
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# goblin — the global CLI dispatcher (W1, PLAN-V1 §2.3 / W1-SPEC §3).
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
4
|
+
# gob verify [--only <id[,id...]>] [--json] [--list] [--source <path>]
|
|
5
|
+
# gob bans [--only <id[,id...]>] [--list]
|
|
6
|
+
# gob audit [--target <dir>] [--print]
|
|
7
|
+
# gob doctor [--platform <p>] # W4a
|
|
8
|
+
# gob emit --platform <p> [...] # W4a/W4b
|
|
9
|
+
# gob init [...] # W6: the first-run wizard
|
|
10
|
+
# gob upgrade [--target .] [...] # W3
|
|
11
|
+
# gob --version
|
|
12
12
|
#
|
|
13
|
-
# Identity: the package is
|
|
13
|
+
# Identity: the package is gobstack (npm @techgoblin/gobstack), the command is `gob`
|
|
14
|
+
# (`goblin` remains as a legacy alias). W1 ships the DISPATCH
|
|
14
15
|
# SHELL only — the node shim and npm packaging are W2, doctor/emit are W4a (exit-2
|
|
15
16
|
# placeholders naming their workstream), upgrade is W3 (same). G3 forbids a runtime
|
|
16
17
|
# rewrite: the engine stays bash, this file only routes and propagates.
|
|
@@ -39,16 +40,16 @@ g_err() { printf 'error: %s\n' "$*" >&2; }
|
|
|
39
40
|
|
|
40
41
|
usage() {
|
|
41
42
|
cat <<'USAGE'
|
|
42
|
-
|
|
43
|
+
gob — the gobstack command line.
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
gob verify [--only <id[,id...]>] [--json] [--list] [--source <path>]
|
|
46
|
+
gob bans [--only <id[,id...]>] [--list]
|
|
47
|
+
gob audit [--target <dir>] [--print]
|
|
48
|
+
gob doctor [--platform <p>] [--target <dir>]
|
|
49
|
+
gob emit --platform <p> --scope project|global [...]
|
|
50
|
+
gob init [--target <dir>] [--class app|A-F] [--dry-run]
|
|
51
|
+
gob upgrade [--target .] [--dry-run] [--yes] [--engine-dir <path>]
|
|
52
|
+
gob --version
|
|
52
53
|
|
|
53
54
|
Exit codes (verify): 0 pass | 1 a check failed | 2 could not run | 3 the manifest is
|
|
54
55
|
broken. Every subcommand propagates the engine's exit code verbatim.
|
package/bin/goblin-doctor
CHANGED
|
@@ -40,9 +40,9 @@ ADAPTERS_DIR="$SRC/adapters"
|
|
|
40
40
|
|
|
41
41
|
usage() {
|
|
42
42
|
cat <<USAGE
|
|
43
|
-
|
|
43
|
+
gob doctor — one run, seven platforms (W4a, the seven since W4b).
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
gob doctor [--platform <$_PLA|hermes|copilot|cursor|opencode|codex|$_G1$_G2>]
|
|
46
46
|
[--target <dir>] [--source <path>]
|
|
47
47
|
|
|
48
48
|
Prints, per platform: the verdict (DETECTED / NOT-DETECTED / DRIFT), the detected
|
|
@@ -71,7 +71,7 @@ PLATFORMS="$_PLA hermes copilot cursor opencode codex $_G1$_G2"
|
|
|
71
71
|
if [ -n "$ONLY" ]; then
|
|
72
72
|
case "$ONLY" in
|
|
73
73
|
$_PLA|hermes|copilot|cursor|opencode|codex|$_G1$_G2) PLATFORMS="$ONLY" ;;
|
|
74
|
-
*) die "unknown platform '$ONLY' -
|
|
74
|
+
*) die "unknown platform '$ONLY' - gob doctor ships $_PLA, hermes, copilot, cursor, opencode, codex, $_G1$_G2" 2 ;;
|
|
75
75
|
esac
|
|
76
76
|
fi
|
|
77
77
|
|
package/bin/goblin-emit
CHANGED
|
@@ -68,9 +68,9 @@ PREIMG="${GOBLIN_PREIMAGES:-$HOME/.goblin-stack/preimages}"
|
|
|
68
68
|
|
|
69
69
|
usage() {
|
|
70
70
|
cat <<USAGE
|
|
71
|
-
|
|
71
|
+
gob emit — per-platform emission (W4a, the seven platforms since W4b).
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
gob emit --platform <$_PLA|hermes|copilot|cursor|opencode|codex|$_G1$_G2>
|
|
74
74
|
--scope project|global
|
|
75
75
|
[--skills core|all|none] [--target <dir>] [--source <path>]
|
|
76
76
|
[--uninstall] [--unshadow] [--dry-run] [--strict]
|
|
@@ -143,7 +143,7 @@ case "$PLATFORM" in
|
|
|
143
143
|
"") printf 'error: emit: --platform is required\n' >&2; usage >&2; exit 2 ;;
|
|
144
144
|
$_PLA|hermes|copilot|cursor|opencode|codex|$_G1$_G2) ;;
|
|
145
145
|
*)
|
|
146
|
-
die "unknown platform '$PLATFORM' -
|
|
146
|
+
die "unknown platform '$PLATFORM' - gob emit ships $_PLA, hermes, copilot, cursor, opencode, codex, $_G1$_G2" 2
|
|
147
147
|
;;
|
|
148
148
|
esac
|
|
149
149
|
TSV="$ADAPTERS_DIR/$PLATFORM/adapter.tsv"
|
package/bin/goblin-init
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# goblin-init — the W6 first-run wizard (`gob init`).
|
|
3
3
|
#
|
|
4
|
-
#
|
|
4
|
+
# gob init [--target <dir>] [--class <app|service|game|research|agent|desktop|A..F>]
|
|
5
5
|
# [--branch <name>] [--email <addr>] [--gate <cmd>]
|
|
6
6
|
# [--emit <p[,p..]>] [--scope project|global] [--yes] [--dry-run]
|
|
7
7
|
#
|
|
@@ -137,7 +137,7 @@ done
|
|
|
137
137
|
for dep in git awk sed grep; do
|
|
138
138
|
command -v "$dep" >/dev/null 2>&1 || { g_err "missing dependency: $dep"; exit 2; }
|
|
139
139
|
done
|
|
140
|
-
[ -f "$SRC/VERSION" ] || { g_err "no VERSION at $SRC — is this a
|
|
140
|
+
[ -f "$SRC/VERSION" ] || { g_err "no VERSION at $SRC — is this a gobstack checkout?"; exit 2; }
|
|
141
141
|
[ -n "$SCOPE" ] || SCOPE="project"
|
|
142
142
|
case "$SCOPE" in project|global) ;; *) g_err "--scope must be project or global, got '$SCOPE'"; exit 2 ;; esac
|
|
143
143
|
TARGET="${TARGET:-$PWD}"
|
|
@@ -146,17 +146,18 @@ TARGET=$(g_expand_tilde "$TARGET")
|
|
|
146
146
|
TARGET=$(cd "$TARGET" && pwd)
|
|
147
147
|
|
|
148
148
|
# ------------------------------------------------------------ screen 1: welcome
|
|
149
|
-
printf '%s\n' "${C_ACCENT}
|
|
150
|
-
printf '%s\n' "${C_ACCENT}█
|
|
151
|
-
printf '%s\n' "${C_ACCENT}
|
|
149
|
+
printf '%s\n' "${C_ACCENT}▄███▄ ▄██▄ ██▄ █ ▄█▄ █ █ ▄██▀ █████ ▄███▄ ▄███ █ █${C_RESET}"
|
|
150
|
+
printf '%s\n' "${C_ACCENT}█ █ █ █ █▄▀█ █ █ █▄ █ ▀██▄ █ █ █ █ █ █${C_RESET}"
|
|
151
|
+
printf '%s\n' "${C_ACCENT}█ ▄██ █ █ █▄▀█ █ █ █ ▄█ ▄█▀ █ █████ █ ██${C_RESET}"
|
|
152
|
+
printf '%s\n' "${C_ACCENT}▀██▀▀ ▀██▀ ██▀ ▀▀▀▀ ▀█▀ █ █ ██▀▀ ▀ █ █ ▀██▀ █ █${C_RESET}"
|
|
152
153
|
printf '%s\n' "${C_DIM} discipline around whatever executes${C_RESET}"
|
|
153
154
|
printf '\n'
|
|
154
|
-
printf '%s\n' "${C_TEXT} gob init${C_RESET} ${C_MUTED}— first-run setup (
|
|
155
|
+
printf '%s\n' "${C_TEXT} gob init${C_RESET} ${C_MUTED}— first-run setup (gobstack v$VERSION)${C_RESET}"
|
|
155
156
|
printf '%s\n' " ${C_MUTED}6 steps · every step has a flag · nothing is written until the plan is confirmed${C_RESET}"
|
|
156
157
|
printf '\n'
|
|
157
158
|
CURRENT=1
|
|
158
159
|
if [ "$TTY_OUT" -eq 1 ]; then rail_print
|
|
159
|
-
else cascade "welcome:
|
|
160
|
+
else cascade "welcome: gobstack v$VERSION — the guided first run"; fi
|
|
160
161
|
|
|
161
162
|
# Platform enum assembled from fragments (the MD_SLUG precedent): a literal tool name in
|
|
162
163
|
# bin/ trips MD-01's model-name pattern, and run-tests.sh greps this directory.
|
|
@@ -227,7 +228,7 @@ if [ -z "$CLASS" ]; then
|
|
|
227
228
|
q " $C_TEXT 4 research specs, replays, reference corpora ${C_DIM}(class D)$C_RESET"
|
|
228
229
|
q " $C_TEXT 5 agent fleets, loops, unattended automation ${C_DIM}(class E)$C_RESET"
|
|
229
230
|
q " $C_TEXT 6 desktop the shell you live in, perf budget ${C_DIM}(class F)$C_RESET"
|
|
230
|
-
q " $C_MUTED↑ number + Enter · Enter = app · the class letter works too (A-F)$C_RESET"
|
|
231
|
+
q " ${C_MUTED}↑ number + Enter · Enter = app · the class letter works too (A-F)$C_RESET"
|
|
231
232
|
printf ' '
|
|
232
233
|
read -r REPLY_CLASS
|
|
233
234
|
ASKED_ANY=1
|
|
@@ -360,7 +361,7 @@ if [ "$TTY_IN" -eq 1 ] && [ -z "$EMIT" ] && [ "$YES" -eq 0 ]; then
|
|
|
360
361
|
hit=""
|
|
361
362
|
while IFS=$' ' read -r dp _a _b _c; do [ "$dp" = "$p" ] && hit=1; done <<< "$DET_ROWS"
|
|
362
363
|
[ -n "$hit" ] || continue
|
|
363
|
-
q " $C_GREEN✔$C_RESET $C_TEXT$p$C_RESET"
|
|
364
|
+
q " ${C_GREEN}✔$C_RESET $C_TEXT$p$C_RESET"
|
|
364
365
|
done
|
|
365
366
|
q " ${C_DIM}Enter = these; or type platform names to override$C_RESET"
|
|
366
367
|
printf ' '
|
|
@@ -388,7 +389,7 @@ add() { BOX="$BOX$1
|
|
|
388
389
|
"; }
|
|
389
390
|
add " ${C_DIM}╭──────────────────────────────────────────────────────╮${C_RESET}"
|
|
390
391
|
add " ${C_DIM}│${C_RESET} ${C_GREEN}✔${C_RESET} ${C_TEXT}install${C_RESET} $C_DIM.goblin/ + the payload into$C_RESET $C_TEXT$TARGET$C_RESET"
|
|
391
|
-
add " ${C_DIM}│${C_RESET} ${C_GREEN}✔${C_RESET} ${C_TEXT}class${C_RESET} $C_TEXT$CLASS$C_RESET $C_DIM· branch $C_TEXT$BRANCH$C_RESET $C_DIM· email$C_RESET $C_TEXT$EMAIL$C_RESET"
|
|
392
|
+
add " ${C_DIM}│${C_RESET} ${C_GREEN}✔${C_RESET} ${C_TEXT}class${C_RESET} $C_TEXT$CLASS$C_RESET ${C_DIM}· branch $C_TEXT$BRANCH$C_RESET ${C_DIM}· email$C_RESET $C_TEXT$EMAIL$C_RESET"
|
|
392
393
|
add " ${C_DIM}│${C_RESET} ${C_GREEN}✔${C_RESET} ${C_TEXT}gate${C_RESET} $C_TEXT$GATE$C_RESET"
|
|
393
394
|
add " ${C_DIM}│${C_RESET} ${C_GREEN}✔${C_RESET} ${C_TEXT}emit${C_RESET} $C_TEXT$EMIT_N platform(s), scope $SCOPE$C_RESET$([ "$EMIT_N" -gt 0 ] && printf '%s' " $C_DIM($(printf '%s' "$EMIT_SELECTED" | awk '{$1=$1};1'))$C_RESET")"
|
|
394
395
|
add " ${C_DIM}╰──────────────────────────────────────────────────────╯${C_RESET}"
|
|
@@ -456,7 +457,7 @@ for p in $EMIT_SELECTED; do
|
|
|
456
457
|
bash "$SRC/bin/goblin-emit" --platform "$p" --scope "$SCOPE" --target "$TARGET"
|
|
457
458
|
EXIT_EMIT=$?
|
|
458
459
|
if [ "$EXIT_EMIT" -ne 0 ]; then
|
|
459
|
-
g_err "emit $p failed (exit $EXIT_EMIT) — run '
|
|
460
|
+
g_err "emit $p failed (exit $EXIT_EMIT) — run 'gob emit --platform $p --scope $SCOPE' to see the refusal again"
|
|
460
461
|
exit "$EXIT_EMIT"
|
|
461
462
|
fi
|
|
462
463
|
EMITTED=$((EMITTED + 1))
|
|
@@ -489,9 +490,9 @@ if [ "$DRYRUN" -eq 0 ]; then
|
|
|
489
490
|
done
|
|
490
491
|
printf '\n'
|
|
491
492
|
if [ "$VERIFY_RC" -eq 0 ]; then
|
|
492
|
-
printf ' %
|
|
493
|
+
printf ' %sgob verify: %s passed, %s failed, %s skipped · exit 0%s\n' "$C_GREEN" "$PASSED" "$FAILED" "$SKIPPED" "$C_RESET"
|
|
493
494
|
else
|
|
494
|
-
printf ' %
|
|
495
|
+
printf ' %sgob verify: %s passed, %s failed, %s skipped · exit %s%s\n' "$C_RED" "$PASSED" "$FAILED" "$SKIPPED" "$VERIFY_RC" "$C_RESET"
|
|
495
496
|
fi
|
|
496
497
|
printf '\n'
|
|
497
498
|
else
|
package/bin/goblin-install
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# goblin-install — drop the harness into a target repo, idempotently.
|
|
3
3
|
#
|
|
4
|
-
# Usage:
|
|
4
|
+
# Usage: gob install --target <dir> [options] (script: bin/goblin-install)
|
|
5
5
|
# --target <dir> required; the repo root to install into
|
|
6
6
|
# --class A|B|C|D|E|F required unless --uninstall or --re-pin; anything else needs an explicit class
|
|
7
7
|
# --models <path> model mapping file (default: $GOBLIN_MODELS -> ~/projects/fleet-model.yaml)
|
package/bin/goblin-upgrade
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# goblin-upgrade — the 0.4.4 per-repo → global migration (W3
|
|
2
|
+
# goblin-upgrade — the 0.4.4 per-repo → global engine migration (W3).
|
|
3
3
|
#
|
|
4
|
-
#
|
|
4
|
+
# gob upgrade [--target <dir>] [--dry-run] [--yes] [--engine-dir <path>]
|
|
5
5
|
#
|
|
6
6
|
# What it does (the corrected 7-step sequence, W3-SPEC §2 — the record is rewritten
|
|
7
7
|
# BEFORE the deletion, and every verify runs against a committed tree, CM-03):
|
|
@@ -230,7 +230,7 @@ if [ "${RECORD_OK:-0}" -eq 1 ] && [ -z "${MODE:-}" ]; then
|
|
|
230
230
|
R8_MSG="upgrade: partial migration detected - the record says global but $PAYLOAD_N engine files are still here. fix: resume by removing them (the engine lives at ${BLOCK_DIR:-$ED}), or roll back: git revert <commit A>"
|
|
231
231
|
elif [ "$BLOCK_MODE" != "global" ] && [ "$RECORD_ENGINE_N" -lt 18 ]; then
|
|
232
232
|
MODE="refuse-R9" # hand-migration: entries PARTIALLY or fully dropped, no block
|
|
233
|
-
R9_MSG="upgrade: $REPO's record is missing its engine: block and carries only $RECORD_ENGINE_N of the 18 engine entries - dropped $((18 - RECORD_ENGINE_N)) by hand, a state
|
|
233
|
+
R9_MSG="upgrade: $REPO's record is missing its engine: block and carries only $RECORD_ENGINE_N of the 18 engine entries - dropped $((18 - RECORD_ENGINE_N)) by hand, a state gob upgrade did not produce. fix: restore the record (git checkout .goblin/installed.json) and re-run"
|
|
234
234
|
elif [ "$PAYLOAD_N" -eq 0 ]; then
|
|
235
235
|
MODE="refuse-R5" # fresh-shape record but no payload to lift
|
|
236
236
|
R5_MSG="upgrade: no engine at $ED and no vendored payload to lift it from - install the engine first (goblin-install a fresh repo, or set --engine-dir)"
|
|
@@ -244,7 +244,7 @@ fi
|
|
|
244
244
|
|
|
245
245
|
# ---- dry-run report -------------------------------------------------------------
|
|
246
246
|
if [ "$DRY_RUN" -eq 1 ]; then
|
|
247
|
-
printf '
|
|
247
|
+
printf 'gob upgrade --dry-run — target %s\n' "$REPO"
|
|
248
248
|
printf ' engine-dir: %s\n' "$ED"
|
|
249
249
|
case "$MODE" in
|
|
250
250
|
migrate)
|
|
@@ -447,7 +447,7 @@ if [ -f "$GATE" ] && grep -q '\.goblin/bin/goblin-lib.sh' "$GATE"; then
|
|
|
447
447
|
awk '
|
|
448
448
|
/^# shellcheck source=\/dev\/null$/ && !done {
|
|
449
449
|
print "# shellcheck source=/dev/null"
|
|
450
|
-
print "# The engine went global (
|
|
450
|
+
print "# The engine went global (gob upgrade): resolve it exactly as"
|
|
451
451
|
print "# goblin-verify does - engine_dir:, then the machine default."
|
|
452
452
|
print "CONFIG=\"$ROOT/.goblin/goblin.yaml\""
|
|
453
453
|
print "ENGINE_DIR=$(sed -n \"s/^engine_dir:[[:space:]]*//p\" \"$CONFIG\" | head -n 1)"
|
package/bin/goblin-verify
CHANGED
|
@@ -1363,7 +1363,7 @@ check_sc_07() {
|
|
|
1363
1363
|
local rec="$ROOT/.goblin/audit.tsv" wfile="$ROOT/.goblin/audit-waiver.tsv"
|
|
1364
1364
|
if [ ! -f "$rec" ]; then
|
|
1365
1365
|
if [ "$ENGINE_MODE" = "global" ]; then
|
|
1366
|
-
printf 'no audit record yet. Run `
|
|
1366
|
+
printf 'no audit record yet. Run `gob audit` once, deliberately, then commit\n.goblin/audit.tsv: SC-07 reads the record and never the network\n'
|
|
1367
1367
|
else
|
|
1368
1368
|
printf 'no audit record yet. Run .goblin/bin/goblin-audit once, deliberately, then commit\n.goblin/audit.tsv: SC-07 reads the record and never the network\n'
|
|
1369
1369
|
fi
|
package/bin/goblin.js
CHANGED
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|
// and a marketplace packager stripping the executable bit breaks the symlink, not
|
|
7
7
|
// the spawnSync path below.
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
9
|
+
// gob verify [--only <id,...>] ... -> bin/goblin-verify
|
|
10
|
+
// gob bans [...] -> bin/goblin-bans
|
|
11
|
+
// gob audit [...] -> bin/goblin-audit
|
|
12
|
+
// gob upgrade [...] -> bin/goblin-upgrade (W3)
|
|
13
|
+
// gob doctor [...] -> bin/goblin-doctor (W4a)
|
|
14
|
+
// gob emit [...] -> bin/goblin-emit (W4a)
|
|
15
|
+
// gob init [...] -> bin/goblin-init (W6, the first-run wizard)
|
|
16
|
+
// gob uninstall [--target <dir>] -> bin/goblin-install --uninstall
|
|
17
|
+
// gob install [...] -> bin/goblin-install (the one legacy fallback)
|
|
18
18
|
// no args | -h/--help | any other unrecognized first arg
|
|
19
19
|
// -> this file's short usage, exit 2. A bare `goblin`
|
|
20
20
|
// used to fall through into the installer; a typo
|
|
@@ -50,23 +50,23 @@ const [cmd, ...rest] = process.argv.slice(2);
|
|
|
50
50
|
// No args, a help flag, or an unrecognized first arg: short usage, exit 2. The one survivor of
|
|
51
51
|
// the old catch-all fallback is the literal `install` first arg — bare `goblin` mapped to the
|
|
52
52
|
// installer through npm's bin default, and a typo (`goblin inti`) silently installed into
|
|
53
|
-
// whatever directory the shell sat in. A bare subcommand-less `
|
|
53
|
+
// whatever directory the shell sat in. A bare subcommand-less `gob install ...` keeps the
|
|
54
54
|
// installer; everything else stops here and names the word it did not know.
|
|
55
55
|
function usage() {
|
|
56
56
|
process.stderr.write(
|
|
57
57
|
[
|
|
58
|
-
"
|
|
58
|
+
"gob <command>",
|
|
59
59
|
"",
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
60
|
+
" gob init start here — the guided first step (detect, class, emit, first verify)",
|
|
61
|
+
" gob verify run the rule matrix against the current repo",
|
|
62
|
+
" gob bans run the ban list (per-pattern red lines over the source tree)",
|
|
63
|
+
" gob audit check recorded dependency claims against live advisory feeds",
|
|
64
|
+
" gob upgrade migrate a repo to the shared global engine at ~/.goblin/engine",
|
|
65
|
+
" gob doctor one detection/drift run across the agent platforms",
|
|
66
|
+
" gob emit write the skills + context block for one platform",
|
|
67
|
+
" gob uninstall --target . remove exactly what an install wrote (preimages)",
|
|
68
68
|
"",
|
|
69
|
-
"start here:
|
|
69
|
+
"start here: gob init",
|
|
70
70
|
"uninstall: npm uninstall -g @techgoblin/gobstack",
|
|
71
71
|
"",
|
|
72
72
|
].join("\n"));
|
|
@@ -77,7 +77,7 @@ if (cmd === undefined || cmd.startsWith("-")) {
|
|
|
77
77
|
process.exit(2);
|
|
78
78
|
}
|
|
79
79
|
if (!SCRIPT[cmd] && cmd !== "install" && cmd !== "uninstall") {
|
|
80
|
-
process.stderr.write(`
|
|
80
|
+
process.stderr.write(`gob: unrecognized command: ${cmd}\n\n`);
|
|
81
81
|
usage();
|
|
82
82
|
process.exit(2);
|
|
83
83
|
}
|
|
@@ -87,7 +87,7 @@ let extra = [];
|
|
|
87
87
|
if (cmd === "install") {
|
|
88
88
|
target = "goblin-install"; // the one legacy fallback, kept verbatim
|
|
89
89
|
} else if (cmd === "uninstall") {
|
|
90
|
-
// `
|
|
90
|
+
// `gob uninstall --target <dir>` routes into the installer's uninstall job — the shape
|
|
91
91
|
// docs/GUIDE.md and README already promise. `--uninstall` is appended FIRST so the user's
|
|
92
92
|
// own `--target <dir>` and options still parse, and a stray literal `--uninstall` cannot
|
|
93
93
|
// appear twice.
|
package/docs/GUIDE.md
CHANGED
|
@@ -31,7 +31,8 @@ design on day one.
|
|
|
31
31
|
|
|
32
32
|
## 1. What this actually is, in plain language
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
gobstack (published on npm as **`@techgoblin/gobstack`**) — this guide, the README and the CLI
|
|
35
|
+
all call it **gobstack**
|
|
35
36
|
is **a folder of files you install into a project** from npm. Once
|
|
36
37
|
installed, three things change:
|
|
37
38
|
|
|
@@ -53,7 +54,7 @@ deliberately, and §8 and §11 say why.
|
|
|
53
54
|
> **A rule that cannot fail is worse than no rule**, because it takes credit for verification it
|
|
54
55
|
> does not perform.
|
|
55
56
|
|
|
56
|
-
Everything else in
|
|
57
|
+
Everything else in gobstack follows from that sentence. If you remember one thing from this
|
|
57
58
|
guide, remember that one — it is also the standard the harness holds itself to, and the reason it
|
|
58
59
|
ships a file of things it *cannot* check (`docs/LIMITS.md`).
|
|
59
60
|
|
|
@@ -87,7 +88,7 @@ one way this guide installs it.
|
|
|
87
88
|
**Do not install into a real project yet.** You want to see what it does before it touches
|
|
88
89
|
something you care about.
|
|
89
90
|
|
|
90
|
-
The guided path is `
|
|
91
|
+
The guided path is `gob init` — one screen per question (class, branch/email, first
|
|
91
92
|
gate, which platforms to emit), every question also answerable by flag, `--dry-run` to
|
|
92
93
|
see the plan first:
|
|
93
94
|
|
|
@@ -96,12 +97,12 @@ see the plan first:
|
|
|
96
97
|
git config user.email "you@example.com"
|
|
97
98
|
git config user.name "you"
|
|
98
99
|
|
|
99
|
-
|
|
100
|
+
gob init --target . --class app --branch main --email "you@example.com" \
|
|
100
101
|
--gate "bash tests/run-tests.sh" --yes
|
|
101
102
|
|
|
102
103
|
or the plain installer this wizard drives, if you prefer the one-shot shape:
|
|
103
104
|
|
|
104
|
-
|
|
105
|
+
gob install --target . --class A
|
|
105
106
|
|
|
106
107
|
Expected output (this is a real transcript, trimmed):
|
|
107
108
|
|
|
@@ -134,7 +135,7 @@ branch** (unless you want to).
|
|
|
134
135
|
## 4. Step 2 — Commit, then verify (the moment it earns its keep)
|
|
135
136
|
|
|
136
137
|
cd /tmp/gs-try
|
|
137
|
-
git add -A && git commit -m "chore: install
|
|
138
|
+
git add -A && git commit -m "chore: install gobstack"
|
|
138
139
|
.goblin/bin/goblin-verify
|
|
139
140
|
|
|
140
141
|
You will see one line per rule. The shape:
|
|
@@ -219,7 +220,7 @@ without you remembering to. And the gate numbers are recorded with a date, so a
|
|
|
219
220
|
### The `practice:` key — the part that makes it yours
|
|
220
221
|
|
|
221
222
|
If you already have a house standard — a `CONTRIBUTING.md`, a `PROJECT-PRACTICE.md`, anything
|
|
222
|
-
written down — point `practice:` at it.
|
|
223
|
+
written down — point `practice:` at it. gobstack does **not** copy its text. It records a
|
|
223
224
|
**hash** of the file and re-checks that hash on every verify.
|
|
224
225
|
|
|
225
226
|
That buys you one specific, valuable thing: **if someone edits your standard, every project that
|
|
@@ -228,7 +229,7 @@ repos follow an old version.
|
|
|
228
229
|
|
|
229
230
|
When *you* legitimately edit your own standard:
|
|
230
231
|
|
|
231
|
-
|
|
232
|
+
gob install --target . --re-pin
|
|
232
233
|
|
|
233
234
|
It re-records the hash and prints the old and new value. Nothing re-pins automatically — an
|
|
234
235
|
edited standard is never a silent no-op.
|
|
@@ -329,7 +330,7 @@ own the same way, once that gate is real: declare it in `.goblin/goblin.yaml` an
|
|
|
329
330
|
can see.
|
|
330
331
|
|
|
331
332
|
A check that is green on **both** the broken and the fixed tree proves nothing — it would have been
|
|
332
|
-
green anyway.
|
|
333
|
+
green anyway. gobstack calls this the **REPLAY** rule, and it is the single practice that has
|
|
333
334
|
caught every real regression in this repository's own development history.
|
|
334
335
|
|
|
335
336
|
---
|
|
@@ -424,7 +425,7 @@ Two readings that are easy to get wrong:
|
|
|
424
425
|
| `refused to overwrite: HANDOFF.md`, exit 1 | your repo already had a HANDOFF | **do not `--force`** — reconcile it (below) |
|
|
425
426
|
| `PT-02 declared main, actual master` | branch mismatch | set `branch:` in the config |
|
|
426
427
|
| `IN-02 ... practice EDITED` | someone changed the pinned standard | re-pin deliberately: `--re-pin` |
|
|
427
|
-
| `
|
|
428
|
+
| `gob install: unknown subcommand` (exit 2) | you ran a bare `gob install` without the npm package installed | install the npm package first: `npm i -g @techgoblin/gobstack`, then `gob install` |
|
|
428
429
|
| `IN-03` fails, "manifest is broken" | a row has a broken check column | fix the row; this is a source defect, not yours |
|
|
429
430
|
| a `FAIL` you believe is wrong | the check may be weak, or your belief may be | run `--only <id>` and read the command it prints |
|
|
430
431
|
|
|
@@ -442,12 +443,12 @@ Declared but unusable (relative path, missing directory, no manifest inside) is
|
|
|
442
443
|
with no fallback** — a repo is never judged by an engine it did not declare. A repo whose record
|
|
443
444
|
says `mode=global` keeps hashing whatever files it still holds; the engine's own identity prints in
|
|
444
445
|
every run's footer (`engine: mode=… cli_sha256=… enforcement_tsv_sha256=…`). The same commands are
|
|
445
|
-
available outside any repo through the npm CLI: `
|
|
446
|
-
`
|
|
446
|
+
available outside any repo through the npm CLI: `gob verify` / `gob bans` / `gob audit` /
|
|
447
|
+
`gob doctor` / `gob emit` / `gob upgrade` / `gob --version`.
|
|
447
448
|
|
|
448
449
|
**Migrating a repo to the global engine (W3):**
|
|
449
450
|
|
|
450
|
-
|
|
451
|
+
gob upgrade # 8 steps, two commits, one report
|
|
451
452
|
|
|
452
453
|
It refuses on a dirty tree, a detached HEAD, a red repo, or a global engine holding different
|
|
453
454
|
bytes — each refusal names the fix. What it does: verifies every recorded hash, lands the engine
|
|
@@ -461,7 +462,7 @@ again. Nothing is deleted before the engine is safely landed and the tree is gre
|
|
|
461
462
|
git revert <commit-A-sha> <commit-B-sha>
|
|
462
463
|
|
|
463
464
|
reverses byte-for-byte: the vendored payload returns, the record drops its `engine:` block, and
|
|
464
|
-
`
|
|
465
|
+
`gob verify` is the 43-green it was before. A second `gob upgrade` on a migrated repo is a
|
|
465
466
|
no-op; `goblin-install` onto one refuses with the revert remedy (re-installing would re-shadow the
|
|
466
467
|
engine and silently de-migrate the record).
|
|
467
468
|
|
|
@@ -474,7 +475,7 @@ engine and silently de-migrate the record).
|
|
|
474
475
|
| `goblin` (npm CLI) | propagates the subcommand's codes verbatim — `verify`/`bans`/`audit`/`--version`; `install`/`uninstall`/`re-pin`/`upgrade` route into `goblin-install` (`upgrade` migrates to the global engine: `0` ok · `1` refusal · `2` bad input); `doctor`/`emit` carry the same contract: `doctor` exits `0` every probed platform DETECTED and clean · `1` any DRIFT · `2` nothing to probe, and `emit` exits `0` ok or no-op · `1` refusal (with the path and the fix) · `2` bad input or unknown platform |
|
|
475
476
|
| platforms (W4b) | `emit`/`doctor` cover seven: `claude`, `hermes`, `copilot`, `cursor`, `opencode`, `codex`, `gemini` — each detected via its own anchor (`~/.claude`, `~/.hermes`, `~/.copilot`, `~/.cursor`, `~/.config/opencode`, `~/.codex`, `~/.gemini`); codex and gemini carry `partial` command-blocking (see LIMITS #47) |
|
|
476
477
|
|
|
477
|
-
`3` is the one to notice: it means
|
|
478
|
+
`3` is the one to notice: it means gobstack's own rule table is malformed, not your project.
|
|
478
479
|
|
|
479
480
|
---
|
|
480
481
|
|
|
@@ -482,10 +483,10 @@ engine and silently de-migrate the record).
|
|
|
482
483
|
|
|
483
484
|
### Commands
|
|
484
485
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
486
|
+
gob install --target <dir> --class A|B|C|D|E|F [options]
|
|
487
|
+
gob install --target <dir> --uninstall
|
|
488
|
+
gob install --target <dir> --re-pin
|
|
489
|
+
gob install --target <dir> --upgrade
|
|
489
490
|
|
|
490
491
|
.goblin/bin/goblin-verify [--only <id[,id...]>] [--json] [--list]
|
|
491
492
|
.goblin/bin/goblin-audit # the only network step
|
|
@@ -505,7 +506,7 @@ Named procedures, installed as project-local skills. Each has a measurable verif
|
|
|
505
506
|
| P5 | `goblin-tdd-repro` | a defect where a regression test is cheap |
|
|
506
507
|
| P6 | `goblin-verify-author` | a project has no live check lane, or its gates drift |
|
|
507
508
|
| P7 | `goblin-pr-gate` | anything that should be reviewed before landing |
|
|
508
|
-
| P8 | `goblin-bootstrap` | adopting
|
|
509
|
+
| P8 | `goblin-bootstrap` | adopting gobstack, or starting a project |
|
|
509
510
|
| P9 | `goblin-handoff` | ending a session, or picking up another's |
|
|
510
511
|
| P10 | `goblin-overnight` | an unattended run over a predicate |
|
|
511
512
|
| P11 | `goblin-sweep` | the same change across many projects |
|
|
@@ -578,10 +579,10 @@ with *"prove it was broken first"* — it is the one practice that survives cont
|
|
|
578
579
|
# 1. try it somewhere disposable
|
|
579
580
|
mkdir -p /tmp/gs-try && cd /tmp/gs-try
|
|
580
581
|
git init -b main
|
|
581
|
-
|
|
582
|
+
gob install --target . --class A # expect: created 50
|
|
582
583
|
|
|
583
584
|
# 2. commit and check
|
|
584
|
-
git add -A && git commit -m "chore: install
|
|
585
|
+
git add -A && git commit -m "chore: install gobstack"
|
|
585
586
|
.goblin/bin/goblin-verify # expect: mostly PASS, some SKIP
|
|
586
587
|
|
|
587
588
|
# 3. make it yours
|
|
@@ -599,12 +600,12 @@ with *"prove it was broken first"* — it is the one practice that survives cont
|
|
|
599
600
|
|
|
600
601
|
# 5. do it for real, in a repo you care about
|
|
601
602
|
cd ~/projects/your-project
|
|
602
|
-
|
|
603
|
-
git add -A && git commit -m "chore: adopt
|
|
603
|
+
gob install --target . --class A
|
|
604
|
+
git add -A && git commit -m "chore: adopt gobstack"
|
|
604
605
|
.goblin/bin/goblin-verify
|
|
605
606
|
$EDITOR HANDOFF.md # state / gates (dated!) / next / NOT verified
|
|
606
607
|
|
|
607
608
|
---
|
|
608
609
|
|
|
609
|
-
*This guide is part of
|
|
610
|
+
*This guide is part of gobstack. If you find a step that does not work as written, that is a
|
|
610
611
|
defect in the guide — report it the same way you would report one in the code.*
|
package/package.json
CHANGED