fix(observer): routing-detector ignorit PASTED-citaty /node (P1 E/G, 4-y FP-klass)

Novyy uzkiy stripPastedContext (fenced+blockquote only) pered detekciey. NE reuse stripQuotedContext (tot stripaet inline - FN). Sohraneno FP-smeshchenie routingGate (SE-1). Regressiya 3185 passed.
This commit is contained in:
Дмитрий
2026-06-09 03:18:55 +03:00
parent 95a5398aa8
commit 74b243e52b
2 changed files with 51 additions and 1 deletions
+17 -1
View File
@@ -35,11 +35,27 @@ export function loadKnownNodes(path = KNOWN_NODES_PATH) {
return out;
}
/**
* Снять ТОЛЬКО PASTED-контекст: fenced-блоки и markdown-blockquote-строки.
* НАМЕРЕННО НЕ трогает inline-backticks/кавычки/guillemets — в отличие от
* stripQuotedContext (tools/enforce-rationalization-audit.mjs), у которого
* ПРОТИВОПОЛОЖНОЕ безопасное направление (FN-safe). Здесь гейт routingGate
* FP-safe: inline-форма `/x` должна оставаться директивой (over-detection
* дешевле тихой потери governance-сигнала; sharp-edges SE-1).
*/
export function stripPastedContext(text) {
if (typeof text !== 'string') return '';
let s = text;
s = s.replace(/```[\s\S]*?```/g, ' '); // fenced blocks
s = s.replace(/^[ \t]*(?:>|>).*$/gm, ' '); // markdown blockquote lines
return s;
}
/**
* @returns {{directed: boolean, node: string|null}}
*/
export function detectMethodDirected(promptText, knownNodes) {
const text = String(promptText || '').toLowerCase();
const text = stripPastedContext(String(promptText || '')).toLowerCase();
for (const node of knownNodes || []) {
const n = String(node).toLowerCase();
if (!n) continue;
+34
View File
@@ -48,3 +48,37 @@ describe('loadKnownNodes', () => {
expect(loadKnownNodes('tools/does-not-exist.txt')).toEqual([]);
});
});
import { stripPastedContext } from './observer-routing-detector.mjs';
describe('stripPastedContext (узкий: fenced + blockquote)', () => {
it('removes fenced blocks', () => {
expect(stripPastedContext('a\n```\n/x here\n```\nb')).not.toContain('/x');
});
it('removes blockquote lines', () => {
expect(stripPastedContext('> /x cited\nkeep')).not.toContain('/x');
});
it('keeps inline backticks (FP-safe bias)', () => {
expect(stripPastedContext('use `/x` now')).toContain('/x');
});
it('is non-string safe', () => {
expect(stripPastedContext(null)).toBe('');
});
});
describe('detectMethodDirected — PASTED-context guard (4-й FP-класс)', () => {
it('does NOT flag a /node inside a fenced code block', () => {
const t = 'смотри лог:\n```\nуже использовал /brainstorming тут\n```\nчто думаешь?';
expect(detectMethodDirected(t, NODES).directed).toBe(false);
});
it('does NOT flag a blockquote-cited directive', () => {
const t = '> используй systematic-debugging здесь\nэто цитата из старого чата';
expect(detectMethodDirected(t, NODES).directed).toBe(false);
});
it('STILL flags an inline /slash directive (FP-safe bias preserved)', () => {
expect(detectMethodDirected('сделай через `/brainstorming`', NODES).directed).toBe(true);
});
it('STILL flags a plain-prose directive', () => {
expect(detectMethodDirected('запусти discovery-interview', NODES).directed).toBe(true);
});
});