harnessed 3.0.1 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { execSync, spawnSync, spawn } from 'child_process';
3
- import { writeFileSync, existsSync, readFileSync, mkdirSync, appendFileSync, readdirSync } from 'fs';
4
- import { resolve, join, dirname, relative } from 'path';
3
+ import { existsSync, mkdirSync, renameSync, writeFileSync, readFileSync, appendFileSync, readdirSync } from 'fs';
4
+ import { join, resolve, dirname, relative } from 'path';
5
+ import { homedir } from 'os';
5
6
  import { Type } from '@sinclair/typebox';
6
7
  import { Value } from '@sinclair/typebox/value';
7
8
  import { LineCounter, parseDocument, parse, isSeq, isScalar } from 'yaml';
8
- import { homedir } from 'os';
9
9
  import { readFile, readdir, unlink, writeFile, stat, rm, cp, access, mkdir, rename } from 'fs/promises';
10
10
  import lockfile from 'proper-lockfile';
11
11
  import { Command } from 'commander';
@@ -78,6 +78,46 @@ var init_origin_check = __esm({
78
78
  "src/cli/lib/origin-check.ts"() {
79
79
  }
80
80
  });
81
+ function getHarnessedRoot() {
82
+ const override = process.env.HARNESSED_ROOT_OVERRIDE;
83
+ if (override !== void 0 && override !== "") return override;
84
+ return join(homedir(), ".claude", "harnessed");
85
+ }
86
+ function harnessedSubdir(name) {
87
+ return join(getHarnessedRoot(), name);
88
+ }
89
+ function harnessedFile(name) {
90
+ return join(getHarnessedRoot(), name);
91
+ }
92
+ function migrateLegacyHarnessedRoot() {
93
+ const legacyRoot = join(homedir(), ".harnessed");
94
+ const newRoot = join(homedir(), ".claude", "harnessed");
95
+ const claudeParent = join(homedir(), ".claude");
96
+ if (!existsSync(legacyRoot)) return;
97
+ if (!existsSync(newRoot)) {
98
+ mkdirSync(claudeParent, { recursive: true });
99
+ renameSync(legacyRoot, newRoot);
100
+ console.error(
101
+ `[harnessed] migrated legacy state directory ${legacyRoot} \u2192 ${newRoot} (v3.0.3 path change)`
102
+ );
103
+ return;
104
+ }
105
+ const safetyBak = join(homedir(), ".harnessed.legacy-bak");
106
+ if (existsSync(safetyBak)) {
107
+ console.error(
108
+ `[harnessed] WARN: ${legacyRoot} reappeared after a prior migration (existing bak at ${safetyBak}); leaving in place \u2014 inspect manually if needed`
109
+ );
110
+ return;
111
+ }
112
+ renameSync(legacyRoot, safetyBak);
113
+ console.error(
114
+ `[harnessed] both ${legacyRoot} and ${newRoot} existed \u2014 legacy directory preserved at ${safetyBak} (review manually if you need data from it; v3.0.3 path change)`
115
+ );
116
+ }
117
+ var init_harnessedRoot = __esm({
118
+ "src/installers/lib/harnessedRoot.ts"() {
119
+ }
120
+ });
81
121
 
82
122
  // src/cli/lib/probe-gstack.ts
83
123
  var probe_gstack_exports = {};
@@ -408,7 +448,7 @@ function writeCheckpoint(c, customPath) {
408
448
  throw new CheckpointWriteError(`Schema validation failed: ${errs}`);
409
449
  }
410
450
  const enforced = enforceBudget(c);
411
- const path = `.harnessed/checkpoints/${enforced.phase}.json`;
451
+ const path = join(harnessedSubdir("checkpoints"), `${enforced.phase}.json`);
412
452
  mkdirSync(dirname(path), { recursive: true });
413
453
  writeFileSync(path, JSON.stringify(enforced, null, 2), "utf8");
414
454
  return path;
