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
70 lines
4.1 KiB
Markdown
70 lines
4.1 KiB
Markdown
# 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
|
|
|
|
1. **A path to user data → the data-root token.** Write the full inline form
|
|
`${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/<subpath>` — never a bare
|
|
`assets/<x>/` and never `${CLAUDE_PLUGIN_ROOT}/assets/<x>/` for data. The inline
|
|
`:-default` expansion means the prose is self-resolving; do **not** write "the data
|
|
dir's `<x>`" and force a lookup into this doc.
|
|
2. **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.
|
|
3. **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).
|