@wrongstack/plugins 0.319.0 → 1.0.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 (49) hide show
  1. package/dist/accessibility-auditor.js +21 -4
  2. package/dist/api-compatibility-gate.js +3 -1
  3. package/dist/auto-i18n-extractor.js +9 -3
  4. package/dist/changelog-writer.js +1 -4
  5. package/dist/checkpoint.js +40 -7
  6. package/dist/code-metrics.js +3 -1
  7. package/dist/config-validator.js +6 -2
  8. package/dist/context-pins.js +4 -1
  9. package/dist/cost-tracker.js +3 -1
  10. package/dist/cron.js +8 -2
  11. package/dist/dep-guard.js +3 -1
  12. package/dist/dependency-vulnerability-gate.js +3 -1
  13. package/dist/diff-summary.js +9 -3
  14. package/dist/doc-sync-guard.js +6 -2
  15. package/dist/feature-flag-tracker.js +3 -1
  16. package/dist/format-on-save.js +3 -1
  17. package/dist/import-organizer.js +3 -1
  18. package/dist/index.js +351 -152
  19. package/dist/interface-contract-guard.js +3 -1
  20. package/dist/knowledge-graph.js +9 -4
  21. package/dist/license-audit-gate.js +15 -3
  22. package/dist/migration-planner.js +1 -1
  23. package/dist/path-guard/index.d.ts +8 -1
  24. package/dist/path-guard.js +42 -16
  25. package/dist/pr-drafter.js +3 -1
  26. package/dist/process-guard.js +3 -1
  27. package/dist/prompt-firewall.js +16 -6
  28. package/dist/refactor-suggester.js +3 -1
  29. package/dist/release-notes-generator.js +1 -1
  30. package/dist/runtime/credential-patterns.d.ts +2 -2
  31. package/dist/runtime/h1-state.d.ts +2 -2
  32. package/dist/runtime/handles.d.ts +2 -2
  33. package/dist/runtime/index.d.ts +19 -2
  34. package/dist/runtime/llm.d.ts +2 -2
  35. package/dist/runtime/redos-guard.d.ts +2 -2
  36. package/dist/runtime/sandbox.d.ts +2 -2
  37. package/dist/secret-scanner.js +1 -1
  38. package/dist/security-hotspot-scanner.js +21 -3
  39. package/dist/semantic-search-indexer.js +12 -7
  40. package/dist/semver-bump.js +17 -12
  41. package/dist/shell-check.js +7 -10
  42. package/dist/smart-rename.js +8 -6
  43. package/dist/spec-linker.js +3 -1
  44. package/dist/template-engine.js +18 -12
  45. package/dist/test-flake-detector.js +1 -4
  46. package/dist/test-generator.js +4 -2
  47. package/dist/test-runner-gate.js +27 -15
  48. package/dist/type-gate.js +3 -1
  49. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -175,7 +175,12 @@ async function auditFile(filePath, projectRoot) {
175
175
  );
176
176
  }
177
177
  if (!hasPrimaryLabel && ATTR_PLACEHOLDER.test(tag)) {
178
- add(lineNo, "low-contrast-placeholder", "warning", "<input> uses placeholder text (often low contrast and disappears on input)");
178
+ add(
179
+ lineNo,
180
+ "low-contrast-placeholder",
181
+ "warning",
182
+ "<input> uses placeholder text (often low contrast and disappears on input)"
183
+ );
179
184
  }
180
185
  }
181
186
  TAG_INPUT.lastIndex = 0;
@@ -186,14 +191,24 @@ async function auditFile(filePath, projectRoot) {
186
191
  const hasAriaLabel = ATTR_ARIA_LABEL.test(tag);
187
192
  const hasTitle = ATTR_TITLE.test(tag);
188
193
  if (!hasText && !hasAriaLabel && !hasTitle) {
189
- add(lineNo, "missing-button-text", "error", "<button> has no visible text or accessible label");
194
+ add(
195
+ lineNo,
196
+ "missing-button-text",
197
+ "error",
198
+ "<button> has no visible text or accessible label"
199
+ );
190
200
  }
191
201
  }
192
202
  TAG_BUTTON.lastIndex = 0;
193
203
  for (const inputButtonMatch of line.matchAll(INPUT_BUTTON)) {
194
204
  const tag = inputButtonMatch[0];
195
205
  if (!ATTR_VALUE.test(tag) && !ATTR_ARIA_LABEL.test(tag) && !ATTR_TITLE.test(tag)) {
196
- add(lineNo, "missing-button-text", "error", `<input type="${inputButtonMatch[1]}"> is missing value/aria-label/title`);
206
+ add(
207
+ lineNo,
208
+ "missing-button-text",
209
+ "error",
210
+ `<input type="${inputButtonMatch[1]}"> is missing value/aria-label/title`
211
+ );
197
212
  }
198
213
  }
199
214
  INPUT_BUTTON.lastIndex = 0;
@@ -328,7 +343,9 @@ var plugin = {
328
343
  }
329
344
  return { additionalContext: summary };
330
345
  };
