@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,319 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { basename, isAbsolute, resolve, relative } from 'path';
|
|
3
|
+
|
|
4
|
+
// src/schema-evolution-guard/index.ts
|
|
5
|
+
var API_VERSION = "^0.1.10";
|
|
6
|
+
var state = {
|
|
7
|
+
invocationCount: 0,
|
|
8
|
+
scannedCount: 0,
|
|
9
|
+
skippedCount: 0,
|
|
10
|
+
hitCount: 0,
|
|
11
|
+
warningCount: 0,
|
|
12
|
+
blockedCount: 0,
|
|
13
|
+
lastFinding: null,
|
|
14
|
+
hookUnregister: null
|
|
15
|
+
};
|
|
16
|
+
var DEFAULT_FILE_PATTERNS = [
|
|
17
|
+
"*.prisma",
|
|
18
|
+
"*migration*.sql",
|
|
19
|
+
"*openapi*.yaml",
|
|
20
|
+
"*openapi*.json",
|
|
21
|
+
"*schema*.ts"
|
|
22
|
+
];
|
|
23
|
+
var DEFAULTS = {
|
|
24
|
+
enabled: true,
|
|
25
|
+
failSeverity: "warn",
|
|
26
|
+
maxFindings: 5,
|
|
27
|
+
filePatterns: [...DEFAULT_FILE_PATTERNS]
|
|
28
|
+
};
|
|
29
|
+
function readConfig(raw) {
|
|
30
|
+
if (!raw || typeof raw !== "object") return { ...DEFAULTS };
|
|
31
|
+
const r = raw;
|
|
32
|
+
return {
|
|
33
|
+
enabled: r["enabled"] !== false,
|
|
34
|
+
failSeverity: r["failSeverity"] === "block" ? "block" : DEFAULTS.failSeverity,
|
|
35
|
+
maxFindings: typeof r["maxFindings"] === "number" && Number.isFinite(r["maxFindings"]) && r["maxFindings"] >= 1 && r["maxFindings"] <= 50 ? Math.floor(r["maxFindings"]) : DEFAULTS.maxFindings,
|
|
36
|
+
filePatterns: Array.isArray(r["filePatterns"]) ? r["filePatterns"].filter((x) => typeof x === "string") : DEFAULTS.filePatterns
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function patternToRegExp(pattern) {
|
|
40
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
41
|
+
return new RegExp(`^${escaped}$`, "i");
|
|
42
|
+
}
|
|
43
|
+
function matchesAnyPattern(fileName, patterns) {
|
|
44
|
+
return patterns.some((p) => patternToRegExp(p).test(fileName));
|
|
45
|
+
}
|
|
46
|
+
function fileKind(fileName) {
|
|
47
|
+
const n = fileName.toLowerCase();
|
|
48
|
+
if (n.endsWith(".prisma")) return "prisma";
|
|
49
|
+
if (n.endsWith(".sql")) return "sql";
|
|
50
|
+
if (n.endsWith(".yaml") || n.endsWith(".yml") || n.endsWith(".json")) return "openapi";
|
|
51
|
+
if (n.endsWith(".ts")) return "typescript-schema";
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
function withinProject(p) {
|
|
55
|
+
if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
|
|
56
|
+
const root = process.cwd();
|
|
57
|
+
const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
|
|
58
|
+
const rel = relative(root, resolved);
|
|
59
|
+
if (rel === "" || rel === ".") return true;
|
|
60
|
+
if (rel.startsWith("..")) return false;
|
|
61
|
+
if (isAbsolute(rel)) return false;
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
function findIssues(content, kind, maxFindings) {
|
|
65
|
+
const findings = [];
|
|
66
|
+
const lines = content.split(/\r?\n/);
|
|
67
|
+
let inOpenApiRequiredList = false;
|
|
68
|
+
for (let i = 0; i < lines.length && findings.length < maxFindings; i++) {
|
|
69
|
+
const line = lines[i];
|
|
70
|
+
const lineNumber = i + 1;
|
|
71
|
+
switch (kind) {
|
|
72
|
+
case "sql": {
|
|
73
|
+
if (/DROP\s+TABLE\b/i.test(line)) {
|
|
74
|
+
findings.push(`DROP TABLE at line ${lineNumber}`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (/DROP\s+COLUMN\b/i.test(line)) {
|
|
78
|
+
findings.push(`DROP COLUMN at line ${lineNumber}`);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (/\bNOT\s+NULL\b/i.test(line) && !/DEFAULT\b/i.test(line) && !/DROP\b/i.test(line)) {
|
|
82
|
+
findings.push(`NOT NULL added without default at line ${lineNumber}`);
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case "prisma": {
|
|
87
|
+
const match = line.match(/^\s+([A-Za-z_]\w*)\s+([A-Za-z_]\w+)(\?)?(\s+@.*)?$/);
|
|
88
|
+
if (match && !match[3] && !match[4]?.includes("@default")) {
|
|
89
|
+
findings.push(
|
|
90
|
+
`required field without default at line ${lineNumber}: ${match[1]} (${match[2]})`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case "openapi": {
|
|
96
|
+
const requiredMatch = line.match(/\b["']?required["']?\s*:\s*(.*)/i);
|
|
97
|
+
if (requiredMatch) {
|
|
98
|
+
const rest = (requiredMatch[1] ?? "").trim();
|
|
99
|
+
if (rest.startsWith("[")) {
|
|
100
|
+
const close = rest.indexOf("]");
|
|
101
|
+
const inner = close >= 0 ? rest.slice(1, close) : rest.slice(1);
|
|
102
|
+
const items = inner.split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
103
|
+
for (const item of items) {
|
|
104
|
+
if (findings.length >= maxFindings) break;
|
|
105
|
+
findings.push(
|
|
106
|
+
`required field added to OpenAPI schema at line ${lineNumber}: ${item}`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
inOpenApiRequiredList = false;
|
|
110
|
+
} else if (rest === "") {
|
|
111
|
+
inOpenApiRequiredList = true;
|
|
112
|
+
} else {
|
|
113
|
+
inOpenApiRequiredList = false;
|
|
114
|
+
}
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (inOpenApiRequiredList) {
|
|
118
|
+
const listMatch = line.match(/^\s*-\s*(\w+)/);
|
|
119
|
+
if (listMatch) {
|
|
120
|
+
findings.push(
|
|
121
|
+
`required field added to OpenAPI schema at line ${lineNumber}: ${listMatch[1]}`
|
|
122
|
+
);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (line.trim() !== "" && !/^\s*#/.test(line)) {
|
|
126
|
+
inOpenApiRequiredList = false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
case "typescript-schema": {
|
|
132
|
+
const match = line.match(/^\s*(\w+)(\?)?\s*:\s*([^=;\n]+?);?$/);
|
|
133
|
+
if (match && !match[2] && !match[3]?.includes("=")) {
|
|
134
|
+
findings.push(`required field without default at line ${lineNumber}: ${match[1]}`);
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return findings;
|
|
141
|
+
}
|
|
142
|
+
var plugin = {
|
|
143
|
+
name: "schema-evolution-guard",
|
|
144
|
+
version: "0.1.0",
|
|
145
|
+
description: "PostToolUse hook that guards database/API schema changes by detecting destructive patterns in schema files",
|
|
146
|
+
apiVersion: API_VERSION,
|
|
147
|
+
capabilities: { tools: true, hooks: true },
|
|
148
|
+
defaultConfig: { ...DEFAULTS },
|
|
149
|
+
configSchema: {
|
|
150
|
+
type: "object",
|
|
151
|
+
properties: {
|
|
152
|
+
enabled: {
|
|
153
|
+
type: "boolean",
|
|
154
|
+
default: true,
|
|
155
|
+
description: "Master switch."
|
|
156
|
+
},
|
|
157
|
+
failSeverity: {
|
|
158
|
+
type: "string",
|
|
159
|
+
enum: ["warn", "block"],
|
|
160
|
+
default: "warn",
|
|
161
|
+
description: "warn = inject additionalContext; block = refuse the mutating tool."
|
|
162
|
+
},
|
|
163
|
+
maxFindings: {
|
|
164
|
+
type: "number",
|
|
165
|
+
minimum: 1,
|
|
166
|
+
maximum: 50,
|
|
167
|
+
default: 5,
|
|
168
|
+
description: "Maximum destructive findings reported per file."
|
|
169
|
+
},
|
|
170
|
+
filePatterns: {
|
|
171
|
+
type: "array",
|
|
172
|
+
items: { type: "string" },
|
|
173
|
+
default: DEFAULT_FILE_PATTERNS,
|
|
174
|
+
description: "Basename patterns that trigger the scan."
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
setup(api) {
|
|
179
|
+
state.invocationCount = 0;
|
|
180
|
+
state.scannedCount = 0;
|
|
181
|
+
state.skippedCount = 0;
|
|
182
|
+
state.hitCount = 0;
|
|
183
|
+
state.warningCount = 0;
|
|
184
|
+
state.blockedCount = 0;
|
|
185
|
+
state.lastFinding = null;
|
|
186
|
+
if (state.hookUnregister) {
|
|
187
|
+
try {
|
|
188
|
+
state.hookUnregister();
|
|
189
|
+
} catch {
|
|
190
|
+
}
|
|
191
|
+
state.hookUnregister = null;
|
|
192
|
+
}
|
|
193
|
+
const cfg = readConfig(api.config.extensions?.["schema-evolution-guard"]);
|
|
194
|
+
const hook = (input) => {
|
|
195
|
+
if (!cfg.enabled) return;
|
|
196
|
+
if (input.toolResult?.isError) return;
|
|
197
|
+
const toolInput = input.toolInput ?? {};
|
|
198
|
+
const filePath = typeof toolInput["path"] === "string" ? toolInput["path"] : void 0;
|
|
199
|
+
if (!filePath) return;
|
|
200
|
+
state.invocationCount += 1;
|
|
201
|
+
const fileName = basename(filePath);
|
|
202
|
+
if (!matchesAnyPattern(fileName, cfg.filePatterns)) {
|
|
203
|
+
state.skippedCount += 1;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const kind = fileKind(fileName);
|
|
207
|
+
if (!kind) {
|
|
208
|
+
state.skippedCount += 1;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
let content;
|
|
212
|
+
if (typeof toolInput["content"] === "string") {
|
|
213
|
+
content = toolInput["content"];
|
|
214
|
+
} else if (withinProject(filePath)) {
|
|
215
|
+
try {
|
|
216
|
+
content = readFileSync(filePath, "utf-8");
|
|
217
|
+
} catch {
|
|
218
|
+
content = void 0;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (content === void 0) {
|
|
222
|
+
state.skippedCount += 1;
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
state.scannedCount += 1;
|
|
226
|
+
const findings = findIssues(content, kind, cfg.maxFindings);
|
|
227
|
+
if (findings.length === 0) return;
|
|
228
|
+
state.hitCount += 1;
|
|
229
|
+
state.lastFinding = findings[0] ?? null;
|
|
230
|
+
const header = `
|
|
231
|
+
\u26A0\uFE0F schema-evolution-guard: destructive schema pattern(s) detected in ${filePath}`;
|
|
232
|
+
const body = findings.map((f) => ` - ${f}`).join("\n");
|
|
233
|
+
const suffix = cfg.failSeverity === "block" ? "\nThis change is blocked. Add a safe migration path (e.g. default value, multi-step alter) and retry." : "\nReview the change before proceeding; consider a safer migration path.";
|
|
234
|
+
const message = `${header}
|
|
235
|
+
${body}${suffix}`;
|
|
236
|
+
if (cfg.failSeverity === "block") {
|
|
237
|
+
state.blockedCount += 1;
|
|
238
|
+
return { decision: "block", reason: message };
|
|
239
|
+
}
|
|
240
|
+
state.warningCount += 1;
|
|
241
|
+
return { additionalContext: message };
|
|
242
|
+
};
|
|
243
|
+
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
|
|
244
|
+
api.tools.register({
|
|
245
|
+
name: "schema_evolution_status",
|
|
246
|
+
description: "Reports schema-evolution-guard state: config, file patterns, and per-session counters.",
|
|
247
|
+
inputSchema: { type: "object", properties: {} },
|
|
248
|
+
permission: "auto",
|
|
249
|
+
category: "Diagnostics",
|
|
250
|
+
mutating: false,
|
|
251
|
+
async execute() {
|
|
252
|
+
return {
|
|
253
|
+
ok: true,
|
|
254
|
+
enabled: cfg.enabled,
|
|
255
|
+
failSeverity: cfg.failSeverity,
|
|
256
|
+
maxFindings: cfg.maxFindings,
|
|
257
|
+
filePatterns: cfg.filePatterns,
|
|
258
|
+
counters: {
|
|
259
|
+
invocations: state.invocationCount,
|
|
260
|
+
scanned: state.scannedCount,
|
|
261
|
+
skipped: state.skippedCount,
|
|
262
|
+
hits: state.hitCount,
|
|
263
|
+
warnings: state.warningCount,
|
|
264
|
+
blocked: state.blockedCount
|
|
265
|
+
},
|
|
266
|
+
lastFinding: state.lastFinding
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
api.log.info("schema-evolution-guard plugin loaded", {
|
|
271
|
+
version: "0.1.0",
|
|
272
|
+
failSeverity: cfg.failSeverity,
|
|
273
|
+
filePatterns: cfg.filePatterns
|
|
274
|
+
});
|
|
275
|
+
},
|
|
276
|
+
teardown(api) {
|
|
277
|
+
if (state.hookUnregister) {
|
|
278
|
+
try {
|
|
279
|
+
state.hookUnregister();
|
|
280
|
+
} catch {
|
|
281
|
+
}
|
|
282
|
+
state.hookUnregister = null;
|
|
283
|
+
}
|
|
284
|
+
const final = {
|
|
285
|
+
invocations: state.invocationCount,
|
|
286
|
+
scanned: state.scannedCount,
|
|
287
|
+
skipped: state.skippedCount,
|
|
288
|
+
hits: state.hitCount,
|
|
289
|
+
warnings: state.warningCount,
|
|
290
|
+
blocked: state.blockedCount
|
|
291
|
+
};
|
|
292
|
+
state.invocationCount = 0;
|
|
293
|
+
state.scannedCount = 0;
|
|
294
|
+
state.skippedCount = 0;
|
|
295
|
+
state.hitCount = 0;
|
|
296
|
+
state.warningCount = 0;
|
|
297
|
+
state.blockedCount = 0;
|
|
298
|
+
state.lastFinding = null;
|
|
299
|
+
api.log.info("schema-evolution-guard: teardown complete", { final });
|
|
300
|
+
},
|
|
301
|
+
async health() {
|
|
302
|
+
return {
|
|
303
|
+
ok: true,
|
|
304
|
+
message: `schema-evolution-guard: ${state.scannedCount} scan(s), ${state.hitCount} hit(s), ${state.blockedCount} blocked`,
|
|
305
|
+
counters: {
|
|
306
|
+
invocations: state.invocationCount,
|
|
307
|
+
scanned: state.scannedCount,
|
|
308
|
+
skipped: state.skippedCount,
|
|
309
|
+
hits: state.hitCount,
|
|
310
|
+
warnings: state.warningCount,
|
|
311
|
+
blocked: state.blockedCount
|
|
312
|
+
},
|
|
313
|
+
lastFinding: state.lastFinding
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
var schema_evolution_guard_default = plugin;
|
|
318
|
+
|
|
319
|
+
export { schema_evolution_guard_default as default };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Plugin } from '@wrongstack/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* security-hotspot-scanner plugin — scans source code for common security
|
|
5
|
+
* anti-patterns and reports them via tools + a PostToolUse hook.
|
|
6
|
+
*
|
|
7
|
+
* Tools registered:
|
|
8
|
+
* - security_hotspot_scan : scan a file or directory for security hotspots
|
|
9
|
+
* - security_hotspot_status : config + per-session counters
|
|
10
|
+
*
|
|
11
|
+
* Hooks registered:
|
|
12
|
+
* - PostToolUse with matcher `write|edit` to warn about newly introduced hotspots.
|
|
13
|
+
*
|
|
14
|
+
* Config (`config.extensions['security-hotspot-scanner']`):
|
|
15
|
+
*
|
|
16
|
+
* ```jsonc
|
|
17
|
+
* {
|
|
18
|
+
* "enabled": true,
|
|
19
|
+
* "severity": "warn", // "warn" | "block"
|
|
20
|
+
* "maxFindings": 10,
|
|
21
|
+
* "scanOnChange": [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".py"]
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
declare const plugin: Plugin;
|
|
29
|
+
|
|
30
|
+
export { plugin as default };
|