calllint 1.4.0 → 1.5.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.
Files changed (2) hide show
  1. package/dist/index.js +775 -25
  2. package/package.json +2 -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 readFileSync20 } from "node:fs";
4
+ import { readFileSync as readFileSync21 } from "node:fs";
5
5
  import { execFileSync } from "node:child_process";
6
6
 
7
7
  // src/args.ts
@@ -74,6 +74,10 @@ COMMANDS
74
74
  diagnostics [target] Emit editor/agent-host diagnostics JSON (calllint.diagnostics.v0)
75
75
  baseline [target] Record the approved risk surface as a baseline
76
76
  approve Record the repo-wide capability surface as approved state (L4)
77
+ guard Continuous Guard: re-decide on authority change; silent when unchanged
78
+ guard install --host <h> Install a guard hook (git pre-commit, github workflow)
79
+ guard status Show baseline / disable / installed-hook state
80
+ guard disable Turn Continuous Guard off (.calllint/guard.json)
77
81
  receipt verify <f> Validate a calllint.receipt.v0 (structure + signature if present)
78
82
  receipt sign <f> Sign a receipt with a local key (--key; development/testing)
79
83
  receipt keygen Generate a local ed25519 keypair (--out; development/testing)
@@ -122,6 +126,13 @@ VERIFY OPTIONS
122
126
  --ci Exit 40 if the surface drifted (from baseline or approved state)
123
127
  --json Emit the drift report JSON
124
128
 
129
+ GUARD OPTIONS
130
+ --host <h> guard install target: git (pre-commit) | github (workflow)
131
+ --approved [file] Approved baseline to diff against (default: .calllint/approved.json)
132
+ --json Emit the guard assessment / status JSON
133
+ (exit) silent/note=0, REVIEW=10, UNKNOWN=20, BLOCK=30; guard self-failure is non-zero
134
+ (env) CALLLINT_GUARD=0 disables the guard
135
+
125
136
  EXAMPLES
126
137
  calllint inventory # List discovered agent configs
127
138
  calllint scan --auto # Discover and scan all agents
@@ -140,6 +151,8 @@ EXAMPLES
140
151
  calllint inbox inspect gmail-reply.normalized.json
141
152
  calllint verify ./mcp.json --ci
142
153
  calllint approve && calllint verify --approved --ci
154
+ calllint approve && calllint guard install --host git # re-decide on every commit
155
+ calllint guard --json # authority-change assessment (silent when unchanged)
143
156
  calllint explain filesystem
