feat(linkedin-studio): N23 — positioning-brief-motor (generering + konsum) [skip-docs]

Persistert posisjonering som alle drafts bindes mot. Innplassert i
/linkedin:setup (Step 3g) etter operatør-friksjonskriteriet — setup eier
allerede ${DATA}/profile/ og har menyen; telling forblir 30.

- config/positioning-brief.template.md (NY): fire seksjoner (Who I am ·
  Who this is for · What I hold · How I differ), hver med _Evidence:_-linje.
- setup.md Step 3g: DELEGERER til linkedin-studio:strategy-advisor.
  Brain-konsum-blokken forblir i én kopi (N21-lærdommen), lint-håndhevet.
- strategy-advisor.md: distillasjonen, kildepresedens, anti-flatteri
  (What I hold må bære påstander en leser kan være UENIG i) og
  evidensgulv ([insufficient evidence] framfor plausibel oppfinnelse).
- Distillat, ikke andre profil: briefen REFERERER user-profile.md framfor
  å gjenta den. Operatørens deklarasjon og daterte anker slår maskinens
  inferens; motstrid rapporteres som spenning, aldri stille anvendt.
- Konsum: post/quick/batch + newsletter Step 1 + differentiation-checker
  (scorer originalitet mot How I differ, ikke generisk nyhet).
- Regenerering er operatør-gatet: explicit go + seksjonsvis diff + aldri
  stille overskriving av operatørens egne redigeringer.

Lint: Section 16y, 17 nye ubetingede sjekker (self-test 1 akseptert /
4 underspesifiserte avvist). Floor 251 → 268. Alle ti suiter grønne:
test-runner 270→287/0 · trends 300 · analytics 202 · hooks 191 · brain 134
· editions 72 · render 63 · specifics-bank 45 · tests 35 · contract-gate 33.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AbwkH8phPsgv9xFd8Qbq7E
This commit is contained in:
Kjell Tore Guttormsen 2026-07-31 21:01:55 +02:00
commit af80150019
10 changed files with 304 additions and 3 deletions

View file

