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.
- package/CHANGELOG.md +12 -0
- package/dist/browser.bundle.js +6 -9
- package/dist/browser.bundle.js.map +2 -2
- package/dist/cjs/browser/sync/authentication_manager.js +4 -1
- package/dist/cjs/browser/sync/authentication_manager.js.map +2 -2
- package/dist/cjs/browser/sync/web_socket_manager.js +1 -7
- package/dist/cjs/browser/sync/web_socket_manager.js.map +2 -2
- package/dist/cjs/cli/aiFiles.js +15 -14
- package/dist/cjs/cli/aiFiles.js.map +2 -2
- package/dist/cjs/cli/configure.js +15 -10
- package/dist/cjs/cli/configure.js.map +2 -2
- package/dist/cjs/cli/lib/aiFiles/agentsmd.js +69 -0
- package/dist/cjs/cli/lib/aiFiles/agentsmd.js.map +7 -0
- package/dist/cjs/cli/lib/aiFiles/claudemd.js +69 -0
- package/dist/cjs/cli/lib/aiFiles/claudemd.js.map +7 -0
- package/dist/cjs/cli/lib/{ai → aiFiles}/config.js +73 -46
- package/dist/cjs/cli/lib/aiFiles/config.js.map +7 -0
- package/dist/cjs/cli/lib/aiFiles/cursorrules.js +48 -0
- package/dist/cjs/cli/lib/aiFiles/cursorrules.js.map +7 -0
- package/dist/cjs/cli/lib/aiFiles/guidelinesmd.js +51 -0
- package/dist/cjs/cli/lib/aiFiles/guidelinesmd.js.map +7 -0
- package/dist/cjs/cli/lib/aiFiles/index.js +231 -0
- package/dist/cjs/cli/lib/aiFiles/index.js.map +7 -0
- package/dist/cjs/cli/lib/aiFiles/paths.js.map +7 -0
- package/dist/cjs/cli/lib/aiFiles/skills.js +180 -0
- package/dist/cjs/cli/lib/aiFiles/skills.js.map +7 -0
- package/dist/cjs/cli/lib/aiFiles/status.js +195 -0
- package/dist/cjs/cli/lib/aiFiles/status.js.map +7 -0
- package/dist/cjs/cli/lib/aiFiles/utils.js +111 -0
- package/dist/cjs/cli/lib/aiFiles/utils.js.map +7 -0
- package/dist/cjs/cli/lib/command.js +6 -1
- package/dist/cjs/cli/lib/command.js.map +2 -2
- package/dist/cjs/cli/lib/config.js +3 -4
- package/dist/cjs/cli/lib/config.js.map +2 -2
- package/dist/cjs/cli/lib/localDeployment/anonymous.js +2 -2
- package/dist/cjs/cli/lib/localDeployment/anonymous.js.map +2 -2
- package/dist/cjs/cli/lib/updates.js +8 -8
- package/dist/cjs/cli/lib/updates.js.map +2 -2
- package/dist/cjs/cli/lib/versionApi.js +7 -4
- package/dist/cjs/cli/lib/versionApi.js.map +2 -2
- package/dist/cjs/cli/lib/workos/workos.js +4 -6
- package/dist/cjs/cli/lib/workos/workos.js.map +2 -2
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs-types/browser/sync/authentication_manager.d.ts.map +1 -1
- package/dist/cjs-types/browser/sync/web_socket_manager.d.ts.map +1 -1
- package/dist/cjs-types/cli/aiFiles.d.ts.map +1 -1
- package/dist/cjs-types/cli/configure.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/aiFiles/agentsmd.d.ts +19 -0
- package/dist/cjs-types/cli/lib/aiFiles/agentsmd.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/aiFiles/agentsmd.test.d.ts +2 -0
- package/dist/cjs-types/cli/lib/aiFiles/agentsmd.test.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/aiFiles/claudemd.d.ts +19 -0
- package/dist/cjs-types/cli/lib/aiFiles/claudemd.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/aiFiles/claudemd.test.d.ts +2 -0
- package/dist/cjs-types/cli/lib/aiFiles/claudemd.test.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/aiFiles/config.d.ts +46 -0
- package/dist/cjs-types/cli/lib/aiFiles/config.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/aiFiles/config.test.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/aiFiles/cursorrules.d.ts +10 -0
- package/dist/cjs-types/cli/lib/aiFiles/cursorrules.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/aiFiles/guidelinesmd.d.ts +12 -0
- package/dist/cjs-types/cli/lib/aiFiles/guidelinesmd.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/aiFiles/guidelinesmd.test.d.ts +2 -0
- package/dist/cjs-types/cli/lib/aiFiles/guidelinesmd.test.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/aiFiles/index.d.ts +40 -0
- package/dist/cjs-types/cli/lib/aiFiles/index.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/aiFiles/index.test.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/aiFiles/integration.test.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/{ai → aiFiles}/paths.d.ts +4 -0
- package/dist/cjs-types/cli/lib/aiFiles/paths.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/aiFiles/prompt.test.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/aiFiles/skills.d.ts +18 -0
- package/dist/cjs-types/cli/lib/aiFiles/skills.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/aiFiles/status.d.ts +3 -0
- package/dist/cjs-types/cli/lib/aiFiles/status.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/aiFiles/utils.d.ts +46 -0
- package/dist/cjs-types/cli/lib/aiFiles/utils.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/config.d.ts +1 -0
- package/dist/cjs-types/cli/lib/config.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/versionApi.d.ts +7 -1
- package/dist/cjs-types/cli/lib/versionApi.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/workos/workos.d.ts.map +1 -1
- package/dist/cjs-types/index.d.ts +1 -1
- package/dist/cli.bundle.cjs +1605 -1548
- package/dist/cli.bundle.cjs.map +4 -4
- package/dist/esm/browser/sync/authentication_manager.js +4 -1
- package/dist/esm/browser/sync/authentication_manager.js.map +2 -2
- package/dist/esm/browser/sync/web_socket_manager.js +1 -7
- package/dist/esm/browser/sync/web_socket_manager.js.map +2 -2
- package/dist/esm/cli/aiFiles.js +17 -17
- package/dist/esm/cli/aiFiles.js.map +2 -2
- package/dist/esm/cli/configure.js +15 -10
- package/dist/esm/cli/configure.js.map +2 -2
- package/dist/esm/cli/lib/aiFiles/agentsmd.js +52 -0
- package/dist/esm/cli/lib/aiFiles/agentsmd.js.map +7 -0
- package/dist/esm/cli/lib/aiFiles/claudemd.js +52 -0
- package/dist/esm/cli/lib/aiFiles/claudemd.js.map +7 -0
- package/dist/esm/cli/lib/{ai → aiFiles}/config.js +71 -45
- package/dist/esm/cli/lib/aiFiles/config.js.map +7 -0
- package/dist/esm/cli/lib/aiFiles/cursorrules.js +16 -0
- package/dist/esm/cli/lib/aiFiles/cursorrules.js.map +7 -0
- package/dist/esm/cli/lib/aiFiles/guidelinesmd.js +28 -0
- package/dist/esm/cli/lib/aiFiles/guidelinesmd.js.map +7 -0
- package/dist/esm/cli/lib/aiFiles/index.js +210 -0
- package/dist/esm/cli/lib/aiFiles/index.js.map +7 -0
- package/dist/esm/cli/lib/aiFiles/paths.js.map +7 -0
- package/dist/esm/cli/lib/aiFiles/skills.js +147 -0
- package/dist/esm/cli/lib/aiFiles/skills.js.map +7 -0
- package/dist/esm/cli/lib/aiFiles/status.js +175 -0
- package/dist/esm/cli/lib/aiFiles/status.js.map +7 -0
- package/dist/esm/cli/lib/aiFiles/utils.js +82 -0
- package/dist/esm/cli/lib/aiFiles/utils.js.map +7 -0
- package/dist/esm/cli/lib/command.js +6 -1
- package/dist/esm/cli/lib/command.js.map +2 -2
- package/dist/esm/cli/lib/config.js +3 -4
- package/dist/esm/cli/lib/config.js.map +2 -2
- package/dist/esm/cli/lib/localDeployment/anonymous.js +2 -2
- package/dist/esm/cli/lib/localDeployment/anonymous.js.map +2 -2
- package/dist/esm/cli/lib/updates.js +8 -8
- package/dist/esm/cli/lib/updates.js.map +2 -2
- package/dist/esm/cli/lib/versionApi.js +7 -4
- package/dist/esm/cli/lib/versionApi.js.map +2 -2
- package/dist/esm/cli/lib/workos/workos.js +4 -6
- package/dist/esm/cli/lib/workos/workos.js.map +2 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm-types/browser/sync/authentication_manager.d.ts.map +1 -1
- package/dist/esm-types/browser/sync/web_socket_manager.d.ts.map +1 -1
- package/dist/esm-types/cli/aiFiles.d.ts.map +1 -1
- package/dist/esm-types/cli/configure.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/aiFiles/agentsmd.d.ts +19 -0
- package/dist/esm-types/cli/lib/aiFiles/agentsmd.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/aiFiles/agentsmd.test.d.ts +2 -0
- package/dist/esm-types/cli/lib/aiFiles/agentsmd.test.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/aiFiles/claudemd.d.ts +19 -0
- package/dist/esm-types/cli/lib/aiFiles/claudemd.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/aiFiles/claudemd.test.d.ts +2 -0
- package/dist/esm-types/cli/lib/aiFiles/claudemd.test.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/aiFiles/config.d.ts +46 -0
- package/dist/esm-types/cli/lib/aiFiles/config.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/aiFiles/config.test.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/aiFiles/cursorrules.d.ts +10 -0
- package/dist/esm-types/cli/lib/aiFiles/cursorrules.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/aiFiles/guidelinesmd.d.ts +12 -0
- package/dist/esm-types/cli/lib/aiFiles/guidelinesmd.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/aiFiles/guidelinesmd.test.d.ts +2 -0
- package/dist/esm-types/cli/lib/aiFiles/guidelinesmd.test.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/aiFiles/index.d.ts +40 -0
- package/dist/esm-types/cli/lib/aiFiles/index.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/aiFiles/index.test.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/aiFiles/integration.test.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/{ai → aiFiles}/paths.d.ts +4 -0
- package/dist/esm-types/cli/lib/aiFiles/paths.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/aiFiles/prompt.test.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/aiFiles/skills.d.ts +18 -0
- package/dist/esm-types/cli/lib/aiFiles/skills.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/aiFiles/status.d.ts +3 -0
- package/dist/esm-types/cli/lib/aiFiles/status.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/aiFiles/utils.d.ts +46 -0
- package/dist/esm-types/cli/lib/aiFiles/utils.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/config.d.ts +1 -0
- package/dist/esm-types/cli/lib/config.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/versionApi.d.ts +7 -1
- package/dist/esm-types/cli/lib/versionApi.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/workos/workos.d.ts.map +1 -1
- package/dist/esm-types/index.d.ts +1 -1
- package/dist/react.bundle.js +6 -9
- package/dist/react.bundle.js.map +2 -2
- package/package.json +1 -1
- package/schemas/convex.schema.json +7 -1
- package/src/browser/sync/authentication_manager.ts +9 -4
- package/src/browser/sync/client_node.test.ts +125 -0
- package/src/browser/sync/web_socket_manager.ts +1 -7
- package/src/cli/aiFiles.ts +20 -27
- package/src/cli/configure.ts +17 -11
- package/src/cli/deploymentSelection.test.ts +56 -2
- package/src/cli/lib/{ai → aiFiles}/MANUAL_TESTING.md +6 -2
- package/src/cli/lib/aiFiles/agentsmd.test.ts +133 -0
- package/src/cli/lib/aiFiles/agentsmd.ts +77 -0
- package/src/cli/lib/aiFiles/claudemd.test.ts +92 -0
- package/src/cli/lib/aiFiles/claudemd.ts +77 -0
- package/src/cli/lib/{ai → aiFiles}/config.test.ts +181 -59
- package/src/cli/lib/{ai → aiFiles}/config.ts +92 -63
- package/src/cli/lib/aiFiles/cursorrules.ts +25 -0
- package/src/cli/lib/aiFiles/guidelinesmd.test.ts +40 -0
- package/src/cli/lib/aiFiles/guidelinesmd.ts +41 -0
- package/src/cli/lib/{ai → aiFiles}/index.test.ts +200 -339
- package/src/cli/lib/aiFiles/index.ts +303 -0
- package/src/cli/lib/{ai → aiFiles}/integration.test.ts +117 -147
- package/src/cli/lib/{ai → aiFiles}/paths.ts +5 -0
- package/src/cli/lib/{ai → aiFiles}/prompt.test.ts +78 -30
- package/src/cli/lib/aiFiles/skills.ts +213 -0
- package/src/cli/lib/aiFiles/status.ts +240 -0
- package/src/cli/lib/aiFiles/utils.ts +163 -0
- package/src/cli/lib/command.ts +6 -1
- package/src/cli/lib/config.test.ts +1 -1
- package/src/cli/lib/config.ts +6 -5
- package/src/cli/lib/localDeployment/anonymous.ts +2 -2
- package/src/cli/lib/updates.test.ts +40 -30
- package/src/cli/lib/updates.ts +8 -8
- package/src/cli/lib/versionApi.test.ts +13 -10
- package/src/cli/lib/versionApi.ts +13 -5
- package/src/cli/lib/workos/workos.ts +4 -5
- package/src/index.ts +1 -1
- package/src/values/.claude/settings.local.json +10 -0
- package/dist/cjs/cli/lib/ai/config.js.map +0 -7
- package/dist/cjs/cli/lib/ai/index.js +0 -704
- package/dist/cjs/cli/lib/ai/index.js.map +0 -7
- package/dist/cjs/cli/lib/ai/paths.js.map +0 -7
- package/dist/cjs-types/cli/lib/ai/config.d.ts +0 -50
- package/dist/cjs-types/cli/lib/ai/config.d.ts.map +0 -1
- package/dist/cjs-types/cli/lib/ai/config.test.d.ts.map +0 -1
- package/dist/cjs-types/cli/lib/ai/index.d.ts +0 -56
- package/dist/cjs-types/cli/lib/ai/index.d.ts.map +0 -1
- package/dist/cjs-types/cli/lib/ai/index.test.d.ts.map +0 -1
- package/dist/cjs-types/cli/lib/ai/integration.test.d.ts.map +0 -1
- package/dist/cjs-types/cli/lib/ai/paths.d.ts.map +0 -1
- package/dist/cjs-types/cli/lib/ai/prompt.test.d.ts.map +0 -1
- package/dist/esm/cli/lib/ai/config.js.map +0 -7
- package/dist/esm/cli/lib/ai/index.js +0 -684
- package/dist/esm/cli/lib/ai/index.js.map +0 -7
- package/dist/esm/cli/lib/ai/paths.js.map +0 -7
- package/dist/esm-types/cli/lib/ai/config.d.ts +0 -50
- package/dist/esm-types/cli/lib/ai/config.d.ts.map +0 -1
- package/dist/esm-types/cli/lib/ai/config.test.d.ts.map +0 -1
- package/dist/esm-types/cli/lib/ai/index.d.ts +0 -56
- package/dist/esm-types/cli/lib/ai/index.d.ts.map +0 -1
- package/dist/esm-types/cli/lib/ai/index.test.d.ts.map +0 -1
- package/dist/esm-types/cli/lib/ai/integration.test.d.ts.map +0 -1
- package/dist/esm-types/cli/lib/ai/paths.d.ts.map +0 -1
- package/dist/esm-types/cli/lib/ai/prompt.test.d.ts.map +0 -1
- package/src/cli/lib/ai/index.ts +0 -1006
- /package/dist/cjs/cli/lib/{ai → aiFiles}/paths.js +0 -0
- /package/dist/cjs-types/cli/lib/{ai → aiFiles}/config.test.d.ts +0 -0
- /package/dist/cjs-types/cli/lib/{ai → aiFiles}/index.test.d.ts +0 -0
- /package/dist/cjs-types/cli/lib/{ai → aiFiles}/integration.test.d.ts +0 -0
- /package/dist/cjs-types/cli/lib/{ai → aiFiles}/prompt.test.d.ts +0 -0
- /package/dist/esm/cli/lib/{ai → aiFiles}/paths.js +0 -0
- /package/dist/esm-types/cli/lib/{ai → aiFiles}/config.test.d.ts +0 -0
- /package/dist/esm-types/cli/lib/{ai → aiFiles}/index.test.d.ts +0 -0
- /package/dist/esm-types/cli/lib/{ai → aiFiles}/integration.test.d.ts +0 -0
- /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
|
-
|
|
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
|
-
|
|
18
|
+
installAiFiles,
|
|
22
19
|
removeAiFiles,
|
|
23
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
219
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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("
|
|
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
|
-
|
|
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
|
-
//
|
|
245
|
+
// installAiFiles
|
|
399
246
|
// ---------------------------------------------------------------------------
|
|
400
247
|
|
|
401
|
-
describe("
|
|
248
|
+
describe("installAiFiles", () => {
|
|
402
249
|
beforeEach(() => {
|
|
403
250
|
vi.clearAllMocks();
|
|
404
251
|
mockFetchAgentSkillsSha.mockResolvedValue("canonical-sha-abc123");
|
|
405
252
|
mockGetVersion.mockResolvedValue({
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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
|
|
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
|
|
317
|
+
await installAiFiles({ projectDir: tmpDir, convexDir });
|
|
494
318
|
|
|
495
319
|
expect(mockWriteAiConfig).toHaveBeenCalledWith(
|
|
496
320
|
expect.objectContaining({
|
|
497
|
-
|
|
498
|
-
|
|
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
|
|
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
|
-
|
|
338
|
+
enabled: false,
|
|
515
339
|
});
|
|
516
340
|
mockDownloadGuidelines.mockResolvedValue(null);
|
|
517
341
|
|
|
518
|
-
await
|
|
342
|
+
await installAiFiles({ projectDir: tmpDir, convexDir });
|
|
519
343
|
|
|
520
344
|
expect(mockWriteAiConfig).toHaveBeenCalledWith(
|
|
521
|
-
expect.objectContaining({
|
|
522
|
-
|
|
523
|
-
|
|
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
|
-
|
|
363
|
+
enabled: false,
|
|
540
364
|
guidelinesHash: null,
|
|
541
365
|
});
|
|
542
366
|
mockDownloadGuidelines.mockResolvedValue("fresh guidelines");
|
|
543
367
|
|
|
544
|
-
await
|
|
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
|
-
|
|
557
|
-
|
|
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
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
416
|
+
kind: "ok",
|
|
417
|
+
data: {
|
|
418
|
+
message: null,
|
|
419
|
+
guidelinesHash: null,
|
|
420
|
+
agentSkillsSha: null,
|
|
421
|
+
disableSkillsCli: true,
|
|
422
|
+
},
|
|
595
423
|
});
|
|
596
424
|
|
|
597
|
-
await
|
|
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
|
|
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
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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("
|
|
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
|
|
702
|
+
await safelyAttemptToDisableAiFiles(tmpDir);
|
|
872
703
|
|
|
873
|
-
expect(
|
|
874
|
-
|
|
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("
|
|
713
|
+
test("safelyAttemptToDisableAiFiles writes config to project root, not convex dir", async () => {
|
|
883
714
|
mockReadAiConfig.mockResolvedValue(null);
|
|
884
715
|
|
|
885
|
-
await
|
|
716
|
+
await safelyAttemptToDisableAiFiles(tmpDir);
|
|
886
717
|
|
|
887
|
-
expect(
|
|
888
|
-
|
|
889
|
-
tmpDir,
|
|
890
|
-
);
|
|
718
|
+
expect(mockWriteAiEnabledToProjectConfig).toHaveBeenCalledWith({
|
|
719
|
+
enabled: false,
|
|
720
|
+
projectDir: tmpDir,
|
|
721
|
+
});
|
|
891
722
|
});
|
|
892
723
|
});
|
|
893
724
|
|
|
894
725
|
// ---------------------------------------------------------------------------
|
|
895
|
-
// statusAiFiles
|
|
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
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
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(
|
|
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
|
|
763
|
+
test("reports disabled when config has enabled=false", async () => {
|
|
927
764
|
mockReadAiConfig.mockResolvedValue({
|
|
928
765
|
...baseConfig,
|
|
929
|
-
|
|
766
|
+
enabled: false,
|
|
930
767
|
});
|
|
931
768
|
|
|
932
|
-
await statusAiFiles(
|
|
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
|
|
782
|
+
test("reports enabled when config exists and enabled=true", async () => {
|
|
943
783
|
mockReadAiConfig.mockResolvedValue(baseConfig);
|
|
944
784
|
|
|
945
|
-
await statusAiFiles(
|
|
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
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
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
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
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
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
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(
|
|
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(
|
|
946
|
+
mockGetVersion.mockResolvedValue({ kind: "error" });
|
|
1092
947
|
|
|
1093
|
-
await statusAiFiles(
|
|
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(
|
|
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"),
|