Skip to content

feat: .walnut package format + alive:share + alive:receive skills#19

Closed
patrickSupernormal wants to merge 22 commits intostackwalnuts:mainfrom
patrickSupernormal:fn-1-89m
Closed

feat: .walnut package format + alive:share + alive:receive skills#19
patrickSupernormal wants to merge 22 commits intostackwalnuts:mainfrom
patrickSupernormal:fn-1-89m

Conversation

@patrickSupernormal
Copy link

@patrickSupernormal patrickSupernormal commented Mar 26, 2026

Summary

  • Defines the .walnut package format — a portable, optionally encrypted archive for sharing walnut context between worlds
  • Adds alive:share skill — exports walnuts/capsules as .walnut packages with scope selection, sensitivity checks, and optional age encryption
  • Adds alive:receive skill — imports .walnut packages with path traversal protection, checksum validation, and per-file keep/drop routing
  • Wires both skills into CLAUDE.md (14 skills), inbox-check hook, and capture skill delegation

Files changed

File What
templates/walnut-package/format-spec.md Package format specification
templates/walnut-package/manifest.yaml Manifest template with SHA-256 checksums
skills/share/SKILL.md Export skill (8-step flow)
skills/receive/SKILL.md Import skill with security hardening
CLAUDE.md Skill table updated
hooks/scripts/alive-inbox-check.sh .walnut file detection
skills/capture/SKILL.md Delegation to alive:receive

Design

Full design research at _core/_capsules/p2p-design/v0.1.md. This PR implements Phase 1 (package format) from the three-phase P2P architecture.

Three scope levels: full (entire walnut), capsule (one or more capsules), snapshot (read-only status briefing). Optional passphrase encryption via age. BagIt-inspired manifest with SHA-256 integrity checks.

Test plan

  • Share a capsule from one walnut, email the .walnut file, import on another machine
  • Test all three scopes (full, capsule, snapshot)
  • Test encrypted + unencrypted packages
  • Verify inbox-check hook detects .walnut files in 03_Inputs/
  • Verify path traversal protection rejects malicious archives

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

lockinlab and others added 22 commits March 17, 2026 23:47
Structured, persistent context management. 12 skills, 6 rules,
12 hooks, capsule architecture, squirrel runtime. Plain files
on your machine. Every session compounds.

github.com/alivecomputer
The squirrel wakes up aware. World key injected at first breath.
Capsule awareness detects deliverables mid-session. Repo reverse-lookup
maps any cwd to its walnut. The statusline shows where you are.

Session runtime:
- World key (.alive/key.md) injected via additionalContext on startup
- Capsule awareness prompt — auto-detects work with a future audience
- Tidy nudge when the world hasn't been cleaned in 7+ days
- Repo detection hook — dev.local_path → walnut context injection
- Squirrel instinct 8: verify past context via subagent, never guess
- Squirrel instinct 9: notice world key drift, flag stale connections

Index-driven architecture:
- Replaced manual ## Walnuts table with generated _index.yaml
- Tidy checks index freshness, not table drift
- World skill checks index staleness, offers regeneration
- Create skill no longer appends to a maintained table

Capsule system:
- New alive:capsule skill (create, share, graduate, status)
- Capsule-aware load menu (continue capsule / start new / load context)
- Shared capsule tracking in companion frontmatter
- Ungraduated capsule detection in tidy (v1 files still in _core/_capsules/)
- Version naming: {name}-draft-{nn}.md (self-documenting in walnut root)

Developer workflow:
- key-codebase.md template — dev: block with repo, local_path, deploy, database
- Create skill Step 2b — explicit codebase detection and field collection
- Multi-account native — different git/deploy/db identities per walnut
- Frontmatter-only parsing in repo-detect hook (safe, no body false positives)

Squirrel consolidation:
- Per-walnut _core/_squirrels/ killed — world-level .alive/_squirrels/ only
- saves: counter replaces signed: boolean
- Squirrel YAML template enriched (transcript, cwd, rules_loaded, tags)

Statusline:
- Active walnut name displayed when loaded
- Token cost dim + strikethrough (most users on OAuth — iykyk)

Cherry-picked from phase-0-1-foundation-and-rules branch.
Take 16, skip 5, fix 2.
Capsule companions no longer have their own task list. Tasks live
in the walnut's _core/tasks.md under a capsule heading. Companion
## Tasks section replaced with a pointer. Prevents split source
of truth where tasks exist in both places.

Re-added deploy.sh (lost in force-push).

Updated: templates/capsule/companion.md, rules/capsules.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The world index was generated but never injected — the squirrel had
no boot-time walnut registry. Session hook now reads .alive/_index.yaml
and injects it wrapped in <WORLD_INDEX> tags alongside the world key.

This gives the squirrel immediate awareness of all walnuts, their
phases, next actions, capsule counts, people, and links — without
needing to scan the filesystem.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…sks-and-cleanup

