@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
@@ -0,0 +1,263 @@
1
+ import { readFileSync } from 'fs';
2
+ import { isAbsolute, resolve, relative } from 'path';
3
+
4
+ // src/test-coverage-gate/index.ts
5
+ var API_VERSION = "^0.1.10";
6
+ var state = {
7
+ invocationCount: 0,
8
+ runCount: 0,
9
+ passCount: 0,
10
+ failCount: 0,
11
+ errorCount: 0,
12
+ skippedCount: 0,
13
+ lastOverallPct: null,
14
+ lastResult: null,
15
+ hookUnregister: null
16
+ };
17
+ var DEFAULTS = {
18
+ enabled: true,
19
+ coveragePath: "coverage/coverage-summary.json",
20
+ threshold: 80,
21
+ mode: "warn",
22
+ deltaThreshold: 1,
23
+ runOnChange: [".ts", ".tsx", ".js", ".jsx"]
24
+ };
25
+ function readConfig(raw) {
26
+ if (!raw || typeof raw !== "object") return { ...DEFAULTS };
27
+ const r = raw;
28
+ return {
29
+ enabled: r["enabled"] !== false,
30
+ coveragePath: typeof r["coveragePath"] === "string" ? r["coveragePath"] : DEFAULTS.coveragePath,
31
+ threshold: typeof r["threshold"] === "number" && r["threshold"] >= 0 && r["threshold"] <= 100 ? r["threshold"] : DEFAULTS.threshold,
32
+ mode: r["mode"] === "block" ? "block" : DEFAULTS.mode,
33
+ deltaThreshold: typeof r["deltaThreshold"] === "number" && r["deltaThreshold"] >= 0 ? r["deltaThreshold"] : DEFAULTS.deltaThreshold,
34
+ runOnChange: Array.isArray(r["runOnChange"]) ? r["runOnChange"].filter((x) => typeof x === "string") : DEFAULTS.runOnChange
35
+ };
36
+ }
37
+ function withinProject(p) {
38
+ if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
39
+ const root = process.cwd();
40
+ const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
41
+ const rel = relative(root, resolved);
42
+ if (rel === "" || rel === ".") return true;
43
+ if (rel.startsWith("..")) return false;
44
+ if (isAbsolute(rel)) return false;
45
+ return true;
46
+ }
47
+ function readCoverageSummary(coveragePath) {
48
+ try {
49
+ const raw = readFileSync(coveragePath, "utf-8");
50
+ return JSON.parse(raw);
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+ function overallPercent(summary) {
56
+ const total = summary.total;
57
+ if (!total || typeof total !== "object") return null;
58
+ const t = total;
59
+ const values = ["lines", "statements", "functions", "branches"].map((k) => t[k]?.pct).filter((v) => typeof v === "number");
60
+ if (values.length === 0) return null;
61
+ return values.reduce((a, b) => a + b, 0) / values.length;
62
+ }
63
+ var plugin = {
64
+ name: "test-coverage-gate",
65
+ version: "0.1.0",
66
+ description: "PostToolUse hook that detects test coverage regressions after source-file edits",
67
+ apiVersion: API_VERSION,
68
+ capabilities: { tools: true, hooks: true },
69
+ defaultConfig: { ...DEFAULTS },
70
+ configSchema: {
71
+ type: "object",
72
+ properties: {
73
+ enabled: {
74
+ type: "boolean",
75
+ default: true,
76
+ description: "Master switch."
77
+ },
78
+ coveragePath: {
79
+ type: "string",
80
+ default: "coverage/coverage-summary.json",
81
+ description: "Path to the JSON coverage summary file."
82
+ },
83
+ threshold: {
84
+ type: "number",
85
+ minimum: 0,
86
+ maximum: 100,
87
+ default: 80,
88
+ description: "Minimum allowed overall coverage percentage."
89
+ },
90
+ mode: {
91
+ type: "string",
92
+ enum: ["warn", "block"],
93
+ default: "warn",
94
+ description: "warn = inject context; block = refuse the mutating tool."
95
+ },
96
+ deltaThreshold: {
97
+ type: "number",
98
+ minimum: 0,
99
+ default: 1,
100
+ description: "Warn/block when coverage drops by at least this many percentage points."
101
+ },
102
+ runOnChange: {
103
+ type: "array",
104
+ items: { type: "string" },
105
+ default: [".ts", ".tsx", ".js", ".jsx"],
106
+ description: "Only check coverage when the edited file has one of these extensions."
107
+ }
108
+ }
109
+ },
110
+ setup(api) {
111
+ state.invocationCount = 0;
112
+ state.runCount = 0;
113
+ state.passCount = 0;
114
+ state.failCount = 0;
115
+ state.errorCount = 0;
116
+ state.skippedCount = 0;
117
+ state.lastOverallPct = null;
118
+ state.lastResult = null;
119
+ state.hookUnregister = null;
120
+ const cfg = readConfig(api.config.extensions?.["test-coverage-gate"]);
121
+ const hook = (input) => {
122
+ if (!cfg.enabled) return;
123
+ if (input.toolResult?.isError) return;
124
+ const inp = input.toolInput ?? {};
125
+ const sourcePath = inp["path"];
126
+ if (!sourcePath || typeof sourcePath !== "string") return;
127
+ if (!withinProject(sourcePath)) return;
128
+ const ext = sourcePath.includes(".") ? sourcePath.slice(sourcePath.lastIndexOf(".")).toLowerCase() : "";
129
+ if (!cfg.runOnChange.includes(ext)) {
130
+ state.skippedCount += 1;
131
+ return;
132
+ }
133
+ state.invocationCount += 1;
134
+ const summary = readCoverageSummary(cfg.coveragePath);
135
+ if (!summary) {
136
+ state.errorCount += 1;
137
+ return;
138
+ }
139
+ const pct = overallPercent(summary);
140
+ if (pct === null) {
141
+ state.errorCount += 1;
142
+ return;
143
+ }
144
+ state.runCount += 1;
145
+ const previous = state.lastOverallPct;
146
+ state.lastOverallPct = pct;
147
+ const belowThreshold = pct < cfg.threshold;
148
+ const dropped = previous !== null && previous - pct >= cfg.deltaThreshold;
149
+ const passed = !belowThreshold && !dropped;
150
+ state.lastResult = {
151
+ overallPct: pct,
152
+ previousOverallPct: previous,
153
+ threshold: cfg.threshold,
154
+ passed,
155
+ when: (/* @__PURE__ */ new Date()).toISOString()
156
+ };
157
+ if (passed) {
158
+ state.passCount += 1;
159
+ return;
160
+ }
161
+ state.failCount += 1;
162
+ const parts = ["\n\u274C test-coverage-gate:"];
163
+ if (belowThreshold) {
164
+ parts.push(
165
+ ` Overall coverage ${pct.toFixed(2)}% is below the ${cfg.threshold}% threshold.`
166
+ );
167
+ }
168
+ if (dropped) {
169
+ parts.push(
170
+ ` Coverage dropped from ${previous.toFixed(2)}% to ${pct.toFixed(2)}% (>= ${cfg.deltaThreshold}% delta).`
171
+ );
172
+ }
173
+ parts.push(" Add or update tests to recover coverage before continuing.");
174
+ const message = parts.join("\n");
175
+ if (cfg.mode === "block") {
176
+ return {
177
+ decision: "block",
178
+ reason: message
179
+ };
180
+ }
181
+ return { additionalContext: message };
182
+ };
183
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
184
+ api.tools.register({
185
+ name: "coverage_gate_status",
186
+ description: "Reports test-coverage-gate state: threshold, mode, last coverage percentage, and counters.",
187
+ inputSchema: { type: "object", properties: {} },
188
+ permission: "auto",
189
+ category: "Diagnostics",
190
+ mutating: false,
191
+ async execute() {
192
+ return {
193
+ ok: true,
194
+ enabled: cfg.enabled,
195
+ coveragePath: cfg.coveragePath,
196
+ threshold: cfg.threshold,
197
+ mode: cfg.mode,
198
+ deltaThreshold: cfg.deltaThreshold,
199
+ lastOverallPct: state.lastOverallPct,
200
+ counters: {
201
+ invocations: state.invocationCount,
202
+ runs: state.runCount,
203
+ passed: state.passCount,
204
+ failed: state.failCount,
205
+ errors: state.errorCount,
206
+ skipped: state.skippedCount
207
+ },
208
+ lastResult: state.lastResult
209
+ };
210
+ }
211
+ });
212
+ api.log.info("test-coverage-gate plugin loaded", {
213
+ version: "0.1.0",
214
+ coveragePath: cfg.coveragePath,
215
+ threshold: cfg.threshold,
216
+ mode: cfg.mode
217
+ });
218
+ },
219
+ teardown(api) {
220
+ if (state.hookUnregister) {
221
+ try {
222
+ state.hookUnregister();
223
+ } catch {
224
+ }
225
+ state.hookUnregister = null;
226
+ }
227
+ const final = {
228
+ invocations: state.invocationCount,
229
+ runs: state.runCount,
230
+ passed: state.passCount,
231
+ failed: state.failCount,
232
+ errors: state.errorCount,
233
+ skipped: state.skippedCount
234
+ };
235
+ state.invocationCount = 0;
236
+ state.runCount = 0;
237
+ state.passCount = 0;
238
+ state.failCount = 0;
239
+ state.errorCount = 0;
240
+ state.skippedCount = 0;
241
+ state.lastOverallPct = null;
242
+ state.lastResult = null;
243
+ api.log.info("test-coverage-gate: teardown complete", { final });
244
+ },
245
+ async health() {
246
+ return {
247
+ ok: true,
248
+ message: state.lastResult ? `test-coverage-gate: ${state.runCount} run(s), last ${state.lastResult.passed ? "PASSED" : "FAILED"} (${state.lastResult.overallPct.toFixed(2)}%)` : `test-coverage-gate: ${state.invocationCount} invocation(s), ${state.runCount} run(s)`,
249
+ counters: {
250
+ invocations: state.invocationCount,
251
+ runs: state.runCount,
252
+ passed: state.passCount,
253
+ failed: state.failCount,
254
+ errors: state.errorCount,
255
+ skipped: state.skippedCount
256
+ },
257
+ lastResult: state.lastResult
258
+ };
259
+ }
260
+ };
261
+ var test_coverage_gate_default = plugin;
262
+
263
+ export { test_coverage_gate_default as default };
@@ -0,0 +1,27 @@
1
+ import { Plugin } from '@wrongstack/core';
2
+
3
+ /**
4
+ * test-flake-detector plugin — runs a test command multiple times and reports
5
+ * tests that fail non-deterministically (flaky tests).
6
+ *
7
+ * Tools registered:
8
+ * - `flake_detect` : run a test command N times and identify flaky tests
9
+ * - `flake_status` : show config + per-session counters
10
+ *
11
+ * Config (`config.extensions['test-flake-detector']`):
12
+ *
13
+ * ```jsonc
14
+ * {
15
+ * "enabled": true,
16
+ * "defaultCommand": "npx vitest run", // used when flake_detect.command is omitted
17
+ * "maxRuns": 20, // hard upper bound for runs param
18
+ * "timeoutMs": 120000 // per-run timeout
19
+ * }
20
+ * ```
21
+ *
22
+ * @public
23
+ */
24
+
25
+ declare const plugin: Plugin;
26
+
27
+ export { plugin as default };
@@ -0,0 +1,274 @@
1
+ import { execSync } from 'child_process';
2
+
3
+ // src/test-flake-detector/index.ts
4
+ var API_VERSION = "^0.1.10";
5
+ var state = {
6
+ invocationCount: 0,
7
+ runCount: 0,
8
+ errorCount: 0,
9
+ lastResult: null
10
+ };
11
+ var DEFAULTS = {
12
+ enabled: true,
13
+ defaultCommand: "npx vitest run",
14
+ maxRuns: 20,
15
+ timeoutMs: 12e4
16
+ };
17
+ function readConfig(raw) {
18
+ if (!raw || typeof raw !== "object") return { ...DEFAULTS };
19
+ const r = raw;
20
+ return {
21
+ enabled: r["enabled"] !== false,
22
+ defaultCommand: typeof r["defaultCommand"] === "string" ? r["defaultCommand"] : DEFAULTS.defaultCommand,
23
+ maxRuns: typeof r["maxRuns"] === "number" && r["maxRuns"] >= 1 && r["maxRuns"] <= 100 ? Math.floor(r["maxRuns"]) : DEFAULTS.maxRuns,
24
+ timeoutMs: typeof r["timeoutMs"] === "number" && r["timeoutMs"] > 0 ? r["timeoutMs"] : DEFAULTS.timeoutMs
25
+ };
26
+ }
27
+ function parseTestOutput(output) {
28
+ const passed = [];
29
+ const failed = [];
30
+ const seen = /* @__PURE__ */ new Set();
31
+ for (const rawLine of output.split(/\r?\n/)) {
32
+ const line = rawLine.trim();
33
+ if (!line) continue;
34
+ const passMatch = line.match(/^[✓✔]\s+(.+)/);
35
+ if (passMatch) {
36
+ const name = passMatch[1].trim();
37
+ if (!seen.has(`p:${name}`)) {
38
+ seen.add(`p:${name}`);
39
+ passed.push(name);
40
+ }
41
+ continue;
42
+ }
43
+ const failMatch = line.match(/^[✕✗×]\s+(.+)/);
44
+ if (failMatch) {
45
+ const name = failMatch[1].trim();
46
+ if (!seen.has(`f:${name}`)) {
47
+ seen.add(`f:${name}`);
48
+ failed.push(name);
49
+ }
50
+ continue;
51
+ }
52
+ const jestFailMatch = line.match(/^●\s+(.+)/);
53
+ if (jestFailMatch) {
54
+ const name = jestFailMatch[1].trim();
55
+ if (!seen.has(`f:${name}`)) {
56
+ seen.add(`f:${name}`);
57
+ failed.push(name);
58
+ }
59
+ }
60
+ }
61
+ return { passed, failed };
62
+ }
63
+ function buildCommand(baseCommand, testPattern) {
64
+ const trimmedBase = baseCommand.trim();
65
+ if (!testPattern) return trimmedBase;
66
+ if (trimmedBase.includes(testPattern)) return trimmedBase;
67
+ return `${trimmedBase} ${testPattern}`;
68
+ }
69
+ function runOnce(command, timeoutMs) {
70
+ try {
71
+ const output = execSync(command, {
72
+ encoding: "utf-8",
73
+ timeout: timeoutMs,
74
+ cwd: process.cwd(),
75
+ stdio: ["pipe", "pipe", "pipe"]
76
+ });
77
+ return { output };
78
+ } catch (err) {
79
+ const e = err;
80
+ if (e.killed) {
81
+ return { output: e.stdout ?? "", error: `timeout after ${timeoutMs}ms` };
82
+ }
83
+ const output = `${e.stdout ?? ""}
84
+ ${e.stderr ?? ""}`.trim();
85
+ return { output, error: e.message ?? "test command exited with non-zero code" };
86
+ }
87
+ }
88
+ var plugin = {
89
+ name: "test-flake-detector",
90
+ version: "0.1.0",
91
+ description: "Runs a test command multiple times and reports tests that fail non-deterministically",
92
+ apiVersion: API_VERSION,
93
+ capabilities: { tools: true },
94
+ defaultConfig: { ...DEFAULTS },
95
+ configSchema: {
96
+ type: "object",
97
+ properties: {
98
+ enabled: {
99
+ type: "boolean",
100
+ default: true,
101
+ description: "Master switch."
102
+ },
103
+ defaultCommand: {
104
+ type: "string",
105
+ default: "npx vitest run",
106
+ description: "Command used when flake_detect.command is omitted."
107
+ },
108
+ maxRuns: {
109
+ type: "number",
110
+ minimum: 1,
111
+ maximum: 100,
112
+ default: 20,
113
+ description: "Hard upper bound for the runs parameter."
114
+ },
115
+ timeoutMs: {
116
+ type: "number",
117
+ minimum: 1e3,
118
+ default: 12e4,
119
+ description: "Per-run timeout in milliseconds."
120
+ }
121
+ }
122
+ },
123
+ setup(api) {
124
+ state.invocationCount = 0;
125
+ state.runCount = 0;
126
+ state.errorCount = 0;
127
+ state.lastResult = null;
128
+ const cfg = readConfig(api.config.extensions?.["test-flake-detector"]);
129
+ api.tools.register({
130
+ name: "flake_detect",
131
+ description: "Runs a test command multiple times and reports tests that fail non-deterministically. Provide testPattern and optionally runs (default 5) and command.",
132
+ inputSchema: {
133
+ type: "object",
134
+ properties: {
135
+ testPattern: {
136
+ type: "string",
137
+ description: "Test file pattern or path to pass to the test command."
138
+ },
139
+ runs: {
140
+ type: "number",
141
+ minimum: 1,
142
+ maximum: 100,
143
+ default: 5,
144
+ description: "How many times to run the command."
145
+ },
146
+ command: {
147
+ type: "string",
148
+ description: "Custom test command. Defaults to the configured defaultCommand."
149
+ }
150
+ }
151
+ },
152
+ permission: "confirm",
153
+ category: "Diagnostics",
154
+ mutating: false,
155
+ async execute(input) {
156
+ if (!cfg.enabled) {
157
+ return { ok: false, error: "test-flake-detector is disabled" };
158
+ }
159
+ const requestedRuns = typeof input.runs === "number" && input.runs >= 1 ? Math.min(Math.floor(input.runs), cfg.maxRuns) : 5;
160
+ const command = buildCommand(
161
+ typeof input.command === "string" && input.command.trim().length > 0 ? input.command : cfg.defaultCommand,
162
+ input.testPattern
163
+ );
164
+ state.invocationCount += 1;
165
+ const start = Date.now();
166
+ const records = /* @__PURE__ */ new Map();
167
+ let runsCompleted = 0;
168
+ const runErrors = [];
169
+ for (let run = 1; run <= requestedRuns; run += 1) {
170
+ const { output, error } = runOnce(command, cfg.timeoutMs);
171
+ state.runCount += 1;
172
+ if (error) {
173
+ state.errorCount += 1;
174
+ runErrors.push(`run ${run}: ${error}`);
175
+ }
176
+ const parsed = parseTestOutput(output);
177
+ runsCompleted += 1;
178
+ for (const name of parsed.passed) {
179
+ const rec = records.get(name) ?? { test: name, passCount: 0, failCount: 0 };
180
+ rec.passCount += 1;
181
+ records.set(name, rec);
182
+ }
183
+ for (const name of parsed.failed) {
184
+ const rec = records.get(name) ?? { test: name, passCount: 0, failCount: 0 };
185
+ rec.failCount += 1;
186
+ records.set(name, rec);
187
+ }
188
+ }
189
+ const all = Array.from(records.values());
190
+ const flakyTests = all.filter((r) => r.passCount > 0 && r.failCount > 0);
191
+ const alwaysFailing = all.filter((r) => r.passCount === 0 && r.failCount > 0);
192
+ const alwaysPassing = all.filter((r) => r.passCount > 0 && r.failCount === 0);
193
+ const durationMs = Date.now() - start;
194
+ state.lastResult = {
195
+ runsRequested: requestedRuns,
196
+ runsCompleted,
197
+ flakyTests,
198
+ alwaysFailing,
199
+ alwaysPassing,
200
+ durationMs,
201
+ when: (/* @__PURE__ */ new Date()).toISOString()
202
+ };
203
+ return {
204
+ ok: true,
205
+ command,
206
+ runsRequested: requestedRuns,
207
+ runsCompleted,
208
+ durationMs,
209
+ totalTestsObserved: all.length,
210
+ flakyCount: flakyTests.length,
211
+ flakyTests,
212
+ alwaysFailing,
213
+ alwaysPassing,
214
+ runErrors: runErrors.length > 0 ? runErrors : void 0
215
+ };
216
+ }
217
+ });
218
+ api.tools.register({
219
+ name: "flake_status",
220
+ description: "Reports test-flake-detector state: config and per-session counters.",
221
+ inputSchema: { type: "object", properties: {} },
222
+ permission: "auto",
223
+ category: "Diagnostics",
224
+ mutating: false,
225
+ async execute() {
226
+ return {
227
+ ok: true,
228
+ enabled: cfg.enabled,
229
+ defaultCommand: cfg.defaultCommand,
230
+ maxRuns: cfg.maxRuns,
231
+ timeoutMs: cfg.timeoutMs,
232
+ counters: {
233
+ invocations: state.invocationCount,
234
+ runs: state.runCount,
235
+ errors: state.errorCount
236
+ },
237
+ lastResult: state.lastResult
238
+ };
239
+ }
240
+ });
241
+ api.log.info("test-flake-detector plugin loaded", {
242
+ version: "0.1.0",
243
+ defaultCommand: cfg.defaultCommand,
244
+ maxRuns: cfg.maxRuns
245
+ });
246
+ },
247
+ teardown(api) {
248
+ const final = {
249
+ invocations: state.invocationCount,
250
+ runs: state.runCount,
251
+ errors: state.errorCount
252
+ };
253
+ state.invocationCount = 0;
254
+ state.runCount = 0;
255
+ state.errorCount = 0;
256
+ state.lastResult = null;
257
+ api.log.info("test-flake-detector: teardown complete", { final });
258
+ },
259
+ async health() {
260
+ return {
261
+ ok: true,
262
+ message: state.lastResult ? `test-flake-detector: ${state.runCount} run(s), last detection found ${state.lastResult.flakyTests.length} flaky test(s)` : `test-flake-detector: ${state.invocationCount} invocation(s), ${state.runCount} run(s)`,
263
+ counters: {
264
+ invocations: state.invocationCount,
265
+ runs: state.runCount,
266
+ errors: state.errorCount
267
+ },
268
+ lastResult: state.lastResult
269
+ };
270
+ }
271
+ };
272
+ var test_flake_detector_default = plugin;
273
+
274
+ export { test_flake_detector_default as default };
@@ -0,0 +1,32 @@
1
+ import { Plugin } from '@wrongstack/core';
2
+
3
+ /**
4
+ * test-generator plugin — generates a Vitest test skeleton from a source file
5
+ * using regex-based export detection.
6
+ *
7
+ * Tool registered:
8
+ * - generate_unit_tests : Read a source file and return a test file skeleton.
9
+ *
10
+ * No hooks are registered.
11
+ *
12
+ * Config (`config.extensions['test-generator']`):
13
+ *
14
+ * ```jsonc
15
+ * {
16
+ * "enabled": true,
17
+ * "framework": "vitest",
18
+ * "testSuffix": ".test",
19
+ * "includeImports": true
20
+ * }
21
+ * ```
22
+ *
23
+ * @public
24
+ */
25
+
26
+ interface DetectedExport {
27
+ name: string;
28
+ kind: 'function' | 'arrow' | 'class' | 'named';
29
+ }
30
+ declare const plugin: Plugin;
31
+
32
+ export { type DetectedExport, plugin as default };