@techgoblin/gobstack 0.6.0-alpha.1 → 0.6.0-alpha.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/CHANGELOG.md CHANGED
@@ -3,7 +3,23 @@
3
3
  One line per released version. `goblin-install --upgrade` prints the delta between the
4
4
  version recorded in a target's `.goblin/installed.json` and the source `VERSION`.
5
5
 
6
- ## 0.6.0-alpha.1
6
+ ## Unreleased (feat/extras-catalogue)
7
+
8
+ - **`gob extras`: the curated catalogue (the surface is seven verbs).** The catalogue
9
+ (`extras-catalogue/catalogue.tsv`, 41 researched rows in 17 columns: 18 RECOMMEND incl.
10
+ `impeccable`, MAYBE/SKIP carried with verdicts) is browsed (`list [category]`, `show <id>`)
11
+ and installed (`install <id...>` — skills per the sync_platforms pattern or `.gob/extras/`,
12
+ mcp rows as an ask-once `.mcp.json` merge, workflows into `.gob/playbooks/`). The catalogue
13
+ is the ALLOWLIST: an unknown id or a SKIP verdict refuses; installs copy from the vendored
14
+ `extras-catalogue/payload/` — no network, no download path.
15
+ - **`gob init` suggests extras; it never installs them.** The AGENT BRIEF carries
16
+ `catalogue_suggestions` (matches-column rows; RECOMMEND pre-ticked, MAYBE visible and
17
+ unticked, SKIP never) and the `discover_extras` tier: web-found candidates proposed under a
18
+ fixed schema (`source_url`/`license`/`rationale`/`suggested_kind`), inert until the curator
19
+ promotes them into catalogue.tsv by PR or edit. `--write` honours only the proposal's TICKED
20
+ rows, through `gob extras install` itself — one allowlist, one code path.
21
+
22
+ ## 0.6.0-alpha.2
7
23
 
8
24
  The v2 breaking release: rules-first. The rules a repo is judged by move from a config file the
9
25
  installer generated into the file a new session already reads, the folder the engine is vendored
package/README.md CHANGED
@@ -76,7 +76,7 @@ The measurement and the vacuous-pass reading are in `docs/CONTRACTS.md`.
76
76
 
77
77
  ## The `gob` CLI
78
78
 
79
- The v2 surface is six verbs. Everything else — audit, doctor, emit/sync, upgrade, install —
79
+ The v2 surface is seven verbs. Everything else — audit, doctor, emit/sync, upgrade, install —
80
80
  is unwired in this alpha: the shim refuses the verb by name and prints the usage.
81
81
 
82
82
  | command | what it does |
@@ -85,6 +85,7 @@ is unwired in this alpha: the shim refuses the verb by name and prints the usage
85
85
  | `gob map` | the feature-map prompt + schema (`--heuristic [target]` runs the starter scanner); `--write <dir> [--force]` validates an agent-written map and installs it; never clobbers — an existing map refuses until `--force` |
86
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 |
87
87
  | `gob bans` | run the ban list (per-pattern red lines over the source tree) |
88
+ | `gob extras` | the curated extras catalogue: `list [category]` / `show <id>` / `install <id...> [­-target <dir>] [--platform <id>] [--with-mcp-config]` — init only SUGGESTS; installs come from the catalogue (the allowlist rule), never the web |
88
89
  | `gob mcp` | serve the harness to your coding agent over MCP stdio — three tools (`gob_verify`, `gob_map_status`, `gob_init_status`), local only, no SDK, no network |
89
90
  | `gob uninstall` | remove everything an install wrote, byte-exactly (`gob install --target <dir> --uninstall` is the same job) |
90
91
 
@@ -128,6 +129,62 @@ checks and the `.gitignore` block; no skills directory, and no files belonging t
128
129
  agent. The proposal schema's `sync_platforms:` line is where the platform choice is recorded
129
130
  when that surface lands.
130
131
 
132
+ ## Extras: the curated catalogue
133
+
134
+ `gob extras` is the catalogue browser + installer for curated third-party agent skills, MCP
135
+ servers and workflows — matched to your stack, suggested at init, installed only on an
136
+ explicit command. The source of truth is `extras-catalogue/catalogue.tsv`, reviewed and
137
+ carried by the curator (one row per candidate: source repo, license, stars, last push, which
138
+ stacks it `matches`, what it `conflicts` with, and a verdict).
139
+
140
+ gob extras list [category] # the catalogue, grouped: frontend | testing | game | node
141
+ gob extras show <id> # one row, every field, + the install hint
142
+ gob extras install <id...> # the only way an extra lands in your repo
143
+ [--target <dir>] [--platform <id>] [--with-mcp-config] [--dry-run]
144
+
145
+ The verdict decides what each row can do: `RECOMMEND` rows are pre-ticked when `gob init`
146
+ suggests, `MAYBE` rows are listed but never pre-ticked, and a `SKIP` row refuses install —
147
+ it stays visible so the curation history is readable, but the curator declined it.
148
+
149
+ **The license gate is hard.** A verdict alone never installs anything: every row carries a
150
+ `license_status` (verified | pending | proprietary-confirmed-ok) and the date it was checked.
151
+ Only `verified` installs. A row whose license is not yet resolved carries verdict
152
+ `LICENSE-PENDING` — it is never pre-ticked at init, and `gob extras install` refuses it with
153
+ `license pending verification` until a curator verifies the license and flips the row's data
154
+ in `catalogue.tsv`. The gate reads the data, not the code: flipping a row installable is a
155
+ catalogue edit (a new `license_status` + `license_check_date`), never an engine change.
156
+
157
+ **The catalogue is the allowlist.** An id that is not a row in `catalogue.tsv` is refused by
158
+ name; nothing is ever installed from a URL, a repo name, or a web search. Installs are
159
+ offline by contract: skill and workflow rows copy from the vendored payload the curator
160
+ maintains under `extras-catalogue/payload/` (a row with no payload refuses, with the named
161
+ fix — it is never downloaded); `mcp` rows print the `mcp.json` snippet, and
162
+ `--with-mcp-config` merges it into the target's `.mcp.json` — ask-once, never overwriting an
163
+ entry you customized. A `--platform <id>` skill install copies into that platform's project
164
+ skills root (the same paths the sync adapters record); with no `--platform`, skills land in
165
+ the repo-neutral `.gob/extras/<skill-name>/`, and workflow rows copy into
166
+ `.gob/playbooks/<id>/`. An existing file with different content is never clobbered.
167
+
168
+ **`gob init` suggests; it never installs.** The AGENT BRIEF carries a
169
+ `catalogue_suggestions` section: rows whose `matches:` column names the stack the scan found,
170
+ RECOMMEND rows pre-ticked and MAYBE rows visible but unticked. The heuristic fallback
171
+ (`--heuristic`) suggests from the same `matches:` column. `init --write` honours the
172
+ proposal's TICKED rows and only those — unticked MAYBE lines install nothing — and it does
173
+ the honouring by calling `gob extras install` itself, so the allowlist, the verdict refusals
174
+ and the payload contract are one code path.
175
+
176
+ **The `discover_extras` tier is proposals only.** The brief also asks the agent to search the
177
+ web — with its own tools — for skills/MCPs/workflows that match the stack and are NOT in the
178
+ catalogue, and to propose them under `discover_extras:` with a live `source_url`, the
179
+ `license` found, and a one-line `rationale`. Nothing auto-installs from discovery: a human
180
+ reviews the proposals, and the curator promotes accepted discoveries into `catalogue.tsv` by
181
+ PR or direct edit. Until a row exists in the catalogue, `gob extras install` refuses it.
182
+
183
+ **How a row is added** (the curation surface): propose it by PR or edit
184
+ `extras-catalogue/catalogue.tsv` directly — the columns are the row schema
185
+ `gob extras show` prints. A row lands with a verdict, a reviewer and a date; the tests pin
186
+ the file's shape so a malformed row cannot ship.
187
+
131
188
  ## What it is not
132
189
 
