Goal
Invert the source of truth. The git repo becomes the canonical store for sessions, workstreams, plans, ADRs, and learnings. /handoff and /resume never touch Notion at runtime. Notion becomes a downstream read-only mirror, updated by a future async sync worker (Plan 2). Roman and Sergei stay in sync via git pull; Notion outages, MCP-not-loaded failures, and CONV-N collisions all become non-issues by construction.
Success criteria
Five operational checks. All measurable, all in scope for the flip workstream.
<INITIALS>-YYMMDD-NN.md (2-digit zero-padded). Atomic-create via O_CREAT | O_EXCL. Two parallel terminals racing in the same second never collide.Session Log gains an entry. INDEX.md MOCs auto-regenerated. HTML render of touched plan/workstream auto-regenerated to docs/rendered/.tool_calls_breakdown.mcp__notion_offplan = 0 asserted in every session frontmatter. The hook aggregates every mcp__notion_offplan__* tool name into this single canonical counter. /handoff prints a loud WARNING if non-zero.mcp__notion_offplan__* tool name into one canonical counter. /audit greps for any future regression. Any drift back to "let's just query Notion real quick" is caught by the stats themselves.
Approach
C — two workstreams under one plan: "flip" then "vault polish". The user-visible architectural flip ships as one coherent change (no half-states where /handoff is Notion-silent but /resume isn't). The Obsidian vault polish ships as a P1 follow-up.
Realistic estimate: each workstream is 2–3 /build sessions, not 1–3. Phase 4 + Phase 9 alone are ~1.5 sessions each.
Implementation principles
- Repo is authoritative. Every "what if Notion is unavailable?" answers itself by removing Notion from the runtime path.
- Local-first ID generation. Session IDs generated by a Python resolver scanning
docs/sessions/; atomic-create. Notion'sunique_id(CONV-N) becomes a vestigial artefact. - External ID is filename-derived, not CONV-derived. The three legacy CONV-30 variants get three distinct external_ids; Plan 2 won't clobber 2 of 3.
- Frontmatter has a single library.
scripts/lib/frontmatter.pyconsumed by /handoff, /resume, backfill, audit, validate, and Plan 2. Schema enforced via JSON Schema + pre-commit validator. - Hook concurrency hardened with flock.
fcntl.flock+ unique per-invocation tmp filenames + orphan sweep. Read-modify-write of the state JSON is wrapped inLOCK_EX. The 50-parallel test must pass before P2 ships. - Source files in plain MD-link form.
[Display](./file.md)everywhere. Wikilinks rejected for source files. - Frontmatter mobile-reorderd. The top 5 fields fit on a phone screen.
Phases
14 phases (Phase 0 + 1–13). Each ships as part of one of the two workstreams.
| # | Phase | Workstream | Output |
|---|---|---|---|
| 0 | Shared frontmatter library + JSON schemas | flip | scripts/lib/frontmatter.py, scripts/schemas/*.json, scripts/validate_frontmatter.py |
| 1 | Consolidated session resolver | flip | .claude/operators.yaml, scripts/session.py with whoami + claim subcommands; atomic-create primitive |
| 2 | Hook-based session telemetry | flip | scripts/hooks/session_state.py — flock + unique tmp + canonical key aggregation |
| 3 | /handoff rewrite (Notion-silent) | flip | Steps 0 → 0.5 confirm → 0.7 first-handoff hint → 1 (--quick) → 2 (state) → 3 (git stats) → 4 (markdown + INVARIANT warn) → 4.5 (regen INDEX + HTML render) → 5 (workstream log) → 6 (explicit pathspecs) → 7 (commit) → 8 (sweep state) |
| 4 | /resume rewrite (legacy-aware) | flip | Scans both <INITIALS>-*.md AND backfilled CONV-*.md via operator: frontmatter. Empty-result branch. Cache for <5s perf. |
| 5 | Session-note schema (canonical contract) | flip | Mobile-reorderd frontmatter; content_hash canonicalisation; sync_status state machine; minimum-viable body branch |
| 6 | Variant wrappers (Bash-driven) | flip | 4 wrappers handoff-rt/handoff-sk/resume-rt/resume-sk with literal OPERATOR= in Bash command line |
| 7 | Workstream / plan / ADR / learning schema upgrade | flip | Template updates; new folders for sync/learnings/state/state-archive; .gitignore updates |
| 8 | Backfill (non-destructive) | flip | scripts/backfill_frontmatter.py — idempotent, preserves legacy keys including notion_page_id: pending-reconcile; legacy_notes: array; filename-derived external_id (3 distinct for CONV-30 trinity); disposes md_to_notion.py |
| 9 | Interim manual Notion sync (stopgap) | flip | scripts/sync_to_notion_oneshot.py — reference impl for Plan 2; rate-limited 3 req/s; --dry-run default |
| 10 | Obsidian workspace baseline | vault | .obsidian/{app,appearance,community-plugins,core-plugins}.json committed; personal-state files gitignored |
| 11 | Per-folder INDEX.md MOCs + paired HTML renders | vault | scripts/regen_indexes.py + scripts/render_md.py (markdown → HTML using canonical visual register); 5 INDEX.md MOCs; auto-render of touched plan/workstream HTMLs at /handoff Step 4.5 |
| 12 | Link normalisation audit | vault | scripts/audit_links.py sweep + blocking pre-commit hook |
| 13 | Documentation split | vault | CLAUDE.md lean (~150 lines); reference material moves to docs/conventions/{frontmatter,obsidian,sync-forward-compat,rendering,frontmatter-changelog}.md |
Workstreams
repo-as-canonical-store-flip
Ships the user-visible architectural flip: locally-generated session IDs (RT-YYMMDD-NN, atomic-create), Notion-silent /handoff and /resume, rich session content per the schema, concurrency-hardened telemetry hook, interim manual sync stopgap. Absorbs all work from the superseded v1 operator-aware-handoff-resume workstream.
Phases: 0–9.
What's next: P0 — build scripts/lib/frontmatter.py (deterministic content_hash, LF normalisation, SHA-256 of body after closing ---\n) + 5 JSON schema files + validate_frontmatter.py. Wire as non-blocking pre-commit warning. Everything else depends on it.
repo-as-canonical-store-vault
Ships the Obsidian vault layer: committed .obsidian/ baseline, per-folder INDEX.md MOCs, markdown→HTML render automation, link normalisation audit, CLAUDE.md split into docs/conventions/*.
Phases: 10–13.
What's next: Blocked until flip workstream ships and has ≥3 working days of dogfood. Then start with P10 (Obsidian baseline) — lowest-risk; immediately useful for daily vault open.
Risks & edge cases (top 10)
| # | Risk | Mitigation |
|---|---|---|
| 1 | Hook lost-update race | flock + unique tmp filenames + atomic rename. 50-parallel test gates P2 done. |
| 2 | Architecture-invariant key drift | mcp__notion_offplan single canonical key; hook flattens; loud WARN at /handoff if non-zero. |
| 3 | First-run null vs zero | Explicit default {mcp__notion_offplan: 0} when state file missing. Never null. |
| 4 | APFS O_EXCL reliability on iCloud paths | Resolver warns if repo path contains Mobile Documents / CloudStorage. mkdir -p defensively. 99-retry cap. |
| 5 | Variant wrapper soft mechanic | Bash-driven OPERATOR= prefix in literal command. End-to-end verified before backfill. |
| 6 | Operator confirmation | Step 0.5 prints resolved operator + email + waits for Enter. Prevents Sergei-on-Roman's-Mac misattribution. |
| 7 | Legacy CONV-30 trinity | Backfill derives external_id from filename basename. 3 distinct IDs. |
| 8 | Backfill clobbering legacy keys | Backfill skips existing non-null values; legacy_notes: [] consolidation; preserves notion_page_id: pending-reconcile. |
| 9 | /resume scale beyond 200 sessions | Cache regenerated at /handoff Step 8. Re-evaluate when count crosses 200 (~Q1 2027). |
| 10 | Plan-1 → Plan-2 Notion staleness | Phase 9 interim manual sync script bridges the gap. |
Council review
6 reviewers ran in parallel during plan ratification: Roman's UX, Sergei's onboarding, data integrity, failure modes, code health, Perplexity Sonar Pro adversarial. 37 findings surfaced; 5 CRITICAL + 19 HIGH fixed in plan v2; 10 MEDIUM acknowledged in Risks; 4 deferred to follow-up workstreams.
Score progression
| Dimension | v1 | v2 | Δ |
|---|---|---|---|
| Completeness | 9.0 | 9.5 | +0.5 |
| Sequencing | 10.0 | 10.0 | — |
| Risk management | 8.0 | 9.5 | +1.5 |
| Testability | 9.0 | 9.5 | +0.5 |
| Estimate realism | 7.0 | 9.0 | +2.0 |
| Integration coherence | 9.0 | 10.0 | +1.0 |
| Overall | 8.6 | 9.6 | +1.0 |
Forward-compat for Plan 2 (sync worker)
Plan 1 bakes in everything Plan 2 needs. Plan 2 becomes a focused workstream:
- Frontmatter contract complete (sync-metadata fields present in every file from flip-ship).
docs/sync/.gitkeepexists forLAST_SYNC.mdaudit log destination.content_hashreference function inscripts/lib/frontmatter.py— Plan 2 imports it for canonicalisation parity.sync_statusstate machine documented; worker NEVER writesmanual_override.- Platform: Cloudflare Workers cron — wrangler authenticated, account
b16caee7481d783c292d26ec76782cb1, CF Pages already live atpreview.offplan.online. - Notion DB additions required before Plan 2 ships (one-time, UI):
External ID,Operator,Content Hash,Last Synced,Sync Status. - Worker conflict policy: clobber-with-audit-log.
sync_status: manual_overrideis the only escape hatch. - Phase 9's interim manual sync script is the TypeScript port's reference impl.
- Future Supabase pgvector:
summary+tags+content_hashalready in frontmatter.