skill-check 0.1.0 → 0.1.1
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/README.md +9 -7
- package/dist/cli/main.js +28 -26
- package/dist/core/defaults.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,11 +5,12 @@ Linter for agent skill files — validates SKILL.md files against the spec with
|
|
|
5
5
|

|
|
6
6
|
|
|
7
7
|
Regenerate with `pnpm run demo:readme` (source: `scripts/readme-demo.tape`).
|
|
8
|
+
The demo runs `npx skill-check .` from repo root and auto-detects skills in tool directories such as `.agents`.
|
|
8
9
|
|
|
9
10
|
## Install
|
|
10
11
|
|
|
11
12
|
```bash
|
|
12
|
-
npx skill-check
|
|
13
|
+
npx skill-check .
|
|
13
14
|
```
|
|
14
15
|
|
|
15
16
|
Global install via curl:
|
|
@@ -29,6 +30,7 @@ brew install skill-check
|
|
|
29
30
|
|
|
30
31
|
| Command | Description |
|
|
31
32
|
|---|---|
|
|
33
|
+
| `skill-check [path]` | Shorthand for `skill-check check [path]` |
|
|
32
34
|
| `skill-check check [path]` | Run validation (and optional security scan) |
|
|
33
35
|
| `skill-check new <name>` | Scaffold a new skill directory with SKILL.md template |
|
|
34
36
|
| `skill-check watch [path]` | Watch for changes and re-run validation on save |
|
|
@@ -64,7 +66,7 @@ brew install skill-check
|
|
|
64
66
|
|
|
65
67
|
**HTML reports** are written to `skill-check-report.html` (or `output.reportPath`). In an interactive terminal the report opens in your browser automatically; use `--no-open` to skip.
|
|
66
68
|
|
|
67
|
-
**View locally:** `npx skill-check
|
|
69
|
+
**View locally:** `npx skill-check . --format html` or open the file directly: `open skill-check-report.html` (macOS).
|
|
68
70
|
|
|
69
71
|
The `text` formatter includes quality score bars per skill, colorized severity badges, and boxed summaries.
|
|
70
72
|
An ASCII CLI banner is shown in interactive text mode; set `SKILL_CHECK_NO_BANNER=1` to disable it.
|
|
@@ -131,18 +133,18 @@ npx skill-check init --interactive
|
|
|
131
133
|
`skill-check` can validate repos or direct skills directories:
|
|
132
134
|
|
|
133
135
|
```bash
|
|
134
|
-
npx skill-check
|
|
136
|
+
npx skill-check /path/to/repo
|
|
135
137
|
npx skill-check check ~/.claude/skills
|
|
136
138
|
```
|
|
137
139
|
|
|
138
140
|
`check` runs the security scan by default.
|
|
139
|
-
If dependencies are missing, `skill-check`
|
|
140
|
-
|
|
141
|
+
If dependencies are missing, `skill-check` automatically installs scanner dependencies by default.
|
|
142
|
+
Use `--no-installs` to hard-block automatic installs.
|
|
141
143
|
|
|
142
144
|
Run security scan without UV by forcing `pipx`:
|
|
143
145
|
|
|
144
146
|
```bash
|
|
145
|
-
npx skill-check security-scan . --security-scan-runner pipx
|
|
147
|
+
npx skill-check security-scan . --security-scan-runner pipx
|
|
146
148
|
```
|
|
147
149
|
|
|
148
150
|
Run validation + security scan in one pipeline step with explicit runner:
|
|
@@ -346,7 +348,7 @@ All rules emit actionable `suggestion` text to guide fixes.
|
|
|
346
348
|
Releases are automated with [semantic-release](https://github.com/semantic-release/semantic-release). Pushing to `main` (after CI passes) runs the release workflow: commits are analyzed for [Conventional Commits](https://www.conventionalcommits.org/) (`fix:`, `feat:`, `BREAKING CHANGE:`), the version is bumped, `CHANGELOG.md` is updated, the package is published to npm, and a GitHub release is created.
|
|
347
349
|
|
|
348
350
|
- **Commit messages** are validated locally by [commitlint](https://commitlint.js.org/) (enforced by the `commit-msg` hook). Use `fix:`, `feat:`, `docs:`, `chore:`, etc.
|
|
349
|
-
- **
|
|
351
|
+
- **npm auth:** Use [npm Trusted Publishing (OIDC)](https://docs.npmjs.com/trusted-publishers) so you don’t need `NPM_TOKEN`. On [npmjs.com](https://www.npmjs.com/) go to the **skill-check** package → **Settings** → **Trusted publishing** → add a GitHub Actions publisher with workflow filename **`publish.yml`** (exact name, including extension). Then the workflow can publish without any npm token. Alternatively, set the `NPM_TOKEN` repository secret for token-based publish.
|
|
350
352
|
|
|
351
353
|
To simulate a release locally (without publishing): `pnpm run release:dry-run`. It will fail `verifyConditions` without `NPM_TOKEN` and `GITHUB_TOKEN`; in CI both are set.
|
|
352
354
|
|
package/dist/cli/main.js
CHANGED
|
@@ -26,6 +26,18 @@ const defaultIO = {
|
|
|
26
26
|
stdout: (text) => process.stdout.write(text),
|
|
27
27
|
stderr: (text) => process.stderr.write(text),
|
|
28
28
|
};
|
|
29
|
+
const ROOT_COMMANDS = new Set([
|
|
30
|
+
'check',
|
|
31
|
+
'report',
|
|
32
|
+
'security-scan',
|
|
33
|
+
'rules',
|
|
34
|
+
'new',
|
|
35
|
+
'diff',
|
|
36
|
+
'watch',
|
|
37
|
+
'init',
|
|
38
|
+
'help',
|
|
39
|
+
'version',
|
|
40
|
+
]);
|
|
29
41
|
function collectList(value, previous) {
|
|
30
42
|
const parsed = value
|
|
31
43
|
.split(',')
|
|
@@ -89,6 +101,16 @@ function parseCommaSeparated(value) {
|
|
|
89
101
|
.map((entry) => entry.trim())
|
|
90
102
|
.filter(Boolean);
|
|
91
103
|
}
|
|
104
|
+
function normalizeRootCommandArgs(argv) {
|
|
105
|
+
if (argv.length === 0) {
|
|
106
|
+
return argv;
|
|
107
|
+
}
|
|
108
|
+
const [first] = argv;
|
|
109
|
+
if (!first || first.startsWith('-') || ROOT_COMMANDS.has(first)) {
|
|
110
|
+
return argv;
|
|
111
|
+
}
|
|
112
|
+
return ['check', ...argv];
|
|
113
|
+
}
|
|
92
114
|
function normalizeCheckCommandOptions(raw) {
|
|
93
115
|
return {
|
|
94
116
|
fix: raw.fix === true,
|
|
@@ -190,7 +212,7 @@ function normalizeAgentScanOptions(raw) {
|
|
|
190
212
|
mode,
|
|
191
213
|
paths: selectedPaths,
|
|
192
214
|
skills: selectedSkills,
|
|
193
|
-
installPolicy: denyInstalls ? 'deny' :
|
|
215
|
+
installPolicy: denyInstalls ? 'deny' : 'allow',
|
|
194
216
|
};
|
|
195
217
|
}
|
|
196
218
|
function shouldUseInteractiveUi(io) {
|
|
@@ -236,34 +258,13 @@ function renderAutoFixSummary(summary) {
|
|
|
236
258
|
}
|
|
237
259
|
return `${lines.join('\n')}\n\n`;
|
|
238
260
|
}
|
|
239
|
-
function
|
|
240
|
-
return [invocation.command, ...invocation.args]
|
|
241
|
-
.map((part) => (part.includes(' ') ? `"${part}"` : part))
|
|
242
|
-
.join(' ');
|
|
243
|
-
}
|
|
244
|
-
async function ensureSecurityScanInstallConsent(scanOptions, invocation, io, format) {
|
|
261
|
+
async function ensureSecurityScanInstallConsent(scanOptions, invocation) {
|
|
245
262
|
const mayInstallDependency = invocation.command !== 'mcp-scan' && !isCommandAvailable('mcp-scan');
|
|
246
263
|
if (!mayInstallDependency)
|
|
247
264
|
return;
|
|
248
265
|
if (scanOptions.installPolicy === 'allow')
|
|
249
266
|
return;
|
|
250
|
-
|
|
251
|
-
if (scanOptions.installPolicy === 'deny') {
|
|
252
|
-
throw new CliError(`Security scan may install dependencies via ${invocation.command}. ${guidance}`, 2);
|
|
253
|
-
}
|
|
254
|
-
const interactivePromptAllowed = shouldUseInteractiveUi(io) &&
|
|
255
|
-
format === 'text' &&
|
|
256
|
-
!isTruthyFlag(process.env.CI);
|
|
257
|
-
if (!interactivePromptAllowed) {
|
|
258
|
-
throw new CliError(`Security scan may install dependencies via ${invocation.command} but interactive approval is unavailable. ${guidance}`, 2);
|
|
259
|
-
}
|
|
260
|
-
const accepted = await confirm({
|
|
261
|
-
message: `Security scan may install "mcp-scan" with: ${formatInvocation(invocation)}. Continue?`,
|
|
262
|
-
initialValue: false,
|
|
263
|
-
});
|
|
264
|
-
if (isCancel(accepted) || !accepted) {
|
|
265
|
-
throw new CliError('Security scan install was declined. Re-run with --no-security-scan or --allow-installs.', 2);
|
|
266
|
-
}
|
|
267
|
+
throw new CliError(`Security scan may install dependencies via ${invocation.command}. Re-run without --no-installs, install mcp-scan manually, or skip scan with --no-security-scan.`, 2);
|
|
267
268
|
}
|
|
268
269
|
async function runValidationPipeline(cwd, config, interactiveUi) {
|
|
269
270
|
const useTaskUi = interactiveUi && config.output.format === 'text';
|
|
@@ -299,7 +300,7 @@ async function runAgentScanWithFeedback(scanOptions, target, io, format) {
|
|
|
299
300
|
if (format === 'text') {
|
|
300
301
|
io.stdout(`${pc.dim('Security scan engine:')} ${pc.bold('agent-scan (mcp-scan)')} ${pc.dim('via')} ${pc.cyan(invocation.command)}\n`);
|
|
301
302
|
}
|
|
302
|
-
await ensureSecurityScanInstallConsent(scanOptions, invocation
|
|
303
|
+
await ensureSecurityScanInstallConsent(scanOptions, invocation);
|
|
303
304
|
if (useInteractiveFeedback) {
|
|
304
305
|
ora().info('Running security scan...');
|
|
305
306
|
}
|
|
@@ -696,7 +697,8 @@ export async function runCli(argv, io = defaultIO) {
|
|
|
696
697
|
finalExitCode = 0;
|
|
697
698
|
});
|
|
698
699
|
try {
|
|
699
|
-
|
|
700
|
+
const normalizedArgv = normalizeRootCommandArgs(argv);
|
|
701
|
+
await program.parseAsync(normalizedArgv, { from: 'user' });
|
|
700
702
|
return finalExitCode;
|
|
701
703
|
}
|
|
702
704
|
catch (error) {
|
package/dist/core/defaults.js
CHANGED