144
157
  `;
145
158
  function helpCommand() {
@@ -407,7 +420,10 @@ var REASON_CODES = [
407
420
  "PROMPT_METADATA_INSTRUCTION",
408
421
  "OAUTH_SCOPE_UNKNOWN_OR_EXPANDED",
409
422
  "TOOL_DESCRIPTOR_CHANGED",
410
- "LONG_RUNNING_GATEWAY_RUNTIME"
423
+ "LONG_RUNNING_GATEWAY_RUNTIME",
424
+ // #13 — appended last (ADR 0044) so the frozen order 0–11 is unchanged. Backed by
425
+ // the flow object (calllint.flow.v0), not a static detector; see REASON_CODE_META.
426
+ "TOXIC_FLOW_COMPOSITION"
411
427
  ];
412
428
  var REASON_CODE_META = {
413
429
  UNPINNED_PACKAGE: {
@@ -471,6 +487,16 @@ var REASON_CODE_META = {
471
487
  backedBy: ["runtime.gateway"],
472
488
  status: "wired",
473
489
  label: "Long-running gateway runtime"
490
+ },
491
+ TOXIC_FLOW_COMPOSITION: {
492
+ // Backed by the flow object (calllint.flow.v0, built by the flow-analyzer package),
493
+ // NOT a static detector Finding. The synthetic backing id keeps every wired code
494
+ // naming its backing without inventing a new status value. No detector emits this id,
495
+ // so findingsToReasonCodes never fabricates the code — it is produced only by the
496
+ // explicit flow-fold step. See ADR 0044 / 0040.
497
+ backedBy: ["flow:toxic-composition"],
498
+ status: "wired",
499
+ label: "Cross-tool toxic-flow composition"
474
500
  }
475
501
  };
476
502
  function reasonCodeForFinding(findingId) {
@@ -491,6 +517,9 @@ var VERDICT_NEXT_ACTION = {
491
517
  // ../../packages/types/src/authority.ts
492
518
  var AUTHORITY_SCHEMA_VERSION = "calllint.authority.v0";
493
519
 
520
+ // ../../packages/types/src/flow.ts
521
+ var FLOW_SCHEMA_VERSION = "calllint.flow.v0";
522
+
494
523
  // ../../packages/types/src/trustDecision.ts
495
524
  var DECISION_SCHEMA_VERSION = "calllint.decision.v0";
496
525
 
@@ -624,11 +653,13 @@ function evidenceFloor(evidence) {
624
653
  function decideOverAuthority(input) {
625
654
  const { authority, policy } = input;
626
655
  const evidence = input.evidence ?? [];
627
- const reasons = authority.capabilities.map((c) => {
656
+ const capabilityReasons = authority.capabilities.map((c) => {
628
657
  const code = reasonCodeFor(c);
629
658
  const contributes = moreSevere(baseVerdict(c), policyFloor(code, policy));
630
659
  return { code, evidenceSource: c.evidenceSource, contributes };
631
660
  });
661
+ const flowReasons = input.flowReasons ?? [];
662
+ const reasons = [...capabilityReasons, ...flowReasons];
632
663
  const unknowns = [...authority.unknowns];
633
664
  const contributions = reasons.map((r) => r.contributes);
634
665
  if (authority.subject.artifactDigest === null) {
@@ -1863,6 +1894,21 @@ function analyzeDocumentSurfaces(surfaces) {
1863
1894
  ];
1864
1895
  }
1865
1896
 
1897
+ // ../../packages/static-analyzer/src/trustSource.ts
1898
+ function classifyTrustSource(cap) {
1899
+ if (cap.action === "read" && cap.resource === "secret") return "sensitive.secret";
1900
+ if (cap.action === "execute" && cap.resource === "process" && cap.evidenceSource === "server.command") {
1901
+ return "trusted.local_project";
1902
+ }
1903
+ return "unknown";
1904
+ }
1905
+ function withTrustSource(caps) {
1906
+ return caps.map((c) => {
1907
+ const ts = classifyTrustSource(c);
1908
+ return ts === "unknown" ? { ...c } : { ...c, trustSource: ts };
1909
+ });
1910
+ }
1911
+
1866
1912
  // ../../packages/static-analyzer/src/instructionAuthority.ts
1867
1913
  var RULES = [
1868
1914
  // 1. privilege-escalation → execute × process (irreversible, must be approved)
@@ -2068,7 +2114,7 @@ function extractInstructionAuthority(surfaces) {
2068
2114
  }
2069
2115
  }
2070
2116
  }
2071
- return sortCapabilities([...seen.values()]);
2117
+ return withTrustSource(sortCapabilities([...seen.values()]));
2072
2118
  }
2073
2119
  function sortCapabilities(caps) {
2074
2120
  return caps.sort((a, b) => {
@@ -2155,7 +2201,7 @@ function deriveConfigCapabilities(server) {
2155
2201
  completeness: "complete"
2156
2202
  });
2157
2203
  }
2158
- return sortCapabilities(caps);
2204
+ return sortCapabilities(withTrustSource(caps));
2159
2205
  }
2160
2206
 
2161
2207
  // ../../packages/risk-engine/src/computeRiskClass.ts
@@ -3366,6 +3412,60 @@ function escalate(current) {
3366
3412
  return current === "BLOCK" || current === "UNKNOWN" ? current : "REVIEW";
3367
3413
  }
3368
3414
 
3415
+ // ../../packages/core/src/state/continuousGuard.ts
3416
+ function assessGuardDrift(report) {
3417
+ if (!report.drifted) {
3418
+ return {
3419
+ action: "silent",
3420
+ verdict: "SAFE",
3421
+ drifted: false,
3422
+ summary: "No authority change since the approved baseline."
3423
+ };
3424
+ }
3425
+ const changed = report.entries.filter((e) => e.status !== "unchanged");
3426
+ const n = changed.length;
3427
+ const surfaces = `${n} surface${n === 1 ? "" : "s"}`;
3428
+ switch (report.verdict) {
3429
+ case "BLOCK":
3430
+ return {
3431
+ action: "refuse",
3432
+ verdict: "BLOCK",
3433
+ drifted: true,
3434
+ summary: `Authority changed on ${surfaces}; the new surface is blocked by policy.`
3435
+ };
3436
+ case "UNKNOWN":
3437
+ return {
3438
+ action: "request-evidence",
3439
+ verdict: "UNKNOWN",
3440
+ drifted: true,
3441
+ summary: `Authority changed on ${surfaces}; the new surface could not be verified \u2014 evidence or approval required.`
3442
+ };
3443
+ case "REVIEW":
3444
+ return {
3445
+ action: "prompt",
3446
+ verdict: "REVIEW",
3447
+ drifted: true,
3448
+ summary: `Authority changed on ${surfaces}; review the new surface before proceeding.`
3449
+ };
3450
+ case "SAFE":
3451
+ return {
3452
+ action: "note",
3453
+ verdict: "SAFE",
3454
+ drifted: true,
3455
+ summary: `Authority changed on ${surfaces}; no new blockers observed.`
3456
+ };
3457
+ }
3458
+ }
3459
+ function guardFailClosed(failure) {
3460
+ return {
3461
+ action: "fail-closed",
3462
+ verdict: "UNKNOWN",
3463
+ drifted: false,
3464
+ summary: "Continuous Guard could not verify the authority surface; failing closed.",
3465
+ failure
3466
+ };
3467
+ }
3468
+
3369
3469
  // ../../packages/core/src/distribution/agentRule.ts
3370
3470
  var RELEVANT_SURFACES = [
3371
3471
  ".cursor/mcp.json",
@@ -3499,6 +3599,64 @@ ${rule()}
3499
3599
  }
3500
3600
  }
3501
3601
 
3602
+ // ../../packages/core/src/distribution/ciGate.ts
3603
+ var CI_GATE_PATHS = [
3604
+ "**/mcp.json",
3605
+ "**/.mcp.json",
3606
+ "**/settings.json",
3607
+ "**/config.toml",
3608
+ ".cursor/**",
3609
+ ".vscode/**",
3610
+ "claude_desktop_config.json",
3611
+ "CLAUDE.md",
3612
+ "AGENTS.md",
3613
+ ".calllint/approved.json"
3614
+ ];
3615
+ function pathsBlock() {
3616
+ return CI_GATE_PATHS.map((p) => ` - ${JSON.stringify(p)}`).join("\n");
3617
+ }
3618
+ var DRIFT_STEP = ` - name: CallLint drift gate
3619
+ shell: bash
3620
+ run: |
3621
+ if [ -f .calllint/approved.json ]; then
3622
+ npx -y calllint verify --approved --ci --no-emoji
3623
+ else
3624
+ echo "No .calllint/approved.json \u2014 seed it with: npx calllint approve"
3625
+ echo "Running report-only scan-all (not gating)."
3626
+ npx -y calllint scan-all --no-emoji || true
3627
+ fi`;
3628
+ var SCAN_ALL_STEP = ` - name: CallLint scan-all (report-only)
3629
+ shell: bash
3630
+ run: npx -y calllint scan-all --no-emoji || true`;
3631
+ function renderCiGate(opts = {}) {
3632
+ const mode = opts.mode ?? "drift";
3633
+ const step = mode === "scan-all" ? SCAN_ALL_STEP : DRIFT_STEP;
3634
+ const headline = mode === "scan-all" ? "CallLint agent-tool surface report (report-only; never gates)." : "CallLint agent-tool surface drift gate (fails on approved-state drift).";
3635
+ return `# ${headline}
3636
+ # Generated by \`calllint\` (generate_ci_gate_snippet). CallLint is static and
3637
+ # NEVER executes a scanned server.
3638
+ name: calllint
3639
+
3640
+ on:
3641
+ pull_request:
3642
+ paths:
3643
+ ${pathsBlock()}
3644
+
3645
+ permissions:
3646
+ contents: read
3647
+
3648
+ jobs:
3649
+ surface-gate:
3650
+ runs-on: ubuntu-latest
3651
+ steps:
3652
+ - uses: actions/checkout@v6
3653
+ - uses: actions/setup-node@v6
3654
+ with:
3655
+ node-version: 20
3656
+ ${step}
3657
+ `;
3658
+ }
3659
+
3502
3660
  // ../../packages/core/src/receipt/createReceipt.ts
3503
3661
  import { randomBytes } from "node:crypto";
3504
3662
  function newReceiptId() {
@@ -5202,8 +5360,7 @@ var WindsurfExtractor = class extends BaseAgentExtractor {
5202
5360
  return configs;
5203
5361
  }
5204
5362
  getUserConfigPath() {
5205
- const appDataDir = this.getAppDataDir();
5206
- return join10(appDataDir, "Windsurf", "mcp.json");
5363
+ return join10(this.resolveHome(), ".codeium", "mcp_config.json");
5207
5364
  }
5208
5365
  createConfig(configPath) {
5209
5366
  const exists = this.isValidConfig(configPath);
@@ -7918,6 +8075,235 @@ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as rea
7918
8075
  import { basename as basename4, join as join16, resolve as resolvePath4 } from "node:path";
7919
8076
  import { homedir, userInfo } from "node:os";
7920
8077
 
8078
+ // ../../packages/flow-analyzer/src/flowRules.ts
8079
+ var UNTRUSTED_OR_SENSITIVE = /* @__PURE__ */ new Set([
8080
+ "sensitive.secret",
8081
+ "sensitive.private_data",
8082
+ "untrusted.public_content",
8083
+ "untrusted.tool_output",
8084
+ "untrusted.peer_agent",
8085
+ "untrusted.memory"
8086
+ ]);
8087
+ var TRUSTED = /* @__PURE__ */ new Set([
8088
+ "trusted.policy",
8089
+ "trusted.user_explicit",
8090
+ "trusted.local_project",
8091
+ "trusted.signed_component"
8092
+ ]);
8093
+ function isUntrustedOrSensitive(ts) {
8094
+ return ts !== void 0 && UNTRUSTED_OR_SENSITIVE.has(ts);
8095
+ }
8096
+ function isTrusted(ts) {
8097
+ return ts !== void 0 && TRUSTED.has(ts);
8098
+ }
8099
+ function sinkKind(sink) {
8100
+ const key = `${sink.action}:${sink.resource}`;
8101
+ if (key === "send:network" || key === "connect:network") return "network";
8102
+ if (key === "send:message") return "message";
8103
+ if (key === "spend:financial") return "financial";
8104
+ return "other";
8105
+ }
8106
+ function hasExternalDestination(sink) {
8107
+ return typeof sink.destination === "string" && sink.destination.length > 0;
8108
+ }
8109
+ var FLOW_RULES = [
8110
+ // CL-FLOW-001 — the canonical toxic flow: untrusted/sensitive data → external network
8111
+ // egress with a concrete outbound destination. The motivating incident (new9): a
8112
+ // secret / injected public content leaving to an attacker-controlled host.
8113
+ {
8114
+ id: "CL-FLOW-001",
8115
+ when: (s, k) => isUntrustedOrSensitive(s.trustSource) && sinkKind(k) === "network" && hasExternalDestination(k),
8116
+ decisionHint: "BLOCK",
8117
+ riskClass: "critical"
8118
+ },
8119
+ // CL-FLOW-002 — untrusted/sensitive data → financial action (spend). Money leaving is
8120
+ // irreversible; a tainted source driving it is a blocker regardless of destination.
8121
+ {
8122
+ id: "CL-FLOW-002",
8123
+ when: (s, k) => isUntrustedOrSensitive(s.trustSource) && sinkKind(k) === "financial",
8124
+ decisionHint: "BLOCK",
8125
+ riskClass: "critical"
8126
+ },
8127
+ // CL-FLOW-003 — untrusted/sensitive data → external network egress WITHOUT a pinned
8128
+ // destination (host unknown). Still dangerous, but the sink is less determined, so it
8129
+ // warrants human review rather than an outright block.
8130
+ {
8131
+ id: "CL-FLOW-003",
8132
+ when: (s, k) => isUntrustedOrSensitive(s.trustSource) && sinkKind(k) === "network" && !hasExternalDestination(k),
8133
+ decisionHint: "REVIEW",
8134
+ riskClass: "high"
8135
+ },
8136
+ // CL-FLOW-004 — untrusted/sensitive data → outbound messaging (email/chat). Exfiltration
8137
+ // via a messaging channel; review-worthy.
8138
+ {
8139
+ id: "CL-FLOW-004",
8140
+ when: (s, k) => isUntrustedOrSensitive(s.trustSource) && sinkKind(k) === "message",
8141
+ decisionHint: "REVIEW",
8142
+ riskClass: "high"
8143
+ },
8144
+ // CL-FLOW-ALLOW-001 — an established TRUSTED source reaching an egress sink is the
8145
+ // benign counterpart (ADR 0041 §2: trusted.user_explicit → send is routine). Only fires
8146
+ // when the source is positively trusted — never on unknown (I-04).
8147
+ {
8148
+ id: "CL-FLOW-ALLOW-001",
8149
+ when: (s, k) => isTrusted(s.trustSource) && sinkKind(k) !== "other",
8150
+ decisionHint: "ALLOW",
8151
+ riskClass: "none"
8152
+ },
8153
+ // CL-FLOW-REVIEW-000 — fail-safe catch-all. Any composition not positively classified
8154
+ // above (including one whose source trust could not be established) is REVIEW, never
8155
+ // ALLOW/SAFE. This keeps the classifier total and dangerous-flow-never-SAFE.
8156
+ {
8157
+ id: "CL-FLOW-REVIEW-000",
8158
+ when: () => true,
8159
+ decisionHint: "REVIEW",
8160
+ riskClass: "medium"
8161
+ }
8162
+ ];
8163
+ function classifyFlow(source, sink) {
8164
+ for (const rule2 of FLOW_RULES) {
8165
+ if (rule2.when(source, sink)) {
8166
+ return { ruleId: rule2.id, decisionHint: rule2.decisionHint, riskClass: rule2.riskClass };
8167
+ }
8168
+ }
8169
+ return { ruleId: "CL-FLOW-REVIEW-000", decisionHint: "REVIEW", riskClass: "medium" };
8170
+ }
8171
+
8172
+ // ../../packages/flow-analyzer/src/buildFlows.ts
8173
+ var CLASSIFIED_SOURCE_TRUST = /* @__PURE__ */ new Set([
8174
+ "sensitive.secret",
8175
+ "sensitive.private_data",
8176
+ "untrusted.public_content",
8177
+ "untrusted.tool_output",
8178
+ "untrusted.peer_agent",
8179
+ "untrusted.memory",
8180
+ "trusted.policy",
8181
+ "trusted.user_explicit",
8182
+ "trusted.local_project",
8183
+ "trusted.signed_component"
8184
+ ]);
8185
+ var EGRESS_SINKS = /* @__PURE__ */ new Set([
8186
+ "send:network",
8187
+ "send:message",
8188
+ "connect:network",
8189
+ "spend:financial"
8190
+ ]);
8191
+ function isSource(c) {
8192
+ return c.action === "read" && c.trustSource !== void 0 && CLASSIFIED_SOURCE_TRUST.has(c.trustSource);
8193
+ }
8194
+ function isSink(c) {
8195
+ return EGRESS_SINKS.has(`${c.action}:${c.resource}`);
8196
+ }
8197
+ function sourceFamily(ts) {
8198
+ if (ts.startsWith("sensitive.")) return "sensitive";
8199
+ if (ts.startsWith("trusted.")) return "trusted";
8200
+ return "untrusted";
8201
+ }
8202
+ var SEVERITY = {
8203
+ none: 0,
8204
+ low: 20,
8205
+ medium: 50,
8206
+ high: 75,
8207
+ critical: 95
8208
+ };
8209
+ function cmp3(a, b) {
8210
+ return a < b ? -1 : a > b ? 1 : 0;
8211
+ }
8212
+ function capKey(t) {
8213
+ const c = t.cap;
8214
+ return [
8215
+ t.manifestDigest,
8216
+ c.trustSource ?? "",
8217
+ c.action,
8218
+ c.resource,
8219
+ c.scope ?? "",
8220
+ c.destination ?? "",
8221
+ c.evidenceSource
8222
+ ].join("|");
8223
+ }
8224
+ function sealFlow(flow) {
8225
+ return { ...flow, digest: hashJson(flow) };
8226
+ }
8227
+ function buildFlows(manifests) {
8228
+ const tagged = manifests.flatMap((m) => m.capabilities.map((cap) => ({ cap, manifestDigest: m.digest }))).sort((a, b) => cmp3(capKey(a), capKey(b)));
8229
+ const sources = tagged.filter((t) => isSource(t.cap));
8230
+ const sinks = tagged.filter((t) => isSink(t.cap));
8231
+ const byDigest = /* @__PURE__ */ new Map();
8232
+ for (const src of sources) {
8233
+ for (const snk of sinks) {
8234
+ if (src.cap === snk.cap) continue;
8235
+ const ts = src.cap.trustSource;
8236
+ const outcome = classifyFlow(src.cap, snk.cap);
8237
+ const risk = {
8238
+ class: outcome.riskClass,
8239
+ severity: SEVERITY[outcome.riskClass]
8240
+ };
8241
+ const sinkTag = `${snk.cap.action}-${snk.cap.resource}`;
8242
+ const flowId = `flow:${sourceFamily(ts)}-to-${sinkTag}`;
8243
+ const evidence = [.../* @__PURE__ */ new Set([src.cap.evidenceSource, snk.cap.evidenceSource])].sort();
8244
+ const authorityDigests = [.../* @__PURE__ */ new Set([src.manifestDigest, snk.manifestDigest])].sort();
8245
+ const flow = sealFlow({
8246
+ schema: FLOW_SCHEMA_VERSION,
8247
+ flowId,
8248
+ source: { trustSource: ts, evidence: [src.cap.evidenceSource] },
8249
+ steps: [{ action: src.cap.action, resource: src.cap.resource, scope: src.cap.scope }],
8250
+ sink: {
8251
+ action: snk.cap.action,
8252
+ resource: snk.cap.resource,
8253
+ destination: snk.cap.destination
8254
+ },
8255
+ risk,
8256
+ decisionHint: outcome.decisionHint,
8257
+ evidence,
8258
+ authorityDigests
8259
+ });
8260
+ if (!byDigest.has(flow.digest)) byDigest.set(flow.digest, flow);
8261
+ }
8262
+ }
8263
+ return [...byDigest.values()].sort(
8264
+ (a, b) => cmp3(a.flowId, b.flowId) || cmp3(a.digest, b.digest)
8265
+ );
8266
+ }
8267
+
8268
+ // ../../packages/flow-analyzer/src/foldFlows.ts
8269
+ function hintToVerdict(hint) {
8270
+ switch (hint) {
8271
+ case "BLOCK":
8272
+ return "BLOCK";
8273
+ case "REVIEW":
8274
+ return "REVIEW";
8275
+ default:
8276
+ return null;
8277
+ }
8278
+ }
8279
+ function foldFlowsIntoReasons(flows) {
8280
+ const byKey = /* @__PURE__ */ new Map();
8281
+ for (const flow of flows) {
8282
+ const contributes = hintToVerdict(flow.decisionHint);
8283
+ if (contributes === null) continue;
8284
+ const firstEvidence = flow.evidence[0];
8285
+ const evidenceSource = firstEvidence ? `${flow.flowId} (${firstEvidence})` : flow.flowId;
8286
+ const reason = {
8287
+ code: "TOXIC_FLOW_COMPOSITION",
8288
+ evidenceSource,
8289
+ contributes
8290
+ };
8291
+ const prev = byKey.get(evidenceSource);
8292
+ if (!prev || severity(contributes) > severity(prev.contributes)) {
8293
+ byKey.set(evidenceSource, reason);
8294
+ }
8295
+ }
8296
+ return [...byKey.values()].sort(
8297
+ (a, b) => cmp4(a.evidenceSource, b.evidenceSource) || severity(b.contributes) - severity(a.contributes)
8298
+ );
8299
+ }
8300
+ function severity(v) {
8301
+ return v === "BLOCK" ? 3 : v === "UNKNOWN" ? 2 : v === "REVIEW" ? 1 : 0;
8302
+ }
8303
+ function cmp4(a, b) {
8304
+ return a < b ? -1 : a > b ? 1 : 0;
8305
+ }
8306
+
7921
8307
  // ../../packages/install-planner/src/buildPlan.ts
7922
8308
  function ptr(segment) {
7923
8309
  return segment.replace(/~/g, "~0").replace(/\//g, "~1");
@@ -8517,9 +8903,91 @@ function claudeCodeServerEntry(server) {
8517
8903
  return entry;
8518
8904
  }
8519
8905
 
8906
+ // ../../packages/install-planner/src/adapters/cursor.ts
8907
+ var CURSOR_HOST_ID = "cursor";
8908
+ var CURSOR_TIER = "A";
8909
+ var cursorAdapter = {
8910
+ id: CURSOR_HOST_ID,
8911
+ tier: CURSOR_TIER,
8912
+ createPlan(ctx, upstream) {
8913
+ return buildInstallPlan({ ...ctx, host: CURSOR_HOST_ID, tier: CURSOR_TIER }, upstream);
8914
+ },
8915
+ validatePlan(plan) {
8916
+ return validatePlan(plan);
8917
+ },
8918
+ applyPlan(plan, ctx) {
8919
+ return applyPlan({
8920
+ plan,
8921
+ approvalDigest: ctx.approvalDigest,
8922
+ configPath: ctx.configPath,
8923
+ backupPath: ctx.backupPath,
8924
+ lockPath: ctx.lockPath,
8925
+ fs: ctx.fs,
8926
+ now: ctx.now
8927
+ });
8928
+ }
8929
+ };
8930
+ function cursorServerEntry(server) {
8931
+ const entry = {};
8932
+ if (server.url) {
8933
+ entry["url"] = server.url;
8934
+ } else if (server.command) {
8935
+ entry["command"] = server.command;
8936
+ entry["args"] = server.args ?? [];
8937
+ }
8938
+ if (server.envKeys && server.envKeys.length > 0) {
8939
+ const env = {};
8940
+ for (const k of [...server.envKeys].sort()) env[k] = "";
8941
+ entry["env"] = env;
8942
+ }
8943
+ return entry;
8944
+ }
8945
+
8946
+ // ../../packages/install-planner/src/adapters/windsurf.ts
8947
+ var WINDSURF_HOST_ID = "windsurf";
8948
+ var WINDSURF_TIER = "A";
8949
+ var windsurfAdapter = {
8950
+ id: WINDSURF_HOST_ID,
8951
+ tier: WINDSURF_TIER,
8952
+ createPlan(ctx, upstream) {
8953
+ return buildInstallPlan({ ...ctx, host: WINDSURF_HOST_ID, tier: WINDSURF_TIER }, upstream);
8954
+ },
8955
+ validatePlan(plan) {
8956
+ return validatePlan(plan);
8957
+ },
8958
+ applyPlan(plan, ctx) {
8959
+ return applyPlan({
8960
+ plan,
8961
+ approvalDigest: ctx.approvalDigest,
8962
+ configPath: ctx.configPath,
8963
+ backupPath: ctx.backupPath,
8964
+ lockPath: ctx.lockPath,
8965
+ fs: ctx.fs,
8966
+ now: ctx.now
8967
+ });
8968
+ }
8969
+ };
8970
+ function windsurfServerEntry(server) {
8971
+ const entry = {};
8972
+ if (server.url) {
8973
+ entry["serverUrl"] = server.url;
8974
+ } else if (server.command) {
8975
+ entry["command"] = server.command;
8976
+ entry["args"] = server.args ?? [];
8977
+ }
8978
+ if (server.envKeys && server.envKeys.length > 0) {
8979
+ const env = {};
8980
+ for (const k of [...server.envKeys].sort()) env[k] = "";
8981
+ entry["env"] = env;
8982
+ }
8983
+ return entry;
8984
+ }
8985
+
8520
8986
  // ../../packages/install-planner/src/index.ts
8521
8987
  var HOST_ADAPTERS = {
8522
- [claudeCodeAdapter.id]: claudeCodeAdapter
8988
+ [claudeCodeAdapter.id]: claudeCodeAdapter,
8989
+ [cursorAdapter.id]: cursorAdapter,
8990
+ [windsurfAdapter.id]: windsurfAdapter
8523
8991
  };
8524
8992
  function getHostAdapter(host) {
8525
8993
  return HOST_ADAPTERS[host] ?? null;
@@ -8694,8 +9162,10 @@ function buildAuthorityForTarget(input, artifactDigest) {
8694
9162
  }
8695
9163
  return buildAuthorityManifest({ artifactDigest, servers, surfaces });
8696
9164
  }
8697
- function defaultHostConfigPath(host) {
9165
+ function defaultHostConfigPath(host, cwd) {
8698
9166
  if (host === CLAUDE_CODE_HOST_ID) return join16(homedir(), ".claude.json");
9167
+ if (host === CURSOR_HOST_ID) return join16(cwd, ".cursor", "mcp.json");
9168
+ if (host === WINDSURF_HOST_ID) return join16(homedir(), ".codeium", "mcp_config.json");
8699
9169
  return null;
8700
9170
  }
8701
9171
  function plannedServersFor(input, host) {
@@ -8706,19 +9176,25 @@ function plannedServersFor(input, host) {
8706
9176
  } catch {
8707
9177
  return [];
8708
9178
  }
9179
+ const entryByHost = {
9180
+ [CLAUDE_CODE_HOST_ID]: claudeCodeServerEntry,
9181
+ [CURSOR_HOST_ID]: cursorServerEntry,
9182
+ [WINDSURF_HOST_ID]: windsurfServerEntry
9183
+ };
9184
+ const entryFor = entryByHost[host] ?? claudeCodeServerEntry;
8709
9185
  return servers.map((s) => ({
8710
9186
  name: s.name,
8711
- entry: claudeCodeServerEntry({ command: s.command, args: s.args, url: s.url, envKeys: s.envKeys })
9187
+ entry: entryFor({ command: s.command, args: s.args, url: s.url, envKeys: s.envKeys })
8712
9188
  }));
8713
9189
  }
8714
9190
  function buildPlanForHost(host, input, artifactDigest, authority, decision, deps, configPathOverride) {
8715
9191
  const adapter = getHostAdapter(host);
8716
9192
  if (!adapter) {
8717
- return { error: `Unknown host "${host}". Known hosts: ${CLAUDE_CODE_HOST_ID}`, exitCode: EXIT.USAGE };
9193
+ return { error: `Unknown host "${host}". Known hosts: ${knownHostList()}`, exitCode: EXIT.USAGE };
8718
9194
  }
8719
9195
  const servers = plannedServersFor(input, host);
8720
9196
  if (servers.length === 0) return null;
8721
- const configPath = configPathOverride ? resolvePath4(deps.cwd, configPathOverride) : defaultHostConfigPath(host);
9197
+ const configPath = configPathOverride ? resolvePath4(deps.cwd, configPathOverride) : defaultHostConfigPath(host, deps.cwd);
8722
9198
  if (!configPath) {
8723
9199
  return { error: `No default config path known for host "${host}"; pass --host-config <path>`, exitCode: EXIT.USAGE };
8724
9200
  }
@@ -8812,7 +9288,9 @@ function trustPrepare(args, deps) {
8812
9288
  const authority = buildAuthorityForTarget(input, artifact.digest ?? null);
8813
9289
  const policyPath = flagStr(args.flags, "policy");
8814
9290
  const policy = loadPolicyOrDefault(policyPath ? resolvePath4(deps.cwd, policyPath) : void 0);
8815
- const decision = artifact.resolution === "resolved" ? decideOverAuthority({ authority, evidence, policy }) : void 0;
9291
+ const flows = buildFlows([authority]);
9292
+ const flowReasons = foldFlowsIntoReasons(flows);
9293
+ const decision = artifact.resolution === "resolved" ? decideOverAuthority({ authority, evidence, policy, flowReasons }) : void 0;
8816
9294
  let plan;
8817
9295
  const hostFlag = flagStr(args.flags, "host");
8818
9296
  if (hostFlag && decision && decision.verdict !== "UNKNOWN") {
@@ -8839,10 +9317,18 @@ function trustPrepare(args, deps) {
8839
9317
  plan written: ${file}
8840
9318
  `;
