@soleri/forge 5.14.10 → 7.0.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 (65) hide show
  1. package/dist/agent-schema.d.ts +323 -0
  2. package/dist/agent-schema.js +151 -0
  3. package/dist/agent-schema.js.map +1 -0
  4. package/dist/compose-claude-md.d.ts +24 -0
  5. package/dist/compose-claude-md.js +197 -0
  6. package/dist/compose-claude-md.js.map +1 -0
  7. package/dist/index.js +0 -0
  8. package/dist/lib.d.ts +12 -1
  9. package/dist/lib.js +10 -1
  10. package/dist/lib.js.map +1 -1
  11. package/dist/scaffold-filetree.d.ts +22 -0
  12. package/dist/scaffold-filetree.js +349 -0
  13. package/dist/scaffold-filetree.js.map +1 -0
  14. package/dist/scaffolder.js +261 -11
  15. package/dist/scaffolder.js.map +1 -1
  16. package/dist/templates/activate.js +39 -1
  17. package/dist/templates/activate.js.map +1 -1
  18. package/dist/templates/agents-md.d.ts +10 -1
  19. package/dist/templates/agents-md.js +76 -16
  20. package/dist/templates/agents-md.js.map +1 -1
  21. package/dist/templates/claude-md-template.js +9 -1
  22. package/dist/templates/claude-md-template.js.map +1 -1
  23. package/dist/templates/entry-point.js +83 -6
  24. package/dist/templates/entry-point.js.map +1 -1
  25. package/dist/templates/inject-claude-md.js +53 -0
  26. package/dist/templates/inject-claude-md.js.map +1 -1
  27. package/dist/templates/package-json.js +4 -1
  28. package/dist/templates/package-json.js.map +1 -1
  29. package/dist/templates/readme.js +4 -3
  30. package/dist/templates/readme.js.map +1 -1
  31. package/dist/templates/setup-script.js +109 -3
  32. package/dist/templates/setup-script.js.map +1 -1
  33. package/dist/templates/shared-rules.js +54 -17
  34. package/dist/templates/shared-rules.js.map +1 -1
  35. package/dist/templates/test-facades.js +151 -6
  36. package/dist/templates/test-facades.js.map +1 -1
  37. package/dist/types.d.ts +75 -10
  38. package/dist/types.js +40 -2
  39. package/dist/types.js.map +1 -1
  40. package/dist/utils/detect-domain-packs.d.ts +25 -0
  41. package/dist/utils/detect-domain-packs.js +104 -0
  42. package/dist/utils/detect-domain-packs.js.map +1 -0
  43. package/package.json +2 -1
  44. package/src/__tests__/detect-domain-packs.test.ts +178 -0
  45. package/src/__tests__/scaffold-filetree.test.ts +243 -0
  46. package/src/__tests__/scaffolder.test.ts +5 -3
  47. package/src/agent-schema.ts +184 -0
  48. package/src/compose-claude-md.ts +252 -0
  49. package/src/lib.ts +14 -1
  50. package/src/scaffold-filetree.ts +409 -0
  51. package/src/scaffolder.ts +299 -15
  52. package/src/templates/activate.ts +39 -0
  53. package/src/templates/agents-md.ts +78 -16
  54. package/src/templates/claude-md-template.ts +12 -1
  55. package/src/templates/entry-point.ts +90 -6
  56. package/src/templates/inject-claude-md.ts +53 -0
  57. package/src/templates/package-json.ts +4 -1
  58. package/src/templates/readme.ts +4 -3
  59. package/src/templates/setup-script.ts +110 -4
  60. package/src/templates/shared-rules.ts +55 -17
  61. package/src/templates/test-facades.ts +156 -6
  62. package/src/types.ts +45 -2
  63. package/src/utils/detect-domain-packs.ts +129 -0
  64. package/tsconfig.json +0 -1
  65. package/vitest.config.ts +1 -2
package/dist/types.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import { z } from 'zod';
2
2
  /** Where to scaffold host/client integration setup. */
3
- export declare const SETUP_TARGETS: readonly ["claude", "codex", "both"];
3
+ export declare const SETUP_TARGETS: readonly ["claude", "codex", "opencode", "both", "all"];
4
4
  export type SetupTarget = (typeof SETUP_TARGETS)[number];
