@soleri/core 2.11.0 → 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 (255) hide show
  1. package/data/flows/build.flow.yaml +128 -0
  2. package/data/flows/deliver.flow.yaml +110 -0
  3. package/data/flows/design.flow.yaml +108 -0
  4. package/data/flows/enhance.flow.yaml +90 -0
  5. package/data/flows/explore.flow.yaml +84 -0
  6. package/data/flows/fix.flow.yaml +90 -0
  7. package/data/flows/plan.flow.yaml +87 -0
  8. package/data/flows/review.flow.yaml +90 -0
  9. package/dist/brain/brain.d.ts.map +1 -1
  10. package/dist/brain/brain.js +10 -0
  11. package/dist/brain/brain.js.map +1 -1
  12. package/dist/brain/intelligence.d.ts.map +1 -1
  13. package/dist/brain/intelligence.js +16 -2
  14. package/dist/brain/intelligence.js.map +1 -1
  15. package/dist/capabilities/chain-mapping.d.ts +21 -0
  16. package/dist/capabilities/chain-mapping.d.ts.map +1 -0
  17. package/dist/capabilities/chain-mapping.js +86 -0
  18. package/dist/capabilities/chain-mapping.js.map +1 -0
  19. package/dist/capabilities/index.d.ts +10 -0
  20. package/dist/capabilities/index.d.ts.map +1 -0
  21. package/dist/capabilities/index.js +8 -0
  22. package/dist/capabilities/index.js.map +1 -0
  23. package/dist/capabilities/registry.d.ts +95 -0
  24. package/dist/capabilities/registry.d.ts.map +1 -0
  25. package/dist/capabilities/registry.js +227 -0
  26. package/dist/capabilities/registry.js.map +1 -0
  27. package/dist/capabilities/types.d.ts +106 -0
  28. package/dist/capabilities/types.d.ts.map +1 -0
  29. package/dist/capabilities/types.js +12 -0
  30. package/dist/capabilities/types.js.map +1 -0
  31. package/dist/control/intent-router.d.ts.map +1 -1
  32. package/dist/control/intent-router.js +58 -2
  33. package/dist/control/intent-router.js.map +1 -1
  34. package/dist/domain-packs/index.d.ts +8 -0
  35. package/dist/domain-packs/index.d.ts.map +1 -0
  36. package/dist/domain-packs/index.js +8 -0
  37. package/dist/domain-packs/index.js.map +1 -0
  38. package/dist/domain-packs/inject-rules.d.ts +24 -0
  39. package/dist/domain-packs/inject-rules.d.ts.map +1 -0
  40. package/dist/domain-packs/inject-rules.js +65 -0
  41. package/dist/domain-packs/inject-rules.js.map +1 -0
  42. package/dist/domain-packs/knowledge-installer.d.ts +27 -0
  43. package/dist/domain-packs/knowledge-installer.d.ts.map +1 -0
  44. package/dist/domain-packs/knowledge-installer.js +89 -0
  45. package/dist/domain-packs/knowledge-installer.js.map +1 -0
  46. package/dist/domain-packs/loader.d.ts +28 -0
  47. package/dist/domain-packs/loader.d.ts.map +1 -0
  48. package/dist/domain-packs/loader.js +105 -0
  49. package/dist/domain-packs/loader.js.map +1 -0
  50. package/dist/domain-packs/pack-runtime.d.ts +80 -0
  51. package/dist/domain-packs/pack-runtime.d.ts.map +1 -0
  52. package/dist/domain-packs/pack-runtime.js +36 -0
  53. package/dist/domain-packs/pack-runtime.js.map +1 -0
  54. package/dist/domain-packs/skills-installer.d.ts +21 -0
  55. package/dist/domain-packs/skills-installer.d.ts.map +1 -0
  56. package/dist/domain-packs/skills-installer.js +38 -0
  57. package/dist/domain-packs/skills-installer.js.map +1 -0
  58. package/dist/domain-packs/token-resolver.d.ts +37 -0
  59. package/dist/domain-packs/token-resolver.d.ts.map +1 -0
  60. package/dist/domain-packs/token-resolver.js +109 -0
  61. package/dist/domain-packs/token-resolver.js.map +1 -0
  62. package/dist/domain-packs/types.d.ts +91 -0
  63. package/dist/domain-packs/types.d.ts.map +1 -0
  64. package/dist/domain-packs/types.js +122 -0
  65. package/dist/domain-packs/types.js.map +1 -0
  66. package/dist/engine/bin/soleri-engine.d.ts +12 -0
  67. package/dist/engine/bin/soleri-engine.d.ts.map +1 -0
  68. package/dist/engine/bin/soleri-engine.js +183 -0
  69. package/dist/engine/bin/soleri-engine.js.map +1 -0
  70. package/dist/engine/core-ops.d.ts +27 -0
  71. package/dist/engine/core-ops.d.ts.map +1 -0
  72. package/dist/engine/core-ops.js +159 -0
  73. package/dist/engine/core-ops.js.map +1 -0
  74. package/dist/engine/index.d.ts +19 -0
  75. package/dist/engine/index.d.ts.map +1 -0
  76. package/dist/engine/index.js +17 -0
  77. package/dist/engine/index.js.map +1 -0
  78. package/dist/engine/register-engine.d.ts +54 -0
  79. package/dist/engine/register-engine.d.ts.map +1 -0
  80. package/dist/engine/register-engine.js +270 -0
  81. package/dist/engine/register-engine.js.map +1 -0
  82. package/dist/engine/test-helpers.d.ts +30 -0
  83. package/dist/engine/test-helpers.d.ts.map +1 -0
  84. package/dist/engine/test-helpers.js +59 -0
  85. package/dist/engine/test-helpers.js.map +1 -0
  86. package/dist/flows/context-router.d.ts +39 -0
  87. package/dist/flows/context-router.d.ts.map +1 -0
  88. package/dist/flows/context-router.js +206 -0
  89. package/dist/flows/context-router.js.map +1 -0
  90. package/dist/flows/dispatch-registry.d.ts +24 -0
  91. package/dist/flows/dispatch-registry.d.ts.map +1 -0
  92. package/dist/flows/dispatch-registry.js +70 -0
  93. package/dist/flows/dispatch-registry.js.map +1 -0
  94. package/dist/flows/epilogue.d.ts +24 -0
  95. package/dist/flows/epilogue.d.ts.map +1 -0
  96. package/dist/flows/epilogue.js +52 -0
  97. package/dist/flows/epilogue.js.map +1 -0
  98. package/dist/flows/executor.d.ts +25 -0
  99. package/dist/flows/executor.d.ts.map +1 -0
  100. package/dist/flows/executor.js +153 -0
  101. package/dist/flows/executor.js.map +1 -0
  102. package/dist/flows/gate-evaluator.d.ts +26 -0
  103. package/dist/flows/gate-evaluator.d.ts.map +1 -0
  104. package/dist/flows/gate-evaluator.js +162 -0
  105. package/dist/flows/gate-evaluator.js.map +1 -0
  106. package/dist/flows/index.d.ts +14 -0
  107. package/dist/flows/index.d.ts.map +1 -0
  108. package/dist/flows/index.js +20 -0
  109. package/dist/flows/index.js.map +1 -0
  110. package/dist/flows/loader.d.ts +17 -0
  111. package/dist/flows/loader.d.ts.map +1 -0
  112. package/dist/flows/loader.js +61 -0
  113. package/dist/flows/loader.js.map +1 -0
  114. package/dist/flows/plan-builder.d.ts +40 -0
  115. package/dist/flows/plan-builder.d.ts.map +1 -0
  116. package/dist/flows/plan-builder.js +213 -0
  117. package/dist/flows/plan-builder.js.map +1 -0
  118. package/dist/flows/probes.d.ts +11 -0
  119. package/dist/flows/probes.d.ts.map +1 -0
  120. package/dist/flows/probes.js +62 -0
  121. package/dist/flows/probes.js.map +1 -0
  122. package/dist/flows/types.d.ts +950 -0
  123. package/dist/flows/types.d.ts.map +1 -0
  124. package/dist/flows/types.js +105 -0
  125. package/dist/flows/types.js.map +1 -0
  126. package/dist/index.d.ts +11 -1
  127. package/dist/index.d.ts.map +1 -1
  128. package/dist/index.js +10 -1
  129. package/dist/index.js.map +1 -1
  130. package/dist/intelligence/loader.d.ts +19 -0
  131. package/dist/intelligence/loader.d.ts.map +1 -1
  132. package/dist/intelligence/loader.js +86 -5
  133. package/dist/intelligence/loader.js.map +1 -1
  134. package/dist/intelligence/types.d.ts +1 -0
  135. package/dist/intelligence/types.d.ts.map +1 -1
  136. package/dist/packs/types.d.ts +58 -19
  137. package/dist/packs/types.d.ts.map +1 -1
  138. package/dist/packs/types.js +14 -0
  139. package/dist/packs/types.js.map +1 -1
  140. package/dist/playbooks/generic/onboarding.d.ts +9 -0
  141. package/dist/playbooks/generic/onboarding.d.ts.map +1 -0
  142. package/dist/playbooks/generic/onboarding.js +74 -0
  143. package/dist/playbooks/generic/onboarding.js.map +1 -0
  144. package/dist/playbooks/playbook-registry.d.ts.map +1 -1
  145. package/dist/playbooks/playbook-registry.js +2 -0
  146. package/dist/playbooks/playbook-registry.js.map +1 -1
  147. package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
  148. package/dist/runtime/admin-extra-ops.js +15 -9
  149. package/dist/runtime/admin-extra-ops.js.map +1 -1
  150. package/dist/runtime/admin-ops.js +4 -4
  151. package/dist/runtime/admin-ops.js.map +1 -1
  152. package/dist/runtime/capture-ops.d.ts.map +1 -1
  153. package/dist/runtime/capture-ops.js +33 -1
  154. package/dist/runtime/capture-ops.js.map +1 -1
  155. package/dist/runtime/domain-ops.d.ts +21 -5
  156. package/dist/runtime/domain-ops.d.ts.map +1 -1
  157. package/dist/runtime/domain-ops.js +85 -8
  158. package/dist/runtime/domain-ops.js.map +1 -1
  159. package/dist/runtime/facades/cognee-facade.d.ts.map +1 -1
  160. package/dist/runtime/facades/cognee-facade.js +3 -1
  161. package/dist/runtime/facades/cognee-facade.js.map +1 -1
  162. package/dist/runtime/facades/index.d.ts.map +1 -1
  163. package/dist/runtime/facades/index.js +10 -6
  164. package/dist/runtime/facades/index.js.map +1 -1
  165. package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
  166. package/dist/runtime/facades/vault-facade.js +2 -0
  167. package/dist/runtime/facades/vault-facade.js.map +1 -1
  168. package/dist/runtime/orchestrate-ops.d.ts +8 -7
  169. package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
  170. package/dist/runtime/orchestrate-ops.js +227 -58
  171. package/dist/runtime/orchestrate-ops.js.map +1 -1
  172. package/dist/runtime/runtime.d.ts.map +1 -1
  173. package/dist/runtime/runtime.js +23 -17
  174. package/dist/runtime/runtime.js.map +1 -1
  175. package/dist/runtime/types.d.ts +6 -2
  176. package/dist/runtime/types.d.ts.map +1 -1
  177. package/dist/runtime/vault-linking-ops.d.ts +13 -0
  178. package/dist/runtime/vault-linking-ops.d.ts.map +1 -0
  179. package/dist/runtime/vault-linking-ops.js +367 -0
  180. package/dist/runtime/vault-linking-ops.js.map +1 -0
  181. package/dist/vault/linking.d.ts +46 -0
  182. package/dist/vault/linking.d.ts.map +1 -0
  183. package/dist/vault/linking.js +275 -0
  184. package/dist/vault/linking.js.map +1 -0
  185. package/dist/vault/vault-types.d.ts +37 -0
  186. package/dist/vault/vault-types.d.ts.map +1 -1
  187. package/dist/vault/vault.d.ts +12 -0
  188. package/dist/vault/vault.d.ts.map +1 -1
  189. package/dist/vault/vault.js +85 -6
  190. package/dist/vault/vault.js.map +1 -1
  191. package/package.json +4 -1
  192. package/src/__tests__/admin-extra-ops.test.ts +1 -1
  193. package/src/__tests__/admin-ops.test.ts +2 -1
  194. package/src/__tests__/cognee-client-gaps.test.ts +470 -0
  195. package/src/__tests__/cognee-hybrid-search.test.ts +478 -0
  196. package/src/__tests__/cognee-sync-manager-deep.test.ts +630 -0
  197. package/src/__tests__/cognee-sync-manager.test.ts +1 -0
  198. package/src/__tests__/core-ops.test.ts +9 -61
  199. package/src/__tests__/domain-packs.test.ts +421 -0
  200. package/src/__tests__/flows.test.ts +604 -0
  201. package/src/__tests__/playbook-registry.test.ts +2 -2
  202. package/src/__tests__/playbook-seeder.test.ts +8 -8
  203. package/src/__tests__/playbook.test.ts +5 -5
  204. package/src/__tests__/token-resolver.test.ts +79 -0
  205. package/src/brain/brain.ts +12 -0
  206. package/src/brain/intelligence.ts +21 -2
  207. package/src/capabilities/chain-mapping.ts +93 -0
  208. package/src/capabilities/index.ts +21 -0
  209. package/src/capabilities/registry.ts +290 -0
  210. package/src/capabilities/types.ts +143 -0
  211. package/src/control/intent-router.ts +46 -2
  212. package/src/domain-packs/index.ts +27 -0
  213. package/src/domain-packs/inject-rules.ts +74 -0
  214. package/src/domain-packs/knowledge-installer.ts +116 -0
  215. package/src/domain-packs/loader.ts +124 -0
  216. package/src/domain-packs/pack-runtime.ts +99 -0
  217. package/src/domain-packs/skills-installer.ts +56 -0
  218. package/src/domain-packs/token-resolver.ts +126 -0
  219. package/src/domain-packs/types.ts +229 -0
  220. package/src/engine/__tests__/register-engine.test.ts +104 -0
  221. package/src/engine/bin/soleri-engine.ts +217 -0
  222. package/src/engine/core-ops.ts +178 -0
  223. package/src/engine/index.ts +19 -0
  224. package/src/engine/register-engine.ts +385 -0
  225. package/src/engine/test-helpers.ts +83 -0
  226. package/src/flows/context-router.ts +257 -0
  227. package/src/flows/dispatch-registry.ts +80 -0
  228. package/src/flows/epilogue.ts +65 -0
  229. package/src/flows/executor.ts +182 -0
  230. package/src/flows/gate-evaluator.ts +171 -0
  231. package/src/flows/index.ts +52 -0
  232. package/src/flows/loader.ts +63 -0
  233. package/src/flows/plan-builder.ts +250 -0
  234. package/src/flows/probes.ts +70 -0
  235. package/src/flows/types.ts +217 -0
  236. package/src/index.ts +68 -1
  237. package/src/intelligence/loader.ts +96 -5
  238. package/src/intelligence/types.ts +1 -0
  239. package/src/packs/types.ts +19 -0
  240. package/src/playbooks/generic/onboarding.ts +79 -0
  241. package/src/playbooks/playbook-registry.ts +2 -0
  242. package/src/runtime/admin-extra-ops.ts +14 -8
  243. package/src/runtime/admin-ops.ts +4 -4
  244. package/src/runtime/capture-ops.ts +40 -1
  245. package/src/runtime/domain-ops.ts +92 -7
  246. package/src/runtime/facades/cognee-facade.ts +3 -1
  247. package/src/runtime/facades/index.ts +12 -6
  248. package/src/runtime/facades/vault-facade.ts +2 -0
  249. package/src/runtime/orchestrate-ops.ts +271 -62
  250. package/src/runtime/runtime.ts +27 -18
  251. package/src/runtime/types.ts +6 -2
  252. package/src/runtime/vault-linking-ops.ts +454 -0
  253. package/src/vault/linking.ts +333 -0
  254. package/src/vault/vault-types.ts +46 -0
  255. package/src/vault/vault.ts +94 -7
