tools-cc 1.0.8 → 1.0.10

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.
@@ -1,317 +1,325 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import fs from 'fs-extra';
3
- import path from 'path';
4
- import { initProject, useSource, unuseSource, listUsedSources, exportProjectConfig, importProjectConfig } from '../../src/core/project';
5
- import { SourceSelection, ExportConfig } from '../../src/types/config';
6
-
7
- describe('Project Module', () => {
8
- const testProjectDir = path.join(__dirname, '../fixtures/test-project');
9
- const testSourceDir = path.join(__dirname, '../fixtures/test-source');
10
-
11
- beforeEach(async () => {
12
- await fs.ensureDir(testProjectDir);
13
- await fs.ensureDir(path.join(testSourceDir, 'skills', 'test-skill'));
14
- });
15
-
16
- afterEach(async () => {
17
- await fs.remove(testProjectDir);
18
- await fs.remove(testSourceDir);
19
- });
20
-
21
- it('should initialize project with .toolscc directory', async () => {
22
- await initProject(testProjectDir);
23
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc'))).toBe(true);
24
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'config.json'))).toBe(true);
25
- });
26
-
27
- it('should use source and copy components', async () => {
28
- await initProject(testProjectDir);
29
- await useSource('test-source', testSourceDir, testProjectDir);
30
-
31
- // skills should be flattened with prefix
32
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'test-source-test-skill'))).toBe(true);
33
- });
34
-
35
- it('should unuse source and remove components', async () => {
36
- await initProject(testProjectDir);
37
- await useSource('test-source', testSourceDir, testProjectDir);
38
- await unuseSource('test-source', testProjectDir);
39
-
40
- const config = await fs.readJson(path.join(testProjectDir, '.toolscc', 'config.json'));
41
- expect(config.sources).not.toHaveProperty('test-source');
42
- });
43
-
44
- it('should list used sources', async () => {
45
- await initProject(testProjectDir);
46
- await useSource('test-source', testSourceDir, testProjectDir);
47
-
48
- const sources = await listUsedSources(testProjectDir);
49
- expect(sources).toContain('test-source');
50
- });
51
- });
52
-
53
- describe('Partial Import', () => {
54
- const testProjectDir = path.join(__dirname, '../fixtures/test-partial-project');
55
- const testSourceDir = path.join(__dirname, '../fixtures/test-partial-source');
56
-
57
- beforeEach(async () => {
58
- // Create test source with multiple skills, commands, and agents
59
- await fs.ensureDir(path.join(testSourceDir, 'skills', 'skill-a'));
60
- await fs.ensureDir(path.join(testSourceDir, 'skills', 'skill-b'));
61
- await fs.ensureDir(path.join(testSourceDir, 'skills', 'skill-c'));
62
- await fs.ensureDir(path.join(testSourceDir, 'commands'));
63
- await fs.ensureDir(path.join(testSourceDir, 'agents'));
64
-
65
- // Create command files
66
- await fs.writeFile(path.join(testSourceDir, 'commands', 'cmd1.md'), '# cmd1');
67
- await fs.writeFile(path.join(testSourceDir, 'commands', 'cmd2.md'), '# cmd2');
68
- await fs.writeFile(path.join(testSourceDir, 'commands', 'cmd3.md'), '# cmd3');
69
-
70
- // Create agent files
71
- await fs.writeFile(path.join(testSourceDir, 'agents', 'agent1.md'), '# agent1');
72
- await fs.writeFile(path.join(testSourceDir, 'agents', 'agent2.md'), '# agent2');
73
-
74
- // Create skill files
75
- await fs.writeFile(path.join(testSourceDir, 'skills', 'skill-a', 'test.md'), '# skill-a');
76
- await fs.writeFile(path.join(testSourceDir, 'skills', 'skill-b', 'test.md'), '# skill-b');
77
- await fs.writeFile(path.join(testSourceDir, 'skills', 'skill-c', 'test.md'), '# skill-c');
78
- });
79
-
80
- afterEach(async () => {
81
- await fs.remove(testProjectDir);
82
- await fs.remove(testSourceDir);
83
- });
84
-
85
- it('should import only selected skills', async () => {
86
- await initProject(testProjectDir);
87
-
88
- const selection: SourceSelection = {
89
- skills: ['skill-a', 'skill-c'],
90
- commands: [],
91
- agents: []
92
- };
93
-
94
- await useSource('partial-source', testSourceDir, testProjectDir, selection);
95
-
96
- // Should copy selected skills
97
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'partial-source-skill-a'))).toBe(true);
98
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'partial-source-skill-c'))).toBe(true);
99
-
100
- // Should NOT copy skill-b
101
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'partial-source-skill-b'))).toBe(false);
102
-
103
- // Should NOT copy any commands or agents (empty selection)
104
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'commands', 'partial-source'))).toBe(false);
105
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'agents', 'partial-source'))).toBe(false);
106
- });
107
-
108
- it('should import all when selection contains wildcard', async () => {
109
- await initProject(testProjectDir);
110
-
111
- const selection: SourceSelection = {
112
- skills: ['*'],
113
- commands: ['*'],
114
- agents: ['*']
115
- };
116
-
117
- await useSource('wildcard-source', testSourceDir, testProjectDir, selection);
118
-
119
- // Should copy all skills
120
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'wildcard-source-skill-a'))).toBe(true);
121
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'wildcard-source-skill-b'))).toBe(true);
122
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'wildcard-source-skill-c'))).toBe(true);
123
-
124
- // Should copy all commands
125
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'commands', 'wildcard-source', 'cmd1.md'))).toBe(true);
126
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'commands', 'wildcard-source', 'cmd2.md'))).toBe(true);
127
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'commands', 'wildcard-source', 'cmd3.md'))).toBe(true);
128
-
129
- // Should copy all agents
130
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'agents', 'wildcard-source', 'agent1.md'))).toBe(true);
131
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'agents', 'wildcard-source', 'agent2.md'))).toBe(true);
132
- });
133
-
134
- it('should import only selected commands and agents', async () => {
135
- await initProject(testProjectDir);
136
-
137
- const selection: SourceSelection = {
138
- skills: [],
139
- commands: ['cmd1', 'cmd3'],
140
- agents: ['agent2']
141
- };
142
-
143
- await useSource('partial-cmd-agent', testSourceDir, testProjectDir, selection);
144
-
145
- // Should NOT copy any skills
146
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'partial-cmd-agent-skill-a'))).toBe(false);
147
-
148
- // Should copy selected commands
149
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'commands', 'partial-cmd-agent', 'cmd1.md'))).toBe(true);
150
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'commands', 'partial-cmd-agent', 'cmd3.md'))).toBe(true);
151
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'commands', 'partial-cmd-agent', 'cmd2.md'))).toBe(false);
152
-
153
- // Should copy selected agents
154
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'agents', 'partial-cmd-agent', 'agent2.md'))).toBe(true);
155
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'agents', 'partial-cmd-agent', 'agent1.md'))).toBe(false);
156
- });
157
-
158
- it('should save selection to project config', async () => {
159
- await initProject(testProjectDir);
160
-
161
- const selection: SourceSelection = {
162
- skills: ['skill-a'],
163
- commands: ['cmd1'],
164
- agents: ['agent1']
165
- };
166
-
167
- await useSource('config-source', testSourceDir, testProjectDir, selection);
168
-
169
- const config = await fs.readJson(path.join(testProjectDir, '.toolscc', 'config.json'));
170
- expect(config.sources['config-source']).toEqual(selection);
171
- });
172
-
173
- it('should default to wildcard when no selection provided', async () => {
174
- await initProject(testProjectDir);
175
-
176
- // Call without selection parameter
177
- await useSource('default-source', testSourceDir, testProjectDir);
178
-
179
- // Should copy all items (default wildcard behavior)
180
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'default-source-skill-a'))).toBe(true);
181
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'default-source-skill-b'))).toBe(true);
182
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'default-source-skill-c'))).toBe(true);
183
-
184
- // Config should have wildcard selection
185
- const config = await fs.readJson(path.join(testProjectDir, '.toolscc', 'config.json'));
186
- expect(config.sources['default-source']).toEqual({
187
- skills: ['*'],
188
- commands: ['*'],
189
- agents: ['*']
190
- });
191
- });
192
- });
193
-
194
- describe('Export/Import Project Config', () => {
195
- const testProjectDir = path.join(__dirname, '../fixtures/test-export-project');
196
- const testSourceDir = path.join(__dirname, '../fixtures/test-export-source');
197
- const exportFilePath = path.join(__dirname, '../fixtures/test-export.json');
198
-
199
- beforeEach(async () => {
200
- // Create test source with multiple skills, commands, and agents
201
- await fs.ensureDir(path.join(testSourceDir, 'skills', 'skill-a'));
202
- await fs.ensureDir(path.join(testSourceDir, 'skills', 'skill-b'));
203
- await fs.ensureDir(path.join(testSourceDir, 'commands'));
204
- await fs.ensureDir(path.join(testSourceDir, 'agents'));
205
-
206
- // Create command files
207
- await fs.writeFile(path.join(testSourceDir, 'commands', 'cmd1.md'), '# cmd1');
208
- await fs.writeFile(path.join(testSourceDir, 'commands', 'cmd2.md'), '# cmd2');
209
-
210
- // Create agent files
211
- await fs.writeFile(path.join(testSourceDir, 'agents', 'agent1.md'), '# agent1');
212
-
213
- // Create skill files
214
- await fs.writeFile(path.join(testSourceDir, 'skills', 'skill-a', 'test.md'), '# skill-a');
215
- await fs.writeFile(path.join(testSourceDir, 'skills', 'skill-b', 'test.md'), '# skill-b');
216
- });
217
-
218
- afterEach(async () => {
219
- await fs.remove(testProjectDir);
220
- await fs.remove(testSourceDir);
221
- await fs.remove(exportFilePath);
222
- });
223
-
224
- it('should export project config to JSON file with version 1.0', async () => {
225
- // Initialize and add sources
226
- await initProject(testProjectDir);
227
-
228
- const selection: SourceSelection = {
229
- skills: ['skill-a'],
230
- commands: ['cmd1'],
231
- agents: ['*']
232
- };
233
-
234
- await useSource('export-source', testSourceDir, testProjectDir, selection);
235
-
236
- // Export config
237
- await exportProjectConfig(testProjectDir, exportFilePath);
238
-
239
- // Verify export file exists
240
- expect(await fs.pathExists(exportFilePath)).toBe(true);
241
-
242
- // Verify export content
243
- const exported: ExportConfig = await fs.readJson(exportFilePath);
244
- expect(exported.version).toBe('1.0');
245
- expect(exported.type).toBe('project');
246
- expect(exported.config).toBeDefined();
247
- expect(exported.config.sources['export-source']).toEqual(selection);
248
- expect(exported.exportedAt).toBeDefined();
249
- });
250
-
251
- it('should import config and apply sources', async () => {
252
- // Create export file first
253
- const exportConfig: ExportConfig = {
254
- version: '1.0',
255
- type: 'project',
256
- config: {
257
- sources: {
258
- 'import-source': {
259
- skills: ['skill-b'],
260
- commands: ['cmd2'],
261
- agents: []
262
- }
263
- },
264
- links: []
265
- },
266
- exportedAt: new Date().toISOString()
267
- };
268
- await fs.writeJson(exportFilePath, exportConfig, { spaces: 2 });
269
-
270
- // Import config
271
- const resolveSourcePath = async (sourceName: string) => {
272
- if (sourceName === 'import-source') {
273
- return testSourceDir;
274
- }
275
- throw new Error(`Unknown source: ${sourceName}`);
276
- };
277
-
278
- await importProjectConfig(exportFilePath, testProjectDir, resolveSourcePath);
279
-
280
- // Verify project config was applied
281
- const config = await fs.readJson(path.join(testProjectDir, '.toolscc', 'config.json'));
282
- expect(config.sources['import-source']).toEqual({
283
- skills: ['skill-b'],
284
- commands: ['cmd2'],
285
- agents: []
286
- });
287
-
288
- // Verify files were copied
289
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'import-source-skill-b'))).toBe(true);
290
- expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'commands', 'import-source', 'cmd2.md'))).toBe(true);
291
- });
292
-
293
- it('should reject unsupported version on import', async () => {
294
- // Create export file with unsupported version
295
- const exportConfig = {
296
- version: '2.0',
297
- type: 'project',
298
- config: {
299
- sources: {},
300
- links: []
301
- },
302
- exportedAt: new Date().toISOString()
303
- };
304
- await fs.writeJson(exportFilePath, exportConfig, { spaces: 2 });
305
-
306
- const resolveSourcePath = async () => testSourceDir;
307
-
308
- await expect(importProjectConfig(exportFilePath, testProjectDir, resolveSourcePath))
309
- .rejects.toThrow('Unsupported config version: 2.0');
310
- });
311
-
312
- it('should throw error when exporting non-initialized project', async () => {
313
- // Don't initialize project
314
- await expect(exportProjectConfig(testProjectDir, exportFilePath))
315
- .rejects.toThrow();
316
- });
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import { initProject, useSource, unuseSource, listUsedSources, exportProjectConfig, importProjectConfig } from '../../src/core/project';
5
+ import { SourceSelection, ExportConfig } from '../../src/types/config';
6
+
7
+ describe('Project Module', () => {
8
+ const testProjectDir = path.join(__dirname, '../fixtures/test-project');
9
+ const testSourceDir = path.join(__dirname, '../fixtures/test-source');
10
+
11
+ beforeEach(async () => {
12
+ await fs.ensureDir(testProjectDir);
13
+ await fs.ensureDir(path.join(testSourceDir, 'skills', 'test-skill'));
14
+ });
15
+
16
+ afterEach(async () => {
17
+ await fs.remove(testProjectDir);
18
+ await fs.remove(testSourceDir);
19
+ });
20
+
21
+ it('should initialize project with .toolscc directory', async () => {
22
+ await initProject(testProjectDir);
23
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc'))).toBe(true);
24
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'config.json'))).toBe(true);
25
+ });
26
+
27
+ it('should use source and copy components', async () => {
28
+ await initProject(testProjectDir);
29
+ await useSource('test-source', testSourceDir, testProjectDir);
30
+
31
+ // skills should be flattened with prefix
32
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'test-source-test-skill'))).toBe(true);
33
+ });
34
+
35
+ it('should unuse source and remove components', async () => {
36
+ await initProject(testProjectDir);
37
+ await useSource('test-source', testSourceDir, testProjectDir);
38
+ await unuseSource('test-source', testProjectDir);
39
+
40
+ const config = await fs.readJson(path.join(testProjectDir, '.toolscc', 'config.json'));
41
+ expect(config.sources).not.toHaveProperty('test-source');
42
+ });
43
+
44
+ it('should list used sources', async () => {
45
+ await initProject(testProjectDir);
46
+ await useSource('test-source', testSourceDir, testProjectDir);
47
+
48
+ const sources = await listUsedSources(testProjectDir);
49
+ expect(sources).toContain('test-source');
50
+ });
51
+ });
52
+
53
+ describe('Partial Import', () => {
54
+ const testProjectDir = path.join(__dirname, '../fixtures/test-partial-project');
55
+ const testSourceDir = path.join(__dirname, '../fixtures/test-partial-source');
56
+
57
+ beforeEach(async () => {
58
+ // Create test source with multiple skills, commands, and agents
59
+ await fs.ensureDir(path.join(testSourceDir, 'skills', 'skill-a'));
60
+ await fs.ensureDir(path.join(testSourceDir, 'skills', 'skill-b'));
61
+ await fs.ensureDir(path.join(testSourceDir, 'skills', 'skill-c'));
62
+ await fs.ensureDir(path.join(testSourceDir, 'commands'));
63
+ await fs.ensureDir(path.join(testSourceDir, 'agents'));
64
+
65
+ // Create command files
66
+ await fs.writeFile(path.join(testSourceDir, 'commands', 'cmd1.md'), '# cmd1');
67
+ await fs.writeFile(path.join(testSourceDir, 'commands', 'cmd2.md'), '# cmd2');
68
+ await fs.writeFile(path.join(testSourceDir, 'commands', 'cmd3.md'), '# cmd3');
69
+
70
+ // Create agent files
71
+ await fs.writeFile(path.join(testSourceDir, 'agents', 'agent1.md'), '# agent1');
72
+ await fs.writeFile(path.join(testSourceDir, 'agents', 'agent2.md'), '# agent2');
73
+
74
+ // Create skill files
75
+ await fs.writeFile(path.join(testSourceDir, 'skills', 'skill-a', 'test.md'), '# skill-a');
76
+ await fs.writeFile(path.join(testSourceDir, 'skills', 'skill-b', 'test.md'), '# skill-b');
77
+ await fs.writeFile(path.join(testSourceDir, 'skills', 'skill-c', 'test.md'), '# skill-c');
78
+ });
79
+
80
+ afterEach(async () => {
81
+ await fs.remove(testProjectDir);
82
+ await fs.remove(testSourceDir);
83
+ });
84
+
85
+ it('should import only selected skills', async () => {
86
+ await initProject(testProjectDir);
87
+
88
+ const selection: SourceSelection = {
89
+ skills: ['skill-a', 'skill-c'],
90
+ commands: [],
91
+ agents: [],
92
+ rules: []
93
+ };
94
+
95
+ await useSource('partial-source', testSourceDir, testProjectDir, selection);
96
+
97
+ // Should copy selected skills
98
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'partial-source-skill-a'))).toBe(true);
99
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'partial-source-skill-c'))).toBe(true);
100
+
101
+ // Should NOT copy skill-b
102
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'partial-source-skill-b'))).toBe(false);
103
+
104
+ // Should NOT copy any commands or agents (empty selection)
105
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'commands', 'partial-source'))).toBe(false);
106
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'agents', 'partial-source'))).toBe(false);
107
+ });
108
+
109
+ it('should import all when selection contains wildcard', async () => {
110
+ await initProject(testProjectDir);
111
+
112
+ const selection: SourceSelection = {
113
+ skills: ['*'],
114
+ commands: ['*'],
115
+ agents: ['*'],
116
+ rules: ['*']
117
+ };
118
+
119
+ await useSource('wildcard-source', testSourceDir, testProjectDir, selection);
120
+
121
+ // Should copy all skills
122
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'wildcard-source-skill-a'))).toBe(true);
123
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'wildcard-source-skill-b'))).toBe(true);
124
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'wildcard-source-skill-c'))).toBe(true);
125
+
126
+ // Should copy all commands
127
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'commands', 'wildcard-source', 'cmd1.md'))).toBe(true);
128
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'commands', 'wildcard-source', 'cmd2.md'))).toBe(true);
129
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'commands', 'wildcard-source', 'cmd3.md'))).toBe(true);
130
+
131
+ // Should copy all agents
132
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'agents', 'wildcard-source', 'agent1.md'))).toBe(true);
133
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'agents', 'wildcard-source', 'agent2.md'))).toBe(true);
134
+ });
135
+
136
+ it('should import only selected commands and agents', async () => {
137
+ await initProject(testProjectDir);
138
+
139
+ const selection: SourceSelection = {
140
+ skills: [],
141
+ commands: ['cmd1', 'cmd3'],
142
+ agents: ['agent2'],
143
+ rules: []
144
+ };
145
+
146
+ await useSource('partial-cmd-agent', testSourceDir, testProjectDir, selection);
147
+
148
+ // Should NOT copy any skills
149
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'partial-cmd-agent-skill-a'))).toBe(false);
150
+
151
+ // Should copy selected commands
152
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'commands', 'partial-cmd-agent', 'cmd1.md'))).toBe(true);
153
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'commands', 'partial-cmd-agent', 'cmd3.md'))).toBe(true);
154
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'commands', 'partial-cmd-agent', 'cmd2.md'))).toBe(false);
155
+
156
+ // Should copy selected agents
157
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'agents', 'partial-cmd-agent', 'agent2.md'))).toBe(true);
158
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'agents', 'partial-cmd-agent', 'agent1.md'))).toBe(false);
159
+ });
160
+
161
+ it('should save selection to project config', async () => {
162
+ await initProject(testProjectDir);
163
+
164
+ const selection: SourceSelection = {
165
+ skills: ['skill-a'],
166
+ commands: ['cmd1'],
167
+ agents: ['agent1'],
168
+ rules: []
169
+ };
170
+
171
+ await useSource('config-source', testSourceDir, testProjectDir, selection);
172
+
173
+ const config = await fs.readJson(path.join(testProjectDir, '.toolscc', 'config.json'));
174
+ expect(config.sources['config-source']).toEqual(selection);
175
+ });
176
+
177
+ it('should default to wildcard when no selection provided', async () => {
178
+ await initProject(testProjectDir);
179
+
180
+ // Call without selection parameter
181
+ await useSource('default-source', testSourceDir, testProjectDir);
182
+
183
+ // Should copy all items (default wildcard behavior)
184
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'default-source-skill-a'))).toBe(true);
185
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'default-source-skill-b'))).toBe(true);
186
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'default-source-skill-c'))).toBe(true);
187
+
188
+ // Config should have wildcard selection
189
+ const config = await fs.readJson(path.join(testProjectDir, '.toolscc', 'config.json'));
190
+ expect(config.sources['default-source']).toEqual({
191
+ skills: ['*'],
192
+ commands: ['*'],
193
+ agents: ['*'],
194
+ rules: ['*']
195
+ });
196
+ });
197
+ });
198
+
199
+ describe('Export/Import Project Config', () => {
200
+ const testProjectDir = path.join(__dirname, '../fixtures/test-export-project');
201
+ const testSourceDir = path.join(__dirname, '../fixtures/test-export-source');
202
+ const exportFilePath = path.join(__dirname, '../fixtures/test-export.json');
203
+
204
+ beforeEach(async () => {
205
+ // Create test source with multiple skills, commands, and agents
206
+ await fs.ensureDir(path.join(testSourceDir, 'skills', 'skill-a'));
207
+ await fs.ensureDir(path.join(testSourceDir, 'skills', 'skill-b'));
208
+ await fs.ensureDir(path.join(testSourceDir, 'commands'));
209
+ await fs.ensureDir(path.join(testSourceDir, 'agents'));
210
+
211
+ // Create command files
212
+ await fs.writeFile(path.join(testSourceDir, 'commands', 'cmd1.md'), '# cmd1');
213
+ await fs.writeFile(path.join(testSourceDir, 'commands', 'cmd2.md'), '# cmd2');
214
+
215
+ // Create agent files
216
+ await fs.writeFile(path.join(testSourceDir, 'agents', 'agent1.md'), '# agent1');
217
+
218
+ // Create skill files
219
+ await fs.writeFile(path.join(testSourceDir, 'skills', 'skill-a', 'test.md'), '# skill-a');
220
+ await fs.writeFile(path.join(testSourceDir, 'skills', 'skill-b', 'test.md'), '# skill-b');
221
+ });
222
+
223
+ afterEach(async () => {
224
+ await fs.remove(testProjectDir);
225
+ await fs.remove(testSourceDir);
226
+ await fs.remove(exportFilePath);
227
+ });
228
+
229
+ it('should export project config to JSON file with version 1.0', async () => {
230
+ // Initialize and add sources
231
+ await initProject(testProjectDir);
232
+
233
+ const selection: SourceSelection = {
234
+ skills: ['skill-a'],
235
+ commands: ['cmd1'],
236
+ agents: ['*'],
237
+ rules: []
238
+ };
239
+
240
+ await useSource('export-source', testSourceDir, testProjectDir, selection);
241
+
242
+ // Export config
243
+ await exportProjectConfig(testProjectDir, exportFilePath);
244
+
245
+ // Verify export file exists
246
+ expect(await fs.pathExists(exportFilePath)).toBe(true);
247
+
248
+ // Verify export content
249
+ const exported: ExportConfig = await fs.readJson(exportFilePath);
250
+ expect(exported.version).toBe('1.0');
251
+ expect(exported.type).toBe('project');
252
+ expect(exported.config).toBeDefined();
253
+ expect(exported.config.sources['export-source']).toEqual(selection);
254
+ expect(exported.exportedAt).toBeDefined();
255
+ });
256
+
257
+ it('should import config and apply sources', async () => {
258
+ // Create export file first
259
+ const exportConfig: ExportConfig = {
260
+ version: '1.0',
261
+ type: 'project',
262
+ config: {
263
+ sources: {
264
+ 'import-source': {
265
+ skills: ['skill-b'],
266
+ commands: ['cmd2'],
267
+ agents: [],
268
+ rules: []
269
+ }
270
+ },
271
+ links: []
272
+ },
273
+ exportedAt: new Date().toISOString()
274
+ };
275
+ await fs.writeJson(exportFilePath, exportConfig, { spaces: 2 });
276
+
277
+ // Import config
278
+ const resolveSourcePath = async (sourceName: string) => {
279
+ if (sourceName === 'import-source') {
280
+ return testSourceDir;
281
+ }
282
+ throw new Error(`Unknown source: ${sourceName}`);
283
+ };
284
+
285
+ await importProjectConfig(exportFilePath, testProjectDir, resolveSourcePath);
286
+
287
+ // Verify project config was applied
288
+ const config = await fs.readJson(path.join(testProjectDir, '.toolscc', 'config.json'));
289
+ expect(config.sources['import-source']).toEqual({
290
+ skills: ['skill-b'],
291
+ commands: ['cmd2'],
292
+ agents: [],
293
+ rules: []
294
+ });
295
+
296
+ // Verify files were copied
297
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'import-source-skill-b'))).toBe(true);
298
+ expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'commands', 'import-source', 'cmd2.md'))).toBe(true);
299
+ });
300
+
301
+ it('should reject unsupported version on import', async () => {
302
+ // Create export file with unsupported version
303
+ const exportConfig = {
304
+ version: '2.0',
305
+ type: 'project',
306
+ config: {
307
+ sources: {},
308
+ links: []
309
+ },
310
+ exportedAt: new Date().toISOString()
311
+ };
312
+ await fs.writeJson(exportFilePath, exportConfig, { spaces: 2 });
313
+
314
+ const resolveSourcePath = async () => testSourceDir;
315
+
316
+ await expect(importProjectConfig(exportFilePath, testProjectDir, resolveSourcePath))
317
+ .rejects.toThrow('Unsupported config version: 2.0');
318
+ });
319
+
320
+ it('should throw error when exporting non-initialized project', async () => {
321
+ // Don't initialize project
322
+ await expect(exportProjectConfig(testProjectDir, exportFilePath))
323
+ .rejects.toThrow();
324
+ });
317
325
  });