@wrongstack/plugins 0.281.3 → 0.282.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 (84) hide show
  1. package/README.md +30 -4
  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-escalate.d.ts +1 -1
  9. package/dist/auto-i18n-extractor.d.ts +36 -0
  10. package/dist/auto-i18n-extractor.js +335 -0
  11. package/dist/branch-guard.d.ts +6 -5
  12. package/dist/branch-guard.js +54 -4
  13. package/dist/checkpoint.js +18 -0
  14. package/dist/code-metrics.d.ts +31 -0
  15. package/dist/code-metrics.js +338 -0
  16. package/dist/commit-validator.js +67 -12
  17. package/dist/context-pins.js +4 -4
  18. package/dist/cost-tracker.d.ts +1 -1
  19. package/dist/cost-tracker.js +58 -19
  20. package/dist/cron.js +18 -8
  21. package/dist/dead-code-detector.d.ts +34 -0
  22. package/dist/dead-code-detector.js +354 -0
  23. package/dist/dep-guard.js +47 -6
  24. package/dist/dependency-vulnerability-gate.d.ts +35 -0
  25. package/dist/dependency-vulnerability-gate.js +308 -0
  26. package/dist/diff-summary.js +97 -10
  27. package/dist/doc-sync-guard.d.ts +33 -0
  28. package/dist/doc-sync-guard.js +223 -0
  29. package/dist/duplicate-code-detector.d.ts +33 -0
  30. package/dist/duplicate-code-detector.js +384 -0
  31. package/dist/feature-flag-tracker.d.ts +38 -0
  32. package/dist/feature-flag-tracker.js +316 -0
  33. package/dist/file-watcher.js +85 -36
  34. package/dist/format-on-save.js +99 -18
  35. package/dist/import-organizer.js +73 -14
  36. package/dist/index.d.ts +27 -0
  37. package/dist/index.js +11205 -2106
  38. package/dist/interface-contract-guard.d.ts +37 -0
  39. package/dist/interface-contract-guard.js +302 -0
  40. package/dist/knowledge-graph.d.ts +45 -0
  41. package/dist/knowledge-graph.js +325 -0
  42. package/dist/license-audit-gate.d.ts +34 -0
  43. package/dist/license-audit-gate.js +260 -0
  44. package/dist/llm-cache.js +5 -0
  45. package/dist/loop-breaker.d.ts +0 -38
  46. package/dist/loop-breaker.js +209 -8
  47. package/dist/migration-planner.d.ts +30 -0
  48. package/dist/migration-planner.js +349 -0
  49. package/dist/model-router.js +5 -0
  50. package/dist/performance-regression-gate.d.ts +33 -0
  51. package/dist/performance-regression-gate.js +315 -0
  52. package/dist/plugin-stack-observer.d.ts +35 -0
  53. package/dist/plugin-stack-observer.js +138 -0
  54. package/dist/pr-drafter.d.ts +35 -0
  55. package/dist/pr-drafter.js +334 -0
  56. package/dist/prompt-firewall.js +5 -0
  57. package/dist/refactor-suggester.d.ts +38 -0
  58. package/dist/refactor-suggester.js +382 -0
  59. package/dist/release-notes-generator.d.ts +27 -0
  60. package/dist/release-notes-generator.js +209 -0
  61. package/dist/schema-evolution-guard.d.ts +42 -0
  62. package/dist/schema-evolution-guard.js +319 -0
  63. package/dist/security-hotspot-scanner.d.ts +30 -0
  64. package/dist/security-hotspot-scanner.js +402 -0
  65. package/dist/semantic-search-indexer.d.ts +38 -0
  66. package/dist/semantic-search-indexer.js +436 -0
  67. package/dist/shell-check.js +38 -3
  68. package/dist/smart-rename.d.ts +26 -0
  69. package/dist/smart-rename.js +170 -0
  70. package/dist/spec-linker.js +273 -133
  71. package/dist/test-coverage-gate.d.ts +37 -0
  72. package/dist/test-coverage-gate.js +263 -0
  73. package/dist/test-flake-detector.d.ts +27 -0
  74. package/dist/test-flake-detector.js +274 -0
  75. package/dist/test-generator.d.ts +32 -0
  76. package/dist/test-generator.js +243 -0
  77. package/dist/test-runner-gate.js +154 -22
  78. package/dist/todo-listener.d.ts +2 -2
  79. package/dist/todo-listener.js +5 -5
  80. package/dist/token-throttle.js +5 -0
  81. package/dist/type-gate.d.ts +37 -0
  82. package/dist/type-gate.js +311 -0
  83. package/package.json +112 -4
  84. package/LICENSE +0 -21