133
190
  Not a rules document (every rule carries a runnable check or is explicitly counted as
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.0-alpha.1
1
+ 0.6.0-alpha.3
package/bin/goblin CHANGED
@@ -5,6 +5,8 @@
5
5
  # gob map [target] [--agent | --heuristic] [--write <dir>] [--force]
6
6
  # gob verify [--only <id[,id...]>] [--json] [--list] [--source <path>]
7
7
  # gob bans [--only <id[,id...]>] [--list]
8
+ # gob extras list [category] | show <id> | install <id...> [--target <dir>]
9
+ # [--platform <id>] [--with-mcp-config] [--dry-run]
8
10
  # gob mcp serve the harness over MCP stdio
9
11
  # gob uninstall --target <dir>
10
12
  # gob --version
@@ -48,6 +50,7 @@ gob — the gobstack command line.
48
50
  gob map [target] [--agent | --heuristic] [--write <dir>] [--force]
49
51
  gob verify [--only <id[,id...]>] [--json] [--list] [--source <path>]
50
52
  gob bans [--only <id[,id...]>] [--list]
53
+ gob extras list [category] | show <id> | install <id...> # the curated extras catalogue
51
54
  gob mcp serve the harness to your agent over MCP stdio (verify/map/init tools)
52
55
  gob uninstall --target <dir>
53
56
  gob --version
@@ -97,6 +100,12 @@ case "$CMD" in
97
100
  bans)
98
101
  exec bash "$SRC/bin/goblin-bans" "$@"
99
102
  ;;
103
+ extras)
104
+ # The curated catalogue browser + installer (bin/goblin-extras): list/show/install
105
+ # over extras-catalogue/catalogue.tsv. The catalogue is the ALLOWLIST: nothing is
106
+ # ever installed that a catalogue row does not name, and `gob init` only suggests.
107
+ exec bash "$SRC/bin/goblin-extras" "$@"
108
+ ;;
100
109
  mcp)
101
110
  # The MCP stdio server (bin/goblin-mcp.js): JSON-RPC 2.0 over stdin/stdout, a LOCAL
102
111
  # tool server over the vendored harness — no API call, no socket. node is the same
package/bin/goblin-audit CHANGED
@@ -23,7 +23,7 @@
23
23
  # because an empty record reads to SC-07 as "clean" and that would be a fabricated pass
24
24
  set -uo pipefail
25
25
 
26
- GOBLIN_AUDIT_VERSION="0.6.0-alpha.1"
26
+ GOBLIN_AUDIT_VERSION="0.6.0-alpha.3"
27
27
 
