linkedin-studio/references/data-path-convention.md

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/ content-history.md
  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).