@phuc1403/musketeer 0.8.0 → 0.9.0

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 (76) hide show
  1. package/README.md +49 -49
  2. package/manifest.json +333 -301
  3. package/package.json +1 -1
  4. package/template/.claude/agents/code-reviewer.md +182 -166
  5. package/template/.claude/hooks/git-skill-reminder.cjs +53 -0
  6. package/template/.claude/hooks/lib/colors.cjs +180 -122
  7. package/template/.claude/hooks/lib/transcript-parser.cjs +300 -277
  8. package/template/.claude/skills/code-review/SKILL.md +201 -54
  9. package/template/.claude/skills/code-review/references/checklist-workflow.md +96 -0
  10. package/template/.claude/skills/code-review/references/checklists/api.md +52 -52
  11. package/template/.claude/skills/code-review/references/checklists/base.md +100 -100
  12. package/template/.claude/skills/code-review/references/checklists/web-app.md +54 -54
  13. package/template/.claude/skills/code-review/references/code-review-reception.md +113 -0
  14. package/template/.claude/skills/code-review/references/codebase-scan-workflow.md +30 -0
  15. package/template/.claude/skills/code-review/references/edge-case-scouting.md +119 -0
  16. package/template/.claude/skills/code-review/references/input-mode-resolution.md +135 -0
  17. package/template/.claude/skills/code-review/references/parallel-review-workflow.md +76 -0
  18. package/template/.claude/skills/code-review/references/requesting-code-review.md +116 -0
  19. package/template/.claude/skills/code-review/references/spec-compliance-review.md +43 -0
  20. package/template/.claude/skills/code-review/references/task-management-reviews.md +140 -0
  21. package/template/.claude/skills/code-review/references/verification-before-completion.md +139 -0
  22. package/template/.claude/skills/git/SKILL.md +131 -115
  23. package/template/.claude/skills/git/references/branch-management.md +88 -88
  24. package/template/.claude/skills/git/references/commit-standards.md +46 -46
  25. package/template/.claude/skills/git/references/context-efficiency.md +54 -0
  26. package/template/.claude/skills/git/references/gh-cli-guide.md +109 -109
  27. package/template/.claude/skills/git/references/safety-protocols.md +69 -69
  28. package/template/.claude/skills/git/references/workflow-commit.md +58 -58
  29. package/template/.claude/skills/git/references/workflow-merge-pr.md +136 -0
  30. package/template/.claude/skills/git/references/workflow-merge.md +48 -48
  31. package/template/.claude/skills/git/references/workflow-pr.md +58 -58
  32. package/template/.claude/skills/git/references/workflow-push.md +52 -52
  33. package/template/.claude/skills/skill-creator/LICENSE.txt +201 -201
  34. package/template/.claude/skills/skill-creator/SKILL.md +154 -149
  35. package/template/.claude/skills/skill-creator/agents/analyzer.md +274 -274
  36. package/template/.claude/skills/skill-creator/agents/comparator.md +202 -202
  37. package/template/.claude/skills/skill-creator/agents/grader.md +223 -223
  38. package/template/.claude/skills/skill-creator/assets/eval_review.html +146 -146
  39. package/template/.claude/skills/skill-creator/eval-viewer/generate_review.py +471 -471
  40. package/template/.claude/skills/skill-creator/eval-viewer/viewer.html +1325 -1325
  41. package/template/.claude/skills/skill-creator/references/benchmark-optimization-guide.md +86 -86
  42. package/template/.claude/skills/skill-creator/references/distribution-guide.md +79 -79
  43. package/template/.claude/skills/skill-creator/references/eval-infrastructure-guide.md +129 -129
  44. package/template/.claude/skills/skill-creator/references/eval-schemas.md +121 -121
  45. package/template/.claude/skills/skill-creator/references/mcp-skills-integration.md +71 -71
  46. package/template/.claude/skills/skill-creator/references/metadata-quality-criteria.md +94 -94
  47. package/template/.claude/skills/skill-creator/references/plugin-marketplace-hosting.md +104 -104
  48. package/template/.claude/skills/skill-creator/references/plugin-marketplace-overview.md +89 -89
  49. package/template/.claude/skills/skill-creator/references/plugin-marketplace-schema.md +93 -93
  50. package/template/.claude/skills/skill-creator/references/plugin-marketplace-sources.md +103 -103
  51. package/template/.claude/skills/skill-creator/references/plugin-marketplace-troubleshooting.md +76 -76
  52. package/template/.claude/skills/skill-creator/references/script-quality-criteria.md +106 -106
  53. package/template/.claude/skills/skill-creator/references/skill-anatomy-and-requirements.md +77 -77
  54. package/template/.claude/skills/skill-creator/references/skill-creation-workflow.md +152 -151
  55. package/template/.claude/skills/skill-creator/references/skill-design-patterns.md +75 -75
  56. package/template/.claude/skills/skill-creator/references/skillmark-benchmark-criteria.md +102 -102
  57. package/template/.claude/skills/skill-creator/references/structure-organization-criteria.md +114 -114
  58. package/template/.claude/skills/skill-creator/references/testing-and-iteration.md +78 -78
  59. package/template/.claude/skills/skill-creator/references/token-efficiency-criteria.md +74 -74
  60. package/template/.claude/skills/skill-creator/references/troubleshooting-guide.md +81 -81
  61. package/template/.claude/skills/skill-creator/references/validation-checklist.md +83 -83
  62. package/template/.claude/skills/skill-creator/references/writing-effective-instructions.md +88 -88
  63. package/template/.claude/skills/skill-creator/references/yaml-frontmatter-reference.md +92 -92
  64. package/template/.claude/skills/skill-creator/scripts/aggregate_benchmark.py +401 -401
  65. package/template/.claude/skills/skill-creator/scripts/encoding_utils.py +36 -36
  66. package/template/.claude/skills/skill-creator/scripts/generate_report.py +326 -326
  67. package/template/.claude/skills/skill-creator/scripts/improve_description.py +248 -248
  68. package/template/.claude/skills/skill-creator/scripts/init_skill.py +360 -360
  69. package/template/.claude/skills/skill-creator/scripts/package_skill.py +143 -143
  70. package/template/.claude/skills/skill-creator/scripts/quick_validate.py +110 -110
  71. package/template/.claude/skills/skill-creator/scripts/run_eval.py +310 -310
  72. package/template/.claude/skills/skill-creator/scripts/run_loop.py +332 -332
  73. package/template/.claude/skills/skill-creator/scripts/utils.py +47 -47
  74. package/template/.claude/statusline.cjs +0 -0
  75. package/template/.claude/skills/code-review/references/adversarial-review.md +0 -223
  76. /package/template/.claude/hooks/{usage-context-awareness.cjs → usage-quota-cache-refresh.cjs} +0 -0
