@techgoblin/gobstack 0.5.0-beta.4 → 0.5.0-beta.5

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 CHANGED
@@ -98,7 +98,7 @@ The measurement and the vacuous-pass reading are in `docs/CONTRACTS.md`.
98
98
  | `gob doctor` | one run across the platforms below: DETECTED / NOT-DETECTED / DRIFT per platform |
99
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; `gob sync` is the same command under its friendlier name — both spellings work |
100
100
  | `gob sync` | the emit verb, renamed (wizard v2): same engine, same flags, same exit contract; `gob emit --help` and `gob sync --help` are byte-identical apart from the verb name |
101
- | `gob init` | the first-run wizard: detect → class → identity → health check → ci → sync → done, 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; the ci step defaults to no — nothing under .github/ unless you opt in (`--ci-gate yes|no` overrides); `--dry-run` prints the plan and writes nothing |
101
+ | `gob init` | the first-run wizard: detect → class → identity → health check → ci → sync → done, 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; the ci step defaults to no — nothing under .github/ unless you opt in (`--ci-gate yes|no` overrides); `--no-verify` skips the closing health check; `--dry-run` prints the plan and writes nothing |
102
102
 
103
103
  `goblin` remains as a legacy alias for every command above — existing scripts keep working, but
104
104
  new commands and docs use `gob`.
package/bin/goblin-emit CHANGED
@@ -67,7 +67,7 @@ LEDGER="${GOBLIN_EMISSIONS:-$HOME/.goblin-stack/emissions.tsv}"
67
67
  PREIMG="${GOBLIN_PREIMAGES:-$HOME/.goblin-stack/preimages}"
68
68
 
69
69
  usage() {
70
- cat <<USAGE
70
+ cat <<'USAGE'
71
71
  gob sync — per-platform skill sync (W4a, the seven platforms since W4b).
72
72
 
73
73
  gob sync --platform <$_PLA|hermes|copilot|cursor|opencode|codex|$_G1$_G2>
@@ -221,9 +221,18 @@ g_index_bytes() {
221
221
  local files=() n
222
222
  for n in $(selected_skills); do files+=("$SRCSKILLS/$n/SKILL.md"); done
223
223
  [ "${#files[@]}" -eq 0 ] && { printf '0'; return 0; }
224
- awk 'FNR==1{c=0;n=0} /^---$/{c++; next}
225
- c==1 && /^name:/{n+=length($0)+1} c==1 && /^description:/{n+=length($0)+1}
226
- ENDFILE{tot+=n} END{print tot+0}' "${files[@]}" 2>/dev/null
224
+ # Portability (BSD catch, 2026-10-06): ENDFILE is a gawk extension. On mawk (the Debian
225
+ # default) and the macOS BWK awk it is a syntax error, stderr was discarded, and the
226
+ # run printed "procedure index size: 0 bytes" on every non-gawk machine. Sum per file
227
+ # with FNR==1 as the reset — the same meaning under every awk.
228
+ local f total=0
229
+ for f in "${files[@]}"; do
230
+ n=$(awk 'FNR==1{n=0} /^---$/{c++; next}
231
+ c==1 && /^name:/{n+=length($0)+1} c==1 && /^description:/{n+=length($0)+1}
232
+ END{print n+0}' "$f" 2>/dev/null)
233
+ total=$((total + ${n:-0}))
234
+ done
235
+ printf '%d' "$total"
227
236
  }
228
237
 
229
238
  # ---- the ledger (§4.4) -------------------------------------------------------------
package/bin/goblin-init CHANGED
@@ -3,7 +3,8 @@
3
3
  #
4
4
  # gob init [--target <dir>] [--class <software|service|game|research|fleet|A..E|app|agent|desktop>]
5
5
  # [--electron] [--branch <name>] [--email <addr>] [--gate <cmd>]
6
- # [--ci-gate yes|no] [--emit <p[,p..]>] [--scope project|global] [--yes] [--dry-run]
6
+ # [--ci-gate yes|no] [--emit <p[,p..]>] [--scope project|global] [--yes]
7
+ # [--no-verify] [--dry-run]
7
8
  #
8
9
  # Seven screens, one per question, answered steps collapsing into the ✔/◆/○ rail above.
9
10
  # On a tty the class and ci steps are a KEYBOARD RADIO (arrows move the ▸ pointer, Enter
@@ -270,7 +271,7 @@ ask_check_pre() {
270
271
  # ------------------------------------------------------------------ flags -----
271
272
  TARGET="" CLASS="" BRANCH="" EMAIL="" GATE="" EMIT="" SCOPE=""
272
273
  ELECTRON=0
273
- YES=0; DRYRUN=0
274
+ YES=0; DRYRUN=0; NOVERIFY=0
274
275
  usage() { sed -n '2,/^$/p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
275
276
 
276
277
  while [ $# -gt 0 ]; do
@@ -285,6 +286,7 @@ while [ $# -gt 0 ]; do
285
286
  --emit) EMIT="${2:-}"; shift 2 ;;
286
287
  --scope) SCOPE="${2:-}"; shift 2 ;;
287
288
  --yes) YES=1; shift ;;
289
+ --no-verify) NOVERIFY=1; shift ;;
288
290
  --dry-run) DRYRUN=1; shift ;;
289
291
  -h|--help) usage; exit 0 ;;
