@codemcp/ade-core 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.
Files changed (71) hide show
  1. package/.prettierignore +1 -0
  2. package/.turbo/turbo-build.log +4 -0
  3. package/.turbo/turbo-format.log +6 -0
  4. package/.turbo/turbo-lint.log +4 -0
  5. package/.turbo/turbo-test.log +21 -0
  6. package/.turbo/turbo-typecheck.log +4 -0
  7. package/LICENSE +21 -0
  8. package/dist/catalog/facets/architecture.d.ts +2 -0
  9. package/dist/catalog/facets/architecture.js +424 -0
  10. package/dist/catalog/facets/backpressure.d.ts +2 -0
  11. package/dist/catalog/facets/backpressure.js +123 -0
  12. package/dist/catalog/facets/practices.d.ts +2 -0
  13. package/dist/catalog/facets/practices.js +163 -0
  14. package/dist/catalog/facets/process.d.ts +2 -0
  15. package/dist/catalog/facets/process.js +47 -0
  16. package/dist/catalog/index.d.ts +14 -0
  17. package/dist/catalog/index.js +71 -0
  18. package/dist/config.d.ts +5 -0
  19. package/dist/config.js +29 -0
  20. package/dist/index.d.ts +12 -0
  21. package/dist/index.js +6 -0
  22. package/dist/registry.d.ts +7 -0
  23. package/dist/registry.js +41 -0
  24. package/dist/resolver.d.ts +7 -0
  25. package/dist/resolver.js +142 -0
  26. package/dist/types.d.ts +110 -0
  27. package/dist/types.js +2 -0
  28. package/dist/writers/git-hooks.d.ts +2 -0
  29. package/dist/writers/git-hooks.js +7 -0
  30. package/dist/writers/instruction.d.ts +2 -0
  31. package/dist/writers/instruction.js +6 -0
  32. package/dist/writers/knowledge.d.ts +2 -0
  33. package/dist/writers/knowledge.js +9 -0
  34. package/dist/writers/setup-note.d.ts +2 -0
  35. package/dist/writers/setup-note.js +7 -0
  36. package/dist/writers/skills.d.ts +2 -0
  37. package/dist/writers/skills.js +7 -0
  38. package/dist/writers/workflows.d.ts +2 -0
  39. package/dist/writers/workflows.js +16 -0
  40. package/eslint.config.mjs +40 -0
  41. package/nodemon.json +7 -0
  42. package/package.json +34 -0
  43. package/src/catalog/catalog.spec.ts +531 -0
  44. package/src/catalog/facets/architecture.ts +438 -0
  45. package/src/catalog/facets/backpressure.ts +143 -0
  46. package/src/catalog/facets/practices.ts +173 -0
  47. package/src/catalog/facets/process.ts +50 -0
  48. package/src/catalog/index.ts +86 -0
  49. package/src/config.spec.ts +165 -0
  50. package/src/config.ts +39 -0
  51. package/src/index.ts +49 -0
  52. package/src/registry.spec.ts +144 -0
  53. package/src/registry.ts +68 -0
  54. package/src/resolver.spec.ts +581 -0
  55. package/src/resolver.ts +170 -0
  56. package/src/types.ts +151 -0
  57. package/src/writers/git-hooks.ts +9 -0
  58. package/src/writers/instruction.spec.ts +42 -0
  59. package/src/writers/instruction.ts +8 -0
  60. package/src/writers/knowledge.spec.ts +26 -0
  61. package/src/writers/knowledge.ts +15 -0
  62. package/src/writers/setup-note.ts +9 -0
  63. package/src/writers/skills.spec.ts +109 -0
  64. package/src/writers/skills.ts +9 -0
  65. package/src/writers/workflows.spec.ts +72 -0
  66. package/src/writers/workflows.ts +26 -0
  67. package/tsconfig.build.json +8 -0
  68. package/tsconfig.json +7 -0
  69. package/tsconfig.tsbuildinfo +1 -0
  70. package/tsconfig.vitest.json +7 -0
  71. package/vitest.config.ts +5 -0
