sessionlog 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 (219) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +388 -0
  3. package/dist/agent/agents/claude-code.d.ts +76 -0
  4. package/dist/agent/agents/claude-code.d.ts.map +1 -0
  5. package/dist/agent/agents/claude-code.js +769 -0
  6. package/dist/agent/agents/claude-code.js.map +1 -0
  7. package/dist/agent/agents/cursor.d.ts +35 -0
  8. package/dist/agent/agents/cursor.d.ts.map +1 -0
  9. package/dist/agent/agents/cursor.js +294 -0
  10. package/dist/agent/agents/cursor.js.map +1 -0
  11. package/dist/agent/agents/gemini-cli.d.ts +62 -0
  12. package/dist/agent/agents/gemini-cli.d.ts.map +1 -0
  13. package/dist/agent/agents/gemini-cli.js +474 -0
  14. package/dist/agent/agents/gemini-cli.js.map +1 -0
  15. package/dist/agent/agents/opencode.d.ts +100 -0
  16. package/dist/agent/agents/opencode.d.ts.map +1 -0
  17. package/dist/agent/agents/opencode.js +423 -0
  18. package/dist/agent/agents/opencode.js.map +1 -0
  19. package/dist/agent/registry.d.ts +54 -0
  20. package/dist/agent/registry.d.ts.map +1 -0
  21. package/dist/agent/registry.js +123 -0
  22. package/dist/agent/registry.js.map +1 -0
  23. package/dist/agent/session-types.d.ts +45 -0
  24. package/dist/agent/session-types.d.ts.map +1 -0
  25. package/dist/agent/session-types.js +48 -0
  26. package/dist/agent/session-types.js.map +1 -0
  27. package/dist/agent/types.d.ts +126 -0
  28. package/dist/agent/types.d.ts.map +1 -0
  29. package/dist/agent/types.js +40 -0
  30. package/dist/agent/types.js.map +1 -0
  31. package/dist/cli.d.ts +12 -0
  32. package/dist/cli.d.ts.map +1 -0
  33. package/dist/cli.js +425 -0
  34. package/dist/cli.js.map +1 -0
  35. package/dist/commands/clean.d.ts +30 -0
  36. package/dist/commands/clean.d.ts.map +1 -0
  37. package/dist/commands/clean.js +98 -0
  38. package/dist/commands/clean.js.map +1 -0
  39. package/dist/commands/disable.d.ts +23 -0
  40. package/dist/commands/disable.d.ts.map +1 -0
  41. package/dist/commands/disable.js +57 -0
  42. package/dist/commands/disable.js.map +1 -0
  43. package/dist/commands/doctor.d.ts +43 -0
  44. package/dist/commands/doctor.d.ts.map +1 -0
  45. package/dist/commands/doctor.js +97 -0
  46. package/dist/commands/doctor.js.map +1 -0
  47. package/dist/commands/enable.d.ts +37 -0
  48. package/dist/commands/enable.d.ts.map +1 -0
  49. package/dist/commands/enable.js +133 -0
  50. package/dist/commands/enable.js.map +1 -0
  51. package/dist/commands/explain.d.ts +68 -0
  52. package/dist/commands/explain.d.ts.map +1 -0
  53. package/dist/commands/explain.js +182 -0
  54. package/dist/commands/explain.js.map +1 -0
  55. package/dist/commands/reset.d.ts +23 -0
  56. package/dist/commands/reset.d.ts.map +1 -0
  57. package/dist/commands/reset.js +68 -0
  58. package/dist/commands/reset.js.map +1 -0
  59. package/dist/commands/resume.d.ts +42 -0
  60. package/dist/commands/resume.d.ts.map +1 -0
  61. package/dist/commands/resume.js +133 -0
  62. package/dist/commands/resume.js.map +1 -0
  63. package/dist/commands/rewind.d.ts +34 -0
  64. package/dist/commands/rewind.d.ts.map +1 -0
  65. package/dist/commands/rewind.js +155 -0
  66. package/dist/commands/rewind.js.map +1 -0
  67. package/dist/commands/status.d.ts +51 -0
  68. package/dist/commands/status.d.ts.map +1 -0
  69. package/dist/commands/status.js +112 -0
  70. package/dist/commands/status.js.map +1 -0
  71. package/dist/config.d.ts +40 -0
  72. package/dist/config.d.ts.map +1 -0
  73. package/dist/config.js +127 -0
  74. package/dist/config.js.map +1 -0
  75. package/dist/git-operations.d.ts +191 -0
  76. package/dist/git-operations.d.ts.map +1 -0
  77. package/dist/git-operations.js +462 -0
  78. package/dist/git-operations.js.map +1 -0
  79. package/dist/hooks/git-hooks.d.ts +22 -0
  80. package/dist/hooks/git-hooks.d.ts.map +1 -0
  81. package/dist/hooks/git-hooks.js +139 -0
  82. package/dist/hooks/git-hooks.js.map +1 -0
  83. package/dist/hooks/lifecycle.d.ts +21 -0
  84. package/dist/hooks/lifecycle.d.ts.map +1 -0
  85. package/dist/hooks/lifecycle.js +179 -0
  86. package/dist/hooks/lifecycle.js.map +1 -0
  87. package/dist/index.d.ts +76 -0
  88. package/dist/index.d.ts.map +1 -0
  89. package/dist/index.js +166 -0
  90. package/dist/index.js.map +1 -0
  91. package/dist/security/redaction.d.ts +35 -0
  92. package/dist/security/redaction.d.ts.map +1 -0
  93. package/dist/security/redaction.js +239 -0
  94. package/dist/security/redaction.js.map +1 -0
  95. package/dist/session/state-machine.d.ts +90 -0
  96. package/dist/session/state-machine.d.ts.map +1 -0
  97. package/dist/session/state-machine.js +345 -0
  98. package/dist/session/state-machine.js.map +1 -0
  99. package/dist/store/checkpoint-store.d.ts +59 -0
  100. package/dist/store/checkpoint-store.d.ts.map +1 -0
  101. package/dist/store/checkpoint-store.js +321 -0
  102. package/dist/store/checkpoint-store.js.map +1 -0
  103. package/dist/store/native-store.d.ts +14 -0
  104. package/dist/store/native-store.d.ts.map +1 -0
  105. package/dist/store/native-store.js +159 -0
  106. package/dist/store/native-store.js.map +1 -0
  107. package/dist/store/provider-types.d.ts +78 -0
  108. package/dist/store/provider-types.d.ts.map +1 -0
  109. package/dist/store/provider-types.js +12 -0
  110. package/dist/store/provider-types.js.map +1 -0
  111. package/dist/store/session-store.d.ts +36 -0
  112. package/dist/store/session-store.d.ts.map +1 -0
  113. package/dist/store/session-store.js +193 -0
  114. package/dist/store/session-store.js.map +1 -0
  115. package/dist/strategy/attribution.d.ts +39 -0
  116. package/dist/strategy/attribution.d.ts.map +1 -0
  117. package/dist/strategy/attribution.js +225 -0
  118. package/dist/strategy/attribution.js.map +1 -0
  119. package/dist/strategy/common.d.ts +57 -0
  120. package/dist/strategy/common.d.ts.map +1 -0
  121. package/dist/strategy/common.js +156 -0
  122. package/dist/strategy/common.js.map +1 -0
  123. package/dist/strategy/content-overlap.d.ts +33 -0
  124. package/dist/strategy/content-overlap.d.ts.map +1 -0
  125. package/dist/strategy/content-overlap.js +176 -0
  126. package/dist/strategy/content-overlap.js.map +1 -0
  127. package/dist/strategy/manual-commit.d.ts +36 -0
  128. package/dist/strategy/manual-commit.d.ts.map +1 -0
  129. package/dist/strategy/manual-commit.js +717 -0
  130. package/dist/strategy/manual-commit.js.map +1 -0
  131. package/dist/strategy/types.d.ts +163 -0
  132. package/dist/strategy/types.d.ts.map +1 -0
  133. package/dist/strategy/types.js +48 -0
  134. package/dist/strategy/types.js.map +1 -0
  135. package/dist/summarize/claude-generator.d.ts +25 -0
  136. package/dist/summarize/claude-generator.d.ts.map +1 -0
  137. package/dist/summarize/claude-generator.js +87 -0
  138. package/dist/summarize/claude-generator.js.map +1 -0
  139. package/dist/summarize/summarize.d.ts +52 -0
  140. package/dist/summarize/summarize.d.ts.map +1 -0
  141. package/dist/summarize/summarize.js +335 -0
  142. package/dist/summarize/summarize.js.map +1 -0
  143. package/dist/types.d.ts +293 -0
  144. package/dist/types.d.ts.map +1 -0
  145. package/dist/types.js +94 -0
  146. package/dist/types.js.map +1 -0
  147. package/dist/utils/chunk-files.d.ts +25 -0
  148. package/dist/utils/chunk-files.d.ts.map +1 -0
  149. package/dist/utils/chunk-files.js +47 -0
  150. package/dist/utils/chunk-files.js.map +1 -0
  151. package/dist/utils/commit-message.d.ts +11 -0
  152. package/dist/utils/commit-message.d.ts.map +1 -0
  153. package/dist/utils/commit-message.js +54 -0
  154. package/dist/utils/commit-message.js.map +1 -0
  155. package/dist/utils/detect-agent.d.ts +19 -0
  156. package/dist/utils/detect-agent.d.ts.map +1 -0
  157. package/dist/utils/detect-agent.js +34 -0
  158. package/dist/utils/detect-agent.js.map +1 -0
  159. package/dist/utils/hook-managers.d.ts +24 -0
  160. package/dist/utils/hook-managers.d.ts.map +1 -0
  161. package/dist/utils/hook-managers.js +96 -0
  162. package/dist/utils/hook-managers.js.map +1 -0
  163. package/dist/utils/ide-tags.d.ts +12 -0
  164. package/dist/utils/ide-tags.d.ts.map +1 -0
  165. package/dist/utils/ide-tags.js +30 -0
  166. package/dist/utils/ide-tags.js.map +1 -0
  167. package/dist/utils/paths.d.ts +32 -0
  168. package/dist/utils/paths.d.ts.map +1 -0
  169. package/dist/utils/paths.js +55 -0
  170. package/dist/utils/paths.js.map +1 -0
  171. package/dist/utils/preview-rewind.d.ts +23 -0
  172. package/dist/utils/preview-rewind.d.ts.map +1 -0
  173. package/dist/utils/preview-rewind.js +63 -0
  174. package/dist/utils/preview-rewind.js.map +1 -0
  175. package/dist/utils/rewind-conflict.d.ts +52 -0
  176. package/dist/utils/rewind-conflict.d.ts.map +1 -0
  177. package/dist/utils/rewind-conflict.js +79 -0
  178. package/dist/utils/rewind-conflict.js.map +1 -0
  179. package/dist/utils/shadow-branch.d.ts +44 -0
  180. package/dist/utils/shadow-branch.d.ts.map +1 -0
  181. package/dist/utils/shadow-branch.js +93 -0
  182. package/dist/utils/shadow-branch.js.map +1 -0
  183. package/dist/utils/string-utils.d.ts +24 -0
  184. package/dist/utils/string-utils.d.ts.map +1 -0
  185. package/dist/utils/string-utils.js +47 -0
  186. package/dist/utils/string-utils.js.map +1 -0
  187. package/dist/utils/todo-extract.d.ts +52 -0
  188. package/dist/utils/todo-extract.d.ts.map +1 -0
  189. package/dist/utils/todo-extract.js +167 -0
  190. package/dist/utils/todo-extract.js.map +1 -0
  191. package/dist/utils/trailers.d.ts +36 -0
  192. package/dist/utils/trailers.d.ts.map +1 -0
  193. package/dist/utils/trailers.js +148 -0
  194. package/dist/utils/trailers.js.map +1 -0
  195. package/dist/utils/transcript-parse.d.ts +57 -0
  196. package/dist/utils/transcript-parse.d.ts.map +1 -0
  197. package/dist/utils/transcript-parse.js +126 -0
  198. package/dist/utils/transcript-parse.js.map +1 -0
  199. package/dist/utils/transcript-timestamp.d.ts +22 -0
  200. package/dist/utils/transcript-timestamp.d.ts.map +1 -0
  201. package/dist/utils/transcript-timestamp.js +56 -0
  202. package/dist/utils/transcript-timestamp.js.map +1 -0
  203. package/dist/utils/tree-ops.d.ts +47 -0
  204. package/dist/utils/tree-ops.d.ts.map +1 -0
  205. package/dist/utils/tree-ops.js +145 -0
  206. package/dist/utils/tree-ops.js.map +1 -0
  207. package/dist/utils/tty.d.ts +25 -0
  208. package/dist/utils/tty.d.ts.map +1 -0
  209. package/dist/utils/tty.js +70 -0
  210. package/dist/utils/tty.js.map +1 -0
  211. package/dist/utils/validation.d.ts +31 -0
  212. package/dist/utils/validation.d.ts.map +1 -0
  213. package/dist/utils/validation.js +59 -0
  214. package/dist/utils/validation.js.map +1 -0
  215. package/dist/utils/worktree.d.ts +16 -0
  216. package/dist/utils/worktree.d.ts.map +1 -0
  217. package/dist/utils/worktree.js +50 -0
  218. package/dist/utils/worktree.js.map +1 -0
  219. package/package.json +64 -0
