@wrongstack/plugins 0.295.1 → 0.296.2

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 (44) hide show
  1. package/dist/accessibility-auditor.js +12 -1
  2. package/dist/branch-guard.js +13 -2
  3. package/dist/commit-validator/index.d.ts.map +1 -1
  4. package/dist/commit-validator.js +18 -1
  5. package/dist/dead-code-detector/index.d.ts.map +1 -1
  6. package/dist/dead-code-detector.js +4 -1
  7. package/dist/diff-summary.js +12 -1
  8. package/dist/doc-sync-guard.js +12 -1
  9. package/dist/git-autocommit/index.d.ts +12 -0
  10. package/dist/git-autocommit/index.d.ts.map +1 -1
  11. package/dist/git-autocommit.js +58 -4
  12. package/dist/import-organizer.js +12 -1
  13. package/dist/index.js +149 -47
  14. package/dist/injection-shield/index.d.ts +16 -0
  15. package/dist/injection-shield/index.d.ts.map +1 -1
  16. package/dist/injection-shield.js +12 -4
  17. package/dist/lint-gate.js +12 -1
  18. package/dist/migration-planner.js +12 -1
  19. package/dist/pr-drafter/index.d.ts.map +1 -1
  20. package/dist/pr-drafter.js +20 -1
  21. package/dist/runtime/handles.d.ts +46 -0
  22. package/dist/runtime/handles.d.ts.map +1 -0
  23. package/dist/runtime/index.d.ts +1 -0
  24. package/dist/runtime/index.d.ts.map +1 -1
  25. package/dist/runtime.js +18 -0
  26. package/dist/secret-scanner/index.d.ts.map +1 -1
  27. package/dist/secret-scanner.js +19 -2
  28. package/dist/session-recap/index.d.ts.map +1 -1
  29. package/dist/session-recap.js +18 -1
  30. package/dist/smart-rename/index.d.ts +2 -0
  31. package/dist/smart-rename/index.d.ts.map +1 -1
  32. package/dist/smart-rename.js +16 -2
  33. package/dist/spec-linker/index.d.ts.map +1 -1
  34. package/dist/spec-linker.js +19 -2
  35. package/dist/template-engine/index.d.ts.map +1 -1
  36. package/dist/template-engine.js +15 -1
  37. package/dist/test-coverage-gate.js +12 -1
  38. package/dist/test-runner-gate.js +12 -1
  39. package/dist/todo-listener.js +12 -1
  40. package/dist/token-budget/index.d.ts.map +1 -1
  41. package/dist/token-budget.js +31 -19
  42. package/dist/type-gate/index.d.ts.map +1 -1
  43. package/dist/type-gate.js +12 -1
  44. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -295,6 +295,17 @@ function safeJsonStringify(value, indent) {
295
295
  }
296
296
  }
297
297
 
298
+ // src/runtime/handles.ts
299
+ function releaseHandle(off) {
300
+ if (off) {
301
+ try {
302
+ off();
303
+ } catch {
304
+ }
305
+ }
306
+ return null;
307
+ }
308
+
298
309
  // src/runtime/index.ts
