harnessed 3.2.0 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -36,7 +36,15 @@ npm install -g harnessed && harnessed setup
36
36
 
37
37
  > Windows PowerShell 5.x 不支持 `&&` 链接,需改 `;` 或分两行 (`npm install -g harnessed; harnessed setup`)。bash / zsh / PowerShell 7+ / cmd.exe 都正常。
38
38
 
39
- 🤖 **或让 AI 帮你装** — 复制 [INSTALL-WITH-AI.md](./INSTALL-WITH-AI.md) 整段粘贴进 Claude Code (或任何 AI 助手),AI 自动处理 OS / 权限 / PATH / corepack 等 edge case。
39
+ 🤖 **或让 AI 帮你装** — 把下面这句话发给 Claude Code (或任何 AI 助手):
40
+
41
+ > 按 `https://github.com/easyinplay/harnessed/blob/main/INSTALL-WITH-AI.md` 的指导帮我装 harnessed
42
+
43
+ AI 会自动 fetch 文档 + 跑安装,处理 OS / 权限 / PATH / corepack 等 edge case,无需复制大段文字。
44
+
45
+ > [!TIP]
46
+ > 🚀 **很多人关心的 Agent Teams 和 Subagent 功能,在 harnessed 中会根据任务自动启用!**
47
+ > 无需手动配置 `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS`,`harnessed setup` v3.3.1+ 会自动写入 `~/.claude/settings.json`;Pattern A 全栈三路 / Pattern C 4-specialist 等 multi-agent workflow 即开即用。
40
48
 
41
49
  ---
42
50
 
@@ -344,6 +352,42 @@ planning-with-files /plan (cross-cutting tool) → write artifacts to .planning/
344
352
 
345
353
  ---
346
354
 
355
+ ## 🛠️ 维护命令 (Operational)
356
+
357
+ > 这些是 harnessed 自身维护命令(setup / 健康检查 / 备份回滚 / 状态恢复等),日常 feature 开发用上面的 slash command 即可,这块通常不需要。
358
+
359
+ ### CLI 命令
360
+
361
+ | 命令 | 说明 |
362
+ | ---- | ---- |
363
+ | `harnessed setup` | 一次性 setup,装 workflow skills 到 `~/.claude/skills/` + MCP 到 `~/.claude.json` |
364
+ | `harnessed resume` | session 中断后恢复至最近 checkpoint |
365
+ | `harnessed status` | 当前 phase + lock holder |
366
+ | `harnessed doctor` | 8-check 健康检查 (Node / MCP / jq / Win bash / 路由 / token budget 等) |
367
+ | `harnessed install <name>` | 装上游 manifest |
368
+ | `harnessed uninstall <name>` | 反向卸载 |
369
+ | `harnessed backup` | snapshot 备份管理 |
370
+ | `harnessed rollback <timestamp>` | 一行回滚 (EOL preserve + sha1 verify) |
371
+ | `harnessed gc` | 清理过期 backups |
372
+ | `harnessed audit-log` | 路由透明日志 query (支持 `--filter` jq 表达式) |
373
+
374
+ ### 参数 (Flags)
375
+
376
+ > 所有命令默认 **apply (immediate write)**,无需加 flag。高级用户可加 `--dry-run` 预览。
377
+
378
+ | Flag | 说明 |
379
+ | ---- | ---- |
380
+ | `--dry-run` | 预览不写盘 (高级用户 opt-in) |
381
+ | `--non-interactive` | CI / 脚本场景 |
382
+ | `--system` | L4 全局装允许 (否则降级 L1 npx ephemeral) |
383
+ | `--yes` | uninstall 跳过交互 confirm |
384
+ | `--full-diff` | 展开 > 200 行的 diff 折叠 |
385
+ | `--no-color` | 强制 nocolor (即使 TTY) |
386
+
387
+ > `--apply` flag 仍保留为向后兼容 alias (no-op, 旧脚本不破)。
388
+
389
+ ---
390
+
347
391
  ## ❓ FAQ
