@omnidev-ai/core 0.1.0 → 0.3.0

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 (42) hide show
  1. package/package.json +3 -2
  2. package/src/capability/commands.test.ts +6 -10
  3. package/src/capability/commands.ts +3 -1
  4. package/src/capability/docs.test.ts +39 -46
  5. package/src/capability/docs.ts +3 -1
  6. package/src/capability/loader.test.ts +10 -157
  7. package/src/capability/loader.ts +8 -69
  8. package/src/capability/registry.test.ts +9 -27
  9. package/src/capability/rules.test.ts +25 -35
  10. package/src/capability/rules.ts +3 -1
  11. package/src/capability/skills.test.ts +6 -10
  12. package/src/capability/skills.ts +3 -1
  13. package/src/capability/sources.test.ts +142 -41
  14. package/src/capability/sources.ts +377 -345
  15. package/src/capability/subagents.test.ts +7 -11
  16. package/src/capability/subagents.ts +3 -1
  17. package/src/capability/wrapping-integration.test.ts +412 -0
  18. package/src/config/capabilities.ts +0 -28
  19. package/src/config/env.test.ts +4 -20
  20. package/src/config/loader.test.ts +4 -88
  21. package/src/config/loader.ts +88 -18
  22. package/src/config/parser.test.ts +0 -25
  23. package/src/config/profiles.test.ts +5 -42
  24. package/src/config/provider.test.ts +5 -18
  25. package/src/index.ts +1 -3
  26. package/src/mcp-json/manager.test.ts +77 -182
  27. package/src/mcp-json/manager.ts +22 -34
  28. package/src/state/active-profile.test.ts +4 -18
  29. package/src/state/index.ts +1 -0
  30. package/src/state/manifest.test.ts +25 -162
  31. package/src/state/manifest.ts +4 -31
  32. package/src/state/providers.test.ts +125 -0
  33. package/src/state/providers.ts +69 -0
  34. package/src/sync.ts +128 -53
  35. package/src/templates/claude.ts +9 -74
  36. package/src/test-utils/helpers.test.ts +18 -0
  37. package/src/test-utils/helpers.ts +98 -1
  38. package/src/test-utils/index.ts +4 -0
  39. package/src/types/capability-export.ts +0 -77
  40. package/src/types/index.ts +66 -22
  41. package/src/gitignore/manager.test.ts +0 -219
  42. package/src/gitignore/manager.ts +0 -167
@@ -1,48 +1,16 @@
1
- import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
1
+ import { describe, expect, test } from "bun:test";
5
2
  import type { LoadedCapability } from "../types";
6
- import { isOmniDevMcp, readMcpJson, syncMcpJson, writeMcpJson } from "./manager";
3
+ import type { ResourceManifest } from "../state/manifest";
4
+ import { setupTestDir } from "@omnidev-ai/core/test-utils";
5
+ import { readMcpJson, syncMcpJson, writeMcpJson } from "./manager";
7
6
 