@@ -0,0 +1,769 @@
1
+ /**
2
+ * Claude Code Agent
3
+ *
4
+ * Implementation of the Entire agent interface for Anthropic's Claude Code.
5
+ * Handles JSONL transcript format, Claude-specific hook installation,
6
+ * and session lifecycle management.
7
+ */
8
+ import * as fs from 'node:fs';
9
+ import * as path from 'node:path';
10
+ import * as os from 'node:os';
11
+ import * as crypto from 'node:crypto';
12
+ import { AGENT_NAMES, AGENT_TYPES, EventType, emptyTokenUsage, } from '../../types.js';
13
+ import { registerAgent } from '../registry.js';
14
+ // ============================================================================
15
+ // Constants
16
+ // ============================================================================
17
+ const CLAUDE_DIR = '.claude';
18
+ const CLAUDE_SETTINGS_FILE = '.claude/settings.json';
19
+ const HOOK_NAMES = [
20
+ 'session-start',
21
+ 'session-end',
22
+ 'stop',
23
+ 'user-prompt-submit',
24
+ 'pre-task',
25
+ 'post-task',
26
+ 'post-todo',
27
+ ];
28
+ /** Tools that modify files (detected in transcript) */
29
+ const FILE_MODIFICATION_TOOLS = new Set([
30
+ 'Write',
31
+ 'Edit',
32
+ 'NotebookEdit',
33
+ 'mcp__acp__Write',
34
+ 'mcp__acp__Edit',
35
+ ]);
36
+ /** Deny rule to prevent agents from reading metadata */
37
+ const METADATA_DENY_RULE = 'Read(./.entire/metadata/**)';
38
+ // ============================================================================
39
+ // Claude Code Agent Implementation
40
+ // ============================================================================
41
+ class ClaudeCodeAgent {
42
+ name = AGENT_NAMES.CLAUDE_CODE;
43
+ type = AGENT_TYPES.CLAUDE_CODE;
44
+ description = 'Anthropic Claude Code CLI';
45
+ isPreview = false;
46
+ protectedDirs = [CLAUDE_DIR];
47
+ async detectPresence(cwd) {
48
+ const repoRoot = cwd ?? process.cwd();
49
+ const claudeDir = path.join(repoRoot, CLAUDE_DIR);
50
+ try {
51
+ const stat = await fs.promises.stat(claudeDir);
52
+ return stat.isDirectory();
53
+ }
54
+ catch {
55
+ return false;
56
+ }
57
+ }
58
+ async getSessionDir(repoPath) {
59
+ // Claude Code stores sessions in ~/.claude/projects/<sanitized-path>/
60
+ const sanitized = sanitizePathForClaude(repoPath);
61
+ return path.join(os.homedir(), '.claude', 'projects', sanitized);
62
+ }
63
+ getSessionID(input) {
64
+ return input.sessionID;
65
+ }
66
+ resolveSessionFile(sessionDir, agentSessionID) {
67
+ return path.join(sessionDir, `${agentSessionID}.jsonl`);
68
+ }
69
+ async readTranscript(sessionRef) {
70
+ return fs.promises.readFile(sessionRef);
71
+ }
72
+ formatResumeCommand(sessionID) {
73
+ return `claude --resume ${sessionID}`;
74
+ }
75
+ // ===========================================================================
76
+ // HookSupport
77
+ // ===========================================================================
78
+ hookNames() {
79
+ return [...HOOK_NAMES];
80
+ }
81
+ parseHookEvent(hookName, stdin) {
82
+ try {
83
+ const data = JSON.parse(stdin);
84
+ switch (hookName) {
85
+ case 'session-start':
86
+ return {
87
+ type: EventType.SessionStart,
88
+ sessionID: String(data.session_id ?? data.sessionID ?? ''),
89
+ sessionRef: String(data.transcript_path ?? data.transcriptPath ?? ''),
90
+ timestamp: new Date(),
91
+ };
92
+ case 'user-prompt-submit':
93
+ return {
94
+ type: EventType.TurnStart,
95
+ sessionID: String(data.session_id ?? data.sessionID ?? ''),
96
+ sessionRef: String(data.transcript_path ?? data.transcriptPath ?? ''),
97
+ prompt: String(data.prompt ?? ''),
98
+ timestamp: new Date(),
99
+ };
100
+ case 'stop':
101
+ return {
102
+ type: EventType.TurnEnd,
103
+ sessionID: String(data.session_id ?? data.sessionID ?? ''),
104
+ sessionRef: String(data.transcript_path ?? data.transcriptPath ?? ''),
105
+ timestamp: new Date(),
106
+ };
107
+ case 'session-end':
108
+ return {
109
+ type: EventType.SessionEnd,
110
+ sessionID: String(data.session_id ?? data.sessionID ?? ''),
111
+ sessionRef: String(data.transcript_path ?? data.transcriptPath ?? ''),
112
+ timestamp: new Date(),
113
+ };
114
+ case 'pre-task':
115
+ return {
116
+ type: EventType.SubagentStart,
117
+ sessionID: String(data.session_id ?? data.sessionID ?? ''),
118
+ sessionRef: String(data.transcript_path ?? data.transcriptPath ?? ''),
119
+ toolUseID: String(data.tool_use_id ?? data.toolUseID ?? ''),
120
+ toolInput: data.tool_input ?? data.toolInput,
121
+ timestamp: new Date(),
122
+ };
123
+ case 'post-task':
124
+ return {
125
+ type: EventType.SubagentEnd,
126
+ sessionID: String(data.session_id ?? data.sessionID ?? ''),
127
+ sessionRef: String(data.transcript_path ?? data.transcriptPath ?? ''),
128
+ toolUseID: String(data.tool_use_id ?? data.toolUseID ?? ''),
129
+ subagentID: data.tool_response?.agentId,
130
+ timestamp: new Date(),
131
+ };
132
+ case 'post-todo':
133
+ return {
134
+ type: EventType.Compaction,
135
+ sessionID: String(data.session_id ?? data.sessionID ?? ''),
136
+ sessionRef: String(data.transcript_path ?? data.transcriptPath ?? ''),
137
+ timestamp: new Date(),
138
+ };
139
+ default:
140
+ return null;
141
+ }
142
+ }
143
+ catch {
144
+ return null;
145
+ }
146
+ }
147
+ async installHooks(repoPath, force = false) {
148
+ const settingsPath = path.join(repoPath, CLAUDE_SETTINGS_FILE);
149
+ let settings = {};
150
+ // Read existing settings
151
+ try {
152
+ const content = await fs.promises.readFile(settingsPath, 'utf-8');
153
+ settings = JSON.parse(content);
154
+ }
155
+ catch {
156
+ // No existing settings
157
+ }
158
+ if (!settings.hooks)
159
+ settings.hooks = {};
160
+ let installed = 0;
161
+ // Install lifecycle hooks
162
+ const hookMappings = [
163
+ { settingsKey: 'SessionStart', hookName: 'session-start' },
164
+ { settingsKey: 'SessionEnd', hookName: 'session-end' },
165
+ { settingsKey: 'UserPromptSubmit', hookName: 'user-prompt-submit' },
166
+ { settingsKey: 'Stop', hookName: 'stop' },
167
+ ];
168
+ // Task hooks (pre/post tool use for Task tool)
169
+ const taskHookMappings = [
170
+ { settingsKey: 'PreToolUse', hookName: 'pre-task', matcher: 'Task' },
171
+ { settingsKey: 'PostToolUse', hookName: 'post-task', matcher: 'Task' },
172
+ { settingsKey: 'PostToolUse', hookName: 'post-todo', matcher: 'TodoWrite' },
173
+ ];
174
+ for (const { settingsKey, hookName } of hookMappings) {
175
+ const existing = settings.hooks[settingsKey] ?? [];
176
+ if (force) {
177
+ // Remove existing entire hooks
178
+ const filtered = existing.filter((m) => !m.hooks.some((h) => h.command.includes('entire ')));
179
+ settings.hooks[settingsKey] = filtered;
180
+ }
181
+ // Check if already installed
182
+ const hasEntireHook = (settings.hooks[settingsKey] ?? []).some((m) => m.hooks.some((h) => h.command.includes('entire ')));
183
+ if (!hasEntireHook) {
184
+ const matchers = settings.hooks[settingsKey] ?? [];
185
+ matchers.push({
186
+ matcher: '',
187
+ hooks: [
188
+ {
189
+ type: 'command',
190
+ command: `entire hooks claude-code ${hookName}`,
191
+ },
192
+ ],
193
+ });
194
+ settings.hooks[settingsKey] = matchers;
195
+ installed++;
196
+ }
197
+ }
198
+ for (const { settingsKey, hookName, matcher } of taskHookMappings) {
199
+ const existing = settings.hooks[settingsKey] ?? [];
200
+ if (force) {
201
+ const filtered = existing.filter((m) => !(m.matcher === matcher && m.hooks.some((h) => h.command.includes('entire '))));
202
+ settings.hooks[settingsKey] = filtered;
203
+ }
204
+ const hasEntireHook = (settings.hooks[settingsKey] ?? []).some((m) => m.matcher === matcher && m.hooks.some((h) => h.command.includes('entire ')));
205
+ if (!hasEntireHook) {
206
+ const matchers = settings.hooks[settingsKey] ?? [];
207
+ matchers.push({
208
+ matcher,
209
+ hooks: [
210
+ {
211
+ type: 'command',
212
+ command: `entire hooks claude-code ${hookName}`,
213
+ },
214
+ ],
215
+ });
216
+ settings.hooks[settingsKey] = matchers;
217
+ installed++;
218
+ }
219
+ }
220
+ // Add metadata deny rule
221
+ if (!settings.permissions)
222
+ settings.permissions = {};
223
+ if (!settings.permissions.deny)
224
+ settings.permissions.deny = [];
225
+ if (!settings.permissions.deny.includes(METADATA_DENY_RULE)) {
226
+ settings.permissions.deny.push(METADATA_DENY_RULE);
227
+ }
228
+ // Write settings
229
+ const dir = path.dirname(settingsPath);
230
+ await fs.promises.mkdir(dir, { recursive: true });
231
+ await fs.promises.writeFile(settingsPath, JSON.stringify(settings, null, 2));
232
+ return installed;
233
+ }
234
+ async uninstallHooks(repoPath) {
235
+ const settingsPath = path.join(repoPath, CLAUDE_SETTINGS_FILE);
236
+ try {
237
+ const content = await fs.promises.readFile(settingsPath, 'utf-8');
238
+ const settings = JSON.parse(content);
239
+ if (settings.hooks) {
240
+ for (const key of Object.keys(settings.hooks)) {
241
+ const matchers = settings.hooks[key];
242
+ if (!matchers)
243
+ continue;
244
+ settings.hooks[key] = matchers.filter((m) => !m.hooks.some((h) => h.command.includes('entire ')));
245
+ if (settings.hooks[key].length === 0) {
246
+ delete settings.hooks[key];
247
+ }
248
+ }
249
+ if (Object.keys(settings.hooks).length === 0) {
250
+ delete settings.hooks;
251
+ }
252
+ }
253
+ // Remove deny rule
254
+ if (settings.permissions?.deny) {
255
+ settings.permissions.deny = settings.permissions.deny.filter((d) => d !== METADATA_DENY_RULE);
256
+ if (settings.permissions.deny.length === 0) {
257
+ delete settings.permissions.deny;
258
+ }
259
+ if (Object.keys(settings.permissions).length === 0) {
260
+ delete settings.permissions;
261
+ }
262
+ }
263
+ await fs.promises.writeFile(settingsPath, JSON.stringify(settings, null, 2));
264
+ }
265
+ catch {
266
+ // No settings to modify
267
+ }
268
+ }
269
+ async areHooksInstalled(repoPath) {
270
+ const settingsPath = path.join(repoPath, CLAUDE_SETTINGS_FILE);
271
+ try {
272
+ const content = await fs.promises.readFile(settingsPath, 'utf-8');
273
+ const settings = JSON.parse(content);
274
+ if (!settings.hooks)
275
+ return false;
276
+ // Check for at least the session-start hook
277
+ const sessionStart = settings.hooks.SessionStart ?? [];
278
+ return sessionStart.some((m) => m.hooks.some((h) => h.command.includes('entire ')));
279
+ }
280
+ catch {
281
+ return false;
282
+ }
283
+ }
284
+ // ===========================================================================
285
+ // TranscriptPreparer — wait for Claude Code's async transcript flush
286
+ // ===========================================================================
287
+ async prepareTranscript(sessionRef) {
288
+ await waitForTranscriptFlush(sessionRef);
289
+ }
290
+ // ===========================================================================
291
+ // TranscriptAnalyzer
292
+ // ===========================================================================
293
+ async getTranscriptPosition(transcriptPath) {
294
+ try {
295
+ const content = await fs.promises.readFile(transcriptPath, 'utf-8');
296
+ const lines = content.split('\n').filter(Boolean);
297
+ return lines.length;
298
+ }
299
+ catch {
300
+ return 0;
301
+ }
302
+ }
303
+ async extractModifiedFilesFromOffset(transcriptPath, startOffset) {
304
+ const content = await fs.promises.readFile(transcriptPath, 'utf-8');
305
+ const lines = content.split('\n').filter(Boolean);
306
+ const files = new Set();
307
+ for (let i = startOffset; i < lines.length; i++) {
308
+ try {
309
+ const line = JSON.parse(lines[i]);
310
+ if (line.type === 'assistant') {
311
+ const contentBlocks = extractContentBlocks(line.message);
312
+ for (const block of contentBlocks) {
313
+ if (block.type === 'tool_use' &&
314
+ block.name &&
315
+ FILE_MODIFICATION_TOOLS.has(block.name)) {
316
+ const filePath = block.input?.file_path;
317
+ if (filePath)
318
+ files.add(filePath);
319
+ }
320
+ }
321
+ }
322
+ }
323
+ catch {
324
+ // Skip malformed lines
325
+ }
326
+ }
327
+ return { files: Array.from(files), currentPosition: lines.length };
328
+ }
329
+ async extractPrompts(sessionRef, fromOffset) {
330
+ const content = await fs.promises.readFile(sessionRef, 'utf-8');
331
+ const lines = content.split('\n').filter(Boolean);
332
+ const prompts = [];
333
+ for (let i = fromOffset; i < lines.length; i++) {
334
+ try {
335
+ const line = JSON.parse(lines[i]);
336
+ if (line.type === 'user') {
337
+ const text = extractUserText(line.message);
338
+ if (text)
339
+ prompts.push(text);
340
+ }
341
+ }
342
+ catch {
343
+ // Skip malformed lines
344
+ }
345
+ }
346
+ return prompts;
347
+ }
348
+ async extractSummary(sessionRef) {
349
+ const prompts = await this.extractPrompts(sessionRef, 0);
350
+ if (prompts.length === 0)
351
+ return '';
352
+ // Return the first prompt as a summary, truncated
353
+ return prompts[0].slice(0, 200);
354
+ }
355
+ // ===========================================================================
356
+ // TokenCalculator
357
+ // ===========================================================================
358
+ async calculateTokenUsage(transcriptData, fromOffset) {
359
+ const content = transcriptData.toString('utf-8');
360
+ const lines = content.split('\n').filter(Boolean);
361
+ // Deduplicate by message ID — streaming may produce multiple rows per message.
362
+ // Keep the entry with the highest output_tokens (final streaming state).
363
+ const usageByMessageID = new Map();
364
+ for (let i = fromOffset; i < lines.length; i++) {
365
+ try {
366
+ const raw = JSON.parse(lines[i]);
367
+ if (raw.type === 'assistant') {
368
+ const msg = raw.message;
369
+ const msgID = msg?.id;
370
+ const msgUsage = msg?.usage;
371
+ if (msgUsage && msgID) {
372
+ const existing = usageByMessageID.get(msgID);
373
+ if (!existing || (msgUsage.output_tokens ?? 0) > (existing.output_tokens ?? 0)) {
374
+ usageByMessageID.set(msgID, msgUsage);
375
+ }
376
+ }
377
+ else if (msgUsage) {
378
+ // No message ID — count each occurrence
379
+ usageByMessageID.set(`_anon_${i}`, msgUsage);
380
+ }
381
+ }
382
+ }
383
+ catch {
384
+ // Skip malformed lines
385
+ }
386
+ }
387
+ const usage = emptyTokenUsage();
388
+ usage.apiCallCount = usageByMessageID.size;
389
+ for (const u of usageByMessageID.values()) {
390
+ usage.inputTokens += u.input_tokens ?? 0;
391
+ usage.cacheCreationTokens += u.cache_creation_input_tokens ?? 0;
392
+ usage.cacheReadTokens += u.cache_read_input_tokens ?? 0;
393
+ usage.outputTokens += u.output_tokens ?? 0;
394
+ }
395
+ return usage;
396
+ }
397
+ // ===========================================================================
398
+ // SubagentAwareExtractor
399
+ // ===========================================================================
400
+ async extractAllModifiedFiles(transcriptData, fromOffset, subagentsDir) {
401
+ if (transcriptData.length === 0)
402
+ return [];
403
+ const content = transcriptData.toString('utf-8');
404
+ const allLines = content.split('\n').filter(Boolean);
405
+ const sliced = allLines.slice(fromOffset);
406
+ const parsed = sliced
407
+ .map((line) => {
408
+ try {
409
+ return JSON.parse(line);
410
+ }
411
+ catch {
412
+ return null;
413
+ }
414
+ })
415
+ .filter((l) => l !== null);
416
+ // Collect modified files from main agent
417
+ const fileSet = new Set();
418
+ for (const f of extractModifiedFiles(parsed)) {
419
+ fileSet.add(f);
420
+ }
421
+ // Find spawned subagents and collect their modified files
422
+ const agentIDs = extractSpawnedAgentIDs(parsed);
423
+ if (subagentsDir) {
424
+ for (const agentID of agentIDs.keys()) {
425
+ const agentPath = path.join(subagentsDir, `agent-${agentID}.jsonl`);
426
+ try {
427
+ const agentContent = await fs.promises.readFile(agentPath, 'utf-8');
428
+ const agentLines = agentContent
429
+ .split('\n')
430
+ .filter(Boolean)
431
+ .map((line) => {
432
+ try {
433
+ return JSON.parse(line);
434
+ }
435
+ catch {
436
+ return null;
437
+ }
438
+ })
439
+ .filter((l) => l !== null);
440
+ for (const f of extractModifiedFiles(agentLines)) {
441
+ fileSet.add(f);
442
+ }
443
+ }
444
+ catch {
445
+ // Subagent transcript may not exist yet
446
+ }
447
+ }
448
+ }
449
+ return Array.from(fileSet);
450
+ }
451
+ async calculateTotalTokenUsage(transcriptData, fromOffset, subagentsDir) {
452
+ if (transcriptData.length === 0)
453
+ return emptyTokenUsage();
454
+ // Calculate main session token usage
455
+ const mainUsage = await this.calculateTokenUsage(transcriptData, fromOffset);
456
+ // Extract spawned agent IDs from the transcript
457
+ const content = transcriptData.toString('utf-8');
458
+ const allLines = content.split('\n').filter(Boolean);
459
+ const sliced = allLines.slice(fromOffset);
460
+ const parsed = sliced
461
+ .map((line) => {
462
+ try {
463
+ return JSON.parse(line);
464
+ }
465
+ catch {
466
+ return null;
467
+ }
468
+ })
469
+ .filter((l) => l !== null);
470
+ const agentIDs = extractSpawnedAgentIDs(parsed);
471
+ // Calculate subagent token usage
472
+ if (agentIDs.size > 0 && subagentsDir) {
473
+ const subagentUsage = emptyTokenUsage();
474
+ let hasSubagentUsage = false;
475
+ for (const agentID of agentIDs.keys()) {
476
+ const agentPath = path.join(subagentsDir, `agent-${agentID}.jsonl`);
477
+ try {
478
+ const agentData = await fs.promises.readFile(agentPath);
479
+ const agentUsage = await this.calculateTokenUsage(agentData, 0);
480
+ subagentUsage.inputTokens += agentUsage.inputTokens;
481
+ subagentUsage.cacheCreationTokens += agentUsage.cacheCreationTokens;
482
+ subagentUsage.cacheReadTokens += agentUsage.cacheReadTokens;
483
+ subagentUsage.outputTokens += agentUsage.outputTokens;
484
+ subagentUsage.apiCallCount += agentUsage.apiCallCount;
485
+ hasSubagentUsage = true;
486
+ }
487
+ catch {
488
+ // Agent transcript may not exist yet
489
+ }
490
+ }
491
+ if (hasSubagentUsage && subagentUsage.apiCallCount > 0) {
492
+ mainUsage.subagentTokens = subagentUsage;
493
+ }
494
+ }
495
+ return mainUsage;
496
+ }
497
+ // ===========================================================================
498
+ // TranscriptChunker
499
+ // ===========================================================================
500
+ async chunkTranscript(content, maxSize) {
501
+ return chunkJSONL(content, maxSize);
502
+ }
503
+ async reassembleTranscript(chunks) {
504
+ return Buffer.concat(chunks);
505
+ }
506
+ }
507
+ // ============================================================================
508
+ // Transcript Parsing Helpers
509
+ // ============================================================================
510
+ function extractContentBlocks(message) {
511
+ if (!message || typeof message !== 'object')
512
+ return [];
513
+ const msg = message;
514
+ const content = msg.content;
515
+ if (Array.isArray(content)) {
516
+ return content;
517
+ }
518
+ return [];
519
+ }
520
+ function extractUserText(message) {
521
+ if (typeof message === 'string')
522
+ return message;
523
+ if (!message || typeof message !== 'object')
524
+ return '';
525
+ const msg = message;
526
+ // Content can be string or array of blocks
527
+ if (typeof msg.content === 'string')
528
+ return msg.content;
529
+ if (Array.isArray(msg.content)) {
530
+ return msg.content
531
+ .filter((b) => b.type === 'text' && b.text)
532
+ .map((b) => b.text)
533
+ .join('\n');
534
+ }
535
+ return '';
536
+ }
537
+ /**
538
+ * Parse a JSONL transcript into structured lines
539
+ */
540
+ export function parseTranscript(content) {
541
+ const lines = [];
542
+ for (const rawLine of content.split('\n').filter(Boolean)) {
543
+ try {
544
+ const parsed = JSON.parse(rawLine);
545
+ if (parsed.type === 'user' || parsed.type === 'assistant') {
546
+ lines.push(parsed);
547
+ }
548
+ }
549
+ catch {
550
+ // Skip malformed lines
551
+ }
552
+ }
553
+ return lines;
554
+ }
555
+ /**
556
+ * Extract all modified files from transcript lines
557
+ */
558
+ export function extractModifiedFiles(lines) {
559
+ const files = new Set();
560
+ for (const line of lines) {
561
+ if (line.type !== 'assistant')
562
+ continue;
563
+ const blocks = extractContentBlocks(line.message);
564
+ for (const block of blocks) {
565
+ if (block.type === 'tool_use' && block.name && FILE_MODIFICATION_TOOLS.has(block.name)) {
566
+ const input = block.input;
567
+ const filePath = (input?.file_path ?? input?.notebook_path);
568
+ if (filePath)
569
+ files.add(filePath);
570
+ }
571
+ }
572
+ }
573
+ return Array.from(files);
574
+ }
575
+ /**
576
+ * Extract the last user prompt from transcript lines
577
+ */
578
+ export function extractLastUserPrompt(lines) {
579
+ for (let i = lines.length - 1; i >= 0; i--) {
580
+ if (lines[i].type === 'user') {
581
+ return extractUserText(lines[i].message);
582
+ }
583
+ }
584
+ return '';
585
+ }
586
+ // ============================================================================
587
+ // Subagent ID Extraction
588
+ // ============================================================================
589
+ /**
590
+ * Extract spawned agent IDs from Task tool results in a transcript.
591
+ * When a Task tool completes, the tool_result contains "agentId: <id>".
592
+ * Returns a map of agentID → toolUseID.
593
+ */
594
+ export function extractSpawnedAgentIDs(lines) {
595
+ const agentIDs = new Map();
596
+ for (const line of lines) {
597
+ if (line.type !== 'user')
598
+ continue;
599
+ const msg = line.message;
600
+ if (!msg)
601
+ continue;
602
+ const content = msg.content;
603
+ if (!Array.isArray(content))
604
+ continue;
605
+ for (const block of content) {
606
+ if (block.type !== 'tool_result')
607
+ continue;
608
+ const toolUseID = String(block.tool_use_id ?? '');
609
+ let textContent = '';
610
+ // Content can be a string or array of text blocks
611
+ if (typeof block.content === 'string') {
612
+ textContent = block.content;
613
+ }
614
+ else if (Array.isArray(block.content)) {
615
+ for (const tb of block.content) {
616
+ if (tb.type === 'text' && typeof tb.text === 'string') {
617
+ textContent += tb.text + '\n';
618
+ }
619
+ }
620
+ }
621
+ const agentID = extractAgentIDFromText(textContent);
622
+ if (agentID) {
623
+ agentIDs.set(agentID, toolUseID);
624
+ }
625
+ }
626
+ }
627
+ return agentIDs;
628
+ }
629
+ /**
630
+ * Extract an agent ID from text containing "agentId: <id>".
631
+ */
632
+ function extractAgentIDFromText(text) {
633
+ const prefix = 'agentId: ';
634
+ const idx = text.indexOf(prefix);
635
+ if (idx === -1)
636
+ return '';
637
+ const start = idx + prefix.length;
638
+ let end = start;
639
+ while (end < text.length && /[a-zA-Z0-9]/.test(text[end])) {
640
+ end++;
641
+ }
642
+ return end > start ? text.slice(start, end) : '';
643
+ }
644
+ // ============================================================================
645
+ // JSONL Chunking
646
+ // ============================================================================
647
+ function chunkJSONL(content, maxSize) {
648
+ if (content.length <= maxSize)
649
+ return [content];
650
+ const str = content.toString('utf-8');
651
+ const lines = str.split('\n');
652
+ const chunks = [];
653
+ let current = [];
654
+ let currentSize = 0;
655
+ for (const line of lines) {
656
+ const lineSize = Buffer.byteLength(line + '\n');
657
+ if (currentSize + lineSize > maxSize && current.length > 0) {
658
+ chunks.push(Buffer.from(current.join('\n') + '\n'));
659
+ current = [];
660
+ currentSize = 0;
661
+ }
662
+ current.push(line);
663
+ currentSize += lineSize;
664
+ }
665
+ if (current.length > 0) {
666
+ const remaining = current.join('\n');
667
+ if (remaining.trim()) {
668
+ chunks.push(Buffer.from(remaining + '\n'));
669
+ }
670
+ }
671
+ return chunks;
672
+ }
673
+ // ============================================================================
674
+ // Transcript Flush Sentinel
675
+ // ============================================================================
676
+ /**
677
+ * String that appears in Claude Code's hook_progress entry when the stop hook
678
+ * has been invoked, indicating the transcript is fully flushed.
679
+ */
680
+ const STOP_HOOK_SENTINEL = 'hooks claude-code stop';
681
+ const FLUSH_MAX_WAIT_MS = 3000;
682
+ const FLUSH_POLL_INTERVAL_MS = 50;
683
+ const FLUSH_TAIL_BYTES = 4096;
684
+ const FLUSH_MAX_SKEW_MS = 2000;
685
+ /**
686
+ * Poll the transcript file for the stop hook sentinel.
687
+ * Falls back silently after a timeout.
688
+ */
689
+ async function waitForTranscriptFlush(transcriptPath) {
690
+ const hookStartTime = Date.now();
691
+ const deadline = hookStartTime + FLUSH_MAX_WAIT_MS;
692
+ while (Date.now() < deadline) {
693
+ if (checkStopSentinel(transcriptPath, hookStartTime)) {
694
+ return;
695
+ }
696
+ await sleep(FLUSH_POLL_INTERVAL_MS);
697
+ }
698
+ // Timeout — proceed anyway
699
+ }
700
+ /**
701
+ * Read the tail of the transcript file and look for the stop hook sentinel
702
+ * with a timestamp within the acceptable skew window.
703
+ */
704
+ function checkStopSentinel(filePath, hookStartTime) {
705
+ let fd;
706
+ try {
707
+ fd = fs.openSync(filePath, 'r');
708
+ }
709
+ catch {
710
+ return false;
711
+ }
712
+ try {
713
+ const stat = fs.fstatSync(fd);
714
+ const offset = Math.max(0, stat.size - FLUSH_TAIL_BYTES);
715
+ const buf = Buffer.alloc(stat.size - offset);
716
+ fs.readSync(fd, buf, 0, buf.length, offset);
717
+ const lines = buf.toString('utf-8').split('\n');
718
+ for (const line of lines) {
719
+ const trimmed = line.trim();
720
+ if (!trimmed || !trimmed.includes(STOP_HOOK_SENTINEL))
721
+ continue;
722
+ try {
723
+ const entry = JSON.parse(trimmed);
724
+ if (!entry.timestamp)
725
+ continue;
726
+ const ts = new Date(entry.timestamp).getTime();
727
+ if (isNaN(ts))
728
+ continue;
729
+ const lowerBound = hookStartTime - FLUSH_MAX_SKEW_MS;
730
+ const upperBound = hookStartTime + FLUSH_MAX_SKEW_MS;
731
+ if (ts > lowerBound && ts < upperBound) {
732
+ return true;
733
+ }
734
+ }
735
+ catch {
736
+ continue;
737
+ }
738
+ }
739
+ return false;
740
+ }
741
+ finally {
742
+ fs.closeSync(fd);
743
+ }
744
+ }
745
+ function sleep(ms) {
746
+ return new Promise((resolve) => setTimeout(resolve, ms));
747
+ }
748
+ // ============================================================================
749
+ // Path Helpers
750
+ // ============================================================================
751
+ /**
752
+ * Sanitize a filesystem path for use as a Claude project directory name
753
+ */
754
+ function sanitizePathForClaude(repoPath) {
755
+ // Claude uses a hash-based directory naming scheme
756
+ return crypto.createHash('sha256').update(repoPath).digest('hex').slice(0, 16);
757
+ }
758
+ // ============================================================================
759
+ // Registration
760
+ // ============================================================================
761
+ /**
762
+ * Create and return a new Claude Code agent instance
763
+ */
764
+ export function createClaudeCodeAgent() {
765
+ return new ClaudeCodeAgent();
766
+ }
767
+ // Auto-register when imported
768
+ registerAgent(AGENT_NAMES.CLAUDE_CODE, () => new ClaudeCodeAgent());
769
+ //# sourceMappingURL=claude-code.js.map