397777089e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
65 lines
2.6 KiB
JavaScript
65 lines
2.6 KiB
JavaScript
// tools/decomposition-detector.mjs
|
||
/**
|
||
* Decomposition detector — router-gate v4 spec §3.8 + v4.1 (Direction 3).
|
||
* Pure: ловит feature, разбитую на 3+ мелких prompts с overlapping keywords без plan skill.
|
||
* v4.1: hard-block mutating at 3+ overlapping (was 5+ soft). LLM-judge verdict инъектируется.
|
||
*/
|
||
import { keywordOverlapCount, isResetMarker } from './safe-baseline-metering.mjs';
|
||
|
||
export { isResetMarker };
|
||
|
||
export const V4_1_DECOMP_THRESHOLD = Object.freeze({
|
||
min_overlapping_prompts: 3,
|
||
min_keyword_intersection: 3,
|
||
window_size_prompts: 10,
|
||
hard_block_mutating: true,
|
||
});
|
||
|
||
export function keywordIntersection(a, b) {
|
||
return keywordOverlapCount(a, b);
|
||
}
|
||
|
||
export function appendHistory(history, entry) {
|
||
return [...(history || []), entry];
|
||
}
|
||
|
||
export function detectDecompositionCandidate(history, currentEntry, threshold = V4_1_DECOMP_THRESHOLD) {
|
||
const window = (history || []).slice(-threshold.window_size_prompts);
|
||
const curKws = currentEntry.primary_keywords || [];
|
||
|
||
const overlapping = window.filter(
|
||
(e) => keywordOverlapCount(e.primary_keywords || [], curKws) >= threshold.min_keyword_intersection,
|
||
);
|
||
|
||
const anySkill = [...overlapping, currentEntry].some((e) => e.skill_invoked_this_prompt === true);
|
||
|
||
if (overlapping.length >= threshold.min_overlapping_prompts && !anySkill) {
|
||
// overlappingKeywords: curKws present in EVERY overlapping prompt
|
||
const overlappingKeywords = curKws.filter((k) =>
|
||
overlapping.every(
|
||
(e) => (e.primary_keywords || []).map((x) => String(x).toLowerCase()).includes(String(k).toLowerCase()),
|
||
),
|
||
);
|
||
return {
|
||
candidate: true,
|
||
overlappingPrompts: overlapping.map((e) => e.prompt_idx),
|
||
overlappingKeywords,
|
||
reason: `${overlapping.length + 1} prompts overlapping keywords [${overlappingKeywords.join(', ')}] без writing-plans/brainstorming skill.`,
|
||
};
|
||
}
|
||
return { candidate: false, overlappingPrompts: [], overlappingKeywords: [] };
|
||
}
|
||
|
||
export function decideDecomposition(candidate, llmVerdict, threshold = V4_1_DECOMP_THRESHOLD) {
|
||
if (!candidate || !candidate.candidate) return { action: 'allow' };
|
||
const verdict = typeof llmVerdict === 'string' ? llmVerdict : llmVerdict?.verdict;
|
||
if (verdict === 'YES') {
|
||
return {
|
||
action: threshold.hard_block_mutating ? 'hard_block_mutating' : 'soft_flag',
|
||
reason: `v4.1 decomp hard-block: ${candidate.reason} LLM-judge confirmed decomposition. Invoke writing-plans skill сейчас.`,
|
||
};
|
||
}
|
||
// candidate but LLM says legit-distinct → soft surface only
|
||
return { action: 'soft_flag', reason: candidate.reason };
|
||
}
|