@wrongstack/tools 0.305.1 → 0.306.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 (61) hide show
  1. package/dist/_shell-pick.d.ts +4 -5
  2. package/dist/_util.d.ts +22 -5
  3. package/dist/audit.d.ts +0 -1
  4. package/dist/audit.js +135 -46
  5. package/dist/bash.js +61 -37
  6. package/dist/browser/index.js +29 -9
  7. package/dist/browser/types.d.ts +7 -1
  8. package/dist/builtin.js +1279 -726
  9. package/dist/codebase-index/codebase-search-tool.d.ts +5 -0
  10. package/dist/codebase-index/index.js +223 -152
  11. package/dist/diff.d.ts +5 -0
  12. package/dist/diff.js +78 -12
  13. package/dist/document.js +18 -6
  14. package/dist/edit.js +69 -16
  15. package/dist/exec.js +44 -22
  16. package/dist/fetch.js +13 -1
  17. package/dist/format.d.ts +4 -2
  18. package/dist/format.js +81 -31
  19. package/dist/glob.js +12 -4
  20. package/dist/grep.d.ts +2 -0
  21. package/dist/grep.js +15 -4
  22. package/dist/index.js +1342 -761
  23. package/dist/install.js +96 -37
  24. package/dist/kanban-tool-types.d.ts +6 -1
  25. package/dist/kanban.js +60 -0
  26. package/dist/languages/index.js +28 -13
  27. package/dist/lint.js +28 -13
  28. package/dist/logs.d.ts +0 -1
  29. package/dist/logs.js +44 -13
  30. package/dist/memory.d.ts +8 -0
  31. package/dist/memory.js +23 -3
  32. package/dist/mode.d.ts +1 -1
  33. package/dist/mode.js +3 -0
  34. package/dist/next-steps.d.ts +2 -3
  35. package/dist/next-steps.js +3 -3
  36. package/dist/outdated.d.ts +0 -3
  37. package/dist/outdated.js +89 -48
  38. package/dist/pack.js +1279 -726
  39. package/dist/plan.js +76 -3
  40. package/dist/process-registry.d.ts +8 -2
  41. package/dist/process-registry.js +28 -13
  42. package/dist/ps-slash.js +22 -12
  43. package/dist/read.js +10 -3
  44. package/dist/replace.d.ts +4 -0
  45. package/dist/replace.js +104 -7
  46. package/dist/search.d.ts +6 -0
  47. package/dist/search.js +47 -26
  48. package/dist/skill.d.ts +6 -0
  49. package/dist/skill.js +9 -10
  50. package/dist/task.js +66 -2
  51. package/dist/test.js +28 -13
  52. package/dist/todo.js +64 -2
  53. package/dist/tool-icons.js +4 -2
  54. package/dist/tool-summary.d.ts +1 -1
  55. package/dist/tool-summary.js +76 -1
  56. package/dist/tool-tier.js +1279 -726
  57. package/dist/tree.js +9 -10
  58. package/dist/typecheck.d.ts +0 -2
  59. package/dist/typecheck.js +98 -31
  60. package/dist/write.js +58 -10
  61. package/package.json +4 -4
