sumulige-claude 1.3.0 → 1.3.2

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 (38) hide show
  1. package/.claude/MEMORY.md +9 -0
  2. package/.claude/commands/audit.md +147 -0
  3. package/.claude/commands/gha.md +136 -0
  4. package/.claude/commands/handoff.md +93 -0
  5. package/.claude/handoffs/INDEX.md +21 -0
  6. package/.claude/handoffs/LATEST.md +76 -0
  7. package/.claude/handoffs/handoff_2026-01-22T13-07-04-757Z.md +76 -0
  8. package/.claude/hooks/auto-handoff.cjs +353 -0
  9. package/.claude/hooks/memory-loader.cjs +208 -0
  10. package/.claude/hooks/memory-saver.cjs +268 -0
  11. package/.claude/rag/skill-index.json +15 -34
  12. package/.claude/sessions/session_2026-01-22T13-07-26-625Z.json +23 -0
  13. package/.claude/settings.json +40 -0
  14. package/.claude/settings.local.json +10 -1
  15. package/.claude/skills/api-tester/SKILL.md +61 -0
  16. package/.claude/skills/api-tester/examples/basic.md +3 -0
  17. package/.claude/skills/api-tester/metadata.yaml +30 -0
  18. package/.claude/skills/api-tester/templates/default.md +3 -0
  19. package/.claude/skills/code-reviewer-123/SKILL.md +61 -0
  20. package/.claude/skills/code-reviewer-123/examples/basic.md +3 -0
  21. package/.claude/skills/code-reviewer-123/metadata.yaml +30 -0
  22. package/.claude/skills/code-reviewer-123/templates/default.md +3 -0
  23. package/.claude/skills/my-skill/SKILL.md +61 -0
  24. package/.claude/skills/my-skill/examples/basic.md +3 -0
  25. package/.claude/skills/my-skill/metadata.yaml +30 -0
  26. package/.claude/skills/my-skill/templates/default.md +3 -0
  27. package/.claude/skills/template/SKILL.md +6 -0
  28. package/.claude/skills/template/metadata.yaml +30 -0
  29. package/.claude/skills/test-skill-name/SKILL.md +61 -0
  30. package/.claude/skills/test-skill-name/examples/basic.md +3 -0
  31. package/.claude/skills/test-skill-name/metadata.yaml +30 -0
  32. package/.claude/skills/test-skill-name/templates/default.md +3 -0
  33. package/CHANGELOG.md +45 -0
  34. package/cli.js +4 -0
  35. package/development/todos/.state.json +3 -1
  36. package/lib/commands.js +51 -0
  37. package/lib/permission-audit.js +255 -0
  38. package/package.json +1 -1
