feat(linkedin): v2.4.0 — editorial-reviewer agent + Step 5.5 craft gate in /linkedin:newsletter
Endring 8 from the change spec (Del 4 production, Maskinrommet). The persona resonance sweep measures reader-response (does it land?); nothing measured prose craft or narrative architecture (is it well-made?). In Del 4 every persona reported PASS, yet the editor found 8 fresh editorial points on first reading — ~6/8 craft/architecture blind spots no agent could see. v2.4.0 adds the missing editor role. New Step 5.5 (editorial-review) runs between fact-check (Step 5) and the persona sweep (Step 6): a new editorial-reviewer agent (Opus) judges two axes — prosa-handverk (em-dash density, verbatim repetition, postulated numbers, contradictions, versal-tic) + narrativ-arkitektur (concrete instantiation, theory-anchored hypotheses, series-title symmetry, equal action per addressee, un-overloaded conclusion). Returns <=10 flags as direction (never copy), each BLOCK/REWORK/NICE, operator-gated via SendUserFile. Runs before the persona sweep so the personas measure resonance instead of stumbling on craft noise. Mirrors the Maskinrommet writing-contract section C2 (bidirectional mirror rule). - agents/editorial-reviewer.md (NEW, Opus, orange) + fasit fixture (editorial-reviewer-cases.md: Del 4 v5 gold standard, 8 points -> 2 axes + severities, 3 BLOCK / 5 REWORK, 6/8 blind spots) + structural lint (7 tests). - Step 5.5 wired into commands/newsletter.md; pipeline 14 -> 15 phases. - editorial-review phase + additive editorialReview state in config/edition-state.template.json; resumption: factcheck-sweep -> Step 5.5, editorial-review -> Step 6 (spec said fact-check; canonical key is factcheck-sweep). - persona-reviewer contract unchanged: editorial-reviewer is supplementary (one measures craft, one measures response). - All doc levels synced (plugin + root README/CLAUDE.md, CHANGELOG, plugin.json 2.3.0 -> 2.4.0; agents 15 -> 16). 94 tests green. Acceptance-criterion #8 (live run on Del 4 v5) delivered as fasit fixture: a live run needs a session reload (new agent not invokable until then) + read access to the Del 4 v5 draft in Maskinrommet. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7ebd28cb0b
commit
9df3de795c
11 changed files with 714 additions and 28 deletions
|
|
@ -0,0 +1,87 @@
|
|||
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 editorial-reviewer fasit fixture.
|
||||
// Mirrors the structure-only discipline of persona-reviewer-fixture.test.mjs and
|
||||
// fact-checker-fixture.test.mjs: this test asserts the SHAPE of the fixture —
|
||||
// the two judging axes, all ten checks (P1–P5 + A1–A5), the three severities,
|
||||
// and the eight Del 4 cases that form the gold standard. 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/editorial-reviewer-cases.md', import.meta.url)
|
||||
);
|
||||
|
||||
const fixture = readFileSync(FIXTURE_PATH, 'utf8');
|
||||
|
||||
// The ten checks: five prose-craft (P1–P5) + five narrative-architecture (A1–A5).
|
||||
const PROSE_CHECKS = ['P1', 'P2', 'P3', 'P4', 'P5'];
|
||||
const ARCH_CHECKS = ['A1', 'A2', 'A3', 'A4', 'A5'];
|
||||
|
||||
// The two axis names (Norwegian, as the agent and the writing contract use them).
|
||||
const AXES = ['prosa-håndverk', 'narrativ-arkitektur'];
|
||||
|
||||
// The three-rung severity scale.
|
||||
const SEVERITIES = ['BLOCK', 'REWORK', 'NICE'];
|
||||
|
||||
describe('editorial-reviewer fixture structure', () => {
|
||||
test('names both judging axes', () => {
|
||||
for (const axis of AXES) {
|
||||
assert.ok(
|
||||
new RegExp(axis, 'i').test(fixture),
|
||||
`fixture must name the axis "${axis}"`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('documents all ten checks (P1–P5 + A1–A5)', () => {
|
||||
for (const check of [...PROSE_CHECKS, ...ARCH_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 eight Del 4 cases', () => {
|
||||
const cases = fixture.match(/^###\s+Case\s+\d+\b/gim) || [];
|
||||
assert.equal(
|
||||
cases.length,
|
||||
8,
|
||||
`fixture must document exactly 8 Del 4 cases (found ${cases.length})`
|
||||
);
|
||||
});
|
||||
|
||||
test('ties the checklist to the Maskinrommet §C2 truth source', () => {
|
||||
assert.ok(
|
||||
/§C2|C2/.test(fixture),
|
||||
'fixture must reference the §C2 writing-contract truth source'
|
||||
);
|
||||
});
|
||||
|
||||
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('records the persona-blindspot rationale (≈6/8 editorial-only)', () => {
|
||||
assert.ok(
|
||||
/blindsone/i.test(fixture) && /6\/8/.test(fixture),
|
||||
'fixture must record why the gate exists (blind spots, ~6/8 editorial-only)'
|
||||
);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue