@wrongstack/plugins 0.295.0 → 0.295.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.
Files changed (90) hide show
  1. package/dist/accessibility-auditor/index.d.ts.map +1 -1
  2. package/dist/accessibility-auditor.js +19 -2
  3. package/dist/agent-handoff/index.d.ts.map +1 -1
  4. package/dist/agent-handoff.js +30 -1
  5. package/dist/auto-doc/index.d.ts.map +1 -1
  6. package/dist/auto-doc.js +3 -1
  7. package/dist/auto-i18n-extractor.js +5 -0
  8. package/dist/branch-guard/index.d.ts.map +1 -1
  9. package/dist/branch-guard.js +103 -1
  10. package/dist/changelog-writer/index.d.ts.map +1 -1
  11. package/dist/changelog-writer.js +137 -3
  12. package/dist/checkpoint/index.d.ts +2 -0
  13. package/dist/checkpoint/index.d.ts.map +1 -1
  14. package/dist/checkpoint.js +34 -8
  15. package/dist/code-metrics.js +5 -0
  16. package/dist/commit-validator/index.d.ts.map +1 -1
  17. package/dist/commit-validator.js +17 -7
  18. package/dist/context-pins/index.d.ts.map +1 -1
  19. package/dist/context-pins.js +6 -5
  20. package/dist/cost-tracker/index.d.ts.map +1 -1
  21. package/dist/cost-tracker.js +101 -1
  22. package/dist/cron/index.d.ts.map +1 -1
  23. package/dist/cron.js +1 -0
  24. package/dist/dead-code-detector.js +5 -0
  25. package/dist/dependency-vulnerability-gate/index.d.ts.map +1 -1
  26. package/dist/dependency-vulnerability-gate.js +43 -8
  27. package/dist/diff-summary/index.d.ts.map +1 -1
  28. package/dist/diff-summary.js +100 -1
  29. package/dist/doc-sync-guard.js +5 -0
  30. package/dist/duplicate-code-detector/index.d.ts.map +1 -1
  31. package/dist/duplicate-code-detector.js +102 -2
  32. package/dist/feature-flag-tracker/index.d.ts.map +1 -1
  33. package/dist/feature-flag-tracker.js +30 -4
  34. package/dist/file-watcher/index.d.ts.map +1 -1
  35. package/dist/file-watcher.js +6 -7
  36. package/dist/format-on-save/index.d.ts +2 -0
  37. package/dist/format-on-save/index.d.ts.map +1 -1
  38. package/dist/format-on-save.js +288 -59
  39. package/dist/import-organizer/index.d.ts.map +1 -1
  40. package/dist/import-organizer.js +140 -20
  41. package/dist/index.js +1265 -544
  42. package/dist/interface-contract-guard/index.d.ts.map +1 -1
  43. package/dist/interface-contract-guard.js +60 -26
  44. package/dist/knowledge-graph/index.d.ts.map +1 -1
  45. package/dist/knowledge-graph.js +7 -6
  46. package/dist/lint-gate/index.d.ts.map +1 -1
  47. package/dist/lint-gate.js +103 -1
  48. package/dist/loop-breaker/index.d.ts.map +1 -1
  49. package/dist/loop-breaker.js +15 -5
  50. package/dist/migration-planner.js +3 -0
  51. package/dist/notify-hub/index.d.ts.map +1 -1
  52. package/dist/notify-hub.js +41 -4
  53. package/dist/prompt-firewall/index.d.ts.map +1 -1
  54. package/dist/prompt-firewall.js +140 -11
  55. package/dist/refactor-suggester/index.d.ts.map +1 -1
  56. package/dist/refactor-suggester.js +119 -4
  57. package/dist/runtime/bounded-map.d.ts +86 -0
  58. package/dist/runtime/bounded-map.d.ts.map +1 -0
  59. package/dist/runtime/credential-patterns.d.ts +42 -0
  60. package/dist/runtime/credential-patterns.d.ts.map +1 -0
  61. package/dist/runtime/index.d.ts +3 -0
  62. package/dist/runtime/index.d.ts.map +1 -1
  63. package/dist/runtime/local-bin.d.ts +120 -0
  64. package/dist/runtime/local-bin.d.ts.map +1 -0
  65. package/dist/runtime/safe-json.d.ts +25 -0
  66. package/dist/runtime/safe-json.d.ts.map +1 -0
  67. package/dist/runtime.js +291 -22
  68. package/dist/schema-evolution-guard.js +5 -0
  69. package/dist/secret-scanner/index.d.ts.map +1 -1
  70. package/dist/secret-scanner.js +175 -48
  71. package/dist/security-hotspot-scanner/index.d.ts.map +1 -1
  72. package/dist/security-hotspot-scanner.js +132 -1
  73. package/dist/semantic-search-indexer/index.d.ts.map +1 -1
  74. package/dist/semantic-search-indexer.js +7 -1
  75. package/dist/semver-bump/index.d.ts.map +1 -1
  76. package/dist/semver-bump.js +21 -2
  77. package/dist/template-engine/index.d.ts.map +1 -1
  78. package/dist/template-engine.js +30 -2
  79. package/dist/test-coverage-gate.js +5 -0
  80. package/dist/test-runner-gate/index.d.ts.map +1 -1
  81. package/dist/test-runner-gate.js +130 -8
  82. package/dist/todo-listener/index.d.ts.map +1 -1
  83. package/dist/todo-listener.js +30 -1
  84. package/dist/todo-tracker/index.d.ts.map +1 -1
  85. package/dist/todo-tracker.js +3 -4
  86. package/dist/token-throttle/index.d.ts.map +1 -1
  87. package/dist/token-throttle.js +17 -2
  88. package/dist/type-gate/index.d.ts.map +1 -1
  89. package/dist/type-gate.js +96 -16
  90. package/package.json +3 -3
