@deimoscloud/coreai 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/.prettierrc +9 -0
  2. package/AGENT_SPEC.md +347 -0
  3. package/ARCHITECTURE.md +547 -0
  4. package/DRAFT_PRD.md +1440 -0
  5. package/IMPLEMENTATION_PLAN.md +256 -0
  6. package/PRODUCT.md +473 -0
  7. package/README.md +303 -0
  8. package/WORKFLOWS.md +295 -0
  9. package/agents/_templates/ic-engineer.md +185 -0
  10. package/agents/_templates/reviewer.md +182 -0
  11. package/agents/backend-engineer.yaml +72 -0
  12. package/agents/devops-engineer.yaml +72 -0
  13. package/agents/engineering-manager.yaml +70 -0
  14. package/agents/examples/android-engineer.md +302 -0
  15. package/agents/examples/backend-engineer.md +320 -0
  16. package/agents/examples/devops-engineer.md +742 -0
  17. package/agents/examples/engineering-manager.md +469 -0
  18. package/agents/examples/frontend-engineer.md +58 -0
  19. package/agents/examples/product-manager.md +315 -0
  20. package/agents/examples/qa-engineer.md +371 -0
  21. package/agents/examples/security-engineer.md +525 -0
  22. package/agents/examples/solutions-architect.md +351 -0
  23. package/agents/examples/wearos-engineer.md +359 -0
  24. package/agents/frontend-engineer.yaml +72 -0
  25. package/commands/core/check-inbox.md +34 -0
  26. package/commands/core/delegate.md +30 -0
  27. package/commands/core/git-commit.md +144 -0
  28. package/commands/core/pr-create.md +193 -0
  29. package/commands/core/review.md +56 -0
  30. package/commands/core/sprint-status.md +65 -0
  31. package/commands/optional/docs-update.md +200 -0
  32. package/commands/optional/jira-create.md +200 -0
  33. package/commands/optional/jira-transition.md +184 -0
  34. package/commands/optional/worktree-cleanup.md +167 -0
  35. package/commands/optional/worktree-setup.md +110 -0
  36. package/dist/cli/index.js +4037 -0
  37. package/dist/cli/index.js.map +1 -0
  38. package/dist/index.d.ts +2978 -0
  39. package/dist/index.js +3867 -0
  40. package/dist/index.js.map +1 -0
  41. package/eslint.config.js +29 -0
  42. package/jest.config.js +22 -0
  43. package/knowledge-library/README.md +118 -0
  44. package/knowledge-library/android-engineer/context/current.txt +42 -0
  45. package/knowledge-library/android-engineer/control/decisions.txt +9 -0
  46. package/knowledge-library/android-engineer/control/dependencies.txt +19 -0
  47. package/knowledge-library/android-engineer/control/objectives.txt +26 -0
  48. package/knowledge-library/android-engineer/history/.gitkeep +0 -0
  49. package/knowledge-library/android-engineer/inbox/processed/.gitkeep +0 -0
  50. package/knowledge-library/android-engineer/outbox/.gitkeep +0 -0
  51. package/knowledge-library/android-engineer/tech/.gitkeep +0 -0
  52. package/knowledge-library/architecture.txt +61 -0
  53. package/knowledge-library/backend-engineer/context/current.txt +42 -0
  54. package/knowledge-library/backend-engineer/control/decisions.txt +9 -0
  55. package/knowledge-library/backend-engineer/control/dependencies.txt +19 -0
  56. package/knowledge-library/backend-engineer/control/objectives.txt +26 -0
  57. package/knowledge-library/backend-engineer/history/.gitkeep +0 -0
  58. package/knowledge-library/backend-engineer/inbox/processed/.gitkeep +0 -0
  59. package/knowledge-library/backend-engineer/outbox/.gitkeep +0 -0
  60. package/knowledge-library/backend-engineer/tech/.gitkeep +0 -0
  61. package/knowledge-library/context.txt +52 -0
  62. package/knowledge-library/devops-engineer/context/current.txt +42 -0
  63. package/knowledge-library/devops-engineer/control/decisions.txt +9 -0
  64. package/knowledge-library/devops-engineer/control/dependencies.txt +19 -0
  65. package/knowledge-library/devops-engineer/control/objectives.txt +26 -0
  66. package/knowledge-library/devops-engineer/history/.gitkeep +0 -0
  67. package/knowledge-library/devops-engineer/inbox/processed/.gitkeep +0 -0
  68. package/knowledge-library/devops-engineer/outbox/.gitkeep +0 -0
  69. package/knowledge-library/devops-engineer/tech/.gitkeep +0 -0
  70. package/knowledge-library/engineering-manager/context/current.txt +40 -0
  71. package/knowledge-library/engineering-manager/control/decisions.txt +9 -0
  72. package/knowledge-library/engineering-manager/control/objectives.txt +27 -0
  73. package/knowledge-library/engineering-manager/history/.gitkeep +0 -0
  74. package/knowledge-library/engineering-manager/inbox/processed/.gitkeep +0 -0
  75. package/knowledge-library/engineering-manager/outbox/.gitkeep +0 -0
  76. package/knowledge-library/engineering-manager/tech/.gitkeep +0 -0
  77. package/knowledge-library/prd.txt +81 -0
  78. package/knowledge-library/product-manager/context/current.txt +42 -0
  79. package/knowledge-library/product-manager/control/decisions.txt +9 -0
  80. package/knowledge-library/product-manager/control/dependencies.txt +19 -0
  81. package/knowledge-library/product-manager/control/objectives.txt +26 -0
  82. package/knowledge-library/product-manager/history/.gitkeep +0 -0
  83. package/knowledge-library/product-manager/inbox/processed/.gitkeep +0 -0
  84. package/knowledge-library/product-manager/outbox/.gitkeep +0 -0
  85. package/knowledge-library/product-manager/tech/.gitkeep +0 -0
  86. package/knowledge-library/qa-engineer/context/current.txt +42 -0
  87. package/knowledge-library/qa-engineer/control/decisions.txt +9 -0
  88. package/knowledge-library/qa-engineer/control/dependencies.txt +19 -0
  89. package/knowledge-library/qa-engineer/control/objectives.txt +26 -0
  90. package/knowledge-library/qa-engineer/history/.gitkeep +0 -0
  91. package/knowledge-library/qa-engineer/inbox/processed/.gitkeep +0 -0
  92. package/knowledge-library/qa-engineer/outbox/.gitkeep +0 -0
  93. package/knowledge-library/qa-engineer/tech/.gitkeep +0 -0
  94. package/knowledge-library/security-engineer/context/current.txt +42 -0
  95. package/knowledge-library/security-engineer/control/decisions.txt +9 -0
  96. package/knowledge-library/security-engineer/control/dependencies.txt +19 -0
  97. package/knowledge-library/security-engineer/control/objectives.txt +26 -0
  98. package/knowledge-library/security-engineer/history/.gitkeep +0 -0
  99. package/knowledge-library/security-engineer/inbox/processed/.gitkeep +0 -0
  100. package/knowledge-library/security-engineer/outbox/.gitkeep +0 -0
  101. package/knowledge-library/security-engineer/tech/.gitkeep +0 -0
  102. package/knowledge-library/solutions-architect/context/current.txt +42 -0
  103. package/knowledge-library/solutions-architect/control/decisions.txt +9 -0
  104. package/knowledge-library/solutions-architect/control/dependencies.txt +19 -0
  105. package/knowledge-library/solutions-architect/control/objectives.txt +26 -0
  106. package/knowledge-library/solutions-architect/history/.gitkeep +0 -0
  107. package/knowledge-library/solutions-architect/inbox/processed/.gitkeep +0 -0
  108. package/knowledge-library/solutions-architect/outbox/.gitkeep +0 -0
  109. package/knowledge-library/solutions-architect/tech/.gitkeep +0 -0
  110. package/knowledge-library/wearos-engineer/context/current.txt +42 -0
  111. package/knowledge-library/wearos-engineer/control/decisions.txt +9 -0
  112. package/knowledge-library/wearos-engineer/control/dependencies.txt +19 -0
  113. package/knowledge-library/wearos-engineer/control/objectives.txt +26 -0
  114. package/knowledge-library/wearos-engineer/history/.gitkeep +0 -0
  115. package/knowledge-library/wearos-engineer/inbox/processed/.gitkeep +0 -0
  116. package/knowledge-library/wearos-engineer/outbox/.gitkeep +0 -0
  117. package/knowledge-library/wearos-engineer/tech/.gitkeep +0 -0
  118. package/package.json +66 -0
  119. package/schemas/agent.schema.json +171 -0
  120. package/schemas/coreai.config.schema.json +257 -0
  121. package/scripts/add-agent.sh +323 -0
  122. package/scripts/install.sh +354 -0
  123. package/src/adapters/factory.test.ts +386 -0
  124. package/src/adapters/factory.ts +305 -0
  125. package/src/adapters/index.ts +113 -0
  126. package/src/adapters/interfaces.ts +268 -0
  127. package/src/adapters/mcp/client.test.ts +130 -0
  128. package/src/adapters/mcp/client.ts +451 -0
  129. package/src/adapters/mcp/discovery.test.ts +315 -0
  130. package/src/adapters/mcp/discovery.ts +340 -0
  131. package/src/adapters/mcp/index.ts +66 -0
  132. package/src/adapters/mcp/mapper.test.ts +218 -0
  133. package/src/adapters/mcp/mapper.ts +536 -0
  134. package/src/adapters/mcp/registry.test.ts +433 -0
  135. package/src/adapters/mcp/registry.ts +550 -0
  136. package/src/adapters/mcp/types.ts +258 -0
  137. package/src/adapters/native/filesystem.test.ts +350 -0
  138. package/src/adapters/native/filesystem.ts +393 -0
  139. package/src/adapters/native/github.test.ts +173 -0
  140. package/src/adapters/native/github.ts +627 -0
  141. package/src/adapters/native/index.ts +22 -0
  142. package/src/adapters/native/selector.test.ts +224 -0
  143. package/src/adapters/native/selector.ts +150 -0
  144. package/src/adapters/types.ts +270 -0
  145. package/src/agents/compiler.test.ts +399 -0
  146. package/src/agents/compiler.ts +359 -0
  147. package/src/agents/index.ts +36 -0
  148. package/src/agents/loader.test.ts +319 -0
  149. package/src/agents/loader.ts +143 -0
  150. package/src/agents/resolver.test.ts +282 -0
  151. package/src/agents/resolver.ts +262 -0
  152. package/src/agents/types.ts +87 -0
  153. package/src/cache/index.ts +38 -0
  154. package/src/cache/interfaces.ts +283 -0
  155. package/src/cache/manager.test.ts +266 -0
  156. package/src/cache/manager.ts +388 -0
  157. package/src/cache/provider.test.ts +485 -0
  158. package/src/cache/provider.ts +745 -0
  159. package/src/cache/types.test.ts +192 -0
  160. package/src/cache/types.ts +313 -0
  161. package/src/cli/commands/build.test.ts +248 -0
  162. package/src/cli/commands/build.ts +244 -0
  163. package/src/cli/commands/cache.test.ts +221 -0
  164. package/src/cli/commands/cache.ts +229 -0
  165. package/src/cli/commands/index.ts +63 -0
  166. package/src/cli/commands/init.test.ts +173 -0
  167. package/src/cli/commands/init.ts +296 -0
  168. package/src/cli/commands/skills.test.ts +272 -0
  169. package/src/cli/commands/skills.ts +348 -0
  170. package/src/cli/commands/status.test.ts +392 -0
  171. package/src/cli/commands/status.ts +332 -0
  172. package/src/cli/commands/sync.test.ts +213 -0
  173. package/src/cli/commands/sync.ts +251 -0
  174. package/src/cli/commands/validate.test.ts +216 -0
  175. package/src/cli/commands/validate.ts +340 -0
  176. package/src/cli/index.test.ts +190 -0
  177. package/src/cli/index.ts +493 -0
  178. package/src/commands/context.test.ts +163 -0
  179. package/src/commands/context.ts +111 -0
  180. package/src/commands/index.ts +56 -0
  181. package/src/commands/loader.test.ts +273 -0
  182. package/src/commands/loader.ts +355 -0
  183. package/src/commands/registry.test.ts +384 -0
  184. package/src/commands/registry.ts +248 -0
  185. package/src/commands/runner.test.ts +297 -0
  186. package/src/commands/runner.ts +222 -0
  187. package/src/commands/types.ts +361 -0
  188. package/src/config/index.ts +19 -0
  189. package/src/config/loader.test.ts +262 -0
  190. package/src/config/loader.ts +188 -0
  191. package/src/config/types.ts +154 -0
  192. package/src/context/index.ts +14 -0
  193. package/src/context/loader.test.ts +334 -0
  194. package/src/context/loader.ts +357 -0
  195. package/src/index.test.ts +13 -0
  196. package/src/index.ts +244 -0
  197. package/src/knowledge-library/index.ts +44 -0
  198. package/src/knowledge-library/manager.test.ts +536 -0
  199. package/src/knowledge-library/manager.ts +804 -0
  200. package/src/knowledge-library/types.ts +432 -0
  201. package/src/skills/generator.test.ts +602 -0
  202. package/src/skills/generator.ts +491 -0
  203. package/src/skills/index.ts +27 -0
  204. package/src/skills/templates.ts +520 -0
  205. package/src/skills/types.ts +251 -0
  206. package/templates/completion-report.md +72 -0
  207. package/templates/feedback.md +56 -0
  208. package/templates/project-files/CLAUDE.md.template +109 -0
  209. package/templates/project-files/coreai.json.example +47 -0
  210. package/templates/project-files/mcp.json.template +20 -0
  211. package/templates/review-complete.md +64 -0
  212. package/templates/review-request.md +67 -0
  213. package/templates/task-assignment.md +51 -0
  214. package/tsconfig.build.json +4 -0
  215. package/tsconfig.json +26 -0
  216. package/tsup.config.ts +23 -0
