alive:system-upgrade · diff
git:20260422.a18add9 to git:20260511.5023f6d
218 added, 385 removed. Audit A to A.
---
name: alive:system-upgrade
- description: "Upgrade from any previous version of the ALIVE Context System. Mines existing structure, detects legacy patterns, handles YAML edge cases, audits sync scripts, and executes a scripted upgrade with rollback."
+ description: "Upgrade ALIVE to the current version. Handles v1/v2/v3.x source states, multi-surface aware (alive-mcp / Hermes / Codex), retroactive version detection, partial-failure resume, dry-run previews, and rollback inspection."
user-invocable: true
---
# System Upgrade
- Upgrade a world from any previous version of the ALIVE Context System to the current version. Handles structural renames, file migrations, terminology updates, legacy cleanup, and integrity verification.
+ Upgrade an ALIVE world from any prior version to the current target. The skill is a thin operator-facing surface; the work happens in the `system_upgrade/` Python package shipped with the plugin (orchestrator + 13 locked phases). Stdlib-only, no PyYAML/ruamel.
- This skill has been battle-tested on a 16GB world with 142 walnuts, 88 bundles, 81 people, and 6 distinct legacy patterns. Every edge case below comes from a real upgrade.
+ If you are reading this on the old monolithic skill (476 lines of inline upgrade logic), that file has been retired in favour of the orchestrator. This file documents what the operator and skill agent need to know to drive it.
---
## When It Fires
- - The session-new hook detects a legacy structure (`.walnut/`, `_core/`, `_capsules/`, `companion.md`, `bundles/`, `_kernel/_generated/`, `tasks.md`, `observations.md`)
- - The human explicitly invokes `/alive:system-upgrade`
- - The human says "upgrade my world", "migrate to the new version", "update alive"
-
- ---
-
- ## Version Reference
-
- | Component | v1 | v2 | v3 |
- |-----------|----|----|-----|
- | System dir | `_core/` | `_kernel/_generated/` | `_kernel/` (flat) |
- | Bundles | `_core/_capsules/` | `bundles/` | walnut root (flat) |
- | Manifest | `companion.md` | `context.manifest.yaml` | `context.manifest.yaml` |
- | Tasks | N/A | `tasks.md` | `tasks.json` (script-operated) |
- | State | `now.md` | `_kernel/_generated/now.json` | `_kernel/now.json` (script-generated) |
- | Observations | N/A | `observations.md` | removed (stash -> log) |
- | Inbox domain | `03_Inputs/` | `03_Inputs/` | `03_Inbox/` |
+ - The human runs `/alive:system-upgrade` (any version of the world — v1, v2, v3.0, v3.1, v3.2).
+ - The session-new hook detects a legacy structure and surfaces the upgrade prompt.
+ - The human says "upgrade my world", "migrate to the new version", "update alive".
+ - The human asks to inspect or restore an earlier upgrade tarball (`--rollback`).
---
- ## Process
-
- ### Phase 1: Mine Existing System
-
- Before touching anything, understand what's there. Dispatch a scout agent (or multiple in parallel) to map the full world structure.
-
- **Core structure scan:**
- - `.walnut/` vs `.alive/` -- which system folder exists? Do BOTH exist (merge needed)?
- - `_core/` vs `_kernel/` -- which kernel structure is in use?
- - `_kernel/_generated/` present? (v2 intermediate directory -- needs flattening in v3)
- - `_capsules/` vs `bundles/` vs flat-at-root -- which bundle structure is in use?
- - `companion.md` vs `context.manifest.yaml` -- which manifest format exists?
- - `now.md` vs `_kernel/_generated/now.json` vs `_kernel/now.json` -- which state format is in use?
- - `tasks.md` present anywhere? (v2 -- needs conversion to `tasks.json` in v3)
- - `observations.md` present in any bundles? (v2 -- removed in v3)
- - Walnut count, people count, bundle/capsule count
- - Squirrel entries and their format
- - Custom skills, rules, hooks in the human's space
- - `.claude/` configuration referencing old paths
-
- **Legacy structure detection (critical -- older ALIVE versions used different layouts):**
- - Un-numbered domain folders at world root: `archive/`, `life/`, `ventures/`, `experiments/`, `inbox/`, `docs/`, `product/`, `plugin/`, `_working/` -- these are from pre-ALIVE-framework eras when domains didn't have numbered prefixes
- - `_brain/` folders inside walnuts (v3 era state management)
- - `_state/` folders inside walnuts (v3 era)
- - Flat walnut structures (key.md at walnut root, no `_core/` or `_kernel/`)
- - `src/` or other code directories orphaned at world root from website development
- - Any folder at world root that is NOT: `01_Archive/`, `02_Life/`, `03_Inbox/`, `04_Ventures/`, `05_Experiments/`, `.alive/`, `.claude/`, or standard dotfiles
+ ## Tool version vs world version
- **v2 structure detection (for v2->v3 upgrade):**
- - `bundles/` directory inside any walnut root (v2 -- bundles flatten to walnut root in v3)
- - `_kernel/_generated/` directory structure (v2 -- `_kernel/` is flat in v3)
- - `tasks.md` files anywhere under any walnut (v2 -- converted to `tasks.json` in v3)
- - `observations.md` files inside bundles (v2 -- removed in v3)
+ Two distinct version concepts; the orchestrator never confuses them.
- **Duplicate walnut detection:**
- - Scan for person walnuts that appear in multiple locations (e.g., `people/professional/jane/` AND `people/jane-smith/`)
- - Compare key.md content: which is the stub, which has real data?
- - Flag all duplicates for human review before upgrade
+ - **Tool version** — read from `plugins/alive/.claude-plugin/plugin.json` `version`. The version of the migrator currently installed. Used for `--resume` plugin-version-skew validation and the upgrade record's `tool_version_at_run` field. Never feeds world-version inference; never feeds the no-op short-circuit.
+ - **World version** — derived from world content fingerprints only: path/file existence, bundle schema fingerprints, hook/script content patterns. Three signals, not four. Lowest-version-wins. When zero signals fire, refuse with `--assume-empty-world` as the explicit override.
+ - **TARGET_WORLD_VERSION** — hardcoded constant in `system_upgrade/__init__.py` (currently `"3.2.0"`). The version the redesign migrates worlds TO. The no-op short-circuit compares the world version (and every per-walnut version) against this constant — never against the tool version. Bumped in lockstep with each plugin minor that introduces world-format changes.
- **World size check:**
- - Run `du -sh` on the world root
- - If >1GB: warn that git is impractical, recommend tarball backup
- - If >5GB: flag specific heavy directories (likely code repos with node_modules)
+ ---
- **Sync script audit:**
- - Read `preferences.yaml` `context_sources:` for configured sync scripts
- - Check `.claude/scripts/` for any scripts with hardcoded paths
- - Flag any that reference `_core/`, `_capsules/`, `.walnut/`, `companion.md`, `now.md`, `_kernel/_generated/`, `bundles/`, `tasks.md`, `observations.md`, or un-numbered domain paths like `inbox/` instead of `03_Inbox/`
+ ## Phases (locked 13)
- **Root-level now.md duplicates:**
- - Some walnuts have `now.md` at BOTH the walnut root AND inside `_core/`
- - Detect all duplicates, flag for merge (use `_core/` version as canonical)
+ The orchestrator runs the same 13 steps every invocation. Numbers below are stable contract numbers — they appear in resume markers, runstate logs, and the `phase_reached` envelope field.
- **_core/ directories with only _capsules/ inside:**
- - Some `_core/` dirs contain only `_capsules/` (no key.md, now.md, etc.)
- - These still need renaming -- include them in the kernel rename phase
+ For higher-level grouping in operator briefings the orchestrator uses the labels **Setup / Detection block (steps 1–5)** and **Mutation / Verify block (steps 6–13)** — labels are not "macro phases" and never re-use the digit `1` to mean step 1 of a sub-grouping; the locked numbers below are the only phase numbers that matter.
```
- ╭─ 🐿️ system scan complete
- │
- │ Current version: [detected]
- │ System folder: [.walnut/ | .alive/ | both]
- │ Kernel: [_core/ | _kernel/_generated/ | _kernel/ (flat) | mixed]
- │ Bundles: [_capsules/ | bundles/ | flat at root | mixed]
- │ State: [now.md | _kernel/_generated/now.json | _kernel/now.json | mixed]
- │ Tasks: [tasks.md | tasks.json | none | mixed]
- │ Observations: [observations.md present | none]
- │ Squirrels: [count] in [location]
- │ Custom: [N skills, N rules, N hooks]
- │
- │ ! Legacy: [un-numbered domains at root | _brain/ folders | etc.]
- │ ! Duplicates: [N person walnuts in multiple locations]
- │ ! World size: [size] -- [git OK | tarball recommended]
- │ ! Sync scripts: [N scripts reference old paths]
- │
- │ Upgrade path: [v1->v3 | v2->v3 | already v3 | specific operations needed]
- ╰─
+ 1. Preflight — resolve world_root, .alive symlink + containment check, then UpgradeLock
+ acquire (lock-meta written ONLY after containment validation), dirty stash,
+ Syncthing, half-sync, submodule guards
+ 2. Snapshot — FileSnapshot pass: both world files + required plugin files. Inputs frozen
+ for the rest of the run.
+ 3. Detect — consume snapshot; produce DetectionReport (world_version + per-walnut
+ versions + all_signals_raw + tool_version_at_run + walkthrough_eligible_matches).
+ The retired-pattern PRE-SCAN runs here as a read-only pass against the
+ snapshot — phase 7 only renders prompts for matches found here.
+ 4. Probe surfaces — each surface's --version --json; collect state_paths for sweep exclusion.
+ NO migrator dispatch yet. --surfaces=none skips per-surface probe + dispatch
+ but does NOT skip the prior-record load — load_prior_final_record runs
+ unconditionally so pending retries from a prior run still reach the no-op gate.
+ 5. NO-OP short-circuit — gate predicate: world_version == TARGET_WORLD_VERSION AND every
+ per-walnut version == TARGET AND walkthrough_eligible_matches is empty AND
+ surface_retry_map is empty AND probe_results contains no hard-fail. On pass:
+ write a no-op upgrade record, release lock, exit 0. On fail: continue.
+ --force-run bypasses; --dry-run logs the would-be no-op without writing.
+ 6. Backup — write .alive/upgrades/pre-upgrade-<iso-ts>.tar.gz (atomic stage → fsync →
+ rename). Stages selected paths into a temp staging dir; excludes
+ .alive/upgrades/, the lock files, and any .alive/.rollback-* dirs to prevent
+ recursive self-inclusion.
+ 7. Walkthrough decide — render prompts for the walkthrough_eligible_matches collected in phase 3;
+ collect y/n decisions. Pure presentation + decision capture; NO writes
+ (dry-run-safe). Phase 7 NEVER re-scans the catalog.
+ 8. Plugin cleanup — world-root sweep + per-walnut audit. Operates ONLY on retired-pattern
+ catalog entries with cleanup_action == "cleanup". migrate_input entries
+ (`_core/`, `_capsules/`, `now.md`, `tasks.md`, `observations.md`,
+ `_kernel/_generated/`, `03_Inputs/`, `companion.md`) are NOT deleted here
+ — phase 9 consumes them.
+ 9. Plugin migrate — per-version migrations (v2→v3.0, v3.0→v3.1, v3.1→v3.2). Consumes the
+ walkthrough decisions from phase 7 to apply extension rewrites; consumes
+ cleanup_action=="migrate_input" catalog entries (reads, transforms into
+ the v3 layout, then removes the source paths atomically).
+ 10. Surface dispatch — run each surface's migrator (alive-mcp, Hermes, Codex). Soft-fail per the
+ four-class probe contract. Also consumes the carried-forward needs_retry[]
+ from phase 4's prior-record load.
+ 11. Verify — live-read verification against post-migration state. Reads disk fresh, NOT
+ the start-of-run snapshot (which would miss failed cleanup/migrate effects).
+ Under --dry-run, verify reads through the virtual post-state overlay.
+ 12. Record — atomic write .alive/upgrades/<iso-ts>.yaml (final upgrade record).
+ 13. Release lock — flock release + lock-meta cleanup. Always runs (`finally` block in the CLI
+ handler) even when an earlier phase refused.
```
- ### Phase 2: Visualise Refactor Plan
-
- Generate an interactive HTML visualisation showing what will change. Open it in the browser so the human can review before committing.
-
- **The visualisation shows:**
- - Every file/folder that will be renamed or moved (old path -> new path)
- - Files that will be converted (companion.md -> context.manifest.yaml, now.md -> now.json, tasks.md -> tasks.json)
- - Files that will be removed (observations.md)
- - Files that won't be touched (log.md, key.md, raw/ contents)
- - Risk assessment per change (safe rename / content conversion / potential conflict)
- - Estimated scope (number of operations, affected walnuts)
- - Legacy folders flagged for cleanup
- - Duplicate walnuts flagged for merge
- - Which upgrade path applies (v1->v2->v3, v2->v3, or verify-only)
-
- ### Phase 3: Ask for Preferences and Permissions
-
- Before executing, confirm with the human:
-
- **Preferences:**
- 1. Squirrel name -- "What should your squirrel be called?" (current name carried over if set)
- 2. Backup strategy -- if world <1GB: "Create a git branch?" If >1GB: "Create a tarball backup of system files?" (always recommended)
- 3. Batch size -- "Upgrade all walnuts at once, or walnut-by-walnut?" (recommended: all at once)
- 4. Legacy folders -- "Clean up [N] legacy folders at world root?" (list them, let human decide)
- 5. Duplicates -- "Merge [N] duplicate person walnuts?" (show pairs, let human pick primary)
-
- **Permissions -- batched into one AskUserQuestion:**
- 1. Approve all (recommended)
- 2. Review each change type
- 3. Do a dry run first
- 4. Cancel
-
- ### Phase 4: Execute Upgrade
-
- Generate a Python upgrade script and run it. The script MUST be generated fresh (not fetched from a URL) so the human can review it. Write it to `.alive/_generated/upgrade.py`.
-
- **The script must handle these known edge cases:**
-
- #### YAML Frontmatter Parsing
-
- companion.md and now.md files in the wild contain YAML that breaks standard parsers:
- - **Em-dashes** (long dashes) in unquoted description strings
- - **Wikilinks** (`[[walnut-name]]`) that YAML interprets as nested flow sequences
- - **Colons** in unquoted description strings (e.g., `description: Schema spec for dev: block`)
- - **Mixed date formats** (`2026-03-12` vs `2026-03-12T14:00:00` vs `2026-03-12T14:00:00Z`)
-
- The parser must:
- 1. Try `yaml.safe_load()` first
- 2. If that fails, sanitize the frontmatter (quote strings with em-dashes, escape wikilinks) and retry
- 3. If that still fails, fall back to a regex-based key-value extractor
- 4. Never crash on malformed YAML -- warn and use what was extracted
-
- #### companion.md -> context.manifest.yaml Conversion (v1->v2, v1->v3)
-
- Field mapping:
- - `type: capsule` -> removed (inferred from location in bundles/)
- - `goal:` -> preserved, also used to generate `description:` (first 120 chars)
- - `status:` -> preserved (known values: draft, prototype, published, done, active)
- - `linked_capsules:` -> `linked_bundles:`
- - `squirrel:` (singular, rare) -> normalize to `squirrels:` (plural, as array)
- - `sources:` -> preserved, but normalize bare strings to `{path: str, description: "", type: "document"}`
- - All other frontmatter fields -> preserved as-is
-
- Body section handling (body sections vary wildly across worlds):
- - `## Context` / `## Summary` / `## Current State` -> `context:` YAML field in manifest
- - `## Tasks` -> extract to `tasks.md` sibling file (if contains `[ ]`, `[x]`, `[~]` markers)
- - `## Work Log` -> extract to `observations.md` sibling file
- - `## Changelog`, `## Decisions`, `## Open Questions`, custom sections -> preserved in `context:` or dropped (these are historical, not operational)
- - Files with no body -> fine, just write frontmatter-only manifest
- - Files with no frontmatter -> warn, skip
+ ---
- After conversion, rename original to `companion.md.bak` (never delete source material during upgrade).
+ ## Skill-invocation pattern
- #### now.md -> now.json Conversion (v1->v2, v1->v3)
+ Every ALIVE skill that drives a Python orchestrator follows the same six steps (per `conventions.md § Skill-invocation convention`). System-upgrade is no exception; the steps are spelled out here so future skill authors and the agents reading this skill know exactly how the surface must behave.
- Field mapping:
- - `phase:` -> preserved (10+ distinct values exist in the wild: active, building, launching, pre-launch, onboarding, waiting, legacy, planning, ready, complete, starting, retainer-pending)
- - `updated:` -> preserved as string (normalize to ISO format if possible)
- - `capsule:` -> rename to `bundle:` (can be null)
- - `next:` -> preserved
- - `squirrel:` -> preserved (can be a hex hash OR the word "migration")
- - `health:` -> dropped (calculated at read time in v2+)
- - `links:` -> dropped (lives in key.md)
- - `model:` -> dropped (lives in squirrel entry)
- - Body (`## Context`, `## Open`, custom headings, or bare prose) -> `context:` JSON string
+ 1. **Resolve world root.** The skill calls `target_resolver.resolve_target_world(cwd=...)` (the legacy-aware resolver shipped in T1) — it walks up from cwd looking for a high-confidence world marker (`.alive/`, two canonical numbered domain dirs, `.walnut/`, `_core/+companion.md`, `_core/+now.md`, or the `companion+now+tasks` triple). For destructive operations against un-numbered legacy domains the resolver refuses to guess; the operator must pass `--world-root` (or the positional path) explicitly. Other ALIVE skills use `_common.find_world_root_with_strategy()`; system-upgrade uses the legacy-aware path because it is the one command that operates on pre-v3 worlds.
+ 2. **Validate flags + read TTY confirmations.** The skill validates flag combinations (mutex on `<world-path>` vs `--world-root`, `--dry-run` requires `--plan-output` or `--json`, etc.), prompts the operator for type-back when the path-policy gate flags a home/cloud target as confirm-required, and gates `--unsafe-confirm-target` on a real TTY (or `--non-interactive` to bypass).
+ 3. **Invoke the orchestrator via subprocess.run.** The skill calls `python -m system_upgrade.cli <args>` (or, in-process, dispatches through the `bin/alive` `_SUBCOMMANDS` registry) with `subprocess.run(..., shell=False, capture_output=True)`. Never `shell=True`; never process substitution (`<(...)`) — both are shell-fragile in tool contexts and break the deterministic-CLI surface contract.
+ 4. **Stream progress to the agent.** Stream the orchestrator's progress lines to stdout via the agent's tool-output channel. No log-prefixing, no banner — the orchestrator already controls user-facing rendering (the `progress.py` module emits the bordered-block UX inline).
+ 5. **Parse the JSON tail.** On non-zero exit OR `--json` mode, parse the orchestrator's pure-JSON tail (`{ok, exit_code, error_code, error, world_root, phase_reached, noop_short_circuit, ...}`) and surface a structured outcome to the agent. The skill never re-renders the JSON — the agent consumes structured fields directly.
+ 6. **Never auto-retry.** The skill never re-runs the orchestrator on its own. Retry/resume is an explicit operator decision via `--resume` (which reads the most-recent `*-resume.yaml` marker, validates `tool_version_at_run` against current `plugin.json`, refuses on skew without `--force`, refuses staleness >24h without `--force`).
- Create `_kernel/_generated/` directory before writing `now.json` (for v2 target). For v3 target, write directly to `_kernel/now.json`. Delete `now.md` after successful conversion.
+ ---
- **Credential scrubbing:** Scan body text for patterns matching `password`, `api[_-]?key`, `secret`, `token`, `Bearer`. Scrub matching lines from the JSON context field. Warn the human about what was removed.
+ ## CLI reference
- #### tasks.md -> tasks.json Conversion (v2->v3)
+ All flags live on `alive system-upgrade` (registered through the `bin/alive` `_SUBCOMMANDS` convention).
- Parse markdown checkbox format into structured JSON:
+ | Flag | Purpose |
+ |---|---|
+ | `<world-path>` (positional) | Target world to upgrade. May be relative; mutually exclusive with `--world-root`. When neither is supplied, the legacy-aware resolver walks up from cwd. |
+ | `--world-root <path>` | Explicit target world root. Required for un-numbered legacy domain layouts (auto-detection refuses to guess on destructive ops). |
+ | `--dry-run` | Read-only after containment, with three narrow allowed transient writes (lock + lock-meta inside `.alive/`, optional `.alive/` dir creation, `--plan-output` plan file). Locks released at phase 13. |
+ | `--plan-output <path>` | Path for the dry-run plan file. Required when `--dry-run` is supplied without `--json`. |
+ | `--resume` | Resume a partial-failure run from the most-recent `*-resume.yaml` marker. Refuses on `tool_version` skew (NOT bypassed by `--force`) and on world-state divergence (bypassed by `--force`). |
+ | `--force` | Bypass world-state divergence on resume. Does NOT bypass `tool_version_at_run` skew. Does NOT bypass any preflight guard. |
+ | `--force-run` | Bypass the phase-5 no-op short-circuit so already-current worlds re-emit verify + record. Does NOT bypass any preflight guard. |
+ | `--assume-empty-world` | Phase-3 detection: bypass the `_kernel/` requirement when fingerprint signals are unanimous-empty. |
+ | `--non-interactive` | Skip every TTY prompt. Combined with `--unsafe-confirm-target` to bypass home/cloud confirm-required gates without a type-back loop. |
+ | `--ext-migration {skip,backup-only,rewrite,abort}` | Walkthrough user-extension migration policy under `--non-interactive`. Default: `rewrite`. |
+ | `--surfaces all\|none\|<csv>` | Surface dispatch policy. `all` (default) probes + dispatches every known surface. `none` skips per-surface probe + dispatch (the prior-record `needs_retry[]` load STILL runs). Otherwise a CSV list of surface names. |
+ | `--rollback [<timestamp>]` | Without an argument: list available pre-upgrade tarballs. With an ISO-8601 timestamp: extract that tarball into `<world>/.alive/.rollback-<ts>/` for inspection. Full automated swap deferred to v3.3. |
+ | `--force-dirty` | Bypass the dirty-session-stash refusal. |
+ | `--syncthing-coordinated` | Bypass the Syncthing-active refusal (operator paused sync). |
+ | `--force-incomplete-sync` | Bypass the half-sync-marker refusal. |
+ | `--unsafe-confirm-target` | Bypass the home/cloud confirm-required path-policy gate. Combined with TTY type-back in interactive mode OR sufficient alone in `--non-interactive`. NEVER bypasses deny categories. |
+ | `--keep-tarballs <days>` | Sweep age cutoff in days (default 30). Tarballs older than this are pruned during phase 8 cleanup. |
+ | `--resume-staleness <hours>` | Resume marker staleness cutoff in hours (default 24). Older markers refuse to resume without `--force`. |
+ | `--json` | Emit a JSON envelope on stdout for agent consumption. |
+ | `-v, --verbose` | Increase progress verbosity (`-vv` is step-level). |
+ | `--plugin-root <path>` | Override the ALIVE plugin root (defaults to `$ALIVE_PLUGIN_ROOT`, then auto-discovery). |
- **Status mapping from checkboxes:**
- - `[ ]` -> `status: "todo"`
- - `[~]` -> `status: "active"`
- - `[x]` -> `status: "done"` -> route to `completed.json` instead of `tasks.json`
+ ---
- **Field extraction:**
- - `@session_id` anywhere in the line -> `session` field (strip the `@` prefix)
- - Section headers map to priority:
- - `## Urgent` or `### Urgent` -> `priority: "urgent"`
- - `## Active` or `### Active` -> `priority: "active"`
- - `## To Do` or `### To Do` -> `priority: "normal"`
- - `## Blocked` or `### Blocked` -> `priority: "blocked"`
- - MoSCoW tags in the line -> `tags` field (array):
- - `[M]` -> `"must"`
- - `[S]` -> `"should"`
- - `[C]` -> `"could"`
- - `[W]` -> `"wont"`
- - The remaining text (after stripping checkbox, session, MoSCoW tags) -> `text` field
+ ## Multi-surface dispatch contract
- **Output format (`tasks.json`):**
- ```json
- {
- "tasks": [
- {
- "text": "Implement the parser",
- "status": "active",
- "priority": "urgent",
- "session": "abc123",
- "tags": ["must"]
- }
- ]
- }
- ```
+ Phase 4 probes every known surface (`alive-mcp`, `hermes`, `codex`) by invoking `<surface> --version --json`. Each surface MUST respond with the following JSON shape on stdout (and exit 0):
- **Output format (`completed.json`):**
```json
{
- "completed": [
- {
- "text": "Write the spec",
- "status": "done",
- "priority": "normal",
- "session": "def456",
- "tags": []
- }
- ]
+ "version": "0.2.0",
+ "compatible": true,
+ "state_paths": [".alive/_mcp/audit.log"],
+ "migrator_argv_prefix": ["alive-mcp", "upgrade"]
}
```
- **Error handling:**
- - Lines that don't match any checkbox pattern -> flag to stderr for manual review
- - Blank lines and section headers -> skip (headers are consumed for priority context)
- - Lines with only whitespace after the checkbox -> skip with warning
-
- After successful conversion, rename original to `tasks.md.bak`.
-
- #### observations.md Handling (v2->v3)
+ - `version` — semver string. Compared against `version_at_retry` for stale-drop on retry carry-forward.
+ - `compatible` — boolean. False means the surface understands the contract but refuses this plugin version (orchestrator soft-fails the surface).
+ - `state_paths` — list of world-relative paths the surface owns. Phase 8 cleanup excludes these from the sweep.
+ - `migrator_argv_prefix` — list of strings (placeholder-free, no shell expansion). Phase 10 dispatch invokes `<prefix> upgrade --json --world-root <world>` to run the surface's migrator.
- - If file is empty or fewer than 3 non-blank lines -> remove (rename to `observations.md.bak`)
- - If file has substantial content (3+ non-blank lines) -> migrate content into the bundle's `context.manifest.yaml` `context:` field, appending to any existing context
- - After migration, rename original to `observations.md.bak`
+ The four probe error classes (consumed by phase 5's no-op gate):
- #### bundles/ Flattening (v2->v3)
+ - `parse_error` — exit 0 but stdout did not match the contract. Hard fail.
+ - `non_zero_exit` — subprocess exited non-zero. Hard fail.
+ - `timeout` — subprocess exceeded its window. Hard fail.
+ - `missing_binary` — surface executable not on `PATH`. Soft fail (a missing optional surface shouldn't force an upgrade run).
+ - `migrator_argv_prefix_invalid` — contract violation (e.g. placeholder strings). Surface is treated as `compatible=False`.
+ - `not_yet_shipped` — Codex stub. Soft signal only.
- Each subdirectory under `bundles/` moves to the walnut root:
- - `walnut-root/bundles/my-bundle/` -> `walnut-root/my-bundle/`
- - Check for naming conflicts at walnut root before moving (e.g., a bundle named `_kernel` would collide)
- - If a conflict exists, warn and skip that bundle (flag for manual resolution)
- - After all bundles are moved, remove the empty `bundles/` directory
+ `alive-mcp` v0.2 is the first conforming implementation (ships AFTER this redesign; soft-fail in the interim — the orchestrator records the dispatch as `skipped` rather than refusing the run).
- #### _kernel/_generated/ Flattening (v2->v3)
+ ---
- - Move `_kernel/_generated/now.json` -> `_kernel/now.json`
- - Move any other files in `_kernel/_generated/` to `_kernel/` (preserving names)
- - Remove the empty `_kernel/_generated/` directory
- - If `_kernel/_generated/` contains subdirectories, warn and skip (flag for manual review)
+ ## Resume + rollback flows
- #### now.json Regeneration (v2->v3)
+ Three distinct failure-recovery surfaces; pick the right one.
- After structural migration is complete:
- - Run `project.py --walnut {path}` to generate a fresh `_kernel/now.json` reflecting the new structure
- - If `project.py` is not available, preserve the migrated `now.json` as-is and warn
+ - **`--resume`** — partial-failure recovery. Re-runs phases 1, 2, 3 fresh, validates `tool_version_at_run` from the marker against the current `plugin.json` (refuses on skew — `--force` does NOT bypass tool-version skew), refuses staleness >24h without `--force`, refuses world-state divergence without `--force`, then resumes from the step after the marker's last completed step. Reads ONLY `*-resume.yaml` markers (NOT `*-runstate.yaml` — runstate is forensic-only).
+ - **`--rollback [<timestamp>]`** — inspection mode. List available `pre-upgrade-<ts>.tar.gz` tarballs at `.alive/upgrades/`, or extract a specific timestamp's tarball into `<world>/.alive/.rollback-<ts>/` for the operator to inspect. The manual restore procedure is printed with exact paths from the tarball manifest. Full automated swap defers to v3.3.
+ - **Tool-version-skew refusal vs world-divergence refusal vs staleness refusal** — three separate gates. Skew = the plugin was upgraded between halt and resume (hard refusal, no override). Divergence = the world content moved between halt and resume (`--force` overrides). Staleness = the marker is older than `--resume-staleness` hours (`--force` overrides).
- #### _capsules/ Move Semantics (two patterns, v1->v2, v1->v3)
+ ---
- - **_capsules/ inside _core/ (now _kernel/):** These must be MOVED to walnut root `bundles/` (v2) or walnut root directly (v3), not just renamed. The move changes relative path depth.
- - **_capsules/ at walnut root:** Simple rename to `bundles/` (v2) or leave in place with rename (v3).
+ ## Pre-flight refusals
- #### Template File Exclusion
+ Every refusal carries a structured `error_code` and exits with the documented code. `--force-*` overrides are scoped — each guard has its own bypass flag.
- Skip files in `templates/` directories -- these are plugin scaffolding templates, not walnut state:
- - `templates/walnut/now.md` -- do not convert
- - `templates/companion/companion.md` -- do not convert
+ | Guard | `error_code` | Override |
+ |---|---|---|
+ | Path-policy: deny category (system paths, `/`, `/etc`, etc.) | `unsafe_target_deny:<reason>` | none — hard refusal |
+ | Path-policy: home/cloud confirm-required | `unsafe_target_tty_confirm_required:<reason>` | TTY type-back OR `--unsafe-confirm-target --non-interactive` |
+ | `.alive/` is a symlink | `boundary_violation:alive_must_be_real_directory` | none — hard refusal |
+ | Symlink/realpath escapes containment root | `boundary_violation:<reason>` | none — hard refusal |
+ | Submodule walnut detected | `submodule_mount_refused` | none — surface as `walnut_boundary_skipped[]` |
+ | Dirty session stash | `dirty_stash` | `--force-dirty` |
+ | Syncthing active | `syncthing_active` | `--syncthing-coordinated` (operator paused sync) |
+ | Half-sync marker present | `half_sync_marker` | `--force-incomplete-sync` |
+ | Upgrade lock contention | `upgrade_lock_busy` (exit 5) | wait + retry; never bypass |
+ | Missing world / not a directory | `missing_world` (exit 3) | fix the path |
+ | `--resume` marker missing | `resume_marker_missing` (exit 3) | run without `--resume` for a fresh upgrade |
+ | `--resume` tool-version skew | `resume_tool_version_skew` | none — hard refusal |
+ | `--resume` world divergence | `resume_world_diverged` | `--force` |
+ | `--resume` staleness >24h | `resume_stale` | `--force` |
+ | `--resume` step not in PHASE_NAMES | `resume_step_unknown` | regenerate marker |
+ | Empty world detected (zero signals fire) | `detect_empty_world` | `--assume-empty-world` |
- #### Root-level now.md Duplicates
+ ---
- If both `walnut-root/now.md` AND `_core/now.md` (or `_kernel/now.md`) exist:
- - Use `_core/` / `_kernel/` version as canonical (it's the system file)
- - Delete the root-level duplicate
- - If root version has unique content, merge it into the context field
+ ## Backward-cleanup table
- #### Execution Order (matters for safety)
+ Every prior forward-fix commit (v3.0 → v3.2) maps to a redesign step that handles its backward cleanup. Source-of-truth: the curated commit inventory at `04_Ventures/alive/upgrade-discipline/audit-public-history.md` § per-version retirement events. Deduplicated by `source_commit` — a single commit may be referenced by multiple `RetiredPattern` catalog entries, but the table has one row per commit.
- **v1->v3 upgrade (full path -- runs v1->v2 steps then v2->v3 steps):**
+ | Source commit | Date | Description | Cleanup step |
+ |---|---|---|---|
+ | `7f7bd27` | 2026-03-29 | v1→v2 layout retirement: `_core/` → `_kernel/`, `_capsules/` → `bundles/`, `companion.md` → `context.manifest.yaml`, `now.md` deleted, `tasks.md` distributed, `People/` → `02_Life/people/`. Forward-fix at v2.0.0 release commit. | T9 (v2→v3.0 migration consumes `migrate_input` catalog entries — `companion.md`, `now.md`, `tasks.md`, `_capsules/`, `_core/`) |
+ | `21ac613` | 2026-04-03 | v2→v3.0 layout retirement: `_kernel/_generated/` flattened, `bundles/` flattened, `03_Inputs/` → `03_Inbox/`, `.walnut/` → `.alive/`, `tasks.md` → `tasks.json`, `observations.md` removed, `_kernel/_generated/`, `bundles/` container removed. Forward-fix at v3.0.0 merge commit. | T9 (consumes 8 `migrate_input` catalog entries) + T4 (3 `verify_only` entries cover the architectural verification rewrite — live-read replaces hardcoded checks) |
+ | `f66fa16` | 2026-04-03 | v3.0 principle statement: "Plugin IS the runtime. World is data. Scripts ship with the plugin, not copied to user machines." Removed `.alive/scripts/` fallback from post-write hook; subagent brief moved to plugin templates. Forward-fix in v3.0.0. | T1 (skill-architecture redesign establishes the orchestrator as plugin-owned; world contains only state, not scripts) |
+ | `f565c81` | 2026-04-16 | v3.0→v3.1 `.alive/scripts/` retirement: introduced `ALIVE_PLUGIN_ROOT` env var; replaced broken paths (`plugins/alive/scripts/tasks.py`, `.alive/scripts/*`) with `$ALIVE_PLUGIN_ROOT/scripts/*` across save, system-cleanup, my-context-graph; eliminated the copy-to-world pattern that caused issue #62 (t003 — world-local copies drift from plugin cache). Forward-fix in v3.1 staging. | T5 (cleanup catalog: 6 `cleanup` entries plus 1 `walkthrough_rewrite` for user-authored extensions referencing `.alive/scripts/`) |
+ | `6a9f629` | 2026-04-29 | v3.1→v3.2 demo-skill retirement of `_stage_outputs/entities/` post-install scaffolding: prevents index double-counting after demo activation. Forward-fix in v3.2 staging. | T5 (cleanup catalog `_stage_outputs/entities/` entry) |
+ | `407ac86` | 2026-04-29 | v3.2 round-5 demo review v2-compatibility migration cleanup. Architectural / verification-only forward fix; no path pattern. | T4 (verification rewrite — live-read covers the v2-compat surfaces without hardcoded path checks) |
- 1. **Create backup** -- tarball of .walnut/ + all _core/ dirs + all companion.md + all now.md + all tasks.md + all observations.md. Write to `.alive/_generated/pre-upgrade-backup.tar.gz`
- 2. **System folder merge** -- `.walnut/` into `.alive/` (handle overlapping squirrels, skills, scripts)
- 3. **Kernel renames** -- `_core/` -> `_kernel/` (deepest first to avoid parent-before-child issues). Include dirs that only contain `_capsules/`.
- 4. **Bundle moves** -- `_capsules/` -> walnut root (check both `_kernel/_capsules/` and walnut-root `_capsules/`)
- 5. **Manifest conversions** -- `companion.md` -> `context.manifest.yaml` (with YAML sanitization)
- 6. **State conversions** -- `now.md` -> `_kernel/now.json` (with credential scrubbing, writing directly to flat `_kernel/`)
- 7. **Task conversions** -- `tasks.md` -> `tasks.json` + `completed.json` (with checkbox parsing)
- 8. **Observations migration** -- `observations.md` -> context field or removed
- 9. **Post-upgrade fixups** -- find-replace `_core/` -> `_kernel/`, `_capsules/` -> bundle names, `_kernel/_generated/` -> `_kernel/`, `bundles/` -> flat refs, etc. in all custom skills, scripts, CLAUDE.md files, hooks
- 10. **Regenerate state** -- run `project.py --walnut {path}` for fresh `now.json`
- 11. **Log the upgrade** -- write entry to `.alive/log.md`
+ R6 acceptance: every commit SHA in `audit-public-history.md` § retirement events has exactly one row; no commit is missed; no commit appears twice.
- **v2->v3 upgrade:**
+ ---
- 1. **Create backup** -- tarball of all `_kernel/_generated/` dirs + all `bundles/` dirs + all `tasks.md` + all `observations.md`. Write to `.alive/_generated/pre-upgrade-backup.tar.gz`
- 2. **Present summary** -- show every change that will happen, grouped by walnut. Wait for confirmation.
- 3. **Flatten _kernel/_generated/** -- move `_kernel/_generated/now.json` -> `_kernel/now.json`, remove empty `_kernel/_generated/`
- 4. **Flatten bundles/** -- move each `bundles/*/` to walnut root, remove empty `bundles/`
- 5. **Convert tasks** -- `tasks.md` -> `tasks.json` + `completed.json` (per bundle and per kernel)
- 6. **Create completed.json** -- `_kernel/completed.json` with migrated done tasks
- 7. **Migrate observations** -- `observations.md` -> manifest context field or removed
- 8. **Rename inbox domain** -- `03_Inputs/` -> `03_Inbox/` at world root (if exists)
- 9. **Post-upgrade fixups** -- find-replace `_kernel/_generated/` -> `_kernel/`, `bundles/X` -> `X`, `tasks.md` -> `tasks.json`, `observations.md` references removed, `03_Inputs` -> `03_Inbox`, in all custom skills, scripts, CLAUDE.md files, hooks
- 10. **Clean domain folders** -- if `02_Life/_kernel/` exists, flag as legacy cruft (domain folders are NOT walnuts). If `People/` exists at world root, flag for move to `02_Life/people/`.
- 11. **Regenerate all projections** -- run `project.py --walnut {path}` for EVERY walnut to generate fresh `_kernel/now.json`. Then run `generate-index.py` on the world root to rebuild the world index with task counts, recent sessions, and unsaved session data. This is the final step that makes `/alive:world` work.
- 12. **Verify structure** -- confirm v3 expectations met
- 13. **Log the upgrade** -- write entry to `.alive/log.md`
+ ## Pure-JSON stdout contract
- **Safety rules for v2->v3 (and all paths):**
- - Back up every file before moving or converting
- - Don't delete originals until migration is verified
- - Present a summary of what will change BEFORE executing
- - Ask for confirmation before each major step (flatten kernel, flatten bundles, convert tasks, migrate observations)
+ When `--json` is set, the orchestrator emits a single JSON object on stdout (no preamble, no trailing whitespace). Schema:
- Log every operation to `.alive/_generated/upgrade-log.yaml` with type, source, target, timestamp, status.
+ ```json
+ {
+ "ok": true,
+ "exit_code": 0,
+ "error_code": null,
+ "error": null,
+ "world_root": "/absolute/path",
+ "phase_reached": "release",
+ "noop_short_circuit": false,
+ "noop_record_path": null,
+ "backup_tarball_path": "/absolute/path/.alive/upgrades/pre-upgrade-2026-05-04T12-34-56.tar.gz",
+ "rollback_pointer": "→ Rollback available: alive system-upgrade --rollback 2026-05-04T12-34-56"
+ }
+ ```
- ### Phase 5: Verify Everything Works
+ `phase_reached` values are drawn from the locked `PHASE_NAMES` set: `preflight`, `snapshot`, `detect`, `probe_surfaces`, `noop_short_circuit`, `backup`, `walkthrough_decide`, `plugin_cleanup`, `plugin_migrate`, `surface_dispatch`, `verify`, `record`, `release`. The CLI guarantees the field is always one of these strings (or `"resume"` / `"rollback_list"` / `"rollback_extract"` for the inspection flows).
- **Structural verification:**
- 1. `.alive/` exists with correct structure (`_squirrels/`, `scripts/`, `preferences.yaml`)
- 2. No `.walnut/` remains
- 3. No `_core/` kernel directories remaining (find + verify)
- 4. No `_capsules/` directories remaining
- 5. No unconverted `companion.md` inside any walnut (`.bak` files are OK)
- 6. No `now.md` remaining in `_kernel/` directories
- 7. No `_kernel/_generated/` directories remaining (v3 flattened)
- 8. No `bundles/` directories remaining at walnut root (v3 flattened)
- 9. No `tasks.md` remaining (converted to `tasks.json`, `.bak` files OK)
- 10. No `observations.md` remaining (migrated or removed, `.bak` files OK)
- 11. All `_kernel/` dirs contain `now.json` directly (not nested in `_generated/`)
- 12. All squirrel entries intact and readable
- 13. Custom skills reference correct paths (grep for `_core/`, `_kernel/_generated/`, `bundles/` -- should find nothing)
- 14. Statusline renders without errors
+ `exit_code` mapping:
- **Task conversion verification (v2->v3):**
- 15. Every `tasks.json` is valid JSON and parseable
- 16. Every `completed.json` is valid JSON and parseable
- 17. Task count in `tasks.json` + `completed.json` matches original `tasks.md` checkbox count (minus flagged unparseable lines)
+ - `0` — success
+ - `1` — general failure / preflight refusal / phase refusal
+ - `2` — usage error (bad flags, mutex violation)
+ - `3` — not found (missing world, missing resume marker, unknown rollback timestamp)
+ - `4` — permission (filesystem permission errors)
+ - `5` — lock contention (`upgrade_lock_busy`)
- **Root-level hygiene:**
- 18. No unexpected folders at world root -- only `01_Archive/`, `02_Life/`, `03_Inbox/`, `04_Ventures/`, `05_Experiments/`, `.alive/`, `.claude/`, and standard dotfiles should exist
- 19. No `People/` at world root (should be `02_Life/people/`)
- 20. No `_kernel/` inside domain folders (`02_Life/_kernel/` is legacy cruft)
- 21. `03_Inbox/` exists (not `03_Inputs/`)
- 22. Flag any legacy folders that remain (un-numbered domains, _working, src, etc.)
+ ---
- **Projection verification:**
- 23. Every walnut has `_kernel/now.json` (generated by project.py)
- 24. `.alive/_index.json` exists and is current (generated by generate-index.py)
- 25. World index includes `recent_sessions` and `unsaved_with_stash` data
+ ## Canonical paths under `.alive/upgrades/`
- **Sync script audit:**
- 20. Check configured sync scripts for old path references -- flag any that will recreate legacy folders
- 21. Surface specific line numbers and suggested fixes
+ Strict regex patterns on basename — the orchestrator, prior-record loader, and rollback all use these classes. NEVER use a `*.yaml` glob; the directory is heterogeneous.
- **Report:**
- ```
- ╭─ 🐿️ upgrade complete
- │
- │ Version: ALIVE Context System v3.0
- │ System folder: .alive/
- │ Walnuts upgraded: N/N
- │ Bundles flattened: N/N
- │ Tasks converted: N/N (N routed to completed.json)
- │ Observations migrated: N/N
- │ Custom capabilities updated: N/N
- │ Verification: N/N checks passed
- │
- │ ! [any remaining issues]
- │
- │ Backup: .alive/_generated/pre-upgrade-backup.tar.gz
- │ Log: .alive/_generated/upgrade-log.yaml
- ╰─
- ```
+ | Filename class | Regex | Owner |
+ |---|---|---|
+ | Final upgrade record | `^(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2})\.yaml$` | phase 12 RECORD; loaded by phase 4 prior-record load |
+ | Resume marker | `^(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2})-resume\.yaml$` | T6 incremental write; loaded ONLY by `--resume` |
+ | Retroactive synthesized record | `^(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2})-retroactive\.yaml$` | T9 retroactive synthesis; read ONLY by T9's de-dup check |
+ | Pre-upgrade tarball | `^pre-upgrade-(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2})\.tar\.gz$` | T5 backup; read by `--rollback` |
+ | Run-state log | `^(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2})-runstate\.yaml$` | T9/T10 forensic-only; NOT consumed by `--resume` |
---
- ## Upgrade Paths Supported
-
- | From | To | Operations |
- |------|----|-----------|
- | Un-numbered domains + `_brain/` + `_state/` | Current v3 | Full restructure -- route to numbered domains, convert state, flatten kernel + bundles, convert tasks |
- | `.alive/` + `_core/` + `_capsules/` + `companion.md` | v3 | Kernel rename, bundle move + flatten, manifest convert, state convert, task convert |
- | `.walnut/` + `_core/` + `_capsules/` + `companion.md` | v3 | System folder merge + all above |
- | `.walnut/` + `_kernel/` + `bundles/` | v3 | System folder merge + flatten kernel + flatten bundles + convert tasks + migrate observations |
- | `.alive/` + `_kernel/_generated/` + `bundles/` + `tasks.md` | v3 | Flatten `_kernel/_generated/` to `_kernel/`, flatten `bundles/` to root, convert `tasks.md` to `tasks.json`, migrate `observations.md` |
- | `.alive/` + `_kernel/` + `bundles/` + `context.manifest.yaml` | v3 | Flatten bundles + convert tasks + migrate observations (partial v2->v3) |
- | `.alive/` + `_kernel/` (flat) + flat bundles + `tasks.json` | (current v3) | Already up to date -- verify only |
- | Both `.walnut/` AND `.alive/` exist | Merge `.walnut/` into `.alive/` then v3 | Handle overlapping squirrels, skills, scripts -- keep newer versions |
- | No system folder | Fresh install | Redirect to `alive:world` for initial setup |
+ ## What this skill does NOT touch
- The upgrade detects what's present and only performs the operations needed. It never forces a full rebuild when a partial upgrade suffices.
+ - **Walnut content** — `key.md`, `log.md`, `insights.md`, raw files. Only moved within renames, never edited (except where retired-pattern catalog entries with `walkthrough_rewrite` request a user-confirmed extension rewrite).
+ - **Git history** — no force pushes, no history rewrites.
+ - **Plugin cache** — `~/.claude/plugins/` is managed by Claude Code, not this skill.
- ---
+ ## What this skill DOES audit (but doesn't auto-fix)
- ## Rollback
+ - **Plugin-surface drift in user extensions** — flagged via the retired-pattern catalog's `walkthrough_eligible` entries. Phase 7 surfaces a y/n prompt per match; phase 9 applies only on accepted decisions, with `.bak.<ts>` siblings preserved.
+ - **Sync scripts referencing retired paths** — surfaced for review; never auto-deleted.
+ - **External integrations** — MCP servers, email/Slack sync scripts referencing old structure are surfaced.
- If something goes wrong mid-upgrade:
+ ---
- 1. Check `upgrade-log.yaml` for the last successful operation
- 2. Offer restore from tarball backup
- 3. Or offer manual fix for the specific failed operation
+ ## Manual canary (production validation)
- The tarball contains every file that was about to be renamed or converted -- full restore is always possible.
+ Before each plugin release that touches the upgrade pipeline, the maintainer runs a **manual canary** against their own private world. This is deliberately NOT automated — the maintainer's world is the highest-stakes upgrade target, and the operator must be human-in-the-loop for it.
- ```
- ╭─ 🐿️ upgrade issue
- │
- │ Failed at: converting [path]/tasks.md
- │ Error: [specific error]
- │
- │ ▸ Options:
- │ 1. Skip this one, continue (fix manually later)
- │ 2. Show me the file so I can fix it
- │ 3. Restore from backup
- ╰─
- ```
+ **Procedure (per release):**
- ---
+ 1. **Snapshot the world** — `tar` the world root to a holding location outside the world tree. The pre-upgrade backup phase 6 also writes an atomic tarball under `.alive/upgrades/`, but the manual snapshot is a second line of defence (the operator controls the location, the timing, and the retention).
+ 2. **Dry-run first** — `/alive:system-upgrade --dry-run`. Verify the planned migration plan, walkthrough-eligible matches, and surface-dispatch list match the operator's mental model. If anything surprises the operator, halt and inspect.
+ 3. **Real run** — drop `--dry-run`. Sit with the run; the orchestrator emits one structured-JSON line per phase. The phases that mutate disk (cleanup, migrate, surface-dispatch, record) are the ones to watch.
+ 4. **Verify** — `flowctl` is irrelevant here; the canary's verifier is `/alive:world` + a manual scan of `_kernel/now.json`, `tasks.json`, and `log.md` against the snapshot from step 1. Anything missing or shape-shifted that wasn't in the migration plan is a regression.
+ 5. **Forensic record** — `.alive/upgrades/<ts>.yaml` carries the full run summary including walkthrough decisions and surface-dispatch results. The operator reads this against the dry-run plan from step 2; any divergence is a finding.
+ 6. **Rollback if needed** — `/alive:system-upgrade --rollback` restores the pre-upgrade tarball atomically. Use the manual snapshot (step 1) as a final fallback if the in-tree tarball is itself suspect.
- ## What This Skill Does NOT Touch
+ **What the canary catches that automation can't:**
- - **Walnut content** -- key.md, log.md, raw files are never modified (only moved within renames)
- - **Git history** -- no force pushes, no history rewrites
- - **Plugin cache** -- `~/.claude/plugins/` is managed by Claude Code, not this skill
+ - Walkthrough-eligible prompts whose y/n decisions only an operator who knows the world's history can answer.
+ - Surface-dispatch failures against the operator's actual MCP / hermes / codex configurations (the test suite mocks these).
+ - Drift between the dry-run plan and the real-run record — automation would just compare planned vs applied counts; a human notices when the *kind* of change is different.
+ - Subjective regressions: bundle headers that look correct to the linter but read wrong to the human who wrote them.
- ## What This Skill DOES Audit (but doesn't auto-fix)
+ **What the canary is NOT:**
- - **Sync scripts** -- flags old paths, shows the fix, lets the human decide
- - **External integrations** -- MCP servers, email, Slack sync scripts are surfaced if they reference old structure
- - **Legacy folders** -- surfaced for human cleanup, never auto-deleted
+ - A test pass/fail gate — `pytest -m system_upgrade` is the gate. The canary is operator confidence.
+ - A substitute for the property-based idempotency tests — those run unattended in CI; the canary runs once per release with attention.
+ - A staging environment — there is no staging world. The maintainer's world IS the canary target. This is intentional: a staging world would diverge from a real-world history and the test would lose its bite.
---
- ## What System Upgrade Is NOT
+ ## What system-upgrade is NOT
- - Not `alive:build-extensions` -- extensions create new capabilities. Upgrade migrates existing structure.
- - Not `alive:system-cleanup` -- cleanup fixes broken things in the current version. Upgrade moves between versions.
- - Not a fresh install -- if no existing system is found, redirect to `alive:world` for initial setup.
+ - Not `alive:build-extensions` — extensions create new capabilities. Upgrade migrates existing structure.
+ - Not `alive:system-cleanup` — cleanup fixes broken things in the current version. Upgrade moves between versions.
+ - Not a fresh install — if the resolver finds no world marker and no override is supplied, the resolver refuses (use `alive:world` for initial setup).
Cleanup fixes. Upgrade transforms.