@wrongstack/plugins 0.297.0 → 0.298.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 (92) hide show
  1. package/dist/audit/index.d.ts +6 -6
  2. package/dist/audit.js +7 -7
  3. package/dist/dead-code-detector.js +31 -30
  4. package/dist/factories/index.d.ts +1 -1
  5. package/dist/factories.js +1 -1
  6. package/dist/file-watcher.js +37 -4
  7. package/dist/index.js +232 -111
  8. package/dist/loop-breaker.js +19 -7
  9. package/dist/manifest.js +1 -1
  10. package/dist/runtime/index.d.ts +2 -2
  11. package/dist/session-recap.js +47 -13
  12. package/dist/spec-linker.js +1 -1
  13. package/dist/template-engine.js +42 -1
  14. package/package.json +9 -8
  15. package/dist/accessibility-auditor/index.d.ts.map +0 -1
  16. package/dist/agent-handoff/index.d.ts.map +0 -1
  17. package/dist/api-compatibility-gate/index.d.ts.map +0 -1
  18. package/dist/audit/index.d.ts.map +0 -1
  19. package/dist/auto-doc/index.d.ts.map +0 -1
  20. package/dist/auto-escalate/index.d.ts.map +0 -1
  21. package/dist/auto-i18n-extractor/index.d.ts.map +0 -1
  22. package/dist/branch-guard/index.d.ts.map +0 -1
  23. package/dist/catalog.d.ts.map +0 -1
  24. package/dist/changelog-writer/index.d.ts.map +0 -1
  25. package/dist/checkpoint/index.d.ts.map +0 -1
  26. package/dist/code-metrics/index.d.ts.map +0 -1
  27. package/dist/commit-validator/index.d.ts.map +0 -1
  28. package/dist/config-validator/index.d.ts.map +0 -1
  29. package/dist/context-pins/index.d.ts.map +0 -1
  30. package/dist/cost-tracker/index.d.ts.map +0 -1
  31. package/dist/cron/index.d.ts.map +0 -1
  32. package/dist/dead-code-detector/index.d.ts.map +0 -1
  33. package/dist/dep-guard/index.d.ts.map +0 -1
  34. package/dist/dependency-vulnerability-gate/index.d.ts.map +0 -1
  35. package/dist/diff-summary/index.d.ts.map +0 -1
  36. package/dist/doc-sync-guard/index.d.ts.map +0 -1
  37. package/dist/duplicate-code-detector/index.d.ts.map +0 -1
  38. package/dist/error-lens/index.d.ts.map +0 -1
  39. package/dist/factories/index.d.ts.map +0 -1
  40. package/dist/feature-flag-tracker/index.d.ts.map +0 -1
  41. package/dist/file-watcher/index.d.ts.map +0 -1
  42. package/dist/format-on-save/index.d.ts.map +0 -1
  43. package/dist/generated-plugin-exports.d.ts.map +0 -1
  44. package/dist/git-autocommit/index.d.ts.map +0 -1
  45. package/dist/import-organizer/index.d.ts.map +0 -1
  46. package/dist/index.d.ts.map +0 -1
  47. package/dist/injection-shield/index.d.ts.map +0 -1
  48. package/dist/interface-contract-guard/index.d.ts.map +0 -1
  49. package/dist/knowledge-graph/index.d.ts.map +0 -1
  50. package/dist/license-audit-gate/index.d.ts.map +0 -1
  51. package/dist/lint-gate/index.d.ts.map +0 -1
  52. package/dist/llm-cache/index.d.ts.map +0 -1
  53. package/dist/loop-breaker/index.d.ts.map +0 -1
  54. package/dist/manifest/index.d.ts.map +0 -1
  55. package/dist/migration-planner/index.d.ts.map +0 -1
  56. package/dist/model-router/index.d.ts.map +0 -1
  57. package/dist/notify-hub/index.d.ts.map +0 -1
  58. package/dist/notify-hub/webhook-channel.d.ts.map +0 -1
  59. package/dist/path-guard/index.d.ts.map +0 -1
  60. package/dist/performance-regression-gate/index.d.ts.map +0 -1
  61. package/dist/plugin-stack-observer/index.d.ts.map +0 -1
  62. package/dist/pr-drafter/index.d.ts.map +0 -1
  63. package/dist/process-guard/index.d.ts.map +0 -1
  64. package/dist/prompt-firewall/index.d.ts.map +0 -1
  65. package/dist/refactor-suggester/index.d.ts.map +0 -1
  66. package/dist/release-notes-generator/index.d.ts.map +0 -1
  67. package/dist/runtime/bounded-map.d.ts.map +0 -1
  68. package/dist/runtime/credential-patterns.d.ts.map +0 -1
  69. package/dist/runtime/handles.d.ts.map +0 -1
  70. package/dist/runtime/index.d.ts.map +0 -1
  71. package/dist/runtime/llm.d.ts.map +0 -1
  72. package/dist/runtime/local-bin.d.ts.map +0 -1
  73. package/dist/runtime/safe-json.d.ts.map +0 -1
  74. package/dist/schema-evolution-guard/index.d.ts.map +0 -1
  75. package/dist/secret-scanner/index.d.ts.map +0 -1
  76. package/dist/security-hotspot-scanner/index.d.ts.map +0 -1
  77. package/dist/semantic-search-indexer/index.d.ts.map +0 -1
  78. package/dist/semver-bump/index.d.ts.map +0 -1
  79. package/dist/session-recap/index.d.ts.map +0 -1
  80. package/dist/shell-check/index.d.ts.map +0 -1
  81. package/dist/smart-rename/index.d.ts.map +0 -1
  82. package/dist/spec-linker/index.d.ts.map +0 -1
  83. package/dist/template-engine/index.d.ts.map +0 -1
  84. package/dist/test-coverage-gate/index.d.ts.map +0 -1
  85. package/dist/test-flake-detector/index.d.ts.map +0 -1
  86. package/dist/test-generator/index.d.ts.map +0 -1
  87. package/dist/test-runner-gate/index.d.ts.map +0 -1
  88. package/dist/todo-listener/index.d.ts.map +0 -1
  89. package/dist/todo-tracker/index.d.ts.map +0 -1
  90. package/dist/token-budget/index.d.ts.map +0 -1
  91. package/dist/token-throttle/index.d.ts.map +0 -1
  92. package/dist/type-gate/index.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -5469,18 +5469,12 @@ async function gatherFiles(root, depth, cfg) {
5469
5469
  function stripNoise(content) {
5470
5470
  let stripped = content.replace(/\/\/.*$/gm, " ");
5471
5471
  stripped = stripped.replace(/\/\*[\s\S]*?\*\//g, " ");
5472
- stripped = stripped.replace(
5473
- /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`/g,
5474
- " "
5475
- );
5472
+ stripped = stripped.replace(/'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`/g, " ");
5476
5473
  return stripped;
5477
5474
  }
5478
5475
  function lineNumber(content, index) {
5479
5476
  return content.slice(0, index).split(/\r?\n/).length;
5480
5477
  }
5481
- function escapeRegex(s) {
5482
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5483
- }
5484
5478
  var DECLARATION_EXPORT_RE = /export\s+(?:async\s+|abstract\s+)?(?:const|let|var|function|class|interface|type|enum)\s+([A-Za-z_$][A-Za-z0-9_$]*)/g;
5485
5479
  var NAMED_EXPORT_RE = /export\s*\{([^}]+)\}/g;
5486
5480
  function extractExports2(content, filePath) {
@@ -5513,26 +5507,33 @@ function extractExports2(content, filePath) {
5513
5507
  }
5514
5508
  return exports;
5515
5509
  }
5516
- function findUnusedSymbols(files) {
5510
+ async function findUnusedSymbols(files) {
5517
5511
  const suspicious = [];
5512
+ const exportedNames = new Set(files.flatMap((file) => file.exports.map((exp) => exp.identifier)));
5513
+ const filesContainingIdentifier = /* @__PURE__ */ new Map();
5514
+ const identifierPattern = /[A-Za-z_$][A-Za-z0-9_$]*/g;
5518
5515
  for (const file of files) {
5519
- const fileExports = extractExports2(file.content, file.path);
5520
- for (const exp of fileExports) {
5521
- const pattern = new RegExp(
5522
- `(?<![A-Za-z0-9_$])${escapeRegex(exp.identifier)}(?![A-Za-z0-9_$])`,
5523
- "g"
5516
+ let content;
5517
+ try {
5518
+ content = await readFile5(file.path, "utf-8");
5519
+ } catch {
5520
+ continue;
5521
+ }
5522
+ const seenInFile = /* @__PURE__ */ new Set();
5523
+ for (const match of stripNoise(content).matchAll(identifierPattern)) {
5524
+ const identifier = match[0];
5525
+ if (exportedNames.has(identifier)) seenInFile.add(identifier);
5526
+ }
5527
+ for (const identifier of seenInFile) {
5528
+ filesContainingIdentifier.set(
5529
+ identifier,
5530
+ (filesContainingIdentifier.get(identifier) ?? 0) + 1
5524
5531
  );
5525
- let usedElsewhere = false;
5526
- for (const other of files) {
5527
- if (other.path === file.path) continue;
5528
- if (pattern.test(other.stripped)) {
5529
- usedElsewhere = true;
5530
- break;
5531
- }
5532
- }
5533
- if (!usedElsewhere) {
5534
- suspicious.push(exp);
5535
- }
5532
+ }
5533
+ }
5534
+ for (const file of files) {
5535
+ for (const exp of file.exports) {
5536
+ if ((filesContainingIdentifier.get(exp.identifier) ?? 0) <= 1) suspicious.push(exp);
5536
5537
  }
5537
5538
  }
5538
5539
  suspicious.sort((a, b) => {
@@ -5548,11 +5549,11 @@ async function scan(root, depth, cfg) {
5548
5549
  for (const p of filePaths) {
5549
5550
  try {
5550
5551
  const content = await readFile5(p, "utf-8");
5551
- files.push({ path: p, content, stripped: stripNoise(content) });
5552
+ files.push({ path: p, exports: extractExports2(content, p) });
5552
5553
  } catch {
5553
5554
  }
5554
5555
  }
5555
- return { findings: findUnusedSymbols(files), scannedFiles: files.length };
5556
+ return { findings: await findUnusedSymbols(files), scannedFiles: files.length };
5556
5557
  }
5557
5558
  async function resolveScanRoot(rawPath) {
5558
5559
  const resolved = resolve10(process.cwd(), rawPath);
@@ -5648,16 +5649,16 @@ var plugin16 = {
5648
5649
  }
5649
5650
  state15.hitCount += 1;
5650
5651
  state15.warningCount += relevant.length;
5651
- const list = relevant.map(
5652
- (f) => ` - ${f.identifier} (${f.exportType}) at ${relativePath2(f.file)}:${f.line}`
5653
- ).join("\n");
5652
+ const list = relevant.map((f) => ` - ${f.identifier} (${f.exportType}) at ${relativePath2(f.file)}:${f.line}`).join("\n");
5654
5653
  const message = `
5655
5654
  \u26A0\uFE0F dead-code-detector: ${relevant.length} exported symbol(s) in ${sourcePath} look unused:
5656
5655
  ${list}
5657
5656
  Consider removing the export if it is not part of the public API.`;
5658
5657
  return { additionalContext: message };
5659
5658
  };
5660
- state15.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
5659
+ state15.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, {
5660
+ background: true
5661
+ });
5661
5662
  api.tools.register({
5662
5663
  name: "dead_code_scan",
5663
5664
  description: "Scan TypeScript/JavaScript source files for exported symbols that appear unused anywhere in the project.",
@@ -8019,6 +8020,9 @@ function nextId() {
8019
8020
  }
8020
8021
  var watches = /* @__PURE__ */ new Map();
8021
8022
  var debounceTimers = /* @__PURE__ */ new Map();
8023
+ var MAX_WATCH_GROUPS = 32;
8024
+ var MAX_PATHS_PER_WATCH = 16;
8025
+ var MAX_FILESYSTEM_WATCHERS = 64;
8022
8026
  var plugin24 = {
8023
8027
  name: "file-watcher",
8024
8028
  version: "0.1.0",
@@ -8156,8 +8160,10 @@ var plugin24 = {
8156
8160
  api.log.warn(`file-watcher: error on ${dirPath}: ${err}`);
8157
8161
  });
8158
8162
  handle.watchers.push(watcher);
8163
+ return true;
8159
8164
  } catch (err) {
8160
8165
  api.log.warn(`file-watcher: could not watch ${dirPath}: ${err}`);
8166
+ return false;
8161
8167
  }
8162
8168
  }
8163
8169
  api.tools.register({
@@ -8169,6 +8175,7 @@ var plugin24 = {
8169
8175
  paths: {
8170
8176
  type: "array",
8171
8177
  items: { type: "string" },
8178
+ maxItems: MAX_PATHS_PER_WATCH,
8172
8179
  description: "File or directory paths to watch"
8173
8180
  },
8174
8181
  events: {
@@ -8197,7 +8204,7 @@ var plugin24 = {
8197
8204
  watch_id: null
8198
8205
  };
8199
8206
  }
8200
- const paths = rawPaths;
8207
+ const paths = [...new Set(rawPaths)];
8201
8208
  if (paths.length === 0) {
8202
8209
  return {
8203
8210
  ok: false,
@@ -8205,6 +8212,31 @@ var plugin24 = {
8205
8212
  watch_id: null
8206
8213
  };
8207
8214
  }
8215
+ if (paths.length > MAX_PATHS_PER_WATCH) {
8216
+ return {
8217
+ ok: false,
8218
+ error: `a watch may contain at most ${MAX_PATHS_PER_WATCH} unique paths`,
8219
+ watch_id: null
8220
+ };
8221
+ }
8222
+ if (watches.size >= MAX_WATCH_GROUPS) {
8223
+ return {
8224
+ ok: false,
8225
+ error: `active watch group limit reached (${MAX_WATCH_GROUPS})`,
8226
+ watch_id: null
8227
+ };
8228
+ }
8229
+ const activeFilesystemWatchers = [...watches.values()].reduce(
8230
+ (total, handle2) => total + handle2.watchers.length,
8231
+ 0
8232
+ );
8233
+ if (activeFilesystemWatchers + paths.length > MAX_FILESYSTEM_WATCHERS) {
8234
+ return {
8235
+ ok: false,
8236
+ error: `filesystem watcher limit reached (${MAX_FILESYSTEM_WATCHERS})`,
8237
+ watch_id: null
8238
+ };
8239
+ }
8208
8240
  const events = input["events"] ?? ["change", "add", "delete"];
8209
8241
  const recursive = input["recursive"] ?? true;
8210
8242
  const bad = paths.find((p) => !withinProject3(p));
@@ -8225,18 +8257,20 @@ var plugin24 = {
8225
8257
  watchers: [],
8226
8258
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
8227
8259
  };
8260
+ const watchedPaths = [];
8228
8261
  for (const p of paths) {
8229
- safeWatchDir(p, recursive, handle);
8262
+ if (safeWatchDir(p, recursive, handle)) watchedPaths.push(p);
8230
8263
  }
8264
+ handle.paths = watchedPaths;
8231
8265
  watches.set(id, handle);
8232
8266
  api.metrics.gauge("active_watches", watches.size);
8233
8267
  return {
8234
8268
  ok: true,
8235
8269
  watch_id: id,
8236
- paths,
8270
+ paths: watchedPaths,
8237
8271
  events,
8238
8272
  recursive,
8239
- message: `Started watching ${paths.length} path(s). Use watch_stop to cancel.`
8273
+ message: `Started watching ${watchedPaths.length} path(s). Use watch_stop to cancel.`
8240
8274
  };
8241
8275
  }
8242
8276
  });
@@ -11402,6 +11436,7 @@ var llm_cache_default = plugin33;
11402
11436
 
11403
11437
  // src/loop-breaker/index.ts
11404
11438
  import { execFile as execFile9 } from "node:child_process";
11439
+ import { isAbsolute as isAbsolute17, relative as relative17 } from "node:path";
11405
11440
  var state31 = {
11406
11441
  lastFingerprint: null,
11407
11442
  streak: 0,
@@ -11511,20 +11546,24 @@ function hashString2(value) {
11511
11546
  }
11512
11547
  return String(h >>> 0);
11513
11548
  }
11514
- async function gitDiffFingerprint(cwd, signal) {
11549
+ async function gitDiffFingerprint(cwd, targetPath, signal) {
11550
+ const pathspec = isAbsolute17(targetPath) ? relative17(cwd, targetPath) : targetPath;
11551
+ if (!pathspec || pathspec === ".." || pathspec.startsWith("../") || pathspec.startsWith("..\\")) {
11552
+ return null;
11553
+ }
11515
11554
  try {
11516
11555
  const diff = await new Promise((resolve28, reject) => {
11517
11556
  execFile9(
11518
11557
  "git",
11519
- ["diff", "--no-ext-diff", "--"],
11558
+ ["diff", "--no-ext-diff", "--", pathspec],
11520
11559
  {
11521
11560
  cwd,
11522
11561
  encoding: "utf8",
11523
11562
  timeout: 1e3,
11524
- // Large dirty worktrees are common during an agent run. The hash
11525
- // itself is capped below, but the subprocess must still be allowed to
11526
- // finish so an oversized diff is not mistaken for "git unavailable".
11527
- maxBuffer: 16 * 1024 * 1024,
11563
+ // The hook follows only the file touched by this edit/write. A whole
11564
+ // repository diff duplicated diff-summary and could retain 16 MiB on
11565
+ // every mutation in a large dirty worktree.
11566
+ maxBuffer: 2 * 1024 * 1024,
11528
11567
  windowsHide: true,
11529
11568
  signal
11530
11569
  },
@@ -11735,7 +11774,14 @@ var plugin34 = {
11735
11774
  state31.lastErrorFingerprint = null;
11736
11775
  state31.repeatedErrorStreak = 0;
11737
11776
  if (!MUTATING_TOOLS.has(toolName)) return;
11738
- const diffFingerprint = await gitDiffFingerprint(input.cwd ?? process.cwd(), runtime.signal);
11777
+ const toolInput = input.toolInput ?? {};
11778
+ const targetPath = toolInput["path"];
11779
+ if (typeof targetPath !== "string" || targetPath.length === 0) return;
11780
+ const diffFingerprint = await gitDiffFingerprint(
11781
+ input.cwd ?? process.cwd(),
11782
+ targetPath,
11783
+ runtime.signal
11784
+ );
11739
11785
  if (diffFingerprint === null) return;
11740
11786
  if (diffFingerprint === state31.lastDiffFingerprint) {
11741
11787
  state31.noDiffStreak += 1;
@@ -13259,7 +13305,7 @@ var path_guard_default = plugin38;
13259
13305
 
13260
13306
  // src/performance-regression-gate/index.ts
13261
13307
  import { existsSync as existsSync5, readFileSync as readFileSync11 } from "node:fs";
13262
- import { isAbsolute as isAbsolute17, relative as relative17, resolve as resolve18 } from "node:path";
13308
+ import { isAbsolute as isAbsolute18, relative as relative18, resolve as resolve18 } from "node:path";
13263
13309
  var API_VERSION25 = "^0.1.10";
13264
13310
  var state36 = {
13265
13311
  invocationCount: 0,
@@ -13285,17 +13331,17 @@ function readConfig34(raw) {
13285
13331
  function withinProject4(p, cwd = process.cwd()) {
13286
13332
  if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
13287
13333
  const root = resolve18(cwd);
13288
- const resolved = isAbsolute17(p) ? resolve18(p) : resolve18(root, p);
13289
- const rel = relative17(root, resolved);
13334
+ const resolved = isAbsolute18(p) ? resolve18(p) : resolve18(root, p);
13335
+ const rel = relative18(root, resolved);
13290
13336
  if (rel === "" || rel === ".") return true;
13291
13337
  if (rel.startsWith("..")) return false;
13292
- if (isAbsolute17(rel)) return false;
13338
+ if (isAbsolute18(rel)) return false;
13293
13339
  return true;
13294
13340
  }
13295
13341
  function resolveProjectPath6(rawPath, cwd = process.cwd()) {
13296
13342
  if (typeof rawPath !== "string" || rawPath.length === 0) return null;
13297
13343
  const root = resolve18(cwd);
13298
- const resolved = isAbsolute17(rawPath) ? resolve18(rawPath) : resolve18(root, rawPath);
13344
+ const resolved = isAbsolute18(rawPath) ? resolve18(rawPath) : resolve18(root, rawPath);
13299
13345
  if (!withinProject4(resolved, cwd)) return null;
13300
13346
  return resolved;
13301
13347
  }
@@ -13710,7 +13756,7 @@ var plugin_stack_observer_default = PLUGIN;
13710
13756
  // src/pr-drafter/index.ts
13711
13757
  import { execFile as execFile10 } from "node:child_process";
13712
13758
  import { mkdir as mkdir2, writeFile as writeFile3 } from "node:fs/promises";
13713
- import { dirname as dirname7, isAbsolute as isAbsolute18, relative as relative18, resolve as resolve19 } from "node:path";
13759
+ import { dirname as dirname7, isAbsolute as isAbsolute19, relative as relative19, resolve as resolve19 } from "node:path";
13714
13760
  var API_VERSION26 = "^0.1.10";
13715
13761
  var state38 = {
13716
13762
  commits: [],
@@ -13750,9 +13796,9 @@ function readConfig36(raw) {
13750
13796
  function resolveProjectPath7(rawPath, cwd = process.cwd()) {
13751
13797
  if (typeof rawPath !== "string" || rawPath.length === 0) return null;
13752
13798
  const root = resolve19(cwd);
13753
- const resolved = isAbsolute18(rawPath) ? resolve19(rawPath) : resolve19(root, rawPath);
13754
- const rel = relative18(root, resolved);
13755
- if (rel === "" || !rel.startsWith("..") && !isAbsolute18(rel)) return resolved;
13799
+ const resolved = isAbsolute19(rawPath) ? resolve19(rawPath) : resolve19(root, rawPath);
13800
+ const rel = relative19(root, resolved);
13801
+ if (rel === "" || !rel.startsWith("..") && !isAbsolute19(rel)) return resolved;
13756
13802
  return null;
13757
13803
  }
13758
13804
  function runGit4(args, timeout) {
@@ -14611,7 +14657,7 @@ var prompt_firewall_default = plugin42;
14611
14657
 
14612
14658
  // src/refactor-suggester/index.ts
14613
14659
  import { readFile as readFile11 } from "node:fs/promises";
14614
- import { isAbsolute as isAbsolute19, relative as relative19, resolve as resolve20 } from "node:path";
14660
+ import { isAbsolute as isAbsolute20, relative as relative20, resolve as resolve20 } from "node:path";
14615
14661
  var API_VERSION27 = "^0.1.10";
14616
14662
  var HOOK_WARNING_COOLDOWN_MS2 = 6e4;
14617
14663
  var state41 = {
@@ -14655,7 +14701,7 @@ function toPosix6(p) {
14655
14701
  return p.replace(/\\/g, "/");
14656
14702
  }
14657
14703
  function relativePath6(p) {
14658
- return toPosix6(relative19(process.cwd(), p));
14704
+ return toPosix6(relative20(process.cwd(), p));
14659
14705
  }
14660
14706
  function leadingIndentLevel(line) {
14661
14707
  const leading = line.match(/^(\s*)/)?.[1] ?? "";
@@ -14742,7 +14788,7 @@ function detectSmells(filePath, content, rules) {
14742
14788
  }
14743
14789
  async function scanPath4(rawPath, cfg) {
14744
14790
  const root = process.cwd();
14745
- const resolved = isAbsolute19(rawPath) ? resolve20(rawPath) : resolve20(root, rawPath);
14791
+ const resolved = isAbsolute20(rawPath) ? resolve20(rawPath) : resolve20(root, rawPath);
14746
14792
  const exts = normalizeExtensions5(cfg.extensions);
14747
14793
  const files = await collectSourceFilesAsync(resolved, { extensions: exts });
14748
14794
  const suggestions = [];
@@ -16055,7 +16101,7 @@ var secret_scanner_default = plugin46;
16055
16101
 
16056
16102
  // src/security-hotspot-scanner/index.ts
16057
16103
  import { readdir as readdir2, readFile as readFile12, stat as stat5 } from "node:fs/promises";
16058
- import { isAbsolute as isAbsolute20, relative as relative20, resolve as resolve21 } from "node:path";
16104
+ import { isAbsolute as isAbsolute21, relative as relative21, resolve as resolve21 } from "node:path";
16059
16105
  var API_VERSION30 = "^0.1.10";
16060
16106
  var state45 = {
16061
16107
  scanCount: 0,
@@ -16146,7 +16192,7 @@ function isSourceFile2(filePath, extensions) {
16146
16192
  async function scanPath5(inputPath, cfg) {
16147
16193
  const start = Date.now();
16148
16194
  const root = process.cwd();
16149
- const resolved = isAbsolute20(inputPath) ? resolve21(inputPath) : resolve21(root, inputPath);
16195
+ const resolved = isAbsolute21(inputPath) ? resolve21(inputPath) : resolve21(root, inputPath);
16150
16196
  if (!withinProject(inputPath)) {
16151
16197
  return {
16152
16198
  path: inputPath,
@@ -16167,7 +16213,7 @@ async function scanPath5(inputPath, cfg) {
16167
16213
  filesScanned += 1;
16168
16214
  const findings = scanSource(content, maxPerFile);
16169
16215
  for (const f of findings) {
16170
- allFindings.push({ ...f, snippet: `${relative20(root, filePath)}:${f.line}: ${f.snippet}` });
16216
+ allFindings.push({ ...f, snippet: `${relative21(root, filePath)}:${f.line}: ${f.snippet}` });
16171
16217
  if (allFindings.length >= cfg.maxFindings) return;
16172
16218
  }
16173
16219
  } catch {
@@ -16451,10 +16497,10 @@ var security_hotspot_scanner_default = plugin47;
16451
16497
 
16452
16498
  // src/semantic-search-indexer/index.ts
16453
16499
  import * as fs2 from "node:fs/promises";
16454
- import { isAbsolute as isAbsolute21, relative as relative21, resolve as resolve22 } from "node:path";
16500
+ import { isAbsolute as isAbsolute22, relative as relative22, resolve as resolve22 } from "node:path";
16455
16501
  import { DEFAULT_WALK_IGNORE_DIRS } from "@wrongstack/core/utils";
16456
16502
  var API_VERSION31 = "^0.1.10";
16457
- var escapeRegex2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16503
+ var escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16458
16504
  var state46 = {
16459
16505
  index: null,
16460
16506
  cachedPath: null,
@@ -16499,7 +16545,7 @@ var DEFAULTS41 = {
16499
16545
  ".html"
16500
16546
  ],
16501
16547
  excludePatterns: [
16502
- ...DEFAULT_WALK_IGNORE_DIRS.map(escapeRegex2),
16548
+ ...DEFAULT_WALK_IGNORE_DIRS.map(escapeRegex),
16503
16549
  "\\.wrongstack",
16504
16550
  "\\.temp_files"
16505
16551
  ],
@@ -16532,18 +16578,18 @@ function normalizeSlashes2(p) {
16532
16578
  function withinProject5(p) {
16533
16579
  if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
16534
16580
  const root = normalizeSlashes2(process.cwd());
16535
- const resolved = normalizeSlashes2(isAbsolute21(p) ? resolve22(p) : resolve22(root, p));
16536
- const rel = normalizeSlashes2(relative21(root, resolved));
16581
+ const resolved = normalizeSlashes2(isAbsolute22(p) ? resolve22(p) : resolve22(root, p));
16582
+ const rel = normalizeSlashes2(relative22(root, resolved));
16537
16583
  if (rel === "" || rel === ".") return true;
16538
16584
  if (rel.startsWith("..")) return false;
16539
- if (isAbsolute21(rel)) return false;
16585
+ if (isAbsolute22(rel)) return false;
16540
16586
  return true;
16541
16587
  }
16542
16588
  function resolveProjectPath8(p) {
16543
16589
  const raw = typeof p === "string" && p.length > 0 ? p : ".";
16544
16590
  if (!withinProject5(raw)) return null;
16545
16591
  const root = normalizeSlashes2(process.cwd());
16546
- return normalizeSlashes2(isAbsolute21(raw) ? resolve22(raw) : resolve22(root, raw));
16592
+ return normalizeSlashes2(isAbsolute22(raw) ? resolve22(raw) : resolve22(root, raw));
16547
16593
  }
16548
16594
  function tokenize(text, minLength) {
16549
16595
  const tokens = [];
@@ -16628,7 +16674,7 @@ async function walkDirectory(absPath, cfg, excludes, fileBatch) {
16628
16674
  return;
16629
16675
  }
16630
16676
  const absChild = normalizeSlashes2(resolve22(absPath, ent.name));
16631
- const relChild = normalizeSlashes2(relative21(root, absChild));
16677
+ const relChild = normalizeSlashes2(relative22(root, absChild));
16632
16678
  if (relChild === "" || relChild === ".") continue;
16633
16679
  if (excludes.some((re) => re.test(relChild))) continue;
16634
16680
  if (ent.isDirectory()) {
@@ -16670,7 +16716,7 @@ async function buildIndex(rootPath, cfg) {
16670
16716
  return;
16671
16717
  }
16672
16718
  if (rootStats.isFile()) {
16673
- const relPath = normalizeSlashes2(relative21(normalizeSlashes2(process.cwd()), rootPath));
16719
+ const relPath = normalizeSlashes2(relative22(normalizeSlashes2(process.cwd()), rootPath));
16674
16720
  await indexFileFromStats(rootPath, relPath === "" ? "." : relPath, rootStats, cfg);
16675
16721
  state46.fileCount = state46.index.files.size;
16676
16722
  } else if (rootStats.isDirectory()) {
@@ -16967,14 +17013,14 @@ import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
16967
17013
  import { toErrorMessage } from "@wrongstack/core/utils";
16968
17014
  import { execFile as execFile12 } from "node:child_process";
16969
17015
  import { access as access3, readFile as readFile14, readdir as readdir4, writeFile as writeFile4 } from "node:fs/promises";
16970
- import { isAbsolute as isAbsolute22, join as join5, relative as relative22, resolve as resolve23 } from "node:path";
17016
+ import { isAbsolute as isAbsolute23, join as join5, relative as relative23, resolve as resolve23 } from "node:path";
16971
17017
  var API_VERSION32 = "^0.1.10";
16972
17018
  function resolveProjectRoot(rawCwd, root = process.cwd()) {
16973
17019
  if (typeof rawCwd !== "string" || rawCwd.length === 0) return root;
16974
17020
  const base = resolve23(root);
16975
- const resolved = isAbsolute22(rawCwd) ? resolve23(rawCwd) : resolve23(base, rawCwd);
16976
- const rel = relative22(base, resolved);
16977
- if (rel === "" || !rel.startsWith("..") && !isAbsolute22(rel)) return resolved;
17021
+ const resolved = isAbsolute23(rawCwd) ? resolve23(rawCwd) : resolve23(base, rawCwd);
17022
+ const rel = relative23(base, resolved);
17023
+ if (rel === "" || !rel.startsWith("..") && !isAbsolute23(rel)) return resolved;
16978
17024
  return null;
16979
17025
  }
16980
17026
  var state47 = {
@@ -17613,25 +17659,59 @@ function topN(map, n) {
17613
17659
  return 0;
17614
17660
  }).slice(0, n);
17615
17661
  }
17662
+ var TRANSCRIPT_TAIL_READ_BYTES = 1024 * 1024;
17663
+ var TRANSCRIPT_TAIL_CHUNK_BYTES = 64 * 1024;
17616
17664
  async function readTranscriptTail(transcriptPath, n) {
17617
17665
  if (!transcriptPath || n <= 0) return [];
17618
- let raw;
17666
+ let handle;
17619
17667
  try {
17620
- const { readFile: readFile18 } = await import("node:fs/promises");
17621
- raw = await readFile18(transcriptPath, "utf-8");
17668
+ const { open } = await import("node:fs/promises");
17669
+ handle = await open(transcriptPath, "r");
17670
+ const size = (await handle.stat()).size;
17671
+ if (size === 0) return [];
17672
+ let position = size;
17673
+ let retainedBytes = 0;
17674
+ let newlines = 0;
17675
+ const chunks = [];
17676
+ while (position > 0 && retainedBytes < TRANSCRIPT_TAIL_READ_BYTES) {
17677
+ const length = Math.min(
17678
+ TRANSCRIPT_TAIL_CHUNK_BYTES,
17679
+ position,
17680
+ TRANSCRIPT_TAIL_READ_BYTES - retainedBytes
17681
+ );
17682
+ position -= length;
17683
+ const buffer = Buffer.allocUnsafe(length);
17684
+ const { bytesRead } = await handle.read(buffer, 0, length, position);
17685
+ if (bytesRead === 0) break;
17686
+ const chunk = bytesRead === length ? buffer : buffer.subarray(0, bytesRead);
17687
+ chunks.unshift(chunk);
17688
+ retainedBytes += bytesRead;
17689
+ let offset = chunk.indexOf(10);
17690
+ while (offset !== -1) {
17691
+ newlines++;
17692
+ offset = chunk.indexOf(10, offset + 1);
17693
+ }
17694
+ if (position === 0) break;
17695
+ if (newlines > n) break;
17696
+ }
17697
+ const joined = Buffer.concat(chunks).toString("utf8");
17698
+ const truncatedHead = position > 0;
17699
+ const allLines = joined.split("\n");
17700
+ const candidateLines = truncatedHead ? allLines.slice(1) : allLines;
17701
+ const tail = candidateLines.filter((line) => line.length > 0).slice(-n);
17702
+ const out = [];
17703
+ for (const l of tail) {
17704
+ try {
17705
+ out.push(JSON.parse(l));
17706
+ } catch {
17707
+ }
17708
+ }
17709
+ return out;
17622
17710
  } catch {
17623
17711
  return [];
17712
+ } finally {
17713
+ await handle?.close().catch(() => void 0);
17624
17714
  }
17625
- const lines = raw.split("\n").filter((l) => l.length > 0);
17626
- const tail = lines.slice(-n);
17627
- const out = [];
17628
- for (const l of tail) {
17629
- try {
17630
- out.push(JSON.parse(l));
17631
- } catch {
17632
- }
17633
- }
17634
- return out;
17635
17715
  }
17636
17716
  function truncate2(s, max) {
17637
17717
  return s.length > max ? s.slice(0, max) + `
@@ -17959,15 +18039,15 @@ var session_recap_default = plugin50;
17959
18039
  // src/shell-check/index.ts
17960
18040
  import { execFile as execFile13 } from "node:child_process";
17961
18041
  import { readdir as readdir5 } from "node:fs/promises";
17962
- import { isAbsolute as isAbsolute23, join as join6, relative as relative23, resolve as resolve24 } from "node:path";
18042
+ import { isAbsolute as isAbsolute24, join as join6, relative as relative24, resolve as resolve24 } from "node:path";
17963
18043
  var API_VERSION33 = "^0.1.10";
17964
18044
  function withinProject6(p) {
17965
18045
  const root = process.cwd();
17966
- const resolved = isAbsolute23(p) ? resolve24(p) : resolve24(root, p);
17967
- const rel = relative23(root, resolved);
18046
+ const resolved = isAbsolute24(p) ? resolve24(p) : resolve24(root, p);
18047
+ const rel = relative24(root, resolved);
17968
18048
  if (rel === "" || rel === ".") return true;
17969
18049
  if (rel.startsWith("..")) return false;
17970
- if (isAbsolute23(rel)) return false;
18050
+ if (isAbsolute24(rel)) return false;
17971
18051
  return true;
17972
18052
  }
17973
18053
  var MAX_PATH_LEN = 4096;
@@ -18260,7 +18340,7 @@ var shell_check_default = plugin51;
18260
18340
 
18261
18341
  // src/smart-rename/index.ts
18262
18342
  import { readFileSync as readFileSync13, writeFileSync as writeFileSync2 } from "node:fs";
18263
- import { extname as extname6, isAbsolute as isAbsolute24, relative as relative24, resolve as resolve25 } from "node:path";
18343
+ import { extname as extname6, isAbsolute as isAbsolute25, relative as relative25, resolve as resolve25 } from "node:path";
18264
18344
  var API_VERSION34 = "^0.1.10";
18265
18345
  var state50 = {
18266
18346
  renameCount: 0,
@@ -18282,20 +18362,20 @@ function readConfig46(raw) {
18282
18362
  function withinProject7(p) {
18283
18363
  if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
18284
18364
  const root = process.cwd();
18285
- const resolved = isAbsolute24(p) ? resolve25(p) : resolve25(root, p);
18286
- const rel = relative24(root, resolved);
18365
+ const resolved = isAbsolute25(p) ? resolve25(p) : resolve25(root, p);
18366
+ const rel = relative25(root, resolved);
18287
18367
  if (rel === "" || rel === ".") return true;
18288
18368
  if (rel.startsWith("..")) return false;
18289
- if (isAbsolute24(rel)) return false;
18369
+ if (isAbsolute25(rel)) return false;
18290
18370
  return true;
18291
18371
  }
18292
18372
  function toPosix7(p) {
18293
18373
  return p.replace(/\\/g, "/");
18294
18374
  }
18295
18375
  function relativePath7(p) {
18296
- return toPosix7(relative24(process.cwd(), p));
18376
+ return toPosix7(relative25(process.cwd(), p));
18297
18377
  }
18298
- function escapeRegex3(s) {
18378
+ function escapeRegex2(s) {
18299
18379
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18300
18380
  }
18301
18381
  var IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
@@ -18304,7 +18384,7 @@ function isIdentifier(name) {
18304
18384
  }
18305
18385
  function renameInContent(content, oldName, newName) {
18306
18386
  const re = new RegExp(
18307
- `(?<![A-Za-z0-9_$])${escapeRegex3(oldName)}(?![A-Za-z0-9_$])`,
18387
+ `(?<![A-Za-z0-9_$])${escapeRegex2(oldName)}(?![A-Za-z0-9_$])`,
18308
18388
  "g"
18309
18389
  );
18310
18390
  let replacements = 0;
@@ -18487,7 +18567,6 @@ var OFFICIAL_PLUGIN_NAMES = [
18487
18567
  "type-gate",
18488
18568
  "token-throttle",
18489
18569
  "plugin-stack-observer",
18490
- "dead-code-detector",
18491
18570
  "dependency-vulnerability-gate",
18492
18571
  "migration-planner",
18493
18572
  "semantic-search-indexer",
@@ -18500,6 +18579,7 @@ var OFFICIAL_PLUGIN_NAMES = [
18500
18579
  "license-audit-gate",
18501
18580
  "accessibility-auditor",
18502
18581
  "security-hotspot-scanner",
18582
+ "dead-code-detector",
18503
18583
  "duplicate-code-detector",
18504
18584
  "code-metrics",
18505
18585
  "refactor-suggester",
@@ -18926,9 +19006,17 @@ var spec_linker_default = plugin53;
18926
19006
 
18927
19007
  // src/template-engine/index.ts
18928
19008
  import { readFile as readFile16, writeFile as writeFile5 } from "node:fs/promises";
18929
- import { isAbsolute as isAbsolute25 } from "node:path";
19009
+ import { isAbsolute as isAbsolute26 } from "node:path";
18930
19010
  var API_VERSION35 = "^0.1.10";
18931
19011
  var templates = /* @__PURE__ */ new Map();
19012
+ var MAX_TEMPLATES = 256;
19013
+ var MAX_TEMPLATE_NAME_CHARS = 128;
19014
+ var MAX_TEMPLATE_CONTENT_CHARS = 256 * 1024;
19015
+ var MAX_TEMPLATE_DESCRIPTION_CHARS = 4 * 1024;
19016
+ var MAX_TOTAL_TEMPLATE_CHARS = 8 * 1024 * 1024;
19017
+ function templateChars(template) {
19018
+ return template.name.length + template.content.length + (template.description?.length ?? 0);
19019
+ }
18932
19020
  var contributorUnregister = null;
18933
19021
  function expandTemplate(template, variables) {
18934
19022
  let result = template;
@@ -18974,7 +19062,7 @@ function renderTemplateRaw(template, variables) {
18974
19062
  return result;
18975
19063
  }
18976
19064
  function validateRelativeTemplatePath(field, value) {
18977
- if (isAbsolute25(value) || value.split(/[\\/]+/).includes("..")) {
19065
+ if (isAbsolute26(value) || value.split(/[\\/]+/).includes("..")) {
18978
19066
  return `${field} must be a relative path without ".." components`;
18979
19067
  }
18980
19068
  if (!withinProject(value)) {
@@ -19138,13 +19226,19 @@ var plugin54 = {
19138
19226
  inputSchema: {
19139
19227
  type: "object",
19140
19228
  properties: {
19141
- name: { type: "string", description: "Unique name for this template" },
19229
+ name: {
19230
+ type: "string",
19231
+ maxLength: MAX_TEMPLATE_NAME_CHARS,
19232
+ description: "Unique name for this template"
19233
+ },
19142
19234
  content: {
19143
19235
  type: "string",
19236
+ maxLength: MAX_TEMPLATE_CONTENT_CHARS,
19144
19237
  description: "Template content with {{variable}} placeholders"
19145
19238
  },
19146
19239
  description: {
19147
19240
  type: "string",
19241
+ maxLength: MAX_TEMPLATE_DESCRIPTION_CHARS,
19148
19242
  description: "Optional description of what this template is for"
19149
19243
  }
19150
19244
  },
@@ -19162,8 +19256,26 @@ var plugin54 = {
19162
19256
  if (!content || typeof content !== "string") {
19163
19257
  return { ok: false, error: "content is required and must be a string" };
19164
19258
  }
19259
+ if (name.length > MAX_TEMPLATE_NAME_CHARS) {
19260
+ return { ok: false, error: `name exceeds ${MAX_TEMPLATE_NAME_CHARS} characters` };
19261
+ }
19262
+ if (content.length > MAX_TEMPLATE_CONTENT_CHARS) {
19263
+ return {
19264
+ ok: false,
19265
+ error: `content exceeds ${MAX_TEMPLATE_CONTENT_CHARS} characters`
19266
+ };
19267
+ }
19268
+ if (description && description.length > MAX_TEMPLATE_DESCRIPTION_CHARS) {
19269
+ return {
19270
+ ok: false,
19271
+ error: `description exceeds ${MAX_TEMPLATE_DESCRIPTION_CHARS} characters`
19272
+ };
19273
+ }
19165
19274
  const now = (/* @__PURE__ */ new Date()).toISOString();
19166
19275
  const existing = templates.get(name);
19276
+ if (!existing && templates.size >= MAX_TEMPLATES) {
19277
+ return { ok: false, error: `template limit reached (${MAX_TEMPLATES})` };
19278
+ }
19167
19279
  const tmpl = {
19168
19280
  name,
19169
19281
  content,
@@ -19171,6 +19283,15 @@ var plugin54 = {
19171
19283
  createdAt: existing?.createdAt ?? now,
19172
19284
  updatedAt: now
19173
19285
  };
19286
+ let retainedChars = 0;
19287
+ for (const stored of templates.values()) retainedChars += templateChars(stored);
19288
+ const nextChars = retainedChars - (existing ? templateChars(existing) : 0) + templateChars(tmpl);
19289
+ if (nextChars > MAX_TOTAL_TEMPLATE_CHARS) {
19290
+ return {
19291
+ ok: false,
19292
+ error: `template store exceeds ${MAX_TOTAL_TEMPLATE_CHARS} retained characters`
19293
+ };
19294
+ }
19174
19295
  templates.set(name, tmpl);
19175
19296
  api.metrics.gauge("template_count", templates.size);
19176
19297
  return {
@@ -19496,7 +19617,7 @@ var test_coverage_gate_default = plugin55;
19496
19617
  import { execFile as execFile14 } from "node:child_process";
19497
19618
  import { readFileSync as readFileSync15 } from "node:fs";
19498
19619
  import { createRequire as createRequire3 } from "node:module";
19499
- import { dirname as dirname8, isAbsolute as isAbsolute26, relative as relative25, resolve as resolve26 } from "node:path";
19620
+ import { dirname as dirname8, isAbsolute as isAbsolute27, relative as relative26, resolve as resolve26 } from "node:path";
19500
19621
  var API_VERSION37 = "^0.1.10";
19501
19622
  var state53 = {
19502
19623
  invocationCount: 0,
@@ -19568,14 +19689,14 @@ var ALLOWED_RUNNER_FLAGS = /* @__PURE__ */ new Set([
19568
19689
  function withinProject8(p) {
19569
19690
  if (p.length === 0 || p.length > 4096 || p.startsWith("-")) return false;
19570
19691
  const root = resolve26(process.cwd());
19571
- const resolved = isAbsolute26(p) ? resolve26(p) : resolve26(root, p);
19572
- const rel = relative25(root, resolved);
19573
- return rel === "" || !rel.startsWith("..") && !isAbsolute26(rel);
19692
+ const resolved = isAbsolute27(p) ? resolve26(p) : resolve26(root, p);
19693
+ const rel = relative26(root, resolved);
19694
+ return rel === "" || !rel.startsWith("..") && !isAbsolute27(rel);
19574
19695
  }
19575
19696
  function isInside3(parent, child) {
19576
19697
  if (parent === child) return true;
19577
- const rel = relative25(parent, child);
19578
- return rel !== "" && !rel.startsWith("..") && !isAbsolute26(rel);
19698
+ const rel = relative26(parent, child);
19699
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute27(rel);
19579
19700
  }
19580
19701
  function tokenizeCommand(command) {
19581
19702
  const trimmed = command.trim();
@@ -19619,7 +19740,7 @@ function resolveTestCommand(baseCommand, testPattern) {
19619
19740
  if (!relativeBin) return null;
19620
19741
  const packageDir = dirname8(packagePath);
19621
19742
  const candidate = resolve26(packageDir, relativeBin);
19622
- if (isAbsolute26(relativeBin) || !isInside3(packageDir, candidate)) {
19743
+ if (isAbsolute27(relativeBin) || !isInside3(packageDir, candidate)) {
19623
19744
  return null;
19624
19745
  }
19625
19746
  resolvedEntry = candidate;
@@ -19862,7 +19983,7 @@ var test_flake_detector_default = plugin56;
19862
19983
 
19863
19984
  // src/test-generator/index.ts
19864
19985
  import { readFileSync as readFileSync16 } from "node:fs";
19865
- import { isAbsolute as isAbsolute27, relative as relative26, resolve as resolve27 } from "node:path";
19986
+ import { isAbsolute as isAbsolute28, relative as relative27, resolve as resolve27 } from "node:path";
19866
19987
  var API_VERSION38 = "^0.1.10";
19867
19988
  var state54 = {
19868
19989
  generateCount: 0,
@@ -19897,18 +20018,18 @@ function readConfig50(raw) {
19897
20018
  function withinProject9(p) {
19898
20019
  if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
19899
20020
  const root = process.cwd();
19900
- const resolved = isAbsolute27(p) ? resolve27(p) : resolve27(root, p);
19901
- const rel = relative26(root, resolved);
20021
+ const resolved = isAbsolute28(p) ? resolve27(p) : resolve27(root, p);
20022
+ const rel = relative27(root, resolved);
19902
20023
  if (rel === "" || rel === ".") return true;
19903
20024
  if (rel.startsWith("..")) return false;
19904
- if (isAbsolute27(rel)) return false;
20025
+ if (isAbsolute28(rel)) return false;
19905
20026
  return true;
19906
20027
  }
19907
20028
  function toPosix8(p) {
19908
20029
  return p.replace(/\\/g, "/");
19909
20030
  }
19910
20031
  function relativePath8(p) {
19911
- return toPosix8(relative26(process.cwd(), p));
20032
+ return toPosix8(relative27(process.cwd(), p));
19912
20033
  }
19913
20034
  function detectExports(content) {
19914
20035
  const exports = [];
@@ -20207,7 +20328,7 @@ var test_generator_default = plugin57;
20207
20328
  // src/test-runner-gate/index.ts
20208
20329
  import { execFile as execFile15 } from "node:child_process";
20209
20330
  import { access as access4 } from "node:fs/promises";
20210
- import { basename as basename6, dirname as dirname9, isAbsolute as isAbsolute28, join as join7 } from "node:path";
20331
+ import { basename as basename6, dirname as dirname9, isAbsolute as isAbsolute29, join as join7 } from "node:path";
20211
20332
  import { buildWin32CmdShimInvocation as buildWin32CmdShimInvocation2, resolveWin32Command as resolveWin32Command2 } from "@wrongstack/tools/win32";
20212
20333
  function resolveExec(command, args) {
20213
20334
  const resolved = resolveWin32Command2(command);
@@ -20376,7 +20497,7 @@ function resolveAllowedCommand2(customCommand) {
20376
20497
  if (ALLOWED_COMMAND_TOKENS.has(head)) {
20377
20498
  return { cmd: head, args: tokens.slice(1) };
20378
20499
  }
20379
- if (isAbsolute28(head)) {
20500
+ if (isAbsolute29(head)) {
20380
20501
  if (!withinProject(head)) return null;
20381
20502
  const base = basename6(head);
20382
20503
  if (ALLOWED_COMMAND_TOKENS.has(base)) {