@wrongstack/plugins 0.307.0 → 0.308.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.
@@ -18,9 +18,382 @@ function resolveExecInvocation(command, args = []) {
18
18
  // src/runtime/index.ts
19
19
  var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
20
20
 
21
+ // src/dep-guard/index.ts
22
+ var state = {
23
+ invocations: 0,
24
+ installsSeen: 0,
25
+ blocks: 0,
26
+ warns: 0,
27
+ llmConfirmCount: 0,
28
+ llmConfirmErrors: 0,
29
+ lastBlock: null,
30
+ hookUnregister: null
31
+ };
32
+ var DEFAULTS = {
33
+ enabled: true,
34
+ mode: "block",
35
+ deny: [],
36
+ allow: [],
37
+ warnOnUnpinned: false,
38
+ typosquatCheck: true,
39
+ confirmTyposquatsWithLlm: false
40
+ };
41
+ function readConfig(raw) {
42
+ if (!raw || typeof raw !== "object") return { ...DEFAULTS };
43
+ const r = raw;
44
+ const strings = (v) => Array.isArray(v) ? v.filter((s) => typeof s === "string" && s.length > 0) : [];
45
+ return {
46
+ enabled: r["enabled"] !== false,
47
+ mode: r["mode"] === "warn" ? "warn" : "block",
48
+ deny: strings(r["deny"]),
49
+ allow: strings(r["allow"]),
50
+ warnOnUnpinned: r["warnOnUnpinned"] === true,
51
+ typosquatCheck: r["typosquatCheck"] !== false,
52
+ confirmTyposquatsWithLlm: r["confirmTyposquatsWithLlm"] === true
53
+ };
54
+ }
55
+ var INSTALL_RE = /(?:^|[;&|]\s*)(npm|pnpm|yarn|bun)\s+(?:install|i|add)\s+([^;&|]+)|(?:^|[;&|]\s*)(pip3?|uv)\s+(?:pip\s+)?install\s+([^;&|]+)|(?:^|[;&|]\s*)(cargo)\s+add\s+([^;&|]+)/gi;
56
+ function parseInstallCommands(command) {
57
+ const out = [];
58
+ INSTALL_RE.lastIndex = 0;
59
+ let m = INSTALL_RE.exec(command);
60
+ while (m !== null) {
61
+ const manager = (m[1] ?? m[3] ?? m[5] ?? "").toLowerCase();
62
+ const argString = m[2] ?? m[4] ?? m[6] ?? "";
63
+ const packages = [];
64
+ for (const token of argString.split(/\s+/)) {
65
+ if (!token || token.startsWith("-")) continue;
66
+ if (/^(\.|\/|file:|git\+|https?:)/i.test(token) || token.endsWith(".tgz")) continue;
67
+ const cleaned = token.replace(/^['"]|['"]$/g, "");
68
+ if (!cleaned) continue;
69
+ let name = cleaned;
70
+ let version = null;
71
+ const pipMatch = /^([A-Za-z0-9_.-]+(?:\[[^\]]+\])?)\s*(==|>=|<=|~=|!=|>|<)\s*(.+)$/.exec(cleaned);
72
+ if (pipMatch?.[1]) {
73
+ name = pipMatch[1];
74
+ const op = pipMatch[2] ?? "";
75
+ const ver = (pipMatch[3] ?? "").trim();
76
+ version = op === "==" || op === "" ? ver || null : `${op}${ver}`;
77
+ } else {
78
+ const at = cleaned.lastIndexOf("@");
79
+ if (at > 0) {
80
+ name = cleaned.slice(0, at);
81
+ version = cleaned.slice(at + 1) || null;
82
+ }
83
+ }
84
+ if (name) packages.push({ name, version });
85
+ }
86
+ out.push({ manager, packages });
87
+ m = INSTALL_RE.exec(command);
88
+ }
89
+ return out;
90
+ }
91
+ function matchesPattern(name, pattern) {
92
+ if (pattern.endsWith("*"))
93
+ return name.toLowerCase().startsWith(pattern.slice(0, -1).toLowerCase());
94
+ return name.toLowerCase() === pattern.toLowerCase();
95
+ }
96
+ var POPULAR_PACKAGES = [
97
+ "react",
98
+ "react-dom",
99
+ "express",
100
+ "lodash",
101
+ "axios",
102
+ "typescript",
103
+ "vite",
104
+ "vitest",
105
+ "next",
106
+ "vue",
107
+ "svelte",
108
+ "zod",
109
+ "prettier",
110
+ "eslint",
111
+ "jest",
112
+ "webpack",
113
+ "commander",
114
+ "chalk",
115
+ "dotenv",
116
+ "requests",
117
+ "numpy",
118
+ "pandas",
119
+ "flask",
120
+ "django",
121
+ "serde",
122
+ "tokio"
123
+ ];
124
+ function editDistance(a, b) {
125
+ if (a === b) return 0;
126
+ if (Math.abs(a.length - b.length) > 2) return 3;
127
+ const d = Array.from({ length: a.length + 1 }, (_, i) => {
128
+ const row = new Array(b.length + 1).fill(0);
129
+ row[0] = i;
130
+ return row;
131
+ });
132
+ for (let j = 0; j <= b.length; j++) d[0][j] = j;
133
+ for (let i = 1; i <= a.length; i++) {
134
+ for (let j = 1; j <= b.length; j++) {
135
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
136
+ const row = d[i];
137
+ const prevRow = d[i - 1];
138
+ row[j] = Math.min(
139
+ prevRow[j] + 1,
140
+ row[j - 1] + 1,
141
+ prevRow[j - 1] + cost
142
+ );
143
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
144
+ row[j] = Math.min(row[j], d[i - 2][j - 2] + 1);
145
+ }
146
+ }
147
+ }
148
+ return d[a.length][b.length];
149
+ }
150
+ function typosquatOf(name) {
151
+ const lower = name.toLowerCase().replace(/^@[^/]+\//, "");
152
+ if (POPULAR_PACKAGES.includes(lower)) return null;
153
+ for (const popular of POPULAR_PACKAGES) {
154
+ if (editDistance(lower, popular) === 1) return popular;
155
+ }
156
+ return null;
157
+ }
158
+ var plugin = {
159
+ name: "dep-guard",
160
+ version: "0.1.0",
161
+ description: "Supervises dependency installs: blocks deny-listed packages, flags typosquat lookalikes, and optionally warns on unpinned versions",
162
+ apiVersion: "^0.1.10",
163
+ capabilities: { tools: true, hooks: true, llm: true },
164
+ defaultConfig: { ...DEFAULTS },
165
+ configSchema: {
166
+ type: "object",
167
+ properties: {
168
+ enabled: { type: "boolean", default: true, description: "Master switch." },
169
+ mode: {
170
+ type: "string",
171
+ enum: ["block", "warn"],
172
+ default: "block",
173
+ description: "How deny-list hits are handled."
174
+ },
175
+ deny: {
176
+ type: "array",
177
+ items: { type: "string" },
178
+ default: [],
179
+ description: 'Package names (exact) or prefix globs ("@evil/*") that must not be installed.'
180
+ },
181
+ allow: {
182
+ type: "array",
183
+ items: { type: "string" },
184
+ default: [],
185
+ description: "Exemptions that override deny."
186
+ },
187
+ warnOnUnpinned: {
188
+ type: "boolean",
189
+ default: false,
190
+ description: "Warn when a package is installed without an explicit version."
191
+ },
192
+ typosquatCheck: {
193
+ type: "boolean",
194
+ default: true,
195
+ description: "Warn when a package name is one edit away from a well-known package."
196
+ },
197
+ confirmTyposquatsWithLlm: {
198
+ type: "boolean",
199
+ default: false,
200
+ description: "Ask the risk-review Council to assess a flagged typosquat, with One Shot fallback. Appended to the warn context, never escalates to a block. Off by default."
201
+ }
202
+ }
203
+ },
204
+ setup(api) {
205
+ state.invocations = 0;
206
+ state.installsSeen = 0;
207
+ state.blocks = 0;
208
+ state.warns = 0;
209
+ state.llmConfirmCount = 0;
210
+ state.llmConfirmErrors = 0;
211
+ state.lastBlock = null;
212
+ if (state.hookUnregister) {
213
+ try {
214
+ state.hookUnregister();
215
+ } catch {
216
+ }
217
+ state.hookUnregister = null;
218
+ }
219
+ const cfg = readConfig(api.config.extensions?.["dep-guard"]);
220
+ const hook = async (input) => {
221
+ if (!cfg.enabled) return;
222
+ state.invocations += 1;
223
+ const ti = input.toolInput ?? {};
224
+ const command = typeof ti["command"] === "string" ? ti["command"] : "";
225
+ if (!command) return;
226
+ const installs = parseInstallCommands(command);
227
+ const packages = installs.flatMap((i) => i.packages);
228
+ if (packages.length === 0) return;
229
+ state.installsSeen += 1;
230
+ api.metrics.counter("installs_seen");
231
+ const notes = [];
232
+ for (const pkg of packages) {
233
+ const allowed = cfg.allow.some((p) => matchesPattern(pkg.name, p));
234
+ const denied = !allowed && cfg.deny.some((p) => matchesPattern(pkg.name, p));
235
+ if (denied) {
236
+ if (cfg.mode === "block") {
237
+ state.blocks += 1;
238
+ state.lastBlock = {
239
+ pkg: pkg.name,
240
+ command: command.slice(0, 200),
241
+ when: (/* @__PURE__ */ new Date()).toISOString()
242
+ };
243
+ api.metrics.counter("blocks");
244
+ return {
245
+ decision: "block",
246
+ reason: `dep-guard: package "${pkg.name}" is on the deny list (config.extensions["dep-guard"].deny) \u2014 install refused. Ask the user before adding this dependency, or add an \`allow\` entry.`
247
+ };
248
+ }
249
+ notes.push(
250
+ `"${pkg.name}" is DENY-LISTED \u2014 do not add it without explicit user approval.`
251
+ );
252
+ }
253
+ if (cfg.typosquatCheck) {
254
+ const lookalike = typosquatOf(pkg.name);
255
+ if (lookalike) {
256
+ const baseNote = `"${pkg.name}" is one edit away from the well-known package "${lookalike}" \u2014 possible typosquat. Verify the name before installing.`;
257
+ if (cfg.confirmTyposquatsWithLlm && api.llm) {
258
+ try {
259
+ const question = `Classify whether npm package "${pkg.name}" is likely a typo or typosquat of "${lookalike}".`;
260
+ const council = api.llm.council ? await api.llm.council(question, {
261
+ context: "The only supplied evidence is that the names have edit distance 1. Do not invent registry, download, ownership, or provenance facts.",
262
+ profile: "risk-review",
263
+ options: [
264
+ { id: "typo", label: "Likely typo or typosquat" },
265
+ { id: "real", label: "Likely distinct real package" },
266
+ { id: "uncertain", label: "Insufficient evidence" }
267
+ ]
268
+ }) : null;
269
+ const councilVerdict = council?.status === "decided" && council.optionId ? `${council.optionId.toUpperCase()}: ${council.reason ?? council.answer ?? "no rationale"}` : null;
270
+ const t = councilVerdict ? councilVerdict : (await api.llm.complete(
271
+ `${question} Reply with ONE sentence starting with "TYPO:", "REAL:", or "UNCERTAIN:".`,
272
+ {
273
+ system: "You are a supply-chain security assistant. Use only supplied evidence and preserve uncertainty.",
274
+ role: "security-reviewer",
275
+ maxTokens: 100
276
+ }
277
+ )).text.trim();
278
+ if (t) {
279
+ state.llmConfirmCount += 1;
280
+ api.metrics.counter("llm_confirm");
281
+ notes.push(`${baseNote} LLM verdict: ${t.slice(0, 300)}`);
282
+ } else {
283
+ notes.push(baseNote);
284
+ }
285
+ } catch {
286
+ state.llmConfirmErrors += 1;
287
+ notes.push(baseNote);
288
+ }
289
+ } else {
290
+ notes.push(baseNote);
291
+ }
292
+ }
293
+ }
294
+ if (cfg.warnOnUnpinned && pkg.version === null) {
295
+ notes.push(`"${pkg.name}" has no pinned version \u2014 consider "${pkg.name}@<version>".`);
296
+ }
297
+ }
298
+ if (notes.length > 0) {
299
+ state.warns += 1;
300
+ api.metrics.counter("warns");
301
+ return {
302
+ decision: "allow",
303
+ additionalContext: `dep-guard:
304
+ ${notes.map((n) => ` - ${n}`).join("\n")}`
305
+ };
306
+ }
307
+ return {
308
+ decision: "allow",
309
+ additionalContext: `dep-guard: this command adds ${packages.length} dependenc${packages.length === 1 ? "y" : "ies"}: ${packages.map((p) => p.name).join(", ")}. Confirm each is intentional.`
310
+ };
311
+ };
312
+ state.hookUnregister = api.registerHook("PreToolUse", "bash|exec", hook, {
313
+ name: "dep-guard",
314
+ stage: "validate",
315
+ failurePolicy: "closed",
316
+ policy: true
317
+ });
318
+ api.tools.register({
319
+ name: "dep_guard_status",
320
+ description: "Reports dep-guard state: deny/allow lists, mode, and counters (installs seen, blocks, warns).",
321
+ inputSchema: { type: "object", properties: {} },
322
+ permission: "auto",
323
+ category: "Diagnostics",
324
+ mutating: false,
325
+ async execute() {
326
+ return {
327
+ ok: true,
328
+ enabled: cfg.enabled,
329
+ mode: cfg.mode,
330
+ deny: cfg.deny,
331
+ allow: cfg.allow,
332
+ warnOnUnpinned: cfg.warnOnUnpinned,
333
+ typosquatCheck: cfg.typosquatCheck,
334
+ counters: {
335
+ invocations: state.invocations,
336
+ installsSeen: state.installsSeen,
337
+ llmConfirmCount: state.llmConfirmCount,
338
+ llmConfirmErrors: state.llmConfirmErrors,
339
+ blocks: state.blocks,
340
+ warns: state.warns
341
+ },
342
+ lastBlock: state.lastBlock
343
+ };
344
+ }
345
+ });
346
+ api.log.info("dep-guard plugin loaded", {
347
+ version: "0.1.0",
348
+ enabled: cfg.enabled,
349
+ mode: cfg.mode,
350
+ denyCount: cfg.deny.length
351
+ });
352
+ },
353
+ teardown(api) {
354
+ if (state.hookUnregister) {
355
+ try {
356
+ state.hookUnregister();
357
+ } catch {
358
+ }
359
+ state.hookUnregister = null;
360
+ }
361
+ const final = {
362
+ invocations: state.invocations,
363
+ installsSeen: state.installsSeen,
364
+ llmConfirmCount: state.llmConfirmCount,
365
+ llmConfirmErrors: state.llmConfirmErrors,
366
+ blocks: state.blocks,
367
+ warns: state.warns
368
+ };
369
+ state.invocations = 0;
370
+ state.installsSeen = 0;
371
+ state.blocks = 0;
372
+ state.warns = 0;
373
+ state.llmConfirmCount = 0;
374
+ state.llmConfirmErrors = 0;
375
+ state.lastBlock = null;
376
+ api.log.info("dep-guard: teardown complete", { final });
377
+ },
378
+ async health() {
379
+ return {
380
+ ok: true,
381
+ message: state.lastBlock === null ? `dep-guard: ${state.installsSeen} install command(s) seen, ${state.blocks} block(s), ${state.warns} warn(s)` : `dep-guard: last block on "${state.lastBlock.pkg}" at ${state.lastBlock.when}`,
382
+ counters: {
383
+ invocations: state.invocations,
384
+ installsSeen: state.installsSeen,
385
+ llmConfirmCount: state.llmConfirmCount,
386
+ llmConfirmErrors: state.llmConfirmErrors,
387
+ blocks: state.blocks,
388
+ warns: state.warns
389
+ }
390
+ };
391
+ }
392
+ };
393
+
21
394
  // src/dependency-vulnerability-gate/index.ts
22
395
  var API_VERSION = "^0.1.10";
23
- var state = {
396
+ var state2 = {
24
397
  invocations: 0,
25
398
  installsSeen: 0,
26
399
  auditsRun: 0,
@@ -30,7 +403,7 @@ var state = {
30
403
  lastResult: null,
31
404
  hookUnregister: null
32
405
  };
33
- var DEFAULTS = {
406
+ var DEFAULTS2 = {
34
407
  enabled: true,
35
408
  severityThreshold: "high",
36
409
  block: true,
@@ -43,15 +416,15 @@ var SEVERITY_RANK = {
43
416
  high: 3,
44
417
  critical: 4
45
418
  };
46
- function readConfig(raw) {
47
- if (!raw || typeof raw !== "object") return { ...DEFAULTS };
419
+ function readConfig2(raw) {
420
+ if (!raw || typeof raw !== "object") return { ...DEFAULTS2 };
48
421
  const r = raw;
49
- const threshold = typeof r["severityThreshold"] === "string" && ["low", "moderate", "high", "critical"].includes(r["severityThreshold"]) ? r["severityThreshold"] : DEFAULTS.severityThreshold;
422
+ const threshold = typeof r["severityThreshold"] === "string" && ["low", "moderate", "high", "critical"].includes(r["severityThreshold"]) ? r["severityThreshold"] : DEFAULTS2.severityThreshold;
50
423
  return {
51
424
  enabled: r["enabled"] !== false,
52
425
  severityThreshold: threshold,
53
426
  block: r["block"] !== false,
54
- timeoutMs: typeof r["timeoutMs"] === "number" && r["timeoutMs"] > 0 ? r["timeoutMs"] : DEFAULTS.timeoutMs
427
+ timeoutMs: typeof r["timeoutMs"] === "number" && r["timeoutMs"] > 0 ? r["timeoutMs"] : DEFAULTS2.timeoutMs
55
428
  };
56
429
  }
57
430
  function collectSeverities(out, severity) {
@@ -122,13 +495,12 @@ function exceedsThreshold(report, threshold) {
122
495
  if (thresholdRank == null || maxRank == null) return false;
123
496
  return maxRank >= thresholdRank;
124
497
  }
125
- var INSTALL_RE = /\b(?:npm\s+(?:install|i)|pnpm\s+add|yarn\s+add)\b/i;
126
498
  function isInstallCommand(input) {
127
499
  if (input.toolName === "install") return true;
128
500
  if (input.toolName !== "bash" && input.toolName !== "exec") return false;
129
501
  const ti = input.toolInput ?? {};
130
502
  const command = typeof ti["command"] === "string" ? ti["command"] : "";
131
- return INSTALL_RE.test(command);
503
+ return parseInstallCommands(command).length > 0;
132
504
  }
133
505
  var LOCKFILE_MANAGERS = [
134
506
  { file: "pnpm-lock.yaml", manager: "pnpm" },
@@ -183,13 +555,13 @@ async function runAudit(cfg) {
183
555
  if (!report) return null;
184
556
  return { manager, report, durationMs: Date.now() - start };
185
557
  }
186
- var plugin = {
558
+ var plugin2 = {
187
559
  name: "dependency-vulnerability-gate",
188
560
  version: "0.1.0",
189
561
  description: "PostToolUse hook that runs npm/pnpm audit after dependency installs and blocks or warns on vulnerabilities above a severity threshold",
190
562
  apiVersion: API_VERSION,
191
563
  capabilities: { tools: true, hooks: true },
192
- defaultConfig: { ...DEFAULTS },
564
+ defaultConfig: { ...DEFAULTS2 },
193
565
  configSchema: {
194
566
  type: "object",
195
567
  properties: {
@@ -218,35 +590,35 @@ var plugin = {
218
590
  }
219
591
  },
220
592
  setup(api) {
221
- state.invocations = 0;
222
- state.installsSeen = 0;
223
- state.auditsRun = 0;
224
- state.blocks = 0;
225
- state.warns = 0;
226
- state.errors = 0;
227
- state.lastResult = null;
228
- if (state.hookUnregister) {
593
+ state2.invocations = 0;
594
+ state2.installsSeen = 0;
595
+ state2.auditsRun = 0;
596
+ state2.blocks = 0;
597
+ state2.warns = 0;
598
+ state2.errors = 0;
599
+ state2.lastResult = null;
600
+ if (state2.hookUnregister) {
229
601
  try {
230
- state.hookUnregister();
602
+ state2.hookUnregister();
231
603
  } catch {
232
604
  }
233
- state.hookUnregister = null;
605
+ state2.hookUnregister = null;
234
606
  }
235
- const cfg = readConfig(api.config.extensions?.["dependency-vulnerability-gate"]);
607
+ const cfg = readConfig2(api.config.extensions?.["dependency-vulnerability-gate"]);
236
608
  const hook = async (input) => {
237
609
  if (!cfg.enabled) return;
238
- state.invocations += 1;
610
+ state2.invocations += 1;
239
611
  if (input.toolResult?.isError) return;
240
612
  if (!isInstallCommand(input)) return;
241
- state.installsSeen += 1;
613
+ state2.installsSeen += 1;
242
614
  api.metrics.counter("installs_seen");
243
615
  const result = await runAudit(cfg);
244
616
  if (!result) {
245
- state.errors += 1;
617
+ state2.errors += 1;
246
618
  return;
247
619
  }
248
- state.auditsRun += 1;
249
- state.lastResult = {
620
+ state2.auditsRun += 1;
621
+ state2.lastResult = {
250
622
  manager: result.manager,
251
623
  exceeded: exceedsThreshold(result.report, cfg.severityThreshold),
252
624
  maxSeverity: result.report.maxSeverity,
@@ -264,18 +636,18 @@ var plugin = {
264
636
  Counts: ${countsText || "unknown"}
265
637
  Review the audit output or adjust the dependency choice.`;
266
638
  if (cfg.block) {
267
- state.blocks += 1;
639
+ state2.blocks += 1;
268
640
  api.metrics.counter("blocks");
269
641
  return {
270
642
  decision: "block",
271
643
  reason: message
272
644
  };
273
645
  }
274
- state.warns += 1;
646
+ state2.warns += 1;
275
647
  api.metrics.counter("warns");
276
648
  return { additionalContext: message };
277
649
  };
278
- state.hookUnregister = api.registerHook("PostToolUse", "install|bash|exec", hook);
650
+ state2.hookUnregister = api.registerHook("PostToolUse", "install|bash|exec", hook);
279
651
  api.tools.register({
280
652
  name: "dependency_audit_status",
281
653
  description: "Reports dependency-vulnerability-gate state: threshold, block mode, and per-session counters.",
@@ -291,14 +663,14 @@ Review the audit output or adjust the dependency choice.`;
291
663
  block: cfg.block,
292
664
  timeoutMs: cfg.timeoutMs,
293
665
  counters: {
294
- invocations: state.invocations,
295
- installsSeen: state.installsSeen,
296
- auditsRun: state.auditsRun,
297
- blocks: state.blocks,
298
- warns: state.warns,
299
- errors: state.errors
666
+ invocations: state2.invocations,
667
+ installsSeen: state2.installsSeen,
668
+ auditsRun: state2.auditsRun,
669
+ blocks: state2.blocks,
670
+ warns: state2.warns,
671
+ errors: state2.errors
300
672
  },
301
- lastResult: state.lastResult
673
+ lastResult: state2.lastResult
302
674
  };
303
675
  }
304
676
  });
@@ -309,47 +681,48 @@ Review the audit output or adjust the dependency choice.`;
309
681
  });
310
682
  },
311
683
  teardown(api) {
312
- if (state.hookUnregister) {
684
+ if (state2.hookUnregister) {
313
685
  try {
314
- state.hookUnregister();
686
+ state2.hookUnregister();
315
687
  } catch {
316
688
  }
317
- state.hookUnregister = null;
689
+ state2.hookUnregister = null;
318
690
  }
319
691
  const final = {
320
- invocations: state.invocations,
321
- installsSeen: state.installsSeen,
322
- auditsRun: state.auditsRun,
323
- blocks: state.blocks,
324
- warns: state.warns,
325
- errors: state.errors
692
+ invocations: state2.invocations,
693
+ installsSeen: state2.installsSeen,
694
+ auditsRun: state2.auditsRun,
695
+ blocks: state2.blocks,
696
+ warns: state2.warns,
697
+ errors: state2.errors
326
698
  };
327
- state.invocations = 0;
328
- state.installsSeen = 0;
329
- state.auditsRun = 0;
330
- state.blocks = 0;
331
- state.warns = 0;
332
- state.errors = 0;
333
- state.lastResult = null;
699
+ state2.invocations = 0;
700
+ state2.installsSeen = 0;
701
+ state2.auditsRun = 0;
702
+ state2.blocks = 0;
703
+ state2.warns = 0;
704
+ state2.errors = 0;
705
+ state2.lastResult = null;
334
706
  api.log.info("dependency-vulnerability-gate: teardown complete", { final });
335
707
  },
336
708
  async health() {
337
709
  return {
338
710
  ok: true,
339
- message: state.lastResult ? `dependency-vulnerability-gate: ${state.auditsRun} audit(s), last ${state.lastResult.exceeded ? "EXCEEDED" : "clean"} (${state.lastResult.maxSeverity})` : `dependency-vulnerability-gate: ${state.installsSeen} install(s) seen, ${state.auditsRun} audit(s)`,
711
+ message: state2.lastResult ? `dependency-vulnerability-gate: ${state2.auditsRun} audit(s), last ${state2.lastResult.exceeded ? "EXCEEDED" : "clean"} (${state2.lastResult.maxSeverity})` : `dependency-vulnerability-gate: ${state2.installsSeen} install(s) seen, ${state2.auditsRun} audit(s)`,
340
712
  counters: {
341
- invocations: state.invocations,
342
- installsSeen: state.installsSeen,
343
- auditsRun: state.auditsRun,
344
- blocks: state.blocks,
345
- warns: state.warns,
346
- errors: state.errors
713
+ invocations: state2.invocations,
714
+ installsSeen: state2.installsSeen,
715
+ auditsRun: state2.auditsRun,
716
+ blocks: state2.blocks,
717
+ warns: state2.warns,
718
+ errors: state2.errors
347
719
  },
348
- lastResult: state.lastResult
720
+ lastResult: state2.lastResult
349
721
  };
350
722
  }
351
723
  };
352
- var dependency_vulnerability_gate_default = plugin;
724
+ var dependency_vulnerability_gate_default = plugin2;
353
725
  export {
354
- dependency_vulnerability_gate_default as default
726
+ dependency_vulnerability_gate_default as default,
727
+ isInstallCommand
355
728
  };
@@ -1,17 +1,32 @@
1
1
  // src/file-watcher/index.ts
2
2
  import { watch as fsWatch } from "node:fs";
3
- import { isAbsolute, join, relative, resolve } from "node:path";
4
- var API_VERSION = "^0.1.10";
3
+ import { join } from "node:path";
4
+
5
+ // src/runtime/index.ts
6
+ import { basename, extname, isAbsolute, relative, resolve } from "node:path";
7
+
8
+ // src/runtime/local-bin.ts
9
+ import { buildWin32CmdShimInvocation, resolveWin32Command } from "@wrongstack/tools/win32";
10
+
11
+ // src/runtime/index.ts
12
+ var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
13
+ function hasLeadingDash(arg) {
14
+ return arg.length > 0 && arg.startsWith("-");
15
+ }
16
+ function withinProjectPath(projectRoot, candidate) {
17
+ if (candidate.length === 0 || candidate.length > 4096) return false;
18
+ if (hasLeadingDash(candidate)) return false;
19
+ const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
20
+ const rel = relative(projectRoot, resolved);
21
+ return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
22
+ }
5
23
  function withinProject(p) {
6
- if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
7
- const root = process.cwd();
8
- const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
9
- const rel = relative(root, resolved);
10
- if (rel === "" || rel === ".") return true;
11
- if (rel.startsWith("..")) return false;
12
- if (isAbsolute(rel)) return false;
13
- return true;
24
+ const cwd = process.cwd();
25
+ return withinProjectPath(cwd, p) || relative(cwd, p) === ".";
14
26
  }
27
+
28
+ // src/file-watcher/index.ts
29
+ var API_VERSION = "^0.1.10";
15
30
  var watch_idCounter = 0;
16
31
  function nextId() {
17
32
  return `watch_${++watch_idCounter}_${Date.now().toString(36)}`;