linkedin-studio/scripts/specifics-bank
Kjell Tore Guttormsen 33f18c35e6 feat(linkedin-studio): per-edition lived-specifics binding + kilder artifact (fix #2 slice 2)
Slice 2 of fix #2 (kilde-så-draft): the per-edition bridge between the global
specifics-bank (slice 1) and a draft. In scripts/specifics-bank:

- binding.ts — livedSpecifics slot-map model + validateBinding, the «vaghet
  avvises» gate (G4). BLOCK on unresolved / dangling specific-id / unjustified
  abstrakt; WARN on an unverified-number-backed slot (regel 6/7); PASS otherwise.
- kilder.ts — renderKilder, the read-only NN-kilder.md sources ledger, rendered
  deterministically from the slot-map + bank (coverage via validateBinding, so
  the artifact and the skeleton-gate cannot disagree).
- cli.ts — validate-binding (exit 1 = BLOCK) + render-kilder subcommands.

edition-state schema gains articles.NN.livedSpecifics ({ slots, status }) + _doc.
Design record + the Step 2 research re-scope spec: docs/fix2/slice2-binding.md.

Wiring into newsletter.md (Step 1.5 elicitation + Step 2.5 gate + pipeline 17->18)
is slice 3 — not in this commit.

Tests 28/28 (specifics-bank; +9 binding +4 kilder), tsc clean, plugin gate 83/0/0
unaffected, gitleaks clean on all 8 files.

[skip-docs] internal plumbing — no command/agent/pipeline surface change until
slice 3 wires it; documenting it in the user-facing root CLAUDE.md/README now would
overclaim unreachable functionality. Docs ARE updated where they belong: the design
record docs/fix2/slice2-binding.md, scripts/specifics-bank/README.md, and the
edition-state _doc. Root-doc update lands with the slice-3 wiring feat.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 21:33:44 +02:00
..
src feat(linkedin-studio): per-edition lived-specifics binding + kilder artifact (fix #2 slice 2) 2026-06-20 21:33:44 +02:00
tests feat(linkedin-studio): per-edition lived-specifics binding + kilder artifact (fix #2 slice 2) 2026-06-20 21:33:44 +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