@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnidev-ai/core",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -9,7 +9,8 @@
9
9
  "directory": "packages/core"
10
10
  },
11
11
  "exports": {
12
- ".": "./src/index.ts"
12
+ ".": "./src/index.ts",
13
+ "./test-utils": "./src/test-utils/index.ts"
13
14
  },
14
15
  "files": [
15
16
  "src",
@@ -1,22 +1,18 @@
1
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import { mkdirSync, rmSync, writeFileSync } from "node:fs";
1
+ import { beforeEach, describe, expect, test } from "bun:test";
2
+ import { mkdirSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
+ import { setupTestDir } from "@omnidev-ai/core/test-utils";
4
5
  import { loadCommands } from "./commands";
5
6
 
6
7
  describe("loadCommands", () => {
7
- const testDir = join(process.cwd(), "test-commands-temp");
8
- const capabilityPath = join(testDir, "test-capability");
8
+ const testDir = setupTestDir("capability-commands-test-");
9
+ let capabilityPath: string;
9
10
 
10
11
  beforeEach(() => {
12
+ capabilityPath = join(testDir.path, "test-capability");
11
13
  mkdirSync(capabilityPath, { recursive: true });
12
14
  });
13
15
 
14
- afterEach(() => {
15
- if (testDir) {
16
- rmSync(testDir, { recursive: true, force: true });
17
- }
18
- });
19
-
20
16
  test("returns empty array when commands directory does not exist", async () => {
21
17
  const commands = await loadCommands(capabilityPath, "test-cap");
22
18
  expect(commands).toEqual([]);
@@ -24,7 +24,9 @@ export async function loadCommands(
24
24
  }
25
25
 
26
26
  const commands: Command[] = [];
27
- const entries = readdirSync(commandsDir, { withFileTypes: true });
27
+ const entries = readdirSync(commandsDir, { withFileTypes: true }).sort((a, b) =>
28
+ a.name.localeCompare(b.name),
29
+ );
28
30
 
29
31
  for (const entry of entries) {
30
32
  if (entry.isDirectory()) {
@@ -1,31 +1,24 @@
1
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import { mkdirSync, rmSync, writeFileSync } from "node:fs";
1
+ import { describe, expect, test } from "bun:test";
2
+ import { mkdirSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
+ import { setupTestDir } from "@omnidev-ai/core/test-utils";
4
5
  import { loadDocs } from "./docs";
5
6
 
6
7
  describe("loadDocs", () => {
7
- let testDir: string;
8
-
9
- beforeEach(() => {
10
- testDir = join(process.cwd(), "test-capability-docs");
11
- mkdirSync(testDir, { recursive: true });
12
- });
13
-
14
- afterEach(() => {
15
- if (testDir) {
16
- rmSync(testDir, { recursive: true, force: true });
17
- }
18
- });
8
+ const testDir = setupTestDir("capability-docs-test-");
19
9
 
20
10
  test("returns empty array when no docs exist", async () => {
21
- const docs = await loadDocs(testDir, "test-cap");
11
+ const docs = await loadDocs(testDir.path, "test-cap");
22
12
  expect(docs).toEqual([]);
23
13
  });
24
14
 
25
15
  test("loads only definition.md when docs directory does not exist", async () => {
26
- writeFileSync(join(testDir, "definition.md"), "# Test Capability\n\nThis is the definition.");
16
+ writeFileSync(
17
+ join(testDir.path, "definition.md"),
18
+ "# Test Capability\n\nThis is the definition.",
19
+ );
27
20
 
28
- const docs = await loadDocs(testDir, "test-cap");
21
+ const docs = await loadDocs(testDir.path, "test-cap");
29
22
  expect(docs).toHaveLength(1);
30
23
  expect(docs[0]?.name).toBe("definition");
31
24
  expect(docs[0]?.content).toBe("# Test Capability\n\nThis is the definition.");
@@ -33,11 +26,11 @@ describe("loadDocs", () => {
33
26
  });
34
27
 
35
28
  test("loads only docs from docs directory when definition.md does not exist", async () => {
36
- const docsDir = join(testDir, "docs");
29
+ const docsDir = join(testDir.path, "docs");
37
30
  mkdirSync(docsDir);
38
31
  writeFileSync(join(docsDir, "guide.md"), "# Guide\n\nGuide content.");
39
32
 
40
- const docs = await loadDocs(testDir, "test-cap");
33
+ const docs = await loadDocs(testDir.path, "test-cap");
41
34
  expect(docs).toHaveLength(1);
42
35
  expect(docs[0]?.name).toBe("guide");
43
36
  expect(docs[0]?.content).toBe("# Guide\n\nGuide content.");
@@ -45,14 +38,14 @@ describe("loadDocs", () => {
45
38
  });
46
39
 
47
40
  test("loads both definition.md and docs from docs directory", async () => {
48
- writeFileSync(join(testDir, "definition.md"), "# Definition");
41
+ writeFileSync(join(testDir.path, "definition.md"), "# Definition");
49
42
 
50
- const docsDir = join(testDir, "docs");
43
+ const docsDir = join(testDir.path, "docs");
51
44
  mkdirSync(docsDir);
52
45
  writeFileSync(join(docsDir, "guide.md"), "# Guide");
53
46
  writeFileSync(join(docsDir, "examples.md"), "# Examples");
54
47
 
55
- const docs = await loadDocs(testDir, "test-cap");
48
+ const docs = await loadDocs(testDir.path, "test-cap");
56
49
  expect(docs).toHaveLength(3);
57
50
 
58
51
  const names = docs.map((d) => d.name).sort();
@@ -60,37 +53,37 @@ describe("loadDocs", () => {
60
53
  });
61
54
 
62
55
  test("trims whitespace from doc content", async () => {
63
- writeFileSync(join(testDir, "definition.md"), "\n\n # Definition\n\nContent.\n\n ");
56
+ writeFileSync(join(testDir.path, "definition.md"), "\n\n # Definition\n\nContent.\n\n ");
64
57
 
65
- const docsDir = join(testDir, "docs");
58
+ const docsDir = join(testDir.path, "docs");
66
59
  mkdirSync(docsDir);
67
60
  writeFileSync(join(docsDir, "guide.md"), " \n# Guide\n\nGuide content.\n ");
68
61
 
69
- const docs = await loadDocs(testDir, "test-cap");
62
+ const docs = await loadDocs(testDir.path, "test-cap");
70
63
  expect(docs).toHaveLength(2);
71
64
  expect(docs[0]?.content).toBe("# Definition\n\nContent.");
72
65
  expect(docs[1]?.content).toBe("# Guide\n\nGuide content.");
73
66
  });
74
67
 
75
68
  test("ignores non-markdown files in docs directory", async () => {
76
- const docsDir = join(testDir, "docs");
69
+ const docsDir = join(testDir.path, "docs");
77
70
  mkdirSync(docsDir);
78
71
  writeFileSync(join(docsDir, "guide.md"), "# Guide");
79
72
  writeFileSync(join(docsDir, "readme.txt"), "Not markdown");
80
73
  writeFileSync(join(docsDir, "config.json"), "{}");
81
74
 
82
- const docs = await loadDocs(testDir, "test-cap");
75
+ const docs = await loadDocs(testDir.path, "test-cap");
83
76
  expect(docs).toHaveLength(1);
84
77
  expect(docs[0]?.name).toBe("guide");
85
78
  });
86
79
 
87
80
  test("ignores directories in docs directory", async () => {
88
- const docsDir = join(testDir, "docs");
81
+ const docsDir = join(testDir.path, "docs");
89
82
  mkdirSync(docsDir);
90
83
  mkdirSync(join(docsDir, "subdir"));
91
84
  writeFileSync(join(docsDir, "guide.md"), "# Guide");
92
85
 
93
- const docs = await loadDocs(testDir, "test-cap");
86
+ const docs = await loadDocs(testDir.path, "test-cap");
94
87
  expect(docs).toHaveLength(1);
95
88
  expect(docs[0]?.name).toBe("guide");
96
89
  });
@@ -115,46 +108,46 @@ const code = "example";
115
108
  |----------|----------|
116
109
  | Cell 1 | Cell 2 |`;
117
110
 
118
- writeFileSync(join(testDir, "definition.md"), complexMarkdown);
111
+ writeFileSync(join(testDir.path, "definition.md"), complexMarkdown);
119
112
 
120
- const docs = await loadDocs(testDir, "test-cap");
113
+ const docs = await loadDocs(testDir.path, "test-cap");
121
114
  expect(docs).toHaveLength(1);
122
115
  expect(docs[0]?.content).toBe(complexMarkdown);
123
116
  });
124
117
 
125
118
  test("handles empty doc files", async () => {
126
- writeFileSync(join(testDir, "definition.md"), "");
119
+ writeFileSync(join(testDir.path, "definition.md"), "");
127
120
 
128
- const docsDir = join(testDir, "docs");
121
+ const docsDir = join(testDir.path, "docs");
129
122
  mkdirSync(docsDir);
130
123
  writeFileSync(join(docsDir, "guide.md"), "");
131
124
 
132
- const docs = await loadDocs(testDir, "test-cap");
125
+ const docs = await loadDocs(testDir.path, "test-cap");
133
126
  expect(docs).toHaveLength(2);
134
127
  expect(docs[0]?.content).toBe("");
135
128
  expect(docs[1]?.content).toBe("");
136
129
  });
137
130
 
138
131
  test("handles docs with only whitespace", async () => {
139
- writeFileSync(join(testDir, "definition.md"), " \n\n ");
132
+ writeFileSync(join(testDir.path, "definition.md"), " \n\n ");
140
133
 
141
- const docsDir = join(testDir, "docs");
134
+ const docsDir = join(testDir.path, "docs");
142
135
  mkdirSync(docsDir);
143
136
  writeFileSync(join(docsDir, "guide.md"), "\n\n\n");
144
137
 
145
- const docs = await loadDocs(testDir, "test-cap");
138
+ const docs = await loadDocs(testDir.path, "test-cap");
146
139
  expect(docs).toHaveLength(2);
147
140
  expect(docs[0]?.content).toBe("");
148
141
  expect(docs[1]?.content).toBe("");
149
142
  });
150
143
 
151
144
  test("handles doc names with hyphens and underscores", async () => {
152
- const docsDir = join(testDir, "docs");
145
+ const docsDir = join(testDir.path, "docs");
153
146
  mkdirSync(docsDir);
154
147
  writeFileSync(join(docsDir, "api-reference.md"), "Content");
155
148
  writeFileSync(join(docsDir, "getting_started.md"), "Content");
156
149
 
157
- const docs = await loadDocs(testDir, "test-cap");
150
+ const docs = await loadDocs(testDir.path, "test-cap");
158
151
  expect(docs).toHaveLength(2);
159
152
 
160
153
  const names = docs.map((d) => d.name).sort();
@@ -162,15 +155,15 @@ const code = "example";
162
155
  });
163
156
 
164
157
  test("loads multiple docs in consistent order", async () => {
165
- writeFileSync(join(testDir, "definition.md"), "Definition");
158
+ writeFileSync(join(testDir.path, "definition.md"), "Definition");
166
159
 
167
- const docsDir = join(testDir, "docs");
160
+ const docsDir = join(testDir.path, "docs");
168
161
  mkdirSync(docsDir);
169
162
  writeFileSync(join(docsDir, "aaa.md"), "AAA");
170
163
  writeFileSync(join(docsDir, "zzz.md"), "ZZZ");
171
164
  writeFileSync(join(docsDir, "mmm.md"), "MMM");
172
165
 
173
- const docs = await loadDocs(testDir, "test-cap");
166
+ const docs = await loadDocs(testDir.path, "test-cap");
174
167
  expect(docs).toHaveLength(4);
175
168
 
176
169
  // definition.md should be first, then docs in filesystem order
@@ -183,16 +176,16 @@ const code = "example";
183
176
  });
184
177
 
185
178
  test("returns empty array when docs directory is empty", async () => {
186
- mkdirSync(join(testDir, "docs"));
187
- const docs = await loadDocs(testDir, "test-cap");
179
+ mkdirSync(join(testDir.path, "docs"));
180
+ const docs = await loadDocs(testDir.path, "test-cap");
188
181
  expect(docs).toEqual([]);
189
182
  });
190
183
 
191
184
  test("handles very long doc content", async () => {
192
185
  const longContent = `# Long Document\n\n${"Content line.\n".repeat(1000)}`;
193
- writeFileSync(join(testDir, "definition.md"), longContent);
186
+ writeFileSync(join(testDir.path, "definition.md"), longContent);
194
187
 
195
- const docs = await loadDocs(testDir, "test-cap");
188
+ const docs = await loadDocs(testDir.path, "test-cap");
196
189
  expect(docs).toHaveLength(1);
197
190
  expect(docs[0]?.content).toBe(longContent.trim());
198
191
  });
@@ -26,7 +26,9 @@ export async function loadDocs(capabilityPath: string, capabilityId: string): Pr
26
26
  // Load docs/*.md
27
27
  const docsDir = join(capabilityPath, "docs");
28
28
  if (existsSync(docsDir)) {
29
- const entries = readdirSync(docsDir, { withFileTypes: true });
29
+ const entries = readdirSync(docsDir, { withFileTypes: true }).sort((a, b) =>
30
+ a.name.localeCompare(b.name),
31
+ );
30
32
 
31
33
  for (const entry of entries) {
32
34
  if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -1,35 +1,17 @@
1
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
- import { tmpdir } from "node:os";
1
+ import { beforeEach, describe, expect, test } from "bun:test";
2
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
4
3
  import { join } from "node:path";
4
+ import { setupTestDir } from "@omnidev-ai/core/test-utils";
5
5
  import { discoverCapabilities, loadCapability, loadCapabilityConfig } from "./loader";
6
6
 
7
7
  describe("discoverCapabilities", () => {
8
- let testDir: string;
8
+ const testDir = setupTestDir("test-capabilities-", { chdir: true });
9
9
  let capabilitiesDir: string;
10
- let originalCwd: string;
11
10
 
12
11
  beforeEach(() => {
13
- // Save current working directory
14
- originalCwd = process.cwd();
15
-
16
12
  // Create test directory in os temp dir
17
- testDir = mkdtempSync(join(tmpdir(), "test-capabilities-"));
18
- capabilitiesDir = join(testDir, "omni", "capabilities");
13
+ capabilitiesDir = join(testDir.path, "omni", "capabilities");
19
14
  mkdirSync(capabilitiesDir, { recursive: true });
20
-
21
- // Change to test directory
22
- process.chdir(testDir);
23
- });
24
-
25
- afterEach(() => {
26
- // Restore working directory
27
- process.chdir(originalCwd);
28
-
29
- // Cleanup
30
- if (existsSync(testDir)) {
31
- rmSync(testDir, { recursive: true, force: true });
32
- }
33
15
  });
34
16
 
35
17
  test("returns empty array when capabilities directory does not exist", async () => {
@@ -143,31 +125,13 @@ describe("discoverCapabilities", () => {
143
125
  });
144
126
 
145
127
  describe("loadCapabilityConfig", () => {
146
- let testDir: string;
128
+ const testDir = setupTestDir("test-capability-config-", { chdir: true });
147
129
  let capabilitiesDir: string;
148
- let originalCwd: string;
149
130
 
150
131
  beforeEach(() => {
151
- // Save current working directory
152
- originalCwd = process.cwd();
153
-
154
132
  // Create test directory in os temp dir
155
- testDir = mkdtempSync(join(tmpdir(), "test-capability-config-"));
156
- capabilitiesDir = join(testDir, ".omni", "capabilities");
133
+ capabilitiesDir = join(testDir.path, ".omni", "capabilities");
157
134
  mkdirSync(capabilitiesDir, { recursive: true });
158
-
159
- // Change to test directory
160
- process.chdir(testDir);
161
- });
162
-
163
- afterEach(() => {
164
- // Restore working directory
165
- process.chdir(originalCwd);
166
-
167
- // Cleanup
168
- if (existsSync(testDir)) {
169
- rmSync(testDir, { recursive: true, force: true });
170
- }
171
135
  });
172
136
 
173
137
  test("loads valid capability config with all required fields", async () => {
@@ -238,78 +202,6 @@ secret = true`,
238
202
  expect(config.env?.[0]?.secret).toBe(true);
239
203
  });
240
204
 
241
- test("loads capability config with optional mcp field", async () => {
242
- const capPath = join(".omni", "capabilities", "with-mcp");
243
- mkdirSync(capPath, { recursive: true });
244
- writeFileSync(
245
- join(capPath, "capability.toml"),
246
- `[capability]
247
- id = "with-mcp"
248
- name = "With MCP"
249
- version = "1.0.0"
250
- description = "Has MCP tools"
251
-
252
- [mcp]
253
- tools = ["test_tool"]`,
254
- );
255
-
256
- const config = await loadCapabilityConfig(capPath);
257
-
258
- expect(config.capability.id).toBe("with-mcp");
259
- expect(config.mcp?.tools).toEqual(["test_tool"]);
260
- });
261
-
262
- test("throws error for reserved capability name (fs)", async () => {
263
- const capPath = join(".omni", "capabilities", "fs");
264
- mkdirSync(capPath, { recursive: true });
265
- writeFileSync(
266
- join(capPath, "capability.toml"),
267
- `[capability]
268
- id = "fs"
269
- name = "File System"
270
- version = "1.0.0"
271
- description = "Reserved name"`,
272
- );
273
-
274
- expect(async () => await loadCapabilityConfig(capPath)).toThrow(
275
- 'Capability name "fs" is reserved. Choose a different name.',
276
- );
277
- });
278
-
279
- test("throws error for reserved capability name (react)", async () => {
280
- const capPath = join(".omni", "capabilities", "react-cap");
281
- mkdirSync(capPath, { recursive: true });
282
- writeFileSync(
283
- join(capPath, "capability.toml"),
284
- `[capability]
285
- id = "react"
286
- name = "React"
287
- version = "1.0.0"
288
- description = "Reserved name"`,
289
- );
290
-
291
- expect(async () => await loadCapabilityConfig(capPath)).toThrow(
292
- 'Capability name "react" is reserved. Choose a different name.',
293
- );
294
- });
295
-
296
- test("throws error for reserved capability name (typescript)", async () => {
297
- const capPath = join(".omni", "capabilities", "ts-cap");
298
- mkdirSync(capPath, { recursive: true });
299
- writeFileSync(
300
- join(capPath, "capability.toml"),
301
- `[capability]
302
- id = "typescript"
303
- name = "TypeScript"
304
- version = "1.0.0"
305
- description = "Reserved name"`,
306
- );
307
-
308
- expect(async () => await loadCapabilityConfig(capPath)).toThrow(
309
- 'Capability name "typescript" is reserved. Choose a different name.',
310
- );
311
- });
312
-
313
205
  test("throws error when capability.toml is missing", async () => {
314
206
  const capPath = join(".omni", "capabilities", "missing-config");
315
207
  mkdirSync(capPath, { recursive: true });
@@ -345,24 +237,6 @@ id = "bad-toml"
345
237
  expect(async () => await loadCapabilityConfig(capPath)).toThrow();
346
238
  });
347
239
 
348
- test("allows non-reserved capability names", async () => {
349
- const capPath = join(".omni", "capabilities", "my-custom-capability");
350
- mkdirSync(capPath, { recursive: true });
351
- writeFileSync(
352
- join(capPath, "capability.toml"),
353
- `[capability]
354
- id = "my-custom-capability"
355
- name = "My Custom Capability"
356
- version = "2.1.0"
357
- description = "A custom capability"`,
358
- );
359
-
360
- const config = await loadCapabilityConfig(capPath);
361
-
362
- expect(config.capability.id).toBe("my-custom-capability");
363
- expect(config.capability.name).toBe("My Custom Capability");
364
- });
365
-
366
240
  test("handles capability config with all optional fields defined", async () => {
367
241
  const capPath = join(".omni", "capabilities", "complete-cap");
368
242
  mkdirSync(capPath, { recursive: true });
@@ -388,9 +262,7 @@ key = "VAR2"
388
262
  description = "Variable 2"
389
263
  required = false
390
264
  secret = true
391
-
392
- [mcp]
393
- tools = ["tool1", "tool2"]`,
265
+ `,
394
266
  );
395
267
 
396
268
  const config = await loadCapabilityConfig(capPath);
@@ -398,36 +270,17 @@ tools = ["tool1", "tool2"]`,
398
270
  expect(config.capability.id).toBe("complete-cap");
399
271
  expect(config.exports?.functions).toEqual(["fn1", "fn2"]);
400
272
  expect(config.env).toHaveLength(2);
401
- expect(config.mcp?.tools).toEqual(["tool1", "tool2"]);
402
273
  });
403
274
  });
404
275
 
405
276
  describe("loadCapability", () => {
406
- let testDir: string;
277
+ const testDir = setupTestDir("test-load-capability-", { chdir: true });
407
278
  let capabilitiesDir: string;
408
- let originalCwd: string;
409
279
 
410
280
  beforeEach(() => {
411
- // Save current working directory
412
- originalCwd = process.cwd();
413
-
414
281
  // Create test directory in os temp dir
415
- testDir = mkdtempSync(join(tmpdir(), "test-load-capability-"));
416
- capabilitiesDir = join(testDir, "omni", "capabilities");
282
+ capabilitiesDir = join(testDir.path, "omni", "capabilities");
417
283
  mkdirSync(capabilitiesDir, { recursive: true });
418
-
419
- // Change to test directory
420
- process.chdir(testDir);
421
- });
422
-
423
- afterEach(() => {
424
- // Restore working directory
425
- process.chdir(originalCwd);
426
-
427
- // Cleanup
428
- if (existsSync(testDir)) {
429
- rmSync(testDir, { recursive: true, force: true });
430
- }
431
284
  });
432
285
 
433
286
  test("loads capability with minimal config (no optional fields)", async () => {
@@ -1,4 +1,4 @@
1
- import { existsSync, readdirSync, statSync } from "node:fs";
1
+ import { existsSync, readdirSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { validateEnv } from "../config/env";
4
4
  import { parseCapabilityConfig } from "../config/parser";
@@ -24,78 +24,24 @@ import { loadSkills } from "./skills";
24
24
  import { loadSubagents } from "./subagents";
25
25
 
26
26
  const CAPABILITIES_DIR = ".omni/capabilities";
27
- const BUILTIN_CAPABILITIES_DIR = "capabilities";
28
-
29
- /**
30
- * Reserved capability names that cannot be used.
31
- * These are common package names that might conflict with imports.
32
- */
33
- const RESERVED_NAMES = [
34
- "fs",
35
- "path",
36
- "http",
37
- "https",
38
- "crypto",
39
- "os",
40
- "child_process",
41
- "stream",
42
- "buffer",
43
- "util",
44
- "events",
45
- "net",
46
- "url",
47
- "querystring",
48
- "react",
49
- "vue",
50
- "lodash",
51
- "axios",
52
- "express",
53
- "typescript",
54
- ];
55
-
56
- /**
57
- * Check if a path is a directory (follows symlinks)
58
- */
59
- function isDirectoryOrSymlink(path: string): boolean {
60
- try {
61
- return statSync(path).isDirectory();
62
- } catch {
63
- return false;
64
- }
65
- }
66
27
 
67
28
  /**
68
29
  * Discovers capabilities by scanning the .omni/capabilities directory.
69
30
  * A directory is considered a capability if it contains a capability.toml file.
70
- * Follows symlinks to support linked capability directories.
71
31
  *
72
32
  * @returns Array of capability directory paths
73
33
  */
74
34
  export async function discoverCapabilities(): Promise<string[]> {
75
35
  const capabilities: string[] = [];
76
36
 
77
- // Discover built-in capabilities (from capabilities/ directory)
78
- if (existsSync(BUILTIN_CAPABILITIES_DIR)) {
79
- const entries = readdirSync(BUILTIN_CAPABILITIES_DIR, { withFileTypes: true });
80
-
81
- for (const entry of entries) {
82
- const entryPath = join(BUILTIN_CAPABILITIES_DIR, entry.name);
83
- if (entry.isDirectory() || (entry.isSymbolicLink() && isDirectoryOrSymlink(entryPath))) {
84
- const configPath = join(entryPath, "capability.toml");
85
- if (existsSync(configPath)) {
86
- capabilities.push(entryPath);
87
- }
88
- }
89
- }
90
- }
91
-
92
- // Discover project-specific capabilities (from .omni/capabilities/)
93
37
  if (existsSync(CAPABILITIES_DIR)) {
94
- const entries = readdirSync(CAPABILITIES_DIR, { withFileTypes: true });
38
+ const entries = readdirSync(CAPABILITIES_DIR, { withFileTypes: true }).sort((a, b) =>
39
+ a.name.localeCompare(b.name),
40
+ );
95
41
 
96
42
  for (const entry of entries) {
97
- const entryPath = join(CAPABILITIES_DIR, entry.name);
98
- if (entry.isDirectory() || (entry.isSymbolicLink() && isDirectoryOrSymlink(entryPath))) {
43
+ if (entry.isDirectory()) {
44
+ const entryPath = join(CAPABILITIES_DIR, entry.name);
99
45
  const configPath = join(entryPath, "capability.toml");
100
46
  if (existsSync(configPath)) {
101
47
  capabilities.push(entryPath);
@@ -109,24 +55,17 @@ export async function discoverCapabilities(): Promise<string[]> {
109
55
 
110
56
  /**
111
57
  * Loads and parses a capability configuration file.
112
- * Validates required fields and checks for reserved names.
58
+ * Validates required fields.
113
59
  *
114
60
  * @param capabilityPath - Path to the capability directory
115
61
  * @returns Parsed capability configuration
116
- * @throws Error if the config is invalid or uses a reserved name
62
+ * @throws Error if the config is invalid
117
63
  */
118
64
  export async function loadCapabilityConfig(capabilityPath: string): Promise<CapabilityConfig> {
119
65
  const configPath = join(capabilityPath, "capability.toml");
120
66
  const content = await Bun.file(configPath).text();
121
67
  const config = parseCapabilityConfig(content);
122
68
 
123
- // Validate name is not reserved
124
- if (RESERVED_NAMES.includes(config.capability.id)) {
125
- throw new Error(
126
- `Capability name "${config.capability.id}" is reserved. Choose a different name.`,
127
- );
128
- }
129
-
130
69
  return config;
131
70
  }
132
71