@wrongstack/plugins 0.319.1 → 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.
@@ -1,5 +1,5 @@
1
1
  // src/checkpoint/index.ts
2
- import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
2
+ import { mkdir, readFile, realpath, stat, writeFile } from "node:fs/promises";
3
3
  import { dirname, isAbsolute, relative, resolve } from "node:path";
4
4
  var state = {
5
5
  snapshots: [],
@@ -28,13 +28,46 @@ function readConfig(raw) {
28
28
  maxFileBytes: typeof r["maxFileBytes"] === "number" && r["maxFileBytes"] >= 1024 ? r["maxFileBytes"] : DEFAULTS.maxFileBytes
29
29
  };
30
30
  }
31
- function resolveProjectPath(rawPath, cwd = process.cwd()) {
31
+ async function resolveProjectPath(rawPath, cwd = process.cwd()) {
32
32
  if (typeof rawPath !== "string" || rawPath.length === 0) return null;
33
33
  const root = resolve(cwd);
34
34
  const resolved = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);
35
35
  const rel = relative(root, resolved);
36
- if (rel === "" || !rel.startsWith("..") && !isAbsolute(rel)) return resolved;
37
- return null;
36
+ const lexicallyInside = rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
37
+ if (!lexicallyInside) return null;
38
+ if (await realResolutionEscapes(resolved, root)) return null;
39
+ return resolved;
40
+ }
41
+ async function realResolutionEscapes(absPath, root) {
42
+ let real;
43
+ try {
44
+ real = await realpath(absPath);
45
+ } catch {
46
+ let current = dirname(absPath);
47
+ let hops = 0;
48
+ while (hops < 64 && !await pathExists(current)) {
49
+ const parent = dirname(current);
50
+ if (parent === current) return false;
51
+ current = parent;
52
+ hops++;
53
+ }
54
+ if (!await pathExists(current)) return false;
55
+ try {
56
+ real = await realpath(current);
57
+ } catch {
58
+ return false;
59
+ }
60
+ }
61
+ const realRel = relative(root, real);
62
+ return realRel !== "" && realRel !== "." && (realRel.startsWith("..") || isAbsolute(realRel));
63
+ }
64
+ async function pathExists(p) {
65
+ try {
66
+ await stat(p);
67
+ return true;
68
+ } catch {
69
+ return false;
70
+ }
38
71
  }