package/dist/tree.js CHANGED
@@ -47,6 +47,7 @@ var treeTool = {
47
47
  mutating: false,
48
48
  capabilities: ["fs.read"],
49
49
  icon: "tree",
50
+ maxOutputBytes: 262144,
50
51
  timeoutMs: 15e3,
51
52
  inputSchema: {
52
53
  type: "object",
@@ -195,17 +196,15 @@ async function walkDir(dir, depth, opts) {
195
196
  if (opts.exclude.has(e.name)) return false;
196
197
  return true;
197
198
  });
198
- if (depth > 0) {
199
- let dirCount = 0;
200
- let fileCount = 0;
201
- for (const e of filtered) {
202
- if (e.isDirectory()) dirCount++;
203
- else if (e.isFile()) fileCount++;
204
- }
205
- opts.totalDirs.value += dirCount;
206
- opts.totalFiles.value += fileCount;
207
- opts.onProgress?.();
199
+ let dirCount = 0;
200
+ let fileCount = 0;
201
+ for (const e of filtered) {
202
+ if (e.isDirectory()) dirCount++;
203
+ else if (e.isFile()) fileCount++;
208
204
  }
205
+ opts.totalDirs.value += dirCount;
206
+ opts.totalFiles.value += fileCount;
207
+ opts.onProgress?.();
209
208
  const items = filtered.sort((a, b) => {
210
209
  if (a.isDirectory() && !b.isDirectory()) return -1;
211
210
  if (!a.isDirectory() && b.isDirectory()) return 1;
@@ -4,8 +4,6 @@ interface TypecheckInput {
4
4
  cwd?: string | undefined;
5
5
  strict?: boolean | undefined;
6
6
  all?: boolean | undefined;
7
- /** Emit JSON for machine-readable output (default: false). */
8
- json?: boolean | undefined;
9
7
  }
10
8
  interface TypecheckOutput {
11
9
  project: string;
package/dist/typecheck.js CHANGED
@@ -367,8 +367,11 @@ var SENSITIVE_FLAG_PATTERNS = [
367
367
  /--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\s,][^\s]*)?/gi,
368
368
  // -t short flag (token): attached (-tVALUE), separated (-t VALUE), or -t=VALUE.
369
369
  // (?<![-\w]) anchors to a token start so we don't match the `-t` inside `--token`.
370
+ // The value must be token-like (>= 8 chars) so ordinary combined flags such
371
+ // as `tar -tf` / `ssh -tt` are not eaten. Global flag: EVERY occurrence is
372
+ // redacted, not just the first.
370
373
  // NOTE: synced with @wrongstack/core observability/redact-command.ts.
371
- /(?<![-\w])-t(?:[=\s]+)?[^\s,-]+/,
374
+ /(?<![-\w])-t(?:[=\s]+)?[^\s,-]{8,}/g,
372
375
  // -p|-password|-a (redis auth) short flags: attached + separated + =value.
373
376
  // Same token-start anchor; over-redaction is an accepted tradeoff for a
374
377
  // redaction function. Synced with core copy.
@@ -376,8 +379,9 @@ var SENSITIVE_FLAG_PATTERNS = [
376
379
  // env var–style secrets: TOKEN=x, API_KEY=y, etc.
377
380
  /(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
378
381
  // Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
379
- // when preceded by a flag name (e.g. --github-token=EyJ...).
380
- /--\w*(?:token|key|secret|password|passwd|auth|credential)\w*[=\s,][A-Za-z0-9+/=]{32,}/
382
+ // when preceded by a flag name (e.g. --github-token=EyJ...). Global flag so
383
+ // every such flag in the command line is redacted, not just the first.
384
+ /--\w*(?:token|key|secret|password|passwd|auth|credential)\w*[=\s,][A-Za-z0-9+/=]{32,}/g
381
385
  ];
382
386
  function redactCommand(cmd) {
383
387
  let result = cmd;
@@ -466,11 +470,15 @@ var ProcessRegistryImpl = class {
466
470
  return Number.isInteger(pid) && pid > 1 && pid !== process.pid && pid !== process.ppid;
467
471
  }
468
472
  _canSignalProcessGroup(p) {
469
- return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && typeof p.child.pid === "number" && p.child.pid === p.pid;
473
+ return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && p.child !== null && typeof p.child.pid === "number" && p.child.pid === p.pid;
470
474
  }
471
475
  _killChildDirect(p, signal) {
472
476
  try {
473
- p.child.kill(signal);
477
+ if (p.child) {
478
+ p.child.kill(signal);
479
+ return;
480
+ }
481
+ if (this._isSafeSignalPid(p.pid)) process.kill(p.pid, signal);
474
482
  } catch {
475
483
  }
476
484
  }
@@ -668,15 +676,15 @@ var ProcessRegistryImpl = class {
668
676
  this._pruneStale(pid);
669
677
  const p = this.processes.get(pid);
670
678
  if (!p) return false;
671
- if (p.killed) return true;
679
+ if (p.killed && opts.force !== true) return true;
672
680
  if (p.protected && opts.includeProtected !== true) return false;
673
681
  if (opts.preserveBackground && p.background) return false;
674
682
  const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;
675
683
  const isWin2 = os.platform() === "win32";
676
684
  if (isWin2) {
677
- const liveRealChild = p.child.exitCode === null && typeof p.child.pid === "number";
685
+ const liveRealChild = p.child === null || p.child.exitCode === null && typeof p.child.pid === "number";
678
686
  const directFallback = () => {
679
- if (p.child.exitCode === null) {
687
+ if (p.child && p.child.exitCode === null) {
680
688
  try {
681
689
  p.child.kill("SIGKILL");
682
690
  } catch {
@@ -688,10 +696,7 @@ var ProcessRegistryImpl = class {
688
696
  onSettled: directFallback
689
697
  })) {
690
698
  } else {
691
- try {
692
- p.child.kill(force ? "SIGKILL" : "SIGTERM");
693
- } catch {
694
- }
699
+ this._killChildDirect(p, force ? "SIGKILL" : "SIGTERM");
695
700
  }
696
701
  p.killed = true;
697
702
  return true;
@@ -702,7 +707,7 @@ var ProcessRegistryImpl = class {
702
707
  } else {
703
708
  this._killPosix(p, "SIGTERM");
704
709
  const timer = setTimeout(() => {
705
- if (this.processes.has(pid) && !p.child.killed) {
710
+ if (this.processes.has(pid) && !p.child?.killed) {
706
711
  this._killPosix(p, "SIGKILL");
707
712
  }
708
713
  }, graceMs);
@@ -757,6 +762,16 @@ var ProcessRegistryImpl = class {
757
762
  * before reusing a PID, but we want to clean up before that becomes a risk.
758
763
  */
759
764
  _isStaleEntry(entry) {
765
+ if (entry.child === null) {
766
+ if (Date.now() - entry.startedAt <= 6e4) return false;
767
+ if (os.platform() === "win32") return false;
768
+ try {
769
+ process.kill(entry.pid, 0);
770
+ return false;
771
+ } catch (err) {
772
+ return err.code !== "EPERM";
773
+ }
774
+ }
760
775
  return entry.child.exitCode !== null && Date.now() - entry.startedAt > 6e4;
761
776
  }
762
777
  /**
@@ -1031,6 +1046,49 @@ function utf8Prefix(text, maxBytes) {
1031
1046
  // src/_util.ts
1032
1047
  import * as path3 from "node:path";
1033
1048
  import * as Core from "@wrongstack/core/utils";
1049
+ async function detectPackageManager(cwd, stopAt) {
1050
+ let dir = path3.resolve(cwd);
1051
+ const stop = stopAt ? path3.resolve(stopAt) : dir;
1052
+ for (; ; ) {
1053
+ const found = await detectPackageManagerInDir(dir);
1054
+ if (found) return found;
1055
+ if (dir === stop) break;
1056
+ const parent = path3.dirname(dir);
1057
+ const relParent = path3.relative(stop, parent);
1058
+ if (parent === dir || relParent.startsWith("..") || path3.isAbsolute(relParent)) break;
1059
+ dir = parent;
1060
+ }
1061
+ return "npm";
1062
+ }
1063
+ async function detectPackageManagerInDir(dir) {
1064
+ const fs6 = await import("node:fs/promises");
1065
+ try {
1066
+ const raw = await fs6.readFile(path3.join(dir, "package.json"), "utf8");
1067
+ const declared = JSON.parse(raw).packageManager;
1068
+ if (typeof declared === "string") {
1069
+ const name = declared.split("@")[0] ?? "";
1070
+ if (name === "pnpm" || name === "yarn") return name;
1071
+ if (name === "npm" || name === "bun") return "npm";
1072
+ }
1073
+ } catch {
1074
+ }
1075
+ const lockfiles = [
1076
+ ["pnpm-lock.yaml", "pnpm"],
1077
+ ["yarn.lock", "yarn"],
1078
+ ["bun.lockb", "npm"],
1079
+ ["bun.lock", "npm"],
1080
+ ["package-lock.json", "npm"],
1081
+ ["npm-shrinkwrap.json", "npm"]
1082
+ ];
1083
+ for (const [file, manager] of lockfiles) {
1084
+ try {
1085
+ await fs6.stat(`${dir}/${file}`);
1086
+ return manager;
1087
+ } catch {
1088
+ }
1089
+ }
1090
+ return null;
1091
+ }
1034
1092
  function resolvePath(input, ctx) {
1035
1093
  return path3.isAbsolute(input) ? path3.normalize(input) : path3.resolve(ctx.workingDir ?? ctx.cwd, input);
1036
1094
  }
@@ -2665,7 +2723,7 @@ async function finalizeCandidate(candidate, projectRoot) {
2665
2723
  const evidence = dedupeEvidence(candidate.evidence).sort(compareEvidence);
2666
2724
  const manifests = [...new Set(candidate.manifests)].sort();
2667
2725
  const confidence = Math.min(1, evidence.reduce((sum, item) => sum + item.weight, 0) / 100);
2668
- const packageManager = await detectPackageManager(candidate.profile, candidate.root, evidence);
2726
+ const packageManager = await detectPackageManager2(candidate.profile, candidate.root, evidence);
2669
2727
  const id = createHash("sha256").update(`${candidate.profile.id}\0${path4.relative(projectRoot, candidate.root)}`).digest("hex").slice(0, 16);
2670
2728
  return Object.freeze({
2671
2729
  id,
@@ -2680,7 +2738,7 @@ async function finalizeCandidate(candidate, projectRoot) {
2680
2738
  )
2681
2739
  });
2682
2740
  }
2683
- async function detectPackageManager(profile, root, evidence) {
2741
+ async function detectPackageManager2(profile, root, evidence) {
2684
2742
  if (profile.packageManagers.length === 1) return profile.packageManagers[0];
2685
2743
  if (profile.id !== "typescript" && profile.id !== "javascript") return void 0;
2686
2744
  let declared;
@@ -3556,11 +3614,7 @@ var typecheckTool = {
3556
3614
  },
3557
3615
  all: {
3558
3616
  type: "boolean",
3559
- description: "Type-check all projects (pnpm -r) (default: false)"
3560
- },
3561
- json: {
3562
- type: "boolean",
3563
- description: "Emit JSON output from tsc (default: false)"
3617
+ description: "Type-check all workspace packages (pnpm workspaces run `pnpm -r exec tsc --noEmit`; other setups run a single `tsc --noEmit` at cwd) (default: false)"
3564
3618
  }
3565
3619
  }
3566
3620
  },
@@ -3596,29 +3650,42 @@ var typecheckTool = {
3596
3650
  };
3597
3651
  return;
3598
3652
  }
3599
- let args;
3653
+ let cmd;
3654
+ let cmdArgs;
3600
3655
  let project;
3601
3656
  if (input.all) {
3602
- args = ["--noEmit"];
3603
3657
  project = "workspace";
3658
+ const tscArgs = ["--noEmit"];
3659
+ if (input.strict) tscArgs.push("--strict");
3660
+ const manager = await detectPackageManager(cwd, ctx.projectRoot);
3661
+ if (manager === "pnpm") {
3662
+ cmd = "pnpm";
3663
+ cmdArgs = ["-r", "--no-bail", "exec", "tsc", ...tscArgs];
3664
+ } else {
3665
+ cmd = "npx";
3666
+ cmdArgs = ["tsc", ...tscArgs];
3667
+ }
3604
3668
  } else {
3605
3669
  const tsconfig = input.project ? safeResolve(input.project, ctx) : await findTsConfig(cwd);
3606
- args = ["--noEmit"];
3607
- if (input.strict) args.push("--strict");
3608
- if (tsconfig) args.push("--project", tsconfig);
3670
+ const tscArgs = ["--noEmit"];
3671
+ if (input.strict) tscArgs.push("--strict");
3672
+ if (tsconfig) tscArgs.push("--project", tsconfig);
3609
3673
  project = tsconfig ?? "default";
3674
+ cmd = "npx";
3675
+ cmdArgs = ["tsc", ...tscArgs];
3610
3676
  }
3611
- if (input.json) args.push("--json");
3612
- yield { type: "log", text: `tsc ${args.join(" ")}`, data: { project } };
3677
+ yield { type: "log", text: `${cmd} ${cmdArgs.join(" ")}`, data: { project } };
3613
3678
  const result = yield* spawnStream({
3614
- cmd: "npx",
3615
- args: ["tsc", ...args],
3679
+ cmd,
3680
+ args: cmdArgs,
3616
3681
  cwd,
3617
3682
  signal: opts.signal,
3618
3683
  maxBytes: 2e5
3619
3684
  });
3620
- const errors = [...result.stdout.matchAll(/\berror\b/gi)].length;
3621
- const warnings = [...result.stdout.matchAll(/\bwarning\b/gi)].length;
3685
+ const combined = `${result.stdout}
3686
+ ${result.stderr}`;
3687
+ const errors = [...combined.matchAll(/^.*\berror TS\d+:/gm)].length;
3688
+ const warnings = [...combined.matchAll(/^.*\bwarning TS\d+:/gm)].length;
3622
3689
  yield {
3623
3690
  type: "final",
3624
3691
  output: {
package/dist/write.js CHANGED
@@ -1,7 +1,13 @@
1
1
  // src/write.ts
2
2
  import * as fs from "node:fs/promises";
3
3
  import { ToolValidationError } from "@wrongstack/core/types";
4
- import { atomicWrite, unifiedDiff } from "@wrongstack/core/utils";
4
+ import {
5
+ atomicWrite,
6
+ detectNewlineStyle,
7
+ normalizeToLf,
8
+ toStyle,
9
+ unifiedDiff
10
+ } from "@wrongstack/core/utils";
5
11
 
6
12
  // src/_syntax-check.ts
7
13
  import * as path from "node:path";
@@ -150,20 +156,51 @@ async function safeResolveReal(input, ctx) {
150
156
  const abs = safeResolve(input, ctx);
151
157
  return await resolveRealInsideRoot(abs, ctx);
152
158
  }
159
+ function truncateDiffPayload(diff, maxBytes) {
160
+ const total = Buffer.byteLength(diff, "utf8");
161
+ if (total <= maxBytes) return { text: diff, truncated: false };
162
+ const MARKER_RESERVE = 96;
163
+ let head = takeHeadBytes(diff, Math.max(0, maxBytes - MARKER_RESERVE));
164
+ const nl = head.lastIndexOf("\n");
165
+ if (nl > 0) head = head.slice(0, nl);
166
+ const kept = Buffer.byteLength(head, "utf8");
167
+ return {
168
+ text: `${head}
169
+ \u2026[diff truncated: ${total - kept} of ${total} bytes omitted]`,
170
+ truncated: true
171
+ };
172
+ }
173
+ function takeHeadBytes(s, maxBytes) {
174
+ if (maxBytes <= 0) return "";
175
+ if (Buffer.byteLength(s, "utf8") <= maxBytes) return s;
176
+ let lo = 0;
177
+ let hi = s.length;
178
+ while (lo < hi) {
179
+ const mid = Math.ceil((lo + hi) / 2);
180
+ if (Buffer.byteLength(s.slice(0, mid), "utf8") <= maxBytes) lo = mid;
181
+ else hi = mid - 1;
182
+ }
183
+ return s.slice(0, lo);
184
+ }
153
185
 
154
186
  // src/write.ts
187
+ var MAX_DIFF_BYTES = 262144;
155
188
  var writeTool = {
156
189
  name: "write",
157
190
  category: "Filesystem",
158
191
  description: "Write or completely overwrite a file on disk. This is a high-privilege operation. For modifying existing files, you should almost always prefer the `edit` tool instead, because `edit` is safer and works on the last-read version of the file.",
159
- usageHint: "RULES FOR CORRECT USAGE:\n- Use `write` primarily for **new files** or when you want to replace the entire content.\n- For any existing file, strongly prefer `edit` (it requires a prior `read` in the same session and is more precise).\n- You MUST have called `read` on the file earlier in the conversation before using `write` on an existing path (the system enforces this for safety).\n- The path is resolved relative to the project root and protected against escaping the workspace.",
192
+ usageHint: "RULES FOR CORRECT USAGE:\n- Use `write` primarily for **new files** or when you want to replace the entire content.\n- For any existing file, strongly prefer `edit` (it requires a prior `read` in the same session and is more precise).\n- When overwriting an existing file, the tool reads the current content itself to compute the diff \u2014 but still `read` the file first before large rewrites so you know what you are replacing.\n- When overwriting an existing file, the content is normalized to the dominant line-ending style (CRLF/LF) of the existing file; new files are written verbatim.\n- The path is resolved relative to the project root and protected against escaping the workspace.",
160
193
  selection: {
161
194
  doNotUseWhen: "making a precise change to part of an existing file.",
162
195
  useInstead: ["edit"]
163
196
  },
164
197
  permission: "confirm",
198
+ // WS-046: gives permission decisions something to key on — the file being
199
+ // written, so trust rules can scope by path.
200
+ subjectKey: "path",
165
201
  mutating: true,
166
202
  timeoutMs: 5e3,
203
+ maxOutputBytes: 262144,
167
204
  capabilities: ["fs.write"],
168
205
  icon: "file",
169
206
  inputSchema: {
@@ -232,31 +269,42 @@ async function prepareWrite(input, ctx) {
232
269
  return { absPath, existed, prev };
233
270
  }
234
271
  async function finishWrite(input, ctx, prepared, signal) {
272
+ const content = prepared.existed ? toStyle(normalizeToLf(input.content), detectNewlineStyle(prepared.prev)) : input.content;
235
273
  signal?.throwIfAborted();
236
- await atomicWrite(prepared.absPath, input.content);
237
- const diff = prepared.existed ? unifiedDiff(prepared.prev, input.content, { fromFile: input.path, toFile: input.path }) : `+++ ${input.path}
238
- + (new file, ${input.content.split("\n").length} lines)`;
274
+ await atomicWrite(prepared.absPath, content);
275
+ const rawDiff = prepared.existed ? unifiedDiff(prepared.prev, content, { fromFile: input.path, toFile: input.path }) : `+++ ${input.path}
276
+ + (new file, ${content.split("\n").length} lines)`;
277
+ const { text: diff, truncated: diffTruncated } = truncateDiffPayload(rawDiff, MAX_DIFF_BYTES);
239
278
  const stat2 = await fs.stat(prepared.absPath);
240
- ctx.recordRead(prepared.absPath, stat2.mtimeMs, "write", sha256hex(input.content));
279
+ ctx.recordRead(prepared.absPath, stat2.mtimeMs, "write", sha256hex(content));
241
280
  ctx.session.recordFileChange({
242
281
  path: prepared.absPath,
243
282
  action: prepared.existed ? "modified" : "created",
244
283
  before: prepared.existed ? prepared.prev : null,
245
- after: input.content
284
+ after: content
246
285
  });
247
286
  const syntax = await checkSyntax(
248
287
  prepared.absPath,
249
- input.content,
288
+ content,
250
289
  prepared.existed ? prepared.prev : void 0
251
290
  ).catch(() => void 0);
252
291
  const hasSyntaxErrors = syntax !== void 0 && syntax.errors.length > 0;
292
+ const notes = [];
293
+ if (diffTruncated) {
294
+ notes.push("Diff truncated to the 256 KiB output budget \u2014 the full write is on disk.");
295
+ }
296
+ if (hasSyntaxErrors) {
297
+ notes.push(
298
+ syntax.preExisting ? "Syntax check: the file still has parse errors (they pre-date this write) \u2014 see syntax_errors." : `Syntax check: the written content has ${syntax.errors.length} parse error(s) \u2014 fix them now, see syntax_errors.`
299
+ );
300
+ }
253
301
  return {
254
302
  path: prepared.absPath,
255
- bytes_written: Buffer.byteLength(input.content, "utf8"),
303
+ bytes_written: Buffer.byteLength(content, "utf8"),
256
304
  created: !prepared.existed,
257
305
  diff,
258
306
  syntax_errors: hasSyntaxErrors ? syntax.errors : void 0,
259
- note: hasSyntaxErrors ? syntax.preExisting ? "Syntax check: the file still has parse errors (they pre-date this write) \u2014 see syntax_errors." : `Syntax check: the written content has ${syntax.errors.length} parse error(s) \u2014 fix them now, see syntax_errors.` : void 0
307
+ note: notes.length > 0 ? notes.join("\n") : void 0
260
308
  };
261
309
  }
262
310
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/tools",
3
- "version": "0.305.1",
3
+ "version": "0.306.0",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack built-in tools: read/write/edit, bash/exec, grep/glob, git, fetch, test, lint, and more.",
6
6
  "repository": {
@@ -253,9 +253,9 @@
253
253
  "turndown": "^7.2.4",
254
254
  "undici": "^8.9.0",
255
255
  "web-tree-sitter": "0.26.12",
256
- "@wrongstack/kanban": "0.305.1",
257
- "@wrongstack/core": "0.305.1",
258
- "@wrongstack/persistence": "0.305.1"
256
+ "@wrongstack/core": "0.306.0",
257
+ "@wrongstack/persistence": "0.306.0",
258
+ "@wrongstack/kanban": "0.306.0"
259
259
  },
260
260
  "devDependencies": {
261
261
  "@types/node": "^26.1.2",