ทบทวนงานแบบ Retrospective: สรุปว่า Claude ใช้ tool อะไร สำเร็จ/ล้มเหลวเท่าไหร่ พร้อมคำแนะนำและบันทึกเป็นไฟล์

เก็บ work log ของทุก tool ที่ Claude เรียก แล้วสรุปเป็นรายงานทบทวนงาน: ใช้เวลาเท่าไหร่ สำเร็จกี่เปอร์เซ็นต์ แตะกี่ไฟล์ ใช้ tool ไหนเยอะสุด พร้อมคำแนะนำอัตโนมัติและคำถามทบทวน ถ้า tool ล้มเหลว 3 ใน 5 ครั้งล่าสุดจะเด้งเตือน
| คำสั่ง | ทำอะไร |
|---|---|
/reflect | ดูรายงาน |
/reflect stats | สถิติสั้นๆ |
/reflect feedback <ข้อความ> | จดบทเรียน (จำข้าม session) |
/reflect save | บันทึกเป็น REFLECT-YYYY-MM-DD.md |
/plugin install reflect-th --marketplace tksoft11/claude-code-mods-th
ต้องตั้ง CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 ก่อนเปิด claude (ดู README หลัก)
เปิด hooks/register.tsx แล้วอ่านตามบทที่เขียนไว้ในคอมเมนต์:
next แล้วอัปเดตผลหลัง next$.store$.fs.writework log อยู่ในหน่วยความจำ จะเริ่มใหม่เมื่อเปิด session ใหม่หรือ Mod ถูกโหลดใหม่
hooks/register.tsx 193 lines1/**
2 * ============================================================================
3 * MOD 07: Reflect TH - ทบทวนตัวเองหลังจบงาน แบบ Scrum Retrospective (ฉบับสอนทำ)
4 * ============================================================================
5 * แรงบันดาลใจจาก: https://github.com/BayramAnnakov/claude-reflect
6 *
7 * หน้าที่ของ Mod นี้:
8 * 1. เก็บ Work Log ทุกครั้งที่ Claude เรียก tool (tool อะไร, แตะไฟล์ไหน, สำเร็จไหม)
9 * 2. ถ้าล้มเหลวติดๆ กัน เด้งเตือนว่าอาจต้องเปลี่ยนวิธี
10 * 3. /reflect สรุปงาน + วิเคราะห์อัตโนมัติ + คำถามทบทวน
11 * 4. /reflect feedback <ข้อความ> จดบทเรียน (จำข้าม session)
12 * 5. /reflect save บันทึกรายงานเป็นไฟล์ REFLECT-YYYY-MM-DD.md
13 */
14
15import type { EngineInterface, Register } from 'claude-code'
16
17interface WorkLog {
18 time: number
19 tool: string
20 target: string // ไฟล์หรือคำสั่งที่เกี่ยวข้อง
21 ok: boolean
22}
23
24interface Feedback {
25 time: number
26 text: string
27}
28
29// ==========================================================================
30// บทที่ 1: state ของ Mod (เริ่มใหม่ทุก session)
31// ==========================================================================
32let workLogs: WorkLog[] = []
33let task = '' // prompt แรกของ session = "งาน" ที่ทำ
34let startedAt = 0
35let projectRoot = ''
36let hasWarnedStreak = false
37
38// สรุปสั้นๆ ว่า tool นี้ทำอะไร
39function targetOf(e: { tool: string } & Record<string, unknown>): string {
40 for (const key of ['file_path', 'notebook_path', 'command', 'url', 'pattern', 'description']) {
41 const value = e[key]
42 if (typeof value === 'string' && value) return value.length > 70 ? value.slice(0, 67) + '...' : value
43 }
44 return ''
45}
46
47function formatDuration(ms: number): string {
48 const sec = Math.floor(ms / 1000)
49 return sec < 60 ? `${sec}วิ` : `${Math.floor(sec / 60)}นาที ${sec % 60}วิ`
50}
51
52function timeAgo(ms: number): string {
53 const sec = Math.floor(ms / 1000)
54 return sec < 60 ? `${sec}วิที่แล้ว` : `${Math.floor(sec / 60)}นาทีที่แล้ว`
55}
56
57async function readFeedbacks($: EngineInterface): Promise<Feedback[]> {
58 const saved = await $.store.get('feedbacks')
59 return Array.isArray(saved) ? (saved as Feedback[]) : []
60}
61
62// ==========================================================================
63// บทที่ 2: วิเคราะห์อัตโนมัติ - หัวใจของ Reflect
64// ==========================================================================
65function buildReport(now: number, feedbacks: Feedback[]): string {
66 const total = workLogs.length
67 const success = workLogs.filter(w => w.ok).length
68 const failed = total - success
69 const rate = total === 0 ? 0 : (success / total) * 100
70 const files = new Set(workLogs.filter(w => /[/\\.]/.test(w.target) && w.tool !== 'Bash').map(w => w.target))
71
72 // นับว่าใช้ tool ไหนบ่อยสุด
73 const toolCount = new Map<string, number>()
74 for (const w of workLogs) toolCount.set(w.tool, (toolCount.get(w.tool) ?? 0) + 1)
75 const [topTool, topCount] = [...toolCount.entries()].sort((a, b) => b[1] - a[1])[0] ?? ['-', 0]
76
77 const lines = [
78 '🪞 Reflect Report - ทบทวนงาน (Mod 07)',
79 '========================================',
80 `📝 งาน: ${task || '(ยังไม่มี prompt)'}`,
81 `⏱️ ใช้เวลา: ${formatDuration(now - startedAt)}`,
82 `🔧 เรียก tool: ${total} ครั้ง (สำเร็จ ${success}, ล้มเหลว ${failed}) - ${rate.toFixed(1)}%`,
83 `📁 แตะไฟล์: ${files.size} ไฟล์`,
84 `🛠️ ใช้เยอะสุด: ${topTool} (${topCount} ครั้ง)`,
85 ]
86
87 if (total > 0) {
88 lines.push('', '📊 ล่าสุด 8 รายการ:')
89 for (const w of workLogs.slice(-8).reverse()) {
90 lines.push(` ${w.ok ? '✅' : '❌'} ${w.tool} - ${w.target || '-'} (${timeAgo(now - w.time)})`)
91 }
92 }
93
94 lines.push('', '🤔 การทบทวน (Self-Reflection):')
95 if (total === 0) lines.push(' ยังไม่มีงานให้ทบทวน')
96 else if (failed === 0) lines.push(' ✅ สำเร็จ 100% - แผนดีมาก')
97 if (failed > total * 0.3) lines.push(' ⚠️ ล้มเหลวเกิน 30% - งานถัดไปลองใช้ Plan Mode วางแผนก่อน')
98 if (total >= 5 && topCount > total * 0.6) lines.push(` 💡 ใช้ ${topTool} เกิน 60% ของทั้งหมด - อาจมีวิธีที่เร็วกว่า`)
99 if (files.size > 10) lines.push(' 💡 แตะไฟล์เยอะมาก - ลองแบ่งงานเป็นชิ้นเล็กลง')
100
101 if (feedbacks.length > 0) {
102 lines.push('', '📌 บทเรียนที่จดไว้:')
103 for (const f of feedbacks.slice(-5)) lines.push(` - ${f.text}`)
104 }
105
106 lines.push('', '💡 คำถามทบทวน:', ' 1. งานนี้สำเร็จตามที่ต้องการไหม?', ' 2. มีอะไรที่ทำได้ดีกว่านี้ไหม?', ' 3. รอบหน้าควรบอก Claude อะไรตั้งแต่แรก?')
107 return lines.join('\n')
108}
109
110export const register: Register = on => {
111 on('session.start', async ($, e, next) => {
112 workLogs = []
113 task = ''
114 hasWarnedStreak = false
115 startedAt = await $.clock.now()
116 projectRoot = e.cwd
117 await $.command.register({ name: 'reflect', description: 'ทบทวนงาน (/reflect stats, /reflect feedback <ข้อความ>, /reflect save)', argumentHint: '[stats|feedback|save]' })
118 return next(e)
119 })
120
121 // จด prompt แรกเป็นชื่องาน (ข้ามคำสั่ง / และข้อความที่ระบบส่งเอง)
122 on('prompt.submit', ($, e, next) => {
123 if (!task && !e.command && !e.isMeta) task = e.text.split('\n')[0]?.slice(0, 100) ?? ''
124 return next(e)
125 })
126
127 // ==========================================================================
128 // บทที่ 3: เก็บ Log ทุกครั้งที่เรียก tool แล้วอัปเดตว่าสำเร็จไหม
129 // ==========================================================================
130 on('tool.call', async ($, e, next) => {
131 const log: WorkLog = { time: await $.clock.now(), tool: e.tool, target: targetOf(e as unknown as { tool: string } & Record<string, unknown>), ok: true }
132 workLogs.push(log)
133 if (workLogs.length > 500) workLogs.shift()
134
135 const ran = await next(e)
136 log.ok = ran.deny === undefined && !ran.isError
137
138 // ใน 5 ครั้งล่าสุด ล้มเหลว 3 ครั้งขึ้นไป -> เตือน (ครั้งเดียวจนกว่าจะดีขึ้น)
139 const recentFails = workLogs.slice(-5).filter(w => !w.ok).length
140 if (recentFails >= 3 && !hasWarnedStreak) {
141 $.ui.toast('🪞 ล้มเหลวหลายครั้งติดกัน อาจต้องเปลี่ยนวิธี ลองกด Esc แล้วบอก Claude ให้วางแผนใหม่')
142 hasWarnedStreak = true
143 }
144 if (recentFails === 0) hasWarnedStreak = false
145
146 return ran
147 })
148
149 // ==========================================================================
150 // บทที่ 4: Feedback Loop และคำสั่ง /reflect
151 // ==========================================================================
152 on('command.run', { command: 'reflect' }, async ($, e) => {
153 const args = e.args.trim()
154 const [sub = '', ...rest] = args.split(/\s+/)
155 const now = await $.clock.now()
156
157 if (sub === 'feedback') {
158 const text = rest.join(' ')
159 if (!text) return { text: 'วิธีใช้: /reflect feedback รอบหน้าควรรัน test ก่อน' }
160 const feedbacks = [...(await readFeedbacks($)), { time: now, text }].slice(-50)
161 await $.store.set('feedbacks', feedbacks)
162 return { text: `📌 จดบทเรียนแล้ว (${feedbacks.length} ข้อ): ${text}` }
163 }
164
165 if (sub === 'stats') {
166 const ok = workLogs.filter(w => w.ok).length
167 return { text: `🪞 ${formatDuration(now - startedAt)} | tool ${workLogs.length} ครั้ง | สำเร็จ ${ok} | ล้มเหลว ${workLogs.length - ok}` }
168 }
169
170 const report = buildReport(now, await readFeedbacks($))
171
172 if (sub === 'save') {
173 const d = new Date(now) // วันที่ตามเวลาเครื่อง (toISOString จะเป็นเวลา UTC)
174 const date = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
175 const sep = projectRoot.includes('\\') ? '\\' : '/'
176 const path = `${projectRoot}${sep}REFLECT-${date}.md`
177 await $.fs.write(path, '```\n' + report + '\n```\n')
178 return { text: `💾 บันทึกแล้ว: ${path}` }
179 }
180
181 return { text: report }
182 })
183}
184
185/**
186 * ========================================================================
187 * การบ้าน / ต่อยอด
188 * ========================================================================
189 * 1. เก็บสรุปทุก session ลง $.store แล้วดูว่าเราทำงานเร็วขึ้นไหม
190 * 2. ส่ง workLogs ให้ $.model.complete วิเคราะห์ว่า "อะไรดี/ไม่ดี" เป็นภาษาไทย
191 * 3. แชร์ REFLECT-*.md ให้ทีมดูใน retrospective
192 */
193