ma-agents 1.6.0 → 1.8.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.md +14 -0
- package/bin/cli.js +98 -28
- package/lib/agents.js +12 -6
- package/lib/installer.js +99 -3
- package/package.json +1 -1
- package/skills/git-workflow-skill/skill.json +7 -1
- package/skills/js-ts-security-skill/skill.json +13 -2
- package/skills/logging-best-practices/skill.json +5 -0
- package/skills/test-accompanied-development/skill.json +5 -0
- package/skills/test-generator/skill.json +12 -2
- package/skills/vercel-react-best-practices/skill.json +14 -2
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
<!-- MA-AGENTS-START -->
|
|
3
|
+
# AI Agent Skills - Planning Instruction
|
|
4
|
+
|
|
5
|
+
You have access to a library of skills in your skills directory. Before starting any task:
|
|
6
|
+
|
|
7
|
+
1. Read the skill manifest at skills/MANIFEST.yaml
|
|
8
|
+
2. Based on the task description, select which skills are relevant
|
|
9
|
+
3. Read only the selected skill files
|
|
10
|
+
4. Then proceed with the task
|
|
11
|
+
|
|
12
|
+
Always load skills marked with always_load: true.
|
|
13
|
+
Do not load skills that are not relevant to the current task.
|
|
14
|
+
<!-- MA-AGENTS-END -->
|
package/bin/cli.js
CHANGED
|
@@ -120,60 +120,113 @@ async function installWizard(preselectedSkill, preselectedAgents, customPath, fo
|
|
|
120
120
|
const skills = listSkills();
|
|
121
121
|
const agents = listAgents();
|
|
122
122
|
|
|
123
|
+
let selectedSkillIds = preselectedSkill ? [preselectedSkill] : [];
|
|
124
|
+
let selectedAgentIds = preselectedAgents || [];
|
|
125
|
+
let installScope = 'project';
|
|
126
|
+
let installPath = customPath || '';
|
|
127
|
+
|
|
128
|
+
const existingStatus = getStatus([], '', 'project');
|
|
129
|
+
let isUpdate = false;
|
|
130
|
+
|
|
131
|
+
// Step 0: Check for existing installation ONLY in interactive mode (no preselected skill)
|
|
132
|
+
if (!preselectedSkill && !customPath && existingStatus.length > 0) {
|
|
133
|
+
console.log(chalk.cyan('\n Existing installation detected in project.'));
|
|
134
|
+
|
|
135
|
+
const { action } = await prompts({
|
|
136
|
+
type: 'select',
|
|
137
|
+
name: 'action',
|
|
138
|
+
message: 'What would you like to do?',
|
|
139
|
+
choices: [
|
|
140
|
+
{ title: 'Update (add/remove skills)', value: 'update' },
|
|
141
|
+
{ title: 'Clean reinstall (remove all first)', value: 'reinstall' },
|
|
142
|
+
{ title: 'Uninstall all skills', value: 'uninstall' },
|
|
143
|
+
{ title: 'Cancel', value: 'cancel' }
|
|
144
|
+
]
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (!action || action === 'cancel') process.exit(0);
|
|
148
|
+
|
|
149
|
+
if (action === 'uninstall') {
|
|
150
|
+
const { confirmed } = await prompts({
|
|
151
|
+
type: 'confirm',
|
|
152
|
+
name: 'confirmed',
|
|
153
|
+
message: 'This will remove all ma-agents skills from this project. Are you sure?',
|
|
154
|
+
initial: false
|
|
155
|
+
});
|
|
156
|
+
if (!confirmed) process.exit(0);
|
|
157
|
+
|
|
158
|
+
for (const entry of existingStatus) {
|
|
159
|
+
for (const skillId of Object.keys(entry.skills)) {
|
|
160
|
+
await uninstallSkill(skillId, [entry.agent.id], '', 'project');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
console.log(chalk.bold.green('\n All skills successfully removed!\n'));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (action === 'reinstall') {
|
|
168
|
+
for (const entry of existingStatus) {
|
|
169
|
+
for (const skillId of Object.keys(entry.skills)) {
|
|
170
|
+
await uninstallSkill(skillId, [entry.agent.id], '', 'project');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
console.log(chalk.gray(' Clean slate prepared.'));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (action === 'update') {
|
|
177
|
+
isUpdate = true;
|
|
178
|
+
// Pre-populate selections from existing status
|
|
179
|
+
const existingSkillIds = new Set();
|
|
180
|
+
const existingAgentIds = new Set();
|
|
181
|
+
existingStatus.forEach(entry => {
|
|
182
|
+
existingAgentIds.add(entry.agent.id);
|
|
183
|
+
Object.keys(entry.skills).forEach(id => existingSkillIds.add(id));
|
|
184
|
+
});
|
|
185
|
+
selectedSkillIds = Array.from(existingSkillIds);
|
|
186
|
+
selectedAgentIds = Array.from(existingAgentIds);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
123
190
|
// Step 1: Select skills
|
|
124
|
-
|
|
125
|
-
if (preselectedSkill) {
|
|
126
|
-
selectedSkillIds = [preselectedSkill];
|
|
127
|
-
} else {
|
|
191
|
+
if (selectedSkillIds.length === 0 || isUpdate) {
|
|
128
192
|
const { skills: chosen } = await prompts({
|
|
129
193
|
type: 'multiselect',
|
|
130
194
|
name: 'skills',
|
|
131
|
-
message: '
|
|
195
|
+
message: 'Select the skills you want to have installed:',
|
|
132
196
|
choices: skills.map(s => ({
|
|
133
197
|
title: chalk.white(s.name) + chalk.gray(` v${s.version} - ${s.description}`),
|
|
134
198
|
value: s.id,
|
|
135
|
-
selected:
|
|
199
|
+
selected: selectedSkillIds.includes(s.id)
|
|
136
200
|
})),
|
|
137
201
|
instructions: chalk.gray(' Use space to select, enter to confirm'),
|
|
138
202
|
min: 1
|
|
139
203
|
});
|
|
140
204
|
|
|
141
|
-
if (!chosen
|
|
142
|
-
console.log(chalk.yellow('No skills selected. Exiting.'));
|
|
143
|
-
process.exit(0);
|
|
144
|
-
}
|
|
205
|
+
if (!chosen) process.exit(0);
|
|
145
206
|
selectedSkillIds = chosen;
|
|
146
207
|
}
|
|
147
208
|
|
|
148
209
|
// Step 2: Select agents
|
|
149
|
-
|
|
150
|
-
if (preselectedAgents && preselectedAgents.length > 0) {
|
|
151
|
-
selectedAgentIds = preselectedAgents;
|
|
152
|
-
} else {
|
|
210
|
+
if (selectedAgentIds.length === 0 || isUpdate) {
|
|
153
211
|
const { agents: chosen } = await prompts({
|
|
154
212
|
type: 'multiselect',
|
|
155
213
|
name: 'agents',
|
|
156
|
-
message: '
|
|
214
|
+
message: 'Select which coding agents to include:',
|
|
157
215
|
choices: agents.map(a => ({
|
|
158
216
|
title: chalk.white(a.name) + chalk.gray(` v${a.version} - ${a.description}`),
|
|
159
217
|
value: a.id,
|
|
160
|
-
selected:
|
|
218
|
+
selected: selectedAgentIds.includes(a.id)
|
|
161
219
|
})),
|
|
162
220
|
instructions: chalk.gray(' Use space to select, enter to confirm'),
|
|
163
221
|
min: 1
|
|
164
222
|
});
|
|
165
223
|
|
|
166
|
-
if (!chosen
|
|
167
|
-
console.log(chalk.yellow('No agents selected. Exiting.'));
|
|
168
|
-
process.exit(0);
|
|
169
|
-
}
|
|
224
|
+
if (!chosen) process.exit(0);
|
|
170
225
|
selectedAgentIds = chosen;
|
|
171
226
|
}
|
|
172
227
|
|
|
173
|
-
// Step 3:
|
|
174
|
-
|
|
175
|
-
let installScope = 'project';
|
|
176
|
-
if (!installPath) {
|
|
228
|
+
// Step 3: Scope (Skip if update, we already know it's project)
|
|
229
|
+
if (!isUpdate && !installPath) {
|
|
177
230
|
const { pathChoice } = await prompts({
|
|
178
231
|
type: 'select',
|
|
179
232
|
name: 'pathChoice',
|
|
@@ -209,16 +262,33 @@ async function installWizard(preselectedSkill, preselectedAgents, customPath, fo
|
|
|
209
262
|
const { confirmed } = await prompts({
|
|
210
263
|
type: 'confirm',
|
|
211
264
|
name: 'confirmed',
|
|
212
|
-
message: 'Proceed with installation?',
|
|
265
|
+
message: isUpdate ? 'Apply changes?' : 'Proceed with installation?',
|
|
213
266
|
initial: true
|
|
214
267
|
});
|
|
215
268
|
|
|
216
269
|
if (!confirmed) {
|
|
217
|
-
console.log(chalk.yellow('
|
|
270
|
+
console.log(chalk.yellow('Operation cancelled.'));
|
|
218
271
|
process.exit(0);
|
|
219
272
|
}
|
|
220
273
|
|
|
221
|
-
// Step 5:
|
|
274
|
+
// Step 5: Execution
|
|
275
|
+
if (isUpdate) {
|
|
276
|
+
// Identify removals
|
|
277
|
+
const currentlyInstalled = new Set();
|
|
278
|
+
existingStatus.forEach(entry => Object.keys(entry.skills).forEach(id => currentlyInstalled.add(id)));
|
|
279
|
+
|
|
280
|
+
const finalSkills = new Set(selectedSkillIds);
|
|
281
|
+
const toRemove = [...currentlyInstalled].filter(id => !finalSkills.has(id));
|
|
282
|
+
|
|
283
|
+
if (toRemove.length > 0) {
|
|
284
|
+
console.log(chalk.gray(`\n Cleaning up ${toRemove.length} removed skills...`));
|
|
285
|
+
for (const skillId of toRemove) {
|
|
286
|
+
await uninstallSkill(skillId, selectedAgentIds, installPath, 'project');
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Install requested skills
|
|
222
292
|
for (const skillId of selectedSkillIds) {
|
|
223
293
|
try {
|
|
224
294
|
await installSkill(skillId, selectedAgentIds, installPath, installScope, { force: !!forceFlag });
|
|
@@ -227,7 +297,7 @@ async function installWizard(preselectedSkill, preselectedAgents, customPath, fo
|
|
|
227
297
|
}
|
|
228
298
|
}
|
|
229
299
|
|
|
230
|
-
console.log(chalk.bold.green('\n
|
|
300
|
+
console.log(chalk.bold.green('\n Done!\n'));
|
|
231
301
|
}
|
|
232
302
|
|
|
233
303
|
// --- Command handlers ---
|
package/lib/agents.js
CHANGED
|
@@ -31,7 +31,8 @@ const agents = [
|
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
fileExtension: '.md',
|
|
34
|
-
template: 'claude-code'
|
|
34
|
+
template: 'claude-code',
|
|
35
|
+
instructionFiles: ['CLAUDE.md']
|
|
35
36
|
},
|
|
36
37
|
{
|
|
37
38
|
id: 'gemini',
|
|
@@ -50,7 +51,8 @@ const agents = [
|
|
|
50
51
|
}
|
|
51
52
|
},
|
|
52
53
|
fileExtension: '.md',
|
|
53
|
-
template: 'generic'
|
|
54
|
+
template: 'generic',
|
|
55
|
+
instructionFiles: [] // Gemini doesn't have a standard project-level instruction file yet
|
|
54
56
|
},
|
|
55
57
|
{
|
|
56
58
|
id: 'copilot',
|
|
@@ -69,7 +71,8 @@ const agents = [
|
|
|
69
71
|
}
|
|
70
72
|
},
|
|
71
73
|
fileExtension: '.md',
|
|
72
|
-
template: 'generic'
|
|
74
|
+
template: 'generic',
|
|
75
|
+
instructionFiles: [] // Copilot uses path-scoped instructions, maybe support .github/instructions/ later
|
|
73
76
|
},
|
|
74
77
|
{
|
|
75
78
|
id: 'kilocode',
|
|
@@ -88,7 +91,8 @@ const agents = [
|
|
|
88
91
|
}
|
|
89
92
|
},
|
|
90
93
|
fileExtension: '.md',
|
|
91
|
-
template: 'generic'
|
|
94
|
+
template: 'generic',
|
|
95
|
+
instructionFiles: []
|
|
92
96
|
},
|
|
93
97
|
{
|
|
94
98
|
id: 'cline',
|
|
@@ -112,7 +116,8 @@ const agents = [
|
|
|
112
116
|
resourceMap: {
|
|
113
117
|
'references': 'docs',
|
|
114
118
|
'assets': 'templates'
|
|
115
|
-
}
|
|
119
|
+
},
|
|
120
|
+
instructionFiles: ['.clinerules']
|
|
116
121
|
},
|
|
117
122
|
{
|
|
118
123
|
id: 'cursor',
|
|
@@ -131,7 +136,8 @@ const agents = [
|
|
|
131
136
|
}
|
|
132
137
|
},
|
|
133
138
|
fileExtension: '.md',
|
|
134
|
-
template: 'generic'
|
|
139
|
+
template: 'generic',
|
|
140
|
+
instructionFiles: [] // Cursor uses .cursorrules or individual .mdc files
|
|
135
141
|
}
|
|
136
142
|
];
|
|
137
143
|
|
package/lib/installer.js
CHANGED
|
@@ -52,6 +52,84 @@ function getInstalledSkillInfo(installPath, skillId) {
|
|
|
52
52
|
return manifest.skills[skillId];
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
async function generateSkillsManifest(installPath, agent) {
|
|
56
|
+
const skills = listSkills();
|
|
57
|
+
const manifest = readManifest(installPath);
|
|
58
|
+
if (!manifest || !manifest.skills) return;
|
|
59
|
+
|
|
60
|
+
const manifestYamlPath = path.join(installPath, 'MANIFEST.yaml');
|
|
61
|
+
let yamlContent = '# skills/MANIFEST.yaml\n\nskills:\n';
|
|
62
|
+
|
|
63
|
+
const skillIds = Object.keys(manifest.skills).sort();
|
|
64
|
+
for (const skillId of skillIds) {
|
|
65
|
+
const skill = skills.find(s => s.id === skillId);
|
|
66
|
+
if (!skill) continue;
|
|
67
|
+
|
|
68
|
+
yamlContent += ` - id: ${skillId}\n`;
|
|
69
|
+
yamlContent += ` file: skills/${skillId}/SKILL.md\n`;
|
|
70
|
+
yamlContent += ` description: ${skill.description}\n`;
|
|
71
|
+
|
|
72
|
+
if (skill.applies_when && Array.isArray(skill.applies_when)) {
|
|
73
|
+
yamlContent += ' applies_when:\n';
|
|
74
|
+
skill.applies_when.forEach(cond => {
|
|
75
|
+
yamlContent += ` - ${cond}\n`;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (skill.always_load) {
|
|
80
|
+
yamlContent += ' always_load: true\n';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
yamlContent += '\n';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
await fs.writeFile(manifestYamlPath, yamlContent, 'utf-8');
|
|
87
|
+
console.log(chalk.cyan(` + Generated ${manifestYamlPath}`));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function updateAgentInstructions(agent, projectRoot) {
|
|
91
|
+
if (!agent.instructionFiles || agent.instructionFiles.length === 0) return;
|
|
92
|
+
|
|
93
|
+
const planningInstruction = `
|
|
94
|
+
# AI Agent Skills - Planning Instruction
|
|
95
|
+
|
|
96
|
+
You have access to a library of skills in your skills directory. Before starting any task:
|
|
97
|
+
|
|
98
|
+
1. Read the skill manifest at skills/MANIFEST.yaml
|
|
99
|
+
2. Based on the task description, select which skills are relevant
|
|
100
|
+
3. Read only the selected skill files
|
|
101
|
+
4. Then proceed with the task
|
|
102
|
+
|
|
103
|
+
Always load skills marked with always_load: true.
|
|
104
|
+
Do not load skills that are not relevant to the current task.
|
|
105
|
+
`;
|
|
106
|
+
|
|
107
|
+
const markerStart = '<!-- MA-AGENTS-START -->';
|
|
108
|
+
const markerEnd = '<!-- MA-AGENTS-END -->';
|
|
109
|
+
const wrappedInstruction = `\n${markerStart}${planningInstruction}${markerEnd}\n`;
|
|
110
|
+
|
|
111
|
+
for (const fileName of agent.instructionFiles) {
|
|
112
|
+
const filePath = path.join(projectRoot, fileName);
|
|
113
|
+
let content = '';
|
|
114
|
+
|
|
115
|
+
if (fs.existsSync(filePath)) {
|
|
116
|
+
content = await fs.readFile(filePath, 'utf-8');
|
|
117
|
+
|
|
118
|
+
const regex = new RegExp(`${markerStart}[\\s\\S]*?${markerEnd}`, 'g');
|
|
119
|
+
if (regex.test(content)) {
|
|
120
|
+
content = content.replace(regex, wrappedInstruction.trim());
|
|
121
|
+
} else {
|
|
122
|
+
content += wrappedInstruction;
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
content = wrappedInstruction;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
129
|
+
console.log(chalk.cyan(` + Updated ${fileName}`));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
55
133
|
/**
|
|
56
134
|
* Compare two semver strings. Returns -1, 0, or 1.
|
|
57
135
|
*/
|
|
@@ -252,6 +330,12 @@ async function installSkill(skillId, agentIds, customPath = '', scope = 'project
|
|
|
252
330
|
delete manifest.skills[skillId];
|
|
253
331
|
writeManifest(installPath, manifest);
|
|
254
332
|
console.log(chalk.green(` - Removed ${skill.name} from ${agent.name}`));
|
|
333
|
+
|
|
334
|
+
// Generate MANIFEST.yaml and update agent instruction files
|
|
335
|
+
await generateSkillsManifest(installPath, agent);
|
|
336
|
+
if (scope === 'project') {
|
|
337
|
+
await updateAgentInstructions(agent, process.cwd());
|
|
338
|
+
}
|
|
255
339
|
continue;
|
|
256
340
|
}
|
|
257
341
|
|
|
@@ -281,6 +365,12 @@ async function installSkill(skillId, agentIds, customPath = '', scope = 'project
|
|
|
281
365
|
agentVersion: agent.version
|
|
282
366
|
};
|
|
283
367
|
writeManifest(installPath, manifest);
|
|
368
|
+
|
|
369
|
+
// Generate MANIFEST.yaml and update agent instruction files
|
|
370
|
+
await generateSkillsManifest(installPath, agent);
|
|
371
|
+
if (scope === 'project') {
|
|
372
|
+
await updateAgentInstructions(agent, process.cwd());
|
|
373
|
+
}
|
|
284
374
|
}
|
|
285
375
|
} catch (error) {
|
|
286
376
|
console.log(chalk.red(` x Failed: ${error.message}`));
|
|
@@ -330,6 +420,12 @@ async function uninstallSkill(skillId, agentIds, customPath = '', scope = 'proje
|
|
|
330
420
|
writeManifest(installPath, manifest);
|
|
331
421
|
|
|
332
422
|
console.log(chalk.green(` - Removed ${skillId} v${installed.version} from ${agent.name}`));
|
|
423
|
+
|
|
424
|
+
// Generate MANIFEST.yaml and update agent instruction files
|
|
425
|
+
await generateSkillsManifest(installPath, agent);
|
|
426
|
+
if (scope === 'project') {
|
|
427
|
+
await updateAgentInstructions(agent, process.cwd());
|
|
428
|
+
}
|
|
333
429
|
} catch (error) {
|
|
334
430
|
console.log(chalk.red(` x Failed: ${error.message}`));
|
|
335
431
|
}
|
|
@@ -353,9 +449,9 @@ function getStatus(agentIds, customPath = '', scope = 'project') {
|
|
|
353
449
|
const pathsToCheck = customPath
|
|
354
450
|
? [{ path: customPath, scope: 'custom' }]
|
|
355
451
|
: [
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
452
|
+
{ path: projectPath, scope: 'project' },
|
|
453
|
+
{ path: globalPath, scope: 'global' }
|
|
454
|
+
];
|
|
359
455
|
|
|
360
456
|
for (const { path: checkPath, scope: checkScope } of pathsToCheck) {
|
|
361
457
|
const manifest = readManifest(checkPath);
|
package/package.json
CHANGED
|
@@ -3,5 +3,16 @@
|
|
|
3
3
|
"description": "Verify security of JavaScript and TypeScript codebases against OWASP Top 10 2025 standards",
|
|
4
4
|
"version": "1.0.0",
|
|
5
5
|
"author": "AI Agent Skills",
|
|
6
|
-
"tags": [
|
|
7
|
-
|
|
6
|
+
"tags": [
|
|
7
|
+
"javascript",
|
|
8
|
+
"typescript",
|
|
9
|
+
"security",
|
|
10
|
+
"OWASP",
|
|
11
|
+
"vulnerability-scanning"
|
|
12
|
+
],
|
|
13
|
+
"applies_when": [
|
|
14
|
+
"writing or reviewing security-critical code",
|
|
15
|
+
"analyzing third-party dependencies",
|
|
16
|
+
"performing security audits"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -3,5 +3,15 @@
|
|
|
3
3
|
"description": "Generates comprehensive unit and integration tests",
|
|
4
4
|
"version": "1.0.0",
|
|
5
5
|
"author": "AI Agent Skills",
|
|
6
|
-
"tags": [
|
|
7
|
-
|
|
6
|
+
"tags": [
|
|
7
|
+
"testing",
|
|
8
|
+
"unit-tests",
|
|
9
|
+
"integration-tests",
|
|
10
|
+
"quality"
|
|
11
|
+
],
|
|
12
|
+
"applies_when": [
|
|
13
|
+
"creating new code files",
|
|
14
|
+
"adding complex logic",
|
|
15
|
+
"refactoring existing code"
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -4,5 +4,17 @@
|
|
|
4
4
|
"version": "1.0.0",
|
|
5
5
|
"author": "vercel-labs",
|
|
6
6
|
"source": "https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices",
|
|
7
|
-
"tags": [
|
|
8
|
-
|
|
7
|
+
"tags": [
|
|
8
|
+
"react",
|
|
9
|
+
"nextjs",
|
|
10
|
+
"performance",
|
|
11
|
+
"optimization",
|
|
12
|
+
"vercel",
|
|
13
|
+
"best-practices"
|
|
14
|
+
],
|
|
15
|
+
"applies_when": [
|
|
16
|
+
"working on React or Next.js components",
|
|
17
|
+
"optimizing page performance",
|
|
18
|
+
"reviewing frontend code architecture"
|
|
19
|
+
]
|
|
20
|
+
}
|