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:
parent
e162cdce38
commit
e69ea1f4c9
20 changed files with 2520 additions and 59 deletions
|
|
@ -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 (C1–C5), 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 (C1–C5)', () => {
|
||||
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'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
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 fact-reviewer fasit fixture.
|
||||
// Mirrors the structure-only discipline of fact-checker-fixture.test.mjs and
|
||||
// editorial-reviewer-fixture.test.mjs: this test asserts the SHAPE of the
|
||||
// fixture — the single axis (faktisk-korrekthet), the four checks (F1–F4), the
|
||||
// 🔴/🟡/🟢 risk sort, the six Del 4 (Security Champions) cases, the
|
||||
// direction-not-copy boundary, and the cold-reader / pivot-risk rationale.
|
||||
// Whether the agent's live verdicts actually reproduce the fasit is
|
||||
// [GATE]/[OPERATØR], never self-certified here.
|
||||
|
||||
const FIXTURE_PATH = fileURLToPath(
|
||||
new URL('../fixtures/fact-reviewer-cases.md', import.meta.url)
|
||||
);
|
||||
|
||||
const fixture = readFileSync(FIXTURE_PATH, 'utf8');
|
||||
|
||||
// The four checks: F1 verifiable claims · F2 quote precision · F3 number
|
||||
// attribution · F4 source quality.
|
||||
const CHECKS = ['F1', 'F2', 'F3', 'F4'];
|
||||
|
||||
// The 🔴/🟡/🟢 risk sort (the emoji are the safest assertion).
|
||||
const VERDICTS = ['🔴', '🟡', '🟢'];
|
||||
|
||||
describe('fact-reviewer fixture structure', () => {
|
||||
test('names the axis "faktisk-korrekthet"', () => {
|
||||
assert.ok(
|
||||
/faktisk-korrekthet/i.test(fixture),
|
||||
'fixture must name the axis "faktisk-korrekthet"'
|
||||
);
|
||||
});
|
||||
|
||||
test('documents all four checks (F1–F4)', () => {
|
||||
for (const check of CHECKS) {
|
||||
assert.ok(
|
||||
fixture.includes(check),
|
||||
`fixture must reference the check "${check}"`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('references the 🔴/🟡/🟢 risk sort', () => {
|
||||
for (const v of VERDICTS) {
|
||||
assert.ok(
|
||||
fixture.includes(v),
|
||||
`fixture must reference the risk verdict "${v}"`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('references the PASS/REWORK/BLOCK gate', () => {
|
||||
for (const gate of ['PASS', 'REWORK', 'BLOCK']) {
|
||||
assert.ok(
|
||||
fixture.includes(gate),
|
||||
`fixture must reference the gate decision "${gate}"`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('documents exactly 6 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('states the direction-not-copy boundary', () => {
|
||||
assert.ok(
|
||||
/direction, not rewritten copy/i.test(fixture),
|
||||
'fixture must state the direction-not-copy boundary'
|
||||
);
|
||||
});
|
||||
|
||||
test('documents the cold-reader / context-pollution principle', () => {
|
||||
assert.ok(
|
||||
/context pollution/i.test(fixture) && /framing-bias/i.test(fixture),
|
||||
'fixture must document the cold-reader / context-pollution / framing-bias principle'
|
||||
);
|
||||
});
|
||||
|
||||
test('records the pivot-premise-risk rationale', () => {
|
||||
assert.ok(
|
||||
/pivot/i.test(fixture),
|
||||
'fixture must record why the gate exists (pivot premise never met Step 5)'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
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 language-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 (norsk-språkkvalitet),
|
||||
// all five checks (L1–L5), the three severities, the six Del 4 cases that form
|
||||
// the gold standard, the direction-not-copy boundary, and the cold-reader /
|
||||
// context-isolation principle. 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/language-reviewer-cases.md', import.meta.url)
|
||||
);
|
||||
|
||||
const fixture = readFileSync(FIXTURE_PATH, 'utf8');
|
||||
|
||||
// The five checks of the one axis.
|
||||
const CHECKS = ['L1', 'L2', 'L3', 'L4', 'L5'];
|
||||
|
||||
// The single axis name (Norwegian, as the agent and the writing contract use it).
|
||||
const AXIS = 'norsk-språkkvalitet';
|
||||
|
||||
// The three-rung severity scale.
|
||||
const SEVERITIES = ['BLOCK', 'REWORK', 'NICE'];
|
||||
|
||||
describe('language-reviewer fixture structure', () => {
|
||||
test('names the judging axis (norsk-språkkvalitet)', () => {
|
||||
assert.ok(
|
||||
new RegExp(AXIS, 'i').test(fixture),
|
||||
`fixture must name the axis "${AXIS}"`
|
||||
);
|
||||
});
|
||||
|
||||
test('documents all five checks (L1–L5)', () => {
|
||||
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 the 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 principle', () => {
|
||||
assert.ok(
|
||||
/cold/i.test(fixture) &&
|
||||
/(context pollution|framing-bias)/i.test(fixture),
|
||||
'fixture must document the cold-reader / context-isolation principle ' +
|
||||
'(context pollution / framing-bias)'
|
||||
);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue