calllint 1.3.0 → 1.4.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.
Files changed (2) hide show
  1. package/dist/index.js +318 -236
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -106,6 +106,10 @@ SCAN OPTIONS
106
106
  --badge Emit a shields.io endpoint badge JSON (SAFE/REVIEW/UNKNOWN/BLOCK)
107
107
  --receipt Generate a cryptographically-verifiable audit receipt (for CI/compliance)
108
108
  --receipt-out <f> Receipt output path (default: calllint-receipt.json)
109
+ --evidence <file> Attach an external scanner report (e.g. SkillSpector JSON/SARIF)
110
+ as supporting evidence; shown side-by-side in a joint Trust
111
+ Packet. Never re-scored, never changes the CallLint verdict.
112
+ --evidence-format <fmt> Force evidence format (json|sarif); default auto-detect
109
113
  --policy <file> Use a policy file (default: built-in defaults)
110
114
  --stdin Read config JSON from stdin
111
115
  --ci Exit non-zero per policy (BLOCK=30, UNKNOWN=20, REVIEW=10 if enabled)
@@ -130,6 +134,7 @@ EXAMPLES
130
134
  calllint scan .cursor/mcp.json --markdown
131
135
  calllint scan .cursor/mcp.json --badge > calllint-badge.json
132
136
  calllint scan .cursor/mcp.json --receipt # Generate audit receipt
137
+ calllint scan .cursor/mcp.json --evidence skillspector-report.json # Joint Trust Packet
133
138
  calllint receipt verify calllint-receipt.json # Verify receipt integrity
134
139
  calllint action inspect payment.json
135
140
  calllint inbox inspect gmail-reply.normalized.json
@@ -682,7 +687,8 @@ function resolveScanOptions(opts) {
682
687
  now: opts?.now ?? 0,
683
688
  generatedAt: opts?.generatedAt ?? "1970-01-01T00:00:00.000Z",
684
689
  extraFindings: opts?.extraFindings ?? {},
685
- surfaces: opts?.surfaces ?? []
690
+ surfaces: opts?.surfaces ?? [],
691
+ evidence: opts?.evidence ?? []
686
692
  };
687
693
  }
688
694
 
@@ -2670,7 +2676,7 @@ function enrichEvidencePositions(reports, positions) {
2670
2676
  }
2671
2677
 
2672
2678
  // ../../packages/core/src/scanConfig.ts