Capsule tasks, world injection and cleanup
- Format spec covers archive structure (tar.gz), three scope levels
  (full, capsule, snapshot), naming conventions, encryption via age,
  export stripping rules, import safety checks, and versioning policy
- Manifest template defines all required/optional fields with types,
  descriptions, and BagIt-inspired SHA-256 integrity checksums

Task: fn-1-89m.1

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Clarify encrypted flag semantics (reflects package encryption state)
- Full scope: note _working/ and _references/ as "if present"
- Add capsule version immutability guarantee (byte-for-byte export)
- Strengthen sensitivity/PII language to MUST surface + SHOULD confirm
- active_sessions stripping: remove key entirely, not just empty it
- Add sort recommendation for file inventory entries
- Fix template size placeholder to use {{size_bytes}}
- Add AppleDouble exclusion to full scope exclusions

Task: fn-1-89m.1

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use explicit -f - in tar pipelines for GNU tar portability
- Template encrypted field as {{encrypted}} instead of hardcoded false
- Remove contradictory "always false inside archive" claim
- Mark .walnut.meta sidecar as OPTIONAL/MAY throughout
- Broaden encryption spec to allow recipient-based age (not just passphrase)
- Add walnut-name slug normalization note (lowercase kebab-case)

Task: fn-1-89m.1

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Make manifest.yaml valid YAML: use concrete example values for
  non-string fields (boolean, integer) instead of {{placeholders}}
- Fix "Schema version" -> "Format version" in manifest header comment
- Fail closed when age not installed and encryption requested
- Add note about template substitution convention

Task: fn-1-89m.1

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- SKILL.md with 8-step flow: scope selection, capsule picker, sensitivity
  gate, scope confirmation, encryption prompt, package creation, output,
  and metadata update
- Three scopes: full (entire _core/), capsule (selected capsules + key.md),
  snapshot (key + now + insights)
- Sensitivity/PII gating follows confirm-before-external pattern
- Optional age passphrase encryption with .walnut.meta sidecar
- SHA-256 checksums via shasum (macOS) with sha256sum fallback (Linux)
- active_sessions stripped from exported capsule companions
- Cross-capsule broken path warnings
- Large package (>25 MB) warnings
- Capsule companion shared: metadata updated after export

Task: fn-1-89m.2
- Fix double .walnut extension: use $OUTPUT_BASE pattern so tar commands
  write to $OUTPUT_BASE.walnut or $OUTPUT_BASE.walnut.age directly
- Fix meta sidecar path: always $OUTPUT_BASE.walnut.meta (sits next to
  .walnut.age, not appended to the full encrypted path)
- Add deterministic Python snippet for stripping active_sessions: from
  capsule companions in staging (no sed, no PyYAML dependency)
- Group capsule picker by status: active capsules first, done capsules
  in separate section

Task: fn-1-89m.2
- SKILL.md with full flow: encryption detection, path traversal protection,
  SHA-256 checksum validation, content preview, and scope-based routing
- Three scope handlers: full (new walnut), capsule (existing walnut), snapshot (read-only)
- Two entry points: direct /alive:receive invocation and inbox scan delegation
- Security: staging extraction, path validation, format/plugin version checks
- Handles log.md via bash to work with log guardian hook
- Cleanup archives .walnut files from 03_Inputs/ (mv, not delete)

Task: fn-1-89m.3
- Pre-extraction path validation via tar -tzf before extracting (prevents
  path traversal attacks during extraction, not just after)
- Reject ALL symlinks in packages (safe in staging != safe at destination)
- Reject special file types (device nodes, FIFOs, sockets)
- Pipeline failure handling with PIPESTATUS checks for age | tar
- Fixed mktemp template to use 8 X's (macOS BSD mktemp compatibility)
- Size validation alongside checksum validation
- Guard against empty manifest entries (regex parse failure detection)
- Note that checksums detect corruption, not authenticity
- Target path validation: all writes must resolve inside world root
- Archive move collision handling with timestamp suffix
- World root discovery and plugin version instructions in prerequisites

Task: fn-1-89m.3
- Pre-extraction type validation via tar -tvzf (rejects symlinks and
  special file types BEFORE extraction, preventing symlink-then-write attacks)
- PIPESTATUS checks on both age and tar in encrypted listing pipeline
- mktemp for members file (prevents clobber/race on fixed /tmp path)
- trap cleanup for staging and members file on early exit
- Unencrypted extraction exit status check added
- Manifest path validation: reject absolute, traversal, and out-of-staging
  paths in manifest entries before opening files
- Streaming hash in 64KB chunks (prevents unbounded memory reads)
- Per-file 500MB and total 2GB safety caps against decompression bombs
- os.path.commonpath for world-root containment (more robust than string prefix)
- Fixed misleading mv -n comment

Task: fn-1-89m.3
- Replace tar -tvzf text parsing with Python tarfile module for portable,
  reliable member validation (handles PAX headers, bsdtar/gnutar differences)
