blun-king-cli 9.1.427 → 9.1.429
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/bin/generated-source-health.cjs +142 -0
- package/bin/windows-bash-dialect-policy.cjs +25 -0
- package/blun.mjs +27 -5
- package/package.json +1 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
|
|
5
|
+
const JAVASCRIPT_LIKE_EXTENSIONS = new Set([
|
|
6
|
+
'.cjs',
|
|
7
|
+
'.cts',
|
|
8
|
+
'.js',
|
|
9
|
+
'.jsx',
|
|
10
|
+
'.mjs',
|
|
11
|
+
'.mts',
|
|
12
|
+
'.ts',
|
|
13
|
+
'.tsx',
|
|
14
|
+
]);
|
|
15
|
+
const DEFAULT_MIN_REPEATS = 8;
|
|
16
|
+
|
|
17
|
+
function findDegenerateGeneratedStatementRun(filePath, content, options = {}) {
|
|
18
|
+
if (!JAVASCRIPT_LIKE_EXTENSIONS.has(path.extname(filePath).toLowerCase())) return null;
|
|
19
|
+
if (typeof content !== 'string' || content.length === 0) return null;
|
|
20
|
+
|
|
21
|
+
const minRepeats = Number.isSafeInteger(options.minRepeats) && options.minRepeats >= 2
|
|
22
|
+
? options.minRepeats
|
|
23
|
+
: DEFAULT_MIN_REPEATS;
|
|
24
|
+
let state = 'code';
|
|
25
|
+
let quote = '';
|
|
26
|
+
let escaped = false;
|
|
27
|
+
let line = 1;
|
|
28
|
+
let statementStart = 0;
|
|
29
|
+
let statementStartLine = 1;
|
|
30
|
+
let parenDepth = 0;
|
|
31
|
+
let bracketDepth = 0;
|
|
32
|
+
let previous = null;
|
|
33
|
+
let runCount = 0;
|
|
34
|
+
let runLine = 1;
|
|
35
|
+
|
|
36
|
+
const resetBoundary = (nextStart) => {
|
|
37
|
+
statementStart = nextStart;
|
|
38
|
+
statementStartLine = line;
|
|
39
|
+
};
|
|
40
|
+
const recordStatement = (end) => {
|
|
41
|
+
const raw = content.slice(statementStart, end);
|
|
42
|
+
const rawStartLine = statementStartLine;
|
|
43
|
+
resetBoundary(end);
|
|
44
|
+
const leading = raw.match(/^\s*/u)?.[0] ?? '';
|
|
45
|
+
const statementLine = rawStartLine + (leading.match(/\n/gu)?.length ?? 0);
|
|
46
|
+
const normalized = raw.replace(/\s+/gu, ' ').trim();
|
|
47
|
+
if (normalized.length === 0 || !/[A-Za-z_$]/u.test(normalized)) {
|
|
48
|
+
previous = null;
|
|
49
|
+
runCount = 0;
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
if (normalized === previous) runCount += 1;
|
|
53
|
+
else {
|
|
54
|
+
previous = normalized;
|
|
55
|
+
runCount = 1;
|
|
56
|
+
runLine = statementLine;
|
|
57
|
+
}
|
|
58
|
+
if (runCount < minRepeats) return null;
|
|
59
|
+
return {
|
|
60
|
+
count: runCount,
|
|
61
|
+
line: runLine,
|
|
62
|
+
preview: normalized.slice(0, 160),
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
67
|
+
const current = content[index] ?? '';
|
|
68
|
+
const next = content[index + 1] ?? '';
|
|
69
|
+
|
|
70
|
+
if (state === 'line-comment') {
|
|
71
|
+
if (current === '\n') {
|
|
72
|
+
line += 1;
|
|
73
|
+
state = 'code';
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (state === 'block-comment') {
|
|
78
|
+
if (current === '\n') line += 1;
|
|
79
|
+
if (current === '*' && next === '/') {
|
|
80
|
+
state = 'code';
|
|
81
|
+
index += 1;
|
|
82
|
+
}
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (state === 'quote') {
|
|
86
|
+
if (current === '\n') line += 1;
|
|
87
|
+
if (escaped) {
|
|
88
|
+
escaped = false;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (current === '\\') {
|
|
92
|
+
escaped = true;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (current === quote) {
|
|
96
|
+
state = 'code';
|
|
97
|
+
quote = '';
|
|
98
|
+
}
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (current === '\n') {
|
|
103
|
+
line += 1;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (current === '/' && next === '/') {
|
|
107
|
+
state = 'line-comment';
|
|
108
|
+
index += 1;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (current === '/' && next === '*') {
|
|
112
|
+
state = 'block-comment';
|
|
113
|
+
index += 1;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (current === "'" || current === '"' || current === '`') {
|
|
117
|
+
state = 'quote';
|
|
118
|
+
quote = current;
|
|
119
|
+
escaped = false;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (current === '(') parenDepth += 1;
|
|
123
|
+
else if (current === ')') parenDepth = Math.max(0, parenDepth - 1);
|
|
124
|
+
else if (current === '[') bracketDepth += 1;
|
|
125
|
+
else if (current === ']') bracketDepth = Math.max(0, bracketDepth - 1);
|
|
126
|
+
else if ((current === '{' || current === '}') && parenDepth === 0 && bracketDepth === 0) {
|
|
127
|
+
previous = null;
|
|
128
|
+
runCount = 0;
|
|
129
|
+
resetBoundary(index + 1);
|
|
130
|
+
} else if (current === ';' && parenDepth === 0 && bracketDepth === 0) {
|
|
131
|
+
const finding = recordStatement(index + 1);
|
|
132
|
+
if (finding !== null) return finding;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = {
|
|
140
|
+
DEFAULT_MIN_REPEATS,
|
|
141
|
+
findDegenerateGeneratedStatementRun,
|
|
142
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const EXPLICIT_CMD_HANDOFF = /(?:^|[;&|]\s*)cmd(?:\.exe)?\s+(?:(?:\/[dqs])\s+)*\/c(?=\s|$)/iu;
|
|
4
|
+
const CMD_MARKERS = [
|
|
5
|
+
['cd /d', /(?:^|[;&|]\s*)cd\s+\/d(?=\s|$)/iu],
|
|
6
|
+
['dir /s /b', /(?:^|[;&|]\s*)dir\s+\/s\s+\/b(?=\s|$)/iu],
|
|
7
|
+
['find /c /v', /(?:^|[;&|]\s*)find\s+\/c\s+\/v(?=\s|$)/iu],
|
|
8
|
+
['2>nul', /(?:^|[\s;|&])(?:[012]?>|&>)\s*nul(?=$|[\s;|&])/iu],
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
function windowsBashDialectRefusal({ command, isWindowsBash = false } = {}) {
|
|
12
|
+
if (!isWindowsBash) return undefined;
|
|
13
|
+
const source = String(command || '');
|
|
14
|
+
if (EXPLICIT_CMD_HANDOFF.test(source)) return undefined;
|
|
15
|
+
|
|
16
|
+
const found = CMD_MARKERS.filter(([, pattern]) => pattern.test(source)).map(([label]) => label);
|
|
17
|
+
if (found.length === 0) return undefined;
|
|
18
|
+
|
|
19
|
+
return `Refused Windows cmd.exe syntax in the Bash tool before execution (found: ${found.join(', ')}). `
|
|
20
|
+
+ 'This tool runs POSIX Bash on Windows, including inside VS Code. Pass the directory with cwd, '
|
|
21
|
+
+ 'use Bash paths and commands such as find or rg, and redirect to /dev/null. '
|
|
22
|
+
+ 'If cmd semantics are intentional, invoke cmd.exe /d /s /c explicitly.';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = { windowsBashDialectRefusal };
|
package/blun.mjs
CHANGED
|
@@ -253564,7 +253564,7 @@ function replaceOnceLiteral(content, oldString, newString) {
|
|
|
253564
253564
|
if (index === -1) return content;
|
|
253565
253565
|
return content.slice(0, index) + newString + content.slice(index + oldString.length);
|
|
253566
253566
|
}
|
|
253567
|
-
var EditInputSchema, EditTool;
|
|
253567
|
+
var EditInputSchema, EditTool, findDegenerateGeneratedStatementRun;
|
|
253568
253568
|
var init_edit = __esmMin((() => {
|
|
253569
253569
|
init_zod$1();
|
|
253570
253570
|
init_tool_access();
|
|
@@ -253575,6 +253575,7 @@ var init_edit = __esmMin((() => {
|
|
|
253575
253575
|
init_source_file_line_limit();
|
|
253576
253576
|
init_edit$1();
|
|
253577
253577
|
init_lsp_diagnostics();
|
|
253578
|
+
({ findDegenerateGeneratedStatementRun } = createRequire(import.meta.url)("./bin/generated-source-health.cjs"));
|
|
253578
253579
|
EditInputSchema = object({
|
|
253579
253580
|
path: string().describe("Path to the text file to edit. Relative paths resolve against the working directory; a path outside the working directory must be absolute."),
|
|
253580
253581
|
old_string: string().min(1).describe("Exact content to replace from the Read output view, without the line-number prefix. Use LF for pure CRLF files; use actual \\r escapes where Read shows \\r."),
|
|
@@ -253582,7 +253583,7 @@ var init_edit = __esmMin((() => {
|
|
|
253582
253583
|
replace_all: boolean$1().optional().describe("Set true only when every occurrence of old_string should be replaced."),
|
|
253583
253584
|
single_file_override: boolean$1().optional().describe("Set true only when the latest direct user message explicitly requires one single source file. The tool verifies that request and reports the override visibly.")
|
|
253584
253585
|
});
|
|
253585
|
-
edit_default = "Perform exact replacements in existing files. Use Edit for every incremental change instead of Write or Bash `sed`. Read the target immediately before every Edit; take `old_string` and `new_string` from that current Read output, remove the line-number prefix and tab, and never guess from memory.\n\n`old_string` must be exact and unique unless `replace_all` is true. Add surrounding context when it is ambiguous. Use `replace_all` only when every occurrence should change, and keep `new_string` different from `old_string`. Parallel Edit calls may target different files only. Before another Edit on the same file, read it again because the first replacement can invalidate the next `old_string`.\n\nFor pure CRLF files, Read shows LF; use LF in both strings and Edit restores CRLF. For mixed endings or lone carriage returns, include the displayed `\\r` escapes exactly.\n\nSource files may contain at most 500 lines. An already oversized file may only be edited when the result has fewer lines. Set `single_file_override=true` only when the latest direct user message explicitly requires one single file; the accepted override is shown to the user.";
|
|
253586
|
+
edit_default = "Perform exact replacements in existing files. Use Edit for every incremental change instead of Write or Bash `sed`. Read the target immediately before every Edit; take `old_string` and `new_string` from that current Read output, remove the line-number prefix and tab, and never guess from memory.\n\n`old_string` must be exact and unique unless `replace_all` is true. Add surrounding context when it is ambiguous. Use `replace_all` only when every occurrence should change, and keep `new_string` different from `old_string`. Parallel Edit calls may target different files only. Before another Edit on the same file, read it again because the first replacement can invalidate the next `old_string`.\n\nFor pure CRLF files, Read shows LF; use LF in both strings and Edit restores CRLF. For mixed endings or lone carriage returns, include the displayed `\\r` escapes exactly.\n\nSource files may contain at most 500 lines. An already oversized file may only be edited when the result has fewer lines. Set `single_file_override=true` only when the latest direct user message explicitly requires one single file; the accepted override is shown to the user. Runaway repeated JavaScript or TypeScript statements are rejected before file I/O.";
|
|
253586
253587
|
EditTool = class {
|
|
253587
253588
|
kaos;
|
|
253588
253589
|
workspace;
|
|
@@ -253627,6 +253628,11 @@ var init_edit = __esmMin((() => {
|
|
|
253627
253628
|
isError: true,
|
|
253628
253629
|
output: "No changes to make: old_string and new_string are exactly the same."
|
|
253629
253630
|
};
|
|
253631
|
+
const degenerateStatementRun = findDegenerateGeneratedStatementRun(safePath, args.new_string);
|
|
253632
|
+
if (degenerateStatementRun !== null) return {
|
|
253633
|
+
isError: true,
|
|
253634
|
+
output: `Refused to edit ${args.path}: generated source contains a runaway repeated statement ${String(degenerateStatementRun.count)} times from line ${String(degenerateStatementRun.line)} (starts with ${JSON.stringify(degenerateStatementRun.preview)}). Regenerate that section in a smaller focused chunk. No file was written.`
|
|
253635
|
+
};
|
|
253630
253636
|
try {
|
|
253631
253637
|
const raw = await this.kaos.readText(safePath);
|
|
253632
253638
|
const modelView = toModelTextView(raw);
|
|
@@ -259815,7 +259821,7 @@ var init_write$1 = __esmMin((() => {
|
|
|
259815
259821
|
}));
|
|
259816
259822
|
//#endregion
|
|
259817
259823
|
write_default = "Create, append to, or completely replace a file. Missing parent directories are created automatically. Overwrite is the default; append adds content at EOF without adding a newline.\n\nUse Write for a new file or a complete replacement. For every incremental change to an existing file, Use Edit instead, even when it is small. Read before overwriting an existing file. Do not create unsolicited documentation, README, summary, or report files unless the user or project instructions require them.\n\nContent is written literally. Never include Read/Edit line prefixes. Supplied LF and CRLF endings are preserved. Source files may contain at most 500 lines; split larger implementations into focused files. Set `single_file_override=true` only when the latest direct user message explicitly requires one source file.\n\nWrite complete files up to 4,096 UTF-8 bytes atomically. For a larger new or completely replaced file, use `continuation`. Keep each chunk within the configured UTF-8 bytes limit and end it on a complete line with `\\n`. Set exact `expected_lines` and, when available, `expected_sha256`. Part 1 uses overwrite, `part=1`, and `start_line=1`; every later part uses append, the next consecutive part, and the exact `next_start_line` returned by Write. Set `final=true` only on the complete final part. The Target file remains unchanged until final line-count and optional SHA-256 checks pass. After an oversized plain Write is refused, that path accepts only continuation calls until the sequence completes. Restart an unfinished sequence only with part 1, overwrite, `start_line=1`, and `reset=true`. Never use continuation for incremental edits.\n\nRunaway generated JavaScript or TypeScript identifiers are rejected before disk I/O; regenerate only the affected section as a smaller chunk.";
|
|
259818
|
-
write_default = "Create, append to, or replace a whole file. Missing parent directories are created automatically. Overwrite is default; append adds content at EOF without a newline.\n\nUse Write only for new files or complete replacements. Use Edit for every incremental change, even a small one. Read before overwriting an existing file. Do not create unsolicited documentation unless the user or project instructions require it.\n\nContent is literal. Never include line prefixes. Supplied LF and CRLF endings are preserved. Large files and source files over 500 lines are accepted natively: the runtime fragments them on disk, assembles them through atomic replacement, and verifies the persisted SHA-256. No manual continuation calls or `single_file_override` are required.\n\nThe explicit `continuation` protocol remains available for streamed generation across several tool calls. Keep each chunk within the configured UTF-8 bytes limit and end it on a complete line with `\\n`. Optionally set exact `expected_lines` and, when available, `expected_sha256`. Part number, start line, and mode are derived from actually staged content when omitted. Set `final=true` only on the complete final part. The target file remains unchanged until final line-count and optional SHA-256 checks pass. Restart an unfinished sequence with `reset=true`. Never use continuation for incremental edits.\n\nRunaway generated JavaScript or TypeScript identifiers are rejected before disk I/O; regenerate only the affected section as a smaller chunk.";
|
|
259824
|
+
write_default = "Create, append to, or replace a whole file. Missing parent directories are created automatically. Overwrite is default; append adds content at EOF without a newline.\n\nUse Write only for new files or complete replacements. Use Edit for every incremental change, even a small one. Read before overwriting an existing file. Do not create unsolicited documentation unless the user or project instructions require it.\n\nContent is literal. Never include line prefixes. Supplied LF and CRLF endings are preserved. Large files and source files over 500 lines are accepted natively: the runtime fragments them on disk, assembles them through atomic replacement, and verifies the persisted SHA-256. No manual continuation calls or `single_file_override` are required.\n\nThe explicit `continuation` protocol remains available for streamed generation across several tool calls. Keep each chunk within the configured UTF-8 bytes limit and end it on a complete line with `\\n`. Optionally set exact `expected_lines` and, when available, `expected_sha256`. Part number, start line, and mode are derived from actually staged content when omitted. Set `final=true` only on the complete final part. The target file remains unchanged until final line-count and optional SHA-256 checks pass. Restart an unfinished sequence with `reset=true`. Never use continuation for incremental edits.\n\nRunaway generated JavaScript or TypeScript identifiers and repeated executable statements are rejected before disk I/O; regenerate only the affected section as a smaller chunk.";
|
|
259819
259825
|
//#region ../../packages/agent-core/src/tools/builtin/file/generated-source-health.ts
|
|
259820
259826
|
/**
|
|
259821
259827
|
* Find runaway generated identifiers while ignoring comments and strings.
|
|
@@ -259913,6 +259919,7 @@ function isIdentifierPart(value) {
|
|
|
259913
259919
|
var JAVASCRIPT_LIKE_EXTENSIONS;
|
|
259914
259920
|
var init_generated_source_health = __esmMin((() => {
|
|
259915
259921
|
init_dist$6();
|
|
259922
|
+
({ findDegenerateGeneratedStatementRun } = createRequire(import.meta.url)("./bin/generated-source-health.cjs"));
|
|
259916
259923
|
JAVASCRIPT_LIKE_EXTENSIONS = new Set([
|
|
259917
259924
|
".cjs",
|
|
259918
259925
|
".cts",
|
|
@@ -260155,6 +260162,11 @@ bytesWritten: number$1().int().nonnegative() });
|
|
|
260155
260162
|
isError: true,
|
|
260156
260163
|
output: `Refused to write ${args.path}: generated source contains a runaway ${String(degenerateIdentifier.length)}-character identifier on line ${String(degenerateIdentifier.line)} (starts with ${JSON.stringify(degenerateIdentifier.preview)}). Regenerate that section in a smaller focused chunk. No file was written.`
|
|
260157
260164
|
};
|
|
260165
|
+
const degenerateStatementRun = findDegenerateGeneratedStatementRun(safePath, args.content);
|
|
260166
|
+
if (degenerateStatementRun !== null) return {
|
|
260167
|
+
isError: true,
|
|
260168
|
+
output: `Refused to write ${args.path}: generated source contains a runaway repeated statement ${String(degenerateStatementRun.count)} times from line ${String(degenerateStatementRun.line)} (starts with ${JSON.stringify(degenerateStatementRun.preview)}). Regenerate that section in a smaller focused chunk. No file was written.`
|
|
260169
|
+
};
|
|
260158
260170
|
let currentContent;
|
|
260159
260171
|
if (isSourceFilePath(safePath)) try {
|
|
260160
260172
|
currentContent = await this.kaos.readText(safePath);
|
|
@@ -263210,7 +263222,7 @@ function windowsPathToPosixPath(path) {
|
|
|
263210
263222
|
function rewriteWindowsNullRedirect(command) {
|
|
263211
263223
|
return command.replace(WINDOWS_NUL_REDIRECT, "$1/dev/null");
|
|
263212
263224
|
}
|
|
263213
|
-
var MS_PER_SECOND, DEFAULT_TIMEOUT_S, MAX_TIMEOUT_S, DEFAULT_BACKGROUND_TIMEOUT_S, MAX_BACKGROUND_TIMEOUT_S, USER_INTERRUPT_REASON, BashInputSchema, SHELL_TIMEOUT_VARS, BashTool, WINDOWS_NUL_REDIRECT, broadRecursiveSearchRefusal;
|
|
263225
|
+
var MS_PER_SECOND, DEFAULT_TIMEOUT_S, MAX_TIMEOUT_S, DEFAULT_BACKGROUND_TIMEOUT_S, MAX_BACKGROUND_TIMEOUT_S, USER_INTERRUPT_REASON, BashInputSchema, SHELL_TIMEOUT_VARS, BashTool, WINDOWS_NUL_REDIRECT, broadRecursiveSearchRefusal, windowsBashDialectRefusal;
|
|
263214
263226
|
var init_bash = __esmMin((() => {
|
|
263215
263227
|
init_zod$1();
|
|
263216
263228
|
init_background();
|
|
@@ -263220,6 +263232,7 @@ var init_bash = __esmMin((() => {
|
|
|
263220
263232
|
init_result_builder();
|
|
263221
263233
|
init_bash$1();
|
|
263222
263234
|
({ broadRecursiveSearchRefusal } = createRequire(import.meta.url)("./bin/bash-search-scope-policy.cjs"));
|
|
263235
|
+
({ windowsBashDialectRefusal } = createRequire(import.meta.url)("./bin/windows-bash-dialect-policy.cjs"));
|
|
263223
263236
|
MS_PER_SECOND = 1e3;
|
|
263224
263237
|
DEFAULT_TIMEOUT_S = 60;
|
|
263225
263238
|
MAX_TIMEOUT_S = 300;
|
|
@@ -263272,7 +263285,8 @@ var init_bash = __esmMin((() => {
|
|
|
263272
263285
|
this.backgroundManager = backgroundManager;
|
|
263273
263286
|
this.isWindowsBash = this.kaos.osEnv.osKind === "Windows";
|
|
263274
263287
|
this.allowBackground = options?.allowBackground ?? true;
|
|
263275
|
-
const
|
|
263288
|
+
const windowsDialectNote = this.isWindowsBash ? "\n\nOn Windows this tool still runs POSIX Bash, including inside VS Code. Do not use cmd.exe syntax such as `cd /d`, `dir /s /b`, `find /c /v`, or `2>nul`; pass the directory with `cwd`, use Bash paths and commands, and redirect to `/dev/null`." : "";
|
|
263289
|
+
const rendered = `${renderBashDescription(this.kaos.osEnv.shellName)}${windowsDialectNote}`;
|
|
263276
263290
|
this.description = this.allowBackground ? rendered : withoutBackgroundDescription(rendered);
|
|
263277
263291
|
}
|
|
263278
263292
|
resolveExecution(args) {
|
|
@@ -263389,6 +263403,14 @@ var init_bash = __esmMin((() => {
|
|
|
263389
263403
|
isError: true,
|
|
263390
263404
|
output: "Command cannot be empty."
|
|
263391
263405
|
};
|
|
263406
|
+
const dialectRefusal = windowsBashDialectRefusal({
|
|
263407
|
+
command: args.command,
|
|
263408
|
+
isWindowsBash: this.isWindowsBash
|
|
263409
|
+
});
|
|
263410
|
+
if (dialectRefusal !== void 0) return {
|
|
263411
|
+
isError: true,
|
|
263412
|
+
output: dialectRefusal
|
|
263413
|
+
};
|
|
263392
263414
|
const searchRefusal = broadRecursiveSearchRefusal({
|
|
263393
263415
|
command: args.command,
|
|
263394
263416
|
cwd: args.cwd ?? this.cwd,
|