@junghanacs/entwurf 0.12.5 → 0.12.7
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/AGENTS.md +31 -17
- package/BASELINE.md +42 -8
- package/CHANGELOG.md +44 -0
- package/DELIVERY.md +71 -14
- package/README.md +77 -45
- package/VERIFY.md +33 -38
- package/docs/setup-clean-host.md +80 -22
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +90 -66
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +54 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +153 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +436 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/context.js +157 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/engraving.js +105 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +90 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +194 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +153 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-deliverability.js +42 -9
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +49 -13
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +104 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +30 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-native-push.js +57 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +10 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-release.js +9 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +21 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +5 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +17 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +125 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/adapter.js +158 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +61 -0
- package/mcp/entwurf-bridge/dist/scripts/agy-imprint.js +166 -0
- package/mcp/entwurf-bridge/dist/scripts/doctor-pi-provider.js +130 -0
- package/mcp/entwurf-bridge/dist/scripts/meta-bridge-prune.js +178 -0
- package/mcp/entwurf-bridge/dist/scripts/new-session-id.js +24 -0
- package/mcp/entwurf-bridge/src/index.ts +101 -67
- package/mcp/entwurf-bridge/test.sh +1 -1
- package/mcp/entwurf-bridge/tsconfig.build.json +23 -3
- package/package.json +10 -6
- package/pi-extensions/lib/entwurf-deliverability.ts +62 -9
- package/pi-extensions/lib/entwurf-self-address.ts +58 -15
- package/pi-extensions/lib/entwurf-v2-contract.ts +120 -12
- package/pi-extensions/lib/entwurf-v2-decider.ts +60 -0
- package/pi-extensions/lib/entwurf-v2-native-push.ts +86 -0
- package/pi-extensions/lib/entwurf-v2-production.ts +20 -0
- package/pi-extensions/lib/entwurf-v2-release.ts +9 -0
- package/pi-extensions/lib/entwurf-v2-runner.ts +29 -1
- package/pi-extensions/lib/entwurf-v2-send.ts +7 -0
- package/pi-extensions/lib/entwurf-v2-surface.ts +17 -0
- package/pi-extensions/lib/meta-sender-identity.ts +154 -0
- package/pi-extensions/lib/native-push/adapter.ts +255 -0
- package/pi-extensions/lib/native-push/register.ts +99 -0
- package/run.sh +974 -248
- package/scripts/agy-bridge-config.py +446 -0
- package/scripts/agy-bridge.sh +359 -0
- package/scripts/agy-hooks-bridge.sh +193 -0
- package/scripts/agy-hooks-config.py +257 -0
- package/scripts/agy-imprint.sh +28 -0
- package/scripts/agy-imprint.ts +193 -0
- package/scripts/agy-statusline-bridge.sh +176 -0
- package/scripts/agy-statusline-config.py +213 -0
- package/scripts/agy-statusline.sh +256 -0
- package/scripts/build-bridge.sh +20 -0
- package/scripts/check-agy-sender-identity.ts +364 -0
- package/scripts/check-entwurf-bridge-boot.ts +8 -2
- package/scripts/check-entwurf-deliverability.ts +34 -0
- package/scripts/check-entwurf-self-address.ts +78 -11
- package/scripts/check-entwurf-v2-contract.ts +136 -1
- package/scripts/check-entwurf-v2-decider.ts +95 -1
- package/scripts/check-entwurf-v2-matrix.ts +14 -3
- package/scripts/check-entwurf-v2-native-push.ts +193 -0
- package/scripts/check-entwurf-v2-production.ts +68 -1
- package/scripts/check-entwurf-v2-runner.ts +58 -0
- package/scripts/check-entwurf-v2-surface.ts +35 -0
- package/scripts/check-install-surface.ts +357 -0
- package/scripts/check-native-push-adapter.ts +319 -0
- package/scripts/check-native-push-register.ts +130 -0
- package/scripts/dev-bin.sh +195 -0
- package/scripts/doctor-pi-provider.ts +140 -0
- package/scripts/meta-bridge-doctor.sh +47 -9
- package/scripts/meta-bridge-install.sh +20 -17
- package/scripts/meta-bridge-state.py +40 -2
- package/scripts/meta-bridge-uninstall.sh +23 -12
- package/scripts/register-pi-package.py +183 -0
- package/scripts/register-pi-provider.py +287 -0
- package/scripts/smoke-agy-hooks-state.sh +172 -0
- package/scripts/smoke-agy-install-state.sh +660 -0
- package/scripts/smoke-agy-native-push-live.ts +243 -0
- package/scripts/smoke-agy-statusline-state.sh +300 -0
- package/scripts/smoke-meta-async-drift.sh +9 -2
- package/scripts/smoke-meta-install-state.sh +178 -8
- package/scripts/smoke-pi-provider-state.sh +182 -0
- package/scripts/smoke-user-scope-citizen.sh +191 -0
- package/scripts/with-dist-lock.sh +81 -0
- package/scripts/__pycache__/meta-bridge-state.cpython-312.pyc +0 -0
package/run.sh
CHANGED
|
@@ -38,6 +38,37 @@ PACKAGE_NAME="@junghanacs/entwurf"
|
|
|
38
38
|
# saved session anchor.
|
|
39
39
|
PROVIDER_ID="entwurf"
|
|
40
40
|
|
|
41
|
+
# THE strip-types fence, in one place. Node REFUSES `--experimental-strip-types`
|
|
42
|
+
# for any .ts below node_modules (ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING), so
|
|
43
|
+
# an installed package MUST run the prepack-emitted JS twin — the same boundary
|
|
44
|
+
# start.sh (0.12.1), the store-doctor (0.12.4), the plugin hook (0.12.5), and the
|
|
45
|
+
# agy imprint (0.12.7) each cross. Every .ts entrypoint routes through here so a
|
|
46
|
+
# NEW one cannot silently reintroduce the class: it was hand-written per surface
|
|
47
|
+
# before, and three operator commands (doctor-pi-provider / new-session-id /
|
|
48
|
+
# meta-bridge-prune) shipped dead under node_modules because of exactly that.
|
|
49
|
+
#
|
|
50
|
+
# A dev-only gate has no emitted twin by design (check-*/smoke-* are not shipped
|
|
51
|
+
# surfaces). Under an installed package it REFUSES rather than falling back to raw
|
|
52
|
+
# .ts — a fallback would just re-raise the fence error with a worse message.
|
|
53
|
+
# check-install-surface pins both halves statically.
|
|
54
|
+
run_ts() {
|
|
55
|
+
local rel="$1"; shift
|
|
56
|
+
case "$REPO_DIR" in
|
|
57
|
+
*/node_modules/*)
|
|
58
|
+
local dist="$REPO_DIR/mcp/entwurf-bridge/dist/${rel%.ts}.js"
|
|
59
|
+
if [ ! -f "$dist" ]; then
|
|
60
|
+
echo "entwurf: '$rel' is a dev-clone-only surface — the installed package ships no compiled twin." >&2
|
|
61
|
+
echo " (Node cannot strip types below node_modules; run this from a checkout.)" >&2
|
|
62
|
+
return 1
|
|
63
|
+
fi
|
|
64
|
+
(cd "$REPO_DIR" && node "$dist" "$@")
|
|
65
|
+
;;
|
|
66
|
+
*)
|
|
67
|
+
(cd "$REPO_DIR" && node --experimental-strip-types "$rel" "$@")
|
|
68
|
+
;;
|
|
69
|
+
esac
|
|
70
|
+
}
|
|
71
|
+
|
|
41
72
|
usage() {
|
|
42
73
|
cat <<'EOF'
|
|
43
74
|
Usage:
|
|
@@ -76,6 +107,7 @@ Usage:
|
|
|
76
107
|
./run.sh smoke-entwurf-v2-spawn-live # LIVE phase gate (0.11 Stage 0 step 5c-3c, D5) — OUT of pnpm check, needs LIVE=1. Exercises the production SpawnBgResumeDeps against REAL OS objects: S1 real unix socket → awaitSocketAlive resolves (real lstat+probe), symlink→forged, absent→abort settles; S2 real child → spawn-event resolve + SIGTERM kill + exit-code capture; S3 watcher integration → real timeout→kill→child-exited→release ×1. Does NOT spawn a real pi resume (that=5d matrix). Run before 5d: LIVE=1 ./run.sh smoke-entwurf-v2-spawn-live
|
|
77
108
|
./run.sh smoke-entwurf-v2-spawn-resume-live # 0.11.0 (A) ACCEPTANCE gate — OUT of pnpm check, needs LIVE=1. The FULL spawn-bg resident lifecycle: mint backend=pi identity → seed a REAL dormant pi session (one-shot into ~/.pi/agent/sessions) → runEntwurfV2(owned-outcome) routes dormant→spawn-bg resume → a REAL detached pi --entwurf-control child stands its socket up, resumes, DOES a model turn. Asserts executed/spawn-bg/socket-alive/released + lock released ×1 + no lock file + pid alive + socket connectable + resume USER & assistant OK nonces in the session JSONL. Model-in-loop IN. The gate v1 deprecation (0.12) is predicated on. Model: ENTWURF_LIVE_TARGET=<provider>/<model> (default openai-codex/gpt-5.4). LIVE=1 ./run.sh smoke-entwurf-v2-spawn-resume-live
|
|
78
109
|
./run.sh smoke-entwurf-v2-matrix-live # LIVE sentinel (0.11 Stage 0 step 5d-5, D4-b) — OUT of pnpm check, needs LIVE=1. Drives REAL production runEntwurfV2 deps over REAL OS objects, 4 cells: C1 control-socket (real pi --entwurf-control resident → RPC send → lock acquire→release ×1), C1b socket-only (record-less live pi → control-socket sent / owned→bad-target, A1 narrow), C2 meta-mailbox deliverable (armed self-fetch citizen → real .msg enqueue, lock-free), C3 meta-mailbox guard (no armed receiver → reject, no garbage). Model-in-loop OUT (transport/lock/enqueue gate, GPT Q2); negative/timeout stay deterministic. Model: ENTWURF_LIVE_TARGET=<provider>/<model> (default openai-codex/gpt-5.4). LIVE=1 ./run.sh smoke-entwurf-v2-matrix-live
|
|
110
|
+
./run.sh smoke-agy-native-push-live # 봉인 8 LIVE acceptance for the native-push (agy) rail — OUT of pnpm check, needs LIVE=1 + AGY_CONVERSATION_ID (a live agy conversation). Drives the REAL antigravity adapter + register core + runEntwurfV2 (production deps): doctor-static preflight (dangling→FAIL, the ③ gate), probe route, register create/attach idempotency, fire→native-push delivered, post-send re-probe (D7 partial), owned-outcome→native-push-no-resume-authority, bogus-conv→native-push-probe-indeterminate. Meta-store isolated to a temp dir (only the agy round-trip is real; no real-store residue). LIVE=1 AGY_CONVERSATION_ID=<convId> ./run.sh smoke-agy-native-push-live
|
|
79
111
|
./run.sh check-entwurf-facts # deterministic gate (0.11 Stage 0 step 4, fact-provider slice 1+2): PURE PeerFact core + resolveFactList union — R1 out-of-domain→unsupported, R3b pi 4-value, facts-only keyset; union: PeerFact+SocketOnlyFact by gardenId, dormant→dead, F3 indeterminate preserved, non-pi+socket fail-loud; pure, no IO
|
|
80
112
|
./run.sh check-socket-discovery # deterministic gate (0.11 Stage 0 step 4, fact-provider slice 3): SOCKET-axis scanSocketProbes — probes (dir sockets) ∪ (in-domain citizen canonical paths) 3-valued; dormant citizen no-file → dead (resumable, not unprobed), stall → indeterminate (F3), dir hygiene/dedup/missing-dir + e2e → resolveFactList; readdir/probe injected, no IO
|
|
81
113
|
./run.sh check-meta-listing # deterministic gate (0.11 Stage 0 step 4, fact-provider slice 4a): META-STORE axis listAllMetaIdentities — explicit-partial: parse failure / body-filename drift → explicit {filename,message} error (verbatim, no synthetic fields), valid records still listed (corrupt doesn't blind); mode strict throws / collect partial; entries/readRecord injected, no IO
|
|
@@ -84,6 +116,9 @@ Usage:
|
|
|
84
116
|
./run.sh check-entwurf-self-address # deterministic gate (SE-1/SE-2 slice 1): self-addressability honesty predicate computeSelfAddressability — pi replyable ⟺ live socket; meta ⟺ recordBacked ∧ ownerAlive ∧ watchArmed (regression-proof record-present rows); SOURCE GUARD buildStrictPiSenderEnvelope drops hardcoded replyable:true + existsSync-probes socket, entwurf_self renders alive vs expected. meta watchArmed wired in slice 2 (same release block)
|
|
85
117
|
./run.sh check-entwurf-deliverability # deterministic gate (SE-1/SE-2 slice 2c): conversational-mailbox deliverability predicate — computeMetaReceiverActive (recordBacked ∧ ownerAlive ∧ watchArmed) + mailboxConversationalDeliverable (self-fetch AND active); direct-inject pi refused (SE-1), self-fetch dead/unarmed refused (SE-2); self-address shares the same atom
|
|
86
118
|
./run.sh check-entwurf-mailbox-guard # deterministic gate (SE-1/SE-2 slice 2d): guarded mailbox enqueue — PURE 0-call (undeliverable target leaves injected enqueue uncalled) + TMPDIR snapshot (refused send leaves mailbox byte-identical, accepted writes one .msg) + fact gathering from record/capability/receiver-marker
|
|
119
|
+
./run.sh check-native-push-adapter # deterministic gate (봉인 3/8): native-push adapter leaf (antigravity) via a FAKE runner — FULL pid scan (not head -1), dead vs indeterminate, VOLATILE route re-discovery (no cache), send argv+ANTIGRAVITY_LS_ADDRESS env, non-zero exit throws, NO adapter-level retry (executor-owned), resolveNativePushAdapter fail-fast
|
|
120
|
+
./run.sh check-native-push-register # deterministic gate (봉인 5): registerNativeConversation (entwurf_register_native core) via fake adapter + isolated mkdtemp store — live probe→CREATE, re-register→ATTACH (same gid, cwd refreshed, no dup), not-live probe→REFUSE (throws, no record), receiver-marker abstinence (보정① source guard)
|
|
121
|
+
./run.sh check-agy-sender-identity # deterministic gate (#46 sender lane): WHO is calling the bridge — real agy hook as a child process writes an antigravity sender marker keyed by its PARENT pid (never on upsert failure), and resolveTrustedMetaSenderIdentity over isolated stores yields 0→null / 1→identity on EITHER backend / two distinct live identities on one owner pid→THROW (never guess, never downgrade to anonymous). This is what turns an agy send from external-mcp/unknown-host into a replyable garden citizen
|
|
87
122
|
./run.sh check-package-source-routing # deterministic gate (#29): package-source -> install-root mapping + fail-fast routing (local/git/npm/missing/project/no-source × local+remote, self-root, resume), no backend
|
|
88
123
|
./run.sh smoke-session-id-name # live 3-turn substrate smoke (Phase 3a): Pi 0.78 --session-id/--name through the bridge — header id/cwd, session_info name, append-not-recreate, spawn-only name, wrong-cwd footgun evidence
|
|
89
124
|
./run.sh new-session-id # print one fresh garden-native session id for operator launchers (--session-id)
|
|
@@ -91,14 +126,27 @@ Usage:
|
|
|
91
126
|
./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
127
|
./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
128
|
./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)
|
|
129
|
+
./run.sh smoke-agy-install-state # agy MCP + exact permission ownership regression (120): isolated HOME+XDG, adopt/state/inverse, symlink refuse, setup degrade. Offline/deterministic
|
|
130
|
+
./run.sh smoke-agy-statusline-state # agy ambient garden-id statusLine install/doctor/inverse regression (62). Offline/deterministic
|
|
131
|
+
./run.sh smoke-agy-hooks-state # agy PreInvocation birth/sender hook install/doctor/inverse + direct stdin→meta-record regression (37). Offline/deterministic
|
|
132
|
+
./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
133
|
./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
134
|
./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
135
|
./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
136
|
./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
137
|
|
|
99
|
-
./run.sh install-meta-bridge #
|
|
138
|
+
./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
139
|
./run.sh uninstall-meta-bridge # 1.0.0 meta-bridge Phase 2: stateful GLOBAL uninstall (restore only keys/items captured in install-state)
|
|
101
140
|
./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)
|
|
141
|
+
./run.sh install-agy-bridge # 봉인 7: agy MCP install adapter — register ONE entwurf-bridge server in the agy mcp_config (adopt file / create / REFUSE symlink), stable bin command, install-state under $XDG_DATA_HOME/entwurf/agy-bridge/
|
|
142
|
+
./run.sh uninstall-agy-bridge # 봉인 7: honest inverse of install-agy-bridge from install-state (restore preimage / remove key; refuse if config became a symlink)
|
|
143
|
+
./run.sh doctor-agy-bridge # fail-loud doctor: MCP config + exact permission rule + state + live probe label
|
|
144
|
+
./run.sh install-agy-statusline # own the agy statusLine subtree with bare entwurf-agy-statusline; preserve unrelated settings
|
|
145
|
+
./run.sh uninstall-agy-statusline # honest inverse from statusline install-state
|
|
146
|
+
./run.sh doctor-agy-statusline # fail-loud statusLine config/bin/state doctor + honest live SKIP
|
|
147
|
+
./run.sh install-agy-hooks # #46 agy birth imprint hook — named PreInvocation hook running bare entwurf-agy-imprint, preserving other hooks
|
|
148
|
+
./run.sh uninstall-agy-hooks # honest inverse of install-agy-hooks from install-state
|
|
149
|
+
./run.sh doctor-agy-hooks # fail-loud doctor for agy hooks.json imprint wiring
|
|
102
150
|
./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])
|
|
103
151
|
./run.sh meta-bridge-managed-keys # 0.10.0 meta-bridge: print the SSOT of settings keys entwurf OWNS (consumers read this to stay disjoint — keyset-owner invariant)
|
|
104
152
|
./run.sh check-keyset-overlap <fragment.json...> # 0.10.0 meta-bridge: PREVENTIVE keyset guard — fail if a consumer fragment collides with any pi-owned key (cross-repo; not in pnpm check)
|
|
@@ -106,9 +154,10 @@ Usage:
|
|
|
106
154
|
./run.sh check-pack # publish gate (dry-run): npm pack --dry-run + tarball invariants (runtime-critical present, dev residue absent)
|
|
107
155
|
./run.sh check-pack-install # heavy publish gate (prepublishOnly): actual npm pack + tar -tf + fresh-temp install smoke with 0.80.x peers
|
|
108
156
|
./run.sh sync-auth # copy ~/.pi/agent/auth.json anthropic OAuth credentials to entwurf alias
|
|
109
|
-
./run.sh install [project-dir] #
|
|
157
|
+
./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
158
|
./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
|
-
./run.sh remove [project-dir] # remove entwurf entries from project .pi/settings.json
|
|
159
|
+
./run.sh remove [project-dir] # remove entwurf entries from project .pi/settings.json (project scope only; global user-scope citizen left intact)
|
|
160
|
+
./run.sh remove-user-scope # explicit GLOBAL inverse of install's user-scope citizen: drop entwurf from ~/.pi/agent/settings.json packages[] (affects ALL cwds — shared entry, not per-project)
|
|
112
161
|
|
|
113
162
|
Notes:
|
|
114
163
|
- project-dir defaults to current directory
|
|
@@ -239,104 +288,113 @@ preflight_dep_integrity() {
|
|
|
239
288
|
fi
|
|
240
289
|
}
|
|
241
290
|
|
|
242
|
-
|
|
243
|
-
local
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
mkdir -p "$project_dir/.pi"
|
|
247
|
-
python3 - "$project_dir/.pi/settings.json" "$REPO_DIR" <<'PY'
|
|
291
|
+
preflight_pi_settings_shapes() {
|
|
292
|
+
local project_settings="$1"
|
|
293
|
+
local user_settings="$2"
|
|
294
|
+
python3 - "$project_settings" "$user_settings" <<'PY'
|
|
248
295
|
import json, sys
|
|
249
296
|
from pathlib import Path
|
|
250
297
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
settings_path.parent.mkdir(parents=True, exist_ok=True)
|
|
254
|
-
if settings_path.exists():
|
|
255
|
-
data = json.loads(settings_path.read_text())
|
|
256
|
-
if not isinstance(data, dict):
|
|
257
|
-
raise SystemExit("settings.json is not an object")
|
|
258
|
-
else:
|
|
259
|
-
data = {}
|
|
298
|
+
project = Path(sys.argv[1])
|
|
299
|
+
user = Path(sys.argv[2])
|
|
260
300
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
data
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
servers
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
# `./run.sh remove` also keeps session-bridge in its cleanup tuple so legacy
|
|
294
|
-
# entries are removed on uninstall too.
|
|
295
|
-
for name in BUNDLED:
|
|
296
|
-
desired_cmd = f"{repo_dir}/mcp/{name}/start.sh"
|
|
297
|
-
desired = {"command": desired_cmd, "args": []}
|
|
298
|
-
existing = servers.get(name)
|
|
299
|
-
if existing is None:
|
|
300
|
-
servers[name] = desired
|
|
301
|
-
print(f"install: added entwurfProvider.mcpServers.{name}")
|
|
302
|
-
elif isinstance(existing, dict) and existing.get("command") == desired_cmd:
|
|
303
|
-
# Already managed by us at the current repo path. Add the default args
|
|
304
|
-
# field when missing, but never overwrite user-customized args.
|
|
305
|
-
if "args" not in existing:
|
|
306
|
-
existing["args"] = []
|
|
307
|
-
print(f"install: normalized entwurfProvider.mcpServers.{name}.args -> []")
|
|
308
|
-
elif existing.get("args") != []:
|
|
309
|
-
print(f"install: preserved entwurfProvider.mcpServers.{name}.args (custom args)")
|
|
310
|
-
else:
|
|
311
|
-
cmd_repr = existing.get("command") if isinstance(existing, dict) else existing
|
|
312
|
-
print(f"install: preserved entwurfProvider.mcpServers.{name} (user override: {cmd_repr})")
|
|
313
|
-
|
|
314
|
-
# Legacy-bundled MCP names that prior versions of this installer wrote and that
|
|
315
|
-
# the current cutover supersedes. One-shot install-time prune (NOT a runtime
|
|
316
|
-
# alias): only remove entries whose command matches the bundled start.sh path,
|
|
317
|
-
# so user-customized commands are left alone.
|
|
318
|
-
# - session-bridge: retracted in 0.4.14 (issue #7, unified entwurf surface).
|
|
319
|
-
# - pi-tools-bridge: renamed to entwurf-bridge in 0.11 S2 cutover.
|
|
320
|
-
LEGACY_BUNDLED = {
|
|
321
|
-
"session-bridge": "retracted in 0.4.14, issue #7",
|
|
322
|
-
"pi-tools-bridge": "renamed to entwurf-bridge in 0.11 S2 cutover",
|
|
323
|
-
}
|
|
324
|
-
for name, reason in LEGACY_BUNDLED.items():
|
|
325
|
-
existing = servers.get(name)
|
|
326
|
-
if not isinstance(existing, dict):
|
|
327
|
-
continue
|
|
328
|
-
cmd = existing.get("command")
|
|
329
|
-
if not isinstance(cmd, str):
|
|
330
|
-
continue
|
|
331
|
-
if cmd == f"{repo_dir}/mcp/{name}/start.sh" or cmd.endswith(f"/entwurf/mcp/{name}/start.sh"):
|
|
332
|
-
del servers[name]
|
|
333
|
-
print(f"install: pruned legacy entwurfProvider.mcpServers.{name} ({reason})")
|
|
334
|
-
|
|
335
|
-
settings_path.write_text(json.dumps(data, indent=2) + "\n")
|
|
336
|
-
print(f"install: updated {settings_path}")
|
|
337
|
-
print(f"install: package source -> {repo_dir}")
|
|
301
|
+
def load_object(path: Path, label: str):
|
|
302
|
+
if not path.exists():
|
|
303
|
+
return None
|
|
304
|
+
data = json.loads(path.read_text())
|
|
305
|
+
if not isinstance(data, dict):
|
|
306
|
+
raise SystemExit(f"{label} settings is not a JSON object: {path}")
|
|
307
|
+
return data
|
|
308
|
+
|
|
309
|
+
def check_packages(data, path: Path, label: str):
|
|
310
|
+
if data is None:
|
|
311
|
+
return
|
|
312
|
+
packages = data.get("packages")
|
|
313
|
+
if packages is not None and not isinstance(packages, list):
|
|
314
|
+
raise SystemExit(f"{label} settings packages is not a JSON array: {path}")
|
|
315
|
+
|
|
316
|
+
def check_project_provider(data, path: Path):
|
|
317
|
+
if data is None:
|
|
318
|
+
return
|
|
319
|
+
provider = data.get("entwurfProvider")
|
|
320
|
+
if provider is None:
|
|
321
|
+
return
|
|
322
|
+
if not isinstance(provider, dict):
|
|
323
|
+
raise SystemExit(f"project settings entwurfProvider is not an object: {path}")
|
|
324
|
+
servers = provider.get("mcpServers")
|
|
325
|
+
if servers is not None and not isinstance(servers, dict):
|
|
326
|
+
raise SystemExit(f"project settings entwurfProvider.mcpServers is not an object: {path}")
|
|
327
|
+
|
|
328
|
+
project_data = load_object(project, "project")
|
|
329
|
+
user_data = load_object(user, "user")
|
|
330
|
+
check_packages(project_data, project, "project")
|
|
331
|
+
check_packages(user_data, user, "user")
|
|
332
|
+
check_project_provider(project_data, project)
|
|
338
333
|
PY
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
install_local_package() {
|
|
337
|
+
local project_dir agent_dir
|
|
338
|
+
project_dir=$(normalize_project_dir "$1")
|
|
339
|
+
agent_dir="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}"
|
|
340
|
+
preflight_dep_integrity
|
|
341
|
+
# Fail BEFORE any settings write if either target config already has a corrupt
|
|
342
|
+
# shape. The packages[] SSOT and provider writer run in two separate steps, so
|
|
343
|
+
# without this preflight a bad entwurfProvider could leave a half-installed
|
|
344
|
+
# packages[] entry behind (2026-07-03 install-boundary hardening).
|
|
345
|
+
preflight_pi_settings_shapes "$project_dir/.pi/settings.json" "$agent_dir/settings.json"
|
|
346
|
+
mkdir -p "$project_dir/.pi"
|
|
347
|
+
# packages[] registration via the shared SSOT — same is_entwurf_source
|
|
348
|
+
# predicate + idempotency as user-scope and remove (not a substring match).
|
|
349
|
+
python3 "$REPO_DIR/scripts/register-pi-package.py" "$project_dir/.pi/settings.json" "$REPO_DIR"
|
|
350
|
+
# entwurfProvider.mcpServers.entwurf-bridge (project scope — checkout-local, NO state; #46
|
|
351
|
+
# Task 2) via the shared register-pi-provider SSOT: normalize the command to the bare stable
|
|
352
|
+
# bin `entwurf-bridge` (ownership-classified: absent/managed-current/managed-legacy adopt, a
|
|
353
|
+
# true user-override is left untouched) + prune legacy bundles. project remove is the inverse.
|
|
354
|
+
python3 "$REPO_DIR/scripts/register-pi-provider.py" install "$project_dir/.pi/settings.json" "$REPO_DIR" --scope project
|
|
339
355
|
ensure_agent_dir_symlinks
|
|
356
|
+
register_user_scope_citizen
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
# Register entwurf as a pi USER-SCOPE citizen so its extensions
|
|
360
|
+
# (entwurf-control.ts → --entwurf-control / --emacs-agent-socket) load from ANY
|
|
361
|
+
# cwd, not only inside the entwurf checkout. project-scope `.pi/settings.json`
|
|
362
|
+
# only applies when pi runs inside the repo; the `pit`/`pia`/`pihome` global
|
|
363
|
+
# launchers and the npm consumer's "installs → just works" both need the entry
|
|
364
|
+
# in ~/.pi/agent/settings.json's packages[]. This is the wiring that dropped when
|
|
365
|
+
# `pi install` was removed from setup (2026-07-03: `--entwurf-control` unknown in
|
|
366
|
+
# a foreign cwd). Idempotent: absent → append, present → no-op; a stale entwurf
|
|
367
|
+
# entry at a different path is normalized to REPO_DIR. Every other package and key
|
|
368
|
+
# in the operator's user settings is preserved untouched.
|
|
369
|
+
register_user_scope_citizen() {
|
|
370
|
+
local agent_dir="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}"
|
|
371
|
+
# Shared idempotent implementation (also driven by smoke-user-scope-citizen).
|
|
372
|
+
python3 "$REPO_DIR/scripts/register-pi-package.py" "$agent_dir/settings.json" "$REPO_DIR"
|
|
373
|
+
# #46 Task 2: own entwurfProvider.mcpServers.entwurf-bridge as the bare stable bin at USER scope
|
|
374
|
+
# (GLOBAL/durable →파급s to every cwd), so its inverse needs an install-state honest inverse
|
|
375
|
+
# under $XDG_DATA_HOME/entwurf/pi-provider/ (Task 0/1 discipline). project scope is checkout-
|
|
376
|
+
# local and covered by `run.sh remove` (no state) — deliberate, reasoned asymmetry.
|
|
377
|
+
local pp_state="${XDG_DATA_HOME:-$HOME/.local/share}/entwurf/pi-provider/install-state.json"
|
|
378
|
+
python3 "$REPO_DIR/scripts/register-pi-provider.py" install "$agent_dir/settings.json" "$REPO_DIR" --scope user --state "$pp_state"
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
# The honest inverse of register_user_scope_citizen: drop entwurf from the GLOBAL
|
|
382
|
+
# ~/.pi/agent/settings.json packages[]. Deliberately NOT folded into `run.sh remove`
|
|
383
|
+
# (project scope): the user-scope citizen is a single GLOBAL entry keyed on this
|
|
384
|
+
# checkout and SHARED by every project + every foreign cwd, so tearing it down as a
|
|
385
|
+
# side effect of one project's remove would break `--entwurf-control` everywhere else
|
|
386
|
+
# (the exact "install → just works from any cwd" invariant register_user_scope_citizen
|
|
387
|
+
# exists to hold). Same explicit-global-lifecycle shape as install/uninstall-meta-bridge.
|
|
388
|
+
# Uses the same is_entwurf_source SSOT + --remove, so it never over-deletes a look-alike
|
|
389
|
+
# (entwurf-notes, openclaw-entwurf) and preserves every other package/key. Idempotent:
|
|
390
|
+
# no entwurf entry → no-op.
|
|
391
|
+
remove_user_scope_citizen() {
|
|
392
|
+
local agent_dir="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}"
|
|
393
|
+
python3 "$REPO_DIR/scripts/register-pi-package.py" "$agent_dir/settings.json" "$REPO_DIR" --remove
|
|
394
|
+
# #46 Task 2: honest inverse of the user-scope entwurfProvider ownership — the install-state
|
|
395
|
+
# drives it (absent/managed-* → remove OUR key; a user-override we never owned is untouched).
|
|
396
|
+
local pp_state="${XDG_DATA_HOME:-$HOME/.local/share}/entwurf/pi-provider/install-state.json"
|
|
397
|
+
python3 "$REPO_DIR/scripts/register-pi-provider.py" remove "$agent_dir/settings.json" "$REPO_DIR" --scope user --state "$pp_state"
|
|
340
398
|
}
|
|
341
399
|
|
|
342
400
|
# Ensure agent-level resources that entwurf code reads from
|
|
@@ -424,68 +482,29 @@ ensure_agent_dir_symlinks() {
|
|
|
424
482
|
remove_local_package() {
|
|
425
483
|
local project_dir
|
|
426
484
|
project_dir=$(normalize_project_dir "$1")
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
#
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
continue
|
|
451
|
-
filtered.append(item)
|
|
452
|
-
data["packages"] = filtered
|
|
453
|
-
|
|
454
|
-
# --- entwurfProvider.mcpServers cleanup ------------------------------------
|
|
455
|
-
# Only remove entries that look like they came from ./run.sh install: either
|
|
456
|
-
# the command matches the current $REPO_DIR anchor exactly, or it ends with
|
|
457
|
-
# the bundled "/entwurf/mcp/<name>/start.sh" pattern (covers a rebuilt
|
|
458
|
-
# checkout under a different directory). Anything else is treated as a user
|
|
459
|
-
# override and left in place.
|
|
460
|
-
BUNDLED = ("entwurf-bridge", "session-bridge", "pi-tools-bridge")
|
|
461
|
-
provider = data.get("entwurfProvider")
|
|
462
|
-
mcp_removed = 0
|
|
463
|
-
if isinstance(provider, dict):
|
|
464
|
-
servers = provider.get("mcpServers")
|
|
465
|
-
if isinstance(servers, dict):
|
|
466
|
-
for name in BUNDLED:
|
|
467
|
-
existing = servers.get(name)
|
|
468
|
-
if not isinstance(existing, dict):
|
|
469
|
-
continue
|
|
470
|
-
cmd = existing.get("command")
|
|
471
|
-
if not isinstance(cmd, str):
|
|
472
|
-
continue
|
|
473
|
-
exact = cmd == f"{repo_dir}/mcp/{name}/start.sh"
|
|
474
|
-
pattern = cmd.endswith(f"/entwurf/mcp/{name}/start.sh")
|
|
475
|
-
if exact or pattern:
|
|
476
|
-
del servers[name]
|
|
477
|
-
mcp_removed += 1
|
|
478
|
-
print(f"remove: removed entwurfProvider.mcpServers.{name}")
|
|
479
|
-
else:
|
|
480
|
-
print(f"remove: preserved entwurfProvider.mcpServers.{name} (user override: {cmd})")
|
|
481
|
-
if not servers:
|
|
482
|
-
provider.pop("mcpServers", None)
|
|
483
|
-
if not provider:
|
|
484
|
-
data.pop("entwurfProvider", None)
|
|
485
|
-
|
|
486
|
-
settings_path.write_text(json.dumps(data, indent=2) + "\n")
|
|
487
|
-
print(f"remove: removed {pkg_removed} packages[] entries, {mcp_removed} mcpServers entries from {settings_path}")
|
|
488
|
-
PY
|
|
485
|
+
# Same fail-before-write rule as install: remove has two writers too
|
|
486
|
+
# (packages[] SSOT, then entwurfProvider cleanup), so a malformed provider must
|
|
487
|
+
# not leave a half-removed packages[] entry behind.
|
|
488
|
+
preflight_pi_settings_shapes "$project_dir/.pi/settings.json" "$(mktemp -u)"
|
|
489
|
+
# packages[] cleanup via the shared SSOT — same is_entwurf_source predicate as
|
|
490
|
+
# install, so remove never over-deletes a look-alike repo (entwurf-notes, …)
|
|
491
|
+
# that install would never have registered.
|
|
492
|
+
python3 "$REPO_DIR/scripts/register-pi-package.py" "$project_dir/.pi/settings.json" "$REPO_DIR" --remove
|
|
493
|
+
# entwurfProvider.mcpServers.entwurf-bridge cleanup (project scope) via the shared SSOT: strip
|
|
494
|
+
# our-managed shapes (the bare stable bin AND the legacy repo start.sh path — a true user
|
|
495
|
+
# override is left in place) + prune legacy bundles. Mirrors install's ownership predicate so it
|
|
496
|
+
# never over-deletes a look-alike, and now also catches the bare bin the Task-2 install writes.
|
|
497
|
+
python3 "$REPO_DIR/scripts/register-pi-provider.py" remove "$project_dir/.pi/settings.json" "$REPO_DIR" --scope project
|
|
498
|
+
# `remove` is project-scope only. The GLOBAL user-scope citizen in
|
|
499
|
+
# ~/.pi/agent/settings.json (written by install's register_user_scope_citizen)
|
|
500
|
+
# is shared across every project + foreign cwd, so it is left intact here to
|
|
501
|
+
# avoid breaking `--entwurf-control` elsewhere. Point the operator at the
|
|
502
|
+
# explicit global inverse so the install↔remove asymmetry is never silent.
|
|
503
|
+
local agent_settings="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}/settings.json"
|
|
504
|
+
if python3 "$REPO_DIR/scripts/register-pi-package.py" "$agent_settings" "$REPO_DIR" --remove --dry-run 2>/dev/null | grep -q 'would remove'; then
|
|
505
|
+
log "note: global user-scope citizen still registered in $agent_settings"
|
|
506
|
+
log " run './run.sh remove-user-scope' to remove it (affects ALL cwds)"
|
|
507
|
+
fi
|
|
489
508
|
}
|
|
490
509
|
|
|
491
510
|
check_model_lock() {
|
|
@@ -493,7 +512,7 @@ check_model_lock() {
|
|
|
493
512
|
# No pi process, no network, no API cost. Mocks ExtensionAPI/Context and
|
|
494
513
|
# drives the model_select handler through every quadrant + edge case
|
|
495
514
|
# (see scripts/check-model-lock.ts header for the full matrix).
|
|
496
|
-
|
|
515
|
+
run_ts scripts/check-model-lock.ts
|
|
497
516
|
}
|
|
498
517
|
|
|
499
518
|
check_shell_quote() {
|
|
@@ -502,7 +521,7 @@ check_shell_quote() {
|
|
|
502
521
|
# across the two duplication sites AND behavioral correctness on the
|
|
503
522
|
# payload classes that caused the 2026-05-18 remote entwurf incident
|
|
504
523
|
# (backtick / $(...) / $VAR / korean tokens). No process spawn, no SSH.
|
|
505
|
-
|
|
524
|
+
run_ts scripts/check-shell-quote.ts
|
|
506
525
|
}
|
|
507
526
|
|
|
508
527
|
check_entwurf_session_identity() {
|
|
@@ -512,7 +531,7 @@ check_entwurf_session_identity() {
|
|
|
512
531
|
# models, titleSlug canonicalization, registry exact-tuple membership, name=
|
|
513
532
|
# info-only invariants, and header-scan collision pre-check. Isolates registry
|
|
514
533
|
# + sessions base to a temp dir. No backend, no API, no spawn.
|
|
515
|
-
|
|
534
|
+
run_ts scripts/check-entwurf-session-identity.ts
|
|
516
535
|
}
|
|
517
536
|
|
|
518
537
|
check_meta_session() {
|
|
@@ -522,7 +541,7 @@ check_meta_session() {
|
|
|
522
541
|
# real temp dir), idempotent existence-keyed decideUpsert + identity-drift
|
|
523
542
|
# refusal, and the pre-drilled read-receipt mutators. Pure functions; no
|
|
524
543
|
# backend, no hook, no API.
|
|
525
|
-
|
|
544
|
+
run_ts scripts/check-meta-session.ts
|
|
526
545
|
}
|
|
527
546
|
|
|
528
547
|
check_meta_record_v2() {
|
|
@@ -532,7 +551,7 @@ check_meta_record_v2() {
|
|
|
532
551
|
# and v2 field-contract crashes. Reader/normalizer only — no v2 writer yet.
|
|
533
552
|
# Kept separate from check-meta-session so 3D's v1-gate rewrites leave this
|
|
534
553
|
# back-compat golden untouched. Pure functions; no backend, no hook, no API.
|
|
535
|
-
|
|
554
|
+
run_ts scripts/check-meta-record-v2.ts
|
|
536
555
|
}
|
|
537
556
|
|
|
538
557
|
check_mailbox_receipt_state() {
|
|
@@ -542,7 +561,7 @@ check_mailbox_receipt_state() {
|
|
|
542
561
|
# keyset, then the fs store (stamp → persist → read-back) in a temp mailbox
|
|
543
562
|
# dir. Schema/store only — no live enqueue/read dual-write (that is 3D). No
|
|
544
563
|
# backend, no hook, no API.
|
|
545
|
-
|
|
564
|
+
run_ts scripts/check-mailbox-receipt-state.ts
|
|
546
565
|
}
|
|
547
566
|
|
|
548
567
|
check_entwurf_capabilities() {
|
|
@@ -553,7 +572,7 @@ check_entwurf_capabilities() {
|
|
|
553
572
|
# META_BACKEND_DESCRIPTORS for the three existing backends (drift guard), and
|
|
554
573
|
# strict keyset/coverage/field crashes. Parser/gate only — no live routing,
|
|
555
574
|
# no record/descriptor consumer change (that is 3D). No backend, no API.
|
|
556
|
-
|
|
575
|
+
run_ts scripts/check-entwurf-capabilities.ts
|
|
557
576
|
}
|
|
558
577
|
|
|
559
578
|
check_meta_dual_read() {
|
|
@@ -563,7 +582,7 @@ check_meta_dual_read() {
|
|
|
563
582
|
# unknown-version crash. Pure functions only — no fs upsert wiring, no
|
|
564
583
|
# readMetaInbox/enqueueMetaMessage change, no record.delivery removal (3D-2/3/4).
|
|
565
584
|
# No backend, no hook, no API.
|
|
566
|
-
|
|
585
|
+
run_ts scripts/check-meta-dual-read.ts
|
|
567
586
|
}
|
|
568
587
|
|
|
569
588
|
check_meta_mailbox_state_write() {
|
|
@@ -575,7 +594,7 @@ check_meta_mailbox_state_write() {
|
|
|
575
594
|
# carries lastEnqueuedAt/lastReadAt with field isolation, lastDeliveredAt is never
|
|
576
595
|
# invented, an empty inbox is a no-op on BOTH record and state (⑥), and a state
|
|
577
596
|
# drift surfaces fail-loud. v2 citizen seeded via upsertMetaSession. No API.
|
|
578
|
-
|
|
597
|
+
run_ts scripts/check-meta-mailbox-state-write.ts
|
|
579
598
|
}
|
|
580
599
|
|
|
581
600
|
check_meta_receiver_marker() {
|
|
@@ -588,7 +607,7 @@ check_meta_receiver_marker() {
|
|
|
588
607
|
# from "no marker"); armProvenance constrained to the arm-capable events so
|
|
589
608
|
# UserPromptSubmit cannot mint presence; reader does NOT gate on record existence
|
|
590
609
|
# (recordBacked is the deliverability predicate's fact). Real tmpdir, no API.
|
|
591
|
-
|
|
610
|
+
run_ts scripts/check-meta-receiver-marker.ts
|
|
592
611
|
}
|
|
593
612
|
|
|
594
613
|
check_meta_migration() {
|
|
@@ -598,7 +617,7 @@ check_meta_migration() {
|
|
|
598
617
|
# state.json). Crash-order: a v1 record's receipts migrate to state BEFORE the v2
|
|
599
618
|
# rewrite (proven via upsert attach), and a drift'd state makes migrate throw with
|
|
600
619
|
# the record STILL v1 (recoverable: next attach re-migrates). Temp dir, no API.
|
|
601
|
-
|
|
620
|
+
run_ts scripts/check-meta-migration.ts
|
|
602
621
|
}
|
|
603
622
|
|
|
604
623
|
check_meta_dual_consumers() {
|
|
@@ -610,7 +629,7 @@ check_meta_dual_consumers() {
|
|
|
610
629
|
# authority ambiguity → throw, so the v2 upsert never duplicate-mints) + v1 normalize +
|
|
611
630
|
# body/filename drift fail-fast. The v1-only raw readers remain for v1-fixture gates.
|
|
612
631
|
# Temp dir, no API.
|
|
613
|
-
|
|
632
|
+
run_ts scripts/check-meta-dual-consumers.ts
|
|
614
633
|
}
|
|
615
634
|
|
|
616
635
|
check_meta_capability_source() {
|
|
@@ -623,7 +642,7 @@ check_meta_capability_source() {
|
|
|
623
642
|
# 3 backends). The record.delivery.wakeMode SLOT stays (removal is 3D-4); only the
|
|
624
643
|
# SOURCE moves. check-entwurf-capabilities still owns the registry ≡ const drift
|
|
625
644
|
# guard. Pure — no fs writes, no backend, no hook, no API.
|
|
626
|
-
|
|
645
|
+
run_ts scripts/check-meta-capability-source.ts
|
|
627
646
|
}
|
|
628
647
|
|
|
629
648
|
check_socket_probe() {
|
|
@@ -634,7 +653,7 @@ check_socket_probe() {
|
|
|
634
653
|
# invariant. Listing lists alive only. Pure classify + GC/listing policy +
|
|
635
654
|
# a two-socket integration (live listener → alive survives; nonexistent →
|
|
636
655
|
# dead GC-eligible). No wire timeout fixture, no backend, no API.
|
|
637
|
-
|
|
656
|
+
run_ts scripts/check-socket-probe.ts
|
|
638
657
|
}
|
|
639
658
|
|
|
640
659
|
check_project_trust_handler() {
|
|
@@ -646,7 +665,7 @@ check_project_trust_handler() {
|
|
|
646
665
|
# never undefined. Plus the thin adapter: fake ctx.ui.select, single-writer
|
|
647
666
|
# (handler never calls store.set — pi persists on remember:true), F5a evidence
|
|
648
667
|
# in the prompt title. No real UI, no backend, no API.
|
|
649
|
-
|
|
668
|
+
run_ts scripts/check-project-trust-handler.ts
|
|
650
669
|
}
|
|
651
670
|
|
|
652
671
|
check_entwurf_v2_contract() {
|
|
@@ -660,7 +679,7 @@ check_entwurf_v2_contract() {
|
|
|
660
679
|
# Q2 owned-outcome+live never auto-sends. R5 taxonomy covers table reasons +
|
|
661
680
|
# pre-claims bad-target/untrusted-fail-fast/target-locked (bucket B F2). Plus
|
|
662
681
|
# a schema↔types drift guard on the TypeBox input/receipt. Pure, no API.
|
|
663
|
-
|
|
682
|
+
run_ts scripts/check-entwurf-v2-contract.ts
|
|
664
683
|
}
|
|
665
684
|
|
|
666
685
|
check_entwurf_v2_lock() {
|
|
@@ -675,7 +694,7 @@ check_entwurf_v2_lock() {
|
|
|
675
694
|
# fail-closed to conflict. Empty/corrupt lockfile = conflict, never
|
|
676
695
|
# auto-deleted. F2-P1: a malformed gid throws before any path is built. Real
|
|
677
696
|
# temp dir (wx atomicity under test); clock/nonce/pid/host/kill injected.
|
|
678
|
-
|
|
697
|
+
run_ts scripts/check-entwurf-v2-lock.ts
|
|
679
698
|
}
|
|
680
699
|
|
|
681
700
|
check_entwurf_v2_decider() {
|
|
@@ -689,7 +708,7 @@ check_entwurf_v2_decider() {
|
|
|
689
708
|
# takes NO lock (?7); resume plan has no mode/wantsReply/provider/model but has
|
|
690
709
|
# expectedSocketPath/observeTimeoutMs/releaseWhen; an invalid gid throws before
|
|
691
710
|
# any lookup (F2-P1). Pure, no IO, no API.
|
|
692
|
-
|
|
711
|
+
run_ts scripts/check-entwurf-v2-decider.ts
|
|
693
712
|
}
|
|
694
713
|
|
|
695
714
|
check_entwurf_v2_matrix() {
|
|
@@ -703,7 +722,7 @@ check_entwurf_v2_matrix() {
|
|
|
703
722
|
# FAILS if any transport / lock class / pre-probe reject is missing — a dropped
|
|
704
723
|
# decider cell cannot pass silently. Thin coverage, NOT a decider re-impl; surface
|
|
705
724
|
# parity stays in check-entwurf-v2-surface. Pure, no IO, no API.
|
|
706
|
-
|
|
725
|
+
run_ts scripts/check-entwurf-v2-matrix.ts
|
|
707
726
|
}
|
|
708
727
|
|
|
709
728
|
check_entwurf_v2_release() {
|
|
@@ -715,7 +734,7 @@ check_entwurf_v2_release() {
|
|
|
715
734
|
# release on the FIRST observed transition (socket-alive ∨ child-exited any code)
|
|
716
735
|
# or a failed start; socket↔exit race idempotent (single release either order);
|
|
717
736
|
# decideReleasePolicy enforces the lock-nullness invariant (?7). Pure, no IO.
|
|
718
|
-
|
|
737
|
+
run_ts scripts/check-entwurf-v2-release.ts
|
|
719
738
|
}
|
|
720
739
|
|
|
721
740
|
check_entwurf_v2_send() {
|
|
@@ -727,7 +746,7 @@ check_entwurf_v2_send() {
|
|
|
727
746
|
# indeterminate->failed+rethrow with deadFallback+mailbox NEVER called (no
|
|
728
747
|
# double-delivery on an alive-but-stalled socket). Release fires exactly once per
|
|
729
748
|
# send-final; a releaseLock throw never masks the send failure (5b). IO-via-dep.
|
|
730
|
-
|
|
749
|
+
run_ts scripts/check-entwurf-v2-send.ts
|
|
731
750
|
}
|
|
732
751
|
|
|
733
752
|
check_entwurf_v2_send_fallback() {
|
|
@@ -740,7 +759,7 @@ check_entwurf_v2_send_fallback() {
|
|
|
740
759
|
# / bad-target + address-conflict->reject pre-probe. Mis-wire (plan/lock gid) fails loud
|
|
741
760
|
# before IO; inspect/probe throws PROPAGATE (the hand owns failed+release); the resolver
|
|
742
761
|
# has NO release seam; every execute plan keeps the held gid and is never spawn-bg.
|
|
743
|
-
|
|
762
|
+
run_ts scripts/check-entwurf-v2-send-fallback.ts
|
|
744
763
|
}
|
|
745
764
|
|
|
746
765
|
check_entwurf_v2_mailbox() {
|
|
@@ -757,7 +776,19 @@ check_entwurf_v2_mailbox() {
|
|
|
757
776
|
# every access throws still resolves). Source guard: the lib code has NO release seam
|
|
758
777
|
# and NO routing seam (no releaseLock / inspect / probe / resolve) — a lock leak or
|
|
759
778
|
# re-route is structurally impossible.
|
|
760
|
-
|
|
779
|
+
run_ts scripts/check-entwurf-v2-mailbox.ts
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
check_entwurf_v2_native_push() {
|
|
783
|
+
# Deterministic gate for 봉인 3/4: the native-push SEND hand (deliverViaNativePush +
|
|
784
|
+
# makeNativePushSend), the executor half of the native-push rail — where the 1-shot retry
|
|
785
|
+
# lives (moved out of the adapter leaf). Proves over a fake adapter (no agy/socket): success
|
|
786
|
+
# first try -> {retried:false}, ONE send over the planted route, ZERO re-probe; fail ->
|
|
787
|
+
# re-probe alive -> re-send success -> {retried:true}, TWO sends, the 2nd over the RE-
|
|
788
|
+
# DISCOVERED route; re-send FAIL -> throws (no 3rd attempt); re-probe dead/indeterminate ->
|
|
789
|
+
# throws (not retried), NO second send. makeNativePushSend resolves the adapter from
|
|
790
|
+
# plan.backend and IGNORES the lock (lock-free rail).
|
|
791
|
+
run_ts scripts/check-entwurf-v2-native-push.ts
|
|
761
792
|
}
|
|
762
793
|
|
|
763
794
|
check_entwurf_v2_runner() {
|
|
@@ -771,7 +802,7 @@ check_entwurf_v2_runner() {
|
|
|
771
802
|
# SendDeliveredReleaseFailedError -> execution-failed{finalizedOutcome, releaseFailed,
|
|
772
803
|
# retrySafe:false}; a plain hand throw -> execution-failed{retrySafe:false} with no
|
|
773
804
|
# finalizedOutcome. Exactly one hand runs per execute.
|
|
774
|
-
|
|
805
|
+
run_ts scripts/check-entwurf-v2-runner.ts
|
|
775
806
|
}
|
|
776
807
|
|
|
777
808
|
check_entwurf_v2_surface() {
|
|
@@ -782,7 +813,7 @@ check_entwurf_v2_surface() {
|
|
|
782
813
|
# lock-retained, N1 delivered-but-dirty) / surface ctx-free source guard / entwurf-control
|
|
783
814
|
# registers entwurf_v2 + reaches the fence via a NON-LITERAL dynamic import (no static fence
|
|
784
815
|
# import → TS5097 stays closed) + decorates sender origin:pi-session/replyable:true.
|
|
785
|
-
|
|
816
|
+
run_ts scripts/check-entwurf-v2-surface.ts
|
|
786
817
|
}
|
|
787
818
|
|
|
788
819
|
check_entwurf_bridge_boot() {
|
|
@@ -793,7 +824,7 @@ check_entwurf_bridge_boot() {
|
|
|
793
824
|
# registered on the runtime surface with its schema (G1b). tools/list only → no tools/call,
|
|
794
825
|
# no lock/fs side effect, no auth → safe in pnpm check. Broad protocol/negative suite stays
|
|
795
826
|
# in check-bridge/test.sh (D1=A안).
|
|
796
|
-
|
|
827
|
+
run_ts scripts/check-entwurf-bridge-boot.ts
|
|
797
828
|
}
|
|
798
829
|
|
|
799
830
|
check_entwurf_bridge_pi_free() {
|
|
@@ -804,7 +835,7 @@ check_entwurf_bridge_pi_free() {
|
|
|
804
835
|
# value-imports @earendil-works/pi-*. Type-only imports and dynamic `await import()`
|
|
805
836
|
# (the intended lazy preflight boundary) are excluded — the runtime boot smoke is the
|
|
806
837
|
# final authority that peers/self/list/mailbox-deliver come up pi-free.
|
|
807
|
-
|
|
838
|
+
run_ts scripts/check-entwurf-bridge-pi-free.ts
|
|
808
839
|
}
|
|
809
840
|
|
|
810
841
|
check_entwurf_v2_production() {
|
|
@@ -815,7 +846,7 @@ check_entwurf_v2_production() {
|
|
|
815
846
|
# lockDir / QB3 the spawn watcher releases via the SHARED lockDir release (not the spawn
|
|
816
847
|
# factory default) / the mailbox hand enqueues onto the wired dirs / a dead control send
|
|
817
848
|
# re-resolves to the SAME sendViaMailbox instance on the SAME dirs (Q3+Q5 no drift).
|
|
818
|
-
|
|
849
|
+
run_ts scripts/check-entwurf-v2-production.ts
|
|
819
850
|
}
|
|
820
851
|
|
|
821
852
|
check_entwurf_control_rpc() {
|
|
@@ -826,7 +857,7 @@ check_entwurf_control_rpc() {
|
|
|
826
857
|
# sendRpcCommand from the lib and no longer defines its own / real short unix-socket round-trip
|
|
827
858
|
# (write command -> matched {type:response,command,success:true} -> resolve) / close-before-
|
|
828
859
|
# response rejects 'connection closed before response'. net.Server only, no model/pi process.
|
|
829
|
-
|
|
860
|
+
run_ts scripts/check-entwurf-control-rpc.ts
|
|
830
861
|
}
|
|
831
862
|
|
|
832
863
|
check_entwurf_v2_spawn() {
|
|
@@ -840,7 +871,7 @@ check_entwurf_v2_spawn() {
|
|
|
840
871
|
# obtainable (grace elapses, or a post-spawn watch dep throws and the exit can't be
|
|
841
872
|
# observed) -> lock-retained fail-closed (released:false, pid/socket/lockPath surfaced) —
|
|
842
873
|
# there is NO direct-release hatch; deps.releaseLock is reached ONLY via reduceRelease.
|
|
843
|
-
|
|
874
|
+
run_ts scripts/check-entwurf-v2-spawn.ts
|
|
844
875
|
}
|
|
845
876
|
|
|
846
877
|
check_entwurf_resume_args() {
|
|
@@ -853,7 +884,7 @@ check_entwurf_resume_args() {
|
|
|
853
884
|
# and the prompt-as-turn positional (`-p` NOT dropped in v2); explicitExtensionArgs
|
|
854
885
|
# preserved exactly once (provider-resolution footgun #29); v2 includes plan.launchArgs
|
|
855
886
|
# (--approve) before the prompt; null provider emits no --provider; no cross-contamination.
|
|
856
|
-
|
|
887
|
+
run_ts scripts/check-entwurf-resume-args.ts
|
|
857
888
|
}
|
|
858
889
|
|
|
859
890
|
check_entwurf_v2_spawn_production() {
|
|
@@ -867,7 +898,7 @@ check_entwurf_v2_spawn_production() {
|
|
|
867
898
|
# (symlink) rejects without connecting, dead→wait→alive, abort clears the sleep. awaitChildExit
|
|
868
899
|
# resolves the code + removes the listener on abort. awaitTimeout schedules + abort-clears.
|
|
869
900
|
# killChild=SIGTERM; releaseLock delegates; a proc-less child fails loud (mis-wire).
|
|
870
|
-
|
|
901
|
+
run_ts scripts/check-entwurf-v2-spawn-production.ts
|
|
871
902
|
}
|
|
872
903
|
|
|
873
904
|
|
|
@@ -883,7 +914,7 @@ smoke_entwurf_v2_spawn_live() {
|
|
|
883
914
|
echo "[smoke-entwurf-v2-spawn-live] skipped — set LIVE=1 to run (spawns real children + opens a real unix socket)."
|
|
884
915
|
return 0
|
|
885
916
|
fi
|
|
886
|
-
|
|
917
|
+
run_ts scripts/smoke-entwurf-v2-spawn-live.ts
|
|
887
918
|
}
|
|
888
919
|
|
|
889
920
|
smoke_acp_socket_citizen_live() {
|
|
@@ -896,7 +927,7 @@ smoke_acp_socket_citizen_live() {
|
|
|
896
927
|
# citizenship, never a backend turn (that is S2). Honest skip when LIVE!=1.
|
|
897
928
|
# Model override: ENTWURF_S1_MODEL (default claude-opus-4-8).
|
|
898
929
|
# LIVE=1 ./run.sh smoke-acp-socket-citizen-live
|
|
899
|
-
|
|
930
|
+
run_ts scripts/smoke-acp-socket-citizen-live.ts
|
|
900
931
|
}
|
|
901
932
|
|
|
902
933
|
smoke_acp_raw_turn_live() {
|
|
@@ -910,7 +941,7 @@ smoke_acp_raw_turn_live() {
|
|
|
910
941
|
# fails acceptance unless ENTWURF_ACP_RAW_TURN_ALLOW_PATH_FALLBACK=1, debug).
|
|
911
942
|
# Model override: ENTWURF_ACP_RAW_TURN_MODEL (default claude-sonnet-5).
|
|
912
943
|
# LIVE=1 ./run.sh smoke-acp-raw-turn-live
|
|
913
|
-
|
|
944
|
+
run_ts scripts/smoke-acp-raw-turn-live.ts
|
|
914
945
|
}
|
|
915
946
|
|
|
916
947
|
smoke_acp_overlay_live() {
|
|
@@ -928,7 +959,7 @@ smoke_acp_overlay_live() {
|
|
|
928
959
|
# fallback fails acceptance unless ENTWURF_ACP_OVERLAY_ALLOW_PATH_FALLBACK=1).
|
|
929
960
|
# Model override: ENTWURF_ACP_OVERLAY_MODEL (default claude-sonnet-5).
|
|
930
961
|
# LIVE=1 ./run.sh smoke-acp-overlay-live
|
|
931
|
-
|
|
962
|
+
run_ts scripts/smoke-acp-overlay-live.ts
|
|
932
963
|
}
|
|
933
964
|
|
|
934
965
|
smoke_acp_memory_containment_live() {
|
|
@@ -943,7 +974,7 @@ smoke_acp_memory_containment_live() {
|
|
|
943
974
|
# fallback fails acceptance unless ENTWURF_ACP_MEMORY_ALLOW_PATH_FALLBACK=1).
|
|
944
975
|
# Model override: ENTWURF_ACP_MEMORY_MODEL (default claude-sonnet-5).
|
|
945
976
|
# LIVE=1 ./run.sh smoke-acp-memory-containment-live
|
|
946
|
-
|
|
977
|
+
run_ts scripts/smoke-acp-memory-containment-live.ts
|
|
947
978
|
}
|
|
948
979
|
|
|
949
980
|
smoke_acp_provider_live() {
|
|
@@ -958,7 +989,7 @@ smoke_acp_provider_live() {
|
|
|
958
989
|
# event-mapper gate owns the tool→notice contract.
|
|
959
990
|
# Model override: ENTWURF_ACP_PROVIDER_MODEL (default claude-sonnet-5).
|
|
960
991
|
# LIVE=1 ./run.sh smoke-acp-provider-live
|
|
961
|
-
|
|
992
|
+
run_ts scripts/smoke-acp-provider-live.ts
|
|
962
993
|
}
|
|
963
994
|
|
|
964
995
|
smoke_acp_session_reuse_live() {
|
|
@@ -972,7 +1003,7 @@ smoke_acp_session_reuse_live() {
|
|
|
972
1003
|
# smoke-acp-provider-live.
|
|
973
1004
|
# Model override: ENTWURF_ACP_PROVIDER_MODEL (default claude-sonnet-5).
|
|
974
1005
|
# LIVE=1 ./run.sh smoke-acp-session-reuse-live
|
|
975
|
-
|
|
1006
|
+
run_ts scripts/smoke-acp-session-reuse-live.ts
|
|
976
1007
|
}
|
|
977
1008
|
|
|
978
1009
|
smoke_acp_carrier_augment_live() {
|
|
@@ -984,7 +1015,7 @@ smoke_acp_carrier_augment_live() {
|
|
|
984
1015
|
# check via SMOKE_ACP_CARRIER_PRESENT=1 (non-blocking).
|
|
985
1016
|
# Model override: ENTWURF_ACP_PROVIDER_MODEL (default claude-sonnet-5).
|
|
986
1017
|
# LIVE=1 ./run.sh smoke-acp-carrier-augment-live
|
|
987
|
-
|
|
1018
|
+
run_ts scripts/smoke-acp-carrier-augment-live.ts
|
|
988
1019
|
}
|
|
989
1020
|
|
|
990
1021
|
smoke_acp_mcp_live() {
|
|
@@ -997,7 +1028,7 @@ smoke_acp_mcp_live() {
|
|
|
997
1028
|
# fix). Isolated probe (not entwurf-bridge) so a failure does not blur into
|
|
998
1029
|
# identity/env wiring. Model override: ENTWURF_ACP_PROVIDER_MODEL.
|
|
999
1030
|
# LIVE=1 ./run.sh smoke-acp-mcp-live
|
|
1000
|
-
|
|
1031
|
+
run_ts scripts/smoke-acp-mcp-live.ts
|
|
1001
1032
|
}
|
|
1002
1033
|
|
|
1003
1034
|
smoke_acp_skill_live() {
|
|
@@ -1009,7 +1040,7 @@ smoke_acp_skill_live() {
|
|
|
1009
1040
|
# the Skill/Skill(*) auto-add reach the live session (the other half of the GLG
|
|
1010
1041
|
# baseline). Model override: ENTWURF_ACP_PROVIDER_MODEL.
|
|
1011
1042
|
# LIVE=1 ./run.sh smoke-acp-skill-live
|
|
1012
|
-
|
|
1043
|
+
run_ts scripts/smoke-acp-skill-live.ts
|
|
1013
1044
|
}
|
|
1014
1045
|
|
|
1015
1046
|
smoke_acp_bundled_mcp_live() {
|
|
@@ -1025,7 +1056,7 @@ smoke_acp_bundled_mcp_live() {
|
|
|
1025
1056
|
# diagnostic backlog, not the 0.11.0 release circuit). Model override:
|
|
1026
1057
|
# ENTWURF_ACP_PROVIDER_MODEL.
|
|
1027
1058
|
# LIVE=1 ./run.sh smoke-acp-bundled-mcp-live
|
|
1028
|
-
|
|
1059
|
+
run_ts scripts/smoke-acp-bundled-mcp-live.ts
|
|
1029
1060
|
}
|
|
1030
1061
|
|
|
1031
1062
|
smoke_acp_rgg_live() {
|
|
@@ -1062,7 +1093,7 @@ smoke_entwurf_v2_matrix_live() {
|
|
|
1062
1093
|
echo "[smoke-entwurf-v2-matrix-live] skipped — set LIVE=1 to run (spawns a real pi --entwurf-control + opens a real socket)."
|
|
1063
1094
|
return 0
|
|
1064
1095
|
fi
|
|
1065
|
-
|
|
1096
|
+
run_ts scripts/smoke-entwurf-v2-matrix-live.ts
|
|
1066
1097
|
}
|
|
1067
1098
|
|
|
1068
1099
|
smoke_entwurf_v2_spawn_resume_live() {
|
|
@@ -1084,7 +1115,7 @@ smoke_entwurf_v2_spawn_resume_live() {
|
|
|
1084
1115
|
echo "[smoke-entwurf-v2-spawn-resume-live] skipped — set LIVE=1 to run (spawns a real pi resume child + opens a real socket)."
|
|
1085
1116
|
return 0
|
|
1086
1117
|
fi
|
|
1087
|
-
|
|
1118
|
+
run_ts scripts/smoke-entwurf-v2-spawn-resume-live.ts
|
|
1088
1119
|
}
|
|
1089
1120
|
|
|
1090
1121
|
check_entwurf_facts() {
|
|
@@ -1096,7 +1127,7 @@ check_entwurf_facts() {
|
|
|
1096
1127
|
# (no proof ≠ dead). facts-only keyset: identity facts + liveness, NO
|
|
1097
1128
|
# verb-routing (resumable/sendable/transport/dispatch/action) and NO
|
|
1098
1129
|
# transcriptPath (동결결정 10). Pure, no IO, no API.
|
|
1099
|
-
|
|
1130
|
+
run_ts scripts/check-entwurf-facts.ts
|
|
1100
1131
|
}
|
|
1101
1132
|
|
|
1102
1133
|
check_socket_discovery() {
|
|
@@ -1108,7 +1139,7 @@ check_socket_discovery() {
|
|
|
1108
1139
|
# indeterminate (F3), never folded to dead by an alive-only listing. Dir
|
|
1109
1140
|
# hygiene (non-.sock / malformed names ignored), dedup, missing-dir, sort, and
|
|
1110
1141
|
# an end-to-end scanSocketProbes→resolveFactList. readdir/probe injected, no IO.
|
|
1111
|
-
|
|
1142
|
+
run_ts scripts/check-socket-discovery.ts
|
|
1112
1143
|
}
|
|
1113
1144
|
|
|
1114
1145
|
check_meta_listing() {
|
|
@@ -1119,7 +1150,7 @@ check_meta_listing() {
|
|
|
1119
1150
|
# becomes an explicit error carrying ONLY {filename, message}, verbatim (a
|
|
1120
1151
|
# salvaged gid string as a fact = synthetic backdoor). mode strict throws on
|
|
1121
1152
|
# any error, collect returns partial. entries/readRecord injected, no IO.
|
|
1122
|
-
|
|
1153
|
+
run_ts scripts/check-meta-listing.ts
|
|
1123
1154
|
}
|
|
1124
1155
|
|
|
1125
1156
|
check_entwurf_fact_provider() {
|
|
@@ -1131,7 +1162,7 @@ check_entwurf_fact_provider() {
|
|
|
1131
1162
|
# survives; impossible wiring invariant (resolveFactList duplicate/unprobed) →
|
|
1132
1163
|
# throw, never swallowed. A collision quarantines BOTH the PeerFact and the
|
|
1133
1164
|
# socket (gid is the universal address). meta + socket deps injected, no IO.
|
|
1134
|
-
|
|
1165
|
+
run_ts scripts/check-entwurf-fact-provider.ts
|
|
1135
1166
|
}
|
|
1136
1167
|
|
|
1137
1168
|
check_entwurf_peers_surface() {
|
|
@@ -1145,7 +1176,7 @@ check_entwurf_peers_surface() {
|
|
|
1145
1176
|
# diagnostics in both surfaces; empty → "(none)"; unsupported shown; enrich null
|
|
1146
1177
|
# → "(not enriched)". WIRING guard: bridge calls listEntwurfFacts+renderEntwurfPeers,
|
|
1147
1178
|
# getLiveSessions gone. Facts fabricated, no IO (only static source read).
|
|
1148
|
-
|
|
1179
|
+
run_ts scripts/check-entwurf-peers-surface.ts
|
|
1149
1180
|
}
|
|
1150
1181
|
|
|
1151
1182
|
|
|
@@ -1162,7 +1193,7 @@ check_entwurf_self_address() {
|
|
|
1162
1193
|
# probes the socket, and entwurf_self renders alive vs expected (no path lie).
|
|
1163
1194
|
# Slice boundary: meta watchArmed is wired from the slice-2 presence marker; do NOT
|
|
1164
1195
|
# claim slice 1 green standalone (1+2 close in the same release block).
|
|
1165
|
-
|
|
1196
|
+
run_ts scripts/check-entwurf-self-address.ts
|
|
1166
1197
|
}
|
|
1167
1198
|
|
|
1168
1199
|
check_entwurf_deliverability() {
|
|
@@ -1174,7 +1205,7 @@ check_entwurf_deliverability() {
|
|
|
1174
1205
|
# (pi) refused even when active (SE-1, no mailbox drain), self-fetch + dead-owner/unarmed
|
|
1175
1206
|
# refused (SE-2, would rot); WIRING that the self-addressability predicate shares the
|
|
1176
1207
|
# SAME active-receiver atom (one source of truth). Pure, no IO.
|
|
1177
|
-
|
|
1208
|
+
run_ts scripts/check-entwurf-deliverability.ts
|
|
1178
1209
|
}
|
|
1179
1210
|
|
|
1180
1211
|
check_entwurf_mailbox_guard() {
|
|
@@ -1185,7 +1216,47 @@ check_entwurf_mailbox_guard() {
|
|
|
1185
1216
|
# one calls it exactly once; TMPDIR SNAPSHOT with the real enqueueMetaMessage — a refused
|
|
1186
1217
|
# send leaves the mailbox tree byte-identical (file list + content hash, not just mtime),
|
|
1187
1218
|
# an accepted send writes exactly one .msg; plus fact gathering from record/capability/marker.
|
|
1188
|
-
|
|
1219
|
+
run_ts scripts/check-entwurf-mailbox-guard.ts
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
check_native_push_adapter() {
|
|
1223
|
+
# Deterministic gate for the native-push adapter LEAF (봉인 3/8). Drives
|
|
1224
|
+
# createAntigravityAdapter with a FAKE runner (no real agy/ss/pgrep). Asserts: FULL pid
|
|
1225
|
+
# scan (only the 2nd host pid serves the conv → probe still finds the route; raw-agy-send
|
|
1226
|
+
# head -1 corrected); dead (no host) vs indeterminate (host alive, no LS port served the
|
|
1227
|
+
# conv, never coerced to dead); VOLATILE route / no cache (a repeated probe re-discovers a
|
|
1228
|
+
# CHANGED route); send argv === [binary,agentapi,send-message,conv,body] with
|
|
1229
|
+
# ANTIGRAVITY_LS_ADDRESS env, non-zero exit THROWS; NO retry in the adapter (single send,
|
|
1230
|
+
# no re-probe — retry is the executor hand's job, step ⑥); resolveNativePushAdapter fail-fast.
|
|
1231
|
+
run_ts scripts/check-native-push-adapter.ts
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
check_native_push_register() {
|
|
1235
|
+
# Deterministic gate for 봉인 5: registerNativeConversation (the core of the
|
|
1236
|
+
# entwurf_register_native MCP tool). Drives it with a FAKE adapter + an ISOLATED mkdtemp
|
|
1237
|
+
# store (never the real ~/.pi). Asserts: live probe -> CREATE (record carries backend/
|
|
1238
|
+
# nativeSessionId/caller-cwd); re-register -> ATTACH (SAME garden id, cwd refreshed, ONE
|
|
1239
|
+
# record, no duplicate mint); dead/indeterminate probe -> REFUSE (throws, NO record written);
|
|
1240
|
+
# RECEIVER-MARKER ABSTINENCE (보정①) — the register source references no receiver-marker
|
|
1241
|
+
# writer (writeMetaReceiverMarker / armProvenance / META_RECEIVER_ARM_PROVENANCES).
|
|
1242
|
+
run_ts scripts/check-native-push-register.ts
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
check_agy_sender_identity() {
|
|
1246
|
+
# Deterministic gate for the #46 sender-identity lane — WHO is calling the bridge.
|
|
1247
|
+
# A birthed agy conversation could already CALL entwurf_v2 for real, yet its message
|
|
1248
|
+
# landed as external-mcp/unknown-host (non-replyable): the hook wrote only the
|
|
1249
|
+
# meta-record, and the bridge's resolver looked markers up under `claude-code` alone.
|
|
1250
|
+
# Behavioral, not source-regex: the real hook runs as a child process (so the marker's
|
|
1251
|
+
# ownerPid is the gate's own pid — the same parent-pid join production performs), and
|
|
1252
|
+
# the resolver runs against isolated marker/record stores.
|
|
1253
|
+
# Rows: hook writes an antigravity marker keyed by its PARENT pid; an upsert failure
|
|
1254
|
+
# writes NO marker (record authority first); resolver 0→null, 1→identity on EITHER
|
|
1255
|
+
# backend, no-record/drifted marker→null, two distinct live identities on one owner pid
|
|
1256
|
+
# →THROW (never guess, never downgrade to anonymous), two markers naming the SAME
|
|
1257
|
+
# identity→not a conflict; antigravity is native-push so its replyable comes from the
|
|
1258
|
+
# adapter probe, never from a mailbox watch it can never arm (보정①).
|
|
1259
|
+
run_ts scripts/check-agy-sender-identity.ts
|
|
1189
1260
|
}
|
|
1190
1261
|
|
|
1191
1262
|
|
|
@@ -1200,7 +1271,7 @@ check_package_source_routing() {
|
|
|
1200
1271
|
# plus self-root fallback and the resume unresolvedAcpIntent signal. Isolated
|
|
1201
1272
|
# via a temp PI_CODING_AGENT_DIR — the real ~/.pi/agent is never touched. No
|
|
1202
1273
|
# backend, no spawn, no API cost.
|
|
1203
|
-
|
|
1274
|
+
run_ts scripts/check-package-source-routing.ts
|
|
1204
1275
|
}
|
|
1205
1276
|
|
|
1206
1277
|
|
|
@@ -1216,7 +1287,7 @@ smoke_session_id_name() {
|
|
|
1216
1287
|
fail "[smoke-session-id-name] pi binary not on PATH — cannot run live substrate proof"
|
|
1217
1288
|
return 1
|
|
1218
1289
|
fi
|
|
1219
|
-
|
|
1290
|
+
run_ts scripts/smoke-session-id-name.ts || {
|
|
1220
1291
|
fail "[smoke-session-id-name] live substrate smoke failed"
|
|
1221
1292
|
return 1
|
|
1222
1293
|
}
|
|
@@ -1469,6 +1540,87 @@ check_install_preflight() {
|
|
|
1469
1540
|
fi
|
|
1470
1541
|
rm -rf "$fake" "$proj"
|
|
1471
1542
|
ok "[check-install-preflight] representative dangling symlink (test -d blind spot) → fails before writing settings, names the dep"
|
|
1543
|
+
|
|
1544
|
+
# negative 3 — corrupt project provider shape. install_local_package now has a
|
|
1545
|
+
# two-step writer (packages[] SSOT, then entwurfProvider.mcpServers), so a
|
|
1546
|
+
# malformed provider MUST fail before the packages[] step writes anything.
|
|
1547
|
+
fake=$(mktemp -d); proj=$(mktemp -d)
|
|
1548
|
+
mkdir -p "$proj/.pi" "$fake/home"
|
|
1549
|
+
printf '{"entwurfProvider": []}\n' > "$proj/.pi/settings.json"
|
|
1550
|
+
local before after
|
|
1551
|
+
before=$(sha256sum "$proj/.pi/settings.json" | cut -d' ' -f1)
|
|
1552
|
+
rc=0; out=$(HOME="$fake/home" PI_CODING_AGENT_DIR="$fake/home/.pi/agent" "$REPO_DIR/run.sh" install "$proj" 2>&1) || rc=$?
|
|
1553
|
+
after=$(sha256sum "$proj/.pi/settings.json" | cut -d' ' -f1)
|
|
1554
|
+
if [ "$rc" -eq 0 ]; then
|
|
1555
|
+
fail "[check-install-preflight] corrupt entwurfProvider did NOT fail install"; echo "$out"; rm -rf "$fake" "$proj"; exit 1
|
|
1556
|
+
fi
|
|
1557
|
+
if [ "$before" != "$after" ]; then
|
|
1558
|
+
fail "[check-install-preflight] corrupt entwurfProvider was partially rewritten (packages[] leak)"; echo "$out"; cat "$proj/.pi/settings.json"; rm -rf "$fake" "$proj"; exit 1
|
|
1559
|
+
fi
|
|
1560
|
+
if ! printf '%s' "$out" | grep -q "entwurfProvider is not an object"; then
|
|
1561
|
+
fail "[check-install-preflight] corrupt entwurfProvider failed for the WRONG reason:"; echo "$out"; rm -rf "$fake" "$proj"; exit 1
|
|
1562
|
+
fi
|
|
1563
|
+
if [ -f "$fake/home/.pi/agent/settings.json" ]; then
|
|
1564
|
+
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
|
|
1565
|
+
fi
|
|
1566
|
+
rm -rf "$fake" "$proj"
|
|
1567
|
+
ok "[check-install-preflight] corrupt project entwurfProvider → fails before any project/user settings write"
|
|
1568
|
+
|
|
1569
|
+
# negative 4 — corrupt project mcpServers shape, same no-partial-write contract.
|
|
1570
|
+
fake=$(mktemp -d); proj=$(mktemp -d)
|
|
1571
|
+
mkdir -p "$proj/.pi" "$fake/home"
|
|
1572
|
+
printf '{"entwurfProvider": {"mcpServers": []}}\n' > "$proj/.pi/settings.json"
|
|
1573
|
+
before=$(sha256sum "$proj/.pi/settings.json" | cut -d' ' -f1)
|
|
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
|
+
after=$(sha256sum "$proj/.pi/settings.json" | cut -d' ' -f1)
|
|
1576
|
+
if [ "$rc" -eq 0 ]; then
|
|
1577
|
+
fail "[check-install-preflight] corrupt entwurfProvider.mcpServers did NOT fail install"; echo "$out"; rm -rf "$fake" "$proj"; exit 1
|
|
1578
|
+
fi
|
|
1579
|
+
if [ "$before" != "$after" ]; then
|
|
1580
|
+
fail "[check-install-preflight] corrupt mcpServers was partially rewritten (packages[] leak)"; echo "$out"; cat "$proj/.pi/settings.json"; rm -rf "$fake" "$proj"; exit 1
|
|
1581
|
+
fi
|
|
1582
|
+
if ! printf '%s' "$out" | grep -q "entwurfProvider.mcpServers is not an object"; then
|
|
1583
|
+
fail "[check-install-preflight] corrupt mcpServers failed for the WRONG reason:"; echo "$out"; rm -rf "$fake" "$proj"; exit 1
|
|
1584
|
+
fi
|
|
1585
|
+
rm -rf "$fake" "$proj"
|
|
1586
|
+
ok "[check-install-preflight] corrupt project entwurfProvider.mcpServers → fails before any project settings write"
|
|
1587
|
+
|
|
1588
|
+
# negative 5 — corrupt user-scope packages shape. Since install writes project
|
|
1589
|
+
# settings before user registration, this preflight must catch the user file up
|
|
1590
|
+
# front so a bad ~/.pi/agent/settings.json cannot leave the project half-wired.
|
|
1591
|
+
fake=$(mktemp -d); proj=$(mktemp -d)
|
|
1592
|
+
mkdir -p "$fake/home/.pi/agent"
|
|
1593
|
+
printf '{"packages": {"broken": true}}\n' > "$fake/home/.pi/agent/settings.json"
|
|
1594
|
+
rc=0; out=$(HOME="$fake/home" PI_CODING_AGENT_DIR="$fake/home/.pi/agent" "$REPO_DIR/run.sh" install "$proj" 2>&1) || rc=$?
|
|
1595
|
+
if [ "$rc" -eq 0 ]; then
|
|
1596
|
+
fail "[check-install-preflight] corrupt user packages did NOT fail install"; echo "$out"; rm -rf "$fake" "$proj"; exit 1
|
|
1597
|
+
fi
|
|
1598
|
+
if [ -f "$proj/.pi/settings.json" ]; then
|
|
1599
|
+
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
|
|
1600
|
+
fi
|
|
1601
|
+
if ! printf '%s' "$out" | grep -q "user settings packages is not a JSON array"; then
|
|
1602
|
+
fail "[check-install-preflight] corrupt user packages failed for the WRONG reason:"; echo "$out"; rm -rf "$fake" "$proj"; exit 1
|
|
1603
|
+
fi
|
|
1604
|
+
rm -rf "$fake" "$proj"
|
|
1605
|
+
ok "[check-install-preflight] corrupt user-scope packages → fails before project settings write"
|
|
1606
|
+
|
|
1607
|
+
# negative 6 — remove has the same two-step write risk (packages[] remove,
|
|
1608
|
+
# then provider cleanup). A corrupt provider must fail before packages[] is
|
|
1609
|
+
# altered, otherwise uninstall becomes a partial destructive write.
|
|
1610
|
+
proj=$(mktemp -d)
|
|
1611
|
+
mkdir -p "$proj/.pi"
|
|
1612
|
+
printf '{"entwurfProvider": [], "packages": ["%s"]}\n' "$REPO_DIR" > "$proj/.pi/settings.json"
|
|
1613
|
+
before=$(sha256sum "$proj/.pi/settings.json" | cut -d' ' -f1)
|
|
1614
|
+
rc=0; out=$("$REPO_DIR/run.sh" remove "$proj" 2>&1) || rc=$?
|
|
1615
|
+
after=$(sha256sum "$proj/.pi/settings.json" | cut -d' ' -f1)
|
|
1616
|
+
if [ "$rc" -eq 0 ]; then
|
|
1617
|
+
fail "[check-install-preflight] corrupt provider did NOT fail remove"; echo "$out"; rm -rf "$proj"; exit 1
|
|
1618
|
+
fi
|
|
1619
|
+
if [ "$before" != "$after" ]; then
|
|
1620
|
+
fail "[check-install-preflight] corrupt provider remove partially rewrote packages[]"; echo "$out"; cat "$proj/.pi/settings.json"; rm -rf "$proj"; exit 1
|
|
1621
|
+
fi
|
|
1622
|
+
rm -rf "$proj"
|
|
1623
|
+
ok "[check-install-preflight] remove with corrupt provider → fails before packages[] removal"
|
|
1472
1624
|
}
|
|
1473
1625
|
|
|
1474
1626
|
check_pi_preflight() {
|
|
@@ -1476,7 +1628,7 @@ check_pi_preflight() {
|
|
|
1476
1628
|
# decision 8 precedence (saved false > saved true > prefix > no-inputs >
|
|
1477
1629
|
# fail-fast) and decision 7's separator-boundary prefix against pi's own
|
|
1478
1630
|
# ProjectTrustStore in a temp agentDir. Deterministic, no network/backend.
|
|
1479
|
-
|
|
1631
|
+
run_ts scripts/check-pi-preflight.ts
|
|
1480
1632
|
}
|
|
1481
1633
|
|
|
1482
1634
|
|
|
@@ -1531,7 +1683,7 @@ check_acp_provider_surface() {
|
|
|
1531
1683
|
# sentinel shape, and a FAIL-LOUD streamSimple (calling it throws — no native
|
|
1532
1684
|
# fallback, no empty-but-successful stream). Pure, no pi runtime, no API.
|
|
1533
1685
|
section "ACP provider surface (S0 loader/fence)"
|
|
1534
|
-
|
|
1686
|
+
run_ts scripts/check-acp-provider-surface.ts
|
|
1535
1687
|
}
|
|
1536
1688
|
|
|
1537
1689
|
check_acp_sdk_surface() {
|
|
@@ -1543,7 +1695,7 @@ check_acp_sdk_surface() {
|
|
|
1543
1695
|
# turn needs (silent-rename gate), and forbids any source-level anthropic SDK
|
|
1544
1696
|
# import / API-client use (the anthropic dep is a peer-pin ONLY).
|
|
1545
1697
|
section "ACP SDK surface (S2a dep pin + peer-resolution + no-client-use)"
|
|
1546
|
-
|
|
1698
|
+
run_ts scripts/check-acp-sdk-surface.ts
|
|
1547
1699
|
}
|
|
1548
1700
|
|
|
1549
1701
|
check_acp_overlay() {
|
|
@@ -1556,7 +1708,7 @@ check_acp_overlay() {
|
|
|
1556
1708
|
# stale symlinks cleaned; binary-owned files preserved; CLAUDE_CONFIG_DIR
|
|
1557
1709
|
# launch-env planted; idempotent. Pure, no live model.
|
|
1558
1710
|
section "ACP overlay (S2b claude-config-overlay)"
|
|
1559
|
-
|
|
1711
|
+
run_ts scripts/check-acp-overlay.ts
|
|
1560
1712
|
}
|
|
1561
1713
|
|
|
1562
1714
|
check_acp_tool_surface() {
|
|
@@ -1567,7 +1719,7 @@ check_acp_tool_surface() {
|
|
|
1567
1719
|
# extraArgs/plugins) + the S2b billing-carrier guard (no _meta.systemPrompt
|
|
1568
1720
|
# unless a caller supplies one). Pure preflight — NOT a backend wire read.
|
|
1569
1721
|
section "ACP tool surface (S2b exclude-tools preflight + session meta)"
|
|
1570
|
-
|
|
1722
|
+
run_ts scripts/check-acp-tool-surface.ts
|
|
1571
1723
|
}
|
|
1572
1724
|
|
|
1573
1725
|
check_acp_event_mapper() {
|
|
@@ -1579,7 +1731,7 @@ check_acp_event_mapper() {
|
|
|
1579
1731
|
# context→ACP-prompt transcript passthrough (excludes systemPrompt/thinking,
|
|
1580
1732
|
# single text block). Pure, no live backend.
|
|
1581
1733
|
section "ACP event mapper (S2c notification→stream + context)"
|
|
1582
|
-
|
|
1734
|
+
run_ts scripts/check-acp-event-mapper.ts
|
|
1583
1735
|
}
|
|
1584
1736
|
|
|
1585
1737
|
check_acp_prompt_builder() {
|
|
@@ -1590,7 +1742,7 @@ check_acp_prompt_builder() {
|
|
|
1590
1742
|
# excluded so a reuse session is not re-injected its own history). Pure, no
|
|
1591
1743
|
# session store yet — locks the builder before S2d wires the reuse paths.
|
|
1592
1744
|
section "ACP prompt builder (S2d bootstrapPath prompt scope)"
|
|
1593
|
-
|
|
1745
|
+
run_ts scripts/check-acp-prompt-builder.ts
|
|
1594
1746
|
}
|
|
1595
1747
|
|
|
1596
1748
|
check_acp_config() {
|
|
@@ -1603,7 +1755,7 @@ check_acp_config() {
|
|
|
1603
1755
|
# headers, and envelope enrich (PI_SESSION_ID/PI_AGENT_ID into entwurf-bridge
|
|
1604
1756
|
# only, stale filtered, post-hash). Pure + temp-dir settings I/O, no child/spawn.
|
|
1605
1757
|
section "ACP provider config (S2g operator mcpServers/skillPlugins/tools passthrough)"
|
|
1606
|
-
|
|
1758
|
+
run_ts scripts/check-acp-config.ts
|
|
1607
1759
|
}
|
|
1608
1760
|
|
|
1609
1761
|
check_acp_session_store() {
|
|
@@ -1614,7 +1766,7 @@ check_acp_session_store() {
|
|
|
1614
1766
|
# (turn-scoped/-p one-shot is ALWAYS new — no in-memory reuse, no persisted
|
|
1615
1767
|
# resume/load in the first cut). Pure + temp-dir record I/O, no child/spawn.
|
|
1616
1768
|
section "ACP session store (S2d-1b-1 signature/compat/bootstrap decision)"
|
|
1617
|
-
|
|
1769
|
+
run_ts scripts/check-acp-session-store.ts
|
|
1618
1770
|
}
|
|
1619
1771
|
|
|
1620
1772
|
check_acp_backend_preflight() {
|
|
@@ -1625,7 +1777,7 @@ check_acp_backend_preflight() {
|
|
|
1625
1777
|
# assertExcludeToolsHonored is wired into the live provider path, not just the
|
|
1626
1778
|
# pure gate. No backend launched (preflight throws first). Pure.
|
|
1627
1779
|
section "ACP backend preflight (S2c runtime exclude-tools wiring)"
|
|
1628
|
-
|
|
1780
|
+
run_ts scripts/check-acp-backend-preflight.ts
|
|
1629
1781
|
}
|
|
1630
1782
|
|
|
1631
1783
|
check_acp_session_reuse() {
|
|
@@ -1638,7 +1790,7 @@ check_acp_session_reuse() {
|
|
|
1638
1790
|
# never torn down between turns, and source-locks buildAcpPrompt wiring +
|
|
1639
1791
|
# single-site applyAcpSessionUpdate via the router. No real child launched.
|
|
1640
1792
|
section "ACP session reuse (S2d-1b-2b delta-only capture + mutable routing)"
|
|
1641
|
-
|
|
1793
|
+
run_ts scripts/check-acp-session-reuse.ts
|
|
1642
1794
|
}
|
|
1643
1795
|
|
|
1644
1796
|
check_acp_carrier_augment() {
|
|
@@ -1650,7 +1802,7 @@ check_acp_carrier_augment() {
|
|
|
1650
1802
|
# so it never enters contextMessageSignatures — with entwurf cwd/AGENTS.md
|
|
1651
1803
|
# de-dup. Pure + temp-dir fs, no spawn.
|
|
1652
1804
|
section "ACP carrier + augment (S2d-1c engraving + first-user augment)"
|
|
1653
|
-
|
|
1805
|
+
run_ts scripts/check-acp-carrier-augment.ts
|
|
1654
1806
|
}
|
|
1655
1807
|
|
|
1656
1808
|
check_pack() {
|
|
@@ -1676,7 +1828,11 @@ check_pack() {
|
|
|
1676
1828
|
# npm's own lifecycle banner stay off stdout; otherwise they pollute the --json
|
|
1677
1829
|
# payload this parses. prepack runs on dry-run too, which is how dist lands in
|
|
1678
1830
|
# this gate's file list.
|
|
1679
|
-
|
|
1831
|
+
# with-dist-lock wraps the WHOLE pack (prepack build-bridge emit + npm's
|
|
1832
|
+
# post-build dist read) so a concurrent pack/build can't `rm -rf dist` mid-read
|
|
1833
|
+
# (the 2026-07-03 phantom "dist missing" race). The nested prepack build-bridge
|
|
1834
|
+
# is reentrant via ENTWURF_BUILD_LOCK_HELD.
|
|
1835
|
+
json=$(cd "$REPO_DIR" && bash scripts/with-dist-lock.sh npm pack --dry-run --json --silent 2>/dev/null) || {
|
|
1680
1836
|
fail "[check-pack] npm pack --dry-run failed"
|
|
1681
1837
|
return 1
|
|
1682
1838
|
}
|
|
@@ -1735,6 +1891,14 @@ check_pack() {
|
|
|
1735
1891
|
# doctor.sh runs this prebuilt JS when installed under node_modules (strip-types
|
|
1736
1892
|
# refuses the .ts there), same boundary start.sh crosses. build-bridge emits it.
|
|
1737
1893
|
"mcp/entwurf-bridge/dist/scripts/meta-bridge-store-doctor.js"
|
|
1894
|
+
# 0.12.7 — node_modules-safe agy PreInvocation hook. The stable npm bin
|
|
1895
|
+
# dispatches here because Node refuses raw .ts below node_modules.
|
|
1896
|
+
"mcp/entwurf-bridge/dist/scripts/agy-imprint.js"
|
|
1897
|
+
# 0.12.7 — the three OPERATOR commands run.sh dispatches. Installed, REPO_DIR is
|
|
1898
|
+
# under node_modules, so run_ts must find a compiled twin or the command is dead.
|
|
1899
|
+
"mcp/entwurf-bridge/dist/scripts/doctor-pi-provider.js"
|
|
1900
|
+
"mcp/entwurf-bridge/dist/scripts/new-session-id.js"
|
|
1901
|
+
"mcp/entwurf-bridge/dist/scripts/meta-bridge-prune.js"
|
|
1738
1902
|
# 0.12.5 — the node_modules-safe plugin hook + its lib. install-meta-bridge copies
|
|
1739
1903
|
# these compiled JS into the assembled plugin when installed (raw .ts can't
|
|
1740
1904
|
# strip-types under node_modules). meta-session.js is shared with the store-doctor
|
|
@@ -1772,6 +1936,13 @@ check_pack() {
|
|
|
1772
1936
|
'\.tmp-verify/'
|
|
1773
1937
|
'\.agent-(reports|shell)/'
|
|
1774
1938
|
'pi/meta-bridge/\.assembled/'
|
|
1939
|
+
# Python bytecode residue — `scripts/` ships whole via the files allowlist,
|
|
1940
|
+
# which BYPASSES .gitignore/.npmignore for its contents, so a `pnpm check`
|
|
1941
|
+
# run's generated scripts/__pycache__/*.pyc rode into the 0.12.6 tarball. The
|
|
1942
|
+
# files-array `!**/__pycache__` / `!**/*.pyc` negations exclude it; this is the
|
|
1943
|
+
# tripwire that fails loud if that negation is ever dropped (결합 규칙).
|
|
1944
|
+
'__pycache__'
|
|
1945
|
+
'\.pyc$'
|
|
1775
1946
|
)
|
|
1776
1947
|
|
|
1777
1948
|
local pass=1 f pat hit
|
|
@@ -1804,7 +1975,7 @@ check_pack() {
|
|
|
1804
1975
|
return 1
|
|
1805
1976
|
}
|
|
1806
1977
|
|
|
1807
|
-
|
|
1978
|
+
_check_pack_install_impl() {
|
|
1808
1979
|
# Heavy publish gate. Runs the remaining three checks in #13's
|
|
1809
1980
|
# publish checklist that check_pack (dry-run only) does not cover:
|
|
1810
1981
|
#
|
|
@@ -1833,8 +2004,15 @@ check_pack_install() {
|
|
|
1833
2004
|
# that lands as `junghanacs-entwurf-<version>.tgz`. Hardcoded against
|
|
1834
2005
|
# the scope above so a name change cannot silently slide past this gate.
|
|
1835
2006
|
tgz_name="junghanacs-entwurf-${version}.tgz"
|
|
1836
|
-
|
|
1837
|
-
|
|
2007
|
+
# Pack into a UNIQUE per-run dir, never the repo root. Writing/removing a fixed
|
|
2008
|
+
# ${REPO_DIR}/<tgz> pollutes the working tree (a crash leaves a stray tarball) AND
|
|
2009
|
+
# lets two concurrent packs corrupt/delete each other's artifact ("tarball data
|
|
2010
|
+
# seems corrupted" / ENOENT; a half-installed package then fails the later
|
|
2011
|
+
# install-meta-bridge check downstream). A per-run pack dir makes the tarball
|
|
2012
|
+
# instance-private end-to-end — every consumer below + the trap follow $pack_tmp.
|
|
2013
|
+
local pack_tmp
|
|
2014
|
+
pack_tmp=$(mktemp -d -t entwurf-pack.XXXXXX)
|
|
2015
|
+
tgz_path="${pack_tmp}/${tgz_name}"
|
|
1838
2016
|
|
|
1839
2017
|
# 0.12.1 C — stale-dist guard. `tsc` emit does NOT prune orphaned files from
|
|
1840
2018
|
# outDir, and `files: ["mcp/"]` would carry any leftover dist file into the
|
|
@@ -1846,7 +2024,11 @@ check_pack_install() {
|
|
|
1846
2024
|
printf 'module.exports = "stale";\n' > "$stale_probe"
|
|
1847
2025
|
|
|
1848
2026
|
echo "[check-pack-install] npm pack -> ${tgz_name}"
|
|
1849
|
-
|
|
2027
|
+
# with-dist-lock: same whole-pack serialization as check-pack — this heavy gate
|
|
2028
|
+
# and a background check-pack (via `pnpm check`) both pack, and unserialized they
|
|
2029
|
+
# race the shared dist dir. The stale-dist sentinel planted just above still
|
|
2030
|
+
# proves build-bridge's own `rm -rf dist` clean step under the lock.
|
|
2031
|
+
(cd "$REPO_DIR" && bash scripts/with-dist-lock.sh npm pack --dry-run=false --pack-destination "$pack_tmp" 2>&1 | tail -1) || {
|
|
1850
2032
|
fail "[check-pack-install] npm pack failed"
|
|
1851
2033
|
return 1
|
|
1852
2034
|
}
|
|
@@ -1867,7 +2049,7 @@ check_pack_install() {
|
|
|
1867
2049
|
# If it did, build-bridge's `rm -rf dist` clean step regressed and stale/orphan
|
|
1868
2050
|
# emit can ship. (See the plant just before npm pack above.)
|
|
1869
2051
|
if grep -qxF "mcp/entwurf-bridge/dist/__stale_probe__.js" <<<"$tar_files"; then
|
|
1870
|
-
rm -
|
|
2052
|
+
rm -rf "$pack_tmp"
|
|
1871
2053
|
fail "[check-pack-install] stale dist file shipped — build-bridge did not clean dist before emit (orphan-emit publish risk)"
|
|
1872
2054
|
return 1
|
|
1873
2055
|
fi
|
|
@@ -1893,6 +2075,14 @@ check_pack_install() {
|
|
|
1893
2075
|
# 0.12.4 — prebuilt node_modules-safe store-scan artifact for the doctor
|
|
1894
2076
|
# (see check-pack). The installed-scan smoke below runs exactly this file.
|
|
1895
2077
|
"mcp/entwurf-bridge/dist/scripts/meta-bridge-store-doctor.js"
|
|
2078
|
+
# 0.12.7 — node_modules-safe agy PreInvocation hook. The installed bin smoke
|
|
2079
|
+
# below executes this exact compiled leaf from under node_modules.
|
|
2080
|
+
"mcp/entwurf-bridge/dist/scripts/agy-imprint.js"
|
|
2081
|
+
# 0.12.7 — the three operator commands (see check-pack). The installed-command
|
|
2082
|
+
# regression below drives each one through the real `entwurf` bin.
|
|
2083
|
+
"mcp/entwurf-bridge/dist/scripts/doctor-pi-provider.js"
|
|
2084
|
+
"mcp/entwurf-bridge/dist/scripts/new-session-id.js"
|
|
2085
|
+
"mcp/entwurf-bridge/dist/scripts/meta-bridge-prune.js"
|
|
1896
2086
|
# 0.12.5 — node_modules-safe plugin hook + lib (see check-pack). The installed
|
|
1897
2087
|
# hook regression below runs exactly this compiled JS from under node_modules.
|
|
1898
2088
|
"mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js"
|
|
@@ -1924,6 +2114,10 @@ check_pack_install() {
|
|
|
1924
2114
|
'^plugins/' '^node_modules/'
|
|
1925
2115
|
'\.tmp-verify/' '\.agent-(reports|shell)/'
|
|
1926
2116
|
'pi/meta-bridge/\.assembled/'
|
|
2117
|
+
# Python bytecode residue (see check-pack forbidden note): scripts/ ships
|
|
2118
|
+
# whole, so generated pyc bypasses ignore files — this cross-checks the actual
|
|
2119
|
+
# tarball, not just the dry-run resolver.
|
|
2120
|
+
'__pycache__' '\.pyc$'
|
|
1927
2121
|
)
|
|
1928
2122
|
for pat in "${tar_forbidden[@]}"; do
|
|
1929
2123
|
local hit
|
|
@@ -1936,7 +2130,7 @@ check_pack_install() {
|
|
|
1936
2130
|
done
|
|
1937
2131
|
|
|
1938
2132
|
if [ "$pass" != "1" ]; then
|
|
1939
|
-
rm -
|
|
2133
|
+
rm -rf "$pack_tmp"
|
|
1940
2134
|
fail "[check-pack-install] tar -tf invariants violated"
|
|
1941
2135
|
return 1
|
|
1942
2136
|
fi
|
|
@@ -1955,7 +2149,7 @@ check_pack_install() {
|
|
|
1955
2149
|
# nested under $tmp, whose pnpm-add node_modules/package.json would make npm
|
|
1956
2150
|
# climb the parent and choke ("Cannot read properties of null").
|
|
1957
2151
|
npm_tmp=$(mktemp -d -t entwurf-npm-managed.XXXXXX)
|
|
1958
|
-
trap 'rm -rf "$tmp" "$npm_tmp" "$
|
|
2152
|
+
trap 'rm -rf "$tmp" "$npm_tmp" "$pack_tmp"' RETURN
|
|
1959
2153
|
|
|
1960
2154
|
printf '%s\n' '{ "name": "entwurf-install-smoke", "version": "0.0.0", "private": true }' > "$tmp/package.json"
|
|
1961
2155
|
|
|
@@ -2002,8 +2196,14 @@ check_pack_install() {
|
|
|
2002
2196
|
return 1
|
|
2003
2197
|
fi
|
|
2004
2198
|
|
|
2005
|
-
|
|
2006
|
-
|
|
2199
|
+
# HOME/PI_CODING_AGENT_DIR redirected to a throwaway dir: this loader smoke must
|
|
2200
|
+
# depend ONLY on the -e package path, never on the operator's real
|
|
2201
|
+
# ~/.pi/agent/settings.json — otherwise a live-config change could silently
|
|
2202
|
+
# pass/fail the gate (the exact "check must not depend on live wiring" impurity
|
|
2203
|
+
# this whole lane is about).
|
|
2204
|
+
local loader_out loader_home="$tmp/loader-home"
|
|
2205
|
+
mkdir -p "$loader_home/.pi/agent"
|
|
2206
|
+
loader_out=$(cd "$tmp" && HOME="$loader_home" XDG_DATA_HOME="$loader_home/.local/share" XDG_STATE_HOME="$loader_home/.local/state" XDG_CACHE_HOME="$loader_home/.cache" PI_CODING_AGENT_DIR="$loader_home/.pi/agent" pi -e "$tmp/node_modules/@junghanacs/entwurf" --list-models entwurf 2>&1) || {
|
|
2007
2207
|
fail "[check-pack-install] pi loader smoke failed (exit non-zero):"
|
|
2008
2208
|
echo "$loader_out" | tail -10 | sed 's/^/ /' >&2
|
|
2009
2209
|
return 1
|
|
@@ -2053,12 +2253,12 @@ check_pack_install() {
|
|
|
2053
2253
|
fail "[check-pack-install] npm-managed layout missing executable run.sh (postinstall-chmod did not run?) at $npm_pkg"
|
|
2054
2254
|
return 1
|
|
2055
2255
|
fi
|
|
2056
|
-
if [ ! -x "$npmroot/node_modules/.bin/entwurf" ] || [ ! -x "$npmroot/node_modules/.bin/entwurf-bridge" ] || [ ! -x "$npmroot/node_modules/.bin/entwurf-statusline" ]; then
|
|
2057
|
-
fail "[check-pack-install] npm-managed neutral install missing package bins (entwurf / entwurf-bridge / entwurf-statusline)"
|
|
2256
|
+
if [ ! -x "$npmroot/node_modules/.bin/entwurf" ] || [ ! -x "$npmroot/node_modules/.bin/entwurf-bridge" ] || [ ! -x "$npmroot/node_modules/.bin/entwurf-statusline" ] || [ ! -x "$npmroot/node_modules/.bin/entwurf-agy-statusline" ] || [ ! -x "$npmroot/node_modules/.bin/entwurf-agy-imprint" ]; then
|
|
2257
|
+
fail "[check-pack-install] npm-managed neutral install missing package bins (entwurf / entwurf-bridge / entwurf-statusline / entwurf-agy-statusline / entwurf-agy-imprint)"
|
|
2058
2258
|
ls -l "$npmroot/node_modules/.bin" 2>/dev/null | sed 's/^/ /' >&2 || true
|
|
2059
2259
|
return 1
|
|
2060
2260
|
fi
|
|
2061
|
-
wire_log=$(HOME="$npmhome" "$npm_pkg/run.sh" install "$npmproj" 2>&1) || {
|
|
2261
|
+
wire_log=$(HOME="$npmhome" XDG_DATA_HOME="$npmhome/.local/share" XDG_STATE_HOME="$npmhome/.local/state" XDG_CACHE_HOME="$npmhome/.cache" "$npm_pkg/run.sh" install "$npmproj" 2>&1) || {
|
|
2062
2262
|
fail "[check-pack-install] npm-managed run.sh install failed (preflight rejected hoisted deps?):"
|
|
2063
2263
|
echo "$wire_log" | tail -15 | sed 's/^/ /' >&2
|
|
2064
2264
|
return 1
|
|
@@ -2075,6 +2275,43 @@ check_pack_install() {
|
|
|
2075
2275
|
fi
|
|
2076
2276
|
echo "[check-pack-install] npm-managed install regression pass (hoisted-dep run.sh install wrote settings)"
|
|
2077
2277
|
|
|
2278
|
+
# THE 2026-07-03 regression gate: removing `pi install` from setup dropped
|
|
2279
|
+
# user-scope citizen registration, so `--entwurf-control` was Unknown in a
|
|
2280
|
+
# foreign cwd. Prove the npm consumer path closes it end-to-end: run.sh install
|
|
2281
|
+
# (run above under HOME="$npmhome") must have registered the package in the USER
|
|
2282
|
+
# settings, and pi must then load the entwurf extension + its flags from a cwd
|
|
2283
|
+
# OUTSIDE the project. Fully isolated in the temp HOME — never touches ~/.pi.
|
|
2284
|
+
local npm_user_settings="$npmhome/.pi/agent/settings.json"
|
|
2285
|
+
if [ ! -f "$npm_user_settings" ]; then
|
|
2286
|
+
fail "[check-pack-install] npm-managed install did not register a user-scope citizen at $npm_user_settings"
|
|
2287
|
+
return 1
|
|
2288
|
+
fi
|
|
2289
|
+
if ! python3 -c "
|
|
2290
|
+
import json,sys
|
|
2291
|
+
p=json.load(open('$npm_user_settings')).get('packages',[])
|
|
2292
|
+
srcs=[(x if isinstance(x,str) else x.get('source')) for x in p]
|
|
2293
|
+
sys.exit(0 if any(isinstance(s,str) and s.endswith('/node_modules/@junghanacs/entwurf') for s in srcs) else 1)
|
|
2294
|
+
"; then
|
|
2295
|
+
fail "[check-pack-install] user-scope settings.json lacks the npm entwurf package in packages[]:"
|
|
2296
|
+
sed 's/^/ /' "$npm_user_settings" >&2
|
|
2297
|
+
return 1
|
|
2298
|
+
fi
|
|
2299
|
+
# Foreign cwd ($tmp — NOT the project, no project .pi): --entwurf-control must be
|
|
2300
|
+
# a KNOWN flag and the entwurf provider must load, sourced only from user scope.
|
|
2301
|
+
# Before the fix this printed "Unknown options: --entwurf-control".
|
|
2302
|
+
local foreign_out
|
|
2303
|
+
foreign_out=$(cd "$tmp" && HOME="$npmhome" XDG_DATA_HOME="$npmhome/.local/share" XDG_STATE_HOME="$npmhome/.local/state" XDG_CACHE_HOME="$npmhome/.cache" PI_CODING_AGENT_DIR="$npmhome/.pi/agent" pi --entwurf-control --list-models entwurf 2>&1) || {
|
|
2304
|
+
fail "[check-pack-install] foreign-cwd --entwurf-control smoke failed (user-scope citizen not loading?):"
|
|
2305
|
+
echo "$foreign_out" | tail -10 | sed 's/^/ /' >&2
|
|
2306
|
+
return 1
|
|
2307
|
+
}
|
|
2308
|
+
if grep -qi "Unknown option" <<<"$foreign_out" || ! grep -q "claude-opus-4-8" <<<"$foreign_out"; then
|
|
2309
|
+
fail "[check-pack-install] foreign-cwd --entwurf-control did not load the entwurf extension from user scope:"
|
|
2310
|
+
echo "$foreign_out" | tail -10 | sed 's/^/ /' >&2
|
|
2311
|
+
return 1
|
|
2312
|
+
fi
|
|
2313
|
+
echo "[check-pack-install] user-scope citizen regression pass (npm consumer: --entwurf-control loads from a foreign cwd)"
|
|
2314
|
+
|
|
2078
2315
|
# Installed meta-bridge ownership regression (0.12.5): package upgrades must not
|
|
2079
2316
|
# bake versioned pnpm-store paths into Claude settings. MCP already uses the
|
|
2080
2317
|
# stable `entwurf-bridge` bin; statusLine must now use `entwurf-statusline`, and
|
|
@@ -2104,7 +2341,7 @@ SH
|
|
|
2104
2341
|
chmod +x "$fake_claude_dir/claude"
|
|
2105
2342
|
local mb_home="$npm_tmp/meta-home" mb_cfg="$npm_tmp/meta-claude" mb_log
|
|
2106
2343
|
mkdir -p "$mb_home" "$mb_cfg"
|
|
2107
|
-
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) || {
|
|
2344
|
+
mb_log=$(HOME="$mb_home" XDG_DATA_HOME="$mb_home/.local/share" XDG_STATE_HOME="$mb_home/.local/state" XDG_CACHE_HOME="$mb_home/.cache" 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) || {
|
|
2108
2345
|
fail "[check-pack-install] installed install-meta-bridge failed under fake claude:"
|
|
2109
2346
|
echo "$mb_log" | tail -20 | sed 's/^/ /' >&2
|
|
2110
2347
|
return 1
|
|
@@ -2239,7 +2476,7 @@ function finish(trimmed) {
|
|
|
2239
2476
|
try { msg = JSON.parse(trimmed); }
|
|
2240
2477
|
catch { console.error('unparseable tools/list:', trimmed.slice(0, 300)); if (stderr.trim()) console.error(stderr.trim()); process.exit(1); }
|
|
2241
2478
|
const names = (msg?.result?.tools ?? []).map((t) => t?.name).sort();
|
|
2242
|
-
for (const need of ['entwurf_v2', 'entwurf_peers', 'entwurf_self', 'entwurf_inbox_read']) {
|
|
2479
|
+
for (const need of ['entwurf_v2', 'entwurf_peers', 'entwurf_self', 'entwurf_inbox_read', 'entwurf_register_native']) {
|
|
2243
2480
|
if (!names.includes(need)) { console.error('missing MCP tool from installed boot:', need, '— got', names.join(',')); process.exit(1); }
|
|
2244
2481
|
}
|
|
2245
2482
|
console.log(names.join(','));
|
|
@@ -2259,6 +2496,109 @@ JS
|
|
|
2259
2496
|
fi
|
|
2260
2497
|
echo "[check-pack-install] installed bridge boot pass (dist boots under node_modules, pi-free: $boot_out)"
|
|
2261
2498
|
|
|
2499
|
+
# 0.12.7 — installed AGY IMPRINT regression. The npm bin resolves through a
|
|
2500
|
+
# node_modules symlink; raw scripts/agy-imprint.ts is therefore forbidden by
|
|
2501
|
+
# Node's strip-types fence. Execute the real installed bin with an isolated
|
|
2502
|
+
# agent dir and prove the neutral hook response + one record write. This is the
|
|
2503
|
+
# exact package bug that a dev-only hook smoke cannot see.
|
|
2504
|
+
local installed_agy_imprint="$npmroot/node_modules/.bin/entwurf-agy-imprint"
|
|
2505
|
+
local agy_imprint_agent="$npm_tmp/agy-imprint-agent" agy_imprint_out agy_record_count
|
|
2506
|
+
mkdir -p "$agy_imprint_agent"
|
|
2507
|
+
if ! agy_imprint_out=$(printf '%s\n' '{"conversationId":"pack-install-agy-conversation","workspacePaths":["/tmp/entwurf-pack-install"],"modelName":"probe-model"}' | HOME="$npmhome" XDG_DATA_HOME="$npmhome/.local/share" XDG_STATE_HOME="$npmhome/.local/state" XDG_CACHE_HOME="$npmhome/.cache" PI_CODING_AGENT_DIR="$agy_imprint_agent" "$installed_agy_imprint" 2>&1); then
|
|
2508
|
+
fail "[check-pack-install] installed entwurf-agy-imprint FAILED under node_modules (raw-.ts strip-types regression or emitted hook missing):"
|
|
2509
|
+
echo "$agy_imprint_out" | tail -15 | sed 's/^/ /' >&2
|
|
2510
|
+
return 1
|
|
2511
|
+
fi
|
|
2512
|
+
if [ "$agy_imprint_out" != '{"injectSteps":[]}' ]; then
|
|
2513
|
+
fail "[check-pack-install] installed entwurf-agy-imprint returned a non-neutral hook response: $agy_imprint_out"
|
|
2514
|
+
return 1
|
|
2515
|
+
fi
|
|
2516
|
+
agy_record_count=$(find "$agy_imprint_agent/meta-sessions" -maxdepth 1 -name '*.meta.json' -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
2517
|
+
if [ "$agy_record_count" != "1" ]; then
|
|
2518
|
+
fail "[check-pack-install] installed entwurf-agy-imprint did not write exactly one isolated meta-record (got $agy_record_count)"
|
|
2519
|
+
return 1
|
|
2520
|
+
fi
|
|
2521
|
+
echo "[check-pack-install] installed agy imprint pass (compiled JS runs under node_modules; neutral response + record write)"
|
|
2522
|
+
|
|
2523
|
+
# 0.12.7 — installed OPERATOR COMMAND regression. `entwurf <cmd>` dispatches through
|
|
2524
|
+
# run.sh, whose REPO_DIR is under node_modules once installed, so a raw-.ts entrypoint
|
|
2525
|
+
# dies on the strip-types fence. These three are operator surfaces, not dev gates:
|
|
2526
|
+
# doctor-pi-provider is the pi-ownership verdict, new-session-id is the alias that mints
|
|
2527
|
+
# a garden citizen (docs/setup-clean-host.md tells operators to run the installed bin),
|
|
2528
|
+
# and meta-bridge-prune is the store maintenance verb. All three shipped DEAD through
|
|
2529
|
+
# 0.12.6 because the only installed smokes drove bins, never subcommands.
|
|
2530
|
+
#
|
|
2531
|
+
# Assert MEANING, not just the absence of the fence string: a command that regressed to
|
|
2532
|
+
# a stub or an empty exit would still "not crash". So: the id must be well-formed, the
|
|
2533
|
+
# doctor must reach its own verdict body, and prune must actually walk a 0-record store.
|
|
2534
|
+
local installed_entwurf="$npmroot/node_modules/.bin/entwurf"
|
|
2535
|
+
local op_agent="$npm_tmp/op-agent" op_out
|
|
2536
|
+
mkdir -p "$op_agent/meta-sessions"
|
|
2537
|
+
|
|
2538
|
+
# Same sandbox root set on every operator-command drive: doctor-pi-provider READS install-state
|
|
2539
|
+
# below XDG_DATA_HOME, so an inherited real root would make this gate's verdict depend on the
|
|
2540
|
+
# operator's host instead of the sandbox (non-hermetic even when nothing is written).
|
|
2541
|
+
local op_xdg_data="$npmhome/.local/share" op_xdg_state="$npmhome/.local/state" op_xdg_cache="$npmhome/.cache"
|
|
2542
|
+
if ! op_out=$(HOME="$npmhome" XDG_DATA_HOME="$op_xdg_data" XDG_STATE_HOME="$op_xdg_state" XDG_CACHE_HOME="$op_xdg_cache" PI_CODING_AGENT_DIR="$op_agent" "$installed_entwurf" new-session-id 2>&1); then
|
|
2543
|
+
fail "[check-pack-install] installed 'entwurf new-session-id' FAILED under node_modules (strip-types fence or missing compiled twin):"
|
|
2544
|
+
echo "$op_out" | tail -8 | sed 's/^/ /' >&2
|
|
2545
|
+
return 1
|
|
2546
|
+
fi
|
|
2547
|
+
# Same shape as SESSION_ID_RE (the garden id SSOT): <denote-stamp>-<6 hex>. A stub or a
|
|
2548
|
+
# truncated id would still "not crash", so the gate reads the id, not just the exit code.
|
|
2549
|
+
if ! printf '%s' "$op_out" | grep -qE '^[0-9]{8}T[0-9]{6}-[0-9a-f]{6}$'; then
|
|
2550
|
+
fail "[check-pack-install] installed 'entwurf new-session-id' did not print a well-formed garden session id: $op_out"
|
|
2551
|
+
return 1
|
|
2552
|
+
fi
|
|
2553
|
+
|
|
2554
|
+
if ! op_out=$(HOME="$npmhome" XDG_DATA_HOME="$op_xdg_data" XDG_STATE_HOME="$op_xdg_state" XDG_CACHE_HOME="$op_xdg_cache" PI_CODING_AGENT_DIR="$op_agent" "$installed_entwurf" doctor-pi-provider 2>&1); then
|
|
2555
|
+
# A doctor may exit non-zero on an unadopted host — that is a VERDICT, not a crash.
|
|
2556
|
+
# The fence, by contrast, kills it before any verdict body is printed.
|
|
2557
|
+
if printf '%s' "$op_out" | grep -q ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING; then
|
|
2558
|
+
fail "[check-pack-install] installed 'entwurf doctor-pi-provider' hit the node_modules strip-types fence:"
|
|
2559
|
+
echo "$op_out" | tail -8 | sed 's/^/ /' >&2
|
|
2560
|
+
return 1
|
|
2561
|
+
fi
|
|
2562
|
+
fi
|
|
2563
|
+
if ! printf '%s' "$op_out" | grep -q '\[pi-provider doctor\]' || ! printf '%s' "$op_out" | grep -q 'EFFECTIVE'; then
|
|
2564
|
+
fail "[check-pack-install] installed 'entwurf doctor-pi-provider' never reached its verdict body (no scopes/EFFECTIVE report):"
|
|
2565
|
+
echo "$op_out" | tail -8 | sed 's/^/ /' >&2
|
|
2566
|
+
return 1
|
|
2567
|
+
fi
|
|
2568
|
+
|
|
2569
|
+
if ! op_out=$(HOME="$npmhome" XDG_DATA_HOME="$op_xdg_data" XDG_STATE_HOME="$op_xdg_state" XDG_CACHE_HOME="$op_xdg_cache" PI_CODING_AGENT_DIR="$op_agent" "$installed_entwurf" meta-bridge-prune "$op_agent/meta-sessions" 2>&1); then
|
|
2570
|
+
fail "[check-pack-install] installed 'entwurf meta-bridge-prune' FAILED on a 0-record store:"
|
|
2571
|
+
echo "$op_out" | tail -8 | sed 's/^/ /' >&2
|
|
2572
|
+
return 1
|
|
2573
|
+
fi
|
|
2574
|
+
if ! printf '%s' "$op_out" | grep -q 'prune candidates' || ! printf '%s' "$op_out" | grep -q "store: $op_agent/meta-sessions"; then
|
|
2575
|
+
fail "[check-pack-install] installed 'entwurf meta-bridge-prune' did not scan the store it was given:"
|
|
2576
|
+
echo "$op_out" | tail -8 | sed 's/^/ /' >&2
|
|
2577
|
+
return 1
|
|
2578
|
+
fi
|
|
2579
|
+
echo "[check-pack-install] installed operator commands pass (new-session-id id-shaped, doctor-pi-provider reaches its verdict, meta-bridge-prune walks a 0-record store)"
|
|
2580
|
+
|
|
2581
|
+
# A dev-only gate has NO compiled twin by design. Under an installed package run_ts must
|
|
2582
|
+
# REFUSE it with a legible message — never fall back to raw .ts (that just re-raises the
|
|
2583
|
+
# fence error) and never exit 0 (a silent no-op would let CI "pass" a gate it never ran).
|
|
2584
|
+
local devgate_out devgate_rc=0
|
|
2585
|
+
devgate_out=$(HOME="$npmhome" XDG_DATA_HOME="$op_xdg_data" XDG_STATE_HOME="$op_xdg_state" XDG_CACHE_HOME="$op_xdg_cache" PI_CODING_AGENT_DIR="$op_agent" "$installed_entwurf" check-meta-session 2>&1) || devgate_rc=$?
|
|
2586
|
+
if [ "$devgate_rc" -eq 0 ]; then
|
|
2587
|
+
fail "[check-pack-install] a dev-only gate (check-meta-session) exited 0 from an installed package — it cannot have run; run_ts must refuse it"
|
|
2588
|
+
return 1
|
|
2589
|
+
fi
|
|
2590
|
+
if printf '%s' "$devgate_out" | grep -q ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING; then
|
|
2591
|
+
fail "[check-pack-install] a dev-only gate leaked the raw strip-types fence error instead of run_ts's refusal:"
|
|
2592
|
+
echo "$devgate_out" | tail -6 | sed 's/^/ /' >&2
|
|
2593
|
+
return 1
|
|
2594
|
+
fi
|
|
2595
|
+
if ! printf '%s' "$devgate_out" | grep -q 'dev-clone-only surface'; then
|
|
2596
|
+
fail "[check-pack-install] a dev-only gate under an installed package did not produce run_ts's refusal message:"
|
|
2597
|
+
echo "$devgate_out" | tail -6 | sed 's/^/ /' >&2
|
|
2598
|
+
return 1
|
|
2599
|
+
fi
|
|
2600
|
+
echo "[check-pack-install] dev-only gate refusal pass (installed package refuses check-* legibly, no raw-.ts fallback, no silent exit 0)"
|
|
2601
|
+
|
|
2262
2602
|
# 0.12.4 — installed STORE-DOCTOR regression. meta-bridge-doctor.sh's full store
|
|
2263
2603
|
# scan runs the prebuilt dist JS when it lives under node_modules (strip-types
|
|
2264
2604
|
# refuses the .ts there — the same class as the bridge boot above). Before the dist
|
|
@@ -2309,6 +2649,41 @@ JS
|
|
|
2309
2649
|
return 0
|
|
2310
2650
|
}
|
|
2311
2651
|
|
|
2652
|
+
check_pack_install() {
|
|
2653
|
+
# SELF-FENCE wrapper (rule 11). Keep this OUTSIDE the implementation so every exit path —
|
|
2654
|
+
# including an early failure before the implementation's cleanup trap is installed — returns
|
|
2655
|
+
# through the comparison. The DATA tree is byte-fenced. STATE cannot be byte-fenced because a
|
|
2656
|
+
# live native session may append legitimate lines concurrently, so fence the gate's unique fake
|
|
2657
|
+
# conversation marker instead; any increase proves that agy-imprint escaped its sandbox.
|
|
2658
|
+
trap - RETURN
|
|
2659
|
+
local real_data_root="${XDG_DATA_HOME:-$HOME/.local/share}/entwurf"
|
|
2660
|
+
local real_imprint_log="${XDG_STATE_HOME:-$HOME/.local/state}/entwurf/agy-imprint.log"
|
|
2661
|
+
local data_before data_after fake_before fake_after rc=0
|
|
2662
|
+
data_before="$( (find "$real_data_root" -type f -print0 2>/dev/null | sort -z | xargs -0r sha256sum) 2>/dev/null || true)"
|
|
2663
|
+
fake_before="$(grep -Fc 'conversationId=pack-install-agy-conversation' "$real_imprint_log" 2>/dev/null || true)"
|
|
2664
|
+
|
|
2665
|
+
_check_pack_install_impl "$@" || rc=$?
|
|
2666
|
+
# _check_pack_install_impl installs a RETURN cleanup trap after allocating its temp roots.
|
|
2667
|
+
# It has fired now; clear it before this wrapper returns so it cannot leak into its caller.
|
|
2668
|
+
trap - RETURN
|
|
2669
|
+
|
|
2670
|
+
data_after="$( (find "$real_data_root" -type f -print0 2>/dev/null | sort -z | xargs -0r sha256sum) 2>/dev/null || true)"
|
|
2671
|
+
fake_after="$(grep -Fc 'conversationId=pack-install-agy-conversation' "$real_imprint_log" 2>/dev/null || true)"
|
|
2672
|
+
if [ "$data_before" != "$data_after" ]; then
|
|
2673
|
+
fail "[check-pack-install] SELF-FENCE: this gate changed the operator's REAL install-state tree ($real_data_root) — a sandbox drive is leaking through an inherited XDG root:"
|
|
2674
|
+
diff <(printf '%s\n' "$data_before") <(printf '%s\n' "$data_after") | sed 's/^/ /' >&2 || true
|
|
2675
|
+
rc=1
|
|
2676
|
+
fi
|
|
2677
|
+
if [ "$fake_before" != "$fake_after" ]; then
|
|
2678
|
+
fail "[check-pack-install] SELF-FENCE: this gate appended its fake agy birth marker to the operator's REAL state log ($real_imprint_log): before=$fake_before after=$fake_after"
|
|
2679
|
+
rc=1
|
|
2680
|
+
fi
|
|
2681
|
+
if [ "$rc" -eq 0 ]; then
|
|
2682
|
+
echo "[check-pack-install] self-fence pass (real DATA tree byte-identical; fake STATE marker count unchanged)"
|
|
2683
|
+
fi
|
|
2684
|
+
return "$rc"
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2312
2687
|
|
|
2313
2688
|
# --- v2 install/runtime verification gates ---
|
|
2314
2689
|
#
|
|
@@ -2350,7 +2725,7 @@ function finishOk(trimmed) {
|
|
|
2350
2725
|
process.exit(1);
|
|
2351
2726
|
}
|
|
2352
2727
|
const names = tools.map((t) => t?.name).sort();
|
|
2353
|
-
const expected = ['entwurf_peers', 'entwurf_self', 'entwurf_inbox_read', 'entwurf_v2'];
|
|
2728
|
+
const expected = ['entwurf_peers', 'entwurf_self', 'entwurf_inbox_read', 'entwurf_register_native', 'entwurf_v2'];
|
|
2354
2729
|
for (const name of expected) {
|
|
2355
2730
|
if (!names.includes(name)) {
|
|
2356
2731
|
console.error(`missing MCP tool: ${name}`);
|
|
@@ -2452,7 +2827,7 @@ setup_all() {
|
|
|
2452
2827
|
|
|
2453
2828
|
echo "[setup] repo: $REPO_DIR"
|
|
2454
2829
|
echo "[setup] project: $project_dir"
|
|
2455
|
-
echo "[setup] scope: entwurf v2
|
|
2830
|
+
echo "[setup] scope: entwurf v2 package + detected native-harness bridges + pi adapter"
|
|
2456
2831
|
echo "[setup] verification: v2 install smoke (entwurf-bridge; LIVE substrate = release-gate)"
|
|
2457
2832
|
|
|
2458
2833
|
(cd "$REPO_DIR" && pnpm install --frozen-lockfile)
|
|
@@ -2472,6 +2847,28 @@ setup_all() {
|
|
|
2472
2847
|
echo "[setup] no native harness (claude) on PATH — skipping meta-bridge wiring (pi-only host)"
|
|
2473
2848
|
fi
|
|
2474
2849
|
|
|
2850
|
+
# Expose entwurf's STABLE bins on PATH for this DEV checkout (막힘 ②) BEFORE wiring agy: the
|
|
2851
|
+
# agy mcp_config, settings.statusLine, and hooks.json record the bare names `entwurf-bridge` /
|
|
2852
|
+
# `entwurf-agy-statusline` / `entwurf-agy-imprint`, so they must resolve for the agy doctors
|
|
2853
|
+
# to pass. NON-FATAL — a foreign bin already on PATH is left as-is.
|
|
2854
|
+
expose_dev_bin
|
|
2855
|
+
|
|
2856
|
+
# Fold agy (Antigravity) MCP bridge wiring into setup (막힘 ①, GLG 2026-07-04: install
|
|
2857
|
+
# ownership moves to entwurf). Same detection-gated, idempotent, NON-FATAL posture as the
|
|
2858
|
+
# meta-bridge block above — agy is an OPTIONAL harness, so a refused/corrupt agy config warns
|
|
2859
|
+
# but never bricks a pi/Claude setup. The hard gate stays doctor-agy-bridge.
|
|
2860
|
+
wire_agy_bridge
|
|
2861
|
+
|
|
2862
|
+
# Fold agy statusLine wiring into setup (#46 Task 1: own the ambient status area so it shows
|
|
2863
|
+
# driver + garden id, the claude meta-bridge symmetry). Same detection-gated NON-FATAL posture
|
|
2864
|
+
# — an explicit install-agy-statusline is fail-loud, but the setup wrapper WARNs + continues.
|
|
2865
|
+
wire_agy_statusline
|
|
2866
|
+
|
|
2867
|
+
# Fold agy PreInvocation birth imprint wiring into setup (#46 close criterion: manual register 0).
|
|
2868
|
+
# The hook runs the thin `entwurf-agy-imprint` bin and returns {"injectSteps":[]} so agy's loop
|
|
2869
|
+
# survives while the meta-record is created/attached by conversationId.
|
|
2870
|
+
wire_agy_hooks
|
|
2871
|
+
|
|
2475
2872
|
# Deterministic preflight lives in `pnpm check`; live substrate acceptance lives
|
|
2476
2873
|
# in `LIVE=1 ./run.sh release-gate <scratch>`. Setup is the install path, so it
|
|
2477
2874
|
# verifies the installed MCP bridge boundary only and does NOT run the legacy
|
|
@@ -2480,13 +2877,175 @@ setup_all() {
|
|
|
2480
2877
|
validate_entwurf_bridge
|
|
2481
2878
|
|
|
2482
2879
|
echo ""
|
|
2483
|
-
echo "DONE: entwurf setup (pi
|
|
2880
|
+
echo "DONE: entwurf setup (pi adapter + detected native bridges + v2 install smoke) green."
|
|
2484
2881
|
if command -v claude >/dev/null 2>&1; then
|
|
2485
|
-
echo "Verify
|
|
2882
|
+
echo "Verify Claude wiring with: ./run.sh doctor-meta-bridge"
|
|
2883
|
+
fi
|
|
2884
|
+
if command -v agy >/dev/null 2>&1; then
|
|
2885
|
+
echo "Verify agy wiring with: ./run.sh doctor-agy-bridge"
|
|
2886
|
+
echo " ./run.sh doctor-agy-statusline"
|
|
2887
|
+
echo " ./run.sh doctor-agy-hooks"
|
|
2486
2888
|
fi
|
|
2487
2889
|
echo "Run 'LIVE=1 ./run.sh release-gate <scratch>' for live substrate acceptance."
|
|
2488
2890
|
}
|
|
2489
2891
|
|
|
2892
|
+
# wire_agy_bridge — detection-gated, NON-FATAL agy (Antigravity) MCP bridge wiring, folded into
|
|
2893
|
+
# setup so a relocate/clone needs ONE idempotent command (막힘 ①). Mirrors the meta-bridge
|
|
2894
|
+
# block: agy on PATH → idempotent install-agy-bridge; no agy → honest skip, NO state ("있으면
|
|
2895
|
+
# 설정, 없으면 담아준다"). NON-FATAL by contract: agy is an OPTIONAL harness, so a refused/corrupt
|
|
2896
|
+
# agy config must NOT brick a pi/Claude setup — the hard gate is doctor-agy-bridge (issue #45:
|
|
2897
|
+
# the '?' surfaces in doctor, not by killing setup). WARNs are reason-specific so a transitional
|
|
2898
|
+
# symlink (someone else's SSOT) and a corrupt config (invalid JSON) are never conflated.
|
|
2899
|
+
wire_agy_bridge() {
|
|
2900
|
+
# Detection = the agy binary on PATH. AGY_BIN pins the target (default `agy`) so the
|
|
2901
|
+
# hermetic smoke can point at a fake agy or a definitely-absent path without depending on
|
|
2902
|
+
# whatever agy the CI/dev host happens to have — production leaves it unset (= `command -v
|
|
2903
|
+
# agy`, no regression). Same override spirit as agy-bridge.sh's AGY_MCP_CONFIG/AGY_BRIDGE_COMMAND.
|
|
2904
|
+
if ! command -v "${AGY_BIN:-agy}" >/dev/null 2>&1; then
|
|
2905
|
+
echo "[setup] no agy on PATH — skipping agy bridge wiring (no state; pi/Claude-only host)"
|
|
2906
|
+
return 0
|
|
2907
|
+
fi
|
|
2908
|
+
section "agy bridge install (native harness detected: Antigravity)"
|
|
2909
|
+
local out rc
|
|
2910
|
+
set +e
|
|
2911
|
+
out="$(bash "$REPO_DIR/scripts/agy-bridge.sh" install 2>&1)"
|
|
2912
|
+
rc=$?
|
|
2913
|
+
set -e
|
|
2914
|
+
printf '%s\n' "$out"
|
|
2915
|
+
if [ "$rc" -eq 0 ]; then
|
|
2916
|
+
echo "[setup] agy bridge wired (idempotent). Verify with: ./run.sh doctor-agy-bridge"
|
|
2917
|
+
return 0
|
|
2918
|
+
fi
|
|
2919
|
+
# NON-FATAL: keep setup alive, surface the reason honestly (never a silent pass).
|
|
2920
|
+
# ORDER MATTERS: the permission failures carry the same words as the mcp_config ones ("refused
|
|
2921
|
+
# (symlink)", "invalid JSON") and MUST be matched first — otherwise a settings.json problem gets
|
|
2922
|
+
# reported as an mcp_config problem and sends the operator to repair the wrong file.
|
|
2923
|
+
case "$out" in
|
|
2924
|
+
*"permission refused (symlink)"*|*"permission invalid JSON"*|*"permission could not be granted"*)
|
|
2925
|
+
# The MCP server IS registered — only the allow rule failed. agy defaults every mcp action to
|
|
2926
|
+
# Ask, so the bridge works but stops for a y/n on EVERY entwurf_v2 call. Half-wired, and said
|
|
2927
|
+
# so: the explicit installer fails loud on this; setup only degrades it, never hides it.
|
|
2928
|
+
echo "[setup] WARN: agy settings.json could not take our permission rule — bridge REGISTERED but NOT GRANTED." >&2
|
|
2929
|
+
echo "[setup] agy will prompt on every entwurf_v2 call until 'mcp(entwurf-bridge/entwurf_v2)'" >&2
|
|
2930
|
+
echo "[setup] is in its permissions.allow (see the line above for why). setup continues." >&2
|
|
2931
|
+
;;
|
|
2932
|
+
*"refused (symlink)"*)
|
|
2933
|
+
echo "[setup] WARN: agy mcp_config is a symlink — someone else's SSOT (transitional)." >&2
|
|
2934
|
+
echo "[setup] Bridge NOT wired; expected until install ownership moves to entwurf." >&2
|
|
2935
|
+
echo "[setup] Re-run setup once the symlink is dropped. setup continues." >&2
|
|
2936
|
+
;;
|
|
2937
|
+
*"invalid JSON"*)
|
|
2938
|
+
echo "[setup] WARN: your agy mcp_config is CORRUPT (invalid JSON) — bridge NOT wired." >&2
|
|
2939
|
+
echo "[setup] doctor-agy-bridge will KEEP FAILING until you repair that file." >&2
|
|
2940
|
+
echo "[setup] (Not a silent skip — fix the config, then re-run setup.) setup continues." >&2
|
|
2941
|
+
;;
|
|
2942
|
+
*)
|
|
2943
|
+
echo "[setup] WARN: agy bridge install did not complete (rc=$rc; see the line above)." >&2
|
|
2944
|
+
echo "[setup] Bridge NOT wired; verify with ./run.sh doctor-agy-bridge. setup continues." >&2
|
|
2945
|
+
;;
|
|
2946
|
+
esac
|
|
2947
|
+
return 0
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
# wire_agy_statusline — detection-gated, NON-FATAL agy statusLine wiring, folded into setup (#46
|
|
2951
|
+
# Task 1). Mirrors wire_agy_bridge: agy on PATH → idempotent install-agy-statusline; no agy →
|
|
2952
|
+
# honest skip, NO state. NON-FATAL by contract (agy is OPTIONAL — a refused/corrupt settings must
|
|
2953
|
+
# not brick a pi/Claude setup); the hard gate is doctor-agy-statusline. The renderer is the
|
|
2954
|
+
# stable bin entwurf-agy-statusline, exposed by expose_dev_bin above.
|
|
2955
|
+
wire_agy_statusline() {
|
|
2956
|
+
if ! command -v "${AGY_BIN:-agy}" >/dev/null 2>&1; then
|
|
2957
|
+
echo "[setup] no agy on PATH — skipping agy statusLine wiring (no state; pi/Claude-only host)"
|
|
2958
|
+
return 0
|
|
2959
|
+
fi
|
|
2960
|
+
section "agy statusLine install (native harness detected: Antigravity)"
|
|
2961
|
+
local out rc
|
|
2962
|
+
set +e
|
|
2963
|
+
out="$(bash "$REPO_DIR/scripts/agy-statusline-bridge.sh" install 2>&1)"
|
|
2964
|
+
rc=$?
|
|
2965
|
+
set -e
|
|
2966
|
+
printf '%s\n' "$out"
|
|
2967
|
+
if [ "$rc" -eq 0 ]; then
|
|
2968
|
+
echo "[setup] agy statusLine wired (idempotent). Verify with: ./run.sh doctor-agy-statusline"
|
|
2969
|
+
return 0
|
|
2970
|
+
fi
|
|
2971
|
+
case "$out" in
|
|
2972
|
+
*"refused (symlink)"*)
|
|
2973
|
+
echo "[setup] WARN: agy settings.json is a symlink — someone else's SSOT (transitional)." >&2
|
|
2974
|
+
echo "[setup] statusLine NOT wired; expected until install ownership moves to entwurf." >&2
|
|
2975
|
+
echo "[setup] Re-run setup once the symlink is dropped. setup continues." >&2
|
|
2976
|
+
;;
|
|
2977
|
+
*"invalid JSON"*)
|
|
2978
|
+
echo "[setup] WARN: your agy settings.json is CORRUPT (invalid JSON) — statusLine NOT wired." >&2
|
|
2979
|
+
echo "[setup] doctor-agy-statusline will KEEP FAILING until you repair that file." >&2
|
|
2980
|
+
echo "[setup] (Not a silent skip — fix the config, then re-run setup.) setup continues." >&2
|
|
2981
|
+
;;
|
|
2982
|
+
*)
|
|
2983
|
+
echo "[setup] WARN: agy statusLine install did not complete (rc=$rc; see the line above)." >&2
|
|
2984
|
+
echo "[setup] statusLine NOT wired; verify with ./run.sh doctor-agy-statusline. setup continues." >&2
|
|
2985
|
+
;;
|
|
2986
|
+
esac
|
|
2987
|
+
return 0
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
# wire_agy_hooks — detection-gated, NON-FATAL agy PreInvocation imprint wiring. Same setup posture
|
|
2991
|
+
# as the MCP/statusLine adapters, but this one is the birth writer that turns statusLine '?' into a
|
|
2992
|
+
# garden id after the first invocation.
|
|
2993
|
+
wire_agy_hooks() {
|
|
2994
|
+
if ! command -v "${AGY_BIN:-agy}" >/dev/null 2>&1; then
|
|
2995
|
+
echo "[setup] no agy on PATH — skipping agy hooks wiring (no state; pi/Claude-only host)"
|
|
2996
|
+
return 0
|
|
2997
|
+
fi
|
|
2998
|
+
section "agy hooks install (native harness detected: Antigravity)"
|
|
2999
|
+
local out rc
|
|
3000
|
+
set +e
|
|
3001
|
+
out="$(bash "$REPO_DIR/scripts/agy-hooks-bridge.sh" install 2>&1)"
|
|
3002
|
+
rc=$?
|
|
3003
|
+
set -e
|
|
3004
|
+
printf '%s\n' "$out"
|
|
3005
|
+
if [ "$rc" -eq 0 ]; then
|
|
3006
|
+
echo "[setup] agy hooks wired (idempotent). Verify with: ./run.sh doctor-agy-hooks"
|
|
3007
|
+
return 0
|
|
3008
|
+
fi
|
|
3009
|
+
case "$out" in
|
|
3010
|
+
*"refused (symlink)"*)
|
|
3011
|
+
echo "[setup] WARN: agy hooks.json is a symlink — someone else's SSOT (transitional)." >&2
|
|
3012
|
+
echo "[setup] birth imprint NOT wired; re-run setup once the symlink is dropped. setup continues." >&2
|
|
3013
|
+
;;
|
|
3014
|
+
*"invalid JSON"*)
|
|
3015
|
+
echo "[setup] WARN: your agy hooks.json is CORRUPT (invalid JSON) — birth imprint NOT wired." >&2
|
|
3016
|
+
echo "[setup] doctor-agy-hooks will KEEP FAILING until you repair that file. setup continues." >&2
|
|
3017
|
+
;;
|
|
3018
|
+
*)
|
|
3019
|
+
echo "[setup] WARN: agy hooks install did not complete (rc=$rc; see the line above)." >&2
|
|
3020
|
+
echo "[setup] Birth imprint NOT wired; verify with ./run.sh doctor-agy-hooks. setup continues." >&2
|
|
3021
|
+
;;
|
|
3022
|
+
esac
|
|
3023
|
+
return 0
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
# expose_dev_bin — make the `entwurf-bridge` STABLE bin resolve for a DEV checkout (막힘 ②).
|
|
3027
|
+
# setup IS the dev install command (consumers get the bin from npm bin-linking), so setup owns a
|
|
3028
|
+
# managed ~/.local/bin/entwurf-bridge symlink into this checkout via scripts/dev-bin.sh, recorded
|
|
3029
|
+
# for an honest inverse (remove-dev-bin). NON-FATAL like wire_agy_bridge: a foreign bin already on
|
|
3030
|
+
# PATH (a real npm install) is REFUSED not clobbered, and a BIN_DIR off PATH only WARNs — neither
|
|
3031
|
+
# bricks setup. The hard gate stays doctor-agy-bridge (a missing/dangling bin FAILs there).
|
|
3032
|
+
expose_dev_bin() {
|
|
3033
|
+
section "dev bin: expose entwurf-bridge on PATH (dev checkout)"
|
|
3034
|
+
local rc
|
|
3035
|
+
set +e
|
|
3036
|
+
bash "$REPO_DIR/scripts/dev-bin.sh" expose
|
|
3037
|
+
rc=$?
|
|
3038
|
+
set -e
|
|
3039
|
+
[ "$rc" -eq 0 ] && return 0
|
|
3040
|
+
if [ "$rc" -eq 3 ]; then
|
|
3041
|
+
echo "[setup] WARN: entwurf-bridge is already on PATH as someone else's bin (not ours) — left as-is." >&2
|
|
3042
|
+
echo "[setup] If that is a stale/foreign bin, remove it and re-run setup. setup continues." >&2
|
|
3043
|
+
else
|
|
3044
|
+
echo "[setup] WARN: dev bin exposure did not complete (rc=$rc; see above). setup continues." >&2
|
|
3045
|
+
fi
|
|
3046
|
+
return 0
|
|
3047
|
+
}
|
|
3048
|
+
|
|
2490
3049
|
# release-gate — the single command that, when GREEN, is sufficient to cut
|
|
2491
3050
|
# release cuts. Runs the full static floor (`pnpm check`) followed by the
|
|
2492
3051
|
# v2-native live gates, then emits one PASS/FAIL/SKIP summary. Everything is
|
|
@@ -2783,6 +3342,13 @@ case "$cmd" in
|
|
|
2783
3342
|
check-shell-quote)
|
|
2784
3343
|
check_shell_quote
|
|
2785
3344
|
;;
|
|
3345
|
+
check-install-surface)
|
|
3346
|
+
# 0.12.7 — structural half of the node_modules strip-types fence: run_ts is the only
|
|
3347
|
+
# crossing, every operator subcommand has a compiled twin, bin wrappers branch, dev
|
|
3348
|
+
# gates stay out of the tarball, and offline smokes never touch the real $HOME.
|
|
3349
|
+
# check-pack-install owns the dynamic half (it drives the installed commands).
|
|
3350
|
+
run_ts scripts/check-install-surface.ts
|
|
3351
|
+
;;
|
|
2786
3352
|
check-entwurf-session-identity)
|
|
2787
3353
|
check_entwurf_session_identity
|
|
2788
3354
|
;;
|
|
@@ -2846,6 +3412,9 @@ case "$cmd" in
|
|
|
2846
3412
|
check-entwurf-v2-mailbox)
|
|
2847
3413
|
check_entwurf_v2_mailbox
|
|
2848
3414
|
;;
|
|
3415
|
+
check-entwurf-v2-native-push)
|
|
3416
|
+
check_entwurf_v2_native_push
|
|
3417
|
+
;;
|
|
2849
3418
|
check-entwurf-v2-runner)
|
|
2850
3419
|
check_entwurf_v2_runner
|
|
2851
3420
|
;;
|
|
@@ -2939,11 +3508,20 @@ case "$cmd" in
|
|
|
2939
3508
|
check-entwurf-mailbox-guard)
|
|
2940
3509
|
check_entwurf_mailbox_guard
|
|
2941
3510
|
;;
|
|
3511
|
+
check-native-push-adapter)
|
|
3512
|
+
check_native_push_adapter
|
|
3513
|
+
;;
|
|
3514
|
+
check-native-push-register)
|
|
3515
|
+
check_native_push_register
|
|
3516
|
+
;;
|
|
3517
|
+
check-agy-sender-identity)
|
|
3518
|
+
check_agy_sender_identity
|
|
3519
|
+
;;
|
|
2942
3520
|
new-session-id)
|
|
2943
3521
|
# Garden launcher helper: print one fresh garden sessionId (SSOT:
|
|
2944
3522
|
# generateSessionId). Used by the operator alias to make every
|
|
2945
3523
|
# --entwurf-control session a garden citizen. Stdout = the id only.
|
|
2946
|
-
|
|
3524
|
+
run_ts scripts/new-session-id.ts
|
|
2947
3525
|
;;
|
|
2948
3526
|
smoke-resident-garden-guard)
|
|
2949
3527
|
# LIVE negative (0 tokens) + opt-in positive gate for the resident
|
|
@@ -2998,6 +3576,55 @@ case "$cmd" in
|
|
|
2998
3576
|
# duplicate/drift records. Offline + deterministic (deps bash+node+python3).
|
|
2999
3577
|
(cd "$REPO_DIR" && bash scripts/smoke-meta-install-state.sh)
|
|
3000
3578
|
;;
|
|
3579
|
+
smoke-agy-install-state)
|
|
3580
|
+
# 봉인 8 regression gate for the agy MCP install adapter: install→doctor→uninstall in an
|
|
3581
|
+
# ISOLATED HOME+XDG with a fake stable bin + fake pgrep/ss — adopt (preserve unrelated) +
|
|
3582
|
+
# state, doctor static-clean/live-SKIP (+live-PASS with a fake agy), honest-inverse
|
|
3583
|
+
# uninstall, symlink refuse, dangling FAIL, create-new inverse, and ⓪ checkout impurity 0.
|
|
3584
|
+
# Offline + deterministic (deps: bash+python3).
|
|
3585
|
+
(cd "$REPO_DIR" && bash scripts/smoke-agy-install-state.sh)
|
|
3586
|
+
;;
|
|
3587
|
+
smoke-agy-statusline-state)
|
|
3588
|
+
# #46 Task 1 regression gate for the agy statusLine install adapter: install→doctor→uninstall
|
|
3589
|
+
# in an ISOLATED HOME+XDG with a fake stable bin (entwurf-agy-statusline) + fake pgrep —
|
|
3590
|
+
# own the statusLine subtree WHOLE (preserve unrelated keys) + state (stable command, prior
|
|
3591
|
+
# subtree as preimage), doctor static-clean/live-SKIP (+live-consistent with a fake agy) /
|
|
3592
|
+
# drift-FAIL / dangling-command-FAIL / not-ours note, honest-inverse uninstall, symlink +
|
|
3593
|
+
# dangling-symlink refuse, create-new inverse, the NON-FATAL wire wrapper, and checkout
|
|
3594
|
+
# impurity 0. Offline + deterministic (deps: bash+python3).
|
|
3595
|
+
(cd "$REPO_DIR" && bash scripts/smoke-agy-statusline-state.sh)
|
|
3596
|
+
;;
|
|
3597
|
+
smoke-agy-hooks-state)
|
|
3598
|
+
# #46 birth imprint regression gate: hooks.json named hook ownership + direct
|
|
3599
|
+
# PreInvocation stdin → upsertMetaSession antigravity record, isolated HOME/XDG/PI agent.
|
|
3600
|
+
(cd "$REPO_DIR" && bash scripts/smoke-agy-hooks-state.sh)
|
|
3601
|
+
;;
|
|
3602
|
+
smoke-pi-provider-state)
|
|
3603
|
+
# #46 Task 2 regression gate for the pi provider install adapter: register-pi-provider.py
|
|
3604
|
+
# (ownership-classified install/remove, user+project scopes) + read-only doctor-pi-provider.ts
|
|
3605
|
+
# (effective shadow view). ISOLATED HOME+XDG + fake stable bin — user ownership matrix
|
|
3606
|
+
# (absent/managed-legacy/managed-current/user-override), state honest-inverse (legacy NOT
|
|
3607
|
+
# restored), sibling + legacy prune, project no-state strip, doctor effective/drift/dangling/
|
|
3608
|
+
# malformed/project-stale/'?', symlink refuse, and checkout impurity 0. Offline (bash+python3+node).
|
|
3609
|
+
(cd "$REPO_DIR" && bash scripts/smoke-pi-provider-state.sh)
|
|
3610
|
+
;;
|
|
3611
|
+
smoke-agy-native-push-live)
|
|
3612
|
+
# 봉인 8 LIVE acceptance gate for the native-push (agy) delivery rail. Drives the REAL
|
|
3613
|
+
# antigravity adapter + register core + runEntwurfV2 (production deps) against a live agy
|
|
3614
|
+
# conversation (AGY_CONVERSATION_ID): probe route, register create/attach idempotency,
|
|
3615
|
+
# fire→native-push delivered, owned-outcome reject, bogus-conv probe-indeterminate reject.
|
|
3616
|
+
# Meta-store is isolated to a temp dir (only the agy round-trip is real); honest SKIP when
|
|
3617
|
+
# LIVE!=1. doctor-static preflight FAILs before the agy bridge is wired (③).
|
|
3618
|
+
run_ts scripts/smoke-agy-native-push-live.ts
|
|
3619
|
+
;;
|
|
3620
|
+
smoke-user-scope-citizen)
|
|
3621
|
+
# 0.12.6 install-boundary gate: register-pi-package.py is the shared
|
|
3622
|
+
# packages[] SSOT for project/user install and remove; user scope makes
|
|
3623
|
+
# --entwurf-control load from any cwd. Idempotent, preserves unrelated
|
|
3624
|
+
# packages/keys, normalizes stale entries, remove is symmetric, and corrupt
|
|
3625
|
+
# settings fail loud. The tripwire the 2026-07-03 `pi install` removal lacked.
|
|
3626
|
+
(cd "$REPO_DIR" && bash scripts/smoke-user-scope-citizen.sh)
|
|
3627
|
+
;;
|
|
3001
3628
|
smoke-claude-native-resume-live)
|
|
3002
3629
|
# LIVE-only Detour A probe: two real Claude Code native turns (fresh +
|
|
3003
3630
|
# --resume) in a scratch cwd. Verifies native resume works while the
|
|
@@ -3007,9 +3634,10 @@ case "$cmd" in
|
|
|
3007
3634
|
;;
|
|
3008
3635
|
install-meta-bridge)
|
|
3009
3636
|
# 1.0.0 meta-bridge step 5: operator-grade GLOBAL install of the garden-native
|
|
3010
|
-
# receive plugin. Assembles a self-contained, node-path-baked copy under
|
|
3011
|
-
#
|
|
3012
|
-
#
|
|
3637
|
+
# receive plugin. Assembles a self-contained, node-path-baked copy under the
|
|
3638
|
+
# XDG data dir ($XDG_DATA_HOME/entwurf/meta-bridge/.assembled — dev clone and
|
|
3639
|
+
# installed package alike, never the checkout) and runs marketplace add +
|
|
3640
|
+
# install --scope user, so every native Claude Code session auto-loads it.
|
|
3013
3641
|
# Idempotent; Linux/macOS only (Windows fail-fast).
|
|
3014
3642
|
(cd "$REPO_DIR" && bash scripts/meta-bridge-install.sh "$@")
|
|
3015
3643
|
;;
|
|
@@ -3027,6 +3655,101 @@ case "$cmd" in
|
|
|
3027
3655
|
# zero claude-code meta-records is a SILENT MISS -> non-zero exit.
|
|
3028
3656
|
(cd "$REPO_DIR" && bash scripts/meta-bridge-doctor.sh "$@")
|
|
3029
3657
|
;;
|
|
3658
|
+
install-agy-bridge)
|
|
3659
|
+
# 봉인 7: the agy (Antigravity) MCP install ADAPTER (SEPARATE from the Claude
|
|
3660
|
+
# marketplace install — only runner/reporting is shared). Registers ONE entwurf-bridge
|
|
3661
|
+
# server entry in the agy mcp_config: adopt a regular file / create a new one / REFUSE a
|
|
3662
|
+
# symlink (someone else's SSOT). Records an install-state under $XDG_DATA_HOME/entwurf/
|
|
3663
|
+
# agy-bridge/ for an honest inverse. The command written is a STABLE bin (entwurf-bridge),
|
|
3664
|
+
# never a repo/git-hash path (the oracle dangling lesson).
|
|
3665
|
+
(cd "$REPO_DIR" && bash scripts/agy-bridge.sh install "$@")
|
|
3666
|
+
;;
|
|
3667
|
+
uninstall-agy-bridge)
|
|
3668
|
+
# 봉인 7: honest inverse of install-agy-bridge from the install-state (restore the
|
|
3669
|
+
# captured preimage / remove our key; remove the file if we created it empty). Refuses if
|
|
3670
|
+
# the managed config became a symlink since install; no state → nothing to undo.
|
|
3671
|
+
(cd "$REPO_DIR" && bash scripts/agy-bridge.sh uninstall "$@")
|
|
3672
|
+
;;
|
|
3673
|
+
doctor-agy-bridge)
|
|
3674
|
+
# 봉인 7: 2-tier fail-loud doctor. STATIC proves both candidate configs (global
|
|
3675
|
+
# ~/.gemini/config/mcp_config.json — the file live agy actually reads — + legacy
|
|
3676
|
+
# ~/.gemini/antigravity-cli which install now cleans) resolve, parse, and carry a
|
|
3677
|
+
# RESOLVABLE command (a dangling command FAILS). LIVE proves runtime-effectiveness only
|
|
3678
|
+
# when an agy process exists; with no agy it is an honest SKIP (never a PASS in disguise).
|
|
3679
|
+
(cd "$REPO_DIR" && bash scripts/agy-bridge.sh doctor "$@")
|
|
3680
|
+
;;
|
|
3681
|
+
wire-agy-bridge)
|
|
3682
|
+
# 막힘 ①: the detection-gated, NON-FATAL setup wrapper around install-agy-bridge (agy on
|
|
3683
|
+
# PATH → idempotent install; no agy → honest skip, no state). HIDDEN/internal — setup calls
|
|
3684
|
+
# this; it is exposed as a subcommand only so smoke-agy-install-state can drive it
|
|
3685
|
+
# deterministically. The hard gate stays doctor-agy-bridge (issue #45: the '?' is a doctor
|
|
3686
|
+
# signal, not a setup-killer).
|
|
3687
|
+
wire_agy_bridge
|
|
3688
|
+
;;
|
|
3689
|
+
install-agy-statusline)
|
|
3690
|
+
# #46 Task 1: own the WHOLE statusLine subtree of agy settings.json → the stable-bin renderer
|
|
3691
|
+
# entwurf-agy-statusline (driver + garden id), the claude meta-bridge statusLine symmetry.
|
|
3692
|
+
# Adopt a regular file / create / REFUSE a symlink. install-state under $XDG_DATA_HOME/
|
|
3693
|
+
# entwurf/agy-statusline/ for an honest inverse. The command is a BARE stable bin — dev AND
|
|
3694
|
+
# installed (the checkout path lives only in the dev-bin symlink state, never in settings).
|
|
3695
|
+
(cd "$REPO_DIR" && bash scripts/agy-statusline-bridge.sh install "$@")
|
|
3696
|
+
;;
|
|
3697
|
+
uninstall-agy-statusline)
|
|
3698
|
+
# #46 Task 1: honest inverse of install-agy-statusline — restore the captured statusLine
|
|
3699
|
+
# subtree preimage (remove the key if absent, else set it back; remove the file if we created
|
|
3700
|
+
# it empty). Refuses if settings.json became a symlink since install; no state → nothing.
|
|
3701
|
+
(cd "$REPO_DIR" && bash scripts/agy-statusline-bridge.sh uninstall "$@")
|
|
3702
|
+
;;
|
|
3703
|
+
doctor-agy-statusline)
|
|
3704
|
+
# #46 Task 1: fail-loud doctor. STATIC proves the SINGLE settings root agy reads statusLine
|
|
3705
|
+
# from (~/.gemini/antigravity-cli/settings.json — Task-1 capture, not the 2-candidate mcp
|
|
3706
|
+
# root) parses and carries OUR RESOLVABLE command (dangling FAILs, state drift FAILs). LIVE
|
|
3707
|
+
# proves runtime-effectiveness only with an agy process; else an honest SKIP.
|
|
3708
|
+
(cd "$REPO_DIR" && bash scripts/agy-statusline-bridge.sh doctor "$@")
|
|
3709
|
+
;;
|
|
3710
|
+
install-agy-hooks)
|
|
3711
|
+
# #46 birth writer: install the Antigravity PreInvocation named hook that runs
|
|
3712
|
+
# entwurf-agy-imprint and returns {"injectSteps":[]}.
|
|
3713
|
+
(cd "$REPO_DIR" && bash scripts/agy-hooks-bridge.sh install "$@")
|
|
3714
|
+
;;
|
|
3715
|
+
uninstall-agy-hooks)
|
|
3716
|
+
# Honest inverse of install-agy-hooks: restore/remove only our named hook.
|
|
3717
|
+
(cd "$REPO_DIR" && bash scripts/agy-hooks-bridge.sh uninstall "$@")
|
|
3718
|
+
;;
|
|
3719
|
+
doctor-agy-hooks)
|
|
3720
|
+
# Fail-loud doctor for agy hooks.json imprint wiring.
|
|
3721
|
+
(cd "$REPO_DIR" && bash scripts/agy-hooks-bridge.sh doctor "$@")
|
|
3722
|
+
;;
|
|
3723
|
+
doctor-pi-provider)
|
|
3724
|
+
# #46 Task 2: read-only fail-loud doctor for the pi provider ownership (entwurfProvider.
|
|
3725
|
+
# mcpServers.entwurf-bridge). Uses config.ts readProviderSettingsFile SSOT for the EFFECTIVE
|
|
3726
|
+
# (project-shadows-user) command — never a re-implemented merge. Reports user/project/effective,
|
|
3727
|
+
# gates on stable-bin resolvability, and distinguishes state-owned drift (FAIL) from an
|
|
3728
|
+
# unowned user override (honest note). No agy/pi process needed — pure settings inspection.
|
|
3729
|
+
run_ts scripts/doctor-pi-provider.ts "$@"
|
|
3730
|
+
;;
|
|
3731
|
+
wire-agy-statusline)
|
|
3732
|
+
# #46 Task 1: the detection-gated, NON-FATAL setup wrapper around install-agy-statusline.
|
|
3733
|
+
# HIDDEN/internal — setup calls this; exposed so smoke-agy-statusline-state can drive it
|
|
3734
|
+
# deterministically. The hard gate stays doctor-agy-statusline.
|
|
3735
|
+
wire_agy_statusline
|
|
3736
|
+
;;
|
|
3737
|
+
wire-agy-hooks)
|
|
3738
|
+
# #46 birth writer setup wrapper around install-agy-hooks.
|
|
3739
|
+
wire_agy_hooks
|
|
3740
|
+
;;
|
|
3741
|
+
expose-dev-bin)
|
|
3742
|
+
# 막힘 ②: expose the entwurf-bridge STABLE bin on PATH for a DEV checkout (a managed symlink
|
|
3743
|
+
# into this checkout, recorded for an honest inverse). HIDDEN/internal — setup calls the
|
|
3744
|
+
# NON-FATAL expose_dev_bin wrapper; exposed here so smoke-agy-install-state can drive the
|
|
3745
|
+
# foreign-refuse WARN path. The exposure logic lives in scripts/dev-bin.sh.
|
|
3746
|
+
expose_dev_bin
|
|
3747
|
+
;;
|
|
3748
|
+
remove-dev-bin)
|
|
3749
|
+
# 막힘 ②: honest inverse of expose-dev-bin — remove ONLY our managed link + state (REFUSE if
|
|
3750
|
+
# it became foreign). The raw script (no wrapper) so an operator sees a loud failure.
|
|
3751
|
+
(cd "$REPO_DIR" && bash scripts/dev-bin.sh remove "$@")
|
|
3752
|
+
;;
|
|
3030
3753
|
meta-bridge-prune)
|
|
3031
3754
|
# 1.0.0 meta-bridge Phase 4: LISTING-ONLY janitor for the meta-session store.
|
|
3032
3755
|
# doctor reds on corrupt/duplicate/drift but intentionally does NOT fail on
|
|
@@ -3037,7 +3760,7 @@ case "$cmd" in
|
|
|
3037
3760
|
# surviving authority). Default store = defaultMetaSessionsDir(); pass [dir]
|
|
3038
3761
|
# + [--ttl-days N] to override.
|
|
3039
3762
|
shift || true
|
|
3040
|
-
|
|
3763
|
+
run_ts scripts/meta-bridge-prune.ts "$@"
|
|
3041
3764
|
;;
|
|
3042
3765
|
meta-bridge-managed-keys)
|
|
3043
3766
|
# 0.10.0 meta-bridge: emit the SSOT of settings.json/~/.claude.json keys that
|
|
@@ -3134,6 +3857,9 @@ case "$cmd" in
|
|
|
3134
3857
|
remove)
|
|
3135
3858
|
remove_local_package "$TARGET_PROJECT_DIR"
|
|
3136
3859
|
;;
|
|
3860
|
+
remove-user-scope)
|
|
3861
|
+
remove_user_scope_citizen
|
|
3862
|
+
;;
|
|
3137
3863
|
-h|--help|help|"")
|
|
3138
3864
|
usage
|
|
3139
3865
|
;;
|