39
72
  function hashContent(s) {
40
73
  const cap = Math.min(s.length, 65536);
@@ -135,7 +168,7 @@ var plugin = {
135
168
  const ti = input.toolInput ?? {};
136
169
  const raw = ti["path"] ?? ti["file_path"] ?? ti["filePath"] ?? ti["TargetFile"] ?? ti["targetFile"] ?? ti["file"];
137
170
  if (typeof raw !== "string" || raw.length === 0) return;
138
- const safePath = resolveProjectPath(raw);
171
+ const safePath = await resolveProjectPath(raw);
139
172
  if (!safePath) return;
140
173
  const captured = await captureFileForHook(safePath, cfg.maxFileBytes, runtime.signal);
141
174
  if (captured === "too-large") {
@@ -206,7 +239,7 @@ var plugin = {
206
239
  const rejectedOutsideProject = [];
207
240
  let skipped = 0;
208
241
  for (const p of paths) {
209
- const safePath = resolveProjectPath(p);
242
+ const safePath = await resolveProjectPath(p);
210
243
  if (!safePath) {
211
244
  rejectedOutsideProject.push(p);
212
245
  continue;
@@ -313,7 +346,7 @@ var plugin = {
313
346
  error: rawId ? `no snapshot with id "${rawId}"` : "no snapshots captured yet"
314
347
  };
315
348
  }
316
- const targetPath = rawPath ? resolveProjectPath(rawPath) ?? rawPath : null;
349
+ const targetPath = rawPath ? await resolveProjectPath(rawPath) ?? rawPath : null;
317
350
  const targets = targetPath ? snapshot.files.filter((f) => f.path === targetPath || f.path === rawPath) : snapshot.files;
318
351
  if (targets.length === 0) {
319
352
  return { ok: false, error: `snapshot ${snapshot.id} has no entry for "${rawPath}"` };
package/dist/index.js CHANGED
@@ -2805,7 +2805,7 @@ var plugin8 = {
2805
2805
  var changelog_writer_default = plugin8;
2806
2806
 
2807
2807
  // src/checkpoint/index.ts
2808
- import { mkdir, readFile as readFile3, stat, writeFile } from "node:fs/promises";
2808
+ import { mkdir, readFile as readFile3, realpath, stat, writeFile } from "node:fs/promises";
2809
2809
  import { dirname as dirname2, isAbsolute as isAbsolute5, relative as relative5, resolve as resolve5 } from "node:path";
2810
2810
  var state9 = {
2811
2811
  snapshots: [],
@@ -2834,13 +2834,46 @@ function readConfig8(raw) {
2834
2834
  maxFileBytes: typeof r["maxFileBytes"] === "number" && r["maxFileBytes"] >= 1024 ? r["maxFileBytes"] : DEFAULTS8.maxFileBytes
2835
2835
  };
2836
2836
  }
2837
- function resolveProjectPath3(rawPath, cwd = process.cwd()) {
2837
+ async function resolveProjectPath3(rawPath, cwd = process.cwd()) {
2838
2838
  if (typeof rawPath !== "string" || rawPath.length === 0) return null;
2839
2839
  const root = resolve5(cwd);
2840
2840
  const resolved = isAbsolute5(rawPath) ? resolve5(rawPath) : resolve5(root, rawPath);
2841
2841
  const rel = relative5(root, resolved);
2842
- if (rel === "" || !rel.startsWith("..") && !isAbsolute5(rel)) return resolved;
2843
- 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
+ }
2844
2877
  }
2845
2878
  function hashContent(s) {
2846
2879
  const cap = Math.min(s.length, 65536);
@@ -2941,7 +2974,7 @@ var plugin9 = {
2941
2974
  const ti = input.toolInput ?? {};
2942
2975
  const raw = ti["path"] ?? ti["file_path"] ?? ti["filePath"] ?? ti["TargetFile"] ?? ti["targetFile"] ?? ti["file"];
2943
2976
  if (typeof raw !== "string" || raw.length === 0) return;
2944
- const safePath = resolveProjectPath3(raw);
2977
+ const safePath = await resolveProjectPath3(raw);
2945
2978
  if (!safePath) return;
2946
2979
  const captured = await captureFileForHook(safePath, cfg.maxFileBytes, runtime.signal);
2947
2980
  if (captured === "too-large") {
@@ -3012,7 +3045,7 @@ var plugin9 = {
3012
3045
  const rejectedOutsideProject = [];
3013
3046
  let skipped = 0;
3014
3047
  for (const p of paths) {
3015
- const safePath = resolveProjectPath3(p);
3048
+ const safePath = await resolveProjectPath3(p);
3016
3049
  if (!safePath) {
3017
3050
  rejectedOutsideProject.push(p);
3018
3051
  continue;
@@ -3119,7 +3152,7 @@ var plugin9 = {
3119
3152
  error: rawId ? `no snapshot with id "${rawId}"` : "no snapshots captured yet"
3120
3153
  };
3121
3154
  }
3122
- const targetPath = rawPath ? resolveProjectPath3(rawPath) ?? rawPath : null;
3155
+ const targetPath = rawPath ? await resolveProjectPath3(rawPath) ?? rawPath : null;
3123
3156
  const targets = targetPath ? snapshot.files.filter((f) => f.path === targetPath || f.path === rawPath) : snapshot.files;
3124
3157
  if (targets.length === 0) {
3125
3158
  return { ok: false, error: `snapshot ${snapshot.id} has no entry for "${rawPath}"` };
@@ -6931,7 +6964,7 @@ Consider mentioning these changes in the documentation.`;
6931
6964
  var doc_sync_guard_default = plugin20;
6932
6965
 
6933
6966
  // src/duplicate-code-detector/index.ts
6934
- 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";
6935
6968
  import { extname as extname4, isAbsolute as isAbsolute8, relative as relative9, resolve as resolve9, sep } from "node:path";
6936
6969
  var API_VERSION14 = "^0.1.10";
6937
6970
  var HOOK_WARNING_COOLDOWN_MS = 6e4;
@@ -7290,7 +7323,7 @@ var plugin21 = {
7290
7323
  if (!isWithinRoot(projectRoot, resolvedFile)) return;
7291
7324
  let changedFile;
7292
7325
  try {
7293
- changedFile = await realpath(resolvedFile);
7326
+ changedFile = await realpath2(resolvedFile);
7294
7327
  } catch {
7295
7328
  state20.errorCount += 1;
7296
7329
  return;
@@ -9530,7 +9563,7 @@ function classifyArtifact(relPath, patterns) {
9530
9563
  }
9531
9564
  return null;
9532
9565
  }
9533
- async function pathExists(p) {
9566
+ async function pathExists2(p) {
9534
9567
  try {
9535
9568
  await access3(p);
9536
9569
  return true;
@@ -9539,7 +9572,7 @@ async function pathExists(p) {
9539
9572
  }
9540
9573
  }
9541
9574
  async function readGitignoreLines(file) {
9542
- if (!await pathExists(file)) return [];
9575
+ if (!await pathExists2(file)) return [];
9543
9576
  try {
9544
9577
  const content = await readFile9(file, "utf8");
9545
9578
  return content.split(/\r?\n/);
@@ -9571,7 +9604,7 @@ function relDirOf(rel) {
9571
9604
  }
9572
9605
  async function firstExistingGitignore(candidates) {
9573
9606
  for (const c of candidates) {
9574
- if (await pathExists(c)) return c;
9607
+ if (await pathExists2(c)) return c;
9575
9608
  }
9576
9609
  return null;
9577
9610
  }
@@ -9582,7 +9615,7 @@ async function appendPatterns(target, patterns, limit) {
9582
9615
  );
9583
9616
  const missing = patterns.filter((p) => !seen.has(p)).slice(0, limit);
9584
9617
  if (missing.length === 0) return { appended: [] };
9585
- const current = await pathExists(target) ? await readFile9(target, "utf8") : "";
9618
+ const current = await pathExists2(target) ? await readFile9(target, "utf8") : "";
9586
9619
  const base = current.length === 0 ? "" : current.endsWith("\n") ? current : `${current}
9587
9620
  `;
9588
9621
  const next = `${base}${missing.map((p) => `${p}
@@ -12781,7 +12814,7 @@ import {
12781
12814
  runOptionalPluginCouncil,
12782
12815
  runOptionalPluginLlm,
12783
12816
  stripOuterMarkdownFence
12784
- } from "@wrongstack/plugin-sdk/runtime";
12817
+ } from "@wrongstack/plugin-sdk/runtime/llm";
12785
12818
 
12786
12819
  // src/migration-planner/index.ts
12787
12820
  var API_VERSION25 = "^0.1.10";
@@ -14033,11 +14066,15 @@ var plugin38 = {
14033
14066
  };
14034
14067
  var notify_hub_default = plugin38;
14035
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
+
14036
14073
  // src/runtime/redos-guard.ts
14037
14074
  import {
14038
14075
  withReDoSGuard,
14039
14076
  guardedMatcher
14040
- } from "@wrongstack/plugin-sdk/runtime";
14077
+ } from "@wrongstack/plugin-sdk/runtime/redos-guard";
14041
14078
 
14042
14079
  // src/path-guard/glob.ts
14043
14080
  var GLOB_REDOS_BUDGET_MS = 250;
@@ -15323,17 +15360,32 @@ function operationLabel(toolName) {
15323
15360
  }
15324
15361
 
15325
15362
  // src/path-guard/index.ts
15326
- import { realpathSync } from "node:fs";
15327
- import { resolve as resolve17 } from "node:path";
15328
15363
  function isSymlinkEscape(path, cwd) {
15329
15364
  if (!(0, runtime_exports.withinProject)(path)) return false;
15365
+ const abs = resolve17(cwd ?? process.cwd(), path);
15366
+ let real;
15330
15367
  try {
15331
- const abs = resolve17(cwd ?? process.cwd(), path);
15332
- const real = realpathSync(abs);
15333
- return !(0, runtime_exports.withinProject)(real);
15368
+ real = realpathSync(abs);
15334
15369
  } catch {
15335
- 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
+ }
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;
15336
15387
  }
15388
+ return null;
15337
15389
  }
15338
15390
  function createState() {
15339
15391
  return {
@@ -15482,6 +15534,9 @@ var plugin39 = {
15482
15534
  path: relativeToInvocationCwd(resolveTargetPath(path, effectiveCwd), input.cwd),
15483
15535
  kind: isRootPathScope(path) && deletesImplicitScope || isUnresolvedPathScope(path) || recursivelyDeletes && (isDirectoryAmbiguousPath(path) || hasConfiguredProtectedDescendant(path, cfg.protect)) ? "deletion-scope" : "file"
15484
15536
  };
15537
+ if (target.kind === "file" && isSymlinkEscape(target.path, input.cwd)) {
15538
+ return target;
15539
+ }
15485
15540
  if (targetFullyAllowed(target, cfg.allow, allowRes)) return null;
15486
15541
  const protectedShellTarget = await targetIntersectsPatternsGuarded(target, cfg.protect, protectRes, {
15487
15542
  onTimeout: bumpRedos
@@ -15609,7 +15664,7 @@ var plugin39 = {
15609
15664
  var path_guard_default = plugin39;
15610
15665
 
15611
15666
  // src/performance-regression-gate/index.ts
15612
- import { existsSync as existsSync6, readFileSync as readFileSync11 } from "node:fs";
15667
+ import { existsSync as existsSync7, readFileSync as readFileSync11 } from "node:fs";
15613
15668
  import { isAbsolute as isAbsolute16, relative as relative16, resolve as resolve18 } from "node:path";
15614
15669
  var API_VERSION26 = "^0.1.10";
15615
15670
  var state36 = {
@@ -15675,7 +15730,7 @@ function flattenResults(results) {
15675
15730
  return flat;
15676
15731
  }
15677
15732
  function loadResults(path) {
15678
- if (!path || !existsSync6(path)) return null;
15733
+ if (!path || !existsSync7(path)) return null;
15679
15734
  try {
15680
15735
  const raw = JSON.parse(readFileSync11(path, "utf-8"));
15681
15736
  return raw;
@@ -16073,7 +16128,7 @@ var plugin_stack_observer_default = PLUGIN;
16073
16128
  // src/pr-drafter/index.ts
16074
16129
  import { execFile as execFile9 } from "node:child_process";
16075
16130
  import { mkdir as mkdir2, writeFile as writeFile4 } from "node:fs/promises";
16076
- 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";
16077
16132
  var API_VERSION27 = "^0.1.10";
16078
16133
  var state38 = {
16079
16134
  commits: [],
@@ -16205,7 +16260,7 @@ async function writeDraft(cfg, llm) {
16205
16260
  }
16206
16261
  const draft = await buildDraft(cfg, llm);
16207
16262
  try {
16208
- await mkdir2(dirname6(resolved), { recursive: true });
16263
+ await mkdir2(dirname7(resolved), { recursive: true });
16209
16264
  await writeFile4(resolved, draft.body);
16210
16265
  state38.draftsWritten += 1;
16211
16266
  } catch {
@@ -16349,7 +16404,7 @@ var plugin41 = {
16349
16404
  const resolved = resolveProjectPath7(outputPathStr);
16350
16405
  if (!resolved) return { ok: false, error: "outputPath resolves outside project" };
16351
16406
  try {
16352
- await mkdir2(dirname6(resolved), { recursive: true });
16407
+ await mkdir2(dirname7(resolved), { recursive: true });
16353
16408
  await writeFile4(resolved, draft.body);
16354
16409
  state38.draftsWritten += 1;
16355
16410
  return {
@@ -16579,7 +16634,7 @@ import { performance as performance2 } from "node:perf_hooks";
16579
16634
  import {
16580
16635
  cloneCredentialPatterns,
16581
16636
  CREDENTIAL_PATTERNS
16582
- } from "@wrongstack/plugin-sdk/runtime";
16637
+ } from "@wrongstack/plugin-sdk/runtime/credential-patterns";
16583
16638
 
16584
16639
  // src/prompt-firewall/index.ts
16585
16640
  var KIND_ALIASES = {
@@ -22532,7 +22587,7 @@ var test_coverage_gate_default = plugin56;
22532
22587
  import { execFile as execFile13 } from "node:child_process";
22533
22588
  import { readFileSync as readFileSync15 } from "node:fs";
22534
22589
  import { createRequire as createRequire2 } from "node:module";
22535
- 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";
22536
22591
  var API_VERSION37 = "^0.1.10";
22537
22592
  var state52 = {
22538
22593
  invocationCount: 0,
@@ -22663,7 +22718,7 @@ function resolveTestCommand(baseCommand, testPattern) {
22663
22718
  const packageJson = JSON.parse(readFileSync15(packagePath, "utf8"));
22664
22719
  const relativeBin = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.[runner] ?? Object.values(packageJson.bin ?? {})[0];
22665
22720
  if (!relativeBin) return null;
22666
- const packageDir = dirname7(packagePath);
22721
+ const packageDir = dirname8(packagePath);
22667
22722
  const candidate = resolve26(packageDir, relativeBin);
22668
22723
  if (isAbsolute25(relativeBin) || !isInside2(packageDir, candidate)) {
22669
22724
  return null;
@@ -23310,7 +23365,7 @@ var test_generator_default = plugin58;
23310
23365
  // src/test-runner-gate/index.ts
23311
23366
  import { execFile as execFile14 } from "node:child_process";
23312
23367
  import { access as access5 } from "node:fs/promises";
23313
- 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";
23314
23369
  import { buildWin32CmdShimInvocation, resolveWin32Command } from "@wrongstack/tools/win32";
23315
23370
  function resolveExec(command, args) {
23316
23371
  const resolved = resolveWin32Command(command);
@@ -23403,7 +23458,7 @@ var ALLOWED_COMMAND_TOKENS = /* @__PURE__ */ new Set([
23403
23458
  function resolveTestFiles(sourcePath, patterns) {
23404
23459
  const name = basename6(sourcePath).replace(/\.[^.]+$/, "");
23405
23460
  const pathNoExt = sourcePath.replace(/\.[^.]+$/, "");
23406
- const dir = dirname8(sourcePath);
23461
+ const dir = dirname9(sourcePath);
23407
23462
  const candidates = [];
23408
23463
  for (const pattern of patterns) {
23409
23464
  const candidate = pattern.replace(/\{name\}/g, name).replace(/\{path\}/g, pathNoExt).replace(/\{dir\}/g, dir);
@@ -24050,7 +24105,7 @@ var todo_listener_default = plugin60;
24050
24105
  // src/todo-tracker/index.ts
24051
24106
  import { randomUUID } from "node:crypto";
24052
24107
  import * as fsp from "node:fs/promises";
24053
- import { dirname as dirname9 } from "node:path";
24108
+ import { dirname as dirname10 } from "node:path";
24054
24109
  import { atomicWrite as atomicWrite3, ensureDir as ensureDir3 } from "@wrongstack/core/utils";
24055
24110
  import { nowIso } from "@wrongstack/primitives";
24056
24111
  function deriveFilePath(api) {
@@ -24083,7 +24138,7 @@ async function loadFile(filePath) {
24083
24138
  }
24084
24139
  }
24085
24140
  async function saveFile(filePath, file) {
24086
- await ensureDir3(dirname9(filePath));
24141
+ await ensureDir3(dirname10(filePath));
24087
24142
  await atomicWrite3(filePath, JSON.stringify(file, null, 2), { mode: 384 });
24088
24143
  }
24089
24144
  var state56 = {
@@ -24990,7 +25045,7 @@ var plugin63 = {
24990
25045
  var token_throttle_default = plugin63;
24991
25046
 
24992
25047
  // src/type-gate/index.ts
24993
- import { existsSync as existsSync7 } from "node:fs";
25048
+ import { existsSync as existsSync8 } from "node:fs";
24994
25049
  var API_VERSION41 = "^0.1.10";
24995
25050
  var state59 = {
24996
25051
  invocationCount: 0,
@@ -25074,7 +25129,7 @@ async function runTypeCheck(cfg) {
25074
25129
  argv = [...argv, "-p", tsConfig];
25075
25130
  }
25076
25131
  }
25077
- if (!cfg.command && tsConfig && !existsSync7(tsConfig)) {
25132
+ if (!cfg.command && tsConfig && !existsSync8(tsConfig)) {
25078
25133
  return null;
25079
25134
  }
25080
25135
  const result = await (0, runtime_exports.runRunnerCommand)([argv[0], ...argv.slice(1)], {
@@ -26,7 +26,7 @@ import {
26
26
  runOptionalPluginCouncil,
27
27
  runOptionalPluginLlm,
28
28
  stripOuterMarkdownFence
29
- } from "@wrongstack/plugin-sdk/runtime";
29
+ } from "@wrongstack/plugin-sdk/runtime/llm";
30
30
 
31
31
  // src/migration-planner/index.ts
32
32
  var API_VERSION = "^0.1.10";
@@ -7,7 +7,14 @@
7
7
  import type { Plugin } from '@wrongstack/core/types';
8
8
  export { compilePathGlob } from './glob.js';
9
9
  export { destructiveTargets } from './shell-targets.js';
10
- /** True when `path` is a project-local symlink whose real target escaped. */
10
+ /**
11
+ * True when `path` is a project-local path whose canonical resolution
12
+ * escaped the project — including when the leaf does not exist yet (e.g. a
13
+ * `write` of a brand-new file): the nearest EXISTING ancestor is
14
+ * canonicalized instead, so a new file created THROUGH an escaped symlink
15
+ * is still detected. A brand-new path under ordinary project directories
16
+ * resolves to an ancestor inside the project and returns false.
17
+ */
11
18
  export declare function isSymlinkEscape(path: string, cwd?: string): boolean;
12
19
  declare const plugin: Plugin;
13
20
  export default plugin;
@@ -12,11 +12,20 @@ var __copyProps = (to, from, except, desc) => {
12
12
  };
13
13
  var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
14
14
 
15
+ // src/path-guard/index.ts
16
+ import { existsSync, realpathSync } from "node:fs";
17
+ import { dirname, resolve } from "node:path";
18
+
19
+ // src/runtime/index.ts
20
+ var runtime_exports = {};
21
+ __reExport(runtime_exports, runtime_star);
22
+ import * as runtime_star from "@wrongstack/plugin-sdk/runtime";
23
+
15
24
  // src/runtime/redos-guard.ts
16
25
  import {
17
26
  withReDoSGuard,
18
27
  guardedMatcher
19
- } from "@wrongstack/plugin-sdk/runtime";
28
+ } from "@wrongstack/plugin-sdk/runtime/redos-guard";
20
29
 
21
30
  // src/path-guard/glob.ts
22
31
  var GLOB_REDOS_BUDGET_MS = 250;
@@ -1301,25 +1310,33 @@ function operationLabel(toolName) {
1301
1310
  return `write via "${toolName}"`;
1302
1311
  }
1303
1312
 
1304
- // src/path-guard/index.ts
1305
- import { realpathSync } from "node:fs";
1306
- import { resolve } from "node:path";
1307
-
1308
- // src/runtime/index.ts
1309
- var runtime_exports = {};
1310
- __reExport(runtime_exports, runtime_star);
1311
- import * as runtime_star from "@wrongstack/plugin-sdk/runtime";
1312
-
1313
1313
  // src/path-guard/index.ts
1314
1314
  function isSymlinkEscape(path, cwd) {
1315
1315
  if (!(0, runtime_exports.withinProject)(path)) return false;
1316
+ const abs = resolve(cwd ?? process.cwd(), path);
1317
+ let real;
1316
1318
  try {
1317
- const abs = resolve(cwd ?? process.cwd(), path);
1318
- const real = realpathSync(abs);
1319
- return !(0, runtime_exports.withinProject)(real);
1319
+ real = realpathSync(abs);
1320
1320
  } catch {
1321
- return false;
1321
+ const ancestor = nearestExistingAncestor(abs);
1322
+ if (ancestor === null) return false;
1323
+ try {
1324
+ real = realpathSync(ancestor);
1325
+ } catch {
1326
+ return false;
1327
+ }
1322
1328
  }
1329
+ return !(0, runtime_exports.withinProject)(real);
1330
+ }
1331
+ function nearestExistingAncestor(abs) {
1332
+ let current = dirname(abs);
1333
+ for (let hops = 0; hops < 64; hops++) {
1334
+ if (existsSync(current)) return current;
1335
+ const parent = dirname(current);
1336
+ if (parent === current) return null;
1337
+ current = parent;
1338
+ }
1339
+ return null;
1323
1340
  }
1324
1341
  function createState() {
1325
1342
  return {
@@ -1468,6 +1485,9 @@ var plugin = {
1468
1485
  path: relativeToInvocationCwd(resolveTargetPath(path, effectiveCwd), input.cwd),
1469
1486
  kind: isRootPathScope(path) && deletesImplicitScope || isUnresolvedPathScope(path) || recursivelyDeletes && (isDirectoryAmbiguousPath(path) || hasConfiguredProtectedDescendant(path, cfg.protect)) ? "deletion-scope" : "file"
1470
1487
  };
1488
+ if (target.kind === "file" && isSymlinkEscape(target.path, input.cwd)) {
1489
+ return target;
1490
+ }
1471
1491
  if (targetFullyAllowed(target, cfg.allow, allowRes)) return null;
1472
1492
  const protectedShellTarget = await targetIntersectsPatternsGuarded(target, cfg.protect, protectRes, {
1473
1493
  onTimeout: bumpRedos
@@ -5,13 +5,13 @@ import { performance } from "node:perf_hooks";
5
5
  import {
6
6
  cloneCredentialPatterns,
7
7
  CREDENTIAL_PATTERNS
8
- } from "@wrongstack/plugin-sdk/runtime";
8
+ } from "@wrongstack/plugin-sdk/runtime/credential-patterns";
9
9
 
10
10
  // src/runtime/redos-guard.ts
11
11
  import {
12
12
  withReDoSGuard,
13
13
  guardedMatcher
14
- } from "@wrongstack/plugin-sdk/runtime";
14
+ } from "@wrongstack/plugin-sdk/runtime/redos-guard";
15
15
 
16
16
  // src/prompt-firewall/index.ts
17
17
  var KIND_ALIASES = {
@@ -7,7 +7,7 @@ import {
7
7
  runOptionalPluginCouncil,
8
8
  runOptionalPluginLlm,
9
9
  stripOuterMarkdownFence
10
- } from "@wrongstack/plugin-sdk/runtime";
10
+ } from "@wrongstack/plugin-sdk/runtime/llm";
11
11
 
12
12
  // src/release-notes-generator/index.ts
13
13
  var API_VERSION = "^0.1.10";
@@ -1,3 +1,3 @@
1
- /** Shim: implementation moved to @wrongstack/plugin-sdk/runtime. */
2
- export { cloneCredentialPatterns, CREDENTIAL_PATTERNS, type CredentialPattern, } from '@wrongstack/plugin-sdk/runtime';
1
+ /** Shim: implementation moved to @wrongstack/plugin-sdk/runtime/credential-patterns (granular). */
2
+ export { cloneCredentialPatterns, CREDENTIAL_PATTERNS, type CredentialPattern, } from '@wrongstack/plugin-sdk/runtime/credential-patterns';
3
3
  //# sourceMappingURL=credential-patterns.d.ts.map
@@ -1,3 +1,3 @@
1
- /** Shim: implementation moved to @wrongstack/plugin-sdk/runtime. */
2
- export { createH1State, type H1State } from '@wrongstack/plugin-sdk/runtime';
1
+ /** Shim: implementation moved to @wrongstack/plugin-sdk/runtime/h1-state (granular). */
2
+ export { createH1State, type H1State } from '@wrongstack/plugin-sdk/runtime/h1-state';
3
3
  //# sourceMappingURL=h1-state.d.ts.map
@@ -1,3 +1,3 @@
1
- /** Shim: implementation moved to @wrongstack/plugin-sdk/runtime. */
2
- export { releaseHandle, releaseHandles, type Unregister } from '@wrongstack/plugin-sdk/runtime';
1
+ /** Shim: implementation moved to @wrongstack/plugin-sdk/runtime/handles (granular). */
2
+ export { releaseHandle, releaseHandles, type Unregister } from '@wrongstack/plugin-sdk/runtime/handles';
3
3
  //# sourceMappingURL=handles.d.ts.map
@@ -1,10 +1,27 @@
1
1
  /**
2
2
  * Backwards-compatible re-export: the runtime helpers moved to
3
3
  * `@wrongstack/plugin-sdk/runtime` so third-party plugin authors get the
4
- * same audit-hardened helpers first-party plugins use, without depending
5
- * on the whole @wrongstack/plugins package. This shim keeps the
4
+ * same audit-hardened helpers first-party plugins use, without depending on
5
+ * the whole @wrongstack/plugins package. This shim keeps the
6
6
  * `@wrongstack/plugins/runtime` subpath (and every in-repo relative
7
7
  * import) working unchanged.
8
+ *
9
+ * Why this one still imports the BARREL while the six leaf shims
10
+ * (`redos-guard`, `sandbox`, `llm`, …) import granular
11
+ * `@wrongstack/plugin-sdk/runtime/*` entries: the language-runner helpers
12
+ * (`resolveRunnerCommand`, `sanitizeRunnerPath`, `runRunnerCommand`,
13
+ * `probeRunnerCommand`), `withinProject`, `collectSourceFiles(Async)` and
14
+ * `matchesExtension` are defined in the sdk's runtime barrel module itself.
15
+ *
16
+ * The runner-extraction unlock LANDED 2026-09-06 (round core-utils-r1):
17
+ * `@wrongstack/core/utils/child-env` imports in ~4.5ms and the plugin-sdk
18
+ * barrel uses it. The extraction was re-attempted as round sdk-runner-r2
19
+ * and REVERTED on measurement: the primary arm's win landed inside the
20
+ * noise band (path-guard 12.33 -> 11.46ms, band ~5.4ms) because the
21
+ * child-env entry already banked the barrel's cost, and the granular
22
+ * 10-resolution shim measurably regressed multi-symbol consumers
23
+ * (branch-guard 10.87 -> 16.95ms). Do not re-attempt without a new
24
+ * hypothesis; see PERF_LOG round sdk-runner-r2.
8
25
  */
9
26
  export * from '@wrongstack/plugin-sdk/runtime';
10
27
  //# sourceMappingURL=index.d.ts.map
@@ -1,3 +1,3 @@
1
- /** Shim: implementation moved to @wrongstack/plugin-sdk/runtime. */
2
- export { parseLlmJsonObject, runOptionalPluginCouncil, runOptionalPluginLlm, stripOuterMarkdownFence, type OptionalCouncilRequest, type OptionalLlmRequest, type OptionalLlmResult, } from '@wrongstack/plugin-sdk/runtime';
1
+ /** Shim: implementation moved to @wrongstack/plugin-sdk/runtime/llm (granular). */
2
+ export { parseLlmJsonObject, runOptionalPluginCouncil, runOptionalPluginLlm, stripOuterMarkdownFence, type OptionalCouncilRequest, type OptionalLlmRequest, type OptionalLlmResult, } from '@wrongstack/plugin-sdk/runtime/llm';
3
3
  //# sourceMappingURL=llm.d.ts.map
@@ -1,3 +1,3 @@
1
- /** Shim: implementation moved to @wrongstack/plugin-sdk/runtime. */
2
- export { withReDoSGuard, guardedMatcher, type ReDoSResult, type ReDoSOptions, } from '@wrongstack/plugin-sdk/runtime';
1
+ /** Shim: implementation moved to @wrongstack/plugin-sdk/runtime/redos-guard (granular). */
2
+ export { withReDoSGuard, guardedMatcher, type ReDoSResult, type ReDoSOptions, } from '@wrongstack/plugin-sdk/runtime/redos-guard';
3
3
  //# sourceMappingURL=redos-guard.d.ts.map
@@ -1,3 +1,3 @@
1
- /** Shim: implementation moved to @wrongstack/plugin-sdk/runtime. */
2
- export { safePath, isInsideProject, type SafePathOptions, } from '@wrongstack/plugin-sdk/runtime';
1
+ /** Shim: implementation moved to @wrongstack/plugin-sdk/runtime/sandbox (granular). */
2
+ export { safePath, isInsideProject, type SafePathOptions, } from '@wrongstack/plugin-sdk/runtime/sandbox';
3
3
  //# sourceMappingURL=sandbox.d.ts.map
@@ -16,7 +16,7 @@ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "defau
16
16
  import {
17
17
  cloneCredentialPatterns,
18
18
  CREDENTIAL_PATTERNS
19
- } from "@wrongstack/plugin-sdk/runtime";
19
+ } from "@wrongstack/plugin-sdk/runtime/credential-patterns";
20
20
 
21
21
  // src/runtime/index.ts
22
22
  var runtime_exports = {};
@@ -8,7 +8,7 @@ import {
8
8
  runOptionalPluginCouncil,
9
9
  runOptionalPluginLlm,
10
10
  stripOuterMarkdownFence
11
- } from "@wrongstack/plugin-sdk/runtime";
11
+ } from "@wrongstack/plugin-sdk/runtime/llm";
12
12
 
13
13
  // src/test-generator/index.ts
14
14
  var API_VERSION = "^0.1.10";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/plugins",
3
- "version": "0.319.1",
3
+ "version": "1.0.0",
4
4
  "description": "Official WrongStack collection of focused plugins for code quality, security, observability, planning, and agent coordination",
5
5
  "license": "MIT",
6
6
  "author": "ECOSTACK TECHNOLOGY OÜ",
@@ -303,10 +303,10 @@
303
303
  "vitest": "^4.1.11"
304
304
  },
305
305
  "dependencies": {
306
- "@wrongstack/core": "0.319.1",
307
- "@wrongstack/primitives": "0.319.1",
308
- "@wrongstack/plugin-sdk": "0.319.1",
309
- "@wrongstack/tools": "0.319.1"
306
+ "@wrongstack/core": "1.0.0",
307
+ "@wrongstack/primitives": "1.0.0",
308
+ "@wrongstack/plugin-sdk": "1.0.0",
309
+ "@wrongstack/tools": "1.0.0"
310
310
  },
311
311
  "scripts": {
312
312
  "build": "node ../../scripts/build-package.mjs",