8841
9319
  }
9320
+ const showFlows = flagBool(args.flags, "flows");
8842
9321
  if (flagBool(args.flags, "json")) {
8843
- return { stdout: JSON.stringify(preparation, null, 2), stderr: "", exitCode };
9322
+ const payload = showFlows ? { preparation, flows } : preparation;
9323
+ return { stdout: JSON.stringify(payload, null, 2), stderr: "", exitCode };
8844
9324
  }
8845
- return { stdout: renderPreparation(preparation) + planNote, stderr: "", exitCode };
9325
+ const flowNote = showFlows ? renderFlows(flows) : "";
9326
+ const conversion = renderConversionPrompt(preparation);
9327
+ return {
9328
+ stdout: renderPreparation(preparation) + flowNote + planNote + conversion,
9329
+ stderr: "",
9330
+ exitCode
9331
+ };
8846
9332
  }
8847
9333
  function loadPreparation(args, deps) {
8848
9334
  const file = args.positionals[1];
@@ -8911,7 +9397,7 @@ function trustApply(args, deps) {
8911
9397
  return { stdout: "", stderr: `Not a calllint.install-plan.v1 document: ${planFile}`, exitCode: EXIT.ERROR };
8912
9398
  }
8913
9399
  const adapter = getHostAdapter(plan.host);
8914
- if (!adapter) return usageErr(`Unknown host "${plan.host}". Known hosts: ${CLAUDE_CODE_HOST_ID}`);
9400
+ if (!adapter) return usageErr(`Unknown host "${plan.host}". Known hosts: ${knownHostList()}`);
8915
9401
  if (!adapter.applyPlan) {
8916
9402
  return usageErr(`Host "${plan.host}" is tier ${adapter.tier} \u2014 plan-only; copy the patch or use a Tier-A host to apply.`);
8917
9403
  }
@@ -9137,6 +9623,46 @@ This is the READ-ONLY half of the Trust Gateway. It touched no live config
9137
9623
  `;
9138
9624
  return out;
9139
9625
  }
9626
+ function renderConversionPrompt(p) {
9627
+ const d = p.decision;
9628
+ if (!d || d.verdict === "BLOCK" || d.verdict === "UNKNOWN") return "";
9629
+ return `
9630
+ Next step (nothing is persisted unless you run one of these):
9631
+ \u2022 Record this surface as approved: calllint approve
9632
+ \u2022 Re-decide on every authority change: calllint guard install --host git
9633
+ \u2022 Gate pull requests in CI: calllint guard install --host github
9634
+ \u2022 Teach your agent the safety rule: calllint gen-rule --host claude --write
9635
+ `;
9636
+ }
9637
+ var FLOW_HINT_SYMBOL = {
9638
+ BLOCK: "\u26D4 BLOCK",
9639
+ REVIEW: "\u26A0 REVIEW",
9640
+ ALLOW: "\u{1F6E1} ALLOW"
9641
+ };
9642
+ function renderFlows(flows) {
9643
+ if (flows.length === 0) {
9644
+ return `
9645
+ toxic-flows: (none \u2014 no cross-capability composition detected)
9646
+ `;
9647
+ }
9648
+ let out = `
9649
+ toxic-flows: ${flows.length} path(s) [calllint.flow.v0]
9650
+ `;
9651
+ for (const f of flows) {
9652
+ const dst = f.sink.destination ? ` \u2192 ${f.sink.destination}` : "";
9653
+ out += ` ${FLOW_HINT_SYMBOL[f.decisionHint] ?? f.decisionHint} ${f.flowId} (${f.risk.class})
9654
+ `;
9655
+ out += ` source: ${f.source.trustSource} \u2192 sink: ${f.sink.action} ${f.sink.resource}${dst}
9656
+ `;
9657
+ out += ` evidence: ${f.evidence.join(", ")}
9658
+ `;
9659
+ }
9660
+ out += ` A dangerous flow is folded into the decision as a TOXIC_FLOW_COMPOSITION reason;
9661
+ `;
9662
+ out += ` it can only raise the verdict, never lower it. An ALLOW flow contributes nothing.
9663
+ `;
9664
+ return out;
9665
+ }
9140
9666
  function renderExplanation(p) {
9141
9667
  let out = `
9142
9668
  CallLint trust explain
@@ -9270,6 +9796,15 @@ The write could not be verified AND the automatic rollback failed. Your
9270
9796
  }