@ -12,6 +12,7 @@ Full-spectrum LinkedIn content engine — short-form feed posts, carousels, vide
- **Post queue:** `assets/drafts/queue.json` (managed by `hooks/scripts/queue-manager.mjs`)
- **Analytics:** CLI `scripts/analytics/` (TypeScript, needs `tsx` + `npm install`); data `assets/analytics/` (gitignored)
- **Specifics-bank:** `scripts/specifics-bank/` (TypeScript, needs `tsx` + `npm install`) — deterministic, topic-tagged store of the operator's lived specifics (real numbers, named cases, held opinions) in the per-user data dir; elicited/bound by `/linkedin:newsletter` Step 1.5 so drafts draw from real inventory, never invented filler. Consumption is logged back at Step 8 lock (`record-usage``usedIn`), so cross-edition re-use of the same material is visible rather than silently encouraged by the dedupe
- **Positioning brief (N23):** `${DATA}/profile/positioning-brief.md` (template `config/positioning-brief.template.md`) — the stance-and-difference layer every drafting surface reads before it writes: **Who I am · Who this is for · What I hold · How I differ**, each carrying an `_Evidence:_` line. It is a **distillate, not a second profile**: `profile/user-profile.md` stays the operator's *declared* identity and the brief **references** it rather than restating it — two files asserting the same fact is how positioning forks. Generated by `/linkedin:setup` **Step 3g**, which *delegates* to `linkedin-studio:strategy-advisor` (the SB-S3a brain readers live there in one copy; the command never copies the block). Precedence is declaration-over-inference: the operations anchor and the declared profile outrank the distillation, and a contradiction is reported as a tension, never silently applied. Consumed by `post`/`quick`/`batch`, `newsletter` Step 1 and `differentiation-checker` (which scores originality against **How I differ** rather than generic novelty). **Regeneration is operator-gated** — explicit go, section-by-section diff, never a silent overwrite of the operator's own edits; a section without evidence is marked `[insufficient evidence]`, never filled with a plausible invention. Absent file ⇒ every surface proceeds silently
- **Series distillate:** `scripts/editions/` (TypeScript, needs `tsx` + `npm install`) — series-level memory: each locked edition's spent narrative units (anecdotes/arguments/hooks) in `<serie>/linkedin/series-distillate.json`; written at `/linkedin:newsletter` Step 8 (`distil-append`), checked against the next skeleton at Step 2.5 (`distil-check`, advisory). Deterministic character-trigram similarity — AI extracts, code compares
- **Editions register:** `scripts/editions/` (same package) — one row per edition in production in the per-user data dir (`editions/register.json`): series, edition, path, current phase, next action, slot, `startedAt`/`completedAt`. Each of the 17 canonical `/linkedin:newsletter` phase transitions runs `register-upsert --edition-state <path>`, which appends `articles.NN.phaseLog` (lead-time telemetry, additive — schemaVersion stays 1) **and** mirrors the row in one call; Step 10 runs `register-complete`. `register-list` is the work-in-progress view. The register is a **mirror** — deterministic resumption reads `edition-state.json` only, and a lost register is rebuilt by the next transition
- **Publishing slots:** `hooks/scripts/slots.mjs` — the weekly production grid. Slots are **operator config** in the per-user data dir (`profile/publishing-slots.json`, schema + opt-in template in `config/publishing-slots.template.json`); absent config ⇒ every slot surface stays silent, and the plugin hardcodes no publishing times. Coverage of a slot counts the short-form queue (`scheduled`/`published`) **and** editions-register rows claiming that date, so long-form and short-form cannot be double-booked. Zero-dep (SessionStart may not spawn tsx) and imported identically by the hook and by the commands (`queue-manager.mjs` pattern), so session-start's vacancy warning and `/linkedin:newsletter` Step 10's slot default are the same computation

View file

@ -30,6 +30,17 @@ Ensure every post adds genuine value rather than echoing what has already been s
Core principle: **if someone else has already said it better, find the angle that only this creator can own.**
**What "only this creator can own" means is not yours to guess.** Read
`${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/positioning-brief.md`
before scoring. Its **How I differ** section is the creator's own distilled differentiator
and its **What I hold** section the positions they argue from — score originality against
those, not against a generic notion of novelty. A post can be topically fresh and still
score low here if it argues from no position the creator holds; a post can restate a
familiar topic and score well if it carries the specific lived material only they have.
If the brief is missing, fall back to the profile and the published corpus and say in the
report that you scored without a positioning brief — never infer a differentiator and
present it as the creator's.
## Similarity Search Process
### Step 1: Extract Core Claims

View file

