@wrongstack/plugins 0.287.0 → 0.291.0
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/dist/accessibility-auditor/index.d.ts.map +1 -1
- package/dist/accessibility-auditor.js +45 -42
- package/dist/auto-i18n-extractor.js +1 -1
- package/dist/code-metrics/index.d.ts.map +1 -1
- package/dist/code-metrics.js +46 -42
- package/dist/cost-tracker/index.d.ts.map +1 -1
- package/dist/cost-tracker.js +4 -2
- package/dist/dead-code-detector.js +3 -3
- package/dist/diff-summary.js +1 -1
- package/dist/doc-sync-guard.js +3 -3
- package/dist/duplicate-code-detector/index.d.ts.map +1 -1
- package/dist/duplicate-code-detector.js +47 -43
- package/dist/feature-flag-tracker/index.d.ts.map +1 -1
- package/dist/feature-flag-tracker.js +46 -42
- package/dist/file-watcher/index.d.ts.map +1 -1
- package/dist/file-watcher.js +6 -2
- package/dist/format-on-save.js +1 -1
- package/dist/import-organizer.js +1 -1
- package/dist/index.js +396 -539
- package/dist/interface-contract-guard/index.d.ts.map +1 -1
- package/dist/interface-contract-guard.js +46 -42
- package/dist/lint-gate/index.d.ts.map +1 -1
- package/dist/lint-gate.js +61 -30
- package/dist/migration-planner.js +1 -1
- package/dist/prompt-firewall/index.d.ts +14 -6
- package/dist/prompt-firewall/index.d.ts.map +1 -1
- package/dist/prompt-firewall.js +7 -6
- package/dist/refactor-suggester/index.d.ts.map +1 -1
- package/dist/refactor-suggester.js +46 -42
- package/dist/runtime/index.d.ts +24 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime.js +63 -24
- package/dist/schema-evolution-guard.js +1 -1
- package/dist/secret-scanner/index.d.ts.map +1 -1
- package/dist/secret-scanner.js +17 -6
- package/dist/security-hotspot-scanner.js +1 -1
- package/dist/spec-linker.js +396 -539
- package/dist/test-runner-gate.js +1 -1
- package/dist/type-gate.js +19 -23
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/interface-contract-guard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAS/C,MAAM,WAAW,wBAAwB;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/interface-contract-guard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAS/C,MAAM,WAAW,wBAAwB;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAqID,QAAA,MAAM,MAAM,EAAE,MAuMb,CAAC;eAEa,MAAM"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// src/interface-contract-guard/index.ts
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 } from "node:path";
|
|
4
4
|
|
|
5
5
|
// src/runtime/index.ts
|
|
6
|
-
import {
|
|
6
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
7
|
+
import { basename, extname, isAbsolute, relative, resolve } from "node:path";
|
|
7
8
|
var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
8
9
|
function hasLeadingDash(arg) {
|
|
9
10
|
return arg.length > 0 && arg.startsWith("-");
|
|
@@ -18,6 +19,47 @@ function withinProjectPath(projectRoot, candidate) {
|
|
|
18
19
|
function withinProject(p) {
|
|
19
20
|
return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
|
|
20
21
|
}
|
|
22
|
+
var DEFAULT_EXCLUDE_DIRS = ["node_modules", "dist", ".git", "coverage"];
|
|
23
|
+
function collectSourceFiles(root, opts) {
|
|
24
|
+
const files = [];
|
|
25
|
+
if (!existsSync(root)) return files;
|
|
26
|
+
const s = statSync(root);
|
|
27
|
+
if (s.isFile()) {
|
|
28
|
+
if (matchesExtension(root, opts.extensions)) files.push(root);
|
|
29
|
+
return files;
|
|
30
|
+
}
|
|
31
|
+
if (!s.isDirectory()) return files;
|
|
32
|
+
const exclude = opts.excludeDirs ?? DEFAULT_EXCLUDE_DIRS;
|
|
33
|
+
function walk(dir, depth) {
|
|
34
|
+
if (opts.maxDepth !== void 0 && depth > opts.maxDepth) return;
|
|
35
|
+
let entries;
|
|
36
|
+
try {
|
|
37
|
+
entries = readdirSync(dir);
|
|
38
|
+
} catch {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
for (const entry of entries) {
|
|
42
|
+
if (exclude.includes(entry)) continue;
|
|
43
|
+
const full = resolve(dir, entry);
|
|
44
|
+
let st;
|
|
45
|
+
try {
|
|
46
|
+
st = statSync(full);
|
|
47
|
+
} catch {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (st.isDirectory()) {
|
|
51
|
+
walk(full, depth + 1);
|
|
52
|
+
} else if (st.isFile() && matchesExtension(full, opts.extensions)) {
|
|
53
|
+
files.push(full);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
walk(root, 0);
|
|
58
|
+
return files;
|
|
59
|
+
}
|
|
60
|
+
function matchesExtension(p, exts) {
|
|
61
|
+
return exts.includes(extname(p).toLowerCase());
|
|
62
|
+
}
|
|
21
63
|
|
|
22
64
|
// src/interface-contract-guard/index.ts
|
|
23
65
|
var API_VERSION = "^0.1.10";
|
|
@@ -46,44 +88,6 @@ function readConfig(raw) {
|
|
|
46
88
|
function normalizeExtensions(exts) {
|
|
47
89
|
return exts.map((e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`);
|
|
48
90
|
}
|
|
49
|
-
function matchesExtension(p, exts) {
|
|
50
|
-
return exts.includes(extname(p).toLowerCase());
|
|
51
|
-
}
|
|
52
|
-
function collectSourceFiles(root, exts) {
|
|
53
|
-
const files = [];
|
|
54
|
-
if (!existsSync(root)) return files;
|
|
55
|
-
const s = statSync(root);
|
|
56
|
-
if (s.isFile()) {
|
|
57
|
-
if (matchesExtension(root, exts)) files.push(root);
|
|
58
|
-
return files;
|
|
59
|
-
}
|
|
60
|
-
if (!s.isDirectory()) return files;
|
|
61
|
-
function walk(dir) {
|
|
62
|
-
let entries;
|
|
63
|
-
try {
|
|
64
|
-
entries = readdirSync(dir);
|
|
65
|
-
} catch {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
for (const entry of entries) {
|
|
69
|
-
if (entry === "node_modules" || entry === "dist" || entry === ".git" || entry === "coverage") continue;
|
|
70
|
-
const full = resolve2(dir, entry);
|
|
71
|
-
let st;
|
|
72
|
-
try {
|
|
73
|
-
st = statSync(full);
|
|
74
|
-
} catch {
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
if (st.isDirectory()) {
|
|
78
|
-
walk(full);
|
|
79
|
-
} else if (st.isFile() && matchesExtension(full, exts)) {
|
|
80
|
-
files.push(full);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
walk(root);
|
|
85
|
-
return files;
|
|
86
|
-
}
|
|
87
91
|
function toPosix(p) {
|
|
88
92
|
return p.replace(/\\/g, "/");
|
|
89
93
|
}
|
|
@@ -110,7 +114,7 @@ function scanPath(rawPath, cfg) {
|
|
|
110
114
|
const root = process.cwd();
|
|
111
115
|
const resolved = isAbsolute2(rawPath) ? resolve2(rawPath) : resolve2(root, rawPath);
|
|
112
116
|
const exts = normalizeExtensions(cfg.extensions);
|
|
113
|
-
const files = collectSourceFiles(resolved, exts);
|
|
117
|
+
const files = collectSourceFiles(resolved, { extensions: exts });
|
|
114
118
|
const contents = [];
|
|
115
119
|
for (const p of files) {
|
|
116
120
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lint-gate/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lint-gate/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAQH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAwU/C,QAAA,MAAM,MAAM,EAAE,MAwWb,CAAC;eAEa,MAAM"}
|
package/dist/lint-gate.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// src/lint-gate/index.ts
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
3
4
|
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
4
6
|
import { tmpdir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
7
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
6
8
|
var API_VERSION = "^0.1.10";
|
|
7
9
|
var state = {
|
|
8
10
|
/** Total PreToolUse invocations. */
|
|
@@ -36,55 +38,74 @@ function readConfig(raw) {
|
|
|
36
38
|
fixRules: Array.isArray(r["fixRules"]) ? r["fixRules"].filter((x) => typeof x === "string") : []
|
|
37
39
|
};
|
|
38
40
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
var LINTER_PACKAGES = {
|
|
42
|
+
biome: "@biomejs/biome",
|
|
43
|
+
eslint: "eslint"
|
|
44
|
+
};
|
|
45
|
+
function isInside(parent, candidate) {
|
|
46
|
+
const rel = relative(parent, candidate);
|
|
47
|
+
return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel);
|
|
48
|
+
}
|
|
49
|
+
function resolveLocalLinter(name, cwd) {
|
|
50
|
+
try {
|
|
51
|
+
const packageName = LINTER_PACKAGES[name];
|
|
52
|
+
const requireFromProject = createRequire(resolve(cwd, "package.json"));
|
|
53
|
+
const packagePath = requireFromProject.resolve(`${packageName}/package.json`);
|
|
54
|
+
const packageJson = JSON.parse(readFileSync(packagePath, "utf-8"));
|
|
55
|
+
const relativeBin = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.[name] ?? Object.values(packageJson.bin ?? {})[0];
|
|
56
|
+
if (!relativeBin || isAbsolute(relativeBin)) return null;
|
|
57
|
+
const packageDir = dirname(packagePath);
|
|
58
|
+
const entry = resolve(packageDir, relativeBin);
|
|
59
|
+
if (!isInside(packageDir, entry)) return null;
|
|
60
|
+
return {
|
|
61
|
+
cmd: process.execPath,
|
|
62
|
+
args: name === "biome" ? [entry, "check", "--reporter=json"] : [entry, "--format=json"],
|
|
63
|
+
name
|
|
64
|
+
};
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
41
68
|
}
|
|
42
|
-
function runCommand(command, args, timeoutMs, signal) {
|
|
43
|
-
return new Promise((
|
|
69
|
+
function runCommand(command, args, timeoutMs, cwd, signal) {
|
|
70
|
+
return new Promise((resolveResult) => {
|
|
44
71
|
try {
|
|
45
72
|
execFile(
|
|
46
|
-
|
|
73
|
+
command,
|
|
47
74
|
args,
|
|
48
75
|
{
|
|
49
76
|
encoding: "utf-8",
|
|
50
77
|
timeout: timeoutMs,
|
|
51
|
-
cwd
|
|
78
|
+
cwd,
|
|
52
79
|
windowsHide: true,
|
|
80
|
+
shell: false,
|
|
53
81
|
maxBuffer: 2 * 1024 * 1024,
|
|
54
82
|
...signal ? { signal } : {}
|
|
55
83
|
},
|
|
56
|
-
(error, stdout) =>
|
|
84
|
+
(error, stdout) => resolveResult({ stdout, error })
|
|
57
85
|
);
|
|
58
86
|
} catch (err) {
|
|
59
|
-
|
|
87
|
+
resolveResult({ stdout: "", error: err instanceof Error ? err : new Error(String(err)) });
|
|
60
88
|
}
|
|
61
89
|
});
|
|
62
90
|
}
|
|
63
|
-
async function detectLinter(requested) {
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if (tryEslint) {
|
|
73
|
-
const probe = await runCommand("npx", ["eslint", "--version"], 5e3);
|
|
74
|
-
if (!probe.error) {
|
|
75
|
-
return { cmd: "npx", args: ["eslint", "--format=json"], name: "eslint" };
|
|
76
|
-
}
|
|
91
|
+
async function detectLinter(requested, cwd) {
|
|
92
|
+
const candidates = requested === "auto" ? ["biome", "eslint"] : [requested];
|
|
93
|
+
for (const name of candidates) {
|
|
94
|
+
const linter = resolveLocalLinter(name, cwd);
|
|
95
|
+
if (!linter) continue;
|
|
96
|
+
const probe = await runCommand(linter.cmd, [linter.args[0], "--version"], 5e3, cwd);
|
|
97
|
+
if (!probe.error) return linter;
|
|
77
98
|
}
|
|
78
99
|
return null;
|
|
79
100
|
}
|
|
80
|
-
async function lintContent(content, filePath, linter, timeoutMs, signal) {
|
|
101
|
+
async function lintContent(content, filePath, linter, timeoutMs, cwd, signal) {
|
|
81
102
|
const ext = filePath.includes(".") ? filePath.slice(filePath.lastIndexOf(".")) : ".ts";
|
|
82
103
|
const tmpDir = await mkdtemp(join(tmpdir(), "lint-gate-"));
|
|
83
104
|
const tmpFile = join(tmpDir, `input${ext}`);
|
|
84
105
|
try {
|
|
85
106
|
await writeFile(tmpFile, content, "utf-8");
|
|
86
107
|
const fullArgs = [...linter.args, tmpFile];
|
|
87
|
-
const result = await runCommand(linter.cmd, fullArgs, timeoutMs, signal);
|
|
108
|
+
const result = await runCommand(linter.cmd, fullArgs, timeoutMs, cwd, signal);
|
|
88
109
|
if (signal.aborted) throw signal.reason;
|
|
89
110
|
if (result.error && !result.stdout) return null;
|
|
90
111
|
return parseLinterOutput(result.stdout, linter.name);
|
|
@@ -95,14 +116,14 @@ async function lintContent(content, filePath, linter, timeoutMs, signal) {
|
|
|
95
116
|
await rm(tmpDir, { recursive: true, force: true }).catch(() => void 0);
|
|
96
117
|
}
|
|
97
118
|
}
|
|
98
|
-
async function lintAndFix(content, filePath, linter, timeoutMs, signal) {
|
|
119
|
+
async function lintAndFix(content, filePath, linter, timeoutMs, cwd, signal) {
|
|
99
120
|
const ext = filePath.includes(".") ? filePath.slice(filePath.lastIndexOf(".")) : ".ts";
|
|
100
121
|
const tmpDir = await mkdtemp(join(tmpdir(), "lint-gate-fix-"));
|
|
101
122
|
const tmpFile = join(tmpDir, `input${ext}`);
|
|
102
123
|
try {
|
|
103
124
|
await writeFile(tmpFile, content, "utf-8");
|
|
104
|
-
const fixArgs = linter.name === "biome" ? [
|
|
105
|
-
await runCommand(linter.cmd, fixArgs, timeoutMs, signal);
|
|
125
|
+
const fixArgs = linter.name === "biome" ? [linter.args[0], "check", "--write", tmpFile] : [linter.args[0], "--fix", tmpFile];
|
|
126
|
+
await runCommand(linter.cmd, fixArgs, timeoutMs, cwd, signal);
|
|
106
127
|
if (signal.aborted) throw signal.reason;
|
|
107
128
|
return await readFile(tmpFile, "utf-8");
|
|
108
129
|
} catch {
|
|
@@ -203,7 +224,8 @@ var plugin = {
|
|
|
203
224
|
state.hookUnregister = null;
|
|
204
225
|
state.lastResult = null;
|
|
205
226
|
const cfg = readConfig(api.config.extensions?.["lint-gate"]);
|
|
206
|
-
const
|
|
227
|
+
const cwd = resolve(api.config.cwd ?? process.cwd());
|
|
228
|
+
const linterReady = detectLinter(cfg.linter, cwd).then((linter) => {
|
|
207
229
|
if (!linter) {
|
|
208
230
|
api.log.warn("lint-gate: no linter found (biome or eslint) \u2014 hook will be a no-op", {
|
|
209
231
|
requested: cfg.linter
|
|
@@ -240,7 +262,14 @@ var plugin = {
|
|
|
240
262
|
} else {
|
|
241
263
|
return;
|
|
242
264
|
}
|
|
243
|
-
const issues = await lintContent(
|
|
265
|
+
const issues = await lintContent(
|
|
266
|
+
content,
|
|
267
|
+
filePath,
|
|
268
|
+
linter,
|
|
269
|
+
cfg.timeoutMs,
|
|
270
|
+
cwd,
|
|
271
|
+
runtime.signal
|
|
272
|
+
);
|
|
244
273
|
if (issues === null) {
|
|
245
274
|
state.linterErrorCount += 1;
|
|
246
275
|
return;
|
|
@@ -280,6 +309,7 @@ ${summary}${truncated}`
|
|
|
280
309
|
filePath,
|
|
281
310
|
linter,
|
|
282
311
|
cfg.timeoutMs,
|
|
312
|
+
cwd,
|
|
283
313
|
runtime.signal
|
|
284
314
|
);
|
|
285
315
|
if (fixedContent !== content) {
|
|
@@ -318,6 +348,7 @@ ${remainingSummary}` : "")
|
|
|
318
348
|
filePath,
|
|
319
349
|
linter,
|
|
320
350
|
cfg.timeoutMs,
|
|
351
|
+
cwd,
|
|
321
352
|
runtime.signal
|
|
322
353
|
);
|
|
323
354
|
if (fixedNewStr !== newStr) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
|
|
4
4
|
// src/runtime/index.ts
|
|
5
|
-
import { basename, isAbsolute, relative, resolve } from "node:path";
|
|
5
|
+
import { basename, extname, isAbsolute, relative, resolve } from "node:path";
|
|
6
6
|
|
|
7
7
|
// src/runtime/llm.ts
|
|
8
8
|
function stripOuterMarkdownFence(text) {
|
|
@@ -9,25 +9,25 @@
|
|
|
9
9
|
* outgoing request's system + message text for high-confidence
|
|
10
10
|
* credential patterns and, depending on `mode`:
|
|
11
11
|
*
|
|
12
|
-
* - `warn`
|
|
12
|
+
* - `warn` — logs + counts + emits a `prompt-firewall:leak`
|
|
13
13
|
* event; the request goes through unchanged
|
|
14
|
-
* - `redact` — replaces each match with `[REDACTED:<kind>]` in a CLONE
|
|
14
|
+
* - `redact` (default) — replaces each match with `[REDACTED:<kind>]` in a CLONE
|
|
15
15
|
* of the request before sending, and also redacts secrets echoed back
|
|
16
16
|
* in the response
|
|
17
17
|
* - `block` — throws before the request is sent (the agent's error
|
|
18
18
|
* path surfaces it), so the secret never reaches the provider
|
|
19
19
|
*
|
|
20
20
|
* Safety posture: opt-in — loads inert until
|
|
21
|
-
* `config.extensions['prompt-firewall'].enabled = true`. `
|
|
22
|
-
* default so
|
|
23
|
-
*
|
|
21
|
+
* `config.extensions['prompt-firewall'].enabled = true`. `redact` is the
|
|
22
|
+
* default so secrets are automatically stripped; switch to `warn` only
|
|
23
|
+
* for detection-mode diagnostics without data loss.
|
|
24
24
|
*
|
|
25
25
|
* Config (`config.extensions['prompt-firewall']`):
|
|
26
26
|
*
|
|
27
27
|
* ```jsonc
|
|
28
28
|
* {
|
|
29
29
|
* "enabled": false,
|
|
30
|
-
* "mode": "
|
|
30
|
+
* "mode": "redact", // "warn" | "redact" | "block"
|
|
31
31
|
* "scanResponse": true, // redact secrets echoed back (redact mode)
|
|
32
32
|
* "allow": [] // regex source strings to exempt (false positives)
|
|
33
33
|
* }
|
|
@@ -50,6 +50,14 @@ export declare function redactSecrets(text: string, allow: RegExp[]): {
|
|
|
50
50
|
text: string;
|
|
51
51
|
redactions: number;
|
|
52
52
|
};
|
|
53
|
+
type FirewallMode = 'warn' | 'redact' | 'block';
|
|
54
|
+
interface PromptFirewallConfig {
|
|
55
|
+
enabled: boolean;
|
|
56
|
+
mode: FirewallMode;
|
|
57
|
+
scanResponse: boolean;
|
|
58
|
+
allow: RegExp[];
|
|
59
|
+
}
|
|
60
|
+
export declare function readConfig(raw: unknown): PromptFirewallConfig;
|
|
53
61
|
declare const plugin: Plugin;
|
|
54
62
|
export default plugin;
|
|
55
63
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/prompt-firewall/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,OAAO,KAAK,EAAE,MAAM,EAAa,MAAM,kBAAkB,CAAC;AA+B1D,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,CAcxE;AAED,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAWjG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/prompt-firewall/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,OAAO,KAAK,EAAE,MAAM,EAAa,MAAM,kBAAkB,CAAC;AA+B1D,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,CAcxE;AAED,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAWjG;AA0CD,KAAK,YAAY,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEhD,UAAU,oBAAoB;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,YAAY,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,oBAAoB,CA0B7D;AAgCD,QAAA,MAAM,MAAM,EAAE,MAkNb,CAAC;eAEa,MAAM"}
|
package/dist/prompt-firewall.js
CHANGED
|
@@ -76,7 +76,7 @@ function redactDeep(value, allow, counter) {
|
|
|
76
76
|
function readConfig(raw) {
|
|
77
77
|
const base = {
|
|
78
78
|
enabled: false,
|
|
79
|
-
mode: "
|
|
79
|
+
mode: "redact",
|
|
80
80
|
scanResponse: true,
|
|
81
81
|
allow: []
|
|
82
82
|
};
|
|
@@ -91,7 +91,7 @@ function readConfig(raw) {
|
|
|
91
91
|
}) : [];
|
|
92
92
|
return {
|
|
93
93
|
enabled: r["enabled"] === true,
|
|
94
|
-
mode: r["mode"] === "
|
|
94
|
+
mode: r["mode"] === "warn" ? "warn" : r["mode"] === "block" ? "block" : "redact",
|
|
95
95
|
scanResponse: r["scanResponse"] !== false,
|
|
96
96
|
allow
|
|
97
97
|
};
|
|
@@ -109,10 +109,10 @@ var state = {
|
|
|
109
109
|
var plugin = {
|
|
110
110
|
name: "prompt-firewall",
|
|
111
111
|
version: "0.1.0",
|
|
112
|
-
description: "Scans the provider wire for credential leaks before context reaches the LLM API (wrapProviderRunner); warn/
|
|
112
|
+
description: "Scans the provider wire for credential leaks before context reaches the LLM API (wrapProviderRunner); redact/warn/block. Opt-in; redact by default.",
|
|
113
113
|
apiVersion: "^0.1.10",
|
|
114
114
|
capabilities: { tools: true },
|
|
115
|
-
defaultConfig: { enabled: false, mode: "
|
|
115
|
+
defaultConfig: { enabled: false, mode: "redact", scanResponse: true, allow: [] },
|
|
116
116
|
configSchema: {
|
|
117
117
|
type: "object",
|
|
118
118
|
properties: {
|
|
@@ -124,8 +124,8 @@ var plugin = {
|
|
|
124
124
|
mode: {
|
|
125
125
|
type: "string",
|
|
126
126
|
enum: ["warn", "redact", "block"],
|
|
127
|
-
default: "
|
|
128
|
-
description: "
|
|
127
|
+
default: "redact",
|
|
128
|
+
description: "redact (default) = strip secrets from the request/response; warn = detect only; block = refuse the request."
|
|
129
129
|
},
|
|
130
130
|
scanResponse: {
|
|
131
131
|
type: "boolean",
|
|
@@ -294,5 +294,6 @@ var prompt_firewall_default = plugin;
|
|
|
294
294
|
export {
|
|
295
295
|
prompt_firewall_default as default,
|
|
296
296
|
detectSecrets,
|
|
297
|
+
readConfig,
|
|
297
298
|
redactSecrets
|
|
298
299
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/refactor-suggester/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAS/C,MAAM,MAAM,SAAS,GACjB,eAAe,GACf,cAAc,GACd,iBAAiB,GACjB,cAAc,GACd,aAAa,CAAC;AAElB,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/refactor-suggester/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAS/C,MAAM,MAAM,SAAS,GACjB,eAAe,GACf,cAAc,GACd,iBAAiB,GACjB,cAAc,GACd,aAAa,CAAC;AAElB,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AA6ND,QAAA,MAAM,MAAM,EAAE,MA4Nb,CAAC;eAEa,MAAM"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// src/refactor-suggester/index.ts
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 } from "node:path";
|
|
4
4
|
|
|
5
5
|
// src/runtime/index.ts
|
|
6
|
-
import {
|
|
6
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
7
|
+
import { basename, extname, isAbsolute, relative, resolve } from "node:path";
|
|
7
8
|
var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
8
9
|
function hasLeadingDash(arg) {
|
|
9
10
|
return arg.length > 0 && arg.startsWith("-");
|
|
@@ -18,6 +19,47 @@ function withinProjectPath(projectRoot, candidate) {
|
|
|
18
19
|
function withinProject(p) {
|
|
19
20
|
return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
|
|
20
21
|
}
|
|
22
|
+
var DEFAULT_EXCLUDE_DIRS = ["node_modules", "dist", ".git", "coverage"];
|
|
23
|
+
function collectSourceFiles(root, opts) {
|
|
24
|
+
const files = [];
|
|
25
|
+
if (!existsSync(root)) return files;
|
|
26
|
+
const s = statSync(root);
|
|
27
|
+
if (s.isFile()) {
|
|
28
|
+
if (matchesExtension(root, opts.extensions)) files.push(root);
|
|
29
|
+
return files;
|
|
30
|
+
}
|
|
31
|
+
if (!s.isDirectory()) return files;
|
|
32
|
+
const exclude = opts.excludeDirs ?? DEFAULT_EXCLUDE_DIRS;
|
|
33
|
+
function walk(dir, depth) {
|
|
34
|
+
if (opts.maxDepth !== void 0 && depth > opts.maxDepth) return;
|
|
35
|
+
let entries;
|
|
36
|
+
try {
|
|
37
|
+
entries = readdirSync(dir);
|
|
38
|
+
} catch {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
for (const entry of entries) {
|
|
42
|
+
if (exclude.includes(entry)) continue;
|
|
43
|
+
const full = resolve(dir, entry);
|
|
44
|
+
let st;
|
|
45
|
+
try {
|
|
46
|
+
st = statSync(full);
|
|
47
|
+
} catch {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (st.isDirectory()) {
|
|
51
|
+
walk(full, depth + 1);
|
|
52
|
+
} else if (st.isFile() && matchesExtension(full, opts.extensions)) {
|
|
53
|
+
files.push(full);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
walk(root, 0);
|
|
58
|
+
return files;
|
|
59
|
+
}
|
|
60
|
+
function matchesExtension(p, exts) {
|
|
61
|
+
return exts.includes(extname(p).toLowerCase());
|
|
62
|
+
}
|
|
21
63
|
|
|
22
64
|
// src/refactor-suggester/index.ts
|
|
23
65
|
var API_VERSION = "^0.1.10";
|
|
@@ -58,44 +100,6 @@ function readConfig(raw) {
|
|
|
58
100
|
function normalizeExtensions(exts) {
|
|
59
101
|
return exts.map((e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`);
|
|
60
102
|
}
|
|
61
|
-
function matchesExtension(p, exts) {
|
|
62
|
-
return exts.includes(extname(p).toLowerCase());
|
|
63
|
-
}
|
|
64
|
-
function collectSourceFiles(root, exts) {
|
|
65
|
-
const files = [];
|
|
66
|
-
if (!existsSync(root)) return files;
|
|
67
|
-
const s = statSync(root);
|
|
68
|
-
if (s.isFile()) {
|
|
69
|
-
if (matchesExtension(root, exts)) files.push(root);
|
|
70
|
-
return files;
|
|
71
|
-
}
|
|
72
|
-
if (!s.isDirectory()) return files;
|
|
73
|
-
function walk(dir) {
|
|
74
|
-
let entries;
|
|
75
|
-
try {
|
|
76
|
-
entries = readdirSync(dir);
|
|
77
|
-
} catch {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
for (const entry of entries) {
|
|
81
|
-
if (entry === "node_modules" || entry === "dist" || entry === ".git" || entry === "coverage") continue;
|
|
82
|
-
const full = resolve2(dir, entry);
|
|
83
|
-
let st;
|
|
84
|
-
try {
|
|
85
|
-
st = statSync(full);
|
|
86
|
-
} catch {
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
if (st.isDirectory()) {
|
|
90
|
-
walk(full);
|
|
91
|
-
} else if (st.isFile() && matchesExtension(full, exts)) {
|
|
92
|
-
files.push(full);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
walk(root);
|
|
97
|
-
return files;
|
|
98
|
-
}
|
|
99
103
|
function toPosix(p) {
|
|
100
104
|
return p.replace(/\\/g, "/");
|
|
101
105
|
}
|
|
@@ -189,7 +193,7 @@ function scanPath(rawPath, cfg) {
|
|
|
189
193
|
const root = process.cwd();
|
|
190
194
|
const resolved = isAbsolute2(rawPath) ? resolve2(rawPath) : resolve2(root, rawPath);
|
|
191
195
|
const exts = normalizeExtensions(cfg.extensions);
|
|
192
|
-
const files = collectSourceFiles(resolved, exts);
|
|
196
|
+
const files = collectSourceFiles(resolved, { extensions: exts });
|
|
193
197
|
const suggestions = [];
|
|
194
198
|
for (const p of files) {
|
|
195
199
|
try {
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -133,4 +133,28 @@ export declare function withinProject(p: string): boolean;
|
|
|
133
133
|
* Returns the absolute path or `null`.
|
|
134
134
|
*/
|
|
135
135
|
export declare function locateRunnerEntry(runtime: LanguageRuntime, projectRoot: string): string | null;
|
|
136
|
+
export interface CollectOptions {
|
|
137
|
+
/** File extensions to include (e.g. ['.ts', '.tsx', '.js']). */
|
|
138
|
+
extensions: string[];
|
|
139
|
+
/** Directory names to skip entirely. Default skips node_modules, dist, .git, coverage. */
|
|
140
|
+
excludeDirs?: string[] | undefined;
|
|
141
|
+
/** Maximum recursion depth. Unlimited when omitted. */
|
|
142
|
+
maxDepth?: number | undefined;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Recursively collect files under `root` whose extension is in
|
|
146
|
+
* `opts.extensions`. Skips directories named in `opts.excludeDirs`
|
|
147
|
+
* (defaulting to node_modules, dist, .git, coverage) and limits depth
|
|
148
|
+
* when `opts.maxDepth` is set.
|
|
149
|
+
*
|
|
150
|
+
* Shared by 6+ plugin source-scan tools that previously duplicated
|
|
151
|
+
* this implementation identically.
|
|
152
|
+
*/
|
|
153
|
+
export declare function collectSourceFiles(root: string, opts: CollectOptions): string[];
|
|
154
|
+
/**
|
|
155
|
+
* Check whether `p` has one of the given extensions (case-insensitive).
|
|
156
|
+
* Replaces a 6-copy helper that was duplicated identically across
|
|
157
|
+
* source-scan plugins.
|
|
158
|
+
*/
|
|
159
|
+
export declare function matchesExtension(p: string, exts: string[]): boolean;
|
|
136
160
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAMH,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,uBAAuB,EACvB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,GACvB,MAAM,UAAU,CAAC;AAElB,MAAM,MAAM,UAAU,GAClB,YAAY,GACZ,YAAY,GACZ,QAAQ,GACR,IAAI,GACJ,MAAM,GACN,OAAO,GACP,MAAM,GACN,MAAM,GACN,QAAQ,GACR,QAAQ,GACR,SAAS,CAAC;AAEd,MAAM,MAAM,gBAAgB,GACxB,KAAK,GACL,MAAM,GACN,MAAM,GACN,KAAK,GACL,KAAK,GACL,QAAQ,GACR,IAAI,GACJ,OAAO,GACP,KAAK,GACL,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,MAAM,CAAC;AAEX,MAAM,WAAW,eAAe;IAC9B,qDAAqD;IACrD,EAAE,EAAE,UAAU,CAAC;IACf;;;;OAIG;IACH,cAAc,EAAE,gBAAgB,CAAC;IACjC;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IACzC;;;;;OAKG;IACH,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAW,SAAQ,cAAc;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,wEAAwE;IACxE,UAAU,EAAE,OAAO,CAAC;CACrB;AAkDD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,cAAmB,GAC3B,MAAM,GAAG,IAAI,CAKf;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,cAAmB,GAC3B,eAAe,GAAG,IAAI,CAoExB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"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"}
|