sumulige-claude 1.0.5 → 1.0.7

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.
Files changed (44) hide show
  1. package/.claude/.kickoff-hint.txt +51 -0
  2. package/.claude/ANCHORS.md +40 -0
  3. package/.claude/MEMORY.md +34 -0
  4. package/.claude/PROJECT_LOG.md +101 -0
  5. package/.claude/THINKING_CHAIN_GUIDE.md +287 -0
  6. package/.claude/commands/commit-push-pr.md +59 -0
  7. package/.claude/commands/commit.md +53 -0
  8. package/.claude/commands/pr.md +76 -0
  9. package/.claude/commands/review.md +61 -0
  10. package/.claude/commands/sessions.md +62 -0
  11. package/.claude/commands/skill-create.md +131 -0
  12. package/.claude/commands/test.md +56 -0
  13. package/.claude/commands/todos.md +99 -0
  14. package/.claude/commands/verify-work.md +63 -0
  15. package/.claude/hooks/code-formatter.cjs +187 -0
  16. package/.claude/hooks/code-tracer.cjs +331 -0
  17. package/.claude/hooks/conversation-recorder.cjs +340 -0
  18. package/.claude/hooks/decision-tracker.cjs +398 -0
  19. package/.claude/hooks/export.cjs +329 -0
  20. package/.claude/hooks/multi-session.cjs +181 -0
  21. package/.claude/hooks/privacy-filter.js +224 -0
  22. package/.claude/hooks/project-kickoff.cjs +114 -0
  23. package/.claude/hooks/rag-skill-loader.cjs +159 -0
  24. package/.claude/hooks/session-end.sh +61 -0
  25. package/.claude/hooks/sync-to-log.sh +83 -0
  26. package/.claude/hooks/thinking-silent.cjs +145 -0
  27. package/.claude/hooks/tl-summary.sh +54 -0
  28. package/.claude/hooks/todo-manager.cjs +248 -0
  29. package/.claude/hooks/verify-work.cjs +134 -0
  30. package/.claude/sessions/active-sessions.json +444 -0
  31. package/.claude/settings.local.json +36 -2
  32. package/.claude/thinking-routes/.last-sync +1 -0
  33. package/.claude/thinking-routes/QUICKREF.md +98 -0
  34. package/CHANGELOG.md +56 -0
  35. package/DEV_TOOLS_GUIDE.md +190 -0
  36. package/PROJECT_STRUCTURE.md +10 -1
  37. package/README.md +20 -6
  38. package/cli.js +85 -824
  39. package/config/defaults.json +34 -0
  40. package/development/todos/INDEX.md +14 -58
  41. package/lib/commands.js +698 -0
  42. package/lib/config.js +71 -0
  43. package/lib/utils.js +62 -0
  44. package/package.json +2 -2