28
28
  usage() { sed -n '2,/^$/p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
29
29
 
package/bin/goblin-bans CHANGED
@@ -28,7 +28,7 @@
28
28
 
29
29
  set -uo pipefail
30
30
 
31
- GOBLIN_BANS_VERSION="0.6.0-alpha.1"
31
+ GOBLIN_BANS_VERSION="0.6.0-alpha.3"
32
32
  SELF_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
33
33
  # shellcheck source=goblin-lib.sh
34
34
  . "$SELF_DIR/goblin-lib.sh"
@@ -0,0 +1,360 @@
1
+ #!/usr/bin/env bash
2
+ # goblin-extras — `gob extras`: the curated catalogue browser + installer (v2 W-extras).
3
+ #
4
+ # gob extras list [category] the catalogue, grouped by category
5
+ # gob extras show <id> one row, every field, + the install hint
6
+ # gob extras install <id...> [--target <dir>] [--platform <id>]
7
+ # [--with-mcp-config] [--dry-run]
8
+ #
9
+ # The ALLOWLIST RULE (non-negotiable): only ids that exist in catalogue.tsv are ever
10
+ # installed. An id the catalogue does not carry is refused by name — there is no path from
11
+ # a URL or a repo name on this command to a file write. `gob init` SUGGESTS rows and never
12
+ # auto-installs; a human (or the proposal the human approves) names ids explicitly, and
13
+ # the curator maintains the catalogue (add a row = PR or edit catalogue.tsv).
14
+ #
15
+ # Verdicts: RECOMMEND rows install; MAYBE rows install (an explicit choice, never
16
+ # pre-ticked at init); SKIP rows refuse — the curator's verdict is the contract.
17
+ # THE LICENSE GATE (W-license, hard): verdict RECOMMEND|MAYBE alone is not enough —
18
+ # a row installs only when its catalogue license_status is `verified`. Rows with an
19
+ # unresolved license carry verdict LICENSE-PENDING (never pre-ticked at init) and
20
+ # refuse install with `license pending verification` until the curator flips the
21
+ # DATA (license_status + license_check_date), never this file.
22
+ #
23
+ # NO NETWORK. Skill and workflow installs copy from a VENDORED PAYLOAD the curator
24
+ # maintains under extras-catalogue/payload/<id>/ (payload/README.md is the layout
25
+ # contract); a row whose payload is absent is refused with the named fix, never
26
+ # downloaded. MCP installs are a printed mcp.json snippet plus — with
27
+ # --with-mcp-config — a merge into the target's .mcp.json that never overwrites an
28
+ # existing server entry (the ask-once rule .mcp.json has always had).
29
+ #
30
+ # Skill destinations follow the sync_platforms pattern: --platform <adapter-id> copies
31
+ # each vendored skill dir under that adapter's project skills root (read from
32
+ # adapters/<id>/adapter.tsv, column skills_path_project); with no --platform the skill
33
+ # lands in the repo-neutral .gob/extras/<skill-name>/ (standalone repos). Workflows copy
34
+ # the payload into .gob/playbooks/<id>/.
35
+ #
36
+ # Exit codes: 0 ok | 1 a refusal (named path + fix) | 2 bad input.
37
+ # No npm, no jq, no yq, no network. bash/awk/sed/grep only (python3 only for the
38
+ # .mcp.json merge, where a hand-rolled JSON edit would corrupt files it must not).
39
+
40
+ set -uo pipefail
41
+
42
+ SELF_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
43
+ SRC=$(cd "$SELF_DIR/.." && pwd)
44
+ # shellcheck source=goblin-lib.sh
45
+ . "$SELF_DIR/goblin-lib.sh"
46
+
47
+ g_info() { printf '%s\n' "$*"; }
48
+
49
+ usage() { sed -n '2,/^$/p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
50
+
51
+ # ---- the catalogue source (overridable for tests) ------------------------------
52
+ # The default is the shipped catalogue; GOB_EXTRAS_CATALOGUE points a test at a fixture.
53
+ # Same for the payload root: a test vendors a fixture payload and aims the installer at it.
54
+ CATALOGUE="${GOB_EXTRAS_CATALOGUE:-$SRC/extras-catalogue/catalogue.tsv}"
55
+ PAYLOAD="${GOB_EXTRAS_PAYLOAD:-$SRC/extras-catalogue/payload}"
56
+
57
+ die() { g_err "extras: $*"; exit "${2:-2}"; }
58
+
59
+ # catalogue_row <id> — the whole data row (tab-separated), empty when absent.
60
+ catalogue_row() {
61
+ awk -F'\t' -v id="$1" 'NR>1 && $1==id { print; exit }' "$CATALOGUE"
62
+ }
63
+
64
+ # catalogue_field <row> <n> — the nth tab field of a row.
65
+ catalogue_field() { printf '%s' "$1" | awk -F'\t' -v n="$2" '{ print $n }'; }
66
+
67
+ # catalogue_ids — every id, file order.
68
+ catalogue_ids() { awk -F'\t' 'NR>1 && $1!="" { print $1 }' "$CATALOGUE"; }
69
+
70
+ # category_order — the categories in catalogue file order of first appearance.
71
+ category_order() { awk -F'\t' 'NR>1 && !seen[$2]++ { print $2 }' "$CATALOGUE"; }
72
+
73
+ # ---- adapters (the platform enum is data, never literals) ----------------------
74
+ # MD-01: agent-family names are read from adapters/<id>/adapter.tsv at run time, never
75
+ # written here — the same discipline the sync engine's assembled platform list keeps.
76
+ adapter_ids() {
77
+ for t in "$SRC"/adapters/*/adapter.tsv; do
78
+ [ -f "$t" ] || continue
79
+ awk -F'\t' '!/^#/ && NF>1 && $1!="id" { print $1 }' "$t"
80
+ done
81
+ }
82
+
83
+ # adapter_skills_root <id> — the PROJECT skills root (the pattern with its
84
+ # `<name>/SKILL.md` tail stripped), one dot-dir per agent family. Read at run time;
85
+ # the family names live in the adapter tables, never here (the MD-01 discipline).
86
+ adapter_skills_root() {
87
+ local t="$SRC/adapters/$1/adapter.tsv" pat
88
+ [ -f "$t" ] || return 0
89
+ pat=$(awk -F'\t' '!/^#/ && NF>1 && $1!="id" { print $4 }' "$t")
90
+ [ -n "$pat" ] || return 0
91
+ printf '%s\n' "${pat%%<name>*}"
92
+ }
93
+
94
+ # ---- list ----------------------------------------------------------------------
95
+ cmd_list() {
96
+ local want="${1:-}"
97
+ [ -f "$CATALOGUE" ] || die "catalogue not found: $CATALOGUE" 2
98
+ if [ -n "$want" ]; then
99
+ # An unknown category is bad input, not an empty table: a typo must not read as
100
+ # "the catalogue has nothing here". Captured, not piped: under pipefail a `grep -q`
101
+ # match closes the pipe early and SIGPIPE-kills the producer (pipeline 141), which
102
+ # would read as "unknown category" for a category the catalogue carries.
103
+ CATS=$(category_order)
104
+ printf '%s\n' "$CATS" | grep -qx "$want" || die "unknown category '$want' (want one of: $(printf '%s' "$CATS" | tr '\n' ' '))" 2
105
+ fi
106
+ local total
107
+ total=$(catalogue_ids | wc -l | tr -d ' ')
108
+ g_info "gob extras — the curated catalogue ($total rows; RECOMMEND pre-ticks at init, MAYBE is visible but never pre-ticked, SKIP refuses install; LICENSE-PENDING rows refuse install until their license is verified)"
109
+ g_info "install: gob extras install <id...> · one row: gob extras show <id>"
110
+ local cat
111
+ for cat in $(category_order); do
112
+ [ -z "$want" ] || [ "$cat" = "$want" ] || continue
113
+ printf '\n[%s]\n' "$cat"
114
+ printf ' %-34s %-8s %-7s %-24s %-10s %-16s %s\n' "id" "kind" "stars" "license" "verdict" "lic-status" "matches"
115
+ awk -F'\t' -v cat="$cat" 'NR>1 && $2==cat {
116
+ printf " %-34s %-8s %-7s %-24s %-10s %-16s %s\n", $1, $3, $9, $8, $15, $18, $11
117
+ }' "$CATALOGUE"
118
+ done
119
+ return 0
120
+ }
121
+
122
+ # ---- show ----------------------------------------------------------------------
123
+ cmd_show() {
124
+ local id="${1:-}"
125
+ [ -n "$id" ] || die "show: an id is required (gob extras list)" 2
126
+ [ -f "$CATALOGUE" ] || die "catalogue not found: $CATALOGUE" 2
127
+ local row
128
+ row=$(catalogue_row "$id")
129
+ [ -n "$row" ] || die "unknown id '$id' — gob extras list names the catalogue" 2
130
+ local labels=(id category kind name source_repo path_within_repo has_skill_md license
131
+ stars last_push matches conflicts requires install_hint verdict
132
+ reviewed_by reviewed_date license_status license_check_date)
133
+ local i
134
+ for i in $(seq 1 19); do
135
+ printf '%-18s %s\n' "${labels[$((i-1))]}:" "$(catalogue_field "$row" "$i")"
136
+ done
137
+ return 0
138
+ }
139
+
140
+ # ---- install -------------------------------------------------------------------
141
+ TARGET="" PLATFORM="" WITH_MCP=0 DRYRUN=0 IDS=()
142
+ parse_install() {
143
+ while [ $# -gt 0 ]; do
144
+ case "$1" in
145
+ --target) TARGET="${2:-}"; shift 2 ;;
146
+ --platform) PLATFORM="${2:-}"; shift 2 ;;
147
+ --with-mcp-config) WITH_MCP=1; shift ;;
148
+ --dry-run) DRYRUN=1; shift ;;
149
+ --yes) shift ;; # accepted for pipe parity; install is already explicit
150
+ -h|--help) usage; exit 0 ;;
151
+ -*) die "install: unknown option: $1" 2 ;;
152
+ *) IDS+=("$1"); shift ;;
153
+ esac
154
+ done
155
+ [ "${#IDS[@]}" -gt 0 ] || die "install: at least one id is required (gob extras list)" 2
156
+ [ -f "$CATALOGUE" ] || die "catalogue not found: $CATALOGUE" 2
157
+ TARGET="${TARGET:-$PWD}"
158
+ TARGET=$(g_expand_tilde "$TARGET")
159
+ [ -d "$TARGET" ] || die "--target is not a directory: $TARGET" 2
160
+ TARGET=$(cd "$TARGET" && pwd)
161
+ if [ -n "$PLATFORM" ]; then
162
+ # Captured, not piped (see cmd_list: pipefail + grep -q closes the pipe early and
163
+ # SIGPIPE-kills the producer — a valid platform id read as unknown).
164
+ ADAPTERS=$(adapter_ids)
165
+ printf '%s\n' "$ADAPTERS" | grep -qx "$PLATFORM" \
166
+ || die "unknown --platform '$PLATFORM' (the adapters this checkout ships: $(printf '%s' "$ADAPTERS" | tr '\n' ' '))" 2
167
+ fi
168
+ }
169
+
170
+ # resolve_one <id> — validates against the ALLOWLIST, the curator's verdicts and the
171
+ # LICENSE GATE; prints the row. Every install path goes through here: no row, no
172
+ # install; no verified license, no install. That is the whole allowlist rule plus the
173
+ # hard gate, in one gate.
174
+ resolve_one() {
175
+ local id="$1" row verdict lic
176
+ row=$(catalogue_row "$id")
177
+ # Captured, not piped (pipefail + grep -q early-exit trap — see cmd_list).
178
+ if [ -n "$row" ]; then
179
+ verdict=$(catalogue_field "$row" 15)
180
+ lic=$(catalogue_field "$row" 18)
181
+ else
182
+ die "unknown id '$id' — nothing outside catalogue.tsv is ever installed (gob extras list names the allowlist)" 2
183
+ fi
184
+ case "$verdict" in
185
+ RECOMMEND|MAYBE|LICENSE-PENDING) ;;
186
+ SKIP) die "id '$id' carries verdict SKIP — the curator declined it; it stays visible in the catalogue but refuses install" 1 ;;
187
+ *) die "id '$id' carries verdict '$verdict' — not an installable verdict (RECOMMEND|MAYBE|SKIP|LICENSE-PENDING)" 2 ;;
188
+ esac
189
+ # THE LICENSE GATE (hard, data-driven): a row whose catalogue says its license is
190
+ # not verified never installs — the verdict alone is not consulted. A curator flips
191
+ # a row to installable by fixing the data (license_status=verified + a check date),
192
+ # never by touching this file.
193
+ case "$lic" in
194
+ verified) ;;
195
+ pending|proprietary-confirmed-ok|"")
196
+ die "id '$id' refuses install: license pending verification — see catalogue.tsv license_check_date" 1 ;;
197
+ *)
198
+ die "id '$id' carries license_status '$lic' — not a known status (verified|pending|proprietary-confirmed-ok)" 2 ;;
199
+ esac
200
+ printf '%s\n' "$row"
201
+ }
202
+
203
+ # copy_tree <src> <dst> — byte-copy with the installer's ownership gate: an existing
204
+ # file is left alone when identical (unchanged) and REFUSED when it differs (a file the
205
+ # project wrote is never clobbered silently). Returns 0 ok, 1 on a refusal (named).
206
+ COPY_REFUSALS=0
207
+ copy_tree() {
208
+ local src="${1%/}" dst="$2" f rel sd dd
209
+ [ -d "$src" ] || die "payload missing: $src" 2
210
+ while IFS= read -r f; do
211
+ rel="${f#"$src"/}"
212
+ dd="$dst/$rel"
213
+ if [ "$DRYRUN" -eq 1 ]; then
214
+ g_info "plan: ${dd#"$TARGET"/}"
215
+ continue
216
+ fi
217
+ mkdir -p "$(dirname "$dd")" || die "cannot create $(dirname "$dd")" 1
218
+ if [ -f "$dd" ]; then
219
+ if [ "$(g_sha256_file "$dd")" = "$(g_sha256_file "$f")" ]; then
220
+ g_info "unchanged ${dd#"$TARGET"/}"
221
+ else
222
+ g_err "refusing to overwrite ${dd#"$TARGET"/}: it exists with different content and goblin-stack did not install it — remove or rename it first"
223
+ COPY_REFUSALS=$((COPY_REFUSALS + 1))
224
+ fi
225
+ continue
226
+ fi
227
+ cp "$f" "$dd" || die "cannot write $dd" 1
228
+ g_info "wrote ${dd#"$TARGET"/}"
229
+ done < <(find "$src" -type f | sort)
230
+ return 0
231
+ }
232
+
233
+ # install_skill <row> — copy the payload's skill dirs into the platform skills root
234
+ # (per sync_platforms) or .gob/extras/ (standalone).
235
+ install_skill() {
236
+ local row="$1" id name src_root dst_root d n=0
237
+ id=$(catalogue_field "$row" 1)
238
+ src_root="$PAYLOAD/$id"
239
+ [ -d "$src_root" ] \
240
+ || die "no vendored payload for '$id' at $src_root — extras install needs no network: vendor the accepted skill dirs there first (see extras-catalogue/payload/README.md)" 2
241
+ if [ -n "$PLATFORM" ]; then
242
+ dst_root="$TARGET/$(adapter_skills_root "$PLATFORM")"
243
+ dst_root="${dst_root%/}/"
244
+ else
245
+ dst_root="$TARGET/.gob/extras"
246
+ fi
247
+ [ -n "$(adapter_skills_root "$PLATFORM")" ] || [ -z "$PLATFORM" ] \
248
+ || die "adapter '$PLATFORM' carries no project skills path" 2
249
+ for d in "$src_root"/*/; do
250
+ [ -d "$d" ] || continue
251
+ name=$(basename "$d")
252
+ n=$((n + 1))
253
+ g_info "extras: skill $name -> ${dst_root#"$TARGET"/}$name/"
254
+ copy_tree "$d" "$dst_root/$name"
255
+ done
256
+ [ "$n" -gt 0 ] || die "payload for '$id' holds no skill dirs ($src_root/*/ with SKILL.md each) — see extras-catalogue/payload/README.md" 2
257
+ return 0
258
+ }
259
+
260
+ # install_workflow <row> — copy the payload into .gob/playbooks/<id>/.
261
+ install_workflow() {
262
+ local row="$1" id src dst
263
+ id=$(catalogue_field "$row" 1)
264
+ src="$PAYLOAD/$id"
265
+ [ -d "$src" ] \
266
+ || die "no vendored payload for '$id' at $src — vendor the playbook files there first (see extras-catalogue/payload/README.md)" 2
267
+ dst="$TARGET/.gob/playbooks/$id"
268
+ g_info "extras: workflow $id -> ${dst#"$TARGET"/}/"
269
+ copy_tree "$src" "$dst"
270
+ return 0
271
+ }
272
+
273
+ # install_mcp <row> — print the mcp.json snippet; --with-mcp-config merges it into
274
+ # the target's .mcp.json. The merge NEVER overwrites: an existing entry for the same
275
+ # server name must match the generated bytes (no-op) or the merge refuses (ask-once).
276
+ install_mcp() {
277
+ local row="$1" id hint cmd rest first_arg
278
+ id=$(catalogue_field "$row" 1)
279
+ hint=$(catalogue_field "$row" 14)
280
+ case "$hint" in
281
+ npx\ *|npm\ exec\ *)
282
+ read -r cmd rest <<< "$hint"
283
+ first_arg="$rest" ;;
284
+ *)
285
+ if [ "$WITH_MCP" -eq 1 ]; then
286
+ die "id '$id' carries install_hint '$hint' — only an 'npx <package>' hint can be merged into .mcp.json; follow the row's own hint by hand" 1
287
+ fi
288
+ g_info "extras: mcp $id — install per the row's own hint: $hint (no .mcp.json snippet is generated for it)"
289
+ return 0 ;;
290
+ esac
291
+ local snippet
292
+ snippet=$(python3 -c 'import json,sys; print(json.dumps({"mcpServers": {sys.argv[1]: {"command": sys.argv[2], "args": sys.argv[3].split()}}}, separators=(",",":")))' "$id" "$cmd" "$first_arg")
293
+ g_info "extras: mcp $id — mcp.json snippet:"
294
+ g_info " $snippet"
295
+ [ "$WITH_MCP" -eq 1 ] || return 0
296
+ [ "$DRYRUN" -eq 0 ] || { g_info "plan: merge $id into ${TARGET#.}/.mcp.json"; return 0; }
297
+ MERGE_OUT=$(MCP_ID="$id" MCP_SNIPPET="$snippet" python3 - "$TARGET/.mcp.json" <<'PYEOF'
298
+ import json, os, sys
299
+ path = sys.argv[1]
300
+ mcp_id = os.environ["MCP_ID"]
301
+ snippet = json.loads(os.environ["MCP_SNIPPET"])
302
+ server = snippet["mcpServers"][mcp_id]
303
+ data = {}
304
+ if os.path.exists(path):
305
+ raw = open(path).read().strip()
306
+ if raw:
307
+ try:
308
+ data = json.loads(raw)
309
+ except ValueError:
310
+ sys.stderr.write("extras: refusing %s: it is not valid JSON - edit it by hand\n" % path)
311
+ sys.exit(1)
312
+ servers = data.setdefault("mcpServers", {})
313
+ if mcp_id in servers:
314
+ if servers[mcp_id] == server:
315
+ print("extras: unchanged %s (already carries the %s registration)" % (path, mcp_id))
316
+ sys.exit(0)
317
+ sys.stderr.write("extras: refusing to overwrite the existing %s entry in %s - an mcp registration you customized is the project's (ask-once)\n" % (mcp_id, path))
318
+ sys.exit(1)
319
+ servers[mcp_id] = server
320
+ with open(path, "w") as fh:
321
+ json.dump(data, fh, separators=(",", ":"))
322
+ fh.write("\n")
323
+ print("extras: merged %s into %s" % (mcp_id, path))
324
+ PYEOF
325
+ )
326
+ local rc=$?
327
+ [ -n "$MERGE_OUT" ] && printf '%s\n' "$MERGE_OUT"
328
+ [ "$rc" -eq 0 ] || exit 1
329
+ return 0
330
+ }
331
+
332
+ cmd_install() {
333
+ parse_install "$@"
334
+ [ "$DRYRUN" -eq 0 ] || g_info "extras: --dry-run (nothing is written)"
335
+ local id row
336
+ for id in "${IDS[@]}"; do
337
+ row=$(resolve_one "$id") || exit $?
338
+ case "$(catalogue_field "$row" 3)" in
339
+ skill) install_skill "$row" ;;
340
+ workflow) install_workflow "$row" ;;
341
+ mcp) install_mcp "$row" ;;
342
+ *) die "row '$id' carries unknown kind '$(catalogue_field "$row" 3)' (want skill|mcp|workflow)" 2 ;;
343
+ esac
344
+ done
345
+ [ "$COPY_REFUSALS" -eq 0 ] || die "$COPY_REFUSALS file(s) refused (named above) — nothing overwritten" 1
346
+ return 0
347
+ }
348
+
349
+ # ---- dispatch ------------------------------------------------------------------
350
+ CMD="${1:-}"
351
+ [ -n "$CMD" ] || { usage; exit 2; }
352
+ shift
353
+ case "$CMD" in
354
+ list) cmd_list "$@" ;;
355
+ show) cmd_show "$@" ;;
356
+ install) cmd_install "$@" ;;
357
+ -h|--help|help) usage; exit 0 ;;
358
+ *) g_err "extras: unknown subcommand: $CMD"; usage >&2; exit 2 ;;
359
+ esac
360
+ exit 0
package/bin/goblin-init CHANGED
@@ -37,6 +37,8 @@ SRC=$(cd "$SELF_DIR/.." && pwd)
37
37
  # shellcheck source=goblin-lib.sh