- Validate-then-extract in single tarfile open: check all members, then
  extract only safe_members (regular files + dirs, no symlinks/links/specials)
- Encrypted path: decrypt to temp file via age -d -o, then tarfile (avoids
  double passphrase prompt from piped approach)
- Capsule "Replace" now correctly removes old capsule before copy (rm -rf)
- rsync --no-perms --no-owner --no-group to avoid foreign permission bits
- Step 8 cleanup conditionally checks package is in 03_Inputs/ before archiving
  (matches prose requirement, prevents accidental Desktop file moves)
- Normalize manifest paths (strip ./) for reliable unlisted-file comparison
- Preserve full compound extensions in archive collision rename

Task: fn-1-89m.3
- Fix lstrip('./') path traversal bypass: replace with proper while-loop
  stripping that only removes leading './' sequences (not arbitrary chars)
- Add pre-extraction size caps in tarfile validation: MAX_MEMBERS (10k),
  MAX_TOTAL_BYTES (2GB), MAX_PATH_LEN (512), checked before extractall
- Case-insensitive duplicate path detection (macOS HFS+/APFS safety)
- Step 8 inbox check uses containment (case prefix) instead of exact
  directory equality (handles 03_Inputs/ subdirectories)
- Plugin version: warn and skip check if undeterminable (don't fabricate)
- All Python scripts use single-quoted -c strings (prevents shell mangling
  of regex escapes in the manifest checksum script)
- Compiled regex for manifest parsing with fail-closed on unexpected format
- Display safety: sanitize manifest strings for terminal escape injection
- Agent state note: trap doesn't persist between tool calls, store staging
  path in conversation state, explicit cleanup in every abort path
- Log.md handling clarified: bash for initial write, Edit for new unsigned
  prepend entries (log guardian permits both patterns)
- rsync replaced with -rt --no-links --no-specials --no-devices
  (defense in depth, no foreign permissions)

Task: fn-1-89m.3
- PAX/GNU metadata entries (XHDTYPE, XGLTYPE, longname, longlink)
  allowed but skipped during extraction instead of rejected
- Stream-iterate tar members instead of loading all into memory
  (abort early when caps exceeded)
- Check raw member name for '..' BEFORE normpath (which erases internal ..)
- Control character rejection in archive paths (ASCII C0, DEL, C1 range)
- Unicode-normalized + casefold duplicate detection (NFC normalization
  catches macOS APFS/HFS+ NFC vs NFD collisions)
- Manifest reading via Python with control char sanitization (no raw cat)
- All Python scripts consistently use single-quoted -c strings
- Compound extension handling for archive collision rename
  (*.walnut.age, *.walnut.meta, *.walnut)
- Manifest regex format constraints documented inline
- Step 8 inbox check uses path prefix containment (handles subdirs)

Task: fn-1-89m.3
- Remove \r from allowed display chars (terminal spoofing via carriage return)
- Consistent display sanitization note for Step 1, 4, and 5
- Shell option-injection protection: add -- to all commands with untrusted paths
  (mkdir, mv, cat, rsync)
- Skip tar entries that normalize to "." or empty (prevent staging dir modification)
- Raw name length cap (16KB) before split to prevent DoS from huge PAX paths
- Step 8: compute TIMESTAMP unconditionally (fixes unset var when only meta collides)
- Step 8: pwd -P for symlink-safe containment check
- Full scope copy uses rsync -rt --no-links --no-specials --no-devices
- Session ID replacement uses lambda + sanitized source name (prevents regex backrefs)
- UTF-8 with errors="replace" for untrusted file reads

Task: fn-1-89m.3
- Add alive:share and alive:receive to CLAUDE.md skill table (12 → 14 skills)
- Update inbox-check hook to detect .walnut/.walnut.age files and nudge with alive:receive
- Add .walnut file delegation to capture skill's inbox scan mode

Task: fn-1-89m.4
Add leading / to alive:receive and alive:capture references in nudge
messages so they match the slash-command convention used everywhere else.

Task: fn-1-89m.4
- Replace age with openssl enc for encryption (no terminal interaction needed)
- Passphrase collected through session, passed via env var
- Encrypted packages are single .walnut file (manifest.yaml cleartext + payload.enc)
- No more .walnut.age or .walnut.meta sidecar files
- Add recipient prompt for shared: metadata
- Hardcode template paths (no Explore agent)
- Strip macOS extended attributes from output
- Update receive skill for new encrypted format
- Update format spec and inbox-check hook

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…dir fix

- Receive skill searches for source walnut by name before showing list
- Walnut discovery uses no depth limit to find sub-walnuts
- Nested walnut list shows sub-walnuts indented under parents
- Capsule import offers create-new-walnut alongside import-into-existing
- Archive enforcer allows cleanup in system temp directories
- Archive enforcer skips paths with unexpanded shell variables

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants