sumulige-claude 1.0.11 → 1.1.1

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 (40) hide show
  1. package/.claude/commands/todos.md +41 -6
  2. package/.claude/hooks/pre-commit.cjs +86 -0
  3. package/.claude/hooks/pre-push.cjs +103 -0
  4. package/.claude/hooks/session-restore.cjs +102 -0
  5. package/.claude/hooks/session-save.cjs +164 -0
  6. package/.claude/hooks/todo-manager.cjs +262 -141
  7. package/.claude/quality-gate.json +61 -0
  8. package/.claude/settings.local.json +12 -1
  9. package/.claude/skills/api-tester/SKILL.md +52 -23
  10. package/.claude/skills/test-workflow/SKILL.md +191 -0
  11. package/.claude/templates/tasks/develop.md +69 -0
  12. package/.claude/templates/tasks/research.md +64 -0
  13. package/.claude/templates/tasks/test.md +96 -0
  14. package/.claude-plugin/marketplace.json +2 -2
  15. package/.versionrc +25 -0
  16. package/AGENTS.md +7 -1
  17. package/CHANGELOG.md +83 -4
  18. package/PROJECT_STRUCTURE.md +40 -3
  19. package/Q&A.md +184 -0
  20. package/README.md +52 -2
  21. package/cli.js +102 -5
  22. package/config/official-skills.json +183 -0
  23. package/config/quality-gate.json +61 -0
  24. package/development/todos/.state.json +4 -0
  25. package/development/todos/INDEX.md +64 -38
  26. package/docs/RELEASE.md +93 -0
  27. package/lib/commands.js +1865 -39
  28. package/lib/config-manager.js +441 -0
  29. package/lib/config-schema.js +408 -0
  30. package/lib/config-validator.js +330 -0
  31. package/lib/config.js +52 -1
  32. package/lib/errors.js +305 -0
  33. package/lib/quality-gate.js +431 -0
  34. package/lib/quality-rules.js +373 -0
  35. package/lib/utils.js +102 -14
  36. package/lib/version-check.js +169 -0
  37. package/package.json +11 -2
  38. package/template/.claude/hooks/project-kickoff.cjs +190 -1
  39. package/template/.claude/hooks/session-restore.cjs +102 -0
  40. package/template/.claude/hooks/session-save.cjs +164 -0
@@ -14,12 +14,37 @@ cat development/todos/INDEX.md
14
14
 
15
15
  ## Task Operations
16
16
 
17
+ ### Task Types (v2.0)
18
+
19
+ 任务管理系统支持 R-D-T 三阶段生命周期:
20
+
21
+ ```
22
+ Research (研究) → Develop (开发) → Test (测试) → Done (完成)
23
+ ```
24
+
25
+ | 类型 | 图标 | 目录 | 说明 |
26
+ |------|------|------|------|
27
+ | Research | 📊 | `active/research/` | 调研/设计/探索 |
28
+ | Develop | 💻 | `active/develop/` | 实现/编码/重构 |
29
+ | Test | 🧪 | `active/test/` | 测试/验证/QA |
30
+
31
+ ### Task Templates
32
+
33
+ 使用 `.claude/templates/tasks/` 中的模板创建任务:
34
+
35
+ - **研究任务**: `.claude/templates/tasks/research.md`
36
+ - **开发任务**: `.claude/templates/tasks/develop.md`
37
+ - **测试任务**: `.claude/templates/tasks/test.md`
38
+
17
39
  ### Create a New Task
18
40
 
19
41
  When user asks to create a task:
20
- 1. Create file in `development/todos/active/`
21
- 2. Use kebab-case for filename (e.g., `user-login.md`)
22
- 3. Use the template format:
42
+ 1. Determine the task type (research/develop/test)
43
+ 2. Create file in `development/todos/active/{type}/`
44
+ 3. Use kebab-case for filename (e.g., `user-authentication.md`)
45
+ 4. Copy from the corresponding template
46
+
47
+ #### Task Template (Legacy Format)
23
48
 
24
49
  ```markdown
25
50
  # [Task Name]
@@ -59,9 +84,19 @@ When user asks to create a task:
59
84
  ### Update Task Status
60
85
 
61
86
  To move a task:
62
- - **Complete**: Move from `active/` to `completed/`
63
- - **Backlog**: Move from `active/` to `backlog/`
64
- - **Archive**: Move from `completed/` to `archived/`
87
+ - **Complete**: Move from `active/{type}/` to `completed/{type}/`
88
+ - **Backlog**: Move from `active/{type}/` to `backlog/{type}/`
89
+ - **Archive**: Move from `completed/{type}/` to `archived/{type}/`
90
+
91
+ Example: Move `active/develop/auth.md` → `completed/develop/auth.md`
92
+
93
+ ### Auto-Transition Suggestions
94
+
95
+ When a develop task is completed, the todo-manager will suggest creating a corresponding test task. Check with:
96
+
97
+ ```bash
98
+ node .claude/hooks/todo-manager.cjs --suggest
99
+ ```
65
100
 
66
101
  ### Update Task Progress
67
102
 
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Pre-commit Quality Gate
4
+ *
5
+ * Runs quality checks before committing.
6
+ * Fails the commit if critical or error issues are found.
7
+ *
8
+ * Install: ln -s ../../.claude/hooks/pre-commit.cjs .git/hooks/pre-commit
9
+ * Or use: smc hooks install
10
+ */
11
+
12
+ const { execSync } = require('child_process');
13
+ const path = require('path');
14
+ const fs = require('fs');
15
+
16
+ const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
17
+
18
+ async function main() {
19
+ // Check if quality gate is enabled
20
+ const configPath = path.join(projectDir, '.claude', 'quality-gate.json');
21
+ let config = { enabled: true, gates: { preCommit: true } };
22
+
23
+ if (fs.existsSync(configPath)) {
24
+ try {
25
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
26
+ } catch {}
27
+ }
28
+
29
+ if (!config.enabled || !config.gates?.preCommit) {
30
+ process.exit(0);
31
+ }
32
+
33
+ // Get staged files
34
+ let stagedFiles = [];
35
+ try {
36
+ const output = execSync('git diff --cached --name-only --diff-filter=ACM', {
37
+ encoding: 'utf-8',
38
+ stdio: 'pipe'
39
+ });
40
+ stagedFiles = output.trim().split('\n').filter(Boolean);
41
+ } catch {
42
+ // Not in git repo or no staged files
43
+ process.exit(0);
44
+ }
45
+
46
+ if (stagedFiles.length === 0) {
47
+ process.exit(0);
48
+ }
49
+
50
+ // Filter to checkable files
51
+ const checkable = stagedFiles.filter(f => {
52
+ const ext = path.extname(f);
53
+ return ['.js', '.ts', '.jsx', '.tsx', '.cjs', '.mjs', '.json', '.md'].includes(ext);
54
+ });
55
+
56
+ if (checkable.length === 0) {
57
+ process.exit(0);
58
+ }
59
+
60
+ console.log(`Running pre-commit quality checks on ${checkable.length} file(s)...`);
61
+
62
+ // Run quality gate
63
+ const { QualityGate } = require(path.join(__dirname, '..', '..', 'lib', 'quality-gate.js'));
64
+ const gate = new QualityGate({
65
+ projectDir,
66
+ config
67
+ });
68
+
69
+ const result = await gate.check({
70
+ files: checkable.map(f => path.join(projectDir, f)),
71
+ severity: 'error' // Block on errors and critical only
72
+ });
73
+
74
+ if (!result.passed) {
75
+ console.error('\nPre-commit quality gate failed.');
76
+ console.error('Fix issues or use --no-verify to bypass (not recommended).\n');
77
+ process.exit(1);
78
+ }
79
+
80
+ console.log('Pre-commit quality checks passed.\n');
81
+ }
82
+
83
+ main().catch(err => {
84
+ console.error('Pre-commit hook error:', err.message);
85
+ process.exit(1);
86
+ });
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Pre-push Quality Gate
4
+ *
5
+ * Runs full quality checks before pushing.
6
+ * More comprehensive than pre-commit.
7
+ *
8
+ * Install: ln -s ../../.claude/hooks/pre-push.cjs .git/hooks/pre-push
9
+ * Or use: smc hooks install
10
+ */
11
+
12
+ const { execSync } = require('child_process');
13
+ const path = require('path');
14
+ const fs = require('fs');
15
+
16
+ const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
17
+
18
+ async function main() {
19
+ // Check if quality gate is enabled
20
+ const configPath = path.join(projectDir, '.claude', 'quality-gate.json');
21
+ let config = { enabled: true, gates: { prePush: true } };
22
+
23
+ if (fs.existsSync(configPath)) {
24
+ try {
25
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
26
+ } catch {}
27
+ }
28
+
29
+ if (!config.enabled || !config.gates?.prePush) {
30
+ process.exit(0);
31
+ }
32
+
33
+ console.log('Running pre-push quality checks...\n');
34
+
35
+ // Get all files that will be pushed
36
+ let filesToCheck = [];
37
+ try {
38
+ // Get files changed since last push
39
+ const upstream = execSync('git rev-parse --abbrev-ref --symbolic-full-name @{u}', {
40
+ encoding: 'utf-8',
41
+ stdio: 'pipe'
42
+ }).trim() || 'origin/main';
43
+
44
+ const output = execSync(`git diff --name-only ${upstream}...HEAD`, {
45
+ encoding: 'utf-8',
46
+ stdio: 'pipe'
47
+ });
48
+ filesToCheck = output.trim().split('\n').filter(Boolean);
49
+ } catch {
50
+ // No upstream or other git error - check staged files only
51
+ try {
52
+ const output = execSync('git diff --cached --name-only --diff-filter=ACM', {
53
+ encoding: 'utf-8',
54
+ stdio: 'pipe'
55
+ });
56
+ filesToCheck = output.trim().split('\n').filter(Boolean);
57
+ } catch {
58
+ // Not in git repo
59
+ process.exit(0);
60
+ }
61
+ }
62
+
63
+ if (filesToCheck.length === 0) {
64
+ process.exit(0);
65
+ }
66
+
67
+ // Filter to checkable files
68
+ const checkable = filesToCheck.filter(f => {
69
+ const ext = path.extname(f);
70
+ return ['.js', '.ts', '.jsx', '.tsx', '.cjs', '.mjs', '.json', '.md'].includes(ext);
71
+ });
72
+
73
+ if (checkable.length === 0) {
74
+ process.exit(0);
75
+ }
76
+
77
+ console.log(`Checking ${checkable.length} changed file(s)...\n`);
78
+
79
+ // Run quality gate
80
+ const { QualityGate } = require(path.join(__dirname, '..', '..', 'lib', 'quality-gate.js'));
81
+ const gate = new QualityGate({
82
+ projectDir,
83
+ config
84
+ });
85
+
86
+ const result = await gate.check({
87
+ files: checkable.map(f => path.join(projectDir, f)),
88
+ severity: 'warn' // Block on warnings too for push
89
+ });
90
+
91
+ if (!result.passed) {
92
+ console.error('\nPush blocked by quality gate.');
93
+ console.error('Fix issues or use --no-verify to bypass (not recommended).\n');
94
+ process.exit(1);
95
+ }
96
+
97
+ console.log('All quality checks passed. Proceeding with push.\n');
98
+ }
99
+
100
+ main().catch(err => {
101
+ console.error('Pre-push hook error:', err.message);
102
+ process.exit(1);
103
+ });
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Session Restore Hook - Restore conversation context from file
4
+ *
5
+ * Provides context continuity across sessions
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ const PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
12
+ const SESSIONS_DIR = path.join(PROJECT_DIR, '.claude', 'sessions');
13
+ const MEMORY_FILE = path.join(PROJECT_DIR, '.claude', 'MEMORY.md');
14
+
15
+ /**
16
+ * Get latest session
17
+ */
18
+ function getLatestSession() {
19
+ if (!fs.existsSync(SESSIONS_DIR)) {
20
+ return null;
21
+ }
22
+
23
+ const files = fs.readdirSync(SESSIONS_DIR)
24
+ .filter(f => f.startsWith('session_') && f.endsWith('.md'))
25
+ .sort()
26
+ .reverse();
27
+
28
+ if (files.length === 0) {
29
+ return null;
30
+ }
31
+
32
+ const latestFile = files[0];
33
+ const filepath = path.join(SESSIONS_DIR, latestFile);
34
+ const content = fs.readFileSync(filepath, 'utf-8');
35
+
36
+ return {
37
+ file: latestFile,
38
+ path: filepath,
39
+ content: content
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Get recent memory entries
45
+ */
46
+ function getRecentMemory(days = 7) {
47
+ if (!fs.existsSync(MEMORY_FILE)) {
48
+ return '';
49
+ }
50
+
51
+ const content = fs.readFileSync(MEMORY_FILE, 'utf-8');
52
+ const entries = content.split('## ').slice(1, days + 1);
53
+
54
+ return entries.join('## ');
55
+ }
56
+
57
+ /**
58
+ * Format context summary for display
59
+ */
60
+ function formatContextSummary(latestSession, recentMemory) {
61
+ let summary = '';
62
+
63
+ if (latestSession) {
64
+ summary += `\n📁 Last Session: ${latestSession.file}\n`;
65
+ summary += ` Date: ${fs.statSync(latestSession.path).mtime.toLocaleString()}\n`;
66
+ }
67
+
68
+ const memoryEntries = recentMemory.split('\n').filter(l => l.trim()).length;
69
+ if (memoryEntries > 0) {
70
+ summary += `\n💾 Memory Entries: ${memoryEntries}\n`;
71
+ }
72
+
73
+ return summary;
74
+ }
75
+
76
+ // Main execution
77
+ if (require.main === module) {
78
+ const args = process.argv.slice(2);
79
+
80
+ if (args[0] === '--latest') {
81
+ const session = getLatestSession();
82
+ if (session) {
83
+ console.log(session.content);
84
+ } else {
85
+ console.log('No sessions found.');
86
+ }
87
+ } else if (args[0] === '--memory') {
88
+ const memory = getRecentMemory(parseInt(args[1]) || 7);
89
+ console.log('# Recent Memory\n');
90
+ console.log(memory);
91
+ } else if (args[0] === '--summary') {
92
+ const session = getLatestSession();
93
+ const memory = getRecentMemory();
94
+ console.log(formatContextSummary(session, memory));
95
+ } else {
96
+ console.log('Usage: node session-restore.cjs [--latest|--memory|--summary]');
97
+ }
98
+ }
99
+
100
+ exports.getLatestSession = getLatestSession;
101
+ exports.getRecentMemory = getRecentMemory;
102
+ exports.formatContextSummary = formatContextSummary;
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Session Save Hook - Save conversation context to file
4
+ *
5
+ * Triggered after each conversation turn
6
+ * Saves conversation history, context, and state
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+
12
+ const PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
13
+ const SESSIONS_DIR = path.join(PROJECT_DIR, '.claude', 'sessions');
14
+ const MEMORY_FILE = path.join(PROJECT_DIR, '.claude', 'MEMORY.md');
15
+
16
+ // Ensure sessions directory exists
17
+ if (!fs.existsSync(SESSIONS_DIR)) {
18
+ fs.mkdirSync(SESSIONS_DIR, { recursive: true });
19
+ }
20
+
21
+ /**
22
+ * Generate session filename
23
+ */
24
+ function getSessionFilename() {
25
+ const now = new Date();
26
+ const date = now.toISOString().split('T')[0];
27
+ const time = now.toTimeString().split(' ')[0].replace(/:/g, '-');
28
+ return `session_${date}_${time}.md`;
29
+ }
30
+
31
+ /**
32
+ * Save session context
33
+ */
34
+ function saveSession(context) {
35
+ const filename = getSessionFilename();
36
+ const filepath = path.join(SESSIONS_DIR, filename);
37
+
38
+ const content = `# Session - ${new Date().toISOString()}
39
+
40
+ > Type: ${context.type || 'chat'}
41
+ > Model: ${context.model || 'unknown'}
42
+ > Duration: ${context.duration || 'unknown'}
43
+
44
+ ---
45
+
46
+ ## Summary
47
+
48
+ ${context.summary || 'No summary provided'}
49
+
50
+ ---
51
+
52
+ ## Context
53
+
54
+ \`\`\`json
55
+ ${JSON.stringify(context.metadata || {}, null, 2)}
56
+ \`\`\`
57
+
58
+ ---
59
+
60
+ ## Key Points
61
+
62
+ ${(context.keyPoints || []).map((p, i) => `${i + 1}. ${p}`).join('\n')}
63
+
64
+ ---
65
+
66
+ ## Artifacts
67
+
68
+ ${(context.artifacts || []).map(a => `- ${a}`).join('\n') || 'None'}
69
+
70
+ ---
71
+
72
+ ## Next Steps
73
+
74
+ ${context.nextSteps || 'None'}
75
+
76
+ ---
77
+
78
+ *Session saved at: ${new Date().toISOString()}*
79
+ `;
80
+
81
+ fs.writeFileSync(filepath, content, 'utf-8');
82
+
83
+ // Update sessions index
84
+ updateSessionsIndex();
85
+
86
+ return filename;
87
+ }
88
+
89
+ /**
90
+ * Update sessions index
91
+ */
92
+ function updateSessionsIndex() {
93
+ const files = fs.readdirSync(SESSIONS_DIR)
94
+ .filter(f => f.endsWith('.md'))
95
+ .sort()
96
+ .reverse();
97
+
98
+ const indexPath = path.join(SESSIONS_DIR, 'INDEX.md');
99
+
100
+ let content = `# Sessions Index
101
+
102
+ > Total sessions: ${files.length}
103
+ > Last updated: ${new Date().toISOString()}
104
+
105
+ ---
106
+
107
+ ## Recent Sessions
108
+
109
+ ${files.slice(0, 20).map(f => {
110
+ const filepath = path.join(SESSIONS_DIR, f);
111
+ const stat = fs.statSync(filepath);
112
+ return `- [${f}](${f}) - ${stat.mtime.toISOString()}`;
113
+ }).join('\n')}
114
+
115
+ ---
116
+ `;
117
+
118
+ fs.writeFileSync(indexPath, content, 'utf-8');
119
+ }
120
+
121
+ /**
122
+ * Update MEMORY.md with latest context
123
+ */
124
+ function updateMemory(context) {
125
+ const timestamp = new Date().toISOString();
126
+
127
+ let content = '';
128
+
129
+ if (fs.existsSync(MEMORY_FILE)) {
130
+ content = fs.readFileSync(MEMORY_FILE, 'utf-8');
131
+ }
132
+
133
+ // Check if we need to add a new entry
134
+ const newEntry = `\n## ${timestamp.split('T')[0]}\n\n${
135
+ context.summary || context.keyPoints?.join('\n') || 'No details'
136
+ }\n`;
137
+
138
+ // Keep only last 7 days
139
+ const entries = content.split('## ').slice(1, 8);
140
+ content = '# Memory\n\n<!-- Project memory updated by AI -->\n' +
141
+ '## ' + entries.join('## ') + newEntry;
142
+
143
+ fs.writeFileSync(MEMORY_FILE, content, 'utf-8');
144
+ }
145
+
146
+ // Main execution
147
+ if (require.main === module) {
148
+ const args = process.argv.slice(2);
149
+
150
+ if (args[0] === '--save' && args[1]) {
151
+ const contextData = JSON.parse(fs.readFileSync(args[1], 'utf-8'));
152
+ const filename = saveSession(contextData);
153
+ console.log(`✅ Session saved: ${filename}`);
154
+
155
+ if (contextData.addToMemory !== false) {
156
+ updateMemory(contextData);
157
+ console.log(`✅ Memory updated`);
158
+ }
159
+ }
160
+ }
161
+
162
+ exports.saveSession = saveSession;
163
+ exports.updateMemory = updateMemory;
164
+ exports.updateSessionsIndex = updateSessionsIndex;