fix(router-gate): stream E — punctuation-aware stop detection + review nits (BOM/JSDoc/??)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env node
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* AskUserQuestion answer parsing library (router-gate v4, Stream E).
|
||||
*
|
||||
@@ -53,14 +53,17 @@ const STOP_TOKENS = new Set(STOP_KEYWORDS.filter((k) => !k.includes(' ')));
|
||||
/**
|
||||
* True if a free-form answer is a stop/abort/cancel intent (S27).
|
||||
* Keyword-based; normalizes (E33 invisible strip + ws-collapse + lowercase) first.
|
||||
* Punctuation attached to tokens (e.g. "нет,") is stripped before matching.
|
||||
*/
|
||||
export function isStopAnswer(text) {
|
||||
const norm = normalizeAnswer(text);
|
||||
if (!norm) return false;
|
||||
const depunct = (s) => s.replace(/[.,;:!?…«»"'()\[\]{}]+/g, ' ').split(/\s+/).filter(Boolean).join(' ');
|
||||
const cleaned = depunct(norm);
|
||||
for (const phrase of STOP_PHRASES) {
|
||||
if (norm.includes(normalizeAnswer(phrase))) return true;
|
||||
if (cleaned.includes(depunct(normalizeAnswer(phrase)))) return true;
|
||||
}
|
||||
const tokens = norm.split(' ');
|
||||
const tokens = cleaned.split(' ');
|
||||
for (const t of tokens) {
|
||||
if (STOP_TOKENS.has(t)) return true;
|
||||
}
|
||||
@@ -72,6 +75,7 @@ export function isStopAnswer(text) {
|
||||
* @param {string} text
|
||||
* @param {{llmJudge?: (text:string)=>Promise<boolean>}} opts
|
||||
* llmJudge default-stub returns false (never escalates). Stream D wires real judge.
|
||||
* The injected llmJudge receives whitespace-collapsed lowercase text (post-normalizeAnswer), not the raw input.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
export async function detectStopWithFallback(text, { llmJudge } = {}) {
|
||||
@@ -149,7 +153,7 @@ export function detectOtherSocialEng(controllerText) {
|
||||
*/
|
||||
export function buildApprovalRecord({ kind, pattern, sessionId, nowMs }) {
|
||||
return {
|
||||
kind: String(kind || 'approve_generic'),
|
||||
kind: String(kind ?? 'approve_generic'),
|
||||
approved_action_pattern: normalizeCommand(pattern),
|
||||
session_id: sessionId || 'unknown',
|
||||
approved_at_ms: typeof nowMs === 'number' ? nowMs : Date.now(),
|
||||
|
||||
@@ -3,6 +3,13 @@ import {
|
||||
stripInvisible,
|
||||
normalizeAnswer,
|
||||
normalizeCommand,
|
||||
STOP_KEYWORDS,
|
||||
isStopAnswer,
|
||||
detectStopWithFallback,
|
||||
parseAskUserResult,
|
||||
matchesApproval,
|
||||
detectOtherSocialEng,
|
||||
buildApprovalRecord,
|
||||
} from './askuser-answer-parser.mjs';
|
||||
|
||||
describe('askuser-answer-parser / stripInvisible (E33)', () => {
|
||||
@@ -49,11 +56,6 @@ describe('askuser-answer-parser / normalizeCommand (E34)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
import {
|
||||
STOP_KEYWORDS,
|
||||
isStopAnswer,
|
||||
detectStopWithFallback,
|
||||
} from './askuser-answer-parser.mjs';
|
||||
|
||||
describe('askuser-answer-parser / STOP_KEYWORDS (S27)', () => {
|
||||
it('includes core Russian + English stop tokens', () => {
|
||||
@@ -98,6 +100,15 @@ describe('askuser-answer-parser / isStopAnswer', () => {
|
||||
it('returns false for non-string', () => {
|
||||
expect(isStopAnswer(null)).toBe(false);
|
||||
});
|
||||
|
||||
it('matches a stop token with a trailing comma', () => {
|
||||
expect(isStopAnswer('нет, это лишнее')).toBe(true);
|
||||
expect(isStopAnswer('стоп.')).toBe(true);
|
||||
});
|
||||
|
||||
it('still matches multi-word phrase without the comma', () => {
|
||||
expect(isStopAnswer('всё поехали назад')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('askuser-answer-parser / detectStopWithFallback', () => {
|
||||
@@ -127,12 +138,6 @@ describe('askuser-answer-parser / detectStopWithFallback', () => {
|
||||
});
|
||||
});
|
||||
|
||||
import {
|
||||
parseAskUserResult,
|
||||
matchesApproval,
|
||||
detectOtherSocialEng,
|
||||
buildApprovalRecord,
|
||||
} from './askuser-answer-parser.mjs';
|
||||
|
||||
describe('askuser-answer-parser / parseAskUserResult', () => {
|
||||
it('extracts a single selected answer label', () => {
|
||||
|
||||
Reference in New Issue
Block a user