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.
- package/CHANGELOG.md +19 -0
- package/CHANGELOG_en.md +19 -0
- package/README.md +31 -5
- package/README_en.md +31 -5
- package/dist/commands/help.js +18 -1
- package/dist/commands/template.d.ts +3 -1
- package/dist/commands/template.js +38 -6
- package/dist/commands/use.js +11 -1
- package/dist/core/manifest.js +10 -1
- package/dist/core/project.js +27 -1
- package/dist/index.js +3 -2
- package/dist/types/config.d.ts +3 -1
- package/dist/types/config.js +4 -2
- package/dist/utils/parsePath.d.ts +1 -1
- package/dist/utils/parsePath.js +5 -3
- package/package.json +1 -1
- package/src/commands/help.ts +18 -1
- package/src/commands/template.ts +202 -160
- package/src/commands/use.ts +11 -1
- package/src/core/manifest.ts +67 -57
- package/src/core/project.ts +304 -276
- package/src/index.ts +254 -253
- package/src/types/config.ts +116 -112
- package/src/utils/parsePath.ts +6 -4
- package/tests/commands/use.test.ts +12 -6
- package/tests/core/project.test.ts +324 -316
- package/tests/types/config.test.ts +44 -16
- package/tests/utils/parsePath.test.ts +18 -9
|
@@ -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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'partial-source-skill-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', '
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'skills', 'wildcard-source-skill-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', 'commands', 'wildcard-source', '
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// Should copy
|
|
149
|
-
expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', '
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
expect(await fs.pathExists(path.join(testProjectDir, '.toolscc', '
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
expect(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
await fs.
|
|
208
|
-
await fs.
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
await fs.
|
|
220
|
-
await fs.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
await
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
//
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
expect(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
expect(
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
});
|