299
310
  var META_CHARS = /["'`;&|<>\r\n]/;
300
311
  var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
@@ -757,7 +768,7 @@ var plugin = {
757
768
  state.findingCount = 0;
758
769
  state.hookInvocationCount = 0;
759
770
  state.lastResult = null;
760
- state.hookUnregister = null;
771
+ state.hookUnregister = releaseHandle(state.hookUnregister);
761
772
  const cfg = readConfig(api.config.extensions?.["accessibility-auditor"]);
762
773
  const hook = async (input) => {
763
774
  if (!cfg.enabled || !cfg.onWriteEdit) return;
@@ -2584,8 +2595,8 @@ var plugin7 = {
2584
2595
  state7.invocationCount = 0;
2585
2596
  state7.blockCount = 0;
2586
2597
  state7.warnCount = 0;
2587
- state7.hookUnregister = null;
2588
- state7.configUnregister = null;
2598
+ state7.hookUnregister = releaseHandle(state7.hookUnregister);
2599
+ state7.configUnregister = releaseHandle(state7.configUnregister);
2589
2600
  state7.lastBlock = null;
2590
2601
  branchCache.clear();
2591
2602
  let cfg = readHostConfig(api.config);
@@ -3975,7 +3986,7 @@ var plugin11 = {
3975
3986
  state11.invalidCount = 0;
3976
3987
  state11.suggestFixCount = 0;
3977
3988
  state11.suggestFixErrors = 0;
3978
- state11.hookUnregister = null;
3989
+ state11.hookUnregister = releaseHandle(state11.hookUnregister);
3979
3990
  state11.lastValidation = null;
3980
3991
  const cfg = readConfig10(api.config.extensions?.["commit-validator"]);
3981
3992
  const hook = async (input) => {
@@ -5507,7 +5518,10 @@ function findUnusedSymbols(files) {
5507
5518
  for (const file of files) {
5508
5519
  const fileExports = extractExports2(file.content, file.path);
5509
5520
  for (const exp of fileExports) {
5510
- const pattern = new RegExp(`\\b${escapeRegex(exp.identifier)}\\b`, "g");
5521
+ const pattern = new RegExp(
5522
+ `(?<![A-Za-z0-9_$])${escapeRegex(exp.identifier)}(?![A-Za-z0-9_$])`,
5523
+ "g"
5524
+ );
5511
5525
  let usedElsewhere = false;
5512
5526
  for (const other of files) {
5513
5527
  if (other.path === file.path) continue;
@@ -6602,7 +6616,7 @@ var plugin19 = {
6602
6616
  state18.fallbackCount = 0;
6603
6617
  state18.throttledCount = 0;
6604
6618
  state18.duplicateContentCount = 0;
6605
- state18.hookUnregister = null;
6619
+ state18.hookUnregister = releaseHandle(state18.hookUnregister);
6606
6620
  state18.lastSummary = null;
6607
6621
  pathMemo.clear();
6608
6622
  const cfg = readConfig16(api.config.extensions?.["diff-summary"]);
@@ -6868,7 +6882,7 @@ var plugin20 = {
6868
6882
  state19.sourceWrites = 0;
6869
6883
  state19.docWrites = 0;
6870
6884
  state19.warningsIssued = 0;
6871
- state19.hookUnregister = null;
6885
+ state19.hookUnregister = releaseHandle(state19.hookUnregister);
6872
6886
  const cfg = readConfig17(api.config.extensions?.["doc-sync-guard"]);
6873
6887
  const hook = (input) => {
6874
6888
  if (!cfg.enabled) return;
@@ -8715,10 +8729,63 @@ async function runGit3(args, cwd, timeoutMs = DEFAULT_GIT_TIMEOUT_MS) {
8715
8729
  );
8716
8730
  });
8717
8731
  }
8732
+ function unquotePorcelainPath(raw) {
8733
+ if (!raw.startsWith('"') || !raw.endsWith('"') || raw.length < 2) return raw;
8734
+ const inner = raw.slice(1, -1);
8735
+ const bytes = [];
8736
+ for (let i = 0; i < inner.length; i++) {
8737
+ const ch = inner[i];
8738
+ if (ch !== "\\") {
8739
+ for (const b of Buffer.from(ch, "utf8")) bytes.push(b);
8740
+ continue;
8741
+ }
8742
+ const next = inner[i + 1];
8743
+ if (next === void 0) {
8744
+ bytes.push(92);
8745
+ break;
8746
+ }
8747
+ const simple = {
8748
+ n: 10,
8749
+ t: 9,
8750
+ r: 13,
8751
+ a: 7,
8752
+ b: 8,
8753
+ f: 12,
8754
+ v: 11,
8755
+ '"': 34,
8756
+ "\\": 92
8757
+ };
8758
+ const mapped = simple[next];
8759
+ if (mapped !== void 0) {
8760
+ bytes.push(mapped);
8761
+ i += 1;
8762
+ continue;
8763
+ }
8764
+ const octal = /^[0-7]{1,3}/.exec(inner.slice(i + 1));
8765
+ if (octal) {
8766
+ bytes.push(Number.parseInt(octal[0], 8) & 255);
8767
+ i += octal[0].length;
8768
+ continue;
8769
+ }
8770
+ for (const b of Buffer.from(next, "utf8")) bytes.push(b);
8771
+ i += 1;
8772
+ }
8773
+ return Buffer.from(bytes).toString("utf8");
8774
+ }
8775
+ function parsePorcelainLine(line) {
8776
+ const body = line.slice(3);
8777
+ if (!body) return null;
8778
+ const status = line.slice(0, 2);
8779
+ if (status.includes("R") || status.includes("C")) {
8780
+ const arrow = body.lastIndexOf(" -> ");
8781
+ if (arrow !== -1) return unquotePorcelainPath(body.slice(arrow + 4).trim());
8782
+ }
8783
+ return unquotePorcelainPath(body.trim());
8784
+ }
8718
8785
  async function getChangedFiles(cwd) {
8719
8786
  const output = await runGit3(["status", "--porcelain"], cwd);
8720
8787
  if (!output) return [];
8721
- return output.split("\n").filter((l) => l.trim()).map((l) => l.slice(3).trim());
8788
+ return output.split("\n").filter((l) => l.trim()).map((l) => parsePorcelainLine(l)).filter((p) => p !== null && p.length > 0);
8722
8789
  }
8723
8790
  async function getStagedFiles(cwd) {
8724
8791
  const output = await runGit3(["diff", "--cached", "--name-only"], cwd);
@@ -8734,7 +8801,7 @@ async function stageFiles(files, cwd) {
8734
8801
  }
8735
8802
  });
8736
8803
  if (existing.length === 0) throw new Error("No files exist to stage");
8737
- await runGit3(["add", ...existing], cwd);
8804
+ await runGit3(["add", "--", ...existing], cwd);
8738
8805
  }
8739
8806
  async function commitWithMessage(message, cwd) {
8740
8807
  return await runGit3(["commit", "-m", message], cwd, GIT_COMMIT_TIMEOUT_MS);
@@ -8787,7 +8854,7 @@ async function externalChangesSinceStage(cwd) {
8787
8854
  const unstaged = out.split("\n").filter((l) => l.trim()).filter((l) => {
8788
8855
  const idx = l[0] ?? " ";
8789
8856
  return idx === " " || idx === "?";
8790
- }).map((l) => l.slice(3).trim());
8857
+ }).map((l) => parsePorcelainLine(l)).filter((p) => p !== null && p.length > 0);
8791
8858
  return unstaged.length > 0 ? unstaged : null;
8792
8859
  } catch {
8793
8860
  return null;
@@ -9389,7 +9456,7 @@ var plugin27 = {
9389
9456
  state24.organizedCount = 0;
9390
9457
  state24.cleanCount = 0;
9391
9458
  state24.errorCount = 0;
9392
- state24.hookUnregister = null;
9459
+ state24.hookUnregister = releaseHandle(state24.hookUnregister);
9393
9460
  state24.lastResult = null;
9394
9461
  state24.probeComplete = false;
9395
9462
  state24.linterAvailable = false;
@@ -9596,12 +9663,19 @@ var PATTERNS = [
9596
9663
  re: /(?:\u200B|\u200C|\u200D|\u2060|\uFEFF){20,}/
9597
9664
  }
9598
9665
  ];
9666
+ var INVISIBLE_CHARS = /[\u00AD\u200B-\u200F\u202A-\u202E\u2060-\u2064\uFEFF]/g;
9667
+ function stripInvisible(text) {
9668
+ INVISIBLE_CHARS.lastIndex = 0;
9669
+ return text.replace(INVISIBLE_CHARS, "");
9670
+ }
9599
9671
  function scanForInjection(text) {
9600
- const hits = [];
9672
+ const hits = /* @__PURE__ */ new Set();
9673
+ const cleaned = stripInvisible(text);
9601
9674
  for (const p of PATTERNS) {
9602
- if (p.re.test(text)) hits.push(p.name);
9675
+ if (p.re.test(text)) hits.add(p.name);
9676
+ else if (cleaned !== text && p.re.test(cleaned)) hits.add(p.name);
9603
9677
  }
9604
- return hits;
9678
+ return [...hits];
9605
9679
  }
9606
9680
  var plugin28 = {
9607
9681
  name: "injection-shield",
@@ -10827,7 +10901,7 @@ var plugin32 = {
10827
10901
  state29.hitCount = 0;
10828
10902
  state29.fixCount = 0;
10829
10903
  state29.linterErrorCount = 0;
10830
- state29.hookUnregister = null;
10904
+ state29.hookUnregister = releaseHandle(state29.hookUnregister);
10831
10905
  state29.lastResult = null;
10832
10906
  linterCache.clear();
10833
10907
  const cfg = readConfig27(api.config.extensions?.["lint-gate"]);
@@ -12054,7 +12128,7 @@ var plugin35 = {
12054
12128
  state32.llmAnalysisCount = 0;
12055
12129
  state32.llmFallbackCount = 0;
12056
12130
  state32.lastPlan = null;
12057
- state32.hookUnregister = null;
12131
+ state32.hookUnregister = releaseHandle(state32.hookUnregister);
12058
12132
  const cfg = readConfig30(api.config.extensions?.["migration-planner"]);
12059
12133
  const hook = (input) => {
12060
12134
  if (!cfg.enabled) return;
@@ -13828,7 +13902,7 @@ var plugin40 = {
13828
13902
  state38.draftsWritten = 0;
13829
13903
  state38.draftErrors = 0;
13830
13904
  state38.stopInvocations = 0;
13831
- state38.stopHookUnregister = null;
13905
+ state38.stopHookUnregister = releaseHandle(state38.stopHookUnregister);
13832
13906
  for (const off of state38.eventUnsubscribers) {
13833
13907
  try {
13834
13908
  off();
@@ -15849,8 +15923,8 @@ var plugin46 = {
15849
15923
  state44.leakCount = 0;
15850
15924
  state44.lastBlock = null;
15851
15925
  state44.lastLeak = null;
15852
- state44.hookUnregister = null;
15853
- state44.postHookUnregister = null;
15926
+ state44.hookUnregister = releaseHandle(state44.hookUnregister);
15927
+ state44.postHookUnregister = releaseHandle(state44.postHookUnregister);
15854
15928
  const cfg = readConfig42(api.config.extensions?.["secret-scanner"]);
15855
15929
  PATTERNS3 = [...BASE_PATTERNS];
15856
15930
  for (const cp of cfg.customPatterns) {
@@ -17618,7 +17692,7 @@ var plugin50 = {
17618
17692
  state48.commitCount = 0;
17619
17693
  state48.startedAt = null;
17620
17694
  state48.lastActivityAt = null;
17621
- state48.stopHookUnregister = null;
17695
+ state48.stopHookUnregister = releaseHandle(state48.stopHookUnregister);
17622
17696
  for (const off of state48.eventUnsubscribers) {
17623
17697
  try {
17624
17698
  off();
@@ -18224,8 +18298,15 @@ function relativePath7(p) {
18224
18298
  function escapeRegex3(s) {
18225
18299
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18226
18300
  }
18301
+ var IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
18302
+ function isIdentifier(name) {
18303
+ return IDENTIFIER_RE.test(name);
18304
+ }
18227
18305
  function renameInContent(content, oldName, newName) {
18228
- const re = new RegExp(`\\b${escapeRegex3(oldName)}\\b`, "g");
18306
+ const re = new RegExp(
18307
+ `(?<![A-Za-z0-9_$])${escapeRegex3(oldName)}(?![A-Za-z0-9_$])`,
18308
+ "g"
18309
+ );
18229
18310
  let replacements = 0;
18230
18311
  const preview = content.replace(re, () => {
18231
18312
  replacements++;
@@ -18287,6 +18368,12 @@ var plugin52 = {
18287
18368
  if (!newName || typeof newName !== "string" || newName.length === 0) {
18288
18369
  return { ok: false, error: "newName is required" };
18289
18370
  }
18371
+ if (!isIdentifier(oldName)) {
18372
+ return { ok: false, error: `oldName "${oldName}" is not a valid identifier` };
18373
+ }
18374
+ if (!isIdentifier(newName)) {
18375
+ return { ok: false, error: `newName "${newName}" is not a valid identifier` };
18376
+ }
18290
18377
  if (!withinProject7(rawPath)) {
18291
18378
  return { ok: false, error: "path is outside the project root" };
18292
18379
  }
@@ -18684,8 +18771,8 @@ var plugin53 = {
18684
18771
  state51.skippedNonMd = 0;
18685
18772
  state51.readErrorCount = 0;
18686
18773
  state51.autoFixApplied = 0;
18687
- state51.postHookUnregister = null;
18688
- state51.preHookUnregister = null;
18774
+ state51.postHookUnregister = releaseHandle(state51.postHookUnregister);
18775
+ state51.preHookUnregister = releaseHandle(state51.preHookUnregister);
18689
18776
  const cfg = readConfig47(api.config.extensions?.["spec-linker"]);
18690
18777
  const postHook = async (input) => {
18691
18778
  if (!cfg.enabled) return;
@@ -18842,6 +18929,7 @@ import { readFile as readFile16, writeFile as writeFile5 } from "node:fs/promise
18842
18929
  import { isAbsolute as isAbsolute25 } from "node:path";
18843
18930
  var API_VERSION35 = "^0.1.10";
18844
18931
  var templates = /* @__PURE__ */ new Map();
18932
+ var contributorUnregister = null;
18845
18933
  function expandTemplate(template, variables) {
18846
18934
  let result = template;
18847
18935
  result = result.replace(/\{\{(\w+)\}\}/g, (match, key) => {
@@ -19114,7 +19202,8 @@ var plugin54 = {
19114
19202
  };
19115
19203
  }
19116
19204
  });
19117
- api.registerSystemPromptContributor(async () => [
19205
+ contributorUnregister = releaseHandle(contributorUnregister);
19206
+ contributorUnregister = api.registerSystemPromptContributor(async () => [
19118
19207
  {
19119
19208
  type: "text",
19120
19209
  text: `Template engine available:
@@ -19129,6 +19218,7 @@ var plugin54 = {
19129
19218
  teardown(api) {
19130
19219
  const count = templates.size;
19131
19220
  templates.clear();
19221
+ contributorUnregister = releaseHandle(contributorUnregister);
19132
19222
  api.log.info("template-engine: teardown complete", { cleared: count });
19133
19223
  },
19134
19224
  async health() {
@@ -19258,7 +19348,7 @@ var plugin55 = {
19258
19348
  state52.skippedCount = 0;
19259
19349
  state52.lastOverallPct = null;
19260
19350
  state52.lastResult = null;
19261
- state52.hookUnregister = null;
19351
+ state52.hookUnregister = releaseHandle(state52.hookUnregister);
19262
19352
  const cfg = readConfig48(api.config.extensions?.["test-coverage-gate"]);
19263
19353
  const hook = (input) => {
19264
19354
  if (!cfg.enabled) return;
@@ -20448,7 +20538,7 @@ var plugin58 = {
20448
20538
  state55.errorCount = 0;
20449
20539
  state55.extensionSkippedCount = 0;
20450
20540
  state55.cachedSkipCount = 0;
20451
- state55.hookUnregister = null;
20541
+ state55.hookUnregister = releaseHandle(state55.hookUnregister);
20452
20542
  state55.lastResult = null;
20453
20543
  lastPassedHash.clear();
20454
20544
  const cfg = readConfig51(api.config.extensions?.["test-runner-gate"]);
@@ -20699,7 +20789,7 @@ var plugin59 = {
20699
20789
  state56.lastMessageId = null;
20700
20790
  state56.lastPayloadHash = "";
20701
20791
  state56.lastBroadcastAt = 0;
20702
- state56.hookUnregister = null;
20792
+ state56.hookUnregister = releaseHandle(state56.hookUnregister);
20703
20793
  const cfg = readConfig52(api.config.extensions?.["todo-listener"]);
20704
20794
  const mailbox = api.mailbox;
20705
20795
  const hook = async (input) => {
@@ -21315,16 +21405,36 @@ function modelMatches(pattern, model) {
21315
21405
  }
21316
21406
  return model.toLowerCase() === pattern.toLowerCase();
21317
21407
  }
21408
+ function readBoundedNumber(value, min, max, fallback) {
21409
+ if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
21410
+ if (value < min || value > max) return fallback;
21411
+ return value;
21412
+ }
21318
21413
  function readConfig53(raw) {
21319
21414
  if (!raw || typeof raw !== "object") return { ...DEFAULTS50 };
21320
21415
  const r = raw;
21416
+ const warnPercent = readBoundedNumber(r["warnPercent"], 1, 100, DEFAULTS50.warnPercent);
21417
+ const stopPercent = readBoundedNumber(r["stopPercent"], 1, 100, DEFAULTS50.stopPercent);
21321
21418
  return {
21322
- limit: typeof r["limit"] === "number" ? r["limit"] : DEFAULTS50.limit,
21323
- warnPercent: typeof r["warnPercent"] === "number" ? r["warnPercent"] : DEFAULTS50.warnPercent,
21324
- stopPercent: typeof r["stopPercent"] === "number" ? r["stopPercent"] : DEFAULTS50.stopPercent,
21419
+ limit: readBoundedNumber(r["limit"], 0, Number.MAX_SAFE_INTEGER, DEFAULTS50.limit),
21420
+ warnPercent,
21421
+ // A warn threshold above the stop threshold can never fire — the run
21422
+ // stops first. Clamp so the pair always describes a reachable window.
21423
+ stopPercent: Math.max(stopPercent, warnPercent),
21325
21424
  model: typeof r["model"] === "string" ? r["model"] : ""
21326
21425
  };
21327
21426
  }
21427
+ function clearRegistrations2() {
21428
+ for (const key of ["hookUnregister", "postHookUnregister"]) {
21429
+ const off = state58[key];
21430
+ if (!off) continue;
21431
+ try {
21432
+ off();
21433
+ } catch {
21434
+ }
21435
+ state58[key] = null;
21436
+ }
21437
+ }
21328
21438
  var plugin61 = {
21329
21439
  name: "token-budget",
21330
21440
  version: "0.1.0",
@@ -21371,9 +21481,8 @@ var plugin61 = {
21371
21481
  state58.stopFired = false;
21372
21482
  state58.warnContextInjected = false;
21373
21483
  state58.stopContextInjected = false;
21374
- state58.hookUnregister = null;
21375
- state58.postHookUnregister = null;
21376
21484
  state58.lastRequest = null;
21485
+ clearRegistrations2();
21377
21486
  const cfg = readConfig53(api.config.extensions?.["token-budget"]);
21378
21487
  api.onEvent("provider.response", (payload) => {
21379
21488
  const p = payload;
@@ -21470,6 +21579,12 @@ var plugin61 = {
21470
21579
  remaining,
21471
21580
  percent,
21472
21581
  requestCount: state58.requestCount,
21582
+ // The EFFECTIVE thresholds, after out-of-range values fall back to
21583
+ // the defaults and warn/stop are ordered. Without these the user
21584
+ // cannot tell that a rejected or clamped setting is not in force.
21585
+ warnPercent: cfg.warnPercent,
21586
+ stopPercent: cfg.stopPercent,
21587
+ model: cfg.model === "" ? null : cfg.model,
21473
21588
  breakdown: {
21474
21589
  prompt: state58.totalPromptTokens,
21475
21590
  completion: state58.totalCompletionTokens
@@ -21488,20 +21603,7 @@ var plugin61 = {
21488
21603
  });
21489
21604
  },
21490
21605
  teardown(api) {
21491
- if (state58.hookUnregister) {
21492
- try {
21493
- state58.hookUnregister();
21494
- } catch {
21495
- }
21496
- state58.hookUnregister = null;
21497
- }
21498
- if (state58.postHookUnregister) {
21499
- try {
21500
- state58.postHookUnregister();
21501
- } catch {
21502
- }
21503
- state58.postHookUnregister = null;
21504
- }
21606
+ clearRegistrations2();
21505
21607
  const final = {
21506
21608
  totalTokens: state58.totalTokens,
21507
21609
  requestCount: state58.requestCount,
@@ -21927,7 +22029,7 @@ var plugin63 = {
21927
22029
  state60.errorCount = 0;
21928
22030
  state60.skippedCount = 0;
21929
22031
  state60.lastResult = null;
21930
- state60.hookUnregister = null;
22032
+ state60.hookUnregister = releaseHandle(state60.hookUnregister);
21931
22033
  const cfg = readConfig55(api.config.extensions?.["type-gate"]);
21932
22034
  const hook = async (input) => {
21933
22035
  if (!cfg.enabled) return;
@@ -41,6 +41,22 @@
41
41
  * @public
42
42
  */
43
43
  import type { Plugin } from '@wrongstack/core/types';
44
+ /**
45
+ * Strip invisible characters so a payload cannot hide between letters.
46
+ *
47
+ * Every pattern here is written against readable words, so a single
48
+ * zero-width space inside a keyword — `i​gnore previous
49
+ * instructions` — defeated all of them while the text stayed perfectly
50
+ * legible to the model it targets. That is the textbook evasion for this
51
+ * class of detector, and the existing `zero-width-flood` rule did not
52
+ * cover it: it only fires on 20+ *consecutive* invisibles, not on the one
53
+ * character it takes to break a word.
54
+ *
55
+ * Scanning is done on both forms: the stripped text catches hidden
56
+ * payloads, and the original still catches the flood rule (whose whole
57
+ * purpose is to notice the invisible characters themselves).
58
+ */
59
+ export declare function stripInvisible(text: string): string;
44
60
  export declare function scanForInjection(text: string): string[];
45
61
  declare const plugin: Plugin;
46
62
  export default plugin;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/injection-shield/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AA6GrD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAMvD;AAMD,QAAA,MAAM,MAAM,EAAE,MAwJb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/injection-shield/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAuHrD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAUvD;AAMD,QAAA,MAAM,MAAM,EAAE,MAwJb,CAAC;eAEa,MAAM"}
@@ -64,12 +64,19 @@ var PATTERNS = [
64
64
  re: /(?:\u200B|\u200C|\u200D|\u2060|\uFEFF){20,}/
65
65
  }
66
66
  ];
67
+ var INVISIBLE_CHARS = /[\u00AD\u200B-\u200F\u202A-\u202E\u2060-\u2064\uFEFF]/g;
68
+ function stripInvisible(text) {
69
+ INVISIBLE_CHARS.lastIndex = 0;
70
+ return text.replace(INVISIBLE_CHARS, "");
71
+ }
67
72
  function scanForInjection(text) {
68
- const hits = [];
73
+ const hits = /* @__PURE__ */ new Set();
74
+ const cleaned = stripInvisible(text);
69
75
  for (const p of PATTERNS) {
70
- if (p.re.test(text)) hits.push(p.name);
76
+ if (p.re.test(text)) hits.add(p.name);
77
+ else if (cleaned !== text && p.re.test(cleaned)) hits.add(p.name);
71
78
  }
72
- return hits;
79
+ return [...hits];
73
80
  }
74
81
  var plugin = {
75
82
  name: "injection-shield",
@@ -204,5 +211,6 @@ var plugin = {
204
211
  var injection_shield_default = plugin;
205
212
  export {
206
213
  injection_shield_default as default,
207
- scanForInjection
214
+ scanForInjection,
215
+ stripInvisible
208
216
  };
package/dist/lint-gate.js CHANGED
@@ -103,6 +103,17 @@ var BoundedMap = class {
103
103
  }
104
104
  };
105
105
 
106
+ // src/runtime/handles.ts
107
+ function releaseHandle(off) {
108
+ if (off) {
109
+ try {
110
+ off();
111
+ } catch {
112
+ }
113
+ }
114
+ return null;
115
+ }
116
+
106
117
  // src/runtime/index.ts
107
118
  var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
108
119
 
@@ -331,7 +342,7 @@ var plugin = {
331
342
  state.hitCount = 0;
332
343
  state.fixCount = 0;
333
344
  state.linterErrorCount = 0;
334
- state.hookUnregister = null;
345
+ state.hookUnregister = releaseHandle(state.hookUnregister);
335
346
  state.lastResult = null;
336
347
  linterCache.clear();
337
348
  const cfg = readConfig(api.config.extensions?.["lint-gate"]);
@@ -53,6 +53,17 @@ async function runOptionalPluginLlm(request) {
53
53
  // src/runtime/local-bin.ts
54
54
  import { buildWin32CmdShimInvocation, resolveWin32Command } from "@wrongstack/tools/win32";
55
55
 
56
+ // src/runtime/handles.ts
57
+ function releaseHandle(off) {
58
+ if (off) {
59
+ try {
60
+ off();
61
+ } catch {
62
+ }
63
+ }
64
+ return null;
65
+ }
66
+
56
67
  // src/runtime/index.ts
57
68
  var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
58
69
  function hasLeadingDash(arg) {
@@ -318,7 +329,7 @@ var plugin = {
318
329
  state.llmAnalysisCount = 0;
319
330
  state.llmFallbackCount = 0;
320
331
  state.lastPlan = null;
321
- state.hookUnregister = null;
332
+ state.hookUnregister = releaseHandle(state.hookUnregister);
322
333
  const cfg = readConfig(api.config.extensions?.["migration-planner"]);
323
334
  const hook = (input) => {
324
335
  if (!cfg.enabled) return;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pr-drafter/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAKH,OAAO,KAAK,EAAE,MAAM,EAAa,MAAM,wBAAwB,CAAC;AA4MhE,QAAA,MAAM,MAAM,EAAE,MAyNb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pr-drafter/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAKH,OAAO,KAAK,EAAE,MAAM,EAAa,MAAM,wBAAwB,CAAC;AA6MhE,QAAA,MAAM,MAAM,EAAE,MAyNb,CAAC;eAEa,MAAM"}
@@ -2,6 +2,25 @@
2
2
  import { execFile } from "node:child_process";
3
3
  import { mkdir, writeFile } from "node:fs/promises";
4
4
  import { dirname, isAbsolute, relative, resolve } from "node:path";
5
+
6
+ // src/runtime/local-bin.ts
7
+ import { buildWin32CmdShimInvocation, resolveWin32Command } from "@wrongstack/tools/win32";
8
+
9
+ // src/runtime/handles.ts
10
+ function releaseHandle(off) {
11
+ if (off) {
12
+ try {
13
+ off();
14
+ } catch {
15
+ }
16
+ }
17
+ return null;
18
+ }
19
+
20
+ // src/runtime/index.ts
21
+ var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
22
+
23
+ // src/pr-drafter/index.ts
5
24
  var API_VERSION = "^0.1.10";
6
25
  var state = {
7
26
  commits: [],
@@ -193,7 +212,7 @@ var plugin = {
193
212
  state.draftsWritten = 0;
194
213
  state.draftErrors = 0;
195
214
  state.stopInvocations = 0;
196
- state.stopHookUnregister = null;
215
+ state.stopHookUnregister = releaseHandle(state.stopHookUnregister);
197
216
  for (const off of state.eventUnsubscribers) {
198
217
  try {
199
218
  off();
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @wrongstack/plugins — unregister-handle discipline.
3
+ *
4
+ * Plugins keep their hook/listener unregister functions in module-scope
5
+ * state, and every plugin's `setup()` is expected to be idempotent: calling
6
+ * it twice must not leave two live registrations. The house style calls
7
+ * this the "H1 pattern".
8
+ *
9
+ * The failure mode this helper exists to prevent is subtle and was present
10
+ * in ~15 plugins: `setup()` reset the handle with
11
+ *
12
+ * ```ts
13
+ * state.hookUnregister = null; // WRONG
14
+ * ```
15
+ *
16
+ * which drops the only reference to the *previous* registration without
17
+ * calling it. The old hook stays live in the registry, unreachable, and
18
+ * fires alongside the new one — so counters double, warnings appear twice,
19
+ * and a gate can block on a stale closure holding the previous config.
20
+ *
21
+ * `releaseHandle` makes the correct spelling a one-liner with the same
22
+ * shape as the wrong one, so the two are hard to confuse:
23
+ *
24
+ * ```ts
25
+ * state.hookUnregister = releaseHandle(state.hookUnregister);
26
+ * ```
27
+ */
28
+ /** An unregister function returned by `registerHook`/`onEvent`/etc. */
29
+ export type Unregister = (() => void) | null | undefined;
30
+ /**
31
+ * Invoke `off` if present, swallowing any error, and return `null` so the
32
+ * caller can assign the result straight back to the handle.
33
+ *
34
+ * Unregistering is best-effort by design: a handle whose owner has already
35
+ * been torn down may throw, and that must never prevent the rest of
36
+ * `setup()`/`teardown()` from running.
37
+ */
38
+ export declare function releaseHandle(off: Unregister): null;
39
+ /**
40
+ * Release several handles held on one state object, clearing each in place.
41
+ *
42
+ * @example
43
+ * releaseHandles(state, ['hookUnregister', 'postHookUnregister']);
44
+ */
45
+ export declare function releaseHandles<S extends object, K extends keyof S>(state: S, keys: readonly K[]): void;
46
+ //# sourceMappingURL=handles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handles.d.ts","sourceRoot":"","sources":["../../src/runtime/handles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,uEAAuE;AACvE,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;AAEzD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CASnD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,CAAC,EAChE,KAAK,EAAE,CAAC,EACR,IAAI,EAAE,SAAS,CAAC,EAAE,GACjB,IAAI,CAIN"}
@@ -31,6 +31,7 @@
31
31
  export { parseLlmJsonObject, runOptionalPluginLlm, stripOuterMarkdownFence, type OptionalLlmRequest, type OptionalLlmResult, } from './llm.js';
32
32
  export { BoundedMap, BoundedSet, type BoundedMapOptions } from './bounded-map.js';
33
33
  export { UNSERIALIZABLE, safeJsonStringify } from './safe-json.js';
34
+ export { releaseHandle, releaseHandles, type Unregister } from './handles.js';
34
35
  export { clearLocalBinCache, findOnPath, resolveExecInvocation, resolveFirstNodeBin, resolveNodeBin, resolveWin32Command, type ExecInvocation, type ResolvedNodeBin, } from './local-bin.js';
35
36
  export type LanguageId = 'typescript' | 'javascript' | 'python' | 'go' | 'rust' | 'shell' | 'ruby' | 'java' | 'kotlin' | 'dotnet' | 'generic';
36
37
  export type PackageManagerId = 'npm' | 'pnpm' | 'yarn' | 'bun' | 'pip' | 'poetry' | 'go' | 'cargo' | 'gem' | 'maven' | 'gradle' | 'dotnet' | 'none';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"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"}