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
@@ -0,0 +1,77 @@
1
+ import {
2
+ AGENTS_MD_START_MARKER,
3
+ AGENTS_MD_END_MARKER,
4
+ agentsMdConvexSection,
5
+ } from "../../codegen_templates/agentsmd.js";
6
+ import { agentsMdPath } from "./paths.js";
7
+ import { type AiFilesConfig } from "./config.js";
8
+ import {
9
+ type ManagedSectionTarget,
10
+ type InjectResult,
11
+ type StripResult,
12
+ injectManagedSection,
13
+ stripManagedSection,
14
+ hasManagedSection,
15
+ removeMarkdownSection,
16
+ } from "./utils.js";
17
+
18
+ function target(projectDir?: string): ManagedSectionTarget {
19
+ return {
20
+ filePath: agentsMdPath(projectDir),
21
+ startMarker: AGENTS_MD_START_MARKER,
22
+ endMarker: AGENTS_MD_END_MARKER,
23
+ };
24
+ }
25
+
26
+ export async function injectAgentsMdSection({
27
+ section,
28
+ projectDir,
29
+ }: {
30
+ section: string;
31
+ projectDir?: string;
32
+ }): Promise<InjectResult> {
33
+ return injectManagedSection({ ...target(projectDir), section });
34
+ }
35
+
36
+ export async function stripAgentsMdSection(
37
+ projectDir: string,
38
+ ): Promise<StripResult> {
39
+ return stripManagedSection(target(projectDir));
40
+ }
41
+
42
+ export async function removeAgentsMdSection(
43
+ projectDir: string,
44
+ ): Promise<boolean> {
45
+ return removeMarkdownSection({
46
+ projectDir,
47
+ strip: stripAgentsMdSection,
48
+ fileName: "AGENTS.md",
49
+ });
50
+ }
51
+
52
+ export async function hasAgentsMdInstalled(
53
+ projectDir: string,
54
+ ): Promise<boolean> {
55
+ return hasManagedSection(target(projectDir));
56
+ }
57
+
58
+ /**
59
+ * Inject (or update) the Convex section in AGENTS.md and record the hash.
60
+ * Returns true if the file was actually written.
61
+ */
62
+ export async function applyAgentsMdSection({
63
+ projectDir,
64
+ config,
65
+ convexDirName,
66
+ }: {
67
+ projectDir: string;
68
+ config: AiFilesConfig;
69
+ convexDirName: string;
70
+ }): Promise<boolean> {
71
+ const result = await injectAgentsMdSection({
72
+ section: agentsMdConvexSection(convexDirName),
73
+ projectDir,
74
+ });
75
+ config.agentsMdSectionHash = result.sectionHash;
76
+ return result.didWrite;
77
+ }
@@ -0,0 +1,92 @@
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 { injectClaudeMdSection, hasClaudeMdInstalled } from "./claudemd.js";
6
+ import {
7
+ CLAUDE_MD_START_MARKER,
8
+ CLAUDE_MD_END_MARKER,
9
+ } from "../../codegen_templates/claudemd.js";
10
+
11
+ describe("injectClaudeMdSection", () => {
12
+ let tmpDir: string;
13
+
14
+ beforeEach(() => {
15
+ tmpDir = fs.mkdtempSync(`${os.tmpdir()}${path.sep}`);
16
+ });
17
+
18
+ afterEach(() => {
19
+ fs.rmSync(tmpDir, { recursive: true, force: true });
20
+ });
21
+
22
+ const section = `${CLAUDE_MD_START_MARKER}\n## Convex\nRead guidelines.\n${CLAUDE_MD_END_MARKER}`;
23
+
24
+ test("creates CLAUDE.md when it does not exist", async () => {
25
+ const result = await injectClaudeMdSection({ section, projectDir: tmpDir });
26
+
27
+ const content = fs.readFileSync(path.join(tmpDir, "CLAUDE.md"), "utf8");
28
+ expect(content).toContain(CLAUDE_MD_START_MARKER);
29
+ expect(content).toContain(CLAUDE_MD_END_MARKER);
30
+ expect(result.didWrite).toBe(true);
31
+ });
32
+
33
+ test("appends managed section to existing CLAUDE.md content", async () => {
34
+ fs.writeFileSync(
35
+ path.join(tmpDir, "CLAUDE.md"),
36
+ "My custom instructions\n",
37
+ );
38
+
39
+ const result = await injectClaudeMdSection({ section, projectDir: tmpDir });
40
+
41
+ const content = fs.readFileSync(path.join(tmpDir, "CLAUDE.md"), "utf8");
42
+ expect(content).toContain("My custom instructions");
43
+ expect(content).toContain(CLAUDE_MD_START_MARKER);
44
+ expect(result.didWrite).toBe(true);
45
+ });
46
+
47
+ test("replaces managed section without touching user content", async () => {
48
+ const oldSection = `${CLAUDE_MD_START_MARKER}\nOld\n${CLAUDE_MD_END_MARKER}`;
49
+ fs.writeFileSync(
50
+ path.join(tmpDir, "CLAUDE.md"),
51
+ `# Header\n\n${oldSection}\n\n# Footer\n`,
52
+ "utf8",
53
+ );
54
+
55
+ await injectClaudeMdSection({ section, projectDir: tmpDir });
56
+
57
+ const content = fs.readFileSync(path.join(tmpDir, "CLAUDE.md"), "utf8");
58
+ expect(content).toContain("# Header");
59
+ expect(content).toContain("# Footer");
60
+ expect(content).toContain("## Convex");
61
+ expect(content).not.toContain("Old");
62
+ });
63
+ });
64
+
65
+ describe("hasClaudeMdInstalled", () => {
66
+ let tmpDir: string;
67
+
68
+ beforeEach(() => {
69
+ tmpDir = fs.mkdtempSync(`${os.tmpdir()}${path.sep}`);
70
+ });
71
+
72
+ afterEach(() => {
73
+ fs.rmSync(tmpDir, { recursive: true, force: true });
74
+ });
75
+
76
+ test("returns false when CLAUDE.md does not exist", async () => {
77
+ expect(await hasClaudeMdInstalled(tmpDir)).toBe(false);
78
+ });
79
+
80
+ test("returns false when CLAUDE.md exists but has no managed markers", async () => {
81
+ fs.writeFileSync(path.join(tmpDir, "CLAUDE.md"), "User content only\n");
82
+ expect(await hasClaudeMdInstalled(tmpDir)).toBe(false);
83
+ });
84
+
85
+ test("returns true when both markers are present", async () => {
86
+ fs.writeFileSync(
87
+ path.join(tmpDir, "CLAUDE.md"),
88
+ `${CLAUDE_MD_START_MARKER}\n## Convex\n${CLAUDE_MD_END_MARKER}\n`,
89
+ );
90
+ expect(await hasClaudeMdInstalled(tmpDir)).toBe(true);
91
+ });
92
+ });
@@ -0,0 +1,77 @@
1
+ import {
2
+ CLAUDE_MD_END_MARKER,
3
+ CLAUDE_MD_START_MARKER,
4
+ claudeMdConvexSection,
5
+ } from "../../codegen_templates/claudemd.js";
6
+ import { claudeMdPath } from "./paths.js";
7
+ import { type AiFilesConfig } from "./config.js";
8
+ import {
9
+ type ManagedSectionTarget,
10
+ type InjectResult,
11
+ type StripResult,
12
+ injectManagedSection,
13
+ stripManagedSection,
14
+ hasManagedSection,
15
+ removeMarkdownSection,
16
+ } from "./utils.js";
17
+
18
+ function target(projectDir?: string): ManagedSectionTarget {
19
+ return {
20
+ filePath: claudeMdPath(projectDir),
21
+ startMarker: CLAUDE_MD_START_MARKER,
22
+ endMarker: CLAUDE_MD_END_MARKER,
23
+ };
24
+ }
25
+
26
+ export async function injectClaudeMdSection({
27
+ section,
28
+ projectDir,
29
+ }: {
30
+ section: string;
31
+ projectDir?: string;
32
+ }): Promise<InjectResult> {
33
+ return injectManagedSection({ ...target(projectDir), section });
34
+ }
35
+
36
+ export async function stripClaudeMdSection(
37
+ projectDir: string,
38
+ ): Promise<StripResult> {
39
+ return stripManagedSection(target(projectDir));
40
+ }
41
+
42
+ export async function removeClaudeMdSection(
43
+ projectDir: string,
44
+ ): Promise<boolean> {
45
+ return removeMarkdownSection({
46
+ projectDir,
47
+ strip: stripClaudeMdSection,
48
+ fileName: "CLAUDE.md",
49
+ });
50
+ }
51
+
52
+ export async function hasClaudeMdInstalled(
53
+ projectDir: string,
54
+ ): Promise<boolean> {
55
+ return hasManagedSection(target(projectDir));
56
+ }
57
+
58
+ /**
59
+ * Inject (or update) the Convex section in CLAUDE.md and record the hash.
60
+ * Returns true if the file was actually written.
61
+ */
62
+ export async function applyClaudeMdSection({
63
+ projectDir,
64
+ config,
65
+ convexDirName,
66
+ }: {
67
+ projectDir: string;
68
+ config: AiFilesConfig;
69
+ convexDirName: string;
70
+ }): Promise<boolean> {
71
+ const result = await injectClaudeMdSection({
72
+ section: claudeMdConvexSection(convexDirName),
73
+ projectDir,
74
+ });
75
+ config.claudeMdHash = result.sectionHash;
76
+ return result.didWrite;
77
+ }
@@ -2,10 +2,11 @@ import { describe, test, expect, vi, beforeEach, afterEach } from "vitest";
2
2
  import * as Sentry from "@sentry/node";
