397777089e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
76 lines
3.1 KiB
JavaScript
76 lines
3.1 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* Routing-gate method-direction detector (brain governance, observer
|
||
* factor-analysis spec §5.1). Pure — given a user-prompt text and a list of
|
||
* known node names, decides whether the user *dictated* a specific method.
|
||
* Conservative-broad: a directive verb within a 40-char window before a node
|
||
* name, or a /slash-command form.
|
||
*
|
||
* Security Guidance #40: pure string ops — no exec/execSync.
|
||
*/
|
||
import { readFileSync, existsSync } from 'fs';
|
||
import { resolve, dirname } from 'path';
|
||
import { fileURLToPath } from 'url';
|
||
|
||
/** Repo root = parent of the tools/ directory where this module lives. */
|
||
const REPO_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
||
|
||
const KNOWN_NODES_PATH = 'tools/observer-known-nodes.txt';
|
||
|
||
const DIRECTIVE_VERBS = [
|
||
'запусти', 'запускай', 'используй', 'вызови', 'вызывай', 'прогони',
|
||
'применяй', 'применить', 'через', 'run', 'use', 'invoke', 'via',
|
||
];
|
||
|
||
/** Load the directable node names from the data file (# comments / blanks skipped). */
|
||
export function loadKnownNodes(path = KNOWN_NODES_PATH) {
|
||
const absPath = resolve(REPO_ROOT, path);
|
||
if (!existsSync(absPath)) return [];
|
||
const out = [];
|
||
for (const line of readFileSync(absPath, 'utf-8').split('\n')) {
|
||
const t = line.trim();
|
||
if (!t || t.startsWith('#')) continue;
|
||
out.push(t);
|
||
}
|
||
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 = stripPastedContext(String(promptText || '')).toLowerCase();
|
||
for (const node of knownNodes || []) {
|
||
const n = String(node).toLowerCase();
|
||
if (!n) continue;
|
||
if (text.includes('/' + n)) return { directed: true, node };
|
||
const idx = text.indexOf(n);
|
||
if (idx === -1) continue;
|
||
const before = text.slice(Math.max(0, idx - 40), idx);
|
||
if (DIRECTIVE_VERBS.some((v) => before.includes(v))) return { directed: true, node };
|
||
}
|
||
return { directed: false, node: null };
|
||
}
|
||
|
||
if (process.argv[1] && process.argv[1].replace(/\\/g, '/').endsWith('/observer-routing-detector.mjs')) {
|
||
const det = detectMethodDirected(process.argv.slice(2).join(' '), loadKnownNodes());
|
||
console.log(JSON.stringify(det));
|
||
process.exit(0);
|
||
}
|