The LAST second-brain slice; the S0–S3e arc is now complete.
(b) Retire the dead, zero-reader content-history.md across its 8 plumbing
surfaces: the flaky Stop-hook writer prose, the template (git-rm'd), the
migrate-data.mjs B1 MOVE entry + its test assertions, .gitignore, the gate
SC2_CLASSES guard, the data-path ref-doc, and a session-start comment. SC1
grep = 0; migrate suite 5/5 (R8 idempotency demonstrated).
(c) `brain reconcile` — read-side triple-post reconciliation joining silo 1
(## Recent Posts, auto-tracked creation) to the silo 2↔3 graph, surfacing the
coverage gap: posts created via the plugin but never `brain ingest`-ed. New
pure core scripts/brain/src/reconcile.ts (parseRecentPosts tracks the WRITER
format state-updater.mjs:116, NOT the date-only pruner; reconcileRecentPosts
matches hook→record.body→graph for the in-graph/in-brain-only/orphaned tiers;
loadRecentPosts reads STATE_FILE via the canonical getStateFile() chain — a new
cross-seam read, the state file lives outside the brain dataRoot). Wired as the
`brain reconcile` CLI subcommand (inline parsePublishedRecord loader, not the
body-less listPublished). Read-only: never writes the state silo.
Tests (verified live): gate 99/0/0 (ASSERT floor 82→84; new Section 16f
self-test + CLI grep) · brain 127/127 (floor 114→127, +13 reconcile) · hook
suite 136/136. SC4/SC6 end-to-end run is a real behavioural pass (STATE_FILE
seam read + fallback). Honest limit: read-side cannot reconstruct un-captured
specifics/trends — auto-capture is the flagged follow-up.
Brief/plan: docs/second-brain/{brief,plan}-sb-s3e.md (fbad29d). Go-before-code
gate cleared (operator: retire · read-side · build-now).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RigJBiRFNtFZKCz21qNbQ4
4.1 KiB
Data-Path Convention
How command, agent, skill, and hook-prompt prose should refer to user data vs plugin-shipped assets after the M0 migration. One token, resolved inline, so the prose Claude reads is directly actionable — no indirection, no stale in-plugin paths.
The two roots
| Root | Token in prose | Holds | Moves on migration? |
|---|---|---|---|
| Data root | ${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio} |
the user's real data (voice profile, analytics, drafts, plans, …) | yes — auto-migrated out of the plugin on session-start |
| Plugin root | ${CLAUDE_PLUGIN_ROOT} |
read-only ships (templates, checklists, references, fonts, framework scaffolds) | no — these stay in the plugin |
The data root mirrors the state file (~/.claude/linkedin-studio.local.md). The
default is overridable by the single env var LINKEDIN_STUDIO_DATA; the deprecated
aliases ANALYTICS_ROOT / STATE_FILE / PLUGIN_ROOT are honored for one minor
version. This generalizes the proven newsletter pattern
${LTL_SERIES_ROOT:-$HOME/linkedin-series} (commands/newsletter.md) to all data.
Data-root layout (mirrors brief §7.1)
${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/
voice-samples/ authentic-voice-samples.md chronicle-voice-drift-log.md
analytics/ exports/ posts/ weekly-reports/ monthly-reports/ ab-tests/
drafts/ queue.json week-*/ carousel/ multiplatform/ repurposed/
profile/ user-profile.md
frameworks/ <slug>.md
audience-insights/ demographics.md engagement-patterns.md
examples/ high-engagement-posts.md
templates/ my-post-templates.md
plans/ <weekly|monthly>-plan-*.md
The prose rule
- A path to user data → the data-root token. Write the full inline form
${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/<subpath>— never a bareassets/<x>/and never${CLAUDE_PLUGIN_ROOT}/assets/<x>/for data. The inline:-defaultexpansion means the prose is self-resolving; do not write "the data dir's<x>" and force a lookup into this doc. - A path to a plugin-shipped read-only asset → keep
${CLAUDE_PLUGIN_ROOT}. Templates (config/*.template.*), checklists,references/, fonts, and framework scaffolds ship with the plugin and stay there. - Preserve existing graceful-degradation guards. "if it exists", placeholder /
sentinel checks, and
<5-sample silent-skips stay as written — only the path token changes.
Scaffold fallback (the COPY classes + the voice placeholder)
Four data files ship a seed/scaffold in-plugin and a canonical instance external:
examples/high-engagement-posts.md, audience-insights/{demographics,engagement-patterns}.md,
templates/my-post-templates.md. The voice profile is similar — the plugin ships a
placeholder at assets/voice-samples/authentic-voice-samples.md; the user's real profile
lives external. Readers prefer the external instance and fall back to the in-plugin seed
when external is absent (the code does this in personalization-score.mjs; prose keeps
its "if it exists" guard). The external instance is canonical — migration never clobbers it.
Why inline, not indirection (the Step-14 GATE finding)
The prototype on the voice-readers family (19 files, 38 refs) measured the open question
from brief D3: can a convention reduce edits, or do commands need literal paths for Claude
to act on? Outcome: command prose that tells Claude to read a file needs a resolvable
path on the line. A "the data dir's voice-samples/ (see this doc)" reference adds a lookup
hop and is not directly actionable. The inline ${LINKEDIN_STUDIO_DATA:-…}/… token is both
self-resolving and points here. So the convention does not cut the edit count below
~1 per reference — it makes every edit a uniform, mechanical token swap instead of a
bespoke per-line decision, with this doc as the single source of truth. Step 15 applies the
same uniform token to the remaining families (D3 realized as one token — not a re-decision).