@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.
- package/dist/_shell-pick.d.ts +4 -5
- package/dist/_util.d.ts +22 -5
- package/dist/audit.d.ts +0 -1
- package/dist/audit.js +135 -46
- package/dist/bash.js +61 -37
- package/dist/browser/index.js +29 -9
- package/dist/browser/types.d.ts +7 -1
- package/dist/builtin.js +1279 -726
- package/dist/codebase-index/codebase-search-tool.d.ts +5 -0
- package/dist/codebase-index/index.js +223 -152
- package/dist/diff.d.ts +5 -0
- package/dist/diff.js +78 -12
- package/dist/document.js +18 -6
- package/dist/edit.js +69 -16
- package/dist/exec.js +44 -22
- package/dist/fetch.js +13 -1
- package/dist/format.d.ts +4 -2
- package/dist/format.js +81 -31
- package/dist/glob.js +12 -4
- package/dist/grep.d.ts +2 -0
- package/dist/grep.js +15 -4
- package/dist/index.js +1342 -761
- package/dist/install.js +96 -37
- package/dist/kanban-tool-types.d.ts +6 -1
- package/dist/kanban.js +60 -0
- package/dist/languages/index.js +28 -13
- package/dist/lint.js +28 -13
- package/dist/logs.d.ts +0 -1
- package/dist/logs.js +44 -13
- package/dist/memory.d.ts +8 -0
- package/dist/memory.js +23 -3
- package/dist/mode.d.ts +1 -1
- package/dist/mode.js +3 -0
- package/dist/next-steps.d.ts +2 -3
- package/dist/next-steps.js +3 -3
- package/dist/outdated.d.ts +0 -3
- package/dist/outdated.js +89 -48
- package/dist/pack.js +1279 -726
- package/dist/plan.js +76 -3
- package/dist/process-registry.d.ts +8 -2
- package/dist/process-registry.js +28 -13
- package/dist/ps-slash.js +22 -12
- package/dist/read.js +10 -3
- package/dist/replace.d.ts +4 -0
- package/dist/replace.js +104 -7
- package/dist/search.d.ts +6 -0
- package/dist/search.js +47 -26
- package/dist/skill.d.ts +6 -0
- package/dist/skill.js +9 -10
- package/dist/task.js +66 -2
- package/dist/test.js +28 -13
- package/dist/todo.js +64 -2
- package/dist/tool-icons.js +4 -2
- package/dist/tool-summary.d.ts +1 -1
- package/dist/tool-summary.js +76 -1
- package/dist/tool-tier.js +1279 -726
- package/dist/tree.js +9 -10
- package/dist/typecheck.d.ts +0 -2
- package/dist/typecheck.js +98 -31
- package/dist/write.js +58 -10
- 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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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;
|
package/dist/typecheck.d.ts
CHANGED
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
3607
|
-
if (input.strict)
|
|
3608
|
-
if (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
|
-
|
|
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
|
|
3615
|
-
args:
|
|
3679
|
+
cmd,
|
|
3680
|
+
args: cmdArgs,
|
|
3616
3681
|
cwd,
|
|
3617
3682
|
signal: opts.signal,
|
|
3618
3683
|
maxBytes: 2e5
|
|
3619
3684
|
});
|
|
3620
|
-
const
|
|
3621
|
-
|
|
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 {
|
|
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-
|
|
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,
|
|
237
|
-
const
|
|
238
|
-
+ (new file, ${
|
|
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(
|
|
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:
|
|
284
|
+
after: content
|
|
246
285
|
});
|
|
247
286
|
const syntax = await checkSyntax(
|
|
248
287
|
prepared.absPath,
|
|
249
|
-
|
|
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(
|
|
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:
|
|
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.
|
|
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/
|
|
257
|
-
"@wrongstack/
|
|
258
|
-
"@wrongstack/
|
|
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",
|