From 045577d0946dde16b290864cc9ff471dc41cd109 Mon Sep 17 00:00:00 2001 From: LifeJiggy Date: Fri, 29 May 2026 17:16:10 +0100 Subject: [PATCH 1/2] fix: replace chalk.yellow with theme-aware hex in session-directory warning --- .changeset/named-color-violation-tui.md | 5 ++ apps/kimi-code/src/cli/run-prompt.ts | 2 +- apps/kimi-code/src/tui/kimi-tui.ts | 2 +- .../test/tui/chalk-named-color-guard.test.ts | 62 +++++++++++++++++++ apps/kimi-code/test/tui/theme-colors.test.ts | 39 ++++++++++++ 5 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 .changeset/named-color-violation-tui.md create mode 100644 apps/kimi-code/test/tui/chalk-named-color-guard.test.ts create mode 100644 apps/kimi-code/test/tui/theme-colors.test.ts diff --git a/.changeset/named-color-violation-tui.md b/.changeset/named-color-violation-tui.md new file mode 100644 index 00000000..4e226e35 --- /dev/null +++ b/.changeset/named-color-violation-tui.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Replace chalk named color with theme-aware hex in session-directory warning. diff --git a/apps/kimi-code/src/cli/run-prompt.ts b/apps/kimi-code/src/cli/run-prompt.ts index e639aed0..e9f4505e 100644 --- a/apps/kimi-code/src/cli/run-prompt.ts +++ b/apps/kimi-code/src/cli/run-prompt.ts @@ -166,7 +166,7 @@ async function resolvePromptSession( } if (target.workDir !== workDir) { stderr.write( - `${chalk.yellow( + `${chalk.hex('#E8A838')( `Session "${opts.session}" was created under a different directory.\n` + ` cd "${target.workDir}" && kimi -r ${opts.session}`, )}\n\n`, diff --git a/apps/kimi-code/src/tui/kimi-tui.ts b/apps/kimi-code/src/tui/kimi-tui.ts index 68258aaa..910fd26c 100644 --- a/apps/kimi-code/src/tui/kimi-tui.ts +++ b/apps/kimi-code/src/tui/kimi-tui.ts @@ -467,7 +467,7 @@ export class KimiTUI { if (target.workDir !== workDir) { this.state.ui.stop(); process.stderr.write( - `${chalk.yellow( + `${chalk.hex(this.state.theme.colors.warning)( `Session "${startup.sessionFlag}" was created under a different directory.\n` + ` cd "${target.workDir}" && kimi -r ${startup.sessionFlag}`, )}\n\n`, diff --git a/apps/kimi-code/test/tui/chalk-named-color-guard.test.ts b/apps/kimi-code/test/tui/chalk-named-color-guard.test.ts new file mode 100644 index 00000000..1375da54 --- /dev/null +++ b/apps/kimi-code/test/tui/chalk-named-color-guard.test.ts @@ -0,0 +1,62 @@ +import { readFileSync, statSync } from 'node:fs'; +import { join, relative } from 'node:path'; + +import { describe, expect, it } from 'vitest'; + +const SRC_ROOT = join(__dirname, '..', '..', 'src'); + +const NAMED_COLORS = [ + 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', + 'white', 'gray', 'grey', 'black', + 'blackBright', 'whiteBright', 'redBright', 'greenBright', + 'yellowBright', 'blueBright', 'magentaBright', 'cyanBright', +]; + +const CHALK_NAMED_PATTERN = new RegExp( + `chalk\\.(${NAMED_COLORS.join('|')})\\(`, +); + +function walk(dir: string, files: string[] = []): string[] { + const entries = [['dir', '']]; + try { + for (const entry of require('fs').readdirSync(dir, { withFileTypes: true })) { + const p = join(dir, entry.name); + if (entry.isDirectory()) { + walk(p, files); + } else if (entry.name.endsWith('.ts') && !entry.name.endsWith('.test.ts') && !entry.name.endsWith('.spec.ts')) { + files.push(p); + } + } + } catch { /* skip */ } + return files; +} + +describe('chalk named color guard', () => { + it('forbids chalk named colors in production source code', () => { + const offenders: { file: string; line: number; snippet: string }[] = []; + const files = walk(SRC_ROOT); + for (const file of files) { + const content = readFileSync(file, 'utf8'); + const lines = content.split('\n'); + for (let i = 0; i < lines.length; i++) { + const line = lines[i] ?? ''; + if (line.trimStart().startsWith('//') || line.trimStart().startsWith('*')) continue; + if (line.includes('*')) continue; + CHALK_NAMED_PATTERN.lastIndex = 0; + const m = CHALK_NAMED_PATTERN.exec(line); + if (m) { + offenders.push({ + file: relative(join(__dirname, '..', '..', 'src'), file), + line: i + 1, + snippet: line.trim(), + }); + } + } + } + expect( + offenders, + `Found chalk named color usages. Use chalk.hex(colors.) or theme styles instead.\n` + + offenders.map((o) => ` ${o.file}:${String(o.line)} ${o.snippet}`).join('\n'), + ).toEqual([]); + }); +}); diff --git a/apps/kimi-code/test/tui/theme-colors.test.ts b/apps/kimi-code/test/tui/theme-colors.test.ts new file mode 100644 index 00000000..14d93df4 --- /dev/null +++ b/apps/kimi-code/test/tui/theme-colors.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it } from 'vitest'; + +import { darkColors, lightColors, getColorPalette } from '#/tui/theme/colors'; +import { createThemeStyles } from '#/tui/theme/styles'; + +describe('ColorPalette warning token', () => { + it('has a defined warning color in both themes', () => { + expect(darkColors.warning).toBeTruthy(); + expect(lightColors.warning).toBeTruthy(); + expect(darkColors.warning).not.toBe(lightColors.warning); + }); + + it('resolves the correct palette by theme name', () => { + expect(getColorPalette('dark')).toBe(darkColors); + expect(getColorPalette('light')).toBe(lightColors); + }); +}); + +describe('ThemeStyles warning helper', () => { + it('wraps text and includes the input', () => { + const styles = createThemeStyles(darkColors); + const result = styles.warning('test'); + expect(result).toContain('test'); + }); + + it('is a function that returns a string', () => { + const darkStyles = createThemeStyles(darkColors); + expect(typeof darkStyles.warning).toBe('function'); + expect(typeof darkStyles.warning('hello')).toBe('string'); + }); + + it('creates independent style sets per palette', () => { + const darkStyles = createThemeStyles(darkColors); + const lightStyles = createThemeStyles(lightColors); + expect(darkStyles.colors.warning).toBe(darkColors.warning); + expect(lightStyles.colors.warning).toBe(lightColors.warning); + expect(darkStyles.colors.warning).not.toBe(lightStyles.colors.warning); + }); +}); From 2597ea852a40dd40c448841d5cb59b8de6d09e38 Mon Sep 17 00:00:00 2001 From: LifeJiggy Date: Fri, 29 May 2026 18:00:15 +0100 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20address=20Codex=20review=20=E2=80=94?= =?UTF-8?q?=20fold=20theme=20tests=20into=20existing=20suite,=20fix=20guar?= =?UTF-8?q?d=20test=20bugs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move theme-colors.test.ts assertions into existing terminal-theme.test.ts to avoid a generic feature test file (Codex P2) - Fix chalk-named-color-guard.test.ts: - Replace require('fs') with ESM import (readdirSync) (P1) - Fix over-broad comment-skip logic — use proper inBlockComment state tracker instead of line.includes('*') which silently skipped valid code lines containing asterisks (P1) - Remove dead code (const entries = [['dir', '']]) (P2) --- .../test/tui/chalk-named-color-guard.test.ts | 24 +++++++++--- .../kimi-code/test/tui/terminal-theme.test.ts | 37 ++++++++++++++++++ apps/kimi-code/test/tui/theme-colors.test.ts | 39 ------------------- 3 files changed, 55 insertions(+), 45 deletions(-) delete mode 100644 apps/kimi-code/test/tui/theme-colors.test.ts diff --git a/apps/kimi-code/test/tui/chalk-named-color-guard.test.ts b/apps/kimi-code/test/tui/chalk-named-color-guard.test.ts index 1375da54..e37a1187 100644 --- a/apps/kimi-code/test/tui/chalk-named-color-guard.test.ts +++ b/apps/kimi-code/test/tui/chalk-named-color-guard.test.ts @@ -1,4 +1,4 @@ -import { readFileSync, statSync } from 'node:fs'; +import { readFileSync, readdirSync } from 'node:fs'; import { join, relative } from 'node:path'; import { describe, expect, it } from 'vitest'; @@ -17,9 +17,8 @@ const CHALK_NAMED_PATTERN = new RegExp( ); function walk(dir: string, files: string[] = []): string[] { - const entries = [['dir', '']]; try { - for (const entry of require('fs').readdirSync(dir, { withFileTypes: true })) { + for (const entry of readdirSync(dir, { withFileTypes: true })) { const p = join(dir, entry.name); if (entry.isDirectory()) { walk(p, files); @@ -35,18 +34,31 @@ describe('chalk named color guard', () => { it('forbids chalk named colors in production source code', () => { const offenders: { file: string; line: number; snippet: string }[] = []; const files = walk(SRC_ROOT); + let inBlockComment = false; for (const file of files) { const content = readFileSync(file, 'utf8'); const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i] ?? ''; - if (line.trimStart().startsWith('//') || line.trimStart().startsWith('*')) continue; - if (line.includes('*')) continue; + const trimmed = line.trimStart(); + + if (inBlockComment) { + if (trimmed.includes('*/')) inBlockComment = false; + continue; + } + + if (trimmed.startsWith('/*')) { + if (!trimmed.includes('*/')) inBlockComment = true; + continue; + } + + if (trimmed.startsWith('//') || trimmed.startsWith('*')) continue; + CHALK_NAMED_PATTERN.lastIndex = 0; const m = CHALK_NAMED_PATTERN.exec(line); if (m) { offenders.push({ - file: relative(join(__dirname, '..', '..', 'src'), file), + file: relative(SRC_ROOT, file), line: i + 1, snippet: line.trim(), }); diff --git a/apps/kimi-code/test/tui/terminal-theme.test.ts b/apps/kimi-code/test/tui/terminal-theme.test.ts index 68a1818b..05537837 100644 --- a/apps/kimi-code/test/tui/terminal-theme.test.ts +++ b/apps/kimi-code/test/tui/terminal-theme.test.ts @@ -1,6 +1,8 @@ import { describe, expect, it, vi } from "vitest"; import type { TUIState } from "#/tui/kimi-tui"; +import { darkColors, lightColors, getColorPalette } from "#/tui/theme/colors"; +import { createThemeStyles } from "#/tui/theme/styles"; import { DISABLE_TERMINAL_THEME_REPORTING, ENABLE_TERMINAL_THEME_REPORTING, @@ -158,3 +160,38 @@ describe("terminal theme tracking", () => { expect(state.terminal.write).toHaveBeenCalledWith(DISABLE_TERMINAL_THEME_REPORTING); }); }); + +describe('ColorPalette warning token', () => { + it('has a defined warning color in both themes', () => { + expect(darkColors.warning).toBeTruthy(); + expect(lightColors.warning).toBeTruthy(); + expect(darkColors.warning).not.toBe(lightColors.warning); + }); + + it('resolves the correct palette by theme name', () => { + expect(getColorPalette('dark')).toBe(darkColors); + expect(getColorPalette('light')).toBe(lightColors); + }); +}); + +describe('ThemeStyles warning helper', () => { + it('wraps text and includes the input', () => { + const styles = createThemeStyles(darkColors); + const result = styles.warning('test'); + expect(result).toContain('test'); + }); + + it('is a function that returns a string', () => { + const darkStyles = createThemeStyles(darkColors); + expect(typeof darkStyles.warning).toBe('function'); + expect(typeof darkStyles.warning('hello')).toBe('string'); + }); + + it('creates independent style sets per palette', () => { + const darkStyles = createThemeStyles(darkColors); + const lightStyles = createThemeStyles(lightColors); + expect(darkStyles.colors.warning).toBe(darkColors.warning); + expect(lightStyles.colors.warning).toBe(lightColors.warning); + expect(darkStyles.colors.warning).not.toBe(lightStyles.colors.warning); + }); +}); diff --git a/apps/kimi-code/test/tui/theme-colors.test.ts b/apps/kimi-code/test/tui/theme-colors.test.ts deleted file mode 100644 index 14d93df4..00000000 --- a/apps/kimi-code/test/tui/theme-colors.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { describe, expect, it } from 'vitest'; - -import { darkColors, lightColors, getColorPalette } from '#/tui/theme/colors'; -import { createThemeStyles } from '#/tui/theme/styles'; - -describe('ColorPalette warning token', () => { - it('has a defined warning color in both themes', () => { - expect(darkColors.warning).toBeTruthy(); - expect(lightColors.warning).toBeTruthy(); - expect(darkColors.warning).not.toBe(lightColors.warning); - }); - - it('resolves the correct palette by theme name', () => { - expect(getColorPalette('dark')).toBe(darkColors); - expect(getColorPalette('light')).toBe(lightColors); - }); -}); - -describe('ThemeStyles warning helper', () => { - it('wraps text and includes the input', () => { - const styles = createThemeStyles(darkColors); - const result = styles.warning('test'); - expect(result).toContain('test'); - }); - - it('is a function that returns a string', () => { - const darkStyles = createThemeStyles(darkColors); - expect(typeof darkStyles.warning).toBe('function'); - expect(typeof darkStyles.warning('hello')).toBe('string'); - }); - - it('creates independent style sets per palette', () => { - const darkStyles = createThemeStyles(darkColors); - const lightStyles = createThemeStyles(lightColors); - expect(darkStyles.colors.warning).toBe(darkColors.warning); - expect(lightStyles.colors.warning).toBe(lightColors.warning); - expect(darkStyles.colors.warning).not.toBe(lightStyles.colors.warning); - }); -});