@junghanacs/entwurf 0.12.0
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 +240 -0
- package/BASELINE.md +227 -0
- package/CHANGELOG.md +1210 -0
- package/CONTRIBUTING.md +63 -0
- package/DELIVERY.md +209 -0
- package/LICENSE +21 -0
- package/README.md +504 -0
- package/VERIFY.md +260 -0
- package/demo/README.md +188 -0
- package/demo/demo-baseline.sh +156 -0
- package/demo/demo.sh +183 -0
- package/docs/assets/entwurf-demo.gif +0 -0
- package/docs/assets/entwurf-doomemacs.gif +0 -0
- package/docs/assets/entwurf-entwurf.gif +0 -0
- package/docs/assets/entwurf-hero.jpg +0 -0
- package/docs/setup-clean-host.md +305 -0
- package/mcp/entwurf-bridge/src/index.ts +513 -0
- package/mcp/entwurf-bridge/start.sh +25 -0
- package/mcp/entwurf-bridge/test.sh +54 -0
- package/mcp/tsconfig.json +29 -0
- package/package.json +130 -0
- package/pi/entwurf-capabilities.json +9 -0
- package/pi/entwurf-targets.json +20 -0
- package/pi/meta-bridge/.claude-plugin/marketplace.json +12 -0
- package/pi/meta-bridge/entwurf-meta-receive/.claude-plugin/plugin.json +5 -0
- package/pi/meta-bridge/entwurf-meta-receive/hooks/hooks.json +50 -0
- package/pi/meta-bridge/entwurf-meta-receive/scripts/doorbell.sh +54 -0
- package/pi/settings.reference.json +43 -0
- package/pi/skill-plugin-example/.claude-plugin/plugin.json +4 -0
- package/pi/skill-plugin-example/skills/hello/SKILL.md +18 -0
- package/pi-extensions/acp-provider.ts +63 -0
- package/pi-extensions/entwurf-control.ts +1692 -0
- package/pi-extensions/lib/acp/acp-client.ts +90 -0
- package/pi-extensions/lib/acp/augment.ts +238 -0
- package/pi-extensions/lib/acp/backend-adapter.ts +319 -0
- package/pi-extensions/lib/acp/backend.ts +873 -0
- package/pi-extensions/lib/acp/config.ts +552 -0
- package/pi-extensions/lib/acp/context.ts +177 -0
- package/pi-extensions/lib/acp/engraving.ts +123 -0
- package/pi-extensions/lib/acp/event-mapper.ts +339 -0
- package/pi-extensions/lib/acp/models.ts +102 -0
- package/pi-extensions/lib/acp/overlay.ts +220 -0
- package/pi-extensions/lib/acp/prompts/engraving.md +1 -0
- package/pi-extensions/lib/acp/session-store.ts +418 -0
- package/pi-extensions/lib/acp/tool-surface.ts +184 -0
- package/pi-extensions/lib/entwurf-control-rpc.ts +208 -0
- package/pi-extensions/lib/entwurf-core.ts +2033 -0
- package/pi-extensions/lib/entwurf-deliverability.ts +123 -0
- package/pi-extensions/lib/entwurf-fact-provider.ts +158 -0
- package/pi-extensions/lib/entwurf-facts.ts +251 -0
- package/pi-extensions/lib/entwurf-mailbox-guard.ts +100 -0
- package/pi-extensions/lib/entwurf-peers-render.ts +154 -0
- package/pi-extensions/lib/entwurf-preflight.ts +247 -0
- package/pi-extensions/lib/entwurf-resume-args.ts +88 -0
- package/pi-extensions/lib/entwurf-self-address.ts +117 -0
- package/pi-extensions/lib/entwurf-v2-contract.ts +427 -0
- package/pi-extensions/lib/entwurf-v2-decider.ts +441 -0
- package/pi-extensions/lib/entwurf-v2-lock.ts +412 -0
- package/pi-extensions/lib/entwurf-v2-mailbox.ts +87 -0
- package/pi-extensions/lib/entwurf-v2-production.ts +326 -0
- package/pi-extensions/lib/entwurf-v2-release.ts +149 -0
- package/pi-extensions/lib/entwurf-v2-resume-marker.ts +34 -0
- package/pi-extensions/lib/entwurf-v2-runner.ts +196 -0
- package/pi-extensions/lib/entwurf-v2-send-fallback.ts +166 -0
- package/pi-extensions/lib/entwurf-v2-send.ts +271 -0
- package/pi-extensions/lib/entwurf-v2-spawn-production.ts +337 -0
- package/pi-extensions/lib/entwurf-v2-spawn.ts +323 -0
- package/pi-extensions/lib/entwurf-v2-surface.ts +205 -0
- package/pi-extensions/lib/meta-mailbox-body.ts +80 -0
- package/pi-extensions/lib/meta-session.ts +1951 -0
- package/pi-extensions/lib/project-trust-handler.ts +154 -0
- package/pi-extensions/lib/session-id.js +57 -0
- package/pi-extensions/lib/socket-discovery.ts +346 -0
- package/pi-extensions/lib/socket-probe.ts +92 -0
- package/pi-extensions/meta-bridge-hook.ts +250 -0
- package/pi-extensions/model-lock.ts +235 -0
- package/prompts/engraving.md +27 -0
- package/protocol.js +31 -0
- package/run.sh +2832 -0
- package/scripts/check-acp-backend-preflight.ts +130 -0
- package/scripts/check-acp-carrier-augment.ts +297 -0
- package/scripts/check-acp-config.ts +322 -0
- package/scripts/check-acp-event-mapper.ts +253 -0
- package/scripts/check-acp-overlay.ts +154 -0
- package/scripts/check-acp-prompt-builder.ts +191 -0
- package/scripts/check-acp-provider-surface.ts +159 -0
- package/scripts/check-acp-sdk-surface.ts +211 -0
- package/scripts/check-acp-session-reuse.ts +756 -0
- package/scripts/check-acp-session-store.ts +387 -0
- package/scripts/check-acp-tool-surface.ts +159 -0
- package/scripts/check-entwurf-bridge-boot.ts +199 -0
- package/scripts/check-entwurf-capabilities.ts +123 -0
- package/scripts/check-entwurf-control-rpc.ts +187 -0
- package/scripts/check-entwurf-deliverability.ts +158 -0
- package/scripts/check-entwurf-fact-provider.ts +316 -0
- package/scripts/check-entwurf-facts.ts +358 -0
- package/scripts/check-entwurf-mailbox-guard.ts +264 -0
- package/scripts/check-entwurf-peers-surface.ts +235 -0
- package/scripts/check-entwurf-resume-args.ts +149 -0
- package/scripts/check-entwurf-self-address.ts +209 -0
- package/scripts/check-entwurf-session-identity.ts +703 -0
- package/scripts/check-entwurf-v2-contract.ts +536 -0
- package/scripts/check-entwurf-v2-decider.ts +795 -0
- package/scripts/check-entwurf-v2-lock.ts +368 -0
- package/scripts/check-entwurf-v2-mailbox.ts +228 -0
- package/scripts/check-entwurf-v2-matrix.ts +437 -0
- package/scripts/check-entwurf-v2-production.ts +406 -0
- package/scripts/check-entwurf-v2-release.ts +245 -0
- package/scripts/check-entwurf-v2-runner.ts +452 -0
- package/scripts/check-entwurf-v2-send-fallback.ts +371 -0
- package/scripts/check-entwurf-v2-send.ts +454 -0
- package/scripts/check-entwurf-v2-spawn-production.ts +452 -0
- package/scripts/check-entwurf-v2-spawn.ts +398 -0
- package/scripts/check-entwurf-v2-surface.ts +352 -0
- package/scripts/check-keyset-overlap.py +120 -0
- package/scripts/check-mailbox-receipt-state.ts +170 -0
- package/scripts/check-meta-capability-source.ts +112 -0
- package/scripts/check-meta-dual-consumers.ts +154 -0
- package/scripts/check-meta-dual-read.ts +158 -0
- package/scripts/check-meta-listing.ts +138 -0
- package/scripts/check-meta-mailbox-state-write.ts +135 -0
- package/scripts/check-meta-migration.ts +212 -0
- package/scripts/check-meta-receiver-marker.ts +185 -0
- package/scripts/check-meta-record-v2.ts +191 -0
- package/scripts/check-meta-session.ts +673 -0
- package/scripts/check-model-lock.ts +408 -0
- package/scripts/check-package-source-routing.ts +253 -0
- package/scripts/check-pi-preflight.ts +304 -0
- package/scripts/check-project-trust-handler.ts +265 -0
- package/scripts/check-shell-quote.ts +121 -0
- package/scripts/check-socket-discovery.ts +428 -0
- package/scripts/check-socket-probe.ts +106 -0
- package/scripts/fixtures/probe-mcp-server.ts +33 -0
- package/scripts/gnew-rpc-drive.ts +211 -0
- package/scripts/lib/acp-child-cleanup.ts +116 -0
- package/scripts/meta-bridge-doctor.sh +315 -0
- package/scripts/meta-bridge-hook-log.sh +26 -0
- package/scripts/meta-bridge-install.sh +135 -0
- package/scripts/meta-bridge-prune.ts +199 -0
- package/scripts/meta-bridge-state.py +549 -0
- package/scripts/meta-bridge-statusline.sh +192 -0
- package/scripts/meta-bridge-store-doctor.ts +64 -0
- package/scripts/meta-bridge-uninstall.sh +39 -0
- package/scripts/new-session-id.ts +25 -0
- package/scripts/postinstall-chmod.cjs +58 -0
- package/scripts/raw-async-delivery/README.md +258 -0
- package/scripts/raw-async-delivery/cc-enqueue-addressed.sh +35 -0
- package/scripts/raw-async-delivery/cc-mailbox-rewake.sh +38 -0
- package/scripts/raw-async-delivery/cc-watch-filechanged.sh +19 -0
- package/scripts/raw-async-delivery/cc-watch-sessionstart.sh +15 -0
- package/scripts/raw-async-delivery/codex-local-appserver.sh +39 -0
- package/scripts/raw-async-delivery/plugin-entwurf-receive/.claude-plugin/plugin.json +5 -0
- package/scripts/raw-async-delivery/plugin-entwurf-receive/hooks/hooks.json +28 -0
- package/scripts/raw-async-delivery/plugin-entwurf-receive/scripts/watch-filechanged.sh +28 -0
- package/scripts/raw-async-delivery/plugin-entwurf-receive/scripts/watch-sessionstart.sh +27 -0
- package/scripts/raw-async-delivery/raw-agy-send.sh +29 -0
- package/scripts/raw-async-delivery/raw-claude-enqueue.sh +30 -0
- package/scripts/raw-async-delivery/raw-codex-ws-turn-start.py +164 -0
- package/scripts/raw-async-delivery/repro-addressed-routing.sh +96 -0
- package/scripts/raw-async-delivery/repro-plugin-idle-wake.sh +104 -0
- package/scripts/resolve-acp-bridge.ts +25 -0
- package/scripts/smoke-acp-bundled-mcp-live.ts +292 -0
- package/scripts/smoke-acp-carrier-augment-live.ts +192 -0
- package/scripts/smoke-acp-mcp-live.ts +129 -0
- package/scripts/smoke-acp-memory-containment-live.ts +389 -0
- package/scripts/smoke-acp-overlay-live.ts +314 -0
- package/scripts/smoke-acp-provider-live.ts +162 -0
- package/scripts/smoke-acp-raw-turn-live.ts +261 -0
- package/scripts/smoke-acp-session-reuse-live.ts +172 -0
- package/scripts/smoke-acp-skill-live.ts +144 -0
- package/scripts/smoke-acp-socket-citizen-live.ts +168 -0
- package/scripts/smoke-claude-native-resume-live.sh +198 -0
- package/scripts/smoke-entwurf-v2-matrix-live.ts +398 -0
- package/scripts/smoke-entwurf-v2-spawn-live.ts +175 -0
- package/scripts/smoke-entwurf-v2-spawn-resume-live.ts +428 -0
- package/scripts/smoke-meta-async-drift.sh +171 -0
- package/scripts/smoke-meta-honesty.sh +147 -0
- package/scripts/smoke-meta-install-state.sh +403 -0
- package/scripts/smoke-meta-keyset-guard.sh +111 -0
- package/scripts/smoke-meta-prune.sh +174 -0
- package/scripts/smoke-resident-garden-guard.sh +433 -0
- package/scripts/smoke-session-id-name.ts +187 -0
- package/scripts/tsconfig.json +34 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# meta-bridge-doctor.sh — the FAIL-LOUD surface for the meta-bridge (1.0.0 step 5).
|
|
3
|
+
#
|
|
4
|
+
# The runtime hook is best-effort + log (it must never scream into a user's
|
|
5
|
+
# terminal or block startup). THIS is where a silent miss becomes loud. It proves
|
|
6
|
+
# the whole chain a real user depends on:
|
|
7
|
+
# platform -> claude/node toolchain -> the BAKED node path still resolves
|
|
8
|
+
# (catches NixOS /nix/store churn after a rebuild) -> plugin installed+enabled
|
|
9
|
+
# globally -> meta-record dir writable -> SessionStart actually landed a record
|
|
10
|
+
# (hook log + >=1 claude-code meta-record). Plugin present but zero record
|
|
11
|
+
# evidence = the dangerous silent miss -> non-zero exit.
|
|
12
|
+
#
|
|
13
|
+
# Exit 0 = the meta-bridge is wired AND proven to have created a garden citizen.
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
MKT_NAME="meta-bridge-local"
|
|
17
|
+
PLUGIN="entwurf-meta-receive"
|
|
18
|
+
CLAUDE_CFG="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
|
|
19
|
+
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
20
|
+
# shellcheck source=scripts/meta-bridge-hook-log.sh
|
|
21
|
+
source "$REPO/scripts/meta-bridge-hook-log.sh"
|
|
22
|
+
|
|
23
|
+
fail=0
|
|
24
|
+
ok() { echo " ok $*"; }
|
|
25
|
+
warn() { echo " WARN $*"; }
|
|
26
|
+
bad() { echo " FAIL $*"; fail=1; }
|
|
27
|
+
|
|
28
|
+
# pi agent dir resolution — mirror meta-session.ts (PI_CODING_AGENT_DIR or ~/.pi/agent).
|
|
29
|
+
agent_dir() {
|
|
30
|
+
if [ -n "${PI_CODING_AGENT_DIR:-}" ]; then
|
|
31
|
+
case "$PI_CODING_AGENT_DIR" in "~") echo "$HOME";; "~/"*) echo "$HOME/${PI_CODING_AGENT_DIR#\~/}";; *) echo "$PI_CODING_AGENT_DIR";; esac
|
|
32
|
+
else echo "$HOME/.pi/agent"; fi
|
|
33
|
+
}
|
|
34
|
+
AGENT="$(agent_dir)"
|
|
35
|
+
META_SESSIONS="${ENTWURF_META_SESSIONS_DIR:-$AGENT/meta-sessions}"
|
|
36
|
+
HOOK_LOG="$AGENT/meta-bridge-hook.log"
|
|
37
|
+
|
|
38
|
+
echo "meta-bridge doctor"
|
|
39
|
+
echo "config=$CLAUDE_CFG agent-dir=$AGENT"
|
|
40
|
+
|
|
41
|
+
echo "[platform]"
|
|
42
|
+
case "$(uname -s)" in
|
|
43
|
+
Linux | Darwin) ok "$(uname -s) supported" ;;
|
|
44
|
+
*) bad "$(uname -s) unsupported (Linux/macOS only)" ;;
|
|
45
|
+
esac
|
|
46
|
+
|
|
47
|
+
echo "[toolchain]"
|
|
48
|
+
if command -v claude >/dev/null; then ok "claude: $(claude --version 2>/dev/null | head -1)"; else bad "claude not on PATH"; fi
|
|
49
|
+
if command -v python3 >/dev/null; then ok "python3: $(python3 --version 2>/dev/null | head -1) (doorbell JSON parser)"; else bad "python3 not on PATH — FileChanged wake runtime would silently die"; fi
|
|
50
|
+
if command -v node >/dev/null; then
|
|
51
|
+
NV="$(node -p 'process.versions.node' 2>/dev/null || echo 0)"; MJ="${NV%%.*}"; MN="$(printf '%s' "$NV" | cut -d. -f2)"
|
|
52
|
+
if [ "${MJ:-0}" -gt 22 ] || { [ "${MJ:-0}" -eq 22 ] && [ "${MN:-0}" -ge 6 ]; }; then ok "node $NV (>= 22.6 strip-types)"; else bad "node $NV too old (need >= 22.6)"; fi
|
|
53
|
+
else bad "node not on PATH"; fi
|
|
54
|
+
|
|
55
|
+
echo "[managed config state]"
|
|
56
|
+
if command -v python3 >/dev/null; then
|
|
57
|
+
# state.py check is the effect-based keyset-survival guard: it asserts every
|
|
58
|
+
# pi-owned key (scalar policy, permissions items, plugin/marketplace,
|
|
59
|
+
# statusLine, user MCP) still holds its expected value. If another consumer
|
|
60
|
+
# (agent-config merge, hand edit) overwrote one, this fails. Surface WHICH key
|
|
61
|
+
# drifted — a blanket "keyset drifted" sends the operator hunting.
|
|
62
|
+
# NB: branch on the exit code, NOT on empty stderr. A bare CHECK_ERR="$(...)"
|
|
63
|
+
# assignment dies under `set -e` the moment state.py exits nonzero (drift), so
|
|
64
|
+
# the doctor would print "[managed config state]" and exit 1 BEFORE showing
|
|
65
|
+
# which key drifted — silent instead of fail-loud. As an `if` condition the
|
|
66
|
+
# substitution's nonzero status is consumed (set -e suspended), so we always
|
|
67
|
+
# reach the detail. stderr (drift detail) is captured via `2>&1 >/dev/null`.
|
|
68
|
+
if CHECK_ERR="$(python3 "$REPO/scripts/meta-bridge-state.py" check --repo "$REPO" --asm "$REPO/pi/meta-bridge/.assembled" 2>&1 >/dev/null)"; then
|
|
69
|
+
ok "state file present and managed settings/MCP keyset survives intact"
|
|
70
|
+
else
|
|
71
|
+
bad "managed settings/MCP keyset missing or overwritten — another consumer may have clobbered a pi-owned key. Re-run ./run.sh install-meta-bridge. Drift detail:"
|
|
72
|
+
printf '%s\n' "$CHECK_ERR" | sed 's/^/ /'
|
|
73
|
+
fi
|
|
74
|
+
else
|
|
75
|
+
bad "cannot validate stateful install without python3"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
echo "[plugin install (global / --scope user)]"
|
|
79
|
+
if claude plugin list 2>/dev/null | grep -q "$PLUGIN@$MKT_NAME"; then
|
|
80
|
+
ok "$PLUGIN@$MKT_NAME present"
|
|
81
|
+
claude plugin list 2>/dev/null | grep -A3 "$PLUGIN@$MKT_NAME" | grep -qi "enabled" && ok "enabled" || bad "installed but NOT enabled"
|
|
82
|
+
else
|
|
83
|
+
bad "$PLUGIN@$MKT_NAME not installed — run ./run.sh install-meta-bridge"
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
echo "[baked node path still resolves (NixOS store-churn guard)]"
|
|
87
|
+
CACHE_HOOKS="$(ls "$CLAUDE_CFG/plugins/cache/$MKT_NAME/$PLUGIN/"*/hooks/hooks.json 2>/dev/null | head -1 || true)"
|
|
88
|
+
if [ -n "$CACHE_HOOKS" ]; then
|
|
89
|
+
# `[ -n "$CACHE_HOOKS" ]` proves the file EXISTS, not that the pattern matches.
|
|
90
|
+
# If a future hooks.json drifts the command format, grep finds nothing, exits 1,
|
|
91
|
+
# and under `set -euo pipefail` the doctor would abort here instead of reaching
|
|
92
|
+
# the parse-drift diagnostic below. Swallow grep's nonzero in-pipe (same idiom as
|
|
93
|
+
# line 158) so BAKED="" routes to that bad(...): an installed hooks.json exists but
|
|
94
|
+
# the doctor cannot read its load-bearing hook command, so the store-churn guard is
|
|
95
|
+
# blind — a verify-impossible state must fail loud, not pass as a soft warn (A3b).
|
|
96
|
+
BAKED="$({ grep -oE '"command": "[^ ]+ \$\{CLAUDE_PLUGIN_ROOT\}/meta-bridge-hook.ts"' "$CACHE_HOOKS" || true; } | head -1 | sed -E 's/.*"command": "([^ ]+) .*/\1/')"
|
|
97
|
+
if [ -n "$BAKED" ] && [ -x "$BAKED" ]; then ok "baked node exists + executable: $BAKED"
|
|
98
|
+
elif [ -n "$BAKED" ]; then bad "baked node path is DEAD (nix GC / version bump?): $BAKED — re-run ./run.sh install-meta-bridge"
|
|
99
|
+
else bad "could not parse baked node path from $CACHE_HOOKS — hook command format drift; doctor cannot verify the NixOS store-churn guard. Re-run ./run.sh install-meta-bridge or update the doctor parser."; fi
|
|
100
|
+
else
|
|
101
|
+
warn "no installed hooks.json in cache (plugin not installed?)"
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
echo "[statusline — garden identity visible in native Claude]"
|
|
105
|
+
if command -v python3 >/dev/null; then
|
|
106
|
+
STATUSLINE_CMD="$(python3 - <<'PY'
|
|
107
|
+
import json, os
|
|
108
|
+
p=os.path.expanduser('~/.claude/settings.json') if not os.environ.get('CLAUDE_CONFIG_DIR') else os.path.join(os.environ['CLAUDE_CONFIG_DIR'], 'settings.json')
|
|
109
|
+
try:
|
|
110
|
+
d=json.load(open(p))
|
|
111
|
+
sl=d.get('statusLine') if isinstance(d, dict) else None
|
|
112
|
+
print(sl.get('command','') if isinstance(sl, dict) else '')
|
|
113
|
+
except Exception:
|
|
114
|
+
print('')
|
|
115
|
+
PY
|
|
116
|
+
)"
|
|
117
|
+
EXPECTED_STATUSLINE="$REPO/scripts/meta-bridge-statusline.sh"
|
|
118
|
+
if [ "$STATUSLINE_CMD" = "$EXPECTED_STATUSLINE" ]; then ok "statusLine.command is repo-owned: $STATUSLINE_CMD"; else bad "statusLine.command drifted (got '$STATUSLINE_CMD', expected '$EXPECTED_STATUSLINE')"; fi
|
|
119
|
+
if [ -x "$EXPECTED_STATUSLINE" ]; then ok "statusline executable"; else bad "statusline script not executable: $EXPECTED_STATUSLINE"; fi
|
|
120
|
+
SAMPLE_STATUSLINE_OUT="$(printf '%s' '{"session_id":"doctor-no-record","workspace":{"current_dir":"/tmp"},"model":{"id":"claude-sonnet-4-6"},"context_window":{"context_window_size":200000,"used_percentage":1,"current_usage":{"input_tokens":1}}}' | "$EXPECTED_STATUSLINE" 2>/dev/null || true)"
|
|
121
|
+
if [ "$(printf '%s\n' "$SAMPLE_STATUSLINE_OUT" | wc -l | tr -d ' ')" = "2" ] && printf '%s\n' "$SAMPLE_STATUSLINE_OUT" | sed -n '1p' | grep -q 'tmp' && printf '%s\n' "$SAMPLE_STATUSLINE_OUT" | sed -n '2p' | grep -q '🪛' && printf '%s\n' "$SAMPLE_STATUSLINE_OUT" | sed -n '2p' | grep -q ' cc | s'; then ok "statusline synthetic execution emits two rows (work context + identity)"; else bad "statusline synthetic execution failed or omitted two-row work/identity marker"; fi
|
|
122
|
+
else
|
|
123
|
+
bad "cannot validate statusline without python3"
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
echo "[receiver MCP reach — entwurf_inbox_read in EVERY native session, NOT plugin-owned]"
|
|
127
|
+
# The plugin owns ONLY the wake/record hooks. The receiver self-fetch tool
|
|
128
|
+
# (entwurf_inbox_read) comes from the user's entwurf-bridge MCP wiring — a Claude
|
|
129
|
+
# Code / agent-config responsibility, never injected here (injecting from the
|
|
130
|
+
# plugin duplicates the server and drops its identity env). The honest test is
|
|
131
|
+
# GLOBAL REACH: a native session in an arbitrary cwd must see the tool. A
|
|
132
|
+
# PROJECT-scoped ~/.mcp.json is NOT enough (it only reaches its own project; a
|
|
133
|
+
# /tmp session would wake with no way to record its receipt). USER scope is.
|
|
134
|
+
# So probe from a neutral non-project cwd, exactly like a real native session.
|
|
135
|
+
if command -v claude >/dev/null; then
|
|
136
|
+
MCP_GET="$(cd /tmp && claude mcp get entwurf-bridge 2>/dev/null || true)"
|
|
137
|
+
if printf '%s\n' "$MCP_GET" | grep -q "Scope: User config" && printf '%s\n' "$MCP_GET" | grep -q "Status: .*Connected"; then
|
|
138
|
+
ok "entwurf-bridge reachable from a neutral cwd (/tmp) as USER-scope MCP — every native session can entwurf_inbox_read"
|
|
139
|
+
else
|
|
140
|
+
bad "entwurf-bridge is not USER-scope+Connected from /tmp — a native session outside the wired project cannot entwurf_inbox_read, so a woken receipt is never recorded. A PROJECT-scoped ~/.mcp.json is not enough; wire it USER scope: claude mcp add -s user entwurf-bridge -e ENTWURF_BRIDGE_EXTERNAL_AGENT_ID=external-mcp/claude-code -- bash \"$REPO/mcp/entwurf-bridge/start.sh\""
|
|
141
|
+
fi
|
|
142
|
+
else
|
|
143
|
+
warn "claude not on PATH — cannot probe MCP reach"
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
echo "[meta-record store]"
|
|
147
|
+
mkdir -p "$META_SESSIONS" 2>/dev/null || true
|
|
148
|
+
if [ -d "$META_SESSIONS" ] && [ -w "$META_SESSIONS" ]; then ok "writable: $META_SESSIONS"; else bad "meta-sessions dir not writable: $META_SESSIONS"; fi
|
|
149
|
+
if command -v node >/dev/null; then
|
|
150
|
+
if node --experimental-strip-types "$REPO/scripts/meta-bridge-store-doctor.ts" "$META_SESSIONS" >/dev/null 2>&1; then
|
|
151
|
+
ok "full store scan: no corrupt records, duplicate nativeSessionId, body/filename drift, or backend↔wakeMode contradiction"
|
|
152
|
+
else
|
|
153
|
+
bad "full store scan failed — corrupt/duplicate/drift meta-record(s) present. Inspect with: node --experimental-strip-types $REPO/scripts/meta-bridge-store-doctor.ts $META_SESSIONS"
|
|
154
|
+
fi
|
|
155
|
+
else
|
|
156
|
+
bad "cannot scan meta-record store without node"
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
echo "[SessionStart creation evidence (silent-miss guard)]"
|
|
160
|
+
# `set -euo pipefail` + empty store = doctor abort: with no *.meta.json the glob
|
|
161
|
+
# stays literal, grep exits 2 (no such file) / 1 (no match), pipefail propagates,
|
|
162
|
+
# and the doctor dies right after the section header — hiding the very "open a
|
|
163
|
+
# session" hint a fresh-but-correct install needs. Swallow grep's nonzero IN the
|
|
164
|
+
# pipe (same idiom as the `[ -f ] || continue` guard below) so CC_COUNT=0 cleanly.
|
|
165
|
+
CC_COUNT="$({ grep -l '"backend": "claude-code"' "$META_SESSIONS"/*.meta.json 2>/dev/null || true; } | wc -l | tr -d ' ')"
|
|
166
|
+
if [ -f "$HOOK_LOG" ]; then
|
|
167
|
+
ok "hook log present: $HOOK_LOG ($(wc -l < "$HOOK_LOG" | tr -d ' ') lines)"
|
|
168
|
+
# ERROR is sticky in an append-only log: a one-time miss that was later recovered
|
|
169
|
+
# must NOT keep the doctor red forever. Recovery is deliberately narrow: only a
|
|
170
|
+
# later `INFO armed watch` proves a real SessionStart/CwdChanged wake path is
|
|
171
|
+
# back. A later UserPromptSubmit `INFO attach record` is degraded record
|
|
172
|
+
# backfill and must NOT clear an arm/upsert failure.
|
|
173
|
+
if hook_status="$(meta_bridge_hook_log_status "$HOOK_LOG")"; then
|
|
174
|
+
if [ "$hook_status" = "no-error" ]; then
|
|
175
|
+
ok "hook log contains no ERROR line(s)"
|
|
176
|
+
else
|
|
177
|
+
ok "hook log has past ERROR(s) but a later INFO armed watch recovered the wake path"
|
|
178
|
+
fi
|
|
179
|
+
else
|
|
180
|
+
bad "unrecovered hook ERROR (no later INFO armed watch) — last ERROR: $hook_status"
|
|
181
|
+
fi
|
|
182
|
+
if grep -q ' INFO sender marker ' "$HOOK_LOG"; then
|
|
183
|
+
ok "sender marker evidence present (native meta-session can send as replyable garden-id)"
|
|
184
|
+
else
|
|
185
|
+
bad "no sender marker evidence in hook log — native sessions may receive mail but send as anonymous external-mcp. Re-run ./run.sh install-meta-bridge, then trigger a Claude prompt/SessionStart so the updated hook writes the marker."
|
|
186
|
+
fi
|
|
187
|
+
else warn "no hook log yet ($HOOK_LOG) — open a Claude Code session first"; fi
|
|
188
|
+
if [ "${CC_COUNT:-0}" -ge 1 ]; then
|
|
189
|
+
ok "$CC_COUNT claude-code meta-record(s) landed (garden citizen proven)"
|
|
190
|
+
else
|
|
191
|
+
if claude plugin list 2>/dev/null | grep -q "$PLUGIN@$MKT_NAME"; then
|
|
192
|
+
bad "plugin installed but ZERO claude-code meta-records — SILENT MISS. Open a Claude Code session; if still zero, inspect $HOOK_LOG."
|
|
193
|
+
else
|
|
194
|
+
warn "no meta-records yet (plugin not installed)"
|
|
195
|
+
fi
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
# ── writer-version parity (source ↔ assembled ↔ installed) ──────────────────
|
|
199
|
+
# The dangerous lag: a meta-record schema cut updates the repo SOURCE but the
|
|
200
|
+
# DEPLOYED bundle (what the live SessionStart hook runs) is not re-assembled, so
|
|
201
|
+
# new sessions are still written by the old writer — invisibly. "source complete"
|
|
202
|
+
# ≠ "deployed complete". This section makes the running writer version legible:
|
|
203
|
+
# live-write schema = does the bundle carry serializeMetaIdentity (v2 identity
|
|
204
|
+
# write) or only mintMetaRecord (v1). Hash = drift catch. The authority for "what
|
|
205
|
+
# records me" is the INSTALLED bundle; a mismatch vs source is a loud FAIL.
|
|
206
|
+
echo
|
|
207
|
+
echo "writer-version parity"
|
|
208
|
+
livewrite_schema() { # $1=meta-session.ts → v2|v1|absent
|
|
209
|
+
[ -f "$1" ] || { echo "absent"; return; }
|
|
210
|
+
if grep -q "serializeMetaIdentity" "$1"; then echo "v2"; else echo "v1"; fi
|
|
211
|
+
}
|
|
212
|
+
hash12() { [ -f "$1" ] && sha256sum "$1" | cut -c1-12 || echo "------------"; }
|
|
213
|
+
registry_for_ms() { # $1=bundle meta-session.ts → sibling plugin-root registry path
|
|
214
|
+
[ -f "$1" ] || { echo ""; return; }
|
|
215
|
+
dirname "$(dirname "$1")"
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
SRC_MS="$REPO/pi-extensions/lib/meta-session.ts"
|
|
219
|
+
SRC_REG="$REPO/pi/entwurf-capabilities.json"
|
|
220
|
+
ASM_MS="$REPO/pi/meta-bridge/.assembled/$PLUGIN/lib/meta-session.ts"
|
|
221
|
+
INST_MS="$(ls "$CLAUDE_CFG"/plugins/cache/"$MKT_NAME"/"$PLUGIN"/*/lib/meta-session.ts 2>/dev/null | head -1 || true)"
|
|
222
|
+
|
|
223
|
+
ASM_ROOT="$(registry_for_ms "$ASM_MS")"
|
|
224
|
+
INST_ROOT="$(registry_for_ms "${INST_MS:-}")"
|
|
225
|
+
ASM_REG="${ASM_ROOT:+$ASM_ROOT/entwurf-capabilities.json}"
|
|
226
|
+
INST_REG="${INST_ROOT:+$INST_ROOT/entwurf-capabilities.json}"
|
|
227
|
+
|
|
228
|
+
src_v="$(livewrite_schema "$SRC_MS")"; src_h="$(hash12 "$SRC_MS")"; src_reg_h="$(hash12 "$SRC_REG")"
|
|
229
|
+
asm_v="$(livewrite_schema "$ASM_MS")"; asm_h="$(hash12 "$ASM_MS")"; asm_reg_h="$(hash12 "${ASM_REG:-/none}")"
|
|
230
|
+
inst_v="$(livewrite_schema "${INST_MS:-/none}")"; inst_h="$(hash12 "${INST_MS:-/none}")"; inst_reg_h="$(hash12 "${INST_REG:-/none}")"
|
|
231
|
+
|
|
232
|
+
echo " source : $src_v ($src_h) registry=$src_reg_h"
|
|
233
|
+
echo " assembled : $asm_v ($asm_h) registry=$asm_reg_h"
|
|
234
|
+
echo " installed : $inst_v ($inst_h) registry=$inst_reg_h ${INST_MS:-<none>}"
|
|
235
|
+
|
|
236
|
+
# store reality — distribution of schemaVersion across landed records.
|
|
237
|
+
sv1=0; sv2=0
|
|
238
|
+
for mf in "$META_SESSIONS"/*.meta.json; do
|
|
239
|
+
[ -f "$mf" ] || continue
|
|
240
|
+
case "$(grep -o '"schemaVersion"[[:space:]]*:[[:space:]]*[0-9]*' "$mf" | grep -o '[0-9]*$' | head -1)" in
|
|
241
|
+
1) sv1=$((sv1 + 1)) ;; 2) sv2=$((sv2 + 1)) ;;
|
|
242
|
+
esac
|
|
243
|
+
done
|
|
244
|
+
echo " store : v1=$sv1 v2=$sv2 (dual-read reads both)"
|
|
245
|
+
|
|
246
|
+
if [ -z "${INST_MS:-}" ]; then
|
|
247
|
+
warn "no installed bundle to compare — run ./run.sh install-meta-bridge"
|
|
248
|
+
elif [ "$inst_h" = "$src_h" ]; then
|
|
249
|
+
ok "deployed writer matches source ($inst_v) — no version lag"
|
|
250
|
+
else
|
|
251
|
+
bad "deployed writer STALE: installed=$inst_v($inst_h) vs source=$src_v($src_h). The live hook records sessions with the OLD writer. Run ./run.sh install-meta-bridge to redeploy, then open a Claude session and re-run this doctor."
|
|
252
|
+
fi
|
|
253
|
+
if [ -f "$ASM_MS" ] && [ "$asm_h" != "$src_h" ]; then
|
|
254
|
+
warn "assembled bundle differs from source ($asm_v vs $src_v) — stale .assembled; install re-assembles it"
|
|
255
|
+
fi
|
|
256
|
+
|
|
257
|
+
# v2 writer dependency: loadMetaCapabilityRegistry reads entwurf-capabilities.json
|
|
258
|
+
# at runtime. In the bundle layout it must sit at the plugin ROOT (resolved via
|
|
259
|
+
# `../` from lib/). A v2 bundle WITHOUT it throws on every mint/parse — a silent
|
|
260
|
+
# hook break that hash parity alone cannot see (it only hashes meta-session.ts).
|
|
261
|
+
check_registry_dep() { # $1=bundle meta-session.ts $2=label $3=registry-path $4=registry-hash
|
|
262
|
+
local ms="$1" label="$2" reg="$3" reg_h="$4"
|
|
263
|
+
[ -f "$ms" ] && grep -q "serializeMetaIdentity" "$ms" || return 0 # v1/absent: no registry dep
|
|
264
|
+
if [ ! -f "$reg" ]; then
|
|
265
|
+
bad "$label is v2 but MISSING entwurf-capabilities.json at plugin root ($reg) — the v2 writer throws on mint/parse (silent hook break). Re-run ./run.sh install-meta-bridge (now bundles the registry)."
|
|
266
|
+
elif [ "$reg_h" != "$src_reg_h" ]; then
|
|
267
|
+
bad "$label v2 carries a STALE capability registry: $reg_h vs source=$src_reg_h ($reg). Re-run ./run.sh install-meta-bridge so the live writer and its load-bearing registry move together."
|
|
268
|
+
else
|
|
269
|
+
ok "$label v2 carries capability registry ($reg, $reg_h)"
|
|
270
|
+
fi
|
|
271
|
+
}
|
|
272
|
+
check_registry_dep "$ASM_MS" "assembled" "${ASM_REG:-}" "$asm_reg_h"
|
|
273
|
+
check_registry_dep "$INST_MS" "installed" "${INST_REG:-}" "$inst_reg_h"
|
|
274
|
+
|
|
275
|
+
echo
|
|
276
|
+
echo "[entwurf-control / v2 dispatch surface]"
|
|
277
|
+
# The entwurf_v2 verb (0.11 step 5d-3) rides two surfaces: the pi-native tool (which
|
|
278
|
+
# depends on the --entwurf-control flag) and the MCP verb. These are SOURCE/static +
|
|
279
|
+
# single-gate checks — a live `pi --entwurf-control` model turn is 5d-5 matrix / live-smoke
|
|
280
|
+
# territory (it takes auth/model state), NOT the doctor's job. `pi --help | grep` is also
|
|
281
|
+
# avoided: flag exposure depends on extension-load conditions, so source+gate is the honest signal.
|
|
282
|
+
V2_CONTROL_SRC="$REPO/pi-extensions/entwurf-control.ts"
|
|
283
|
+
V2_SURFACE_SRC="$REPO/pi-extensions/lib/entwurf-v2-surface.ts"
|
|
284
|
+
V2_MCP_SRC="$REPO/mcp/entwurf-bridge/src/index.ts"
|
|
285
|
+
if [ -f "$V2_CONTROL_SRC" ] && grep -q 'ENTWURF_FLAG = "entwurf-control"' "$V2_CONTROL_SRC" && grep -q 'registerFlag(ENTWURF_FLAG' "$V2_CONTROL_SRC"; then
|
|
286
|
+
ok "pi-native --entwurf-control flag registered (ENTWURF_FLAG)"
|
|
287
|
+
else
|
|
288
|
+
bad "pi-native --entwurf-control flag not found in entwurf-control.ts — the v2 pi tool depends on it"
|
|
289
|
+
fi
|
|
290
|
+
if [ -f "$V2_CONTROL_SRC" ] && grep -q 'registerEntwurfV2Tool' "$V2_CONTROL_SRC" && grep -q 'name: "entwurf_v2"' "$V2_CONTROL_SRC"; then
|
|
291
|
+
ok "pi-native entwurf_v2 tool registered"
|
|
292
|
+
else
|
|
293
|
+
bad "pi-native entwurf_v2 tool not found in entwurf-control.ts"
|
|
294
|
+
fi
|
|
295
|
+
if [ -f "$V2_MCP_SRC" ] && grep -q 'server.tool(' "$V2_MCP_SRC" && grep -q '"entwurf_v2"' "$V2_MCP_SRC"; then
|
|
296
|
+
ok "MCP entwurf_v2 verb registered"
|
|
297
|
+
else
|
|
298
|
+
bad "MCP entwurf_v2 verb not found in entwurf-bridge"
|
|
299
|
+
fi
|
|
300
|
+
if [ -f "$V2_SURFACE_SRC" ] && (cd "$REPO" && node --experimental-strip-types scripts/check-entwurf-v2-surface.ts >/dev/null 2>&1); then
|
|
301
|
+
ok "check-entwurf-v2-surface gate passes (surface adapter + pi-native/MCP wiring)"
|
|
302
|
+
else
|
|
303
|
+
bad "check-entwurf-v2-surface gate FAILED or surface adapter missing — run: ./run.sh check-entwurf-v2-surface"
|
|
304
|
+
fi
|
|
305
|
+
# prefixRoots operator policy (5d-4b): the shared env SSOT both v2 surfaces read. Display
|
|
306
|
+
# ONLY — the parser SSOT lives in entwurf-v2-surface.ts (proven by check-entwurf-v2-surface);
|
|
307
|
+
# the doctor does NOT re-implement parsing. Unset ⇒ no prefix promotion (the safe default).
|
|
308
|
+
if [ -z "${ENTWURF_PREFIX_ROOTS:-}" ]; then
|
|
309
|
+
ok "ENTWURF_PREFIX_ROOTS unset → no prefix auto-approve (preflight default trust)"
|
|
310
|
+
else
|
|
311
|
+
ok "ENTWURF_PREFIX_ROOTS set → operator prefix-approve roots: $ENTWURF_PREFIX_ROOTS"
|
|
312
|
+
fi
|
|
313
|
+
|
|
314
|
+
echo
|
|
315
|
+
if [ "$fail" -eq 0 ]; then echo "meta-bridge doctor: PASS"; else echo "meta-bridge doctor: FAIL (see above)"; exit 1; fi
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Helper for doctor/smoke: decide whether an append-only meta-bridge hook log has
|
|
3
|
+
# an unrecovered ERROR. Recovery is intentionally narrow: only `INFO armed watch`
|
|
4
|
+
# proves the Claude SessionStart/CwdChanged hook re-armed the mailbox watch. A
|
|
5
|
+
# later `INFO attach record (event=UserPromptSubmit, ...)` is merely degraded
|
|
6
|
+
# backfill and must NOT clear an arm/upsert failure.
|
|
7
|
+
|
|
8
|
+
meta_bridge_hook_log_status() {
|
|
9
|
+
local log="$1"
|
|
10
|
+
if ! grep -q ' ERROR ' "$log" 2>/dev/null; then
|
|
11
|
+
echo "no-error"
|
|
12
|
+
return 0
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
local last_err last_recovery
|
|
16
|
+
last_err="$(grep -n ' ERROR ' "$log" | tail -1 | cut -d: -f1)"
|
|
17
|
+
last_recovery="$(grep -n ' INFO armed watch ' "$log" | tail -1 | cut -d: -f1)"
|
|
18
|
+
|
|
19
|
+
if [ -z "$last_recovery" ] || [ "$last_err" -gt "$last_recovery" ]; then
|
|
20
|
+
grep ' ERROR ' "$log" | tail -1
|
|
21
|
+
return 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
echo "recovered-after-armed-watch"
|
|
25
|
+
return 0
|
|
26
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# meta-bridge-install.sh — operator-grade global installer for the garden-native
|
|
3
|
+
# meta-bridge plugin (1.0.0 step 5). Makes the exact Claude Code wiring the repo
|
|
4
|
+
# needs, so a user never hand-edits hook/plugin settings or passes --plugin-dir.
|
|
5
|
+
#
|
|
6
|
+
# Mechanism (all proven on 2026-06-05):
|
|
7
|
+
# 1. ASSEMBLE a self-contained plugin under pi/meta-bridge/.assembled/ (gitignored):
|
|
8
|
+
# copy the committed skeleton, copy the entry shell + its lib (so
|
|
9
|
+
# ${CLAUDE_PLUGIN_ROOT} self-locates them), and BAKE the node abspath into
|
|
10
|
+
# hooks.json. The node path is the ONLY templated surface — the mailbox /
|
|
11
|
+
# meta-record dirs resolve at runtime inside entry.ts (<pi-agent-dir>, a
|
|
12
|
+
# fixed ~/ path). The plugin is wake/record hooks ONLY; the receiver-side
|
|
13
|
+
# entwurf_inbox_read tool comes from USER-scope entwurf-bridge MCP wiring
|
|
14
|
+
# (`claude mcp add -s user ...`). Project-scoped .mcp.json is deliberately
|
|
15
|
+
# not enough: a /tmp native session would wake without a receipt tool.
|
|
16
|
+
# 2. marketplace add <repo-stable .assembled> (NOT /tmp — ephemeral source
|
|
17
|
+
# would break `claude plugin marketplace update`).
|
|
18
|
+
# 3. install entwurf-meta-receive@meta-bridge-local --scope user (= global:
|
|
19
|
+
# every native session auto-loads it; no manual --plugin-dir).
|
|
20
|
+
# 4. install/update USER-scope entwurf-bridge MCP, so every native session has
|
|
21
|
+
# entwurf_inbox_read without duplicating MCP inside the plugin.
|
|
22
|
+
# Idempotent: re-running removes the prior marketplace/plugin first, so a
|
|
23
|
+
# `nix rebuild` that moved node just re-bakes and re-installs cleanly.
|
|
24
|
+
#
|
|
25
|
+
# Platform: Linux + macOS only. Windows fails fast (no "untested but maybe works").
|
|
26
|
+
set -euo pipefail
|
|
27
|
+
|
|
28
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
29
|
+
REPO="$(cd "$HERE/.." && pwd)"
|
|
30
|
+
MKT_NAME="meta-bridge-local"
|
|
31
|
+
PLUGIN="entwurf-meta-receive"
|
|
32
|
+
SRC="$REPO/pi/meta-bridge"
|
|
33
|
+
ASM="$SRC/.assembled"
|
|
34
|
+
|
|
35
|
+
die() { echo "meta-bridge-install: $*" >&2; exit 1; }
|
|
36
|
+
|
|
37
|
+
# --- platform gate (Linux/macOS only) ---------------------------------------
|
|
38
|
+
case "$(uname -s)" in
|
|
39
|
+
Linux | Darwin) ;;
|
|
40
|
+
*) die "unsupported platform '$(uname -s)'. meta-bridge supports Linux + macOS only (Windows is fail-fast, not silently attempted)." ;;
|
|
41
|
+
esac
|
|
42
|
+
|
|
43
|
+
# --- toolchain gate ---------------------------------------------------------
|
|
44
|
+
command -v claude >/dev/null || die "'claude' CLI not on PATH. Install Claude Code first."
|
|
45
|
+
command -v python3 >/dev/null || die "'python3' not on PATH. The FileChanged doorbell parses hook JSON with python3; install refuses a silently-dead wake runtime."
|
|
46
|
+
NODE_BIN="$(command -v node)" || die "'node' not on PATH (the hook needs it to run the entry shell)."
|
|
47
|
+
# Need TypeScript type-stripping: native default >= 23.6, --experimental-strip-types >= 22.6.
|
|
48
|
+
NODE_VER="$(node -p 'process.versions.node')"
|
|
49
|
+
NODE_MAJOR="${NODE_VER%%.*}"
|
|
50
|
+
NODE_MINOR="$(printf '%s' "$NODE_VER" | cut -d. -f2)"
|
|
51
|
+
if [ "$NODE_MAJOR" -lt 22 ] || { [ "$NODE_MAJOR" -eq 22 ] && [ "$NODE_MINOR" -lt 6 ]; }; then
|
|
52
|
+
die "node $NODE_VER too old; meta-bridge entry needs >= 22.6.0 (TypeScript strip-types)."
|
|
53
|
+
fi
|
|
54
|
+
echo "[meta-bridge-install] platform=$(uname -s) node=$NODE_VER ($NODE_BIN) python3=$(command -v python3)"
|
|
55
|
+
|
|
56
|
+
# Capture the user's pre-install values BEFORE any Claude CLI helper can mutate
|
|
57
|
+
# settings.json / ~/.claude.json. Re-runs preserve the first snapshot, so
|
|
58
|
+
# uninstall restores the true pre-entwurf state rather than the last install
|
|
59
|
+
# run's already-managed values.
|
|
60
|
+
python3 "$REPO/scripts/meta-bridge-state.py" prepare --repo "$REPO" --asm "$ASM"
|
|
61
|
+
|
|
62
|
+
# --- 1. assemble a self-contained, node-baked plugin ------------------------
|
|
63
|
+
rm -rf "$ASM"
|
|
64
|
+
mkdir -p "$ASM"
|
|
65
|
+
cp -r "$SRC/.claude-plugin" "$ASM/.claude-plugin"
|
|
66
|
+
cp -r "$SRC/$PLUGIN" "$ASM/$PLUGIN"
|
|
67
|
+
# entry shell + its lib travel WITH the plugin so the install copy is self-contained.
|
|
68
|
+
cp "$REPO/pi-extensions/meta-bridge-hook.ts" "$ASM/$PLUGIN/meta-bridge-hook.ts"
|
|
69
|
+
mkdir -p "$ASM/$PLUGIN/lib"
|
|
70
|
+
cp "$REPO/pi-extensions/lib/meta-session.ts" "$ASM/$PLUGIN/lib/meta-session.ts"
|
|
71
|
+
cp "$REPO/pi-extensions/lib/session-id.js" "$ASM/$PLUGIN/lib/session-id.js"
|
|
72
|
+
# v2 writer (3D-3+) reads the capability registry at runtime
|
|
73
|
+
# (loadMetaCapabilityRegistry). It MUST travel at the plugin ROOT — meta-session's
|
|
74
|
+
# metaCapabilitiesFilePath() resolves it via `../` from lib/ in the bundle layout
|
|
75
|
+
# (the repo `../../pi` path escapes the plugin dir under the cache version dir).
|
|
76
|
+
# Without this, a v2 writer throws on every mint/parse. doctor-meta-bridge asserts it.
|
|
77
|
+
cp "$REPO/pi/entwurf-capabilities.json" "$ASM/$PLUGIN/entwurf-capabilities.json"
|
|
78
|
+
chmod +x "$ASM/$PLUGIN/scripts/doorbell.sh"
|
|
79
|
+
# Bake the node abspath into hooks.json — the ONLY templated surface. mailbox /
|
|
80
|
+
# meta-record dirs resolve at runtime inside entry.ts (<pi-agent-dir>, fixed ~/).
|
|
81
|
+
# The plugin owns ONLY the wake/record hooks; the receiver-side entwurf_inbox_read
|
|
82
|
+
# tool is NOT the plugin's job. It comes from USER-scope entwurf-bridge MCP
|
|
83
|
+
# wiring (`claude mcp add -s user ...`), never a plugin .mcp.json duplicate.
|
|
84
|
+
HOOKS="$ASM/$PLUGIN/hooks/hooks.json"
|
|
85
|
+
HOOKS_PATH="$HOOKS" NODE_PATH_TO_BAKE="$NODE_BIN" python3 - <<'PY'
|
|
86
|
+
from pathlib import Path
|
|
87
|
+
import os
|
|
88
|
+
hooks = Path(os.environ["HOOKS_PATH"])
|
|
89
|
+
node = os.environ["NODE_PATH_TO_BAKE"]
|
|
90
|
+
text = hooks.read_text(encoding="utf-8")
|
|
91
|
+
if "__NODE_BIN__" not in text:
|
|
92
|
+
raise SystemExit(f"node-path bake failed before replacement (placeholder absent in {hooks})")
|
|
93
|
+
hooks.write_text(text.replace("__NODE_BIN__", node), encoding="utf-8")
|
|
94
|
+
PY
|
|
95
|
+
grep -q "__NODE_BIN__" "$HOOKS" && die "node-path bake failed (placeholder still present in $HOOKS)."
|
|
96
|
+
echo "[meta-bridge-install] assembled $ASM (node baked, entry+lib bundled; MCP wiring is NOT plugin-owned)"
|
|
97
|
+
|
|
98
|
+
# --- validate the manifests before touching user config ---------------------
|
|
99
|
+
claude plugin validate "$ASM" >/dev/null || die "marketplace manifest validation failed for $ASM"
|
|
100
|
+
echo "[meta-bridge-install] manifest validate: ok"
|
|
101
|
+
|
|
102
|
+
# --- 2 + 3. (re)register the marketplace and (re)install globally -----------
|
|
103
|
+
claude plugin uninstall "$PLUGIN@$MKT_NAME" >/dev/null 2>&1 || true
|
|
104
|
+
claude plugin marketplace remove "$MKT_NAME" >/dev/null 2>&1 || true
|
|
105
|
+
claude plugin marketplace add "$ASM" >/dev/null
|
|
106
|
+
claude plugin install "$PLUGIN@$MKT_NAME" --scope user >/dev/null
|
|
107
|
+
echo "[meta-bridge-install] installed $PLUGIN@$MKT_NAME (scope: user = global)"
|
|
108
|
+
|
|
109
|
+
# --- 4. ensure USER-scope receiver MCP wiring -------------------------------
|
|
110
|
+
# One canonical MCP entry only: user-scope entwurf-bridge via the repo-managed
|
|
111
|
+
# start.sh. This reaches /tmp and every other native Claude Code cwd. Do not put
|
|
112
|
+
# entwurf-bridge in the plugin (.mcp.json): that duplicates the server and drops
|
|
113
|
+
# the canonical external identity env.
|
|
114
|
+
claude mcp remove entwurf-bridge -s user >/dev/null 2>&1 || true
|
|
115
|
+
# 0.11 S2 cutover: drop any stale USER-scope pi-tools-bridge entry written by a
|
|
116
|
+
# prior version (one-shot rename cleanup, not a runtime alias).
|
|
117
|
+
claude mcp remove pi-tools-bridge -s user >/dev/null 2>&1 || true
|
|
118
|
+
claude mcp add -s user entwurf-bridge \
|
|
119
|
+
-e ENTWURF_BRIDGE_EXTERNAL_AGENT_ID=external-mcp/claude-code \
|
|
120
|
+
-- bash "$REPO/mcp/entwurf-bridge/start.sh" >/dev/null
|
|
121
|
+
(cd /tmp && claude mcp get entwurf-bridge 2>/dev/null | grep -q "Scope: User config") || \
|
|
122
|
+
die "post-install: entwurf-bridge is not reachable as USER-scope MCP from /tmp"
|
|
123
|
+
echo "[meta-bridge-install] installed entwurf-bridge MCP (scope: user = global receiver tools)"
|
|
124
|
+
|
|
125
|
+
# Re-assert the repo-owned keyset through our stateful manager. The Claude CLI
|
|
126
|
+
# calls above are allowed to maintain their cache/registry files, but the
|
|
127
|
+
# operator-facing JSON keys are owned here so uninstall can be honest.
|
|
128
|
+
python3 "$REPO/scripts/meta-bridge-state.py" apply --repo "$REPO" --asm "$ASM"
|
|
129
|
+
|
|
130
|
+
# --- evidence ---------------------------------------------------------------
|
|
131
|
+
echo "--- claude plugin list ---"
|
|
132
|
+
claude plugin list 2>/dev/null | grep -A3 "$PLUGIN" || die "post-install: plugin not in list (install did not take)."
|
|
133
|
+
echo
|
|
134
|
+
echo "[meta-bridge-install] DONE. Open a Claude Code session, then verify with:"
|
|
135
|
+
echo " ./run.sh doctor-meta-bridge"
|