@wrongstack/plugins 0.281.3 → 0.282.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 (78) hide show
  1. package/README.md +29 -3
  2. package/dist/accessibility-auditor.d.ts +40 -0
  3. package/dist/accessibility-auditor.js +411 -0
  4. package/dist/agent-handoff.d.ts +37 -0
  5. package/dist/agent-handoff.js +298 -0
  6. package/dist/api-compatibility-gate.d.ts +38 -0
  7. package/dist/api-compatibility-gate.js +357 -0
  8. package/dist/auto-i18n-extractor.d.ts +36 -0
  9. package/dist/auto-i18n-extractor.js +335 -0
  10. package/dist/checkpoint.js +18 -0
  11. package/dist/code-metrics.d.ts +31 -0
  12. package/dist/code-metrics.js +338 -0
  13. package/dist/commit-validator.js +67 -12
  14. package/dist/cost-tracker.js +58 -16
  15. package/dist/dead-code-detector.d.ts +34 -0
  16. package/dist/dead-code-detector.js +354 -0
  17. package/dist/dep-guard.js +47 -6
  18. package/dist/dependency-vulnerability-gate.d.ts +35 -0
  19. package/dist/dependency-vulnerability-gate.js +308 -0
  20. package/dist/diff-summary.js +97 -10
  21. package/dist/doc-sync-guard.d.ts +33 -0
  22. package/dist/doc-sync-guard.js +223 -0
  23. package/dist/duplicate-code-detector.d.ts +33 -0
  24. package/dist/duplicate-code-detector.js +384 -0
  25. package/dist/feature-flag-tracker.d.ts +38 -0
  26. package/dist/feature-flag-tracker.js +316 -0
  27. package/dist/file-watcher.js +85 -36
  28. package/dist/format-on-save.js +76 -10
  29. package/dist/import-organizer.js +73 -14
  30. package/dist/index.d.ts +27 -0
  31. package/dist/index.js +14067 -5054
  32. package/dist/interface-contract-guard.d.ts +37 -0
  33. package/dist/interface-contract-guard.js +302 -0
  34. package/dist/knowledge-graph.d.ts +45 -0
  35. package/dist/knowledge-graph.js +325 -0
  36. package/dist/license-audit-gate.d.ts +34 -0
  37. package/dist/license-audit-gate.js +260 -0
  38. package/dist/llm-cache.js +5 -0
  39. package/dist/loop-breaker.d.ts +0 -38
  40. package/dist/loop-breaker.js +209 -8
  41. package/dist/migration-planner.d.ts +30 -0
  42. package/dist/migration-planner.js +349 -0
  43. package/dist/model-router.js +5 -0
  44. package/dist/performance-regression-gate.d.ts +33 -0
  45. package/dist/performance-regression-gate.js +315 -0
  46. package/dist/plugin-stack-observer.d.ts +35 -0
  47. package/dist/plugin-stack-observer.js +125 -0
  48. package/dist/pr-drafter.d.ts +35 -0
  49. package/dist/pr-drafter.js +334 -0
  50. package/dist/prompt-firewall.js +5 -0
  51. package/dist/refactor-suggester.d.ts +38 -0
  52. package/dist/refactor-suggester.js +382 -0
  53. package/dist/release-notes-generator.d.ts +27 -0
  54. package/dist/release-notes-generator.js +209 -0
  55. package/dist/schema-evolution-guard.d.ts +42 -0
  56. package/dist/schema-evolution-guard.js +319 -0
  57. package/dist/security-hotspot-scanner.d.ts +30 -0
  58. package/dist/security-hotspot-scanner.js +402 -0
  59. package/dist/semantic-search-indexer.d.ts +38 -0
  60. package/dist/semantic-search-indexer.js +436 -0
  61. package/dist/shell-check.js +38 -3
  62. package/dist/smart-rename.d.ts +26 -0
  63. package/dist/smart-rename.js +170 -0
  64. package/dist/spec-linker.js +273 -133
  65. package/dist/test-coverage-gate.d.ts +37 -0
  66. package/dist/test-coverage-gate.js +263 -0
  67. package/dist/test-flake-detector.d.ts +27 -0
  68. package/dist/test-flake-detector.js +274 -0
  69. package/dist/test-generator.d.ts +32 -0
  70. package/dist/test-generator.js +243 -0
  71. package/dist/test-runner-gate.js +154 -22
  72. package/dist/todo-listener.d.ts +2 -2
  73. package/dist/todo-listener.js +5 -5
  74. package/dist/token-throttle.js +5 -0
  75. package/dist/type-gate.d.ts +37 -0
  76. package/dist/type-gate.js +311 -0
  77. package/package.json +112 -4
  78. package/LICENSE +0 -21
