fix(#2150): treat the UI hint yes/no line as authoritative in checkUiPresence
checkUiPresence ran UI_TOKENS (which includes the bare token 'UI') over the raw phase-section text, so GSD's own '**UI hint**: no' metadata line matched the 'UI' token and reported hasUI=true — blocking non-frontend phases that explicitly declare themselves non-frontend via the documented convention (the plan-phase UI-SPEC gate fired under the default workflow.ui_safety_gate=true). - An explicit '**UI hint**: yes|no' line is now authoritative (mirrors how progress.md / new-project.md already parse it via 'UI hint.*yes'). hint:no -> hasUI=false; hint:yes -> hasUI=true. - Any '**UI hint**:' line is stripped before token-sniffing, so a hint without a recognised yes/no cannot false-positive on the bare 'UI' token. - With no hint line, behaviour is unchanged (token-sniffing on the rest). Closes #2150
This commit is contained in:
@@ -77,8 +77,24 @@ export function checkUiPresence(text: string): UiPresenceResult {
|
||||
// Normalise CRLF so the pattern sees consistent line boundaries.
|
||||
const normalised = text.replace(/\r\n/g, '\n');
|
||||
|
||||
// #2150: an explicit `**UI hint**: yes|no` metadata line is the author's
|
||||
// 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);
|
||||
const hint = hintMatch ? hintMatch[1].toLowerCase() : null;
|
||||
|
||||
// Strip ANY `**UI hint**:` line before token-sniffing so a hint without a
|
||||
// recognised yes/no (or one we did not short-circuit on) cannot false-positive
|
||||
// on the bare `UI` token.
|
||||
const sniffable = normalised
|
||||
.split('\n')
|
||||
.filter((line) => !/^\s*\*\*UI hint\*\*\s*:/i.test(line))
|
||||
.join('\n');
|
||||
|
||||
const found = new Set<string>();
|
||||
for (const line of normalised.split('\n')) {
|
||||
for (const line of sniffable.split('\n')) {
|
||||
// Reset lastIndex before each line so the global pattern restarts from 0.
|
||||
UI_GATE_PATTERN_GLOBAL.lastIndex = 0;
|
||||
for (const m of line.matchAll(UI_GATE_PATTERN_GLOBAL)) {
|
||||
@@ -86,6 +102,13 @@ export function checkUiPresence(text: string): UiPresenceResult {
|
||||
}
|
||||
}
|
||||
|
||||
if (hint === 'no') {
|
||||
return { hasUI: false, tokens: [] };
|
||||
}
|
||||
if (hint === 'yes') {
|
||||
return { hasUI: true, tokens: [...found] };
|
||||
}
|
||||
|
||||
return { hasUI: found.size > 0, tokens: [...found] };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user