@@ -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
+ };
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Memory Loader Hook - SessionStart Auto-Load System
4
+ *
5
+ * Claude Official Hook: SessionStart
6
+ * Triggered: Once at the beginning of each session
7
+ *
8
+ * Features:
9
+ * - Auto-load MEMORY.md for recent context
10
+ * - Auto-load ANCHORS.md for module navigation
11
+ * - Restore TODO state from .state.json
12
+ * - Inject session context summary
13
+ *
14
+ * Environment Variables:
15
+ * - CLAUDE_PROJECT_DIR: Project directory path
16
+ * - CLAUDE_SESSION_ID: Unique session 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 MEMORY_FILE = path.join(CLAUDE_DIR, 'MEMORY.md');
25
+ const ANCHORS_FILE = path.join(CLAUDE_DIR, 'ANCHORS.md');
26
+ const STATE_FILE = path.join(PROJECT_DIR, 'development', 'todos', '.state.json');
27
+ const SESSION_ID = process.env.CLAUDE_SESSION_ID || 'unknown';
28
+
29
+ /**
30
+ * Load memory file content (recent entries only)
31
+ */
32
+ function loadMemory(days = 7) {
33
+ if (!fs.existsSync(MEMORY_FILE)) {
34
+ return { exists: false, content: '', entries: 0 };
35
+ }
36
+
37
+ const content = fs.readFileSync(MEMORY_FILE, 'utf-8');
38
+ const entries = content.split('## ').slice(1, days + 1);
39
+
40
+ return {
41
+ exists: true,
42
+ content: entries.length > 0 ? '## ' + entries.join('## ') : '',
43
+ entries: entries.length
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Load anchors file for quick navigation
49
+ */
50
+ function loadAnchors() {
51
+ if (!fs.existsSync(ANCHORS_FILE)) {
52
+ return { exists: false, content: '', modules: 0 };
53
+ }
54
+
55
+ const content = fs.readFileSync(ANCHORS_FILE, 'utf-8');
56
+ const moduleMatches = content.match(/##\s+[\w-]+/g) || [];
57
+
58
+ return {
59
+ exists: true,
60
+ content: content,
61
+ modules: moduleMatches.length
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Load TODO state
67
+ */
68
+ function loadTodoState() {
69
+ if (!fs.existsSync(STATE_FILE)) {
70
+ return { exists: false, active: 0, completed: 0 };
71
+ }
72
+
73
+ try {
74
+ const state = JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8'));
75
+ return {
76
+ exists: true,
77
+ active: state.active?.length || 0,
78
+ completed: state.completed?.length || 0,
79
+ lastUpdated: state.lastUpdated || null
80
+ };
81
+ } catch (e) {
82
+ return { exists: false, active: 0, completed: 0 };
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Get project info
88
+ */
89
+ function getProjectInfo() {
90
+ const pkgPath = path.join(PROJECT_DIR, 'package.json');
91
+ if (!fs.existsSync(pkgPath)) {
92
+ return { name: path.basename(PROJECT_DIR), version: 'unknown' };
93
+ }
94
+
95
+ try {
96
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
97
+ return {
98
+ name: pkg.name || path.basename(PROJECT_DIR),
99
+ version: pkg.version || 'unknown'
100
+ };
101
+ } catch (e) {
102
+ return { name: path.basename(PROJECT_DIR), version: 'unknown' };
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Generate session start context
108
+ */
109
+ function generateSessionContext() {
110
+ const memory = loadMemory();
111
+ const anchors = loadAnchors();
112
+ const todos = loadTodoState();
113
+ const project = getProjectInfo();
114
+
115
+ const timestamp = new Date().toISOString();
116
+
117
+ // Build context object
118
+ const context = {
119
+ session: {
120
+ id: SESSION_ID,
121
+ startTime: timestamp,
122
+ project: project.name,
123
+ version: project.version
124
+ },
125
+ memory: {
126
+ loaded: memory.exists,
127
+ entries: memory.entries
128
+ },
129
+ anchors: {
130
+ loaded: anchors.exists,
131
+ modules: anchors.modules
132
+ },
133
+ todos: {
134
+ loaded: todos.exists,
135
+ active: todos.active,
136
+ completed: todos.completed
137
+ }
138
+ };
139
+
140
+ // Save session state
141
+ const sessionStateFile = path.join(CLAUDE_DIR, '.session-state.json');
142
+ try {
143
+ fs.writeFileSync(sessionStateFile, JSON.stringify(context, null, 2));
144
+ } catch (e) {
145
+ // Ignore write errors
146
+ }
147
+
148
+ return context;
149
+ }
150
+
151
+ /**
152
+ * Format session summary for output
153
+ */
154
+ function formatSessionSummary(context) {
155
+ let summary = '';
156
+
157
+ summary += `\nšŸ“‚ Session: ${context.session.project} v${context.session.version}\n`;
158
+
159
+ if (context.memory.loaded && context.memory.entries > 0) {
160
+ summary += `šŸ’¾ Memory: ${context.memory.entries} entries loaded\n`;
161
+ }
162
+
163
+ if (context.anchors.loaded && context.anchors.modules > 0) {
164
+ summary += `šŸ”– Anchors: ${context.anchors.modules} modules indexed\n`;
165
+ }
166
+
167
+ if (context.todos.loaded && (context.todos.active > 0 || context.todos.completed > 0)) {
168
+ summary += `šŸ“‹ TODOs: ${context.todos.active} active, ${context.todos.completed} completed\n`;
169
+ }
170
+
171
+ return summary;
172
+ }
173
+
174
+ /**
175
+ * Main execution
176
+ */
177
+ function main() {
178
+ try {
179
+ const context = generateSessionContext();
180
+
181
+ // Only output summary if there's meaningful context
182
+ const hasContext = context.memory.entries > 0 ||
183
+ context.anchors.modules > 0 ||
184
+ context.todos.active > 0;
185
+
186
+ if (hasContext) {
187
+ console.log(formatSessionSummary(context));
188
+ }
189
+
190
+ process.exit(0);
191
+ } catch (e) {
192
+ // Silent failure - don't interrupt session
193
+ process.exit(0);
194
+ }
195
+ }
196
+
197
+ // Run
198
+ if (require.main === module) {
199
+ main();
200
+ }
201
+
202
+ module.exports = {
203
+ loadMemory,
204
+ loadAnchors,
205
+ loadTodoState,
206
+ generateSessionContext,
207
+ formatSessionSummary
208
+ };