@omnidev-ai/core 0.1.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 (59) hide show
  1. package/package.json +31 -0
  2. package/src/capability/AGENTS.md +58 -0
  3. package/src/capability/commands.test.ts +414 -0
  4. package/src/capability/commands.ts +70 -0
  5. package/src/capability/docs.test.ts +199 -0
  6. package/src/capability/docs.ts +46 -0
  7. package/src/capability/index.ts +20 -0
  8. package/src/capability/loader.test.ts +815 -0
  9. package/src/capability/loader.ts +492 -0
  10. package/src/capability/registry.test.ts +473 -0
  11. package/src/capability/registry.ts +55 -0
  12. package/src/capability/rules.test.ts +145 -0
  13. package/src/capability/rules.ts +133 -0
  14. package/src/capability/skills.test.ts +316 -0
  15. package/src/capability/skills.ts +56 -0
  16. package/src/capability/sources.test.ts +338 -0
  17. package/src/capability/sources.ts +966 -0
  18. package/src/capability/subagents.test.ts +478 -0
  19. package/src/capability/subagents.ts +103 -0
  20. package/src/capability/yaml-parser.ts +81 -0
  21. package/src/config/AGENTS.md +46 -0
  22. package/src/config/capabilities.ts +82 -0
  23. package/src/config/env.test.ts +286 -0
  24. package/src/config/env.ts +96 -0
  25. package/src/config/index.ts +6 -0
  26. package/src/config/loader.test.ts +282 -0
  27. package/src/config/loader.ts +137 -0
  28. package/src/config/parser.test.ts +281 -0
  29. package/src/config/parser.ts +55 -0
  30. package/src/config/profiles.test.ts +259 -0
  31. package/src/config/profiles.ts +75 -0
  32. package/src/config/provider.test.ts +79 -0
  33. package/src/config/provider.ts +55 -0
  34. package/src/debug.ts +20 -0
  35. package/src/gitignore/manager.test.ts +219 -0
  36. package/src/gitignore/manager.ts +167 -0
  37. package/src/index.test.ts +26 -0
  38. package/src/index.ts +39 -0
  39. package/src/mcp-json/index.ts +1 -0
  40. package/src/mcp-json/manager.test.ts +415 -0
  41. package/src/mcp-json/manager.ts +118 -0
  42. package/src/state/active-profile.test.ts +131 -0
  43. package/src/state/active-profile.ts +41 -0
  44. package/src/state/index.ts +2 -0
  45. package/src/state/manifest.test.ts +548 -0
  46. package/src/state/manifest.ts +164 -0
  47. package/src/sync.ts +213 -0
  48. package/src/templates/agents.test.ts +23 -0
  49. package/src/templates/agents.ts +14 -0
  50. package/src/templates/claude.test.ts +48 -0
  51. package/src/templates/claude.ts +122 -0
  52. package/src/test-utils/helpers.test.ts +196 -0
  53. package/src/test-utils/helpers.ts +187 -0
  54. package/src/test-utils/index.ts +30 -0
  55. package/src/test-utils/mocks.test.ts +83 -0
  56. package/src/test-utils/mocks.ts +101 -0
  57. package/src/types/capability-export.ts +234 -0
  58. package/src/types/index.test.ts +28 -0
  59. package/src/types/index.ts +270 -0
