@codemcp/ade-cli 0.1.1 → 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.
- package/dist/index.d.ts +0 -1
- package/dist/index.js +280 -38
- package/package.json +16 -14
- package/.prettierignore +0 -1
- package/.turbo/turbo-build.log +0 -4
- package/.turbo/turbo-format.log +0 -6
- package/.turbo/turbo-lint.log +0 -4
- package/.turbo/turbo-test.log +0 -1264
- package/.turbo/turbo-typecheck.log +0 -4
- package/dist/commands/install.d.ts +0 -1
- package/dist/commands/install.js +0 -39
- package/dist/commands/setup.d.ts +0 -2
- package/dist/commands/setup.js +0 -177
- package/dist/knowledge-installer.d.ts +0 -12
- package/dist/knowledge-installer.js +0 -38
- package/dist/version.d.ts +0 -1
- package/dist/version.js +0 -1
- package/eslint.config.mjs +0 -40
- package/nodemon.json +0 -7
- package/src/commands/conventions.integration.spec.ts +0 -267
- package/src/commands/install.integration.spec.ts +0 -123
- package/src/commands/install.spec.ts +0 -169
- package/src/commands/install.ts +0 -63
- package/src/commands/knowledge.integration.spec.ts +0 -129
- package/src/commands/setup.integration.spec.ts +0 -148
- package/src/commands/setup.spec.ts +0 -442
- package/src/commands/setup.ts +0 -252
- package/src/index.ts +0 -52
- package/src/knowledge-installer.spec.ts +0 -111
- package/src/knowledge-installer.ts +0 -54
- package/src/version.ts +0 -1
- package/tsconfig.build.json +0 -8
- package/tsconfig.json +0 -10
- package/tsconfig.tsbuildinfo +0 -1
- package/tsconfig.vitest.json +0 -7
- package/vitest.config.ts +0 -5
|
@@ -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
|
-
});
|
package/src/commands/install.ts
DELETED
|
@@ -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
|
-
});
|