docs(linkedin-studio): M0-14 — D3 path-convention + voice-readers prototype

This commit is contained in:
Kjell Tore Guttormsen 2026-06-18 12:38:17 +02:00
commit c0abb82d9c
21 changed files with 138 additions and 38 deletions

View file

@ -41,7 +41,7 @@ Before scanning, load the user's content pillars and expertise areas:
- Extract: 5 core expertise areas, target audience, voice preferences
- If file does not exist, ask the user for their 5 content pillars before proceeding
2. **Read voice samples:** `${CLAUDE_PLUGIN_ROOT}/assets/voice-samples/` (glob for .md files)
2. **Read voice samples:** `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/` (glob for .md files)
- Understand their typical angle and tone
3. **Check recent posts:** `${CLAUDE_PLUGIN_ROOT}/assets/analytics/posts/` (if available)

View file

@ -34,7 +34,7 @@ ${CLAUDE_PLUGIN_ROOT}/references/video-strategy-guide.md → Script
${CLAUDE_PLUGIN_ROOT}/references/linkedin-formats.md → Video specs, algorithm data, technical requirements
${CLAUDE_PLUGIN_ROOT}/references/engagement-frameworks.md → Hook types, CTAs, story structures
${CLAUDE_PLUGIN_ROOT}/references/thought-leadership-angles.md → 8 universal angles
${CLAUDE_PLUGIN_ROOT}/assets/voice-samples/ → User's authentic voice (ALWAYS read before scripting)
${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/ → User's authentic voice (ALWAYS read before scripting)
${CLAUDE_PLUGIN_ROOT}/assets/examples/high-engagement-posts.md → Successful content patterns
~/.claude/linkedin-studio.local.md → User state, recent topics, streak
```
@ -150,7 +150,7 @@ When converting an existing text post to video:
After drafting the script:
1. Read `assets/voice-samples/` to match the user's natural speech patterns
1. Read `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/` to match the user's natural speech patterns
2. Check for:
- **Sentence length** — match their natural rhythm
- **Vocabulary level** — match their word choices

View file

@ -52,7 +52,7 @@ This is the single most important rule of this agent.
- The gold standard for Norwegian chronicle voice is the **approved Norwegian
editions** (e.g. the series' approved Del 1 + Del 2). The caller passes the
path(s); read them as the corpus before scrubbing.
- **Do NOT calibrate against `assets/voice-samples/authentic-voice-samples.md`.**
- **Do NOT calibrate against `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md`.**
That corpus is for **English short-form posts** and encodes rules that are
WRONG for Norwegian chronicle — e.g. it forbids the em-dash, which the author
*does* use in long-form Norwegian. Using it as the gold standard would actively
@ -111,7 +111,7 @@ overwrite identity-level voice.
After scrubbing, append what you learned to a drift log so the agent gets sharper
each edition:
- Write to `${CLAUDE_PLUGIN_ROOT}/assets/voice-samples/chronicle-voice-drift-log.md`
- Write to `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/chronicle-voice-drift-log.md`
(create if absent). One dated entry per run: which tells recurred, which voice
traits the draft drifted on, and any newly-confirmed gold-standard pattern.
- Do **not** rewrite the general voice profile (`config/user-profile.local.md`) —

View file

@ -133,7 +133,7 @@ Architecture: [prose/sectioned/framework]
### Analysis Process
1. **Gather** — Read all files in `${CLAUDE_PLUGIN_ROOT}/assets/voice-samples/`, existing profile from `config/user-profile.local.md`, and template from `config/user-profile.template.md`
1. **Gather** — Read all files in `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/`, existing profile from `config/user-profile.local.md`, and template from `config/user-profile.template.md`
2. **Analyze** — Apply all six dimensions to each sample. Note dates for temporal analysis. Flag inconsistent samples as outliers or evolution.
3. **Synthesize** — Patterns in 70%+ of samples = core traits. 40-70% = situational traits (note context). <40% = experimental traits. Track temporal trends.
4. **Build** — Compile into Voice Profile Document format. Include confidence levels (high/medium/low) and concrete examples for every trait.
@ -323,7 +323,7 @@ Fixes: [specific corrections with baseline examples]
## References
Read these files for context and methodology:
- `${CLAUDE_PLUGIN_ROOT}/assets/voice-samples/` — Source samples for analysis
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/` — Source samples for analysis
- `${CLAUDE_PLUGIN_ROOT}/config/user-profile.template.md` — Profile structure template
- `${CLAUDE_PLUGIN_ROOT}/config/user-profile.local.md` — Current voice profile (if exists)
- `${CLAUDE_PLUGIN_ROOT}/references/ai-content-framework.md` — AI content anti-patterns and quality checklist

View file

@ -24,7 +24,7 @@ You are a LinkedIn carousel content specialist. Create high-engagement carousel
## Step 0: Load Context
- Read `~/.claude/linkedin-studio.local.md` for posting state and expertise areas
- Read `assets/voice-samples/authentic-voice-samples.md` for voice profile
- Read `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md` for voice profile
- Check recent posts to avoid topic repetition
## Step 1: Choose Template

View file

@ -24,7 +24,7 @@ The first post doesn't need to be perfect. It needs to EXIST. Every day without
## Step 0: Load Context
Read `~/.claude/linkedin-studio.local.md` for current state.
Read `assets/voice-samples/authentic-voice-samples.md` for voice profile (if it exists).
Read `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md` for voice profile (if it exists).
Check: If `first_post_date` is already set, this user has posted before. Suggest `/linkedin:post` or `/linkedin:quick` instead, and explain this command is for true first-timers.
@ -44,7 +44,7 @@ Total: ~10 minutes. Let's go.
## Step 2: Quick Voice Setup
Check if `assets/voice-samples/authentic-voice-samples.md` has substantive content (more than just the template headers).
Check if `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md` has substantive content (more than just the template headers).
**If voice profile exists:** Say "I already have your voice profile. Let's use it." Skip to Step 3.

View file

@ -25,7 +25,7 @@ worked plan: who to engage, what to say, and exactly when — persisted to state
## Step 0: Load Context
- Read `~/.claude/linkedin-studio.local.md` for posting state (streak, weekly progress, recent posts, follower phase).
- Read `assets/voice-samples/authentic-voice-samples.md` so every draft comment is in the user's voice.
- Read `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md` so every draft comment is in the user's voice.
- Note the user's growth phase (follower count) — it sets daily comment volume and target split.
## Step 1: Identify the Post
@ -113,6 +113,6 @@ delayed spike) with the post-feedback monitor — invoke it via `Task` with
## Reference Files
- `assets/voice-samples/authentic-voice-samples.md` — voice matching for the draft comments
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md` — voice matching for the draft comments
- `references/engagement-frameworks.md` — hook types, CEA, engagement hierarchy
- `references/algorithm-signals-reference.md` — first-hour weighting, signal order, timing data

View file

@ -170,8 +170,8 @@ the edition left off before doing anything.
Step 2. Do not confuse `<serie>/STATE.md` (this edition's production state)
with the plugin's own `STATE.md` / `docs/BUILD-HANDOVER.local.md` (which govern
building the plugin itself).
4. **Read the voice profile**`assets/voice-samples/authentic-voice-samples.md`
and anything else under `assets/voice-samples/`. Long-form must match the
4. **Read the voice profile**`${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md`
and anything else under `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/`. Long-form must match the
author's voice; this is the reference for every drafting and review phase.
5. **Resolve the active personas (per-artifact).** Personas are configured **per
edition**, not from one fixed global file. Resolve the set for
@ -541,7 +541,7 @@ Typically ~2030 % of the edition's final length.
**Procedure:**
1. **Re-read the voice profile** (`assets/voice-samples/`) before writing a
1. **Re-read the voice profile** (`${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/`) before writing a
single sentence — this is the existing LTL rule and it is not optional for
long-form. Voice match starts at the spine, not at expansion.
@ -635,7 +635,7 @@ turning-points the spine already named.
**Procedure:**
1. **Re-read the voice profile** (`assets/voice-samples/`) before expanding —
1. **Re-read the voice profile** (`${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/`) before expanding —
the voice was set at the spine; do not lose it in expansion.
2. **Expand section by section, against the spine.** Each section's paragraph
@ -724,7 +724,7 @@ linkedin-studio:voice-scrubber`, from THIS command layer in the
foreground (principle 4). Pass it the draft path AND the paths to the **approved
Norwegian editions** as the gold standard (e.g. earlier parts' locked
`linkedin/NN/POST.html` or their approved `NN-utkast.md`). **Do NOT** point it at
`assets/voice-samples/authentic-voice-samples.md` — that corpus is English
`${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md` — that corpus is English
short-form and forbids the em-dash; using it as the gold standard would degrade
the Norwegian chronicle voice. The scrubber runs two passes: Pass 1 strips
AI-tells (objective — «la meg være ærlig», reflex rule-of-three, em-dash-spam,
@ -1587,7 +1587,7 @@ the honest decision surface; it sells nothing.
- `${CLAUDE_PLUGIN_ROOT}/commands/headless-review.md` — the Step 6.5 cold review package as a standalone command (run in a fresh session for maximum isolation)
- `${CLAUDE_PLUGIN_ROOT}/commands/pivot.md` — re-opens the pipeline after a late pivot so Steps 56.5 re-run on the changed version before lock
- `${CLAUDE_PLUGIN_ROOT}/commands/react.md` — multi-source synthesis discipline (reused in Step 2)
- `${CLAUDE_PLUGIN_ROOT}/assets/voice-samples/authentic-voice-samples.md` — voice matching
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md` — voice matching
- `${CLAUDE_PLUGIN_ROOT}/references/longform-quality-rules.md` — canonical long-form rules (Steps 2.5, 3a, 3b, 49 all reference)
- `${CLAUDE_PLUGIN_ROOT}/render/build-linkedin.mjs` — POST.html delivery; reads `linkedin/NN/cover.png` + credit/caption (Step 8)
- `${CLAUDE_PLUGIN_ROOT}/render/build-html.mjs` — annotatable review renderer (Step 7)

View file

@ -140,7 +140,7 @@ Use AskUserQuestion:
4. "Paste a paragraph you've written that sounds like YOU (email, doc, anything)"
5. "Any words or phrases you'd NEVER use?"
Save the responses to `assets/voice-samples/authentic-voice-samples.md`. **If the
Save the responses to `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md`. **If the
file is the shipped placeholder** (it contains `<!-- VOICE_PLACEHOLDER -->`),
**REPLACE it entirely** with the profile built from the answers — the
`<!-- VOICE_PLACEHOLDER -->` sentinel must NOT remain, or the voice score stays at
@ -196,7 +196,7 @@ Then ask: "Give me a sentence or two about what you have in mind." If expertise
### 3.2 — Write the post (3-line formula)
Draft the post using the voice profile from Phase 2 (or the existing `assets/voice-samples/` profile):
Draft the post using the voice profile from Phase 2 (or the existing `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/` profile):
- **Line 1 — Hook (110-140 chars):** specific to their experience, no generic opening
- **Line 2 — Context (1-3 sentences):** the what and why, kept tight
- **Line 3 — Insight + question:** their takeaway, ending on a genuine question that invites comments

View file

@ -26,7 +26,7 @@ You are a LinkedIn content pipeline orchestrator. Guide the user through the com
Load persistent state and personalization:
- Read `~/.claude/linkedin-studio.local.md` for posting state
- Read `${CLAUDE_PLUGIN_ROOT}/skills/linkedin-studio/SKILL.md` for profile and preferences
- Check `assets/voice-samples/` for voice matching
- Check `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/` for voice matching
- Read `assets/templates/my-post-templates.md` for proven post templates — use these in Step 2 (Draft)
- Read `assets/frameworks/framework-template.md` if the topic involves a framework or methodology
@ -81,7 +81,7 @@ Run the draft through optimization checks:
- [ ] Authentic voice (not AI-sounding)
**Voice check:**
Compare against `assets/voice-samples/` to ensure natural tone.
Compare against `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/` to ensure natural tone.
Present optimized version with before/after comparison.
@ -207,5 +207,5 @@ Replace placeholders with actual post data. Set `next_planned_topic` manually if
- `${CLAUDE_PLUGIN_ROOT}/references/linkedin-formats.md`
- `${CLAUDE_PLUGIN_ROOT}/references/scheduling-strategy.md`
- `${CLAUDE_PLUGIN_ROOT}/assets/checklists/quality-scorecard.md`
- `${CLAUDE_PLUGIN_ROOT}/assets/voice-samples/`
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/`
- `${CLAUDE_PLUGIN_ROOT}/assets/drafts/queue.json`

View file

@ -36,7 +36,7 @@ Check weekly progress:
- If `posts_this_week == weekly_goal - 1`, note: "This is your last post to hit this week's goal."
Check for existing assets:
- `assets/voice-samples/` - Match the user's natural voice
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/` - Match the user's natural voice
- `assets/examples/high-engagement-posts.md` - Study past successful posts and replicable patterns
- `assets/frameworks/framework-template.md` - Reference user's documented frameworks for framework posts
- `assets/templates/my-post-templates.md` - User's proven post templates with success rates. **Prefer these over generic structures.**

View file

@ -25,7 +25,7 @@ You are a LinkedIn content creator specializing in turning external content into
First, load persistent state and personalization:
- Read `~/.claude/linkedin-studio.local.md` for posting state (streak, weekly progress, recent topics)
- Read `assets/voice-samples/authentic-voice-samples.md` for voice profile
- Read `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md` for voice profile
- Check recent posts to avoid topic repetition within 7 days
## Step 1: Get URL(s)
@ -264,7 +264,7 @@ Same as Step 8 — run `state-updater.mjs` with actual post data.
## Reference Files
- `assets/voice-samples/authentic-voice-samples.md` — Voice matching
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md` — Voice matching
- `references/thought-leadership-angles.md` — 8 universal angles
- `references/engagement-frameworks.md` — Hooks, structure, CTAs
- `assets/checklists/quality-scorecard.md` — Pre-publish check

View file

@ -26,7 +26,7 @@ Read these 8 asset files and detect placeholder patterns to calculate the curren
| Category | Weight | File/Directory | Placeholder Detection |
|----------|--------|----------------|----------------------|
| Voice samples | 25 | `assets/voice-samples/authentic-voice-samples.md` | Placeholder if it contains the `<!-- VOICE_PLACEHOLDER -->` sentinel (or has <50 lines) |
| Voice samples | 25 | `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md` | Placeholder if it contains the `<!-- VOICE_PLACEHOLDER -->` sentinel (or has <50 lines) |
| User profile | 20 | `config/user-profile.local.md` | Check if file exists; count `[Your ` placeholders |
| Case studies | 15 | `assets/case-studies/*.md` | Count non-template `.md` files (exclude `case-study-template.md`) |
| Frameworks | 10 | `assets/frameworks/*.md` | Count non-template `.md` files (exclude `framework-template.md`) |
@ -81,7 +81,7 @@ Based on their answer, run the corresponding sub-workflow below.
## Step 3a: Voice Samples Workflow
**Goal:** Populate `assets/voice-samples/authentic-voice-samples.md` with real voice data.
**Goal:** Populate `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md` with real voice data.
**Delegate the analysis + profile construction to the `voice-trainer` agent** — invoke it via `Task` with `subagent_type: linkedin-studio:voice-trainer` (foreground, from this command layer). The agent performs the pattern detection and extraction (steps 23 below) and returns the structured voice profile; this command owns collecting the samples (step 1) and writing the profile back to disk (steps 46).
@ -98,7 +98,7 @@ Based on their answer, run the corresponding sub-workflow below.
- Words/phrases they avoid
- How they handle technical depth
- How they conclude (CTA style, takeaway style)
4. Read the existing `assets/voice-samples/authentic-voice-samples.md`
4. Read the existing `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md`
5. **If the file is the shipped placeholder** (it contains `<!-- VOICE_PLACEHOLDER -->`):
**REPLACE it entirely** with the profile built from the user's samples. The
placeholder's `<!-- VOICE_PLACEHOLDER -->` sentinel must NOT survive — if it

View file

@ -39,7 +39,7 @@ Load video-specific references:
- Read `references/linkedin-formats.md` (Video Content Deep Dive section) for algorithm data and technical specs
Check for existing assets:
- `assets/voice-samples/` — Match the user's natural voice (REQUIRED before scripting)
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/` — Match the user's natural voice (REQUIRED before scripting)
- `assets/examples/high-engagement-posts.md` — Study successful patterns
## Step 1: Choose Video Type
@ -87,7 +87,7 @@ Delegate script generation to the `video-scripter` agent — invoke it via `Task
- Visual cues (`[CAM:]`, `[SCREEN:]`, `[SLIDE:]`, `[TEXT:]`)
- Energy cues (`[ENERGY: up]`, `[PAUSE: 1s]`)
- Transition markers (`[CUT]`, `[TRANSITION:]`)
4. Match voice against `assets/voice-samples/`
4. Match voice against `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/`
5. Generate captions, thumbnail suggestion, post caption, and first comment
## Step 5: Quality Check

View file

@ -4,6 +4,36 @@ Running record of decisions, deviations, and out-of-scope follow-ups discovered
during M0 execution. Plan: `docs/m0/plan.md` (18 steps). History → git; this file
captures only what the commit messages cannot.
## Session 4 — Steps 1418 (2026-06-18)
### Step 14 GATE outcome — the D3 convention works; edit count is ~1:1, not reduced
Prototyped `references/data-path-convention.md` on the voice-readers family:
**38 refs across 19 files repointed** — exactly the plan's prediction. The measured
answer to brief D3's open question (*can a convention reduce edits, or do commands
need literal paths for Claude to act on?*): command/agent prose that tells Claude to
**read** a file needs a resolvable path **on the line**. A "the data dir's
`voice-samples/` (see convention doc)" reference adds a lookup hop and is not directly
actionable. The inline `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/…`
token is both self-resolving **and** points at the doc. So the convention does **not**
cut edit count below ~1 per ref — it makes every edit a **uniform mechanical token
swap** (vs bespoke per-line decisions), with the doc as single source of truth.
**GATE = proceed** (convention confirmed working); Step 15 applies the same uniform
token to the remaining families. This is D3 realized as one token — NOT a re-decision
to literal-edit (alt. a). Applied via an ordered swap (prefixed `${CLAUDE_PLUGIN_ROOT}/
assets/voice-samples/` form before bare `assets/voice-samples/`, so the bare pass can't
corrupt the prefixed one).
### Designed inter-step red lint (Step 14 → Step 16)
After Step 14 the structure lint is **Failed: 1**`references/*.md: 26 (expected 25)`,
the new convention doc as the 26th ref file. `EXPECT_REFS` bumps to 26 in **Step 16**
(plan Session-4 scope forbids touching tests). This session lands 14→18 in one go, so
the lint is restored to green at Step 16 — no red is left at session end. The only
surviving bare `assets/voice-samples/` is inside the convention doc itself (it documents
the in-plugin placeholder-scaffold location for the fallback rule) — Step 16's
no-bare-path assertion must exempt `references/data-path-convention.md`.
## Session 3 — Steps 1113 (2026-06-18)
### Environment reality vs. plan assumptions

View file

@ -54,7 +54,7 @@ Provide reminders naturally based on what was done in the session. If no LinkedI
If a LinkedIn post was created or finalized in this session, save the full post text as a voice sample:
- Read the full post text from the draft that was just created
- Check if `assets/voice-samples/authentic-voice-samples.md` exists
- Check if `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md` exists
- Append the full post to the `## Collected Post Samples` section:
```
### [YYYY-MM-DD] — [post type] ([char count] chars)

View file

@ -28,7 +28,7 @@ If two or more of these are missing, flag it alongside the AI-pattern alert: the
## 2. Six-Dimension Voice Drift Scoring
Read the voice profile and collected post samples from `${CLAUDE_PLUGIN_ROOT}/assets/voice-samples/authentic-voice-samples.md`.
Read the voice profile and collected post samples from `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md`.
Score the draft against these 6 dimensions (0 = perfect match, 1 = minor drift per dimension):

View file

@ -0,0 +1,70 @@
# 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).

View file

@ -248,5 +248,5 @@ Deviation from established personal voice profile. Measured across 6 dimensions:
### Voice Profile
Quantified signature of a creator's unique writing style across sentence structure, vocabulary, hook preferences, storytelling approach, tone, and formatting. Updated quarterly. Identity-level traits (avoided words, tone, humor) are protected from automatic modification.
**Used in:** `agents/voice-trainer.md`, `assets/voice-samples/authentic-voice-samples.md`
**Used in:** `agents/voice-trainer.md`, `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md`

View file

@ -77,7 +77,7 @@ These rules apply to ALL content created by any skill or command:
3. **No external links** in post body (correlate with lower reach — see `references/algorithm-signals-reference.md`)
4. **No corporate buzzwords:** leverage, synergy, paradigm shift, thought leader, disruptive, value proposition, ecosystem, holistic approach
5. **Topic alignment:** Must align with user's 5 core expertise areas (topic-relevance signal)
6. **Voice:** Always read `assets/voice-samples/` before generating content
6. **Voice:** Always read `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/` before generating content
7. **Quality scorecard:** See `assets/checklists/quality-scorecard.md`
---

View file

@ -138,12 +138,12 @@ Over time, content can drift from your authentic voice -- especially when using
**Prevention:**
- Quarterly voice audits (use voice-trainer agent)
- Read posts aloud before publishing
- Maintain voice samples in `assets/voice-samples/`
- Maintain voice samples in `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/`
- Compare drafts against your voice profile
### Voice Samples
**Rule:** Always read `assets/voice-samples/` before generating content. This directory contains reference posts that represent the user's authentic voice.
**Rule:** Always read `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/` before generating content. This directory contains reference posts that represent the user's authentic voice.
---
@ -199,5 +199,5 @@ The differentiation-checker agent evaluates content across five dimensions:
|------|--------------|
| `references/algorithm-signals-reference.md` | Profile optimization, topic-relevance |
| `references/linkedin-visual-style.md` | Visual identity consistency |
| `assets/voice-samples/` | Voice reference (always read before content creation) |
| `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/` | Voice reference (always read before content creation) |
| `config/user-profile.template.md` | User personalization setup |