opencodekit 0.0.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 (123) hide show
  1. package/README.md +258 -0
  2. package/dist/index.js +3391 -0
  3. package/dist/template/.opencode/.env.example +193 -0
  4. package/dist/template/.opencode/AGENTS.md +214 -0
  5. package/dist/template/.opencode/README.md +269 -0
  6. package/dist/template/.opencode/agent/build.md +75 -0
  7. package/dist/template/.opencode/agent/explore.md +66 -0
  8. package/dist/template/.opencode/agent/planner.md +83 -0
  9. package/dist/template/.opencode/agent/review.md +90 -0
  10. package/dist/template/.opencode/agent/rush.md +85 -0
  11. package/dist/template/.opencode/agent/scout.md +93 -0
  12. package/dist/template/.opencode/command/analyze-project.md +39 -0
  13. package/dist/template/.opencode/command/brainstorm.md +11 -0
  14. package/dist/template/.opencode/command/commit.md +11 -0
  15. package/dist/template/.opencode/command/create.md +118 -0
  16. package/dist/template/.opencode/command/design.md +15 -0
  17. package/dist/template/.opencode/command/finish.md +233 -0
  18. package/dist/template/.opencode/command/fix-ci.md +20 -0
  19. package/dist/template/.opencode/command/fix-types.md +10 -0
  20. package/dist/template/.opencode/command/fix-ui.md +22 -0
  21. package/dist/template/.opencode/command/fix.md +22 -0
  22. package/dist/template/.opencode/command/handoff.md +146 -0
  23. package/dist/template/.opencode/command/implement.md +167 -0
  24. package/dist/template/.opencode/command/import-plan.md +188 -0
  25. package/dist/template/.opencode/command/integration-test.md +36 -0
  26. package/dist/template/.opencode/command/issue.md +41 -0
  27. package/dist/template/.opencode/command/plan.md +158 -0
  28. package/dist/template/.opencode/command/pr.md +36 -0
  29. package/dist/template/.opencode/command/quick-build.md +13 -0
  30. package/dist/template/.opencode/command/research-and-implement.md +21 -0
  31. package/dist/template/.opencode/command/research-ui.md +32 -0
  32. package/dist/template/.opencode/command/research.md +153 -0
  33. package/dist/template/.opencode/command/resume.md +127 -0
  34. package/dist/template/.opencode/command/review-codebase.md +13 -0
  35. package/dist/template/.opencode/command/skill-create.md +29 -0
  36. package/dist/template/.opencode/command/skill-optimize.md +28 -0
  37. package/dist/template/.opencode/command/status.md +109 -0
  38. package/dist/template/.opencode/command/ui-review.md +28 -0
  39. package/dist/template/.opencode/dcp.jsonc +34 -0
  40. package/dist/template/.opencode/memory/README.md +128 -0
  41. package/dist/template/.opencode/memory/_templates/handoff.md +33 -0
  42. package/dist/template/.opencode/memory/_templates/research.md +29 -0
  43. package/dist/template/.opencode/memory/_templates/task-prd.md +43 -0
  44. package/dist/template/.opencode/memory/_templates/task-review.md +73 -0
  45. package/dist/template/.opencode/memory/_templates/task-spec.md +71 -0
  46. package/dist/template/.opencode/memory/design-guidelines.md +281 -0
  47. package/dist/template/.opencode/memory/handoffs/README.md +83 -0
  48. package/dist/template/.opencode/opencode.json +469 -0
  49. package/dist/template/.opencode/package.json +23 -0
  50. package/dist/template/.opencode/pickle-thinker.jsonc +11 -0
  51. package/dist/template/.opencode/plugin/README.md +162 -0
  52. package/dist/template/.opencode/plugin/notification.ts +88 -0
  53. package/dist/template/.opencode/plugin/sessions.ts +434 -0
  54. package/dist/template/.opencode/plugin/superpowers.ts +332 -0
  55. package/dist/template/.opencode/plugin/tsconfig.json +15 -0
  56. package/dist/template/.opencode/superpowers/.claude/settings.local.json +141 -0
  57. package/dist/template/.opencode/superpowers/.claude-plugin/marketplace.json +20 -0
  58. package/dist/template/.opencode/superpowers/.claude-plugin/plugin.json +13 -0
  59. package/dist/template/.opencode/superpowers/.codex/INSTALL.md +35 -0
  60. package/dist/template/.opencode/superpowers/.codex/superpowers-bootstrap.md +33 -0
  61. package/dist/template/.opencode/superpowers/.codex/superpowers-codex +267 -0
  62. package/dist/template/.opencode/superpowers/.github/FUNDING.yml +3 -0
  63. package/dist/template/.opencode/superpowers/.opencode/INSTALL.md +135 -0
  64. package/dist/template/.opencode/superpowers/.opencode/plugin/superpowers.js +215 -0
  65. package/dist/template/.opencode/superpowers/LICENSE +21 -0
  66. package/dist/template/.opencode/superpowers/README.md +165 -0
  67. package/dist/template/.opencode/superpowers/RELEASE-NOTES.md +493 -0
  68. package/dist/template/.opencode/superpowers/agents/code-reviewer.md +48 -0
  69. package/dist/template/.opencode/superpowers/commands/brainstorm.md +5 -0
  70. package/dist/template/.opencode/superpowers/commands/execute-plan.md +5 -0
  71. package/dist/template/.opencode/superpowers/commands/write-plan.md +5 -0
  72. package/dist/template/.opencode/superpowers/docs/README.codex.md +153 -0
  73. package/dist/template/.opencode/superpowers/docs/README.opencode.md +234 -0
  74. package/dist/template/.opencode/superpowers/docs/plans/2025-11-22-opencode-support-design.md +294 -0
  75. package/dist/template/.opencode/superpowers/docs/plans/2025-11-22-opencode-support-implementation.md +1095 -0
  76. package/dist/template/.opencode/superpowers/hooks/hooks.json +15 -0
  77. package/dist/template/.opencode/superpowers/hooks/session-start.sh +34 -0
  78. package/dist/template/.opencode/superpowers/lib/skills-core.js +208 -0
  79. package/dist/template/.opencode/superpowers/skills/brainstorming/SKILL.md +54 -0
  80. package/dist/template/.opencode/superpowers/skills/condition-based-waiting/SKILL.md +120 -0
  81. package/dist/template/.opencode/superpowers/skills/condition-based-waiting/example.ts +158 -0
  82. package/dist/template/.opencode/superpowers/skills/defense-in-depth/SKILL.md +127 -0
  83. package/dist/template/.opencode/superpowers/skills/dispatching-parallel-agents/SKILL.md +180 -0
  84. package/dist/template/.opencode/superpowers/skills/executing-plans/SKILL.md +76 -0
  85. package/dist/template/.opencode/superpowers/skills/finishing-a-development-branch/SKILL.md +200 -0
  86. package/dist/template/.opencode/superpowers/skills/frontend-aesthetics/SKILL.md +137 -0
  87. package/dist/template/.opencode/superpowers/skills/gemini-large-context/SKILL.md +205 -0
  88. package/dist/template/.opencode/superpowers/skills/receiving-code-review/SKILL.md +209 -0
  89. package/dist/template/.opencode/superpowers/skills/requesting-code-review/SKILL.md +105 -0
  90. package/dist/template/.opencode/superpowers/skills/requesting-code-review/code-reviewer.md +146 -0
  91. package/dist/template/.opencode/superpowers/skills/root-cause-tracing/SKILL.md +174 -0
  92. package/dist/template/.opencode/superpowers/skills/root-cause-tracing/find-polluter.sh +63 -0
  93. package/dist/template/.opencode/superpowers/skills/sharing-skills/SKILL.md +194 -0
  94. package/dist/template/.opencode/superpowers/skills/subagent-driven-development/SKILL.md +189 -0
  95. package/dist/template/.opencode/superpowers/skills/systematic-debugging/CREATION-LOG.md +119 -0
  96. package/dist/template/.opencode/superpowers/skills/systematic-debugging/SKILL.md +295 -0
  97. package/dist/template/.opencode/superpowers/skills/systematic-debugging/test-academic.md +14 -0
  98. package/dist/template/.opencode/superpowers/skills/systematic-debugging/test-pressure-1.md +58 -0
  99. package/dist/template/.opencode/superpowers/skills/systematic-debugging/test-pressure-2.md +68 -0
  100. package/dist/template/.opencode/superpowers/skills/systematic-debugging/test-pressure-3.md +69 -0
  101. package/dist/template/.opencode/superpowers/skills/test-driven-development/SKILL.md +364 -0
  102. package/dist/template/.opencode/superpowers/skills/testing-anti-patterns/SKILL.md +302 -0
  103. package/dist/template/.opencode/superpowers/skills/testing-skills-with-subagents/SKILL.md +387 -0
  104. package/dist/template/.opencode/superpowers/skills/testing-skills-with-subagents/examples/CLAUDE_MD_TESTING.md +189 -0
  105. package/dist/template/.opencode/superpowers/skills/ui-ux-research/SKILL.md +191 -0
  106. package/dist/template/.opencode/superpowers/skills/using-git-worktrees/SKILL.md +213 -0
  107. package/dist/template/.opencode/superpowers/skills/using-superpowers/SKILL.md +101 -0
  108. package/dist/template/.opencode/superpowers/skills/verification-before-completion/SKILL.md +139 -0
  109. package/dist/template/.opencode/superpowers/skills/writing-plans/SKILL.md +116 -0
  110. package/dist/template/.opencode/superpowers/skills/writing-skills/SKILL.md +622 -0
  111. package/dist/template/.opencode/superpowers/skills/writing-skills/anthropic-best-practices.md +1150 -0
  112. package/dist/template/.opencode/superpowers/skills/writing-skills/graphviz-conventions.dot +172 -0
  113. package/dist/template/.opencode/superpowers/skills/writing-skills/persuasion-principles.md +187 -0
  114. package/dist/template/.opencode/superpowers/tests/opencode/run-tests.sh +165 -0
  115. package/dist/template/.opencode/superpowers/tests/opencode/setup.sh +73 -0
  116. package/dist/template/.opencode/superpowers/tests/opencode/test-plugin-loading.sh +81 -0
  117. package/dist/template/.opencode/superpowers/tests/opencode/test-priority.sh +198 -0
  118. package/dist/template/.opencode/superpowers/tests/opencode/test-skills-core.sh +440 -0
  119. package/dist/template/.opencode/superpowers/tests/opencode/test-tools.sh +104 -0
  120. package/dist/template/.opencode/tool/memory-read.ts +66 -0
  121. package/dist/template/.opencode/tool/memory-update.ts +61 -0
  122. package/dist/template/.opencode/tsconfig.json +21 -0
  123. package/package.json +52 -0
