From 6b44e7afd8949eeb7aabbe8fb4870a7579bd7e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Sun, 7 Jun 2026 18:44:09 +0300 Subject: [PATCH] =?UTF-8?q?feat(m6):=20toFloorEscapeRecord=20=E2=80=94=20?= =?UTF-8?q?=D1=80=D0=B0=D1=81=D0=BF=D0=BE=D0=B7=D0=BD=D0=B0=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20escape-=D0=BE=D0=B4=D0=BE=D0=B1=D1=80?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/askuser-answer-parser.mjs | 13 +++++++++++++ tools/askuser-answer-parser.test.mjs | 14 ++++++++++++++ 2 files changed, 27 insertions(+) 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); + }); +});