331
- state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
346
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, {
347
+ background: true
348
+ });
332
349
  api.tools.register({
333
350
  name: "a11y_audit",
334
351
  description: "Audit a file or directory for accessibility issues. Scans .tsx/.jsx/.html/.vue files for missing alt text, missing labels, low-contrast placeholders, missing button text, and duplicate ids.",
@@ -1058,7 +1075,9 @@ var plugin3 = {
1058
1075
  }
1059
1076
  return { additionalContext: message };
1060
1077
  };
1061
- state3.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
1078
+ state3.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, {
1079
+ background: true
1080
+ });
1062
1081
  api.tools.register({
1063
1082
  name: "api_compat_status",
1064
1083
  description: "Reports api-compatibility-gate state: severity, entry-point patterns, and per-session counters.",
@@ -1760,7 +1779,9 @@ function looksLikeUserText(value, minLength) {
1760
1779
  if (value.length < minLength) return false;
1761
1780
  if (/^\s*$/.test(value)) return false;
1762
1781
  if (/^[0-9\s\W_]+$/.test(value)) return false;
1763
- if (/^(https?:|ftp:|www\.|\/|\.\/|@|#[0-9a-f]{3,8}$|\.?(jsx?|tsx?|vue|css|scss|json|png|svg|jpg)$)/i.test(value)) {
1782
+ if (/^(https?:|ftp:|www\.|\/|\.\/|@|#[0-9a-f]{3,8}$|\.?(jsx?|tsx?|vue|css|scss|json|png|svg|jpg)$)/i.test(
1783
+ value
1784
+ )) {
1764
1785
  return false;
1765
1786
  }
1766
1787
  return true;
@@ -1768,7 +1789,9 @@ function looksLikeUserText(value, minLength) {
1768
1789
  function isExcludedContext(line, quoteIndex, excludeAttributes) {
1769
1790
  const before = line.slice(0, quoteIndex);
1770
1791
  for (const attr of excludeAttributes) {
1771
- const re = new RegExp(`\\b${attr.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*=\\s*(?:\\{\\s*)?["']?$`);
1792
+ const re = new RegExp(
1793
+ `\\b${attr.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*=\\s*(?:\\{\\s*)?["']?$`
1794
+ );
1772
1795
  if (re.test(before)) return true;
1773
1796
  }
1774
1797
  return false;
@@ -1900,7 +1923,9 @@ var plugin6 = {
1900
1923
  ${lines.join("\n")}${more}`;
1901
1924
  return { additionalContext: context };
1902
1925
  };
1903
- state6.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
1926
+ state6.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, {
1927
+ background: true
1928
+ });
1904
1929
  api.tools.register({
1905
1930
  name: "i18n_extract",
1906
1931
  description: "Scan a UI source file (.tsx/.jsx/.vue) for hardcoded user-facing string literals and return suggested i18n keys.",
@@ -2710,10 +2735,7 @@ var plugin8 = {
2710
2735
  }
2711
2736
  const rawInput = input ?? {};
2712
2737
  const shouldPolish = input.polish === true || rawInput["use_llm"] === true || rawInput["useLlm"] === true || rawInput["ai"] === true || rawInput["use_ai"] === true;
2713
- const block = await maybePolish(
2714
- renderUnreleasedBlock(state8.entries),
2715
- shouldPolish
2716
- );
2738
+ const block = await maybePolish(renderUnreleasedBlock(state8.entries), shouldPolish);
2717
2739
  let existing = null;
2718
2740
  try {
2719
2741
  existing = readFileSync3(cfg.filePath, "utf-8");
@@ -2783,7 +2805,7 @@ var plugin8 = {
2783
2805
  var changelog_writer_default = plugin8;
2784
2806
 
2785
2807
  // src/checkpoint/index.ts
2786
- import { mkdir, readFile as readFile3, stat, writeFile } from "node:fs/promises";
2808
+ import { mkdir, readFile as readFile3, realpath, stat, writeFile } from "node:fs/promises";
2787
2809
  import { dirname as dirname2, isAbsolute as isAbsolute5, relative as relative5, resolve as resolve5 } from "node:path";
2788
2810
  var state9 = {
2789
2811
  snapshots: [],
@@ -2812,13 +2834,46 @@ function readConfig8(raw) {
2812
2834
  maxFileBytes: typeof r["maxFileBytes"] === "number" && r["maxFileBytes"] >= 1024 ? r["maxFileBytes"] : DEFAULTS8.maxFileBytes
2813
2835
  };
2814
2836
  }
2815
- function resolveProjectPath3(rawPath, cwd = process.cwd()) {
2837
+ async function resolveProjectPath3(rawPath, cwd = process.cwd()) {
2816
2838
  if (typeof rawPath !== "string" || rawPath.length === 0) return null;
2817
2839
  const root = resolve5(cwd);
2818
2840
  const resolved = isAbsolute5(rawPath) ? resolve5(rawPath) : resolve5(root, rawPath);
2819
2841
  const rel = relative5(root, resolved);
2820
- if (rel === "" || !rel.startsWith("..") && !isAbsolute5(rel)) return resolved;
2821
- return null;
2842
+ const lexicallyInside = rel === "" || !rel.startsWith("..") && !isAbsolute5(rel);
2843
+ if (!lexicallyInside) return null;
2844
+ if (await realResolutionEscapes(resolved, root)) return null;
2845
+ return resolved;
2846
+ }
2847
+ async function realResolutionEscapes(absPath, root) {
2848
+ let real;
2849
+ try {
2850
+ real = await realpath(absPath);
2851
+ } catch {
2852
+ let current = dirname2(absPath);
2853
+ let hops = 0;
2854
+ while (hops < 64 && !await pathExists(current)) {
2855
+ const parent = dirname2(current);
2856
+ if (parent === current) return false;
2857
+ current = parent;
2858
+ hops++;
2859
+ }
2860
+ if (!await pathExists(current)) return false;
2861
+ try {
2862
+ real = await realpath(current);
2863
+ } catch {
2864
+ return false;
2865
+ }
2866
+ }
2867
+ const realRel = relative5(root, real);
2868
+ return realRel !== "" && realRel !== "." && (realRel.startsWith("..") || isAbsolute5(realRel));
2869
+ }
2870
+ async function pathExists(p) {
2871
+ try {
2872
+ await stat(p);
2873
+ return true;
2874
+ } catch {
2875
+ return false;
2876
+ }
2822
2877
  }
2823
2878
  function hashContent(s) {
2824
2879
  const cap = Math.min(s.length, 65536);
@@ -2919,7 +2974,7 @@ var plugin9 = {
2919
2974
  const ti = input.toolInput ?? {};
2920
2975
  const raw = ti["path"] ?? ti["file_path"] ?? ti["filePath"] ?? ti["TargetFile"] ?? ti["targetFile"] ?? ti["file"];
2921
2976
  if (typeof raw !== "string" || raw.length === 0) return;
2922
- const safePath = resolveProjectPath3(raw);
2977
+ const safePath = await resolveProjectPath3(raw);
2923
2978
  if (!safePath) return;
2924
2979
  const captured = await captureFileForHook(safePath, cfg.maxFileBytes, runtime.signal);
2925
2980
  if (captured === "too-large") {
@@ -2990,7 +3045,7 @@ var plugin9 = {
2990
3045
  const rejectedOutsideProject = [];
2991
3046
  let skipped = 0;
2992
3047
  for (const p of paths) {
2993
- const safePath = resolveProjectPath3(p);
3048
+ const safePath = await resolveProjectPath3(p);
2994
3049
  if (!safePath) {
2995
3050
  rejectedOutsideProject.push(p);
2996
3051
  continue;
@@ -3097,7 +3152,7 @@ var plugin9 = {
3097
3152
  error: rawId ? `no snapshot with id "${rawId}"` : "no snapshots captured yet"
3098
3153
  };
3099
3154
  }
3100
- const targetPath = rawPath ? resolveProjectPath3(rawPath) ?? rawPath : null;
3155
+ const targetPath = rawPath ? await resolveProjectPath3(rawPath) ?? rawPath : null;
3101
3156
  const targets = targetPath ? snapshot.files.filter((f) => f.path === targetPath || f.path === rawPath) : snapshot.files;
3102
3157
  if (targets.length === 0) {
3103
3158
  return { ok: false, error: `snapshot ${snapshot.id} has no entry for "${rawPath}"` };
@@ -3403,7 +3458,9 @@ var plugin10 = {
3403
3458
  \u{1F4CA} code-metrics: ${relativePath(resolved)} \u2014 ${metrics.lines} lines (${metrics.codeLines} code, ${metrics.commentLines} comments, ${metrics.blankLines} blank), ${metrics.functionCount} function(s), complexity ${metrics.complexity}.`
3404
3459
  };
3405
3460
  };
3406
- state10.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
3461
+ state10.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, {
3462
+ background: true
3463
+ });
3407
3464
  api.tools.register({
3408
3465
  name: "measure_code_metrics",
3409
3466
  description: "Measure lines, comments, blank lines, function count, and cyclomatic-complexity-like score for a source file or directory.",
@@ -3920,7 +3977,9 @@ function readConfig11(raw) {
3920
3977
  const rawMax = r["maxFileBytes"] ?? r["max_file_bytes"] ?? r["maxBytes"] ?? r["max_bytes"];
3921
3978
  return {
3922
3979
  enabled: r["enabled"] !== false,
3923
- extensions: Array.isArray(rawExts) ? rawExts.filter((e) => typeof e === "string" && e.trim().length > 0).map((e) => e.trim().startsWith(".") ? e.trim().toLowerCase() : `.${e.trim().toLowerCase()}`) : [...DEFAULT_EXTENSIONS],
3980
+ extensions: Array.isArray(rawExts) ? rawExts.filter((e) => typeof e === "string" && e.trim().length > 0).map(
3981
+ (e) => e.trim().startsWith(".") ? e.trim().toLowerCase() : `.${e.trim().toLowerCase()}`
3982
+ ) : [...DEFAULT_EXTENSIONS],
3924
3983
  maxFileBytes: typeof rawMax === "number" && rawMax >= 1024 ? rawMax : DEFAULTS11.maxFileBytes
3925
3984
  };
3926
3985
  }
@@ -4214,7 +4273,9 @@ var plugin12 = {
4214
4273
  \u2026 and ${problems.length - 10} more` : "") + "\nFix these before moving on \u2014 downstream tools will fail on this file."
4215
4274
  };
4216
4275
  };
4217
- state12.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
4276
+ state12.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, {
4277
+ background: true
4278
+ });
4218
4279
  api.tools.register({
4219
4280
  name: "config_validator_status",
4220
4281
  description: "Reports config-validator state: watched extensions and counters (files checked, problems found).",
@@ -4335,7 +4396,10 @@ async function persistPins(filePath) {
4335
4396
  if (!filePath) return true;
4336
4397
  try {
4337
4398
  await ensureDir(dirname3(filePath));
4338
- await atomicWrite(filePath, JSON.stringify({ pins: state13.pins, nextId: state13.nextId }, null, 2));
4399
+ await atomicWrite(
4400
+ filePath,
4401
+ JSON.stringify({ pins: state13.pins, nextId: state13.nextId }, null, 2)
4402
+ );
4339
4403
  return true;
4340
4404
  } catch {
4341
4405
  state13.persistErrors += 1;
@@ -4707,7 +4771,9 @@ var plugin14 = {
4707
4771
  const rawCacheWrite = Number(u["cacheWrite"] ?? u["cache_creation_input_tokens"] ?? 0) || 0;
4708
4772
  const freshTokens = rawInput + rawCacheWrite;
4709
4773
  const promptTokens = freshTokens + cachedTokens;
4710
- const completionTokens = Number(u["output"] ?? u["completion_tokens"] ?? u["outputTokens"] ?? u["completionTokens"] ?? 0) || 0;
4774
+ const completionTokens = Number(
4775
+ u["output"] ?? u["completion_tokens"] ?? u["outputTokens"] ?? u["completionTokens"] ?? 0
4776
+ ) || 0;
4711
4777
  const totalTokens = promptTokens + completionTokens;
4712
4778
  const costUsd = estimateCost(model, freshTokens, completionTokens, cachedTokens);
4713
4779
  const record = {
@@ -5073,8 +5139,14 @@ var plugin15 = {
5073
5139
  type: "object",
5074
5140
  properties: {
5075
5141
  name: { type: "string", description: "Unique name for this cron job" },
5076
- intervalMs: { type: "number", description: "Interval between runs in milliseconds (minimum 1000)" },
5077
- action: { type: "string", description: "Action identifier or description of what to run" },
5142
+ intervalMs: {
5143
+ type: "number",
5144
+ description: "Interval between runs in milliseconds (minimum 1000)"
5145
+ },
5146
+ action: {
5147
+ type: "string",
5148
+ description: "Action identifier or description of what to run"
5149
+ },
5078
5150
  enabled: { type: "boolean", default: true }
5079
5151
  },
5080
5152
  required: ["name", "intervalMs", "action"]
@@ -5602,7 +5674,9 @@ function parseInstallCommands(command) {
5602
5674
  if (!cleaned) continue;
5603
5675
  let name = cleaned;
5604
5676
  let version = null;
5605
- const pipMatch = /^([A-Za-z0-9_.-]+(?:\[[^\]]+\])?)\s*(==|>=|<=|~=|!=|>|<)\s*(.+)$/.exec(cleaned);
5677
+ const pipMatch = /^([A-Za-z0-9_.-]+(?:\[[^\]]+\])?)\s*(==|>=|<=|~=|!=|>|<)\s*(.+)$/.exec(
5678
+ cleaned
5679
+ );
5606
5680
  if (pipMatch?.[1]) {
5607
5681
  name = pipMatch[1];
5608
5682
  const op = pipMatch[2] ?? "";
@@ -6520,8 +6594,12 @@ var plugin19 = {
6520
6594
  return;
6521
6595
  }
6522
6596
  const oldStr = String(inp["old_string"] ?? inp["TargetContent"] ?? inp["oldContent"] ?? "");
6523
- const newStr = String(inp["new_string"] ?? inp["ReplacementContent"] ?? inp["newContent"] ?? "");
6524
- const contentStr = String(inp["content"] ?? inp["CodeContent"] ?? inp["code"] ?? inp["text"] ?? inp["contents"] ?? inp["body"] ?? "");
6597
+ const newStr = String(
6598
+ inp["new_string"] ?? inp["ReplacementContent"] ?? inp["newContent"] ?? ""
6599
+ );
6600
+ const contentStr = String(
6601
+ inp["content"] ?? inp["CodeContent"] ?? inp["code"] ?? inp["text"] ?? inp["contents"] ?? inp["body"] ?? ""
6602
+ );
6525
6603
  const toolInputForHash = oldStr || newStr ? `${oldStr}:::${newStr}` : contentStr;
6526
6604
  const now = Date.now();
6527
6605
  const memo = pathMemo.get(filePath);
@@ -6576,7 +6654,9 @@ var plugin19 = {
6576
6654
  additionalContext: header + summary
6577
6655
  };
6578
6656
  };
6579
- state18.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
6657
+ state18.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, {
6658
+ background: true
6659
+ });
6580
6660
  api.tools.register({
6581
6661
  name: "diff_summary_status",
6582
6662
  description: "Reports diff-summary state: mode, maxLines, and per-session invocation/injected/fallback counters.",
@@ -6804,7 +6884,9 @@ var plugin20 = {
6804
6884
  }
6805
6885
  }
6806
6886
  if (!content || state19.changedFiles.length === 0) return;
6807
- const missing = state19.changedFiles.filter((changedPath) => !isReferenced(changedPath, content));
6887
+ const missing = state19.changedFiles.filter(
6888
+ (changedPath) => !isReferenced(changedPath, content)
6889
+ );
6808
6890
  if (missing.length === 0) return;
6809
6891
  state19.warningsIssued += 1;
6810
6892
  const missingList = missing.map((p) => ` - ${p}`).join("\n");
@@ -6815,7 +6897,9 @@ Consider mentioning these changes in the documentation.`;
6815
6897
  return { additionalContext: message };
6816
6898
  }
6817
6899
  };
6818
- state19.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
6900
+ state19.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, {
6901
+ background: true
6902
+ });
6819
6903
  api.tools.register({
6820
6904
  name: "doc_sync_status",
6821
6905
  description: "Reports doc-sync-guard state: tracked changed files, doc-write count, and warning count.",
@@ -6880,7 +6964,7 @@ Consider mentioning these changes in the documentation.`;
6880
6964
  var doc_sync_guard_default = plugin20;
6881
6965
 
6882
6966
  // src/duplicate-code-detector/index.ts
6883
- import { readFile as readFile6, realpath, stat as stat3 } from "node:fs/promises";
6967
+ import { readFile as readFile6, realpath as realpath2, stat as stat3 } from "node:fs/promises";
6884
6968
  import { extname as extname4, isAbsolute as isAbsolute8, relative as relative9, resolve as resolve9, sep } from "node:path";
6885
6969
  var API_VERSION14 = "^0.1.10";
6886
6970
  var HOOK_WARNING_COOLDOWN_MS = 6e4;
@@ -7239,7 +7323,7 @@ var plugin21 = {
7239
7323
  if (!isWithinRoot(projectRoot, resolvedFile)) return;
7240
7324
  let changedFile;
7241
7325
  try {
7242
- changedFile = await realpath(resolvedFile);
7326
+ changedFile = await realpath2(resolvedFile);
7243
7327
  } catch {
7244
7328
  state20.errorCount += 1;
7245
7329
  return;
@@ -7908,7 +7992,9 @@ var plugin23 = {
7908
7992
  Make sure flag behavior is intentional and consider updating flag inventory/docs.`
7909
7993
  };
7910
7994
  };
7911
- state22.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
7995
+ state22.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, {
7996
+ background: true
7997
+ });
7912
7998
  api.tools.register({
7913
7999
  name: "scan_feature_flags",
7914
8000
  description: "Scan source files for feature-flag-like expressions (isFeatureEnabled, featureFlags.*, useFeatureFlag, flags.*, plus custom patterns).",
@@ -8678,7 +8764,9 @@ var plugin25 = {
8678
8764
  state23.cleanCount += 1;
8679
8765
  return;
8680
8766
  };
8681
- state23.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
8767
+ state23.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, {
8768
+ background: true
8769
+ });
8682
8770
  state23.patternUnregister = api.onPattern(
8683
8771
  "import-organizer:done",
8684
8772
  (_eventName, payload) => {
@@ -9475,7 +9563,7 @@ function classifyArtifact(relPath, patterns) {
9475
9563
  }
9476
9564
  return null;
9477
9565
  }
9478
- async function pathExists(p) {
9566
+ async function pathExists2(p) {
9479
9567
  try {
9480
9568
  await access3(p);
9481
9569
  return true;
@@ -9484,7 +9572,7 @@ async function pathExists(p) {
9484
9572
  }
9485
9573
  }
9486
9574
  async function readGitignoreLines(file) {
9487
- if (!await pathExists(file)) return [];
9575
+ if (!await pathExists2(file)) return [];
9488
9576
  try {
9489
9577
  const content = await readFile9(file, "utf8");
9490
9578
  return content.split(/\r?\n/);
@@ -9516,7 +9604,7 @@ function relDirOf(rel) {
9516
9604
  }
9517
9605
  async function firstExistingGitignore(candidates) {
9518
9606
  for (const c of candidates) {
9519
- if (await pathExists(c)) return c;
9607
+ if (await pathExists2(c)) return c;
9520
9608
  }
9521
9609
  return null;
9522
9610
  }
@@ -9527,7 +9615,7 @@ async function appendPatterns(target, patterns, limit) {
9527
9615
  );
9528
9616
  const missing = patterns.filter((p) => !seen.has(p)).slice(0, limit);
9529
9617
  if (missing.length === 0) return { appended: [] };
9530
- const current = await pathExists(target) ? await readFile9(target, "utf8") : "";
9618
+ const current = await pathExists2(target) ? await readFile9(target, "utf8") : "";
9531
9619
  const base = current.length === 0 ? "" : current.endsWith("\n") ? current : `${current}
9532
9620
  `;
9533
9621
  const next = `${base}${missing.map((p) => `${p}
@@ -10187,7 +10275,9 @@ ${result.stderr.trim()}`
10187
10275
  }
10188
10276
  return;
10189
10277
  };
10190
- state25.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
10278
+ state25.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, {
10279
+ background: true
10280
+ });
10191
10281
  api.tools.register({
10192
10282
  name: "import_organizer_status",
10193
10283
  description: "Reports import-organizer state: linter availability, config, and per-session organized/clean/error counters.",
@@ -10678,7 +10768,9 @@ var plugin30 = {
10678
10768
  If you changed member shapes, search the project for implementers/\`satisfies\`/\`as\` usages and update them accordingly.`
10679
10769
  };
10680
10770
  };
10681
- state27.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
10771
+ state27.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, {
10772
+ background: true
10773
+ });
10682
10774
  api.tools.register({
10683
10775
  name: "check_interface_contracts",
10684
10776
  description: "Scan TypeScript files for interface declarations that have no visible implementer (implements / as / satisfies).",
@@ -11030,9 +11122,12 @@ var plugin31 = {
11030
11122
  const hasMatch = f.subject.toLowerCase().includes(q) || f.relation.toLowerCase().includes(q) || f.object.toLowerCase().includes(q);
11031
11123
  if (!hasMatch) return false;
11032
11124
  }
11033
- if (input.subject && !f.subject.toLowerCase().includes(input.subject.toLowerCase())) return false;
11034
- if (input.relation && !f.relation.toLowerCase().includes(input.relation.toLowerCase())) return false;
11035
- if (input.object && !f.object.toLowerCase().includes(input.object.toLowerCase())) return false;
11125
+ if (input.subject && !f.subject.toLowerCase().includes(input.subject.toLowerCase()))
11126
+ return false;
11127
+ if (input.relation && !f.relation.toLowerCase().includes(input.relation.toLowerCase()))
11128
+ return false;
11129
+ if (input.object && !f.object.toLowerCase().includes(input.object.toLowerCase()))
11130
+ return false;
11036
11131
  if (filterConf && f.confidence.toLowerCase() !== filterConf) return false;
11037
11132
  return true;
11038
11133
  });
@@ -11064,7 +11159,9 @@ var plugin31 = {
11064
11159
  const raw = input ?? {};
11065
11160
  const rawId = String(input.id ?? raw["factId"] ?? raw["fact_id"] ?? "").trim();
11066
11161
  const normalized = rawId.toLowerCase().startsWith("kg-") ? rawId.toLowerCase() : `kg-${rawId.toLowerCase()}`;
11067
- state28.facts = state28.facts.filter((f) => f.id.toLowerCase() !== normalized && f.id !== rawId);
11162
+ state28.facts = state28.facts.filter(
11163
+ (f) => f.id.toLowerCase() !== normalized && f.id !== rawId
11164
+ );
11068
11165
  const removed = before - state28.facts.length;
11069
11166
  if (removed === 0) return { ok: false, error: `no fact matches "${input.id ?? rawId}"` };
11070
11167
  state28.removals += removed;
@@ -11160,7 +11257,15 @@ var state29 = {
11160
11257
  };
11161
11258
  var DEFAULTS27 = {
11162
11259
  enabled: true,
11163
- allowedLicenses: ["MIT", "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause", "ISC", "0BSD", "Unlicense"],
11260
+ allowedLicenses: [
11261
+ "MIT",
11262
+ "Apache-2.0",
11263
+ "BSD-2-Clause",
11264
+ "BSD-3-Clause",
11265
+ "ISC",
11266
+ "0BSD",
11267
+ "Unlicense"
11268
+ ],
11164
11269
  block: true
11165
11270
  };
11166
11271
  function normalizeStrings(v) {
@@ -11199,7 +11304,9 @@ function extractLicenseStrings(pkg) {
11199
11304
  }
11200
11305
  function parsePackageNames(command) {
11201
11306
  return [
11202
- ...new Set(parseInstallCommands(command).flatMap((entry) => entry.packages.map((pkg) => pkg.name)))
11307
+ ...new Set(
11308
+ parseInstallCommands(command).flatMap((entry) => entry.packages.map((pkg) => pkg.name))
11309
+ )
11203
11310
  ];
11204
11311
  }
11205
11312
  function splitTopLevel(expr, separator) {
@@ -12707,7 +12814,7 @@ import {
12707
12814
  runOptionalPluginCouncil,
12708
12815
  runOptionalPluginLlm,
12709
12816
  stripOuterMarkdownFence
12710
- } from "@wrongstack/plugin-sdk/runtime";
12817
+ } from "@wrongstack/plugin-sdk/runtime/llm";
12711
12818
 
12712
12819
  // src/migration-planner/index.ts
12713
12820
  var API_VERSION25 = "^0.1.10";
@@ -13959,11 +14066,15 @@ var plugin38 = {
13959
14066
  };
13960
14067
  var notify_hub_default = plugin38;
13961
14068
 
14069
+ // src/path-guard/index.ts
14070
+ import { existsSync as existsSync6, realpathSync } from "node:fs";
14071
+ import { dirname as dirname6, resolve as resolve17 } from "node:path";
14072
+
13962
14073
  // src/runtime/redos-guard.ts
13963
14074
  import {
13964
14075
  withReDoSGuard,
13965
14076
  guardedMatcher
13966
- } from "@wrongstack/plugin-sdk/runtime";
14077
+ } from "@wrongstack/plugin-sdk/runtime/redos-guard";
13967
14078
 
13968
14079
  // src/path-guard/glob.ts
13969
14080
  var GLOB_REDOS_BUDGET_MS = 250;
@@ -15249,17 +15360,32 @@ function operationLabel(toolName) {
15249
15360
  }
15250
15361
 
15251
15362
  // src/path-guard/index.ts
15252
- import { realpathSync } from "node:fs";
15253
- import { resolve as resolve17 } from "node:path";
15254
15363
  function isSymlinkEscape(path, cwd) {
15255
15364
  if (!(0, runtime_exports.withinProject)(path)) return false;
15365
+ const abs = resolve17(cwd ?? process.cwd(), path);
15366
+ let real;
15256
15367
  try {
15257
- const abs = resolve17(cwd ?? process.cwd(), path);
15258
- const real = realpathSync(abs);
15259
- return !(0, runtime_exports.withinProject)(real);
15368
+ real = realpathSync(abs);
15260
15369
  } catch {
15261
- return false;
15370
+ const ancestor = nearestExistingAncestor(abs);
15371
+ if (ancestor === null) return false;
15372
+ try {
15373
+ real = realpathSync(ancestor);
15374
+ } catch {
15375
+ return false;
15376
+ }
15262
15377
  }
15378
+ return !(0, runtime_exports.withinProject)(real);
15379
+ }
15380
+ function nearestExistingAncestor(abs) {
15381
+ let current = dirname6(abs);
15382
+ for (let hops = 0; hops < 64; hops++) {
15383
+ if (existsSync6(current)) return current;
15384
+ const parent = dirname6(current);
15385
+ if (parent === current) return null;
15386
+ current = parent;
15387
+ }
15388
+ return null;
15263
15389
  }
15264
15390
  function createState() {
15265
15391
  return {
@@ -15408,10 +15534,19 @@ var plugin39 = {
15408
15534
  path: relativeToInvocationCwd(resolveTargetPath(path, effectiveCwd), input.cwd),
15409
15535
  kind: isRootPathScope(path) && deletesImplicitScope || isUnresolvedPathScope(path) || recursivelyDeletes && (isDirectoryAmbiguousPath(path) || hasConfiguredProtectedDescendant(path, cfg.protect)) ? "deletion-scope" : "file"
15410
15536
  };
15537
+ if (target.kind === "file" && isSymlinkEscape(target.path, input.cwd)) {
15538
+ return target;
15539
+ }
15411
15540
  if (targetFullyAllowed(target, cfg.allow, allowRes)) return null;
15412
- const protectedShellTarget = await targetIntersectsPatternsGuarded(target, cfg.protect, protectRes, { onTimeout: bumpRedos }) || await matchesAnyGuarded(`${target.path.replace(/\/$/, "")}/.path-guard-probe`, protectRes, {
15541
+ const protectedShellTarget = await targetIntersectsPatternsGuarded(target, cfg.protect, protectRes, {
15413
15542
  onTimeout: bumpRedos
15414
- });
15543
+ }) || await matchesAnyGuarded(
15544
+ `${target.path.replace(/\/$/, "")}/.path-guard-probe`,
15545
+ protectRes,
15546
+ {
15547
+ onTimeout: bumpRedos
15548
+ }
15549
+ );
15415
15550
  return protectedShellTarget ? target : null;
15416
15551
  })
15417
15552
  );
@@ -15529,7 +15664,7 @@ var plugin39 = {
15529
15664
  var path_guard_default = plugin39;
15530
15665
 
15531
15666
  // src/performance-regression-gate/index.ts
15532
- import { existsSync as existsSync6, readFileSync as readFileSync11 } from "node:fs";
15667
+ import { existsSync as existsSync7, readFileSync as readFileSync11 } from "node:fs";
15533
15668
  import { isAbsolute as isAbsolute16, relative as relative16, resolve as resolve18 } from "node:path";
15534
15669
  var API_VERSION26 = "^0.1.10";
15535
15670
  var state36 = {
@@ -15595,7 +15730,7 @@ function flattenResults(results) {
15595
15730
  return flat;
15596
15731
  }
15597
15732
  function loadResults(path) {
15598
- if (!path || !existsSync6(path)) return null;
15733
+ if (!path || !existsSync7(path)) return null;
15599
15734
  try {
15600
15735
  const raw = JSON.parse(readFileSync11(path, "utf-8"));
15601
15736
  return raw;
@@ -15993,7 +16128,7 @@ var plugin_stack_observer_default = PLUGIN;
15993
16128
  // src/pr-drafter/index.ts
15994
16129
  import { execFile as execFile9 } from "node:child_process";
15995
16130
  import { mkdir as mkdir2, writeFile as writeFile4 } from "node:fs/promises";
15996
- import { dirname as dirname6, isAbsolute as isAbsolute17, relative as relative17, resolve as resolve19 } from "node:path";
16131
+ import { dirname as dirname7, isAbsolute as isAbsolute17, relative as relative17, resolve as resolve19 } from "node:path";
15997
16132
  var API_VERSION27 = "^0.1.10";
15998
16133
  var state38 = {
15999
16134
  commits: [],
@@ -16125,7 +16260,7 @@ async function writeDraft(cfg, llm) {
16125
16260
  }
16126
16261
  const draft = await buildDraft(cfg, llm);
16127
16262
  try {
16128
- await mkdir2(dirname6(resolved), { recursive: true });
16263
+ await mkdir2(dirname7(resolved), { recursive: true });
16129
16264
  await writeFile4(resolved, draft.body);
16130
16265
  state38.draftsWritten += 1;
16131
16266
  } catch {
@@ -16257,7 +16392,9 @@ var plugin41 = {
16257
16392
  async execute(input = {}) {
16258
16393
  if (!cfg.enabled) return { ok: false, error: "pr-drafter is disabled" };
16259
16394
  const raw = input ?? {};
16260
- const preview = Boolean(input?.preview ?? raw["dryRun"] ?? raw["dry_run"] ?? raw["dry"] ?? raw["previewOnly"]);
16395
+ const preview = Boolean(
16396
+ input?.preview ?? raw["dryRun"] ?? raw["dry_run"] ?? raw["dry"] ?? raw["previewOnly"]
16397
+ );
16261
16398
  const rawOutputPath = raw["outputPath"] ?? raw["output_path"] ?? raw["path"] ?? raw["filePath"] ?? raw["file"] ?? raw["TargetFile"] ?? raw["targetFile"] ?? cfg.outputPath;
16262
16399
  const outputPathStr = typeof rawOutputPath === "string" && rawOutputPath.trim().length > 0 ? rawOutputPath.trim() : cfg.outputPath;
16263
16400
  const draft = await buildDraft(cfg, api.llm);
@@ -16267,7 +16404,7 @@ var plugin41 = {
16267
16404
  const resolved = resolveProjectPath7(outputPathStr);
16268
16405
  if (!resolved) return { ok: false, error: "outputPath resolves outside project" };
16269
16406
  try {
16270
- await mkdir2(dirname6(resolved), { recursive: true });
16407
+ await mkdir2(dirname7(resolved), { recursive: true });
16271
16408
  await writeFile4(resolved, draft.body);
16272
16409
  state38.draftsWritten += 1;
16273
16410
  return {
@@ -16406,7 +16543,9 @@ var plugin42 = {
16406
16543
  const rawCmd = ti["command"] ?? ti["CommandLine"] ?? ti["cmd"] ?? ti["script"] ?? ti["input"];
16407
16544
  const command = typeof rawCmd === "string" ? rawCmd : "";
16408
16545
  if (!command) return;
16409
- const isKillRelated = /\b(?:kill|taskkill|stop-process|tskill|pkill|killall|wmic)\b/i.test(command);
16546
+ const isKillRelated = /\b(?:kill|taskkill|stop-process|tskill|pkill|killall|wmic)\b/i.test(
16547
+ command
16548
+ );
16410
16549
  if (!isKillRelated) return;
16411
16550
  state39.detections += 1;
16412
16551
  state39.lastDetection = {
@@ -16495,7 +16634,7 @@ import { performance as performance2 } from "node:perf_hooks";
16495
16634
  import {
16496
16635
  cloneCredentialPatterns,
16497
16636
  CREDENTIAL_PATTERNS
16498
- } from "@wrongstack/plugin-sdk/runtime";
16637
+ } from "@wrongstack/plugin-sdk/runtime/credential-patterns";
16499
16638
 
16500
16639
  // src/prompt-firewall/index.ts
16501
16640
  var KIND_ALIASES = {
@@ -16772,9 +16911,12 @@ function surfaceScanTrips(api, tripped) {
16772
16911
  }
16773
16912
  state40.timeoutCount += 1;
16774
16913
  api.metrics.counter("redos_skips", 1);
16775
- api.log.warn("prompt-firewall: scan-pass budget exceeded \u2014 patterns skipped mid-pass (issue #370)", {
16776
- skipped: [...tripped]
16777
- });
16914
+ api.log.warn(
16915
+ "prompt-firewall: scan-pass budget exceeded \u2014 patterns skipped mid-pass (issue #370)",
16916
+ {
16917
+ skipped: [...tripped]
16918
+ }
16919
+ );
16778
16920
  }
16779
16921
  var plugin43 = {
16780
16922
  name: "prompt-firewall",
@@ -16876,7 +17018,14 @@ var plugin43 = {
16876
17018
  if (cfg.mode === "redact") {
16877
17019
  const counter = { n: 0 };
16878
17020
  const deadline = createScanDeadline();
16879
- const redactedReq = redactDeep(req, cfg.allow, counter, skipSet, void 0, deadline);
17021
+ const redactedReq = redactDeep(
17022
+ req,
17023
+ cfg.allow,
17024
+ counter,
17025
+ skipSet,
17026
+ void 0,
17027
+ deadline
17028
+ );
16880
17029
  state40.requestRedactions += counter.n;
16881
17030
  api.metrics.counter("request_redactions", counter.n);
16882
17031
  if (deadline.tripped.size > 0) surfaceScanTrips(api, deadline.tripped);
@@ -17273,7 +17422,9 @@ var plugin44 = {
17273
17422
  contextAs: "separate"
17274
17423
  };
17275
17424
  };
17276
- state41.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
17425
+ state41.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, {
17426
+ background: true
17427
+ });
17277
17428
  api.tools.register({
17278
17429
  name: "suggest_refactors",
17279
17430
  description: "Scan source files for refactoring smells: long functions, deep nesting, many parameters, magic numbers, and console logging.",
@@ -18600,7 +18751,19 @@ var DEFAULTS42 = {
18600
18751
  enabled: false,
18601
18752
  severity: "warn",
18602
18753
  maxFindings: 10,
18603
- scanOnChange: [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".py", ".java", ".go", ".rb", ".php"]
18754
+ scanOnChange: [
18755
+ ".js",
18756
+ ".jsx",
18757
+ ".ts",
18758
+ ".tsx",
18759
+ ".mjs",
18760
+ ".cjs",
18761
+ ".py",
18762
+ ".java",
18763
+ ".go",
18764
+ ".rb",
18765
+ ".php"
18766
+ ]
18604
18767
  };
18605
18768
  var scanOnChangeSet = new Set(DEFAULTS42.scanOnChange);
18606
18769
  function readConfig44(raw) {
@@ -18623,7 +18786,11 @@ function readConfig44(raw) {
18623
18786
  }
18624
18787
  var PATTERNS4 = [
18625
18788
  { type: "eval_call", severity: "high", regex: /\beval\s*\(/i },
18626
- { type: "function_constructor", severity: "high", regex: /\bnew\s+Function\s*\(|\bFunction\s*\(/i },
18789
+ {
18790
+ type: "function_constructor",
18791
+ severity: "high",
18792
+ regex: /\bnew\s+Function\s*\(|\bFunction\s*\(/i
18793
+ },
18627
18794
  {
18628
18795
  type: "unsafe_html",
18629
18796
  severity: "high",
@@ -18861,7 +19028,9 @@ Review or remove the risky pattern(s).`;
18861
19028
  }
18862
19029
  return { additionalContext: message };
18863
19030
  };
18864
- state44.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
19031
+ state44.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, {
19032
+ background: true
19033
+ });
18865
19034
  api.tools.register({
18866
19035
  name: "security_hotspot_scan",
18867
19036
  description: "Scan a file or directory for security anti-patterns (eval, Function constructor, innerHTML, SQL concatenation, http URLs, credential logging, variable exec commands).",
@@ -19036,11 +19205,7 @@ var DEFAULTS43 = {
19036
19205
  ".scss",
19037
19206
  ".html"
19038
19207
  ],
19039
- excludePatterns: [
19040
- ...DEFAULT_WALK_IGNORE_DIRS.map(escapeRegex),
19041
- "\\.wrongstack",
19042
- "\\.temp_files"
19043
- ],
19208
+ excludePatterns: [...DEFAULT_WALK_IGNORE_DIRS.map(escapeRegex), "\\.wrongstack", "\\.temp_files"],
19044
19209
  maxFileBytes: 1e6,
19045
19210
  defaultLimit: 10,
19046
19211
  minTokenLength: 2,
@@ -19048,7 +19213,12 @@ var DEFAULTS43 = {
19048
19213
  maxFiles: 5e3
19049
19214
  };
19050
19215
  function readConfig45(raw) {
19051
- if (!raw || typeof raw !== "object") return { ...DEFAULTS43, includeExtensions: [...DEFAULTS43.includeExtensions], excludePatterns: [...DEFAULTS43.excludePatterns] };
19216
+ if (!raw || typeof raw !== "object")
19217
+ return {
19218
+ ...DEFAULTS43,
19219
+ includeExtensions: [...DEFAULTS43.includeExtensions],
19220
+ excludePatterns: [...DEFAULTS43.excludePatterns]
19221
+ };
19052
19222
  const r = raw;
19053
19223
  const rawExts = r["includeExtensions"] ?? r["include_extensions"] ?? r["extensions"] ?? r["file_extensions"];
19054
19224
  const includeExtensions = Array.isArray(rawExts) ? rawExts.filter((x) => typeof x === "string") : [...DEFAULTS43.includeExtensions];
@@ -19359,7 +19529,11 @@ var plugin49 = {
19359
19529
  description: "Builds an in-memory keyword index over project source files and answers ranked search queries",
19360
19530
  apiVersion: API_VERSION32,
19361
19531
  capabilities: { tools: true },
19362
- defaultConfig: { ...DEFAULTS43, includeExtensions: [...DEFAULTS43.includeExtensions], excludePatterns: [...DEFAULTS43.excludePatterns] },
19532
+ defaultConfig: {
19533
+ ...DEFAULTS43,
19534
+ includeExtensions: [...DEFAULTS43.includeExtensions],
19535
+ excludePatterns: [...DEFAULTS43.excludePatterns]
19536
+ },
19363
19537
  configSchema: {
19364
19538
  type: "object",
19365
19539
  properties: {
@@ -19644,19 +19818,24 @@ var state46 = {
19644
19818
  };
19645
19819
  function runCommand3(command, args, cwd) {
19646
19820
  return new Promise((resolve28, reject) => {
19647
- execFile11(command, args, {
19648
- encoding: "utf-8",
19649
- cwd,
19650
- timeout: 3e4,
19651
- windowsHide: true
19652
- }, (err, stdout, stderr) => {
19653
- if (!err) {
19654
- resolve28(stdout.trim());
19655
- return;
19821
+ execFile11(
19822
+ command,
19823
+ args,
19824
+ {
19825
+ encoding: "utf-8",
19826
+ cwd,
19827
+ timeout: 3e4,
19828
+ windowsHide: true
19829
+ },
19830
+ (err, stdout, stderr) => {
19831
+ if (!err) {
19832
+ resolve28(stdout.trim());
19833
+ return;
19834
+ }
19835
+ const failure = err;
19836
+ reject(Object.assign(failure, { stderr: stderr || void 0 }));
19656
19837
  }
19657
- const failure = err;
19658
- reject(Object.assign(failure, { stderr: stderr || void 0 }));
19659
- });
19838
+ );
19660
19839
  });
19661
19840
  }
19662
19841
  async function runGit6(args, cwd) {
@@ -20704,15 +20883,10 @@ var state48 = {
20704
20883
  async function runShellCheck(files, severity, cwd) {
20705
20884
  try {
20706
20885
  await new Promise((resolvePromise, rejectPromise) => {
20707
- execFile12(
20708
- "shellcheck",
20709
- ["--version"],
20710
- { encoding: "utf-8", windowsHide: true },
20711
- (err) => {
20712
- if (err) rejectPromise(err);
20713
- else resolvePromise();
20714
- }
20715
- );
20886
+ execFile12("shellcheck", ["--version"], { encoding: "utf-8", windowsHide: true }, (err) => {
20887
+ if (err) rejectPromise(err);
20888
+ else resolvePromise();
20889
+ });
20716
20890
  });
20717
20891
  } catch {
20718
20892
  throw new Error(
@@ -20873,7 +21047,9 @@ var plugin52 = {
20873
21047
  if (typeof rawFiles === "string" && rawFiles.trim().length > 0) {
20874
21048
  files = [rawFiles.trim()];
20875
21049
  } else if (Array.isArray(rawFiles)) {
20876
- files = rawFiles.filter((f) => typeof f === "string" && f.trim().length > 0);
21050
+ files = rawFiles.filter(
21051
+ (f) => typeof f === "string" && f.trim().length > 0
21052
+ );
20877
21053
  }
20878
21054
  }
20879
21055
  const rawDirectory = inp.directory ?? input["dir"] ?? input["SearchDirectory"];
@@ -21018,7 +21194,8 @@ function normalizeExtensions6(exts) {
21018
21194
  return exts.map((e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`);
21019
21195
  }
21020
21196
  function readConfig47(raw) {
21021
- if (!raw || typeof raw !== "object") return { ...DEFAULTS45, extensions: normalizeExtensions6(DEFAULTS45.extensions) };
21197
+ if (!raw || typeof raw !== "object")
21198
+ return { ...DEFAULTS45, extensions: normalizeExtensions6(DEFAULTS45.extensions) };
21022
21199
  const r = raw;
21023
21200
  const rawExts = r["extensions"] ?? r["file_extensions"] ?? r["fileExtensions"];
21024
21201
  const exts = Array.isArray(rawExts) ? rawExts.filter((x) => typeof x === "string") : DEFAULTS45.extensions;
@@ -21051,10 +21228,7 @@ function isIdentifier(name) {
21051
21228
  return IDENTIFIER_RE2.test(name);
21052
21229
  }
21053
21230
  function renameInContent(content, oldName, newName) {
21054
- const re = new RegExp(
21055
- `(?<![A-Za-z0-9_$])${escapeRegex2(oldName)}(?![A-Za-z0-9_$])`,
21056
- "g"
21057
- );
21231
+ const re = new RegExp(`(?<![A-Za-z0-9_$])${escapeRegex2(oldName)}(?![A-Za-z0-9_$])`, "g");
21058
21232
  let replacements = 0;
21059
21233
  const preview = content.replace(re, () => {
21060
21234
  replacements++;
@@ -21095,7 +21269,11 @@ var plugin53 = {
21095
21269
  path: { type: "string", description: "Source file path (relative to project root)." },
21096
21270
  oldName: { type: "string", description: "Identifier to replace." },
21097
21271
  newName: { type: "string", description: "New identifier." },
21098
- apply: { type: "boolean", default: false, description: "When true, write the preview back to disk." }
21272
+ apply: {
21273
+ type: "boolean",
21274
+ default: false,
21275
+ description: "When true, write the preview back to disk."
21276
+ }
21099
21277
  },
21100
21278
  required: ["path", "oldName", "newName"]
21101
21279
  },
@@ -21612,7 +21790,9 @@ var plugin54 = {
21612
21790
  ${lines}${overflowNote}`
21613
21791
  };
21614
21792
  };
21615
- state50.postHookUnregister = api.registerHook("PostToolUse", "write|edit", postHook, { background: true });
21793
+ state50.postHookUnregister = api.registerHook("PostToolUse", "write|edit", postHook, {
21794
+ background: true
21795
+ });
21616
21796
  if (cfg.autoFix) {
21617
21797
  const preHook = async (input) => {
21618
21798
  if (!cfg.enabled) return;
@@ -21756,21 +21936,27 @@ function expandTemplate(template, variables) {
21756
21936
  return result;
21757
21937
  }
21758
21938
  function expandConditionals(template, variables) {
21759
- return template.replace(/\{\{#if\s+([\w.-]+)\s*\}\}([\s\S]*?)\{\{\/if\}\}/g, (_, key, content) => {
21760
- const val = variables[key];
21761
- return val !== void 0 && val !== "" && val !== "false" && val !== "0" ? content : "";
21762
- });
21939
+ return template.replace(
21940
+ /\{\{#if\s+([\w.-]+)\s*\}\}([\s\S]*?)\{\{\/if\}\}/g,
21941
+ (_, key, content) => {
21942
+ const val = variables[key];
21943
+ return val !== void 0 && val !== "" && val !== "false" && val !== "0" ? content : "";
21944
+ }
21945
+ );
21763
21946
  }
21764
21947
  function expandLoops(template, variables) {
21765
- return template.replace(/\{\{#each\s+([\w.-]+)\s*\}\}([\s\S]*?)\{\{\/each\}\}/g, (_, key, content) => {
21766
- const val = variables[key];
21767
- if (!val) return "";
21768
- if (typeof val === "string" && val.includes(",")) {
21769
- const items = val.split(",").map((s) => s.trim());
21770
- return items.map((item) => expandTemplate(content, { ...variables, [key]: item, item })).join("\n");
21771
- }
21772
- return expandTemplate(content, variables);
21773
- });
21948
+ return template.replace(
21949
+ /\{\{#each\s+([\w.-]+)\s*\}\}([\s\S]*?)\{\{\/each\}\}/g,
21950
+ (_, key, content) => {
21951
+ const val = variables[key];
21952
+ if (!val) return "";
21953
+ if (typeof val === "string" && val.includes(",")) {
21954
+ const items = val.split(",").map((s) => s.trim());
21955
+ return items.map((item) => expandTemplate(content, { ...variables, [key]: item, item })).join("\n");
21956
+ }
21957
+ return expandTemplate(content, variables);
21958
+ }
21959
+ );
21774
21960
  }
21775
21961
  function renderTemplate(template, variables, escapeHtml = true) {
21776
21962
  let result = template;
@@ -22401,7 +22587,7 @@ var test_coverage_gate_default = plugin56;
22401
22587
  import { execFile as execFile13 } from "node:child_process";
22402
22588
  import { readFileSync as readFileSync15 } from "node:fs";
22403
22589
  import { createRequire as createRequire2 } from "node:module";
22404
- import { dirname as dirname7, isAbsolute as isAbsolute25, relative as relative24, resolve as resolve26 } from "node:path";
22590
+ import { dirname as dirname8, isAbsolute as isAbsolute25, relative as relative24, resolve as resolve26 } from "node:path";
22405
22591
  var API_VERSION37 = "^0.1.10";
22406
22592
  var state52 = {
22407
22593
  invocationCount: 0,
@@ -22532,7 +22718,7 @@ function resolveTestCommand(baseCommand, testPattern) {
22532
22718
  const packageJson = JSON.parse(readFileSync15(packagePath, "utf8"));
22533
22719
  const relativeBin = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.[runner] ?? Object.values(packageJson.bin ?? {})[0];
22534
22720
  if (!relativeBin) return null;
22535
- const packageDir = dirname7(packagePath);
22721
+ const packageDir = dirname8(packagePath);
22536
22722
  const candidate = resolve26(packageDir, relativeBin);
22537
22723
  if (isAbsolute25(relativeBin) || !isInside2(packageDir, candidate)) {
22538
22724
  return null;
@@ -22661,10 +22847,7 @@ var plugin57 = {
22661
22847
  const commandString = typeof rawCommand === "string" && rawCommand.trim().length > 0 ? rawCommand.trim() : cfg.defaultCommand;
22662
22848
  const rawRuns = input.runs ?? raw["runsRequested"] ?? raw["count"] ?? raw["repeat"] ?? raw["times"];
22663
22849
  const requestedRuns = typeof rawRuns === "number" && rawRuns >= 1 ? Math.min(Math.floor(rawRuns), cfg.maxRuns) : 5;
22664
- const command = resolveTestCommand(
22665
- commandString,
22666
- testPattern
22667
- );
22850
+ const command = resolveTestCommand(commandString, testPattern);
22668
22851
  if (!command) {
22669
22852
  return {
22670
22853
  ok: false,
@@ -22953,7 +23136,9 @@ function generateTestContent(sourcePath, sourceModule, detected, cfg) {
22953
23136
  }
22954
23137
  if (detected.length === 0) {
22955
23138
  lines.push(` it('has no exported symbols to test', () => {`);
22956
- lines.push(cfg.framework === "node:test" ? ` assert.ok(true);` : ` expect(true).toBe(true);`);
23139
+ lines.push(
23140
+ cfg.framework === "node:test" ? ` assert.ok(true);` : ` expect(true).toBe(true);`
23141
+ );
22957
23142
  lines.push(` });`);
22958
23143
  }
22959
23144
  lines.push(`});`);
@@ -23180,7 +23365,7 @@ var test_generator_default = plugin58;
23180
23365
  // src/test-runner-gate/index.ts
23181
23366
  import { execFile as execFile14 } from "node:child_process";
23182
23367
  import { access as access5 } from "node:fs/promises";
23183
- import { basename as basename6, dirname as dirname8, isAbsolute as isAbsolute27, join as join7 } from "node:path";
23368
+ import { basename as basename6, dirname as dirname9, isAbsolute as isAbsolute27, join as join7 } from "node:path";
23184
23369
  import { buildWin32CmdShimInvocation, resolveWin32Command } from "@wrongstack/tools/win32";
23185
23370
  function resolveExec(command, args) {
23186
23371
  const resolved = resolveWin32Command(command);
@@ -23273,7 +23458,7 @@ var ALLOWED_COMMAND_TOKENS = /* @__PURE__ */ new Set([
23273
23458
  function resolveTestFiles(sourcePath, patterns) {
23274
23459
  const name = basename6(sourcePath).replace(/\.[^.]+$/, "");
23275
23460
  const pathNoExt = sourcePath.replace(/\.[^.]+$/, "");
23276
- const dir = dirname8(sourcePath);
23461
+ const dir = dirname9(sourcePath);
23277
23462
  const candidates = [];
23278
23463
  for (const pattern of patterns) {
23279
23464
  const candidate = pattern.replace(/\{name\}/g, name).replace(/\{path\}/g, pathNoExt).replace(/\{dir\}/g, dir);
@@ -23311,13 +23496,18 @@ async function detectRunner(requested) {
23311
23496
  try {
23312
23497
  await new Promise((resolve28, reject) => {
23313
23498
  const ex = resolveExec("npx", [`${match.name}`, "--version"]);
23314
- execFile14(ex.cmd, ex.args, {
23315
- encoding: "utf-8",
23316
- timeout: 5e3,
23317
- cwd: process.cwd(),
23318
- windowsHide: true,
23319
- ...ex.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}
23320
- }, (err) => err ? reject(err) : resolve28());
23499
+ execFile14(
23500
+ ex.cmd,
23501
+ ex.args,
23502
+ {
23503
+ encoding: "utf-8",
23504
+ timeout: 5e3,
23505
+ cwd: process.cwd(),
23506
+ windowsHide: true,
23507
+ ...ex.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}
23508
+ },
23509
+ (err) => err ? reject(err) : resolve28()
23510
+ );
23321
23511
  });
23322
23512
  return match;
23323
23513
  } catch {
@@ -23328,13 +23518,18 @@ async function detectRunner(requested) {
23328
23518
  try {
23329
23519
  await new Promise((resolve28, reject) => {
23330
23520
  const ex = resolveExec("npx", [`${candidate.name}`, "--version"]);
23331
- execFile14(ex.cmd, ex.args, {
23332
- encoding: "utf-8",
23333
- timeout: 5e3,
23334
- cwd: process.cwd(),
23335
- windowsHide: true,
23336
- ...ex.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}
23337
- }, (err) => err ? reject(err) : resolve28());
23521
+ execFile14(
23522
+ ex.cmd,
23523
+ ex.args,
23524
+ {
23525
+ encoding: "utf-8",
23526
+ timeout: 5e3,
23527
+ cwd: process.cwd(),
23528
+ windowsHide: true,
23529
+ ...ex.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}
23530
+ },
23531
+ (err) => err ? reject(err) : resolve28()
23532
+ );
23338
23533
  });
23339
23534
  return candidate;
23340
23535
  } catch {
@@ -23602,7 +23797,9 @@ var plugin59 = {
23602
23797
  Fix the failing tests or revert the change if it broke something.`
23603
23798
  };
23604
23799
  };
23605
- state54.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
23800
+ state54.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, {
23801
+ background: true
23802
+ });
23606
23803
  api.tools.register({
23607
23804
  name: "test_gate_status",
23608
23805
  description: "Reports test-runner-gate state: command, patterns, and per-session pass/fail/error/no-test counters.",
@@ -23908,7 +24105,7 @@ var todo_listener_default = plugin60;
23908
24105
  // src/todo-tracker/index.ts
23909
24106
  import { randomUUID } from "node:crypto";
23910
24107
  import * as fsp from "node:fs/promises";
23911
- import { dirname as dirname9 } from "node:path";
24108
+ import { dirname as dirname10 } from "node:path";
23912
24109
  import { atomicWrite as atomicWrite3, ensureDir as ensureDir3 } from "@wrongstack/core/utils";
23913
24110
  import { nowIso } from "@wrongstack/primitives";
23914
24111
  function deriveFilePath(api) {
@@ -23941,7 +24138,7 @@ async function loadFile(filePath) {
23941
24138
  }
23942
24139
  }
23943
24140
  async function saveFile(filePath, file) {
23944
- await ensureDir3(dirname9(filePath));
24141
+ await ensureDir3(dirname10(filePath));
23945
24142
  await atomicWrite3(filePath, JSON.stringify(file, null, 2), { mode: 384 });
23946
24143
  }
23947
24144
  var state56 = {
@@ -24848,7 +25045,7 @@ var plugin63 = {
24848
25045
  var token_throttle_default = plugin63;
24849
25046
 
24850
25047
  // src/type-gate/index.ts
24851
- import { existsSync as existsSync7 } from "node:fs";
25048
+ import { existsSync as existsSync8 } from "node:fs";
24852
25049
  var API_VERSION41 = "^0.1.10";
24853
25050
  var state59 = {
24854
25051
  invocationCount: 0,
@@ -24932,7 +25129,7 @@ async function runTypeCheck(cfg) {
24932
25129
  argv = [...argv, "-p", tsConfig];
24933
25130
  }
24934
25131
  }
24935
- if (!cfg.command && tsConfig && !existsSync7(tsConfig)) {
25132
+ if (!cfg.command && tsConfig && !existsSync8(tsConfig)) {
24936
25133
  return null;
24937
25134
  }
24938
25135
  const result = await (0, runtime_exports.runRunnerCommand)([argv[0], ...argv.slice(1)], {
@@ -25070,7 +25267,9 @@ Fix the type error(s) or adjust the change.`;
25070
25267
  }
25071
25268
  return { additionalContext: message };
25072
25269
  };
25073
- state59.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
25270
+ state59.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, {
25271
+ background: true
25272
+ });
25074
25273
  api.tools.register({
25075
25274
  name: "type_gate_status",
25076
25275
  description: "Reports type-gate state: command, tsconfig, severity, and per-session pass/fail/error counters.",