2673
- function aggregate(configPath, reports, generatedAt) {
2679
+ function aggregate(configPath, reports, generatedAt, evidence) {
2674
2680
  const counts = { SAFE: 0, REVIEW: 0, BLOCK: 0, UNKNOWN: 0 };
2675
2681
  for (const r of reports) counts[r.verdict]++;
2676
2682
  const verdict = reports.length === 0 ? "UNKNOWN" : mostSevereVerdict(reports.map((r) => r.verdict));
@@ -2683,18 +2689,23 @@ function aggregate(configPath, reports, generatedAt) {
2683
2689
  counts,
2684
2690
  reports,
2685
2691
  diagnostics: [],
2686
- generatedAt
2692
+ generatedAt,
2693
+ // Additive projection (ADR 0034). Attached only when the CLI imported evidence
2694
+ // via `scan --evidence`; omitted otherwise so default scan output is
2695
+ // byte-identical (the offline corpus never attaches evidence). Never re-scored:
2696
+ // the `verdict` above is unaffected by these findings.
2697
+ ...evidence.length > 0 ? { evidence } : {}
2687
2698
  };
2688
2699
  }
2689
2700
  function scanParsed(parsed, opts) {
2690
- const { generatedAt, surfaces } = resolveScanOptions(opts);
2701
+ const { generatedAt, surfaces, evidence } = resolveScanOptions(opts);
2691
2702
  const reports = parsed.servers.map(
2692
2703
  (server) => scanServer({ server, targetKind: parsed.kind }, opts)
2693
2704
  );
2694
2705
  enrichEvidencePositions(reports, parsed.positions);
2695
2706
  const surfaceReport = scanDocumentSurfaces(surfaces, parsed.configPath, generatedAt);
2696
2707
  if (surfaceReport) reports.push(surfaceReport);
2697
- return aggregate(parsed.configPath, reports, generatedAt);
2708
+ return aggregate(parsed.configPath, reports, generatedAt, evidence);
2698
2709
  }
2699
2710
  function scanConfigText(text, configPath, opts) {
2700
2711
  return scanParsed(parseConfigText(text, configPath), opts);
@@ -4442,6 +4453,275 @@ function renderHtml(summary) {
4442
4453
  </html>`;
4443
4454
  }
4444
4455
 
4456
+ // ../../packages/report-renderer/src/renderTrustPacket.ts
4457
+ var COMPLETENESS_HINT = {
4458
+ complete: "complete",
4459
+ partial: "partial (incomplete \u2014 treat as inconclusive)",
4460
+ degraded: "degraded (not a pass)",
4461
+ failed: "failed (not a pass)"
4462
+ };
4463
+ function topProviderSeverity(ev) {
4464
+ const sevs = ev.findings.map(
4465
+ (f) => f && typeof f === "object" && "providerSeverity" in f ? String(f.providerSeverity) : void 0
4466
+ ).filter((s) => Boolean(s));
4467
+ return sevs[0];
4468
+ }
4469
+ function whyTheyDiffer(ev, authorityVerdict) {
4470
+ if (ev.completeness === "failed" || ev.completeness === "degraded") {
4471
+ return "Why they differ: the content scan is " + COMPLETENESS_HINT[ev.completeness] + ", so it carries no weight here; CallLint's authority verdict stands on its own evidence.";
4472
+ }
4473
+ if (ev.findings.length === 0 && authorityVerdict !== "SAFE") {
4474
+ return "Why they differ: the content scan found nothing malicious, but CallLint judges the granted authority itself too broad \u2014 clean code can still request unsafe capabilities.";
4475
+ }
4476
+ return "Why they differ: the two tools answer different questions \u2014 content risk (the scanner) vs. whether the requested authority is acceptable (CallLint). Neither overrides the other.";
4477
+ }
4478
+ function renderTrustPacket(summary, toolVersion, style = DEFAULT_STYLE) {
4479
+ const evidence = summary.evidence;
4480
+ if (!evidence || evidence.length === 0) return "";
4481
+ const lines = ["", "Joint Trust Packet", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"];
4482
+ lines.push("Content scan");
4483
+ for (const ev of evidence) {
4484
+ const sev = topProviderSeverity(ev);
4485
+ const findingsLabel = ev.findings.length === 0 ? "no findings" : `${ev.findings.length} finding${ev.findings.length === 1 ? "" : "s"}` + (sev ? ` (top severity: ${sev})` : "");
4486
+ lines.push(
4487
+ ` ${ev.provider} ${ev.providerVersion} scanMode: ${ev.scanMode} completeness: ${COMPLETENESS_HINT[ev.completeness]}`
4488
+ );
4489
+ lines.push(` ${findingsLabel}`);
4490
+ lines.push(` raw report digest: ${ev.rawReportDigest}`);
4491
+ for (const reason of ev.degradedReasons) {
4492
+ lines.push(` degraded: ${reason}`);
4493
+ }
4494
+ }
4495
+ lines.push("Authority scan");
4496
+ lines.push(
4497
+ ` CallLint ${toolVersion} ${verdictTag(summary.verdict, style)} (${summary.publicVerdictLabel})`
4498
+ );
4499
+ lines.push(whyTheyDiffer(evidence[0], summary.verdict));
4500
+ return lines.join("\n");
4501
+ }
4502
+
4503
+ // ../../packages/evidence/src/types.ts
4504
+ var EVIDENCE_SCHEMA_VERSION = "calllint.evidence-provider.v0";
4505
+
4506
+ // ../../packages/evidence/src/providers/skillspector.ts
4507
+ function pinnedVersion(raw) {
4508
+ const tool = raw.tool;
4509
+ const commit = typeof raw.commit === "string" && raw.commit || tool && typeof tool.commit === "string" && tool.commit || "";
4510
+ if (commit) return `git:${commit}`;
4511
+ const version = typeof raw.version === "string" && raw.version || tool && typeof tool.version === "string" && tool.version || "";
4512
+ return version || void 0;
4513
+ }
4514
+ function parseSkillSpectorJson(parsed) {
4515
+ const raw = asObject(parsed, "SkillSpector JSON root");
4516
+ const degradedReasons = [];
4517
+ const usedLlm = raw.llm_used === true || raw.llmUsed === true;
4518
+ const scanMode = usedLlm ? "llm" : "static";
4519
+ const rawFindings = arrayOf(raw.findings) ?? arrayOf(raw.results) ?? [];
4520
+ const findings = rawFindings.map((f) => {
4521
+ const o = asObject(f, "SkillSpector finding");
4522
+ return {
4523
+ providerRuleId: String(o.rule_id ?? o.ruleId ?? o.id ?? "unknown"),
4524
+ providerSeverity: String(o.severity ?? o.level ?? "unknown"),
4525
+ message: typeof o.message === "string" ? o.message : void 0,
4526
+ locations: extractLocations(o)
4527
+ };
4528
+ });
4529
+ let completenessHint;
4530
+ const status = String(raw.status ?? raw.scan_status ?? "").toLowerCase();
4531
+ if (status && status !== "complete" && status !== "completed" && status !== "success") {
4532
+ degradedReasons.push(`SkillSpector reported status "${status}"`);
4533
+ completenessHint = status === "partial" ? "partial" : "degraded";
4534
+ }
4535
+ if (raw.partial === true) {
4536
+ degradedReasons.push("SkillSpector reported a partial scan");
4537
+ completenessHint = completenessHint ?? "partial";
4538
+ }
4539
+ if (raw.degraded === true) {
4540
+ degradedReasons.push("SkillSpector reported a degraded scan");
4541
+ completenessHint = "degraded";
4542
+ }
4543
+ const coverage = (arrayOf(raw.categories) ?? arrayOf(raw.coverage) ?? []).map((c) => String(c));
4544
+ return {
4545
+ provider: "skillspector",
4546
+ providerVersion: pinnedVersion(raw),
4547
+ scanMode,
4548
+ coverage,
4549
+ findings,
4550
+ degradedReasons,
4551
+ completenessHint
4552
+ };
4553
+ }
4554
+ function parseSkillSpectorSarif(parsed) {
4555
+ const raw = asObject(parsed, "SARIF root");
4556
+ const runs = arrayOf(raw.runs) ?? [];
4557
+ if (runs.length === 0) {
4558
+ return {
4559
+ provider: "skillspector",
4560
+ findings: [],
4561
+ degradedReasons: ["SARIF had no runs"]
4562
+ };
4563
+ }
4564
+ const degradedReasons = [];
4565
+ const findings = [];
4566
+ let providerVersion;
4567
+ const coverage = [];
4568
+ for (const runUnknown of runs) {
4569
+ const run2 = asObject(runUnknown, "SARIF run");
4570
+ const tool = asObject(run2.tool ?? {}, "SARIF tool");
4571
+ const driver = asObject(tool.driver ?? {}, "SARIF tool.driver");
4572
+ if (!providerVersion) {
4573
+ const v = typeof driver.semanticVersion === "string" && driver.semanticVersion || typeof driver.version === "string" && driver.version || "";
4574
+ if (v) providerVersion = v;
4575
+ }
4576
+ const results = arrayOf(run2.results) ?? [];
4577
+ for (const resUnknown of results) {
4578
+ const res = asObject(resUnknown, "SARIF result");
4579
+ findings.push({
4580
+ providerRuleId: String(res.ruleId ?? "unknown"),
4581
+ providerSeverity: String(res.level ?? "warning"),
4582
+ message: extractSarifMessage(res),
4583
+ locations: extractSarifLocations(res)
4584
+ });
4585
+ }
4586
+ }
4587
+ degradedReasons.push("SARIF import: provider-specific fields may be lost vs JSON form");
4588
+ return {
4589
+ provider: "skillspector",
4590
+ providerVersion,
4591
+ scanMode: "static",
4592
+ coverage,
4593
+ findings,
4594
+ degradedReasons
4595
+ };
4596
+ }
4597
+ function asObject(v, what) {
4598
+ if (v && typeof v === "object" && !Array.isArray(v)) return v;
4599
+ throw new Error(`expected object for ${what}`);
4600
+ }
4601
+ function arrayOf(v) {
4602
+ return Array.isArray(v) ? v : void 0;
4603
+ }
4604
+ function extractLocations(o) {
4605
+ const loc = o.location ?? o.locations ?? o.path;
4606
+ if (typeof loc === "string") return [loc];
4607
+ if (Array.isArray(loc)) return loc.map((x) => String(x));
4608
+ return void 0;
4609
+ }
4610
+ function extractSarifMessage(res) {
4611
+ const msg = res.message;
4612
+ if (msg && typeof msg.text === "string") return msg.text;
4613
+ return void 0;
4614
+ }
4615
+ function extractSarifLocations(res) {
4616
+ const locs = arrayOf(res.locations);
4617
+ if (!locs) return void 0;
4618
+ const out = [];
4619
+ for (const l of locs) {
4620
+ const lo = l;
4621
+ const phys = lo?.physicalLocation;
4622
+ const art = phys?.artifactLocation;
4623
+ const uri = art && typeof art.uri === "string" ? art.uri : void 0;
4624
+ const region = phys?.region;
4625
+ const line = region && typeof region.startLine === "number" ? region.startLine : void 0;
4626
+ if (uri) out.push(line ? `${uri}:${line}` : uri);
4627
+ }
4628
+ return out.length > 0 ? out : void 0;
4629
+ }
4630
+
4631
+ // ../../packages/evidence/src/importEvidence.ts
4632
+ var ZERO_DIGEST = `sha256:${"0".repeat(64)}`;
4633
+ function detectProvider(raw, explicit) {
4634
+ if (explicit) return explicit;
4635
+ const r = raw;
4636
+ if (!r) return "unknown";
4637
+ const topTool = r.tool;
4638
+ const jsonName = typeof r.scanner === "string" && r.scanner || topTool && typeof topTool.name === "string" && topTool.name || "";
4639
+ let sarifName = "";
4640
+ const runs = r.runs;
4641
+ if (Array.isArray(runs) && runs.length > 0) {
4642
+ const run2 = runs[0];
4643
+ const tool = run2?.tool;
4644
+ const driver = tool?.driver;
4645
+ if (driver && typeof driver.name === "string") sarifName = driver.name;
4646
+ }
4647
+ if (/skillspector/i.test(String(jsonName)) || /skillspector/i.test(sarifName)) {
4648
+ return "skillspector";
4649
+ }
4650
+ return "unknown";
4651
+ }
4652
+ function importEvidence(rawText, opts = {}) {
4653
+ const format = opts.format ?? (looksLikeSarif(rawText) ? "sarif" : "json");
4654
+ const rawReportDigest = sha256(rawText);
4655
+ let parsed;
4656
+ try {
4657
+ parsed = JSON.parse(rawText);
4658
+ } catch {
4659
+ return failClosed(opts.provider ?? "unknown", rawReportDigest, [
4660
+ `report is not valid JSON (format=${format})`
4661
+ ]);
4662
+ }
4663
+ const provider = detectProvider(parsed, opts.provider);
4664
+ let result;
4665
+ try {
4666
+ if (provider === "skillspector") {
4667
+ result = format === "sarif" ? parseSkillSpectorSarif(parsed) : parseSkillSpectorJson(parsed);
4668
+ } else {
4669
+ return failClosed(provider, rawReportDigest, [
4670
+ `no adapter for provider "${provider}"; evidence not interpreted`
4671
+ ]);
4672
+ }
4673
+ } catch (err2) {
4674
+ return failClosed(provider, rawReportDigest, [
4675
+ `adapter error: ${err2.message}`
4676
+ ]);
4677
+ }
4678
+ return finalizeEnvelope(result, rawReportDigest);
4679
+ }
4680
+ function failClosed(provider, rawReportDigest, reasons) {
4681
+ return {
4682
+ schema_version: EVIDENCE_SCHEMA_VERSION,
4683
+ provider: provider || "unknown",
4684
+ providerVersion: "unknown",
4685
+ artifactDigest: ZERO_DIGEST,
4686
+ scanMode: "static",
4687
+ coverage: [],
4688
+ completeness: "failed",
4689
+ findings: [],
4690
+ rawReportDigest,
4691
+ degradedReasons: reasons
4692
+ };
4693
+ }
4694
+ function finalizeEnvelope(r, rawReportDigest) {
4695
+ const degradedReasons = [...r.degradedReasons ?? []];
4696
+ const rank = { complete: 0, partial: 1, degraded: 2, failed: 3 };
4697
+ const rankToCompleteness = ["complete", "partial", "degraded", "failed"];
4698
+ let level = rank[r.completenessHint ?? (degradedReasons.length > 0 ? "degraded" : "complete")];
4699
+ let providerVersion = r.providerVersion?.trim() || "";
4700
+ if (!providerVersion) {
4701
+ providerVersion = "unknown";
4702
+ degradedReasons.push("provider version not pinned (no release/commit reported)");
4703
+ level = Math.max(level, rank.degraded);
4704
+ }
4705
+ const completeness = rankToCompleteness[level];
4706
+ return {
4707
+ schema_version: EVIDENCE_SCHEMA_VERSION,
4708
+ provider: r.provider,
4709
+ providerVersion,
4710
+ artifactDigest: r.artifactDigest ?? ZERO_DIGEST,
4711
+ scanMode: r.scanMode ?? "static",
4712
+ coverage: r.coverage ?? [],
4713
+ completeness,
4714
+ findings: r.findings,
4715
+ rawReportDigest,
4716
+ startedAt: r.startedAt,
4717
+ finishedAt: r.finishedAt,
4718
+ degradedReasons
4719
+ };
4720
+ }
4721
+ function looksLikeSarif(text) {
4722
+ return /"runs"\s*:/.test(text) && /sarif/i.test(text);
4723
+ }
4724
+
4445
4725
  // src/exitCode.ts
4446
4726
  function exitCodeFor(summary, policy) {
4447
4727
  const verdict = summary.verdict;
@@ -5030,6 +5310,15 @@ function scanCommand(args, deps) {
5030
5310
  function scanOneConfig(text, configPath, policy, args, deps, allowReceipt = true) {
5031
5311
  const surfaceDir = flagStr(args.flags, "surface-dir");
5032
5312
  const surfaces = surfaceDir ? readDocumentSurfaces(resolve3(deps.cwd, surfaceDir)) : void 0;
5313
+ const evidenceFile = flagStr(args.flags, "evidence");
5314
+ let evidence;
5315
+ if (evidenceFile) {
5316
+ const loaded = loadEvidence(evidenceFile, args, deps);
5317
+ if ("error" in loaded) {
5318
+ return { stdout: "", stderr: loaded.error, exitCode: loaded.exitCode };
5319
+ }
5320
+ evidence = [loaded];
5321
+ }
5033
5322
  let summary;
5034
5323
  try {
5035
5324
  summary = scanConfigText(text, configPath, {
@@ -5037,7 +5326,8 @@ function scanOneConfig(text, configPath, policy, args, deps, allowReceipt = true
5037
5326
  now: deps.now,
5038
5327
  generatedAt: deps.generatedAt,
5039
5328
  extraFindings: deps.online?.extraFindings,
5040
- surfaces
5329
+ surfaces,
5330
+ evidence
5041
5331
  });
5042
5332
  } catch (err2) {
5043
5333
  if (err2 instanceof ConfigParseError) {
@@ -5055,7 +5345,7 @@ function scanOneConfig(text, configPath, policy, args, deps, allowReceipt = true
5055
5345
  } catch {
5056
5346
  }
5057
5347
  }
5058
- const stdout = renderSummary(summary, args);
5348
+ const stdout = renderSummary(summary, args, deps.toolVersion);
5059
5349
  if (allowReceipt && flagBool(args.flags, "receipt")) {
5060
5350
  const err2 = writeReceiptFile(summary, text, configPath, policy, args, deps);
5061
5351
  if (err2) return { stdout: "", stderr: err2, exitCode: EXIT.ERROR };
@@ -5088,15 +5378,31 @@ function writeReceiptFile(summary, text, configPath, policy, args, deps) {
5088
5378
  }
5089
5379
  return void 0;
5090
5380
  }
5091
- function renderSummary(summary, args) {
5381
+ function loadEvidence(file, args, deps) {
5382
+ let rawText;
5383
+ try {
5384
+ rawText = readFileSync12(resolve3(deps.cwd, file), "utf8");
5385
+ } catch (err2) {
5386
+ const e = err2;
5387
+ return {
5388
+ error: e.code === "ENOENT" ? `Evidence file not found: ${file}` : e.message,
5389
+ exitCode: EXIT.USAGE
5390
+ };
5391
+ }
5392
+ const fmt = flagStr(args.flags, "evidence-format");
5393
+ const format = fmt === "sarif" ? "sarif" : fmt === "json" ? "json" : void 0;
5394
+ return importEvidence(rawText, { format });
5395
+ }
5396
+ function renderSummary(summary, args, toolVersion) {
5092
5397
  const style = flagBool(args.flags, "no-emoji") ? NO_EMOJI_STYLE : DEFAULT_STYLE;
5093
5398
  if (flagBool(args.flags, "json")) return renderJson(summary);
5094
5399
  if (flagBool(args.flags, "sarif")) return renderSarif(summary);
5095
5400
  if (flagBool(args.flags, "markdown")) return renderMarkdown(summary);
5096
5401
  if (flagBool(args.flags, "badge")) return renderBadge(summary);
5097
5402
  if (flagBool(args.flags, "html")) return renderHtml(summary);
5098
- if (flagBool(args.flags, "compact")) return renderCompact(summary, style);
5099
- return renderTerminal(summary, style);
5403
+ const base = flagBool(args.flags, "compact") ? renderCompact(summary, style) : renderTerminal(summary, style);
5404
+ const packet = renderTrustPacket(summary, toolVersion ?? "0.0.0-dev", style);
5405
+ return packet ? base + "\n" + packet : base;
5100
5406
  }
5101
5407
  function scanChangedCommand(args, deps) {
5102
5408
  const policyPath = flagStr(args.flags, "policy");
@@ -7502,230 +7808,6 @@ function formatAgentType(agentType) {
7502
7808
  // src/commands/evidence.ts
7503
7809
  import { readFileSync as readFileSync16 } from "node:fs";
7504
7810
  import { resolve as resolve8 } from "node:path";
7505
-
7506
- // ../../packages/evidence/src/types.ts
7507
- var EVIDENCE_SCHEMA_VERSION = "calllint.evidence-provider.v0";
7508
-
7509
- // ../../packages/evidence/src/providers/skillspector.ts
7510
- function pinnedVersion(raw) {
7511
- const tool = raw.tool;
7512
- const commit = typeof raw.commit === "string" && raw.commit || tool && typeof tool.commit === "string" && tool.commit || "";
7513
- if (commit) return `git:${commit}`;
7514
- const version = typeof raw.version === "string" && raw.version || tool && typeof tool.version === "string" && tool.version || "";
7515
- return version || void 0;
7516
- }
7517
- function parseSkillSpectorJson(parsed) {
7518
- const raw = asObject(parsed, "SkillSpector JSON root");
7519
- const degradedReasons = [];
7520
- const usedLlm = raw.llm_used === true || raw.llmUsed === true;
7521
- const scanMode = usedLlm ? "llm" : "static";
7522
- const rawFindings = arrayOf(raw.findings) ?? arrayOf(raw.results) ?? [];
7523
- const findings = rawFindings.map((f) => {
7524
- const o = asObject(f, "SkillSpector finding");
7525
- return {
7526
- providerRuleId: String(o.rule_id ?? o.ruleId ?? o.id ?? "unknown"),
7527
- providerSeverity: String(o.severity ?? o.level ?? "unknown"),
7528
- message: typeof o.message === "string" ? o.message : void 0,
7529
- locations: extractLocations(o)
7530
- };
7531
- });
7532
- let completenessHint;
7533
- const status = String(raw.status ?? raw.scan_status ?? "").toLowerCase();
7534
- if (status && status !== "complete" && status !== "completed" && status !== "success") {
7535
- degradedReasons.push(`SkillSpector reported status "${status}"`);
7536
- completenessHint = status === "partial" ? "partial" : "degraded";
7537
- }
7538
- if (raw.partial === true) {
7539
- degradedReasons.push("SkillSpector reported a partial scan");
7540
- completenessHint = completenessHint ?? "partial";
7541
- }
7542
- if (raw.degraded === true) {
7543
- degradedReasons.push("SkillSpector reported a degraded scan");
7544
- completenessHint = "degraded";
7545
- }
7546
- const coverage = (arrayOf(raw.categories) ?? arrayOf(raw.coverage) ?? []).map((c) => String(c));
7547
- return {
7548
- provider: "skillspector",
7549
- providerVersion: pinnedVersion(raw),
7550
- scanMode,
7551
- coverage,
7552
- findings,
7553
- degradedReasons,
7554
- completenessHint
7555
- };
7556
- }
7557
- function parseSkillSpectorSarif(parsed) {
7558
- const raw = asObject(parsed, "SARIF root");
7559
- const runs = arrayOf(raw.runs) ?? [];
7560
- if (runs.length === 0) {
7561
- return {
7562
- provider: "skillspector",
7563
- findings: [],
7564
- degradedReasons: ["SARIF had no runs"]
7565
- };
7566
- }
7567
- const degradedReasons = [];
7568
- const findings = [];
7569
- let providerVersion;
7570
- const coverage = [];
7571
- for (const runUnknown of runs) {
7572
- const run2 = asObject(runUnknown, "SARIF run");
7573
- const tool = asObject(run2.tool ?? {}, "SARIF tool");
7574
- const driver = asObject(tool.driver ?? {}, "SARIF tool.driver");
7575
- if (!providerVersion) {
7576
- const v = typeof driver.semanticVersion === "string" && driver.semanticVersion || typeof driver.version === "string" && driver.version || "";
7577
- if (v) providerVersion = v;
7578
- }
7579
- const results = arrayOf(run2.results) ?? [];
7580
- for (const resUnknown of results) {
7581
- const res = asObject(resUnknown, "SARIF result");
7582
- findings.push({
7583
- providerRuleId: String(res.ruleId ?? "unknown"),
7584
- providerSeverity: String(res.level ?? "warning"),
7585
- message: extractSarifMessage(res),
7586
- locations: extractSarifLocations(res)
7587
- });
7588
- }
7589
- }
7590
- degradedReasons.push("SARIF import: provider-specific fields may be lost vs JSON form");
7591
- return {
7592
- provider: "skillspector",
7593
- providerVersion,
7594
- scanMode: "static",
7595
- coverage,
7596
- findings,
7597
- degradedReasons
7598
- };
7599
- }
7600
- function asObject(v, what) {
7601
- if (v && typeof v === "object" && !Array.isArray(v)) return v;
7602
- throw new Error(`expected object for ${what}`);
7603
- }
7604
- function arrayOf(v) {
7605
- return Array.isArray(v) ? v : void 0;
7606
- }
7607
- function extractLocations(o) {
7608
- const loc = o.location ?? o.locations ?? o.path;
7609
- if (typeof loc === "string") return [loc];
7610
- if (Array.isArray(loc)) return loc.map((x) => String(x));
7611
- return void 0;
7612
- }
7613
- function extractSarifMessage(res) {
7614
- const msg = res.message;
7615
- if (msg && typeof msg.text === "string") return msg.text;
7616
- return void 0;
7617
- }
7618
- function extractSarifLocations(res) {
7619
- const locs = arrayOf(res.locations);
7620
- if (!locs) return void 0;
7621
- const out = [];
7622
- for (const l of locs) {
7623
- const lo = l;
7624
- const phys = lo?.physicalLocation;
7625
- const art = phys?.artifactLocation;
7626
- const uri = art && typeof art.uri === "string" ? art.uri : void 0;
7627
- const region = phys?.region;
7628
- const line = region && typeof region.startLine === "number" ? region.startLine : void 0;
7629
- if (uri) out.push(line ? `${uri}:${line}` : uri);
7630
- }
7631
- return out.length > 0 ? out : void 0;
7632
- }
7633
-
7634
- // ../../packages/evidence/src/importEvidence.ts
7635
- var ZERO_DIGEST = `sha256:${"0".repeat(64)}`;
7636
- function detectProvider(raw, explicit) {
7637
- if (explicit) return explicit;
7638
- const r = raw;
7639
- if (!r) return "unknown";
7640
- const topTool = r.tool;
7641
- const jsonName = typeof r.scanner === "string" && r.scanner || topTool && typeof topTool.name === "string" && topTool.name || "";
7642
- let sarifName = "";
7643
- const runs = r.runs;
7644
- if (Array.isArray(runs) && runs.length > 0) {
7645
- const run2 = runs[0];
7646
- const tool = run2?.tool;
7647
- const driver = tool?.driver;
7648
- if (driver && typeof driver.name === "string") sarifName = driver.name;
7649
- }
7650
- if (/skillspector/i.test(String(jsonName)) || /skillspector/i.test(sarifName)) {
7651
- return "skillspector";
7652
- }
7653
- return "unknown";
7654
- }
7655
- function importEvidence(rawText, opts = {}) {
7656
- const format = opts.format ?? (looksLikeSarif(rawText) ? "sarif" : "json");
7657
- const rawReportDigest = sha256(rawText);
7658
- let parsed;
7659
- try {
7660
- parsed = JSON.parse(rawText);
7661
- } catch {
7662
- return failClosed(opts.provider ?? "unknown", rawReportDigest, [
7663
- `report is not valid JSON (format=${format})`
7664
- ]);
7665
- }
7666
- const provider = detectProvider(parsed, opts.provider);
7667
- let result;
7668
- try {
7669
- if (provider === "skillspector") {
7670
- result = format === "sarif" ? parseSkillSpectorSarif(parsed) : parseSkillSpectorJson(parsed);
7671
- } else {
7672
- return failClosed(provider, rawReportDigest, [
7673
- `no adapter for provider "${provider}"; evidence not interpreted`
7674
- ]);
7675
- }
7676
- } catch (err2) {
7677
- return failClosed(provider, rawReportDigest, [
7678
- `adapter error: ${err2.message}`
7679
- ]);
7680
- }
7681
- return finalizeEnvelope(result, rawReportDigest);
7682
- }
7683
- function failClosed(provider, rawReportDigest, reasons) {
7684
- return {
7685
- schema_version: EVIDENCE_SCHEMA_VERSION,
7686
- provider: provider || "unknown",
7687
- providerVersion: "unknown",
7688
- artifactDigest: ZERO_DIGEST,
7689
- scanMode: "static",
7690
- coverage: [],
7691
- completeness: "failed",
7692
- findings: [],
7693
- rawReportDigest,
7694
- degradedReasons: reasons
7695
- };
7696
- }
7697
- function finalizeEnvelope(r, rawReportDigest) {
7698
- const degradedReasons = [...r.degradedReasons ?? []];
7699
- const rank = { complete: 0, partial: 1, degraded: 2, failed: 3 };
7700
- const rankToCompleteness = ["complete", "partial", "degraded", "failed"];
7701
- let level = rank[r.completenessHint ?? (degradedReasons.length > 0 ? "degraded" : "complete")];
7702
- let providerVersion = r.providerVersion?.trim() || "";
7703
- if (!providerVersion) {
7704
- providerVersion = "unknown";
7705
- degradedReasons.push("provider version not pinned (no release/commit reported)");
7706
- level = Math.max(level, rank.degraded);
7707
- }
7708
- const completeness = rankToCompleteness[level];
7709
- return {
7710
- schema_version: EVIDENCE_SCHEMA_VERSION,
7711
- provider: r.provider,
7712
- providerVersion,
7713
- artifactDigest: r.artifactDigest ?? ZERO_DIGEST,
7714
- scanMode: r.scanMode ?? "static",
7715
- coverage: r.coverage ?? [],
7716
- completeness,
7717
- findings: r.findings,
7718
- rawReportDigest,
7719
- startedAt: r.startedAt,
7720
- finishedAt: r.finishedAt,
7721
- degradedReasons
7722
- };
7723
- }
7724
- function looksLikeSarif(text) {
7725
- return /"runs"\s*:/.test(text) && /sarif/i.test(text);
7726
- }
7727
-
7728
- // src/commands/evidence.ts
7729
7811
  function evidenceCommand(args, deps) {
7730
7812
  const subcommand = args.positionals[0];
7731
7813
  if (!subcommand || subcommand === "help") {
@@ -8681,7 +8763,7 @@ function writePlanFile(plan, deps) {
8681
8763
  writeFileSync10(file, JSON.stringify(plan, null, 2) + "\n", "utf8");
8682
8764
  return file;
8683
8765
  }
8684
- function loadEvidence(file, deps, format, provider) {
8766
+ function loadEvidence2(file, deps, format, provider) {
8685
8767
  let rawText;
8686
8768
  try {
8687
8769
  rawText = readFileSync18(resolvePath4(deps.cwd, file), "utf8");
@@ -8720,7 +8802,7 @@ function trustPrepare(args, deps) {
8720
8802
  const providerFlag = typeof args.flags["provider"] === "string" ? args.flags["provider"] : void 0;
8721
8803
  let evidence;
8722
8804
  if (evidenceFile) {
8723
- const imported = loadEvidence(evidenceFile, deps, format, providerFlag);
8805
+ const imported = loadEvidence2(evidenceFile, deps, format, providerFlag);
8724
8806
  if ("error" in imported) {
8725
8807
  return { stdout: "", stderr: `Error: ${imported.error}`, exitCode: imported.exitCode };
8726
8808
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "calllint",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Evidence-backed security verdicts for MCP servers and agent tools. Lint agent tool-call risk before tools run — SAFE / REVIEW / BLOCK / UNKNOWN, with evidence. Never executes the server it judges.",
5
5
  "keywords": [
6
6
  "mcp",