ma-agents 1.3.0 → 1.5.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/DEVELOPMENT.md +173 -0
- package/README.md +201 -309
- package/SKILLS_STRUCTURE.md +392 -0
- package/bin/cli.js +127 -28
- package/index.js +8 -6
- package/lib/agents.js +12 -1
- package/lib/installer.js +311 -44
- package/package.json +2 -2
- package/skills/README.md +25 -0
- package/skills/code-review/SKILL.md +39 -0
- package/skills/commit-message/SKILL.md +75 -0
- package/skills/create-hardened-docker-skill/SKILL.md +0 -5
- package/skills/git-workflow-skill/SKILL.md +0 -5
- package/skills/js-ts-security-skill/SKILL.md +0 -4
- package/skills/logging-best-practices/SKILL.md +46 -0
- package/skills/logging-best-practices/examples/cpp.md +36 -0
- package/skills/logging-best-practices/examples/csharp.md +49 -0
- package/skills/logging-best-practices/examples/javascript.md +77 -0
- package/skills/logging-best-practices/examples/python.md +57 -0
- package/skills/logging-best-practices/references/logging-standards.md +29 -0
- package/skills/logging-best-practices/skill.json +13 -0
- package/skills/skill-creator/SKILL.md +211 -0
- package/skills/skill-creator/claude-code.md +6 -8
- package/skills/skill-creator/generic.md +0 -5
- package/skills/test-accompanied-development/SKILL.md +39 -0
- package/skills/test-accompanied-development/skill.json +12 -0
- package/skills/test-generator/SKILL.md +61 -0
- package/skills/vercel-react-best-practices/SKILL.md +105 -0
- package/skills/verify-hardened-docker-skill/SKILL.md +0 -5
package/index.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* ma-agents - Programmatic API
|
|
3
3
|
*
|
|
4
4
|
* This module exports the core functionality for use as a library.
|
|
5
|
-
* For CLI usage, use: npx
|
|
5
|
+
* For CLI usage, use: npx ma-agents
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
const { installSkill, listSkills, listAgents } = require('./lib/installer');
|
|
8
|
+
const { installSkill, uninstallSkill, getStatus, listSkills, listAgents, readManifest, getInstalledSkillInfo, compareSemver } = require('./lib/installer');
|
|
9
9
|
const { getAgent, getAllAgents } = require('./lib/agents');
|
|
10
10
|
|
|
11
11
|
module.exports = {
|
|
12
|
-
// Installer functions
|
|
13
12
|
installSkill,
|
|
13
|
+
uninstallSkill,
|
|
14
|
+
getStatus,
|
|
14
15
|
listSkills,
|
|
15
16
|
listAgents,
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
readManifest,
|
|
18
|
+
getInstalledSkillInfo,
|
|
19
|
+
compareSemver,
|
|
18
20
|
getAgent,
|
|
19
21
|
getAllAgents
|
|
20
22
|
};
|
package/lib/agents.js
CHANGED
|
@@ -17,6 +17,7 @@ const agents = [
|
|
|
17
17
|
{
|
|
18
18
|
id: 'claude-code',
|
|
19
19
|
name: 'Claude Code',
|
|
20
|
+
version: '1.0.0',
|
|
20
21
|
description: 'Anthropic Claude Code CLI',
|
|
21
22
|
getProjectPath: () => path.join(process.cwd(), '.claude', 'skills'),
|
|
22
23
|
getGlobalPath: () => {
|
|
@@ -35,6 +36,7 @@ const agents = [
|
|
|
35
36
|
{
|
|
36
37
|
id: 'gemini',
|
|
37
38
|
name: 'Google Gemini',
|
|
39
|
+
version: '1.0.0',
|
|
38
40
|
description: 'Google Gemini Code Assist',
|
|
39
41
|
getProjectPath: () => path.join(process.cwd(), '.gemini', 'skills'),
|
|
40
42
|
getGlobalPath: () => {
|
|
@@ -53,6 +55,7 @@ const agents = [
|
|
|
53
55
|
{
|
|
54
56
|
id: 'copilot',
|
|
55
57
|
name: 'GitHub Copilot',
|
|
58
|
+
version: '1.0.0',
|
|
56
59
|
description: 'GitHub Copilot Agent Mode',
|
|
57
60
|
getProjectPath: () => path.join(process.cwd(), '.github', 'copilot', 'skills'),
|
|
58
61
|
getGlobalPath: () => {
|
|
@@ -71,6 +74,7 @@ const agents = [
|
|
|
71
74
|
{
|
|
72
75
|
id: 'kilocode',
|
|
73
76
|
name: 'Kilocode',
|
|
77
|
+
version: '1.0.0',
|
|
74
78
|
description: 'Kilocode AI Assistant',
|
|
75
79
|
getProjectPath: () => path.join(process.cwd(), '.kilocode', 'skills'),
|
|
76
80
|
getGlobalPath: () => {
|
|
@@ -89,6 +93,7 @@ const agents = [
|
|
|
89
93
|
{
|
|
90
94
|
id: 'cline',
|
|
91
95
|
name: 'Cline',
|
|
96
|
+
version: '1.0.0',
|
|
92
97
|
description: 'Cline AI Assistant',
|
|
93
98
|
getProjectPath: () => path.join(process.cwd(), '.cline', 'skills'),
|
|
94
99
|
getGlobalPath: () => {
|
|
@@ -102,11 +107,17 @@ const agents = [
|
|
|
102
107
|
}
|
|
103
108
|
},
|
|
104
109
|
fileExtension: '.md',
|
|
105
|
-
template: 'cline'
|
|
110
|
+
template: 'cline',
|
|
111
|
+
// Cline uses different directory names than the generic structure
|
|
112
|
+
resourceMap: {
|
|
113
|
+
'references': 'docs',
|
|
114
|
+
'assets': 'templates'
|
|
115
|
+
}
|
|
106
116
|
},
|
|
107
117
|
{
|
|
108
118
|
id: 'cursor',
|
|
109
119
|
name: 'Cursor',
|
|
120
|
+
version: '1.0.0',
|
|
110
121
|
description: 'Cursor AI Editor',
|
|
111
122
|
getProjectPath: () => path.join(process.cwd(), '.cursor', 'skills'),
|
|
112
123
|
getGlobalPath: () => {
|
package/lib/installer.js
CHANGED
|
@@ -1,11 +1,72 @@
|
|
|
1
1
|
const fs = require('fs-extra');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
|
+
const prompts = require('prompts');
|
|
4
5
|
const { getAgent, getAllAgents } = require('./agents');
|
|
5
6
|
|
|
7
|
+
const MANIFEST_FILE = '.ma-agents.json';
|
|
8
|
+
const MANIFEST_VERSION = '1.0.0';
|
|
9
|
+
|
|
10
|
+
function getPackageVersion() {
|
|
11
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
12
|
+
return pkg.version;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// --- Manifest functions ---
|
|
16
|
+
|
|
17
|
+
function readManifest(installPath) {
|
|
18
|
+
const manifestPath = path.join(installPath, MANIFEST_FILE);
|
|
19
|
+
if (!fs.existsSync(manifestPath)) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function writeManifest(installPath, manifest) {
|
|
30
|
+
const manifestPath = path.join(installPath, MANIFEST_FILE);
|
|
31
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function ensureManifest(installPath, agentId, scope) {
|
|
35
|
+
let manifest = readManifest(installPath);
|
|
36
|
+
if (!manifest) {
|
|
37
|
+
manifest = {
|
|
38
|
+
manifestVersion: MANIFEST_VERSION,
|
|
39
|
+
agent: agentId,
|
|
40
|
+
scope: scope,
|
|
41
|
+
skills: {}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return manifest;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getInstalledSkillInfo(installPath, skillId) {
|
|
48
|
+
const manifest = readManifest(installPath);
|
|
49
|
+
if (!manifest || !manifest.skills || !manifest.skills[skillId]) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return manifest.skills[skillId];
|
|
53
|
+
}
|
|
54
|
+
|
|
6
55
|
/**
|
|
7
|
-
*
|
|
56
|
+
* Compare two semver strings. Returns -1, 0, or 1.
|
|
8
57
|
*/
|
|
58
|
+
function compareSemver(a, b) {
|
|
59
|
+
const pa = (a || '0.0.0').split('.').map(Number);
|
|
60
|
+
const pb = (b || '0.0.0').split('.').map(Number);
|
|
61
|
+
for (let i = 0; i < 3; i++) {
|
|
62
|
+
if (pa[i] > pb[i]) return 1;
|
|
63
|
+
if (pa[i] < pb[i]) return -1;
|
|
64
|
+
}
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// --- Skill listing ---
|
|
69
|
+
|
|
9
70
|
function listSkills() {
|
|
10
71
|
const skillsDir = path.join(__dirname, '..', 'skills');
|
|
11
72
|
|
|
@@ -32,17 +93,82 @@ function listSkills() {
|
|
|
32
93
|
}).filter(Boolean);
|
|
33
94
|
}
|
|
34
95
|
|
|
35
|
-
/**
|
|
36
|
-
* Get all supported agents
|
|
37
|
-
*/
|
|
38
96
|
function listAgents() {
|
|
39
97
|
return getAllAgents();
|
|
40
98
|
}
|
|
41
99
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
100
|
+
// --- Core install logic (no prompts) ---
|
|
101
|
+
|
|
102
|
+
async function performInstall(skillId, skill, agent, installPath) {
|
|
103
|
+
const skillSourceDir = path.join(__dirname, '..', 'skills', skillId);
|
|
104
|
+
let sourceFile = path.join(skillSourceDir, `${agent.template}${agent.fileExtension}`);
|
|
105
|
+
|
|
106
|
+
if (!fs.existsSync(sourceFile)) {
|
|
107
|
+
sourceFile = path.join(skillSourceDir, `generic${agent.fileExtension}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!fs.existsSync(sourceFile)) {
|
|
111
|
+
const skillMdPath = path.join(skillSourceDir, 'SKILL.md');
|
|
112
|
+
if (fs.existsSync(skillMdPath)) {
|
|
113
|
+
sourceFile = skillMdPath;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!fs.existsSync(sourceFile)) {
|
|
118
|
+
console.log(chalk.yellow(` Warning: No template found for ${agent.name}, skipping`));
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const skillDir = path.join(installPath, skillId);
|
|
123
|
+
await fs.ensureDir(skillDir);
|
|
124
|
+
|
|
125
|
+
let content = await fs.readFile(sourceFile, 'utf-8');
|
|
126
|
+
|
|
127
|
+
// Strip any existing YAML frontmatter from the source
|
|
128
|
+
const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
|
|
129
|
+
content = content.replace(frontmatterRegex, '');
|
|
130
|
+
|
|
131
|
+
// Inject YAML frontmatter from skill.json (single source of truth)
|
|
132
|
+
const frontmatter = [
|
|
133
|
+
'---',
|
|
134
|
+
`name: ${skill.name}`,
|
|
135
|
+
`description: ${skill.description}`,
|
|
136
|
+
'---',
|
|
137
|
+
''
|
|
138
|
+
].join('\n');
|
|
139
|
+
content = frontmatter + content;
|
|
140
|
+
|
|
141
|
+
const targetFile = path.join(skillDir, 'SKILL.md');
|
|
142
|
+
await fs.writeFile(targetFile, content, 'utf-8');
|
|
143
|
+
console.log(chalk.green(` + Installed to ${targetFile}`));
|
|
144
|
+
|
|
145
|
+
// Copy bundled resources
|
|
146
|
+
const resourceMap = agent.resourceMap || {};
|
|
147
|
+
const resourceDirs = ['scripts', 'references', 'assets', 'examples', 'hooks', 'docs', 'templates'];
|
|
148
|
+
for (const dir of resourceDirs) {
|
|
149
|
+
const resourceSource = path.join(skillSourceDir, dir);
|
|
150
|
+
if (fs.existsSync(resourceSource)) {
|
|
151
|
+
const targetDirName = resourceMap[dir] || dir;
|
|
152
|
+
const resourceTarget = path.join(skillDir, targetDirName);
|
|
153
|
+
await fs.copy(resourceSource, resourceTarget);
|
|
154
|
+
console.log(chalk.green(` + Copied ${dir}/ → ${targetDirName}/`));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Copy template.md if it exists
|
|
159
|
+
const templateSource = path.join(skillSourceDir, 'template.md');
|
|
160
|
+
if (fs.existsSync(templateSource)) {
|
|
161
|
+
await fs.copy(templateSource, path.join(skillDir, 'template.md'));
|
|
162
|
+
console.log(chalk.green(` + Copied template.md`));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// --- Install with upgrade detection ---
|
|
169
|
+
|
|
170
|
+
async function installSkill(skillId, agentIds, customPath = '', scope = 'project', options = {}) {
|
|
171
|
+
const { force = false } = options;
|
|
46
172
|
const skills = listSkills();
|
|
47
173
|
const skill = skills.find(s => s.id === skillId);
|
|
48
174
|
|
|
@@ -50,7 +176,7 @@ async function installSkill(skillId, agentIds, customPath = '', scope = 'project
|
|
|
50
176
|
throw new Error(`Skill '${skillId}' not found. Run "list" to see available skills.`);
|
|
51
177
|
}
|
|
52
178
|
|
|
53
|
-
console.log(chalk.cyan(`\nInstalling skill: ${skill.name}`));
|
|
179
|
+
console.log(chalk.cyan(`\nInstalling skill: ${skill.name} v${skill.version}`));
|
|
54
180
|
|
|
55
181
|
for (const agentId of agentIds) {
|
|
56
182
|
const agent = getAgent(agentId);
|
|
@@ -60,61 +186,202 @@ async function installSkill(skillId, agentIds, customPath = '', scope = 'project
|
|
|
60
186
|
continue;
|
|
61
187
|
}
|
|
62
188
|
|
|
63
|
-
console.log(chalk.gray(` Installing for ${agent.name}...`));
|
|
64
|
-
|
|
65
189
|
try {
|
|
66
|
-
// Determine installation path
|
|
67
190
|
const installPath = customPath || (scope === 'global' ? agent.getGlobalPath() : agent.getProjectPath());
|
|
68
|
-
|
|
69
|
-
// Ensure the skills directory exists
|
|
70
191
|
await fs.ensureDir(installPath);
|
|
71
192
|
|
|
72
|
-
|
|
73
|
-
const skillSourceDir = path.join(__dirname, '..', 'skills', skillId);
|
|
74
|
-
let sourceFile = path.join(skillSourceDir, `${agent.template}${agent.fileExtension}`);
|
|
193
|
+
const installed = getInstalledSkillInfo(installPath, skillId);
|
|
75
194
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
sourceFile = path.join(skillSourceDir, `generic${agent.fileExtension}`);
|
|
79
|
-
}
|
|
195
|
+
if (installed && !force) {
|
|
196
|
+
const cmp = compareSemver(skill.version, installed.version);
|
|
80
197
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
198
|
+
let action;
|
|
199
|
+
if (cmp > 0) {
|
|
200
|
+
// Upgrade available
|
|
201
|
+
console.log(chalk.yellow(` ${skill.name} v${installed.version} → v${skill.version} update available for ${agent.name}`));
|
|
202
|
+
const { choice } = await prompts({
|
|
203
|
+
type: 'select',
|
|
204
|
+
name: 'choice',
|
|
205
|
+
message: 'What would you like to do?',
|
|
206
|
+
choices: [
|
|
207
|
+
{ title: 'Update (recommended)', value: 'update' },
|
|
208
|
+
{ title: 'Skip (keep current)', value: 'skip' },
|
|
209
|
+
{ title: 'Clean reinstall', value: 'reinstall' },
|
|
210
|
+
{ title: 'Remove (uninstall)', value: 'remove' }
|
|
211
|
+
]
|
|
212
|
+
});
|
|
213
|
+
action = choice;
|
|
214
|
+
} else if (cmp === 0) {
|
|
215
|
+
// Same version
|
|
216
|
+
console.log(chalk.gray(` ${skill.name} v${installed.version} already installed for ${agent.name}`));
|
|
217
|
+
const { choice } = await prompts({
|
|
218
|
+
type: 'select',
|
|
219
|
+
name: 'choice',
|
|
220
|
+
message: 'What would you like to do?',
|
|
221
|
+
choices: [
|
|
222
|
+
{ title: 'Skip (keep current)', value: 'skip' },
|
|
223
|
+
{ title: 'Clean reinstall', value: 'reinstall' },
|
|
224
|
+
{ title: 'Remove (uninstall)', value: 'remove' }
|
|
225
|
+
]
|
|
226
|
+
});
|
|
227
|
+
action = choice;
|
|
228
|
+
} else {
|
|
229
|
+
// Downgrade
|
|
230
|
+
console.log(chalk.yellow(` ${skill.name} v${installed.version} installed, package has v${skill.version} for ${agent.name}`));
|
|
231
|
+
const { choice } = await prompts({
|
|
232
|
+
type: 'select',
|
|
233
|
+
name: 'choice',
|
|
234
|
+
message: 'What would you like to do?',
|
|
235
|
+
choices: [
|
|
236
|
+
{ title: 'Skip (keep current)', value: 'skip' },
|
|
237
|
+
{ title: `Downgrade to v${skill.version}`, value: 'update' },
|
|
238
|
+
{ title: 'Remove (uninstall)', value: 'remove' }
|
|
239
|
+
]
|
|
240
|
+
});
|
|
241
|
+
action = choice;
|
|
86
242
|
}
|
|
243
|
+
|
|
244
|
+
if (!action || action === 'skip') {
|
|
245
|
+
console.log(chalk.gray(` Skipped`));
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (action === 'remove') {
|
|
250
|
+
await performUninstall(skillId, installPath);
|
|
251
|
+
const manifest = ensureManifest(installPath, agentId, scope);
|
|
252
|
+
delete manifest.skills[skillId];
|
|
253
|
+
writeManifest(installPath, manifest);
|
|
254
|
+
console.log(chalk.green(` - Removed ${skill.name} from ${agent.name}`));
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (action === 'reinstall') {
|
|
259
|
+
await performUninstall(skillId, installPath);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// action === 'update' or 'reinstall' → proceed to install below
|
|
87
263
|
}
|
|
88
264
|
|
|
89
|
-
if (!
|
|
90
|
-
console.log(chalk.
|
|
91
|
-
continue;
|
|
265
|
+
if (!installed || force) {
|
|
266
|
+
console.log(chalk.gray(` Installing for ${agent.name}...`));
|
|
92
267
|
}
|
|
93
268
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
269
|
+
const success = await performInstall(skillId, skill, agent, installPath);
|
|
270
|
+
|
|
271
|
+
if (success) {
|
|
272
|
+
// Update manifest
|
|
273
|
+
const manifest = ensureManifest(installPath, agentId, scope);
|
|
274
|
+
const now = new Date().toISOString();
|
|
275
|
+
const existing = manifest.skills[skillId];
|
|
276
|
+
manifest.skills[skillId] = {
|
|
277
|
+
version: skill.version,
|
|
278
|
+
installedAt: existing ? existing.installedAt : now,
|
|
279
|
+
updatedAt: now,
|
|
280
|
+
installerVersion: getPackageVersion(),
|
|
281
|
+
agentVersion: agent.version
|
|
282
|
+
};
|
|
283
|
+
writeManifest(installPath, manifest);
|
|
284
|
+
}
|
|
285
|
+
} catch (error) {
|
|
286
|
+
console.log(chalk.red(` x Failed: ${error.message}`));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// --- Uninstall ---
|
|
292
|
+
|
|
293
|
+
async function performUninstall(skillId, installPath) {
|
|
294
|
+
const skillDir = path.join(installPath, skillId);
|
|
295
|
+
if (fs.existsSync(skillDir)) {
|
|
296
|
+
await fs.remove(skillDir);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async function uninstallSkill(skillId, agentIds, customPath = '', scope = 'project') {
|
|
301
|
+
console.log(chalk.cyan(`\nUninstalling skill: ${skillId}`));
|
|
302
|
+
|
|
303
|
+
for (const agentId of agentIds) {
|
|
304
|
+
const agent = getAgent(agentId);
|
|
305
|
+
|
|
306
|
+
if (!agent) {
|
|
307
|
+
console.log(chalk.yellow(` Warning: Skipping unknown agent: ${agentId}`));
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const installPath = customPath || (scope === 'global' ? agent.getGlobalPath() : agent.getProjectPath());
|
|
313
|
+
const installed = getInstalledSkillInfo(installPath, skillId);
|
|
314
|
+
|
|
315
|
+
if (!installed) {
|
|
316
|
+
const skillDir = path.join(installPath, skillId);
|
|
317
|
+
if (fs.existsSync(skillDir)) {
|
|
318
|
+
await performUninstall(skillId, installPath);
|
|
319
|
+
console.log(chalk.green(` - Removed ${skillId} from ${agent.name} (legacy install)`));
|
|
320
|
+
} else {
|
|
321
|
+
console.log(chalk.gray(` ${skillId} is not installed for ${agent.name}`));
|
|
108
322
|
}
|
|
323
|
+
continue;
|
|
109
324
|
}
|
|
325
|
+
|
|
326
|
+
await performUninstall(skillId, installPath);
|
|
327
|
+
|
|
328
|
+
const manifest = ensureManifest(installPath, agentId, scope);
|
|
329
|
+
delete manifest.skills[skillId];
|
|
330
|
+
writeManifest(installPath, manifest);
|
|
331
|
+
|
|
332
|
+
console.log(chalk.green(` - Removed ${skillId} v${installed.version} from ${agent.name}`));
|
|
110
333
|
} catch (error) {
|
|
111
334
|
console.log(chalk.red(` x Failed: ${error.message}`));
|
|
112
335
|
}
|
|
113
336
|
}
|
|
114
337
|
}
|
|
115
338
|
|
|
339
|
+
// --- Status ---
|
|
340
|
+
|
|
341
|
+
function getStatus(agentIds, customPath = '', scope = 'project') {
|
|
342
|
+
const results = [];
|
|
343
|
+
|
|
344
|
+
const targetAgents = agentIds && agentIds.length > 0
|
|
345
|
+
? agentIds.map(id => getAgent(id)).filter(Boolean)
|
|
346
|
+
: getAllAgents();
|
|
347
|
+
|
|
348
|
+
for (const agent of targetAgents) {
|
|
349
|
+
const projectPath = agent.getProjectPath();
|
|
350
|
+
const globalPath = agent.getGlobalPath();
|
|
351
|
+
|
|
352
|
+
// Check both scopes unless custom path is specified
|
|
353
|
+
const pathsToCheck = customPath
|
|
354
|
+
? [{ path: customPath, scope: 'custom' }]
|
|
355
|
+
: [
|
|
356
|
+
{ path: projectPath, scope: 'project' },
|
|
357
|
+
{ path: globalPath, scope: 'global' }
|
|
358
|
+
];
|
|
359
|
+
|
|
360
|
+
for (const { path: checkPath, scope: checkScope } of pathsToCheck) {
|
|
361
|
+
const manifest = readManifest(checkPath);
|
|
362
|
+
if (!manifest || !manifest.skills || Object.keys(manifest.skills).length === 0) {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
results.push({
|
|
367
|
+
agent: agent,
|
|
368
|
+
installPath: checkPath,
|
|
369
|
+
scope: checkScope,
|
|
370
|
+
skills: manifest.skills
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return results;
|
|
376
|
+
}
|
|
377
|
+
|
|
116
378
|
module.exports = {
|
|
117
379
|
listSkills,
|
|
118
380
|
listAgents,
|
|
119
|
-
installSkill
|
|
381
|
+
installSkill,
|
|
382
|
+
uninstallSkill,
|
|
383
|
+
getStatus,
|
|
384
|
+
readManifest,
|
|
385
|
+
getInstalledSkillInfo,
|
|
386
|
+
compareSemver
|
|
120
387
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ma-agents",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "NPX tool to install skills for AI coding agents (Claude Code, Gemini, Copilot, Kilocode, Cline, Cursor)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -32,4 +32,4 @@
|
|
|
32
32
|
"engines": {
|
|
33
33
|
"node": ">=14.0.0"
|
|
34
34
|
}
|
|
35
|
-
}
|
|
35
|
+
}
|
package/skills/README.md
CHANGED
|
@@ -191,6 +191,31 @@ Creates production-ready hardened Docker configurations following security best
|
|
|
191
191
|
|
|
192
192
|
---
|
|
193
193
|
|
|
194
|
+
### 5. Test-Accompanied Development (TAD)
|
|
195
|
+
**Directory:** `test-accompanied-development/`
|
|
196
|
+
|
|
197
|
+
Enforces a "Test-Alongside" policy where every public method is accompanied by a corresponding unit test. It ensures high code quality by mandating that all public interfaces are verified by automated tests at the moment of creation.
|
|
198
|
+
|
|
199
|
+
**Key Features:**
|
|
200
|
+
- ✅ **Automatic Enforcement**: Mandates tests for every new public method.
|
|
201
|
+
- ✅ **Integration**: References `test-generator` for implementation standards.
|
|
202
|
+
- ✅ **Workflow Guardrail**: Prevents "test debt" by requiring tests alongside code.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
### 6. Logging Best Practices
|
|
207
|
+
**Directory:** `logging-best-practices/`
|
|
208
|
+
|
|
209
|
+
Standardizes structured logging (JSON) across Backend, Frontend, Realtime, and Algorithmic domains. Enforces mandatory exception logging and OpenTelemetry-aligned context fields.
|
|
210
|
+
|
|
211
|
+
**Key Features:**
|
|
212
|
+
- ✅ **Structured Logging**: Mandates JSON format for machine-readability.
|
|
213
|
+
- ✅ **Domain-Specific**: Tailored guidance for Web, Server, and High-Performance Algorithm domains.
|
|
214
|
+
- ✅ **Mandatory Exceptions**: Every catch block must log full stack traces.
|
|
215
|
+
- ✅ **Context-Rich**: Standardizes fields like `trace_id`, `placement`, and `process_name`.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
194
219
|
## Requirements
|
|
195
220
|
|
|
196
221
|
### All Skills
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Code Review
|
|
2
|
+
|
|
3
|
+
Perform comprehensive code reviews following industry best practices.
|
|
4
|
+
|
|
5
|
+
## What to Review
|
|
6
|
+
|
|
7
|
+
1. **Code Quality**: Readability, naming conventions, structure
|
|
8
|
+
2. **Best Practices**: Language-specific patterns, error handling, performance
|
|
9
|
+
3. **Security**: Common vulnerabilities (SQL injection, XSS, CSRF, etc.)
|
|
10
|
+
4. **Testing**: Coverage, edge cases, test quality
|
|
11
|
+
5. **Documentation**: Comments, API docs, clarity
|
|
12
|
+
|
|
13
|
+
## Review Process
|
|
14
|
+
|
|
15
|
+
- Analyze code for bugs and logical errors
|
|
16
|
+
- Check adherence to coding standards
|
|
17
|
+
- Identify security vulnerabilities
|
|
18
|
+
- Suggest refactoring opportunities
|
|
19
|
+
- Assess test coverage and documentation
|
|
20
|
+
|
|
21
|
+
## Output Format
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
## Code Review Summary
|
|
25
|
+
|
|
26
|
+
### Strengths
|
|
27
|
+
- [Positive aspects]
|
|
28
|
+
|
|
29
|
+
### Issues Found
|
|
30
|
+
- **[High/Medium/Low]** [Issue description]
|
|
31
|
+
- Location: [file:line]
|
|
32
|
+
- Fix: [recommendation]
|
|
33
|
+
|
|
34
|
+
### Suggestions
|
|
35
|
+
- [Improvements]
|
|
36
|
+
|
|
37
|
+
### Overall Assessment
|
|
38
|
+
[Summary and rating]
|
|
39
|
+
```
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Commit Message Generator
|
|
2
|
+
|
|
3
|
+
Generate meaningful commit messages following Conventional Commits specification.
|
|
4
|
+
|
|
5
|
+
## Format
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
<type>(<scope>): <subject>
|
|
9
|
+
|
|
10
|
+
<body>
|
|
11
|
+
|
|
12
|
+
<footer>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Types
|
|
16
|
+
|
|
17
|
+
- `feat`: New feature
|
|
18
|
+
- `fix`: Bug fix
|
|
19
|
+
- `docs`: Documentation changes
|
|
20
|
+
- `style`: Code style/formatting
|
|
21
|
+
- `refactor`: Code refactoring
|
|
22
|
+
- `test`: Adding/updating tests
|
|
23
|
+
- `chore`: Maintenance tasks
|
|
24
|
+
- `perf`: Performance improvements
|
|
25
|
+
- `ci`: CI/CD changes
|
|
26
|
+
- `build`: Build system changes
|
|
27
|
+
- `revert`: Revert previous commit
|
|
28
|
+
|
|
29
|
+
## Guidelines
|
|
30
|
+
|
|
31
|
+
1. **Subject line** (max 50 chars):
|
|
32
|
+
- Use imperative mood ("Add" not "Added")
|
|
33
|
+
- Don't capitalize first letter
|
|
34
|
+
- No period at the end
|
|
35
|
+
|
|
36
|
+
2. **Body** (optional):
|
|
37
|
+
- Explain what and why, not how
|
|
38
|
+
- Wrap at 72 characters
|
|
39
|
+
|
|
40
|
+
3. **Footer** (optional):
|
|
41
|
+
- Breaking changes: `BREAKING CHANGE: description`
|
|
42
|
+
- Issue references: `Fixes #123`
|
|
43
|
+
|
|
44
|
+
## Examples
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
feat(auth): add JWT token refresh mechanism
|
|
48
|
+
|
|
49
|
+
Implement automatic token refresh to improve user experience
|
|
50
|
+
and reduce re-authentication prompts.
|
|
51
|
+
|
|
52
|
+
Fixes #456
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
fix(api): resolve memory leak in user service
|
|
57
|
+
|
|
58
|
+
The user cache was not being cleared properly, causing
|
|
59
|
+
memory to grow over time.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
docs: update installation instructions
|
|
64
|
+
|
|
65
|
+
Add steps for Windows users and clarify dependency requirements.
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Process
|
|
69
|
+
|
|
70
|
+
1. Analyze the code changes
|
|
71
|
+
2. Determine the type of change
|
|
72
|
+
3. Identify the scope (component/module affected)
|
|
73
|
+
4. Write clear, concise subject
|
|
74
|
+
5. Add body if changes need explanation
|
|
75
|
+
6. Add footer for breaking changes or issue refs
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: create-hardened-docker
|
|
3
|
-
description: Creates production-ready hardened Docker configurations (Dockerfile, docker-compose.yml, nginx.conf, .dockerignore) following CIS Docker Benchmark, OWASP, and NIST SP 800-190 standards. Includes multi-stage builds, non-root execution, read-only filesystems, capability dropping, and comprehensive security hardening.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
1
|
# Create Hardened Docker
|
|
7
2
|
|
|
8
3
|
## Overview
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: git-workflow-skill
|
|
3
|
-
description: Mandatory feature branch workflow using git worktrees for parallel multi-agent development. Use this skill BEFORE making ANY change to files in a Git repository that are not ignored by .gitignore. Enforces isolated worktrees per feature, conventional commits, and PR-based merging. Supports multiple AI agents working simultaneously on different features without conflicts.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
1
|
# Git Workflow (Worktree-Based)
|
|
7
2
|
|
|
8
3
|
Multi-agent parallel development using git worktrees. Each agent gets an isolated working directory — no branch switching, no conflicts between agents.
|