290
292
  *) g_err "unknown option: $1"; usage >&2; exit 2 ;;
@@ -683,7 +685,25 @@ done
683
685
  if [ "$TTY_OUT" -eq 0 ]; then
684
686
  printf 'gob init [verify] .goblin/bin/goblin-verify\n'
685
687
  fi
686
- if [ "$DRYRUN" -eq 0 ]; then
688
+ VERIFY_RC=0
689
+ if [ "$DRYRUN" -eq 0 ] && [ "$NOVERIFY" -eq 1 ]; then
690
+ # --no-verify: the operator asked for the install without the health check. The skip is
691
+ # named, not silent — a run that says nothing reads like a run that verified.
692
+ if [ "$TTY_OUT" -eq 1 ]; then
693
+ rail_note " ${C_MUTED}health check skipped (--no-verify) — run .goblin/bin/goblin-verify when ready${C_RESET}"
694
+ else
695
+ printf 'gob init [verify] skipped (--no-verify)\n'
696
+ fi
697
+ elif [ "$DRYRUN" -eq 0 ]; then
698
+ # Before the run: what the operator is about to see, and whose debt a red row is. On a
699
+ # fresh install a dozen rows fail or skip because the repo has not earned them yet —
700
+ # that is the documented day-one state, not a broken install (docs/GUIDE.md, section 4).
701
+ local_rows=$(awk -F'\t' 'NR>1 && $2=="target" {n++} END{print n+0}' "$TARGET/.goblin/manifest/enforcement.tsv" 2>/dev/null)
702
+ if [ "$TTY_OUT" -eq 1 ]; then
703
+ rail_note " ${C_MUTED}running the health check (${local_rows} manifest rows, about 10-30s) — red rows found here are the repo day-one debts, not install defects${C_RESET}"
704
+ else
705
+ printf 'gob init [verify] running the health check (%s manifest rows, about 10-30s); red rows are the repo day-one debts, not install defects\n' "$local_rows"
706
+ fi
687
707
  VERIFY_OUT=$( cd "$TARGET" && bash .goblin/bin/goblin-verify 2>&1 )
688
708
  VERIFY_RC=$?
689
709
  PASSED=$(printf '%s' "$VERIFY_OUT" | grep -oE '[0-9]+ passed' | head -n 1 | awk '{print $1}')
@@ -693,6 +713,18 @@ if [ "$DRYRUN" -eq 0 ]; then
693
713
  PASSED="${PASSED:-0}"; FAILED="${FAILED:-0}"; ADVISORY="${ADVISORY:-0}"; SKIPPED="${SKIPPED:-0}"
694
714
 
695
715
  if [ "$TTY_OUT" -eq 1 ]; then
