@rhize/skill-forge 0.20.0 → 0.20.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 CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  **The supply-chain gate for agent skills.**
4
4
 
5
- Source version: `@rhize/skill-forge@0.20.0` (2026-09-14), 0.x beta (Pro features free until 1.0).
6
- Npm publication is a separate tag-driven step; `npm view @rhize/skill-forge version` reports the
7
- latest published version.
5
+ Published version: `@rhize/skill-forge@0.20.0` (2026-09-14), 0.x beta (Pro features free until 1.0).
6
+ Released through the tag/OIDC workflow; `npm view @rhize/skill-forge version` reports the
7
+ current published version.
8
8
 
9
9
  > This project is unrelated to the [`skillforge`](https://www.npmjs.com/package/skillforge)
10
10
  > package on npm, which is a Claude Skills *evaluation* framework. `skill-forge` (this package,
@@ -128,7 +128,10 @@ per-feature implementation status.
128
128
  outright; a lower-severity finding produces `warn`; a clean scan is `pass`.
129
129
  - **npm-sourced MCP candidates:** fetched with `--ignore-scripts`, and every tarball member path is
130
130
  validated before extraction (a path-traversal guard).
131
- - **SkillSpector, when installed,** is purely additive — its absence never blocks the gate.
131
+ - **SkillSpector, when installed,** adds its individual findings, including modern `issues`
132
+ reports. Its absence preserves the built-in-only gate. A failed, malformed or incomplete
133
+ installed scan is an explicit HIGH `skillspector-unavailable` finding, which blocks under
134
+ default strictness. A LOW aggregate score cannot hide a HIGH individual issue.
132
135
 
133
136
  See [docs/security-model.md](docs/security-model.md) for the full rule table and current status of
134
137
  each check.
package/dist/cli.js CHANGED
@@ -1515,10 +1515,68 @@ function which(cmd) {
1515
1515
  }
1516
1516
  return void 0;
1517
1517
  }
1518
- function normalizeSeverity(sev) {
1519
- const s = (sev ?? "").toUpperCase();
1520
- if (s === "LOW" || s === "MEDIUM" || s === "HIGH" || s === "CRITICAL") return s;
1521
- return "MEDIUM";
1518
+ function scannerUnavailable(targetPath) {
1519
+ return {
1520
+ severity: "HIGH",
1521
+ rule: "skillspector-unavailable",
1522
+ file: targetPath,
1523
+ detail: "Installed SkillSpector failed or returned an unsupported/incomplete report; external safety evidence is unavailable."
1524
+ };
1525
+ }
1526
+ function reportRecord(value) {
1527
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid report record");
1528
+ return value;
1529
+ }
1530
+ function reportSeverity(value) {
1531
+ if (typeof value !== "string") throw new Error("Missing severity");
1532
+ const severity = value.toUpperCase();
1533
+ if (severity === "LOW" || severity === "MEDIUM" || severity === "HIGH" || severity === "CRITICAL") return severity;
1534
+ throw new Error("Unknown severity");
1535
+ }
1536
+ function parseSkillSpectorReport(value, targetPath) {
1537
+ try {
1538
+ const report = reportRecord(value);
1539
+ const modern = "issues" in report;
1540
+ const raw = modern ? report.issues : report.filtered_findings ?? report.findings;
1541
+ if (!Array.isArray(raw)) throw new Error("Missing findings");
1542
+ const findings = raw.map((entry) => {
1543
+ const issue = reportRecord(entry);
1544
+ const rule = modern ? issue.id : issue.rule_id;
1545
+ const detail = modern ? issue.finding ?? issue.explanation : issue.message;
1546
+ if (typeof rule !== "string" || !rule || typeof detail !== "string") throw new Error("Invalid finding");
1547
+ const location = modern ? reportRecord(issue.location) : void 0;
1548
+ if (location && typeof location.file !== "string") throw new Error("Invalid location");
1549
+ const line = location?.start_line;
1550
+ return {
1551
+ severity: reportSeverity(issue.severity),
1552
+ rule,
1553
+ file: location?.file ?? targetPath,
1554
+ detail: typeof line === "number" && Number.isInteger(line) && line > 0 ? `${line}: ${detail}` : detail
1555
+ };
1556
+ });
1557
+ const assessment = modern ? reportRecord(report.risk_assessment) : void 0;
1558
+ const summary = assessment?.severity ?? report.risk_severity;
1559
+ if (modern || summary !== void 0) {
1560
+ const severity = reportSeverity(summary);
1561
+ if (severity !== "LOW") findings.push({
1562
+ severity,
1563
+ rule: "skillspector-summary",
1564
+ file: targetPath,
1565
+ detail: `SkillSpector aggregate risk: ${severity}.`
1566
+ });
1567
+ }
1568
+ if (modern) {
1569
+ const completeness = reportRecord(report.analysis_completeness);
1570
+ if (report.execution_successful !== true || completeness.is_complete !== true || completeness.execution_successful === false) {
1571
+ findings.push(scannerUnavailable(targetPath));
1572
+ }
1573
+ } else if (report.execution_successful === false) {
1574
+ findings.push(scannerUnavailable(targetPath));
1575
+ }
1576
+ return findings;
1577
+ } catch {
1578
+ return [scannerUnavailable(targetPath)];
1579
+ }
1522
1580
  }
1523
1581
  async function scanWithSkillSpector(targetPath) {
1524
1582
  const bin = which("skillspector");
@@ -1528,16 +1586,15 @@ async function scanWithSkillSpector(targetPath) {
1528
1586
  timeout: 6e4,
1529
1587
  maxBuffer: 10 * 1024 * 1024
1530
1588
  });
1531
- const parsed = JSON.parse(stdout);
1532
- const raw = parsed.filtered_findings ?? parsed.findings ?? [];
1533
- return raw.map((f) => ({
1534
- severity: normalizeSeverity(f.severity),
1535
- rule: f.rule_id ?? "skillspector",
1536
- file: targetPath,
1537
- detail: f.message ?? ""
1538
- }));
1539
- } catch {
1540
- return [];
1589
+ return parseSkillSpectorReport(JSON.parse(stdout), targetPath);
1590
+ } catch (error) {
1591
+ const failed = scannerUnavailable(targetPath);
1592
+ try {
1593
+ const stdout = error.stdout;
1594
+ return stdout ? [...parseSkillSpectorReport(JSON.parse(stdout), targetPath), failed] : [failed];
1595
+ } catch {
1596
+ return [failed];
1597
+ }
1541
1598
  }
1542
1599
  }
1543
1600
  async function scanSafety(targetPath, strictness = DEFAULT_STRICTNESS, capturedFiles) {