@codemcp/ade-cli 0.0.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.
@@ -0,0 +1,129 @@
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
+ });
@@ -0,0 +1,148 @@
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
+ });