@ -75,6 +75,33 @@ The operations centre is the user-authored other half of the second brain (the b
**Graceful absence:** If `brain/operations.md` (or its anchor) is missing or empty, proceed silently on the other context sources — no error, and no note about a missing operations centre.
### Distilling the positioning brief
Invoked by `/linkedin:setup` Step 3g. You produce the **positioning brief** — the distilled answer to the four questions a draft cannot answer for itself — and write it to:
```
${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/positioning-brief.md
```
Shape it on `${CLAUDE_PLUGIN_ROOT}/config/positioning-brief.template.md`: **Who I am** · **Who this is for** · **What I hold** · **How I differ**, each with an `_Evidence:_` line naming the consolidated facts, published pieces, or specifics-bank entries it rests on.
**Sources, in precedence order:**
1. `brain/operations.md` — the dated `## Who I am now` anchor (user-declared direction)
2. `profile/user-profile.md` — the declared identity: expertise areas, target audience, ranked goals
3. `brain/profile.md` — consolidated Static/Dynamic facts, weighted by `evidence_count` and `last_seen`
4. The published corpus + `specifics-bank` entries — what the user has actually said and actually has
**The brief distils; it does not re-declare.** Where `user-profile.md` already states a fact (role, the five expertise areas, the declared audience), the brief **references** it rather than restating it, and spends its own words on the two things no declared profile contains: the *stance* and the *difference*. **Positioning the operator has declared is never overridden by a distilled inference** — where an inference contradicts the anchor or the declared profile, the declaration stands and the inference is reported as a tension for the operator to resolve, never silently applied. This is the frozen-past-self guard extended from facts to positioning.
**Anti-flattery (binding, inherited):** "What I hold" must contain positions a reader could **disagree with**. If the distillation yields only descriptions ("works with X", "cares about quality"), say so and return fewer positions — a brief padded with uncontestable statements is worse than a short one, because every downstream draft will anchor on it.
**Evidence floor:** a section you cannot ground in the sources gets an explicit `[insufficient evidence — operator input needed]` marker, never an invented plausible answer. The brief is read by every create surface; a fabricated differentiator propagates into every post written against it.
**Regeneration is operator-gated.** When a brief already exists you **never overwrite** it without an **explicit go**: present a section-by-section **diff** of the proposed distillation against the current file (including any operator edits), and write only what the operator approves. The operator's own edits outrank your distillation by default.
**Graceful absence:** if `brain/profile.md` and the published corpus are both empty there is **no brief yet** — say that the second brain has nothing to distil and point to `/linkedin:setup` voice-samples and profile steps first. Never scaffold a brief out of the template's placeholders.
### New Creator Advantage Detection
From the state file, extract `first_post_date`. Calculate the creator window status:

View file

@ -31,6 +31,7 @@ Load state and personalization:
- Read `${CLAUDE_PLUGIN_ROOT}/skills/linkedin-studio/SKILL.md` for profile and preferences
- Check `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/plans/` for existing weekly plan
- Read `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/templates/my-post-templates.md` for proven templates — vary templates across the batch for format diversity
- Read `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/positioning-brief.md` — the week's posts must add up to one position, not five unrelated takes. Use **What I hold** to keep the batch arguing from the same stance, and **How I differ** as the check on the plan as a whole: if the week could have been produced by any account in the niche, revise the plan before writing. Absent file: proceed silently
If a plan exists for this week, use it as the foundation. If not, create one first.

View file

@ -341,6 +341,16 @@ Establish the edition brief with **at most ~3 calibration questions**. Infer
everything you can from Step 0 (series brief, STATE.md, prior edition); only ask
what genuinely changes the work.
**Read the positioning brief first** —
`${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/positioning-brief.md`.
A long-form edition is the format where a missing position shows most: 1,500 words of
competent survey that takes no stance is the failure mode. Use **What I hold** to fix
what this edition *argues* (not merely covers) and **How I differ** as the brief-level
test — if the edition as briefed could carry another author's byline unchanged, the
angle is not yet an angle. Where the brief and the operator's stated brief for THIS
edition disagree, the operator wins; note the tension in one line rather than quietly
steering to the file. Absent file: proceed silently on the other sources.
> **Trend intake (optional bridge — N7).** If this edition is being started from a
> `/linkedin:trends` candidate — the operator supplies a **trend id** (shown in the
> trends brief and via `CLI list --json`, where

View file

@ -45,6 +45,7 @@ Check weekly progress:
Check for existing assets:
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/` - Match the user's natural voice
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/positioning-brief.md` - What the user stands for and how they differ. Write the angle AGAINST this: the "How I differ" section is the test for whether this post could have been written by anyone else on the topic. Absent file: proceed silently (nothing to anchor on yet).
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/examples/high-engagement-posts.md` - Study past successful posts and replicable patterns
- `${CLAUDE_PLUGIN_ROOT}/assets/frameworks/framework-template.md` - Reference user's documented frameworks for framework posts
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/templates/my-post-templates.md` - User's proven post templates with success rates. **Prefer these over generic structures.**

View file

@ -37,6 +37,12 @@ Read `${CLAUDE_PLUGIN_ROOT}/skills/linkedin-studio/SKILL.md` for:
- Core expertise areas (for topical alignment)
- Phrases they commonly use
Read `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/positioning-brief.md` for:
- **What I hold** — the positions to argue from, so a 5-minute post still says something the
user actually believes rather than something generically true
- **How I differ** — the one-line test before you present the draft: could any other account
in this niche have posted this? Absent file: proceed silently.
Read `${CLAUDE_PLUGIN_ROOT}/assets/quick-post-resources.md` for:
- Hooks bank
- CTAs bank

View file

@ -67,7 +67,7 @@ Use AskUserQuestion:
**What would you like to set up?**
Options (always show all 7):
Options (always show all 8):
1. **Voice samples** -- Paste 3-5 of your best posts so I can analyze your writing voice
2. **Case study** -- Walk through a guided interview to document a real case study
@ -75,7 +75,8 @@ Options (always show all 7):
4. **Post analysis** -- Add your high-engagement posts with metrics for pattern analysis
5. **Demographics** -- Guide you through LinkedIn Analytics to capture audience demographics
6. **User profile** -- Set up your personalization profile (name, expertise, goals, voice)
7. **Show score details** -- See detailed breakdown of what's missing in each category
7. **Positioning brief** -- Distil what you stand for and how you differ, so every draft is written against it
8. **Show score details** -- See detailed breakdown of what's missing in each category
Based on their answer, run the corresponding sub-workflow below.
@ -346,6 +347,29 @@ Guide through each section of the profile:
**Important:** This file is gitignored (`.local.md` pattern), so personal data stays private.
## Step 3g: Positioning Brief
**Goal:** Distil `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/positioning-brief.md` — the stance-and-difference layer every drafting surface reads before it writes.
This step **delegates**. Do not read the second brain yourself and do not restate its consumption rules here: invoke the agent that already owns them.
1. **Check what exists first.** Read the current `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/positioning-brief.md` if present, and note whether it carries operator edits (`## Provenance` → "Operator edits since generation").
2. **Invoke the distiller** via `Task` with `subagent_type: linkedin-studio:strategy-advisor`, asking it to distil the positioning brief. It reads `brain/profile.md`, `brain/operations.md`, `profile/user-profile.md` and the published corpus, and shapes the result on `${CLAUDE_PLUGIN_ROOT}/config/positioning-brief.template.md`.
3. **First run (no brief exists):** show the four distilled sections, ask for corrections, then write the file.
4. **Regeneration (a brief already exists):** this is **operator-gated**.
- Present a **diff**, section by section, of the proposed distillation against the current file.
- Ask for an **explicit go** per section — "keep mine / take the new one / merge".
- **Never overwrite** the existing brief wholesale, and never discard an operator edit because the distillation disagrees with it. Positioning is the operator's own; the machine's read of it is a proposal.
5. **Thin evidence is reported, not filled.** Sections the agent marks `[insufficient evidence — operator input needed]` stay marked. Offer to interview the operator for that section instead — an invented differentiator would propagate into every post written against the brief.
6. **If there is no brain yet** the agent will say there is **no brief yet** — point the user at options 1 (voice samples) and 6 (user profile) first, and exit this step without writing a placeholder file.
**Important:** This file lives in the per-user data dir, survives reinstalls, and is read by `/linkedin:post`, `/linkedin:quick`, `/linkedin:batch`, `/linkedin:newsletter` Step 1 and the `differentiation-checker`.
## Step 4: Recalculate Score
After completing any sub-workflow:

View file

@ -0,0 +1,71 @@
# Positioning Brief
> **What this file is.** A distillate — the second brain's evidence-weighted read of your
> positioning, written by `/linkedin:setup` (Step 3g) and consumed by every drafting
> surface. It answers four questions a draft cannot answer for itself: who is writing,
> who it is for, what they actually hold, and why it is not interchangeable with the
> other twenty posts on the topic that week.
>
> **What this file is not.** It **does not restate** `user-profile.md`. That file is your
> *declared* identity — name, role, the five expertise areas, the target audience, the
> ranked goals — filled in by you and authoritative as such. This file is the *distilled*
> layer above it: the stance and the difference, inferred from what you have actually
> published and consolidated into `brain/profile.md`. When the two disagree, the declared
> profile and your dated `## Who I am now` anchor win, and the inference is the thing that
> gets corrected. Two files asserting the same fact is how positioning quietly forks.
>
> **Editing.** This file is yours to edit directly. Regeneration never overwrites it
> silently: `/linkedin:setup` shows a diff and asks for an explicit go first.
---
## Who I am
[One or two sentences. The working identity a reader would recognise across your posts —
not your job title. Distilled from `brain/profile.md` Static facts and the operations
anchor; grounded in what you have published, not in what you aspire to.]
_Evidence:_ [which consolidated facts or published pieces support this]
---
## Who this is for
[The reader you are actually writing to — the one whose problem your posts solve. Narrower
than the declared target audience if the published record says so; say which, and why.]
_Evidence:_ [engagement patterns, recurring commenters, the questions you actually get]
---
## What I hold
[The two to four positions you argue from — the opinions a reader could disagree with. A
position that no one could disagree with is a description, not a stance, and does not
belong here.]
1. [Position — stated as a claim, not a topic]
2. [Position]
3. [Position]
_Evidence:_ [posts or editions where you took this position explicitly]
---
## How I differ
[Why this account and not the generic account on the same topic. Concrete: the lived
material only you have — the named cases, the real numbers, the work you actually did.
"More practical" and "more authentic" are not differentiators; the specific thing you can
say that a competitor cannot is.]
_Evidence:_ [specifics-bank entries, case studies, frameworks you authored]
---
## Provenance
- **Generated:** [YYYY-MM-DD] by `/linkedin:setup` Step 3g
- **Sources:** `brain/profile.md` (consolidated) · `brain/operations.md` (anchor) ·
`profile/user-profile.md` (declared) · published corpus
- **Operator edits since generation:** [none / describe]

View file

@ -2930,6 +2930,155 @@ fi
echo ""
# --- Section 16y: Positioning Brief - generation, consumption, operator gate (N23 / E#4) ---
echo "--- Positioning Brief: distillation + create-surface consumption + regeneration gate (N23) ---"
# A draft is only as anchored as the positioning it is written against. The brief is a
# DISTILLATE of the second brain, not a second copy of the declared profile, and three
# disciplines carry the whole feature - each of which fails silently if eroded:
# (E#4 separation) user-profile.md stays the operator's DECLARED identity; the brief is
# the evidence-weighted distillate. Two files restating the same claim
# is the N22 defect class (conflicting numbers across surfaces), so the
# brief must DEFER to the profile and to the operations anchor.
# (E#4 consumption) a brief nothing reads is a file, not an engine. The create surfaces
# and newsletter Step 1 must read it the way they read voice-samples.
# (E#4 gate) positioning is the operator's own. Regeneration is operator-gated:
# an explicit go, a visible diff, and never a silent overwrite. This is
# the check most likely to be "simplified" away, because a confirmation
# prompt reads as friction rather than the guarantee it is.
# (anti-drift) the brain-consumption rules stay in ONE file (strategy-advisor). The
# hosting command DELEGATES; it does not copy the block. Six copies of
# the standard skeleton (N21) is exactly what that copy would become.
BRIEF_PATH_N23="profile/positioning-brief.md"
SETUP_N23="commands/setup.md"
ADVISOR_N23="agents/strategy-advisor.md"
TMPL_N23="config/positioning-brief.template.md"
DIFFCHK_N23="agents/differentiation-checker.md"
NEWS_N23="commands/newsletter.md"
gate_honest() { # $1 = text; honest iff it REQUIRES a go, SHOWS the change, and FORBIDS a silent overwrite
# -qiF, not -qF: these are concepts in running prose, where sentence position decides the
# casing. Case-pinning would force the prose to fit the lint instead of the other way round.
echo "$1" | grep -qiF "explicit go" \
&& echo "$1" | grep -qiF "never overwrite" \
&& echo "$1" | grep -qiF "diff"
}
GATE_SELFTEST_N23=1
if ! gate_honest "regeneration requires an explicit go: show the diff first and never overwrite the existing brief without it"; then
GATE_SELFTEST_N23=0; echo " non-vacuity FAIL: a fully-gated regeneration probe was not detected"
fi
while IFS= read -r probe; do
[ -z "$probe" ] && continue
if gate_honest "$probe"; then
GATE_SELFTEST_N23=0; echo " false-positive FAIL: under-specified gate probe accepted -> $probe"
fi
done <<'NEGATIVE16Y'
regenerate the brief from the brain and overwrite it with the new distillation
ask the operator before regenerating, then write the new brief over the old one
show the diff and regenerate on confirmation, replacing the previous brief
an explicit go is required before regenerating the positioning brief
NEGATIVE16Y
if [ "$GATE_SELFTEST_N23" -eq 1 ]; then
pass "regeneration-gate self-test: predicate needs explicit-go + diff + no-silent-overwrite (1 accepted, 4 under-specified rejected)"
else
fail "regeneration-gate self-test failed - the N23 gate lint is vacuous or over-eager"
fi
# (E#4 generation) the template exists and carries the four positioning questions
if [ -f "$TMPL_N23" ] \
&& grep -qF "## Who I am" "$TMPL_N23" 2>/dev/null \
&& grep -qF "## Who this is for" "$TMPL_N23" 2>/dev/null \
&& grep -qF "## What I hold" "$TMPL_N23" 2>/dev/null \
&& grep -qF "## How I differ" "$TMPL_N23" 2>/dev/null; then
pass "positioning-brief template carries all four positioning sections (E#4)"
else
fail "$TMPL_N23 missing or incomplete - the brief has no declared shape (E#4)"
fi
# (E#4 separation) the template states it is a distillate that defers, not a second profile
if grep -qF "does not restate" "$TMPL_N23" 2>/dev/null \
&& grep -qF "user-profile.md" "$TMPL_N23" 2>/dev/null; then
pass "template records that the brief defers to the declared profile instead of restating it"
else
fail "$TMPL_N23 does not separate distillate from declared profile - two sources of positioning truth (N22 class)"
fi
# (E#4 generation) setup hosts the generator as its own step and names the target path
if grep -qF "## Step 3g: Positioning Brief" "$SETUP_N23" 2>/dev/null \
&& grep -qF "$BRIEF_PATH_N23" "$SETUP_N23" 2>/dev/null; then
pass "setup Step 3g generates the brief to the per-user data dir (E#4)"
else
fail "$SETUP_N23 has no Step 3g writing $BRIEF_PATH_N23 - the generator has no surface (E#4)"
fi
# (E#4 generation) the step is reachable: the Step 2 menu offers it
if grep -qF "Positioning brief" "$SETUP_N23" 2>/dev/null \
&& grep -qF "always show all 8" "$SETUP_N23" 2>/dev/null; then
pass "setup Step 2 menu offers the positioning brief (8 options) - the step is reachable"
else
fail "$SETUP_N23 Step 2 menu does not offer the brief - Step 3g is unreachable (E#4)"
fi
# (anti-drift) setup DELEGATES the distillation instead of copying the brain-consumption block
if grep -qF "linkedin-studio:strategy-advisor" "$SETUP_N23" 2>/dev/null; then
pass "setup delegates the distillation to the namespaced strategy-advisor (no copied brain block)"
else
fail "$SETUP_N23 does not delegate to linkedin-studio:strategy-advisor - the brain rules will be copied (N21 class)"
fi
# (anti-drift) the brain-consumption rules stay in exactly one file
BRAINDOC_N23=$(grep -lF "Consuming the evolving profile" agents/*.md commands/*.md 2>/dev/null | wc -l | tr -d ' ')
if [ "$BRAINDOC_N23" = "1" ]; then
pass "brain-consumption rules live in exactly 1 file - no second copy to drift (SB-S3a)"
else
fail "brain-consumption rules found in $BRAINDOC_N23 files - a copy will drift from the original (N21 class)"
fi
# (E#4 generation) the advisor owns the distillation and writes the four sections
if grep -qF "Distilling the positioning brief" "$ADVISOR_N23" 2>/dev/null \
&& grep -qF "$BRIEF_PATH_N23" "$ADVISOR_N23" 2>/dev/null; then
pass "strategy-advisor owns the positioning distillation (reuses its brain readers)"
else
fail "$ADVISOR_N23 has no positioning-brief distillation - setup would have to reimplement it (E#4)"
fi
# (E#4 separation) the distillation defers to the operator's own anchor over an older inference
if grep -qF "Who I am now" "$ADVISOR_N23" 2>/dev/null \
&& grep -qiF "operator has declared is never overridden by a distilled inference" "$ADVISOR_N23" 2>/dev/null; then
pass "distillation subordinates itself to the operator's dated anchor (frozen-past-self guard extends to positioning)"
else
fail "$ADVISOR_N23 may let a machine distillate outrank the operator's own declaration (E#4)"
fi
# (E#4 gate) regeneration is operator-gated on BOTH surfaces that can trigger it
for f_n23 in "$SETUP_N23" "$ADVISOR_N23"; do
if gate_honest "$(cat "$f_n23" 2>/dev/null)"; then
pass "$(basename "$f_n23"): regeneration is gated (explicit go + diff + no silent overwrite)"
else
fail "$f_n23 may regenerate the brief without an explicit go, a diff, or without preserving the old one (E#4)"
fi
done
# (E#4 consumption) the create surfaces and newsletter Step 1 read the brief, traceably
for f_n23 in commands/post.md commands/quick.md commands/batch.md "$NEWS_N23" "$DIFFCHK_N23"; do
if grep -qF "$BRIEF_PATH_N23" "$f_n23" 2>/dev/null; then
pass "$(basename "$f_n23") reads the positioning brief (E#4 consumption)"
else
fail "$f_n23 does not read $BRIEF_PATH_N23 - the brief steers nothing there (E#4)"
fi
done
# (E#4 consumption) absence is graceful: a fresh install has no brain and no brief
if grep -qF "no brief yet" "$ADVISOR_N23" 2>/dev/null \
|| grep -qF "no brief yet" "$SETUP_N23" 2>/dev/null; then
pass "an absent brief degrades silently (fresh installs have no brain to distil)"
else
fail "absent-brief handling is undocumented - a fresh install would error or nag (E#4)"
fi
echo ""
# --- Section 18: Assertion-Count Anti-Erosion (SC6) ---
# The lint self-modifies its own checks, so a green run could mask a silently dropped
# assertion. Pin the total pass()+fail() invocations as a monotonic floor; the count
@ -3015,7 +3164,7 @@ echo ""
# the count), so it is bumped only by UNCONDITIONAL new checks — NOT pinned to the
# deps-present TOTAL_CHECKS (that would zero the warn-skip margin and false-fail a fresh
# clone). Runs last so TOTAL_CHECKS sees every prior check.
ASSERT_BASELINE_FLOOR=251
ASSERT_BASELINE_FLOOR=268
TOTAL_CHECKS=$((PASS + FAIL))
if [ "$TOTAL_CHECKS" -ge "$ASSERT_BASELINE_FLOOR" ]; then
pass "assertion-count anti-erosion: $TOTAL_CHECKS checks >= baseline floor $ASSERT_BASELINE_FLOOR"