@@ -34,8 +34,9 @@ describe('createSemanticFacades', () => {
34
34
  return op;
35
35
  }
36
36
 
37
- it('should return 232 ops', () => {
38
- expect(ops.length).toBe(314);
37
+ it('should return ops from all semantic facades (without cognee)', () => {
38
+ // Op count varies as new ops are added. Just verify a reasonable minimum.
39
+ expect(ops.length).toBeGreaterThan(250);
39
40
  });
40
41
 
41
42
  it('should have all expected op names', () => {
@@ -103,12 +104,8 @@ describe('createSemanticFacades', () => {
103
104
  expect(names).toContain('route_intent');
104
105
  expect(names).toContain('morph');
105
106
  expect(names).toContain('get_behavior_rules');
106
- // Cognee
107
- expect(names).toContain('cognee_status');
108
- expect(names).toContain('cognee_search');
109
- expect(names).toContain('cognee_add');
110
- expect(names).toContain('cognee_cognify');
111
- expect(names).toContain('cognee_config');
107
+ // Cognee — only present when cognee is enabled in runtime
108
+ // (this test creates runtime without cognee: true)
112
109
  // Context Engine (#172)
113
110
  expect(names).toContain('context_extract_entities');
114
111
  expect(names).toContain('context_retrieve_knowledge');
@@ -324,10 +321,7 @@ describe('createSemanticFacades', () => {
324
321
  // Prompt templates
325
322
  expect(names).toContain('render_prompt');
326
323
  expect(names).toContain('list_templates');
327
- // Cognee Sync ops
328
- expect(names).toContain('cognee_sync_status');
329
- expect(names).toContain('cognee_sync_drain');
330
- expect(names).toContain('cognee_sync_reconcile');
324
+ // Cognee Sync ops — only present when cognee is enabled
331
325
  // Intake ops
332
326
  expect(names).toContain('intake_ingest_book');
333
327
  expect(names).toContain('intake_process');
@@ -515,55 +509,9 @@ describe('createSemanticFacades', () => {
515
509
  expect(result.totalEntries).toBe(1);
516
510
  });
517
511
 
518
- it('cognee_status should return health check result', async () => {
519
- // Cognee is not running in tests should degrade gracefully
520
- const result = (await findOp('cognee_status').handler({})) as {
521
- available: boolean;
522
- url: string;
523
- };
524
- expect(typeof result.available).toBe('boolean');
525
- expect(typeof result.url).toBe('string');
526
- });
527
-
528
- it('cognee_search should return empty when unavailable', async () => {
529
- const results = (await findOp('cognee_search').handler({
530
- query: 'test pattern',
531
- })) as unknown[];
532
- expect(results).toEqual([]);
533
- });
534
-
535
- it('cognee_add should return 0 when unavailable', async () => {
536
- runtime.vault.seed([
537
- {
538
- id: 'cog-1',
539
- type: 'pattern',
540
- domain: 'testing',
541
- title: 'Cognee test',
542
- severity: 'warning',
543
- description: 'Test.',
544
- tags: ['test'],
545
- },
546
- ]);
547
- const result = (await findOp('cognee_add').handler({
548
- entryIds: ['cog-1'],
549
- })) as { added: number };
550
- expect(result.added).toBe(0);
551
- });
552
-
553
- it('cognee_cognify should return unavailable when Cognee is down', async () => {
554
- const result = (await findOp('cognee_cognify').handler({})) as { status: string };
555
- expect(result.status).toBe('unavailable');
556
- });
557
-
558
- it('cognee_config should return config and null status', async () => {
559
- const result = (await findOp('cognee_config').handler({})) as {
560
- config: { baseUrl: string; dataset: string };
561
- cachedStatus: null;
562
- };
563
- expect(result.config.baseUrl).toBe('http://localhost:8000');
564
- expect(result.config.dataset).toBe('test-core-ops');
565
- expect(result.cachedStatus).toBeNull();
566
- });
512
+ // Cognee ops are only registered when runtime has cognee enabled.
513
+ // This test creates runtime without cognee: true, so these ops are not present.
514
+ // See e2e/full-pipeline.test.ts for cognee facade tests with cognee enabled.
567
515
 
568
516
  it('llm_rotate should return rotation status', async () => {
569
517
  const result = (await findOp('llm_rotate').handler({ provider: 'openai' })) as {
@@ -0,0 +1,421 @@
1
+ /**
2
+ * Domain Packs — failing tests for the DomainPack primitive.
3
+ *
4
+ * These tests define the contract for domain packs:
5
+ * - DomainPack types and validation
6
+ * - Pack loading and dependency resolution
7
+ * - Extended createDomainFacades() with pack support
8
+ * - OCP: standard domains unchanged when packs are absent
9
+ */
10
+
11
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
12
+ import { createAgentRuntime } from '../runtime/runtime.js';
13
+ import { createDomainFacade, createDomainFacades } from '../runtime/domain-ops.js';
14
+ import type { AgentRuntime } from '../runtime/types.js';
15
+ import { z } from 'zod';
16
+
17
+ import type { DomainPack } from '../domain-packs/types.js';
18
+ import { validateDomainPack, SEMANTIC_FACADE_NAMES } from '../domain-packs/types.js';
19
+ import { resolveDependencies } from '../domain-packs/loader.js';
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Helpers: mock domain packs for testing
23
+ // ---------------------------------------------------------------------------
24
+
25
+ function createMockPack(overrides: Partial<DomainPack> = {}): DomainPack {
26
+ return {
27
+ name: 'test-design',
28
+ version: '1.0.0',
29
+ domains: ['design'],
30
+ ops: [
31
+ {
32
+ name: 'check_contrast',
33
+ description: 'Check WCAG contrast ratio between two colors.',
34
+ auth: 'read' as const,
35
+ schema: z.object({
36
+ foreground: z.string(),
37
+ background: z.string(),
38
+ }),
39
+ handler: async (_params) => {
40
+ return { ratio: 4.5, passes: true, level: 'AA' };
41
+ },
42
+ },
43
+ {
44
+ name: 'validate_token',
45
+ description: 'Validate a design token name against the token schema.',
46
+ auth: 'read' as const,
47
+ schema: z.object({ token: z.string() }),
48
+ handler: async (params) => {
49
+ return { valid: true, token: params.token };
50
+ },
51
+ },
52
+ {
53
+ name: 'get_color_pairs',
54
+ description: 'Get accessible color pair suggestions.',
55
+ auth: 'read' as const,
56
+ schema: z.object({ background: z.string() }),
57
+ handler: async (params) => {
58
+ return { pairs: [{ fg: '#000', bg: params.background, ratio: 21 }] };
59
+ },
60
+ },
61
+ ],
62
+ ...overrides,
63
+ };
64
+ }
65
+
66
+ function createMockPackWithFacades(): DomainPack {
67
+ return {
68
+ name: 'test-design-full',
69
+ version: '1.0.0',
70
+ domains: ['design'],
71
+ ops: [
72
+ {
73
+ name: 'check_contrast',
74
+ description: 'Check contrast.',
75
+ auth: 'read' as const,
76
+ handler: async () => ({ ratio: 4.5 }),
77
+ },
78
+ ],
79
+ facades: [
80
+ {
81
+ name: 'design_rules',
82
+ description: 'Design rules and guidelines.',
83
+ ops: [
84
+ {
85
+ name: 'get_clean_code_rules',
86
+ description: 'Get clean code rules for design.',
87
+ auth: 'read' as const,
88
+ handler: async () => ({ rules: ['no-hex-colors', 'semantic-tokens-only'] }),
89
+ },
90
+ {
91
+ name: 'get_architecture_patterns',
92
+ description: 'Get architecture patterns.',
93
+ auth: 'read' as const,
94
+ handler: async () => ({ patterns: ['component-composition'] }),
95
+ },
96
+ ],
97
+ },
98
+ ],
99
+ };
100
+ }
101
+
102
+ // ---------------------------------------------------------------------------
103
+ // 1. DomainPack Types & Validation
104
+ // ---------------------------------------------------------------------------
105
+
106
+ describe('DomainPack types and validation', () => {
107
+ it('should validate a well-formed domain pack', () => {
108
+ const pack = createMockPack();
109
+ const result = validateDomainPack(pack);
110
+ expect(result.success).toBe(true);
111
+ if (result.success) {
112
+ expect(result.data.name).toBe('test-design');
113
+ expect(result.data.ops.length).toBe(3);
114
+ }
115
+ });
116
+
117
+ it('should reject pack with missing required fields', () => {
118
+ const result = validateDomainPack({ name: 'incomplete' });
119
+ expect(result.success).toBe(false);
120
+ });
121
+
122
+ it('should reject pack with duplicate op names', () => {
123
+ const pack = createMockPack({
124
+ ops: [
125
+ {
126
+ name: 'duplicate_op',
127
+ description: 'First.',
128
+ auth: 'read',
129
+ handler: async () => ({}),
130
+ },
131
+ {
132
+ name: 'duplicate_op',
133
+ description: 'Second.',
134
+ auth: 'read',
135
+ handler: async () => ({}),
136
+ },
137
+ ],
138
+ });
139
+ const result = validateDomainPack(pack);
140
+ expect(result.success).toBe(false);
141
+ });
142
+
143
+ it('should reject pack with facade name colliding with semantic facades', () => {
144
+ const pack = createMockPack({
145
+ facades: [
146
+ {
147
+ name: 'vault', // collides with semantic facade
148
+ description: 'Bad facade.',
149
+ ops: [],
150
+ },
151
+ ],
152
+ });
153
+ const result = validateDomainPack(pack);
154
+ expect(result.success).toBe(false);
155
+ });
156
+
157
+ it('should export SEMANTIC_FACADE_NAMES containing core facade names', () => {
158
+ expect(SEMANTIC_FACADE_NAMES).toContain('vault');
159
+ expect(SEMANTIC_FACADE_NAMES).toContain('plan');
160
+ expect(SEMANTIC_FACADE_NAMES).toContain('brain');
161
+ expect(SEMANTIC_FACADE_NAMES).toContain('memory');
162
+ expect(SEMANTIC_FACADE_NAMES).toContain('admin');
163
+ expect(SEMANTIC_FACADE_NAMES).toContain('curator');
164
+ });
165
+
166
+ it('should accept pack with valid KnowledgeManifest tiers', () => {
167
+ const pack = createMockPack({
168
+ knowledge: {
169
+ canonical: './knowledge/canonical',
170
+ curated: './knowledge/curated',
171
+ },
172
+ });
173
+ const result = validateDomainPack(pack);
174
+ expect(result.success).toBe(true);
175
+ });
176
+ });
177
+
178
+ // ---------------------------------------------------------------------------
179
+ // 2. Pack Dependency Resolution
180
+ // ---------------------------------------------------------------------------
181
+
182
+ describe('Pack dependency resolution', () => {
183
+ it('should resolve packs with no dependencies', () => {
184
+ const packA = createMockPack({ name: 'pack-a' });
185
+ const packB = createMockPack({ name: 'pack-b', domains: ['testing'] });
186
+ const sorted = resolveDependencies([packA, packB]);
187
+ expect(sorted.length).toBe(2);
188
+ });
189
+
190
+ it('should sort packs by dependency order', () => {
191
+ const packA = createMockPack({ name: 'pack-a', requires: ['pack-b'] });
192
+ const packB = createMockPack({ name: 'pack-b', domains: ['testing'] });
193
+ const sorted = resolveDependencies([packA, packB]);
194
+ const names = sorted.map((p) => p.name);
195
+ expect(names.indexOf('pack-b')).toBeLessThan(names.indexOf('pack-a'));
196
+ });
197
+
198
+ it('should detect circular dependencies', () => {
199
+ const packA = createMockPack({ name: 'pack-a', requires: ['pack-b'] });
200
+ const packB = createMockPack({ name: 'pack-b', domains: ['testing'], requires: ['pack-a'] });
201
+ expect(() => resolveDependencies([packA, packB])).toThrow(/circular/i);
202
+ });
203
+
204
+ it('should throw on missing dependency', () => {
205
+ const packA = createMockPack({ name: 'pack-a', requires: ['pack-nonexistent'] });
206
+ expect(() => resolveDependencies([packA])).toThrow(/not found/i);
207
+ });
208
+ });
209
+
210
+ // ---------------------------------------------------------------------------
211
+ // 3. Extended createDomainFacades() with pack support
212
+ // ---------------------------------------------------------------------------
213
+
214
+ describe('createDomainFacades with packs', () => {
215
+ let runtime: AgentRuntime;
216
+
217
+ beforeEach(() => {
218
+ runtime = createAgentRuntime({
219
+ agentId: 'test-packs',
220
+ vaultPath: ':memory:',
221
+ });
222
+ });
223
+
224
+ afterEach(() => {
225
+ runtime.close();
226
+ });
227
+
228
+ // --- OCP: no packs = identical behavior ---
229
+
230
+ it('should work identically without packs parameter', () => {
231
+ const withoutPacks = createDomainFacades(runtime, 'test-packs', ['security']);
232
+ expect(withoutPacks.length).toBe(1);
233
+ expect(withoutPacks[0].ops.length).toBe(5);
234
+ expect(withoutPacks[0].name).toBe('test-packs_security');
235
+ });
236
+
237
+ it('should work identically with empty packs array', () => {
238
+ const withEmpty = createDomainFacades(runtime, 'test-packs', ['security'], []);
239
+ expect(withEmpty.length).toBe(1);
240
+ expect(withEmpty[0].ops.length).toBe(5);
241
+ });
242
+
243
+ // --- Pack ops override standard ops ---
244
+
245
+ it('should use pack ops for claimed domain', () => {
246
+ const pack = createMockPack(); // claims 'design'
247
+ const facades = createDomainFacades(runtime, 'test-packs', ['design'], [pack]);
248
+ const designFacade = facades.find((f) => f.name.includes('design'));
249
+ expect(designFacade).toBeDefined();
250
+
251
+ // Should have pack's 3 custom ops + standard fallbacks for unclaimed names
252
+ const opNames = designFacade!.ops.map((o) => o.name);
253
+ expect(opNames).toContain('check_contrast');
254
+ expect(opNames).toContain('validate_token');
255
+ expect(opNames).toContain('get_color_pairs');
256
+ });
257
+
258
+ it('should keep standard ops as fallbacks for names not in pack', () => {
259
+ const pack = createMockPack(); // has check_contrast, validate_token, get_color_pairs
260
+ const facades = createDomainFacades(runtime, 'test-packs', ['design'], [pack]);
261
+ const designFacade = facades.find((f) => f.name.includes('design'));
262
+ const opNames = designFacade!.ops.map((o) => o.name);
263
+
264
+ // Standard ops not overridden by pack should still exist as fallbacks
265
+ expect(opNames).toContain('get_patterns');
266
+ expect(opNames).toContain('search');
267
+ expect(opNames).toContain('get_entry');
268
+ expect(opNames).toContain('capture');
269
+ expect(opNames).toContain('remove');
270
+ });
271
+
272
+ it('should use pack handler when pack overrides a standard op name', () => {
273
+ const customSearchHandler = async () => ({ custom: true, source: 'pack' });
274
+ const pack = createMockPack({
275
+ ops: [
276
+ {
277
+ name: 'search', // overrides standard search
278
+ description: 'Custom domain search with specialized ranking.',
279
+ auth: 'read' as const,
280
+ handler: customSearchHandler,
281
+ },
282
+ ],
283
+ });
284
+ const facades = createDomainFacades(runtime, 'test-packs', ['design'], [pack]);
285
+ const designFacade = facades.find((f) => f.name.includes('design'));
286
+ const searchOp = designFacade!.ops.find((o) => o.name === 'search');
287
+ expect(searchOp!.handler).toBe(customSearchHandler);
288
+ });
289
+
290
+ // --- Unclaimed domains get standard 5 ops ---
291
+
292
+ it('should give unclaimed domains standard 5 ops', () => {
293
+ const pack = createMockPack(); // claims 'design' only
294
+ const facades = createDomainFacades(runtime, 'test-packs', ['design', 'security'], [pack]);
295
+ const securityFacade = facades.find((f) => f.name.includes('security'));
296
+ expect(securityFacade).toBeDefined();
297
+ expect(securityFacade!.ops.length).toBe(5);
298
+ expect(securityFacade!.ops.map((o) => o.name)).not.toContain('check_contrast');
299
+ });
300
+
301
+ // --- Pack standalone facades ---
302
+
303
+ it('should register pack standalone facades as additional facades', () => {
304
+ const pack = createMockPackWithFacades();
305
+ const facades = createDomainFacades(runtime, 'test-packs', ['design'], [pack]);
306
+
307
+ // Should have domain facade + standalone design_rules facade
308
+ const facadeNames = facades.map((f) => f.name);
309
+ expect(facadeNames.some((n) => n.includes('design_rules'))).toBe(true);
310
+ });
311
+
312
+ it('should prefix standalone facade names with agentId', () => {
313
+ const pack = createMockPackWithFacades();
314
+ const facades = createDomainFacades(runtime, 'test-packs', ['design'], [pack]);
315
+ const rulesFacade = facades.find((f) => f.name.includes('design_rules'));
316
+ expect(rulesFacade!.name).toBe('test-packs_design_rules');
317
+ });
318
+
319
+ it('standalone facade ops should be callable', async () => {
320
+ const pack = createMockPackWithFacades();
321
+ const facades = createDomainFacades(runtime, 'test-packs', ['design'], [pack]);
322
+ const rulesFacade = facades.find((f) => f.name.includes('design_rules'));
323
+ const cleanCodeOp = rulesFacade!.ops.find((o) => o.name === 'get_clean_code_rules');
324
+ const result = (await cleanCodeOp!.handler({})) as { rules: string[] };
325
+ expect(result.rules).toContain('no-hex-colors');
326
+ });
327
+
328
+ // --- Custom ops are callable ---
329
+
330
+ it('custom pack ops should execute and return results', async () => {
331
+ const pack = createMockPack();
332
+ const facades = createDomainFacades(runtime, 'test-packs', ['design'], [pack]);
333
+ const designFacade = facades.find((f) => f.name.includes('design'));
334
+ const contrastOp = designFacade!.ops.find((o) => o.name === 'check_contrast');
335
+ const result = (await contrastOp!.handler({
336
+ foreground: '#000000',
337
+ background: '#ffffff',
338
+ })) as { ratio: number; passes: boolean };
339
+ expect(result.ratio).toBe(4.5);
340
+ expect(result.passes).toBe(true);
341
+ });
342
+
343
+ // --- Multiple packs ---
344
+
345
+ it('should support multiple packs claiming different domains', () => {
346
+ const designPack = createMockPack({ name: 'design-pack', domains: ['design'] });
347
+ const securityPack = createMockPack({
348
+ name: 'security-pack',
349
+ domains: ['security'],
350
+ ops: [
351
+ {
352
+ name: 'scan_vulnerabilities',
353
+ description: 'Scan for security vulnerabilities.',
354
+ auth: 'read' as const,
355
+ handler: async () => ({ vulnerabilities: [] }),
356
+ },
357
+ ],
358
+ });
359
+ const facades = createDomainFacades(
360
+ runtime,
361
+ 'test-packs',
362
+ ['design', 'security'],
363
+ [designPack, securityPack],
364
+ );
365
+ expect(facades.length).toBeGreaterThanOrEqual(2);
366
+
367
+ const designOps = facades.find((f) => f.name.includes('design'))!.ops.map((o) => o.name);
368
+ const securityOps = facades.find((f) => f.name.includes('security'))!.ops.map((o) => o.name);
369
+
370
+ expect(designOps).toContain('check_contrast');
371
+ expect(securityOps).toContain('scan_vulnerabilities');
372
+ });
373
+ });
374
+
375
+ // ---------------------------------------------------------------------------
376
+ // 4. OCP Regression — existing behavior preserved
377
+ // ---------------------------------------------------------------------------
378
+
379
+ describe('OCP regression: existing domain-ops behavior', () => {
380
+ let runtime: AgentRuntime;
381
+
382
+ beforeEach(() => {
383
+ runtime = createAgentRuntime({
384
+ agentId: 'test-ocp',
385
+ vaultPath: ':memory:',
386
+ });
387
+ });
388
+
389
+ afterEach(() => {
390
+ runtime.close();
391
+ });
392
+
393
+ it('createDomainFacade (singular) still works unchanged', () => {
394
+ const facade = createDomainFacade(runtime, 'test-ocp', 'security');
395
+ expect(facade.ops.length).toBe(5);
396
+ expect(facade.name).toBe('test-ocp_security');
397
+ });
398
+
399
+ it('createDomainFacades without 4th arg still returns standard facades', () => {
400
+ const facades = createDomainFacades(runtime, 'test-ocp', ['security', 'api-design']);
401
+ expect(facades.length).toBe(2);
402
+ facades.forEach((f) => {
403
+ expect(f.ops.length).toBe(5);
404
+ });
405
+ });
406
+
407
+ it('standard domain capture still integrates with governance', async () => {
408
+ const facade = createDomainFacade(runtime, 'test-ocp', 'security');
409
+ const captureOp = facade.ops.find((o) => o.name === 'capture')!;
410
+ const result = (await captureOp.handler({
411
+ id: 'ocp-test-1',
412
+ type: 'pattern',
413
+ title: 'OCP Test',
414
+ severity: 'warning',
415
+ description: 'Should still work.',
416
+ tags: ['ocp'],
417
+ })) as { captured: boolean; governance: { action: string } };
418
+ expect(result.captured).toBe(true);
419
+ expect(result.governance.action).toBe('capture');
420
+ });
421
+ });