feat(linkedin): progressive onboarding — hide score until 3+ posts, suppress voice guardian noise

- session-start.mjs: count published posts, gate personalization score
  display and reminder behind >= 3 published posts
- voice-guardian.md: suppress LOW CONFIDENCE messages, silently skip
  drift scoring when < 5 samples
- state-file.template.md: add "general" default for expertise_areas
- onboarding.md: show friendly defaults message for new users, move
  score dashboard to returning-user flow

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-04-11 00:50:18 +02:00
commit 8606abf5ee
4 changed files with 44 additions and 6 deletions

View file

@ -22,6 +22,21 @@ Read `~/.claude/linkedin-thought-leadership.local.md` for current state.
**Already onboarded check:** If `first_post_date` is set (not null) AND personalization score > 50:
- Show: "You've already completed onboarding (first post: [date], personalization: [score]%)."
- If `## Recent Posts` has 3+ entries, show the personalization score dashboard:
```
Personalization Score: [XX]%
Category Weight Status
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Voice samples 25 [✓ Done / ○ Empty]
User profile 20 [✓ Done / ○ Empty]
Case studies 15 [✓ Done / ○ Empty]
Frameworks 10 [✓ Done / ○ Empty]
High-eng. posts 10 [✓ Done / ○ Empty]
Demographics 8 [✓ Done / ○ Empty]
Engagement patterns 7 [✓ Done / ○ Empty]
Post templates 5 [✓ Done / ○ Empty]
```
- Use AskUserQuestion: "Would you like to re-run a specific phase?"
1. Re-optimize profile (360Brew) → jump to Phase 1
2. Improve personalization → jump to Phase 2
@ -68,6 +83,20 @@ After each item, ask if done or needs to skip. Don't block — mark skipped item
╚═════════════════════════════════════════════╝
```
Count published posts by checking `## Recent Posts` entries in state file.
**If fewer than 3 published posts (new user):**
Show: "Your plugin is ready to use with sensible defaults. Personalization makes content more authentic — we'll suggest improvements after you've published a few posts."
Use AskUserQuestion:
1. **Set up voice profile** (optional, recommended later) — 5 questions about your writing style
2. **Set up user profile** (optional, recommended later) — Your name, industry, expertise areas
3. **Both** — Do voice + user profile now
4. **Skip for now** — Use defaults, I'll run `/linkedin:setup` when ready
**If 3+ published posts (returning user):**
Calculate personalization score:
```bash
node --input-type=module -e "

View file

@ -34,7 +34,7 @@ content_series_active: ""
# Profile
expertise_areas:
- ""
- "general"
- ""
- ""
- ""

View file

@ -37,7 +37,7 @@ Score the draft against these 6 dimensions (0 = perfect match, 1 = minor drift p
| 4-5 | ALERT | Significant drift — list all deviating dimensions with rewrites |
| 6 | REWRITE | Content doesn't sound like the user — recommend starting over |
**Confidence gate:** If `## Collected Post Samples` has fewer than 5 posts, output: "Voice drift: LOW CONFIDENCE (X/5 samples). Scoring based on voice profile only." and score only against the profile description (dimensions 1-2 and 4-6), skipping opening patterns (dimension 3) which needs real samples.
**Confidence gate:** If `## Collected Post Samples` has fewer than 5 posts, perform ONLY the AI Pattern Detection (section 1). Skip the Six-Dimension Voice Drift Scoring entirely — there is insufficient data for meaningful drift analysis. Do NOT output "LOW CONFIDENCE" messages. Instead, silently skip drift scoring and only flag if 3+ AI patterns are detected.
**Output format (always include at end of system message):**
```

View file

@ -162,6 +162,13 @@ if (existsSync(STATE_FILE)) {
// Non-critical: don't block session start on pruning failure
}
// Count published posts for progressive onboarding
const recentPostsSection = stateContent.match(/^## Recent Posts\n([\s\S]*?)(?=\n## [^R]|\n## $|$)/m);
let publishedPostCount = 0;
if (recentPostsSection) {
publishedPostCount = (recentPostsSection[1].match(/^\s*[-\[]/gm) || []).length;
}
// Build status line
let statusLine = `LinkedIn: ${postsThisWeek}/${weeklyGoal} posts this week | Streak: ${currentStreak} days`;
if (lastPostDate && lastPostDate !== 'null') {
@ -177,12 +184,14 @@ if (existsSync(STATE_FILE)) {
statusLine += ` | ${followerCount}/${followerTarget} followers (${milestonePhase})`;
}
// Personalization score
// Personalization score (only show after 3+ published posts — progressive onboarding)
let pScore = null;
try {
const { score } = calculateScore(PLUGIN_ROOT);
pScore = score;
statusLine += ` | Personalization: ${score}%`;
if (publishedPostCount >= 3) {
statusLine += ` | Personalization: ${score}%`;
}
} catch { /* ignore */ }
// New creator window
@ -272,8 +281,8 @@ if (existsSync(STATE_FILE)) {
reminders += `- ${weekRemaining} posts remaining to hit weekly goal of ${weeklyGoal}. It's late in the week.\\n`;
}
// Personalization score check
if (pScore !== null && pScore < 50) {
// Personalization score check (only after 3+ posts — progressive onboarding)
if (pScore !== null && pScore < 50 && publishedPostCount >= 3) {
reminders += `- Personalization score is ${pScore}%. Run /linkedin:setup to improve content quality with your real voice, case studies, and audience data.\\n`;
}