@vellumai/assistant 0.4.34 → 0.4.36

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 (251) hide show
  1. package/AGENTS.md +1 -1
  2. package/ARCHITECTURE.md +44 -49
  3. package/README.md +32 -20
  4. package/docs/architecture/keychain-broker.md +186 -0
  5. package/docs/architecture/security.md +110 -116
  6. package/docs/runbook-trusted-contacts.md +2 -2
  7. package/docs/skills.md +25 -25
  8. package/package.json +4 -1
  9. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +11 -2
  10. package/src/__tests__/actor-token-service.test.ts +1 -0
  11. package/src/__tests__/amazon-cdp-integration.test.ts +74 -0
  12. package/src/__tests__/assistant-feature-flags-integration.test.ts +38 -9
  13. package/src/__tests__/assistant-id-boundary-guard.test.ts +91 -43
  14. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  15. package/src/__tests__/bundle-scanner.test.ts +1 -1
  16. package/src/__tests__/channel-guardian.test.ts +102 -102
  17. package/src/__tests__/channel-invite-transport.test.ts +155 -256
  18. package/src/__tests__/channel-readiness-routes.test.ts +336 -0
  19. package/src/__tests__/checker.test.ts +6 -6
  20. package/src/__tests__/chrome-cdp.test.ts +350 -0
  21. package/src/__tests__/computer-use-session-lifecycle.test.ts +3 -3
  22. package/src/__tests__/computer-use-session-working-dir.test.ts +86 -52
  23. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +1 -1
  24. package/src/__tests__/config-loader-migration.test.ts +85 -0
  25. package/src/__tests__/conversation-pairing.test.ts +370 -5
  26. package/src/__tests__/credential-broker-browser-fill.test.ts +1 -10
  27. package/src/__tests__/credential-broker-server-use.test.ts +1 -10
  28. package/src/__tests__/credential-security-e2e.test.ts +7 -1
  29. package/src/__tests__/credential-security-invariants.test.ts +14 -20
  30. package/src/__tests__/credential-vault-unit.test.ts +1 -11
  31. package/src/__tests__/credential-vault.test.ts +5 -19
  32. package/src/__tests__/credentials-cli.test.ts +806 -0
  33. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +23 -4
  34. package/src/__tests__/email-invite-adapter.test.ts +78 -0
  35. package/src/__tests__/email-service-config-fallback.test.ts +102 -0
  36. package/src/__tests__/encrypted-store.test.ts +6 -6
  37. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  38. package/src/__tests__/gateway-only-enforcement.test.ts +5 -1
  39. package/src/__tests__/guardian-actions-endpoint.test.ts +70 -12
  40. package/src/__tests__/guardian-outbound-http.test.ts +53 -47
  41. package/src/__tests__/handle-user-message-secret-resume.test.ts +23 -0
  42. package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +32 -23
  43. package/src/__tests__/handlers-telegram-config.test.ts +8 -2
  44. package/src/__tests__/handlers-twitter-config.test.ts +2 -2
  45. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +108 -7
  46. package/src/__tests__/ingress-reconcile.test.ts +6 -0
  47. package/src/__tests__/intent-routing.test.ts +23 -4
  48. package/src/__tests__/invite-routes-http.test.ts +12 -0
  49. package/src/__tests__/ipc-snapshot.test.ts +8 -2
  50. package/src/__tests__/keychain-broker-client.test.ts +543 -0
  51. package/src/__tests__/llm-usage-store.test.ts +344 -0
  52. package/src/__tests__/mcp-client-auth.test.ts +2 -2
  53. package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
  54. package/src/__tests__/migration-transport.test.ts +49 -0
  55. package/src/__tests__/notification-broadcaster.test.ts +205 -5
  56. package/src/__tests__/notification-deep-link.test.ts +365 -1
  57. package/src/__tests__/oauth-connect-handler.test.ts +2 -2
  58. package/src/__tests__/onboarding-starter-tasks.test.ts +17 -4
  59. package/src/__tests__/proxy-approval-callback.test.ts +1 -1
  60. package/src/__tests__/recording-handler.test.ts +1 -1
  61. package/src/__tests__/recording-intent-handler.test.ts +6 -1
  62. package/src/__tests__/recording-state-machine.test.ts +1 -1
  63. package/src/__tests__/relay-server.test.ts +9 -1
  64. package/src/__tests__/ride-shotgun-handler.test.ts +499 -0
  65. package/src/__tests__/runtime-attachment-metadata.test.ts +160 -1
  66. package/src/__tests__/script-proxy-injection-runtime.test.ts +299 -2
  67. package/src/__tests__/script-proxy-profile-template-fallback.test.ts +1 -1
  68. package/src/__tests__/secret-onetime-send.test.ts +8 -2
  69. package/src/__tests__/secure-keys.test.ts +175 -216
  70. package/src/__tests__/session-confirmation-signals.test.ts +1 -1
  71. package/src/__tests__/session-messaging-secret-redirect.test.ts +1 -1
  72. package/src/__tests__/session-queue.test.ts +2 -1
  73. package/src/__tests__/session-tool-setup-app-refresh.test.ts +2 -2
  74. package/src/__tests__/skill-feature-flags-integration.test.ts +29 -4
  75. package/src/__tests__/skill-feature-flags.test.ts +12 -9
  76. package/src/__tests__/skill-load-feature-flag.test.ts +26 -5
  77. package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
  78. package/src/__tests__/skills.test.ts +34 -4
  79. package/src/__tests__/slack-channel-config.test.ts +2 -2
  80. package/src/__tests__/system-prompt.test.ts +26 -4
  81. package/src/__tests__/telegram-bot-username-resolution.test.ts +212 -0
  82. package/src/__tests__/telegram-invite-adapter.test.ts +164 -0
  83. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  84. package/src/__tests__/tool-permission-simulate-handler.test.ts +8 -2
  85. package/src/__tests__/trusted-contact-approval-notifier.test.ts +9 -1
  86. package/src/__tests__/twitter-auth-handler.test.ts +2 -2
  87. package/src/__tests__/twitter-oauth-client.test.ts +1 -1
  88. package/src/__tests__/usage-routes.test.ts +339 -0
  89. package/src/__tests__/whatsapp-invite-adapter.test.ts +94 -0
  90. package/src/agent/loop.ts +3 -0
  91. package/src/amazon/checkout.ts +0 -1
  92. package/src/approvals/guardian-request-resolvers.ts +9 -1
  93. package/src/bundler/app-bundler.ts +28 -12
  94. package/src/bundler/bundle-scanner.ts +1 -1
  95. package/src/bundler/bundle-signer.ts +3 -3
  96. package/src/bundler/manifest.ts +1 -1
  97. package/src/bundler/signature-verifier.ts +3 -3
  98. package/src/channels/config.ts +1 -1
  99. package/src/cli/AGENTS.md +63 -0
  100. package/src/cli/__tests__/notifications.test.ts +470 -0
  101. package/src/cli/amazon.ts +344 -167
  102. package/src/cli/audit.ts +85 -0
  103. package/src/cli/autonomy.ts +369 -0
  104. package/src/cli/channels.ts +51 -0
  105. package/src/cli/completions.ts +208 -0
  106. package/src/cli/config.ts +220 -0
  107. package/src/cli/contacts.ts +471 -0
  108. package/src/cli/credentials.ts +564 -0
  109. package/src/cli/default-action.ts +14 -0
  110. package/src/cli/dev.ts +131 -0
  111. package/src/cli/doctor.ts +398 -0
  112. package/src/cli/email.ts +491 -0
  113. package/src/cli/influencer.ts +72 -0
  114. package/src/cli/integrations.ts +248 -57
  115. package/src/cli/keys.ts +114 -0
  116. package/src/cli/map.ts +46 -54
  117. package/src/cli/mcp.ts +111 -3
  118. package/src/cli/{config-commands.ts → memory.ts} +133 -242
  119. package/src/cli/notifications.ts +407 -0
  120. package/src/cli/program.ts +65 -0
  121. package/src/cli/reference.ts +48 -0
  122. package/src/cli/sequence.ts +154 -0
  123. package/src/cli/sessions.ts +262 -0
  124. package/src/cli/trust.ts +177 -0
  125. package/src/cli/twitter.ts +323 -106
  126. package/src/config/__tests__/build-cli-reference-section.test.ts +49 -0
  127. package/src/config/bundled-skills/amazon/SKILL.md +2 -2
  128. package/src/config/bundled-skills/app-builder/TOOLS.json +26 -0
  129. package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +13 -0
  130. package/src/config/bundled-skills/contacts/SKILL.md +178 -10
  131. package/src/config/bundled-skills/doordash/doordash-cli.ts +23 -168
  132. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +175 -145
  133. package/src/config/bundled-skills/messaging/tools/shared.ts +4 -1
  134. package/src/config/bundled-skills/twilio-setup/SKILL.md +70 -17
  135. package/src/config/bundled-tool-registry.ts +2 -0
  136. package/src/config/core-schema.ts +7 -0
  137. package/src/config/feature-flag-registry.json +16 -0
  138. package/src/config/loader.ts +26 -0
  139. package/src/config/schema.ts +4 -0
  140. package/src/config/skill-state.ts +0 -13
  141. package/src/config/system-prompt.ts +27 -0
  142. package/src/contacts/contact-store.ts +25 -0
  143. package/src/daemon/computer-use-session.ts +1 -1
  144. package/src/daemon/handlers/apps.ts +1 -0
  145. package/src/daemon/handlers/config-channels.ts +3 -3
  146. package/src/daemon/handlers/config-dispatch.ts +29 -0
  147. package/src/daemon/handlers/config-inbox.ts +4 -3
  148. package/src/daemon/handlers/config.ts +3 -43
  149. package/src/daemon/handlers/contacts.ts +34 -0
  150. package/src/daemon/handlers/index.ts +17 -3
  151. package/src/daemon/handlers/session-user-message.ts +7 -0
  152. package/src/daemon/handlers/sessions.ts +21 -2
  153. package/src/daemon/handlers/shared.ts +17 -0
  154. package/src/daemon/ipc-contract/apps.ts +2 -0
  155. package/src/daemon/ipc-contract/computer-use.ts +9 -0
  156. package/src/daemon/ipc-contract/contacts.ts +3 -3
  157. package/src/daemon/ipc-contract/inbox.ts +2 -0
  158. package/src/daemon/ipc-contract/messages.ts +4 -0
  159. package/src/daemon/ipc-contract/sessions.ts +8 -0
  160. package/src/daemon/ipc-contract-inventory.json +1 -0
  161. package/src/daemon/lifecycle.ts +0 -5
  162. package/src/daemon/ride-shotgun-handler.ts +139 -25
  163. package/src/daemon/session-agent-loop-handlers.ts +100 -0
  164. package/src/daemon/session-agent-loop.ts +72 -0
  165. package/src/daemon/session-tool-setup.ts +7 -0
  166. package/src/daemon/session.ts +23 -1
  167. package/src/daemon/tool-side-effects.ts +39 -1
  168. package/src/email/service.ts +59 -2
  169. package/src/index.ts +2 -60
  170. package/src/mcp/mcp-oauth-provider.ts +90 -8
  171. package/src/media/app-icon-generator.ts +86 -0
  172. package/src/memory/db-init.ts +12 -1
  173. package/src/memory/llm-usage-store.ts +186 -0
  174. package/src/memory/migrations/026-guardian-verification-sessions.ts +28 -9
  175. package/src/memory/migrations/027a-guardian-bootstrap-token.ts +16 -3
  176. package/src/memory/migrations/038-actor-token-records.ts +8 -1
  177. package/src/memory/migrations/039-actor-refresh-token-records.ts +11 -2
  178. package/src/memory/migrations/110-channel-guardian.ts +27 -6
  179. package/src/memory/migrations/112-assistant-inbox.ts +39 -15
  180. package/src/memory/migrations/114-notifications.ts +37 -15
  181. package/src/memory/migrations/117-conversation-attention.ts +33 -9
  182. package/src/memory/migrations/137-usage-dashboard-indexes.ts +26 -0
  183. package/src/memory/migrations/139-drop-usage-composite-indexes.ts +30 -0
  184. package/src/memory/migrations/index.ts +2 -0
  185. package/src/memory/migrations/schema-introspection.ts +18 -0
  186. package/src/memory/schema-migration.ts +1 -0
  187. package/src/memory/shared-app-links-store.ts +1 -1
  188. package/src/messaging/registry.ts +27 -0
  189. package/src/notifications/README.md +79 -70
  190. package/src/notifications/broadcaster.ts +2 -1
  191. package/src/notifications/conversation-pairing.ts +147 -13
  192. package/src/notifications/copy-composer.ts +7 -3
  193. package/src/notifications/destination-resolver.ts +14 -1
  194. package/src/notifications/emit-signal.ts +3 -2
  195. package/src/notifications/signal.ts +105 -1
  196. package/src/notifications/types.ts +16 -0
  197. package/src/permissions/checker.ts +29 -3
  198. package/src/permissions/prompter.ts +11 -3
  199. package/src/runtime/access-request-helper.ts +2 -1
  200. package/src/runtime/auth/route-policy.ts +7 -1
  201. package/src/runtime/channel-invite-transport.ts +40 -63
  202. package/src/runtime/channel-invite-transports/email.ts +13 -39
  203. package/src/runtime/channel-invite-transports/slack.ts +5 -34
  204. package/src/runtime/channel-invite-transports/sms.ts +8 -29
  205. package/src/runtime/channel-invite-transports/telegram.ts +69 -28
  206. package/src/runtime/channel-invite-transports/voice.ts +0 -7
  207. package/src/runtime/channel-invite-transports/whatsapp.ts +43 -0
  208. package/src/runtime/channel-readiness-service.ts +202 -45
  209. package/src/runtime/confirmation-request-guardian-bridge.ts +2 -1
  210. package/src/runtime/guardian-outbound-actions.ts +8 -5
  211. package/src/runtime/http-server.ts +5 -9
  212. package/src/runtime/http-types.ts +13 -1
  213. package/src/runtime/invite-instruction-generator.ts +178 -0
  214. package/src/runtime/invite-service.ts +22 -25
  215. package/src/runtime/migrations/migration-transport.ts +13 -0
  216. package/src/runtime/routes/app-routes.ts +1 -1
  217. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +8 -7
  218. package/src/runtime/routes/channel-readiness-routes.ts +30 -11
  219. package/src/runtime/routes/contact-routes.ts +54 -26
  220. package/src/runtime/routes/guardian-bootstrap-routes.ts +1 -1
  221. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +1 -1
  222. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +2 -1
  223. package/src/runtime/routes/inbound-stages/verification-intercept.ts +2 -1
  224. package/src/runtime/routes/integration-routes.ts +1 -1
  225. package/src/runtime/routes/invite-routes.ts +1 -1
  226. package/src/runtime/routes/secret-routes.ts +31 -7
  227. package/src/runtime/routes/surface-content-routes.ts +104 -0
  228. package/src/runtime/routes/twilio-routes.ts +32 -1
  229. package/src/runtime/routes/usage-routes.ts +114 -0
  230. package/src/runtime/tool-grant-request-helper.ts +2 -1
  231. package/src/security/encrypted-store.ts +9 -5
  232. package/src/security/keychain-broker-client.ts +393 -0
  233. package/src/security/secure-keys.ts +106 -321
  234. package/src/tools/apps/executors.ts +73 -0
  235. package/src/tools/browser/auto-navigate.ts +15 -6
  236. package/src/tools/browser/chrome-cdp.ts +211 -0
  237. package/src/tools/browser/network-recorder.test.ts +83 -0
  238. package/src/tools/browser/network-recorder.ts +8 -7
  239. package/src/tools/browser/x-auto-navigate.ts +12 -6
  240. package/src/tools/credentials/policy-types.ts +24 -0
  241. package/src/tools/credentials/vault.ts +22 -27
  242. package/src/tools/network/script-proxy/session-manager.ts +47 -3
  243. package/src/tools/permission-checker.ts +1 -0
  244. package/src/tools/types.ts +2 -0
  245. package/src/tools/ui-surface/definitions.ts +1 -2
  246. package/src/tools/watch/watch-state.ts +2 -0
  247. package/src/__tests__/key-migration.test.ts +0 -240
  248. package/src/__tests__/keychain.test.ts +0 -286
  249. package/src/cli/core-commands.ts +0 -899
  250. package/src/security/keychain-to-encrypted-migration.ts +0 -66
  251. package/src/security/keychain.ts +0 -490
