svharness 0.15.11 → 0.15.13
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/README.md +106 -2
- package/auto/auto-harness-detail-design.md +136 -4
- package/auto/skills/svharness/SKILL.md +44 -1
- package/auto/templates/auto-pack-req.config.example.yaml +29 -0
- package/auto/templates/auto.config.example.yaml +1 -1
- package/auto/templates/phase-prompts/S40-adversarial-review.md +31 -0
- package/auto/templates/phase-prompts/S40-review.md +22 -0
- package/auto/templates/phase-prompts/S40-sample-trace.md +26 -0
- package/auto/templates/requirements-review-rubric.yaml +79 -0
- package/dist/commands/auto-pack-req.js +70 -0
- package/dist/commands/auto.js +16 -1
- package/dist/commands/convert.js +2 -0
- package/dist/commands/shell-integration.js +29 -56
- package/dist/config/merge-options.js +2 -0
- package/dist/config/normalize.js +7 -0
- package/dist/constants/convert.js +5 -0
- package/dist/core/markitdown-client.js +7 -2
- package/dist/index.js +77 -5
- package/dist/lib/acp-agent-cli-resolver.js +193 -0
- package/dist/lib/acp-client.js +4 -2
- package/dist/lib/agent-launcher.js +9 -4
- package/dist/lib/auto-assets.js +1 -0
- package/dist/lib/auto-optimize.js +23 -3
- package/dist/lib/auto-orchestrator.js +53 -20
- package/dist/lib/auto-pack-req-context.js +88 -0
- package/dist/lib/auto-pack-req-finalize.js +28 -0
- package/dist/lib/auto-req-pack-review.js +161 -0
- package/dist/lib/auto-sample-trace.js +144 -0
- package/dist/lib/auto-state.js +124 -15
- package/dist/lib/gate-checker.js +20 -10
- package/dist/lib/phase-runner.js +13 -2
- package/dist/lib/ps-codechat-alias.js +29 -16
- package/dist/lib/review-parser.js +14 -0
- package/dist/lib/score-aggregator.js +21 -0
- package/dist/lib/win-registry.js +1 -1
- package/dist/utils/validate-args.js +2 -1
- package/package.json +3 -2
- package/templates/codechat/Start-CodeChat.ps1 +176 -135
- package/templates/codechat/bash.exe.stackdump +29 -0
- package/templates/codechat/docs/project-structure.md +59 -0
- package/templates/svharness.config.example.yaml +2 -0
- package/dist/lib/launch-script-path.js +0 -24
- package/templates/codechat/.claude/.env +0 -59
package/dist/commands/auto.js
CHANGED
|
@@ -12,6 +12,7 @@ const logger_1 = require("../utils/logger");
|
|
|
12
12
|
const harness_resolver_1 = require("../lib/harness-resolver");
|
|
13
13
|
const auto_orchestrator_1 = require("../lib/auto-orchestrator");
|
|
14
14
|
const dashboard_1 = require("../dashboard");
|
|
15
|
+
const acp_agent_cli_resolver_1 = require("../lib/acp-agent-cli-resolver");
|
|
15
16
|
/**
|
|
16
17
|
* Prevent system sleep on Windows during long-running auto execution.
|
|
17
18
|
* Registers process-level handlers so sleep is restored on any exit path.
|
|
@@ -46,6 +47,13 @@ async function runAuto(positionalWorkdir, opts, cmd) {
|
|
|
46
47
|
const merged = (0, config_1.mergeAutoOptions)(opts, loaded?.config.auto, loaded?.config.defaults, cmd);
|
|
47
48
|
const ctx = await resolveAutoContext(positionalWorkdir, merged);
|
|
48
49
|
(0, logger_1.setVerbose)(ctx.verbose);
|
|
50
|
+
if (!ctx.dryRun) {
|
|
51
|
+
logger_1.logger.section('[auto] 启动前检查:Agent CLI');
|
|
52
|
+
if (!ctx.acpAgentCli) {
|
|
53
|
+
throw new Error('内部错误:ACP Agent CLI 未解析');
|
|
54
|
+
}
|
|
55
|
+
logger_1.logger.success(`已找到 Agent CLI → ${(0, acp_agent_cli_resolver_1.formatAcpAgentCliLog)(ctx.acpAgentCli)}`);
|
|
56
|
+
}
|
|
49
57
|
// Start dashboard server (non-blocking, best-effort)
|
|
50
58
|
(0, dashboard_1.startDashboard)(7879).catch(() => { });
|
|
51
59
|
// Prevent system sleep during execution; restore on any exit path
|
|
@@ -63,12 +71,14 @@ async function runAuto(positionalWorkdir, opts, cmd) {
|
|
|
63
71
|
async function resolveAutoContext(positionalWorkdir, opts) {
|
|
64
72
|
const workDir = node_path_1.default.resolve(positionalWorkdir ?? opts.workDir ?? process.cwd());
|
|
65
73
|
const harnessRoot = await (0, harness_resolver_1.resolveHarnessRoot)(workDir);
|
|
74
|
+
const acpAgentCli = opts.dryRun ? null : (0, acp_agent_cli_resolver_1.ensureAcpAgentCliAvailable)();
|
|
66
75
|
return {
|
|
67
76
|
workDir,
|
|
68
77
|
harnessRoot,
|
|
69
78
|
harnessExists: !!harnessRoot,
|
|
79
|
+
acpAgentCli,
|
|
70
80
|
targetScore: normalizeNumber(opts.targetScore, 9.5, '--target-score'),
|
|
71
|
-
maxOptimizeRounds: normalizeInteger(opts.maxOptimizeRounds,
|
|
81
|
+
maxOptimizeRounds: normalizeInteger(opts.maxOptimizeRounds, 10, '--max-optimize-rounds'),
|
|
72
82
|
dryRun: !!opts.dryRun,
|
|
73
83
|
autoApprove: !!opts.autoApprove,
|
|
74
84
|
yes: !!opts.yes,
|
|
@@ -80,6 +90,8 @@ async function resolveAutoContext(positionalWorkdir, opts) {
|
|
|
80
90
|
acpTimeoutMs: normalizeInteger(opts.acpTimeoutMs, 1_800_000, '--acp-timeout-ms'),
|
|
81
91
|
maxRetriesPerPhase: normalizeInteger(opts.maxRetriesPerPhase, 2, '--max-retries-per-phase'),
|
|
82
92
|
baselineAutoExtract: normalizeBaselineExtract(opts.baselineAutoExtract),
|
|
93
|
+
scope: normalizeScope(opts.scope),
|
|
94
|
+
sampleCount: normalizeInteger(opts.sampleCount, 7, '--sample-count'),
|
|
83
95
|
promptDir: opts.promptDir,
|
|
84
96
|
checkpoint: opts.checkpoint,
|
|
85
97
|
maxPhase: opts.maxPhase,
|
|
@@ -107,3 +119,6 @@ function normalizeBaselineExtract(value) {
|
|
|
107
119
|
}
|
|
108
120
|
return 'conservative';
|
|
109
121
|
}
|
|
122
|
+
function normalizeScope(value) {
|
|
123
|
+
return value === 'requirements' ? 'requirements' : 'full';
|
|
124
|
+
}
|
package/dist/commands/convert.js
CHANGED
|
@@ -182,6 +182,8 @@ async function runConvert(opts) {
|
|
|
182
182
|
catch (err) {
|
|
183
183
|
const e = err;
|
|
184
184
|
logger_1.logger.error(`markitdown_serve unreachable at ${v.endpoint}: ${e.message}`);
|
|
185
|
+
logger_1.logger.plain(' - if another user is converting a large PDF, the service may still be busy; retry in a minute');
|
|
186
|
+
logger_1.logger.plain(' - production should run uvicorn with --workers 4+ and nginx proxy_read_timeout ≥ 900s');
|
|
185
187
|
logger_1.logger.plain('');
|
|
186
188
|
logger_1.logger.plain(' - confirm the service is deployed and reachable from this machine');
|
|
187
189
|
logger_1.logger.plain(' - pass --endpoint <url> or comma-separated URLs for failover');
|
|
@@ -3,7 +3,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.resolveLaunchScriptPath = void 0;
|
|
7
6
|
exports.resolveShellMenuIconSourcePath = resolveShellMenuIconSourcePath;
|
|
8
7
|
exports.installShellMenuIcon = installShellMenuIcon;
|
|
9
8
|
exports.resolveWindowsTerminalPath = resolveWindowsTerminalPath;
|
|
@@ -19,11 +18,8 @@ const child_process_1 = require("child_process");
|
|
|
19
18
|
const logger_1 = require("../utils/logger");
|
|
20
19
|
const win_registry_1 = require("../lib/win-registry");
|
|
21
20
|
const ps_codechat_alias_1 = require("../lib/ps-codechat-alias");
|
|
22
|
-
const launch_script_path_1 = require("../lib/launch-script-path");
|
|
23
|
-
Object.defineProperty(exports, "resolveLaunchScriptPath", { enumerable: true, get: function () { return launch_script_path_1.resolveLaunchScriptPath; } });
|
|
24
21
|
const STUB_DIR = path_1.default.join(process.env.LOCALAPPDATA ?? path_1.default.join(os_1.default.homedir(), 'AppData', 'Local'), 'svharness', 'bin');
|
|
25
|
-
const
|
|
26
|
-
const LEGACY_STUB_PATH = path_1.default.join(STUB_DIR, 'launch_codechat_cli.cmd');
|
|
22
|
+
const PS1_INSTALL_PATH = path_1.default.join(STUB_DIR, 'Start-CodeChat.ps1');
|
|
27
23
|
const ICON_FILENAME = 'codechat-agent.ico';
|
|
28
24
|
const ICON_STUB_PATH = path_1.default.join(STUB_DIR, ICON_FILENAME);
|
|
29
25
|
const STANDALONE_MENU_KEY_BG = 'HKCU\\Software\\Classes\\Directory\\Background\\shell\\CodeChatStandaloneAgent';
|
|
@@ -102,31 +98,24 @@ function resolveWindowsTerminalPath() {
|
|
|
102
98
|
}
|
|
103
99
|
return null;
|
|
104
100
|
}
|
|
105
|
-
function buildStubContent(launchScriptPath) {
|
|
106
|
-
const escapedNodeScript = launchScriptPath.replace(/'/g, "''");
|
|
107
|
-
// ASCII-only + UTF-8 BOM. Legacy conhost fallback sets UTF-8 before CodeChat TUI.
|
|
108
|
-
return `param(
|
|
109
|
-
[string]$WorkDir = (Get-Location).Path
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
$ErrorActionPreference = 'Stop'
|
|
113
|
-
|
|
114
|
-
try { chcp 65001 | Out-Null } catch {}
|
|
115
|
-
[Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
116
|
-
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
117
|
-
$OutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
118
|
-
|
|
119
|
-
Set-Location -LiteralPath $WorkDir
|
|
120
|
-
& node '${escapedNodeScript}' "$WorkDir"
|
|
121
|
-
exit $LASTEXITCODE
|
|
122
|
-
`;
|
|
123
|
-
}
|
|
124
|
-
function writeStubFile(launchScriptPath) {
|
|
125
|
-
const content = buildStubContent(launchScriptPath);
|
|
126
|
-
return fs_extra_1.default.writeFile(STUB_PATH, `\uFEFF${content}`, 'utf8');
|
|
127
|
-
}
|
|
128
101
|
function buildPsInvokeArgs(ps1Path, dirPlaceholder) {
|
|
129
|
-
return `-NoExit -NoProfile -ExecutionPolicy Bypass -File "${ps1Path}" ${dirPlaceholder}`;
|
|
102
|
+
return `-NoExit -NoProfile -ExecutionPolicy Bypass -File "${ps1Path}" -Action Start -WorkDir ${dirPlaceholder}`;
|
|
103
|
+
}
|
|
104
|
+
async function installStartCodeChatPs1() {
|
|
105
|
+
const fromDist = path_1.default.resolve(__dirname, '..', '..', 'templates', 'codechat', 'Start-CodeChat.ps1');
|
|
106
|
+
const fromCwd = path_1.default.resolve(__dirname, '..', '..', '..', 'templates', 'codechat', 'Start-CodeChat.ps1');
|
|
107
|
+
let source = '';
|
|
108
|
+
if (fs_extra_1.default.existsSync(fromDist)) {
|
|
109
|
+
source = fromDist;
|
|
110
|
+
}
|
|
111
|
+
else if (fs_extra_1.default.existsSync(fromCwd)) {
|
|
112
|
+
source = fromCwd;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
throw new Error(`\u672A\u627E\u5230 Start-CodeChat.ps1\uFF08\u9884\u671F\uFF1A${fromDist}\uFF09`);
|
|
116
|
+
}
|
|
117
|
+
await fs_extra_1.default.copy(source, PS1_INSTALL_PATH, { overwrite: true });
|
|
118
|
+
return PS1_INSTALL_PATH;
|
|
130
119
|
}
|
|
131
120
|
function buildMenuCommand(ps1Path, dirPlaceholder, wtPath) {
|
|
132
121
|
const psArgs = buildPsInvokeArgs(ps1Path, dirPlaceholder);
|
|
@@ -150,23 +139,18 @@ async function runShellInstall(opts = {}) {
|
|
|
150
139
|
// Avoid duplicate menu entries: overwrite standalone menu if present.
|
|
151
140
|
(0, win_registry_1.regDeleteKey)(STANDALONE_MENU_KEY_BG);
|
|
152
141
|
(0, win_registry_1.regDeleteKey)(STANDALONE_MENU_KEY_DIR);
|
|
153
|
-
const launchScriptPath = (0, launch_script_path_1.resolveLaunchScriptPath)();
|
|
154
142
|
await fs_extra_1.default.ensureDir(STUB_DIR);
|
|
155
|
-
await
|
|
156
|
-
if (await fs_extra_1.default.pathExists(LEGACY_STUB_PATH)) {
|
|
157
|
-
await fs_extra_1.default.remove(LEGACY_STUB_PATH);
|
|
158
|
-
}
|
|
143
|
+
const ps1Path = await installStartCodeChatPs1();
|
|
159
144
|
const iconValue = await installShellMenuIcon();
|
|
160
145
|
const wtPath = resolveWindowsTerminalPath();
|
|
161
|
-
const bgCommand = buildMenuCommand(
|
|
162
|
-
const dirCommand = buildMenuCommand(
|
|
146
|
+
const bgCommand = buildMenuCommand(PS1_INSTALL_PATH, '"%V"', wtPath);
|
|
147
|
+
const dirCommand = buildMenuCommand(PS1_INSTALL_PATH, '"%1"', wtPath);
|
|
163
148
|
registerMenuKey(win_registry_1.SHELL_MENU_KEY_BG, bgCommand, iconValue);
|
|
164
149
|
registerMenuKey(win_registry_1.SHELL_MENU_KEY_DIR, dirCommand, iconValue);
|
|
165
150
|
const via = wtPath ? 'Windows Terminal' : 'PowerShell(UTF-8)';
|
|
166
151
|
logSuccess(`Windows 右键菜单已注册:「在此启动 CodeChat Agent」(${via})`, opts.silent);
|
|
167
|
-
logInfo(`
|
|
152
|
+
logInfo(` 脚本: ${ps1Path}`, opts.silent);
|
|
168
153
|
logInfo(` icon: ${ICON_STUB_PATH}`, opts.silent);
|
|
169
|
-
logInfo(` 脚本: ${launchScriptPath}`, opts.silent);
|
|
170
154
|
if (wtPath) {
|
|
171
155
|
logInfo(` wt: ${wtPath}`, opts.silent);
|
|
172
156
|
}
|
|
@@ -183,15 +167,12 @@ async function runShellUninstall(opts = {}) {
|
|
|
183
167
|
// Also remove standalone script menu (Start-CodeChat.ps1 independent install)
|
|
184
168
|
(0, win_registry_1.regDeleteKey)(STANDALONE_MENU_KEY_BG);
|
|
185
169
|
(0, win_registry_1.regDeleteKey)(STANDALONE_MENU_KEY_DIR);
|
|
186
|
-
if (await fs_extra_1.default.pathExists(
|
|
187
|
-
await fs_extra_1.default.remove(
|
|
170
|
+
if (await fs_extra_1.default.pathExists(PS1_INSTALL_PATH)) {
|
|
171
|
+
await fs_extra_1.default.remove(PS1_INSTALL_PATH);
|
|
188
172
|
}
|
|
189
173
|
if (await fs_extra_1.default.pathExists(ICON_STUB_PATH)) {
|
|
190
174
|
await fs_extra_1.default.remove(ICON_STUB_PATH);
|
|
191
175
|
}
|
|
192
|
-
if (await fs_extra_1.default.pathExists(LEGACY_STUB_PATH)) {
|
|
193
|
-
await fs_extra_1.default.remove(LEGACY_STUB_PATH);
|
|
194
|
-
}
|
|
195
176
|
// Clean up standalone script directory (Start-CodeChat.ps1 independent install)
|
|
196
177
|
const standaloneDir = path_1.default.join(process.env.LOCALAPPDATA ?? path_1.default.join(os_1.default.homedir(), 'AppData', 'Local'), 'codechat-standalone');
|
|
197
178
|
if (await fs_extra_1.default.pathExists(standaloneDir)) {
|
|
@@ -205,24 +186,17 @@ async function runShellUninstall(opts = {}) {
|
|
|
205
186
|
*/
|
|
206
187
|
function getShellStatus() {
|
|
207
188
|
assertWin32();
|
|
208
|
-
|
|
209
|
-
try {
|
|
210
|
-
launchScriptPath = (0, launch_script_path_1.resolveLaunchScriptPath)();
|
|
211
|
-
}
|
|
212
|
-
catch {
|
|
213
|
-
launchScriptPath = '(未找到)';
|
|
214
|
-
}
|
|
189
|
+
const ps1Installed = fs_extra_1.default.existsSync(PS1_INSTALL_PATH);
|
|
215
190
|
const backgroundMenu = (0, win_registry_1.regKeyExists)(win_registry_1.SHELL_MENU_KEY_BG);
|
|
216
191
|
const directoryMenu = (0, win_registry_1.regKeyExists)(win_registry_1.SHELL_MENU_KEY_DIR);
|
|
217
|
-
const stubExists = fs_extra_1.default.existsSync(STUB_PATH);
|
|
218
192
|
const wtPath = resolveWindowsTerminalPath();
|
|
219
193
|
const aliasStatus = (0, ps_codechat_alias_1.getCodechatAliasStatus)();
|
|
220
194
|
return {
|
|
221
|
-
installed: backgroundMenu && directoryMenu &&
|
|
222
|
-
stubPath:
|
|
195
|
+
installed: backgroundMenu && directoryMenu && ps1Installed,
|
|
196
|
+
stubPath: PS1_INSTALL_PATH,
|
|
223
197
|
backgroundMenu,
|
|
224
198
|
directoryMenu,
|
|
225
|
-
launchScriptPath,
|
|
199
|
+
launchScriptPath: PS1_INSTALL_PATH,
|
|
226
200
|
viaWindowsTerminal: wtPath !== null,
|
|
227
201
|
aliasInstalled: aliasStatus.installed,
|
|
228
202
|
profilePath: aliasStatus.profilePath,
|
|
@@ -232,10 +206,9 @@ function printShellStatus() {
|
|
|
232
206
|
const status = getShellStatus();
|
|
233
207
|
logger_1.logger.section('Windows shell 集成状态');
|
|
234
208
|
logger_1.logger.plain(` 右键菜单完整: ${status.installed ? '是' : '否'}`);
|
|
235
|
-
logger_1.logger.plain(`
|
|
209
|
+
logger_1.logger.plain(` Start-CodeChat.ps1: ${status.stubPath}`);
|
|
236
210
|
logger_1.logger.plain(` 空白处菜单: ${status.backgroundMenu ? '已注册' : '未注册'}`);
|
|
237
211
|
logger_1.logger.plain(` 文件夹菜单: ${status.directoryMenu ? '已注册' : '未注册'}`);
|
|
238
|
-
logger_1.logger.plain(` 启动脚本: ${status.launchScriptPath}`);
|
|
239
212
|
logger_1.logger.plain(` 经 WT 启动: ${status.viaWindowsTerminal ? '是(install 时检测到 wt.exe)' : '否'}`);
|
|
240
213
|
logger_1.logger.plain(` codechat 别名: ${status.aliasInstalled ? '已安装' : '未安装'}`);
|
|
241
214
|
logger_1.logger.plain(` profile 路径: ${status.profilePath}`);
|
|
@@ -85,6 +85,8 @@ function mergeAutoOptions(cli, configSection, defaults, cmd) {
|
|
|
85
85
|
promptDir: pickString('promptDir', cli.promptDir, cfg.promptDir, cmd),
|
|
86
86
|
baselineAutoExtract: pickString('baselineAutoExtract', cli.baselineAutoExtract, cfg.baselineAutoExtract, cmd),
|
|
87
87
|
convertEndpoint: pickString('convertEndpoint', cli.convertEndpoint, cfg.convertEndpoint, cmd),
|
|
88
|
+
scope: pickString('scope', cli.scope, cfg.scope, cmd),
|
|
89
|
+
sampleCount: pickNumber('sampleCount', cli.sampleCount, cfg.sampleCount, cmd),
|
|
88
90
|
};
|
|
89
91
|
}
|
|
90
92
|
function mergeBuildOptions(cli, configSection, defaults, cmd) {
|
package/dist/config/normalize.js
CHANGED
|
@@ -50,6 +50,7 @@ function pickAutoSection(raw) {
|
|
|
50
50
|
'costAlertThreshold',
|
|
51
51
|
'acpTimeoutMs',
|
|
52
52
|
'maxRetriesPerPhase',
|
|
53
|
+
'sampleCount',
|
|
53
54
|
]) {
|
|
54
55
|
if (raw[k] !== undefined && raw[k] !== null) {
|
|
55
56
|
s[k] = Number(raw[k]);
|
|
@@ -235,5 +236,11 @@ function normalizeConfig(raw, configPath) {
|
|
|
235
236
|
}
|
|
236
237
|
cfg.auto = pickAutoSection(raw.auto);
|
|
237
238
|
}
|
|
239
|
+
if (raw.auto_pack_req !== undefined) {
|
|
240
|
+
if (!isPlainObject(raw.auto_pack_req)) {
|
|
241
|
+
throw new Error(`配置 auto_pack_req 必须是对象:${configPath}`);
|
|
242
|
+
}
|
|
243
|
+
cfg.auto_pack_req = pickAutoSection(raw.auto_pack_req);
|
|
244
|
+
}
|
|
238
245
|
return cfg;
|
|
239
246
|
}
|
|
@@ -57,7 +57,7 @@ function parseEndpointList(raw) {
|
|
|
57
57
|
}
|
|
58
58
|
return out;
|
|
59
59
|
}
|
|
60
|
-
async function ping(endpoint, timeoutMs =
|
|
60
|
+
async function ping(endpoint, timeoutMs = 30_000) {
|
|
61
61
|
const controller = new AbortController();
|
|
62
62
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
63
63
|
try {
|
|
@@ -80,7 +80,7 @@ async function ping(endpoint, timeoutMs = 10_000) {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
/** Probe endpoints in order; return those that respond to /healthz. */
|
|
83
|
-
async function filterHealthyEndpoints(endpoints, timeoutMs =
|
|
83
|
+
async function filterHealthyEndpoints(endpoints, timeoutMs = 30_000) {
|
|
84
84
|
const healthy = [];
|
|
85
85
|
for (const endpoint of endpoints) {
|
|
86
86
|
try {
|
|
@@ -186,6 +186,11 @@ function extractTraceId(body) {
|
|
|
186
186
|
}
|
|
187
187
|
function describeHttpError(status, body, endpoint) {
|
|
188
188
|
const prefix = endpoint ? `[${endpoint}] ` : '';
|
|
189
|
+
if (status === 504) {
|
|
190
|
+
return (`${prefix}gateway timeout (HTTP 504): reverse proxy gave up waiting for markitdown_serve ` +
|
|
191
|
+
`(worker may still be running VLM/OCR). Increase nginx proxy_read_timeout to match ` +
|
|
192
|
+
`--timeout-sec, or reduce MARKITDOWN_OCR_MAX_RENDER_PX / use a shorter PDF export.`);
|
|
193
|
+
}
|
|
189
194
|
if (body && typeof body === 'object') {
|
|
190
195
|
const b = body;
|
|
191
196
|
if (status === 415 && b.error === 'unsupported_format') {
|
package/dist/index.js
CHANGED
|
@@ -5,13 +5,15 @@ const logger_1 = require("./utils/logger");
|
|
|
5
5
|
const version_1 = require("./utils/version");
|
|
6
6
|
const validate_args_1 = require("./utils/validate-args");
|
|
7
7
|
const harness_name_1 = require("./utils/harness-name");
|
|
8
|
+
const convert_1 = require("./constants/convert");
|
|
8
9
|
const init_1 = require("./commands/init");
|
|
9
10
|
const apply_1 = require("./commands/apply");
|
|
10
|
-
const
|
|
11
|
+
const convert_2 = require("./commands/convert");
|
|
11
12
|
const wizard_1 = require("./commands/wizard");
|
|
12
13
|
const doctor_1 = require("./commands/doctor");
|
|
13
14
|
const wiki_1 = require("./commands/wiki");
|
|
14
15
|
const auto_1 = require("./commands/auto");
|
|
16
|
+
const auto_pack_req_1 = require("./commands/auto-pack-req");
|
|
15
17
|
const requirements_1 = require("./commands/requirements");
|
|
16
18
|
const start_agent_1 = require("./commands/start-agent");
|
|
17
19
|
const shell_integration_1 = require("./commands/shell-integration");
|
|
@@ -163,7 +165,7 @@ function attachBuildOptions(cmd) {
|
|
|
163
165
|
.option('--convert-endpoint <url>', '【build 自动 convert】markitdown_serve 基址(默认 http://markitdown.desaysz.site)')
|
|
164
166
|
.option('--convert-concurrency <n>', '【build 自动 convert】并发上传数(默认沿用 convert)', (v) => Number(v))
|
|
165
167
|
.option('--convert-max-file-mb <n>', '【build 自动 convert】单文件大小上限 MB(默认沿用 convert)', (v) => Number(v))
|
|
166
|
-
.option('--convert-timeout-sec <n>',
|
|
168
|
+
.option('--convert-timeout-sec <n>', `【build 自动 convert】超时秒数(默认 ${convert_1.DEFAULT_CONVERT_TIMEOUT_SEC},15 分钟)`, (v) => Number(v))
|
|
167
169
|
.option('--convert-force', '【build 自动 convert】覆盖已存在同名 .md')
|
|
168
170
|
.option('--force', '强制覆盖已存在的目标目录')
|
|
169
171
|
.option('-y, --yes', '跳过交互确认,使用默认值')
|
|
@@ -218,6 +220,10 @@ function main() {
|
|
|
218
220
|
'│ # 方式六:对当前工程生成 wiki 骨架 ',
|
|
219
221
|
'│ svharness wiki ',
|
|
220
222
|
'│ ',
|
|
223
|
+
'│ # 方式七:在 requirements 资料目录直接条目化(或指定资料包根) ',
|
|
224
|
+
'│ cd ./requirements && svharness auto_pack_req --yes ',
|
|
225
|
+
'│ svharness auto_pack_req ./req-pack --yes ',
|
|
226
|
+
'│ ',
|
|
221
227
|
'└──────────────────────────────────────────────────────────────────────────',
|
|
222
228
|
' 📚 使用说明文档:https://yesv-desaysv.feishu.cn/docx/OEBwdywTfoncPNxPZ0acClNincg',
|
|
223
229
|
'',
|
|
@@ -248,7 +254,7 @@ function main() {
|
|
|
248
254
|
.option('--extra-skills <path...>', '额外运行期资源路径(覆盖自动检测)')
|
|
249
255
|
.option('--force', '目标 harness 已存在时覆盖')
|
|
250
256
|
.option('--target-score <score>', '目标综合评分(默认 9.5)', (v) => Number(v))
|
|
251
|
-
.option('--max-optimize-rounds <n>', '最大优化轮数(默认
|
|
257
|
+
.option('--max-optimize-rounds <n>', '最大优化轮数(默认 10)', (v) => Number(v))
|
|
252
258
|
.option('--checkpoint <phase>', '到达阶段前暂停')
|
|
253
259
|
.option('--max-phase <phase>', '只运行到此阶段即停止')
|
|
254
260
|
.option('--dry-run', '打印 prompt 和动作,不调用 Claude')
|
|
@@ -277,6 +283,72 @@ function main() {
|
|
|
277
283
|
process.exitCode = 1;
|
|
278
284
|
}
|
|
279
285
|
});
|
|
286
|
+
program
|
|
287
|
+
.command('auto_pack_req [workdir]')
|
|
288
|
+
.description('需求包全自动条目化(S40 专项):S30+S40 → 机械门禁+对抗验证+抽样验证,综合分须严格大于目标分')
|
|
289
|
+
.addHelpText('after', [
|
|
290
|
+
'',
|
|
291
|
+
'说明:',
|
|
292
|
+
' 独立于 svharness auto,仅跑 S30_convert_docs + S40_extract_requirements,不执行 S50~S90。',
|
|
293
|
+
' 综合分 = 机械门禁×0.40 + 对抗验证×0.30 + 抽样验证×0.30',
|
|
294
|
+
' 通过条件:综合分 > targetScore(默认 9.5)且 verify/doctor 硬门禁全通过。',
|
|
295
|
+
'',
|
|
296
|
+
'工作目录(无参数时默认当前目录):',
|
|
297
|
+
' · 当前目录是 requirements 资料夹(含 doc/md/pdf 等)→ 视为输入,在上级目录创建 harness',
|
|
298
|
+
' · 当前目录是资料包根(含 requirements/ 子目录)→ 在目录内创建 harness',
|
|
299
|
+
' · 当前目录是已有 harness 根 → 重置 S40 后重做条目化',
|
|
300
|
+
'',
|
|
301
|
+
'两种入口:',
|
|
302
|
+
' · 资料包新建 — 无 harness.yaml,自动 build 后条目化',
|
|
303
|
+
' · 已有 harness 重做 — 检测到 harness.yaml 后重置 S40=PENDING,仅重跑 S40',
|
|
304
|
+
'',
|
|
305
|
+
'内置默认:targetScore=9.5, maxOptimizeRounds=10, sampleCount=7, autoApprove=yes',
|
|
306
|
+
'配置示例:auto/templates/auto-pack-req.config.example.yaml(auto_pack_req: 节)',
|
|
307
|
+
'详细设计:auto/auto-harness-detail-design.md §2.2.1',
|
|
308
|
+
'',
|
|
309
|
+
'示例:',
|
|
310
|
+
' cd ./requirements && svharness auto_pack_req --yes',
|
|
311
|
+
' svharness auto_pack_req ./req-pack --yes',
|
|
312
|
+
' svharness auto_pack_req ./my-harness --requirements ./docs --yes',
|
|
313
|
+
' svharness auto_pack_req --dry-run',
|
|
314
|
+
'',
|
|
315
|
+
].join('\n'))
|
|
316
|
+
.option('--work-dir <path>', '工作目录(资料包根或已有 harness 根)')
|
|
317
|
+
.option('--config <path>', '配置文件路径(支持 auto_pack_req: 节)')
|
|
318
|
+
.option('--harness-name <name>', 'harness 名称(资料包模式可自动推断)')
|
|
319
|
+
.option('--goal <text>', '构建目的描述')
|
|
320
|
+
.option('--arch <arch>', '架构模板:' + (0, validate_args_1.listSupportedArches)().join(' | '))
|
|
321
|
+
.option('--agent <agent>', '目标 Agent(默认 claude-code)')
|
|
322
|
+
.option('--requirements <path>', '需求文档路径(覆盖自动检测)')
|
|
323
|
+
.option('--force', '目标 harness 已存在时覆盖')
|
|
324
|
+
.option('--target-score <score>', '目标综合评分(默认 9.5,须严格大于此值)', (v) => Number(v))
|
|
325
|
+
.option('--max-optimize-rounds <n>', '最大优化轮数(默认 10)', (v) => Number(v))
|
|
326
|
+
.option('--sample-count <n>', '抽样验证片段数(默认 7)', (v) => Number(v))
|
|
327
|
+
.option('--dry-run', '打印 prompt 和动作,不调用 Agent')
|
|
328
|
+
.option('--auto-approve', '自动通过无人值守下的表单门禁')
|
|
329
|
+
.option('-y, --yes', '跳过交互确认')
|
|
330
|
+
.option('--verbose', '显示详细日志')
|
|
331
|
+
.option('--max-total-calls <n>', 'ACP 调用次数上限(默认 100)', (v) => Number(v))
|
|
332
|
+
.option('--budget-usd <amount>', '费用预算上限(USD)', (v) => Number(v))
|
|
333
|
+
.option('--cost-alert <amount>', '单次调用费用告警阈值(USD)', (v) => Number(v))
|
|
334
|
+
.option('--acp-mode <mode>', 'ACP 模式:auto | cli-print | sdk')
|
|
335
|
+
.option('--acp-timeout-ms <n>', '单次 ACP 调用超时毫秒', (v) => Number(v))
|
|
336
|
+
.option('--max-retries-per-phase <n>', '每阶段最大重试次数', (v) => Number(v))
|
|
337
|
+
.option('--prompt-dir <path>', '覆盖 auto phase prompt 模板目录')
|
|
338
|
+
.option('--convert-endpoint <url>', 'markitdown_serve 基址')
|
|
339
|
+
.option('--no-auto-repair', '禁用阶段失败自动修复(默认启用)')
|
|
340
|
+
.action(async (workdir, opts, cmd) => {
|
|
341
|
+
try {
|
|
342
|
+
await (0, auto_pack_req_1.runAutoPackReq)(workdir, {
|
|
343
|
+
...opts,
|
|
344
|
+
costAlertThreshold: opts.costAlert,
|
|
345
|
+
}, cmd);
|
|
346
|
+
}
|
|
347
|
+
catch (err) {
|
|
348
|
+
logger_1.logger.error(err.message);
|
|
349
|
+
process.exitCode = 1;
|
|
350
|
+
}
|
|
351
|
+
});
|
|
280
352
|
program
|
|
281
353
|
.command('wizard')
|
|
282
354
|
.description('交互式向导:分步填写路径与说明,可保存到 svharness.config.yaml 并/或立即执行 build/apply/convert。')
|
|
@@ -299,7 +371,7 @@ function main() {
|
|
|
299
371
|
.option('--endpoint <url>', 'markitdown_serve 基址(逗号分隔可配置 failover)')
|
|
300
372
|
.option('--concurrency <n>', '【默认 3】并发上传数', (v) => Number(v))
|
|
301
373
|
.option('--max-file-mb <n>', '【默认 50】单文件大小上限(MB)', (v) => Number(v))
|
|
302
|
-
.option('--timeout-sec <n>',
|
|
374
|
+
.option('--timeout-sec <n>', `【默认 ${convert_1.DEFAULT_CONVERT_TIMEOUT_SEC},15 分钟】单文件转换超时秒数`, (v) => Number(v))
|
|
303
375
|
.option('--type <type>', 'requirements | references')
|
|
304
376
|
.option('--force', '覆盖已存在同名 .md')
|
|
305
377
|
.option('--split-sheets-suffix <suffix>', 'xlsx/xls 按 sheet(##)拆分时的子目录后缀', '_split')
|
|
@@ -326,7 +398,7 @@ function main() {
|
|
|
326
398
|
yes: opts.yes,
|
|
327
399
|
verbose: opts.verbose,
|
|
328
400
|
}, loaded?.config.convert, loaded?.config.defaults, cmd);
|
|
329
|
-
await (0,
|
|
401
|
+
await (0, convert_2.runConvert)({
|
|
330
402
|
input: merged.input ?? [],
|
|
331
403
|
harness: merged.harness,
|
|
332
404
|
output: merged.output,
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getPreferredLocalClaudePath = getPreferredLocalClaudePath;
|
|
7
|
+
exports.resolveAcpAgentCli = resolveAcpAgentCli;
|
|
8
|
+
exports.ensureAcpAgentCliAvailable = ensureAcpAgentCliAvailable;
|
|
9
|
+
exports.formatAcpAgentCliLog = formatAcpAgentCliLog;
|
|
10
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const os_1 = __importDefault(require("os"));
|
|
13
|
+
const node_child_process_1 = require("node:child_process");
|
|
14
|
+
const codechat_runner_resolver_1 = require("./codechat-runner-resolver");
|
|
15
|
+
/** Preferred Claude install: ~/.local/bin/claude(.exe) */
|
|
16
|
+
function getPreferredLocalClaudePath(homedir = os_1.default.homedir(), platform = process.platform) {
|
|
17
|
+
const binName = platform === 'win32' ? 'claude.exe' : 'claude';
|
|
18
|
+
return node_path_1.default.join(homedir, '.local', 'bin', binName);
|
|
19
|
+
}
|
|
20
|
+
function normalizeComparable(p) {
|
|
21
|
+
return node_path_1.default.resolve(p).replace(/\\/g, '/').toLowerCase();
|
|
22
|
+
}
|
|
23
|
+
function canAccessExecutable(filePath) {
|
|
24
|
+
try {
|
|
25
|
+
fs_extra_1.default.accessSync(filePath, fs_extra_1.default.constants.F_OK);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function findExecutableInPath(name, platform = process.platform) {
|
|
33
|
+
try {
|
|
34
|
+
if (platform === 'win32') {
|
|
35
|
+
const out = (0, node_child_process_1.execSync)(`where.exe ${name}`, {
|
|
36
|
+
encoding: 'utf8',
|
|
37
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
38
|
+
windowsHide: true,
|
|
39
|
+
});
|
|
40
|
+
for (const line of out.split(/\r?\n/)) {
|
|
41
|
+
const trimmed = line.trim();
|
|
42
|
+
if (trimmed && canAccessExecutable(trimmed)) {
|
|
43
|
+
return node_path_1.default.resolve(trimmed);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const out = (0, node_child_process_1.execSync)(`command -v ${name}`, {
|
|
49
|
+
encoding: 'utf8',
|
|
50
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
51
|
+
}).trim();
|
|
52
|
+
if (out && canAccessExecutable(out)) {
|
|
53
|
+
return node_path_1.default.resolve(out);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// not in PATH
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function getOtherClaudeCandidates(homedir, platform, excludePath) {
|
|
62
|
+
const exclude = excludePath ? normalizeComparable(excludePath) : undefined;
|
|
63
|
+
const found = [];
|
|
64
|
+
const seen = new Set();
|
|
65
|
+
const push = (candidate) => {
|
|
66
|
+
if (!candidate || !canAccessExecutable(candidate))
|
|
67
|
+
return;
|
|
68
|
+
const key = normalizeComparable(candidate);
|
|
69
|
+
if (exclude && key === exclude)
|
|
70
|
+
return;
|
|
71
|
+
if (seen.has(key))
|
|
72
|
+
return;
|
|
73
|
+
seen.add(key);
|
|
74
|
+
found.push(node_path_1.default.resolve(candidate));
|
|
75
|
+
};
|
|
76
|
+
push(findExecutableInPath('claude', platform));
|
|
77
|
+
if (platform === 'win32') {
|
|
78
|
+
const appData = process.env.APPDATA ?? node_path_1.default.join(homedir, 'AppData', 'Roaming');
|
|
79
|
+
push(node_path_1.default.join(appData, 'npm', 'claude.cmd'));
|
|
80
|
+
push(node_path_1.default.join(appData, 'npm', 'claude'));
|
|
81
|
+
push(node_path_1.default.join(homedir, '.claude', 'local', 'claude.exe'));
|
|
82
|
+
}
|
|
83
|
+
return found;
|
|
84
|
+
}
|
|
85
|
+
const ALTERNATIVE_CLI_NAMES = [
|
|
86
|
+
'codechat',
|
|
87
|
+
'qodercli',
|
|
88
|
+
'traecli',
|
|
89
|
+
'opencode',
|
|
90
|
+
'codex',
|
|
91
|
+
];
|
|
92
|
+
function tryResolveCodechatRunner() {
|
|
93
|
+
try {
|
|
94
|
+
const { runner, source, label } = (0, codechat_runner_resolver_1.resolveCodechatRunner)();
|
|
95
|
+
return {
|
|
96
|
+
command: runner,
|
|
97
|
+
kind: 'codechat-runner',
|
|
98
|
+
source: `codechat-${source}`,
|
|
99
|
+
label,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function tryAlternativeClis(platform) {
|
|
107
|
+
for (const name of ALTERNATIVE_CLI_NAMES) {
|
|
108
|
+
const resolved = findExecutableInPath(name, platform);
|
|
109
|
+
if (resolved) {
|
|
110
|
+
return {
|
|
111
|
+
command: resolved,
|
|
112
|
+
kind: 'alternative-cli',
|
|
113
|
+
source: 'path',
|
|
114
|
+
label: name,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return tryResolveCodechatRunner();
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Resolve the CLI used by ACP (`--print` mode).
|
|
122
|
+
*
|
|
123
|
+
* Priority:
|
|
124
|
+
* 1. ~/.local/bin/claude(.exe)
|
|
125
|
+
* 2. Other claude installs (PATH, npm global, etc.)
|
|
126
|
+
* 3. Alternative CLIs (codechat, qodercli, traecli, …) or CodeChat runner
|
|
127
|
+
*/
|
|
128
|
+
function resolveAcpAgentCli(options = {}) {
|
|
129
|
+
if (options.testCandidates?.length) {
|
|
130
|
+
return options.testCandidates[0];
|
|
131
|
+
}
|
|
132
|
+
const platform = options.platform ?? process.platform;
|
|
133
|
+
const homedir = options.homedir ?? os_1.default.homedir();
|
|
134
|
+
const localClaude = getPreferredLocalClaudePath(homedir, platform);
|
|
135
|
+
if (canAccessExecutable(localClaude)) {
|
|
136
|
+
return {
|
|
137
|
+
command: node_path_1.default.resolve(localClaude),
|
|
138
|
+
kind: 'claude-local-bin',
|
|
139
|
+
source: 'local-bin',
|
|
140
|
+
label: localClaude,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (!options.skipProbe) {
|
|
144
|
+
const otherClaude = getOtherClaudeCandidates(homedir, platform, localClaude);
|
|
145
|
+
if (otherClaude.length > 0) {
|
|
146
|
+
return {
|
|
147
|
+
command: otherClaude[0],
|
|
148
|
+
kind: 'claude-path',
|
|
149
|
+
source: 'path',
|
|
150
|
+
label: otherClaude[0],
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const alt = tryAlternativeClis(platform);
|
|
154
|
+
if (alt)
|
|
155
|
+
return alt;
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
function buildAgentCliNotFoundError(homedir, platform) {
|
|
160
|
+
const localClaude = getPreferredLocalClaudePath(homedir, platform);
|
|
161
|
+
const altList = ALTERNATIVE_CLI_NAMES.join('、');
|
|
162
|
+
const lines = [
|
|
163
|
+
'未找到可用于 ACP 调用的 Agent CLI(codechat / claude)。已按优先级检查:',
|
|
164
|
+
` 1. 本地 Claude → ${localClaude}`,
|
|
165
|
+
' 2. 其他 Claude 安装(PATH / npm global)',
|
|
166
|
+
` 3. 其他 CLI(${altList})或 CodeChat runner`,
|
|
167
|
+
'',
|
|
168
|
+
'请先安装并配置其中之一,例如:',
|
|
169
|
+
` - 将 claude.exe 放到 ${node_path_1.default.join(homedir, '.local', 'bin')}`,
|
|
170
|
+
' - 或安装 CodeChat CLI / IDE 插件(~/.codechat/cli_app/)',
|
|
171
|
+
` - 或确保 ${altList} 之一在 PATH 中`,
|
|
172
|
+
'',
|
|
173
|
+
'配置完成后再执行:svharness auto',
|
|
174
|
+
];
|
|
175
|
+
return lines.join('\n');
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Ensure an Agent CLI is available before auto mode starts.
|
|
179
|
+
* Throws with a user-facing message when nothing is found.
|
|
180
|
+
*/
|
|
181
|
+
function ensureAcpAgentCliAvailable(options = {}) {
|
|
182
|
+
const resolved = resolveAcpAgentCli(options);
|
|
183
|
+
if (!resolved) {
|
|
184
|
+
const homedir = options.homedir ?? os_1.default.homedir();
|
|
185
|
+
const platform = options.platform ?? process.platform;
|
|
186
|
+
throw new Error(buildAgentCliNotFoundError(homedir, platform));
|
|
187
|
+
}
|
|
188
|
+
return resolved;
|
|
189
|
+
}
|
|
190
|
+
function formatAcpAgentCliLog(resolved) {
|
|
191
|
+
const detail = resolved.label ?? resolved.command;
|
|
192
|
+
return `${resolved.command} (${resolved.source}${detail ? `: ${detail}` : ''})`;
|
|
193
|
+
}
|
package/dist/lib/acp-client.js
CHANGED
|
@@ -29,8 +29,10 @@ function parseCompletionSignal(output) {
|
|
|
29
29
|
*/
|
|
30
30
|
class AcpClient {
|
|
31
31
|
mode;
|
|
32
|
-
|
|
32
|
+
agentCommand;
|
|
33
|
+
constructor(mode = 'auto', agentCommand = 'claude') {
|
|
33
34
|
this.mode = mode;
|
|
35
|
+
this.agentCommand = agentCommand;
|
|
34
36
|
}
|
|
35
37
|
async call(opts) {
|
|
36
38
|
if (this.mode === 'sdk') {
|
|
@@ -45,7 +47,7 @@ class AcpClient {
|
|
|
45
47
|
const timeoutMs = opts.timeoutMs ?? 1_800_000;
|
|
46
48
|
const isWin = process.platform === 'win32';
|
|
47
49
|
return new Promise((resolve) => {
|
|
48
|
-
const child = (0, node_child_process_1.spawn)(
|
|
50
|
+
const child = (0, node_child_process_1.spawn)(this.agentCommand, args, {
|
|
49
51
|
cwd: opts.workDir,
|
|
50
52
|
shell: isWin,
|
|
51
53
|
});
|
|
@@ -230,10 +230,15 @@ async function ensureUserEnv(workdir, config, syncEnv) {
|
|
|
230
230
|
}
|
|
231
231
|
function buildRunnerArgs(config, workdir, skipPermissions) {
|
|
232
232
|
const envFile = resolveUserEnvPath(config);
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
233
|
+
const resolvedWorkdir = path_1.default.resolve(workdir);
|
|
234
|
+
const args = [formatRunnerArg('--env-file', envFile)];
|
|
235
|
+
// run.bat → bootstrap.ps1: -cwd <path> must be two args; --cwd=path is dropped via cmd %*.
|
|
236
|
+
if (process.platform === 'win32') {
|
|
237
|
+
args.push('-cwd', resolvedWorkdir);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
args.push(formatRunnerArg('--cwd', resolvedWorkdir));
|
|
241
|
+
}
|
|
237
242
|
if (skipPermissions) {
|
|
238
243
|
args.push('--dangerously-skip-permissions');
|
|
239
244
|
}
|