716
+ # After the run: the counts and the day-one framing FIRST, the row detail after —
717
+ # a fresh install is red on purpose (docs/GUIDE.md section 4), so the verdict line
718
+ # is the headline and the rows are the evidence, not a wall that hides it.
719
+ if [ "$VERIFY_RC" -eq 0 ]; then
720
+ printf ' %sgob verify: %s passed, %s failed, %s skipped · exit 0%s\n' "$C_GREEN" "$PASSED" "$FAILED" "$SKIPPED" "$C_RESET"
721
+ else
722
+ printf ' %sgob verify: %s passed, %s failed, %s skipped · exit %s%s\n' "$C_RED" "$PASSED" "$FAILED" "$SKIPPED" "$VERIFY_RC" "$C_RESET"
723
+ fi
724
+ if [ "$FAILED" -gt 0 ]; then
725
+ printf ' %sthe %s failed rows are day-one debts — listed below, each says the fix%s\n' "$C_MUTED" "$FAILED" "$C_RESET"
726
+ fi
727
+ printf '\n'
696
728
  printf '%s\n' "$VERIFY_OUT" | while IFS= read -r vl; do
697
729
  case "$vl" in
698
730
  PASS*) printf ' %s%s%s\n' "$C_GREEN" "$vl" "$C_RESET" ;;
@@ -702,12 +734,6 @@ if [ "$DRYRUN" -eq 0 ]; then
702
734
  *) printf '%s\n' "$vl" ;;
703
735
  esac
704
736
  done
705
- printf '\n'
706
- if [ "$VERIFY_RC" -eq 0 ]; then
707
- printf ' %sgob verify: %s passed, %s failed, %s skipped · exit 0%s\n' "$C_GREEN" "$PASSED" "$FAILED" "$SKIPPED" "$C_RESET"
708
- else
709
- printf ' %sgob verify: %s passed, %s failed, %s skipped · exit %s%s\n' "$C_RED" "$PASSED" "$FAILED" "$SKIPPED" "$VERIFY_RC" "$C_RESET"
710
- fi
711
737
  else
712
738
  g_info "gob init [verify] exit $VERIFY_RC: $PASSED passed, $FAILED failed, $ADVISORY advisory, $SKIPPED skipped"
713
739
  fi
package/bin/goblin-lib.sh CHANGED
@@ -19,10 +19,66 @@
19
19
  GOBLIN_LIB_VERSION="0.5.0"
20
20
 
21
21
  # ---------------------------------------------------------------- output -----
22
- g_pass() { printf 'PASS %-6s %s\n' "$1" "$2"; }
22
+ # g_trunc <width> <text> — fold a long detail to one line at <width> columns, keeping the
23
+ # head. Pure awk substr, no regex: the same shape under every awk (mawk, BWK, gawk).
24
+ g_trunc() {
25
+ awk -v w="$1" -v t="$2" 'BEGIN{
26
+ if (length(t) <= w) { print t; exit }
27
+ print substr(t, 1, w - 1) "~"
28
+ }'
29
+ }
30
+ # g_fold <width> <indent> <text> — word-wrap <text> at <width>, continuing lines at
31
+ # <indent> (the cannot-see footer shape). Byte-safe: awk length on bytes approximates
32
+ # columns for ASCII prose, which is all this text is.
33
+ g_fold() {
34
+ awk -v w="$1" -v ind="$2" '
35
+ {
36
+ line = $0
37
+ while (length(line) > w) {
38
+ cut = w
39
+ while (cut > 1 && substr(line, cut, 1) != " ") cut--
40
+ if (cut <= 1) cut = w
41
+ print substr(line, 1, cut)
42
+ sub(/^[ ]+/, "", substr(line, cut + 1))
43
+ line = substr(line, cut + 1)
44
+ sub(/^[ ]+/, "", line)
45
+ line = ind line
46
+ }
47
+ print line
48
+ }'
49
+ }
50
+ g_pass() {
51
+ if [ "${GOB_VERIFY_VERBOSE:-0}" -eq 1 ]; then
52
+ printf 'PASS %-6s %s\n' "$1" "$(g_trunc "${GOB_REPORT_COLS:-100}" "$2")"
53
+ elif printf '%s' "$2" | grep -q "$(printf '\n')"; then
54
+ # Multi-line PASS payloads print whole: the payload often IS the pin a test reads
55
+ # (SK-03's advisory-ceiling line), and the collapse must not swallow it.
56
+ printf 'PASS %-6s %s\n' "$1" "$2"
57
+ else
58
+ printf 'PASS %-6s (ok)\n' "$1"
59
+ fi
60
+ }
23
61
  g_fail() { printf 'FAIL %-6s %s\n' "$1" "$2"; }
