@soleri/core 2.12.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 (251) 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/intelligence.d.ts.map +1 -1
  10. package/dist/brain/intelligence.js +16 -2
  11. package/dist/brain/intelligence.js.map +1 -1
  12. package/dist/capabilities/chain-mapping.d.ts +21 -0
  13. package/dist/capabilities/chain-mapping.d.ts.map +1 -0
  14. package/dist/capabilities/chain-mapping.js +86 -0
  15. package/dist/capabilities/chain-mapping.js.map +1 -0
  16. package/dist/capabilities/index.d.ts +10 -0
  17. package/dist/capabilities/index.d.ts.map +1 -0
  18. package/dist/capabilities/index.js +8 -0
  19. package/dist/capabilities/index.js.map +1 -0
  20. package/dist/capabilities/registry.d.ts +95 -0
  21. package/dist/capabilities/registry.d.ts.map +1 -0
  22. package/dist/capabilities/registry.js +227 -0
  23. package/dist/capabilities/registry.js.map +1 -0
  24. package/dist/capabilities/types.d.ts +106 -0
  25. package/dist/capabilities/types.d.ts.map +1 -0
  26. package/dist/capabilities/types.js +12 -0
  27. package/dist/capabilities/types.js.map +1 -0
  28. package/dist/control/intent-router.d.ts.map +1 -1
  29. package/dist/control/intent-router.js +58 -2
  30. package/dist/control/intent-router.js.map +1 -1
  31. package/dist/domain-packs/index.d.ts +8 -0
  32. package/dist/domain-packs/index.d.ts.map +1 -0
  33. package/dist/domain-packs/index.js +8 -0
  34. package/dist/domain-packs/index.js.map +1 -0
  35. package/dist/domain-packs/inject-rules.d.ts +24 -0
  36. package/dist/domain-packs/inject-rules.d.ts.map +1 -0
  37. package/dist/domain-packs/inject-rules.js +65 -0
  38. package/dist/domain-packs/inject-rules.js.map +1 -0
  39. package/dist/domain-packs/knowledge-installer.d.ts +27 -0
  40. package/dist/domain-packs/knowledge-installer.d.ts.map +1 -0
  41. package/dist/domain-packs/knowledge-installer.js +89 -0
  42. package/dist/domain-packs/knowledge-installer.js.map +1 -0
  43. package/dist/domain-packs/loader.d.ts +28 -0
  44. package/dist/domain-packs/loader.d.ts.map +1 -0
  45. package/dist/domain-packs/loader.js +105 -0
  46. package/dist/domain-packs/loader.js.map +1 -0
  47. package/dist/domain-packs/pack-runtime.d.ts +80 -0
  48. package/dist/domain-packs/pack-runtime.d.ts.map +1 -0
  49. package/dist/domain-packs/pack-runtime.js +36 -0
  50. package/dist/domain-packs/pack-runtime.js.map +1 -0
  51. package/dist/domain-packs/skills-installer.d.ts +21 -0
  52. package/dist/domain-packs/skills-installer.d.ts.map +1 -0
  53. package/dist/domain-packs/skills-installer.js +38 -0
  54. package/dist/domain-packs/skills-installer.js.map +1 -0
  55. package/dist/domain-packs/token-resolver.d.ts +37 -0
  56. package/dist/domain-packs/token-resolver.d.ts.map +1 -0
  57. package/dist/domain-packs/token-resolver.js +109 -0
  58. package/dist/domain-packs/token-resolver.js.map +1 -0
  59. package/dist/domain-packs/types.d.ts +91 -0
  60. package/dist/domain-packs/types.d.ts.map +1 -0
  61. package/dist/domain-packs/types.js +122 -0
  62. package/dist/domain-packs/types.js.map +1 -0
  63. package/dist/engine/bin/soleri-engine.d.ts +12 -0
  64. package/dist/engine/bin/soleri-engine.d.ts.map +1 -0
  65. package/dist/engine/bin/soleri-engine.js +183 -0
  66. package/dist/engine/bin/soleri-engine.js.map +1 -0
  67. package/dist/engine/core-ops.d.ts +27 -0
  68. package/dist/engine/core-ops.d.ts.map +1 -0
  69. package/dist/engine/core-ops.js +159 -0
  70. package/dist/engine/core-ops.js.map +1 -0
  71. package/dist/engine/index.d.ts +19 -0
  72. package/dist/engine/index.d.ts.map +1 -0
  73. package/dist/engine/index.js +17 -0
  74. package/dist/engine/index.js.map +1 -0
  75. package/dist/engine/register-engine.d.ts +54 -0
  76. package/dist/engine/register-engine.d.ts.map +1 -0
  77. package/dist/engine/register-engine.js +270 -0
  78. package/dist/engine/register-engine.js.map +1 -0
  79. package/dist/engine/test-helpers.d.ts +30 -0
  80. package/dist/engine/test-helpers.d.ts.map +1 -0
  81. package/dist/engine/test-helpers.js +59 -0
  82. package/dist/engine/test-helpers.js.map +1 -0
  83. package/dist/flows/context-router.d.ts +39 -0
  84. package/dist/flows/context-router.d.ts.map +1 -0
  85. package/dist/flows/context-router.js +206 -0
  86. package/dist/flows/context-router.js.map +1 -0
  87. package/dist/flows/dispatch-registry.d.ts +24 -0
  88. package/dist/flows/dispatch-registry.d.ts.map +1 -0
  89. package/dist/flows/dispatch-registry.js +70 -0
  90. package/dist/flows/dispatch-registry.js.map +1 -0
  91. package/dist/flows/epilogue.d.ts +24 -0
  92. package/dist/flows/epilogue.d.ts.map +1 -0
  93. package/dist/flows/epilogue.js +52 -0
  94. package/dist/flows/epilogue.js.map +1 -0
  95. package/dist/flows/executor.d.ts +25 -0
  96. package/dist/flows/executor.d.ts.map +1 -0
  97. package/dist/flows/executor.js +153 -0
  98. package/dist/flows/executor.js.map +1 -0
  99. package/dist/flows/gate-evaluator.d.ts +26 -0
  100. package/dist/flows/gate-evaluator.d.ts.map +1 -0
  101. package/dist/flows/gate-evaluator.js +162 -0
  102. package/dist/flows/gate-evaluator.js.map +1 -0
  103. package/dist/flows/index.d.ts +14 -0
  104. package/dist/flows/index.d.ts.map +1 -0
  105. package/dist/flows/index.js +20 -0
  106. package/dist/flows/index.js.map +1 -0
  107. package/dist/flows/loader.d.ts +17 -0
  108. package/dist/flows/loader.d.ts.map +1 -0
  109. package/dist/flows/loader.js +61 -0
  110. package/dist/flows/loader.js.map +1 -0
  111. package/dist/flows/plan-builder.d.ts +40 -0
  112. package/dist/flows/plan-builder.d.ts.map +1 -0
  113. package/dist/flows/plan-builder.js +213 -0
  114. package/dist/flows/plan-builder.js.map +1 -0
  115. package/dist/flows/probes.d.ts +11 -0
  116. package/dist/flows/probes.d.ts.map +1 -0
  117. package/dist/flows/probes.js +62 -0
  118. package/dist/flows/probes.js.map +1 -0
  119. package/dist/flows/types.d.ts +950 -0
  120. package/dist/flows/types.d.ts.map +1 -0
  121. package/dist/flows/types.js +105 -0
  122. package/dist/flows/types.js.map +1 -0
  123. package/dist/index.d.ts +11 -1
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +10 -1
  126. package/dist/index.js.map +1 -1
  127. package/dist/intelligence/loader.d.ts +19 -0
  128. package/dist/intelligence/loader.d.ts.map +1 -1
  129. package/dist/intelligence/loader.js +35 -0
  130. package/dist/intelligence/loader.js.map +1 -1
  131. package/dist/intelligence/types.d.ts +1 -0
  132. package/dist/intelligence/types.d.ts.map +1 -1
  133. package/dist/packs/types.d.ts +58 -19
  134. package/dist/packs/types.d.ts.map +1 -1
  135. package/dist/packs/types.js +14 -0
  136. package/dist/packs/types.js.map +1 -1
  137. package/dist/playbooks/generic/onboarding.d.ts +9 -0
  138. package/dist/playbooks/generic/onboarding.d.ts.map +1 -0
  139. package/dist/playbooks/generic/onboarding.js +74 -0
  140. package/dist/playbooks/generic/onboarding.js.map +1 -0
  141. package/dist/playbooks/playbook-registry.d.ts.map +1 -1
  142. package/dist/playbooks/playbook-registry.js +2 -0
  143. package/dist/playbooks/playbook-registry.js.map +1 -1
  144. package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
  145. package/dist/runtime/admin-extra-ops.js +15 -9
  146. package/dist/runtime/admin-extra-ops.js.map +1 -1
  147. package/dist/runtime/admin-ops.js +4 -4
  148. package/dist/runtime/admin-ops.js.map +1 -1
  149. package/dist/runtime/capture-ops.d.ts.map +1 -1
  150. package/dist/runtime/capture-ops.js +33 -1
  151. package/dist/runtime/capture-ops.js.map +1 -1
  152. package/dist/runtime/domain-ops.d.ts +21 -5
  153. package/dist/runtime/domain-ops.d.ts.map +1 -1
  154. package/dist/runtime/domain-ops.js +64 -6
  155. package/dist/runtime/domain-ops.js.map +1 -1
  156. package/dist/runtime/facades/cognee-facade.d.ts.map +1 -1
  157. package/dist/runtime/facades/cognee-facade.js +3 -1
  158. package/dist/runtime/facades/cognee-facade.js.map +1 -1
  159. package/dist/runtime/facades/index.d.ts.map +1 -1
  160. package/dist/runtime/facades/index.js +10 -6
  161. package/dist/runtime/facades/index.js.map +1 -1
  162. package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
  163. package/dist/runtime/facades/vault-facade.js +2 -0
  164. package/dist/runtime/facades/vault-facade.js.map +1 -1
  165. package/dist/runtime/orchestrate-ops.d.ts +8 -7
  166. package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
  167. package/dist/runtime/orchestrate-ops.js +217 -61
  168. package/dist/runtime/orchestrate-ops.js.map +1 -1
  169. package/dist/runtime/runtime.d.ts.map +1 -1
  170. package/dist/runtime/runtime.js +23 -17
  171. package/dist/runtime/runtime.js.map +1 -1
  172. package/dist/runtime/types.d.ts +6 -2
  173. package/dist/runtime/types.d.ts.map +1 -1
  174. package/dist/runtime/vault-linking-ops.d.ts +13 -0
  175. package/dist/runtime/vault-linking-ops.d.ts.map +1 -0
  176. package/dist/runtime/vault-linking-ops.js +367 -0
  177. package/dist/runtime/vault-linking-ops.js.map +1 -0
  178. package/dist/vault/linking.d.ts +46 -0
  179. package/dist/vault/linking.d.ts.map +1 -0
  180. package/dist/vault/linking.js +275 -0
  181. package/dist/vault/linking.js.map +1 -0
  182. package/dist/vault/vault-types.d.ts +37 -0
  183. package/dist/vault/vault-types.d.ts.map +1 -1
  184. package/dist/vault/vault.d.ts +12 -0
  185. package/dist/vault/vault.d.ts.map +1 -1
  186. package/dist/vault/vault.js +85 -6
  187. package/dist/vault/vault.js.map +1 -1
  188. package/package.json +4 -1
  189. package/src/__tests__/admin-extra-ops.test.ts +1 -1
  190. package/src/__tests__/admin-ops.test.ts +2 -1
  191. package/src/__tests__/cognee-client-gaps.test.ts +470 -0
  192. package/src/__tests__/cognee-hybrid-search.test.ts +478 -0
  193. package/src/__tests__/cognee-sync-manager-deep.test.ts +630 -0
  194. package/src/__tests__/cognee-sync-manager.test.ts +1 -0
  195. package/src/__tests__/core-ops.test.ts +9 -61
  196. package/src/__tests__/domain-packs.test.ts +421 -0
  197. package/src/__tests__/flows.test.ts +604 -0
  198. package/src/__tests__/playbook-registry.test.ts +2 -2
  199. package/src/__tests__/playbook-seeder.test.ts +8 -8
  200. package/src/__tests__/playbook.test.ts +5 -5
  201. package/src/__tests__/token-resolver.test.ts +79 -0
  202. package/src/brain/intelligence.ts +21 -2
  203. package/src/capabilities/chain-mapping.ts +93 -0
  204. package/src/capabilities/index.ts +21 -0
  205. package/src/capabilities/registry.ts +290 -0
  206. package/src/capabilities/types.ts +143 -0
  207. package/src/control/intent-router.ts +46 -2
  208. package/src/domain-packs/index.ts +27 -0
  209. package/src/domain-packs/inject-rules.ts +74 -0
  210. package/src/domain-packs/knowledge-installer.ts +116 -0
  211. package/src/domain-packs/loader.ts +124 -0
  212. package/src/domain-packs/pack-runtime.ts +99 -0
  213. package/src/domain-packs/skills-installer.ts +56 -0
  214. package/src/domain-packs/token-resolver.ts +126 -0
  215. package/src/domain-packs/types.ts +229 -0
  216. package/src/engine/__tests__/register-engine.test.ts +104 -0
  217. package/src/engine/bin/soleri-engine.ts +217 -0
  218. package/src/engine/core-ops.ts +178 -0
  219. package/src/engine/index.ts +19 -0
  220. package/src/engine/register-engine.ts +385 -0
  221. package/src/engine/test-helpers.ts +83 -0
  222. package/src/flows/context-router.ts +257 -0
  223. package/src/flows/dispatch-registry.ts +80 -0
  224. package/src/flows/epilogue.ts +65 -0
  225. package/src/flows/executor.ts +182 -0
  226. package/src/flows/gate-evaluator.ts +171 -0
  227. package/src/flows/index.ts +52 -0
  228. package/src/flows/loader.ts +63 -0
  229. package/src/flows/plan-builder.ts +250 -0
  230. package/src/flows/probes.ts +70 -0
  231. package/src/flows/types.ts +217 -0
  232. package/src/index.ts +68 -1
  233. package/src/intelligence/loader.ts +38 -0
  234. package/src/intelligence/types.ts +1 -0
  235. package/src/packs/types.ts +19 -0
  236. package/src/playbooks/generic/onboarding.ts +79 -0
  237. package/src/playbooks/playbook-registry.ts +2 -0
  238. package/src/runtime/admin-extra-ops.ts +14 -8
  239. package/src/runtime/admin-ops.ts +4 -4
  240. package/src/runtime/capture-ops.ts +40 -1
  241. package/src/runtime/domain-ops.ts +71 -5
  242. package/src/runtime/facades/cognee-facade.ts +3 -1
  243. package/src/runtime/facades/index.ts +12 -6
  244. package/src/runtime/facades/vault-facade.ts +2 -0
  245. package/src/runtime/orchestrate-ops.ts +261 -65
  246. package/src/runtime/runtime.ts +27 -18
  247. package/src/runtime/types.ts +6 -2
  248. package/src/runtime/vault-linking-ops.ts +454 -0
  249. package/src/vault/linking.ts +333 -0
  250. package/src/vault/vault-types.ts +46 -0
  251. package/src/vault/vault.ts +94 -7