38
38
  . "$SELF_DIR/goblin-lib.sh"
39
39
  VERSION=$(cat "$SRC/VERSION" 2>/dev/null || printf 'unknown')
40
+ # The extras catalogue (overridable for tests, the same env var goblin-extras reads).
41
+ CATALOGUE_TSV="${GOB_EXTRAS_CATALOGUE:-$SRC/extras-catalogue/catalogue.tsv}"
40
42
 
41
43
  g_info() { printf '%s\n' "$*"; }
42
44
 
@@ -61,6 +63,7 @@ done
61
63
  for dep in git awk sed grep; do
62
64
  command -v "$dep" >/dev/null 2>&1 || { g_err "missing dependency: $dep"; exit 2; }
63
65
  done
66
+ command -v python3 >/dev/null 2>&1 || { g_err "missing dependency: python3 (the extras .mcp.json merge needs it)"; exit 2; }
64
67
  [ -f "$SRC/VERSION" ] || { g_err "no VERSION at $SRC — is this a gobstack checkout?"; exit 2; }
65
68
  TARGET="${TARGET:-$PWD}"
66
69
  TARGET=$(g_expand_tilde "$TARGET")
@@ -120,7 +123,47 @@ back one proposal file. Do this now:
120
123
  - gate_commit_cmd: ONE command that proves the repo is healthy and must stay green
