@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,402 @@
|
|
|
1
|
+
import { statSync, readdirSync, readFileSync } from 'fs';
|
|
2
|
+
import { isAbsolute, resolve, relative } from 'path';
|
|
3
|
+
|
|
4
|
+
// src/security-hotspot-scanner/index.ts
|
|
5
|
+
var API_VERSION = "^0.1.10";
|
|
6
|
+
var state = {
|
|
7
|
+
scanCount: 0,
|
|
8
|
+
fileScanCount: 0,
|
|
9
|
+
findingCount: 0,
|
|
10
|
+
hookInvocationCount: 0,
|
|
11
|
+
skippedCount: 0,
|
|
12
|
+
blockedCount: 0,
|
|
13
|
+
lastResult: null,
|
|
14
|
+
knownHotspots: /* @__PURE__ */ new Set(),
|
|
15
|
+
hookUnregister: null
|
|
16
|
+
};
|
|
17
|
+
var DEFAULTS = {
|
|
18
|
+
enabled: false,
|
|
19
|
+
severity: "warn",
|
|
20
|
+
maxFindings: 10,
|
|
21
|
+
scanOnChange: [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".py", ".java", ".go", ".rb", ".php"]
|
|
22
|
+
};
|
|
23
|
+
function readConfig(raw) {
|
|
24
|
+
if (!raw || typeof raw !== "object") return { ...DEFAULTS };
|
|
25
|
+
const r = raw;
|
|
26
|
+
return {
|
|
27
|
+
enabled: r["enabled"] === true,
|
|
28
|
+
severity: r["severity"] === "block" ? "block" : DEFAULTS.severity,
|
|
29
|
+
maxFindings: typeof r["maxFindings"] === "number" && r["maxFindings"] >= 1 && r["maxFindings"] <= 100 ? r["maxFindings"] : DEFAULTS.maxFindings,
|
|
30
|
+
scanOnChange: Array.isArray(r["scanOnChange"]) ? r["scanOnChange"].filter((x) => typeof x === "string") : DEFAULTS.scanOnChange
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
var PATTERNS = [
|
|
34
|
+
{ type: "eval_call", severity: "high", regex: /\beval\s*\(/i },
|
|
35
|
+
{ type: "function_constructor", severity: "high", regex: /\bnew\s+Function\s*\(|\bFunction\s*\(/i },
|
|
36
|
+
{
|
|
37
|
+
type: "unsafe_html",
|
|
38
|
+
severity: "high",
|
|
39
|
+
regex: /\binnerHTML\s*=|dangerouslySetInnerHTML/i
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
type: "sql_concatenation",
|
|
43
|
+
severity: "high",
|
|
44
|
+
regex: /\b(SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|CREATE)\b[^;]{0,120}(\+|\$\{|\.concat\s*\()/i
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
type: "hardcoded_http",
|
|
48
|
+
severity: "medium",
|
|
49
|
+
regex: /http:\/\/[a-zA-Z0-9][^\s'"\)\\]*/i
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: "console_log_credentials",
|
|
53
|
+
severity: "medium",
|
|
54
|
+
regex: /\bconsole\.log\s*\([^)]{0,120}(?:password|token|secret|key|credential|apikey|auth)/i
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: "exec_variable_command",
|
|
58
|
+
severity: "high",
|
|
59
|
+
regex: /\bexec\s*\(\s*(?:`[^`]*\$\{[^}]*\}[^`]*`|[a-zA-Z_$][\w$]*)/i
|
|
60
|
+
}
|
|
61
|
+
];
|
|
62
|
+
function scanSource(content, maxFindings) {
|
|
63
|
+
const findings = [];
|
|
64
|
+
const lines = content.split(/\r?\n/);
|
|
65
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
66
|
+
const line = lines[i];
|
|
67
|
+
for (const pattern of PATTERNS) {
|
|
68
|
+
const match = pattern.regex.exec(line);
|
|
69
|
+
if (match) {
|
|
70
|
+
findings.push({
|
|
71
|
+
type: pattern.type,
|
|
72
|
+
severity: pattern.severity,
|
|
73
|
+
line: i + 1,
|
|
74
|
+
snippet: line.trim().slice(0, 160)
|
|
75
|
+
});
|
|
76
|
+
if (findings.length >= maxFindings) return findings;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return findings;
|
|
81
|
+
}
|
|
82
|
+
function isSourceFile(filePath, extensions) {
|
|
83
|
+
const lower = filePath.toLowerCase();
|
|
84
|
+
return extensions.some((ext) => lower.endsWith(ext));
|
|
85
|
+
}
|
|
86
|
+
function withinProject(p) {
|
|
87
|
+
if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
|
|
88
|
+
const root = process.cwd();
|
|
89
|
+
const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
|
|
90
|
+
const rel = relative(root, resolved);
|
|
91
|
+
if (rel === "" || rel === ".") return true;
|
|
92
|
+
if (rel.startsWith("..")) return false;
|
|
93
|
+
if (isAbsolute(rel)) return false;
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
function scanPath(inputPath, cfg) {
|
|
97
|
+
const start = Date.now();
|
|
98
|
+
const root = process.cwd();
|
|
99
|
+
const resolved = isAbsolute(inputPath) ? resolve(inputPath) : resolve(root, inputPath);
|
|
100
|
+
if (!withinProject(inputPath)) {
|
|
101
|
+
return {
|
|
102
|
+
path: inputPath,
|
|
103
|
+
scanned: false,
|
|
104
|
+
findings: [],
|
|
105
|
+
filesScanned: 0,
|
|
106
|
+
error: "path is outside the project directory",
|
|
107
|
+
durationMs: Date.now() - start
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
let filesScanned = 0;
|
|
111
|
+
const allFindings = [];
|
|
112
|
+
const maxPerFile = Math.max(1, Math.floor(cfg.maxFindings / 2));
|
|
113
|
+
const scanFile = (filePath) => {
|
|
114
|
+
if (!isSourceFile(filePath, cfg.scanOnChange)) return;
|
|
115
|
+
try {
|
|
116
|
+
const content = readFileSync(filePath, "utf-8");
|
|
117
|
+
filesScanned += 1;
|
|
118
|
+
const findings = scanSource(content, maxPerFile);
|
|
119
|
+
for (const f of findings) {
|
|
120
|
+
allFindings.push({ ...f, snippet: `${relative(root, filePath)}:${f.line}: ${f.snippet}` });
|
|
121
|
+
if (allFindings.length >= cfg.maxFindings) return;
|
|
122
|
+
}
|
|
123
|
+
} catch {
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const walk = (dir) => {
|
|
127
|
+
let entries;
|
|
128
|
+
try {
|
|
129
|
+
entries = readdirSync(dir);
|
|
130
|
+
} catch {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
for (const entry of entries) {
|
|
134
|
+
if (allFindings.length >= cfg.maxFindings) return;
|
|
135
|
+
const full = resolve(dir, entry);
|
|
136
|
+
try {
|
|
137
|
+
const st = statSync(full);
|
|
138
|
+
if (st.isDirectory()) {
|
|
139
|
+
if (entry.startsWith(".") || entry === "node_modules" || entry === "dist" || entry === "coverage") {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
walk(full);
|
|
143
|
+
} else if (st.isFile()) {
|
|
144
|
+
scanFile(full);
|
|
145
|
+
}
|
|
146
|
+
} catch {
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
try {
|
|
151
|
+
const st = statSync(resolved);
|
|
152
|
+
if (st.isDirectory()) {
|
|
153
|
+
walk(resolved);
|
|
154
|
+
} else if (st.isFile()) {
|
|
155
|
+
scanFile(resolved);
|
|
156
|
+
} else {
|
|
157
|
+
return {
|
|
158
|
+
path: inputPath,
|
|
159
|
+
scanned: false,
|
|
160
|
+
findings: [],
|
|
161
|
+
filesScanned: 0,
|
|
162
|
+
error: "path is not a file or directory",
|
|
163
|
+
durationMs: Date.now() - start
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
} catch {
|
|
167
|
+
return {
|
|
168
|
+
path: inputPath,
|
|
169
|
+
scanned: false,
|
|
170
|
+
findings: [],
|
|
171
|
+
filesScanned: 0,
|
|
172
|
+
error: "path does not exist or cannot be read",
|
|
173
|
+
durationMs: Date.now() - start
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
path: inputPath,
|
|
178
|
+
scanned: true,
|
|
179
|
+
findings: allFindings,
|
|
180
|
+
filesScanned,
|
|
181
|
+
durationMs: Date.now() - start
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
var plugin = {
|
|
185
|
+
name: "security-hotspot-scanner",
|
|
186
|
+
version: "0.1.0",
|
|
187
|
+
description: "Scans source code for security anti-patterns and warns after writes/edits that introduce new hotspots",
|
|
188
|
+
apiVersion: API_VERSION,
|
|
189
|
+
capabilities: { tools: true, hooks: true },
|
|
190
|
+
defaultConfig: { ...DEFAULTS },
|
|
191
|
+
configSchema: {
|
|
192
|
+
type: "object",
|
|
193
|
+
properties: {
|
|
194
|
+
enabled: { type: "boolean", default: false, description: "Master switch." },
|
|
195
|
+
severity: {
|
|
196
|
+
type: "string",
|
|
197
|
+
enum: ["warn", "block"],
|
|
198
|
+
default: "warn",
|
|
199
|
+
description: "warn = inject hotspots as additionalContext; block = refuse the mutating tool."
|
|
200
|
+
},
|
|
201
|
+
maxFindings: {
|
|
202
|
+
type: "number",
|
|
203
|
+
minimum: 1,
|
|
204
|
+
maximum: 100,
|
|
205
|
+
default: 10,
|
|
206
|
+
description: "Maximum hotspots reported per scan."
|
|
207
|
+
},
|
|
208
|
+
scanOnChange: {
|
|
209
|
+
type: "array",
|
|
210
|
+
items: { type: "string" },
|
|
211
|
+
default: DEFAULTS.scanOnChange,
|
|
212
|
+
description: "Only scan files with one of these extensions."
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
setup(api) {
|
|
217
|
+
state.scanCount = 0;
|
|
218
|
+
state.fileScanCount = 0;
|
|
219
|
+
state.findingCount = 0;
|
|
220
|
+
state.hookInvocationCount = 0;
|
|
221
|
+
state.skippedCount = 0;
|
|
222
|
+
state.blockedCount = 0;
|
|
223
|
+
state.lastResult = null;
|
|
224
|
+
state.knownHotspots.clear();
|
|
225
|
+
if (state.hookUnregister) {
|
|
226
|
+
try {
|
|
227
|
+
state.hookUnregister();
|
|
228
|
+
} catch {
|
|
229
|
+
}
|
|
230
|
+
state.hookUnregister = null;
|
|
231
|
+
}
|
|
232
|
+
const cfg = readConfig(api.config.extensions?.["security-hotspot-scanner"]);
|
|
233
|
+
const hook = (input) => {
|
|
234
|
+
if (!cfg.enabled) return;
|
|
235
|
+
if (input.toolResult?.isError) return;
|
|
236
|
+
const inp = input.toolInput ?? {};
|
|
237
|
+
const sourcePath = inp["path"];
|
|
238
|
+
if (!sourcePath || typeof sourcePath !== "string") return;
|
|
239
|
+
if (!withinProject(sourcePath)) return;
|
|
240
|
+
const ext = sourcePath.includes(".") ? sourcePath.slice(sourcePath.lastIndexOf(".")).toLowerCase() : "";
|
|
241
|
+
if (!cfg.scanOnChange.includes(ext)) {
|
|
242
|
+
state.skippedCount += 1;
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
state.hookInvocationCount += 1;
|
|
246
|
+
const result = scanPath(sourcePath, cfg);
|
|
247
|
+
state.scanCount += 1;
|
|
248
|
+
state.fileScanCount += result.filesScanned;
|
|
249
|
+
if (!result.scanned) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const newFindings = result.findings.filter((f) => {
|
|
253
|
+
const key = `${result.path}:${f.type}:${f.line}`;
|
|
254
|
+
if (state.knownHotspots.has(key)) return false;
|
|
255
|
+
state.knownHotspots.add(key);
|
|
256
|
+
return true;
|
|
257
|
+
});
|
|
258
|
+
if (newFindings.length === 0) return;
|
|
259
|
+
state.findingCount += newFindings.length;
|
|
260
|
+
state.lastResult = {
|
|
261
|
+
path: result.path,
|
|
262
|
+
findings: newFindings.length,
|
|
263
|
+
durationMs: result.durationMs,
|
|
264
|
+
when: (/* @__PURE__ */ new Date()).toISOString()
|
|
265
|
+
};
|
|
266
|
+
const lines = newFindings.map(
|
|
267
|
+
(f) => ` [${f.severity.toUpperCase()}] ${f.type} at line ${f.line}: ${f.snippet}`
|
|
268
|
+
);
|
|
269
|
+
const message = `
|
|
270
|
+
\u{1F6E1}\uFE0F security-hotspot-scanner: ${newFindings.length} new security hotspot(s) introduced by editing ${sourcePath}.
|
|
271
|
+
${lines.join("\n")}` + (result.findings.length >= cfg.maxFindings ? `
|
|
272
|
+
\u2026 and possibly more (showing first ${cfg.maxFindings})` : "") + `
|
|
273
|
+
Review or remove the risky pattern(s).`;
|
|
274
|
+
if (cfg.severity === "block") {
|
|
275
|
+
state.blockedCount += 1;
|
|
276
|
+
return {
|
|
277
|
+
decision: "block",
|
|
278
|
+
reason: message
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
return { additionalContext: message };
|
|
282
|
+
};
|
|
283
|
+
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
|
|
284
|
+
api.tools.register({
|
|
285
|
+
name: "security_hotspot_scan",
|
|
286
|
+
description: "Scan a file or directory for security anti-patterns (eval, Function constructor, innerHTML, SQL concatenation, http URLs, credential logging, variable exec commands).",
|
|
287
|
+
inputSchema: {
|
|
288
|
+
type: "object",
|
|
289
|
+
properties: {
|
|
290
|
+
path: {
|
|
291
|
+
type: "string",
|
|
292
|
+
description: "File or directory path to scan (relative or absolute, must be inside the project)."
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
required: ["path"]
|
|
296
|
+
},
|
|
297
|
+
permission: "auto",
|
|
298
|
+
category: "Security",
|
|
299
|
+
mutating: false,
|
|
300
|
+
async execute(input) {
|
|
301
|
+
if (!cfg.enabled) return { ok: false, error: "security-hotspot-scanner is disabled" };
|
|
302
|
+
const result = scanPath(input.path, cfg);
|
|
303
|
+
state.scanCount += 1;
|
|
304
|
+
state.fileScanCount += result.filesScanned;
|
|
305
|
+
state.findingCount += result.findings.length;
|
|
306
|
+
if (!result.scanned) {
|
|
307
|
+
return { ok: false, error: result.error, path: input.path };
|
|
308
|
+
}
|
|
309
|
+
state.lastResult = {
|
|
310
|
+
path: result.path,
|
|
311
|
+
findings: result.findings.length,
|
|
312
|
+
durationMs: result.durationMs,
|
|
313
|
+
when: (/* @__PURE__ */ new Date()).toISOString()
|
|
314
|
+
};
|
|
315
|
+
return {
|
|
316
|
+
ok: true,
|
|
317
|
+
path: input.path,
|
|
318
|
+
filesScanned: result.filesScanned,
|
|
319
|
+
findings: result.findings,
|
|
320
|
+
findingCount: result.findings.length,
|
|
321
|
+
durationMs: result.durationMs
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
api.tools.register({
|
|
326
|
+
name: "security_hotspot_status",
|
|
327
|
+
description: "Reports security-hotspot-scanner state: severity, extensions, pattern types, and per-session counters.",
|
|
328
|
+
inputSchema: { type: "object", properties: {} },
|
|
329
|
+
permission: "auto",
|
|
330
|
+
category: "Security",
|
|
331
|
+
mutating: false,
|
|
332
|
+
async execute() {
|
|
333
|
+
return {
|
|
334
|
+
ok: true,
|
|
335
|
+
enabled: cfg.enabled,
|
|
336
|
+
severity: cfg.severity,
|
|
337
|
+
maxFindings: cfg.maxFindings,
|
|
338
|
+
scanOnChange: cfg.scanOnChange,
|
|
339
|
+
patternTypes: PATTERNS.map((p) => p.type),
|
|
340
|
+
counters: {
|
|
341
|
+
scans: state.scanCount,
|
|
342
|
+
filesScanned: state.fileScanCount,
|
|
343
|
+
findings: state.findingCount,
|
|
344
|
+
hookInvocations: state.hookInvocationCount,
|
|
345
|
+
skipped: state.skippedCount,
|
|
346
|
+
blocked: state.blockedCount
|
|
347
|
+
},
|
|
348
|
+
lastResult: state.lastResult
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
api.log.info("security-hotspot-scanner plugin loaded", {
|
|
353
|
+
version: "0.1.0",
|
|
354
|
+
severity: cfg.severity,
|
|
355
|
+
maxFindings: cfg.maxFindings
|
|
356
|
+
});
|
|
357
|
+
},
|
|
358
|
+
teardown(api) {
|
|
359
|
+
if (state.hookUnregister) {
|
|
360
|
+
try {
|
|
361
|
+
state.hookUnregister();
|
|
362
|
+
} catch {
|
|
363
|
+
}
|
|
364
|
+
state.hookUnregister = null;
|
|
365
|
+
}
|
|
366
|
+
const final = {
|
|
367
|
+
scans: state.scanCount,
|
|
368
|
+
filesScanned: state.fileScanCount,
|
|
369
|
+
findings: state.findingCount,
|
|
370
|
+
hookInvocations: state.hookInvocationCount,
|
|
371
|
+
skipped: state.skippedCount,
|
|
372
|
+
blocked: state.blockedCount
|
|
373
|
+
};
|
|
374
|
+
state.scanCount = 0;
|
|
375
|
+
state.fileScanCount = 0;
|
|
376
|
+
state.findingCount = 0;
|
|
377
|
+
state.hookInvocationCount = 0;
|
|
378
|
+
state.skippedCount = 0;
|
|
379
|
+
state.blockedCount = 0;
|
|
380
|
+
state.lastResult = null;
|
|
381
|
+
state.knownHotspots.clear();
|
|
382
|
+
api.log.info("security-hotspot-scanner: teardown complete", { final });
|
|
383
|
+
},
|
|
384
|
+
async health() {
|
|
385
|
+
return {
|
|
386
|
+
ok: true,
|
|
387
|
+
message: state.lastResult ? `security-hotspot-scanner: ${state.scanCount} scan(s), last scan found ${state.lastResult.findings} hotspot(s)` : `security-hotspot-scanner: ${state.scanCount} scan(s), ${state.findingCount} finding(s)`,
|
|
388
|
+
counters: {
|
|
389
|
+
scans: state.scanCount,
|
|
390
|
+
filesScanned: state.fileScanCount,
|
|
391
|
+
findings: state.findingCount,
|
|
392
|
+
hookInvocations: state.hookInvocationCount,
|
|
393
|
+
skipped: state.skippedCount,
|
|
394
|
+
blocked: state.blockedCount
|
|
395
|
+
},
|
|
396
|
+
lastResult: state.lastResult
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
var security_hotspot_scanner_default = plugin;
|
|
401
|
+
|
|
402
|
+
export { security_hotspot_scanner_default as default };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Plugin } from '@wrongstack/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* semantic-search-indexer plugin — lightweight keyword-based search over
|
|
5
|
+
* project source files.
|
|
6
|
+
*
|
|
7
|
+
* The plugin builds an in-memory inverted index (no persistence, no
|
|
8
|
+
* embeddings) and exposes two tools:
|
|
9
|
+
*
|
|
10
|
+
* - `semantic_search` — ranked keyword search with TF scoring
|
|
11
|
+
* - `semantic_index_status` — index counters and configuration
|
|
12
|
+
*
|
|
13
|
+
* Files are tokenized into lowercase alphanumeric terms and indexed with
|
|
14
|
+
* per-document term frequencies. Queries are scored by summing the TF of
|
|
15
|
+
* each matched query token, then results are returned with the matching
|
|
16
|
+
* file path, score, and a handful of matched lines.
|
|
17
|
+
*
|
|
18
|
+
* Config (`config.extensions['semantic-search-indexer']`):
|
|
19
|
+
*
|
|
20
|
+
* ```jsonc
|
|
21
|
+
* {
|
|
22
|
+
* "enabled": true,
|
|
23
|
+
* "includeExtensions": [".ts", ".tsx", ".js", ".jsx"],
|
|
24
|
+
* "excludePatterns": ["node_modules", "\\.git", "dist"],
|
|
25
|
+
* "maxFileBytes": 1_000_000,
|
|
26
|
+
* "defaultLimit": 10,
|
|
27
|
+
* "minTokenLength": 2,
|
|
28
|
+
* "maxMatchesPerFile": 10,
|
|
29
|
+
* "maxFiles": 5000
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
declare const plugin: Plugin;
|
|
37
|
+
|
|
38
|
+
export { plugin as default };
|