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
@@ -6,8 +6,9 @@ import { promises as fs } from "fs";
6
6
  import path from "path";
7
7
  import { z } from "zod";
8
8
  import { aiFilesStatePathForConvexDir } from "./paths.js";
9
+ import { iife, readFileSafe } from "./utils.js";
9
10
 
10
- const aiFilesStateSchema = z.object({
11
+ export const aiFilesStateSchema = z.object({
11
12
  guidelinesHash: z.string().nullable(),
12
13
  agentsMdSectionHash: z.string().nullable(),
13
14
  claudeMdHash: z.string().nullable(),
@@ -23,17 +24,21 @@ const aiFilesProjectConfigSchema = z
23
24
  .object({
24
25
  aiFiles: z
25
26
  .object({
26
- disableStalenessMessage: z.boolean().default(false),
27
+ // `enabled` is the canonical field. When present it takes full
28
+ // precedence - `enabled: true` will re-enable even if the legacy
29
+ // disableStalenessMessage field is still `true` in the file.
30
+ enabled: z.boolean().optional(),
31
+ // @deprecated - use `enabled` instead. Read for backward compat;
32
+ // new writes always emit `enabled` and drop this key.
33
+ disableStalenessMessage: z.boolean().optional(),
27
34
  })
28
- .default({ disableStalenessMessage: false }),
35
+ .default({}),
29
36
  })
30
37
  .passthrough();
31
38
 
32
- export const aiFilesSchema = aiFilesStateSchema;
33
-
34
39
  type AiFilesState = z.infer<typeof aiFilesStateSchema>;
35
40
  export type AiFilesConfig = AiFilesState & {
36
- disableStalenessMessage: boolean;
41
+ enabled: boolean;
37
42
  };
38
43
 
39
44
  const EMPTY_AI_STATE: AiFilesState = {
@@ -44,35 +49,38 @@ const EMPTY_AI_STATE: AiFilesState = {
44
49
  installedSkillNames: [],
45
50
  };
46
51
 
47
- async function readAiDisabledFromProjectConfig(
52
+ async function readAiEnabledFromProjectConfig(
48
53
  projectDir: string,
49
54
  ): Promise<boolean> {
50
- let raw: string;
51
- try {
52
- raw = await fs.readFile(path.join(projectDir, "convex.json"), "utf8");
53
- } catch {
54
- return false;
55
- }
55
+ const raw = await readFileSafe(path.join(projectDir, "convex.json"));
56
+ if (raw === null) return true;
56
57
  try {
57
58
  const parsed = aiFilesProjectConfigSchema.parse(JSON.parse(raw));
58
- return parsed.aiFiles.disableStalenessMessage;
59
+ // `enabled` takes full precedence when explicitly set.
60
+ if (parsed.aiFiles.enabled !== undefined) return parsed.aiFiles.enabled;
61
+ // Legacy `disableStalenessMessage` - invert it.
62
+ return !(parsed.aiFiles.disableStalenessMessage ?? false);
59
63
  } catch (err) {
60
64
  Sentry.captureException(err);
61
- return false;
65
+ return true;
62
66
  }
63
67
  }
64
68
 
65
- export async function writeAiDisabledToProjectConfig(
66
- disableStalenessMessage: boolean,
67
- projectDir: string,
68
- ): Promise<void> {
69
+ export async function writeAiEnabledToProjectConfig({
70
+ projectDir,
71
+ enabled,
72
+ }: {
73
+ projectDir: string;
74
+ enabled: boolean;
75
+ }): Promise<void> {
69
76
  const filePath = path.join(projectDir, "convex.json");
70
- let existing: unknown = {};
71
- try {
72
- existing = JSON.parse(await fs.readFile(filePath, "utf8"));
73
- } catch {
74
- // Use a minimal object when convex.json doesn't exist yet or is unreadable.
75
- }
77
+ const existing = await iife(async () => {
78
+ try {
79
+ return JSON.parse(await fs.readFile(filePath, "utf8")) as unknown;
80
+ } catch {
81
+ return {} as unknown;
82
+ }
83
+ });
76
84
  const base =
77
85
  existing !== null &&
78
86
  typeof existing === "object" &&
@@ -86,54 +94,75 @@ export async function writeAiDisabledToProjectConfig(
86
94
  ? (base.aiFiles as Record<string, unknown>)
87
95
  : {};
88
96
  const { $schema, ...rest } = base;
97
+ // Remove legacy keys on every write.
98
+ const { disableStalenessMessage: _legacy, ...restAiFiles } = aiFilesValue;
89
99
  const next: Record<string, unknown> = {
90
100
  $schema: $schema ?? "node_modules/convex/schemas/convex.schema.json",
91
101
  ...rest,
92
- aiFiles: {
93
- ...aiFilesValue,
94
- disableStalenessMessage,
95
- },
102
+ aiFiles: { ...restAiFiles, enabled },
96
103
  };
97
104
  await fs.writeFile(filePath, JSON.stringify(next, null, 2) + "\n", "utf8");
98
105
  }
99
106
 
100
- export async function readAiConfig(
101
- projectDir: string,
102
- convexDir: string,
103
- ): Promise<AiFilesConfig | null> {
104
- const disableStalenessMessage =
105
- await readAiDisabledFromProjectConfig(projectDir);
106
- let rawState: string;
107
- try {
108
- rawState = await fs.readFile(
109
- aiFilesStatePathForConvexDir(convexDir),
110
- "utf8",
111
- );
112
- } catch {
107
+ export async function readAiConfig({
108
+ projectDir,
109
+ convexDir,
110
+ }: {
111
+ projectDir: string;
112
+ convexDir: string;
113
+ }): Promise<AiFilesConfig | null> {
114
+ const enabled = await readAiEnabledFromProjectConfig(projectDir);
115
+ const rawState = await readFileSafe(aiFilesStatePathForConvexDir(convexDir));
116
+ if (rawState === null) {
113
117
  // No state file means AI files are not installed, unless the user has
114
- // explicitly disabled install/staleness messages in convex.json.
115
- return disableStalenessMessage
116
- ? { ...EMPTY_AI_STATE, disableStalenessMessage }
117
- : null;
118
+ // explicitly disabled in convex.json.
119
+ return !enabled ? { ...EMPTY_AI_STATE, enabled } : null;
118
120
  }
119
121
  try {
120
122
  const state = aiFilesStateSchema.parse(JSON.parse(rawState));
121
- return {
122
- ...state,
123
- disableStalenessMessage,
124
- };
123
+ return { ...state, enabled };
125
124
  } catch (err) {
126
125
  Sentry.captureException(err);
127
126
  return null;
128
127
  }
129
128
  }
130
129
 
131
- export async function writeAiConfig(
132
- config: AiFilesConfig,
133
- projectDir: string,
134
- convexDir: string,
135
- options?: { persistDisabledPreference?: "ifTrue" | "always" | "never" },
136
- ): Promise<void> {
130
+ export async function hasAiFilesConfig({
131
+ projectDir,
132
+ convexDir,
133
+ }: {
134
+ projectDir: string;
135
+ convexDir: string;
136
+ }): Promise<boolean> {
137
+ if (!(await readAiEnabledFromProjectConfig(projectDir))) {
138
+ return true;
139
+ }
140
+ try {
141
+ const rawState = await fs.readFile(
142
+ aiFilesStatePathForConvexDir(convexDir),
143
+ "utf8",
144
+ );
145
+ aiFilesStateSchema.parse(JSON.parse(rawState));
146
+ return true;
147
+ } catch (err) {
148
+ if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
149
+ Sentry.captureException(err);
150
+ }
151
+ return false;
152
+ }
153
+ }
154
+
155
+ export async function writeAiConfig({
156
+ config,
157
+ projectDir,
158
+ convexDir,
159
+ options,
160
+ }: {
161
+ config: AiFilesConfig;
162
+ projectDir: string;
163
+ convexDir: string;
164
+ options?: { persistEnabledPreference?: "ifFalse" | "always" | "never" };
165
+ }): Promise<void> {
137
166
  const state = aiFilesStateSchema.parse({
138
167
  guidelinesHash: config.guidelinesHash,
139
168
  agentsMdSectionHash: config.agentsMdSectionHash,
@@ -146,14 +175,14 @@ export async function writeAiConfig(
146
175
  JSON.stringify(state, null, 2) + "\n",
147
176
  "utf8",
148
177
  );
149
- const persistMode = options?.persistDisabledPreference ?? "ifTrue";
178
+
179
+ const persistMode = options?.persistEnabledPreference ?? "ifFalse";
150
180
  if (
151
181
  persistMode === "always" ||
152
- (persistMode === "ifTrue" && config.disableStalenessMessage)
153
- ) {
154
- await writeAiDisabledToProjectConfig(
155
- config.disableStalenessMessage,
182
+ (persistMode === "ifFalse" && !config.enabled)
183
+ )
184
+ await writeAiEnabledToProjectConfig({
156
185
  projectDir,
157
- );
158
- }
186
+ enabled: config.enabled,
187
+ });
159
188
  }
@@ -0,0 +1,25 @@
1
+ import path from "path";
2
+ import { chalkStderr } from "chalk";
3
+ import { logMessage } from "../../../bundler/log.js";
4
+ import { safelyDeleteFile } from "./utils.js";
5
+
6
+ /**
7
+ * Remove the legacy `.cursor/rules/convex_rules.mdc` file if it exists.
8
+ * This file was written by the old cursor rules auto-update feature (removed
9
+ * in favour of the AI files system). We clean it up unconditionally
10
+ * during `writeAiFiles`, `convex ai-files update`, and `convex ai-files remove`
11
+ * since it was always auto-managed and is now superseded by
12
+ * `convex/_generated/ai/guidelines.md`.
13
+ */
14
+ export async function removeLegacyCursorRulesFile(
15
+ projectDir: string,
16
+ ): Promise<boolean> {
17
+ const removed = await safelyDeleteFile(
18
+ path.join(projectDir, ".cursor", "rules", "convex_rules.mdc"),
19
+ );
20
+ if (removed)
21
+ logMessage(
22
+ `${chalkStderr.green("✔")} Removed legacy .cursor/rules/convex_rules.mdc (superseded by convex/_generated/ai/guidelines.md).`,
23
+ );
24
+ return removed;
25
+ }
@@ -0,0 +1,40 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from "vitest";
2
+ import fs from "fs";
3
+ import os from "os";
4
+ import path from "path";
5
+ import { hasGuidelinesInstalled } from "./guidelinesmd.js";
6
+
7
+ describe("hasGuidelinesInstalled", () => {
8
+ let tmpDir: string;
9
+ let convexDir: string;
10
+
11
+ beforeEach(() => {
12
+ tmpDir = fs.mkdtempSync(`${os.tmpdir()}${path.sep}`);
13
+ convexDir = path.join(tmpDir, "convex");
14
+ fs.mkdirSync(path.join(convexDir, "_generated", "ai"), { recursive: true });
15
+ });
16
+
17
+ afterEach(() => {
18
+ fs.rmSync(tmpDir, { recursive: true, force: true });
19
+ });
20
+
21
+ test("returns false when guidelines.md does not exist", async () => {
22
+ expect(await hasGuidelinesInstalled(convexDir)).toBe(false);
23
+ });
24
+
25
+ test("returns true when guidelines.md exists", async () => {
26
+ fs.writeFileSync(
27
+ path.join(convexDir, "_generated", "ai", "guidelines.md"),
28
+ "some content",
29
+ );
30
+ expect(await hasGuidelinesInstalled(convexDir)).toBe(true);
31
+ });
32
+
33
+ test("returns true even when guidelines.md is empty", async () => {
34
+ fs.writeFileSync(
35
+ path.join(convexDir, "_generated", "ai", "guidelines.md"),
36
+ "",
37
+ );
38
+ expect(await hasGuidelinesInstalled(convexDir)).toBe(true);
39
+ });
40
+ });
@@ -0,0 +1,41 @@
1
+ // eslint-disable-next-line no-restricted-imports
2
+ import { promises as fs } from "fs";
3
+ import { chalkStderr } from "chalk";
4
+ import { logMessage } from "../../../bundler/log.js";
5
+ import { downloadGuidelines } from "../versionApi.js";
6
+ import { hashSha256 } from "../utils/hash.js";
7
+ import { guidelinesPathForConvexDir } from "./paths.js";
8
+ import { readFileSafe } from "./utils.js";
9
+ import { type AiFilesConfig } from "./config.js";
10
+
11
+ export async function hasGuidelinesInstalled(
12
+ convexDir: string,
13
+ ): Promise<boolean> {
14
+ return (await readFileSafe(guidelinesPathForConvexDir(convexDir))) !== null;
15
+ }
16
+
17
+ /**
18
+ * Download and write the guidelines file.
19
+ * Guidelines live in `_generated/` so local edits are not expected and are
20
+ * not preserved.
21
+ */
22
+ export async function installGuidelinesFile({
23
+ convexDir,
24
+ config,
25
+ }: {
26
+ convexDir: string;
27
+ config: AiFilesConfig;
28
+ }): Promise<void> {
29
+ const guidelines = await downloadGuidelines();
30
+ if (guidelines === null) {
31
+ logMessage(
32
+ chalkStderr.yellow(
33
+ "Could not download Convex AI guidelines right now. You can retry with: npx convex ai-files install",
34
+ ),
35
+ );
36
+ return;
37
+ }
38
+
39
+ await fs.writeFile(guidelinesPathForConvexDir(convexDir), guidelines, "utf8");
40
+ config.guidelinesHash = hashSha256(guidelines);
41
+ }