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:
parent
8c91c409cd
commit
af80150019
10 changed files with 304 additions and 3 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
71
config/positioning-brief.template.md
Normal file
71
config/positioning-brief.template.md
Normal 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]
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue