sumulige-claude 1.0.6 → 1.0.8
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/.kickoff-hint.txt +51 -0
- package/.claude/ANCHORS.md +40 -0
- package/.claude/MEMORY.md +34 -0
- package/.claude/PROJECT_LOG.md +101 -0
- package/.claude/THINKING_CHAIN_GUIDE.md +287 -0
- package/.claude/commands/commit-push-pr.md +59 -0
- package/.claude/commands/commit.md +53 -0
- package/.claude/commands/pr.md +76 -0
- package/.claude/commands/review.md +61 -0
- package/.claude/commands/sessions.md +62 -0
- package/.claude/commands/skill-create.md +131 -0
- package/.claude/commands/test.md +56 -0
- package/.claude/commands/todos.md +99 -0
- package/.claude/commands/verify-work.md +63 -0
- package/.claude/hooks/code-formatter.cjs +187 -0
- package/.claude/hooks/code-tracer.cjs +331 -0
- package/.claude/hooks/conversation-recorder.cjs +340 -0
- package/.claude/hooks/decision-tracker.cjs +398 -0
- package/.claude/hooks/export.cjs +329 -0
- package/.claude/hooks/multi-session.cjs +181 -0
- package/.claude/hooks/privacy-filter.js +224 -0
- package/.claude/hooks/project-kickoff.cjs +114 -0
- package/.claude/hooks/rag-skill-loader.cjs +159 -0
- package/.claude/hooks/session-end.sh +61 -0
- package/.claude/hooks/sync-to-log.sh +83 -0
- package/.claude/hooks/thinking-silent.cjs +145 -0
- package/.claude/hooks/tl-summary.sh +54 -0
- package/.claude/hooks/todo-manager.cjs +248 -0
- package/.claude/hooks/verify-work.cjs +134 -0
- package/.claude/sessions/active-sessions.json +359 -0
- package/.claude/settings.local.json +43 -2
- package/.claude/thinking-routes/.last-sync +1 -0
- package/.claude/thinking-routes/QUICKREF.md +98 -0
- package/.claude-plugin/marketplace.json +71 -0
- package/.github/workflows/sync-skills.yml +74 -0
- package/AGENTS.md +81 -22
- package/DEV_TOOLS_GUIDE.md +190 -0
- package/PROJECT_STRUCTURE.md +10 -1
- package/README.md +142 -708
- package/cli.js +116 -822
- package/config/defaults.json +34 -0
- package/config/skill-categories.json +40 -0
- package/development/todos/INDEX.md +14 -58
- package/docs/DEVELOPMENT.md +423 -0
- package/docs/MARKETPLACE.md +352 -0
- package/lib/commands.js +698 -0
- package/lib/config.js +71 -0
- package/lib/marketplace.js +486 -0
- package/lib/utils.js +62 -0
- package/package.json +11 -5
- package/scripts/sync-external.mjs +298 -0
- package/scripts/update-registry.mjs +325 -0
- package/sources.yaml +83 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ThinkingLens 无感知追踪
|
|
4
|
+
*
|
|
5
|
+
* 特点:
|
|
6
|
+
* - 完全静默,0 输出
|
|
7
|
+
* - 自动追踪对话流
|
|
8
|
+
* - 智能判断重要时刻
|
|
9
|
+
* - 后台运行,不打扰
|
|
10
|
+
* - 定期同步到 PROJECT_LOG.md(每 20 轮)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { spawn } = require('child_process');
|
|
16
|
+
|
|
17
|
+
const THINKING_DIR = path.join(process.env.CLAUDE_PROJECT_DIR, '.claude/thinking-routes');
|
|
18
|
+
const ACTIVITY_FILE = path.join(THINKING_DIR, '.conversation-flow.json');
|
|
19
|
+
const SESSION_FILE = path.join(THINKING_DIR, '.current-session');
|
|
20
|
+
const SYNC_MARKER_FILE = path.join(THINKING_DIR, '.last-sync');
|
|
21
|
+
const SYNC_INTERVAL = 20; // 每 20 轮同步一次
|
|
22
|
+
|
|
23
|
+
// 确保目录存在
|
|
24
|
+
try { fs.mkdirSync(THINKING_DIR, { recursive: true }); } catch (e) {}
|
|
25
|
+
|
|
26
|
+
// 获取当前会话
|
|
27
|
+
function getSession() {
|
|
28
|
+
let sessionId = fs.existsSync(SESSION_FILE)
|
|
29
|
+
? fs.readFileSync(SESSION_FILE, 'utf-8').trim()
|
|
30
|
+
: null;
|
|
31
|
+
|
|
32
|
+
if (!sessionId) {
|
|
33
|
+
const date = new Date().toISOString().split('T')[0].replace(/-/g, '');
|
|
34
|
+
const project = path.basename(process.env.CLAUDE_PROJECT_DIR).slice(0, 10);
|
|
35
|
+
sessionId = `s-${date}-${project}`;
|
|
36
|
+
fs.writeFileSync(SESSION_FILE, sessionId);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return sessionId;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 获取对话流数据
|
|
43
|
+
function getConversationFlow() {
|
|
44
|
+
if (!fs.existsSync(ACTIVITY_FILE)) {
|
|
45
|
+
return { sessionId: getSession(), turns: [], startTime: new Date().toISOString() };
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(fs.readFileSync(ACTIVITY_FILE, 'utf-8'));
|
|
49
|
+
} catch (e) {
|
|
50
|
+
return { sessionId: getSession(), turns: [], startTime: new Date().toISOString() };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 保存对话流
|
|
55
|
+
function saveConversationFlow(data) {
|
|
56
|
+
// 只保留最近 100 条
|
|
57
|
+
if (data.turns.length > 100) {
|
|
58
|
+
data.turns = data.turns.slice(-100);
|
|
59
|
+
}
|
|
60
|
+
fs.writeFileSync(ACTIVITY_FILE, JSON.stringify(data, null, 2));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 检查是否需要同步到日志
|
|
64
|
+
function shouldSync(turnCount) {
|
|
65
|
+
const lastSync = fs.existsSync(SYNC_MARKER_FILE)
|
|
66
|
+
? parseInt(fs.readFileSync(SYNC_MARKER_FILE, 'utf-8'), 10)
|
|
67
|
+
: 0;
|
|
68
|
+
return turnCount - lastSync >= SYNC_INTERVAL;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 标记已同步
|
|
72
|
+
function markSynced(turnCount) {
|
|
73
|
+
fs.writeFileSync(SYNC_MARKER_FILE, turnCount.toString());
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 静默同步到 PROJECT_LOG.md
|
|
77
|
+
function syncToLog() {
|
|
78
|
+
const syncScript = path.join(process.env.CLAUDE_PROJECT_DIR, '.claude/hooks/sync-to-log.sh');
|
|
79
|
+
if (fs.existsSync(syncScript)) {
|
|
80
|
+
try {
|
|
81
|
+
// 使用 spawn 静默执行,不输出任何内容
|
|
82
|
+
spawn('bash', [syncScript], {
|
|
83
|
+
stdio: 'ignore',
|
|
84
|
+
detached: true
|
|
85
|
+
}).unref();
|
|
86
|
+
} catch (e) {
|
|
87
|
+
// 静默忽略错误
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 检测是否是关键操作
|
|
93
|
+
function detectKeyOperation(toolInput, toolName) {
|
|
94
|
+
const keywords = [
|
|
95
|
+
'完成', '实现', '创建', '添加', '修复', '更新',
|
|
96
|
+
'完成', '测试', '部署', '推送', '设计', '决定'
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
// 如果包含关键词,可能是关键操作
|
|
100
|
+
if (keywords.some(k => toolInput.includes(k))) {
|
|
101
|
+
return 'potential-action';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 如果是代码编辑
|
|
105
|
+
if (toolName === 'Edit' || toolName === 'Write') {
|
|
106
|
+
return 'code-edit';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return 'normal';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 主函数 - 完全静默
|
|
113
|
+
function main() {
|
|
114
|
+
try {
|
|
115
|
+
const sessionId = getSession();
|
|
116
|
+
const flow = getConversationFlow();
|
|
117
|
+
const toolInput = process.env.CLAUDE_TOOL_INPUT || '';
|
|
118
|
+
const toolName = process.env.CLAUDE_TOOL_NAME || '';
|
|
119
|
+
|
|
120
|
+
// 添加新的对话轮次
|
|
121
|
+
flow.turns.push({
|
|
122
|
+
time: new Date().toISOString(),
|
|
123
|
+
toolName,
|
|
124
|
+
inputLength: toolInput.length,
|
|
125
|
+
type: detectKeyOperation(toolInput, toolName),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
saveConversationFlow(flow);
|
|
129
|
+
|
|
130
|
+
// 检查是否需要同步到日志
|
|
131
|
+
const turnCount = flow.turns.length;
|
|
132
|
+
if (shouldSync(turnCount)) {
|
|
133
|
+
syncToLog();
|
|
134
|
+
markSynced(turnCount);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
} catch (e) {
|
|
138
|
+
// 完全静默,忽略任何错误
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 永远不输出任何内容
|
|
142
|
+
process.exit(0);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
main();
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# tl-summary - 查看今日对话摘要
|
|
3
|
+
#
|
|
4
|
+
# 用法:
|
|
5
|
+
# .claude/hooks/tl-summary.sh
|
|
6
|
+
|
|
7
|
+
# 自动检测项目目录
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
CLAUDE_PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
|
10
|
+
|
|
11
|
+
THINKING_DIR="$CLAUDE_PROJECT_DIR/.claude/thinking-routes"
|
|
12
|
+
FLOW_FILE="$THINKING_DIR/.conversation-flow.json"
|
|
13
|
+
|
|
14
|
+
if [[ ! -f "$FLOW_FILE" ]]; then
|
|
15
|
+
echo "📭 暂无对话记录"
|
|
16
|
+
exit 0
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
# 解析 JSON(使用 Node.js)
|
|
20
|
+
node -e "
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const flow = JSON.parse(fs.readFileSync('$FLOW_FILE', 'utf-8'));
|
|
23
|
+
|
|
24
|
+
console.log('');
|
|
25
|
+
console.log('📅 今日对话摘要');
|
|
26
|
+
console.log('─'.repeat(40));
|
|
27
|
+
console.log('会话 ID: ' + flow.sessionId);
|
|
28
|
+
console.log('开始时间: ' + new Date(flow.startTime).toLocaleString('zh-CN'));
|
|
29
|
+
console.log('对话轮次: ' + flow.turns.length);
|
|
30
|
+
console.log('');
|
|
31
|
+
|
|
32
|
+
// 统计操作类型
|
|
33
|
+
const types = {};
|
|
34
|
+
flow.turns.forEach(t => {
|
|
35
|
+
types[t.type] = (types[t.type] || 0) + 1;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
console.log('📊 操作统计:');
|
|
39
|
+
Object.entries(types).forEach(([type, count]) => {
|
|
40
|
+
const emoji = type === 'potential-action' ? '⚡' :
|
|
41
|
+
type === 'code-edit' ? '📝' : '💬';
|
|
42
|
+
console.log(' ' + emoji + ' ' + type + ': ' + count);
|
|
43
|
+
});
|
|
44
|
+
console.log('');
|
|
45
|
+
|
|
46
|
+
// 显示最近 5 个操作
|
|
47
|
+
console.log('🕐 最近活动:');
|
|
48
|
+
flow.turns.slice(-5).reverse().forEach(t => {
|
|
49
|
+
const time = new Date(t.time).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
|
|
50
|
+
const emoji = t.type === 'potential-action' ? '⚡' :
|
|
51
|
+
t.type === 'code-edit' ? '📝' : '💬';
|
|
52
|
+
console.log(' ' + time + ' ' + emoji + ' ' + t.toolName);
|
|
53
|
+
});
|
|
54
|
+
"
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* TODO Manager - AI 自动任务管理系统
|
|
4
|
+
*
|
|
5
|
+
* 功能:
|
|
6
|
+
* - 自动追踪项目任务
|
|
7
|
+
* - 生成可点击的任务索引
|
|
8
|
+
* - 维护任务状态流转
|
|
9
|
+
* - 静默运行,不打扰工作流
|
|
10
|
+
*
|
|
11
|
+
* 目录结构:
|
|
12
|
+
* development/todos/
|
|
13
|
+
* ├── INDEX.md # 任务总览
|
|
14
|
+
* ├── active/ # 进行中的任务
|
|
15
|
+
* ├── completed/ # 已完成的任务
|
|
16
|
+
* ├── backlog/ # 待办任务
|
|
17
|
+
* └── archived/ # 已归档任务
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
|
|
23
|
+
const PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
24
|
+
const TODOS_DIR = path.join(PROJECT_DIR, 'development', 'todos');
|
|
25
|
+
const INDEX_FILE = path.join(TODOS_DIR, 'INDEX.md');
|
|
26
|
+
const STATE_FILE = path.join(TODOS_DIR, '.state.json');
|
|
27
|
+
|
|
28
|
+
// 任务状态
|
|
29
|
+
const STATUS = {
|
|
30
|
+
ACTIVE: 'active',
|
|
31
|
+
COMPLETED: 'completed',
|
|
32
|
+
BACKLOG: 'backlog',
|
|
33
|
+
ARCHIVED: 'archived'
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// 确保目录存在
|
|
37
|
+
function ensureDirectories() {
|
|
38
|
+
[TODOS_DIR, STATUS.ACTIVE, STATUS.COMPLETED, STATUS.BACKLOG, STATUS.ARCHIVED].forEach(dir => {
|
|
39
|
+
const fullPath = dir.startsWith('/') ? dir : path.join(TODOS_DIR, dir);
|
|
40
|
+
try { fs.mkdirSync(fullPath, { recursive: true }); } catch (e) {}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 扫描任务文件
|
|
45
|
+
function scanTasks() {
|
|
46
|
+
const tasks = {
|
|
47
|
+
active: [],
|
|
48
|
+
completed: [],
|
|
49
|
+
backlog: [],
|
|
50
|
+
archived: []
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
for (const [key, dirName] of Object.entries(STATUS)) {
|
|
54
|
+
const dir = path.join(TODOS_DIR, dirName);
|
|
55
|
+
if (!fs.existsSync(dir)) continue;
|
|
56
|
+
|
|
57
|
+
const files = fs.readdirSync(dir)
|
|
58
|
+
.filter(f => f.endsWith('.md') && f !== '_README.md');
|
|
59
|
+
|
|
60
|
+
tasks[dirName] = files.map(f => {
|
|
61
|
+
const filePath = path.join(dir, f);
|
|
62
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
63
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
64
|
+
const statusMatch = content.match(/\*\*状态\*\*:\s*([\u{1F300}-\u{1F9FF}\s]+)/u);
|
|
65
|
+
const priorityMatch = content.match(/\*\*优先级\*\*:\s*(P[0-3])/);
|
|
66
|
+
const branchMatch = content.match(/\*\*分支\*\*:\s*`([^`]+)`/);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
file: f,
|
|
70
|
+
title: titleMatch ? titleMatch[1] : path.basename(f, '.md'),
|
|
71
|
+
status: statusMatch ? statusMatch[1].trim() : '🚧 进行中',
|
|
72
|
+
priority: priorityMatch ? priorityMatch[1] : 'P2',
|
|
73
|
+
branch: branchMatch ? branchMatch[1] : null,
|
|
74
|
+
path: `${dirName}/${f}`
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return tasks;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 生成任务索引
|
|
83
|
+
function generateIndex(tasks) {
|
|
84
|
+
const now = new Date().toISOString().split('T')[0];
|
|
85
|
+
|
|
86
|
+
let md = `# 项目任务追踪系统
|
|
87
|
+
|
|
88
|
+
> 本目录由 AI 自动维护,记录项目开发任务和进度
|
|
89
|
+
|
|
90
|
+
**最后更新**: ${now}
|
|
91
|
+
|
|
92
|
+
@version: 1.0.0
|
|
93
|
+
|
|
94
|
+
## 目录结构
|
|
95
|
+
|
|
96
|
+
\`\`\`
|
|
97
|
+
development/todos/
|
|
98
|
+
├── INDEX.md # 本文件 - 任务总览
|
|
99
|
+
├── active/ # 进行中的任务 (${tasks.active.length})
|
|
100
|
+
├── completed/ # 已完成的任务 (${tasks.completed.length})
|
|
101
|
+
├── backlog/ # 待规划的任务 (${tasks.backlog.length})
|
|
102
|
+
└── archived/ # 已归档的任务 (${tasks.archived.length})
|
|
103
|
+
\`\`\`
|
|
104
|
+
|
|
105
|
+
## 快速跳转
|
|
106
|
+
|
|
107
|
+
`;
|
|
108
|
+
|
|
109
|
+
// 进行中的任务
|
|
110
|
+
md += `## 🚧 进行中的任务 (${tasks.active.length})\n\n`;
|
|
111
|
+
if (tasks.active.length > 0) {
|
|
112
|
+
tasks.active.forEach(t => {
|
|
113
|
+
md += `- [${t.priority}] [${t.title}](./${t.path}) - ${t.status}${t.branch ? ` \`branch: ${t.branch}\`` : ''}\n`;
|
|
114
|
+
});
|
|
115
|
+
} else {
|
|
116
|
+
md += `*暂无进行中的任务*\n`;
|
|
117
|
+
}
|
|
118
|
+
md += `\n`;
|
|
119
|
+
|
|
120
|
+
// 最近完成的任务(最多5个)
|
|
121
|
+
md += `## ✅ 最近完成的任务\n\n`;
|
|
122
|
+
const recentCompleted = tasks.completed.slice(0, 5);
|
|
123
|
+
if (recentCompleted.length > 0) {
|
|
124
|
+
recentCompleted.forEach(t => {
|
|
125
|
+
md += `- [${t.title}](./${t.path})\n`;
|
|
126
|
+
});
|
|
127
|
+
if (tasks.completed.length > 5) {
|
|
128
|
+
md += `- ...还有 ${tasks.completed.length - 5} 个已完成任务\n`;
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
md += `*暂无已完成的任务*\n`;
|
|
132
|
+
}
|
|
133
|
+
md += `\n`;
|
|
134
|
+
|
|
135
|
+
// 待办任务
|
|
136
|
+
md += `## 📋 待办任务 (${tasks.backlog.length})\n\n`;
|
|
137
|
+
if (tasks.backlog.length > 0) {
|
|
138
|
+
tasks.backlog.slice(0, 10).forEach(t => {
|
|
139
|
+
md += `- [${t.priority}] [${t.title}](./${t.path})\n`;
|
|
140
|
+
});
|
|
141
|
+
if (tasks.backlog.length > 10) {
|
|
142
|
+
md += `- ...还有 ${tasks.backlog.length - 10} 个待办任务\n`;
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
md += `*暂无待办任务*\n`;
|
|
146
|
+
}
|
|
147
|
+
md += `\n`;
|
|
148
|
+
|
|
149
|
+
// 全部目录链接
|
|
150
|
+
md += `## 全部目录\n\n`;
|
|
151
|
+
md += `- [🚧 所有进行中的任务](./active/) - 当前开发重点\n`;
|
|
152
|
+
md += `- [✅ 所有已完成的任务](./completed/) - 完整历史\n`;
|
|
153
|
+
md += `- [📋 所有待办任务](./backlog/) - 待规划\n`;
|
|
154
|
+
md += `- [📦 所有已归档任务](./archived/) - 历史记录\n`;
|
|
155
|
+
md += `\n`;
|
|
156
|
+
|
|
157
|
+
// 使用说明
|
|
158
|
+
md += `## 使用方式\n\n`;
|
|
159
|
+
md += `### 查看任务\n`;
|
|
160
|
+
md += `点击上方链接跳转到对应目录,或使用:\n`;
|
|
161
|
+
md += `\`\`\`bash\n`;
|
|
162
|
+
md += `# 查看进行中的任务\n`;
|
|
163
|
+
md += `cat development/todos/active/*.md\n\n`;
|
|
164
|
+
md += `# 查看特定任务\n`;
|
|
165
|
+
md += `cat development/todos/active/feature-name.md\n`;
|
|
166
|
+
md += `\`\`\`\n\n`;
|
|
167
|
+
md += `### 创建新任务\n`;
|
|
168
|
+
md += `在 Claude Code 中:\n`;
|
|
169
|
+
md += `\`\`\`\n`;
|
|
170
|
+
md += `创建一个新任务:实现用户登录功能\n`;
|
|
171
|
+
md += `\`\`\`\n\n`;
|
|
172
|
+
md += `AI 会自动在 \`active/\` 目录创建对应的任务文件。\n\n`;
|
|
173
|
+
md += `### 更新任务状态\n`;
|
|
174
|
+
md += `\`\`\`\n`;
|
|
175
|
+
md += `将 [任务名] 标记为完成\n`;
|
|
176
|
+
md += `\`\`\`\n\n`;
|
|
177
|
+
md += `AI 会自动将任务移动到 \`completed/\` 目录。\n\n`;
|
|
178
|
+
|
|
179
|
+
md += `---\n\n`;
|
|
180
|
+
md += `> **维护说明**: 本系统由 AI 自动维护,请勿手动编辑(除非你知道自己在做什么)\n`;
|
|
181
|
+
|
|
182
|
+
return md;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 更新索引
|
|
186
|
+
function updateIndex() {
|
|
187
|
+
try {
|
|
188
|
+
ensureDirectories();
|
|
189
|
+
const tasks = scanTasks();
|
|
190
|
+
const index = generateIndex(tasks);
|
|
191
|
+
|
|
192
|
+
// 检查是否需要更新 - 比较任务总数
|
|
193
|
+
let needsUpdate = true;
|
|
194
|
+
if (fs.existsSync(INDEX_FILE)) {
|
|
195
|
+
const existing = fs.readFileSync(INDEX_FILE, 'utf-8');
|
|
196
|
+
// 从现有索引中提取任务数量
|
|
197
|
+
const activeMatch = existing.match(/## 🚧 进行中的任务 \((\d+)\)/);
|
|
198
|
+
const completedMatch = existing.match(/## ✅ 最近完成的任务/);
|
|
199
|
+
const existingActive = activeMatch ? parseInt(activeMatch[1], 10) : 0;
|
|
200
|
+
const newActive = tasks.active.length;
|
|
201
|
+
|
|
202
|
+
// 如果活跃任务数量相同且没有完成任务内容变化,则不更新
|
|
203
|
+
if (existingActive === newActive && tasks.completed.length === 0) {
|
|
204
|
+
// 检查现有索引是否已有完成任务
|
|
205
|
+
const hasCompletedInExisting = existing.includes('[completed/') || existing.includes('./completed/');
|
|
206
|
+
const hasCompletedNow = tasks.completed.length > 0;
|
|
207
|
+
needsUpdate = hasCompletedInExisting !== hasCompletedNow;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (needsUpdate) {
|
|
212
|
+
fs.writeFileSync(INDEX_FILE, index);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return { tasks, updated: needsUpdate };
|
|
216
|
+
} catch (e) {
|
|
217
|
+
return { tasks: { active: [], completed: [], backlog: [], archived: [] }, updated: false };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// 主函数
|
|
222
|
+
function main() {
|
|
223
|
+
const result = updateIndex();
|
|
224
|
+
|
|
225
|
+
// 在 AgentStop 或特定事件时输出摘要
|
|
226
|
+
const eventType = process.env.CLAUDE_EVENT_TYPE || '';
|
|
227
|
+
if (eventType === 'AgentStop') {
|
|
228
|
+
const { active, completed } = result.tasks;
|
|
229
|
+
if (active.length > 0) {
|
|
230
|
+
console.log(`\n📋 [任务追踪] ${active.length} 个进行中, ${completed.length} 个已完成`);
|
|
231
|
+
console.log(` 查看: development/todos/INDEX.md\n`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
process.exit(0);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// 如果直接运行,强制更新索引
|
|
239
|
+
if (require.main === module) {
|
|
240
|
+
if (process.argv[2] === '--force') {
|
|
241
|
+
updateIndex();
|
|
242
|
+
console.log('✅ Task index updated');
|
|
243
|
+
} else {
|
|
244
|
+
main();
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
main();
|
|
248
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Verify Work Hook - 验证反馈循环
|
|
4
|
+
*
|
|
5
|
+
* 根据 Boris Cherny 的最佳实践,这是获得高质量结果的关键。
|
|
6
|
+
* 在重要任务完成后自动触发验证流程,能让最终质量提升 2-3 倍。
|
|
7
|
+
*
|
|
8
|
+
* 触发时机:
|
|
9
|
+
* - AgentStop 事件(会话结束时)
|
|
10
|
+
* - 检测到关键操作完成(如 commit、push 等)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
17
|
+
const VERIFY_DIR = path.join(PROJECT_DIR, '.claude/verify');
|
|
18
|
+
const VERIFY_PENDING_FILE = path.join(VERIFY_DIR, '.pending-verify.json');
|
|
19
|
+
const VERIFY_LOG_FILE = path.join(VERIFY_DIR, 'verify-log.md');
|
|
20
|
+
|
|
21
|
+
// 确保目录存在
|
|
22
|
+
try { fs.mkdirSync(VERIFY_DIR, { recursive: true }); } catch (e) {}
|
|
23
|
+
|
|
24
|
+
// 检测是否需要验证
|
|
25
|
+
function shouldVerify(toolName, toolInput) {
|
|
26
|
+
// 在这些操作后建议验证
|
|
27
|
+
const verifyAfterOps = [
|
|
28
|
+
'git commit',
|
|
29
|
+
'git push',
|
|
30
|
+
'deploy',
|
|
31
|
+
'build',
|
|
32
|
+
'test',
|
|
33
|
+
'release'
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const lowerInput = toolInput.toLowerCase();
|
|
37
|
+
return verifyAfterOps.some(op => lowerInput.includes(op));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 获取待验证任务
|
|
41
|
+
function getPendingVerifies() {
|
|
42
|
+
if (!fs.existsSync(VERIFY_PENDING_FILE)) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(fs.readFileSync(VERIFY_PENDING_FILE, 'utf-8'));
|
|
47
|
+
} catch (e) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 保存待验证任务
|
|
53
|
+
function savePendingVerifies(tasks) {
|
|
54
|
+
fs.writeFileSync(VERIFY_PENDING_FILE, JSON.stringify(tasks, null, 2));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 添加验证提示到输出
|
|
58
|
+
function addVerifyHint(toolInput) {
|
|
59
|
+
const hints = {
|
|
60
|
+
'git commit': '建议运行测试套件验证更改是否破坏现有功能',
|
|
61
|
+
'git push': '建议在 CI/CD 中确认所有检查通过',
|
|
62
|
+
'deploy': '建议在预发布环境验证关键用户流程',
|
|
63
|
+
'build': '建议运行构建产物检查是否有警告',
|
|
64
|
+
'test': '建议检查测试覆盖率是否满足要求',
|
|
65
|
+
'release': '建议进行完整的回归测试'
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
for (const [key, hint] of Object.entries(hints)) {
|
|
69
|
+
if (toolInput.toLowerCase().includes(key)) {
|
|
70
|
+
return hint;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return '建议验证核心功能是否正常工作';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 记录验证日志
|
|
77
|
+
function logVerify(action, hint) {
|
|
78
|
+
const timestamp = new Date().toISOString();
|
|
79
|
+
const logEntry = `\n## ${timestamp}\n**操作**: ${action}\n**验证建议**: ${hint}\n`;
|
|
80
|
+
|
|
81
|
+
if (!fs.existsSync(VERIFY_LOG_FILE)) {
|
|
82
|
+
fs.writeFileSync(VERIFY_LOG_FILE, `# 验证日志\n\n此文件记录所有建议验证的操作。\n${logEntry}`);
|
|
83
|
+
} else {
|
|
84
|
+
fs.appendFileSync(VERIFY_LOG_FILE, logEntry);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 主函数
|
|
89
|
+
function main() {
|
|
90
|
+
try {
|
|
91
|
+
const toolName = process.env.CLAUDE_TOOL_NAME || '';
|
|
92
|
+
const toolInput = process.env.CLAUDE_TOOL_INPUT || '';
|
|
93
|
+
const eventType = process.env.CLAUDE_EVENT_TYPE || '';
|
|
94
|
+
|
|
95
|
+
// 检查是否需要验证
|
|
96
|
+
if (shouldVerify(toolName, toolInput)) {
|
|
97
|
+
const hint = addVerifyHint(toolInput);
|
|
98
|
+
|
|
99
|
+
// 保存到待验证列表
|
|
100
|
+
const pending = getPendingVerifies();
|
|
101
|
+
pending.push({
|
|
102
|
+
time: new Date().toISOString(),
|
|
103
|
+
action: toolName + ': ' + toolInput.slice(0, 100),
|
|
104
|
+
hint
|
|
105
|
+
});
|
|
106
|
+
savePendingVerifies(pending);
|
|
107
|
+
|
|
108
|
+
// 记录到日志
|
|
109
|
+
logVerify(toolName, hint);
|
|
110
|
+
|
|
111
|
+
// 输出验证提示(仅在非 PostToolUse 事件,避免重复)
|
|
112
|
+
if (eventType !== 'PostToolUse') {
|
|
113
|
+
console.log(`\n🔍 [验证提醒] ${hint}`);
|
|
114
|
+
console.log(`使用 /verify-work 查看待验证任务列表\n`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// AgentStop 时显示待验证任务摘要
|
|
119
|
+
if (eventType === 'AgentStop') {
|
|
120
|
+
const pending = getPendingVerifies();
|
|
121
|
+
if (pending.length > 0) {
|
|
122
|
+
console.log(`\n🔍 [待验证任务] 还有 ${pending.length} 个任务待验证`);
|
|
123
|
+
console.log(`使用 /verify-work 查看详情并标记完成\n`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
} catch (e) {
|
|
128
|
+
// 静默处理错误
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
process.exit(0);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
main();
|