skillsets 0.9.0 → 0.9.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.
@@ -16,6 +16,7 @@ export interface AuditResults {
16
16
  mcpServers: AuditResult;
17
17
  runtimeDeps: AuditResult;
18
18
  installNotes: AuditResult;
19
+ license: AuditResult;
19
20
  ccExtensions: AuditResult;
20
21
  skillsetName?: string;
21
22
  skillsetVersion?: string;
@@ -17,6 +17,7 @@ export function isAuditPassing(results, enforceMcp) {
17
17
  (enforceMcp ? results.mcpServers.status === 'PASS' : true) &&
18
18
  (enforceMcp ? results.runtimeDeps.status === 'PASS' : true) &&
19
19
  results.installNotes.status === 'PASS' &&
20
+ results.license.status === 'PASS' &&
20
21
  (enforceMcp ? results.ccExtensions.status === 'PASS' : true);
21
22
  }
22
23
  function statusIcon(status) {
@@ -38,7 +39,7 @@ export function hasWarnings(results) {
38
39
  results.manifest, results.requiredFiles, results.contentStructure,
39
40
  results.fileSize, results.binary, results.secrets, results.readmeLinks,
40
41
  results.versionCheck, results.mcpServers, results.runtimeDeps,
41
- results.installNotes, results.ccExtensions,
42
+ results.installNotes, results.license, results.ccExtensions,
42
43
  ];
43
44
  return checks.some(c => c.status === 'WARNING');
44
45
  }
@@ -73,6 +74,7 @@ export function generateReport(results, enforceMcp = false) {
73
74
  | MCP Servers | ${statusIcon(results.mcpServers.status)} | ${results.mcpServers.details} |
74
75
  | Runtime Dependencies | ${statusIcon(results.runtimeDeps.status)} | ${results.runtimeDeps.details} |
75
76
  | Install Notes | ${statusIcon(results.installNotes.status)} | ${results.installNotes.details} |
77
+ | License | ${statusIcon(results.license.status)} | ${results.license.details} |
76
78
  | CC Extensions | ${statusIcon(results.ccExtensions.status)} | ${results.ccExtensions.details} |
77
79
 
78
80
  ---
@@ -130,7 +132,11 @@ ${results.runtimeDeps.findings || 'Runtime dependency declarations are consisten
130
132
 
131
133
  ${results.installNotes.findings || 'Install notes present and valid.'}
132
134
 
133
- ### 11. CC Extensions
135
+ ### 11. License
136
+
137
+ ${results.license.findings || 'License file present and populated.'}
138
+
139
+ ### 12. CC Extensions
134
140
 
135
141
  ${results.ccExtensions.findings || 'CC extension declarations are consistent with manifest.'}
136
142
 
@@ -52,12 +52,16 @@ function getAllFiles(dir, baseDir = dir) {
52
52
  }
53
53
  return files;
54
54
  }
55
+ const TEXT_BASENAMES = new Set(['LICENSE', 'LICENCE', 'Makefile', 'Dockerfile', 'Gemfile']);
55
56
  function isBinaryFile(filePath) {
56
57
  const ext = filePath.substring(filePath.lastIndexOf('.')).toLowerCase();
57
58
  if (TEXT_EXTENSIONS.has(ext))
58
59
  return false;
59
60
  if (filePath.endsWith('.example'))
60
61
  return false;
62
+ const basename = filePath.substring(filePath.lastIndexOf('/') + 1);
63
+ if (TEXT_BASENAMES.has(basename))
64
+ return false;
61
65
  // Check for null bytes in first 512 bytes
62
66
  try {
63
67
  const buffer = Buffer.alloc(512);
@@ -252,6 +256,7 @@ export async function audit(options = {}) {
252
256
  mcpServers: { status: 'PASS', details: '' },
253
257
  runtimeDeps: { status: 'PASS', details: '' },
254
258
  installNotes: { status: 'PASS', details: '' },
259
+ license: { status: 'PASS', details: '' },
255
260
  ccExtensions: { status: 'PASS', details: '' },
256
261
  isUpdate: false,
257
262
  files: [],
@@ -282,6 +287,7 @@ export async function audit(options = {}) {
282
287
  const hasReadme = !!findReadme(cwd);
283
288
  const hasQuickstart = existsSync(join(cwd, 'content', 'QUICKSTART.md'));
284
289
  const hasInstallNotes = existsSync(join(cwd, 'content', 'INSTALL_NOTES.md'));
290
+ const hasLicense = existsSync(join(cwd, 'content', 'LICENSE'));
285
291
  const hasSkillsetYaml = existsSync(join(cwd, 'skillset.yaml'));
286
292
  const missingFiles = [];
287
293
  if (!hasSkillsetYaml)
@@ -294,6 +300,8 @@ export async function audit(options = {}) {
294
300
  missingFiles.push('content/QUICKSTART.md');
295
301
  if (!hasInstallNotes)
296
302
  missingFiles.push('content/INSTALL_NOTES.md');
303
+ if (!hasLicense)
304
+ missingFiles.push('content/LICENSE');
297
305
  if (missingFiles.length === 0) {
298
306
  results.requiredFiles = { status: 'PASS', details: 'All present' };
299
307
  }
@@ -329,6 +337,24 @@ export async function audit(options = {}) {
329
337
  else {
330
338
  results.installNotes = { status: 'FAIL', details: 'Missing' };
331
339
  }
340
+ // 2c. License validation
341
+ spinner.text = 'Validating license...';
342
+ if (hasLicense) {
343
+ const licenseContent = readFileSync(join(cwd, 'content', 'LICENSE'), 'utf-8').trim();
344
+ if (licenseContent.length === 0) {
345
+ results.license = {
346
+ status: 'FAIL',
347
+ details: 'Empty LICENSE file',
348
+ findings: 'content/LICENSE must contain a valid license. Populate it before submitting.',
349
+ };
350
+ }
351
+ else {
352
+ results.license = { status: 'PASS', details: 'Valid' };
353
+ }
354
+ }
355
+ else {
356
+ results.license = { status: 'FAIL', details: 'Missing' };
357
+ }
332
358
  // 3. Content structure
333
359
  spinner.text = 'Verifying content structure...';
334
360
  const hasClaudeDir = existsSync(join(cwd, 'content', '.claude'));
@@ -476,6 +502,7 @@ export async function audit(options = {}) {
476
502
  [results.mcpServers, 'MCP Servers'],
477
503
  [results.runtimeDeps, 'Runtime Deps'],
478
504
  [results.installNotes, 'Install Notes'],
505
+ [results.license, 'License'],
479
506
  [results.ccExtensions, 'CC Extensions'],
480
507
  ];
481
508
  console.log('\n' + chalk.bold('Audit Summary:'));
@@ -76,9 +76,6 @@ npx skillsets install {{AUTHOR_HANDLE}}/{{NAME}}
76
76
 
77
77
  [List the key files and their purposes]
78
78
 
79
- ## License
80
-
81
- [Your license]
82
79
  `;
83
80
  const QUICKSTART_TEMPLATE = `# Quickstart
84
81
 
@@ -439,6 +436,10 @@ export async function init(options) {
439
436
  .replace(/\{\{NAME\}\}/g, name);
440
437
  writeFileSync(join(cwd, 'content', 'INSTALL_NOTES.md'), installNotes);
441
438
  }
439
+ // Generate empty content/LICENSE (if not copying existing)
440
+ if (!existsSync(join(cwd, 'content', 'LICENSE'))) {
441
+ writeFileSync(join(cwd, 'content', 'LICENSE'), '');
442
+ }
442
443
  spinner.succeed('Skillset structure created');
443
444
  // Summary
444
445
  console.log(chalk.green('\n✓ Initialized skillset submission:\n'));
@@ -447,6 +448,7 @@ export async function init(options) {
447
448
  console.log(` ├── ${readmeFilename} - Documentation`);
448
449
  console.log(' ├── QUICKSTART.md - Post-install guide');
449
450
  console.log(' ├── INSTALL_NOTES.md - Pre-install notes');
451
+ console.log(' ├── LICENSE - License (populate before audit)');
450
452
  if (filesToCopy.length > 0) {
451
453
  filesToCopy.forEach((f) => console.log(` └── ${f}`));
452
454
  }
@@ -145,6 +145,12 @@ export async function install(skillsetId, options) {
145
145
  await rm(tempDir, { recursive: true, force: true });
146
146
  throw new Error('Checksum verification failed - files may be corrupted');
147
147
  }
148
+ // Strip redirect README.md if a README_*.md exists (avoid clobbering user's README)
149
+ const tempEntries = await readdir(tempDir);
150
+ const hasNamedReadme = tempEntries.some(f => /^README_[^/]+\.md$/i.test(f));
151
+ if (hasNamedReadme && tempEntries.includes('README.md')) {
152
+ await rm(join(tempDir, 'README.md'));
153
+ }
148
154
  // Checksums valid — move verified content to cwd
149
155
  spinner.text = 'Installing verified content...';
150
156
  const entries = await readdir(tempDir, { withFileTypes: true });
@@ -236,6 +236,7 @@ Submitted via \`npx skillsets submit\`
236
236
  - [x] \`skillset.yaml\` validated against schema
237
237
  - [x] \`README_${skillset.name.toUpperCase()}.md\` with installation and usage instructions
238
238
  - [x] \`content/INSTALL_NOTES.md\` with install notes
239
+ - [x] \`content/LICENSE\` populated
239
240
  - [x] \`AUDIT_REPORT.md\` generated and passing
240
241
  - [x] \`content/\` directory with skillset files
241
242
 
@@ -1,4 +1,4 @@
1
1
  export declare const SKILLSET_YAML_TEMPLATE = "schema_version: \"1.0\"\nbatch_id: \"{{BATCH_ID}}\"\n\n# Identity\nname: \"{{NAME}}\"\nversion: \"1.0.0\"\ndescription: \"{{DESCRIPTION}}\"\n\nauthor:\n handle: \"{{AUTHOR_HANDLE}}\"\n url: \"{{AUTHOR_URL}}\"\n\n# Verification\nverification:\n production_links:\n - url: \"{{PRODUCTION_URL}}\"\n audit_report: \"./AUDIT_REPORT.md\"\n\n# Discovery\ntags:\n{{TAGS}}\n\ncompatibility:\n claude_code_version: \">=1.0.0\"\n languages:\n - \"any\"\n\n# Lifecycle\nstatus: \"active\"\n\n# Content\nentry_point: \"./content/CLAUDE.md\"\n";
2
- export declare const README_TEMPLATE = "# {{NAME}}\n\n{{DESCRIPTION}}\n\n## Installation\n\n```bash\nnpx skillsets install {{AUTHOR_HANDLE}}/{{NAME}}\n```\n\n## Usage\n\n[Describe how to use your skillset]\n\n## What's Included\n\n[List the key files and their purposes]\n\n## License\n\n[Your license]\n";
2
+ export declare const README_TEMPLATE = "# {{NAME}}\n\n{{DESCRIPTION}}\n\n## Installation\n\n```bash\nnpx skillsets install {{AUTHOR_HANDLE}}/{{NAME}}\n```\n\n## Usage\n\n[Describe how to use your skillset]\n\n## What's Included\n\n[List the key files and their purposes]\n\n";
3
3
  export declare const QUICKSTART_TEMPLATE = "# Quickstart\n\nAfter installing via `npx skillsets install {{AUTHOR_HANDLE}}/{{NAME}}`, customize the workflow for your project.\n\n---\n\n## What Was Installed\n\n```\nyour-project/\n\u251C\u2500\u2500 .claude/ # Skills, agents, resources\n\u251C\u2500\u2500 CLAUDE.md # Project config \u2190 START HERE\n\u2514\u2500\u2500 README_{{NAME}}.md # Skillset documentation\n```\n\n---\n\n## Getting Started\n\n1. **Edit CLAUDE.md** \u2014 Replace placeholder content with your project's specifics\n2. **Customize .claude/** \u2014 Adapt skills, agents, and resources for your stack\n3. **Run** \u2014 `claude` to start using the skillset\n\n---\n\n## Customization Checklist\n\n- [ ] Update Identity & Constraints in CLAUDE.md\n- [ ] Configure style guides in .claude/resources/\n- [ ] Adapt agent definitions in .claude/agents/\n- [ ] Set up any required infrastructure (Docker, API keys, etc.)\n\n---\n\n## Resources\n\n[Add links to documentation, examples, or support channels]\n";
4
4
  export declare const INSTALL_NOTES_TEMPLATE = "# {{NAME}}\n\n<!--\nInstall notes for pre-install display. Max 4000 characters total.\nWhat does this skillset do? What should users know before installing?\nThe dependency section below is populated by /audit-skill during review.\n-->\n\n## Dependencies\n\n<!-- Populated automatically by /audit-skill -->\n";
@@ -49,9 +49,6 @@ npx skillsets install {{AUTHOR_HANDLE}}/{{NAME}}
49
49
 
50
50
  [List the key files and their purposes]
51
51
 
52
- ## License
53
-
54
- [Your license]
55
52
  `;
56
53
  export const QUICKSTART_TEMPLATE = `# Quickstart
57
54
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillsets",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "CLI tool for discovering and installing verified skillsets",
5
5
  "type": "module",
6
6
  "bin": {