convex 1.34.0 → 1.34.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/browser.bundle.js +6 -9
  3. package/dist/browser.bundle.js.map +2 -2
  4. package/dist/cjs/browser/sync/authentication_manager.js +4 -1
  5. package/dist/cjs/browser/sync/authentication_manager.js.map +2 -2
  6. package/dist/cjs/browser/sync/web_socket_manager.js +1 -7
  7. package/dist/cjs/browser/sync/web_socket_manager.js.map +2 -2
  8. package/dist/cjs/cli/aiFiles.js +15 -14
  9. package/dist/cjs/cli/aiFiles.js.map +2 -2
  10. package/dist/cjs/cli/configure.js +15 -10
  11. package/dist/cjs/cli/configure.js.map +2 -2
  12. package/dist/cjs/cli/lib/aiFiles/agentsmd.js +69 -0
  13. package/dist/cjs/cli/lib/aiFiles/agentsmd.js.map +7 -0
  14. package/dist/cjs/cli/lib/aiFiles/claudemd.js +69 -0
  15. package/dist/cjs/cli/lib/aiFiles/claudemd.js.map +7 -0
  16. package/dist/cjs/cli/lib/{ai → aiFiles}/config.js +73 -46
  17. package/dist/cjs/cli/lib/aiFiles/config.js.map +7 -0
  18. package/dist/cjs/cli/lib/aiFiles/cursorrules.js +48 -0
  19. package/dist/cjs/cli/lib/aiFiles/cursorrules.js.map +7 -0
  20. package/dist/cjs/cli/lib/aiFiles/guidelinesmd.js +51 -0
  21. package/dist/cjs/cli/lib/aiFiles/guidelinesmd.js.map +7 -0
  22. package/dist/cjs/cli/lib/aiFiles/index.js +231 -0
  23. package/dist/cjs/cli/lib/aiFiles/index.js.map +7 -0
  24. package/dist/cjs/cli/lib/aiFiles/paths.js.map +7 -0
  25. package/dist/cjs/cli/lib/aiFiles/skills.js +180 -0
  26. package/dist/cjs/cli/lib/aiFiles/skills.js.map +7 -0
  27. package/dist/cjs/cli/lib/aiFiles/status.js +195 -0
  28. package/dist/cjs/cli/lib/aiFiles/status.js.map +7 -0
  29. package/dist/cjs/cli/lib/aiFiles/utils.js +111 -0
  30. package/dist/cjs/cli/lib/aiFiles/utils.js.map +7 -0
  31. package/dist/cjs/cli/lib/command.js +6 -1
  32. package/dist/cjs/cli/lib/command.js.map +2 -2
  33. package/dist/cjs/cli/lib/config.js +3 -4
  34. package/dist/cjs/cli/lib/config.js.map +2 -2
  35. package/dist/cjs/cli/lib/localDeployment/anonymous.js +2 -2
  36. package/dist/cjs/cli/lib/localDeployment/anonymous.js.map +2 -2
  37. package/dist/cjs/cli/lib/updates.js +8 -8
  38. package/dist/cjs/cli/lib/updates.js.map +2 -2
  39. package/dist/cjs/cli/lib/versionApi.js +7 -4
  40. package/dist/cjs/cli/lib/versionApi.js.map +2 -2
  41. package/dist/cjs/cli/lib/workos/workos.js +4 -6
  42. package/dist/cjs/cli/lib/workos/workos.js.map +2 -2
  43. package/dist/cjs/index.js +1 -1
  44. package/dist/cjs/index.js.map +1 -1
  45. package/dist/cjs-types/browser/sync/authentication_manager.d.ts.map +1 -1
  46. package/dist/cjs-types/browser/sync/web_socket_manager.d.ts.map +1 -1
  47. package/dist/cjs-types/cli/aiFiles.d.ts.map +1 -1
  48. package/dist/cjs-types/cli/configure.d.ts.map +1 -1
  49. package/dist/cjs-types/cli/lib/aiFiles/agentsmd.d.ts +19 -0
  50. package/dist/cjs-types/cli/lib/aiFiles/agentsmd.d.ts.map +1 -0
  51. package/dist/cjs-types/cli/lib/aiFiles/agentsmd.test.d.ts +2 -0
  52. package/dist/cjs-types/cli/lib/aiFiles/agentsmd.test.d.ts.map +1 -0
  53. package/dist/cjs-types/cli/lib/aiFiles/claudemd.d.ts +19 -0
  54. package/dist/cjs-types/cli/lib/aiFiles/claudemd.d.ts.map +1 -0
  55. package/dist/cjs-types/cli/lib/aiFiles/claudemd.test.d.ts +2 -0
  56. package/dist/cjs-types/cli/lib/aiFiles/claudemd.test.d.ts.map +1 -0
  57. package/dist/cjs-types/cli/lib/aiFiles/config.d.ts +46 -0
  58. package/dist/cjs-types/cli/lib/aiFiles/config.d.ts.map +1 -0
  59. package/dist/cjs-types/cli/lib/aiFiles/config.test.d.ts.map +1 -0
  60. package/dist/cjs-types/cli/lib/aiFiles/cursorrules.d.ts +10 -0
  61. package/dist/cjs-types/cli/lib/aiFiles/cursorrules.d.ts.map +1 -0
  62. package/dist/cjs-types/cli/lib/aiFiles/guidelinesmd.d.ts +12 -0
  63. package/dist/cjs-types/cli/lib/aiFiles/guidelinesmd.d.ts.map +1 -0
  64. package/dist/cjs-types/cli/lib/aiFiles/guidelinesmd.test.d.ts +2 -0
  65. package/dist/cjs-types/cli/lib/aiFiles/guidelinesmd.test.d.ts.map +1 -0
  66. package/dist/cjs-types/cli/lib/aiFiles/index.d.ts +40 -0
  67. package/dist/cjs-types/cli/lib/aiFiles/index.d.ts.map +1 -0
  68. package/dist/cjs-types/cli/lib/aiFiles/index.test.d.ts.map +1 -0
  69. package/dist/cjs-types/cli/lib/aiFiles/integration.test.d.ts.map +1 -0
  70. package/dist/cjs-types/cli/lib/{ai → aiFiles}/paths.d.ts +4 -0
  71. package/dist/cjs-types/cli/lib/aiFiles/paths.d.ts.map +1 -0
  72. package/dist/cjs-types/cli/lib/aiFiles/prompt.test.d.ts.map +1 -0
  73. package/dist/cjs-types/cli/lib/aiFiles/skills.d.ts +18 -0
  74. package/dist/cjs-types/cli/lib/aiFiles/skills.d.ts.map +1 -0
  75. package/dist/cjs-types/cli/lib/aiFiles/status.d.ts +3 -0
  76. package/dist/cjs-types/cli/lib/aiFiles/status.d.ts.map +1 -0
  77. package/dist/cjs-types/cli/lib/aiFiles/utils.d.ts +46 -0
  78. package/dist/cjs-types/cli/lib/aiFiles/utils.d.ts.map +1 -0
  79. package/dist/cjs-types/cli/lib/config.d.ts +1 -0
  80. package/dist/cjs-types/cli/lib/config.d.ts.map +1 -1
  81. package/dist/cjs-types/cli/lib/versionApi.d.ts +7 -1
  82. package/dist/cjs-types/cli/lib/versionApi.d.ts.map +1 -1
  83. package/dist/cjs-types/cli/lib/workos/workos.d.ts.map +1 -1
  84. package/dist/cjs-types/index.d.ts +1 -1
  85. package/dist/cli.bundle.cjs +1605 -1548
  86. package/dist/cli.bundle.cjs.map +4 -4
  87. package/dist/esm/browser/sync/authentication_manager.js +4 -1
  88. package/dist/esm/browser/sync/authentication_manager.js.map +2 -2
  89. package/dist/esm/browser/sync/web_socket_manager.js +1 -7
  90. package/dist/esm/browser/sync/web_socket_manager.js.map +2 -2
  91. package/dist/esm/cli/aiFiles.js +17 -17
  92. package/dist/esm/cli/aiFiles.js.map +2 -2
  93. package/dist/esm/cli/configure.js +15 -10
  94. package/dist/esm/cli/configure.js.map +2 -2
  95. package/dist/esm/cli/lib/aiFiles/agentsmd.js +52 -0
  96. package/dist/esm/cli/lib/aiFiles/agentsmd.js.map +7 -0
  97. package/dist/esm/cli/lib/aiFiles/claudemd.js +52 -0
  98. package/dist/esm/cli/lib/aiFiles/claudemd.js.map +7 -0
  99. package/dist/esm/cli/lib/{ai → aiFiles}/config.js +71 -45
  100. package/dist/esm/cli/lib/aiFiles/config.js.map +7 -0
  101. package/dist/esm/cli/lib/aiFiles/cursorrules.js +16 -0
  102. package/dist/esm/cli/lib/aiFiles/cursorrules.js.map +7 -0
  103. package/dist/esm/cli/lib/aiFiles/guidelinesmd.js +28 -0
  104. package/dist/esm/cli/lib/aiFiles/guidelinesmd.js.map +7 -0
  105. package/dist/esm/cli/lib/aiFiles/index.js +210 -0
  106. package/dist/esm/cli/lib/aiFiles/index.js.map +7 -0
  107. package/dist/esm/cli/lib/aiFiles/paths.js.map +7 -0
  108. package/dist/esm/cli/lib/aiFiles/skills.js +147 -0
  109. package/dist/esm/cli/lib/aiFiles/skills.js.map +7 -0
  110. package/dist/esm/cli/lib/aiFiles/status.js +175 -0
  111. package/dist/esm/cli/lib/aiFiles/status.js.map +7 -0
  112. package/dist/esm/cli/lib/aiFiles/utils.js +82 -0
  113. package/dist/esm/cli/lib/aiFiles/utils.js.map +7 -0
  114. package/dist/esm/cli/lib/command.js +6 -1
  115. package/dist/esm/cli/lib/command.js.map +2 -2
  116. package/dist/esm/cli/lib/config.js +3 -4
  117. package/dist/esm/cli/lib/config.js.map +2 -2
  118. package/dist/esm/cli/lib/localDeployment/anonymous.js +2 -2
  119. package/dist/esm/cli/lib/localDeployment/anonymous.js.map +2 -2
  120. package/dist/esm/cli/lib/updates.js +8 -8
  121. package/dist/esm/cli/lib/updates.js.map +2 -2
  122. package/dist/esm/cli/lib/versionApi.js +7 -4
  123. package/dist/esm/cli/lib/versionApi.js.map +2 -2
  124. package/dist/esm/cli/lib/workos/workos.js +4 -6
  125. package/dist/esm/cli/lib/workos/workos.js.map +2 -2
  126. package/dist/esm/index.js +1 -1
  127. package/dist/esm/index.js.map +1 -1
  128. package/dist/esm-types/browser/sync/authentication_manager.d.ts.map +1 -1
  129. package/dist/esm-types/browser/sync/web_socket_manager.d.ts.map +1 -1
  130. package/dist/esm-types/cli/aiFiles.d.ts.map +1 -1
  131. package/dist/esm-types/cli/configure.d.ts.map +1 -1
  132. package/dist/esm-types/cli/lib/aiFiles/agentsmd.d.ts +19 -0
  133. package/dist/esm-types/cli/lib/aiFiles/agentsmd.d.ts.map +1 -0
  134. package/dist/esm-types/cli/lib/aiFiles/agentsmd.test.d.ts +2 -0
  135. package/dist/esm-types/cli/lib/aiFiles/agentsmd.test.d.ts.map +1 -0
  136. package/dist/esm-types/cli/lib/aiFiles/claudemd.d.ts +19 -0
  137. package/dist/esm-types/cli/lib/aiFiles/claudemd.d.ts.map +1 -0
  138. package/dist/esm-types/cli/lib/aiFiles/claudemd.test.d.ts +2 -0
  139. package/dist/esm-types/cli/lib/aiFiles/claudemd.test.d.ts.map +1 -0
  140. package/dist/esm-types/cli/lib/aiFiles/config.d.ts +46 -0
  141. package/dist/esm-types/cli/lib/aiFiles/config.d.ts.map +1 -0
  142. package/dist/esm-types/cli/lib/aiFiles/config.test.d.ts.map +1 -0
  143. package/dist/esm-types/cli/lib/aiFiles/cursorrules.d.ts +10 -0
  144. package/dist/esm-types/cli/lib/aiFiles/cursorrules.d.ts.map +1 -0
  145. package/dist/esm-types/cli/lib/aiFiles/guidelinesmd.d.ts +12 -0
  146. package/dist/esm-types/cli/lib/aiFiles/guidelinesmd.d.ts.map +1 -0
  147. package/dist/esm-types/cli/lib/aiFiles/guidelinesmd.test.d.ts +2 -0
  148. package/dist/esm-types/cli/lib/aiFiles/guidelinesmd.test.d.ts.map +1 -0
  149. package/dist/esm-types/cli/lib/aiFiles/index.d.ts +40 -0
  150. package/dist/esm-types/cli/lib/aiFiles/index.d.ts.map +1 -0
  151. package/dist/esm-types/cli/lib/aiFiles/index.test.d.ts.map +1 -0
  152. package/dist/esm-types/cli/lib/aiFiles/integration.test.d.ts.map +1 -0
  153. package/dist/esm-types/cli/lib/{ai → aiFiles}/paths.d.ts +4 -0
  154. package/dist/esm-types/cli/lib/aiFiles/paths.d.ts.map +1 -0
  155. package/dist/esm-types/cli/lib/aiFiles/prompt.test.d.ts.map +1 -0
  156. package/dist/esm-types/cli/lib/aiFiles/skills.d.ts +18 -0
  157. package/dist/esm-types/cli/lib/aiFiles/skills.d.ts.map +1 -0
  158. package/dist/esm-types/cli/lib/aiFiles/status.d.ts +3 -0
  159. package/dist/esm-types/cli/lib/aiFiles/status.d.ts.map +1 -0
  160. package/dist/esm-types/cli/lib/aiFiles/utils.d.ts +46 -0
  161. package/dist/esm-types/cli/lib/aiFiles/utils.d.ts.map +1 -0
  162. package/dist/esm-types/cli/lib/config.d.ts +1 -0
  163. package/dist/esm-types/cli/lib/config.d.ts.map +1 -1
  164. package/dist/esm-types/cli/lib/versionApi.d.ts +7 -1
  165. package/dist/esm-types/cli/lib/versionApi.d.ts.map +1 -1
  166. package/dist/esm-types/cli/lib/workos/workos.d.ts.map +1 -1
  167. package/dist/esm-types/index.d.ts +1 -1
  168. package/dist/react.bundle.js +6 -9
  169. package/dist/react.bundle.js.map +2 -2
  170. package/package.json +1 -1
  171. package/schemas/convex.schema.json +7 -1
  172. package/src/browser/sync/authentication_manager.ts +9 -4
  173. package/src/browser/sync/client_node.test.ts +125 -0
  174. package/src/browser/sync/web_socket_manager.ts +1 -7
  175. package/src/cli/aiFiles.ts +20 -27
  176. package/src/cli/configure.ts +17 -11
  177. package/src/cli/deploymentSelection.test.ts +56 -2
  178. package/src/cli/lib/{ai → aiFiles}/MANUAL_TESTING.md +6 -2
  179. package/src/cli/lib/aiFiles/agentsmd.test.ts +133 -0
  180. package/src/cli/lib/aiFiles/agentsmd.ts +77 -0
  181. package/src/cli/lib/aiFiles/claudemd.test.ts +92 -0
  182. package/src/cli/lib/aiFiles/claudemd.ts +77 -0
  183. package/src/cli/lib/{ai → aiFiles}/config.test.ts +181 -59
  184. package/src/cli/lib/{ai → aiFiles}/config.ts +92 -63
  185. package/src/cli/lib/aiFiles/cursorrules.ts +25 -0
  186. package/src/cli/lib/aiFiles/guidelinesmd.test.ts +40 -0
  187. package/src/cli/lib/aiFiles/guidelinesmd.ts +41 -0
  188. package/src/cli/lib/{ai → aiFiles}/index.test.ts +200 -339
  189. package/src/cli/lib/aiFiles/index.ts +303 -0
  190. package/src/cli/lib/{ai → aiFiles}/integration.test.ts +117 -147
  191. package/src/cli/lib/{ai → aiFiles}/paths.ts +5 -0
  192. package/src/cli/lib/{ai → aiFiles}/prompt.test.ts +78 -30
  193. package/src/cli/lib/aiFiles/skills.ts +213 -0
  194. package/src/cli/lib/aiFiles/status.ts +240 -0
  195. package/src/cli/lib/aiFiles/utils.ts +163 -0
  196. package/src/cli/lib/command.ts +6 -1
  197. package/src/cli/lib/config.test.ts +1 -1
  198. package/src/cli/lib/config.ts +6 -5
  199. package/src/cli/lib/localDeployment/anonymous.ts +2 -2
  200. package/src/cli/lib/updates.test.ts +40 -30
  201. package/src/cli/lib/updates.ts +8 -8
  202. package/src/cli/lib/versionApi.test.ts +13 -10
  203. package/src/cli/lib/versionApi.ts +13 -5
  204. package/src/cli/lib/workos/workos.ts +4 -5
  205. package/src/index.ts +1 -1
  206. package/src/values/.claude/settings.local.json +10 -0
  207. package/dist/cjs/cli/lib/ai/config.js.map +0 -7
  208. package/dist/cjs/cli/lib/ai/index.js +0 -704
  209. package/dist/cjs/cli/lib/ai/index.js.map +0 -7
  210. package/dist/cjs/cli/lib/ai/paths.js.map +0 -7
  211. package/dist/cjs-types/cli/lib/ai/config.d.ts +0 -50
  212. package/dist/cjs-types/cli/lib/ai/config.d.ts.map +0 -1
  213. package/dist/cjs-types/cli/lib/ai/config.test.d.ts.map +0 -1
  214. package/dist/cjs-types/cli/lib/ai/index.d.ts +0 -56
  215. package/dist/cjs-types/cli/lib/ai/index.d.ts.map +0 -1
  216. package/dist/cjs-types/cli/lib/ai/index.test.d.ts.map +0 -1
  217. package/dist/cjs-types/cli/lib/ai/integration.test.d.ts.map +0 -1
  218. package/dist/cjs-types/cli/lib/ai/paths.d.ts.map +0 -1
  219. package/dist/cjs-types/cli/lib/ai/prompt.test.d.ts.map +0 -1
  220. package/dist/esm/cli/lib/ai/config.js.map +0 -7
  221. package/dist/esm/cli/lib/ai/index.js +0 -684
  222. package/dist/esm/cli/lib/ai/index.js.map +0 -7
  223. package/dist/esm/cli/lib/ai/paths.js.map +0 -7
  224. package/dist/esm-types/cli/lib/ai/config.d.ts +0 -50
  225. package/dist/esm-types/cli/lib/ai/config.d.ts.map +0 -1
  226. package/dist/esm-types/cli/lib/ai/config.test.d.ts.map +0 -1
  227. package/dist/esm-types/cli/lib/ai/index.d.ts +0 -56
  228. package/dist/esm-types/cli/lib/ai/index.d.ts.map +0 -1
  229. package/dist/esm-types/cli/lib/ai/index.test.d.ts.map +0 -1
  230. package/dist/esm-types/cli/lib/ai/integration.test.d.ts.map +0 -1
  231. package/dist/esm-types/cli/lib/ai/paths.d.ts.map +0 -1
  232. package/dist/esm-types/cli/lib/ai/prompt.test.d.ts.map +0 -1
  233. package/src/cli/lib/ai/index.ts +0 -1006
  234. /package/dist/cjs/cli/lib/{ai → aiFiles}/paths.js +0 -0
  235. /package/dist/cjs-types/cli/lib/{ai → aiFiles}/config.test.d.ts +0 -0
  236. /package/dist/cjs-types/cli/lib/{ai → aiFiles}/index.test.d.ts +0 -0
  237. /package/dist/cjs-types/cli/lib/{ai → aiFiles}/integration.test.d.ts +0 -0
  238. /package/dist/cjs-types/cli/lib/{ai → aiFiles}/prompt.test.d.ts +0 -0
  239. /package/dist/esm/cli/lib/{ai → aiFiles}/paths.js +0 -0
  240. /package/dist/esm-types/cli/lib/{ai → aiFiles}/config.test.d.ts +0 -0
  241. /package/dist/esm-types/cli/lib/{ai → aiFiles}/index.test.d.ts +0 -0
  242. /package/dist/esm-types/cli/lib/{ai → aiFiles}/integration.test.d.ts +0 -0
  243. /package/dist/esm-types/cli/lib/{ai → aiFiles}/prompt.test.d.ts +0 -0
