calllint 0.7.0 → 0.9.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.
- package/dist/index.js +296 -10
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFileSync as
|
|
4
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
5
5
|
import { execFileSync } from "node:child_process";
|
|
6
6
|
|
|
7
7
|
// src/args.ts
|
|
@@ -67,6 +67,7 @@ COMMANDS
|
|
|
67
67
|
diagnostics [target] Emit editor/agent-host diagnostics JSON (calllint.diagnostics.v0)
|
|
68
68
|
baseline [target] Record the approved risk surface as a baseline
|
|
69
69
|
approve Record the repo-wide capability surface as approved state (L4)
|
|
70
|
+
receipt verify <f> Structurally validate a local calllint.receipt.v0 file
|
|
70
71
|
gen-rule --host <h> Emit the CallLint agent-safety rule for a host (CLAUDE.md, etc.)
|
|
71
72
|
policy init Write a default calllint.policy.json
|
|
72
73
|
policy explain Show the effective policy
|
|
@@ -92,6 +93,8 @@ SCAN OPTIONS
|
|
|
92
93
|
--markdown Emit Markdown for PR comments / GitHub Step Summary
|
|
93
94
|
--html Emit a self-contained HTML report
|
|
94
95
|
--badge Emit a shields.io endpoint badge JSON (SAFE/REVIEW/UNKNOWN/BLOCK)
|
|
96
|
+
--receipt Also write a local calllint.receipt.v0 (offline reporting layer)
|
|
97
|
+
--receipt-out <f> Receipt output path (default: calllint-receipt.json)
|
|
95
98
|
--policy <file> Use a policy file (default: built-in defaults)
|
|
96
99
|
--stdin Read config JSON from stdin
|
|
97
100
|
--ci Exit non-zero per policy (BLOCK=30, UNKNOWN=20, REVIEW=10 if enabled)
|
|
@@ -112,6 +115,7 @@ EXAMPLES
|
|
|
112
115
|
calllint check ./mcp.json --json
|
|
113
116
|
calllint scan .cursor/mcp.json --markdown
|
|
114
117
|
calllint scan .cursor/mcp.json --badge > calllint-badge.json
|
|
118
|
+
calllint scan .cursor/mcp.json --receipt && calllint receipt verify calllint-receipt.json
|
|
115
119
|
calllint verify ./mcp.json --ci
|
|
116
120
|
calllint approve && calllint verify --approved --ci
|
|
117
121
|
calllint explain filesystem
|
|
@@ -2978,6 +2982,147 @@ ${rule()}
|
|
|
2978
2982
|
}
|
|
2979
2983
|
}
|
|
2980
2984
|
|
|
2985
|
+
// ../../packages/core/src/receipt/createReceipt.ts
|
|
2986
|
+
import { randomBytes } from "node:crypto";
|
|
2987
|
+
function newReceiptId() {
|
|
2988
|
+
const b64 = randomBytes(16).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
2989
|
+
return `clrec_${b64}`;
|
|
2990
|
+
}
|
|
2991
|
+
function riskCounts(counts) {
|
|
2992
|
+
return {
|
|
2993
|
+
safe: counts.SAFE ?? 0,
|
|
2994
|
+
review: counts.REVIEW ?? 0,
|
|
2995
|
+
block: counts.BLOCK ?? 0,
|
|
2996
|
+
unknown: counts.UNKNOWN ?? 0
|
|
2997
|
+
};
|
|
2998
|
+
}
|
|
2999
|
+
function findingRefs(reports) {
|
|
3000
|
+
const refs = [];
|
|
3001
|
+
for (const report of reports) {
|
|
3002
|
+
for (const f of report.findings ?? []) {
|
|
3003
|
+
const path = f.evidence?.find((e) => typeof e.path === "string")?.path;
|
|
3004
|
+
refs.push({
|
|
3005
|
+
rule_id: f.id,
|
|
3006
|
+
severity: f.severity,
|
|
3007
|
+
...path ? { evidence_path: path } : {}
|
|
3008
|
+
});
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
return refs;
|
|
3012
|
+
}
|
|
3013
|
+
function createReceipt(input, now) {
|
|
3014
|
+
const summary = input.scanReport;
|
|
3015
|
+
const receipt = {
|
|
3016
|
+
schema_version: "calllint.receipt.v0",
|
|
3017
|
+
receipt_id: newReceiptId(),
|
|
3018
|
+
created_at: now,
|
|
3019
|
+
tool: { name: "calllint", version: input.toolVersion },
|
|
3020
|
+
subject: input.subject,
|
|
3021
|
+
verdict: summary.verdict,
|
|
3022
|
+
hashes: {
|
|
3023
|
+
input_hash: hashInput(input.inputForHash),
|
|
3024
|
+
policy_hash: hashJson(input.effectivePolicyForHash),
|
|
3025
|
+
report_hash: hashJson(input.scanReport),
|
|
3026
|
+
ruleset_hash: hashJson(input.rulesetForHash)
|
|
3027
|
+
},
|
|
3028
|
+
risk_counts: riskCounts(summary.counts),
|
|
3029
|
+
finding_refs: findingRefs(summary.reports ?? []),
|
|
3030
|
+
trust_boundaries: {
|
|
3031
|
+
executed_target: false,
|
|
3032
|
+
network_used: input.networkUsed === true,
|
|
3033
|
+
llm_in_verdict_path: false,
|
|
3034
|
+
secret_values_read: false
|
|
3035
|
+
},
|
|
3036
|
+
...input.corpus ? { corpus: input.corpus } : {}
|
|
3037
|
+
};
|
|
3038
|
+
return receipt;
|
|
3039
|
+
}
|
|
3040
|
+
function hashInput(value) {
|
|
3041
|
+
return typeof value === "string" ? sha256(value) : hashJson(value);
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
// ../../packages/core/src/receipt/verifyReceipt.ts
|
|
3045
|
+
var SHA256_RE = /^sha256:[0-9a-f]{64}$/;
|
|
3046
|
+
var RECEIPT_ID_RE = /^clrec_[A-Za-z0-9_-]+$/;
|
|
3047
|
+
var VERDICTS = /* @__PURE__ */ new Set(["SAFE", "REVIEW", "BLOCK", "UNKNOWN"]);
|
|
3048
|
+
var HASH_KEYS = ["input_hash", "policy_hash", "report_hash", "ruleset_hash"];
|
|
3049
|
+
var COUNT_KEYS = ["safe", "review", "block", "unknown"];
|
|
3050
|
+
function isObject(v) {
|
|
3051
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3052
|
+
}
|
|
3053
|
+
function verifyReceipt(input) {
|
|
3054
|
+
const errors = [];
|
|
3055
|
+
const push = (msg) => errors.push(msg);
|
|
3056
|
+
if (!isObject(input)) {
|
|
3057
|
+
return { valid: false, errors: ["receipt is not a JSON object"], signed: false };
|
|
3058
|
+
}
|
|
3059
|
+
const r = input;
|
|
3060
|
+
if (r.schema_version !== "calllint.receipt.v0") {
|
|
3061
|
+
push(`schema_version must be "calllint.receipt.v0" (got ${JSON.stringify(r.schema_version)})`);
|
|
3062
|
+
}
|
|
3063
|
+
if (typeof r.receipt_id !== "string" || !RECEIPT_ID_RE.test(r.receipt_id)) {
|
|
3064
|
+
push("receipt_id must match /^clrec_[A-Za-z0-9_-]+$/");
|
|
3065
|
+
}
|
|
3066
|
+
if (typeof r.created_at !== "string" || Number.isNaN(Date.parse(r.created_at))) {
|
|
3067
|
+
push("created_at must be an ISO-8601 timestamp");
|
|
3068
|
+
}
|
|
3069
|
+
if (!isObject(r.tool) || r.tool.name !== "calllint" || typeof r.tool.version !== "string") {
|
|
3070
|
+
push('tool must be { name: "calllint", version: <string> }');
|
|
3071
|
+
}
|
|
3072
|
+
if (!isObject(r.subject) || r.subject.type !== "scan") {
|
|
3073
|
+
push('subject.type must be "scan"');
|
|
3074
|
+
}
|
|
3075
|
+
if (typeof r.verdict !== "string" || !VERDICTS.has(r.verdict)) {
|
|
3076
|
+
push("verdict must be one of SAFE | REVIEW | BLOCK | UNKNOWN");
|
|
3077
|
+
}
|
|
3078
|
+
if (!isObject(r.hashes)) {
|
|
3079
|
+
push("hashes must be an object");
|
|
3080
|
+
} else {
|
|
3081
|
+
for (const key of HASH_KEYS) {
|
|
3082
|
+
const val = r.hashes[key];
|
|
3083
|
+
if (typeof val !== "string" || !SHA256_RE.test(val)) {
|
|
3084
|
+
push(`hashes.${key} must match sha256:<64 lowercase hex>`);
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
if (!isObject(r.risk_counts)) {
|
|
3089
|
+
push("risk_counts must be an object");
|
|
3090
|
+
} else {
|
|
3091
|
+
for (const key of COUNT_KEYS) {
|
|
3092
|
+
const val = r.risk_counts[key];
|
|
3093
|
+
if (typeof val !== "number" || !Number.isInteger(val) || val < 0) {
|
|
3094
|
+
push(`risk_counts.${key} must be an integer >= 0`);
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
if (!Array.isArray(r.finding_refs)) {
|
|
3099
|
+
push("finding_refs must be an array");
|
|
3100
|
+
} else {
|
|
3101
|
+
r.finding_refs.forEach((ref, i) => {
|
|
3102
|
+
if (!isObject(ref) || typeof ref.rule_id !== "string" || typeof ref.severity !== "string") {
|
|
3103
|
+
push(`finding_refs[${i}] must have string rule_id and severity`);
|
|
3104
|
+
}
|
|
3105
|
+
});
|
|
3106
|
+
}
|
|
3107
|
+
if (!isObject(r.trust_boundaries)) {
|
|
3108
|
+
push("trust_boundaries must be an object");
|
|
3109
|
+
} else {
|
|
3110
|
+
const tb = r.trust_boundaries;
|
|
3111
|
+
if (tb.executed_target !== false) push("trust_boundaries.executed_target must be false");
|
|
3112
|
+
if (tb.llm_in_verdict_path !== false) push("trust_boundaries.llm_in_verdict_path must be false");
|
|
3113
|
+
if (tb.secret_values_read !== false) push("trust_boundaries.secret_values_read must be false");
|
|
3114
|
+
if (typeof tb.network_used !== "boolean") push("trust_boundaries.network_used must be a boolean");
|
|
3115
|
+
}
|
|
3116
|
+
const signed = isObject(r.signature);
|
|
3117
|
+
if (signed) {
|
|
3118
|
+
const sig = r.signature;
|
|
3119
|
+
if (typeof sig.algorithm !== "string" || typeof sig.key_id !== "string" || typeof sig.value !== "string") {
|
|
3120
|
+
push("signature, when present, must have string algorithm, key_id, and value");
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
return { valid: errors.length === 0, errors, signed };
|
|
3124
|
+
}
|
|
3125
|
+
|
|
2981
3126
|
// ../../packages/report-renderer/src/style.ts
|
|
2982
3127
|
var DEFAULT_STYLE = { emoji: true };
|
|
2983
3128
|
var NO_EMOJI_STYLE = { emoji: false };
|
|
@@ -3738,7 +3883,7 @@ function readDocumentSurfaces(dir) {
|
|
|
3738
3883
|
}
|
|
3739
3884
|
|
|
3740
3885
|
// src/commands/scan.ts
|
|
3741
|
-
import { readFileSync as readFileSync7 } from "node:fs";
|
|
3886
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
|
|
3742
3887
|
function scanCommand(args, deps) {
|
|
3743
3888
|
if (flagBool(args.flags, "changed")) {
|
|
3744
3889
|
return scanChangedCommand(args, deps);
|
|
@@ -3761,7 +3906,7 @@ function scanCommand(args, deps) {
|
|
|
3761
3906
|
const { text, configPath } = input;
|
|
3762
3907
|
return scanOneConfig(text, configPath, policy, args, deps);
|
|
3763
3908
|
}
|
|
3764
|
-
function scanOneConfig(text, configPath, policy, args, deps) {
|
|
3909
|
+
function scanOneConfig(text, configPath, policy, args, deps, allowReceipt = true) {
|
|
3765
3910
|
const surfaceDir = flagStr(args.flags, "surface-dir");
|
|
3766
3911
|
const surfaces = surfaceDir ? readDocumentSurfaces(resolve2(deps.cwd, surfaceDir)) : void 0;
|
|
3767
3912
|
let summary;
|
|
@@ -3790,9 +3935,38 @@ function scanOneConfig(text, configPath, policy, args, deps) {
|
|
|
3790
3935
|
}
|
|
3791
3936
|
}
|
|
3792
3937
|
const stdout = renderSummary(summary, args);
|
|
3938
|
+
if (allowReceipt && flagBool(args.flags, "receipt")) {
|
|
3939
|
+
const err = writeReceiptFile(summary, text, configPath, policy, args, deps);
|
|
3940
|
+
if (err) return { stdout: "", stderr: err, exitCode: EXIT.ERROR };
|
|
3941
|
+
}
|
|
3793
3942
|
const exitCode = flagBool(args.flags, "ci") ? exitCodeFor(summary, policy) : EXIT.OK;
|
|
3794
3943
|
return { stdout, exitCode };
|
|
3795
3944
|
}
|
|
3945
|
+
function writeReceiptFile(summary, text, configPath, policy, args, deps) {
|
|
3946
|
+
const toolVersion = deps.toolVersion ?? "0.0.0-dev";
|
|
3947
|
+
const receipt = createReceipt(
|
|
3948
|
+
{
|
|
3949
|
+
toolVersion,
|
|
3950
|
+
subject: { type: "scan", target: configPath },
|
|
3951
|
+
inputForHash: text,
|
|
3952
|
+
effectivePolicyForHash: policy ?? { policy: "default" },
|
|
3953
|
+
scanReport: summary,
|
|
3954
|
+
rulesetForHash: { tool: "calllint", version: toolVersion },
|
|
3955
|
+
networkUsed: flagBool(args.flags, "online")
|
|
3956
|
+
},
|
|
3957
|
+
deps.generatedAt
|
|
3958
|
+
);
|
|
3959
|
+
const outPath = resolve2(
|
|
3960
|
+
deps.cwd,
|
|
3961
|
+
flagStr(args.flags, "receipt-out") ?? "calllint-receipt.json"
|
|
3962
|
+
);
|
|
3963
|
+
try {
|
|
3964
|
+
writeFileSync3(outPath, JSON.stringify(receipt, null, 2) + "\n", "utf8");
|
|
3965
|
+
} catch (e) {
|
|
3966
|
+
return `Could not write receipt to ${outPath}: ${e instanceof Error ? e.message : String(e)}`;
|
|
3967
|
+
}
|
|
3968
|
+
return void 0;
|
|
3969
|
+
}
|
|
3796
3970
|
function renderSummary(summary, args) {
|
|
3797
3971
|
const style = flagBool(args.flags, "no-emoji") ? NO_EMOJI_STYLE : DEFAULT_STYLE;
|
|
3798
3972
|
if (flagBool(args.flags, "json")) return renderJson(summary);
|
|
@@ -3931,7 +4105,7 @@ function worstExit2(decisions) {
|
|
|
3931
4105
|
}
|
|
3932
4106
|
|
|
3933
4107
|
// src/commands/genRule.ts
|
|
3934
|
-
import { writeFileSync as
|
|
4108
|
+
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "node:fs";
|
|
3935
4109
|
import { dirname as dirname3, join as join7, resolve as resolve3 } from "node:path";
|
|
3936
4110
|
function isRuleHost(v) {
|
|
3937
4111
|
return v !== void 0 && RULE_HOSTS.includes(v);
|
|
@@ -3978,7 +4152,7 @@ Run \`calllint gen-rule\` to list hosts.`,
|
|
|
3978
4152
|
}
|
|
3979
4153
|
function defaultWrite(path, content) {
|
|
3980
4154
|
mkdirSync3(dirname3(path), { recursive: true });
|
|
3981
|
-
|
|
4155
|
+
writeFileSync4(path, content, "utf8");
|
|
3982
4156
|
}
|
|
3983
4157
|
|
|
3984
4158
|
// src/commands/diagnostics.ts
|
|
@@ -4061,7 +4235,7 @@ function explainCommand(args, deps) {
|
|
|
4061
4235
|
}
|
|
4062
4236
|
|
|
4063
4237
|
// src/commands/policy.ts
|
|
4064
|
-
import { existsSync as existsSync6, writeFileSync as
|
|
4238
|
+
import { existsSync as existsSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4065
4239
|
import { join as join9 } from "node:path";
|
|
4066
4240
|
function policyCommand(args, deps) {
|
|
4067
4241
|
const sub = args.positionals[0];
|
|
@@ -4074,7 +4248,7 @@ function policyCommand(args, deps) {
|
|
|
4074
4248
|
exitCode: EXIT.USAGE
|
|
4075
4249
|
};
|
|
4076
4250
|
}
|
|
4077
|
-
|
|
4251
|
+
writeFileSync5(path, defaultPolicyJson(), "utf8");
|
|
4078
4252
|
return { stdout: `Wrote default policy to ${path}`, exitCode: EXIT.OK };
|
|
4079
4253
|
}
|
|
4080
4254
|
if (sub === "explain") {
|
|
@@ -4231,6 +4405,73 @@ function approveCommand(args, deps) {
|
|
|
4231
4405
|
};
|
|
4232
4406
|
}
|
|
4233
4407
|
|
|
4408
|
+
// src/commands/receipt.ts
|
|
4409
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
4410
|
+
import { resolve as resolve4 } from "node:path";
|
|
4411
|
+
function receiptCommand(args, deps) {
|
|
4412
|
+
const sub = args.positionals[0];
|
|
4413
|
+
if (sub !== "verify") {
|
|
4414
|
+
return {
|
|
4415
|
+
stdout: "",
|
|
4416
|
+
stderr: "Usage: calllint receipt verify <receipt.json>",
|
|
4417
|
+
exitCode: EXIT.USAGE
|
|
4418
|
+
};
|
|
4419
|
+
}
|
|
4420
|
+
const file = args.positionals[1];
|
|
4421
|
+
if (!file) {
|
|
4422
|
+
return {
|
|
4423
|
+
stdout: "",
|
|
4424
|
+
stderr: "Usage: calllint receipt verify <receipt.json>",
|
|
4425
|
+
exitCode: EXIT.USAGE
|
|
4426
|
+
};
|
|
4427
|
+
}
|
|
4428
|
+
let raw;
|
|
4429
|
+
try {
|
|
4430
|
+
raw = readFileSync8(resolve4(deps.cwd, file), "utf8");
|
|
4431
|
+
} catch (e) {
|
|
4432
|
+
return {
|
|
4433
|
+
stdout: "",
|
|
4434
|
+
stderr: `Could not read receipt ${file}: ${e instanceof Error ? e.message : String(e)}`,
|
|
4435
|
+
exitCode: EXIT.ERROR
|
|
4436
|
+
};
|
|
4437
|
+
}
|
|
4438
|
+
let parsed;
|
|
4439
|
+
try {
|
|
4440
|
+
parsed = JSON.parse(raw);
|
|
4441
|
+
} catch (e) {
|
|
4442
|
+
const msg = `receipt is not valid JSON: ${e instanceof Error ? e.message : String(e)}`;
|
|
4443
|
+
if (flagBool(args.flags, "json")) {
|
|
4444
|
+
return {
|
|
4445
|
+
stdout: JSON.stringify({ valid: false, errors: [msg], signed: false }, null, 2),
|
|
4446
|
+
exitCode: 1
|
|
4447
|
+
};
|
|
4448
|
+
}
|
|
4449
|
+
return { stdout: "", stderr: `CallLint receipt: invalid
|
|
4450
|
+
- ${msg}`, exitCode: 1 };
|
|
4451
|
+
}
|
|
4452
|
+
const result = verifyReceipt(parsed);
|
|
4453
|
+
if (flagBool(args.flags, "json")) {
|
|
4454
|
+
return {
|
|
4455
|
+
stdout: JSON.stringify(result, null, 2),
|
|
4456
|
+
exitCode: result.valid ? EXIT.OK : 1
|
|
4457
|
+
};
|
|
4458
|
+
}
|
|
4459
|
+
if (!result.valid) {
|
|
4460
|
+
const lines = ["CallLint receipt: invalid", ...result.errors.map((e) => ` - ${e}`)];
|
|
4461
|
+
return { stdout: "", stderr: lines.join("\n"), exitCode: 1 };
|
|
4462
|
+
}
|
|
4463
|
+
const r = parsed;
|
|
4464
|
+
const signature = r.signature ? "present (shape-only, not cryptographically verified)" : "unsigned local receipt";
|
|
4465
|
+
const stdout = [
|
|
4466
|
+
"CallLint receipt: valid",
|
|
4467
|
+
`receipt_id: ${r.receipt_id}`,
|
|
4468
|
+
`schema: ${r.schema_version}`,
|
|
4469
|
+
`verdict: ${r.verdict}`,
|
|
4470
|
+
`signature: ${signature}`
|
|
4471
|
+
].join("\n");
|
|
4472
|
+
return { stdout, exitCode: EXIT.OK };
|
|
4473
|
+
}
|
|
4474
|
+
|
|
4234
4475
|
// src/run.ts
|
|
4235
4476
|
function run(argv, deps) {
|
|
4236
4477
|
const args = parseArgs(argv);
|
|
@@ -4260,7 +4501,8 @@ function run(argv, deps) {
|
|
|
4260
4501
|
generatedAt: deps.generatedAt,
|
|
4261
4502
|
writeCacheFile: deps.writeCacheFile,
|
|
4262
4503
|
online: deps.online,
|
|
4263
|
-
getChangedFilesDiff: deps.getChangedFilesDiff
|
|
4504
|
+
getChangedFilesDiff: deps.getChangedFilesDiff,
|
|
4505
|
+
toolVersion: deps.toolVersion
|
|
4264
4506
|
});
|
|
4265
4507
|
case "diagnostics":
|
|
4266
4508
|
return diagnosticsCommand(args, {
|
|
@@ -4296,6 +4538,8 @@ function run(argv, deps) {
|
|
|
4296
4538
|
});
|
|
4297
4539
|
case "explain":
|
|
4298
4540
|
return explainCommand(args, { cwd: deps.cwd });
|
|
4541
|
+
case "receipt":
|
|
4542
|
+
return receiptCommand(args, { cwd: deps.cwd });
|
|
4299
4543
|
case "gen-rule":
|
|
4300
4544
|
return genRuleCommand(args, { cwd: deps.cwd });
|
|
4301
4545
|
case "policy":
|
|
@@ -4350,15 +4594,39 @@ async function fetchNpmFacts(packageSpec, fetchJson) {
|
|
|
4350
4594
|
(k) => typeof scripts[k] === "string" && scripts[k].length > 0
|
|
4351
4595
|
);
|
|
4352
4596
|
const deprecated = typeof versionDoc.deprecated === "string" ? versionDoc.deprecated : void 0;
|
|
4597
|
+
const description = typeof versionDoc.description === "string" ? versionDoc.description : void 0;
|
|
4598
|
+
const readme = typeof doc.readme === "string" ? doc.readme : void 0;
|
|
4353
4599
|
return {
|
|
4354
4600
|
name,
|
|
4355
4601
|
versionExists: true,
|
|
4356
4602
|
installScripts,
|
|
4357
4603
|
deprecated,
|
|
4358
4604
|
latestVersion,
|
|
4359
|
-
resolvedVersion
|
|
4605
|
+
resolvedVersion,
|
|
4606
|
+
description,
|
|
4607
|
+
readme
|
|
4360
4608
|
};
|
|
4361
4609
|
}
|
|
4610
|
+
function surfacesFromNpmFacts(facts) {
|
|
4611
|
+
const surfaces = [];
|
|
4612
|
+
if (typeof facts.description === "string" && facts.description.length > 0) {
|
|
4613
|
+
surfaces.push({
|
|
4614
|
+
path: `registry:${facts.name}#description`,
|
|
4615
|
+
kind: "registry-description",
|
|
4616
|
+
text: facts.description,
|
|
4617
|
+
truncated: false
|
|
4618
|
+
});
|
|
4619
|
+
}
|
|
4620
|
+
if (typeof facts.readme === "string" && facts.readme.length > 0) {
|
|
4621
|
+
surfaces.push({
|
|
4622
|
+
path: `registry:${facts.name}#readme`,
|
|
4623
|
+
kind: "registry-readme",
|
|
4624
|
+
text: facts.readme,
|
|
4625
|
+
truncated: false
|
|
4626
|
+
});
|
|
4627
|
+
}
|
|
4628
|
+
return surfaces;
|
|
4629
|
+
}
|
|
4362
4630
|
function findingsFromNpmFacts(facts, fetchedAt) {
|
|
4363
4631
|
const findings = [];
|
|
4364
4632
|
const stamp = (f) => ({ ...f, source: "online", fetchedAt });
|
|
@@ -4416,6 +4684,9 @@ function findingsFromNpmFacts(facts, fetchedAt) {
|
|
|
4416
4684
|
fix: "Migrate to the maintained successor or a supported version."
|
|
4417
4685
|
}));
|
|
4418
4686
|
}
|
|
4687
|
+
for (const f of analyzeDocumentSurfaces(surfacesFromNpmFacts(facts))) {
|
|
4688
|
+
findings.push(stamp(f));
|
|
4689
|
+
}
|
|
4419
4690
|
return findings;
|
|
4420
4691
|
}
|
|
4421
4692
|
async function enrichNpmPackage(packageSpec, fetchJson, fetchedAt) {
|
|
@@ -4541,10 +4812,24 @@ async function breathe(argv, deps = {}) {
|
|
|
4541
4812
|
}
|
|
4542
4813
|
}
|
|
4543
4814
|
|
|
4815
|
+
// src/version.ts
|
|
4816
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
4817
|
+
import { fileURLToPath } from "node:url";
|
|
4818
|
+
import { dirname as dirname4, join as join11 } from "node:path";
|
|
4819
|
+
function resolveToolVersion() {
|
|
4820
|
+
try {
|
|
4821
|
+
const pkgPath = join11(dirname4(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
4822
|
+
const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
|
|
4823
|
+
return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : "unknown";
|
|
4824
|
+
} catch {
|
|
4825
|
+
return "unknown";
|
|
4826
|
+
}
|
|
4827
|
+
}
|
|
4828
|
+
|
|
4544
4829
|
// src/index.ts
|
|
4545
4830
|
function readStdin() {
|
|
4546
4831
|
try {
|
|
4547
|
-
return
|
|
4832
|
+
return readFileSync10(0, "utf8");
|
|
4548
4833
|
} catch {
|
|
4549
4834
|
return "";
|
|
4550
4835
|
}
|
|
@@ -4593,6 +4878,7 @@ async function main() {
|
|
|
4593
4878
|
now,
|
|
4594
4879
|
generatedAt,
|
|
4595
4880
|
online,
|
|
4881
|
+
toolVersion: resolveToolVersion(),
|
|
4596
4882
|
getChangedFilesDiff: () => gitChangedFiles(process.cwd())
|
|
4597
4883
|
});
|
|
4598
4884
|
if (result.stdout) process.stdout.write(result.stdout + "\n");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "calllint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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",
|