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.
- package/.claude/.sumulige-claude-version +1 -0
- package/.claude/AGENTS.md +6 -6
- package/.claude/commands/workflow.md +81 -0
- package/.claude/hooks/auto-handoff.cjs +0 -0
- package/.claude/hooks/hook-dispatcher.cjs +304 -0
- package/.claude/hooks/hook-registry.json +73 -0
- package/.claude/hooks/lib/cache.cjs +161 -0
- package/.claude/hooks/lib/fs-utils.cjs +133 -0
- package/.claude/hooks/memory-loader.cjs +0 -0
- package/.claude/hooks/memory-saver.cjs +0 -0
- package/.claude/hooks/rag-skill-loader.cjs +84 -4
- package/.claude/settings.json +8 -82
- package/.claude/settings.local.json +4 -1
- package/CHANGELOG.md +70 -0
- package/README.md +158 -1
- package/cli.js +1 -1
- package/config/version-manifest.json +85 -0
- package/lib/commands.js +139 -0
- package/lib/incremental-sync.js +274 -0
- package/lib/version-manifest.js +171 -0
- package/package.json +1 -1
- package/template/.claude/commands/workflow.md +81 -0
- package/template/.claude/hooks/auto-handoff.cjs +353 -0
- package/template/.claude/hooks/hook-dispatcher.cjs +304 -0
- package/template/.claude/hooks/hook-registry.json +73 -0
- package/template/.claude/hooks/lib/cache.cjs +161 -0
- package/template/.claude/hooks/lib/fs-utils.cjs +133 -0
- package/template/.claude/hooks/memory-loader.cjs +208 -0
- package/template/.claude/hooks/memory-saver.cjs +268 -0
- package/template/.claude/hooks/rag-skill-loader.cjs +84 -4
- package/template/.claude/settings.json +36 -70
- package/template/CHANGELOG.md +297 -0
- package/template/README.md +558 -88
|
@@ -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
|
+
};
|