@@ -1,8 +1,19 @@
1
- import { execSync } from 'child_process';
1
+ import { execSync, execFileSync } from 'child_process';
2
2
  import { existsSync, statSync } from 'fs';
3
+ import { isAbsolute, resolve, relative } from 'path';
3
4
 
4
5
  // src/format-on-save/index.ts
5
6
  var API_VERSION = "^0.1.10";
7
+ function withinProject(p) {
8
+ if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
9
+ const root = process.cwd();
10
+ const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
11
+ const rel = relative(root, resolved);
12
+ if (rel === "" || rel === ".") return true;
13
+ if (rel.startsWith("..")) return false;
14
+ if (isAbsolute(rel)) return false;
15
+ return true;
16
+ }
6
17
  var state = {
7
18
  invocationCount: 0,
8
19
  /** Times formatting was applied (file changed). */
@@ -11,24 +22,57 @@ var state = {
11
22
  cleanCount: 0,
12
23
  /** Times biome failed (not installed, timeout, parse error). */
13
24
  errorCount: 0,
25
+ /** Times the format pass was skipped because import-organizer
26
+ * had already covered the path within the TTL window. */
27
+ coveredSkipCount: 0,
14
28
  /** Hook handle for teardown. */
15
29
  hookUnregister: null,
30
+ /** Cross-plugin event listener handle for teardown. */
31
+ patternUnregister: null,
16
32
  /** Last format result — surfaced by health() + status tool. */
17
33
  lastResult: null
18
34
  };
19
35
  var DEFAULTS = {
20
36
  enabled: true,
21
- timeoutMs: 5e3
37
+ timeoutMs: 5e3,
38
+ skipWhenCoveredBy: true,
39
+ skipTtlMs: 3e4
22
40
  };
23
41
  function readConfig(raw) {
24
42
  if (!raw || typeof raw !== "object") return { ...DEFAULTS };
25
43
  const r = raw;
26
44
  return {
27
45
  enabled: r["enabled"] !== false,
28
- timeoutMs: typeof r["timeoutMs"] === "number" && r["timeoutMs"] > 0 ? r["timeoutMs"] : DEFAULTS.timeoutMs
46
+ timeoutMs: typeof r["timeoutMs"] === "number" && r["timeoutMs"] > 0 ? r["timeoutMs"] : DEFAULTS.timeoutMs,
47
+ skipWhenCoveredBy: r["skipWhenCoveredBy"] !== false,
48
+ skipTtlMs: typeof r["skipTtlMs"] === "number" && r["skipTtlMs"] >= 0 ? r["skipTtlMs"] : DEFAULTS.skipTtlMs
29
49
  };
30
50
  }
51
+ var recentlyCovered = /* @__PURE__ */ new Map();
52
+ function clearRegistrations() {
53
+ if (state.hookUnregister) {
54
+ try {
55
+ state.hookUnregister();
56
+ } catch {
57
+ }
58
+ state.hookUnregister = null;
59
+ }
60
+ if (state.patternUnregister) {
61
+ try {
62
+ state.patternUnregister();
63
+ } catch {
64
+ }
65
+ state.patternUnregister = null;
66
+ }
67
+ }
68
+ function evictExpired(ttlMs) {
69
+ const cutoff = Date.now() - ttlMs;
70
+ for (const [path, ts] of recentlyCovered) {
71
+ if (ts < cutoff) recentlyCovered.delete(path);
72
+ }
73
+ }
31
74
  function formatFile(filePath, timeoutMs) {
75
+ if (!withinProject(filePath)) return null;
32
76
  if (!existsSync(filePath)) return null;
33
77
  let bytesBefore;
34
78
  try {
@@ -37,7 +81,7 @@ function formatFile(filePath, timeoutMs) {
37
81
  return null;
38
82
  }
39
83
  try {
40
- execSync(`npx biome format --write "${filePath}"`, {
84
+ execFileSync("npx", ["biome", "format", "--write", filePath], {
41
85
  encoding: "utf-8",
42
86
  timeout: timeoutMs,
43
87
  cwd: process.cwd(),
@@ -57,7 +101,7 @@ function formatFile(filePath, timeoutMs) {
57
101
  return { changed: true, bytesBefore, bytesAfter };
58
102
  }
59
103
  try {
60
- execSync(`npx biome format "${filePath}"`, {
104
+ execFileSync("npx", ["biome", "format", filePath], {
61
105
  encoding: "utf-8",
62
106
  timeout: timeoutMs,
63
107
  cwd: process.cwd(),
@@ -88,16 +132,29 @@ var plugin = {
88
132
  minimum: 1e3,
89
133
  default: 5e3,
90
134
  description: "Biome format process timeout in milliseconds."
135
+ },
136
+ skipWhenCoveredBy: {
137
+ type: "boolean",
138
+ default: true,
139
+ description: "Skip the format pass when another plugin (e.g. import-organizer) just touched the same path. Saves one biome invocation per write/edit when both plugins are enabled."
140
+ },
141
+ skipTtlMs: {
142
+ type: "number",
143
+ minimum: 0,
144
+ default: 3e4,
145
+ description: "How long (ms) to remember a path covered by another plugin. 0 disables the memory."
91
146
  }
92
147
  }
93
148
  },
94
149
  setup(api) {
150
+ clearRegistrations();
95
151
  state.invocationCount = 0;
96
152
  state.formattedCount = 0;
97
153
  state.cleanCount = 0;
98
154
  state.errorCount = 0;
99
- state.hookUnregister = null;
155
+ state.coveredSkipCount = 0;
100
156
  state.lastResult = null;
157
+ recentlyCovered.clear();
101
158
  const cfg = readConfig(api.config.extensions?.["format-on-save"]);
102
159
  let biomeAvailable = false;
103
160
  try {
@@ -120,6 +177,20 @@ var plugin = {
120
177
  const inp = input.toolInput ?? {};
121
178
  const filePath = inp["path"];
122
179
  if (!filePath || typeof filePath !== "string") return;
180
+ if (cfg.skipWhenCoveredBy && cfg.skipTtlMs > 0) {
181
+ evictExpired(cfg.skipTtlMs);
182
+ if (recentlyCovered.has(filePath)) {
183
+ state.coveredSkipCount = (state.coveredSkipCount ?? 0) + 1;
184
+ recentlyCovered.delete(filePath);
185
+ api.log.info(
186
+ `format-on-save: skipped ${filePath} \u2014 already formatted by import-organizer`,
187
+ {
188
+ tool: toolName
189
+ }
190
+ );
191
+ return;
192
+ }
193
+ }
123
194
  state.invocationCount += 1;
124
195
  const result = formatFile(filePath, cfg.timeoutMs);
125
196
  if (!result) {
@@ -150,9 +221,18 @@ var plugin = {
150
221
  return;
151
222
  };
152
223
  state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
224
+ state.patternUnregister = api.onPattern(
225
+ "import-organizer:done",
226
+ (_eventName, payload) => {
227
+ const p = payload ?? {};
228
+ if (typeof p.path !== "string" || p.path.length === 0) return;
229
+ recentlyCovered.set(p.path, Date.now());
230
+ api.metrics.counter("covered_notice");
231
+ }
232
+ );
153
233
  api.tools.register({
154
234
  name: "format_on_save_status",
155
- description: "Reports format-on-save state: biome availability, and per-session formatted/clean/error counters.",
235
+ description: "Reports format-on-save state: biome availability, and per-session formatted/clean/error/skipped counters.",
156
236
  inputSchema: { type: "object", properties: {} },
157
237
  permission: "auto",
158
238
  category: "Code Quality",
@@ -163,11 +243,14 @@ var plugin = {
163
243
  enabled: cfg.enabled,
164
244
  biomeAvailable,
165
245
  timeoutMs: cfg.timeoutMs,
246
+ skipWhenCoveredBy: cfg.skipWhenCoveredBy,
247
+ skipTtlMs: cfg.skipTtlMs,
166
248
  counters: {
167
249
  invocations: state.invocationCount,
168
250
  formatted: state.formattedCount,
169
251
  clean: state.cleanCount,
170
- errors: state.errorCount
252
+ errors: state.errorCount,
253
+ coveredSkips: state.coveredSkipCount
171
254
  },
172
255
  lastResult: state.lastResult
173
256
  };
@@ -180,35 +263,33 @@ var plugin = {
180
263
  });
181
264
  },
182
265
  teardown(api) {
183
- if (state.hookUnregister) {
184
- try {
185
- state.hookUnregister();
186
- } catch {
187
- }
188
- state.hookUnregister = null;
189
- }
266
+ clearRegistrations();
190
267
  const final = {
191
268
  invocations: state.invocationCount,
192
269
  formatted: state.formattedCount,
193
270
  clean: state.cleanCount,
194
- errors: state.errorCount
271
+ errors: state.errorCount,
272
+ coveredSkips: state.coveredSkipCount
195
273
  };
196
274
  state.invocationCount = 0;
197
275
  state.formattedCount = 0;
198
276
  state.cleanCount = 0;
199
277
  state.errorCount = 0;
278
+ state.coveredSkipCount = 0;
200
279
  state.lastResult = null;
280
+ recentlyCovered.clear();
201
281
  api.log.info("format-on-save: teardown complete", { final });
202
282
  },
203
283
  async health() {
204
284
  return {
205
285
  ok: true,
206
- message: state.lastResult === null ? `format-on-save: ${state.invocationCount} invocation(s), ${state.formattedCount} formatted` : state.lastResult.changed ? `format-on-save: last formatted ${state.lastResult.path} (${state.lastResult.tool}) at ${state.lastResult.when}` : `format-on-save: last check on ${state.lastResult.path} was already clean`,
286
+ message: state.lastResult === null ? `format-on-save: ${state.invocationCount} invocation(s), ${state.formattedCount} formatted, ${state.coveredSkipCount} covered-skipped` : state.lastResult.changed ? `format-on-save: last formatted ${state.lastResult.path} (${state.lastResult.tool}) at ${state.lastResult.when}` : `format-on-save: last check on ${state.lastResult.path} was already clean`,
207
287
  counters: {
208
288
  invocations: state.invocationCount,
209
289
  formatted: state.formattedCount,
210
290
  clean: state.cleanCount,
211
- errors: state.errorCount
291
+ errors: state.errorCount,
292
+ coveredSkips: state.coveredSkipCount
212
293
  },
213
294
  lastResult: state.lastResult
214
295
  };
@@ -1,7 +1,48 @@
1
1
  import { spawn } from 'child_process';
2
2
  import { existsSync, statSync } from 'fs';
3
+ import { isAbsolute, resolve, relative, basename } from 'path';
3
4
 
4
5
  // src/import-organizer/index.ts
6
+ function withinProject(p) {
7
+ if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
8
+ const root = process.cwd();
9
+ const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
10
+ const rel = relative(root, resolved);
11
+ if (rel === "" || rel === ".") return true;
12
+ if (rel.startsWith("..")) return false;
13
+ if (isAbsolute(rel)) return false;
14
+ return true;
15
+ }
16
+ var ALLOWED_FIRST_TOKENS = /* @__PURE__ */ new Set([
17
+ "npx",
18
+ "pnpm",
19
+ "npm",
20
+ "yarn",
21
+ "biome",
22
+ "@biomejs/biome",
23
+ "eslint",
24
+ "node"
25
+ // Recognise a fully-qualified project-local path like
26
+ // "./node_modules/.bin/biome"; basename check kicks in below.
27
+ ]);
28
+ function resolveAllowedCommand(command) {
29
+ const tokens = command.split(/\s+/).filter(Boolean);
30
+ if (tokens.length === 0) return null;
31
+ const head = tokens[0];
32
+ if (ALLOWED_FIRST_TOKENS.has(head)) {
33
+ return { cmd: head, args: tokens.slice(1) };
34
+ }
35
+ if (isAbsolute(head)) {
36
+ if (!withinProject(head)) return null;
37
+ const base = basename(head);
38
+ if (ALLOWED_FIRST_TOKENS.has(base)) return { cmd: head, args: tokens.slice(1) };
39
+ }
40
+ if (!isAbsolute(head) && withinProject(head)) {
41
+ const base = basename(head);
42
+ if (ALLOWED_FIRST_TOKENS.has(base)) return { cmd: head, args: tokens.slice(1) };
43
+ }
44
+ return null;
45
+ }
5
46
  var API_VERSION = "^0.1.10";
6
47
  var state = {
7
48
  invocationCount: 0,
@@ -17,7 +58,8 @@ var DEFAULTS = {
17
58
  enabled: true,
18
59
  command: "npx @biomejs/biome check --write --unsafe",
19
60
  fallbackCommand: "npx eslint --fix",
20
- timeoutMs: 1e4
61
+ timeoutMs: 1e4,
62
+ notifyFormatOnSave: true
21
63
  };
22
64
  function readConfig(raw) {
23
65
  if (!raw || typeof raw !== "object") return { ...DEFAULTS };
@@ -26,11 +68,12 @@ function readConfig(raw) {
26
68
  enabled: r["enabled"] !== false,
27
69
  command: typeof r["command"] === "string" && r["command"].length > 0 ? r["command"] : DEFAULTS.command,
28
70
  fallbackCommand: typeof r["fallbackCommand"] === "string" && r["fallbackCommand"].length > 0 ? r["fallbackCommand"] : DEFAULTS.fallbackCommand,
29
- timeoutMs: typeof r["timeoutMs"] === "number" && r["timeoutMs"] > 0 ? r["timeoutMs"] : DEFAULTS.timeoutMs
71
+ timeoutMs: typeof r["timeoutMs"] === "number" && r["timeoutMs"] > 0 ? r["timeoutMs"] : DEFAULTS.timeoutMs,
72
+ notifyFormatOnSave: r["notifyFormatOnSave"] !== false
30
73
  };
31
74
  }
32
75
  function runCommand(command, args, timeoutMs, cwd) {
33
- return new Promise((resolve) => {
76
+ return new Promise((resolve2) => {
34
77
  let timedOut = false;
35
78
  const stdoutChunks = [];
36
79
  const stderrChunks = [];
@@ -42,17 +85,17 @@ function runCommand(command, args, timeoutMs, cwd) {
42
85
  signal: AbortSignal.timeout(timeoutMs)
43
86
  });
44
87
  } catch {
45
- resolve({ code: 127, stdout: "", stderr: "", timedOut: false });
88
+ resolve2({ code: 127, stdout: "", stderr: "", timedOut: false });
46
89
  return;
47
90
  }
48
91
  child.stdout?.on("data", (c) => stdoutChunks.push(c));
49
92
  child.stderr?.on("data", (c) => stderrChunks.push(c));
50
93
  child.on("error", () => {
51
- resolve({ code: 127, stdout: "", stderr: "", timedOut: false });
94
+ resolve2({ code: 127, stdout: "", stderr: "", timedOut: false });
52
95
  });
53
96
  child.on("close", (code) => {
54
97
  if (timedOut) return;
55
- resolve({
98
+ resolve2({
56
99
  code,
57
100
  stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
58
101
  stderr: Buffer.concat(stderrChunks).toString("utf-8"),
@@ -61,11 +104,12 @@ function runCommand(command, args, timeoutMs, cwd) {
61
104
  });
62
105
  child.on("abort", () => {
63
106
  timedOut = true;
64
- resolve({ code: null, stdout: "", stderr: "", timedOut: true });
107
+ resolve2({ code: null, stdout: "", stderr: "", timedOut: true });
65
108
  });
66
109
  });
67
110
  }
68
111
  async function organizeImports(filePath, cfg, cwd) {
112
+ if (!withinProject(filePath)) return null;
69
113
  if (!existsSync(filePath)) return null;
70
114
  let bytesBefore;
71
115
  try {
@@ -73,15 +117,15 @@ async function organizeImports(filePath, cfg, cwd) {
73
117
  } catch {
74
118
  return null;
75
119
  }
76
- const primary = cfg.command.split(/\s+/).filter(Boolean);
77
- if (primary.length === 0) return null;
78
- const [primaryCmd, ...primaryArgs] = primary;
120
+ const primary = resolveAllowedCommand(cfg.command);
121
+ if (!primary) return null;
122
+ const [primaryCmd, ...primaryArgs] = [primary.cmd, ...primary.args];
79
123
  let result = await runCommand(primaryCmd, [...primaryArgs, filePath], cfg.timeoutMs, cwd);
80
124
  let usedCommand = cfg.command;
81
125
  if (result.code === 127 && cfg.fallbackCommand) {
82
- const fallback = cfg.fallbackCommand.split(/\s+/).filter(Boolean);
83
- if (fallback.length > 0) {
84
- const [fbCmd, ...fbArgs] = fallback;
126
+ const fallback = resolveAllowedCommand(cfg.fallbackCommand);
127
+ if (fallback) {
128
+ const [fbCmd, ...fbArgs] = [fallback.cmd, ...fallback.args];
85
129
  result = await runCommand(fbCmd, [...fbArgs, filePath], cfg.timeoutMs, cwd);
86
130
  usedCommand = cfg.fallbackCommand;
87
131
  }
@@ -132,6 +176,11 @@ var plugin = {
132
176
  minimum: 1e3,
133
177
  default: 1e4,
134
178
  description: "Per-invocation linter timeout in milliseconds."
179
+ },
180
+ notifyFormatOnSave: {
181
+ type: "boolean",
182
+ default: true,
183
+ description: "Emit `import-organizer:done` after each successful run so `format-on-save` can skip its redundant `biome format --write` pass on the same file. Set false to keep both running unconditionally."
135
184
  }
136
185
  }
137
186
  },
@@ -160,13 +209,23 @@ var plugin = {
160
209
  if (!state.linterAvailable) {
161
210
  state.linterAvailable = false;
162
211
  state.probeComplete = true;
163
- api.log.warn("import-organizer: no linter available \u2014 hook will be a no-op for the rest of the session");
212
+ api.log.warn(
213
+ "import-organizer: no linter available \u2014 hook will be a no-op for the rest of the session"
214
+ );
164
215
  }
165
216
  state.errorCount += 1;
166
217
  return;
167
218
  }
168
219
  state.linterAvailable = true;
169
220
  state.probeComplete = true;
221
+ if (cfg.notifyFormatOnSave) {
222
+ api.emitCustom("import-organizer:done", {
223
+ path: filePath,
224
+ changed: result.changed,
225
+ command: result.command,
226
+ when: (/* @__PURE__ */ new Date()).toISOString()
227
+ });
228
+ }
170
229
  state.lastResult = {
171
230
  path: filePath,
172
231
  tool: toolName,
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { default as agentHandoffPlugin } from './agent-handoff.js';
1
2
  export { default as autoDocPlugin } from './auto-doc.js';
2
3
  export { default as autoEscalatePlugin } from './auto-escalate.js';
3
4
  export { default as branchGuardPlugin } from './branch-guard.js';
@@ -16,12 +17,15 @@ export { default as formatOnSavePlugin } from './format-on-save.js';
16
17
  export { default as gitAutocommitPlugin } from './git-autocommit.js';
17
18
  export { default as importOrganizerPlugin } from './import-organizer.js';
18
19
  export { default as injectionShieldPlugin } from './injection-shield.js';
20
+ export { default as knowledgeGraphPlugin } from './knowledge-graph.js';
19
21
  export { default as lintGatePlugin } from './lint-gate.js';
20
22
  export { default as llmCachePlugin } from './llm-cache.js';
21
23
  export { default as loopBreakerPlugin } from './loop-breaker.js';
22
24
  export { default as modelRouterPlugin } from './model-router.js';
23
25
  export { default as notifyHubPlugin } from './notify-hub.js';
24
26
  export { default as pathGuardPlugin } from './path-guard.js';
27
+ export { default as pluginStackObserverPlugin } from './plugin-stack-observer.js';
28
+ export { default as prDrafterPlugin } from './pr-drafter.js';
25
29
  export { default as promptFirewallPlugin } from './prompt-firewall.js';
26
30
  export { default as secretScannerPlugin } from './secret-scanner.js';
27
31
  export { default as semverBumpPlugin } from './semver-bump.js';
@@ -29,7 +33,30 @@ export { default as sessionRecapPlugin } from './session-recap.js';
29
33
  export { default as shellCheckPlugin } from './shell-check.js';
30
34
  export { default as specLinkerPlugin } from './spec-linker.js';
31
35
  export { default as templateEnginePlugin } from './template-engine.js';
36
+ export { default as testCoverageGatePlugin } from './test-coverage-gate.js';
32
37
  export { default as testRunnerGatePlugin } from './test-runner-gate.js';
38
+ export { default as typeGatePlugin } from './type-gate.js';
39
+ export { default as accessibilityAuditorPlugin } from './accessibility-auditor.js';
40
+ export { default as apiCompatibilityGatePlugin } from './api-compatibility-gate.js';
41
+ export { default as autoI18nExtractorPlugin } from './auto-i18n-extractor.js';
42
+ export { default as codeMetricsPlugin } from './code-metrics.js';
43
+ export { default as deadCodeDetectorPlugin } from './dead-code-detector.js';
44
+ export { default as dependencyVulnerabilityGatePlugin } from './dependency-vulnerability-gate.js';
45
+ export { default as docSyncGuardPlugin } from './doc-sync-guard.js';
46
+ export { default as duplicateCodeDetectorPlugin } from './duplicate-code-detector.js';
47
+ export { default as featureFlagTrackerPlugin } from './feature-flag-tracker.js';
48
+ export { default as interfaceContractGuardPlugin } from './interface-contract-guard.js';
49
+ export { default as licenseAuditGatePlugin } from './license-audit-gate.js';
50
+ export { default as migrationPlannerPlugin } from './migration-planner.js';
51
+ export { default as performanceRegressionGatePlugin } from './performance-regression-gate.js';
52
+ export { default as refactorSuggesterPlugin } from './refactor-suggester.js';
53
+ export { default as releaseNotesGeneratorPlugin } from './release-notes-generator.js';
54
+ export { default as schemaEvolutionGuardPlugin } from './schema-evolution-guard.js';
55
+ export { default as securityHotspotScannerPlugin } from './security-hotspot-scanner.js';
56
+ export { default as semanticSearchIndexerPlugin } from './semantic-search-indexer.js';
57
+ export { default as smartRenamePlugin } from './smart-rename.js';
58
+ export { default as testFlakeDetectorPlugin } from './test-flake-detector.js';
59
+ export { default as testGeneratorPlugin } from './test-generator.js';
33
60
  export { default as todoListenerPlugin } from './todo-listener.js';
34
61
  export { default as todoTrackerPlugin } from './todo-tracker.js';
35
62
  export { default as tokenBudgetPlugin } from './token-budget.js';