/** * Model-scoped ANNOTATION on top of the subtraction lens. * * This module generates no candidates of its own. `optimize --subtract` already * surfaces compensatory instructions through the single `compensatory-instruction` * detector (`BP-SUB-001`); this answers a narrower question over text that * already passed it: is this specifically the class of instruction a named model * documents as redundant — self-verification, or verification delegated to a * subagent? * * A second competing detector is deliberately NOT what this is. The register * entry it cites carries `lensCheck: null`, the same discipline `BP-JUDG-001` * shipped under: a plausible-looking detector for "instruction a model no longer * needs" measured 7/7 false positives across 409 real CLAUDE.md files, so this * claim class rides an existing measured detector rather than widening the * candidate set. * * Precision comes from requiring a reflexive/delegate TARGET alongside the * verify verb, never from narrowing the verb list. A bare "check"/"verify" also * matches EXTERNAL verification ("check the CI status") — which stays a true * negative here even though it is, correctly, still a `BP-SUB-001` candidate * upstream. * * Pure: text → annotation or null. Zero external dependencies. */ import { LB, RB } from './subtraction-prefilter.mjs'; /** * The reflexive self-verification target. This is what narrows a bare * verify/check imperative down to the specific claim the model's documented * self-correction contradicts. */ const SELF_TARGET_RE = new RegExp( LB + '(?:your (?:own )?(?:work|output|answer|changes)|yourself|' + 'before (?:responding|submitting|finalizing)|dine egne?|deg selv|før du svarer)' + RB, 'i', ); /** * Verify verbs — deliberately as broad as `subtraction-prefilter.mjs`'s * `IMPERATIVE_RE`. Breadth here is safe because a co-occurring target is * required; narrowing the list would only lose true positives. */ const VERIFY_VERB_RE = new RegExp( LB + '(?:double-check|re-verify|re-check|confirm|verify|review|check|' + 'dobbeltsjekk|verifiser|sjekk)' + RB, 'i', ); /** * Delegated verification. Order-free on purpose: it must match both * "verify X with a subagent" and "use a subagent to verify X". */ const DELEGATE_VERIFY_RE = new RegExp( LB + '(?:subagent|sub-agent|another (?:agent|instance)|task tool)' + RB, 'i', ); /** * Does this text carry a self- or delegate-targeted verification instruction? * * @param {string} text * @returns {boolean} */ export function isModelContradictedVerification(text) { return ( VERIFY_VERB_RE.test(text) && (SELF_TARGET_RE.test(text) || DELEGATE_VERIFY_RE.test(text)) ); } /** * Model names are compared on their alphanumeric skeleton, so "opus-5", * "Opus 5" and "OPUS5" are one model. A typo'd name simply fails to match — * the CLI reports that it did not recognize the name rather than reporting a * silent zero. * * @param {unknown} s * @returns {string} */ const normalizeModel = (s) => String(s || '') .toLowerCase() .replace(/[^a-z0-9]/g, ''); /** * Annotate a subtraction candidate with a model-scoped register citation. * * @param {string} text candidate block text (already a `BP-SUB-001` candidate) * @param {string|null|undefined} targetModel the model named by `--for-model` * @param {Array<{id: string, claim: string, modelScope?: string[]}>} entries * confirmed register entries with `category: 'prompting-fit'` * @returns {{registerId: string, claim: string, requestedModel: string}|null} */ export function matchModelScope(text, targetModel, entries) { if (!targetModel || !isModelContradictedVerification(text)) return null; const wanted = normalizeModel(targetModel); const entry = (entries || []).find((e) => (e.modelScope || []).some((m) => normalizeModel(m) === wanted), ); if (!entry) return null; return { registerId: entry.id, claim: entry.claim, requestedModel: targetModel }; } export { normalizeModel };