348
392
 
349
393
  <details>
@@ -423,41 +467,6 @@ harnessed setup --apply # 自动装齐 gstack + GSD + superpowers + planning-wi
423
467
 
424
468
  ---
425
469
 
426
- ## 🛠️ 维护命令 (Operational)
427
-
428
- > 这些是 harnessed 自身维护命令(setup / 健康检查 / 备份回滚 / 状态恢复等),日常 feature 开发用上面的 slash command 即可,这块通常不需要。
429
-
430
- ### CLI 命令
431
-
432
- | 命令 | 说明 |
433
- | ---- | ---- |
434
- | `harnessed setup` | 一次性 setup,装 workflow skills 到 `~/.claude/skills/` + MCP 到 `~/.claude.json` |
435
- | `harnessed resume` | session 中断后恢复至最近 checkpoint |
436
- | `harnessed status` | 当前 phase + lock holder |
437
- | `harnessed doctor` | 8-check 健康检查 (Node / MCP / jq / Win bash / 路由 / token budget 等) |
438
- | `harnessed install <name>` | 装上游 manifest |
439
- | `harnessed uninstall <name>` | 反向卸载 |
440
- | `harnessed backup` | snapshot 备份管理 |
441
- | `harnessed rollback <timestamp>` | 一行回滚 (EOL preserve + sha1 verify) |
442
- | `harnessed gc` | 清理过期 backups |
443
- | `harnessed audit-log` | 路由透明日志 query (支持 `--filter` jq 表达式) |
444
-
445
- ### 参数 (Flags)
446
-
447
- > 所有命令默认 **apply (immediate write)**,无需加 flag。高级用户可加 `--dry-run` 预览。
448
-
449
- | Flag | 说明 |
450
- | ---- | ---- |
451
- | `--dry-run` | 预览不写盘 (高级用户 opt-in) |
452
- | `--non-interactive` | CI / 脚本场景 |
453
- | `--system` | L4 全局装允许 (否则降级 L1 npx ephemeral) |
454
- | `--yes` | uninstall 跳过交互 confirm |
455
- | `--full-diff` | 展开 > 200 行的 diff 折叠 |
456
- | `--no-color` | 强制 nocolor (即使 TTY) |
457
-
458
- > `--apply` flag 仍保留为向后兼容 alias (no-op, 旧脚本不破)。
459
-
460
- ---
461
470
 
462
471
  ## License
463
472
 