3
3
  import { promises as fs } from "fs";
4
4
  import {
5
- aiFilesSchema,
5
+ aiFilesStateSchema,
6
+ hasAiFilesConfig,
6
7
  readAiConfig,
7
8
  writeAiConfig,
8
- writeAiDisabledToProjectConfig,
9
+ writeAiEnabledToProjectConfig,
9
10
  } from "./config.js";
10
11
 
11
12
  vi.mock("@sentry/node", () => ({
@@ -26,9 +27,9 @@ const mockCaptureException = vi.mocked(Sentry.captureException);
26
27
  const dummyProjectDir = "/tmp/test-project";
27
28
  const dummyConvexDir = "/tmp/test-project/convex";
28
29
 
29
- describe("aiFilesSchema", () => {
30
+ describe("aiFilesStateSchema", () => {
30
31
  test("accepts a fully populated valid state object", () => {
31
- const result = aiFilesSchema.safeParse({
32
+ const result = aiFilesStateSchema.safeParse({
32
33
  guidelinesHash: "abc123",
33
34
  agentsMdSectionHash: "def456",
34
35
  claudeMdHash: "ghi789",
@@ -39,7 +40,7 @@ describe("aiFilesSchema", () => {
39
40
  });
40
41
 
41
42
  test("accepts null hashes", () => {
42
- const result = aiFilesSchema.safeParse({
43
+ const result = aiFilesStateSchema.safeParse({
43
44
  guidelinesHash: null,
44
45
  agentsMdSectionHash: null,
45
46
  claudeMdHash: null,
@@ -49,7 +50,7 @@ describe("aiFilesSchema", () => {
49
50
  });
50
51
 
51
52
  test("applies default for installedSkillNames when absent", () => {
52
- const result = aiFilesSchema.safeParse({
53
+ const result = aiFilesStateSchema.safeParse({
53
54
  guidelinesHash: "abc",
54
55
  agentsMdSectionHash: null,
55
56
  claudeMdHash: null,
@@ -62,7 +63,7 @@ describe("aiFilesSchema", () => {
62
63
  });
63
64
 
64
65
  test("rejects a number where a string hash is expected", () => {
65
- const result = aiFilesSchema.safeParse({
66
+ const result = aiFilesStateSchema.safeParse({
66
67
  guidelinesHash: 123,
67
68
  agentsMdSectionHash: null,
68
69
  claudeMdHash: null,
@@ -72,7 +73,7 @@ describe("aiFilesSchema", () => {
72
73
  });
73
74
 
74
75
  test("rejects missing required fields", () => {
75
- const result = aiFilesSchema.safeParse({
76
+ const result = aiFilesStateSchema.safeParse({
76
77
  guidelinesHash: "abc",
77
78
  });
78
79
  expect(result.success).toBe(false);
@@ -88,13 +89,16 @@ describe("readAiConfig", () => {
88
89
  Object.assign(new Error("ENOENT"), { code: "ENOENT" }),
89
90
  );
90
91
 
91
- const result = await readAiConfig(dummyProjectDir, dummyConvexDir);
92
+ const result = await readAiConfig({
93
+ projectDir: dummyProjectDir,
94
+ convexDir: dummyConvexDir,
95
+ });
92
96
 
93
97
  expect(result).toBeNull();
94
98
  expect(mockCaptureException).not.toHaveBeenCalled();
95
99
  });
96
100
 
97
- test("returns parsed state with disableStalenessMessage=false when convex.json is missing", async () => {
101
+ test("returns parsed state with enabled=true when convex.json is missing", async () => {
98
102
  mockFs.readFile
99
103
  .mockRejectedValueOnce(
100
104
  Object.assign(new Error("ENOENT"), { code: "ENOENT" }),
@@ -108,7 +112,10 @@ describe("readAiConfig", () => {
108
112
  }),
109
113
  );
110
114
 
111
- const result = await readAiConfig(dummyProjectDir, dummyConvexDir);
115
+ const result = await readAiConfig({
116
+ projectDir: dummyProjectDir,
117
+ convexDir: dummyConvexDir,
118
+ });
112
119
 
113
120
  expect(result).toEqual({
114
121
  guidelinesHash: "abc",
@@ -116,7 +123,7 @@ describe("readAiConfig", () => {
116
123
  claudeMdHash: null,
117
124
  agentSkillsSha: null,
118
125
  installedSkillNames: [],
119
- disableStalenessMessage: false,
126
+ enabled: true,
120
127
  });
121
128
  });
122
129
 
@@ -129,7 +136,10 @@ describe("readAiConfig", () => {
129
136
  Object.assign(new Error("ENOENT"), { code: "ENOENT" }),
130
137
  );
131
138
 
132
- const result = await readAiConfig(dummyProjectDir, dummyConvexDir);
139
+ const result = await readAiConfig({
140
+ projectDir: dummyProjectDir,
141
+ convexDir: dummyConvexDir,
142
+ });
133
143
 
134
144
  expect(result).toEqual({
135
145
  guidelinesHash: null,
@@ -137,7 +147,7 @@ describe("readAiConfig", () => {
137
147
  claudeMdHash: null,
138
148
  agentSkillsSha: null,
139
149
  installedSkillNames: [],
140
- disableStalenessMessage: true,
150
+ enabled: false,
141
151
  });
142
152
  });
143
153
 
@@ -148,7 +158,10 @@ describe("readAiConfig", () => {
148
158
  )
149
159
  .mockResolvedValueOnce("not valid json {{{}");
150
160
 
151
- const result = await readAiConfig(dummyProjectDir, dummyConvexDir);
161
+ const result = await readAiConfig({
162
+ projectDir: dummyProjectDir,
163
+ convexDir: dummyConvexDir,
164
+ });
152
165
 
153
166
  expect(result).toBeNull();
154
167
  expect(mockCaptureException).toHaveBeenCalledWith(expect.any(Error));
@@ -166,18 +179,21 @@ describe("readAiConfig", () => {
166
179
  }),
167
180
  );
168
181
 
169
- const result = await readAiConfig(dummyProjectDir, dummyConvexDir);
182
+ const result = await readAiConfig({
183
+ projectDir: dummyProjectDir,
184
+ convexDir: dummyConvexDir,
185
+ });
170
186
 
171
187
  expect(result).toBeNull();
172
188
  expect(mockCaptureException).toHaveBeenCalledWith(expect.any(Error));
173
189
  });
174
190
 
175
- test("returns parsed state and reads disableStalenessMessage from convex.json", async () => {
191
+ test("reads enabled=false from legacy disableStalenessMessage field for backward compat", async () => {
176
192
  const stored = {
177
193
  guidelinesHash: "abc",
178
194
  agentsMdSectionHash: "def",
179
195
  claudeMdHash: null,
180
- agentSkillsSha: "deadbeef",
196
+ agentSkillsSha: null,
181
197
  };
182
198
  mockFs.readFile
183
199
  .mockResolvedValueOnce(
@@ -185,13 +201,113 @@ describe("readAiConfig", () => {
185
201
  )
186
202
  .mockResolvedValueOnce(JSON.stringify(stored));
187
203
 
188
- const result = await readAiConfig(dummyProjectDir, dummyConvexDir);
204
+ const result = await readAiConfig({
205
+ projectDir: dummyProjectDir,
206
+ convexDir: dummyConvexDir,
207
+ });
189
208
 
190
209
  expect(result).toEqual({
191
210
  ...stored,
192
211
  installedSkillNames: [],
193
- disableStalenessMessage: true,
212
+ enabled: false,
213
+ });
214
+ });
215
+
216
+ test("enabled: true takes precedence over legacy disableStalenessMessage: true", async () => {
217
+ const stored = {
218
+ guidelinesHash: "abc",
219
+ agentsMdSectionHash: "def",
220
+ claudeMdHash: null,
221
+ agentSkillsSha: null,
222
+ };
223
+ mockFs.readFile
224
+ .mockResolvedValueOnce(
225
+ JSON.stringify({
226
+ aiFiles: { enabled: true, disableStalenessMessage: true },
227
+ }),
228
+ )
229
+ .mockResolvedValueOnce(JSON.stringify(stored));
230
+
231
+ const result = await readAiConfig({
232
+ projectDir: dummyProjectDir,
233
+ convexDir: dummyConvexDir,
234
+ });
235
+
236
+ expect(result).toEqual({
237
+ ...stored,
238
+ installedSkillNames: [],
239
+ enabled: true,
240
+ });
241
+ });
242
+ });
243
+
244
+ describe("hasAiFilesConfig", () => {
245
+ beforeEach(() => vi.clearAllMocks());
246
+ afterEach(() => vi.resetAllMocks());
247
+
248
+ test("returns false when neither convex.json nor state file exists", async () => {
249
+ mockFs.readFile.mockRejectedValue(
250
+ Object.assign(new Error("ENOENT"), { code: "ENOENT" }),
251
+ );
252
+
253
+ const result = await hasAiFilesConfig({
254
+ projectDir: dummyProjectDir,
255
+ convexDir: dummyConvexDir,
256
+ });
257
+
258
+ expect(result).toBe(false);
259
+ expect(mockCaptureException).not.toHaveBeenCalled();
260
+ });
261
+
262
+ test("returns true when convex.json disables staleness messages", async () => {
263
+ mockFs.readFile.mockResolvedValueOnce(
264
+ JSON.stringify({ aiFiles: { disableStalenessMessage: true } }),
265
+ );
266
+
267
+ const result = await hasAiFilesConfig({
268
+ projectDir: dummyProjectDir,
269
+ convexDir: dummyConvexDir,
194
270
  });
271
+
272
+ expect(result).toBe(true);
273
+ });
274
+
275
+ test("returns true when a valid state file exists", async () => {
276
+ mockFs.readFile
277
+ .mockRejectedValueOnce(
278
+ Object.assign(new Error("ENOENT"), { code: "ENOENT" }),
279
+ )
280
+ .mockResolvedValueOnce(
281
+ JSON.stringify({
282
+ guidelinesHash: "abc",
283
+ agentsMdSectionHash: "def",
284
+ claudeMdHash: null,
285
+ agentSkillsSha: null,
286
+ }),
287
+ );
288
+
289
+ const result = await hasAiFilesConfig({
290
+ projectDir: dummyProjectDir,
291
+ convexDir: dummyConvexDir,
292
+ });
293
+
294
+ expect(result).toBe(true);
295
+ });
296
+
297
+ test("returns false and captures exception when the state file is invalid", async () => {
298
+ mockFs.readFile
299
+ .mockRejectedValueOnce(
300
+ Object.assign(new Error("ENOENT"), { code: "ENOENT" }),
301
+ )
302
+ .mockResolvedValueOnce("not valid json {{{}");
303
+
304
+ const result = await hasAiFilesConfig({
305
+ projectDir: dummyProjectDir,
306
+ convexDir: dummyConvexDir,
307
+ });
308
+
309
+ expect(result).toBe(false);
310
+ expect(mockCaptureException).toHaveBeenCalledWith(expect.any(Error));
195
311
  });
196
312
  });
197
313
 
@@ -199,11 +315,11 @@ describe("writeAiConfig", () => {
199
315
  beforeEach(() => vi.clearAllMocks());
200
316
  afterEach(() => vi.resetAllMocks());
201
317
 
202
- test("writes state file and does not persist disableStalenessMessage=false by default", async () => {
318
+ test("writes state file and does not persist enabled=true by default", async () => {
203
319
  mockFs.writeFile.mockResolvedValue(undefined);
204
320
 
205
321
  const config = {
206
- disableStalenessMessage: false,
322
+ enabled: true,
207
323
  guidelinesHash: "abc",
208
324
  agentsMdSectionHash: "def",
209
325
  claudeMdHash: null,
@@ -211,7 +327,11 @@ describe("writeAiConfig", () => {
211
327
  installedSkillNames: ["convex-migrations"],
212
328
  };
213
329
 
214
- await writeAiConfig(config, dummyProjectDir, dummyConvexDir);
330
+ await writeAiConfig({
331
+ config,
332
+ projectDir: dummyProjectDir,
333
+ convexDir: dummyConvexDir,
334
+ });
215
335
 
216
336
  expect(mockFs.writeFile).toHaveBeenCalledTimes(1);
217
337
  expect(mockFs.writeFile).toHaveBeenCalledWith(
@@ -231,23 +351,23 @@ describe("writeAiConfig", () => {
231
351
  );
232
352
  });
233
353
 
234
- test("persists disableStalenessMessage=false when requested explicitly", async () => {
354
+ test("persists enabled=true when requested explicitly", async () => {
235
355
  mockFs.writeFile.mockResolvedValue(undefined);
236
356
  mockFs.readFile.mockResolvedValue("{}");
237
357
 
238
- await writeAiConfig(
239
- {
240
- disableStalenessMessage: false,
358
+ await writeAiConfig({
359
+ config: {
360
+ enabled: true,
241
361
  guidelinesHash: null,
242
362
  agentsMdSectionHash: null,
243
363
  claudeMdHash: null,
244
364
  agentSkillsSha: null,
245
365
  installedSkillNames: [],
246
366
  },
247
- dummyProjectDir,
248
- dummyConvexDir,
249
- { persistDisabledPreference: "always" },
250
- );
367
+ projectDir: dummyProjectDir,
368
+ convexDir: dummyConvexDir,
369
+ options: { persistEnabledPreference: "always" },
370
+ });
251
371
 
252
372
  expect(mockFs.writeFile).toHaveBeenNthCalledWith(
253
373
  2,
@@ -255,9 +375,7 @@ describe("writeAiConfig", () => {
255
375
  JSON.stringify(
256
376
  {
257
377
  $schema: "node_modules/convex/schemas/convex.schema.json",
258
- aiFiles: {
259
- disableStalenessMessage: false,
260
- },
378
+ aiFiles: { enabled: true },
261
379
  },
262
380
  null,
263
381
  2,
@@ -266,48 +384,48 @@ describe("writeAiConfig", () => {
266
384
  );
267
385
  });
268
386
 
269
- test("persists to convex.json by default when disableStalenessMessage is true", async () => {
387
+ test("persists enabled=false to convex.json by default", async () => {
270
388
  mockFs.writeFile.mockResolvedValue(undefined);
271
389
  mockFs.readFile.mockResolvedValue("{}");
272
390
 
273
- await writeAiConfig(
274
- {
275
- disableStalenessMessage: true,
391
+ await writeAiConfig({
392
+ config: {
393
+ enabled: false,
276
394
  guidelinesHash: null,
277
395
  agentsMdSectionHash: null,
278
396
  claudeMdHash: null,
279
397
  agentSkillsSha: null,
280
398
  installedSkillNames: [],
281
399
  },
282
- dummyProjectDir,
283
- dummyConvexDir,
284
- );
400
+ projectDir: dummyProjectDir,
401
+ convexDir: dummyConvexDir,
402
+ });
285
403
 
286
404
  expect(mockFs.writeFile).toHaveBeenCalledTimes(2);
287
405
  expect(mockFs.writeFile).toHaveBeenNthCalledWith(
288
406
  2,
289
407
  expect.stringContaining("convex.json"),
290
- expect.stringContaining('"disableStalenessMessage": true'),
408
+ expect.stringContaining('"enabled": false'),
291
409
  "utf8",
292
410
  );
293
411
  });
294
412
 
295
- test("never writes to convex.json when persistDisabledPreference is 'never'", async () => {
413
+ test("never writes to convex.json when persistEnabledPreference is 'never'", async () => {
296
414
  mockFs.writeFile.mockResolvedValue(undefined);
297
415
 
298
- await writeAiConfig(
299
- {
300
- disableStalenessMessage: true,
416
+ await writeAiConfig({
417
+ config: {
418
+ enabled: false,
301
419
  guidelinesHash: null,
302
420
  agentsMdSectionHash: null,
303
421
  claudeMdHash: null,
304
422
  agentSkillsSha: null,
305
423
  installedSkillNames: [],
306
424
  },
307
- dummyProjectDir,
308
- dummyConvexDir,
309
- { persistDisabledPreference: "never" },
310
- );
425
+ projectDir: dummyProjectDir,
426
+ convexDir: dummyConvexDir,
427
+ options: { persistEnabledPreference: "never" },
428
+ });
311
429
 
312
430
  expect(mockFs.writeFile).toHaveBeenCalledTimes(1);
313
431
  expect(mockFs.writeFile).toHaveBeenCalledWith(
@@ -318,21 +436,25 @@ describe("writeAiConfig", () => {
318
436
  });
319
437
  });
320
438
 
321
- describe("writeAiDisabledToProjectConfig", () => {
439
+ describe("writeAiEnabledToProjectConfig", () => {
322
440
  beforeEach(() => vi.clearAllMocks());
323
441
  afterEach(() => vi.resetAllMocks());
324
442
 
325
- test("writes only convex.json with disableStalenessMessage=true", async () => {
326
- mockFs.readFile.mockResolvedValue("{}");
443
+ test("writes enabled=false to convex.json and drops legacy disableStalenessMessage", async () => {
444
+ mockFs.readFile.mockResolvedValue(
445
+ JSON.stringify({ aiFiles: { disableStalenessMessage: true } }),
446
+ );
327
447
  mockFs.writeFile.mockResolvedValue(undefined);
328
448
 
329
- await writeAiDisabledToProjectConfig(true, dummyProjectDir);
449
+ await writeAiEnabledToProjectConfig({
450
+ projectDir: dummyProjectDir,
451
+ enabled: false,
452
+ });
330
453
 
331
454
  expect(mockFs.writeFile).toHaveBeenCalledTimes(1);
332
- expect(mockFs.writeFile).toHaveBeenCalledWith(
333
- expect.stringContaining("convex.json"),
334
- expect.stringContaining('"disableStalenessMessage": true'),
335
- "utf8",
336
- );
455
+ const written = mockFs.writeFile.mock.calls[0][1] as string;
456
+ const parsed = JSON.parse(written);
457
+ expect(parsed.aiFiles.enabled).toBe(false);
458
+ expect(parsed.aiFiles.disableStalenessMessage).toBeUndefined();
337
459
  });
338
460
  });