@@ -35,7 +35,10 @@ let currentConfig: Record<string, unknown> = {
35
35
  const DECLARED_FLAG_KEY = "feature_flags.hatch-new-assistant.enabled";
36
36
  const DECLARED_SKILL_ID = "hatch-new-assistant";
37
37
 
38
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
39
+ const realPlatform = require("../util/platform.js");
38
40
  mock.module("../util/platform.js", () => ({
41
+ ...realPlatform,
39
42
  getRootDir: () => TEST_DIR,
40
43
  getDataDir: () => TEST_DIR,
41
44
  getWorkspaceDir: () => TEST_DIR,
@@ -59,38 +62,60 @@ mock.module("../util/platform.js", () => ({
59
62
  isWindows: () => false,
60
63
  getPlatformName: () => "linux",
61
64
  getClipboardCommand: () => null,
65
+ readSessionToken: () => null,
62
66
  removeSocketFile: () => {},
63
67
  migratePath: () => {},
64
68
  migrateToWorkspaceLayout: () => {},
65
69
  migrateToDataLayout: () => {},
66
70
  }));
67
71
 
72
+ const noopLogger = new Proxy({} as Record<string, unknown>, {
73
+ get: (_target, prop) => (prop === "child" ? () => noopLogger : () => {}),
74
+ });
75
+
76
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
77
+ const realLogger = require("../util/logger.js");
68
78
  mock.module("../util/logger.js", () => ({
69
- getLogger: () =>
70
- new Proxy({} as Record<string, unknown>, {
71
- get: () => () => {},
72
- }),
79
+ ...realLogger,
80
+ getLogger: () => noopLogger,
81
+ getCliLogger: () => noopLogger,
73
82
  isDebug: () => false,
74
83
  truncateForLog: (v: string) => v,
84
+ initLogger: () => {},
85
+ pruneOldLogFiles: () => 0,
75
86
  }));
76
87
 
77
88
  mock.module("../config/loader.js", () => ({
78
89
  getConfig: () => currentConfig,
90
+ loadConfig: () => currentConfig,
91
+ loadRawConfig: () => ({}),
92
+ saveConfig: () => {},
93
+ saveRawConfig: () => {},
94
+ invalidateConfigCache: () => {},
95
+ getNestedValue: () => undefined,
96
+ setNestedValue: () => {},
97
+ syncConfigToLockfile: () => {},
79
98
  }));
80
99
 
100
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
101
+ const realUserReference = require("../config/user-reference.js");
81
102
  mock.module("../config/user-reference.js", () => ({
103
+ ...realUserReference,
82
104
  resolveUserReference: () => "TestUser",
83
105
  resolveUserPronouns: () => null,
84
106
  }));
85
107
 
108
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
109
+ const realCredentialMetadataStore = require("../tools/credentials/metadata-store.js");
86
110
  mock.module("../tools/credentials/metadata-store.js", () => ({
111
+ ...realCredentialMetadataStore,
87
112
  listCredentialMetadata: () => [],
88
113
  }));
89
114
 
90
115
  const { buildSystemPrompt } = await import("../config/system-prompt.js");
91
116
  const { isAssistantFeatureFlagEnabled } =
92
117
  await import("../config/assistant-feature-flags.js");
93
- const { isSkillFeatureEnabled } = await import("../config/skill-state.js");
118
+ const { skillFlagKey } = await import("../config/skill-state.js");
94
119
 
95
120
  // ---------------------------------------------------------------------------
96
121
  // Setup / Teardown
@@ -301,18 +326,22 @@ describe("isAssistantFeatureFlagEnabled", () => {
301
326
  });
302
327
  });
303
328
 
304
- describe("legacy isSkillFeatureEnabled backward compat", () => {
305
- test("delegates to the canonical resolver", () => {
329
+ describe("isAssistantFeatureFlagEnabled with skillFlagKey", () => {
330
+ test("resolves skill flag via canonical path", () => {
306
331
  const config = {
307
332
  assistantFeatureFlagValues: { [DECLARED_FLAG_KEY]: false },
308
333
  } as any;
309
334
 
310
- expect(isSkillFeatureEnabled(DECLARED_SKILL_ID, config)).toBe(false);
335
+ expect(
336
+ isAssistantFeatureFlagEnabled(skillFlagKey(DECLARED_SKILL_ID), config),
337
+ ).toBe(false);
311
338
  });
312
339
 
313
340
  test("disabled when no override set (registry default is false)", () => {
314
341
  const config = {} as any;
315
342
 
316
- expect(isSkillFeatureEnabled(DECLARED_SKILL_ID, config)).toBe(false);
343
+ expect(
344
+ isAssistantFeatureFlagEnabled(skillFlagKey(DECLARED_SKILL_ID), config),
345
+ ).toBe(false);
317
346
  });
318
347
  });
@@ -481,8 +481,8 @@ describe("assistant ID boundary", () => {
481
481
  const repoRoot = getRepoRoot();
482
482
 
483
483
  // Scan all Drizzle schema files for assistantId column definitions.
484
- // The Drizzle ORM pattern is `assistantId: text(` for defining a text
485
- // column named assistantId.
484
+ // Match `assistantId:` followed by any Drizzle column builder (text(,
485
+ // integer(, blob(, real(, etc.) — not just text(.
486
486
  const schemaGlobs = [
487
487
  "assistant/src/memory/schema/*.ts",
488
488
  "assistant/src/memory/schema/**/*.ts",
@@ -492,7 +492,7 @@ describe("assistant ID boundary", () => {
492
492
  try {
493
493
  grepOutput = execFileSync(
494
494
  "git",
495
- ["grep", "-nE", "assistantId\\s*:\\s*text\\(", "--", ...schemaGlobs],
495
+ ["grep", "-nE", "assistantId\\s*:", "--", ...schemaGlobs],
496
496
  { encoding: "utf-8", cwd: repoRoot },
497
497
  ).trim();
498
498
  } catch (err) {
@@ -546,6 +546,9 @@ describe("assistant ID boundary", () => {
546
546
  // Scan store files for exported function signatures that include
547
547
  // assistantId as a parameter. This covers memory stores, contact stores,
548
548
  // notification stores, credential/token stores, and call stores.
549
+ //
550
+ // We read each file and extract full parameter lists (which may span
551
+ // multiple lines) from exported functions to catch multiline signatures.
549
552
  const storeGlobs = [
550
553
  "assistant/src/memory/*.ts",
551
554
  "assistant/src/contacts/*.ts",
@@ -556,52 +559,97 @@ describe("assistant ID boundary", () => {
556
559
  "assistant/src/calls/call-store.ts",
557
560
  ];
558
561
 
559
- // Match exported function declarations/expressions with assistantId in
560
- // their parameter lists. Patterns:
561
- // export function foo(assistantId
562
- // export function foo(bar, assistantId
563
- // export async function foo(assistantId
564
- // export const foo = (assistantId
565
- // export const foo = async (assistantId
566
- // We use a broad pattern that catches assistantId appearing after an
567
- // opening paren in an export context.
568
- const pattern =
569
- "export\\s+(async\\s+)?function\\s+\\w+\\s*\\([^)]*assistantId|export\\s+const\\s+\\w+\\s*=\\s*(async\\s+)?\\([^)]*assistantId";
570
-
571
- let grepOutput = "";
572
- try {
573
- grepOutput = execFileSync(
574
- "git",
575
- ["grep", "-nE", pattern, "--", ...storeGlobs],
576
- { encoding: "utf-8", cwd: repoRoot },
577
- ).trim();
578
- } catch (err) {
579
- // Exit code 1 means no matches — happy path
580
- if ((err as { status?: number }).status === 1) {
581
- return;
562
+ // Find matching files using git ls-files with each glob
563
+ const matchedFiles: string[] = [];
564
+ for (const glob of storeGlobs) {
565
+ try {
566
+ const output = execFileSync("git", ["ls-files", "--", glob], {
567
+ encoding: "utf-8",
568
+ cwd: repoRoot,
569
+ }).trim();
570
+ if (output) {
571
+ matchedFiles.push(...output.split("\n").filter((f) => f.length > 0));
572
+ }
573
+ } catch {
574
+ // Ignore errors — glob may not match anything
582
575
  }
583
- throw err;
584
576
  }
585
577
 
586
- const allLines = grepOutput.split("\n").filter((l) => l.length > 0);
587
- const violations = allLines.filter((line) => {
588
- const filePath = line.split(":")[0];
589
- if (isTestFile(filePath)) return false;
590
- if (isMigrationFile(filePath)) return false;
578
+ const violations: string[] = [];
591
579
 
592
- // Allow comments
593
- const parts = line.split(":");
594
- const content = parts.slice(2).join(":").trim();
595
- if (
596
- content.startsWith("//") ||
597
- content.startsWith("*") ||
598
- content.startsWith("/*")
580
+ // Regex to find the start of an exported function declaration or
581
+ // arrow-function expression. We capture everything from `export` up to
582
+ // and including the opening parenthesis of the parameter list.
583
+ const exportFnStartRegex =
584
+ /export\s+(?:async\s+)?function\s+\w+\s*\(|export\s+const\s+\w+\s*=\s*(?:async\s+)?\(/g;
585
+
586
+ for (const relPath of matchedFiles) {
587
+ if (isTestFile(relPath) || isMigrationFile(relPath)) continue;
588
+
589
+ const content = readFileSync(join(repoRoot, relPath), "utf-8");
590
+
591
+ exportFnStartRegex.lastIndex = 0;
592
+ for (
593
+ let match = exportFnStartRegex.exec(content);
594
+ match;
595
+ match = exportFnStartRegex.exec(content)
599
596
  ) {
600
- return false;
601
- }
597
+ // Skip matches that fall inside comments. Find the beginning of
598
+ // the line containing the match and check for comment prefixes.
599
+ const lineStart = content.lastIndexOf("\n", match.index) + 1;
600
+ const linePrefix = content.slice(lineStart, match.index).trim();
601
+ if (linePrefix.startsWith("//")) {
602
+ continue;
603
+ }
604
+ // For block comments: check if the match is inside an unclosed
605
+ // block comment. A prefix starting with `*` (continuation line)
606
+ // or `/*` only counts if there is no closing `*/` between the
607
+ // last `/*` opener and the match position — otherwise the
608
+ // comment was already closed (e.g. `/** docs */ export …`).
609
+ if (linePrefix.startsWith("*") || linePrefix.startsWith("/*")) {
610
+ const textBeforeMatch = content.slice(lineStart, match.index);
611
+ const lastOpen = textBeforeMatch.lastIndexOf("/*");
612
+ if (lastOpen === -1) {
613
+ // No block-comment opener on this line but starts with `*`,
614
+ // so it's a continuation line inside a multi-line comment.
615
+ continue;
616
+ }
617
+ const closeBetween = textBeforeMatch.indexOf("*/", lastOpen + 2);
618
+ if (closeBetween === -1) {
619
+ // The block comment is still open at the match position.
620
+ continue;
621
+ }
622
+ // The block comment was closed before the match — fall through
623
+ // and evaluate the match normally.
624
+ }
602
625
 
603
- return true;
604
- });
626
+ // Find the matching closing paren to extract the full parameter list,
627
+ // which may span multiple lines.
628
+ const parenStart = match.index + match[0].length - 1; // index of '('
629
+ let depth = 1;
630
+ let paramEnd = parenStart + 1;
631
+ for (let i = parenStart + 1; i < content.length && depth > 0; i++) {
632
+ if (content[i] === "(") depth++;
633
+ if (content[i] === ")") depth--;
634
+ if (depth === 0) {
635
+ paramEnd = i;
636
+ break;
637
+ }
638
+ }
639
+
640
+ const paramList = content.slice(parenStart + 1, paramEnd);
641
+
642
+ // Check if the parameter list contains assistantId as a word boundary
643
+ if (/\bassistantId\b/.test(paramList)) {
644
+ // Determine the line number of the export keyword for reporting
645
+ const lineNum = content.slice(0, match.index).split("\n").length;
646
+ const firstLine = content
647
+ .slice(match.index, match.index + match[0].length)
648
+ .trim();
649
+ violations.push(`${relPath}:${lineNum}: ${firstLine}...`);
650
+ }
651
+ }
652
+ }
605
653
 
606
654
  if (violations.length > 0) {
607
655
  const message = [
@@ -60,7 +60,7 @@ let mockGetCredentialMetadata: ReturnType<typeof mock>;
60
60
  mock.module("../security/secure-keys.js", () => ({
61
61
  getSecureKey: (...args: unknown[]) => mockGetSecureKey(...args),
62
62
  setSecureKey: () => true,
63
- deleteSecureKey: () => true,
63
+ deleteSecureKey: () => "deleted",
64
64
  listSecureKeys: () => [],
65
65
  getBackendType: () => "encrypted",
66
66
  _resetBackend: () => {},
@@ -49,7 +49,7 @@ async function createBundle(
49
49
  const data = await zip.generateAsync({ type: "uint8array" });
50
50
  const path = join(
51
51
  tempDir,
52
- `test-${Date.now()}-${Math.random().toString(36).slice(2)}.vellumapp`,
52
+ `test-${Date.now()}-${Math.random().toString(36).slice(2)}.vellum`,
53
53
  );
54
54
  await Bun.write(path, data);
55
55
  return path;