@@ -0,0 +1,338 @@
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";
4
+ import { join } from "node:path";
5
+ import {
6
+ parseSourceConfig,
7
+ sourceToGitUrl,
8
+ getSourceCapabilityPath,
9
+ getLockFilePath,
10
+ loadLockFile,
11
+ saveLockFile,
12
+ } from "./sources";
13
+ import type { CapabilitiesLockFile, GitCapabilitySourceConfig } from "../types/index.js";
14
+
15
+ describe("parseSourceConfig", () => {
16
+ test("parses simple github shorthand", () => {
17
+ const config = parseSourceConfig("github:user/repo") as GitCapabilitySourceConfig;
18
+
19
+ expect(config.source).toBe("github:user/repo");
20
+ expect(config.ref).toBeUndefined();
21
+ });
22
+
23
+ test("parses github shorthand with ref", () => {
24
+ const config = parseSourceConfig("github:user/repo#v1.0.0") as GitCapabilitySourceConfig;
25
+
26
+ expect(config.source).toBe("github:user/repo");
27
+ expect(config.ref).toBe("v1.0.0");
28
+ });
29
+
30
+ test("parses github shorthand with branch ref", () => {
31
+ const config = parseSourceConfig("github:user/repo#main") as GitCapabilitySourceConfig;
32
+
33
+ expect(config.source).toBe("github:user/repo");
34
+ expect(config.ref).toBe("main");
35
+ });
36
+
37
+ test("parses github shorthand with commit ref", () => {
38
+ const config = parseSourceConfig("github:user/repo#abc123def") as GitCapabilitySourceConfig;
39
+
40
+ expect(config.source).toBe("github:user/repo");
41
+ expect(config.ref).toBe("abc123def");
42
+ });
43
+
44
+ test("parses git ssh URL", () => {
45
+ const config = parseSourceConfig("git@github.com:user/repo.git") as GitCapabilitySourceConfig;
46
+
47
+ expect(config.source).toBe("git@github.com:user/repo.git");
48
+ expect(config.ref).toBeUndefined();
49
+ });
50
+
51
+ test("parses https git URL", () => {
52
+ const config = parseSourceConfig(
53
+ "https://github.com/user/repo.git",
54
+ ) as GitCapabilitySourceConfig;
55
+
56
+ expect(config.source).toBe("https://github.com/user/repo.git");
57
+ expect(config.ref).toBeUndefined();
58
+ });
59
+
60
+ test("passes through full config object", () => {
61
+ const config = parseSourceConfig({
62
+ source: "github:user/repo",
63
+ ref: "v2.0.0",
64
+ type: "wrap",
65
+ }) as GitCapabilitySourceConfig;
66
+
67
+ expect(config.source).toBe("github:user/repo");
68
+ expect(config.ref).toBe("v2.0.0");
69
+ expect(config.type).toBe("wrap");
70
+ });
71
+
72
+ test("passes through config with path", () => {
73
+ const config = parseSourceConfig({
74
+ source: "github:user/monorepo",
75
+ path: "packages/my-cap",
76
+ }) as GitCapabilitySourceConfig;
77
+
78
+ expect(config.source).toBe("github:user/monorepo");
79
+ expect(config.path).toBe("packages/my-cap");
80
+ });
81
+ });
82
+
83
+ describe("sourceToGitUrl", () => {
84
+ test("converts github shorthand to https URL", () => {
85
+ const url = sourceToGitUrl("github:user/repo");
86
+
87
+ expect(url).toBe("https://github.com/user/repo.git");
88
+ });
89
+
90
+ test("converts github shorthand with org to https URL", () => {
91
+ const url = sourceToGitUrl("github:my-org/my-repo");
92
+
93
+ expect(url).toBe("https://github.com/my-org/my-repo.git");
94
+ });
95
+
96
+ test("passes through https URL unchanged", () => {
97
+ const url = sourceToGitUrl("https://github.com/user/repo.git");
98
+
99
+ expect(url).toBe("https://github.com/user/repo.git");
100
+ });
101
+
102
+ test("passes through ssh URL unchanged", () => {
103
+ const url = sourceToGitUrl("git@github.com:user/repo.git");
104
+
105
+ expect(url).toBe("git@github.com:user/repo.git");
106
+ });
107
+
108
+ test("passes through gitlab https URL unchanged", () => {
109
+ const url = sourceToGitUrl("https://gitlab.com/user/repo.git");
110
+
111
+ expect(url).toBe("https://gitlab.com/user/repo.git");
112
+ });
113
+ });
114
+
115
+ describe("getSourceCapabilityPath", () => {
116
+ test("returns correct path for capability id", () => {
117
+ const path = getSourceCapabilityPath("my-cap");
118
+
119
+ expect(path).toBe(".omni/capabilities/my-cap");
120
+ });
121
+
122
+ test("handles capability id with hyphens", () => {
123
+ const path = getSourceCapabilityPath("my-awesome-capability");
124
+
125
+ expect(path).toBe(".omni/capabilities/my-awesome-capability");
126
+ });
127
+ });
128
+
129
+ describe("getLockFilePath", () => {
130
+ test("returns correct lock file path", () => {
131
+ const path = getLockFilePath();
132
+
133
+ expect(path).toBe("omni.lock.toml");
134
+ });
135
+ });
136
+
137
+ describe("loadLockFile", () => {
138
+ let testDir: string;
139
+ let originalCwd: string;
140
+
141
+ beforeEach(() => {
142
+ originalCwd = process.cwd();
143
+ testDir = mkdtempSync(join(tmpdir(), "test-lock-file-"));
144
+ mkdirSync(join(testDir, ".omni"), { recursive: true });
145
+ process.chdir(testDir);
146
+ });
147
+
148
+ afterEach(() => {
149
+ process.chdir(originalCwd);
150
+ if (existsSync(testDir)) {
151
+ rmSync(testDir, { recursive: true, force: true });
152
+ }
153
+ });
154
+
155
+ test("returns empty capabilities when lock file does not exist", async () => {
156
+ const lockFile = await loadLockFile();
157
+
158
+ expect(lockFile.capabilities).toEqual({});
159
+ });
160
+
161
+ test("loads lock file with single capability", async () => {
162
+ writeFileSync(
163
+ "omni.lock.toml",
164
+ `[capabilities.my-cap]
165
+ source = "github:user/repo"
166
+ version = "1.0.0"
167
+ commit = "abc123def456"
168
+ updated_at = "2026-01-01T00:00:00Z"
169
+ `,
170
+ );
171
+
172
+ const lockFile = await loadLockFile();
173
+
174
+ expect(lockFile.capabilities["my-cap"]).toBeDefined();
175
+ expect(lockFile.capabilities["my-cap"]?.source).toBe("github:user/repo");
176
+ expect(lockFile.capabilities["my-cap"]?.version).toBe("1.0.0");
177
+ expect(lockFile.capabilities["my-cap"]?.commit).toBe("abc123def456");
178
+ });
179
+
180
+ test("loads lock file with multiple capabilities", async () => {
181
+ writeFileSync(
182
+ "omni.lock.toml",
183
+ `[capabilities.cap1]
184
+ source = "github:user/repo1"
185
+ version = "1.0.0"
186
+ commit = "abc123"
187
+ updated_at = "2026-01-01T00:00:00Z"
188
+
189
+ [capabilities.cap2]
190
+ source = "github:user/repo2"
191
+ version = "2.0.0"
192
+ commit = "def456"
193
+ ref = "v2.0.0"
194
+ updated_at = "2026-01-02T00:00:00Z"
195
+ `,
196
+ );
197
+
198
+ const lockFile = await loadLockFile();
199
+
200
+ expect(Object.keys(lockFile.capabilities)).toHaveLength(2);
201
+ expect(lockFile.capabilities["cap1"]?.version).toBe("1.0.0");
202
+ expect(lockFile.capabilities["cap2"]?.version).toBe("2.0.0");
203
+ expect(lockFile.capabilities["cap2"]?.ref).toBe("v2.0.0");
204
+ });
205
+
206
+ test("returns empty capabilities for invalid TOML", async () => {
207
+ writeFileSync("omni.lock.toml", "invalid [[[toml");
208
+
209
+ const lockFile = await loadLockFile();
210
+
211
+ expect(lockFile.capabilities).toEqual({});
212
+ });
213
+ });
214
+
215
+ describe("saveLockFile", () => {
216
+ let testDir: string;
217
+ let originalCwd: string;
218
+
219
+ beforeEach(() => {
220
+ originalCwd = process.cwd();
221
+ testDir = mkdtempSync(join(tmpdir(), "test-save-lock-"));
222
+ mkdirSync(join(testDir, ".omni"), { recursive: true });
223
+ process.chdir(testDir);
224
+ });
225
+
226
+ afterEach(() => {
227
+ process.chdir(originalCwd);
228
+ if (existsSync(testDir)) {
229
+ rmSync(testDir, { recursive: true, force: true });
230
+ }
231
+ });
232
+
233
+ test("creates lock file with single capability", async () => {
234
+ const lockFile: CapabilitiesLockFile = {
235
+ capabilities: {
236
+ "my-cap": {
237
+ source: "github:user/repo",
238
+ version: "1.0.0",
239
+ commit: "abc123",
240
+ updated_at: "2026-01-01T00:00:00Z",
241
+ },
242
+ },
243
+ };
244
+
245
+ await saveLockFile(lockFile);
246
+
247
+ expect(existsSync("omni.lock.toml")).toBe(true);
248
+
249
+ const content = await Bun.file("omni.lock.toml").text();
250
+ expect(content).toContain("[capabilities.my-cap]");
251
+ expect(content).toContain('source = "github:user/repo"');
252
+ expect(content).toContain('version = "1.0.0"');
253
+ expect(content).toContain('commit = "abc123"');
254
+ });
255
+
256
+ test("creates lock file with ref when present", async () => {
257
+ const lockFile: CapabilitiesLockFile = {
258
+ capabilities: {
259
+ "pinned-cap": {
260
+ source: "github:user/repo",
261
+ version: "2.0.0",
262
+ commit: "def456",
263
+ ref: "v2.0.0",
264
+ updated_at: "2026-01-01T00:00:00Z",
265
+ },
266
+ },
267
+ };
268
+
269
+ await saveLockFile(lockFile);
270
+
271
+ const content = await Bun.file("omni.lock.toml").text();
272
+ expect(content).toContain('ref = "v2.0.0"');
273
+ });
274
+
275
+ test("creates capabilities directory if it does not exist", async () => {
276
+ rmSync(".omni/capabilities", { recursive: true, force: true });
277
+
278
+ const lockFile: CapabilitiesLockFile = {
279
+ capabilities: {
280
+ test: {
281
+ source: "github:user/repo",
282
+ version: "1.0.0",
283
+ updated_at: "2026-01-01T00:00:00Z",
284
+ },
285
+ },
286
+ };
287
+
288
+ await saveLockFile(lockFile);
289
+
290
+ expect(existsSync(".omni/capabilities")).toBe(true);
291
+ expect(existsSync("omni.lock.toml")).toBe(true);
292
+ });
293
+
294
+ test("includes header comment in lock file", async () => {
295
+ const lockFile: CapabilitiesLockFile = {
296
+ capabilities: {},
297
+ };
298
+
299
+ await saveLockFile(lockFile);
300
+
301
+ const content = await Bun.file("omni.lock.toml").text();
302
+ expect(content).toContain("# Auto-generated by OmniDev");
303
+ expect(content).toContain("DO NOT EDIT");
304
+ });
305
+
306
+ test("roundtrip: save and load produces same data", async () => {
307
+ const original: CapabilitiesLockFile = {
308
+ capabilities: {
309
+ cap1: {
310
+ source: "github:user/repo1",
311
+ version: "1.0.0",
312
+ commit: "abc123",
313
+ updated_at: "2026-01-01T00:00:00Z",
314
+ },
315
+ cap2: {
316
+ source: "github:user/repo2",
317
+ version: "2.0.0",
318
+ commit: "def456",
319
+ ref: "v2.0.0",
320
+ updated_at: "2026-01-02T00:00:00Z",
321
+ },
322
+ },
323
+ };
324
+
325
+ await saveLockFile(original);
326
+ const loaded = await loadLockFile();
327
+
328
+ expect(loaded.capabilities["cap1"]?.source).toBe(original.capabilities["cap1"]?.source);
329
+ expect(loaded.capabilities["cap1"]?.version).toBe(original.capabilities["cap1"]?.version);
330
+ expect(loaded.capabilities["cap1"]?.commit).toBe(original.capabilities["cap1"]?.commit);
331
+ expect(loaded.capabilities["cap2"]?.ref).toBe(original.capabilities["cap2"]?.ref);
332
+ });
333
+ });
334
+
335
+ // Note: Tests for fetchCapabilitySource and fetchAllCapabilitySources require
336
+ // network access and git operations. These should be tested via integration tests
337
+ // or with mocked git commands. The manual test in /tmp/omni-test verified the
338
+ // full flow works correctly with the real obsidian-skills repository.