# Bootstrap & Repair

The **plugin is the single source of truth** for ECHO's structure. Everything needed to stand up a vault ships in this skill under `scaffold/` — there is no dependency on any in-vault control doc and no external/local re-seed path. This makes the vault portable: point the REST API at any empty Obsidian vault, run this procedure, and it becomes a working ECHO vault.

The vault holds **data only**. It carries no `CLAUDE.md` / `BOOTSTRAP.md` / `STRUCTURE.md` / `index.md`. The "is this vault set up?" signal is a small marker file, `_agent/echo-vault.md`.

## Quick path — run the scripts

Bootstrap, repair, and migration are deterministic scripts; prefer them over running the curl steps by hand. They resolve the scaffold relative to their own location, so they work regardless of the caller's CWD:

```bash
SCRIPTS="${CLAUDE_PLUGIN_ROOT}/skills/echo-memory/scripts"
python3 "$SCRIPTS/bootstrap.py" --dry-run   # preview what would be seeded
python3 "$SCRIPTS/bootstrap.py"             # idempotent, additive — fills only what is missing (also the repair path)
python3 "$SCRIPTS/migrate.py"               # plan a schema migration (dry-run)
python3 "$SCRIPTS/migrate.py" --apply       # perform the migration (moves/deletes, after review)
# Windows: use `python` or `py -3` if `python3` is not on PATH.
```