@@ -0,0 +1,88 @@
1
+ /**
2
+ * OpenCode Notification Plugin
3
+ * Sends native notifications when sessions complete
4
+ */
5
+
6
+ import type { Plugin } from "@opencode-ai/plugin";
7
+
8
+ export const NotificationPlugin: Plugin = async ({ client, $ }) => {
9
+ // Log plugin initialization
10
+ client.app
11
+ .log({
12
+ body: {
13
+ service: "notification-plugin",
14
+ level: "info",
15
+ message: "🔔 Notification Plugin loaded",
16
+ },
17
+ })
18
+ .catch(() => {});
19
+
20
+ return {
21
+ event: async ({ event }) => {
22
+ // Send notification on session completion
23
+ if (event.type === "session.idle") {
24
+ const sessionId = event.properties?.sessionID;
25
+
26
+ // Get session summary with simple retry
27
+ setTimeout(async () => {
28
+ try {
29
+ let summary = "Session completed";
30
+
31
+ // Try to fetch session summary
32
+ const messagesResponse = await client.session.messages({
33
+ path: { id: sessionId },
34
+ });
35
+
36
+ if (messagesResponse.data && Array.isArray(messagesResponse.data)) {
37
+ const lastUserMessage = messagesResponse.data
38
+ .filter((m) => m.info.role === "user")
39
+ .pop();
40
+
41
+ // Type guard for summary object
42
+ const messageSummary = lastUserMessage?.info?.summary;
43
+ if (
44
+ messageSummary &&
45
+ typeof messageSummary === "object" &&
46
+ messageSummary !== null
47
+ ) {
48
+ if ("body" in messageSummary && messageSummary.body) {
49
+ summary = String(messageSummary.body).trim().slice(0, 100);
50
+ } else if ("title" in messageSummary && messageSummary.title) {
51
+ summary = String(messageSummary.title).trim();
52
+ }
53
+ }
54
+ }
55
+
56
+ // Send notification
57
+ if (process.platform === "darwin") {
58
+ const script = `display notification "${summary.replace(/"/g, '\\"')}" with title "OpenCode"`;
59
+ await $`osascript -e ${script}`;
60
+ } else {
61
+ await $`notify-send "OpenCode" ${summary}`;
62
+ }
63
+
64
+ client.app
65
+ .log({
66
+ body: {
67
+ service: "notification-plugin",
68
+ level: "info",
69
+ message: `✅ Notification sent: ${summary}`,
70
+ },
71
+ })
72
+ .catch(() => {});
73
+ } catch (error) {
74
+ client.app
75
+ .log({
76
+ body: {
77
+ service: "notification-plugin",
78
+ level: "warn",
79
+ message: `⚠️ Notification failed: ${(error as Error).message}`,
80
+ },
81
+ })
82
+ .catch(() => {});
83
+ }
84
+ }, 2000);
85
+ }
86
+ },
87
+ };
88
+ };
@@ -0,0 +1,434 @@
1
+ import { existsSync, readdirSync } from "fs";
2
+ import { join } from "path";
3
+ import { type Plugin, tool } from "@opencode-ai/plugin";
4
+ import { readFile } from "fs/promises";
5
+
6
+ /**
7
+ * read-session plugin - AmpCode-style thread context transfer
8
+ *
9
+ * Enables short, focused sessions by allowing cross-session context reads.
10
+ * Similar to AmpCode's read_thread tool.
11
+ *
12
+ * OpenCode Storage Structure:
13
+ * - Sessions: ~/.local/share/opencode/storage/session/<project_hash>/<session_id>.json
14
+ * - Messages: ~/.local/share/opencode/storage/message/<session_id>/msg_*.json
15
+ * - Diffs: ~/.local/share/opencode/storage/session_diff/<session_id>.json
16
+ */
17
+
18
+ interface SessionMetadata {
19
+ id: string;
20
+ title?: string;
21
+ directory?: string;
22
+ projectID?: string;
23
+ time?: {
24
+ created: number;
25
+ updated: number;
26
+ };
27
+ summary?: any;
28
+ }
29
+
30
+ interface SessionInfo extends SessionMetadata {
31
+ messageCount?: number;
32
+ fileCount?: number;
33
+ }
34
+
35
+ function parseDate(dateStr: string): Date | null {
36
+ const today = new Date();
37
+ today.setHours(0, 0, 0, 0);
38
+
39
+ if (dateStr === "today") {
40
+ return today;
41
+ }
42
+
43
+ if (dateStr === "yesterday") {
44
+ const yesterday = new Date(today);
45
+ yesterday.setDate(yesterday.getDate() - 1);
46
+ return yesterday;
47
+ }
48
+
49
+ if (dateStr === "this week") {
50
+ const weekStart = new Date(today);
51
+ weekStart.setDate(weekStart.getDate() - weekStart.getDay());
52
+ return weekStart;
53
+ }
54
+
55
+ // Try parsing ISO date
56
+ const parsed = new Date(dateStr);
57
+ if (!Number.isNaN(parsed.getTime())) {
58
+ return parsed;
59
+ }
60
+
61
+ return null;
62
+ }
63
+
64
+ export const SessionsPlugin: Plugin = async ({ directory }) => {
65
+ const storageDir = join(
66
+ process.env.HOME || "",
67
+ ".local/share/opencode/storage",
68
+ );
69
+
70
+ async function getSessionMetadata(
71
+ sessionId: string,
72
+ ): Promise<SessionMetadata | null> {
73
+ const sessionDir = join(storageDir, "session");
74
+ if (!existsSync(sessionDir)) return null;
75
+
76
+ // Search all project directories
77
+ const projectDirs = readdirSync(sessionDir);
78
+ for (const projectHash of projectDirs) {
79
+ const sessionFile = join(sessionDir, projectHash, `${sessionId}.json`);
80
+ if (existsSync(sessionFile)) {
81
+ const content = await readFile(sessionFile, "utf-8");
82
+ return JSON.parse(content);
83
+ }
84
+ }
85
+ return null;
86
+ }
87
+
88
+ async function listSessions(
89
+ projectDir?: string,
90
+ since?: string,
91
+ limit = 20,
92
+ ): Promise<SessionInfo[]> {
93
+ const messageDir = join(storageDir, "message");
94
+ if (!existsSync(messageDir)) return [];
95
+
96
+ const sessionIds = readdirSync(messageDir).filter((f) =>
97
+ f.startsWith("ses_"),
98
+ );
99
+
100
+ const sessions: SessionInfo[] = [];
101
+ let sinceDate: Date | null = null;
102
+
103
+ if (since) {
104
+ sinceDate = parseDate(since);
105
+ }
106
+
107
+ for (const sessionId of sessionIds) {
108
+ const metadata = await getSessionMetadata(sessionId);
109
+ if (!metadata) continue;
110
+
111
+ // Filter by project
112
+ if (projectDir && metadata.directory !== projectDir) {
113
+ continue;
114
+ }
115
+
116
+ // Filter by date
117
+ if (sinceDate && metadata.time) {
118
+ const sessionDate = new Date(metadata.time.created);
119
+ if (sessionDate < sinceDate) {
120
+ continue;
121
+ }
122
+ }
123
+
124
+ // Get message count
125
+ const sessionMsgDir = join(messageDir, sessionId);
126
+ let messageCount = 0;
127
+ if (existsSync(sessionMsgDir)) {
128
+ messageCount = readdirSync(sessionMsgDir).filter((f) =>
129
+ f.endsWith(".json"),
130
+ ).length;
131
+ }
132
+
133
+ // Get file count
134
+ const diffFile = join(storageDir, "session_diff", `${sessionId}.json`);
135
+ let fileCount = 0;
136
+ if (existsSync(diffFile)) {
137
+ const diffContent = await readFile(diffFile, "utf-8");
138
+ const diffs = JSON.parse(diffContent);
139
+ fileCount = diffs.length;
140
+ }
141
+
142
+ sessions.push({
143
+ ...metadata,
144
+ messageCount,
145
+ fileCount,
146
+ });
147
+ }
148
+
149
+ // Sort by time descending
150
+ sessions.sort((a, b) => {
151
+ const aTime = a.time?.created || 0;
152
+ const bTime = b.time?.created || 0;
153
+ return bTime - aTime;
154
+ });
155
+
156
+ return sessions.slice(0, limit);
157
+ }
158
+
159
+ return {
160
+ tool: {
161
+ list_sessions: tool({
162
+ description:
163
+ "List OpenCode sessions with metadata. Filter by project and date. Use this before read_session to discover available sessions.",
164
+ args: {
165
+ project: tool.schema
166
+ .string()
167
+ .optional()
168
+ .describe(
169
+ "Filter by project: 'current' (default), 'all', or absolute path",
170
+ ),
171
+ since: tool.schema
172
+ .string()
173
+ .optional()
174
+ .describe(
175
+ "Filter by date: 'today', 'yesterday', 'this week', or ISO date (e.g. '2024-12-10')",
176
+ ),
177
+ limit: tool.schema
178
+ .number()
179
+ .optional()
180
+ .describe("Max sessions to return (default: 20)"),
181
+ },
182
+ async execute(args) {
183
+ const projectFilter =
184
+ !args.project || args.project === "current"
185
+ ? directory
186
+ : args.project === "all"
187
+ ? undefined
188
+ : args.project;
189
+
190
+ const sessions = await listSessions(
191
+ projectFilter,
192
+ args.since,
193
+ args.limit || 20,
194
+ );
195
+
196
+ if (sessions.length === 0) {
197
+ return "No sessions found matching filters.";
198
+ }
199
+
200
+ let output = `# Sessions (${sessions.length})\n\n`;
201
+
202
+ if (projectFilter && projectFilter !== "all") {
203
+ output += `**Project:** ${projectFilter}\n`;
204
+ }
205
+ if (args.since) {
206
+ output += `**Since:** ${args.since}\n`;
207
+ }
208
+ output += "\n";
209
+
210
+ sessions.forEach((s, i) => {
211
+ const date = s.time?.created
212
+ ? new Date(s.time.created).toLocaleString()
213
+ : "Unknown";
214
+ const title = s.title || s.id;
215
+ output += `${i + 1}. **${s.id}**\n`;
216
+ output += ` Title: ${title}\n`;
217
+ output += ` Date: ${date}\n`;
218
+ output += ` Messages: ${s.messageCount || 0}, Files: ${s.fileCount || 0}\n`;
219
+ if (s.summary && typeof s.summary === "object") {
220
+ const sumObj = s.summary as any;
221
+ if (
222
+ sumObj.additions !== undefined ||
223
+ sumObj.deletions !== undefined
224
+ ) {
225
+ output += ` Changes: +${sumObj.additions || 0}/-${sumObj.deletions || 0}\n`;
226
+ }
227
+ }
228
+ output += "\n";
229
+ });
230
+
231
+ return output;
232
+ },
233
+ }),
234
+
235
+ read_session: tool({
236
+ description: `Read context from a previous OpenCode session. Use list_sessions first to discover sessions. Supports project filtering and date-based selection.`,
237
+ args: {
238
+ session_reference: tool.schema
239
+ .string()
240
+ .describe(
241
+ "Session ID (e.g. 'ses_abc123'), relative ref ('last', 'previous', '2 ago'), or date ('today', 'yesterday')",
242
+ ),
243
+ project: tool.schema
244
+ .string()
245
+ .optional()
246
+ .describe(
247
+ "Filter by project: 'current' (default), 'all', or absolute path. Applied when using relative/date references.",
248
+ ),
249
+ focus: tool.schema
250
+ .string()
251
+ .optional()
252
+ .describe(
253
+ "Optional: specific aspect to extract (e.g. 'implementation', 'bug findings', 'file changes')",
254
+ ),
255
+ },
256
+ async execute(args) {
257
+ if (!existsSync(storageDir)) {
258
+ return "Error: OpenCode storage directory not found.";
259
+ }
260
+
261
+ let sessionId = args.session_reference;
262
+ const projectFilter =
263
+ !args.project || args.project === "current"
264
+ ? directory
265
+ : args.project === "all"
266
+ ? undefined
267
+ : args.project;
268
+
269
+ // Handle date-based references
270
+ const parsedDate = parseDate(sessionId);
271
+ if (parsedDate) {
272
+ const sessions = await listSessions(projectFilter, sessionId, 1);
273
+ if (sessions.length === 0) {
274
+ return `Error: No sessions found for '${sessionId}' in ${projectFilter || "all projects"}`;
275
+ }
276
+ sessionId = sessions[0].id;
277
+ }
278
+ // Handle relative references
279
+ else if (sessionId.match(/^(last|previous|\d+\s*(ago|back))$/i)) {
280
+ const sessions = await listSessions(projectFilter, undefined, 50);
281
+
282
+ if (sessions.length === 0) {
283
+ return `Error: No sessions found in ${projectFilter || "all projects"}`;
284
+ }
285
+
286
+ if (
287
+ sessionId.toLowerCase() === "last" ||
288
+ sessionId.toLowerCase() === "previous"
289
+ ) {
290
+ sessionId = sessions[0].id;
291
+ } else {
292
+ const match = sessionId.match(/^(\d+)/);
293
+ const index = match ? Number.parseInt(match[1]) : 1;
294
+ if (index >= sessions.length) {
295
+ return `Error: Only ${sessions.length} sessions available, requested ${index} ago`;
296
+ }
297
+ sessionId = sessions[index].id;
298
+ }
299
+ }
300
+
301
+ // Clean session ID
302
+ sessionId = sessionId.split("/").pop() || sessionId;
303
+ if (!sessionId.startsWith("ses_")) {
304
+ return `Error: Invalid session ID format. Expected 'ses_...' but got '${sessionId}'`;
305
+ }
306
+
307
+ // Read session metadata, messages, and diffs
308
+ const messageDir = join(storageDir, "message", sessionId);
309
+ const diffFile = join(
310
+ storageDir,
311
+ "session_diff",
312
+ `${sessionId}.json`,
313
+ );
314
+
315
+ if (!existsSync(messageDir)) {
316
+ const available = await listSessions(projectFilter, undefined, 5);
317
+ const list = available.map((s) => s.id).join("\n");
318
+ return `Error: Session '${sessionId}' not found.\n\nRecent sessions:\n${list}`;
319
+ }
320
+
321
+ const metadata = await getSessionMetadata(sessionId);
322
+
323
+ // Read all message files
324
+ const messageFiles = readdirSync(messageDir).filter((f) =>
325
+ f.endsWith(".json"),
326
+ );
327
+ const messages = await Promise.all(
328
+ messageFiles.map(async (file) => {
329
+ const content = await readFile(join(messageDir, file), "utf-8");
330
+ return JSON.parse(content);
331
+ }),
332
+ );
333
+
334
+ // Read diffs if available
335
+ let diffs = [];
336
+ if (existsSync(diffFile)) {
337
+ const diffContent = await readFile(diffFile, "utf-8");
338
+ diffs = JSON.parse(diffContent);
339
+ }
340
+
341
+ // Build summary
342
+ let summary = `# Session: ${sessionId}\n\n`;
343
+
344
+ if (metadata) {
345
+ summary += `**Title:** ${metadata.title || "Untitled"}\n`;
346
+ summary += `**Project:** ${metadata.directory || "Unknown"}\n`;
347
+ if (metadata.time?.created) {
348
+ summary += `**Date:** ${new Date(metadata.time.created).toLocaleString()}\n`;
349
+ }
350
+ summary += "\n";
351
+ }
352
+
353
+ // Message analysis
354
+ const userMessages = messages.filter((m: any) => m.role === "user");
355
+ const assistantMessages = messages.filter(
356
+ (m: any) => m.role === "assistant",
357
+ );
358
+
359
+ summary += `**Messages:** ${messages.length} total (${userMessages.length} user, ${assistantMessages.length} assistant)\n`;
360
+
361
+ if (diffs.length > 0) {
362
+ const totalAdditions = diffs.reduce(
363
+ (sum: number, d: any) => sum + (d.additions || 0),
364
+ 0,
365
+ );
366
+ const totalDeletions = diffs.reduce(
367
+ (sum: number, d: any) => sum + (d.deletions || 0),
368
+ 0,
369
+ );
370
+ summary += `**File Changes:** ${diffs.length} files (+${totalAdditions}/-${totalDeletions})\n\n`;
371
+ } else {
372
+ summary += "\n";
373
+ }
374
+
375
+ // Focus filtering
376
+ if (args.focus) {
377
+ summary += `**Focus:** ${args.focus}\n\n`;
378
+
379
+ if (
380
+ args.focus.toLowerCase().includes("file") ||
381
+ args.focus.toLowerCase().includes("change") ||
382
+ args.focus.toLowerCase().includes("diff")
383
+ ) {
384
+ // Show file changes
385
+ if (diffs.length > 0) {
386
+ summary += `## File Changes\n\n`;
387
+ diffs.slice(0, 10).forEach((diff: any) => {
388
+ summary += `### ${diff.file}\n`;
389
+ summary += `+${diff.additions || 0}/-${diff.deletions || 0}\n\n`;
390
+ if (diff.before || diff.after) {
391
+ summary += `**Before:** ${(diff.before || "").substring(0, 200)}...\n`;
392
+ summary += `**After:** ${(diff.after || "").substring(0, 200)}...\n\n`;
393
+ }
394
+ });
395
+ }
396
+ } else {
397
+ // Filter messages by keyword
398
+ const relevant = messages.filter((m: any) =>
399
+ JSON.stringify(m)
400
+ .toLowerCase()
401
+ .includes(args.focus!.toLowerCase()),
402
+ );
403
+ summary += `Found ${relevant.length} relevant messages.\n\n`;
404
+ }
405
+ } else {
406
+ // Full summary
407
+ summary += `## User Messages\n\n`;
408
+ userMessages.slice(0, 5).forEach((m: any, i: number) => {
409
+ const content = m.summary?.title || m.content || "[No content]";
410
+ summary += `${i + 1}. ${content.substring(0, 200)}\n`;
411
+ });
412
+
413
+ if (diffs.length > 0) {
414
+ summary += `\n## Files Modified\n\n`;
415
+ diffs.slice(0, 10).forEach((diff: any) => {
416
+ summary += `- ${diff.file} (+${diff.additions || 0}/-${diff.deletions || 0})\n`;
417
+ });
418
+ }
419
+
420
+ // Last assistant message
421
+ if (assistantMessages.length > 0) {
422
+ const last = assistantMessages[assistantMessages.length - 1];
423
+ const lastContent =
424
+ last.summary?.body || last.content || "[No content]";
425
+ summary += `\n## Last Assistant Response\n\n${lastContent.substring(0, 500)}\n`;
426
+ }
427
+ }
428
+
429
+ return summary;
430
+ },
431
+ }),
432
+ },
433
+ };
434
+ };