24
- g_adv() { printf 'ADV %-6s %s\n' "$1" "$2"; }
25
- g_skip() { printf 'SKIP %-6s %s\n' "$1" "$2"; }
62
+ g_adv() {
63
+ # Multi-line ADV payloads print whole: tests pin phrases on the SECOND line of a lane
64
+ # advisory (the W5-6 family statement), and the fold must not eat them. Single-line
65
+ # payloads are the ones the ~100-col truncation is for.
66
+ if printf '%s' "$2" | grep -q "$(printf '\n')"; then
67
+ printf 'ADV %-6s %s\n' "$1" "$2"
68
+ else
69
+ printf 'ADV %-6s %s\n' "$1" "$(g_trunc "${GOB_REPORT_COLS:-100}" "$2")"
70
+ fi
71
+ }
72
+ g_skip() {
73
+ # SKIPs keep their row line in every mode (tests and scripts read them by id). The
74
+ # collapse is the WIDTH, not the disappearance: the reason is truncated to the report
75
+ # width unless --verbose/--only asks for it whole.
76
+ if [ "${GOB_VERIFY_VERBOSE:-0}" -eq 1 ]; then
77
+ printf 'SKIP %-6s %s\n' "$1" "$2"
78
+ else
79
+ printf 'SKIP %-6s %s\n' "$1" "$(g_trunc "${GOB_REPORT_COLS:-100}" "$2")"
80
+ fi
81
+ }
26
82
  g_info() { printf '%s\n' "$*"; }
27
83
  g_err() { printf 'error: %s\n' "$*" >&2; }
28
84
 
package/bin/goblin-verify CHANGED
@@ -28,12 +28,18 @@ ONLY=""
28
28
  JSON=0
29
29
  LIST=0
30
30
  SOURCE=""
31
+ VERBOSE=0
32
+ # PASS details collapse to (ok) in the default listing; --verbose (or --only, which is a
33
+ # forensic single-row run) restores the full payload line by line.
34
+ if [ "${ONLY:-}" != "" ]; then VERBOSE=1; fi
31
35
 
32
36
  usage() {
33
37
  cat <<'USAGE'
34
38
  goblin-verify — prove a goblin-stack install is still what it says it is.
35
39
 
36
40
  --only <id[,id...]> run only these manifest rows
41
+ --verbose print every row full-width (the default collapses PASS payloads
42
+ to a count; FAIL and ADV always print in full)
37
43
  --list list the manifest rows and exit
38
44
  --json machine-readable output
39
45
  --source <path> read the manifest from a goblin-stack checkout instead of .goblin/
@@ -44,9 +50,10 @@ USAGE
44
50
 
45
51
  while [ $# -gt 0 ]; do
46
52
  case "$1" in
47
- --only) ONLY="${2:-}"; shift 2 ;;
53
+ --only) ONLY="${2:-}"; VERBOSE=1; shift 2 ;;
48
54
  --json) JSON=1; shift ;;
49
55
  --list) LIST=1; shift ;;
56
+ --verbose) VERBOSE=1; shift ;;
50
57
  --source) SOURCE="${2:-}"; shift 2 ;;
51
58
  -h|--help) usage; exit 0 ;;
52
59
  *) g_err "unknown option: $1"; usage >&2; exit 2 ;;
@@ -355,6 +362,14 @@ else
355
362
  export GOBLIN_HARNESS_STATE=none
356
363
  fi
357
364
  fi
365
+
366
+ # ---- the report format (collapsed listing) ------------------------------------
367
+ # The default listing is one line per row: PASS payloads collapse to (ok) and long ADV
368
+ # sentences fold to the report width; --verbose (or --only) prints every payload
369
+ # full-width. The lib's row printers read these.
370
+ export GOB_VERIFY_VERBOSE="$VERBOSE"
371
+ export GOB_REPORT_COLS="${COLUMNS:-100}"
372
+ [ "$GOB_REPORT_COLS" -ge 40 ] 2>/dev/null || GOB_REPORT_COLS=100
358
373
  export GOBLIN_ROOT="$ROOT"
359
374
  export GOBLIN_CONFIG="$CONFIG"
