@techgoblin/gobstack 0.5.0-beta.7 → 0.6.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/README.md +112 -123
- package/VERSION +1 -1
- package/automations/drift-audit.sh +4 -4
- package/bans/layer-check.sh +10 -8
- package/bin/goblin +61 -52
- package/bin/goblin-audit +11 -13
- package/bin/goblin-bans +11 -11
- package/bin/goblin-init +275 -713
- package/bin/goblin-install +160 -114
- package/bin/goblin-lib.sh +234 -1
- package/bin/goblin-map +607 -0
- package/bin/goblin-mcp.js +492 -0
- package/bin/goblin-model +4 -4
- package/bin/goblin-upgrade +1 -1
- package/bin/goblin-verify +159 -145
- package/bin/goblin.js +33 -49
- package/docs/ADOPTION.md +15 -15
- package/docs/CONTRACTS.md +16 -15
- package/docs/DESIGN.md +1 -1
- package/docs/ENFORCEMENT.md +89 -90
- package/docs/FLOWS.md +1 -1
- package/docs/GLOSSARY.md +3 -3
- package/docs/GUARDRAILS.md +5 -5
- package/docs/GUIDE.md +178 -169
- package/docs/INTEGRATION.md +1 -1
- package/docs/LIMITS.md +25 -0
- package/docs/LOOP.md +12 -12
- package/docs/RE-PLAYBOOK.md +3 -3
- package/docs/ROLES.md +5 -5
- package/manifest/bans.tsv +8 -8
- package/manifest/classes.tsv +3 -3
- package/manifest/enforcement.tsv +40 -40
- package/manifest/glossary.tsv +3 -3
- package/manifest/playbooks.tsv +1 -1
- package/package.json +1 -1
- package/presets/electron-overlay.yaml +2 -2
- package/presets/fleet.yaml +8 -7
- package/presets/game.yaml +1 -1
- package/presets/research.yaml +1 -1
- package/presets/service.yaml +1 -1
- package/presets/software.yaml +1 -1
- package/skills/goblin-bootstrap/SKILL.md +2 -2
- package/templates/AGENTS.md.tmpl +8 -18
- package/templates/HANDOFF.md.tmpl +5 -5
- package/templates/agents-block.tmpl +45 -0
- package/templates/audit-waiver.tsv.tmpl +2 -2
- package/templates/boundary-waivers.tmpl +1 -1
- package/templates/checks/gate.sh.tmpl +6 -6
- package/templates/install-hooks.allowlist.tmpl +1 -1
- package/templates/ci/goblin-gate.yml.tmpl +0 -46
- package/templates/goblin.yaml.tmpl +0 -146
- package/templates/loop/decisions.tsv.tmpl +0 -1
- package/templates/loop/predicate.tmpl +0 -16
package/bin/goblin
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# goblin — the
|
|
2
|
+
# goblin — the CLI dispatcher.
|
|
3
3
|
#
|
|
4
|
-
# gob
|
|
5
|
-
# gob
|
|
6
|
-
# gob
|
|
7
|
-
# gob
|
|
8
|
-
# gob
|
|
9
|
-
# gob
|
|
10
|
-
# gob upgrade [--target .] [...] # W3
|
|
4
|
+
# gob init [--heuristic | --write <proposal>] [--target <dir>] [...]
|
|
5
|
+
# gob map [target] [--agent | --heuristic] [--write <dir>] [--force]
|
|
6
|
+
# gob verify [--only <id[,id...]>] [--json] [--list] [--source <path>]
|
|
7
|
+
# gob bans [--only <id[,id...]>] [--list]
|
|
8
|
+
# gob mcp serve the harness over MCP stdio
|
|
9
|
+
# gob uninstall --target <dir>
|
|
11
10
|
# gob --version
|
|
12
11
|
#
|
|
12
|
+
# v2 SURFACE (AI-driven development): the CLI calls no AI API. `gob init` and `gob map`
|
|
13
|
+
# EMIT a prompt + schema that the agent already running in this repo fulfils with its own
|
|
14
|
+
# tools; heuristic detection demotes to the --heuristic fallback.
|
|
15
|
+
#
|
|
16
|
+
# UNWIRED this session (code kept under bin/, deletion is session 3): doctor, audit,
|
|
17
|
+
# upgrade, emit/sync. `sync` becomes an init-internal step (hidden --resync flag).
|
|
18
|
+
#
|
|
13
19
|
# Identity: the package is gobstack (npm @techgoblin/gobstack), the command is `gob`
|
|
14
|
-
# (`goblin` remains as a legacy alias).
|
|
15
|
-
# SHELL only — the node shim and npm packaging are W2, doctor/emit are W4a (exit-2
|
|
16
|
-
# placeholders naming their workstream), upgrade is W3 (same). G3 forbids a runtime
|
|
17
|
-
# rewrite: the engine stays bash, this file only routes and propagates.
|
|
20
|
+
# (`goblin` remains as a legacy alias).
|
|
18
21
|
#
|
|
19
22
|
# The four-value verify contract is unchanged and non-negotiable (bin/goblin-verify:5-9):
|
|
20
23
|
# 0 every executed check passed
|
|
@@ -22,11 +25,10 @@
|
|
|
22
25
|
# 2 could not run
|
|
23
26
|
# 3 the manifest itself is broken
|
|
24
27
|
# Every subcommand propagates the engine's exit code verbatim; no wrapper translates a
|
|
25
|
-
# 1 into a 0.
|
|
28
|
+
# 1 into a 0.
|
|
26
29
|
#
|
|
27
|
-
# Root resolution: find_installed_root()
|
|
28
|
-
#
|
|
29
|
-
# then $PWD — the D20 nested-repo fix). W1 changes nothing about its logic.
|
|
30
|
+
# Root resolution: find_installed_root() walks up from $PWD to an AGENTS.md carrying the
|
|
31
|
+
# gob marker block; fall back to git toplevel, then $PWD — the D20 nested-repo fix.
|
|
30
32
|
#
|
|
31
33
|
# No npm, no jq, no yq, no network. bash/awk/sed/grep only.
|
|
32
34
|
|
|
@@ -42,26 +44,30 @@ usage() {
|
|
|
42
44
|
cat <<'USAGE'
|
|
43
45
|
gob — the gobstack command line.
|
|
44
46
|
|
|
45
|
-
gob
|
|
46
|
-
gob
|
|
47
|
-
gob
|
|
48
|
-
gob
|
|
49
|
-
gob
|
|
50
|
-
gob
|
|
51
|
-
gob init [--target <dir>] [--class app|A-F] [--dry-run]
|
|
52
|
-
gob upgrade [--target .] [--dry-run] [--yes] [--engine-dir <path>]
|
|
47
|
+
gob init [--heuristic | --write <proposal>] [--target <dir>] [--class app|A-F] [--dry-run]
|
|
48
|
+
gob map [target] [--agent | --heuristic] [--write <dir>] [--force]
|
|
49
|
+
gob verify [--only <id[,id...]>] [--json] [--list] [--source <path>]
|
|
50
|
+
gob bans [--only <id[,id...]>] [--list]
|
|
51
|
+
gob mcp serve the harness to your agent over MCP stdio (verify/map/init tools)
|
|
52
|
+
gob uninstall --target <dir>
|
|
53
53
|
gob --version
|
|
54
54
|
|
|
55
|
+
gob init and gob map are AI-DRIVEN: they print a prompt + schema for the agent already
|
|
56
|
+
running in this repo, which writes the proposal file; `--write` validates and installs
|
|
57
|
+
it. The heuristic detectors are the --heuristic fallback.
|
|
58
|
+
|
|
55
59
|
Exit codes (verify): 0 pass | 1 a check failed | 2 could not run | 3 the manifest is
|
|
56
60
|
broken. Every subcommand propagates the engine's exit code verbatim.
|
|
57
61
|
USAGE
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
# ---- the repo-root locator
|
|
64
|
+
# ---- the repo-root locator (an AGENTS.md gob block is the install marker) ----
|
|
61
65
|
find_installed_root() {
|
|
62
66
|
local d="$PWD"
|
|
63
67
|
while :; do
|
|
64
|
-
if [ -f "$d
|
|
68
|
+
if [ -f "$d/AGENTS.md" ] && grep -q '^<!-- gob:begin' "$d/AGENTS.md" 2>/dev/null; then
|
|
69
|
+
printf '%s\n' "$d"; return 0
|
|
70
|
+
fi
|
|
65
71
|
[ "$d" = "/" ] && return 1
|
|
66
72
|
d=$(dirname "$d")
|
|
67
73
|
done
|
|
@@ -73,6 +79,15 @@ CMD="${1:-}"
|
|
|
73
79
|
shift
|
|
74
80
|
|
|
75
81
|
case "$CMD" in
|
|
82
|
+
init)
|
|
83
|
+
# The prompt+schema engine: prints the agent brief, or installs a proposal.
|
|
84
|
+
exec bash "$SRC/bin/goblin-init" "$@"
|
|
85
|
+
;;
|
|
86
|
+
map)
|
|
87
|
+
# The standalone feature-map generator: `--agent` (default) prints prompt+schema;
|
|
88
|
+
# --heuristic runs the scanner. Routes and propagates its 0/1/2 contract verbatim.
|
|
89
|
+
exec bash "$SRC/bin/goblin-map" "$@"
|
|
90
|
+
;;
|
|
76
91
|
verify)
|
|
77
92
|
# The engine is whatever the per-repo chain resolves (W1 §2.3): --source, vendored,
|
|
78
93
|
# engine_dir:, GOBLIN_ENGINE_DIR, ~/.goblin/engine — all inside goblin-verify. The
|
|
@@ -82,33 +97,16 @@ case "$CMD" in
|
|
|
82
97
|
bans)
|
|
83
98
|
exec bash "$SRC/bin/goblin-bans" "$@"
|
|
84
99
|
;;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
# propagates the doctor's 0/1/2 verbatim, like every other subcommand.
|
|
91
|
-
exec bash "$SRC/bin/goblin-doctor" "$@"
|
|
100
|
+
mcp)
|
|
101
|
+
# The MCP stdio server (bin/goblin-mcp.js): JSON-RPC 2.0 over stdin/stdout, a LOCAL
|
|
102
|
+
# tool server over the vendored harness — no API call, no socket. node is the same
|
|
103
|
+
# runtime bin/goblin.js already needs; it is exec'd directly, args pass through.
|
|
104
|
+
exec node "$SRC/bin/goblin-mcp.js" "$@"
|
|
92
105
|
;;
|
|
93
|
-
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
exec bash "$SRC/bin/goblin-
|
|
97
|
-
;;
|
|
98
|
-
sync)
|
|
99
|
-
# Wizard v2: the friendlier name for the same emission engine. `emit` stays a
|
|
100
|
-
# first-class verb; this case only adds the alias and propagates identically.
|
|
101
|
-
exec bash "$SRC/bin/goblin-emit" "$@"
|
|
102
|
-
;;
|
|
103
|
-
init)
|
|
104
|
-
# W6: the first-run wizard. It drives install/emit/verify and propagates their
|
|
105
|
-
# exit contract verbatim, like every other subcommand here.
|
|
106
|
-
exec bash "$SRC/bin/goblin-init" "$@"
|
|
107
|
-
;;
|
|
108
|
-
upgrade)
|
|
109
|
-
# W3: the migration lives in its own checkout-level script (W3-SPEC §1.1);
|
|
110
|
-
# the dispatcher routes and propagates the four-value contract verbatim.
|
|
111
|
-
exec bash "$SRC/bin/goblin-upgrade" "$@"
|
|
106
|
+
uninstall)
|
|
107
|
+
# Routes into the installer's uninstall job. `--uninstall` is appended FIRST so the
|
|
108
|
+
# user's own `--target <dir>` and options still parse.
|
|
109
|
+
exec bash "$SRC/bin/goblin-install" --uninstall "$@"
|
|
112
110
|
;;
|
|
113
111
|
--version|-V|-v)
|
|
114
112
|
printf '%s\n' "$VERSION"
|
|
@@ -118,6 +116,17 @@ case "$CMD" in
|
|
|
118
116
|
usage
|
|
119
117
|
exit 0
|
|
120
118
|
;;
|
|
119
|
+
doctor|audit|upgrade|emit|sync|install)
|
|
120
|
+
# Unwired from usage in v2 (code kept; deletion is session 3). The refusal names
|
|
121
|
+
# what replaced each verb rather than silently executing the old path.
|
|
122
|
+
g_err "gob $CMD is unwired in v2."
|
|
123
|
+
case "$CMD" in
|
|
124
|
+
install|init-legacy) g_err "fix: gob init --write <proposal> installs a repo now." ;;
|
|
125
|
+
sync|emit) g_err "fix: gob init --write <proposal> runs the sync step internally." ;;
|
|
126
|
+
doctor|audit|upgrade) g_err "fix: gob verify (audit stays a deliberate manual step; see docs/)." ;;
|
|
127
|
+
esac
|
|
128
|
+
exit 2
|
|
129
|
+
;;
|
|
121
130
|
*)
|
|
122
131
|
g_err "unknown subcommand: $CMD"
|
|
123
132
|
usage >&2
|
package/bin/goblin-audit
CHANGED
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
#
|
|
17
17
|
# Exit codes:
|
|
18
18
|
# 0 the record was written (whether or not it is clean)
|
|
19
|
-
# 2 usage, or no .
|
|
19
|
+
# 2 usage, or no AGENTS.md frontmatter to read `security.audit_cmd` from
|
|
20
20
|
# 3 the class declares no audit command (security.audit_cmd is empty) - nothing to run
|
|
21
21
|
# 4 the declared command could not run
|
|
22
22
|
# 5 the output could not be parsed as an audit report - REFUSING to write an empty record,
|
|
23
23
|
# because an empty record reads to SC-07 as "clean" and that would be a fabricated pass
|
|
24
24
|
set -uo pipefail
|
|
25
25
|
|
|
26
|
-
GOBLIN_AUDIT_VERSION="0.
|
|
26
|
+
GOBLIN_AUDIT_VERSION="0.6.0-alpha.1"
|
|
27
27
|
|
|
28
28
|
usage() { sed -n '2,/^$/p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
|
|
29
29
|
|
|
@@ -40,22 +40,20 @@ done
|
|
|
40
40
|
[ -n "$TARGET" ] || { printf 'goblin-audit: --target needs a directory\n' >&2; exit 2; }
|
|
41
41
|
cd "$TARGET" || { printf 'goblin-audit: no such directory: %s\n' "$TARGET" >&2; exit 2; }
|
|
42
42
|
|
|
43
|
-
CONFIG=".
|
|
44
|
-
[ -f "$CONFIG" ]
|
|
43
|
+
CONFIG="AGENTS.md"
|
|
44
|
+
[ -f "$CONFIG" ] && grep -q "^$GOB_AGENTS_BEGIN" "$CONFIG" 2>/dev/null \
|
|
45
|
+
|| { printf 'goblin-audit: %s has no %s gob block - run gob init first\n' "$TARGET" "$CONFIG" >&2; exit 2; }
|
|
45
46
|
|
|
46
47
|
# Self-contained on purpose: the audit runner must work even if goblin-lib.sh was edited or is
|
|
47
48
|
# missing, because it is the one tool a human runs by hand.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
v = $0; sub("^ " k ":[[:space:]]*", "", v); print v; exit
|
|
54
|
-
}
|
|
55
|
-
' "$1"
|
|
49
|
+
g_agents_read_inline() { # <file> <dotted-key> — the one reader the audit needs, inline
|
|
50
|
+
local f="$1" k="$2" line
|
|
51
|
+
while IFS= read -r line; do
|
|
52
|
+
case "$line" in "$k:"*) printf '%s\n' "${line#"$k":}"; return 0 ;; esac
|
|
53
|
+
done < "$f"
|
|
56
54
|
}
|
|
57
55
|
|
|
58
|
-
AUDIT_CMD=$(
|
|
56
|
+
AUDIT_CMD=$(g_agents_read_inline "$CONFIG" security.audit_cmd)
|
|
59
57
|
if [ -z "$AUDIT_CMD" ]; then
|
|
60
58
|
printf 'goblin-audit: this class declares no audit command (security.audit_cmd is empty in\n'
|
|
61
59
|
printf ' %s). Nothing to run, and no record to write.\n' "$CONFIG"
|
package/bin/goblin-bans
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
set -uo pipefail
|
|
30
30
|
|
|
31
|
-
GOBLIN_BANS_VERSION="0.
|
|
31
|
+
GOBLIN_BANS_VERSION="0.6.0-alpha.1"
|
|
32
32
|
SELF_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
33
33
|
# shellcheck source=goblin-lib.sh
|
|
34
34
|
. "$SELF_DIR/goblin-lib.sh"
|
|
@@ -49,7 +49,7 @@ done
|
|
|
49
49
|
find_installed_root() {
|
|
50
50
|
local d="$PWD"
|
|
51
51
|
while :; do
|
|
52
|
-
if [ -f "$d
|
|
52
|
+
if [ -f "$d/AGENTS.md" ] && grep -q "^$GOB_AGENTS_BEGIN" "$d/AGENTS.md" 2>/dev/null; then printf '%s\n' "$d"; return 0; fi
|
|
53
53
|
[ "$d" = "/" ] && return 1
|
|
54
54
|
d=$(dirname "$d")
|
|
55
55
|
done
|
|
@@ -60,9 +60,9 @@ if [ -z "$ROOT" ]; then
|
|
|
60
60
|
[ -n "$ROOT" ] || ROOT="$PWD"
|
|
61
61
|
fi
|
|
62
62
|
|
|
63
|
-
CONFIG="$ROOT
|
|
64
|
-
if [ -f "$ROOT/.
|
|
65
|
-
BANS="$ROOT/.
|
|
63
|
+
CONFIG="$ROOT/AGENTS.md"
|
|
64
|
+
if [ -f "$ROOT/.gob/manifest/bans.tsv" ]; then
|
|
65
|
+
BANS="$ROOT/.gob/manifest/bans.tsv"
|
|
66
66
|
else
|
|
67
67
|
BANS="$SELF_DIR/../manifest/bans.tsv"
|
|
68
68
|
fi
|
|
@@ -85,13 +85,11 @@ fi
|
|
|
85
85
|
# ---- the config: which bans this project turns on, and its narrow exceptions --
|
|
86
86
|
ENABLED=""
|
|
87
87
|
if [ -f "$CONFIG" ]; then
|
|
88
|
-
ENABLED=$(
|
|
89
|
-
ENABLED=${ENABLED#[}; ENABLED=${ENABLED%]}
|
|
90
|
-
ENABLED=$(printf '%s' "$ENABLED" | tr ',' ' ' | tr -s ' ' ' ')
|
|
88
|
+
ENABLED=$(g_agents_list "$CONFIG" bans | tr '\n' ' ')
|
|
91
89
|
# The electron opt-in (software class): `electron: true` turns the electron ban set ON even
|
|
92
90
|
# when a hand-edited bans: list omits it — the declaration is the contract, and the engine
|
|
93
91
|
# (not the LLM) is what makes a listed ban non-skippable. A repo without the key is unaffected.
|
|
94
|
-
if [ "$(
|
|
92
|
+
if [ "$(g_agents_read "$CONFIG" electron)" = "true" ]; then
|
|
95
93
|
for e in BN-06 BN-07 BN-08 BN-09; do
|
|
96
94
|
case " $ENABLED " in *" $e "*) ;; *) ENABLED="$ENABLED $e" ;; esac
|
|
97
95
|
done
|
|
@@ -113,7 +111,9 @@ enabled() {
|
|
|
113
111
|
# keeps the old behaviour, which fails CLOSED, never open.
|
|
114
112
|
exempt_paths() { # exempt_paths <id> -> path prefixes, one per line
|
|
115
113
|
[ -f "$CONFIG" ] || return 0
|
|
116
|
-
|
|
114
|
+
# v2 shape: one flat `[BN-01 src, BN-02 app]` array — item 1 is the ban id, item 2 its
|
|
115
|
+
# path prefix (g_agents_list splits on the commas, so an item keeps its inner space).
|
|
116
|
+
g_agents_list "$CONFIG" bans_exempt | awk -v id="$1" '$1==id {print $2}'
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
only_selected() {
|
|
@@ -131,7 +131,7 @@ while IFS=$'\t' read -r id ban globs detect replacement escape reviewer source;
|
|
|
131
131
|
SEL=$((SEL + 1))
|
|
132
132
|
|
|
133
133
|
if ! enabled "$id"; then
|
|
134
|
-
SKIP=$((SKIP + 1)); g_skip "$id" "$ban (not enabled in bans: - add $id to .
|
|
134
|
+
SKIP=$((SKIP + 1)); g_skip "$id" "$ban (not enabled in bans: - add $id to the bans: array in AGENTS.md to turn it on)"
|
|
135
135
|
continue
|
|
136
136
|
fi
|
|
137
137
|
# A ban whose globs match no file is a SKIP with a reason, not a pass and not a fail.
|