@haus-tech/haus-workflow 5.0.0 → 5.0.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/dist/cli.js CHANGED
@@ -1998,7 +1998,7 @@ var init_sync = __esm({
1998
1998
  // src/cli.ts
1999
1999
  init_offline();
2000
2000
  import { readFileSync as readFileSync5 } from "node:fs";
2001
- import path90 from "node:path";
2001
+ import path91 from "node:path";
2002
2002
  import { Command, Option } from "commander";
2003
2003
 
2004
2004
  // src/commands/apply.ts
@@ -2354,9 +2354,162 @@ function summarizeGitignoredHausDirs(paths) {
2354
2354
  return [...dirs];
2355
2355
  }
2356
2356
 
2357
+ // src/claude/migrate-request-state.ts
2358
+ init_exec();
2359
+ init_paths();
2360
+ import path12 from "node:path";
2361
+ import fs9 from "fs-extra";
2362
+ var LEGACY_REQUEST_DIR = ".haus";
2363
+ var LEGACY_REQUEST_STATE_MOVES = [
2364
+ { from: `${LEGACY_REQUEST_DIR}/requests`, to: `${HAUS_DIR}/requests`, keepTracked: false },
2365
+ { from: `${LEGACY_REQUEST_DIR}/request.json`, to: `${HAUS_DIR}/request.json`, keepTracked: true }
2366
+ ];
2367
+ async function isSymlink(abs) {
2368
+ try {
2369
+ return (await fs9.lstat(abs)).isSymbolicLink();
2370
+ } catch {
2371
+ return false;
2372
+ }
2373
+ }
2374
+ async function exists(abs) {
2375
+ if (await fs9.pathExists(abs)) return true;
2376
+ return isSymlink(abs);
2377
+ }
2378
+ async function trackedUnder(root, rel) {
2379
+ try {
2380
+ const result = await runGit(["ls-files", "--", rel], { cwd: root, timeout: 5e3 });
2381
+ if (result.exitCode !== 0) return [];
2382
+ return result.stdout.split("\n").map((line2) => line2.trim()).filter(Boolean);
2383
+ } catch {
2384
+ return [];
2385
+ }
2386
+ }
2387
+ async function runGitStep(root, args, notify) {
2388
+ try {
2389
+ const result = await runGit([...args], { cwd: root, timeout: 5e3 });
2390
+ if (result.exitCode === 0) return true;
2391
+ notify(
2392
+ `Could not run \`git ${args.join(" ")}\`: ${result.stderr.trim() || result.stdout.trim()}`
2393
+ );
2394
+ return false;
2395
+ } catch (error2) {
2396
+ notify(
2397
+ `Could not run \`git ${args.join(" ")}\`: ${error2 instanceof Error ? error2.message : String(error2)}`
2398
+ );
2399
+ return false;
2400
+ }
2401
+ }
2402
+ async function reconcileIndex(root, move, dryRun, notify) {
2403
+ const tracked = await trackedUnder(root, move.from);
2404
+ if (tracked.length === 0) return;
2405
+ if (dryRun) {
2406
+ notify(
2407
+ move.keepTracked ? `Would stage the move of ${move.from} to ${move.to} in git (${tracked.length} tracked file(s)).` : `Would untrack ${tracked.length} committed file(s) under ${move.from} from git \u2014 request pricing material should never have been committed.`
2408
+ );
2409
+ return;
2410
+ }
2411
+ const removed = await runGitStep(
2412
+ root,
2413
+ ["rm", "--cached", "-r", "--quiet", "--", move.from],
2414
+ notify
2415
+ );
2416
+ if (!removed) return;
2417
+ if (move.keepTracked) {
2418
+ await runGitStep(root, ["add", "--", move.to], notify);
2419
+ notify(
2420
+ `Staged the move of ${move.from} to ${move.to} in git \u2014 it is board configuration and stays tracked. Run \`git status\` to review before committing this change.`
2421
+ );
2422
+ return;
2423
+ }
2424
+ notify(
2425
+ `Untracked ${tracked.length} file(s) under ${move.from} from git \u2014 request pricing material should never have been committed, and \`${move.to}\` is gitignored from here on. Run \`git status\` to review before committing this change.`
2426
+ );
2427
+ }
2428
+ async function removeEmptyLegacyDir(root, previewedMoves, dryRun, notify) {
2429
+ const legacyAbs = path12.join(root, LEGACY_REQUEST_DIR);
2430
+ if (!await fs9.pathExists(legacyAbs)) return;
2431
+ if (await isSymlink(legacyAbs)) return;
2432
+ try {
2433
+ const remaining = await fs9.readdir(legacyAbs);
2434
+ if (dryRun) {
2435
+ const wouldLeave = new Set(previewedMoves.map((m) => path12.basename(m.from)));
2436
+ if (remaining.some((entry) => !wouldLeave.has(entry))) return;
2437
+ notify(`Would remove the ${LEGACY_REQUEST_DIR}/ directory once the moves above empty it.`);
2438
+ return;
2439
+ }
2440
+ if (remaining.length > 0) return;
2441
+ await fs9.remove(legacyAbs);
2442
+ notify(`Removed the now-empty ${LEGACY_REQUEST_DIR}/ directory.`);
2443
+ } catch (error2) {
2444
+ notify(
2445
+ `Could not remove the empty ${LEGACY_REQUEST_DIR}/ directory: ${error2 instanceof Error ? error2.message : String(error2)}. The request state was migrated successfully \u2014 delete ${LEGACY_REQUEST_DIR}/ by hand.`
2446
+ );
2447
+ }
2448
+ }
2449
+ async function migrateRequestState(root, dryRun, notify) {
2450
+ const moved = [];
2451
+ const conflicts = [];
2452
+ const failures = [];
2453
+ const legacyAbs = path12.join(root, LEGACY_REQUEST_DIR);
2454
+ if (!await exists(legacyAbs)) return { moved, conflicts, failures };
2455
+ if (await isSymlink(legacyAbs)) {
2456
+ for (const move of LEGACY_REQUEST_STATE_MOVES) {
2457
+ conflicts.push({ ...move, reason: "legacy-dir-is-symlink" });
2458
+ }
2459
+ notify(
2460
+ `Refused to migrate request state: ${LEGACY_REQUEST_DIR}/ is a symlink. Moving through it would relocate the link target's real archive into this repo. Migrate the link's target yourself, replace the link with a real directory, then re-run.`
2461
+ );
2462
+ return { moved, conflicts, failures };
2463
+ }
2464
+ for (const move of LEGACY_REQUEST_STATE_MOVES) {
2465
+ const fromAbs = path12.join(root, move.from);
2466
+ const toAbs = path12.join(root, move.to);
2467
+ if (!await exists(fromAbs)) continue;
2468
+ if (await isSymlink(fromAbs)) {
2469
+ conflicts.push({ ...move, reason: "source-is-symlink" });
2470
+ notify(
2471
+ `Refused to migrate ${move.from} to ${move.to}: ${move.from} is a symlink. Moving the link would leave the real request state outside \`${HAUS_DIR}/\`. Move the link's target yourself, then re-run.`
2472
+ );
2473
+ continue;
2474
+ }
2475
+ if (await exists(toAbs)) {
2476
+ conflicts.push({ ...move, reason: "destination-exists" });
2477
+ notify(
2478
+ `Refused to migrate ${move.from} to ${move.to}: ${move.to} already exists. Two locations hold request state \u2014 reconcile them by hand, then remove ${move.from} so this migration can finish.`
2479
+ );
2480
+ continue;
2481
+ }
2482
+ if (dryRun) {
2483
+ moved.push(move);
2484
+ notify(`Would move ${move.from} to ${move.to} (request state now lives under ${HAUS_DIR}/).`);
2485
+ await reconcileIndex(root, move, true, notify);
2486
+ continue;
2487
+ }
2488
+ const trackedBefore = await trackedUnder(root, move.from);
2489
+ try {
2490
+ await fs9.ensureDir(path12.dirname(toAbs));
2491
+ await fs9.move(fromAbs, toAbs);
2492
+ } catch (error2) {
2493
+ const message2 = error2 instanceof Error ? error2.message : String(error2);
2494
+ failures.push({ ...move, message: message2 });
2495
+ notify(
2496
+ `Could not migrate ${move.from} to ${move.to}: ${message2}. ${move.from} was left in place \u2014 fix the cause and re-run \`haus apply --write\`.`
2497
+ );
2498
+ continue;
2499
+ }
2500
+ moved.push(move);
2501
+ notify(
2502
+ `Moved ${move.from} to ${move.to} \u2014 request state now lives under \`${HAUS_DIR}/\`, where haus's gitignore rules cover it (ADR-0103).`
2503
+ );
2504
+ if (trackedBefore.length > 0) await reconcileIndex(root, move, false, notify);
2505
+ }
2506
+ if (moved.length > 0) await removeEmptyLegacyDir(root, moved, dryRun, notify);
2507
+ return { moved, conflicts, failures };
2508
+ }
2509
+
2357
2510
  // src/claude/write-claude-files.ts
2358
- import path26 from "node:path";
2359
- import fs24 from "fs-extra";
2511
+ import path27 from "node:path";
2512
+ import fs25 from "fs-extra";
2360
2513
  init_ingest_catalog();
2361
2514
 
2362
2515
  // src/catalog/skill-description.ts
@@ -2441,7 +2594,7 @@ function findOrphanedLockEntries(lock, recommendedIds) {
2441
2594
  init_validation_rules2();
2442
2595
 
2443
2596
  // src/scanner/detection.ts
2444
- import path12 from "node:path";
2597
+ import path13 from "node:path";
2445
2598
 
2446
2599
  // src/security/sensitive-paths.ts
2447
2600
  var DENY_PATHS = [
@@ -2638,7 +2791,7 @@ function finalizeRoles(registryRoles, deps, files) {
2638
2791
  function collectUnsupportedSignals(files) {
2639
2792
  return [
2640
2793
  ...new Set(
2641
- files.map((f) => UNSUPPORTED_MARKERS[path12.basename(f)]).filter((s) => Boolean(s))
2794
+ files.map((f) => UNSUPPORTED_MARKERS[path13.basename(f)]).filter((s) => Boolean(s))
2642
2795
  )
2643
2796
  ].sort();
2644
2797
  }
@@ -2721,8 +2874,8 @@ function isSupersededByPlugin(lockId, activePinIds) {
2721
2874
  }
2722
2875
 
2723
2876
  // src/scanner/detect-package-manager.ts
2724
- import path13 from "node:path";
2725
- import fs9 from "fs-extra";
2877
+ import path14 from "node:path";
2878
+ import fs10 from "fs-extra";
2726
2879
  function detectPackageManager(root, packageManagerField) {
2727
2880
  const field = String(packageManagerField ?? "").trim();
2728
2881
  if (field.startsWith("yarn@")) {
@@ -2740,16 +2893,16 @@ function detectPackageManager(root, packageManagerField) {
2740
2893
  if (satisfiesVersion(version, ">=9")) return "npm";
2741
2894
  return "unknown";
2742
2895
  }
2743
- if (fs9.existsSync(path13.join(root, "yarn.lock"))) return "yarn";
2744
- if (fs9.existsSync(path13.join(root, "pnpm-lock.yaml"))) return "pnpm";
2745
- if (fs9.existsSync(path13.join(root, "package-lock.json"))) return "npm";
2896
+ if (fs10.existsSync(path14.join(root, "yarn.lock"))) return "yarn";
2897
+ if (fs10.existsSync(path14.join(root, "pnpm-lock.yaml"))) return "pnpm";
2898
+ if (fs10.existsSync(path14.join(root, "package-lock.json"))) return "npm";
2746
2899
  return "unknown";
2747
2900
  }
2748
2901
 
2749
2902
  // src/update/hash-installed.ts
2750
- import path15 from "node:path";
2903
+ import path16 from "node:path";
2751
2904
  import fg3 from "fast-glob";
2752
- import fs11 from "fs-extra";
2905
+ import fs12 from "fs-extra";
2753
2906
 
2754
2907
  // src/claude/haus-managed-header.ts
2755
2908
  function parseHausManagedAttrs(line2) {
@@ -2787,9 +2940,9 @@ function parseHausManagedHeader(line2) {
2787
2940
  }
2788
2941
 
2789
2942
  // src/claude/superpowers-install.ts
2790
- import path14 from "node:path";
2943
+ import path15 from "node:path";
2791
2944
  import fg2 from "fast-glob";
2792
- import fs10 from "fs-extra";
2945
+ import fs11 from "fs-extra";
2793
2946
  init_logger();
2794
2947
  var SKILL_DEP_ARTIFACTS = ["node_modules/**"];
2795
2948
  var SKILL_DEP_ARTIFACT_DIRS = /* @__PURE__ */ new Set(["node_modules"]);
@@ -2812,34 +2965,34 @@ async function previewInstalledSkill(srcFile, opts) {
2812
2965
  return next === text ? void 0 : Buffer.from(next, "utf8");
2813
2966
  }
2814
2967
  async function readRewritableText(file) {
2815
- const raw = await fs10.readFile(file);
2968
+ const raw = await fs11.readFile(file);
2816
2969
  const text = raw.toString("utf8");
2817
2970
  return Buffer.from(text, "utf8").equals(raw) ? text : void 0;
2818
2971
  }
2819
2972
  async function composeInstalledDescription(destination, signal) {
2820
- const skillFile = path14.join(destination, SKILL_ENTRY_FILE);
2821
- if (!await fs10.pathExists(skillFile)) return;
2973
+ const skillFile = path15.join(destination, SKILL_ENTRY_FILE);
2974
+ if (!await fs11.pathExists(skillFile)) return;
2822
2975
  const text = await readRewritableText(skillFile);
2823
2976
  if (text === void 0) return;
2824
2977
  const composed = rewriteSkillDescription(text, signal);
2825
- if (composed !== text) await fs10.writeFile(skillFile, composed, "utf8");
2978
+ if (composed !== text) await fs11.writeFile(skillFile, composed, "utf8");
2826
2979
  }
2827
2980
  async function installCatalogSkill(sourcePath, destination, opts) {
2828
2981
  if (opts.dryRun) return;
2829
- await fs10.ensureDir(path14.dirname(destination));
2830
- if (await fs10.pathExists(destination)) {
2982
+ await fs11.ensureDir(path15.dirname(destination));
2983
+ if (await fs11.pathExists(destination)) {
2831
2984
  const extra = await extraDestFiles(sourcePath, destination);
2832
2985
  if (extra.length > 0) {
2833
2986
  warn(
2834
2987
  `${destination}: removing ${extra.length} untracked file(s) not present in the catalog source before reinstall: ${extra.join(", ")}`
2835
2988
  );
2836
2989
  }
2837
- for (const entry of await fs10.readdir(destination)) {
2990
+ for (const entry of await fs11.readdir(destination)) {
2838
2991
  if (SKILL_DEP_ARTIFACT_DIRS.has(entry)) continue;
2839
- await fs10.remove(path14.join(destination, entry));
2992
+ await fs11.remove(path15.join(destination, entry));
2840
2993
  }
2841
2994
  }
2842
- await fs10.copy(sourcePath, destination, { overwrite: true, errorOnExist: false });
2995
+ await fs11.copy(sourcePath, destination, { overwrite: true, errorOnExist: false });
2843
2996
  if (opts.description) await composeInstalledDescription(destination, opts.description);
2844
2997
  }
2845
2998
 
@@ -2862,12 +3015,12 @@ async function hashInstalledPaths(root, relPaths, opts = {}) {
2862
3015
  const normalized = [...new Set(relPaths.map((p) => p.replace(/\\/g, "/")))].sort();
2863
3016
  const fileDigests = [];
2864
3017
  for (const rel of normalized) {
2865
- const abs = path15.join(root, rel);
2866
- if (!await fs11.pathExists(abs)) continue;
2867
- const stat = followSymlinks ? await fs11.stat(abs) : await fs11.lstat(abs);
3018
+ const abs = path16.join(root, rel);
3019
+ if (!await fs12.pathExists(abs)) continue;
3020
+ const stat = followSymlinks ? await fs12.stat(abs) : await fs12.lstat(abs);
2868
3021
  if (!followSymlinks && stat.isSymbolicLink()) continue;
2869
3022
  if (stat.isFile()) {
2870
- const body = await fs11.readFile(abs);
3023
+ const body = await fs12.readFile(abs);
2871
3024
  fileDigests.push({ rel, digest: digestFileContent(body) });
2872
3025
  continue;
2873
3026
  }
@@ -2887,10 +3040,10 @@ async function hashInstalledPaths(root, relPaths, opts = {}) {
2887
3040
  ignore: [...SKILL_DEP_ARTIFACTS]
2888
3041
  });
2889
3042
  for (const sub of inner.sort()) {
2890
- const absFile = path15.join(abs, sub);
2891
- if (!followSymlinks && (await fs11.lstat(absFile)).isSymbolicLink()) continue;
2892
- const relFile = path15.join(rel, sub).replace(/\\/g, "/");
2893
- const body = await fs11.readFile(absFile);
3043
+ const absFile = path16.join(abs, sub);
3044
+ if (!followSymlinks && (await fs12.lstat(absFile)).isSymbolicLink()) continue;
3045
+ const relFile = path16.join(rel, sub).replace(/\\/g, "/");
3046
+ const body = await fs12.readFile(absFile);
2894
3047
  fileDigests.push({ rel: relFile, digest: digestFileContent(body) });
2895
3048
  }
2896
3049
  }
@@ -2929,15 +3082,15 @@ init_logger();
2929
3082
  init_paths();
2930
3083
 
2931
3084
  // src/claude/managed-write.ts
2932
- import fs12 from "fs-extra";
3085
+ import fs13 from "fs-extra";
2933
3086
  init_fs();
2934
3087
  init_logger();
2935
3088
  init_paths();
2936
3089
  async function writeManagedText(root, filePath, nextText, dryRun, quiet = false) {
2937
3090
  const say = quiet ? () => {
2938
3091
  } : log;
2939
- const existed = await fs12.pathExists(filePath);
2940
- const prev = existed ? await fs12.readFile(filePath, "utf8") : "";
3092
+ const existed = await fs13.pathExists(filePath);
3093
+ const prev = existed ? await fs13.readFile(filePath, "utf8") : "";
2941
3094
  const printable = displayPath(root, filePath);
2942
3095
  if (dryRun) {
2943
3096
  if (!existed) {
@@ -3023,19 +3176,19 @@ function buildAllowRules() {
3023
3176
 
3024
3177
  // src/install/settings-merge.ts
3025
3178
  init_fs();
3026
- import path17 from "node:path";
3027
- import fs13 from "fs-extra";
3179
+ import path18 from "node:path";
3180
+ import fs14 from "fs-extra";
3028
3181
 
3029
3182
  // src/install/manifest.ts
3030
3183
  init_fs();
3031
3184
  init_paths();
3032
- import path16 from "node:path";
3185
+ import path17 from "node:path";
3033
3186
  var MANIFEST_SCHEMA = "haus-install-manifest/1";
3034
3187
  function globalClaudeDir() {
3035
3188
  return claudeConfigDir();
3036
3189
  }
3037
3190
  function hausManifestPath() {
3038
- return path16.join(globalClaudeDir(), "haus", "install-manifest.json");
3191
+ return path17.join(globalClaudeDir(), "haus", "install-manifest.json");
3039
3192
  }
3040
3193
  async function readManifest() {
3041
3194
  return readJson(hausManifestPath());
@@ -3074,7 +3227,7 @@ function buildManifest(source, files, hooks, plugins) {
3074
3227
  // src/install/settings-merge.ts
3075
3228
  init_fs();
3076
3229
  function settingsJsonPath() {
3077
- return path17.join(globalClaudeDir(), "settings.json");
3230
+ return path18.join(globalClaudeDir(), "settings.json");
3078
3231
  }
3079
3232
  async function readSettings() {
3080
3233
  const filePath = settingsJsonPath();
@@ -3334,7 +3487,7 @@ function stripHausAsk(settings) {
3334
3487
  async function loadHooksFragment(fragmentPath) {
3335
3488
  let raw;
3336
3489
  try {
3337
- raw = await fs13.readJson(fragmentPath);
3490
+ raw = await fs14.readJson(fragmentPath);
3338
3491
  } catch {
3339
3492
  return [];
3340
3493
  }
@@ -3394,8 +3547,8 @@ function buildDenyRules() {
3394
3547
  if (RUNTIME_ONLY_DENY_COMMANDS.includes(command)) continue;
3395
3548
  rules.push(`Bash(${command}:*)`);
3396
3549
  }
3397
- for (const path91 of DENY_PATHS) {
3398
- const pattern = DENY_DIRS.has(path91) ? `${path91}/**` : path91;
3550
+ for (const path92 of DENY_PATHS) {
3551
+ const pattern = DENY_DIRS.has(path92) ? `${path92}/**` : path92;
3399
3552
  for (const tool of FILE_TOOLS) {
3400
3553
  rules.push(`${tool}(${pattern})`);
3401
3554
  }
@@ -3485,8 +3638,8 @@ async function applyProjectSettingsMerge(root) {
3485
3638
 
3486
3639
  // src/claude/remove-items.ts
3487
3640
  import crypto2 from "node:crypto";
3488
- import path18 from "node:path";
3489
- import fs14 from "fs-extra";
3641
+ import path19 from "node:path";
3642
+ import fs15 from "fs-extra";
3490
3643
  init_fs();
3491
3644
  init_paths();
3492
3645
 
@@ -3516,17 +3669,17 @@ function classifyBackupName(name) {
3516
3669
  async function unsafeTrackedPath(root, rel) {
3517
3670
  const abs = resolveContainedPath(root, rel);
3518
3671
  if (abs === null) return "escapes-root";
3519
- if (!await fs14.pathExists(abs)) {
3672
+ if (!await fs15.pathExists(abs)) {
3520
3673
  try {
3521
- if ((await fs14.lstat(abs)).isSymbolicLink()) return "symlink";
3674
+ if ((await fs15.lstat(abs)).isSymbolicLink()) return "symlink";
3522
3675
  } catch {
3523
3676
  return null;
3524
3677
  }
3525
3678
  return null;
3526
3679
  }
3527
- if ((await fs14.lstat(abs)).isSymbolicLink()) return "symlink";
3528
- const [realAbs, realRoot] = await Promise.all([fs14.realpath(abs), fs14.realpath(root)]);
3529
- const prefix = realRoot.endsWith(path18.sep) ? realRoot : realRoot + path18.sep;
3680
+ if ((await fs15.lstat(abs)).isSymbolicLink()) return "symlink";
3681
+ const [realAbs, realRoot] = await Promise.all([fs15.realpath(abs), fs15.realpath(root)]);
3682
+ const prefix = realRoot.endsWith(path19.sep) ? realRoot : realRoot + path19.sep;
3530
3683
  if (realAbs !== realRoot && !realAbs.startsWith(prefix)) return "symlink";
3531
3684
  return null;
3532
3685
  }
@@ -3550,8 +3703,8 @@ async function resolveExistingTrackedPaths(root, itemId, relPaths, warnFn, unsaf
3550
3703
  warnFn(`Refusing to touch ${itemId}'s recorded path through a symlink: ${rel}`);
3551
3704
  continue;
3552
3705
  }
3553
- const abs = path18.resolve(root, rel);
3554
- if (await fs14.pathExists(abs)) existing.push({ rel, abs });
3706
+ const abs = path19.resolve(root, rel);
3707
+ if (await fs15.pathExists(abs)) existing.push({ rel, abs });
3555
3708
  }
3556
3709
  return existing;
3557
3710
  }
@@ -3569,29 +3722,29 @@ async function backupTrackedPaths(root, absPaths, say, prefix) {
3569
3722
  const stamp = `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${crypto2.randomBytes(3).toString("hex")}`;
3570
3723
  const backupRoot = hausPath(root, "backups", dirBackupDirName(prefix, stamp));
3571
3724
  for (const abs of absPaths) {
3572
- if (!await fs14.pathExists(abs)) continue;
3573
- const rel = path18.relative(root, abs);
3574
- const backupPath = path18.join(backupRoot, rel);
3575
- await fs14.ensureDir(path18.dirname(backupPath));
3576
- await fs14.copy(abs, backupPath);
3725
+ if (!await fs15.pathExists(abs)) continue;
3726
+ const rel = path19.relative(root, abs);
3727
+ const backupPath = path19.join(backupRoot, rel);
3728
+ await fs15.ensureDir(path19.dirname(backupPath));
3729
+ await fs15.copy(abs, backupPath);
3577
3730
  }
3578
3731
  say(
3579
- `Backed up ${absPaths.length} path(s) to ${path18.relative(root, backupRoot)} before deleting them.`
3732
+ `Backed up ${absPaths.length} path(s) to ${path19.relative(root, backupRoot)} before deleting them.`
3580
3733
  );
3581
3734
  return backupRoot;
3582
3735
  }
3583
3736
  async function deleteTrackedPaths(root, itemId, existing, { verb, reason }, say) {
3584
3737
  for (const { abs } of existing) {
3585
- await fs14.remove(abs);
3586
- await pruneEmptyDir(path18.dirname(abs));
3738
+ await fs15.remove(abs);
3739
+ await pruneEmptyDir(path19.dirname(abs));
3587
3740
  say([verb, reason, displayPath(root, abs)].filter(Boolean).join(" ") + ` (${itemId})`);
3588
3741
  }
3589
3742
  }
3590
3743
 
3591
3744
  // src/claude/skill-dep-install.ts
3592
3745
  init_exec();
3593
- import path19 from "node:path";
3594
- import fs15 from "fs-extra";
3746
+ import path20 from "node:path";
3747
+ import fs16 from "fs-extra";
3595
3748
  function skillDepInstallCommand(manager) {
3596
3749
  switch (manager) {
3597
3750
  case "npm":
@@ -3628,7 +3781,7 @@ function skillDepInstallCommand(manager) {
3628
3781
  }
3629
3782
  async function hasDeclaredDependencies(dir) {
3630
3783
  try {
3631
- const raw = await fs15.readFile(path19.join(dir, "package.json"), "utf8");
3784
+ const raw = await fs16.readFile(path20.join(dir, "package.json"), "utf8");
3632
3785
  const parsed = JSON.parse(raw);
3633
3786
  if (typeof parsed !== "object" || parsed === null) return false;
3634
3787
  const deps = parsed.dependencies;
@@ -3640,16 +3793,16 @@ async function hasDeclaredDependencies(dir) {
3640
3793
  }
3641
3794
  async function realpathOrSelf(target) {
3642
3795
  try {
3643
- return await fs15.realpath(target);
3796
+ return await fs16.realpath(target);
3644
3797
  } catch {
3645
- return path19.resolve(target);
3798
+ return path20.resolve(target);
3646
3799
  }
3647
3800
  }
3648
3801
  async function resolveSkillInstallDir(projectRoot, skillDir) {
3649
- const skillsRoot = await realpathOrSelf(path19.join(projectRoot, ".claude", "skills"));
3802
+ const skillsRoot = await realpathOrSelf(path20.join(projectRoot, ".claude", "skills"));
3650
3803
  const resolved = await realpathOrSelf(skillDir);
3651
- const rel = path19.relative(skillsRoot, resolved);
3652
- const contained = rel !== "" && !path19.isAbsolute(rel) && !rel.split(path19.sep).includes("..") && // A directory named exactly `.claude/skills` is the container, not a skill.
3804
+ const rel = path20.relative(skillsRoot, resolved);
3805
+ const contained = rel !== "" && !path20.isAbsolute(rel) && !rel.split(path20.sep).includes("..") && // A directory named exactly `.claude/skills` is the container, not a skill.
3653
3806
  rel.length > 0;
3654
3807
  if (!contained) {
3655
3808
  return { ok: false, reason: `refusing to install outside .claude/skills (${resolved})` };
@@ -3670,8 +3823,8 @@ async function installSkillDependencies(opts, deps = {}) {
3670
3823
  if (!planned.ok) return { status: "skipped", reason: planned.reason };
3671
3824
  const { command, args } = planned.command;
3672
3825
  if (opts.dryRun) return { status: "would-install", command, args };
3673
- const exists = deps.exists ?? probeCommand;
3674
- const probe = await exists(command);
3826
+ const exists2 = deps.exists ?? probeCommand;
3827
+ const probe = await exists2(command);
3675
3828
  switch (probe.status) {
3676
3829
  case "available":
3677
3830
  break;
@@ -3685,7 +3838,7 @@ async function installSkillDependencies(opts, deps = {}) {
3685
3838
  default:
3686
3839
  return assertNeverCommandProbe(probe);
3687
3840
  }
3688
- if (!await fs15.pathExists(contained.dir)) {
3841
+ if (!await fs16.pathExists(contained.dir)) {
3689
3842
  return { status: "skipped", reason: `install directory disappeared (${contained.dir})` };
3690
3843
  }
3691
3844
  const run = deps.run ?? defaultRun;
@@ -3699,7 +3852,7 @@ async function installSkillDependencies(opts, deps = {}) {
3699
3852
  // src/claude/verify-hooks-contract.ts
3700
3853
  init_fs();
3701
3854
  init_paths();
3702
- import fs16 from "fs-extra";
3855
+ import fs17 from "fs-extra";
3703
3856
 
3704
3857
  // src/claude/load-hooks.ts
3705
3858
  var CANONICAL_HOOKS = {
@@ -3797,7 +3950,7 @@ async function assertPostApplySettingsHausContract(root) {
3797
3950
  }
3798
3951
  async function verifyProjectSettingsHooksContract(root) {
3799
3952
  const settingsPath = claudePath(root, "settings.json");
3800
- if (!await fs16.pathExists(settingsPath)) {
3953
+ if (!await fs17.pathExists(settingsPath)) {
3801
3954
  return {
3802
3955
  ok: true,
3803
3956
  skipped: true,
@@ -3825,10 +3978,10 @@ async function verifyProjectSettingsHooksContract(root) {
3825
3978
 
3826
3979
  // src/claude/write-ci-gate-workflow.ts
3827
3980
  init_logger();
3828
- import path20 from "node:path";
3829
- import fs17 from "fs-extra";
3981
+ import path21 from "node:path";
3982
+ import fs18 from "fs-extra";
3830
3983
  import YAML from "yaml";
3831
- var CI_GATE_WORKFLOW_REL = path20.join(".github", "workflows", "haus-gate.yml");
3984
+ var CI_GATE_WORKFLOW_REL = path21.join(".github", "workflows", "haus-gate.yml");
3832
3985
  var WORKFLOW = `# Written by \`haus init\`. Yours to edit \u2014 haus will not rewrite this file.
3833
3986
  #
3834
3987
  # Runs the checks haus already knows how to run, on every pull request:
@@ -3868,15 +4021,15 @@ jobs:
3868
4021
  run: haus ci-gate
3869
4022
  `;
3870
4023
  async function writeCiGateWorkflow(root, dryRun, quiet = false) {
3871
- const dest = path20.join(root, CI_GATE_WORKFLOW_REL);
3872
- if (await fs17.pathExists(dest)) return void 0;
4024
+ const dest = path21.join(root, CI_GATE_WORKFLOW_REL);
4025
+ if (await fs18.pathExists(dest)) return void 0;
3873
4026
  if (await projectRunsCiGate(root)) return void 0;
3874
4027
  if (dryRun) {
3875
4028
  if (!quiet) log(`Would write ${CI_GATE_WORKFLOW_REL} (CI gate: haus ci-gate on every PR)`);
3876
4029
  return dest;
3877
4030
  }
3878
- await fs17.ensureDir(path20.dirname(dest));
3879
- await fs17.writeFile(dest, WORKFLOW, "utf8");
4031
+ await fs18.ensureDir(path21.dirname(dest));
4032
+ await fs18.writeFile(dest, WORKFLOW, "utf8");
3880
4033
  if (!quiet) log(`Wrote ${CI_GATE_WORKFLOW_REL} \u2014 runs \`haus ci-gate\` on every pull request`);
3881
4034
  return dest;
3882
4035
  }
@@ -3895,17 +4048,17 @@ function runScripts(node, found = []) {
3895
4048
  return found;
3896
4049
  }
3897
4050
  async function projectRunsCiGate(root) {
3898
- const dir = path20.join(root, ".github", "workflows");
4051
+ const dir = path21.join(root, ".github", "workflows");
3899
4052
  let entries;
3900
4053
  try {
3901
- entries = await fs17.readdir(dir);
4054
+ entries = await fs18.readdir(dir);
3902
4055
  } catch {
3903
4056
  return false;
3904
4057
  }
3905
4058
  for (const entry of entries) {
3906
4059
  if (!/\.ya?ml$/i.test(entry)) continue;
3907
4060
  try {
3908
- const text = await fs17.readFile(path20.join(dir, entry), "utf8");
4061
+ const text = await fs18.readFile(path21.join(dir, entry), "utf8");
3909
4062
  if (runScripts(YAML.parse(text)).some((script) => CI_GATE_COMMAND.test(script))) return true;
3910
4063
  } catch {
3911
4064
  }
@@ -3914,8 +4067,8 @@ async function projectRunsCiGate(root) {
3914
4067
  }
3915
4068
 
3916
4069
  // src/claude/write-decisions-seed.ts
3917
- import path21 from "node:path";
3918
- import fs18 from "fs-extra";
4070
+ import path22 from "node:path";
4071
+ import fs19 from "fs-extra";
3919
4072
 
3920
4073
  // library/catalog/decisions-triggers.json
3921
4074
  var decisions_triggers_default = {
@@ -3958,17 +4111,17 @@ Why we chose X \u2014 short, dated, write-once decision log for humans and agent
3958
4111
  | --- | ----- | -------------- | ------ |
3959
4112
  `;
3960
4113
  async function writeDecisionsSeed(root, dryRun, quiet = false) {
3961
- const decisionsDir = path21.join(root, DECISIONS_TRIGGERS.decisionsDir);
3962
- const readmePath = path21.join(decisionsDir, "README.md");
3963
- if (await fs18.pathExists(readmePath)) return null;
3964
- if (!dryRun) await fs18.ensureDir(decisionsDir);
4114
+ const decisionsDir = path22.join(root, DECISIONS_TRIGGERS.decisionsDir);
4115
+ const readmePath = path22.join(decisionsDir, "README.md");
4116
+ if (await fs19.pathExists(readmePath)) return null;
4117
+ if (!dryRun) await fs19.ensureDir(decisionsDir);
3965
4118
  await writeManagedText(root, readmePath, SEED_README, dryRun, quiet);
3966
4119
  return readmePath;
3967
4120
  }
3968
4121
 
3969
4122
  // src/claude/write-prettierignore.ts
3970
- import path22 from "node:path";
3971
- import fs19 from "fs-extra";
4123
+ import path23 from "node:path";
4124
+ import fs20 from "fs-extra";
3972
4125
  var PRETTIERIGNORE_BEGIN = "# HAUS:BEGIN haus-managed v=1";
3973
4126
  var PRETTIERIGNORE_END = "# HAUS:END haus-managed";
3974
4127
  var IGNORED_PATHS = [".haus-workflow/", ".claude/"];
@@ -4036,16 +4189,16 @@ ${block}
4036
4189
  `;
4037
4190
  }
4038
4191
  async function writePrettierIgnore(root, dryRun, quiet = false) {
4039
- const filePath = path22.join(root, ".prettierignore");
4040
- const prev = await fs19.pathExists(filePath) ? await fs19.readFile(filePath, "utf8") : "";
4192
+ const filePath = path23.join(root, ".prettierignore");
4193
+ const prev = await fs20.pathExists(filePath) ? await fs20.readFile(filePath, "utf8") : "";
4041
4194
  const next = injectPrettierIgnoreBlock(prev, buildPrettierIgnoreBlock());
4042
4195
  await writeManagedText(root, filePath, next, dryRun, quiet);
4043
4196
  return filePath;
4044
4197
  }
4045
4198
 
4046
4199
  // src/claude/write-root-claude-md.ts
4047
- import path23 from "node:path";
4048
- import fs20 from "fs-extra";
4200
+ import path24 from "node:path";
4201
+ import fs21 from "fs-extra";
4049
4202
  var BLOCK_BEGIN = "<!-- HAUS:BEGIN haus-imports v=1 -->";
4050
4203
  var BLOCK_END = "<!-- HAUS:END haus-imports -->";
4051
4204
  var WORKSPACE_BLOCK_BEGIN = "<!-- HAUS:BEGIN haus-workspace-imports v=1 -->";
@@ -4123,9 +4276,9 @@ ${block}
4123
4276
  `;
4124
4277
  }
4125
4278
  async function writeRootClaudeMd(root, dryRun, quiet = false) {
4126
- const filePath = path23.join(root, "CLAUDE.md");
4279
+ const filePath = path24.join(root, "CLAUDE.md");
4127
4280
  const block = buildImportBlock();
4128
- const prev = await fs20.pathExists(filePath) ? await fs20.readFile(filePath, "utf8") : "";
4281
+ const prev = await fs21.pathExists(filePath) ? await fs21.readFile(filePath, "utf8") : "";
4129
4282
  const next = injectHausBlock(prev, block);
4130
4283
  await writeManagedText(root, filePath, next, dryRun, quiet);
4131
4284
  return filePath;
@@ -4135,13 +4288,13 @@ async function writeRootClaudeMd(root, dryRun, quiet = false) {
4135
4288
  init_fs();
4136
4289
  init_logger();
4137
4290
  init_paths();
4138
- import path25 from "node:path";
4139
- import fs22 from "fs-extra";
4291
+ import path26 from "node:path";
4292
+ import fs23 from "fs-extra";
4140
4293
 
4141
4294
  // src/claude/derive-workflow-config.ts
4142
4295
  init_fs();
4143
- import path24 from "node:path";
4144
- import fs21 from "fs-extra";
4296
+ import path25 from "node:path";
4297
+ import fs22 from "fs-extra";
4145
4298
  function binCmd(pm, bin, args) {
4146
4299
  const tail = args ? ` ${args}` : "";
4147
4300
  if (pm === "yarn") return `yarn ${bin}${tail}`;
@@ -4150,7 +4303,7 @@ function binCmd(pm, bin, args) {
4150
4303
  }
4151
4304
  async function deriveWorkflowConfig(root, ctx) {
4152
4305
  const pm = ctx.packageManager === "unknown" ? "npm" : ctx.packageManager;
4153
- const pkg = await readJson(path24.join(root, "package.json"));
4306
+ const pkg = await readJson(path25.join(root, "package.json"));
4154
4307
  const scripts = pkg?.scripts ?? {};
4155
4308
  const deps = new Set(ctx.dependencies);
4156
4309
  const stacks = Object.values(ctx.detectedStacks ?? {}).flat();
@@ -4160,17 +4313,17 @@ async function deriveWorkflowConfig(root, ctx) {
4160
4313
  return null;
4161
4314
  };
4162
4315
  const hasDep = (name) => deps.has(name);
4163
- const exists = (rel) => fs21.pathExistsSync(path24.join(root, rel));
4316
+ const exists2 = (rel) => fs22.pathExistsSync(path25.join(root, rel));
4164
4317
  const hasPlaywright = hasDep("@playwright/test") || stacks.includes("playwright");
4165
4318
  const hasCypress = hasDep("cypress");
4166
- const preCommitTool = exists("lefthook.yml") || exists("lefthook.yaml") ? "lefthook" : exists(".husky") || hasDep("husky") || (scripts.prepare ?? "").includes("husky") ? "husky" : exists(".pre-commit-config.yaml") ? "pre-commit (Python framework)" : null;
4319
+ const preCommitTool = exists2("lefthook.yml") || exists2("lefthook.yaml") ? "lefthook" : exists2(".husky") || hasDep("husky") || (scripts.prepare ?? "").includes("husky") ? "husky" : exists2(".pre-commit-config.yaml") ? "pre-commit (Python framework)" : null;
4167
4320
  return {
4168
4321
  test: script("test") ?? `${pm} test`,
4169
4322
  testE2E: firstScript("test:e2e", "e2e", "test:integration") ?? (hasPlaywright ? binCmd(pm, "playwright", "test") : null) ?? (hasCypress ? binCmd(pm, "cypress", "run") : null),
4170
4323
  preCommitTool,
4171
- specPath: exists("docs/SPEC.md") ? "docs/SPEC.md" : null,
4172
- designPath: exists("docs/DESIGN.md") ? "docs/DESIGN.md" : null,
4173
- uxPath: exists("docs/UX.md") ? "docs/UX.md" : null
4324
+ specPath: exists2("docs/SPEC.md") ? "docs/SPEC.md" : null,
4325
+ designPath: exists2("docs/DESIGN.md") ? "docs/DESIGN.md" : null,
4326
+ uxPath: exists2("docs/UX.md") ? "docs/UX.md" : null
4174
4327
  };
4175
4328
  }
4176
4329
 
@@ -4229,19 +4382,19 @@ async function writeWorkflowConfig(root, dryRun, opts = {}) {
4229
4382
  } : log;
4230
4383
  const destPath = hausPath(root, "workflow-config.md");
4231
4384
  const printable = displayPath(root, destPath);
4232
- const exists = await fs22.pathExists(destPath);
4233
- if (exists && !opts.refill) {
4385
+ const exists2 = await fs23.pathExists(destPath);
4386
+ if (exists2 && !opts.refill) {
4234
4387
  if (dryRun) say(printable + ": exists (project-owned, skipping)");
4235
4388
  return null;
4236
4389
  }
4237
4390
  const ctx = await readJson(hausPath(root, "context-map.json")) ?? {
4238
4391
  ...FALLBACK_CONTEXT,
4239
4392
  root,
4240
- repoName: path25.basename(root)
4393
+ repoName: path26.basename(root)
4241
4394
  };
4242
4395
  const values = await deriveWorkflowConfig(root, ctx);
4243
- if (exists) {
4244
- const current = await fs22.readFile(destPath, "utf8");
4396
+ if (exists2) {
4397
+ const current = await fs23.readFile(destPath, "utf8");
4245
4398
  const refilled = refillContent(current, values);
4246
4399
  if (refilled === current) {
4247
4400
  if (dryRun) say(printable + ": no blank fields to refill");
@@ -4264,7 +4417,7 @@ async function writeWorkflowConfig(root, dryRun, opts = {}) {
4264
4417
 
4265
4418
  // src/claude/write-workflow.ts
4266
4419
  init_offline();
4267
- import fs23 from "fs-extra";
4420
+ import fs24 from "fs-extra";
4268
4421
  init_fs();
4269
4422
  init_logger();
4270
4423
  init_paths();
@@ -4306,8 +4459,8 @@ async function writeWorkflow(root, pkgVersion, dryRun, force = false, quiet = fa
4306
4459
  ${templateContent}`;
4307
4460
  const destPath = hausPath(root, "WORKFLOW.md");
4308
4461
  const printable = displayPath(root, destPath);
4309
- if (await fs23.pathExists(destPath)) {
4310
- const existing = await fs23.readFile(destPath, "utf8");
4462
+ if (await fs24.pathExists(destPath)) {
4463
+ const existing = await fs24.readFile(destPath, "utf8");
4311
4464
  const firstLine = existing.split("\n")[0] ?? "";
4312
4465
  const parsed = parseHausManagedHeader(firstLine);
4313
4466
  if (!parsed) {
@@ -4351,7 +4504,7 @@ ${existingContent}`;
4351
4504
  }
4352
4505
  }
4353
4506
  if (dryRun) {
4354
- const prev = await fs23.pathExists(destPath) ? await fs23.readFile(destPath, "utf8") : "";
4507
+ const prev = await fs24.pathExists(destPath) ? await fs24.readFile(destPath, "utf8") : "";
4355
4508
  if (!prev) {
4356
4509
  say(createUnifiedDiff(printable, "", next));
4357
4510
  } else {
@@ -4406,14 +4559,14 @@ function decodeIfText(buf) {
4406
4559
  return Buffer.from(asText, "utf8").equals(buf) ? asText : null;
4407
4560
  }
4408
4561
  async function listFilesRecursive2(dir) {
4409
- if (!await fs24.pathExists(dir)) return [];
4562
+ if (!await fs25.pathExists(dir)) return [];
4410
4563
  const files = [];
4411
4564
  const walk = async (current, relPrefix) => {
4412
- const names = await fs24.readdir(current);
4565
+ const names = await fs25.readdir(current);
4413
4566
  for (const name of names) {
4414
- const abs = path26.join(current, name);
4567
+ const abs = path27.join(current, name);
4415
4568
  const rel = relPrefix ? `${relPrefix}/${name}` : name;
4416
- const stat = await fs24.lstat(abs);
4569
+ const stat = await fs25.lstat(abs);
4417
4570
  if (stat.isSymbolicLink()) {
4418
4571
  warn(`Skipping symlink in dry-run diff preview: ${rel}`);
4419
4572
  continue;
@@ -4430,14 +4583,14 @@ async function listFilesRecursive2(dir) {
4430
4583
  }
4431
4584
  async function logFileDryRunDiff(root, srcFile, destFile, itemId, say, transformed) {
4432
4585
  const printable = displayPath(root, destFile);
4433
- const nextBuf = transformed ?? await fs24.readFile(srcFile);
4434
- if (!await fs24.pathExists(destFile)) {
4586
+ const nextBuf = transformed ?? await fs25.readFile(srcFile);
4587
+ if (!await fs25.pathExists(destFile)) {
4435
4588
  say(`${printable}: would create (${itemId})`);
4436
4589
  const nextText2 = decodeIfText(nextBuf);
4437
4590
  if (nextText2 !== null) say(createUnifiedDiff(printable, "", nextText2));
4438
4591
  return;
4439
4592
  }
4440
- const prevBuf = await fs24.readFile(destFile);
4593
+ const prevBuf = await fs25.readFile(destFile);
4441
4594
  if (prevBuf.equals(nextBuf)) {
4442
4595
  say(`${printable}: unchanged (${itemId})`);
4443
4596
  return;
@@ -4453,28 +4606,28 @@ async function logSkillDryRunDiff(root, sourcePath, destination, itemId, say, in
4453
4606
  const sourceFiles = await listFilesRecursive2(sourcePath);
4454
4607
  const destFiles = await listFilesRecursive2(destination);
4455
4608
  for (const rel of sourceFiles) {
4456
- const src = path26.join(sourcePath, rel);
4609
+ const src = path27.join(sourcePath, rel);
4457
4610
  const transformed = rel === "SKILL.md" ? await previewInstalledSkill(src, install) : void 0;
4458
- await logFileDryRunDiff(root, src, path26.join(destination, rel), itemId, say, transformed);
4611
+ await logFileDryRunDiff(root, src, path27.join(destination, rel), itemId, say, transformed);
4459
4612
  }
4460
4613
  const sourceFileSet = new Set(sourceFiles);
4461
4614
  for (const rel of destFiles) {
4462
4615
  if (!sourceFileSet.has(rel)) {
4463
- say(`${displayPath(root, path26.join(destination, rel))}: would remove (${itemId})`);
4616
+ say(`${displayPath(root, path27.join(destination, rel))}: would remove (${itemId})`);
4464
4617
  }
4465
4618
  }
4466
4619
  }
4467
4620
  async function removeLegacyManagedStub(root, relPathSegments, stub, dryRun, say) {
4468
4621
  const target = claudePath(root, ...relPathSegments);
4469
- if (!await fs24.pathExists(target)) return;
4470
- const content2 = await fs24.readFile(target, "utf8");
4622
+ if (!await fs25.pathExists(target)) return;
4623
+ const content2 = await fs25.readFile(target, "utf8");
4471
4624
  if (content2 !== stub && content2 !== `${stub}
4472
4625
  ` && content2 !== `${stub}\r
4473
4626
  `) return;
4474
4627
  if (dryRun) {
4475
4628
  say(`[dry-run] would remove stale ${displayPath(root, target)}`);
4476
4629
  } else {
4477
- await fs24.remove(target);
4630
+ await fs25.remove(target);
4478
4631
  }
4479
4632
  }
4480
4633
  async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
@@ -4492,7 +4645,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
4492
4645
  estimatedTokenReductionPct: 0
4493
4646
  };
4494
4647
  const pkgRoot = packageRoot();
4495
- const hausVersion2 = (await readJson(path26.join(pkgRoot, "package.json")))?.version ?? "0.0.0";
4648
+ const hausVersion2 = (await readJson(path27.join(pkgRoot, "package.json")))?.version ?? "0.0.0";
4496
4649
  const { items: manifestItems, contentRoot } = await loadCatalogContext(root);
4497
4650
  const coreFiles = [claudePath(root, "settings.json"), claudePath(root, "rules", "haus.md")];
4498
4651
  const rootClaudeMdPath = await writeRootClaudeMd(root, dryRun, opts.quiet);
@@ -4582,11 +4735,11 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
4582
4735
  ];
4583
4736
  for (const rel of LEGACY_PRUNED_ARTIFACTS) {
4584
4737
  const legacyPath = hausPath(root, rel);
4585
- if (await fs24.pathExists(legacyPath)) {
4738
+ if (await fs25.pathExists(legacyPath)) {
4586
4739
  if (dryRun) {
4587
4740
  say(`[dry-run] would remove stale ${displayPath(root, legacyPath)}`);
4588
4741
  } else {
4589
- await fs24.remove(legacyPath);
4742
+ await fs25.remove(legacyPath);
4590
4743
  }
4591
4744
  }
4592
4745
  }
@@ -4650,7 +4803,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
4650
4803
  let curatedReviewStatusSkips = 0;
4651
4804
  const projectPackageManager = detectPackageManager(
4652
4805
  root,
4653
- (await readJson(path26.join(root, "package.json")))?.packageManager
4806
+ (await readJson(path27.join(root, "package.json")))?.packageManager
4654
4807
  );
4655
4808
  for (const item of catalogItems) {
4656
4809
  const manifestItem = manifestById.get(item.id);
@@ -4704,7 +4857,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
4704
4857
  }
4705
4858
  const sourcePath = catalogItemContentPath(contentRoot, manifestItem);
4706
4859
  const destination = placement.destination;
4707
- if (await fs24.pathExists(sourcePath)) {
4860
+ if (await fs25.pathExists(sourcePath)) {
4708
4861
  if (dryRun) {
4709
4862
  if (item.type === "skill") {
4710
4863
  await logSkillDryRunDiff(root, sourcePath, destination, item.id, say, {
@@ -4735,13 +4888,13 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
4735
4888
  )) {
4736
4889
  continue;
4737
4890
  } else if (item.type === "skill") {
4738
- const skillFiles = (await fs24.readdir(sourcePath, { recursive: true })).filter(
4891
+ const skillFiles = (await fs25.readdir(sourcePath, { recursive: true })).filter(
4739
4892
  (f) => typeof f === "string" && f.endsWith(".md")
4740
4893
  );
4741
4894
  let skillValid = true;
4742
4895
  for (const relFile of skillFiles) {
4743
- const mdPath = path26.join(sourcePath, relFile);
4744
- const mdContent = await fs24.readFile(mdPath, "utf8");
4896
+ const mdPath = path27.join(sourcePath, relFile);
4897
+ const mdContent = await fs25.readFile(mdPath, "utf8");
4745
4898
  const mdValidation = validateCatalogItem(
4746
4899
  {
4747
4900
  id: manifestItem.id,
@@ -4775,7 +4928,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
4775
4928
  say
4776
4929
  );
4777
4930
  } else {
4778
- const fileContent = await fs24.readFile(sourcePath, "utf8");
4931
+ const fileContent = await fs25.readFile(sourcePath, "utf8");
4779
4932
  const validation = validateCatalogItem(
4780
4933
  {
4781
4934
  id: manifestItem.id,
@@ -4789,11 +4942,11 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
4789
4942
  warn(`Skipping ${item.id}: pre-copy validation failed \u2014 ${validation.reason}`);
4790
4943
  continue;
4791
4944
  }
4792
- await fs24.ensureDir(path26.dirname(destination));
4793
- await fs24.copy(sourcePath, destination, { overwrite: true, errorOnExist: false });
4945
+ await fs25.ensureDir(path27.dirname(destination));
4946
+ await fs25.copy(sourcePath, destination, { overwrite: true, errorOnExist: false });
4794
4947
  }
4795
4948
  files.push(destination);
4796
- const relPaths = [path26.relative(root, destination)];
4949
+ const relPaths = [path27.relative(root, destination)];
4797
4950
  const current = installedPathsByItem.get(item.id) ?? [];
4798
4951
  installedPathsByItem.set(item.id, [...current, ...relPaths]);
4799
4952
  installedIds.add(item.id);
@@ -4916,7 +5069,7 @@ async function hasProtectedUnsafePath(root, relPaths, unsafeByRel) {
4916
5069
  for (const rel of relPaths) {
4917
5070
  const unsafe = unsafeByRel.get(rel) ?? null;
4918
5071
  if (unsafe === "symlink") return true;
4919
- if (unsafe === "escapes-root" && await fs24.pathExists(path26.resolve(root, rel))) return true;
5072
+ if (unsafe === "escapes-root" && await fs25.pathExists(path27.resolve(root, rel))) return true;
4920
5073
  }
4921
5074
  return false;
4922
5075
  }
@@ -4981,8 +5134,8 @@ async function cleanupMigratedCatalogItems(root, prevLock, migrations, dryRun, q
4981
5134
  void await backupTrackedPaths(root, allAbsPaths, say, "migrate");
4982
5135
  for (const { existing } of toRemove) {
4983
5136
  for (const { abs } of existing) {
4984
- await fs24.remove(abs);
4985
- await pruneEmptyDir(path26.dirname(abs));
5137
+ await fs25.remove(abs);
5138
+ await pruneEmptyDir(path27.dirname(abs));
4986
5139
  }
4987
5140
  }
4988
5141
  }
@@ -5040,10 +5193,10 @@ async function cleanupStaleCatalogItems(root, manifestById, dryRun, quiet, activ
5040
5193
  continue;
5041
5194
  }
5042
5195
  const removable = superseded ? existing.filter(({ rel }) => {
5043
- const isShared = rel.split(path26.sep).join("/") === LEGACY_SUPERPOWERS_SHARED_REL_PATH;
5196
+ const isShared = rel.split(path27.sep).join("/") === LEGACY_SUPERPOWERS_SHARED_REL_PATH;
5044
5197
  if (isShared && keepShared) {
5045
5198
  say(
5046
- `Keeping ${displayPath(root, path26.join(root, rel))} \u2014 a locally-edited superpowers skill still uses it`
5199
+ `Keeping ${displayPath(root, path27.join(root, rel))} \u2014 a locally-edited superpowers skill still uses it`
5047
5200
  );
5048
5201
  return false;
5049
5202
  }
@@ -5062,8 +5215,8 @@ async function cleanupStaleCatalogItems(root, manifestById, dryRun, quiet, activ
5062
5215
  say(`[dry-run] would remove ${pruneReason} ${displayPath(root, abs)} (${entry.id})`);
5063
5216
  continue;
5064
5217
  }
5065
- await fs24.remove(abs);
5066
- await pruneEmptyDir(path26.dirname(abs));
5218
+ await fs25.remove(abs);
5219
+ await pruneEmptyDir(path27.dirname(abs));
5067
5220
  say(`Removed ${pruneReason} ${displayPath(root, abs)} (${entry.id})`);
5068
5221
  }
5069
5222
  }
@@ -5117,8 +5270,8 @@ async function pruneOrphanedCatalogItems(root, prevLock, notOrphanedIds, dryRun,
5117
5270
 
5118
5271
  // src/claude/write-gitignore.ts
5119
5272
  init_exec();
5120
- import path27 from "node:path";
5121
- import fs25 from "fs-extra";
5273
+ import path28 from "node:path";
5274
+ import fs26 from "fs-extra";
5122
5275
  var GITIGNORE_BEGIN = "# HAUS:BEGIN haus-managed v=1";
5123
5276
  var GITIGNORE_END = "# HAUS:END haus-managed";
5124
5277
  var GITIGNORED_ARTIFACT_PATHS = [
@@ -5131,7 +5284,15 @@ var GITIGNORED_ARTIFACT_PATHS = [
5131
5284
  // Hydration locks and the detached hydration log/last-run record (#419): live
5132
5285
  // process state and machine-local diagnostics, meaningless in another checkout.
5133
5286
  ".haus-workflow/locks/",
5134
- ".haus-workflow/logs/"
5287
+ ".haus-workflow/logs/",
5288
+ // The request pipeline's archive (#472, ADR-0103): per-request pricing material —
5289
+ // specs, impact analyses, scores — written by the request skill, not this CLI. It
5290
+ // lived at `.haus/requests/` behind a hand-added ignore line OUTSIDE this block,
5291
+ // so no client repo ever received the rule; `migrate-request-state.ts` moves it
5292
+ // here precisely so this block can own it. Note `.haus-workflow/request.json`
5293
+ // (the Asana board binding) is deliberately absent: it is shared config, not
5294
+ // machine-local state, and stays tracked like `workflow-config.md`.
5295
+ ".haus-workflow/requests/"
5135
5296
  ];
5136
5297
  function findLineMarker3(content2, marker, from = 0) {
5137
5298
  let idx = content2.indexOf(marker, from);
@@ -5238,15 +5399,15 @@ function injectLinkContextGitignoreBlock(existing) {
5238
5399
  );
5239
5400
  }
5240
5401
  async function writeGitignore(root, dryRun, quiet = false) {
5241
- const filePath = path27.join(root, ".gitignore");
5242
- const prev = await fs25.pathExists(filePath) ? await fs25.readFile(filePath, "utf8") : "";
5402
+ const filePath = path28.join(root, ".gitignore");
5403
+ const prev = await fs26.pathExists(filePath) ? await fs26.readFile(filePath, "utf8") : "";
5243
5404
  const next = injectGitignoreBlock(prev);
5244
5405
  await writeManagedText(root, filePath, next, dryRun, quiet);
5245
5406
  return filePath;
5246
5407
  }
5247
5408
  async function writeLinkContextGitignore(root, dryRun, quiet = false) {
5248
- const filePath = path27.join(root, ".gitignore");
5249
- const prev = await fs25.pathExists(filePath) ? await fs25.readFile(filePath, "utf8") : "";
5409
+ const filePath = path28.join(root, ".gitignore");
5410
+ const prev = await fs26.pathExists(filePath) ? await fs26.readFile(filePath, "utf8") : "";
5250
5411
  const next = injectLinkContextGitignoreBlock(prev);
5251
5412
  await writeManagedText(root, filePath, next, dryRun, quiet);
5252
5413
  return filePath;
@@ -5287,7 +5448,7 @@ async function untrackMachineLocalArtifacts(root, dryRun, notify) {
5287
5448
  }
5288
5449
 
5289
5450
  // src/install/ensure-global-plugins.ts
5290
- import path28 from "node:path";
5451
+ import path29 from "node:path";
5291
5452
  init_offline();
5292
5453
  init_exec();
5293
5454
  init_fs();
@@ -5526,7 +5687,7 @@ function parseInstalled(stdout2) {
5526
5687
  }
5527
5688
  }
5528
5689
  async function readMarketplaceRefs(configDir) {
5529
- const settings = await readJson(path28.join(configDir, "settings.json"));
5690
+ const settings = await readJson(path29.join(configDir, "settings.json"));
5530
5691
  const out = {};
5531
5692
  for (const [name, entry] of Object.entries(settings?.extraKnownMarketplaces ?? {})) {
5532
5693
  const ref = entry?.source?.ref;
@@ -5536,7 +5697,7 @@ async function readMarketplaceRefs(configDir) {
5536
5697
  }
5537
5698
  async function readClonedPluginVersion(installLocation) {
5538
5699
  const manifest = await readJson(
5539
- path28.join(installLocation, ".claude-plugin", "plugin.json")
5700
+ path29.join(installLocation, ".claude-plugin", "plugin.json")
5540
5701
  );
5541
5702
  return typeof manifest?.version === "string" ? manifest.version : void 0;
5542
5703
  }
@@ -5814,26 +5975,26 @@ async function cleanupStaleGlobalPlugins(desired, deps = {}) {
5814
5975
  }
5815
5976
 
5816
5977
  // src/refs/fetch-refs.ts
5817
- import path30 from "node:path";
5818
- import fs27 from "fs-extra";
5978
+ import path31 from "node:path";
5979
+ import fs28 from "fs-extra";
5819
5980
 
5820
5981
  // src/refs/cache-meta.ts
5821
- import path29 from "node:path";
5822
- import fs26 from "fs-extra";
5982
+ import path30 from "node:path";
5983
+ import fs27 from "fs-extra";
5823
5984
  var META_FILENAME = "cache-meta.json";
5824
5985
  async function readCacheMeta(cacheDir) {
5825
- const metaPath = path29.join(cacheDir, META_FILENAME);
5986
+ const metaPath = path30.join(cacheDir, META_FILENAME);
5826
5987
  try {
5827
- const raw = await fs26.readFile(metaPath, "utf8");
5988
+ const raw = await fs27.readFile(metaPath, "utf8");
5828
5989
  return JSON.parse(raw);
5829
5990
  } catch {
5830
5991
  return {};
5831
5992
  }
5832
5993
  }
5833
5994
  async function writeCacheMeta(cacheDir, meta) {
5834
- await fs26.ensureDir(cacheDir);
5835
- const metaPath = path29.join(cacheDir, META_FILENAME);
5836
- await fs26.writeFile(metaPath, JSON.stringify(meta, null, 2) + "\n", "utf8");
5995
+ await fs27.ensureDir(cacheDir);
5996
+ const metaPath = path30.join(cacheDir, META_FILENAME);
5997
+ await fs27.writeFile(metaPath, JSON.stringify(meta, null, 2) + "\n", "utf8");
5837
5998
  }
5838
5999
 
5839
6000
  // src/refs/fetch-refs.ts
@@ -5853,8 +6014,8 @@ function isLlmsTxtUrl(ref) {
5853
6014
  async function fetchSingleRef(url, cacheDir, meta) {
5854
6015
  const existing = meta[url];
5855
6016
  const headers = {};
5856
- const safeFile = existing?.file && existing.file === path30.basename(existing.file);
5857
- const cachedFileExists = safeFile && await fs27.pathExists(path30.join(cacheDir, existing.file));
6017
+ const safeFile = existing?.file && existing.file === path31.basename(existing.file);
6018
+ const cachedFileExists = safeFile && await fs28.pathExists(path31.join(cacheDir, existing.file));
5858
6019
  if (cachedFileExists) {
5859
6020
  if (existing?.etag) headers["If-None-Match"] = existing.etag;
5860
6021
  if (existing?.lastModified) headers["If-Modified-Since"] = existing.lastModified;
@@ -5865,8 +6026,8 @@ async function fetchSingleRef(url, cacheDir, meta) {
5865
6026
  if (!res.ok) return { result: "failed" };
5866
6027
  const text = await res.text();
5867
6028
  const file = `${urlToSlug(url)}.md`;
5868
- await fs27.ensureDir(cacheDir);
5869
- await fs27.writeFile(path30.join(cacheDir, file), text, "utf8");
6029
+ await fs28.ensureDir(cacheDir);
6030
+ await fs28.writeFile(path31.join(cacheDir, file), text, "utf8");
5870
6031
  meta[url] = {
5871
6032
  url,
5872
6033
  etag: res.headers.get("etag") ?? void 0,
@@ -5889,8 +6050,8 @@ async function pruneOrphanedRefs(cacheDir, keepUrls) {
5889
6050
  if (keepUrls.has(url)) continue;
5890
6051
  delete meta[url];
5891
6052
  removed++;
5892
- const safeFile = typeof entry.file === "string" && entry.file === path30.basename(entry.file);
5893
- if (safeFile) await fs27.remove(path30.join(cacheDir, entry.file)).catch(() => {
6053
+ const safeFile = typeof entry.file === "string" && entry.file === path31.basename(entry.file);
6054
+ if (safeFile) await fs28.remove(path31.join(cacheDir, entry.file)).catch(() => {
5894
6055
  });
5895
6056
  }
5896
6057
  if (removed > 0) await writeCacheMeta(cacheDir, meta);
@@ -5912,10 +6073,10 @@ async function fetchRefsForItems(items, cacheDir) {
5912
6073
  const outcome = await fetchSingleRef(url, cacheDir, meta);
5913
6074
  if (outcome.result === "fetched") {
5914
6075
  summary.fetched++;
5915
- summary.cachedFiles[url] = path30.join(cacheDir, outcome.file);
6076
+ summary.cachedFiles[url] = path31.join(cacheDir, outcome.file);
5916
6077
  } else if (outcome.result === "unchanged") {
5917
6078
  summary.unchanged++;
5918
- if (meta[url]?.file) summary.cachedFiles[url] = path30.join(cacheDir, meta[url].file);
6079
+ if (meta[url]?.file) summary.cachedFiles[url] = path31.join(cacheDir, meta[url].file);
5919
6080
  } else {
5920
6081
  summary.failed++;
5921
6082
  summary.failedUrls.push(url);
@@ -6097,6 +6258,7 @@ async function runApply(options) {
6097
6258
  // Copies a global plugin already provides are removed here (issue #272).
6098
6259
  activePluginIds: await activeGlobalPluginIds(root)
6099
6260
  });
6261
+ await migrateRequestState(root, isDryRun, warn);
6100
6262
  await writeGitignore(root, isDryRun);
6101
6263
  await untrackMachineLocalArtifacts(root, isDryRun, warn);
6102
6264
  if (!isDryRun) {
@@ -6163,8 +6325,8 @@ async function runApply(options) {
6163
6325
  }
6164
6326
 
6165
6327
  // src/commands/backups.ts
6166
- import path31 from "node:path";
6167
- import fs28 from "fs-extra";
6328
+ import path32 from "node:path";
6329
+ import fs29 from "fs-extra";
6168
6330
  init_logger();
6169
6331
  init_paths();
6170
6332
 
@@ -6231,14 +6393,14 @@ Enter a number (1-${choices.length})`, io);
6231
6393
  // src/commands/backups.ts
6232
6394
  async function listBackups(root) {
6233
6395
  const dir = hausPath(root, "backups");
6234
- if (!await fs28.pathExists(dir)) return [];
6235
- const names = await fs28.readdir(dir);
6396
+ if (!await fs29.pathExists(dir)) return [];
6397
+ const names = await fs29.readdir(dir);
6236
6398
  const entries = [];
6237
6399
  for (const name of names) {
6238
6400
  const kind = classifyBackupName(name);
6239
6401
  if (!kind) continue;
6240
- const absPath = path31.join(dir, name);
6241
- const stat = await fs28.lstat(absPath);
6402
+ const absPath = path32.join(dir, name);
6403
+ const stat = await fs29.lstat(absPath);
6242
6404
  if (stat.isSymbolicLink()) continue;
6243
6405
  const expectDirectory = kind !== "lock";
6244
6406
  if (stat.isDirectory() !== expectDirectory) continue;
@@ -6277,13 +6439,13 @@ async function runList(root, json) {
6277
6439
  }
6278
6440
  }
6279
6441
  async function isUnsafeWriteTarget(root, target) {
6280
- const segments = path31.relative(root, target).split(path31.sep).filter(Boolean);
6442
+ const segments = path32.relative(root, target).split(path32.sep).filter(Boolean);
6281
6443
  let current = root;
6282
6444
  for (let i = 0; i < segments.length; i++) {
6283
- current = path31.join(current, segments[i]);
6445
+ current = path32.join(current, segments[i]);
6284
6446
  const isLastSegment = i === segments.length - 1;
6285
6447
  try {
6286
- const stat = await fs28.lstat(current);
6448
+ const stat = await fs29.lstat(current);
6287
6449
  if (isLastSegment) {
6288
6450
  if (!stat.isFile()) return true;
6289
6451
  } else if (!stat.isDirectory()) {
@@ -6298,7 +6460,7 @@ async function isUnsafeWriteTarget(root, target) {
6298
6460
  }
6299
6461
  async function lstatIfRegularFile(abs) {
6300
6462
  try {
6301
- const stat = await fs28.lstat(abs);
6463
+ const stat = await fs29.lstat(abs);
6302
6464
  return stat.isFile() ? stat : null;
6303
6465
  } catch (err) {
6304
6466
  if (err.code === "ENOENT") return null;
@@ -6313,8 +6475,8 @@ async function restoreLockBackup(root, entry, yes, json, dryRun) {
6313
6475
  );
6314
6476
  return { ok: false, restored: 0 };
6315
6477
  }
6316
- if (await fs28.pathExists(target)) {
6317
- const targetStat = await fs28.stat(target);
6478
+ if (await fs29.pathExists(target)) {
6479
+ const targetStat = await fs29.stat(target);
6318
6480
  if (targetStat.mtimeMs > entry.mtimeMs) {
6319
6481
  warn(
6320
6482
  `Current haus.lock.json (${new Date(targetStat.mtimeMs).toISOString()}) is newer than backup ${entry.id} (${new Date(entry.mtimeMs).toISOString()}) \u2014 it will be overwritten.`
@@ -6348,7 +6510,7 @@ async function restoreLockBackup(root, entry, yes, json, dryRun) {
6348
6510
  error(`Refusing to restore: ${entry.id} or ${target} changed since being checked.`);
6349
6511
  return { ok: false, restored: 0 };
6350
6512
  }
6351
- await fs28.copy(entry.absPath, target, { overwrite: true });
6513
+ await fs29.copy(entry.absPath, target, { overwrite: true });
6352
6514
  if (!json) log(`Restored .haus-workflow/haus.lock.json from ${entry.id}.`);
6353
6515
  return { ok: true, restored: 1 };
6354
6516
  }
@@ -6356,10 +6518,10 @@ async function collectFilesRecursive(dir) {
6356
6518
  const files = [];
6357
6519
  const skippedLinks = [];
6358
6520
  const skippedOther = [];
6359
- const names = await fs28.readdir(dir);
6521
+ const names = await fs29.readdir(dir);
6360
6522
  for (const name of names) {
6361
- const abs = path31.join(dir, name);
6362
- const stat = await fs28.lstat(abs);
6523
+ const abs = path32.join(dir, name);
6524
+ const stat = await fs29.lstat(abs);
6363
6525
  if (stat.isSymbolicLink()) {
6364
6526
  skippedLinks.push(abs);
6365
6527
  continue;
@@ -6381,12 +6543,12 @@ async function restoreDirBackup(root, entry, yes, json, dryRun) {
6381
6543
  const { files, skippedLinks, skippedOther } = await collectFilesRecursive(entry.absPath);
6382
6544
  if (skippedLinks.length > 0) {
6383
6545
  warn(
6384
- `Skipped ${skippedLinks.length} symlink(s) inside backup ${entry.id} \u2014 never followed: ${skippedLinks.map((abs) => path31.relative(entry.absPath, abs)).join(", ")}`
6546
+ `Skipped ${skippedLinks.length} symlink(s) inside backup ${entry.id} \u2014 never followed: ${skippedLinks.map((abs) => path32.relative(entry.absPath, abs)).join(", ")}`
6385
6547
  );
6386
6548
  }
6387
6549
  if (skippedOther.length > 0) {
6388
6550
  warn(
6389
- `Skipped ${skippedOther.length} non-regular file(s) inside backup ${entry.id} (not restorable): ${skippedOther.map((abs) => path31.relative(entry.absPath, abs)).join(", ")}`
6551
+ `Skipped ${skippedOther.length} non-regular file(s) inside backup ${entry.id} (not restorable): ${skippedOther.map((abs) => path32.relative(entry.absPath, abs)).join(", ")}`
6390
6552
  );
6391
6553
  }
6392
6554
  if (files.length === 0) {
@@ -6396,8 +6558,8 @@ async function restoreDirBackup(root, entry, yes, json, dryRun) {
6396
6558
  const safeFiles = [];
6397
6559
  const unsafeTargets = [];
6398
6560
  for (const abs of files) {
6399
- const rel = path31.relative(entry.absPath, abs);
6400
- const target = path31.join(root, rel);
6561
+ const rel = path32.relative(entry.absPath, abs);
6562
+ const target = path32.join(root, rel);
6401
6563
  if (await isUnsafeWriteTarget(root, target)) unsafeTargets.push(rel);
6402
6564
  else safeFiles.push(abs);
6403
6565
  }
@@ -6414,16 +6576,16 @@ async function restoreDirBackup(root, entry, yes, json, dryRun) {
6414
6576
  const vanished = [];
6415
6577
  const toRestore = [];
6416
6578
  for (const abs of safeFiles) {
6417
- const rel = path31.relative(entry.absPath, abs);
6579
+ const rel = path32.relative(entry.absPath, abs);
6418
6580
  const backupFileStat = await lstatIfRegularFile(abs);
6419
6581
  if (!backupFileStat) {
6420
6582
  vanished.push(rel);
6421
6583
  continue;
6422
6584
  }
6423
6585
  toRestore.push(abs);
6424
- const target = path31.join(root, rel);
6425
- if (await fs28.pathExists(target)) {
6426
- const targetStat = await fs28.stat(target);
6586
+ const target = path32.join(root, rel);
6587
+ if (await fs29.pathExists(target)) {
6588
+ const targetStat = await fs29.stat(target);
6427
6589
  if (targetStat.mtimeMs > backupFileStat.mtimeMs) {
6428
6590
  stale.push({
6429
6591
  rel,
@@ -6451,7 +6613,7 @@ async function restoreDirBackup(root, entry, yes, json, dryRun) {
6451
6613
  );
6452
6614
  }
6453
6615
  if (dryRun) {
6454
- const rels = toRestore.map((abs) => path31.relative(entry.absPath, abs));
6616
+ const rels = toRestore.map((abs) => path32.relative(entry.absPath, abs));
6455
6617
  if (!json) {
6456
6618
  log(`[dry-run] Would restore ${rels.length} file(s) from ${entry.id}:`);
6457
6619
  for (const rel of rels) log(` ${rel}`);
@@ -6477,14 +6639,14 @@ async function restoreDirBackup(root, entry, yes, json, dryRun) {
6477
6639
  let restoredCount = 0;
6478
6640
  const skippedAtWrite = [];
6479
6641
  for (const abs of toRestore) {
6480
- const rel = path31.relative(entry.absPath, abs);
6481
- const target = path31.join(root, rel);
6642
+ const rel = path32.relative(entry.absPath, abs);
6643
+ const target = path32.join(root, rel);
6482
6644
  if (!await lstatIfRegularFile(abs) || await isUnsafeWriteTarget(root, target)) {
6483
6645
  skippedAtWrite.push(rel);
6484
6646
  continue;
6485
6647
  }
6486
- await fs28.ensureDir(path31.dirname(target));
6487
- await fs28.copy(abs, target, { overwrite: true });
6648
+ await fs29.ensureDir(path32.dirname(target));
6649
+ await fs29.copy(abs, target, { overwrite: true });
6488
6650
  restoredCount++;
6489
6651
  }
6490
6652
  if (skippedAtWrite.length > 0) {
@@ -6565,7 +6727,7 @@ Keeping ${keepCount}.`
6565
6727
  const failed = [];
6566
6728
  for (const entry of removeList) {
6567
6729
  try {
6568
- await fs28.remove(entry.absPath);
6730
+ await fs29.remove(entry.absPath);
6569
6731
  removed.push(entry.id);
6570
6732
  } catch (err) {
6571
6733
  failed.push({ id: entry.id, message: err instanceof Error ? err.message : String(err) });
@@ -6678,8 +6840,8 @@ init_ingest_catalog();
6678
6840
  init_manifest_format();
6679
6841
  init_path_safety();
6680
6842
  import os2 from "node:os";
6681
- import path32 from "node:path";
6682
- import fs29 from "fs-extra";
6843
+ import path33 from "node:path";
6844
+ import fs30 from "fs-extra";
6683
6845
  import * as tar from "tar";
6684
6846
  init_fs();
6685
6847
  init_logger();
@@ -6698,7 +6860,7 @@ async function runBundleExport(options) {
6698
6860
  return;
6699
6861
  }
6700
6862
  const cacheDir = getCacheDir();
6701
- const manifest = await readJson(path32.join(cacheDir, MANIFEST_FILE));
6863
+ const manifest = await readJson(path33.join(cacheDir, MANIFEST_FILE));
6702
6864
  if (!manifest?.items?.length) {
6703
6865
  error("Catalog cache is empty \u2014 run `haus update` on a connected machine first.");
6704
6866
  process.exitCode = 1;
@@ -6709,52 +6871,52 @@ async function runBundleExport(options) {
6709
6871
  catalogRef: typeof manifest.catalogRef === "string" ? manifest.catalogRef : null,
6710
6872
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
6711
6873
  };
6712
- await writeBundleAsTar(cacheDir, meta, path32.resolve(out));
6874
+ await writeBundleAsTar(cacheDir, meta, path33.resolve(out));
6713
6875
  log(
6714
6876
  `Exported ${manifest.items.length} catalog item(s) to ${out}${meta.catalogRef ? ` (${meta.catalogRef})` : ""}.`
6715
6877
  );
6716
6878
  log("Move it to the disconnected machine and run `haus catalog bundle import <file>`.");
6717
6879
  }
6718
6880
  async function writeBundleAsTar(cacheDir, meta, dest) {
6719
- const bundleMetaPath = path32.join(cacheDir, BUNDLE_META_FILE);
6720
- const previousBundleMeta = await fs29.pathExists(bundleMetaPath) ? await fs29.readFile(bundleMetaPath, "utf8") : null;
6881
+ const bundleMetaPath = path33.join(cacheDir, BUNDLE_META_FILE);
6882
+ const previousBundleMeta = await fs30.pathExists(bundleMetaPath) ? await fs30.readFile(bundleMetaPath, "utf8") : null;
6721
6883
  await writeJson(bundleMetaPath, meta);
6722
6884
  try {
6723
- await fs29.ensureDir(path32.dirname(dest));
6885
+ await fs30.ensureDir(path33.dirname(dest));
6724
6886
  await tar.create({ gzip: true, cwd: cacheDir, file: dest }, ["."]);
6725
6887
  } finally {
6726
- if (previousBundleMeta === null) await fs29.remove(bundleMetaPath);
6727
- else await fs29.writeFile(bundleMetaPath, previousBundleMeta, "utf8");
6888
+ if (previousBundleMeta === null) await fs30.remove(bundleMetaPath);
6889
+ else await fs30.writeFile(bundleMetaPath, previousBundleMeta, "utf8");
6728
6890
  }
6729
6891
  }
6730
6892
  function isMarkdownFile(relOrAbsPath) {
6731
6893
  return relOrAbsPath.toLowerCase().endsWith(".md");
6732
6894
  }
6733
6895
  async function listAllFilesUnfiltered(root, current = root) {
6734
- const entries = await fs29.readdir(current, { withFileTypes: true });
6896
+ const entries = await fs30.readdir(current, { withFileTypes: true });
6735
6897
  const out = [];
6736
6898
  for (const entry of entries) {
6737
6899
  if (entry.isSymbolicLink()) continue;
6738
- const abs = path32.join(current, entry.name);
6900
+ const abs = path33.join(current, entry.name);
6739
6901
  if (entry.isDirectory()) {
6740
6902
  out.push(...await listAllFilesUnfiltered(root, abs));
6741
6903
  } else if (entry.isFile()) {
6742
- out.push(path32.relative(root, abs).split(path32.sep).join("/"));
6904
+ out.push(path33.relative(root, abs).split(path33.sep).join("/"));
6743
6905
  }
6744
6906
  }
6745
6907
  return out;
6746
6908
  }
6747
6909
  async function validateStagedItem(item, absPath) {
6748
- const stat = await fs29.stat(absPath).catch(() => null);
6910
+ const stat = await fs30.stat(absPath).catch(() => null);
6749
6911
  if (!stat) return { ok: true };
6750
6912
  if (stat.isFile()) {
6751
- const content2 = await fs29.readFile(absPath, "utf8");
6913
+ const content2 = await fs30.readFile(absPath, "utf8");
6752
6914
  return validateCatalogItem(item, content2);
6753
6915
  }
6754
6916
  const files = await listAllFilesUnfiltered(absPath);
6755
6917
  for (const rel of files) {
6756
6918
  if (!isMarkdownFile(rel)) continue;
6757
- const content2 = await fs29.readFile(path32.join(absPath, rel), "utf8");
6919
+ const content2 = await fs30.readFile(path33.join(absPath, rel), "utf8");
6758
6920
  const verdict = validateCatalogItem(item, content2);
6759
6921
  if (!verdict.ok) return verdict;
6760
6922
  }
@@ -6776,7 +6938,7 @@ async function validateStagedItems(staging, items) {
6776
6938
  continue;
6777
6939
  }
6778
6940
  coveredPaths.push(item.path);
6779
- if (!await fs29.pathExists(contentPath)) {
6941
+ if (!await fs30.pathExists(contentPath)) {
6780
6942
  if (!isSynthetic) {
6781
6943
  accepted.push(item.id);
6782
6944
  contentlessIds.add(item.id);
@@ -6786,7 +6948,7 @@ async function validateStagedItems(staging, items) {
6786
6948
  const verdict = await validateStagedItem(item, contentPath);
6787
6949
  if (!verdict.ok) {
6788
6950
  warn(`Rejected ${item.id}: ${verdict.reason}`);
6789
- await fs29.remove(contentPath);
6951
+ await fs30.remove(contentPath);
6790
6952
  if (!isSynthetic) rejected += 1;
6791
6953
  continue;
6792
6954
  }
@@ -6798,30 +6960,30 @@ async function validateStagedItems(staging, items) {
6798
6960
  const covered = coveredPaths.some((p) => rel === p || rel.startsWith(`${p}/`));
6799
6961
  if (!covered) {
6800
6962
  warn(`Refusing staged file outside any catalog item's path: ${rel}`);
6801
- await fs29.remove(path32.join(staging, rel));
6963
+ await fs30.remove(path33.join(staging, rel));
6802
6964
  }
6803
6965
  }
6804
6966
  await pruneEmptyDirs(staging);
6805
6967
  return { accepted, rejected, contentlessIds };
6806
6968
  }
6807
6969
  async function pruneEmptyDirs(root, dir = root) {
6808
- const entries = await fs29.readdir(dir, { withFileTypes: true });
6970
+ const entries = await fs30.readdir(dir, { withFileTypes: true });
6809
6971
  for (const entry of entries) {
6810
6972
  if (entry.isDirectory()) {
6811
- await pruneEmptyDirs(root, path32.join(dir, entry.name));
6973
+ await pruneEmptyDirs(root, path33.join(dir, entry.name));
6812
6974
  }
6813
6975
  }
6814
- if (dir !== root && (await fs29.readdir(dir)).length === 0) {
6815
- await fs29.remove(dir);
6976
+ if (dir !== root && (await fs30.readdir(dir)).length === 0) {
6977
+ await fs30.remove(dir);
6816
6978
  }
6817
6979
  }
6818
6980
  async function stripSymlinks(dir) {
6819
6981
  const removed = [];
6820
- const entries = await fs29.readdir(dir, { withFileTypes: true });
6982
+ const entries = await fs30.readdir(dir, { withFileTypes: true });
6821
6983
  for (const entry of entries) {
6822
- const abs = path32.join(dir, entry.name);
6984
+ const abs = path33.join(dir, entry.name);
6823
6985
  if (entry.isSymbolicLink()) {
6824
- await fs29.remove(abs);
6986
+ await fs30.remove(abs);
6825
6987
  removed.push(abs);
6826
6988
  continue;
6827
6989
  }
@@ -6833,13 +6995,13 @@ async function stripSymlinks(dir) {
6833
6995
  }
6834
6996
  async function runBundleImport(file, options = {}) {
6835
6997
  const dryRun = Boolean(options.dryRun);
6836
- const archive = path32.resolve(file);
6837
- if (!await fs29.pathExists(archive)) {
6998
+ const archive = path33.resolve(file);
6999
+ if (!await fs30.pathExists(archive)) {
6838
7000
  error(`No such bundle: ${file}`);
6839
7001
  process.exitCode = 1;
6840
7002
  return;
6841
7003
  }
6842
- const staging = await fs29.mkdtemp(path32.join(os2.tmpdir(), "haus-catalog-bundle-import-"));
7004
+ const staging = await fs30.mkdtemp(path33.join(os2.tmpdir(), "haus-catalog-bundle-import-"));
6843
7005
  try {
6844
7006
  await tar.extract({
6845
7007
  file: archive,
@@ -6859,9 +7021,9 @@ async function runBundleImport(file, options = {}) {
6859
7021
  });
6860
7022
  const strippedSymlinks = await stripSymlinks(staging);
6861
7023
  for (const abs of strippedSymlinks) {
6862
- warn(`Refusing symlink in bundle: ${path32.relative(staging, abs)}`);
7024
+ warn(`Refusing symlink in bundle: ${path33.relative(staging, abs)}`);
6863
7025
  }
6864
- const manifest = await readJson(path32.join(staging, MANIFEST_FILE));
7026
+ const manifest = await readJson(path33.join(staging, MANIFEST_FILE));
6865
7027
  if (!manifest?.items?.length) {
6866
7028
  error("Bundle contains no catalog manifest \u2014 refusing to import.");
6867
7029
  process.exitCode = 1;
@@ -6872,7 +7034,7 @@ async function runBundleImport(file, options = {}) {
6872
7034
  staging,
6873
7035
  manifest.items
6874
7036
  );
6875
- const meta = await readJson(path32.join(staging, BUNDLE_META_FILE));
7037
+ const meta = await readJson(path33.join(staging, BUNDLE_META_FILE));
6876
7038
  const label = meta?.catalogRef ?? (meta?.version ? `v${meta.version}` : "unknown ref");
6877
7039
  const installedCount = accepted.length;
6878
7040
  if (dryRun) {
@@ -6895,9 +7057,9 @@ async function runBundleImport(file, options = {}) {
6895
7057
  }
6896
7058
  return;
6897
7059
  }
6898
- await fs29.ensureDir(getCacheDir());
6899
- for (const entry of await fs29.readdir(staging)) {
6900
- await fs29.copy(path32.join(staging, entry), path32.join(getCacheDir(), entry), {
7060
+ await fs30.ensureDir(getCacheDir());
7061
+ for (const entry of await fs30.readdir(staging)) {
7062
+ await fs30.copy(path33.join(staging, entry), path33.join(getCacheDir(), entry), {
6901
7063
  overwrite: true,
6902
7064
  errorOnExist: false
6903
7065
  });
@@ -6914,17 +7076,17 @@ async function runBundleImport(file, options = {}) {
6914
7076
  }
6915
7077
  throw err;
6916
7078
  } finally {
6917
- await fs29.remove(staging);
7079
+ await fs30.remove(staging);
6918
7080
  }
6919
7081
  }
6920
7082
 
6921
7083
  // src/commands/doctor.ts
6922
- import path48 from "node:path";
6923
- import fs40 from "fs-extra";
7084
+ import path49 from "node:path";
7085
+ import fs41 from "fs-extra";
6924
7086
  init_manifest_format();
6925
7087
 
6926
7088
  // src/claude/former-install-orphans.ts
6927
- import fs30 from "fs-extra";
7089
+ import fs31 from "fs-extra";
6928
7090
  init_path_safety();
6929
7091
  init_paths();
6930
7092
  var toPosix = (value) => value.replace(/\\/g, "/");
@@ -6948,7 +7110,7 @@ async function findOrphanedFormerInstalls(root, items, lockRows) {
6948
7110
  const projectRel = `.claude/${relPath}`;
6949
7111
  if (trackedPaths.has(projectRel) || reported.has(projectRel)) continue;
6950
7112
  const abs = safeJoin(claudePath(root), relPath);
6951
- if (abs === null || !await fs30.pathExists(abs)) continue;
7113
+ if (abs === null || !await fs31.pathExists(abs)) continue;
6952
7114
  reported.add(projectRel);
6953
7115
  orphans.push({ formerId: entry.formerId, currentId: entry.currentId, relPath: projectRel });
6954
7116
  }
@@ -6956,13 +7118,13 @@ async function findOrphanedFormerInstalls(root, items, lockRows) {
6956
7118
  }
6957
7119
 
6958
7120
  // src/claude/plugin-duplicate-skills.ts
6959
- import fs31 from "node:fs";
6960
- import path33 from "node:path";
7121
+ import fs32 from "node:fs";
7122
+ import path34 from "node:path";
6961
7123
  init_paths();
6962
7124
  function subdirectoryNames(dir) {
6963
7125
  let entries;
6964
7126
  try {
6965
- entries = fs31.readdirSync(dir, { withFileTypes: true });
7127
+ entries = fs32.readdirSync(dir, { withFileTypes: true });
6966
7128
  } catch {
6967
7129
  return [];
6968
7130
  }
@@ -6971,7 +7133,7 @@ function subdirectoryNames(dir) {
6971
7133
  function isDirectorySymlink(dir, entry) {
6972
7134
  if (!entry.isSymbolicLink()) return false;
6973
7135
  try {
6974
- return fs31.statSync(path33.join(dir, entry.name)).isDirectory();
7136
+ return fs32.statSync(path34.join(dir, entry.name)).isDirectory();
6975
7137
  } catch {
6976
7138
  return false;
6977
7139
  }
@@ -6989,7 +7151,7 @@ function findPluginDuplicateSkills(root, pluginSkillDirs) {
6989
7151
  return [...duplicates].sort();
6990
7152
  }
6991
7153
  function pluginSkillsDir(installLocation) {
6992
- return path33.join(installLocation, "skills");
7154
+ return path34.join(installLocation, "skills");
6993
7155
  }
6994
7156
  function reportPluginDuplicates(root, { activePins, marketplaces, lockIds }) {
6995
7157
  if (activePins.length === 0) return { supersededIds: [], untrackedNames: [] };
@@ -7016,7 +7178,7 @@ init_project_pin();
7016
7178
  init_fs();
7017
7179
  init_logger();
7018
7180
  init_paths();
7019
- import fs32 from "fs-extra";
7181
+ import fs33 from "fs-extra";
7020
7182
  function normalizeIdList(value) {
7021
7183
  if (!Array.isArray(value)) return [];
7022
7184
  return [
@@ -7044,9 +7206,9 @@ async function readProjectConfigObject(root) {
7044
7206
  async function updateProjectConfig(root, mutate) {
7045
7207
  const file = hausPath(root, PROJECT_CONFIG_FILE);
7046
7208
  let existing = {};
7047
- if (await fs32.pathExists(file)) {
7209
+ if (await fs33.pathExists(file)) {
7048
7210
  try {
7049
- const parsed = JSON.parse(await fs32.readFile(file, "utf8"));
7211
+ const parsed = JSON.parse(await fs33.readFile(file, "utf8"));
7050
7212
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
7051
7213
  existing = { ...parsed };
7052
7214
  } else {
@@ -7128,9 +7290,9 @@ async function addProjectExcludedIds(root, ids) {
7128
7290
 
7129
7291
  // src/claude/skill-reference-check.ts
7130
7292
  init_paths();
7131
- import path34 from "node:path";
7293
+ import path35 from "node:path";
7132
7294
  import fg4 from "fast-glob";
7133
- import fs33 from "fs-extra";
7295
+ import fs34 from "fs-extra";
7134
7296
  var REFERENCE_PATTERN = /\b([a-z][a-z0-9]*(?:-[a-z0-9]+)+):([a-z][a-z0-9]*(?:-[a-z0-9]+)*)\b(?!:\/\/)/g;
7135
7297
  function escapeRegExp2(value) {
7136
7298
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -7303,14 +7465,14 @@ async function findUnresolvedAgentReferences(root, options = {}) {
7303
7465
  }
7304
7466
  async function readInstalledSkillNames(root) {
7305
7467
  const skillsRoot = claudePath(root, "skills");
7306
- if (!await fs33.pathExists(skillsRoot)) return /* @__PURE__ */ new Set();
7307
- const entries = await fs33.readdir(skillsRoot, { withFileTypes: true });
7468
+ if (!await fs34.pathExists(skillsRoot)) return /* @__PURE__ */ new Set();
7469
+ const entries = await fs34.readdir(skillsRoot, { withFileTypes: true });
7308
7470
  return new Set(entries.filter((e) => e.isDirectory() && e.name !== "shared").map((e) => e.name));
7309
7471
  }
7310
7472
  async function readInstalledAgentNames(root) {
7311
7473
  const agentsRoot = claudePath(root, "agents");
7312
- if (!await fs33.pathExists(agentsRoot)) return /* @__PURE__ */ new Set();
7313
- const entries = await fs33.readdir(agentsRoot, { withFileTypes: true });
7474
+ if (!await fs34.pathExists(agentsRoot)) return /* @__PURE__ */ new Set();
7475
+ const entries = await fs34.readdir(agentsRoot, { withFileTypes: true });
7314
7476
  return new Set(
7315
7477
  entries.filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => e.name.replace(/\.md$/, ""))
7316
7478
  );
@@ -7319,14 +7481,14 @@ async function readScannedMarkdown(root) {
7319
7481
  const scanned = [];
7320
7482
  for (const dir of ["skills", "agents", "rules"]) {
7321
7483
  const dirRoot = claudePath(root, dir);
7322
- if (!await fs33.pathExists(dirRoot)) continue;
7484
+ if (!await fs34.pathExists(dirRoot)) continue;
7323
7485
  const pattern = dir === "skills" ? "*/**/*.md" : "**/*.md";
7324
7486
  const files = await fg4(pattern, { cwd: dirRoot, onlyFiles: true, dot: true });
7325
7487
  for (const rel of files) {
7326
7488
  if (dir === "skills" && rel.startsWith("shared/")) continue;
7327
- const raw = await fs33.readFile(path34.join(dirRoot, rel), "utf8");
7489
+ const raw = await fs34.readFile(path35.join(dirRoot, rel), "utf8");
7328
7490
  scanned.push({
7329
- file: path34.posix.join(".claude", dir, rel.split(path34.sep).join("/")),
7491
+ file: path35.posix.join(".claude", dir, rel.split(path35.sep).join("/")),
7330
7492
  lines: stripFencedCodeBlocks(raw).split("\n")
7331
7493
  });
7332
7494
  }
@@ -7336,14 +7498,14 @@ async function readScannedMarkdown(root) {
7336
7498
 
7337
7499
  // src/decisions/doctor.ts
7338
7500
  init_fs();
7339
- import path39 from "node:path";
7340
- import fs37 from "fs-extra";
7501
+ import path40 from "node:path";
7502
+ import fs38 from "fs-extra";
7341
7503
 
7342
7504
  // src/decisions/check.ts
7343
7505
  init_fs();
7344
7506
  init_paths();
7345
- import path38 from "node:path";
7346
- import fs36 from "fs-extra";
7507
+ import path39 from "node:path";
7508
+ import fs37 from "fs-extra";
7347
7509
 
7348
7510
  // src/decisions/dep-bump.ts
7349
7511
  var DEP_SECTIONS = [
@@ -7403,7 +7565,7 @@ function isDependencyBumpOnly(beforeText, afterText) {
7403
7565
  // src/decisions/diff.ts
7404
7566
  init_exec();
7405
7567
  init_fs();
7406
- import path35 from "node:path";
7568
+ import path36 from "node:path";
7407
7569
  var NUL = String.fromCharCode(0);
7408
7570
  function parseNumstat(output) {
7409
7571
  const fields2 = output.split(NUL);
@@ -7490,7 +7652,7 @@ async function resolvePackageJsonPair(root, file, opts) {
7490
7652
  return { before: before2, after: after2 };
7491
7653
  }
7492
7654
  const before = await readBlob(root, `${range}:${file}`) ?? empty;
7493
- const after = await readText(path35.join(root, file)) ?? empty;
7655
+ const after = await readText(path36.join(root, file)) ?? empty;
7494
7656
  return { before, after };
7495
7657
  }
7496
7658
  async function collectDiffStats(root, opts) {
@@ -7553,23 +7715,23 @@ function firstMatchingRegex(filePath, patterns) {
7553
7715
  }
7554
7716
 
7555
7717
  // src/decisions/paths.ts
7556
- import path36 from "node:path";
7557
- import fs34 from "fs-extra";
7718
+ import path37 from "node:path";
7719
+ import fs35 from "fs-extra";
7558
7720
  async function resolveDecisionsDir(root) {
7559
- const primary = path36.join(root, DECISIONS_TRIGGERS.decisionsDir);
7560
- if (await fs34.pathExists(primary)) return primary;
7561
- const legacy = path36.join(root, DECISIONS_TRIGGERS.legacyDecisionsDir ?? "docs/adr");
7562
- if (await fs34.pathExists(legacy)) return legacy;
7721
+ const primary = path37.join(root, DECISIONS_TRIGGERS.decisionsDir);
7722
+ if (await fs35.pathExists(primary)) return primary;
7723
+ const legacy = path37.join(root, DECISIONS_TRIGGERS.legacyDecisionsDir ?? "docs/adr");
7724
+ if (await fs35.pathExists(legacy)) return legacy;
7563
7725
  return primary;
7564
7726
  }
7565
7727
  function relativeDecisionPath(decisionsDir, root) {
7566
- return path36.relative(root, decisionsDir).replace(/\\/g, "/");
7728
+ return path37.relative(root, decisionsDir).replace(/\\/g, "/");
7567
7729
  }
7568
7730
 
7569
7731
  // src/decisions/validate.ts
7570
7732
  init_fs();
7571
- import path37 from "node:path";
7572
- import fs35 from "fs-extra";
7733
+ import path38 from "node:path";
7734
+ import fs36 from "fs-extra";
7573
7735
  function validateDecisionContent(content2) {
7574
7736
  const errors = [];
7575
7737
  for (const section of DECISIONS_TRIGGERS.requiredSections) {
@@ -7598,14 +7760,14 @@ function decisionNumberFromFile(name) {
7598
7760
  return match?.[1] ?? null;
7599
7761
  }
7600
7762
  async function validateIndexSync(decisionsDir, decisionFiles) {
7601
- const readmePath = path37.join(decisionsDir, "README.md");
7602
- if (!await fs35.pathExists(readmePath)) {
7763
+ const readmePath = path38.join(decisionsDir, "README.md");
7764
+ if (!await fs36.pathExists(readmePath)) {
7603
7765
  return ["docs/decisions/README.md is missing"];
7604
7766
  }
7605
7767
  const readme = await readText(readmePath) ?? "";
7606
7768
  const errors = [];
7607
7769
  for (const file of decisionFiles) {
7608
- const num = decisionNumberFromFile(path37.basename(file));
7770
+ const num = decisionNumberFromFile(path38.basename(file));
7609
7771
  if (num && !indexReferencesNumber(readme, num)) {
7610
7772
  errors.push(`README.md missing index row for ADR-${num}`);
7611
7773
  }
@@ -7705,22 +7867,22 @@ async function checkSatisfaction(input2) {
7705
7867
  const errors = [];
7706
7868
  const decisionsPrefix = `${input2.decisionsRel}/`;
7707
7869
  const newDecisionFiles = input2.changedFiles.filter(
7708
- (f) => f.startsWith(decisionsPrefix) && isDecisionFileName(path38.basename(f))
7870
+ (f) => f.startsWith(decisionsPrefix) && isDecisionFileName(path39.basename(f))
7709
7871
  );
7710
7872
  if (newDecisionFiles.length === 0) {
7711
7873
  errors.push(`no new or updated decision under ${input2.decisionsRel}/`);
7712
7874
  return errors;
7713
7875
  }
7714
- const decisionsDir = path38.join(input2.root, input2.decisionsRel);
7876
+ const decisionsDir = path39.join(input2.root, input2.decisionsRel);
7715
7877
  for (const rel of newDecisionFiles) {
7716
- const abs = path38.join(input2.root, rel);
7878
+ const abs = path39.join(input2.root, rel);
7717
7879
  const content2 = await readText(abs) ?? "";
7718
7880
  const valid = validateDecisionContent(content2);
7719
7881
  if (!valid.ok) errors.push(...valid.errors.map((e) => `${rel}: ${e}`));
7720
7882
  }
7721
7883
  const indexErrors = await validateIndexSync(
7722
7884
  decisionsDir,
7723
- newDecisionFiles.map((f) => path38.basename(f))
7885
+ newDecisionFiles.map((f) => path39.basename(f))
7724
7886
  );
7725
7887
  errors.push(...indexErrors);
7726
7888
  return errors;
@@ -7776,8 +7938,8 @@ ${opts.commitMessages ?? ""}`;
7776
7938
  changedFiles: stats.files
7777
7939
  });
7778
7940
  if (satisfactionErrors.length > 0 && decisionsRel !== legacyRel) {
7779
- const legacyExists = await fs36.pathExists(path38.join(root, legacyRel));
7780
- if (legacyExists && !await fs36.pathExists(decisionsDir)) {
7941
+ const legacyExists = await fs37.pathExists(path39.join(root, legacyRel));
7942
+ if (legacyExists && !await fs37.pathExists(decisionsDir)) {
7781
7943
  satisfactionErrors = await checkSatisfaction({
7782
7944
  root,
7783
7945
  decisionsRel: legacyRel,
@@ -7799,18 +7961,18 @@ ${opts.commitMessages ?? ""}`;
7799
7961
  return { triggered: true, satisfied: false, reasons, securityTriggered };
7800
7962
  }
7801
7963
  async function listDecisionNumbers(decisionsDir) {
7802
- if (!await fs36.pathExists(decisionsDir)) return [];
7803
- const entries = await fs36.readdir(decisionsDir);
7964
+ if (!await fs37.pathExists(decisionsDir)) return [];
7965
+ const entries = await fs37.readdir(decisionsDir);
7804
7966
  return entries.map((name) => decisionNumberFromFile(name)).filter((n) => n != null).map((n) => Number(n));
7805
7967
  }
7806
7968
 
7807
7969
  // src/decisions/doctor.ts
7808
7970
  async function auditDecisionsLayout(root) {
7809
7971
  const findings = [];
7810
- const primary = path39.join(root, DECISIONS_TRIGGERS.decisionsDir);
7811
- const legacy = path39.join(root, DECISIONS_TRIGGERS.legacyDecisionsDir ?? "docs/adr");
7812
- const hasPrimary = await fs37.pathExists(primary);
7813
- const hasLegacy = await fs37.pathExists(legacy);
7972
+ const primary = path40.join(root, DECISIONS_TRIGGERS.decisionsDir);
7973
+ const legacy = path40.join(root, DECISIONS_TRIGGERS.legacyDecisionsDir ?? "docs/adr");
7974
+ const hasPrimary = await fs38.pathExists(primary);
7975
+ const hasLegacy = await fs38.pathExists(legacy);
7814
7976
  if (!hasPrimary && hasLegacy) {
7815
7977
  findings.push({
7816
7978
  level: "advisory",
@@ -7823,8 +7985,8 @@ async function auditDecisionsLayout(root) {
7823
7985
  if (!hasPrimary) return findings;
7824
7986
  const decisionsDir = await resolveDecisionsDir(root);
7825
7987
  const rel = relativeDecisionPath(decisionsDir, root);
7826
- const readmePath = path39.join(decisionsDir, "README.md");
7827
- if (!await fs37.pathExists(readmePath)) {
7988
+ const readmePath = path40.join(decisionsDir, "README.md");
7989
+ if (!await fs38.pathExists(readmePath)) {
7828
7990
  findings.push({
7829
7991
  level: "advisory",
7830
7992
  line: `- DECISIONS: missing ${rel}/README.md index`,
@@ -7834,7 +7996,7 @@ async function auditDecisionsLayout(root) {
7834
7996
  return findings;
7835
7997
  }
7836
7998
  const readme = await readText(readmePath);
7837
- const entries = (await fs37.readdir(decisionsDir)).filter(
7999
+ const entries = (await fs38.readdir(decisionsDir)).filter(
7838
8000
  (f) => f.endsWith(".md") && f !== "README.md"
7839
8001
  );
7840
8002
  for (const file of entries) {
@@ -7848,7 +8010,7 @@ async function auditDecisionsLayout(root) {
7848
8010
  });
7849
8011
  }
7850
8012
  }
7851
- const claude = await readText(path39.join(root, "CLAUDE.md"));
8013
+ const claude = await readText(path40.join(root, "CLAUDE.md"));
7852
8014
  if (claude && !claude.includes(`@${rel}/README.md`)) {
7853
8015
  findings.push({
7854
8016
  level: "advisory",
@@ -7882,14 +8044,14 @@ async function auditDecisionsLayout(root) {
7882
8044
  init_fs();
7883
8045
  init_logger();
7884
8046
  init_paths();
7885
- import path40 from "node:path";
7886
- import fs38 from "fs-extra";
8047
+ import path41 from "node:path";
8048
+ import fs39 from "fs-extra";
7887
8049
  function manifestPathIn(configDir) {
7888
- return path40.join(configDir, "haus", "install-manifest.json");
8050
+ return path41.join(configDir, "haus", "install-manifest.json");
7889
8051
  }
7890
8052
  function relativeInside(base, target) {
7891
- const rel = path40.relative(base, target);
7892
- if (!rel || rel === ".." || rel.startsWith(`..${path40.sep}`) || path40.isAbsolute(rel)) {
8053
+ const rel = path41.relative(base, target);
8054
+ if (!rel || rel === ".." || rel.startsWith(`..${path41.sep}`) || path41.isAbsolute(rel)) {
7893
8055
  return void 0;
7894
8056
  }
7895
8057
  return rel;
@@ -7901,27 +8063,27 @@ async function sameContent(a, b) {
7901
8063
  async function pruneVacatedDirs(root, vacatedFiles) {
7902
8064
  const dirs = /* @__PURE__ */ new Set();
7903
8065
  for (const file of vacatedFiles) {
7904
- let dir = path40.dirname(file);
8066
+ let dir = path41.dirname(file);
7905
8067
  while (relativeInside(root, dir) !== void 0) {
7906
8068
  dirs.add(dir);
7907
- dir = path40.dirname(dir);
8069
+ dir = path41.dirname(dir);
7908
8070
  }
7909
8071
  }
7910
- const deepestFirst = [...dirs].sort((a, b) => b.split(path40.sep).length - a.split(path40.sep).length);
8072
+ const deepestFirst = [...dirs].sort((a, b) => b.split(path41.sep).length - a.split(path41.sep).length);
7911
8073
  for (const dir of deepestFirst) await pruneEmptyDir(dir);
7912
8074
  }
7913
8075
  async function destinationWritable(dir) {
7914
- let candidate = path40.resolve(dir);
8076
+ let candidate = path41.resolve(dir);
7915
8077
  for (; ; ) {
7916
- if (await fs38.pathExists(candidate)) {
8078
+ if (await fs39.pathExists(candidate)) {
7917
8079
  try {
7918
- await fs38.access(candidate, fs38.constants.W_OK);
8080
+ await fs39.access(candidate, fs39.constants.W_OK);
7919
8081
  return true;
7920
8082
  } catch (err) {
7921
8083
  return err instanceof Error ? err.message : String(err);
7922
8084
  }
7923
8085
  }
7924
- const parent = path40.dirname(candidate);
8086
+ const parent = path41.dirname(candidate);
7925
8087
  if (parent === candidate) return `no existing ancestor directory for ${dir}`;
7926
8088
  candidate = parent;
7927
8089
  }
@@ -7947,7 +8109,7 @@ async function migrateGlobalConfigDir(options = {}) {
7947
8109
  if (!legacy || !Array.isArray(legacy.files)) {
7948
8110
  return { ...base, reason: "no install manifest under ~/.claude" };
7949
8111
  }
7950
- if (await fs38.pathExists(manifestPathIn(to))) {
8112
+ if (await fs39.pathExists(manifestPathIn(to))) {
7951
8113
  return { ...base, reason: "an install manifest already exists in the resolved config dir" };
7952
8114
  }
7953
8115
  const writable = await destinationWritable(to);
@@ -7969,15 +8131,15 @@ async function migrateGlobalConfigDir(options = {}) {
7969
8131
  rewritten.push(entry);
7970
8132
  continue;
7971
8133
  }
7972
- const newDest = path40.join(to, rel);
7973
- if (!await fs38.pathExists(entry.destPath)) {
8134
+ const newDest = path41.join(to, rel);
8135
+ if (!await fs39.pathExists(entry.destPath)) {
7974
8136
  rewritten.push({ ...entry, destPath: newDest });
7975
8137
  continue;
7976
8138
  }
7977
- if (await fs38.pathExists(newDest)) {
8139
+ if (await fs39.pathExists(newDest)) {
7978
8140
  if (await sameContent(entry.destPath, newDest)) {
7979
8141
  if (!dryRun) {
7980
- await fs38.remove(entry.destPath);
8142
+ await fs39.remove(entry.destPath);
7981
8143
  vacated.push(entry.destPath);
7982
8144
  }
7983
8145
  moved.push(newDest);
@@ -7994,7 +8156,7 @@ async function migrateGlobalConfigDir(options = {}) {
7994
8156
  continue;
7995
8157
  }
7996
8158
  try {
7997
- await fs38.move(entry.destPath, newDest);
8159
+ await fs39.move(entry.destPath, newDest);
7998
8160
  vacated.push(entry.destPath);
7999
8161
  moved.push(newDest);
8000
8162
  rewritten.push({ ...entry, destPath: newDest });
@@ -8019,7 +8181,7 @@ async function migrateGlobalConfigDir(options = {}) {
8019
8181
  }
8020
8182
  try {
8021
8183
  await writeJson(manifestPathIn(to), { ...legacy, files: rewritten });
8022
- await fs38.remove(legacyManifestPath);
8184
+ await fs39.remove(legacyManifestPath);
8023
8185
  vacated.push(legacyManifestPath);
8024
8186
  } catch (err) {
8025
8187
  return {
@@ -8148,12 +8310,12 @@ function formatPrereqReport(report) {
8148
8310
  }
8149
8311
 
8150
8312
  // src/scanner/read-context.ts
8151
- import path47 from "node:path";
8152
- import fs39 from "fs-extra";
8313
+ import path48 from "node:path";
8314
+ import fs40 from "fs-extra";
8153
8315
 
8154
8316
  // src/commands/workspace/config.ts
8155
8317
  init_fs();
8156
- import path41 from "node:path";
8318
+ import path42 from "node:path";
8157
8319
  import YAML2 from "yaml";
8158
8320
  var WORKSPACE_FILE = "haus.workspace.yaml";
8159
8321
  function parseWorkspaceConfig(text) {
@@ -8184,7 +8346,7 @@ function parseWorkspaceConfig(text) {
8184
8346
  };
8185
8347
  }
8186
8348
  async function readWorkspaceConfig(workspaceRoot) {
8187
- return parseWorkspaceConfig(await readText(path41.join(workspaceRoot, WORKSPACE_FILE)));
8349
+ return parseWorkspaceConfig(await readText(path42.join(workspaceRoot, WORKSPACE_FILE)));
8188
8350
  }
8189
8351
 
8190
8352
  // src/scanner/read-context.ts
@@ -8192,7 +8354,7 @@ init_fs();
8192
8354
  init_paths();
8193
8355
 
8194
8356
  // src/workspace/members.ts
8195
- import path42 from "node:path";
8357
+ import path43 from "node:path";
8196
8358
  init_fs();
8197
8359
  var REPOS_MANIFEST_FILE = "repos.manifest.json";
8198
8360
  var REPOS_LOCAL_FILE = "repos.local.json";
@@ -8206,14 +8368,14 @@ var MemberConfigError = class extends Error {
8206
8368
  };
8207
8369
  function resolveAbsPath(workspaceRoot, folder, overrides) {
8208
8370
  const override = overrides[folder];
8209
- if (override) return path42.resolve(workspaceRoot, override);
8210
- return path42.resolve(workspaceRoot, folder);
8371
+ if (override) return path43.resolve(workspaceRoot, override);
8372
+ return path43.resolve(workspaceRoot, folder);
8211
8373
  }
8212
8374
  function isSelfMember(workspaceRoot, member) {
8213
- return path42.resolve(member.absPath) === path42.resolve(workspaceRoot);
8375
+ return path43.resolve(member.absPath) === path43.resolve(workspaceRoot);
8214
8376
  }
8215
8377
  async function readPathOverrides(workspaceRoot) {
8216
- const filePath = path42.join(workspaceRoot, REPOS_LOCAL_FILE);
8378
+ const filePath = path43.join(workspaceRoot, REPOS_LOCAL_FILE);
8217
8379
  const result = await readJsonDetailed(filePath);
8218
8380
  if (result.status === "missing") return {};
8219
8381
  if (result.status === "invalid") throw new MemberConfigError(filePath, "invalid JSON");
@@ -8229,7 +8391,7 @@ async function readPathOverrides(workspaceRoot) {
8229
8391
  return overrides;
8230
8392
  }
8231
8393
  async function readFromWorkspaceYaml(workspaceRoot, overrides) {
8232
- const filePath = path42.join(workspaceRoot, WORKSPACE_FILE);
8394
+ const filePath = path43.join(workspaceRoot, WORKSPACE_FILE);
8233
8395
  const text = await readText(filePath);
8234
8396
  if (text === void 0) return void 0;
8235
8397
  const config = parseWorkspaceConfig(text);
@@ -8242,7 +8404,7 @@ async function readFromWorkspaceYaml(workspaceRoot, overrides) {
8242
8404
  }));
8243
8405
  }
8244
8406
  async function readFromManifest(workspaceRoot, overrides) {
8245
- const filePath = path42.join(workspaceRoot, REPOS_MANIFEST_FILE);
8407
+ const filePath = path43.join(workspaceRoot, REPOS_MANIFEST_FILE);
8246
8408
  const result = await readJsonDetailed(filePath);
8247
8409
  if (result.status === "missing") return void 0;
8248
8410
  if (result.status === "invalid") throw new MemberConfigError(filePath, "invalid JSON");
@@ -8284,12 +8446,12 @@ async function readMembers(rootInfo) {
8284
8446
  }
8285
8447
 
8286
8448
  // src/scanner/scan-project.ts
8287
- import path46 from "node:path";
8449
+ import path47 from "node:path";
8288
8450
  init_fs();
8289
8451
 
8290
8452
  // src/utils/git-root.ts
8291
8453
  init_exec();
8292
- import path43 from "node:path";
8454
+ import path44 from "node:path";
8293
8455
  function fallback(start) {
8294
8456
  return {
8295
8457
  cwd: start,
@@ -8305,11 +8467,11 @@ function fallback(start) {
8305
8467
  async function resolveAbsoluteGitPath(flag, cwd, repoRoot) {
8306
8468
  const withFormat = await runGit(["rev-parse", "--path-format=absolute", flag], { cwd });
8307
8469
  const raw = withFormat.exitCode === 0 ? withFormat.stdout.trim() : "";
8308
- if (raw && path43.isAbsolute(raw)) return path43.resolve(raw);
8470
+ if (raw && path44.isAbsolute(raw)) return path44.resolve(raw);
8309
8471
  const plain = await runGit(["rev-parse", flag], { cwd });
8310
8472
  const plainRaw = plain.exitCode === 0 ? plain.stdout.trim() : "";
8311
8473
  if (!plainRaw) return "";
8312
- return path43.isAbsolute(plainRaw) ? path43.resolve(plainRaw) : path43.resolve(repoRoot, plainRaw);
8474
+ return path44.isAbsolute(plainRaw) ? path44.resolve(plainRaw) : path44.resolve(repoRoot, plainRaw);
8313
8475
  }
8314
8476
  async function resolveRoots(start) {
8315
8477
  const cwd = start ?? process.cwd();
@@ -8318,15 +8480,15 @@ async function resolveRoots(start) {
8318
8480
  if (toplevel.exitCode !== 0) return fallback(cwd);
8319
8481
  const toplevelRaw = toplevel.stdout.trim();
8320
8482
  if (!toplevelRaw) return fallback(cwd);
8321
- const repoRoot = path43.resolve(toplevelRaw);
8483
+ const repoRoot = path44.resolve(toplevelRaw);
8322
8484
  const [gitDir, gitCommonDir] = await Promise.all([
8323
8485
  resolveAbsoluteGitPath("--git-dir", cwd, repoRoot),
8324
8486
  resolveAbsoluteGitPath("--git-common-dir", cwd, repoRoot)
8325
8487
  ]);
8326
8488
  if (!gitDir || !gitCommonDir) return fallback(cwd);
8327
8489
  const isLinkedWorktree = gitDir !== gitCommonDir;
8328
- const mainRoot = isLinkedWorktree && path43.basename(gitCommonDir) === ".git" ? path43.dirname(gitCommonDir) : repoRoot;
8329
- const worktreeName = isLinkedWorktree ? path43.basename(gitDir) : null;
8490
+ const mainRoot = isLinkedWorktree && path44.basename(gitCommonDir) === ".git" ? path44.dirname(gitCommonDir) : repoRoot;
8491
+ const worktreeName = isLinkedWorktree ? path44.basename(gitDir) : null;
8330
8492
  return {
8331
8493
  cwd,
8332
8494
  repoRoot,
@@ -8626,7 +8788,7 @@ function runDetection(ctx, rules = STACK_RULES) {
8626
8788
  // src/scanner/render.ts
8627
8789
  init_fs();
8628
8790
  import { readFile } from "node:fs/promises";
8629
- import path44 from "node:path";
8791
+ import path45 from "node:path";
8630
8792
  async function buildContentBlob(root, files) {
8631
8793
  const candidates = files.filter(
8632
8794
  (f) => f.endsWith(".ts") || f.endsWith(".js") || f.endsWith(".php") || f.endsWith(".json") || f.endsWith(".yml") || f.endsWith(".yaml")
@@ -8634,7 +8796,7 @@ async function buildContentBlob(root, files) {
8634
8796
  const slice = candidates.slice(0, 300);
8635
8797
  const parts = await mapWithConcurrency(slice, async (rel) => {
8636
8798
  try {
8637
- return await readFile(path44.join(root, rel), "utf8");
8799
+ return await readFile(path45.join(root, rel), "utf8");
8638
8800
  } catch {
8639
8801
  return "";
8640
8802
  }
@@ -8643,7 +8805,7 @@ async function buildContentBlob(root, files) {
8643
8805
  }
8644
8806
 
8645
8807
  // src/scanner/workspace-packages.ts
8646
- import path45 from "node:path";
8808
+ import path46 from "node:path";
8647
8809
  import fg5 from "fast-glob";
8648
8810
  import YAML3 from "yaml";
8649
8811
  init_fs();
@@ -8651,7 +8813,7 @@ var PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
8651
8813
  var MAX_WORKSPACE_MEMBERS = 200;
8652
8814
  function isRepoRelativeGlob(glob) {
8653
8815
  if (glob.length === 0) return false;
8654
- if (path45.isAbsolute(glob) || glob.startsWith("/")) return false;
8816
+ if (path46.isAbsolute(glob) || glob.startsWith("/")) return false;
8655
8817
  return !glob.split("/").includes("..");
8656
8818
  }
8657
8819
  function stringEntries(value) {
@@ -8676,7 +8838,7 @@ function workspaceGlobsFromPnpmYaml(text) {
8676
8838
  }
8677
8839
  async function readWorkspaceGlobs(root, pkg) {
8678
8840
  const fromPkg = workspaceGlobsFromPackageJson(pkg);
8679
- const pnpmText = await readText(path45.join(root, PNPM_WORKSPACE_FILE));
8841
+ const pnpmText = await readText(path46.join(root, PNPM_WORKSPACE_FILE));
8680
8842
  const fromPnpm = pnpmText == null ? [] : workspaceGlobsFromPnpmYaml(pnpmText);
8681
8843
  return [.../* @__PURE__ */ new Set([...fromPkg, ...fromPnpm])];
8682
8844
  }
@@ -8697,7 +8859,7 @@ async function collectWorkspaceMemberPackages(root, globs) {
8697
8859
  } catch {
8698
8860
  return [];
8699
8861
  }
8700
- const rootManifest = path45.resolve(root, "package.json");
8862
+ const rootManifest = path46.resolve(root, "package.json");
8701
8863
  const contained = [
8702
8864
  ...new Set(
8703
8865
  matches.map((rel) => resolveContainedPath(root, rel)).filter((abs) => abs != null && abs !== rootManifest)
@@ -8706,8 +8868,8 @@ async function collectWorkspaceMemberPackages(root, globs) {
8706
8868
  const read = await mapWithConcurrency(contained, async (abs) => {
8707
8869
  const pkg = await readJson(abs);
8708
8870
  if (!isRecord(pkg)) return null;
8709
- const rel = path45.relative(root, path45.dirname(abs));
8710
- return { dir: rel.split(path45.sep).join("/"), pkg };
8871
+ const rel = path46.relative(root, path46.dirname(abs));
8872
+ return { dir: rel.split(path46.sep).join("/"), pkg };
8711
8873
  });
8712
8874
  return read.filter((entry) => entry != null);
8713
8875
  }
@@ -8827,8 +8989,8 @@ var SAFE_FILES = [
8827
8989
  "Gemfile"
8828
8990
  ];
8829
8991
  async function scanProject(root, options = {}) {
8830
- const pkg = await readJson(path46.join(root, "package.json"));
8831
- const composer = await readJson(path46.join(root, "composer.json"));
8992
+ const pkg = await readJson(path47.join(root, "package.json"));
8993
+ const composer = await readJson(path47.join(root, "composer.json"));
8832
8994
  const files = await listFiles(root, SAFE_FILES);
8833
8995
  const safeFiles = files.filter((f) => !blocked(f));
8834
8996
  const workspaceGlobs = await readWorkspaceGlobs(root, pkg ?? void 0);
@@ -8865,7 +9027,7 @@ async function scanProject(root, options = {}) {
8865
9027
  crossRepoHints.push("Monorepo orchestration detected");
8866
9028
  const unsupportedSignals = collectUnsupportedSignals(safeFiles);
8867
9029
  const detectionStatus = computeDetectionStatus(roles, stacks, unsupportedSignals);
8868
- const repoNameFallback = pkg?.name ? String(pkg.name) : path46.basename((await resolveRoots(root)).mainRoot);
9030
+ const repoNameFallback = pkg?.name ? String(pkg.name) : path47.basename((await resolveRoots(root)).mainRoot);
8869
9031
  const context = {
8870
9032
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8871
9033
  repoName: repoNameFallback,
@@ -8889,15 +9051,15 @@ async function scanProject(root, options = {}) {
8889
9051
  // src/scanner/read-context.ts
8890
9052
  async function isCacheFresh(root, cachePath) {
8891
9053
  const markerPaths = [
8892
- path47.join(root, "package.json"),
8893
- path47.join(root, "composer.json"),
8894
- path47.join(root, WORKSPACE_FILE),
8895
- path47.join(root, REPOS_MANIFEST_FILE)
9054
+ path48.join(root, "package.json"),
9055
+ path48.join(root, "composer.json"),
9056
+ path48.join(root, WORKSPACE_FILE),
9057
+ path48.join(root, REPOS_MANIFEST_FILE)
8896
9058
  ];
8897
- const cacheStat = await fs39.stat(cachePath);
9059
+ const cacheStat = await fs40.stat(cachePath);
8898
9060
  for (const markerPath of markerPaths) {
8899
- if (!await fs39.pathExists(markerPath)) continue;
8900
- const markerStat = await fs39.stat(markerPath);
9061
+ if (!await fs40.pathExists(markerPath)) continue;
9062
+ const markerStat = await fs40.stat(markerPath);
8901
9063
  if (markerStat.mtimeMs > cacheStat.mtimeMs) return false;
8902
9064
  }
8903
9065
  return true;
@@ -9030,7 +9192,7 @@ async function runDoctor(options) {
9030
9192
  } else {
9031
9193
  ok(`- HOOKS OK: ${hooks.message}`);
9032
9194
  }
9033
- const rootClaudeMdPath = path48.join(root, "CLAUDE.md");
9195
+ const rootClaudeMdPath = path49.join(root, "CLAUDE.md");
9034
9196
  const rootClaudeMdContent = await readText(rootClaudeMdPath);
9035
9197
  if (!rootClaudeMdContent) {
9036
9198
  flag(
@@ -9073,10 +9235,10 @@ async function runDoctor(options) {
9073
9235
  ok("- CLAUDE.md: import block present");
9074
9236
  }
9075
9237
  for (const target of actualTargets) {
9076
- const resolved = path48.resolve(root, target);
9077
- const relFromRoot = path48.relative(root, resolved);
9078
- const escapesRoot = relFromRoot === ".." || relFromRoot.startsWith(`..${path48.sep}`) || path48.isAbsolute(relFromRoot);
9079
- if (escapesRoot || !await fs40.pathExists(resolved)) {
9238
+ const resolved = path49.resolve(root, target);
9239
+ const relFromRoot = path49.relative(root, resolved);
9240
+ const escapesRoot = relFromRoot === ".." || relFromRoot.startsWith(`..${path49.sep}`) || path49.isAbsolute(relFromRoot);
9241
+ if (escapesRoot || !await fs41.pathExists(resolved)) {
9080
9242
  flag(
9081
9243
  `- CLAUDE.md import: @${target} does not resolve (run \`haus apply --write\`)`,
9082
9244
  `A file CLAUDE.md links to (${target}) is missing, so part of the guidance won't load`,
@@ -9094,7 +9256,7 @@ async function runDoctor(options) {
9094
9256
  );
9095
9257
  }
9096
9258
  const workflowPath = hausPath(root, "WORKFLOW.md");
9097
- const workflowExists = await fs40.pathExists(workflowPath);
9259
+ const workflowExists = await fs41.pathExists(workflowPath);
9098
9260
  if (!workflowExists) {
9099
9261
  flag(
9100
9262
  "- .haus-workflow/WORKFLOW.md: missing (run `haus apply --write`)",
@@ -9109,15 +9271,15 @@ async function runDoctor(options) {
9109
9271
  ok("- .haus-workflow/WORKFLOW.md: OK (user-owned)");
9110
9272
  } else {
9111
9273
  const bodyContent = workflowContent.slice(firstLine.length + 1);
9112
- const cachePath = path48.join(getCacheDir(), "templates/agentic-workflow-standard.md");
9113
- const bundledPath = path48.join(
9274
+ const cachePath = path49.join(getCacheDir(), "templates/agentic-workflow-standard.md");
9275
+ const bundledPath = path49.join(
9114
9276
  packageRoot(),
9115
9277
  "library",
9116
9278
  "global",
9117
9279
  "templates",
9118
9280
  "agentic-workflow-standard.md"
9119
9281
  );
9120
- const templatePath = await fs40.pathExists(cachePath) ? cachePath : bundledPath;
9282
+ const templatePath = await fs41.pathExists(cachePath) ? cachePath : bundledPath;
9121
9283
  const templateContent = await readText(templatePath);
9122
9284
  const verdict = checkManagedTamper(bodyContent, managedHeader.hash, templateContent ?? null);
9123
9285
  if (verdict.status === "tampered" || verdict.status === "legacy-diverged") {
@@ -9145,7 +9307,7 @@ async function runDoctor(options) {
9145
9307
  }
9146
9308
  }
9147
9309
  const workflowConfigPath = hausPath(root, "workflow-config.md");
9148
- const workflowConfigExists = await fs40.pathExists(workflowConfigPath);
9310
+ const workflowConfigExists = await fs41.pathExists(workflowConfigPath);
9149
9311
  if (!workflowConfigExists) {
9150
9312
  flag(
9151
9313
  "- .haus-workflow/workflow-config.md: missing (run `haus apply --write`)",
@@ -9153,7 +9315,7 @@ async function runDoctor(options) {
9153
9315
  "haus apply --write"
9154
9316
  );
9155
9317
  } else {
9156
- const cfg = await fs40.readFile(workflowConfigPath, "utf8");
9318
+ const cfg = await fs41.readFile(workflowConfigPath, "utf8");
9157
9319
  const unfilled = cfg.split("\n").filter((l) => l.includes("<!-- fill in")).length;
9158
9320
  if (unfilled > 0) {
9159
9321
  suggest(
@@ -9165,11 +9327,11 @@ async function runDoctor(options) {
9165
9327
  ok("- .haus-workflow/workflow-config.md: OK (project-owned)");
9166
9328
  }
9167
9329
  }
9168
- const docsSkillInstalled = await fs40.pathExists(
9169
- path48.join(root, ".claude", "skills", "writing-documentation", "SKILL.md")
9330
+ const docsSkillInstalled = await fs41.pathExists(
9331
+ path49.join(root, ".claude", "skills", "writing-documentation", "SKILL.md")
9170
9332
  );
9171
9333
  if (docsSkillInstalled) {
9172
- if (await fs40.pathExists(path48.join(root, "docs", "SUMMARY.md"))) {
9334
+ if (await fs41.pathExists(path49.join(root, "docs", "SUMMARY.md"))) {
9173
9335
  ok("- PROJECT DOCS: OK (docs/SUMMARY.md present)");
9174
9336
  } else {
9175
9337
  suggest(
@@ -9180,7 +9342,7 @@ async function runDoctor(options) {
9180
9342
  }
9181
9343
  }
9182
9344
  if (workflowExists) {
9183
- const prettierIgnore = await readText(path48.join(root, ".prettierignore")) ?? "";
9345
+ const prettierIgnore = await readText(path49.join(root, ".prettierignore")) ?? "";
9184
9346
  const lines = prettierIgnore.split("\n");
9185
9347
  const missing = IGNORED_PATHS.filter((p) => !prettierIgnoreProtects(lines, p));
9186
9348
  if (missing.length > 0) {
@@ -9328,7 +9490,7 @@ async function runDoctor(options) {
9328
9490
  const catalogAgents = catalogItems === null ? [] : catalogItems.flatMap((item) => {
9329
9491
  if (item.type !== "agent") return [];
9330
9492
  const target = installTargetFor(item);
9331
- return target.ok ? [{ id: item.id, name: path48.basename(target.relPath, ".md") }] : [];
9493
+ return target.ok ? [{ id: item.id, name: path49.basename(target.relPath, ".md") }] : [];
9332
9494
  });
9333
9495
  const STACK_GATE_SKIP_CODES = /* @__PURE__ */ new Set([
9334
9496
  "requires-any-unsatisfied",
@@ -9556,7 +9718,7 @@ async function runDoctor(options) {
9556
9718
  let onDisk = false;
9557
9719
  for (const rel of row.paths ?? []) {
9558
9720
  const abs = resolveContainedPath(root, rel);
9559
- if (abs !== null && await fs40.pathExists(abs)) {
9721
+ if (abs !== null && await fs41.pathExists(abs)) {
9560
9722
  onDisk = true;
9561
9723
  break;
9562
9724
  }
@@ -9572,7 +9734,7 @@ async function runDoctor(options) {
9572
9734
  );
9573
9735
  }
9574
9736
  }
9575
- const pkgJson = await readJson(path48.join(packageRoot(), "package.json"));
9737
+ const pkgJson = await readJson(path49.join(packageRoot(), "package.json"));
9576
9738
  const currentVersion = pkgJson?.version ?? "0.0.0";
9577
9739
  const npmStatus = await fetchNpmVersionStatus(currentVersion);
9578
9740
  if (npmStatus.updateAvailable && npmStatus.latest !== null) {
@@ -9623,16 +9785,16 @@ async function runDoctor(options) {
9623
9785
  }
9624
9786
 
9625
9787
  // src/commands/update.ts
9626
- import path52 from "node:path";
9788
+ import path53 from "node:path";
9627
9789
  init_github_rate_limit();
9628
9790
  init_private_catalog_access();
9629
9791
 
9630
9792
  // src/claude/refresh-project.ts
9631
- import fs41 from "fs-extra";
9793
+ import fs42 from "fs-extra";
9632
9794
  init_fs();
9633
9795
  init_paths();
9634
9796
  async function isHausProject(root) {
9635
- if (await fs41.pathExists(hausPath(root, "recommendation.json"))) return true;
9797
+ if (await fs42.pathExists(hausPath(root, "recommendation.json"))) return true;
9636
9798
  const settings = await readJsonDetailed(claudePath(root, "settings.json"));
9637
9799
  if (settings.status === "ok" && settings.value._haus != null) return true;
9638
9800
  const lock = await readJsonDetailed(hausPath(root, "haus.lock.json"));
@@ -9650,8 +9812,8 @@ async function refreshProjectApply(root) {
9650
9812
 
9651
9813
  // src/install/apply.ts
9652
9814
  import crypto3 from "node:crypto";
9653
- import path50 from "node:path";
9654
- import fs43 from "fs-extra";
9815
+ import path51 from "node:path";
9816
+ import fs44 from "fs-extra";
9655
9817
  init_fs();
9656
9818
  init_logger();
9657
9819
  init_paths();
@@ -9760,8 +9922,8 @@ ${content2}`;
9760
9922
 
9761
9923
  // src/install/internal-channel.ts
9762
9924
  init_fs();
9763
- import path49 from "node:path";
9764
- import fs42 from "fs-extra";
9925
+ import path50 from "node:path";
9926
+ import fs43 from "fs-extra";
9765
9927
  var INSTALL_CONFIG_SCHEMA = "haus-install-config/1";
9766
9928
  var INTERNAL_NAME_PREFIXES = ["request", "slack-cowork"];
9767
9929
  function internalPrefixMatch(name) {
@@ -9778,7 +9940,7 @@ function isInternalAsset(stableId) {
9778
9940
  return internalPrefixMatch(name);
9779
9941
  }
9780
9942
  function installConfigPath() {
9781
- return path49.join(globalClaudeDir(), "haus", "install-config.json");
9943
+ return path50.join(globalClaudeDir(), "haus", "install-config.json");
9782
9944
  }
9783
9945
  async function readInstallConfig() {
9784
9946
  const raw = await readJson(installConfigPath());
@@ -9795,7 +9957,7 @@ async function writeInstallConfig(config) {
9795
9957
  }
9796
9958
  async function removeInstallConfig() {
9797
9959
  const p = installConfigPath();
9798
- if (fs42.pathExistsSync(p)) await fs42.remove(p);
9960
+ if (fs43.pathExistsSync(p)) await fs43.remove(p);
9799
9961
  }
9800
9962
  function envOverride() {
9801
9963
  const raw = process.env.HAUS_INTERNAL_CHANNEL?.trim().toLowerCase();
@@ -9825,12 +9987,12 @@ async function pruneEmptyDirsUpward(dirs, stopAt) {
9825
9987
  let dir = start;
9826
9988
  while (dir.startsWith(stopAt) && dir !== stopAt) {
9827
9989
  try {
9828
- if ((await fs43.readdir(dir)).length > 0) break;
9829
- await fs43.remove(dir);
9990
+ if ((await fs44.readdir(dir)).length > 0) break;
9991
+ await fs44.remove(dir);
9830
9992
  } catch {
9831
9993
  break;
9832
9994
  }
9833
- dir = path50.dirname(dir);
9995
+ dir = path51.dirname(dir);
9834
9996
  }
9835
9997
  }
9836
9998
  }
@@ -9839,65 +10001,65 @@ function hashContent(content2) {
9839
10001
  }
9840
10002
  function destFileMissing(destPath) {
9841
10003
  try {
9842
- return !fs43.statSync(destPath).isFile();
10004
+ return !fs44.statSync(destPath).isFile();
9843
10005
  } catch {
9844
10006
  return true;
9845
10007
  }
9846
10008
  }
9847
10009
  function sourceVersion() {
9848
10010
  try {
9849
- const pkgPath = path50.join(packageRoot(), "package.json");
9850
- const pkg = JSON.parse(fs43.readFileSync(pkgPath, "utf8"));
10011
+ const pkgPath = path51.join(packageRoot(), "package.json");
10012
+ const pkg = JSON.parse(fs44.readFileSync(pkgPath, "utf8"));
9851
10013
  return `${pkg.name ?? "haus"}@${pkg.version ?? "0.0.0"}`;
9852
10014
  } catch {
9853
10015
  return "haus@0.0.0";
9854
10016
  }
9855
10017
  }
9856
10018
  function globalSrcDir() {
9857
- return path50.join(packageRoot(), "library", "global");
10019
+ return path51.join(packageRoot(), "library", "global");
9858
10020
  }
9859
10021
  function collectSkillAuxFiles(skillsDir, claudeDir, skillName) {
9860
- const skillDir = path50.join(skillsDir, skillName);
10022
+ const skillDir = path51.join(skillsDir, skillName);
9861
10023
  const entries = [];
9862
- for (const rel of fs43.readdirSync(skillDir, { recursive: true })) {
10024
+ for (const rel of fs44.readdirSync(skillDir, { recursive: true })) {
9863
10025
  if (rel === "SKILL.md") continue;
9864
- const abs = path50.join(skillDir, rel);
9865
- if (!fs43.statSync(abs).isFile()) continue;
9866
- const posixRel = rel.split(path50.sep).join("/");
10026
+ const abs = path51.join(skillDir, rel);
10027
+ if (!fs44.statSync(abs).isFile()) continue;
10028
+ const posixRel = rel.split(path51.sep).join("/");
9867
10029
  entries.push({
9868
10030
  stableId: `skill.${skillName}.${posixRel.replace(/\//g, ".")}`,
9869
- srcRelPath: path50.join("library", "global", "skills", skillName, rel),
9870
- destPath: path50.join(claudeDir, "skills", skillName, rel)
10031
+ srcRelPath: path51.join("library", "global", "skills", skillName, rel),
10032
+ destPath: path51.join(claudeDir, "skills", skillName, rel)
9871
10033
  });
9872
10034
  }
9873
10035
  return entries;
9874
10036
  }
9875
10037
  function collectFlatChannel(srcDir, claudeDir, dirName, idPrefix) {
9876
10038
  const entries = [];
9877
- const channelDir = path50.join(srcDir, dirName);
9878
- if (!fs43.pathExistsSync(channelDir)) return entries;
9879
- for (const fileName of fs43.readdirSync(channelDir)) {
10039
+ const channelDir = path51.join(srcDir, dirName);
10040
+ if (!fs44.pathExistsSync(channelDir)) return entries;
10041
+ for (const fileName of fs44.readdirSync(channelDir)) {
9880
10042
  if (!fileName.endsWith(".md")) continue;
9881
10043
  const name = fileName.slice(0, -".md".length);
9882
10044
  entries.push({
9883
10045
  stableId: `${idPrefix}.${name}`,
9884
- srcRelPath: path50.join("library", "global", dirName, fileName),
9885
- destPath: path50.join(claudeDir, dirName, fileName)
10046
+ srcRelPath: path51.join("library", "global", dirName, fileName),
10047
+ destPath: path51.join(claudeDir, dirName, fileName)
9886
10048
  });
9887
10049
  }
9888
10050
  return entries;
9889
10051
  }
9890
10052
  function collectSourceFiles(srcDir, claudeDir) {
9891
10053
  const entries = [];
9892
- const skillsDir = path50.join(srcDir, "skills");
9893
- if (fs43.pathExistsSync(skillsDir)) {
9894
- for (const skillName of fs43.readdirSync(skillsDir)) {
9895
- const skillFile = path50.join(skillsDir, skillName, "SKILL.md");
9896
- if (fs43.pathExistsSync(skillFile)) {
10054
+ const skillsDir = path51.join(srcDir, "skills");
10055
+ if (fs44.pathExistsSync(skillsDir)) {
10056
+ for (const skillName of fs44.readdirSync(skillsDir)) {
10057
+ const skillFile = path51.join(skillsDir, skillName, "SKILL.md");
10058
+ if (fs44.pathExistsSync(skillFile)) {
9897
10059
  entries.push({
9898
10060
  stableId: `skill.${skillName}`,
9899
- srcRelPath: path50.join("library", "global", "skills", skillName, "SKILL.md"),
9900
- destPath: path50.join(claudeDir, "skills", skillName, "SKILL.md")
10061
+ srcRelPath: path51.join("library", "global", "skills", skillName, "SKILL.md"),
10062
+ destPath: path51.join(claudeDir, "skills", skillName, "SKILL.md")
9901
10063
  });
9902
10064
  entries.push(...collectSkillAuxFiles(skillsDir, claudeDir, skillName));
9903
10065
  }
@@ -9937,7 +10099,7 @@ async function applyInstall(options = {}) {
9937
10099
  };
9938
10100
  const manifestFiles = [];
9939
10101
  for (const entry of sourceFiles) {
9940
- const srcPath = path50.join(packageRoot(), entry.srcRelPath);
10102
+ const srcPath = path51.join(packageRoot(), entry.srcRelPath);
9941
10103
  const rawContent = await readText(srcPath);
9942
10104
  if (rawContent === void 0) {
9943
10105
  warn(`Source file not found: ${entry.srcRelPath}`);
@@ -9966,7 +10128,7 @@ async function applyInstall(options = {}) {
9966
10128
  }
9967
10129
  continue;
9968
10130
  }
9969
- const destExists = fs43.pathExistsSync(entry.destPath);
10131
+ const destExists = fs44.pathExistsSync(entry.destPath);
9970
10132
  if (destExists) {
9971
10133
  const currentContent = await readText(entry.destPath);
9972
10134
  if (currentContent !== void 0) {
@@ -10006,7 +10168,7 @@ async function applyInstall(options = {}) {
10006
10168
  }
10007
10169
  let mergedSettings;
10008
10170
  if (!check) {
10009
- const fragmentPath = path50.join(srcDir, "settings-fragments", "hooks.json");
10171
+ const fragmentPath = path51.join(srcDir, "settings-fragments", "hooks.json");
10010
10172
  const fragments = await loadHooksFragment(fragmentPath);
10011
10173
  const settings = await readSettings();
10012
10174
  const { settings: hookSettings, addedIds } = mergeHooks(settings, fragments);
@@ -10026,14 +10188,14 @@ async function applyInstall(options = {}) {
10026
10188
  manifestFiles.push(entry);
10027
10189
  continue;
10028
10190
  }
10029
- if (!fs43.pathExistsSync(entry.destPath)) continue;
10191
+ if (!fs44.pathExistsSync(entry.destPath)) continue;
10030
10192
  const content2 = await readText(entry.destPath);
10031
10193
  if (!content2) continue;
10032
10194
  const hasHeader = parseMarkdownHeader(content2) !== void 0;
10033
10195
  const currentHash = hashContent(content2);
10034
10196
  if (hasHeader && currentHash === entry.hash) {
10035
- if (!dryRun) await fs43.remove(entry.destPath);
10036
- emptiedDirs.add(path50.dirname(entry.destPath));
10197
+ if (!dryRun) await fs44.remove(entry.destPath);
10198
+ emptiedDirs.add(path51.dirname(entry.destPath));
10037
10199
  result.deleted.push(entry.destPath);
10038
10200
  } else {
10039
10201
  warn(`Orphaned file ${entry.destPath} was user-modified \u2014 leaving in place`);
@@ -10127,7 +10289,7 @@ function summarizeLockDiff(before, after) {
10127
10289
  // src/update/lockfile.ts
10128
10290
  import { constants as fsConstants } from "node:fs";
10129
10291
  import { copyFile, mkdir, readFile as readFile2 } from "node:fs/promises";
10130
- import path51 from "node:path";
10292
+ import path52 from "node:path";
10131
10293
  init_fs();
10132
10294
  init_paths();
10133
10295
  async function readLockSummary(root) {
@@ -10167,7 +10329,7 @@ async function backupLockfile(root) {
10167
10329
  await mkdir(backupDir, { recursive: true });
10168
10330
  let stamp = Date.now();
10169
10331
  for (; ; ) {
10170
- const dest = path51.join(backupDir, lockBackupFileName(stamp));
10332
+ const dest = path52.join(backupDir, lockBackupFileName(stamp));
10171
10333
  try {
10172
10334
  await copyFile(lockPath, dest, fsConstants.COPYFILE_EXCL);
10173
10335
  return;
@@ -10218,7 +10380,7 @@ function diffLock(before, after) {
10218
10380
  }
10219
10381
  async function hasLocalOverrides(root) {
10220
10382
  try {
10221
- await readFile2(path51.join(root, ".claude", "settings.json"), "utf8");
10383
+ await readFile2(path52.join(root, ".claude", "settings.json"), "utf8");
10222
10384
  return true;
10223
10385
  } catch {
10224
10386
  return false;
@@ -10263,7 +10425,7 @@ async function runUpdate(options) {
10263
10425
  return;
10264
10426
  }
10265
10427
  if (options.check) {
10266
- const pkgJson2 = await readJson(path52.join(packageRoot(), "package.json"));
10428
+ const pkgJson2 = await readJson(path53.join(packageRoot(), "package.json"));
10267
10429
  const currentVersion2 = pkgJson2?.version ?? "0.0.0";
10268
10430
  const [
10269
10431
  status,
@@ -10315,7 +10477,7 @@ async function runUpdate(options) {
10315
10477
  }
10316
10478
  return;
10317
10479
  }
10318
- const pkgJson = await readJson(path52.join(packageRoot(), "package.json"));
10480
+ const pkgJson = await readJson(path53.join(packageRoot(), "package.json"));
10319
10481
  const currentVersion = pkgJson?.version ?? "0.0.0";
10320
10482
  const npmStatus = await fetchNpmVersionStatus(currentVersion);
10321
10483
  if (npmStatus.updateAvailable && npmStatus.latest !== null) {
@@ -10386,7 +10548,7 @@ async function runFromHookCheck(root) {
10386
10548
  try {
10387
10549
  const summary = await readLockSummary(root);
10388
10550
  if (summary.count === 0) return;
10389
- const pkgJson = await readJson(path52.join(packageRoot(), "package.json"));
10551
+ const pkgJson = await readJson(path53.join(packageRoot(), "package.json"));
10390
10552
  const currentVersion = pkgJson?.version ?? "0.0.0";
10391
10553
  const pin = readProjectCatalogPin(root);
10392
10554
  const honoursPin = pin.kind === "pinned" && summary.catalogRef === pin.ref;
@@ -10581,7 +10743,7 @@ init_exec();
10581
10743
  init_logger();
10582
10744
  import { existsSync as existsSync2 } from "node:fs";
10583
10745
  import { readdir } from "node:fs/promises";
10584
- import path53 from "node:path";
10746
+ import path54 from "node:path";
10585
10747
  var GIT_LOCATION_VARS = [
10586
10748
  "GIT_DIR",
10587
10749
  "GIT_WORK_TREE",
@@ -10641,11 +10803,11 @@ async function runClone(url, opts = {}) {
10641
10803
  process.exitCode = 1;
10642
10804
  return;
10643
10805
  }
10644
- const target = path53.resolve(opts.dir?.trim() || repoNameFromUrl(url));
10806
+ const target = path54.resolve(opts.dir?.trim() || repoNameFromUrl(url));
10645
10807
  if (existsSync2(target) && !await isEmptyDirectory(target)) {
10646
10808
  const check = await checkExistingTarget(target, url);
10647
10809
  if (check.kind === "matches") {
10648
- log(`\u2022 ${path53.basename(target)} already cloned here, matches ${url} \u2014 skipped`);
10810
+ log(`\u2022 ${path54.basename(target)} already cloned here, matches ${url} \u2014 skipped`);
10649
10811
  return;
10650
10812
  }
10651
10813
  const message2 = check.kind === "not-a-repo" ? `${target} already exists and is not a git repository \u2014 refusing to clone ${url} there. Remove or rename it, or pass a different [dir].` : `${target} already exists as a different repository (origin: ${check.origin ?? "(no origin remote)"}) \u2014 refusing to clone ${url} there. Remove or rename it, or pass a different [dir].`;
@@ -10694,8 +10856,8 @@ function isCatalogItemInstallable(item) {
10694
10856
 
10695
10857
  // src/install/scaffold.ts
10696
10858
  init_logger();
10697
- import path54 from "node:path";
10698
- import fs44 from "fs-extra";
10859
+ import path55 from "node:path";
10860
+ import fs45 from "fs-extra";
10699
10861
  async function scaffoldConfigItems(projectRoot, catalogRoot, items, opts = {}) {
10700
10862
  const scaffolded = [];
10701
10863
  const skipped = [];
@@ -10708,27 +10870,27 @@ async function scaffoldConfigItems(projectRoot, catalogRoot, items, opts = {}) {
10708
10870
  skipped.push(name);
10709
10871
  if (outcome === "skipped-exists") skippedExisting.push(name);
10710
10872
  };
10711
- const realRoot = await fs44.realpath(catalogRoot).catch(() => null);
10873
+ const realRoot = await fs45.realpath(catalogRoot).catch(() => null);
10712
10874
  for (const item of items) {
10713
10875
  if (item.type !== "config") continue;
10714
- const sourcePath = path54.resolve(catalogRoot, item.path);
10715
- const rel = path54.relative(catalogRoot, sourcePath);
10716
- const escapes = rel === "" || path54.isAbsolute(rel) || rel.split(/[/\\]/).some((seg) => seg === "..");
10876
+ const sourcePath = path55.resolve(catalogRoot, item.path);
10877
+ const rel = path55.relative(catalogRoot, sourcePath);
10878
+ const escapes = rel === "" || path55.isAbsolute(rel) || rel.split(/[/\\]/).some((seg) => seg === "..");
10717
10879
  if (escapes) {
10718
10880
  warn(`Skipping ${item.id}: path '${item.path}' escapes the catalog root`);
10719
10881
  continue;
10720
10882
  }
10721
- const realSource = await fs44.realpath(sourcePath).catch(() => null);
10883
+ const realSource = await fs45.realpath(sourcePath).catch(() => null);
10722
10884
  if (!realRoot || !realSource) {
10723
10885
  warn(`Skipping ${item.id}: source not found at ${sourcePath}`);
10724
10886
  continue;
10725
10887
  }
10726
- const realRel = path54.relative(realRoot, realSource);
10727
- if (realRel !== "" && (path54.isAbsolute(realRel) || realRel.split(/[/\\]/)[0] === "..")) {
10888
+ const realRel = path55.relative(realRoot, realSource);
10889
+ if (realRel !== "" && (path55.isAbsolute(realRel) || realRel.split(/[/\\]/)[0] === "..")) {
10728
10890
  warn(`Skipping ${item.id}: path '${item.path}' resolves outside the catalog root`);
10729
10891
  continue;
10730
10892
  }
10731
- const stat = await fs44.lstat(sourcePath).catch(() => null);
10893
+ const stat = await fs45.lstat(sourcePath).catch(() => null);
10732
10894
  if (!stat) {
10733
10895
  warn(`Skipping ${item.id}: source not found at ${sourcePath}`);
10734
10896
  continue;
@@ -10738,14 +10900,14 @@ async function scaffoldConfigItems(projectRoot, catalogRoot, items, opts = {}) {
10738
10900
  continue;
10739
10901
  }
10740
10902
  if (stat.isFile()) {
10741
- const filename = path54.basename(sourcePath);
10742
- const dest = path54.join(projectRoot, filename);
10903
+ const filename = path55.basename(sourcePath);
10904
+ const dest = path55.join(projectRoot, filename);
10743
10905
  record(filename, await scaffoldFile(sourcePath, dest, item.id, projectRoot, opts));
10744
10906
  } else if (stat.isDirectory()) {
10745
- const entries = await fs44.readdir(sourcePath);
10907
+ const entries = await fs45.readdir(sourcePath);
10746
10908
  for (const entry of entries) {
10747
- const src = path54.join(sourcePath, entry);
10748
- const dest = path54.join(projectRoot, entry);
10909
+ const src = path55.join(sourcePath, entry);
10910
+ const dest = path55.join(projectRoot, entry);
10749
10911
  record(entry, await scaffoldFile(src, dest, item.id, projectRoot, opts));
10750
10912
  }
10751
10913
  }
@@ -10753,42 +10915,42 @@ async function scaffoldConfigItems(projectRoot, catalogRoot, items, opts = {}) {
10753
10915
  return { scaffolded, skipped, skippedExisting };
10754
10916
  }
10755
10917
  async function scaffoldFile(src, dest, itemId, projectRoot, opts) {
10756
- const destDisplay = path54.relative(projectRoot, dest) || path54.basename(dest);
10757
- const srcStat = await fs44.lstat(src).catch(() => null);
10918
+ const destDisplay = path55.relative(projectRoot, dest) || path55.basename(dest);
10919
+ const srcStat = await fs45.lstat(src).catch(() => null);
10758
10920
  if (!srcStat) {
10759
- warn(`Skipping ${path54.basename(src)}: source not found`);
10921
+ warn(`Skipping ${path55.basename(src)}: source not found`);
10760
10922
  return "skipped-refused";
10761
10923
  }
10762
10924
  if (srcStat.isSymbolicLink()) {
10763
- warn(`Skipping ${path54.basename(src)}: source is a symlink`);
10925
+ warn(`Skipping ${path55.basename(src)}: source is a symlink`);
10764
10926
  return "skipped-refused";
10765
10927
  }
10766
- const exists = await fs44.pathExists(dest);
10767
- if (exists && !opts.force) {
10928
+ const exists2 = await fs45.pathExists(dest);
10929
+ if (exists2 && !opts.force) {
10768
10930
  warn(`Skipping ${destDisplay}: already exists \u2014 re-run with --force to overwrite it`);
10769
10931
  return "skipped-exists";
10770
10932
  }
10771
10933
  if (opts.dryRun) {
10772
- log(`[dry-run] would ${exists ? "overwrite" : "create"} ${path54.basename(dest)} (${itemId})`);
10934
+ log(`[dry-run] would ${exists2 ? "overwrite" : "create"} ${path55.basename(dest)} (${itemId})`);
10773
10935
  return "scaffolded";
10774
10936
  }
10775
- if (exists && opts.force) {
10776
- await fs44.remove(dest);
10937
+ if (exists2 && opts.force) {
10938
+ await fs45.remove(dest);
10777
10939
  }
10778
- await fs44.ensureDir(path54.dirname(dest));
10779
- await fs44.copy(src, dest, {
10940
+ await fs45.ensureDir(path55.dirname(dest));
10941
+ await fs45.copy(src, dest, {
10780
10942
  overwrite: true,
10781
10943
  dereference: false,
10782
10944
  // A vanished/unreadable entry mid-walk must skip that path, not crash the copy.
10783
10945
  filter: (s) => {
10784
10946
  try {
10785
- return !fs44.lstatSync(s).isSymbolicLink();
10947
+ return !fs45.lstatSync(s).isSymbolicLink();
10786
10948
  } catch {
10787
10949
  return false;
10788
10950
  }
10789
10951
  }
10790
10952
  });
10791
- if (!opts.quiet) log(`${exists ? "Overwrote" : "Created"} ${path54.basename(dest)} (${itemId})`);
10953
+ if (!opts.quiet) log(`${exists2 ? "Overwrote" : "Created"} ${path55.basename(dest)} (${itemId})`);
10792
10954
  return "scaffolded";
10793
10955
  }
10794
10956
 
@@ -10920,7 +11082,7 @@ async function runDecisionsGuard(options = {}) {
10920
11082
  }
10921
11083
 
10922
11084
  // src/commands/decisions.ts
10923
- import path55 from "node:path";
11085
+ import path56 from "node:path";
10924
11086
 
10925
11087
  // src/decisions/next-number.ts
10926
11088
  async function nextDecisionNumber(root) {
@@ -11031,7 +11193,7 @@ async function runDecisions(sub, arg, options = {}) {
11031
11193
  return;
11032
11194
  }
11033
11195
  if (sub === "validate") {
11034
- const target = arg ? path55.resolve(root, arg) : void 0;
11196
+ const target = arg ? path56.resolve(root, arg) : void 0;
11035
11197
  if (!target) {
11036
11198
  error("usage: haus decisions validate <path-to-decision.md>");
11037
11199
  process.exitCode = 1;
@@ -11500,27 +11662,27 @@ function guardBash(command) {
11500
11662
  }
11501
11663
 
11502
11664
  // src/security/guard-file-access.ts
11503
- import fs45 from "node:fs";
11504
- import path56 from "node:path";
11665
+ import fs46 from "node:fs";
11666
+ import path57 from "node:path";
11505
11667
  function matchesDenyTier(candidate) {
11506
11668
  const normalized = normalizePathForMatch(candidate);
11507
11669
  return DENY_PATH_REGEXES.some((re) => re.test(normalized));
11508
11670
  }
11509
11671
  function resolveOnDisk(candidate) {
11510
11672
  try {
11511
- const resolved = fs45.realpathSync(candidate);
11512
- return resolved === path56.resolve(candidate) ? {} : { resolved };
11673
+ const resolved = fs46.realpathSync(candidate);
11674
+ return resolved === path57.resolve(candidate) ? {} : { resolved };
11513
11675
  } catch {
11514
11676
  try {
11515
- if (fs45.lstatSync(candidate).isSymbolicLink()) return { unverifiable: true };
11677
+ if (fs46.lstatSync(candidate).isSymbolicLink()) return { unverifiable: true };
11516
11678
  } catch {
11517
11679
  }
11518
- const parent = path56.dirname(candidate);
11680
+ const parent = path57.dirname(candidate);
11519
11681
  if (parent === candidate) return {};
11520
11682
  const viaParent = resolveOnDisk(parent);
11521
11683
  if (viaParent.unverifiable) return { unverifiable: true };
11522
11684
  if (viaParent.resolved)
11523
- return { resolved: path56.join(viaParent.resolved, path56.basename(candidate)) };
11685
+ return { resolved: path57.join(viaParent.resolved, path57.basename(candidate)) };
11524
11686
  return {};
11525
11687
  }
11526
11688
  }
@@ -12070,7 +12232,7 @@ function buildOptInEligible(items, skipped) {
12070
12232
  }
12071
12233
 
12072
12234
  // src/commands/workspace/discover.ts
12073
- import path57 from "node:path";
12235
+ import path58 from "node:path";
12074
12236
  import fg6 from "fast-glob";
12075
12237
  import YAML4 from "yaml";
12076
12238
  init_fs();
@@ -12113,10 +12275,10 @@ async function findRepoRoots(workspaceRoot, maxDepth = DEFAULT_MAX_DEPTH) {
12113
12275
  const gitDirs = /* @__PURE__ */ new Set();
12114
12276
  const manifestDirs = /* @__PURE__ */ new Set();
12115
12277
  for (const match of gitMatches) {
12116
- gitDirs.add(path57.posix.dirname(match));
12278
+ gitDirs.add(path58.posix.dirname(match));
12117
12279
  }
12118
12280
  for (const match of manifestMatches) {
12119
- manifestDirs.add(path57.posix.dirname(match));
12281
+ manifestDirs.add(path58.posix.dirname(match));
12120
12282
  }
12121
12283
  const repoRoots = [...gitDirs];
12122
12284
  const manifestSorted = [...manifestDirs].sort(
@@ -12133,9 +12295,9 @@ async function findRepoRoots(workspaceRoot, maxDepth = DEFAULT_MAX_DEPTH) {
12133
12295
  async function discoverRepos(workspaceRoot, maxDepth = DEFAULT_MAX_DEPTH) {
12134
12296
  const repoRoots = (await findRepoRoots(workspaceRoot, maxDepth)).filter((dir) => dir !== ".");
12135
12297
  return mapWithConcurrency(repoRoots, async (relDir) => {
12136
- const absDir = path57.resolve(workspaceRoot, relDir);
12137
- const pkg = await readJson(path57.join(absDir, "package.json"));
12138
- const name = typeof pkg?.name === "string" && pkg.name.length > 0 ? pkg.name : path57.basename(absDir);
12298
+ const absDir = path58.resolve(workspaceRoot, relDir);
12299
+ const pkg = await readJson(path58.join(absDir, "package.json"));
12300
+ const name = typeof pkg?.name === "string" && pkg.name.length > 0 ? pkg.name : path58.basename(absDir);
12139
12301
  let role = "auto";
12140
12302
  try {
12141
12303
  const scan = await scanProject(absDir);
@@ -12178,7 +12340,7 @@ function renderWorkspaceYaml(config) {
12178
12340
  });
12179
12341
  }
12180
12342
  async function runDiscover(workspaceRoot, opts = {}) {
12181
- const yamlPath = path57.join(workspaceRoot, "haus.workspace.yaml");
12343
+ const yamlPath = path58.join(workspaceRoot, "haus.workspace.yaml");
12182
12344
  const existingText = await readText(yamlPath);
12183
12345
  const existing = parseWorkspaceConfig(existingText);
12184
12346
  if (existingText && !existing) {
@@ -12611,11 +12773,11 @@ async function runInstall(options, deps = {}) {
12611
12773
  }
12612
12774
 
12613
12775
  // src/commands/new.ts
12614
- import path59 from "node:path";
12776
+ import path60 from "node:path";
12615
12777
 
12616
12778
  // src/new-project/create-project.ts
12617
- import path58 from "node:path";
12618
- import fs46 from "fs-extra";
12779
+ import path59 from "node:path";
12780
+ import fs47 from "fs-extra";
12619
12781
  init_exec();
12620
12782
  init_logger();
12621
12783
 
@@ -12800,10 +12962,10 @@ var defaultDeps = {
12800
12962
  };
12801
12963
  var NAME_RE = /^[a-z0-9][a-z0-9._-]*$/;
12802
12964
  async function recordPackageManager(projectPath, pm, deps) {
12803
- const pkgPath = path58.join(projectPath, "package.json");
12965
+ const pkgPath = path59.join(projectPath, "package.json");
12804
12966
  let pkg;
12805
12967
  try {
12806
- pkg = await fs46.readJson(pkgPath);
12968
+ pkg = await fs47.readJson(pkgPath);
12807
12969
  } catch {
12808
12970
  return {};
12809
12971
  }
@@ -12818,7 +12980,7 @@ async function recordPackageManager(projectPath, pm, deps) {
12818
12980
  note: `installed ${pm} is ${version}, which haus does not support \u2014 the package manager was not recorded, so the scanner will report it as unknown until you upgrade`
12819
12981
  };
12820
12982
  }
12821
- await fs46.writeJson(pkgPath, { ...pkg, packageManager: field }, { spaces: 2 });
12983
+ await fs47.writeJson(pkgPath, { ...pkg, packageManager: field }, { spaces: 2 });
12822
12984
  return {};
12823
12985
  }
12824
12986
  var CONFIG_TOOLING = {
@@ -12831,12 +12993,12 @@ var CONFIG_TOOLING = {
12831
12993
  }
12832
12994
  };
12833
12995
  async function ensureConfigTooling(projectPath, scaffolded) {
12834
- const needed = scaffolded.map((file) => CONFIG_TOOLING[path58.basename(file)]).filter((entry) => entry !== void 0);
12996
+ const needed = scaffolded.map((file) => CONFIG_TOOLING[path59.basename(file)]).filter((entry) => entry !== void 0);
12835
12997
  if (needed.length === 0) return [];
12836
- const pkgPath = path58.join(projectPath, "package.json");
12998
+ const pkgPath = path59.join(projectPath, "package.json");
12837
12999
  let pkg;
12838
13000
  try {
12839
- pkg = await fs46.readJson(pkgPath);
13001
+ pkg = await fs47.readJson(pkgPath);
12840
13002
  } catch {
12841
13003
  return [];
12842
13004
  }
@@ -12860,12 +13022,12 @@ async function ensureConfigTooling(projectPath, scaffolded) {
12860
13022
  }
12861
13023
  }
12862
13024
  if (!changed) return [];
12863
- await fs46.writeJson(pkgPath, { ...pkg, devDependencies, scripts }, { spaces: 2 });
13025
+ await fs47.writeJson(pkgPath, { ...pkg, devDependencies, scripts }, { spaces: 2 });
12864
13026
  return added;
12865
13027
  }
12866
13028
  async function createProject(opts, deps = defaultDeps) {
12867
13029
  const steps = [];
12868
- const projectPath = path58.resolve(opts.parentDir, opts.name);
13030
+ const projectPath = path59.resolve(opts.parentDir, opts.name);
12869
13031
  const installHint = `cd ${opts.name} && ${opts.pm}${opts.pm === "yarn" ? "" : " install"}`;
12870
13032
  const fail = (step, detail) => {
12871
13033
  steps.push({ step, status: "failed", detail });
@@ -12873,20 +13035,20 @@ async function createProject(opts, deps = defaultDeps) {
12873
13035
  };
12874
13036
  const stack = getStack(opts.stackId);
12875
13037
  if (!stack) return fail("preflight", `Unknown stack "${opts.stackId}". Run \`haus new --list\`.`);
12876
- const parentStat = await fs46.stat(path58.dirname(projectPath)).catch(() => null);
13038
+ const parentStat = await fs47.stat(path59.dirname(projectPath)).catch(() => null);
12877
13039
  if (!parentStat?.isDirectory()) {
12878
13040
  return fail(
12879
13041
  "preflight",
12880
13042
  `Parent directory ${opts.parentDir} is not an existing directory \u2014 pass an existing one to --dir.`
12881
13043
  );
12882
13044
  }
12883
- if (!NAME_RE.test(opts.name) || opts.name.includes("/") || opts.name.includes("\\") || path58.dirname(projectPath) !== path58.resolve(opts.parentDir)) {
13045
+ if (!NAME_RE.test(opts.name) || opts.name.includes("/") || opts.name.includes("\\") || path59.dirname(projectPath) !== path59.resolve(opts.parentDir)) {
12884
13046
  return fail(
12885
13047
  "preflight",
12886
13048
  `Invalid project name "${opts.name}" (lowercase letters, digits, . _ - only).`
12887
13049
  );
12888
13050
  }
12889
- if (await fs46.pathExists(projectPath)) {
13051
+ if (await fs47.pathExists(projectPath)) {
12890
13052
  return fail("preflight", `Target ${projectPath} already exists \u2014 refusing to write into it.`);
12891
13053
  }
12892
13054
  const scaffoldArgs = stack.scaffold.args.map(
@@ -12958,13 +13120,13 @@ async function createProject(opts, deps = defaultDeps) {
12958
13120
  });
12959
13121
  if (scaffold.exitCode !== 0) {
12960
13122
  const why = scaffold.killed ? `${pinned} did not complete: ${scaffold.stderr.trim() || `timed out after ${SCAFFOLD_TIMEOUT_MS}ms`}. Scaffolders that stall this way are usually waiting on a prompt despite --yes \u2014 run it yourself to see: npx ${npxArgs.join(" ")}` : `${pinned} exited ${scaffold.exitCode}.`;
12961
- const leftBehind = await fs46.pathExists(projectPath) ? ` The directory was left for inspection \u2014 review it and remove it manually if unwanted (haus never auto-deletes).` : "";
13123
+ const leftBehind = await fs47.pathExists(projectPath) ? ` The directory was left for inspection \u2014 review it and remove it manually if unwanted (haus never auto-deletes).` : "";
12962
13124
  return fail("scaffold", `${why}${leftBehind}`);
12963
13125
  }
12964
13126
  steps.push({ step: "scaffold", status: "done" });
12965
13127
  phase = "git";
12966
13128
  if (!opts.json) log("\u25B6 git: init/branch/commit");
12967
- if (!await fs46.pathExists(path58.join(projectPath, ".git"))) {
13129
+ if (!await fs47.pathExists(path59.join(projectPath, ".git"))) {
12968
13130
  const init = await deps.git(["init"], { cwd: projectPath });
12969
13131
  if (init.exitCode !== 0) return fail("git", `git init failed: ${init.stderr}`);
12970
13132
  }
@@ -13190,7 +13352,7 @@ function resolveNewOptions(stackArg, nameArg, flags, cwd) {
13190
13352
  if (flags.dir !== void 0 && flags.dir.trim() === "") {
13191
13353
  return { ok: false, error: "--dir needs a path (omit it to scaffold into the cwd)." };
13192
13354
  }
13193
- const parentDir = flags.dir === void 0 ? cwd : path59.resolve(cwd, flags.dir);
13355
+ const parentDir = flags.dir === void 0 ? cwd : path60.resolve(cwd, flags.dir);
13194
13356
  const pm = flags.pm ?? "yarn";
13195
13357
  if (!PMS.includes(pm)) {
13196
13358
  return { ok: false, error: `Unsupported --pm "${pm}" \u2014 use yarn, npm, or pnpm.` };
@@ -13288,7 +13450,7 @@ async function runRecommend(options) {
13288
13450
  }
13289
13451
 
13290
13452
  // src/commands/remove.ts
13291
- import path60 from "node:path";
13453
+ import path61 from "node:path";
13292
13454
  init_fs();
13293
13455
  init_logger();
13294
13456
  init_paths();
@@ -13395,7 +13557,7 @@ async function runRemove(ids, options = {}) {
13395
13557
  }
13396
13558
  const allAbs = cleared.flatMap(({ existing }) => existing.map(({ abs }) => abs));
13397
13559
  const backupRoot = await backupTrackedPaths(root, allAbs, say, "remove");
13398
- report.backupDir = backupRoot === null ? null : path60.basename(backupRoot);
13560
+ report.backupDir = backupRoot === null ? null : path61.basename(backupRoot);
13399
13561
  for (const { id, existing } of cleared) {
13400
13562
  await deleteTrackedPaths(root, id, existing, { verb: "Removed", reason: "" }, say);
13401
13563
  }
@@ -13485,8 +13647,8 @@ async function runScan(options) {
13485
13647
  }
13486
13648
 
13487
13649
  // src/commands/undo.ts
13488
- import path61 from "node:path";
13489
- import fs47 from "fs-extra";
13650
+ import path62 from "node:path";
13651
+ import fs48 from "fs-extra";
13490
13652
 
13491
13653
  // src/claude/managed-paths.ts
13492
13654
  init_paths();
@@ -13505,7 +13667,7 @@ init_paths();
13505
13667
  async function collectCoreManagedPaths(root) {
13506
13668
  const existing = [];
13507
13669
  for (const abs of coreManagedAbsolutePaths(root)) {
13508
- if (await fs47.pathExists(abs)) existing.push(abs);
13670
+ if (await fs48.pathExists(abs)) existing.push(abs);
13509
13671
  }
13510
13672
  return existing;
13511
13673
  }
@@ -13519,7 +13681,7 @@ async function collectLockTrackedPaths(root) {
13519
13681
  const unsafeByRel = await unsafeReasonsFor(root, relPaths);
13520
13682
  const existing = await resolveExistingTrackedPaths(root, id, relPaths, log, unsafeByRel);
13521
13683
  for (const [rel, reason] of unsafeByRel) {
13522
- if (reason !== null) preserved.push(path61.resolve(root, rel));
13684
+ if (reason !== null) preserved.push(path62.resolve(root, rel));
13523
13685
  }
13524
13686
  if (existing.length === 0) continue;
13525
13687
  const gate = await hashGateEntry(root, { hash: row.hash }, existing);
@@ -13535,51 +13697,51 @@ async function collectLockTrackedPaths(root) {
13535
13697
  }
13536
13698
  async function settingsHasHausContent(root) {
13537
13699
  const settingsPath = claudePath(root, "settings.json");
13538
- if (!await fs47.pathExists(settingsPath)) return false;
13700
+ if (!await fs48.pathExists(settingsPath)) return false;
13539
13701
  const settings = await readProjectSettings(root);
13540
13702
  return settings._haus != null;
13541
13703
  }
13542
13704
  async function claudeMdHasHausBlock(root) {
13543
- const filePath = path61.join(root, "CLAUDE.md");
13544
- if (!await fs47.pathExists(filePath)) return false;
13545
- const text = await fs47.readFile(filePath, "utf8");
13705
+ const filePath = path62.join(root, "CLAUDE.md");
13706
+ if (!await fs48.pathExists(filePath)) return false;
13707
+ const text = await fs48.readFile(filePath, "utf8");
13546
13708
  return text.includes(BLOCK_BEGIN);
13547
13709
  }
13548
13710
  async function stripProjectSettings(root) {
13549
13711
  const settingsPath = claudePath(root, "settings.json");
13550
- if (!await fs47.pathExists(settingsPath)) return false;
13712
+ if (!await fs48.pathExists(settingsPath)) return false;
13551
13713
  let settings = await readProjectSettings(root);
13552
13714
  settings = stripHausHooks(stripHausAsk(stripHausAllow(stripHausDeny(settings))));
13553
13715
  const hasContent = Object.keys(settings).length > 0;
13554
13716
  if (hasContent) {
13555
13717
  await writeProjectSettings(root, settings);
13556
- log(`Stripped haus rules from ${path61.relative(root, settingsPath)} (user settings preserved).`);
13718
+ log(`Stripped haus rules from ${path62.relative(root, settingsPath)} (user settings preserved).`);
13557
13719
  return true;
13558
13720
  }
13559
- await fs47.remove(settingsPath);
13560
- log(`Removed ${path61.relative(root, settingsPath)} (no user-owned settings remained).`);
13721
+ await fs48.remove(settingsPath);
13722
+ log(`Removed ${path62.relative(root, settingsPath)} (no user-owned settings remained).`);
13561
13723
  return true;
13562
13724
  }
13563
13725
  async function stripRootClaudeMd(root) {
13564
- const filePath = path61.join(root, "CLAUDE.md");
13565
- if (!await fs47.pathExists(filePath)) return false;
13566
- const prev = await fs47.readFile(filePath, "utf8");
13726
+ const filePath = path62.join(root, "CLAUDE.md");
13727
+ if (!await fs48.pathExists(filePath)) return false;
13728
+ const prev = await fs48.readFile(filePath, "utf8");
13567
13729
  if (!prev.includes(BLOCK_BEGIN)) return false;
13568
13730
  const next = stripHausBlock(prev);
13569
13731
  if (next.length === 0) {
13570
- await fs47.remove(filePath);
13732
+ await fs48.remove(filePath);
13571
13733
  log("Removed CLAUDE.md (only contained haus import block).");
13572
13734
  } else {
13573
- await fs47.writeFile(filePath, next, "utf8");
13735
+ await fs48.writeFile(filePath, next, "utf8");
13574
13736
  log("Removed haus import block from CLAUDE.md (user content preserved).");
13575
13737
  }
13576
13738
  return true;
13577
13739
  }
13578
13740
  async function pruneDirIfEmpty(dir) {
13579
- if (!await fs47.pathExists(dir)) return;
13741
+ if (!await fs48.pathExists(dir)) return;
13580
13742
  try {
13581
- const entries = await fs47.readdir(dir);
13582
- if (entries.length === 0) await fs47.remove(dir);
13743
+ const entries = await fs48.readdir(dir);
13744
+ if (entries.length === 0) await fs48.remove(dir);
13583
13745
  } catch {
13584
13746
  }
13585
13747
  }
@@ -13588,13 +13750,13 @@ async function backupManagedFilesBeforeUndo(root, managedAbsPaths) {
13588
13750
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
13589
13751
  const backupRoot = hausPath(root, "backups", dirBackupDirName("undo", stamp));
13590
13752
  for (const abs of managedAbsPaths) {
13591
- if (!await fs47.pathExists(abs)) continue;
13592
- const rel = path61.relative(root, abs);
13593
- const backupPath = path61.join(backupRoot, rel);
13594
- await fs47.ensureDir(path61.dirname(backupPath));
13595
- await fs47.copy(abs, backupPath);
13753
+ if (!await fs48.pathExists(abs)) continue;
13754
+ const rel = path62.relative(root, abs);
13755
+ const backupPath = path62.join(backupRoot, rel);
13756
+ await fs48.ensureDir(path62.dirname(backupPath));
13757
+ await fs48.copy(abs, backupPath);
13596
13758
  }
13597
- log(`Backed up ${managedAbsPaths.length} managed file(s) to ${path61.relative(root, backupRoot)}.`);
13759
+ log(`Backed up ${managedAbsPaths.length} managed file(s) to ${path62.relative(root, backupRoot)}.`);
13598
13760
  }
13599
13761
  async function runUndo(options) {
13600
13762
  const root = options.root ?? process.cwd();
@@ -13608,7 +13770,7 @@ async function runUndo(options) {
13608
13770
  log("Nothing to remove: no haus-managed files found in this directory.");
13609
13771
  return;
13610
13772
  }
13611
- const relTargets = managed.map((p) => path61.relative(root, p));
13773
+ const relTargets = managed.map((p) => path62.relative(root, p));
13612
13774
  const summaryParts = [...relTargets];
13613
13775
  if (stripSettings) summaryParts.push(".claude/settings.json (haus rules only)");
13614
13776
  if (stripClaudeMd) summaryParts.push("CLAUDE.md (haus import block only)");
@@ -13634,9 +13796,9 @@ User-owned .claude/ files will be preserved.`
13634
13796
  }
13635
13797
  await backupManagedFilesBeforeUndo(root, managed);
13636
13798
  for (const abs of coreManaged) {
13637
- if (!await fs47.pathExists(abs)) continue;
13638
- await fs47.remove(abs);
13639
- log(`Removed ${path61.relative(root, abs)}`);
13799
+ if (!await fs48.pathExists(abs)) continue;
13800
+ await fs48.remove(abs);
13801
+ log(`Removed ${path62.relative(root, abs)}`);
13640
13802
  }
13641
13803
  for (const { id, existing } of lockRemovable) {
13642
13804
  await deleteTrackedPaths(root, id, existing, { verb: "Removed" }, log);
@@ -13653,8 +13815,8 @@ init_fs();
13653
13815
  init_logger();
13654
13816
  init_paths();
13655
13817
  import crypto4 from "node:crypto";
13656
- import path62 from "node:path";
13657
- import fs48 from "fs-extra";
13818
+ import path63 from "node:path";
13819
+ import fs49 from "fs-extra";
13658
13820
  async function runUninstall(options = {}) {
13659
13821
  const { force = false, removePlugins = removeGlobalPlugins } = options;
13660
13822
  const manifest = await readManifest();
@@ -13691,8 +13853,8 @@ async function runUninstall(options = {}) {
13691
13853
  );
13692
13854
  }
13693
13855
  for (const entry of manifest.files) {
13694
- const exists = fs48.pathExistsSync(entry.destPath);
13695
- if (!exists) continue;
13856
+ const exists2 = fs49.pathExistsSync(entry.destPath);
13857
+ if (!exists2) continue;
13696
13858
  const content2 = await readText(entry.destPath);
13697
13859
  if (content2 === void 0) continue;
13698
13860
  const header = parseMarkdownHeader(content2);
@@ -13709,24 +13871,24 @@ async function runUninstall(options = {}) {
13709
13871
  result.skipped.push(entry.destPath);
13710
13872
  continue;
13711
13873
  }
13712
- await fs48.remove(entry.destPath);
13713
- await pruneEmptyDir(path62.dirname(entry.destPath));
13874
+ await fs49.remove(entry.destPath);
13875
+ await pruneEmptyDir(path63.dirname(entry.destPath));
13714
13876
  result.deleted.push(entry.destPath);
13715
13877
  }
13716
13878
  const settings = await readSettings();
13717
13879
  const stripped = stripHausHooks(stripHausAsk(stripHausAllow(stripHausDeny(settings))));
13718
13880
  await writeSettings(stripped);
13719
13881
  result.hooksStripped = true;
13720
- const hausDir = path62.join(globalClaudeDir(), "haus");
13882
+ const hausDir = path63.join(globalClaudeDir(), "haus");
13721
13883
  const manifestPath2 = hausManifestPath();
13722
- if (fs48.pathExistsSync(manifestPath2)) {
13723
- await fs48.remove(manifestPath2);
13884
+ if (fs49.pathExistsSync(manifestPath2)) {
13885
+ await fs49.remove(manifestPath2);
13724
13886
  }
13725
13887
  await removeInstallConfig();
13726
- if (fs48.pathExistsSync(hausDir)) {
13888
+ if (fs49.pathExistsSync(hausDir)) {
13727
13889
  try {
13728
- const remaining = await fs48.readdir(hausDir);
13729
- if (remaining.length === 0) await fs48.remove(hausDir);
13890
+ const remaining = await fs49.readdir(hausDir);
13891
+ if (remaining.length === 0) await fs49.remove(hausDir);
13730
13892
  } catch {
13731
13893
  }
13732
13894
  }
@@ -13795,15 +13957,15 @@ async function runUninstallCommand(options) {
13795
13957
  }
13796
13958
 
13797
13959
  // src/commands/validate-catalog.ts
13798
- import path64 from "node:path";
13960
+ import path65 from "node:path";
13799
13961
 
13800
13962
  // src/catalog/validate-core.ts
13801
13963
  init_forbidden_content();
13802
13964
  init_manifest_item_fields();
13803
13965
  init_path_safety();
13804
13966
  init_validation_rules2();
13805
- import fs49 from "node:fs";
13806
- import path63 from "node:path";
13967
+ import fs50 from "node:fs";
13968
+ import path64 from "node:path";
13807
13969
  var ITEM_SEMVER_RE = /^\d+\.\d+\.\d+$/;
13808
13970
  var ITEM_ID_PATTERN = /^haus\./;
13809
13971
  function auditForbiddenStacks(items) {
@@ -13963,14 +14125,14 @@ function checkRequiredFrontmatter(text, label) {
13963
14125
  }
13964
14126
  function readForAudit(absPath, label, failures) {
13965
14127
  try {
13966
- return fs49.readFileSync(absPath, "utf8");
14128
+ return fs50.readFileSync(absPath, "utf8");
13967
14129
  } catch (err) {
13968
14130
  failures.push(`${label}: unreadable (${err instanceof Error ? err.message : String(err)})`);
13969
14131
  return null;
13970
14132
  }
13971
14133
  }
13972
14134
  function auditTemplateContent(manifestDir, absPath, itemId) {
13973
- const rel = path63.relative(manifestDir, absPath);
14135
+ const rel = path64.relative(manifestDir, absPath);
13974
14136
  const failures = [];
13975
14137
  const text = readForAudit(absPath, `${itemId}: ${rel}`, failures);
13976
14138
  if (text === null) return failures;
@@ -13992,55 +14154,55 @@ function auditShippedFiles(manifestDir, items) {
13992
14154
  const failures = [];
13993
14155
  for (const item of items) {
13994
14156
  if (!item.path || !isSafeCatalogPath(item.path)) continue;
13995
- const absPath = path63.join(manifestDir, item.path);
14157
+ const absPath = path64.join(manifestDir, item.path);
13996
14158
  if (item.type === "skill") {
13997
- const skillMd = path63.join(absPath, "SKILL.md");
13998
- if (!fs49.existsSync(skillMd)) {
13999
- failures.push(`${item.id}: missing ${path63.relative(manifestDir, skillMd)}`);
14159
+ const skillMd = path64.join(absPath, "SKILL.md");
14160
+ if (!fs50.existsSync(skillMd)) {
14161
+ failures.push(`${item.id}: missing ${path64.relative(manifestDir, skillMd)}`);
14000
14162
  continue;
14001
14163
  }
14002
14164
  const text = readForAudit(skillMd, `${item.id}: SKILL.md`, failures);
14003
14165
  if (text === null) continue;
14004
14166
  failures.push(...checkRequiredFrontmatter(text, `${item.id}: SKILL.md`));
14005
14167
  failures.push(
14006
- ...auditForbiddenTagsInText(text, `${item.id}: ${path63.relative(manifestDir, skillMd)}`)
14168
+ ...auditForbiddenTagsInText(text, `${item.id}: ${path64.relative(manifestDir, skillMd)}`)
14007
14169
  );
14008
14170
  } else if (item.type === "agent") {
14009
- if (!fs49.existsSync(absPath)) {
14171
+ if (!fs50.existsSync(absPath)) {
14010
14172
  failures.push(`${item.id}: missing agent file ${item.path}`);
14011
14173
  continue;
14012
14174
  }
14013
- const rel = path63.relative(manifestDir, absPath);
14175
+ const rel = path64.relative(manifestDir, absPath);
14014
14176
  const text = readForAudit(absPath, `${item.id}: ${rel}`, failures);
14015
14177
  if (text === null) continue;
14016
14178
  failures.push(...checkRequiredFrontmatter(text, `${item.id}: ${rel}`));
14017
14179
  failures.push(...auditForbiddenTagsInText(text, `${item.id}: ${rel}`));
14018
14180
  } else if (item.type === "template") {
14019
- if (!fs49.existsSync(absPath)) {
14181
+ if (!fs50.existsSync(absPath)) {
14020
14182
  failures.push(`${item.id}: missing template file ${item.path}`);
14021
14183
  continue;
14022
14184
  }
14023
14185
  failures.push(...auditTemplateContent(manifestDir, absPath, item.id));
14024
14186
  } else if (item.type === "command") {
14025
- if (!fs49.existsSync(absPath)) {
14187
+ if (!fs50.existsSync(absPath)) {
14026
14188
  failures.push(`${item.id}: missing command file ${item.path}`);
14027
14189
  continue;
14028
14190
  }
14029
- const rel = path63.relative(manifestDir, absPath);
14191
+ const rel = path64.relative(manifestDir, absPath);
14030
14192
  const text = readForAudit(absPath, `${item.id}: ${rel}`, failures);
14031
14193
  if (text === null) continue;
14032
14194
  failures.push(...checkRequiredFrontmatter(text, `${item.id}: ${rel}`));
14033
14195
  failures.push(...auditTemplateContent(manifestDir, absPath, item.id));
14034
14196
  } else if (item.type === "config") {
14035
- if (!fs49.existsSync(absPath)) {
14197
+ if (!fs50.existsSync(absPath)) {
14036
14198
  failures.push(`${item.id}: missing config path ${item.path}`);
14037
14199
  }
14038
14200
  } else if (item.type === "rule") {
14039
- if (!fs49.existsSync(absPath)) {
14201
+ if (!fs50.existsSync(absPath)) {
14040
14202
  failures.push(`${item.id}: missing rule file ${item.path}`);
14041
14203
  }
14042
14204
  } else if (item.type === "asset") {
14043
- if (!fs49.existsSync(absPath)) {
14205
+ if (!fs50.existsSync(absPath)) {
14044
14206
  failures.push(`${item.id}: missing asset file ${item.path}`);
14045
14207
  }
14046
14208
  }
@@ -14075,7 +14237,7 @@ function resolveItemType(relPath, pathTypeMap) {
14075
14237
  function walkAllFiles(dir, fn, failures) {
14076
14238
  let entries;
14077
14239
  try {
14078
- entries = fs49.readdirSync(dir, { withFileTypes: true });
14240
+ entries = fs50.readdirSync(dir, { withFileTypes: true });
14079
14241
  } catch (err) {
14080
14242
  failures.push(
14081
14243
  `${dir}: unreadable subtree (${err instanceof Error ? err.message : String(err)})`
@@ -14084,7 +14246,7 @@ function walkAllFiles(dir, fn, failures) {
14084
14246
  }
14085
14247
  for (const entry of entries) {
14086
14248
  if (entry.name === ".DS_Store") continue;
14087
- const full = path63.join(dir, entry.name);
14249
+ const full = path64.join(dir, entry.name);
14088
14250
  if (entry.isDirectory()) walkAllFiles(full, fn, failures);
14089
14251
  else fn(full);
14090
14252
  }
@@ -14108,12 +14270,12 @@ function auditDiskOrphans(manifestDir, items) {
14108
14270
  const failures = [];
14109
14271
  const claimedPaths = items.filter((item) => item.path && isSafeCatalogPath(item.path)).map((item) => item.path.replace(/\\/g, "/"));
14110
14272
  for (const dir of CONTENT_ROOTS) {
14111
- const abs = path63.join(manifestDir, dir);
14112
- if (!fs49.existsSync(abs)) continue;
14273
+ const abs = path64.join(manifestDir, dir);
14274
+ if (!fs50.existsSync(abs)) continue;
14113
14275
  walkAllFiles(
14114
14276
  abs,
14115
14277
  (file) => {
14116
- const relPath = path63.relative(manifestDir, file).replace(/\\/g, "/");
14278
+ const relPath = path64.relative(manifestDir, file).replace(/\\/g, "/");
14117
14279
  if (!isPathClaimed(relPath, claimedPaths)) {
14118
14280
  failures.push(`orphaned on disk, claimed by no manifest item: ${relPath}`);
14119
14281
  }
@@ -14126,7 +14288,7 @@ function auditDiskOrphans(manifestDir, items) {
14126
14288
  function walkMd(dir, fn, failures) {
14127
14289
  let entries;
14128
14290
  try {
14129
- entries = fs49.readdirSync(dir, { withFileTypes: true });
14291
+ entries = fs50.readdirSync(dir, { withFileTypes: true });
14130
14292
  } catch (err) {
14131
14293
  failures.push(
14132
14294
  `${dir}: unreadable subtree (${err instanceof Error ? err.message : String(err)})`
@@ -14134,7 +14296,7 @@ function walkMd(dir, fn, failures) {
14134
14296
  return;
14135
14297
  }
14136
14298
  for (const entry of entries) {
14137
- const full = path63.join(dir, entry.name);
14299
+ const full = path64.join(dir, entry.name);
14138
14300
  if (entry.isDirectory()) walkMd(full, fn, failures);
14139
14301
  else if (entry.name.endsWith(".md")) fn(full);
14140
14302
  }
@@ -14144,13 +14306,13 @@ function auditMarkdownContent(manifestDir, items) {
14144
14306
  const pathSourceMap = buildItemPathSourceMap(items);
14145
14307
  const pathTypeMap = buildItemPathTypeMap(items);
14146
14308
  for (const dir of ["skills", "agents", "templates", "commands", "sources"]) {
14147
- const abs = path63.join(manifestDir, dir);
14148
- if (!fs49.existsSync(abs)) continue;
14309
+ const abs = path64.join(manifestDir, dir);
14310
+ if (!fs50.existsSync(abs)) continue;
14149
14311
  const dirType = DIR_ITEM_TYPE[dir] ?? "";
14150
14312
  walkMd(
14151
14313
  abs,
14152
14314
  (file) => {
14153
- const rel = path63.relative(manifestDir, file);
14315
+ const rel = path64.relative(manifestDir, file);
14154
14316
  const text = readForAudit(file, rel, failures);
14155
14317
  if (text === null) return;
14156
14318
  const norm = rel.replace(/\\/g, "/");
@@ -14198,8 +14360,8 @@ async function runValidateCatalog(manifestPath2) {
14198
14360
  process.exitCode = 1;
14199
14361
  return;
14200
14362
  }
14201
- const abs = path64.resolve(process.cwd(), manifestPath2);
14202
- const manifestDir = path64.dirname(abs);
14363
+ const abs = path65.resolve(process.cwd(), manifestPath2);
14364
+ const manifestDir = path65.dirname(abs);
14203
14365
  const data = await readJson(abs);
14204
14366
  if (!data?.items) {
14205
14367
  error(`Could not read catalog manifest at ${abs}`);
@@ -14220,8 +14382,8 @@ init_logger();
14220
14382
 
14221
14383
  // src/workspace/worktree/add.ts
14222
14384
  init_fs();
14223
- import path71 from "node:path";
14224
- import fs53 from "fs-extra";
14385
+ import path72 from "node:path";
14386
+ import fs54 from "fs-extra";
14225
14387
 
14226
14388
  // src/workspace/worktree/git-worktree.ts
14227
14389
  init_exec();
@@ -14243,8 +14405,8 @@ function isBranchCheckedOutElsewhereError(stderr) {
14243
14405
  return CHECKED_OUT_ELSEWHERE_PATTERNS.some((re) => re.test(stderr));
14244
14406
  }
14245
14407
  async function addWorktree(cwd, worktreePath2, branch, opts = {}) {
14246
- const exists = await branchExists(cwd, branch);
14247
- if (exists) {
14408
+ const exists2 = await branchExists(cwd, branch);
14409
+ if (exists2) {
14248
14410
  const result2 = await runGit(["worktree", "add", worktreePath2, branch], { cwd });
14249
14411
  return {
14250
14412
  ok: result2.exitCode === 0,
@@ -14341,13 +14503,13 @@ async function hasUnpushedWork(cwd) {
14341
14503
  }
14342
14504
 
14343
14505
  // src/workspace/worktree/hydrate.ts
14344
- import path67 from "node:path";
14345
- import fs51 from "fs-extra";
14506
+ import path68 from "node:path";
14507
+ import fs52 from "fs-extra";
14346
14508
 
14347
14509
  // src/workspace/worktree/cow-copy.ts
14348
14510
  init_exec();
14349
- import path65 from "node:path";
14350
- import fs50 from "fs-extra";
14511
+ import path66 from "node:path";
14512
+ import fs51 from "fs-extra";
14351
14513
  async function getMacFilesystemType(dir) {
14352
14514
  const df = await runCommand("df", ["-P", dir]);
14353
14515
  if (df.exitCode !== 0) return void 0;
@@ -14378,14 +14540,14 @@ async function detectCowStrategy(dir) {
14378
14540
  return "unknown-platform";
14379
14541
  }
14380
14542
  async function cowCopyDir(src, dest) {
14381
- const srcExists = await fs50.pathExists(src);
14543
+ const srcExists = await fs51.pathExists(src);
14382
14544
  if (!srcExists)
14383
14545
  return { strategy: "unsupported", attempted: false, ok: false, error: "source missing" };
14384
- const strategy = await detectCowStrategy(path65.dirname(dest));
14546
+ const strategy = await detectCowStrategy(path66.dirname(dest));
14385
14547
  if (strategy === "unsupported" || strategy === "unknown-platform") {
14386
14548
  return { strategy, attempted: false, ok: false };
14387
14549
  }
14388
- await fs50.ensureDir(path65.dirname(dest));
14550
+ await fs51.ensureDir(path66.dirname(dest));
14389
14551
  const args = strategy === "darwin-clonefile" ? ["-c", "-R", src, dest] : ["-a", "--reflink=always", src, dest];
14390
14552
  const result = await runCommand("cp", args);
14391
14553
  return {
@@ -14399,7 +14561,7 @@ async function cowCopyDir(src, dest) {
14399
14561
  // src/workspace/worktree/install.ts
14400
14562
  init_exec();
14401
14563
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
14402
- import path66 from "node:path";
14564
+ import path67 from "node:path";
14403
14565
  import fg7 from "fast-glob";
14404
14566
  function detectInstallPlan(signals) {
14405
14567
  if (signals.hasYarnLock) {
@@ -14420,10 +14582,10 @@ function detectInstallPlan(signals) {
14420
14582
  return { manager: "none" };
14421
14583
  }
14422
14584
  async function computeLockfileSignals(dir) {
14423
- const hasYarnLock = existsSync3(path66.join(dir, "yarn.lock"));
14424
- const hasPnpmLock = existsSync3(path66.join(dir, "pnpm-lock.yaml"));
14425
- const hasPackageLock = existsSync3(path66.join(dir, "package-lock.json"));
14426
- const hasComposerLock = existsSync3(path66.join(dir, "composer.lock"));
14585
+ const hasYarnLock = existsSync3(path67.join(dir, "yarn.lock"));
14586
+ const hasPnpmLock = existsSync3(path67.join(dir, "pnpm-lock.yaml"));
14587
+ const hasPackageLock = existsSync3(path67.join(dir, "package-lock.json"));
14588
+ const hasComposerLock = existsSync3(path67.join(dir, "composer.lock"));
14427
14589
  const dotnetFiles = await fg7(["**/*.csproj", "**/*.sln"], {
14428
14590
  cwd: dir,
14429
14591
  onlyFiles: true,
@@ -14437,7 +14599,7 @@ async function computeLockfileSignals(dir) {
14437
14599
  });
14438
14600
  const hasDotnetProject = dotnetFiles.length > 0;
14439
14601
  let hasPackageManagerField = false;
14440
- const pkgPath = path66.join(dir, "package.json");
14602
+ const pkgPath = path67.join(dir, "package.json");
14441
14603
  if (existsSync3(pkgPath)) {
14442
14604
  try {
14443
14605
  const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
@@ -14480,7 +14642,7 @@ function missingYarnrcEnvVars(content2, env) {
14480
14642
  return findRequiredYarnrcEnvVars(content2).filter((name) => env[name] === void 0);
14481
14643
  }
14482
14644
  function missingYarnrcEnvVarsInDir(dir, env) {
14483
- const yarnrcPath = path66.join(dir, ".yarnrc.yml");
14645
+ const yarnrcPath = path67.join(dir, ".yarnrc.yml");
14484
14646
  if (!existsSync3(yarnrcPath)) return [];
14485
14647
  try {
14486
14648
  return missingYarnrcEnvVars(readFileSync3(yarnrcPath, "utf8"), env);
@@ -14576,13 +14738,13 @@ async function hydrateMember(mainMemberPath, worktreeMemberPath, opts = {}) {
14576
14738
  const start = Date.now();
14577
14739
  const targets = [];
14578
14740
  for (const target of HYDRATION_TARGETS) {
14579
- const src = path67.join(mainMemberPath, target);
14580
- const dest = path67.join(worktreeMemberPath, target);
14581
- if (!await fs51.pathExists(src)) continue;
14582
- const destExists = await fs51.pathExists(dest);
14741
+ const src = path68.join(mainMemberPath, target);
14742
+ const dest = path68.join(worktreeMemberPath, target);
14743
+ if (!await fs52.pathExists(src)) continue;
14744
+ const destExists = await fs52.pathExists(dest);
14583
14745
  if (!opts.force && destExists) continue;
14584
14746
  if (opts.force && destExists) {
14585
- await fs51.remove(dest);
14747
+ await fs52.remove(dest);
14586
14748
  }
14587
14749
  const copy = await cowCopyDir(src, dest);
14588
14750
  targets.push({ target, copy });
@@ -14595,11 +14757,11 @@ async function hydrateMember(mainMemberPath, worktreeMemberPath, opts = {}) {
14595
14757
 
14596
14758
  // src/workspace/worktree/local-files.ts
14597
14759
  init_exec();
14598
- import path68 from "node:path";
14599
- import fs52 from "fs-extra";
14760
+ import path69 from "node:path";
14761
+ import fs53 from "fs-extra";
14600
14762
  var MACHINE_LOCAL_FILES = [
14601
14763
  "docker-compose.local.yml",
14602
- path68.join(".claude", "settings.local.json")
14764
+ path69.join(".claude", "settings.local.json")
14603
14765
  ];
14604
14766
  async function isTracked(repoRoot, relFile) {
14605
14767
  const gitPath = relFile.replace(/\\/g, "/");
@@ -14609,12 +14771,12 @@ async function isTracked(repoRoot, relFile) {
14609
14771
  async function copyMachineLocalFiles(mainMemberPath, worktreeMemberPath) {
14610
14772
  const copied = [];
14611
14773
  for (const rel of MACHINE_LOCAL_FILES) {
14612
- const src = path68.join(mainMemberPath, rel);
14613
- if (!await fs52.pathExists(src)) continue;
14774
+ const src = path69.join(mainMemberPath, rel);
14775
+ if (!await fs53.pathExists(src)) continue;
14614
14776
  if (await isTracked(mainMemberPath, rel)) continue;
14615
- const dest = path68.join(worktreeMemberPath, rel);
14616
- await fs52.ensureDir(path68.dirname(dest));
14617
- await fs52.copy(src, dest, { overwrite: true });
14777
+ const dest = path69.join(worktreeMemberPath, rel);
14778
+ await fs53.ensureDir(path69.dirname(dest));
14779
+ await fs53.copy(src, dest, { overwrite: true });
14618
14780
  copied.push(rel);
14619
14781
  }
14620
14782
  return copied;
@@ -14622,15 +14784,15 @@ async function copyMachineLocalFiles(mainMemberPath, worktreeMemberPath) {
14622
14784
 
14623
14785
  // src/workspace/worktree/root.ts
14624
14786
  import { existsSync as existsSync4 } from "node:fs";
14625
- import path69 from "node:path";
14787
+ import path70 from "node:path";
14626
14788
  async function resolveWorkspaceForWorktree(start) {
14627
14789
  const rootInfo = await resolveRoots(start);
14628
14790
  if (!rootInfo.isGitRepo) {
14629
14791
  return { ok: false, reason: "Not inside a git repository." };
14630
14792
  }
14631
14793
  const workspaceRoot = rootInfo.mainRoot;
14632
- const hasYaml = existsSync4(path69.join(workspaceRoot, WORKSPACE_FILE));
14633
- const hasManifest = existsSync4(path69.join(workspaceRoot, REPOS_MANIFEST_FILE));
14794
+ const hasYaml = existsSync4(path70.join(workspaceRoot, WORKSPACE_FILE));
14795
+ const hasManifest = existsSync4(path70.join(workspaceRoot, REPOS_MANIFEST_FILE));
14634
14796
  if (!hasYaml && !hasManifest) {
14635
14797
  return {
14636
14798
  ok: false,
@@ -14656,10 +14818,10 @@ function selectMembers(members, only) {
14656
14818
 
14657
14819
  // src/workspace/worktree/state.ts
14658
14820
  init_fs();
14659
- import path70 from "node:path";
14821
+ import path71 from "node:path";
14660
14822
  var WORKTREE_STATE_FILE = ".haus-worktree.json";
14661
14823
  function worktreesDir(mainRoot) {
14662
- return path70.join(mainRoot, ".claude", "worktrees");
14824
+ return path71.join(mainRoot, ".claude", "worktrees");
14663
14825
  }
14664
14826
  function isValidWorktreeSlug(slug) {
14665
14827
  if (slug === "" || slug === "." || slug === "..") return false;
@@ -14672,22 +14834,22 @@ function worktreePath(mainRoot, slug) {
14672
14834
  `Invalid worktree slug ${JSON.stringify(slug)} \u2014 slugs may only contain letters, digits, ".", "_", "-" (no path separators, and not "." or "..").`
14673
14835
  );
14674
14836
  }
14675
- return path70.join(worktreesDir(mainRoot), slug);
14837
+ return path71.join(worktreesDir(mainRoot), slug);
14676
14838
  }
14677
14839
  function slugFromWorktreePath(mainRoot, somePath) {
14678
14840
  const dir = worktreesDir(mainRoot);
14679
- const prefix = `${dir}${path70.sep}`;
14841
+ const prefix = `${dir}${path71.sep}`;
14680
14842
  if (!somePath.startsWith(prefix)) return void 0;
14681
- const segment = somePath.slice(prefix.length).split(path70.sep)[0];
14843
+ const segment = somePath.slice(prefix.length).split(path71.sep)[0];
14682
14844
  return segment !== void 0 && segment !== "" ? segment : void 0;
14683
14845
  }
14684
14846
  async function readWorktreeState(worktreeRoot) {
14685
- const state = await readJson(path70.join(worktreeRoot, WORKTREE_STATE_FILE));
14847
+ const state = await readJson(path71.join(worktreeRoot, WORKTREE_STATE_FILE));
14686
14848
  if (state == null || typeof state !== "object" || !Array.isArray(state.members)) return void 0;
14687
14849
  return state;
14688
14850
  }
14689
14851
  async function writeWorktreeState(worktreeRoot, state) {
14690
- await writeJson(path70.join(worktreeRoot, WORKTREE_STATE_FILE), state);
14852
+ await writeJson(path71.join(worktreeRoot, WORKTREE_STATE_FILE), state);
14691
14853
  }
14692
14854
 
14693
14855
  // src/workspace/worktree/add.ts
@@ -14702,7 +14864,7 @@ async function runAdd(opts) {
14702
14864
  }
14703
14865
  const branch = opts.branch ?? opts.slug;
14704
14866
  const wsWorktreePath = worktreePath(workspaceRoot, opts.slug);
14705
- if (await fs53.pathExists(wsWorktreePath)) {
14867
+ if (await fs54.pathExists(wsWorktreePath)) {
14706
14868
  return {
14707
14869
  ok: false,
14708
14870
  error: `Workspace worktree already exists at ${wsWorktreePath} \u2014 run \`haus workspace worktree hydrate --materialize\` from inside it to check out any missing members (e.g. a worktree created by plain \`git worktree add\`), or \`remove\` first to start over.`
@@ -14721,7 +14883,7 @@ async function runAdd(opts) {
14721
14883
  const memberResults = [];
14722
14884
  for (const member of selected) {
14723
14885
  const start = Date.now();
14724
- const memberWorktreePath = path71.join(wsWorktreePath, member.folder);
14886
+ const memberWorktreePath = path72.join(wsWorktreePath, member.folder);
14725
14887
  const outcome = await addWorktree(member.absPath, memberWorktreePath, branch, {
14726
14888
  preferDefaultBranchFrom: true
14727
14889
  });
@@ -14751,7 +14913,7 @@ async function runAdd(opts) {
14751
14913
  await mapWithConcurrency(okResults, async (result) => {
14752
14914
  const member = selected.find((m) => m.id === result.member);
14753
14915
  if (!member) return;
14754
- const memberWorktreePath = path71.join(wsWorktreePath, member.folder);
14916
+ const memberWorktreePath = path72.join(wsWorktreePath, member.folder);
14755
14917
  const hydrationStart = Date.now();
14756
14918
  if (opts.hydrate !== false) {
14757
14919
  result.hydration = await hydrateMember(member.absPath, memberWorktreePath);
@@ -14788,9 +14950,9 @@ async function runDryRun(args) {
14788
14950
  const wsBranchExists = await branchExists(workspaceRoot, branch);
14789
14951
  const members = [];
14790
14952
  for (const member of selected) {
14791
- const exists = await branchExists(member.absPath, branch);
14953
+ const exists2 = await branchExists(member.absPath, branch);
14792
14954
  let startPoint;
14793
- if (!exists) {
14955
+ if (!exists2) {
14794
14956
  const def = await resolveDefaultBranch(member.absPath);
14795
14957
  startPoint = def?.ref;
14796
14958
  }
@@ -14799,7 +14961,7 @@ async function runDryRun(args) {
14799
14961
  folder: member.folder,
14800
14962
  status: "planned",
14801
14963
  branch,
14802
- branchAction: exists ? "checkout" : "create",
14964
+ branchAction: exists2 ? "checkout" : "create",
14803
14965
  startPoint,
14804
14966
  durationMs: 0
14805
14967
  });
@@ -14819,17 +14981,17 @@ async function runDryRun(args) {
14819
14981
  // src/workspace/worktree/autohydrate.ts
14820
14982
  import { spawn } from "node:child_process";
14821
14983
  import fsSync from "node:fs";
14822
- import path76 from "node:path";
14823
- import fs55 from "fs-extra";
14984
+ import path77 from "node:path";
14985
+ import fs56 from "fs-extra";
14824
14986
 
14825
14987
  // src/workspace/worktree/doctor.ts
14826
14988
  import { existsSync as existsSync6 } from "node:fs";
14827
- import path74 from "node:path";
14989
+ import path75 from "node:path";
14828
14990
 
14829
14991
  // src/workspace/worktree/hydrate-lock.ts
14830
14992
  init_paths();
14831
- import path72 from "node:path";
14832
- import fs54 from "fs-extra";
14993
+ import path73 from "node:path";
14994
+ import fs55 from "fs-extra";
14833
14995
  var STALE_LOCK_MS = 30 * 60 * 1e3;
14834
14996
  function locksDir(mainRoot) {
14835
14997
  return hausPath(mainRoot, "locks");
@@ -14846,13 +15008,13 @@ function assertSlug(slug) {
14846
15008
  return slug;
14847
15009
  }
14848
15010
  function hydrateLockPath(mainRoot, slug) {
14849
- return path72.join(locksDir(mainRoot), `hydrate-${assertSlug(slug)}.lock`);
15011
+ return path73.join(locksDir(mainRoot), `hydrate-${assertSlug(slug)}.lock`);
14850
15012
  }
14851
15013
  function hydrateLogPath(mainRoot, slug) {
14852
- return path72.join(logsDir(mainRoot), `hydrate-${assertSlug(slug)}.log`);
15014
+ return path73.join(logsDir(mainRoot), `hydrate-${assertSlug(slug)}.log`);
14853
15015
  }
14854
15016
  function hydrateLastRunPath(mainRoot, slug) {
14855
- return path72.join(logsDir(mainRoot), `hydrate-${assertSlug(slug)}.last.json`);
15017
+ return path73.join(logsDir(mainRoot), `hydrate-${assertSlug(slug)}.last.json`);
14856
15018
  }
14857
15019
  function defaultIsAlive(pid) {
14858
15020
  try {
@@ -14864,7 +15026,7 @@ function defaultIsAlive(pid) {
14864
15026
  }
14865
15027
  async function readOwnerPid(lockPath) {
14866
15028
  try {
14867
- const raw = (await fs54.readFile(path72.join(lockPath, "pid"), "utf8")).trim();
15029
+ const raw = (await fs55.readFile(path73.join(lockPath, "pid"), "utf8")).trim();
14868
15030
  if (raw === "") return void 0;
14869
15031
  const pid = Number.parseInt(raw, 10);
14870
15032
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
@@ -14873,13 +15035,13 @@ async function readOwnerPid(lockPath) {
14873
15035
  }
14874
15036
  }
14875
15037
  async function claim(lockPath, pid) {
14876
- await fs54.ensureDir(path72.dirname(lockPath));
15038
+ await fs55.ensureDir(path73.dirname(lockPath));
14877
15039
  try {
14878
- await fs54.mkdir(lockPath);
15040
+ await fs55.mkdir(lockPath);
14879
15041
  } catch {
14880
15042
  return false;
14881
15043
  }
14882
- await fs54.writeFile(path72.join(lockPath, "pid"), String(pid), "utf8");
15044
+ await fs55.writeFile(path73.join(lockPath, "pid"), String(pid), "utf8");
14883
15045
  return true;
14884
15046
  }
14885
15047
  async function acquireHydrateLock(mainRoot, slug, deps = {}) {
@@ -14898,7 +15060,7 @@ async function acquireHydrateLock(mainRoot, slug, deps = {}) {
14898
15060
  }
14899
15061
  if (alive) return { acquired: false, reason: "live", ownerPid, lockPath };
14900
15062
  } else {
14901
- const stat = await fs54.stat(lockPath).catch(() => void 0);
15063
+ const stat = await fs55.stat(lockPath).catch(() => void 0);
14902
15064
  if (stat === void 0) {
14903
15065
  return await claim(lockPath, pid) ? { acquired: true, lockPath } : { acquired: false, reason: "live", lockPath };
14904
15066
  }
@@ -14906,18 +15068,18 @@ async function acquireHydrateLock(mainRoot, slug, deps = {}) {
14906
15068
  return { acquired: false, reason: "starting", lockPath };
14907
15069
  }
14908
15070
  }
14909
- await fs54.remove(lockPath);
15071
+ await fs55.remove(lockPath);
14910
15072
  return await claim(lockPath, pid) ? { acquired: true, lockPath } : { acquired: false, reason: "live", ownerPid, lockPath };
14911
15073
  }
14912
15074
  async function recordHydrateLockOwner(mainRoot, slug, pid) {
14913
- await fs54.writeFile(path72.join(hydrateLockPath(mainRoot, slug), "pid"), String(pid), "utf8");
15075
+ await fs55.writeFile(path73.join(hydrateLockPath(mainRoot, slug), "pid"), String(pid), "utf8");
14914
15076
  }
14915
15077
  async function releaseHydrateLock(mainRoot, slug) {
14916
- await fs54.remove(hydrateLockPath(mainRoot, slug));
15078
+ await fs55.remove(hydrateLockPath(mainRoot, slug));
14917
15079
  }
14918
15080
  async function readHydrateLock(mainRoot, slug) {
14919
15081
  const lockPath = hydrateLockPath(mainRoot, slug);
14920
- const stat = await fs54.stat(lockPath).catch(() => void 0);
15082
+ const stat = await fs55.stat(lockPath).catch(() => void 0);
14921
15083
  if (stat === void 0) return { held: false };
14922
15084
  const ownerPid = await readOwnerPid(lockPath);
14923
15085
  return {
@@ -14930,21 +15092,21 @@ async function readHydrateLock(mainRoot, slug) {
14930
15092
 
14931
15093
  // src/workspace/worktree/orphans.ts
14932
15094
  import { existsSync as existsSync5 } from "node:fs";
14933
- import path73 from "node:path";
15095
+ import path74 from "node:path";
14934
15096
  var WORKSPACE_REPO_ID = "(workspace)";
14935
15097
  async function findOrphanedRegistrations(repos, wsWorktreesDir) {
14936
15098
  const orphans = [];
14937
15099
  for (const repo of repos) {
14938
15100
  const entries = await listWorktrees(repo.absPath);
14939
15101
  for (const entry of entries) {
14940
- const entryPath = path73.normalize(entry.path);
14941
- const marker = `${path73.sep}.claude${path73.sep}worktrees${path73.sep}`;
15102
+ const entryPath = path74.normalize(entry.path);
15103
+ const marker = `${path74.sep}.claude${path74.sep}worktrees${path74.sep}`;
14942
15104
  const idx = entryPath.indexOf(marker);
14943
15105
  if (idx === -1) continue;
14944
15106
  const rest = entryPath.slice(idx + marker.length);
14945
- const slug = rest.split(path73.sep)[0];
15107
+ const slug = rest.split(path74.sep)[0];
14946
15108
  if (!slug) continue;
14947
- if (existsSync5(path73.join(wsWorktreesDir, slug))) continue;
15109
+ if (existsSync5(path74.join(wsWorktreesDir, slug))) continue;
14948
15110
  orphans.push({
14949
15111
  repoId: repo.id,
14950
15112
  ownerRepo: repo.absPath,
@@ -14959,7 +15121,7 @@ async function findOrphanedRegistrations(repos, wsWorktreesDir) {
14959
15121
  // src/workspace/worktree/doctor.ts
14960
15122
  var HYDRATION_MARKERS = ["node_modules", "vendor", "obj", "bin"];
14961
15123
  function looksHydrated(memberPath) {
14962
- return HYDRATION_MARKERS.some((marker) => existsSync6(path74.join(memberPath, marker)));
15124
+ return HYDRATION_MARKERS.some((marker) => existsSync6(path75.join(memberPath, marker)));
14963
15125
  }
14964
15126
  async function runDoctor2() {
14965
15127
  const rootInfo = await resolveRoots();
@@ -14976,7 +15138,7 @@ async function runDoctor2() {
14976
15138
  };
14977
15139
  }
14978
15140
  const workspaceRoot = rootInfo.mainRoot;
14979
- const isWorkspace = existsSync6(path74.join(workspaceRoot, WORKSPACE_FILE)) || existsSync6(path74.join(workspaceRoot, REPOS_MANIFEST_FILE));
15141
+ const isWorkspace = existsSync6(path75.join(workspaceRoot, WORKSPACE_FILE)) || existsSync6(path75.join(workspaceRoot, REPOS_MANIFEST_FILE));
14980
15142
  if (!isWorkspace) {
14981
15143
  return {
14982
15144
  isWorkspace: false,
@@ -14992,7 +15154,7 @@ async function runDoctor2() {
14992
15154
  };
14993
15155
  }
14994
15156
  const wsWorktreesDir = worktreesDir(workspaceRoot);
14995
- const underWorktreesDir = rootInfo.repoRoot.startsWith(`${wsWorktreesDir}${path74.sep}`);
15157
+ const underWorktreesDir = rootInfo.repoRoot.startsWith(`${wsWorktreesDir}${path75.sep}`);
14996
15158
  const inWorkspaceWorktree = rootInfo.isLinkedWorktree && underWorktreesDir;
14997
15159
  const workspaceConfig = await readWorkspaceConfig(workspaceRoot);
14998
15160
  const autoHydrate = workspaceConfig?.worktree?.autoHydrate === true;
@@ -15005,10 +15167,10 @@ async function runDoctor2() {
15005
15167
  let hydrationOwnerPid;
15006
15168
  if (inWorkspaceWorktree) {
15007
15169
  const wtPath = rootInfo.repoRoot;
15008
- slug = rootInfo.worktreeName ?? path74.basename(wtPath);
15170
+ slug = rootInfo.worktreeName ?? path75.basename(wtPath);
15009
15171
  const state = await readWorktreeState(wtPath);
15010
15172
  for (const member of members) {
15011
- const memberPath = path74.join(wtPath, member.folder);
15173
+ const memberPath = path75.join(wtPath, member.folder);
15012
15174
  const materialized = existsSync6(memberPath);
15013
15175
  const expectedBranch = state?.members.find((m) => m.folder === member.folder)?.branch ?? state?.branch;
15014
15176
  const actualBranch = materialized ? await currentBranch(memberPath) : void 0;
@@ -15053,7 +15215,7 @@ async function runDoctor2() {
15053
15215
  wsWorktreesDir
15054
15216
  );
15055
15217
  const orphans = orphanScan.map(
15056
- (o) => `${o.repoId}: worktree registered at ${o.registeredPath} but ${path74.join(wsWorktreesDir, o.slug)} no longer exists.`
15218
+ (o) => `${o.repoId}: worktree registered at ${o.registeredPath} but ${path75.join(wsWorktreesDir, o.slug)} no longer exists.`
15057
15219
  );
15058
15220
  if (orphans.length > 0) {
15059
15221
  problems.push(...orphans);
@@ -15076,7 +15238,7 @@ async function runDoctor2() {
15076
15238
  // src/workspace/worktree/hydrate-command.ts
15077
15239
  init_fs();
15078
15240
  import { existsSync as existsSync7 } from "node:fs";
15079
- import path75 from "node:path";
15241
+ import path76 from "node:path";
15080
15242
  async function runHydrateWorktree(opts) {
15081
15243
  const rootInfo = await resolveRoots();
15082
15244
  if (!rootInfo.isGitRepo || !rootInfo.isLinkedWorktree) {
@@ -15087,7 +15249,7 @@ async function runHydrateWorktree(opts) {
15087
15249
  }
15088
15250
  const workspaceRoot = rootInfo.mainRoot;
15089
15251
  const wtPath = rootInfo.repoRoot;
15090
- if (!wtPath.startsWith(`${worktreesDir(workspaceRoot)}${path75.sep}`)) {
15252
+ if (!wtPath.startsWith(`${worktreesDir(workspaceRoot)}${path76.sep}`)) {
15091
15253
  return {
15092
15254
  ok: false,
15093
15255
  error: "Current worktree is not one of this workspace's .claude/worktrees/<slug> checkouts."
@@ -15099,13 +15261,13 @@ async function runHydrateWorktree(opts) {
15099
15261
  return { ok: false, error: `Unknown --only repo(s): ${unknown.join(", ")}` };
15100
15262
  }
15101
15263
  const state = await readWorktreeState(wtPath);
15102
- const slug = state?.slug ?? path75.basename(wtPath);
15264
+ const slug = state?.slug ?? path76.basename(wtPath);
15103
15265
  const detectedBranch = await currentBranch(wtPath);
15104
15266
  const targetBranch = state?.branch ?? (detectedBranch !== "HEAD" ? detectedBranch : void 0) ?? slug;
15105
15267
  if (opts.dryRun) {
15106
15268
  const results2 = await Promise.all(
15107
15269
  selected.map(async (member) => {
15108
- const memberPath = path75.join(wtPath, member.folder);
15270
+ const memberPath = path76.join(wtPath, member.folder);
15109
15271
  if (existsSync7(memberPath)) {
15110
15272
  return { member: member.id, folder: member.folder, status: "planned" };
15111
15273
  }
@@ -15126,7 +15288,7 @@ async function runHydrateWorktree(opts) {
15126
15288
  const results = await mapWithConcurrency(
15127
15289
  selected,
15128
15290
  async (member) => {
15129
- const memberPath = path75.join(wtPath, member.folder);
15291
+ const memberPath = path76.join(wtPath, member.folder);
15130
15292
  let branchAction;
15131
15293
  if (!existsSync7(memberPath)) {
15132
15294
  if (!opts.materialize) {
@@ -15247,7 +15409,7 @@ function defaultSpawnDetached(spec) {
15247
15409
  }
15248
15410
  function appendLog(logPath, line2) {
15249
15411
  try {
15250
- fsSync.mkdirSync(path76.dirname(logPath), { recursive: true });
15412
+ fsSync.mkdirSync(path77.dirname(logPath), { recursive: true });
15251
15413
  const size = fsSync.statSync(logPath, { throwIfNoEntry: false })?.size ?? 0;
15252
15414
  if (size > LOG_SIZE_CAP_BYTES) {
15253
15415
  fsSync.writeFileSync(logPath, `--- log rotated (exceeded ${LOG_SIZE_CAP_BYTES} bytes) ---
@@ -15262,11 +15424,11 @@ async function consumeLastRunFailure(mainRoot, slug) {
15262
15424
  const file = hydrateLastRunPath(mainRoot, slug);
15263
15425
  let parsed;
15264
15426
  try {
15265
- parsed = JSON.parse(await fs55.readFile(file, "utf8"));
15427
+ parsed = JSON.parse(await fs56.readFile(file, "utf8"));
15266
15428
  } catch {
15267
15429
  return void 0;
15268
15430
  }
15269
- await fs55.remove(file).catch(() => void 0);
15431
+ await fs56.remove(file).catch(() => void 0);
15270
15432
  if (parsed && parsed.ok === false) return parsed;
15271
15433
  return void 0;
15272
15434
  }
@@ -15338,7 +15500,7 @@ async function runAutoHydrate(opts = {}, deps = {}) {
15338
15500
  reason: "could not determine the haus CLI entry point to re-invoke"
15339
15501
  };
15340
15502
  }
15341
- await fs55.ensureDir(logsDir(mainRoot));
15503
+ await fs56.ensureDir(logsDir(mainRoot));
15342
15504
  appendLog(
15343
15505
  logPath,
15344
15506
  `autohydrate: forking hydrate --materialize for "${slug}" (${missingMembers.join(", ")})`
@@ -15407,21 +15569,21 @@ async function runDetachedHydration() {
15407
15569
  async function writeLastRun(mainRoot, slug, ok, failures) {
15408
15570
  const file = hydrateLastRunPath(mainRoot, slug);
15409
15571
  if (ok) {
15410
- await fs55.remove(file).catch(() => void 0);
15572
+ await fs56.remove(file).catch(() => void 0);
15411
15573
  return;
15412
15574
  }
15413
15575
  const record = { slug, finishedAt: (/* @__PURE__ */ new Date()).toISOString(), ok, failures };
15414
- await fs55.ensureDir(path76.dirname(file));
15415
- await fs55.writeJson(file, record, { spaces: 2 }).catch(() => void 0);
15576
+ await fs56.ensureDir(path77.dirname(file));
15577
+ await fs56.writeJson(file, record, { spaces: 2 }).catch(() => void 0);
15416
15578
  }
15417
15579
 
15418
15580
  // src/workspace/worktree/dematerialize.ts
15419
- import path78 from "node:path";
15420
- import fs57 from "fs-extra";
15581
+ import path79 from "node:path";
15582
+ import fs58 from "fs-extra";
15421
15583
 
15422
15584
  // src/workspace/worktree/member-targets.ts
15423
- import path77 from "node:path";
15424
- import fs56 from "fs-extra";
15585
+ import path78 from "node:path";
15586
+ import fs57 from "fs-extra";
15425
15587
  function normalizeStateMembers(state, allMembers) {
15426
15588
  return state?.members.map((m) => ({ id: m.id, folder: m.folder, absPath: m.absPath })) ?? allMembers.map((m) => ({ id: m.id, folder: m.folder, absPath: m.absPath }));
15427
15589
  }
@@ -15438,7 +15600,7 @@ function resolveMemberTargets(wsWorktreePath, stateMembers, allMembers) {
15438
15600
  targets.push({
15439
15601
  repo: configMember?.id ?? stateMember.id,
15440
15602
  ownerRepo,
15441
- path: path77.join(wsWorktreePath, stateMember.folder)
15603
+ path: path78.join(wsWorktreePath, stateMember.folder)
15442
15604
  });
15443
15605
  }
15444
15606
  return { targets, unresolvedDroppedMembers };
@@ -15446,7 +15608,7 @@ function resolveMemberTargets(wsWorktreePath, stateMembers, allMembers) {
15446
15608
  async function filterExistingTargets(targets) {
15447
15609
  const existing = [];
15448
15610
  for (const target of targets) {
15449
- if (await fs56.pathExists(target.path)) existing.push(target);
15611
+ if (await fs57.pathExists(target.path)) existing.push(target);
15450
15612
  }
15451
15613
  return existing;
15452
15614
  }
@@ -15491,7 +15653,7 @@ async function runDematerialize(opts) {
15491
15653
  } catch (err) {
15492
15654
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
15493
15655
  }
15494
- if (!await fs57.pathExists(wsWorktreePath)) {
15656
+ if (!await fs58.pathExists(wsWorktreePath)) {
15495
15657
  return { ok: false, error: `No workspace worktree found at ${wsWorktreePath}.` };
15496
15658
  }
15497
15659
  const lock = await readHydrateLock(workspaceRoot, slug);
@@ -15524,9 +15686,9 @@ async function runDematerialize(opts) {
15524
15686
  const removed = [];
15525
15687
  const failed = [];
15526
15688
  for (const folder of unresolvedDroppedMembers) {
15527
- const droppedPath = path78.join(wsWorktreePath, folder);
15528
- if (await fs57.pathExists(droppedPath)) {
15529
- await fs57.remove(droppedPath);
15689
+ const droppedPath = path79.join(wsWorktreePath, folder);
15690
+ if (await fs58.pathExists(droppedPath)) {
15691
+ await fs58.remove(droppedPath);
15530
15692
  failed.push({ repo: folder, error: unresolvedDroppedFailureReason() });
15531
15693
  }
15532
15694
  }
@@ -15540,26 +15702,26 @@ async function runDematerialize(opts) {
15540
15702
  }
15541
15703
 
15542
15704
  // src/workspace/worktree/list.ts
15543
- import path79 from "node:path";
15544
- import fs58 from "fs-extra";
15705
+ import path80 from "node:path";
15706
+ import fs59 from "fs-extra";
15545
15707
  async function runList2() {
15546
15708
  const resolved = await resolveWorkspaceForWorktree();
15547
15709
  if (!resolved.ok) return { ok: false, error: resolved.reason };
15548
15710
  const { rootInfo, workspaceRoot } = resolved;
15549
15711
  const dir = worktreesDir(workspaceRoot);
15550
- if (!await fs58.pathExists(dir)) return { ok: true, workspaceRoot, worktrees: [] };
15551
- const entries = await fs58.readdir(dir, { withFileTypes: true });
15712
+ if (!await fs59.pathExists(dir)) return { ok: true, workspaceRoot, worktrees: [] };
15713
+ const entries = await fs59.readdir(dir, { withFileTypes: true });
15552
15714
  const slugs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
15553
15715
  const configuredMembers = await readMembers(rootInfo);
15554
15716
  const worktrees = [];
15555
15717
  for (const slug of slugs) {
15556
- const wtPath = path79.join(dir, slug);
15718
+ const wtPath = path80.join(dir, slug);
15557
15719
  const state = await readWorktreeState(wtPath);
15558
15720
  const members = [];
15559
15721
  const source = state?.members ?? configuredMembers.map((m) => ({ id: m.id, folder: m.folder }));
15560
15722
  for (const m of source) {
15561
- const memberPath = path79.join(wtPath, m.folder);
15562
- const materialized = await fs58.pathExists(memberPath);
15723
+ const memberPath = path80.join(wtPath, m.folder);
15724
+ const materialized = await fs59.pathExists(memberPath);
15563
15725
  const actualBranch = materialized ? await currentBranch(memberPath) : void 0;
15564
15726
  members.push({
15565
15727
  id: m.id,
@@ -15581,23 +15743,23 @@ async function runList2() {
15581
15743
 
15582
15744
  // src/workspace/worktree/reclaim.ts
15583
15745
  import { existsSync as existsSync8 } from "node:fs";
15584
- import path80 from "node:path";
15585
- import fs59 from "fs-extra";
15746
+ import path81 from "node:path";
15747
+ import fs60 from "fs-extra";
15586
15748
  var MS_PER_DAY = 24 * 60 * 60 * 1e3;
15587
15749
  async function lastTouchedMs(worktreeRoot) {
15588
- const candidates = [worktreeRoot, path80.join(worktreeRoot, ".haus-worktree.json")];
15750
+ const candidates = [worktreeRoot, path81.join(worktreeRoot, ".haus-worktree.json")];
15589
15751
  try {
15590
- const dotGit = await fs59.readFile(path80.join(worktreeRoot, ".git"), "utf8");
15752
+ const dotGit = await fs60.readFile(path81.join(worktreeRoot, ".git"), "utf8");
15591
15753
  const match = /^gitdir:\s*(.+)$/m.exec(dotGit);
15592
15754
  if (match?.[1]) {
15593
15755
  const gitdir = match[1].trim();
15594
- candidates.push(path80.join(gitdir, "HEAD"), path80.join(gitdir, "index"));
15756
+ candidates.push(path81.join(gitdir, "HEAD"), path81.join(gitdir, "index"));
15595
15757
  }
15596
15758
  } catch {
15597
15759
  }
15598
15760
  let newest = 0;
15599
15761
  for (const candidate of candidates) {
15600
- const stat = await fs59.stat(candidate).catch(() => void 0);
15762
+ const stat = await fs60.stat(candidate).catch(() => void 0);
15601
15763
  if (stat && stat.mtimeMs > newest) newest = stat.mtimeMs;
15602
15764
  }
15603
15765
  return newest;
@@ -15624,7 +15786,7 @@ async function runReclaim(opts = {}) {
15624
15786
  if (!existsSync8(dir)) {
15625
15787
  return { ok: true, dryRun: !opts.write, olderThanDays, candidates: [], reclaimed: 0 };
15626
15788
  }
15627
- const allEntries = (await fs59.readdir(dir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name).sort();
15789
+ const allEntries = (await fs60.readdir(dir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name).sort();
15628
15790
  const entries = allEntries.filter((name) => isValidWorktreeSlug(name));
15629
15791
  const unaddressable = allEntries.filter((name) => !isValidWorktreeSlug(name));
15630
15792
  const members = await readMembers(rootInfo);
@@ -15634,17 +15796,17 @@ async function runReclaim(opts = {}) {
15634
15796
  for (const name of unaddressable) {
15635
15797
  candidates.push({
15636
15798
  slug: name,
15637
- ageDays: (Date.now() - await lastTouchedMs(path80.join(dir, name))) / MS_PER_DAY,
15799
+ ageDays: (Date.now() - await lastTouchedMs(path81.join(dir, name))) / MS_PER_DAY,
15638
15800
  materializedMembers: 0,
15639
15801
  action: "skip",
15640
15802
  reason: "directory name is not a valid worktree slug \u2014 no haus worktree command can address it; remove it by hand if it is debris"
15641
15803
  });
15642
15804
  }
15643
15805
  for (const slug of entries) {
15644
- const wtPath = path80.join(dir, slug);
15806
+ const wtPath = path81.join(dir, slug);
15645
15807
  const state = await readWorktreeState(wtPath);
15646
15808
  const stateMembers = normalizeStateMembers(state, members);
15647
- const materialized = stateMembers.filter((m) => existsSync8(path80.join(wtPath, m.folder)));
15809
+ const materialized = stateMembers.filter((m) => existsSync8(path81.join(wtPath, m.folder)));
15648
15810
  const ageDays = (Date.now() - await lastTouchedMs(wtPath)) / MS_PER_DAY;
15649
15811
  const base = { slug, ageDays, materializedMembers: materialized.length };
15650
15812
  if (slug === currentSlug) {
@@ -15705,13 +15867,13 @@ async function runReclaim(opts = {}) {
15705
15867
  }
15706
15868
 
15707
15869
  // src/workspace/worktree/remove.ts
15708
- import fs60 from "fs-extra";
15870
+ import fs61 from "fs-extra";
15709
15871
  async function runRemove2(opts) {
15710
15872
  const resolved = await resolveWorkspaceForWorktree();
15711
15873
  if (!resolved.ok) return { ok: false, error: resolved.reason };
15712
15874
  const { rootInfo, workspaceRoot } = resolved;
15713
15875
  const wsWorktreePath = worktreePath(workspaceRoot, opts.slug);
15714
- if (!await fs60.pathExists(wsWorktreePath)) {
15876
+ if (!await fs61.pathExists(wsWorktreePath)) {
15715
15877
  return { ok: false, error: `No workspace worktree found at ${wsWorktreePath}.` };
15716
15878
  }
15717
15879
  const state = await readWorktreeState(wsWorktreePath);
@@ -15779,8 +15941,8 @@ async function runRemove2(opts) {
15779
15941
  }
15780
15942
  await pruneWorktrees(workspaceRoot);
15781
15943
  }
15782
- if (wsRemoveOk && await fs60.pathExists(wsWorktreePath)) {
15783
- await fs60.remove(wsWorktreePath);
15944
+ if (wsRemoveOk && await fs61.pathExists(wsWorktreePath)) {
15945
+ await fs61.remove(wsWorktreePath);
15784
15946
  }
15785
15947
  return { ok: failed.length === 0, dryRun: false, slug: opts.slug, removed, failed };
15786
15948
  }
@@ -16241,7 +16403,7 @@ async function runWorktree(action, opts = {}) {
16241
16403
 
16242
16404
  // src/commands/workspace.ts
16243
16405
  import { existsSync as existsSync12, statSync as statSync3 } from "node:fs";
16244
- import path89 from "node:path";
16406
+ import path90 from "node:path";
16245
16407
  init_fs();
16246
16408
  init_logger();
16247
16409
 
@@ -16300,7 +16462,7 @@ ${summaries.map(
16300
16462
 
16301
16463
  // src/commands/workspace/doctor.ts
16302
16464
  import { existsSync as existsSync9 } from "node:fs";
16303
- import path82 from "node:path";
16465
+ import path83 from "node:path";
16304
16466
  init_logger();
16305
16467
  init_paths();
16306
16468
 
@@ -16308,14 +16470,14 @@ init_paths();
16308
16470
  init_fs();
16309
16471
  init_paths();
16310
16472
  import { readFileSync as readFileSync4 } from "node:fs";
16311
- import path81 from "node:path";
16473
+ import path82 from "node:path";
16312
16474
  var MANIFEST_FILE2 = "workspace.manifest.json";
16313
16475
  function manifestPath(workspaceRoot) {
16314
16476
  return hausPath(workspaceRoot, MANIFEST_FILE2);
16315
16477
  }
16316
16478
  function hausVersion() {
16317
16479
  try {
16318
- const pkg = JSON.parse(readFileSync4(path81.join(packageRoot(), "package.json"), "utf8"));
16480
+ const pkg = JSON.parse(readFileSync4(path82.join(packageRoot(), "package.json"), "utf8"));
16319
16481
  return pkg.version ?? "0.0.0";
16320
16482
  } catch {
16321
16483
  return "0.0.0";
@@ -16407,7 +16569,7 @@ async function runWorkspaceDoctor(workspaceRoot, opts = {}) {
16407
16569
  const manifestByName = new Map(manifest.repos.map((r) => [r.name, r]));
16408
16570
  const catalogRefByRepo = [];
16409
16571
  for (const repo of config.repos) {
16410
- const repoRoot = path82.resolve(workspaceRoot, repo.path);
16572
+ const repoRoot = path83.resolve(workspaceRoot, repo.path);
16411
16573
  const entry = manifestByName.get(repo.name);
16412
16574
  if (!entry) {
16413
16575
  flag({
@@ -16480,7 +16642,7 @@ async function runWorkspaceDoctor(workspaceRoot, opts = {}) {
16480
16642
  });
16481
16643
  continue;
16482
16644
  }
16483
- if (!existsSync9(path82.join(workspaceRoot, entry.path))) {
16645
+ if (!existsSync9(path83.join(workspaceRoot, entry.path))) {
16484
16646
  flag({
16485
16647
  repo: entry.repo,
16486
16648
  kind: "missing-linked-context-copy",
@@ -16526,18 +16688,18 @@ function emit(args) {
16526
16688
  init_logger();
16527
16689
 
16528
16690
  // src/workspace/link-context/link.ts
16529
- import path85 from "node:path";
16530
- import fs63 from "fs-extra";
16691
+ import path86 from "node:path";
16692
+ import fs64 from "fs-extra";
16531
16693
  init_fs();
16532
16694
  init_logger();
16533
16695
 
16534
16696
  // src/workspace/link-context/plan.ts
16535
- import path84 from "node:path";
16536
- import fs62 from "fs-extra";
16697
+ import path85 from "node:path";
16698
+ import fs63 from "fs-extra";
16537
16699
 
16538
16700
  // src/workspace/link-context/scan-source.ts
16539
- import path83 from "node:path";
16540
- import fs61 from "fs-extra";
16701
+ import path84 from "node:path";
16702
+ import fs62 from "fs-extra";
16541
16703
  var ASSET_SUBDIR = {
16542
16704
  skill: "skills",
16543
16705
  agent: "agents",
@@ -16545,36 +16707,36 @@ var ASSET_SUBDIR = {
16545
16707
  };
16546
16708
  async function hasLinkableContext(member) {
16547
16709
  for (const subdir of Object.values(ASSET_SUBDIR)) {
16548
- if (await fs61.pathExists(path83.join(member.absPath, ".claude", subdir))) return true;
16710
+ if (await fs62.pathExists(path84.join(member.absPath, ".claude", subdir))) return true;
16549
16711
  }
16550
16712
  return false;
16551
16713
  }
16552
16714
  async function collectSkills(member) {
16553
- const dir = path83.join(member.absPath, ".claude", "skills");
16554
- if (!await fs61.pathExists(dir)) return [];
16555
- const names = await fs61.readdir(dir);
16715
+ const dir = path84.join(member.absPath, ".claude", "skills");
16716
+ if (!await fs62.pathExists(dir)) return [];
16717
+ const names = await fs62.readdir(dir);
16556
16718
  const out = [];
16557
16719
  for (const name of [...names].sort()) {
16558
- const abs = path83.join(dir, name);
16559
- const stat = await fs61.lstat(abs);
16720
+ const abs = path84.join(dir, name);
16721
+ const stat = await fs62.lstat(abs);
16560
16722
  if (!stat.isDirectory()) continue;
16561
- if (!await fs61.pathExists(path83.join(abs, "SKILL.md"))) continue;
16562
- out.push({ type: "skill", name, sourceRelPath: path83.posix.join(".claude", "skills", name) });
16723
+ if (!await fs62.pathExists(path84.join(abs, "SKILL.md"))) continue;
16724
+ out.push({ type: "skill", name, sourceRelPath: path84.posix.join(".claude", "skills", name) });
16563
16725
  }
16564
16726
  return out;
16565
16727
  }
16566
16728
  async function collectFlatMarkdown(member, type) {
16567
16729
  const subdir = ASSET_SUBDIR[type];
16568
- const dir = path83.join(member.absPath, ".claude", subdir);
16569
- if (!await fs61.pathExists(dir)) return [];
16570
- const names = await fs61.readdir(dir);
16730
+ const dir = path84.join(member.absPath, ".claude", subdir);
16731
+ if (!await fs62.pathExists(dir)) return [];
16732
+ const names = await fs62.readdir(dir);
16571
16733
  const out = [];
16572
16734
  for (const fileName of [...names].sort()) {
16573
16735
  if (!fileName.endsWith(".md")) continue;
16574
- const abs = path83.join(dir, fileName);
16575
- if (!(await fs61.lstat(abs)).isFile()) continue;
16736
+ const abs = path84.join(dir, fileName);
16737
+ if (!(await fs62.lstat(abs)).isFile()) continue;
16576
16738
  const name = fileName.slice(0, -".md".length);
16577
- out.push({ type, name, sourceRelPath: path83.posix.join(".claude", subdir, fileName) });
16739
+ out.push({ type, name, sourceRelPath: path84.posix.join(".claude", subdir, fileName) });
16578
16740
  }
16579
16741
  return out;
16580
16742
  }
@@ -16589,9 +16751,9 @@ async function collectSourceAssets(member) {
16589
16751
 
16590
16752
  // src/workspace/link-context/plan.ts
16591
16753
  function repoFolderPrefix(member) {
16592
- const base = path84.posix.basename(member.folder);
16754
+ const base = path85.posix.basename(member.folder);
16593
16755
  if (base && base !== "." && base !== "/") return base;
16594
- const fromAbs = path84.basename(member.absPath);
16756
+ const fromAbs = path85.basename(member.absPath);
16595
16757
  return fromAbs || member.id;
16596
16758
  }
16597
16759
  function assetKey(type, destKey) {
@@ -16606,7 +16768,7 @@ async function buildLinkPlan(members) {
16606
16768
  const byKey = /* @__PURE__ */ new Map();
16607
16769
  const membersById = new Map(members.map((m) => [m.id, m]));
16608
16770
  for (const member of members) {
16609
- if (!await fs62.pathExists(member.absPath)) {
16771
+ if (!await fs63.pathExists(member.absPath)) {
16610
16772
  skipped.push({ repo: member.id, reason: `not cloned locally (${member.absPath}) \u2014 skipped` });
16611
16773
  continue;
16612
16774
  }
@@ -16648,7 +16810,7 @@ async function buildLinkPlan(members) {
16648
16810
  name: asset.name,
16649
16811
  destKey,
16650
16812
  sourceRelPath: asset.sourceRelPath,
16651
- sourceAbsPath: path84.join(member.absPath, asset.sourceRelPath),
16813
+ sourceAbsPath: path85.join(member.absPath, asset.sourceRelPath),
16652
16814
  sourceHash
16653
16815
  });
16654
16816
  }
@@ -16659,7 +16821,7 @@ async function buildLinkPlan(members) {
16659
16821
  function destRelPathFor(type, destKey) {
16660
16822
  const subdir = `${type}s`;
16661
16823
  const leaf = type === "skill" ? destKey : `${destKey}.md`;
16662
- return path85.posix.join(".claude", subdir, leaf);
16824
+ return path86.posix.join(".claude", subdir, leaf);
16663
16825
  }
16664
16826
  async function runLinkContext(workspaceRoot, opts = {}) {
16665
16827
  const write = resolveWriteMode(opts, "preview-by-default").kind === "write";
@@ -16685,17 +16847,17 @@ async function runLinkContext(workspaceRoot, opts = {}) {
16685
16847
  const linkedAt = (/* @__PURE__ */ new Date()).toISOString();
16686
16848
  for (const entry of plan.entries) {
16687
16849
  const destRel = destRelPathFor(entry.type, entry.destKey);
16688
- const destAbs = path85.join(workspaceRoot, destRel);
16850
+ const destAbs = path86.join(workspaceRoot, destRel);
16689
16851
  if (write) {
16690
- await fs63.ensureDir(path85.dirname(destAbs));
16691
- if (await fs63.pathExists(destAbs)) await fs63.remove(destAbs);
16692
- await fs63.copy(entry.sourceAbsPath, destAbs, {
16852
+ await fs64.ensureDir(path86.dirname(destAbs));
16853
+ if (await fs64.pathExists(destAbs)) await fs64.remove(destAbs);
16854
+ await fs64.copy(entry.sourceAbsPath, destAbs, {
16693
16855
  overwrite: true,
16694
16856
  errorOnExist: false,
16695
16857
  dereference: false,
16696
16858
  filter: (s) => {
16697
16859
  try {
16698
- return !fs63.lstatSync(s).isSymbolicLink();
16860
+ return !fs64.lstatSync(s).isSymbolicLink();
16699
16861
  } catch {
16700
16862
  return false;
16701
16863
  }
@@ -16724,7 +16886,7 @@ async function runLinkContext(workspaceRoot, opts = {}) {
16724
16886
  warn(`Refusing to remove linked context path outside the workspace root: ${old.path}`);
16725
16887
  continue;
16726
16888
  }
16727
- if (await fs63.pathExists(abs)) await fs63.remove(abs);
16889
+ if (await fs64.pathExists(abs)) await fs64.remove(abs);
16728
16890
  }
16729
16891
  }
16730
16892
  if (write) {
@@ -16776,11 +16938,11 @@ async function runWorkspaceLinkContext(workspaceRoot, opts = {}) {
16776
16938
 
16777
16939
  // src/commands/workspace/setup.ts
16778
16940
  import { existsSync as existsSync10, statSync } from "node:fs";
16779
- import path87 from "node:path";
16941
+ import path88 from "node:path";
16780
16942
 
16781
16943
  // src/claude/write-workspace-claude-md.ts
16782
- import path86 from "node:path";
16783
- import fs64 from "fs-extra";
16944
+ import path87 from "node:path";
16945
+ import fs65 from "fs-extra";
16784
16946
  init_fs();
16785
16947
  init_logger();
16786
16948
  init_paths();
@@ -16805,11 +16967,11 @@ ${WORKSPACE_BLOCK_END}`;
16805
16967
  async function writeWorkspaceClaudeMd(workspaceRoot, opts) {
16806
16968
  const block = buildWorkspaceImportBlock(opts.client, opts.members);
16807
16969
  const dryRun = opts.dryRun ?? false;
16808
- const filePath = opts.collision ? hausPath(workspaceRoot, "WORKSPACE.md") : path86.join(workspaceRoot, "CLAUDE.md");
16970
+ const filePath = opts.collision ? hausPath(workspaceRoot, "WORKSPACE.md") : path87.join(workspaceRoot, "CLAUDE.md");
16809
16971
  const say = opts.quiet ? () => {
16810
16972
  } : log;
16811
- const existed = await fs64.pathExists(filePath);
16812
- const prev = existed ? await fs64.readFile(filePath, "utf8") : "";
16973
+ const existed = await fs65.pathExists(filePath);
16974
+ const prev = existed ? await fs65.readFile(filePath, "utf8") : "";
16813
16975
  const next = opts.collision ? `${block}
16814
16976
  ` : injectHausBlock(prev, block, WORKSPACE_SENTINELS);
16815
16977
  const printable = displayPath(workspaceRoot, filePath);
@@ -16838,20 +17000,20 @@ async function writeWorkspaceClaudeMd(workspaceRoot, opts) {
16838
17000
  init_fs();
16839
17001
  init_logger();
16840
17002
  function resolveWorkspaceRoot(start = process.cwd()) {
16841
- let dir = path87.resolve(start);
17003
+ let dir = path88.resolve(start);
16842
17004
  for (; ; ) {
16843
- if (existsSync10(path87.join(dir, WORKSPACE_FILE))) return dir;
16844
- const parent = path87.dirname(dir);
16845
- if (parent === dir) return path87.resolve(start);
17005
+ if (existsSync10(path88.join(dir, WORKSPACE_FILE))) return dir;
17006
+ const parent = path88.dirname(dir);
17007
+ if (parent === dir) return path88.resolve(start);
16846
17008
  dir = parent;
16847
17009
  }
16848
17010
  }
16849
17011
  function isRootRepo(workspaceRoot, repoPath) {
16850
- return path87.resolve(workspaceRoot, repoPath) === path87.resolve(workspaceRoot);
17012
+ return path88.resolve(workspaceRoot, repoPath) === path88.resolve(workspaceRoot);
16851
17013
  }
16852
17014
  async function runWorkspaceSetup(workspaceRoot, options = {}) {
16853
17015
  const apply = options.write ?? false;
16854
- const configText = await readText(path87.join(workspaceRoot, WORKSPACE_FILE));
17016
+ const configText = await readText(path88.join(workspaceRoot, WORKSPACE_FILE));
16855
17017
  if (!configText) {
16856
17018
  error(`Missing ${WORKSPACE_FILE}. Run \`haus workspace discover\` or \`init\` first.`);
16857
17019
  process.exitCode = 1;
@@ -16878,7 +17040,7 @@ async function runWorkspaceSetup(workspaceRoot, options = {}) {
16878
17040
  const statuses = [];
16879
17041
  const aggregateInputs = [];
16880
17042
  for (const repo of repos) {
16881
- const repoRoot = path87.resolve(workspaceRoot, repo.path);
17043
+ const repoRoot = path88.resolve(workspaceRoot, repo.path);
16882
17044
  say(`
16883
17045
  \u2192 ${repo.name} (${repo.path})`);
16884
17046
  let res;
@@ -16994,7 +17156,7 @@ async function runWorkspaceSetup(workspaceRoot, options = {}) {
16994
17156
  const status = statusByName.get(repo.name);
16995
17157
  const role = repo.role ?? status?.roles?.[0] ?? "auto";
16996
17158
  if (status?.status === "ok") {
16997
- const lock = await checkLock(path87.resolve(workspaceRoot, repo.path));
17159
+ const lock = await checkLock(path88.resolve(workspaceRoot, repo.path));
16998
17160
  manifestRepos.push({
16999
17161
  name: repo.name,
17000
17162
  path: repo.path,
@@ -17050,7 +17212,7 @@ async function runWorkspaceSetup(workspaceRoot, options = {}) {
17050
17212
  try {
17051
17213
  const linkResult = await runLinkContext(workspaceRoot, { write: true, json: options.json });
17052
17214
  if (linkResult.ok) {
17053
- written.push(...linkResult.added.map((rel) => path87.join(workspaceRoot, rel)));
17215
+ written.push(...linkResult.added.map((rel) => path88.join(workspaceRoot, rel)));
17054
17216
  if (!options.json) {
17055
17217
  say(
17056
17218
  `Linked ${linkResult.linked.length} cross-repo skill/agent/command copy(ies) into .claude/ (${linkResult.added.length} new, ${linkResult.removed.length} removed)${linkResult.skipped.length > 0 ? ` \u2014 ${linkResult.skipped.length} member(s) skipped, see above` : ""}.`
@@ -17083,8 +17245,8 @@ Workspace setup complete: ${ok} ok, ${failed} failed. Wrote ${written.length} fi
17083
17245
 
17084
17246
  // src/commands/workspace/undo.ts
17085
17247
  import { existsSync as existsSync11, statSync as statSync2 } from "node:fs";
17086
- import path88 from "node:path";
17087
- import fs65 from "fs-extra";
17248
+ import path89 from "node:path";
17249
+ import fs66 from "fs-extra";
17088
17250
  init_fs();
17089
17251
  init_logger();
17090
17252
  init_paths();
@@ -17114,7 +17276,7 @@ async function runWorkspaceUndo(workspaceRoot, options = {}) {
17114
17276
  }
17115
17277
  if (options.dryRun) {
17116
17278
  for (const repo of config.repos) {
17117
- const repoRoot = path88.resolve(workspaceRoot, repo.path);
17279
+ const repoRoot = path89.resolve(workspaceRoot, repo.path);
17118
17280
  log(`
17119
17281
  \u2192 ${repo.name} (${repo.path})`);
17120
17282
  try {
@@ -17135,7 +17297,7 @@ async function runWorkspaceUndo(workspaceRoot, options = {}) {
17135
17297
  warn(`Refusing to remove linked context path outside the workspace root: ${entry.path}`);
17136
17298
  continue;
17137
17299
  }
17138
- if (await fs65.pathExists(abs)) {
17300
+ if (await fs66.pathExists(abs)) {
17139
17301
  log(`[dry-run] Would remove ${entry.path} (linked ${entry.type} from ${entry.repo})`);
17140
17302
  }
17141
17303
  }
@@ -17145,12 +17307,12 @@ async function runWorkspaceUndo(workspaceRoot, options = {}) {
17145
17307
  hausPath(workspaceRoot, "WORKSPACE.md")
17146
17308
  ];
17147
17309
  for (const abs of workspaceArtifacts) {
17148
- if (await fs65.pathExists(abs)) {
17149
- log(`[dry-run] Would remove ${path88.relative(workspaceRoot, abs)}`);
17310
+ if (await fs66.pathExists(abs)) {
17311
+ log(`[dry-run] Would remove ${path89.relative(workspaceRoot, abs)}`);
17150
17312
  }
17151
17313
  }
17152
- if (!await fs65.pathExists(hausPath(workspaceRoot, "WORKSPACE.md"))) {
17153
- const claudeMd = await readText(path88.join(workspaceRoot, "CLAUDE.md"));
17314
+ if (!await fs66.pathExists(hausPath(workspaceRoot, "WORKSPACE.md"))) {
17315
+ const claudeMd = await readText(path89.join(workspaceRoot, "CLAUDE.md"));
17154
17316
  if (claudeMd?.includes(WORKSPACE_BLOCK_BEGIN) || claudeMd && hasLegacyWorkspaceBlock(claudeMd)) {
17155
17317
  log("[dry-run] Would remove the workspace haus import block from CLAUDE.md");
17156
17318
  }
@@ -17169,7 +17331,7 @@ async function runWorkspaceUndo(workspaceRoot, options = {}) {
17169
17331
  }
17170
17332
  let anyRepoFailed = false;
17171
17333
  for (const repo of config.repos) {
17172
- const repoRoot = path88.resolve(workspaceRoot, repo.path);
17334
+ const repoRoot = path89.resolve(workspaceRoot, repo.path);
17173
17335
  log(`
17174
17336
  \u2192 ${repo.name} (${repo.path})`);
17175
17337
  try {
@@ -17189,29 +17351,29 @@ async function runWorkspaceUndo(workspaceRoot, options = {}) {
17189
17351
  warn(`Refusing to remove linked context path outside the workspace root: ${entry.path}`);
17190
17352
  continue;
17191
17353
  }
17192
- if (await fs65.pathExists(abs)) {
17193
- await fs65.remove(abs);
17354
+ if (await fs66.pathExists(abs)) {
17355
+ await fs66.remove(abs);
17194
17356
  log(`Removed ${entry.path} (linked ${entry.type} from ${entry.repo})`);
17195
17357
  }
17196
17358
  }
17197
17359
  for (const rel of WORKSPACE_AGGREGATE_FILES) {
17198
17360
  const abs = hausPath(workspaceRoot, rel);
17199
- if (await fs65.pathExists(abs)) {
17200
- await fs65.remove(abs);
17201
- log(`Removed ${path88.relative(workspaceRoot, abs)}`);
17361
+ if (await fs66.pathExists(abs)) {
17362
+ await fs66.remove(abs);
17363
+ log(`Removed ${path89.relative(workspaceRoot, abs)}`);
17202
17364
  }
17203
17365
  }
17204
17366
  const manifestFile = manifestPath(workspaceRoot);
17205
- if (await fs65.pathExists(manifestFile)) {
17206
- await fs65.remove(manifestFile);
17207
- log(`Removed ${path88.relative(workspaceRoot, manifestFile)}`);
17367
+ if (await fs66.pathExists(manifestFile)) {
17368
+ await fs66.remove(manifestFile);
17369
+ log(`Removed ${path89.relative(workspaceRoot, manifestFile)}`);
17208
17370
  }
17209
17371
  const workspaceMdPath = hausPath(workspaceRoot, "WORKSPACE.md");
17210
- if (await fs65.pathExists(workspaceMdPath)) {
17211
- await fs65.remove(workspaceMdPath);
17212
- log(`Removed ${path88.relative(workspaceRoot, workspaceMdPath)}`);
17372
+ if (await fs66.pathExists(workspaceMdPath)) {
17373
+ await fs66.remove(workspaceMdPath);
17374
+ log(`Removed ${path89.relative(workspaceRoot, workspaceMdPath)}`);
17213
17375
  } else {
17214
- const claudeMdPath = path88.join(workspaceRoot, "CLAUDE.md");
17376
+ const claudeMdPath = path89.join(workspaceRoot, "CLAUDE.md");
17215
17377
  if (existsSync11(claudeMdPath)) {
17216
17378
  const prev = await readText(claudeMdPath);
17217
17379
  if (prev?.includes(WORKSPACE_BLOCK_BEGIN)) {
@@ -17220,19 +17382,19 @@ async function runWorkspaceUndo(workspaceRoot, options = {}) {
17220
17382
  end: WORKSPACE_BLOCK_END
17221
17383
  });
17222
17384
  if (next.length === 0) {
17223
- await fs65.remove(claudeMdPath);
17385
+ await fs66.remove(claudeMdPath);
17224
17386
  log("Removed CLAUDE.md (only contained the workspace haus import block).");
17225
17387
  } else {
17226
- await fs65.writeFile(claudeMdPath, next, "utf8");
17388
+ await fs66.writeFile(claudeMdPath, next, "utf8");
17227
17389
  log("Removed haus import block from CLAUDE.md (user content preserved).");
17228
17390
  }
17229
17391
  } else if (prev && hasLegacyWorkspaceBlock(prev)) {
17230
17392
  const next = stripHausBlock(prev, { begin: BLOCK_BEGIN, end: BLOCK_END });
17231
17393
  if (next.length === 0) {
17232
- await fs65.remove(claudeMdPath);
17394
+ await fs66.remove(claudeMdPath);
17233
17395
  log("Removed CLAUDE.md (only contained the pre-upgrade workspace haus import block).");
17234
17396
  } else {
17235
- await fs65.writeFile(claudeMdPath, next, "utf8");
17397
+ await fs66.writeFile(claudeMdPath, next, "utf8");
17236
17398
  log(
17237
17399
  "Removed pre-upgrade workspace haus import block from CLAUDE.md (user content preserved)."
17238
17400
  );
@@ -17280,7 +17442,7 @@ relationships: []
17280
17442
  log("Workspace initialized.");
17281
17443
  }
17282
17444
  async function scanWorkspace(workspaceRoot, opts) {
17283
- const configText = await readText(path89.join(workspaceRoot, WORKSPACE_FILE));
17445
+ const configText = await readText(path90.join(workspaceRoot, WORKSPACE_FILE));
17284
17446
  if (!configText) {
17285
17447
  error(`Missing ${WORKSPACE_FILE}. Run \`haus workspace discover\` or \`init\` first.`);
17286
17448
  process.exitCode = 1;
@@ -17302,7 +17464,7 @@ async function scanWorkspace(workspaceRoot, opts) {
17302
17464
  const inputs = [];
17303
17465
  const failures = [];
17304
17466
  for (const repo of config.repos) {
17305
- const repoRoot = path89.resolve(workspaceRoot, repo.path);
17467
+ const repoRoot = path90.resolve(workspaceRoot, repo.path);
17306
17468
  try {
17307
17469
  if (!existsSync12(repoRoot) || !statSync3(repoRoot).isDirectory()) {
17308
17470
  throw new Error("path is not a directory");
@@ -17371,7 +17533,7 @@ init_logger();
17371
17533
  init_paths();
17372
17534
  function cliVersion() {
17373
17535
  try {
17374
- const pkgPath = path90.join(packageRoot(), "package.json");
17536
+ const pkgPath = path91.join(packageRoot(), "package.json");
17375
17537
  const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
17376
17538
  return pkg.version ?? "0.0.0";
17377
17539
  } catch {
@@ -17381,7 +17543,7 @@ function cliVersion() {
17381
17543
  var program = new Command();
17382
17544
  function validateRuntimeNodeVersion() {
17383
17545
  try {
17384
- const pkgPath = path90.join(packageRoot(), "package.json");
17546
+ const pkgPath = path91.join(packageRoot(), "package.json");
17385
17547
  const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
17386
17548
  const requiredRange = pkg.engines?.node;
17387
17549
  if (requiredRange && !satisfiesVersion(process.version, requiredRange)) {