ktg-plugin-marketplace/plugins/linkedin-studio/agents/__tests__/language-reviewer-fixture.test.mjs
Kjell Tore Guttormsen e69ea1f4c9 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>
2026-05-29 13:01:24 +02:00

80 lines
2.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 (L1L5), 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 (L1L5)', () => {
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)'
);
});
});