chore(linkedin-studio): M0-13 — 4 D2 templates + scrub leak + scaffold fallback

This commit is contained in:
Kjell Tore Guttormsen 2026-06-18 12:19:48 +02:00
commit 1d985991d1
6 changed files with 896 additions and 0 deletions

View file

@ -0,0 +1,211 @@
# Audience Demographics
Track WHO is actually engaging with your content. LinkedIn Analytics provides this data for free - use it to understand your real audience vs. your intended audience.
## How to Access This Data
1. Go to LinkedIn Analytics: https://www.linkedin.com/analytics/
2. Click on any post
3. Navigate to "Demographics" tab
4. Review data monthly and update this file
---
## Current Demographics (Last Updated: [Date])
### Industries (Top 10)
Based on LinkedIn Analytics → Post Analytics → Demographics
| Rank | Industry | % of Engagement | Trend |
|------|----------|----------------|--------|
| 1 | [Industry name] | [X]% | [↑/→/↓] |
| 2 | [Industry name] | [X]% | [↑/→/↓] |
| 3 | [Industry name] | [X]% | [↑/→/↓] |
| 4 | [Industry name] | [X]% | [↑/→/↓] |
| 5 | [Industry name] | [X]% | [↑/→/↓] |
| 6 | [Industry name] | [X]% | [↑/→/↓] |
| 7 | [Industry name] | [X]% | [↑/→/↓] |
| 8 | [Industry name] | [X]% | [↑/→/↓] |
| 9 | [Industry name] | [X]% | [↑/→/↓] |
| 10 | [Industry name] | [X]% | [↑/→/↓] |
**Key insights:**
- [Observation 1 - e.g., "60% from government sector, higher than expected"]
- [Observation 2 - e.g., "Tech companies underrepresented vs. my assumptions"]
- [Implication - e.g., "Should increase public sector case studies"]
---
### Job Functions (Top 10)
| Rank | Function | % of Engagement | Trend |
|------|----------|----------------|--------|
| 1 | [Function] | [X]% | [↑/→/↓] |
| 2 | [Function] | [X]% | [↑/→/↓] |
| 3 | [Function] | [X]% | [↑/→/↓] |
| 4 | [Function] | [X]% | [↑/→/↓] |
| 5 | [Function] | [X]% | [↑/→/↓] |
| 6 | [Function] | [X]% | [↑/→/↓] |
| 7 | [Function] | [X]% | [↑/→/↓] |
| 8 | [Function] | [X]% | [↑/→/↓] |
| 9 | [Function] | [X]% | [↑/→/↓] |
| 10 | [Function] | [X]% | [↑/→/↓] |
**Key insights:**
- [Who is actually engaging]
- [Implication for content framing]
---
### Seniority Levels
| Level | % of Engagement | Change vs. Last Month |
|-------|----------------|----------------------|
| Entry level | [X]% | [+/-X%] |
| Individual contributor | [X]% | [+/-X%] |
| Manager | [X]% | [+/-X%] |
| Director | [X]% | [+/-X%] |
| VP | [X]% | [+/-X%] |
| C-level | [X]% | [+/-X%] |
| Owner/Partner | [X]% | [+/-X%] |
**Key insights:**
- **Dominant level:** [Which level engages most]
- **Decision-maker presence:** [% at Director+ level]
- **Content implication:** [How technical/strategic should content be?]
---
### Geographic Distribution (Top 10 Countries)
| Rank | Country | % of Engagement | Trend |
|------|---------|----------------|--------|
| 1 | [Country] | [X]% | [↑/→/↓] |
| 2 | [Country] | [X]% | [↑/→/↓] |
| 3 | [Country] | [X]% | [↑/→/↓] |
| 4 | [Country] | [X]% | [↑/→/↓] |
| 5 | [Country] | [X]% | [↑/→/↓] |
| 6 | [Country] | [X]% | [↑/→/↓] |
| 7 | [Country] | [X]% | [↑/→/↓] |
| 8 | [Country] | [X]% | [↑/→/↓] |
| 9 | [Country] | [X]% | [↑/→/↓] |
| 10 | [Country] | [X]% | [↑/→/↓] |
**Key insights:**
- **Primary market:** [Where most engagement comes from]
- **Time zone implications:** [Optimal posting times]
- **Regional context:** [Does content need localization?]
---
### Company Size (Of Engagers)
| Size | % of Engagement | Trend |
|------|----------------|--------|
| 1-10 employees | [X]% | [↑/→/↓] |
| 11-50 | [X]% | [↑/→/↓] |
| 51-200 | [X]% | [↑/→/↓] |
| 201-500 | [X]% | [↑/→/↓] |
| 501-1000 | [X]% | [↑/→/↓] |
| 1001-5000 | [X]% | [↑/→/↓] |
| 5001-10000 | [X]% | [↑/→/↓] |
| 10000+ | [X]% | [↑/→/↓] |
**Key insights:**
- **Dominant segment:** [Enterprise/Mid-market/SMB]
- **Content implication:** [Scale of examples, budget assumptions]
- **Opportunity:** [Underserved segment to target]
---
## Intended vs. Actual Audience
### Who I Thought My Audience Was
- **Industries:** [Your original assumptions]
- **Roles:** [Your original assumptions]
- **Seniority:** [Your original assumptions]
- **Geography:** [Your original assumptions]
### Who My Audience Actually Is
- **Industries:** [Reality from data above]
- **Roles:** [Reality from data above]
- **Seniority:** [Reality from data above]
- **Geography:** [Reality from data above]
### Strategic Implications
**Content adjustments needed:**
1. [Adjustment 1 - e.g., "Increase public sector examples, decrease startup references"]
2. [Adjustment 2 - e.g., "Frame for Director-level, not just technical ICs"]
3. [Adjustment 3 - e.g., "Add European regulatory context"]
**Opportunities identified:**
1. [Opportunity 1 - e.g., "Large enterprise segment underserved by competitors"]
2. [Opportunity 2 - e.g., "Growing Nordic audience interested in topic X"]
---
## Follower vs. Engager Analysis
**Important distinction:**
- Your followers = who follows you
- Your engagers = who actually interacts with content
Often these are different groups. LinkedIn prioritizes showing your content to engagers, not just followers.
### Follower Demographics
[If you have LinkedIn Premium, note follower demographics here]
- [Key differences from engager demographics]
### Insight
[What the difference between followers and engagers tells you]
---
## Competitive Audience Analysis
How does your audience compare to key competitors/peers?
| Peer | Their Primary Industry | Their Seniority Level | Difference from Mine |
|------|----------------------|---------------------|---------------------|
| [Name] | [Industry] | [Level] | [What's different] |
| [Name] | [Industry] | [Level] | [What's different] |
| [Name] | [Industry] | [Level] | [What's different] |
**Content gap opportunity:**
[Where your unique audience positioning creates content opportunities]
---
## Month-over-Month Trends
### [Current Month] vs. [Previous Month]
**Industry shifts:**
- [What changed and why]
**Seniority shifts:**
- [What changed and why]
**Geographic shifts:**
- [What changed and why]
**Analysis:**
[What these trends indicate about content resonance and audience evolution]
---
## Update Schedule
- **Monthly:** Update all demographics from LinkedIn Analytics
- **Quarterly:** Deep analysis of trends and strategic implications
- **Yearly:** Major review of intended vs. actual audience fit
---
## Update Log
- **[Date]:** Initial demographics captured
- **[Date]:** Observed [significant change] in [demographic category]
- **[Date]:** Shifted content strategy based on [insight]

View file

@ -0,0 +1,267 @@
# My Audience Engagement Patterns
Track YOUR audience's specific behaviors and preferences here. This data is more valuable than generic "best practices" because it's based on YOUR actual results.
## Update Frequency
**Weekly (5 minutes):** Update posting times and add best-performing topic from the week
**Monthly (15 minutes):** Deep dive into patterns, update demographics, analyze format performance
---
## Best Posting Times (Based on MY Data)
**Important:** These should be YOUR times based on YOUR analytics, not generic advice. Track this in LinkedIn Analytics under "Post impressions by time of day."
### Primary Posting Windows
1. **[Day] at [Time]:** Avg. impressions: [X] | Avg. engagement: [Y]
- Why this works: [e.g., "My audience (public sector leaders) checks LinkedIn during lunch break"]
2. **[Day] at [Time]:** Avg. impressions: [X] | Avg. engagement: [Y]
- Why this works: [Your analysis]
3. **[Day] at [Time]:** Avg. impressions: [X] | Avg. engagement: [Y]
- Why this works: [Your analysis]
### Worst Posting Times (To Avoid)
- [Day/Time]: [Why it underperforms for YOUR audience]
- [Day/Time]: [Why it underperforms for YOUR audience]
**Update Log:**
- [Date]: [Change observed - e.g., "Tuesday 2pm now outperforms Friday 8am"]
---
## Top-Performing Topics (Last 90 Days)
Track which topics YOUR audience actually engages with, not what you think they should care about.
1. **[Topic]:** Avg. engagement: [X] | Posts: [Y]
- Best-performing post example: [Brief description]
- Why it resonates: [Your analysis]
2. **[Topic]:** Avg. engagement: [X] | Posts: [Y]
- Best-performing post example: [Brief description]
- Why it resonates: [Your analysis]
3. **[Topic]:** Avg. engagement: [X] | Posts: [Y]
- Best-performing post example: [Brief description]
- Why it resonates: [Your analysis]
### Topics That Surprisingly Underperformed
- **[Topic]:** [Why you thought it would work] → [Why it didn't]
- **[Topic]:** [Analysis]
**Implication for content strategy:**
[What you'll do differently based on this data]
---
## Format Performance (MY Audience)
Based on YOUR analytics, not generic benchmarks. Track in LinkedIn Analytics and your own spreadsheet.
### Format Rankings (By Engagement)
1. **[Format - e.g., "Story-based posts"]:**
- Avg. impressions: [X]
- Avg. engagement rate: [Y%]
- Best time to post: [When]
- Character sweet spot: [Range]
2. **[Format - e.g., "Framework posts"]:**
- Avg. impressions: [X]
- Avg. engagement rate: [Y%]
- Best time to post: [When]
- Character sweet spot: [Range]
3. **[Format - e.g., "Data/research posts"]:**
- [Same metrics]
4. **[Format - e.g., "Case study posts"]:**
- [Same metrics]
### Visual Content Performance
- **Posts with images:** Avg. engagement: [X] vs text-only: [Y]
- **Posts with documents:** Avg. engagement: [X]
- **Posts with carousels:** Avg. engagement: [X]
- **Video posts:** Avg. engagement: [X]
**Your insights:**
[What format performs best for YOUR audience and why]
---
## Hook Types That Work for ME
Not all hook styles work for all audiences. Track which hooks YOUR audience responds to.
### Top-Performing Hook Styles
1. **[Hook type - e.g., "Counterintuitive stat"]**
- Example: [Actual hook you used]
- Avg. engagement: [X]
- Why it works for your audience: [Analysis]
2. **[Hook type - e.g., "Bold contrarian statement"]**
- Example: [Actual hook]
- Avg. engagement: [X]
- Why it works: [Analysis]
3. **[Hook type - e.g., "Personal story opening"]**
- Example: [Actual hook]
- Avg. engagement: [X]
- Why it works: [Analysis]
### Hook Styles That Don't Work for YOUR Audience
- **[Hook type]:** [Why it underperforms with your specific audience]
- **[Hook type]:** [Why it underperforms]
---
## CTA Performance Analysis
Which calls-to-action actually drive engagement from YOUR audience?
### High-Performing CTAs
1. **[CTA type - e.g., "Specific implementation question"]**
- Example: "Which stage is your organization in?"
- Avg. comments generated: [X]
2. **[CTA type]**
- Example: [Actual CTA]
- Avg. comments generated: [X]
### Low-Performing CTAs (To Avoid)
- **[CTA type]:** [Why YOUR audience doesn't respond to this]
---
## Audience Demographics (Who Actually Engages)
Based on LinkedIn Analytics → Analytics → Demographics of people who interacted with your posts
### Industries (Top 5)
1. [Industry]: [% of engagement]
2. [Industry]: [% of engagement]
3. [Industry]: [% of engagement]
4. [Industry]: [% of engagement]
5. [Industry]: [% of engagement]
**Insight:** [What this means for content focus]
### Job Functions (Top 5)
1. [Function]: [% of engagement]
2. [Function]: [% of engagement]
3. [Function]: [% of engagement]
4. [Function]: [% of engagement]
5. [Function]: [% of engagement]
**Insight:** [How this should shape your content]
### Seniority Levels
- C-level: [%]
- VP/Director: [%]
- Manager: [%]
- Individual contributor: [%]
- Entry level: [%]
**Insight:** [Technical depth and framing implications]
### Geographic Distribution (Top 5 Countries)
1. [Country]: [%]
2. [Country]: [%]
3. [Country]: [%]
4. [Country]: [%]
5. [Country]: [%]
**Insight:** [Time zone and regional context considerations]
### Company Size (Of Engagers)
- 1-10 employees: [%]
- 11-50: [%]
- 51-200: [%]
- 201-500: [%]
- 501-1000: [%]
- 1001-5000: [%]
- 5001-10000: [%]
- 10000+: [%]
**Insight:** [Scale and organizational context implications]
---
## Content Length Performance (YOUR Data)
Track the optimal length for YOUR audience, not generic advice.
- **800-1000 characters:** Avg. engagement: [X]
- **1000-1200 characters:** Avg. engagement: [X]
- **1200-1500 characters:** Avg. engagement: [X]
- **1500-1900 characters:** Avg. engagement: [X]
- **1900+ characters:** Avg. engagement: [X]
**Your sweet spot:** [Range that consistently performs best]
**Why:** [Your analysis of why this works for your audience]
---
## Engagement Velocity Patterns
How quickly does YOUR content gain traction?
### First Hour Performance
- **Average engagement in first 60 minutes:** [X] likes, [Y] comments
- **Threshold for algorithm boost:** [Based on your data, when does reach accelerate?]
- **Your current hit rate:** [% of posts that hit the threshold]
### 24-Hour Patterns
- **Most engagement happens in:** [Time window - e.g., "First 3 hours"]
- **Secondary surge times:** [If applicable]
- **Typical engagement curve:** [Description of how your posts perform over 24 hours]
---
## Strategic Insights (The "So What")
Based on all the data above, what should you do differently?
### Content Strategy Adjustments
1. **More of this:** [What data says you should double down on]
2. **Less of this:** [What data says isn't working]
3. **Test this:** [New hypotheses based on patterns]
### Audience Alignment
- **Who you thought your audience was:** [Original assumption]
- **Who actually engages:** [Reality based on data]
- **Strategic implication:** [How content should shift]
### Competitive Edge Opportunities
Based on YOUR unique audience makeup:
- **Gap 1:** [Underserved need you could fill]
- **Gap 2:** [Content angle competitors miss]
- **Gap 3:** [Format opportunity]
---
## Monthly Comparison
Track month-over-month to see if patterns are stable or shifting.
### [Current Month]
- Avg. impressions per post: [X]
- Avg. engagement per post: [Y]
- Follower growth: [+X]
- Best-performing topic: [Topic]
- Best-performing format: [Format]
### [Previous Month]
- [Same metrics for comparison]
**Key changes:** [What's different and why]
---
## Update Log
- **[Date]:** [Significant finding - e.g., "Discovered Thursday posts now outperform Tuesday"]
- **[Date]:** [Pattern shift - e.g., "Framework posts have overtaken story posts in engagement"]
- **[Date]:** [Audience insight - e.g., "Realize 60% of engagers are from enterprise, not SMB"]

View file

@ -0,0 +1,60 @@
# High-Engagement Posts Collection
Store your top-performing posts here for pattern analysis. Add 5-10 of your best posts to identify what consistently works for YOUR audience.
> **Placeholder seed.** Your real, per-user collection lives in your external data
> dir (`~/.claude/linkedin-studio/examples/high-engagement-posts.md`). Replace the
> example structure below with your own posts.
## How to Use This File
After each successful post (high engagement relative to your baseline):
1. Add a new `## Post N` section (where N is a number) per saved post
2. Note engagement metrics and timing
3. Analyze WHY it worked (hook, angle, timing, CTA)
4. Document the replicable pattern
Claude studies these to learn your successful patterns and apply them to new content.
## Entry Format
Each saved post is one `## Post N` section with these fields:
- **Posted:** date, time, timezone
- **Engagement:** likes / comments / shares
- **Reach:** impressions and engagement rate
- **The Post:** the full post text
- **Why It Worked:** hook, angle, timing, CTA
- **Pattern to Replicate:** the elements you want to reuse
(Add your first `## Post N` section above this line once you have a high performer.)
## Patterns Across All High-Performing Posts
**Common Elements:**
- [ ] [Element you notice across your best posts]
- [ ] [Element 2]
**Audience Preferences (What YOUR Audience Responds To):**
- Format: [Your best-performing format]
- Length: [Your best-performing length]
- Tone: [Your best-performing tone]
- CTAs: [What drives replies for you]
**Topics That Resonate:**
1. [Topic]
2. [Topic]
**Best Posting Times (Based on YOUR Data):**
- Primary: [Time]
- Secondary: [Time]
- Avoid: [Time]
## Update Log
- [Date]: [What you added or learned]

View file

@ -0,0 +1,249 @@
# My Custom Post Templates
Save your proven post structures here. When you find a format that works consistently, document it so Claude can replicate the pattern.
---
## Template 1: [Name - e.g., "My Framework Introduction Template"]
**When to use:** [e.g., "When introducing a new framework or model I've developed"]
**Structure:**
```
[HOOK - Counterintuitive stat or bold statement]
(1-2 lines, <110 characters)
[CONTEXT - The problem this framework solves]
(2-3 lines explaining why people struggle)
[FRAMEWORK INTRODUCTION]
"I developed [Framework Name] to solve this."
(Brief one-line description)
[COMPONENT BREAKDOWN]
Stage 1: [Name]
→ [Key characteristic in one line]
Stage 2: [Name]
→ [Key characteristic in one line]
Stage 3: [Name]
→ [Key characteristic in one line]
[IMPLICATION]
"Most organizations are stuck at Stage 1.
Here's what moving to Stage 2 unlocks..."
(2-3 lines on practical value)
[CTA]
"Which stage is your organization in?"
```
**Why this works for me:**
- [Reason 1 - e.g., "My audience loves actionable frameworks"]
- [Reason 2 - e.g., "The diagnostic question always generates 15+ comments"]
**Example posts using this template:**
- [Link to post 1]
- [Link to post 2]
**Average engagement:** [Metrics]
---
## Template 2: [Name - e.g., "My Before/After Transformation Story"]
**When to use:** [e.g., "When sharing case study or project results"]
**Structure:**
```
[HOOK - The transformation in numbers]
"6 months ago: [painful metric]
Today: [improved metric]"
[THE BEFORE]
[Organization] was struggling with [specific problem].
(Paint picture of pain - 3-4 lines)
[THE TURNING POINT]
We decided to [key decision].
Most teams choose [alternative]. Here's why we didn't...
[THE APPROACH]
"Three things mattered:
• [Element 1]
• [Element 2]
• [Element 3]"
[THE AFTER]
Results:
→ [Metric 1]: [Before] → [After]
→ [Metric 2]: [Before] → [After]
→ [Metric 3]: [Before] → [After]
[KEY LESSON]
"The real breakthrough wasn't [expected thing].
It was [non-obvious insight]."
[CTA]
"What's been YOUR biggest lesson in [topic]?"
```
**Why this works for me:**
- [Reason 1]
- [Reason 2]
**Average engagement:** [Metrics]
---
## Template 3: [Name - e.g., "My Contrarian Take"]
**When to use:** [e.g., "When challenging conventional wisdom in my field"]
**Structure:**
```
[HOOK - Bold contrarian statement]
"Everyone says [conventional wisdom].
I think that's wrong."
[THE CONVENTIONAL APPROACH]
Most [target audience] believe [common belief].
(Explain the mainstream view fairly - 2-3 lines)
[WHY IT FAILS]
But here's the problem...
(2-3 specific reasons with examples)
[THE ALTERNATIVE]
Instead, try this:
→ [Alternative approach 1]
→ [Alternative approach 2]
→ [Alternative approach 3]
[EVIDENCE]
"I've seen this play out across [X] projects:
[Specific result/pattern you've observed]"
[NUANCE]
"To be clear: [conventional wisdom] works if [specific condition].
But for [your context], [your approach] is better because..."
[CTA]
"What's your experience? Am I missing something?"
```
**Why this works for me:**
- [Reason 1]
- [Reason 2]
**Average engagement:** [Metrics]
---
## Template 4: [Name - e.g., "My Failure Lesson Post"]
**When to use:** [e.g., "When sharing what didn't work to build trust"]
**Structure:**
```
[HOOK - Admission of failure]
"[Approach] should have worked.
It failed spectacularly."
[SETUP]
We were trying to [goal].
The plan: [what you intended to do]
On paper, perfect.
[THE FAILURE]
"Here's what actually happened..."
(Specific description of what went wrong - 3-4 lines)
[WHY IT FAILED]
Looking back, three mistakes:
1. [Mistake 1] - We assumed [wrong assumption]
2. [Mistake 2] - We underestimated [factor]
3. [Mistake 3] - We ignored [warning sign]
[THE PIVOT]
"So we tried [different approach] instead.
That worked because..."
[THE LEARNING]
"Key lesson:
[Non-obvious insight that only came from the failure]"
[CTA]
"Have you failed at [topic] too? What did you learn?"
```
**Why this works for me:**
- [Reason 1]
- [Reason 2]
**Average engagement:** [Metrics]
---
## Template 5: [Name - Your custom template]
**When to use:** [Context]
**Structure:**
[Your proven structure]
**Why this works for me:**
[Analysis]
**Average engagement:** [Metrics]
---
## Template Performance Comparison
| Template | Avg. Likes | Avg. Comments | Avg. Reach | Best Use Case |
|----------|-----------|---------------|------------|---------------|
| Framework Intro | [X] | [Y] | [Z] | [When] |
| Before/After | [X] | [Y] | [Z] | [When] |
| Contrarian | [X] | [Y] | [Z] | [When] |
| Failure Lesson | [X] | [Y] | [Z] | [When] |
**Insights:**
[What these patterns tell you about your audience preferences]
---
## Template Selection Guide
**Use Framework template when:**
- Introducing new model/system
- Teaching actionable process
- Want high saves (reference value)
**Use Before/After template when:**
- Have strong results to share
- Building credibility
- Want case study authority
**Use Contrarian template when:**
- Challenging assumptions
- Positioning unique POV
- Want engagement/debate
**Use Failure template when:**
- Building trust/authenticity
- Sharing hard-won lessons
- Want vulnerable connection
---
## Update Log
- [Date]: Created template 1 based on [successful posts]
- [Date]: Refined template 2 after [pattern observation]
- [Date]: Added template 3 for [new content type]

59
docs/m0/log.md Normal file
View file

@ -0,0 +1,59 @@
# M0 — Implementation Log
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 3 — Steps 1113 (2026-06-18)
### Environment reality vs. plan assumptions
The plan was authored assuming the operator's **real `.local.md` runtime data**
sat in the plugin tree (227-line voice profile, analytics exports, draft queue).
On this machine that data is **absent** — it is a clean clone:
- `assets/voice-samples/`: only the PII-free placeholder `authentic-voice-samples.md`
(+ `.template.md`). No `.local.md` source.
- `assets/drafts/`: only `.gitkeep`. `assets/analytics/`: only `README.md` + empty `ab-tests/`.
- The 4 tracked D2 scaffold instances DO exist (`high-engagement-posts.md`,
`demographics.md`, `engagement-patterns.md`, `my-post-templates.md`).
**Consequence for Step 12 (live migration):** every `MOVE_FILES` / `MOVE_DIRS`
entry resolved to an absent source → clean no-op. Only the 4 `COPY_FILES`
scaffolds were relocated. Result: `migrated — moved 0, copied 4, skipped 0`;
`.migrated` marker written; idempotent re-run confirmed `already-migrated`.
**Step 12 verify adapted:** the plan's literal check
(`test -f .../voice-samples/authentic-voice-samples.md`) cannot pass without a
`.local.md` source to move, so it was replaced with the achievable post-condition:
`.migrated` marker present + 4 scaffolds external + `migrateData` wired into
`session-start.mjs` + idempotency. The voice MOVE correctly no-op'd. (Operator
pre-approved this adaptation before the run.)
### OUT OF M0 SCOPE — git-history scrub of the leaked post (FOLLOW-UP)
`assets/examples/high-engagement-posts.md` held the operator's **real** LinkedIn
post at HEAD (the "Ralph Wiggum / vibe-coding" post — real names, real engagement
metrics, real personal narrative). Step 13 scrubbed the **working-tree** content
to a generic placeholder (0 `## Post N` sections → personalization score 0, no
PII). **This does not remove the post from git history.** A history rewrite
(`git filter-repo` / BFG) on `assets/examples/high-engagement-posts.md` is a
separate, explicit operation — **deferred, not done here** (brief §13: out of M0
scope). Track until the repo is published.
### Note — external instance retains pre-scrub content (by design)
The Step-12 migration copied `high-engagement-posts.md` to
`~/.claude/linkedin-studio/examples/high-engagement-posts.md` **before** Step 13
scrubbed the in-plugin file (B3 ordering). The external copy therefore still holds
the migrated content. That is the operator's private data dir (outside any repo) —
the operator may curate it; not an M0 concern.
### D2 scaffolds completed
All 6 scaffolds now have a read-only `*-template.*` seed: `case-study-template.md`
and `framework-template.md` already shipped; Step 13 added the 4 missing ones
(`high-engagement-posts-template.md`, `demographics-template.md`,
`engagement-patterns-template.md`, `my-post-templates-template.md`). The 3
already-generic instances seeded their templates verbatim; the high-engagement
template is a freshly-authored generic seed (the old instance was the leak).

View file

@ -83,3 +83,53 @@ describe('calculateScore — reads external instance data (M0-7)', () => {
assert.equal(score, 0);
});
});
describe('calculateScore — scaffold categories read the external instance (M0-13)', () => {
let dataDir, pluginRoot;
const saved = { LINKEDIN_STUDIO_DATA: process.env.LINKEDIN_STUDIO_DATA };
afterEach(() => {
for (const d of [dataDir, pluginRoot]) {
if (d && existsSync(d)) rmSync(d, { recursive: true, force: true });
}
dataDir = pluginRoot = undefined;
if (saved.LINKEDIN_STUDIO_DATA === undefined) delete process.env.LINKEDIN_STUDIO_DATA;
else process.env.LINKEDIN_STUDIO_DATA = saved.LINKEDIN_STUDIO_DATA;
});
test('a populated external high-engagement-posts instance earns the 10 points', () => {
({ dataDir, pluginRoot } = makeRoots());
process.env.LINKEDIN_STUDIO_DATA = dataDir;
mkdirSync(join(dataDir, 'examples'), { recursive: true });
const posts = ['## Post 1', '## Post 2', '## Post 3'].join('\n\n');
writeFileSync(join(dataDir, 'examples', 'high-engagement-posts.md'), `# Posts\n\n${posts}\n`, 'utf-8');
const { score, personalized } = calculateScore(pluginRoot);
assert.equal(score, 10, '3+ saved posts in the external instance earn the 10 points');
assert.equal(personalized, 1);
});
test('the generic placeholder seed (no line-leading ## Post N) scores 0 — no crash', () => {
({ dataDir, pluginRoot } = makeRoots());
process.env.LINKEDIN_STUDIO_DATA = dataDir;
mkdirSync(join(dataDir, 'examples'), { recursive: true });
writeFileSync(join(dataDir, 'examples', 'high-engagement-posts.md'), `# Posts\n\nPlaceholder — add a ## Post N section per saved post.\n`, 'utf-8');
const { score, personalized } = calculateScore(pluginRoot);
assert.equal(score, 0, 'a placeholder with no ## Post [0-9] section scores 0');
assert.equal(personalized, 0);
});
test('scaffold absent at the external root → 0, no crash (graceful degradation)', () => {
({ dataDir, pluginRoot } = makeRoots());
process.env.LINKEDIN_STUDIO_DATA = dataDir;
// examples/ dir never created — the category is simply skipped, never throws
const { score, personalized } = calculateScore(pluginRoot);
assert.equal(score, 0);
assert.equal(personalized, 0);
});
});