@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.
- package/README.md +30 -4
- package/dist/accessibility-auditor.d.ts +40 -0
- package/dist/accessibility-auditor.js +411 -0
- package/dist/agent-handoff.d.ts +37 -0
- package/dist/agent-handoff.js +298 -0
- package/dist/api-compatibility-gate.d.ts +38 -0
- package/dist/api-compatibility-gate.js +357 -0
- package/dist/auto-escalate.d.ts +1 -1
- package/dist/auto-i18n-extractor.d.ts +36 -0
- package/dist/auto-i18n-extractor.js +335 -0
- package/dist/branch-guard.d.ts +6 -5
- package/dist/branch-guard.js +54 -4
- package/dist/checkpoint.js +18 -0
- package/dist/code-metrics.d.ts +31 -0
- package/dist/code-metrics.js +338 -0
- package/dist/commit-validator.js +67 -12
- package/dist/context-pins.js +4 -4
- package/dist/cost-tracker.d.ts +1 -1
- package/dist/cost-tracker.js +58 -19
- package/dist/cron.js +18 -8
- package/dist/dead-code-detector.d.ts +34 -0
- package/dist/dead-code-detector.js +354 -0
- package/dist/dep-guard.js +47 -6
- package/dist/dependency-vulnerability-gate.d.ts +35 -0
- package/dist/dependency-vulnerability-gate.js +308 -0
- package/dist/diff-summary.js +97 -10
- package/dist/doc-sync-guard.d.ts +33 -0
- package/dist/doc-sync-guard.js +223 -0
- package/dist/duplicate-code-detector.d.ts +33 -0
- package/dist/duplicate-code-detector.js +384 -0
- package/dist/feature-flag-tracker.d.ts +38 -0
- package/dist/feature-flag-tracker.js +316 -0
- package/dist/file-watcher.js +85 -36
- package/dist/format-on-save.js +99 -18
- package/dist/import-organizer.js +73 -14
- package/dist/index.d.ts +27 -0
- package/dist/index.js +11205 -2106
- package/dist/interface-contract-guard.d.ts +37 -0
- package/dist/interface-contract-guard.js +302 -0
- package/dist/knowledge-graph.d.ts +45 -0
- package/dist/knowledge-graph.js +325 -0
- package/dist/license-audit-gate.d.ts +34 -0
- package/dist/license-audit-gate.js +260 -0
- package/dist/llm-cache.js +5 -0
- package/dist/loop-breaker.d.ts +0 -38
- package/dist/loop-breaker.js +209 -8
- package/dist/migration-planner.d.ts +30 -0
- package/dist/migration-planner.js +349 -0
- package/dist/model-router.js +5 -0
- package/dist/performance-regression-gate.d.ts +33 -0
- package/dist/performance-regression-gate.js +315 -0
- package/dist/plugin-stack-observer.d.ts +35 -0
- package/dist/plugin-stack-observer.js +138 -0
- package/dist/pr-drafter.d.ts +35 -0
- package/dist/pr-drafter.js +334 -0
- package/dist/prompt-firewall.js +5 -0
- package/dist/refactor-suggester.d.ts +38 -0
- package/dist/refactor-suggester.js +382 -0
- package/dist/release-notes-generator.d.ts +27 -0
- package/dist/release-notes-generator.js +209 -0
- package/dist/schema-evolution-guard.d.ts +42 -0
- package/dist/schema-evolution-guard.js +319 -0
- package/dist/security-hotspot-scanner.d.ts +30 -0
- package/dist/security-hotspot-scanner.js +402 -0
- package/dist/semantic-search-indexer.d.ts +38 -0
- package/dist/semantic-search-indexer.js +436 -0
- package/dist/shell-check.js +38 -3
- package/dist/smart-rename.d.ts +26 -0
- package/dist/smart-rename.js +170 -0
- package/dist/spec-linker.js +273 -133
- package/dist/test-coverage-gate.d.ts +37 -0
- package/dist/test-coverage-gate.js +263 -0
- package/dist/test-flake-detector.d.ts +27 -0
- package/dist/test-flake-detector.js +274 -0
- package/dist/test-generator.d.ts +32 -0
- package/dist/test-generator.js +243 -0
- package/dist/test-runner-gate.js +154 -22
- package/dist/todo-listener.d.ts +2 -2
- package/dist/todo-listener.js +5 -5
- package/dist/token-throttle.js +5 -0
- package/dist/type-gate.d.ts +37 -0
- package/dist/type-gate.js +311 -0
- package/package.json +112 -4
- package/LICENSE +0 -21
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Plugin } from '@wrongstack/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* interface-contract-guard plugin — checks TypeScript interface declarations
|
|
5
|
+
* for apparent implementers, warning when an interface looks unimplemented.
|
|
6
|
+
*
|
|
7
|
+
* Tools registered:
|
|
8
|
+
* - check_interface_contracts : Scan TypeScript files for unimplemented interfaces.
|
|
9
|
+
* - interface_contract_status : Report config + counters.
|
|
10
|
+
*
|
|
11
|
+
* Hooks registered:
|
|
12
|
+
* - PostToolUse with matcher `write|edit` to .ts/.tsx files, warning when the
|
|
13
|
+
* changed file contains interface declarations because implementers may need
|
|
14
|
+
* updating.
|
|
15
|
+
*
|
|
16
|
+
* Config (`config.extensions['interface-contract-guard']`):
|
|
17
|
+
*
|
|
18
|
+
* ```jsonc
|
|
19
|
+
* {
|
|
20
|
+
* "enabled": true,
|
|
21
|
+
* "extensions": [".ts", ".tsx"],
|
|
22
|
+
* "maxFindings": 50
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
interface InterfaceContractFinding {
|
|
30
|
+
interfaceName: string;
|
|
31
|
+
file: string;
|
|
32
|
+
line: number;
|
|
33
|
+
message: string;
|
|
34
|
+
}
|
|
35
|
+
declare const plugin: Plugin;
|
|
36
|
+
|
|
37
|
+
export { type InterfaceContractFinding, plugin as default };
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { readFileSync, existsSync, statSync, readdirSync } from 'fs';
|
|
2
|
+
import { resolve, isAbsolute, relative, extname } from 'path';
|
|
3
|
+
|
|
4
|
+
// src/interface-contract-guard/index.ts
|
|
5
|
+
var API_VERSION = "^0.1.10";
|
|
6
|
+
var state = {
|
|
7
|
+
scanCount: 0,
|
|
8
|
+
findingCount: 0,
|
|
9
|
+
hookInvocationCount: 0,
|
|
10
|
+
warningCount: 0,
|
|
11
|
+
errorCount: 0,
|
|
12
|
+
hookUnregister: null
|
|
13
|
+
};
|
|
14
|
+
var DEFAULTS = {
|
|
15
|
+
enabled: false,
|
|
16
|
+
extensions: [".ts", ".tsx"],
|
|
17
|
+
maxFindings: 50
|
|
18
|
+
};
|
|
19
|
+
function readConfig(raw) {
|
|
20
|
+
if (!raw || typeof raw !== "object") return { ...DEFAULTS };
|
|
21
|
+
const r = raw;
|
|
22
|
+
return {
|
|
23
|
+
enabled: r["enabled"] !== false,
|
|
24
|
+
extensions: Array.isArray(r["extensions"]) ? r["extensions"].filter((x) => typeof x === "string") : DEFAULTS.extensions,
|
|
25
|
+
maxFindings: typeof r["maxFindings"] === "number" && r["maxFindings"] >= 1 && r["maxFindings"] <= 500 ? r["maxFindings"] : DEFAULTS.maxFindings
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function withinProject(p) {
|
|
29
|
+
if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
|
|
30
|
+
const root = process.cwd();
|
|
31
|
+
const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
|
|
32
|
+
const rel = relative(root, resolved);
|
|
33
|
+
if (rel === "" || rel === ".") return true;
|
|
34
|
+
if (rel.startsWith("..")) return false;
|
|
35
|
+
if (isAbsolute(rel)) return false;
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
function normalizeExtensions(exts) {
|
|
39
|
+
return exts.map((e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`);
|
|
40
|
+
}
|
|
41
|
+
function matchesExtension(p, exts) {
|
|
42
|
+
return exts.includes(extname(p).toLowerCase());
|
|
43
|
+
}
|
|
44
|
+
function collectSourceFiles(root, exts) {
|
|
45
|
+
const files = [];
|
|
46
|
+
if (!existsSync(root)) return files;
|
|
47
|
+
const s = statSync(root);
|
|
48
|
+
if (s.isFile()) {
|
|
49
|
+
if (matchesExtension(root, exts)) files.push(root);
|
|
50
|
+
return files;
|
|
51
|
+
}
|
|
52
|
+
if (!s.isDirectory()) return files;
|
|
53
|
+
function walk(dir) {
|
|
54
|
+
let entries;
|
|
55
|
+
try {
|
|
56
|
+
entries = readdirSync(dir);
|
|
57
|
+
} catch {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
if (entry === "node_modules" || entry === "dist" || entry === ".git" || entry === "coverage") continue;
|
|
62
|
+
const full = resolve(dir, entry);
|
|
63
|
+
let st;
|
|
64
|
+
try {
|
|
65
|
+
st = statSync(full);
|
|
66
|
+
} catch {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (st.isDirectory()) {
|
|
70
|
+
walk(full);
|
|
71
|
+
} else if (st.isFile() && matchesExtension(full, exts)) {
|
|
72
|
+
files.push(full);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
walk(root);
|
|
77
|
+
return files;
|
|
78
|
+
}
|
|
79
|
+
function toPosix(p) {
|
|
80
|
+
return p.replace(/\\/g, "/");
|
|
81
|
+
}
|
|
82
|
+
function relativePath(p) {
|
|
83
|
+
return toPosix(relative(process.cwd(), p));
|
|
84
|
+
}
|
|
85
|
+
var INTERFACE_RE = /(?:export\s+)?interface\s+([A-Za-z_$][A-Za-z0-9_$]*)/g;
|
|
86
|
+
function extractInterfaceNames(content) {
|
|
87
|
+
const names = [];
|
|
88
|
+
let m;
|
|
89
|
+
INTERFACE_RE.lastIndex = 0;
|
|
90
|
+
while ((m = INTERFACE_RE.exec(content)) !== null) {
|
|
91
|
+
names.push({ name: m[1], line: content.slice(0, m.index).split(/\r?\n/).length });
|
|
92
|
+
}
|
|
93
|
+
return names;
|
|
94
|
+
}
|
|
95
|
+
function hasImplementer(name, contents) {
|
|
96
|
+
const implRe = new RegExp(`(?:implements|satisfies|\\bas)\\s+${name}\\b`, "g");
|
|
97
|
+
for (const content of contents) {
|
|
98
|
+
if (implRe.test(content)) return true;
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
function scanPath(rawPath, cfg) {
|
|
103
|
+
const root = process.cwd();
|
|
104
|
+
const resolved = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);
|
|
105
|
+
const exts = normalizeExtensions(cfg.extensions);
|
|
106
|
+
const files = collectSourceFiles(resolved, exts);
|
|
107
|
+
const contents = [];
|
|
108
|
+
for (const p of files) {
|
|
109
|
+
try {
|
|
110
|
+
contents.push({ path: p, content: readFileSync(p, "utf-8") });
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const allContentStrings = contents.map((c) => c.content);
|
|
115
|
+
const findings = [];
|
|
116
|
+
for (const { path, content } of contents) {
|
|
117
|
+
for (const { name, line } of extractInterfaceNames(content)) {
|
|
118
|
+
if (!hasImplementer(name, allContentStrings)) {
|
|
119
|
+
findings.push({
|
|
120
|
+
interfaceName: name,
|
|
121
|
+
file: relativePath(path),
|
|
122
|
+
line,
|
|
123
|
+
message: `interface "${name}" is declared but has no visible implementer`
|
|
124
|
+
});
|
|
125
|
+
if (findings.length >= cfg.maxFindings) break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (findings.length >= cfg.maxFindings) break;
|
|
129
|
+
}
|
|
130
|
+
return { findings, scannedFiles: contents.length };
|
|
131
|
+
}
|
|
132
|
+
var plugin = {
|
|
133
|
+
name: "interface-contract-guard",
|
|
134
|
+
version: "0.1.0",
|
|
135
|
+
description: "Checks TypeScript interfaces for visible implementers and warns about contract drift",
|
|
136
|
+
apiVersion: API_VERSION,
|
|
137
|
+
capabilities: { tools: true, hooks: true },
|
|
138
|
+
defaultConfig: { ...DEFAULTS },
|
|
139
|
+
configSchema: {
|
|
140
|
+
type: "object",
|
|
141
|
+
properties: {
|
|
142
|
+
enabled: { type: "boolean", default: true, description: "Master switch." },
|
|
143
|
+
extensions: {
|
|
144
|
+
type: "array",
|
|
145
|
+
items: { type: "string" },
|
|
146
|
+
default: [".ts", ".tsx"],
|
|
147
|
+
description: "File extensions to scan."
|
|
148
|
+
},
|
|
149
|
+
maxFindings: {
|
|
150
|
+
type: "number",
|
|
151
|
+
minimum: 1,
|
|
152
|
+
maximum: 500,
|
|
153
|
+
default: 50,
|
|
154
|
+
description: "Maximum findings reported per scan."
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
setup(api) {
|
|
159
|
+
state.scanCount = 0;
|
|
160
|
+
state.findingCount = 0;
|
|
161
|
+
state.hookInvocationCount = 0;
|
|
162
|
+
state.warningCount = 0;
|
|
163
|
+
state.errorCount = 0;
|
|
164
|
+
if (state.hookUnregister) {
|
|
165
|
+
try {
|
|
166
|
+
state.hookUnregister();
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
state.hookUnregister = null;
|
|
170
|
+
}
|
|
171
|
+
const cfg = readConfig(api.config.extensions?.["interface-contract-guard"]);
|
|
172
|
+
const hook = (input) => {
|
|
173
|
+
if (!cfg.enabled) return;
|
|
174
|
+
if (input.toolResult?.isError) return;
|
|
175
|
+
const inp = input.toolInput ?? {};
|
|
176
|
+
const sourcePath = inp["path"];
|
|
177
|
+
if (!sourcePath || typeof sourcePath !== "string") return;
|
|
178
|
+
if (!withinProject(sourcePath)) return;
|
|
179
|
+
const exts = normalizeExtensions(cfg.extensions);
|
|
180
|
+
if (!matchesExtension(sourcePath, exts)) return;
|
|
181
|
+
state.hookInvocationCount += 1;
|
|
182
|
+
const resolved = resolve(process.cwd(), sourcePath);
|
|
183
|
+
let content;
|
|
184
|
+
try {
|
|
185
|
+
content = readFileSync(resolved, "utf-8");
|
|
186
|
+
} catch {
|
|
187
|
+
state.errorCount += 1;
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const interfaces = extractInterfaceNames(content);
|
|
191
|
+
if (interfaces.length === 0) return;
|
|
192
|
+
state.warningCount += 1;
|
|
193
|
+
const names = interfaces.map((i) => i.name).join(", ");
|
|
194
|
+
return {
|
|
195
|
+
additionalContext: `
|
|
196
|
+
\u{1F6E1}\uFE0F interface-contract-guard: ${sourcePath} declares interface(s): ${names}.
|
|
197
|
+
If you changed member shapes, search the project for implementers/\`satisfies\`/\`as\` usages and update them accordingly.`
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
|
|
201
|
+
api.tools.register({
|
|
202
|
+
name: "check_interface_contracts",
|
|
203
|
+
description: "Scan TypeScript files for interface declarations that have no visible implementer (implements / as / satisfies).",
|
|
204
|
+
inputSchema: {
|
|
205
|
+
type: "object",
|
|
206
|
+
properties: {
|
|
207
|
+
path: { type: "string", default: ".", description: "File or directory path to scan." }
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
permission: "auto",
|
|
211
|
+
category: "Diagnostics",
|
|
212
|
+
mutating: false,
|
|
213
|
+
async execute(input) {
|
|
214
|
+
if (!cfg.enabled) return { ok: false, error: "interface-contract-guard is disabled" };
|
|
215
|
+
const rawPath = typeof input.path === "string" ? input.path : ".";
|
|
216
|
+
if (!withinProject(rawPath)) {
|
|
217
|
+
return { ok: false, error: "path is outside the project root" };
|
|
218
|
+
}
|
|
219
|
+
state.scanCount += 1;
|
|
220
|
+
let result;
|
|
221
|
+
try {
|
|
222
|
+
result = scanPath(rawPath, cfg);
|
|
223
|
+
} catch (err) {
|
|
224
|
+
state.errorCount += 1;
|
|
225
|
+
return { ok: false, error: String(err) };
|
|
226
|
+
}
|
|
227
|
+
state.findingCount += result.findings.length;
|
|
228
|
+
return {
|
|
229
|
+
ok: true,
|
|
230
|
+
path: relativePath(resolve(process.cwd(), rawPath)),
|
|
231
|
+
scannedFiles: result.scannedFiles,
|
|
232
|
+
findings: result.findings
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
api.tools.register({
|
|
237
|
+
name: "interface_contract_status",
|
|
238
|
+
description: "Reports interface-contract-guard state: config + counters.",
|
|
239
|
+
inputSchema: { type: "object", properties: {} },
|
|
240
|
+
permission: "auto",
|
|
241
|
+
category: "Diagnostics",
|
|
242
|
+
mutating: false,
|
|
243
|
+
async execute() {
|
|
244
|
+
return {
|
|
245
|
+
ok: true,
|
|
246
|
+
enabled: cfg.enabled,
|
|
247
|
+
extensions: cfg.extensions,
|
|
248
|
+
maxFindings: cfg.maxFindings,
|
|
249
|
+
counters: {
|
|
250
|
+
scans: state.scanCount,
|
|
251
|
+
findings: state.findingCount,
|
|
252
|
+
hookInvocations: state.hookInvocationCount,
|
|
253
|
+
warnings: state.warningCount,
|
|
254
|
+
errors: state.errorCount
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
api.log.info("interface-contract-guard plugin loaded", {
|
|
260
|
+
version: "0.1.0",
|
|
261
|
+
extensions: cfg.extensions
|
|
262
|
+
});
|
|
263
|
+
},
|
|
264
|
+
teardown(api) {
|
|
265
|
+
if (state.hookUnregister) {
|
|
266
|
+
try {
|
|
267
|
+
state.hookUnregister();
|
|
268
|
+
} catch {
|
|
269
|
+
}
|
|
270
|
+
state.hookUnregister = null;
|
|
271
|
+
}
|
|
272
|
+
const final = {
|
|
273
|
+
scans: state.scanCount,
|
|
274
|
+
findings: state.findingCount,
|
|
275
|
+
hookInvocations: state.hookInvocationCount,
|
|
276
|
+
warnings: state.warningCount,
|
|
277
|
+
errors: state.errorCount
|
|
278
|
+
};
|
|
279
|
+
state.scanCount = 0;
|
|
280
|
+
state.findingCount = 0;
|
|
281
|
+
state.hookInvocationCount = 0;
|
|
282
|
+
state.warningCount = 0;
|
|
283
|
+
state.errorCount = 0;
|
|
284
|
+
api.log.info("interface-contract-guard: teardown complete", { final });
|
|
285
|
+
},
|
|
286
|
+
async health() {
|
|
287
|
+
return {
|
|
288
|
+
ok: state.errorCount === 0,
|
|
289
|
+
message: state.errorCount ? `interface-contract-guard: ${state.errorCount} error(s)` : `interface-contract-guard: ${state.scanCount} scan(s), ${state.findingCount} finding(s)`,
|
|
290
|
+
counters: {
|
|
291
|
+
scans: state.scanCount,
|
|
292
|
+
findings: state.findingCount,
|
|
293
|
+
hookInvocations: state.hookInvocationCount,
|
|
294
|
+
warnings: state.warningCount,
|
|
295
|
+
errors: state.errorCount
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
var interface_contract_guard_default = plugin;
|
|
301
|
+
|
|
302
|
+
export { interface_contract_guard_default as default };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Plugin } from '@wrongstack/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* knowledge-graph plugin — accumulates structured facts about the project
|
|
5
|
+
* across sessions.
|
|
6
|
+
*
|
|
7
|
+
* The plugin gives the agent three tools:
|
|
8
|
+
* - `kg_add_fact` — add a (subject, relation, object) triple
|
|
9
|
+
* - `kg_query` — query facts by subject, relation, or object
|
|
10
|
+
* - `kg_remove_fact` — remove a fact by id
|
|
11
|
+
* - `kg_status` — counters + persisted path
|
|
12
|
+
*
|
|
13
|
+
* Facts persist in a project-local JSON file (default:
|
|
14
|
+
* `<projectDir>/.wrongstack/knowledge-graph.json`) so they survive across
|
|
15
|
+
* sessions. Optionally the plugin can contribute a compact summary of
|
|
16
|
+
* relevant facts to the system prompt for the current task.
|
|
17
|
+
*
|
|
18
|
+
* Config (`config.extensions['knowledge-graph']`):
|
|
19
|
+
*
|
|
20
|
+
* ```jsonc
|
|
21
|
+
* {
|
|
22
|
+
* "enabled": true,
|
|
23
|
+
* "filePath": ".wrongstack/knowledge-graph.json",
|
|
24
|
+
* "maxFacts": 200,
|
|
25
|
+
* "maxFactChars": 300,
|
|
26
|
+
* "contributeToSystemPrompt": true,
|
|
27
|
+
* "contributeMaxChars": 1500
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @public
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
interface Fact {
|
|
35
|
+
id: string;
|
|
36
|
+
subject: string;
|
|
37
|
+
relation: string;
|
|
38
|
+
object: string;
|
|
39
|
+
source: string | null;
|
|
40
|
+
confidence: 'low' | 'medium' | 'high';
|
|
41
|
+
createdAt: string;
|
|
42
|
+
}
|
|
43
|
+
declare const plugin: Plugin;
|
|
44
|
+
|
|
45
|
+
export { type Fact, plugin as default };
|