linkedin-studio/scripts/specifics-bank
Kjell Tore Guttormsen b54e450c3e feat(linkedin-studio): N11 — serie-destillat + skjelett-sjekk + specifics-bank bruks-logging [skip-docs]
Every long-form gate agent sees ONE edition, so «the reader has heard this
before» was structurally invisible — and the specifics-bank dedupe actively
encouraged re-surfacing the same material. At series cadence that is the
fastest-growing defect class. N11 makes it visible BEFORE the skeleton is
approved, at two grains:

- Series grain — new scripts/editions package: each locked edition's spent
  anecdotes/arguments/hooks are written to <serie>/linkedin/series-distillate.json
  at Step 8 lock (distil-append), and the next skeleton is checked against it at
  Step 2.5 (distil-check) with the finding folded into the annotation gate the
  operator already reads. Advisory, never blocking: a deliberate callback is a
  legitimate move, an unnoticed retread is not.
- Material grain — specifics-bank usedIn log: record-usage stamps «used in
  edition NN» on the specifics an edition actually consumed (read from the bound
  slot-map, so abstrakt/ekstern stamp nothing). At lock, so it means published;
  idempotent under a pivot re-lock. Additive-optional, schema stays v1.

Placement deviates from the plan text (${DATA}) after premise-verification:
per-series state belongs in the series root beside edition-state.json, where
Step 0 already resolves the path and no slug→path map is needed. Operator
approved. The distillate module also lands in scripts/editions now rather than
at N12, since the AC required a testable roundtrip and N12 planned that package
anyway — N12 extends it instead of creating it.

Similarity is character-trigram Jaccard, not word overlap: the plugin is
language-general and inflection (migrere/migreringen) breaks word tokens.
Calibrated on real paraphrase pairs — retellings 0.44-0.55, unrelated 0.04-0.06,
same-topic-different-story 0.24 — so the default threshold sits in the gap at
0.40. Word-Jaccard scored a shortened hook paraphrase at 0.11.

TDD (Iron Law): tests written first and verified red before implementation, for
both the new package and the bank logging.

Suites: editions 27/0 (new suite line + guard, floor 27) · specifics-bank
28 -> 45 · test-runner 163 -> 173 (Section 16r: 9 unconditional greps +
non-vacuity self-test; anti-erosion floor 146 -> 155) · trends 300/0 ·
brain 134/0 · hooks 140/0 · tests 35/0 · render 60/0. tsc --noEmit clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QxvWAjte7vPcF79QeSRvRJ
2026-07-25 06:29:01 +02:00
..
src feat(linkedin-studio): N11 — serie-destillat + skjelett-sjekk + specifics-bank bruks-logging [skip-docs] 2026-07-25 06:29:01 +02:00
tests feat(linkedin-studio): N11 — serie-destillat + skjelett-sjekk + specifics-bank bruks-logging [skip-docs] 2026-07-25 06:29:01 +02:00
package-lock.json feat(linkedin-studio): specifics-bank store — lived-specifics inventory (fix #2 slice 1) 2026-06-20 21:03:18 +02:00
package.json feat(linkedin-studio): specifics-bank store — lived-specifics inventory (fix #2 slice 1) 2026-06-20 21:03:18 +02:00
README.md feat(linkedin-studio): per-edition lived-specifics binding + kilder artifact (fix #2 slice 2) 2026-06-20 21:33:44 +02:00
tsconfig.json feat(linkedin-studio): specifics-bank store — lived-specifics inventory (fix #2 slice 1) 2026-06-20 21:03:18 +02:00

specifics-bank — the lived-specifics inventory (Fix #2, kilde-så-draft)

A deterministic, topic-tagged store of the operator's real, un-generatable raw material — the specific measured number, the named (anonymized-but-real) case, the thing that actually broke, the unfashionable opinion privately held, the moment they changed their mind. The point (retning §3, dream-spec «Lived-Specifics Extraction»): sourcing, not styling, is where authenticity is won. Drafts must draw from real inventory instead of inventing plausible filler, so every downstream voice gate polishes grounded content, not hollow content.

This is the store half (deterministic + tested). The elicitation half — a guided interview that refuses vague answers (escape hatches: mark a slot abstrakt or ekstern) — lives in the command layer (/linkedin:newsletter Step 1.5, slice 3). The bank never holds AI-generated lived experience; every content is the operator's own words.

Where the bank lives

${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/specifics-bank/specifics-bank.json

It sits in the per-user data dir (M0 data-path convention), outside the plugin install, so it survives plugin upgrades and reinstalls. It is the first concrete tributary of the broader profile-evolution / "second brain" architecture.

Setup

cd scripts/specifics-bank && npm install

Use

# Surface inventory the operator already has for an edition's topics
node --import tsx src/cli.ts query --tags "saksbehandling,produktivitet"

# Fold newly-elicited material back into the bank
node --import tsx src/cli.ts add --type number \
  --content "Saksbehandleren sparte to dager i uka." \
  --tags "saksbehandling,produktivitet" --source "seres/05"

# Inspect the whole bank
node --import tsx src/cli.ts list

Flags: --bank <path> overrides the default location; --json emits machine output for query/list. add defaults --verification to unverified (numbers stay guilty-until-checked — regel 6/7) and --source to manual.

--typenumber | named-case | what-broke | contrarian | mind-change | other.

Model

  • Dedupe key = sha256(normalizeContent(content)).slice(0,12) (lowercase + trimmed + whitespace-collapsed). Re-capturing the same material under a new edition's topic does not add a second entry — its topicTags are unioned in, enriching the record.
  • Query returns active specifics whose tags overlap, ranked by overlap (desc) then recency (capturedAt desc); archived material is kept but never offered.
  • Content is stored verbatim; only the id is normalized.

Per-edition binding (slice 2)

The bank is the global inventory; an edition binds its load-bearing claims to real material before any prose exists. That bridge lives here too:

  • binding.ts — the per-edition slot-map model + validateBinding, the «vaghet avvises» gate (a slot must be specific / abstrakt / ekstern; an unresolved slot, a dangling specific id, or an unjustified abstrakt → BLOCK; an unverified-number-backed slot → WARN per regel 6/7).
  • kilder.tsrenderKilder, the read-only NN-kilder.md sources artifact, rendered deterministically from the slot-map + the bank.
# Gate one edition's binding (exit 1 = BLOCK)
node --import tsx src/cli.ts validate-binding --edition "<abs>/linkedin/edition-state.json"
# Render the sources ledger
node --import tsx src/cli.ts render-kilder --edition "<abs>/linkedin/edition-state.json" --out "<abs>/linkedin/NN-kilder.md"

Design record + the Step 2 research re-scope spec: docs/fix2/slice2-binding.md. The elicitation interview that POPULATES the slot-map (Step 1.5) is slice 3.

Test

npm test     # node:test, deterministic (callers supply capturedAt)
npm run build # tsc — must stay clean