@@ -189,7 +189,7 @@ describe('playbook_create op', () => {
189
189
  vaultPath: ':memory:',
190
190
  plansPath: join(plannerDir, 'plans.json'),
191
191
  });
192
- ops = createSemanticFacades(runtime, 'test').flatMap(f => f.ops);
192
+ ops = createSemanticFacades(runtime, 'test').flatMap((f) => f.ops);
193
193
  });
194
194
 
195
195
  afterEach(() => {
@@ -300,7 +300,7 @@ describe('playbook_match op', () => {
300
300
  vaultPath: ':memory:',
301
301
  plansPath: join(plannerDir, 'plans.json'),
302
302
  });
303
- ops = createSemanticFacades(runtime, 'test').flatMap(f => f.ops);
303
+ ops = createSemanticFacades(runtime, 'test').flatMap((f) => f.ops);
304
304
  });
305
305
 
306
306
  afterEach(() => {
@@ -356,7 +356,7 @@ describe('playbook_seed op', () => {
356
356
  vaultPath: ':memory:',
357
357
  plansPath: join(plannerDir, 'plans.json'),
358
358
  });
359
- ops = createSemanticFacades(runtime, 'test').flatMap(f => f.ops);
359
+ ops = createSemanticFacades(runtime, 'test').flatMap((f) => f.ops);
360
360
  });
