@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
package/run.sh
CHANGED
|
@@ -91,12 +91,13 @@ Usage:
|
|
|
91
91
|
./run.sh smoke-meta-async-drift # 1.0.0 meta-bridge step 1: drift sentinel — version pins + Claude binary undocumented-behavior markers (LIVE=1 adds plugin watch-arm probe)
|
|
92
92
|
./run.sh smoke-meta-honesty # 1.0.0 meta-bridge: honesty regression gate (#30 blockers) — doorbell counts ALL msgs honestly + hook logs failures as ERROR (best-effort, no scream). Offline/deterministic (deps: bash+node+python3)
|
|
93
93
|
./run.sh smoke-meta-install-state # 1.0.0 meta-bridge Phase 2: stateful install/uninstall + store-doctor regression gate. Offline/deterministic (deps: bash+node+python3)
|
|
94
|
+
./run.sh smoke-user-scope-citizen # 0.12.6 install-boundary: pi packages[] registration SSOT (register-pi-package.py) — idempotent + preserves unrelated + normalizes stale + remove symmetry + fails loud. Offline/hermetic (deps: bash+python3)
|
|
94
95
|
./run.sh smoke-meta-prune # 1.0.0 meta-bridge Phase 4: listing-only store janitor regression gate — classify keep/orphan/stale/ambiguous, delete nothing. Offline/deterministic (deps: bash+node)
|
|
95
96
|
./run.sh smoke-meta-keyset-guard # 0.10.0 meta-bridge: keyset-owner guard regression — check-keyset-overlap + managed-keys SSOT (disjoint passes, collisions fail). Offline/hermetic (deps: bash+python3)
|
|
96
97
|
./run.sh check-meta-manifest-schema # 0.12.2 meta-bridge: CLI-version-INDEPENDENT static guard — plugin manifests pinned to the minimal keyset that validates on the lowest supported Claude (closed-schema regression that broke 0.12.1 install on floor) + desired_mcp installed-vs-clone dual-mode. Offline (deps: python3)
|
|
97
98
|
./run.sh smoke-claude-native-resume-live # LIVE-only: Claude Code native fresh→--resume continuity + meta-record uniqueness; proves meta-bridge records identity without touching the backend resume path
|
|
98
99
|
|
|
99
|
-
./run.sh install-meta-bridge #
|
|
100
|
+
./run.sh install-meta-bridge # INTERNAL part of `setup` (native-harness plugin) + doctor recovery path — prefer `setup`; stateful GLOBAL install (plugin + USER MCP + settings keyset, honest uninstall state)
|
|
100
101
|
./run.sh uninstall-meta-bridge # 1.0.0 meta-bridge Phase 2: stateful GLOBAL uninstall (restore only keys/items captured in install-state)
|
|
101
102
|
./run.sh doctor-meta-bridge # 1.0.0 meta-bridge Phase 2: fail-loud doctor — toolchain + state + plugin/MCP + store scan + hook errors + SessionStart evidence + writer-version parity (source↔assembled↔installed: FAIL on a stale deployed meta-record writer)
|
|
102
103
|
./run.sh meta-bridge-prune # 1.0.0 meta-bridge Phase 4: LISTING-ONLY store hygiene — classify orphan/stale/ambiguous/keep, print manual rm commands, delete NOTHING ([dir] [--ttl-days N])
|
|
@@ -106,7 +107,7 @@ Usage:
|
|
|
106
107
|
./run.sh check-pack # publish gate (dry-run): npm pack --dry-run + tarball invariants (runtime-critical present, dev residue absent)
|
|
107
108
|
./run.sh check-pack-install # heavy publish gate (prepublishOnly): actual npm pack + tar -tf + fresh-temp install smoke with 0.80.x peers
|
|
108
109
|
./run.sh sync-auth # copy ~/.pi/agent/auth.json anthropic OAuth credentials to entwurf alias
|
|
109
|
-
./run.sh install [project-dir] #
|
|
110
|
+
./run.sh install [project-dir] # INTERNAL part of `setup` (project .pi/settings.json wiring) + npm-consumer entry — prefer `setup`, don't call directly for dev
|
|
110
111
|
./run.sh setup:links [--force] # repair ~/.pi/agent/entwurf-targets.json link (use --force to replace a stale operator file or wrong symlink; a .bak is taken)
|
|
111
112
|
./run.sh remove [project-dir] # remove entwurf entries from project .pi/settings.json
|
|
112
113
|
|
|
@@ -239,11 +240,67 @@ preflight_dep_integrity() {
|
|
|
239
240
|
fi
|
|
240
241
|
}
|
|
241
242
|
|
|
243
|
+
preflight_pi_settings_shapes() {
|
|
244
|
+
local project_settings="$1"
|
|
245
|
+
local user_settings="$2"
|
|
246
|
+
python3 - "$project_settings" "$user_settings" <<'PY'
|
|
247
|
+
import json, sys
|
|
248
|
+
from pathlib import Path
|
|
249
|
+
|
|
250
|
+
project = Path(sys.argv[1])
|
|
251
|
+
user = Path(sys.argv[2])
|
|
252
|
+
|
|
253
|
+
def load_object(path: Path, label: str):
|
|
254
|
+
if not path.exists():
|
|
255
|
+
return None
|
|
256
|
+
data = json.loads(path.read_text())
|
|
257
|
+
if not isinstance(data, dict):
|
|
258
|
+
raise SystemExit(f"{label} settings is not a JSON object: {path}")
|
|
259
|
+
return data
|
|
260
|
+
|
|
261
|
+
def check_packages(data, path: Path, label: str):
|
|
262
|
+
if data is None:
|
|
263
|
+
return
|
|
264
|
+
packages = data.get("packages")
|
|
265
|
+
if packages is not None and not isinstance(packages, list):
|
|
266
|
+
raise SystemExit(f"{label} settings packages is not a JSON array: {path}")
|
|
267
|
+
|
|
268
|
+
def check_project_provider(data, path: Path):
|
|
269
|
+
if data is None:
|
|
270
|
+
return
|
|
271
|
+
provider = data.get("entwurfProvider")
|
|
272
|
+
if provider is None:
|
|
273
|
+
return
|
|
274
|
+
if not isinstance(provider, dict):
|
|
275
|
+
raise SystemExit(f"project settings entwurfProvider is not an object: {path}")
|
|
276
|
+
servers = provider.get("mcpServers")
|
|
277
|
+
if servers is not None and not isinstance(servers, dict):
|
|
278
|
+
raise SystemExit(f"project settings entwurfProvider.mcpServers is not an object: {path}")
|
|
279
|
+
|
|
280
|
+
project_data = load_object(project, "project")
|
|
281
|
+
user_data = load_object(user, "user")
|
|
282
|
+
check_packages(project_data, project, "project")
|
|
283
|
+
check_packages(user_data, user, "user")
|
|
284
|
+
check_project_provider(project_data, project)
|
|
285
|
+
PY
|
|
286
|
+
}
|
|
287
|
+
|
|
242
288
|
install_local_package() {
|
|
243
|
-
local project_dir
|
|
289
|
+
local project_dir agent_dir
|
|
244
290
|
project_dir=$(normalize_project_dir "$1")
|
|
291
|
+
agent_dir="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}"
|
|
245
292
|
preflight_dep_integrity
|
|
293
|
+
# Fail BEFORE any settings write if either target config already has a corrupt
|
|
294
|
+
# shape. The packages[] SSOT and provider writer run in two separate steps, so
|
|
295
|
+
# without this preflight a bad entwurfProvider could leave a half-installed
|
|
296
|
+
# packages[] entry behind (2026-07-03 install-boundary hardening).
|
|
297
|
+
preflight_pi_settings_shapes "$project_dir/.pi/settings.json" "$agent_dir/settings.json"
|
|
246
298
|
mkdir -p "$project_dir/.pi"
|
|
299
|
+
# packages[] registration via the shared SSOT — same is_entwurf_source
|
|
300
|
+
# predicate + idempotency as user-scope and remove (not a substring match).
|
|
301
|
+
python3 "$REPO_DIR/scripts/register-pi-package.py" "$project_dir/.pi/settings.json" "$REPO_DIR"
|
|
302
|
+
# entwurfProvider.mcpServers + legacy prune (project-scope only; packages[] was
|
|
303
|
+
# already written above by register-pi-package.py).
|
|
247
304
|
python3 - "$project_dir/.pi/settings.json" "$REPO_DIR" <<'PY'
|
|
248
305
|
import json, sys
|
|
249
306
|
from pathlib import Path
|
|
@@ -258,23 +315,6 @@ if settings_path.exists():
|
|
|
258
315
|
else:
|
|
259
316
|
data = {}
|
|
260
317
|
|
|
261
|
-
# --- packages[] registration --------------------------------------------------
|
|
262
|
-
packages = data.get("packages")
|
|
263
|
-
if not isinstance(packages, list):
|
|
264
|
-
packages = []
|
|
265
|
-
|
|
266
|
-
filtered = []
|
|
267
|
-
for item in packages:
|
|
268
|
-
source = item.get("source") if isinstance(item, dict) else item
|
|
269
|
-
if isinstance(source, str) and ("entwurf" in source) and source != repo_dir:
|
|
270
|
-
continue
|
|
271
|
-
filtered.append(item)
|
|
272
|
-
|
|
273
|
-
if repo_dir not in filtered:
|
|
274
|
-
filtered.append(repo_dir)
|
|
275
|
-
|
|
276
|
-
data["packages"] = filtered
|
|
277
|
-
|
|
278
318
|
# --- entwurfProvider.mcpServers bundled entries ---------------------------
|
|
279
319
|
# Ship the two in-repo MCP adapters pre-wired so `pi install` produces a
|
|
280
320
|
# working setup without the consumer hand-editing settings.json. User-authored
|
|
@@ -334,9 +374,25 @@ for name, reason in LEGACY_BUNDLED.items():
|
|
|
334
374
|
|
|
335
375
|
settings_path.write_text(json.dumps(data, indent=2) + "\n")
|
|
336
376
|
print(f"install: updated {settings_path}")
|
|
337
|
-
print(f"install: package source -> {repo_dir}")
|
|
338
377
|
PY
|
|
339
378
|
ensure_agent_dir_symlinks
|
|
379
|
+
register_user_scope_citizen
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
# Register entwurf as a pi USER-SCOPE citizen so its extensions
|
|
383
|
+
# (entwurf-control.ts → --entwurf-control / --emacs-agent-socket) load from ANY
|
|
384
|
+
# cwd, not only inside the entwurf checkout. project-scope `.pi/settings.json`
|
|
385
|
+
# only applies when pi runs inside the repo; the `pit`/`pia`/`pihome` global
|
|
386
|
+
# launchers and the npm consumer's "installs → just works" both need the entry
|
|
387
|
+
# in ~/.pi/agent/settings.json's packages[]. This is the wiring that dropped when
|
|
388
|
+
# `pi install` was removed from setup (2026-07-03: `--entwurf-control` unknown in
|
|
389
|
+
# a foreign cwd). Idempotent: absent → append, present → no-op; a stale entwurf
|
|
390
|
+
# entry at a different path is normalized to REPO_DIR. Every other package and key
|
|
391
|
+
# in the operator's user settings is preserved untouched.
|
|
392
|
+
register_user_scope_citizen() {
|
|
393
|
+
local agent_dir="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}"
|
|
394
|
+
# Shared idempotent implementation (also driven by smoke-user-scope-citizen).
|
|
395
|
+
python3 "$REPO_DIR/scripts/register-pi-package.py" "$agent_dir/settings.json" "$REPO_DIR"
|
|
340
396
|
}
|
|
341
397
|
|
|
342
398
|
# Ensure agent-level resources that entwurf code reads from
|
|
@@ -424,6 +480,14 @@ ensure_agent_dir_symlinks() {
|
|
|
424
480
|
remove_local_package() {
|
|
425
481
|
local project_dir
|
|
426
482
|
project_dir=$(normalize_project_dir "$1")
|
|
483
|
+
# Same fail-before-write rule as install: remove has two writers too
|
|
484
|
+
# (packages[] SSOT, then entwurfProvider cleanup), so a malformed provider must
|
|
485
|
+
# not leave a half-removed packages[] entry behind.
|
|
486
|
+
preflight_pi_settings_shapes "$project_dir/.pi/settings.json" "$(mktemp -u)"
|
|
487
|
+
# packages[] cleanup via the shared SSOT — same is_entwurf_source predicate as
|
|
488
|
+
# install, so remove never over-deletes a look-alike repo (entwurf-notes, …)
|
|
489
|
+
# that install would never have registered.
|
|
490
|
+
python3 "$REPO_DIR/scripts/register-pi-package.py" "$project_dir/.pi/settings.json" "$REPO_DIR" --remove
|
|
427
491
|
python3 - "$project_dir/.pi/settings.json" "$REPO_DIR" <<'PY'
|
|
428
492
|
import json, sys
|
|
429
493
|
from pathlib import Path
|
|
@@ -438,19 +502,6 @@ data = json.loads(settings_path.read_text())
|
|
|
438
502
|
if not isinstance(data, dict):
|
|
439
503
|
raise SystemExit("settings.json is not an object")
|
|
440
504
|
|
|
441
|
-
# --- packages[] cleanup -------------------------------------------------------
|
|
442
|
-
packages = data.get("packages")
|
|
443
|
-
pkg_removed = 0
|
|
444
|
-
if isinstance(packages, list):
|
|
445
|
-
filtered = []
|
|
446
|
-
for item in packages:
|
|
447
|
-
source = item.get("source") if isinstance(item, dict) else item
|
|
448
|
-
if isinstance(source, str) and ("entwurf" in source):
|
|
449
|
-
pkg_removed += 1
|
|
450
|
-
continue
|
|
451
|
-
filtered.append(item)
|
|
452
|
-
data["packages"] = filtered
|
|
453
|
-
|
|
454
505
|
# --- entwurfProvider.mcpServers cleanup ------------------------------------
|
|
455
506
|
# Only remove entries that look like they came from ./run.sh install: either
|
|
456
507
|
# the command matches the current $REPO_DIR anchor exactly, or it ends with
|
|
@@ -484,7 +535,7 @@ if isinstance(provider, dict):
|
|
|
484
535
|
data.pop("entwurfProvider", None)
|
|
485
536
|
|
|
486
537
|
settings_path.write_text(json.dumps(data, indent=2) + "\n")
|
|
487
|
-
print(f"remove: removed {
|
|
538
|
+
print(f"remove: removed {mcp_removed} mcpServers entries from {settings_path}")
|
|
488
539
|
PY
|
|
489
540
|
}
|
|
490
541
|
|
|
@@ -1469,6 +1520,87 @@ check_install_preflight() {
|
|
|
1469
1520
|
fi
|
|
1470
1521
|
rm -rf "$fake" "$proj"
|
|
1471
1522
|
ok "[check-install-preflight] representative dangling symlink (test -d blind spot) → fails before writing settings, names the dep"
|
|
1523
|
+
|
|
1524
|
+
# negative 3 — corrupt project provider shape. install_local_package now has a
|
|
1525
|
+
# two-step writer (packages[] SSOT, then entwurfProvider.mcpServers), so a
|
|
1526
|
+
# malformed provider MUST fail before the packages[] step writes anything.
|
|
1527
|
+
fake=$(mktemp -d); proj=$(mktemp -d)
|
|
1528
|
+
mkdir -p "$proj/.pi" "$fake/home"
|
|
1529
|
+
printf '{"entwurfProvider": []}\n' > "$proj/.pi/settings.json"
|
|
1530
|
+
local before after
|
|
1531
|
+
before=$(sha256sum "$proj/.pi/settings.json" | cut -d' ' -f1)
|
|
1532
|
+
rc=0; out=$(HOME="$fake/home" PI_CODING_AGENT_DIR="$fake/home/.pi/agent" "$REPO_DIR/run.sh" install "$proj" 2>&1) || rc=$?
|
|
1533
|
+
after=$(sha256sum "$proj/.pi/settings.json" | cut -d' ' -f1)
|
|
1534
|
+
if [ "$rc" -eq 0 ]; then
|
|
1535
|
+
fail "[check-install-preflight] corrupt entwurfProvider did NOT fail install"; echo "$out"; rm -rf "$fake" "$proj"; exit 1
|
|
1536
|
+
fi
|
|
1537
|
+
if [ "$before" != "$after" ]; then
|
|
1538
|
+
fail "[check-install-preflight] corrupt entwurfProvider was partially rewritten (packages[] leak)"; echo "$out"; cat "$proj/.pi/settings.json"; rm -rf "$fake" "$proj"; exit 1
|
|
1539
|
+
fi
|
|
1540
|
+
if ! printf '%s' "$out" | grep -q "entwurfProvider is not an object"; then
|
|
1541
|
+
fail "[check-install-preflight] corrupt entwurfProvider failed for the WRONG reason:"; echo "$out"; rm -rf "$fake" "$proj"; exit 1
|
|
1542
|
+
fi
|
|
1543
|
+
if [ -f "$fake/home/.pi/agent/settings.json" ]; then
|
|
1544
|
+
fail "[check-install-preflight] corrupt project provider still wrote user-scope settings"; cat "$fake/home/.pi/agent/settings.json"; rm -rf "$fake" "$proj"; exit 1
|
|
1545
|
+
fi
|
|
1546
|
+
rm -rf "$fake" "$proj"
|
|
1547
|
+
ok "[check-install-preflight] corrupt project entwurfProvider → fails before any project/user settings write"
|
|
1548
|
+
|
|
1549
|
+
# negative 4 — corrupt project mcpServers shape, same no-partial-write contract.
|
|
1550
|
+
fake=$(mktemp -d); proj=$(mktemp -d)
|
|
1551
|
+
mkdir -p "$proj/.pi" "$fake/home"
|
|
1552
|
+
printf '{"entwurfProvider": {"mcpServers": []}}\n' > "$proj/.pi/settings.json"
|
|
1553
|
+
before=$(sha256sum "$proj/.pi/settings.json" | cut -d' ' -f1)
|
|
1554
|
+
rc=0; out=$(HOME="$fake/home" PI_CODING_AGENT_DIR="$fake/home/.pi/agent" "$REPO_DIR/run.sh" install "$proj" 2>&1) || rc=$?
|
|
1555
|
+
after=$(sha256sum "$proj/.pi/settings.json" | cut -d' ' -f1)
|
|
1556
|
+
if [ "$rc" -eq 0 ]; then
|
|
1557
|
+
fail "[check-install-preflight] corrupt entwurfProvider.mcpServers did NOT fail install"; echo "$out"; rm -rf "$fake" "$proj"; exit 1
|
|
1558
|
+
fi
|
|
1559
|
+
if [ "$before" != "$after" ]; then
|
|
1560
|
+
fail "[check-install-preflight] corrupt mcpServers was partially rewritten (packages[] leak)"; echo "$out"; cat "$proj/.pi/settings.json"; rm -rf "$fake" "$proj"; exit 1
|
|
1561
|
+
fi
|
|
1562
|
+
if ! printf '%s' "$out" | grep -q "entwurfProvider.mcpServers is not an object"; then
|
|
1563
|
+
fail "[check-install-preflight] corrupt mcpServers failed for the WRONG reason:"; echo "$out"; rm -rf "$fake" "$proj"; exit 1
|
|
1564
|
+
fi
|
|
1565
|
+
rm -rf "$fake" "$proj"
|
|
1566
|
+
ok "[check-install-preflight] corrupt project entwurfProvider.mcpServers → fails before any project settings write"
|
|
1567
|
+
|
|
1568
|
+
# negative 5 — corrupt user-scope packages shape. Since install writes project
|
|
1569
|
+
# settings before user registration, this preflight must catch the user file up
|
|
1570
|
+
# front so a bad ~/.pi/agent/settings.json cannot leave the project half-wired.
|
|
1571
|
+
fake=$(mktemp -d); proj=$(mktemp -d)
|
|
1572
|
+
mkdir -p "$fake/home/.pi/agent"
|
|
1573
|
+
printf '{"packages": {"broken": true}}\n' > "$fake/home/.pi/agent/settings.json"
|
|
1574
|
+
rc=0; out=$(HOME="$fake/home" PI_CODING_AGENT_DIR="$fake/home/.pi/agent" "$REPO_DIR/run.sh" install "$proj" 2>&1) || rc=$?
|
|
1575
|
+
if [ "$rc" -eq 0 ]; then
|
|
1576
|
+
fail "[check-install-preflight] corrupt user packages did NOT fail install"; echo "$out"; rm -rf "$fake" "$proj"; exit 1
|
|
1577
|
+
fi
|
|
1578
|
+
if [ -f "$proj/.pi/settings.json" ]; then
|
|
1579
|
+
fail "[check-install-preflight] corrupt user packages still wrote project settings (partial install)"; echo "$out"; cat "$proj/.pi/settings.json"; rm -rf "$fake" "$proj"; exit 1
|
|
1580
|
+
fi
|
|
1581
|
+
if ! printf '%s' "$out" | grep -q "user settings packages is not a JSON array"; then
|
|
1582
|
+
fail "[check-install-preflight] corrupt user packages failed for the WRONG reason:"; echo "$out"; rm -rf "$fake" "$proj"; exit 1
|
|
1583
|
+
fi
|
|
1584
|
+
rm -rf "$fake" "$proj"
|
|
1585
|
+
ok "[check-install-preflight] corrupt user-scope packages → fails before project settings write"
|
|
1586
|
+
|
|
1587
|
+
# negative 6 — remove has the same two-step write risk (packages[] remove,
|
|
1588
|
+
# then provider cleanup). A corrupt provider must fail before packages[] is
|
|
1589
|
+
# altered, otherwise uninstall becomes a partial destructive write.
|
|
1590
|
+
proj=$(mktemp -d)
|
|
1591
|
+
mkdir -p "$proj/.pi"
|
|
1592
|
+
printf '{"entwurfProvider": [], "packages": ["%s"]}\n' "$REPO_DIR" > "$proj/.pi/settings.json"
|
|
1593
|
+
before=$(sha256sum "$proj/.pi/settings.json" | cut -d' ' -f1)
|
|
1594
|
+
rc=0; out=$("$REPO_DIR/run.sh" remove "$proj" 2>&1) || rc=$?
|
|
1595
|
+
after=$(sha256sum "$proj/.pi/settings.json" | cut -d' ' -f1)
|
|
1596
|
+
if [ "$rc" -eq 0 ]; then
|
|
1597
|
+
fail "[check-install-preflight] corrupt provider did NOT fail remove"; echo "$out"; rm -rf "$proj"; exit 1
|
|
1598
|
+
fi
|
|
1599
|
+
if [ "$before" != "$after" ]; then
|
|
1600
|
+
fail "[check-install-preflight] corrupt provider remove partially rewrote packages[]"; echo "$out"; cat "$proj/.pi/settings.json"; rm -rf "$proj"; exit 1
|
|
1601
|
+
fi
|
|
1602
|
+
rm -rf "$proj"
|
|
1603
|
+
ok "[check-install-preflight] remove with corrupt provider → fails before packages[] removal"
|
|
1472
1604
|
}
|
|
1473
1605
|
|
|
1474
1606
|
check_pi_preflight() {
|
|
@@ -1735,6 +1867,12 @@ check_pack() {
|
|
|
1735
1867
|
# doctor.sh runs this prebuilt JS when installed under node_modules (strip-types
|
|
1736
1868
|
# refuses the .ts there), same boundary start.sh crosses. build-bridge emits it.
|
|
1737
1869
|
"mcp/entwurf-bridge/dist/scripts/meta-bridge-store-doctor.js"
|
|
1870
|
+
# 0.12.5 — the node_modules-safe plugin hook + its lib. install-meta-bridge copies
|
|
1871
|
+
# these compiled JS into the assembled plugin when installed (raw .ts can't
|
|
1872
|
+
# strip-types under node_modules). meta-session.js is shared with the store-doctor
|
|
1873
|
+
# above; listed here too so the hook axis fails loud if the emit graph drops it.
|
|
1874
|
+
"mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js"
|
|
1875
|
+
"mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js"
|
|
1738
1876
|
"scripts/postinstall-chmod.cjs"
|
|
1739
1877
|
"pi/entwurf-capabilities.json"
|
|
1740
1878
|
"pi/entwurf-targets.json"
|
|
@@ -1887,6 +2025,10 @@ check_pack_install() {
|
|
|
1887
2025
|
# 0.12.4 — prebuilt node_modules-safe store-scan artifact for the doctor
|
|
1888
2026
|
# (see check-pack). The installed-scan smoke below runs exactly this file.
|
|
1889
2027
|
"mcp/entwurf-bridge/dist/scripts/meta-bridge-store-doctor.js"
|
|
2028
|
+
# 0.12.5 — node_modules-safe plugin hook + lib (see check-pack). The installed
|
|
2029
|
+
# hook regression below runs exactly this compiled JS from under node_modules.
|
|
2030
|
+
"mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js"
|
|
2031
|
+
"mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js"
|
|
1890
2032
|
"scripts/postinstall-chmod.cjs"
|
|
1891
2033
|
"pi/entwurf-capabilities.json"
|
|
1892
2034
|
"pi/entwurf-targets.json"
|
|
@@ -1992,8 +2134,14 @@ check_pack_install() {
|
|
|
1992
2134
|
return 1
|
|
1993
2135
|
fi
|
|
1994
2136
|
|
|
1995
|
-
|
|
1996
|
-
|
|
2137
|
+
# HOME/PI_CODING_AGENT_DIR redirected to a throwaway dir: this loader smoke must
|
|
2138
|
+
# depend ONLY on the -e package path, never on the operator's real
|
|
2139
|
+
# ~/.pi/agent/settings.json — otherwise a live-config change could silently
|
|
2140
|
+
# pass/fail the gate (the exact "check must not depend on live wiring" impurity
|
|
2141
|
+
# this whole lane is about).
|
|
2142
|
+
local loader_out loader_home="$tmp/loader-home"
|
|
2143
|
+
mkdir -p "$loader_home/.pi/agent"
|
|
2144
|
+
loader_out=$(cd "$tmp" && HOME="$loader_home" PI_CODING_AGENT_DIR="$loader_home/.pi/agent" pi -e "$tmp/node_modules/@junghanacs/entwurf" --list-models entwurf 2>&1) || {
|
|
1997
2145
|
fail "[check-pack-install] pi loader smoke failed (exit non-zero):"
|
|
1998
2146
|
echo "$loader_out" | tail -10 | sed 's/^/ /' >&2
|
|
1999
2147
|
return 1
|
|
@@ -2043,8 +2191,8 @@ check_pack_install() {
|
|
|
2043
2191
|
fail "[check-pack-install] npm-managed layout missing executable run.sh (postinstall-chmod did not run?) at $npm_pkg"
|
|
2044
2192
|
return 1
|
|
2045
2193
|
fi
|
|
2046
|
-
if [ ! -x "$npmroot/node_modules/.bin/entwurf" ] || [ ! -x "$npmroot/node_modules/.bin/entwurf-bridge" ]; then
|
|
2047
|
-
fail "[check-pack-install] npm-managed neutral install missing package bins (entwurf / entwurf-bridge)"
|
|
2194
|
+
if [ ! -x "$npmroot/node_modules/.bin/entwurf" ] || [ ! -x "$npmroot/node_modules/.bin/entwurf-bridge" ] || [ ! -x "$npmroot/node_modules/.bin/entwurf-statusline" ]; then
|
|
2195
|
+
fail "[check-pack-install] npm-managed neutral install missing package bins (entwurf / entwurf-bridge / entwurf-statusline)"
|
|
2048
2196
|
ls -l "$npmroot/node_modules/.bin" 2>/dev/null | sed 's/^/ /' >&2 || true
|
|
2049
2197
|
return 1
|
|
2050
2198
|
fi
|
|
@@ -2065,6 +2213,158 @@ check_pack_install() {
|
|
|
2065
2213
|
fi
|
|
2066
2214
|
echo "[check-pack-install] npm-managed install regression pass (hoisted-dep run.sh install wrote settings)"
|
|
2067
2215
|
|
|
2216
|
+
# THE 2026-07-03 regression gate: removing `pi install` from setup dropped
|
|
2217
|
+
# user-scope citizen registration, so `--entwurf-control` was Unknown in a
|
|
2218
|
+
# foreign cwd. Prove the npm consumer path closes it end-to-end: run.sh install
|
|
2219
|
+
# (run above under HOME="$npmhome") must have registered the package in the USER
|
|
2220
|
+
# settings, and pi must then load the entwurf extension + its flags from a cwd
|
|
2221
|
+
# OUTSIDE the project. Fully isolated in the temp HOME — never touches ~/.pi.
|
|
2222
|
+
local npm_user_settings="$npmhome/.pi/agent/settings.json"
|
|
2223
|
+
if [ ! -f "$npm_user_settings" ]; then
|
|
2224
|
+
fail "[check-pack-install] npm-managed install did not register a user-scope citizen at $npm_user_settings"
|
|
2225
|
+
return 1
|
|
2226
|
+
fi
|
|
2227
|
+
if ! python3 -c "
|
|
2228
|
+
import json,sys
|
|
2229
|
+
p=json.load(open('$npm_user_settings')).get('packages',[])
|
|
2230
|
+
srcs=[(x if isinstance(x,str) else x.get('source')) for x in p]
|
|
2231
|
+
sys.exit(0 if any(isinstance(s,str) and s.endswith('/node_modules/@junghanacs/entwurf') for s in srcs) else 1)
|
|
2232
|
+
"; then
|
|
2233
|
+
fail "[check-pack-install] user-scope settings.json lacks the npm entwurf package in packages[]:"
|
|
2234
|
+
sed 's/^/ /' "$npm_user_settings" >&2
|
|
2235
|
+
return 1
|
|
2236
|
+
fi
|
|
2237
|
+
# Foreign cwd ($tmp — NOT the project, no project .pi): --entwurf-control must be
|
|
2238
|
+
# a KNOWN flag and the entwurf provider must load, sourced only from user scope.
|
|
2239
|
+
# Before the fix this printed "Unknown options: --entwurf-control".
|
|
2240
|
+
local foreign_out
|
|
2241
|
+
foreign_out=$(cd "$tmp" && HOME="$npmhome" PI_CODING_AGENT_DIR="$npmhome/.pi/agent" pi --entwurf-control --list-models entwurf 2>&1) || {
|
|
2242
|
+
fail "[check-pack-install] foreign-cwd --entwurf-control smoke failed (user-scope citizen not loading?):"
|
|
2243
|
+
echo "$foreign_out" | tail -10 | sed 's/^/ /' >&2
|
|
2244
|
+
return 1
|
|
2245
|
+
}
|
|
2246
|
+
if grep -qi "Unknown option" <<<"$foreign_out" || ! grep -q "claude-opus-4-8" <<<"$foreign_out"; then
|
|
2247
|
+
fail "[check-pack-install] foreign-cwd --entwurf-control did not load the entwurf extension from user scope:"
|
|
2248
|
+
echo "$foreign_out" | tail -10 | sed 's/^/ /' >&2
|
|
2249
|
+
return 1
|
|
2250
|
+
fi
|
|
2251
|
+
echo "[check-pack-install] user-scope citizen regression pass (npm consumer: --entwurf-control loads from a foreign cwd)"
|
|
2252
|
+
|
|
2253
|
+
# Installed meta-bridge ownership regression (0.12.5): package upgrades must not
|
|
2254
|
+
# bake versioned pnpm-store paths into Claude settings. MCP already uses the
|
|
2255
|
+
# stable `entwurf-bridge` bin; statusLine must now use `entwurf-statusline`, and
|
|
2256
|
+
# the plugin marketplace source must be the version-stable operator data dir
|
|
2257
|
+
# rather than <node_modules>/pi/meta-bridge/.assembled. Use a fake claude CLI so
|
|
2258
|
+
# this stays deterministic/offline while running the REAL installed
|
|
2259
|
+
# install-meta-bridge path and meta-bridge-state apply.
|
|
2260
|
+
local fake_claude_dir="$npm_tmp/fake-claude-bin" fake_claude_log="$npm_tmp/fake-claude.log"
|
|
2261
|
+
mkdir -p "$fake_claude_dir"
|
|
2262
|
+
cat > "$fake_claude_dir/claude" <<'SH'
|
|
2263
|
+
#!/usr/bin/env bash
|
|
2264
|
+
printf '%s\n' "$*" >> "${FAKE_CLAUDE_LOG:?}"
|
|
2265
|
+
case "$1${2:+ $2}" in
|
|
2266
|
+
"--version") echo "2.1.197 (Claude Code)" ;;
|
|
2267
|
+
"plugin validate") : ;;
|
|
2268
|
+
"plugin uninstall") : ;;
|
|
2269
|
+
"plugin marketplace") : ;;
|
|
2270
|
+
"plugin install") : ;;
|
|
2271
|
+
"plugin list") printf '%s\n' "entwurf-meta-receive@meta-bridge-local" " Status: enabled" ;;
|
|
2272
|
+
"mcp remove") : ;;
|
|
2273
|
+
"mcp add") : ;;
|
|
2274
|
+
"mcp get") printf '%s\n' "Scope: User config" "Status: ✔ Connected" ;;
|
|
2275
|
+
*) : ;;
|
|
2276
|
+
esac
|
|
2277
|
+
exit 0
|
|
2278
|
+
SH
|
|
2279
|
+
chmod +x "$fake_claude_dir/claude"
|
|
2280
|
+
local mb_home="$npm_tmp/meta-home" mb_cfg="$npm_tmp/meta-claude" mb_log
|
|
2281
|
+
mkdir -p "$mb_home" "$mb_cfg"
|
|
2282
|
+
mb_log=$(HOME="$mb_home" XDG_DATA_HOME="$mb_home/.local/share" CLAUDE_CONFIG_DIR="$mb_cfg" FAKE_CLAUDE_LOG="$fake_claude_log" PATH="$fake_claude_dir:$npmroot/node_modules/.bin:$PATH" "$npm_pkg/run.sh" install-meta-bridge 2>&1) || {
|
|
2283
|
+
fail "[check-pack-install] installed install-meta-bridge failed under fake claude:"
|
|
2284
|
+
echo "$mb_log" | tail -20 | sed 's/^/ /' >&2
|
|
2285
|
+
return 1
|
|
2286
|
+
}
|
|
2287
|
+
local stable_asm="$mb_home/.local/share/entwurf/meta-bridge/.assembled"
|
|
2288
|
+
local installed_hook="$stable_asm/entwurf-meta-receive/meta-bridge-hook.js"
|
|
2289
|
+
if [ ! -f "$installed_hook" ]; then
|
|
2290
|
+
fail "[check-pack-install] installed meta-bridge did not assemble the compiled hook JS into the stable operator data dir: $installed_hook"
|
|
2291
|
+
return 1
|
|
2292
|
+
fi
|
|
2293
|
+
if [ -f "$stable_asm/entwurf-meta-receive/meta-bridge-hook.ts" ]; then
|
|
2294
|
+
fail "[check-pack-install] installed meta-bridge shipped a raw .ts hook — installed packages must run compiled JS (strip-types is refused under node_modules)"
|
|
2295
|
+
return 1
|
|
2296
|
+
fi
|
|
2297
|
+
# The artifact existing is not enough (review BLOCKER 1): Claude runs the hooks.json
|
|
2298
|
+
# COMMAND, so assert the baked command actually targets the compiled .js with both
|
|
2299
|
+
# placeholders resolved. A bake that mis-targeted .ts (or left a placeholder) would
|
|
2300
|
+
# still pass the direct-JS smoke below, then fail live.
|
|
2301
|
+
local installed_hooks_json="$stable_asm/entwurf-meta-receive/hooks/hooks.json"
|
|
2302
|
+
if ! grep -q 'meta-bridge-hook\.js' "$installed_hooks_json"; then
|
|
2303
|
+
fail "[check-pack-install] installed hooks.json does not point at the compiled meta-bridge-hook.js: $installed_hooks_json"
|
|
2304
|
+
return 1
|
|
2305
|
+
fi
|
|
2306
|
+
if grep -qE 'meta-bridge-hook\.ts|__HOOK_ENTRY__|__NODE_BIN__' "$installed_hooks_json"; then
|
|
2307
|
+
fail "[check-pack-install] installed hooks.json references a raw .ts entry or an unbaked placeholder (__HOOK_ENTRY__/__NODE_BIN__): $installed_hooks_json"
|
|
2308
|
+
return 1
|
|
2309
|
+
fi
|
|
2310
|
+
if [ -e "$npm_pkg/pi/meta-bridge/.assembled/entwurf-meta-receive" ]; then
|
|
2311
|
+
fail "[check-pack-install] installed meta-bridge still assembled inside the versioned package store: $npm_pkg/pi/meta-bridge/.assembled"
|
|
2312
|
+
return 1
|
|
2313
|
+
fi
|
|
2314
|
+
# 0.12.5 strip-types-fence regression (GPT safety pin). The compiled hook must run
|
|
2315
|
+
# FROM UNDER node_modules with plain node — the exact fence that broke oracle's
|
|
2316
|
+
# 0.12.4 raw-.ts hook (ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING). L2 relocates
|
|
2317
|
+
# the runtime marketplace source to XDG, but this hardens the ARTIFACT itself so
|
|
2318
|
+
# it is safe even if a future cache/marketplace path lands under node_modules.
|
|
2319
|
+
local nm_probe="$npmroot/node_modules/@junghanacs/entwurf/pi/meta-bridge/.assembled-packprobe/entwurf-meta-receive"
|
|
2320
|
+
mkdir -p "$nm_probe/lib"
|
|
2321
|
+
cp "$installed_hook" "$nm_probe/meta-bridge-hook.js"
|
|
2322
|
+
cp "$stable_asm/entwurf-meta-receive/lib/meta-session.js" "$nm_probe/lib/meta-session.js"
|
|
2323
|
+
cp "$stable_asm/entwurf-meta-receive/lib/session-id.js" "$nm_probe/lib/session-id.js"
|
|
2324
|
+
cp "$stable_asm/entwurf-meta-receive/entwurf-capabilities.json" "$nm_probe/entwurf-capabilities.json"
|
|
2325
|
+
local probe_env='{"session_id":"pack-probe","transcript_path":"/tmp/x.jsonl","cwd":"/tmp","hook_event_name":"SessionStart","model":{"id":"probe"}}'
|
|
2326
|
+
local probe_out
|
|
2327
|
+
if probe_out="$(printf '%s' "$probe_env" | env PI_CODING_AGENT_DIR="$(mktemp -d)" CLAUDE_PLUGIN_ROOT="$nm_probe" node "$nm_probe/meta-bridge-hook.js" 2>&1)" && printf '%s' "$probe_out" | grep -q hookSpecificOutput; then
|
|
2328
|
+
: # compiled hook crosses the node_modules strip-types fence safely
|
|
2329
|
+
else
|
|
2330
|
+
fail "[check-pack-install] compiled hook failed to run under node_modules with plain node: $(printf '%s' "$probe_out" | tr '\n' ' ' | cut -c1-200)"
|
|
2331
|
+
rm -rf "$nm_probe"; return 1
|
|
2332
|
+
fi
|
|
2333
|
+
# FAIL-reproduction (review BLOCKER 2): a raw .ts at the SAME node_modules location
|
|
2334
|
+
# must be refused SPECIFICALLY by the strip-types fence — not by some unrelated
|
|
2335
|
+
# failure. A missing lib would also exit nonzero and hollow out the proof, so
|
|
2336
|
+
# capture stderr and require the exact ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING.
|
|
2337
|
+
cp "$npm_pkg/pi-extensions/meta-bridge-hook.ts" "$nm_probe/meta-bridge-hook.ts"
|
|
2338
|
+
local ts_out ts_rc
|
|
2339
|
+
ts_out="$(printf '%s' "$probe_env" | env PI_CODING_AGENT_DIR="$(mktemp -d)" CLAUDE_PLUGIN_ROOT="$nm_probe" node "$nm_probe/meta-bridge-hook.ts" 2>&1)" && ts_rc=0 || ts_rc=$?
|
|
2340
|
+
if [ "${ts_rc:-0}" -eq 0 ]; then
|
|
2341
|
+
fail "[check-pack-install] raw .ts hook UNEXPECTEDLY ran under node_modules — strip-types fence moved; the compiled-hook rationale must be revisited"
|
|
2342
|
+
rm -rf "$nm_probe"; return 1
|
|
2343
|
+
fi
|
|
2344
|
+
if ! printf '%s' "$ts_out" | grep -q 'ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING'; then
|
|
2345
|
+
fail "[check-pack-install] raw .ts hook failed under node_modules but NOT via the strip-types fence (expected ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING); got: $(printf '%s' "$ts_out" | tr '\n' ' ' | cut -c1-200) — the regression no longer proves the compiled-hook rationale"
|
|
2346
|
+
rm -rf "$nm_probe"; return 1
|
|
2347
|
+
fi
|
|
2348
|
+
rm -rf "$nm_probe"
|
|
2349
|
+
echo "[check-pack-install] installed hook is node_modules-safe compiled JS (hooks.json points at .js; runs under node_modules with plain node; raw .ts refused there by the strip-types fence)"
|
|
2350
|
+
python3 - "$mb_cfg/settings.json" "$mb_home/.claude.json" "$stable_asm" <<'PY'
|
|
2351
|
+
import json, sys
|
|
2352
|
+
settings = json.load(open(sys.argv[1]))
|
|
2353
|
+
root = json.load(open(sys.argv[2]))
|
|
2354
|
+
stable_asm = sys.argv[3]
|
|
2355
|
+
market = settings.get("extraKnownMarketplaces", {}).get("meta-bridge-local", {})
|
|
2356
|
+
assert market == {"source": {"source": "directory", "path": stable_asm}}, market
|
|
2357
|
+
assert settings.get("statusLine") == {"type": "command", "command": "entwurf-statusline"}, settings.get("statusLine")
|
|
2358
|
+
mcp = root.get("mcpServers", {}).get("entwurf-bridge", {})
|
|
2359
|
+
assert mcp.get("command") == "entwurf-bridge" and mcp.get("args") == [], mcp
|
|
2360
|
+
PY
|
|
2361
|
+
if ! grep -q "$stable_asm" "$fake_claude_log"; then
|
|
2362
|
+
fail "[check-pack-install] fake claude did not receive the stable marketplace path during install-meta-bridge"
|
|
2363
|
+
sed 's/^/ /' "$fake_claude_log" >&2 || true
|
|
2364
|
+
return 1
|
|
2365
|
+
fi
|
|
2366
|
+
echo "[check-pack-install] installed meta-bridge ownership pass (stable statusline bin + stable marketplace dir + stable MCP bin)"
|
|
2367
|
+
|
|
2068
2368
|
# 0.12.1 C — installed bridge BOOT regression. This is the test whose absence
|
|
2069
2369
|
# let the 0.12.0 install bug ship: the README's bridge launcher was
|
|
2070
2370
|
# `node --experimental-strip-types src/index.ts`, which Node REFUSES under
|
|
@@ -2873,6 +3173,14 @@ case "$cmd" in
|
|
|
2873
3173
|
# duplicate/drift records. Offline + deterministic (deps bash+node+python3).
|
|
2874
3174
|
(cd "$REPO_DIR" && bash scripts/smoke-meta-install-state.sh)
|
|
2875
3175
|
;;
|
|
3176
|
+
smoke-user-scope-citizen)
|
|
3177
|
+
# 0.12.6 install-boundary gate: register-pi-package.py is the shared
|
|
3178
|
+
# packages[] SSOT for project/user install and remove; user scope makes
|
|
3179
|
+
# --entwurf-control load from any cwd. Idempotent, preserves unrelated
|
|
3180
|
+
# packages/keys, normalizes stale entries, remove is symmetric, and corrupt
|
|
3181
|
+
# settings fail loud. The tripwire the 2026-07-03 `pi install` removal lacked.
|
|
3182
|
+
(cd "$REPO_DIR" && bash scripts/smoke-user-scope-citizen.sh)
|
|
3183
|
+
;;
|
|
2876
3184
|
smoke-claude-native-resume-live)
|
|
2877
3185
|
# LIVE-only Detour A probe: two real Claude Code native turns (fresh +
|
|
2878
3186
|
# --resume) in a scratch cwd. Verifies native resume works while the
|
|
@@ -2882,9 +3190,10 @@ case "$cmd" in
|
|
|
2882
3190
|
;;
|
|
2883
3191
|
install-meta-bridge)
|
|
2884
3192
|
# 1.0.0 meta-bridge step 5: operator-grade GLOBAL install of the garden-native
|
|
2885
|
-
# receive plugin. Assembles a self-contained, node-path-baked copy under
|
|
2886
|
-
#
|
|
2887
|
-
#
|
|
3193
|
+
# receive plugin. Assembles a self-contained, node-path-baked copy under the
|
|
3194
|
+
# XDG data dir ($XDG_DATA_HOME/entwurf/meta-bridge/.assembled — dev clone and
|
|
3195
|
+
# installed package alike, never the checkout) and runs marketplace add +
|
|
3196
|
+
# install --scope user, so every native Claude Code session auto-loads it.
|
|
2888
3197
|
# Idempotent; Linux/macOS only (Windows fail-fast).
|
|
2889
3198
|
(cd "$REPO_DIR" && bash scripts/meta-bridge-install.sh "$@")
|
|
2890
3199
|
;;
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -17,6 +17,23 @@ MKT_NAME="meta-bridge-local"
|
|
|
17
17
|
PLUGIN="entwurf-meta-receive"
|
|
18
18
|
CLAUDE_CFG="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
|
|
19
19
|
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
20
|
+
# The live artifact lives under the XDG data dir (dev clone and installed package
|
|
21
|
+
# alike — never the checkout). Prefer the path RECORDED in install-state so a doctor
|
|
22
|
+
# run under a different XDG_DATA_HOME than install still hashes the REAL assembled
|
|
23
|
+
# bundle (and state.py check compares the same recorded marketplace path); fall back
|
|
24
|
+
# to the current-XDG computation only when no state exists yet (fresh host).
|
|
25
|
+
# LIB_EXT still tracks the hook artifact mode (0.12.5): installed packages ship the
|
|
26
|
+
# tsc-emitted `.js` closure (node_modules-safe), dev clones run the `.ts` source.
|
|
27
|
+
# The writer-version parity below hashes meta-session.<LIB_EXT> so an installed
|
|
28
|
+
# `.js` bundle is compared against the SAME-pipeline dist `.js`, never against the
|
|
29
|
+
# `.ts` source (which would hash-mismatch and false-STALE).
|
|
30
|
+
ASM="${XDG_DATA_HOME:-$HOME/.local/share}/entwurf/meta-bridge/.assembled"
|
|
31
|
+
RECORDED_ASM="$(python3 "$REPO/scripts/meta-bridge-state.py" assembled-path --repo "$REPO" 2>/dev/null || true)"
|
|
32
|
+
[ -n "$RECORDED_ASM" ] && ASM="$RECORDED_ASM"
|
|
33
|
+
case "$REPO" in
|
|
34
|
+
*/node_modules/@junghanacs/entwurf) LIB_EXT="js" ;;
|
|
35
|
+
*) LIB_EXT="ts" ;;
|
|
36
|
+
esac
|
|
20
37
|
# shellcheck source=scripts/meta-bridge-hook-log.sh
|
|
21
38
|
source "$REPO/scripts/meta-bridge-hook-log.sh"
|
|
22
39
|
|
|
@@ -65,7 +82,7 @@ if command -v python3 >/dev/null; then
|
|
|
65
82
|
# which key drifted — silent instead of fail-loud. As an `if` condition the
|
|
66
83
|
# substitution's nonzero status is consumed (set -e suspended), so we always
|
|
67
84
|
# reach the detail. stderr (drift detail) is captured via `2>&1 >/dev/null`.
|
|
68
|
-
if CHECK_ERR="$(python3 "$REPO/scripts/meta-bridge-state.py" check --repo "$REPO" --asm "$
|
|
85
|
+
if CHECK_ERR="$(python3 "$REPO/scripts/meta-bridge-state.py" check --repo "$REPO" --asm "$ASM" 2>&1 >/dev/null)"; then
|
|
69
86
|
ok "state file present and managed settings/MCP keyset survives intact"
|
|
70
87
|
else
|
|
71
88
|
bad "managed settings/MCP keyset missing or overwritten — another consumer may have clobbered a pi-owned key. Re-run ./run.sh install-meta-bridge. Drift detail:"
|
|
@@ -93,8 +110,23 @@ if [ -n "$CACHE_HOOKS" ]; then
|
|
|
93
110
|
# line 158) so BAKED="" routes to that bad(...): an installed hooks.json exists but
|
|
94
111
|
# the doctor cannot read its load-bearing hook command, so the store-churn guard is
|
|
95
112
|
# blind — a verify-impossible state must fail loud, not pass as a soft warn (A3b).
|
|
96
|
-
|
|
97
|
-
|
|
113
|
+
# Match either hook artifact (0.12.5): installed ships meta-bridge-hook.js, dev
|
|
114
|
+
# clones meta-bridge-hook.ts. Capture the whole command so we run the SAME entry
|
|
115
|
+
# file the live hook runs (never a hardcoded extension that drifts from install).
|
|
116
|
+
HOOK_CMD="$({ grep -oE '"command": "[^ ]+ \$\{CLAUDE_PLUGIN_ROOT\}/meta-bridge-hook\.(ts|js)"' "$CACHE_HOOKS" || true; } | head -1)"
|
|
117
|
+
BAKED="$(printf '%s' "$HOOK_CMD" | sed -E 's/.*"command": "([^ ]+) .*/\1/')"
|
|
118
|
+
HOOK_FILE="$(printf '%s' "$HOOK_CMD" | sed -E 's#.*/([^/"]+)"$#\1#')"
|
|
119
|
+
if [ -n "$BAKED" ] && [ -x "$BAKED" ]; then
|
|
120
|
+
ok "baked node exists + executable: $BAKED"
|
|
121
|
+
CACHE_ROOT="$(cd "$(dirname "$CACHE_HOOKS")/.." && pwd)"
|
|
122
|
+
TMP_AGENT="$(mktemp -d 2>/dev/null || mktemp -d -t entwurf-doctor-hook)"
|
|
123
|
+
HOOK_ENV='{"session_id":"doctor-synthetic-native","transcript_path":"/tmp/entwurf-doctor-synthetic-transcript.jsonl","cwd":"/tmp","hook_event_name":"SessionStart","model":{"id":"doctor-model"}}'
|
|
124
|
+
if HOOK_OUT="$(printf '%s' "$HOOK_ENV" | env PI_CODING_AGENT_DIR="$TMP_AGENT" CLAUDE_PLUGIN_ROOT="$CACHE_ROOT" "$BAKED" "$CACHE_ROOT/$HOOK_FILE" 2>&1)" && printf '%s' "$HOOK_OUT" | grep -q 'hookSpecificOutput'; then
|
|
125
|
+
ok "cached SessionStart hook executes cleanly in an isolated temp agent dir"
|
|
126
|
+
else
|
|
127
|
+
bad "cached SessionStart hook failed to execute — stale plugin cache / unsupported TS syntax / broken bundle. Re-run install-meta-bridge from the intended surface. Detail: $(printf '%s' "$HOOK_OUT" | tr '\n' ' ' | cut -c1-300)"
|
|
128
|
+
fi
|
|
129
|
+
rm -rf "$TMP_AGENT" 2>/dev/null || true
|
|
98
130
|
elif [ -n "$BAKED" ]; then bad "baked node path is DEAD (nix GC / version bump?): $BAKED — re-run ./run.sh install-meta-bridge"
|
|
99
131
|
else bad "could not parse baked node path from $CACHE_HOOKS — hook command format drift; doctor cannot verify the NixOS store-churn guard. Re-run ./run.sh install-meta-bridge or update the doctor parser."; fi
|
|
100
132
|
else
|
|
@@ -114,9 +146,15 @@ except Exception:
|
|
|
114
146
|
print('')
|
|
115
147
|
PY
|
|
116
148
|
)"
|
|
117
|
-
EXPECTED_STATUSLINE="$REPO/scripts/meta-bridge-statusline.
|
|
118
|
-
if [ "$STATUSLINE_CMD" = "$EXPECTED_STATUSLINE" ]; then ok "statusLine.command
|
|
119
|
-
if [
|
|
149
|
+
EXPECTED_STATUSLINE="$(python3 "$REPO/scripts/meta-bridge-state.py" desired-statusline --repo "$REPO" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("command", ""))')"
|
|
150
|
+
if [ "$STATUSLINE_CMD" = "$EXPECTED_STATUSLINE" ]; then ok "statusLine.command matches expected (dev: checkout path; installed: entwurf-statusline bin shim): $STATUSLINE_CMD"; else bad "statusLine.command drifted (got '$STATUSLINE_CMD', expected '$EXPECTED_STATUSLINE')"; fi
|
|
151
|
+
if [[ "$EXPECTED_STATUSLINE" == */* ]]; then
|
|
152
|
+
if [ -x "$EXPECTED_STATUSLINE" ]; then ok "statusline executable"; else bad "statusline script not executable: $EXPECTED_STATUSLINE"; fi
|
|
153
|
+
elif command -v "$EXPECTED_STATUSLINE" >/dev/null 2>&1; then
|
|
154
|
+
ok "statusline bin resolves on PATH: $(command -v "$EXPECTED_STATUSLINE")"
|
|
155
|
+
else
|
|
156
|
+
bad "statusline bin not on PATH: $EXPECTED_STATUSLINE"
|
|
157
|
+
fi
|
|
120
158
|
SAMPLE_STATUSLINE_OUT="$(printf '%s' '{"session_id":"doctor-no-record","workspace":{"current_dir":"/tmp"},"model":{"id":"claude-sonnet-5"},"context_window":{"context_window_size":200000,"used_percentage":1,"current_usage":{"input_tokens":1}}}' | "$EXPECTED_STATUSLINE" 2>/dev/null || true)"
|
|
121
159
|
if [ "$(printf '%s\n' "$SAMPLE_STATUSLINE_OUT" | wc -l | tr -d ' ')" = "2" ] && printf '%s\n' "$SAMPLE_STATUSLINE_OUT" | sed -n '1p' | grep -q 'tmp' && printf '%s\n' "$SAMPLE_STATUSLINE_OUT" | sed -n '2p' | grep -q '🪛' && printf '%s\n' "$SAMPLE_STATUSLINE_OUT" | sed -n '2p' | grep -q ' cc | s'; then ok "statusline synthetic execution emits two rows (work context + identity)"; else bad "statusline synthetic execution failed or omitted two-row work/identity marker"; fi
|
|
122
160
|
else
|
|
@@ -241,10 +279,17 @@ registry_for_ms() { # $1=bundle meta-session.ts → sibling plugin-root registry
|
|
|
241
279
|
dirname "$(dirname "$1")"
|
|
242
280
|
}
|
|
243
281
|
|
|
244
|
-
|
|
282
|
+
# Source authority for parity depends on the mode: an installed bundle carries the
|
|
283
|
+
# dist `.js`, so compare it against the dist `.js` (same tsc pipeline → hash-equal);
|
|
284
|
+
# a dev clone carries the `.ts`, so compare against the `.ts` source.
|
|
285
|
+
if [ "$LIB_EXT" = "js" ]; then
|
|
286
|
+
SRC_MS="$REPO/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js"
|
|
287
|
+
else
|
|
288
|
+
SRC_MS="$REPO/pi-extensions/lib/meta-session.ts"
|
|
289
|
+
fi
|
|
245
290
|
SRC_REG="$REPO/pi/entwurf-capabilities.json"
|
|
246
|
-
ASM_MS="$
|
|
247
|
-
INST_MS="$(ls "$CLAUDE_CFG"/plugins/cache/"$MKT_NAME"/"$PLUGIN"/*/lib/meta-session.
|
|
291
|
+
ASM_MS="$ASM/$PLUGIN/lib/meta-session.$LIB_EXT"
|
|
292
|
+
INST_MS="$(ls "$CLAUDE_CFG"/plugins/cache/"$MKT_NAME"/"$PLUGIN"/*/lib/meta-session."$LIB_EXT" 2>/dev/null | head -1 || true)"
|
|
248
293
|
|
|
249
294
|
ASM_ROOT="$(registry_for_ms "$ASM_MS")"
|
|
250
295
|
INST_ROOT="$(registry_for_ms "${INST_MS:-}")"
|