@@ -0,0 +1,386 @@
1
+ import { AdapterFactory, createAdapterFactory, createAdapterInfo } from './factory.js';
2
+ import { AdapterError } from './types.js';
3
+ import type { CoreAIConfig } from '../config/types.js';
4
+ import type { IssueTrackerAdapter, StateProviderAdapter } from './interfaces.js';
5
+ import type {
6
+ AdapterInfo,
7
+ Issue,
8
+ IssueQuery,
9
+ CreateIssueData,
10
+ UpdateIssueData,
11
+ IssueStatus,
12
+ StateEntry,
13
+ StateOptions,
14
+ } from './types.js';
15
+
16
+ // Mock adapter implementations for testing
17
+ function createMockIssueTrackerAdapter(
18
+ provider: string,
19
+ implementation: 'mcp' | 'native' | 'mock'
20
+ ): IssueTrackerAdapter {
21
+ const info: AdapterInfo = {
22
+ type: 'issue_tracker',
23
+ provider,
24
+ implementation,
25
+ connected: false,
26
+ };
27
+
28
+ return {
29
+ getInfo: () => info,
30
+ isConnected: () => info.connected,
31
+ connect: async () => {
32
+ info.connected = true;
33
+ },
34
+ disconnect: async () => {
35
+ info.connected = false;
36
+ },
37
+ getIssue: async (id: string): Promise<Issue> => ({
38
+ id,
39
+ key: `MOCK-${id}`,
40
+ title: 'Mock Issue',
41
+ status: 'todo',
42
+ }),
43
+ listIssues: async (_query?: IssueQuery): Promise<Issue[]> => [],
44
+ createIssue: async (data: CreateIssueData): Promise<Issue> => ({
45
+ id: '1',
46
+ key: 'MOCK-1',
47
+ title: data.title,
48
+ status: 'todo',
49
+ }),
50
+ updateIssue: async (id: string, data: UpdateIssueData): Promise<Issue> => ({
51
+ id,
52
+ key: `MOCK-${id}`,
53
+ title: data.title ?? 'Updated',
54
+ status: data.status ?? 'todo',
55
+ }),
56
+ transitionIssue: async (id: string, status: IssueStatus): Promise<Issue> => ({
57
+ id,
58
+ key: `MOCK-${id}`,
59
+ title: 'Mock Issue',
60
+ status,
61
+ }),
62
+ addComment: async (): Promise<void> => {
63
+ // No-op for mock
64
+ },
65
+ };
66
+ }
67
+
68
+ function createMockStateAdapter(
69
+ provider: string,
70
+ implementation: 'mcp' | 'native' | 'mock'
71
+ ): StateProviderAdapter {
72
+ const info: AdapterInfo = {
73
+ type: 'state',
74
+ provider,
75
+ implementation,
76
+ connected: false,
77
+ };
78
+
79
+ const storage = new Map<string, string>();
80
+
81
+ return {
82
+ getInfo: () => info,
83
+ isConnected: () => info.connected,
84
+ connect: async () => {
85
+ info.connected = true;
86
+ },
87
+ disconnect: async () => {
88
+ info.connected = false;
89
+ },
90
+ read: async (path: string): Promise<string> => {
91
+ const content = storage.get(path);
92
+ if (!content) throw new Error('Not found');
93
+ return content;
94
+ },
95
+ write: async (path: string, content: string): Promise<void> => {
96
+ storage.set(path, content);
97
+ },
98
+ exists: async (path: string): Promise<boolean> => storage.has(path),
99
+ list: async (_path: string): Promise<StateEntry[]> => [],
100
+ delete: async (path: string, _options?: StateOptions): Promise<void> => {
101
+ storage.delete(path);
102
+ },
103
+ };
104
+ }
105
+
106
+ const mockConfig: CoreAIConfig = {
107
+ version: '1.0',
108
+ project: {
109
+ name: 'test-project',
110
+ },
111
+ integrations: {
112
+ issue_tracker: {
113
+ provider: 'jira',
114
+ config: {
115
+ project_key: 'TEST',
116
+ base_url: 'https://jira.example.com',
117
+ },
118
+ strategy: 'auto',
119
+ },
120
+ git: {
121
+ provider: 'github',
122
+ config: {
123
+ repo: 'org/repo',
124
+ },
125
+ strategy: 'native',
126
+ },
127
+ state: {
128
+ provider: 'local',
129
+ config: {
130
+ base_path: './KnowledgeLibrary',
131
+ },
132
+ },
133
+ },
134
+ };
135
+
136
+ describe('AdapterFactory', () => {
137
+ describe('createAdapterFactory', () => {
138
+ it('should create a factory from config', () => {
139
+ const factory = createAdapterFactory(mockConfig);
140
+ expect(factory).toBeInstanceOf(AdapterFactory);
141
+ });
142
+ });
143
+
144
+ describe('createAdapterInfo', () => {
145
+ it('should create adapter info with defaults', () => {
146
+ const info = createAdapterInfo('issue_tracker', 'jira', 'mcp');
147
+ expect(info).toEqual({
148
+ type: 'issue_tracker',
149
+ provider: 'jira',
150
+ implementation: 'mcp',
151
+ connected: false,
152
+ });
153
+ });
154
+
155
+ it('should create adapter info with connected state', () => {
156
+ const info = createAdapterInfo('git', 'github', 'native', true);
157
+ expect(info.connected).toBe(true);
158
+ });
159
+ });
160
+
161
+ describe('hasIntegration', () => {
162
+ it('should return true for configured integrations', () => {
163
+ const factory = createAdapterFactory(mockConfig);
164
+ expect(factory.hasIntegration('issue_tracker')).toBe(true);
165
+ expect(factory.hasIntegration('git')).toBe(true);
166
+ expect(factory.hasIntegration('state')).toBe(true);
167
+ });
168
+
169
+ it('should return false for unconfigured integrations', () => {
170
+ const factory = createAdapterFactory(mockConfig);
171
+ expect(factory.hasIntegration('documentation')).toBe(false);
172
+ });
173
+
174
+ it('should return false when no integrations configured', () => {
175
+ const factory = createAdapterFactory({
176
+ version: '1.0',
177
+ project: { name: 'test' },
178
+ });
179
+ expect(factory.hasIntegration('issue_tracker')).toBe(false);
180
+ });
181
+ });
182
+
183
+ describe('getIntegrationInfo', () => {
184
+ it('should return provider and strategy for configured integration', () => {
185
+ const factory = createAdapterFactory(mockConfig);
186
+ const info = factory.getIntegrationInfo('issue_tracker');
187
+ expect(info).toEqual({
188
+ provider: 'jira',
189
+ strategy: 'auto',
190
+ });
191
+ });
192
+
193
+ it('should return strategy from config', () => {
194
+ const factory = createAdapterFactory(mockConfig);
195
+ const info = factory.getIntegrationInfo('git');
196
+ expect(info?.strategy).toBe('native');
197
+ });
198
+
199
+ it('should default strategy to auto', () => {
200
+ const factory = createAdapterFactory(mockConfig);
201
+ const info = factory.getIntegrationInfo('state');
202
+ expect(info?.strategy).toBe('auto');
203
+ });
204
+
205
+ it('should return null for unconfigured integration', () => {
206
+ const factory = createAdapterFactory(mockConfig);
207
+ expect(factory.getIntegrationInfo('documentation')).toBeNull();
208
+ });
209
+ });
210
+
211
+ describe('registerCreator', () => {
212
+ it('should register and use MCP creator', async () => {
213
+ const factory = createAdapterFactory(mockConfig);
214
+
215
+ factory.registerCreator('issue_tracker', 'mcp', async () =>
216
+ createMockIssueTrackerAdapter('jira', 'mcp')
217
+ );
218
+
219
+ const adapter = await factory.getIssueTracker({ implementation: 'mcp' });
220
+ const info = adapter.getInfo();
221
+
222
+ expect(info.type).toBe('issue_tracker');
223
+ expect(info.provider).toBe('jira');
224
+ expect(info.implementation).toBe('mcp');
225
+ expect(adapter.isConnected()).toBe(true);
226
+ });
227
+
228
+ it('should register and use native creator', async () => {
229
+ const factory = createAdapterFactory(mockConfig);
230
+
231
+ factory.registerCreator('issue_tracker', 'native', async () =>
232
+ createMockIssueTrackerAdapter('jira', 'native')
233
+ );
234
+
235
+ const adapter = await factory.getIssueTracker({ implementation: 'native' });
236
+ expect(adapter.getInfo().implementation).toBe('native');
237
+ });
238
+ });
239
+
240
+ describe('getAdapter', () => {
241
+ it('should cache adapters', async () => {
242
+ const factory = createAdapterFactory(mockConfig);
243
+
244
+ let callCount = 0;
245
+ factory.registerCreator('issue_tracker', 'mcp', async () => {
246
+ callCount++;
247
+ return createMockIssueTrackerAdapter('jira', 'mcp');
248
+ });
249
+
250
+ const adapter1 = await factory.getIssueTracker();
251
+ const adapter2 = await factory.getIssueTracker();
252
+
253
+ expect(adapter1).toBe(adapter2);
254
+ expect(callCount).toBe(1);
255
+ });
256
+
257
+ it('should throw for missing implementation', async () => {
258
+ const factory = createAdapterFactory(mockConfig);
259
+
260
+ await expect(factory.getIssueTracker()).rejects.toThrow(AdapterError);
261
+ });
262
+
263
+ it('should select MCP over native in auto mode', async () => {
264
+ const factory = createAdapterFactory(mockConfig);
265
+
266
+ factory.registerCreator('issue_tracker', 'mcp', async () =>
267
+ createMockIssueTrackerAdapter('jira', 'mcp')
268
+ );
269
+ factory.registerCreator('issue_tracker', 'native', async () =>
270
+ createMockIssueTrackerAdapter('jira', 'native')
271
+ );
272
+
273
+ const adapter = await factory.getIssueTracker();
274
+ expect(adapter.getInfo().implementation).toBe('mcp');
275
+ });
276
+
277
+ it('should fall back to native when MCP unavailable', async () => {
278
+ const factory = createAdapterFactory(mockConfig);
279
+
280
+ factory.registerCreator('issue_tracker', 'native', async () =>
281
+ createMockIssueTrackerAdapter('jira', 'native')
282
+ );
283
+
284
+ const adapter = await factory.getIssueTracker();
285
+ expect(adapter.getInfo().implementation).toBe('native');
286
+ });
287
+
288
+ it('should respect strategy override in options', async () => {
289
+ const factory = createAdapterFactory(mockConfig);
290
+
291
+ factory.registerCreator('issue_tracker', 'mcp', async () =>
292
+ createMockIssueTrackerAdapter('jira', 'mcp')
293
+ );
294
+ factory.registerCreator('issue_tracker', 'native', async () =>
295
+ createMockIssueTrackerAdapter('jira', 'native')
296
+ );
297
+
298
+ const adapter = await factory.getIssueTracker({ strategy: 'native' });
299
+ expect(adapter.getInfo().implementation).toBe('native');
300
+ });
301
+
302
+ it('should throw when requested strategy is unavailable', async () => {
303
+ const factory = createAdapterFactory(mockConfig);
304
+
305
+ factory.registerCreator('issue_tracker', 'native', async () =>
306
+ createMockIssueTrackerAdapter('jira', 'native')
307
+ );
308
+
309
+ await expect(factory.getIssueTracker({ strategy: 'mcp' })).rejects.toThrow(
310
+ 'MCP adapter not available'
311
+ );
312
+ });
313
+ });
314
+
315
+ describe('typed adapter getters', () => {
316
+ it('should get issue tracker adapter', async () => {
317
+ const factory = createAdapterFactory(mockConfig);
318
+ factory.registerCreator('issue_tracker', 'mock', async () =>
319
+ createMockIssueTrackerAdapter('jira', 'mock')
320
+ );
321
+
322
+ const adapter = await factory.getIssueTracker();
323
+ expect(adapter.getInfo().type).toBe('issue_tracker');
324
+
325
+ // Test that interface methods exist
326
+ const issue = await adapter.getIssue('123');
327
+ expect(issue.key).toBe('MOCK-123');
328
+ });
329
+
330
+ it('should get state provider adapter', async () => {
331
+ const factory = createAdapterFactory(mockConfig);
332
+ factory.registerCreator('state', 'mock', async () => createMockStateAdapter('local', 'mock'));
333
+
334
+ const adapter = await factory.getStateProvider();
335
+ expect(adapter.getInfo().type).toBe('state');
336
+
337
+ // Test that interface methods work
338
+ await adapter.write('/test.txt', 'hello');
339
+ const content = await adapter.read('/test.txt');
340
+ expect(content).toBe('hello');
341
+ });
342
+ });
343
+
344
+ describe('disconnectAll', () => {
345
+ it('should disconnect all cached adapters', async () => {
346
+ const factory = createAdapterFactory(mockConfig);
347
+
348
+ factory.registerCreator('issue_tracker', 'mock', async () =>
349
+ createMockIssueTrackerAdapter('jira', 'mock')
350
+ );
351
+ factory.registerCreator('state', 'mock', async () => createMockStateAdapter('local', 'mock'));
352
+
353
+ const issueTracker = await factory.getIssueTracker();
354
+ const stateProvider = await factory.getStateProvider();
355
+
356
+ expect(issueTracker.isConnected()).toBe(true);
357
+ expect(stateProvider.isConnected()).toBe(true);
358
+
359
+ await factory.disconnectAll();
360
+
361
+ expect(issueTracker.isConnected()).toBe(false);
362
+ expect(stateProvider.isConnected()).toBe(false);
363
+ });
364
+ });
365
+ });
366
+
367
+ describe('AdapterError', () => {
368
+ it('should create error with message and code', () => {
369
+ const error = new AdapterError('Something went wrong', 'network_error');
370
+ expect(error.message).toBe('Something went wrong');
371
+ expect(error.code).toBe('network_error');
372
+ expect(error.name).toBe('AdapterError');
373
+ });
374
+
375
+ it('should include adapter info', () => {
376
+ const info = createAdapterInfo('git', 'github', 'native', true);
377
+ const error = new AdapterError('Failed', 'provider_error', info);
378
+ expect(error.adapter).toEqual(info);
379
+ });
380
+
381
+ it('should include cause', () => {
382
+ const cause = new Error('Original error');
383
+ const error = new AdapterError('Wrapped error', 'network_error', undefined, cause);
384
+ expect(error.cause).toBe(cause);
385
+ });
386
+ });
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Adapter Factory
3
+ *
4
+ * Creates and manages adapter instances based on configuration.
5
+ */
6
+
7
+ import type { CoreAIConfig, AdapterStrategy } from '../config/types.js';
8
+ import type { AdapterType, AdapterInfo, AdapterImplementation } from './types.js';
9
+ import { AdapterError } from './types.js';
10
+ import type {
11
+ IssueTrackerAdapter,
12
+ GitProviderAdapter,
13
+ DocumentationProviderAdapter,
14
+ StateProviderAdapter,
15
+ Adapter,
16
+ } from './interfaces.js';
17
+
18
+ /**
19
+ * Options for creating an adapter
20
+ */
21
+ export interface AdapterFactoryOptions {
22
+ /**
23
+ * Override the strategy from config
24
+ */
25
+ strategy?: AdapterStrategy;
26
+
27
+ /**
28
+ * Force a specific implementation
29
+ */
30
+ implementation?: AdapterImplementation;
31
+ }
32
+
33
+ /**
34
+ * Registry of adapter creators
35
+ */
36
+ interface AdapterCreator<T extends Adapter> {
37
+ mcp?: (config: CoreAIConfig) => Promise<T>;
38
+ native?: (config: CoreAIConfig) => Promise<T>;
39
+ mock?: (config: CoreAIConfig) => Promise<T>;
40
+ }
41
+
42
+ /**
43
+ * Adapter Factory
44
+ *
45
+ * Creates adapter instances based on configuration and available implementations.
46
+ */
47
+ export class AdapterFactory {
48
+ private config: CoreAIConfig;
49
+ private adapters = new Map<AdapterType, Adapter>();
50
+
51
+ /**
52
+ * Registry of adapter creators by type
53
+ */
54
+ private creators: {
55
+ issue_tracker: AdapterCreator<IssueTrackerAdapter>;
56
+ git: AdapterCreator<GitProviderAdapter>;
57
+ documentation: AdapterCreator<DocumentationProviderAdapter>;
58
+ state: AdapterCreator<StateProviderAdapter>;
59
+ } = {
60
+ issue_tracker: {},
61
+ git: {},
62
+ documentation: {},
63
+ state: {},
64
+ };
65
+
66
+ constructor(config: CoreAIConfig) {
67
+ this.config = config;
68
+ }
69
+
70
+ /**
71
+ * Register an adapter creator
72
+ */
73
+ registerCreator<T extends AdapterType>(
74
+ type: T,
75
+ implementation: AdapterImplementation,
76
+ creator: (config: CoreAIConfig) => Promise<Adapter>
77
+ ): void {
78
+ const typeCreators = this.creators[type] as AdapterCreator<Adapter>;
79
+ typeCreators[implementation] = creator;
80
+ }
81
+
82
+ /**
83
+ * Get an issue tracker adapter
84
+ */
85
+ async getIssueTracker(options?: AdapterFactoryOptions): Promise<IssueTrackerAdapter> {
86
+ return this.getAdapter('issue_tracker', options) as Promise<IssueTrackerAdapter>;
87
+ }
88
+
89
+ /**
90
+ * Get a git provider adapter
91
+ */
92
+ async getGitProvider(options?: AdapterFactoryOptions): Promise<GitProviderAdapter> {
93
+ return this.getAdapter('git', options) as Promise<GitProviderAdapter>;
94
+ }
95
+
96
+ /**
97
+ * Get a documentation provider adapter
98
+ */
99
+ async getDocumentationProvider(
100
+ options?: AdapterFactoryOptions
101
+ ): Promise<DocumentationProviderAdapter> {
102
+ return this.getAdapter('documentation', options) as Promise<DocumentationProviderAdapter>;
103
+ }
104
+
105
+ /**
106
+ * Get a state provider adapter
107
+ */
108
+ async getStateProvider(options?: AdapterFactoryOptions): Promise<StateProviderAdapter> {
109
+ return this.getAdapter('state', options) as Promise<StateProviderAdapter>;
110
+ }
111
+
112
+ /**
113
+ * Get an adapter by type
114
+ */
115
+ async getAdapter(type: AdapterType, options?: AdapterFactoryOptions): Promise<Adapter> {
116
+ // Check if we already have this adapter cached
117
+ const cached = this.adapters.get(type);
118
+ if (cached && cached.isConnected()) {
119
+ return cached;
120
+ }
121
+
122
+ // Determine the strategy
123
+ const strategy = this.getStrategy(type, options);
124
+
125
+ // Get the implementation to use
126
+ const implementation = options?.implementation ?? this.selectImplementation(type, strategy);
127
+
128
+ // Create the adapter
129
+ const adapter = await this.createAdapter(type, implementation);
130
+
131
+ // Cache it
132
+ this.adapters.set(type, adapter);
133
+
134
+ return adapter;
135
+ }
136
+
137
+ /**
138
+ * Check if an adapter type is configured
139
+ */
140
+ hasIntegration(type: AdapterType): boolean {
141
+ const integrations = this.config.integrations;
142
+ if (!integrations) return false;
143
+
144
+ switch (type) {
145
+ case 'issue_tracker':
146
+ return !!integrations.issue_tracker?.provider;
147
+ case 'git':
148
+ return !!integrations.git?.provider;
149
+ case 'documentation':
150
+ return !!integrations.documentation?.provider;
151
+ case 'state':
152
+ return !!integrations.state?.provider;
153
+ default:
154
+ return false;
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Get information about a configured integration
160
+ */
161
+ getIntegrationInfo(type: AdapterType): { provider: string; strategy: AdapterStrategy } | null {
162
+ const integrations = this.config.integrations;
163
+ if (!integrations) return null;
164
+
165
+ let integration;
166
+ switch (type) {
167
+ case 'issue_tracker':
168
+ integration = integrations.issue_tracker;
169
+ break;
170
+ case 'git':
171
+ integration = integrations.git;
172
+ break;
173
+ case 'documentation':
174
+ integration = integrations.documentation;
175
+ break;
176
+ case 'state':
177
+ integration = integrations.state;
178
+ break;
179
+ }
180
+
181
+ if (!integration?.provider) return null;
182
+
183
+ return {
184
+ provider: integration.provider,
185
+ strategy: integration.strategy ?? 'auto',
186
+ };
187
+ }
188
+
189
+ /**
190
+ * Disconnect and clear all cached adapters
191
+ */
192
+ async disconnectAll(): Promise<void> {
193
+ const disconnects: Promise<void>[] = [];
194
+
195
+ for (const adapter of this.adapters.values()) {
196
+ if (adapter.isConnected()) {
197
+ disconnects.push(adapter.disconnect());
198
+ }
199
+ }
200
+
201
+ await Promise.all(disconnects);
202
+ this.adapters.clear();
203
+ }
204
+
205
+ /**
206
+ * Get the strategy for an adapter type
207
+ */
208
+ private getStrategy(type: AdapterType, options?: AdapterFactoryOptions): AdapterStrategy {
209
+ if (options?.strategy) {
210
+ return options.strategy;
211
+ }
212
+
213
+ const info = this.getIntegrationInfo(type);
214
+ return info?.strategy ?? 'auto';
215
+ }
216
+
217
+ /**
218
+ * Select the implementation based on strategy and availability
219
+ */
220
+ private selectImplementation(
221
+ type: AdapterType,
222
+ strategy: AdapterStrategy
223
+ ): AdapterImplementation {
224
+ const creators = this.creators[type];
225
+
226
+ switch (strategy) {
227
+ case 'mcp':
228
+ if (creators.mcp) return 'mcp';
229
+ throw new AdapterError(
230
+ `MCP adapter not available for ${type}`,
231
+ 'not_implemented',
232
+ undefined
233
+ );
234
+
235
+ case 'native':
236
+ if (creators.native) return 'native';
237
+ throw new AdapterError(
238
+ `Native adapter not available for ${type}`,
239
+ 'not_implemented',
240
+ undefined
241
+ );
242
+
243
+ case 'auto':
244
+ default:
245
+ // Prefer MCP, fall back to native
246
+ if (creators.mcp) return 'mcp';
247
+ if (creators.native) return 'native';
248
+ if (creators.mock) return 'mock';
249
+
250
+ throw new AdapterError(
251
+ `No adapter implementation available for ${type}`,
252
+ 'not_implemented',
253
+ undefined
254
+ );
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Create an adapter instance
260
+ */
261
+ private async createAdapter(
262
+ type: AdapterType,
263
+ implementation: AdapterImplementation
264
+ ): Promise<Adapter> {
265
+ const creators = this.creators[type];
266
+ const creator = creators[implementation];
267
+
268
+ if (!creator) {
269
+ throw new AdapterError(
270
+ `No ${implementation} adapter creator registered for ${type}`,
271
+ 'not_implemented',
272
+ undefined
273
+ );
274
+ }
275
+
276
+ const adapter = await creator(this.config);
277
+ await adapter.connect();
278
+
279
+ return adapter;
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Create an adapter factory from configuration
285
+ */
286
+ export function createAdapterFactory(config: CoreAIConfig): AdapterFactory {
287
+ return new AdapterFactory(config);
288
+ }
289
+
290
+ /**
291
+ * Create adapter info helper
292
+ */
293
+ export function createAdapterInfo(
294
+ type: AdapterType,
295
+ provider: string,
296
+ implementation: AdapterImplementation,
297
+ connected = false
298
+ ): AdapterInfo {
299
+ return {
300
+ type,
301
+ provider,
302
+ implementation,
303
+ connected,
304
+ };
305
+ }