feat(extensions): establish canonical goal source (#54, #55)

This commit is contained in:
2026-09-06 02:32:32 -05:00
parent 44f257cb06
commit d4696d09eb
43 changed files with 6845 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
# Run the canonical goal extension through its isolated project-local installation.
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/.."
unset MOSAIC_LAUNCH_INCARNATION
scripts/sync-dev-extensions.sh
exec pi --no-extensions --extension "$PWD/.pi/extensions/goal/index.ts" \
--session-dir "$PWD/.pi/state/sessions" "$@"
+142
View File
@@ -0,0 +1,142 @@
#!/usr/bin/env bash
# Install canonical extension source into the project-local native Pi directory.
set -euo pipefail
cd "$(dirname "$0")/.."
MODE="sync"
case "${1:-}" in
"") ;;
--check) MODE="check" ;;
*) echo "usage: scripts/sync-dev-extensions.sh [--check]" >&2; exit 4 ;;
esac
SOURCE_ROOT="$PWD/extensions"
PI_ROOT="$PWD/.pi"
DEST="$PI_ROOT/extensions"
RECORD="$PI_ROOT/extensions.installed.sha256"
LOCK="$PI_ROOT/.extensions-sync.lock"
TRANSACTION="$PI_ROOT/.extensions-transaction"
BACKUP="$PI_ROOT/.extensions-backup"
MANAGED=(goal mosaic-core)
mkdir -p "$PI_ROOT"
command -v flock >/dev/null || { echo "sync-dev-extensions: flock is required" >&2; exit 2; }
exec 9>"$LOCK"
if ! flock -n 9; then
echo "sync-dev-extensions: another sync is active" >&2
exit 3
fi
manifest_tree() {
local root="$1" output="$2"
if find "$root" -type l -print -quit | grep -q .; then
echo "sync-dev-extensions: symlink found in $root" >&2
return 2
fi
(
cd "$root"
while IFS= read -r -d '' path; do
path="${path#./}"
if [ -d "$path" ]; then
printf 'd %q\n' "$path"
elif [ -f "$path" ]; then
printf 'f %s %q\n' "$(sha256sum "$path" | cut -d ' ' -f 1)" "$path"
else
echo "sync-dev-extensions: unsupported entry $path" >&2
exit 2
fi
done < <(find . -mindepth 1 -print0 | LC_ALL=C sort -z)
) > "$output"
}
# Recover a process killed after transaction intent was recorded. A matching
# destination and installed manifest commits; every other state rolls back.
recover_transaction() {
[ -f "$TRANSACTION" ] || return 0
local current="$PI_ROOT/.recovery-manifest.$$"
local destination_matches=false
if [ -d "$DEST" ] && [ -f "$RECORD" ] && manifest_tree "$DEST" "$current" && cmp -s "$RECORD" "$current"; then
destination_matches=true
fi
rm -f "$current"
if [ -d "$BACKUP" ]; then
if [ "$destination_matches" = "true" ]; then
rm -rf "$BACKUP"
else
rm -rf "$DEST"
mv "$BACKUP" "$DEST"
fi
elif [ "$destination_matches" = "false" ]; then
rm -rf "$DEST"
fi
rm -f "$TRANSACTION"
}
recover_transaction
find "$PI_ROOT" -mindepth 1 -maxdepth 1 -type d -name '.extensions-sync.*' -exec rm -rf {} +
WORK="$(mktemp -d "$PI_ROOT/.extensions-sync.XXXXXX")"
COMMITTED=false
cleanup() {
local rc=$?
if [ "$COMMITTED" = "false" ] && [ -f "$TRANSACTION" ]; then recover_transaction || true; fi
rm -rf "$WORK"
exit "$rc"
}
trap cleanup EXIT
for name in "${MANAGED[@]}"; do
[ -d "$SOURCE_ROOT/$name" ] || { echo "sync-dev-extensions: missing source $SOURCE_ROOT/$name" >&2; exit 2; }
done
if find "$SOURCE_ROOT" -type l -print -quit | grep -q .; then
echo "sync-dev-extensions: source symlinks are forbidden" >&2
exit 2
fi
mapfile -t ENTRYPOINTS < <(find "$SOURCE_ROOT" -name index.ts -type f -printf '%P\n' | LC_ALL=C sort)
if [ "${#ENTRYPOINTS[@]}" -ne 1 ] || [ "${ENTRYPOINTS[0]}" != "goal/index.ts" ]; then
echo "sync-dev-extensions: expected exactly one entrypoint, goal/index.ts" >&2
exit 2
fi
EXPECTED="$WORK/expected"
mkdir "$EXPECTED"
for name in "${MANAGED[@]}"; do cp -a "$SOURCE_ROOT/$name" "$EXPECTED/$name"; done
EXPECTED_MANIFEST="$WORK/expected.sha256"
manifest_tree "$EXPECTED" "$EXPECTED_MANIFEST"
if [ "$MODE" = "check" ]; then
[ -d "$DEST" ] || { echo "sync-dev-extensions: development installation is missing" >&2; exit 1; }
CURRENT="$WORK/current.sha256"
manifest_tree "$DEST" "$CURRENT" || exit 1
if ! cmp -s "$EXPECTED_MANIFEST" "$CURRENT"; then
echo "sync-dev-extensions: development installation differs from canonical source" >&2
exit 1
fi
echo "sync-dev-extensions: canonical source matches .pi installation"
COMMITTED=true
exit 0
fi
if [ -d "$DEST" ]; then
CURRENT="$WORK/current.sha256"
manifest_tree "$DEST" "$CURRENT" || exit 1
if [ -f "$RECORD" ]; then
cmp -s "$RECORD" "$CURRENT" || { echo "sync-dev-extensions: refusing to overwrite local changes under .pi/extensions" >&2; exit 1; }
else
cmp -s "$EXPECTED_MANIFEST" "$CURRENT" || { echo "sync-dev-extensions: unrecorded .pi/extensions differs from canonical source" >&2; exit 1; }
fi
fi
[ ! -e "$BACKUP" ] || { echo "sync-dev-extensions: unreconciled backup remains" >&2; exit 2; }
printf 'prepared\n' > "$TRANSACTION"
if [ -e "$DEST" ]; then mv "$DEST" "$BACKUP"; fi
mv "$EXPECTED" "$DEST"
if [ "${MOSAIC_SYNC_TEST_FAULT:-}" = "after-destination" ]; then
kill -KILL "$$"
fi
RECORD_TMP="$WORK/installed.sha256"
cp "$EXPECTED_MANIFEST" "$RECORD_TMP"
mv "$RECORD_TMP" "$RECORD"
COMMITTED=true
rm -rf "$BACKUP"
rm -f "$TRANSACTION"
echo "sync-dev-extensions: installed canonical goal extension into .pi/extensions"
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env bash
# Verify canonical extension source and fail-closed native development packaging.
set -uo pipefail
cd "$(dirname "$0")/.."
SANDBOX="$(mktemp -d)"
trap 'rm -rf "$SANDBOX"' EXIT
mkdir -p "$SANDBOX/repo/scripts" "$SANDBOX/repo/extensions"
cp scripts/sync-dev-extensions.sh "$SANDBOX/repo/scripts/"
cp -a extensions/goal extensions/mosaic-core "$SANDBOX/repo/extensions/"
PASS=0
FAIL=0
check_rc() {
local name="$1" expected="$2"
shift 2
"$@" >/dev/null 2>&1
local rc=$?
if [ "$rc" -eq "$expected" ]; then PASS=$((PASS+1)); printf 'OK %s\n' "$name"
else FAIL=$((FAIL+1)); printf 'FAIL %s (exit %s, expected %s)\n' "$name" "$rc" "$expected"
fi
}
SYNC="$SANDBOX/repo/scripts/sync-dev-extensions.sh"
check_rc "initial ordinary-file install" 0 bash "$SYNC"
check_rc "installed tree matches canonical source" 0 bash "$SYNC" --check
if [ -z "$(find "$SANDBOX/repo/.pi/extensions" -type l -print -quit)" ]; then
PASS=$((PASS+1)); echo "OK installed tree has no symlinks"
else FAIL=$((FAIL+1)); echo "FAIL installed tree contains symlinks"
fi
printf '\nlocal edit\n' >> "$SANDBOX/repo/.pi/extensions/goal/README.md"
check_rc "check detects installation drift" 1 bash "$SYNC" --check
check_rc "sync refuses to overwrite installation drift" 1 bash "$SYNC"
cp "$SANDBOX/repo/extensions/goal/README.md" "$SANDBOX/repo/.pi/extensions/goal/README.md"
printf '\nextra\n' > "$SANDBOX/repo/.pi/extensions/extra-file"
check_rc "check detects an extra destination file" 1 bash "$SYNC" --check
rm "$SANDBOX/repo/.pi/extensions/extra-file"
mkdir "$SANDBOX/repo/.pi/extensions/extra-directory"
check_rc "check detects an extra destination directory" 1 bash "$SYNC" --check
rmdir "$SANDBOX/repo/.pi/extensions/extra-directory"
ln -s goal "$SANDBOX/repo/.pi/extensions/extra-link"
check_rc "check rejects a destination symlink" 1 bash "$SYNC" --check
rm "$SANDBOX/repo/.pi/extensions/extra-link"
printf '\ncanonical edit\n' >> "$SANDBOX/repo/extensions/goal/README.md"
check_rc "sync accepts a canonical source update" 0 bash "$SYNC"
check_rc "updated installation matches canonical source" 0 bash "$SYNC" --check
printf '\ninterrupted edit\n' >> "$SANDBOX/repo/extensions/goal/README.md"
check_rc "forced interruption kills the replacing process" 137 env MOSAIC_SYNC_TEST_FAULT=after-destination bash "$SYNC"
check_rc "next invocation recovers old consistent installation" 1 bash "$SYNC" --check
if ! grep -q 'interrupted edit' "$SANDBOX/repo/.pi/extensions/goal/README.md"; then
PASS=$((PASS+1)); echo "OK interrupted replacement rolled back"
else FAIL=$((FAIL+1)); echo "FAIL interrupted replacement remained active"
fi
check_rc "sync succeeds after interruption recovery" 0 bash "$SYNC"
touch "$SANDBOX/repo/.pi/.extensions-sync.lock"
check_rc "unlocked stale lock file does not block" 0 bash "$SYNC" --check
exec 8>"$SANDBOX/repo/.pi/.extensions-sync.lock"
flock -n 8
check_rc "active lock refuses a concurrent sync" 3 bash "$SYNC" --check
flock -u 8
ln -s README.md "$SANDBOX/repo/extensions/goal/forbidden-link"
check_rc "source symlink fails closed" 2 bash "$SYNC"
rm "$SANDBOX/repo/extensions/goal/forbidden-link"
mkdir "$SANDBOX/repo/extensions/goal/nested"
cp "$SANDBOX/repo/extensions/goal/index.ts" "$SANDBOX/repo/extensions/goal/nested/index.ts"
check_rc "nested second entrypoint fails closed" 2 bash "$SYNC"
printf '\nextension package selftest: %s passed, %s failed\n' "$PASS" "$FAIL"
[ "$FAIL" -eq 0 ]
+106
View File
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""No-model native Pi smoke: project discovery, footer, /goal and Alt+G."""
import fcntl
import json
import os
from pathlib import Path
import pty
import re
import select
import signal
import struct
import subprocess
import tempfile
import termios
import time
import uuid
ROOT = Path(__file__).resolve().parent.parent
ANSI = re.compile(r"\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1b\\))")
TEXT = "native recall " + "full acceptance criteria " * 16 + "END-OF-GOAL"
# A fresh clone has no ignored development installation. Exercise the same
# fail-closed sync used by the launcher before testing Pi discovery.
subprocess.run([str(ROOT / "scripts/sync-dev-extensions.sh")], cwd=ROOT, check=True)
def receive(fd, pattern, timeout=12):
data = b""
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
ready, _, _ = select.select([fd], [], [], max(0, deadline - time.monotonic()))
if not ready:
break
try:
data += os.read(fd, 65536)
except OSError:
break
clean = ANSI.sub("", data.decode(errors="replace"))
if pattern in clean:
return data
raise AssertionError(f"missing {pattern!r}: {data[-4000:]!r}")
with tempfile.TemporaryDirectory(prefix="ng-native-pi-") as home:
env = {**os.environ, "PI_CODING_AGENT_DIR": home, "PI_OFFLINE": "1", "TERM": "xterm-256color"}
env.pop("MOSAIC_LAUNCH_INCARNATION", None)
rpc = subprocess.Popen(["pi", "--approve", "--offline", "--mode", "rpc", "--no-session", "--no-tools"], cwd=ROOT, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
try:
rpc.stdin.write('{"id":"commands","type":"get_commands"}\n')
rpc.stdin.flush()
deadline = time.monotonic() + 15
while True:
assert time.monotonic() < deadline, "RPC discovery timed out"
response = json.loads(rpc.stdout.readline())
if response.get("id") == "commands":
break
commands = [c for c in response["data"]["commands"] if c["name"] == "goal"]
assert len(commands) == 1, commands
assert str(ROOT / ".pi/extensions/goal/index.ts") in json.dumps(commands[0]), commands
print("PASS native project discovery: exactly one local /goal", flush=True)
finally:
rpc.terminate()
rpc.communicate(timeout=10)
for status, label, width, no_color in [("paused", "Paused", 120, False), ("blocked", "Blocked", 45, False), ("none", "Complete", 120, True), ("active", "Waiting", 45, False)]:
incarnation = "ng-native-test-" + uuid.uuid4().hex
statefile = ROOT / ".pi/state/goal" / f"goal-state.{incarnation}.json"
statefile.parent.mkdir(parents=True, exist_ok=True)
state = dict(version=1, text=TEXT, status=status, checks=0, maxChecks=25, noProgressReports=0, maxNoProgressReports=3, workEventSinceReport=False, setAt="2026-09-06T00:00:00.000Z")
if status == "none":
state.update(text="", lastOutcome=dict(text=TEXT, status="complete", evidence="native fixture", at="2026-09-06T00:00:00.000Z"))
if status == "active":
state.update(waitTimeoutSeconds=3600, waitWakeUsed=False, activeWait=dict(owner="native fixture", nextCheck="manual acceptance", deadlineAt=int(time.time() * 1000) + 3600000, wakeSent=False))
statefile.write_text(json.dumps(state))
tty_env = {**env, "MOSAIC_LAUNCH_INCARNATION": incarnation}
tty_env.pop("NO_COLOR", None)
if no_color:
tty_env["NO_COLOR"] = "1"
master, slave = pty.openpty()
fcntl.ioctl(slave, termios.TIOCSWINSZ, struct.pack("HHHH", 40, width, 0, 0))
child = subprocess.Popen(["pi", "--approve", "--offline", "--no-session", "--no-tools", "--no-skills", "--no-context-files"], cwd=ROOT, env=tty_env, stdin=slave, stdout=slave, stderr=slave, start_new_session=True)
os.close(slave)
try:
startup = receive(master, f"Goal: {label}")
Path("/tmp/ng-goal-native-startup.log").write_bytes(startup)
os.write(master, b"\x1b[1;1R\x1b[?1;2c\x1b[?0u")
# Let terminal capability negotiation and editor initialization settle.
while select.select([master], [], [], 0.5)[0]:
os.read(master, 65536)
os.write(master, b"\x1b[103;3u")
receive(master, "END-OF-GOAL")
os.write(master, b"\x1b[200~/goal --max 0\x1b[201~")
receive(master, "/goal --max 0")
os.write(master, b"\x1b[13u")
receive(master, 'integer, got "0"')
os.write(master, b"\x1b[200~/goal\x1b[201~")
entered = receive(master, "/goal")
Path("/tmp/ng-goal-native-entered.log").write_bytes(entered)
os.write(master, b"\x1b[13u")
receive(master, "END-OF-GOAL")
print(f"PASS native {label}, width={width}, NO_COLOR={no_color}: footer + /goal + Alt+G", flush=True)
finally:
os.killpg(child.pid, signal.SIGTERM)
child.wait(timeout=10)
os.close(master)
statefile.unlink(missing_ok=True)