@jhihjian/claude-daemon 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/install.sh ADDED
@@ -0,0 +1,257 @@
1
+ #!/bin/bash
2
+ # Claude Code 会话历史系统 - 一键安装脚本
3
+ # 支持在任何电脑上快速部署
4
+
5
+ set -e
6
+
7
+ # 颜色输出
8
+ RED='\033[0;31m'
9
+ GREEN='\033[0;32m'
10
+ YELLOW='\033[1;33m'
11
+ NC='\033[0m' # No Color
12
+
13
+ echo -e "${GREEN}======================================${NC}"
14
+ echo -e "${GREEN}Claude Code 会话历史系统 - 安装程序${NC}"
15
+ echo -e "${GREEN}======================================${NC}"
16
+ echo ""
17
+
18
+ # 检测系统
19
+ OS=$(uname -s)
20
+ echo -e "${YELLOW}检测到系统: $OS${NC}"
21
+
22
+ # 获取脚本所在目录
23
+ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
24
+ echo -e "${YELLOW}安装源目录: $SCRIPT_DIR${NC}"
25
+ echo ""
26
+
27
+ # 1. 检查 Bun
28
+ echo -e "${GREEN}[1/6] 检查 Bun 运行时...${NC}"
29
+ if command -v bun &> /dev/null; then
30
+ BUN_PATH=$(which bun)
31
+ echo -e "${GREEN}✓ Bun 已安装: $BUN_PATH${NC}"
32
+ else
33
+ echo -e "${YELLOW}⚠ Bun 未安装,正在安装...${NC}"
34
+ curl -fsSL https://bun.sh/install | bash
35
+
36
+ # 添加到当前 shell
37
+ export BUN_INSTALL="$HOME/.bun"
38
+ export PATH="$BUN_INSTALL/bin:$PATH"
39
+
40
+ BUN_PATH="$HOME/.bun/bin/bun"
41
+ echo -e "${GREEN}✓ Bun 安装完成: $BUN_PATH${NC}"
42
+ fi
43
+ echo ""
44
+
45
+ # 2. 创建目录结构(设置安全权限)
46
+ echo -e "${GREEN}[2/6] 创建目录结构...${NC}"
47
+ mkdir -p ~/.claude/SESSIONS/raw
48
+ mkdir -p ~/.claude/SESSIONS/analysis/summaries
49
+ mkdir -p ~/.claude/SESSIONS/analysis/by-type
50
+ mkdir -p ~/.claude/SESSIONS/analysis/by-directory
51
+ mkdir -p ~/.claude/SESSIONS/index
52
+ mkdir -p ~/.claude/hooks
53
+
54
+ # 设置目录权限:700(仅所有者可访问)
55
+ chmod 700 ~/.claude/SESSIONS
56
+ chmod -R 700 ~/.claude/SESSIONS/*
57
+ chmod 700 ~/.claude/hooks
58
+
59
+ echo -e "${GREEN}✓ 目录创建完成(权限:700)${NC}"
60
+ echo ""
61
+
62
+ # 3. 配置 hooks(使用 #!/usr/bin/env bun)
63
+ echo -e "${GREEN}[3/6] 配置 hooks...${NC}"
64
+ for hook in "$SCRIPT_DIR/hooks"/*.ts; do
65
+ if [ -f "$hook" ]; then
66
+ hook_name=$(basename "$hook")
67
+ target_hook=~/.claude/hooks/"$hook_name"
68
+
69
+ # 直接复制文件(保留 #!/usr/bin/env bun)
70
+ cp "$hook" "$target_hook"
71
+
72
+ # 设置权限:700(仅所有者可读写执行)
73
+ chmod 700 "$target_hook"
74
+
75
+ echo -e "${GREEN} ✓ $hook_name${NC}"
76
+ fi
77
+ done
78
+ echo ""
79
+
80
+ # 4. 配置 Claude Code settings
81
+ echo -e "${GREEN}[4/7] 配置 Claude Code...${NC}"
82
+ SETTINGS_FILE=~/.claude/settings.json
83
+
84
+ if [ -f "$SETTINGS_FILE" ]; then
85
+ echo -e "${YELLOW}⚠ settings.json 已存在,备份到 settings.json.backup${NC}"
86
+ cp "$SETTINGS_FILE" "$SETTINGS_FILE.backup"
87
+ fi
88
+
89
+ # 创建或更新 settings.json
90
+ cat > "$SETTINGS_FILE" << EOF
91
+ {
92
+ "model": "opus",
93
+ "hooks": {
94
+ "SessionStart": [
95
+ {
96
+ "hooks": [
97
+ {
98
+ "type": "command",
99
+ "command": "$HOME/.claude/hooks/SessionRecorder.hook.ts"
100
+ }
101
+ ]
102
+ }
103
+ ],
104
+ "PostToolUse": [
105
+ {
106
+ "hooks": [
107
+ {
108
+ "type": "command",
109
+ "command": "$HOME/.claude/hooks/SessionToolCapture-v2.hook.ts"
110
+ }
111
+ ]
112
+ }
113
+ ],
114
+ "Stop": [
115
+ {
116
+ "hooks": [
117
+ {
118
+ "type": "command",
119
+ "command": "$HOME/.claude/hooks/SessionAnalyzer.hook.ts"
120
+ }
121
+ ]
122
+ }
123
+ ]
124
+ }
125
+ }
126
+ EOF
127
+
128
+ echo -e "${GREEN}✓ settings.json 配置完成${NC}"
129
+ echo ""
130
+
131
+ # 5. 安装查询工具
132
+ echo -e "${GREEN}[5/7] 安装查询工具...${NC}"
133
+ mkdir -p ~/bin
134
+
135
+ # 创建查询工具的包装脚本
136
+ cat > ~/bin/claude-sessions << EOF
137
+ #!/bin/bash
138
+ # Claude 会话历史查询工具
139
+
140
+ BUN_PATH="$BUN_PATH"
141
+ TOOLS_DIR="$SCRIPT_DIR/tools"
142
+
143
+ case "\$1" in
144
+ recent|type|dir)
145
+ \$BUN_PATH \$TOOLS_DIR/SessionQuery.ts "\$@"
146
+ ;;
147
+ stats)
148
+ shift
149
+ \$BUN_PATH \$TOOLS_DIR/SessionStats.ts "\$@"
150
+ ;;
151
+ show)
152
+ \$TOOLS_DIR/show-conversation.sh "\$2"
153
+ ;;
154
+ *)
155
+ echo "用法:"
156
+ echo " claude-sessions recent [N] - 查看最近 N 个会话"
157
+ echo " claude-sessions type <类型> - 查看指定类型的会话"
158
+ echo " claude-sessions dir <目录> - 查看指定目录的会话"
159
+ echo " claude-sessions stats global - 查看全局统计"
160
+ echo " claude-sessions show <会话ID> - 查看会话详情"
161
+ ;;
162
+ esac
163
+ EOF
164
+
165
+ chmod +x ~/bin/claude-sessions
166
+ echo -e "${GREEN}✓ 查询工具安装完成${NC}"
167
+ echo ""
168
+
169
+ # 6. 添加到 PATH
170
+ echo -e "${GREEN}[6/7] 配置环境变量...${NC}"
171
+
172
+ # 检测 shell
173
+ if [ -n "$ZSH_VERSION" ]; then
174
+ SHELL_RC=~/.zshrc
175
+ elif [ -n "$BASH_VERSION" ]; then
176
+ SHELL_RC=~/.bashrc
177
+ else
178
+ SHELL_RC=~/.profile
179
+ fi
180
+
181
+ # 添加 PATH
182
+ if ! grep -q "export PATH=\"\$HOME/bin:\$PATH\"" "$SHELL_RC"; then
183
+ echo "" >> "$SHELL_RC"
184
+ echo "# Claude 会话历史工具" >> "$SHELL_RC"
185
+ echo "export PATH=\"\$HOME/bin:\$PATH\"" >> "$SHELL_RC"
186
+ echo -e "${GREEN}✓ 已添加到 $SHELL_RC${NC}"
187
+ else
188
+ echo -e "${GREEN}✓ PATH 已配置${NC}"
189
+ fi
190
+
191
+ echo ""
192
+
193
+ # 7. 验证安装
194
+ echo -e "${GREEN}[7/7] 验证安装...${NC}"
195
+
196
+ # 检查 Bun 是否在 PATH 中
197
+ if command -v bun &> /dev/null; then
198
+ echo -e "${GREEN} ✓ Bun 在 PATH 中${NC}"
199
+ else
200
+ echo -e "${YELLOW} ⚠ Bun 不在 PATH 中,hooks 可能无法执行${NC}"
201
+ echo -e "${YELLOW} 请确保 ~/.bun/bin 在 PATH 中${NC}"
202
+ fi
203
+
204
+ # 检查 hooks 是否可执行
205
+ hooks_ok=true
206
+ for hook_name in SessionRecorder.hook.ts SessionToolCapture-v2.hook.ts SessionAnalyzer.hook.ts; do
207
+ hook_file=~/.claude/hooks/$hook_name
208
+ if [ -x "$hook_file" ]; then
209
+ echo -e "${GREEN} ✓ $hook_name 可执行${NC}"
210
+ else
211
+ echo -e "${RED} ✗ $hook_name 不可执行${NC}"
212
+ hooks_ok=false
213
+ fi
214
+ done
215
+
216
+ # 检查目录权限
217
+ if [ -d ~/.claude/SESSIONS ] && [ "$(stat -c %a ~/.claude/SESSIONS 2>/dev/null || stat -f %A ~/.claude/SESSIONS 2>/dev/null)" = "700" ]; then
218
+ echo -e "${GREEN} ✓ 目录权限正确(700)${NC}"
219
+ else
220
+ echo -e "${YELLOW} ⚠ 目录权限可能不正确${NC}"
221
+ fi
222
+
223
+ # 检查 lib 目录
224
+ if [ -d "$SCRIPT_DIR/lib" ] && [ -f "$SCRIPT_DIR/lib/logger.ts" ]; then
225
+ echo -e "${GREEN} ✓ lib 目录存在${NC}"
226
+ else
227
+ echo -e "${RED} ✗ lib 目录不存在或不完整${NC}"
228
+ hooks_ok=false
229
+ fi
230
+
231
+ echo ""
232
+
233
+ if [ "$hooks_ok" = true ]; then
234
+ echo -e "${GREEN}======================================${NC}"
235
+ echo -e "${GREEN}✓ 安装完成!${NC}"
236
+ echo -e "${GREEN}======================================${NC}"
237
+ else
238
+ echo -e "${YELLOW}======================================${NC}"
239
+ echo -e "${YELLOW}⚠ 安装完成,但有警告${NC}"
240
+ echo -e "${YELLOW}======================================${NC}"
241
+ fi
242
+
243
+ echo ""
244
+ echo -e "${YELLOW}使用方法:${NC}"
245
+ echo ""
246
+ echo -e " ${GREEN}claude-sessions recent 5${NC} # 查看最近 5 个会话"
247
+ echo -e " ${GREEN}claude-sessions stats global${NC} # 查看统计信息"
248
+ echo -e " ${GREEN}claude-sessions show <ID>${NC} # 查看会话详情"
249
+ echo ""
250
+ echo -e "${YELLOW}重要提示:${NC}"
251
+ echo -e " 1. 重新加载 shell 配置: ${GREEN}source $SHELL_RC${NC}"
252
+ echo -e " 2. 或者重启终端"
253
+ echo -e " 3. 设置日志级别: ${GREEN}export SESSION_LOG_LEVEL=DEBUG${NC}"
254
+ echo ""
255
+ echo -e "${YELLOW}数据同步:${NC}"
256
+ echo -e " 查看同步指南: ${GREEN}cat $SCRIPT_DIR/SYNC-GUIDE.md${NC}"
257
+ echo ""
package/lib/config.ts ADDED
@@ -0,0 +1,223 @@
1
+ /**
2
+ * config.ts
3
+ * 统一的配置管理模块
4
+ *
5
+ * 功能:
6
+ * - 集中管理所有配置项
7
+ * - 支持环境变量覆盖
8
+ * - 提供默认值
9
+ */
10
+
11
+ import { join } from 'path';
12
+ import { homedir } from 'os';
13
+ import { existsSync, readFileSync } from 'fs';
14
+ import { safeJSONParse } from './errors.ts';
15
+
16
+ export interface SessionConfig {
17
+ // 路径配置
18
+ paiDir: string;
19
+ sessionsDir: string;
20
+ rawDir: string;
21
+ analysisDir: string;
22
+ summariesDir: string;
23
+ indexDir: string;
24
+
25
+ // 行为配置
26
+ maxOutputLength: number;
27
+ hookTimeout: number;
28
+ gitTimeout: number;
29
+
30
+ // 分类阈值
31
+ classificationThresholds: {
32
+ coding: number; // Edit/Write 占比
33
+ debugging: number; // 测试命令 + Read > Edit
34
+ research: number; // Search 占比
35
+ writing: number; // Markdown 文件编辑占比
36
+ git: number; // Git 命令占比
37
+ };
38
+
39
+ // 日志配置
40
+ logLevel: string;
41
+
42
+ // 性能配置
43
+ enableCache: boolean;
44
+ cacheTTL: number;
45
+ }
46
+
47
+ class ConfigManager {
48
+ private config: SessionConfig;
49
+ private configFilePath?: string;
50
+
51
+ constructor() {
52
+ this.config = this.loadConfig();
53
+ }
54
+
55
+ private loadConfig(): SessionConfig {
56
+ // 默认配置
57
+ const defaults: SessionConfig = {
58
+ // 路径配置
59
+ paiDir: process.env.PAI_DIR || join(homedir(), '.claude'),
60
+ sessionsDir: '',
61
+ rawDir: '',
62
+ analysisDir: '',
63
+ summariesDir: '',
64
+ indexDir: '',
65
+
66
+ // 行为配置
67
+ maxOutputLength: parseInt(process.env.MAX_OUTPUT_LENGTH || '5000', 10),
68
+ hookTimeout: parseInt(process.env.HOOK_TIMEOUT || '10000', 10),
69
+ gitTimeout: parseInt(process.env.GIT_TIMEOUT || '3000', 10),
70
+
71
+ // 分类阈值
72
+ classificationThresholds: {
73
+ coding: parseFloat(process.env.THRESHOLD_CODING || '0.4'),
74
+ debugging: parseFloat(process.env.THRESHOLD_DEBUGGING || '0.0'),
75
+ research: parseFloat(process.env.THRESHOLD_RESEARCH || '0.3'),
76
+ writing: parseFloat(process.env.THRESHOLD_WRITING || '0.5'),
77
+ git: parseFloat(process.env.THRESHOLD_GIT || '0.5'),
78
+ },
79
+
80
+ // 日志配置
81
+ logLevel: process.env.SESSION_LOG_LEVEL || 'INFO',
82
+
83
+ // 性能配置
84
+ enableCache: process.env.ENABLE_CACHE !== 'false',
85
+ cacheTTL: parseInt(process.env.CACHE_TTL || '300000', 10), // 5 分钟
86
+ };
87
+
88
+ // 计算派生路径
89
+ defaults.sessionsDir = join(defaults.paiDir, 'SESSIONS');
90
+ defaults.rawDir = join(defaults.sessionsDir, 'raw');
91
+ defaults.analysisDir = join(defaults.sessionsDir, 'analysis');
92
+ defaults.summariesDir = join(defaults.analysisDir, 'summaries');
93
+ defaults.indexDir = join(defaults.sessionsDir, 'index');
94
+
95
+ // 尝试从配置文件加载
96
+ this.configFilePath = join(defaults.paiDir, 'session-config.json');
97
+ if (existsSync(this.configFilePath)) {
98
+ try {
99
+ const fileContent = readFileSync(this.configFilePath, 'utf-8');
100
+ const fileConfig = safeJSONParse<Partial<SessionConfig>>(
101
+ fileContent,
102
+ {},
103
+ 'session-config.json'
104
+ );
105
+
106
+ // 合并配置(文件配置优先,但环境变量最优先)
107
+ return this.mergeConfig(defaults, fileConfig);
108
+ } catch (error) {
109
+ // 配置文件加载失败,使用默认配置
110
+ console.error('[Config] Failed to load config file, using defaults');
111
+ }
112
+ }
113
+
114
+ return defaults;
115
+ }
116
+
117
+ private mergeConfig(defaults: SessionConfig, fileConfig: Partial<SessionConfig>): SessionConfig {
118
+ return {
119
+ ...defaults,
120
+ ...fileConfig,
121
+ // 确保环境变量优先
122
+ paiDir: process.env.PAI_DIR || fileConfig.paiDir || defaults.paiDir,
123
+ maxOutputLength: parseInt(
124
+ process.env.MAX_OUTPUT_LENGTH ||
125
+ String(fileConfig.maxOutputLength || defaults.maxOutputLength),
126
+ 10
127
+ ),
128
+ hookTimeout: parseInt(
129
+ process.env.HOOK_TIMEOUT ||
130
+ String(fileConfig.hookTimeout || defaults.hookTimeout),
131
+ 10
132
+ ),
133
+ gitTimeout: parseInt(
134
+ process.env.GIT_TIMEOUT ||
135
+ String(fileConfig.gitTimeout || defaults.gitTimeout),
136
+ 10
137
+ ),
138
+ logLevel: process.env.SESSION_LOG_LEVEL || fileConfig.logLevel || defaults.logLevel,
139
+ enableCache: process.env.ENABLE_CACHE !== 'false' &&
140
+ (fileConfig.enableCache ?? defaults.enableCache),
141
+ cacheTTL: parseInt(
142
+ process.env.CACHE_TTL ||
143
+ String(fileConfig.cacheTTL || defaults.cacheTTL),
144
+ 10
145
+ ),
146
+ };
147
+ }
148
+
149
+ /**
150
+ * 获取配置
151
+ */
152
+ get(): SessionConfig {
153
+ return { ...this.config };
154
+ }
155
+
156
+ /**
157
+ * 获取特定配置项
158
+ */
159
+ getPath(key: keyof Pick<SessionConfig, 'paiDir' | 'sessionsDir' | 'rawDir' | 'analysisDir' | 'summariesDir' | 'indexDir'>): string {
160
+ return this.config[key];
161
+ }
162
+
163
+ /**
164
+ * 获取会话文件路径
165
+ */
166
+ getSessionFilePath(sessionId: string, yearMonth: string): string {
167
+ return join(this.config.rawDir, yearMonth, `session-${sessionId}.jsonl`);
168
+ }
169
+
170
+ /**
171
+ * 获取摘要文件路径
172
+ */
173
+ getSummaryFilePath(sessionId: string, yearMonth: string): string {
174
+ return join(this.config.summariesDir, yearMonth, `summary-${sessionId}.json`);
175
+ }
176
+
177
+ /**
178
+ * 获取类型索引路径
179
+ */
180
+ getTypeIndexPath(sessionType: string): string {
181
+ return join(this.config.analysisDir, 'by-type', sessionType, 'sessions.json');
182
+ }
183
+
184
+ /**
185
+ * 获取目录索引路径
186
+ */
187
+ getDirectoryIndexPath(dirHash: string): string {
188
+ return join(this.config.analysisDir, 'by-directory', dirHash, 'sessions.json');
189
+ }
190
+
191
+ /**
192
+ * 获取全局元数据路径
193
+ */
194
+ getMetadataPath(): string {
195
+ return join(this.config.indexDir, 'metadata.json');
196
+ }
197
+
198
+ /**
199
+ * 获取年月字符串
200
+ */
201
+ getYearMonth(date: Date = new Date()): string {
202
+ return date.toISOString().slice(0, 7);
203
+ }
204
+
205
+ /**
206
+ * 重新加载配置
207
+ */
208
+ reload(): void {
209
+ this.config = this.loadConfig();
210
+ }
211
+ }
212
+
213
+ /**
214
+ * 全局配置实例
215
+ */
216
+ export const config = new ConfigManager();
217
+
218
+ /**
219
+ * 便捷函数
220
+ */
221
+ export function getConfig(): SessionConfig {
222
+ return config.get();
223
+ }
package/lib/errors.ts ADDED
@@ -0,0 +1,213 @@
1
+ /**
2
+ * errors.ts
3
+ * 统一的错误处理模块
4
+ *
5
+ * 功能:
6
+ * - 自定义错误类型
7
+ * - 错误恢复策略
8
+ * - 友好的错误消息
9
+ */
10
+
11
+ import { logger } from './logger.ts';
12
+
13
+ /**
14
+ * 基础错误类
15
+ */
16
+ export class SessionError extends Error {
17
+ constructor(
18
+ message: string,
19
+ public readonly code: string,
20
+ public readonly recoverable: boolean = true,
21
+ public readonly context?: Record<string, any>
22
+ ) {
23
+ super(message);
24
+ this.name = 'SessionError';
25
+ }
26
+
27
+ toJSON() {
28
+ return {
29
+ name: this.name,
30
+ message: this.message,
31
+ code: this.code,
32
+ recoverable: this.recoverable,
33
+ context: this.context,
34
+ };
35
+ }
36
+ }
37
+
38
+ /**
39
+ * 文件系统错误
40
+ */
41
+ export class FileSystemError extends SessionError {
42
+ constructor(message: string, filePath: string, operation: string) {
43
+ super(message, 'FS_ERROR', true, { filePath, operation });
44
+ this.name = 'FileSystemError';
45
+ }
46
+ }
47
+
48
+ /**
49
+ * 解析错误
50
+ */
51
+ export class ParseError extends SessionError {
52
+ constructor(message: string, data: string) {
53
+ super(message, 'PARSE_ERROR', true, { data: data.slice(0, 100) });
54
+ this.name = 'ParseError';
55
+ }
56
+ }
57
+
58
+ /**
59
+ * 配置错误
60
+ */
61
+ export class ConfigError extends SessionError {
62
+ constructor(message: string, configKey?: string) {
63
+ super(message, 'CONFIG_ERROR', false, { configKey });
64
+ this.name = 'ConfigError';
65
+ }
66
+ }
67
+
68
+ /**
69
+ * 超时错误
70
+ */
71
+ export class TimeoutError extends SessionError {
72
+ constructor(operation: string, timeout: number) {
73
+ super(`Operation timed out: ${operation}`, 'TIMEOUT_ERROR', true, {
74
+ operation,
75
+ timeout,
76
+ });
77
+ this.name = 'TimeoutError';
78
+ }
79
+ }
80
+
81
+ /**
82
+ * 执行带超时的异步操作
83
+ */
84
+ export async function withTimeout<T>(
85
+ promise: Promise<T>,
86
+ timeout: number,
87
+ operation: string
88
+ ): Promise<T> {
89
+ return Promise.race([
90
+ promise,
91
+ new Promise<T>((_, reject) =>
92
+ setTimeout(() => reject(new TimeoutError(operation, timeout)), timeout)
93
+ ),
94
+ ]);
95
+ }
96
+
97
+ /**
98
+ * 安全执行函数(捕获并记录错误,但不抛出)
99
+ */
100
+ export async function safeExecute<T>(
101
+ fn: () => Promise<T> | T,
102
+ fallback: T,
103
+ context: string
104
+ ): Promise<T> {
105
+ try {
106
+ return await fn();
107
+ } catch (error) {
108
+ logger.error(`Error in ${context}`, {
109
+ error: error instanceof Error ? error.message : String(error),
110
+ stack: error instanceof Error ? error.stack : undefined,
111
+ });
112
+ return fallback;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * 重试执行函数
118
+ */
119
+ export async function retry<T>(
120
+ fn: () => Promise<T>,
121
+ options: {
122
+ maxAttempts?: number;
123
+ delay?: number;
124
+ backoff?: number;
125
+ context?: string;
126
+ } = {}
127
+ ): Promise<T> {
128
+ const { maxAttempts = 3, delay = 1000, backoff = 2, context = 'operation' } = options;
129
+
130
+ let lastError: Error | undefined;
131
+
132
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
133
+ try {
134
+ return await fn();
135
+ } catch (error) {
136
+ lastError = error instanceof Error ? error : new Error(String(error));
137
+
138
+ if (attempt < maxAttempts) {
139
+ const waitTime = delay * Math.pow(backoff, attempt - 1);
140
+ logger.warn(`Retry ${attempt}/${maxAttempts} for ${context}`, {
141
+ error: lastError.message,
142
+ nextRetryIn: waitTime,
143
+ });
144
+ await new Promise(resolve => setTimeout(resolve, waitTime));
145
+ }
146
+ }
147
+ }
148
+
149
+ throw lastError || new Error(`Failed after ${maxAttempts} attempts`);
150
+ }
151
+
152
+ /**
153
+ * Hook 错误处理包装器
154
+ * 确保 Hook 永远不会阻塞 Claude Code
155
+ */
156
+ export function hookErrorHandler(hookName: string) {
157
+ return (error: unknown): void => {
158
+ // 记录错误但不抛出
159
+ logger.error(`Hook ${hookName} failed`, {
160
+ error: error instanceof Error ? error.message : String(error),
161
+ stack: error instanceof Error ? error.stack : undefined,
162
+ recoverable: error instanceof SessionError ? error.recoverable : true,
163
+ });
164
+
165
+ // Hook 失败不应阻塞 Claude Code
166
+ // 只记录到 stderr,让系统继续运行
167
+ };
168
+ }
169
+
170
+ /**
171
+ * 验证函数
172
+ */
173
+ export function validateRequired<T>(
174
+ value: T | null | undefined,
175
+ fieldName: string
176
+ ): T {
177
+ if (value === null || value === undefined) {
178
+ throw new ConfigError(`Required field missing: ${fieldName}`, fieldName);
179
+ }
180
+ return value;
181
+ }
182
+
183
+ /**
184
+ * 验证文件路径
185
+ */
186
+ export function validatePath(path: string, fieldName: string = 'path'): string {
187
+ if (!path || typeof path !== 'string') {
188
+ throw new ConfigError(`Invalid path: ${fieldName}`, fieldName);
189
+ }
190
+ if (path.includes('\0')) {
191
+ throw new ConfigError(`Path contains null bytes: ${fieldName}`, fieldName);
192
+ }
193
+ return path;
194
+ }
195
+
196
+ /**
197
+ * 安全的 JSON 解析
198
+ */
199
+ export function safeJSONParse<T>(
200
+ data: string,
201
+ fallback: T,
202
+ context: string = 'JSON'
203
+ ): T {
204
+ try {
205
+ return JSON.parse(data) as T;
206
+ } catch (error) {
207
+ logger.warn(`Failed to parse ${context}`, {
208
+ error: error instanceof Error ? error.message : String(error),
209
+ dataPreview: data.slice(0, 100),
210
+ });
211
+ return fallback;
212
+ }
213
+ }