skillsets 0.1.0 → 0.1.2
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/dist/commands/audit.js +47 -1
- package/dist/commands/init.js +5 -5
- package/dist/commands/submit.js +24 -12
- package/package.json +1 -1
package/dist/commands/audit.js
CHANGED
|
@@ -78,6 +78,27 @@ function isBinaryFile(filePath) {
|
|
|
78
78
|
return false;
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
+
function scanReadmeLinks(cwd) {
|
|
82
|
+
const readmePath = join(cwd, 'README.md');
|
|
83
|
+
if (!existsSync(readmePath))
|
|
84
|
+
return [];
|
|
85
|
+
const relativeLinks = [];
|
|
86
|
+
const content = readFileSync(readmePath, 'utf-8');
|
|
87
|
+
const lines = content.split('\n');
|
|
88
|
+
// Match markdown links: [text](url)
|
|
89
|
+
const linkRegex = /\[([^\]]*)\]\(([^)]+)\)/g;
|
|
90
|
+
for (let i = 0; i < lines.length; i++) {
|
|
91
|
+
let match;
|
|
92
|
+
while ((match = linkRegex.exec(lines[i])) !== null) {
|
|
93
|
+
const url = match[2];
|
|
94
|
+
// Flag relative links to content/.claude/ that aren't using GitHub URLs
|
|
95
|
+
if (url.startsWith('content/.claude/') || url.startsWith('./content/.claude/')) {
|
|
96
|
+
relativeLinks.push({ line: i + 1, link: url });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return relativeLinks;
|
|
101
|
+
}
|
|
81
102
|
function scanForSecrets(dir) {
|
|
82
103
|
const secrets = [];
|
|
83
104
|
const files = getAllFiles(dir);
|
|
@@ -167,6 +188,7 @@ function generateReport(results, cwd) {
|
|
|
167
188
|
results.contentStructure.status === 'PASS' &&
|
|
168
189
|
results.fileSize.status !== 'FAIL' &&
|
|
169
190
|
results.secrets.status === 'PASS' &&
|
|
191
|
+
results.readmeLinks.status === 'PASS' &&
|
|
170
192
|
results.versionCheck.status === 'PASS';
|
|
171
193
|
const submissionType = results.isUpdate
|
|
172
194
|
? `Update (${results.existingVersion} → ${results.skillsetVersion})`
|
|
@@ -197,6 +219,7 @@ function generateReport(results, cwd) {
|
|
|
197
219
|
| File Size Check | ${statusIcon(results.fileSize.status)} | ${results.fileSize.details} |
|
|
198
220
|
| Binary Detection | ${statusIcon(results.binary.status)} | ${results.binary.details} |
|
|
199
221
|
| Secret Detection | ${statusIcon(results.secrets.status)} | ${results.secrets.details} |
|
|
222
|
+
| README Links | ${statusIcon(results.readmeLinks.status)} | ${results.readmeLinks.details} |
|
|
200
223
|
| Version Check | ${statusIcon(results.versionCheck.status)} | ${results.versionCheck.details} |
|
|
201
224
|
|
|
202
225
|
---
|
|
@@ -236,6 +259,12 @@ ${results.secrets.findings || 'No secrets detected.'}
|
|
|
236
259
|
|
|
237
260
|
${results.secretsFound.length > 0 ? '**Potential Secrets Found:**\n' + results.secretsFound.map(s => `- ${s.file}:${s.line} (${s.pattern})`).join('\n') : ''}
|
|
238
261
|
|
|
262
|
+
### 7. README Link Check
|
|
263
|
+
|
|
264
|
+
${results.readmeLinks.findings || 'All links use valid GitHub URLs.'}
|
|
265
|
+
|
|
266
|
+
${results.relativeLinks.length > 0 ? '**Relative Links Found:**\n' + results.relativeLinks.map(l => `- Line ${l.line}: ${l.link}`).join('\n') : ''}
|
|
267
|
+
|
|
239
268
|
---
|
|
240
269
|
|
|
241
270
|
## File Inventory
|
|
@@ -285,11 +314,13 @@ export async function audit() {
|
|
|
285
314
|
binary: { status: 'PASS', details: '' },
|
|
286
315
|
secrets: { status: 'PASS', details: '' },
|
|
287
316
|
versionCheck: { status: 'PASS', details: '' },
|
|
317
|
+
readmeLinks: { status: 'PASS', details: '' },
|
|
288
318
|
isUpdate: false,
|
|
289
319
|
files: [],
|
|
290
320
|
largeFiles: [],
|
|
291
321
|
binaryFiles: [],
|
|
292
322
|
secretsFound: [],
|
|
323
|
+
relativeLinks: [],
|
|
293
324
|
};
|
|
294
325
|
// 1. Manifest validation
|
|
295
326
|
spinner.text = 'Validating manifest...';
|
|
@@ -396,7 +427,20 @@ export async function audit() {
|
|
|
396
427
|
findings: 'Remove all API keys, tokens, and passwords before submitting.',
|
|
397
428
|
};
|
|
398
429
|
}
|
|
399
|
-
// 7.
|
|
430
|
+
// 7. README link check
|
|
431
|
+
spinner.text = 'Checking README links...';
|
|
432
|
+
results.relativeLinks = scanReadmeLinks(cwd);
|
|
433
|
+
if (results.relativeLinks.length === 0) {
|
|
434
|
+
results.readmeLinks = { status: 'PASS', details: 'All links valid' };
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
results.readmeLinks = {
|
|
438
|
+
status: 'FAIL',
|
|
439
|
+
details: `${results.relativeLinks.length} relative link(s)`,
|
|
440
|
+
findings: 'README links to content/.claude/ must use full GitHub URLs.\nFormat: https://github.com/skillsets-cc/main/blob/main/skillsets/%40username/skillset-name/content/.claude/...',
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
// 8. Version check (for updates)
|
|
400
444
|
spinner.text = 'Checking registry...';
|
|
401
445
|
if (results.skillsetName && results.authorHandle) {
|
|
402
446
|
const skillsetId = `${results.authorHandle}/${results.skillsetName}`;
|
|
@@ -441,6 +485,7 @@ export async function audit() {
|
|
|
441
485
|
results.contentStructure.status === 'PASS' &&
|
|
442
486
|
results.fileSize.status !== 'FAIL' &&
|
|
443
487
|
results.secrets.status === 'PASS' &&
|
|
488
|
+
results.readmeLinks.status === 'PASS' &&
|
|
444
489
|
results.versionCheck.status === 'PASS';
|
|
445
490
|
console.log('\n' + chalk.bold('Audit Summary:'));
|
|
446
491
|
console.log('');
|
|
@@ -457,6 +502,7 @@ export async function audit() {
|
|
|
457
502
|
console.log(` ${icon(results.fileSize.status)} File Sizes: ${results.fileSize.details}`);
|
|
458
503
|
console.log(` ${icon(results.binary.status)} Binary Files: ${results.binary.details}`);
|
|
459
504
|
console.log(` ${icon(results.secrets.status)} Secrets: ${results.secrets.details}`);
|
|
505
|
+
console.log(` ${icon(results.readmeLinks.status)} README Links: ${results.readmeLinks.details}`);
|
|
460
506
|
console.log(` ${icon(results.versionCheck.status)} Version: ${results.versionCheck.details}`);
|
|
461
507
|
console.log('');
|
|
462
508
|
if (allPassed) {
|
package/dist/commands/init.js
CHANGED
|
@@ -76,7 +76,7 @@ This skillset has been verified in production.
|
|
|
76
76
|
[List projects or products built using this skillset]
|
|
77
77
|
`;
|
|
78
78
|
const AUDIT_SKILL_MD = `---
|
|
79
|
-
name:
|
|
79
|
+
name: audit-skill
|
|
80
80
|
description: Qualitative review of skillset content against Claude Code best practices. Evaluates all primitives (skills, agents, hooks, MCP, CLAUDE.md) for proper frontmatter, descriptions, and structure. Appends analysis to AUDIT_REPORT.md.
|
|
81
81
|
---
|
|
82
82
|
|
|
@@ -419,8 +419,8 @@ export async function init(options) {
|
|
|
419
419
|
// Generate PROOF.md
|
|
420
420
|
const proof = PROOF_TEMPLATE.replace('{{PRODUCTION_URL}}', productionUrl);
|
|
421
421
|
writeFileSync(join(cwd, 'PROOF.md'), proof);
|
|
422
|
-
// Install
|
|
423
|
-
const skillDir = join(cwd, '.claude', 'skills', '
|
|
422
|
+
// Install audit-skill skill to .claude/skills/
|
|
423
|
+
const skillDir = join(cwd, '.claude', 'skills', 'audit-skill');
|
|
424
424
|
mkdirSync(skillDir, { recursive: true });
|
|
425
425
|
writeFileSync(join(skillDir, 'SKILL.md'), AUDIT_SKILL_MD);
|
|
426
426
|
writeFileSync(join(skillDir, 'CRITERIA.md'), AUDIT_CRITERIA_MD);
|
|
@@ -438,12 +438,12 @@ export async function init(options) {
|
|
|
438
438
|
console.log(' └── (add your .claude/ and/or CLAUDE.md here)');
|
|
439
439
|
}
|
|
440
440
|
console.log(' .claude/skills/ - Audit skill installed');
|
|
441
|
-
console.log(' └──
|
|
441
|
+
console.log(' └── audit-skill/');
|
|
442
442
|
console.log(chalk.cyan('\nNext steps:'));
|
|
443
443
|
console.log(' 1. Edit PROOF.md with production evidence');
|
|
444
444
|
console.log(' 2. Ensure content/ has your skillset files');
|
|
445
445
|
console.log(' 3. Run: npx skillsets audit');
|
|
446
|
-
console.log(' 4. Run: /
|
|
446
|
+
console.log(' 4. Run: /audit-skill [AUDIT_REPORT.md] [path/to/reference-repo]');
|
|
447
447
|
}
|
|
448
448
|
catch (error) {
|
|
449
449
|
spinner.fail('Failed to create structure');
|
package/dist/commands/submit.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import ora from 'ora';
|
|
3
|
-
import { execSync } from 'child_process';
|
|
3
|
+
import { execSync, spawnSync } from 'child_process';
|
|
4
4
|
import { existsSync, readFileSync, mkdirSync, cpSync, rmSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
6
|
import yaml from 'js-yaml';
|
|
@@ -59,11 +59,17 @@ function parseSkillsetYaml(cwd) {
|
|
|
59
59
|
try {
|
|
60
60
|
const content = readFileSync(yamlPath, 'utf-8');
|
|
61
61
|
const data = yaml.load(content);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
const name = data.name;
|
|
63
|
+
const author = data.author?.handle?.replace('@', '');
|
|
64
|
+
const version = data.version;
|
|
65
|
+
// Validate format to prevent command injection
|
|
66
|
+
if (!name || !/^[A-Za-z0-9_-]+$/.test(name))
|
|
67
|
+
return null;
|
|
68
|
+
if (!author || !/^[A-Za-z0-9_-]+$/.test(author))
|
|
69
|
+
return null;
|
|
70
|
+
if (!version || !/^[0-9]+\.[0-9]+\.[0-9]+$/.test(version))
|
|
71
|
+
return null;
|
|
72
|
+
return { name, author, version };
|
|
67
73
|
}
|
|
68
74
|
catch {
|
|
69
75
|
return null;
|
|
@@ -185,7 +191,7 @@ export async function submit() {
|
|
|
185
191
|
execSync(`gh repo clone ${REGISTRY_REPO} "${tempDir}" -- --depth=1`, { stdio: 'ignore' });
|
|
186
192
|
// Create branch
|
|
187
193
|
spinner.text = 'Creating branch...';
|
|
188
|
-
|
|
194
|
+
spawnSync('git', ['checkout', '-b', branchName], { cwd: tempDir, stdio: 'ignore' });
|
|
189
195
|
// Create skillset directory
|
|
190
196
|
const skillsetDir = join(tempDir, 'skillsets', `@${skillset.author}`, skillset.name);
|
|
191
197
|
mkdirSync(skillsetDir, { recursive: true });
|
|
@@ -199,14 +205,14 @@ export async function submit() {
|
|
|
199
205
|
}
|
|
200
206
|
// Commit
|
|
201
207
|
spinner.text = 'Committing changes...';
|
|
202
|
-
|
|
208
|
+
spawnSync('git', ['add', '.'], { cwd: tempDir, stdio: 'ignore' });
|
|
203
209
|
const commitMsg = isUpdate
|
|
204
210
|
? `Update ${skillsetId} to v${skillset.version}`
|
|
205
211
|
: `Add ${skillsetId}`;
|
|
206
|
-
|
|
212
|
+
spawnSync('git', ['commit', '-m', commitMsg], { cwd: tempDir, stdio: 'ignore' });
|
|
207
213
|
// Push to fork
|
|
208
214
|
spinner.text = 'Pushing to fork...';
|
|
209
|
-
|
|
215
|
+
spawnSync('git', ['push', '-u', 'origin', branchName, '--force'], { cwd: tempDir, stdio: 'ignore' });
|
|
210
216
|
// Create PR
|
|
211
217
|
spinner.text = 'Creating pull request...';
|
|
212
218
|
const prTitle = isUpdate
|
|
@@ -254,13 +260,19 @@ _Add any additional context for reviewers here._
|
|
|
254
260
|
---
|
|
255
261
|
Submitted via \`npx skillsets submit\`
|
|
256
262
|
`;
|
|
257
|
-
const prResult =
|
|
263
|
+
const prResult = spawnSync('gh', ['pr', 'create', '--repo', REGISTRY_REPO, '--title', prTitle, '--body', prBody], {
|
|
264
|
+
cwd: tempDir,
|
|
265
|
+
encoding: 'utf-8',
|
|
266
|
+
});
|
|
267
|
+
if (prResult.status !== 0) {
|
|
268
|
+
throw new Error(prResult.stderr || 'Failed to create PR');
|
|
269
|
+
}
|
|
258
270
|
// Cleanup
|
|
259
271
|
spinner.text = 'Cleaning up...';
|
|
260
272
|
rmSync(tempDir, { recursive: true, force: true });
|
|
261
273
|
spinner.succeed('Pull request created');
|
|
262
274
|
// Extract PR URL from result
|
|
263
|
-
const prUrl = prResult.trim();
|
|
275
|
+
const prUrl = (prResult.stdout || '').trim();
|
|
264
276
|
console.log(chalk.green(`\n✓ ${isUpdate ? 'Update' : 'Submission'} complete!\n`));
|
|
265
277
|
console.log(` Skillset: ${chalk.bold(skillsetId)}`);
|
|
266
278
|
if (isUpdate) {
|