From 8846210ca2cea291c39960db00e2d0ea09b7c3f9 Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Mon, 30 Mar 2026 01:50:17 -0400 Subject: [PATCH] fix: unblock unicode safety CI lint (#1017) * fix: unblock unicode safety CI lint * fix: unblock shared CI regressions --- commands/learn-eval.md | 16 ++++----- commands/multi-plan.md | 14 ++++---- scripts/ci/check-unicode-safety.js | 52 +++++++++++++++++++++++++++--- tests/scripts/codex-hooks.test.js | 6 ++-- 4 files changed, 66 insertions(+), 22 deletions(-) diff --git a/commands/learn-eval.md b/commands/learn-eval.md index c6788559..23b3695c 100644 --- a/commands/learn-eval.md +++ b/commands/learn-eval.md @@ -75,17 +75,17 @@ origin: auto-extracted **Guideline dimensions** (informing the verdict, not scored): - - **Specificity & Actionability**: Contains code examples or commands that are immediately usable - - **Scope Fit**: Name, trigger conditions, and content are aligned and focused on a single pattern - - **Uniqueness**: Provides value not covered by existing skills (informed by checklist results) - - **Reusability**: Realistic trigger scenarios exist in future sessions +- **Specificity & Actionability**: Contains code examples or commands that are immediately usable +- **Scope Fit**: Name, trigger conditions, and content are aligned and focused on a single pattern +- **Uniqueness**: Provides value not covered by existing skills (informed by checklist results) +- **Reusability**: Realistic trigger scenarios exist in future sessions 6. **Verdict-specific confirmation flow** - - **Improve then Save**: Present the required improvements + revised draft + updated checklist/verdict after one re-evaluation; if the revised verdict is **Save**, save after user confirmation, otherwise follow the new verdict - - **Save**: Present save path + checklist results + 1-line verdict rationale + full draft → save after user confirmation - - **Absorb into [X]**: Present target path + additions (diff format) + checklist results + verdict rationale → append after user confirmation - - **Drop**: Show checklist results + reasoning only (no confirmation needed) +- **Improve then Save**: Present the required improvements + revised draft + updated checklist/verdict after one re-evaluation; if the revised verdict is **Save**, save after user confirmation, otherwise follow the new verdict +- **Save**: Present save path + checklist results + 1-line verdict rationale + full draft → save after user confirmation +- **Absorb into [X]**: Present target path + additions (diff format) + checklist results + verdict rationale → append after user confirmation +- **Drop**: Show checklist results + reasoning only (no confirmation needed) 7. Save / Absorb to the determined location diff --git a/commands/multi-plan.md b/commands/multi-plan.md index 4d7941a4..a899059f 100644 --- a/commands/multi-plan.md +++ b/commands/multi-plan.md @@ -203,17 +203,17 @@ Synthesize both analyses, generate **Step-by-step Implementation Plan**: 2. Save plan to `.claude/plan/.md` (extract feature name from requirement, e.g., `user-auth`, `payment-module`) 3. Output prompt in **bold text** (MUST use actual saved file path): - --- +--- **Plan generated and saved to `.claude/plan/actual-feature-name.md`** **Please review the plan above. You can:** - - **Modify plan**: Tell me what needs adjustment, I'll update the plan - - **Execute plan**: Copy the following command to a new session +- **Modify plan**: Tell me what needs adjustment, I'll update the plan +- **Execute plan**: Copy the following command to a new session - ``` - /ccg:execute .claude/plan/actual-feature-name.md - ``` - --- +``` +/ccg:execute .claude/plan/actual-feature-name.md +``` +--- **NOTE**: The `actual-feature-name.md` above MUST be replaced with the actual saved filename! diff --git a/scripts/ci/check-unicode-safety.js b/scripts/ci/check-unicode-safety.js index 4c35980d..455cf415 100644 --- a/scripts/ci/check-unicode-safety.js +++ b/scripts/ci/check-unicode-safety.js @@ -50,9 +50,7 @@ const writeModeSkip = new Set([ path.normalize('tests/scripts/check-unicode-safety.test.js'), ]); -const dangerousInvisibleRe = - /[\u200B-\u200D\u2060\uFEFF\u202A-\u202E\u2066-\u2069\uFE00-\uFE0F\u{E0100}-\u{E01EF}]/gu; -const emojiRe = /[\p{Extended_Pictographic}\p{Regional_Indicator}]/gu; +const emojiRe = /(?:\p{Extended_Pictographic}|\p{Regional_Indicator})/gu; const allowedSymbolCodePoints = new Set([ 0x00A9, 0x00AE, @@ -106,9 +104,31 @@ function isAllowedEmojiLikeSymbol(char) { return allowedSymbolCodePoints.has(char.codePointAt(0)); } +function isDangerousInvisibleCodePoint(codePoint) { + return ( + (codePoint >= 0x200B && codePoint <= 0x200D) || + codePoint === 0x2060 || + codePoint === 0xFEFF || + (codePoint >= 0x202A && codePoint <= 0x202E) || + (codePoint >= 0x2066 && codePoint <= 0x2069) || + (codePoint >= 0xFE00 && codePoint <= 0xFE0F) || + (codePoint >= 0xE0100 && codePoint <= 0xE01EF) + ); +} + +function stripDangerousInvisibleChars(text) { + let next = ''; + for (const char of text) { + if (!isDangerousInvisibleCodePoint(char.codePointAt(0))) { + next += char; + } + } + return next; +} + function sanitizeText(text) { let next = text; - next = next.replace(dangerousInvisibleRe, ''); + next = stripDangerousInvisibleChars(next); for (const [pattern, replacement] of targetedReplacements) { next = next.replace(pattern, replacement); @@ -146,6 +166,28 @@ function collectMatches(text, regex, kind) { return matches; } +function collectDangerousInvisibleMatches(text) { + const matches = []; + let index = 0; + + for (const char of text) { + const codePoint = char.codePointAt(0); + if (isDangerousInvisibleCodePoint(codePoint)) { + const { line, column } = lineAndColumn(text, index); + matches.push({ + kind: 'dangerous-invisible', + char, + codePoint: `U+${codePoint.toString(16).toUpperCase()}`, + line, + column, + }); + } + index += char.length; + } + + return matches; +} + const changedFiles = []; const violations = []; @@ -172,7 +214,7 @@ for (const filePath of listFiles(repoRoot)) { } const fileViolations = [ - ...collectMatches(text, dangerousInvisibleRe, 'dangerous-invisible'), + ...collectDangerousInvisibleMatches(text), ...collectMatches(text, emojiRe, 'emoji'), ]; diff --git a/tests/scripts/codex-hooks.test.js b/tests/scripts/codex-hooks.test.js index 27978c29..2ed9e689 100644 --- a/tests/scripts/codex-hooks.test.js +++ b/tests/scripts/codex-hooks.test.js @@ -116,8 +116,10 @@ if ( fs.mkdirSync(codexDir, { recursive: true }); fs.writeFileSync(configPath, config); - const syncResult = runBash(syncScript, [], { HOME: homeDir, CODEX_HOME: codexDir }); - assert.strictEqual(syncResult.status, 0, syncResult.stderr || syncResult.stdout); + const syncResult = runBash(syncScript, ['--update-mcp'], { HOME: homeDir, CODEX_HOME: codexDir }); + assert.strictEqual(syncResult.status, 0, `${syncResult.stdout}\n${syncResult.stderr}`); + const syncedConfig = fs.readFileSync(configPath, 'utf8'); + assert.match(syncedConfig, /^\[mcp_servers\.context7\]$/m); const checkResult = runBash(checkScript, [], { HOME: homeDir, CODEX_HOME: codexDir }); assert.strictEqual(checkResult.status, 0, checkResult.stderr || checkResult.stdout);