9271
9797
  return out;
9272
9798
  }
9799
+ function hostHelpDescriptions() {
9800
+ return Object.values(HOST_ADAPTERS).map((a) => {
9801
+ const capability = a.applyPlan ? `Tier ${a.tier}, applies` : `Tier ${a.tier}, plan-only \u2014 you apply the emitted patch`;
9802
+ return `${a.id} (${capability})`;
9803
+ }).join("; ");
9804
+ }
9805
+ function knownHostList() {
9806
+ return Object.keys(HOST_ADAPTERS).join(", ");
9807
+ }
9273
9808
  function trustHelp() {
9274
9809
  return `
9275
9810
  calllint trust \u2014 Automated Trust Gateway (prepare \u2192 approve \u2192 apply \u2192 verify)
@@ -9301,11 +9836,16 @@ OPTIONS (prepare)
9301
9836
  --format json|sarif Force the evidence format (default: auto-detect)
9302
9837
  --provider <name> Force the evidence provider adapter (default: auto-detect)
9303
9838
  --policy <file> Use a policy file for the decision (default: built-in defaults)
9304
- --host <id> Build an install plan for a host (G5: claude-code). Reads
9305
- the host config READ-ONLY; never applies. Plan is emitted
9306
- only for a non-blocking decision (SAFE/REVIEW).
9307
- --host-config <path> Override the host config path (default: ~/.claude.json)
9839
+ --host <id> Build an install plan for a host \u2014 ${hostHelpDescriptions()}.
9840
+ Reads the host config READ-ONLY; never applies here. Plan
9841
+ is emitted only for a non-blocking decision.
9842
+ --host-config <path> Override the host config path (default: per host \u2014
9843
+ ~/.claude.json for claude-code, .cursor/mcp.json for
9844
+ cursor, ~/.codeium/mcp_config.json for windsurf)
9308
9845
  --write-plan Persist the plan to .calllint/plans/<plan-id>.json
9846
+ --flows Show static toxic-flow paths (calllint.flow.v0) behind the
9847
+ decision's TOXIC_FLOW_COMPOSITION reasons. With --json, emits
9848
+ { preparation, flows }. A dangerous flow only raises the verdict.
9309
9849
  --no-llm Default posture: no LLM in the verdict path (accepted, no-op)
9310
9850
  --json Emit the raw calllint.trust-preparation.v0 document
9311
9851
 
@@ -9350,6 +9890,209 @@ SEE ALSO
9350
9890
  `;
9351
9891
  }
