Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
0af8691
feat(p2p-v3): branch scaffold + file stubs (fn-7-7cw.1)
patrickbrosnan11-spec Apr 7, 2026
4958406
fix(p2p-v3): align scaffold with spec acceptance (fn-7-7cw.1)
patrickbrosnan11-spec Apr 7, 2026
a30f9f5
docs(p2p-v3): record locked design decisions (fn-7-7cw.2)
patrickbrosnan11-spec Apr 7, 2026
525ab59
feat(p2p-v3): port crypto/tar/sig foundations + walnut_paths module
patrickbrosnan11-spec Apr 7, 2026
861be62
feat(p2p-v3): v3 flat staging layer + stub semantics (fn-7-7cw.4)
patrickbrosnan11-spec Apr 7, 2026
a9ae9ca
feat(p2p-v3): manifest schema + canonical JSON + stdlib YAML (fn-7-7c…
patrickbrosnan11-spec Apr 7, 2026
65820f2
docs(p2p-v3): note v2 _update_manifest_encrypted gap in generate_mani…
patrickbrosnan11-spec Apr 7, 2026
c86b0e1
docs(p2p-v3): record review hold-position rationale for fn-7-7cw.5
patrickbrosnan11-spec Apr 7, 2026
ad4b089
feat(p2p-v3): v2 to v3 layout migration helper (fn-7-7cw.6)
patrickbrosnan11-spec Apr 7, 2026
90c4b16
refactor(p2p-v3): clarify migrate_v2_layout idempotency check (fn-7-7…
patrickbrosnan11-spec Apr 7, 2026
649e00b
feat(p2p-v3): share skill + create CLI + glob matcher (fn-7-7cw.7)
patrickbrosnan11-spec Apr 7, 2026
25ec6ba
feat(p2p-v3): receive pipeline + receive skill (fn-7-7cw.8)
patrickbrosnan11-spec Apr 7, 2026
471e67a
feat(p2p-v3): v2 to v3 migration path in receive (fn-7-7cw.9)
patrickbrosnan11-spec Apr 7, 2026
60c9f82
feat(p2p-v3): fresh relay skill + hook + probe + gh_client (fn-7-7cw.10)
patrickbrosnan11-spec Apr 7, 2026
5fede24
feat(p2p-v3): test harness + RSA hybrid + round-trip tests (fn-7-7cw.11)
patrickbrosnan11-spec Apr 7, 2026
9268daa
test(p2p-v3): v2 to v3 migration matrix + tar safety suite (fn-7-7cw.12)
patrickbrosnan11-spec Apr 7, 2026
5e2e9f6
chore(p2p-v3): version bump 3.1.0 + CLAUDE.md audit + preferences tem…
patrickbrosnan11-spec Apr 7, 2026
712e3bf
feat(p2p-v3): inject auto-generated README.md at staging time (fn-7-7…
patrickbrosnan11-spec Apr 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/alive/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "alive",
"version": "3.0.0",
"version": "3.1.0",
"description": "Personal Context Manager for Claude Code. Your life in walnuts.",
"author": {
"name": "Lock-in Lab",
Expand Down
17 changes: 10 additions & 7 deletions plugins/alive/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
version: 3.0.0
version: 3.1.0
runtime: squirrel.core@3.0
---

Expand All @@ -21,12 +21,12 @@ Install: `claude plugin install alive@alivecontext`

When a walnut is active, read these in order before responding:
1. `_kernel/key.md` — full
2. `_kernel/now.json` — full
2. `_kernel/now.json` — full (computed projection via `scripts/project.py`)
3. `_kernel/insights.md` — frontmatter
4. `_kernel/log.md` — frontmatter, then first ~100 lines
5. `.alive/_squirrels/`scan for unsaved entries
6. `bundles/`context.manifest.yaml frontmatter only
7. `bundles/*/tasks.md` — current task queues per bundle
5. `_kernel/tasks.json`current task queue (v3 uses JSON, not markdown)
6. `.alive/_squirrels/`scan for unsaved entries
7. Top-level bundle dirs — `{walnut}/{bundle}/context.manifest.yaml` frontmatter only (v3 flat layout; bundles live at walnut root, not under `bundles/`)
8. `.alive/preferences.yaml` — full (if exists)

Do not respond about a walnut without reading its kernel files. Never guess at file contents.
Expand All @@ -46,7 +46,7 @@ Do not respond about a walnut without reading its kernel files. Never guess at f

---

## Fifteen Skills
## Eighteen Skills

```
/alive:world see your world
Expand All @@ -60,10 +60,13 @@ Do not respond about a walnut without reading its kernel files. Never guess at f
/alive:settings customize preferences, voice, rhythm
/alive:session-history squirrel activity, session timeline
/alive:mine-for-context deep context extraction
/alive:build-extensions create skills, rules, hooks for your world
/alive:build-extensions create skills, rules, hooks for your world
/alive:my-context-graph render the world graph
/alive:session-context-rebuild rebuild context from past sessions
/alive:system-upgrade migrate from legacy alive to current
/alive:share package a walnut or bundle for sharing (P2P)
/alive:receive import a .walnut package from inbox or relay
/alive:relay set up GitHub relay + manage peers
```

---
Expand Down
12 changes: 11 additions & 1 deletion plugins/alive/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"description": "ALIVE Context System v3 — 13 hooks. Session hooks read/write .alive/_squirrels/. All read stdin JSON for session_id.",
"description": "ALIVE Context System hooks. Session hooks read/write .alive/_squirrels/. All read stdin JSON for session_id.",
"hooks": {
"SessionStart": [
{
Expand All @@ -14,6 +14,11 @@
"type": "command",
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/alive-repo-detect.sh",
"timeout": 10
},
{
"type": "command",
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/alive-relay-check.sh",
"timeout": 10
}
]
},
Expand All @@ -24,6 +29,11 @@
"type": "command",
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/alive-session-resume.sh",
"timeout": 10
},
{
"type": "command",
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/alive-relay-check.sh",
"timeout": 10
}
]
},
Expand Down
160 changes: 160 additions & 0 deletions plugins/alive/hooks/scripts/alive-relay-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
#!/usr/bin/env bash
# alive-relay-check.sh -- SessionStart relay state probe (LD16, fn-7-7cw).
#
# Runs once at session-start (matchers: startup, resume) to refresh
# ~/.alive/relay/state.json so the user sees up-to-date pending package
# counts. Rate-limited to once every 10 minutes via the top-level
# `last_probe` field in state.json (LD17 -- the field replaces the prior
# `last_sync` name).
#
# Exit code policy (LD16, exact):
# 0 -- success: probe ran OR within cooldown OR relay not configured.
# Per-peer failures are recorded INSIDE state.json as data; the
# hook still exits 0 because peer-level failures are routine
# (offline, quota, rate-limited).
# 1 -- hard local failure: cannot read relay.json, cannot write
# state.json, gh CLI missing. Rare; the session continues either
# way because Claude Code only treats exit 2 as a chain block.
# NEVER 2 -- exit 2 would block the SessionStart hook chain. This is
# a notification hook, not a guard.
#
# Sources alive-common.sh for read_hook_input/find_world; the hook reads
# stdin (Claude Code passes the session JSON) but does not require any
# field beyond the implicit cwd.

set -u

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=alive-common.sh
source "$SCRIPT_DIR/alive-common.sh"

# Drain stdin so Claude Code's hook chain does not block on a closed pipe.
# read_hook_input reads stdin once; subsequent helpers read from $HOOK_INPUT.
read_hook_input

# Resolve world root for the optional discovery-hint codepath. Probe itself
# does not depend on a world being loaded -- relay config is per-user, not
# per-world -- so a missing world root is fine.
find_world || true

RELAY_DIR="${HOME}/.alive/relay"
RELAY_JSON="${RELAY_DIR}/relay.json"
STATE_JSON="${RELAY_DIR}/state.json"
COOLDOWN_SECONDS=600 # 10 minutes per LD16

# ---------------------------------------------------------------------------
# Optional: discovery hint when relay not configured.
# ---------------------------------------------------------------------------
maybe_discovery_hint() {
# Only print the hint if the user opted in via preferences.yaml top-level
# `discovery_hints: true`. Hint goes to stderr (informational, never
# blocks). Hook still exits 0 from the main flow.
if [ -z "${WORLD_ROOT:-}" ]; then
return 0
fi
local prefs="${WORLD_ROOT}/.alive/preferences.yaml"
if [ ! -f "$prefs" ]; then
return 0
fi
# Cheap grep -- we are not parsing YAML here, just spotting the opt-in.
# Lines starting with '#' are skipped (commented defaults).
if grep -E '^[[:space:]]*discovery_hints:[[:space:]]*true' "$prefs" >/dev/null 2>&1; then
printf '# alive: P2P relay available -- run /alive:relay setup\n' >&2
fi
}

if [ ! -f "$RELAY_JSON" ]; then
# Not configured. LD16: exit 0. Optionally hint the feature exists.
maybe_discovery_hint
exit 0
fi

# ---------------------------------------------------------------------------
# Cooldown check: read state.json `last_probe` and skip if within window.
# ---------------------------------------------------------------------------
if [ -f "$STATE_JSON" ]; then
LAST_PROBE_RAW=""
if [ "$ALIVE_JSON_RT" = "python3" ]; then
LAST_PROBE_RAW=$(python3 - <<'PY' 2>/dev/null
import json, sys, os
path = os.environ.get("ALIVE_RELAY_STATE_JSON")
try:
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
print(data.get("last_probe") or "")
except Exception:
print("")
PY
)
elif [ "$ALIVE_JSON_RT" = "node" ]; then
LAST_PROBE_RAW=$(ALIVE_RELAY_STATE_JSON="$STATE_JSON" node -e "
try {
const d = JSON.parse(require('fs').readFileSync(process.env.ALIVE_RELAY_STATE_JSON, 'utf8'));
process.stdout.write(d.last_probe || '');
} catch (e) {
process.stdout.write('');
}
" 2>/dev/null)
fi
# Compute age in seconds. We accept ISO-8601 with trailing Z.
if [ -n "$LAST_PROBE_RAW" ] && [ "$ALIVE_JSON_RT" = "python3" ]; then
AGE_SECONDS=$(ALIVE_RELAY_LAST_PROBE="$LAST_PROBE_RAW" python3 - <<'PY' 2>/dev/null
import os, datetime
raw = os.environ.get("ALIVE_RELAY_LAST_PROBE", "")
if not raw:
print(-1)
raise SystemExit(0)
try:
if raw.endswith("Z"):
raw = raw[:-1] + "+00:00"
t = datetime.datetime.fromisoformat(raw)
if t.tzinfo is None:
t = t.replace(tzinfo=datetime.timezone.utc)
now = datetime.datetime.now(datetime.timezone.utc)
print(int((now - t).total_seconds()))
except Exception:
print(-1)
PY
)
if [ -n "$AGE_SECONDS" ] && [ "$AGE_SECONDS" -ge 0 ] 2>/dev/null; then
if [ "$AGE_SECONDS" -lt "$COOLDOWN_SECONDS" ]; then
# Within cooldown -- skip silently. LD16 exit 0.
exit 0
fi
fi
fi
fi

export ALIVE_RELAY_STATE_JSON="$STATE_JSON"

# ---------------------------------------------------------------------------
# Run the probe in the background. We do not block session start on the
# network round-trip -- the next session reads whatever the probe wrote.
# ---------------------------------------------------------------------------
PROBE_SCRIPT="${SCRIPT_DIR}/../../scripts/relay-probe.py"
if [ ! -f "$PROBE_SCRIPT" ]; then
# Plugin layout drift -- not a user-fixable issue. Surface as exit 1.
printf 'alive-relay-check: probe script missing at %s\n' "$PROBE_SCRIPT" >&2
exit 1
fi
if ! command -v python3 >/dev/null 2>&1; then
printf 'alive-relay-check: python3 not on PATH\n' >&2
exit 1
fi
if ! command -v gh >/dev/null 2>&1; then
# gh missing is a soft failure -- the user might be on a machine without
# gh installed. Record nothing, exit 0 so the session-start chain runs.
exit 0
fi

# Background fire. Discard stdout (we have nothing to say); keep stderr so
# diagnostic messages from relay-probe.py reach the session log if the user
# is running with hook output enabled.
(
python3 "$PROBE_SCRIPT" probe --all-peers --output "$STATE_JSON" >/dev/null 2>&1 || true
) &

# Detach from the background job so the hook returns immediately.
disown 2>/dev/null || true

exit 0
Loading