diff --git a/tools/askuser-answer-parser.mjs b/tools/askuser-answer-parser.mjs index 7a8122b8..56486eef 100644 --- a/tools/askuser-answer-parser.mjs +++ b/tools/askuser-answer-parser.mjs @@ -189,6 +189,19 @@ export function toApprovalRecord(answer, { question, nowMs = Date.now() } = {}) return { type: 'approve_git_operation', command, ts: nowMs }; } +const FLOOR_ESCAPE_RE = /FLOOR-ESCAPE:\s*([^\n]+?)\s*$/; + +/** Перевести ответ AskUser в floor_escape-запись, либо null. Stop-намерения → null. */ +export function toFloorEscapeRecord(answer, { nowMs = Date.now() } = {}) { + if (typeof answer !== 'string') return null; + if (isStopAnswer(answer)) return null; + const m = FLOOR_ESCAPE_RE.exec(answer); + if (!m) return null; + const action = m[1].trim(); + if (!action) return null; + return { type: 'floor_escape', action, ts: nowMs }; +} + import { signPayload, verifyReceipt, RECEIPT_DOMAINS } from './receipt-sign.mjs'; /** diff --git a/tools/askuser-answer-parser.test.mjs b/tools/askuser-answer-parser.test.mjs index b00881f4..2452f408 100644 --- a/tools/askuser-answer-parser.test.mjs +++ b/tools/askuser-answer-parser.test.mjs @@ -299,3 +299,17 @@ describe('toApprovalRecord (Stream H Task 6 — schema sync)', () => { expect(toApprovalRecord(42)).toBeNull(); }); }); + +import { toFloorEscapeRecord } from './askuser-answer-parser.mjs'; +describe('askuser-answer-parser toFloorEscapeRecord', () => { + it('распознаёт FLOOR-ESCAPE токен → запись floor_escape', () => { + const r = toFloorEscapeRecord('Разрешаю FLOOR-ESCAPE: bash:git push --force', { nowMs: 5 }); + expect(r).toEqual({ type: 'floor_escape', action: 'bash:git push --force', ts: 5 }); + }); + it('без токена → null', () => { + expect(toFloorEscapeRecord('просто да', { nowMs: 5 })).toBe(null); + }); + it('stop-намерение → null', () => { + expect(toFloorEscapeRecord('отмена FLOOR-ESCAPE: bash:x', { nowMs: 5 })).toBe(null); + }); +});