@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
@@ -26,6 +26,11 @@ function mergeConfigs(base: OmniConfig, override: OmniConfig): OmniConfig {
26
26
  };
27
27
  }
28
28
 
29
+ // Deep merge mcps (only if at least one config has it)
30
+ if (base.mcps || override.mcps) {
31
+ merged.mcps = { ...base.mcps, ...override.mcps };
32
+ }
33
+
29
34
  return merged;
30
35
  }
31
36
 
@@ -70,65 +75,130 @@ export async function writeConfig(config: OmniConfig): Promise<void> {
70
75
  function generateConfigToml(config: OmniConfig): string {
71
76
  const lines: string[] = [];
72
77
 
78
+ lines.push("# =============================================================================");
73
79
  lines.push("# OmniDev Configuration");
74
- lines.push("# Main configuration for your OmniDev project");
80
+ lines.push("# =============================================================================");
81
+ lines.push("# This file defines your project's capabilities, profiles, and settings.");
82
+ lines.push("#");
83
+ lines.push("# Files:");
84
+ lines.push("# • omni.toml - Main config (commit to share with team)");
85
+ lines.push("# • omni.local.toml - Local overrides (add to .gitignore)");
86
+ lines.push("# • omni.lock.toml - Version lock file (commit for reproducibility)");
87
+ lines.push("#");
88
+ lines.push("# Quick start:");
89
+ lines.push("# 1. Add capability sources to [capabilities.sources]");
90
+ lines.push("# 2. Reference them in your profiles");
91
+ lines.push("# 3. Run: omnidev sync");
92
+ lines.push("# 4. Switch profiles: omnidev profile use <name>");
75
93
  lines.push("");
76
94
 
77
95
  // Project name
78
96
  if (config.project) {
79
97
  lines.push(`project = "${config.project}"`);
98
+ lines.push("");
80
99
  }
81
100
 
82
101
  // Note: active_profile is stored in .omni/state/active-profile, not in config.toml
83
102
  // We still read it from config.toml for backwards compatibility, but don't write it here
84
103
 
85
- // Sandbox mode
86
- if (config.sandbox_enabled !== undefined) {
87
- lines.push(`sandbox_enabled = ${config.sandbox_enabled}`);
88
- }
89
-
90
- lines.push("");
91
-
92
104
  // Providers
93
105
  if (config.providers?.enabled && config.providers.enabled.length > 0) {
106
+ lines.push("# AI providers to enable (claude, codex, or both)");
94
107
  lines.push("[providers]");
95
108
  lines.push(`enabled = [${config.providers.enabled.map((p) => `"${p}"`).join(", ")}]`);
96
109
  lines.push("");
97
110
  }
98
111
 
99
112
  // Environment variables
113
+ lines.push("# =============================================================================");
114
+ lines.push("# Environment Variables");
115
+ lines.push("# =============================================================================");
116
+ lines.push("# Global environment variables available to all capabilities.");
117
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: Example of env var syntax
118
+ lines.push("# Use ${VAR_NAME} syntax to reference shell environment variables.");
119
+ lines.push("#");
100
120
  if (config.env && Object.keys(config.env).length > 0) {
101
121
  lines.push("[env]");
102
122
  for (const [key, value] of Object.entries(config.env)) {
103
123
  lines.push(`${key} = "${value}"`);
104
124
  }
105
- lines.push("");
125
+ } else {
126
+ lines.push("# [env]");
127
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: Example of env var syntax
128
+ lines.push('# DATABASE_URL = "${DATABASE_URL}"');
129
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: Example of env var syntax
130
+ lines.push('# API_KEY = "${MY_API_KEY}"');
106
131
  }
132
+ lines.push("");
107
133
 
108
134
  // Capability sources (commented examples)
109
135
  lines.push("# =============================================================================");
110
136
  lines.push("# Capability Sources");
111
137
  lines.push("# =============================================================================");
112
- lines.push("# Fetch capabilities from Git repositories. On sync, these are cloned/updated");
113
- lines.push("# and wrapped into capabilities automatically.");
138
+ lines.push("# Fetch capabilities from Git repositories. On sync, these are");
139
+ lines.push("# cloned/updated and made available to your profiles.");
114
140
  lines.push("#");
115
141
  lines.push("# [capabilities.sources]");
116
- lines.push("# # Simple GitHub reference (auto-wrapped if no capability.toml)");
117
- lines.push('# obsidian = "github:kepano/obsidian-skills"');
142
+ lines.push("# # GitHub shorthand (uses latest commit)");
143
+ lines.push('# tasks = "github:example-org/tasks-capability"');
144
+ lines.push("#");
145
+ lines.push("# # Version pinning (recommended for production)");
146
+ lines.push('# ralph = { source = "github:example-org/ralph", ref = "v1.2.0" }');
147
+ lines.push("#");
148
+ lines.push("# # Other Git sources");
149
+ lines.push('# private = "git@github.com:company/private-cap.git"');
150
+ lines.push('# gitlab = "https://gitlab.com/user/capability.git"');
151
+ lines.push("");
152
+
153
+ // MCP servers (commented examples)
154
+ lines.push("# =============================================================================");
155
+ lines.push("# MCP Servers");
156
+ lines.push("# =============================================================================");
157
+ lines.push("# Define MCP servers that automatically become capabilities.");
158
+ lines.push(
159
+ '# Reference in profiles using the MCP name directly, e.g. capabilities = ["filesystem"]',
160
+ );
118
161
  lines.push("#");
119
- lines.push("# # Full configuration with version pinning");
120
- lines.push('# my-cap = { source = "github:user/repo", ref = "v1.0.0" }');
162
+ lines.push("# [mcps.filesystem]");
163
+ lines.push('# command = "npx"');
164
+ lines.push('# args = ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"]');
165
+ lines.push('# transport = "stdio" # stdio (default), sse, or http');
121
166
  lines.push("#");
122
- lines.push("# # Force wrap mode (generate capability.toml from discovered content)");
123
- lines.push('# external = { source = "github:user/skills-repo", type = "wrap" }');
167
+ lines.push("# [mcps.database]");
168
+ lines.push('# command = "node"');
169
+ lines.push('# args = ["./servers/database.js"]');
170
+ lines.push('# cwd = "./mcp-servers"');
171
+ lines.push("# [mcps.database.env]");
172
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: Example of env var syntax
173
+ lines.push('# DB_URL = "${DATABASE_URL}"');
174
+ lines.push("");
175
+
176
+ // Always enabled capabilities
177
+ lines.push("# =============================================================================");
178
+ lines.push("# Always Enabled Capabilities");
179
+ lines.push("# =============================================================================");
180
+ lines.push("# Capabilities that load in ALL profiles, regardless of profile config.");
181
+ lines.push("# Useful for essential tools needed everywhere.");
182
+ lines.push("#");
183
+ lines.push('# always_enabled_capabilities = ["git-tools", "linting"]');
124
184
  lines.push("");
125
185
 
126
186
  // Profiles
187
+ lines.push("# =============================================================================");
188
+ lines.push("# Profiles");
189
+ lines.push("# =============================================================================");
190
+ lines.push("# Define different capability sets for different workflows.");
191
+ lines.push("# Switch profiles with: omnidev profile use <name>");
192
+ lines.push("");
127
193
  if (config.profiles && Object.keys(config.profiles).length > 0) {
128
194
  for (const [name, profile] of Object.entries(config.profiles)) {
129
195
  lines.push(`[profiles.${name}]`);
130
196
  const capabilities = profile.capabilities ?? [];
131
- lines.push(`capabilities = [${capabilities.map((id) => `"${id}"`).join(", ")}]`);
197
+ if (capabilities.length > 0) {
198
+ lines.push(`capabilities = [${capabilities.map((id) => `"${id}"`).join(", ")}]`);
199
+ } else {
200
+ lines.push("capabilities = []");
201
+ }
132
202
  lines.push("");
133
203
  }
134
204
  }
@@ -183,31 +183,6 @@ default = "https://api.example.com"
183
183
  });
