linkedin-studio/docs/m0/log.md

10 KiB
Raw Blame History

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 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: 1references/*.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.

Step 15 — 130 refs / 34 files repointed; shipped read-only preserved

Rule-based repoint (negative-lookahead per subdir): analytics 50, drafts 24, audience-insights 14, profile/D1 12, examples 11, plans 6, my-post-templates 4, frameworks 3, case-studies 3, network 2, repurposing-tracker 1. Counts match the plan's family estimates once the shipped exclusions are accounted for (frameworks 6→3 drops framework-template.md; case-studies 5→3 drops case-study-template.md; analytics drops README.md; the Step-11 ANALYTICS_ROOT pins were already gone). Style-A ${CLAUDE_PLUGIN_ROOT} preserved for shipped read-only: analytics/README.md, all *-template.md seeds, assets/checklists/, assets/quick-post-resources.md, the shipped assets/templates/* (only the my-post-templates.md instance repointed), config/*.template.*, and every scripts/analytics / hooks/scripts CODE path. profile/D1: config/user-profile.local.md${…}/profile/user-profile.md (path + filename change, drops .local, per MOVE_FILES). ab-tests/ routes under ${…}/analytics/ab-tests/ (brief §7.1); plans/ at ${…}/plans/ (top-level). Verify: the only in-plugin data-dir paths left are the three shipped exclusions; full lint Failed:1 = EXPECT_REFS only.

Step 15 scope notes — two additions, one deferral

  • network/ + repurposing-tracker.md repointed (additions beyond the plan's named families). Both are code-invisible data classes the agents write (network-builder.md, content-repurposer.md save trackers into the plugin tree). They fit Step 15's stated goal ("route the code-invisible data classes so they don't silently orphan when the default flips"); the plan named ab-tests/plans as examples, not an exhaustive list. As write-targets they are self-sufficient — the agent creates the file external on first write, no migration entry needed.
  • config/personas.local.md deferred — OUT of M0 scope. Unlike the write-targets, this is a read fallback in a resolution chain (edition-state → series file → plugin personas.local.md → template). Repointing the read external without a migration dest would break the read, and adding it to MOVE_FILES is a .mjs change Step 15's scope fence forbids. Personas are newsletter/series production data (already external via $LTL_SERIES_ROOT); the plugin-level personas.local.md is a deliberate in-plugin fallback library. Left as-is; Step 16's no-bare-path lint targets the migrated data classes only, so it does not flag personas. Track as a follow-up if a full personas externalization is wanted later.

Step 16 — lint extended 74→81 checks; the no-pin guard caught 5 real survivors

Added to test-runner.sh: EXPECT_REFS 25→26 + "delta = exactly +1 (data-path-convention.md)" assert (m3/m11); Section 13 (no command/agent/skill/hook prose references a migrated user-data path in-plugin — bare or ${CLAUDE_PLUGIN_ROOT}-pinned — R1; + the no-ANALYTICS_ROOT- pin sibling M3; + the SC2 git status --porcelain --ignored dry-run), each with a non-vacuity self-test mirroring Section 8; Section 14 assertion-count anti-erosion floor (pre-M0 baseline 74, count may only grow — SC6 / assumption 3).

The no-pin guard earned its keep on first run: it flagged 5 surviving in-plugin ANALYTICS_ROOT="${CLAUDE_PLUGIN_ROOT}/assets/analytics" pins that Step 11 missed — agents/analytics-interpreter.md (×2) + skills/linkedin-analytics/SKILL.md (×3). Step 11 scoped the pin-drop to the commands (import.md/report.md); the agent + skill invoke the CLI directly and kept the pin, which would force the analytics CLI to read in-plugin even after the default flipped external (the exact M3 failure). Dropped all 5 (surgical: removed only the ANALYTICS_ROOT=… prefix, kept the node --import tsx form — no CLI-style change), matching how Step 11 left the commands. Lint now 81/0.

Step 18 — release 0.4.0 → 0.5.0 (minor, non-breaking)

Bumped .claude-plugin/plugin.json + the three lint-grepped literals (README version-0.5.0-blue badge, CLAUDE.md (v0.5.0) header, CHANGELOG ## [0.5.0]). Also synced the README reference_docs-25→26 badge (not lint-checked, but stale after the convention doc) and refreshed the now-false "user data still lives inside the plugin" prose in README + CLAUDE.md to "M0 done — data external." Version-consistency lint green; the only remaining 0.4.0 refs are CHANGELOG history + the docs/m0/ planning docs that describe the 0.4.0 → 0.5.0 transition itself (correct, not stale declarations).

Plan deviation — root marketplace docs NOT touched (scope). Plan Step 18 listed /Users/ktg/.claude/plugins/marketplaces/ktg-plugin-marketplace/{README,CLAUDE}.md as "root docs." Skipped: that path is the installed marketplace artifact, outside this polyrepo, and STATE.md scope is "KUN denne pluginnen" (+ global "don't write in other repos"). The binding gate (the lint) checks only this plugin's four declarations, all green. Follow-up (separate catalog repo task): reflect linkedin-studio v0.5.0 in the marketplace catalog README if/when desired — operator's call, not this plugin's M0.

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).