sumulige-claude 1.3.1 → 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.
- package/.claude/MEMORY.md +9 -0
- package/.claude/handoffs/INDEX.md +21 -0
- package/.claude/handoffs/LATEST.md +76 -0
- package/.claude/handoffs/handoff_2026-01-22T13-07-04-757Z.md +76 -0
- package/.claude/hooks/auto-handoff.cjs +353 -0
- package/.claude/hooks/memory-loader.cjs +208 -0
- package/.claude/hooks/memory-saver.cjs +268 -0
- package/.claude/sessions/session_2026-01-22T13-07-26-625Z.json +23 -0
- package/.claude/settings.json +40 -0
- package/.claude/settings.local.json +5 -1
- package/.claude/skills/api-tester/SKILL.md +61 -0
- package/.claude/skills/api-tester/examples/basic.md +3 -0
- package/.claude/skills/api-tester/metadata.yaml +30 -0
- package/.claude/skills/api-tester/templates/default.md +3 -0
- package/.claude/skills/code-reviewer-123/SKILL.md +61 -0
- package/.claude/skills/code-reviewer-123/examples/basic.md +3 -0
- package/.claude/skills/code-reviewer-123/metadata.yaml +30 -0
- package/.claude/skills/code-reviewer-123/templates/default.md +3 -0
- package/.claude/skills/my-skill/SKILL.md +61 -0
- package/.claude/skills/my-skill/examples/basic.md +3 -0
- package/.claude/skills/my-skill/metadata.yaml +30 -0
- package/.claude/skills/my-skill/templates/default.md +3 -0
- package/.claude/skills/template/SKILL.md +6 -0
- package/.claude/skills/template/metadata.yaml +30 -0
- package/.claude/skills/test-skill-name/SKILL.md +61 -0
- package/.claude/skills/test-skill-name/examples/basic.md +3 -0
- package/.claude/skills/test-skill-name/metadata.yaml +30 -0
- package/.claude/skills/test-skill-name/templates/default.md +3 -0
- package/CHANGELOG.md +20 -0
- package/development/todos/.state.json +3 -1
- package/package.json +1 -1
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Memory Saver Hook - SessionEnd Auto-Save System
|
|
4
|
+
*
|
|
5
|
+
* Claude Official Hook: SessionEnd
|
|
6
|
+
* Triggered: Once at the end of each session
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Auto-save session summary to MEMORY.md
|
|
10
|
+
* - Sync TODO state changes
|
|
11
|
+
* - Archive session state
|
|
12
|
+
* - Generate session statistics
|
|
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 SESSION_STATE_FILE = path.join(CLAUDE_DIR, '.session-state.json');
|
|
26
|
+
const SESSIONS_DIR = path.join(CLAUDE_DIR, 'sessions');
|
|
27
|
+
const STATE_FILE = path.join(PROJECT_DIR, 'development', 'todos', '.state.json');
|
|
28
|
+
const SESSION_ID = process.env.CLAUDE_SESSION_ID || 'unknown';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Ensure directories exist
|
|
32
|
+
*/
|
|
33
|
+
function ensureDirectories() {
|
|
34
|
+
[CLAUDE_DIR, SESSIONS_DIR].forEach(dir => {
|
|
35
|
+
if (!fs.existsSync(dir)) {
|
|
36
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Load session state from SessionStart
|
|
43
|
+
*/
|
|
44
|
+
function loadSessionState() {
|
|
45
|
+
if (!fs.existsSync(SESSION_STATE_FILE)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(fs.readFileSync(SESSION_STATE_FILE, 'utf-8'));
|
|
51
|
+
} catch (e) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Calculate session duration
|
|
58
|
+
*/
|
|
59
|
+
function calculateDuration(startTime) {
|
|
60
|
+
if (!startTime) return 'unknown';
|
|
61
|
+
|
|
62
|
+
const start = new Date(startTime);
|
|
63
|
+
const now = new Date();
|
|
64
|
+
const diffMs = now - start;
|
|
65
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
66
|
+
|
|
67
|
+
if (diffMins < 1) return 'less than 1 minute';
|
|
68
|
+
if (diffMins < 60) return `${diffMins} minutes`;
|
|
69
|
+
|
|
70
|
+
const hours = Math.floor(diffMins / 60);
|
|
71
|
+
const mins = diffMins % 60;
|
|
72
|
+
return `${hours}h ${mins}m`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Save session summary to MEMORY.md
|
|
77
|
+
*/
|
|
78
|
+
function saveToMemory(sessionState) {
|
|
79
|
+
ensureDirectories();
|
|
80
|
+
|
|
81
|
+
const now = new Date();
|
|
82
|
+
const dateStr = now.toISOString().split('T')[0];
|
|
83
|
+
const duration = sessionState?.session?.startTime
|
|
84
|
+
? calculateDuration(sessionState.session.startTime)
|
|
85
|
+
: 'unknown';
|
|
86
|
+
|
|
87
|
+
// Generate session entry
|
|
88
|
+
const entry = `### Session ${now.toISOString()}
|
|
89
|
+
|
|
90
|
+
- **Duration**: ${duration}
|
|
91
|
+
- **Project**: ${sessionState?.session?.project || 'unknown'}
|
|
92
|
+
- **Memory entries**: ${sessionState?.memory?.entries || 0}
|
|
93
|
+
- **TODOs**: ${sessionState?.todos?.active || 0} active, ${sessionState?.todos?.completed || 0} completed
|
|
94
|
+
|
|
95
|
+
`;
|
|
96
|
+
|
|
97
|
+
// Read existing memory
|
|
98
|
+
let content = '';
|
|
99
|
+
if (fs.existsSync(MEMORY_FILE)) {
|
|
100
|
+
content = fs.readFileSync(MEMORY_FILE, 'utf-8');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Check if today's section exists
|
|
104
|
+
const todaySection = `## ${dateStr}`;
|
|
105
|
+
if (content.includes(todaySection)) {
|
|
106
|
+
// Append to today's section
|
|
107
|
+
const parts = content.split(todaySection);
|
|
108
|
+
const beforeToday = parts[0];
|
|
109
|
+
const afterHeader = parts[1];
|
|
110
|
+
|
|
111
|
+
// Find next section or end
|
|
112
|
+
const nextSectionMatch = afterHeader.match(/\n## \d{4}-\d{2}-\d{2}/);
|
|
113
|
+
if (nextSectionMatch) {
|
|
114
|
+
const insertPoint = nextSectionMatch.index;
|
|
115
|
+
const todayContent = afterHeader.slice(0, insertPoint);
|
|
116
|
+
const restContent = afterHeader.slice(insertPoint);
|
|
117
|
+
content = beforeToday + todaySection + todayContent + entry + restContent;
|
|
118
|
+
} else {
|
|
119
|
+
content = beforeToday + todaySection + afterHeader + entry;
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
// Create new day section at the top
|
|
123
|
+
const header = content.startsWith('#') ? '' : '# Memory\n\n<!-- Project memory updated by AI -->\n\n';
|
|
124
|
+
const existingContent = content.replace(/^# Memory\n+(?:<!-- [^>]+ -->\n+)?/, '');
|
|
125
|
+
content = header + `${todaySection}\n\n${entry}` + existingContent;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Keep only last 7 days
|
|
129
|
+
const sections = content.split(/(?=\n## \d{4}-\d{2}-\d{2})/);
|
|
130
|
+
const header = sections[0];
|
|
131
|
+
const daySections = sections.slice(1, 8); // Keep 7 days max
|
|
132
|
+
content = header + daySections.join('');
|
|
133
|
+
|
|
134
|
+
fs.writeFileSync(MEMORY_FILE, content.trim() + '\n');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Archive session state
|
|
139
|
+
*/
|
|
140
|
+
function archiveSession(sessionState) {
|
|
141
|
+
ensureDirectories();
|
|
142
|
+
|
|
143
|
+
const now = new Date();
|
|
144
|
+
const filename = `session_${now.toISOString().replace(/[:.]/g, '-')}.json`;
|
|
145
|
+
const filepath = path.join(SESSIONS_DIR, filename);
|
|
146
|
+
|
|
147
|
+
const archiveData = {
|
|
148
|
+
...sessionState,
|
|
149
|
+
endTime: now.toISOString(),
|
|
150
|
+
duration: sessionState?.session?.startTime
|
|
151
|
+
? calculateDuration(sessionState.session.startTime)
|
|
152
|
+
: 'unknown'
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
fs.writeFileSync(filepath, JSON.stringify(archiveData, null, 2));
|
|
156
|
+
|
|
157
|
+
// Clean up old sessions (keep last 20)
|
|
158
|
+
const files = fs.readdirSync(SESSIONS_DIR)
|
|
159
|
+
.filter(f => f.startsWith('session_') && f.endsWith('.json'))
|
|
160
|
+
.sort()
|
|
161
|
+
.reverse();
|
|
162
|
+
|
|
163
|
+
if (files.length > 20) {
|
|
164
|
+
files.slice(20).forEach(f => {
|
|
165
|
+
try {
|
|
166
|
+
fs.unlinkSync(path.join(SESSIONS_DIR, f));
|
|
167
|
+
} catch (e) {
|
|
168
|
+
// Ignore deletion errors
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return filename;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Update TODO state sync timestamp
|
|
178
|
+
*/
|
|
179
|
+
function syncTodoState() {
|
|
180
|
+
if (!fs.existsSync(STATE_FILE)) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const state = JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8'));
|
|
186
|
+
state.lastSynced = new Date().toISOString();
|
|
187
|
+
state.sessionId = SESSION_ID;
|
|
188
|
+
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
189
|
+
} catch (e) {
|
|
190
|
+
// Ignore sync errors
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Clean up session state file
|
|
196
|
+
*/
|
|
197
|
+
function cleanupSessionState() {
|
|
198
|
+
if (fs.existsSync(SESSION_STATE_FILE)) {
|
|
199
|
+
try {
|
|
200
|
+
fs.unlinkSync(SESSION_STATE_FILE);
|
|
201
|
+
} catch (e) {
|
|
202
|
+
// Ignore cleanup errors
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Format session end summary
|
|
209
|
+
*/
|
|
210
|
+
function formatEndSummary(sessionState, archiveFile) {
|
|
211
|
+
let summary = '';
|
|
212
|
+
|
|
213
|
+
const duration = sessionState?.session?.startTime
|
|
214
|
+
? calculateDuration(sessionState.session.startTime)
|
|
215
|
+
: 'unknown';
|
|
216
|
+
|
|
217
|
+
summary += `\n✅ Session ended (${duration})\n`;
|
|
218
|
+
summary += `💾 Memory saved to MEMORY.md\n`;
|
|
219
|
+
summary += `📁 Archived: ${archiveFile}\n`;
|
|
220
|
+
|
|
221
|
+
return summary;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Main execution
|
|
226
|
+
*/
|
|
227
|
+
function main() {
|
|
228
|
+
try {
|
|
229
|
+
const sessionState = loadSessionState();
|
|
230
|
+
|
|
231
|
+
if (sessionState) {
|
|
232
|
+
// Save to memory
|
|
233
|
+
saveToMemory(sessionState);
|
|
234
|
+
|
|
235
|
+
// Archive session
|
|
236
|
+
const archiveFile = archiveSession(sessionState);
|
|
237
|
+
|
|
238
|
+
// Sync TODO state
|
|
239
|
+
syncTodoState();
|
|
240
|
+
|
|
241
|
+
// Output summary
|
|
242
|
+
console.log(formatEndSummary(sessionState, archiveFile));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Clean up
|
|
246
|
+
cleanupSessionState();
|
|
247
|
+
|
|
248
|
+
process.exit(0);
|
|
249
|
+
} catch (e) {
|
|
250
|
+
// Silent failure - don't interrupt session end
|
|
251
|
+
cleanupSessionState();
|
|
252
|
+
process.exit(0);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Run
|
|
257
|
+
if (require.main === module) {
|
|
258
|
+
main();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
module.exports = {
|
|
262
|
+
loadSessionState,
|
|
263
|
+
saveToMemory,
|
|
264
|
+
archiveSession,
|
|
265
|
+
syncTodoState,
|
|
266
|
+
calculateDuration,
|
|
267
|
+
formatEndSummary
|
|
268
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"session": {
|
|
3
|
+
"id": "unknown",
|
|
4
|
+
"startTime": "2026-01-22T13:06:23.872Z",
|
|
5
|
+
"project": "sumulige-claude",
|
|
6
|
+
"version": "1.3.1"
|
|
7
|
+
},
|
|
8
|
+
"memory": {
|
|
9
|
+
"loaded": true,
|
|
10
|
+
"entries": 4
|
|
11
|
+
},
|
|
12
|
+
"anchors": {
|
|
13
|
+
"loaded": true,
|
|
14
|
+
"modules": 0
|
|
15
|
+
},
|
|
16
|
+
"todos": {
|
|
17
|
+
"loaded": true,
|
|
18
|
+
"active": 0,
|
|
19
|
+
"completed": 0
|
|
20
|
+
},
|
|
21
|
+
"endTime": "2026-01-22T13:07:26.625Z",
|
|
22
|
+
"duration": "1 minutes"
|
|
23
|
+
}
|
package/.claude/settings.json
CHANGED
|
@@ -1,4 +1,44 @@
|
|
|
1
1
|
{
|
|
2
|
+
"env": {
|
|
3
|
+
"ENABLE_TOOL_SEARCH": "true",
|
|
4
|
+
"DISABLE_AUTOUPDATER": "1"
|
|
5
|
+
},
|
|
6
|
+
"SessionStart": [
|
|
7
|
+
{
|
|
8
|
+
"matcher": {},
|
|
9
|
+
"hooks": [
|
|
10
|
+
{
|
|
11
|
+
"type": "command",
|
|
12
|
+
"command": "node \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/memory-loader.cjs",
|
|
13
|
+
"timeout": 2000
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"SessionEnd": [
|
|
19
|
+
{
|
|
20
|
+
"matcher": {},
|
|
21
|
+
"hooks": [
|
|
22
|
+
{
|
|
23
|
+
"type": "command",
|
|
24
|
+
"command": "node \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/memory-saver.cjs",
|
|
25
|
+
"timeout": 3000
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"PreCompact": [
|
|
31
|
+
{
|
|
32
|
+
"matcher": {},
|
|
33
|
+
"hooks": [
|
|
34
|
+
{
|
|
35
|
+
"type": "command",
|
|
36
|
+
"command": "node \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/auto-handoff.cjs",
|
|
37
|
+
"timeout": 5000
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
],
|
|
2
42
|
"UserPromptSubmit": [
|
|
3
43
|
{
|
|
4
44
|
"matcher": {},
|
|
@@ -128,7 +128,11 @@
|
|
|
128
128
|
"Bash(/opt/homebrew/Cellar/node/25.3.0/bin/npm view sumulige-claude versions --json)",
|
|
129
129
|
"Bash(/opt/homebrew/Cellar/node/25.3.0/bin/npx sumulige-claude@1.3.0 sync)",
|
|
130
130
|
"WebFetch(domain:raw.githubusercontent.com)",
|
|
131
|
-
"Bash(/opt/homebrew/Cellar/node/25.3.0/bin/npm version 1.3.1 --no-git-tag-version)"
|
|
131
|
+
"Bash(/opt/homebrew/Cellar/node/25.3.0/bin/npm version 1.3.1 --no-git-tag-version)",
|
|
132
|
+
"Bash(/opt/homebrew/Cellar/node/25.3.0/bin/node:*)",
|
|
133
|
+
"WebFetch(domain:docs.anthropic.com)",
|
|
134
|
+
"Bash(CLAUDE_PROJECT_DIR=\"/Users/sumulige/Documents/Antigravity/sumulige-claude\" /opt/homebrew/Cellar/node/25.3.0/bin/node:*)",
|
|
135
|
+
"Bash(CLAUDE_PROJECT_DIR=\"/Users/sumulige/Documents/Antigravity/sumulige-claude\" /opt/homebrew/Cellar/node/25.3.0/bin/node .claude/hooks/memory-saver.cjs)"
|
|
132
136
|
]
|
|
133
137
|
},
|
|
134
138
|
"hooks": {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Api Tester
|
|
2
|
+
|
|
3
|
+
> 简短描述这个技能的作用(一句话)
|
|
4
|
+
|
|
5
|
+
**版本**: 1.0.0
|
|
6
|
+
**作者**: @username
|
|
7
|
+
**标签**: [category1, category2]
|
|
8
|
+
**难度**: 初级/中级/高级
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 概述
|
|
13
|
+
|
|
14
|
+
详细描述这个技能的功能和用途。
|
|
15
|
+
|
|
16
|
+
## 适用场景
|
|
17
|
+
|
|
18
|
+
- 场景 1
|
|
19
|
+
- 场景 2
|
|
20
|
+
- 场景 3
|
|
21
|
+
|
|
22
|
+
## 触发关键词
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
keyword1, keyword2, "exact phrase"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 使用方法
|
|
29
|
+
|
|
30
|
+
### 基础用法
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# 示例命令
|
|
34
|
+
your-command-here
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 高级用法
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
# 配置示例
|
|
41
|
+
key: value
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 输出格式
|
|
45
|
+
|
|
46
|
+
描述这个技能的输出结果格式。
|
|
47
|
+
|
|
48
|
+
## 注意事项
|
|
49
|
+
|
|
50
|
+
- 注意事项 1
|
|
51
|
+
- 注意事项 2
|
|
52
|
+
|
|
53
|
+
## 相关技能
|
|
54
|
+
|
|
55
|
+
- [related-skill](../related-skill/)
|
|
56
|
+
- [another-skill](../another-skill/)
|
|
57
|
+
|
|
58
|
+
## 更新日志
|
|
59
|
+
|
|
60
|
+
### 1.0.0 (YYYY-MM-DD)
|
|
61
|
+
- 初始版本
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Skill Metadata
|
|
2
|
+
# 这个文件定义技能的基本信息,用于自动发现和索引
|
|
3
|
+
|
|
4
|
+
name: api-tester
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
author: @username
|
|
7
|
+
description: 简短描述技能功能
|
|
8
|
+
|
|
9
|
+
tags:
|
|
10
|
+
- category1
|
|
11
|
+
- category2
|
|
12
|
+
|
|
13
|
+
triggers:
|
|
14
|
+
- keyword1
|
|
15
|
+
- keyword2
|
|
16
|
+
- "exact phrase"
|
|
17
|
+
|
|
18
|
+
dependencies: [] # 依赖的其他技能
|
|
19
|
+
|
|
20
|
+
difficulty: beginner # beginner | intermediate | advanced
|
|
21
|
+
|
|
22
|
+
# 模板文件
|
|
23
|
+
templates:
|
|
24
|
+
- name: default
|
|
25
|
+
file: templates/default.md
|
|
26
|
+
|
|
27
|
+
# 示例文件
|
|
28
|
+
examples:
|
|
29
|
+
- name: basic
|
|
30
|
+
file: examples/basic.md
|