package/dist/cli.mjs CHANGED
@@ -847,7 +847,7 @@ var init_resume = __esm({
847
847
 
848
848
  // package.json
849
849
  var package_default = {
850
- version: "3.2.0"};
850
+ version: "3.3.1"};
851
851
 
852
852
  // src/manifest/errors.ts
853
853
  function instancePathToKeyPath(instancePath) {
@@ -1633,7 +1633,7 @@ function renderHumanTable(records) {
1633
1633
  }
1634
1634
  }
1635
1635
  function pipeToJq(filterExpr, lines) {
1636
- return new Promise((resolve9, reject) => {
1636
+ return new Promise((resolve10, reject) => {
1637
1637
  const child = spawn("jq", [filterExpr], {
1638
1638
  stdio: ["pipe", "inherit", "inherit"],
1639
1639
  windowsHide: true
@@ -1642,12 +1642,12 @@ function pipeToJq(filterExpr, lines) {
1642
1642
  const e = err2;
1643
1643
  if (e.code === "ENOENT") {
1644
1644
  console.error("\u2717 jq not found in PATH \u2014 run: harnessed doctor");
1645
- resolve9(1);
1645
+ resolve10(1);
1646
1646
  } else {
1647
1647
  reject(err2);
1648
1648
  }
1649
1649
  });
1650
- child.on("close", (code) => resolve9(code ?? 0));
1650
+ child.on("close", (code) => resolve10(code ?? 0));
1651
1651
  child.stdin.write(lines.join("\n"));
1652
1652
  child.stdin.end();
1653
1653
  });
@@ -2999,26 +2999,11 @@ function loadPhases(yamlPath, vars) {
2999
2999
  return validated;
3000
3000
  }
3001
3001
 
3002
- // src/cli/lib/validateFlags.ts
3003
- function validateNonInteractiveFlags(raw, cmdName) {
3004
- if (raw.nonInteractive && !raw.apply && !raw.dryRun) {
3005
- console.error(
3006
- `error: --non-interactive requires --apply or --dry-run
3007
- fix: 'harnessed ${cmdName} --non-interactive --dry-run' or '--apply'`
3008
- );
3009
- process.exit(2);
3010
- }
3011
- }
3012
-
3013
3002
  // src/cli/execute-task.ts
3014
3003
  function registerExecuteTask(program2) {
3015
3004
  program2.command("execute-task").description(
3016
3005
  "Run execute-task workflow (4-phase chain \u2192 ralph-loop COMPLETE; immediate by default \u2014 use --dry-run for preview)"
3017
- ).requiredOption("--task <text>", "task description (required)").option("--workflow <name>", "workflow name", "execute-task").option(
3018
- "--apply",
3019
- "(deprecated; kept for backward compat \u2014 execute-task spawns immediately by default)"
3020
- ).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) => {
3021
- validateNonInteractiveFlags(raw, "execute-task --task <text>");
3006
+ ).requiredOption("--task <text>", "task description (required)").option("--workflow <name>", "workflow name", "execute-task").option("--dry-run", "preview only \u2014 do not spawn subagent (opt-in for advanced users)").option("--non-interactive", "CI / scripts").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) => {
3022
3007
  if (!raw.task) {
3023
3008
  console.error("error: --task <text> is required");
3024
3009
  process.exit(2);
@@ -3065,7 +3050,7 @@ function registerExecuteTask(program2) {
3065
3050
  packageRoot: process.cwd(),
3066
3051
  cmdType: "git-commit",
3067
3052
  hasUserApproval: true
3068
- // --apply explicit
3053
+ // apply-immediate default
3069
3054
  });
3070
3055
  } catch (err2) {
3071
3056
  console.warn(
@@ -3120,7 +3105,7 @@ async function dirSizeKb(dir) {
3120
3105
  function registerGc(program2) {
3121
3106
  program2.command("gc").description(
3122
3107
  "Garbage-collect old backup snapshots (immediate by default \u2014 use --dry-run for preview)"
3123
- ).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) => {
3108
+ ).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("--dry-run", "preview only \u2014 do not delete (opt-in for advanced users)").action(async (opts) => {
3124
3109
  const dryRun = opts.dryRun === true;
3125
3110
  const olderMs = parseDuration(opts.olderThan ?? "30d");
3126
3111
  if (olderMs == null) {
@@ -3379,10 +3364,10 @@ var installCcHookAdd = async (ctx) => {
3379
3364
  const e = pre.errors[0] ?? err(ctx, "/", "preflight failed", "preflight");
3380
3365
  return { ok: false, phase: "preflight", error: e };
3381
3366
  }
3382
- const settingsPath = join(homedir(), ".claude", "settings.json");
3367
+ const settingsPath2 = join(homedir(), ".claude", "settings.json");
3383
3368
  let existing;
3384
3369
  try {
3385
- existing = await readFile(settingsPath, "utf8");
3370
+ existing = await readFile(settingsPath2, "utf8");
3386
3371
  } catch {
3387
3372
  existing = null;
3388
3373
  }
@@ -3413,7 +3398,7 @@ var installCcHookAdd = async (ctx) => {
3413
3398
  const newText = `${JSON.stringify(settings, null, 2)}
3414
3399
  `;
3415
3400
  const plan = {
3416
- files: [{ target: settingsPath, scope: "HOME", oldText: existing ?? "", newText }]
3401
+ files: [{ target: settingsPath2, scope: "HOME", oldText: existing ?? "", newText }]
3417
3402
  };
3418
3403
  process.stdout.write(renderDiff(plan, ctx));
3419
3404
  const conf = await confirmAt("L3", { ...ctx});
@@ -3424,10 +3409,10 @@ var installCcHookAdd = async (ctx) => {
3424
3409
  if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
3425
3410
  const bk = await backup(plan, ctx);
3426
3411
  if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
3427
- await writeFile(settingsPath, newText);
3412
+ await writeFile(settingsPath2, newText);
3428
3413
  let verify;
3429
3414
  try {
3430
- verify = JSON.parse(await readFile(settingsPath, "utf8"));
3415
+ verify = JSON.parse(await readFile(settingsPath2, "utf8"));
3431
3416
  } catch (e) {
3432
3417
  return {
3433
3418
  ok: false,
@@ -3455,7 +3440,7 @@ var installCcHookAdd = async (ctx) => {
3455
3440
  };
3456
3441
  }
3457
3442
  await updateInstalled(ctx.cwd, ctx.manifest.metadata.name, "", "");
3458
- return { ok: true, backupId: bk.backupId, appliedFiles: [settingsPath] };
3443
+ return { ok: true, backupId: bk.backupId, appliedFiles: [settingsPath2] };
3459
3444
  };
3460
3445
  function getUserClaudeJsonPath() {
3461
3446
  return join(homedir(), ".claude.json");
@@ -3491,7 +3476,7 @@ async function isPluginRegistered(pluginName) {
3491
3476
  return Object.keys(plugins).some((k) => k.split("@")[0] === pluginName);
3492
3477
  }
3493
3478
  function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
3494
- return new Promise((resolve9) => {
3479
+ return new Promise((resolve10) => {
3495
3480
  const isWin = process.platform === "win32";
3496
3481
  const child = isWin ? spawn("cmd.exe", ["/c", "claude", ...claudeArgs], { cwd, windowsHide: true }) : spawn("claude", claudeArgs, { cwd, shell: false });
3497
3482
  let stderr = "";
@@ -3500,15 +3485,15 @@ function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
3500
3485
  });
3501
3486
  const timer = setTimeout(() => {
3502
3487
  child.kill("SIGKILL");
3503
- resolve9({ exitCode: -1, stderr: `${stderr}[timeout after ${timeoutMs}ms]` });
3488
+ resolve10({ exitCode: -1, stderr: `${stderr}[timeout after ${timeoutMs}ms]` });
3504
3489
  }, timeoutMs);
3505
3490
  child.on("error", (e) => {
3506
3491
  clearTimeout(timer);
3507
- resolve9({ exitCode: -1, stderr: `${stderr}${e.message}` });
3492
+ resolve10({ exitCode: -1, stderr: `${stderr}${e.message}` });
3508
3493
  });
3509
3494
  child.on("close", (code) => {
3510
3495
  clearTimeout(timer);
3511
- resolve9({ exitCode: code ?? -1, stderr });
3496
+ resolve10({ exitCode: code ?? -1, stderr });
3512
3497
  });
3513
3498
  });
3514
3499
  }
@@ -3687,10 +3672,10 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
3687
3672
  child.stderr?.setEncoding("utf8").on("data", (chunk) => {
3688
3673
  stderr += chunk;
3689
3674
  });
3690
- return await new Promise((resolve9) => {
3675
+ return await new Promise((resolve10) => {
3691
3676
  const timer = setTimeout(() => {
3692
3677
  child.kill("SIGKILL");
3693
- resolve9({
3678
+ resolve10({
3694
3679
  ok: false,
3695
3680
  phase: "spawn",
3696
3681
  error: {
@@ -3705,7 +3690,7 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
3705
3690
  }, effectiveTimeoutMs);
3706
3691
  child.on("error", (err2) => {
3707
3692
  clearTimeout(timer);
3708
- resolve9({
3693
+ resolve10({
3709
3694
  ok: false,
3710
3695
  phase: "spawn",
3711
3696
  error: {
@@ -3720,14 +3705,14 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
3720
3705
  });
3721
3706
  child.on("close", (code) => {
3722
3707
  clearTimeout(timer);
3723
- resolve9({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
3708
+ resolve10({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
3724
3709
  });
3725
3710
  });
3726
3711
  }
3727
3712
 
3728
3713
  // src/installers/gitCloneWithSetup.ts
3729
3714
  function gitRevParseHead(cwd, timeoutMs = 1e4) {
3730
- return new Promise((resolve9) => {
3715
+ return new Promise((resolve10) => {
3731
3716
  const isWin = process.platform === "win32";
3732
3717
  const child = isWin ? spawn("cmd.exe", ["/c", "git", "rev-parse", "HEAD"], { cwd, windowsHide: true }) : spawn("git", ["rev-parse", "HEAD"], { cwd, shell: false });
3733
3718
  let stdout2 = "";
@@ -3736,15 +3721,15 @@ function gitRevParseHead(cwd, timeoutMs = 1e4) {
3736
3721
  });
3737
3722
  const timer = setTimeout(() => {
3738
3723
  child.kill("SIGKILL");
3739
- resolve9({ sha: "", exit: -1 });
3724
+ resolve10({ sha: "", exit: -1 });
3740
3725
  }, timeoutMs);
3741
3726
  child.on("error", () => {
3742
3727
  clearTimeout(timer);
3743
- resolve9({ sha: "", exit: -1 });
3728
+ resolve10({ sha: "", exit: -1 });
3744
3729
  });
3745
3730
  child.on("close", (code) => {
3746
3731
  clearTimeout(timer);
3747
- resolve9({ sha: stdout2.trim(), exit: code ?? -1 });
3732
+ resolve10({ sha: stdout2.trim(), exit: code ?? -1 });
3748
3733
  });
3749
3734
  });
3750
3735
  }
@@ -4475,11 +4460,10 @@ function formatError(e) {
4475
4460
  return `${head}${where}${tip}`;
4476
4461
  }
4477
4462
  function registerInstall(program2) {
4478
- 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(
4463
+ program2.command("install <name>").description("Install an upstream (immediate by default \u2014 use --dry-run for preview)").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)").option("--full-diff", "expand diffs longer than 200 lines").option("--no-color", "disable ANSI colors (auto-detected when piped)").option(
4479
4464
  "--known-good",
4480
4465
  "use known-good version lock from versions/<harnessed-ver>-known-good.yaml"
4481
4466
  ).action(async (name, raw) => {
4482
- validateNonInteractiveFlags(raw, "install <name>");
4483
4467
  const { resolveAlias: resolveAlias2 } = await Promise.resolve().then(() => (init_aliases(), aliases_exports));
4484
4468
  const resolvedName = resolveAlias2(name) ?? name;
4485
4469
  checkPathSafe(resolvedName);
@@ -4558,11 +4542,7 @@ async function listBaseManifests(cwd) {
4558
4542
  function registerInstallBase(program2) {
4559
4543
  program2.command("install-base").description(
4560
4544
  "Install the phase 1.3 base profile (immediate by default \u2014 use --dry-run for preview)"
4561
- ).option(
4562
- "--apply",
4563
- "(deprecated; kept for backward compat \u2014 install-base is immediate by default)"
4564
- ).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) => {
4565
- validateNonInteractiveFlags(raw, "install-base");
4545
+ ).option("--dry-run", "preview only \u2014 do not write to disk (opt-in for advanced users)").option("--non-interactive", "skip all prompts (CI / scripts)").action(async (raw) => {
4566
4546
  const dryRun = raw.dryRun === true;
4567
4547
  const opts = {
4568
4548
  apply: !dryRun,
@@ -4628,11 +4608,7 @@ function basename(upstream) {
4628
4608
  function registerManifestAdd(program2) {
4629
4609
  program2.command("manifest-add <upstream>").description(
4630
4610
  "Add a new upstream adapter (EE-5 5-question merge gate; immediate by default \u2014 use --dry-run for preview)"
4631
- ).option("--category <cat>", "manifest category (skill-packs | tools)", "skill-packs").option("--name <name>", "short adapter name (defaults to <upstream> basename)").option(
4632
- "--apply",
4633
- "(deprecated; kept for backward compat \u2014 manifest-add persists immediately by default)"
4634
- ).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) => {
4635
- validateNonInteractiveFlags(raw, "manifest-add <upstream>");
4611
+ ).option("--category <cat>", "manifest category (skill-packs | tools)", "skill-packs").option("--name <name>", "short adapter name (defaults to <upstream> basename)").option("--dry-run", "preview only \u2014 do not write JSON (opt-in for advanced users)").option("--non-interactive", "CI/scripts \u2014 WARN-only dry-run").action(async (upstream, raw) => {
4636
4612
  const name = raw.name ?? basename(upstream);
4637
4613
  const category = raw.category ?? "skill-packs";
4638
4614
  const outPath = `manifests/${category}/${name}.ee5-answers.json`;
@@ -4677,11 +4653,7 @@ function registerManifestAdd(program2) {
4677
4653
  function registerResearch(program2) {
4678
4654
  program2.command("research").description(
4679
4655
  "Run research workflow (search category sub-routing \u2192 spawn \u2192 verbatim COMPLETE; immediate by default \u2014 use --dry-run for preview)"
4680
- ).requiredOption("--query <text>", "research prompt (required)").option(
4681
- "--apply",
4682
- "(deprecated; kept for backward compat \u2014 research spawns immediately by default)"
4683
- ).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) => {
4684
- validateNonInteractiveFlags(raw, "research --query <text>");
4656
+ ).requiredOption("--query <text>", "research prompt (required)").option("--dry-run", "preview only \u2014 do not spawn subagent (opt-in for advanced users)").option("--non-interactive", "skip all prompts (CI / scripts)").option("--model <model>", "subagent model: 'haiku' | 'sonnet' | 'opus'").action(async (raw) => {
4685
4657
  if (!raw.query) {
4686
4658
  console.error("error: --query <text> is required");
4687
4659
  process.exit(2);
@@ -4720,7 +4692,7 @@ function registerResearch(program2) {
4720
4692
  if ("ok" in result && result.ok === false) {
4721
4693
  console.error(`error: ${result.phase} \u2014 ${result.error.message}`);
4722
4694
  if (result.phase === "install") {
4723
- console.error(` fix: 'harnessed install <skill> --apply' (see error above)`);
4695
+ console.error(` fix: 'harnessed install <skill>' (see error above)`);
4724
4696
  }
4725
4697
  process.exit(1);
4726
4698
  }
@@ -4810,6 +4782,80 @@ function registerRollback(program2) {
4810
4782
  console.log(`restored ${meta.files.length} file(s) from ${timestamp}`);
4811
4783
  });
4812
4784
  }
4785
+
4786
+ // src/cli/lib/enableAgentTeamsInSettings.ts
4787
+ init_harnessedRoot();
4788
+ function settingsPath() {
4789
+ return resolve(homedir(), ".claude", "settings.json");
4790
+ }
4791
+ async function enableAgentTeamsInSettings() {
4792
+ const path = settingsPath();
4793
+ let raw;
4794
+ try {
4795
+ raw = await readFile(path, "utf8");
4796
+ } catch (err2) {
4797
+ const code = err2.code;
4798
+ if (code !== "ENOENT") {
4799
+ return { status: "warn", message: `read ${path} failed: ${err2.message}` };
4800
+ }
4801
+ return createFreshSettings(path);
4802
+ }
4803
+ let data;
4804
+ try {
4805
+ const parsed = JSON.parse(raw);
4806
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
4807
+ return { status: "warn", message: `${path} is not a JSON object` };
4808
+ }
4809
+ data = parsed;
4810
+ } catch (err2) {
4811
+ return { status: "warn", message: `${path} malformed JSON: ${err2.message}` };
4812
+ }
4813
+ const env = data.env ?? {};
4814
+ if (env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1") {
4815
+ return { status: "already-enabled", path };
4816
+ }
4817
+ const backupPath = await backupOriginal(raw);
4818
+ if (backupPath.status === "warn") return backupPath;
4819
+ data.env = { ...env, CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" };
4820
+ const writeErr = await atomicWrite(path, `${JSON.stringify(data, null, 2)}
4821
+ `);
4822
+ if (writeErr) return { status: "warn", message: writeErr };
4823
+ return { status: "enabled", path, backupPath: backupPath.path };
4824
+ }
4825
+ async function createFreshSettings(path) {
4826
+ const data = { env: { CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" } };
4827
+ try {
4828
+ await mkdir(join(homedir(), ".claude"), { recursive: true });
4829
+ } catch (err2) {
4830
+ return { status: "warn", message: `mkdir ~/.claude failed: ${err2.message}` };
4831
+ }
4832
+ const writeErr = await atomicWrite(path, `${JSON.stringify(data, null, 2)}
4833
+ `);
4834
+ if (writeErr) return { status: "warn", message: writeErr };
4835
+ return { status: "created", path };
4836
+ }
4837
+ async function backupOriginal(raw) {
4838
+ const backupRoot = harnessedSubdir("backups");
4839
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-");
4840
+ const backupPath = join(backupRoot, `settings.json.${ts}.bak`);
4841
+ try {
4842
+ await mkdir(backupRoot, { recursive: true });
4843
+ await writeFile(backupPath, raw, "utf8");
4844
+ return { status: "ok", path: backupPath };
4845
+ } catch (err2) {
4846
+ return { status: "warn", message: `backup ${backupPath} failed: ${err2.message}` };
4847
+ }
4848
+ }
4849
+ async function atomicWrite(path, content) {
4850
+ const tmpPath = `${path}.tmp-${process.pid}-${Date.now()}`;
4851
+ try {
4852
+ await writeFile(tmpPath, content, "utf8");
4853
+ await rename(tmpPath, path);
4854
+ return void 0;
4855
+ } catch (err2) {
4856
+ return `write ${path} failed: ${err2.message}`;
4857
+ }
4858
+ }
4813
4859
  init_checkAgentTeams();
4814
4860
  var FLAT_LEGACY_DEPRECATED = /* @__PURE__ */ new Set(["plan-feature", "execute-task", "verify-work"]);
4815
4861
  var FLAT_LEGACY_KEEP = /* @__PURE__ */ new Set(["research", "retro", "auto"]);
@@ -5032,6 +5078,18 @@ function registerSetup(program2) {
5032
5078
  `
5033
5079
  Step A complete: ${skillsInstalled} workflow skill(s) installed to ${skillsBase}`
5034
5080
  );
5081
+ const cResult = await enableAgentTeamsInSettings();
5082
+ if (cResult.status === "created") {
5083
+ console.log(` [C] created ${cResult.path} + enabled Agent Teams`);
5084
+ } else if (cResult.status === "already-enabled") {
5085
+ console.log(` [C] Agent Teams already enabled (${cResult.path})`);
5086
+ } else if (cResult.status === "enabled") {
5087
+ console.log(
5088
+ ` [C] enabled Agent Teams in ${cResult.path} (backup saved \u2192 ${cResult.backupPath})`
5089
+ );
5090
+ } else {
5091
+ console.warn(` [C] Agent Teams enable skipped: ${cResult.message}`);
5092
+ }
5035
5093
  const manifestPaths = await listBaseManifests2(pkgRoot);
5036
5094
  const b = await runStepBInstall(manifestPaths);
5037
5095
  const stepBMs = (b.elapsedMs / 1e3).toFixed(1);
@@ -5120,10 +5178,10 @@ var uninstallCcHookAdd = async (ctx) => {
5120
5178
  }
5121
5179
  const abort = dryRunGate(ctx);
5122
5180
  if (abort) return abort;
5123
- const settingsPath = join(homedir(), ".claude", "settings.json");
5181
+ const settingsPath2 = join(homedir(), ".claude", "settings.json");
5124
5182
  let existing;
5125
5183
  try {
5126
- existing = await readFile(settingsPath, "utf8");
5184
+ existing = await readFile(settingsPath2, "utf8");
5127
5185
  } catch {
5128
5186
  return { ok: true, removedPaths: [] };
5129
5187
  }
@@ -5152,8 +5210,8 @@ var uninstallCcHookAdd = async (ctx) => {
5152
5210
  if (settings.hooks?.[ev]?.length === before || before === settings.hooks?.[ev]?.length) ;
5153
5211
  const newText = `${JSON.stringify(settings, null, 2)}
5154
5212
  `;
5155
- await writeFile(settingsPath, newText);
5156
- return { ok: true, removedPaths: [settingsPath] };
5213
+ await writeFile(settingsPath2, newText);
5214
+ return { ok: true, removedPaths: [settingsPath2] };
5157
5215
  };
5158
5216
 
5159
5217
  // src/uninstallers/ccPluginMarketplace.ts
@@ -5289,7 +5347,7 @@ var uninstallNpmCli = async (ctx) => {
5289
5347
  const m = install.cmd.match(/npm\s+(?:install|i)\s+(?:-g\s+)?(\S+)/);
5290
5348
  const pkg = m?.[1] ?? ctx.manifest.metadata.upstream.source;
5291
5349
  const isWin = process.platform === "win32";
5292
- const result = await new Promise((resolve9) => {
5350
+ const result = await new Promise((resolve10) => {
5293
5351
  const child = isWin ? spawn("cmd.exe", ["/c", "npm", "uninstall", "-g", pkg], { windowsHide: true }) : spawn("npm", ["uninstall", "-g", pkg], { shell: false });
5294
5352
  let stderr = "";
5295
5353
  child.stderr?.setEncoding("utf8").on("data", (c) => {
@@ -5297,15 +5355,15 @@ var uninstallNpmCli = async (ctx) => {
5297
5355
  });
5298
5356
  const timer = setTimeout(() => {
5299
5357
  child.kill("SIGKILL");
5300
- resolve9({ exitCode: -1, stderr: `${stderr}[timeout]` });
5358
+ resolve10({ exitCode: -1, stderr: `${stderr}[timeout]` });
5301
5359
  }, 3e4);
5302
5360
  child.on("error", (e) => {
5303
5361
  clearTimeout(timer);
5304
- resolve9({ exitCode: -1, stderr: e.message });
5362
+ resolve10({ exitCode: -1, stderr: e.message });
5305
5363
  });
5306
5364
  child.on("close", (code) => {
5307
5365
  clearTimeout(timer);
5308
- resolve9({ exitCode: code ?? -1, stderr });
5366
+ resolve10({ exitCode: code ?? -1, stderr });
5309
5367
  });
5310
5368
  });
5311
5369
  if (result.exitCode !== 0) {
@@ -5355,7 +5413,7 @@ async function runUninstall(manifest, opts) {
5355
5413
 
5356
5414
  // src/cli/uninstall.ts
5357
5415
  function registerUninstall(program2) {
5358
- 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) => {
5416
+ program2.command("uninstall <name>").description("Uninstall an upstream (immediate by default \u2014 use --dry-run for preview)").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) => {
5359
5417
  const yes = raw.yes === true || raw.nonInteractive === true;
5360
5418
  if (yes && raw.dryRun) {
5361
5419
  console.error(