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

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
@@ -62,7 +62,8 @@ After installing, in this order:
62
62
  **A default software-class install (no agent skills — those are `gob emit`'s job) verifies green —
63
63
  `38 passed, 0 failed, 11 advisory, 33 skipped`, exit 0 — once
64
64
  `HANDOFF.md` names a commit that exists. Before that edit the scaffold's `0000000` placeholder is
65
- the one expected red: `37 passed, 1 failed`, `HP-05`. Both numbers measured at W6 (neutral-first);
65
+ the one expected red: `37 passed, 1 failed`, `HP-05`. Both numbers measured at the 0.5.0 neutral-first
66
+ revision (see `docs/RECORD-NOTES.md`, V3);
66
67
  the run and the fix are step 2 of `docs/GUIDE.md`.**
67
68
  Thirty-three rows skip — the five skill rows (`SK-01`..`SK-04`, `AU-04`) skip on the `playbooks`
68
69
  opt-out a skills-free install records, then the not-yet rows (`HS-02` has no pinned pre-change
@@ -155,6 +156,8 @@ below is the reference material the guide points into, so the two do not compete
155
156
  | `docs/RISKS.md` | the risk register, the advisory rows named, the non-goals |
156
157
  | `docs/CI.md` | the CI lane: what makes a workflow a gate, the four settings a repository cannot set, and the electron opt-in |
157
158
  | `docs/LOOP.md` | the judge role and the loop contract: what a goal-mode loop actually does, the record, and what neither can see |
159
+ | `docs/RECORD-NOTES.md` | the wave codes the changelog and the matrix parentheticals use, one line each |
160
+ | `docs/GLOSSARY.md` | every term of art in one table (rendered from the shipped `.goblin/manifest/glossary.tsv`) |
158
161
  | `docs/ADOPTION.md` | the five classes, the preset matrix, the adoption order |
159
162
  | `docs/LIMITS.md` | where this is weaker than its sources, and what is unproven |
160
163
 
@@ -174,7 +177,7 @@ run · `3` the manifest is broken. Every run prints what it cannot see.
174
177
  bash tests/run-tests.sh
175
178
 
176
179
  Runs the source-scope rules (PR-01..PR-05) and the test scripts, including `t-verify-red.sh` —
177
- one control per target-scope row (168 over 82 target rows), each required to go RED and then
180
+ one control per target-scope row (172 over 82 target rows), each required to go RED and then
178
181
  restored, plus `t-audit.sh` for the SC-07 producer. **A verifier that only ever prints GREEN is a
179
182
  failure**, so that file is the one that matters most.
180
183
 
package/bin/goblin-lib.sh CHANGED
@@ -58,7 +58,31 @@ g_pass() {
58
58
  printf 'PASS %-6s (ok)\n' "$1"
59
59
  fi
60
60
  }
61
- g_fail() { printf 'FAIL %-6s %s\n' "$1" "$2"; }
61
+ # FAIL keeps its row line whole in every mode, and the matrix's remedy column rides under it.
62
+ # The remedy is the TSV's own cell (IN-01/IN-02/GT-03/CM-01 carry prose there; the pre-remedy
63
+ # rows carry `—`, which prints nothing) — the JG-02 judge-lane `remedy:` line, brought to every
64
+ # row that names one, so the fix a failure needs is printed where the failure is read. A missing
65
+ # column (a manifest written before the column existed) is no remedy, not an error: empty is the
66
+ # same as `—`.
67
+ g_fail() {
68
+ printf 'FAIL %-6s %s\n' "$1" "$2"
69
+ local rem
70
+ rem=$(g_remedy "$1")
71
+ case "$rem" in
72
+ —*) return 0 ;; # an em-dash cell: history and blindness, never a remedy
73
+ esac
74
+ # Whole in verbose/--only mode (the mode that reads, not scans), width-folded otherwise —
75
+ # the same contract the row printers keep.
76
+ if [ "${GOB_VERIFY_VERBOSE:-0}" -eq 1 ]; then
77
+ [ -n "$rem" ] && printf 'remedy: %s\n' "$rem"
78
+ else
79
+ [ -n "$rem" ] && printf 'remedy: %s\n' "$(g_trunc "${GOB_REPORT_COLS:-100}" "$rem")"
80
+ fi
81
+ }
82
+ g_remedy() {
83
+ [ -f "${_GOB_MANIFEST:-}" ] || return 0
84
+ awk -F'\t' -v id="$1" 'NR>1 && $1==id { r = $7; if (r != "") print r; exit }' "${_GOB_MANIFEST:-}"
85
+ }
62
86
  g_adv() {
63
87
  # Multi-line ADV payloads print whole: tests pin phrases on the SECOND line of a lane
64
88
  # advisory (the W5-6 family statement), and the fold must not eat them. Single-line
package/bin/goblin-verify CHANGED
@@ -367,9 +367,12 @@ fi
367
367
  # The default listing is one line per row: PASS payloads collapse to (ok) and long ADV
368
368
  # sentences fold to the report width; --verbose (or --only) prints every payload
369
369
  # full-width. The lib's row printers read these.
370
+ # The report width and the manifest path the row printers read (the remedy lookup is
371
+ # manifest-driven: the tsv is the source of truth, the engine only renders it).
370
372
  export GOB_VERIFY_VERBOSE="$VERBOSE"
371
373
  export GOB_REPORT_COLS="${COLUMNS:-100}"
372
374
  [ "$GOB_REPORT_COLS" -ge 40 ] 2>/dev/null || GOB_REPORT_COLS=100
375
+ export _GOB_MANIFEST="$MANIFEST"
373
376
  export GOBLIN_ROOT="$ROOT"
374
377
  export GOBLIN_CONFIG="$CONFIG"
375
378
 
@@ -467,7 +470,10 @@ check_in_01() {
467
470
  fi
468
471
  [ -s "$INSTALLED" ] && grep -q '"version"' "$INSTALLED" \
469
472
  && { printf 'the install record exists and names its version\n'; return 0; }
473
+ # IN-01 recovery line (review 1, scope 4): the record is corrupt or missing; name the two
474
+ # restores. The matrix's remedy: line rides under the payload via g_fail.
470
475
  printf 'installed.json is absent or records no version\n'
476
+ printf 'recover: gob install --upgrade, or git checkout -- .goblin/installed.json\n'
471
477
  return 1
472
478
  }
473
479
 
@@ -543,6 +549,7 @@ check_in_02() {
543
549
  # The engine hash itself is printed in the footer on every run (see the footer, below);
544
550
  # this row proves the REPO-LOCAL bytes, and a global engine answers elsewhere.
545
551
  local p h cur n=0 bad=0 pin="" detail=""
552
+ local DRIFTED=""
546
553
  while IFS=$'\t' read -r p h; do
547
554
  [ -n "$p" ] || continue
548
555
  if [ "$ENGINE_MODE" = "global" ]; then
@@ -568,6 +575,7 @@ check_in_02() {
568
575
  cur=$(g_sha256_file "$ROOT/$p")
569
576
  if [ "$cur" != "$h" ]; then
570
577
  printf 'drift: %s recorded %s now %s\n' "$p" "$h" "$cur"
578
+ DRIFTED="$p"
571
579
  bad=1
572
580
  fi
573
581
  done < <(g_installed_files_strict "$INSTALLED")
@@ -603,6 +611,12 @@ check_in_02() {
603
611
  return
604
612
  fi
605
613
  printf '%d installed files hashed%s\n' "$n" "$pin"
614
+ # IN-02 recovery line (review 1, scope 4): a drift FAIL carries the exact restore commands,
615
+ # naming the drifted file the payload above reported. Sits between the row's own lines and
616
+ # the summary; g_fail appends the matrix's remedy: line under the whole payload.
617
+ if [ "$bad" -ne 0 ] && [ -n "$DRIFTED" ]; then
618
+ printf 'recover: git checkout -- %s, or gob install --upgrade\n' "$DRIFTED"
619
+ fi
606
620
  [ "$bad" -eq 0 ]
607
621
  }
608
622
 
@@ -716,14 +730,41 @@ check_gt_01() {
716
730
  }
717
731
 
718
732
  # run the declared gates; optionally write the one-line measured numbers
733
+ # GT-02 stderr (review 2, scope 2): a gate that fails with nothing on screen (`cmd: false`,
734
+ # exit 127) fails in silence. The LAST ~20 lines of the failing gate's combined output are
735
+ # kept in GT02_TAIL, and the row's FAIL payload carries them indented under the FAIL line,
736
+ # attributed to the gate that produced them. The counts line and every other row's payload
737
+ # are untouched (byte-stable where no gate fails).
738
+ GT02_TAIL=""
719
739
  run_gates() {
720
- local write_line="$1" name cmd rc bad=0 line=""
740
+ local write_line="$1" name cmd rc bad=0 line="" out
741
+ GT02_TAIL=""
721
742
  while IFS=$'\t' read -r name cmd; do
722
743
  [ -n "$name" ] || continue
723
- ( cd "$ROOT" && bash -c "$cmd" ) >/dev/null 2>&1
744
+ out=$( cd "$ROOT" && bash -c "$cmd" 2>&1 )
724
745
  rc=$?
725
746
  line="$line $name=$rc"
726
- [ "$rc" -eq 0 ] || { printf 'gate %s: %s -> exit %d\n' "$name" "$cmd" "$rc"; bad=1; }
747
+ if [ "$rc" -ne 0 ]; then
748
+ printf 'gate %s: %s -> exit %d\n' "$name" "$cmd" "$rc"
749
+ bad=1
750
+ local tail_lines
751
+ tail_lines=$(printf '%s\n' "$out" | grep -v '^[[:space:]]*$' | tail -n 20)
752
+ if [ -n "$tail_lines" ]; then
753
+ if [ -n "$GT02_TAIL" ]; then GT02_TAIL="$GT02_TAIL
754
+ "; fi
755
+ GT02_TAIL="${GT02_TAIL}gate ${name} output (last 20 lines):"
756
+ while IFS= read -r tl; do
757
+ GT02_TAIL="$GT02_TAIL
758
+ $tl"
759
+ done <<EOF_TAIL
760
+ $tail_lines
761
+ EOF_TAIL
762
+ else
763
+ [ -n "$GT02_TAIL" ] && GT02_TAIL="$GT02_TAIL
764
+ "
765
+ GT02_TAIL="${GT02_TAIL}gate ${name} printed nothing on stdout or stderr before exit ${rc} (a silent failure - the command itself is the diagnostic)"
766
+ fi
767
+ fi
727
768
  done < <(g_yaml_gates "$CONFIG")
728
769
  if [ "$write_line" = "yes" ]; then
729
770
  local rname rcmd rval
@@ -739,7 +780,12 @@ run_gates() {
739
780
  return $bad
740
781
  }
741
782
 
742
- check_gt_02() { run_gates yes; }
783
+ check_gt_02() {
784
+ local _s=0
785
+ run_gates yes || _s=$?
786
+ if [ "$_s" -ne 0 ] && [ -n "$GT02_TAIL" ]; then printf '%s\n' "$GT02_TAIL"; fi
787
+ return "$_s"
788
+ }
743
789
 
744
790
  check_gt_04() {
745
791
  local n c ceil
@@ -2671,6 +2717,33 @@ rc_hdr_field() {
2671
2717
  '
2672
2718
  }
2673
2719
 
2720
+ # ---- the day-one framing (review 1, scope 3) ----------------------------------
2721
+ # Two shapes are undeserved reds for a person who just cloned a repo. They are DETECTED and
2722
+ # framed — the verdict's exit semantics, the counts and every row's payload are untouched:
2723
+ # (a) owner_email in goblin.yaml is not the current git identity -> the CM-01 red names
2724
+ # whose email the repo actually expects (the remedy column says where to point it);
2725
+ # (b) the repo looks freshly cloned (.goblin committed, but fewer than 3 commits) -> one
2726
+ # banner line after the verdict. Commit count only: an mtime comparison against the
2727
+ # reflog fired after every ordinary GT-02 write, so the banner lingered on aged repos
2728
+ # (the verify itself refreshes last-gate-line forever); a repo under 3 commits is the
2729
+ # only unambiguous fresh-clone shape.
2730
+ owner_mismatch() {
2731
+ [ -f "$CONFIG" ] || return 1
2732
+ local declared cur
2733
+ declared=$(grep '^owner_email:' "$CONFIG" | head -n 1 | sed 's/^owner_email:[[:space:]]*//' | tr -d '"')
2734
+ cur=$(cd "$ROOT" && git config user.email 2>/dev/null)
2735
+ [ -n "$declared" ] && [ -n "$cur" ] && [ "$declared" != "$cur" ]
2736
+ }
2737
+
2738
+ fresh_clone() {
2739
+ [ -f "$INSTALLED" ] || return 1
2740
+ [ -d "$ROOT/.git" ] || return 1
2741
+ local n
2742
+ n=$(cd "$ROOT" && git rev-list --count HEAD 2>/dev/null) || return 1
2743
+ [ "$n" -lt 3 ] && return 0
2744
+ return 1
2745
+ }
2746
+
2674
2747
  # ================================================================ runner ======
2675
2748
 
2676
2749
  PASS=0; FAIL=0; ADV=0; SKIP=0; BAD=0
@@ -2752,7 +2825,14 @@ while IFS=$'\t' read -r id scope rule enforced_by artifact check why; do
2752
2825
  if [ "$rc" -eq 0 ]; then
2753
2826
  [ -n "$detail" ] || detail="($check)"
2754
2827
  else
2755
- detail="($check -> exit $rc)"
2828
+ # GT-03's one-liner is self-explaining now (review 1, scope 5): the raw `test -f
2829
+ # ... -nt ...` invocation dump told the reader the mechanics and nothing about what
2830
+ # to do; the human line says both. The TSV remedy column carries the same sentence.
2831
+ if [ "$id" = "GT-03" ]; then
2832
+ detail="GT-03 the gate line is older than the last commit — re-run gob verify to refresh it (it rewrites .goblin/last-gate-line)"
2833
+ else
2834
+ detail="($check -> exit $rc)"
2835
+ fi
2756
2836
  rc=1
2757
2837
  fi
2758
2838
  ;;
@@ -2772,6 +2852,18 @@ if [ "$JSON" -eq 1 ]; then
2772
2852
  "$(json_escape "$ROOT")" "$(json_escape "$CLASS")" "$PASS" "$FAIL" "$ADV" "$SKIP" "${JSON_ROWS%,}"
2773
2853
  else
2774
2854
  printf ' %d passed, %d failed, %d advisory, %d skipped\n' "$PASS" "$FAIL" "$ADV" "$SKIP"
2855
+ # Day-one framing, part (a) (review 1, scope 3): the owner mismatch names whose email the
2856
+ # repo expects, under the verdict, before the row detail. One line, no exit semantics.
2857
+ if [ "$FAIL" -gt 0 ] && owner_mismatch; then
2858
+ _owner_declared=$(grep '^owner_email:' "$CONFIG" | head -n 1 | sed 's/^owner_email:[[:space:]]*//' | tr -d '"')
2859
+ printf ' note: this repo records a different owner (you are probably new here) — commit with --author or update owner_email: %s\n' "$_owner_declared"
2860
+ unset _owner_declared
2861
+ fi
2862
+ # Day-one framing, part (b): a fresh clone is not your debt. One banner line, after the
2863
+ # verdict; counts and exit codes untouched.
2864
+ if [ "$FAIL" -gt 0 ] && fresh_clone; then
2865
+ printf ' fresh clone detected: some of these fails are not yours — see docs/GUIDE.md §9 (day one for a new teammate)\n'
2866
+ fi
2775
2867
  # Z1-7: the advisory number above counts TWO different things - the rows the matrix LABELS
2776
2868
  # advisory, and a row that is not advisory-labelled but whose lane cannot be resolved here
2777
2869
  # (JG-02's unresolved judge lane returns 2 = ADV by design). SK-03 counts the first, the ADV
@@ -31,7 +31,7 @@ Measured shape of this table: **87 rows** - 82 target, 5 source; advisory 10, ga
31
31
  | id | scope | enforced by | rule | check | if it cannot be enforced, why |
32
32
  |---|---|---|---|---|---|
33
33
  | `IN-01` | target | script | The install exists and records its version + every file's hash. | goblin-verify --only IN-01 | — (W1: the check is a builtin so the engine.mode=global clause can run — a global, declaration-only repo has no install record and SKIPs with `global engine mode — no per-repo install record`; the vendored clauses are the old one-liner: the record exists and names its version) |
34
- | `IN-02` | target | script | Every installed file still matches its recorded hash. | `goblin-verify --only IN-02` (builtin) | — |
34
+ | `IN-02` | target | script | Every installed file still matches its recorded hash. | `goblin-verify --only IN-02` (builtin) | recover: `gob install --upgrade`, or `git checkout -- .goblin/installed.json`; for a file it names drifted: `git checkout -- <path>` (if the edit is yours and intended, re-pin it: `gob install --target <dir> --re-pin`) |
35
35
  | `IN-03` | target | script | The verifier's own manifest is complete: every rule has a check or is advisory. | `goblin-verify --only IN-03` (builtin) | — (this row is the reason the matrix cannot rot; the second clause is D6's shape in general: a row that carries no check must be labelled advisory, or it claims verification it does not perform. The third clause is Z1-5: `enforced_by` is documented as a closed enum in docs/ENFORCEMENT.md and was read by NOTHING, so a typo in that cell changed nothing - `script\|lint\|gate\|advisory` plus `test`, the source-scope value whose check is tests/run-tests.sh. W1: the check runs against whichever manifest the engine actually resolved (the chain in `bin/goblin-verify`), no longer the hardcoded per-repo path — the rule's meaning is untouched, only the path input follows the engine) |
36
36
  | `IN-04` | target | script | No file goblin-stack did not create has been overwritten. | `goblin-verify --only IN-04` (builtin) | Detects a file the installer recorded as pre-existing (a `refused` entry) that has since vanished, or that is listed as installed anyway. The second clause is an internal-consistency guard: with correct code a refused path is never written, so it fires only if the installer regresses. The negative control exercises the vanished branch. |
37
37
  | `HP-01` | target | gate | HANDOFF.md exists at the root. | `test -f HANDOFF.md` | — |
@@ -50,7 +50,7 @@ Measured shape of this table: **87 rows** - 82 target, 5 source; advisory 10, ga
50
50
  | `HS-01` | target | lint | Asserting harnesses follow the house shape. | `goblin-verify --only HS-01` (builtin) | A declared harness_dir that is absent FAILS when the class scaffolds one (config `scaffold_checks: yes`, classes A and C); a class that ships no harness dir (B/D/E) SKIPs with a reason. Keying the skip off the path alone let one config line switch this row and HS-02 off. A report utility in the same dir is counted and reported separately rather than failing the run. |
51
51
  | `HS-02` | target | gate | A check green on both trees proves nothing - the REPLAY must show RED pre-change. | `goblin-verify --only HS-02` (builtin) | The declared `replay.cmd` is EXECUTED with `{name}` replaced by each harness's name, in the pre-change worktree, with `replay.env=<commit>` set (Z1-4). Two clauses that used to be unheard: a command that interpolates no `{name}` FAILs (it cannot be running the harness it names, so nothing was replayed), and a command that cannot be executed at all - exit 126 or 127 - FAILs rather than counting as a RED harness. The harness's name is substituted SHELL-QUOTED (`printf %q`), because the name is part of the command text the shell parses; unquoted, a name carrying `;` or `#` reached the shell as syntax (Z2-3, the G8-1 surface class), and the control for it carries a metacharacter-bearing file name. What it cannot see: a command that runs *something else* under the harness's name and exits non-zero, and whether the harness tests the right path rather than merely failing on this tree. |
52
52
  | `HS-03` | target | advisory | Source probes read text with comments blanked first. | `advisory` | Recognising 'this probe reads source text' is semantic; a grep for the blanking helper produces false FAILs on harnesses that do not probe source. |
53
- | `CM-01` | target | gate | Commits carry the owner identity, not an ambient one. Current scope: this gates the identity of HEAD (the last commit) at the moment of the run - earlier commits by other authors are not scanned. | `test "$(git log -1 --format='%ae')" = "$(grep '^owner_email:' .goblin/goblin.yaml \| cut -d' ' -f2)"` | — |
53
+ | `CM-01` | target | gate | Commits carry the owner identity, not an ambient one. Current scope: this gates the identity of HEAD (the last commit) at the moment of the run - earlier commits by other authors are not scanned. | `test "$(git log -1 --format='%ae')"` = `"$(grep '^owner_email:' .goblin/goblin.yaml \| cut -d' ' -f2)"` | note: this repo records a different owner (you are probably new here) — commit with `git -c user.email=<owner_email> --author=<owner_email>`, or update `owner_email:` in `.goblin/goblin.yaml` |
54
54
  | `CM-02` | target | advisory | The commit message was written to a file, not passed with -m. | `advisory` | A backtick lost to command substitution leaves no trace a later check can read. Reported as a heuristic (unbalanced backticks in a body) only. |
55
55
  | `CM-03` | target | script | Commit-as-you-go: the working tree is not carrying a dead run's work. | `goblin-verify --only CM-03` (builtin) | — |
56
56
  | `MD-01` | target | lint | No model name is hardcoded in any reusable rule. | `for d in skills manifest bin templates presets .goblin .hermes; do [ -d "$d" ] \|\| continue; grep -rniE '(d[e]epseek\|cl[a]ude\|g[p]t-[0-9]\|gr[o]k\|g[e]mini\|g[l]m-[0-9]\|k[i]mi)[a-z0-9.:_-]*' "$d" && exit 1; done; exit 0` | — (the pattern is written with character classes so this row cannot match itself; tests/t-verify-red.sh proves it still catches a real model name. SCOPE, stated so a repo-wide grep is not re-reported as a hole (G8-10): this row reads the seven directories an install writes rules into - skills/ manifest/ bin/ templates/ presets/ .goblin/ .hermes/. It does NOT read tests/, docs/, or the source automations/ directory; tests/ is where a control must be free to WRITE the thing it guards, and the source directories are covered by MD-01's own body in tests/run-tests.sh, which scans skills manifest bin templates presets automations. Both control strings are assembled at run time, so a repo-wide grep over the whole tree is 0 and the row's own scope is 0 by construction.) |
@@ -0,0 +1,40 @@
1
+ # Glossary
2
+
3
+ Every term of art in one table, rendered from `manifest/glossary.tsv` — that file is the source;
4
+ edit it there and re-render (or hand-render: one row per term, alphabetical, the definition one
5
+ sentence). A term a new reader might trip on belongs here, not in a footnote.
6
+
7
+ | term | definition | source |
8
+ |---|---|---|
9
+ | advisory | A rule with no executable check. Counted in the verify summary and capped by advisory_ceiling; never a silent pass. | R6 sec.5 |
10
+ | archive | A class-D flag: verify requires no HANDOFF and no gates, and the summary says so. | R7 sec.2.4 |
11
+ | class | A project category A-E that selects which parts are required, optional or off. See manifest/classes.tsv. | R7 sec.4 |
12
+ | drift | A file whose current bytes no longer match the hash recorded at install time. IN-02 reports it; the remedy column says how to recover. | UX-review-2026-10-06 |
13
+ | engine | The bash programs plus the manifest a verify run actually resolved: per-repo (.goblin/bin) or global (engine_dir:). Named in every run's footer. | UX-review-2026-10-06 |
14
+ | forge | The hosting platform a repo pushes to (GitHub, GitLab, ...). PG-05 and PG-06 read the workflow text but can never see what the forge itself enforces. | UX-review-2026-10-06 |
15
+ | foreman | The orchestrating agent that delegates work to the fleet; the role that reads HANDOFF.md first and writes it last. | UX-review-2026-10-06 |
16
+ | gate | A declared command that must exit 0. Declared per project, never inferred from the stack. | PP sec.4 |
17
+ | HANDOFF | The session-boundary contract at the repo root: START HERE / State / Gates / Next steps / NOT verified. | PP sec.1 |
18
+ | harness | An asserting check file: a failures counter, an assert(name, ok, detail) printer, and a non-zero exit on failure. | PP sec.3 |
19
+ | judge | A role that decides whether a PROCESS met its own predicate, from a command's output and a pointer it can resolve - never from a report. One lane per verdict; never the author's lane. | docs/LOOP.md |
20
+ | lane | A family of rules that share a mechanism and a blind spot: the ban lane, the judge/loop lane, the CI lane, the reference lane. The cannot-see footer reports per lane. | UX-review-2026-10-06 |
21
+ | loop | One unattended run over a predicate, recorded in the committed .goblin/loop/ (predicate, pin, first-run, budget, one decisions.tsv row per iteration). | docs/LOOP.md |
22
+ | never-relax | The predicate's digest is recorded at loop start and never updates itself; relaxing it is closing this loop and opening another, with the old predicate archived under .goblin/loop/closed-<date>/. | docs/LOOP.md |
23
+ | opt-out | A part recorded in disabled: so its required checks report SKIP (opt-out) instead of failing. | R6 sec.6.3 |
24
+ | overnight | P10: an unattended run over a checkable predicate written before iteration 1. | R6 sec.3 |
25
+ | part | One installable unit a class requires or forbids: handoff, spec, gate, replay, ratchet, pr-gate, review-panel, playbooks, tokens. | R7 sec.5 |
26
+ | patch-id | git patch-id --stable of base..head. A new head voids a verdict; a matching commit message does not restore it. | R1 sec.10 |
27
+ | playbook | A named, ordered procedure with a measurable verification step. goblin-stack ships 15. | R6 sec.3 |
28
+ | predicate | One shell command that exits 0 when the loop is finished. Written before iteration 1 and run once before it, so its first state is known to be red. A duration is not a predicate. | docs/LOOP.md + pstack guide/07 |
29
+ | preimage | The input that produces a known hash. The hash checks here prove non-drift, not preimage resistance - they are tamper-evidence, not signatures (docs/LIMITS.md #18). | UX-review-2026-10-06 |
30
+ | profile | A worker identity in the Hermes fleet (architect, coder, reviewer, ...). Owns memory and skills. | R4 |
31
+ | review-panel | N independent verdict lanes, each its own card with its own resolved model. Only at stakes S3+. | R6 sec.4.3 |
32
+ | REPLAY | Re-running each assertion against the pinned pre-change commit and requiring it to be RED there. A check green on both trees proves nothing. | PP sec.3 |
33
+ | ratchet | A count that must not rise, with a declared ceiling; a rise is allowed only when re-anchored with the arithmetic (old + N new = new). | PP sec.4 |
34
+ | role | What the work needs: code \| judgment \| review-panel \| synthesis \| investigate \| judge. A role maps to a profile; it never names a model. | R6 sec.4.1 |
35
+ | round | One full pass of the work cycle: plan, implement, gate, replay, hand off. The unit a wave code counts. | UX-review-2026-10-06 |
36
+ | skill | A Hermes SKILL.md directory. goblin-stack installs its skills into the target repo at .hermes/skills/ (project tier), never into a profile. | R6 sec.0.1 / sec.7.3 |
37
+ | SPEC | A per-round document written before implementation: measured root cause plus an AC: list checkable without a human. | PP sec.2 |
38
+ | stakes | The S0-S4 ladder that chooses the review gate strength by the change, not by the repo. | R5 sec.3.2 |
39
+ | sweep | P11: the same change or question across many projects, enumerated by glob, one card each. | R6 sec.3 |
40
+ | vendored | Copied into the repo rather than read from the package, so it survives offline and is hashed by IN-02 like any installed file. | UX-review-2026-10-06 |
package/docs/GUIDE.md CHANGED
@@ -58,6 +58,10 @@ Everything else in gobstack follows from that sentence. If you remember one thin
58
58
  guide, remember that one — it is also the standard the harness holds itself to, and the reason it
59
59
  ships a file of things it *cannot* check (`docs/LIMITS.md`).
60
60
 
61
+ **Words this guide uses** — gate, ratchet, class, part, replay and the rest are defined in one
62
+ sentence each in `docs/GLOSSARY.md` (rendered from the glossary table the install ships:
63
+ `.goblin/manifest/glossary.tsv`).
64
+
61
65
  ---
62
66
 
63
67
  ## 2. Before you begin
@@ -78,8 +82,8 @@ You need node ≥ 18 (for the npm shim only), beyond the row above.
78
82
 
79
83
  npm i -g @techgoblin/gobstack
80
84
 
81
- This puts a single command, `goblin`, on your PATH — the node shim over the bash engine, and the
82
- one way this guide installs it.
85
+ This puts **two** commands on your PATH — `gob` and `goblin`, both the same node shim over the
86
+ bash engine. The docs say `gob` throughout; either works.
83
87
 
84
88
  ---
85
89
 
@@ -113,7 +117,8 @@ or the plain installer this wizard drives, if you prefer the one-shot shape:
113
117
 
114
118
  Expected output (this is a real transcript, trimmed):
115
119
 
116
- created 25 · updated 0 · unchanged 0 · skipped 0
120
+ created 24 · updated 0 · unchanged 0 · skipped 0
121
+ recorded opt-out: ci-gate
117
122
 
118
123
  next:
119
124
  1. cd /tmp/gs-try && git add -A && git commit # the install is a change like any other
@@ -121,12 +126,13 @@ Expected output (this is a real transcript, trimmed):
121
126
  3. edit .goblin/goblin.yaml: replace the default gate with your real commands (P8 step 3)
122
127
  4. agent skills are opt-in: gob sync --platform <p> # hermes, claude, copilot, cursor, opencode, codex, gemini
123
128
 
124
- **`created 25`** is the installer's count of the files it **tracks** — the 16 in its `files`
125
- map, the 8 it `owns`, and `.gitignore`. It writes **26**: the 26th is `.goblin/installed.json`,
126
- the record it keeps for itself, which it writes but does not count. It has written nothing
127
- outside this directory. The default install ships **no agent skills** — the harness is neutral,
128
- and `gob sync --platform <p>` is the per-platform opt-in (`gob emit` is the same command under
129
- its original name; the old `--skills yes` default is
129
+ **`created 24`** is the installer's count of the files it **tracks** — one fewer than the plain
130
+ installer's 25, because the wizard's unanswered ci screen defaults to an explicit **no**, which is
131
+ recorded as an opt-out and suppresses `.github/workflows/goblin-gate.yml`. It writes **25**: the
132
+ 25th is `.goblin/installed.json`, the record it keeps for itself, which it writes but does not
133
+ count. It has written nothing outside this directory. The default install ships **no agent
134
+ skills** — the harness is neutral, and `gob sync --platform <p>` is the per-platform opt-in
135
+ (`gob emit` is the same command under its original name; the old `--skills yes` default is
130
136
  still there for repos that want the Hermes project tier vendored).
131
137
 
132
138
  ### Why `git init -b main` matters
@@ -150,15 +156,16 @@ branch** (unless you want to).
150
156
 
151
157
  You will see one line per rule. The shape:
152
158
 
153
- PASS IN-01 (test -s .goblin/installed.json && grep -q '"version"' .goblin/installed.json)
154
- PASS IN-02 16 installed files hashed | practice pin ok
159
+ PASS IN-01 the install record exists and names its version
160
+ PASS IN-02 15 installed files hashed
155
161
  FAIL HP-05 HANDOFF.md names no commit that exists in this repo
162
+ FAIL GT-02 gate commit: bash tests/run-tests.sh -> exit 127
156
163
  SKIP HS-02 no pinned pre-change commit yet - the REPLAY is not provable
157
164
  ADV HP-04 A stale sentence is corrected in place... (advisory)
158
165
 
159
166
  and a summary line at the bottom:
160
167
 
161
- 37 passed, 1 failed, 11 advisory, 33 skipped # the one FAIL is HP-05, below
168
+ 35 passed, 2 failed, 11 advisory, 34 skipped # HP-05 and GT-02, below
162
169
 
163
170
  ### How to read that output
164
171
 
@@ -178,6 +185,25 @@ one. The list of what is still skipping *is* your onboarding checklist.
178
185
  telling you the HANDOFF does not yet name a commit — fix it by naming your HEAD in the `State`
179
186
  section.
180
187
 
188
+ ### The day-one table, measured on this exact walk
189
+
190
+ | Step | Command | Verify prints | The FAILs |
191
+ |---|---|---|---|
192
+ | 1. the wizard ran | `gob init ... --yes` | `31 passed, 6 failed, 11 advisory, 34 skipped` | the install is uncommitted (`CM-03`), `HP-05`, `GT-02` exit 127, and the identity/branch rows if you skipped the flags |
193
+ | 2. the first commit | `git add -A && git commit` | `35 passed, 2 failed, 11 advisory, 34 skipped` | `HP-05` (the placeholder) and `GT-02` |
194
+ | 3. name a real HEAD — and **commit that too** | edit `HANDOFF.md`, then `git add -A && git commit` | `36 passed, 1 failed, 11 advisory, 34 skipped` | `GT-02` only |
195
+ | 4. your real gate | edit `gates:` in `.goblin/goblin.yaml` (§5) | `36 passed, 0 failed, ...` | none — green |
196
+
197
+ Two of those deserve their name spelled out:
198
+
199
+ - **`GT-02` exit 127 is the guide's own teaching point, not a defect.** The default gate is
200
+ `bash tests/run-tests.sh`, and a throwaway repo has no `tests/` — the shell's own
201
+ *command not found*. It stays red until §5's most valuable edit (your real `gates:`) replaces
202
+ it. The failure line tells you this: `gate commit: bash tests/run-tests.sh -> exit 127`.
203
+ - **Step 3 is two steps on purpose.** Naming a real HEAD in `HANDOFF.md` without committing it
204
+ re-reds `CM-03` (`1 dirty entr(y|ies)`) — commit-as-you-go starts on minute one. Edit, commit,
205
+ then verify.
206
+
181
207
  ### The three-day-one failures, and why they are not a broken harness
182
208
 
183
209
  If you ran step 1 without `-b main`, or with the wrong git identity, you will see:
@@ -194,8 +220,9 @@ against a declared expectation. That is exactly what you want it to do.
194
220
  `HP-05` deserves one sentence more, because it surprises people: the scaffold ships
195
221
  `HEAD when this file was written: `0000000``, and `HP-05` **rejects that placeholder on purpose**.
196
222
  A file that names a commit which does not exist is worse than one that names none — it looks like a
197
- record. Commit first, then write the real short SHA in. Measured: with the placeholder left in,
198
- verify reports `37 passed, 1 failed`; with the real SHA, `38 passed, 0 failed`.
223
+ record. Commit first, then write the real short SHA in. Measured on this walk: with the placeholder
224
+ left in, verify reports `35 passed, 2 failed`; with the real SHA (and the edit committed),
225
+ `36 passed, 1 failed` — the one FAIL being the `GT-02` exit 127 above.
199
226
 
200
227
  ---
201
228
 
@@ -402,16 +429,20 @@ the next session.
402
429
 
403
430
  ## 9. What to expect on day one (so you do not misread it)
404
431
 
405
- A software-class install lands on a specific shape. The scaffold ships one deliberate red — `HP-05`, the
406
- `0000000` placeholder in `HANDOFF.md` (§4) — so a literal first run prints:
432
+ A software-class install through the wizard lands on a specific shape. Two of the first reds are
433
+ the scaffold teaching on purpose — `HP-05`, the `0000000` placeholder in `HANDOFF.md` (§4), and
434
+ `GT-02`, the default gate pointing at a test script a throwaway repo does not have. The walk in §4
435
+ measured, step by step:
407
436
 
408
- 37 passed, 1 failed, 11 advisory, 33 skipped (the one FAIL is HP-05)
437
+ 31 passed, 6 failed, 11 advisory, 34 skipped # straight after the wizard, nothing committed
438
+ 35 passed, 2 failed, 11 advisory, 34 skipped # first commit: HP-05 and GT-02 left
439
+ 36 passed, 1 failed, 11 advisory, 34 skipped # real HEAD named and committed: GT-02 left
409
440
 
410
- Name a real commit in `HANDOFF.md` and commit, and it is green:
441
+ Name a real commit in `HANDOFF.md`, commit, and give `gates:` real commands (§5), and it is green:
411
442
 
412
- 38 passed, 0 failed, 11 advisory, 33 skipped (on a real project; your numbers will differ)
443
+ 36 passed, 0 failed, 11 advisory, 34 skipped (on a real project; your numbers will differ)
413
444
 
414
- **Thirty-three rows skipping is correct**, and each skip prints its reason. In plain terms: the
445
+ **Thirty-four rows skipping is correct**, and each skip prints its reason. In plain terms: the
415
446
  harness is telling you which of its rules have nothing to read yet. It is a checklist, not a
416
447
  scolding.
417
448
 
@@ -440,13 +471,14 @@ Two readings that are easy to get wrong:
440
471
  | `gob install: unknown subcommand` (exit 2) | you ran a bare `gob install` without the npm package installed | install the npm package first: `npm i -g @techgoblin/gobstack`, then `gob install` |
441
472
  | `IN-03` fails, "manifest is broken" | a row has a broken check column | fix the row; this is a source defect, not yours |
442
473
  | a `FAIL` you believe is wrong | the check may be weak, or your belief may be | run `--only <id>` and read the command it prints |
474
+ | a changelog or matrix note says `W6` or `Z1-4` | that is a revision wave code | `docs/RECORD-NOTES.md` is the legend, one line per code |
443
475
 
444
476
  **The `HANDOFF.md` refusal is the most common one, and `--force` is never the answer.** `--force`
445
477
  replaces your project's own record with a blank scaffold — the exact act the refusal exists to
446
478
  prevent. Reconcile instead: keep your file, and add the five sections it is missing. The measured
447
479
  cost of that edit, on a real 2450-line handoff, was **15 lines added, none removed**.
448
480
 
449
- **One engine, many repos (W1):** the rule table does not have to live in every repo. A repo can
481
+ **One engine, many repos:** the rule table does not have to live in every repo. A repo can
450
482
  point at a shared engine with one line in `.goblin/goblin.yaml`:
451
483
 
452
484
  engine_dir: ~/.goblin/engine # absolute or ~/-prefixed; absent = per-repo engine
@@ -458,7 +490,7 @@ every run's footer (`engine: mode=… cli_sha256=… enforcement_tsv_sha256=…`
458
490
  available outside any repo through the npm CLI: `gob verify` / `gob bans` / `gob audit` /
459
491
  `gob doctor` / `gob sync` / `gob upgrade` / `gob --version`.
460
492
 
461
- **Migrating a repo to the global engine (W3):**
493
+ **Migrating a repo to the global engine:**
462
494
 
463
495
  gob upgrade # 8 steps, two commits, one report
464
496
 
@@ -532,6 +564,8 @@ Named procedures, installed as project-local skills. Each has a measurable verif
532
564
  | File | Read it for |
533
565
  |---|---|
534
566
  | `docs/DESIGN.md` | the thesis and every rejected alternative |
567
+ | `docs/GLOSSARY.md` | every term of art in one table (rendered from `.goblin/manifest/glossary.tsv`) |
568
+ | `docs/RECORD-NOTES.md` | the wave codes the changelog uses, one line each |
535
569
  | `docs/FLOWS.md` | the playbooks in full, with reasons |
536
570
  | `docs/ENFORCEMENT.md` | the rule matrix, rendered for a human |
537
571
  | `docs/LIMITS.md` | **what this cannot check** — read this one early |
package/docs/LIMITS.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # Limits — where this is weaker, and what is unproven
2
2
 
3
3
  Honest accounting. Nothing here is a hedge for a defect that could be fixed; each is either a
4
- deliberate trade or an unfilled gap.
4
+ deliberate trade or an unfilled gap. (This file keeps the revision wave codes — `W6`, `Z1`, and
5
+ the rest — in its dated parentheticals; `docs/RECORD-NOTES.md` is the legend.)
5
6
 
6
7
  ## Weaker than the Cursor harness it learns from
7
8
 
@@ -0,0 +1,21 @@
1
+ # Record notes — the wave codes
2
+
3
+ The CHANGELOG, the enforcement matrix's parentheticals, and the older commit messages name
4
+ revisions by a short wave code. This file is the legend: one line each, oldest last. The prose
5
+ documents (README, GUIDE) do not use the codes; when one of these revisions matters to a reader,
6
+ the docs name it by what it changed and point here.
7
+
8
+ | code | revision | in one line |
9
+ |---|---|---|
10
+ | W1 | engine mode | the rule table can live in a shared engine (`engine_dir:`), not only per-repo; declared-but-unusable is exit 2 with no fallback |
11
+ | W3 | `gob upgrade` | the 8-step migration of a repo to the global engine, two commits, refusal on every unsafe tree |
12
+ | W4 | class + ci-gate | the five domain classes replaced the A–E letters (read-time aliases kept), and `ci-gate` became a declared part with its own workflow |
13
+ | W5 | report width | the verify report learned columns: payloads fold at the report width, multi-line payloads print whole, `--verbose` prints everything |
14
+ | W6 | neutral-first | a default install ships no agent skills (`gob sync --platform <p>` is the opt-in), the desktop class merged into software, and the wizard got its health-check screen |
15
+ | G8 | audit finding 8 | the verify-UX audit's surface classes: refusals that print nothing, rows that pass vacuously, `--only` selections that run nothing |
16
+ | G8-2 | stdout discipline | the engine banner moved to stderr; stdout carries verdict lines and the run's own annotations only |
17
+ | X1 | exit-code audit | every refusal and every silent path got a distinct exit code and a stated reason (0 green, 1 red, 2 refusal/config, 3 skip-by-design) |
18
+ | AB3 | claims pinned | every count a document states about this artifact is asserted against a real install (file maps, row counts, summary lines) |
19
+ | Z1 | record integrity | the loop/judge record contract: pins, digests, the never-relax rule, and what a record proves versus what it cannot |
20
+ | V3 | day-one framing | the verify run explains its reds to a newcomer: remedy lines under FAILs, the owner-mismatch note, the fresh-clone banner, recovery lines |
21
+ | G9 | guide re-walk | docs/GUIDE.md re-walked on the wizard path with every number re-measured (see t-doc-guide-init.sh) |
@@ -1,6 +1,6 @@
1
1
  id scope rule enforced_by artifact check if_not_why
2
2
  IN-01 target The install exists and records its version + every file's hash. script .goblin/installed.json goblin-verify --only IN-01 — (W1: the check is a builtin so the engine.mode=global clause can run — a global, declaration-only repo has no install record and SKIPs with `global engine mode — no per-repo install record`; the vendored clauses are the old one-liner: the record exists and names its version)
3
- IN-02 target Every installed file still matches its recorded hash. script .goblin/installed.json goblin-verify --only IN-02 —
3
+ IN-02 target Every installed file still matches its recorded hash. script .goblin/installed.json goblin-verify --only IN-02 recover: gob install --upgrade, or git checkout -- .goblin/installed.json; for a file it names drifted: git checkout -- <path> (if the edit is yours and intended, re-pin it: gob install --target <dir> --re-pin)
4
4
  IN-03 target The verifier's own manifest is complete: every rule has a check or is advisory. script manifest/enforcement.tsv goblin-verify --only IN-03 — (this row is the reason the matrix cannot rot; the second clause is D6's shape in general: a row that carries no check must be labelled advisory, or it claims verification it does not perform. The third clause is Z1-5: `enforced_by` is documented as a closed enum in docs/ENFORCEMENT.md and was read by NOTHING, so a typo in that cell changed nothing - `script|lint|gate|advisory` plus `test`, the source-scope value whose check is tests/run-tests.sh. W1: the check runs against whichever manifest the engine actually resolved (the chain in bin/goblin-verify), no longer the hardcoded per-repo path — the rule's meaning is untouched, only the path input follows the engine)
5
5
  IN-04 target No file goblin-stack did not create has been overwritten. script .goblin/installed.json goblin-verify --only IN-04 Detects a file the installer recorded as pre-existing (a `refused` entry) that has since vanished, or that is listed as installed anyway. The second clause is an internal-consistency guard: with correct code a refused path is never written, so it fires only if the installer regresses. The negative control exercises the vanished branch.
6
6
  HP-01 target HANDOFF.md exists at the root. gate HANDOFF.md test -f HANDOFF.md —
@@ -19,7 +19,7 @@ GT-05 target The ratchet has not risen. gate ratchet.cmd goblin-verify --only GT
19
19
  HS-01 target Asserting harnesses follow the house shape. lint harness_dir/* goblin-verify --only HS-01 A declared harness_dir that is absent FAILS when the class scaffolds one (config `scaffold_checks: yes`, classes A and C); a class that ships no harness dir (B/D/E) SKIPs with a reason. Keying the skip off the path alone let one config line switch this row and HS-02 off. A report utility in the same dir is counted and reported separately rather than failing the run.
20
20
  HS-02 target A check green on both trees proves nothing - the REPLAY must show RED pre-change. gate harness_dir/*, replay.env, replay.cmd goblin-verify --only HS-02 The declared `replay.cmd` is EXECUTED with `{name}` replaced by each harness's name, in the pre-change worktree, with `replay.env=<commit>` set (Z1-4). Two clauses that used to be unheard: a command that interpolates no `{name}` FAILs (it cannot be running the harness it names, so nothing was replayed), and a command that cannot be executed at all - exit 126 or 127 - FAILs rather than counting as a RED harness. The harness's name is substituted SHELL-QUOTED (`printf %q`), because the name is part of the command text the shell parses; unquoted, a name carrying `;` or `#` reached the shell as syntax (Z2-3, the G8-1 surface class), and the control for it carries a metacharacter-bearing file name. What it cannot see: a command that runs *something else* under the harness's name and exits non-zero, and whether the harness tests the right path rather than merely failing on this tree.
21
21
  HS-03 target Source probes read text with comments blanked first. advisory harness_dir/* advisory Recognising 'this probe reads source text' is semantic; a grep for the blanking helper produces false FAILs on harnesses that do not probe source.
22
- CM-01 target Commits carry the owner identity, not an ambient one. Current scope: this gates the identity of HEAD (the last commit) at the moment of the run - earlier commits by other authors are not scanned. gate git log test "$(git log -1 --format='%ae')" = "$(grep '^owner_email:' .goblin/goblin.yaml | cut -d' ' -f2)" —
22
+ CM-01 target Commits carry the owner identity, not an ambient one. Current scope: this gates the identity of HEAD (the last commit) at the moment of the run - earlier commits by other authors are not scanned. gate git log test "$(git log -1 --format='%ae')" = "$(grep '^owner_email:' .goblin/goblin.yaml | cut -d' ' -f2)" note: this repo records a different owner (you are probably new here) — commit with git -c user.email=<owner_email> --author=<owner_email>, or update owner_email: in .goblin/goblin.yaml
23
23
  CM-02 target The commit message was written to a file, not passed with -m. advisory git log advisory A backtick lost to command substitution leaves no trace a later check can read. Reported as a heuristic (unbalanced backticks in a body) only.
24
24
  CM-03 target Commit-as-you-go: the working tree is not carrying a dead run's work. script git status --porcelain goblin-verify --only CM-03 —
25
25
  MD-01 target No model name is hardcoded in any reusable rule. lint skills/ manifest/ bin/ templates/ presets/ .goblin/ .hermes/ for d in skills manifest bin templates presets .goblin .hermes; do [ -d "$d" ] || continue; grep -rniE '(d[e]epseek|cl[a]ude|g[p]t-[0-9]|gr[o]k|g[e]mini|g[l]m-[0-9]|k[i]mi)[a-z0-9.:_-]*' "$d" && exit 1; done; exit 0 — (the pattern is written with character classes so this row cannot match itself; tests/t-verify-red.sh proves it still catches a real model name. SCOPE, stated so a repo-wide grep is not re-reported as a hole (G8-10): this row reads the seven directories an install writes rules into - skills/ manifest/ bin/ templates/ presets/ .goblin/ .hermes/. It does NOT read tests/, docs/, or the source automations/ directory; tests/ is where a control must be free to WRITE the thing it guards, and the source directories are covered by MD-01's own body in tests/run-tests.sh, which scans skills manifest bin templates presets automations. Both control strings are assembled at run time, so a repo-wide grep over the whole tree is 0 and the row's own scope is 0 by construction.)
@@ -23,3 +23,11 @@ judge A role that decides whether a PROCESS met its own predicate, from a comman
23
23
  predicate One shell command that exits 0 when the loop is finished. Written before iteration 1 and run once before it, so its first state is known to be red. A duration is not a predicate. docs/LOOP.md + pstack guide/07
24
24
  loop One unattended run over a predicate, recorded in the committed .goblin/loop/ (predicate, pin, first-run, budget, one decisions.tsv row per iteration). docs/LOOP.md
25
25
  never-relax The predicate's digest is recorded at loop start and never updates itself; relaxing it is closing this loop and opening another, with the old predicate archived under .goblin/loop/closed-<date>/. docs/LOOP.md
26
+ round One full pass of the work cycle: plan, implement, gate, replay, hand off. The unit a wave code counts. UX-review-2026-10-06
27
+ vendored Copied into the repo rather than read from the package, so it survives offline and is hashed by IN-02 like any installed file. UX-review-2026-10-06
28
+ forge The hosting platform a repo pushes to (GitHub, GitLab, ...). PG-05 and PG-06 read the workflow text but can never see what the forge itself enforces. UX-review-2026-10-06
29
+ drift A file whose current bytes no longer match the hash recorded at install time. IN-02 reports it; the remedy column says how to recover. UX-review-2026-10-06
30
+ preimage The input that produces a known hash. The hash checks here prove non-drift, not preimage resistance - they are tamper-evidence, not signatures (docs/LIMITS.md #18). UX-review-2026-10-06
31
+ lane A family of rules that share a mechanism and a blind spot: the ban lane, the judge/loop lane, the CI lane, the reference lane. The cannot-see footer reports per lane. UX-review-2026-10-06
32
+ foreman The orchestrating agent that delegates work to the fleet; the role that reads HANDOFF.md first and writes it last. UX-review-2026-10-06
33
+ engine The bash programs plus the manifest a verify run actually resolved: per-repo (.goblin/bin) or global (engine_dir:). Named in every run's footer. UX-review-2026-10-06
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techgoblin/gobstack",
3
- "version": "0.5.0-beta.5",
3
+ "version": "0.5.0-beta.6",
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",
@@ -1,334 +0,0 @@
1
- Script started on 2026-10-02 00:53:48+07:00 [COMMAND="bash /home/harvey/projects/goblin-stack/bin/goblin-init --target /tmp/ptyfinal/repo --yes" <not executed on terminal>]
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
- ▄███▄ ▄██▄ ██▄ █ ▄█▄ █ █ ▄██▀ █████ ▄███▄ ▄███ █ █
11
- █ █ █ █ █▄▀█ █ █ █▄ █ ▀██▄ █ █ █ █ █ █
12
- █ ▄██ █ █ █▄▀█ █ █ █ ▄█ ▄█▀ █ █████ █ ██
13
- ▀██▀▀ ▀██▀ ██▀ ▀▀▀▀ ▀█▀ █ █ ██▀▀ ▀ █ █ ▀██▀ █ █
14
-  discipline around whatever executes
15
- 
16
-  gob init — first-run setup (gobstack v0.4.4)
17
-  6 steps · every step has a flag · nothing is written until the plan is confirmed
18
- 
19
-  ◆ 1. platforms 
20
-  ○ 2. class 
21
-  ○ 3. branch/email 
22
-  ○ 4. first gate 
23
-  ○ 5. emit 
24
-  ○ 6. done 
25
- ▄███▄ ▄██▄ ██▄ █ ▄█▄ █ █ ▄██▀ █████ ▄███▄ ▄███ █ █
26
- █ █ █ █ █▄▀█ █ █ █▄ █ ▀██▄ █ █ █ █ █ █
27
- █ ▄██ █ █ █▄▀█ █ █ █ ▄█ ▄█▀ █ █████ █ ██
28
- ▀██▀▀ ▀██▀ ██▀ ▀▀▀▀ ▀█▀ █ █ ██▀▀ ▀ █ █ ▀██▀ █ █
29
-  discipline around whatever executes
30
- 
31
-  gob init — first-run setup (gobstack v0.4.4)
32
-  6 steps · every step has a flag · nothing is written until the plan is confirmed
33
- 
34
-  ✔ 1. platforms 
35
-  ◆ 2. class 
36
-  ○ 3. branch/email 
37
-  ○ 4. first gate 
38
-  ○ 5. emit 
39
-  ○ 6. done 
40
- 
41
-  ✔ 1. platforms 0 detected
42
-  no platform detected here — the wizard will still install the harness; emit later with gob emit
43
- ▄███▄ ▄██▄ ██▄ █ ▄█▄ █ █ ▄██▀ █████ ▄███▄ ▄███ █ █
44
- █ █ █ █ █▄▀█ █ █ █▄ █ ▀██▄ █ █ █ █ █ █
45
- █ ▄██ █ █ █▄▀█ █ █ █ ▄█ ▄█▀ █ █████ █ ██
46
- ▀██▀▀ ▀██▀ ██▀ ▀▀▀▀ ▀█▀ █ █ ██▀▀ ▀ █ █ ▀██▀ █ █
47
-  discipline around whatever executes
48
- 
49
-  gob init — first-run setup (gobstack v0.4.4)
50
-  6 steps · every step has a flag · nothing is written until the plan is confirmed
51
- 
52
- 
53
-  ✔ 1. platforms 0 detected
54
-  no platform detected here — the wizard will still install the harness; emit later with gob emit
55
-  ✔ 1. platforms 0 detected
56
-  ✔ 2. class 
57
-  ◆ 3. branch/email 
58
-  ○ 4. first gate 
59
-  ○ 5. emit 
60
-  ○ 6. done 
61
- 
62
-  what kind of work does this repo do?
63
-   1 ▸ app shipped features, PRs, review gates (class A)
64
-   2 service backend jobs, config, unattended runs (class B)
65
-   3 game playable builds, perf budgets (class C)
66
-   4 research specs, replays, reference corpora (class D)
67
-   5 agent fleets, loops, unattended automation (class E)
68
-   6 desktop the shell you live in, perf budget (class F)
69
-  ↑ number + Enter · Enter = app · the class letter works too (A-F)
70
- 
71
-  ✔ 2. class A — shipped-software
72
- ▄███▄ ▄██▄ ██▄ █ ▄█▄ █ █ ▄██▀ █████ ▄███▄ ▄███ █ █
73
- █ █ █ █ █▄▀█ █ █ █▄ █ ▀██▄ █ █ █ █ █ █
74
- █ ▄██ █ █ █▄▀█ █ █ █ ▄█ ▄█▀ █ █████ █ ██
75
- ▀██▀▀ ▀██▀ ██▀ ▀▀▀▀ ▀█▀ █ █ ██▀▀ ▀ █ █ ▀██▀ █ █
76
-  discipline around whatever executes
77
- 
78
-  gob init — first-run setup (gobstack v0.4.4)
79
-  6 steps · every step has a flag · nothing is written until the plan is confirmed
80
- 
81
- 
82
-  ✔ 1. platforms 0 detected
83
-  no platform detected here — the wizard will still install the harness; emit later with gob emit
84
- 
85
-  what kind of work does this repo do?
86
-   1 ▸ app shipped features, PRs, review gates (class A)
87
-   2 service backend jobs, config, unattended runs (class B)
88
-   3 game playable builds, perf budgets (class C)
89
-   4 research specs, replays, reference corpora (class D)
90
-   5 agent fleets, loops, unattended automation (class E)
91
-   6 desktop the shell you live in, perf budget (class F)
92
-  ↑ number + Enter · Enter = app · the class letter works too (A-F)
93
- 
94
-  ✔ 2. class A — shipped-software
95
-  ✔ 1. platforms 0 detected
96
-  ✔ 2. class A (shipped-software)
97
-  ✔ 3. branch/email 
98
-  ◆ 4. first gate 
99
-  ○ 5. emit 
100
-  ○ 6. done 
101
- 
102
-  the forge contract — the identity this repo commits with (CM-01)
103
- default branch ─ main
104
- owner email ─ t@t.co
105
-  ✔ 3. branch/email main · t@t.co
106
- ▄███▄ ▄██▄ ██▄ █ ▄█▄ █ █ ▄██▀ █████ ▄███▄ ▄███ █ █
107
- █ █ █ █ █▄▀█ █ █ █▄ █ ▀██▄ █ █ █ █ █ █
108
- █ ▄██ █ █ █▄▀█ █ █ █ ▄█ ▄█▀ █ █████ █ ██
109
- ▀██▀▀ ▀██▀ ██▀ ▀▀▀▀ ▀█▀ █ █ ██▀▀ ▀ █ █ ▀██▀ █ █
110
-  discipline around whatever executes
111
- 
112
-  gob init — first-run setup (gobstack v0.4.4)
113
-  6 steps · every step has a flag · nothing is written until the plan is confirmed
114
- 
115
- 
116
-  ✔ 1. platforms 0 detected
117
-  no platform detected here — the wizard will still install the harness; emit later with gob emit
118
- 
119
-  what kind of work does this repo do?
120
-   1 ▸ app shipped features, PRs, review gates (class A)
121
-   2 service backend jobs, config, unattended runs (class B)
122
-   3 game playable builds, perf budgets (class C)
123
-   4 research specs, replays, reference corpora (class D)
124
-   5 agent fleets, loops, unattended automation (class E)
125
-   6 desktop the shell you live in, perf budget (class F)
126
-  ↑ number + Enter · Enter = app · the class letter works too (A-F)
127
- 
128
-  ✔ 2. class A — shipped-software
129
- 
130
-  the forge contract — the identity this repo commits with (CM-01)
131
- 
132
- 
133
-  ✔ 3. branch/email main · t@t.co
134
-  ✔ 1. platforms 0 detected
135
-  ✔ 2. class A (shipped-software)
136
-  ✔ 3. branch/email main · t@t.co
137
-  ✔ 4. first gate 
138
-  ◆ 5. emit 
139
-  ○ 6. done 
140
- 
141
-  one real command that must stay green — suggested for a bash suite:
142
-  bash tests/run-tests.sh
143
- gate command ─ bash tests/run-tests.sh
144
-  ✔ 4. first gate bash tests/run-tests.sh
145
- ▄███▄ ▄██▄ ██▄ █ ▄█▄ █ █ ▄██▀ █████ ▄███▄ ▄███ █ █
146
- █ █ █ █ █▄▀█ █ █ █▄ █ ▀██▄ █ █ █ █ █ █
147
- █ ▄██ █ █ █▄▀█ █ █ █ ▄█ ▄█▀ █ █████ █ ██
148
- ▀██▀▀ ▀██▀ ██▀ ▀▀▀▀ ▀█▀ █ █ ██▀▀ ▀ █ █ ▀██▀ █ █
149
-  discipline around whatever executes
150
- 
151
-  gob init — first-run setup (gobstack v0.4.4)
152
-  6 steps · every step has a flag · nothing is written until the plan is confirmed
153
- 
154
- 
155
-  ✔ 1. platforms 0 detected
156
-  no platform detected here — the wizard will still install the harness; emit later with gob emit
157
- 
158
-  what kind of work does this repo do?
159
-   1 ▸ app shipped features, PRs, review gates (class A)
160
-   2 service backend jobs, config, unattended runs (class B)
161
-   3 game playable builds, perf budgets (class C)
162
-   4 research specs, replays, reference corpora (class D)
163
-   5 agent fleets, loops, unattended automation (class E)
164
-   6 desktop the shell you live in, perf budget (class F)
165
-  ↑ number + Enter · Enter = app · the class letter works too (A-F)
166
- 
167
-  ✔ 2. class A — shipped-software
168
- 
169
-  the forge contract — the identity this repo commits with (CM-01)
170
- 
171
- 
172
-  ✔ 3. branch/email main · t@t.co
173
- 
174
-  one real command that must stay green — suggested for a bash suite:
175
-  bash tests/run-tests.sh
176
- 
177
-  ✔ 4. first gate bash tests/run-tests.sh
178
-  ✔ 1. platforms 0 detected
179
-  ✔ 2. class A (shipped-software)
180
-  ✔ 3. branch/email main · t@t.co
181
-  ✔ 4. first gate bash tests/run-tests.sh
182
-  ✔ 5. emit 
183
-  ◆ 6. done 
184
-  ✔ 5. emit 0 platform(s) · scope project
185
-
186
- running: goblin-install --target /tmp/ptyfinal/repo --class A
187
- created 50 · updated 0 · unchanged 0 · skipped 0
188
-
189
- next:
190
- 1. cd /tmp/ptyfinal/repo && git add -A && git commit # the install is a change like any other
191
- 2. .goblin/bin/goblin-verify # or add .goblin/bin to PATH
192
- 3. edit .goblin/goblin.yaml: replace the default gate with your real commands (P8 step 3)
193
- 4. hermes skills trust /tmp/ptyfinal/repo # one-time, so the project-tier skills load
194
-
195
- automations (optional; neither writes outside this repo, and A-02 has no agent in it):
196
- bash /tmp/ptyfinal/repo/.goblin/automations/drift-audit.sh --dry-run # read the record first
197
- cp /tmp/ptyfinal/repo/.goblin/automations/drift-audit.sh ~/.hermes/scripts/
198
- hermes cron create "0 9 * * 1" --name "Drift audit - weekly" --no-agent \
199
- --script drift-audit.sh --deliver <channel>
200
- P13/P14 in docs/FLOWS.md; the whole model in automations/README.md
201
- PASS IN-01 the install record exists and names its version
202
- PASS IN-02 41 installed files hashed
203
- PASS IN-03 the manifest is complete: every rule carries a check or is advisory, inside the enum
204
- PASS IN-04 0 pre-existing file(s) left untouched
205
- PASS HP-01 (test -f HANDOFF.md)
206
- PASS HP-02 (for h in 'START HERE' 'STATE|STATUS' 'GATES?' 'NEXT STEPS|NEXT' 'NOT VERIFIED|UNVERIFIED|NOT PROVEN|UNPROVEN|PENDING[^.]*DEVICE TEST'; do grep -qiE "^#{2,3}[[:space:]]+[^[:alpha:]]*($h)\b" HANDOFF.md || { echo "missing section: $h"; exit 1; }; done)
207
- PASS HP-03 1 gate line(s) inside the Gates section, each with a measured date (anchored on the declared gate name(s): commit todo_ceiling)
208
- ADV HP-04 A stale sentence is corrected in place with a dated parenthetical, never deleted. (advisory)
209
- PASS HP-05 names 3c96403 (ancestor of HEAD 3c96403)
210
- PASS SP-01 (ls ./*-SPEC.md >/dev/null 2>&1)
211
- FAIL SP-02 (test -z "$(git ls-files --others --exclude-standard -- '*-SPEC.md')" -> exit 1)
212
- PASS SP-03 (awk '/^[[:space:]]*[-*][[:space:]]/ && /AC[0-9]*:/ && !/`|==|===|exit|<|>/ {print; bad=1} END{exit bad}' ./*-SPEC.md)
213
- PASS GT-01 2 declared gate(s), each with a cmd
214
- PASS GT-02 commit=0 todo_ceiling=0 · client_js_bytes 0 (ceiling 0) · measured 2026-10-02
215
- PASS GT-03 (test -f .goblin/last-gate-line && [ .goblin/last-gate-line -nt "$(git rev-parse --git-dir)/logs/HEAD" ])
216
- PASS GT-04 ratchet client_js_bytes ceiling 0
217
- PASS GT-05 client_js_bytes 0 (ceiling 0)
218
- PASS HS-01 not an asserting harness (report utility): gate.sh
219
- 1 asserting harness(es), 1 report utility(ies)
220
- SKIP HS-02 no pinned pre-change commit yet (replay.commit empty or unknown) - REPLAY not provable
221
- ADV HS-03 Source probes read text with comments blanked first. (advisory)
222
- PASS CM-01 (test "$(git log -1 --format='%ae')" = "$(grep '^owner_email:' .goblin/goblin.yaml | cut -d' ' -f2)")
223
- ADV CM-02 The commit message was written to a file, not passed with -m. (advisory)
224
- FAIL CM-03 9 dirty entr(y|ies), max_dirty 0
225
- PASS MD-01 (for d in skills manifest bin templates presets .goblin .hermes; do [ -d "$d" ] || continue; grep -rniE '(d[e]epseek|cl[a]ude|g[p]t-[0-9]|gr[o]k|g[e]mini|g[l]m-[0-9]|k[i]mi)[a-z0-9.:_-]*' "$d" && exit 1; done; exit 0)
226
- ADV MD-02 no model mapping configured (/tmp/ptyfinal/home/projects/fleet-model.yaml absent) - advisory
227
- ADV MD-03 Role-pinned fan-out goes through kanban, not a model-less subagent spawn. (advisory)
228
- PASS PG-01 0 review note(s)
229
- PASS PG-02 0 review note(s) classified
230
- PASS PG-03 0 review note(s) patch-id checked
231
- ADV PG-04 Never bypass what the forge enforces. (advisory)
232
- PASS PG-05 1 workflow file(s) checked for a self-skipping required check
233
- PASS PG-06 goblin-gate.yml: runs the verifier, which executes every declared gate
234
- 1 workflow file(s) compared with the 2 declared gate(s)
235
- PASS DS-01 unchanged: .goblin/state.json=missing
236
- PASS DS-02 snapshot .goblin/state.json=missing
237
- ADV DOC-01 A significant change updates the docs that teach it. (advisory)
238
- ADV DOC-02 System-level changes are recorded wherever the project's standard says they live. (advisory)
239
- PASS SK-01 
240
- PASS SK-02 20 installed skill file(s) hashed
241
- PASS SK-03 advisory 10 of ceiling 10 (0 free slots: the next advisory row FAILs)
242
- PASS SK-04 
243
- PASS PT-01 (for d in skills manifest bin templates presets .goblin .hermes; do [ -d "$d" ] || continue; grep -rniE --exclude=goblin.yaml --exclude=installed.json '(h[a]rvey|tech-g[o]blin|/h[o]me/[a-z]+|g[o]blin-ui|op[e]n-door|sup[r]eme|bb[t]ech|c[l]v)' "$d" && exit 1; done; exit 0)
244
- PASS PT-02 declared main, actual main
245
- PASS CL-01 part pr-gate: optional, present
246
- part review-panel: optional, present
247
- part tokens: optional, absent
248
- PASS CL-02 archive: false - the full class requirement applies
249
- PASS SC-01 0 tracked secret file(s)
250
- PASS SC-02 the whole secret family is ignored (.env, .env.local, .env.production proved by git)
251
- PASS SC-03 0 client-visible secret-shaped name(s); no build output to scan for clause 2
252
- PASS SC-04 0 cookie write(s) checked; a JS-written cookie is readable by any script - reported, not failed
253
- PASS SC-05 0 write handler(s), 0 waived; 11 dir(s) declared
254
- SKIP SC-06 no package.json - there is no dependency manifest to lock
255
- SKIP SC-07 no audit record yet. Run .goblin/bin/goblin-audit once, deliberately, then commit
256
- .goblin/audit.tsv: SC-07 reads the record and never the network
257
- SKIP SC-08 no package-lock.json - there is no install-hook surface to enumerate
258
- ADV SC-09 Auth is applied consistently across sibling routes. (advisory)
259
- SKIP PF-01 no perf baseline recorded yet: run `find .next/static dist/assets dist -type f -name '*.js' -exec cat {} + 2>/dev/null | wc -c` on a pinned commit, then write
260
- perf.baseline_commit, perf.baseline_value, perf.measured and the matching ratchet ceiling
261
- PASS AU-01 2 automation producer(s) scanned, none opens with a network or forge verb
262
- SKIP AU-02 no reports/*/report.yaml - nothing to dedup
263
- SKIP AU-03 no reports/ - no reporter run in this repo to audit
264
- PASS AU-04 
265
- PASS BN-00 8 ban(s), each with an enforcement row and a named replacement
266
- SKIP BN-01 No `any` in application TypeScript. (no matching files: src) 
267
- SKIP BN-02 No `@ts-ignore` / `@ts-expect-error` suppressions. (no matching files: src) 
268
- SKIP BN-03 No direct network call from a component. (not enabled in bans: - add BN-03 to .goblin/goblin.yaml to turn it on) 
269
- SKIP BN-05 No import across a declared layer boundary. (no matching files: src) 
270
- SKIP BN-06 No renderer with Node access (`nodeIntegration: true`). (not enabled in bans: - add BN-06 to .goblin/goblin.yaml to turn it on) 
271
- SKIP BN-07 No renderer with context isolation or the process sandbox turned off. (not enabled in bans: - add BN-07 to .goblin/goblin.yaml to turn it on) 
272
- SKIP BN-08 No dangerous webPreferences. (not enabled in bans: - add BN-08 to .goblin/goblin.yaml to turn it on) 
273
- SKIP BN-09 No synchronous IPC and no `@electron/remote`. (not enabled in bans: - add BN-09 to .goblin/goblin.yaml to turn it on) 
274
- SKIP FM-01 feature_map: is empty - no map is declared, so there is nothing to index
275
- SKIP FM-02 feature_map: is empty - no map is declared, so there are no entry paths to resolve
276
- SKIP VA-01 verify_doctor: is empty - no doctor is declared, so "the generated skill was executed once" is not provable here
277
- SKIP JG-01 no loop record in this repo - no judge verdict to check
278
- ADV JG-02 no model mapping configured (/tmp/ptyfinal/home/projects/fleet-model.yaml absent) - advisory
279
- remedy: add a `judge:` entry beside the code lane in the file declared as models_file: in /tmp/ptyfinal/repo/.goblin/goblin.yaml
280
- ADV JG-03 A judge lane that has never returned a non-`done` verdict is escalated. (advisory)
281
- SKIP LP-01 no loop has run in this repo
282
- SKIP LP-02 no loop has run in this repo
283
- SKIP LP-03 no loop has run in this repo
284
- SKIP LP-04 no loop has run in this repo
285
- SKIP LP-05 no loop has run in this repo
286
- SKIP RC-01 reference_manifest: is empty - no reference corpus is declared, so there is nothing for a build to match against
287
- SKIP RC-02 reference_manifest: is empty - there is no manifest to shape-check (RC-01 skips for the same reason)
288
- SKIP RC-03 no manifests/ directory - this is not a lab repo with a reference corpus, so there are no tracked bytes to check
289
- SKIP RC-04 no manifests/*.sha256 - no acquisition record is declared, so there is none to read
290
- 41 passed, 2 failed, 11 advisory, 28 skipped
291
- of those 11 advisory: the matrix labels 10 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
292
- cannot see: whether a check in the harness dir tests the right path rather than
293
- merely passing; whether the forge is bound by the workflow CL-01 found; whether a human
294
- read the diff;
295
- whether the model mapping names a family that actually differs (MD-02 reports it
296
- and cannot change it); and whether .goblin/installed.json — the record every drift
297
- check trusts — was itself rewritten. It is not signed, so one edit to it (with the
298
- matching edit to the file it protects) defeats IN-02, SK-02 and HS-01 at once
299
- (docs/LIMITS.md #18). The ban lane carries the same blind spot and two of its own:
300
- the ban table is a manifest file under the same unsigned record, so a defanged ban
301
- passes BN-00 (docs/LIMITS.md #28); a ban probe is a TEXT probe with no AST, so a
302
- `Record<string, any>` is missed and a `: any` inside a string is reported
303
- (docs/LIMITS.md #27); and no check here can reach a ban IN CONTEXT — the row proves
304
- the forbidden path fails mechanically, never that the ban reached the agent that
305
- wrote the line. The judge/loop lane adds its own: every JG-/LP- row reads a RECORD, so
306
- it proves a handle, a pin, a budget and a pointer exist — never that the loop made
307
- progress (a changed pointer is a proxy, docs/LIMITS.md #33), never which lane returned
308
- a verdict, and never that the predicate was the right one or was really run. The CI lane
309
- adds its own: PG-05 and PG-06 read a workflow's TEXT, so PG-05 sees a job whose gate step is
310
- conditional and cannot see branch protection, the required-check list, or a forge that never
311
- ran the job at all, and PG-06 proves a declared gate is INVOKED in a file under
312
- .github/workflows/ — never that the forge marks that job required, never that it is the job
313
- the forge waits on, and never that the workflow can fail (that is PG-05). A required check
314
- that reports Success after skipping its gate is a green light for a commit whose gate never
315
- ran (docs/CI.md). The reference-corpus lane adds its own: RC-01 matches EXACT sha256 only,
316
- so a resized, recoloured or re-encoded reference asset, or copied text inside a shipped
317
- string, passes it unseen; RC-02 proves the manifest is well-shaped, never that its entries
318
- are the right hashes (as strong as tamper hashing, and installed.json is unsigned); RC-03
319
- reads the tracked tree and cannot see a payload renamed and re-encoded; and RC-04 proves an
320
- acquisition record EXISTS, never that the number beside it came from the store (HP-03's
321
- defect, one artifact over). No row here reaches a reference asset in CONTEXT either.
322
- engine: mode=vendored cli_sha256=2d37cf1c9f38a582ad60f3f75d2fcd53d10b743905dc3274eab6511c5c5ea008 enforcement_tsv_sha256=fd474c268b0852587c632910c503144fbcfe5e211ed2182902949a79fc5282b1
323
-
324
- gob verify: 41 passed, 2 failed, 28 skipped · exit 1
325
-
326
- next:
327
- 1. the 28 skipped checks ARE your checklist — gob verify names each one
328
- 2. replace the placeholder gate in .goblin/goblin.yaml with your real commands (P8 step 3)
329
- 3. not emitted here: gob emit --platform <p> --scope global
330
- 4. hermes skills trust /tmp/ptyfinal/repo # one-time, so the project-tier skills load
331
-
332
- ▙ the goblin sees you. the gate is RED — start there.
333
-
334
- Script done on 2026-10-02 00:53:50+07:00 [COMMAND_EXIT_CODE="0"]