@vellumai/assistant 0.5.5 → 0.5.7

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 (382) hide show
  1. package/.env.example +16 -2
  2. package/ARCHITECTURE.md +6 -75
  3. package/Dockerfile +4 -5
  4. package/README.md +0 -2
  5. package/bun.lock +0 -414
  6. package/docs/architecture/keychain-broker.md +45 -240
  7. package/docs/architecture/security.md +0 -17
  8. package/docs/credential-execution-service.md +2 -2
  9. package/node_modules/@vellumai/ces-contracts/package.json +1 -0
  10. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +119 -0
  11. package/node_modules/@vellumai/credential-storage/package.json +1 -0
  12. package/node_modules/@vellumai/egress-proxy/package.json +1 -0
  13. package/package.json +2 -3
  14. package/src/__tests__/actor-token-service.test.ts +1 -2
  15. package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
  16. package/src/__tests__/browser-skill-endstate.test.ts +6 -5
  17. package/src/__tests__/btw-routes.test.ts +0 -39
  18. package/src/__tests__/call-domain.test.ts +0 -128
  19. package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
  20. package/src/__tests__/channel-approval-routes.test.ts +0 -5
  21. package/src/__tests__/channel-readiness-service.test.ts +1 -60
  22. package/src/__tests__/checker.test.ts +4 -2
  23. package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
  24. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  25. package/src/__tests__/config-schema.test.ts +3 -3
  26. package/src/__tests__/context-window-manager.test.ts +78 -0
  27. package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
  28. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  29. package/src/__tests__/conversation-skill-tools.test.ts +0 -54
  30. package/src/__tests__/conversation-title-service.test.ts +117 -1
  31. package/src/__tests__/credential-execution-feature-gates.test.ts +28 -14
  32. package/src/__tests__/credential-execution-managed-contract.test.ts +33 -18
  33. package/src/__tests__/credential-security-e2e.test.ts +0 -66
  34. package/src/__tests__/credential-security-invariants.test.ts +4 -45
  35. package/src/__tests__/credentials-cli.test.ts +78 -0
  36. package/src/__tests__/db-migration-rollback.test.ts +2015 -1
  37. package/src/__tests__/docker-signing-key-bootstrap.test.ts +98 -0
  38. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
  39. package/src/__tests__/guardian-routing-state.test.ts +0 -5
  40. package/src/__tests__/host-shell-tool.test.ts +6 -7
  41. package/src/__tests__/http-user-message-parity.test.ts +3 -103
  42. package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
  43. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
  44. package/src/__tests__/intent-routing.test.ts +0 -13
  45. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
  46. package/src/__tests__/keychain-broker-client.test.ts +161 -22
  47. package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
  48. package/src/__tests__/memory-regressions.test.ts +8 -30
  49. package/src/__tests__/migration-export-http.test.ts +2 -2
  50. package/src/__tests__/migration-import-commit-http.test.ts +2 -2
  51. package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
  52. package/src/__tests__/migration-validate-http.test.ts +2 -2
  53. package/src/__tests__/non-member-access-request.test.ts +0 -5
  54. package/src/__tests__/notification-decision-fallback.test.ts +4 -0
  55. package/src/__tests__/notification-decision-identity.test.ts +4 -0
  56. package/src/__tests__/permission-types.test.ts +1 -0
  57. package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
  58. package/src/__tests__/qdrant-manager.test.ts +28 -2
  59. package/src/__tests__/registry.test.ts +0 -6
  60. package/src/__tests__/require-fresh-approval.test.ts +4 -0
  61. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
  62. package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
  63. package/src/__tests__/secure-keys.test.ts +83 -263
  64. package/src/__tests__/shell-identity.test.ts +96 -6
  65. package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
  66. package/src/__tests__/skill-feature-flags.test.ts +46 -45
  67. package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
  68. package/src/__tests__/skill-load-inline-command.test.ts +8 -12
  69. package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
  70. package/src/__tests__/skill-load-tool.test.ts +0 -2
  71. package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
  72. package/src/__tests__/skills.test.ts +0 -2
  73. package/src/__tests__/slack-inbound-verification.test.ts +0 -4
  74. package/src/__tests__/suggestion-routes.test.ts +1 -32
  75. package/src/__tests__/system-prompt.test.ts +0 -1
  76. package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -0
  77. package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
  78. package/src/__tests__/tool-executor.test.ts +4 -0
  79. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
  80. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
  81. package/src/__tests__/update-bulletin.test.ts +0 -2
  82. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
  83. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -6
  84. package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
  85. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +218 -0
  86. package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
  87. package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
  88. package/src/calls/audio-store.test.ts +97 -0
  89. package/src/calls/audio-store.ts +205 -0
  90. package/src/calls/call-controller.ts +85 -7
  91. package/src/calls/call-domain.ts +3 -0
  92. package/src/calls/call-store.ts +10 -3
  93. package/src/calls/fish-audio-client.ts +117 -0
  94. package/src/calls/relay-server.ts +27 -0
  95. package/src/calls/twilio-routes.ts +2 -1
  96. package/src/calls/types.ts +1 -0
  97. package/src/calls/voice-ingress-preflight.ts +0 -42
  98. package/src/calls/voice-quality.ts +26 -5
  99. package/src/calls/voice-session-bridge.ts +6 -12
  100. package/src/cli/commands/config.ts +1 -4
  101. package/src/cli/commands/conversations.ts +0 -18
  102. package/src/cli/commands/credentials.ts +34 -4
  103. package/src/cli/commands/oauth/index.ts +7 -0
  104. package/src/cli/commands/oauth/platform.ts +179 -0
  105. package/src/cli/commands/platform.ts +3 -3
  106. package/src/config/assistant-feature-flags.ts +186 -5
  107. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  108. package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
  109. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  110. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
  111. package/src/config/bundled-tool-registry.ts +1 -11
  112. package/src/config/env-registry.ts +1 -1
  113. package/src/config/env.ts +16 -16
  114. package/src/config/feature-flag-registry.json +48 -16
  115. package/src/config/loader.ts +98 -31
  116. package/src/config/schema.ts +4 -25
  117. package/src/config/schemas/calls.ts +13 -0
  118. package/src/config/schemas/fish-audio.ts +39 -0
  119. package/src/config/schemas/memory.ts +0 -4
  120. package/src/config/schemas/platform.ts +1 -1
  121. package/src/config/schemas/security.ts +4 -4
  122. package/src/config/types.ts +0 -1
  123. package/src/contacts/contact-store.ts +39 -0
  124. package/src/contacts/types.ts +2 -0
  125. package/src/context/window-manager.ts +53 -2
  126. package/src/credential-execution/approval-bridge.ts +1 -0
  127. package/src/credential-execution/executable-discovery.ts +28 -4
  128. package/src/credential-execution/feature-gates.ts +16 -0
  129. package/src/credential-execution/process-manager.ts +38 -0
  130. package/src/daemon/assistant-attachments.ts +9 -0
  131. package/src/daemon/config-watcher.ts +6 -4
  132. package/src/daemon/conversation-agent-loop.ts +0 -60
  133. package/src/daemon/conversation-memory.ts +0 -117
  134. package/src/daemon/conversation-runtime-assembly.ts +0 -2
  135. package/src/daemon/conversation-tool-setup.ts +0 -105
  136. package/src/daemon/conversation.ts +10 -1
  137. package/src/daemon/handlers/config-vercel.ts +92 -0
  138. package/src/daemon/handlers/conversations.ts +0 -11
  139. package/src/daemon/handlers/skills.ts +2 -15
  140. package/src/daemon/install-symlink.ts +195 -0
  141. package/src/daemon/lifecycle.ts +229 -96
  142. package/src/daemon/message-types/conversations.ts +3 -4
  143. package/src/daemon/message-types/diagnostics.ts +3 -22
  144. package/src/daemon/message-types/messages.ts +0 -2
  145. package/src/daemon/message-types/upgrades.ts +8 -0
  146. package/src/daemon/server.ts +30 -92
  147. package/src/events/domain-events.ts +2 -1
  148. package/src/followups/followup-store.ts +5 -2
  149. package/src/inbound/platform-callback-registration.ts +3 -3
  150. package/src/instrument.ts +8 -5
  151. package/src/memory/conversation-crud.ts +0 -236
  152. package/src/memory/conversation-title-service.ts +76 -11
  153. package/src/memory/db-init.ts +15 -11
  154. package/src/memory/indexer.ts +15 -106
  155. package/src/memory/items-extractor.ts +15 -1
  156. package/src/memory/job-handlers/conversation-starters.ts +4 -1
  157. package/src/memory/job-handlers/embedding.ts +0 -79
  158. package/src/memory/job-utils.ts +1 -1
  159. package/src/memory/jobs-store.ts +30 -13
  160. package/src/memory/jobs-worker.ts +31 -27
  161. package/src/memory/migrations/001-job-deferrals.ts +19 -0
  162. package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
  163. package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
  164. package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
  165. package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
  166. package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
  167. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
  168. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
  169. package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
  170. package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
  171. package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
  172. package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
  173. package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
  174. package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
  175. package/src/memory/migrations/116-messages-fts.ts +106 -1
  176. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
  177. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
  178. package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
  179. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
  180. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
  181. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
  182. package/src/memory/migrations/141-rename-verification-table.ts +54 -0
  183. package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
  184. package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
  185. package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
  186. package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
  187. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
  188. package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
  189. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
  190. package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
  191. package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
  192. package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
  193. package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
  194. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
  195. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
  196. package/src/memory/migrations/189-drop-simplified-memory.ts +42 -0
  197. package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
  198. package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
  199. package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
  200. package/src/memory/migrations/index.ts +5 -3
  201. package/src/memory/migrations/registry.ts +90 -0
  202. package/src/memory/migrations/validate-migration-state.ts +137 -11
  203. package/src/memory/qdrant-circuit-breaker.ts +9 -0
  204. package/src/memory/qdrant-client.ts +4 -6
  205. package/src/memory/qdrant-manager.ts +64 -7
  206. package/src/memory/schema/calls.ts +1 -0
  207. package/src/memory/schema/contacts.ts +1 -0
  208. package/src/memory/schema/conversations.ts +0 -3
  209. package/src/memory/schema/index.ts +0 -2
  210. package/src/messaging/draft-store.ts +2 -2
  211. package/src/notifications/decision-engine.ts +4 -1
  212. package/src/oauth/connection-resolver.ts +6 -4
  213. package/src/permissions/checker.ts +0 -38
  214. package/src/permissions/defaults.ts +3 -3
  215. package/src/permissions/shell-identity.ts +76 -22
  216. package/src/permissions/trust-client.ts +2 -13
  217. package/src/permissions/trust-store.ts +8 -3
  218. package/src/permissions/types.ts +4 -2
  219. package/src/platform/client.ts +35 -7
  220. package/src/prompts/persona-resolver.ts +138 -0
  221. package/src/prompts/system-prompt.ts +36 -4
  222. package/src/prompts/templates/users/default.md +1 -0
  223. package/src/providers/registry.ts +27 -40
  224. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
  225. package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
  226. package/src/runtime/auth/external-assistant-id.ts +13 -59
  227. package/src/runtime/auth/route-policy.ts +29 -1
  228. package/src/runtime/auth/token-service.ts +53 -15
  229. package/src/runtime/channel-readiness-service.ts +1 -16
  230. package/src/runtime/http-server.ts +29 -2
  231. package/src/runtime/middleware/error-handler.ts +1 -9
  232. package/src/runtime/routes/audio-routes.ts +40 -0
  233. package/src/runtime/routes/btw-routes.ts +0 -17
  234. package/src/runtime/routes/conversation-management-routes.ts +0 -36
  235. package/src/runtime/routes/conversation-query-routes.ts +106 -2
  236. package/src/runtime/routes/conversation-routes.ts +4 -43
  237. package/src/runtime/routes/diagnostics-routes.ts +1 -477
  238. package/src/runtime/routes/identity-routes.ts +18 -29
  239. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
  240. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
  241. package/src/runtime/routes/integrations/vercel.ts +89 -0
  242. package/src/runtime/routes/log-export-routes.ts +5 -0
  243. package/src/runtime/routes/memory-item-routes.test.ts +221 -3
  244. package/src/runtime/routes/memory-item-routes.ts +144 -4
  245. package/src/runtime/routes/migration-rollback-routes.ts +209 -0
  246. package/src/runtime/routes/migration-routes.ts +17 -1
  247. package/src/runtime/routes/notification-routes.ts +58 -0
  248. package/src/runtime/routes/schedule-routes.ts +65 -0
  249. package/src/runtime/routes/settings-routes.ts +41 -1
  250. package/src/runtime/routes/tts-routes.ts +86 -0
  251. package/src/runtime/routes/upgrade-broadcast-routes.ts +175 -0
  252. package/src/runtime/routes/workspace-commit-routes.ts +62 -0
  253. package/src/runtime/routes/workspace-routes.test.ts +22 -1
  254. package/src/runtime/routes/workspace-routes.ts +1 -1
  255. package/src/runtime/routes/workspace-utils.ts +86 -2
  256. package/src/schedule/schedule-store.ts +0 -21
  257. package/src/security/ces-credential-client.ts +59 -22
  258. package/src/security/ces-rpc-credential-backend.ts +85 -0
  259. package/src/security/credential-backend.ts +12 -88
  260. package/src/security/keychain-broker-client.ts +10 -2
  261. package/src/security/secure-keys.ts +94 -113
  262. package/src/skills/catalog-install.ts +13 -7
  263. package/src/skills/inline-command-render.ts +5 -1
  264. package/src/skills/inline-command-runner.ts +30 -2
  265. package/src/telemetry/usage-telemetry-reporter.ts +4 -2
  266. package/src/tools/calls/call-start.ts +1 -0
  267. package/src/tools/executor.ts +0 -4
  268. package/src/tools/memory/handlers.ts +1 -129
  269. package/src/tools/network/script-proxy/session-manager.ts +19 -4
  270. package/src/tools/network/web-fetch.ts +3 -1
  271. package/src/tools/permission-checker.ts +18 -0
  272. package/src/tools/skills/execute.ts +1 -1
  273. package/src/tools/skills/load.ts +9 -2
  274. package/src/tools/types.ts +0 -8
  275. package/src/util/errors.ts +0 -12
  276. package/src/util/platform.ts +8 -55
  277. package/src/util/xml.ts +8 -0
  278. package/src/workspace/git-service.ts +5 -2
  279. package/src/workspace/heartbeat-service.ts +5 -24
  280. package/src/workspace/migrations/001-avatar-rename.ts +15 -0
  281. package/src/workspace/migrations/003-seed-device-id.ts +17 -1
  282. package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
  283. package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
  284. package/src/workspace/migrations/006-services-config.ts +49 -0
  285. package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
  286. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
  287. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
  288. package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
  289. package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
  290. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
  291. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
  292. package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
  293. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
  294. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
  295. package/src/workspace/migrations/017-seed-persona-dirs.ts +95 -0
  296. package/src/workspace/migrations/migrate-to-workspace-volume.ts +23 -1
  297. package/src/workspace/migrations/registry.ts +8 -0
  298. package/src/workspace/migrations/runner.ts +106 -2
  299. package/src/workspace/migrations/types.ts +4 -0
  300. package/src/__tests__/archive-recall.test.ts +0 -560
  301. package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
  302. package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
  303. package/src/__tests__/conversation-memory-dirty-tail.test.ts +0 -150
  304. package/src/__tests__/conversation-switch-memory-reduction.test.ts +0 -474
  305. package/src/__tests__/db-memory-archive-migration.test.ts +0 -372
  306. package/src/__tests__/db-memory-brief-state-migration.test.ts +0 -213
  307. package/src/__tests__/db-memory-reducer-checkpoints.test.ts +0 -273
  308. package/src/__tests__/diagnostics-export.test.ts +0 -288
  309. package/src/__tests__/local-gateway-health.test.ts +0 -209
  310. package/src/__tests__/memory-brief-open-loops.test.ts +0 -530
  311. package/src/__tests__/memory-brief-time.test.ts +0 -285
  312. package/src/__tests__/memory-brief-wrapper.test.ts +0 -311
  313. package/src/__tests__/memory-chunk-archive.test.ts +0 -400
  314. package/src/__tests__/memory-chunk-dual-write.test.ts +0 -453
  315. package/src/__tests__/memory-episode-archive.test.ts +0 -370
  316. package/src/__tests__/memory-episode-dual-write.test.ts +0 -626
  317. package/src/__tests__/memory-observation-archive.test.ts +0 -375
  318. package/src/__tests__/memory-observation-dual-write.test.ts +0 -318
  319. package/src/__tests__/memory-reducer-job.test.ts +0 -538
  320. package/src/__tests__/memory-reducer-scheduling.test.ts +0 -473
  321. package/src/__tests__/memory-reducer-store.test.ts +0 -728
  322. package/src/__tests__/memory-reducer-types.test.ts +0 -707
  323. package/src/__tests__/memory-reducer.test.ts +0 -704
  324. package/src/__tests__/memory-simplified-config.test.ts +0 -281
  325. package/src/__tests__/secret-ingress-handler.test.ts +0 -120
  326. package/src/__tests__/simplified-memory-e2e.test.ts +0 -666
  327. package/src/__tests__/simplified-memory-runtime.test.ts +0 -616
  328. package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
  329. package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
  330. package/src/__tests__/swarm-orchestrator.test.ts +0 -463
  331. package/src/__tests__/swarm-plan-validator.test.ts +0 -384
  332. package/src/__tests__/swarm-recursion.test.ts +0 -197
  333. package/src/__tests__/swarm-router-planner.test.ts +0 -234
  334. package/src/__tests__/swarm-tool.test.ts +0 -185
  335. package/src/__tests__/swarm-worker-backend.test.ts +0 -144
  336. package/src/__tests__/swarm-worker-runner.test.ts +0 -288
  337. package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
  338. package/src/commands/cc-command-registry.ts +0 -248
  339. package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
  340. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
  341. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
  342. package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
  343. package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
  344. package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
  345. package/src/config/schemas/memory-simplified.ts +0 -101
  346. package/src/config/schemas/swarm.ts +0 -82
  347. package/src/logfire.ts +0 -135
  348. package/src/memory/archive-recall.ts +0 -516
  349. package/src/memory/archive-store.ts +0 -400
  350. package/src/memory/brief-formatting.ts +0 -33
  351. package/src/memory/brief-open-loops.ts +0 -266
  352. package/src/memory/brief-time.ts +0 -162
  353. package/src/memory/brief.ts +0 -75
  354. package/src/memory/job-handlers/backfill-simplified-memory.ts +0 -462
  355. package/src/memory/job-handlers/reduce-conversation-memory.ts +0 -229
  356. package/src/memory/migrations/185-memory-brief-state.ts +0 -52
  357. package/src/memory/migrations/186-memory-archive.ts +0 -109
  358. package/src/memory/migrations/187-memory-reducer-checkpoints.ts +0 -19
  359. package/src/memory/reducer-scheduler.ts +0 -242
  360. package/src/memory/reducer-store.ts +0 -271
  361. package/src/memory/reducer-types.ts +0 -106
  362. package/src/memory/reducer.ts +0 -467
  363. package/src/memory/schema/memory-archive.ts +0 -121
  364. package/src/memory/schema/memory-brief.ts +0 -55
  365. package/src/runtime/local-gateway-health.ts +0 -275
  366. package/src/security/secret-ingress.ts +0 -68
  367. package/src/swarm/backend-claude-code.ts +0 -225
  368. package/src/swarm/checkpoint.ts +0 -137
  369. package/src/swarm/graph-utils.ts +0 -53
  370. package/src/swarm/index.ts +0 -55
  371. package/src/swarm/limits.ts +0 -66
  372. package/src/swarm/orchestrator.ts +0 -424
  373. package/src/swarm/plan-validator.ts +0 -117
  374. package/src/swarm/router-planner.ts +0 -162
  375. package/src/swarm/router-prompts.ts +0 -39
  376. package/src/swarm/synthesizer.ts +0 -81
  377. package/src/swarm/types.ts +0 -72
  378. package/src/swarm/worker-backend.ts +0 -131
  379. package/src/swarm/worker-prompts.ts +0 -80
  380. package/src/swarm/worker-runner.ts +0 -170
  381. package/src/tools/claude-code/claude-code.ts +0 -610
  382. package/src/tools/swarm/delegate.ts +0 -205