@@ -0,0 +1,170 @@
1
+ import type {
2
+ UserConfig,
3
+ Catalog,
4
+ WriterRegistry,
5
+ LogicalConfig,
6
+ McpServerEntry,
7
+ ResolutionContext,
8
+ DocsetDef
9
+ } from "./types.js";
10
+ import { getFacet, getOption } from "./catalog/index.js";
11
+ import { getProvisionWriter } from "./registry.js";
12
+
13
+ export async function resolve(
14
+ userConfig: UserConfig,
15
+ catalog: Catalog,
16
+ registry: WriterRegistry
17
+ ): Promise<LogicalConfig> {
18
+ const result: LogicalConfig = {
19
+ mcp_servers: [],
20
+ instructions: [],
21
+ cli_actions: [],
22
+ knowledge_sources: [],
23
+ skills: [],
24
+ git_hooks: [],
25
+ setup_notes: []
26
+ };
27
+
28
+ const context: ResolutionContext = { resolved: {} };
29
+
30
+ for (const [facetId, optionId] of Object.entries(userConfig.choices)) {
31
+ const facet = getFacet(catalog, facetId);
32
+ if (!facet) {
33
+ continue;
34
+ }
35
+
36
+ const selectedIds = Array.isArray(optionId) ? optionId : [optionId];
37
+
38
+ for (const selectedId of selectedIds) {
39
+ const option = getOption(facet, selectedId);
40
+ if (!option) {
41
+ throw new Error(
42
+ `Unknown option "${selectedId}" for facet "${facetId}"`
43
+ );
44
+ }
45
+
46
+ context.resolved[facetId] = { optionId: selectedId, option };
47
+
48
+ for (const provision of option.recipe) {
49
+ const writer = getProvisionWriter(registry, provision.writer);
50
+ if (!writer) {
51
+ continue;
52
+ }
53
+ const partial = await writer.write(provision.config, context);
54
+ if (partial.mcp_servers) {
55
+ result.mcp_servers.push(...partial.mcp_servers);
56
+ }
57
+ if (partial.instructions) {
58
+ result.instructions.push(...partial.instructions);
59
+ }
60
+ if (partial.cli_actions) {
61
+ result.cli_actions.push(...partial.cli_actions);
62
+ }
63
+ if (partial.knowledge_sources) {
64
+ result.knowledge_sources.push(...partial.knowledge_sources);
65
+ }
66
+ if (partial.skills) {
67
+ result.skills.push(...partial.skills);
68
+ }
69
+ if (partial.git_hooks) {
70
+ result.git_hooks.push(...partial.git_hooks);
71
+ }
72
+ if (partial.setup_notes) {
73
+ result.setup_notes.push(...partial.setup_notes);
74
+ }
75
+ }
76
+ }
77
+ }
78
+
79
+ // Collect docsets from all selected options, dedup by id, filter exclusions
80
+ const seenDocsets = new Map<string, DocsetDef>();
81
+ for (const [facetId, optionId] of Object.entries(userConfig.choices)) {
82
+ const facet = getFacet(catalog, facetId);
83
+ if (!facet) continue;
84
+ const selectedIds = Array.isArray(optionId) ? optionId : [optionId];
85
+ for (const selectedId of selectedIds) {
86
+ const option = getOption(facet, selectedId);
87
+ if (!option?.docsets) continue;
88
+ for (const docset of option.docsets) {
89
+ if (!seenDocsets.has(docset.id)) {
90
+ seenDocsets.set(docset.id, docset);
91
+ }
92
+ }
93
+ }
94
+ }
95
+
96
+ const excludedSet = new Set(userConfig.excluded_docsets ?? []);
97
+ for (const [id, docset] of seenDocsets) {
98
+ if (excludedSet.has(id)) continue;
99
+ result.knowledge_sources.push({
100
+ name: docset.id,
101
+ origin: docset.origin,
102
+ description: docset.description
103
+ });
104
+ }
105
+
106
+ // Add knowledge-server MCP entry if any knowledge_sources were collected
107
+ if (result.knowledge_sources.length > 0) {
108
+ result.mcp_servers.push({
109
+ ref: "knowledge",
110
+ command: "npx",
111
+ args: ["-y", "@codemcp/knowledge-server"],
112
+ env: {}
113
+ });
114
+ }
115
+
116
+ // Add skills-server MCP entry if any skills were collected
117
+ if (result.skills.length > 0) {
118
+ result.mcp_servers.push({
119
+ ref: "agentskills",
120
+ command: "npx",
121
+ args: ["-y", "@codemcp/skills-server"],
122
+ env: {}
123
+ });
124
+ }
125
+
126
+ // Merge custom section
127
+ if (userConfig.custom) {
128
+ if (userConfig.custom.instructions) {
129
+ result.instructions.push(...userConfig.custom.instructions);
130
+ }
131
+ if (userConfig.custom.mcp_servers) {
132
+ result.mcp_servers.push(...userConfig.custom.mcp_servers);
133
+ }
134
+ }
135
+
136
+ // Dedup mcp_servers by ref (last wins)
137
+ const serversByRef = new Map<string, McpServerEntry>();
138
+ for (const server of result.mcp_servers) {
139
+ serversByRef.set(server.ref, server);
140
+ }
141
+ result.mcp_servers = Array.from(serversByRef.values());
142
+
143
+ return result;
144
+ }
145
+
146
+ /**
147
+ * Collect all unique docsets implied by the given choices.
148
+ * Used by the TUI to present docsets for confirmation before resolution.
149
+ */
150
+ export function collectDocsets(
151
+ choices: Record<string, string | string[]>,
152
+ catalog: Catalog
153
+ ): DocsetDef[] {
154
+ const seen = new Map<string, DocsetDef>();
155
+ for (const [facetId, optionId] of Object.entries(choices)) {
156
+ const facet = getFacet(catalog, facetId);
157
+ if (!facet) continue;
158
+ const selectedIds = Array.isArray(optionId) ? optionId : [optionId];
159
+ for (const selectedId of selectedIds) {
160
+ const option = getOption(facet, selectedId);
161
+ if (!option?.docsets) continue;
162
+ for (const docset of option.docsets) {
163
+ if (!seen.has(docset.id)) {
164
+ seen.set(docset.id, docset);
165
+ }
166
+ }
167
+ }
168
+ }
169
+ return Array.from(seen.values());
170
+ }
package/src/types.ts ADDED
@@ -0,0 +1,151 @@
1
+ // --- Catalog types ---
2
+
3
+ export interface Catalog {
4
+ facets: Facet[];
5
+ }
6
+
7
+ export interface Facet {
8
+ id: string;
9
+ label: string;
10
+ description: string;
11
+ required: boolean;
12
+ multiSelect?: boolean;
13
+ dependsOn?: string[];
14
+ options: Option[];
15
+ }
16
+
17
+ export interface Option {
18
+ id: string;
19
+ label: string;
20
+ description: string;
21
+ recipe: Provision[];
22
+ docsets?: DocsetDef[];
23
+ available?: (deps: Record<string, Option | undefined>) => boolean;
24
+ }
25
+
26
+ export interface DocsetDef {
27
+ id: string;
28
+ label: string;
29
+ origin: string;
30
+ description: string;
31
+ }
32
+
33
+ export interface Provision {
34
+ writer: ProvisionWriter;
35
+ config: Record<string, unknown>;
36
+ }
37
+
38
+ export type ProvisionWriter =
39
+ | "workflows"
40
+ | "skills"
41
+ | "knowledge"
42
+ | "mcp-server"
43
+ | "instruction"
44
+ | "installable"
45
+ | "git-hooks"
46
+ | "setup-note";
47
+
48
+ // --- LogicalConfig types ---
49
+
50
+ export interface InlineSkill {
51
+ name: string;
52
+ description: string;
53
+ body: string;
54
+ }
55
+
56
+ export interface ExternalSkill {
57
+ name: string;
58
+ source: string;
59
+ }
60
+
61
+ export type SkillDefinition = InlineSkill | ExternalSkill;
62
+
63
+ export interface GitHook {
64
+ phase: "pre-commit" | "pre-push";
65
+ script: string;
66
+ }
67
+
68
+ export interface LogicalConfig {
69
+ mcp_servers: McpServerEntry[];
70
+ instructions: string[];
71
+ cli_actions: CliAction[];
72
+ knowledge_sources: KnowledgeSource[];
73
+ skills: SkillDefinition[];
74
+ git_hooks: GitHook[];
75
+ setup_notes: string[];
76
+ }
77
+
78
+ export interface McpServerEntry {
79
+ ref: string;
80
+ command: string;
81
+ args: string[];
82
+ env: Record<string, string>;
83
+ /**
84
+ * Tool names the agent is pre-approved to use from this server.
85
+ * Defaults to `["*"]` (all tools) when not specified.
86
+ */
87
+ allowedTools?: string[];
88
+ }
89
+
90
+ export interface CliAction {
91
+ command: string;
92
+ args: string[];
93
+ phase: "setup" | "install";
94
+ }
95
+
96
+ export interface KnowledgeSource {
97
+ name: string;
98
+ origin: string;
99
+ description: string;
100
+ }
101
+
102
+ // --- Resolution context ---
103
+
104
+ export interface ResolutionContext {
105
+ resolved: Record<string, ResolvedFacet>;
106
+ }
107
+
108
+ export interface ResolvedFacet {
109
+ optionId: string;
110
+ option: Option;
111
+ }
112
+
113
+ // --- Config file types ---
114
+
115
+ export interface UserConfig {
116
+ choices: Record<string, string | string[]>;
117
+ excluded_docsets?: string[];
118
+ harnesses?: string[];
119
+ custom?: {
120
+ mcp_servers?: McpServerEntry[];
121
+ instructions?: string[];
122
+ };
123
+ }
124
+
125
+ export interface LockFile {
126
+ version: 1;
127
+ generated_at: string;
128
+ choices: Record<string, string | string[]>;
129
+ harnesses?: string[];
130
+ logical_config: LogicalConfig;
131
+ }
132
+
133
+ // --- Writer contracts (open, any package can implement) ---
134
+
135
+ export interface ProvisionWriterDef {
136
+ id: string;
137
+ write(
138
+ config: Record<string, unknown>,
139
+ context: ResolutionContext
140
+ ): Promise<Partial<LogicalConfig>>;
141
+ }
142
+
143
+ export interface AgentWriterDef {
144
+ id: string;
145
+ install(config: LogicalConfig, projectRoot: string): Promise<void>;
146
+ }
147
+
148
+ export interface WriterRegistry {
149
+ provisions: Map<string, ProvisionWriterDef>;
150
+ agents: Map<string, AgentWriterDef>;
151
+ }
@@ -0,0 +1,9 @@
1
+ import type { ProvisionWriterDef, GitHook } from "../types.js";
2
+
3
+ export const gitHooksWriter: ProvisionWriterDef = {
4
+ id: "git-hooks",
5
+ async write(config) {
6
+ const { hooks } = config as { hooks: GitHook[] };
7
+ return { git_hooks: hooks };
8
+ }
9
+ };
@@ -0,0 +1,42 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { instructionWriter } from "./instruction.js";
3
+ import type { ResolutionContext } from "../types.js";
4
+
5
+ describe("instructionWriter", () => {
6
+ const context: ResolutionContext = { resolved: {} };
7
+
8
+ it("has id 'instruction'", () => {
9
+ expect(instructionWriter.id).toBe("instruction");
10
+ });
11
+
12
+ it("returns the text wrapped in an instructions array", async () => {
13
+ const result = await instructionWriter.write(
14
+ { text: "Always use strict mode" },
15
+ context
16
+ );
17
+ expect(result).toEqual({ instructions: ["Always use strict mode"] });
18
+ });
19
+
20
+ it("passes through the exact text without modification", async () => {
21
+ const verbatim = " leading spaces and trailing spaces ";
22
+ const result = await instructionWriter.write({ text: verbatim }, context);
23
+ expect(result).toEqual({ instructions: [verbatim] });
24
+ });
25
+
26
+ it("only returns instructions, not other LogicalConfig keys", async () => {
27
+ const result = await instructionWriter.write(
28
+ { text: "some instruction" },
29
+ context
30
+ );
31
+ expect(Object.keys(result)).toEqual(["instructions"]);
32
+ expect(result).not.toHaveProperty("mcp_servers");
33
+ expect(result).not.toHaveProperty("cli_actions");
34
+ expect(result).not.toHaveProperty("knowledge_sources");
35
+ });
36
+
37
+ it("handles multi-line text correctly", async () => {
38
+ const multiLine = "Line one\nLine two\nLine three";
39
+ const result = await instructionWriter.write({ text: multiLine }, context);
40
+ expect(result).toEqual({ instructions: [multiLine] });
41
+ });
42
+ });
@@ -0,0 +1,8 @@
1
+ import type { ProvisionWriterDef } from "../types.js";
2
+
3
+ export const instructionWriter: ProvisionWriterDef = {
4
+ id: "instruction",
5
+ async write(config) {
6
+ return { instructions: [(config as { text: string }).text] };
7
+ }
8
+ };
@@ -0,0 +1,26 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { knowledgeWriter } from "./knowledge.js";
3
+
4
+ describe("knowledgeWriter", () => {
5
+ it("has id 'knowledge'", () => {
6
+ expect(knowledgeWriter.id).toBe("knowledge");
7
+ });
8
+
9
+ it("produces a knowledge_sources entry from config", async () => {
10
+ const result = await knowledgeWriter.write(
11
+ {
12
+ name: "react-docs",
13
+ origin: "https://github.com/facebook/react.git",
14
+ description: "Official React documentation"
15
+ },
16
+ { resolved: {} }
17
+ );
18
+
19
+ expect(result.knowledge_sources).toHaveLength(1);
20
+ expect(result.knowledge_sources![0]).toEqual({
21
+ name: "react-docs",
22
+ origin: "https://github.com/facebook/react.git",
23
+ description: "Official React documentation"
24
+ });
25
+ });
26
+ });
@@ -0,0 +1,15 @@
1
+ import type { ProvisionWriterDef } from "../types.js";
2
+
3
+ export const knowledgeWriter: ProvisionWriterDef = {
4
+ id: "knowledge",
5
+ async write(config) {
6
+ const { name, origin, description } = config as {
7
+ name: string;
8
+ origin: string;
9
+ description: string;
10
+ };
11
+ return {
12
+ knowledge_sources: [{ name, origin, description }]
13
+ };
14
+ }
15
+ };
@@ -0,0 +1,9 @@
1
+ import type { ProvisionWriterDef } from "../types.js";
2
+
3
+ export const setupNoteWriter: ProvisionWriterDef = {
4
+ id: "setup-note",
5
+ async write(config) {
6
+ const { text } = config as { text: string };
7
+ return { setup_notes: [text] };
8
+ }
9
+ };
@@ -0,0 +1,109 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { skillsWriter } from "./skills.js";
3
+
4
+ describe("skillsWriter", () => {
5
+ const emptyContext = { resolved: {} };
6
+
7
+ it("returns skills from config", async () => {
8
+ const result = await skillsWriter.write(
9
+ {
10
+ skills: [
11
+ {
12
+ name: "my-skill",
13
+ description: "A test skill",
14
+ body: "Do the thing."
15
+ }
16
+ ]
17
+ },
18
+ emptyContext
19
+ );
20
+
21
+ expect(result.skills).toHaveLength(1);
22
+ expect(result.skills![0]).toEqual({
23
+ name: "my-skill",
24
+ description: "A test skill",
25
+ body: "Do the thing."
26
+ });
27
+ });
28
+
29
+ it("returns multiple skills", async () => {
30
+ const result = await skillsWriter.write(
31
+ {
32
+ skills: [
33
+ { name: "skill-a", description: "First", body: "Body A" },
34
+ { name: "skill-b", description: "Second", body: "Body B" }
35
+ ]
36
+ },
37
+ emptyContext
38
+ );
39
+
40
+ expect(result.skills).toHaveLength(2);
41
+ expect(result.skills!.map((s) => s.name)).toEqual(["skill-a", "skill-b"]);
42
+ });
43
+
44
+ it("returns only the skills key", async () => {
45
+ const result = await skillsWriter.write(
46
+ {
47
+ skills: [{ name: "x", description: "desc", body: "body" }]
48
+ },
49
+ emptyContext
50
+ );
51
+
52
+ expect(Object.keys(result)).toEqual(["skills"]);
53
+ });
54
+
55
+ it("preserves multi-line body content", async () => {
56
+ const body =
57
+ "# Architecture\n\nUse layered architecture.\n\n## Rules\n- Rule 1\n- Rule 2";
58
+ const result = await skillsWriter.write(
59
+ {
60
+ skills: [{ name: "arch", description: "Architecture", body }]
61
+ },
62
+ emptyContext
63
+ );
64
+
65
+ expect(result.skills![0]).toMatchObject({ body });
66
+ });
67
+
68
+ it("returns external skills with source reference", async () => {
69
+ const result = await skillsWriter.write(
70
+ {
71
+ skills: [
72
+ {
73
+ name: "playwright-cli",
74
+ source: "microsoft/playwright-cli/skills/playwright-cli"
75
+ }
76
+ ]
77
+ },
78
+ emptyContext
79
+ );
80
+
81
+ expect(result.skills).toHaveLength(1);
82
+ expect(result.skills![0]).toEqual({
83
+ name: "playwright-cli",
84
+ source: "microsoft/playwright-cli/skills/playwright-cli"
85
+ });
86
+ });
87
+
88
+ it("handles mixed inline and external skills", async () => {
89
+ const result = await skillsWriter.write(
90
+ {
91
+ skills: [
92
+ { name: "my-skill", description: "Inline", body: "Do stuff." },
93
+ { name: "ext-skill", source: "org/repo/skills/ext" }
94
+ ]
95
+ },
96
+ emptyContext
97
+ );
98
+
99
+ expect(result.skills).toHaveLength(2);
100
+ expect(result.skills![0]).toMatchObject({
101
+ name: "my-skill",
102
+ body: "Do stuff."
103
+ });
104
+ expect(result.skills![1]).toMatchObject({
105
+ name: "ext-skill",
106
+ source: "org/repo/skills/ext"
107
+ });
108
+ });
109
+ });
@@ -0,0 +1,9 @@
1
+ import type { ProvisionWriterDef, SkillDefinition } from "../types.js";
2
+
3
+ export const skillsWriter: ProvisionWriterDef = {
4
+ id: "skills",
5
+ async write(config) {
6
+ const { skills } = config as { skills: SkillDefinition[] };
7
+ return { skills };
8
+ }
9
+ };
@@ -0,0 +1,72 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { workflowsWriter } from "./workflows.js";
3
+ import type { ResolutionContext } from "../types.js";
4
+
5
+ describe("workflowsWriter", () => {
6
+ const context: ResolutionContext = { resolved: {} };
7
+
8
+ it("has id 'workflows'", () => {
9
+ expect(workflowsWriter.id).toBe("workflows");
10
+ });
11
+
12
+ it("returns mcp_servers with correct ref, command, and args for a given package", async () => {
13
+ const result = await workflowsWriter.write(
14
+ { package: "@codemcp/workflows-server" },
15
+ context
16
+ );
17
+ expect(result).toEqual({
18
+ mcp_servers: [
19
+ {
20
+ ref: "@codemcp/workflows-server",
21
+ command: "npx",
22
+ args: ["@codemcp/workflows-server"],
23
+ env: {}
24
+ }
25
+ ]
26
+ });
27
+ });
28
+
29
+ it("uses ref override when provided", async () => {
30
+ const result = await workflowsWriter.write(
31
+ { package: "@codemcp/workflows-server@latest", ref: "workflows" },
32
+ context
33
+ );
34
+ expect(result.mcp_servers![0].ref).toBe("workflows");
35
+ expect(result.mcp_servers![0].args).toEqual([
36
+ "@codemcp/workflows-server@latest"
37
+ ]);
38
+ });
39
+
40
+ it("includes env in the entry when env is specified", async () => {
41
+ const result = await workflowsWriter.write(
42
+ {
43
+ package: "@codemcp/workflows-server",
44
+ env: { API_KEY: "secret", NODE_ENV: "production" }
45
+ },
46
+ context
47
+ );
48
+ expect(result.mcp_servers![0].env).toEqual({
49
+ API_KEY: "secret",
50
+ NODE_ENV: "production"
51
+ });
52
+ });
53
+
54
+ it("defaults env to an empty object when not specified", async () => {
55
+ const result = await workflowsWriter.write(
56
+ { package: "@codemcp/workflows-server" },
57
+ context
58
+ );
59
+ expect(result.mcp_servers![0].env).toEqual({});
60
+ });
61
+
62
+ it("only returns mcp_servers, not other LogicalConfig keys", async () => {
63
+ const result = await workflowsWriter.write(
64
+ { package: "@codemcp/workflows-server" },
65
+ context
66
+ );
67
+ expect(Object.keys(result)).toEqual(["mcp_servers"]);
68
+ expect(result).not.toHaveProperty("instructions");
69
+ expect(result).not.toHaveProperty("cli_actions");
70
+ expect(result).not.toHaveProperty("knowledge_sources");
71
+ });
72
+ });
@@ -0,0 +1,26 @@
1
+ import type { ProvisionWriterDef } from "../types.js";
2
+
3
+ export const workflowsWriter: ProvisionWriterDef = {
4
+ id: "workflows",
5
+ async write(config) {
6
+ const {
7
+ package: pkg,
8
+ ref,
9
+ env
10
+ } = config as {
11
+ package: string;
12
+ ref?: string;
13
+ env?: Record<string, string>;
14
+ };
15
+ return {
16
+ mcp_servers: [
17
+ {
18
+ ref: ref ?? pkg,
19
+ command: "npx",
20
+ args: [pkg],
21
+ env: env ?? {}
22
+ }
23
+ ]
24
+ };
25
+ }
26
+ };
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.build.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist"
5
+ },
6
+ "include": ["src/**/*"],
7
+ "exclude": ["**/*.spec.ts"]
8
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "baseUrl": "."
5
+ },
6
+ "include": ["src/**/*"]
7
+ }