@@ -0,0 +1,354 @@
1
+ import { statSync, readFileSync, readdirSync } from 'fs';
2
+ import { resolve, isAbsolute, relative, join, extname } from 'path';
3
+
4
+ // src/dead-code-detector/index.ts
5
+ var API_VERSION = "^0.1.10";
6
+ var state = {
7
+ scanCount: 0,
8
+ hitCount: 0,
9
+ missCount: 0,
10
+ hookInvocationCount: 0,
11
+ warningCount: 0,
12
+ errorCount: 0,
13
+ hookUnregister: null
14
+ };
15
+ var DEFAULTS = {
16
+ enabled: false,
17
+ extensions: [".ts", ".tsx", ".js", ".jsx"],
18
+ defaultDepth: 3,
19
+ maxDepth: 10,
20
+ excludeDirs: ["node_modules", "dist", ".git", "coverage"]
21
+ };
22
+ function readConfig(raw) {
23
+ if (!raw || typeof raw !== "object") return { ...DEFAULTS };
24
+ const r = raw;
25
+ return {
26
+ enabled: r["enabled"] === true,
27
+ extensions: Array.isArray(r["extensions"]) ? r["extensions"].filter((x) => typeof x === "string") : DEFAULTS.extensions,
28
+ defaultDepth: typeof r["defaultDepth"] === "number" && r["defaultDepth"] >= 0 && r["defaultDepth"] <= DEFAULTS.maxDepth ? r["defaultDepth"] : DEFAULTS.defaultDepth,
29
+ maxDepth: DEFAULTS.maxDepth,
30
+ excludeDirs: Array.isArray(r["excludeDirs"]) ? r["excludeDirs"].filter((x) => typeof x === "string") : DEFAULTS.excludeDirs
31
+ };
32
+ }
33
+ function gatherFiles(root, depth, cfg) {
34
+ const files = [];
35
+ function walk(dir, remaining) {
36
+ let entries;
37
+ try {
38
+ entries = readdirSync(dir, { withFileTypes: true });
39
+ } catch {
40
+ return;
41
+ }
42
+ for (const entry of entries) {
43
+ if (entry.isDirectory()) {
44
+ if (remaining > 0 && !cfg.excludeDirs.includes(entry.name)) {
45
+ walk(join(dir, entry.name), remaining - 1);
46
+ }
47
+ } else if (entry.isFile()) {
48
+ const ext = extname(entry.name).toLowerCase();
49
+ if (cfg.extensions.includes(ext)) {
50
+ files.push(join(dir, entry.name));
51
+ }
52
+ }
53
+ }
54
+ }
55
+ walk(resolve(root), depth);
56
+ return files;
57
+ }
58
+ function stripNoise(content) {
59
+ let stripped = content.replace(/\/\/.*$/gm, " ");
60
+ stripped = stripped.replace(/\/\*[\s\S]*?\*\//g, " ");
61
+ stripped = stripped.replace(
62
+ /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`/g,
63
+ " "
64
+ );
65
+ return stripped;
66
+ }
67
+ function lineNumber(content, index) {
68
+ return content.slice(0, index).split(/\r?\n/).length;
69
+ }
70
+ function escapeRegex(s) {
71
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
72
+ }
73
+ var DECLARATION_EXPORT_RE = /export\s+(?:async\s+|abstract\s+)?(?:const|let|var|function|class|interface|type|enum)\s+([A-Za-z_$][A-Za-z0-9_$]*)/g;
74
+ var NAMED_EXPORT_RE = /export\s*\{([^}]+)\}/g;
75
+ function extractExports(content, filePath) {
76
+ const exports = [];
77
+ let match;
78
+ DECLARATION_EXPORT_RE.lastIndex = 0;
79
+ while ((match = DECLARATION_EXPORT_RE.exec(content)) !== null) {
80
+ exports.push({
81
+ identifier: match[1],
82
+ file: filePath,
83
+ line: lineNumber(content, match.index),
84
+ exportType: "declaration"
85
+ });
86
+ }
87
+ NAMED_EXPORT_RE.lastIndex = 0;
88
+ while ((match = NAMED_EXPORT_RE.exec(content)) !== null) {
89
+ const line = lineNumber(content, match.index);
90
+ const names = match[1].split(",").map((s) => s.trim()).filter(Boolean);
91
+ for (const name of names) {
92
+ const parts = name.split(/\s+as\s+/);
93
+ const exportedName = parts.length > 1 ? parts[parts.length - 1].trim() : name;
94
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(exportedName)) {
95
+ exports.push({
96
+ identifier: exportedName,
97
+ file: filePath,
98
+ line,
99
+ exportType: "named"
100
+ });
101
+ }
102
+ }
103
+ }
104
+ return exports;
105
+ }
106
+ function findUnusedSymbols(files) {
107
+ const suspicious = [];
108
+ for (const file of files) {
109
+ const fileExports = extractExports(file.content, file.path);
110
+ for (const exp of fileExports) {
111
+ const pattern = new RegExp(`\\b${escapeRegex(exp.identifier)}\\b`, "g");
112
+ let usedElsewhere = false;
113
+ for (const other of files) {
114
+ if (other.path === file.path) continue;
115
+ if (pattern.test(other.stripped)) {
116
+ usedElsewhere = true;
117
+ break;
118
+ }
119
+ }
120
+ if (!usedElsewhere) {
121
+ suspicious.push(exp);
122
+ }
123
+ }
124
+ }
125
+ return suspicious;
126
+ }
127
+ function scan(root, depth, cfg) {
128
+ const filePaths = gatherFiles(root, depth, cfg);
129
+ const files = [];
130
+ for (const p of filePaths) {
131
+ try {
132
+ const content = readFileSync(p, "utf-8");
133
+ files.push({ path: p, content, stripped: stripNoise(content) });
134
+ } catch {
135
+ }
136
+ }
137
+ return { findings: findUnusedSymbols(files), scannedFiles: files.length };
138
+ }
139
+ function withinProject(p) {
140
+ if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
141
+ const root = process.cwd();
142
+ const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
143
+ const rel = relative(root, resolved);
144
+ if (rel === "" || rel === ".") return true;
145
+ if (rel.startsWith("..")) return false;
146
+ if (isAbsolute(rel)) return false;
147
+ return true;
148
+ }
149
+ function resolveScanRoot(rawPath) {
150
+ const resolved = resolve(process.cwd(), rawPath);
151
+ try {
152
+ const stats = statSync(resolved);
153
+ if (!stats.isDirectory()) {
154
+ return resolve(resolved, "..");
155
+ }
156
+ } catch {
157
+ }
158
+ return resolved;
159
+ }
160
+ function toPosix(p) {
161
+ return p.replace(/\\/g, "/");
162
+ }
163
+ function relativePath(p) {
164
+ return toPosix(relative(process.cwd(), p));
165
+ }
166
+ var plugin = {
167
+ name: "dead-code-detector",
168
+ version: "0.1.0",
169
+ description: "Lightweight regex-based scan for exported identifiers that appear unused anywhere in the project",
170
+ apiVersion: API_VERSION,
171
+ capabilities: { tools: true, hooks: true },
172
+ defaultConfig: { ...DEFAULTS },
173
+ configSchema: {
174
+ type: "object",
175
+ properties: {
176
+ enabled: {
177
+ type: "boolean",
178
+ default: false,
179
+ description: "Master switch."
180
+ },
181
+ extensions: {
182
+ type: "array",
183
+ items: { type: "string" },
184
+ default: [".ts", ".tsx", ".js", ".jsx"],
185
+ description: "File extensions to scan."
186
+ },
187
+ defaultDepth: {
188
+ type: "number",
189
+ minimum: 0,
190
+ maximum: 10,
191
+ default: 3,
192
+ description: "Default recursion depth for scans."
193
+ },
194
+ excludeDirs: {
195
+ type: "array",
196
+ items: { type: "string" },
197
+ default: ["node_modules", "dist", ".git", "coverage"],
198
+ description: "Directory names to skip while scanning."
199
+ }
200
+ }
201
+ },
202
+ setup(api) {
203
+ state.scanCount = 0;
204
+ state.hitCount = 0;
205
+ state.missCount = 0;
206
+ state.hookInvocationCount = 0;
207
+ state.warningCount = 0;
208
+ state.errorCount = 0;
209
+ if (state.hookUnregister) {
210
+ try {
211
+ state.hookUnregister();
212
+ } catch {
213
+ }
214
+ state.hookUnregister = null;
215
+ }
216
+ const cfg = readConfig(api.config.extensions?.["dead-code-detector"]);
217
+ const hook = (input) => {
218
+ if (!cfg.enabled) return;
219
+ if (input.toolResult?.isError) return;
220
+ const inp = input.toolInput ?? {};
221
+ const sourcePath = inp["path"];
222
+ if (!sourcePath || typeof sourcePath !== "string") return;
223
+ if (!withinProject(sourcePath)) return;
224
+ const ext = sourcePath.includes(".") ? sourcePath.slice(sourcePath.lastIndexOf(".")).toLowerCase() : "";
225
+ if (!cfg.extensions.includes(ext)) return;
226
+ state.hookInvocationCount += 1;
227
+ const scanRoot = resolveScanRoot(sourcePath);
228
+ let result;
229
+ try {
230
+ result = scan(scanRoot, 1, cfg);
231
+ } catch (err) {
232
+ state.errorCount += 1;
233
+ return;
234
+ }
235
+ const changedFile = resolve(process.cwd(), sourcePath);
236
+ const relevant = result.findings.filter((f) => resolve(f.file) === changedFile);
237
+ if (relevant.length === 0) {
238
+ state.missCount += 1;
239
+ return;
240
+ }
241
+ state.hitCount += 1;
242
+ state.warningCount += relevant.length;
243
+ const list = relevant.map(
244
+ (f) => ` - ${f.identifier} (${f.exportType}) at ${relativePath(f.file)}:${f.line}`
245
+ ).join("\n");
246
+ const message = `
247
+ \u26A0\uFE0F dead-code-detector: ${relevant.length} exported symbol(s) in ${sourcePath} look unused:
248
+ ${list}
249
+ Consider removing the export if it is not part of the public API.`;
250
+ return { additionalContext: message };
251
+ };
252
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
253
+ api.tools.register({
254
+ name: "dead_code_scan",
255
+ description: "Scan TypeScript/JavaScript source files for exported symbols that appear unused anywhere in the project.",
256
+ inputSchema: {
257
+ type: "object",
258
+ properties: {
259
+ path: {
260
+ type: "string",
261
+ default: ".",
262
+ description: "Directory or file path to scan."
263
+ },
264
+ depth: {
265
+ type: "number",
266
+ default: 3,
267
+ minimum: 0,
268
+ maximum: 10,
269
+ description: "Maximum recursion depth."
270
+ }
271
+ }
272
+ },
273
+ permission: "auto",
274
+ category: "Diagnostics",
275
+ mutating: false,
276
+ async execute(input) {
277
+ if (!cfg.enabled) return { ok: false, error: "dead-code-detector is disabled" };
278
+ const rawPath = typeof input.path === "string" ? input.path : ".";
279
+ const rawDepth = typeof input.depth === "number" ? input.depth : cfg.defaultDepth;
280
+ const depth = Math.max(0, Math.min(Math.floor(rawDepth), cfg.maxDepth));
281
+ if (!withinProject(rawPath)) {
282
+ return { ok: false, error: "scan path is outside the project root" };
283
+ }
284
+ state.scanCount += 1;
285
+ const scanRoot = resolveScanRoot(rawPath);
286
+ let result;
287
+ try {
288
+ result = scan(scanRoot, depth, cfg);
289
+ } catch (err) {
290
+ state.errorCount += 1;
291
+ return { ok: false, error: String(err) };
292
+ }
293
+ return {
294
+ ok: true,
295
+ scanRoot: relativePath(scanRoot),
296
+ depth,
297
+ scannedFiles: result.scannedFiles,
298
+ findings: result.findings.map((f) => ({
299
+ identifier: f.identifier,
300
+ file: relativePath(f.file),
301
+ line: f.line,
302
+ exportType: f.exportType
303
+ }))
304
+ };
305
+ }
306
+ });
307
+ api.log.info("dead-code-detector plugin loaded", {
308
+ version: "0.1.0",
309
+ extensions: cfg.extensions,
310
+ defaultDepth: cfg.defaultDepth
311
+ });
312
+ },
313
+ teardown(api) {
314
+ if (state.hookUnregister) {
315
+ try {
316
+ state.hookUnregister();
317
+ } catch {
318
+ }
319
+ state.hookUnregister = null;
320
+ }
321
+ const final = {
322
+ scans: state.scanCount,
323
+ hits: state.hitCount,
324
+ misses: state.missCount,
325
+ hookInvocations: state.hookInvocationCount,
326
+ warnings: state.warningCount,
327
+ errors: state.errorCount
328
+ };
329
+ state.scanCount = 0;
330
+ state.hitCount = 0;
331
+ state.missCount = 0;
332
+ state.hookInvocationCount = 0;
333
+ state.warningCount = 0;
334
+ state.errorCount = 0;
335
+ api.log.info("dead-code-detector: teardown complete", { final });
336
+ },
337
+ async health() {
338
+ return {
339
+ ok: state.errorCount === 0,
340
+ message: state.errorCount ? `dead-code-detector: ${state.errorCount} error(s)` : `dead-code-detector: ${state.scanCount} scan(s), ${state.hitCount} finding(s), ${state.warningCount} warning(s)`,
341
+ counters: {
342
+ scans: state.scanCount,
343
+ hits: state.hitCount,
344
+ misses: state.missCount,
345
+ hookInvocations: state.hookInvocationCount,
346
+ warnings: state.warningCount,
347
+ errors: state.errorCount
348
+ }
349
+ };
350
+ }
351
+ };
352
+ var dead_code_detector_default = plugin;
353
+
354
+ export { dead_code_detector_default as default };
package/dist/dep-guard.js CHANGED
@@ -4,6 +4,8 @@ var state = {
4
4
  installsSeen: 0,
5
5
  blocks: 0,
6
6
  warns: 0,
7
+ llmConfirmCount: 0,
8
+ llmConfirmErrors: 0,
7
9
  lastBlock: null,
8
10
  hookUnregister: null
9
11
  };
@@ -13,7 +15,8 @@ var DEFAULTS = {
13
15
  deny: [],
14
16
  allow: [],
15
17
  warnOnUnpinned: false,
16
- typosquatCheck: true
18
+ typosquatCheck: true,
19
+ confirmTyposquatsWithLlm: false
17
20
  };
18
21
  function readConfig(raw) {
19
22
  if (!raw || typeof raw !== "object") return { ...DEFAULTS };
@@ -25,7 +28,8 @@ function readConfig(raw) {
25
28
  deny: strings(r["deny"]),
26
29
  allow: strings(r["allow"]),
27
30
  warnOnUnpinned: r["warnOnUnpinned"] === true,
28
- typosquatCheck: r["typosquatCheck"] !== false
31
+ typosquatCheck: r["typosquatCheck"] !== false,
32
+ confirmTyposquatsWithLlm: r["confirmTyposquatsWithLlm"] === true
29
33
  };
30
34
  }
31
35
  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;
@@ -167,6 +171,11 @@ var plugin = {
167
171
  type: "boolean",
168
172
  default: true,
169
173
  description: "Warn when a package name is one edit away from a well-known package."
174
+ },
175
+ confirmTyposquatsWithLlm: {
176
+ type: "boolean",
177
+ default: false,
178
+ description: "Ask the host LLM to confirm whether a flagged typosquat is real-but-obscure or genuinely a typo. Appended to the warn context, never escalates to a block. Off by default."
170
179
  }
171
180
  }
172
181
  },
@@ -175,6 +184,8 @@ var plugin = {
175
184
  state.installsSeen = 0;
176
185
  state.blocks = 0;
177
186
  state.warns = 0;
187
+ state.llmConfirmCount = 0;
188
+ state.llmConfirmErrors = 0;
178
189
  state.lastBlock = null;
179
190
  if (state.hookUnregister) {
180
191
  try {
@@ -184,7 +195,7 @@ var plugin = {
184
195
  state.hookUnregister = null;
185
196
  }
186
197
  const cfg = readConfig(api.config.extensions?.["dep-guard"]);
187
- const hook = (input) => {
198
+ const hook = async (input) => {
188
199
  if (!cfg.enabled) return;
189
200
  state.invocations += 1;
190
201
  const ti = input.toolInput ?? {};
@@ -220,9 +231,31 @@ var plugin = {
220
231
  if (cfg.typosquatCheck) {
221
232
  const lookalike = typosquatOf(pkg.name);
222
233
  if (lookalike) {
223
- notes.push(
224
- `"${pkg.name}" is one edit away from the well-known package "${lookalike}" \u2014 possible typosquat. Verify the name before installing.`
225
- );
234
+ const baseNote = `"${pkg.name}" is one edit away from the well-known package "${lookalike}" \u2014 possible typosquat. Verify the name before installing.`;
235
+ if (cfg.confirmTyposquatsWithLlm && api.llm) {
236
+ try {
237
+ const verdict = await api.llm.complete(
238
+ `A user is installing the npm package "${pkg.name}" which is 1 edit away from the well-known "${lookalike}". Is "${pkg.name}" likely a typo of "${lookalike}" or a real-but-obscure package? Reply with ONE sentence starting with "TYPO:" or "REAL:".`,
239
+ {
240
+ system: "You are a supply-chain security assistant. Reply tersely with a single TYPO: or REAL: verdict.",
241
+ maxTokens: 80
242
+ }
243
+ );
244
+ const t = verdict.text.trim();
245
+ if (t) {
246
+ state.llmConfirmCount += 1;
247
+ api.metrics.counter("llm_confirm");
248
+ notes.push(`${baseNote} LLM verdict: ${t.slice(0, 300)}`);
249
+ } else {
250
+ notes.push(baseNote);
251
+ }
252
+ } catch {
253
+ state.llmConfirmErrors += 1;
254
+ notes.push(baseNote);
255
+ }
256
+ } else {
257
+ notes.push(baseNote);
258
+ }
226
259
  }
227
260
  }
228
261
  if (cfg.warnOnUnpinned && pkg.version === null) {
@@ -263,6 +296,8 @@ ${notes.map((n) => ` - ${n}`).join("\n")}`
263
296
  counters: {
264
297
  invocations: state.invocations,
265
298
  installsSeen: state.installsSeen,
299
+ llmConfirmCount: state.llmConfirmCount,
300
+ llmConfirmErrors: state.llmConfirmErrors,
266
301
  blocks: state.blocks,
267
302
  warns: state.warns
268
303
  },
@@ -288,6 +323,8 @@ ${notes.map((n) => ` - ${n}`).join("\n")}`
288
323
  const final = {
289
324
  invocations: state.invocations,
290
325
  installsSeen: state.installsSeen,
326
+ llmConfirmCount: state.llmConfirmCount,
327
+ llmConfirmErrors: state.llmConfirmErrors,
291
328
  blocks: state.blocks,
292
329
  warns: state.warns
293
330
  };
@@ -295,6 +332,8 @@ ${notes.map((n) => ` - ${n}`).join("\n")}`
295
332
  state.installsSeen = 0;
296
333
  state.blocks = 0;
297
334
  state.warns = 0;
335
+ state.llmConfirmCount = 0;
336
+ state.llmConfirmErrors = 0;
298
337
  state.lastBlock = null;
299
338
  api.log.info("dep-guard: teardown complete", { final });
300
339
  },
@@ -305,6 +344,8 @@ ${notes.map((n) => ` - ${n}`).join("\n")}`
305
344
  counters: {
306
345
  invocations: state.invocations,
307
346
  installsSeen: state.installsSeen,
347
+ llmConfirmCount: state.llmConfirmCount,
348
+ llmConfirmErrors: state.llmConfirmErrors,
308
349
  blocks: state.blocks,
309
350
  warns: state.warns
310
351
  }
@@ -0,0 +1,35 @@
1
+ import { Plugin } from '@wrongstack/core';
2
+
3
+ /**
4
+ * dependency-vulnerability-gate plugin — PostToolUse hook that audits
5
+ * dependencies after install commands.
6
+ *
7
+ * Runs `npm audit --json` (or `pnpm audit --json` when pnpm-lock.yaml is
8
+ * present) after the `install` tool or bash/exec commands containing
9
+ * `npm install`/`pnpm add`/`yarn add`. Parses the audit report and blocks
10
+ * or warns when any vulnerability severity meets or exceeds the configured
11
+ * threshold.
12
+ *
13
+ * Tool registered:
14
+ * - dependency_audit_status : Show config + per-session counters.
15
+ *
16
+ * Hooks registered:
17
+ * - PostToolUse with matcher `install|bash|exec`.
18
+ *
19
+ * Config (`config.extensions['dependency-vulnerability-gate']`):
20
+ *
21
+ * ```jsonc
22
+ * {
23
+ * "enabled": true,
24
+ * "severityThreshold": "high", // "low" | "moderate" | "high" | "critical"
25
+ * "block": true, // true = block, false = warn
26
+ * "timeoutMs": 120000
27
+ * }
28
+ * ```
29
+ *
30
+ * @public
31
+ */
32
+
33
+ declare const plugin: Plugin;
34
+
35
+ export { plugin as default };