@wrongstack/tools 0.272.2 → 0.273.1
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/audit.js.map +1 -1
- package/dist/bash.js +225 -24
- package/dist/bash.js.map +1 -1
- package/dist/builtin.js +344 -110
- package/dist/builtin.js.map +1 -1
- package/dist/codebase-index/index.js +9 -5
- package/dist/codebase-index/index.js.map +1 -1
- package/dist/codebase-index/worker.js +9 -5
- package/dist/codebase-index/worker.js.map +1 -1
- package/dist/exec.d.ts +20 -1
- package/dist/exec.js +132 -71
- package/dist/exec.js.map +1 -1
- package/dist/format.js.map +1 -1
- package/dist/index.d.ts +111 -2
- package/dist/index.js +415 -140
- package/dist/index.js.map +1 -1
- package/dist/install.js.map +1 -1
- package/dist/lint.js.map +1 -1
- package/dist/outdated.js.map +1 -1
- package/dist/pack.js +344 -110
- package/dist/pack.js.map +1 -1
- package/dist/replace.js +2 -2
- package/dist/replace.js.map +1 -1
- package/dist/test.js.map +1 -1
- package/dist/typecheck.js.map +1 -1
- package/package.json +2 -2
package/dist/pack.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { spawn, execFileSync } from 'node:child_process';
|
|
2
2
|
import * as Core from '@wrongstack/core';
|
|
3
|
-
import { buildChildEnv, detectNewlineStyle, normalizeToLf, toStyle, atomicWrite, unifiedDiff, isPrivateIPv4, isPrivateIPv6, assessCommitSafety, compileGlob, expectDefined, recordPackageAction, detectPackageEcosystem, mutatePlan, clearPlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, mutateTasks, formatTaskList, formatPlan, toErrorMessage, computeTaskItemProgress, loadPlan, savePlan, loadTasks, saveTasks, wstackGlobalRoot, resolveWstackPaths, truncate } from '@wrongstack/core';
|
|
3
|
+
import { buildChildEnv, detectNewlineStyle, normalizeToLf, toStyle, atomicWrite, unifiedDiff, isPrivateIPv4, isPrivateIPv6, assessCommitSafety, compileGlob, expectDefined, recordPackageAction, detectPackageEcosystem, mutatePlan, clearPlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, mutateTasks, formatTaskList, formatPlan, toErrorMessage as toErrorMessage$2, computeTaskItemProgress, loadPlan, savePlan, loadTasks, saveTasks, wstackGlobalRoot, resolveWstackPaths, truncate } from '@wrongstack/core';
|
|
4
4
|
import * as fs from 'node:fs';
|
|
5
5
|
import { statSync, mkdirSync, createWriteStream } from 'node:fs';
|
|
6
6
|
import * as fs2 from 'node:fs/promises';
|
|
7
7
|
import * as path3 from 'node:path';
|
|
8
8
|
import { resolve, sep, dirname, join } from 'node:path';
|
|
9
9
|
import * as os2 from 'node:os';
|
|
10
|
-
import { toErrorMessage as toErrorMessage$
|
|
10
|
+
import { toErrorMessage as toErrorMessage$3 } from '@wrongstack/core/utils';
|
|
11
11
|
import { createRequire } from 'node:module';
|
|
12
12
|
import { fileURLToPath } from 'node:url';
|
|
13
13
|
import { Worker } from 'node:worker_threads';
|
|
14
14
|
import * as ts from 'typescript';
|
|
15
|
+
import { toErrorMessage as toErrorMessage$1 } from '@wrongstack/core/utils/error';
|
|
15
16
|
import * as dns from 'node:dns/promises';
|
|
16
17
|
import * as net from 'node:net';
|
|
17
18
|
import { Agent } from 'undici';
|
|
@@ -660,6 +661,21 @@ function resolveWin32Command(cmd) {
|
|
|
660
661
|
}
|
|
661
662
|
return cmd;
|
|
662
663
|
}
|
|
664
|
+
function resolvePowerShell(cmd) {
|
|
665
|
+
if (process.platform !== "win32") return cmd;
|
|
666
|
+
const lower = cmd.toLowerCase();
|
|
667
|
+
if (lower !== "pwsh" && lower !== "powershell" && lower !== "pwsh.exe" && lower !== "powershell.exe") {
|
|
668
|
+
return resolveWin32Command(cmd);
|
|
669
|
+
}
|
|
670
|
+
const primary = lower.startsWith("pwsh") ? "pwsh.exe" : "powershell.exe";
|
|
671
|
+
const fallback = lower.startsWith("pwsh") ? "powershell.exe" : "pwsh.exe";
|
|
672
|
+
const resolved = resolveWin32Command(primary);
|
|
673
|
+
if (resolved !== primary) {
|
|
674
|
+
const fb = resolveWin32Command(fallback);
|
|
675
|
+
return fb === fallback ? cmd : fb;
|
|
676
|
+
}
|
|
677
|
+
return resolved;
|
|
678
|
+
}
|
|
663
679
|
var WIN32_SHELL_META = /[&|<>\r\n\0]/;
|
|
664
680
|
function assertSafeWin32ShellArgs(args) {
|
|
665
681
|
for (const a of args) {
|
|
@@ -1084,6 +1100,21 @@ function parseAuditOutput(json, exitCode) {
|
|
|
1084
1100
|
}
|
|
1085
1101
|
}
|
|
1086
1102
|
var REGISTRY_FILE = ".wrongstack/process-registry.json";
|
|
1103
|
+
function toErrorMessage(err) {
|
|
1104
|
+
return err instanceof Error ? err.message : String(err);
|
|
1105
|
+
}
|
|
1106
|
+
function emitStructuredLog(level, event, message, error) {
|
|
1107
|
+
const payload = {
|
|
1108
|
+
level,
|
|
1109
|
+
event,
|
|
1110
|
+
message,
|
|
1111
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1112
|
+
};
|
|
1113
|
+
if (error !== void 0) {
|
|
1114
|
+
payload.error = toErrorMessage(error);
|
|
1115
|
+
}
|
|
1116
|
+
console.log(JSON.stringify(payload));
|
|
1117
|
+
}
|
|
1087
1118
|
var HEARTBEAT_INTERVAL_MS = 5e3;
|
|
1088
1119
|
var STALE_THRESHOLD_MS = 3e4;
|
|
1089
1120
|
var LOCKFILE = ".wrongstack/.process-registry.lock";
|
|
@@ -1093,6 +1124,9 @@ function generateInstanceId() {
|
|
|
1093
1124
|
const random = Math.random().toString(36).slice(2, 8);
|
|
1094
1125
|
return `${hostname2}:${pid}:${random}`;
|
|
1095
1126
|
}
|
|
1127
|
+
function isNodeError(err) {
|
|
1128
|
+
return typeof err === "object" && err !== null && "code" in err;
|
|
1129
|
+
}
|
|
1096
1130
|
async function acquireLock(lockfilePath, timeoutMs = 5e3) {
|
|
1097
1131
|
const start = Date.now();
|
|
1098
1132
|
const pidStr = String(process.pid);
|
|
@@ -1107,7 +1141,7 @@ async function acquireLock(lockfilePath, timeoutMs = 5e3) {
|
|
|
1107
1141
|
}
|
|
1108
1142
|
};
|
|
1109
1143
|
} catch (err) {
|
|
1110
|
-
if (err.code === "EEXIST") {
|
|
1144
|
+
if (isNodeError(err) && err.code === "EEXIST") {
|
|
1111
1145
|
try {
|
|
1112
1146
|
const content = await fs2.readFile(lockfilePath, "utf-8");
|
|
1113
1147
|
const parts = content.split(":");
|
|
@@ -1144,7 +1178,7 @@ async function readRegistryFile(filePath) {
|
|
|
1144
1178
|
}
|
|
1145
1179
|
return parsed;
|
|
1146
1180
|
} catch (err) {
|
|
1147
|
-
if (err.code === "ENOENT") {
|
|
1181
|
+
if (isNodeError(err) && err.code === "ENOENT") {
|
|
1148
1182
|
return {
|
|
1149
1183
|
version: 1,
|
|
1150
1184
|
instances: /* @__PURE__ */ new Map(),
|
|
@@ -1180,7 +1214,7 @@ var PersistentProcessRegistry = class {
|
|
|
1180
1214
|
this.lockPath = path3.join(homeDir, LOCKFILE);
|
|
1181
1215
|
this.baseRegistry = baseRegistry ?? getProcessRegistry();
|
|
1182
1216
|
this.ensureDirectory().catch((err) => {
|
|
1183
|
-
|
|
1217
|
+
emitStructuredLog("warn", "process_registry.dir_create_failed", "PersistentProcessRegistry: failed to create .wrongstack directory", err);
|
|
1184
1218
|
});
|
|
1185
1219
|
}
|
|
1186
1220
|
async ensureDirectory() {
|
|
@@ -1188,7 +1222,7 @@ var PersistentProcessRegistry = class {
|
|
|
1188
1222
|
try {
|
|
1189
1223
|
await fs2.mkdir(dir, { recursive: true });
|
|
1190
1224
|
} catch (err) {
|
|
1191
|
-
if (err.code !== "EEXIST") throw err;
|
|
1225
|
+
if (!isNodeError(err) || err.code !== "EEXIST") throw err;
|
|
1192
1226
|
}
|
|
1193
1227
|
}
|
|
1194
1228
|
/**
|
|
@@ -1268,6 +1302,7 @@ var PersistentProcessRegistry = class {
|
|
|
1268
1302
|
try {
|
|
1269
1303
|
const data = await readRegistryFile(this.registryPath);
|
|
1270
1304
|
data.instances.set(String(entry.pid), entry);
|
|
1305
|
+
const child = null;
|
|
1271
1306
|
this.baseRegistry.register({
|
|
1272
1307
|
pid: entry.pid,
|
|
1273
1308
|
name: entry.name,
|
|
@@ -1275,8 +1310,7 @@ var PersistentProcessRegistry = class {
|
|
|
1275
1310
|
startedAt: entry.startedAt,
|
|
1276
1311
|
sessionId: entry.sessionId,
|
|
1277
1312
|
protected: entry.protected,
|
|
1278
|
-
child
|
|
1279
|
-
// Main process has no child handle
|
|
1313
|
+
child
|
|
1280
1314
|
});
|
|
1281
1315
|
await writeRegistryFile(this.registryPath, data);
|
|
1282
1316
|
} finally {
|
|
@@ -1324,7 +1358,7 @@ var PersistentProcessRegistry = class {
|
|
|
1324
1358
|
data.lastCleanup = now;
|
|
1325
1359
|
await writeRegistryFile(this.registryPath, data);
|
|
1326
1360
|
} catch (err) {
|
|
1327
|
-
|
|
1361
|
+
emitStructuredLog("warn", "process_registry.sync_failed", "PersistentProcessRegistry: sync failed", err);
|
|
1328
1362
|
} finally {
|
|
1329
1363
|
await release();
|
|
1330
1364
|
}
|
|
@@ -1345,7 +1379,11 @@ var PersistentProcessRegistry = class {
|
|
|
1345
1379
|
if (process.platform !== "win32") {
|
|
1346
1380
|
process.kill(entry.pid, 0);
|
|
1347
1381
|
} else {
|
|
1348
|
-
|
|
1382
|
+
emitStructuredLog(
|
|
1383
|
+
"debug",
|
|
1384
|
+
"process_registry.stale_pid_check",
|
|
1385
|
+
`PersistentProcessRegistry: checking stale pid ${entry.pid} (${age}ms old)`
|
|
1386
|
+
);
|
|
1349
1387
|
}
|
|
1350
1388
|
} catch {
|
|
1351
1389
|
stalePids.push(_pidStr);
|
|
@@ -1359,7 +1397,7 @@ var PersistentProcessRegistry = class {
|
|
|
1359
1397
|
await writeRegistryFile(this.registryPath, data);
|
|
1360
1398
|
}
|
|
1361
1399
|
} catch (err) {
|
|
1362
|
-
|
|
1400
|
+
emitStructuredLog("warn", "process_registry.cleanup_failed", "PersistentProcessRegistry: cleanup failed", err);
|
|
1363
1401
|
} finally {
|
|
1364
1402
|
await release();
|
|
1365
1403
|
}
|
|
@@ -1582,7 +1620,7 @@ async function isKillProtected(kill) {
|
|
|
1582
1620
|
const entries = await getProtectedEntries();
|
|
1583
1621
|
const killNameLower = kill.name.toLowerCase();
|
|
1584
1622
|
for (const entry of entries) {
|
|
1585
|
-
if (entry.name
|
|
1623
|
+
if (entry.name?.toLowerCase().includes(killNameLower)) {
|
|
1586
1624
|
return true;
|
|
1587
1625
|
}
|
|
1588
1626
|
}
|
|
@@ -1637,6 +1675,110 @@ async function checkAndBlockKillCommand(command) {
|
|
|
1637
1675
|
}
|
|
1638
1676
|
return { blocked: false };
|
|
1639
1677
|
}
|
|
1678
|
+
function pickShell(platform3, command, env) {
|
|
1679
|
+
const override = env.get("WRONGSTACK_SHELL")?.trim().toLowerCase();
|
|
1680
|
+
if (override === "cmd" || override === "cmd.exe") return "cmd";
|
|
1681
|
+
if (override === "powershell" || override === "powershell.exe") return "powershell";
|
|
1682
|
+
if (override === "pwsh" || override === "pwsh.exe") return "pwsh";
|
|
1683
|
+
if (looksLikePowerShell(command)) return "pwsh";
|
|
1684
|
+
return "cmd";
|
|
1685
|
+
}
|
|
1686
|
+
function looksLikePowerShell(command) {
|
|
1687
|
+
if (!command) return false;
|
|
1688
|
+
const trimmed = command.trimStart();
|
|
1689
|
+
if (/\.ps1\b/i.test(trimmed)) return true;
|
|
1690
|
+
if (/^\s*#requires\s/i.test(trimmed)) return true;
|
|
1691
|
+
if (/^\s*param\s*\(/i.test(trimmed)) return true;
|
|
1692
|
+
if (/\$[\w:{]/i.test(trimmed)) return true;
|
|
1693
|
+
if (/\$\(/.test(trimmed)) return true;
|
|
1694
|
+
if (/@\s*['"]/.test(trimmed)) return true;
|
|
1695
|
+
if (/&\s+\$/.test(trimmed)) return true;
|
|
1696
|
+
if (/(^|\s)@\s*\(/.test(trimmed)) return true;
|
|
1697
|
+
if (/(^|\s)@\{/.test(trimmed)) return true;
|
|
1698
|
+
if (/(?:^|[\s\[\(\{,;])(?:-eq|-ne|-lt|-gt|-le|-ge|-like|-notlike|-match|-notmatch|-contains|-notcontains|-in|-notin|-and|-or|-not|-band|-bor|-bxor|-replace|-isplit|-csplit|-osplit|-join|-is|-as|-f)(?:$|[\s\]\)\},;])/i.test(trimmed)) {
|
|
1699
|
+
return true;
|
|
1700
|
+
}
|
|
1701
|
+
if (PS_VERB_RE.test(trimmed)) return true;
|
|
1702
|
+
if (/(?:^|[\s;&|])(gci|gi|gp|gcm|gps|sl|rm|cat|cp|mv)\b/i.test(trimmed)) {
|
|
1703
|
+
return true;
|
|
1704
|
+
}
|
|
1705
|
+
if (looksLikePowerShellExtended(command)) return true;
|
|
1706
|
+
return false;
|
|
1707
|
+
}
|
|
1708
|
+
function looksLikePowerShellExtended(command) {
|
|
1709
|
+
if (!command) return false;
|
|
1710
|
+
const trimmed = command.trimStart();
|
|
1711
|
+
if (/(?:^|\s)[-/](?:WhatIf|Confirm|ErrorAction)(?::[^\s]+|\s|=|$)/i.test(trimmed)) {
|
|
1712
|
+
return true;
|
|
1713
|
+
}
|
|
1714
|
+
if (/(?:^|[\s;&|])(Where-Object|ForEach-Object|Select-Object|Sort-Object|Group-Object|Measure-Object|Compare-Object|Tee-Object)(?:\s|$)/i.test(trimmed)) {
|
|
1715
|
+
return true;
|
|
1716
|
+
}
|
|
1717
|
+
if (/\bWrite-(?:Host|Output|Error|Warning|Verbose|Debug|Information)(?:\s|$)/i.test(trimmed)) {
|
|
1718
|
+
return true;
|
|
1719
|
+
}
|
|
1720
|
+
if (/HK(?:LM|CU|CR|U|CC|DD|PD):\\/i.test(trimmed)) return true;
|
|
1721
|
+
if (/\[(?:string|int|bool|xml|double|float|decimal|char|byte|long|System\.)/i.test(trimmed)) {
|
|
1722
|
+
return true;
|
|
1723
|
+
}
|
|
1724
|
+
if (/^\s*<#|#>\s*$/m.test(trimmed)) return true;
|
|
1725
|
+
if (/(?:^|\s)[-/\/](?:AsPlainText|PipelineVariable|pv|FilterHashtable|OutVariable|ov)(?:\s|=|$)/i.test(trimmed)) {
|
|
1726
|
+
return true;
|
|
1727
|
+
}
|
|
1728
|
+
return false;
|
|
1729
|
+
}
|
|
1730
|
+
function wrapPowerShellScript(command) {
|
|
1731
|
+
const bootstrap = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8;$ConfirmPreference='None';$WhatIfPreference=$false";
|
|
1732
|
+
return "\uFEFF" + bootstrap + "\ntry {\n" + command + "\n} finally { exit $LASTEXITCODE }";
|
|
1733
|
+
}
|
|
1734
|
+
var PS_VERB_RE = new RegExp(
|
|
1735
|
+
// Boundaries: start-of-string, whitespace, `;`, `&`, `|`, `(`, `{`, `,`.
|
|
1736
|
+
"(?:^|[\\s;&|\\(\\{,])(?:Get|Set|New|Remove|Add|Clear|Copy|Move|Rename|Test|Update|Write|Read|Push|Pop|Invoke|Start|Stop|Wait|Out|Format|Group|Measure|Compare|Resolve|ConvertTo|ConvertFrom|Convert|Import|Export|Select|Where|ForEach|Sort|Tee|Split|Join|Limit|Skip|Step|Trace|Debug|Register|Unregister|Enable|Disable|Restart|Suspend|Resume|Save|Open|Close|Lock|Unlock|Mount|Dismount|Enter|Exit|Use|Show|Hide|Find|Search|Watch|Initialize|Optimize|Compress|Expand|Merge|Checkpoint|Undo|Redo|Approve|Deny|Block|Grant|Revoke|Assert|Confirm|Receive|Send|Connect|Disconnect|Reset|Backup|Restore|Publish|Unpublish|Install|Uninstall|Build|Rebuild|Deploy|Submit|Process|Complete|Approve|Revoke|Pay|Refund|Decline|Receive|Send)-[A-Za-z][A-Za-z0-9]+(?:[\\-\\+][A-Za-z][A-Za-z0-9]+)*(?:$|[\\s\\-\\;\\&\\|\\(\\)\\{\\},])",
|
|
1737
|
+
"i"
|
|
1738
|
+
);
|
|
1739
|
+
function shellArgs(shell) {
|
|
1740
|
+
if (shell === "powershell" || shell === "pwsh") {
|
|
1741
|
+
return ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", "-"];
|
|
1742
|
+
}
|
|
1743
|
+
return ["/c"];
|
|
1744
|
+
}
|
|
1745
|
+
function diagnoseBashism(command, shell) {
|
|
1746
|
+
if (!command) return void 0;
|
|
1747
|
+
const isCmd = shell === "cmd";
|
|
1748
|
+
const hints = [];
|
|
1749
|
+
const add = (h) => {
|
|
1750
|
+
if (!hints.includes(h)) hints.push(h);
|
|
1751
|
+
};
|
|
1752
|
+
if (/\/dev\/null/.test(command)) {
|
|
1753
|
+
add(
|
|
1754
|
+
isCmd ? "use `nul` instead of `/dev/null` (e.g. `2>nul`)" : "use `$null` instead of `/dev/null` (e.g. `2>$null`)"
|
|
1755
|
+
);
|
|
1756
|
+
}
|
|
1757
|
+
if (/(^|[;&|]\s*)export\s+[A-Za-z_]\w*=/.test(command)) {
|
|
1758
|
+
add(
|
|
1759
|
+
isCmd ? "set env vars with `set NAME=value`, not `export`" : "set env vars with `$env:NAME = 'value'`, not `export`"
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
if (/<<-?\s*['"]?[A-Za-z_]\w*/.test(command)) {
|
|
1763
|
+
add(
|
|
1764
|
+
isCmd ? "cmd has no heredocs \u2014 write the content to a file or use multiple `echo` lines" : "PowerShell has no heredocs \u2014 use a single-quoted here-string `@'\u2026'@` (closing `'@` at column 0)"
|
|
1765
|
+
);
|
|
1766
|
+
}
|
|
1767
|
+
if (shell === "powershell" && /(&&|\|\|)/.test(command)) {
|
|
1768
|
+
add("Windows PowerShell 5.1 has no `&&`/`||` \u2014 separate commands with `;` (check `$LASTEXITCODE`)");
|
|
1769
|
+
}
|
|
1770
|
+
if (/\brm\s+-[A-Za-z]*[rf]/.test(command)) {
|
|
1771
|
+
add(
|
|
1772
|
+
isCmd ? "`rm` is not a cmd builtin \u2014 use `del` (files) or `rmdir /s /q` (dirs)" : "use `Remove-Item -Recurse -Force` \u2014 the `rm -rf` bash flags don't exist in PowerShell"
|
|
1773
|
+
);
|
|
1774
|
+
}
|
|
1775
|
+
if (/\bwhich\s+\S/.test(command)) {
|
|
1776
|
+
add(isCmd ? "use `where <cmd>` instead of `which`" : "use `Get-Command <cmd>` instead of `which`");
|
|
1777
|
+
}
|
|
1778
|
+
if (hints.length === 0) return void 0;
|
|
1779
|
+
const label = isCmd ? "cmd.exe" : shell === "pwsh" ? "PowerShell 7" : "Windows PowerShell";
|
|
1780
|
+
return `[wrongstack] This command failed and contains bash/POSIX syntax that ${label} does not accept \u2014 ${hints.join("; ")}. Rewrite it in ${isCmd ? "cmd" : "PowerShell"} syntax and retry.`;
|
|
1781
|
+
}
|
|
1640
1782
|
|
|
1641
1783
|
// src/bash.ts
|
|
1642
1784
|
var MAX_OUTPUT = 32768;
|
|
@@ -1733,18 +1875,36 @@ var bashTool = {
|
|
|
1733
1875
|
}
|
|
1734
1876
|
const timeoutMs = Math.max(1, Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT_MS, 6e5));
|
|
1735
1877
|
const isWin3 = os2.platform() === "win32";
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1878
|
+
let plan;
|
|
1879
|
+
let winShellKind;
|
|
1880
|
+
if (isWin3) {
|
|
1881
|
+
const shell2 = pickShell("win32", input.command, {
|
|
1882
|
+
get: (k) => process.env[k]
|
|
1883
|
+
});
|
|
1884
|
+
winShellKind = shell2;
|
|
1885
|
+
const bin = shell2 === "powershell" ? resolvePowerShell("powershell.exe") : shell2 === "pwsh" ? resolvePowerShell("pwsh.exe") : process.env["COMSPEC"] ?? "cmd.exe";
|
|
1886
|
+
plan = {
|
|
1887
|
+
bin,
|
|
1888
|
+
argv: shellArgs(shell2),
|
|
1889
|
+
useStdin: shell2 === "powershell" || shell2 === "pwsh",
|
|
1890
|
+
stdinBody: shell2 === "powershell" || shell2 === "pwsh" ? wrapPowerShellScript(input.command) : void 0
|
|
1891
|
+
};
|
|
1892
|
+
} else {
|
|
1893
|
+
const explicit = process.env["WRONGSTACK_SHELL"];
|
|
1894
|
+
let bin;
|
|
1895
|
+
if (explicit) bin = explicit;
|
|
1896
|
+
else {
|
|
1897
|
+
const fromEnv = process.env["SHELL"];
|
|
1898
|
+
if (fromEnv) {
|
|
1899
|
+
const name = fromEnv.split("/").pop() ?? "";
|
|
1900
|
+
if (["bash", "zsh", "sh", "dash", "fish"].includes(name)) bin = fromEnv;
|
|
1901
|
+
else bin = "/bin/bash";
|
|
1902
|
+
} else bin = "/bin/bash";
|
|
1903
|
+
}
|
|
1904
|
+
plan = { bin, argv: ["-c"], useStdin: false, stdinBody: void 0 };
|
|
1905
|
+
}
|
|
1906
|
+
const shell = plan.bin;
|
|
1907
|
+
const args = plan.useStdin ? [...plan.argv] : [...plan.argv, input.command];
|
|
1748
1908
|
const env = buildChildEnv(ctx.session?.id);
|
|
1749
1909
|
const detached = !isWin3;
|
|
1750
1910
|
const startedAt = Date.now();
|
|
@@ -1754,7 +1914,9 @@ var bashTool = {
|
|
|
1754
1914
|
const child2 = spawn(shell, args, {
|
|
1755
1915
|
cwd: ctx.projectRoot,
|
|
1756
1916
|
env,
|
|
1757
|
-
|
|
1917
|
+
// PowerShell takes the script on stdin (no argv quoting); cmd.exe
|
|
1918
|
+
// and POSIX shells ignore stdin when given the command inline.
|
|
1919
|
+
stdio: [plan.useStdin ? "pipe" : "ignore", "pipe", "pipe"],
|
|
1758
1920
|
// win32: CreateProcess IGNORES CREATE_NO_WINDOW (windowsHide) when
|
|
1759
1921
|
// DETACHED_PROCESS (detached: true) is set, so the console-less
|
|
1760
1922
|
// cmd.exe's grandchildren (node, dev servers) each allocate a fresh
|
|
@@ -1765,6 +1927,13 @@ var bashTool = {
|
|
|
1765
1927
|
detached: !isWin3,
|
|
1766
1928
|
windowsHide: true
|
|
1767
1929
|
});
|
|
1930
|
+
if (plan.useStdin) {
|
|
1931
|
+
try {
|
|
1932
|
+
child2.stdin?.write(plan.stdinBody ?? input.command);
|
|
1933
|
+
child2.stdin?.end();
|
|
1934
|
+
} catch {
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1768
1937
|
const pid2 = child2.pid;
|
|
1769
1938
|
if (typeof pid2 === "number") {
|
|
1770
1939
|
registry.register({
|
|
@@ -1819,11 +1988,20 @@ var bashTool = {
|
|
|
1819
1988
|
const child = spawn(shell, args, {
|
|
1820
1989
|
cwd: ctx.projectRoot,
|
|
1821
1990
|
env,
|
|
1822
|
-
|
|
1991
|
+
// PowerShell takes the script on stdin (no argv quoting); cmd.exe
|
|
1992
|
+
// and POSIX shells ignore stdin when given the command inline.
|
|
1993
|
+
stdio: [plan.useStdin ? "pipe" : "ignore", "pipe", "pipe"],
|
|
1823
1994
|
detached,
|
|
1824
1995
|
windowsHide: true,
|
|
1825
1996
|
...isWin3 ? {} : { signal: opts.signal }
|
|
1826
1997
|
});
|
|
1998
|
+
if (plan.useStdin) {
|
|
1999
|
+
try {
|
|
2000
|
+
child.stdin?.write(plan.stdinBody ?? input.command);
|
|
2001
|
+
child.stdin?.end();
|
|
2002
|
+
} catch {
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
1827
2005
|
const pid = child.pid;
|
|
1828
2006
|
if (typeof pid === "number") {
|
|
1829
2007
|
registry.register({
|
|
@@ -1974,10 +2152,13 @@ var bashTool = {
|
|
|
1974
2152
|
yield { type: "partial_output", text: remainder };
|
|
1975
2153
|
}
|
|
1976
2154
|
const spooled = spool.finalize();
|
|
2155
|
+
const hint = !timedOut && typeof c.code === "number" && c.code !== 0 && winShellKind ? diagnoseBashism(input.command, winShellKind) : void 0;
|
|
1977
2156
|
yield {
|
|
1978
2157
|
type: "final",
|
|
1979
2158
|
output: {
|
|
1980
|
-
output: normalizeCommandOutput(buf) + (spooled ? spoolNote(spooled) : "")
|
|
2159
|
+
output: normalizeCommandOutput(buf) + (spooled ? spoolNote(spooled) : "") + (hint ? `
|
|
2160
|
+
|
|
2161
|
+
${hint}` : ""),
|
|
1981
2162
|
exit_code: c.code,
|
|
1982
2163
|
timed_out: timedOut
|
|
1983
2164
|
}
|
|
@@ -2346,7 +2527,7 @@ function loadDatabaseSync() {
|
|
|
2346
2527
|
DatabaseSyncCtor = req("node:sqlite").DatabaseSync;
|
|
2347
2528
|
} catch (err) {
|
|
2348
2529
|
throw new Error(
|
|
2349
|
-
`The codebase index needs Node's built-in SQLite (node:sqlite), available since Node 22.5. This runtime doesn't provide it: ${toErrorMessage$
|
|
2530
|
+
`The codebase index needs Node's built-in SQLite (node:sqlite), available since Node 22.5. This runtime doesn't provide it: ${toErrorMessage$3(err)}`
|
|
2350
2531
|
);
|
|
2351
2532
|
}
|
|
2352
2533
|
return DatabaseSyncCtor;
|
|
@@ -3683,11 +3864,15 @@ async function tryNativeParse(file, content) {
|
|
|
3683
3864
|
const crateDir = path3.join(toolsDir, "syn-parser");
|
|
3684
3865
|
const tmpFile = path3.join(crateDir, "src", "input.rs");
|
|
3685
3866
|
await fs2.writeFile(tmpFile, content, "utf8");
|
|
3686
|
-
const proc = spawn(
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3867
|
+
const proc = spawn(
|
|
3868
|
+
"cargo",
|
|
3869
|
+
["run", "--manifest-path", path3.join(toolsDir, "Cargo.toml")],
|
|
3870
|
+
{
|
|
3871
|
+
cwd: process.cwd(),
|
|
3872
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
3873
|
+
windowsHide: true
|
|
3874
|
+
}
|
|
3875
|
+
);
|
|
3691
3876
|
let stdout = "";
|
|
3692
3877
|
proc.stdout?.on("data", (chunk) => {
|
|
3693
3878
|
stdout += chunk.toString();
|
|
@@ -5359,49 +5544,71 @@ function findSimilarity(haystack, needle) {
|
|
|
5359
5544
|
return line;
|
|
5360
5545
|
}
|
|
5361
5546
|
var isWin2 = process.platform === "win32";
|
|
5362
|
-
var
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5547
|
+
var DEFAULT_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
|
|
5548
|
+
// JS / TS toolchain
|
|
5549
|
+
"node",
|
|
5550
|
+
"npm",
|
|
5551
|
+
"pnpm",
|
|
5552
|
+
"yarn",
|
|
5553
|
+
"npx",
|
|
5554
|
+
"bun",
|
|
5555
|
+
"deno",
|
|
5556
|
+
"tsc",
|
|
5557
|
+
"vitest",
|
|
5558
|
+
"jest",
|
|
5559
|
+
"biome",
|
|
5560
|
+
"eslint",
|
|
5561
|
+
"prettier",
|
|
5562
|
+
// version control
|
|
5563
|
+
"git",
|
|
5564
|
+
// Rust
|
|
5565
|
+
"cargo",
|
|
5566
|
+
"rustc",
|
|
5567
|
+
// Go
|
|
5568
|
+
"go",
|
|
5569
|
+
// Python
|
|
5570
|
+
"python",
|
|
5571
|
+
"python3",
|
|
5572
|
+
"pip",
|
|
5573
|
+
"pip3",
|
|
5574
|
+
// Ruby
|
|
5575
|
+
"ruby",
|
|
5576
|
+
"gem",
|
|
5577
|
+
"bundle",
|
|
5578
|
+
// JVM
|
|
5579
|
+
"java",
|
|
5580
|
+
"javac",
|
|
5581
|
+
"mvn",
|
|
5582
|
+
"gradle",
|
|
5583
|
+
"gradlew",
|
|
5584
|
+
// .NET
|
|
5585
|
+
"dotnet",
|
|
5586
|
+
// C / C++ / native build
|
|
5587
|
+
"make",
|
|
5588
|
+
"cmake",
|
|
5589
|
+
// containers / orchestration (read-only subcommands; see BLOCKED_ARG_PATTERNS)
|
|
5590
|
+
"docker",
|
|
5591
|
+
"kubectl",
|
|
5592
|
+
// common POSIX file/text utilities
|
|
5593
|
+
"ls",
|
|
5594
|
+
"cat",
|
|
5595
|
+
"head",
|
|
5596
|
+
"tail",
|
|
5597
|
+
"wc",
|
|
5598
|
+
"grep",
|
|
5599
|
+
"find",
|
|
5600
|
+
"echo",
|
|
5601
|
+
"mkdir",
|
|
5602
|
+
"cp",
|
|
5603
|
+
"mv",
|
|
5604
|
+
"rm",
|
|
5605
|
+
"touch"
|
|
5606
|
+
]);
|
|
5607
|
+
var allowedCommands = new Set(DEFAULT_ALLOWED_COMMANDS);
|
|
5608
|
+
var normalizeCmd = (c) => c.trim();
|
|
5609
|
+
function isExecCommandAllowed(cmd) {
|
|
5610
|
+
return allowedCommands.has(normalizeCmd(cmd));
|
|
5611
|
+
}
|
|
5405
5612
|
var MAX_ARGS = 20;
|
|
5406
5613
|
var MAX_OUTPUT2 = 2e5;
|
|
5407
5614
|
var DEFAULT_TIMEOUT_MS2 = 3e4;
|
|
@@ -5463,7 +5670,7 @@ var execTool = {
|
|
|
5463
5670
|
name: "exec",
|
|
5464
5671
|
category: "Shell",
|
|
5465
5672
|
description: "Execute a **whitelisted, restricted set of commands** with strict argument validation. This is the **preferred and safer** alternative to the `bash` tool for running development tools (node, npm, pnpm, tsc, git, tests, linters, etc.). It prevents arbitrary command injection and limits what the model can do.",
|
|
5466
|
-
usageHint: "PREFERRED SHELL TOOL for most cases.\n\nUse this instead of `bash` whenever possible.\n- `command` must be
|
|
5673
|
+
usageHint: "PREFERRED SHELL TOOL for most cases.\n\nUse this instead of `bash` whenever possible.\n- `command` must be in the allowlist. Defaults cover JS (node/npm/pnpm/yarn/bun/deno/tsc/vitest/eslint/biome), Go (`go build`/`go test`), Rust (cargo), Python (python/pip), Ruby (gem/bundle), JVM (java/mvn/gradle), .NET (dotnet), native (make/cmake), and git. Users can extend it via `tools.exec.allow` in config.\n- Arguments are passed as a clean array (no shell interpretation).\n- `cwd` is validated to stay inside the project.\n- If a command is not allowlisted, the error explains how to add it; for one-off arbitrary commands, fall back to `bash` (with strong justification).\nThis tool significantly reduces the risk compared to full shell access.",
|
|
5467
5674
|
permission: "confirm",
|
|
5468
5675
|
mutating: true,
|
|
5469
5676
|
riskTier: "standard",
|
|
@@ -5517,12 +5724,12 @@ var execTool = {
|
|
|
5517
5724
|
truncated: false,
|
|
5518
5725
|
allowed: false
|
|
5519
5726
|
};
|
|
5520
|
-
if (!(cmd
|
|
5727
|
+
if (!isExecCommandAllowed(cmd)) {
|
|
5521
5728
|
return {
|
|
5522
5729
|
command: cmd,
|
|
5523
5730
|
args: input.args ?? [],
|
|
5524
5731
|
stdout: "",
|
|
5525
|
-
stderr: `Command "${cmd}" not in allowlist.
|
|
5732
|
+
stderr: `Command "${cmd}" not in allowlist. Add it to your ~/.wrongstack/config.json under "tools": { "exec": { "allow": ["${cmd}"] } }, or use the bash tool for one-off arbitrary commands.`,
|
|
5526
5733
|
exitCode: 1,
|
|
5527
5734
|
truncated: false,
|
|
5528
5735
|
allowed: false
|
|
@@ -5565,19 +5772,58 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
5565
5772
|
let stdout = "";
|
|
5566
5773
|
let stderr = "";
|
|
5567
5774
|
let killed = false;
|
|
5775
|
+
const resolvedOnce = { value: false };
|
|
5776
|
+
const finish = (result) => {
|
|
5777
|
+
if (resolvedOnce.value) return;
|
|
5778
|
+
resolvedOnce.value = true;
|
|
5779
|
+
resolve6(result);
|
|
5780
|
+
};
|
|
5568
5781
|
const startedAt = Date.now();
|
|
5569
5782
|
const spool = createOutputSpool({ tool: `exec-${cmd}`, thresholdBytes: MAX_OUTPUT2 });
|
|
5570
5783
|
const resolved = resolveWin32Command(cmd);
|
|
5571
5784
|
const needsShell = isWin2 && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
|
|
5572
5785
|
const spawnCmd = needsShell ? cmd : resolved;
|
|
5573
5786
|
if (needsShell) assertSafeWin32ShellArgs(args);
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5787
|
+
let child;
|
|
5788
|
+
try {
|
|
5789
|
+
child = spawn(spawnCmd, args, {
|
|
5790
|
+
cwd,
|
|
5791
|
+
env: buildChildEnv(sessionId),
|
|
5792
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
5793
|
+
windowsHide: true,
|
|
5794
|
+
...isWin2 ? {} : { signal },
|
|
5795
|
+
...needsShell ? { shell: true, windowsVerbatimArguments: true } : {}
|
|
5796
|
+
});
|
|
5797
|
+
} catch (err) {
|
|
5798
|
+
spool.finalize();
|
|
5799
|
+
finish({
|
|
5800
|
+
command: cmd,
|
|
5801
|
+
args,
|
|
5802
|
+
stdout: "",
|
|
5803
|
+
stderr: `spawn failed: ${toErrorMessage$1(err)}`,
|
|
5804
|
+
exitCode: 1,
|
|
5805
|
+
truncated: false,
|
|
5806
|
+
allowed: true
|
|
5807
|
+
});
|
|
5808
|
+
return;
|
|
5809
|
+
}
|
|
5810
|
+
child.on("error", (err) => {
|
|
5811
|
+
const isAbort = err && err.code === "ABORT_ERR";
|
|
5812
|
+
const stderrText = isAbort ? `Aborted: ${err.message}` : err.message;
|
|
5813
|
+
clearTimeout(timer);
|
|
5814
|
+
if (isWin2) signal.removeEventListener("abort", onAbort);
|
|
5815
|
+
if (typeof pid === "number") registry.unregister(pid);
|
|
5816
|
+
registry.afterCall(Date.now() - startedAt, true);
|
|
5817
|
+
spool.finalize();
|
|
5818
|
+
finish({
|
|
5819
|
+
command: cmd,
|
|
5820
|
+
args,
|
|
5821
|
+
stdout: normalizeCommandOutput(stdout),
|
|
5822
|
+
stderr: stderrText,
|
|
5823
|
+
exitCode: isAbort ? 124 : 1,
|
|
5824
|
+
truncated: Buffer.byteLength(stdout, "utf8") > COMMAND_OUTPUT_MAX_BYTES,
|
|
5825
|
+
allowed: true
|
|
5826
|
+
});
|
|
5581
5827
|
});
|
|
5582
5828
|
const registry = getProcessRegistry();
|
|
5583
5829
|
const pid = child.pid;
|
|
@@ -5617,7 +5863,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
5617
5863
|
const exitCode = killed ? 124 : code ?? 1;
|
|
5618
5864
|
registry.afterCall(durationMs, exitCode !== 0);
|
|
5619
5865
|
const spooled = spool.finalize();
|
|
5620
|
-
|
|
5866
|
+
finish({
|
|
5621
5867
|
command: cmd,
|
|
5622
5868
|
args,
|
|
5623
5869
|
stdout: normalizeCommandOutput(stdout) + (spooled ? spoolNote(spooled) : ""),
|
|
@@ -5627,22 +5873,6 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
5627
5873
|
allowed: true
|
|
5628
5874
|
});
|
|
5629
5875
|
});
|
|
5630
|
-
child.on("error", (err) => {
|
|
5631
|
-
clearTimeout(timer);
|
|
5632
|
-
if (isWin2) signal.removeEventListener("abort", onAbort);
|
|
5633
|
-
if (typeof pid === "number") registry.unregister(pid);
|
|
5634
|
-
registry.afterCall(Date.now() - startedAt, true);
|
|
5635
|
-
spool.finalize();
|
|
5636
|
-
resolve6({
|
|
5637
|
-
command: cmd,
|
|
5638
|
-
args,
|
|
5639
|
-
stdout: normalizeCommandOutput(stdout),
|
|
5640
|
-
stderr: err.message,
|
|
5641
|
-
exitCode: 1,
|
|
5642
|
-
truncated: Buffer.byteLength(stdout, "utf8") > COMMAND_OUTPUT_MAX_BYTES,
|
|
5643
|
-
allowed: true
|
|
5644
|
-
});
|
|
5645
|
-
});
|
|
5646
5876
|
});
|
|
5647
5877
|
}
|
|
5648
5878
|
var TD = new TurndownService({
|
|
@@ -7842,7 +8072,7 @@ var readTool = {
|
|
|
7842
8072
|
} catch (err) {
|
|
7843
8073
|
const code = err.code;
|
|
7844
8074
|
if (code === "ENOENT") throw new Error(`read: file not found "${input.path}"`);
|
|
7845
|
-
throw new Error(`read: failed to stat "${input.path}": ${toErrorMessage(err)}`);
|
|
8075
|
+
throw new Error(`read: failed to stat "${input.path}": ${toErrorMessage$2(err)}`);
|
|
7846
8076
|
}
|
|
7847
8077
|
if (!stat11.isFile()) throw new Error(`read: "${input.path}" is not a regular file`);
|
|
7848
8078
|
if (stat11.size > MAX_BYTES2) {
|
|
@@ -8017,7 +8247,7 @@ var replaceTool = {
|
|
|
8017
8247
|
if (err.code === "ENOENT") return null;
|
|
8018
8248
|
throw err;
|
|
8019
8249
|
});
|
|
8020
|
-
if (!lstat2
|
|
8250
|
+
if (!lstat2?.isFile()) continue;
|
|
8021
8251
|
if (lstat2.isSymbolicLink()) continue;
|
|
8022
8252
|
let realPath;
|
|
8023
8253
|
try {
|
|
@@ -8028,7 +8258,7 @@ var replaceTool = {
|
|
|
8028
8258
|
const rel = path3.relative(realRoot, realPath);
|
|
8029
8259
|
if (rel.startsWith("..") || path3.isAbsolute(rel)) continue;
|
|
8030
8260
|
const stat11 = await fs2.stat(realPath).catch(() => null);
|
|
8031
|
-
if (!stat11
|
|
8261
|
+
if (!stat11?.isFile()) continue;
|
|
8032
8262
|
let content;
|
|
8033
8263
|
try {
|
|
8034
8264
|
const buf = await fs2.readFile(realPath);
|
|
@@ -8438,7 +8668,7 @@ async function duckduckgoSearch(query2, num, signal) {
|
|
|
8438
8668
|
truncated: results.length >= num
|
|
8439
8669
|
};
|
|
8440
8670
|
} catch (err) {
|
|
8441
|
-
console.log(JSON.stringify({ level: "debug", event: "search_failed", query: query2, error: toErrorMessage$
|
|
8671
|
+
console.log(JSON.stringify({ level: "debug", event: "search_failed", query: query2, error: toErrorMessage$3(err) }));
|
|
8442
8672
|
return {
|
|
8443
8673
|
query: query2,
|
|
8444
8674
|
results: [{ title: "Search unavailable", url: "", snippet: "Could not reach DuckDuckGo" }],
|
|
@@ -8602,7 +8832,7 @@ var setWorkingDirTool = {
|
|
|
8602
8832
|
} catch (err) {
|
|
8603
8833
|
return {
|
|
8604
8834
|
current: ctx.workingDir,
|
|
8605
|
-
error: toErrorMessage$
|
|
8835
|
+
error: toErrorMessage$3(err)
|
|
8606
8836
|
};
|
|
8607
8837
|
}
|
|
8608
8838
|
try {
|
|
@@ -8752,7 +8982,11 @@ var taskTool = {
|
|
|
8752
8982
|
const newIds = new Set(input.tasks.map((t) => t.id));
|
|
8753
8983
|
if (newIds.size !== input.tasks.length) {
|
|
8754
8984
|
const seen = /* @__PURE__ */ new Set();
|
|
8755
|
-
const dupes = [...new Set(input.tasks.map((t) => t.id).filter((id) =>
|
|
8985
|
+
const dupes = [...new Set(input.tasks.map((t) => t.id).filter((id) => {
|
|
8986
|
+
if (seen.has(id)) return true;
|
|
8987
|
+
seen.add(id);
|
|
8988
|
+
return false;
|
|
8989
|
+
}))];
|
|
8756
8990
|
early = {
|
|
8757
8991
|
ok: false,
|
|
8758
8992
|
message: `action=replace has duplicate task IDs: ${dupes.join(", ")}. Each task id must be unique.`,
|
|
@@ -8787,7 +9021,7 @@ var taskTool = {
|
|
|
8787
9021
|
}
|
|
8788
9022
|
case "add": {
|
|
8789
9023
|
const t = input.task;
|
|
8790
|
-
if (!t
|
|
9024
|
+
if (!t?.title) {
|
|
8791
9025
|
early = { ok: false, message: "action=add requires `task` with at least `title`.", count: 0, completed: 0, inProgress: 0 };
|
|
8792
9026
|
return f;
|
|
8793
9027
|
}
|