diff --git a/src/ui-safety-gate.cts b/src/ui-safety-gate.cts index 1aaf89b35..88649a74a 100644 --- a/src/ui-safety-gate.cts +++ b/src/ui-safety-gate.cts @@ -81,8 +81,10 @@ export function checkUiPresence(text: string): UiPresenceResult { // authoritative declaration of whether the phase has a UI surface — progress.md // and new-project.md already parse this line (`UI hint.*yes`). The bare token // `UI` in the line itself must not count as a UI indicator, and the declaration - // overrides token-sniffing. - const hintMatch = normalised.match(/\*\*UI hint\*\*\s*:\s*(yes|no)/i); + // overrides token-sniffing. Line-anchored (`m`) so a mid-line prose mention is + // not treated as the metadata line; word-boundary on the value so `nope`/`not` + // do not match `no`. + const hintMatch = normalised.match(/^\s*\*\*UI hint\*\*\s*:\s*(yes|no)\b/im); const hint = hintMatch ? hintMatch[1].toLowerCase() : null; // Strip ANY `**UI hint**:` line before token-sniffing so a hint without a diff --git a/tests/ui-safety-gate.test.cjs b/tests/ui-safety-gate.test.cjs index 6d5810f91..6d2d0bd40 100644 --- a/tests/ui-safety-gate.test.cjs +++ b/tests/ui-safety-gate.test.cjs @@ -96,6 +96,18 @@ describe('checkUiPresence', () => { 'a phase that explicitly declares UI hint: yes must be flagged as UI'); }); + test('#2150 `**UI hint**: yes` over a pure-backend body still flags UI', () => { + const result = checkUiPresence('**UI hint**: yes\n\nBackend API refactor.\n'); + assert.strictEqual(result.hasUI, true, 'hint:yes is authoritative even with no UI tokens'); + assert.deepStrictEqual(result.tokens, []); + }); + + test('#2150 hint value is whole-word matched (nope/not do not mean no)', () => { + const result = checkUiPresence('**UI hint**: nope\n\nBuild a dashboard component.\n'); + assert.strictEqual(result.hasUI, true, + 'a malformed hint value like "nope" must not be read as "no"; fall through to token-sniffing'); + }); + test('#2150 a hint line without yes/no is stripped (bare UI token does not fire)', () => { // A malformed hint (`UI hint: maybe`) must not false-positive on the bare // `UI` token in the line itself; other UI tokens elsewhere still detect.