@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,258 @@
1
+ /**
2
+ * MCP Integration Types
3
+ *
4
+ * Types for MCP server configuration and integration.
5
+ */
6
+
7
+ /**
8
+ * Transport type for MCP servers
9
+ */
10
+ export type McpTransportType = 'stdio' | 'http' | 'sse';
11
+
12
+ /**
13
+ * Configuration for a stdio-based MCP server
14
+ */
15
+ export interface StdioServerConfig {
16
+ transport: 'stdio';
17
+ command: string;
18
+ args?: string[];
19
+ env?: Record<string, string>;
20
+ cwd?: string;
21
+ }
22
+
23
+ /**
24
+ * Configuration for an HTTP-based MCP server
25
+ */
26
+ export interface HttpServerConfig {
27
+ transport: 'http' | 'sse';
28
+ url: string;
29
+ headers?: Record<string, string>;
30
+ }
31
+
32
+ /**
33
+ * Union type for all MCP server configurations
34
+ */
35
+ export type McpServerConfig = StdioServerConfig | HttpServerConfig;
36
+
37
+ /**
38
+ * MCP server definition from config
39
+ */
40
+ export interface McpServerDefinition {
41
+ /**
42
+ * Unique name for this server
43
+ */
44
+ name: string;
45
+
46
+ /**
47
+ * Server configuration
48
+ */
49
+ config: McpServerConfig;
50
+
51
+ /**
52
+ * Whether this server is enabled
53
+ */
54
+ enabled?: boolean;
55
+
56
+ /**
57
+ * Description of what this server provides
58
+ */
59
+ description?: string;
60
+ }
61
+
62
+ /**
63
+ * MCP config file structure (e.g., mcp.json)
64
+ */
65
+ export interface McpConfigFile {
66
+ /**
67
+ * MCP servers to connect to
68
+ */
69
+ mcpServers?: Record<string, McpServerConfig>;
70
+ }
71
+
72
+ /**
73
+ * Information about a connected MCP server
74
+ */
75
+ export interface McpServerInfo {
76
+ /**
77
+ * Server name
78
+ */
79
+ name: string;
80
+
81
+ /**
82
+ * Server version (if available)
83
+ */
84
+ version?: string;
85
+
86
+ /**
87
+ * Protocol version
88
+ */
89
+ protocolVersion?: string;
90
+
91
+ /**
92
+ * Server capabilities
93
+ */
94
+ capabilities?: McpCapabilities;
95
+
96
+ /**
97
+ * Server instructions (if any)
98
+ */
99
+ instructions?: string;
100
+
101
+ /**
102
+ * Whether the server is connected
103
+ */
104
+ connected: boolean;
105
+ }
106
+
107
+ /**
108
+ * MCP server capabilities
109
+ */
110
+ export interface McpCapabilities {
111
+ tools?: boolean;
112
+ resources?: boolean;
113
+ prompts?: boolean;
114
+ logging?: boolean;
115
+ }
116
+
117
+ /**
118
+ * MCP tool definition
119
+ */
120
+ export interface McpTool {
121
+ /**
122
+ * Tool name
123
+ */
124
+ name: string;
125
+
126
+ /**
127
+ * Tool description
128
+ */
129
+ description?: string;
130
+
131
+ /**
132
+ * Input schema for the tool
133
+ */
134
+ inputSchema: {
135
+ type: 'object';
136
+ properties?: Record<string, unknown>;
137
+ required?: string[];
138
+ };
139
+
140
+ /**
141
+ * Tool annotations
142
+ */
143
+ annotations?: {
144
+ title?: string;
145
+ readOnlyHint?: boolean;
146
+ destructiveHint?: boolean;
147
+ idempotentHint?: boolean;
148
+ };
149
+ }
150
+
151
+ /**
152
+ * MCP resource definition
153
+ */
154
+ export interface McpResource {
155
+ /**
156
+ * Resource URI
157
+ */
158
+ uri: string;
159
+
160
+ /**
161
+ * Resource name
162
+ */
163
+ name: string;
164
+
165
+ /**
166
+ * Resource description
167
+ */
168
+ description?: string;
169
+
170
+ /**
171
+ * MIME type
172
+ */
173
+ mimeType?: string;
174
+ }
175
+
176
+ /**
177
+ * Result of calling an MCP tool
178
+ */
179
+ export interface McpToolResult {
180
+ /**
181
+ * Tool output content
182
+ */
183
+ content: McpContent[];
184
+
185
+ /**
186
+ * Whether the tool call resulted in an error
187
+ */
188
+ isError?: boolean;
189
+
190
+ /**
191
+ * Structured content (if tool has outputSchema)
192
+ */
193
+ structuredContent?: Record<string, unknown>;
194
+ }
195
+
196
+ /**
197
+ * Content returned by MCP tools and resources
198
+ */
199
+ export type McpContent = McpTextContent | McpImageContent | McpResourceContent;
200
+
201
+ /**
202
+ * Text content
203
+ */
204
+ export interface McpTextContent {
205
+ type: 'text';
206
+ text: string;
207
+ }
208
+
209
+ /**
210
+ * Image content
211
+ */
212
+ export interface McpImageContent {
213
+ type: 'image';
214
+ data: string;
215
+ mimeType: string;
216
+ }
217
+
218
+ /**
219
+ * Embedded resource content
220
+ */
221
+ export interface McpResourceContent {
222
+ type: 'resource';
223
+ resource: {
224
+ uri: string;
225
+ text?: string;
226
+ blob?: string;
227
+ mimeType?: string;
228
+ };
229
+ }
230
+
231
+ /**
232
+ * Error from MCP operations
233
+ */
234
+ export class McpError extends Error {
235
+ constructor(
236
+ message: string,
237
+ public readonly code: McpErrorCode,
238
+ public readonly server?: string,
239
+ cause?: Error
240
+ ) {
241
+ super(message, { cause });
242
+ this.name = 'McpError';
243
+ }
244
+ }
245
+
246
+ /**
247
+ * MCP error codes
248
+ */
249
+ export type McpErrorCode =
250
+ | 'connection_failed'
251
+ | 'connection_closed'
252
+ | 'server_not_found'
253
+ | 'tool_not_found'
254
+ | 'resource_not_found'
255
+ | 'invalid_config'
256
+ | 'transport_error'
257
+ | 'protocol_error'
258
+ | 'timeout';
@@ -0,0 +1,350 @@
1
+ import { mkdtempSync, rmSync, writeFileSync, mkdirSync, readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { tmpdir } from 'os';
4
+ import { FilesystemAdapter, createFilesystemAdapter } from './filesystem.js';
5
+ import { AdapterError } from '../types.js';
6
+
7
+ describe('FilesystemAdapter', () => {
8
+ let tempDir: string;
9
+ let adapter: FilesystemAdapter;
10
+
11
+ beforeEach(() => {
12
+ tempDir = mkdtempSync(join(tmpdir(), 'filesystem-adapter-test-'));
13
+ adapter = createFilesystemAdapter({ basePath: tempDir });
14
+ });
15
+
16
+ afterEach(() => {
17
+ rmSync(tempDir, { recursive: true, force: true });
18
+ });
19
+
20
+ describe('constructor', () => {
21
+ it('should create adapter with base path', () => {
22
+ const info = adapter.getInfo();
23
+ expect(info.type).toBe('state');
24
+ expect(info.provider).toBe('filesystem');
25
+ expect(info.implementation).toBe('native');
26
+ expect(info.connected).toBe(false);
27
+ });
28
+ });
29
+
30
+ describe('connect', () => {
31
+ it('should connect when base path exists', async () => {
32
+ await adapter.connect();
33
+ expect(adapter.isConnected()).toBe(true);
34
+ });
35
+
36
+ it('should create base path if it does not exist', async () => {
37
+ const newPath = join(tempDir, 'new-dir');
38
+ const newAdapter = createFilesystemAdapter({ basePath: newPath });
39
+
40
+ await newAdapter.connect();
41
+ expect(newAdapter.isConnected()).toBe(true);
42
+ });
43
+
44
+ it('should throw if base path is a file', async () => {
45
+ const filePath = join(tempDir, 'file.txt');
46
+ writeFileSync(filePath, 'content');
47
+
48
+ const fileAdapter = createFilesystemAdapter({ basePath: filePath });
49
+ await expect(fileAdapter.connect()).rejects.toThrow(AdapterError);
50
+ });
51
+ });
52
+
53
+ describe('disconnect', () => {
54
+ it('should disconnect', async () => {
55
+ await adapter.connect();
56
+ await adapter.disconnect();
57
+ expect(adapter.isConnected()).toBe(false);
58
+ });
59
+ });
60
+
61
+ describe('read', () => {
62
+ beforeEach(async () => {
63
+ await adapter.connect();
64
+ });
65
+
66
+ it('should read file content', async () => {
67
+ writeFileSync(join(tempDir, 'test.txt'), 'hello world');
68
+
69
+ const content = await adapter.read('test.txt');
70
+ expect(content).toBe('hello world');
71
+ });
72
+
73
+ it('should read file from subdirectory', async () => {
74
+ mkdirSync(join(tempDir, 'subdir'));
75
+ writeFileSync(join(tempDir, 'subdir', 'file.txt'), 'nested content');
76
+
77
+ const content = await adapter.read('subdir/file.txt');
78
+ expect(content).toBe('nested content');
79
+ });
80
+
81
+ it('should throw for non-existent file', async () => {
82
+ await expect(adapter.read('nonexistent.txt')).rejects.toThrow(AdapterError);
83
+ });
84
+
85
+ it('should throw if not connected', async () => {
86
+ await adapter.disconnect();
87
+ await expect(adapter.read('test.txt')).rejects.toThrow(AdapterError);
88
+ });
89
+ });
90
+
91
+ describe('write', () => {
92
+ beforeEach(async () => {
93
+ await adapter.connect();
94
+ });
95
+
96
+ it('should write file content', async () => {
97
+ await adapter.write('output.txt', 'new content');
98
+
99
+ const content = readFileSync(join(tempDir, 'output.txt'), 'utf-8');
100
+ expect(content).toBe('new content');
101
+ });
102
+
103
+ it('should overwrite existing file', async () => {
104
+ writeFileSync(join(tempDir, 'existing.txt'), 'old content');
105
+
106
+ await adapter.write('existing.txt', 'new content');
107
+
108
+ const content = readFileSync(join(tempDir, 'existing.txt'), 'utf-8');
109
+ expect(content).toBe('new content');
110
+ });
111
+
112
+ it('should create parent directories with recursive option', async () => {
113
+ await adapter.write('deep/nested/file.txt', 'content', { recursive: true });
114
+
115
+ const content = readFileSync(join(tempDir, 'deep/nested/file.txt'), 'utf-8');
116
+ expect(content).toBe('content');
117
+ });
118
+
119
+ it('should throw for non-existent parent without recursive', async () => {
120
+ await expect(adapter.write('nonexistent/file.txt', 'content')).rejects.toThrow(AdapterError);
121
+ });
122
+ });
123
+
124
+ describe('exists', () => {
125
+ beforeEach(async () => {
126
+ await adapter.connect();
127
+ });
128
+
129
+ it('should return true for existing file', async () => {
130
+ writeFileSync(join(tempDir, 'exists.txt'), 'content');
131
+
132
+ const exists = await adapter.exists('exists.txt');
133
+ expect(exists).toBe(true);
134
+ });
135
+
136
+ it('should return true for existing directory', async () => {
137
+ mkdirSync(join(tempDir, 'subdir'));
138
+
139
+ const exists = await adapter.exists('subdir');
140
+ expect(exists).toBe(true);
141
+ });
142
+
143
+ it('should return false for non-existent path', async () => {
144
+ const exists = await adapter.exists('nonexistent');
145
+ expect(exists).toBe(false);
146
+ });
147
+ });
148
+
149
+ describe('list', () => {
150
+ beforeEach(async () => {
151
+ await adapter.connect();
152
+ });
153
+
154
+ it('should list directory contents', async () => {
155
+ writeFileSync(join(tempDir, 'file1.txt'), 'content1');
156
+ writeFileSync(join(tempDir, 'file2.txt'), 'content2');
157
+ mkdirSync(join(tempDir, 'subdir'));
158
+
159
+ const entries = await adapter.list('.');
160
+ expect(entries.length).toBe(3);
161
+
162
+ const names = entries.map((e) => e.name).sort();
163
+ expect(names).toEqual(['file1.txt', 'file2.txt', 'subdir']);
164
+
165
+ const file1 = entries.find((e) => e.name === 'file1.txt');
166
+ expect(file1?.type).toBe('file');
167
+ expect(file1?.size).toBeDefined();
168
+
169
+ const subdir = entries.find((e) => e.name === 'subdir');
170
+ expect(subdir?.type).toBe('directory');
171
+ });
172
+
173
+ it('should throw for non-existent directory', async () => {
174
+ await expect(adapter.list('nonexistent')).rejects.toThrow(AdapterError);
175
+ });
176
+
177
+ it('should throw if path is a file', async () => {
178
+ writeFileSync(join(tempDir, 'file.txt'), 'content');
179
+ await expect(adapter.list('file.txt')).rejects.toThrow(AdapterError);
180
+ });
181
+ });
182
+
183
+ describe('delete', () => {
184
+ beforeEach(async () => {
185
+ await adapter.connect();
186
+ });
187
+
188
+ it('should delete a file', async () => {
189
+ writeFileSync(join(tempDir, 'to-delete.txt'), 'content');
190
+
191
+ await adapter.delete('to-delete.txt');
192
+
193
+ const exists = await adapter.exists('to-delete.txt');
194
+ expect(exists).toBe(false);
195
+ });
196
+
197
+ it('should delete empty directory', async () => {
198
+ mkdirSync(join(tempDir, 'empty-dir'));
199
+
200
+ await adapter.delete('empty-dir');
201
+
202
+ const exists = await adapter.exists('empty-dir');
203
+ expect(exists).toBe(false);
204
+ });
205
+
206
+ it('should delete non-empty directory with recursive', async () => {
207
+ mkdirSync(join(tempDir, 'non-empty'));
208
+ writeFileSync(join(tempDir, 'non-empty', 'file.txt'), 'content');
209
+
210
+ await adapter.delete('non-empty', { recursive: true });
211
+
212
+ const exists = await adapter.exists('non-empty');
213
+ expect(exists).toBe(false);
214
+ });
215
+
216
+ it('should throw for non-empty directory without recursive', async () => {
217
+ mkdirSync(join(tempDir, 'non-empty'));
218
+ writeFileSync(join(tempDir, 'non-empty', 'file.txt'), 'content');
219
+
220
+ await expect(adapter.delete('non-empty')).rejects.toThrow(AdapterError);
221
+ });
222
+
223
+ it('should not throw for non-existent path', async () => {
224
+ await expect(adapter.delete('nonexistent')).resolves.toBeUndefined();
225
+ });
226
+ });
227
+
228
+ describe('mkdir', () => {
229
+ beforeEach(async () => {
230
+ await adapter.connect();
231
+ });
232
+
233
+ it('should create directory', async () => {
234
+ await adapter.mkdir('new-dir');
235
+
236
+ const exists = await adapter.exists('new-dir');
237
+ expect(exists).toBe(true);
238
+ });
239
+
240
+ it('should create nested directories with recursive', async () => {
241
+ await adapter.mkdir('a/b/c', { recursive: true });
242
+
243
+ const exists = await adapter.exists('a/b/c');
244
+ expect(exists).toBe(true);
245
+ });
246
+
247
+ it('should not throw if directory already exists', async () => {
248
+ mkdirSync(join(tempDir, 'existing-dir'));
249
+
250
+ await expect(adapter.mkdir('existing-dir')).resolves.toBeUndefined();
251
+ });
252
+
253
+ it('should throw for nested path without recursive', async () => {
254
+ await expect(adapter.mkdir('a/b/c')).rejects.toThrow(AdapterError);
255
+ });
256
+ });
257
+
258
+ describe('move', () => {
259
+ beforeEach(async () => {
260
+ await adapter.connect();
261
+ });
262
+
263
+ it('should move/rename a file', async () => {
264
+ writeFileSync(join(tempDir, 'source.txt'), 'content');
265
+
266
+ await adapter.move('source.txt', 'dest.txt');
267
+
268
+ expect(await adapter.exists('source.txt')).toBe(false);
269
+ expect(await adapter.exists('dest.txt')).toBe(true);
270
+
271
+ const content = await adapter.read('dest.txt');
272
+ expect(content).toBe('content');
273
+ });
274
+
275
+ it('should move/rename a directory', async () => {
276
+ mkdirSync(join(tempDir, 'source-dir'));
277
+ writeFileSync(join(tempDir, 'source-dir', 'file.txt'), 'content');
278
+
279
+ await adapter.move('source-dir', 'dest-dir');
280
+
281
+ expect(await adapter.exists('source-dir')).toBe(false);
282
+ expect(await adapter.exists('dest-dir')).toBe(true);
283
+ expect(await adapter.exists('dest-dir/file.txt')).toBe(true);
284
+ });
285
+
286
+ it('should throw for non-existent source', async () => {
287
+ await expect(adapter.move('nonexistent', 'dest')).rejects.toThrow(AdapterError);
288
+ });
289
+ });
290
+
291
+ describe('copy', () => {
292
+ beforeEach(async () => {
293
+ await adapter.connect();
294
+ });
295
+
296
+ it('should copy a file', async () => {
297
+ writeFileSync(join(tempDir, 'source.txt'), 'content');
298
+
299
+ await adapter.copy('source.txt', 'copy.txt');
300
+
301
+ expect(await adapter.exists('source.txt')).toBe(true);
302
+ expect(await adapter.exists('copy.txt')).toBe(true);
303
+
304
+ const content = await adapter.read('copy.txt');
305
+ expect(content).toBe('content');
306
+ });
307
+
308
+ it('should copy a directory recursively', async () => {
309
+ mkdirSync(join(tempDir, 'source-dir'));
310
+ mkdirSync(join(tempDir, 'source-dir', 'nested'));
311
+ writeFileSync(join(tempDir, 'source-dir', 'file.txt'), 'content');
312
+ writeFileSync(join(tempDir, 'source-dir', 'nested', 'nested.txt'), 'nested');
313
+
314
+ await adapter.copy('source-dir', 'copy-dir');
315
+
316
+ expect(await adapter.exists('source-dir')).toBe(true);
317
+ expect(await adapter.exists('copy-dir')).toBe(true);
318
+ expect(await adapter.exists('copy-dir/file.txt')).toBe(true);
319
+ expect(await adapter.exists('copy-dir/nested/nested.txt')).toBe(true);
320
+
321
+ const content = await adapter.read('copy-dir/nested/nested.txt');
322
+ expect(content).toBe('nested');
323
+ });
324
+
325
+ it('should throw for non-existent source', async () => {
326
+ await expect(adapter.copy('nonexistent', 'dest')).rejects.toThrow(AdapterError);
327
+ });
328
+ });
329
+
330
+ describe('path traversal protection', () => {
331
+ beforeEach(async () => {
332
+ await adapter.connect();
333
+ });
334
+
335
+ it('should prevent path traversal with ..', async () => {
336
+ await expect(adapter.read('../outside.txt')).rejects.toThrow(AdapterError);
337
+ });
338
+
339
+ it('should prevent path traversal with absolute paths', async () => {
340
+ await expect(adapter.read('/etc/passwd')).rejects.toThrow(AdapterError);
341
+ });
342
+ });
343
+ });
344
+
345
+ describe('createFilesystemAdapter', () => {
346
+ it('should create a filesystem adapter', () => {
347
+ const adapter = createFilesystemAdapter({ basePath: '/tmp' });
348
+ expect(adapter).toBeInstanceOf(FilesystemAdapter);
349
+ });
350
+ });