9352
9892
 
9893
+ // src/commands/guard.ts
9894
+ import { existsSync as existsSync12, readFileSync as readFileSync19, writeFileSync as writeFileSync11, mkdirSync as mkdirSync6 } from "node:fs";
9895
+ import { dirname as dirname6, join as join17, resolve as resolve10 } from "node:path";
9896
+ var GUARD_HOSTS = ["git", "github"];
9897
+ function guardConfigPath(cwd) {
9898
+ return join17(cwd, ".calllint", "guard.json");
9899
+ }
9900
+ function readGuardConfig(cwd) {
9901
+ const path = guardConfigPath(cwd);
9902
+ if (!existsSync12(path)) return void 0;
9903
+ try {
9904
+ return JSON.parse(readFileSync19(path, "utf8"));
9905
+ } catch {
9906
+ return void 0;
9907
+ }
9908
+ }
9909
+ function isDisabled(cwd, env) {
9910
+ if (env["CALLLINT_GUARD"] === "0") return true;
9911
+ return readGuardConfig(cwd)?.enabled === false;
9912
+ }
9913
+ function guardCommand(args, deps) {
9914
+ const sub = args.positionals[0];
9915
+ const env = deps.env ?? process.env;
9916
+ switch (sub) {
9917
+ case "install":
9918
+ return guardInstall(args, deps);
9919
+ case "status":
9920
+ return guardStatus(args, deps, env);
9921
+ case "disable":
9922
+ return guardSetEnabled(deps, false);
9923
+ case "enable":
9924
+ return guardSetEnabled(deps, true);
9925
+ case void 0:
9926
+ return guardRun(args, deps, env);
9927
+ default:
9928
+ return {
9929
+ stdout: "",
9930
+ stderr: `Unknown guard subcommand: ${sub}
9931
+ Run \`calllint guard\` (assess) or \`calllint guard install|status|disable|enable\`.`,
9932
+ exitCode: EXIT.USAGE
9933
+ };
9934
+ }
9935
+ }
9936
+ function exitForAction(a) {
9937
+ switch (a.action) {
9938
+ case "silent":
9939
+ case "note":
9940
+ return EXIT.OK;
9941
+ case "prompt":
9942
+ return EXIT.REVIEW;
9943
+ case "request-evidence":
9944
+ return EXIT.UNKNOWN;
9945
+ case "refuse":
9946
+ return EXIT.BLOCK;
9947
+ case "fail-closed":
9948
+ return EXIT.ERROR;
9949
+ }
9950
+ }
9951
+ function guardRun(args, deps, env) {
9952
+ const json = flagBool(args.flags, "json");
9953
+ if (isDisabled(deps.cwd, env)) {
9954
+ const note = "Continuous Guard is disabled (CALLLINT_GUARD=0 or .calllint/guard.json).";
9955
+ return {
9956
+ stdout: json ? JSON.stringify({ enabled: false, note }) : note,
9957
+ exitCode: EXIT.OK
9958
+ };
9959
+ }
9960
+ const approvedPath = flagStr(args.flags, "approved") ?? defaultApprovedPath(deps.cwd);
9961
+ const approved = readApproved(approvedPath);
9962
+ if (!approved) {
9963
+ return {
9964
+ stdout: "",
9965
+ stderr: `No approved baseline at ${approvedPath}. Run \`calllint approve\` first.`,
9966
+ exitCode: EXIT.USAGE
9967
+ };
9968
+ }
9969
+ let assessment;
9970
+ try {
9971
+ const current = decideRepoSurfaces(deps.cwd, { now: deps.now, generatedAt: deps.generatedAt });
9972
+ const drift = verifyApproved(current, approved, deps.generatedAt);
9973
+ assessment = assessGuardDrift(drift);
9974
+ } catch (err2) {
9975
+ assessment = guardFailClosed(err2 instanceof Error ? err2.message : String(err2));
9976
+ }
9977
+ const exitCode = exitForAction(assessment);
9978
+ if (json) {
9979
+ return { stdout: JSON.stringify(assessment), exitCode };
9980
+ }
9981
+ if (assessment.action === "silent") {
9982
+ return { stdout: "", exitCode: EXIT.OK };
9983
+ }
9984
+ const prefix = assessment.action === "fail-closed" ? "GUARD FAILED CLOSED" : assessment.action === "refuse" ? "BLOCK" : assessment.action === "request-evidence" ? "UNKNOWN" : assessment.action === "prompt" ? "REVIEW" : "NOTE";
9985
+ const failLine = assessment.failure ? `
9986
+ reason: ${assessment.failure}` : "";
9987
+ return { stdout: `${prefix}: ${assessment.summary}${failLine}`, exitCode };
9988
+ }
9989
+ function isGuardHost(v) {
9990
+ return v !== void 0 && GUARD_HOSTS.includes(v);
9991
+ }
9992
+ var GIT_HOOK = `#!/usr/bin/env bash
9993
+ # CallLint Continuous Guard (ADR 0045). Re-decides the agent-tool authority
9994
+ # surface on commit; silent when nothing changed. Generated by \`calllint guard install\`.
9995
+ # CallLint is static and NEVER executes a scanned server.
9996
+ npx -y calllint guard --no-emoji
9997
+ `;
9998
+ function guardArtifact(host) {
9999
+ if (host === "git") {
10000
+ return { path: join17(".git", "hooks", "pre-commit"), content: GIT_HOOK, label: "git pre-commit hook" };
10001
+ }
10002
+ return {
10003
+ path: join17(".github", "workflows", "calllint.yml"),
10004
+ content: renderCiGate({ mode: "drift" }),
10005
+ label: "GitHub Actions drift-gate workflow"
10006
+ };
10007
+ }
10008
+ function guardInstall(args, deps) {
10009
+ const host = flagStr(args.flags, "host");
10010
+ if (!host) {
10011
+ const list = GUARD_HOSTS.map((h2) => ` ${h2.padEnd(8)} \u2192 ${guardArtifact(h2).label}`).join("\n");
10012
+ return {
10013
+ stdout: `Usage: calllint guard install --host <host>
10014
+
10015
+ Hosts:
10016
+ ${list}`,
10017
+ exitCode: EXIT.OK
10018
+ };
10019
+ }
10020
+ if (!isGuardHost(host)) {
10021
+ return {
10022
+ stdout: "",
10023
+ stderr: `Unknown guard host: ${host}
10024
+ Run \`calllint guard install\` to list hosts.`,
10025
+ exitCode: EXIT.USAGE
10026
+ };
10027
+ }
10028
+ const art = guardArtifact(host);
10029
+ const rel = flagStr(args.flags, "out") ?? art.path;
10030
+ if (deps.writeFile === false) {
10031
+ return { stdout: `Would write ${rel} (${art.label})`, exitCode: EXIT.OK };
10032
+ }
10033
+ const abs = resolve10(deps.cwd, rel);
10034
+ try {
10035
+ mkdirSync6(dirname6(abs), { recursive: true });
10036
+ writeFileSync11(abs, art.content, "utf8");
10037
+ } catch (e) {
10038
+ return {
10039
+ stdout: "",
10040
+ stderr: `Failed to write ${rel}: ${e instanceof Error ? e.message : String(e)}`,
10041
+ exitCode: EXIT.ERROR
10042
+ };
10043
+ }
10044
+ const note = host === "git" ? `
10045
+ Make it executable: chmod +x ${rel}` : "";
10046
+ return { stdout: `Wrote ${rel} (${art.label})${note}`, exitCode: EXIT.OK };
10047
+ }
10048
+ function guardStatus(args, deps, env) {
10049
+ const approvedPath = flagStr(args.flags, "approved") ?? defaultApprovedPath(deps.cwd);
10050
+ const hasBaseline = existsSync12(approvedPath);
10051
+ const disabled = isDisabled(deps.cwd, env);
10052
+ const gitHook = existsSync12(join17(deps.cwd, ".git", "hooks", "pre-commit"));
10053
+ const ghWorkflow = existsSync12(join17(deps.cwd, ".github", "workflows", "calllint.yml"));
10054
+ const status = {
10055
+ enabled: !disabled,
10056
+ disabledBy: disabled ? env["CALLLINT_GUARD"] === "0" ? "env:CALLLINT_GUARD=0" : "flag:.calllint/guard.json" : null,
10057
+ approvedBaseline: hasBaseline ? approvedPath : null,
10058
+ installedHooks: {
10059
+ "git:pre-commit": gitHook,
10060
+ "github:workflow": ghWorkflow
10061
+ }
10062
+ };
10063
+ if (flagBool(args.flags, "json")) {
10064
+ return { stdout: JSON.stringify(status), exitCode: EXIT.OK };
10065
+ }
10066
+ const lines = [
10067
+ `Continuous Guard: ${status.enabled ? "enabled" : `disabled (${status.disabledBy})`}`,
10068
+ `Approved baseline: ${hasBaseline ? approvedPath : "none \u2014 run `calllint approve`"}`,
10069
+ `git pre-commit hook: ${gitHook ? "installed" : "not installed"}`,
10070
+ `GitHub workflow: ${ghWorkflow ? "installed" : "not installed"}`
10071
+ ];
10072
+ return { stdout: lines.join("\n"), exitCode: EXIT.OK };
10073
+ }
10074
+ function guardSetEnabled(deps, enabled) {
10075
+ const cfg = { schemaVersion: "calllint.guard-config.v0", enabled };
10076
+ if (deps.writeFile === false) {
10077
+ return { stdout: enabled ? "Would enable Continuous Guard" : "Would disable Continuous Guard", exitCode: EXIT.OK };
10078
+ }
10079
+ const path = guardConfigPath(deps.cwd);
10080
+ try {
10081
+ mkdirSync6(dirname6(path), { recursive: true });
10082
+ writeFileSync11(path, JSON.stringify(cfg, null, 2), "utf8");
10083
+ } catch (e) {
10084
+ return {
10085
+ stdout: "",
10086
+ stderr: `Failed to write ${path}: ${e instanceof Error ? e.message : String(e)}`,
10087
+ exitCode: EXIT.ERROR
10088
+ };
10089
+ }
10090
+ return {
10091
+ stdout: enabled ? "Continuous Guard enabled (.calllint/guard.json)." : "Continuous Guard disabled (.calllint/guard.json). Re-enable with `calllint guard enable`.",
10092
+ exitCode: EXIT.OK
10093
+ };
10094
+ }
10095
+
9353
10096
  // src/run.ts
