@junghanacs/entwurf 0.12.4 → 0.12.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/CHANGELOG.md +36 -0
- package/README.md +9 -1
- package/VERIFY.md +18 -28
- package/docs/setup-clean-host.md +27 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js +218 -0
- package/mcp/entwurf-bridge/tsconfig.build.json +18 -1
- package/package.json +4 -4
- package/pi/meta-bridge/entwurf-meta-receive/hooks/hooks.json +3 -3
- package/pi-extensions/meta-bridge-hook.ts +8 -3
- package/run.sh +351 -42
- package/scripts/__pycache__/meta-bridge-state.cpython-312.pyc +0 -0
- package/scripts/__pycache__/meta-bridge-state.cpython-313.pyc +0 -0
- package/scripts/__pycache__/register-pi-package.cpython-313.pyc +0 -0
- package/scripts/meta-bridge-doctor.sh +54 -9
- package/scripts/meta-bridge-install.sh +50 -13
- package/scripts/meta-bridge-state.py +60 -3
- package/scripts/meta-bridge-uninstall.sh +24 -0
- package/scripts/register-pi-package.py +149 -0
- package/scripts/smoke-meta-install-state.sh +164 -8
- package/scripts/smoke-user-scope-citizen.sh +129 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# smoke-user-scope-citizen — deterministic gate for pi packages[] citizen
|
|
3
|
+
# registration/removal (register-pi-package.py). Offline/hermetic: a fake
|
|
4
|
+
# settings file + a fake REPO_DIR under $TMP, no pi, no network, no ~/.pi touched.
|
|
5
|
+
#
|
|
6
|
+
# Guards the wiring that dropped when `pi install` was removed from setup
|
|
7
|
+
# (2026-07-03: `--entwurf-control` / `--emacs-agent-socket` unknown in a foreign
|
|
8
|
+
# cwd because entwurf was absent from ~/.pi/agent/settings.json packages[]).
|
|
9
|
+
# statusline `?` had a tripwire; THIS omission had none and hid until GLG hit it
|
|
10
|
+
# in another repo. This gate is that missing tripwire: if the registration ever
|
|
11
|
+
# regresses, `pnpm check` goes red.
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
|
+
REPO="$(cd "$HERE/.." && pwd)"
|
|
16
|
+
REG="$REPO/scripts/register-pi-package.py"
|
|
17
|
+
|
|
18
|
+
fail=0
|
|
19
|
+
ok() { echo " ok $*"; }
|
|
20
|
+
bad() { echo " FAIL $*"; fail=1; }
|
|
21
|
+
|
|
22
|
+
command -v python3 >/dev/null || { echo "FAIL: python3 not on PATH"; exit 1; }
|
|
23
|
+
|
|
24
|
+
TMP="$(mktemp -d -t psa-user-citizen.XXXXXX)"
|
|
25
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
26
|
+
S="$TMP/settings.json"
|
|
27
|
+
FAKE_REPO="$TMP/fake/entwurf"; mkdir -p "$FAKE_REPO"
|
|
28
|
+
RESOLVED="$(cd "$FAKE_REPO" && pwd)"
|
|
29
|
+
|
|
30
|
+
has_pkg() { python3 -c "import json,sys;p=json.load(open('$S'))['packages'];sys.exit(0 if '$1' in p else 1)"; }
|
|
31
|
+
count_entwurf() { python3 -c "import json;p=json.load(open('$S'))['packages'];print(sum(1 for x in p if isinstance((x if isinstance(x,str) else x.get('source')),str) and (x if isinstance(x,str) else x.get('source')).rstrip('/').split('/')[-1]=='entwurf' or (x if isinstance(x,str) else x.get('source'))=='$RESOLVED'))"; }
|
|
32
|
+
|
|
33
|
+
# 1. absent settings file → created with entwurf registered
|
|
34
|
+
python3 "$REG" "$S" "$FAKE_REPO" >/dev/null
|
|
35
|
+
if [ -f "$S" ] && has_pkg "$RESOLVED"; then ok "absent settings → entwurf registered (file created)"; else bad "entwurf not registered into a fresh settings file"; fi
|
|
36
|
+
|
|
37
|
+
# 2. idempotent: second run is a no-op AND does not even rewrite the file (mtime stable)
|
|
38
|
+
MT1="$(stat -c %Y "$S")"
|
|
39
|
+
sleep 1
|
|
40
|
+
OUT2="$(python3 "$REG" "$S" "$FAKE_REPO")"
|
|
41
|
+
MT2="$(stat -c %Y "$S")"
|
|
42
|
+
if printf '%s' "$OUT2" | grep -q 'no-op'; then ok "second run is a no-op (idempotent)"; else bad "second run was not a no-op: $OUT2"; fi
|
|
43
|
+
if [ "$MT1" = "$MT2" ]; then ok "no-op does NOT rewrite the file (mtime stable — strongest idempotence)"; else bad "no-op rewrote the file (mtime changed $MT1 -> $MT2)"; fi
|
|
44
|
+
if [ "$(count_entwurf)" = "1" ]; then ok "no duplicate entwurf entry after idempotent re-run"; else bad "duplicate entwurf entries: $(count_entwurf)"; fi
|
|
45
|
+
|
|
46
|
+
# 3. preserves unrelated packages AND unrelated keys — including look-alike repos
|
|
47
|
+
# whose path merely CONTAINS 'entwurf' and remote repos whose last segment is
|
|
48
|
+
# 'entwurf' but are not THIS package.
|
|
49
|
+
cat > "$S" <<'JSON'
|
|
50
|
+
{
|
|
51
|
+
"defaultProvider": "openai-codex",
|
|
52
|
+
"theme": "glg-dark",
|
|
53
|
+
"packages": ["../../repos/gh/andenken", "git:github.com/badlogic/pi-telegram",
|
|
54
|
+
"/home/me/entwurf-notes", "/x/openclaw-entwurf",
|
|
55
|
+
"git:github.com/someone/entwurf", "https://github.com/someone/entwurf"]
|
|
56
|
+
}
|
|
57
|
+
JSON
|
|
58
|
+
python3 "$REG" "$S" "$FAKE_REPO" >/dev/null
|
|
59
|
+
if python3 -c "
|
|
60
|
+
import json
|
|
61
|
+
d=json.load(open('$S')); p=d['packages']
|
|
62
|
+
assert '../../repos/gh/andenken' in p, 'andenken dropped'
|
|
63
|
+
assert 'git:github.com/badlogic/pi-telegram' in p, 'pi-telegram dropped'
|
|
64
|
+
assert '/home/me/entwurf-notes' in p, 'entwurf-notes wrongly filtered (substring false-positive)'
|
|
65
|
+
assert '/x/openclaw-entwurf' in p, 'openclaw-entwurf wrongly filtered (substring false-positive)'
|
|
66
|
+
assert 'git:github.com/someone/entwurf' in p, 'foreign git repo named entwurf wrongly filtered'
|
|
67
|
+
assert 'https://github.com/someone/entwurf' in p, 'foreign URL repo named entwurf wrongly filtered'
|
|
68
|
+
assert '$RESOLVED' in p, 'entwurf missing'
|
|
69
|
+
assert d['defaultProvider']=='openai-codex' and d['theme']=='glg-dark', 'unrelated key dropped'
|
|
70
|
+
" 2>/dev/null; then ok "preserves unrelated packages incl. look-alikes/foreign entwurf repos and keys"; else bad "clobbered unrelated packages/keys or a look-alike repo"; fi
|
|
71
|
+
|
|
72
|
+
# 4. stale entwurf paths (bare local 'entwurf' dir, npm install path, explicit npm
|
|
73
|
+
# package source) normalize to one canonical REPO_DIR.
|
|
74
|
+
cat > "$S" <<'JSON'
|
|
75
|
+
{"packages": ["/old/moved/entwurf", "/some/root/node_modules/@junghanacs/entwurf",
|
|
76
|
+
"npm:@junghanacs/entwurf@0.12.4", "../../repos/gh/andenken"]}
|
|
77
|
+
JSON
|
|
78
|
+
python3 "$REG" "$S" "$FAKE_REPO" >/dev/null
|
|
79
|
+
if python3 -c "
|
|
80
|
+
import json
|
|
81
|
+
p=json.load(open('$S'))['packages']
|
|
82
|
+
assert '/old/moved/entwurf' not in p, 'stale bare-entwurf path not dropped'
|
|
83
|
+
assert '/some/root/node_modules/@junghanacs/entwurf' not in p, 'stale npm path not dropped'
|
|
84
|
+
assert 'npm:@junghanacs/entwurf@0.12.4' not in p, 'stale explicit npm source not dropped'
|
|
85
|
+
assert '$RESOLVED' in p, 'entwurf not normalized to REPO_DIR'
|
|
86
|
+
assert '../../repos/gh/andenken' in p, 'andenken dropped'
|
|
87
|
+
ent=[x for x in p if isinstance((x if isinstance(x,str) else x.get('source')),str) and ((x if isinstance(x,str) else x.get('source')).rstrip('/').split('/')[-1]=='entwurf' or (x if isinstance(x,str) else x.get('source')).endswith('/node_modules/@junghanacs/entwurf') or (x if isinstance(x,str) else x.get('source'))=='$RESOLVED')]
|
|
88
|
+
assert len(ent)==1, f'expected exactly 1 entwurf entry, got {ent}'
|
|
89
|
+
" 2>/dev/null; then ok "stale entwurf paths/sources (bare + npm) normalized to a single REPO_DIR"; else bad "stale entwurf normalization failed"; fi
|
|
90
|
+
|
|
91
|
+
# 5. object-form entwurf entries collapse to one canonical string (no duplicate)
|
|
92
|
+
cat > "$S" <<JSON
|
|
93
|
+
{"packages": [{"source": "$RESOLVED"}, "../../repos/gh/andenken"]}
|
|
94
|
+
JSON
|
|
95
|
+
python3 "$REG" "$S" "$FAKE_REPO" >/dev/null
|
|
96
|
+
python3 "$REG" "$S" "$FAKE_REPO" >/dev/null # twice → still one
|
|
97
|
+
if python3 -c "
|
|
98
|
+
import json
|
|
99
|
+
p=json.load(open('$S'))['packages']
|
|
100
|
+
srcs=[(x if isinstance(x,str) else x.get('source')) for x in p]
|
|
101
|
+
assert srcs.count('$RESOLVED')==1, f'object-form produced duplicate/none: {p}'
|
|
102
|
+
assert all(not isinstance(x,dict) or x.get('source')!='$RESOLVED' for x in p), 'object-form entwurf survived (should collapse to string)'
|
|
103
|
+
assert '../../repos/gh/andenken' in p, 'andenken dropped'
|
|
104
|
+
" 2>/dev/null; then ok "object-form entwurf collapses to a single canonical string (no dup across re-runs)"; else bad "object-form entwurf caused a duplicate or survived as object"; fi
|
|
105
|
+
|
|
106
|
+
# 6. a non-object settings file fails loud (never silently wipes operator config)
|
|
107
|
+
echo '[]' > "$S"
|
|
108
|
+
if python3 "$REG" "$S" "$FAKE_REPO" >/dev/null 2>&1; then bad "non-object settings should fail, not overwrite"; else ok "non-object settings fails loud (no silent clobber)"; fi
|
|
109
|
+
|
|
110
|
+
# 7. a non-array packages value fails loud (never silently coerced to [])
|
|
111
|
+
echo '{"packages": {"broken": true}}' > "$S"
|
|
112
|
+
if python3 "$REG" "$S" "$FAKE_REPO" >/dev/null 2>&1; then bad "non-array packages should fail, not coerce to []"; else ok "non-array packages fails loud (no silent drop of operator packages)"; fi
|
|
113
|
+
|
|
114
|
+
# 8. --remove drops every entwurf entry but preserves unrelated + look-alikes
|
|
115
|
+
# (symmetric with install: same is_entwurf_source predicate, no over-delete).
|
|
116
|
+
cat > "$S" <<JSON
|
|
117
|
+
{"packages": ["$RESOLVED", "../../repos/gh/andenken", "/home/me/entwurf-notes"]}
|
|
118
|
+
JSON
|
|
119
|
+
python3 "$REG" "$S" "$FAKE_REPO" --remove >/dev/null
|
|
120
|
+
if python3 -c "
|
|
121
|
+
import json
|
|
122
|
+
p=json.load(open('$S'))['packages']
|
|
123
|
+
assert '$RESOLVED' not in p, 'entwurf not removed'
|
|
124
|
+
assert '../../repos/gh/andenken' in p, 'andenken over-removed'
|
|
125
|
+
assert '/home/me/entwurf-notes' in p, 'entwurf-notes over-removed (substring over-delete)'
|
|
126
|
+
" 2>/dev/null; then ok "--remove drops entwurf, preserves unrelated + look-alikes (symmetric with install)"; else bad "--remove over-deleted a look-alike or missed entwurf"; fi
|
|
127
|
+
|
|
128
|
+
echo
|
|
129
|
+
if [ "$fail" -eq 0 ]; then echo "smoke-user-scope-citizen: PASS"; else echo "smoke-user-scope-citizen: FAIL (see above)"; exit 1; fi
|