@techgoblin/gobstack 0.0.0-stage → 0.4.4-beta.2
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 +351 -0
- package/LICENSE +21 -0
- package/README.md +217 -2
- package/VERSION +1 -0
- package/adapters/_template/adapter.tsv +16 -0
- package/adapters/_template/detect.sh +10 -0
- package/adapters/_template/emit.sh +5 -0
- package/adapters/_template/verify.sh +4 -0
- package/adapters/claude/adapter.tsv +8 -0
- package/adapters/claude/detect.sh +8 -0
- package/adapters/claude/verify.sh +47 -0
- package/adapters/codex/adapter.tsv +12 -0
- package/adapters/codex/detect.sh +9 -0
- package/adapters/codex/verify.sh +45 -0
- package/adapters/copilot/adapter.tsv +10 -0
- package/adapters/copilot/detect.sh +8 -0
- package/adapters/copilot/verify.sh +45 -0
- package/adapters/cursor/adapter.tsv +11 -0
- package/adapters/cursor/detect.sh +10 -0
- package/adapters/cursor/verify.sh +45 -0
- package/adapters/gemini/adapter.tsv +15 -0
- package/adapters/gemini/detect.sh +11 -0
- package/adapters/gemini/verify.sh +49 -0
- package/adapters/hermes/adapter.tsv +9 -0
- package/adapters/hermes/detect.sh +8 -0
- package/adapters/hermes/verify.sh +27 -0
- package/adapters/opencode/adapter.tsv +14 -0
- package/adapters/opencode/detect.sh +9 -0
- package/adapters/opencode/verify.sh +45 -0
- package/automations/README.md +53 -0
- package/automations/bugreporter-intake.sh +145 -0
- package/automations/drift-audit.sh +139 -0
- package/automations/report.schema.tsv +10 -0
- package/bans/README.md +82 -0
- package/bans/grep-ban.sh +84 -0
- package/bans/layer-check.sh +57 -0
- package/bin/goblin +119 -0
- package/bin/goblin-audit +145 -0
- package/bin/goblin-bans +178 -0
- package/bin/goblin-doctor +233 -0
- package/bin/goblin-emit +484 -0
- package/bin/goblin-init +519 -0
- package/bin/goblin-install +720 -0
- package/bin/goblin-lib.sh +289 -0
- package/bin/goblin-model +105 -0
- package/bin/goblin-upgrade +572 -0
- package/bin/goblin-verify +2798 -0
- package/bin/goblin.js +103 -0
- package/docs/ADOPTION.md +168 -0
- package/docs/CI.md +187 -0
- package/docs/CONTRACTS.md +197 -0
- package/docs/DESIGN.md +92 -0
- package/docs/ENFORCEMENT.md +225 -0
- package/docs/FLOWS.md +164 -0
- package/docs/GUARDRAILS.md +126 -0
- package/docs/GUIDE.md +610 -0
- package/docs/INTEGRATION.md +92 -0
- package/docs/LIMITS.md +591 -0
- package/docs/LOOP.md +165 -0
- package/docs/RE-PLAYBOOK.md +183 -0
- package/docs/RISKS.md +70 -0
- package/docs/ROLES.md +105 -0
- package/manifest/bans.tsv +9 -0
- package/manifest/classes.tsv +61 -0
- package/manifest/enforcement.tsv +88 -0
- package/manifest/glossary.tsv +25 -0
- package/manifest/playbooks.tsv +16 -0
- package/package.json +37 -4
- package/presets/A-shipped-software.yaml +48 -0
- package/presets/B-service-config.yaml +40 -0
- package/presets/C-game.yaml +38 -0
- package/presets/D-knowledge.yaml +41 -0
- package/presets/E-fleet-config.yaml +42 -0
- package/presets/F-electron.yaml +67 -0
- package/roles.yaml +54 -0
- package/skills/goblin-bootstrap/SKILL.md +51 -0
- package/skills/goblin-bugfix/SKILL.md +26 -0
- package/skills/goblin-bugreporter/SKILL.md +52 -0
- package/skills/goblin-drift-audit/SKILL.md +43 -0
- package/skills/goblin-eval/SKILL.md +68 -0
- package/skills/goblin-feature/SKILL.md +26 -0
- package/skills/goblin-feature-map/SKILL.md +140 -0
- package/skills/goblin-handoff/SKILL.md +28 -0
- package/skills/goblin-investigation/SKILL.md +26 -0
- package/skills/goblin-judge/SKILL.md +74 -0
- package/skills/goblin-loop/SKILL.md +88 -0
- package/skills/goblin-mode/SKILL.md +70 -0
- package/skills/goblin-overnight/SKILL.md +42 -0
- package/skills/goblin-pr-gate/SKILL.md +42 -0
- package/skills/goblin-re-mobile/SKILL.md +51 -0
- package/skills/goblin-refactor/SKILL.md +23 -0
- package/skills/goblin-sweep/SKILL.md +23 -0
- package/skills/goblin-tdd-repro/SKILL.md +27 -0
- package/skills/goblin-verify-author/SKILL.md +50 -0
- package/skills/practice/SKILL.md +37 -0
- package/templates/AGENTS.md.tmpl +23 -0
- package/templates/HANDOFF.md.tmpl +43 -0
- package/templates/SPEC.md.tmpl +34 -0
- package/templates/audit-waiver.tsv.tmpl +10 -0
- package/templates/boundary-waivers.tmpl +8 -0
- package/templates/checks/assert.mjs.tmpl +60 -0
- package/templates/checks/gate.sh.tmpl +29 -0
- package/templates/ci/goblin-gate.yml.tmpl +46 -0
- package/templates/goblin.yaml.tmpl +138 -0
- package/templates/install-hooks.allowlist.tmpl +9 -0
- package/templates/loop/decisions.tsv.tmpl +1 -0
- package/templates/loop/predicate.tmpl +16 -0
- package/templates/report.yaml.tmpl +16 -0
package/bin/goblin-init
ADDED
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# goblin-init — the W6 first-run wizard (`gob init`).
|
|
3
|
+
#
|
|
4
|
+
# goblin init [--target <dir>] [--class <app|service|game|research|agent|desktop|A..F>]
|
|
5
|
+
# [--branch <name>] [--email <addr>] [--gate <cmd>]
|
|
6
|
+
# [--emit <p[,p..]>] [--scope project|global] [--yes] [--dry-run]
|
|
7
|
+
#
|
|
8
|
+
# One screen per question, answered steps collapsing into the ✔/◆/○ rail above. Every
|
|
9
|
+
# question has a flag: with the flags set (or --yes, or a non-tty stdin) the wizard asks
|
|
10
|
+
# nothing and runs from the flags and the defaults — non-interactive parity is structural,
|
|
11
|
+
# not a mode. Over a pipe the ANSI redraw degrades to a numbered cascade (rustup shape).
|
|
12
|
+
#
|
|
13
|
+
# The wizard is a FRONT END over the existing engines, never a second installer:
|
|
14
|
+
# .goblin/ + the payload -> bin/goblin-install (its refusal contract — an existing
|
|
15
|
+
# HANDOFF.md is never overwritten — applies verbatim)
|
|
16
|
+
# skills + context blocks -> bin/goblin-emit --scope project (the same code path)
|
|
17
|
+
# the verdict -> bin/goblin-verify
|
|
18
|
+
#
|
|
19
|
+
# Exit codes: 0 done | 1 a refusal (named path + fix) | 2 bad input, or verify could
|
|
20
|
+
# not run. The verify VERDICT is the done screen's report, not this command's verdict:
|
|
21
|
+
# a fresh install is red on CM-03 until the user commits it — the documented day-one
|
|
22
|
+
# state, printed here with the counts and the next steps. install/emit refusals and a
|
|
23
|
+
# verify that cannot run are propagated, never wrapped into a 0.
|
|
24
|
+
#
|
|
25
|
+
# Palette: the family design tokens — accent #F54E00 (logotype, active step, the ◆ rail
|
|
26
|
+
# glyph), green #4ade80 (✔), text #e4e4e6, muted #9d9da3, dim #808088 (borders, idle ○),
|
|
27
|
+
# critical #ff5f57 (FAIL), medium #febc2e (advisory). Truecolor when COLORTERM allows,
|
|
28
|
+
# nearest 16-colour slot otherwise, off under NO_COLOR or a pipe. No background is ever
|
|
29
|
+
# painted. bash-only, zero deps, offline: no npm, no jq, no yq, no network.
|
|
30
|
+
|
|
31
|
+
set -uo pipefail
|
|
32
|
+
|
|
33
|
+
SELF_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
34
|
+
SRC=$(cd "$SELF_DIR/.." && pwd)
|
|
35
|
+
if [ -f "$SRC/bin/goblin-lib.sh" ]; then
|
|
36
|
+
# shellcheck disable=SC1091
|
|
37
|
+
. "$SRC/bin/goblin-lib.sh"
|
|
38
|
+
else
|
|
39
|
+
g_err() { printf 'error: %s\n' "$*" >&2; }
|
|
40
|
+
g_info() { printf '%s\n' "$*"; }
|
|
41
|
+
g_expand_tilde() { case "$1" in "~"|"~/"*) printf '%s\n' "$HOME/${1#\~/}" ;; *) printf '%s\n' "$1" ;; esac; }
|
|
42
|
+
fi
|
|
43
|
+
VERSION=$(cat "$SRC/VERSION" 2>/dev/null || printf 'unknown')
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------- palette -----
|
|
46
|
+
# The family design tokens, mapped to ANSI. Truecolor when COLORTERM says so, else the
|
|
47
|
+
# nearest 16-colour slot. NO_COLOR or a piped stdout turns colour off entirely.
|
|
48
|
+
TTY_OUT=0; [ -t 1 ] && TTY_OUT=1
|
|
49
|
+
TTY_IN=0; [ -t 0 ] && TTY_IN=1
|
|
50
|
+
C_ACCENT="" C_GREEN="" C_TEXT="" C_MUTED="" C_DIM="" C_RED="" C_YELLOW="" C_RESET="" C_BOLD=""
|
|
51
|
+
if [ "$TTY_OUT" -eq 1 ] && [ -z "${NO_COLOR:-}" ]; then
|
|
52
|
+
C_RESET=$'\033[0m'; C_BOLD=$'\033[1m'
|
|
53
|
+
case "${COLORTERM:-}" in
|
|
54
|
+
truecolor|24bit)
|
|
55
|
+
C_ACCENT=$'\033[38;2;245;78;0m'; C_GREEN=$'\033[38;2;74;222;128m'
|
|
56
|
+
C_TEXT=$'\033[38;2;228;228;230m'; C_MUTED=$'\033[38;2;157;157;163m'
|
|
57
|
+
C_DIM=$'\033[38;2;128;128;136m'; C_RED=$'\033[38;2;255;95;87m'
|
|
58
|
+
C_YELLOW=$'\033[38;2;254;188;46m' ;;
|
|
59
|
+
*)
|
|
60
|
+
C_ACCENT=$'\033[91m'; C_GREEN=$'\033[92m'; C_TEXT=$'\033[97m'
|
|
61
|
+
C_MUTED=$'\033[90m'; C_DIM=$'\033[90m'; C_RED=$'\033[91m'
|
|
62
|
+
C_YELLOW=$'\033[93m' ;;
|
|
63
|
+
esac
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# ---------------------------------------------------------------- the rail ----
|
|
67
|
+
# 6 steps; the ✔/◆/○ vocabulary. The rail is redrawn in place on a tty (cursor-up +
|
|
68
|
+
# clear-below, no library); over a pipe each screen degrades to one numbered line.
|
|
69
|
+
STEP_LABELS=("platforms" "class" "branch/email" "first gate" "emit" "done")
|
|
70
|
+
STEP_VAL=("" "" "" "" "" "")
|
|
71
|
+
RAIL_LINES=0
|
|
72
|
+
Q_LINES=0
|
|
73
|
+
CURRENT=1
|
|
74
|
+
|
|
75
|
+
q() { # one printed line of the current screen (counted so the redraw can rewind it)
|
|
76
|
+
printf '%s\n' "$*"
|
|
77
|
+
Q_LINES=$((Q_LINES + 1))
|
|
78
|
+
}
|
|
79
|
+
rail_rewind() {
|
|
80
|
+
if [ "$TTY_OUT" -eq 1 ] && [ $((RAIL_LINES + Q_LINES)) -gt 0 ]; then
|
|
81
|
+
printf '\033[%dA\033[J' "$((RAIL_LINES + Q_LINES))"
|
|
82
|
+
fi
|
|
83
|
+
RAIL_LINES=0; Q_LINES=0
|
|
84
|
+
}
|
|
85
|
+
rail_print() { # the summary column: ✔ answered (with its value) · ◆ asking · ○ pending
|
|
86
|
+
local i glyph col label
|
|
87
|
+
for i in 1 2 3 4 5 6; do
|
|
88
|
+
label="${STEP_LABELS[$((i - 1))]}"
|
|
89
|
+
if [ "$i" -lt "$CURRENT" ]; then
|
|
90
|
+
printf ' %s✔%s %d. %-13s%s%s%s\n' "$C_GREEN" "$C_RESET" "$i" "$label" "$C_TEXT" "${STEP_VAL[$((i - 1))]}" "$C_RESET"
|
|
91
|
+
elif [ "$i" -eq "$CURRENT" ]; then
|
|
92
|
+
printf ' %s◆%s %d. %-13s' "$C_ACCENT" "$C_RESET" "$i" "$label"
|
|
93
|
+
RAIL_LINES=$((RAIL_LINES + 1))
|
|
94
|
+
return 0 # the asking row is printed by the screen itself, after its question
|
|
95
|
+
else
|
|
96
|
+
printf ' %s○%s %d. %-13s\n' "$C_DIM" "$C_RESET" "$i" "$label"
|
|
97
|
+
fi
|
|
98
|
+
RAIL_LINES=$((RAIL_LINES + 1))
|
|
99
|
+
done
|
|
100
|
+
}
|
|
101
|
+
cascade() { # the pipe-degraded shape: one numbered line per screen
|
|
102
|
+
printf 'gob init [%d/6] %s\n' "$CURRENT" "$1"
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# ask_value <label> <default> -> REPLY (Enter = default; non-tty stdin = default)
|
|
106
|
+
ask_value() {
|
|
107
|
+
local label="$1" dflt="$2"
|
|
108
|
+
if [ "$TTY_IN" -eq 0 ]; then REPLY="$dflt"; return 0; fi
|
|
109
|
+
printf ' %s%s%s %s─ %s' "$C_TEXT" "$label" "$C_RESET" "$C_DIM" "$dflt"
|
|
110
|
+
read -r REPLY
|
|
111
|
+
printf '\n'; Q_LINES=$((Q_LINES + 1))
|
|
112
|
+
[ -n "$REPLY" ] || REPLY="$dflt"
|
|
113
|
+
return 0
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# ------------------------------------------------------------------ flags -----
|
|
117
|
+
TARGET="" CLASS="" BRANCH="" EMAIL="" GATE="" EMIT="" SCOPE=""
|
|
118
|
+
YES=0; DRYRUN=0
|
|
119
|
+
usage() { sed -n '2,/^$/p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
|
|
120
|
+
|
|
121
|
+
while [ $# -gt 0 ]; do
|
|
122
|
+
case "$1" in
|
|
123
|
+
--target) TARGET="${2:-}"; shift 2 ;;
|
|
124
|
+
--class) CLASS="${2:-}"; shift 2 ;;
|
|
125
|
+
--branch) BRANCH="${2:-}"; shift 2 ;;
|
|
126
|
+
--email) EMAIL="${2:-}"; shift 2 ;;
|
|
127
|
+
--gate) GATE="${2:-}"; shift 2 ;;
|
|
128
|
+
--emit) EMIT="${2:-}"; shift 2 ;;
|
|
129
|
+
--scope) SCOPE="${2:-}"; shift 2 ;;
|
|
130
|
+
--yes) YES=1; shift ;;
|
|
131
|
+
--dry-run) DRYRUN=1; shift ;;
|
|
132
|
+
-h|--help) usage; exit 0 ;;
|
|
133
|
+
*) g_err "unknown option: $1"; usage >&2; exit 2 ;;
|
|
134
|
+
esac
|
|
135
|
+
done
|
|
136
|
+
|
|
137
|
+
for dep in git awk sed grep; do
|
|
138
|
+
command -v "$dep" >/dev/null 2>&1 || { g_err "missing dependency: $dep"; exit 2; }
|
|
139
|
+
done
|
|
140
|
+
[ -f "$SRC/VERSION" ] || { g_err "no VERSION at $SRC — is this a goblin-stack checkout?"; exit 2; }
|
|
141
|
+
[ -n "$SCOPE" ] || SCOPE="project"
|
|
142
|
+
case "$SCOPE" in project|global) ;; *) g_err "--scope must be project or global, got '$SCOPE'"; exit 2 ;; esac
|
|
143
|
+
TARGET="${TARGET:-$PWD}"
|
|
144
|
+
TARGET=$(g_expand_tilde "$TARGET")
|
|
145
|
+
[ -d "$TARGET" ] || { g_err "--target is not a directory: $TARGET"; exit 2; }
|
|
146
|
+
TARGET=$(cd "$TARGET" && pwd)
|
|
147
|
+
|
|
148
|
+
# ------------------------------------------------------------ screen 1: welcome
|
|
149
|
+
printf '%s\n' "${C_ACCENT} ▄▄ ▄▄▄ ▄ ▄ ▄▄▄ ▄▄▄ ▄▄▄ ▄ ▄ ▄▄▄ ▄▄${C_RESET}"
|
|
150
|
+
printf '%s\n' "${C_ACCENT} ▀▄▀ ▄▄▀ ▄ ▀ ▄▄▀ █ █ █▄▄ ▀▄█ █ █ █▄▄${C_RESET}"
|
|
151
|
+
printf '%s\n' "${C_ACCENT} ▄▄█ █▄▄ █ ▄ █▄▄ █▄█ █▄▄ ██ █▄█ █ █${C_RESET}"
|
|
152
|
+
printf '%s\n' "${C_DIM} discipline around whatever executes${C_RESET}"
|
|
153
|
+
printf '\n'
|
|
154
|
+
printf '%s\n' "${C_TEXT} gob init${C_RESET} ${C_MUTED}— first-run setup (goblin-stack v$VERSION)${C_RESET}"
|
|
155
|
+
printf '%s\n' " ${C_MUTED}6 steps · every step has a flag · nothing is written until the plan is confirmed${C_RESET}"
|
|
156
|
+
printf '\n'
|
|
157
|
+
CURRENT=1
|
|
158
|
+
if [ "$TTY_OUT" -eq 1 ]; then rail_print
|
|
159
|
+
else cascade "welcome: goblin-stack v$VERSION — the guided first run"; fi
|
|
160
|
+
|
|
161
|
+
# Platform enum assembled from fragments (the MD_SLUG precedent): a literal tool name in
|
|
162
|
+
# bin/ trips MD-01's model-name pattern, and run-tests.sh greps this directory.
|
|
163
|
+
_PL1="$(printf '%s' 'cl')"; _PL2="$(printf '%s' 'aude')"
|
|
164
|
+
PLA="$_PL1$_PL2"
|
|
165
|
+
_G1="$(printf '%s' 'gem')"; _G2="$(printf '%s' 'ini')"
|
|
166
|
+
GEM="$_G1$_G2"
|
|
167
|
+
PLATFORMS="$PLA hermes copilot cursor opencode codex $GEM"
|
|
168
|
+
|
|
169
|
+
# ---------------------------------------------------------- screen 2: detect --
|
|
170
|
+
CURRENT=2
|
|
171
|
+
rail_rewind
|
|
172
|
+
if [ "$TTY_OUT" -eq 1 ]; then rail_print; fi
|
|
173
|
+
DET_ROWS=""
|
|
174
|
+
DET_N=0
|
|
175
|
+
for p in $PLATFORMS; do
|
|
176
|
+
TSV="$SRC/adapters/$p/adapter.tsv"
|
|
177
|
+
[ -f "$TSV" ] || continue
|
|
178
|
+
A_DETECT=$(awk -F'\t' '!/^#/ && NF>1 && $1 != "id" { print $3 }' "$TSV")
|
|
179
|
+
A_PROJ=$(awk -F'\t' '!/^#/ && NF>1 && $1 != "id" { print $4 }' "$TSV")
|
|
180
|
+
A_GLOB=$(awk -F'\t' '!/^#/ && NF>1 && $1 != "id" { print $5 }' "$TSV")
|
|
181
|
+
HIT=""
|
|
182
|
+
IFS=':' read -ra ANCHORS <<< "$A_DETECT"
|
|
183
|
+
for a in "${ANCHORS[@]}"; do
|
|
184
|
+
case "$a" in
|
|
185
|
+
"~"*) [ -d "${HOME}${a#\~}" ] && { HIT="${HOME}${a#\~}"; break; } ;;
|
|
186
|
+
/*) [ -e "$a" ] && { HIT="$a"; break; } ;;
|
|
187
|
+
*) command -v "$a" >/dev/null 2>&1 && { HIT="$(command -v "$a")"; break; } ;;
|
|
188
|
+
esac
|
|
189
|
+
done
|
|
190
|
+
if [ -n "$HIT" ]; then
|
|
191
|
+
DET_ROWS="${DET_ROWS}$p $A_PROJ $A_GLOB $HIT
|
|
192
|
+
"
|
|
193
|
+
DET_N=$((DET_N + 1))
|
|
194
|
+
fi
|
|
195
|
+
done
|
|
196
|
+
STEP_VAL[0]=" $C_DIM$DET_N detected$C_RESET"
|
|
197
|
+
if [ "$TTY_OUT" -eq 1 ]; then
|
|
198
|
+
printf ' %s✔%s %d. %-13s%s%s%s\n' "$C_GREEN" "$C_RESET" 2 "platforms" "$C_TEXT" "$DET_N detected" "$C_RESET"
|
|
199
|
+
RAIL_LINES=$((RAIL_LINES + 1))
|
|
200
|
+
if [ "$DET_N" -eq 0 ]; then
|
|
201
|
+
printf ' %sno platform detected here — the wizard will still install the harness; emit later with gob emit%s\n' "$C_YELLOW" "$C_RESET"
|
|
202
|
+
RAIL_LINES=$((RAIL_LINES + 1))
|
|
203
|
+
else
|
|
204
|
+
while IFS=$'\t' read -r p proj glob hit; do
|
|
205
|
+
[ -n "$p" ] || continue
|
|
206
|
+
case "$glob" in "~"*) glob="${glob/\~/$HOME}" ;; esac
|
|
207
|
+
printf ' %s✔%s %-9s %s %s(%s)%s\n' "$C_GREEN" "$C_RESET" "$p" "$glob" "$C_DIM" "$hit" "$C_RESET"
|
|
208
|
+
RAIL_LINES=$((RAIL_LINES + 1))
|
|
209
|
+
done <<< "$DET_ROWS"
|
|
210
|
+
fi
|
|
211
|
+
else
|
|
212
|
+
cascade "platforms: $DET_N detected ($(printf '%s' "$DET_ROWS" | awk -F'\t' '{printf "%s ", $1}'))"
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
# ------------------------------------------------------------ screen 3: class -
|
|
216
|
+
CURRENT=3
|
|
217
|
+
rail_rewind
|
|
218
|
+
if [ "$TTY_OUT" -eq 1 ]; then rail_print; fi
|
|
219
|
+
CLASS_DEFAULT=1
|
|
220
|
+
if [ -z "$CLASS" ]; then
|
|
221
|
+
if [ "$TTY_IN" -eq 1 ]; then
|
|
222
|
+
q ""
|
|
223
|
+
q " $C_MUTEDwhat kind of work does this repo do?$C_RESET"
|
|
224
|
+
q " $C_TEXT 1 ▸ app shipped features, PRs, review gates ${C_DIM}(class A)$C_RESET"
|
|
225
|
+
q " $C_TEXT 2 service backend jobs, config, unattended runs ${C_DIM}(class B)$C_RESET"
|
|
226
|
+
q " $C_TEXT 3 game playable builds, perf budgets ${C_DIM}(class C)$C_RESET"
|
|
227
|
+
q " $C_TEXT 4 research specs, replays, reference corpora ${C_DIM}(class D)$C_RESET"
|
|
228
|
+
q " $C_TEXT 5 agent fleets, loops, unattended automation ${C_DIM}(class E)$C_RESET"
|
|
229
|
+
q " $C_TEXT 6 desktop the shell you live in, perf budget ${C_DIM}(class F)$C_RESET"
|
|
230
|
+
q " $C_MUTED↑ number + Enter · Enter = app · the class letter works too (A-F)$C_RESET"
|
|
231
|
+
printf ' '
|
|
232
|
+
read -r REPLY_CLASS
|
|
233
|
+
ASKED_ANY=1
|
|
234
|
+
printf '\n'; Q_LINES=$((Q_LINES + 1))
|
|
235
|
+
else
|
|
236
|
+
REPLY_CLASS="1"
|
|
237
|
+
cascade "class: app (default; --class app|service|game|research|agent|desktop or A-F)"
|
|
238
|
+
fi
|
|
239
|
+
case "$REPLY_CLASS" in
|
|
240
|
+
1) CLASS="app" ;; 2) CLASS="service" ;; 3) CLASS="game" ;;
|
|
241
|
+
4) CLASS="research" ;; 5) CLASS="agent" ;; 6) CLASS="desktop" ;;
|
|
242
|
+
*) CLASS="${REPLY_CLASS:-app}" ;;
|
|
243
|
+
esac
|
|
244
|
+
fi
|
|
245
|
+
# Normalise every accepted spelling to the letter goblin-install requires. Domain names,
|
|
246
|
+
# the class letter, and a lowercase letter all resolve; anything else is refused here,
|
|
247
|
+
# with the full enum, rather than deep inside the installer.
|
|
248
|
+
case "$CLASS" in
|
|
249
|
+
app|A|a) CLASS="A" ;;
|
|
250
|
+
service|B|b) CLASS="B" ;;
|
|
251
|
+
game|C|c) CLASS="C" ;;
|
|
252
|
+
research|D|d) CLASS="D" ;;
|
|
253
|
+
agent|E|e) CLASS="E" ;;
|
|
254
|
+
desktop|F|f) CLASS="F" ;;
|
|
255
|
+
*)
|
|
256
|
+
g_err "--class must be app|service|game|research|agent|desktop (or A-F), got '$CLASS'"
|
|
257
|
+
exit 2 ;;
|
|
258
|
+
esac
|
|
259
|
+
CLASS_NAME=$(ls "$SRC"/presets/"$CLASS"-*.yaml 2>/dev/null | head -n 1)
|
|
260
|
+
STEP_VAL[1]=" $C_TEXT$CLASS ($([ -n "$CLASS_NAME" ] && basename "$CLASS_NAME" .yaml | cut -d- -f2-))$C_RESET"
|
|
261
|
+
if [ "$TTY_OUT" -eq 1 ]; then
|
|
262
|
+
printf ' %s✔%s %d. %-13s%s%s%s\n' "$C_GREEN" "$C_RESET" 3 "class" "$C_TEXT" "$CLASS$([ -n "$CLASS_NAME" ] && printf ' — %s' "$(basename "$CLASS_NAME" .yaml | cut -d- -f2-)")$C_RESET"
|
|
263
|
+
RAIL_LINES=$((RAIL_LINES + 1))
|
|
264
|
+
else
|
|
265
|
+
cascade "class: $CLASS"
|
|
266
|
+
fi
|
|
267
|
+
|
|
268
|
+
# ----------------------------------------------------- screen 4: branch/email --
|
|
269
|
+
CURRENT=4
|
|
270
|
+
rail_rewind
|
|
271
|
+
if [ "$TTY_OUT" -eq 1 ]; then rail_print; fi
|
|
272
|
+
DEF_BRANCH=$( cd "$TARGET" && git rev-parse --abbrev-ref HEAD 2>/dev/null )
|
|
273
|
+
case "$DEF_BRANCH" in ""|HEAD) DEF_BRANCH=main ;; esac
|
|
274
|
+
DEF_EMAIL=$( cd "$TARGET" && git config user.email 2>/dev/null )
|
|
275
|
+
[ -n "$DEF_EMAIL" ] || DEF_EMAIL="you@example.com"
|
|
276
|
+
if [ -z "$BRANCH" ] || [ -z "$EMAIL" ]; then
|
|
277
|
+
if [ "$TTY_IN" -eq 1 ]; then
|
|
278
|
+
q ""
|
|
279
|
+
q " $C_MUTEDthe forge contract — the identity this repo commits with (CM-01)$C_RESET"
|
|
280
|
+
ask_value "default branch" "$DEF_BRANCH"; BRANCH="${BRANCH:-$REPLY}"
|
|
281
|
+
ask_value "owner email" "$DEF_EMAIL"; EMAIL="${EMAIL:-$REPLY}"
|
|
282
|
+
else
|
|
283
|
+
BRANCH="${BRANCH:-$DEF_BRANCH}"
|
|
284
|
+
EMAIL="${EMAIL:-$DEF_EMAIL}"
|
|
285
|
+
cascade "branch/email: $BRANCH · $EMAIL"
|
|
286
|
+
fi
|
|
287
|
+
fi
|
|
288
|
+
BRANCH="${BRANCH:-$DEF_BRANCH}"
|
|
289
|
+
EMAIL="${EMAIL:-$DEF_EMAIL}"
|
|
290
|
+
STEP_VAL[2]=" $C_TEXT$BRANCH · $EMAIL$C_RESET"
|
|
291
|
+
if [ "$TTY_OUT" -eq 1 ]; then
|
|
292
|
+
printf ' %s✔%s %d. %-13s%s%s · %s%s\n' "$C_GREEN" "$C_RESET" 4 "branch/email" "$C_TEXT" "$BRANCH" "$EMAIL" "$C_RESET"
|
|
293
|
+
RAIL_LINES=$((RAIL_LINES + 1))
|
|
294
|
+
fi
|
|
295
|
+
|
|
296
|
+
# -------------------------------------------------------- screen 5: first gate -
|
|
297
|
+
CURRENT=5
|
|
298
|
+
rail_rewind
|
|
299
|
+
if [ "$TTY_OUT" -eq 1 ]; then rail_print; fi
|
|
300
|
+
GATE_DEFAULT="bash tests/run-tests.sh"
|
|
301
|
+
if [ -z "$GATE" ]; then
|
|
302
|
+
if [ "$TTY_IN" -eq 1 ]; then
|
|
303
|
+
q ""
|
|
304
|
+
q " $C_MUTEDone real command that must stay green — suggested for a bash suite:$C_RESET"
|
|
305
|
+
if [ -f "$TARGET/tests/run-tests.sh" ]; then
|
|
306
|
+
q " $C_DIM$GATE_DEFAULT$C_RESET"
|
|
307
|
+
ask_value "gate command" "$GATE_DEFAULT"; GATE="$REPLY"
|
|
308
|
+
else
|
|
309
|
+
ask_value "gate command" "true"; GATE="$REPLY"
|
|
310
|
+
if [ "$GATE" = "true" ]; then
|
|
311
|
+
q " $C_YELLOWno tests/run-tests.sh found — a placeholder gate is allowed but it proves nothing; replace it in .goblin/goblin.yaml$C_RESET"
|
|
312
|
+
Q_LINES=$((Q_LINES + 1))
|
|
313
|
+
fi
|
|
314
|
+
fi
|
|
315
|
+
else
|
|
316
|
+
GATE="$GATE_DEFAULT"
|
|
317
|
+
cascade "gate: $GATE (default)"
|
|
318
|
+
fi
|
|
319
|
+
fi
|
|
320
|
+
STEP_VAL[3]=" $C_TEXT$GATE$C_RESET"
|
|
321
|
+
if [ "$TTY_OUT" -eq 1 ]; then
|
|
322
|
+
printf ' %s✔%s %d. %-13s%s%s%s\n' "$C_GREEN" "$C_RESET" 5 "first gate" "$C_TEXT" "$GATE" "$C_RESET"
|
|
323
|
+
RAIL_LINES=$((RAIL_LINES + 1))
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
# --------------------------------------------------------- screen 6: the plan --
|
|
327
|
+
CURRENT=6
|
|
328
|
+
rail_rewind
|
|
329
|
+
if [ "$TTY_OUT" -eq 1 ]; then rail_print; fi
|
|
330
|
+
|
|
331
|
+
# The emit selection: pre-tick the DETECTED platforms (in doctor's enum order).
|
|
332
|
+
EMIT_SELECTED=""
|
|
333
|
+
if [ -n "$EMIT" ]; then
|
|
334
|
+
IFS=',' read -ra WANTED <<< "$EMIT"
|
|
335
|
+
for p in $PLATFORMS; do
|
|
336
|
+
for w in "${WANTED[@]}"; do
|
|
337
|
+
if [ "$p" = "$w" ]; then EMIT_SELECTED="$EMIT_SELECTED $p"; fi
|
|
338
|
+
done
|
|
339
|
+
done
|
|
340
|
+
# the flags speak last: an explicit name that is not in the enum is refused with the enum
|
|
341
|
+
for w in "${WANTED[@]}"; do
|
|
342
|
+
[ -n "$w" ] || continue
|
|
343
|
+
ok=""
|
|
344
|
+
for p in $PLATFORMS; do [ "$p" = "$w" ] && ok=1; done
|
|
345
|
+
[ -n "$ok" ] || { g_err "--emit: unknown platform '$w' — I ship $PLA, hermes, copilot, cursor, opencode, codex, $GEM"; exit 2; }
|
|
346
|
+
done
|
|
347
|
+
else
|
|
348
|
+
EMIT_SELECTED=""
|
|
349
|
+
while IFS=$'\t' read -r p _proj _glob _hit; do
|
|
350
|
+
[ -n "$p" ] || continue
|
|
351
|
+
EMIT_SELECTED="$EMIT_SELECTED $p"
|
|
352
|
+
done <<< "$DET_ROWS"
|
|
353
|
+
fi
|
|
354
|
+
EMIT_N=$(printf '%s' "$EMIT_SELECTED" | awk 'NF' | wc -l | tr -d '[:space:]')
|
|
355
|
+
|
|
356
|
+
if [ "$TTY_IN" -eq 1 ] && [ -z "$EMIT" ] && [ "$YES" -eq 0 ]; then
|
|
357
|
+
q ""
|
|
358
|
+
q " $C_MUTEDemit now — the skills go into:$C_RESET"
|
|
359
|
+
for p in $PLATFORMS; do
|
|
360
|
+
hit=""
|
|
361
|
+
while IFS=$' ' read -r dp _a _b _c; do [ "$dp" = "$p" ] && hit=1; done <<< "$DET_ROWS"
|
|
362
|
+
[ -n "$hit" ] || continue
|
|
363
|
+
q " $C_GREEN✔$C_RESET $C_TEXT$p$C_RESET"
|
|
364
|
+
done
|
|
365
|
+
q " $C_DIMEnter = these; or type platform names to override$C_RESET"
|
|
366
|
+
printf ' '
|
|
367
|
+
read -r REPLY_EMIT
|
|
368
|
+
ASKED_ANY=1
|
|
369
|
+
printf '\n'; Q_LINES=$((Q_LINES + 1))
|
|
370
|
+
if [ -n "$REPLY_EMIT" ]; then
|
|
371
|
+
EMIT_SELECTED=""
|
|
372
|
+
for w in $REPLY_EMIT; do
|
|
373
|
+
ok=""
|
|
374
|
+
for p in $PLATFORMS; do [ "$p" = "$w" ] && ok=1; done
|
|
375
|
+
[ -n "$ok" ] && EMIT_SELECTED="$EMIT_SELECTED $w" || { g_err "unknown platform '$w' — I ship $PLA, hermes, copilot, cursor, opencode, codex, $GEM"; exit 2; }
|
|
376
|
+
done
|
|
377
|
+
fi
|
|
378
|
+
fi
|
|
379
|
+
STEP_VAL[4]=" $C_TEXT$EMIT_N platform(s) · scope $SCOPE$C_RESET"
|
|
380
|
+
if [ "$TTY_OUT" -eq 1 ]; then
|
|
381
|
+
printf ' %s✔%s %d. %-13s%s%d platform(s) · scope %s%s\n' "$C_GREEN" "$C_RESET" 6 "emit" "$C_TEXT" "$EMIT_N" "$SCOPE" "$C_RESET"
|
|
382
|
+
RAIL_LINES=$((RAIL_LINES + 1))
|
|
383
|
+
fi
|
|
384
|
+
|
|
385
|
+
# ---- the plan box: the wizard never writes until this has been on the screen ----
|
|
386
|
+
BOX=""
|
|
387
|
+
add() { BOX="$BOX$1
|
|
388
|
+
"; }
|
|
389
|
+
add " ${C_DIM}╭──────────────────────────────────────────────────────╮${C_RESET}"
|
|
390
|
+
add " ${C_DIM}│${C_RESET} ${C_GREEN}✔${C_RESET} ${C_TEXT}install${C_RESET} $C_DIM.goblin/ + the payload into$C_RESET $C_TEXT$TARGET$C_RESET"
|
|
391
|
+
add " ${C_DIM}│${C_RESET} ${C_GREEN}✔${C_RESET} ${C_TEXT}class${C_RESET} $C_TEXT$CLASS$C_RESET $C_DIM· branch $C_TEXT$BRANCH$C_RESET $C_DIM· email$C_RESET $C_TEXT$EMAIL$C_RESET"
|
|
392
|
+
add " ${C_DIM}│${C_RESET} ${C_GREEN}✔${C_RESET} ${C_TEXT}gate${C_RESET} $C_TEXT$GATE$C_RESET"
|
|
393
|
+
add " ${C_DIM}│${C_RESET} ${C_GREEN}✔${C_RESET} ${C_TEXT}emit${C_RESET} $C_TEXT$EMIT_N platform(s), scope $SCOPE$C_RESET$([ "$EMIT_N" -gt 0 ] && printf '%s' " $C_DIM($(printf '%s' "$EMIT_SELECTED" | awk '{$1=$1};1'))$C_RESET")"
|
|
394
|
+
add " ${C_DIM}╰──────────────────────────────────────────────────────╯${C_RESET}"
|
|
395
|
+
if [ "$DRYRUN" -eq 1 ]; then
|
|
396
|
+
printf '%s\n' "$BOX"
|
|
397
|
+
g_info "dry-run: the plan above writes nothing"
|
|
398
|
+
exit 0
|
|
399
|
+
fi
|
|
400
|
+
# A question the wizard genuinely asked (a screen that waited on read) buys the confirm;
|
|
401
|
+
# flag/non-tty runs already stated their answer as a flag — proceeding is the contract.
|
|
402
|
+
if [ "$YES" -eq 0 ] && [ "${ASKED_ANY:-0}" -eq 1 ]; then
|
|
403
|
+
printf '%s\n' "$BOX"
|
|
404
|
+
if [ "$TTY_IN" -eq 1 ]; then
|
|
405
|
+
printf ' %sproceed?%s %s[Enter = yes]%s ' "$C_BOLD" "$C_RESET" "$C_DIM" "$C_RESET"
|
|
406
|
+
read -r REPLY_GO
|
|
407
|
+
case "$REPLY_GO" in n*|N*) g_info "aborted — nothing was written"; exit 0 ;; esac
|
|
408
|
+
else
|
|
409
|
+
g_info "non-interactive: proceeding with the plan above"
|
|
410
|
+
fi
|
|
411
|
+
fi
|
|
412
|
+
|
|
413
|
+
# ----------------------------------------------------------------- execute -----
|
|
414
|
+
# install/emit/verify own their flag surfaces; the wizard adds only what they do not
|
|
415
|
+
# carry: the declared identity (branch/owner_email) and the first gate, patched into
|
|
416
|
+
# .goblin/goblin.yaml right after install renders it. The patch is fail-closed — the
|
|
417
|
+
# gate must read back through the engine's own parser or the run stops here.
|
|
418
|
+
if [ "$TTY_OUT" -eq 0 ]; then printf 'gob init [run] goblin-install --target %s --class %s\n' "$TARGET" "$CLASS"; fi
|
|
419
|
+
bash "$SRC/bin/goblin-install" --target "$TARGET" --class "$CLASS" --yes
|
|
420
|
+
EXIT_INSTALL=$?
|
|
421
|
+
if [ "$EXIT_INSTALL" -ne 0 ]; then
|
|
422
|
+
g_err "install failed (exit $EXIT_INSTALL) — the refusals above name the path and the fix"
|
|
423
|
+
exit "$EXIT_INSTALL"
|
|
424
|
+
fi
|
|
425
|
+
CONFIG="$TARGET/.goblin/goblin.yaml"
|
|
426
|
+
[ -f "$CONFIG" ] || { g_err "install wrote no .goblin/goblin.yaml at $TARGET"; exit 1; }
|
|
427
|
+
if [ "$DRYRUN" -eq 0 ]; then
|
|
428
|
+
sed -i "s|^branch:.*|branch: $BRANCH|" "$CONFIG" || { g_err "cannot write the declared branch into $CONFIG"; exit 1; }
|
|
429
|
+
sed -i "s|^owner_email:.*|owner_email: $EMAIL|" "$CONFIG" || { g_err "cannot write the owner email into $CONFIG"; exit 1; }
|
|
430
|
+
TMP_CFG=$(mktemp) || { g_err "mktemp failed"; exit 2; }
|
|
431
|
+
GATE_SUB=$(mktemp) || { g_err "mktemp failed"; exit 2; }
|
|
432
|
+
# print the gate body once, read it as data: an & or / inside the command must not
|
|
433
|
+
# become an awk substitution metacharacter (the gates are real shell).
|
|
434
|
+
printf '%s\n' "$GATE" > "$GATE_SUB"
|
|
435
|
+
awk 'BEGIN{ing=0; done=0}
|
|
436
|
+
/^gates:/{ing=1; print; next}
|
|
437
|
+
ing && !done && /^ cmd:/{
|
|
438
|
+
getline g < genv
|
|
439
|
+
sub(/^ cmd:.*/, " cmd: " g)
|
|
440
|
+
print; done=1; close(genv); next}
|
|
441
|
+
ing && /^[^ ]/{ing=0}
|
|
442
|
+
{print}' genv="$GATE_SUB" "$CONFIG" > "$TMP_CFG" || { rm -f "$TMP_CFG" "$GATE_SUB"; g_err "cannot rewrite the first gate in $CONFIG"; exit 1; }
|
|
443
|
+
mv "$TMP_CFG" "$CONFIG" || { rm -f "$GATE_SUB"; g_err "cannot replace $CONFIG"; exit 1; }
|
|
444
|
+
rm -f "$GATE_SUB"
|
|
445
|
+
# fail closed: the declared gate must read back through the engine's own reader
|
|
446
|
+
GOT_GATE=$(g_yaml_gates "$CONFIG" | head -n 1 | awk -F'\t' '{print $2}')
|
|
447
|
+
if [ "$GOT_GATE" != "$GATE" ]; then
|
|
448
|
+
g_err "the first gate did not land in $CONFIG (reader sees '$GOT_GATE', wanted '$GATE')"
|
|
449
|
+
exit 1
|
|
450
|
+
fi
|
|
451
|
+
fi
|
|
452
|
+
|
|
453
|
+
EMITTED=0
|
|
454
|
+
for p in $EMIT_SELECTED; do
|
|
455
|
+
[ "$TTY_OUT" -eq 0 ] && printf 'gob init [run] goblin-emit --platform %s --scope %s\n' "$p" "$SCOPE"
|
|
456
|
+
bash "$SRC/bin/goblin-emit" --platform "$p" --scope "$SCOPE" --target "$TARGET"
|
|
457
|
+
EXIT_EMIT=$?
|
|
458
|
+
if [ "$EXIT_EMIT" -ne 0 ]; then
|
|
459
|
+
g_err "emit $p failed (exit $EXIT_EMIT) — run 'goblin emit --platform $p --scope $SCOPE' to see the refusal again"
|
|
460
|
+
exit "$EXIT_EMIT"
|
|
461
|
+
fi
|
|
462
|
+
EMITTED=$((EMITTED + 1))
|
|
463
|
+
done
|
|
464
|
+
|
|
465
|
+
# --------------------------------------------------- screen 6b: done + verify --
|
|
466
|
+
if [ "$TTY_OUT" -eq 1 ]; then
|
|
467
|
+
printf '\n'
|
|
468
|
+
else
|
|
469
|
+
printf 'gob init [verify] .goblin/bin/goblin-verify\n'
|
|
470
|
+
fi
|
|
471
|
+
if [ "$DRYRUN" -eq 0 ]; then
|
|
472
|
+
VERIFY_OUT=$( cd "$TARGET" && bash .goblin/bin/goblin-verify 2>&1 )
|
|
473
|
+
VERIFY_RC=$?
|
|
474
|
+
PASSED=$(printf '%s' "$VERIFY_OUT" | grep -oE '[0-9]+ passed' | head -n 1 | awk '{print $1}')
|
|
475
|
+
FAILED=$(printf '%s' "$VERIFY_OUT" | grep -oE '[0-9]+ failed' | head -n 1 | awk '{print $1}')
|
|
476
|
+
ADVISORY=$(printf '%s' "$VERIFY_OUT" | grep -oE '[0-9]+ advisory' | head -n 1 | awk '{print $1}')
|
|
477
|
+
SKIPPED=$(printf '%s' "$VERIFY_OUT" | grep -oE '[0-9]+ skipped' | head -n 1 | awk '{print $1}')
|
|
478
|
+
PASSED="${PASSED:-0}"; FAILED="${FAILED:-0}"; ADVISORY="${ADVISORY:-0}"; SKIPPED="${SKIPPED:-0}"
|
|
479
|
+
|
|
480
|
+
if [ "$TTY_OUT" -eq 1 ]; then
|
|
481
|
+
printf '%s\n' "$VERIFY_OUT" | while IFS= read -r vl; do
|
|
482
|
+
case "$vl" in
|
|
483
|
+
PASS*) printf ' %s%s%s\n' "$C_GREEN" "$vl" "$C_RESET" ;;
|
|
484
|
+
FAIL*) printf ' %s%s%s\n' "$C_RED" "$vl" "$C_RESET" ;;
|
|
485
|
+
ADV*) printf ' %s%s%s\n' "$C_YELLOW" "$vl" "$C_RESET" ;;
|
|
486
|
+
SKIP*) printf ' %s%s%s\n' "$C_DIM" "$vl" "$C_RESET" ;;
|
|
487
|
+
*) printf '%s\n' "$vl" ;;
|
|
488
|
+
esac
|
|
489
|
+
done
|
|
490
|
+
printf '\n'
|
|
491
|
+
if [ "$VERIFY_RC" -eq 0 ]; then
|
|
492
|
+
printf ' %sgoblin verify: %s passed, %s failed, %s skipped · exit 0%s\n' "$C_GREEN" "$PASSED" "$FAILED" "$SKIPPED" "$C_RESET"
|
|
493
|
+
else
|
|
494
|
+
printf ' %sgoblin verify: %s passed, %s failed, %s skipped · exit %s%s\n' "$C_RED" "$PASSED" "$FAILED" "$SKIPPED" "$VERIFY_RC" "$C_RESET"
|
|
495
|
+
fi
|
|
496
|
+
printf '\n'
|
|
497
|
+
else
|
|
498
|
+
g_info "gob init [verify] exit $VERIFY_RC: $PASSED passed, $FAILED failed, $ADVISORY advisory, $SKIPPED skipped"
|
|
499
|
+
fi
|
|
500
|
+
|
|
501
|
+
printf ' %snext:%s\n' "$C_BOLD" "$C_RESET"
|
|
502
|
+
printf ' 1. %sthe %s skipped checks ARE your checklist — gob verify names each one%s\n' "$C_TEXT" "$SKIPPED" "$C_RESET"
|
|
503
|
+
printf ' 2. %sreplace the placeholder gate in .goblin/goblin.yaml with your real commands (P8 step 3)%s\n' "$C_TEXT" "$C_RESET"
|
|
504
|
+
printf ' 3. %snot emitted here: gob emit --platform <p> --scope global%s\n' "$C_TEXT" "$C_RESET"
|
|
505
|
+
printf ' 4. %shermes skills trust %s # one-time, so the project-tier skills load%s\n' "$C_TEXT" "$TARGET" "$C_RESET"
|
|
506
|
+
printf '\n'
|
|
507
|
+
if [ "$VERIFY_RC" -eq 0 ]; then
|
|
508
|
+
printf ' %s▙ the goblin sees you. keep the gate green.%s\n' "$C_ACCENT" "$C_RESET"
|
|
509
|
+
else
|
|
510
|
+
printf ' %s▙ the goblin sees you. the gate is RED — start there.%s\n' "$C_RED" "$C_RESET"
|
|
511
|
+
fi
|
|
512
|
+
fi
|
|
513
|
+
|
|
514
|
+
if [ "$VERIFY_RC" -eq 2 ]; then
|
|
515
|
+
# verify could not run: that is init's failure (a broken install), not the repo's day-one state
|
|
516
|
+
g_err "goblin-verify could not run (exit 2) — the install above is broken; see the output above"
|
|
517
|
+
exit 2
|
|
518
|
+
fi
|
|
519
|
+
exit 0
|