9354
10097
  function run(argv, deps) {
9355
10098
  const args = parseArgs(argv);
@@ -9428,6 +10171,13 @@ function run(argv, deps) {
9428
10171
  return evidenceCommand(args, { cwd: deps.cwd });
9429
10172
  case "trust":
9430
10173
  return trustCommand(args, { cwd: deps.cwd, generatedAt: deps.generatedAt, toolVersion: deps.toolVersion });
10174
+ case "guard":
10175
+ return guardCommand(args, {
10176
+ cwd: deps.cwd,
10177
+ now: deps.now,
10178
+ generatedAt: deps.generatedAt,
10179
+ writeFile: deps.writeCacheFile
10180
+ });
9431
10181
  case "gen-rule":
9432
10182
  return genRuleCommand(args, { cwd: deps.cwd });
9433
10183
  case "policy":
@@ -9701,13 +10451,13 @@ async function breathe(argv, deps = {}) {
9701
10451
  }
9702
10452
 
9703
10453
  // src/version.ts
9704
- import { readFileSync as readFileSync19 } from "node:fs";
10454
+ import { readFileSync as readFileSync20 } from "node:fs";
9705
10455
  import { fileURLToPath } from "node:url";
9706
- import { dirname as dirname6, join as join17 } from "node:path";
10456
+ import { dirname as dirname7, join as join18 } from "node:path";
9707
10457
  function resolveToolVersion() {
9708
10458
  try {
9709
- const pkgPath = join17(dirname6(fileURLToPath(import.meta.url)), "..", "package.json");
9710
- const pkg = JSON.parse(readFileSync19(pkgPath, "utf8"));
10459
+ const pkgPath = join18(dirname7(fileURLToPath(import.meta.url)), "..", "package.json");
10460
+ const pkg = JSON.parse(readFileSync20(pkgPath, "utf8"));
9711
10461
  return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : "unknown";
9712
10462
  } catch {
9713
10463
  return "unknown";
@@ -9717,7 +10467,7 @@ function resolveToolVersion() {
9717
10467
  // src/index.ts
9718
10468
  function readStdin() {
9719
10469
  try {
9720
- return readFileSync20(0, "utf8");
10470
+ return readFileSync21(0, "utf8");
9721
10471
  } catch {
9722
10472
  return "";
9723
10473
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "calllint",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
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",
@@ -55,6 +55,7 @@
55
55
  "@calllint/evidence": "workspace:*",
56
56
  "@calllint/fingerprint": "workspace:*",
57
57
  "@calllint/fixtures": "workspace:*",
58
+ "@calllint/flow-analyzer": "workspace:*",
58
59
  "@calllint/install-planner": "workspace:*",
59
60
  "@calllint/online": "workspace:*",
60
61
  "@calllint/policy": "workspace:*",