@wrongstack/tools 0.305.0 → 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 +1280 -728
- package/dist/codebase-index/codebase-search-tool.d.ts +5 -0
- package/dist/codebase-index/index.d.ts +1 -0
- package/dist/codebase-index/index.js +225 -153
- package/dist/codebase-index/project-server-endpoint.d.ts +1 -2
- package/dist/codebase-index/project-server.js +19 -20
- 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 +1357 -765
- 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 +1280 -728
- 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 -4
- 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/session-kanban.d.ts +25 -0
- package/dist/session-kanban.js +16 -0
- 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 +1280 -728
- 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 -3
package/dist/plan.js
CHANGED
|
@@ -1390,6 +1390,18 @@ var KANBAN_INPUT_SCHEMA = {
|
|
|
1390
1390
|
},
|
|
1391
1391
|
transitionAction: { type: "string" },
|
|
1392
1392
|
transitionComment: { type: "string" },
|
|
1393
|
+
tickChecks: {
|
|
1394
|
+
type: "array",
|
|
1395
|
+
items: {
|
|
1396
|
+
type: "object",
|
|
1397
|
+
properties: {
|
|
1398
|
+
checkId: { type: "string" },
|
|
1399
|
+
checkStatus: { type: "string", enum: ["passed", "failed", "skipped"] }
|
|
1400
|
+
},
|
|
1401
|
+
required: ["checkId", "checkStatus"]
|
|
1402
|
+
},
|
|
1403
|
+
description: "`transition_task` (to=done only): flip one or more manual criteria to `passed` before the gate fires. Read ids from kanban get_task. Non-manual criteria are refused."
|
|
1404
|
+
},
|
|
1393
1405
|
attachmentUrl: { type: "string" },
|
|
1394
1406
|
attachmentTitle: { type: "string" },
|
|
1395
1407
|
attachmentType: {
|
|
@@ -1577,6 +1589,9 @@ var kanbanTool = {
|
|
|
1577
1589
|
description: KANBAN_TOOL_DESCRIPTION,
|
|
1578
1590
|
usageHint: KANBAN_TOOL_USAGE_HINT,
|
|
1579
1591
|
permission: "confirm",
|
|
1592
|
+
// WS-046: gives permission decisions something to key on.
|
|
1593
|
+
// The action performed; kanban has no single file or path subject.
|
|
1594
|
+
subjectKey: "action",
|
|
1580
1595
|
mutating: true,
|
|
1581
1596
|
capabilities: ["fs.write"],
|
|
1582
1597
|
icon: "task",
|
|
@@ -2018,6 +2033,7 @@ var kanbanTool = {
|
|
|
2018
2033
|
actor: input.author,
|
|
2019
2034
|
comment: input.transitionComment,
|
|
2020
2035
|
...input.transitionAction !== void 0 ? { action: input.transitionAction } : {},
|
|
2036
|
+
...input.tickChecks !== void 0 ? { tickChecks: input.tickChecks } : {},
|
|
2021
2037
|
...input.attachmentUrl !== void 0 ? {
|
|
2022
2038
|
attachment: {
|
|
2023
2039
|
url: input.attachmentUrl,
|
|
@@ -2410,8 +2426,52 @@ var kanbanTool = {
|
|
|
2410
2426
|
} catch (err) {
|
|
2411
2427
|
return fail(stripLifecycleIssues(err instanceof Error ? err.message : String(err)));
|
|
2412
2428
|
}
|
|
2429
|
+
},
|
|
2430
|
+
serialize(output, input) {
|
|
2431
|
+
return serializeKanbanOutput(output, input);
|
|
2413
2432
|
}
|
|
2414
2433
|
};
|
|
2434
|
+
var KANBAN_BOARD_TRANSCRIPT_BYTE_CAP = 16384;
|
|
2435
|
+
var KANBAN_FULL_BOARD_ACTIONS = /* @__PURE__ */ new Set([
|
|
2436
|
+
"get_board",
|
|
2437
|
+
"export_markdown",
|
|
2438
|
+
"export_task_graph"
|
|
2439
|
+
]);
|
|
2440
|
+
function serializeKanbanOutput(output, input) {
|
|
2441
|
+
const action = input && typeof input === "object" ? input.action : void 0;
|
|
2442
|
+
const board = output.board;
|
|
2443
|
+
if (board) {
|
|
2444
|
+
const keepFull = typeof action === "string" && KANBAN_FULL_BOARD_ACTIONS.has(action);
|
|
2445
|
+
let boardBytes = 0;
|
|
2446
|
+
if (!keepFull) {
|
|
2447
|
+
try {
|
|
2448
|
+
boardBytes = Buffer.byteLength(JSON.stringify(board), "utf8");
|
|
2449
|
+
} catch {
|
|
2450
|
+
boardBytes = 0;
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
if (!keepFull && boardBytes > KANBAN_BOARD_TRANSCRIPT_BYTE_CAP) {
|
|
2454
|
+
const columns = {};
|
|
2455
|
+
for (const column of board.columns) {
|
|
2456
|
+
columns[column.title || column.id] = board.tasks.filter(
|
|
2457
|
+
(task) => task.columnId === column.id
|
|
2458
|
+
).length;
|
|
2459
|
+
}
|
|
2460
|
+
const compact = {
|
|
2461
|
+
...output,
|
|
2462
|
+
board: {
|
|
2463
|
+
id: board.id,
|
|
2464
|
+
title: board.title,
|
|
2465
|
+
columns,
|
|
2466
|
+
totalTasks: board.tasks.length,
|
|
2467
|
+
note: `Full board (${boardBytes} bytes) omitted from the transcript; use get_board to load it.`
|
|
2468
|
+
}
|
|
2469
|
+
};
|
|
2470
|
+
return JSON.stringify(compact, null, 2);
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
return JSON.stringify(output, null, 2);
|
|
2474
|
+
}
|
|
2415
2475
|
|
|
2416
2476
|
// src/todo.ts
|
|
2417
2477
|
function normalizedTitle(value) {
|
|
@@ -2750,7 +2810,8 @@ var todoTool = {
|
|
|
2750
2810
|
}
|
|
2751
2811
|
for (const planId of completedPlanIds) {
|
|
2752
2812
|
if (pendingPlanIds.has(planId)) continue;
|
|
2753
|
-
const
|
|
2813
|
+
const meta = ctx.meta;
|
|
2814
|
+
const planPath = meta["plan.path.resolved"] ?? meta["plan.path"];
|
|
2754
2815
|
if (typeof planPath !== "string" || !planPath) continue;
|
|
2755
2816
|
try {
|
|
2756
2817
|
const plan = await loadPlan2(planPath);
|
|
@@ -2763,7 +2824,8 @@ var todoTool = {
|
|
|
2763
2824
|
}
|
|
2764
2825
|
for (const taskId of completedTaskIds) {
|
|
2765
2826
|
if (pendingTaskIds.has(taskId)) continue;
|
|
2766
|
-
const
|
|
2827
|
+
const meta = ctx.meta;
|
|
2828
|
+
const taskPath = meta["task.path.resolved"] ?? meta["task.path"];
|
|
2767
2829
|
if (typeof taskPath !== "string" || !taskPath) continue;
|
|
2768
2830
|
try {
|
|
2769
2831
|
const file = await loadTasks3(taskPath);
|
|
@@ -2879,7 +2941,16 @@ var planTool = {
|
|
|
2879
2941
|
sessionPlanPath.lastIndexOf("/"),
|
|
2880
2942
|
sessionPlanPath.lastIndexOf("\\")
|
|
2881
2943
|
);
|
|
2882
|
-
|
|
2944
|
+
if (lastSep < 0) {
|
|
2945
|
+
return {
|
|
2946
|
+
ok: false,
|
|
2947
|
+
message: `Cannot derive the project-scoped plan path: session plan path "${sessionPlanPath}" has no directory component.`,
|
|
2948
|
+
plan: "",
|
|
2949
|
+
count: 0,
|
|
2950
|
+
open: 0
|
|
2951
|
+
};
|
|
2952
|
+
}
|
|
2953
|
+
planPath = sessionPlanPath.slice(0, lastSep + 1) + "backlog.plan.json";
|
|
2883
2954
|
}
|
|
2884
2955
|
} else {
|
|
2885
2956
|
planPath = sessionPlanPath;
|
|
@@ -3072,6 +3143,7 @@ var planTool = {
|
|
|
3072
3143
|
open: 0
|
|
3073
3144
|
};
|
|
3074
3145
|
}
|
|
3146
|
+
ctx.meta["plan.path.resolved"] = planPath;
|
|
3075
3147
|
await projectSessionPlanToKanban(ctx.projectRoot, plan.items, sessionId);
|
|
3076
3148
|
if (todosToReplace) {
|
|
3077
3149
|
await todoTool.execute({ todos: todosToReplace }, ctx, {
|
|
@@ -3104,6 +3176,7 @@ var planTool = {
|
|
|
3104
3176
|
});
|
|
3105
3177
|
return f;
|
|
3106
3178
|
});
|
|
3179
|
+
ctx.meta["task.path.resolved"] = taskPath;
|
|
3107
3180
|
return mkResult(
|
|
3108
3181
|
plan,
|
|
3109
3182
|
true,
|
|
@@ -11,8 +11,14 @@ export interface TrackedProcess {
|
|
|
11
11
|
sessionId?: string | undefined;
|
|
12
12
|
/** The raw ChildProcess handle. Never call .kill() directly on this —
|
|
13
13
|
* use `kill()` below which handles process groups correctly on POSIX
|
|
14
|
-
* and degrades gracefully on Windows.
|
|
15
|
-
|
|
14
|
+
* and degrades gracefully on Windows.
|
|
15
|
+
*
|
|
16
|
+
* `null` for entries mirrored from the persistent registry
|
|
17
|
+
* (process-registry-persistent.ts): those track processes owned by OTHER
|
|
18
|
+
* host instances, so there is no live handle in this process. All
|
|
19
|
+
* registry paths must tolerate a null child (kill by PID, liveness by
|
|
20
|
+
* `process.kill(pid, 0)` probe). */
|
|
21
|
+
child: ChildProcess | null;
|
|
16
22
|
/** True only when this child was spawned as a POSIX process-group/session
|
|
17
23
|
* leader (for example `spawn(..., { detached: true })`) and `pid` is the
|
|
18
24
|
* actual `child.pid`. Negative-PID signaling is host-wide dangerous for
|
package/dist/process-registry.js
CHANGED
|
@@ -201,8 +201,11 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
201
201
|
/--(?: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,
|
|
202
202
|
// -t short flag (token): attached (-tVALUE), separated (-t VALUE), or -t=VALUE.
|
|
203
203
|
// (?<![-\w]) anchors to a token start so we don't match the `-t` inside `--token`.
|
|
204
|
+
// The value must be token-like (>= 8 chars) so ordinary combined flags such
|
|
205
|
+
// as `tar -tf` / `ssh -tt` are not eaten. Global flag: EVERY occurrence is
|
|
206
|
+
// redacted, not just the first.
|
|
204
207
|
// NOTE: synced with @wrongstack/core observability/redact-command.ts.
|
|
205
|
-
/(?<![-\w])-t(?:[=\s]+)?[^\s,-]
|
|
208
|
+
/(?<![-\w])-t(?:[=\s]+)?[^\s,-]{8,}/g,
|
|
206
209
|
// -p|-password|-a (redis auth) short flags: attached + separated + =value.
|
|
207
210
|
// Same token-start anchor; over-redaction is an accepted tradeoff for a
|
|
208
211
|
// redaction function. Synced with core copy.
|
|
@@ -210,8 +213,9 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
210
213
|
// env var–style secrets: TOKEN=x, API_KEY=y, etc.
|
|
211
214
|
/(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
|
|
212
215
|
// Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
|
|
213
|
-
// when preceded by a flag name (e.g. --github-token=EyJ...).
|
|
214
|
-
|
|
216
|
+
// when preceded by a flag name (e.g. --github-token=EyJ...). Global flag so
|
|
217
|
+
// every such flag in the command line is redacted, not just the first.
|
|
218
|
+
/--\w*(?:token|key|secret|password|passwd|auth|credential)\w*[=\s,][A-Za-z0-9+/=]{32,}/g
|
|
215
219
|
];
|
|
216
220
|
function redactCommand(cmd) {
|
|
217
221
|
let result = cmd;
|
|
@@ -300,11 +304,15 @@ var ProcessRegistryImpl = class {
|
|
|
300
304
|
return Number.isInteger(pid) && pid > 1 && pid !== process.pid && pid !== process.ppid;
|
|
301
305
|
}
|
|
302
306
|
_canSignalProcessGroup(p) {
|
|
303
|
-
return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && typeof p.child.pid === "number" && p.child.pid === p.pid;
|
|
307
|
+
return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && p.child !== null && typeof p.child.pid === "number" && p.child.pid === p.pid;
|
|
304
308
|
}
|
|
305
309
|
_killChildDirect(p, signal) {
|
|
306
310
|
try {
|
|
307
|
-
p.child
|
|
311
|
+
if (p.child) {
|
|
312
|
+
p.child.kill(signal);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (this._isSafeSignalPid(p.pid)) process.kill(p.pid, signal);
|
|
308
316
|
} catch {
|
|
309
317
|
}
|
|
310
318
|
}
|
|
@@ -502,15 +510,15 @@ var ProcessRegistryImpl = class {
|
|
|
502
510
|
this._pruneStale(pid);
|
|
503
511
|
const p = this.processes.get(pid);
|
|
504
512
|
if (!p) return false;
|
|
505
|
-
if (p.killed) return true;
|
|
513
|
+
if (p.killed && opts.force !== true) return true;
|
|
506
514
|
if (p.protected && opts.includeProtected !== true) return false;
|
|
507
515
|
if (opts.preserveBackground && p.background) return false;
|
|
508
516
|
const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;
|
|
509
517
|
const isWin = os.platform() === "win32";
|
|
510
518
|
if (isWin) {
|
|
511
|
-
const liveRealChild = p.child.exitCode === null && typeof p.child.pid === "number";
|
|
519
|
+
const liveRealChild = p.child === null || p.child.exitCode === null && typeof p.child.pid === "number";
|
|
512
520
|
const directFallback = () => {
|
|
513
|
-
if (p.child.exitCode === null) {
|
|
521
|
+
if (p.child && p.child.exitCode === null) {
|
|
514
522
|
try {
|
|
515
523
|
p.child.kill("SIGKILL");
|
|
516
524
|
} catch {
|
|
@@ -522,10 +530,7 @@ var ProcessRegistryImpl = class {
|
|
|
522
530
|
onSettled: directFallback
|
|
523
531
|
})) {
|
|
524
532
|
} else {
|
|
525
|
-
|
|
526
|
-
p.child.kill(force ? "SIGKILL" : "SIGTERM");
|
|
527
|
-
} catch {
|
|
528
|
-
}
|
|
533
|
+
this._killChildDirect(p, force ? "SIGKILL" : "SIGTERM");
|
|
529
534
|
}
|
|
530
535
|
p.killed = true;
|
|
531
536
|
return true;
|
|
@@ -536,7 +541,7 @@ var ProcessRegistryImpl = class {
|
|
|
536
541
|
} else {
|
|
537
542
|
this._killPosix(p, "SIGTERM");
|
|
538
543
|
const timer = setTimeout(() => {
|
|
539
|
-
if (this.processes.has(pid) && !p.child
|
|
544
|
+
if (this.processes.has(pid) && !p.child?.killed) {
|
|
540
545
|
this._killPosix(p, "SIGKILL");
|
|
541
546
|
}
|
|
542
547
|
}, graceMs);
|
|
@@ -591,6 +596,16 @@ var ProcessRegistryImpl = class {
|
|
|
591
596
|
* before reusing a PID, but we want to clean up before that becomes a risk.
|
|
592
597
|
*/
|
|
593
598
|
_isStaleEntry(entry) {
|
|
599
|
+
if (entry.child === null) {
|
|
600
|
+
if (Date.now() - entry.startedAt <= 6e4) return false;
|
|
601
|
+
if (os.platform() === "win32") return false;
|
|
602
|
+
try {
|
|
603
|
+
process.kill(entry.pid, 0);
|
|
604
|
+
return false;
|
|
605
|
+
} catch (err) {
|
|
606
|
+
return err.code !== "EPERM";
|
|
607
|
+
}
|
|
608
|
+
}
|
|
594
609
|
return entry.child.exitCode !== null && Date.now() - entry.startedAt > 6e4;
|
|
595
610
|
}
|
|
596
611
|
/**
|
package/dist/ps-slash.js
CHANGED
|
@@ -270,11 +270,15 @@ var ProcessRegistryImpl = class {
|
|
|
270
270
|
return Number.isInteger(pid) && pid > 1 && pid !== process.pid && pid !== process.ppid;
|
|
271
271
|
}
|
|
272
272
|
_canSignalProcessGroup(p) {
|
|
273
|
-
return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && typeof p.child.pid === "number" && p.child.pid === p.pid;
|
|
273
|
+
return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && p.child !== null && typeof p.child.pid === "number" && p.child.pid === p.pid;
|
|
274
274
|
}
|
|
275
275
|
_killChildDirect(p, signal) {
|
|
276
276
|
try {
|
|
277
|
-
p.child
|
|
277
|
+
if (p.child) {
|
|
278
|
+
p.child.kill(signal);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (this._isSafeSignalPid(p.pid)) process.kill(p.pid, signal);
|
|
278
282
|
} catch {
|
|
279
283
|
}
|
|
280
284
|
}
|
|
@@ -472,15 +476,15 @@ var ProcessRegistryImpl = class {
|
|
|
472
476
|
this._pruneStale(pid);
|
|
473
477
|
const p = this.processes.get(pid);
|
|
474
478
|
if (!p) return false;
|
|
475
|
-
if (p.killed) return true;
|
|
479
|
+
if (p.killed && opts.force !== true) return true;
|
|
476
480
|
if (p.protected && opts.includeProtected !== true) return false;
|
|
477
481
|
if (opts.preserveBackground && p.background) return false;
|
|
478
482
|
const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;
|
|
479
483
|
const isWin = os.platform() === "win32";
|
|
480
484
|
if (isWin) {
|
|
481
|
-
const liveRealChild = p.child.exitCode === null && typeof p.child.pid === "number";
|
|
485
|
+
const liveRealChild = p.child === null || p.child.exitCode === null && typeof p.child.pid === "number";
|
|
482
486
|
const directFallback = () => {
|
|
483
|
-
if (p.child.exitCode === null) {
|
|
487
|
+
if (p.child && p.child.exitCode === null) {
|
|
484
488
|
try {
|
|
485
489
|
p.child.kill("SIGKILL");
|
|
486
490
|
} catch {
|
|
@@ -492,10 +496,7 @@ var ProcessRegistryImpl = class {
|
|
|
492
496
|
onSettled: directFallback
|
|
493
497
|
})) {
|
|
494
498
|
} else {
|
|
495
|
-
|
|
496
|
-
p.child.kill(force ? "SIGKILL" : "SIGTERM");
|
|
497
|
-
} catch {
|
|
498
|
-
}
|
|
499
|
+
this._killChildDirect(p, force ? "SIGKILL" : "SIGTERM");
|
|
499
500
|
}
|
|
500
501
|
p.killed = true;
|
|
501
502
|
return true;
|
|
@@ -506,7 +507,7 @@ var ProcessRegistryImpl = class {
|
|
|
506
507
|
} else {
|
|
507
508
|
this._killPosix(p, "SIGTERM");
|
|
508
509
|
const timer = setTimeout(() => {
|
|
509
|
-
if (this.processes.has(pid) && !p.child
|
|
510
|
+
if (this.processes.has(pid) && !p.child?.killed) {
|
|
510
511
|
this._killPosix(p, "SIGKILL");
|
|
511
512
|
}
|
|
512
513
|
}, graceMs);
|
|
@@ -561,6 +562,16 @@ var ProcessRegistryImpl = class {
|
|
|
561
562
|
* before reusing a PID, but we want to clean up before that becomes a risk.
|
|
562
563
|
*/
|
|
563
564
|
_isStaleEntry(entry) {
|
|
565
|
+
if (entry.child === null) {
|
|
566
|
+
if (Date.now() - entry.startedAt <= 6e4) return false;
|
|
567
|
+
if (os.platform() === "win32") return false;
|
|
568
|
+
try {
|
|
569
|
+
process.kill(entry.pid, 0);
|
|
570
|
+
return false;
|
|
571
|
+
} catch (err) {
|
|
572
|
+
return err.code !== "EPERM";
|
|
573
|
+
}
|
|
574
|
+
}
|
|
564
575
|
return entry.child.exitCode !== null && Date.now() - entry.startedAt > 6e4;
|
|
565
576
|
}
|
|
566
577
|
/**
|
|
@@ -854,7 +865,6 @@ var PersistentProcessRegistry = class {
|
|
|
854
865
|
try {
|
|
855
866
|
const data = await readRegistryFile(this.registryPath);
|
|
856
867
|
data.instances.set(String(entry.pid), entry);
|
|
857
|
-
const child = null;
|
|
858
868
|
this.baseRegistry.register({
|
|
859
869
|
pid: entry.pid,
|
|
860
870
|
name: entry.name,
|
|
@@ -862,7 +872,7 @@ var PersistentProcessRegistry = class {
|
|
|
862
872
|
startedAt: entry.startedAt,
|
|
863
873
|
sessionId: entry.sessionId,
|
|
864
874
|
protected: entry.protected,
|
|
865
|
-
child
|
|
875
|
+
child: null
|
|
866
876
|
});
|
|
867
877
|
await writeRegistryFile(this.registryPath, data);
|
|
868
878
|
} finally {
|
package/dist/read.js
CHANGED
|
@@ -7277,7 +7277,6 @@ import * as fs10 from "node:fs";
|
|
|
7277
7277
|
import * as os3 from "node:os";
|
|
7278
7278
|
import * as path14 from "node:path";
|
|
7279
7279
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7280
|
-
import { assertUnixSocketPathWithinLimit } from "@wrongstack/core/utils";
|
|
7281
7280
|
var PROJECT_INDEX_SERVER_PROTOCOL_VERSION = 1;
|
|
7282
7281
|
var PROJECT_INDEX_SERVER_METADATA_FILE = "server.json";
|
|
7283
7282
|
var PROJECT_INDEX_SERVER_SOCKET_DIR = `wsci-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}`;
|
|
@@ -8254,7 +8253,7 @@ var MAX_BYTES = 5 * 1024 * 1024;
|
|
|
8254
8253
|
var readTool = {
|
|
8255
8254
|
name: "read",
|
|
8256
8255
|
category: "Filesystem",
|
|
8257
|
-
description: "Read the contents of a file with line numbers. This is the primary way to inspect source code, configuration, or any text file before making changes. Lines are returned 1-indexed
|
|
8256
|
+
description: "Read the contents of a file with line numbers. This is the primary way to inspect source code, configuration, or any text file before making changes. Lines are returned 1-indexed in the form `N\u2192content` (line number, then a `\u2192` separator, then the raw line). The `N\u2192` prefix is display-only \u2014 always strip it before reusing the text, e.g. never include it in `edit.old_string`. When advanced mode is on or `includeSymbols` is set, the result also includes a `symbols` field listing codebase-index symbol names, kinds, and line numbers for the file (not file content).",
|
|
8258
8257
|
usageHint: "FOUNDATIONAL TOOL \u2014 call this before almost any edit operation.\n\nBest practices:\n- Always read a file before using `edit`, `replace`, or `write` on it (the system often requires it for safety).\n- Use `offset` + `limit` for very large files instead of reading everything at once.\n- Default limit is generous (2000 lines) but can be increased.\n- The output format is designed to be directly usable as context for `edit` operations.\n- Set `includeSymbols: true` to also receive the codebase-index symbol listing for the file.\n- Enable advanced mode (`ctx.meta['tools.read.advancedMode'] = true`) to auto-inject symbols on every read.",
|
|
8259
8258
|
selection: {
|
|
8260
8259
|
doNotUseWhen: "you need to search many files for matching content.",
|
|
@@ -8275,11 +8274,13 @@ var readTool = {
|
|
|
8275
8274
|
},
|
|
8276
8275
|
offset: {
|
|
8277
8276
|
type: "integer",
|
|
8277
|
+
minimum: 1,
|
|
8278
8278
|
description: "1-based starting line number. Use together with `limit` for large files."
|
|
8279
8279
|
},
|
|
8280
8280
|
limit: {
|
|
8281
8281
|
type: "integer",
|
|
8282
|
-
|
|
8282
|
+
minimum: 0,
|
|
8283
|
+
description: "Maximum number of lines to return (default 2000). Values above 5000 are clamped to 5000 \u2014 page with `offset` for more."
|
|
8283
8284
|
},
|
|
8284
8285
|
mode: {
|
|
8285
8286
|
type: "string",
|
|
@@ -8358,7 +8359,12 @@ var readTool = {
|
|
|
8358
8359
|
}
|
|
8359
8360
|
const buf = await fs13.readFile(absPath);
|
|
8360
8361
|
if (isBinaryBuffer(buf)) {
|
|
8361
|
-
throw new
|
|
8362
|
+
throw new FsError({
|
|
8363
|
+
message: `read: "${input.path}" appears to be binary`,
|
|
8364
|
+
code: "FS_READ_FAILED",
|
|
8365
|
+
path: absPath,
|
|
8366
|
+
context: { reason: "binary" }
|
|
8367
|
+
});
|
|
8362
8368
|
}
|
|
8363
8369
|
const text = buf.toString("utf8");
|
|
8364
8370
|
const contentHash = sha256hex(text);
|
package/dist/replace.d.ts
CHANGED
|
@@ -16,7 +16,11 @@ interface ReplaceOutput {
|
|
|
16
16
|
diff?: string | undefined;
|
|
17
17
|
}[];
|
|
18
18
|
dry_run: boolean;
|
|
19
|
+
/** Set when the combined diff payload was truncated to the output budget. */
|
|
20
|
+
note?: string | undefined;
|
|
19
21
|
}
|
|
20
22
|
export declare const replaceTool: Tool<ReplaceInput, ReplaceOutput>;
|
|
23
|
+
/** Test-only: forget the cached rg availability so mocks can vary per test. */
|
|
24
|
+
export declare function __resetRgDetectionForTests(): void;
|
|
21
25
|
export {};
|
|
22
26
|
//# sourceMappingURL=replace.d.ts.map
|
package/dist/replace.js
CHANGED
|
@@ -165,6 +165,20 @@ function ensureInsideRoot(absPath, ctx) {
|
|
|
165
165
|
function safeResolve(input, ctx) {
|
|
166
166
|
return ensureInsideRoot(resolvePath(input, ctx), ctx);
|
|
167
167
|
}
|
|
168
|
+
function truncateDiffPayload(diff, maxBytes) {
|
|
169
|
+
const total = Buffer.byteLength(diff, "utf8");
|
|
170
|
+
if (total <= maxBytes) return { text: diff, truncated: false };
|
|
171
|
+
const MARKER_RESERVE = 96;
|
|
172
|
+
let head = takeHeadBytes(diff, Math.max(0, maxBytes - MARKER_RESERVE));
|
|
173
|
+
const nl = head.lastIndexOf("\n");
|
|
174
|
+
if (nl > 0) head = head.slice(0, nl);
|
|
175
|
+
const kept = Buffer.byteLength(head, "utf8");
|
|
176
|
+
return {
|
|
177
|
+
text: `${head}
|
|
178
|
+
\u2026[diff truncated: ${total - kept} of ${total} bytes omitted]`,
|
|
179
|
+
truncated: true
|
|
180
|
+
};
|
|
181
|
+
}
|
|
168
182
|
function isBinaryBuffer(buf) {
|
|
169
183
|
const len = Math.min(buf.length, 8192);
|
|
170
184
|
for (let i = 0; i < len; i++) {
|
|
@@ -172,14 +186,27 @@ function isBinaryBuffer(buf) {
|
|
|
172
186
|
}
|
|
173
187
|
return false;
|
|
174
188
|
}
|
|
189
|
+
function takeHeadBytes(s, maxBytes) {
|
|
190
|
+
if (maxBytes <= 0) return "";
|
|
191
|
+
if (Buffer.byteLength(s, "utf8") <= maxBytes) return s;
|
|
192
|
+
let lo = 0;
|
|
193
|
+
let hi = s.length;
|
|
194
|
+
while (lo < hi) {
|
|
195
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
196
|
+
if (Buffer.byteLength(s.slice(0, mid), "utf8") <= maxBytes) lo = mid;
|
|
197
|
+
else hi = mid - 1;
|
|
198
|
+
}
|
|
199
|
+
return s.slice(0, lo);
|
|
200
|
+
}
|
|
175
201
|
|
|
176
202
|
// src/replace.ts
|
|
203
|
+
var MAX_DIFF_BYTES = 262144;
|
|
177
204
|
var DEFAULT_IGNORE = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
|
|
178
205
|
var replaceTool = {
|
|
179
206
|
name: "replace",
|
|
180
207
|
category: "Transform",
|
|
181
208
|
description: "Perform a search-and-replace across multiple files using a regex pattern. This is a powerful bulk transformation tool. Dry-run is ON by default \u2014 set `dry_run: false` to apply changes.",
|
|
182
|
-
usageHint: "DANGEROUS IF USED CARELESSLY \u2014 review the diff output carefully.\n\nRecommended workflow:\n1. Run without `dry_run: false` first to see exactly what would change (dry-run is the default).\n2. Review the diff output, then re-run with `dry_run: false` to apply.\n3. Use a specific enough `pattern` (and `glob` / `files`) to avoid accidental broad changes.\n4. `replace_all` controls whether only the first match per file or all matches are replaced.\nThis tool is excellent for large-scale refactors (renaming, import updates, etc.) but must be used with caution.",
|
|
209
|
+
usageHint: "DANGEROUS IF USED CARELESSLY \u2014 review the diff output carefully.\n\nRecommended workflow:\n1. Run without `dry_run: false` first to see exactly what would change (dry-run is the default).\n2. Review the diff output, then re-run with `dry_run: false` to apply.\n3. Use a specific enough `pattern` (and `glob` / `files`) to avoid accidental broad changes.\n4. `replace_all` controls whether only the first match per file or all matches are replaced.\n5. `replacement` supports regex substitutions: `$1`\u2013`$9` insert capture groups, `$&` inserts the whole match, and `$$` inserts a literal dollar sign.\nThis tool is excellent for large-scale refactors (renaming, import updates, etc.) but must be used with caution.",
|
|
183
210
|
permission: "confirm",
|
|
184
211
|
// WS-046: gives permission decisions something to key on.
|
|
185
212
|
// The file scope being rewritten, not the pattern: a trust rule should say
|
|
@@ -189,11 +216,15 @@ var replaceTool = {
|
|
|
189
216
|
capabilities: ["fs.write"],
|
|
190
217
|
icon: "edit",
|
|
191
218
|
timeoutMs: 3e4,
|
|
219
|
+
maxOutputBytes: 262144,
|
|
192
220
|
inputSchema: {
|
|
193
221
|
type: "object",
|
|
194
222
|
properties: {
|
|
195
223
|
pattern: { type: "string", description: "Regex pattern to match" },
|
|
196
|
-
replacement: {
|
|
224
|
+
replacement: {
|
|
225
|
+
type: "string",
|
|
226
|
+
description: "Replacement string. Supports `$1`\u2013`$9` (capture groups), `$&` (whole match), and `$$` (literal dollar sign) \u2014 same semantics as JavaScript String.replace."
|
|
227
|
+
},
|
|
197
228
|
files: {
|
|
198
229
|
type: "string",
|
|
199
230
|
description: "File(s) to target: single path, comma-separated list, or glob pattern"
|
|
@@ -242,6 +273,9 @@ var replaceTool = {
|
|
|
242
273
|
const realRoot = await fs.realpath(ctx.projectRoot).catch(() => ctx.projectRoot);
|
|
243
274
|
const results = [];
|
|
244
275
|
let totalReplacements = 0;
|
|
276
|
+
let diffBytesUsed = 0;
|
|
277
|
+
let diffsOmitted = 0;
|
|
278
|
+
let diffsTruncated = 0;
|
|
245
279
|
for (const absPath of fileList) {
|
|
246
280
|
const lstat2 = await fs.lstat(absPath).catch((err) => {
|
|
247
281
|
if (err.code === "ENOENT") return null;
|
|
@@ -277,7 +311,7 @@ var replaceTool = {
|
|
|
277
311
|
let newContentLf = contentLf;
|
|
278
312
|
for (let i = matches.length - 1; i >= 0; i--) {
|
|
279
313
|
const m = expectDefined(matches[i]);
|
|
280
|
-
newContentLf = newContentLf.slice(0, m.index) + input.replacement + newContentLf.slice(expectDefined(m.index) + m[0].length);
|
|
314
|
+
newContentLf = newContentLf.slice(0, m.index) + expandReplacement(input.replacement, m) + newContentLf.slice(expectDefined(m.index) + m[0].length);
|
|
281
315
|
}
|
|
282
316
|
re.lastIndex = 0;
|
|
283
317
|
totalReplacements += count;
|
|
@@ -295,24 +329,76 @@ var replaceTool = {
|
|
|
295
329
|
after: newContent
|
|
296
330
|
});
|
|
297
331
|
}
|
|
298
|
-
|
|
332
|
+
let diff = dryRun || matches.length > 0 ? unifiedDiff(content, toStyle(newContentLf, style), {
|
|
299
333
|
fromFile: absPath,
|
|
300
334
|
toFile: absPath
|
|
301
335
|
}) : void 0;
|
|
336
|
+
if (diff !== void 0) {
|
|
337
|
+
const remaining = MAX_DIFF_BYTES - diffBytesUsed;
|
|
338
|
+
if (remaining <= 0) {
|
|
339
|
+
diff = void 0;
|
|
340
|
+
diffsOmitted++;
|
|
341
|
+
} else {
|
|
342
|
+
const capped = truncateDiffPayload(diff, remaining);
|
|
343
|
+
if (capped.truncated) diffsTruncated++;
|
|
344
|
+
diff = capped.text;
|
|
345
|
+
diffBytesUsed += Buffer.byteLength(diff, "utf8");
|
|
346
|
+
}
|
|
347
|
+
}
|
|
302
348
|
results.push({
|
|
303
349
|
path: absPath,
|
|
304
350
|
replacements: matches.length,
|
|
305
351
|
diff
|
|
306
352
|
});
|
|
307
353
|
}
|
|
354
|
+
const overBudget = diffsOmitted > 0 || diffsTruncated > 0;
|
|
308
355
|
return {
|
|
309
356
|
files_modified: results.length,
|
|
310
357
|
total_replacements: totalReplacements,
|
|
311
358
|
results,
|
|
312
|
-
dry_run: dryRun
|
|
359
|
+
dry_run: dryRun,
|
|
360
|
+
note: overBudget ? `Diff payload exceeded the 256 KiB output budget: ${diffsTruncated} diff(s) truncated, ${diffsOmitted} diff(s) omitted. Replacement counts are complete; use the read tool to inspect individual files.` : void 0
|
|
313
361
|
};
|
|
314
362
|
}
|
|
315
363
|
};
|
|
364
|
+
function expandReplacement(template, match) {
|
|
365
|
+
if (!template.includes("$")) return template;
|
|
366
|
+
let out = "";
|
|
367
|
+
for (let i = 0; i < template.length; i++) {
|
|
368
|
+
const ch = template[i];
|
|
369
|
+
if (ch !== "$") {
|
|
370
|
+
out += ch;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
const next = template[i + 1];
|
|
374
|
+
if (next === "$") {
|
|
375
|
+
out += "$";
|
|
376
|
+
i++;
|
|
377
|
+
} else if (next === "&") {
|
|
378
|
+
out += match[0];
|
|
379
|
+
i++;
|
|
380
|
+
} else if (next !== void 0 && next >= "1" && next <= "9") {
|
|
381
|
+
const idx = next.charCodeAt(0) - 48;
|
|
382
|
+
if (idx < match.length) {
|
|
383
|
+
out += match[idx] ?? "";
|
|
384
|
+
i++;
|
|
385
|
+
} else {
|
|
386
|
+
out += "$";
|
|
387
|
+
}
|
|
388
|
+
} else {
|
|
389
|
+
out += "$";
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return out;
|
|
393
|
+
}
|
|
394
|
+
function passesExtraGlob(extraGlob, name, full) {
|
|
395
|
+
extraGlob.lastIndex = 0;
|
|
396
|
+
const nameMatch = extraGlob.test(name);
|
|
397
|
+
extraGlob.lastIndex = 0;
|
|
398
|
+
const fullMatch = extraGlob.test(full);
|
|
399
|
+
extraGlob.lastIndex = 0;
|
|
400
|
+
return nameMatch || fullMatch;
|
|
401
|
+
}
|
|
316
402
|
async function resolveFiles(filesInput, ctx, extraGlob) {
|
|
317
403
|
const base = ctx.cwd;
|
|
318
404
|
const normalized = filesInput.trim();
|
|
@@ -323,6 +409,7 @@ async function resolveFiles(filesInput, ctx, extraGlob) {
|
|
|
323
409
|
const resolved = [];
|
|
324
410
|
for (const p of parts) {
|
|
325
411
|
const absPath = safeResolve(p, ctx);
|
|
412
|
+
if (extraGlob && !passesExtraGlob(extraGlob, path2.basename(absPath), absPath)) continue;
|
|
326
413
|
const stat2 = await fs.stat(absPath).catch(() => null);
|
|
327
414
|
if (stat2?.isFile()) {
|
|
328
415
|
resolved.push(absPath);
|
|
@@ -335,14 +422,22 @@ async function globFiles(pattern, base, extraGlob) {
|
|
|
335
422
|
if (rgAvailable) {
|
|
336
423
|
try {
|
|
337
424
|
const { promise } = spawnRgFind(pattern, base);
|
|
338
|
-
|
|
425
|
+
const files = await promise;
|
|
426
|
+
if (extraGlob) {
|
|
427
|
+
return files.filter((f) => passesExtraGlob(extraGlob, path2.basename(f), f));
|
|
428
|
+
}
|
|
429
|
+
return files;
|
|
339
430
|
} catch {
|
|
340
431
|
}
|
|
341
432
|
}
|
|
342
433
|
return await globNative(pattern, base, extraGlob);
|
|
343
434
|
}
|
|
435
|
+
var rgAvailabilityCache;
|
|
436
|
+
function __resetRgDetectionForTests() {
|
|
437
|
+
rgAvailabilityCache = void 0;
|
|
438
|
+
}
|
|
344
439
|
function checkRg() {
|
|
345
|
-
|
|
440
|
+
rgAvailabilityCache ??= new Promise((resolve2) => {
|
|
346
441
|
try {
|
|
347
442
|
const p = spawn("rg", ["--version"], {
|
|
348
443
|
env: buildChildEnv(),
|
|
@@ -355,6 +450,7 @@ function checkRg() {
|
|
|
355
450
|
resolve2(false);
|
|
356
451
|
}
|
|
357
452
|
});
|
|
453
|
+
return rgAvailabilityCache;
|
|
358
454
|
}
|
|
359
455
|
function spawnRgFind(pattern, base) {
|
|
360
456
|
const args = ["--files", "--glob", pattern, base];
|
|
@@ -421,6 +517,7 @@ async function globNative(pattern, base, extraGlob) {
|
|
|
421
517
|
return results;
|
|
422
518
|
}
|
|
423
519
|
export {
|
|
520
|
+
__resetRgDetectionForTests,
|
|
424
521
|
replaceTool
|
|
425
522
|
};
|
|
426
523
|
//# sourceMappingURL=replace.js.map
|
package/dist/search.d.ts
CHANGED
|
@@ -15,6 +15,12 @@ interface SearchOutput {
|
|
|
15
15
|
source: string;
|
|
16
16
|
truncated: boolean;
|
|
17
17
|
cached: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Present when the search engine could not be reached or returned an
|
|
20
|
+
* unusable response. `results` is empty in that case — the failure is never
|
|
21
|
+
* disguised as a synthetic result entry.
|
|
22
|
+
*/
|
|
23
|
+
error?: string | undefined;
|
|
18
24
|
}
|
|
19
25
|
export declare const searchTool: Tool<SearchInput, SearchOutput>;
|
|
20
26
|
/** Exposed for tests so they can reset the module-level cache between cases. */
|