cognitive-core 0.2.1 → 0.2.2

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 (331) hide show
  1. package/dist/atlas.d.ts +10 -0
  2. package/dist/atlas.d.ts.map +1 -1
  3. package/dist/atlas.js +65 -0
  4. package/dist/atlas.js.map +1 -1
  5. package/dist/learning/pipeline.d.ts +4 -31
  6. package/dist/learning/pipeline.d.ts.map +1 -1
  7. package/dist/learning/pipeline.js +12 -64
  8. package/dist/learning/pipeline.js.map +1 -1
  9. package/dist/memory/curated-loader.d.ts +21 -4
  10. package/dist/memory/curated-loader.d.ts.map +1 -1
  11. package/dist/memory/curated-loader.js +53 -16
  12. package/dist/memory/curated-loader.js.map +1 -1
  13. package/dist/memory/index.d.ts +2 -1
  14. package/dist/memory/index.d.ts.map +1 -1
  15. package/dist/memory/index.js +3 -1
  16. package/dist/memory/index.js.map +1 -1
  17. package/dist/memory/playbook.d.ts +6 -0
  18. package/dist/memory/playbook.d.ts.map +1 -1
  19. package/dist/memory/playbook.js +15 -0
  20. package/dist/memory/playbook.js.map +1 -1
  21. package/dist/memory/source-resolver.d.ts +120 -0
  22. package/dist/memory/source-resolver.d.ts.map +1 -0
  23. package/dist/memory/source-resolver.js +300 -0
  24. package/dist/memory/source-resolver.js.map +1 -0
  25. package/dist/types/config.d.ts +141 -0
  26. package/dist/types/config.d.ts.map +1 -1
  27. package/dist/types/config.js +40 -0
  28. package/dist/types/config.js.map +1 -1
  29. package/dist/types/index.d.ts +1 -1
  30. package/dist/types/index.d.ts.map +1 -1
  31. package/dist/types/index.js +1 -1
  32. package/dist/types/index.js.map +1 -1
  33. package/dist/workspace/types.d.ts +12 -54
  34. package/dist/workspace/types.d.ts.map +1 -1
  35. package/dist/workspace/types.js.map +1 -1
  36. package/package.json +2 -2
  37. package/playbooks/compound-engineering/adversarial-review.json +51 -0
  38. package/playbooks/compound-engineering/agent-native-architecture.json +59 -0
  39. package/playbooks/compound-engineering/agent-native-review.json +54 -0
  40. package/playbooks/compound-engineering/api-contract-review.json +52 -0
  41. package/playbooks/compound-engineering/brainstorm-requirements.json +55 -0
  42. package/playbooks/compound-engineering/bug-reproduction.json +62 -0
  43. package/playbooks/compound-engineering/confidence-calibration.json +49 -0
  44. package/playbooks/compound-engineering/correctness-review.json +49 -0
  45. package/playbooks/compound-engineering/data-migration-safety.json +59 -0
  46. package/playbooks/compound-engineering/deployment-verification.json +63 -0
  47. package/playbooks/compound-engineering/error-recovery-patterns.json +53 -0
  48. package/playbooks/compound-engineering/implementation-planning.json +64 -0
  49. package/playbooks/compound-engineering/issue-pattern-analysis.json +53 -0
  50. package/playbooks/compound-engineering/knowledge-compounding.json +63 -0
  51. package/playbooks/compound-engineering/learnings-research.json +54 -0
  52. package/playbooks/compound-engineering/maintainability-review.json +49 -0
  53. package/playbooks/compound-engineering/performance-review.json +54 -0
  54. package/playbooks/compound-engineering/plan-adversarial-review.json +56 -0
  55. package/playbooks/compound-engineering/plan-feasibility-review.json +56 -0
  56. package/playbooks/compound-engineering/project-standards-review.json +52 -0
  57. package/playbooks/compound-engineering/reliability-review.json +53 -0
  58. package/playbooks/compound-engineering/review-orchestration.json +64 -0
  59. package/playbooks/compound-engineering/security-review.json +54 -0
  60. package/playbooks/compound-engineering/systematic-execution.json +64 -0
  61. package/playbooks/compound-engineering/testing-review.json +50 -0
  62. package/src/atlas.ts +96 -0
  63. package/src/memory/curated-loader.ts +69 -16
  64. package/src/memory/index.ts +16 -0
  65. package/src/memory/playbook.ts +19 -0
  66. package/src/memory/source-resolver.ts +422 -0
  67. package/src/types/config.ts +46 -0
  68. package/src/types/index.ts +4 -0
  69. package/src/workspace/types.ts +22 -78
  70. package/tests/integration/curated-sources-e2e.test.ts +502 -0
  71. package/tests/memory/compound-engineering-seed.test.ts +338 -0
  72. package/tests/memory/curated-loader-extended.test.ts +225 -0
  73. package/tests/memory/playbook-quality-validation.test.ts +430 -0
  74. package/tests/memory/source-resolver.test.ts +700 -0
  75. package/.claude/settings.local.json +0 -11
  76. package/dist/learning/llm-extractor.d.ts +0 -88
  77. package/dist/learning/llm-extractor.d.ts.map +0 -1
  78. package/dist/learning/llm-extractor.js +0 -372
  79. package/dist/learning/llm-extractor.js.map +0 -1
  80. package/dist/learning/loop-coordinator.d.ts +0 -61
  81. package/dist/learning/loop-coordinator.d.ts.map +0 -1
  82. package/dist/learning/loop-coordinator.js +0 -96
  83. package/dist/learning/loop-coordinator.js.map +0 -1
  84. package/references/agent-workspace/CLAUDE.md +0 -74
  85. package/references/agent-workspace/README.md +0 -587
  86. package/references/agent-workspace/media/banner.png +0 -0
  87. package/references/agent-workspace/package-lock.json +0 -2061
  88. package/references/agent-workspace/package.json +0 -54
  89. package/references/agent-workspace/src/handle.ts +0 -122
  90. package/references/agent-workspace/src/index.ts +0 -32
  91. package/references/agent-workspace/src/manager.ts +0 -102
  92. package/references/agent-workspace/src/readers/json.ts +0 -71
  93. package/references/agent-workspace/src/readers/markdown.ts +0 -37
  94. package/references/agent-workspace/src/readers/raw.ts +0 -27
  95. package/references/agent-workspace/src/types.ts +0 -68
  96. package/references/agent-workspace/src/validation.ts +0 -93
  97. package/references/agent-workspace/src/writers/json.ts +0 -17
  98. package/references/agent-workspace/src/writers/markdown.ts +0 -27
  99. package/references/agent-workspace/src/writers/raw.ts +0 -22
  100. package/references/agent-workspace/tests/errors.test.ts +0 -652
  101. package/references/agent-workspace/tests/handle.test.ts +0 -144
  102. package/references/agent-workspace/tests/manager.test.ts +0 -124
  103. package/references/agent-workspace/tests/readers.test.ts +0 -205
  104. package/references/agent-workspace/tests/validation.test.ts +0 -196
  105. package/references/agent-workspace/tests/writers.test.ts +0 -108
  106. package/references/agent-workspace/tsconfig.json +0 -20
  107. package/references/agent-workspace/tsup.config.ts +0 -9
  108. package/references/minimem/.claude/settings.json +0 -7
  109. package/references/minimem/.sudocode/issues.jsonl +0 -18
  110. package/references/minimem/.sudocode/specs.jsonl +0 -1
  111. package/references/minimem/CLAUDE.md +0 -310
  112. package/references/minimem/README.md +0 -556
  113. package/references/minimem/claude-plugin/.claude-plugin/plugin.json +0 -10
  114. package/references/minimem/claude-plugin/.mcp.json +0 -7
  115. package/references/minimem/claude-plugin/README.md +0 -158
  116. package/references/minimem/claude-plugin/commands/recall.md +0 -47
  117. package/references/minimem/claude-plugin/commands/remember.md +0 -41
  118. package/references/minimem/claude-plugin/hooks/__tests__/hooks.test.ts +0 -272
  119. package/references/minimem/claude-plugin/hooks/hooks.json +0 -27
  120. package/references/minimem/claude-plugin/hooks/session-end.sh +0 -86
  121. package/references/minimem/claude-plugin/hooks/session-start.sh +0 -85
  122. package/references/minimem/claude-plugin/skills/memory/SKILL.md +0 -108
  123. package/references/minimem/package-lock.json +0 -5373
  124. package/references/minimem/package.json +0 -60
  125. package/references/minimem/scripts/postbuild.js +0 -35
  126. package/references/minimem/src/__tests__/edge-cases.test.ts +0 -371
  127. package/references/minimem/src/__tests__/errors.test.ts +0 -265
  128. package/references/minimem/src/__tests__/helpers.ts +0 -199
  129. package/references/minimem/src/__tests__/internal.test.ts +0 -407
  130. package/references/minimem/src/__tests__/knowledge.test.ts +0 -287
  131. package/references/minimem/src/__tests__/minimem.integration.test.ts +0 -1127
  132. package/references/minimem/src/__tests__/session.test.ts +0 -190
  133. package/references/minimem/src/cli/__tests__/commands.test.ts +0 -759
  134. package/references/minimem/src/cli/commands/__tests__/conflicts.test.ts +0 -141
  135. package/references/minimem/src/cli/commands/append.ts +0 -76
  136. package/references/minimem/src/cli/commands/config.ts +0 -262
  137. package/references/minimem/src/cli/commands/conflicts.ts +0 -413
  138. package/references/minimem/src/cli/commands/daemon.ts +0 -169
  139. package/references/minimem/src/cli/commands/index.ts +0 -12
  140. package/references/minimem/src/cli/commands/init.ts +0 -88
  141. package/references/minimem/src/cli/commands/mcp.ts +0 -177
  142. package/references/minimem/src/cli/commands/push-pull.ts +0 -213
  143. package/references/minimem/src/cli/commands/search.ts +0 -158
  144. package/references/minimem/src/cli/commands/status.ts +0 -84
  145. package/references/minimem/src/cli/commands/sync-init.ts +0 -290
  146. package/references/minimem/src/cli/commands/sync.ts +0 -70
  147. package/references/minimem/src/cli/commands/upsert.ts +0 -197
  148. package/references/minimem/src/cli/config.ts +0 -584
  149. package/references/minimem/src/cli/index.ts +0 -264
  150. package/references/minimem/src/cli/shared.ts +0 -161
  151. package/references/minimem/src/cli/sync/__tests__/central.test.ts +0 -152
  152. package/references/minimem/src/cli/sync/__tests__/conflicts.test.ts +0 -209
  153. package/references/minimem/src/cli/sync/__tests__/daemon.test.ts +0 -118
  154. package/references/minimem/src/cli/sync/__tests__/detection.test.ts +0 -207
  155. package/references/minimem/src/cli/sync/__tests__/integration.test.ts +0 -476
  156. package/references/minimem/src/cli/sync/__tests__/registry.test.ts +0 -363
  157. package/references/minimem/src/cli/sync/__tests__/state.test.ts +0 -255
  158. package/references/minimem/src/cli/sync/__tests__/validation.test.ts +0 -193
  159. package/references/minimem/src/cli/sync/__tests__/watcher.test.ts +0 -178
  160. package/references/minimem/src/cli/sync/central.ts +0 -292
  161. package/references/minimem/src/cli/sync/conflicts.ts +0 -204
  162. package/references/minimem/src/cli/sync/daemon.ts +0 -407
  163. package/references/minimem/src/cli/sync/detection.ts +0 -138
  164. package/references/minimem/src/cli/sync/index.ts +0 -107
  165. package/references/minimem/src/cli/sync/operations.ts +0 -373
  166. package/references/minimem/src/cli/sync/registry.ts +0 -279
  167. package/references/minimem/src/cli/sync/state.ts +0 -355
  168. package/references/minimem/src/cli/sync/validation.ts +0 -206
  169. package/references/minimem/src/cli/sync/watcher.ts +0 -234
  170. package/references/minimem/src/cli/version.ts +0 -34
  171. package/references/minimem/src/core/index.ts +0 -9
  172. package/references/minimem/src/core/indexer.ts +0 -628
  173. package/references/minimem/src/core/searcher.ts +0 -221
  174. package/references/minimem/src/db/schema.ts +0 -183
  175. package/references/minimem/src/db/sqlite-vec.ts +0 -24
  176. package/references/minimem/src/embeddings/__tests__/embeddings.test.ts +0 -431
  177. package/references/minimem/src/embeddings/batch-gemini.ts +0 -392
  178. package/references/minimem/src/embeddings/batch-openai.ts +0 -409
  179. package/references/minimem/src/embeddings/embeddings.ts +0 -434
  180. package/references/minimem/src/index.ts +0 -109
  181. package/references/minimem/src/internal.ts +0 -299
  182. package/references/minimem/src/minimem.ts +0 -1276
  183. package/references/minimem/src/search/__tests__/hybrid.test.ts +0 -247
  184. package/references/minimem/src/search/graph.ts +0 -234
  185. package/references/minimem/src/search/hybrid.ts +0 -151
  186. package/references/minimem/src/search/search.ts +0 -256
  187. package/references/minimem/src/server/__tests__/mcp.test.ts +0 -341
  188. package/references/minimem/src/server/__tests__/tools.test.ts +0 -364
  189. package/references/minimem/src/server/mcp.ts +0 -326
  190. package/references/minimem/src/server/tools.ts +0 -720
  191. package/references/minimem/src/session.ts +0 -460
  192. package/references/minimem/tsconfig.json +0 -19
  193. package/references/minimem/tsup.config.ts +0 -26
  194. package/references/minimem/vitest.config.ts +0 -24
  195. package/references/sessionlog/.husky/pre-commit +0 -1
  196. package/references/sessionlog/.lintstagedrc.json +0 -4
  197. package/references/sessionlog/.prettierignore +0 -4
  198. package/references/sessionlog/.prettierrc.json +0 -11
  199. package/references/sessionlog/LICENSE +0 -21
  200. package/references/sessionlog/README.md +0 -453
  201. package/references/sessionlog/eslint.config.js +0 -58
  202. package/references/sessionlog/package-lock.json +0 -3672
  203. package/references/sessionlog/package.json +0 -65
  204. package/references/sessionlog/src/__tests__/agent-hooks.test.ts +0 -570
  205. package/references/sessionlog/src/__tests__/agent-registry.test.ts +0 -127
  206. package/references/sessionlog/src/__tests__/claude-code-hooks.test.ts +0 -225
  207. package/references/sessionlog/src/__tests__/claude-generator.test.ts +0 -46
  208. package/references/sessionlog/src/__tests__/commit-msg.test.ts +0 -86
  209. package/references/sessionlog/src/__tests__/cursor-agent.test.ts +0 -224
  210. package/references/sessionlog/src/__tests__/e2e-live.test.ts +0 -890
  211. package/references/sessionlog/src/__tests__/event-log.test.ts +0 -183
  212. package/references/sessionlog/src/__tests__/flush-sentinel.test.ts +0 -105
  213. package/references/sessionlog/src/__tests__/gemini-agent.test.ts +0 -375
  214. package/references/sessionlog/src/__tests__/git-hooks.test.ts +0 -78
  215. package/references/sessionlog/src/__tests__/hook-managers.test.ts +0 -121
  216. package/references/sessionlog/src/__tests__/lifecycle-tasks.test.ts +0 -759
  217. package/references/sessionlog/src/__tests__/opencode-agent.test.ts +0 -338
  218. package/references/sessionlog/src/__tests__/redaction.test.ts +0 -136
  219. package/references/sessionlog/src/__tests__/session-repo.test.ts +0 -353
  220. package/references/sessionlog/src/__tests__/session-store.test.ts +0 -166
  221. package/references/sessionlog/src/__tests__/setup-ccweb.test.ts +0 -466
  222. package/references/sessionlog/src/__tests__/skill-live.test.ts +0 -461
  223. package/references/sessionlog/src/__tests__/summarize.test.ts +0 -348
  224. package/references/sessionlog/src/__tests__/task-plan-e2e.test.ts +0 -610
  225. package/references/sessionlog/src/__tests__/task-plan-live.test.ts +0 -632
  226. package/references/sessionlog/src/__tests__/transcript-timestamp.test.ts +0 -121
  227. package/references/sessionlog/src/__tests__/types.test.ts +0 -166
  228. package/references/sessionlog/src/__tests__/utils.test.ts +0 -333
  229. package/references/sessionlog/src/__tests__/validation.test.ts +0 -103
  230. package/references/sessionlog/src/__tests__/worktree.test.ts +0 -57
  231. package/references/sessionlog/src/agent/agents/claude-code.ts +0 -1089
  232. package/references/sessionlog/src/agent/agents/cursor.ts +0 -361
  233. package/references/sessionlog/src/agent/agents/gemini-cli.ts +0 -632
  234. package/references/sessionlog/src/agent/agents/opencode.ts +0 -540
  235. package/references/sessionlog/src/agent/registry.ts +0 -143
  236. package/references/sessionlog/src/agent/session-types.ts +0 -113
  237. package/references/sessionlog/src/agent/types.ts +0 -220
  238. package/references/sessionlog/src/cli.ts +0 -597
  239. package/references/sessionlog/src/commands/clean.ts +0 -133
  240. package/references/sessionlog/src/commands/disable.ts +0 -84
  241. package/references/sessionlog/src/commands/doctor.ts +0 -145
  242. package/references/sessionlog/src/commands/enable.ts +0 -202
  243. package/references/sessionlog/src/commands/explain.ts +0 -261
  244. package/references/sessionlog/src/commands/reset.ts +0 -105
  245. package/references/sessionlog/src/commands/resume.ts +0 -180
  246. package/references/sessionlog/src/commands/rewind.ts +0 -195
  247. package/references/sessionlog/src/commands/setup-ccweb.ts +0 -275
  248. package/references/sessionlog/src/commands/status.ts +0 -172
  249. package/references/sessionlog/src/config.ts +0 -165
  250. package/references/sessionlog/src/events/event-log.ts +0 -126
  251. package/references/sessionlog/src/git-operations.ts +0 -558
  252. package/references/sessionlog/src/hooks/git-hooks.ts +0 -165
  253. package/references/sessionlog/src/hooks/lifecycle.ts +0 -391
  254. package/references/sessionlog/src/index.ts +0 -650
  255. package/references/sessionlog/src/security/redaction.ts +0 -283
  256. package/references/sessionlog/src/session/state-machine.ts +0 -452
  257. package/references/sessionlog/src/store/checkpoint-store.ts +0 -509
  258. package/references/sessionlog/src/store/native-store.ts +0 -173
  259. package/references/sessionlog/src/store/provider-types.ts +0 -99
  260. package/references/sessionlog/src/store/session-store.ts +0 -266
  261. package/references/sessionlog/src/strategy/attribution.ts +0 -296
  262. package/references/sessionlog/src/strategy/common.ts +0 -207
  263. package/references/sessionlog/src/strategy/content-overlap.ts +0 -228
  264. package/references/sessionlog/src/strategy/manual-commit.ts +0 -988
  265. package/references/sessionlog/src/strategy/types.ts +0 -279
  266. package/references/sessionlog/src/summarize/claude-generator.ts +0 -115
  267. package/references/sessionlog/src/summarize/summarize.ts +0 -432
  268. package/references/sessionlog/src/types.ts +0 -508
  269. package/references/sessionlog/src/utils/chunk-files.ts +0 -49
  270. package/references/sessionlog/src/utils/commit-message.ts +0 -65
  271. package/references/sessionlog/src/utils/detect-agent.ts +0 -36
  272. package/references/sessionlog/src/utils/hook-managers.ts +0 -125
  273. package/references/sessionlog/src/utils/ide-tags.ts +0 -32
  274. package/references/sessionlog/src/utils/paths.ts +0 -79
  275. package/references/sessionlog/src/utils/preview-rewind.ts +0 -80
  276. package/references/sessionlog/src/utils/rewind-conflict.ts +0 -121
  277. package/references/sessionlog/src/utils/shadow-branch.ts +0 -109
  278. package/references/sessionlog/src/utils/string-utils.ts +0 -46
  279. package/references/sessionlog/src/utils/todo-extract.ts +0 -188
  280. package/references/sessionlog/src/utils/trailers.ts +0 -187
  281. package/references/sessionlog/src/utils/transcript-parse.ts +0 -177
  282. package/references/sessionlog/src/utils/transcript-timestamp.ts +0 -59
  283. package/references/sessionlog/src/utils/tree-ops.ts +0 -219
  284. package/references/sessionlog/src/utils/tty.ts +0 -72
  285. package/references/sessionlog/src/utils/validation.ts +0 -65
  286. package/references/sessionlog/src/utils/worktree.ts +0 -58
  287. package/references/sessionlog/src/wire-types.ts +0 -59
  288. package/references/sessionlog/templates/setup-env.sh +0 -153
  289. package/references/sessionlog/tsconfig.json +0 -18
  290. package/references/sessionlog/vitest.config.ts +0 -12
  291. package/references/skill-tree/.claude/settings.json +0 -6
  292. package/references/skill-tree/.sudocode/issues.jsonl +0 -19
  293. package/references/skill-tree/.sudocode/specs.jsonl +0 -3
  294. package/references/skill-tree/CLAUDE.md +0 -126
  295. package/references/skill-tree/README.md +0 -372
  296. package/references/skill-tree/docs/GAPS_v1.md +0 -221
  297. package/references/skill-tree/docs/INTEGRATION_PLAN.md +0 -467
  298. package/references/skill-tree/docs/TODOS.md +0 -91
  299. package/references/skill-tree/docs/anthropic_skill_guide.md +0 -1364
  300. package/references/skill-tree/docs/design/federated-skill-trees.md +0 -524
  301. package/references/skill-tree/docs/design/multi-agent-sync.md +0 -759
  302. package/references/skill-tree/docs/scraper/BRAINSTORM.md +0 -583
  303. package/references/skill-tree/docs/scraper/POC_PLAN.md +0 -420
  304. package/references/skill-tree/docs/scraper/README.md +0 -170
  305. package/references/skill-tree/examples/basic-usage.ts +0 -164
  306. package/references/skill-tree/package-lock.json +0 -1852
  307. package/references/skill-tree/package.json +0 -66
  308. package/references/skill-tree/scraper/README.md +0 -123
  309. package/references/skill-tree/scraper/docs/DESIGN.md +0 -683
  310. package/references/skill-tree/scraper/docs/PLAN.md +0 -336
  311. package/references/skill-tree/scraper/drizzle.config.ts +0 -10
  312. package/references/skill-tree/scraper/package-lock.json +0 -6329
  313. package/references/skill-tree/scraper/package.json +0 -68
  314. package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-description.md +0 -7
  315. package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-name.md +0 -7
  316. package/references/skill-tree/scraper/test/fixtures/minimal-skill/SKILL.md +0 -27
  317. package/references/skill-tree/scraper/test/fixtures/skill-json/SKILL.json +0 -21
  318. package/references/skill-tree/scraper/test/fixtures/skill-with-meta/SKILL.md +0 -54
  319. package/references/skill-tree/scraper/test/fixtures/skill-with-meta/_meta.json +0 -24
  320. package/references/skill-tree/scraper/test/fixtures/valid-skill/SKILL.md +0 -93
  321. package/references/skill-tree/scraper/test/fixtures/valid-skill/_meta.json +0 -22
  322. package/references/skill-tree/scraper/tsup.config.ts +0 -14
  323. package/references/skill-tree/scraper/vitest.config.ts +0 -17
  324. package/references/skill-tree/scripts/convert-to-vitest.ts +0 -166
  325. package/references/skill-tree/skills/skill-writer/SKILL.md +0 -339
  326. package/references/skill-tree/skills/skill-writer/references/examples.md +0 -326
  327. package/references/skill-tree/skills/skill-writer/references/patterns.md +0 -210
  328. package/references/skill-tree/skills/skill-writer/references/quality-checklist.md +0 -123
  329. package/references/skill-tree/test/run-all.ts +0 -106
  330. package/references/skill-tree/test/utils.ts +0 -128
  331. package/references/skill-tree/vitest.config.ts +0 -16
