mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 02:11:28 +00:00
Merge pull request #420 from kaitranntt/kai/fix/windows-websearch-duplicates
fix(websearch): normalize Windows path separators in hook detection
This commit is contained in:
@@ -12,6 +12,7 @@ import * as os from 'os';
|
||||
import { info, warn } from '../ui';
|
||||
import { getWebSearchConfig } from '../../config/unified-config-loader';
|
||||
import { getCcsDir } from '../config-manager';
|
||||
import { isCcsWebSearchHook, deduplicateCcsHooks } from './hook-utils';
|
||||
|
||||
// Hook file name
|
||||
const WEBSEARCH_HOOK = 'websearch-transformer.cjs';
|
||||
@@ -123,11 +124,21 @@ export function ensureHookConfig(): boolean {
|
||||
if (hooks?.PreToolUse) {
|
||||
const webSearchHookIndex = hooks.PreToolUse.findIndex((h: unknown) => {
|
||||
const hook = h as Record<string, unknown>;
|
||||
return hook.matcher === 'WebSearch';
|
||||
if (hook.matcher !== 'WebSearch') return false;
|
||||
|
||||
const hookArray = hook.hooks as Array<Record<string, unknown>> | undefined;
|
||||
const command = hookArray?.[0]?.command;
|
||||
if (typeof command !== 'string') return false;
|
||||
|
||||
const normalized = command.replace(/\\/g, '/');
|
||||
return normalized.includes('.ccs/hooks/websearch-transformer');
|
||||
});
|
||||
|
||||
if (webSearchHookIndex !== -1) {
|
||||
// Hook exists - check if it needs updating
|
||||
// Hook exists - first clean up any duplicates
|
||||
const hadDuplicates = deduplicateCcsHooks(settings);
|
||||
|
||||
// Then check if it needs updating
|
||||
const existingHook = hooks.PreToolUse[webSearchHookIndex] as Record<string, unknown>;
|
||||
const existingHooks = existingHook.hooks as Array<Record<string, unknown>>;
|
||||
const currentHookConfig = getWebSearchHookConfig();
|
||||
@@ -147,10 +158,13 @@ export function ensureHookConfig(): boolean {
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
if (needsUpdate) {
|
||||
// Combine into single write if either changed
|
||||
if (hadDuplicates || needsUpdate) {
|
||||
fs.writeFileSync(getClaudeSettingsPath(), JSON.stringify(settings, null, 2), 'utf8');
|
||||
if (process.env.CCS_DEBUG) {
|
||||
console.error(info('Updated WebSearch hook config in settings.json'));
|
||||
if (hadDuplicates)
|
||||
console.error(info('Removed duplicate WebSearch hooks from settings.json'));
|
||||
if (needsUpdate) console.error(info('Updated WebSearch hook config in settings.json'));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -170,6 +184,12 @@ export function ensureHookConfig(): boolean {
|
||||
settingsHooks.PreToolUse = [];
|
||||
}
|
||||
|
||||
// Remove any existing CCS hooks first to prevent duplicates
|
||||
settingsHooks.PreToolUse = settingsHooks.PreToolUse.filter((h: unknown) => {
|
||||
const hook = h as Record<string, unknown>;
|
||||
return !isCcsWebSearchHook(hook);
|
||||
});
|
||||
|
||||
// Add our hook config
|
||||
const preToolUseHooks = hookConfig.PreToolUse as unknown[];
|
||||
settingsHooks.PreToolUse.push(...preToolUseHooks);
|
||||
@@ -229,7 +249,9 @@ export function removeHookConfig(): boolean {
|
||||
if (!hookArray?.[0]?.command) return true; // Keep malformed entries
|
||||
|
||||
const command = hookArray[0].command as string;
|
||||
return !command.includes('.ccs/hooks/websearch-transformer'); // Remove if CCS hook
|
||||
// Normalize path separators for cross-platform matching (Windows uses backslashes)
|
||||
const normalizedCommand = command.replace(/\\/g, '/');
|
||||
return !normalizedCommand.includes('.ccs/hooks/websearch-transformer'); // Remove if CCS hook
|
||||
});
|
||||
|
||||
if (hooks.PreToolUse.length === originalLength) {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* WebSearch Hook Utilities
|
||||
*
|
||||
* Shared helper functions for WebSearch hook detection and deduplication.
|
||||
*
|
||||
* @module utils/websearch/hook-utils
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check if a hook entry is a CCS WebSearch hook
|
||||
* Normalizes path separators for cross-platform matching (Windows uses backslashes)
|
||||
*/
|
||||
export function isCcsWebSearchHook(hook: Record<string, unknown>): boolean {
|
||||
if (hook.matcher !== 'WebSearch') return false;
|
||||
|
||||
const hookArray = hook.hooks as Array<Record<string, unknown>> | undefined;
|
||||
if (!hookArray?.[0]?.command) return false;
|
||||
|
||||
const command = hookArray[0].command;
|
||||
if (typeof command !== 'string') return false;
|
||||
|
||||
// Normalize path separators for cross-platform matching
|
||||
const normalizedCommand = command.replace(/\\/g, '/');
|
||||
return normalizedCommand.includes('.ccs/hooks/websearch-transformer');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove duplicate CCS WebSearch hooks from settings, keeping only the first one
|
||||
* Returns true if duplicates were removed
|
||||
*/
|
||||
export function deduplicateCcsHooks(settings: Record<string, unknown>): boolean {
|
||||
const hooks = settings.hooks as Record<string, unknown[]> | undefined;
|
||||
if (!hooks?.PreToolUse) return false;
|
||||
|
||||
let foundFirst = false;
|
||||
const originalLength = hooks.PreToolUse.length;
|
||||
|
||||
hooks.PreToolUse = hooks.PreToolUse.filter((h: unknown) => {
|
||||
const hook = h as Record<string, unknown>;
|
||||
if (!isCcsWebSearchHook(hook)) return true; // Keep non-CCS hooks
|
||||
|
||||
if (!foundFirst) {
|
||||
foundFirst = true;
|
||||
return true; // Keep first CCS hook
|
||||
}
|
||||
return false; // Remove subsequent duplicates
|
||||
});
|
||||
|
||||
return hooks.PreToolUse.length < originalLength;
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import { getWebSearchHookConfig, getHookPath } from './hook-config';
|
||||
import { getWebSearchConfig } from '../../config/unified-config-loader';
|
||||
import { removeHookConfig } from './hook-config';
|
||||
import { getCcsDir } from '../config-manager';
|
||||
import { isCcsWebSearchHook, deduplicateCcsHooks } from './hook-utils';
|
||||
|
||||
// Valid profile name pattern (alphanumeric, dash, underscore only)
|
||||
const VALID_PROFILE_NAME = /^[a-zA-Z0-9_-]+$/;
|
||||
@@ -33,14 +34,7 @@ function hasCcsHook(settings: Record<string, unknown>): boolean {
|
||||
if (!hooks?.PreToolUse) return false;
|
||||
|
||||
return hooks.PreToolUse.some((h: unknown) => {
|
||||
const hook = h as Record<string, unknown>;
|
||||
if (hook.matcher !== 'WebSearch') return false;
|
||||
|
||||
const hookArray = hook.hooks as Array<Record<string, unknown>> | undefined;
|
||||
if (!hookArray?.[0]?.command) return false;
|
||||
|
||||
const command = hookArray[0].command as string;
|
||||
return command.includes('.ccs/hooks/websearch-transformer');
|
||||
return isCcsWebSearchHook(h as Record<string, unknown>);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -126,6 +120,16 @@ export function ensureProfileHooks(profileName: string): boolean {
|
||||
|
||||
// Check if CCS hook already present
|
||||
if (hasCcsHook(settings)) {
|
||||
// Clean up any duplicates that may have accumulated (Windows path bug fix)
|
||||
const hadDuplicates = deduplicateCcsHooks(settings);
|
||||
if (hadDuplicates) {
|
||||
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
||||
if (process.env.CCS_DEBUG) {
|
||||
console.error(
|
||||
info(`Removed duplicate WebSearch hooks from ${profileName}.settings.json`)
|
||||
);
|
||||
}
|
||||
}
|
||||
// Update timeout if needed
|
||||
return updateHookTimeoutIfNeeded(settings, settingsPath);
|
||||
}
|
||||
@@ -188,8 +192,11 @@ function updateHookTimeoutIfNeeded(
|
||||
const hookArray = hook.hooks as Array<Record<string, unknown>>;
|
||||
if (!hookArray?.[0]?.command) continue;
|
||||
|
||||
const command = hookArray[0].command as string;
|
||||
if (!command.includes('.ccs/hooks/websearch-transformer')) continue;
|
||||
const command = hookArray[0].command;
|
||||
if (typeof command !== 'string') continue;
|
||||
// Normalize path separators for cross-platform matching (Windows uses backslashes)
|
||||
const normalizedCommand = command.replace(/\\/g, '/');
|
||||
if (!normalizedCommand.includes('.ccs/hooks/websearch-transformer')) continue;
|
||||
|
||||
// Found CCS hook - check if needs update
|
||||
if (hookArray[0].command !== expectedCommand) {
|
||||
|
||||
Reference in New Issue
Block a user