sumulige-claude 1.2.0 → 1.3.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.
- package/.claude/.kickoff-hint.txt +3 -2
- package/.claude/AGENTS.md +6 -6
- package/.claude/CLAUDE.md +138 -0
- package/.claude/README.md +234 -43
- package/.claude/USAGE.md +175 -0
- package/.claude/boris-optimizations.md +167 -0
- package/.claude/commands/fix.md +83 -0
- package/.claude/commands/plan.md +88 -0
- package/.claude/commands/refactor.md +102 -0
- package/.claude/commands/todos.md +6 -41
- package/.claude/hooks/code-formatter.cjs +2 -7
- package/.claude/hooks/conversation-logger.cjs +222 -0
- package/.claude/hooks/multi-session.cjs +3 -9
- package/.claude/hooks/pre-push.cjs +3 -2
- package/.claude/hooks/project-kickoff.cjs +198 -20
- package/.claude/hooks/rag-skill-loader.cjs +0 -7
- package/.claude/hooks/session-restore.cjs +0 -0
- package/.claude/hooks/session-save.cjs +0 -0
- package/.claude/hooks/thinking-silent.cjs +3 -9
- package/.claude/hooks/todo-manager.cjs +142 -269
- package/.claude/hooks/verify-work.cjs +4 -10
- package/.claude/rag/skill-index.json +147 -8
- package/.claude/rules/coding-style.md +119 -0
- package/.claude/rules/hooks.md +288 -0
- package/.claude/rules/performance.md +78 -0
- package/.claude/rules/security.md +56 -0
- package/.claude/rules/testing.md +89 -0
- package/.claude/settings.json +115 -0
- package/.claude/settings.local.json +19 -1
- package/.claude/skills/SKILLS.md +145 -0
- package/.claude/skills/design-brain/SKILL.md +190 -0
- package/.claude/skills/design-brain/metadata.yaml +26 -0
- package/.claude/skills/examples/README.md +47 -0
- package/.claude/skills/examples/basic-task.md +67 -0
- package/.claude/skills/examples/bug-fix-workflow.md +92 -0
- package/.claude/skills/examples/feature-development.md +81 -0
- package/.claude/skills/manus-kickoff/SKILL.md +128 -0
- package/.claude/skills/manus-kickoff/examples/basic.md +84 -0
- package/.claude/skills/manus-kickoff/metadata.yaml +33 -0
- package/.claude/skills/manus-kickoff/templates/PROJECT_KICKOFF.md +89 -0
- package/.claude/skills/manus-kickoff/templates/PROJECT_PROPOSAL.md +227 -0
- package/.claude/skills/manus-kickoff/templates/TASK_PLAN.md +121 -0
- package/.claude/skills/quality-guard/SKILL.md +138 -0
- package/.claude/skills/quality-guard/metadata.yaml +27 -0
- package/.claude/skills/quick-fix/SKILL.md +138 -0
- package/.claude/skills/quick-fix/metadata.yaml +26 -0
- package/.claude/skills/test-master/SKILL.md +186 -0
- package/.claude/skills/test-master/metadata.yaml +29 -0
- package/.claude/templates/PROJECT_KICKOFF.md +89 -0
- package/.claude/templates/PROJECT_PROPOSAL.md +227 -0
- package/.claude/templates/TASK_PLAN.md +121 -0
- package/.claude-plugin/marketplace.json +2 -2
- package/AGENTS.md +49 -7
- package/CHANGELOG.md +56 -2
- package/CLAUDE-template.md +114 -0
- package/README.md +73 -1
- package/config/official-skills.json +2 -2
- package/development/knowledge-base/.index.clean.json +1 -0
- package/jest.config.js +3 -1
- package/lib/commands.js +1626 -1207
- package/lib/marketplace.js +1 -0
- package/package.json +1 -1
- package/project-paradigm.md +313 -0
- package/prompts/how-to-find.md +163 -0
- package/tests/commands.test.js +940 -17
- package/tests/config-schema.test.js +425 -0
- package/tests/marketplace.test.js +330 -214
- package/tests/sync-external.test.js +214 -0
- package/tests/update-registry.test.js +251 -0
- package/tests/utils.test.js +12 -8
- package/tests/web-search.test.js +392 -0
- package/thinkinglens-silent.md +138 -0
- package/.claude/skills/api-tester/SKILL.md +0 -90
- package/.claude/skills/api-tester/examples/basic.md +0 -3
- package/.claude/skills/api-tester/metadata.yaml +0 -30
- package/.claude/skills/api-tester/templates/default.md +0 -3
- package/.claude/skills/template/SKILL.md +0 -6
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync External Skills 脚本单元测试
|
|
3
|
+
* 测试外部技能同步功能
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
describe('Sync External Skills', () => {
|
|
9
|
+
describe('YAML parsing', () => {
|
|
10
|
+
it('should parse valid sources.yaml structure', () => {
|
|
11
|
+
const yamlStructure = {
|
|
12
|
+
version: 1,
|
|
13
|
+
skills: [
|
|
14
|
+
{
|
|
15
|
+
name: 'test-skill',
|
|
16
|
+
description: 'A test skill',
|
|
17
|
+
native: true
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'external-skill',
|
|
21
|
+
description: 'External skill',
|
|
22
|
+
source: {
|
|
23
|
+
repo: 'owner/repo',
|
|
24
|
+
ref: 'main',
|
|
25
|
+
path: '/skills/external'
|
|
26
|
+
},
|
|
27
|
+
target: {
|
|
28
|
+
path: 'template/.claude/skills/external-skill'
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
expect(yamlStructure.version).toBeDefined();
|
|
35
|
+
expect(yamlStructure.skills).toBeInstanceOf(Array);
|
|
36
|
+
expect(yamlStructure.skills[0].name).toBe('test-skill');
|
|
37
|
+
expect(yamlStructure.skills[0].native).toBe(true);
|
|
38
|
+
expect(yamlStructure.skills[1].source?.repo).toBe('owner/repo');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should handle skills array', () => {
|
|
42
|
+
const skills = [
|
|
43
|
+
{ name: 'skill1', native: true },
|
|
44
|
+
{ name: 'skill2', source: { repo: 'owner/repo' } }
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
expect(skills).toHaveLength(2);
|
|
48
|
+
expect(skills[0].name).toBe('skill1');
|
|
49
|
+
expect(skills[1].name).toBe('skill2');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should parse boolean native field', () => {
|
|
53
|
+
const nativeSkill = { native: true };
|
|
54
|
+
const externalSkill = { native: false };
|
|
55
|
+
|
|
56
|
+
expect(nativeSkill.native).toBe(true);
|
|
57
|
+
expect(externalSkill.native).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should parse nested source configuration', () => {
|
|
61
|
+
const source = {
|
|
62
|
+
repo: 'owner/repo',
|
|
63
|
+
ref: 'main',
|
|
64
|
+
path: '/path/to/skill'
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
expect(source.repo).toBe('owner/repo');
|
|
68
|
+
expect(source.ref).toBe('main');
|
|
69
|
+
expect(source.path).toBe('/path/to/skill');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should parse sync configuration', () => {
|
|
73
|
+
const sync = {
|
|
74
|
+
include: ['*.md', '*.json'],
|
|
75
|
+
exclude: ['node_modules', '*.lock']
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
expect(sync.include).toContain('*.md');
|
|
79
|
+
expect(sync.exclude).toContain('node_modules');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('File pattern matching', () => {
|
|
84
|
+
it('should match include patterns', () => {
|
|
85
|
+
const include = ['*.md', '*.json'];
|
|
86
|
+
|
|
87
|
+
// .md file matches
|
|
88
|
+
const mdRegex = new RegExp(include[0].replace('*', '.*'));
|
|
89
|
+
expect(mdRegex.test('file.md')).toBe(true);
|
|
90
|
+
expect(mdRegex.test('file.txt')).toBe(false);
|
|
91
|
+
|
|
92
|
+
// .json file matches
|
|
93
|
+
const jsonRegex = new RegExp(include[1].replace('*', '.*'));
|
|
94
|
+
expect(jsonRegex.test('file.json')).toBe(true);
|
|
95
|
+
expect(jsonRegex.test('file.txt')).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should match exclude patterns', () => {
|
|
99
|
+
const exclude = ['node_modules', '*.lock'];
|
|
100
|
+
|
|
101
|
+
// node_modules exclusion
|
|
102
|
+
const nmRegex = new RegExp(exclude[0].replace('*', '.*'));
|
|
103
|
+
expect(nmRegex.test('node_modules')).toBe(true);
|
|
104
|
+
expect(nmRegex.test('src')).toBe(false);
|
|
105
|
+
|
|
106
|
+
// .lock exclusion
|
|
107
|
+
const lockRegex = new RegExp(exclude[1].replace('*', '.*'));
|
|
108
|
+
expect(lockRegex.test('package-lock.json')).toBe(true);
|
|
109
|
+
expect(lockRegex.test('package.json')).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('Git operations', () => {
|
|
114
|
+
it('should construct correct git clone command', () => {
|
|
115
|
+
const repo = 'owner/repo';
|
|
116
|
+
const ref = 'main';
|
|
117
|
+
const targetDir = '/tmp/test';
|
|
118
|
+
|
|
119
|
+
const expectedUrl = `https://github.com/${repo}.git`;
|
|
120
|
+
const command = `git clone --depth 1 --branch ${ref} ${expectedUrl} ${targetDir}`;
|
|
121
|
+
|
|
122
|
+
expect(command).toContain('git clone');
|
|
123
|
+
expect(command).toContain('--depth 1');
|
|
124
|
+
expect(command).toContain('--branch main');
|
|
125
|
+
expect(command).toContain('https://github.com/owner/repo.git');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('Source JSON structure', () => {
|
|
130
|
+
it('should create valid JSON structure', () => {
|
|
131
|
+
const skill = {
|
|
132
|
+
name: 'test-skill',
|
|
133
|
+
description: 'A test skill',
|
|
134
|
+
source: {
|
|
135
|
+
repo: 'owner/repo',
|
|
136
|
+
path: '/skills/test',
|
|
137
|
+
ref: 'main'
|
|
138
|
+
},
|
|
139
|
+
author: 'Test Author',
|
|
140
|
+
license: 'MIT',
|
|
141
|
+
homepage: 'https://example.com',
|
|
142
|
+
verified: true
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const expectedFields = ['name', 'description', 'source', 'author', 'license', 'homepage', 'verified'];
|
|
146
|
+
expectedFields.forEach(field => {
|
|
147
|
+
expect(skill).toHaveProperty(field);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should include synced_at timestamp', () => {
|
|
152
|
+
const sourceJson = {
|
|
153
|
+
name: 'test-skill',
|
|
154
|
+
source: {
|
|
155
|
+
repo: 'owner/repo',
|
|
156
|
+
ref: 'main',
|
|
157
|
+
synced_at: new Date().toISOString()
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
expect(sourceJson.source.synced_at).toBeDefined();
|
|
162
|
+
expect(new Date(sourceJson.source.synced_at)).toBeInstanceOf(Date);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('Sync logic', () => {
|
|
167
|
+
it('should skip native skills', () => {
|
|
168
|
+
const nativeSkill = { name: 'native-skill', native: true };
|
|
169
|
+
const shouldSkip = nativeSkill.native === true;
|
|
170
|
+
|
|
171
|
+
expect(shouldSkip).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should require source.repo for external skills', () => {
|
|
175
|
+
const externalSkill = { name: 'external-skill' };
|
|
176
|
+
const hasRepo = externalSkill.source?.repo;
|
|
177
|
+
|
|
178
|
+
expect(hasRepo).toBeUndefined();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should validate required fields', () => {
|
|
182
|
+
const validSkill = {
|
|
183
|
+
name: 'test',
|
|
184
|
+
source: { repo: 'owner/repo' },
|
|
185
|
+
target: { path: '/path' }
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
expect(validSkill.name).toBeDefined();
|
|
189
|
+
expect(validSkill.source?.repo).toBeDefined();
|
|
190
|
+
expect(validSkill.target?.path).toBeDefined();
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('File paths', () => {
|
|
195
|
+
it('should have correct sources.yaml path', () => {
|
|
196
|
+
const sourcesPath = path.join(__dirname, '..', 'sources.yaml');
|
|
197
|
+
expect(sourcesPath).toContain('sources.yaml');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should have correct skills directory path', () => {
|
|
201
|
+
const skillsDir = path.join(__dirname, '..', 'template', '.claude', 'skills');
|
|
202
|
+
expect(skillsDir).toContain('template');
|
|
203
|
+
expect(skillsDir).toContain('.claude');
|
|
204
|
+
expect(skillsDir).toContain('skills');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should construct correct temp directory path', () => {
|
|
208
|
+
const skillName = 'test-skill';
|
|
209
|
+
const tmpDir = path.join(__dirname, '..', '.tmp', skillName);
|
|
210
|
+
expect(tmpDir).toContain('.tmp');
|
|
211
|
+
expect(tmpDir).toContain('test-skill');
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update Registry 脚本单元测试
|
|
3
|
+
* 测试市场注册表生成功能
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
describe('Update Registry Script', () => {
|
|
9
|
+
describe('findSkillDirs', () => {
|
|
10
|
+
it('should identify skill directories with SKILL.md', () => {
|
|
11
|
+
const hasSkillFile = 'SKILL.md';
|
|
12
|
+
expect(hasSkillFile).toBe('SKILL.md');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should identify skill directories with metadata.yaml', () => {
|
|
16
|
+
const hasMetadata = 'metadata.yaml';
|
|
17
|
+
expect(hasMetadata).toBe('metadata.yaml');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should identify skill directories with .source.json', () => {
|
|
21
|
+
const hasSourceJson = '.source.json';
|
|
22
|
+
expect(hasSourceJson).toBe('.source.json');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should skip template directory', () => {
|
|
26
|
+
const skipDirs = ['template', 'examples', '__tests__'];
|
|
27
|
+
expect(skipDirs).toContain('template');
|
|
28
|
+
expect(skipDirs).toContain('examples');
|
|
29
|
+
expect(skipDirs).toContain('__tests__');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should not skip regular skill directories', () => {
|
|
33
|
+
const skipDirs = ['template', 'examples', '__tests__'];
|
|
34
|
+
expect(skipDirs).not.toContain('my-skill');
|
|
35
|
+
expect(skipDirs).not.toContain('code-reviewer');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('parseMetadata', () => {
|
|
40
|
+
it('should parse simple key-value pairs', () => {
|
|
41
|
+
const yamlContent = `
|
|
42
|
+
name: test-skill
|
|
43
|
+
description: A test skill
|
|
44
|
+
version: 1.0.0
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
expect(yamlContent).toContain('name:');
|
|
48
|
+
expect(yamlContent).toContain('description:');
|
|
49
|
+
expect(yamlContent).toContain('version:');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should parse boolean values', () => {
|
|
53
|
+
const yamlTrue = 'enabled: true';
|
|
54
|
+
const yamlFalse = 'enabled: false';
|
|
55
|
+
|
|
56
|
+
expect(yamlTrue).toContain('true');
|
|
57
|
+
expect(yamlFalse).toContain('false');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should parse array values', () => {
|
|
61
|
+
const yamlArray = 'tags: [tag1, tag2, tag3]';
|
|
62
|
+
const yamlEmptyArray = 'tags: []';
|
|
63
|
+
|
|
64
|
+
expect(yamlArray).toContain('[');
|
|
65
|
+
expect(yamlEmptyArray).toBe('tags: []');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should skip comments', () => {
|
|
69
|
+
const yamlWithComments = `
|
|
70
|
+
# This is a comment
|
|
71
|
+
name: test-skill
|
|
72
|
+
# Another comment
|
|
73
|
+
description: Test
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
const lines = yamlWithComments.split('\n').filter(l => l.trim() && !l.trim().startsWith('#'));
|
|
77
|
+
expect(lines.length).toBe(2);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should handle empty YAML', () => {
|
|
81
|
+
const emptyYaml = '';
|
|
82
|
+
expect(emptyYaml).toBe('');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('parseSourceJson', () => {
|
|
87
|
+
it('should parse valid JSON', () => {
|
|
88
|
+
const json = {
|
|
89
|
+
name: 'test-skill',
|
|
90
|
+
source: {
|
|
91
|
+
repo: 'owner/repo',
|
|
92
|
+
ref: 'main'
|
|
93
|
+
},
|
|
94
|
+
synced_at: '2024-01-01T00:00:00.000Z'
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
expect(json.name).toBe('test-skill');
|
|
98
|
+
expect(json.source.repo).toBe('owner/repo');
|
|
99
|
+
expect(json.source.ref).toBe('main');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should handle missing .source.json', () => {
|
|
103
|
+
const missing = null;
|
|
104
|
+
expect(missing).toBeNull();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('getSkillDescription', () => {
|
|
109
|
+
it('should extract first non-heading paragraph', () => {
|
|
110
|
+
const content = `
|
|
111
|
+
# Test Skill
|
|
112
|
+
|
|
113
|
+
This is the description of the skill.
|
|
114
|
+
|
|
115
|
+
More details here.
|
|
116
|
+
`;
|
|
117
|
+
|
|
118
|
+
const lines = content.split('\n').filter(l => l.trim() && !l.trim().startsWith('#'));
|
|
119
|
+
expect(lines[0].trim()).toBe('This is the description of the skill.');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should skip HTML comments and headings', () => {
|
|
123
|
+
const content = `
|
|
124
|
+
<!-- This is a comment -->
|
|
125
|
+
# Title
|
|
126
|
+
|
|
127
|
+
This is real content.
|
|
128
|
+
`;
|
|
129
|
+
|
|
130
|
+
const lines = content.split('\n').filter(l => {
|
|
131
|
+
const trimmed = l.trim();
|
|
132
|
+
return trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('>') && !trimmed.startsWith('<!--');
|
|
133
|
+
});
|
|
134
|
+
expect(lines[0]).toContain('This is real content');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should handle empty content', () => {
|
|
138
|
+
const emptyContent = '';
|
|
139
|
+
const lines = emptyContent.split('\n').filter(l => l.trim());
|
|
140
|
+
expect(lines.length).toBe(0);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('getSkillCategory', () => {
|
|
145
|
+
it('should return default category when no match', () => {
|
|
146
|
+
const pathParts = ['some', 'random', 'path'];
|
|
147
|
+
const categories = { tools: { name: 'Tools' } };
|
|
148
|
+
|
|
149
|
+
const found = pathParts.some(part => categories[part]);
|
|
150
|
+
expect(found).toBe(false);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should find category in path', () => {
|
|
154
|
+
const pathParts = ['tools', 'skill-name'];
|
|
155
|
+
const categories = { tools: { name: 'Tools' } };
|
|
156
|
+
|
|
157
|
+
const found = pathParts.find(part => categories[part]);
|
|
158
|
+
expect(found).toBe('tools');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should default to tools category', () => {
|
|
162
|
+
const defaultCategory = 'tools';
|
|
163
|
+
expect(defaultCategory).toBe('tools');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('generateRegistry structure', () => {
|
|
168
|
+
it('should have required top-level fields', () => {
|
|
169
|
+
const registry = {
|
|
170
|
+
name: 'smc-skills',
|
|
171
|
+
description: 'Test registry',
|
|
172
|
+
homepage: 'https://github.com/test/test',
|
|
173
|
+
owner: { name: 'test' },
|
|
174
|
+
plugins: [],
|
|
175
|
+
metadata: {}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
expect(registry).toHaveProperty('name');
|
|
179
|
+
expect(registry).toHaveProperty('description');
|
|
180
|
+
expect(registry).toHaveProperty('homepage');
|
|
181
|
+
expect(registry).toHaveProperty('owner');
|
|
182
|
+
expect(registry).toHaveProperty('plugins');
|
|
183
|
+
expect(registry).toHaveProperty('metadata');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should create plugin with skills array', () => {
|
|
187
|
+
const plugin = {
|
|
188
|
+
name: 'smc-skills',
|
|
189
|
+
description: 'Test plugin',
|
|
190
|
+
source: './',
|
|
191
|
+
skills: ['./skills/skill1', './skills/skill2'],
|
|
192
|
+
skill_list: [
|
|
193
|
+
{ name: 'skill1', path: './skills/skill1' },
|
|
194
|
+
{ name: 'skill2', path: './skills/skill2' }
|
|
195
|
+
]
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
expect(plugin.skills).toHaveLength(2);
|
|
199
|
+
expect(plugin.skill_list).toHaveLength(2);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should include metadata with generated_at timestamp', () => {
|
|
203
|
+
const metadata = {
|
|
204
|
+
version: '1.0.0',
|
|
205
|
+
generated_at: new Date().toISOString(),
|
|
206
|
+
skill_count: 10,
|
|
207
|
+
categories: {}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
expect(metadata.generated_at).toBeDefined();
|
|
211
|
+
expect(new Date(metadata.generated_at)).toBeInstanceOf(Date);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe('File paths', () => {
|
|
216
|
+
it('should have correct marketplace.json path', () => {
|
|
217
|
+
const marketplacePath = path.join(__dirname, '..', '.claude-plugin', 'marketplace.json');
|
|
218
|
+
expect(marketplacePath).toContain('.claude-plugin');
|
|
219
|
+
expect(marketplacePath).toContain('marketplace.json');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should have correct skills directory path', () => {
|
|
223
|
+
const skillsDir = path.join(__dirname, '..', 'template', '.claude', 'skills');
|
|
224
|
+
expect(skillsDir).toContain('template');
|
|
225
|
+
expect(skillsDir).toContain('.claude');
|
|
226
|
+
expect(skillsDir).toContain('skills');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('should have correct categories file path', () => {
|
|
230
|
+
const categoriesPath = path.join(__dirname, '..', 'config', 'skill-categories.json');
|
|
231
|
+
expect(categoriesPath).toContain('config');
|
|
232
|
+
expect(categoriesPath).toContain('skill-categories.json');
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe('getPackageVersion', () => {
|
|
237
|
+
it('should return version from package.json', () => {
|
|
238
|
+
const pkg = {
|
|
239
|
+
name: 'test-package',
|
|
240
|
+
version: '1.2.3'
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
expect(pkg.version).toBe('1.2.3');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should return default version when not found', () => {
|
|
247
|
+
const defaultVersion = '1.0.0';
|
|
248
|
+
expect(defaultVersion).toBe('1.0.0');
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
});
|
package/tests/utils.test.js
CHANGED
|
@@ -31,9 +31,9 @@ describe('Utils Module', () => {
|
|
|
31
31
|
|
|
32
32
|
it('should return 0 when source does not exist', () => {
|
|
33
33
|
const nonExistent = path.join(os.tmpdir(), 'non-existent-' + Date.now());
|
|
34
|
-
const
|
|
34
|
+
const result = utils.copyRecursive(nonExistent, tempDest);
|
|
35
35
|
|
|
36
|
-
expect(
|
|
36
|
+
expect(result).toEqual({ copied: 0, skipped: 0, backedup: 0 });
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
it('should copy files recursively', () => {
|
|
@@ -42,9 +42,11 @@ describe('Utils Module', () => {
|
|
|
42
42
|
fs.writeFileSync(path.join(tempSrc, 'file1.txt'), 'content1');
|
|
43
43
|
fs.writeFileSync(path.join(tempSrc, 'subdir', 'file2.txt'), 'content2');
|
|
44
44
|
|
|
45
|
-
const
|
|
45
|
+
const result = utils.copyRecursive(tempSrc, tempDest);
|
|
46
46
|
|
|
47
|
-
expect(
|
|
47
|
+
expect(result.copied).toBe(2);
|
|
48
|
+
expect(result.skipped).toBe(0);
|
|
49
|
+
expect(result.backedup).toBe(0);
|
|
48
50
|
expect(fs.existsSync(path.join(tempDest, 'file1.txt'))).toBe(true);
|
|
49
51
|
expect(fs.existsSync(path.join(tempDest, 'subdir', 'file2.txt'))).toBe(true);
|
|
50
52
|
});
|
|
@@ -73,9 +75,10 @@ describe('Utils Module', () => {
|
|
|
73
75
|
fs.mkdirSync(tempDest, { recursive: true });
|
|
74
76
|
fs.writeFileSync(path.join(tempDest, 'file.txt'), 'old content');
|
|
75
77
|
|
|
76
|
-
const
|
|
78
|
+
const result = utils.copyRecursive(tempSrc, tempDest, false);
|
|
77
79
|
|
|
78
|
-
expect(
|
|
80
|
+
expect(result.copied).toBe(0);
|
|
81
|
+
expect(result.skipped).toBe(1);
|
|
79
82
|
const content = fs.readFileSync(path.join(tempDest, 'file.txt'), 'utf-8');
|
|
80
83
|
expect(content).toBe('old content');
|
|
81
84
|
});
|
|
@@ -85,9 +88,10 @@ describe('Utils Module', () => {
|
|
|
85
88
|
fs.mkdirSync(tempDest, { recursive: true });
|
|
86
89
|
fs.writeFileSync(path.join(tempDest, 'file.txt'), 'old content');
|
|
87
90
|
|
|
88
|
-
const
|
|
91
|
+
const result = utils.copyRecursive(tempSrc, tempDest, true);
|
|
89
92
|
|
|
90
|
-
expect(
|
|
93
|
+
expect(result.copied).toBe(1);
|
|
94
|
+
expect(result.skipped).toBe(0);
|
|
91
95
|
const content = fs.readFileSync(path.join(tempDest, 'file.txt'), 'utf-8');
|
|
92
96
|
expect(content).toBe('new content');
|
|
93
97
|
});
|