121
124
  (a real test/lint command from package.json — never a placeholder)
122
125
 
123
- 3. WRITE the proposal file exactly in the SCHEMA below, at a path you choose (for
126
+ 3. THE CATALOGUE — `catalogue_suggestions` (init SUGGESTS, it never installs):
127
+
128
+ Read the bundled catalogue (`gob extras list` shows it; the file is
129
+ `extras-catalogue/catalogue.tsv` in the package). From it, build the
130
+ `catalogue_suggestions:` list for this repo:
131
+
132
+ - rows whose `matches:` column names this repo's detected stack (the words you
133
+ found in step 1 — react, vue, playwright, godot, unity, node, postgres, …);
134
+ - RECOMMEND rows are pre-ticked (write `- id` with `[x]`); MAYBE rows are
135
+ listed but NOT pre-ticked (`[ ]`) — they are visible choices, never defaults;
136
+ - a row whose `conflicts:` names an id you also picked: pick at most one of them;
137
+ - SKIP rows are never suggested and refuse install; leave them out entirely.
138
+
139
+ Record the picks in the proposal's summary section under `catalogue_suggestions:`
140
+ (one `- [x] <id> # <why>` line each). NOTHING installs at init: installing is a
141
+ separate, explicit step the human runs —
142
+
143
+ npx @techgoblin/gobstack extras install <id...>
144
+
145
+ 4. DISCOVER — `discover_extras` (proposals only, nothing auto-installs):
146
+
147
+ ALSO search the web with your own tools for agent skills / MCP servers / workflows
148
+ that match this repo's stack and are NOT in the bundled catalogue. For each
149
+ discovery, propose one entry in the summary under `discover_extras:` with exactly
150
+ these keys:
151
+
152
+ discover_extras:
153
+ - id: <short-kebab-id>
154
+ source_url: <the repo or docs URL you actually fetched>
155
+ license: <spdx or "unknown — verify">
156
+ rationale: <one line — what it adds to THIS repo>
157
+ suggested_kind: <skill|mcp|workflow>
158
+
159
+ Rules: verify the URL is live before proposing it; state the license you found;
160
+ never propose anything you did not actually read. Proposals are INERT — nothing
161
+ auto-installs from discovery, and no discovery reaches this repo from the CLI.
162
+ A human reviews the proposals, then the curator promotes accepted discoveries
163
+ into catalogue.tsv by PR or direct edit. Until a row exists in the
164
+ catalogue, `gob extras install` refuses it — the catalogue is the allowlist.
165
+
166
+ 5. WRITE the proposal file exactly in the SCHEMA below, at a path you choose (for
124
167
  example .gob-init-proposal.md), then run:
125
168
 
126
169
  npx @techgoblin/gobstack init --write <proposal-path>
@@ -145,6 +188,15 @@ sync_platforms: <optional array — the agent platform ids the sync reads>
145
188
 
146
189
  - <one bullet per decision: what you scanned, what you chose, why>
147
190
  - <framework / package manager / test command found>
191
+ - catalogue_suggestions:
192
+ - [x] <catalogue id> # <one line why — RECOMMEND rows pre-ticked>
193
+ - [ ] <catalogue id> # <a MAYBE row: visible, never pre-ticked>
194
+ - discover_extras:
195
+ - id: <kebab-id>
196
+ source_url: <live URL>
197
+ license: <spdx or "unknown — verify">
198
+ rationale: <one line>
199
+ suggested_kind: <skill|mcp|workflow>
148
200
  ============================================================================== ==
149
201
  BRIEF
150
202
  }
@@ -169,6 +221,40 @@ print_heuristic() {
169
221
  for d in app pages routes src; do
170
222
  [ -d "$TARGET/$d" ] && printf 'route/entry root candidate: %s/\n' "$d"
171
223
  done
224
+ # The catalogue_suggestions fallback: rows whose `matches:` cell names a token the
225
+ # heuristic scan actually saw in this repo (package.json deps + lockfile + config
226
+ # files). The SAME matches column the agent brief reads — the fallback suggests from
227
+ # the catalogue, never from its own private list. Verdicts are respected: only
228
+ # RECOMMEND rows pre-tick; MAYBE rows print unticked; SKIP rows never appear.
229
+ if [ -f "$CATALOGUE_TSV" ]; then
230
+ local scan
231
+ scan=$(cat "$TARGET/package.json" "$TARGET"/*.lock "$TARGET"/pnpm-lock.yaml \
232
+ "$TARGET"/vue.config.* "$TARGET"/nuxt.config.* "$TARGET"/godot/project.godot \
233
+ "$TARGET"/unity/ProjectSettings/ProjectVersion.txt 2>/dev/null | tr 'A-Z' 'a-z')
234
+ if [ -n "$scan" ]; then
235
+ local ticked="" maybe=""
236
+ while IFS=$'\t' read -r cid ccat ckind cname crepo cpath chmd clic cstars cpush \
237
+ cmatches cconf creq chint cverdict cby cdate; do
238
+ [ -n "$cid" ] || continue
239
+ case "$cverdict" in RECOMMEND|MAYBE) ;; *) continue ;; esac
240
+ local hit=0 tok
241
+ for tok in $cmatches; do
242
+ case "$scan" in *"$tok"*) hit=1; break ;; esac
243
+ done
244
+ [ "$hit" -eq 1 ] || continue
245
+ case "$cverdict" in
246
+ RECOMMEND) ticked="$ticked - [x] $cid # matches this repo ($cmatches)"$'\n' ;;
247
+ MAYBE) maybe="$maybe - [ ] $cid # matches, verdict MAYBE (not pre-ticked)"$'\n' ;;
248
+ esac
249
+ done < "$CATALOGUE_TSV"
250
+ if [ -n "$ticked$maybe" ]; then
251
+ printf 'catalogue_suggestions (heuristic — verify the matches before trusting):\n'
252
+ printf '%s' "$ticked"
253
+ printf '%s' "$maybe"
254
+ printf 'install later with: gob extras install <id...> (init itself installs nothing)\n'
255
+ fi
256
+ fi
257
+ fi
172
258
  if [ "$got" -eq 0 ] && [ ! -d "$TARGET/src" ]; then
173
259
  printf 'no package.json, no src/ — the scan above is empty; say so in the summary\n'
174
260
  fi
@@ -233,10 +319,17 @@ EMAIL=$(g_agents_read "$PROP" owner_email)
233
319
  [ -n "$EMAIL" ] || EMAIL=$( cd "$TARGET" && git config user.email 2>/dev/null )
234
320
  [ -n "$EMAIL" ] || EMAIL="you@example.com"
235
321
  SYNC_SEL=$(g_agents_list "$PROP" sync_platforms | tr '\n' ' ')
322
+ # The extras the proposal's human approved (catalogue_suggestions tick lines in the
323
+ # summary). Only TICKED ids count — an unticked MAYBE line is a visible choice the
324
+ # human did not make. Every id is re-resolved through the catalogue's own allowlist
325
+ # gate in goblin-extras (unknown id -> refusal; SKIP verdict -> refusal): a proposal
326
+ # cannot install anything the catalogue does not carry.
327
+ EXTRAS_SEL=$(sed -n '/catalogue_suggestions:/,/^[^ -]/{s/^[[:space:]]*-[[:space:]]*\[x\][[:space:]]*\([A-Za-z0-9_-]*\).*/\1/p;}' "$PROP" | sort -u | tr '\n' ' ')
236
328
 
237
329
  if [ "$DRYRUN" -eq 1 ]; then
238
330
  g_info "gob init --write --dry-run: validated OK (class $CLASS, branch $BRANCH, owner $EMAIL, 1+ gate)"
239
331
  g_info " sync: ${SYNC_SEL:-none}"
332
+ g_info " extras: ${EXTRAS_SEL:-none}"
240
333
  g_info " would write: AGENTS.md (body + gob block), .gob/engine/, HANDOFF.md"
241
334
  exit 0
242
335
  fi
@@ -310,11 +403,27 @@ for p in $SYNC_SEL; do
310
403
  RC=$?; g_err "sync $p failed (exit $RC)"; exit "$RC"; }
