@codemcp/ade-cli 0.2.0 → 0.2.2

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.
@@ -1,123 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
- import { mkdtemp, rm, readFile } from "node:fs/promises";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
-
6
- // Mock only the TUI — everything else is real
7
- vi.mock("@clack/prompts", () => ({
8
- intro: vi.fn(),
9
- outro: vi.fn(),
10
- log: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
11
- select: vi.fn(),
12
- multiselect: vi.fn(),
13
- isCancel: vi.fn().mockReturnValue(false),
14
- cancel: vi.fn(),
15
- spinner: vi.fn().mockReturnValue({ start: vi.fn(), stop: vi.fn() })
16
- }));
17
-
18
- import * as clack from "@clack/prompts";
19
- import { runSetup } from "./setup.js";
20
- import { runInstall } from "./install.js";
21
- import { getDefaultCatalog } from "../../../core/src/catalog/index.js";
22
-
23
- describe("install integration (real temp dir)", () => {
24
- let dir: string;
25
-
26
- beforeEach(async () => {
27
- vi.clearAllMocks();
28
- dir = await mkdtemp(join(tmpdir(), "ade-install-"));
29
- });
30
-
31
- afterEach(async () => {
32
- await rm(dir, { recursive: true, force: true });
33
- });
34
-
35
- it("applies lock file to regenerate agent files without re-resolving", async () => {
36
- const catalog = getDefaultCatalog();
37
-
38
- // Step 1: Run setup to create config.yaml + config.lock.yaml
39
- vi.mocked(clack.select)
40
- .mockResolvedValueOnce("codemcp-workflows") // process
41
- .mockResolvedValueOnce("__skip__"); // architecture
42
- vi.mocked(clack.multiselect)
43
- .mockResolvedValueOnce([]) // practices: none
44
- .mockResolvedValueOnce(["claude-code"]); // harnesses
45
- await runSetup(dir, catalog);
46
-
47
- // Step 2: Delete agent output files to simulate a fresh clone
48
- await rm(join(dir, ".mcp.json"));
49
- await rm(join(dir, ".claude"), { recursive: true, force: true });
50
-
51
- // Step 3: Run install — should regenerate from config.lock.yaml
52
- await runInstall(dir, ["claude-code"]);
53
-
54
- // Agent files should be back
55
- const agentMd = await readFile(
56
- join(dir, ".claude", "agents", "ade.md"),
57
- "utf-8"
58
- );
59
- expect(agentMd).toContain("Call whats_next()");
60
-
61
- const mcpJson = JSON.parse(await readFile(join(dir, ".mcp.json"), "utf-8"));
62
- expect(mcpJson.mcpServers["workflows"]).toMatchObject({
63
- command: "npx",
64
- args: ["@codemcp/workflows-server@latest"]
65
- });
66
- });
67
-
68
- it("does not modify the lock file", async () => {
69
- const catalog = getDefaultCatalog();
70
-
71
- // Setup first
72
- vi.mocked(clack.select)
73
- .mockResolvedValueOnce("codemcp-workflows") // process
74
- .mockResolvedValueOnce("__skip__"); // architecture
75
- vi.mocked(clack.multiselect)
76
- .mockResolvedValueOnce([]) // practices: none
77
- .mockResolvedValueOnce(["claude-code"]); // harnesses
78
- await runSetup(dir, catalog);
79
-
80
- const lockRawBefore = await readFile(
81
- join(dir, "config.lock.yaml"),
82
- "utf-8"
83
- );
84
-
85
- // Re-install
86
- await runInstall(dir, ["claude-code"]);
87
-
88
- const lockRawAfter = await readFile(join(dir, "config.lock.yaml"), "utf-8");
89
- // Lock file should be byte-identical (install doesn't rewrite it)
90
- expect(lockRawAfter).toBe(lockRawBefore);
91
- });
92
-
93
- it("fails when no config.lock.yaml exists", async () => {
94
- await expect(runInstall(dir, ["claude-code"])).rejects.toThrow(
95
- /config\.lock\.yaml not found/i
96
- );
97
- });
98
-
99
- it("works with native-agents-md option", async () => {
100
- const catalog = getDefaultCatalog();
101
-
102
- // Setup with native-agents-md
103
- vi.mocked(clack.select)
104
- .mockResolvedValueOnce("native-agents-md") // process
105
- .mockResolvedValueOnce("__skip__"); // architecture
106
- vi.mocked(clack.multiselect)
107
- .mockResolvedValueOnce([]) // practices: none
108
- .mockResolvedValueOnce(["claude-code"]); // harnesses
109
- await runSetup(dir, catalog);
110
-
111
- // Delete agent output
112
- await rm(join(dir, ".claude"), { recursive: true, force: true });
113
-
114
- // Re-install
115
- await runInstall(dir, ["claude-code"]);
116
-
117
- const agentMd = await readFile(
118
- join(dir, ".claude", "agents", "ade.md"),
119
- "utf-8"
120
- );
121
- expect(agentMd).toContain("AGENTS.md");
122
- });
123
- });
@@ -1,169 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
- import type { LogicalConfig } from "@codemcp/ade-core";
3
-
4
- // ── Mocks ────────────────────────────────────────────────────────────────────
5
-
6
- vi.mock("@clack/prompts", () => ({
7
- intro: vi.fn(),
8
- outro: vi.fn(),
9
- log: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }
10
- }));
11
-
12
- const mockLogical: LogicalConfig = {
13
- mcp_servers: [],
14
- instructions: ["test instruction"],
15
- cli_actions: [],
16
- knowledge_sources: [],
17
- skills: [],
18
- git_hooks: [],
19
- setup_notes: []
20
- };
21
-
22
- vi.mock("@codemcp/ade-core", async (importOriginal) => {
23
- const actual = (await importOriginal()) as typeof import("@codemcp/ade-core");
24
- return {
25
- ...actual,
26
- readLockFile: vi.fn()
27
- };
28
- });
29
-
30
- const mockInstall = vi.fn().mockResolvedValue(undefined);
31
-
32
- vi.mock("@codemcp/ade-harnesses", () => ({
33
- getHarnessWriter: vi.fn().mockImplementation((id: string) => {
34
- if (id === "universal" || id === "claude-code" || id === "cursor") {
35
- return { id, install: mockInstall };
36
- }
37
- return undefined;
38
- }),
39
- getHarnessIds: vi
40
- .fn()
41
- .mockReturnValue([
42
- "universal",
43
- "claude-code",
44
- "cursor",
45
- "copilot",
46
- "windsurf",
47
- "cline",
48
- "roo-code",
49
- "kiro",
50
- "opencode"
51
- ]),
52
- installSkills: vi.fn().mockResolvedValue(undefined),
53
- writeInlineSkills: vi.fn().mockResolvedValue([])
54
- }));
55
-
56
- import * as clack from "@clack/prompts";
57
- import { readLockFile } from "@codemcp/ade-core";
58
- import { runInstall } from "./install.js";
59
-
60
- // ── Tests ────────────────────────────────────────────────────────────────────
61
-
62
- describe("runInstall", () => {
63
- beforeEach(async () => {
64
- vi.clearAllMocks();
65
- // Re-set the default implementation after clearAllMocks
66
- const { getHarnessWriter } = await import("@codemcp/ade-harnesses");
67
- vi.mocked(getHarnessWriter).mockImplementation((id: string) => {
68
- if (id === "universal" || id === "claude-code" || id === "cursor") {
69
- return {
70
- id,
71
- label: id,
72
- description: "test",
73
- install: mockInstall
74
- };
75
- }
76
- return undefined;
77
- });
78
- });
79
-
80
- it("reads config.lock.yaml and applies logical config", async () => {
81
- vi.mocked(readLockFile).mockResolvedValueOnce({
82
- version: 1,
83
- generated_at: "2024-01-01T00:00:00.000Z",
84
- choices: { process: "codemcp-workflows" },
85
- logical_config: mockLogical
86
- });
87
-
88
- await runInstall("/tmp/project");
89
-
90
- expect(readLockFile).toHaveBeenCalledWith("/tmp/project");
91
- });
92
-
93
- it("defaults to universal harness when none specified", async () => {
94
- vi.mocked(readLockFile).mockResolvedValueOnce({
95
- version: 1,
96
- generated_at: "2024-01-01T00:00:00.000Z",
97
- choices: { process: "codemcp-workflows" },
98
- logical_config: mockLogical
99
- });
100
-
101
- await runInstall("/tmp/project");
102
-
103
- expect(mockInstall).toHaveBeenCalledWith(mockLogical, "/tmp/project");
104
- });
105
-
106
- it("uses harnesses from lock file when present", async () => {
107
- vi.mocked(readLockFile).mockResolvedValueOnce({
108
- version: 1,
109
- generated_at: "2024-01-01T00:00:00.000Z",
110
- choices: { process: "codemcp-workflows" },
111
- harnesses: ["claude-code", "cursor"],
112
- logical_config: mockLogical
113
- });
114
-
115
- await runInstall("/tmp/project");
116
-
117
- expect(mockInstall).toHaveBeenCalledTimes(2);
118
- });
119
-
120
- it("uses explicit harness ids when provided", async () => {
121
- vi.mocked(readLockFile).mockResolvedValueOnce({
122
- version: 1,
123
- generated_at: "2024-01-01T00:00:00.000Z",
124
- choices: { process: "codemcp-workflows" },
125
- harnesses: ["claude-code"],
126
- logical_config: mockLogical
127
- });
128
-
129
- await runInstall("/tmp/project", ["cursor"]);
130
-
131
- // Explicit takes priority over lock file
132
- expect(mockInstall).toHaveBeenCalledTimes(1);
133
- });
134
-
135
- it("throws when config.lock.yaml is missing", async () => {
136
- vi.mocked(readLockFile).mockResolvedValueOnce(null);
137
-
138
- await expect(runInstall("/tmp/project")).rejects.toThrow(
139
- /config\.lock\.yaml not found/i
140
- );
141
- });
142
-
143
- it("throws when harness id is unknown", async () => {
144
- vi.mocked(readLockFile).mockResolvedValueOnce({
145
- version: 1,
146
- generated_at: "2024-01-01T00:00:00.000Z",
147
- choices: { process: "codemcp-workflows" },
148
- logical_config: mockLogical
149
- });
150
-
151
- await expect(runInstall("/tmp/project", ["unknown-agent"])).rejects.toThrow(
152
- /unknown harness/i
153
- );
154
- });
155
-
156
- it("shows intro and outro messages", async () => {
157
- vi.mocked(readLockFile).mockResolvedValueOnce({
158
- version: 1,
159
- generated_at: "2024-01-01T00:00:00.000Z",
160
- choices: { process: "codemcp-workflows" },
161
- logical_config: mockLogical
162
- });
163
-
164
- await runInstall("/tmp/project");
165
-
166
- expect(clack.intro).toHaveBeenCalled();
167
- expect(clack.outro).toHaveBeenCalled();
168
- });
169
- });
@@ -1,63 +0,0 @@
1
- import * as clack from "@clack/prompts";
2
- import { readLockFile } from "@codemcp/ade-core";
3
- import {
4
- getHarnessWriter,
5
- getHarnessIds,
6
- installSkills,
7
- writeInlineSkills
8
- } from "@codemcp/ade-harnesses";
9
-
10
- export async function runInstall(
11
- projectRoot: string,
12
- harnessIds?: string[]
13
- ): Promise<void> {
14
- clack.intro("ade install");
15
-
16
- const lockFile = await readLockFile(projectRoot);
17
- if (!lockFile) {
18
- throw new Error("config.lock.yaml not found. Run `ade setup` first.");
19
- }
20
-
21
- // Determine which harnesses to install for:
22
- // 1. --harness flag (comma-separated)
23
- // 2. harnesses saved in the lock file
24
- // 3. default: universal
25
- const ids = harnessIds ?? lockFile.harnesses ?? ["universal"];
26
-
27
- const validIds = getHarnessIds();
28
- for (const id of ids) {
29
- if (!validIds.includes(id)) {
30
- throw new Error(
31
- `Unknown harness "${id}". Available: ${validIds.join(", ")}`
32
- );
33
- }
34
- }
35
-
36
- const logicalConfig = lockFile.logical_config;
37
-
38
- for (const id of ids) {
39
- const writer = getHarnessWriter(id);
40
- if (writer) {
41
- await writer.install(logicalConfig, projectRoot);
42
- }
43
- }
44
-
45
- const modifiedSkills = await writeInlineSkills(logicalConfig, projectRoot);
46
- if (modifiedSkills.length > 0) {
47
- clack.log.warn(
48
- `The following skills have been locally modified and will NOT be updated:\n` +
49
- modifiedSkills.map((s) => ` - ${s}`).join("\n") +
50
- `\n\nTo use the latest defaults, remove .ade/skills/ and re-run install.`
51
- );
52
- }
53
-
54
- await installSkills(logicalConfig.skills, projectRoot);
55
-
56
- if (logicalConfig.knowledge_sources.length > 0) {
57
- clack.log.info(
58
- "Knowledge sources configured. Initialize them separately:\n npx @codemcp/knowledge init"
59
- );
60
- }
61
-
62
- clack.outro("Install complete!");
63
- }
@@ -1,129 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
- import { mkdtemp, rm, readFile } from "node:fs/promises";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
-
6
- vi.mock("@clack/prompts", () => ({
7
- intro: vi.fn(),
8
- outro: vi.fn(),
9
- select: vi.fn(),
10
- multiselect: vi.fn(),
11
- confirm: vi.fn(),
12
- isCancel: vi.fn().mockReturnValue(false),
13
- cancel: vi.fn(),
14
- log: { info: vi.fn(), warn: vi.fn() },
15
- spinner: vi.fn().mockReturnValue({ start: vi.fn(), stop: vi.fn() })
16
- }));
17
-
18
- import * as clack from "@clack/prompts";
19
- import { runSetup } from "./setup.js";
20
- import { readLockFile } from "@codemcp/ade-core";
21
- import { getDefaultCatalog } from "../../../core/src/catalog/index.js";
22
-
23
- describe("knowledge integration", () => {
24
- let dir: string;
25
-
26
- beforeEach(async () => {
27
- vi.clearAllMocks();
28
- dir = await mkdtemp(join(tmpdir(), "ade-knowledge-"));
29
- });
30
-
31
- afterEach(async () => {
32
- await rm(dir, { recursive: true, force: true });
33
- });
34
-
35
- it(
36
- "records knowledge sources in lock file when tanstack is selected",
37
- { timeout: 60_000 },
38
- async () => {
39
- const catalog = getDefaultCatalog();
40
-
41
- vi.mocked(clack.select)
42
- .mockResolvedValueOnce("codemcp-workflows") // process
43
- .mockResolvedValueOnce("tanstack"); // architecture
44
-
45
- vi.mocked(clack.multiselect)
46
- .mockResolvedValueOnce([]) // practices: none
47
- .mockResolvedValueOnce([]) // backpressure: none
48
- .mockResolvedValueOnce([
49
- "tanstack-router-docs",
50
- "tanstack-query-docs",
51
- "tanstack-form-docs",
52
- "tanstack-table-docs"
53
- ])
54
- .mockResolvedValueOnce(["claude-code"]); // harnesses
55
-
56
- await runSetup(dir, catalog);
57
-
58
- // Lock file should contain knowledge_sources
59
- const lock = await readLockFile(dir);
60
- expect(lock!.logical_config.knowledge_sources).toHaveLength(4);
61
- expect(lock!.logical_config.knowledge_sources.map((s) => s.name)).toEqual(
62
- expect.arrayContaining([
63
- "tanstack-router-docs",
64
- "tanstack-query-docs",
65
- "tanstack-form-docs",
66
- "tanstack-table-docs"
67
- ])
68
- );
69
-
70
- // MCP server entry for knowledge should be in .mcp.json
71
- const mcpJson = JSON.parse(
72
- await readFile(join(dir, ".mcp.json"), "utf-8")
73
- );
74
- expect(mcpJson.mcpServers["knowledge"]).toMatchObject({
75
- command: "npx",
76
- args: ["-y", "@codemcp/knowledge-server"]
77
- });
78
-
79
- // Knowledge init is deferred — user should see a hint
80
- expect(clack.log.info).toHaveBeenCalledWith(
81
- expect.stringContaining("npx @codemcp/knowledge init")
82
- );
83
- }
84
- );
85
-
86
- it(
87
- "excludes deselected docsets from lock file",
88
- { timeout: 60_000 },
89
- async () => {
90
- const catalog = getDefaultCatalog();
91
-
92
- vi.mocked(clack.select)
93
- .mockResolvedValueOnce("codemcp-workflows") // process
94
- .mockResolvedValueOnce("tanstack"); // architecture
95
-
96
- vi.mocked(clack.multiselect)
97
- .mockResolvedValueOnce([]) // practices: none
98
- .mockResolvedValueOnce([]) // backpressure: none
99
- .mockResolvedValueOnce(["tanstack-router-docs", "tanstack-query-docs"])
100
- .mockResolvedValueOnce(["claude-code"]); // harnesses
101
-
102
- await runSetup(dir, catalog);
103
-
104
- // Lock file should only have the 2 selected sources
105
- const lock = await readLockFile(dir);
106
- expect(lock!.logical_config.knowledge_sources).toHaveLength(2);
107
- expect(lock!.logical_config.knowledge_sources.map((s) => s.name)).toEqual(
108
- expect.arrayContaining(["tanstack-router-docs", "tanstack-query-docs"])
109
- );
110
- }
111
- );
112
-
113
- it("does not show knowledge hint when no docsets are implied", async () => {
114
- const catalog = getDefaultCatalog();
115
-
116
- vi.mocked(clack.select)
117
- .mockResolvedValueOnce("native-agents-md") // process
118
- .mockResolvedValueOnce("__skip__"); // architecture: skip
119
- vi.mocked(clack.multiselect)
120
- .mockResolvedValueOnce(["tdd-london"]) // practices: no docsets
121
- .mockResolvedValueOnce(["claude-code"]); // harnesses
122
-
123
- await runSetup(dir, catalog);
124
-
125
- expect(clack.log.info).not.toHaveBeenCalledWith(
126
- expect.stringContaining("npx @codemcp/knowledge init")
127
- );
128
- });
129
- });
@@ -1,148 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
- import { mkdtemp, rm } from "node:fs/promises";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
-
6
- // Mock only the TUI — everything else (catalog, registry, resolver, config I/O) is real
7
- vi.mock("@clack/prompts", () => ({
8
- intro: vi.fn(),
9
- outro: vi.fn(),
10
- select: vi.fn(),
11
- multiselect: vi.fn(),
12
- confirm: vi.fn(),
13
- isCancel: vi.fn().mockReturnValue(false),
14
- cancel: vi.fn(),
15
- spinner: vi.fn().mockReturnValue({ start: vi.fn(), stop: vi.fn() })
16
- }));
17
-
18
- import * as clack from "@clack/prompts";
19
- import { runSetup } from "./setup.js";
20
- import { readUserConfig, readLockFile } from "@codemcp/ade-core";
21
- import { getDefaultCatalog } from "../../../core/src/catalog/index.js";
22
-
23
- describe("setup integration (real temp dir)", () => {
24
- let dir: string;
25
-
26
- beforeEach(async () => {
27
- vi.clearAllMocks();
28
- dir = await mkdtemp(join(tmpdir(), "ade-setup-"));
29
- });
30
-
31
- afterEach(async () => {
32
- await rm(dir, { recursive: true, force: true });
33
- });
34
-
35
- it("writes config.yaml and config.lock.yaml for codemcp-workflows", async () => {
36
- const catalog = getDefaultCatalog();
37
-
38
- vi.mocked(clack.select)
39
- .mockResolvedValueOnce("codemcp-workflows") // process
40
- .mockResolvedValueOnce("__skip__"); // architecture
41
- vi.mocked(clack.multiselect)
42
- .mockResolvedValueOnce([]) // practices: none
43
- .mockResolvedValueOnce(["claude-code"]); // harnesses
44
-
45
- await runSetup(dir, catalog);
46
-
47
- // ── config.yaml ──────────────────────────────────────────────────────
48
- const config = await readUserConfig(dir);
49
- expect(config).not.toBeNull();
50
- expect(config!.choices).toEqual({ process: "codemcp-workflows" });
51
-
52
- // ── config.lock.yaml ─────────────────────────────────────────────────
53
- const lock = await readLockFile(dir);
54
- expect(lock).not.toBeNull();
55
- expect(lock!.version).toBe(1);
56
- expect(lock!.choices).toEqual({ process: "codemcp-workflows" });
57
- expect(lock!.generated_at).toBeTruthy();
58
-
59
- // LogicalConfig was produced by the real resolver with real writers
60
- const lc = lock!.logical_config;
61
- expect(lc.mcp_servers).toHaveLength(1);
62
- expect(lc.mcp_servers[0].ref).toBe("workflows");
63
- expect(lc.instructions.length).toBeGreaterThan(0);
64
-
65
- // ── Agent output: .mcp.json ─────────────────────────────────────────
66
- const { readFile } = await import("node:fs/promises");
67
- const mcpJson = JSON.parse(await readFile(join(dir, ".mcp.json"), "utf-8"));
68
- expect(mcpJson.mcpServers["workflows"]).toMatchObject({
69
- command: "npx",
70
- args: ["@codemcp/workflows-server@latest"]
71
- });
72
-
73
- // ── Agent output: .claude/agents/ade.md ────────────────────────────
74
- const agentMd = await readFile(
75
- join(dir, ".claude", "agents", "ade.md"),
76
- "utf-8"
77
- );
78
- expect(agentMd).toContain("Call whats_next()");
79
- });
80
-
81
- it("writes config.yaml, lock, and AGENTS.md for native-agents-md", async () => {
82
- const catalog = getDefaultCatalog();
83
-
84
- vi.mocked(clack.select)
85
- .mockResolvedValueOnce("native-agents-md") // process
86
- .mockResolvedValueOnce("__skip__"); // architecture
87
- vi.mocked(clack.multiselect)
88
- .mockResolvedValueOnce([]) // practices: none
89
- .mockResolvedValueOnce(["claude-code"]); // harnesses
90
-
91
- await runSetup(dir, catalog);
92
-
93
- const config = await readUserConfig(dir);
94
- expect(config!.choices).toEqual({ process: "native-agents-md" });
95
-
96
- const lock = await readLockFile(dir);
97
- expect(lock!.choices).toEqual({ process: "native-agents-md" });
98
- expect(lock!.logical_config.instructions.length).toBeGreaterThan(0);
99
-
100
- // Agent output: .claude/agents/ade.md is written with instruction text
101
- const { readFile } = await import("node:fs/promises");
102
- const agentMd = await readFile(
103
- join(dir, ".claude", "agents", "ade.md"),
104
- "utf-8"
105
- );
106
- expect(agentMd).toContain("AGENTS.md");
107
- });
108
-
109
- it("does not write any files when user cancels", async () => {
110
- const catalog = getDefaultCatalog();
111
- const cancelSymbol = Symbol("cancel");
112
-
113
- vi.mocked(clack.select).mockResolvedValueOnce(cancelSymbol);
114
- vi.mocked(clack.isCancel).mockReturnValue(true);
115
-
116
- await runSetup(dir, catalog);
117
-
118
- const config = await readUserConfig(dir);
119
- expect(config).toBeNull();
120
-
121
- const lock = await readLockFile(dir);
122
- expect(lock).toBeNull();
123
- });
124
-
125
- it("produces valid YAML that roundtrips through read", async () => {
126
- const catalog = getDefaultCatalog();
127
-
128
- vi.mocked(clack.select)
129
- .mockResolvedValueOnce("codemcp-workflows") // process
130
- .mockResolvedValueOnce("__skip__"); // architecture
131
- vi.mocked(clack.multiselect)
132
- .mockResolvedValueOnce([]) // practices: none
133
- .mockResolvedValueOnce(["claude-code"]); // harnesses
134
-
135
- await runSetup(dir, catalog);
136
-
137
- // Read the raw file and re-parse to ensure valid YAML
138
- const { readFile } = await import("node:fs/promises");
139
- const rawConfig = await readFile(join(dir, "config.yaml"), "utf-8");
140
- const rawLock = await readFile(join(dir, "config.lock.yaml"), "utf-8");
141
-
142
- // Both files should be non-empty valid YAML (not "undefined" or empty)
143
- expect(rawConfig.length).toBeGreaterThan(0);
144
- expect(rawLock.length).toBeGreaterThan(0);
145
- expect(rawConfig).toContain("codemcp-workflows");
146
- expect(rawLock).toContain("codemcp-workflows");
147
- });
148
- });