harnessed 3.0.0 → 3.0.2
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 +91 -123
- package/dist/cli.mjs +143 -147
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/workflows/capabilities.yaml +1 -1
- package/workflows/defaults.yaml +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -793,7 +793,7 @@ var init_resume = __esm({
|
|
|
793
793
|
|
|
794
794
|
// package.json
|
|
795
795
|
var package_default = {
|
|
796
|
-
version: "3.0.
|
|
796
|
+
version: "3.0.2"};
|
|
797
797
|
|
|
798
798
|
// src/manifest/errors.ts
|
|
799
799
|
function instancePathToKeyPath(instancePath) {
|
|
@@ -2940,7 +2940,12 @@ function validateNonInteractiveFlags(raw, cmdName) {
|
|
|
2940
2940
|
|
|
2941
2941
|
// src/cli/execute-task.ts
|
|
2942
2942
|
function registerExecuteTask(program2) {
|
|
2943
|
-
program2.command("execute-task").description(
|
|
2943
|
+
program2.command("execute-task").description(
|
|
2944
|
+
"Run execute-task workflow (4-phase chain \u2192 ralph-loop COMPLETE; immediate by default \u2014 use --dry-run for preview)"
|
|
2945
|
+
).requiredOption("--task <text>", "task description (required)").option("--workflow <name>", "workflow name", "execute-task").option(
|
|
2946
|
+
"--apply",
|
|
2947
|
+
"(deprecated; kept for backward compat \u2014 execute-task spawns immediately by default)"
|
|
2948
|
+
).option("--dry-run", "preview only \u2014 do not spawn subagent (opt-in for advanced users)").option("--non-interactive", "CI / scripts \u2014 requires --apply or --dry-run").option("--model <model>", "subagent model: 'haiku' | 'sonnet' | 'opus'").option("--model-tier <tier>", "override: 'inherit' bypasses per-phase phase.model (B-10)").option("--max-iterations <n>", "ralph-loop max iter (default 20)", (v) => parseInt(v, 10)).action(async (raw) => {
|
|
2944
2949
|
validateNonInteractiveFlags(raw, "execute-task --task <text>");
|
|
2945
2950
|
if (!raw.task) {
|
|
2946
2951
|
console.error("error: --task <text> is required");
|
|
@@ -2963,7 +2968,7 @@ function registerExecuteTask(program2) {
|
|
|
2963
2968
|
};
|
|
2964
2969
|
}
|
|
2965
2970
|
const taskCtx = { task: raw.task, task_type: "execute-task" };
|
|
2966
|
-
const isDryRun = raw.dryRun === true
|
|
2971
|
+
const isDryRun = raw.dryRun === true;
|
|
2967
2972
|
if (isDryRun) {
|
|
2968
2973
|
console.log(
|
|
2969
2974
|
JSON.stringify({ workflow: phases.workflow, phases: phases.phases, taskCtx }, null, 2)
|
|
@@ -3041,8 +3046,10 @@ async function dirSizeKb(dir) {
|
|
|
3041
3046
|
return Math.round(total / 1024);
|
|
3042
3047
|
}
|
|
3043
3048
|
function registerGc(program2) {
|
|
3044
|
-
program2.command("gc").description(
|
|
3045
|
-
|
|
3049
|
+
program2.command("gc").description(
|
|
3050
|
+
"Garbage-collect old backup snapshots (immediate by default \u2014 use --dry-run for preview)"
|
|
3051
|
+
).option("--older-than <duration>", "delete snapshots older than (e.g. 30d / 24h / 4w)", "30d").option("--keep-last <N>", "always keep the most recent N snapshots", "0").option("--apply", "(deprecated; kept for backward compat \u2014 gc deletes immediately by default)").option("--dry-run", "preview only \u2014 do not delete (opt-in for advanced users)").action(async (opts) => {
|
|
3052
|
+
const dryRun = opts.dryRun === true;
|
|
3046
3053
|
const olderMs = parseDuration(opts.olderThan ?? "30d");
|
|
3047
3054
|
if (olderMs == null) {
|
|
3048
3055
|
console.error(
|
|
@@ -3092,7 +3099,7 @@ function registerGc(program2) {
|
|
|
3092
3099
|
console.log(` ${c.ts} ${c.manifest} (${c.sizeKb} KB)`);
|
|
3093
3100
|
if (!dryRun) await rm(c.path, { recursive: true, force: true });
|
|
3094
3101
|
}
|
|
3095
|
-
if (dryRun) console.log("\n(dry-run \u2014 re-run
|
|
3102
|
+
if (dryRun) console.log("\n(dry-run \u2014 re-run without --dry-run to actually delete)");
|
|
3096
3103
|
});
|
|
3097
3104
|
}
|
|
3098
3105
|
async function confirmAt(level, ctx) {
|
|
@@ -3397,6 +3404,9 @@ function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
|
|
|
3397
3404
|
});
|
|
3398
3405
|
});
|
|
3399
3406
|
}
|
|
3407
|
+
function getMcpSpawnCwd() {
|
|
3408
|
+
return homedir();
|
|
3409
|
+
}
|
|
3400
3410
|
|
|
3401
3411
|
// src/installers/ccPluginMarketplace.ts
|
|
3402
3412
|
function parseCmd(cmd) {
|
|
@@ -3448,8 +3458,8 @@ var installCcPluginMarketplace = async (ctx) => {
|
|
|
3448
3458
|
}
|
|
3449
3459
|
};
|
|
3450
3460
|
}
|
|
3451
|
-
const pluginName = parsed.pluginAtMkt.split("@")[0];
|
|
3452
|
-
const installArgs = ["plugin", "install", parsed.pluginAtMkt, "--scope", "
|
|
3461
|
+
const pluginName = parsed.pluginAtMkt.split("@")[0] ?? parsed.pluginAtMkt;
|
|
3462
|
+
const installArgs = ["plugin", "install", parsed.pluginAtMkt, "--scope", "user"];
|
|
3453
3463
|
const allArgs = [];
|
|
3454
3464
|
if (parsed.marketplaceRef !== null) {
|
|
3455
3465
|
allArgs.push(["plugin", "marketplace", "add", parsed.marketplaceRef]);
|
|
@@ -3457,30 +3467,30 @@ var installCcPluginMarketplace = async (ctx) => {
|
|
|
3457
3467
|
allArgs.push(installArgs);
|
|
3458
3468
|
for (const argSet of allArgs) {
|
|
3459
3469
|
for (const a of argSet) {
|
|
3460
|
-
const
|
|
3461
|
-
if (
|
|
3470
|
+
const violation = checkCmdString(a);
|
|
3471
|
+
if (violation) {
|
|
3462
3472
|
return {
|
|
3463
3473
|
ok: false,
|
|
3464
3474
|
phase: "preflight",
|
|
3465
3475
|
error: err(
|
|
3466
3476
|
ctx,
|
|
3467
3477
|
"/spec/install/cmd",
|
|
3468
|
-
`shell escape detected in constructed cc-plugin arg '${a.slice(0, 60)}': ${
|
|
3478
|
+
`shell escape detected in constructed cc-plugin arg '${a.slice(0, 60)}': ${violation.label} (${violation.hint})`,
|
|
3469
3479
|
"security-gate-bypass"
|
|
3470
3480
|
)
|
|
3471
3481
|
};
|
|
3472
3482
|
}
|
|
3473
3483
|
}
|
|
3474
3484
|
}
|
|
3475
|
-
const settingsFile = `${
|
|
3485
|
+
const settingsFile = `${getMcpSpawnCwd()}/.claude.json`;
|
|
3476
3486
|
const newEntry = JSON.stringify({ enabledPlugins: { [parsed.pluginAtMkt]: true } }, null, 2);
|
|
3477
3487
|
const plan = {
|
|
3478
3488
|
files: [
|
|
3479
3489
|
{
|
|
3480
3490
|
target: settingsFile,
|
|
3481
|
-
scope: "
|
|
3491
|
+
scope: "HOME",
|
|
3482
3492
|
oldText: "",
|
|
3483
|
-
newText: `// will be merged into
|
|
3493
|
+
newText: `// will be merged into ~/.claude.json enabledPlugins map by \`claude plugin install --scope user\`:
|
|
3484
3494
|
${newEntry}
|
|
3485
3495
|
`
|
|
3486
3496
|
}
|
|
@@ -3495,15 +3505,13 @@ ${newEntry}
|
|
|
3495
3505
|
if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
|
|
3496
3506
|
const bk = await backup(plan, ctx);
|
|
3497
3507
|
if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
|
|
3508
|
+
const spawnCwd = install.cwd ?? getMcpSpawnCwd();
|
|
3498
3509
|
let stepOneStderr = "";
|
|
3499
3510
|
if (parsed.marketplaceRef !== null) {
|
|
3500
|
-
const r1 = await runArgs(
|
|
3501
|
-
["plugin", "marketplace", "add", parsed.marketplaceRef],
|
|
3502
|
-
install.cwd ?? ctx.cwd
|
|
3503
|
-
);
|
|
3511
|
+
const r1 = await runArgs(["plugin", "marketplace", "add", parsed.marketplaceRef], spawnCwd);
|
|
3504
3512
|
stepOneStderr = r1.stderr;
|
|
3505
3513
|
}
|
|
3506
|
-
const r2 = await runArgs(installArgs,
|
|
3514
|
+
const r2 = await runArgs(installArgs, spawnCwd);
|
|
3507
3515
|
if (r2.exitCode !== 0) {
|
|
3508
3516
|
return {
|
|
3509
3517
|
ok: false,
|
|
@@ -3517,43 +3525,34 @@ ${newEntry}
|
|
|
3517
3525
|
)
|
|
3518
3526
|
};
|
|
3519
3527
|
}
|
|
3520
|
-
const verifyShell = process.platform === "win32" ? "cmd.exe" : "/bin/sh";
|
|
3521
|
-
const verifyFlag = process.platform === "win32" ? "/c" : "-c";
|
|
3522
|
-
const verifyLine = `claude plugin list --json | grep -q ${pluginName}`;
|
|
3523
|
-
const violation = checkCmdString(verifyLine);
|
|
3524
|
-
if (violation) {
|
|
3525
|
-
return {
|
|
3526
|
-
ok: false,
|
|
3527
|
-
phase: "verify",
|
|
3528
|
-
backupId: bk.backupId,
|
|
3529
|
-
error: err(
|
|
3530
|
-
ctx,
|
|
3531
|
-
"/spec/verify/cmd",
|
|
3532
|
-
`verify shell escape: ${violation.label}`,
|
|
3533
|
-
"security-gate-bypass"
|
|
3534
|
-
)
|
|
3535
|
-
};
|
|
3536
|
-
}
|
|
3537
3528
|
const vr = await new Promise((resolve9) => {
|
|
3538
|
-
const child = spawn(
|
|
3529
|
+
const child = spawn("claude", ["plugin", "list", "--json"], {
|
|
3530
|
+
cwd: spawnCwd,
|
|
3531
|
+
shell: process.platform === "win32",
|
|
3532
|
+
windowsHide: true
|
|
3533
|
+
});
|
|
3534
|
+
let stdout2 = "";
|
|
3539
3535
|
let stderr = "";
|
|
3536
|
+
child.stdout?.setEncoding("utf8").on("data", (c) => {
|
|
3537
|
+
stdout2 += c;
|
|
3538
|
+
});
|
|
3540
3539
|
child.stderr?.setEncoding("utf8").on("data", (c) => {
|
|
3541
3540
|
stderr += c;
|
|
3542
3541
|
});
|
|
3543
3542
|
const timer = setTimeout(() => {
|
|
3544
3543
|
child.kill("SIGKILL");
|
|
3545
|
-
resolve9({ exitCode: -1, stderr: `${stderr}[timeout]
|
|
3544
|
+
resolve9({ exitCode: -1, stderr: `${stderr}[timeout]`, stdout: stdout2 });
|
|
3546
3545
|
}, 15e3);
|
|
3547
3546
|
child.on("error", (e) => {
|
|
3548
3547
|
clearTimeout(timer);
|
|
3549
|
-
resolve9({ exitCode: -1, stderr: e.message });
|
|
3548
|
+
resolve9({ exitCode: -1, stderr: e.message, stdout: stdout2 });
|
|
3550
3549
|
});
|
|
3551
3550
|
child.on("close", (code) => {
|
|
3552
3551
|
clearTimeout(timer);
|
|
3553
|
-
resolve9({ exitCode: code ?? -1, stderr });
|
|
3552
|
+
resolve9({ exitCode: code ?? -1, stderr, stdout: stdout2 });
|
|
3554
3553
|
});
|
|
3555
3554
|
});
|
|
3556
|
-
if (vr.exitCode !== 0) {
|
|
3555
|
+
if (vr.exitCode !== 0 || !vr.stdout.includes(pluginName)) {
|
|
3557
3556
|
return {
|
|
3558
3557
|
ok: false,
|
|
3559
3558
|
phase: "verify",
|
|
@@ -3561,7 +3560,7 @@ ${newEntry}
|
|
|
3561
3560
|
error: err(
|
|
3562
3561
|
ctx,
|
|
3563
3562
|
"/spec/verify/cmd",
|
|
3564
|
-
`verify exit ${vr.exitCode}: ${vr.stderr.slice(0, 200)}`,
|
|
3563
|
+
`verify exit ${vr.exitCode} or '${pluginName}' not in plugin list stdout: ${vr.stderr.slice(0, 200)}`,
|
|
3565
3564
|
"verify-failed"
|
|
3566
3565
|
)
|
|
3567
3566
|
};
|
|
@@ -3569,8 +3568,9 @@ ${newEntry}
|
|
|
3569
3568
|
await updateInstalled(ctx.cwd, ctx.manifest.metadata.name, install.git_ref, "");
|
|
3570
3569
|
return { ok: true, backupId: bk.backupId, appliedFiles: [settingsFile] };
|
|
3571
3570
|
};
|
|
3572
|
-
var
|
|
3573
|
-
|
|
3571
|
+
var DEFAULT_VERIFY_TIMEOUT_MS = 15e3;
|
|
3572
|
+
var DEFAULT_INSTALL_TIMEOUT_MS = 6e4;
|
|
3573
|
+
async function spawnCmd(ctx, cmd, args, timeoutMs) {
|
|
3574
3574
|
const violation = checkCmdString(cmd);
|
|
3575
3575
|
if (violation) {
|
|
3576
3576
|
return {
|
|
@@ -3587,8 +3587,7 @@ async function spawnCmd(ctx, cmd, args) {
|
|
|
3587
3587
|
};
|
|
3588
3588
|
}
|
|
3589
3589
|
const installCfg = ctx.manifest.spec.install;
|
|
3590
|
-
const
|
|
3591
|
-
const timeoutMs = verifyCfg.timeout_ms ?? DEFAULT_TIMEOUT_MS;
|
|
3590
|
+
const effectiveTimeoutMs = timeoutMs ?? DEFAULT_INSTALL_TIMEOUT_MS;
|
|
3592
3591
|
const env = { ...process.env, ...installCfg.env ?? {} };
|
|
3593
3592
|
const cwd = installCfg.cwd ?? ctx.cwd;
|
|
3594
3593
|
let child;
|
|
@@ -3615,13 +3614,13 @@ async function spawnCmd(ctx, cmd, args) {
|
|
|
3615
3614
|
error: {
|
|
3616
3615
|
file: ctx.manifest.metadata.name,
|
|
3617
3616
|
path: "/spec/install/cmd",
|
|
3618
|
-
message: `spawn timed out after ${
|
|
3617
|
+
message: `spawn timed out after ${effectiveTimeoutMs}ms (cmd: ${cmd}); partial stderr: ${stderr.slice(0, 200)}`,
|
|
3619
3618
|
line: null,
|
|
3620
3619
|
column: null,
|
|
3621
3620
|
keyword: "spawn-timeout"
|
|
3622
3621
|
}
|
|
3623
3622
|
});
|
|
3624
|
-
},
|
|
3623
|
+
}, effectiveTimeoutMs);
|
|
3625
3624
|
child.on("error", (err2) => {
|
|
3626
3625
|
clearTimeout(timer);
|
|
3627
3626
|
resolve9({
|
|
@@ -3767,7 +3766,7 @@ var installGitCloneWithSetup = async (ctx) => {
|
|
|
3767
3766
|
if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
|
|
3768
3767
|
const bk = await backup(plan, ctx);
|
|
3769
3768
|
if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
|
|
3770
|
-
const sp = await spawnCmd(ctx, install.cmd, []);
|
|
3769
|
+
const sp = await spawnCmd(ctx, install.cmd, [], DEFAULT_INSTALL_TIMEOUT_MS);
|
|
3771
3770
|
if (!("exitCode" in sp)) return { ...sp, backupId: bk.backupId };
|
|
3772
3771
|
if (sp.exitCode !== 0) {
|
|
3773
3772
|
return {
|
|
@@ -3809,7 +3808,8 @@ var installGitCloneWithSetup = async (ctx) => {
|
|
|
3809
3808
|
)
|
|
3810
3809
|
};
|
|
3811
3810
|
}
|
|
3812
|
-
const
|
|
3811
|
+
const verifyTimeoutMs = ctx.manifest.spec.verify.timeout_ms ?? DEFAULT_VERIFY_TIMEOUT_MS;
|
|
3812
|
+
const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, [], verifyTimeoutMs);
|
|
3813
3813
|
if (!("exitCode" in vr)) return { ...vr, backupId: bk.backupId };
|
|
3814
3814
|
const expected = ctx.manifest.spec.verify.expected_exit_code ?? 0;
|
|
3815
3815
|
if (vr.exitCode !== expected) {
|
|
@@ -3912,33 +3912,23 @@ var installMcpHttpAdd = async (ctx) => {
|
|
|
3912
3912
|
}
|
|
3913
3913
|
};
|
|
3914
3914
|
}
|
|
3915
|
-
const addArgs = [
|
|
3916
|
-
"mcp",
|
|
3917
|
-
"add",
|
|
3918
|
-
"--scope",
|
|
3919
|
-
"project",
|
|
3920
|
-
"--transport",
|
|
3921
|
-
"http",
|
|
3922
|
-
...hdr.flat,
|
|
3923
|
-
name,
|
|
3924
|
-
url
|
|
3925
|
-
];
|
|
3915
|
+
const addArgs = ["mcp", "add", "--scope", "user", "--transport", "http", ...hdr.flat, name, url];
|
|
3926
3916
|
for (const a of addArgs) {
|
|
3927
|
-
const
|
|
3928
|
-
if (
|
|
3917
|
+
const violation = checkCmdString(a);
|
|
3918
|
+
if (violation) {
|
|
3929
3919
|
return {
|
|
3930
3920
|
ok: false,
|
|
3931
3921
|
phase: "preflight",
|
|
3932
3922
|
error: err(
|
|
3933
3923
|
ctx,
|
|
3934
3924
|
"/spec/install/cmd",
|
|
3935
|
-
`shell escape detected in constructed mcp-http-add arg '${a.slice(0, 60)}': ${
|
|
3925
|
+
`shell escape detected in constructed mcp-http-add arg '${a.slice(0, 60)}': ${violation.label} (${violation.hint})`,
|
|
3936
3926
|
"security-gate-bypass"
|
|
3937
3927
|
)
|
|
3938
3928
|
};
|
|
3939
3929
|
}
|
|
3940
3930
|
}
|
|
3941
|
-
const mcpFile = `${
|
|
3931
|
+
const mcpFile = `${getMcpSpawnCwd()}/.claude.json`;
|
|
3942
3932
|
const headersObj = {};
|
|
3943
3933
|
for (let i = 0; i < hdr.flat.length; i += 2) {
|
|
3944
3934
|
const kv = hdr.flat[i + 1];
|
|
@@ -3952,9 +3942,9 @@ var installMcpHttpAdd = async (ctx) => {
|
|
|
3952
3942
|
files: [
|
|
3953
3943
|
{
|
|
3954
3944
|
target: mcpFile,
|
|
3955
|
-
scope: "
|
|
3945
|
+
scope: "HOME",
|
|
3956
3946
|
oldText: "",
|
|
3957
|
-
newText: `// will be merged into .
|
|
3947
|
+
newText: `// will be merged into ~/.claude.json mcpServers map by \`claude mcp add --scope user\`:
|
|
3958
3948
|
${newEntry}
|
|
3959
3949
|
`
|
|
3960
3950
|
}
|
|
@@ -3969,9 +3959,10 @@ ${newEntry}
|
|
|
3969
3959
|
if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
|
|
3970
3960
|
const bk = await backup(plan, ctx);
|
|
3971
3961
|
if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
|
|
3972
|
-
const
|
|
3962
|
+
const spawnCwd = install.cwd ?? getMcpSpawnCwd();
|
|
3963
|
+
const r = await runArgs(addArgs, spawnCwd);
|
|
3973
3964
|
if (r.exitCode !== 0) {
|
|
3974
|
-
if (r.stderr.includes("already exists
|
|
3965
|
+
if (r.stderr.includes("already exists")) {
|
|
3975
3966
|
return { ok: true, alreadyInstalled: true, backupId: bk.backupId };
|
|
3976
3967
|
}
|
|
3977
3968
|
return {
|
|
@@ -3986,43 +3977,34 @@ ${newEntry}
|
|
|
3986
3977
|
)
|
|
3987
3978
|
};
|
|
3988
3979
|
}
|
|
3989
|
-
const verifyShell = process.platform === "win32" ? "cmd.exe" : "/bin/sh";
|
|
3990
|
-
const verifyFlag = process.platform === "win32" ? "/c" : "-c";
|
|
3991
|
-
const verifyLine = `claude mcp list | grep -q ${name}`;
|
|
3992
|
-
const violation = checkCmdString(verifyLine);
|
|
3993
|
-
if (violation) {
|
|
3994
|
-
return {
|
|
3995
|
-
ok: false,
|
|
3996
|
-
phase: "verify",
|
|
3997
|
-
backupId: bk.backupId,
|
|
3998
|
-
error: err(
|
|
3999
|
-
ctx,
|
|
4000
|
-
"/spec/verify/cmd",
|
|
4001
|
-
`verify shell escape: ${violation.label}`,
|
|
4002
|
-
"security-gate-bypass"
|
|
4003
|
-
)
|
|
4004
|
-
};
|
|
4005
|
-
}
|
|
4006
3980
|
const vr = await new Promise((resolve9) => {
|
|
4007
|
-
const child = spawn(
|
|
3981
|
+
const child = spawn("claude", ["mcp", "list"], {
|
|
3982
|
+
cwd: spawnCwd,
|
|
3983
|
+
shell: process.platform === "win32",
|
|
3984
|
+
windowsHide: true
|
|
3985
|
+
});
|
|
3986
|
+
let stdout2 = "";
|
|
4008
3987
|
let stderr = "";
|
|
3988
|
+
child.stdout?.setEncoding("utf8").on("data", (c) => {
|
|
3989
|
+
stdout2 += c;
|
|
3990
|
+
});
|
|
4009
3991
|
child.stderr?.setEncoding("utf8").on("data", (c) => {
|
|
4010
3992
|
stderr += c;
|
|
4011
3993
|
});
|
|
4012
3994
|
const timer = setTimeout(() => {
|
|
4013
3995
|
child.kill("SIGKILL");
|
|
4014
|
-
resolve9({ exitCode: -1, stderr: `${stderr}[timeout]
|
|
3996
|
+
resolve9({ exitCode: -1, stderr: `${stderr}[timeout]`, stdout: stdout2 });
|
|
4015
3997
|
}, 15e3);
|
|
4016
3998
|
child.on("error", (e) => {
|
|
4017
3999
|
clearTimeout(timer);
|
|
4018
|
-
resolve9({ exitCode: -1, stderr: e.message });
|
|
4000
|
+
resolve9({ exitCode: -1, stderr: e.message, stdout: stdout2 });
|
|
4019
4001
|
});
|
|
4020
4002
|
child.on("close", (code) => {
|
|
4021
4003
|
clearTimeout(timer);
|
|
4022
|
-
resolve9({ exitCode: code ?? -1, stderr });
|
|
4004
|
+
resolve9({ exitCode: code ?? -1, stderr, stdout: stdout2 });
|
|
4023
4005
|
});
|
|
4024
4006
|
});
|
|
4025
|
-
if (vr.exitCode !== 0) {
|
|
4007
|
+
if (vr.exitCode !== 0 || !vr.stdout.includes(name)) {
|
|
4026
4008
|
return {
|
|
4027
4009
|
ok: false,
|
|
4028
4010
|
phase: "verify",
|
|
@@ -4030,7 +4012,7 @@ ${newEntry}
|
|
|
4030
4012
|
error: err(
|
|
4031
4013
|
ctx,
|
|
4032
4014
|
"/spec/verify/cmd",
|
|
4033
|
-
`verify exit ${vr.exitCode}: ${vr.stderr.slice(0, 200)}`,
|
|
4015
|
+
`verify exit ${vr.exitCode} or '${name}' not in mcp list stdout: ${vr.stderr.slice(0, 200)}`,
|
|
4034
4016
|
"verify-failed"
|
|
4035
4017
|
)
|
|
4036
4018
|
};
|
|
@@ -4066,7 +4048,7 @@ var installMcpStdioAdd = async (ctx) => {
|
|
|
4066
4048
|
"mcp",
|
|
4067
4049
|
"add",
|
|
4068
4050
|
"--scope",
|
|
4069
|
-
"
|
|
4051
|
+
"user",
|
|
4070
4052
|
"--transport",
|
|
4071
4053
|
"stdio",
|
|
4072
4054
|
name,
|
|
@@ -4076,21 +4058,21 @@ var installMcpStdioAdd = async (ctx) => {
|
|
|
4076
4058
|
`${pkg}@${ver}`
|
|
4077
4059
|
];
|
|
4078
4060
|
for (const a of addArgs) {
|
|
4079
|
-
const
|
|
4080
|
-
if (
|
|
4061
|
+
const violation = checkCmdString(a);
|
|
4062
|
+
if (violation) {
|
|
4081
4063
|
return {
|
|
4082
4064
|
ok: false,
|
|
4083
4065
|
phase: "preflight",
|
|
4084
4066
|
error: err(
|
|
4085
4067
|
ctx,
|
|
4086
4068
|
"/spec/install/cmd",
|
|
4087
|
-
`shell escape detected in constructed mcp-add arg '${a.slice(0, 60)}': ${
|
|
4069
|
+
`shell escape detected in constructed mcp-add arg '${a.slice(0, 60)}': ${violation.label} (${violation.hint})`,
|
|
4088
4070
|
"security-gate-bypass"
|
|
4089
4071
|
)
|
|
4090
4072
|
};
|
|
4091
4073
|
}
|
|
4092
4074
|
}
|
|
4093
|
-
const mcpFile = `${
|
|
4075
|
+
const mcpFile = `${getMcpSpawnCwd()}/.claude.json`;
|
|
4094
4076
|
const newEntry = JSON.stringify(
|
|
4095
4077
|
{ [name]: { type: "stdio", command: "npx", args: ["--yes", `${pkg}@${ver}`] } },
|
|
4096
4078
|
null,
|
|
@@ -4100,9 +4082,9 @@ var installMcpStdioAdd = async (ctx) => {
|
|
|
4100
4082
|
files: [
|
|
4101
4083
|
{
|
|
4102
4084
|
target: mcpFile,
|
|
4103
|
-
scope: "
|
|
4085
|
+
scope: "HOME",
|
|
4104
4086
|
oldText: "",
|
|
4105
|
-
newText: `// will be merged into .
|
|
4087
|
+
newText: `// will be merged into ~/.claude.json mcpServers map by \`claude mcp add --scope user\`:
|
|
4106
4088
|
${newEntry}
|
|
4107
4089
|
`
|
|
4108
4090
|
}
|
|
@@ -4117,9 +4099,10 @@ ${newEntry}
|
|
|
4117
4099
|
if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
|
|
4118
4100
|
const bk = await backup(plan, ctx);
|
|
4119
4101
|
if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
|
|
4120
|
-
const
|
|
4102
|
+
const spawnCwd = install.cwd ?? getMcpSpawnCwd();
|
|
4103
|
+
const r = await runArgs(addArgs, spawnCwd);
|
|
4121
4104
|
if (r.exitCode !== 0) {
|
|
4122
|
-
if (r.stderr.includes("already exists
|
|
4105
|
+
if (r.stderr.includes("already exists")) {
|
|
4123
4106
|
return { ok: true, alreadyInstalled: true, backupId: bk.backupId };
|
|
4124
4107
|
}
|
|
4125
4108
|
return {
|
|
@@ -4134,43 +4117,34 @@ ${newEntry}
|
|
|
4134
4117
|
)
|
|
4135
4118
|
};
|
|
4136
4119
|
}
|
|
4137
|
-
const verifyShell = process.platform === "win32" ? "cmd.exe" : "/bin/sh";
|
|
4138
|
-
const verifyFlag = process.platform === "win32" ? "/c" : "-c";
|
|
4139
|
-
const verifyLine = `claude mcp list | grep -q ${name}`;
|
|
4140
|
-
const violation = checkCmdString(verifyLine);
|
|
4141
|
-
if (violation) {
|
|
4142
|
-
return {
|
|
4143
|
-
ok: false,
|
|
4144
|
-
phase: "verify",
|
|
4145
|
-
backupId: bk.backupId,
|
|
4146
|
-
error: err(
|
|
4147
|
-
ctx,
|
|
4148
|
-
"/spec/verify/cmd",
|
|
4149
|
-
`verify shell escape: ${violation.label}`,
|
|
4150
|
-
"security-gate-bypass"
|
|
4151
|
-
)
|
|
4152
|
-
};
|
|
4153
|
-
}
|
|
4154
4120
|
const vr = await new Promise((resolve9) => {
|
|
4155
|
-
const child = spawn(
|
|
4121
|
+
const child = spawn("claude", ["mcp", "list"], {
|
|
4122
|
+
cwd: spawnCwd,
|
|
4123
|
+
shell: process.platform === "win32",
|
|
4124
|
+
windowsHide: true
|
|
4125
|
+
});
|
|
4126
|
+
let stdout2 = "";
|
|
4156
4127
|
let stderr = "";
|
|
4128
|
+
child.stdout?.setEncoding("utf8").on("data", (c) => {
|
|
4129
|
+
stdout2 += c;
|
|
4130
|
+
});
|
|
4157
4131
|
child.stderr?.setEncoding("utf8").on("data", (c) => {
|
|
4158
4132
|
stderr += c;
|
|
4159
4133
|
});
|
|
4160
4134
|
const timer = setTimeout(() => {
|
|
4161
4135
|
child.kill("SIGKILL");
|
|
4162
|
-
resolve9({ exitCode: -1, stderr: `${stderr}[timeout]
|
|
4136
|
+
resolve9({ exitCode: -1, stderr: `${stderr}[timeout]`, stdout: stdout2 });
|
|
4163
4137
|
}, 15e3);
|
|
4164
4138
|
child.on("error", (e) => {
|
|
4165
4139
|
clearTimeout(timer);
|
|
4166
|
-
resolve9({ exitCode: -1, stderr: e.message });
|
|
4140
|
+
resolve9({ exitCode: -1, stderr: e.message, stdout: stdout2 });
|
|
4167
4141
|
});
|
|
4168
4142
|
child.on("close", (code) => {
|
|
4169
4143
|
clearTimeout(timer);
|
|
4170
|
-
resolve9({ exitCode: code ?? -1, stderr });
|
|
4144
|
+
resolve9({ exitCode: code ?? -1, stderr, stdout: stdout2 });
|
|
4171
4145
|
});
|
|
4172
4146
|
});
|
|
4173
|
-
if (vr.exitCode !== 0) {
|
|
4147
|
+
if (vr.exitCode !== 0 || !vr.stdout.includes(name)) {
|
|
4174
4148
|
return {
|
|
4175
4149
|
ok: false,
|
|
4176
4150
|
phase: "verify",
|
|
@@ -4178,7 +4152,7 @@ ${newEntry}
|
|
|
4178
4152
|
error: err(
|
|
4179
4153
|
ctx,
|
|
4180
4154
|
"/spec/verify/cmd",
|
|
4181
|
-
`verify exit ${vr.exitCode}: ${vr.stderr.slice(0, 200)}`,
|
|
4155
|
+
`verify exit ${vr.exitCode} or '${name}' not in mcp list stdout: ${vr.stderr.slice(0, 200)}`,
|
|
4182
4156
|
"verify-failed"
|
|
4183
4157
|
)
|
|
4184
4158
|
};
|
|
@@ -4244,7 +4218,7 @@ var installNpmCli = async (ctx) => {
|
|
|
4244
4218
|
if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
|
|
4245
4219
|
const bk = await backup(plan, ctx);
|
|
4246
4220
|
if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
|
|
4247
|
-
const sp = await spawnCmd(ctx, cmd, []);
|
|
4221
|
+
const sp = await spawnCmd(ctx, cmd, [], DEFAULT_INSTALL_TIMEOUT_MS);
|
|
4248
4222
|
if (!("exitCode" in sp)) return { ...sp, backupId: bk.backupId };
|
|
4249
4223
|
if (sp.exitCode !== 0) {
|
|
4250
4224
|
return {
|
|
@@ -4259,7 +4233,8 @@ var installNpmCli = async (ctx) => {
|
|
|
4259
4233
|
)
|
|
4260
4234
|
};
|
|
4261
4235
|
}
|
|
4262
|
-
const
|
|
4236
|
+
const verifyTimeoutMs = ctx.manifest.spec.verify.timeout_ms ?? DEFAULT_VERIFY_TIMEOUT_MS;
|
|
4237
|
+
const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, [], verifyTimeoutMs);
|
|
4263
4238
|
if (!("exitCode" in vr)) return { ...vr, backupId: bk.backupId };
|
|
4264
4239
|
const expected = ctx.manifest.spec.verify.expected_exit_code ?? 0;
|
|
4265
4240
|
if (vr.exitCode !== expected) {
|
|
@@ -4360,7 +4335,7 @@ var installNpxSkillInstaller = async (ctx) => {
|
|
|
4360
4335
|
if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
|
|
4361
4336
|
const bk = await backup(plan, ctx);
|
|
4362
4337
|
if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
|
|
4363
|
-
const sp = await spawnCmd(ctx, install.cmd, []);
|
|
4338
|
+
const sp = await spawnCmd(ctx, install.cmd, [], DEFAULT_INSTALL_TIMEOUT_MS);
|
|
4364
4339
|
if (!("exitCode" in sp)) return { ...sp, backupId: bk.backupId };
|
|
4365
4340
|
if (sp.exitCode !== 0) {
|
|
4366
4341
|
return {
|
|
@@ -4393,7 +4368,8 @@ var installNpxSkillInstaller = async (ctx) => {
|
|
|
4393
4368
|
}
|
|
4394
4369
|
};
|
|
4395
4370
|
}
|
|
4396
|
-
const
|
|
4371
|
+
const verifyTimeoutMs = ctx.manifest.spec.verify.timeout_ms ?? DEFAULT_VERIFY_TIMEOUT_MS;
|
|
4372
|
+
const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, [], verifyTimeoutMs);
|
|
4397
4373
|
if (!("exitCode" in vr)) return { ...vr, backupId: bk.backupId };
|
|
4398
4374
|
const expected = ctx.manifest.spec.verify.expected_exit_code ?? 0;
|
|
4399
4375
|
if (vr.exitCode !== expected) {
|
|
@@ -4465,7 +4441,7 @@ function formatError(e) {
|
|
|
4465
4441
|
return `${head}${where}${tip}`;
|
|
4466
4442
|
}
|
|
4467
4443
|
function registerInstall(program2) {
|
|
4468
|
-
program2.command("install <name>").description("Install an upstream (
|
|
4444
|
+
program2.command("install <name>").description("Install an upstream (immediate by default \u2014 use --dry-run for preview)").option("--apply", "(deprecated; kept for backward compat \u2014 install is immediate by default)").option("--dry-run", "preview only \u2014 do not write to disk (opt-in for advanced users)").option("--system", "allow L4 system-wide install (e.g. global npm install)").option("--non-interactive", "skip all prompts (CI / scripts) \u2014 requires --apply or --dry-run").option("--full-diff", "expand diffs longer than 200 lines").option("--no-color", "disable ANSI colors (auto-detected when piped)").option(
|
|
4469
4445
|
"--known-good",
|
|
4470
4446
|
"use known-good version lock from versions/<harnessed-ver>-known-good.yaml"
|
|
4471
4447
|
).action(async (name, raw) => {
|
|
@@ -4497,9 +4473,10 @@ function registerInstall(program2) {
|
|
|
4497
4473
|
console.error(` fix: run 'harnessed audit' to inspect manifest issues`);
|
|
4498
4474
|
process.exit(1);
|
|
4499
4475
|
}
|
|
4476
|
+
const dryRun = raw.dryRun === true;
|
|
4500
4477
|
const opts = {
|
|
4501
|
-
apply:
|
|
4502
|
-
dryRun
|
|
4478
|
+
apply: !dryRun,
|
|
4479
|
+
dryRun,
|
|
4503
4480
|
system: raw.system === true,
|
|
4504
4481
|
nonInteractive: raw.nonInteractive === true,
|
|
4505
4482
|
fullDiff: raw.fullDiff === true,
|
|
@@ -4545,11 +4522,17 @@ async function listBaseManifests(cwd) {
|
|
|
4545
4522
|
return out;
|
|
4546
4523
|
}
|
|
4547
4524
|
function registerInstallBase(program2) {
|
|
4548
|
-
program2.command("install-base").description(
|
|
4525
|
+
program2.command("install-base").description(
|
|
4526
|
+
"Install the phase 1.3 base profile (immediate by default \u2014 use --dry-run for preview)"
|
|
4527
|
+
).option(
|
|
4528
|
+
"--apply",
|
|
4529
|
+
"(deprecated; kept for backward compat \u2014 install-base is immediate by default)"
|
|
4530
|
+
).option("--dry-run", "preview only \u2014 do not write to disk (opt-in for advanced users)").option("--non-interactive", "skip all prompts (CI / scripts) \u2014 requires --apply or --dry-run").action(async (raw) => {
|
|
4549
4531
|
validateNonInteractiveFlags(raw, "install-base");
|
|
4532
|
+
const dryRun = raw.dryRun === true;
|
|
4550
4533
|
const opts = {
|
|
4551
|
-
apply:
|
|
4552
|
-
dryRun
|
|
4534
|
+
apply: !dryRun,
|
|
4535
|
+
dryRun,
|
|
4553
4536
|
system: false,
|
|
4554
4537
|
nonInteractive: raw.nonInteractive === true,
|
|
4555
4538
|
fullDiff: false,
|
|
@@ -4609,7 +4592,12 @@ function basename(upstream) {
|
|
|
4609
4592
|
return (upstream.split("/").pop() ?? upstream).replace(/\.git$/, "");
|
|
4610
4593
|
}
|
|
4611
4594
|
function registerManifestAdd(program2) {
|
|
4612
|
-
program2.command("manifest-add <upstream>").description(
|
|
4595
|
+
program2.command("manifest-add <upstream>").description(
|
|
4596
|
+
"Add a new upstream adapter (EE-5 5-question merge gate; immediate by default \u2014 use --dry-run for preview)"
|
|
4597
|
+
).option("--category <cat>", "manifest category (skill-packs | tools)", "skill-packs").option("--name <name>", "short adapter name (defaults to <upstream> basename)").option(
|
|
4598
|
+
"--apply",
|
|
4599
|
+
"(deprecated; kept for backward compat \u2014 manifest-add persists immediately by default)"
|
|
4600
|
+
).option("--dry-run", "preview only \u2014 do not write JSON (opt-in for advanced users)").option("--non-interactive", "CI/scripts \u2014 requires --apply or --dry-run; WARN-only dry-run").action(async (upstream, raw) => {
|
|
4613
4601
|
validateNonInteractiveFlags(raw, "manifest-add <upstream>");
|
|
4614
4602
|
const name = raw.name ?? basename(upstream);
|
|
4615
4603
|
const category = raw.category ?? "skill-packs";
|
|
@@ -4638,7 +4626,8 @@ function registerManifestAdd(program2) {
|
|
|
4638
4626
|
payload[f] = a;
|
|
4639
4627
|
}
|
|
4640
4628
|
rl.close();
|
|
4641
|
-
|
|
4629
|
+
const dryRun = raw.dryRun === true;
|
|
4630
|
+
if (!dryRun) {
|
|
4642
4631
|
writeFileSync(outPath, `${JSON.stringify(payload, null, 2)}
|
|
4643
4632
|
`, "utf8");
|
|
4644
4633
|
console.log(`[manifest-add] EE-5 gate passed; wrote ${outPath}`);
|
|
@@ -4652,14 +4641,19 @@ function registerManifestAdd(program2) {
|
|
|
4652
4641
|
|
|
4653
4642
|
// src/cli/research.ts
|
|
4654
4643
|
function registerResearch(program2) {
|
|
4655
|
-
program2.command("research").description(
|
|
4644
|
+
program2.command("research").description(
|
|
4645
|
+
"Run research workflow (search category sub-routing \u2192 spawn \u2192 verbatim COMPLETE; immediate by default \u2014 use --dry-run for preview)"
|
|
4646
|
+
).requiredOption("--query <text>", "research prompt (required)").option(
|
|
4647
|
+
"--apply",
|
|
4648
|
+
"(deprecated; kept for backward compat \u2014 research spawns immediately by default)"
|
|
4649
|
+
).option("--dry-run", "preview only \u2014 do not spawn subagent (opt-in for advanced users)").option("--non-interactive", "skip all prompts (CI / scripts) \u2014 requires --apply or --dry-run").option("--model <model>", "subagent model: 'haiku' | 'sonnet' | 'opus'").action(async (raw) => {
|
|
4656
4650
|
validateNonInteractiveFlags(raw, "research --query <text>");
|
|
4657
4651
|
if (!raw.query) {
|
|
4658
4652
|
console.error("error: --query <text> is required");
|
|
4659
4653
|
process.exit(2);
|
|
4660
4654
|
}
|
|
4661
4655
|
const taskCtx = { task: raw.query, task_type: "search" };
|
|
4662
|
-
if (raw.dryRun === true
|
|
4656
|
+
if (raw.dryRun === true) {
|
|
4663
4657
|
const preview = await runRouting(taskCtx, {
|
|
4664
4658
|
skillsRoot: void 0,
|
|
4665
4659
|
// Stub spawn — dry-run never reaches it; explicit COMPLETE keeps shape happy.
|
|
@@ -4677,7 +4671,9 @@ function registerResearch(program2) {
|
|
|
4677
4671
|
}
|
|
4678
4672
|
console.log(`[dry-run] matched_rule: ${preview.matchedRule?.id ?? "(fallback supervisor)"}`);
|
|
4679
4673
|
console.log(`[dry-run] query: ${raw.query}`);
|
|
4680
|
-
console.log(
|
|
4674
|
+
console.log(
|
|
4675
|
+
" (run without --dry-run to spawn the subagent and emit verbatim COMPLETE round-trip)"
|
|
4676
|
+
);
|
|
4681
4677
|
process.exit(0);
|
|
4682
4678
|
}
|
|
4683
4679
|
const result = await runRouting(taskCtx, {
|
|
@@ -4869,7 +4865,7 @@ async function warnIfAgentTeamsMissing() {
|
|
|
4869
4865
|
console.warn("\n\u26A0\uFE0F Agent Teams \u672A\u542F\u7528 \u2014 parallelism-gate \u5347\u7EA7\u8DEF\u5F84\u4E0D\u53EF\u7528");
|
|
4870
4866
|
console.warn(" \u4FEE\u590D: claude config set env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS 1");
|
|
4871
4867
|
console.warn(
|
|
4872
|
-
" \u8BF4\u660E: harnessed
|
|
4868
|
+
" \u8BF4\u660E: harnessed v3.0 \u4E09\u5C42\u6808\u65B9\u6CD5\u8BBA parallelism-gate \u5347\u7EA7\u8DEF\u5F84\u9700 CC 2.1.133+ Agent Teams enable"
|
|
4873
4869
|
);
|
|
4874
4870
|
console.warn(
|
|
4875
4871
|
" \u4E0D\u963B\u585E setup,\u540E\u7EED parallelism-gate workflow phase \u89E6\u53D1\u65F6\u81EA\u52A8\u964D\u7EA7 subagent fan-out\n"
|
|
@@ -5025,7 +5021,7 @@ MCP servers configured. Run \`/mcp\` in Claude Code to verify each server's conn
|
|
|
5025
5021
|
);
|
|
5026
5022
|
}
|
|
5027
5023
|
console.log(
|
|
5028
|
-
"\n\u2713 harnessed
|
|
5024
|
+
"\n\u2713 harnessed v3.0 \u4E09\u5C42\u6808\u65B9\u6CD5\u8BBA bundled \u2014 23 workflows (4 master + 18 sub + 1 standalone) + 6 disciplines + 10 judgments + ~83 capabilities ready"
|
|
5029
5025
|
);
|
|
5030
5026
|
console.log(
|
|
5031
5027
|
" workflows in <packageRoot>/workflows/ (Pure bundled, NOT user-dir override per D-01)"
|
|
@@ -5320,12 +5316,12 @@ async function runUninstall(manifest, opts) {
|
|
|
5320
5316
|
|
|
5321
5317
|
// src/cli/uninstall.ts
|
|
5322
5318
|
function registerUninstall(program2) {
|
|
5323
|
-
program2.command("uninstall <name>").description("Uninstall an upstream (
|
|
5319
|
+
program2.command("uninstall <name>").description("Uninstall an upstream (immediate by default \u2014 use --dry-run for preview)").option("--apply", "(deprecated; kept for backward compat \u2014 uninstall is immediate by default)").option("--dry-run", "preview only \u2014 do not delete files (opt-in for advanced users)").option("--yes", "skip interactive confirm (CI / scripts) \u2014 fatal with --dry-run").option("--non-interactive", "alias for --yes (CI compat)").action(async (name, raw) => {
|
|
5324
5320
|
const yes = raw.yes === true || raw.nonInteractive === true;
|
|
5325
|
-
if (yes &&
|
|
5321
|
+
if (yes && raw.dryRun) {
|
|
5326
5322
|
console.error(
|
|
5327
|
-
`error: --yes
|
|
5328
|
-
fix: harnessed uninstall ${name} --yes --
|
|
5323
|
+
`error: --yes is incompatible with --dry-run (dry-run does not mutate)
|
|
5324
|
+
fix: harnessed uninstall ${name} --yes (immediate) OR harnessed uninstall ${name} --dry-run (preview)`
|
|
5329
5325
|
);
|
|
5330
5326
|
process.exit(2);
|
|
5331
5327
|
}
|
|
@@ -5356,10 +5352,10 @@ function registerUninstall(program2) {
|
|
|
5356
5352
|
process.exit(1);
|
|
5357
5353
|
}
|
|
5358
5354
|
const method = v.manifest.spec.install.method;
|
|
5359
|
-
const dryRun = raw.dryRun === true
|
|
5355
|
+
const dryRun = raw.dryRun === true;
|
|
5360
5356
|
if (dryRun) {
|
|
5361
5357
|
console.log(`[dry-run] would uninstall '${resolvedName}' via method '${method}'`);
|
|
5362
|
-
console.log(` run
|
|
5358
|
+
console.log(` run without --dry-run to execute`);
|
|
5363
5359
|
process.exit(2);
|
|
5364
5360
|
}
|
|
5365
5361
|
if (!yes) {
|