@@ -0,0 +1,329 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Export Tool - 导出对话和决策数据
4
+ *
5
+ * 功能:
6
+ * - 导出为 JSON 格式
7
+ * - 导出为 Markdown(可转换为 PDF)
8
+ * - 支持按日期范围导出
9
+ * - 支持按主题导出
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ const PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
16
+ const TRANSCRIPTS_DIR = path.join(PROJECT_DIR, '.claude', 'transcripts');
17
+ const DECISIONS_DIR = path.join(PROJECT_DIR, '.claude', 'decisions');
18
+ const CODE_TRACE_DIR = path.join(PROJECT_DIR, '.claude', 'code-trace');
19
+ const EXPORT_DIR = path.join(PROJECT_DIR, '.claude', 'export');
20
+
21
+ // 确保导出目录存在
22
+ try { fs.mkdirSync(EXPORT_DIR, { recursive: true }); } catch (e) {}
23
+
24
+ /**
25
+ * 获取今日日期字符串
26
+ */
27
+ function getDateStamp() {
28
+ const now = new Date();
29
+ return now.toISOString().split('T')[0];
30
+ }
31
+
32
+ /**
33
+ * 读取所有 transcripts
34
+ */
35
+ function readAllTranscripts() {
36
+ const transcripts = [];
37
+
38
+ function readDir(dir, prefix = '') {
39
+ if (!fs.existsSync(dir)) return;
40
+
41
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
42
+ entries.forEach(entry => {
43
+ const fullPath = path.join(dir, entry.name);
44
+ if (entry.isDirectory()) {
45
+ readDir(fullPath, prefix + entry.name + '/');
46
+ } else if (entry.name.endsWith('.md')) {
47
+ const content = fs.readFileSync(fullPath, 'utf-8');
48
+ transcripts.push({
49
+ path: prefix + entry.name,
50
+ fullPath,
51
+ content,
52
+ size: content.length,
53
+ modified: fs.statSync(fullPath).mtime
54
+ });
55
+ }
56
+ });
57
+ }
58
+
59
+ readDir(TRANSCRIPTS_DIR);
60
+ return transcripts.sort((a, b) => a.path.localeCompare(b.path));
61
+ }
62
+
63
+ /**
64
+ * 读取所有决策
65
+ */
66
+ function readAllDecisions() {
67
+ const decisionsPath = path.join(DECISIONS_DIR, 'DECISIONS.md');
68
+ if (!fs.existsSync(decisionsPath)) {
69
+ return { content: '', count: 0 };
70
+ }
71
+
72
+ const content = fs.readFileSync(decisionsPath, 'utf-8');
73
+ const blocks = content.split(/^## /m).filter(s => s.trim());
74
+
75
+ return {
76
+ content,
77
+ count: blocks.length,
78
+ blocks
79
+ };
80
+ }
81
+
82
+ /**
83
+ * 读取代码追踪数据
84
+ */
85
+ function readCodeTrace() {
86
+ const filesMapPath = path.join(CODE_TRACE_DIR, 'files-map.json');
87
+ const decisionsMapPath = path.join(CODE_TRACE_DIR, 'decisions-map.json');
88
+
89
+ let filesMap = { files: {}, lastUpdated: null };
90
+ let decisionsMap = { decisions: {}, lastUpdated: null };
91
+
92
+ if (fs.existsSync(filesMapPath)) {
93
+ filesMap = JSON.parse(fs.readFileSync(filesMapPath, 'utf-8'));
94
+ }
95
+ if (fs.existsSync(decisionsMapPath)) {
96
+ decisionsMap = JSON.parse(fs.readFileSync(decisionsMapPath, 'utf-8'));
97
+ }
98
+
99
+ return { filesMap, decisionsMap };
100
+ }
101
+
102
+ /**
103
+ * 导出为 JSON
104
+ */
105
+ function exportJSON(options = {}) {
106
+ const { includeTranscripts = true, includeDecisions = true, includeTrace = true } = options;
107
+
108
+ const data = {
109
+ exportedAt: new Date().toISOString(),
110
+ project: path.basename(PROJECT_DIR),
111
+ version: '1.0'
112
+ };
113
+
114
+ if (includeTranscripts) {
115
+ data.transcripts = readAllTranscripts();
116
+ data.transcriptCount = data.transcripts.length;
117
+ }
118
+
119
+ if (includeDecisions) {
120
+ const decisions = readAllDecisions();
121
+ data.decisions = decisions;
122
+ data.decisionCount = decisions.count;
123
+ }
124
+
125
+ if (includeTrace) {
126
+ const trace = readCodeTrace();
127
+ data.codeTrace = trace;
128
+ }
129
+
130
+ return data;
131
+ }
132
+
133
+ /**
134
+ * 导出为 Markdown
135
+ */
136
+ function exportMarkdown(options = {}) {
137
+ const { includeTranscripts = true, includeDecisions = true, includeTrace = true } = options;
138
+
139
+ let md = `# Thinking Chain Export\n\n`;
140
+ md += `**项目**: ${path.basename(PROJECT_DIR)}\n`;
141
+ md += `**导出时间**: ${new Date().toLocaleString('zh-CN')}\n\n`;
142
+ md += `---\n\n`;
143
+
144
+ if (includeTranscripts) {
145
+ md += `## 📝 对话记录\n\n`;
146
+ const transcripts = readAllTranscripts();
147
+ transcripts.forEach(t => {
148
+ md += `### ${t.path}\n\n`;
149
+ md += t.content + '\n\n---\n\n';
150
+ });
151
+ }
152
+
153
+ if (includeDecisions) {
154
+ md += `## 🔗 决策记录\n\n`;
155
+ const decisions = readAllDecisions();
156
+ md += decisions.content + '\n\n---\n\n';
157
+ }
158
+
159
+ if (includeTrace) {
160
+ md += `## 🔍 代码追踪\n\n`;
161
+ const trace = readCodeTrace();
162
+ md += `### 文件关联\n\n`;
163
+ Object.entries(trace.filesMap.files || {}).forEach(([file, data]) => {
164
+ md += `- **${file}**: ${data.decisions.join(', ') || '无关联'}\n`;
165
+ });
166
+ md += `\n### 决策关联\n\n`;
167
+ Object.entries(trace.decisionsMap.decisions || {}).forEach(([id, data]) => {
168
+ md += `- **${id}**: ${data.files.join(', ') || '无文件'}\n`;
169
+ });
170
+ }
171
+
172
+ return md;
173
+ }
174
+
175
+ /**
176
+ * 保存导出文件
177
+ */
178
+ function saveExport(content, format, filename) {
179
+ const dateStamp = getDateStamp();
180
+ const defaultName = `thinking-chain-${dateStamp}.${format}`;
181
+ const outputName = filename || defaultName;
182
+ const outputPath = path.join(EXPORT_DIR, outputName);
183
+
184
+ if (format === 'json') {
185
+ fs.writeFileSync(outputPath, JSON.stringify(content, null, 2), 'utf-8');
186
+ } else {
187
+ fs.writeFileSync(outputPath, content, 'utf-8');
188
+ }
189
+
190
+ return outputPath;
191
+ }
192
+
193
+ /**
194
+ * 获取导出文件大小
195
+ */
196
+ function getFileSize(filePath) {
197
+ const stats = fs.statSync(filePath);
198
+ const size = stats.size;
199
+ if (size < 1024) return `${size}B`;
200
+ if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)}KB`;
201
+ return `${(size / 1024 / 1024).toFixed(1)}MB`;
202
+ }
203
+
204
+ // CLI
205
+ function main() {
206
+ const args = process.argv.slice(2);
207
+ const command = args[0];
208
+
209
+ switch (command) {
210
+ case 'json': {
211
+ const data = exportJSON();
212
+ const outputPath = saveExport(data, 'json', args[1]);
213
+ console.log(`✅ 导出 JSON → ${outputPath} (${getFileSize(outputPath)})`);
214
+ console.log(` 包含 ${data.transcriptCount || 0} 个对话, ${data.decisionCount || 0} 个决策`);
215
+ break;
216
+ }
217
+
218
+ case 'md': {
219
+ const content = exportMarkdown();
220
+ const outputPath = saveExport(content, 'md', args[1]);
221
+ console.log(`✅ 导出 Markdown → ${outputPath} (${getFileSize(outputPath)})`);
222
+ break;
223
+ }
224
+
225
+ case 'pdf': {
226
+ // Markdown 转 PDF 需要额外工具
227
+ console.log(`
228
+ PDF 导出需要安装额外工具。请选择一种方式:
229
+
230
+ 方式 1 - 使用 pandoc:
231
+ brew install pandoc
232
+ pandoc .claude/export/thinking-chain.md -o .claude/export/thinking-chain.pdf
233
+
234
+ 方式 2 - 使用 markdown-pdf:
235
+ npm install -g markdown-pdf
236
+ markdown-pdf .claude/export/thinking-chain.md
237
+
238
+ 方式 3 - 在 VSCode 中:
239
+ 1. 安装 Markdown PDF 扩展
240
+ 2. 打开 .claude/export/thinking-chain.md
241
+ 3. 右键 → Markdown PDF: Export (pdf)
242
+ `);
243
+
244
+ // 先导出 Markdown
245
+ const content = exportMarkdown();
246
+ const mdPath = saveExport(content, 'md', null);
247
+ console.log(`\n✅ 已准备 Markdown 文件: ${mdPath}`);
248
+ break;
249
+ }
250
+
251
+ case 'decisions': {
252
+ // 只导出决策
253
+ const content = exportMarkdown({ includeTranscripts: false, includeTrace: false });
254
+ const outputPath = saveExport(content, 'md', `decisions-${getDateStamp()}.md`);
255
+ console.log(`✅ 导出决策 → ${outputPath}`);
256
+ break;
257
+ }
258
+
259
+ case 'transcripts': {
260
+ // 只导出对话
261
+ const content = exportMarkdown({ includeDecisions: false, includeTrace: false });
262
+ const outputPath = saveExport(content, 'md', `transcripts-${getDateStamp()}.md`);
263
+ console.log(`✅ 导出对话 → ${outputPath}`);
264
+ break;
265
+ }
266
+
267
+ case 'list': {
268
+ // 列出导出文件
269
+ if (fs.existsSync(EXPORT_DIR)) {
270
+ const files = fs.readdirSync(EXPORT_DIR);
271
+ console.log('\n📂 导出文件:\n');
272
+ files.forEach(f => {
273
+ const filePath = path.join(EXPORT_DIR, f);
274
+ const stats = fs.statSync(filePath);
275
+ console.log(` ${f} (${getFileSize(filePath)}) ${stats.mtime.toLocaleDateString()}`);
276
+ });
277
+ } else {
278
+ console.log('📭 暂无导出文件');
279
+ }
280
+ break;
281
+ }
282
+
283
+ case 'clean': {
284
+ // 清理旧导出
285
+ if (fs.existsSync(EXPORT_DIR)) {
286
+ const files = fs.readdirSync(EXPORT_DIR);
287
+ let cleaned = 0;
288
+ files.forEach(f => {
289
+ const filePath = path.join(EXPORT_DIR, f);
290
+ if (f !== '.gitkeep') {
291
+ fs.unlinkSync(filePath);
292
+ cleaned++;
293
+ }
294
+ });
295
+ console.log(`✅ 清理了 ${cleaned} 个导出文件`);
296
+ }
297
+ break;
298
+ }
299
+
300
+ default:
301
+ console.log(`
302
+ Export Tool - 数据导出工具
303
+
304
+ 用法:
305
+ node export.cjs json [文件名] 导出为 JSON
306
+ node export.cjs md [文件名] 导出为 Markdown
307
+ node export.cjs pdf 导出为 PDF(需要额外工具)
308
+ node export.cjs decisions 只导出决策
309
+ node export.cjs transcripts 只导出对话
310
+ node export.cjs list 列出导出文件
311
+ node export.cjs clean 清理导出文件
312
+
313
+ 快捷命令:
314
+ alias export='node .claude/hooks/export.cjs'
315
+ `);
316
+ }
317
+ }
318
+
319
+ module.exports = {
320
+ exportJSON,
321
+ exportMarkdown,
322
+ readAllTranscripts,
323
+ readAllDecisions,
324
+ readCodeTrace
325
+ };
326
+
327
+ if (require.main === module) {
328
+ main();
329
+ }
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Multi-Session Manager - 并行多会话管理
4
+ *
5
+ * 根据 Boris Cherny 的实践,并行运行 5 个 Claude 可以极大提升效率。
6
+ * 这个 hook 帮助跟踪和管理多个并行会话。
7
+ *
8
+ * 功能:
9
+ * - 跟踪活跃的会话
10
+ * - 为每个会话分配编号
11
+ * - 记录会话状态和任务
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const os = require('os');
17
+
18
+ const PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
19
+ const SESSIONS_DIR = path.join(PROJECT_DIR, '.claude', 'sessions');
20
+ const ACTIVE_SESSIONS_FILE = path.join(SESSIONS_DIR, 'active-sessions.json');
21
+ const SESSION_LOCK_FILE = path.join(SESSIONS_DIR, '.session-lock');
22
+
23
+ // 确保目录存在
24
+ try { fs.mkdirSync(SESSIONS_DIR, { recursive: true }); } catch (e) {}
25
+
26
+ // 获取主机名和用户信息用于标识会话来源
27
+ function getSessionIdentifier() {
28
+ const hostname = os.hostname();
29
+ const platform = os.platform();
30
+ const username = process.env.USER || process.env.USERNAME || 'unknown';
31
+ return `${username}@${hostname} (${platform})`;
32
+ }
33
+
34
+ // 获取当前会话 ID
35
+ function getCurrentSessionId() {
36
+ // 从环境变量或进程信息生成唯一 ID
37
+ const pid = process.pid;
38
+ const ppid = process.ppid;
39
+ const timestamp = Date.now();
40
+ return `session-${pid}-${ppid}`;
41
+ }
42
+
43
+ // 获取活跃会话列表
44
+ function getActiveSessions() {
45
+ if (!fs.existsSync(ACTIVE_SESSIONS_FILE)) {
46
+ return [];
47
+ }
48
+ try {
49
+ const data = fs.readFileSync(ACTIVE_SESSIONS_FILE, 'utf-8');
50
+ const sessions = JSON.parse(data);
51
+ // 清理超过 1 小时的旧会话
52
+ const oneHourAgo = Date.now() - 60 * 60 * 1000;
53
+ return sessions.filter(s => s.lastHeartbeat > oneHourAgo);
54
+ } catch (e) {
55
+ return [];
56
+ }
57
+ }
58
+
59
+ // 保存活跃会话列表
60
+ function saveActiveSessions(sessions) {
61
+ fs.writeFileSync(ACTIVE_SESSIONS_FILE, JSON.stringify(sessions, null, 2));
62
+ }
63
+
64
+ // 更新当前会话的心跳
65
+ function updateHeartbeat() {
66
+ const sessions = getActiveSessions();
67
+ const currentId = getCurrentSessionId();
68
+ const now = Date.now();
69
+
70
+ const existingSession = sessions.find(s => s.id === currentId);
71
+ if (existingSession) {
72
+ existingSession.lastHeartbeat = now;
73
+ existingSession.heartbeatCount = (existingSession.heartbeatCount || 0) + 1;
74
+ } else {
75
+ sessions.push({
76
+ id: currentId,
77
+ identifier: getSessionIdentifier(),
78
+ startedAt: now,
79
+ lastHeartbeat: now,
80
+ heartbeatCount: 1,
81
+ status: 'active'
82
+ });
83
+ }
84
+
85
+ saveActiveSessions(sessions);
86
+ return sessions;
87
+ }
88
+
89
+ // 获取会话编号(1-5,类似 Boris 的标签页编号)
90
+ function getSessionNumber(sessions) {
91
+ const currentId = getCurrentSessionId();
92
+ const session = sessions.find(s => s.id === currentId);
93
+ if (session && session.number) {
94
+ return session.number;
95
+ }
96
+
97
+ // 分配一个可用的编号
98
+ const usedNumbers = new Set(sessions.filter(s => s.number).map(s => s.number));
99
+ for (let i = 1; i <= 10; i++) {
100
+ if (!usedNumbers.has(i)) {
101
+ // 更新会话的编号
102
+ const updatedSessions = getActiveSessions();
103
+ const targetSession = updatedSessions.find(s => s.id === currentId);
104
+ if (targetSession) {
105
+ targetSession.number = i;
106
+ saveActiveSessions(updatedSessions);
107
+ return i;
108
+ }
109
+ return i;
110
+ }
111
+ }
112
+
113
+ return sessions.length + 1;
114
+ }
115
+
116
+ // 显示会话状态
117
+ function displaySessionStatus() {
118
+ const sessions = getActiveSessions();
119
+ const currentId = getCurrentSessionId();
120
+ const currentNumber = getSessionNumber(sessions);
121
+
122
+ console.log(`\n🔄 [多会话] 当前会话: #${currentNumber} | 活跃会话: ${sessions.length}`);
123
+ console.log(` 主机: ${getSessionIdentifier()}`);
124
+
125
+ if (sessions.length > 1) {
126
+ console.log(`\n 其他活跃会话:`);
127
+ sessions
128
+ .filter(s => s.id !== currentId)
129
+ .forEach(s => {
130
+ const number = s.number || '?';
131
+ const duration = Math.round((Date.now() - s.startedAt) / 1000 / 60);
132
+ console.log(` - 会话 #${number}: ${s.identifier} (${duration}分钟前启动)`);
133
+ });
134
+ }
135
+
136
+ console.log('');
137
+ }
138
+
139
+ // 主函数
140
+ function main() {
141
+ try {
142
+ const eventType = process.env.CLAUDE_EVENT_TYPE || '';
143
+ const toolInput = process.env.CLAUDE_TOOL_INPUT || '';
144
+
145
+ // 在每次交互时更新心跳
146
+ const sessions = updateHeartbeat();
147
+ const sessionNumber = getSessionNumber(sessions);
148
+
149
+ // 在会话开始或重要操作时显示状态
150
+ if (eventType === 'UserPromptSubmit' && toolInput.length > 20) {
151
+ // 只在较长的用户输入时显示,避免噪音
152
+ if (sessions.length > 1 || process.env.SHOW_SESSION_STATUS) {
153
+ console.log(`\n🔄 [会话 #${sessionNumber}] 活跃会话: ${sessions.length}\n`);
154
+ }
155
+ }
156
+
157
+ // 在会话结束时清理
158
+ if (eventType === 'AgentStop') {
159
+ const currentId = getCurrentSessionId();
160
+ const updatedSessions = getActiveSessions();
161
+ const index = updatedSessions.findIndex(s => s.id === currentId);
162
+ if (index !== -1) {
163
+ updatedSessions[index].status = 'ended';
164
+ updatedSessions[index].endedAt = Date.now();
165
+ saveActiveSessions(updatedSessions);
166
+ }
167
+ }
168
+
169
+ } catch (e) {
170
+ // 静默处理错误
171
+ }
172
+
173
+ process.exit(0);
174
+ }
175
+
176
+ // 如果直接运行此脚本,显示会话状态
177
+ if (require.main === module && process.argv[2] === '--status') {
178
+ displaySessionStatus();
179
+ } else {
180
+ main();
181
+ }