@@ -2,9 +2,109 @@
2
2
  import { execFile } from "node:child_process";
3
3
  import { access } from "node:fs/promises";
4
4
  import { basename as basename2, dirname, isAbsolute as isAbsolute2, join } from "node:path";
5
+ import { buildWin32CmdShimInvocation as buildWin32CmdShimInvocation2, resolveWin32Command as resolveWin32Command2 } from "@wrongstack/tools/win32";
5
6
 
6
7
  // src/runtime/index.ts
7
8
  import { basename, extname, isAbsolute, relative, resolve } from "node:path";
9
+
10
+ // src/runtime/local-bin.ts
11
+ import { buildWin32CmdShimInvocation, resolveWin32Command } from "@wrongstack/tools/win32";
12
+
13
+ // src/runtime/bounded-map.ts
14
+ var BoundedMap = class {
15
+ map = /* @__PURE__ */ new Map();
16
+ max;
17
+ ttlMs;
18
+ now;
19
+ /** Entries dropped to stay under `max`. Surfaced by plugin health(). */
20
+ evictions = 0;
21
+ constructor(options) {
22
+ const normalizedMax = Math.floor(options.max);
23
+ this.max = Number.isSafeInteger(normalizedMax) ? Math.max(1, normalizedMax) : 1;
24
+ this.ttlMs = options.ttlMs;
25
+ this.now = options.now ?? Date.now;
26
+ }
27
+ expired(entry) {
28
+ return this.ttlMs !== void 0 && this.now() - entry.storedAt > this.ttlMs;
29
+ }
30
+ get(key) {
31
+ const entry = this.map.get(key);
32
+ if (entry === void 0) return void 0;
33
+ if (this.expired(entry)) {
34
+ this.map.delete(key);
35
+ return void 0;
36
+ }
37
+ this.map.delete(key);
38
+ this.map.set(key, entry);
39
+ return entry.value;
40
+ }
41
+ /**
42
+ * Read without promoting the key to most-recently-used. Use for
43
+ * diagnostics that must not perturb the eviction order.
44
+ */
45
+ peek(key) {
46
+ const entry = this.map.get(key);
47
+ if (entry === void 0 || this.expired(entry)) return void 0;
48
+ return entry.value;
49
+ }
50
+ has(key) {
51
+ const entry = this.map.get(key);
52
+ if (entry === void 0) return false;
53
+ if (this.expired(entry)) {
54
+ this.map.delete(key);
55
+ return false;
56
+ }
57
+ return true;
58
+ }
59
+ set(key, value) {
60
+ this.map.delete(key);
61
+ this.map.set(key, { value, storedAt: this.now() });
62
+ while (this.map.size > this.max) {
63
+ const coldest = this.map.keys().next().value;
64
+ if (coldest === void 0) break;
65
+ this.map.delete(coldest);
66
+ this.evictions += 1;
67
+ }
68
+ return this;
69
+ }
70
+ delete(key) {
71
+ return this.map.delete(key);
72
+ }
73
+ clear() {
74
+ this.map.clear();
75
+ this.evictions = 0;
76
+ }
77
+ get size() {
78
+ return this.map.size;
79
+ }
80
+ /** How many entries have been dropped to respect `max`, since the last clear. */
81
+ get evictionCount() {
82
+ return this.evictions;
83
+ }
84
+ /** Drop every expired entry. Cheap enough to call from a status tool. */
85
+ prune() {
86
+ if (this.ttlMs === void 0) return 0;
87
+ let removed = 0;
88
+ for (const [key, entry] of this.map) {
89
+ if (this.expired(entry)) {
90
+ this.map.delete(key);
91
+ removed += 1;
92
+ }
93
+ }
94
+ return removed;
95
+ }
96
+ /** Live (non-expired) entries, coldest first. */
97
+ *entries() {
98
+ for (const [key, entry] of this.map) {
99
+ if (!this.expired(entry)) yield [key, entry.value];
100
+ }
101
+ }
102
+ [Symbol.iterator]() {
103
+ return this.entries();
104
+ }
105
+ };
106
+
107
+ // src/runtime/index.ts
8
108
  var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
