@techgoblin/gobstack 0.4.4-beta.1 → 0.4.4-beta.3
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 +62 -8
- package/bin/goblin-emit +2 -0
- package/bin/goblin-init +6 -6
- package/bin/goblin.js +58 -3
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -5,13 +5,34 @@ installs an **executable rule manifest**, a set of **project-local skills**, and
|
|
|
5
5
|
**installer/verifier pair** into any project — so that an AI coding session never re-improvises,
|
|
6
6
|
and a rule that cannot be checked is counted rather than asserted.
|
|
7
7
|
|
|
8
|
-
Install it from npm —
|
|
8
|
+
Install it from npm — globally; it is a CLI, not a library:
|
|
9
9
|
|
|
10
|
-
npm
|
|
10
|
+
npm install -g @techgoblin/gobstack@beta # gives you the `goblin` CLI (and `gob`)
|
|
11
|
+
npx @techgoblin/gobstack@beta init # or the one-shot: run the wizard, install nothing globally
|
|
11
12
|
|
|
12
13
|
## Install
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
**Global, not local.** gobstack is a CLI with zero runtime dependencies. Install it once per
|
|
16
|
+
machine:
|
|
17
|
+
|
|
18
|
+
npm install -g @techgoblin/gobstack@beta
|
|
19
|
+
|
|
20
|
+
or run a single command without installing:
|
|
21
|
+
|
|
22
|
+
npx @techgoblin/gobstack@beta init
|
|
23
|
+
|
|
24
|
+
**Do NOT add it to an app project's `package.json`.** A `npm install @techgoblin/gobstack` (or a
|
|
25
|
+
`package.json` dependency) inside your app pollutes the app's lockfile with a package the app
|
|
26
|
+
never imports, and can fail resolution outright with `ERESOLVE` when the app's own peer
|
|
27
|
+
dependencies disagree with npm's. If you see `ERESOLVE` after a local install, remove the
|
|
28
|
+
dependency from `package.json` and install globally instead.
|
|
29
|
+
|
|
30
|
+
**The two-layer model.** The global install gives you the CLI only. `goblin init` (or
|
|
31
|
+
`goblin install --target <dir> --class A`) then vendors a self-contained engine into the target
|
|
32
|
+
repo under `.goblin/` — verifier, manifest, ban probes, skills, all of it. That second layer is
|
|
33
|
+
why an initialized repo keeps working on machines with **no gobstack installed at all**: the
|
|
34
|
+
engine lives in the repo, not in your `node_modules`, and `bash .goblin/bin/goblin-verify` (or a
|
|
35
|
+
plain `git` + `bash` box) is the only runtime the repo's gate needs.
|
|
15
36
|
|
|
16
37
|
Then, from any project:
|
|
17
38
|
|
|
@@ -63,7 +84,7 @@ The measurement and the vacuous-pass reading are in `docs/CONTRACTS.md`.
|
|
|
63
84
|
| `goblin bans` | run the ban list (per-pattern red lines over the source tree) |
|
|
64
85
|
| `goblin audit` | check recorded dependency claims against live advisory feeds — the only command that touches the network |
|
|
65
86
|
| `goblin install` | install the manifest, skills and verifier into a target repo |
|
|
66
|
-
| `goblin uninstall` | remove everything an install wrote (
|
|
87
|
+
| `goblin uninstall` | remove everything an install wrote, byte-exactly (`goblin install --target <dir> --uninstall` is the same job) |
|
|
67
88
|
| `goblin upgrade` | migrate a repo to the shared global engine at `~/.goblin/engine` — 8 steps, two commits, one report |
|
|
68
89
|
| `goblin doctor` | one run across the platforms below: DETECTED / NOT-DETECTED / DRIFT per platform |
|
|
69
90
|
| `goblin emit` | write the skills + context block for one platform (`--scope project` or `global`); `--unshadow` removes a hermes project skill whose hash equals the source |
|
|
@@ -138,11 +159,44 @@ failure**, so that file is the one that matters most.
|
|
|
138
159
|
|
|
139
160
|
## Uninstall
|
|
140
161
|
|
|
141
|
-
|
|
162
|
+
gobstack lives in three layers. Each is removed by its own command, and removing one never
|
|
163
|
+
touches the others.
|
|
164
|
+
|
|
165
|
+
**(a) The global CLI** — the npm package itself:
|
|
166
|
+
|
|
167
|
+
npm uninstall -g @techgoblin/gobstack
|
|
168
|
+
|
|
169
|
+
This removes the `goblin` and `gob` commands from the machine and nothing else: no project, no
|
|
170
|
+
repo, no `.goblin/` directory anywhere is touched. Repos you already initialized keep working
|
|
171
|
+
fully — the engine is vendored into each repo's `.goblin/`, so the CLI's absence removes no
|
|
172
|
+
capability (you lose the installer/upgrade/emit entry points, not the gate; see layer (c) for
|
|
173
|
+
the machine-level skills the CLI wrote).
|
|
174
|
+
|
|
175
|
+
**(b) A project's harness** — the `.goblin/` tree an install created in one repo:
|
|
176
|
+
|
|
177
|
+
goblin uninstall --target .
|
|
178
|
+
|
|
179
|
+
(equivalently `goblin install --target . --uninstall`; through the short alias:
|
|
180
|
+
`gob uninstall --target .`). The uninstall is **byte-exact**: it removes exactly the files
|
|
181
|
+
`installed.json` records — hash-compared preimages, so a file you edited after install is
|
|
182
|
+
reported and kept, never clobbered — then every directory that leaves empty. After it, the repo
|
|
183
|
+
has zero goblin files; only the project's own record (`HANDOFF.md`, `AGENTS.md`, `reviews/`, the
|
|
184
|
+
`.gitignore` block) survives, because that is the project's, not the harness's to delete. And
|
|
185
|
+
because the engine is vendored, the repo needs no gobstack installed to run this — it is
|
|
186
|
+
self-contained until the moment you remove it.
|
|
187
|
+
|
|
188
|
+
**(c) Global agent skills** — the machine-level skills an `emit --scope global` wrote outside any
|
|
189
|
+
repo:
|
|
190
|
+
|
|
191
|
+
goblin emit --undo --platform <p> --scope global
|
|
192
|
+
|
|
193
|
+
(`--undo` is the same byte-exact reversal as `--uninstall`, under its friendlier name). By hand,
|
|
194
|
+
the same job is deleting the platform's anchor entries: `~/.claude/skills/goblin-*` (and the
|
|
195
|
+
equivalents under `~/.hermes`, `~/.copilot`, `~/.cursor`, `~/.config/opencode`, `~/.codex`,
|
|
196
|
+
`~/.gemini` — `goblin doctor` lists which platforms were detected).
|
|
142
197
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
the project's record is not the harness's to delete.
|
|
198
|
+
The short version, for a full removal from a machine and its repos: (c) first, then (b) in each
|
|
199
|
+
initialized repo, then (a).
|
|
146
200
|
|
|
147
201
|
## Re-pin the referenced standard
|
|
148
202
|
|
package/bin/goblin-emit
CHANGED
|
@@ -80,6 +80,7 @@ goblin emit — per-platform emission (W4a, the seven platforms since W4b).
|
|
|
80
80
|
--skills core (the 6 procedure skills), all (every shipped skill), none (context block only)
|
|
81
81
|
--source the skills payload; default: this CLI checkout's skills/
|
|
82
82
|
--uninstall reverse every recorded write for the platform, byte-exactly, newest first
|
|
83
|
+
(--undo is the same job under its friendlier name)
|
|
83
84
|
--unshadow hermes only: remove project skills whose hash EQUALS the source;
|
|
84
85
|
refuse-and-name on any that differ (a real local edit is never auto-removed)
|
|
85
86
|
--dry-run print the full write plan (every path + the priced index size), write nothing
|
|
@@ -127,6 +128,7 @@ while [ $# -gt 0 ]; do
|
|
|
127
128
|
--target) TARGET="${2:-}"; OPT_TARGET=1; shift 2 ;;
|
|
128
129
|
--source) SRCSKILLS="${2:-}"; shift 2 ;;
|
|
129
130
|
--uninstall) UNINSTALL=1; shift ;;
|
|
131
|
+
--undo) UNINSTALL=1; shift ;;
|
|
130
132
|
--unshadow) UNSHADOW=1; shift ;;
|
|
131
133
|
--dry-run) DRYRUN=1; shift ;;
|
|
132
134
|
--strict) STRICT=1; shift ;;
|
package/bin/goblin-init
CHANGED
|
@@ -220,7 +220,7 @@ CLASS_DEFAULT=1
|
|
|
220
220
|
if [ -z "$CLASS" ]; then
|
|
221
221
|
if [ "$TTY_IN" -eq 1 ]; then
|
|
222
222
|
q ""
|
|
223
|
-
q " $
|
|
223
|
+
q " ${C_MUTED}what kind of work does this repo do?$C_RESET"
|
|
224
224
|
q " $C_TEXT 1 ▸ app shipped features, PRs, review gates ${C_DIM}(class A)$C_RESET"
|
|
225
225
|
q " $C_TEXT 2 service backend jobs, config, unattended runs ${C_DIM}(class B)$C_RESET"
|
|
226
226
|
q " $C_TEXT 3 game playable builds, perf budgets ${C_DIM}(class C)$C_RESET"
|
|
@@ -276,7 +276,7 @@ DEF_EMAIL=$( cd "$TARGET" && git config user.email 2>/dev/null )
|
|
|
276
276
|
if [ -z "$BRANCH" ] || [ -z "$EMAIL" ]; then
|
|
277
277
|
if [ "$TTY_IN" -eq 1 ]; then
|
|
278
278
|
q ""
|
|
279
|
-
q " $
|
|
279
|
+
q " ${C_MUTED}the forge contract — the identity this repo commits with (CM-01)$C_RESET"
|
|
280
280
|
ask_value "default branch" "$DEF_BRANCH"; BRANCH="${BRANCH:-$REPLY}"
|
|
281
281
|
ask_value "owner email" "$DEF_EMAIL"; EMAIL="${EMAIL:-$REPLY}"
|
|
282
282
|
else
|
|
@@ -301,14 +301,14 @@ GATE_DEFAULT="bash tests/run-tests.sh"
|
|
|
301
301
|
if [ -z "$GATE" ]; then
|
|
302
302
|
if [ "$TTY_IN" -eq 1 ]; then
|
|
303
303
|
q ""
|
|
304
|
-
q " $
|
|
304
|
+
q " ${C_MUTED}one real command that must stay green — suggested for a bash suite:$C_RESET"
|
|
305
305
|
if [ -f "$TARGET/tests/run-tests.sh" ]; then
|
|
306
306
|
q " $C_DIM$GATE_DEFAULT$C_RESET"
|
|
307
307
|
ask_value "gate command" "$GATE_DEFAULT"; GATE="$REPLY"
|
|
308
308
|
else
|
|
309
309
|
ask_value "gate command" "true"; GATE="$REPLY"
|
|
310
310
|
if [ "$GATE" = "true" ]; then
|
|
311
|
-
q " $
|
|
311
|
+
q " ${C_YELLOW}no tests/run-tests.sh found — a placeholder gate is allowed but it proves nothing; replace it in .goblin/goblin.yaml$C_RESET"
|
|
312
312
|
Q_LINES=$((Q_LINES + 1))
|
|
313
313
|
fi
|
|
314
314
|
fi
|
|
@@ -355,14 +355,14 @@ EMIT_N=$(printf '%s' "$EMIT_SELECTED" | awk 'NF' | wc -l | tr -d '[:space:]')
|
|
|
355
355
|
|
|
356
356
|
if [ "$TTY_IN" -eq 1 ] && [ -z "$EMIT" ] && [ "$YES" -eq 0 ]; then
|
|
357
357
|
q ""
|
|
358
|
-
q " $
|
|
358
|
+
q " ${C_MUTED}emit now — the skills go into:$C_RESET"
|
|
359
359
|
for p in $PLATFORMS; do
|
|
360
360
|
hit=""
|
|
361
361
|
while IFS=$' ' read -r dp _a _b _c; do [ "$dp" = "$p" ] && hit=1; done <<< "$DET_ROWS"
|
|
362
362
|
[ -n "$hit" ] || continue
|
|
363
363
|
q " $C_GREEN✔$C_RESET $C_TEXT$p$C_RESET"
|
|
364
364
|
done
|
|
365
|
-
q " $
|
|
365
|
+
q " ${C_DIM}Enter = these; or type platform names to override$C_RESET"
|
|
366
366
|
printf ' '
|
|
367
367
|
read -r REPLY_EMIT
|
|
368
368
|
ASKED_ANY=1
|
package/bin/goblin.js
CHANGED
|
@@ -13,7 +13,13 @@
|
|
|
13
13
|
// goblin doctor [...] -> bin/goblin-doctor (W4a)
|
|
14
14
|
// goblin emit [...] -> bin/goblin-emit (W4a)
|
|
15
15
|
// goblin init [...] -> bin/goblin-init (W6, the first-run wizard)
|
|
16
|
-
//
|
|
16
|
+
// goblin uninstall [--target <dir>] -> bin/goblin-install --uninstall
|
|
17
|
+
// goblin install [...] -> bin/goblin-install (the one legacy fallback)
|
|
18
|
+
// no args | -h/--help | any other unrecognized first arg
|
|
19
|
+
// -> this file's short usage, exit 2. A bare `goblin`
|
|
20
|
+
// used to fall through into the installer; a typo
|
|
21
|
+
// (`goblin inti`) silently installed too. Both now
|
|
22
|
+
// print the usage and stop.
|
|
17
23
|
//
|
|
18
24
|
// Non-negotiables (§4.3): args are passed as an ARRAY, never a shell string (no
|
|
19
25
|
// injection surface); `bash` is named explicitly (a packager stripping the
|
|
@@ -40,9 +46,58 @@ if (arg0 === "--version" || arg0 === "-V" || arg0 === "-v") {
|
|
|
40
46
|
|
|
41
47
|
const SCRIPT = { verify: "goblin-verify", bans: "goblin-bans", audit: "goblin-audit", upgrade: "goblin-upgrade", doctor: "goblin-doctor", emit: "goblin-emit", init: "goblin-init" };
|
|
42
48
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
43
|
-
|
|
49
|
+
|
|
50
|
+
// No args, a help flag, or an unrecognized first arg: short usage, exit 2. The one survivor of
|
|
51
|
+
// the old catch-all fallback is the literal `install` first arg — bare `goblin` mapped to the
|
|
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 `goblin install ...` keeps the
|
|
54
|
+
// installer; everything else stops here and names the word it did not know.
|
|
55
|
+
function usage() {
|
|
56
|
+
process.stderr.write(
|
|
57
|
+
[
|
|
58
|
+
"goblin <command>",
|
|
59
|
+
"",
|
|
60
|
+
" goblin init start here — the guided first step (detect, class, emit, first verify)",
|
|
61
|
+
" goblin verify run the rule matrix against the current repo",
|
|
62
|
+
" goblin bans run the ban list (per-pattern red lines over the source tree)",
|
|
63
|
+
" goblin audit check recorded dependency claims against live advisory feeds",
|
|
64
|
+
" goblin upgrade migrate a repo to the shared global engine at ~/.goblin/engine",
|
|
65
|
+
" goblin doctor one detection/drift run across the agent platforms",
|
|
66
|
+
" goblin emit write the skills + context block for one platform",
|
|
67
|
+
" goblin uninstall --target . remove exactly what an install wrote (preimages)",
|
|
68
|
+
"",
|
|
69
|
+
"start here: goblin init",
|
|
70
|
+
"uninstall: npm uninstall -g @techgoblin/gobstack",
|
|
71
|
+
"",
|
|
72
|
+
].join("\n"));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (cmd === undefined || cmd.startsWith("-")) {
|
|
76
|
+
usage();
|
|
77
|
+
process.exit(2);
|
|
78
|
+
}
|
|
79
|
+
if (!SCRIPT[cmd] && cmd !== "install" && cmd !== "uninstall") {
|
|
80
|
+
process.stderr.write(`goblin: unrecognized command: ${cmd}\n\n`);
|
|
81
|
+
usage();
|
|
82
|
+
process.exit(2);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let target;
|
|
86
|
+
let extra = [];
|
|
87
|
+
if (cmd === "install") {
|
|
88
|
+
target = "goblin-install"; // the one legacy fallback, kept verbatim
|
|
89
|
+
} else if (cmd === "uninstall") {
|
|
90
|
+
// `goblin uninstall --target <dir>` routes into the installer's uninstall job — the shape
|
|
91
|
+
// docs/GUIDE.md and README already promise. `--uninstall` is appended FIRST so the user's
|
|
92
|
+
// own `--target <dir>` and options still parse, and a stray literal `--uninstall` cannot
|
|
93
|
+
// appear twice.
|
|
94
|
+
target = "goblin-install";
|
|
95
|
+
extra = ["--uninstall"];
|
|
96
|
+
} else {
|
|
97
|
+
target = SCRIPT[cmd];
|
|
98
|
+
}
|
|
44
99
|
const file = path.join(__dirname, "..", "bin", target);
|
|
45
100
|
// execPath-independent: call bash explicitly so Windows-WSL/Git-Bash works and no
|
|
46
101
|
// shebang resolution is needed.
|
|
47
|
-
const r = spawnSync("bash", [file, ...rest], { stdio: "inherit" });
|
|
102
|
+
const r = spawnSync("bash", [file, ...extra, ...rest], { stdio: "inherit" });
|
|
48
103
|
process.exit(r.status ?? 2);
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@techgoblin/gobstack",
|
|
3
|
-
"version": "0.4.4-beta.
|
|
3
|
+
"version": "0.4.4-beta.3",
|
|
4
4
|
"description": "Agent-discipline toolkit: one verify command, an enforcement matrix, and LIMITS. bash engine, npm shim.",
|
|
5
5
|
"bin": {
|
|
6
|
-
"goblin": "bin/goblin.js"
|
|
6
|
+
"goblin": "bin/goblin.js",
|
|
7
|
+
"gob": "bin/goblin.js"
|
|
7
8
|
},
|
|
8
9
|
"publishConfig": {
|
|
9
10
|
"access": "public"
|