* fix(#448): resolve UI safety gate helper against the GSD install dir The §5.6 UI Design Contract Gate (and autonomous §3a.5) resolved ui-safety-gate.cjs via `git rev-parse --show-toplevel`, i.e. the consuming project's git root — which has no bin/lib. The node call failed, its exit code was conflated with "no UI", and the gate silently no-opped so frontend phases skipped the UI-SPEC prompt. Resolve the helper against the GSD install dir via RUNTIME_DIR (the same idiom §1 uses for gsd-tools), with git-toplevel and $HOME/.claude fallbacks. When the helper genuinely can't be found, fail OPEN with a stderr warning (assume UI present) rather than silently skipping. Tests: bug-3706 structural guard now requires RUNTIME_DIR resolution and forbids the consuming-project GSD_REPO_ROOT anchor; a new behavioral test resolves and runs the helper from a temp consuming project (no bin/lib) with RUNTIME_DIR set. autonomous-ui-steps updated to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(#448): add changeset fragment for PR #539 * fix(#448): add get-shit-done/bin/lib/ to UI gate probe and deploy helper there The installer copies get-shit-done/ to the target but not root bin/lib/, so the helper was never found for installed users. Placing ui-safety-gate.cjs in get-shit-done/bin/lib/ ensures the installer deploys it, and probing that path first makes the gate work correctly in installed runtimes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: update changeset to cover get-shit-done/bin/lib/ deployment Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: update inventory for ui-safety-gate.cjs in get-shit-done/bin/lib/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
112 lines
3.9 KiB
JavaScript
112 lines
3.9 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* UI Safety Gate — shell-free implementation (#3706, #3718)
|
|
*
|
|
* Replaces the bash shell-based one-liner that silently degraded on Windows
|
|
* PowerShell / cmd.exe because the locale env-var prefix was not recognised.
|
|
* This module runs inside Node.js — no shell dependency, works identically
|
|
* on bash, Git-Bash, PowerShell, and cmd.exe.
|
|
*
|
|
* Word-boundary anchoring:
|
|
* (^|[^a-zA-Z0-9])(TOKEN)([^a-zA-Z0-9]|$)
|
|
* Equivalent to POSIX ERE [^[:alnum:]] — matches tokens only when they are not
|
|
* interior substrings of alphanumeric compound words (e.g. "microfrontend" is NOT
|
|
* matched; "micro-frontend" and "micro frontend" ARE matched).
|
|
*
|
|
* Public API:
|
|
* checkUiPresence(text: string): { hasUI: boolean, tokens: string[] }
|
|
*
|
|
* CLI usage — reads phase-section text from STDIN to avoid ARG_MAX limits:
|
|
* echo "$PHASE_SECTION" | node get-shit-done/bin/lib/ui-safety-gate.cjs
|
|
* echo $? → 0 if UI tokens found, 1 if not, 2 on usage error
|
|
*
|
|
* Exit codes mirror grep: 0 = match found, 1 = no match, 2 = usage error.
|
|
*
|
|
* Canonical location: get-shit-done/bin/lib/ui-safety-gate.cjs (#448)
|
|
* This path is deployed by the GSD installer to $RUNTIME_DIR/get-shit-done/bin/lib/.
|
|
* bin/lib/ui-safety-gate.cjs (root) is retained for source-repo and npm usage.
|
|
*/
|
|
|
|
const UI_TOKENS = [
|
|
'UI',
|
|
'interface',
|
|
'frontend',
|
|
'component',
|
|
'layout',
|
|
'page',
|
|
'screen',
|
|
'view',
|
|
'form',
|
|
'dashboard',
|
|
'widget',
|
|
];
|
|
|
|
/**
|
|
* Built once at module load — no per-call compilation overhead.
|
|
* ASCII word boundaries — matches the original ASCII-grep intent of #3706.
|
|
* Note: JS [a-zA-Z0-9] is ASCII-only and NOT equivalent to POSIX [[:alnum:]],
|
|
* which is locale-sensitive and includes accented characters.
|
|
*/
|
|
const UI_GATE_PATTERN = new RegExp(
|
|
'(^|[^a-zA-Z0-9])(' + UI_TOKENS.join('|') + ')([^a-zA-Z0-9]|$)',
|
|
'i'
|
|
);
|
|
|
|
// Global-flagged variant for extracting ALL matches per line (matchAll).
|
|
const UI_GATE_PATTERN_GLOBAL = new RegExp(UI_GATE_PATTERN.source, 'gi');
|
|
|
|
/**
|
|
* Check a roadmap phase section string for frontend UI indicators.
|
|
*
|
|
* @param {string} text - The roadmap phase section content (may be multi-line, CRLF or LF).
|
|
* @returns {{ hasUI: boolean, tokens: string[] }}
|
|
* hasUI — true if any UI token was matched as a standalone word.
|
|
* tokens — matched token strings (lowercased), deduplicated.
|
|
*/
|
|
function checkUiPresence(text) {
|
|
if (typeof text !== 'string') {
|
|
return { hasUI: false, tokens: [] };
|
|
}
|
|
|
|
// Normalise CRLF so the pattern sees consistent line boundaries.
|
|
const normalised = text.replace(/\r\n/g, '\n');
|
|
|
|
const found = new Set();
|
|
for (const line of normalised.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)) {
|
|
found.add(m[2].toLowerCase());
|
|
}
|
|
}
|
|
|
|
return { hasUI: found.size > 0, tokens: [...found] };
|
|
}
|
|
|
|
module.exports = { checkUiPresence, UI_TOKENS };
|
|
|
|
// ── CLI entry point ─────────────────────────────────────────────────────────
|
|
// Reads phase-section text from STDIN (not argv) to avoid OS ARG_MAX limits.
|
|
// Invoked by workflow .md bash blocks as: echo "$PHASE_SECTION" | node .../ui-safety-gate.cjs
|
|
// Exit 0 = UI found, 1 = no UI, 2 = startup error.
|
|
|
|
if (require.main === module) {
|
|
// Collect stdin chunks asynchronously.
|
|
const chunks = [];
|
|
process.stdin.setEncoding('utf-8');
|
|
|
|
process.stdin.on('data', (chunk) => chunks.push(chunk));
|
|
|
|
process.stdin.on('end', () => {
|
|
const input = chunks.join('');
|
|
const result = checkUiPresence(input);
|
|
process.exit(result.hasUI ? 0 : 1);
|
|
});
|
|
|
|
process.stdin.on('error', (err) => {
|
|
process.stderr.write(`ERROR: ui-safety-gate.cjs stdin read failed: ${err.message}\n`);
|
|
process.exit(2);
|
|
});
|
|
}
|