311
404
  done
312
405
 
313
- # ------------------------------------------------------------- mcp config -----
314
- # The --write mode lands the same .mcp.json (ask-once, never overwrite).
406
+ # ------------------------------------------------------------- extras ----------
407
+ # The proposal's TICKED catalogue_suggestions install through the goblin-extras
408
+ # allowlist itself — one code path, one set of refusals (unknown id, SKIP verdict,
409
+ # missing payload). init --write never copies an extra by its own hand: the catalogue
410
+ # is the allowlist, and the CLI is the only installer. A refusal fails the whole
411
+ # --write (the human re-ticks).
412
+ #
413
+ # ORDER MATTERS: --with-mcp-config writes the gob registration FIRST, then the mcp-kind
414
+ # extras merge INTO it — the other order would leave .mcp.json carrying only the extra
415
+ # and do_mcp_config would refuse it (the generated-bytes ask-once check). Extras mcp
416
+ # merges are themselves ask-once: an entry you customized is never overwritten.
315
417
  if [ "$WITH_MCP" -eq 1 ]; then
316
418
  do_mcp_config || exit 1
317
419
  fi
420
+ if [ -n "$EXTRAS_SEL" ]; then
421
+ for xid in $EXTRAS_SEL; do
422
+ printf 'gob init [run] extras install %s\n' "$xid"
423
+ bash "$SRC/bin/goblin-extras" install "$xid" --target "$TARGET" || {
424
+ RC=$?; g_err "extras install $xid failed (exit $RC) — fix the proposal's catalogue_suggestions and re-run"; exit "$RC"; }
425
+ done
426
+ fi
318
427
 
319
428
  # --------------------------------------------------------------------- done ----
320
429
  printf '\n'
@@ -31,7 +31,7 @@
31
31
 
32
32
  set -uo pipefail
33
33
 
34
- GOBLIN_INSTALL_VERSION="0.6.0-alpha.1"
34
+ GOBLIN_INSTALL_VERSION="0.6.0-alpha.3"
35
35
  SELF_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
36
36
  SRC=$(cd "$SELF_DIR/.." && pwd)
37
37
  # shellcheck source=goblin-lib.sh
package/bin/goblin-lib.sh CHANGED
@@ -16,7 +16,7 @@
16
16
  # - name: typecheck four-space-indented second member of a list entry
17
17
  # cmd: npx tsc --noEmit
18
18
 
19
- GOBLIN_LIB_VERSION="0.6.0-alpha.1"
19
+ GOBLIN_LIB_VERSION="0.6.0-alpha.3"
20
20
 
21
21
  # ---------------------------------------------------------------- output -----
22
22
  # g_trunc <width> <text> — fold a long detail to one line at <width> columns, keeping the
@@ -30,7 +30,7 @@
30
30
  # `git revert` of this command's own two commits (W3-SPEC §5).
31
31
  set -uo pipefail
32
32
 
33
- GOBLIN_UPGRADE_VERSION="0.6.0-alpha.1"
33
+ GOBLIN_UPGRADE_VERSION="0.6.0-alpha.3"
34
34
 