@@ -416,6 +456,7 @@ function writeCheckpoint(c, customPath) {
416
456
  var BUDGET_TOKEN, CheckpointTooLargeError, CheckpointWriteError;
417
457
  var init_template = __esm({
418
458
  "src/checkpoint/template.ts"() {
459
+ init_harnessedRoot();
419
460
  init_schema();
420
461
  BUDGET_TOKEN = 1e3;
421
462
  CheckpointTooLargeError = class extends Error {
@@ -579,10 +620,25 @@ var init_check_planning_with_files = __esm({
579
620
  REMEDIATION = "install via Claude Code plugin marketplace: `claude plugin install planning-with-files` (requires >=2.2.0 per R20.15 + D-15)";
580
621
  }
581
622
  });
623
+ function statePath() {
624
+ return harnessedFile("current-workflow.json");
625
+ }
626
+ function lockTarget() {
627
+ return getHarnessedRoot();
628
+ }
629
+ function lockOpts() {
630
+ return {
631
+ stale: 1e4,
632
+ retries: { retries: 3, factor: 2, minTimeout: 100 },
633
+ lockfilePath: harnessedFile(".lock")
634
+ };
635
+ }
582
636
  async function withLock(fn) {
637
+ const target = lockTarget();
638
+ await mkdir(target, { recursive: true });
583
639
  let release;
584
640
  try {
585
- release = await lockfile.lock(LOCK_TARGET, LOCK_OPTS);
641
+ release = await lockfile.lock(target, lockOpts());
586
642
  } catch (e) {
587
643
  if (e.code === "ELOCKED") throw new LockHeldError();
588
644
  throw e;
@@ -596,7 +652,7 @@ async function withLock(fn) {
596
652
  async function readCurrentWorkflow() {
597
653
  let raw;
598
654
  try {
599
- raw = await readFile(STATE_PATH, "utf8");
655
+ raw = await readFile(statePath(), "utf8");
600
656
  } catch {
601
657
  return null;
602
658
  }
@@ -617,9 +673,10 @@ async function writeCurrentWorkflow(s) {
617
673
  const errs = [...Value.Errors(CurrentWorkflowV1, s)].map((e) => e.message).join("; ");
618
674
  throw new WorkflowStateError(`current-workflow schema validation failed: ${errs}`);
619
675
  }
620
- await mkdir(dirname(STATE_PATH), { recursive: true });
676
+ const path = statePath();
677
+ await mkdir(dirname(path), { recursive: true });
621
678
  await withLock(async () => {
622
- await writeFile(STATE_PATH, JSON.stringify(s, null, 2), "utf8");
679
+ await writeFile(path, JSON.stringify(s, null, 2), "utf8");
623
680
  });
624
681
  }
625
682
  async function activate(phase, checkpointPath = null) {
@@ -636,18 +693,12 @@ async function complete() {
636
693
  if (!s) return;
637
694
  await writeCurrentWorkflow({ ...s, status: "complete", completed_at: (/* @__PURE__ */ new Date()).toISOString() });
638
695
  }
639
- var STATE_PATH, LOCK_TARGET, LOCK_OPTS, WorkflowStateError, LockHeldError;
696
+ var WorkflowStateError, LockHeldError;
640
697
  var init_state = __esm({
641
698
  "src/checkpoint/state.ts"() {
699
+ init_harnessedRoot();
642
700
  init_schemaVersion();
643
701
  init_schema();
644
- STATE_PATH = ".harnessed/current-workflow.json";
645
- LOCK_TARGET = ".harnessed";
646
- LOCK_OPTS = {
647
- stale: 1e4,
648
- retries: { retries: 3, factor: 2, minTimeout: 100 },
649
- lockfilePath: ".harnessed/.lock"
650
- };
651
702
  WorkflowStateError = class extends Error {
652
703
  constructor(message) {
653
704
  super(message);
@@ -657,7 +708,7 @@ var init_state = __esm({
657
708
  LockHeldError = class _LockHeldError extends Error {
658
709
  constructor() {
659
710
  super(
660
- "another harnessed process holds the lock at .harnessed/.lock \u2014 wait or kill stale process (try: harnessed status)"
711
+ `another harnessed process holds the lock at ${harnessedFile(".lock")} \u2014 wait or kill stale process (try: harnessed status)`
661
712
  );
662
713
  this.name = "LockHeldError";
663
714
  Object.setPrototypeOf(this, _LockHeldError.prototype);
@@ -740,7 +791,10 @@ __export(resume_exports, {
740
791
  async function runResume() {
741
792
  const current = await readCurrentWorkflow();
742
793
  if (!current) {
743
- return { status: "no-paused-phase", error: "no .harnessed/current-workflow.json found" };
794
+ return {
795
+ status: "no-paused-phase",
796
+ error: "no current-workflow.json found under <harnessed-root>"
797
+ };
744
798
  }
745
799
  if (current.status !== "paused") {
746
800
  return {
@@ -793,7 +847,7 @@ var init_resume = __esm({
793
847
 
794
848
  // package.json
795
849
  var package_default = {
796
- version: "3.0.1"};
850
+ version: "3.0.3"};
797
851
 
798
852
  // src/manifest/errors.ts
799
853
  function instancePathToKeyPath(instancePath) {
@@ -1546,7 +1600,12 @@ audited ${yamls.length} manifest${yamls.length === 1 ? "" : "s"} \u2014 ${findin
1546
1600
  process.exit(errorCount > 0 ? 1 : 0);
1547
1601
  });
1548
1602
  }
1549
- var AUDIT_PATH = ".harnessed/audit.log";
1603
+
1604
+ // src/cli/audit-log.ts
1605
+ init_harnessedRoot();
1606
+ function auditPath() {
1607
+ return harnessedFile("audit.log");
1608
+ }
1550
1609
  var REDACT_PATTERNS = [
1551
1610
  [/api[_-]?key\s*[:=]\s*\S+/gi, "api_key=[REDACTED]"],
1552
1611
  [/\btoken\s*[:=]\s*\S+/gi, "token=[REDACTED]"],
@@ -1594,7 +1653,9 @@ function pipeToJq(filterExpr, lines) {
1594
1653
  });
1595
1654
  }
1596
1655
  function registerAuditLog(program2) {
1597
- program2.command("audit-log").description("Query routing audit log (.harnessed/audit.log) with optional jq filter (R10.1)").option("--filter <expr>", `jq filter expression (e.g. '.category=="engineering"')`).option("--tail <n>", "show N most recent records (default 50)", "50").option("--head <n>", "show N oldest records (--head takes priority over --tail)").option("--reverse", "flip output order").option("--json", "output full 12-field JSON instead of human table").action(
1656
+ program2.command("audit-log").description(
1657
+ "Query routing audit log (<harnessed-root>/audit.log) with optional jq filter (R10.1)"
1658
+ ).option("--filter <expr>", `jq filter expression (e.g. '.category=="engineering"')`).option("--tail <n>", "show N most recent records (default 50)", "50").option("--head <n>", "show N oldest records (--head takes priority over --tail)").option("--reverse", "flip output order").option("--json", "output full 12-field JSON instead of human table").action(
1598
1659
  async (opts) => {
1599
1660
  const tailN = opts.tail !== void 0 ? Number(opts.tail) : 50;
1600
1661
  if (Number.isNaN(tailN) || tailN < 1) {
@@ -1606,11 +1667,12 @@ function registerAuditLog(program2) {
1606
1667
  console.error("\u2717 --head must be a positive integer");
1607
1668
  process.exit(2);
1608
1669
  }
1609
- if (!existsSync(AUDIT_PATH)) {
1610
- console.log("no audit records found (.harnessed/audit.log does not exist)");
1670
+ const path = auditPath();
1671
+ if (!existsSync(path)) {
1672
+ console.log(`no audit records found (${path} does not exist)`);
1611
1673
  process.exit(0);
1612
1674
  }
1613
- const raw = readFileSync(AUDIT_PATH, "utf8");
1675
+ const raw = readFileSync(path, "utf8");
1614
1676
  const lines = raw.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
1615
1677
  if (lines.length === 0) {
1616
1678
  console.log("no audit records found (audit.log is empty)");
@@ -1646,8 +1708,11 @@ function registerAuditLog(program2) {
1646
1708
  }
1647
1709
  );
1648
1710
  }
1711
+
1712
+ // src/installers/lib/backup.ts
1713
+ init_harnessedRoot();
1649
1714
  function getBackupRoot() {
1650
- return join(homedir(), ".harnessed", "backups");
1715
+ return harnessedSubdir("backups");
1651
1716
  }
1652
1717
  var HOME_DIR = process.env.HOME ?? process.env.USERPROFILE ?? "";
1653
1718
  function mirrorPath(target, scope, backupDir) {
@@ -2317,7 +2382,12 @@ function matchesWhen(when, task) {
2317
2382
  }
2318
2383
  return true;
2319
2384
  }
2320
- var AUDIT_PATH2 = ".harnessed/audit.log";
2385
+
2386
+ // src/audit/log.ts
2387
+ init_harnessedRoot();
2388
+ function auditPath2() {
2389
+ return harnessedFile("audit.log");
2390
+ }
2321
2391
  Type.Object(
2322
2392
  {
2323
2393
  ts: Type.String(),
@@ -2352,8 +2422,9 @@ function buildAuditRecord(task, decision, matched, ctx) {
2352
2422
  };
2353
2423
  }
2354
2424
  function emitAuditRecord(record) {
2355
- mkdirSync(dirname(AUDIT_PATH2), { recursive: true });
2356
- appendFileSync(AUDIT_PATH2, `${JSON.stringify(record)}
2425
+ const path = auditPath2();
2426
+ mkdirSync(dirname(path), { recursive: true });
2427
+ appendFileSync(path, `${JSON.stringify(record)}
2357
2428
  `);
2358
2429
  }
2359
2430
 
@@ -2371,18 +2442,19 @@ function emitAudit(task, decision, matched, outcome, sessionId) {
2371
2442
  }
2372
2443
 
2373
2444
  // src/checkpoint/engineHook.ts
2445
+ init_harnessedRoot();
2374
2446
  init_schemaVersion();
2375
2447
  init_state();
2376
2448
  init_template();
2377
2449
  async function activatePhase(phaseId) {
2378
- const checkpointPath = `.harnessed/checkpoints/${phaseId}.json`;
2450
+ const checkpointPath = join(harnessedSubdir("checkpoints"), `${phaseId}.json`);
2379
2451
  await activate(phaseId, checkpointPath);
2380
2452
  return { checkpointPath };
2381
2453
  }
2382
2454
  async function completePhase(ctx) {
2383
2455
  if (ctx.phaseId === "unknown") {
2384
2456
  console.error(
2385
- '[harnessed] WARN engineHook: phaseId="unknown" \u2014 checkpoint paths fall back to .harnessed/checkpoints/unknown.json (Karpathy fail-loud non-blocking; W-04 mitigation)'
2457
+ `[harnessed] WARN engineHook: phaseId="unknown" \u2014 checkpoint paths fall back to ${join(harnessedSubdir("checkpoints"), "unknown.json")} (Karpathy fail-loud non-blocking; W-04 mitigation)`
2386
2458
  );
2387
2459
  }
2388
2460
  writeCheckpoint({
@@ -2395,7 +2467,7 @@ async function completePhase(ctx) {
2395
2467
  ...ctx.sessionId ? { session_id: ctx.sessionId } : {},
2396
2468
  cwd: process.cwd(),
2397
2469
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2398
- archive_path: `.harnessed/archive/phase-${ctx.phaseId}/`
2470
+ archive_path: `${join(harnessedSubdir("archive"), `phase-${ctx.phaseId}`)}/`
2399
2471
  });
2400
2472
  await complete();
2401
2473
  }
@@ -2479,11 +2551,11 @@ var VerbatimCompleteFailError = class extends Error {
2479
2551
  }
2480
2552
  lastMessage;
2481
2553
  };
2482
- async function ralphLoopWrap(spawn9, maxIter) {
2554
+ async function ralphLoopWrap(spawn6, maxIter) {
2483
2555
  let last = "";
2484
2556
  let sessionId;
2485
2557
  for (let i = 0; i < maxIter; i++) {
2486
- last = await spawn9(sessionId, (id) => {
2558
+ last = await spawn6(sessionId, (id) => {
2487
2559
  sessionId = id;
2488
2560
  });
2489
2561
  if (isComplete(last)) return last;
@@ -3239,12 +3311,15 @@ function preflight(ctx) {
3239
3311
  }
3240
3312
  return { ok: errors.length === 0, errors };
3241
3313
  }
3314
+
3315
+ // src/installers/lib/state.ts
3316
+ init_harnessedRoot();
3242
3317
  var DEFAULT_STATE = { version: "1", installed: {} };
3243
- function statePath(cwd) {
3244
- return join(cwd, ".harnessed", "state.json");
3318
+ function statePath2(_cwd) {
3319
+ return harnessedFile("state.json");
3245
3320
  }
3246
3321
  async function readState(cwd) {
3247
- const path = statePath(cwd);
3322
+ const path = statePath2();
3248
3323
  let raw;
3249
3324
  try {
3250
3325
  raw = await readFile(path, "utf8");
@@ -3265,7 +3340,7 @@ async function readState(cwd) {
3265
3340
  }
3266
3341
  }
3267
3342
  async function writeState(cwd, state) {
3268
- const path = statePath(cwd);
3343
+ const path = statePath2();
3269
3344
  const tmp = `${path}.tmp`;
3270
3345
  await mkdir(dirname(path), { recursive: true });
3271
3346
  await writeFile(tmp, `${JSON.stringify(state, null, 2)}
@@ -3273,7 +3348,7 @@ async function writeState(cwd, state) {
3273
3348
  await rename(tmp, path);
3274
3349
  }
3275
3350
  async function updateInstalled(cwd, name, version, manifestSha1) {
3276
- const state = await readState(cwd);
3351
+ const state = await readState();
3277
3352
  state.installed[name] = {
3278
3353
  version,
3279
3354
  installedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -3382,6 +3457,39 @@ var installCcHookAdd = async (ctx) => {
3382
3457
  await updateInstalled(ctx.cwd, ctx.manifest.metadata.name, "", "");
3383
3458
  return { ok: true, backupId: bk.backupId, appliedFiles: [settingsPath] };
3384
3459
  };
3460
+ function getUserClaudeJsonPath() {
3461
+ return join(homedir(), ".claude.json");
3462
+ }
3463
+ async function readUserClaudeJson() {
3464
+ const path = getUserClaudeJsonPath();
3465
+ let raw;
3466
+ try {
3467
+ raw = await readFile(path, "utf8");
3468
+ } catch (err2) {
3469
+ if (err2.code === "ENOENT") return {};
3470
+ throw err2;
3471
+ }
3472
+ try {
3473
+ const parsed = JSON.parse(raw);
3474
+ if (parsed === null || typeof parsed !== "object") return {};
3475
+ return parsed;
3476
+ } catch {
3477
+ return {};
3478
+ }
3479
+ }
3480
+ async function isMcpServerRegistered(name) {
3481
+ const config = await readUserClaudeJson();
3482
+ const servers = config.mcpServers;
3483
+ if (!servers || typeof servers !== "object") return false;
3484
+ return Object.hasOwn(servers, name);
3485
+ }
3486
+ async function isPluginRegistered(pluginName) {
3487
+ const config = await readUserClaudeJson();
3488
+ const plugins = config.enabledPlugins;
3489
+ if (!plugins || typeof plugins !== "object") return false;
3490
+ if (Object.hasOwn(plugins, pluginName)) return true;
3491
+ return Object.keys(plugins).some((k) => k.split("@")[0] === pluginName);
3492
+ }
3385
3493
  function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
3386
3494
  return new Promise((resolve9) => {
3387
3495
  const isWin = process.platform === "win32";
@@ -3404,6 +3512,9 @@ function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
3404
3512
  });
3405
3513
  });
3406
3514
  }
3515
+ function getMcpSpawnCwd() {
3516
+ return homedir();
3517
+ }
3407
3518
 
3408
3519
  // src/installers/ccPluginMarketplace.ts
3409
3520
  function parseCmd(cmd) {
@@ -3455,8 +3566,8 @@ var installCcPluginMarketplace = async (ctx) => {
3455
3566
  }
3456
3567
  };
3457
3568
  }
3458
- const pluginName = parsed.pluginAtMkt.split("@")[0];
3459
- const installArgs = ["plugin", "install", parsed.pluginAtMkt, "--scope", "project"];
3569
+ const pluginName = parsed.pluginAtMkt.split("@")[0] ?? parsed.pluginAtMkt;
3570
+ const installArgs = ["plugin", "install", parsed.pluginAtMkt, "--scope", "user"];
3460
3571
  const allArgs = [];
3461
3572
  if (parsed.marketplaceRef !== null) {
3462
3573
  allArgs.push(["plugin", "marketplace", "add", parsed.marketplaceRef]);
@@ -3464,30 +3575,30 @@ var installCcPluginMarketplace = async (ctx) => {
3464
3575
  allArgs.push(installArgs);
3465
3576
  for (const argSet of allArgs) {
3466
3577
  for (const a of argSet) {
3467
- const violation2 = checkCmdString(a);
3468
- if (violation2) {
3578
+ const violation = checkCmdString(a);
3579
+ if (violation) {
3469
3580
  return {
3470
3581
  ok: false,
3471
3582
  phase: "preflight",
3472
3583
  error: err(
3473
3584
  ctx,
3474
3585
  "/spec/install/cmd",
3475
- `shell escape detected in constructed cc-plugin arg '${a.slice(0, 60)}': ${violation2.label} (${violation2.hint})`,
3586
+ `shell escape detected in constructed cc-plugin arg '${a.slice(0, 60)}': ${violation.label} (${violation.hint})`,
3476
3587
  "security-gate-bypass"
3477
3588
  )
3478
3589
  };
3479
3590
  }
3480
3591
  }
3481
3592
  }
3482
- const settingsFile = `${ctx.cwd}/.claude/settings.json`;
3593
+ const settingsFile = `${getMcpSpawnCwd()}/.claude.json`;
3483
3594
  const newEntry = JSON.stringify({ enabledPlugins: { [parsed.pluginAtMkt]: true } }, null, 2);
3484
3595
  const plan = {
3485
3596
  files: [
3486
3597
  {
3487
3598
  target: settingsFile,
3488
- scope: "PROJECT",
3599
+ scope: "HOME",
3489
3600
  oldText: "",
3490
- newText: `// will be merged into .claude/settings.json enabledPlugins map by \`claude plugin install\`:
3601
+ newText: `// will be merged into ~/.claude.json enabledPlugins map by \`claude plugin install --scope user\`:
3491
3602
  ${newEntry}
3492
3603
  `
3493
3604
  }
@@ -3502,15 +3613,13 @@ ${newEntry}
3502
3613
  if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
3503
3614
  const bk = await backup(plan, ctx);
3504
3615
  if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
3616
+ const spawnCwd = install.cwd ?? getMcpSpawnCwd();
3505
3617
  let stepOneStderr = "";
3506
3618
  if (parsed.marketplaceRef !== null) {
3507
- const r1 = await runArgs(
3508
- ["plugin", "marketplace", "add", parsed.marketplaceRef],
3509
- install.cwd ?? ctx.cwd
3510
- );
3619
+ const r1 = await runArgs(["plugin", "marketplace", "add", parsed.marketplaceRef], spawnCwd);
3511
3620
  stepOneStderr = r1.stderr;
3512
3621
  }
3513
- const r2 = await runArgs(installArgs, install.cwd ?? ctx.cwd);
3622
+ const r2 = await runArgs(installArgs, spawnCwd);
3514
3623
  if (r2.exitCode !== 0) {
3515
3624
  return {
3516
3625
  ok: false,
@@ -3524,11 +3633,8 @@ ${newEntry}
3524
3633
  )
3525
3634
  };
3526
3635
  }
3527
- const verifyShell = process.platform === "win32" ? "cmd.exe" : "/bin/sh";
3528
- const verifyFlag = process.platform === "win32" ? "/c" : "-c";
3529
- const verifyLine = `claude plugin list --json | grep -q ${pluginName}`;
3530
- const violation = checkCmdString(verifyLine);
3531
- if (violation) {
3636
+ const registered = await isPluginRegistered(pluginName);
3637
+ if (!registered) {
3532
3638
  return {
3533
3639
  ok: false,
3534
3640
  phase: "verify",
@@ -3536,39 +3642,7 @@ ${newEntry}
3536
3642
  error: err(
3537
3643
  ctx,
3538
3644
  "/spec/verify/cmd",
3539
- `verify shell escape: ${violation.label}`,
3540
- "security-gate-bypass"
3541
- )
3542
- };
3543
- }
3544
- const vr = await new Promise((resolve9) => {
3545
- const child = spawn(verifyShell, [verifyFlag, verifyLine], { cwd: ctx.cwd, windowsHide: true });
3546
- let stderr = "";
3547
- child.stderr?.setEncoding("utf8").on("data", (c) => {
3548
- stderr += c;
3549
- });
3550
- const timer = setTimeout(() => {
3551
- child.kill("SIGKILL");
3552
- resolve9({ exitCode: -1, stderr: `${stderr}[timeout]` });
3553
- }, 15e3);
3554
- child.on("error", (e) => {
3555
- clearTimeout(timer);
3556
- resolve9({ exitCode: -1, stderr: e.message });
3557
- });
3558
- child.on("close", (code) => {
3559
- clearTimeout(timer);
3560
- resolve9({ exitCode: code ?? -1, stderr });
3561
- });
3562
- });
3563
- if (vr.exitCode !== 0) {
3564
- return {
3565
- ok: false,
3566
- phase: "verify",
3567
- backupId: bk.backupId,
3568
- error: err(
3569
- ctx,
3570
- "/spec/verify/cmd",
3571
- `verify exit ${vr.exitCode}: ${vr.stderr.slice(0, 200)}`,
3645
+ `verify: plugin '${pluginName}' not found in enabledPlugins map of ~/.claude.json after install spawn exit 0 (file may have been overwritten, or claude plugin install wrote to a non-default location)`,
3572
3646
  "verify-failed"
3573
3647
  )
3574
3648
  };
@@ -3576,8 +3650,9 @@ ${newEntry}
3576
3650
  await updateInstalled(ctx.cwd, ctx.manifest.metadata.name, install.git_ref, "");
3577
3651
  return { ok: true, backupId: bk.backupId, appliedFiles: [settingsFile] };
3578
3652
  };
3579
- var DEFAULT_TIMEOUT_MS = 15e3;
3580
- async function spawnCmd(ctx, cmd, args) {
3653
+ var DEFAULT_VERIFY_TIMEOUT_MS = 15e3;
3654
+ var DEFAULT_INSTALL_TIMEOUT_MS = 6e4;
3655
+ async function spawnCmd(ctx, cmd, args, timeoutMs) {
3581
3656
  const violation = checkCmdString(cmd);
3582
3657
  if (violation) {
3583
3658
  return {
@@ -3594,8 +3669,7 @@ async function spawnCmd(ctx, cmd, args) {
3594
3669
  };
3595
3670
  }
3596
3671
  const installCfg = ctx.manifest.spec.install;
3597
- const verifyCfg = ctx.manifest.spec.verify;
3598
- const timeoutMs = verifyCfg.timeout_ms ?? DEFAULT_TIMEOUT_MS;
3672
+ const effectiveTimeoutMs = timeoutMs ?? DEFAULT_INSTALL_TIMEOUT_MS;
3599
3673
  const env = { ...process.env, ...installCfg.env ?? {} };
3600
3674
  const cwd = installCfg.cwd ?? ctx.cwd;
3601
3675
  let child;
@@ -3622,13 +3696,13 @@ async function spawnCmd(ctx, cmd, args) {
3622
3696
  error: {
3623
3697
  file: ctx.manifest.metadata.name,
3624
3698
  path: "/spec/install/cmd",
3625
- message: `spawn timed out after ${timeoutMs}ms (cmd: ${cmd}); partial stderr: ${stderr.slice(0, 200)}`,
3699
+ message: `spawn timed out after ${effectiveTimeoutMs}ms (cmd: ${cmd}); partial stderr: ${stderr.slice(0, 200)}`,
3626
3700
  line: null,
3627
3701
  column: null,
3628
3702
  keyword: "spawn-timeout"
3629
3703
  }
3630
3704
  });
3631
- }, timeoutMs);
3705
+ }, effectiveTimeoutMs);
3632
3706
  child.on("error", (err2) => {
3633
3707
  clearTimeout(timer);
3634
3708
  resolve9({
@@ -3774,7 +3848,7 @@ var installGitCloneWithSetup = async (ctx) => {
3774
3848
  if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
3775
3849
  const bk = await backup(plan, ctx);
3776
3850
  if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
3777
- const sp = await spawnCmd(ctx, install.cmd, []);
3851
+ const sp = await spawnCmd(ctx, install.cmd, [], DEFAULT_INSTALL_TIMEOUT_MS);
3778
3852
  if (!("exitCode" in sp)) return { ...sp, backupId: bk.backupId };
3779
3853
  if (sp.exitCode !== 0) {
3780
3854
  return {
@@ -3816,7 +3890,8 @@ var installGitCloneWithSetup = async (ctx) => {
3816
3890
  )
3817
3891
  };
3818
3892
  }
3819
- const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, []);
3893
+ const verifyTimeoutMs = ctx.manifest.spec.verify.timeout_ms ?? DEFAULT_VERIFY_TIMEOUT_MS;
3894
+ const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, [], verifyTimeoutMs);
3820
3895
  if (!("exitCode" in vr)) return { ...vr, backupId: bk.backupId };
3821
3896
  const expected = ctx.manifest.spec.verify.expected_exit_code ?? 0;
3822
3897
  if (vr.exitCode !== expected) {
@@ -3835,6 +3910,8 @@ var installGitCloneWithSetup = async (ctx) => {
3835
3910
  await updateInstalled(ctx.cwd, name, install.git_ref, "");
3836
3911
  return { ok: true, backupId: bk.backupId, appliedFiles: [cloneTarget] };
3837
3912
  };
3913
+
3914
+ // src/installers/mcpHttpAdd.ts
3838
3915
  function resolveEnvVars(value) {
3839
3916
  const pattern = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
3840
3917
  let resolved = value;
@@ -3919,33 +3996,23 @@ var installMcpHttpAdd = async (ctx) => {
3919
3996
  }
3920
3997
  };
3921
3998
  }
3922
- const addArgs = [
3923
- "mcp",
3924
- "add",
3925
- "--scope",
3926
- "project",
3927
- "--transport",
3928
- "http",
3929
- ...hdr.flat,
3930
- name,
3931
- url
3932
- ];
3999
+ const addArgs = ["mcp", "add", "--scope", "user", "--transport", "http", ...hdr.flat, name, url];
3933
4000
  for (const a of addArgs) {
3934
- const violation2 = checkCmdString(a);
3935
- if (violation2) {
4001
+ const violation = checkCmdString(a);
4002
+ if (violation) {
3936
4003
  return {
3937
4004
  ok: false,
3938
4005
  phase: "preflight",
3939
4006
  error: err(
3940
4007
  ctx,
3941
4008
  "/spec/install/cmd",
3942
- `shell escape detected in constructed mcp-http-add arg '${a.slice(0, 60)}': ${violation2.label} (${violation2.hint})`,
4009
+ `shell escape detected in constructed mcp-http-add arg '${a.slice(0, 60)}': ${violation.label} (${violation.hint})`,
3943
4010
  "security-gate-bypass"
3944
4011
  )
3945
4012
  };
3946
4013
  }
3947
4014
  }
3948
- const mcpFile = `${ctx.cwd}/.mcp.json`;
4015
+ const mcpFile = `${getMcpSpawnCwd()}/.claude.json`;
3949
4016
  const headersObj = {};
3950
4017
  for (let i = 0; i < hdr.flat.length; i += 2) {
3951
4018
  const kv = hdr.flat[i + 1];
@@ -3959,9 +4026,9 @@ var installMcpHttpAdd = async (ctx) => {
3959
4026
  files: [
3960
4027
  {
3961
4028
  target: mcpFile,
3962
- scope: "PROJECT",
4029
+ scope: "HOME",
3963
4030
  oldText: "",
3964
- newText: `// will be merged into .mcp.json mcpServers map by \`claude mcp add\`:
4031
+ newText: `// will be merged into ~/.claude.json mcpServers map by \`claude mcp add --scope user\`:
3965
4032
  ${newEntry}
3966
4033
  `
3967
4034
  }
@@ -3976,9 +4043,10 @@ ${newEntry}
3976
4043
  if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
3977
4044
  const bk = await backup(plan, ctx);
3978
4045
  if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
3979
- const r = await runArgs(addArgs, install.cwd ?? ctx.cwd);
4046
+ const spawnCwd = install.cwd ?? getMcpSpawnCwd();
4047
+ const r = await runArgs(addArgs, spawnCwd);
3980
4048
  if (r.exitCode !== 0) {
3981
- if (r.stderr.includes("already exists in .mcp.json")) {
4049
+ if (r.stderr.includes("already exists")) {
3982
4050
  return { ok: true, alreadyInstalled: true, backupId: bk.backupId };
3983
4051
  }
3984
4052
  return {
@@ -3993,43 +4061,8 @@ ${newEntry}
3993
4061
  )
3994
4062
  };
3995
4063
  }
3996
- const verifyShell = process.platform === "win32" ? "cmd.exe" : "/bin/sh";
3997
- const verifyFlag = process.platform === "win32" ? "/c" : "-c";
3998
- const verifyLine = `claude mcp list | grep -q ${name}`;
3999
- const violation = checkCmdString(verifyLine);
4000
- if (violation) {
4001
- return {
4002
- ok: false,
4003
- phase: "verify",
4004
- backupId: bk.backupId,
4005
- error: err(
4006
- ctx,
4007
- "/spec/verify/cmd",
4008
- `verify shell escape: ${violation.label}`,
4009
- "security-gate-bypass"
4010
- )
4011
- };
4012
- }
4013
- const vr = await new Promise((resolve9) => {
4014
- const child = spawn(verifyShell, [verifyFlag, verifyLine], { cwd: ctx.cwd, windowsHide: true });
4015
- let stderr = "";
4016
- child.stderr?.setEncoding("utf8").on("data", (c) => {
4017
- stderr += c;
4018
- });
4019
- const timer = setTimeout(() => {
4020
- child.kill("SIGKILL");
4021
- resolve9({ exitCode: -1, stderr: `${stderr}[timeout]` });
4022
- }, 15e3);
4023
- child.on("error", (e) => {
4024
- clearTimeout(timer);
4025
- resolve9({ exitCode: -1, stderr: e.message });
4026
- });
4027
- child.on("close", (code) => {
4028
- clearTimeout(timer);
4029
- resolve9({ exitCode: code ?? -1, stderr });
4030
- });
4031
- });
4032
- if (vr.exitCode !== 0) {
4064
+ const registered = await isMcpServerRegistered(name);
4065
+ if (!registered) {
4033
4066
  return {
4034
4067
  ok: false,
4035
4068
  phase: "verify",
@@ -4037,7 +4070,7 @@ ${newEntry}
4037
4070
  error: err(
4038
4071
  ctx,
4039
4072
  "/spec/verify/cmd",
4040
- `verify exit ${vr.exitCode}: ${vr.stderr.slice(0, 200)}`,
4073
+ `verify: '${name}' not found in mcpServers map of ~/.claude.json after install spawn exit 0 (file may have been overwritten, or claude mcp add wrote to a non-default location)`,
4041
4074
  "verify-failed"
4042
4075
  )
4043
4076
  };
@@ -4045,6 +4078,8 @@ ${newEntry}
4045
4078
  await updateInstalled(ctx.cwd, name, install.npm_version, "");
4046
4079
  return { ok: true, backupId: bk.backupId, appliedFiles: [mcpFile] };
4047
4080
  };
4081
+
4082
+ // src/installers/mcpStdioAdd.ts
4048
4083
  var installMcpStdioAdd = async (ctx) => {
4049
4084
  const install = ctx.manifest.spec.install;
4050
4085
  if (install.method !== "mcp-stdio-add") {
@@ -4073,7 +4108,7 @@ var installMcpStdioAdd = async (ctx) => {
4073
4108
  "mcp",
4074
4109
  "add",
4075
4110
  "--scope",
4076
- "project",
4111
+ "user",
4077
4112
  "--transport",
4078
4113
  "stdio",
4079
4114
  name,
@@ -4083,21 +4118,21 @@ var installMcpStdioAdd = async (ctx) => {
4083
4118
  `${pkg}@${ver}`
4084
4119
  ];
4085
4120
  for (const a of addArgs) {
4086
- const violation2 = checkCmdString(a);
4087
- if (violation2) {
4121
+ const violation = checkCmdString(a);
4122
+ if (violation) {
4088
4123
  return {
4089
4124
  ok: false,
4090
4125
  phase: "preflight",
4091
4126
  error: err(
4092
4127
  ctx,
4093
4128
  "/spec/install/cmd",
4094
- `shell escape detected in constructed mcp-add arg '${a.slice(0, 60)}': ${violation2.label} (${violation2.hint})`,
4129
+ `shell escape detected in constructed mcp-add arg '${a.slice(0, 60)}': ${violation.label} (${violation.hint})`,
4095
4130
  "security-gate-bypass"
4096
4131
  )
4097
4132
  };
4098
4133
  }
4099
4134
  }
4100
- const mcpFile = `${ctx.cwd}/.mcp.json`;
4135
+ const mcpFile = `${getMcpSpawnCwd()}/.claude.json`;
4101
4136
  const newEntry = JSON.stringify(
4102
4137
  { [name]: { type: "stdio", command: "npx", args: ["--yes", `${pkg}@${ver}`] } },
4103
4138
  null,
@@ -4107,9 +4142,9 @@ var installMcpStdioAdd = async (ctx) => {
4107
4142
  files: [
4108
4143
  {
4109
4144
  target: mcpFile,
4110
- scope: "PROJECT",
4145
+ scope: "HOME",
4111
4146
  oldText: "",
4112
- newText: `// will be merged into .mcp.json mcpServers map by \`claude mcp add\`:
4147
+ newText: `// will be merged into ~/.claude.json mcpServers map by \`claude mcp add --scope user\`:
4113
4148
  ${newEntry}
4114
4149
  `
4115
4150
  }
@@ -4124,9 +4159,10 @@ ${newEntry}
4124
4159
  if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
4125
4160
  const bk = await backup(plan, ctx);
4126
4161
  if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
4127
- const r = await runArgs(addArgs, install.cwd ?? ctx.cwd);
4162
+ const spawnCwd = install.cwd ?? getMcpSpawnCwd();
4163
+ const r = await runArgs(addArgs, spawnCwd);
4128
4164
  if (r.exitCode !== 0) {
4129
- if (r.stderr.includes("already exists in .mcp.json")) {
4165
+ if (r.stderr.includes("already exists")) {
4130
4166
  return { ok: true, alreadyInstalled: true, backupId: bk.backupId };
4131
4167
  }
4132
4168
  return {
@@ -4141,11 +4177,8 @@ ${newEntry}
4141
4177
  )
4142
4178
  };
4143
4179
  }
4144
- const verifyShell = process.platform === "win32" ? "cmd.exe" : "/bin/sh";
4145
- const verifyFlag = process.platform === "win32" ? "/c" : "-c";
4146
- const verifyLine = `claude mcp list | grep -q ${name}`;
4147
- const violation = checkCmdString(verifyLine);
4148
- if (violation) {
4180
+ const registered = await isMcpServerRegistered(name);
4181
+ if (!registered) {
4149
4182
  return {
4150
4183
  ok: false,
4151
4184
  phase: "verify",
@@ -4153,39 +4186,7 @@ ${newEntry}
4153
4186
  error: err(
4154
4187
  ctx,
4155
4188
  "/spec/verify/cmd",
4156
- `verify shell escape: ${violation.label}`,
4157
- "security-gate-bypass"
4158
- )
4159
- };
4160
- }
4161
- const vr = await new Promise((resolve9) => {
4162
- const child = spawn(verifyShell, [verifyFlag, verifyLine], { cwd: ctx.cwd, windowsHide: true });
4163
- let stderr = "";
4164
- child.stderr?.setEncoding("utf8").on("data", (c) => {
4165
- stderr += c;
4166
- });
4167
- const timer = setTimeout(() => {
4168
- child.kill("SIGKILL");
4169
- resolve9({ exitCode: -1, stderr: `${stderr}[timeout]` });
4170
- }, 15e3);
4171
- child.on("error", (e) => {
4172
- clearTimeout(timer);
4173
- resolve9({ exitCode: -1, stderr: e.message });
4174
- });
4175
- child.on("close", (code) => {
4176
- clearTimeout(timer);
4177
- resolve9({ exitCode: code ?? -1, stderr });
4178
- });
4179
- });
4180
- if (vr.exitCode !== 0) {
4181
- return {
4182
- ok: false,
4183
- phase: "verify",
4184
- backupId: bk.backupId,
4185
- error: err(
4186
- ctx,
4187
- "/spec/verify/cmd",
4188
- `verify exit ${vr.exitCode}: ${vr.stderr.slice(0, 200)}`,
4189
+ `verify: '${name}' not found in mcpServers map of ~/.claude.json after install spawn exit 0 (file may have been overwritten, or claude mcp add wrote to a non-default location)`,
4189
4190
  "verify-failed"
4190
4191
  )
4191
4192
  };
@@ -4251,7 +4252,7 @@ var installNpmCli = async (ctx) => {
4251
4252
  if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
4252
4253
  const bk = await backup(plan, ctx);
4253
4254
  if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
4254
- const sp = await spawnCmd(ctx, cmd, []);
4255
+ const sp = await spawnCmd(ctx, cmd, [], DEFAULT_INSTALL_TIMEOUT_MS);
4255
4256
  if (!("exitCode" in sp)) return { ...sp, backupId: bk.backupId };
4256
4257
  if (sp.exitCode !== 0) {
4257
4258
  return {
@@ -4266,7 +4267,8 @@ var installNpmCli = async (ctx) => {
4266
4267
  )
4267
4268
  };
4268
4269
  }
4269
- const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, []);
4270
+ const verifyTimeoutMs = ctx.manifest.spec.verify.timeout_ms ?? DEFAULT_VERIFY_TIMEOUT_MS;
4271
+ const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, [], verifyTimeoutMs);
4270
4272
  if (!("exitCode" in vr)) return { ...vr, backupId: bk.backupId };
4271
4273
  const expected = ctx.manifest.spec.verify.expected_exit_code ?? 0;
4272
4274
  if (vr.exitCode !== expected) {
@@ -4367,7 +4369,7 @@ var installNpxSkillInstaller = async (ctx) => {
4367
4369
  if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
4368
4370
  const bk = await backup(plan, ctx);
4369
4371
  if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
4370
- const sp = await spawnCmd(ctx, install.cmd, []);
4372
+ const sp = await spawnCmd(ctx, install.cmd, [], DEFAULT_INSTALL_TIMEOUT_MS);
4371
4373
  if (!("exitCode" in sp)) return { ...sp, backupId: bk.backupId };
4372
4374
  if (sp.exitCode !== 0) {
4373
4375
  return {
@@ -4400,7 +4402,8 @@ var installNpxSkillInstaller = async (ctx) => {
4400
4402
  }
4401
4403
  };
4402
4404
  }
4403
- const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, []);
4405
+ const verifyTimeoutMs = ctx.manifest.spec.verify.timeout_ms ?? DEFAULT_VERIFY_TIMEOUT_MS;
4406
+ const vr = await spawnCmd(ctx, ctx.manifest.spec.verify.cmd, [], verifyTimeoutMs);
4404
4407
  if (!("exitCode" in vr)) return { ...vr, backupId: bk.backupId };
4405
4408
  const expected = ctx.manifest.spec.verify.expected_exit_code ?? 0;
4406
4409
  if (vr.exitCode !== expected) {
@@ -4896,7 +4899,7 @@ async function warnIfAgentTeamsMissing() {
4896
4899
  console.warn("\n\u26A0\uFE0F Agent Teams \u672A\u542F\u7528 \u2014 parallelism-gate \u5347\u7EA7\u8DEF\u5F84\u4E0D\u53EF\u7528");
4897
4900
  console.warn(" \u4FEE\u590D: claude config set env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS 1");
4898
4901
  console.warn(
4899
- " \u8BF4\u660E: harnessed v2.0 \u4E09\u5C42\u6808\u65B9\u6CD5\u8BBA parallelism-gate \u5347\u7EA7\u8DEF\u5F84\u9700 CC 2.1.133+ Agent Teams enable (sister ~/.claude/rules/agent-teams.md)"
4902
+ " \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"
4900
4903
  );
4901
4904
  console.warn(
4902
4905
  " \u4E0D\u963B\u585E setup,\u540E\u7EED parallelism-gate workflow phase \u89E6\u53D1\u65F6\u81EA\u52A8\u964D\u7EA7 subagent fan-out\n"
@@ -5052,7 +5055,7 @@ MCP servers configured. Run \`/mcp\` in Claude Code to verify each server's conn
5052
5055
  );
5053
5056
  }
5054
5057
  console.log(
5055
- "\n\u2713 harnessed v2.0 \u4E09\u5C42\u6808\u65B9\u6CD5\u8BBA bundled \u2014 4 workflows + 6 judgments + 37 capabilities ready"
5058
+ "\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"
5056
5059
  );
5057
5060
  console.log(
5058
5061
  " workflows in <packageRoot>/workflows/ (Pure bundled, NOT user-dir override per D-01)"
@@ -5060,12 +5063,15 @@ MCP servers configured. Run \`/mcp\` in Claude Code to verify each server's conn
5060
5063
  process.exit(0);
5061
5064
  });
5062
5065
  }
5066
+
5067
+ // src/cli/status.ts
5068
+ init_harnessedRoot();
5063
5069
  function registerStatus(program2) {
5064
- program2.command("status").description("Show installed upstreams (from .harnessed/state.json)").action(async () => {
5070
+ program2.command("status").description("Show installed upstreams (from <harnessed-root>/state.json)").action(async () => {
5065
5071
  const state = await readState(process.cwd());
5066
5072
  const names = Object.keys(state.installed).sort();
5067
5073
  if (names.length === 0) {
5068
- console.log("no installs recorded (.harnessed/state.json absent or empty)");
5074
+ console.log(`no installs recorded (${harnessedFile("state.json")} absent or empty)`);
5069
5075
  } else {
5070
5076
  for (const n of names) {
5071
5077
  const e = state.installed[n];
@@ -5075,18 +5081,19 @@ function registerStatus(program2) {
5075
5081
  console.log(`
5076
5082
  ${names.length} install${names.length === 1 ? "" : "s"} recorded`);
5077
5083
  }
5084
+ const lockPath = harnessedFile(".lock");
5078
5085
  try {
5079
- const isLocked = await lockfile.check(".harnessed", {
5080
- lockfilePath: ".harnessed/.lock",
5086
+ const isLocked = await lockfile.check(getHarnessedRoot(), {
5087
+ lockfilePath: lockPath,
5081
5088
  stale: 1e4
5082
5089
  });
5083
5090
  if (isLocked) {
5084
- const s = await stat(".harnessed/.lock");
5091
+ const s = await stat(lockPath);
5085
5092
  const ageMs = Date.now() - s.mtime.getTime();
5086
5093
  const stale = ageMs > 1e4;
5087
5094
  console.log(`
5088
5095
  lock: held (since ${s.mtime.toISOString()})${stale ? " \u2014 STALE" : ""}`);
5089
- console.log(" to release: wait for process to finish or delete .harnessed/.lock");
5096
+ console.log(` to release: wait for process to finish or delete ${lockPath}`);
5090
5097
  } else {
5091
5098
  console.log("\nlock: free");
5092
5099
  }
@@ -5415,6 +5422,8 @@ function registerUninstall(program2) {
5415
5422
  }
5416
5423
 
5417
5424
  // src/cli.ts
5425
+ init_harnessedRoot();
5426
+ migrateLegacyHarnessedRoot();
5418
5427
  var program = new Command();
5419
5428
  program.name("harnessed").description("AI coding harness package manager + composition orchestrator").version(package_default.version);
5420
5429
  registerInstall(program);