360
375
 
@@ -609,8 +624,14 @@ check_hp_03() {
609
624
  names=${names% }
610
625
  rc=0
611
626
  out=$(awk -v names="$names" '
612
- function hasname(line, k, i, a, m) {
613
- m = split(line, a, /[^A-Za-z0-9_./-]+/)
627
+ function hasname(line, k, i, a, m, pat) {
628
+ # Portability (BSD catch, 2026-10-06): the split pattern must be a STRING, not a
629
+ # regex literal. In /[^A-Za-z0-9_./-]+/ the third slash ENDS the regex, so the
630
+ # macOS BWK awk dies with a syntax error ("nonterminated character class") before
631
+ # reading a single line; gawk accepts it. A string pattern means the same thing
632
+ # everywhere. (No apostrophes here: this program is a single-quoted shell string.)
633
+ pat = "[^A-Za-z0-9_./-]+"
634
+ m = split(line, a, pat)
614
635
  for (i = 1; i <= m; i++) if (a[i] == k) return 1
615
636
  return 0
616
637
  }
@@ -2761,7 +2782,10 @@ else
2761
2782
  printf ' of those %d advisory: the matrix labels %d row(s) advisory; the difference is the non-advisory row(s) reporting ADV by design (an unresolved lane) and any labelled row skipped by an opt-out\n' \
2762
2783
  "$ADV" "$adv_labelled"
2763
2784
  fi
2764
- cat <<'SEE'
2785
+ # The collapsed listing keeps one line per row (a SKIP's reason is the row's whole
2786
+ # point and scripts read them by id); the width discipline lives in the lib's printers.
2787
+ if [ "$VERBOSE" -eq 1 ]; then
2788
+ cat <<'SEE'
2765
2789
  cannot see: whether a check in the harness dir tests the right path rather than
2766
2790
  merely passing; whether the forge is bound by the workflow CL-01 found; whether a human
2767
2791
  read the diff;
@@ -2793,6 +2817,16 @@ else
2793
2817
  acquisition record EXISTS, never that the number beside it came from the store (HP-03's
2794
2818
  defect, one artifact over). No row here reaches a reference asset in CONTEXT either.
2795
2819
  SEE
2820
+ else
2821
+ # The same statement, folded to a pointer. The unsigned-record admission rides the
2822
+ # short form too: a doc pin (F2-3) reads it out of every run, not only verbose ones.
2823
+ cat <<'SEE'
2824
+ cannot see: whether a check tests the right path rather than merely passing; whether a
2825
+ human read the diff; whether the unsigned install record (.goblin/installed.json — it is
2826
+ not signed) was rewritten; and the per-lane blind spots of the ban, judge/loop, CI and
2827
+ reference lanes. Full statement: `goblin-verify --verbose`, docs/LIMITS.md #18 #27 #28 #33 #43.
2828
+ SEE
2829
+ fi
2796
2830
  # ---- the engine footer (W1 §2.2, R-1's residue -> docs/LIMITS.md #43) --------
2797
2831
  # Every run states WHICH engine judged it: the sha256 of the running verifier and of
2798
2832
  # the manifest that judged this run, so a repo can name its judge. The statement is
package/docs/CONTRACTS.md CHANGED
@@ -22,6 +22,8 @@ same way the fleet's own tool reads it. Everything else is line-oriented shell.
22
22
  record already has skills installed, an OMITTED flag keeps them; an explicit
23
23
  --skills no removes them.
24
24
  --dry-run print the plan; write nothing
25
+ --no-verify skip the health check (goblin-verify) at the end; the wizard
26
+ still installs and syncs, and prints the skip notice
25
27
  --upgrade re-install at the current version; report created/updated/unchanged/skipped
26
28
  --opt-out <part> record the part in disabled: so its required checks are skipped
27
29
  --uninstall remove exactly the files in installed.json
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techgoblin/gobstack",
3
- "version": "0.5.0-beta.4",
3
+ "version": "0.5.0-beta.5",
4
4
  "description": "Agent-discipline toolkit: one verify command, an enforcement matrix, and LIMITS. bash engine, npm shim.",
5
5
  "bin": {
6
6
  "goblin": "bin/goblin.js",