@wrongstack/plugins 0.308.6 → 0.309.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.
Files changed (51) hide show
  1. package/dist/accessibility-auditor.js +26 -82
  2. package/dist/agent-handoff.js +16 -26
  3. package/dist/auto-i18n-extractor.js +19 -23
  4. package/dist/branch-guard.js +19 -111
  5. package/dist/changelog-writer.js +20 -133
  6. package/dist/code-metrics.js +26 -71
  7. package/dist/commit-validator.js +16 -14
  8. package/dist/config-validator.js +18 -22
  9. package/dist/cost-tracker.js +15 -96
  10. package/dist/dead-code-detector.js +27 -31
  11. package/dist/dependency-vulnerability-gate.js +18 -15
  12. package/dist/diff-summary.js +19 -128
  13. package/dist/doc-sync-guard.js +24 -39
  14. package/dist/duplicate-code-detector.js +30 -169
  15. package/dist/feature-flag-tracker.js +26 -71
  16. package/dist/file-watcher.js +19 -23
  17. package/dist/format-on-save.js +25 -214
  18. package/dist/import-organizer.js +31 -106
  19. package/dist/index.js +452 -1236
  20. package/dist/interface-contract-guard.js +26 -71
  21. package/dist/lint-gate.js +19 -111
  22. package/dist/migration-planner.js +28 -120
  23. package/dist/notify-hub.js +18 -28
  24. package/dist/path-guard.js +27 -102
  25. package/dist/pr-drafter.js +18 -16
  26. package/dist/prompt-firewall.js +8 -205
  27. package/dist/refactor-suggester.js +27 -166
  28. package/dist/release-notes-generator.js +6 -35
  29. package/dist/runtime/bounded-map.d.ts +2 -85
  30. package/dist/runtime/credential-patterns.d.ts +2 -41
  31. package/dist/runtime/h1-state.d.ts +2 -61
  32. package/dist/runtime/handles.d.ts +2 -45
  33. package/dist/runtime/index.d.ts +8 -180
  34. package/dist/runtime/llm.d.ts +2 -43
  35. package/dist/runtime/local-bin.d.ts +2 -119
  36. package/dist/runtime/redos-guard.d.ts +2 -68
  37. package/dist/runtime/safe-json.d.ts +2 -24
  38. package/dist/runtime/sandbox.d.ts +2 -58
  39. package/dist/runtime.js +1 -868
  40. package/dist/schema-evolution-guard.js +20 -24
  41. package/dist/secret-scanner.js +22 -146
  42. package/dist/security-hotspot-scanner.js +24 -154
  43. package/dist/session-recap.js +16 -14
  44. package/dist/spec-linker.js +19 -17
  45. package/dist/template-engine.js +22 -37
  46. package/dist/test-coverage-gate.js +19 -34
  47. package/dist/test-generator.js +6 -35
  48. package/dist/test-runner-gate.js +34 -143
  49. package/dist/todo-listener.js +17 -38
  50. package/dist/type-gate.js +23 -311
  51. package/package.json +4 -3
@@ -1,79 +1,22 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __copyProps = (to, from, except, desc) => {
6
+ if (from && typeof from === "object" || typeof from === "function") {
7
+ for (let key of __getOwnPropNames(from))
8
+ if (!__hasOwnProp.call(to, key) && key !== except)
9
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
10
+ }
11
+ return to;
12
+ };
13
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
14
+
1
15
  // src/runtime/redos-guard.ts