184
184
  });
185
185
 
186
- test("parses capability with MCP config", () => {
187
- const toml = `
188
- [capability]
189
- id = "custom"
190
- name = "Custom Capability"
191
- version = "1.0.0"
192
- description = "Custom MCP"
193
-
194
- [mcp]
195
- command = "node"
196
- args = ["server.js"]
197
- transport = "stdio"
198
-
199
- [mcp.env]
200
- PORT = "3000"
201
- `;
202
-
203
- const config = parseCapabilityConfig(toml);
204
-
205
- expect(config.mcp?.command).toBe("node");
206
- expect(config.mcp?.args).toEqual(["server.js"]);
207
- expect(config.mcp?.transport).toBe("stdio");
208
- expect(config.mcp?.env?.PORT).toBe("3000");
209
- });
210
-
211
186
  test("throws error when capability.id is missing", () => {
212
187
  const toml = `
213
188
  [capability]
@@ -1,30 +1,12 @@
1
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
1
+ import { describe, expect, test } from "bun:test";
2
+ import { mkdirSync, writeFileSync } from "node:fs";
3
+ import { setupTestDir } from "@omnidev-ai/core/test-utils";
3
4
  import { readActiveProfileState } from "../state/active-profile.js";
4
5
  import type { OmniConfig } from "../types/index.js";
5
6
  import { getActiveProfile, resolveEnabledCapabilities, setActiveProfile } from "./profiles.js";
6
7
 
7
8
  describe("getActiveProfile", () => {
8
- const TEST_DIR = ".omni-test-profiles";
9
- let originalCwd: string;
10
-
11
- beforeEach(() => {
12
- if (!existsSync(TEST_DIR)) {
13
- mkdirSync(TEST_DIR, { recursive: true });
14
- }
15
- originalCwd = process.cwd();
16
- process.chdir(TEST_DIR);
17
- if (!existsSync(".omni")) {
18
- mkdirSync(".omni", { recursive: true });
19
- }
20
- });
21
-
22
- afterEach(() => {
23
- process.chdir(originalCwd);
24
- if (existsSync(TEST_DIR)) {
25
- rmSync(TEST_DIR, { recursive: true, force: true });
26
- }
27
- });
9
+ setupTestDir("profiles-test-", { chdir: true, createOmniDir: true });
28
10
 
29
11
  test("returns null when no state file or config exists", async () => {
30
12
  const profile = await getActiveProfile();
@@ -60,26 +42,7 @@ describe("getActiveProfile", () => {
60
42
  });
61
43
 
62
44
  describe("setActiveProfile", () => {
63
- const TEST_DIR = ".omni-test-profiles-set";
64
- let originalCwd: string;
65
-
66
- beforeEach(() => {
67
- if (!existsSync(TEST_DIR)) {
68
- mkdirSync(TEST_DIR, { recursive: true });
69
- }
70
- originalCwd = process.cwd();
71
- process.chdir(TEST_DIR);
72
- if (!existsSync(".omni")) {
73
- mkdirSync(".omni", { recursive: true });
74
- }
75
- });
76
-
77
- afterEach(() => {
78
- process.chdir(originalCwd);
79
- if (existsSync(TEST_DIR)) {
80
- rmSync(TEST_DIR, { recursive: true, force: true });
81
- }
82
- });
45
+ setupTestDir("profiles-set-test-", { chdir: true, createOmniDir: true });
83
46
 
84
47
  test("sets active_profile in state file", async () => {
85
48
  await setActiveProfile("staging");
@@ -1,21 +1,8 @@
1
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import { existsSync, mkdirSync, rmSync } from "node:fs";
1
+ import { describe, expect, test } from "bun:test";
2
+ import { setupTestDir } from "@omnidev-ai/core/test-utils";
3
3
  import { parseProviderFlag } from "./provider.js";
4
4
 
5
- const TEST_DIR = ".test-omni";
6
-
7
- beforeEach(() => {
8
- if (existsSync(TEST_DIR)) {
9
- rmSync(TEST_DIR, { recursive: true });
10
- }
11
- mkdirSync(TEST_DIR, { recursive: true });
12
- });
13
-
14
- afterEach(() => {
15
- if (existsSync(TEST_DIR)) {
16
- rmSync(TEST_DIR, { recursive: true });
17
- }
18
- });
5
+ const testDir = setupTestDir("provider-test-", { chdir: true });
19
6
 
20
7
  describe("parseProviderFlag", () => {
21
8
  test("parses 'claude' flag", () => {
@@ -43,7 +30,7 @@ describe("parseProviderFlag", () => {
43
30
 
44
31
  describe("writeProviderConfig", () => {
45
32
  test("writes single provider config", async () => {
46
- const testPath = `${TEST_DIR}/provider.toml`;
33
+ const testPath = `${testDir.path}/provider.toml`;
47
34
 
48
35
  // Manually write for testing
49
36
  const lines: string[] = [];
@@ -61,7 +48,7 @@ describe("writeProviderConfig", () => {
61
48
  });
62
49
 
63
50
  test("writes multiple providers config", async () => {
64
- const testPath = `${TEST_DIR}/provider.toml`;
51
+ const testPath = `${testDir.path}/provider.toml`;
65
52
 
66
53
  const lines: string[] = [];
67
54
  lines.push("# OmniDev Provider Configuration");
package/src/index.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * @omnidev-ai/core - Core functionality for OmniDev
3
3
  *
4
4
  * This package contains shared types, utilities, and core logic
5
- * used across the CLI and MCP server packages.
5
+ * used across the CLI and capability tooling packages.
6
6
  */
7
7
 
8
8
  // Re-export @stricli/core for capabilities to use
@@ -20,8 +20,6 @@ export * from "./capability";
20
20
 
21
21
  // Export config functionality
22
22
  export * from "./config";
23
- // Export gitignore management
24
- export * from "./gitignore/manager";
25
23
  // Export MCP JSON management
26
24
  export * from "./mcp-json";
27
25
  // Export state management