sumulige-claude 1.3.2 → 1.4.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.
@@ -0,0 +1,81 @@
1
+ # /workflow - 统一工作流命令
2
+
3
+ 一键执行常见工作流操作。
4
+
5
+ ## 可用子命令
6
+
7
+ ### `/workflow check` - 检查状态
8
+ 检查 sumulige-claude 更新和项目状态。
9
+
10
+ ```bash
11
+ # 检查版本和更新
12
+ smc sync --check-update
13
+ ```
14
+
15
+ ### `/workflow pull` - 拉取更新
16
+ 增量同步 sumulige-claude 更新到当前项目。
17
+
18
+ ```bash
19
+ # 增量同步(推荐)
20
+ smc sync --incremental
21
+
22
+ # 强制全量同步
23
+ smc sync --hooks
24
+ ```
25
+
26
+ ### `/workflow task <description>` - 执行任务
27
+ 标准任务执行流程:
28
+ 1. 分析任务需求
29
+ 2. 创建 TODO 列表
30
+ 3. 逐步实现
31
+ 4. 更新文档
32
+
33
+ ### `/workflow sync` - 同步文档
34
+ 更新项目文档和记忆:
35
+ - MEMORY.md - 最新变更
36
+ - PROJECT_LOG.md - 历史记录
37
+ - CHANGELOG.md - 版本日志
38
+
39
+ ### `/workflow commit <message>` - 提交变更
40
+ Git 提交工作流:
41
+ 1. 检查变更状态
42
+ 2. 暂存相关文件
43
+ 3. 提交并生成消息
44
+
45
+ ### `/workflow push` - 推送远程
46
+ 将本地变更推送到远程仓库。
47
+
48
+ ### `/workflow full <description>` - 一键完整流程
49
+ 执行完整的开发工作流:
50
+
51
+ ```
52
+ 1. 检查更新 → 2. 增量同步 → 3. 执行任务 → 4. 更新文档 → 5. 提交 → 6. 推送
53
+ ```
54
+
55
+ 示例:
56
+ ```
57
+ /workflow full "实现用户认证功能"
58
+ ```
59
+
60
+ ## 使用示例
61
+
62
+ ```
63
+ # 日常任务
64
+ /workflow task "修复登录 bug"
65
+
66
+ # 完整流程
67
+ /workflow full "添加暗黑模式"
68
+
69
+ # 仅同步文档
70
+ /workflow sync
71
+
72
+ # 仅提交
73
+ /workflow commit "fix: 修复登录问题"
74
+ ```
75
+
76
+ ## 注意事项
77
+
78
+ - 任务执行前会自动检查更新
79
+ - 增量同步只更新变更的文件
80
+ - 文档同步包括 MEMORY.md 和 PROJECT_LOG.md
81
+ - 提交前会自动运行测试(如配置)
@@ -0,0 +1,353 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Auto Handoff Hook - PreCompact Context Preservation
4
+ *
5
+ * Claude Official Hook: PreCompact
6
+ * Triggered: Before conversation context is compressed
7
+ *
8
+ * Features:
9
+ * - Auto-generate handoff document before context compression
10
+ * - Preserve critical context that might be lost during compaction
11
+ * - Save current state including progress, blockers, and next steps
12
+ *
13
+ * Environment Variables:
14
+ * - CLAUDE_PROJECT_DIR: Project directory path
15
+ * - CLAUDE_SESSION_ID: Unique session identifier
16
+ * - CLAUDE_CONVERSATION_ID: Conversation identifier
17
+ */
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+
22
+ const PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
23
+ const CLAUDE_DIR = path.join(PROJECT_DIR, '.claude');
24
+ const HANDOFFS_DIR = path.join(CLAUDE_DIR, 'handoffs');
25
+ const SESSION_STATE_FILE = path.join(CLAUDE_DIR, '.session-state.json');
26
+ const STATE_FILE = path.join(PROJECT_DIR, 'development', 'todos', '.state.json');
27
+ const SESSION_ID = process.env.CLAUDE_SESSION_ID || 'unknown';
28
+ const CONVERSATION_ID = process.env.CLAUDE_CONVERSATION_ID || 'unknown';
29
+
30
+ /**
31
+ * Ensure handoffs directory exists
32
+ */
33
+ function ensureHandoffsDir() {
34
+ if (!fs.existsSync(HANDOFFS_DIR)) {
35
+ fs.mkdirSync(HANDOFFS_DIR, { recursive: true });
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Load current session state
41
+ */
42
+ function loadSessionState() {
43
+ if (!fs.existsSync(SESSION_STATE_FILE)) {
44
+ return {
45
+ session: { project: path.basename(PROJECT_DIR) },
46
+ memory: { entries: 0 },
47
+ todos: { active: 0, completed: 0 }
48
+ };
49
+ }
50
+
51
+ try {
52
+ return JSON.parse(fs.readFileSync(SESSION_STATE_FILE, 'utf-8'));
53
+ } catch (e) {
54
+ return {
55
+ session: { project: path.basename(PROJECT_DIR) },
56
+ memory: { entries: 0 },
57
+ todos: { active: 0, completed: 0 }
58
+ };
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Load active TODOs
64
+ */
65
+ function loadActiveTodos() {
66
+ const todosDir = path.join(PROJECT_DIR, 'development', 'todos', 'active');
67
+ if (!fs.existsSync(todosDir)) {
68
+ return [];
69
+ }
70
+
71
+ try {
72
+ const files = fs.readdirSync(todosDir)
73
+ .filter(f => f.endsWith('.md') && f !== '_README.md');
74
+
75
+ return files.map(f => {
76
+ const content = fs.readFileSync(path.join(todosDir, f), 'utf-8');
77
+ const titleMatch = content.match(/^#\s+(.+)$/m);
78
+ return {
79
+ file: f,
80
+ title: titleMatch ? titleMatch[1] : path.basename(f, '.md')
81
+ };
82
+ });
83
+ } catch (e) {
84
+ return [];
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Get recently modified files
90
+ */
91
+ function getRecentlyModifiedFiles(hours = 24) {
92
+ const recentFiles = [];
93
+ const cutoff = Date.now() - (hours * 60 * 60 * 1000);
94
+
95
+ // Check common source directories
96
+ const sourceDirs = ['src', 'lib', '.claude', 'development'];
97
+
98
+ for (const dir of sourceDirs) {
99
+ const fullPath = path.join(PROJECT_DIR, dir);
100
+ if (!fs.existsSync(fullPath)) continue;
101
+
102
+ try {
103
+ const walkDir = (dirPath, depth = 0) => {
104
+ if (depth > 3) return; // Limit depth
105
+
106
+ const items = fs.readdirSync(dirPath, { withFileTypes: true });
107
+ for (const item of items) {
108
+ const itemPath = path.join(dirPath, item.name);
109
+
110
+ if (item.isDirectory() && !item.name.startsWith('.') && item.name !== 'node_modules') {
111
+ walkDir(itemPath, depth + 1);
112
+ } else if (item.isFile()) {
113
+ try {
114
+ const stat = fs.statSync(itemPath);
115
+ if (stat.mtimeMs > cutoff) {
116
+ const relativePath = path.relative(PROJECT_DIR, itemPath);
117
+ recentFiles.push({
118
+ path: relativePath,
119
+ modified: stat.mtime.toISOString()
120
+ });
121
+ }
122
+ } catch (e) {
123
+ // Ignore stat errors
124
+ }
125
+ }
126
+ }
127
+ };
128
+
129
+ walkDir(fullPath);
130
+ } catch (e) {
131
+ // Ignore directory errors
132
+ }
133
+ }
134
+
135
+ // Sort by modification time (most recent first)
136
+ recentFiles.sort((a, b) => b.modified.localeCompare(a.modified));
137
+
138
+ return recentFiles.slice(0, 20); // Return top 20
139
+ }
140
+
141
+ /**
142
+ * Generate handoff document
143
+ */
144
+ function generateHandoff(sessionState) {
145
+ const now = new Date();
146
+ const todos = loadActiveTodos();
147
+ const recentFiles = getRecentlyModifiedFiles();
148
+
149
+ let content = `# Handoff: Pre-Compact Context Preservation
150
+
151
+ > Auto-generated before context compression
152
+ > Date: ${now.toISOString()}
153
+ > Session: ${SESSION_ID}
154
+ > Conversation: ${CONVERSATION_ID}
155
+
156
+ ---
157
+
158
+ ## Session Info
159
+
160
+ - **Project**: ${sessionState.session?.project || 'unknown'}
161
+ - **Version**: ${sessionState.session?.version || 'unknown'}
162
+ - **Start Time**: ${sessionState.session?.startTime || 'unknown'}
163
+
164
+ ---
165
+
166
+ ## Memory State
167
+
168
+ - **Entries Loaded**: ${sessionState.memory?.entries || 0}
169
+ - **Anchors Modules**: ${sessionState.anchors?.modules || 0}
170
+
171
+ ---
172
+
173
+ ## Active TODOs (${todos.length})
174
+
175
+ `;
176
+
177
+ if (todos.length > 0) {
178
+ todos.forEach(todo => {
179
+ content += `- [ ] ${todo.title} (\`${todo.file}\`)\n`;
180
+ });
181
+ } else {
182
+ content += `*No active TODOs*\n`;
183
+ }
184
+
185
+ content += `
186
+
187
+ ---
188
+
189
+ ## Recently Modified Files (Last 24h)
190
+
191
+ `;
192
+
193
+ if (recentFiles.length > 0) {
194
+ recentFiles.slice(0, 10).forEach(f => {
195
+ content += `- \`${f.path}\` (${f.modified.split('T')[0]})\n`;
196
+ });
197
+ if (recentFiles.length > 10) {
198
+ content += `- *...and ${recentFiles.length - 10} more files*\n`;
199
+ }
200
+ } else {
201
+ content += `*No recently modified files*\n`;
202
+ }
203
+
204
+ content += `
205
+
206
+ ---
207
+
208
+ ## Context Preservation Notes
209
+
210
+ **Important**: This handoff was auto-generated before context compaction.
211
+ The following information should be re-loaded after compaction:
212
+
213
+ 1. Read \`.claude/MEMORY.md\` for recent session context
214
+ 2. Check \`development/todos/INDEX.md\` for task status
215
+ 3. Review recent git commits for code changes
216
+
217
+ ---
218
+
219
+ ## Recovery Commands
220
+
221
+ \`\`\`bash
222
+ # View recent memory
223
+ cat .claude/MEMORY.md | head -100
224
+
225
+ # Check active TODOs
226
+ ls development/todos/active/
227
+
228
+ # View recent changes
229
+ git log --oneline -10
230
+ git status
231
+ \`\`\`
232
+
233
+ ---
234
+
235
+ *Auto-generated by auto-handoff.cjs at ${now.toISOString()}*
236
+ `;
237
+
238
+ return content;
239
+ }
240
+
241
+ /**
242
+ * Save handoff document
243
+ */
244
+ function saveHandoff(content) {
245
+ ensureHandoffsDir();
246
+
247
+ const now = new Date();
248
+ const filename = `handoff_${now.toISOString().replace(/[:.]/g, '-')}.md`;
249
+ const filepath = path.join(HANDOFFS_DIR, filename);
250
+
251
+ fs.writeFileSync(filepath, content);
252
+
253
+ // Also save as latest handoff for easy access
254
+ const latestPath = path.join(HANDOFFS_DIR, 'LATEST.md');
255
+ fs.writeFileSync(latestPath, content);
256
+
257
+ // Clean up old handoffs (keep last 10)
258
+ const files = fs.readdirSync(HANDOFFS_DIR)
259
+ .filter(f => f.startsWith('handoff_') && f.endsWith('.md'))
260
+ .sort()
261
+ .reverse();
262
+
263
+ if (files.length > 10) {
264
+ files.slice(10).forEach(f => {
265
+ try {
266
+ fs.unlinkSync(path.join(HANDOFFS_DIR, f));
267
+ } catch (e) {
268
+ // Ignore deletion errors
269
+ }
270
+ });
271
+ }
272
+
273
+ return filename;
274
+ }
275
+
276
+ /**
277
+ * Update handoffs index
278
+ */
279
+ function updateHandoffsIndex() {
280
+ const indexPath = path.join(HANDOFFS_DIR, 'INDEX.md');
281
+
282
+ const files = fs.readdirSync(HANDOFFS_DIR)
283
+ .filter(f => f.startsWith('handoff_') && f.endsWith('.md'))
284
+ .sort()
285
+ .reverse();
286
+
287
+ let content = `# Handoffs Index
288
+
289
+ > Auto-generated context preservation documents
290
+ > Updated: ${new Date().toISOString()}
291
+
292
+ ---
293
+
294
+ ## Recent Handoffs (${files.length})
295
+
296
+ `;
297
+
298
+ files.slice(0, 20).forEach(f => {
299
+ const filepath = path.join(HANDOFFS_DIR, f);
300
+ const stat = fs.statSync(filepath);
301
+ content += `- [${f}](./${f}) - ${stat.mtime.toISOString()}\n`;
302
+ });
303
+
304
+ content += `
305
+
306
+ ---
307
+
308
+ ## Latest Handoff
309
+
310
+ See [LATEST.md](./LATEST.md) for the most recent context snapshot.
311
+
312
+ ---
313
+
314
+ *Index maintained by auto-handoff.cjs*
315
+ `;
316
+
317
+ fs.writeFileSync(indexPath, content);
318
+ }
319
+
320
+ /**
321
+ * Main execution
322
+ */
323
+ function main() {
324
+ try {
325
+ const sessionState = loadSessionState();
326
+ const content = generateHandoff(sessionState);
327
+ const filename = saveHandoff(content);
328
+ updateHandoffsIndex();
329
+
330
+ console.log(`\n⚡ PreCompact: Context preserved → ${filename}`);
331
+ console.log(` Recovery: .claude/handoffs/LATEST.md\n`);
332
+
333
+ process.exit(0);
334
+ } catch (e) {
335
+ // Silent failure - don't interrupt compaction
336
+ console.error(`PreCompact handoff error: ${e.message}`);
337
+ process.exit(0);
338
+ }
339
+ }
340
+
341
+ // Run
342
+ if (require.main === module) {
343
+ main();
344
+ }
345
+
346
+ module.exports = {
347
+ loadSessionState,
348
+ loadActiveTodos,
349
+ getRecentlyModifiedFiles,
350
+ generateHandoff,
351
+ saveHandoff,
352
+ updateHandoffsIndex
353
+ };