2
- import { Worker } from "node:worker_threads";
3
- function withReDoSGuard(re, input, budgetMs = 50, options = {}) {
4
- const opts = { budgetMs, ...options };
5
- const start = Date.now();
6
- const workerSource = buildWorkerSource(re.source, input, re.flags);
7
- const worker = new Worker(workerSource, {
8
- eval: true,
9
- name: `redos-guard:${re.source.slice(0, 32)}`
10
- });
11
- return new Promise((resolve3) => {
12
- let settled = false;
13
- const onMessage = (msg) => {
14
- if (settled) return;
15
- settled = true;
16
- clearTimeout(timer);
17
- worker.terminate().catch(() => {
18
- });
19
- if (!msg.ok) {
20
- resolve3({ timedOut: true, match: null });
21
- return;
22
- }
23
- resolve3({ timedOut: false, match: msg.match });
24
- };
25
- const onError = () => {
26
- if (settled) return;
27
- settled = true;
28
- clearTimeout(timer);
29
- worker.terminate().catch(() => {
30
- });
31
- resolve3({ timedOut: true, match: null });
32
- };
33
- const timer = setTimeout(() => {
34
- if (settled) return;
35
- settled = true;
36
- const elapsedMs = Date.now() - start;
37
- worker.terminate().catch(() => {
38
- });
39
- try {
40
- opts.onTimeout?.({
41
- regex: re,
42
- input,
43
- budgetMs: opts.budgetMs,
44
- elapsedMs
45
- });
46
- } catch {
47
- }
48
- resolve3({ timedOut: true, match: null });
49
- }, opts.budgetMs);
50
- timer.unref?.();
51
- worker.on("message", onMessage);
52
- worker.on("error", onError);
53
- });
54
- }
55
- function buildWorkerSource(source, input, flags) {
56
- const S = JSON.stringify(source);
57
- const I = JSON.stringify(input);
58
- const F = JSON.stringify(flags);
59
- return `
60
- const { parentPort } = require('node:worker_threads');
61
- const source = ${S};
62
- const input = ${I};
63
- const flags = ${F};
64
- try {
65
- const re = new RegExp(source, flags);
66
- const match = re.exec(input);
67
- // parentPort.postMessage, NOT bare postMessage: with eval:true
68
- // workers this Node version does not expose the bare postMessage
69
- // global \u2014 the worker throws ReferenceError at startup and the
70
- // host misreads it as a timeout (positive-path regression).
71
- parentPort.postMessage({ ok: true, match });
72
- } catch (err) {
73
- parentPort.postMessage({ ok: false, error: err && err.message ? err.message : String(err) });
74
- }
75
- `;
76
- }
16
+ import {
17
+ withReDoSGuard,
18
+ guardedMatcher
19
+ } from "@wrongstack/plugin-sdk/runtime";
77
20
 
78
21
  // src/path-guard/glob.ts
79
22
  var GLOB_REDOS_BUDGET_MS = 250;
