@junghanacs/entwurf 0.14.2 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -5
- package/CHANGELOG.md +149 -0
- package/DELIVERY.md +72 -59
- package/README.md +120 -20
- package/VERIFY.md +10 -7
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +2 -2
- package/docs/external-mcp-host.md +26 -4
- package/docs/setup-clean-host.md +118 -18
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +8 -6
- package/mcp/entwurf-bridge/dist/pi-extensions/entwurf-capabilities.json +1 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/copilot-fresh-preflight.js +253 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +10 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +102 -28
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +77 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook-copilot.js +288 -0
- package/mcp/entwurf-bridge/src/index.ts +10 -6
- package/mcp/entwurf-bridge/tsconfig.build.json +2 -1
- package/package.json +22 -11
- package/pi/copilot-receive/entwurf-receive/extension.mjs +323 -0
- package/pi/entwurf-capabilities.json +1 -0
- package/pi/meta-bridge-copilot/.claude-plugin/marketplace.json +11 -0
- package/pi/meta-bridge-copilot/entwurf-meta-receive-copilot/.claude-plugin/plugin.json +5 -0
- package/pi/meta-bridge-copilot/entwurf-meta-receive-copilot/hooks/hooks.json +7 -0
- package/pi/meta-bridge-copilot/entwurf-meta-receive-copilot/scripts/copilot-hook-launch.sh +85 -0
- package/pi-extensions/entwurf-control.ts +17 -8
- package/pi-extensions/lib/copilot-fresh-preflight.ts +251 -0
- package/pi-extensions/lib/meta-sender-identity.ts +10 -2
- package/pi-extensions/lib/meta-session.ts +102 -29
- package/pi-extensions/lib/mux-fresh-call.ts +82 -11
- package/pi-extensions/meta-bridge-hook-copilot.ts +323 -0
- package/run.sh +1079 -186
- package/scripts/agy-bridge.sh +4 -2
- package/scripts/check-copilot-birth-hook.ts +1174 -0
- package/scripts/check-copilot-launch.ts +395 -0
- package/scripts/check-copilot-receive-arm.ts +734 -0
- package/scripts/check-copilot-statusline.ts +122 -0
- package/scripts/check-entwurf-capabilities.ts +25 -6
- package/scripts/check-fresh-cut-gate.sh +104 -27
- package/scripts/check-gate-qualification.ts +10 -1
- package/scripts/check-install-container.sh +2 -2
- package/scripts/check-install-surface.ts +1 -1
- package/scripts/check-meta-capability-source.ts +89 -5
- package/scripts/check-meta-doctor-oracle.sh +26 -0
- package/scripts/check-meta-manifest-schema.py +38 -0
- package/scripts/check-meta-receiver-marker.ts +25 -2
- package/scripts/check-meta-session.ts +32 -0
- package/scripts/check-setup-qualification.sh +126 -0
- package/scripts/copilot-bridge-doctor.sh +242 -0
- package/scripts/copilot-bridge-install.sh +384 -0
- package/scripts/copilot-bridge-oracle.sh +223 -0
- package/scripts/copilot-bridge-uninstall.sh +137 -0
- package/scripts/copilot-launch.sh +236 -0
- package/scripts/copilot-mcp-bridge.sh +177 -0
- package/scripts/copilot-mcp-config.py +221 -0
- package/scripts/copilot-receive-bridge.sh +483 -0
- package/scripts/copilot-statusline-bridge.sh +149 -0
- package/scripts/copilot-statusline-config.py +201 -0
- package/scripts/copilot-statusline.sh +68 -0
- package/scripts/dev-bin.sh +53 -9
- package/scripts/fake-copilot-vendor.sh +67 -0
- package/scripts/meta-bridge-hook-log.sh +17 -5
- package/scripts/mutants/capability-cache.json +64 -0
- package/scripts/mutants/copilot-birth.json +244 -0
- package/scripts/mutants/copilot-launch.json +187 -0
- package/scripts/mutants/copilot-receive.json +263 -0
- package/scripts/mutants/fresh-cut.json +17 -0
- package/scripts/mutants/mux-fresh-call.json +216 -2
- package/scripts/mutants/pack-install.json +17 -0
- package/scripts/mutants/pi-package-ownership.json +92 -0
- package/scripts/mutants/setup-verdict.json +148 -0
- package/scripts/mutants/source-install.json +32 -0
- package/scripts/pi_settings_io.py +22 -0
- package/scripts/raw-async-delivery/README.md +170 -125
- package/scripts/raw-async-delivery/copilot-enqueue-addressed.sh +35 -0
- package/scripts/raw-async-delivery/copilot-extension-receive/extension.mjs +123 -0
- package/scripts/register-pi-package.py +525 -7
- package/scripts/register-pi-provider.py +162 -12
- package/scripts/smoke-agy-install-state.sh +86 -30
- package/scripts/smoke-agy-statusline-state.sh +9 -6
- package/scripts/smoke-copilot-mcp-state.sh +153 -0
- package/scripts/smoke-copilot-statusline-state.sh +131 -0
- package/scripts/smoke-mux-fresh-call-live.ts +2 -0
- package/scripts/smoke-mux-lifecycle-live.ts +3 -1
- package/scripts/smoke-setup-verdict.sh +291 -0
- package/scripts/smoke-user-scope-citizen.sh +388 -1
- package/scripts/tsconfig.json +1 -0
package/run.sh
CHANGED
|
@@ -119,7 +119,7 @@ run_vitest() {
|
|
|
119
119
|
usage() {
|
|
120
120
|
cat <<'EOF'
|
|
121
121
|
Usage:
|
|
122
|
-
./run.sh setup [project-dir] # ONE
|
|
122
|
+
./run.sh setup [project-dir] # ONE presence-driven composition (#86): mode-first (source bootstrap only on a checkout), then per-component PASS/SKIP/FAIL for pi (presence+floor)/claude/agy/copilot (all four units: birth→MCP→receiver→footer, independently) + stable dev bins + v2 install smoke; absent harness = zero-state SKIP, detected-incomplete = named FAIL + nonzero exit. Never installs a harness or touches credentials
|
|
123
123
|
./run.sh release-gate [project-dir] [--cut] [--allow-skip-gemini] # SINGLE release gate: full static (pnpm run check:full) + the v2-native live gates (v2 matrix-live, check-bridge, doctor-pi-provider, RGG) + the ACP plugin acceptance floor (12 LIVE smokes: socket-citizen/raw-turn/overlay/provider/session-reuse/carrier-augment/memory-containment/rgg/mcp/skill/bundled-mcp/v2-send) + the one surviving axis the aggregate used to omit silently (claude-native-resume; Cortex stays a documented on-demand direct call) + the cross-harness delivery chain (smoke-entwurf-chain-live). TWO-TIER summary: MUST (release-blocking, owns the exit code — "green" applies here) + BEHAVIOR (advisory, non-blocking: RGG positives model-in-loop turn). STEP OUTCOME protocol: every step is INVOKED and reports its own PASS / SKIP (exit 97, a prerequisite it does not have) / FAIL — a skip is never counted as a pass. Without --cut this is the unattended diagnostic (SKIPs reported, exit 0). WITH --cut it is read as release acceptance and ANY MUST SKIP is red, which is what makes "a CUT needs LIVE=1, SKIP=0" executable instead of prose. --allow-skip-gemini accepted-but-ignored (back-compat). final cut authorization is GLG's.
|
|
124
124
|
./run.sh check-bridge # entwurf-bridge direct MCP smoke + protocol/negative-path test.sh (live substrate = v2 live smokes)
|
|
125
125
|
./run.sh check-entwurf-bridge-boot # deterministic gate (5d-5-pre, G1a/G1b/G1e/G1f, IN pnpm run check:full): boot start.sh under strip-types + assert v2 fence graph loads + entwurf_v2 and entwurf_resume_call registered/schema + the tools/list surface is EXACTLY the seven shipped garden verbs; tools/list only, no auth/side-effect
|
|
@@ -130,6 +130,10 @@ Usage:
|
|
|
130
130
|
./run.sh check-meta-session # deterministic gate (#30 step 2, V3-only): fs store — idempotent decideUpsert/upsertMetaSession + mailbox enqueue/read + receipt state, no API
|
|
131
131
|
./run.sh check-meta-v3-record # deterministic gate: the ONE live record schema (v3) — canonical serialize/round-trip/mint, foreign-generation rejections name fresh-cut with the actual version value, strict keyset, no API
|
|
132
132
|
./run.sh check-mailbox-receipt-state # deterministic gate (0.11 Stage 0 step 3B): mailbox receipt state schema + store (stamp→persist→read-back) in a temp mailbox, strict keyset, no API
|
|
133
|
+
./run.sh check-copilot-birth-hook # #82 gate: drives the real Copilot assembler into a temp dir, fires the baked launcher with NO ARGV (the way Copilot's `exec`-string schema forces), and requires a backend:"copilot" v3 record + attach + peer row + a SENDER marker the resolver joins back to that record, and still zero mailbox/receiver marker (who-sent needs a shared parent; a receiver needs a doorbell this backend has not got). Hermetic; no Copilot, no model turn
|
|
134
|
+
./run.sh check-copilot-receive-arm # #82 RAIL 5 gate: the REAL receiver installer + the REAL extension.mjs forked with a stubbed SDK. Arms only after birth, marker owned by the WATCHER pid, self-fetch dispatch answer, doorbell carries the garden id and NOT the body, id-drift/foreign-parent refusals. Hermetic; no Copilot, no model turn
|
|
135
|
+
./run.sh check-copilot-launch # #82 RAIL 7 gate: the MANAGED launch `entwurf copilot`, driven through its public address against a FAKE VENDOR on a sandbox PATH. Receiver precondition refusals, EXTENSIONS token + operator token preservation, injected defaults before the `--` terminator, byte-identical argv, the 11 explicit permission/surface policy overrides that suppress `--yolo`, exec (not fork) pid identity, exit passthrough, recursion refusal. Hermetic; no Copilot, no model turn
|
|
136
|
+
./run.sh check-copilot-statusline # #82 Copilot custom-footer renderer. session_id → ready/?/gid + rail `cop`; exit 0. IN pnpm check. No Copilot, no model turn
|
|
133
137
|
./run.sh check-entwurf-capabilities # deterministic gate (0.11 Stage 0 step 3C): backend capability registry (pi/entwurf-capabilities.json) — coverage==META_CITIZEN_BACKENDS + agrees with live META_BACKEND_DESCRIPTORS + strict keyset, no API
|
|
134
138
|
./run.sh check-capability-bundle-reach # deterministic gate (IN pnpm check): re-ask EVERY shipped copy of meta-session (source + bridge bundle emit) whether metaCapabilitiesFilePath() reaches the registry — the artifact-depth check the source-path gates cannot make; needs a built dist, missing dist FAILS
|
|
135
139
|
./run.sh smoke-pi-attach # deterministic gate (#50 C2 checkpoint + C3 ACP tail): a pi session attaches as a V3 meta-record citizen (backend:"pi"), the gardenId is the RECORD's not pi's session id, the control socket is keyed on it, a re-open ATTACHES to the same address (never a second mint), the BUILT DIST ENTRY driven over MCP stdio lists the citizen + delivers entwurf_v2 to that socket with an RPC ack, and the ACP identity chain lands a send AS the host record (enrichMcpServersWithEnvelope env → bridge sender = host gardenId). mkdtemp-isolated; the live store is never read
|
|
@@ -177,7 +181,8 @@ Usage:
|
|
|
177
181
|
./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)
|
|
178
182
|
./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)
|
|
179
183
|
./run.sh check-meta-doctor-oracle # 0.12.8 (#51): detection power of the release ORACLE — healthy fixture must reach `doctor: PASS`, then 21 planted defects (retired shell form, partial hand-patch, launcher bypass/repoint/provenance loss, malformed exec args, owner type drift, extra leaf/group, doorbell asyncRewake/path/timeout, no live bridge, stale receiver, ambiguous/missing cache, missing hook log/writer, failing CLI probes) must each turn it FAIL naming their own cause, plus a positive case pinning that a long-writing CLI is NOT a false negative. Offline/deterministic (deps: bash+node+python3)
|
|
180
|
-
./run.sh smoke-agy-install-state # agy MCP + exact permission ownership regression: isolated HOME+XDG, adopt/state/inverse, symlink refuse, setup
|
|
184
|
+
./run.sh smoke-agy-install-state # agy MCP + exact permission ownership regression: isolated HOME+XDG, adopt/state/inverse, symlink refuse, truthful setup outcomes. Offline/deterministic
|
|
185
|
+
./run.sh smoke-setup-verdict # #86 C1+C3b aggregate setup verdict fixture: all-absent SKIP/green, pi presence+floor, detected-FAIL nonzero, copilot four-unit composition (present=independent PASS/FAIL rows, absent=zero-state SKIP), installed-mode named branch, credential store untouched. Offline/deterministic
|
|
181
186
|
./run.sh check-agy-permission-matrix # AGY permission CONTRACT SPACE as a literal table (55 cells): parser-state × operation × settings × ownership × precedence with stated exclusion rules; expectations are hand-written literals, never read from the SUT. Offline/deterministic (deps: python3)
|
|
182
187
|
./run.sh smoke-entwurf-chain-live # LIVE cross-harness delivery chain: native Claude Code -> pi GPT -> pi ACP Sonnet -> mailbox terminus, proving sender identity/replyable at every hop and a real read receipt at the end. Prerequisites (claude on PATH, pi credentials per backend) report protocol SKIP, never a pass
|
|
183
188
|
./run.sh check-release-gate-outcomes # release-gate STEP OUTCOME protocol (P1): one skip exit code shared by the shell + TS halves, classifier never rounds a skip up to a pass, `--cut` refuses a MUST SKIP while a bare diagnostic stays exit 0, no LIVE smoke keeps the old exit-0 skip shape, and both real skip surfaces are INVOKED and observed to propagate the code
|
|
@@ -185,8 +190,10 @@ Usage:
|
|
|
185
190
|
./run.sh check-probe-ordering # §11-7 ordering-probe deterministic gate: raw-client SAMENESS pinned to backend.ts (sequence/args/timeouts/permission policy — the probe may never measure a lookalike), phase attribution incl. set-model, probe-mode fixture wire markers (delay honored, probeRunId REQUIRED, smoke-acp-mcp-live legacy compat), event-log door integrity (reserved keys refused at write; unknown marker name, broken sort axis, or a payload the classifier cannot judge on is MALFORMED, never a quiet event), and the §11-7 paired-verdict truth table (P0/I0 outside the space, phase-qualified D, B promotion ladder, C, A two-delay rule). Offline/deterministic; one product claim (no production prompt cutoff) is replant-qualified via scripts/mutants/probe-ordering.json, while the other assertions are direct [CHECK:*] contracts
|
|
186
191
|
./run.sh check-probe-cli-shim # §11-7-c B-name-snapshot PRODUCER gate: the CLI shim driven as a REAL process against fake CLIs (no API, no cost). Proves what a defect would buy — FABRICATED evidence (a malformed init is never reported as an empty name set; the boot report carries the true target path+sha256 the classifier verifies against the roster), a DESTROYED turn (byte transparency across mid-UTF8/CRLF/oversized/unterminated framing, exit-code fidelity, signal re-raise, inbound signal forwarding, stderr passthrough, stdout backpressure), and LEAKED operator state (exact-allowlist env scrub, no argv/env/prompt body in the log). Offline/deterministic; 20 direct [CHECK:*] contracts, deliberately no longer replant-qualified after #70 subtraction
|
|
187
192
|
./run.sh smoke-agy-statusline-state # agy ambient garden-id statusLine install/doctor/inverse regression. Offline/deterministic
|
|
193
|
+
./run.sh smoke-copilot-statusline-state # Copilot custom-footer install/doctor/inverse regression. Offline/deterministic
|
|
194
|
+
./run.sh smoke-copilot-mcp-state # Copilot MCP install/doctor/inverse regression. Offline/deterministic
|
|
188
195
|
./run.sh smoke-agy-hooks-state # agy PreInvocation birth/sender hook install/doctor/inverse + direct stdin→meta-record regression. Offline/deterministic
|
|
189
|
-
./run.sh smoke-user-scope-citizen # 0.12.6 install-boundary: pi packages[] registration SSOT (register-pi-package.py) — idempotent + preserves unrelated + normalizes stale
|
|
196
|
+
./run.sh smoke-user-scope-citizen # 0.12.6 install-boundary: pi packages[] registration SSOT (register-pi-package.py) — idempotent + preserves unrelated + remove symmetry + fails loud, and the #86 C2 explicit ownership cells (project scope still normalizes ITS OWN stale entries; user scope refuses other owners and only takeover-user-scope moves the shared entry). Offline/hermetic (deps: bash+python3)
|
|
190
197
|
./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)
|
|
191
198
|
./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)
|
|
192
199
|
./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)
|
|
@@ -195,6 +202,19 @@ Usage:
|
|
|
195
202
|
./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)
|
|
196
203
|
./run.sh uninstall-meta-bridge # 1.0.0 meta-bridge Phase 2: stateful GLOBAL uninstall (restore only keys/items captured in install-state)
|
|
197
204
|
./run.sh doctor-meta-bridge # THE RELEASE ORACLE (#51, Linux-certified repair axis). exit 0 = every required layer was MEASURED on this Linux host: toolchain + state + plugin/MCP + resolved-artifact launch-form classification (all 3 owner hooks + doorbell static contract) + synthetic owner join + store scan + hook errors + SessionStart evidence + REQUIRED live MCP↔marker join + writer-version parity. Missing live evidence is NOT CERTIFIED (open a Claude session and re-run), never a pass; Darwin is not yet verified/certified and stays nonzero for this cut (future validation may reopen it). Detection power is held by check-meta-doctor-oracle
|
|
205
|
+
./run.sh copilot [args...] # #82 RAIL 7: the MANAGED Copilot launch. exec()s the vendor CLI in THIS terminal (cwd/pid/exit preserved) with COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS set for that one invocation — entwurf owns no part of your shell and writes nothing to it, but it owns the process it is about to become. Without that flag Copilot skips the extension scan SILENTLY. Refuses to launch unless the receiver unit is actually installed. Injects `--model auto` when no --model was given and `--yolo` when no explicit permission/surface policy flag was given, both BEFORE any `--`; every argument you pass is forwarded byte-identical. RUNNING THIS IS YOUR CONSENT to that profile — use plain `copilot` for stock vendor behaviour. Not tmux, not fresh-call, mints no citizen (birth is still the first prompt)
|
|
206
|
+
./run.sh install-copilot-bridge # #82: GLOBAL install of the Copilot BIRTH plugin (own marketplace root; node+entry baked into the no-argv exec string). MCP wiring and the RECEIVER extension are separate install surfaces (install-copilot-mcp, install-copilot-receive). Also retires the stale Claude unit (--keep-stale-claude-unit opts out)
|
|
207
|
+
./run.sh uninstall-copilot-bridge # #86 C3a: honest inverse of install-copilot-bridge from the package-owned install-state (exact qualified id + exact marketplace name/path + recorded assembly only; state deleted LAST; a failing vendor list is UNKNOWN and refuses; never --force, never the stale Claude unit)
|
|
208
|
+
./run.sh doctor-copilot-bridge # #82/#86: fail-loud surface for that unit — runtime axis (red = a hook that RAN and failed, or a broken/unbaked artifact; "installed with zero records" is NOT red and is reported as NOT-YET: a Copilot session is born on its FIRST PROMPT, not when the window opens, measured) + ownership axis (install-state shape/binding, marketplace path drift, UNKNOWN vendor lists; legacy no-state install = named non-green, repair = install-copilot-bridge adoption). Either axis red = red
|
|
209
|
+
./run.sh install-copilot-statusline # own Copilot statusLine + footer.showCustom with an install-state preimage
|
|
210
|
+
./run.sh uninstall-copilot-statusline # honest inverse of install-copilot-statusline
|
|
211
|
+
./run.sh doctor-copilot-statusline # static ownership/config/bin doctor; never claims a render receipt
|
|
212
|
+
./run.sh install-copilot-mcp # #82 RAIL 5: register ONE entwurf-bridge server in ~/.copilot/mcp-config.json (adopt / create / REFUSE symlink), type:local, install-state under $XDG_DATA_HOME/entwurf/copilot-mcp/
|
|
213
|
+
./run.sh uninstall-copilot-mcp # honest inverse of install-copilot-mcp from install-state
|
|
214
|
+
./run.sh doctor-copilot-mcp # static ownership/config/boot doctor; RED only when install-state exists
|
|
215
|
+
./run.sh install-copilot-receive # #82 RAIL 5: install the RECEIVER extension (user scope ~/.copilot/extensions/entwurf-receive). Owns the artifact and CHECKS the launch flag; it never sets one (a launch does — see `./run.sh copilot`). Arms per session after birth
|
|
216
|
+
./run.sh uninstall-copilot-receive # honest inverse from install-state; never removes a unit it did not install
|
|
217
|
+
./run.sh doctor-copilot-receive # artifact + digest, COPILOT_CLI_ENABLED_FEATURE_FLAGS on the LIVE copilot processes (the silent failure), live receiver markers via the production reader, receiver log. RED only when install-state exists
|
|
198
218
|
./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/
|
|
199
219
|
./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)
|
|
200
220
|
./run.sh probe-bridge-command <cmd> [args...] # #81: BOOT the given bridge invocation and require the entwurf MCP tool surface back. `--invocation-json '{"command":"…","args":[],"env":{}}'` preserves a harness config exactly. It waits for a valid initialize response, then sends initialized + tools/list only (no tools/call, lock, record, or delivery). exit 0 = it serves the bridge; 1 = it does not. The pi/agy doctors use this leaf.
|
|
@@ -213,13 +233,15 @@ Usage:
|
|
|
213
233
|
./run.sh check-dep-versions # local deterministic check that the pi pin agrees across package.json (devDeps + peer range), run.sh (peer-install pins), and the baseline docs (AGENTS/README/ROADMAP/setup-clean-host/demo)
|
|
214
234
|
./run.sh check-node-floor-coherence # binds the Node floor (24+, single axis) across engines.node, run.sh setup preflight, meta-bridge install/doctor judgment logic, clean-host docs, the bridge launcher header, and the CI runner node-version — engines.node is the SSOT, everything else is derived; sweeps tracked contract text for an unregistered declaration
|
|
215
235
|
./run.sh check-pack # publish gate (dry-run): npm pack --dry-run + tarball invariants (runtime-critical present, dev residue absent)
|
|
236
|
+
./run.sh check-pack-pin-matcher # pure self-test of check-pack-install's pin-leak matcher against synthetic .pnpm lookalikes (version boundary: @0.84.30 must leak, @0.84.3 bare/peer-hash must pass); snapshot-safe qualification oracle, also run first inside check-pack-install
|
|
216
237
|
./run.sh check-fresh-cut-gate # SOURCE cell of the generation-boundary proof (IN pnpm run check:full): drives real install/setup/fresh-cut in a sandbox; certification refusal is pre-write, quiescence is fail-closed, archives preserve bytes, and the #54 exit matrix distinguishes complete / no-move / usage / incomplete transition / complete-with-cleanup-residue. No model/network/cost
|
|
217
|
-
./run.sh check-pack-install # heavy publish gate (prepublishOnly): actual npm pack + tar -tf + fresh-temp install smoke with the pinned pi peers (
|
|
238
|
+
./run.sh check-pack-install # heavy publish gate (prepublishOnly): actual npm pack + tar -tf + fresh-temp install smoke with the pinned pi peers (pins derived from the package.json devDep; check-dep-versions binds them) + the npm-installed bridge BOOTS (tools/list) and DELIVERS (tools/call entwurf_v2 → .msg lands) + the installed all-absent and copilot-present (four-unit fake-vendor) `entwurf setup` rows + the INSTALLED generation lifecycle on a seeded previous-generation host (REFUSE before activation writes / zero Claude invocations → installed fresh-cut archives + opens empty → install-meta-bridge PASSES)
|
|
218
239
|
./run.sh check-install-container # 0.12.8 (#51 C): Linux artifact-CONSUMER gate — one candidate .tgz handed read-only to a checkout-invisible node:<engines-major>-bookworm cell. Default packs once to temp; ENTWURF_CANDIDATE_TGZ=/absolute/preserved.tgz consumes those exact bytes with no re-pack and prints canonical path+sha256 for release. Non-root global PATH install, frozen package, MCP tools/list, fake-Claude install-meta-bridge, path+sha256 fence, strict doctor, and the GENERATION host-state matrix (clean / v3-only store bytes unchanged / previous-generation REFUSE→fresh-cut→retry PASS) seeded inline. Docker missing = honest SKIP; ENTWURF_REQUIRE_DOCKER=1 makes that RED (required CI)
|
|
219
|
-
./run.sh sync-auth # copy ~/.pi/agent/auth.json anthropic OAuth credentials to entwurf alias
|
|
220
240
|
./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
|
|
221
241
|
./run.sh remove [project-dir] # remove entwurf entries from project .pi/settings.json (project scope only; global user-scope citizen left intact)
|
|
222
|
-
./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)
|
|
242
|
+
./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). #86 C2: same-owner-only — a LIVE foreign owner refuses; a MISSING owner is removed only when package entry + package state + provider installerRoot all align (reported orphan cleanup)
|
|
243
|
+
./run.sh takeover-user-scope # #86 C2: operator-EXPLICIT ownership move of the shared user-scope registration to THIS root (old→new reported, packages[] entry + provider installerRoot together). The only writer over another owner — normal install/setup/remove refuse instead. No --force exists
|
|
244
|
+
./run.sh doctor-pi-package # #86 C2: package-side user-scope ownership verdict — unregistered / owned / owned-by-other(live) / legacy-no-state / mismatch / missing-owner (nonzero on defect verdicts). Provider runtime verdicts stay with doctor-pi-provider
|
|
223
245
|
|
|
224
246
|
Notes:
|
|
225
247
|
- project-dir defaults to current directory
|
|
@@ -249,44 +271,12 @@ print(os.path.abspath(os.path.expanduser(sys.argv[1])))
|
|
|
249
271
|
PY
|
|
250
272
|
}
|
|
251
273
|
|
|
252
|
-
sync_auth()
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
auth_path = Path(sys.argv[1]).expanduser()
|
|
259
|
-
provider_id = sys.argv[2]
|
|
260
|
-
auth_path.parent.mkdir(parents=True, exist_ok=True)
|
|
261
|
-
|
|
262
|
-
if auth_path.exists():
|
|
263
|
-
data = json.loads(auth_path.read_text())
|
|
264
|
-
if not isinstance(data, dict):
|
|
265
|
-
raise SystemExit("auth.json is not an object")
|
|
266
|
-
else:
|
|
267
|
-
data = {}
|
|
268
|
-
|
|
269
|
-
anthropic = data.get("anthropic")
|
|
270
|
-
if not isinstance(anthropic, dict):
|
|
271
|
-
print("sync-auth: skipped (no anthropic OAuth credentials in ~/.pi/agent/auth.json)")
|
|
272
|
-
raise SystemExit(0)
|
|
273
|
-
|
|
274
|
-
before = json.dumps(data.get(provider_id), sort_keys=True)
|
|
275
|
-
after = json.dumps(anthropic, sort_keys=True)
|
|
276
|
-
if before == after:
|
|
277
|
-
print(f"sync-auth: already synced ({provider_id})")
|
|
278
|
-
raise SystemExit(0)
|
|
279
|
-
|
|
280
|
-
data[provider_id] = anthropic
|
|
281
|
-
backup = auth_path.with_suffix(auth_path.suffix + ".bak")
|
|
282
|
-
if auth_path.exists():
|
|
283
|
-
backup.write_text(auth_path.read_text())
|
|
284
|
-
auth_path.write_text(json.dumps(data, indent=2) + "\n")
|
|
285
|
-
print(f"sync-auth: wrote {provider_id} alias to {auth_path}")
|
|
286
|
-
if backup.exists():
|
|
287
|
-
print(f"sync-auth: backup -> {backup}")
|
|
288
|
-
PY
|
|
289
|
-
}
|
|
274
|
+
# The old `sync_auth` credential mutation is GONE (#86 A5): setup composes what
|
|
275
|
+
# the operator already installed and NEVER reads, copies, aliases or backs up a
|
|
276
|
+
# harness credential store — the `entwurf` alias it wrote had no in-repo reader
|
|
277
|
+
# (the ACP provider passes pi's auth check with the no-auth sentinel), and the
|
|
278
|
+
# `auth.json.bak` it left is documented manual-operator-cleanup ONLY. Do not
|
|
279
|
+
# reintroduce credential copying as a convenience helper in any install path.
|
|
290
280
|
|
|
291
281
|
# Repo dependency integrity is a HARD requirement for install — NOT gated on
|
|
292
282
|
# backend (Claude/ACP) auth. A cloned-but-not-installed or a moved/renamed repo
|
|
@@ -495,6 +485,14 @@ PY
|
|
|
495
485
|
}
|
|
496
486
|
|
|
497
487
|
install_local_package() {
|
|
488
|
+
# Named interpreter prerequisite BEFORE any preflight/write (#86 E5): the
|
|
489
|
+
# packages[]/provider writers below are python3-backed and `entwurf install`
|
|
490
|
+
# dispatches here directly, so a python3-less host gets this verdict instead
|
|
491
|
+
# of an accidental mid-write failure.
|
|
492
|
+
command -v python3 >/dev/null 2>&1 || {
|
|
493
|
+
echo "[install] entwurf install requires python3 on PATH (settings writers are python3-backed); nothing was written." >&2
|
|
494
|
+
exit 1
|
|
495
|
+
}
|
|
498
496
|
local project_dir agent_dir
|
|
499
497
|
project_dir=$(normalize_project_dir "$1")
|
|
500
498
|
agent_dir="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}"
|
|
@@ -534,19 +532,79 @@ install_local_package() {
|
|
|
534
532
|
# launchers and the npm consumer's "installs → just works" both need the entry
|
|
535
533
|
# in ~/.pi/agent/settings.json's packages[]. This is the wiring that dropped when
|
|
536
534
|
# `pi install` was removed from setup (2026-07-03: `--entwurf-control` unknown in
|
|
537
|
-
# a foreign cwd). Idempotent: absent → append
|
|
538
|
-
#
|
|
539
|
-
#
|
|
535
|
+
# a foreign cwd). Idempotent for THIS root: absent → append + owner state, present
|
|
536
|
+
# same-root → no-op. #86 C2 retires the silent normalization of other roots'
|
|
537
|
+
# entries: a different owner (live or missing) is a zero-write refusal that names
|
|
538
|
+
# takeover-user-scope. Every other package and key is preserved untouched.
|
|
539
|
+
_pi_package_state() { echo "${XDG_DATA_HOME:-$HOME/.local/share}/entwurf/pi-package/install-state.json"; }
|
|
540
|
+
_pi_provider_state() { echo "${XDG_DATA_HOME:-$HOME/.local/share}/entwurf/pi-provider/install-state.json"; }
|
|
541
|
+
|
|
540
542
|
register_user_scope_citizen() {
|
|
541
543
|
local agent_dir="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}"
|
|
542
544
|
# Shared idempotent implementation (also driven by smoke-user-scope-citizen).
|
|
543
|
-
|
|
545
|
+
# #86 C2: user scope carries a recorded OWNER (packageRoot in the pi-package
|
|
546
|
+
# install-state). Another owner — live or missing — makes this a zero-write
|
|
547
|
+
# ownership refusal (exit 6) that propagates: install fails, setup records the
|
|
548
|
+
# pi component FAIL. The only writer over another owner is takeover-user-scope.
|
|
549
|
+
#
|
|
550
|
+
# ATOMIC (#86 C2 amendment): BOTH ownership halves are decided READ-ONLY before
|
|
551
|
+
# either writer runs — a provider-side refusal must leave the package side
|
|
552
|
+
# byte-identical (and vice versa), never a half-installed user scope.
|
|
553
|
+
python3 "$REPO_DIR/scripts/register-pi-package.py" "$agent_dir/settings.json" "$REPO_DIR" \
|
|
554
|
+
--scope user --state "$(_pi_package_state)" --preflight >/dev/null
|
|
555
|
+
python3 "$REPO_DIR/scripts/register-pi-provider.py" install "$agent_dir/settings.json" "$REPO_DIR" \
|
|
556
|
+
--scope user --state "$(_pi_provider_state)" --preflight >/dev/null
|
|
557
|
+
python3 "$REPO_DIR/scripts/register-pi-package.py" "$agent_dir/settings.json" "$REPO_DIR" \
|
|
558
|
+
--scope user --state "$(_pi_package_state)"
|
|
544
559
|
# #46 Task 2: own entwurfProvider.mcpServers.entwurf-bridge as the bare stable bin at USER scope
|
|
545
560
|
# (GLOBAL/durable →파급s to every cwd), so its inverse needs an install-state honest inverse
|
|
546
561
|
# under $XDG_DATA_HOME/entwurf/pi-provider/ (Task 0/1 discipline). project scope is checkout-
|
|
547
562
|
# local and covered by `run.sh remove` (no state) — deliberate, reasoned asymmetry.
|
|
548
|
-
|
|
549
|
-
|
|
563
|
+
# #86 C2: the provider state records installerRoot, so the same foreign-owner refusal
|
|
564
|
+
# holds here — and the package refusal above fires FIRST, before any provider write.
|
|
565
|
+
python3 "$REPO_DIR/scripts/register-pi-provider.py" install "$agent_dir/settings.json" "$REPO_DIR" --scope user --state "$(_pi_provider_state)"
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
# takeover-user-scope — the operator-explicit ownership move (#86 C2). The ONLY
|
|
569
|
+
# writer that replaces another root's user-scope registration: normal install/
|
|
570
|
+
# setup/remove refuse instead. Reports old→new for both the packages[] entry
|
|
571
|
+
# (packageRoot) and the provider key (installerRoot). No --force variant exists.
|
|
572
|
+
takeover_user_scope() {
|
|
573
|
+
local agent_dir="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}" pp_out
|
|
574
|
+
command -v python3 >/dev/null 2>&1 || { echo "[takeover] requires python3 on PATH; nothing was written." >&2; exit 1; }
|
|
575
|
+
section "takeover-user-scope: move the shared user-scope registration to this root"
|
|
576
|
+
# ATOMIC (#86 C2 amendment): both halves decided READ-ONLY first — an exact-entry
|
|
577
|
+
# mismatch on the package side or a refusal on the provider side leaves the other
|
|
578
|
+
# half byte-identical.
|
|
579
|
+
python3 "$REPO_DIR/scripts/register-pi-package.py" "$agent_dir/settings.json" "$REPO_DIR" \
|
|
580
|
+
--scope user --state "$(_pi_package_state)" --takeover --preflight >/dev/null
|
|
581
|
+
python3 "$REPO_DIR/scripts/register-pi-provider.py" install "$agent_dir/settings.json" "$REPO_DIR" \
|
|
582
|
+
--scope user --state "$(_pi_provider_state)" --takeover --preflight >/dev/null
|
|
583
|
+
python3 "$REPO_DIR/scripts/register-pi-package.py" "$agent_dir/settings.json" "$REPO_DIR" \
|
|
584
|
+
--scope user --state "$(_pi_package_state)" --takeover
|
|
585
|
+
pp_out=$(python3 "$REPO_DIR/scripts/register-pi-provider.py" install "$agent_dir/settings.json" "$REPO_DIR" \
|
|
586
|
+
--scope user --state "$(_pi_provider_state)" --takeover)
|
|
587
|
+
printf '%s\n' "$pp_out"
|
|
588
|
+
# Split verdict (#86 C2 amendment): a user-override provider key is preserved and
|
|
589
|
+
# stays UNOWNED — say exactly which half moved instead of a false "both owned".
|
|
590
|
+
if printf '%s' "$pp_out" | grep -q 'provider override preserved'; then
|
|
591
|
+
ok "takeover-user-scope: package owner moved to this root ($REPO_DIR); provider key preserved as the operator's override (unowned — doctor-pi-provider reports it as such)"
|
|
592
|
+
else
|
|
593
|
+
ok "takeover-user-scope: this root now owns the user-scope registration ($REPO_DIR)"
|
|
594
|
+
fi
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
# doctor-pi-package — package-side ownership verdict only (#86 C2): unregistered /
|
|
598
|
+
# owned / owned-by-other(live) / legacy-no-state / mismatch / missing-owner.
|
|
599
|
+
# Deliberately NOT mixed into doctor-pi-provider (provider runtime verdicts stay there).
|
|
600
|
+
doctor_pi_package() {
|
|
601
|
+
local agent_dir="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}"
|
|
602
|
+
command -v python3 >/dev/null 2>&1 || { echo "[doctor-pi-package] requires python3 on PATH." >&2; exit 1; }
|
|
603
|
+
# #86 C2 amendment: the provider state rides along for the OWNERSHIP coupling
|
|
604
|
+
# verdict only (installerRoot vs packageRoot mismatch = FAIL); provider RUNTIME
|
|
605
|
+
# verdicts stay with doctor-pi-provider.
|
|
606
|
+
python3 "$REPO_DIR/scripts/register-pi-package.py" "$agent_dir/settings.json" "$REPO_DIR" \
|
|
607
|
+
--scope user --state "$(_pi_package_state)" --provider-state "$(_pi_provider_state)" --doctor
|
|
550
608
|
}
|
|
551
609
|
|
|
552
610
|
# The honest inverse of register_user_scope_citizen: drop entwurf from the GLOBAL
|
|
@@ -562,11 +620,54 @@ register_user_scope_citizen() {
|
|
|
562
620
|
# reported rather than deleted — install never writes that form (#53 B).
|
|
563
621
|
remove_user_scope_citizen() {
|
|
564
622
|
local agent_dir="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}"
|
|
565
|
-
|
|
623
|
+
local pkg_state pp_state orphan_args=()
|
|
624
|
+
pkg_state="$(_pi_package_state)"; pp_state="$(_pi_provider_state)"
|
|
625
|
+
# #86 C2 aligned orphan path: --orphan-cleanup is passed ONLY when the package
|
|
626
|
+
# entry, the package state's packageRoot and the provider state's installerRoot
|
|
627
|
+
# all name the SAME root and that root is MISSING on disk. Any live foreign
|
|
628
|
+
# owner, or any mismatch between the three, refuses inside the SSOTs instead.
|
|
629
|
+
if python3 - "$agent_dir/settings.json" "$pkg_state" "$pp_state" <<'PY'
|
|
630
|
+
import json, os, sys
|
|
631
|
+
settings_path, pkg_state_path, pp_state_path = sys.argv[1:4]
|
|
632
|
+
def load(p):
|
|
633
|
+
try:
|
|
634
|
+
with open(p) as fh: return json.load(fh)
|
|
635
|
+
except (OSError, json.JSONDecodeError): return None
|
|
636
|
+
pkg = load(pkg_state_path)
|
|
637
|
+
if not isinstance(pkg, dict) or not isinstance(pkg.get("packageRoot"), str): sys.exit(1)
|
|
638
|
+
owner = pkg["packageRoot"]
|
|
639
|
+
if os.path.isdir(owner): sys.exit(1) # live owner: never orphan
|
|
640
|
+
# Alignment REQUIRES the provider state to exist AND to be bound to the same
|
|
641
|
+
# missing root (#86 C2 amendment): an absent, legacy (no installerRoot) or
|
|
642
|
+
# elsewhere-bound provider state is NOT alignment — both sides refuse instead.
|
|
643
|
+
pp = load(pp_state_path)
|
|
644
|
+
if not isinstance(pp, dict): sys.exit(1)
|
|
645
|
+
if pp.get("installerRoot") != owner: sys.exit(1)
|
|
646
|
+
settings = load(settings_path)
|
|
647
|
+
pkgs = settings.get("packages") if isinstance(settings, dict) else None
|
|
648
|
+
if not isinstance(pkgs, list): sys.exit(1)
|
|
649
|
+
def src(x): return x.get("source") if isinstance(x, dict) else x
|
|
650
|
+
if not any(isinstance(src(x), str) and src(x).rstrip("/") == owner for x in pkgs): sys.exit(1)
|
|
651
|
+
sys.exit(0)
|
|
652
|
+
PY
|
|
653
|
+
then
|
|
654
|
+
orphan_args=(--orphan-cleanup)
|
|
655
|
+
log "remove-user-scope: package entry + package state + provider installerRoot align on a MISSING owner — reported orphan cleanup"
|
|
656
|
+
fi
|
|
657
|
+
# ATOMIC (#86 C2 amendment): both removal halves are decided READ-ONLY first, so
|
|
658
|
+
# a provider-side refusal (live-other, legacy, mismatch) leaves the package
|
|
659
|
+
# entry/state byte-identical instead of a half-removed user scope.
|
|
660
|
+
python3 "$REPO_DIR/scripts/register-pi-package.py" "$agent_dir/settings.json" "$REPO_DIR" \
|
|
661
|
+
--scope user --state "$pkg_state" --remove ${orphan_args[@]+"${orphan_args[@]}"} --preflight >/dev/null
|
|
662
|
+
python3 "$REPO_DIR/scripts/register-pi-provider.py" remove "$agent_dir/settings.json" "$REPO_DIR" \
|
|
663
|
+
--scope user --state "$pp_state" ${orphan_args[@]+"${orphan_args[@]}"} --preflight >/dev/null
|
|
664
|
+
python3 "$REPO_DIR/scripts/register-pi-package.py" "$agent_dir/settings.json" "$REPO_DIR" \
|
|
665
|
+
--scope user --state "$pkg_state" --remove ${orphan_args[@]+"${orphan_args[@]}"}
|
|
566
666
|
# #46 Task 2: honest inverse of the user-scope entwurfProvider ownership — the install-state
|
|
567
667
|
# drives it (absent/managed-* → remove OUR key; a user-override we never owned is untouched).
|
|
568
|
-
|
|
569
|
-
python3 "$REPO_DIR/scripts/register-pi-provider.py" remove "$agent_dir/settings.json" "$REPO_DIR"
|
|
668
|
+
# #86 C2: installerRoot makes this same-owner-only; the aligned orphan flag rides along.
|
|
669
|
+
python3 "$REPO_DIR/scripts/register-pi-provider.py" remove "$agent_dir/settings.json" "$REPO_DIR" \
|
|
670
|
+
--scope user --state "$pp_state" ${orphan_args[@]+"${orphan_args[@]}"}
|
|
570
671
|
}
|
|
571
672
|
|
|
572
673
|
# The ~/.pi/agent/entwurf-targets.json symlink machinery (ensure_agent_dir_symlinks
|
|
@@ -738,6 +839,41 @@ check_meta_receiver_marker() {
|
|
|
738
839
|
run_ts scripts/check-meta-receiver-marker.ts
|
|
739
840
|
}
|
|
740
841
|
|
|
842
|
+
check_copilot_birth_hook() {
|
|
843
|
+
# #82 gate: the Copilot BIRTH path, proven without Copilot. Drives the REAL
|
|
844
|
+
# assembler (--assemble-only, into a temp dir), then fires the baked launcher the
|
|
845
|
+
# way Copilot fires it — NO ARGV, envelope on stdin — and requires a v3 record with
|
|
846
|
+
# backend "copilot", an attach on the second event of the same prompt, no
|
|
847
|
+
# mailbox/marker of any kind, and a peer row with liveness `unsupported`. Refusals
|
|
848
|
+
# (disagreeing ids, missing cwd, no id, malformed) must refuse rather than guess.
|
|
849
|
+
# It proves the MECHANISM, never the admission: §6 acceptance is a record minted by
|
|
850
|
+
# a real Copilot session, which costs a model turn.
|
|
851
|
+
run_ts scripts/check-copilot-birth-hook.ts
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
check_copilot_statusline() {
|
|
855
|
+
# #82 Copilot custom-footer renderer. Drives scripts/copilot-statusline.sh with
|
|
856
|
+
# session_id on stdin in an isolated store. ready / ? / exact gid; exit 0.
|
|
857
|
+
run_ts scripts/check-copilot-statusline.ts
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
check_copilot_launch() {
|
|
861
|
+
# #82 RAIL 7. The subject is a process REPLACEMENT, so the oracle is a fake vendor
|
|
862
|
+
# executable on a sandboxed PATH that reports the argv/env/pid/cwd it was handed —
|
|
863
|
+
# never a reading of the launcher's source. HOME/XDG/PATH are all redirected, so the
|
|
864
|
+
# install-state, the extensions root and the resolved binary are the fixture's.
|
|
865
|
+
run_ts scripts/check-copilot-launch.ts
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
check_copilot_receive_arm() {
|
|
869
|
+
# #82 RAIL 5 gate: drives the REAL receiver installer into a temp extensions dir, then
|
|
870
|
+
# forks the REAL extension.mjs with the SDK specifier resolved by a loader hook (the
|
|
871
|
+
# vendor's own mechanism). Binds arm-after-birth, watcher-owned marker, the self-fetch
|
|
872
|
+
# dispatch answer, a doorbell that carries the id and not the body, and every refusal.
|
|
873
|
+
# Needs the compiled bridge closure; hermetic otherwise (no Copilot, no model turn).
|
|
874
|
+
run_ts scripts/check-copilot-receive-arm.ts
|
|
875
|
+
}
|
|
876
|
+
|
|
741
877
|
check_hook_launch_topology() {
|
|
742
878
|
# #51 gate 1, deliberately built only AFTER the B/B2 verdict. Binds the SHIPPED
|
|
743
879
|
# hooks.json to real child topology: every leaf is exec form through the shipped
|
|
@@ -1099,7 +1235,10 @@ check_mux_fresh_call() {
|
|
|
1099
1235
|
# could not see: real bridge boot → tools/list schema/description, Rust-regex-family pattern
|
|
1100
1236
|
# validity on every emitted pattern (the #62 escape), and the schema riding the actual
|
|
1101
1237
|
# anthropic-messages request body. No fake tmux: the real-window axis is smoke-mux-fresh-call-live.
|
|
1102
|
-
|
|
1238
|
+
# copilot-fresh-preflight rides here rather than in its own gate: it exists only as
|
|
1239
|
+
# freshCall's pre-mutation branch (#82 RAIL 9), and splitting it would let the two halves
|
|
1240
|
+
# of one refusal be certified in different runs.
|
|
1241
|
+
run_vitest test/mux-fresh-call.test.ts test/copilot-fresh-preflight.test.ts test/fresh-call-surfaces.contract.test.ts test/fresh-call-provider.contract.test.ts
|
|
1103
1242
|
}
|
|
1104
1243
|
|
|
1105
1244
|
smoke_mux_fresh_call_live() {
|
|
@@ -1629,13 +1768,13 @@ assert.equal(peerTui, piAi,
|
|
|
1629
1768
|
// floor tracks the devDep pin so a consumer can't install against a pi lacking
|
|
1630
1769
|
// the public trust exports the bridge imports at the pinned minor, AND an upper
|
|
1631
1770
|
// bound at the next minor stops a fresh install from silently pulling a future
|
|
1632
|
-
// pi (past the declared ceiling — 0.85+ at the current 0.84.
|
|
1771
|
+
// pi (past the declared ceiling — 0.85+ at the current 0.84.3 pin) whose
|
|
1633
1772
|
// internal export surface has drifted from the one we typecheck against.
|
|
1634
1773
|
// pi moves its public surface every minor (the 0.79→0.80 getModels→provider-
|
|
1635
1774
|
// factory churn is exactly this), so an open `>=` floor is exactly how the next
|
|
1636
1775
|
// installer re-acquires the drift. The floor is also the HARD MINIMUM a consumer
|
|
1637
|
-
// install resolves: at `>=0.84.
|
|
1638
|
-
// Expected shape: `>=<devDep> <0.<minor+1>` (e.g. `>=0.84.
|
|
1776
|
+
// install resolves: at `>=0.84.3` an existing 0.83.x host is upgraded, not kept.
|
|
1777
|
+
// Expected shape: `>=<devDep> <0.<minor+1>` (e.g. `>=0.84.3 <0.85`).
|
|
1639
1778
|
const [piMaj, piMin] = piAi.split('.').map(Number);
|
|
1640
1779
|
assert.equal(piMaj, 0,
|
|
1641
1780
|
`pi pin major must stay 0 for the next-minor ceiling rule (got ${piAi}); revisit check-dep-versions when pi reaches 1.x`);
|
|
@@ -2757,6 +2896,9 @@ check_pack() {
|
|
|
2757
2896
|
# strip-types under node_modules). meta-session.js is shared with the store-doctor
|
|
2758
2897
|
# above; listed here too so the hook axis fails loud if the emit graph drops it.
|
|
2759
2898
|
"mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js"
|
|
2899
|
+
# #82 — the Copilot birth entry's compiled closure, for the same node_modules
|
|
2900
|
+
# strip-types refusal that forces the Claude one.
|
|
2901
|
+
"mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook-copilot.js"
|
|
2760
2902
|
"mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js"
|
|
2761
2903
|
"scripts/postinstall-chmod.cjs"
|
|
2762
2904
|
"pi/entwurf-capabilities.json"
|
|
@@ -2769,6 +2911,25 @@ check_pack() {
|
|
|
2769
2911
|
# It rides a per-FILE entry in the files array (not a whole directory), so one
|
|
2770
2912
|
# deleted line drops it from the tarball while every other gate stays green.
|
|
2771
2913
|
"pi/meta-bridge/entwurf-meta-receive/scripts/hook-launch.sh"
|
|
2914
|
+
# #82 — the Copilot BIRTH unit. It is a SECOND marketplace root, not a second
|
|
2915
|
+
# plugin inside the Claude one: the Claude installer assembles one root and copies
|
|
2916
|
+
# one plugin out of it, so a shared marketplace.json would publish a `source` the
|
|
2917
|
+
# assembly does not contain. Same per-FILE listing discipline as the Claude unit.
|
|
2918
|
+
"pi/meta-bridge-copilot/.claude-plugin/marketplace.json"
|
|
2919
|
+
"pi/meta-bridge-copilot/entwurf-meta-receive-copilot/.claude-plugin/plugin.json"
|
|
2920
|
+
"pi/meta-bridge-copilot/entwurf-meta-receive-copilot/hooks/hooks.json"
|
|
2921
|
+
"pi/meta-bridge-copilot/entwurf-meta-receive-copilot/scripts/copilot-hook-launch.sh"
|
|
2922
|
+
"pi-extensions/meta-bridge-hook-copilot.ts"
|
|
2923
|
+
"scripts/copilot-bridge-install.sh"
|
|
2924
|
+
"scripts/copilot-bridge-uninstall.sh"
|
|
2925
|
+
"scripts/copilot-bridge-oracle.sh"
|
|
2926
|
+
"scripts/copilot-bridge-doctor.sh"
|
|
2927
|
+
# #82 RAIL 5 — the Copilot RECEIVER. A vendor EXTENSION (forked child, stdio
|
|
2928
|
+
# JSON-RPC), not a plugin, so it ships as its own unit and installs into the user
|
|
2929
|
+
# extensions dir. `extension.mjs` is the vendor's required entry NAME: a rename in
|
|
2930
|
+
# the tarball is not a degraded path, it is a unit Copilot never discovers.
|
|
2931
|
+
"pi/copilot-receive/entwurf-receive/extension.mjs"
|
|
2932
|
+
"scripts/copilot-receive-bridge.sh"
|
|
2772
2933
|
"pi-extensions/meta-bridge-hook.ts"
|
|
2773
2934
|
"pi-extensions/lib/meta-session.ts"
|
|
2774
2935
|
"pi-extensions/lib/session-id.js"
|
|
@@ -2836,7 +2997,41 @@ check_pack() {
|
|
|
2836
2997
|
return 1
|
|
2837
2998
|
}
|
|
2838
2999
|
|
|
3000
|
+
# The pin-leak filter for the install tree's .pnpm listing, shared by the real scan and its
|
|
3001
|
+
# self-test below. The version BOUNDARY is load-bearing: a pnpm .pnpm entry is
|
|
3002
|
+
# `<name>@<version>` followed by either `_<peer-hash>` or end-of-name (measured pnpm 11.20.0
|
|
3003
|
+
# on this tree), so an unbounded substring match would bless a lookalike such as `@0.84.30`
|
|
3004
|
+
# while announcing the pinned floor — a false-green oracle (found by independent review,
|
|
3005
|
+
# 2026-08-25).
|
|
3006
|
+
pack_install_leaked_pi() {
|
|
3007
|
+
grep '^@earendil-works+pi-' | grep -Ev '@0\.84\.3(_|$)' || true
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
# Matcher self-test on SYNTHETIC lookalikes: a healthy install tree cannot exercise the
|
|
3011
|
+
# false-green shape (it contains no 0.84.30), so the oracle is proven against a fixture
|
|
3012
|
+
# listing. Expected: the two lookalikes leak, the pinned version passes bare and with a
|
|
3013
|
+
# peer-hash suffix. Exposed as its own snapshot-safe subcommand because the heavy
|
|
3014
|
+
# check-pack-install cannot run inside the qualification snapshot (no install environment),
|
|
3015
|
+
# and a kill-proof needs a control-green gate there; check-pack-install still runs this
|
|
3016
|
+
# first so the heavy gate cannot proceed on a broken oracle.
|
|
3017
|
+
check_pack_pin_matcher() {
|
|
3018
|
+
local matcher_probe
|
|
3019
|
+
matcher_probe=$(printf '%s\n' \
|
|
3020
|
+
'@earendil-works+pi-ai@0.84.3' \
|
|
3021
|
+
'@earendil-works+pi-ai@0.84.3_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6' \
|
|
3022
|
+
'@earendil-works+pi-ai@0.84.30' \
|
|
3023
|
+
'@earendil-works+pi-agent-core@0.84.2' | pack_install_leaked_pi)
|
|
3024
|
+
if [ "$matcher_probe" != '@earendil-works+pi-ai@0.84.30
|
|
3025
|
+
@earendil-works+pi-agent-core@0.84.2' ]; then
|
|
3026
|
+
fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the 0.84.30/0.84.2 lookalikes and pass 0.84.3 bare or with a peer-hash — got: ${matcher_probe:-<nothing leaked>}"
|
|
3027
|
+
return 1
|
|
3028
|
+
fi
|
|
3029
|
+
echo "[check-pack-pin-matcher] ok — the pin-leak matcher is version-bounded (lookalikes leak, pinned version passes bare and with a peer-hash)"
|
|
3030
|
+
}
|
|
3031
|
+
|
|
2839
3032
|
_check_pack_install_impl() {
|
|
3033
|
+
check_pack_pin_matcher || return 1
|
|
3034
|
+
|
|
2840
3035
|
# Heavy publish gate. Runs the remaining three checks in #13's
|
|
2841
3036
|
# publish checklist that check_pack (dry-run only) does not cover:
|
|
2842
3037
|
#
|
|
@@ -2954,6 +3149,9 @@ _check_pack_install_impl() {
|
|
|
2954
3149
|
# 0.12.5 — node_modules-safe plugin hook + lib (see check-pack). The installed
|
|
2955
3150
|
# hook regression below runs exactly this compiled JS from under node_modules.
|
|
2956
3151
|
"mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js"
|
|
3152
|
+
# #82 — the Copilot birth entry's compiled closure, for the same node_modules
|
|
3153
|
+
# strip-types refusal that forces the Claude one.
|
|
3154
|
+
"mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook-copilot.js"
|
|
2957
3155
|
"mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js"
|
|
2958
3156
|
"scripts/postinstall-chmod.cjs"
|
|
2959
3157
|
"pi/entwurf-capabilities.json"
|
|
@@ -2965,6 +3163,25 @@ _check_pack_install_impl() {
|
|
|
2965
3163
|
# both callers source. The install smoke below asserts the ASSEMBLED launcher is
|
|
2966
3164
|
# executable; this asserts the artifact it is assembled FROM actually shipped.
|
|
2967
3165
|
"pi/meta-bridge/entwurf-meta-receive/scripts/hook-launch.sh"
|
|
3166
|
+
# #82 — the Copilot BIRTH unit. It is a SECOND marketplace root, not a second
|
|
3167
|
+
# plugin inside the Claude one: the Claude installer assembles one root and copies
|
|
3168
|
+
# one plugin out of it, so a shared marketplace.json would publish a `source` the
|
|
3169
|
+
# assembly does not contain. Same per-FILE listing discipline as the Claude unit.
|
|
3170
|
+
"pi/meta-bridge-copilot/.claude-plugin/marketplace.json"
|
|
3171
|
+
"pi/meta-bridge-copilot/entwurf-meta-receive-copilot/.claude-plugin/plugin.json"
|
|
3172
|
+
"pi/meta-bridge-copilot/entwurf-meta-receive-copilot/hooks/hooks.json"
|
|
3173
|
+
"pi/meta-bridge-copilot/entwurf-meta-receive-copilot/scripts/copilot-hook-launch.sh"
|
|
3174
|
+
"pi-extensions/meta-bridge-hook-copilot.ts"
|
|
3175
|
+
"scripts/copilot-bridge-install.sh"
|
|
3176
|
+
"scripts/copilot-bridge-uninstall.sh"
|
|
3177
|
+
"scripts/copilot-bridge-oracle.sh"
|
|
3178
|
+
"scripts/copilot-bridge-doctor.sh"
|
|
3179
|
+
# #82 RAIL 5 — the Copilot RECEIVER. A vendor EXTENSION (forked child, stdio
|
|
3180
|
+
# JSON-RPC), not a plugin, so it ships as its own unit and installs into the user
|
|
3181
|
+
# extensions dir. `extension.mjs` is the vendor's required entry NAME: a rename in
|
|
3182
|
+
# the tarball is not a degraded path, it is a unit Copilot never discovers.
|
|
3183
|
+
"pi/copilot-receive/entwurf-receive/extension.mjs"
|
|
3184
|
+
"scripts/copilot-receive-bridge.sh"
|
|
2968
3185
|
"pi-extensions/meta-bridge-hook.ts"
|
|
2969
3186
|
"pi-extensions/lib/meta-session.ts"
|
|
2970
3187
|
"pi-extensions/lib/session-id.js"
|
|
@@ -3013,9 +3230,9 @@ _check_pack_install_impl() {
|
|
|
3013
3230
|
# to our pnpm-workspace.yaml; --ignore-scripts blocks the husky
|
|
3014
3231
|
# prepare hook (and any future install scripts) from running inside
|
|
3015
3232
|
# the consumer project. Peer deps are pinned to the CURRENT pi release
|
|
3016
|
-
# baseline (
|
|
3017
|
-
#
|
|
3018
|
-
# pi user would have after `pi install`.
|
|
3233
|
+
# baseline (the package.json devDep pin — the pins below are what
|
|
3234
|
+
# check-dep-versions binds to that devDep) so the smoke matches the same
|
|
3235
|
+
# shape an external pi user would have after `pi install`.
|
|
3019
3236
|
local tmp npm_tmp
|
|
3020
3237
|
tmp=$(mktemp -d -t entwurf-install-smoke.XXXXXX)
|
|
3021
3238
|
# Separate tree for the npm-managed regression below: npm install must NOT be
|
|
@@ -3027,7 +3244,7 @@ _check_pack_install_impl() {
|
|
|
3027
3244
|
printf '%s\n' '{ "name": "entwurf-install-smoke", "version": "0.0.0", "private": true }' > "$tmp/package.json"
|
|
3028
3245
|
|
|
3029
3246
|
# pi-agent-core is pinned even though we never import it: pi-coding-agent depends
|
|
3030
|
-
# on it by CARET (`^0.84.
|
|
3247
|
+
# on it by CARET (`^0.84.3`), so with no lockfile in this fresh temp project it
|
|
3031
3248
|
# floats to whatever pi published last — and that newer core then drags a NESTED
|
|
3032
3249
|
# pi-ai of its own. Measured 2026-07-21: pinning only the three we import left
|
|
3033
3250
|
# pi-agent-core@0.80.10 + pi-ai@0.80.10 in the tree while the gate still announced
|
|
@@ -3046,12 +3263,12 @@ _check_pack_install_impl() {
|
|
|
3046
3263
|
local install_log
|
|
3047
3264
|
install_log=$(cd "$tmp" && pnpm add \
|
|
3048
3265
|
"$tgz_path" \
|
|
3049
|
-
"@earendil-works/pi-ai@0.84.
|
|
3050
|
-
"@earendil-works/pi-coding-agent@0.84.
|
|
3051
|
-
"@earendil-works/pi-tui@0.84.
|
|
3052
|
-
"@earendil-works/pi-agent-core@0.84.
|
|
3053
|
-
"@earendil-works/pi-client@0.84.
|
|
3054
|
-
"@earendil-works/pi-protocol@0.84.
|
|
3266
|
+
"@earendil-works/pi-ai@0.84.3" \
|
|
3267
|
+
"@earendil-works/pi-coding-agent@0.84.3" \
|
|
3268
|
+
"@earendil-works/pi-tui@0.84.3" \
|
|
3269
|
+
"@earendil-works/pi-agent-core@0.84.3" \
|
|
3270
|
+
"@earendil-works/pi-client@0.84.3" \
|
|
3271
|
+
"@earendil-works/pi-protocol@0.84.3" \
|
|
3055
3272
|
"typebox@latest" \
|
|
3056
3273
|
--ignore-workspace --ignore-scripts 2>&1) || {
|
|
3057
3274
|
fail "[check-pack-install] pnpm add failed:"
|
|
@@ -3061,17 +3278,17 @@ _check_pack_install_impl() {
|
|
|
3061
3278
|
|
|
3062
3279
|
# A pin is a wish until the resolved tree is read back. Assert it: EVERY
|
|
3063
3280
|
# @earendil-works pi package present — direct or transitive, top level or nested —
|
|
3064
|
-
# must be the pinned 0.84.
|
|
3281
|
+
# must be the pinned 0.84.3. Anything else means an unpinned caret floated and the
|
|
3065
3282
|
# rest of this gate would be exercising a runtime nobody verified, while still
|
|
3066
|
-
# printing "pinned pi 0.84.
|
|
3283
|
+
# printing "pinned pi 0.84.3". Fail loud instead of proving the wrong floor.
|
|
3067
3284
|
local leaked_pi
|
|
3068
|
-
leaked_pi=$(ls "$tmp/node_modules/.pnpm" 2>/dev/null |
|
|
3285
|
+
leaked_pi=$(ls "$tmp/node_modules/.pnpm" 2>/dev/null | pack_install_leaked_pi)
|
|
3069
3286
|
if [ -n "$leaked_pi" ]; then
|
|
3070
|
-
fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 0.84.
|
|
3287
|
+
fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 0.84.3):"
|
|
3071
3288
|
printf '%s\n' "$leaked_pi" | sed 's/^/ /' >&2
|
|
3072
3289
|
return 1
|
|
3073
3290
|
fi
|
|
3074
|
-
echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works pi package is 0.84.
|
|
3291
|
+
echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works pi package is 0.84.3"
|
|
3075
3292
|
|
|
3076
3293
|
# Resolve the installed package.json and confirm pi.extensions
|
|
3077
3294
|
# arrived intact. If pi.extensions is empty or missing, the
|
|
@@ -3224,6 +3441,11 @@ _check_pack_install_impl() {
|
|
|
3224
3441
|
ls -l "$npmroot/node_modules/.bin" 2>/dev/null | sed 's/^/ /' >&2 || true
|
|
3225
3442
|
return 1
|
|
3226
3443
|
fi
|
|
3444
|
+
if [ ! -x "$npmroot/node_modules/.bin/entwurf-copilot-statusline" ]; then
|
|
3445
|
+
fail "[check-pack-install] npm-managed neutral install missing package bin entwurf-copilot-statusline"
|
|
3446
|
+
ls -l "$npmroot/node_modules/.bin" 2>/dev/null | sed 's/^/ /' >&2 || true
|
|
3447
|
+
return 1
|
|
3448
|
+
fi
|
|
3227
3449
|
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) || {
|
|
3228
3450
|
fail "[check-pack-install] npm-managed run.sh install failed (preflight rejected hoisted deps?):"
|
|
3229
3451
|
echo "$wire_log" | tail -15 | sed 's/^/ /' >&2
|
|
@@ -3278,6 +3500,230 @@ sys.exit(0 if any(isinstance(s,str) and s.endswith('/node_modules/@junghanacs/en
|
|
|
3278
3500
|
fi
|
|
3279
3501
|
echo "[check-pack-install] user-scope citizen regression pass (npm consumer: --entwurf-control loads from a foreign cwd)"
|
|
3280
3502
|
|
|
3503
|
+
# Two-root ownership row (#86 C2, L1b extension): a SECOND npm root under the
|
|
3504
|
+
# SAME consumer HOME must not silently steal the user-scope registration the
|
|
3505
|
+
# first root owns — normal install refuses (naming takeover-user-scope) and the
|
|
3506
|
+
# operator-explicit takeover is what moves the shared entry, old→new reported.
|
|
3507
|
+
local npmroot2="$npm_tmp/npmroot2" npmproj2="$npm_tmp/npmproj2" npm2_log two_root_out two_root_rc npm2_pkg
|
|
3508
|
+
mkdir -p "$npmroot2" "$npmproj2"
|
|
3509
|
+
npm2_log=$(cd "$npmroot2" && npm install "$tgz_path" --no-audit --no-fund 2>&1) || {
|
|
3510
|
+
fail "[check-pack-install] second npm root install failed:"
|
|
3511
|
+
echo "$npm2_log" | tail -10 | sed 's/^/ /' >&2
|
|
3512
|
+
return 1
|
|
3513
|
+
}
|
|
3514
|
+
npm2_pkg="$npmroot2/node_modules/@junghanacs/entwurf"
|
|
3515
|
+
set +e
|
|
3516
|
+
two_root_out=$(HOME="$npmhome" XDG_DATA_HOME="$npmhome/.local/share" XDG_STATE_HOME="$npmhome/.local/state" XDG_CACHE_HOME="$npmhome/.cache" "$npm2_pkg/run.sh" install "$npmproj2" 2>&1)
|
|
3517
|
+
two_root_rc=$?
|
|
3518
|
+
set -e
|
|
3519
|
+
if [ "$two_root_rc" -eq 0 ] || ! grep -q "takeover-user-scope" <<<"$two_root_out"; then
|
|
3520
|
+
fail "[check-pack-install] a second npm root's normal install must REFUSE the owned user-scope registration and name takeover-user-scope (rc=$two_root_rc):"
|
|
3521
|
+
echo "$two_root_out" | tail -10 | sed 's/^/ /' >&2
|
|
3522
|
+
return 1
|
|
3523
|
+
fi
|
|
3524
|
+
if ! python3 -c "
|
|
3525
|
+
import json,sys
|
|
3526
|
+
p=json.load(open('$npm_user_settings')).get('packages',[])
|
|
3527
|
+
srcs=[(x if isinstance(x,str) else x.get('source')) for x in p]
|
|
3528
|
+
ok_a=any(isinstance(s,str) and s.rstrip('/')== '$npm_pkg' for s in srcs)
|
|
3529
|
+
bad_b=any(isinstance(s,str) and s.rstrip('/')== '$npm2_pkg' for s in srcs)
|
|
3530
|
+
sys.exit(0 if ok_a and not bad_b else 1)
|
|
3531
|
+
"; then
|
|
3532
|
+
fail "[check-pack-install] the refused second-root install still altered the user-scope packages[] entry:"
|
|
3533
|
+
sed 's/^/ /' "$npm_user_settings" >&2
|
|
3534
|
+
return 1
|
|
3535
|
+
fi
|
|
3536
|
+
set +e
|
|
3537
|
+
two_root_out=$(HOME="$npmhome" XDG_DATA_HOME="$npmhome/.local/share" XDG_STATE_HOME="$npmhome/.local/state" XDG_CACHE_HOME="$npmhome/.cache" "$npm2_pkg/run.sh" takeover-user-scope 2>&1)
|
|
3538
|
+
two_root_rc=$?
|
|
3539
|
+
set -e
|
|
3540
|
+
if [ "$two_root_rc" -ne 0 ] || ! grep -q "takeover: user-scope entwurf registration moved" <<<"$two_root_out"; then
|
|
3541
|
+
fail "[check-pack-install] explicit takeover-user-scope from the second root failed (rc=$two_root_rc):"
|
|
3542
|
+
echo "$two_root_out" | tail -10 | sed 's/^/ /' >&2
|
|
3543
|
+
return 1
|
|
3544
|
+
fi
|
|
3545
|
+
if ! python3 -c "
|
|
3546
|
+
import json,sys
|
|
3547
|
+
p=json.load(open('$npm_user_settings')).get('packages',[])
|
|
3548
|
+
srcs=[(x if isinstance(x,str) else x.get('source')) for x in p]
|
|
3549
|
+
ok_b=any(isinstance(s,str) and s.rstrip('/')== '$npm2_pkg' for s in srcs)
|
|
3550
|
+
bad_a=any(isinstance(s,str) and s.rstrip('/')== '$npm_pkg' for s in srcs)
|
|
3551
|
+
sys.exit(0 if ok_b and not bad_a else 1)
|
|
3552
|
+
"; then
|
|
3553
|
+
fail "[check-pack-install] takeover did not move the user-scope entry old→new:"
|
|
3554
|
+
sed 's/^/ /' "$npm_user_settings" >&2
|
|
3555
|
+
return 1
|
|
3556
|
+
fi
|
|
3557
|
+
# #86 C2 amendment: BOTH ownership halves moved — the provider installerRoot must
|
|
3558
|
+
# now name the new root, and the OLD root's inverse must refuse (live foreign owner).
|
|
3559
|
+
if ! python3 -c "
|
|
3560
|
+
import json,sys
|
|
3561
|
+
st=json.load(open('$npmhome/.local/share/entwurf/pi-provider/install-state.json'))
|
|
3562
|
+
sys.exit(0 if st.get('installerRoot','').rstrip('/')== '$npm2_pkg' else 1)
|
|
3563
|
+
"; then
|
|
3564
|
+
fail "[check-pack-install] takeover did not rebind the provider installerRoot to the new root:"
|
|
3565
|
+
sed 's/^/ /' "$npmhome/.local/share/entwurf/pi-provider/install-state.json" >&2
|
|
3566
|
+
return 1
|
|
3567
|
+
fi
|
|
3568
|
+
set +e
|
|
3569
|
+
two_root_out=$(HOME="$npmhome" XDG_DATA_HOME="$npmhome/.local/share" XDG_STATE_HOME="$npmhome/.local/state" XDG_CACHE_HOME="$npmhome/.cache" "$npm_pkg/run.sh" remove-user-scope 2>&1)
|
|
3570
|
+
two_root_rc=$?
|
|
3571
|
+
set -e
|
|
3572
|
+
if [ "$two_root_rc" -eq 0 ] || ! python3 -c "
|
|
3573
|
+
import json,sys
|
|
3574
|
+
p=json.load(open('$npm_user_settings')).get('packages',[])
|
|
3575
|
+
srcs=[(x if isinstance(x,str) else x.get('source')) for x in p]
|
|
3576
|
+
sys.exit(0 if any(isinstance(s,str) and s.rstrip('/')== '$npm2_pkg' for s in srcs) else 1)
|
|
3577
|
+
"; then
|
|
3578
|
+
fail "[check-pack-install] the OLD root's remove-user-scope must refuse after takeover (rc=$two_root_rc) and leave the new owner's entry intact:"
|
|
3579
|
+
echo "$two_root_out" | tail -8 | sed 's/^/ /' >&2
|
|
3580
|
+
return 1
|
|
3581
|
+
fi
|
|
3582
|
+
echo "[check-pack-install] two-root ownership row pass (second root: normal install refused naming takeover-user-scope; explicit takeover moved entry + provider installerRoot old->new; old root's inverse refused)"
|
|
3583
|
+
|
|
3584
|
+
# Installed-package aggregate `setup` row (#86 C1, review blocker 2026-08-26):
|
|
3585
|
+
# the composed public command itself, driven through the consumer bin from the
|
|
3586
|
+
# SAME installed candidate, on a FRESH sandbox home with every harness absent.
|
|
3587
|
+
# This is the first living consumer of package-installed `entwurf setup`:
|
|
3588
|
+
# mode=installed decided by name before anything else, NO pnpm bootstrap inside
|
|
3589
|
+
# node_modules, pi/claude/agy explicit zero-state SKIPs, stable bins PASS as
|
|
3590
|
+
# npm-provided, core bridge boundary PASS, computed green, zero harness/auth
|
|
3591
|
+
# writes. Harness probes are pinned absent via the PI_BIN/CLAUDE_BIN/AGY_BIN/
|
|
3592
|
+
# COPILOT_BIN seams so the gate host's real harnesses never leak in.
|
|
3593
|
+
local setup_home="$npm_tmp/setuphome" setup_proj="$npm_tmp/setupproj" setup_out setup_rc setup_auth
|
|
3594
|
+
mkdir -p "$setup_home/.pi/agent" "$setup_proj"
|
|
3595
|
+
setup_auth="$setup_home/.pi/agent/auth.json"
|
|
3596
|
+
printf '{\n "anthropic": {\n "type": "oauth",\n "access": "consumer-oauth-token"\n }\n}\n' > "$setup_auth"
|
|
3597
|
+
local setup_auth_before
|
|
3598
|
+
setup_auth_before="$(sha256sum "$setup_auth" | cut -d' ' -f1)"
|
|
3599
|
+
set +e
|
|
3600
|
+
setup_out=$(HOME="$setup_home" XDG_DATA_HOME="$setup_home/.local/share" XDG_STATE_HOME="$setup_home/.local/state" \
|
|
3601
|
+
XDG_CACHE_HOME="$setup_home/.cache" XDG_CONFIG_HOME="$setup_home/.config" PI_CODING_AGENT_DIR="$setup_home/.pi/agent" \
|
|
3602
|
+
PI_BIN="$npm_tmp/definitely-absent" CLAUDE_BIN="$npm_tmp/definitely-absent" AGY_BIN="$npm_tmp/definitely-absent" \
|
|
3603
|
+
COPILOT_BIN="$npm_tmp/definitely-absent" \
|
|
3604
|
+
"$npmroot/node_modules/.bin/entwurf" setup "$setup_proj" 2>&1)
|
|
3605
|
+
setup_rc=$?
|
|
3606
|
+
set -e
|
|
3607
|
+
if [ "$setup_rc" -ne 0 ]; then
|
|
3608
|
+
fail "[check-pack-install] installed all-absent setup exited $setup_rc — core must stay green with every harness absent, and an installed package must never re-enter the source pnpm bootstrap:"
|
|
3609
|
+
echo "$setup_out" | tail -25 | sed 's/^/ /' >&2
|
|
3610
|
+
return 1
|
|
3611
|
+
fi
|
|
3612
|
+
if ! head -n 1 <<<"$setup_out" | grep -q "mode: installed package"; then
|
|
3613
|
+
fail "[check-pack-install] installed setup did not name its mode FIRST:"
|
|
3614
|
+
echo "$setup_out" | head -5 | sed 's/^/ /' >&2
|
|
3615
|
+
return 1
|
|
3616
|
+
fi
|
|
3617
|
+
if grep -Eqi "pnpm install|Lockfile is up to date|Progress: resolved" <<<"$setup_out"; then
|
|
3618
|
+
fail "[check-pack-install] installed setup reached the source pnpm bootstrap inside node_modules:"
|
|
3619
|
+
echo "$setup_out" | grep -Ei "pnpm|Lockfile|Progress" | sed 's/^/ /' >&2
|
|
3620
|
+
return 1
|
|
3621
|
+
fi
|
|
3622
|
+
local skip_probe
|
|
3623
|
+
for skip_probe in "pi: SKIP" "claude: SKIP" "agy: SKIP" "copilot: SKIP"; do
|
|
3624
|
+
if ! grep -q "$skip_probe" <<<"$setup_out"; then
|
|
3625
|
+
fail "[check-pack-install] installed all-absent setup missing explicit zero-state '$skip_probe':"
|
|
3626
|
+
echo "$setup_out" | tail -25 | sed 's/^/ /' >&2
|
|
3627
|
+
return 1
|
|
3628
|
+
fi
|
|
3629
|
+
done
|
|
3630
|
+
if ! grep -q "bins: PASS — provided by npm bin linking" <<<"$setup_out"; then
|
|
3631
|
+
fail "[check-pack-install] installed setup must report stable bins PASS as npm-provided (capability present, not absent):"
|
|
3632
|
+
echo "$setup_out" | tail -25 | sed 's/^/ /' >&2
|
|
3633
|
+
return 1
|
|
3634
|
+
fi
|
|
3635
|
+
if ! grep -q "core: PASS" <<<"$setup_out" || ! grep -q "result: green (computed from the component outcomes above)" <<<"$setup_out"; then
|
|
3636
|
+
fail "[check-pack-install] installed setup core/computed-green missing:"
|
|
3637
|
+
echo "$setup_out" | tail -25 | sed 's/^/ /' >&2
|
|
3638
|
+
return 1
|
|
3639
|
+
fi
|
|
3640
|
+
if [ -e "$setup_proj/.pi" ] || [ -e "$setup_home/.pi/agent/settings.json" ] || [ -e "$setup_home/.gemini" ] || [ -e "$setup_home/.copilot" ]; then
|
|
3641
|
+
fail "[check-pack-install] installed all-absent setup wrote harness state (must be zero-write)"
|
|
3642
|
+
return 1
|
|
3643
|
+
fi
|
|
3644
|
+
if [ "$(sha256sum "$setup_auth" | cut -d' ' -f1)" != "$setup_auth_before" ] || [ -e "$setup_auth.bak" ]; then
|
|
3645
|
+
fail "[check-pack-install] installed setup touched the credential store (byte drift or .bak)"
|
|
3646
|
+
return 1
|
|
3647
|
+
fi
|
|
3648
|
+
echo "[check-pack-install] installed all-absent setup pass (mode-first, no bootstrap, 4x SKIP, bins npm-provided, core PASS, computed green, zero writes)"
|
|
3649
|
+
|
|
3650
|
+
# Installed copilot-present aggregate `setup` row (#86 C3b): the SAME consumer
|
|
3651
|
+
# bin on a fresh sandbox home, with the shared fake vendor
|
|
3652
|
+
# (scripts/fake-copilot-vendor.sh) on PATH. The presence-driven composition
|
|
3653
|
+
# must run all FOUR Copilot units from the installed package: birth assembles
|
|
3654
|
+
# the COMPILED hook (no raw .ts under node_modules) and drives the real
|
|
3655
|
+
# marketplace-add→plugin-install sequence, MCP + receiver + footer land their
|
|
3656
|
+
# configs/units in the sandbox, each unit keeps its package-owned
|
|
3657
|
+
# install-state (its inverse authority), and the verdict is computed green.
|
|
3658
|
+
local cop_setup_home="$npm_tmp/copsetuphome" cop_setup_proj="$npm_tmp/copsetupproj" cop_fake="$npm_tmp/fake-copilot-vendor"
|
|
3659
|
+
mkdir -p "$cop_setup_home/.pi/agent" "$cop_setup_proj"
|
|
3660
|
+
printf '{\n "anthropic": {\n "type": "oauth",\n "access": "consumer-oauth-token"\n }\n}\n' > "$cop_setup_home/.pi/agent/auth.json"
|
|
3661
|
+
local cop_auth_before
|
|
3662
|
+
cop_auth_before="$(sha256sum "$cop_setup_home/.pi/agent/auth.json" | cut -d' ' -f1)"
|
|
3663
|
+
# EXECUTED, never sourced: this impl holds a `trap … RETURN`, and bash fires a
|
|
3664
|
+
# RETURN trap when a sourced script finishes — a `. factory` here deleted the
|
|
3665
|
+
# whole npm sandbox mid-gate (measured 2026-08-27, C3b review).
|
|
3666
|
+
bash "$REPO_DIR/scripts/fake-copilot-vendor.sh" "$cop_fake" "$npm_pkg/pi/meta-bridge-copilot/entwurf-meta-receive-copilot/.claude-plugin/plugin.json" || {
|
|
3667
|
+
fail "[check-pack-install] could not build the fake copilot vendor from the PACKED plugin.json (is pi/meta-bridge-copilot missing from the tarball?)"
|
|
3668
|
+
return 1
|
|
3669
|
+
}
|
|
3670
|
+
set +e
|
|
3671
|
+
setup_out=$(HOME="$cop_setup_home" XDG_DATA_HOME="$cop_setup_home/.local/share" XDG_STATE_HOME="$cop_setup_home/.local/state" \
|
|
3672
|
+
XDG_CACHE_HOME="$cop_setup_home/.cache" XDG_CONFIG_HOME="$cop_setup_home/.config" PI_CODING_AGENT_DIR="$cop_setup_home/.pi/agent" \
|
|
3673
|
+
PI_BIN="$npm_tmp/definitely-absent" CLAUDE_BIN="$npm_tmp/definitely-absent" AGY_BIN="$npm_tmp/definitely-absent" \
|
|
3674
|
+
COPILOT_BIN="$cop_fake/copilot" PATH="$cop_fake:$npmroot/node_modules/.bin:$PATH" \
|
|
3675
|
+
"$npmroot/node_modules/.bin/entwurf" setup "$cop_setup_proj" 2>&1)
|
|
3676
|
+
setup_rc=$?
|
|
3677
|
+
set -e
|
|
3678
|
+
if [ "$setup_rc" -ne 0 ]; then
|
|
3679
|
+
fail "[check-pack-install] installed copilot-present setup exited $setup_rc — the four-unit composition must be green against the fake vendor:"
|
|
3680
|
+
echo "$setup_out" | tail -30 | sed 's/^/ /' >&2
|
|
3681
|
+
return 1
|
|
3682
|
+
fi
|
|
3683
|
+
local cop_row
|
|
3684
|
+
for cop_row in "copilot-birth: PASS" "copilot-mcp: PASS" "copilot-receive: PASS" "copilot-statusline: PASS"; do
|
|
3685
|
+
if ! grep -q "$cop_row" <<<"$setup_out"; then
|
|
3686
|
+
fail "[check-pack-install] installed copilot-present setup missing unit verdict '$cop_row':"
|
|
3687
|
+
echo "$setup_out" | tail -30 | sed 's/^/ /' >&2
|
|
3688
|
+
return 1
|
|
3689
|
+
fi
|
|
3690
|
+
done
|
|
3691
|
+
if ! grep -q "result: green (computed from the component outcomes above)" <<<"$setup_out"; then
|
|
3692
|
+
fail "[check-pack-install] installed copilot-present setup did not compute green:"
|
|
3693
|
+
echo "$setup_out" | tail -10 | sed 's/^/ /' >&2
|
|
3694
|
+
return 1
|
|
3695
|
+
fi
|
|
3696
|
+
local cop_setup_asm="$cop_setup_home/.local/share/entwurf/meta-bridge-copilot/.assembled/entwurf-meta-receive-copilot"
|
|
3697
|
+
if [ ! -f "$cop_setup_asm/meta-bridge-hook-copilot.js" ] || [ -f "$cop_setup_asm/meta-bridge-hook-copilot.ts" ]; then
|
|
3698
|
+
fail "[check-pack-install] installed copilot-present setup did not assemble the compiled birth hook (want meta-bridge-hook-copilot.js, no raw .ts) in $cop_setup_asm"
|
|
3699
|
+
return 1
|
|
3700
|
+
fi
|
|
3701
|
+
if ! grep -q "^plugin marketplace add " "$cop_fake/calls.log" || ! grep -q "^plugin install entwurf-meta-receive-copilot@meta-bridge-copilot-local$" "$cop_fake/calls.log"; then
|
|
3702
|
+
fail "[check-pack-install] installed copilot-present setup did not drive the vendor sequence (marketplace add + plugin install) — calls:"
|
|
3703
|
+
sed 's/^/ /' "$cop_fake/calls.log" >&2
|
|
3704
|
+
return 1
|
|
3705
|
+
fi
|
|
3706
|
+
if [ ! -f "$cop_setup_home/.copilot/extensions/entwurf-receive/extension.mjs" ] || [ ! -f "$cop_setup_home/.copilot/extensions/entwurf-receive/lib/meta-session.js" ]; then
|
|
3707
|
+
fail "[check-pack-install] installed copilot-present setup did not deploy the receiver unit from the shipped dist closure"
|
|
3708
|
+
return 1
|
|
3709
|
+
fi
|
|
3710
|
+
if ! grep -q "entwurf-bridge" "$cop_setup_home/.copilot/mcp-config.json" || ! grep -q "entwurf-copilot-statusline" "$cop_setup_home/.copilot/settings.json"; then
|
|
3711
|
+
fail "[check-pack-install] installed copilot-present setup did not land the MCP config / footer settings in the sandbox home"
|
|
3712
|
+
return 1
|
|
3713
|
+
fi
|
|
3714
|
+
local cop_state
|
|
3715
|
+
for cop_state in copilot-bridge copilot-mcp copilot-receive copilot-statusline; do
|
|
3716
|
+
if [ ! -f "$cop_setup_home/.local/share/entwurf/$cop_state/install-state.json" ]; then
|
|
3717
|
+
fail "[check-pack-install] installed copilot-present setup left no package-owned install-state for $cop_state (its inverse authority)"
|
|
3718
|
+
return 1
|
|
3719
|
+
fi
|
|
3720
|
+
done
|
|
3721
|
+
if [ "$(sha256sum "$cop_setup_home/.pi/agent/auth.json" | cut -d' ' -f1)" != "$cop_auth_before" ] || [ -e "$cop_setup_home/.pi/agent/auth.json.bak" ]; then
|
|
3722
|
+
fail "[check-pack-install] installed copilot-present setup touched the credential store (byte drift or .bak)"
|
|
3723
|
+
return 1
|
|
3724
|
+
fi
|
|
3725
|
+
echo "[check-pack-install] installed copilot-present setup pass (four units PASS with install-states, compiled birth hook, vendor sequence driven, computed green, credentials untouched)"
|
|
3726
|
+
|
|
3281
3727
|
# Installed meta-bridge ownership regression (0.12.5): package upgrades must not
|
|
3282
3728
|
# bake versioned pnpm-store paths into Claude settings. MCP already uses the
|
|
3283
3729
|
# stable `entwurf-bridge` bin; statusLine must now use `entwurf-statusline`, and
|
|
@@ -3394,6 +3840,48 @@ SH
|
|
|
3394
3840
|
fi
|
|
3395
3841
|
rm -rf "$nm_probe"
|
|
3396
3842
|
echo "[check-pack-install] installed hook is node_modules-safe compiled JS (hooks.json points at .js; runs under node_modules with plain node; raw .ts refused there by the strip-types fence)"
|
|
3843
|
+
|
|
3844
|
+
# --- #82: the Copilot birth unit's INSTALLED-PACKAGE branch ------------------
|
|
3845
|
+
# check-copilot-birth-hook drives the installer from a DEV CLONE, so it only ever
|
|
3846
|
+
# exercises the `.ts` branch. The installed-package branch — compiled
|
|
3847
|
+
# meta-bridge-hook-copilot.js, its rewritten `./lib/meta-session.js` import, the
|
|
3848
|
+
# registry copied to the plugin root — was covered by pack LISTS and by nothing that
|
|
3849
|
+
# RUNS it, which is the shape Hard Rule 11 exists to refuse (cross-review, terra +
|
|
3850
|
+
# glm independently, 2026-08-21).
|
|
3851
|
+
#
|
|
3852
|
+
# `--assemble-only` stops before the Copilot CLI, so this needs no Copilot and no fake
|
|
3853
|
+
# of one. A fake Copilot CLI would only prove our script calls our fake the way we
|
|
3854
|
+
# wrote it — the vendor contract is unmeasured, and the first real execution is the
|
|
3855
|
+
# §6 admission itself. This asserts the CITIZEN, not merely that a node process ran.
|
|
3856
|
+
local cop_asm="$npm_tmp/copilot-asm" cop_store cop_log
|
|
3857
|
+
cop_store="$(mktemp -d)"
|
|
3858
|
+
if ! cop_log=$(HOME="$mb_home" XDG_DATA_HOME="$mb_home/.local/share" ENTWURF_COPILOT_ASM="$cop_asm" PATH="$npmroot/node_modules/.bin:$PATH" "$npm_pkg/run.sh" install-copilot-bridge --assemble-only 2>&1); then
|
|
3859
|
+
fail "[check-pack-install] installed install-copilot-bridge --assemble-only failed:"
|
|
3860
|
+
echo "$cop_log" | tail -20 | sed 's/^/ /' >&2
|
|
3861
|
+
return 1
|
|
3862
|
+
fi
|
|
3863
|
+
local cop_unit="$cop_asm/entwurf-meta-receive-copilot"
|
|
3864
|
+
# Branch SELECTION, pinned: an installed package must assemble the compiled entry and
|
|
3865
|
+
# no raw .ts. Without this the fire below could pass on a layout that silently chose
|
|
3866
|
+
# the dev-clone branch.
|
|
3867
|
+
if [ ! -f "$cop_unit/meta-bridge-hook-copilot.js" ] || [ -f "$cop_unit/meta-bridge-hook-copilot.ts" ]; then
|
|
3868
|
+
fail "[check-pack-install] installed install-copilot-bridge did not select the compiled branch (want meta-bridge-hook-copilot.js, no raw .ts) in $cop_unit"
|
|
3869
|
+
return 1
|
|
3870
|
+
fi
|
|
3871
|
+
local cop_out
|
|
3872
|
+
# Fired the way Copilot fires it: NO ARGV, envelope on stdin.
|
|
3873
|
+
if ! cop_out="$(printf '%s' '{"sessionId":"pack-copilot-probe","cwd":"/tmp","source":"new"}' | env PI_CODING_AGENT_DIR="$cop_store" "$cop_unit/scripts/copilot-hook-launch.sh" 2>&1)"; then
|
|
3874
|
+
fail "[check-pack-install] the installed Copilot launcher failed: $(printf '%s' "$cop_out" | tr '\n' ' ' | cut -c1-200)"
|
|
3875
|
+
return 1
|
|
3876
|
+
fi
|
|
3877
|
+
local cop_records
|
|
3878
|
+
cop_records="$(grep -l '"backend": "copilot"' "$cop_store"/meta-sessions/*.meta.json 2>/dev/null | wc -l | tr -d ' ')"
|
|
3879
|
+
if [ "$cop_records" != "1" ]; then
|
|
3880
|
+
fail "[check-pack-install] the installed Copilot unit did not mint exactly one copilot citizen (found ${cop_records:-0}); log: $(tail -1 "$cop_store/meta-bridge-hook.log" 2>/dev/null)"
|
|
3881
|
+
return 1
|
|
3882
|
+
fi
|
|
3883
|
+
rm -rf "$cop_store"
|
|
3884
|
+
echo "[check-pack-install] installed Copilot birth unit assembles its compiled branch and mints a citizen from a no-argv launch"
|
|
3397
3885
|
python3 - "$mb_cfg/settings.json" "$mb_home/.claude.json" "$stable_asm" <<'PY'
|
|
3398
3886
|
import json, sys
|
|
3399
3887
|
settings = json.load(open(sys.argv[1]))
|
|
@@ -3596,6 +4084,60 @@ JS
|
|
|
3596
4084
|
return 1
|
|
3597
4085
|
fi
|
|
3598
4086
|
|
|
4087
|
+
# #82 RAIL 7 — the managed launch, from the INSTALLED package. Hard Rule 11: a green
|
|
4088
|
+
# clone is not a green tarball. `entwurf copilot` is a bash leaf, so its risk here is
|
|
4089
|
+
# not the strip-types fence but PACKAGING — `scripts/copilot-launch.sh` has to be in
|
|
4090
|
+
# the tarball and reachable from the installed `run.sh`, and a file-list assertion
|
|
4091
|
+
# cannot tell a shipped script from a shipped script that resolves its siblings wrongly
|
|
4092
|
+
# once it lives under node_modules. So the cell RUNS it, against a fake vendor on a
|
|
4093
|
+
# sandbox PATH and a fake receiver install-state, and reads what the vendor was handed.
|
|
4094
|
+
local cop_launch_home="$npm_tmp/cop-launch-home" cop_launch_xdg="$npm_tmp/cop-launch-xdg" cop_launch_bin="$npm_tmp/cop-launch-bin"
|
|
4095
|
+
local cop_unit_dir="$cop_launch_home/.copilot/extensions/entwurf-receive"
|
|
4096
|
+
local cop_state_dir="$cop_launch_xdg/entwurf/copilot-receive"
|
|
4097
|
+
mkdir -p "$cop_unit_dir" "$cop_state_dir" "$cop_launch_bin"
|
|
4098
|
+
printf '// fixture\n' > "$cop_unit_dir/extension.mjs"
|
|
4099
|
+
printf '{"schemaVersion":1,"unit":"entwurf-receive","path":"%s"}\n' "$cop_unit_dir" > "$cop_state_dir/install-state.json"
|
|
4100
|
+
# The stand-in is installed under the REAL name on a sandbox PATH: the launcher resolves
|
|
4101
|
+
# `copilot` and nothing else, so there is no env switch here to prove the wrong contract with.
|
|
4102
|
+
{
|
|
4103
|
+
printf '#!/usr/bin/env bash\n'
|
|
4104
|
+
printf 'echo "FLAG=[${COPILOT_CLI_ENABLED_FEATURE_FLAGS-<unset>}]"\n'
|
|
4105
|
+
printf 'for a in "$@"; do printf "ARG<%%s>\\n" "$a"; done\n'
|
|
4106
|
+
} > "$cop_launch_bin/copilot"
|
|
4107
|
+
chmod +x "$cop_launch_bin/copilot"
|
|
4108
|
+
# `env -u` for BOTH carriers, not just a reassignment: an operator running this gate with
|
|
4109
|
+
# their own COPILOT_CLI_ENABLED_FEATURE_FLAGS exported would otherwise inherit those tokens
|
|
4110
|
+
# into the oracle below and read a FALSE RED, and an inherited launch sentinel would trip
|
|
4111
|
+
# the recursion fence before the vendor was ever reached. The cell must measure the
|
|
4112
|
+
# installed launcher, not the shell that happened to start it.
|
|
4113
|
+
if ! op_out=$(env -u COPILOT_CLI_ENABLED_FEATURE_FLAGS -u ENTWURF_COPILOT_LAUNCH_ACTIVE \
|
|
4114
|
+
HOME="$cop_launch_home" XDG_DATA_HOME="$cop_launch_xdg" PATH="$cop_launch_bin:$PATH" \
|
|
4115
|
+
"$installed_entwurf" copilot -p hi 2>&1); then
|
|
4116
|
+
fail "[check-pack-install] installed 'entwurf copilot' FAILED to reach the vendor:"
|
|
4117
|
+
echo "$op_out" | tail -8 | sed 's/^/ /' >&2
|
|
4118
|
+
return 1
|
|
4119
|
+
fi
|
|
4120
|
+
# Assert MEANING: the scan flag this whole surface exists to set, the managed defaults,
|
|
4121
|
+
# and the operator's own argument, all arriving at the vendor from the installed tree.
|
|
4122
|
+
if ! printf '%s' "$op_out" | grep -q 'FLAG=\[EXTENSIONS\]'; then
|
|
4123
|
+
fail "[check-pack-install] installed 'entwurf copilot' did not hand the vendor COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS:"
|
|
4124
|
+
echo "$op_out" | tail -8 | sed 's/^/ /' >&2
|
|
4125
|
+
return 1
|
|
4126
|
+
fi
|
|
4127
|
+
for want in 'ARG<-p>' 'ARG<hi>' 'ARG<--model>' 'ARG<auto>' 'ARG<--yolo>'; do
|
|
4128
|
+
if ! printf '%s' "$op_out" | grep -qF "$want"; then
|
|
4129
|
+
fail "[check-pack-install] installed 'entwurf copilot' did not forward the managed argv (missing $want):"
|
|
4130
|
+
echo "$op_out" | tail -8 | sed 's/^/ /' >&2
|
|
4131
|
+
return 1
|
|
4132
|
+
fi
|
|
4133
|
+
done
|
|
4134
|
+
if printf '%s' "$op_out" | grep -qF 'ARG<copilot>'; then
|
|
4135
|
+
fail "[check-pack-install] installed 'entwurf copilot' leaked the dispatcher verb into the vendor's argv:"
|
|
4136
|
+
echo "$op_out" | tail -8 | sed 's/^/ /' >&2
|
|
4137
|
+
return 1
|
|
4138
|
+
fi
|
|
4139
|
+
echo "[check-pack-install] installed 'entwurf copilot' reached the vendor with the scan flag and the managed argv"
|
|
4140
|
+
|
|
3599
4141
|
# The generation verb must reach its verdict from under node_modules. On the
|
|
3600
4142
|
# sandbox 0-record agent dir it archives nothing and opens a fresh generation.
|
|
3601
4143
|
# The exit code alone can't tell a verdict from a fence crash, so read the line.
|
|
@@ -3994,24 +4536,109 @@ check_bridge() {
|
|
|
3994
4536
|
}
|
|
3995
4537
|
|
|
3996
4538
|
|
|
3997
|
-
#
|
|
4539
|
+
# ── setup verdict engine (#86 C1) ────────────────────────────────────────────
|
|
4540
|
+
# setup composes harnesses the operator ALREADY installed. Per component the
|
|
4541
|
+
# verdict is PASS (detected + completed), SKIP (truly absent — zero state
|
|
4542
|
+
# written), or FAIL (detected but incomplete/below-floor/refused). The final
|
|
4543
|
+
# summary and exit status are COMPUTED from these outcomes: core survival never
|
|
4544
|
+
# relabels a detected harness failure as green, and a FAIL owns a nonzero setup
|
|
4545
|
+
# result while every valid earlier write stays installed (re-running setup is
|
|
4546
|
+
# the documented repair action).
|
|
4547
|
+
SETUP_RESULTS=()
|
|
4548
|
+
setup_result() { # $1=component $2=PASS|SKIP|FAIL $3=detail
|
|
4549
|
+
SETUP_RESULTS+=("$1|$2|$3")
|
|
4550
|
+
case "$2" in
|
|
4551
|
+
PASS) ok "$1: PASS — $3" ;;
|
|
4552
|
+
SKIP) log "$1: SKIP — $3" ;;
|
|
4553
|
+
*) fail "$1: FAIL — $3" ;;
|
|
4554
|
+
esac
|
|
4555
|
+
}
|
|
4556
|
+
|
|
4557
|
+
# Installed-vs-source is decided by PACKAGE LOCATION — the same `*/node_modules/*`
|
|
4558
|
+
# seam run_ts and the bridge launcher already branch on — never by which of
|
|
4559
|
+
# pnpm/pi happens to be missing (an accidental failure is not a mode gate).
|
|
4560
|
+
setup_mode() {
|
|
4561
|
+
case "$REPO_DIR" in
|
|
4562
|
+
*/node_modules/*) echo installed ;;
|
|
4563
|
+
*) echo source ;;
|
|
4564
|
+
esac
|
|
4565
|
+
}
|
|
4566
|
+
|
|
4567
|
+
# Supported pi range, derived at runtime from the package.json devDependencies
|
|
4568
|
+
# pin (the SSOT check-dep-versions binds: peer range == `>=<pin> <0.<minor+1>`).
|
|
4569
|
+
# Never retyped here as a second literal.
|
|
4570
|
+
pi_supported_range() {
|
|
4571
|
+
node -e '
|
|
4572
|
+
const pkg = require(process.argv[1]);
|
|
4573
|
+
const pin = pkg.devDependencies?.["@earendil-works/pi-coding-agent"];
|
|
4574
|
+
if (!/^0\.\d+\.\d+$/.test(pin ?? "")) { console.error(`unparseable pi pin: ${pin}`); process.exit(1); }
|
|
4575
|
+
const [, min] = pin.split(".").map(Number);
|
|
4576
|
+
console.log(`>=${pin} <0.${min + 1}`);
|
|
4577
|
+
' "$REPO_DIR/package.json"
|
|
4578
|
+
}
|
|
4579
|
+
|
|
4580
|
+
pi_version_in_range() { # $1=detected version $2=range ">=a.b.c <x.y"; exit 0 in-range
|
|
4581
|
+
node -e '
|
|
4582
|
+
const [ver, range] = process.argv.slice(1);
|
|
4583
|
+
const m = range.match(/^>=(\d+)\.(\d+)\.(\d+) <(\d+)\.(\d+)$/);
|
|
4584
|
+
const v = ver.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
4585
|
+
if (!m || !v) process.exit(2);
|
|
4586
|
+
const [maj, min, pat] = v.slice(1).map(Number);
|
|
4587
|
+
const lo = m.slice(1, 4).map(Number);
|
|
4588
|
+
const hi = m.slice(4, 6).map(Number);
|
|
4589
|
+
const geFloor = maj > lo[0] || (maj === lo[0] && (min > lo[1] || (min === lo[1] && pat >= lo[2])));
|
|
4590
|
+
const ltCeil = maj < hi[0] || (maj === hi[0] && min < hi[1]);
|
|
4591
|
+
process.exit(geFloor && ltCeil ? 0 : 1);
|
|
4592
|
+
' "$1" "$2"
|
|
4593
|
+
}
|
|
4594
|
+
|
|
4595
|
+
# setup_all — one-command presence-driven composition (#86).
|
|
3998
4596
|
#
|
|
3999
|
-
#
|
|
4000
|
-
#
|
|
4001
|
-
#
|
|
4002
|
-
#
|
|
4597
|
+
# Entwurf installs itself only: this command completes the integration of each
|
|
4598
|
+
# harness the operator already installed and NEVER installs a harness binary,
|
|
4599
|
+
# subscription, credential or login — `pi` has no privileged exception. It
|
|
4600
|
+
# verifies the installed bridge boundary; ACP/v1 backend interview gates are
|
|
4601
|
+
# deliberately not part of setup on v2-only (the heavier live v2 substrate
|
|
4602
|
+
# proof is release-gate's job). Copilot's four native units (birth / MCP /
|
|
4603
|
+
# receiver / visible footer) compose presence-driven since the birth inverse
|
|
4604
|
+
# landed (#86 C3a→C3b); the explicit install-copilot-* / uninstall-copilot-*
|
|
4605
|
+
# surfaces remain the operator-selected repair and inverse path.
|
|
4003
4606
|
#
|
|
4004
4607
|
# An external harness that consumes entwurf (e.g. agent-config as a
|
|
4005
4608
|
# pi package + skills set) may still have its own install/setup for its
|
|
4006
4609
|
# own concerns; those are outside the scope of this script.
|
|
4007
4610
|
setup_all() {
|
|
4611
|
+
# MODE FIRST (#86 C1): the installed-vs-source branch is a NAMED decision
|
|
4612
|
+
# ahead of every prerequisite check and write, so an installed consumer is
|
|
4613
|
+
# never mode-gated by an accidental missing `pnpm`/`pi`.
|
|
4614
|
+
local mode
|
|
4615
|
+
mode=$(setup_mode)
|
|
4616
|
+
case "$mode" in
|
|
4617
|
+
installed)
|
|
4618
|
+
echo "[setup] mode: installed package — composition only (no source bootstrap; setup never runs npm/pnpm inside node_modules)" ;;
|
|
4619
|
+
*)
|
|
4620
|
+
echo "[setup] mode: source checkout — repo dev bootstrap, then the same composition semantics" ;;
|
|
4621
|
+
esac
|
|
4622
|
+
|
|
4623
|
+
# Named interpreter prerequisite, decided BEFORE any write (#86 C1/E5):
|
|
4624
|
+
# setup's project-path normalization and every pi/agy wiring writer are
|
|
4625
|
+
# python3-backed today. `entwurf --help` / `entwurf check-bridge` stay
|
|
4626
|
+
# python3-free; only the commands that invoke those writers declare it.
|
|
4627
|
+
command -v python3 >/dev/null 2>&1 || {
|
|
4628
|
+
echo "[setup] entwurf setup requires python3 on PATH (project-path normalization + wiring writers)." >&2
|
|
4629
|
+
echo "[setup] Install python3, or use python3-free surfaces (--help, check-bridge)." >&2
|
|
4630
|
+
exit 1
|
|
4631
|
+
}
|
|
4632
|
+
require_cmd node
|
|
4633
|
+
|
|
4008
4634
|
local project_dir
|
|
4009
4635
|
project_dir=$(normalize_project_dir "$1")
|
|
4010
4636
|
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4637
|
+
if [ "$mode" = "source" ]; then
|
|
4638
|
+
# Source-only dev fixtures: the pinned repo dependencies are build/test
|
|
4639
|
+
# fixtures, never a product promise that consumers receive them (#86 A4).
|
|
4640
|
+
require_cmd pnpm
|
|
4641
|
+
fi
|
|
4015
4642
|
|
|
4016
4643
|
# Node 24+ is the SINGLE supported axis (GLG, 2026-07-21). The floor is not
|
|
4017
4644
|
# derived from a feature gate: type-stripping alone would only demand 23.6.
|
|
@@ -4032,81 +4659,217 @@ setup_all() {
|
|
|
4032
4659
|
echo "[setup] scope: entwurf v2 package + detected native-harness bridges + pi adapter"
|
|
4033
4660
|
echo "[setup] verification: v2 install smoke (entwurf-bridge; LIVE substrate = release-gate)"
|
|
4034
4661
|
|
|
4035
|
-
# Store gate FIRST — ahead of
|
|
4036
|
-
#
|
|
4037
|
-
#
|
|
4038
|
-
#
|
|
4039
|
-
#
|
|
4040
|
-
#
|
|
4041
|
-
# runs before dependencies exist.
|
|
4662
|
+
# Store gate FIRST — ahead of the source bootstrap and every settings writer
|
|
4663
|
+
# below. On an existing development host this is the only step standing
|
|
4664
|
+
# between a `git pull` and a V3-only activation over a store the live schema
|
|
4665
|
+
# cannot read; putting it inside install_local_package would already be one
|
|
4666
|
+
# write too late. The store-doctor imports repo-local libs and node builtins
|
|
4667
|
+
# only, so it runs before dependencies exist.
|
|
4042
4668
|
preflight_v3_store setup
|
|
4043
4669
|
|
|
4044
|
-
(
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
#
|
|
4054
|
-
|
|
4670
|
+
SETUP_RESULTS=()
|
|
4671
|
+
|
|
4672
|
+
if [ "$mode" = "source" ]; then
|
|
4673
|
+
# Source bootstrap is the ONLY install-mode difference (#86): the pinned
|
|
4674
|
+
# repo dev dependencies are fixtures this checkout builds/tests with. A
|
|
4675
|
+
# broken bootstrap is a broken checkout — crash, don't degrade.
|
|
4676
|
+
(cd "$REPO_DIR" && pnpm install --frozen-lockfile)
|
|
4677
|
+
fi
|
|
4678
|
+
|
|
4679
|
+
# ── pi (presence + floor, #86 C1/C9) ── pi is OPTIONAL-BY-PRESENCE: absent is
|
|
4680
|
+
# an explicit zero-state SKIP (setup never installs pi and writes no Pi
|
|
4681
|
+
# wiring), below-floor/unreadable is detected-incomplete FAIL — never a SKIP,
|
|
4682
|
+
# so a stale pi is told the truth instead of being silently skipped. PI_BIN
|
|
4683
|
+
# pins the probe for the hermetic smoke (same seam as AGY_BIN); production
|
|
4684
|
+
# leaves it unset.
|
|
4685
|
+
local pi_bin="${PI_BIN:-pi}" pi_range pi_ver
|
|
4686
|
+
if ! command -v "$pi_bin" >/dev/null 2>&1; then
|
|
4687
|
+
setup_result pi SKIP "pi not on PATH — zero Pi wiring written (the operator installs pi; setup never does)"
|
|
4688
|
+
else
|
|
4689
|
+
pi_range=$(pi_supported_range)
|
|
4690
|
+
# Bounded capture: a resolvable pi whose --version crashes or prints garbage
|
|
4691
|
+
# must land in the computed FAIL verdict below, never abort setup via set -e
|
|
4692
|
+
# before the summary (review defect, 2026-08-26).
|
|
4693
|
+
pi_ver="$("$pi_bin" --version 2>/dev/null | head -n 1 | tr -d '[:space:]')" || pi_ver=""
|
|
4694
|
+
if ! pi_version_in_range "${pi_ver:-0}" "$pi_range"; then
|
|
4695
|
+
setup_result pi FAIL "detected pi ${pi_ver:-<unreadable version>} is outside the supported range $pi_range — Pi wiring not written; align pi, then re-run setup"
|
|
4696
|
+
elif (install_local_package "$project_dir"); then
|
|
4697
|
+
setup_result pi PASS "project + user-scope wiring complete (pi $pi_ver in $pi_range) — verify: ./run.sh doctor-pi-provider"
|
|
4698
|
+
else
|
|
4699
|
+
setup_result pi FAIL "pi wiring did not complete (see above) — repair, then re-run setup"
|
|
4700
|
+
fi
|
|
4701
|
+
fi
|
|
4702
|
+
|
|
4703
|
+
# ── claude (meta-bridge) ── single confident install command (GLG 2026-06-23):
|
|
4704
|
+
# setup ALSO wires the native-harness meta-bridge so a relocate/clone needs ONE
|
|
4705
|
+
# command. Detection-gated ("있으면 설정, 없으면 담아준다");
|
|
4706
|
+
# meta-bridge-install.sh is idempotent and enforces the Claude floor + its own
|
|
4707
|
+
# named prerequisites itself, so a below-floor or refused host lands here as a
|
|
4708
|
+
# detected FAIL, never a cosmetic green. CLAUDE_BIN pins the probe for the
|
|
4709
|
+
# hermetic smoke; production leaves it unset.
|
|
4710
|
+
local claude_bin="${CLAUDE_BIN:-claude}"
|
|
4711
|
+
if ! command -v "$claude_bin" >/dev/null 2>&1; then
|
|
4712
|
+
setup_result claude SKIP "claude not on PATH — zero meta-bridge wiring written"
|
|
4713
|
+
else
|
|
4055
4714
|
section "meta-bridge install (native harness detected: Claude Code)"
|
|
4056
|
-
(cd "$REPO_DIR" && bash scripts/meta-bridge-install.sh)
|
|
4715
|
+
if (cd "$REPO_DIR" && bash scripts/meta-bridge-install.sh); then
|
|
4716
|
+
setup_result claude PASS "meta-bridge wired — verify: ./run.sh doctor-meta-bridge"
|
|
4717
|
+
else
|
|
4718
|
+
setup_result claude FAIL "detected Claude Code, but the meta-bridge integration did not complete (see above) — repair, then re-run setup"
|
|
4719
|
+
fi
|
|
4720
|
+
fi
|
|
4721
|
+
|
|
4722
|
+
# ── stable command exposure ── source mode exposes entwurf's STABLE bins on
|
|
4723
|
+
# PATH for this DEV checkout (막힘 ②) BEFORE wiring agy: the operator command
|
|
4724
|
+
# `entwurf` that Copilot fresh resolves as its managed runtime, plus the bare
|
|
4725
|
+
# bridge/statusline/imprint names native-harness configs record. An installed
|
|
4726
|
+
# consumer already has the same commands from npm bin linking — nothing to
|
|
4727
|
+
# expose there. The core operator stays fail-loud; helper units are attempted
|
|
4728
|
+
# independently and a refused helper is a named FAIL, not a cosmetic green.
|
|
4729
|
+
local bins_rc
|
|
4730
|
+
if [ "$mode" = "installed" ]; then
|
|
4731
|
+
# PASS, not SKIP (review, 2026-08-26): the capability IS present — npm bin
|
|
4732
|
+
# linking provides the same stable commands the source lane must expose.
|
|
4733
|
+
# SKIP stays reserved for a truly absent capability with zero state.
|
|
4734
|
+
setup_result bins PASS "provided by npm bin linking (installed package needs no source exposure)"
|
|
4057
4735
|
else
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
# Fold agy (Antigravity) MCP bridge wiring into setup (막힘 ①, GLG 2026-07-04: install
|
|
4068
|
-
# ownership moves to entwurf). Same detection-gated, idempotent, NON-FATAL posture as the
|
|
4069
|
-
# meta-bridge block above — agy is an OPTIONAL harness, so a refused/corrupt agy config warns
|
|
4070
|
-
# but never bricks a pi/Claude setup. The hard gate stays doctor-agy-bridge.
|
|
4071
|
-
wire_agy_bridge
|
|
4072
|
-
|
|
4073
|
-
# Fold agy statusLine wiring into setup (#46 Task 1: own the ambient status area so it shows
|
|
4074
|
-
# driver + garden id, the claude meta-bridge symmetry). Same detection-gated NON-FATAL posture
|
|
4075
|
-
# — an explicit install-agy-statusline is fail-loud, but the setup wrapper WARNs + continues.
|
|
4076
|
-
wire_agy_statusline
|
|
4077
|
-
|
|
4078
|
-
# Fold agy PreInvocation birth imprint wiring into setup (#46 close criterion: manual register 0).
|
|
4079
|
-
# The hook runs the thin `entwurf-agy-imprint` bin and returns {"injectSteps":[]} so agy's loop
|
|
4080
|
-
# survives while the meta-record is created/attached by conversationId.
|
|
4081
|
-
wire_agy_hooks
|
|
4082
|
-
|
|
4083
|
-
# Deterministic preflight lives in `pnpm run check:full`; live substrate acceptance lives
|
|
4084
|
-
# in `LIVE=1 ./run.sh release-gate <scratch> --cut`. Setup is the install path, so it
|
|
4085
|
-
# verifies the installed MCP bridge boundary only and does NOT run the legacy
|
|
4086
|
-
# ACP/v1 session-messaging/sentinel gates.
|
|
4087
|
-
section "v2 install smoke: entwurf-bridge (direct MCP protocol)"
|
|
4088
|
-
validate_entwurf_bridge
|
|
4736
|
+
bins_rc=0; expose_dev_bin || bins_rc=$?
|
|
4737
|
+
if [ "$bins_rc" -eq 0 ]; then
|
|
4738
|
+
setup_result bins PASS "source operator + helper bins exposed and certified (PATH winner: this checkout)"
|
|
4739
|
+
elif [ "$bins_rc" -eq 3 ]; then
|
|
4740
|
+
setup_result bins FAIL "foreign helper bin(s) refused (named above; every unit was attempted) — repair them, then re-run setup"
|
|
4741
|
+
else
|
|
4742
|
+
setup_result bins FAIL "source operator command not certified (see above) — repair PATH/link, then re-run setup"
|
|
4743
|
+
fi
|
|
4744
|
+
fi
|
|
4089
4745
|
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4746
|
+
# ── agy (Antigravity) ── detection-gated (막힘 ①, GLG 2026-07-04: install
|
|
4747
|
+
# ownership moves to entwurf); AGY_BIN pins the probe for the hermetic smoke.
|
|
4748
|
+
# agy absent is one zero-state SKIP. agy PRESENT runs all three leaves
|
|
4749
|
+
# (bridge / statusLine / PreInvocation imprint) independently; a refused or
|
|
4750
|
+
# corrupt config is a named component FAIL that keeps the rest of setup alive
|
|
4751
|
+
# but owns a nonzero final result — the old WARN-then-green posture is retired
|
|
4752
|
+
# (#86 C6/C7). Hard per-leaf verdicts stay with the doctor commands.
|
|
4753
|
+
local agy_rc
|
|
4754
|
+
if ! command -v "${AGY_BIN:-agy}" >/dev/null 2>&1; then
|
|
4755
|
+
setup_result agy SKIP "agy not on PATH — zero agy wiring written"
|
|
4756
|
+
else
|
|
4757
|
+
agy_rc=0; wire_agy_bridge || agy_rc=$?
|
|
4758
|
+
if [ "$agy_rc" -eq 0 ]; then
|
|
4759
|
+
setup_result agy-bridge PASS "MCP bridge wired — verify: ./run.sh doctor-agy-bridge"
|
|
4760
|
+
else
|
|
4761
|
+
setup_result agy-bridge FAIL "detected agy, but the MCP bridge integration did not complete (reason above) — repair, then re-run setup"
|
|
4762
|
+
fi
|
|
4763
|
+
agy_rc=0; wire_agy_statusline || agy_rc=$?
|
|
4764
|
+
if [ "$agy_rc" -eq 0 ]; then
|
|
4765
|
+
setup_result agy-statusline PASS "statusLine wired — verify: ./run.sh doctor-agy-statusline"
|
|
4766
|
+
else
|
|
4767
|
+
setup_result agy-statusline FAIL "detected agy, but the statusLine integration did not complete (reason above) — repair, then re-run setup"
|
|
4768
|
+
fi
|
|
4769
|
+
agy_rc=0; wire_agy_hooks || agy_rc=$?
|
|
4770
|
+
if [ "$agy_rc" -eq 0 ]; then
|
|
4771
|
+
setup_result agy-hooks PASS "birth imprint wired — verify: ./run.sh doctor-agy-hooks"
|
|
4772
|
+
else
|
|
4773
|
+
setup_result agy-hooks FAIL "detected agy, but the birth-imprint integration did not complete (reason above) — repair, then re-run setup"
|
|
4774
|
+
fi
|
|
4775
|
+
fi
|
|
4776
|
+
|
|
4777
|
+
# ── copilot (GitHub Copilot CLI) ── presence-driven composition (#86 C3b):
|
|
4778
|
+
# the operator's copilot on PATH is the ONLY trigger; absent is one zero-state
|
|
4779
|
+
# SKIP (setup never installs a harness). Present runs all FOUR units the
|
|
4780
|
+
# fresh-call preflight requires — birth → MCP → receiver → visible footer —
|
|
4781
|
+
# INDEPENDENTLY: a failed unit is a named component FAIL that keeps the other
|
|
4782
|
+
# units and the rest of setup alive but owns a nonzero final result. Every
|
|
4783
|
+
# unit is an operator-selectable install with a package-owned inverse (#86
|
|
4784
|
+
# C3a closed the birth inverse), so setup composes only lifecycles it can
|
|
4785
|
+
# also undo. COPILOT_BIN pins the PROBE for the hermetic gates (same seam
|
|
4786
|
+
# spirit as PI_BIN/CLAUDE_BIN/AGY_BIN); the unit scripts address the vendor
|
|
4787
|
+
# as `copilot` on PATH, so production leaves it unset and the two agree.
|
|
4788
|
+
local copilot_rc
|
|
4789
|
+
if ! command -v "${COPILOT_BIN:-copilot}" >/dev/null 2>&1; then
|
|
4790
|
+
setup_result copilot SKIP "copilot not on PATH — zero Copilot wiring written"
|
|
4791
|
+
else
|
|
4792
|
+
section "copilot units (native harness detected: GitHub Copilot CLI)"
|
|
4793
|
+
copilot_rc=0; (cd "$REPO_DIR" && bash scripts/copilot-bridge-install.sh) || copilot_rc=$?
|
|
4794
|
+
if [ "$copilot_rc" -eq 0 ]; then
|
|
4795
|
+
setup_result copilot-birth PASS "birth plugin installed — verify: ./run.sh doctor-copilot-bridge"
|
|
4796
|
+
else
|
|
4797
|
+
setup_result copilot-birth FAIL "detected copilot, but the birth plugin install did not complete (see above) — repair, then re-run setup"
|
|
4798
|
+
fi
|
|
4799
|
+
copilot_rc=0; (cd "$REPO_DIR" && bash scripts/copilot-mcp-bridge.sh install) || copilot_rc=$?
|
|
4800
|
+
if [ "$copilot_rc" -eq 0 ]; then
|
|
4801
|
+
setup_result copilot-mcp PASS "MCP server registered — verify: ./run.sh doctor-copilot-mcp"
|
|
4802
|
+
else
|
|
4803
|
+
setup_result copilot-mcp FAIL "detected copilot, but the MCP registration did not complete (see above) — repair, then re-run setup"
|
|
4804
|
+
fi
|
|
4805
|
+
# The receiver deploys the COMPILED dist closure on every install shape
|
|
4806
|
+
# (Copilot's extension runtime cannot strip-types). The tarball ships dist;
|
|
4807
|
+
# a source checkout may not have built it yet, and those bytes are
|
|
4808
|
+
# entwurf's OWN artifact (Hard Rule 17: the source bootstrap supplies
|
|
4809
|
+
# entwurf's own bytes), so the source lane builds it here once when absent.
|
|
4810
|
+
copilot_rc=0
|
|
4811
|
+
if [ "$mode" = "source" ] && [ ! -f "$REPO_DIR/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js" ]; then
|
|
4812
|
+
(cd "$REPO_DIR" && pnpm run build-bridge) || copilot_rc=$?
|
|
4813
|
+
fi
|
|
4814
|
+
if [ "$copilot_rc" -ne 0 ]; then
|
|
4815
|
+
setup_result copilot-receive FAIL "detected copilot, but the receiver's compiled dist closure failed to build (see above) — repair, then re-run setup"
|
|
4816
|
+
else
|
|
4817
|
+
copilot_rc=0; (cd "$REPO_DIR" && bash scripts/copilot-receive-bridge.sh install) || copilot_rc=$?
|
|
4818
|
+
if [ "$copilot_rc" -eq 0 ]; then
|
|
4819
|
+
setup_result copilot-receive PASS "receiver extension installed — verify: ./run.sh doctor-copilot-receive"
|
|
4820
|
+
else
|
|
4821
|
+
setup_result copilot-receive FAIL "detected copilot, but the receiver extension install did not complete (see above) — repair, then re-run setup"
|
|
4822
|
+
fi
|
|
4823
|
+
fi
|
|
4824
|
+
copilot_rc=0; (cd "$REPO_DIR" && bash scripts/copilot-statusline-bridge.sh install) || copilot_rc=$?
|
|
4825
|
+
if [ "$copilot_rc" -eq 0 ]; then
|
|
4826
|
+
setup_result copilot-statusline PASS "visible footer wired — verify: ./run.sh doctor-copilot-statusline"
|
|
4827
|
+
else
|
|
4828
|
+
setup_result copilot-statusline FAIL "detected copilot, but the visible-footer integration did not complete (see above) — repair, then re-run setup"
|
|
4829
|
+
fi
|
|
4094
4830
|
fi
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4831
|
+
|
|
4832
|
+
# ── core bridge boundary ── deterministic preflight lives in `pnpm run
|
|
4833
|
+
# check:full`; live substrate acceptance lives in `LIVE=1 ./run.sh
|
|
4834
|
+
# release-gate <scratch> --cut`. Setup is the install path, so it verifies the
|
|
4835
|
+
# installed MCP bridge boundary only and does NOT run the legacy ACP/v1
|
|
4836
|
+
# session-messaging/sentinel gates.
|
|
4837
|
+
section "v2 install smoke: entwurf-bridge (direct MCP protocol)"
|
|
4838
|
+
if (validate_entwurf_bridge); then
|
|
4839
|
+
setup_result core PASS "installed bridge boots and lists the exact v2 tool set"
|
|
4840
|
+
else
|
|
4841
|
+
setup_result core FAIL "installed bridge boundary did not validate (see above)"
|
|
4842
|
+
fi
|
|
4843
|
+
|
|
4844
|
+
# ── computed summary (#86 C7/C8) ── the final verdict is DERIVED from the
|
|
4845
|
+
# component outcomes above, never printed independently of them. A detected
|
|
4846
|
+
# integration FAIL owns a nonzero exit while valid components stay installed;
|
|
4847
|
+
# re-running the same setup is the documented repair action (idempotent).
|
|
4848
|
+
section "setup summary (computed from component outcomes)"
|
|
4849
|
+
local entry rest s_name s_verdict s_fails=""
|
|
4850
|
+
for entry in "${SETUP_RESULTS[@]}"; do
|
|
4851
|
+
s_name="${entry%%|*}"; rest="${entry#*|}"; s_verdict="${rest%%|*}"
|
|
4852
|
+
printf ' %-14s %-4s %s\n' "$s_name" "$s_verdict" "${rest#*|}"
|
|
4853
|
+
if [ "$s_verdict" = "FAIL" ]; then s_fails="$s_fails $s_name"; fi
|
|
4854
|
+
done
|
|
4855
|
+
echo ""
|
|
4856
|
+
if [ -n "$s_fails" ]; then
|
|
4857
|
+
echo "DONE: entwurf setup — result: NON-GREEN (FAIL:$s_fails). Valid components above remain installed; repair the named ones and re-run setup."
|
|
4858
|
+
return 1
|
|
4099
4859
|
fi
|
|
4860
|
+
echo "DONE: entwurf setup — result: green (computed from the component outcomes above)."
|
|
4100
4861
|
echo "Run 'LIVE=1 ./run.sh release-gate <scratch> --cut' for live substrate acceptance (--cut refuses any MUST SKIP)."
|
|
4101
4862
|
}
|
|
4102
4863
|
|
|
4103
|
-
# wire_agy_bridge — detection-gated
|
|
4864
|
+
# wire_agy_bridge — detection-gated agy (Antigravity) MCP bridge wiring, folded into
|
|
4104
4865
|
# setup so a relocate/clone needs ONE idempotent command (막힘 ①). Mirrors the meta-bridge
|
|
4105
4866
|
# block: agy on PATH → idempotent install-agy-bridge; no agy → honest skip, NO state ("있으면
|
|
4106
|
-
# 설정, 없으면 담아준다").
|
|
4107
|
-
#
|
|
4108
|
-
#
|
|
4109
|
-
#
|
|
4867
|
+
# 설정, 없으면 담아준다"). A refused/corrupt agy config still never bricks the rest of setup —
|
|
4868
|
+
# later components are attempted — but it RETURNS 1 so the aggregate verdict records a named
|
|
4869
|
+
# component FAIL and setup exits non-green (#86 C6/C7: the old WARN-then-exit-0 posture was the
|
|
4870
|
+
# false-success shape). The hard per-leaf gate stays doctor-agy-bridge (issue #45). WARNs are
|
|
4871
|
+
# reason-specific so a transitional symlink (someone else's SSOT) and a corrupt config
|
|
4872
|
+
# (invalid JSON) are never conflated.
|
|
4110
4873
|
wire_agy_bridge() {
|
|
4111
4874
|
# Detection = the agy binary on PATH. AGY_BIN pins the target (default `agy`) so the
|
|
4112
4875
|
# hermetic smoke can point at a fake agy or a definitely-absent path without depending on
|
|
@@ -4127,7 +4890,8 @@ wire_agy_bridge() {
|
|
|
4127
4890
|
echo "[setup] agy bridge wired (idempotent). Verify with: ./run.sh doctor-agy-bridge"
|
|
4128
4891
|
return 0
|
|
4129
4892
|
fi
|
|
4130
|
-
#
|
|
4893
|
+
# Keep setup alive for the remaining components, surface the reason honestly, and return 1
|
|
4894
|
+
# so the aggregate verdict records this component as FAIL (never a silent pass).
|
|
4131
4895
|
# ORDER MATTERS: the permission failures carry the same words as the mcp_config ones ("refused
|
|
4132
4896
|
# (symlink)", "invalid JSON") and MUST be matched first — otherwise a settings.json problem gets
|
|
4133
4897
|
# reported as an mcp_config problem and sends the operator to repair the wrong file.
|
|
@@ -4156,14 +4920,14 @@ wire_agy_bridge() {
|
|
|
4156
4920
|
echo "[setup] Bridge NOT wired; verify with ./run.sh doctor-agy-bridge. setup continues." >&2
|
|
4157
4921
|
;;
|
|
4158
4922
|
esac
|
|
4159
|
-
return
|
|
4923
|
+
return 1
|
|
4160
4924
|
}
|
|
4161
4925
|
|
|
4162
|
-
# wire_agy_statusline — detection-gated
|
|
4926
|
+
# wire_agy_statusline — detection-gated agy statusLine wiring, folded into setup (#46
|
|
4163
4927
|
# Task 1). Mirrors wire_agy_bridge: agy on PATH → idempotent install-agy-statusline; no agy →
|
|
4164
|
-
# honest skip, NO state
|
|
4165
|
-
#
|
|
4166
|
-
# stable bin entwurf-agy-statusline, exposed by expose_dev_bin above.
|
|
4928
|
+
# honest skip, NO state; a refused/corrupt settings keeps the rest of setup alive but returns 1
|
|
4929
|
+
# for a named component FAIL (#86 C6/C7). The hard gate is doctor-agy-statusline. The renderer
|
|
4930
|
+
# is the stable bin entwurf-agy-statusline, exposed by expose_dev_bin above.
|
|
4167
4931
|
wire_agy_statusline() {
|
|
4168
4932
|
if ! command -v "${AGY_BIN:-agy}" >/dev/null 2>&1; then
|
|
4169
4933
|
echo "[setup] no agy on PATH — skipping agy statusLine wiring (no state; this host runs no Antigravity)"
|
|
@@ -4196,12 +4960,13 @@ wire_agy_statusline() {
|
|
|
4196
4960
|
echo "[setup] statusLine NOT wired; verify with ./run.sh doctor-agy-statusline. setup continues." >&2
|
|
4197
4961
|
;;
|
|
4198
4962
|
esac
|
|
4199
|
-
return
|
|
4963
|
+
return 1
|
|
4200
4964
|
}
|
|
4201
4965
|
|
|
4202
|
-
# wire_agy_hooks — detection-gated
|
|
4203
|
-
# as the MCP/statusLine adapters
|
|
4204
|
-
# garden id after the first
|
|
4966
|
+
# wire_agy_hooks — detection-gated agy PreInvocation imprint wiring. Same setup posture
|
|
4967
|
+
# as the MCP/statusLine adapters (skip on absence, named FAIL on detected incompleteness), but
|
|
4968
|
+
# this one is the birth writer that turns statusLine '?' into a garden id after the first
|
|
4969
|
+
# invocation.
|
|
4205
4970
|
wire_agy_hooks() {
|
|
4206
4971
|
if ! command -v "${AGY_BIN:-agy}" >/dev/null 2>&1; then
|
|
4207
4972
|
echo "[setup] no agy on PATH — skipping agy hooks wiring (no state; this host runs no Antigravity)"
|
|
@@ -4232,30 +4997,39 @@ wire_agy_hooks() {
|
|
|
4232
4997
|
echo "[setup] Birth imprint NOT wired; verify with ./run.sh doctor-agy-hooks. setup continues." >&2
|
|
4233
4998
|
;;
|
|
4234
4999
|
esac
|
|
4235
|
-
return
|
|
5000
|
+
return 1
|
|
4236
5001
|
}
|
|
4237
5002
|
|
|
4238
|
-
# expose_dev_bin — make the
|
|
4239
|
-
# setup IS the dev install command (consumers get
|
|
4240
|
-
# managed ~/.local/bin
|
|
4241
|
-
#
|
|
4242
|
-
#
|
|
4243
|
-
#
|
|
5003
|
+
# expose_dev_bin — make the package's stable commands resolve for a DEV checkout (막힘 ②).
|
|
5004
|
+
# setup IS the dev install command (consumers get these names from npm bin-linking), so setup owns
|
|
5005
|
+
# managed ~/.local/bin symlinks into this checkout via scripts/dev-bin.sh, each recorded for an
|
|
5006
|
+
# honest inverse. The set starts with `entwurf` → run.sh: Copilot fresh deliberately launches the
|
|
5007
|
+
# managed `entwurf copilot`, never the raw vendor. That operator command is CORE and is certified
|
|
5008
|
+
# after exposure by dev-bin.sh's own target authority: our symlink, our target, and the exact PATH
|
|
5009
|
+
# winner. A foreign/off-PATH/shadowed operator therefore fails setup (return 1). Helper units are
|
|
5010
|
+
# attempted INDEPENDENTLY by dev-bin.sh (#86 C10) and any refusal propagates as return 3 — the
|
|
5011
|
+
# aggregate verdict records it as a named FAIL instead of the old WARN-then-green; the refused
|
|
5012
|
+
# names are already printed truthfully by dev-bin's attempted/ok/refused summary.
|
|
4244
5013
|
expose_dev_bin() {
|
|
4245
|
-
section "dev
|
|
5014
|
+
section "dev bins: expose stable package commands on PATH (dev checkout)"
|
|
4246
5015
|
local rc
|
|
4247
5016
|
set +e
|
|
4248
5017
|
bash "$REPO_DIR/scripts/dev-bin.sh" expose
|
|
4249
5018
|
rc=$?
|
|
4250
5019
|
set -e
|
|
5020
|
+
|
|
5021
|
+
if ! bash "$REPO_DIR/scripts/dev-bin.sh" verify entwurf; then
|
|
5022
|
+
return 1
|
|
5023
|
+
fi
|
|
5024
|
+
|
|
4251
5025
|
[ "$rc" -eq 0 ] && return 0
|
|
4252
5026
|
if [ "$rc" -eq 3 ]; then
|
|
4253
|
-
echo "[setup]
|
|
4254
|
-
echo "[setup]
|
|
5027
|
+
echo "[setup] dev-bin: foreign helper bin path(s) refused, never clobbered — the refused names are in the summary above." >&2
|
|
5028
|
+
echo "[setup] Every unit was attempted independently; the source operator itself is certified." >&2
|
|
4255
5029
|
else
|
|
4256
|
-
echo "[setup]
|
|
5030
|
+
echo "[setup] dev-bin: helper exposure did not complete (rc=$rc; see above)." >&2
|
|
4257
5031
|
fi
|
|
4258
|
-
return
|
|
5032
|
+
return "$rc"
|
|
4259
5033
|
}
|
|
4260
5034
|
|
|
4261
5035
|
# release-gate — the single command that, when GREEN, is sufficient to cut
|
|
@@ -4677,6 +5451,18 @@ case "$cmd" in
|
|
|
4677
5451
|
check-hook-launch-topology)
|
|
4678
5452
|
check_hook_launch_topology
|
|
4679
5453
|
;;
|
|
5454
|
+
check-copilot-birth-hook)
|
|
5455
|
+
check_copilot_birth_hook
|
|
5456
|
+
;;
|
|
5457
|
+
check-copilot-statusline)
|
|
5458
|
+
check_copilot_statusline
|
|
5459
|
+
;;
|
|
5460
|
+
check-copilot-launch)
|
|
5461
|
+
check_copilot_launch
|
|
5462
|
+
;;
|
|
5463
|
+
check-copilot-receive-arm)
|
|
5464
|
+
check_copilot_receive_arm
|
|
5465
|
+
;;
|
|
4680
5466
|
check-meta-capability-source)
|
|
4681
5467
|
check_meta_capability_source
|
|
4682
5468
|
;;
|
|
@@ -4937,6 +5723,16 @@ case "$cmd" in
|
|
|
4937
5723
|
# Offline + deterministic (deps: bash+python3).
|
|
4938
5724
|
(cd "$REPO_DIR" && bash scripts/smoke-agy-install-state.sh)
|
|
4939
5725
|
;;
|
|
5726
|
+
smoke-setup-verdict)
|
|
5727
|
+
# #86 C1 aggregate setup verdict fixture — the first automated consumer of
|
|
5728
|
+
# `entwurf setup` itself: all-absent SKIP/green, pi below-floor detected FAIL
|
|
5729
|
+
# (never SKIP), pi-present wiring PASS, agy detected+corrupt named FAIL with
|
|
5730
|
+
# NON-GREEN nonzero exit, installed-mode named branch before the bootstrap,
|
|
5731
|
+
# and the credential store byte-identical in every cell. Sandboxed HOME/XDG/
|
|
5732
|
+
# agent/dev-bin roots; presence pinned via PI_BIN/CLAUDE_BIN/AGY_BIN.
|
|
5733
|
+
# Offline + deterministic (deps: bash+node+python3+pnpm).
|
|
5734
|
+
(cd "$REPO_DIR" && bash scripts/smoke-setup-verdict.sh)
|
|
5735
|
+
;;
|
|
4940
5736
|
check-agy-permission-matrix)
|
|
4941
5737
|
# The agy permission engine's CONTRACT SPACE as a literal table (55 cells + stated
|
|
4942
5738
|
# exclusions R2/R4-R7): parser-state × operation × settings × ownership × precedence.
|
|
@@ -4993,8 +5789,8 @@ case "$cmd" in
|
|
|
4993
5789
|
# own the statusLine subtree WHOLE (preserve unrelated keys) + state (stable command, prior
|
|
4994
5790
|
# subtree as preimage), doctor static-clean/live-SKIP (+live-consistent with a fake agy) /
|
|
4995
5791
|
# drift-FAIL / dangling-command-FAIL / not-ours note, honest-inverse uninstall, symlink +
|
|
4996
|
-
# dangling-symlink refuse, create-new inverse, the
|
|
4997
|
-
# impurity 0. Offline + deterministic (deps: bash+python3).
|
|
5792
|
+
# dangling-symlink refuse, create-new inverse, the truthful wire wrapper (detected refusal
|
|
5793
|
+
# → nonzero, #86 C6/C7), and checkout impurity 0. Offline + deterministic (deps: bash+python3).
|
|
4998
5794
|
(cd "$REPO_DIR" && bash scripts/smoke-agy-statusline-state.sh)
|
|
4999
5795
|
;;
|
|
5000
5796
|
smoke-agy-hooks-state)
|
|
@@ -5024,8 +5820,10 @@ case "$cmd" in
|
|
|
5024
5820
|
# 0.12.6 install-boundary gate: register-pi-package.py is the shared
|
|
5025
5821
|
# packages[] SSOT for project/user install and remove; user scope makes
|
|
5026
5822
|
# --entwurf-control load from any cwd. Idempotent, preserves unrelated
|
|
5027
|
-
# packages/keys,
|
|
5028
|
-
#
|
|
5823
|
+
# packages/keys, remove is symmetric, corrupt settings fail loud; project
|
|
5824
|
+
# scope normalizes ITS OWN stale entries while user scope carries the #86 C2
|
|
5825
|
+
# explicit ownership contract (other owners refuse; takeover-user-scope moves).
|
|
5826
|
+
# The tripwire the 2026-07-03 `pi install` removal lacked.
|
|
5029
5827
|
(cd "$REPO_DIR" && bash scripts/smoke-user-scope-citizen.sh)
|
|
5030
5828
|
;;
|
|
5031
5829
|
smoke-claude-native-resume-live)
|
|
@@ -5061,6 +5859,86 @@ case "$cmd" in
|
|
|
5061
5859
|
# zero claude-code meta-records is a SILENT MISS -> non-zero exit.
|
|
5062
5860
|
(cd "$REPO_DIR" && bash scripts/meta-bridge-doctor.sh "$@")
|
|
5063
5861
|
;;
|
|
5862
|
+
install-copilot-bridge)
|
|
5863
|
+
# #82: the Copilot BIRTH install. Deliberately not a mode of install-meta-bridge —
|
|
5864
|
+
# it assembles its OWN marketplace root (the Claude assembler copies one plugin out
|
|
5865
|
+
# of one root, so a shared marketplace.json would publish a missing `source`), it
|
|
5866
|
+
# bakes node+entry into the launcher rather than into the manifest (Copilot's exec
|
|
5867
|
+
# form is a single string with no argv beside it), and it wires NO MCP: a drain tool
|
|
5868
|
+
# for a mailbox nothing rings would advertise delivery this backend does not have.
|
|
5869
|
+
shift || true
|
|
5870
|
+
(cd "$REPO_DIR" && bash scripts/copilot-bridge-install.sh "$@")
|
|
5871
|
+
;;
|
|
5872
|
+
uninstall-copilot-bridge)
|
|
5873
|
+
# #86 C3a: the package-owned inverse of install-copilot-bridge. The ownership state
|
|
5874
|
+
# is the sole removal authority (exact qualified id, exact marketplace name+path,
|
|
5875
|
+
# recorded assembly only); a failing vendor list is UNKNOWN and refuses; state is
|
|
5876
|
+
# deleted LAST so any partial failure keeps a rerun-repair authority. Never touches
|
|
5877
|
+
# the stale Claude unit and never uses --force.
|
|
5878
|
+
shift || true
|
|
5879
|
+
(cd "$REPO_DIR" && bash scripts/copilot-bridge-uninstall.sh "$@")
|
|
5880
|
+
;;
|
|
5881
|
+
doctor-copilot-bridge)
|
|
5882
|
+
# #82: the fail-loud surface for the Copilot unit. Its red conditions differ from
|
|
5883
|
+
# the Claude doctor's on purpose: a Copilot session mints on its FIRST PROMPT, not
|
|
5884
|
+
# at session open, so "installed with zero records" is reported as NOT-YET rather
|
|
5885
|
+
# than as a silent miss. Red is a hook that RAN and failed.
|
|
5886
|
+
shift || true
|
|
5887
|
+
(cd "$REPO_DIR" && bash scripts/copilot-bridge-doctor.sh "$@")
|
|
5888
|
+
;;
|
|
5889
|
+
install-copilot-statusline)
|
|
5890
|
+
(cd "$REPO_DIR" && bash scripts/copilot-statusline-bridge.sh install "$@")
|
|
5891
|
+
;;
|
|
5892
|
+
uninstall-copilot-statusline)
|
|
5893
|
+
(cd "$REPO_DIR" && bash scripts/copilot-statusline-bridge.sh uninstall "$@")
|
|
5894
|
+
;;
|
|
5895
|
+
doctor-copilot-statusline)
|
|
5896
|
+
(cd "$REPO_DIR" && bash scripts/copilot-statusline-bridge.sh doctor "$@")
|
|
5897
|
+
;;
|
|
5898
|
+
smoke-copilot-statusline-state)
|
|
5899
|
+
(cd "$REPO_DIR" && bash scripts/smoke-copilot-statusline-state.sh)
|
|
5900
|
+
;;
|
|
5901
|
+
install-copilot-mcp)
|
|
5902
|
+
(cd "$REPO_DIR" && bash scripts/copilot-mcp-bridge.sh install "$@")
|
|
5903
|
+
;;
|
|
5904
|
+
uninstall-copilot-mcp)
|
|
5905
|
+
(cd "$REPO_DIR" && bash scripts/copilot-mcp-bridge.sh uninstall "$@")
|
|
5906
|
+
;;
|
|
5907
|
+
doctor-copilot-mcp)
|
|
5908
|
+
(cd "$REPO_DIR" && bash scripts/copilot-mcp-bridge.sh doctor "$@")
|
|
5909
|
+
;;
|
|
5910
|
+
smoke-copilot-mcp-state)
|
|
5911
|
+
(cd "$REPO_DIR" && bash scripts/smoke-copilot-mcp-state.sh)
|
|
5912
|
+
;;
|
|
5913
|
+
copilot)
|
|
5914
|
+
# #82 RAIL 7: the managed launch. `exec` and NO subshell/cd on purpose — the vendor
|
|
5915
|
+
# must inherit this terminal exactly: the caller's cwd, this pid, this tty, and its own
|
|
5916
|
+
# exit status. The sibling install/doctor verbs wrap themselves in `(cd "$REPO_DIR" && …)`
|
|
5917
|
+
# because they operate on the repo; this one operates on the operator's session, and a
|
|
5918
|
+
# subshell would make run.sh a parent that outlives nothing and owns nothing.
|
|
5919
|
+
#
|
|
5920
|
+
# `shift` is load-bearing here, not cosmetic: this dispatcher keeps the verb in "$@"
|
|
5921
|
+
# and each branch drops it for itself. The neighbouring install/doctor verbs get away
|
|
5922
|
+
# without one because their bridge scripts take a leading subcommand and ignore the
|
|
5923
|
+
# trailing noise; this branch forwards straight into the VENDOR's argv, where a stray
|
|
5924
|
+
# "copilot" would arrive as a prompt argument.
|
|
5925
|
+
shift || true
|
|
5926
|
+
exec bash "$REPO_DIR/scripts/copilot-launch.sh" "$@"
|
|
5927
|
+
;;
|
|
5928
|
+
install-copilot-receive)
|
|
5929
|
+
# #82 RAIL 5: install the RECEIVER extension into the Copilot USER extensions dir.
|
|
5930
|
+
# Artifact ownership only — it sets no launch flag (`run.sh copilot` owns one
|
|
5931
|
+
# invocation's environment; this owns the unit on disk), and it arms
|
|
5932
|
+
# nothing by itself: a session arms after birth, on a CLI launched with
|
|
5933
|
+
# COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS.
|
|
5934
|
+
(cd "$REPO_DIR" && bash scripts/copilot-receive-bridge.sh install "$@")
|
|
5935
|
+
;;
|
|
5936
|
+
uninstall-copilot-receive)
|
|
5937
|
+
(cd "$REPO_DIR" && bash scripts/copilot-receive-bridge.sh uninstall "$@")
|
|
5938
|
+
;;
|
|
5939
|
+
doctor-copilot-receive)
|
|
5940
|
+
(cd "$REPO_DIR" && bash scripts/copilot-receive-bridge.sh doctor "$@")
|
|
5941
|
+
;;
|
|
5064
5942
|
install-agy-bridge)
|
|
5065
5943
|
# 봉인 7: the agy (Antigravity) MCP install ADAPTER (SEPARATE from the Claude
|
|
5066
5944
|
# marketplace install — only runner/reporting is shared). Registers ONE entwurf-bridge
|
|
@@ -5085,11 +5963,12 @@ case "$cmd" in
|
|
|
5085
5963
|
(cd "$REPO_DIR" && bash scripts/agy-bridge.sh doctor "$@")
|
|
5086
5964
|
;;
|
|
5087
5965
|
wire-agy-bridge)
|
|
5088
|
-
# 막힘 ①: the detection-gated,
|
|
5089
|
-
# PATH → idempotent install; no agy → honest skip, no state
|
|
5090
|
-
#
|
|
5091
|
-
#
|
|
5092
|
-
#
|
|
5966
|
+
# 막힘 ①: the detection-gated, truthful setup wrapper around install-agy-bridge (agy on
|
|
5967
|
+
# PATH → idempotent install; no agy → honest skip, no state; detected refusal/corrupt →
|
|
5968
|
+
# reason-specific WARN + nonzero so the aggregate records a named component FAIL while later
|
|
5969
|
+
# components stay attempted, #86 C6/C7). HIDDEN/internal — setup calls this; it is exposed
|
|
5970
|
+
# as a subcommand only so smoke-agy-install-state can drive it deterministically. The hard
|
|
5971
|
+
# per-leaf gate stays doctor-agy-bridge (issue #45).
|
|
5093
5972
|
wire_agy_bridge
|
|
5094
5973
|
;;
|
|
5095
5974
|
install-agy-statusline)
|
|
@@ -5157,9 +6036,10 @@ case "$cmd" in
|
|
|
5157
6036
|
run_ts scripts/doctor-pi-provider.ts "$@"
|
|
5158
6037
|
;;
|
|
5159
6038
|
wire-agy-statusline)
|
|
5160
|
-
# #46 Task 1: the detection-gated,
|
|
5161
|
-
#
|
|
5162
|
-
#
|
|
6039
|
+
# #46 Task 1: the detection-gated, truthful setup wrapper around install-agy-statusline
|
|
6040
|
+
# (detected refusal/corrupt → WARN + nonzero, named component FAIL in the aggregate, #86
|
|
6041
|
+
# C6/C7). HIDDEN/internal — setup calls this; exposed so smoke-agy-statusline-state can
|
|
6042
|
+
# drive it deterministically. The hard per-leaf gate stays doctor-agy-statusline.
|
|
5163
6043
|
wire_agy_statusline
|
|
5164
6044
|
;;
|
|
5165
6045
|
wire-agy-hooks)
|
|
@@ -5167,14 +6047,16 @@ case "$cmd" in
|
|
|
5167
6047
|
wire_agy_hooks
|
|
5168
6048
|
;;
|
|
5169
6049
|
expose-dev-bin)
|
|
5170
|
-
# 막힘 ②: expose the
|
|
5171
|
-
#
|
|
5172
|
-
#
|
|
5173
|
-
#
|
|
6050
|
+
# 막힘 ②: expose the stable package commands on PATH for a DEV checkout, including the
|
|
6051
|
+
# `entwurf` operator command Copilot fresh resolves. HIDDEN/internal — setup requires the
|
|
6052
|
+
# core operator certification and attempts every helper unit independently; a foreign helper
|
|
6053
|
+
# propagates rc=3 as a named bins FAIL (#86 C10). Exposed here so the install smoke can
|
|
6054
|
+
# drive ownership/refusal.
|
|
6055
|
+
# The exposure logic lives in scripts/dev-bin.sh.
|
|
5174
6056
|
expose_dev_bin
|
|
5175
6057
|
;;
|
|
5176
6058
|
remove-dev-bin)
|
|
5177
|
-
# 막힘 ②: honest inverse of expose-dev-bin — remove ONLY our managed
|
|
6059
|
+
# 막힘 ②: honest inverse of expose-dev-bin — remove ONLY our managed links + states (REFUSE if
|
|
5178
6060
|
# it became foreign). The raw script (no wrapper) so an operator sees a loud failure.
|
|
5179
6061
|
shift || true
|
|
5180
6062
|
(cd "$REPO_DIR" && bash scripts/dev-bin.sh remove "$@")
|
|
@@ -5316,6 +6198,9 @@ case "$cmd" in
|
|
|
5316
6198
|
check-pack)
|
|
5317
6199
|
check_pack
|
|
5318
6200
|
;;
|
|
6201
|
+
check-pack-pin-matcher)
|
|
6202
|
+
check_pack_pin_matcher
|
|
6203
|
+
;;
|
|
5319
6204
|
check-pack-install)
|
|
5320
6205
|
check_pack_install
|
|
5321
6206
|
;;
|
|
@@ -5329,9 +6214,6 @@ case "$cmd" in
|
|
|
5329
6214
|
# from under node_modules rather than re-packing the installed copy.
|
|
5330
6215
|
(cd "$REPO_DIR" && bash scripts/check-install-container.sh "$@")
|
|
5331
6216
|
;;
|
|
5332
|
-
sync-auth)
|
|
5333
|
-
sync_auth
|
|
5334
|
-
;;
|
|
5335
6217
|
install)
|
|
5336
6218
|
install_local_package "$TARGET_PROJECT_DIR"
|
|
5337
6219
|
;;
|
|
@@ -5341,6 +6223,17 @@ case "$cmd" in
|
|
|
5341
6223
|
remove-user-scope)
|
|
5342
6224
|
remove_user_scope_citizen
|
|
5343
6225
|
;;
|
|
6226
|
+
takeover-user-scope)
|
|
6227
|
+
# #86 C2: operator-explicit ownership move — the ONLY writer allowed to replace
|
|
6228
|
+
# another root's user-scope registration (normal install/setup/remove refuse).
|
|
6229
|
+
takeover_user_scope
|
|
6230
|
+
;;
|
|
6231
|
+
doctor-pi-package)
|
|
6232
|
+
# #86 C2: package-side ownership verdict (unregistered/owned/owned-by-other/
|
|
6233
|
+
# legacy-no-state/mismatch/missing-owner). Provider runtime stays with
|
|
6234
|
+
# doctor-pi-provider — the two doctors are deliberately not mixed.
|
|
6235
|
+
doctor_pi_package
|
|
6236
|
+
;;
|
|
5344
6237
|
-h|--help|help|"")
|
|
5345
6238
|
usage
|
|
5346
6239
|
;;
|