5
+ /** Available model presets for agent configuration */
6
+ export declare const MODEL_PRESETS: readonly ["claude-code-sonnet-4", "claude-code-opus-4", "claude-code-3.7-sonnet", "claude-code-3.5-haiku", "claude-4-sonnet", "claude-4-opus", "gpt-4.1", "gpt-4.1-mini", "gemini-2.5", "gemini-2.5-flash"];
5
7
  /** Agent configuration — everything needed to scaffold */
6
8
  export declare const AgentConfigSchema: z.ZodObject<{
7
9
  /** Agent identifier (kebab-case, used for directory and package name) */
@@ -26,38 +28,101 @@ export declare const AgentConfigSchema: z.ZodObject<{
26
28
  hookPacks: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
27
29
  /** Skills to include (if omitted, all skills are included for backward compat) */
28
30
  skills: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
31
+ /** Primary model for the coder agent */
32
+ model: z.ZodDefault<z.ZodOptional<z.ZodString>>;
29
33
  /** AI client setup target: Claude Code, Codex, or both */
30
- setupTarget: z.ZodDefault<z.ZodOptional<z.ZodEnum<["claude", "codex", "both"]>>>;
34
+ setupTarget: z.ZodDefault<z.ZodOptional<z.ZodEnum<["claude", "codex", "opencode", "both", "all"]>>>;
31
35
  /** Enable Telegram transport scaffolding. Default: false. */
32
36
  telegram: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
37
+ /** Enable Cognee vector search integration. Default: false. */
38
+ cognee: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
39
+ /** Domain packs — npm packages with custom ops, knowledge, rules, and skills. */
40
+ domainPacks: z.ZodOptional<z.ZodArray<z.ZodObject<{
41
+ name: z.ZodString;
42
+ package: z.ZodString;
43
+ version: z.ZodOptional<z.ZodString>;
44
+ }, "strip", z.ZodTypeAny, {
45
+ name: string;
46
+ package: string;
47
+ version?: string | undefined;
48
+ }, {
49
+ name: string;
50
+ package: string;
51
+ version?: string | undefined;
52
+ }>, "many">>;
53
+ /** Vault connections — link to existing vaults instead of importing knowledge. */
54
+ vaults: z.ZodOptional<z.ZodArray<z.ZodObject<{
55
+ /** Display name for this vault connection */
56
+ name: z.ZodString;
57
+ /** Absolute path to the vault SQLite database */
58
+ path: z.ZodString;
59
+ /** Search priority (0-1). Higher = results ranked higher. Default: 0.5 */
60
+ priority: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
61
+ }, "strip", z.ZodTypeAny, {
62
+ name: string;
63
+ path: string;
64
+ priority: number;
65
+ }, {
66
+ name: string;
67
+ path: string;
68
+ priority?: number | undefined;
69
+ }>, "many">>;
70
+ /** @deprecated Use vaults[] instead. Shorthand for a single shared vault at priority 0.6. */
71
+ sharedVaultPath: z.ZodOptional<z.ZodString>;
33
72
  }, "strip", z.ZodTypeAny, {
34
- id: string;
35
73
  name: string;
36
- role: string;
74
+ cognee: boolean;
75
+ model: string;
76
+ id: string;
37
77
  description: string;
78
+ role: string;
38
79
  domains: string[];
39
80
  principles: string[];
40
81
  tone: "precise" | "mentor" | "pragmatic";
41
82
  outputDir: string;
42
- setupTarget: "claude" | "codex" | "both";
83
+ setupTarget: "claude" | "codex" | "opencode" | "both" | "all";
43
84
  telegram: boolean;
44
85
  greeting?: string | undefined;
45
- hookPacks?: string[] | undefined;
86
+ vaults?: {
87
+ name: string;
88
+ path: string;
89
+ priority: number;
90
+ }[] | undefined;
46
91
  skills?: string[] | undefined;
92
+ hookPacks?: string[] | undefined;
93
+ domainPacks?: {
94
+ name: string;
95
+ package: string;
96
+ version?: string | undefined;
97
+ }[] | undefined;
98
+ sharedVaultPath?: string | undefined;
47
99
  }, {
48
- id: string;
49
100
  name: string;
50
- role: string;
101
+ id: string;
51
102
  description: string;
103
+ role: string;
52
104
  domains: string[];
53
105
  principles: string[];
106
+ cognee?: boolean | undefined;
107
+ model?: string | undefined;
54
108
  tone?: "precise" | "mentor" | "pragmatic" | undefined;
55
109
  greeting?: string | undefined;
110
+ vaults?: {
111
+ name: string;
112
+ path: string;
113
+ priority?: number | undefined;
114
+ }[] | undefined;
115
+ skills?: string[] | undefined;
56
116
  outputDir?: string | undefined;
57
117
  hookPacks?: string[] | undefined;
58
- skills?: string[] | undefined;
59
- setupTarget?: "claude" | "codex" | "both" | undefined;
118
+ setupTarget?: "claude" | "codex" | "opencode" | "both" | "all" | undefined;
60
119
  telegram?: boolean | undefined;
120
+ domainPacks?: {
121
+ name: string;
122
+ package: string;
123
+ version?: string | undefined;
124
+ }[] | undefined;
125
+ sharedVaultPath?: string | undefined;
61
126
  }>;
62
127
  export type AgentConfig = z.infer<typeof AgentConfigSchema>;
63
128
  /** Input type — fields with defaults are optional (use before Zod parsing) */
package/dist/types.js CHANGED
@@ -2,7 +2,20 @@ import { z } from 'zod';
2
2
  /** Communication tone for the agent persona */
3
3
  const TONES = ['precise', 'mentor', 'pragmatic'];
4
4
  /** Where to scaffold host/client integration setup. */
5
- export const SETUP_TARGETS = ['claude', 'codex', 'both'];
5
+ export const SETUP_TARGETS = ['claude', 'codex', 'opencode', 'both', 'all'];
6
+ /** Available model presets for agent configuration */
7
+ export const MODEL_PRESETS = [
8
+ 'claude-code-sonnet-4',
9
+ 'claude-code-opus-4',
10
+ 'claude-code-3.7-sonnet',
11
+ 'claude-code-3.5-haiku',
12
+ 'claude-4-sonnet',
13
+ 'claude-4-opus',
14
+ 'gpt-4.1',
15
+ 'gpt-4.1-mini',
16
+ 'gemini-2.5',
17
+ 'gemini-2.5-flash',
18
+ ];
6
19
  /** Agent configuration — everything needed to scaffold */
7
20
  export const AgentConfigSchema = z.object({
8
21
  /** Agent identifier (kebab-case, used for directory and package name) */
@@ -27,9 +40,34 @@ export const AgentConfigSchema = z.object({
27
40
  hookPacks: z.array(z.string()).optional(),
28
41
  /** Skills to include (if omitted, all skills are included for backward compat) */
29
42
  skills: z.array(z.string()).optional(),
43
+ /** Primary model for the coder agent */
44
+ model: z.string().optional().default('claude-code-sonnet-4'),
30
45
  /** AI client setup target: Claude Code, Codex, or both */
31
- setupTarget: z.enum(SETUP_TARGETS).optional().default('claude'),
46
+ setupTarget: z.enum(SETUP_TARGETS).optional().default('opencode'),
32
47
  /** Enable Telegram transport scaffolding. Default: false. */
33
48
  telegram: z.boolean().optional().default(false),
49
+ /** Enable Cognee vector search integration. Default: false. */
50
+ cognee: z.boolean().optional().default(false),
51
+ /** Domain packs — npm packages with custom ops, knowledge, rules, and skills. */
52
+ domainPacks: z
53
+ .array(z.object({
54
+ name: z.string(),
55
+ package: z.string(),
56
+ version: z.string().optional(),
57
+ }))
58
+ .optional(),
59
+ /** Vault connections — link to existing vaults instead of importing knowledge. */
60
+ vaults: z
61
+ .array(z.object({
62
+ /** Display name for this vault connection */
63
+ name: z.string(),
64
+ /** Absolute path to the vault SQLite database */
65
+ path: z.string(),
66
+ /** Search priority (0-1). Higher = results ranked higher. Default: 0.5 */
67
+ priority: z.number().min(0).max(1).optional().default(0.5),
68
+ }))
69
+ .optional(),
70
+ /** @deprecated Use vaults[] instead. Shorthand for a single shared vault at priority 0.6. */
71
+ sharedVaultPath: z.string().optional(),
34
72
  });
35
73
  //# sourceMappingURL=types.js.map
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,+CAA+C;AAC/C,MAAM,KAAK,GAAG,CAAC,SAAS,EAAE,QAAQ,EAAE,WAAW,CAAU,CAAC;AAE1D,uDAAuD;AACvD,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAU,CAAC;AAGlE,0DAA0D;AAC1D,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,yEAAyE;IACzE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,mBAAmB,EAAE,gDAAgD,CAAC;IAC3F,kCAAkC;IAClC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IAC/B,gCAAgC;IAChC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IAChC,iDAAiD;IACjD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IACxC,0CAA0C;IAC1C,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IAClD,0DAA0D;IAC1D,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IAC9C,wDAAwD;IACxD,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC;IACnD,gFAAgF;IAChF,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;IAChD,oFAAoF;IACpF,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IAC9D,yDAAyD;IACzD,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACzC,kFAAkF;IAClF,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACtC,0DAA0D;IAC1D,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC;IAC/D,6DAA6D;IAC7D,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CAChD,CAAC,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Auto-detect installed @soleri/domain-* packages.
3
+ * Scans node_modules for packages matching the pattern.
4
+ */
5
+ export interface DetectedDomainPack {
6
+ /** Display name (e.g., 'design') */
7
+ name: string;
8
+ /** npm package name (e.g., '@soleri/domain-design') */
9
+ package: string;
10
+ /** Installed version from package.json */
11
+ version: string;
12
+ }
13
+ /**
14
+ * Scan node_modules/@soleri/ for domain-* packages and return refs
15
+ * suitable for merging into AgentConfig.domainPacks.
16
+ *
17
+ * Walks up from basePath to find the nearest node_modules with @soleri scope.
18
+ * For each `domain-*` directory found, reads its package.json to extract
19
+ * name and version, then does a lightweight structural check (the package
20
+ * must export a default or named 'pack' with name, version, domains, ops fields).
21
+ *
22
+ * @param basePath - Directory to start searching from (typically config.outputDir)
23
+ * @returns Array of detected domain pack references
24
+ */
25
+ export declare function detectInstalledDomainPacks(basePath: string): DetectedDomainPack[];
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Auto-detect installed @soleri/domain-* packages.
3
+ * Scans node_modules for packages matching the pattern.
4
+ */
5
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ /**
8
+ * Scan node_modules/@soleri/ for domain-* packages and return refs
9
+ * suitable for merging into AgentConfig.domainPacks.
10
+ *
11
+ * Walks up from basePath to find the nearest node_modules with @soleri scope.
12
+ * For each `domain-*` directory found, reads its package.json to extract
13
+ * name and version, then does a lightweight structural check (the package
14
+ * must export a default or named 'pack' with name, version, domains, ops fields).
15
+ *
16
+ * @param basePath - Directory to start searching from (typically config.outputDir)
17
+ * @returns Array of detected domain pack references
18
+ */
19
+ export function detectInstalledDomainPacks(basePath) {
20
+ const results = [];
21
+ const soleriScope = findSoleriScope(basePath);
22
+ if (!soleriScope)
23
+ return results;
24
+ let entries;
25
+ try {
26
+ entries = readdirSync(soleriScope);
27
+ }
28
+ catch {
29
+ return results;
30
+ }
31
+ for (const entry of entries) {
32
+ if (!entry.startsWith('domain-'))
33
+ continue;
34
+ const packDir = join(soleriScope, entry);
35
+ const pkgJsonPath = join(packDir, 'package.json');
36
+ if (!existsSync(pkgJsonPath))
37
+ continue;
38
+ try {
39
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
40
+ if (!pkg.name || !pkg.version)
41
+ continue;
42
+ // Lightweight structural check: look for an index/main file that
43
+ // would be importable. We don't actually import it (that would
44
+ // execute arbitrary code at scaffold time), but we verify the
45
+ // package looks like a domain pack by checking package.json keywords
46
+ // or the presence of expected exports.
47
+ if (looksLikeDomainPack(packDir, pkg)) {
48
+ const shortName = entry.replace(/^domain-/, '');
49
+ results.push({
50
+ name: shortName,
51
+ package: pkg.name,
52
+ version: pkg.version,
53
+ });
54
+ }
55
+ }
56
+ catch {
57
+ // Skip packages with invalid package.json
58
+ }
59
+ }
60
+ return results;
61
+ }
62
+ /**
63
+ * Walk up from basePath to find node_modules/@soleri directory.
64
+ */
65
+ function findSoleriScope(basePath) {
66
+ let current = basePath;
67
+ const seen = new Set();
68
+ while (current && !seen.has(current)) {
69
+ seen.add(current);
70
+ const candidate = join(current, 'node_modules', '@soleri');
71
+ if (existsSync(candidate)) {
72
+ return candidate;
73
+ }
74
+ const parent = join(current, '..');
75
+ if (parent === current)
76
+ break;
77
+ current = parent;
78
+ }
79
+ return null;
80
+ }
81
+ /**
82
+ * Lightweight check that a package looks like a domain pack without importing it.
83
+ * Checks for:
84
+ * - A soleri-domain-pack keyword in package.json, OR
85
+ * - An entry point file that exists
86
+ */
87
+ function looksLikeDomainPack(packDir, pkg) {
88
+ // Fast path: keyword-based detection
89
+ if (Array.isArray(pkg.keywords) && pkg.keywords.includes('soleri-domain-pack')) {
90
+ return true;
91
+ }
92
+ // Check that the package has an entry point (main or exports)
93
+ if (pkg.main) {
94
+ return existsSync(join(packDir, pkg.main));
95
+ }
96
+ // Check common entry points
97
+ for (const candidate of ['dist/index.js', 'index.js', 'dist/index.mjs']) {
98
+ if (existsSync(join(packDir, candidate))) {
99
+ return true;
100
+ }
101
+ }
102
+ return false;
103
+ }
104
+ //# sourceMappingURL=detect-domain-packs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-domain-packs.js","sourceRoot":"","sources":["../../src/utils/detect-domain-packs.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soleri/forge",
3
- "version": "5.14.10",
3
+ "version": "7.0.0",
4
4
  "description": "Scaffold AI agents that learn, remember, and grow with you.",
5
5
  "keywords": [
6
6
  "agent",
@@ -51,6 +51,7 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@modelcontextprotocol/sdk": "^1.12.1",
54
+ "yaml": "^2.8.2",
54
55
  "zod": "^3.24.2"
55
56
  },
56
57
  "engines": {
@@ -0,0 +1,178 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { detectInstalledDomainPacks } from '../utils/detect-domain-packs.js';
6
+
7
+ function createTempDir(): string {
8
+ const dir = join(
9
+ tmpdir(),
10
+ `soleri-forge-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
11
+ );
12
+ mkdirSync(dir, { recursive: true });
13
+ return dir;
14
+ }
15
+
16
+ describe('detectInstalledDomainPacks', () => {
17
+ let tempDir: string;
18
+
19
+ beforeEach(() => {
20
+ tempDir = createTempDir();
21
+ });
22
+
23
+ afterEach(() => {
24
+ try {
25
+ rmSync(tempDir, { recursive: true, force: true });
26
+ } catch {
27
+ // best-effort cleanup
28
+ }
29
+ });
30
+
31
+ it('returns empty array when no node_modules exists', () => {
32
+ const result = detectInstalledDomainPacks(tempDir);
33
+ expect(result).toEqual([]);
34
+ });
35
+
36
+ it('returns empty array when @soleri scope exists but has no domain-* packages', () => {
37
+ const scope = join(tempDir, 'node_modules', '@soleri', 'core');
38
+ mkdirSync(scope, { recursive: true });
39
+ writeFileSync(
40
+ join(scope, 'package.json'),
41
+ JSON.stringify({ name: '@soleri/core', version: '1.0.0' }),
42
+ );
43
+
44
+ const result = detectInstalledDomainPacks(tempDir);
45
+ expect(result).toEqual([]);
46
+ });
47
+
48
+ it('detects a domain pack with soleri-domain-pack keyword', () => {
49
+ const packDir = join(tempDir, 'node_modules', '@soleri', 'domain-design');
50
+ mkdirSync(packDir, { recursive: true });
51
+ writeFileSync(
52
+ join(packDir, 'package.json'),
53
+ JSON.stringify({
54
+ name: '@soleri/domain-design',
55
+ version: '2.1.0',
56
+ keywords: ['soleri-domain-pack'],
57
+ }),
58
+ );
59
+
60
+ const result = detectInstalledDomainPacks(tempDir);
61
+ expect(result).toEqual([
62
+ { name: 'design', package: '@soleri/domain-design', version: '2.1.0' },
63
+ ]);
64
+ });
65
+
66
+ it('detects a domain pack with a main entry point', () => {
67
+ const packDir = join(tempDir, 'node_modules', '@soleri', 'domain-security');
68
+ const distDir = join(packDir, 'dist');
69
+ mkdirSync(distDir, { recursive: true });
70
+ writeFileSync(
71
+ join(packDir, 'package.json'),
72
+ JSON.stringify({
73
+ name: '@soleri/domain-security',
74
+ version: '1.0.0',
75
+ main: 'dist/index.js',
76
+ }),
77
+ );
78
+ writeFileSync(join(distDir, 'index.js'), 'module.exports = {};');
79
+
80
+ const result = detectInstalledDomainPacks(tempDir);
81
+ expect(result).toEqual([
82
+ { name: 'security', package: '@soleri/domain-security', version: '1.0.0' },
83
+ ]);
84
+ });
85
+
86
+ it('detects multiple domain packs', () => {
87
+ for (const [suffix, ver] of [
88
+ ['design', '2.0.0'],
89
+ ['security', '1.0.0'],
90
+ ['analytics', '0.5.0'],
91
+ ]) {
92
+ const packDir = join(tempDir, 'node_modules', '@soleri', `domain-${suffix}`);
93
+ mkdirSync(packDir, { recursive: true });
94
+ writeFileSync(
95
+ join(packDir, 'package.json'),
96
+ JSON.stringify({
97
+ name: `@soleri/domain-${suffix}`,
98
+ version: ver,
99
+ keywords: ['soleri-domain-pack'],
100
+ }),
101
+ );
102
+ }
103
+
104
+ const result = detectInstalledDomainPacks(tempDir);
105
+ expect(result).toHaveLength(3);
106
+ expect(result.map((r) => r.name).sort()).toEqual(['analytics', 'design', 'security']);
107
+ });
108
+
109
+ it('skips packages without valid package.json', () => {
110
+ const packDir = join(tempDir, 'node_modules', '@soleri', 'domain-broken');
111
+ mkdirSync(packDir, { recursive: true });
112
+ writeFileSync(join(packDir, 'package.json'), '{ invalid json }');
113
+
114
+ const result = detectInstalledDomainPacks(tempDir);
115
+ expect(result).toEqual([]);
116
+ });
117
+
118
+ it('skips packages missing name or version', () => {
119
+ const packDir = join(tempDir, 'node_modules', '@soleri', 'domain-incomplete');
120
+ mkdirSync(packDir, { recursive: true });
121
+ writeFileSync(
122
+ join(packDir, 'package.json'),
123
+ JSON.stringify({ name: '@soleri/domain-incomplete' }), // no version
124
+ );
125
+
126
+ const result = detectInstalledDomainPacks(tempDir);
127
+ expect(result).toEqual([]);
128
+ });
129
+
130
+ it('skips packages that have no entry point and no keyword', () => {
131
+ const packDir = join(tempDir, 'node_modules', '@soleri', 'domain-empty');
132
+ mkdirSync(packDir, { recursive: true });
133
+ writeFileSync(
134
+ join(packDir, 'package.json'),
135
+ JSON.stringify({ name: '@soleri/domain-empty', version: '1.0.0' }),
136
+ );
137
+
138
+ const result = detectInstalledDomainPacks(tempDir);
139
+ expect(result).toEqual([]);
140
+ });
141
+
142
+ it('walks up to find node_modules in a parent directory', () => {
143
+ // Create node_modules in tempDir but search from a subdirectory
144
+ const packDir = join(tempDir, 'node_modules', '@soleri', 'domain-design');
145
+ mkdirSync(packDir, { recursive: true });
146
+ writeFileSync(
147
+ join(packDir, 'package.json'),
148
+ JSON.stringify({
149
+ name: '@soleri/domain-design',
150
+ version: '1.0.0',
151
+ keywords: ['soleri-domain-pack'],
152
+ }),
153
+ );
154
+
155
+ const subDir = join(tempDir, 'projects', 'my-agent');
156
+ mkdirSync(subDir, { recursive: true });
157
+
158
+ const result = detectInstalledDomainPacks(subDir);
159
+ expect(result).toEqual([
160
+ { name: 'design', package: '@soleri/domain-design', version: '1.0.0' },
161
+ ]);
162
+ });
163
+
164
+ it('detects domain pack with dist/index.js entry point (no main field)', () => {
165
+ const packDir = join(tempDir, 'node_modules', '@soleri', 'domain-testing');
166
+ mkdirSync(join(packDir, 'dist'), { recursive: true });
167
+ writeFileSync(
168
+ join(packDir, 'package.json'),
169
+ JSON.stringify({ name: '@soleri/domain-testing', version: '1.0.0' }),
170
+ );
171
+ writeFileSync(join(packDir, 'dist', 'index.js'), 'export default {};');
172
+
173
+ const result = detectInstalledDomainPacks(tempDir);
174
+ expect(result).toEqual([
175
+ { name: 'testing', package: '@soleri/domain-testing', version: '1.0.0' },
176
+ ]);
177
+ });
178
+ });