@@ -1,1089 +0,0 @@
1
- /**
2
- * Claude Code Agent
3
- *
4
- * Implementation of the Sessionlog agent interface for Anthropic's Claude Code.
5
- * Handles JSONL transcript format, Claude-specific hook installation,
6
- * and session lifecycle management.
7
- */
8
-
9
- import * as fs from 'node:fs';
10
- import * as path from 'node:path';
11
- import * as os from 'node:os';
12
- import * as crypto from 'node:crypto';
13
- import {
14
- AGENT_NAMES,
15
- AGENT_TYPES,
16
- type HookInput,
17
- type Event,
18
- type TokenUsage,
19
- EventType,
20
- emptyTokenUsage,
21
- } from '../../types.js';
22
- import type {
23
- Agent,
24
- HookSupport,
25
- TranscriptAnalyzer,
26
- TokenCalculator,
27
- TranscriptChunker,
28
- TranscriptPreparer,
29
- SubagentAwareExtractor,
30
- } from '../types.js';
31
- import { registerAgent } from '../registry.js';
32
-
33
- // ============================================================================
34
- // Constants
35
- // ============================================================================
36
-
37
- const CLAUDE_DIR = '.claude';
38
- const CLAUDE_SETTINGS_FILE = '.claude/settings.json';
39
-
40
- const HOOK_NAMES = [
41
- 'session-start',
42
- 'session-end',
43
- 'stop',
44
- 'user-prompt-submit',
45
- 'pre-task',
46
- 'post-task',
47
- 'post-todo',
48
- 'post-task-create',
49
- 'post-task-update',
50
- 'post-plan-enter',
51
- 'post-plan-exit',
52
- 'post-skill',
53
- ] as const;
54
-
55
- /** Tools that modify files (detected in transcript) */
56
- const FILE_MODIFICATION_TOOLS = new Set([
57
- 'Write',
58
- 'Edit',
59
- 'NotebookEdit',
60
- 'mcp__acp__Write',
61
- 'mcp__acp__Edit',
62
- ]);
63
-
64
- /** Deny rule to prevent agents from reading metadata */
65
- const METADATA_DENY_RULE = 'Read(./.sessionlog/metadata/**)';
66
-
67
- // ============================================================================
68
- // Transcript Types (JSONL)
69
- // ============================================================================
70
-
71
- export interface TranscriptLine {
72
- type: 'user' | 'assistant';
73
- uuid?: string;
74
- message: unknown;
75
- }
76
-
77
- export interface AssistantContent {
78
- type: string;
79
- text?: string;
80
- name?: string;
81
- input?: Record<string, unknown>;
82
- id?: string;
83
- }
84
-
85
- interface MessageUsage {
86
- input_tokens?: number;
87
- cache_creation_input_tokens?: number;
88
- cache_read_input_tokens?: number;
89
- output_tokens?: number;
90
- }
91
-
92
- // ============================================================================
93
- // Claude Code Settings Types
94
- // ============================================================================
95
-
96
- interface ClaudeHookEntry {
97
- type: string;
98
- command: string;
99
- }
100
-
101
- interface ClaudeHookMatcher {
102
- matcher: string;
103
- hooks: ClaudeHookEntry[];
104
- }
105
-
106
- interface ClaudeSettings {
107
- hooks?: {
108
- SessionStart?: ClaudeHookMatcher[];
109
- SessionEnd?: ClaudeHookMatcher[];
110
- UserPromptSubmit?: ClaudeHookMatcher[];
111
- Stop?: ClaudeHookMatcher[];
112
- PreToolUse?: ClaudeHookMatcher[];
113
- PostToolUse?: ClaudeHookMatcher[];
114
- };
115
- permissions?: {
116
- deny?: string[];
117
- };
118
- [key: string]: unknown;
119
- }
120
-
121
- // ============================================================================
122
- // Claude Code Agent Implementation
123
- // ============================================================================
124
-
125
- class ClaudeCodeAgent
126
- implements
127
- Agent,
128
- HookSupport,
129
- TranscriptAnalyzer,
130
- TokenCalculator,
131
- TranscriptChunker,
132
- TranscriptPreparer,
133
- SubagentAwareExtractor
134
- {
135
- readonly name = AGENT_NAMES.CLAUDE_CODE;
136
- readonly type = AGENT_TYPES.CLAUDE_CODE;
137
- readonly description = 'Anthropic Claude Code CLI';
138
- readonly isPreview = false;
139
- readonly protectedDirs = [CLAUDE_DIR];
140
-
141
- async detectPresence(cwd?: string): Promise<boolean> {
142
- const repoRoot = cwd ?? process.cwd();
143
- const claudeDir = path.join(repoRoot, CLAUDE_DIR);
144
- try {
145
- const stat = await fs.promises.stat(claudeDir);
146
- return stat.isDirectory();
147
- } catch {
148
- return false;
149
- }
150
- }
151
-
152
- async getSessionDir(repoPath: string): Promise<string> {
153
- // Claude Code stores sessions in ~/.claude/projects/<sanitized-path>/
154
- const sanitized = sanitizePathForClaude(repoPath);
155
- return path.join(os.homedir(), '.claude', 'projects', sanitized);
156
- }
157
-
158
- getSessionID(input: HookInput): string {
159
- return input.sessionID;
160
- }
161
-
162
- resolveSessionFile(sessionDir: string, agentSessionID: string): string {
163
- return path.join(sessionDir, `${agentSessionID}.jsonl`);
164
- }
165
-
166
- async readTranscript(sessionRef: string): Promise<Buffer> {
167
- return fs.promises.readFile(sessionRef);
168
- }
169
-
170
- formatResumeCommand(sessionID: string): string {
171
- return `claude --resume ${sessionID}`;
172
- }
173
-
174
- // ===========================================================================
175
- // HookSupport
176
- // ===========================================================================
177
-
178
- hookNames(): string[] {
179
- return [...HOOK_NAMES];
180
- }
181
-
182
- parseHookEvent(hookName: string, stdin: string): Event | null {
183
- try {
184
- const data = JSON.parse(stdin) as Record<string, unknown>;
185
-
186
- switch (hookName) {
187
- case 'session-start':
188
- return {
189
- type: EventType.SessionStart,
190
- sessionID: String(data.session_id ?? data.sessionID ?? ''),
191
- sessionRef: String(data.transcript_path ?? data.transcriptPath ?? ''),
192
- timestamp: new Date(),
193
- };
194
-
195
- case 'user-prompt-submit':
196
- return {
197
- type: EventType.TurnStart,
198
- sessionID: String(data.session_id ?? data.sessionID ?? ''),
199
- sessionRef: String(data.transcript_path ?? data.transcriptPath ?? ''),
200
- prompt: String(data.prompt ?? ''),
201
- timestamp: new Date(),
202
- };
203
-
204
- case 'stop':
205
- return {
206
- type: EventType.TurnEnd,
207
- sessionID: String(data.session_id ?? data.sessionID ?? ''),
208
- sessionRef: String(data.transcript_path ?? data.transcriptPath ?? ''),
209
- timestamp: new Date(),
210
- };
211
-
212
- case 'session-end':
213
- return {
214
- type: EventType.SessionEnd,
215
- sessionID: String(data.session_id ?? data.sessionID ?? ''),
216
- sessionRef: String(data.transcript_path ?? data.transcriptPath ?? ''),
217
- timestamp: new Date(),
218
- };
219
-
220
- case 'pre-task':
221
- return {
222
- type: EventType.SubagentStart,
223
- sessionID: String(data.session_id ?? data.sessionID ?? ''),
224
- sessionRef: String(data.transcript_path ?? data.transcriptPath ?? ''),
225
- toolUseID: String(data.tool_use_id ?? data.toolUseID ?? ''),
226
- toolInput: data.tool_input ?? data.toolInput,
227
- timestamp: new Date(),
228
- };
229
-
230
- case 'post-task':
231
- return {
232
- type: EventType.SubagentEnd,
233
- sessionID: String(data.session_id ?? data.sessionID ?? ''),
234
- sessionRef: String(data.transcript_path ?? data.transcriptPath ?? ''),
235
- toolUseID: String(data.tool_use_id ?? data.toolUseID ?? ''),
236
- subagentID: (data.tool_response as Record<string, unknown>)?.agentId as string,
237
- timestamp: new Date(),
238
- };
239
-
240
- case 'post-todo':
241
- return {
242
- type: EventType.Compaction,
243
- sessionID: String(data.session_id ?? data.sessionID ?? ''),
244
- sessionRef: String(data.transcript_path ?? data.transcriptPath ?? ''),
245
- timestamp: new Date(),
246
- };
247
-
248
- case 'post-task-create': {
249
- const tcInput = (data.tool_input ?? data.toolInput) as
250
- | Record<string, unknown>
251
- | undefined;
252
- const tcResponse = (data.tool_response ?? data.toolResponse) as
253
- | Record<string, unknown>
254
- | undefined;
255
- return {
256
- type: EventType.TaskCreate,
257
- sessionID: String(data.session_id ?? data.sessionID ?? ''),
258
- sessionRef: String(data.transcript_path ?? data.transcriptPath ?? ''),
259
- toolUseID: String(data.tool_use_id ?? data.toolUseID ?? ''),
260
- taskID: String(tcResponse?.taskId ?? tcResponse?.id ?? ''),
261
- taskSubject: String(tcInput?.subject ?? ''),
262
- taskActiveForm: tcInput?.activeForm as string | undefined,
263
- taskDescription: tcInput?.description as string | undefined,
264
- toolInput: tcInput,
265
- timestamp: new Date(),
266
- };
267
- }
268
-
269
- case 'post-task-update': {
270
- const tuInput = (data.tool_input ?? data.toolInput) as
271
- | Record<string, unknown>
272
- | undefined;
273
- return {
274
- type: EventType.TaskUpdate,
275
- sessionID: String(data.session_id ?? data.sessionID ?? ''),
276
- sessionRef: String(data.transcript_path ?? data.transcriptPath ?? ''),
277
- toolUseID: String(data.tool_use_id ?? data.toolUseID ?? ''),
278
- taskID: String(tuInput?.taskId ?? ''),
279
- taskStatus: tuInput?.status as string | undefined,
280
- taskSubject: tuInput?.subject as string | undefined,
281
- taskDescription: tuInput?.description as string | undefined,
282
- toolInput: tuInput,
283
- timestamp: new Date(),
284
- };
285
- }
286
-
287
- case 'post-plan-enter':
288
- return {
289
- type: EventType.PlanModeEnter,
290
- sessionID: String(data.session_id ?? data.sessionID ?? ''),
291
- sessionRef: String(data.transcript_path ?? data.transcriptPath ?? ''),
292
- timestamp: new Date(),
293
- };
294
-
295
- case 'post-plan-exit': {
296
- const peInput = (data.tool_input ?? data.toolInput) as
297
- | Record<string, unknown>
298
- | undefined;
299
- const peResponse = (data.tool_response ?? data.toolResponse) as
300
- | Record<string, unknown>
301
- | string
302
- | undefined;
303
- // Extract plan file path from tool_response
304
- let planFilePath: string | undefined;
305
- if (typeof peResponse === 'object' && peResponse !== null) {
306
- planFilePath = (peResponse.planFilePath ??
307
- peResponse.planFile ??
308
- peResponse.file_path) as string | undefined;
309
- // Also try parsing from a message like "Your plan has been saved to: <path>"
310
- if (!planFilePath && typeof peResponse.content === 'string') {
311
- const match = peResponse.content.match(/saved to:\s*(.+)/i);
312
- if (match) planFilePath = match[1].trim();
313
- }
314
- } else if (typeof peResponse === 'string') {
315
- const match = peResponse.match(/saved to:\s*(.+)/i);
316
- if (match) planFilePath = match[1].trim();
317
- }
318
- return {
319
- type: EventType.PlanModeExit,
320
- sessionID: String(data.session_id ?? data.sessionID ?? ''),
321
- sessionRef: String(data.transcript_path ?? data.transcriptPath ?? ''),
322
- planAllowedPrompts: peInput?.allowedPrompts as
323
- | Array<{ tool: string; prompt: string }>
324
- | undefined,
325
- planFilePath,
326
- timestamp: new Date(),
327
- };
328
- }
329
-
330
- case 'post-skill': {
331
- const skInput = (data.tool_input ?? data.toolInput) as
332
- | Record<string, unknown>
333
- | undefined;
334
- return {
335
- type: EventType.SkillUse,
336
- sessionID: String(data.session_id ?? data.sessionID ?? ''),
337
- sessionRef: String(data.transcript_path ?? data.transcriptPath ?? ''),
338
- toolUseID: String(data.tool_use_id ?? data.toolUseID ?? ''),
339
- skillName: String(skInput?.skill ?? ''),
340
- skillArgs: skInput?.args as string | undefined,
341
- toolInput: skInput,
342
- timestamp: new Date(),
343
- };
344
- }
345
-
346
- default:
347
- return null;
348
- }
349
- } catch {
350
- return null;
351
- }
352
- }
353
-
354
- async installHooks(repoPath: string, force = false): Promise<number> {
355
- const settingsPath = path.join(repoPath, CLAUDE_SETTINGS_FILE);
356
- let settings: ClaudeSettings = {};
357
-
358
- // Read existing settings
359
- try {
360
- const content = await fs.promises.readFile(settingsPath, 'utf-8');
361
- settings = JSON.parse(content) as ClaudeSettings;
362
- } catch {
363
- // No existing settings
364
- }
365
-
366
- if (!settings.hooks) settings.hooks = {};
367
-
368
- let installed = 0;
369
-
370
- // Install lifecycle hooks
371
- const hookMappings: Array<{
372
- settingsKey: keyof NonNullable<ClaudeSettings['hooks']>;
373
- hookName: string;
374
- }> = [
375
- { settingsKey: 'SessionStart', hookName: 'session-start' },
376
- { settingsKey: 'SessionEnd', hookName: 'session-end' },
377
- { settingsKey: 'UserPromptSubmit', hookName: 'user-prompt-submit' },
378
- { settingsKey: 'Stop', hookName: 'stop' },
379
- ];
380
-
381
- // Task hooks (pre/post tool use for Task tool)
382
- const taskHookMappings: Array<{
383
- settingsKey: keyof NonNullable<ClaudeSettings['hooks']>;
384
- hookName: string;
385
- matcher: string;
386
- }> = [
387
- { settingsKey: 'PreToolUse', hookName: 'pre-task', matcher: 'Task' },
388
- { settingsKey: 'PostToolUse', hookName: 'post-task', matcher: 'Task' },
389
- { settingsKey: 'PostToolUse', hookName: 'post-todo', matcher: 'TodoWrite' },
390
- { settingsKey: 'PostToolUse', hookName: 'post-task-create', matcher: 'TaskCreate' },
391
- { settingsKey: 'PostToolUse', hookName: 'post-task-update', matcher: 'TaskUpdate' },
392
- { settingsKey: 'PostToolUse', hookName: 'post-plan-enter', matcher: 'EnterPlanMode' },
393
- { settingsKey: 'PostToolUse', hookName: 'post-plan-exit', matcher: 'ExitPlanMode' },
394
- { settingsKey: 'PostToolUse', hookName: 'post-skill', matcher: 'Skill' },
395
- ];
396
-
397
- for (const { settingsKey, hookName } of hookMappings) {
398
- const existing = settings.hooks[settingsKey] ?? [];
399
-
400
- if (force) {
401
- // Remove existing sessionlog hooks
402
- const filtered = existing.filter(
403
- (m) => !m.hooks.some((h) => h.command.includes('sessionlog ')),
404
- );
405
- settings.hooks[settingsKey] = filtered;
406
- }
407
-
408
- // Check if already installed
409
- const hasSessionlogHook = (settings.hooks[settingsKey] ?? []).some((m) =>
410
- m.hooks.some((h) => h.command.includes('sessionlog ')),
411
- );
412
-
413
- if (!hasSessionlogHook) {
414
- const matchers = settings.hooks[settingsKey] ?? [];
415
- matchers.push({
416
- matcher: '',
417
- hooks: [
418
- {
419
- type: 'command',
420
- command: `sessionlog hooks claude-code ${hookName}`,
421
- },
422
- ],
423
- });
424
- settings.hooks[settingsKey] = matchers;
425
- installed++;
426
- }
427
- }
428
-
429
- for (const { settingsKey, hookName, matcher } of taskHookMappings) {
430
- const existing = settings.hooks[settingsKey] ?? [];
431
-
432
- if (force) {
433
- const filtered = existing.filter(
434
- (m) => !(m.matcher === matcher && m.hooks.some((h) => h.command.includes('sessionlog '))),
435
- );
436
- settings.hooks[settingsKey] = filtered;
437
- }
438
-
439
- const hasSessionlogHook = (settings.hooks[settingsKey] ?? []).some(
440
- (m) => m.matcher === matcher && m.hooks.some((h) => h.command.includes('sessionlog ')),
441
- );
442
-
443
- if (!hasSessionlogHook) {
444
- const matchers = settings.hooks[settingsKey] ?? [];
445
- matchers.push({
446
- matcher,
447
- hooks: [
448
- {
449
- type: 'command',
450
- command: `sessionlog hooks claude-code ${hookName}`,
451
- },
452
- ],
453
- });
454
- settings.hooks[settingsKey] = matchers;
455
- installed++;
456
- }
457
- }
458
-
459
- // Add metadata deny rule
460
- if (!settings.permissions) settings.permissions = {};
461
- if (!settings.permissions.deny) settings.permissions.deny = [];
462
- if (!settings.permissions.deny.includes(METADATA_DENY_RULE)) {
463
- settings.permissions.deny.push(METADATA_DENY_RULE);
464
- }
465
-
466
- // Write settings
467
- const dir = path.dirname(settingsPath);
468
- await fs.promises.mkdir(dir, { recursive: true });
469
- await fs.promises.writeFile(settingsPath, JSON.stringify(settings, null, 2));
470
-
471
- return installed;
472
- }
473
-
474
- async uninstallHooks(repoPath: string): Promise<void> {
475
- const settingsPath = path.join(repoPath, CLAUDE_SETTINGS_FILE);
476
-
477
- try {
478
- const content = await fs.promises.readFile(settingsPath, 'utf-8');
479
- const settings = JSON.parse(content) as ClaudeSettings;
480
-
481
- if (settings.hooks) {
482
- for (const key of Object.keys(settings.hooks) as Array<
483
- keyof NonNullable<ClaudeSettings['hooks']>
484
- >) {
485
- const matchers = settings.hooks[key];
486
- if (!matchers) continue;
487
-
488
- settings.hooks[key] = matchers.filter(
489
- (m) => !m.hooks.some((h) => h.command.includes('sessionlog ')),
490
- );
491
-
492
- if (settings.hooks[key]!.length === 0) {
493
- delete settings.hooks[key];
494
- }
495
- }
496
-
497
- if (Object.keys(settings.hooks).length === 0) {
498
- delete settings.hooks;
499
- }
500
- }
501
-
502
- // Remove deny rule
503
- if (settings.permissions?.deny) {
504
- settings.permissions.deny = settings.permissions.deny.filter(
505
- (d) => d !== METADATA_DENY_RULE,
506
- );
507
- if (settings.permissions.deny.length === 0) {
508
- delete settings.permissions.deny;
509
- }
510
- if (Object.keys(settings.permissions).length === 0) {
511
- delete settings.permissions;
512
- }
513
- }
514
-
515
- await fs.promises.writeFile(settingsPath, JSON.stringify(settings, null, 2));
516
- } catch {
517
- // No settings to modify
518
- }
519
- }
520
-
521
- async areHooksInstalled(repoPath: string): Promise<boolean> {
522
- const settingsPath = path.join(repoPath, CLAUDE_SETTINGS_FILE);
523
-
524
- try {
525
- const content = await fs.promises.readFile(settingsPath, 'utf-8');
526
- const settings = JSON.parse(content) as ClaudeSettings;
527
-
528
- if (!settings.hooks) return false;
529
-
530
- // Check for at least the session-start hook
531
- const sessionStart = settings.hooks.SessionStart ?? [];
532
- return sessionStart.some((m) => m.hooks.some((h) => h.command.includes('sessionlog ')));
533
- } catch {
534
- return false;
535
- }
536
- }
537
-
538
- // ===========================================================================
539
- // TranscriptPreparer — wait for Claude Code's async transcript flush
540
- // ===========================================================================
541
-
542
- async prepareTranscript(sessionRef: string): Promise<void> {
543
- await waitForTranscriptFlush(sessionRef);
544
- }
545
-
546
- // ===========================================================================
547
- // TranscriptAnalyzer
548
- // ===========================================================================
549
-
550
- async getTranscriptPosition(transcriptPath: string): Promise<number> {
551
- try {
552
- const content = await fs.promises.readFile(transcriptPath, 'utf-8');
553
- const lines = content.split('\n').filter(Boolean);
554
- return lines.length;
555
- } catch {
556
- return 0;
557
- }
558
- }
559
-
560
- async extractModifiedFilesFromOffset(
561
- transcriptPath: string,
562
- startOffset: number,
563
- ): Promise<{ files: string[]; currentPosition: number }> {
564
- const content = await fs.promises.readFile(transcriptPath, 'utf-8');
565
- const lines = content.split('\n').filter(Boolean);
566
- const files = new Set<string>();
567
-
568
- for (let i = startOffset; i < lines.length; i++) {
569
- try {
570
- const line = JSON.parse(lines[i]) as TranscriptLine;
571
- if (line.type === 'assistant') {
572
- const contentBlocks = extractContentBlocks(line.message);
573
- for (const block of contentBlocks) {
574
- if (
575
- block.type === 'tool_use' &&
576
- block.name &&
577
- FILE_MODIFICATION_TOOLS.has(block.name)
578
- ) {
579
- const filePath = (block.input as Record<string, unknown>)?.file_path as string;
580
- if (filePath) files.add(filePath);
581
- }
582
- }
583
- }
584
- } catch {
585
- // Skip malformed lines
586
- }
587
- }
588
-
589
- return { files: Array.from(files), currentPosition: lines.length };
590
- }
591
-
592
- async extractPrompts(sessionRef: string, fromOffset: number): Promise<string[]> {
593
- const content = await fs.promises.readFile(sessionRef, 'utf-8');
594
- const lines = content.split('\n').filter(Boolean);
595
- const prompts: string[] = [];
596
-
597
- for (let i = fromOffset; i < lines.length; i++) {
598
- try {
599
- const line = JSON.parse(lines[i]) as TranscriptLine;
600
- if (line.type === 'user') {
601
- const text = extractUserText(line.message);
602
- if (text) prompts.push(text);
603
- }
604
- } catch {
605
- // Skip malformed lines
606
- }
607
- }
608
-
609
- return prompts;
610
- }
611
-
612
- async extractSummary(sessionRef: string): Promise<string> {
613
- const prompts = await this.extractPrompts(sessionRef, 0);
614
- if (prompts.length === 0) return '';
615
- // Return the first prompt as a summary, truncated
616
- return prompts[0].slice(0, 200);
617
- }
618
-
619
- // ===========================================================================
620
- // TokenCalculator
621
- // ===========================================================================
622
-
623
- async calculateTokenUsage(transcriptData: Buffer, fromOffset: number): Promise<TokenUsage> {
624
- const content = transcriptData.toString('utf-8');
625
- const lines = content.split('\n').filter(Boolean);
626
-
627
- // Deduplicate by message ID — streaming may produce multiple rows per message.
628
- // Keep the entry with the highest output_tokens (final streaming state).
629
- const usageByMessageID = new Map<string, MessageUsage>();
630
-
631
- for (let i = fromOffset; i < lines.length; i++) {
632
- try {
633
- const raw = JSON.parse(lines[i]) as Record<string, unknown>;
634
- if (raw.type === 'assistant') {
635
- const msg = raw.message as Record<string, unknown> | undefined;
636
- const msgID = msg?.id as string | undefined;
637
- const msgUsage = msg?.usage as MessageUsage | undefined;
638
- if (msgUsage && msgID) {
639
- const existing = usageByMessageID.get(msgID);
640
- if (!existing || (msgUsage.output_tokens ?? 0) > (existing.output_tokens ?? 0)) {
641
- usageByMessageID.set(msgID, msgUsage);
642
- }
643
- } else if (msgUsage) {
644
- // No message ID — count each occurrence
645
- usageByMessageID.set(`_anon_${i}`, msgUsage);
646
- }
647
- }
648
- } catch {
649
- // Skip malformed lines
650
- }
651
- }
652
-
653
- const usage = emptyTokenUsage();
654
- usage.apiCallCount = usageByMessageID.size;
655
- for (const u of usageByMessageID.values()) {
656
- usage.inputTokens += u.input_tokens ?? 0;
657
- usage.cacheCreationTokens += u.cache_creation_input_tokens ?? 0;
658
- usage.cacheReadTokens += u.cache_read_input_tokens ?? 0;
659
- usage.outputTokens += u.output_tokens ?? 0;
660
- }
661
-
662
- return usage;
663
- }
664
-
665
- // ===========================================================================
666
- // SubagentAwareExtractor
667
- // ===========================================================================
668
-
669
- async extractAllModifiedFiles(
670
- transcriptData: Buffer,
671
- fromOffset: number,
672
- subagentsDir: string,
673
- ): Promise<string[]> {
674
- if (transcriptData.length === 0) return [];
675
-
676
- const content = transcriptData.toString('utf-8');
677
- const allLines = content.split('\n').filter(Boolean);
678
- const sliced = allLines.slice(fromOffset);
679
- const parsed = sliced
680
- .map((line) => {
681
- try {
682
- return JSON.parse(line) as TranscriptLine;
683
- } catch {
684
- return null;
685
- }
686
- })
687
- .filter((l): l is TranscriptLine => l !== null);
688
-
689
- // Collect modified files from main agent
690
- const fileSet = new Set<string>();
691
- for (const f of extractModifiedFiles(parsed)) {
692
- fileSet.add(f);
693
- }
694
-
695
- // Find spawned subagents and collect their modified files
696
- const agentIDs = extractSpawnedAgentIDs(parsed);
697
- if (subagentsDir) {
698
- for (const agentID of agentIDs.keys()) {
699
- const agentPath = path.join(subagentsDir, `agent-${agentID}.jsonl`);
700
- try {
701
- const agentContent = await fs.promises.readFile(agentPath, 'utf-8');
702
- const agentLines = agentContent
703
- .split('\n')
704
- .filter(Boolean)
705
- .map((line) => {
706
- try {
707
- return JSON.parse(line) as TranscriptLine;
708
- } catch {
709
- return null;
710
- }
711
- })
712
- .filter((l): l is TranscriptLine => l !== null);
713
- for (const f of extractModifiedFiles(agentLines)) {
714
- fileSet.add(f);
715
- }
716
- } catch {
717
- // Subagent transcript may not exist yet
718
- }
719
- }
720
- }
721
-
722
- return Array.from(fileSet);
723
- }
724
-
725
- async calculateTotalTokenUsage(
726
- transcriptData: Buffer,
727
- fromOffset: number,
728
- subagentsDir: string,
729
- ): Promise<TokenUsage> {
730
- if (transcriptData.length === 0) return emptyTokenUsage();
731
-
732
- // Calculate main session token usage
733
- const mainUsage = await this.calculateTokenUsage(transcriptData, fromOffset);
734
-
735
- // Extract spawned agent IDs from the transcript
736
- const content = transcriptData.toString('utf-8');
737
- const allLines = content.split('\n').filter(Boolean);
738
- const sliced = allLines.slice(fromOffset);
739
- const parsed = sliced
740
- .map((line) => {
741
- try {
742
- return JSON.parse(line) as TranscriptLine;
743
- } catch {
744
- return null;
745
- }
746
- })
747
- .filter((l): l is TranscriptLine => l !== null);
748
-
749
- const agentIDs = extractSpawnedAgentIDs(parsed);
750
-
751
- // Calculate subagent token usage
752
- if (agentIDs.size > 0 && subagentsDir) {
753
- const subagentUsage = emptyTokenUsage();
754
- let hasSubagentUsage = false;
755
-
756
- for (const agentID of agentIDs.keys()) {
757
- const agentPath = path.join(subagentsDir, `agent-${agentID}.jsonl`);
758
- try {
759
- const agentData = await fs.promises.readFile(agentPath);
760
- const agentUsage = await this.calculateTokenUsage(agentData, 0);
761
- subagentUsage.inputTokens += agentUsage.inputTokens;
762
- subagentUsage.cacheCreationTokens += agentUsage.cacheCreationTokens;
763
- subagentUsage.cacheReadTokens += agentUsage.cacheReadTokens;
764
- subagentUsage.outputTokens += agentUsage.outputTokens;
765
- subagentUsage.apiCallCount += agentUsage.apiCallCount;
766
- hasSubagentUsage = true;
767
- } catch {
768
- // Agent transcript may not exist yet
769
- }
770
- }
771
-
772
- if (hasSubagentUsage && subagentUsage.apiCallCount > 0) {
773
- mainUsage.subagentTokens = subagentUsage;
774
- }
775
- }
776
-
777
- return mainUsage;
778
- }
779
-
780
- // ===========================================================================
781
- // TranscriptChunker
782
- // ===========================================================================
783
-
784
- async chunkTranscript(content: Buffer, maxSize: number): Promise<Buffer[]> {
785
- return chunkJSONL(content, maxSize);
786
- }
787
-
788
- async reassembleTranscript(chunks: Buffer[]): Promise<Buffer> {
789
- return Buffer.concat(chunks);
790
- }
791
- }
792
-
793
- // ============================================================================
794
- // Transcript Parsing Helpers
795
- // ============================================================================
796
-
797
- function extractContentBlocks(message: unknown): AssistantContent[] {
798
- if (!message || typeof message !== 'object') return [];
799
-
800
- const msg = message as Record<string, unknown>;
801
- const content = msg.content;
802
-
803
- if (Array.isArray(content)) {
804
- return content as AssistantContent[];
805
- }
806
-
807
- return [];
808
- }
809
-
810
- function extractUserText(message: unknown): string {
811
- if (typeof message === 'string') return message;
812
-
813
- if (!message || typeof message !== 'object') return '';
814
-
815
- const msg = message as Record<string, unknown>;
816
-
817
- // Content can be string or array of blocks
818
- if (typeof msg.content === 'string') return msg.content;
819
-
820
- if (Array.isArray(msg.content)) {
821
- return (msg.content as Array<{ type: string; text?: string }>)
822
- .filter((b) => b.type === 'text' && b.text)
823
- .map((b) => b.text!)
824
- .join('\n');
825
- }
826
-
827
- return '';
828
- }
829
-
830
- /**
831
- * Parse a JSONL transcript into structured lines
832
- */
833
- export function parseTranscript(content: string): TranscriptLine[] {
834
- const lines: TranscriptLine[] = [];
835
-
836
- for (const rawLine of content.split('\n').filter(Boolean)) {
837
- try {
838
- const parsed = JSON.parse(rawLine) as TranscriptLine;
839
- if (parsed.type === 'user' || parsed.type === 'assistant') {
840
- lines.push(parsed);
841
- }
842
- } catch {
843
- // Skip malformed lines
844
- }
845
- }
846
-
847
- return lines;
848
- }
849
-
850
- /**
851
- * Extract all modified files from transcript lines
852
- */
853
- export function extractModifiedFiles(lines: TranscriptLine[]): string[] {
854
- const files = new Set<string>();
855
-
856
- for (const line of lines) {
857
- if (line.type !== 'assistant') continue;
858
- const blocks = extractContentBlocks(line.message);
859
- for (const block of blocks) {
860
- if (block.type === 'tool_use' && block.name && FILE_MODIFICATION_TOOLS.has(block.name)) {
861
- const input = block.input as Record<string, unknown> | undefined;
862
- const filePath = (input?.file_path ?? input?.notebook_path) as string | undefined;
863
- if (filePath) files.add(filePath);
864
- }
865
- }
866
- }
867
-
868
- return Array.from(files);
869
- }
870
-
871
- /**
872
- * Extract the last user prompt from transcript lines
873
- */
874
- export function extractLastUserPrompt(lines: TranscriptLine[]): string {
875
- for (let i = lines.length - 1; i >= 0; i--) {
876
- if (lines[i].type === 'user') {
877
- return extractUserText(lines[i].message);
878
- }
879
- }
880
- return '';
881
- }
882
-
883
- // ============================================================================
884
- // Subagent ID Extraction
885
- // ============================================================================
886
-
887
- /**
888
- * Extract spawned agent IDs from Task tool results in a transcript.
889
- * When a Task tool completes, the tool_result contains "agentId: <id>".
890
- * Returns a map of agentID → toolUseID.
891
- */
892
- export function extractSpawnedAgentIDs(lines: TranscriptLine[]): Map<string, string> {
893
- const agentIDs = new Map<string, string>();
894
-
895
- for (const line of lines) {
896
- if (line.type !== 'user') continue;
897
-
898
- const msg = line.message as Record<string, unknown> | undefined;
899
- if (!msg) continue;
900
-
901
- const content = msg.content;
902
- if (!Array.isArray(content)) continue;
903
-
904
- for (const block of content as Array<Record<string, unknown>>) {
905
- if (block.type !== 'tool_result') continue;
906
-
907
- const toolUseID = String(block.tool_use_id ?? '');
908
- let textContent = '';
909
-
910
- // Content can be a string or array of text blocks
911
- if (typeof block.content === 'string') {
912
- textContent = block.content;
913
- } else if (Array.isArray(block.content)) {
914
- for (const tb of block.content as Array<Record<string, unknown>>) {
915
- if (tb.type === 'text' && typeof tb.text === 'string') {
916
- textContent += tb.text + '\n';
917
- }
918
- }
919
- }
920
-
921
- const agentID = extractAgentIDFromText(textContent);
922
- if (agentID) {
923
- agentIDs.set(agentID, toolUseID);
924
- }
925
- }
926
- }
927
-
928
- return agentIDs;
929
- }
930
-
931
- /**
932
- * Extract an agent ID from text containing "agentId: <id>".
933
- */
934
- function extractAgentIDFromText(text: string): string {
935
- const prefix = 'agentId: ';
936
- const idx = text.indexOf(prefix);
937
- if (idx === -1) return '';
938
-
939
- const start = idx + prefix.length;
940
- let end = start;
941
- while (end < text.length && /[a-zA-Z0-9]/.test(text[end])) {
942
- end++;
943
- }
944
-
945
- return end > start ? text.slice(start, end) : '';
946
- }
947
-
948
- // ============================================================================
949
- // JSONL Chunking
950
- // ============================================================================
951
-
952
- function chunkJSONL(content: Buffer, maxSize: number): Buffer[] {
953
- if (content.length <= maxSize) return [content];
954
-
955
- const str = content.toString('utf-8');
956
- const lines = str.split('\n');
957
- const chunks: Buffer[] = [];
958
- let current: string[] = [];
959
- let currentSize = 0;
960
-
961
- for (const line of lines) {
962
- const lineSize = Buffer.byteLength(line + '\n');
963
-
964
- if (currentSize + lineSize > maxSize && current.length > 0) {
965
- chunks.push(Buffer.from(current.join('\n') + '\n'));
966
- current = [];
967
- currentSize = 0;
968
- }
969
-
970
- current.push(line);
971
- currentSize += lineSize;
972
- }
973
-
974
- if (current.length > 0) {
975
- const remaining = current.join('\n');
976
- if (remaining.trim()) {
977
- chunks.push(Buffer.from(remaining + '\n'));
978
- }
979
- }
980
-
981
- return chunks;
982
- }
983
-
984
- // ============================================================================
985
- // Transcript Flush Sentinel
986
- // ============================================================================
987
-
988
- /**
989
- * String that appears in Claude Code's hook_progress entry when the stop hook
990
- * has been invoked, indicating the transcript is fully flushed.
991
- */
992
- const STOP_HOOK_SENTINEL = 'hooks claude-code stop';
993
-
994
- const FLUSH_MAX_WAIT_MS = 3000;
995
- const FLUSH_POLL_INTERVAL_MS = 50;
996
- const FLUSH_TAIL_BYTES = 4096;
997
- const FLUSH_MAX_SKEW_MS = 2000;
998
-
999
- /**
1000
- * Poll the transcript file for the stop hook sentinel.
1001
- * Falls back silently after a timeout.
1002
- */
1003
- async function waitForTranscriptFlush(transcriptPath: string): Promise<void> {
1004
- const hookStartTime = Date.now();
1005
- const deadline = hookStartTime + FLUSH_MAX_WAIT_MS;
1006
-
1007
- while (Date.now() < deadline) {
1008
- if (checkStopSentinel(transcriptPath, hookStartTime)) {
1009
- return;
1010
- }
1011
- await sleep(FLUSH_POLL_INTERVAL_MS);
1012
- }
1013
- // Timeout — proceed anyway
1014
- }
1015
-
1016
- /**
1017
- * Read the tail of the transcript file and look for the stop hook sentinel
1018
- * with a timestamp within the acceptable skew window.
1019
- */
1020
- function checkStopSentinel(filePath: string, hookStartTime: number): boolean {
1021
- let fd: number;
1022
- try {
1023
- fd = fs.openSync(filePath, 'r');
1024
- } catch {
1025
- return false;
1026
- }
1027
-
1028
- try {
1029
- const stat = fs.fstatSync(fd);
1030
- const offset = Math.max(0, stat.size - FLUSH_TAIL_BYTES);
1031
- const buf = Buffer.alloc(stat.size - offset);
1032
- fs.readSync(fd, buf, 0, buf.length, offset);
1033
-
1034
- const lines = buf.toString('utf-8').split('\n');
1035
- for (const line of lines) {
1036
- const trimmed = line.trim();
1037
- if (!trimmed || !trimmed.includes(STOP_HOOK_SENTINEL)) continue;
1038
-
1039
- try {
1040
- const entry = JSON.parse(trimmed) as { timestamp?: string };
1041
- if (!entry.timestamp) continue;
1042
-
1043
- const ts = new Date(entry.timestamp).getTime();
1044
- if (isNaN(ts)) continue;
1045
-
1046
- const lowerBound = hookStartTime - FLUSH_MAX_SKEW_MS;
1047
- const upperBound = hookStartTime + FLUSH_MAX_SKEW_MS;
1048
- if (ts > lowerBound && ts < upperBound) {
1049
- return true;
1050
- }
1051
- } catch {
1052
- continue;
1053
- }
1054
- }
1055
- return false;
1056
- } finally {
1057
- fs.closeSync(fd);
1058
- }
1059
- }
1060
-
1061
- function sleep(ms: number): Promise<void> {
1062
- return new Promise((resolve) => setTimeout(resolve, ms));
1063
- }
1064
-
1065
- // ============================================================================
1066
- // Path Helpers
1067
- // ============================================================================
1068
-
1069
- /**
1070
- * Sanitize a filesystem path for use as a Claude project directory name
1071
- */
1072
- function sanitizePathForClaude(repoPath: string): string {
1073
- // Claude uses a hash-based directory naming scheme
1074
- return crypto.createHash('sha256').update(repoPath).digest('hex').slice(0, 16);
1075
- }
1076
-
1077
- // ============================================================================
1078
- // Registration
1079
- // ============================================================================
1080
-
1081
- /**
1082
- * Create and return a new Claude Code agent instance
1083
- */
1084
- export function createClaudeCodeAgent(): ClaudeCodeAgent {
1085
- return new ClaudeCodeAgent();
1086
- }
1087
-
1088
- // Auto-register when imported
1089
- registerAgent(AGENT_NAMES.CLAUDE_CODE, () => new ClaudeCodeAgent());