8
7
  describe("mcp-json manager", () => {
9
- let originalCwd: string;
10
- let tempDir: string;
11
-
12
- beforeEach(() => {
13
- originalCwd = process.cwd();
14
- tempDir = mkdtempSync(join(tmpdir(), "mcp-json-test-"));
15
- mkdirSync(join(tempDir, ".omni"), { recursive: true });
16
- process.chdir(tempDir);
17
- });
18
-
19
- afterEach(() => {
20
- process.chdir(originalCwd);
21
- rmSync(tempDir, { recursive: true, force: true });
22
- });
23
-
24
- describe("isOmniDevMcp", () => {
25
- test("returns true for 'omnidev' server name", () => {
26
- expect(isOmniDevMcp("omnidev")).toBe(true);
27
- });
8
+ setupTestDir("mcp-json-test-", { chdir: true, createOmniDir: true });
28
9
 
29
- test("returns true for 'omni-' prefixed server names", () => {
30
- expect(isOmniDevMcp("omni-tasks")).toBe(true);
31
- expect(isOmniDevMcp("omni-context7")).toBe(true);
32
- expect(isOmniDevMcp("omni-my-capability")).toBe(true);
33
- });
34
-
35
- test("returns false for non-OmniDev server names", () => {
36
- expect(isOmniDevMcp("myserver")).toBe(false);
37
- expect(isOmniDevMcp("playwright")).toBe(false);
38
- expect(isOmniDevMcp("custom-mcp")).toBe(false);
39
- });
40
-
41
- test("returns false for similar but different names", () => {
42
- expect(isOmniDevMcp("omnidev-extra")).toBe(false);
43
- expect(isOmniDevMcp("my-omnidev")).toBe(false);
44
- expect(isOmniDevMcp("omnisomething")).toBe(false);
45
- });
10
+ const createEmptyManifest = (): ResourceManifest => ({
11
+ version: 1,
12
+ syncedAt: new Date().toISOString(),
13
+ capabilities: {},
46
14
  });
47
15
 
48
16
  describe("readMcpJson", () => {
@@ -147,68 +115,8 @@ describe("mcp-json manager", () => {
147
115
  exports: {},
148
116
  });
149
117
 
150
- describe("sandbox enabled mode (default)", () => {
151
- test("adds only omnidev server when sandbox enabled", async () => {
152
- const capabilities = [createMockCapability("tasks")];
153
-
154
- await syncMcpJson(capabilities, true, { silent: true });
155
-
156
- const config = await readMcpJson();
157
- expect(config.mcpServers).toHaveProperty("omnidev");
158
- expect(config.mcpServers.omnidev).toEqual({
159
- command: "bunx",
160
- args: ["omnidev", "serve"],
161
- });
162
- });
163
-
164
- test("removes omni- prefixed servers when switching to sandbox enabled", async () => {
165
- // Pre-populate with omni- entries
166
- await Bun.write(
167
- ".mcp.json",
168
- JSON.stringify({
169
- mcpServers: {
170
- "omni-tasks": { command: "npx", args: ["tasks-mcp"] },
171
- "omni-context7": { command: "npx", args: ["context7-mcp"] },
172
- },
173
- }),
174
- );
175
-
176
- const capabilities = [
177
- createMockCapability("tasks", { command: "npx", args: ["tasks-mcp"] }),
178
- ];
179
-
180
- await syncMcpJson(capabilities, true, { silent: true });
181
-
182
- const config = await readMcpJson();
183
- expect(config.mcpServers).not.toHaveProperty("omni-tasks");
184
- expect(config.mcpServers).not.toHaveProperty("omni-context7");
185
- expect(config.mcpServers).toHaveProperty("omnidev");
186
- });
187
-
188
- test("preserves user MCPs when sandbox enabled", async () => {
189
- await Bun.write(
190
- ".mcp.json",
191
- JSON.stringify({
192
- mcpServers: {
193
- myserver: { command: "node", args: ["my-server.js"] },
194
- playwright: { command: "npx", args: ["@playwright/mcp"] },
195
- },
196
- }),
197
- );
198
-
199
- const capabilities = [createMockCapability("tasks")];
200
-
201
- await syncMcpJson(capabilities, true, { silent: true });
202
-
203
- const config = await readMcpJson();
204
- expect(config.mcpServers).toHaveProperty("myserver");
205
- expect(config.mcpServers).toHaveProperty("playwright");
206
- expect(config.mcpServers).toHaveProperty("omnidev");
207
- });
208
- });
209
-
210
- describe("sandbox disabled mode", () => {
211
- test("adds omni- prefixed servers for MCP capabilities", async () => {
118
+ describe("MCP wrapping", () => {
119
+ test("adds MCP servers using capability ID", async () => {
212
120
  const capabilities = [
213
121
  createMockCapability("context7", {
214
122
  command: "npx",
@@ -216,15 +124,14 @@ describe("mcp-json manager", () => {
216
124
  }),
217
125
  ];
218
126
 
219
- await syncMcpJson(capabilities, false, { silent: true });
127
+ await syncMcpJson(capabilities, createEmptyManifest(), { silent: true });
220
128
 
221
129
  const config = await readMcpJson();
222
- expect(config.mcpServers).toHaveProperty("omni-context7");
223
- expect(config.mcpServers["omni-context7"]).toEqual({
130
+ expect(config.mcpServers).toHaveProperty("context7");
131
+ expect(config.mcpServers["context7"]).toEqual({
224
132
  command: "npx",
225
133
  args: ["-y", "@upstash/context7-mcp"],
226
134
  });
227
- expect(config.mcpServers).not.toHaveProperty("omnidev");
228
135
  });
229
136
 
230
137
  test("does not add entries for capabilities without MCP", async () => {
@@ -233,11 +140,11 @@ describe("mcp-json manager", () => {
233
140
  createMockCapability("context7", { command: "npx", args: ["context7-mcp"] }),
234
141
  ];
235
142
 
236
- await syncMcpJson(capabilities, false, { silent: true });
143
+ await syncMcpJson(capabilities, createEmptyManifest(), { silent: true });
237
144
 
238
145
  const config = await readMcpJson();
239
- expect(config.mcpServers).not.toHaveProperty("omni-tasks");
240
- expect(config.mcpServers).toHaveProperty("omni-context7");
146
+ expect(config.mcpServers).not.toHaveProperty("tasks");
147
+ expect(config.mcpServers).toHaveProperty("context7");
241
148
  });
242
149
 
243
150
  test("includes env when present in MCP config", async () => {
@@ -249,37 +156,49 @@ describe("mcp-json manager", () => {
249
156
  }),
250
157
  ];
251
158
 
252
- await syncMcpJson(capabilities, false, { silent: true });
159
+ await syncMcpJson(capabilities, createEmptyManifest(), { silent: true });
253
160
 
254
161
  const config = await readMcpJson();
255
- expect(config.mcpServers["omni-my-cap"].env).toEqual({
162
+ expect(config.mcpServers["my-cap"].env).toEqual({
256
163
  API_KEY: "secret",
257
164
  DEBUG: "true",
258
165
  });
259
166
  });
260
167
 
261
- test("removes omnidev server when switching to sandbox disabled", async () => {
168
+ test("removes previously managed MCP from manifest", async () => {
169
+ // Setup: pre-populate .mcp.json with an old MCP
262
170
  await Bun.write(
263
171
  ".mcp.json",
264
172
  JSON.stringify({
265
173
  mcpServers: {
266
- omnidev: { command: "bunx", args: ["omnidev", "serve"] },
174
+ oldcap: { command: "npx", args: ["old-mcp"] },
175
+ userserver: { command: "node", args: ["user.js"] },
267
176
  },
268
177
  }),
269
178
  );
270
179
 
180
+ // Previous manifest tracks oldcap as managed
181
+ const previousManifest: ResourceManifest = {
182
+ version: 1,
183
+ syncedAt: new Date().toISOString(),
184
+ capabilities: {
185
+ oldcap: { skills: [], rules: [], commands: [], subagents: [], mcps: ["oldcap"] },
186
+ },
187
+ };
188
+
271
189
  const capabilities = [
272
190
  createMockCapability("context7", { command: "npx", args: ["context7-mcp"] }),
273
191
  ];
274
192
 
275
- await syncMcpJson(capabilities, false, { silent: true });
193
+ await syncMcpJson(capabilities, previousManifest, { silent: true });
276
194
 
277
195
  const config = await readMcpJson();
278
- expect(config.mcpServers).not.toHaveProperty("omnidev");
279
- expect(config.mcpServers).toHaveProperty("omni-context7");
196
+ expect(config.mcpServers).not.toHaveProperty("oldcap"); // Removed (was managed)
197
+ expect(config.mcpServers).toHaveProperty("userserver"); // Preserved (not managed)
198
+ expect(config.mcpServers).toHaveProperty("context7"); // Added
280
199
  });
281
200
 
282
- test("preserves user MCPs when sandbox disabled", async () => {
201
+ test("preserves user MCPs", async () => {
283
202
  await Bun.write(
284
203
  ".mcp.json",
285
204
  JSON.stringify({
@@ -293,122 +212,98 @@ describe("mcp-json manager", () => {
293
212
  createMockCapability("context7", { command: "npx", args: ["context7-mcp"] }),
294
213
  ];
295
214
 
296
- await syncMcpJson(capabilities, false, { silent: true });
215
+ await syncMcpJson(capabilities, createEmptyManifest(), { silent: true });
297
216
 
298
217
  const config = await readMcpJson();
299
218
  expect(config.mcpServers).toHaveProperty("myserver");
300
- expect(config.mcpServers).toHaveProperty("omni-context7");
219
+ expect(config.mcpServers).toHaveProperty("context7");
301
220
  });
302
221
 
303
- test("results in empty omni entries when no MCP capabilities", async () => {
222
+ test("does not add entries when no MCP capabilities", async () => {
304
223
  const capabilities = [
305
224
  createMockCapability("tasks"), // No MCP
306
225
  createMockCapability("ralph"), // No MCP
307
226
  ];
308
227
 
309
- await syncMcpJson(capabilities, false, { silent: true });
228
+ await syncMcpJson(capabilities, createEmptyManifest(), { silent: true });
310
229
 
311
230
  const config = await readMcpJson();
312
- const omniEntries = Object.keys(config.mcpServers).filter(isOmniDevMcp);
313
- expect(omniEntries).toHaveLength(0);
314
- });
315
- });
316
-
317
- describe("mode switching", () => {
318
- test("switching from sandbox disabled to enabled cleans up correctly", async () => {
319
- // Start in sandbox disabled mode
320
- const capabilities = [
321
- createMockCapability("context7", { command: "npx", args: ["context7-mcp"] }),
322
- ];
323
- await syncMcpJson(capabilities, false, { silent: true });
324
-
325
- let config = await readMcpJson();
326
- expect(config.mcpServers).toHaveProperty("omni-context7");
327
- expect(config.mcpServers).not.toHaveProperty("omnidev");
328
-
329
- // Switch to sandbox enabled
330
- await syncMcpJson(capabilities, true, { silent: true });
331
-
332
- config = await readMcpJson();
333
- expect(config.mcpServers).not.toHaveProperty("omni-context7");
334
- expect(config.mcpServers).toHaveProperty("omnidev");
335
- });
336
-
337
- test("switching from sandbox enabled to disabled cleans up correctly", async () => {
338
- // Start in sandbox enabled mode
339
- const capabilities = [
340
- createMockCapability("context7", { command: "npx", args: ["context7-mcp"] }),
341
- ];
342
- await syncMcpJson(capabilities, true, { silent: true });
343
-
344
- let config = await readMcpJson();
345
- expect(config.mcpServers).toHaveProperty("omnidev");
346
- expect(config.mcpServers).not.toHaveProperty("omni-context7");
347
-
348
- // Switch to sandbox disabled
349
- await syncMcpJson(capabilities, false, { silent: true });
350
-
351
- config = await readMcpJson();
352
- expect(config.mcpServers).toHaveProperty("omni-context7");
353
- expect(config.mcpServers).not.toHaveProperty("omnidev");
231
+ expect(config.mcpServers).not.toHaveProperty("tasks");
232
+ expect(config.mcpServers).not.toHaveProperty("ralph");
354
233
  });
355
234
  });
356
235
 
357
- describe("capability toggle (sandbox disabled)", () => {
236
+ describe("capability toggle", () => {
358
237
  test("enabling MCP capability adds its entry", async () => {
359
238
  // Start with no MCP capabilities
360
- await syncMcpJson([createMockCapability("tasks")], false, { silent: true });
239
+ let manifest = createEmptyManifest();
240
+ await syncMcpJson([createMockCapability("tasks")], manifest, { silent: true });
361
241
 
362
242
  let config = await readMcpJson();
363
- expect(Object.keys(config.mcpServers).filter(isOmniDevMcp)).toHaveLength(0);
364
-
365
- // Enable MCP capability
243
+ expect(Object.keys(config.mcpServers)).toHaveLength(0);
244
+
245
+ // Enable MCP capability - update manifest to track tasks (no mcps)
246
+ manifest = {
247
+ version: 1,
248
+ syncedAt: new Date().toISOString(),
249
+ capabilities: {
250
+ tasks: { skills: [], rules: [], commands: [], subagents: [], mcps: [] },
251
+ },
252
+ };
366
253
  await syncMcpJson(
367
254
  [
368
255
  createMockCapability("tasks"),
369
256
  createMockCapability("context7", { command: "npx", args: ["context7-mcp"] }),
370
257
  ],
371
- false,
258
+ manifest,
372
259
  { silent: true },
373
260
  );
374
261
 
375
262
  config = await readMcpJson();
376
- expect(config.mcpServers).toHaveProperty("omni-context7");
263
+ expect(config.mcpServers).toHaveProperty("context7");
377
264
  });
378
265
 
379
266
  test("disabling MCP capability removes its entry", async () => {
380
267
  // Start with MCP capability
268
+ let manifest = createEmptyManifest();
381
269
  await syncMcpJson(
382
270
  [createMockCapability("context7", { command: "npx", args: ["context7-mcp"] })],
383
- false,
271
+ manifest,
384
272
  { silent: true },
385
273
  );
386
274
 
387
275
  let config = await readMcpJson();
388
- expect(config.mcpServers).toHaveProperty("omni-context7");
389
-
390
- // Disable the capability (only non-MCP capabilities remain)
391
- await syncMcpJson([createMockCapability("tasks")], false, { silent: true });
276
+ expect(config.mcpServers).toHaveProperty("context7");
277
+
278
+ // Disable the capability - manifest now tracks context7 with its MCP
279
+ manifest = {
280
+ version: 1,
281
+ syncedAt: new Date().toISOString(),
282
+ capabilities: {
283
+ context7: { skills: [], rules: [], commands: [], subagents: [], mcps: ["context7"] },
284
+ },
285
+ };
286
+ await syncMcpJson([createMockCapability("tasks")], manifest, { silent: true });
392
287
 
393
288
  config = await readMcpJson();
394
- expect(config.mcpServers).not.toHaveProperty("omni-context7");
289
+ expect(config.mcpServers).not.toHaveProperty("context7");
395
290
  });
396
291
  });
397
292
 
398
293
  describe("multiple MCP capabilities", () => {
399
- test("adds all MCP capabilities when sandbox disabled", async () => {
294
+ test("adds all MCP capabilities", async () => {
400
295
  const capabilities = [
401
296
  createMockCapability("context7", { command: "npx", args: ["context7-mcp"] }),
402
297
  createMockCapability("playwright", { command: "npx", args: ["playwright-mcp"] }),
403
298
  createMockCapability("tasks"), // No MCP
404
299
  ];
405
300
 
406
- await syncMcpJson(capabilities, false, { silent: true });
301
+ await syncMcpJson(capabilities, createEmptyManifest(), { silent: true });
407
302
 
408
303
  const config = await readMcpJson();
409
- expect(config.mcpServers).toHaveProperty("omni-context7");
410
- expect(config.mcpServers).toHaveProperty("omni-playwright");
411
- expect(config.mcpServers).not.toHaveProperty("omni-tasks");
304
+ expect(config.mcpServers).toHaveProperty("context7");
305
+ expect(config.mcpServers).toHaveProperty("playwright");
306
+ expect(config.mcpServers).not.toHaveProperty("tasks");
412
307
  });
413
308
  });
414
309
  });
@@ -1,5 +1,6 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import type { LoadedCapability, McpConfig } from "../types";
3
+ import type { ResourceManifest } from "../state/manifest";
3
4
 
4
5
  /**
5
6
  * MCP server configuration in .mcp.json
@@ -19,13 +20,6 @@ export interface McpJsonConfig {
19
20
 
20
21
  const MCP_JSON_PATH = ".mcp.json";
21
22
 
22
- /**
23
- * Check if a server name is managed by OmniDev
24
- */
25
- export function isOmniDevMcp(serverName: string): boolean {
26
- return serverName === "omnidev" || serverName.startsWith("omni-");
27
- }
28
-
29
23
  /**
30
24
  * Read .mcp.json or return empty config if doesn't exist
31
25
  */
@@ -70,49 +64,43 @@ function buildMcpServerConfig(mcp: McpConfig): McpServerConfig {
70
64
  }
71
65
 
72
66
  /**
73
- * Sync .mcp.json based on sandbox mode
67
+ * Sync .mcp.json with enabled capability MCP servers
74
68
  *
75
- * When sandboxEnabled = true (default):
76
- * - Only "omnidev" MCP server is registered
77
- * - Capability MCPs run as children of OmniDev server
78
- *
79
- * When sandboxEnabled = false:
80
- * - Each capability's MCP is registered as "omni-{capabilityId}"
81
- * - OmniDev server is NOT registered
69
+ * Each capability with an [mcp] section is registered using its capability ID.
70
+ * Uses the previous manifest to track which MCPs were managed by OmniDev.
82
71
  */
83
72
  export async function syncMcpJson(
84
73
  capabilities: LoadedCapability[],
85
- sandboxEnabled: boolean,
74
+ previousManifest: ResourceManifest,
86
75
  options: { silent?: boolean } = {},
87
76
  ): Promise<void> {
88
77
  const mcpJson = await readMcpJson();
89
78
 
90
- // Remove all OmniDev-managed MCPs first
91
- for (const serverName of Object.keys(mcpJson.mcpServers)) {
92
- if (isOmniDevMcp(serverName)) {
93
- delete mcpJson.mcpServers[serverName];
79
+ // Collect all MCP server names from previous manifest
80
+ const previouslyManagedMcps = new Set<string>();
81
+ for (const resources of Object.values(previousManifest.capabilities)) {
82
+ for (const mcpName of resources.mcps) {
83
+ previouslyManagedMcps.add(mcpName);
94
84
  }
95
85
  }
96
86
 
97
- if (sandboxEnabled) {
98
- // Add only OmniDev MCP server
99
- mcpJson.mcpServers["omnidev"] = {
100
- command: "bunx",
101
- args: ["omnidev", "serve"],
102
- };
103
- } else {
104
- // Add MCPs from all enabled capabilities
105
- for (const cap of capabilities) {
106
- if (cap.config.mcp) {
107
- mcpJson.mcpServers[`omni-${cap.id}`] = buildMcpServerConfig(cap.config.mcp);
108
- }
87
+ // Remove previously managed MCPs
88
+ for (const serverName of previouslyManagedMcps) {
89
+ delete mcpJson.mcpServers[serverName];
90
+ }
91
+
92
+ // Add MCPs from all enabled capabilities
93
+ let addedCount = 0;
94
+ for (const cap of capabilities) {
95
+ if (cap.config.mcp) {
96
+ mcpJson.mcpServers[cap.id] = buildMcpServerConfig(cap.config.mcp);
97
+ addedCount++;
109
98
  }
110
99
  }
111
100
 
112
101
  await writeMcpJson(mcpJson);
113
102
 
114
103
  if (!options.silent) {
115
- const count = Object.keys(mcpJson.mcpServers).filter(isOmniDevMcp).length;
116
- console.log(` - .mcp.json (${count} MCP server(s))`);
104
+ console.log(` - .mcp.json (${addedCount} MCP server(s))`);
117
105
  }
118
106
  }
@@ -1,7 +1,6 @@
1
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import { existsSync, mkdirSync, mkdtempSync, rmSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
1
+ import { describe, expect, test } from "bun:test";
2
+ import { existsSync, mkdirSync } from "node:fs";
3
+ import { setupTestDir } from "@omnidev-ai/core/test-utils";
5
4
  import {
6
5
  clearActiveProfileState,
7
6
  readActiveProfileState,
@@ -9,20 +8,7 @@ import {
9
8
  } from "./active-profile";
10
9
 
11
10
  describe("active-profile state", () => {
12
- let originalCwd: string;
13
- let tempDir: string;
14
-
15
- beforeEach(() => {
16
- originalCwd = process.cwd();
17
- tempDir = mkdtempSync(join(tmpdir(), "active-profile-test-"));
18
- mkdirSync(join(tempDir, ".omni"), { recursive: true });
19
- process.chdir(tempDir);
20
- });
21
-
22
- afterEach(() => {
23
- process.chdir(originalCwd);
24
- rmSync(tempDir, { recursive: true, force: true });
25
- });
11
+ setupTestDir("active-profile-test-", { chdir: true, createOmniDir: true });
26
12
 
27
13
  describe("readActiveProfileState", () => {
28
14
  test("returns null when state file does not exist", async () => {
@@ -1,2 +1,3 @@
1
1
  export * from "./active-profile";
2
2
  export * from "./manifest";
3
+ export * from "./providers";