@techgoblin/gobstack 0.4.4-beta.8 → 0.5.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -7
- package/VERSION +1 -1
- package/bin/goblin-audit +1 -1
- package/bin/goblin-bans +9 -1
- package/bin/goblin-init +35 -29
- package/bin/goblin-install +37 -7
- package/bin/goblin-lib.sh +28 -1
- package/bin/goblin-upgrade +1 -1
- package/bin/goblin-verify +30 -11
- package/docs/ADOPTION.md +34 -32
- package/docs/CI.md +11 -8
- package/docs/CONTRACTS.md +8 -5
- package/docs/ENFORCEMENT.md +24 -20
- package/docs/GUIDE.md +21 -19
- package/docs/LIMITS.md +19 -2
- package/manifest/classes.tsv +50 -60
- package/package.json +1 -1
- package/presets/electron-overlay.yaml +38 -0
- package/presets/{E-fleet-config.yaml → fleet.yaml} +1 -1
- package/presets/{C-game.yaml → game.yaml} +1 -1
- package/presets/{D-knowledge.yaml → research.yaml} +1 -1
- package/presets/{B-service-config.yaml → service.yaml} +1 -1
- package/presets/{A-shipped-software.yaml → software.yaml} +1 -1
- package/skills/goblin-bootstrap/SKILL.md +6 -3
- package/templates/goblin.yaml.tmpl +9 -1
- package/presets/F-electron.yaml +0 -67
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ dependencies disagree with npm's. If you see `ERESOLVE` after a local install, r
|
|
|
28
28
|
dependency from `package.json` and install globally instead.
|
|
29
29
|
|
|
30
30
|
**The two-layer model.** The global install gives you the CLI only. `gob init` (or
|
|
31
|
-
`gob install --target <dir> --class
|
|
31
|
+
`gob install --target <dir> --class software`) 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
|
-
gob install --target /path/to/repo --class
|
|
39
|
+
gob install --target /path/to/repo --class software
|
|
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`,
|
|
@@ -46,6 +46,12 @@ of file it manages: `docs/CONTRACTS.md`.
|
|
|
46
46
|
A repo that already has its own `HANDOFF.md` exits 1 on the refusal. That is the contract, not a
|
|
47
47
|
failure: reconcile the file rather than forcing over it — `docs/ADOPTION.md`.
|
|
48
48
|
|
|
49
|
+
`--class` picks the preset: **software** (the default — shipped features, PRs, review gates),
|
|
50
|
+
**service** (backend jobs, config, unattended runs), **game** (playable builds, perf budgets),
|
|
51
|
+
**research** (specs, replays, reference corpora) and **fleet** (config-of-the-agent repos).
|
|
52
|
+
The single letters `A`–`E` are accepted aliases. What each preset turns on is the matrix in
|
|
53
|
+
`docs/ADOPTION.md`; the older names still resolve (see `docs/CONTRACTS.md`).
|
|
54
|
+
|
|
49
55
|
After installing, in this order:
|
|
50
56
|
|
|
51
57
|
cd <target> && git add -A && git commit # the install is a change like any other
|
|
@@ -53,7 +59,7 @@ After installing, in this order:
|
|
|
53
59
|
gob emit --platform <p> # optional, per platform: the agent skills are an opt-in
|
|
54
60
|
gob audit # once, deliberately: the ONLY network step (SC-07)
|
|
55
61
|
|
|
56
|
-
**A default class
|
|
62
|
+
**A default software-class install (no agent skills — those are `gob emit`'s job) verifies green —
|
|
57
63
|
`38 passed, 0 failed, 11 advisory, 33 skipped`, exit 0 — once
|
|
58
64
|
`HANDOFF.md` names a commit that exists. Before that edit the scaffold's `0000000` placeholder is
|
|
59
65
|
the one expected red: `37 passed, 1 failed`, `HP-05`. Both numbers measured at W6 (neutral-first);
|
|
@@ -91,7 +97,7 @@ The measurement and the vacuous-pass reading are in `docs/CONTRACTS.md`.
|
|
|
91
97
|
| `gob upgrade` | migrate a repo to the shared global engine at `~/.goblin/engine` — 8 steps, two commits, one report |
|
|
92
98
|
| `gob doctor` | one run across the platforms below: DETECTED / NOT-DETECTED / DRIFT per platform |
|
|
93
99
|
| `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 |
|
|
94
|
-
| `gob init` | the first-run wizard: detect → class → branch/email → first gate → emit → verify, one screen per question; every question has a flag (`--class
|
|
100
|
+
| `gob init` | the first-run wizard: detect → class → branch/email → first gate → emit → verify, one screen per question; every question has a flag (`--class software --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 |
|
|
95
101
|
|
|
96
102
|
`goblin` remains as a legacy alias for every command above — existing scripts keep working, but
|
|
97
103
|
new commands and docs use `gob`.
|
|
@@ -145,9 +151,9 @@ below is the reference material the guide points into, so the two do not compete
|
|
|
145
151
|
| `docs/CONTRACTS.md` | the installer/verifier interface, exit codes, idempotency, uninstall |
|
|
146
152
|
| `docs/INTEGRATION.md` | the board, cron, the skills precedence order, the referenced standard |
|
|
147
153
|
| `docs/RISKS.md` | the risk register, the advisory rows named, the non-goals |
|
|
148
|
-
| `docs/CI.md` | the CI lane: what makes a workflow a gate, the four settings a repository cannot set, and the
|
|
154
|
+
| `docs/CI.md` | the CI lane: what makes a workflow a gate, the four settings a repository cannot set, and the electron opt-in |
|
|
149
155
|
| `docs/LOOP.md` | the judge role and the loop contract: what a goal-mode loop actually does, the record, and what neither can see |
|
|
150
|
-
| `docs/ADOPTION.md` | the
|
|
156
|
+
| `docs/ADOPTION.md` | the five classes, the preset matrix, the adoption order |
|
|
151
157
|
| `docs/LIMITS.md` | where this is weaker than its sources, and what is unproven |
|
|
152
158
|
|
|
153
159
|
`manifest/enforcement.tsv` is the source of truth for rules; `manifest/classes.tsv` for what a
|
|
@@ -166,7 +172,7 @@ run · `3` the manifest is broken. Every run prints what it cannot see.
|
|
|
166
172
|
bash tests/run-tests.sh
|
|
167
173
|
|
|
168
174
|
Runs the source-scope rules (PR-01..PR-05) and the test scripts, including `t-verify-red.sh` —
|
|
169
|
-
one control per target-scope row (
|
|
175
|
+
one control per target-scope row (168 over 82 target rows), each required to go RED and then
|
|
170
176
|
restored, plus `t-audit.sh` for the SC-07 producer. **A verifier that only ever prints GREEN is a
|
|
171
177
|
failure**, so that file is the one that matters most.
|
|
172
178
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.5.0
|
package/bin/goblin-audit
CHANGED
package/bin/goblin-bans
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
set -uo pipefail
|
|
30
30
|
|
|
31
|
-
GOBLIN_BANS_VERSION="0.
|
|
31
|
+
GOBLIN_BANS_VERSION="0.5.0"
|
|
32
32
|
SELF_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
33
33
|
# shellcheck source=goblin-lib.sh
|
|
34
34
|
. "$SELF_DIR/goblin-lib.sh"
|
|
@@ -88,6 +88,14 @@ if [ -f "$CONFIG" ]; then
|
|
|
88
88
|
ENABLED=$(g_yaml_scalar "$CONFIG" bans)
|
|
89
89
|
ENABLED=${ENABLED#[}; ENABLED=${ENABLED%]}
|
|
90
90
|
ENABLED=$(printf '%s' "$ENABLED" | tr ',' ' ' | tr -s ' ' ' ')
|
|
91
|
+
# The electron opt-in (software class): `electron: true` turns the electron ban set ON even
|
|
92
|
+
# when a hand-edited bans: list omits it — the declaration is the contract, and the engine
|
|
93
|
+
# (not the LLM) is what makes a listed ban non-skippable. A repo without the key is unaffected.
|
|
94
|
+
if [ "$(g_yaml_scalar "$CONFIG" electron)" = "true" ]; then
|
|
95
|
+
for e in BN-06 BN-07 BN-08 BN-09; do
|
|
96
|
+
case " $ENABLED " in *" $e "*) ;; *) ENABLED="$ENABLED $e" ;; esac
|
|
97
|
+
done
|
|
98
|
+
fi
|
|
91
99
|
fi
|
|
92
100
|
enabled() {
|
|
93
101
|
[ -z "$ENABLED" ] && return 1
|
package/bin/goblin-init
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# goblin-init — the W6 first-run wizard (`gob init`).
|
|
3
3
|
#
|
|
4
|
-
# gob init [--target <dir>] [--class <
|
|
5
|
-
# [--branch <name>] [--email <addr>] [--gate <cmd>]
|
|
4
|
+
# gob init [--target <dir>] [--class <software|service|game|research|fleet|A..E|app|agent|desktop>]
|
|
5
|
+
# [--electron] [--branch <name>] [--email <addr>] [--gate <cmd>]
|
|
6
6
|
# [--emit <p[,p..]>] [--scope project|global] [--yes] [--dry-run]
|
|
7
7
|
#
|
|
8
8
|
# One screen per question, answered steps collapsing into the ✔/◆/○ rail above. On a tty
|
|
@@ -143,6 +143,7 @@ ask_value() {
|
|
|
143
143
|
|
|
144
144
|
# ------------------------------------------------------------------ flags -----
|
|
145
145
|
TARGET="" CLASS="" BRANCH="" EMAIL="" GATE="" EMIT="" SCOPE=""
|
|
146
|
+
ELECTRON=0
|
|
146
147
|
YES=0; DRYRUN=0
|
|
147
148
|
usage() { sed -n '2,/^$/p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
|
|
148
149
|
|
|
@@ -150,6 +151,7 @@ while [ $# -gt 0 ]; do
|
|
|
150
151
|
case "$1" in
|
|
151
152
|
--target) TARGET="${2:-}"; shift 2 ;;
|
|
152
153
|
--class) CLASS="${2:-}"; shift 2 ;;
|
|
154
|
+
--electron) ELECTRON=1; shift ;;
|
|
153
155
|
--branch) BRANCH="${2:-}"; shift 2 ;;
|
|
154
156
|
--email) EMAIL="${2:-}"; shift 2 ;;
|
|
155
157
|
--gate) GATE="${2:-}"; shift 2 ;;
|
|
@@ -255,45 +257,45 @@ if [ -z "$CLASS" ]; then
|
|
|
255
257
|
if [ "$TTY_IN" -eq 1 ]; then
|
|
256
258
|
rail_say ""
|
|
257
259
|
rail_say " ${C_MUTED}what kind of work does this repo do?$C_RESET"
|
|
258
|
-
rail_say " $C_TEXT 1 ▸
|
|
260
|
+
rail_say " $C_TEXT 1 ▸ software shipped features, PRs, review gates ${C_DIM}(class A)$C_RESET"
|
|
259
261
|
rail_say " $C_TEXT 2 service backend jobs, config, unattended runs ${C_DIM}(class B)$C_RESET"
|
|
260
262
|
rail_say " $C_TEXT 3 game playable builds, perf budgets ${C_DIM}(class C)$C_RESET"
|
|
261
263
|
rail_say " $C_TEXT 4 research specs, replays, reference corpora ${C_DIM}(class D)$C_RESET"
|
|
262
|
-
rail_say " $C_TEXT 5
|
|
263
|
-
rail_say " $
|
|
264
|
-
rail_say " ${
|
|
264
|
+
rail_say " $C_TEXT 5 fleet fleets, loops, unattended automation ${C_DIM}(class E)$C_RESET"
|
|
265
|
+
rail_say " ${C_MUTED}↑ number + Enter · Enter = software · a class letter also works (A-E)$C_RESET"
|
|
266
|
+
rail_say " ${C_DIM}an Electron desktop shell: pick software, then --electron$C_RESET"
|
|
265
267
|
printf ' '
|
|
266
268
|
read -r REPLY_CLASS
|
|
267
269
|
ASKED_ANY=1
|
|
268
270
|
rail_say ""
|
|
269
271
|
else
|
|
270
272
|
REPLY_CLASS="1"
|
|
271
|
-
cascade "class:
|
|
273
|
+
cascade "class: software (default; --class software|service|game|research|fleet or A-E)"
|
|
272
274
|
fi
|
|
273
275
|
case "$REPLY_CLASS" in
|
|
274
|
-
1) CLASS="
|
|
275
|
-
4) CLASS="research" ;; 5) CLASS="
|
|
276
|
-
*) CLASS="${REPLY_CLASS:-
|
|
276
|
+
1) CLASS="software" ;; 2) CLASS="service" ;; 3) CLASS="game" ;;
|
|
277
|
+
4) CLASS="research" ;; 5) CLASS="fleet" ;;
|
|
278
|
+
*) CLASS="${REPLY_CLASS:-software}" ;;
|
|
277
279
|
esac
|
|
278
280
|
fi
|
|
279
|
-
#
|
|
280
|
-
#
|
|
281
|
-
#
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
research|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
*)
|
|
290
|
-
g_err "--class must be app|service|game|research|agent|desktop (or A-F), got '$CLASS'"
|
|
291
|
-
exit 2 ;;
|
|
281
|
+
# Resolve every accepted spelling to the canonical class NAME. The letters A-E and the old
|
|
282
|
+
# taught names (app/agent/desktop) are read-time aliases; anything else is refused here, with
|
|
283
|
+
# the full enum, rather than deep inside the installer. `desktop`/F means the OLD desktop
|
|
284
|
+
# install, so it also sets the electron opt-in.
|
|
285
|
+
g_class_is_electron_alias "$CLASS" && ELECTRON=1
|
|
286
|
+
CLASS_RAW="$CLASS"
|
|
287
|
+
CLASS=$(g_class_canon "$CLASS") \
|
|
288
|
+
|| { g_err "--class must be software|service|game|research|fleet (or A-E / app / agent / desktop), got '$CLASS_RAW'"; exit 2; }
|
|
289
|
+
case "$CLASS_RAW" in
|
|
290
|
+
app|agent|desktop) g_info "note: '$CLASS_RAW' is now called '$CLASS' (accepted as an alias)";;
|
|
292
291
|
esac
|
|
293
|
-
|
|
294
|
-
|
|
292
|
+
if [ "$ELECTRON" -eq 1 ]; then
|
|
293
|
+
g_info "electron: on (the desktop opt-in — the app_bundle_bytes ratchet, BN-06..09 bans, a host gate)"
|
|
294
|
+
fi
|
|
295
|
+
CLASS_NAME="$SRC/presets/$CLASS.yaml"
|
|
296
|
+
STEP_VAL[1]="$C_TEXT$CLASS$C_RESET"
|
|
295
297
|
if [ "$TTY_OUT" -eq 1 ]; then
|
|
296
|
-
rail_say " ✔ $((CURRENT - 1)). class $C_TEXT$CLASS$
|
|
298
|
+
rail_say " ✔ $((CURRENT - 1)). class $C_TEXT$CLASS$C_RESET"
|
|
297
299
|
else
|
|
298
300
|
cascade "class: $CLASS"
|
|
299
301
|
fi
|
|
@@ -460,11 +462,15 @@ fi
|
|
|
460
462
|
# never rewinds past its own buffer by counting lines it did not print.
|
|
461
463
|
if [ "$TTY_OUT" -eq 1 ]; then
|
|
462
464
|
rail_note ""
|
|
463
|
-
rail_note " ${C_MUTED}running: goblin-install --target $TARGET --class $CLASS${C_RESET}"
|
|
465
|
+
rail_note " ${C_MUTED}running: goblin-install --target $TARGET --class $CLASS$([ "$ELECTRON" -eq 1 ] && printf ' --electron')${C_RESET}"
|
|
466
|
+
else
|
|
467
|
+
printf 'gob init [run] goblin-install --target %s --class %s%s\n' "$TARGET" "$CLASS" "$([ "$ELECTRON" -eq 1 ] && printf ' --electron')"
|
|
468
|
+
fi
|
|
469
|
+
if [ "$ELECTRON" -eq 1 ]; then
|
|
470
|
+
bash "$SRC/bin/goblin-install" --target "$TARGET" --class "$CLASS" --electron --skills no --yes
|
|
464
471
|
else
|
|
465
|
-
|
|
472
|
+
bash "$SRC/bin/goblin-install" --target "$TARGET" --class "$CLASS" --skills no --yes
|
|
466
473
|
fi
|
|
467
|
-
bash "$SRC/bin/goblin-install" --target "$TARGET" --class "$CLASS" --skills no --yes
|
|
468
474
|
EXIT_INSTALL=$?
|
|
469
475
|
if [ "$EXIT_INSTALL" -ne 0 ]; then
|
|
470
476
|
g_err "install failed (exit $EXIT_INSTALL) — the refusals above name the path and the fix"
|
package/bin/goblin-install
CHANGED
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
#
|
|
4
4
|
# Usage: gob install --target <dir> [options] (script: bin/goblin-install)
|
|
5
5
|
# --target <dir> required; the repo root to install into
|
|
6
|
-
# --class
|
|
6
|
+
# --class <name> required unless --uninstall or --re-pin; one of
|
|
7
|
+
# software|service|game|research|fleet, or the aliases A-E / app / agent
|
|
8
|
+
# / desktop (desktop/F => software + --electron); anything else is refused
|
|
9
|
+
# --electron the electron opt-in overlay over the software class (electron: true):
|
|
10
|
+
# the app_bundle_bytes ratchet, BN-06..09 bans, the host-gate declaration
|
|
7
11
|
# --models <path> model mapping file (default: $GOBLIN_MODELS -> ~/projects/fleet-model.yaml)
|
|
8
12
|
# --practice <path> the referenced standard (default: $GOBLIN_PRACTICE -> ~/projects/PROJECT-PRACTICE.md); a named path that is absent is reported, never silently dropped
|
|
9
13
|
# --parts <list> comma list to install; default = every part the class requires
|
|
@@ -23,7 +27,7 @@
|
|
|
23
27
|
|
|
24
28
|
set -uo pipefail
|
|
25
29
|
|
|
26
|
-
GOBLIN_INSTALL_VERSION="0.
|
|
30
|
+
GOBLIN_INSTALL_VERSION="0.5.0"
|
|
27
31
|
SELF_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
28
32
|
SRC=$(cd "$SELF_DIR/.." && pwd)
|
|
29
33
|
# shellcheck source=goblin-lib.sh
|
|
@@ -57,6 +61,10 @@ UNINSTALL=0
|
|
|
57
61
|
RE_PIN=0
|
|
58
62
|
FORCE=0
|
|
59
63
|
OPT_OUT=""
|
|
64
|
+
# The electron opt-in (the merged desktop/F class): renders presets/electron-overlay.yaml over
|
|
65
|
+
# the software preset. Auto-set when --class resolves via the desktop/F/f alias, so the alias
|
|
66
|
+
# behaves as the old class F did rather than silently installing weaker software.
|
|
67
|
+
ELECTRON=0
|
|
60
68
|
|
|
61
69
|
usage() {
|
|
62
70
|
# The header comment runs from line 2 to the first blank line, so adding an option cannot
|
|
@@ -73,6 +81,7 @@ while [ $# -gt 0 ]; do
|
|
|
73
81
|
--practice) PRACTICE="${2:-}"; PRACTICE_ARG="$PRACTICE"; shift 2 ;;
|
|
74
82
|
--parts) PARTS="${2:-}"; shift 2 ;;
|
|
75
83
|
--archive) ARCHIVE="true"; shift ;;
|
|
84
|
+
--electron) ELECTRON=1; shift ;;
|
|
76
85
|
--skills) SKILLS="${2:-}"; SKILLS_NAMED=1; shift 2 ;;
|
|
77
86
|
--dry-run) DRY_RUN=1; shift ;;
|
|
78
87
|
--upgrade) UPGRADE=1; shift ;;
|
|
@@ -239,8 +248,14 @@ if [ "$RE_PIN" -eq 1 ]; then
|
|
|
239
248
|
exit 0
|
|
240
249
|
fi
|
|
241
250
|
|
|
242
|
-
[ -n "$CLASS" ] || { g_err "--class is required (
|
|
243
|
-
|
|
251
|
+
[ -n "$CLASS" ] || { g_err "--class is required (software|service|game|research|fleet, or A-E / app / agent / desktop)"; exit 2; }
|
|
252
|
+
# Resolve every accepted spelling to the canonical class NAME before anything reads it. The
|
|
253
|
+
# letters A-E and the old names app/agent/desktop are read-time aliases; desktop/F/f also sets
|
|
254
|
+
# the electron opt-in so the merged class behaves as the old class F did.
|
|
255
|
+
g_class_is_electron_alias "$CLASS" && ELECTRON=1
|
|
256
|
+
CLASS_RAW="$CLASS"
|
|
257
|
+
CLASS=$(g_class_canon "$CLASS") \
|
|
258
|
+
|| { g_err "--class must be software|service|game|research|fleet (or A-E / app / agent / desktop), got '$CLASS_RAW'"; exit 2; }
|
|
244
259
|
case "$SKILLS" in yes|no) ;; *) g_err "--skills must be yes or no"; exit 2 ;; esac
|
|
245
260
|
|
|
246
261
|
# W6 migration safety: on a --upgrade (or any re-install) of a repo whose record shows skills
|
|
@@ -257,10 +272,24 @@ if [ -f "$INSTALLED" ] && [ "$SKILLS_NAMED" -eq 0 ]; then
|
|
|
257
272
|
fi
|
|
258
273
|
fi
|
|
259
274
|
|
|
260
|
-
PRESET
|
|
261
|
-
[ -
|
|
275
|
+
PRESET="$SRC/presets/$CLASS.yaml"
|
|
276
|
+
[ -f "$PRESET" ] || { g_err "no preset for class $CLASS ($PRESET)"; exit 2; }
|
|
262
277
|
|
|
263
|
-
|
|
278
|
+
# The electron overlay (the merged desktop/F class): every key it declares — including an empty
|
|
279
|
+
# one like sec_write_routes: "" — wins over the class preset; an absent key falls through. This
|
|
280
|
+
# is config, not a second code path: the same preset() reader, one layer up.
|
|
281
|
+
OVERLAY=""
|
|
282
|
+
if [ "$ELECTRON" -eq 1 ]; then
|
|
283
|
+
OVERLAY="$SRC/presets/electron-overlay.yaml"
|
|
284
|
+
[ -f "$OVERLAY" ] || { g_err "no electron overlay at $OVERLAY"; exit 2; }
|
|
285
|
+
fi
|
|
286
|
+
overlay_has() { [ -n "$OVERLAY" ] && grep -qE "^$1:" "$OVERLAY"; }
|
|
287
|
+
|
|
288
|
+
preset() {
|
|
289
|
+
local v
|
|
290
|
+
if overlay_has "$1"; then v=$(g_yaml_scalar "$OVERLAY" "$1"); else v=$(g_yaml_scalar "$PRESET" "$1"); fi
|
|
291
|
+
printf '%s' "${v%\"}" | sed 's/^"//'
|
|
292
|
+
}
|
|
264
293
|
|
|
265
294
|
HARNESS_DIR=$(preset harness_dir); [ -n "$HARNESS_DIR" ] || HARNESS_DIR=checks
|
|
266
295
|
SCAFFOLD=$(preset scaffold_checks); [ -n "$SCAFFOLD" ] || SCAFFOLD=no
|
|
@@ -488,6 +517,7 @@ render_config() {
|
|
|
488
517
|
runtime=" - $RUNTIME_DATA"
|
|
489
518
|
render "$SRC/templates/goblin.yaml.tmpl" \
|
|
490
519
|
CLASS "$CLASS" \
|
|
520
|
+
ELECTRON "$( [ "$ELECTRON" -eq 1 ] && printf 'true' || printf 'false' )" \
|
|
491
521
|
BRANCH "$BRANCH" \
|
|
492
522
|
MODELS_FILE "$MODELS" \
|
|
493
523
|
PRACTICE "$PRACTICE" \
|
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.
|
|
19
|
+
GOBLIN_LIB_VERSION="0.5.0"
|
|
20
20
|
|
|
21
21
|
# ---------------------------------------------------------------- output -----
|
|
22
22
|
g_pass() { printf 'PASS %-6s %s\n' "$1" "$2"; }
|
|
@@ -185,6 +185,33 @@ g_part_disabled() {
|
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
# ------------------------------------------------------------- class data ----
|
|
188
|
+
# g_class_canon <spelling> -> the canonical class NAME
|
|
189
|
+
# software | service | game | research | fleet
|
|
190
|
+
# The taxonomy is five domain-named classes. The letters A-E and the older taught domain names
|
|
191
|
+
# (app, agent, desktop) stay as READ-TIME aliases so every existing installed.json / goblin.yaml
|
|
192
|
+
# - which record a letter or an old name - keeps verifying with no rewrite. `desktop` / `F` / `f`
|
|
193
|
+
# resolve to `software`: F was merged into A (their classes.tsv need columns are identical), and
|
|
194
|
+
# what made a desktop shell different is the `electron:` opt-in + ban list, config keys the repo
|
|
195
|
+
# already carries. Unknown spelling -> empty output; the caller refuses with the enum.
|
|
196
|
+
g_class_canon() {
|
|
197
|
+
case "$1" in
|
|
198
|
+
software|A|a|app) printf 'software' ;;
|
|
199
|
+
service|B|b) printf 'service' ;;
|
|
200
|
+
game|C|c) printf 'game' ;;
|
|
201
|
+
research|D|d) printf 'research' ;;
|
|
202
|
+
fleet|E|e|agent) printf 'fleet' ;;
|
|
203
|
+
desktop|F|f) printf 'software' ;;
|
|
204
|
+
*) return 1 ;;
|
|
205
|
+
esac
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
# g_class_is_electron_alias <spelling> -> 0 when the spelling is the merged desktop/F spelling.
|
|
209
|
+
# `--class desktop` (or F/f) must mean the OLD desktop install, not a silently weaker software
|
|
210
|
+
# one: the installer auto-sets electron: true so the alias behaves as F did.
|
|
211
|
+
g_class_is_electron_alias() {
|
|
212
|
+
case "$1" in desktop|F|f) return 0 ;; *) return 1 ;; esac
|
|
213
|
+
}
|
|
214
|
+
|
|
188
215
|
# g_class_need <classes.tsv> <class> <part> -> R | O | -
|
|
189
216
|
g_class_need() {
|
|
190
217
|
awk -F'\t' -v c="$2" -v p="$3" '
|
package/bin/goblin-upgrade
CHANGED
package/bin/goblin-verify
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
set -uo pipefail
|
|
21
21
|
|
|
22
|
-
GOBLIN_VERIFY_VERSION="0.
|
|
22
|
+
GOBLIN_VERIFY_VERSION="0.5.0"
|
|
23
23
|
SELF_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
24
24
|
# shellcheck source=goblin-lib.sh
|
|
25
25
|
. "$SELF_DIR/goblin-lib.sh"
|
|
@@ -261,15 +261,17 @@ if [ -n "$ONLY" ]; then
|
|
|
261
261
|
fi
|
|
262
262
|
|
|
263
263
|
if [ ! -f "$CONFIG" ]; then
|
|
264
|
-
g_err "not installed: $CONFIG is absent. Run: goblin-install --target $ROOT --class <
|
|
264
|
+
g_err "not installed: $CONFIG is absent. Run: goblin-install --target $ROOT --class <software|service|game|research|fleet>"
|
|
265
265
|
exit 2
|
|
266
266
|
fi
|
|
267
267
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
268
|
+
# The class is read-time resolved: goblin.yaml records the canonical name on new installs, but
|
|
269
|
+
# every repo installed before the 5-class merge records a letter (A..F) or an old name
|
|
270
|
+
# (app/agent/desktop). g_class_canon maps them all to the five canonical names; the tsv carries
|
|
271
|
+
# the canonical names, so nothing else in this file has to know about letters. `desktop`/F -> software.
|
|
272
|
+
CLASS_RAW=$(g_yaml_scalar "$CONFIG" class)
|
|
273
|
+
CLASS=$(g_class_canon "$CLASS_RAW") \
|
|
274
|
+
|| { g_err "unknown class '${CLASS_RAW:-<empty>}' in $CONFIG (expected software|service|game|research|fleet, or A-E / app / agent / desktop)"; exit 2; }
|
|
273
275
|
|
|
274
276
|
# Root vs git toplevel. find_installed_root resolves the CONFIG lookup correctly, but the
|
|
275
277
|
# rows that shell out to git run in $ROOT and git resolves that to the ENCLOSING repo when
|
|
@@ -374,8 +376,12 @@ PERF_BASELINE_COMMIT=$(g_unquote "$(g_yaml_block_scalar "$CONFIG" perf baseline_
|
|
|
374
376
|
PERF_BASELINE_VALUE=$(g_yaml_block_scalar "$CONFIG" perf baseline_value)
|
|
375
377
|
PERF_MEASURED=$(g_unquote "$(g_yaml_block_scalar "$CONFIG" perf measured)")
|
|
376
378
|
PERF_HOST_GATE=$(g_yaml_block_scalar "$CONFIG" perf host_gate)
|
|
379
|
+
# The electron opt-in (software class): true means the electron ban set stays on regardless of a
|
|
380
|
+
# hand-edited bans: list, and a perf host gate must be declared (PF-01). Absent on a repo
|
|
381
|
+
# installed before the merge — read as off, so a legacy class-F repo verifies with no rewrite.
|
|
382
|
+
ELECTRON=$(g_yaml_scalar "$CONFIG" electron)
|
|
377
383
|
export SEC_GITIGNORE_FAMILY SEC_BUILD_OUTPUT SEC_AUDIT_CMD SEC_AUDIT_MAX_AGE SEC_WAIVER_MAX_AGE
|
|
378
|
-
export SEC_WRITE_ROUTES PERF_METRIC PERF_CMD PERF_BASELINE_VALUE PERF_HOST_GATE
|
|
384
|
+
export SEC_WRITE_ROUTES PERF_METRIC PERF_CMD PERF_BASELINE_VALUE PERF_HOST_GATE ELECTRON
|
|
379
385
|
|
|
380
386
|
# Whole days between an ISO date and today, or empty when the date does not parse on this host
|
|
381
387
|
# (`date -d` is GNU; the row FAILS rather than assuming, because an unreadable date is not a
|
|
@@ -613,8 +619,14 @@ check_hp_03() {
|
|
|
613
619
|
m = split(names, dn, " ")
|
|
614
620
|
for (i = 1; i <= m; i++) if (dn[i] != "") decl[dn[i]] = 1
|
|
615
621
|
}
|
|
616
|
-
|
|
617
|
-
|
|
622
|
+
# Portability (clean-room catch, 2026-10-05): the Debian default awk (mawk) has no
|
|
623
|
+
# interval expressions ({2,3}) — the header rule matched nothing and HP-03 went red on
|
|
624
|
+
# every fresh Debian install. The hash-header variants are spelled out as two pattern
|
|
625
|
+
# statements, and the date as repeated [0-9] groups. (No apostrophes here: this
|
|
626
|
+
# program is a single-quoted shell string.)
|
|
627
|
+
tolower($0) ~ /^###[[:space:]]+[^[:alpha:]]*gates?([^[:alpha:]]|$)/ { g = 1; next }
|
|
628
|
+
tolower($0) ~ /^##[[:space:]]+[^[:alpha:]]*gates?([^[:alpha:]]|$)/ { g = 1; next }
|
|
629
|
+
g && (/^###[[:space:]]/ || /^##[[:space:]]/ || /^#[[:space:]]/) { g = 0 }
|
|
618
630
|
!g { next }
|
|
619
631
|
tolower($0) ~ /example of the required form/ { next }
|
|
620
632
|
$0 !~ /=/ { next }
|
|
@@ -624,7 +636,7 @@ check_hp_03() {
|
|
|
624
636
|
if (!hit) for (k in decl) if (hasname($0, k)) { hit = 1; break }
|
|
625
637
|
if (hit) {
|
|
626
638
|
n++
|
|
627
|
-
if ($0 !~ /measured [0-9]
|
|
639
|
+
if ($0 !~ /measured [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/) {
|
|
628
640
|
print "a gate line carries no measured date: " $0
|
|
629
641
|
bad = 1
|
|
630
642
|
}
|
|
@@ -1594,6 +1606,13 @@ check_pf_01() {
|
|
|
1594
1606
|
# to ignore it.
|
|
1595
1607
|
local rname bad=0
|
|
1596
1608
|
rname=$(g_yaml_block_scalar "$CONFIG" ratchet name)
|
|
1609
|
+
# The electron opt-in's done-definition: an Electron repo declares its host gate (the FPS /
|
|
1610
|
+
# main_thread_busy_pct number a hermetic rule cannot measure). `electron: true` with no host
|
|
1611
|
+
# gate is a repo missing half its definition — FAIL, before the baseline skip below can mask it.
|
|
1612
|
+
if [ "$ELECTRON" = "true" ] && [ -z "$PERF_HOST_GATE" ]; then
|
|
1613
|
+
printf 'electron: true requires a declared perf.host_gate — declare the host-gate probe (the number no hermetic rule can measure) or drop electron: true\n'
|
|
1614
|
+
return 1
|
|
1615
|
+
fi
|
|
1597
1616
|
[ -n "$PERF_METRIC" ] || {
|
|
1598
1617
|
printf 'this class declares no perf metric (perf.metric is empty). The ratchet still carries\n%s; declare metric/cmd here the day you pick one\n' "${rname:-nothing}"
|
|
1599
1618
|
return 3
|
package/docs/ADOPTION.md
CHANGED
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
# Adoption — classes, presets, and the order
|
|
2
2
|
|
|
3
|
-
## The
|
|
3
|
+
## The five classes
|
|
4
4
|
|
|
5
5
|
A class is **not** a stringency level. It selects which parts are required, optional or off, and
|
|
6
6
|
it supplies the default gate and ratchet shape. The gate vocabulary differs by class; the
|
|
7
|
-
harness does not.
|
|
7
|
+
harness does not. The names are words; the letters `A`..`E` are read-time aliases.
|
|
8
8
|
|
|
9
9
|
| Class | What "done" means |
|
|
10
10
|
|---|---|
|
|
11
|
-
| **
|
|
12
|
-
| **B
|
|
13
|
-
| **C
|
|
14
|
-
| **
|
|
15
|
-
| **
|
|
16
|
-
| **F. Desktop shell** | the renderer is isolated from Node, the main process is not busy, and the packaged bundle ships no dev dependency |
|
|
11
|
+
| **software** (A) | a gate set reports measured numbers, a round lands, the artifact deploys or publishes |
|
|
12
|
+
| **service** (B) | a contract (schema, route, API) is unchanged, or the change is intentional and migrated |
|
|
13
|
+
| **game** (C) | a suite green in the Editor **and** a human feel verdict — the verdict is a first-class deliverable |
|
|
14
|
+
| **research** (D) | a question is answered with sources and the answer is findable |
|
|
15
|
+
| **fleet** (E) | a config change is applied, verified against the **artifact**, and versioned |
|
|
17
16
|
|
|
18
17
|
Two placements worth arguing about:
|
|
19
18
|
|
|
20
19
|
- A repo whose code is small and lives elsewhere, while the repo holds *output*, belongs in
|
|
21
|
-
**
|
|
22
|
-
not compilation.
|
|
23
|
-
- An input directory that is not a build target at all belongs in **
|
|
20
|
+
**research**, not **software** — gating it like an application gates the wrong artifact; its gate
|
|
21
|
+
is freshness, not compilation.
|
|
22
|
+
- An input directory that is not a build target at all belongs in **research** with **`--archive`**.
|
|
24
23
|
Without the flag the installer keeps producing HANDOFFs for a directory whose own design
|
|
25
24
|
folders are empty.
|
|
26
25
|
|
|
@@ -29,25 +28,28 @@ Two placements worth arguing about:
|
|
|
29
28
|
`R` = required · `O` = optional (installed, reported) · `—` = off. The same data is in
|
|
30
29
|
`manifest/classes.tsv`, and `CL-01` checks it against the repo.
|
|
31
30
|
|
|
32
|
-
| Part |
|
|
33
|
-
|
|
34
|
-
| HANDOFF | R | R | R | R | R |
|
|
35
|
-
| SPEC before change | R | R | R | — | R |
|
|
36
|
-
| Verification gate | R | R | R | O | R |
|
|
37
|
-
| Pinned-commit REPLAY | R | — | R | — | O |
|
|
38
|
-
| Ratchet | R | O | O | — | O |
|
|
39
|
-
| PR gate | O | — | O | — | O |
|
|
40
|
-
| Review panel | O | — | R | — | O |
|
|
41
|
-
| Playbooks (the skills) | R | R | R | R | R |
|
|
42
|
-
| Design tokens | O | — | — | — |
|
|
43
|
-
| CI lane | R | — | O | — | O |
|
|
44
|
-
|
|
45
|
-
**
|
|
46
|
-
(`BN-06`..`BN-09`) and
|
|
47
|
-
|
|
48
|
-
shipped rule may depend on. Its
|
|
49
|
-
|
|
50
|
-
|
|
31
|
+
| Part | software | service | game | research | fleet |
|
|
32
|
+
|---|---|---|---|---|---|
|
|
33
|
+
| HANDOFF | R | R | R | R | R |
|
|
34
|
+
| SPEC before change | R | R | R | — | R |
|
|
35
|
+
| Verification gate | R | R | R | O | R |
|
|
36
|
+
| Pinned-commit REPLAY | R | — | R | — | O |
|
|
37
|
+
| Ratchet | R | O | O | — | O |
|
|
38
|
+
| PR gate | O | — | O | — | O |
|
|
39
|
+
| Review panel | O | — | R | — | O |
|
|
40
|
+
| Playbooks (the skills) | R | R | R | R | R |
|
|
41
|
+
| Design tokens | O | — | — | — | O |
|
|
42
|
+
| CI lane | R | — | O | — | O |
|
|
43
|
+
|
|
44
|
+
**The electron opt-in, not a class**, added at W4: an Electron app is the **software** class with
|
|
45
|
+
`electron: true`, which declares the Electron failure surface as bans (`BN-06`..`BN-09`) and
|
|
46
|
+
declares its FPS number as a **host gate** rather than a ratchet, because the probe that measures
|
|
47
|
+
it needs Playwright or Electron plus a display — neither of which a shipped rule may depend on. Its
|
|
48
|
+
ratchet carries `app_bundle_bytes` instead. The old sixth class was merged into `software` (its need
|
|
49
|
+
column measured identical on all ten parts), and the old `F` letter remains an install-time alias
|
|
50
|
+
that selects `software` **with** the opt-in. `docs/CI.md` §3 argues it in full, including why frame
|
|
51
|
+
time is the wrong number (measured flat at 16.70 ms while the main thread went from 1.8 % to 54.5 %
|
|
52
|
+
busy).
|
|
51
53
|
|
|
52
54
|
**`—` is a real, enforced option.** The installer records every off part in `disabled:`, so its
|
|
53
55
|
rows report `SKIP (opt-out)`; `CL-01` fails if a forbidden part's artifact exists. A repo with
|
|
@@ -105,7 +107,7 @@ Then, in order:
|
|
|
105
107
|
.goblin/bin/goblin-verify # 37 passed, 1 failed - HP-05, until HANDOFF names a commit
|
|
106
108
|
gob emit --platform <p> # optional, per platform: the agent skills are an opt-in
|
|
107
109
|
|
|
108
|
-
A default class
|
|
110
|
+
A default software-class install (no agent skills) is **green** — `38 passed, 0 failed, 11 advisory,
|
|
109
111
|
33 skipped`, exit 0 — once
|
|
110
112
|
`HANDOFF.md` names a commit that exists; before that edit the scaffold's `0000000` placeholder is
|
|
111
113
|
the one expected red (`37 passed, 1 failed`). Both numbers are measured, not assumed
|
|
@@ -164,7 +166,7 @@ The remedy is a reconciliation. The project's file stays the file of record; not
|
|
|
164
166
|
.goblin/bin/goblin-verify # HP-02, HP-03, HP-05 go green
|
|
165
167
|
|
|
166
168
|
Success is the class's full green path (`38 passed, 0 failed, 11 advisory, 33 skipped`, exit 0 for
|
|
167
|
-
class
|
|
169
|
+
the software class) with `git status --short` empty.
|
|
168
170
|
|
|
169
171
|
The edit is additive and small — measured on the model repo (§1's exemplar, 2450 lines): three
|
|
170
172
|
headings plus a `State` block, one dated gate line and a `Not verified` block, 15 lines, no line
|
package/docs/CI.md
CHANGED
|
@@ -43,8 +43,8 @@ not the model.
|
|
|
43
43
|
|
|
44
44
|
## 2. What goblin-stack places, and what it checks
|
|
45
45
|
|
|
46
|
-
The `ci-gate` part is `R` for
|
|
47
|
-
(`manifest/classes.tsv`). When it is installed, the installer renders
|
|
46
|
+
The `ci-gate` part is `R` for **software**, `O` for **game** and **fleet**, and `-` for **service**
|
|
47
|
+
and **research** (`manifest/classes.tsv`). When it is installed, the installer renders
|
|
48
48
|
`templates/ci/goblin-gate.yml.tmpl` into `.github/workflows/goblin-gate.yml` — one job, no `if:` at
|
|
49
49
|
any level, whose only step runs `.goblin/bin/goblin-verify`. It is `owned`, so a second install is a
|
|
50
50
|
no-op and a hand-edited copy is never overwritten.
|
|
@@ -70,11 +70,14 @@ comment and cannot count toward a pass.
|
|
|
70
70
|
|
|
71
71
|
---
|
|
72
72
|
|
|
73
|
-
## 3. The Electron
|
|
73
|
+
## 3. The Electron opt-in (software class)
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
An Electron app is the **`software`** class with `electron: true`. The opt-in exists because such an
|
|
76
|
+
app needs a part no other shape has — a **host gate**, a number measured on a machine with a display
|
|
77
|
+
— and forbids a thing the plain software class allows: a **renderer that reaches Node or the
|
|
78
|
+
filesystem directly**. The merge measured the old sixth class's part needs identical to `software` on
|
|
79
|
+
all ten parts, so none of this is a sixth column in `manifest/classes.tsv`: it is the `bans:` list and
|
|
80
|
+
the `perf.host_gate:` key, rendered over the software preset by `presets/electron-overlay.yaml`.
|
|
78
81
|
|
|
79
82
|
### 3.1 The failure surface, and the check for each
|
|
80
83
|
|
|
@@ -94,14 +97,14 @@ reaches Node or the filesystem directly**. That is a new row-set, not a flag on
|
|
|
94
97
|
|
|
95
98
|
### 3.2 The perf lane: one ratchet, and a host gate beside it
|
|
96
99
|
|
|
97
|
-
There is **one** mechanism, and
|
|
100
|
+
There is **one** mechanism, and the opt-in reuses it unchanged: `ratchet: {name, cmd, ceiling}`,
|
|
98
101
|
enforced by `GT-04`/`GT-05` and pinned to a commit by `PF-01`. A second perf mechanism is not
|
|
99
102
|
introduced.
|
|
100
103
|
|
|
101
104
|
What the ratchet measures here is `app_bundle_bytes` — the packaged bundle's byte count. It is
|
|
102
105
|
hermetic, it needs no browser, no display and no dependency, and a fat bundle is a slow cold start on
|
|
103
106
|
every machine. **The FPS number is declared as a host gate instead** (`perf_host_gate:` in
|
|
104
|
-
`presets/
|
|
107
|
+
`presets/electron-overlay.yaml`), and carried in the HANDOFF with the date it was measured.
|
|
105
108
|
|
|
106
109
|
This is a **deviation from G6 §B.3**, which put `main_thread_busy_pct` in the ratchet, and the reason
|
|
107
110
|
is measured: the instrument that produces it — CDP `Performance.getMetrics`, or
|
package/docs/CONTRACTS.md
CHANGED
|
@@ -9,7 +9,10 @@ same way the fleet's own tool reads it. Everything else is line-oriented shell.
|
|
|
9
9
|
goblin-install --target <dir> [options]
|
|
10
10
|
|
|
11
11
|
--target <dir> required; the repo root to install into
|
|
12
|
-
--class
|
|
12
|
+
--class <class> required unless --uninstall or --re-pin. One of the five domain classes:
|
|
13
|
+
software · service · game · research · fleet. The letters A-E and the
|
|
14
|
+
older names app (software), agent (fleet) and desktop/F (software + the
|
|
15
|
+
electron opt-in) are read-time aliases.
|
|
13
16
|
--models <path> model mapping file (default: $GOBLIN_MODELS -> ~/projects/fleet-model.yaml)
|
|
14
17
|
--practice <path> the referenced standard (default: $GOBLIN_PRACTICE -> ~/projects/PROJECT-PRACTICE.md)
|
|
15
18
|
--parts <list> comma list to install; default = every part the class requires
|
|
@@ -115,7 +118,7 @@ Output is one line per executed row, in manifest order, plus a summary line at t
|
|
|
115
118
|
ADV MD-02 code lane and review lane both resolve to the same family
|
|
116
119
|
SKIP HS-02 no pinned pre-change commit yet - REPLAY not provable
|
|
117
120
|
|
|
118
|
-
Those four lines are one row of each marking. The summary line of a green default class
|
|
121
|
+
Those four lines are one row of each marking. The summary line of a green default software-class run is:
|
|
119
122
|
|
|
120
123
|
38 passed, 0 failed, 11 advisory, 33 skipped
|
|
121
124
|
|
|
@@ -138,7 +141,7 @@ settings that make a workflow a **gate** are written down.
|
|
|
138
141
|
|
|
139
142
|
### A fresh install verifies green
|
|
140
143
|
|
|
141
|
-
Measured on a fresh DEFAULT class
|
|
144
|
+
Measured on a fresh DEFAULT software-class install (skills opt-in, W6 neutral-first), committed with no
|
|
142
145
|
hand edit: **`38 passed, 0 failed, 11 advisory, 33 skipped`, exit 0.** Thirty-three rows skip with
|
|
143
146
|
a reason — the same not-yet rows as before, plus the five skill rows (`SK-01`..`SK-04`,
|
|
144
147
|
`AU-04`) that skip on the `playbooks` opt-out a skills-free install records: `HS-02` — no pre-change commit
|
|
@@ -178,7 +181,7 @@ verifier is reporting FAILs.
|
|
|
178
181
|
- **Per part:** `--opt-out <part>` records the part in `disabled:`. `goblin-verify` then reports
|
|
179
182
|
the part's rows as `SKIP (opt-out)` in the summary, so the opt-out is **visible rather than
|
|
180
183
|
absent**. The same mechanism is what makes a class's `-` (off) real.
|
|
181
|
-
- **The opt-out numbers are pinned (V3-3).** A class
|
|
184
|
+
- **The opt-out numbers are pinned (V3-3).** A software-class install with an explicit `--skills no`
|
|
182
185
|
verifies `38 passed, 0 failed, 11 advisory, 33 skipped`, exit 0, and
|
|
183
186
|
`tests/t-install-off-switch.sh` asserts that line: a silent drift in the opt-out path is caught
|
|
184
187
|
rather than left as a number nobody wrote down (the `--skills no` count moved from `37/0/9/11`
|
|
@@ -201,7 +204,7 @@ verifier is reporting FAILs.
|
|
|
201
204
|
|
|
202
205
|
## The two commands, verbatim
|
|
203
206
|
|
|
204
|
-
bash bin/goblin-install --target /path/to/repo --class
|
|
207
|
+
bash bin/goblin-install --target /path/to/repo --class software
|
|
205
208
|
.goblin/bin/goblin-verify
|
|
206
209
|
|
|
207
210
|
From a checkout, without installing anything:
|
package/docs/ENFORCEMENT.md
CHANGED
|
@@ -164,26 +164,30 @@ OR its `enforced_by` cell does.
|
|
|
164
164
|
the installer records every `-` part in `disabled:`, so its rows report `SKIP (opt-out)`
|
|
165
165
|
instead of silently passing, and `CL-01` fails if a forbidden part's artifact exists.
|
|
166
166
|
|
|
167
|
-
| part |
|
|
168
|
-
|
|
169
|
-
| handoff | R | R | R | R | R |
|
|
170
|
-
| spec | R | R | R | - | R |
|
|
171
|
-
| gate | R | R | R | O | R |
|
|
172
|
-
| replay | R | - | R | - | O |
|
|
173
|
-
| ratchet | R | O | O | - | O |
|
|
174
|
-
| pr-gate | O | - | O | - | O |
|
|
175
|
-
| review-panel | O | - | R | - | O |
|
|
176
|
-
| playbooks | R | R | R | R | R |
|
|
177
|
-
| tokens | O | - | - | - |
|
|
178
|
-
| ci-gate | R | - | O | - | O |
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
167
|
+
| part | software | service | game | research | fleet |
|
|
168
|
+
|---|---|---|---|---|---|
|
|
169
|
+
| handoff | R | R | R | R | R |
|
|
170
|
+
| spec | R | R | R | - | R |
|
|
171
|
+
| gate | R | R | R | O | R |
|
|
172
|
+
| replay | R | - | R | - | O |
|
|
173
|
+
| ratchet | R | O | O | - | O |
|
|
174
|
+
| pr-gate | O | - | O | - | O |
|
|
175
|
+
| review-panel | O | - | R | - | O |
|
|
176
|
+
| playbooks | R | R | R | R | R |
|
|
177
|
+
| tokens | O | - | - | - | O |
|
|
178
|
+
| ci-gate | R | - | O | - | O |
|
|
179
|
+
|
|
180
|
+
The five columns carry the domain names; the letters `A`..`E` and the older names `app` (software)
|
|
181
|
+
and `agent` (fleet) are read-time aliases. The old `F` class — the Electron shell — was merged into
|
|
182
|
+
`software`: the merge measured the two need columns identical on all ten parts, so what it added
|
|
183
|
+
lives in config, not in this table. The **electron opt-in** (`electron: true`) turns the electron
|
|
184
|
+
bans `BN-06`..`BN-09` on even when a hand-edited `bans:` list omits them, and requires a declared
|
|
185
|
+
**host gate** — a number measured on a machine with a display; `docs/CI.md` §3 is the contract for
|
|
186
|
+
it. `ci-gate` is the one part added at W4: when it is required or optional the installer renders
|
|
187
|
+
`templates/ci/goblin-gate.yml.tmpl` into `.github/workflows/goblin-gate.yml`, and when it is `-` the
|
|
188
|
+
artifact must be **absent** (which is why `CL-01` keys off that exact path, not the `.github/`
|
|
189
|
+
directory — a repo is still allowed CI of its own). `docs/CI.md` is the contract for what that file
|
|
190
|
+
does and does not make true.
|
|
187
191
|
|
|
188
192
|
## The ban list (G5)
|
|
189
193
|
|
package/docs/GUIDE.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
A step-by-step guide for your first week. **Read this before the README.** The README tells you
|
|
4
4
|
what the pieces are; this tells you what to *do*, in order, and what you should see when it works.
|
|
5
5
|
|
|
6
|
-
Version: `0.
|
|
6
|
+
Version: `0.5.0` · Last measured: 2026-10-05 · Every command and every output below was run on a
|
|
7
7
|
real repository while writing this guide.
|
|
8
8
|
|
|
9
9
|
---
|
|
@@ -97,12 +97,12 @@ see the plan first:
|
|
|
97
97
|
git config user.email "you@example.com"
|
|
98
98
|
git config user.name "you"
|
|
99
99
|
|
|
100
|
-
gob init --target . --class
|
|
100
|
+
gob init --target . --class software --branch main --email "you@example.com" \
|
|
101
101
|
--gate "bash tests/run-tests.sh" --yes
|
|
102
102
|
|
|
103
103
|
or the plain installer this wizard drives, if you prefer the one-shot shape:
|
|
104
104
|
|
|
105
|
-
gob install --target . --class
|
|
105
|
+
gob install --target . --class software
|
|
106
106
|
|
|
107
107
|
Expected output (this is a real transcript, trimmed):
|
|
108
108
|
|
|
@@ -199,7 +199,7 @@ Everything you configure lives in **one file**, created once and then never over
|
|
|
199
199
|
|
|
200
200
|
Open it. The keys that matter on day one:
|
|
201
201
|
|
|
202
|
-
class:
|
|
202
|
+
class: software # software|service|game|research|fleet (A-E are aliases) - what kind of project this is (step 6)
|
|
203
203
|
branch: main # DECLARED, never assumed
|
|
204
204
|
owner_email: you@example.com # the commit identity this repo expects
|
|
205
205
|
practice: /path/to/your-standard.md # optional: your own house rules, hash-pinned
|
|
@@ -245,19 +245,21 @@ supplies the default gate shape. Choose by asking *what does "done" mean here?*
|
|
|
245
245
|
|
|
246
246
|
| Class | Choose it when | "Done" means |
|
|
247
247
|
|---|---|---|
|
|
248
|
-
| **
|
|
249
|
-
| **B
|
|
250
|
-
| **C
|
|
251
|
-
| **
|
|
252
|
-
| **
|
|
253
|
-
|
|
248
|
+
| **software** (A) | an app, library, or tool users run | a gate set reports measured numbers and a round lands |
|
|
249
|
+
| **service** (B) | an API, schema, route, or deployment config | the contract is unchanged, or the change is deliberate and migrated |
|
|
250
|
+
| **game** (C) | a game | a suite is green **and** a human feel verdict exists |
|
|
251
|
+
| **research** (D) | notes, a vault, a research directory | a question is answered with sources and is findable |
|
|
252
|
+
| **fleet** (E) | your agent's own config (`~/.hermes`) | the change is applied, verified against the artifact, versioned |
|
|
253
|
+
|
|
254
|
+
An Electron app is **software** with `electron: true` — the opt-in adds the electron bans and a host
|
|
255
|
+
gate, not a sixth class. The old `F` letter still resolves there as an install alias.
|
|
254
256
|
|
|
255
257
|
**Two placements people get wrong:**
|
|
256
258
|
|
|
257
|
-
- A repo that holds *output* while the code lives elsewhere → **
|
|
258
|
-
application gates the wrong artifact.
|
|
259
|
-
- A plain input directory that is not a build target → **
|
|
260
|
-
expect no HANDOFF and no gates, and to say so.
|
|
259
|
+
- A repo that holds *output* while the code lives elsewhere → **research**, not **software**. Gating
|
|
260
|
+
it like an application gates the wrong artifact.
|
|
261
|
+
- A plain input directory that is not a build target → **research** with `--archive`, which tells
|
|
262
|
+
verify to expect no HANDOFF and no gates, and to say so.
|
|
261
263
|
|
|
262
264
|
Switch class later by editing `class:` in the config and re-running install. The parts you no longer
|
|
263
265
|
need are recorded as **disabled** and will report `SKIP (opt-out)` rather than failing.
|
|
@@ -392,7 +394,7 @@ the next session.
|
|
|
392
394
|
|
|
393
395
|
## 9. What to expect on day one (so you do not misread it)
|
|
394
396
|
|
|
395
|
-
A class
|
|
397
|
+
A software-class install lands on a specific shape. The scaffold ships one deliberate red — `HP-05`, the
|
|
396
398
|
`0000000` placeholder in `HANDOFF.md` (§4) — so a literal first run prints:
|
|
397
399
|
|
|
398
400
|
37 passed, 1 failed, 11 advisory, 33 skipped (the one FAIL is HP-05)
|
|
@@ -409,7 +411,7 @@ Two readings that are easy to get wrong:
|
|
|
409
411
|
|
|
410
412
|
- **Advisory rows are not passes.** Ten rules are labelled `advisory` — counted, not enforced, and
|
|
411
413
|
nine of them carry no executable check at all. The count is capped by `advisory_ceiling: 10`, and
|
|
412
|
-
a class
|
|
414
|
+
a software-class install already sits at 10 of 10: adding another unenforceable rule fails verify until
|
|
413
415
|
one is removed. That is intentional. (The summary line can print `11 advisory`: the eleventh ADV
|
|
414
416
|
line is `JG-02`, a row with a real command of its own that reports ADV here because your model
|
|
415
417
|
file declares no `judge:` lane — it prints the remedy rather than failing a repo for a fleet's
|
|
@@ -485,7 +487,7 @@ engine and silently de-migrate the record).
|
|
|
485
487
|
|
|
486
488
|
### Commands
|
|
487
489
|
|
|
488
|
-
gob install --target <dir> --class
|
|
490
|
+
gob install --target <dir> --class <software|service|game|research|fleet> [options]
|
|
489
491
|
gob install --target <dir> --uninstall
|
|
490
492
|
gob install --target <dir> --re-pin
|
|
491
493
|
gob install --target <dir> --upgrade
|
|
@@ -581,7 +583,7 @@ with *"prove it was broken first"* — it is the one practice that survives cont
|
|
|
581
583
|
# 1. try it somewhere disposable
|
|
582
584
|
mkdir -p /tmp/gs-try && cd /tmp/gs-try
|
|
583
585
|
git init -b main
|
|
584
|
-
gob install --target . --class
|
|
586
|
+
gob install --target . --class software # expect: created 25 (no skills — those are gob emit)
|
|
585
587
|
|
|
586
588
|
# 2. commit and check
|
|
587
589
|
git add -A && git commit -m "chore: install gobstack"
|
|
@@ -602,7 +604,7 @@ with *"prove it was broken first"* — it is the one practice that survives cont
|
|
|
602
604
|
|
|
603
605
|
# 5. do it for real, in a repo you care about
|
|
604
606
|
cd ~/projects/your-project
|
|
605
|
-
gob install --target . --class
|
|
607
|
+
gob install --target . --class software
|
|
606
608
|
git add -A && git commit -m "chore: adopt gobstack"
|
|
607
609
|
.goblin/bin/goblin-verify
|
|
608
610
|
$EDITOR HANDOFF.md # state / gates (dated!) / next / NOT verified
|
package/docs/LIMITS.md
CHANGED
|
@@ -275,7 +275,8 @@ Electron perf number is a host gate, and the ratchet deliberately carries a diff
|
|
|
275
275
|
flow-style `jobs: {…}` mapping is refused, and a `#` inside a quoted string truncates the line
|
|
276
276
|
it is on. Two further measured gaps: the template's job is `ubuntu-latest` with no cache, so a
|
|
277
277
|
repo whose gate needs a display, a licence, a GPU or a signed-in session cannot use it at all
|
|
278
|
-
(that is a **host** gate — the class-C rule, restated for class F
|
|
278
|
+
(that is a **host** gate — the class-C rule, restated for class F; **corrected 2026-10-02 (W6):**
|
|
279
|
+
F is merged into `software`, so this is the electron opt-in), and a private repo's
|
|
279
280
|
Actions minutes are billed to the account (2,000/month free). Measured ground truth at W4:
|
|
280
281
|
**one** first-party workflow exists in the whole estate and it self-skips; five of the six
|
|
281
282
|
repos with a remote have none.
|
|
@@ -295,7 +296,9 @@ Electron perf number is a host gate, and the ratchet deliberately carries a diff
|
|
|
295
296
|
while the main thread went from 1.8 % to 54.5 % busy, `docs/CI.md` §3.3); an absolute FPS
|
|
296
297
|
claim is not. Three further Electron failure modes are **recorded, not mechanised**, and
|
|
297
298
|
`docs/CI.md` §4 says why: the dependency-graph boundary check, `ipcMain` sender validation,
|
|
298
|
-
and fuses at package time.
|
|
299
|
+
and fuses at package time. **Corrected 2026-10-02 (W6):** this preset is now
|
|
300
|
+
`presets/electron-overlay.yaml`, rendered over `presets/software.yaml` by `--electron` (or the
|
|
301
|
+
`desktop`/`F` install alias) — the old `F` class was merged into `software`.
|
|
299
302
|
36. **A ban's exemption reaches the probe through its environment, so a custom probe can ignore it.**
|
|
300
303
|
`bans_exempt:` and the inline `// BAN-OK(<id>): <reason>` are filtered *before* the exit code is
|
|
301
304
|
chosen, because a filter applied to a probe's stdout afterwards cannot change a verdict — that
|
|
@@ -589,3 +592,17 @@ the Node the gates ran under.
|
|
|
589
592
|
a source row can never fail a user's repo, and a target row can never substitute for the
|
|
590
593
|
framework's own suite. Recorded as a definition; `docs/ENFORCEMENT.md`'s scope paragraph
|
|
591
594
|
carries the same sentence for the reader who arrives there first.
|
|
595
|
+
|
|
596
|
+
53. **The sixth class is gone: the desktop shell is `software` + `electron: true`, and `A`..`E` are
|
|
597
|
+
read-time aliases.** W6 merged `F` into `software` because their `manifest/classes.tsv` need
|
|
598
|
+
columns were measured identical on all ten parts — the old column added config (the electron
|
|
599
|
+
`bans:`, the `perf.host_gate:`, the `app_bundle_bytes` ratchet, the `dist out release`
|
|
600
|
+
build-output scope), never a part. Measured on the merge tree (2026-10-02): the tsv is 50 rows
|
|
601
|
+
over five classes; `gob init --class desktop`, `--class F` and `--class software --electron`
|
|
602
|
+
render byte-identical `goblin.yaml` (`class: software`, `electron: true`); and a pre-merge repo
|
|
603
|
+
carrying `class: F` with no `electron:` key verifies unchanged, `38 passed, 0 failed, 11
|
|
604
|
+
advisory, 33 skipped`, exit 0, with `git status --porcelain` empty — zero writes, because its
|
|
605
|
+
bans and host gate live in its own config. What this costs, recorded rather than fixed: such a
|
|
606
|
+
repo gets the merge's declaration-time host-gate check only after hand-adding `electron: true`;
|
|
607
|
+
its bans and host gate keep running either way, so nothing fails closed, and the CLI keeps
|
|
608
|
+
accepting `desktop`/`F` as aliases.
|
package/manifest/classes.tsv
CHANGED
|
@@ -1,61 +1,51 @@
|
|
|
1
1
|
class part need
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
C tokens -
|
|
53
|
-
D tokens -
|
|
54
|
-
E tokens -
|
|
55
|
-
F tokens O
|
|
56
|
-
A ci-gate R
|
|
57
|
-
B ci-gate -
|
|
58
|
-
C ci-gate O
|
|
59
|
-
D ci-gate -
|
|
60
|
-
E ci-gate O
|
|
61
|
-
F ci-gate R
|
|
2
|
+
software handoff R
|
|
3
|
+
service handoff R
|
|
4
|
+
game handoff R
|
|
5
|
+
research handoff R
|
|
6
|
+
fleet handoff R
|
|
7
|
+
software spec R
|
|
8
|
+
service spec R
|
|
9
|
+
game spec R
|
|
10
|
+
research spec -
|
|
11
|
+
fleet spec R
|
|
12
|
+
software gate R
|
|
13
|
+
service gate R
|
|
14
|
+
game gate R
|
|
15
|
+
research gate O
|
|
16
|
+
fleet gate R
|
|
17
|
+
software replay R
|
|
18
|
+
service replay -
|
|
19
|
+
game replay R
|
|
20
|
+
research replay -
|
|
21
|
+
fleet replay O
|
|
22
|
+
software ratchet R
|
|
23
|
+
service ratchet O
|
|
24
|
+
game ratchet O
|
|
25
|
+
research ratchet -
|
|
26
|
+
fleet ratchet O
|
|
27
|
+
software pr-gate O
|
|
28
|
+
service pr-gate -
|
|
29
|
+
game pr-gate O
|
|
30
|
+
research pr-gate -
|
|
31
|
+
fleet pr-gate O
|
|
32
|
+
software review-panel O
|
|
33
|
+
service review-panel -
|
|
34
|
+
game review-panel R
|
|
35
|
+
research review-panel -
|
|
36
|
+
fleet review-panel O
|
|
37
|
+
software playbooks R
|
|
38
|
+
service playbooks R
|
|
39
|
+
game playbooks R
|
|
40
|
+
research playbooks R
|
|
41
|
+
fleet playbooks R
|
|
42
|
+
software tokens O
|
|
43
|
+
service tokens -
|
|
44
|
+
game tokens -
|
|
45
|
+
research tokens -
|
|
46
|
+
fleet tokens O
|
|
47
|
+
software ci-gate R
|
|
48
|
+
service ci-gate -
|
|
49
|
+
game ci-gate O
|
|
50
|
+
research ci-gate -
|
|
51
|
+
fleet ci-gate O
|
package/package.json
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# presets/electron-overlay.yaml — the electron opt-in for the `software` class.
|
|
2
|
+
#
|
|
3
|
+
# The `desktop` class is gone; F was merged into software because their classes.tsv need
|
|
4
|
+
# columns are IDENTICAL on all 10 parts (measured). Everything F added beyond software lives in
|
|
5
|
+
# plain goblin.yaml config keys, so it is an OVERLAY, not a class: `gob install --class software
|
|
6
|
+
# --electron` (or the `--class desktop` / `F` alias, which sets --electron) renders the keys
|
|
7
|
+
# below OVER the software preset. An absent key falls through to presets/software.yaml; a key
|
|
8
|
+
# present here — including an empty one like sec_write_routes: "" — wins.
|
|
9
|
+
#
|
|
10
|
+
# The prose F carried (label / done_means / notes) is preserved here for the record; it is not a
|
|
11
|
+
# rendered template key, so it never reaches a target's goblin.yaml.
|
|
12
|
+
label: Desktop shell
|
|
13
|
+
done_means: the renderer is isolated from Node, the main process is not busy, and the packaged bundle ships no dev dependency
|
|
14
|
+
# software's A-only second gate (the TODO ceiling) is NOT applied: F never had it.
|
|
15
|
+
gate2_name: ""
|
|
16
|
+
gate2_cmd: ""
|
|
17
|
+
# The perf lane is the shipped ratchet, reused verbatim; the hermetic number here is the
|
|
18
|
+
# packaged bundle's byte count (a fat bundle is a slow cold start on every machine).
|
|
19
|
+
ratchet_name: app_bundle_bytes
|
|
20
|
+
ratchet_cmd: find dist out release -type f -exec cat {} + 2>/dev/null | wc -c
|
|
21
|
+
sec_build_output: dist out release
|
|
22
|
+
# A renderer that reaches the filesystem or Node directly is forbidden, so there are no write
|
|
23
|
+
# routes to allow (software allows app/ and src/app/; the overlay blanks that).
|
|
24
|
+
sec_write_routes: ""
|
|
25
|
+
# perf_metric must EQUAL ratchet_name (PF-01 fails when the budget and the measurement disagree).
|
|
26
|
+
perf_metric: app_bundle_bytes
|
|
27
|
+
perf_cmd: find dist out release -type f -exec cat {} + 2>/dev/null | wc -c
|
|
28
|
+
# THE FPS NUMBER IS A HOST GATE, not the ratchet: the instrument that produces
|
|
29
|
+
# main_thread_busy_pct needs Playwright or Electron plus a GUI, which the no-npm contract
|
|
30
|
+
# forbids a shipped rule to launch. It is DECLARED here and carried in the HANDOFF with its date
|
|
31
|
+
# (docs/LIMITS.md #34, docs/CI.md carries the sweep). `electron: true` with this empty FAILs PF-01.
|
|
32
|
+
perf_host_gate: "Electron run - main_thread_busy_pct with a window open (app.getAppMetrics()[i].cpu.percentCPUUsage, or CDP Performance.getMetrics), on a machine with a display"
|
|
33
|
+
# The nine electron bans (G5's mechanism, G6's failure surface): nodeIntegration, context
|
|
34
|
+
# isolation / sandbox, the dangerous webPreferences, and synchronous IPC / @electron/remote. An
|
|
35
|
+
# unlisted ban SKIPs with a reason, so the other classes are unaffected; with electron: true the
|
|
36
|
+
# engine also treats BN-06..09 as enabled even if a hand-edited bans: list omits them.
|
|
37
|
+
bans: BN-01, BN-02, BN-05, BN-06, BN-07, BN-08, BN-09
|
|
38
|
+
notes: a perf number measured on one box is not a user's experience - the renderer half can be a CI job, the Electron half is a host gate and cannot be.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# presets/
|
|
1
|
+
# presets/fleet.yaml — the `fleet` class (letters/aliases E, agent).
|
|
2
2
|
#
|
|
3
3
|
# "Done" means a config change is applied, verified against the ARTIFACT, and versioned.
|
|
4
4
|
# The gate therefore checks the artifact (a commit exists, dated), never the intention.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# presets/
|
|
1
|
+
# presets/game.yaml — the `game` class (letter/alias C).
|
|
2
2
|
#
|
|
3
3
|
# "Done" means a suite green in the Editor AND a human feel verdict. The verdict is a
|
|
4
4
|
# first-class deliverable, which is why this class requires the review-panel part: the panel
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# presets/
|
|
1
|
+
# presets/research.yaml — the `research` class (letter/alias D).
|
|
2
2
|
#
|
|
3
3
|
# "Done" means a question is answered with sources and the answer is findable. A research
|
|
4
4
|
# note is not a spec, so the SPEC part is OFF, not optional. So are the REPLAY and the PR
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# presets/
|
|
1
|
+
# presets/service.yaml — the `service` class (letter/alias B).
|
|
2
2
|
#
|
|
3
3
|
# "Done" means a contract (schema, route, API) is unchanged, or the change is intentional
|
|
4
4
|
# and migrated. The class turns off the REPLAY and the PR gate, because a config repo has no
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# presets/
|
|
1
|
+
# presets/software.yaml — the `software` class (letters/aliases A, app).
|
|
2
2
|
#
|
|
3
3
|
# A class is not a stringency level. It selects WHICH PARTS are required, optional or off
|
|
4
4
|
# (manifest/classes.tsv), and it supplies the default gate/ratchet shape below. The gate
|
|
@@ -7,12 +7,15 @@ description: P8: adopt goblin-stack in a repo - classify, install, verify, then
|
|
|
7
7
|
|
|
8
8
|
Use when adopting goblin-stack in a repo, or starting one.
|
|
9
9
|
|
|
10
|
-
1. **Classify the project
|
|
11
|
-
|
|
10
|
+
1. **Classify the project into one of five classes.** `software`, `service`, `game`, `research` or
|
|
11
|
+
`fleet` — the class selects which parts are required, optional or off; it is not a stringency
|
|
12
|
+
level. `software` also carries the electron opt-in (`--electron`): an Electron app is `software`
|
|
13
|
+
with the electron bans and a host gate, not a sixth class. The letters `A`-`E` and the older
|
|
14
|
+
names are read-time aliases.
|
|
12
15
|
2. **`goblin-install --target <dir> --class <x>`** — the default install is a NEUTRAL harness:
|
|
13
16
|
no agent skills. Opt in per platform afterwards with `gob emit --platform <p>` (or vendor the
|
|
14
17
|
Hermes project tier with `--skills yes`).
|
|
15
|
-
3. **`goblin-verify`** — a default class
|
|
18
|
+
3. **`goblin-verify`** — a default software-class install (no agent skills) verifies green:
|
|
16
19
|
`38 passed, 0 failed, 11 advisory, 33 skipped`, exit 0, once `HANDOFF.md` names a commit that
|
|
17
20
|
exists; before that edit the
|
|
18
21
|
scaffold's `0000000` placeholder is `HP-05`'s one expected day-one red (`37 passed, 1 failed`).
|
|
@@ -4,9 +4,17 @@
|
|
|
4
4
|
# Format: a flat, line-oriented YAML subset, parsed by .goblin/bin/goblin-lib.sh. No YAML
|
|
5
5
|
# library, no network, no npm. Keep one key per line and keep comments on their own line.
|
|
6
6
|
|
|
7
|
-
# Which class this project is
|
|
7
|
+
# Which class this project is: software | service | game | research | fleet.
|
|
8
|
+
# The letters A-E and the older names app/agent/desktop are READ-TIME aliases, so an old
|
|
9
|
+
# value keeps verifying; `desktop`/`F` resolve to software. Selects the required parts
|
|
10
|
+
# (manifest/classes.tsv).
|
|
8
11
|
class: {{CLASS}}
|
|
9
12
|
|
|
13
|
+
# Electron opt-in (software class). true turns on the electron ban set (BN-06..09) even if the
|
|
14
|
+
# bans: list below omits them, and requires a declared perf.host_gate (PF-01 fails without one).
|
|
15
|
+
# The `desktop`/`F` install alias sets this true; a text-editor app leaves it false.
|
|
16
|
+
electron: {{ELECTRON}}
|
|
17
|
+
|
|
10
18
|
# The default branch, DECLARED, never assumed. A preset that assumes the wrong branch
|
|
11
19
|
# silently skips a repo.
|
|
12
20
|
branch: {{BRANCH}}
|
package/presets/F-electron.yaml
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# presets/F-electron.yaml — class F: desktop shell (Electron).
|
|
2
|
-
#
|
|
3
|
-
# A class is not a stringency level: it selects WHICH PARTS are required, optional or off
|
|
4
|
-
# (manifest/classes.tsv) and supplies the default gate/ratchet shape. A desktop shell needs a
|
|
5
|
-
# part no other class has — a HOST gate, a number measured on a machine with a display — and it
|
|
6
|
-
# forbids a thing the others allow: a renderer that reaches the filesystem or Node directly.
|
|
7
|
-
# That is a new row-set, not a flag on class A (G6 section B.2, Part B).
|
|
8
|
-
#
|
|
9
|
-
# THE PERF LANE IS THE SHIPPED RATCHET, NOT A SECOND ONE (G6 section B.3). There is one
|
|
10
|
-
# mechanism — `ratchet: {name, cmd, ceiling}`, enforced by GT-04/GT-05 and pinned to a commit by
|
|
11
|
-
# PF-01 — and this class reuses it verbatim. What the ratchet measures here is the one hermetic
|
|
12
|
-
# perf number a desktop shell has: the packaged bundle's byte count. A fat bundle is a slow
|
|
13
|
-
# cold start on every machine, and the number needs no browser, no display and no dependency.
|
|
14
|
-
#
|
|
15
|
-
# THE FPS NUMBER IS A HOST GATE, and this is a DEVIATION from G6 section B.3, recorded with its
|
|
16
|
-
# measured reason. G6 wants `main_thread_busy_pct` in the ratchet. It cannot be: the instrument
|
|
17
|
-
# that produces it (CDP `Performance.getMetrics` over a real Chromium, or
|
|
18
|
-
# `app.getAppMetrics()[i].cpu.percentCPUUsage` inside a real Electron) needs Playwright or
|
|
19
|
-
# Electron plus a GUI, and the dependency contract (docs/CONTRACTS.md) allows a shipped rule
|
|
20
|
-
# nothing but bash/git/awk/sed/grep/python3. A `ratchet.cmd` that cannot run on a fresh install
|
|
21
|
-
# makes a fresh install BORN RED, which is the one thing every class must not be. So the probe
|
|
22
|
-
# belongs to the project, next to the code it measures, and the number it produces is declared
|
|
23
|
-
# here as a host gate and carried in the HANDOFF with its date (the class C pattern).
|
|
24
|
-
#
|
|
25
|
-
# Why frame time is the WRONG number, measured in G6 on a real Chromium (CDP, 0 to 32 ms of
|
|
26
|
-
# work per frame): p50 frame time stayed FLAT at 16.70 ms while the main thread went from 1.8%
|
|
27
|
-
# to 54.5% busy, and the dropped-frame count was non-monotone (0,0,0,1,3,11,0 — the worst
|
|
28
|
-
# workload read 0). A frame-time gate at 16.7 ms is green on the idle tree AND on the loaded
|
|
29
|
-
# tree: PROJECT-PRACTICE section 3, reproduced live in the exact metric the note proposed.
|
|
30
|
-
# `main_thread_busy_pct` is the only monotone instrument in that sweep (1.8 -> 13.5 -> 26.0 ->
|
|
31
|
-
# 54.5 -> 76.9 -> 99.5%), which is why it is named here as the host gate's metric.
|
|
32
|
-
# docs/CI.md carries the sweep; docs/LIMITS.md #34 carries the gap.
|
|
33
|
-
label: Desktop shell
|
|
34
|
-
done_means: the renderer is isolated from Node, the main process is not busy, and the packaged bundle ships no dev dependency
|
|
35
|
-
harness_dir: checks
|
|
36
|
-
scaffold_checks: yes
|
|
37
|
-
gate_name: commit
|
|
38
|
-
gate_cmd: git rev-parse --verify --quiet HEAD
|
|
39
|
-
ratchet_name: app_bundle_bytes
|
|
40
|
-
ratchet_cmd: find dist out release -type f -exec cat {} + 2>/dev/null | wc -c
|
|
41
|
-
ratchet_ceiling: measure
|
|
42
|
-
replay_env: GOBLIN_PRE_COMMIT
|
|
43
|
-
replay_cmd: node checks/{name}.mjs
|
|
44
|
-
runtime_data: .goblin/state.json
|
|
45
|
-
sec_gitignore_family: yes
|
|
46
|
-
sec_build_output: dist out release
|
|
47
|
-
sec_audit_cmd: npm audit --json
|
|
48
|
-
sec_audit_max_age_days: 90
|
|
49
|
-
sec_waiver_max_age_days: 180
|
|
50
|
-
sec_write_routes: ""
|
|
51
|
-
# perf_metric must EQUAL ratchet_name (PF-01 fails when the budget and the measurement disagree),
|
|
52
|
-
# so the hermetic metric is named here too, and the host gate carries the FPS number beside it.
|
|
53
|
-
perf_metric: app_bundle_bytes
|
|
54
|
-
perf_cmd: find dist out release -type f -exec cat {} + 2>/dev/null | wc -c
|
|
55
|
-
perf_baseline_commit: ""
|
|
56
|
-
perf_baseline_value: 0
|
|
57
|
-
perf_measured: ""
|
|
58
|
-
perf_host_gate: "Electron run - main_thread_busy_pct with a window open (app.getAppMetrics()[i].cpu.percentCPUUsage, or CDP Performance.getMetrics), on a machine with a display"
|
|
59
|
-
# The nine electron bans (G5's mechanism, G6's failure surface): nodeIntegration, context
|
|
60
|
-
# isolation / sandbox, the dangerous webPreferences, and synchronous IPC / @electron/remote.
|
|
61
|
-
# An unlisted ban SKIPs with a reason, so the other classes are unaffected by this list.
|
|
62
|
-
bans: BN-01, BN-02, BN-05, BN-06, BN-07, BN-08, BN-09
|
|
63
|
-
notes: a perf number measured on one box is not a user's experience - the renderer half can be a CI job, the Electron half is a host gate and cannot be.
|
|
64
|
-
|
|
65
|
-
# The loop ceiling (G2, LP-03): the most turns ONE loop record may declare in its budget.
|
|
66
|
-
# The same number for every class - a ceiling, not a per-class policy. 20 is the engine default.
|
|
67
|
-
loop_max_turns_ceiling: 20
|