`bootstrap.py` writes through `echo.py`, so every step is status-checked and the marker is written last. The scripts are pure Python (only an interpreter required — no bash, no platform-specific `date`), so they run identically on Windows, macOS, and Linux. The manual steps below document what the script does (and serve as a fallback if the script can't run).

```
AUTH="Authorization: Bearer $ECHO_KEY"   # export ECHO_KEY first; echo.py resolves it automatically
BASE="https://echoapi.alwisp.com"
```

---

## Probe — is the vault bootstrapped?

At session start, GET the marker:

```bash
curl -s -o /dev/null -w "%{http_code}" -H "$AUTH" "$BASE/vault/_agent/echo-vault.md"
```

- **200** → bootstrapped. Read the marker's `schema_version`; if it is **less than** the plugin's current schema (2), run a migration pass (see *Migrations* below), otherwise proceed straight to the loading procedure in `SKILL.md`.
- **404** → empty/unconfigured vault. Run **Fresh bootstrap** below. (If you expected an existing vault, confirm with the operator once that the REST API is pointed at the right vault before seeding.)

---

## Fresh bootstrap (empty vault)

Idempotent and additive. **Never overwrite an existing file** — GET-probe each path and skip any that already returns `200`. The marker is written **last**, so the vault is only flagged "set up" after every piece is in place.

Throughout, `{{DATE}}` means today's date (`YYYY-MM-DD`), resolved from the conversation's `currentDate`. Substitute it before PUTting any scaffold file or anchor seed.

### 1. Folder tree

The REST API auto-creates parent directories on PUT, so creating the tree = seeding a file into each leaf. Obsidian and git both ignore empty dirs, so drop a one-line `README.md` into any leaf that wouldn't otherwise receive a file. Required tree (leaves marked `→ README`):

```
inbox/captures  inbox/imports  inbox/processing-log
journal/daily  journal/weekly  journal/monthly  journal/quarterly  journal/annual  journal/templates
projects/active  projects/incubating  projects/on-hold  projects/archived
areas/business  areas/personal  areas/learning  areas/systems
resources/concepts  resources/references  resources/people  resources/companies  resources/meetings
decisions/by-date
_agent/context  _agent/memory/working  _agent/memory/episodic  _agent/memory/semantic
_agent/sessions  _agent/health  _agent/templates  _agent/heartbeat
_agent/skills/active  _agent/skills/archived
```

> `decisions/by-project/` is intentionally **not** created — it is retired. A project-relevant decision is mirrored as a `[[wikilink]]` under that project's `## Key Decisions` heading instead.
>
> `reviews/` is **not** created — it is retired. Journal rollups (weekly/monthly/quarterly/annual) live under `journal/`; the monthly vault-health audit lives under `_agent/health/`.

A leaf README is just a one-liner, e.g.:

```bash
printf '# %s\n\nMemory vault folder. See the echo-memory plugin for conventions.\n' "captures" \
 | curl -s -X PUT -H "$AUTH" -H "Content-Type: text/markdown" --data-binary @- \
   "$BASE/vault/inbox/captures/README.md"
```

### 2. Templates

PUT every file under this skill's `scaffold/templates/` to its mirrored vault path. The tree mirrors 1:1:

| Scaffold file | Vault path |
|---|---|
| `scaffold/templates/_agent/templates/session-log-template.md` | `_agent/templates/session-log-template.md` |
| `scaffold/templates/_agent/templates/context-bundle-template.md` | `_agent/templates/context-bundle-template.md` |
| `scaffold/templates/_agent/templates/working-memory-template.md` | `_agent/templates/working-memory-template.md` |
| `scaffold/templates/_agent/templates/semantic-memory-template.md` | `_agent/templates/semantic-memory-template.md` |
| `scaffold/templates/journal/templates/daily-note-template.md` | `journal/templates/daily-note-template.md` |
| `scaffold/templates/journal/templates/weekly-review-template.md` | `journal/templates/weekly-review-template.md` |
| `scaffold/templates/projects/project-template.md` | `projects/project-template.md` |
| `scaffold/templates/decisions/decision-template.md` | `decisions/decision-template.md` |

Templates keep their Obsidian Templater tokens (`{{date:YYYY-MM-DD}}` etc.) verbatim — those are resolved by Templater / by the skill's daily-note routine, not at seed time.

Resolve scaffold paths against the skill directory — **never a bare relative `@scaffold/...`**, which assumes the caller's CWD is the skill dir and silently sends an empty body otherwise:

```bash
SCAFFOLD="${CLAUDE_PLUGIN_ROOT}/skills/echo-memory/scaffold"
curl -s -X PUT -H "$AUTH" -H "Content-Type: text/markdown" \
  --data-binary @"$SCAFFOLD/templates/journal/templates/daily-note-template.md" \
  "$BASE/vault/journal/templates/daily-note-template.md"
```

### 3. Anchor seeds (only if absent — no fabricated facts)

Substitute `{{DATE}}` → today, then PUT each:

| Scaffold file | Vault path |
|---|---|
| `scaffold/anchors/operator-preferences.seed.md` | `_agent/memory/semantic/operator-preferences.md` |
| `scaffold/anchors/current-context.seed.md` | `_agent/context/current-context.md` |
| `scaffold/anchors/inbox.seed.md` | `inbox/captures/inbox.md` |

The operator-preferences seed is deliberately empty — **do not invent preferences.** Real facts accrue through use.

### 4. Vault README (human signpost)

Substitute `{{DATE}}` if present, then PUT `scaffold/README.vault.md` → `/vault/README.md`. This is the only human-facing control doc in the vault and is **not** read by the agent for routing.

### 5. Marker (write last)

Substitute `{{DATE}}`, then PUT `scaffold/echo-vault.md` → `/vault/_agent/echo-vault.md`. Once this returns `200`, the vault is bootstrapped.

### 6. First-run trace

Create today's daily note from `journal/templates/daily-note-template.md` (resolve the `{{date:…}}` tokens to today), append a one-line `## Agent Log` entry noting the bootstrap, and write a session log in `_agent/sessions/YYYY-MM-DD-HHMM-bootstrap.md`. Tell the operator briefly what was created.

---

## Repair (existing vault, something missing)

Run the same steps 1–5, but GET-probe each path first and **only create what is missing**. Never overwrite. If a file exists but looks malformed, flag it in the session log rather than replacing it. Repair is safe to run any time and is the right response if the loading procedure hits an unexpected `404` on a structural path.

---

## Migrations (`schema_version` mismatch)

`scripts/migrate.py` automates this: it reads the marker's `schema_version`, applies each intervening migration (idempotent, additive; destructive steps gated behind `--apply` and printed first), and stamps the marker at the end. Run it dry-run, review the plan, then `--apply`. The steps below are what it encodes — and the manual fallback.

When the marker's `schema_version` is older than the plugin's, apply the migration steps for each intervening version, then PATCH the marker's `schema_version` frontmatter to the new value.

- **0 → 1** (control-docs-in-plugin): the vault previously carried root control docs (`CLAUDE.md`, `BOOTSTRAP.md`, `STRUCTURE.md`, `index.md`). Back them up outside the vault, DELETE them, PUT the thin `scaffold/README.vault.md` over the old verbose `README.md`, write the `_agent/echo-vault.md` marker, and scrub now-dangling `[[CLAUDE]]`/`[[BOOTSTRAP]]`/`[[STRUCTURE]]`/`[[index]]` links from the `## Related` sections of `operator-preferences.md` and `current-context.md` (leave historical session logs alone). Confirm with the operator before deleting.
- **1 → 2** (reviews-folded-into-journal): the `reviews/` tree is retired. (a) For each note under `reviews/weekly/` and `reviews/monthly/`, MOVE it into `journal/weekly/` (rename `YYYY-Www-review.md` → `YYYY-Www.md`) and `journal/monthly/` respectively, preserving the earliest `created:`. (b) Move any `reviews/monthly/YYYY-MM-vault-health.md` to `_agent/health/`. (c) Move `reviews/quarterly|annual/` artifacts to `journal/quarterly|annual/`. (d) Update inbound `[[reviews/...]]` wikilinks in `## Related` sections to the new paths. (e) DELETE the now-empty `reviews/` tree. Confirm with the operator before deleting; leave historical session logs alone. *(Jason's live vault was hand-migrated for the one existing `2026-W24` artifact on 2026-06-10; this step covers any vault bootstrapped under schema 1.)*
- **2 → 3** (entity index + cross-linking): adds the `_agent/index/` folder for the machine-maintained entity registry. `migrate.py` creates the folder; the **data back-fill is `sweep.py`** — after migrating, run `python3 scripts/sweep.py --apply` to build `_agent/index/entities.json` from the notes already in the vault and to symmetrize existing `## Related` cross-links (it adds only the missing reciprocal direction, never invents links). `sweep.py` is idempotent and dry-run by default; re-run it any time `/echo-health` reports index drift.