@@ -1,277 +1,300 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * Transcript Parser - Extract tool/agent/todo state from session JSONL
6
- * @module transcript-parser
7
- */
8
-
9
- const fs = require('fs');
10
- const readline = require('readline');
11
-
12
- function isNativeTaskTodo(todo) {
13
- return Boolean(todo && todo._source === 'native_task');
14
- }
15
-
16
- function normalizeTodo(todo) {
17
- if (!todo || typeof todo !== 'object') return null;
18
- const normalized = {
19
- content: todo.content ?? '',
20
- status: todo.status ?? 'pending',
21
- activeForm: todo.activeForm ?? null
22
- };
23
- if (todo.id != null) normalized.id = todo.id;
24
- return normalized;
25
- }
26
-
27
- function extractTaskIdFromString(text) {
28
- if (!text || typeof text !== 'string') return null;
29
- const trimmed = text.trim();
30
- if (!trimmed) return null;
31
-
32
- try {
33
- const parsed = JSON.parse(trimmed);
34
- return extractTaskIdFromValue(parsed);
35
- } catch {
36
- // Not JSON, continue with regex extraction.
37
- }
38
-
39
- const match = trimmed.match(/["']?task[_-]?id["']?\s*[:=]\s*["']([^"']+)["']/i);
40
- if (match && match[1]) return match[1];
41
- return null;
42
- }
43
-
44
- function extractTaskIdFromValue(value) {
45
- if (value == null) return null;
46
-
47
- if (typeof value === 'string') {
48
- return extractTaskIdFromString(value);
49
- }
50
-
51
- if (typeof value !== 'object') return null;
52
-
53
- if (typeof value.taskId === 'string' || typeof value.taskId === 'number') {
54
- return String(value.taskId);
55
- }
56
- if (typeof value.task_id === 'string' || typeof value.task_id === 'number') {
57
- return String(value.task_id);
58
- }
59
-
60
- if (Array.isArray(value)) {
61
- for (const item of value) {
62
- const taskId = extractTaskIdFromValue(item);
63
- if (taskId) return taskId;
64
- }
65
- return null;
66
- }
67
-
68
- for (const fieldValue of Object.values(value)) {
69
- const taskId = extractTaskIdFromValue(fieldValue);
70
- if (taskId) return taskId;
71
- }
72
- return null;
73
- }
74
-
75
- /**
76
- * Parse transcript JSONL file
77
- * @param {string} transcriptPath - Path to transcript file
78
- * @returns {Promise<TranscriptData>}
79
- */
80
- async function parseTranscript(transcriptPath) {
81
- const result = {
82
- tools: [],
83
- agents: [],
84
- todos: [],
85
- sessionStart: null
86
- };
87
-
88
- if (!transcriptPath || !fs.existsSync(transcriptPath)) {
89
- return result;
90
- }
91
-
92
- const toolMap = new Map();
93
- const agentMap = new Map();
94
- let latestTodos = [];
95
-
96
- try {
97
- const fileStream = fs.createReadStream(transcriptPath);
98
- const rl = readline.createInterface({
99
- input: fileStream,
100
- crlfDelay: Infinity
101
- });
102
-
103
- for await (const line of rl) {
104
- if (!line.trim()) continue;
105
-
106
- try {
107
- const entry = JSON.parse(line);
108
- processEntry(entry, toolMap, agentMap, latestTodos, result);
109
- } catch {
110
- // Skip malformed lines
111
- }
112
- }
113
- } catch {
114
- // Return partial results on error
115
- }
116
-
117
- result.tools = Array.from(toolMap.values()).slice(-20);
118
- result.agents = Array.from(agentMap.values()).slice(-10);
119
- result.todos = latestTodos
120
- .map(normalizeTodo)
121
- .filter(Boolean);
122
-
123
- return result;
124
- }
125
-
126
- /**
127
- * Process single JSONL entry
128
- * @param {Object} entry - Parsed JSON line
129
- * @param {Map} toolMap - Tool tracking map
130
- * @param {Map} agentMap - Agent tracking map
131
- * @param {Array} latestTodos - Latest todo array reference
132
- * @param {Object} result - Result object
133
- */
134
- function processEntry(entry, toolMap, agentMap, latestTodos, result) {
135
- const timestamp = entry.timestamp ? new Date(entry.timestamp) : new Date();
136
-
137
- // Track session start
138
- if (!result.sessionStart && entry.timestamp) {
139
- result.sessionStart = timestamp;
140
- }
141
-
142
- const content = entry.message?.content;
143
- if (!content || !Array.isArray(content)) return;
144
-
145
- for (const block of content) {
146
- // Handle tool_use blocks
147
- if (block.type === 'tool_use' && block.id && block.name) {
148
- if (block.name === 'Task') {
149
- // Agent spawn
150
- agentMap.set(block.id, {
151
- id: block.id,
152
- type: block.input?.subagent_type ?? 'unknown',
153
- model: block.input?.model ?? null,
154
- description: block.input?.description ?? null,
155
- status: 'running',
156
- startTime: timestamp,
157
- endTime: null
158
- });
159
- } else if (block.name === 'TodoWrite') {
160
- // Legacy: Replace todo array (deprecated, kept for backwards compatibility)
161
- if (block.input?.todos && Array.isArray(block.input.todos)) {
162
- latestTodos.length = 0;
163
- latestTodos.push(
164
- ...block.input.todos.map(todo => ({
165
- ...todo,
166
- _source: 'legacy_todowrite'
167
- }))
168
- );
169
- }
170
- } else if (block.name === 'TaskCreate') {
171
- // Native Task API: add new task.
172
- // Track by tool_use id first; hydrate real task id from matching tool_result when present.
173
- if (block.input?.subject) {
174
- latestTodos.push({
175
- id: block.id,
176
- content: block.input.subject,
177
- status: 'pending',
178
- activeForm: block.input.activeForm || null,
179
- _source: 'native_task',
180
- _toolUseId: block.id
181
- });
182
- }
183
- } else if (block.name === 'TaskUpdate') {
184
- // Native Task API: Update existing task status
185
- // Match by taskId against native-task ids first.
186
- // Numeric fallback maps to native-task creation order only (not legacy TodoWrite items).
187
- if (block.input?.taskId && block.input?.status) {
188
- const taskId = String(block.input.taskId);
189
- const nativeTodos = latestTodos.filter(isNativeTaskTodo);
190
- let task = nativeTodos.find(t => String(t.id) === taskId);
191
- if (!task && /^\d+$/.test(taskId)) {
192
- const idx = Number(taskId) - 1;
193
- if (idx >= 0 && idx < nativeTodos.length) task = nativeTodos[idx];
194
- }
195
-
196
- if (task) {
197
- task.status = block.input.status;
198
- if (Object.prototype.hasOwnProperty.call(block.input, 'activeForm')) {
199
- task.activeForm = block.input.activeForm || null;
200
- }
201
- }
202
- }
203
- } else {
204
- // Regular tool
205
- toolMap.set(block.id, {
206
- id: block.id,
207
- name: block.name,
208
- target: extractTarget(block.name, block.input),
209
- status: 'running',
210
- startTime: timestamp,
211
- endTime: null
212
- });
213
- }
214
- }
215
-
216
- // Handle tool_result blocks
217
- if (block.type === 'tool_result' && block.tool_use_id) {
218
- const tool = toolMap.get(block.tool_use_id);
219
- if (tool) {
220
- tool.status = block.is_error ? 'error' : 'completed';
221
- tool.endTime = timestamp;
222
- }
223
-
224
- const agent = agentMap.get(block.tool_use_id);
225
- if (agent) {
226
- agent.status = 'completed';
227
- agent.endTime = timestamp;
228
- }
229
-
230
- const createdTask = latestTodos.find(
231
- todo => isNativeTaskTodo(todo) && todo._toolUseId === block.tool_use_id
232
- );
233
- if (createdTask) {
234
- const hydratedId = extractTaskIdFromValue(block.content);
235
- if (hydratedId) {
236
- createdTask.id = hydratedId;
237
- }
238
- }
239
- }
240
- }
241
- }
242
-
243
- /**
244
- * Extract target from tool input
245
- * @param {string} toolName - Tool name
246
- * @param {Object} input - Tool input object
247
- * @returns {string|null} - Extracted target
248
- */
249
- function extractTarget(toolName, input) {
250
- if (!input) return null;
251
-
252
- switch (toolName) {
253
- case 'Read':
254
- case 'Write':
255
- case 'Edit':
256
- return input.file_path ?? input.path ?? null;
257
-
258
- case 'Glob':
259
- case 'Grep':
260
- return input.pattern ?? null;
261
-
262
- case 'Bash':
263
- const cmd = input.command;
264
- if (!cmd) return null;
265
- return cmd.length > 30 ? cmd.slice(0, 30) + '...' : cmd;
266
-
267
- default:
268
- return null;
269
- }
270
- }
271
-
272
- module.exports = {
273
- parseTranscript,
274
- // Export for testing
275
- processEntry,
276
- extractTarget
277
- };
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Transcript Parser - Extract tool/agent/todo state from session JSONL
6
+ * @module transcript-parser
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const readline = require('readline');
11
+
12
+ function isNativeTaskTodo(todo) {
13
+ return Boolean(todo && todo._source === 'native_task');
14
+ }
15
+
16
+ function normalizeTodo(todo) {
17
+ if (!todo || typeof todo !== 'object') return null;
18
+ const normalized = {
19
+ content: todo.content ?? '',
20
+ status: todo.status ?? 'pending',
21
+ activeForm: todo.activeForm ?? null
22
+ };
23
+ if (todo.id != null) normalized.id = todo.id;
24
+ return normalized;
25
+ }
26
+
27
+ function extractTaskIdFromString(text) {
28
+ if (!text || typeof text !== 'string') return null;
29
+ const trimmed = text.trim();
30
+ if (!trimmed) return null;
31
+
32
+ try {
33
+ const parsed = JSON.parse(trimmed);
34
+ return extractTaskIdFromValue(parsed);
35
+ } catch {
36
+ // Not JSON, continue with regex extraction.
37
+ }
38
+
39
+ const match = trimmed.match(/["']?task[_-]?id["']?\s*[:=]\s*["']([^"']+)["']/i);
40
+ if (match && match[1]) return match[1];
41
+ return null;
42
+ }
43
+
44
+ function extractTaskIdFromValue(value) {
45
+ if (value == null) return null;
46
+
47
+ if (typeof value === 'string') {
48
+ return extractTaskIdFromString(value);
49
+ }
50
+
51
+ if (typeof value !== 'object') return null;
52
+
53
+ if (typeof value.taskId === 'string' || typeof value.taskId === 'number') {
54
+ return String(value.taskId);
55
+ }
56
+ if (typeof value.task_id === 'string' || typeof value.task_id === 'number') {
57
+ return String(value.task_id);
58
+ }
59
+
60
+ if (Array.isArray(value)) {
61
+ for (const item of value) {
62
+ const taskId = extractTaskIdFromValue(item);
63
+ if (taskId) return taskId;
64
+ }
65
+ return null;
66
+ }
67
+
68
+ for (const fieldValue of Object.values(value)) {
69
+ const taskId = extractTaskIdFromValue(fieldValue);
70
+ if (taskId) return taskId;
71
+ }
72
+ return null;
73
+ }
74
+
75
+ /**
76
+ * Parse transcript JSONL file
77
+ * @param {string} transcriptPath - Path to transcript file
78
+ * @returns {Promise<TranscriptData>}
79
+ */
80
+ async function parseTranscript(transcriptPath) {
81
+ const result = {
82
+ tools: [],
83
+ agents: [],
84
+ todos: [],
85
+ sessionStart: null,
86
+ statuslineActivityCount: 0,
87
+ invalidLineCount: 0,
88
+ lastValidEntryAt: null,
89
+ lastActivityAt: null
90
+ };
91
+
92
+ if (!transcriptPath || !fs.existsSync(transcriptPath)) {
93
+ return result;
94
+ }
95
+
96
+ const toolMap = new Map();
97
+ const agentMap = new Map();
98
+ let latestTodos = [];
99
+
100
+ try {
101
+ const fileStream = fs.createReadStream(transcriptPath);
102
+ const rl = readline.createInterface({
103
+ input: fileStream,
104
+ crlfDelay: Infinity
105
+ });
106
+
107
+ for await (const line of rl) {
108
+ if (!line.trim()) continue;
109
+
110
+ try {
111
+ const entry = JSON.parse(line);
112
+ processEntry(entry, toolMap, agentMap, latestTodos, result);
113
+ } catch {
114
+ result.invalidLineCount += 1;
115
+ }
116
+ }
117
+ } catch {
118
+ // Return partial results on error
119
+ }
120
+
121
+ result.tools = Array.from(toolMap.values()).slice(-20);
122
+ result.agents = Array.from(agentMap.values()).slice(-10);
123
+ result.todos = latestTodos
124
+ .map(normalizeTodo)
125
+ .filter(Boolean);
126
+
127
+ return result;
128
+ }
129
+
130
+ /**
131
+ * Process single JSONL entry
132
+ * @param {Object} entry - Parsed JSON line
133
+ * @param {Map} toolMap - Tool tracking map
134
+ * @param {Map} agentMap - Agent tracking map
135
+ * @param {Array} latestTodos - Latest todo array reference
136
+ * @param {Object} result - Result object
137
+ */
138
+ function processEntry(entry, toolMap, agentMap, latestTodos, result) {
139
+ const parsedTimestamp = entry.timestamp ? new Date(entry.timestamp) : new Date();
140
+ const timestamp = Number.isNaN(parsedTimestamp.getTime()) ? new Date() : parsedTimestamp;
141
+ const timestampIso = timestamp.toISOString();
142
+ let hadActivity = false;
143
+
144
+ result.lastValidEntryAt = timestampIso;
145
+
146
+ // Track session start
147
+ if (!result.sessionStart && entry.timestamp) {
148
+ result.sessionStart = timestamp;
149
+ }
150
+
151
+ const content = entry.message?.content;
152
+ if (!content || !Array.isArray(content)) return;
153
+
154
+ for (const block of content) {
155
+ // Handle tool_use blocks
156
+ if (block.type === 'tool_use' && block.id && block.name) {
157
+ if (block.name === 'Task') {
158
+ result.statuslineActivityCount += 1;
159
+ hadActivity = true;
160
+ // Agent spawn
161
+ agentMap.set(block.id, {
162
+ id: block.id,
163
+ type: block.input?.subagent_type ?? 'unknown',
164
+ model: block.input?.model ?? null,
165
+ description: block.input?.description ?? null,
166
+ status: 'running',
167
+ startTime: timestamp,
168
+ endTime: null
169
+ });
170
+ } else if (block.name === 'TodoWrite') {
171
+ result.statuslineActivityCount += 1;
172
+ hadActivity = true;
173
+ // Legacy: Replace todo array (deprecated, kept for backwards compatibility)
174
+ if (block.input?.todos && Array.isArray(block.input.todos)) {
175
+ latestTodos.length = 0;
176
+ latestTodos.push(
177
+ ...block.input.todos.map(todo => ({
178
+ ...todo,
179
+ _source: 'legacy_todowrite'
180
+ }))
181
+ );
182
+ }
183
+ } else if (block.name === 'TaskCreate') {
184
+ result.statuslineActivityCount += 1;
185
+ hadActivity = true;
186
+ // Native Task API: add new task.
187
+ // Track by tool_use id first; hydrate real task id from matching tool_result when present.
188
+ if (block.input?.subject) {
189
+ latestTodos.push({
190
+ id: block.id,
191
+ content: block.input.subject,
192
+ status: 'pending',
193
+ activeForm: block.input.activeForm || null,
194
+ _source: 'native_task',
195
+ _toolUseId: block.id
196
+ });
197
+ }
198
+ } else if (block.name === 'TaskUpdate') {
199
+ result.statuslineActivityCount += 1;
200
+ hadActivity = true;
201
+ // Native Task API: Update existing task status
202
+ // Match by taskId against native-task ids first.
203
+ // Numeric fallback maps to native-task creation order only (not legacy TodoWrite items).
204
+ if (block.input?.taskId && block.input?.status) {
205
+ const taskId = String(block.input.taskId);
206
+ const nativeTodos = latestTodos.filter(isNativeTaskTodo);
207
+ let task = nativeTodos.find(t => String(t.id) === taskId);
208
+ if (!task && /^\d+$/.test(taskId)) {
209
+ const idx = Number(taskId) - 1;
210
+ if (idx >= 0 && idx < nativeTodos.length) task = nativeTodos[idx];
211
+ }
212
+
213
+ if (task) {
214
+ task.status = block.input.status;
215
+ if (Object.prototype.hasOwnProperty.call(block.input, 'activeForm')) {
216
+ task.activeForm = block.input.activeForm || null;
217
+ }
218
+ }
219
+ }
220
+ } else {
221
+ // Regular tool
222
+ toolMap.set(block.id, {
223
+ id: block.id,
224
+ name: block.name,
225
+ target: extractTarget(block.name, block.input),
226
+ status: 'running',
227
+ startTime: timestamp,
228
+ endTime: null
229
+ });
230
+ }
231
+ }
232
+
233
+ // Handle tool_result blocks
234
+ if (block.type === 'tool_result' && block.tool_use_id) {
235
+ const tool = toolMap.get(block.tool_use_id);
236
+ if (tool) {
237
+ tool.status = block.is_error ? 'error' : 'completed';
238
+ tool.endTime = timestamp;
239
+ }
240
+
241
+ const agent = agentMap.get(block.tool_use_id);
242
+ if (agent) {
243
+ result.statuslineActivityCount += 1;
244
+ hadActivity = true;
245
+ agent.status = 'completed';
246
+ agent.endTime = timestamp;
247
+ }
248
+
249
+ const createdTask = latestTodos.find(
250
+ todo => isNativeTaskTodo(todo) && todo._toolUseId === block.tool_use_id
251
+ );
252
+ if (createdTask) {
253
+ const hydratedId = extractTaskIdFromValue(block.content);
254
+ if (hydratedId) {
255
+ createdTask.id = hydratedId;
256
+ }
257
+ }
258
+ }
259
+ }
260
+
261
+ if (hadActivity) {
262
+ result.lastActivityAt = timestampIso;
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Extract target from tool input
268
+ * @param {string} toolName - Tool name
269
+ * @param {Object} input - Tool input object
270
+ * @returns {string|null} - Extracted target
271
+ */
272
+ function extractTarget(toolName, input) {
273
+ if (!input) return null;
274
+
275
+ switch (toolName) {
276
+ case 'Read':
277
+ case 'Write':
278
+ case 'Edit':
279
+ return input.file_path ?? input.path ?? null;
280
+
281
+ case 'Glob':
282
+ case 'Grep':
283
+ return input.pattern ?? null;
284
+
285
+ case 'Bash':
286
+ const cmd = input.command;
287
+ if (!cmd) return null;
288
+ return cmd.length > 30 ? cmd.slice(0, 30) + '...' : cmd;
289
+
290
+ default:
291
+ return null;
292
+ }
293
+ }
294
+
295
+ module.exports = {
296
+ parseTranscript,
297
+ // Export for testing
298
+ processEntry,
299
+ extractTarget
300
+ };