feat(linkedin-studio): v3.1.0 — Endring 9 adversarial review-pakke + per-artefakt personas

Cold, adversarial review package for the long-form pipeline + configurable
per-edition personas. Motivated by Del 4 (Security Champions pivot): the
in-session editor + persona sweep shared the drafting session's framing-bias,
so the shipped version was never independently re-reviewed.

Headless package (9a/9b):
- New Step 6.5 (headless-review) in /linkedin:newsletter, after the persona
  sweep, before lock — the independence layer the in-session gates can't be.
- New standalone /linkedin:headless-review command (run in a fresh session for
  maximum isolation; reconstructs frozen draft + contract + personas from disk).
- 3 new Opus archetypes, each with a cardinal context-isolation block that
  refuses drafting-session framing as "context pollution":
  - content-reviewer (argument integrity C1–C5, ≤8 flags)
  - language-reviewer (Norwegian language L1–L5, ≤10 flags)
  - fact-reviewer (cold re-verification F1–F4, risk-sort + pivot-risk, WebSearch)
- Deliberate redundancy with fact-checker / editorial-reviewer documented so
  the pairs are never de-duplicated.

Pivot-reopen (9c):
- New /linkedin:pivot command: logs articles.NN.pivots[], resets currentPhase,
  un-locks, marks gates to re-run.
- Pivot-detection gate in Step 8 lock precondition (>20% word-count change or
  >2 new sections re-opens cleared gates). Del 4 v8→v11 worked example.

Per-artifact personas (new requirement):
- articles.NN.personas with resolution order (edition-state → series file →
  plugin library → interactive). One or more readers configurable per edition.

Schema/docs:
- edition-state.template.json: additive personas[], pivots[], headlessReview,
  headless-review phase (16 phases); personaSweep.resonance.wordCount baseline.
- 3 fasit fixtures + 3 structural lint tests (Del 4 worked cases).
- Counts: 24→26 commands, 16→19 agents, 15→16 newsletter phases.
- README + CLAUDE.md (plugin + root) + CHANGELOG synced.

Verification: 35 agent-fixture + 59 hook + 20 render tests green. Backward-
compatible (additive state); reload required before the 3 new agents resolve.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-05-29 13:01:24 +02:00
commit e69ea1f4c9
20 changed files with 2520 additions and 59 deletions

View file

@ -0,0 +1,82 @@
import { describe, test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
// Lint-test for the content-reviewer fasit fixture.
// Mirrors the structure-only discipline of editorial-reviewer-fixture.test.mjs,
// persona-reviewer-fixture.test.mjs, and fact-checker-fixture.test.mjs: this test
// asserts the SHAPE of the fixture — the one judging axis (argument-integritet),
// all five checks (C1C5), the three severities, the six Del 4 cases, the
// direction-not-copy boundary, and the cold-reader / context-isolation rationale.
// Whether the agent's live flags actually reproduce the fasit directions is
// [GATE]/[OPERATØR], never self-certified here.
const FIXTURE_PATH = fileURLToPath(
new URL('../fixtures/content-reviewer-cases.md', import.meta.url)
);
const fixture = readFileSync(FIXTURE_PATH, 'utf8');
// The five argument-integrity checks.
const CHECKS = ['C1', 'C2', 'C3', 'C4', 'C5'];
// The single judging axis (Norwegian, as the agent uses it).
const AXIS = 'argument-integritet';
// The three-rung severity scale.
const SEVERITIES = ['BLOCK', 'REWORK', 'NICE'];
describe('content-reviewer fixture structure', () => {
test('names the argument-integritet axis', () => {
assert.ok(
new RegExp(AXIS, 'i').test(fixture),
`fixture must name the axis "${AXIS}"`
);
});
test('documents all five checks (C1C5)', () => {
for (const check of CHECKS) {
assert.ok(
fixture.includes(check),
`fixture must reference the check "${check}"`
);
}
});
test('defines the three-rung severity scale', () => {
for (const sev of SEVERITIES) {
assert.ok(
fixture.includes(sev),
`fixture must define the severity "${sev}"`
);
}
});
test('documents exactly six Del 4 cases', () => {
const cases = fixture.match(/^###\s+Case\s+\d+\b/gim) || [];
assert.equal(
cases.length,
6,
`fixture must document exactly 6 Del 4 cases (found ${cases.length})`
);
});
test('keeps the jury-judges-writer-writes boundary (direction, not copy)', () => {
assert.ok(
/direction, not rewritten copy/i.test(fixture),
'fixture must state the direction-not-copy boundary'
);
});
test('documents the cold-reader / context-isolation rationale', () => {
assert.ok(
/context pollution/i.test(fixture),
'fixture must document the context-isolation principle (context pollution)'
);
assert.ok(
/cold/i.test(fixture),
'fixture must describe the agent as a cold reader'
);
});
});