35
35
  usage() { sed -n '2,/^$/p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
36
36
 
package/bin/goblin-verify CHANGED
@@ -19,7 +19,7 @@
19
19
 
20
20
  set -uo pipefail
21
21
 
22
- GOBLIN_VERIFY_VERSION="0.6.0-alpha.1"
22
+ GOBLIN_VERIFY_VERSION="0.6.0-alpha.3"
23
23
  SELF_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
24
24
  # shellcheck source=goblin-lib.sh
25
25
  . "$SELF_DIR/goblin-lib.sh"
package/bin/goblin.js CHANGED
@@ -10,6 +10,7 @@
10
10
  // gob map [...] -> bin/goblin-map (prompt+schema; --heuristic fallback)
11
11
  // gob verify [...] -> bin/goblin-verify
12
12
  // gob bans [...] -> bin/goblin-bans
13
+ // gob extras [...] -> bin/goblin-extras (curated catalogue)
13
14
  // gob mcp -> bin/goblin-mcp.js (the MCP stdio server)
14
15
  // gob uninstall [--target <dir>] -> bin/goblin-install --uninstall
15
16
  // no args | -h/--help | any other first arg -> this file's short usage, exit 2.
@@ -37,7 +38,7 @@ if (arg0 === "--version" || arg0 === "-V" || arg0 === "-v") {
37
38
  process.exit(0);
38
39
  }
39
40
 
40
- const SCRIPT = { init: "goblin-init", map: "goblin-map", verify: "goblin-verify", bans: "goblin-bans", mcp: "goblin-mcp.js" };
41
+ const SCRIPT = { init: "goblin-init", map: "goblin-map", verify: "goblin-verify", bans: "goblin-bans", extras: "goblin-extras", mcp: "goblin-mcp.js" };
41
42
  const [cmd, ...rest] = process.argv.slice(2);
42
43
 
43
44
  // No args, a help flag, or an unrecognized first arg: short usage, exit 2. A bare
@@ -51,6 +52,7 @@ function usage() {
51
52
  " gob init start here — prints the agent brief + proposal schema; --write installs it",
52
53
  " gob verify run the rule matrix against the current repo",
53
54
  " gob bans run the ban list (per-pattern red lines over the source tree)",
55
+ " gob extras browse + install the curated extras catalogue (list | show | install)",
54
56
  " gob map print the feature-map prompt + schema; --heuristic scans instead",
55
57
  " gob mcp serve the harness to your agent over MCP stdio (verify/map/init tools)",
56
58
  " gob uninstall --target . remove exactly what an install wrote (preimages)",
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.6.0-alpha.1` · Last measured: 2026-10-09 · Every command and every output below was run on a
6
+ Version: `0.6.0-alpha.2` · Last measured: 2026-10-09 · Every command and every output below was run on a
7
7
  real repository while writing this guide.
8
8
 
9
9
  ---
@@ -524,6 +524,7 @@ same commands are available outside any repo through the npm CLI: `gob init` / `
524
524
  [--with-mcp-config]
525
525
  gob map [--heuristic [target]] [--write <dir>] [--force]
526
526
  gob mcp # the MCP stdio server (three tools, local only)
527
+ gob extras list | show <id> | install <id...> # the curated extras catalogue
527
528
 
528
529
  .gob/bin/goblin-verify [--only <id[,id...]>] [--json] [--list]
529
530
  .gob/bin/goblin-bans # run the ban list
@@ -570,6 +571,36 @@ Named procedures, installed as project-local skills. Each has a measurable verif
570
571
  | P14 | `goblin-drift-audit` | a recorded claim disagrees with the artifact |
571
572
  | P15 | `goblin-re-mobile` | one shipped Android build must be understood as facts for study |
572
573
 
574
+ ### The extras catalogue (suggestions, not installs)
575
+
576
+ `gob extras` browses and installs the curated extras catalogue — third-party agent skills,
577
+ MCP servers and workflows the curator reviewed and carried in `extras-catalogue/catalogue.tsv`.
578
+ The one rule to hold onto: **init suggests, the catalogue allows, you install.**
579
+
580
+ gob extras list # the whole catalogue, grouped by category
581
+ gob extras list testing # one category: frontend | testing | game | node
582
+ gob extras show playwright-mcp # one row, every field
583
+ gob extras install playwright-mcp --target . # mcp: prints the mcp.json snippet
584
+ gob extras install taste-skill --target . --platform claude # skill: .claude/skills/taste/
585
+ gob extras install taste-skill --target . # no platform: .gob/extras/taste/
586
+
587
+ Verdicts: a `RECOMMEND` row is pre-ticked when `gob init` suggests; a `MAYBE` row is listed
588
+ but never pre-ticked; a `SKIP` row refuses install (visible history, declined candidate).
589
+ The license gate rides on top, hard: a row installs only when its `license_status` is
590
+ `verified` — a `LICENSE-PENDING` row (license unresolved) is never pre-ticked and
591
+ `gob extras install` refuses it with `license pending verification` until the curator flips
592
+ the row's data in `catalogue.tsv`.
593
+ Nothing installs at init — `gob init --write` honours only the rows your proposal ticks, and
594
+ it installs them by calling `gob extras install`, so the allowlist is one code path. An id
595
+ that is not in the catalogue is refused; installs copy from the vendored payload in the
596
+ package (no network), and an mcp row merges into `.mcp.json` only with `--with-mcp-config`,
597
+ never overwriting an entry you customized.
598
+
599
+ The brief also asks your agent to propose discoveries (`discover_extras:`): web-found skills
600
+ that match the stack but are not in the catalogue, each with a live URL, a license and a
601
+ rationale. Proposals are inert; the curator promotes accepted ones into `catalogue.tsv` by PR
602
+ or edit, and only then can `gob extras install` name them.
603
+
573
604
  ### Where the real documentation lives
574
605
 
575
606
  | File | Read it for |
@@ -0,0 +1,42 @@
1
+ id category kind name source_repo path_within_repo has_skill_md license stars last_push matches conflicts requires install_hint verdict reviewed_by reviewed_date license_status license_check_date
2
+ taste-skill frontend skill taste-skill Leonxlnx/taste-skill . yes MIT 93730 2026-10-07 react nextjs vue nuxt frontend ui design - none copy-folder RECOMMEND harvey 2026-10-08 verified 2026-10-09
3
+ vercel-agent-skills frontend skill vercel-labs agent skills (react pack) vercel-labs/agent-skills skills/ skill none (verify per-skill) 32078 2026-08-28 react nextjs frontend ui design - none copy-folder LICENSE-PENDING harvey 2026-10-08 pending 2026-10-09
4
+ anthropic-frontend-design frontend skill frontend-design (anthropics/skills) anthropics/skills frontend-design yes none (verify per-skill) 180150 2026-10-05 frontend ui react vue design - none copy-folder LICENSE-PENDING harvey 2026-10-08 pending 2026-10-09
5
+ vuejs-skills frontend skill vuejs-ai skills (vue 3 pack) vuejs-ai/skills skills/ skill MIT 2889 2026-05-30 vue nuxt frontend - none copy-folder RECOMMEND harvey 2026-10-08 verified 2026-10-09
6
+ ux-ui-agent-skills frontend skill ux-ui agent skills (design tokens, WCAG) plugin87/ux-ui-agent-skills skills/ skill MIT 1551 2026-10-08 frontend ui design accessibility - none copy-folder RECOMMEND harvey 2026-10-08 verified 2026-10-09
7
+ impeccable frontend skill impeccable (design language) pbakaus/impeccable .dotagent-dir/ skill Apache-2.0 78552 2026-10-07 frontend ui design - none copy-folder RECOMMEND harvey 2026-10-08 verified 2026-10-09
8
+ playwright-mcp testing mcp Playwright MCP (Microsoft, official) microsoft/playwright-mcp - no Apache-2.0 37929 2026-10-08 playwright e2e testing browser automation - node >= 18 npx @playwright/mcp@latest RECOMMEND harvey 2026-10-08 verified 2026-10-09
9
+ chrome-devtools-mcp testing mcp Chrome DevTools MCP (official) ChromeDevTools/chrome-devtools-mcp - no Apache-2.0 53117 2026-10-08 browser debugging cdp e2e performance - node >= 18 npx chrome-devtools-mcp@latest RECOMMEND harvey 2026-10-08 verified 2026-10-09
10
+ playwright-skill testing skill playwright-skill (no MCP registration) lackeyjb/playwright-skill . yes MIT 3180 2026-10-01 playwright e2e testing - playwright installed copy-folder RECOMMEND harvey 2026-10-08 verified 2026-10-09
11
+ webapp-testing testing skill webapp-testing (anthropics/skills) anthropics/skills webapp-testing yes none (verify per-skill) 180150 2026-10-05 e2e testing browser playwright - playwright installed copy-folder LICENSE-PENDING harvey 2026-10-08 pending 2026-10-09
12
+ godot-ai game mcp Godot AI (hi-godot) hi-godot/godot-ai - no MIT 2862 2026-10-08 godot game - see repo README see repo README RECOMMEND harvey 2026-10-08 verified 2026-10-09
13
+ godot-mcp game mcp Godot MCP (community default) Coding-Solo/godot-mcp - no MIT 5976 2026-04-16 godot game godot-ai see repo README see repo README RECOMMEND harvey 2026-10-08 verified 2026-10-09
14
+ godotprompter game skill GodotPrompter (66 godot skills) jame581/GodotPrompter skills/ skill MIT 798 2026-10-06 godot game - none copy-folder RECOMMEND harvey 2026-10-08 verified 2026-10-09
15
+ unity-mcp game mcp Unity MCP (CoplayDev) CoplayDev/unity-mcp - no MIT 14767 2026-10-07 unity game - unity editor + see repo README see repo README RECOMMEND harvey 2026-10-08 verified 2026-10-09
16
+ awesome-gamedev-skills game skill awesome gamedev agent skills (74 skills, harvest source) gamedev-skills/awesome-gamedev-agent-skills skills/ skill Apache-2.0 1365 2026-09-27 game godot unity unreal phaser threejs bevy pygame - none copy-folder RECOMMEND harvey 2026-10-08 verified 2026-10-09
17
+ supabase-agent-skills node skill Supabase agent skills (official) supabase/agent-skills skills/ skill MIT 2708 2026-10-02 node backend supabase database auth - none copy-folder RECOMMEND harvey 2026-10-08 verified 2026-10-09
18
+ pg-aiguide node mcp pg-aiguide (Timescale, postgres) timescale/pg-aiguide - no Apache-2.0 1860 2026-10-07 node postgres database backend - see repo README see repo README RECOMMEND harvey 2026-10-08 verified 2026-10-09
19
+ hono-skills node skill Hono agent skills (official) honojs/skills skills/ skill MIT 13 2026-10-08 node hono backend api - none copy-folder RECOMMEND harvey 2026-10-08 verified 2026-10-09
20
+ superdesign-skill frontend skill superdesign-skill (design iteration loop) superdesigndev/superdesign-skill . yes MIT 633 2026-08-21 frontend ui design taste-skill vercel-agent-skills none copy-folder MAYBE harvey 2026-10-08 verified 2026-10-09
21
+ garden-skills frontend skill garden-skills (collection, harvest source) ConardLi/garden-skills skills/ skill MIT 12771 2026-07-12 frontend ui design - none copy-folder MAYBE harvey 2026-10-08 verified 2026-10-09
22
+ dev-browser testing skill dev-browser (general web browser skill) SawyerHood/dev-browser . yes MIT 6656 2026-09-05 browser e2e testing playwright-mcp none copy-folder MAYBE harvey 2026-10-08 verified 2026-10-09
23
+ currents-playwright-best-practices testing skill playwright best practices (Currents.dev) currents-dev/playwright-best-practices-skill . yes MIT 393 2026-07-21 playwright e2e testing testdino-playwright-skill none copy-folder MAYBE harvey 2026-10-08 verified 2026-10-09
24
+ testdino-playwright-skill testing skill playwright best practices (TestDino) testdino-hq/playwright-skill . yes MIT 389 2026-09-06 playwright e2e testing currents-playwright-best-practices none copy-folder MAYBE harvey 2026-10-08 verified 2026-10-09
25
+ unity-mcp-alt game mcp Unity-MCP (IvanMurzak, skills + CLI) IvanMurzak/Unity-MCP - no Apache-2.0 4400 2026-10-04 unity game unity-mcp unity editor + see repo README see repo README MAYBE harvey 2026-10-08 verified 2026-10-09
26
+ cc-game-studios game workflow Game-Studios mega-pack (49 agents, 72 skills) Donchitos/Cc-S-B . skill MIT 25900 2026-10-08 game godot unity - none copy-folder MAYBE harvey 2026-10-08 verified 2026-10-09
27
+ godot-ui-integration game skill godot-ui-integration (design to godot UI) zimo-xiao-zheng/godot-ui-integration . yes MIT 275 2026-08-23 godot game ui - none copy-folder MAYBE harvey 2026-10-08 verified 2026-10-09
28
+ agent-nestjs-skills node skill NestJS agent skills Kadajett/agent-nestjs-skills skills/ skill none (verify) 289 2026-07-23 node nestjs backend api - none copy-folder LICENSE-PENDING harvey 2026-10-08 pending 2026-10-09
29
+ openapi-to-skills node skill openapi-to-skills (spec to skills generator) neutree-ai/openapi-to-skills . skill Apache-2.0 342 2026-06-23 node api openapi - none copy-folder MAYBE harvey 2026-10-08 verified 2026-10-09
30
+ do-app-platform-skills node skill DigitalOcean App Platform skills digitalocean-labs/do-app-platform-skills skills/ skill MIT 37 2026-10-04 node deployment database - none copy-folder MAYBE harvey 2026-10-08 verified 2026-10-09
31
+ agent-skills-standard node skill agent-skills-standard (per-framework best practice) HoangNguyen0403/agent-skills-standard skills/ skill MIT 572 2026-10-08 node backend typescript - none copy-folder MAYBE harvey 2026-10-08 verified 2026-10-09
32
+ frontend-design-toolkit frontend workflow Frontend-Design-Toolkit (list, harvest source) wilwaldon/cc-frontend-design-toolkit . skill none 1176 2026-04-11 frontend ui design - none copy-folder SKIP harvey 2026-10-08 pending 2026-10-09
33
+ nuxt-skills frontend skill nuxt-skills (vue/nuxt/nuxthub) onmax/nuxt-skills skills/ skill none 719 2026-10-06 vue nuxt frontend - none copy-folder SKIP harvey 2026-10-08 pending 2026-10-09
34
+ design-audit frontend skill design-audit (UI/UX checklist) wonjyou/design-audit . yes none 8 2026-03-28 frontend ui design - none copy-folder SKIP harvey 2026-10-08 pending 2026-10-09
35
+ vibe-coded-website-review frontend skill vibe-coded-website-review (YC design review) zuocharles/vibe-coded-website-review . yes none 0 2026-03-29 frontend ui design - none copy-folder SKIP harvey 2026-10-08 pending 2026-10-09
36
+ qa-skills testing workflow qa-skills (e2e generation pipeline) neonwatty/qa-skills . skill MIT 33 2026-05-03 e2e testing - none copy-folder SKIP harvey 2026-10-08 verified 2026-10-09
37
+ agentmantis-test-skills testing skill test-skills (playwright e2e pack) agentmantis/test-skills skills/ skill MIT 12 2026-04-08 playwright e2e testing - none copy-folder SKIP harvey 2026-10-08 verified 2026-10-09
38
+ invisible-playwright-mcp testing mcp invisible_playwright_mcp (anti-detect) feder-cr/invisible_playwright_mcp - no none 2660 2026-10-08 browser e2e playwright - see repo README see repo README SKIP harvey 2026-10-08 pending 2026-10-09
39
+ mcp-unity game mcp mcp-unity (CoderGamester, superseded) CoderGamester/mcp-unity - no MIT 1922 2026-09-03 unity game unity-mcp unity editor + see repo README see repo README SKIP harvey 2026-10-08 verified 2026-10-09
40
+ godot-mcp-native game mcp Godot-MCP-Native (duplicate function) yurineko73/Godot-MCP-Native - no MIT 836 2026-08-03 godot game godot-mcp see repo README see repo README SKIP harvey 2026-10-08 verified 2026-10-09
41
+ fastify-skills node skill fastify-skills (undocumented) TheCodePace/fastify-skills skills/ skill none 12 2026-10-08 node fastify backend - none copy-folder SKIP harvey 2026-10-08 pending 2026-10-09
42
+ drizzle-orm-skill node skill drizzle-orm-skill (no credible alternative exists) oldirty/drizzle-orm-skill . yes none 0 2026-02-24 node drizzle orm database - none copy-folder SKIP harvey 2026-10-08 pending 2026-10-09
@@ -0,0 +1,14 @@
1
+ # extras-catalogue/payload/ — the vendored extras payload
2
+
3
+ `gob extras install` needs **no network**: every skill or workflow it writes is copied
4
+ from this directory. One directory per catalogue id:
5
+
6
+ payload/<id>/<skill-name>/SKILL.md # a skill row: one directory per skill
7
+ payload/<id>/… # a workflow row: the playbook files verbatim
8
+
9
+ `mcp` rows carry no payload — their install is the generated mcp.json snippet.
10
+
11
+ Until a row's payload is vendored, installing it is REFUSED with exit 2 and a named fix
12
+ (never a download): vendor the accepted upstream copy here first, respecting the row's
13
+ license cell. The placeholder payloads shipped today mark the layout only — the curator
14
+ replaces them with the reviewed upstream bytes.
@@ -0,0 +1,3 @@
1
+ # taste — reference
2
+
3
+ Placeholder reference page for the vendored payload layout.
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: taste
3
+ description: Design-taste guardrails so agent UI output is not generic. Vendored from the catalogue row 'taste-skill'; replace with the curated upstream copy.
4
+ ---
5
+ # taste
6
+
7
+ Vendored placeholder payload — the curator replaces this directory with the accepted
8
+ upstream skill (catalogue row `taste-skill`, MIT). Layout contract: one directory per
9
+ skill, each with a SKILL.md.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techgoblin/gobstack",
3
- "version": "0.6.0-alpha.1",
3
+ "version": "0.6.0-alpha.3",
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",
@@ -23,6 +23,8 @@
23
23
  "automations/",
24
24
  "bans/",
25
25
  "docs/",
26
+ "extras-catalogue/catalogue.tsv",
27
+ "extras-catalogue/payload/",
26
28
  "roles.yaml",
27
29
  "VERSION",
28
30
  "CHANGELOG.md"