361
361
 
362
362
  afterEach(() => {
@@ -371,7 +371,7 @@ describe('playbook_seed op', () => {
371
371
  errors: number;
372
372
  };
373
373
 
374
- expect(result.seeded).toBe(6);
374
+ expect(result.seeded).toBe(7);
375
375
  expect(result.skipped).toBe(0);
376
376
  expect(result.errors).toBe(0);
377
377
  });
@@ -384,6 +384,6 @@ describe('playbook_seed op', () => {
384
384
  };
385
385
 
386
386
  expect(result.seeded).toBe(0);
387
- expect(result.skipped).toBe(6);
387
+ expect(result.skipped).toBe(7);
388
388
  });
389
389
  });
@@ -0,0 +1,79 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ resolveToken,
4
+ listProjectTokens,
5
+ buildReverseIndex,
6
+ } from '../domain-packs/token-resolver.js';
7
+ import type { PackProjectContext } from '../domain-packs/pack-runtime.js';
8
+
9
+ const mockProject: PackProjectContext = {
10
+ id: 'test-project',
11
+ name: 'Test Project',
12
+ path: '/test',
13
+ colors: {
14
+ primary: {
15
+ base: '#3B82F6',
16
+ scale: { '50': '#EFF6FF', '100': '#DBEAFE', '500': '#3B82F6', '900': '#1E3A5F' },
17
+ },
18
+ neutral: {
19
+ base: '#6B7280',
20
+ scale: { '50': '#F9FAFB', '100': '#F3F4F6', '500': '#6B7280', '900': '#111827' },
21
+ },
22
+ },
23
+ semanticTokens: {
24
+ 'text-inverse': '#FFFFFF',
25
+ 'bg-surface': '#F9FAFB',
26
+ },
27
+ };
28
+
29
+ describe('resolveToken', () => {
30
+ it('should pass through hex values', () => {
31
+ expect(resolveToken('#FF0000', mockProject)).toBe('#FF0000');
32
+ });
33
+
34
+ it('should resolve named colors', () => {
35
+ expect(resolveToken('white', mockProject)).toBe('#FFFFFF');
36
+ expect(resolveToken('black', mockProject)).toBe('#000000');
37
+ });
38
+
39
+ it('should resolve semantic tokens', () => {
40
+ expect(resolveToken('text-inverse', mockProject)).toBe('#FFFFFF');
41
+ expect(resolveToken('bg-surface', mockProject)).toBe('#F9FAFB');
42
+ });
43
+
44
+ it('should resolve SCALE[SHADE] format', () => {
45
+ expect(resolveToken('PRIMARY[500]', mockProject)).toBe('#3B82F6');
46
+ expect(resolveToken('neutral[900]', mockProject)).toBe('#111827');
47
+ });
48
+
49
+ it('should resolve Tailwind-style tokens', () => {
50
+ expect(resolveToken('bg-primary-500', mockProject)).toBe('#3B82F6');
51
+ expect(resolveToken('text-neutral-900', mockProject)).toBe('#111827');
52
+ });
53
+
54
+ it('should throw for unknown tokens', () => {
55
+ expect(() => resolveToken('unknown-token', mockProject)).toThrow('Cannot resolve');
56
+ });
57
+
58
+ it('should throw for unknown scales', () => {
59
+ expect(() => resolveToken('ACCENT[500]', mockProject)).toThrow('Unknown color scale');
60
+ });
61
+ });
62
+
63
+ describe('listProjectTokens', () => {
64
+ it('should list all scale and semantic tokens', () => {
65
+ const tokens = listProjectTokens(mockProject);
66
+ expect(tokens.length).toBeGreaterThan(0);
67
+ expect(tokens.some((t) => t.token === 'primary-500')).toBe(true);
68
+ expect(tokens.some((t) => t.token === 'text-inverse')).toBe(true);
69
+ expect(tokens.some((t) => t.scale === 'semantic')).toBe(true);
70
+ });
71
+ });
72
+
73
+ describe('buildReverseIndex', () => {
74
+ it('should map hex to token name', () => {
75
+ const index = buildReverseIndex(mockProject);
76
+ expect(index.get('#3B82F6')).toBe('primary-500');
77
+ expect(index.get('#FFFFFF')).toBe('text-inverse');
78
+ });
79
+ });
@@ -497,12 +497,31 @@ export class BrainIntelligence {
497
497
  limit?: number;
498
498
  }): PatternStrength[] {
499
499
  const limit = context.limit ?? 5;
500
- const strengths = this.getStrengths({
500
+
501
+ // Try domain-filtered first, fall back to all domains if too few results
502
+ let strengths = this.getStrengths({
501
503
  domain: context.domain,
502
- minStrength: 30,
504
+ minStrength: 20, // lowered from 30 — small corpus needs lower threshold
503
505
  limit: limit * 3,
504
506
  });
505
507
 
508
+ // If domain-filtered returns too few, try without domain filter
509
+ // This handles cases where domain was stored as 'unknown' due to
510
+ // vault.get() returning null during computeStrengths
511
+ if (strengths.length < limit && context.domain) {
512
+ const allStrengths = this.getStrengths({
513
+ minStrength: 20,
514
+ limit: limit * 5,
515
+ });
516
+ // Include domain-matching AND entries where domain lookup failed
517
+ const additional = allStrengths.filter(
518
+ (s) =>
519
+ !strengths.some((existing) => existing.pattern === s.pattern) &&
520
+ (s.domain === context.domain || s.domain === 'unknown'),
521
+ );
522
+ strengths = [...strengths, ...additional];
523
+ }
524
+
506
525
  // If task context provided, boost patterns with matching terms
507
526
  if (context.task) {
508
527
  const taskTerms = new Set(context.task.toLowerCase().split(/\W+/).filter(Boolean));
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Chain-to-capability mapping — v1 to v2 bridge.
3
+ *
4
+ * Maps Soleri's existing 26 v1 chain names (from data/flows/*.flow.yaml)
5
+ * to capability IDs in domain.action format.
6
+ *
7
+ * Used during migration: flows with chains: [] are auto-translated to
8
+ * capability references. Remove this file when all flows use needs: []
9
+ * exclusively.
10
+ *
11
+ * @see docs/architecture/capability-packs.md — "Chain-to-capability mapping"
12
+ */
13
+
14
+ /**
15
+ * Exhaustive map of every chain name used across Soleri's 8 flow YAML files.
16
+ *
17
+ * Sources:
18
+ * build.flow.yaml — vault-search, memory-search, component-search,
19
+ * recommend-design-system, architecture-search,
20
+ * brain-recommend, component-workflow,
21
+ * validate-component, validate-tokens
22
+ * deliver.flow.yaml — validate-component, validate-tokens,
23
+ * design-rules-check, test-coverage-check,
24
+ * performance-audit, delivery-checklist
25
+ * design.flow.yaml — vault-search, memory-search, cognee-design-search,
26
+ * recommend-design-system, recommend-style,
27
+ * recommend-palette, recommend-typography,
28
+ * get-stack-guidelines, brain-recommend
29
+ * enhance.flow.yaml — vault-search, memory-search, architecture-search,
30
+ * brain-recommend, validate-component, validate-tokens
31
+ * explore.flow.yaml — vault-search, memory-search, brain-strengths,
32
+ * brain-recommend, playbook-search
33
+ * fix.flow.yaml — vault-search-antipatterns, memory-search,
34
+ * error-pattern-search, brain-recommend,
35
+ * validate-component, validate-tokens
36
+ * plan.flow.yaml — vault-search, memory-search, brain-recommend,
37
+ * architecture-search, plan-create
38
+ * review.flow.yaml — validate-component, validate-tokens,
39
+ * design-rules-check, vault-search, contrast-check,
40
+ * accessibility-audit, review-report
41
+ */
42
+ const CHAIN_TO_CAPABILITY: Record<string, string> = {
43
+ // Vault & Knowledge
44
+ 'vault-search': 'vault.search',
45
+ 'vault-search-antipatterns': 'vault.search',
46
+ 'memory-search': 'memory.search',
47
+ 'playbook-search': 'vault.playbook',
48
+
49
+ // Brain
50
+ 'brain-recommend': 'brain.recommend',
51
+ 'brain-strengths': 'brain.strengths',
52
+
53
+ // Components
54
+ 'component-search': 'component.search',
55
+ 'component-workflow': 'component.workflow',
56
+ 'validate-component': 'component.validate',
57
+
58
+ // Design
59
+ 'contrast-check': 'color.validate',
60
+ 'validate-tokens': 'token.check',
61
+ 'design-rules-check': 'design.rules',
62
+ 'recommend-design-system': 'design.recommend',
63
+ 'recommend-palette': 'design.palette',
64
+ 'recommend-style': 'design.style',
65
+ 'recommend-typography': 'design.typography',
66
+ 'get-stack-guidelines': 'stack.guidelines',
67
+
68
+ // Architecture
69
+ 'architecture-search': 'architecture.search',
70
+ 'cognee-design-search': 'cognee.search',
71
+
72
+ // Planning
73
+ 'plan-create': 'plan.create',
74
+
75
+ // Review & Quality
76
+ 'review-report': 'review.report',
77
+ 'accessibility-audit': 'a11y.audit',
78
+ 'performance-audit': 'perf.audit',
79
+ 'test-coverage-check': 'test.coverage',
80
+ 'error-pattern-search': 'debug.patterns',
81
+ 'delivery-checklist': 'deliver.checklist',
82
+ };
83
+
84
+ /**
85
+ * Translate a v1 chain name to a v2 capability ID.
86
+ *
87
+ * @param chain - The chain name from a flow YAML `chains:` field
88
+ * @returns The corresponding capability ID in domain.action format, or
89
+ * undefined if no mapping exists
90
+ */
91
+ export function chainToCapability(chain: string): string | undefined {
92
+ return CHAIN_TO_CAPABILITY[chain];
93
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Capability system — barrel export.
3
+ *
4
+ * @see docs/architecture/capability-packs.md
5
+ */
6
+
7
+ export { CapabilityRegistry } from './registry.js';
8
+ export type { FlowForValidation } from './registry.js';
9
+ export { chainToCapability } from './chain-mapping.js';
10
+ export type {
11
+ CapabilityDefinition,
12
+ CapabilityHandler,
13
+ CapabilityContext,
14
+ CapabilityResult,
15
+ KnowledgeContext,
16
+ BrainRecommendation,
17
+ RegisteredCapability,
18
+ ResolvedCapability,
19
+ PackSuggestion,
20
+ FlowValidation,
21
+ } from './types.js';
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Capability Registry — runtime resolution engine for the three-layer architecture.
3
+ *
4
+ * Maps capability IDs (domain.action) to pack handlers. Resolves dependencies,
5
+ * checks availability, and supports graceful degradation when capabilities are
6
+ * missing.
7
+ *
8
+ * Registration flow:
9
+ * 1. Pack manifest declares capabilities (static, serializable)
10
+ * 2. Pack's onActivate() provides handlers (runtime, async)
11
+ * 3. registerPack() reconciles declarations with handlers
12
+ *
13
+ * @see docs/architecture/capability-packs.md
14
+ */
15
+
16
+ import type {
17
+ CapabilityDefinition,
18
+ CapabilityHandler,
19
+ RegisteredCapability,
20
+ ResolvedCapability,
21
+ PackSuggestion,
22
+ FlowValidation,
23
+ } from './types.js';
24
+ import { chainToCapability } from './chain-mapping.js';
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Flow shape accepted by validateFlow — intentionally minimal so callers
28
+ // don't need to import the full Flow type from ../flows/types.js.
29
+ // ---------------------------------------------------------------------------
30
+
31
+ export interface FlowForValidation {
32
+ steps: Array<{
33
+ needs?: string[];
34
+ chains?: string[];
35
+ }>;
36
+ onMissingCapability?: {
37
+ default?: string;
38
+ blocking?: string[];
39
+ };
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Registry
44
+ // ---------------------------------------------------------------------------
45
+
46
+ export class CapabilityRegistry {
47
+ private capabilities = new Map<string, RegisteredCapability>();
48
+ private packs = new Map<string, { id: string; capabilities: CapabilityDefinition[] }>();
49
+
50
+ // -----------------------------------------------------------------------
51
+ // Registration
52
+ // -----------------------------------------------------------------------
53
+
54
+ /**
55
+ * Register all capabilities from an installed pack.
56
+ *
57
+ * For each definition in `definitions`, the corresponding handler is looked
58
+ * up in `handlers`. If no handler is found the capability is skipped with a
59
+ * warning. When multiple packs provide the same capability, providers are
60
+ * sorted by priority (descending) — highest priority wins on resolve().
61
+ *
62
+ * @param packId - Unique identifier for the pack
63
+ * @param definitions - Capability definitions from the pack manifest
64
+ * @param handlers - Map of capabilityId → handler from onActivate()
65
+ * @param priority - Higher = preferred (core=100, user=75, domain=50, fallback=0)
66
+ */
67
+ registerPack(
68
+ packId: string,
69
+ definitions: CapabilityDefinition[],
70
+ handlers: Map<string, CapabilityHandler>,
71
+ priority: number = 0,
72
+ ): void {
73
+ for (const definition of definitions) {
74
+ const handler = handlers.get(definition.id);
75
+ if (!handler) {
76
+ console.warn(
77
+ `Pack "${packId}" declares capability "${definition.id}" but no handler provided — skipping`,
78
+ );
79
+ continue;
80
+ }
81
+
82
+ const existing = this.capabilities.get(definition.id);
83
+ if (existing) {
84
+ // Add as additional provider, keep sorted by priority descending
85
+ existing.providers.push({ packId, handler, priority });
86
+ existing.providers.sort((a, b) => b.priority - a.priority);
87
+ } else {
88
+ this.capabilities.set(definition.id, {
89
+ definition,
90
+ providers: [{ packId, handler, priority }],
91
+ });
92
+ }
93
+ }
94
+
95
+ // Store pack metadata for suggestPacksFor() lookups
96
+ this.packs.set(packId, { id: packId, capabilities: definitions });
97
+ }
98
+
99
+ // -----------------------------------------------------------------------
100
+ // Query
101
+ // -----------------------------------------------------------------------
102
+
103
+ /**
104
+ * Check if a capability is registered (has at least one provider).
105
+ */
106
+ has(capabilityId: string): boolean {
107
+ return this.capabilities.has(capabilityId);
108
+ }
109
+
110
+ /**
111
+ * Resolve a capability — returns the highest-priority handler, knowledge
112
+ * refs, and provider list. If the capability is missing or has unsatisfied
113
+ * dependencies, returns `available: false` with suggestions.
114
+ */
115
+ resolve(capabilityId: string): ResolvedCapability {
116
+ const registered = this.capabilities.get(capabilityId);
117
+
118
+ if (!registered) {
119
+ return {
120
+ available: false,
121
+ capabilityId,
122
+ suggestion: this.suggestPacksFor([capabilityId]),
123
+ };
124
+ }
125
+
126
+ // Check dependency satisfaction
127
+ const depends = registered.definition.depends ?? [];
128
+ const missingDeps = depends.filter((dep) => !this.capabilities.has(dep));
129
+
130
+ if (missingDeps.length > 0) {
131
+ return {
132
+ available: false,
133
+ capabilityId,
134
+ missingDependencies: missingDeps,
135
+ suggestion: this.suggestPacksFor(missingDeps),
136
+ };
137
+ }
138
+
139
+ // Resolved — return the highest-priority provider (index 0 after sort)
140
+ const primary = registered.providers[0];
141
+ return {
142
+ available: true,
143
+ capabilityId,
144
+ handler: primary.handler,
145
+ providers: registered.providers.map((p) => p.packId),
146
+ knowledge: registered.definition.knowledge ?? [],
147
+ };
148
+ }
149
+
150
+ /**
151
+ * List all registered capabilities grouped by domain.
152
+ *
153
+ * Domain is derived from the first segment of the capability ID
154
+ * (e.g., "color" from "color.validate").
155
+ */
156
+ list(): Map<string, CapabilityDefinition[]> {
157
+ const grouped = new Map<string, CapabilityDefinition[]>();
158
+
159
+ for (const [id, registered] of this.capabilities) {
160
+ const domain = id.split('.')[0];
161
+ const group = grouped.get(domain) ?? [];
162
+ group.push(registered.definition);
163
+ grouped.set(domain, group);
164
+ }
165
+
166
+ return grouped;
167
+ }
168
+
169
+ /**
170
+ * Search registered packs for capabilities matching the requested IDs.
171
+ *
172
+ * This searches ALL packs (not just capabilities currently registered with
173
+ * handlers) — useful for suggesting which packs to install when a capability
174
+ * is missing.
175
+ */
176
+ suggestPacksFor(capabilityIds: string[]): PackSuggestion[] {
177
+ if (capabilityIds.length === 0) return [];
178
+
179
+ const idSet = new Set(capabilityIds);
180
+ const suggestions: PackSuggestion[] = [];
181
+
182
+ for (const [, pack] of this.packs) {
183
+ const provides = pack.capabilities.filter((cap) => idSet.has(cap.id)).map((cap) => cap.id);
184
+
185
+ if (provides.length > 0) {
186
+ suggestions.push({ packId: pack.id, provides });
187
+ }
188
+ }
189
+
190
+ return suggestions;
191
+ }
192
+
193
+ // -----------------------------------------------------------------------
194
+ // Validation
195
+ // -----------------------------------------------------------------------
196
+
197
+ /**
198
+ * Validate a flow's capability requirements against installed packs.
199
+ *
200
+ * Reads both `needs` (v2 preferred) and `chains` (v1 deprecated) fields.
201
+ * For v1 chains, attempts best-effort mapping via chainToCapability().
202
+ *
203
+ * Missing capabilities are classified as either "blocking" or "degraded"
204
+ * based on the flow's onMissingCapability config.
205
+ */
206
+ validateFlow(flow: FlowForValidation): FlowValidation {
207
+ // Collect all capability IDs needed across all steps
208
+ const needed = new Set<string>();
209
+
210
+ for (const step of flow.steps) {
211
+ // v2: needs field (preferred)
212
+ if (step.needs) {
213
+ for (const cap of step.needs) {
214
+ needed.add(cap);
215
+ }
216
+ }
217
+
218
+ // v1: chains field (deprecated, best-effort mapping)
219
+ if (step.chains) {
220
+ for (const chain of step.chains) {
221
+ const capId = chainToCapability(chain);
222
+ if (capId) needed.add(capId);
223
+ }
224
+ }
225
+ }
226
+
227
+ // Partition into available vs missing
228
+ const available: string[] = [];
229
+ const missing: string[] = [];
230
+
231
+ for (const capId of needed) {
232
+ if (this.has(capId)) {
233
+ available.push(capId);
234
+ } else {
235
+ missing.push(capId);
236
+ }
237
+ }
238
+
239
+ // Classify missing capabilities by impact
240
+ const blockingSet = new Set(flow.onMissingCapability?.blocking ?? []);
241
+
242
+ const degraded = missing.map((capability) => ({
243
+ capability,
244
+ impact: blockingSet.has(capability) ? ('blocking' as const) : ('degraded' as const),
245
+ suggestion: this.suggestPacksFor([capability]),
246
+ }));
247
+
248
+ const hasBlocker = degraded.some((d) => d.impact === 'blocking');
249
+
250
+ return {
251
+ valid: missing.length === 0,
252
+ available,
253
+ missing,
254
+ degraded,
255
+ canRunPartially: !hasBlocker,
256
+ };
257
+ }
258
+
259
+ // -----------------------------------------------------------------------
260
+ // Inspection (useful for debugging / CLI commands)
261
+ // -----------------------------------------------------------------------
262
+
263
+ /**
264
+ * Total number of registered capabilities.
265
+ */
266
+ get size(): number {
267
+ return this.capabilities.size;
268
+ }
269
+
270
+ /**
271
+ * Total number of registered packs.
272
+ */
273
+ get packCount(): number {
274
+ return this.packs.size;
275
+ }
276
+
277
+ /**
278
+ * Get all registered capability IDs.
279
+ */
280
+ ids(): string[] {
281
+ return [...this.capabilities.keys()];
282
+ }
283
+
284
+ /**
285
+ * Get the RegisteredCapability for a given ID, or undefined.
286
+ */
287
+ get(capabilityId: string): RegisteredCapability | undefined {
288
+ return this.capabilities.get(capabilityId);
289
+ }
290
+ }