@@ -189,8 +132,8 @@ function isRootPathScope(path) {
189
132
  function isDirectoryAmbiguousPath(path) {
190
133
  const normalized = normalizePath(path).replace(/\/$/, "");
191
134
  if (isRootPathScope(normalized)) return true;
192
- const basename2 = normalized.slice(normalized.lastIndexOf("/") + 1);
193
- return path.endsWith("/") || basename2.length > 0 && !basename2.includes(".");
135
+ const basename = normalized.slice(normalized.lastIndexOf("/") + 1);
136
+ return path.endsWith("/") || basename.length > 0 && !basename.includes(".");
194
137
  }
195
138
  function hasConfiguredProtectedDescendant(path, patterns) {
196
139
  const normalized = normalizePath(path).replace(/\/$/, "").toLowerCase();
@@ -1365,38 +1308,20 @@ function operationLabel(toolName) {
1365
1308
 
1366
1309
  // src/path-guard/index.ts
1367
1310
  import { realpathSync } from "node:fs";
1368
- import { resolve as resolve2 } from "node:path";
1369
-
1370
- // src/runtime/index.ts
1371
- import { basename, extname, isAbsolute, relative, resolve } from "node:path";
1372
-
1373
- // src/runtime/local-bin.ts
1374
- import { buildWin32CmdShimInvocation, resolveWin32Command } from "@wrongstack/tools/win32";
1311
+ import { resolve } from "node:path";
1375
1312
 
1376
1313
  // src/runtime/index.ts
1377
- var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
1378
- function hasLeadingDash(arg) {
1379
- return arg.length > 0 && arg.startsWith("-");
1380
- }
1381
- function withinProjectPath(projectRoot, candidate) {
1382
- if (candidate.length === 0 || candidate.length > 4096) return false;
1383
- if (hasLeadingDash(candidate)) return false;
1384
- const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
1385
- const rel = relative(projectRoot, resolved);
1386
- return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
1387
- }
1388
- function withinProject(p) {
1389
- const cwd = process.cwd();
1390
- return withinProjectPath(cwd, p) || relative(cwd, p) === ".";
1391
- }
1314
+ var runtime_exports = {};
1315
+ __reExport(runtime_exports, runtime_star);
1316
+ import * as runtime_star from "@wrongstack/plugin-sdk/runtime";
1392
1317
 
1393
1318
  // src/path-guard/index.ts
1394
1319
  function isSymlinkEscape(path, cwd) {
1395
- if (!withinProject(path)) return false;
1320
+ if (!(0, runtime_exports.withinProject)(path)) return false;
1396
1321
  try {
1397
- const abs = resolve2(cwd ?? process.cwd(), path);
1322
+ const abs = resolve(cwd ?? process.cwd(), path);
1398
1323
  const real = realpathSync(abs);
1399
- return !withinProject(real);
1324
+ return !(0, runtime_exports.withinProject)(real);
1400
1325
  } catch {
1401
1326
  return false;
1402
1327
  }
@@ -1,24 +1,26 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __copyProps = (to, from, except, desc) => {
6
+ if (from && typeof from === "object" || typeof from === "function") {
7
+ for (let key of __getOwnPropNames(from))
8
+ if (!__hasOwnProp.call(to, key) && key !== except)
9
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
10
+ }
11
+ return to;
12
+ };
13
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
14
+
1
15
  // src/pr-drafter/index.ts
2
16
  import { execFile } from "node:child_process";
3
17
  import { mkdir, writeFile } from "node:fs/promises";
4
18
  import { dirname, isAbsolute, relative, resolve } from "node:path";
5
19
 
6
- // src/runtime/local-bin.ts
7
- import { buildWin32CmdShimInvocation, resolveWin32Command } from "@wrongstack/tools/win32";
8
-
9
- // src/runtime/handles.ts
10
- function releaseHandle(off) {
11
- if (off) {
12
- try {
13
- off();
14
- } catch {
15
- }
16
- }
17
- return null;
18
- }
19
-
20
20
  // src/runtime/index.ts
21
- var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
21
+ var runtime_exports = {};
22
+ __reExport(runtime_exports, runtime_star);
23
+ import * as runtime_star from "@wrongstack/plugin-sdk/runtime";
22
24
 
23
25
  // src/pr-drafter/index.ts
24
26
  var API_VERSION = "^0.1.10";
@@ -216,7 +218,7 @@ var plugin = {
216
218
  state.draftsWritten = 0;
217
219
  state.draftErrors = 0;
218
220
  state.stopInvocations = 0;
219
- state.stopHookUnregister = releaseHandle(state.stopHookUnregister);
221
+ state.stopHookUnregister = (0, runtime_exports.releaseHandle)(state.stopHookUnregister);
220
222
  for (const off of state.eventUnsubscribers) {
221
223
  try {
222
224
  off();
@@ -2,213 +2,16 @@
2
2
  import { performance } from "node:perf_hooks";
3
3
 
4
4
  // src/runtime/credential-patterns.ts
5
- var CREDENTIAL_PATTERNS = [
6
- // LLM provider keys
7
- {
8
- type: "anthropic_key",
9
- regex: /(?<![A-Za-z0-9])sk-ant-api\d+-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g
10
- },
11
- { type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?!ant)(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g },
12
- // GitHub. `ghp_` is only the personal-access-token prefix — the OAuth
13
- // (`gho_`), user-to-server (`ghu_`), server-to-server (`ghs_`) and
14
- // refresh (`ghr_`) tokens grant the same or broader access and were
15
- // previously not detected at all.
16
- { type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g },
17
- {
18
- type: "github_oauth_token",
19
- regex: /(?<![A-Za-z0-9])gh[ousr]_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g
20
- },
21
- { type: "github_pat_v2", regex: /(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{50,}(?![A-Za-z0-9])/g },
22
- // GitLab
23
- { type: "gitlab_pat", regex: /(?<![A-Za-z0-9])glpat-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g },
24
- {
25
- type: "gitlab_runner_token",
26
- regex: /(?<![A-Za-z0-9])glrt-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g
27
- },
28
- // npm — a leaked publish token is a supply-chain compromise.
29
- { type: "npm_token", regex: /(?<![A-Za-z0-9])npm_[A-Za-z0-9]{36}(?![A-Za-z0-9])/g },
30
- // AWS
31
- { type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g },
32
- // GCP
33
- { type: "gcp_key", regex: /(?<![A-Za-z0-9])AIza[0-9A-Za-z_-]{35}(?![A-Za-z0-9])/g },
34
- // Slack. `xoxe` (token-rotation) and `xapp` (app-level) were missing;
35
- // both are as sensitive as the bot/user tokens already covered.
36
- {
37
- type: "slack_token",
38
- regex: /(?<![A-Za-z0-9-])xox[abposer]-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g
39
- },
40
- { type: "slack_app_token", regex: /(?<![A-Za-z0-9-])xapp-\d-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g },
41
- {
42
- type: "slack_webhook",
43
- regex: /https:\/\/hooks\.slack\.com\/services\/T[A-Za-z0-9_-]+\/B[A-Za-z0-9_-]+\/[A-Za-z0-9]{16,}/g
44
- },
45
- // Stripe
46
- {
47
- type: "stripe_key",
48
- regex: /(?<![A-Za-z0-9])sk_(?:live|test)_[A-Za-z0-9]{24,}(?![A-Za-z0-9])/g
49
- },
50
- // Twilio
51
- { type: "twilio_sid", regex: /(?<![A-Za-z0-9])AC[a-f0-9]{32}(?![A-Za-z0-9])/g },
52
- // Telegram
53
- {
54
- type: "telegram_bot_token",
55
- regex: /(?:(?<![A-Za-z0-9_])|(?<=(?:^|[^A-Za-z0-9_])bot))\d+:[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g
56
- },
57
- // JWT
58
- {
59
- type: "jwt",
60
- regex: /(?<![A-Za-z0-9/+=])eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}(?![A-Za-z0-9/+=])/g
61
- },
62
- // Private keys
63
- {
64
- type: "private_key",
65
- regex: /(?:^|\n)(?:-----BEGIN (?:RSA|EC|OPENSSH|DSA)? ?PRIVATE KEY-----[\s\S]*?-----END (?:RSA|EC|OPENSSH|DSA)? ?PRIVATE KEY-----|-----BEGIN PGP PRIVATE KEY BLOCK-----[\s\S]*?-----END PGP PRIVATE KEY BLOCK-----)(?!\S)/g
66
- },
67
- // AI/ML provider tokens
68
- { type: "huggingface_token", regex: /(?<![A-Za-z0-9])hf_[A-Za-z0-9]{34}(?![A-Za-z0-9])/g },
69
- { type: "replicate_token", regex: /(?<![A-Za-z0-9])r8_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
70
- { type: "perplexity_key", regex: /(?<![A-Za-z0-9])pplx-[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
71
- { type: "groq_key", regex: /(?<![A-Za-z0-9])gsk_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
72
- // SaaS / infrastructure tokens — each grants API access on the user's
73
- // account, and each was previously invisible to this gate.
74
- { type: "sendgrid_key", regex: /(?<![A-Za-z0-9])SG\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g },
75
- { type: "digitalocean_token", regex: /(?<![A-Za-z0-9])dop_v1_[a-f0-9]{64}(?![A-Za-z0-9])/g },
76
- { type: "doppler_token", regex: /(?<![A-Za-z0-9])dp\.(?:pt|st|sa|scim|audit)\.[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
77
- { type: "shopify_token", regex: /(?<![A-Za-z0-9])shp(?:at|ca|pa|ss)_[a-fA-F0-9]{32}(?![A-Za-z0-9])/g },
78
- { type: "docker_pat", regex: /(?<![A-Za-z0-9])dckr_pat_[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g },
79
- { type: "linear_key", regex: /(?<![A-Za-z0-9])lin_api_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
80
- { type: "atlassian_token", regex: /(?<![A-Za-z0-9])ATATT3[A-Za-z0-9_\-=]{40,}(?![A-Za-z0-9_\-=])/g },
81
- { type: "square_token", regex: /(?<![A-Za-z0-9])(?:sq0(?:atp|csp)-|EAAA)[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g },
82
- {
83
- type: "azure_storage_key",
84
- regex: /AccountKey=[A-Za-z0-9+/]{80,}={0,2}/g
85
- },
86
- {
87
- type: "google_oauth_client_secret",
88
- regex: /(?<![A-Za-z0-9_-])GOCSPX-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g
89
- },
90
- // Bearer tokens
91
- {
92
- type: "bearer_token",
93
- regex: /(?<![A-Za-z0-9_.~+/-])Bearer\s+[A-Za-z0-9._~+/-]{12,512}=*(?![A-Za-z0-9._~+/-])/g
94
- },
95
- // Database URIs. Require password-bearing user-info; credential-free values stay scannable.
96
- { type: "mongodb_uri", regex: /mongodb(?:\+srv)?:\/\/[^\s:/@"'`]*:[^\s/@"'`]+@[^\s"'`]+/g },
97
- {
98
- type: "postgres_uri",
99
- // Query parsers decode percent-encoded parameter names. Recognize each
100
- // encoded character in `password` so mixed forms such as `pass%77ord`
101
- // cannot bypass detection while keeping the scan strictly bounded.
102
- regex: /postgres(?:ql)?:\/\/(?:[^\s:/@"'`]*:[^\s/@"'`]+@[^\s"'`]+|[^&\s?"'`#]{1,2048}\?(?:(?!(?:p|%70)(?:a|%61)(?:s|%73)(?:s|%73)(?:w|%77)(?:o|%6[fF])(?:r|%72)(?:d|%64)=)[^&\s#"'`]{1,256}&){0,32}(?:p|%70)(?:a|%61)(?:s|%73)(?:s|%73)(?:w|%77)(?:o|%6[fF])(?:r|%72)(?:d|%64)=[^&\s#"'`]{1,4096})/g
103
- },
104
- { type: "mysql_uri", regex: /mysql:\/\/[^\s:/@"'`]*:[^\s/@"'`]+@[^\s"'`]+/g },
105
- { type: "redis_uri", regex: /redis:\/\/[^\s:/@"'`]*:[^\s/@"'`]+@[^\s"'`]+/g },
106
- {
107
- // Credentials serialised as JSON, keyed rather than prefixed. Every other
108
- // entry in this table recognises a credential by its SHAPE (`ghp_`, `sk-`,
109
- // `eyJ`), which means a key with no distinctive prefix — Azure, a
110
- // self-hosted gateway, an Anthropic/Codex OAuth token — was invisible to
111
- // both surfaces. `prompt-firewall` guards the outgoing provider request, so
112
- // this is what stops a JSON-shaped tool result carrying such a value to a
113
- // third party.
114
- //
115
- // The key is matched in a LOOKBEHIND, so the reported match is the secret
116
- // itself and the pattern keeps zero capturing groups — `secret-scanner`
117
- // maps a combined-regex group index back to the pattern that fired, and an
118
- // inner group would shift that mapping (see the style note above, enforced
119
- // by credential-pattern-parity.test.ts).
120
- //
121
- // Mirrors `json_credential_key` in
122
- // `@wrongstack/core` → `src/security/secret-scrubber.ts`. Keep the two key
123
- // lists in step; the core side additionally preserves the key name when it
124
- // rewrites, which is why it is written with capture groups instead.
125
- type: "json_credential_key",
126
- regex: /(?<="[A-Za-z0-9_]{0,64}(?:apiKey|api_key|token|secret|password|authorization|bearer|private_key|access_token|refresh_token|client_secret)"\s{0,8}:\s{0,8}")[^"\\]{8,512}(?=")/gi
127
- }
128
- ];
129
- function cloneCredentialPatterns() {
130
- return CREDENTIAL_PATTERNS.map((p) => ({
131
- type: p.type,
132
- regex: new RegExp(p.regex.source, p.regex.flags)
133
- }));
134
- }
5
+ import {
6
+ cloneCredentialPatterns,
7
+ CREDENTIAL_PATTERNS
8
+ } from "@wrongstack/plugin-sdk/runtime";
135
9
 
136
10
  // src/runtime/redos-guard.ts
137
- import { Worker } from "node:worker_threads";
138
- function withReDoSGuard(re, input, budgetMs = 50, options = {}) {
139
- const opts = { budgetMs, ...options };
140
- const start = Date.now();
141
- const workerSource = buildWorkerSource(re.source, input, re.flags);
142
- const worker = new Worker(workerSource, {
143
- eval: true,
144
- name: `redos-guard:${re.source.slice(0, 32)}`
145
- });
146
- return new Promise((resolve) => {
147
- let settled = false;
148
- const onMessage = (msg) => {
149
- if (settled) return;
150
- settled = true;
151
- clearTimeout(timer);
152
- worker.terminate().catch(() => {
153
- });
154
- if (!msg.ok) {
155
- resolve({ timedOut: true, match: null });
156
- return;
157
- }
158
- resolve({ timedOut: false, match: msg.match });
159
- };
160
- const onError = () => {
161
- if (settled) return;
162
- settled = true;
163
- clearTimeout(timer);
164
- worker.terminate().catch(() => {
165
- });
166
- resolve({ timedOut: true, match: null });
167
- };
168
- const timer = setTimeout(() => {
169
- if (settled) return;
170
- settled = true;
171
- const elapsedMs = Date.now() - start;
172
- worker.terminate().catch(() => {
173
- });
174
- try {
175
- opts.onTimeout?.({
176
- regex: re,
177
- input,
178
- budgetMs: opts.budgetMs,
179
- elapsedMs
180
- });
181
- } catch {
182
- }
183
- resolve({ timedOut: true, match: null });
184
- }, opts.budgetMs);
185
- timer.unref?.();
186
- worker.on("message", onMessage);
187
- worker.on("error", onError);
188
- });
189
- }
190
- function buildWorkerSource(source, input, flags) {
191
- const S = JSON.stringify(source);
192
- const I = JSON.stringify(input);
193
- const F = JSON.stringify(flags);
194
- return `
195
- const { parentPort } = require('node:worker_threads');
196
- const source = ${S};
197
- const input = ${I};
198
- const flags = ${F};
199
- try {
200
- const re = new RegExp(source, flags);
201
- const match = re.exec(input);
202
- // parentPort.postMessage, NOT bare postMessage: with eval:true
203
- // workers this Node version does not expose the bare postMessage
204
- // global \u2014 the worker throws ReferenceError at startup and the
205
- // host misreads it as a timeout (positive-path regression).
206
- parentPort.postMessage({ ok: true, match });
207
- } catch (err) {
208
- parentPort.postMessage({ ok: false, error: err && err.message ? err.message : String(err) });
209
- }
210
- `;
211
- }
11
+ import {
12
+ withReDoSGuard,
13
+ guardedMatcher
14
+ } from "@wrongstack/plugin-sdk/runtime";
212
15
 
213
16
  // src/prompt-firewall/index.ts
214
17
  var KIND_ALIASES = {
@@ -1,164 +1,25 @@
1
- // src/refactor-suggester/index.ts
2
- import { readFile } from "node:fs/promises";
3
- import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 } from "node:path";
4
-
5
- // src/runtime/index.ts
6
- import { basename, extname, isAbsolute, relative, resolve } from "node:path";
7
-
8
- // src/runtime/local-bin.ts
9
- import { buildWin32CmdShimInvocation, resolveWin32Command } from "@wrongstack/tools/win32";
10
-
11
- // src/runtime/bounded-map.ts
12
- var BoundedMap = class {
13
- map = /* @__PURE__ */ new Map();
14
- max;
15
- ttlMs;
16
- now;
17
- /** Entries dropped to stay under `max`. Surfaced by plugin health(). */
18
- evictions = 0;
19
- constructor(options) {
20
- const normalizedMax = Math.floor(options.max);
21
- this.max = Number.isSafeInteger(normalizedMax) ? Math.max(1, normalizedMax) : 1;
22
- this.ttlMs = options.ttlMs;
23
- this.now = options.now ?? Date.now;
24
- }
25
- expired(entry) {
26
- return this.ttlMs !== void 0 && this.now() - entry.storedAt > this.ttlMs;
27
- }
28
- get(key) {
29
- const entry = this.map.get(key);
30
- if (entry === void 0) return void 0;
31
- if (this.expired(entry)) {
32
- this.map.delete(key);
33
- return void 0;
34
- }
35
- this.map.delete(key);
36
- this.map.set(key, entry);
37
- return entry.value;
38
- }
39
- /**
40
- * Read without promoting the key to most-recently-used. Use for
41
- * diagnostics that must not perturb the eviction order.
42
- */
43
- peek(key) {
44
- const entry = this.map.get(key);
45
- if (entry === void 0 || this.expired(entry)) return void 0;
46
- return entry.value;
47
- }
48
- has(key) {
49
- const entry = this.map.get(key);
50
- if (entry === void 0) return false;
51
- if (this.expired(entry)) {
52
- this.map.delete(key);
53
- return false;
54
- }
55
- return true;
56
- }
57
- set(key, value) {
58
- this.map.delete(key);
59
- this.map.set(key, { value, storedAt: this.now() });
60
- while (this.map.size > this.max) {
61
- const coldest = this.map.keys().next().value;
62
- if (coldest === void 0) break;
63
- this.map.delete(coldest);
64
- this.evictions += 1;
65
- }
66
- return this;
67
- }
68
- delete(key) {
69
- return this.map.delete(key);
70
- }
71
- clear() {
72
- this.map.clear();
73
- this.evictions = 0;
74
- }
75
- get size() {
76
- return this.map.size;
77
- }
78
- /** How many entries have been dropped to respect `max`, since the last clear. */
79
- get evictionCount() {
80
- return this.evictions;
81
- }
82
- /** Drop every expired entry. Cheap enough to call from a status tool. */
83
- prune() {
84
- if (this.ttlMs === void 0) return 0;
85
- let removed = 0;
86
- for (const [key, entry] of this.map) {
87
- if (this.expired(entry)) {
88
- this.map.delete(key);
89
- removed += 1;
90
- }
91
- }
92
- return removed;
93
- }
94
- /** Live (non-expired) entries, coldest first. */
95
- *entries() {
96
- for (const [key, entry] of this.map) {
97
- if (!this.expired(entry)) yield [key, entry.value];
98
- }
99
- }
100
- [Symbol.iterator]() {
101
- return this.entries();
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __copyProps = (to, from, except, desc) => {
6
+ if (from && typeof from === "object" || typeof from === "function") {
7
+ for (let key of __getOwnPropNames(from))
8
+ if (!__hasOwnProp.call(to, key) && key !== except)
9
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
102
10
  }
11
+ return to;
103
12
  };
13
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
14
+
15
+ // src/refactor-suggester/index.ts
16
+ import { readFile } from "node:fs/promises";
17
+ import { isAbsolute, relative, resolve } from "node:path";
104
18
 
105
19
  // src/runtime/index.ts
106
- var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
107
- function hasLeadingDash(arg) {
108
- return arg.length > 0 && arg.startsWith("-");
109
- }
110
- function withinProjectPath(projectRoot, candidate) {
111
- if (candidate.length === 0 || candidate.length > 4096) return false;
112
- if (hasLeadingDash(candidate)) return false;
113
- const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
114
- const rel = relative(projectRoot, resolved);
115
- return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
116
- }
117
- function withinProject(p) {
118
- const cwd = process.cwd();
119
- return withinProjectPath(cwd, p) || relative(cwd, p) === ".";
120
- }
121
- var DEFAULT_EXCLUDE_DIRS = ["node_modules", "dist", ".git", "coverage"];
122
- async function collectSourceFilesAsync(root, opts) {
123
- const { readdir, stat } = await import("node:fs/promises");
124
- const files = [];
125
- try {
126
- const s = await stat(root);
127
- if (s.isFile()) {
128
- if (matchesExtension(root, opts.extensions)) files.push(root);
129
- return files;
130
- }
131
- if (!s.isDirectory()) return files;
132
- } catch {
133
- return files;
134
- }
135
- const exclude = opts.excludeDirs ?? DEFAULT_EXCLUDE_DIRS;
136
- const excludeSet = new Set(exclude);
137
- async function walk(dir, depth) {
138
- if (opts.maxDepth !== void 0 && depth > opts.maxDepth) return;
139
- let entries;
140
- try {
141
- entries = await readdir(dir, { withFileTypes: true });
142
- } catch {
143
- return;
144
- }
145
- entries.sort((a, b) => a.name.localeCompare(b.name));
146
- for (const entry of entries) {
147
- if (excludeSet.has(entry.name)) continue;
148
- const full = resolve(dir, entry.name);
149
- if (entry.isDirectory()) {
150
- await walk(full, depth + 1);
151
- } else if (entry.isFile() && matchesExtension(full, opts.extensions)) {
152
- files.push(full);
153
- }
154
- }
155
- }
156
- await walk(root, 0);
157
- return files;
158
- }
159
- function matchesExtension(p, exts) {
160
- return exts.includes(extname(p).toLowerCase());
161
- }
20
+ var runtime_exports = {};
21
+ __reExport(runtime_exports, runtime_star);
22
+ import * as runtime_star from "@wrongstack/plugin-sdk/runtime";
162
23
 
163
24
  // src/refactor-suggester/index.ts
164
25
  var API_VERSION = "^0.1.10";
@@ -170,7 +31,7 @@ var state = {
170
31
  warningCount: 0,
171
32
  errorCount: 0,
172
33
  hookUnregister: null,
173
- lastHookWarning: new BoundedMap({ max: 512, ttlMs: HOOK_WARNING_COOLDOWN_MS })
34
+ lastHookWarning: new runtime_exports.BoundedMap({ max: 512, ttlMs: HOOK_WARNING_COOLDOWN_MS })
174
35
  };
175
36
  var DEFAULTS = {
176
37
  enabled: false,
@@ -204,7 +65,7 @@ function toPosix(p) {
204
65
  return p.replace(/\\/g, "/");
205
66
  }
206
67
  function relativePath(p) {
207
- return toPosix(relative2(process.cwd(), p));
68
+ return toPosix(relative(process.cwd(), p));
208
69
  }
209
70
  function leadingIndentLevel(line) {
210
71
  const leading = line.match(/^(\s*)/)?.[1] ?? "";
@@ -291,9 +152,9 @@ function detectSmells(filePath, content, rules) {
291
152
  }
292
153
  async function scanPath(rawPath, cfg) {
293
154
  const root = process.cwd();
294
- const resolved = isAbsolute2(rawPath) ? resolve2(rawPath) : resolve2(root, rawPath);
155
+ const resolved = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);
295
156
  const exts = normalizeExtensions(cfg.extensions);
296
- const files = await collectSourceFilesAsync(resolved, { extensions: exts });
157
+ const files = await (0, runtime_exports.collectSourceFilesAsync)(resolved, { extensions: exts });
297
158
  const suggestions = [];
298
159
  let scannedFiles = 0;
299
160
  let truncated = false;
@@ -371,14 +232,14 @@ var plugin = {
371
232
  const inp = input.toolInput ?? {};
372
233
  const sourcePath = inp["path"];
373
234
  if (!sourcePath || typeof sourcePath !== "string") return;
374
- if (!withinProject(sourcePath)) return;
235
+ if (!(0, runtime_exports.withinProject)(sourcePath)) return;
375
236
  const exts = normalizeExtensions(cfg.extensions);
376
- if (!matchesExtension(sourcePath, exts)) return;
237
+ if (!(0, runtime_exports.matchesExtension)(sourcePath, exts)) return;
377
238
  state.hookInvocationCount += 1;
378
239
  const now = Date.now();
379
240
  const lastWarning = state.lastHookWarning.get(sourcePath);
380
241
  if (lastWarning !== void 0 && now - lastWarning < HOOK_WARNING_COOLDOWN_MS) return;
381
- const resolved = resolve2(process.cwd(), sourcePath);
242
+ const resolved = resolve(process.cwd(), sourcePath);
382
243
  let content;
383
244
  try {
384
245
  content = await readFile(resolved, "utf-8");
@@ -411,7 +272,7 @@ var plugin = {
411
272
  async execute(input) {
412
273
  if (!cfg.enabled) return { ok: false, error: "refactor-suggester is disabled" };
413
274
  const rawPath = typeof input.path === "string" ? input.path : ".";
414
- if (!withinProject(rawPath)) {
275
+ if (!(0, runtime_exports.withinProject)(rawPath)) {
415
276
  return { ok: false, error: "path is outside the project root" };
416
277
  }
417
278
  state.scanCount += 1;
@@ -425,7 +286,7 @@ var plugin = {
425
286
  state.suggestionCount += result.suggestions.length;
426
287
  return {
427
288
  ok: true,
428
- path: relativePath(resolve2(process.cwd(), rawPath)),
289
+ path: relativePath(resolve(process.cwd(), rawPath)),
429
290
  scannedFiles: result.scannedFiles,
430
291
  discoveredFiles: result.discoveredFiles,
431
292
  // Say so when the cap stopped the walk early: a partial scan