9
109
  function hasLeadingDash(arg) {
10
110
  return arg.length > 0 && arg.startsWith("-");
@@ -22,6 +122,15 @@ function withinProject(p) {
22
122
  }
23
123
 
24
124
  // src/test-runner-gate/index.ts
125
+ function resolveExec(command, args) {
126
+ const resolved = resolveWin32Command2(command);
127
+ const needsShell = process.platform === "win32" && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
128
+ if (needsShell) {
129
+ const shim = buildWin32CmdShimInvocation2(resolved, args);
130
+ return { cmd: shim.command, args: shim.args, windowsVerbatimArguments: true };
131
+ }
132
+ return { cmd: resolved, args: [...args], windowsVerbatimArguments: false };
133
+ }
25
134
  var API_VERSION = "^0.1.10";
26
135
  var state = {
27
136
  invocationCount: 0,
@@ -86,7 +195,7 @@ function pathContentHash(content) {
86
195
  }
87
196
  return h >>> 0;
88
197
  }
89
- var lastPassedHash = /* @__PURE__ */ new Map();
198
+ var lastPassedHash = new BoundedMap({ max: 1024 });
90
199
  var ALLOWED_COMMAND_TOKENS = /* @__PURE__ */ new Set([
91
200
  "npx",
92
201
  "pnpm",
@@ -141,10 +250,13 @@ async function detectRunner(requested) {
141
250
  if (!match) return null;
142
251
  try {
143
252
  await new Promise((resolve2, reject) => {
144
- execFile("npx", [`${match.name}`, "--version"], {
253
+ const ex = resolveExec("npx", [`${match.name}`, "--version"]);
254
+ execFile(ex.cmd, ex.args, {
145
255
  encoding: "utf-8",
146
256
  timeout: 5e3,
147
- cwd: process.cwd()
257
+ cwd: process.cwd(),
258
+ windowsHide: true,
259
+ ...ex.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}
148
260
  }, (err) => err ? reject(err) : resolve2());
149
261
  });
150
262
  return match;
@@ -155,10 +267,13 @@ async function detectRunner(requested) {
155
267
  for (const candidate of candidates) {
156
268
  try {
157
269
  await new Promise((resolve2, reject) => {
158
- execFile("npx", [`${candidate.name}`, "--version"], {
270
+ const ex = resolveExec("npx", [`${candidate.name}`, "--version"]);
271
+ execFile(ex.cmd, ex.args, {
159
272
  encoding: "utf-8",
160
273
  timeout: 5e3,
161
- cwd: process.cwd()
274
+ cwd: process.cwd(),
275
+ windowsHide: true,
276
+ ...ex.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}
162
277
  }, (err) => err ? reject(err) : resolve2());
163
278
  });
164
279
  return candidate;
@@ -206,10 +321,17 @@ async function runTests(testFile, runner, customCommand, timeoutMs) {
206
321
  try {
207
322
  const { stdout: out } = await new Promise(
208
323
  (resolve2, reject) => {
324
+ const ex = resolveExec(cmd, fullArgs);
209
325
  execFile(
210
- cmd,
211
- fullArgs,
212
- { encoding: "utf-8", timeout: timeoutMs, cwd: process.cwd() },
326
+ ex.cmd,
327
+ ex.args,
328
+ {
329
+ encoding: "utf-8",
330
+ timeout: timeoutMs,
331
+ cwd: process.cwd(),
332
+ windowsHide: true,
333
+ ...ex.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}
334
+ },
213
335
  (err, out2, stderr) => {
214
336
  if (err) reject(Object.assign(err, { stdout: out2, stderr }));
215
337
  else resolve2({ stdout: out2, stderr });
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/todo-listener/index.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAmGrD,QAAA,MAAM,MAAM,EAAE,MAqNb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/todo-listener/index.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAoGrD,QAAA,MAAM,MAAM,EAAE,MAuNb,CAAC;eAEa,MAAM"}
@@ -1,3 +1,32 @@
1
+ // src/runtime/local-bin.ts
2
+ import { buildWin32CmdShimInvocation, resolveWin32Command } from "@wrongstack/tools/win32";
3
+
4
+ // src/runtime/safe-json.ts
5
+ var UNSERIALIZABLE = "[unserializable]";
6
+ function safeJsonStringify(value, indent) {
7
+ try {
8
+ const stack = [];
9
+ const out = JSON.stringify(
10
+ value,
11
+ function replacer(_key, val) {
12
+ if (typeof val === "bigint") return `${val.toString()}n`;
13
+ if (val === null || typeof val !== "object") return val;
14
+ while (stack.length > 0 && stack[stack.length - 1] !== this) stack.pop();
15
+ if (stack.includes(val)) return "[circular]";
16
+ stack.push(val);
17
+ return val;
18
+ },
19
+ indent
20
+ );
21
+ return out ?? String(value);
22
+ } catch {
23
+ return UNSERIALIZABLE;
24
+ }
25
+ }
26
+
27
+ // src/runtime/index.ts
28
+ var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
29
+
1
30
  // src/todo-listener/index.ts
2
31
  var state = {
3
32
  invocationCount: 0,
@@ -112,7 +141,7 @@ var plugin = {
112
141
  0,
113
142
  200
114
143
  );
115
- const body = JSON.stringify(payload, null, 2);
144
+ const body = safeJsonStringify(payload, 2);
116
145
  const sendInput = {
117
146
  from: `plugin:todo-listener`,
118
147
  to: "*",
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/todo-tracker/index.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAmKrD,QAAA,MAAM,MAAM,EAAE,MAsZb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/todo-tracker/index.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAuKrD,QAAA,MAAM,MAAM,EAAE,MAsZb,CAAC;eAEa,MAAM"}
@@ -1,6 +1,7 @@
1
1
  // src/todo-tracker/index.ts
2
2
  import * as fsp from "node:fs/promises";
3
3
  import { randomUUID } from "node:crypto";
4
+ import { atomicWrite, ensureDir } from "@wrongstack/core/utils";
4
5
  function deriveFilePath(api) {
5
6
  const raw = api.config.extensions?.["todo-tracker"];
6
7
  const explicit = typeof raw?.["filePath"] === "string" ? raw["filePath"] : null;
@@ -30,10 +31,8 @@ async function loadFile(filePath) {
30
31
  }
31
32
  }
32
33
  async function saveFile(filePath, file) {
33
- const tmp = `${filePath}.${randomUUID().slice(0, 8)}.tmp`;
34
- await fsp.mkdir(filePath.replace(/[/\\][^/\\]+$/, ""), { recursive: true });
35
- await fsp.writeFile(tmp, JSON.stringify(file, null, 2), { encoding: "utf8", mode: 384 });
36
- await fsp.rename(tmp, filePath);
34
+ await ensureDir(filePath.replace(/[/\\][^/\\]+$/, ""));
35
+ await atomicWrite(filePath, JSON.stringify(file, null, 2), { mode: 384 });
37
36
  }
38
37
  var state = {
39
38
  filePath: null,
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/token-throttle/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,EAAE,MAAM,EAAa,MAAM,wBAAwB,CAAC;AA8ChE,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,gEAAgE;AAChE,wBAAgB,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,EAAE,CAE5E;AAED,0CAA0C;AAC1C,wBAAgB,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CAEzD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,UAAU,EAAE,EACrB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAChB,MAAM,CAoBR;AA4CD,QAAA,MAAM,MAAM,EAAE,MAoKb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/token-throttle/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,EAAE,MAAM,EAAa,MAAM,wBAAwB,CAAC;AA8ChE,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,gEAAgE;AAChE,wBAAgB,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,EAAE,CAE5E;AAED,0CAA0C;AAC1C,wBAAgB,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CAEzD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,UAAU,EAAE,EACrB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAChB,MAAM,CAoBR;AAyED,QAAA,MAAM,MAAM,EAAE,MAqKb,CAAC;eAEa,MAAM"}
@@ -60,7 +60,21 @@ function estimateRequestTokens(request, charsPerToken) {
60
60
  walk(request["messages"]);
61
61
  return Math.ceil(chars / charsPerToken);
62
62
  }
63
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
63
+ function sleep(ms, signal) {
64
+ if (signal?.aborted) return Promise.resolve();
65
+ return new Promise((resolve) => {
66
+ const timer = setTimeout(() => {
67
+ signal?.removeEventListener("abort", onAbort);
68
+ resolve();
69
+ }, ms);
70
+ timer.unref?.();
71
+ function onAbort() {
72
+ clearTimeout(timer);
73
+ resolve();
74
+ }
75
+ signal?.addEventListener("abort", onAbort, { once: true });
76
+ });
77
+ }
64
78
  var plugin = {
65
79
  name: "token-throttle",
66
80
  version: "0.1.0",
@@ -119,6 +133,7 @@ var plugin = {
119
133
  name: "token-throttle",
120
134
  owner: "token-throttle",
121
135
  async wrapProviderRunner(_ctx, request, inner) {
136
+ const signal = _ctx?.signal;
122
137
  const req = request ?? {};
123
138
  state.invocations += 1;
124
139
  const now = Date.now();
@@ -132,7 +147,7 @@ var plugin = {
132
147
  api.metrics.counter("throttled");
133
148
  api.metrics.histogram("delay_ms", delay);
134
149
  api.log.info("token-throttle: delaying provider call", { delayMs: delay, projected });
135
- await sleep(delay);
150
+ await sleep(delay, signal);
136
151
  }
137
152
  const response = await inner(_ctx, request);
138
153
  const used = (response?.usage?.input ?? 0) + (response?.usage?.output ?? 0) || projected;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/type-gate/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAuNrD,QAAA,MAAM,MAAM,EAAE,MAkOb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/type-gate/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AA+NrD,QAAA,MAAM,MAAM,EAAE,MAkOb,CAAC;eAEa,MAAM"}
package/dist/type-gate.js CHANGED
@@ -3,7 +3,69 @@ import { existsSync } from "node:fs";
3
3
 
4
4
  // src/runtime/index.ts
5
5
  import { execFile } from "node:child_process";
6
- import { basename, extname, isAbsolute, relative, resolve } from "node:path";
6
+ import { basename, extname as extname2, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 } from "node:path";
7
+
8
+ // src/runtime/local-bin.ts
9
+ import { createRequire } from "node:module";
10
+ import { delimiter, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
11
+ import { accessSync, constants, readFileSync } from "node:fs";
12
+ import { buildWin32CmdShimInvocation, resolveWin32Command } from "@wrongstack/tools/win32";
13
+ function resolveExecInvocation(command, args = []) {
14
+ const resolved = resolveWin32Command(command);
15
+ const normalizedResolved = resolved.toLowerCase();
16
+ const needsShell = process.platform === "win32" && (normalizedResolved.endsWith(".cmd") || normalizedResolved.endsWith(".bat"));
17
+ if (needsShell) {
18
+ const shim = buildWin32CmdShimInvocation(resolved, args);
19
+ return { cmd: shim.command, args: shim.args, windowsVerbatimArguments: true };
20
+ }
21
+ return { cmd: resolved, args: [...args], windowsVerbatimArguments: false };
22
+ }
23
+ function isInside(parent, candidate) {
24
+ const rel = relative(parent, candidate);
25
+ return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel);
26
+ }
27
+ var binCache = /* @__PURE__ */ new Map();
28
+ var BIN_CACHE_MAX = 64;
29
+ var NEGATIVE_BIN_CACHE_TTL_MS = 5e3;
30
+ function cachePut(key, value) {
31
+ while (binCache.size >= BIN_CACHE_MAX) {
32
+ const oldest = binCache.keys().next().value;
33
+ if (oldest === void 0) break;
34
+ binCache.delete(oldest);
35
+ }
36
+ binCache.set(key, { value, cachedAt: Date.now() });
37
+ return value;
38
+ }
39
+ function resolveNodeBin(packageName, binName, cwd, extraArgs = []) {
40
+ const key = `${packageName}|${binName}|${cwd}`;
41
+ const cached = binCache.get(key);
42
+ if (cached !== void 0) {
43
+ if (cached.value !== null || Date.now() - cached.cachedAt < NEGATIVE_BIN_CACHE_TTL_MS) {
44
+ return cached.value === null ? null : { ...cached.value, args: [cached.value.entry, ...extraArgs] };
45
+ }
46
+ binCache.delete(key);
47
+ }
48
+ let resolved = null;
49
+ try {
50
+ const requireFromProject = createRequire(resolve(cwd, "package.json"));
51
+ const packagePath = requireFromProject.resolve(`${packageName}/package.json`);
52
+ const packageJson = JSON.parse(readFileSync(packagePath, "utf-8"));
53
+ const relativeBin = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.[binName] ?? Object.values(packageJson.bin ?? {})[0];
54
+ if (relativeBin && !isAbsolute(relativeBin)) {
55
+ const packageDir = dirname(packagePath);
56
+ const entry = resolve(packageDir, relativeBin);
57
+ if (isInside(packageDir, entry)) {
58
+ resolved = { cmd: process.execPath, args: [entry], entry };
59
+ }
60
+ }
61
+ } catch {
62
+ resolved = null;
63
+ }
64
+ cachePut(key, resolved);
65
+ return resolved === null ? null : { ...resolved, args: [resolved.entry, ...extraArgs] };
66
+ }
67
+
68
+ // src/runtime/index.ts
7
69
  var META_CHARS = /["'`;&|<>\r\n]/;
8
70
  var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
9
71
  function hasLeadingDash(arg) {
@@ -17,9 +79,9 @@ function safeSplit(command) {
17
79
  function withinProjectPath(projectRoot, candidate) {
18
80
  if (candidate.length === 0 || candidate.length > 4096) return false;
19
81
  if (hasLeadingDash(candidate)) return false;
20
- const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
21
- const rel = relative(projectRoot, resolved);
22
- return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
82
+ const resolved = isAbsolute2(candidate) ? resolve2(candidate) : resolve2(projectRoot, candidate);
83
+ const rel = relative2(projectRoot, resolved);
84
+ return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
23
85
  }
24
86
  function everyFlagAllowed(allowed, args) {
25
87
  for (const arg of args) {
@@ -31,14 +93,14 @@ function everyFlagAllowed(allowed, args) {
31
93
  }
32
94
  function sanitizeRunnerPath(value, options = {}) {
33
95
  if (!value || hasLeadingDash(value)) return null;
34
- const projectRoot = resolve(options.projectRoot ?? process.cwd());
96
+ const projectRoot = resolve2(options.projectRoot ?? process.cwd());
35
97
  if (!withinProjectPath(projectRoot, value)) return null;
36
- return isAbsolute(value) ? resolve(value) : resolve(projectRoot, value);
98
+ return isAbsolute2(value) ? resolve2(value) : resolve2(projectRoot, value);
37
99
  }
38
100
  function resolveRunnerCommand(runtime, command, options = {}) {
39
101
  const tokens = safeSplit(command);
40
102
  if (!tokens || tokens.length === 0) return null;
41
- const projectRoot = resolve(options.projectRoot ?? process.cwd());
103
+ const projectRoot = resolve2(options.projectRoot ?? process.cwd());
42
104
  const launcher = runtime.packageManager;
43
105
  const [head, second, ...rest] = tokens;
44
106
  if (!head) return null;
@@ -69,7 +131,7 @@ function resolveRunnerCommand(runtime, command, options = {}) {
69
131
  return { cmd: head, args: [second, exe, ...tail], display };
70
132
  }
71
133
  }
72
- if (isAbsolute(head)) {
134
+ if (isAbsolute2(head)) {
73
135
  if (!withinProjectPath(projectRoot, head)) return null;
74
136
  const base = basename(head);
75
137
  if (base !== runtime.executable && base !== launcher) return null;
@@ -90,7 +152,7 @@ function runRunnerCommand(argv, options) {
90
152
  });
91
153
  }
92
154
  return new Promise((resolvePromise) => {
93
- const projectRoot = resolve(options.projectRoot ?? process.cwd());
155
+ const projectRoot = resolve2(options.projectRoot ?? process.cwd());
94
156
  const trimmedCwd = options.cwd.trim();
95
157
  if (!withinProjectPath(projectRoot, trimmedCwd)) {
96
158
  resolvePromise({
@@ -108,21 +170,37 @@ function runRunnerCommand(argv, options) {
108
170
  const stderrChunks = [];
109
171
  let stdoutBytes = 0;
110
172
  let stderrBytes = 0;
111
- options.signal?.addEventListener("abort", () => {
173
+ const onAbort = () => {
112
174
  timedOut = true;
113
- }, { once: true });
175
+ };
176
+ let invocation;
177
+ try {
178
+ invocation = resolveExecInvocation(argv[0], argv.slice(1));
179
+ } catch (err) {
180
+ resolvePromise({
181
+ code: null,
182
+ stdout: "",
183
+ stderr: `runtime helper: ${err instanceof Error ? err.message : String(err)}`,
184
+ timedOut: false,
185
+ spawnError: true
186
+ });
187
+ return;
188
+ }
189
+ options.signal?.addEventListener("abort", onAbort, { once: true });
114
190
  const child = execFile(
115
- argv[0],
116
- argv.slice(1),
191
+ invocation.cmd,
192
+ invocation.args,
117
193
  {
118
194
  cwd: trimmedCwd,
119
195
  timeout: options.timeoutMs,
120
196
  signal: options.signal,
121
197
  maxBuffer: MAX_BUFFER_BYTES,
122
198
  windowsHide: true,
123
- shell: false
199
+ shell: false,
200
+ ...invocation.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}
124
201
  },
125
202
  (err) => {
203
+ options.signal?.removeEventListener("abort", onAbort);
126
204
  if (timedOut) {
127
205
  resolvePromise({
128
206
  code: null,
@@ -186,7 +264,7 @@ function runRunnerCommand(argv, options) {
186
264
  }
187
265
  function withinProject(p) {
188
266
  const cwd = process.cwd();
189
- return withinProjectPath(cwd, p) || relative(cwd, p) === ".";
267
+ return withinProjectPath(cwd, p) || relative2(cwd, p) === ".";
190
268
  }
191
269
 
192
270
  // src/type-gate/index.ts
@@ -263,7 +341,9 @@ async function runTypeCheck(cfg) {
263
341
  if (!resolved) return null;
264
342
  argv = [resolved.cmd, ...resolved.args];
265
343
  } else {
266
- argv = ["npx", "tsc", "--noEmit", "--incremental"];
344
+ const tscFlags = ["--noEmit", "--incremental"];
345
+ const localTsc = resolveNodeBin("typescript", "tsc", process.cwd(), tscFlags);
346
+ argv = localTsc ? [localTsc.cmd, ...localTsc.args] : ["npx", "tsc", ...tscFlags];
267
347
  if (tsConfig && tsConfig !== "tsconfig.json") {
268
348
  argv = [...argv, "-p", tsConfig];
269
349
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/plugins",
3
- "version": "0.295.0",
3
+ "version": "0.295.1",
4
4
  "description": "Official WrongStack collection of focused plugins for code quality, security, observability, planning, and agent coordination",
5
5
  "license": "MIT",
6
6
  "author": "ECOSTACK TECHNOLOGY OÜ",
@@ -294,8 +294,8 @@
294
294
  "vitest": "^4.1.10"
295
295
  },
296
296
  "dependencies": {
297
- "@wrongstack/core": "0.295.0",
298
- "@wrongstack/tools": "0.295.0"
297
+ "@wrongstack/core": "0.295.1",
298
+ "@wrongstack/tools": "0.295.1"
299
299
  },
300
300
  "scripts": {
301
301
  "build": "node ../../scripts/build-package.mjs",