@@ -1,10 +1,9 @@
1
1
  import { describe, test, expect, vi, beforeEach, afterEach } from "vitest";
2
- import * as Sentry from "@sentry/node";
3
2
  import { logMessage } from "../../../bundler/log.js";
4
3
  import {
5
4
  readAiConfig,
6
5
  writeAiConfig,
7
- writeAiDisabledToProjectConfig,
6
+ writeAiEnabledToProjectConfig,
8
7
  } from "./config.js";
9
8
  import {
10
9
  downloadGuidelines,
@@ -15,14 +14,12 @@ import fs from "fs";
15
14
  import os from "os";
16
15
  import path from "path";
17
16
  import {
18
- injectAgentsMdSection,
19
- injectClaudeMdSection,
20
17
  checkAiFilesStaleness,
21
- updateAiFiles,
18
+ installAiFiles,
22
19
  removeAiFiles,
23
- disableAiFiles,
24
- statusAiFiles,
20
+ safelyAttemptToDisableAiFiles,
25
21
  } from "./index.js";
22
+ import { statusAiFiles } from "./status.js";
26
23
  import {
27
24
  AGENTS_MD_START_MARKER,
28
25
  AGENTS_MD_END_MARKER,
@@ -33,147 +30,7 @@ import {
33
30
  } from "../../codegen_templates/claudemd.js";
34
31
 
35
32
  // ---------------------------------------------------------------------------
36
- // injectAgentsMdSection — tested with real temp directories to exercise the
37
- // actual file I/O and string-surgery logic without complex mock wiring.
38
- // ---------------------------------------------------------------------------
39
-
40
- describe("injectAgentsMdSection", () => {
41
- let tmpDir: string;
42
-
43
- beforeEach(() => {
44
- tmpDir = fs.mkdtempSync(`${os.tmpdir()}${path.sep}`);
45
- });
46
-
47
- afterEach(() => {
48
- fs.rmSync(tmpDir, { recursive: true, force: true });
49
- });
50
-
51
- const section = `${AGENTS_MD_START_MARKER}\n## Convex\nRead guidelines.\n${AGENTS_MD_END_MARKER}`;
52
-
53
- test("creates AGENTS.md when it does not exist", async () => {
54
- await injectAgentsMdSection(section, tmpDir);
55
-
56
- const content = fs.readFileSync(path.join(tmpDir, "AGENTS.md"), "utf8");
57
- expect(content).toContain(AGENTS_MD_START_MARKER);
58
- expect(content).toContain(AGENTS_MD_END_MARKER);
59
- expect(content).toContain("## Convex");
60
- });
61
-
62
- test("appends to an existing AGENTS.md that has no Convex section", async () => {
63
- const existing = "# My project\n\nSome existing content.\n";
64
- fs.writeFileSync(path.join(tmpDir, "AGENTS.md"), existing);
65
-
66
- await injectAgentsMdSection(section, tmpDir);
67
-
68
- const content = fs.readFileSync(path.join(tmpDir, "AGENTS.md"), "utf8");
69
- expect(content).toContain("# My project");
70
- expect(content).toContain("Some existing content.");
71
- expect(content).toContain(AGENTS_MD_START_MARKER);
72
- expect(content).toContain("## Convex");
73
- });
74
-
75
- test("replaces an existing Convex section when markers are present", async () => {
76
- const oldSection = `${AGENTS_MD_START_MARKER}\n## Convex\nOld content.\n${AGENTS_MD_END_MARKER}`;
77
- const existing = `# My project\n\n${oldSection}\n`;
78
- fs.writeFileSync(path.join(tmpDir, "AGENTS.md"), existing);
79
-
80
- const newSection = `${AGENTS_MD_START_MARKER}\n## Convex\nNew content.\n${AGENTS_MD_END_MARKER}`;
81
- await injectAgentsMdSection(newSection, tmpDir);
82
-
83
- const content = fs.readFileSync(path.join(tmpDir, "AGENTS.md"), "utf8");
84
- expect(content).toContain("New content.");
85
- expect(content).not.toContain("Old content.");
86
- // Only one occurrence of the start marker
87
- expect(content.split(AGENTS_MD_START_MARKER).length - 1).toBe(1);
88
- });
89
-
90
- test("preserves content before and after an existing Convex section", async () => {
91
- const oldSection = `${AGENTS_MD_START_MARKER}\n## Convex\nOld.\n${AGENTS_MD_END_MARKER}`;
92
- const existing = `# Before\n\n${oldSection}\n\n# After\n`;
93
- fs.writeFileSync(path.join(tmpDir, "AGENTS.md"), existing);
94
-
95
- await injectAgentsMdSection(section, tmpDir);
96
-
97
- const content = fs.readFileSync(path.join(tmpDir, "AGENTS.md"), "utf8");
98
- expect(content).toContain("# Before");
99
- expect(content).toContain("# After");
100
- });
101
-
102
- test("returns a non-null hash of the written content", async () => {
103
- const hash = await injectAgentsMdSection(section, tmpDir);
104
- expect(typeof hash).toBe("string");
105
- expect(hash!.length).toBeGreaterThan(0);
106
- });
107
-
108
- test("returns hash of the section content, not the entire file", async () => {
109
- fs.writeFileSync(
110
- path.join(tmpDir, "AGENTS.md"),
111
- "# My project\n\nExisting content.\n",
112
- );
113
-
114
- const hash = await injectAgentsMdSection(section, tmpDir);
115
-
116
- const { hashSha256 } = await import("../utils/hash.js");
117
- expect(hash).toBe(hashSha256(section));
118
- });
119
- });
120
-
121
- describe("injectClaudeMdSection", () => {
122
- let tmpDir: string;
123
-
124
- beforeEach(() => {
125
- tmpDir = fs.mkdtempSync(`${os.tmpdir()}${path.sep}`);
126
- });
127
-
128
- afterEach(() => {
129
- fs.rmSync(tmpDir, { recursive: true, force: true });
130
- });
131
-
132
- const section = `${CLAUDE_MD_START_MARKER}\n## Convex\nRead guidelines.\n${CLAUDE_MD_END_MARKER}`;
133
-
134
- test("creates CLAUDE.md when it does not exist", async () => {
135
- const result = await injectClaudeMdSection(section, tmpDir);
136
-
137
- const content = fs.readFileSync(path.join(tmpDir, "CLAUDE.md"), "utf8");
138
- expect(content).toContain(CLAUDE_MD_START_MARKER);
139
- expect(content).toContain(CLAUDE_MD_END_MARKER);
140
- expect(result.didWrite).toBe(true);
141
- });
142
-
143
- test("appends managed section to existing CLAUDE.md content", async () => {
144
- fs.writeFileSync(
145
- path.join(tmpDir, "CLAUDE.md"),
146
- "My custom instructions\n",
147
- );
148
-
149
- const result = await injectClaudeMdSection(section, tmpDir);
150
-
151
- const content = fs.readFileSync(path.join(tmpDir, "CLAUDE.md"), "utf8");
152
- expect(content).toContain("My custom instructions");
153
- expect(content).toContain(CLAUDE_MD_START_MARKER);
154
- expect(result.didWrite).toBe(true);
155
- });
156
-
157
- test("replaces managed section without touching user content", async () => {
158
- const oldSection = `${CLAUDE_MD_START_MARKER}\nOld\n${CLAUDE_MD_END_MARKER}`;
159
- fs.writeFileSync(
160
- path.join(tmpDir, "CLAUDE.md"),
161
- `# Header\n\n${oldSection}\n\n# Footer\n`,
162
- "utf8",
163
- );
164
-
165
- await injectClaudeMdSection(section, tmpDir);
166
-
167
- const content = fs.readFileSync(path.join(tmpDir, "CLAUDE.md"), "utf8");
168
- expect(content).toContain("# Header");
169
- expect(content).toContain("# Footer");
170
- expect(content).toContain("## Convex");
171
- expect(content).not.toContain("Old");
172
- });
173
- });
174
-
175
- // ---------------------------------------------------------------------------
176
- // checkAiFilesStaleness — mock-based: logic only, no real I/O needed.
33
+ // Mocks
177
34
  // ---------------------------------------------------------------------------
178
35
 
179
36
  vi.mock("@sentry/node", () => ({
@@ -188,7 +45,7 @@ vi.mock("../../../bundler/log.js", () => ({
188
45
  vi.mock("./config.js", () => ({
189
46
  readAiConfig: vi.fn(),
190
47
  writeAiConfig: vi.fn(),
191
- writeAiDisabledToProjectConfig: vi.fn(),
48
+ writeAiEnabledToProjectConfig: vi.fn(),
192
49
  }));
193
50
 
194
51
  vi.mock("../versionApi.js", () => ({
@@ -201,7 +58,6 @@ vi.mock("child_process", () => ({
201
58
  default: {
202
59
  spawn: vi.fn(() => {
203
60
  const emitter = { on: vi.fn() };
204
- // Immediately simulate a successful exit.
205
61
  emitter.on.mockImplementation(
206
62
  (event: string, cb: (arg: number) => void) => {
207
63
  if (event === "close") cb(0);
@@ -215,13 +71,12 @@ vi.mock("child_process", () => ({
215
71
  const mockLogMessage = vi.mocked(logMessage);
216
72
  const mockReadAiConfig = vi.mocked(readAiConfig);
217
73
  const mockWriteAiConfig = vi.mocked(writeAiConfig);
218
- const mockWriteAiDisabledToProjectConfig = vi.mocked(
219
- writeAiDisabledToProjectConfig,
74
+ const mockWriteAiEnabledToProjectConfig = vi.mocked(
75
+ writeAiEnabledToProjectConfig,
220
76
  );
221
77
  const mockDownloadGuidelines = vi.mocked(downloadGuidelines);
222
78
  const mockFetchAgentSkillsSha = vi.mocked(fetchAgentSkillsSha);
223
79
  const mockGetVersion = vi.mocked(getVersion);
224
- const mockCaptureException = vi.mocked(Sentry.captureException);
225
80
 
226
81
  /** Minimal valid config used across tests; includes all required fields. */
227
82
  const baseConfig = {
@@ -230,9 +85,13 @@ const baseConfig = {
230
85
  claudeMdHash: null,
231
86
  agentSkillsSha: null,
232
87
  installedSkillNames: [] as string[],
233
- disableStalenessMessage: false,
88
+ enabled: true,
234
89
  };
235
90
 
91
+ // ---------------------------------------------------------------------------
92
+ // checkAiFilesStaleness
93
+ // ---------------------------------------------------------------------------
94
+
236
95
  describe("checkAiFilesStaleness", () => {
237
96
  beforeEach(() => vi.clearAllMocks());
238
97
  afterEach(() => {
@@ -246,7 +105,12 @@ describe("checkAiFilesStaleness", () => {
246
105
  test("logs install nudge when no state file exists, even with null canonical values", async () => {
247
106
  mockReadAiConfig.mockResolvedValue(null);
248
107
 
249
- await checkAiFilesStaleness(null, null, dummyProjectDir, dummyConvexDir);
108
+ await checkAiFilesStaleness({
109
+ canonicalGuidelinesHash: null,
110
+ canonicalAgentSkillsSha: null,
111
+ projectDir: dummyProjectDir,
112
+ convexDir: dummyConvexDir,
113
+ });
250
114
 
251
115
  expect(mockReadAiConfig).toHaveBeenCalled();
252
116
  expect(mockLogMessage).toHaveBeenCalledWith(
@@ -263,7 +127,12 @@ describe("checkAiFilesStaleness", () => {
263
127
  guidelinesHash: "some-hash",
264
128
  });
265
129
 
266
- await checkAiFilesStaleness(null, null, dummyProjectDir, dummyConvexDir);
130
+ await checkAiFilesStaleness({
131
+ canonicalGuidelinesHash: null,
132
+ canonicalAgentSkillsSha: null,
133
+ projectDir: dummyProjectDir,
134
+ convexDir: dummyConvexDir,
135
+ });
267
136
 
268
137
  expect(mockLogMessage).not.toHaveBeenCalled();
269
138
  });
@@ -271,12 +140,12 @@ describe("checkAiFilesStaleness", () => {
271
140
  test("logs install nudge when no state file exists, even if canonical hashes are available", async () => {
272
141
  mockReadAiConfig.mockResolvedValue(null);
273
142
 
274
- await checkAiFilesStaleness(
275
- "canonical-hash",
276
- null,
277
- dummyProjectDir,
278
- dummyConvexDir,
279
- );
143
+ await checkAiFilesStaleness({
144
+ canonicalGuidelinesHash: "canonical-hash",
145
+ canonicalAgentSkillsSha: null,
146
+ projectDir: dummyProjectDir,
147
+ convexDir: dummyConvexDir,
148
+ });
280
149
 
281
150
  expect(mockLogMessage).toHaveBeenCalledWith(
282
151
  expect.stringContaining("npx convex ai-files install"),
@@ -289,40 +158,18 @@ describe("checkAiFilesStaleness", () => {
289
158
  );
290
159
  });
291
160
 
292
- test("adds agent-specific guidance when in agent mode", async () => {
293
- vi.stubEnv("CONVEX_AGENT_MODE", "anonymous");
294
- mockReadAiConfig.mockResolvedValue(null);
295
-
296
- await checkAiFilesStaleness(
297
- "canonical-hash",
298
- null,
299
- dummyProjectDir,
300
- dummyConvexDir,
301
- );
302
-
303
- expect(mockLogMessage).toHaveBeenCalledWith(
304
- expect.stringContaining("If you are an agent tell the human to run"),
305
- );
306
- expect(mockLogMessage).toHaveBeenCalledWith(
307
- expect.stringContaining("npx convex ai-files install"),
308
- );
309
- expect(mockLogMessage).toHaveBeenCalledWith(
310
- expect.stringContaining("npx convex ai-files disable"),
311
- );
312
- });
313
-
314
- test("does nothing when config has disableStalenessMessage=true (user opted out)", async () => {
161
+ test("does nothing when config has enabled=false (user opted out)", async () => {
315
162
  mockReadAiConfig.mockResolvedValue({
316
163
  ...baseConfig,
317
- disableStalenessMessage: true,
164
+ enabled: false,
318
165
  });
319
166
 
320
- await checkAiFilesStaleness(
321
- "canonical-hash",
322
- null,
323
- dummyProjectDir,
324
- dummyConvexDir,
325
- );
167
+ await checkAiFilesStaleness({
168
+ canonicalGuidelinesHash: "canonical-hash",
169
+ canonicalAgentSkillsSha: null,
170
+ projectDir: dummyProjectDir,
171
+ convexDir: dummyConvexDir,
172
+ });
326
173
 
327
174
  expect(mockLogMessage).not.toHaveBeenCalled();
328
175
  });
@@ -333,12 +180,12 @@ describe("checkAiFilesStaleness", () => {
333
180
  guidelinesHash: "same-hash",
334
181
  });
335
182
 
336
- await checkAiFilesStaleness(
337
- "same-hash",
338
- null,
339
- dummyProjectDir,
340
- dummyConvexDir,
341
- );
183
+ await checkAiFilesStaleness({
184
+ canonicalGuidelinesHash: "same-hash",
185
+ canonicalAgentSkillsSha: null,
186
+ projectDir: dummyProjectDir,
187
+ convexDir: dummyConvexDir,
188
+ });
342
189
 
343
190
  expect(mockLogMessage).not.toHaveBeenCalled();
344
191
  });
@@ -349,12 +196,12 @@ describe("checkAiFilesStaleness", () => {
349
196
  guidelinesHash: "old-hash",
350
197
  });
351
198
 
352
- await checkAiFilesStaleness(
353
- "new-canonical-hash",
354
- null,
355
- dummyProjectDir,
356
- dummyConvexDir,
357
- );
199
+ await checkAiFilesStaleness({
200
+ canonicalGuidelinesHash: "new-canonical-hash",
201
+ canonicalAgentSkillsSha: null,
202
+ projectDir: dummyProjectDir,
203
+ convexDir: dummyConvexDir,
204
+ });
358
205
 
359
206
  expect(mockLogMessage).toHaveBeenCalledWith(
360
207
  expect.stringContaining("npx convex ai-files update"),
@@ -368,12 +215,12 @@ describe("checkAiFilesStaleness", () => {
368
215
  agentSkillsSha: "old-sha",
369
216
  });
370
217
 
371
- await checkAiFilesStaleness(
372
- "current-hash",
373
- "new-sha",
374
- dummyProjectDir,
375
- dummyConvexDir,
376
- );
218
+ await checkAiFilesStaleness({
219
+ canonicalGuidelinesHash: "current-hash",
220
+ canonicalAgentSkillsSha: "new-sha",
221
+ projectDir: dummyProjectDir,
222
+ convexDir: dummyConvexDir,
223
+ });
377
224
 
378
225
  expect(mockLogMessage).toHaveBeenCalledWith(
379
226
  expect.stringContaining("npx convex ai-files update"),
@@ -383,30 +230,33 @@ describe("checkAiFilesStaleness", () => {
383
230
  test("does nothing when stored guidelinesHash is null (never written)", async () => {
384
231
  mockReadAiConfig.mockResolvedValue(baseConfig);
385
232
 
386
- await checkAiFilesStaleness(
387
- "some-hash",
388
- "some-sha",
389
- dummyProjectDir,
390
- dummyConvexDir,
391
- );
233
+ await checkAiFilesStaleness({
234
+ canonicalGuidelinesHash: "some-hash",
235
+ canonicalAgentSkillsSha: "some-sha",
236
+ projectDir: dummyProjectDir,
237
+ convexDir: dummyConvexDir,
238
+ });
392
239
 
393
240
  expect(mockLogMessage).not.toHaveBeenCalled();
394
241
  });
395
242
  });
396
243
 
397
244
  // ---------------------------------------------------------------------------
398
- // updateAiFiles — mock-based.
245
+ // installAiFiles
399
246
  // ---------------------------------------------------------------------------
400
247
 
401
- describe("updateAiFiles", () => {
248
+ describe("installAiFiles", () => {
402
249
  beforeEach(() => {
403
250
  vi.clearAllMocks();
404
251
  mockFetchAgentSkillsSha.mockResolvedValue("canonical-sha-abc123");
405
252
  mockGetVersion.mockResolvedValue({
406
- message: null,
407
- guidelinesHash: null,
408
- agentSkillsSha: "canonical-sha-abc123",
409
- disableSkillsCli: false,
253
+ kind: "ok",
254
+ data: {
255
+ message: null,
256
+ guidelinesHash: null,
257
+ agentSkillsSha: "canonical-sha-abc123",
258
+ disableSkillsCli: false,
259
+ },
410
260
  });
411
261
  });
412
262
  afterEach(() => vi.resetAllMocks());
@@ -422,7 +272,7 @@ describe("updateAiFiles", () => {
422
272
 
423
273
  mockDownloadGuidelines.mockResolvedValue("guidelines content");
424
274
 
425
- await updateAiFiles(tmpDir, convexDir);
275
+ await installAiFiles({ projectDir: tmpDir, convexDir });
426
276
 
427
277
  expect(
428
278
  fs.existsSync(
@@ -441,32 +291,6 @@ describe("updateAiFiles", () => {
441
291
  }
442
292
  });
443
293
 
444
- test("reports up to date when guidelines hash already matches", async () => {
445
- mockDownloadGuidelines.mockResolvedValue("guidelines content");
446
- const tmpDir = fs.mkdtempSync(`${os.tmpdir()}${path.sep}`);
447
- const convexDir = path.join(tmpDir, "convex");
448
-
449
- const { hashSha256 } = await import("../utils/hash.js");
450
- const realHash = hashSha256("guidelines content");
451
-
452
- mockReadAiConfig.mockResolvedValue({
453
- ...baseConfig,
454
- guidelinesHash: realHash,
455
- agentSkillsSha: "canonical-sha-abc123",
456
- });
457
-
458
- try {
459
- await updateAiFiles(tmpDir, convexDir);
460
-
461
- expect(mockLogMessage).toHaveBeenCalledWith(
462
- expect.stringContaining("already up to date"),
463
- );
464
- expect(mockCaptureException).not.toHaveBeenCalled();
465
- } finally {
466
- fs.rmSync(tmpDir, { recursive: true, force: true });
467
- }
468
- });
469
-
470
294
  test("stores canonical agentSkillsSha and skill names after successful install", async () => {
471
295
  const tmpDir = fs.mkdtempSync(`${os.tmpdir()}${path.sep}`);
472
296
  const convexDir = path.join(tmpDir, "convex");
@@ -490,37 +314,37 @@ describe("updateAiFiles", () => {
490
314
  agentSkillsSha: "old-sha",
491
315
  });
492
316
 
493
- await updateAiFiles(tmpDir, convexDir);
317
+ await installAiFiles({ projectDir: tmpDir, convexDir });
494
318
 
495
319
  expect(mockWriteAiConfig).toHaveBeenCalledWith(
496
320
  expect.objectContaining({
497
- agentSkillsSha: "canonical-sha-abc123",
498
- installedSkillNames: ["migration-helper", "schema-builder"],
321
+ config: expect.objectContaining({
322
+ agentSkillsSha: "canonical-sha-abc123",
323
+ installedSkillNames: ["migration-helper", "schema-builder"],
324
+ }),
499
325
  }),
500
- expect.anything(),
501
- expect.anything(),
502
326
  );
503
327
  } finally {
504
328
  fs.rmSync(tmpDir, { recursive: true, force: true });
505
329
  }
506
330
  });
507
331
 
508
- test("update does not clear disableStalenessMessage when set true", async () => {
332
+ test("update does not clear enabled=false when set", async () => {
509
333
  const tmpDir = fs.mkdtempSync(`${os.tmpdir()}${path.sep}`);
510
334
  const convexDir = path.join(tmpDir, "convex");
511
335
  try {
512
336
  mockReadAiConfig.mockResolvedValue({
513
337
  ...baseConfig,
514
- disableStalenessMessage: true,
338
+ enabled: false,
515
339
  });
516
340
  mockDownloadGuidelines.mockResolvedValue(null);
517
341
 
518
- await updateAiFiles(tmpDir, convexDir);
342
+ await installAiFiles({ projectDir: tmpDir, convexDir });
519
343
 
520
344
  expect(mockWriteAiConfig).toHaveBeenCalledWith(
521
- expect.objectContaining({ disableStalenessMessage: true }),
522
- expect.anything(),
523
- expect.anything(),
345
+ expect.objectContaining({
346
+ config: expect.objectContaining({ enabled: false }),
347
+ }),
524
348
  );
525
349
  } finally {
526
350
  fs.rmSync(tmpDir, { recursive: true, force: true });
@@ -536,12 +360,12 @@ describe("updateAiFiles", () => {
536
360
  fs.writeFileSync(path.join(tmpDir, "convex.json"), "{}");
537
361
  mockReadAiConfig.mockResolvedValue({
538
362
  ...baseConfig,
539
- disableStalenessMessage: true,
363
+ enabled: false,
540
364
  guidelinesHash: null,
541
365
  });
542
366
  mockDownloadGuidelines.mockResolvedValue("fresh guidelines");
543
367
 
544
- await updateAiFiles(tmpDir, convexDir);
368
+ await installAiFiles({ projectDir: tmpDir, convexDir });
545
369
 
546
370
  expect(fs.existsSync(path.join(convexDir, "_generated", "ai"))).toBe(
547
371
  true,
@@ -553,11 +377,12 @@ describe("updateAiFiles", () => {
553
377
  ).toBe(true);
554
378
  expect(mockWriteAiConfig).toHaveBeenCalledWith(
555
379
  expect.objectContaining({
556
- disableStalenessMessage: true,
557
- guidelinesHash: expect.any(String),
380
+ config: expect.objectContaining({
381
+ enabled: false,
382
+ guidelinesHash: expect.any(String),
383
+ }),
384
+ projectDir: tmpDir,
558
385
  }),
559
- tmpDir,
560
- expect.anything(),
561
386
  );
562
387
  } finally {
563
388
  fs.rmSync(tmpDir, { recursive: true, force: true });
@@ -571,7 +396,7 @@ describe("updateAiFiles", () => {
571
396
  mockReadAiConfig.mockResolvedValue(baseConfig);
572
397
  mockDownloadGuidelines.mockResolvedValue(null);
573
398
 
574
- await updateAiFiles(tmpDir, convexDir);
399
+ await installAiFiles({ projectDir: tmpDir, convexDir });
575
400
 
576
401
  expect(mockLogMessage).toHaveBeenCalledWith(
577
402
  expect.stringContaining("Could not download Convex AI guidelines"),
@@ -588,13 +413,16 @@ describe("updateAiFiles", () => {
588
413
  mockReadAiConfig.mockResolvedValue(baseConfig);
589
414
  mockDownloadGuidelines.mockResolvedValue("guidelines content");
590
415
  mockGetVersion.mockResolvedValue({
591
- message: null,
592
- guidelinesHash: null,
593
- agentSkillsSha: null,
594
- disableSkillsCli: true,
416
+ kind: "ok",
417
+ data: {
418
+ message: null,
419
+ guidelinesHash: null,
420
+ agentSkillsSha: null,
421
+ disableSkillsCli: true,
422
+ },
595
423
  });
596
424
 
597
- await updateAiFiles(tmpDir, convexDir);
425
+ await installAiFiles({ projectDir: tmpDir, convexDir });
598
426
 
599
427
  const { default: cp } = await import("child_process");
600
428
  const spawnCalls = vi.mocked(cp.spawn).mock.calls;
@@ -612,7 +440,7 @@ describe("updateAiFiles", () => {
612
440
  });
613
441
 
614
442
  // ---------------------------------------------------------------------------
615
- // removeAiFiles — tested with real temp directories.
443
+ // removeAiFiles
616
444
  // ---------------------------------------------------------------------------
617
445
 
618
446
  describe("removeAiFiles", () => {
@@ -622,10 +450,13 @@ describe("removeAiFiles", () => {
622
450
  beforeEach(() => {
623
451
  vi.clearAllMocks();
624
452
  mockGetVersion.mockResolvedValue({
625
- message: null,
626
- guidelinesHash: null,
627
- agentSkillsSha: "canonical-sha-abc123",
628
- disableSkillsCli: false,
453
+ kind: "ok",
454
+ data: {
455
+ message: null,
456
+ guidelinesHash: null,
457
+ agentSkillsSha: "canonical-sha-abc123",
458
+ disableSkillsCli: false,
459
+ },
629
460
  });
630
461
  tmpDir = fs.mkdtempSync(`${os.tmpdir()}${path.sep}`);
631
462
  convexDir = path.join(tmpDir, "convex");
@@ -649,10 +480,9 @@ describe("removeAiFiles", () => {
649
480
  }
650
481
 
651
482
  test("logs nothing-to-remove when no config exists", async () => {
652
- // No config file written — readAiConfig returns null.
653
483
  mockReadAiConfig.mockResolvedValue(null);
654
484
 
655
- await removeAiFiles(tmpDir, convexDir);
485
+ await removeAiFiles({ projectDir: tmpDir, convexDir });
656
486
 
657
487
  expect(mockLogMessage).toHaveBeenCalledWith(
658
488
  expect.stringContaining("nothing to remove"),
@@ -666,7 +496,7 @@ describe("removeAiFiles", () => {
666
496
  const agentsMdContent = `${AGENTS_MD_START_MARKER}\n## Convex\nGuidelines.\n${AGENTS_MD_END_MARKER}\n`;
667
497
  fs.writeFileSync(path.join(tmpDir, "AGENTS.md"), agentsMdContent, "utf8");
668
498
 
669
- await removeAiFiles(tmpDir, convexDir);
499
+ await removeAiFiles({ projectDir: tmpDir, convexDir });
670
500
 
671
501
  expect(fs.existsSync(path.join(tmpDir, "AGENTS.md"))).toBe(false);
672
502
  });
@@ -681,7 +511,7 @@ describe("removeAiFiles", () => {
681
511
  `# After\n`;
682
512
  fs.writeFileSync(path.join(tmpDir, "AGENTS.md"), agentsMdContent, "utf8");
683
513
 
684
- await removeAiFiles(tmpDir, convexDir);
514
+ await removeAiFiles({ projectDir: tmpDir, convexDir });
685
515
 
686
516
  const result = fs.readFileSync(path.join(tmpDir, "AGENTS.md"), "utf8");
687
517
  expect(result).toContain("# My project");
@@ -696,7 +526,7 @@ describe("removeAiFiles", () => {
696
526
  const managed = `${CLAUDE_MD_START_MARKER}\n## Convex\nRead guidelines.\n${CLAUDE_MD_END_MARKER}\n`;
697
527
  fs.writeFileSync(path.join(tmpDir, "CLAUDE.md"), managed, "utf8");
698
528
 
699
- await removeAiFiles(tmpDir, convexDir);
529
+ await removeAiFiles({ projectDir: tmpDir, convexDir });
700
530
 
701
531
  expect(fs.existsSync(path.join(tmpDir, "CLAUDE.md"))).toBe(false);
702
532
  });
@@ -707,7 +537,7 @@ describe("removeAiFiles", () => {
707
537
 
708
538
  fs.writeFileSync(path.join(tmpDir, "CLAUDE.md"), "User content\n", "utf8");
709
539
 
710
- await removeAiFiles(tmpDir, convexDir);
540
+ await removeAiFiles({ projectDir: tmpDir, convexDir });
711
541
 
712
542
  expect(fs.existsSync(path.join(tmpDir, "CLAUDE.md"))).toBe(true);
713
543
  });
@@ -722,7 +552,7 @@ describe("removeAiFiles", () => {
722
552
  "utf8",
723
553
  );
724
554
 
725
- await removeAiFiles(tmpDir, convexDir);
555
+ await removeAiFiles({ projectDir: tmpDir, convexDir });
726
556
 
727
557
  const content = fs.readFileSync(path.join(tmpDir, "CLAUDE.md"), "utf8");
728
558
  expect(content).toContain("# User header");
@@ -743,7 +573,7 @@ describe("removeAiFiles", () => {
743
573
  "utf8",
744
574
  );
745
575
 
746
- await removeAiFiles(tmpDir, convexDir);
576
+ await removeAiFiles({ projectDir: tmpDir, convexDir });
747
577
 
748
578
  expect(fs.existsSync(path.join(tmpDir, "CLAUDE.md"))).toBe(true);
749
579
  expect(fs.readFileSync(path.join(tmpDir, "CLAUDE.md"), "utf8")).toBe(
@@ -758,9 +588,8 @@ describe("removeAiFiles", () => {
758
588
  installedSkillNames: skillNames,
759
589
  });
760
590
 
761
- await removeAiFiles(tmpDir, convexDir);
591
+ await removeAiFiles({ projectDir: tmpDir, convexDir });
762
592
 
763
- // child_process.spawn should have been called with the skill names.
764
593
  const { default: cp } = await import("child_process");
765
594
  const spawnCalls = vi.mocked(cp.spawn).mock.calls;
766
595
  const removeCall = spawnCalls.find(
@@ -791,7 +620,7 @@ describe("removeAiFiles", () => {
791
620
  "utf8",
792
621
  );
793
622
 
794
- await removeAiFiles(tmpDir, convexDir);
623
+ await removeAiFiles({ projectDir: tmpDir, convexDir });
795
624
 
796
625
  expect(fs.existsSync(path.join(tmpDir, "skills-lock.json"))).toBe(false);
797
626
  });
@@ -817,7 +646,7 @@ describe("removeAiFiles", () => {
817
646
  "utf8",
818
647
  );
819
648
 
820
- await removeAiFiles(tmpDir, convexDir);
649
+ await removeAiFiles({ projectDir: tmpDir, convexDir });
821
650
 
822
651
  expect(fs.existsSync(path.join(tmpDir, "skills-lock.json"))).toBe(true);
823
652
  });
@@ -829,13 +658,16 @@ describe("removeAiFiles", () => {
829
658
  installedSkillNames: skillNames,
830
659
  });
831
660
  mockGetVersion.mockResolvedValue({
832
- message: null,
833
- guidelinesHash: null,
834
- agentSkillsSha: null,
835
- disableSkillsCli: true,
661
+ kind: "ok",
662
+ data: {
663
+ message: null,
664
+ guidelinesHash: null,
665
+ agentSkillsSha: null,
666
+ disableSkillsCli: true,
667
+ },
836
668
  });
837
669
 
838
- await removeAiFiles(tmpDir, convexDir);
670
+ await removeAiFiles({ projectDir: tmpDir, convexDir });
839
671
 
840
672
  const { default: cp } = await import("child_process");
841
673
  const spawnCalls = vi.mocked(cp.spawn).mock.calls;
@@ -852,13 +684,12 @@ describe("removeAiFiles", () => {
852
684
  writeConfig();
853
685
  mockReadAiConfig.mockResolvedValue(baseConfig);
854
686
 
855
- await removeAiFiles(tmpDir, convexDir);
687
+ await removeAiFiles({ projectDir: tmpDir, convexDir });
856
688
 
857
- // removeAiFiles should not call writeAiConfig — that is disableAiFiles's job.
858
689
  expect(mockWriteAiConfig).not.toHaveBeenCalled();
859
690
  });
860
691
 
861
- test("disableAiFiles writes disableStalenessMessage=true without removing files", async () => {
692
+ test("safelyAttemptToDisableAiFiles writes enabled=false without removing files", async () => {
862
693
  writeConfig({ guidelinesHash: null });
863
694
  mockReadAiConfig.mockResolvedValue(baseConfig);
864
695
 
@@ -868,31 +699,31 @@ describe("removeAiFiles", () => {
868
699
  "utf8",
869
700
  );
870
701
 
871
- await disableAiFiles(tmpDir);
702
+ await safelyAttemptToDisableAiFiles(tmpDir);
872
703
 
873
- expect(mockWriteAiDisabledToProjectConfig).toHaveBeenCalledWith(
874
- true,
875
- tmpDir,
876
- );
704
+ expect(mockWriteAiEnabledToProjectConfig).toHaveBeenCalledWith({
705
+ enabled: false,
706
+ projectDir: tmpDir,
707
+ });
877
708
  expect(
878
709
  fs.existsSync(path.join(convexDir, "_generated", "ai", "guidelines.md")),
879
710
  ).toBe(true);
880
711
  });
881
712
 
882
- test("disableAiFiles writes config to project root, not convex dir", async () => {
713
+ test("safelyAttemptToDisableAiFiles writes config to project root, not convex dir", async () => {
883
714
  mockReadAiConfig.mockResolvedValue(null);
884
715
 
885
- await disableAiFiles(tmpDir);
716
+ await safelyAttemptToDisableAiFiles(tmpDir);
886
717
 
887
- expect(mockWriteAiDisabledToProjectConfig).toHaveBeenCalledWith(
888
- true,
889
- tmpDir,
890
- );
718
+ expect(mockWriteAiEnabledToProjectConfig).toHaveBeenCalledWith({
719
+ enabled: false,
720
+ projectDir: tmpDir,
721
+ });
891
722
  });
892
723
  });
893
724
 
894
725
  // ---------------------------------------------------------------------------
895
- // statusAiFiles — mock-based.
726
+ // statusAiFiles
896
727
  // ---------------------------------------------------------------------------
897
728
 
898
729
  describe("statusAiFiles", () => {
@@ -902,10 +733,13 @@ describe("statusAiFiles", () => {
902
733
  beforeEach(() => {
903
734
  vi.clearAllMocks();
904
735
  mockGetVersion.mockResolvedValue({
905
- message: null,
906
- guidelinesHash: "canonical-guidelines-hash",
907
- agentSkillsSha: "canonical-skills-sha",
908
- disableSkillsCli: false,
736
+ kind: "ok",
737
+ data: {
738
+ message: null,
739
+ guidelinesHash: "canonical-guidelines-hash",
740
+ agentSkillsSha: "canonical-skills-sha",
741
+ disableSkillsCli: false,
742
+ },
909
743
  });
910
744
  });
911
745
  afterEach(() => vi.resetAllMocks());
@@ -913,7 +747,10 @@ describe("statusAiFiles", () => {
913
747
  test("reports not installed when config is null", async () => {
914
748
  mockReadAiConfig.mockResolvedValue(null);
915
749
 
916
- await statusAiFiles(dummyProjectDir, dummyConvexDir);
750
+ await statusAiFiles({
751
+ projectDir: dummyProjectDir,
752
+ convexDir: dummyConvexDir,
753
+ });
917
754
 
918
755
  expect(mockLogMessage).toHaveBeenCalledWith(
919
756
  expect.stringContaining("not installed"),
@@ -923,13 +760,16 @@ describe("statusAiFiles", () => {
923
760
  );
924
761
  });
925
762
 
926
- test("reports disabled when config has disableStalenessMessage=true", async () => {
763
+ test("reports disabled when config has enabled=false", async () => {
927
764
  mockReadAiConfig.mockResolvedValue({
928
765
  ...baseConfig,
929
- disableStalenessMessage: true,
766
+ enabled: false,
930
767
  });
931
768
 
932
- await statusAiFiles(dummyProjectDir, dummyConvexDir);
769
+ await statusAiFiles({
770
+ projectDir: dummyProjectDir,
771
+ convexDir: dummyConvexDir,
772
+ });
933
773
 
934
774
  expect(mockLogMessage).toHaveBeenCalledWith(
935
775
  expect.stringContaining("disabled"),
@@ -939,10 +779,13 @@ describe("statusAiFiles", () => {
939
779
  );
940
780
  });
941
781
 
942
- test("reports enabled when config exists and disableStalenessMessage=false", async () => {
782
+ test("reports enabled when config exists and enabled=true", async () => {
943
783
  mockReadAiConfig.mockResolvedValue(baseConfig);
944
784
 
945
- await statusAiFiles(dummyProjectDir, dummyConvexDir);
785
+ await statusAiFiles({
786
+ projectDir: dummyProjectDir,
787
+ convexDir: dummyConvexDir,
788
+ });
946
789
 
947
790
  expect(mockLogMessage).toHaveBeenCalledWith(
948
791
  expect.stringContaining("enabled"),
@@ -971,13 +814,16 @@ describe("statusAiFiles", () => {
971
814
  guidelinesHash: hash,
972
815
  });
973
816
  mockGetVersion.mockResolvedValue({
974
- message: null,
975
- guidelinesHash: hash,
976
- agentSkillsSha: null,
977
- disableSkillsCli: false,
817
+ kind: "ok",
818
+ data: {
819
+ message: null,
820
+ guidelinesHash: hash,
821
+ agentSkillsSha: null,
822
+ disableSkillsCli: false,
823
+ },
978
824
  });
979
825
 
980
- await statusAiFiles(tmpDir, convexDir);
826
+ await statusAiFiles({ projectDir: tmpDir, convexDir });
981
827
 
982
828
  const calls = mockLogMessage.mock.calls.map((c) => c[0]);
983
829
  expect(calls.some((m) => /guidelines\.md.*up to date/.test(m))).toBe(
@@ -1009,13 +855,16 @@ describe("statusAiFiles", () => {
1009
855
  guidelinesHash: hashSha256(content),
1010
856
  });
1011
857
  mockGetVersion.mockResolvedValue({
1012
- message: null,
1013
- guidelinesHash: "new-canonical-hash",
1014
- agentSkillsSha: null,
1015
- disableSkillsCli: false,
858
+ kind: "ok",
859
+ data: {
860
+ message: null,
861
+ guidelinesHash: "new-canonical-hash",
862
+ agentSkillsSha: null,
863
+ disableSkillsCli: false,
864
+ },
1016
865
  });
1017
866
 
1018
- await statusAiFiles(tmpDir, convexDir);
867
+ await statusAiFiles({ projectDir: tmpDir, convexDir });
1019
868
 
1020
869
  expect(mockLogMessage).toHaveBeenCalledWith(
1021
870
  expect.stringContaining("out of date"),
@@ -1048,7 +897,7 @@ describe("statusAiFiles", () => {
1048
897
  guidelinesHash: hashSha256("original content"),
1049
898
  });
1050
899
 
1051
- await statusAiFiles(tmpDir, convexDir);
900
+ await statusAiFiles({ projectDir: tmpDir, convexDir });
1052
901
 
1053
902
  expect(mockLogMessage).toHaveBeenCalledWith(
1054
903
  expect.stringContaining("modified locally"),
@@ -1065,13 +914,19 @@ describe("statusAiFiles", () => {
1065
914
  agentSkillsSha: "old-sha",
1066
915
  });
1067
916
  mockGetVersion.mockResolvedValue({
1068
- message: null,
1069
- guidelinesHash: null,
1070
- agentSkillsSha: "new-sha",
1071
- disableSkillsCli: false,
917
+ kind: "ok",
918
+ data: {
919
+ message: null,
920
+ guidelinesHash: null,
921
+ agentSkillsSha: "new-sha",
922
+ disableSkillsCli: false,
923
+ },
1072
924
  });
1073
925
 
1074
- await statusAiFiles(dummyProjectDir, dummyConvexDir);
926
+ await statusAiFiles({
927
+ projectDir: dummyProjectDir,
928
+ convexDir: dummyConvexDir,
929
+ });
1075
930
 
1076
931
  expect(mockLogMessage).toHaveBeenCalledWith(
1077
932
  expect.stringContaining("out of date"),
@@ -1088,9 +943,12 @@ describe("statusAiFiles", () => {
1088
943
  agentSkillsSha: "old-sha",
1089
944
  installedSkillNames: ["migration-helper"],
1090
945
  });
1091
- mockGetVersion.mockResolvedValue(null);
946
+ mockGetVersion.mockResolvedValue({ kind: "error" });
1092
947
 
1093
- await statusAiFiles(dummyProjectDir, dummyConvexDir);
948
+ await statusAiFiles({
949
+ projectDir: dummyProjectDir,
950
+ convexDir: dummyConvexDir,
951
+ });
1094
952
 
1095
953
  const calls = mockLogMessage.mock.calls.map((c) => c[0]);
1096
954
  expect(calls.some((m) => /out of date/.test(m))).toBe(false);
@@ -1103,7 +961,10 @@ describe("statusAiFiles", () => {
1103
961
  agentSkillsSha: "canonical-skills-sha",
1104
962
  });
1105
963
 
1106
- await statusAiFiles(dummyProjectDir, dummyConvexDir);
964
+ await statusAiFiles({
965
+ projectDir: dummyProjectDir,
966
+ convexDir: dummyConvexDir,
967
+ });
1107
968
 
1108
969
  expect(mockLogMessage).toHaveBeenCalledWith(
1109
970
  expect.stringContaining("migration-helper"),
@@ -1124,7 +985,7 @@ describe("statusAiFiles", () => {
1124
985
  );
1125
986
  mockReadAiConfig.mockResolvedValue(baseConfig);
1126
987
 
1127
- await statusAiFiles(tmpDir, convexDir);
988
+ await statusAiFiles({ projectDir: tmpDir, convexDir });
1128
989
 
1129
990
  expect(mockLogMessage).toHaveBeenCalledWith(
1130
991
  expect.stringContaining("CLAUDE.md: no Convex section present"),