@@ -1,288 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import type { SwarmTaskNode } from "../swarm/types.js";
4
- import type {
5
- SwarmWorkerBackend,
6
- SwarmWorkerBackendInput,
7
- } from "../swarm/worker-backend.js";
8
- import {
9
- buildWorkerPrompt,
10
- parseWorkerOutput,
11
- } from "../swarm/worker-prompts.js";
12
- import type { WorkerStatusKind } from "../swarm/worker-runner.js";
13
- import { runWorkerTask } from "../swarm/worker-runner.js";
14
-
15
- function makeTask(overrides?: Partial<SwarmTaskNode>): SwarmTaskNode {
16
- return {
17
- id: "test-task",
18
- role: "coder",
19
- objective: "Write a function",
20
- dependencies: [],
21
- ...overrides,
22
- };
23
- }
24
-
25
- function makeBackend(
26
- overrides?: Partial<SwarmWorkerBackend>,
27
- ): SwarmWorkerBackend {
28
- return {
29
- name: "test-backend",
30
- isAvailable: () => true,
31
- runTask: async () => ({
32
- success: true,
33
- output:
34
- '```json\n{"summary":"Done","artifacts":[],"issues":[],"nextSteps":[]}\n```',
35
- durationMs: 100,
36
- }),
37
- ...overrides,
38
- };
39
- }
40
-
41
- describe("runWorkerTask", () => {
42
- test("returns completed result on success", async () => {
43
- const result = await runWorkerTask({
44
- task: makeTask(),
45
- backend: makeBackend(),
46
- workingDir: "/tmp",
47
- timeoutMs: 5000,
48
- });
49
- expect(result.status).toBe("completed");
50
- expect(result.taskId).toBe("test-task");
51
- expect(result.summary).toBe("Done");
52
- expect(result.durationMs).toBe(100);
53
- });
54
-
55
- test("returns failed result when backend is unavailable", async () => {
56
- const result = await runWorkerTask({
57
- task: makeTask(),
58
- backend: makeBackend({ isAvailable: () => false }),
59
- workingDir: "/tmp",
60
- timeoutMs: 5000,
61
- });
62
- expect(result.status).toBe("failed");
63
- expect(result.issues[0]).toContain("unavailable");
64
- });
65
-
66
- test("returns failed result when backend returns failure", async () => {
67
- const result = await runWorkerTask({
68
- task: makeTask(),
69
- backend: makeBackend({
70
- runTask: async () => ({
71
- success: false,
72
- output: "Something went wrong",
73
- failureReason: "timeout",
74
- durationMs: 900,
75
- }),
76
- }),
77
- workingDir: "/tmp",
78
- timeoutMs: 5000,
79
- });
80
- expect(result.status).toBe("failed");
81
- expect(result.issues).toContain("timeout");
82
- expect(result.durationMs).toBe(900);
83
- });
84
-
85
- test("returns failed result when backend throws", async () => {
86
- const result = await runWorkerTask({
87
- task: makeTask(),
88
- backend: makeBackend({
89
- runTask: async () => {
90
- throw new Error("Boom");
91
- },
92
- }),
93
- workingDir: "/tmp",
94
- timeoutMs: 5000,
95
- });
96
- expect(result.status).toBe("failed");
97
- expect(result.summary).toContain("Boom");
98
- });
99
-
100
- test("emits status callbacks in order", async () => {
101
- const statuses: WorkerStatusKind[] = [];
102
- await runWorkerTask({
103
- task: makeTask(),
104
- backend: makeBackend(),
105
- workingDir: "/tmp",
106
- timeoutMs: 5000,
107
- onStatus: (_taskId, status) => statuses.push(status),
108
- });
109
- expect(statuses).toEqual(["queued", "running", "completed"]);
110
- });
111
-
112
- test("emits queued then failed when backend unavailable", async () => {
113
- const statuses: WorkerStatusKind[] = [];
114
- await runWorkerTask({
115
- task: makeTask(),
116
- backend: makeBackend({ isAvailable: () => false }),
117
- workingDir: "/tmp",
118
- timeoutMs: 5000,
119
- onStatus: (_taskId, status) => statuses.push(status),
120
- });
121
- expect(statuses).toEqual(["queued", "failed"]);
122
- });
123
-
124
- test("continues execution when onStatus callback throws", async () => {
125
- const result = await runWorkerTask({
126
- task: makeTask(),
127
- backend: makeBackend(),
128
- workingDir: "/tmp",
129
- timeoutMs: 5000,
130
- onStatus: () => {
131
- throw new Error("status callback failed");
132
- },
133
- });
134
- expect(result.status).toBe("completed");
135
- expect(result.summary).toBe("Done");
136
- });
137
-
138
- test("maps role to correct profile in prompt", async () => {
139
- let capturedInput: SwarmWorkerBackendInput | null = null;
140
- await runWorkerTask({
141
- task: makeTask({ role: "researcher" }),
142
- backend: makeBackend({
143
- runTask: async (input) => {
144
- capturedInput = input;
145
- return { success: true, output: "ok", durationMs: 50 };
146
- },
147
- }),
148
- workingDir: "/tmp",
149
- timeoutMs: 5000,
150
- });
151
- expect(capturedInput!.profile).toBe("researcher");
152
- });
153
-
154
- test("includes dependency outputs in prompt", async () => {
155
- let capturedInput: SwarmWorkerBackendInput | null = null;
156
- await runWorkerTask({
157
- task: makeTask(),
158
- dependencyOutputs: [{ taskId: "dep-1", summary: "Research complete" }],
159
- backend: makeBackend({
160
- runTask: async (input) => {
161
- capturedInput = input;
162
- return { success: true, output: "ok", durationMs: 50 };
163
- },
164
- }),
165
- workingDir: "/tmp",
166
- timeoutMs: 5000,
167
- });
168
- expect(capturedInput!.prompt).toContain("dep-1");
169
- expect(capturedInput!.prompt).toContain("Research complete");
170
- });
171
- });
172
-
173
- describe("parseWorkerOutput", () => {
174
- test("parses valid fenced JSON", () => {
175
- const raw =
176
- 'Some preamble\n```json\n{"summary":"Done","artifacts":["file.ts"],"issues":[],"nextSteps":["test"]}\n```\nSome epilogue';
177
- const result = parseWorkerOutput(raw);
178
- expect(result.summary).toBe("Done");
179
- expect(result.artifacts).toEqual(["file.ts"]);
180
- expect(result.nextSteps).toEqual(["test"]);
181
- });
182
-
183
- test("falls back to raw summary on invalid JSON", () => {
184
- const raw = "```json\n{invalid json}\n```";
185
- const result = parseWorkerOutput(raw);
186
- expect(result.summary).toBe(raw.slice(0, 500));
187
- expect(result.artifacts).toEqual([]);
188
- });
189
-
190
- test("falls back to raw summary when no JSON block", () => {
191
- const raw = "Just a plain text output without any JSON.";
192
- const result = parseWorkerOutput(raw);
193
- expect(result.summary).toBe(raw);
194
- expect(result.artifacts).toEqual([]);
195
- });
196
-
197
- test("truncates long raw output to 500 chars", () => {
198
- const raw = "x".repeat(1000);
199
- const result = parseWorkerOutput(raw);
200
- expect(result.summary.length).toBe(500);
201
- });
202
-
203
- test("uses the final fenced JSON block when multiple are present", () => {
204
- const raw = [
205
- "```json",
206
- '{"summary":"intermediate","artifacts":["a.ts"],"issues":[],"nextSteps":[]}',
207
- "```",
208
- "",
209
- "```json",
210
- '{"summary":"final","artifacts":["b.ts"],"issues":["warn"],"nextSteps":["ship"]}',
211
- "```",
212
- ].join("\n");
213
- const result = parseWorkerOutput(raw);
214
- expect(result.summary).toBe("final");
215
- expect(result.artifacts).toEqual(["b.ts"]);
216
- expect(result.issues).toEqual(["warn"]);
217
- expect(result.nextSteps).toEqual(["ship"]);
218
- });
219
-
220
- test("skips trailing non-contract JSON and picks the last valid block", () => {
221
- const raw = [
222
- "```json",
223
- '{"summary":"real result","artifacts":["out.ts"],"issues":[],"nextSteps":["deploy"]}',
224
- "```",
225
- "",
226
- "Here is an example config:",
227
- "```json",
228
- '{"port":3000,"debug":true}',
229
- "```",
230
- ].join("\n");
231
- const result = parseWorkerOutput(raw);
232
- expect(result.summary).toBe("real result");
233
- expect(result.artifacts).toEqual(["out.ts"]);
234
- expect(result.nextSteps).toEqual(["deploy"]);
235
- });
236
-
237
- test("skips trailing malformed JSON and picks an earlier valid block", () => {
238
- const raw = [
239
- "```json",
240
- '{"summary":"good","artifacts":[],"issues":[],"nextSteps":[]}',
241
- "```",
242
- "",
243
- "```json",
244
- "{this is not valid json}",
245
- "```",
246
- ].join("\n");
247
- const result = parseWorkerOutput(raw);
248
- expect(result.summary).toBe("good");
249
- });
250
- });
251
-
252
- describe("buildWorkerPrompt", () => {
253
- test("includes role and objective", () => {
254
- const prompt = buildWorkerPrompt({
255
- role: "coder",
256
- objective: "Build feature X",
257
- });
258
- expect(prompt).toContain("coder");
259
- expect(prompt).toContain("Build feature X");
260
- });
261
-
262
- test("includes upstream context when provided", () => {
263
- const prompt = buildWorkerPrompt({
264
- role: "coder",
265
- objective: "Build it",
266
- upstreamContext: "This is a React project",
267
- });
268
- expect(prompt).toContain("This is a React project");
269
- });
270
-
271
- test("includes dependency outputs when provided", () => {
272
- const prompt = buildWorkerPrompt({
273
- role: "coder",
274
- objective: "Build it",
275
- dependencyOutputs: [
276
- { taskId: "research", summary: "Found the API docs" },
277
- ],
278
- });
279
- expect(prompt).toContain("research");
280
- expect(prompt).toContain("Found the API docs");
281
- });
282
-
283
- test("includes output contract instructions", () => {
284
- const prompt = buildWorkerPrompt({ role: "coder", objective: "Test" });
285
- expect(prompt).toContain("```json");
286
- expect(prompt).toContain("summary");
287
- });
288
- });
@@ -1,396 +0,0 @@
1
- import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
5
-
6
- import {
7
- discoverCCCommands,
8
- getCCCommand,
9
- invalidateCCCommandCache,
10
- loadCCCommandTemplate,
11
- } from "../cc-command-registry.js";
12
-
13
- let tmpDir: string;
14
-
15
- beforeEach(() => {
16
- tmpDir = mkdtempSync(join(tmpdir(), "cc-cmd-test-"));
17
- invalidateCCCommandCache();
18
- });
19
-
20
- afterEach(() => {
21
- rmSync(tmpDir, { recursive: true, force: true });
22
- invalidateCCCommandCache();
23
- });
24
-
25
- /** Helper to create a .claude/commands/ directory with markdown files. */
26
- function createCommandsDir(base: string, files: Record<string, string>): void {
27
- const commandsDir = join(base, ".claude", "commands");
28
- mkdirSync(commandsDir, { recursive: true });
29
- for (const [name, content] of Object.entries(files)) {
30
- writeFileSync(join(commandsDir, name), content, "utf-8");
31
- }
32
- }
33
-
34
- describe("discoverCCCommands", () => {
35
- test("discovers commands in .claude/commands/", () => {
36
- createCommandsDir(tmpDir, {
37
- "hello.md": "# Hello World\nThis is the hello command.",
38
- "deploy.md": "Deploy the application to production.",
39
- });
40
-
41
- const registry = discoverCCCommands(tmpDir);
42
- expect(registry.entries.size).toBe(2);
43
-
44
- const hello = registry.entries.get("hello");
45
- expect(hello).toBeDefined();
46
- expect(hello!.name).toBe("hello");
47
- expect(hello!.summary).toBe("Hello World");
48
- expect(hello!.source).toBe(tmpDir);
49
-
50
- const deploy = registry.entries.get("deploy");
51
- expect(deploy).toBeDefined();
52
- expect(deploy!.name).toBe("deploy");
53
- expect(deploy!.summary).toBe("Deploy the application to production.");
54
- });
55
-
56
- test("child directory commands override parent on name collisions", () => {
57
- // Create parent commands
58
- createCommandsDir(tmpDir, {
59
- "shared.md": "Parent version of shared command.",
60
- });
61
-
62
- // Create child directory with overriding command
63
- const childDir = join(tmpDir, "project");
64
- mkdirSync(childDir, { recursive: true });
65
- createCommandsDir(childDir, {
66
- "shared.md": "Child version of shared command.",
67
- });
68
-
69
- const registry = discoverCCCommands(childDir);
70
- const shared = registry.entries.get("shared");
71
- expect(shared).toBeDefined();
72
- expect(shared!.summary).toBe("Child version of shared command.");
73
- expect(shared!.source).toBe(childDir);
74
- });
75
-
76
- test("invalid filenames are skipped", () => {
77
- createCommandsDir(tmpDir, {
78
- "valid-name.md": "A valid command.",
79
- ".hidden.md": "Hidden file should be skipped.",
80
- "-starts-with-dash.md": "Invalid start character.",
81
- });
82
-
83
- const registry = discoverCCCommands(tmpDir);
84
- expect(registry.entries.size).toBe(1);
85
- expect(registry.entries.has("valid-name")).toBe(true);
86
- expect(registry.entries.has(".hidden")).toBe(false);
87
- expect(registry.entries.has("-starts-with-dash")).toBe(false);
88
- });
89
-
90
- test("non-.md files are ignored", () => {
91
- createCommandsDir(tmpDir, {
92
- "readme.txt": "Not a markdown file.",
93
- "command.md": "A real command.",
94
- "notes.json": "{}",
95
- });
96
-
97
- const registry = discoverCCCommands(tmpDir);
98
- expect(registry.entries.size).toBe(1);
99
- expect(registry.entries.has("command")).toBe(true);
100
- });
101
-
102
- test("empty directory returns empty registry", () => {
103
- const commandsDir = join(tmpDir, ".claude", "commands");
104
- mkdirSync(commandsDir, { recursive: true });
105
-
106
- const registry = discoverCCCommands(tmpDir);
107
- expect(registry.entries.size).toBe(0);
108
- });
109
-
110
- test("no .claude/commands/ directory returns empty registry", () => {
111
- const registry = discoverCCCommands(tmpDir);
112
- expect(registry.entries.size).toBe(0);
113
- });
114
-
115
- test("commands from multiple ancestor levels are merged", () => {
116
- // Parent has a unique command
117
- createCommandsDir(tmpDir, {
118
- "parent-only.md": "Only in parent.",
119
- });
120
-
121
- // Child has a different command
122
- const childDir = join(tmpDir, "child");
123
- mkdirSync(childDir, { recursive: true });
124
- createCommandsDir(childDir, {
125
- "child-only.md": "Only in child.",
126
- });
127
-
128
- const registry = discoverCCCommands(childDir);
129
- expect(registry.entries.size).toBe(2);
130
- expect(registry.entries.has("parent-only")).toBe(true);
131
- expect(registry.entries.has("child-only")).toBe(true);
132
- });
133
- });
134
-
135
- describe("caching", () => {
136
- test("cache returns same instance within TTL", () => {
137
- createCommandsDir(tmpDir, {
138
- "test.md": "Test command.",
139
- });
140
-
141
- const first = discoverCCCommands(tmpDir);
142
- const second = discoverCCCommands(tmpDir);
143
- expect(first).toBe(second); // same object reference
144
- });
145
-
146
- test("invalidateCCCommandCache forces re-discovery", () => {
147
- createCommandsDir(tmpDir, {
148
- "test.md": "Test command.",
149
- });
150
-
151
- const first = discoverCCCommands(tmpDir);
152
-
153
- invalidateCCCommandCache();
154
-
155
- const second = discoverCCCommands(tmpDir);
156
- expect(first).not.toBe(second); // different object reference
157
- expect(second.entries.size).toBe(1);
158
- });
159
-
160
- test("expired TTL forces re-discovery", () => {
161
- createCommandsDir(tmpDir, {
162
- "test.md": "Test command.",
163
- });
164
-
165
- // Use a very short TTL
166
- const first = discoverCCCommands(tmpDir, 0);
167
- const second = discoverCCCommands(tmpDir, 0);
168
- expect(first).not.toBe(second); // different object reference due to expired TTL
169
- });
170
- });
171
-
172
- describe("getCCCommand", () => {
173
- test("looks up command by name (case-insensitive)", () => {
174
- createCommandsDir(tmpDir, {
175
- "MyCommand.md": "My command description.",
176
- });
177
-
178
- const entry = getCCCommand(tmpDir, "mycommand");
179
- expect(entry).toBeDefined();
180
- expect(entry!.name).toBe("MyCommand");
181
-
182
- const entryUpper = getCCCommand(tmpDir, "MYCOMMAND");
183
- expect(entryUpper).toBeDefined();
184
- expect(entryUpper!.name).toBe("MyCommand");
185
- });
186
-
187
- test("returns undefined for non-existent command", () => {
188
- createCommandsDir(tmpDir, {
189
- "exists.md": "I exist.",
190
- });
191
-
192
- const entry = getCCCommand(tmpDir, "nonexistent");
193
- expect(entry).toBeUndefined();
194
- });
195
- });
196
-
197
- describe("loadCCCommandTemplate", () => {
198
- test("reads full file content at execution time", () => {
199
- const fullContent =
200
- "---\ntitle: Test\n---\n\n# Test Command\n\nThis is the full template body.\n\n## Arguments\n- arg1: required\n- arg2: optional\n";
201
- createCommandsDir(tmpDir, {
202
- "test.md": fullContent,
203
- });
204
-
205
- const registry = discoverCCCommands(tmpDir);
206
- const entry = registry.entries.get("test")!;
207
- expect(entry).toBeDefined();
208
-
209
- const template = loadCCCommandTemplate(entry);
210
- expect(template).toBe(fullContent);
211
- });
212
- });
213
-
214
- describe("summary extraction", () => {
215
- test("skips YAML frontmatter", () => {
216
- createCommandsDir(tmpDir, {
217
- "with-frontmatter.md":
218
- "---\ntitle: My Command\nauthor: test\n---\n\nActual summary line.",
219
- });
220
-
221
- const registry = discoverCCCommands(tmpDir);
222
- const entry = registry.entries.get("with-frontmatter");
223
- expect(entry).toBeDefined();
224
- expect(entry!.summary).toBe("Actual summary line.");
225
- });
226
-
227
- test("strips heading markers", () => {
228
- createCommandsDir(tmpDir, {
229
- "heading.md": "## This is a heading",
230
- });
231
-
232
- const registry = discoverCCCommands(tmpDir);
233
- const entry = registry.entries.get("heading");
234
- expect(entry).toBeDefined();
235
- expect(entry!.summary).toBe("This is a heading");
236
- });
237
-
238
- test("strips multiple heading levels", () => {
239
- createCommandsDir(tmpDir, {
240
- "h1.md": "# H1 Heading",
241
- "h3.md": "### H3 Heading",
242
- });
243
-
244
- const registry = discoverCCCommands(tmpDir);
245
- expect(registry.entries.get("h1")!.summary).toBe("H1 Heading");
246
- expect(registry.entries.get("h3")!.summary).toBe("H3 Heading");
247
- });
248
-
249
- test("skips empty lines before summary", () => {
250
- createCommandsDir(tmpDir, {
251
- "empty-lines.md": "\n\n\nFirst real line.",
252
- });
253
-
254
- const registry = discoverCCCommands(tmpDir);
255
- expect(registry.entries.get("empty-lines")!.summary).toBe(
256
- "First real line.",
257
- );
258
- });
259
-
260
- test("truncates summary to 100 characters", () => {
261
- const longLine = "A".repeat(150);
262
- createCommandsDir(tmpDir, {
263
- "long.md": longLine,
264
- });
265
-
266
- const registry = discoverCCCommands(tmpDir);
267
- const entry = registry.entries.get("long");
268
- expect(entry).toBeDefined();
269
- expect(entry!.summary.length).toBe(100);
270
- expect(entry!.summary).toBe("A".repeat(100));
271
- });
272
-
273
- test("handles file with only frontmatter and no content", () => {
274
- createCommandsDir(tmpDir, {
275
- "empty-body.md": "---\ntitle: Empty\n---\n",
276
- });
277
-
278
- const registry = discoverCCCommands(tmpDir);
279
- const entry = registry.entries.get("empty-body");
280
- expect(entry).toBeDefined();
281
- expect(entry!.summary).toBe("");
282
- });
283
-
284
- test("returns empty summary when frontmatter is truncated by partial read", () => {
285
- // Simulate frontmatter that exceeds SUMMARY_READ_BYTES (1024).
286
- // The closing --- delimiter will be cut off, causing FRONTMATTER_REGEX to
287
- // fail. extractSummary should return '' instead of '---'.
288
- const largeFrontmatter =
289
- "---\n" + "key: " + "x".repeat(1100) + "\n---\n\nActual summary.";
290
- createCommandsDir(tmpDir, {
291
- "big-frontmatter.md": largeFrontmatter,
292
- });
293
-
294
- const registry = discoverCCCommands(tmpDir);
295
- const entry = registry.entries.get("big-frontmatter");
296
- expect(entry).toBeDefined();
297
- expect(entry!.summary).toBe("");
298
- });
299
-
300
- test("returns empty summary when frontmatter is truncated (CRLF)", () => {
301
- const largeFrontmatter =
302
- "---\r\n" + "key: " + "x".repeat(1100) + "\r\n---\r\n\r\nActual summary.";
303
- createCommandsDir(tmpDir, {
304
- "big-frontmatter-crlf.md": largeFrontmatter,
305
- });
306
-
307
- const registry = discoverCCCommands(tmpDir);
308
- const entry = registry.entries.get("big-frontmatter-crlf");
309
- expect(entry).toBeDefined();
310
- expect(entry!.summary).toBe("");
311
- });
312
-
313
- test("returns empty summary when frontmatter is truncated with multibyte UTF-8 characters", () => {
314
- // When frontmatter contains multibyte UTF-8 characters (e.g., CJK text),
315
- // the JavaScript string length (UTF-16 code units) is smaller than the
316
- // byte length. The truncation guard must compare byte length, not
317
- // string length, against SUMMARY_READ_BYTES (1024).
318
- //
319
- // Each CJK character is 3 bytes in UTF-8 but 1 code unit in UTF-16.
320
- // We need the total byte count to reach 1024 while string length stays
321
- // well below 1024 to exercise the bug.
322
- const cjkChars = "\u4e00".repeat(340); // 340 chars * 3 bytes = 1020 bytes
323
- // '---\n' is 4 bytes, so total = 4 + 1020 = 1024 bytes, but string
324
- // length = 4 + 340 = 344 chars — well under 1024.
325
- const truncatedContent = "---\n" + cjkChars;
326
- createCommandsDir(tmpDir, {
327
- "multibyte-frontmatter.md": truncatedContent,
328
- });
329
-
330
- const registry = discoverCCCommands(tmpDir);
331
- const entry = registry.entries.get("multibyte-frontmatter");
332
- expect(entry).toBeDefined();
333
- // Should return '' because the frontmatter opening delimiter is present
334
- // but the closing delimiter is missing and the byte length reached the
335
- // read limit — indicating truncation.
336
- expect(entry!.summary).toBe("");
337
- });
338
-
339
- test("returns summary for small file starting with thematic break ---", () => {
340
- // A small markdown file that starts with "---" as a thematic break (not
341
- // frontmatter) should still have its first content line extracted as a
342
- // summary, rather than being treated as truncated frontmatter.
343
- createCommandsDir(tmpDir, {
344
- "thematic-break.md":
345
- "---\nThis is a valid summary after a thematic break.",
346
- });
347
-
348
- const registry = discoverCCCommands(tmpDir);
349
- const entry = registry.entries.get("thematic-break");
350
- expect(entry).toBeDefined();
351
- expect(entry!.summary).toBe(
352
- "This is a valid summary after a thematic break.",
353
- );
354
- });
355
-
356
- test("handles frontmatter with Windows-style line endings", () => {
357
- createCommandsDir(tmpDir, {
358
- "crlf.md": "---\r\ntitle: Test\r\n---\r\n\r\nSummary with CRLF.",
359
- });
360
-
361
- const registry = discoverCCCommands(tmpDir);
362
- const entry = registry.entries.get("crlf");
363
- expect(entry).toBeDefined();
364
- expect(entry!.summary).toBe("Summary with CRLF.");
365
- });
366
- });
367
-
368
- describe("command name validation", () => {
369
- test("accepts valid names with dots, dashes, underscores", () => {
370
- createCommandsDir(tmpDir, {
371
- "my-command.md": "Dashed name.",
372
- "my_command.md": "Underscored name.",
373
- "my.command.md": "Dotted name.",
374
- "Command123.md": "Alphanumeric.",
375
- "a.md": "Single char.",
376
- });
377
-
378
- const registry = discoverCCCommands(tmpDir);
379
- expect(registry.entries.has("my-command")).toBe(true);
380
- expect(registry.entries.has("my_command")).toBe(true);
381
- expect(registry.entries.has("my.command")).toBe(true);
382
- expect(registry.entries.has("command123")).toBe(true);
383
- expect(registry.entries.has("a")).toBe(true);
384
- });
385
-
386
- test("rejects names starting with special characters", () => {
387
- createCommandsDir(tmpDir, {
388
- "_start.md": "Starts with underscore.",
389
- ".start.md": "Starts with dot.",
390
- "-start.md": "Starts with dash.",
391
- });
392
-
393
- const registry = discoverCCCommands(tmpDir);
394
- expect(registry.entries.size).toBe(0);
395
- });
396
- });